瀏覽代碼

refactor(web): Allow dropdown for more general use (#4515)

Daniel Dietzler 1 年之前
父節點
當前提交
5a7ef02387
共有 2 個文件被更改,包括 57 次插入28 次删除
  1. 48 24
      web/src/lib/components/elements/dropdown.svelte
  2. 9 4
      web/src/routes/(user)/albums/+page.svelte

+ 48 - 24
web/src/lib/components/elements/dropdown.svelte

@@ -1,17 +1,31 @@
-<script lang="ts">
-  import Check from 'svelte-material-icons/Check.svelte';
+<script lang="ts" context="module">
+  // Necessary for eslint
+  /* eslint-disable @typescript-eslint/no-explicit-any */
+  type T = any;
+</script>
+
+<script lang="ts" generics="T">
+  import _ from 'lodash';
   import LinkButton from './buttons/link-button.svelte';
   import { clickOutside } from '$lib/utils/click-outside';
   import { fly } from 'svelte/transition';
   import type Icon from 'svelte-material-icons/DotsVertical.svelte';
+  import Check from 'svelte-material-icons/Check.svelte';
   import { createEventDispatcher } from 'svelte';
 
   const dispatch = createEventDispatcher<{
-    select: string;
+    select: T;
   }>();
-  export let options: string[];
-  export let value = options[0];
-  export let icons: (typeof Icon)[] | undefined = undefined;
+
+  export let options: T[];
+  export let selectedOption = options[0];
+
+  export let render: (item: T) => string | RenderedOption = (item) => String(item);
+
+  type RenderedOption = {
+    title: string;
+    icon?: typeof Icon;
+  };
 
   let showMenu = false;
 
@@ -19,28 +33,37 @@
     showMenu = false;
   };
 
-  const handleSelectOption = (index: number) => {
-    if (options[index] === value) {
-      dispatch('select', value);
-    } else {
-      value = options[index];
-    }
+  const handleSelectOption = (option: T) => {
+    dispatch('select', option);
+    selectedOption = option;
 
     showMenu = false;
   };
 
-  $: index = options.findIndex((option) => option === value);
-  $: icon = icons?.[index];
+  const renderOption = (option: T): RenderedOption => {
+    const renderedOption = render(option);
+    switch (typeof renderedOption) {
+      case 'string':
+        return { title: renderedOption };
+      default:
+        return {
+          title: renderedOption.title,
+          icon: renderedOption.icon,
+        };
+    }
+  };
+
+  $: renderedSelectedOption = renderOption(selectedOption);
 </script>
 
 <div id="dropdown-button" use:clickOutside on:outclick={handleClickOutside} on:escape={handleClickOutside}>
   <!-- BUTTON TITLE -->
   <LinkButton on:click={() => (showMenu = true)}>
     <div class="flex place-items-center gap-2 text-sm">
-      {#if icon}
-        <svelte:component this={icon} size="18" />
+      {#if renderedSelectedOption?.icon}
+        <svelte:component this={renderedSelectedOption.icon} size="18" />
       {/if}
-      <p class="hidden sm:block">{value}</p>
+      <p class="hidden sm:block">{renderedSelectedOption.title}</p>
     </div>
   </LinkButton>
 
@@ -50,22 +73,23 @@
       transition:fly={{ y: -30, x: 30, duration: 200 }}
       class="text-md absolute right-0 top-5 z-50 flex min-w-[250px] flex-col rounded-2xl bg-gray-100 py-4 text-black shadow-lg dark:bg-gray-700 dark:text-white"
     >
-      {#each options as option, index (option)}
+      {#each options as option (option)}
+        {@const renderedOption = renderOption(option)}
         <button
           class="grid grid-cols-[20px,1fr] place-items-center gap-2 p-4 transition-all hover:bg-gray-300 dark:hover:bg-gray-800"
-          on:click={() => handleSelectOption(index)}
+          on:click={() => handleSelectOption(option)}
         >
-          {#if value == option}
-            <div class="font-medium text-immich-primary dark:text-immich-dark-primary">
+          {#if _.isEqual(selectedOption, option)}
+            <div class="text-immich-primary dark:text-immich-dark-primary">
               <Check size="18" />
             </div>
-            <p class="justify-self-start font-medium text-immich-primary dark:text-immich-dark-primary">
-              {option}
+            <p class="justify-self-start text-immich-primary dark:text-immich-dark-primary">
+              {renderedOption.title}
             </p>
           {:else}
             <div />
             <p class="justify-self-start">
-              {option}
+              {renderedOption.title}
             </p>
           {/if}
         </button>

+ 9 - 4
web/src/routes/(user)/albums/+page.svelte

@@ -216,15 +216,20 @@
     </LinkButton>
 
     <Dropdown
-      options={Object.values(sortByOptions).map((CourseInfo) => CourseInfo.sortTitle)}
-      bind:value={$albumViewSettings.sortBy}
-      icons={Object.keys(sortByOptions).map((key) => (sortByOptions[key].sortDesc ? ArrowDownThin : ArrowUpThin))}
+      options={Object.values(sortByOptions)}
+      render={(option) => {
+        return {
+          title: option.sortTitle,
+          icon: option.sortDesc ? ArrowDownThin : ArrowUpThin,
+        };
+      }}
       on:select={(event) => {
         for (const key in sortByOptions) {
-          if (sortByOptions[key].sortTitle === event.detail) {
+          if (sortByOptions[key].sortTitle === event.detail.sortTitle) {
             sortByOptions[key].sortDesc = !sortByOptions[key].sortDesc;
           }
         }
+        $albumViewSettings.sortBy = event.detail.sortTitle;
       }}
     />