@immich/ui 0.50.1 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/components/Alert/Alert.svelte +1 -1
  2. package/dist/components/Badge/Badge.svelte +1 -1
  3. package/dist/components/Card/Card.svelte +2 -2
  4. package/dist/components/CommandPalette/CommandPaletteDefaultProvider.svelte +14 -0
  5. package/dist/components/CommandPalette/CommandPaletteDefaultProvider.svelte.d.ts +8 -0
  6. package/dist/components/CommandPalette/CommandPaletteItem.svelte +4 -23
  7. package/dist/components/CommandPalette/CommandPaletteItem.svelte.d.ts +0 -1
  8. package/dist/components/CommandPalette/CommandPaletteProvider.svelte +18 -0
  9. package/dist/components/CommandPalette/CommandPaletteProvider.svelte.d.ts +7 -0
  10. package/dist/components/Heading/Heading.svelte +6 -13
  11. package/dist/components/IconButton/IconButton.svelte +2 -2
  12. package/dist/components/Markdown/Heading.svelte +9 -12
  13. package/dist/components/Modal/Modal.svelte +31 -31
  14. package/dist/components/Textarea/Textarea.svelte +8 -2
  15. package/dist/components/Toast/ToastPanel.svelte +19 -16
  16. package/dist/components/Tooltip/Tooltip.svelte +38 -0
  17. package/dist/components/Tooltip/Tooltip.svelte.d.ts +11 -0
  18. package/dist/components/Tooltip/TooltipProvider.svelte +15 -0
  19. package/dist/components/Tooltip/TooltipProvider.svelte.d.ts +8 -0
  20. package/dist/constants.d.ts +1 -0
  21. package/dist/constants.js +1 -0
  22. package/dist/index.d.ts +4 -1
  23. package/dist/index.js +4 -1
  24. package/dist/internal/Button.svelte +44 -27
  25. package/dist/internal/CommandPaletteModal.svelte +26 -23
  26. package/dist/internal/CommandPaletteModal.svelte.d.ts +3 -1
  27. package/dist/internal/Select.svelte +1 -1
  28. package/dist/services/command-palette-manager.svelte.d.ts +19 -77
  29. package/dist/services/command-palette-manager.svelte.js +44 -152
  30. package/dist/site/SiteFooter.svelte +3 -0
  31. package/dist/site/constants.d.ts +1 -0
  32. package/dist/site/constants.js +2 -1
  33. package/dist/types.d.ts +1 -0
  34. package/dist/utilities/common.d.ts +1 -0
  35. package/dist/utilities/common.js +15 -1
  36. package/dist/utilities/internal.d.ts +3 -1
  37. package/dist/utilities/internal.js +3 -0
  38. package/package.json +2 -2
  39. package/dist/components/CommandPalette/CommandPaletteContext.svelte +0 -18
  40. package/dist/components/CommandPalette/CommandPaletteContext.svelte.d.ts +0 -7
@@ -15,13 +15,17 @@
15
15
  type CommandPaletteTranslations,
16
16
  } from '../services/command-palette-manager.svelte.js';
17
17
  import { t } from '../services/translation.svelte.js';
18
+ import type { ActionItem } from '../types.js';
18
19
  import { mdiArrowDown, mdiArrowUp, mdiKeyboardEsc, mdiKeyboardReturn, mdiMagnify } from '@mdi/js';
19
20
 
20
21
  type Props = {
21
- onClose: () => void;
22
+ onClose: (action?: ActionItem) => void;
22
23
  translations?: CommandPaletteTranslations;
24
+ initialQuery?: string;
23
25
  };
24
26
 
27
+ const { onClose, translations, initialQuery = '' }: Props = $props();
28
+
25
29
  const handleUp = (event: KeyboardEvent) => handleNavigate(event, 'up');
26
30
  const handleDown = (event: KeyboardEvent) => handleNavigate(event, 'down');
27
31
  const handleSelect = (event: KeyboardEvent) => handleNavigate(event, 'select');
@@ -30,23 +34,31 @@
30
34
 
31
35
  switch (direction) {
32
36
  case 'up': {
33
- commandPaletteManager.up();
37
+ selectedIndex = Math.max((selectedIndex === 0 ? commandPaletteManager.items.length : selectedIndex) - 1, 0);
34
38
  break;
35
39
  }
36
40
 
37
41
  case 'down': {
38
- commandPaletteManager.down();
42
+ if (!query && commandPaletteManager.items.length === 0) {
43
+ commandPaletteManager.loadAllItems();
44
+ break;
45
+ }
46
+
47
+ selectedIndex = (selectedIndex + 1) % commandPaletteManager.items.length || 0;
39
48
  break;
40
49
  }
41
50
 
42
51
  case 'select': {
43
- await commandPaletteManager.select();
52
+ onClose(commandPaletteManager.items[selectedIndex]);
44
53
  break;
45
54
  }
46
55
  }
47
56
  };
48
57
 
49
- const { onClose, translations }: Props = $props();
58
+ let selectedIndex = $state(0);
59
+ let query = $state(initialQuery);
60
+
61
+ $effect(() => commandPaletteManager.queryUpdate(query));
50
62
  </script>
51
63
 
52
64
  <svelte:window
@@ -61,43 +73,34 @@
61
73
  ]}
62
74
  />
63
75
 
64
- <Modal size="large" {onClose} closeOnBackdropClick class="max-h-[75vh] lg:max-h-[50vh]">
76
+ <Modal size="large" {onClose} closeOnBackdropClick class="max-h-[85vh] lg:max-h-[75vh]">
65
77
  <ModalHeader>
66
78
  <div class="flex place-items-center gap-1">
67
79
  <Input
68
- bind:value={commandPaletteManager.query}
80
+ bind:value={query}
69
81
  placeholder={t('search_placeholder', translations)}
70
82
  leadingIcon={mdiMagnify}
71
83
  tabindex={1}
72
84
  />
73
85
  <div>
74
- <CloseButton onclick={() => commandPaletteManager.close()} class="md:hidden" />
86
+ <CloseButton onclick={() => onClose()} class="md:hidden" />
75
87
  </div>
76
88
  </div>
77
89
  </ModalHeader>
78
90
  <ModalBody>
79
91
  <Stack gap={2}>
80
- {#if commandPaletteManager.query}
81
- {#if commandPaletteManager.results.length === 0}
92
+ {#if query}
93
+ {#if commandPaletteManager.items.length === 0}
82
94
  <Text>{t('search_no_results', translations)}</Text>
83
95
  {/if}
84
- {:else if commandPaletteManager.recentItems.length > 0}
85
- <Text>{t('search_recently_used', translations)}</Text>
86
96
  {:else}
87
97
  <Text>{t('command_palette_prompt_default', translations)}</Text>
88
98
  {/if}
89
99
 
90
- {#if commandPaletteManager.results.length > 0}
100
+ {#if commandPaletteManager.items.length > 0}
91
101
  <div class="flex flex-col">
92
- {#each commandPaletteManager.results as item, i (item.id)}
93
- <CommandPaletteItem
94
- {item}
95
- selected={commandPaletteManager.selectedIndex === i}
96
- onRemove={commandPaletteManager.query || commandPaletteManager.isShowAll
97
- ? undefined
98
- : () => commandPaletteManager.remove(i)}
99
- onSelect={() => commandPaletteManager.select(i)}
100
- />
102
+ {#each commandPaletteManager.items as item, i (item.id)}
103
+ <CommandPaletteItem {item} selected={selectedIndex === i} onSelect={() => onClose(item)} />
101
104
  {/each}
102
105
  </div>
103
106
  {/if}
@@ -105,7 +108,7 @@
105
108
  </ModalBody>
106
109
  <ModalFooter>
107
110
  <div class="flex w-full justify-around">
108
- {#if commandPaletteManager.isEmpty}
111
+ {#if !query && commandPaletteManager.items.length === 0}
109
112
  <div class="flex place-items-center gap-1">
110
113
  <span class="flex gap-1 rounded bg-gray-300 p-1 dark:bg-gray-500">
111
114
  <Icon icon={mdiArrowDown} size="1rem" />
@@ -1,7 +1,9 @@
1
1
  import { type CommandPaletteTranslations } from '../services/command-palette-manager.svelte.js';
2
+ import type { ActionItem } from '../types.js';
2
3
  type Props = {
3
- onClose: () => void;
4
+ onClose: (action?: ActionItem) => void;
4
5
  translations?: CommandPaletteTranslations;
6
+ initialQuery?: string;
5
7
  };
6
8
  declare const CommandPaletteModal: import("svelte").Component<Props, {}, "">;
7
9
  type CommandPaletteModal = ReturnType<typeof CommandPaletteModal>;
@@ -136,7 +136,7 @@
136
136
  >
137
137
  {#snippet children({ selected })}
138
138
  <div
139
- class="flex cursor-pointer items-center justify-center gap-2 text-sm font-medium whitespace-nowrap transition-colors"
139
+ class="flex items-center justify-center gap-2 text-sm font-medium whitespace-nowrap transition-colors"
140
140
  >
141
141
  <span>{label}</span>
142
142
  </div>
@@ -1,64 +1,20 @@
1
- import type { ActionItem, MaybeArray, TranslationProps } from '../types.js';
1
+ import type { ActionItem, MaybePromise, TranslationProps } from '../types.js';
2
2
  export type CommandPaletteTranslations = TranslationProps<'search_placeholder' | 'search_no_results' | 'search_recently_used' | 'command_palette_prompt_default' | 'command_palette_to_select' | 'command_palette_to_close' | 'command_palette_to_navigate' | 'command_palette_to_show_all' | 'global'>;
3
- export declare const asText: (...items: unknown[]) => string;
3
+ export type ActionProvider = {
4
+ name: string;
5
+ onSearch: (query?: string) => MaybePromise<ActionItem[]>;
6
+ };
7
+ export declare const defaultProvider: ({ name, actions }: {
8
+ name: string;
9
+ actions: ActionItem[];
10
+ }) => {
11
+ name: string;
12
+ onSearch: (query?: string) => ActionItem[];
13
+ };
4
14
  declare class CommandPaletteManager {
5
15
  #private;
6
- selectedIndex: number;
7
- items: ({
8
- title: string;
9
- description?: string;
10
- type?: string;
11
- searchText?: string;
12
- icon: import("../types.js").IconLike;
13
- iconClass?: string;
14
- color?: import("../types.js").Color;
15
- onAction: import("../types.js").ActionItemHandler;
16
- shortcuts?: MaybeArray<import("../actions/shortcut.js").Shortcut>;
17
- shortcutOptions?: {
18
- ignoreInputFields?: boolean;
19
- preventDefault?: boolean;
20
- };
21
- isGlobal?: boolean;
22
- } & import("../types.js").IfLike & {
23
- id: string;
24
- })[];
25
- filteredItems: ({
26
- title: string;
27
- description?: string;
28
- type?: string;
29
- searchText?: string;
30
- icon: import("../types.js").IconLike;
31
- iconClass?: string;
32
- color?: import("../types.js").Color;
33
- onAction: import("../types.js").ActionItemHandler;
34
- shortcuts?: MaybeArray<import("../actions/shortcut.js").Shortcut>;
35
- shortcutOptions?: {
36
- ignoreInputFields?: boolean;
37
- preventDefault?: boolean;
38
- };
39
- isGlobal?: boolean;
40
- } & import("../types.js").IfLike & {
41
- id: string;
42
- })[];
43
- recentItems: ({
44
- title: string;
45
- description?: string;
46
- type?: string;
47
- searchText?: string;
48
- icon: import("../types.js").IconLike;
49
- iconClass?: string;
50
- color?: import("../types.js").Color;
51
- onAction: import("../types.js").ActionItemHandler;
52
- shortcuts?: MaybeArray<import("../actions/shortcut.js").Shortcut>;
53
- shortcutOptions?: {
54
- ignoreInputFields?: boolean;
55
- preventDefault?: boolean;
56
- };
57
- isGlobal?: boolean;
58
- } & import("../types.js").IfLike & {
59
- id: string;
60
- })[];
61
- results: ({
16
+ get isEnabled(): boolean;
17
+ get items(): ({
62
18
  title: string;
63
19
  description?: string;
64
20
  type?: string;
@@ -67,7 +23,7 @@ declare class CommandPaletteManager {
67
23
  iconClass?: string;
68
24
  color?: import("../types.js").Color;
69
25
  onAction: import("../types.js").ActionItemHandler;
70
- shortcuts?: MaybeArray<import("../actions/shortcut.js").Shortcut>;
26
+ shortcuts?: import("../types.js").MaybeArray<import("../actions/shortcut.js").Shortcut>;
71
27
  shortcutOptions?: {
72
28
  ignoreInputFields?: boolean;
73
29
  preventDefault?: boolean;
@@ -76,26 +32,12 @@ declare class CommandPaletteManager {
76
32
  } & import("../types.js").IfLike & {
77
33
  id: string;
78
34
  })[];
79
- get isEnabled(): boolean;
80
35
  enable(): void;
81
- get query(): string;
82
- set query(query: string);
83
- get isShowAll(): boolean;
84
- get isEmpty(): boolean;
85
36
  setTranslations(translations?: CommandPaletteTranslations): void;
86
- pushContextLayer(): (() => void) | undefined;
87
- popContextLayer(): void;
88
- open(): void;
89
- close(): Promise<void> | undefined;
90
- select(selectedIndex?: number): Promise<void>;
91
- remove(index: number): void;
92
- up(): void;
93
- down(): void;
94
- reset(): void;
95
- addCommands(itemOrItems: MaybeArray<ActionItem>): () => void;
96
- removeCommands(itemOrItems: MaybeArray<{
97
- id: string;
98
- }>): void;
37
+ queryUpdate(query: string): void;
38
+ open(initialQuery?: string): void;
39
+ loadAllItems(): void;
40
+ addProvider(provider: ActionProvider): () => void;
99
41
  }
100
42
  export declare const commandPaletteManager: CommandPaletteManager;
101
43
  export {};
@@ -1,39 +1,23 @@
1
- import { matchesShortcut, shortcuts, shouldIgnoreEvent } from '../actions/shortcut.js';
1
+ import { matchesShortcut, shortcuts } from '../actions/shortcut.js';
2
2
  import CommandPaletteModal from '../internal/CommandPaletteModal.svelte';
3
- import { generateId, isEnabled } from '../utilities/internal.js';
4
3
  import { modalManager } from './modal-manager.svelte.js';
5
- export const asText = (...items) => {
6
- return items
7
- .filter((item) => item !== undefined && item !== null)
8
- .map((items) => String(items))
9
- .join('|')
10
- .toLowerCase();
11
- };
12
- const isMatch = ({ title, description, type, searchText = asText(title, description, type) }, query) => {
13
- if (!query) {
14
- return true;
15
- }
16
- return searchText.includes(query);
17
- };
4
+ import { asArray, generateId, getSearchString } from '../utilities/internal.js';
5
+ export const defaultProvider = ({ name, actions }) => ({
6
+ name,
7
+ onSearch: (query) => query ? actions.filter((action) => getSearchString(action).includes(query.toLowerCase())) : actions,
8
+ });
18
9
  class CommandPaletteManager {
19
- #query = $state('');
20
- selectedIndex = $state(0);
21
- #isEnabled = $state(false);
22
- #normalizedQuery = $derived(this.#query.toLowerCase());
23
- #modal;
24
10
  #translations = {};
11
+ #providers = [];
12
+ #isEnabled = false;
25
13
  #isOpen = false;
26
- #isShowAll = $state(false);
27
- #globalLayer = $state({ items: [], recentItems: [] });
28
- #layers = $state([{ items: [], recentItems: [] }]);
29
- items = $derived([...this.#globalLayer.items, ...this.#layers.at(-1).items].filter(isEnabled));
30
- filteredItems = $derived(this.items.filter((item) => isMatch(item, this.#normalizedQuery)).slice(0, 100));
31
- recentItems = $derived([...this.#globalLayer.recentItems, ...this.#layers.at(-1).recentItems].filter(isEnabled));
32
- results = $derived(this.#isShowAll ? this.items : this.#query ? this.filteredItems : this.recentItems);
33
- #isEmpty = $derived(!this.#isShowAll && !this.#query && this.results.length === 0);
14
+ #items = $state([]);
34
15
  get isEnabled() {
35
16
  return this.#isEnabled;
36
17
  }
18
+ get items() {
19
+ return this.#items;
20
+ }
37
21
  enable() {
38
22
  if (this.#isEnabled) {
39
23
  return;
@@ -48,146 +32,54 @@ class CommandPaletteManager {
48
32
  document.body.addEventListener('keydown', (event) => this.#handleKeydown(event));
49
33
  }
50
34
  }
51
- get query() {
52
- return this.#query;
53
- }
54
- set query(query) {
55
- this.#query = query;
56
- if (this.#isShowAll && query) {
57
- this.#isShowAll = false;
58
- }
59
- }
60
- get isShowAll() {
61
- return this.#isShowAll;
62
- }
63
- get isEmpty() {
64
- return this.#isEmpty;
65
- }
66
- async #handleKeydown(event) {
67
- const command = this.items.find(({ shortcuts }) => {
68
- if (!shortcuts) {
69
- return;
70
- }
71
- if (shortcuts)
72
- return Array.isArray(shortcuts)
73
- ? shortcuts.some((shortcut) => matchesShortcut(event, shortcut))
74
- : matchesShortcut(event, shortcuts);
75
- });
76
- if (!command) {
77
- return;
78
- }
79
- const { ignoreInputFields = true, preventDefault = true } = command.shortcutOptions ?? {};
80
- if (ignoreInputFields && shouldIgnoreEvent(event)) {
81
- return;
82
- }
83
- if (preventDefault) {
84
- event.preventDefault();
85
- }
86
- await command.onAction(command);
87
- }
88
35
  setTranslations(translations = {}) {
89
36
  this.#translations = translations;
90
37
  }
91
- pushContextLayer() {
92
- if (!this.#isEnabled) {
93
- return;
94
- }
95
- // we do not want the command palette to have its own context layer
96
- if (this.#isOpen) {
97
- return;
98
- }
99
- this.#layers.push({ items: [], recentItems: [] });
100
- return () => this.popContextLayer();
101
- }
102
- popContextLayer() {
103
- if (this.#layers.length > 1) {
104
- this.#layers = this.#layers.slice(0, -1);
105
- }
38
+ async #onSearch(query) {
39
+ const newItems = await Promise.all(this.#providers.map((provider) => Promise.resolve(provider.onSearch(query))));
40
+ this.#items = newItems.flat().map((item) => ({ ...item, id: generateId() }));
106
41
  }
107
- open() {
108
- if (this.#modal || !this.#isEnabled) {
42
+ queryUpdate(query) {
43
+ if (!query) {
44
+ this.#items = [];
109
45
  return;
110
46
  }
111
- this.selectedIndex = 0;
112
- this.#isOpen = true;
113
- const { close, onClose } = modalManager.open(CommandPaletteModal, { translations: this.#translations });
114
- this.#modal = { close };
115
- void onClose.then(() => this.#onClose());
47
+ void this.#onSearch(query);
116
48
  }
117
- close() {
118
- if (!this.#modal) {
119
- return;
49
+ async #handleKeydown(event) {
50
+ const actions = await Promise.all(this.#providers.map((provider) => Promise.resolve(provider.onSearch())));
51
+ for (const action of actions.flat()) {
52
+ const shortcuts = asArray(action.shortcuts);
53
+ if (shortcuts.some((shortcut) => matchesShortcut(event, shortcut))) {
54
+ action?.onAction(action);
55
+ }
120
56
  }
121
- return this.#modal.close();
122
57
  }
123
- #onClose() {
124
- this.#query = '';
125
- this.#modal = undefined;
58
+ async #onClose(action) {
59
+ await action?.onAction(action);
126
60
  this.#isOpen = false;
127
- this.#isShowAll = false;
128
- }
129
- async select(selectedIndex) {
130
- const selected = this.results[selectedIndex ?? this.selectedIndex];
131
- if (!selected) {
132
- return;
133
- }
134
- if (selected.isGlobal) {
135
- this.#globalLayer.recentItems = this.#globalLayer.recentItems.filter(({ id }) => id !== selected.id);
136
- this.#globalLayer.recentItems.unshift(selected);
137
- }
138
- else {
139
- this.#layers.at(-1).recentItems = this.#layers.at(-1).recentItems.filter(({ id }) => id !== selected.id);
140
- this.#layers.at(-1)?.recentItems.unshift(selected);
141
- }
142
- await selected.onAction(selected);
143
- await this.close();
144
- }
145
- remove(index) {
146
- const item = this.recentItems.at(index);
147
- if (!item) {
148
- return;
149
- }
150
- this.#globalLayer.recentItems = this.#globalLayer.recentItems.filter(({ id }) => id !== item.id);
151
- this.#layers.at(-1).recentItems = this.#layers.at(-1).recentItems.filter(({ id }) => id !== item.id);
61
+ this.#items = [];
152
62
  }
153
- up() {
154
- this.selectedIndex = (this.selectedIndex - 1 + this.results.length) % (this.results.length || 1);
155
- }
156
- down() {
157
- if (this.#isEmpty) {
158
- this.#isShowAll = true;
63
+ open(initialQuery) {
64
+ if (this.#isOpen) {
159
65
  return;
160
66
  }
161
- this.selectedIndex = (this.selectedIndex + 1) % (this.results.length || 1);
162
- }
163
- reset() {
164
- this.#layers = [{ items: [], recentItems: [] }];
165
- this.#globalLayer = { items: [], recentItems: [] };
166
- this.#query = '';
67
+ const { onClose } = modalManager.open(CommandPaletteModal, {
68
+ translations: this.#translations,
69
+ initialQuery,
70
+ });
71
+ this.#isOpen = true;
72
+ void onClose.then((action) => this.#onClose(action));
167
73
  }
168
- addCommands(itemOrItems) {
169
- const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
170
- const itemsWithId = items.map((item) => ({
171
- ...item,
172
- id: generateId(),
173
- }));
174
- const globalItems = itemsWithId.filter(({ isGlobal }) => isGlobal);
175
- const localItems = itemsWithId.filter(({ isGlobal }) => !isGlobal);
176
- this.#globalLayer.items.push(...globalItems);
177
- this.#layers.at(-1).items.push(...localItems);
178
- return () => this.removeCommands(itemsWithId);
74
+ loadAllItems() {
75
+ void this.#onSearch();
179
76
  }
180
- #removeCommands(layer, ids) {
181
- return {
182
- items: layer.items.filter(({ id }) => !ids[id]),
183
- recentItems: layer.recentItems.filter(({ id }) => !ids[id]),
184
- };
77
+ addProvider(provider) {
78
+ this.#providers.push(provider);
79
+ return () => this.#removeProvider(provider);
185
80
  }
186
- removeCommands(itemOrItems) {
187
- const items = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
188
- const ids = items.reduce((acc, { id }) => ({ ...acc, [id]: true }), {});
189
- this.#layers = this.#layers.map((layer) => this.#removeCommands(layer, ids));
190
- this.#globalLayer = this.#removeCommands(this.#globalLayer, ids);
81
+ #removeProvider(provider) {
82
+ this.#providers = this.#providers.filter((actionProvider) => actionProvider !== provider);
191
83
  }
192
84
  }
193
85
  export const commandPaletteManager = new CommandPaletteManager();
@@ -31,6 +31,9 @@
31
31
  <SiteFooterLink href={Constants.Sites.Docs} text="Documentation" />
32
32
  <SiteFooterLink href={Constants.Sites.My} text="My Immich" />
33
33
  <SiteFooterLink href={Constants.Sites.Api} text="Immich API" />
34
+ <SiteFooterLink href={Constants.Sites.Data} text="Immich Data" />
35
+ <SiteFooterLink href={Constants.Sites.Datasets} text="Immich Datasets" />
36
+ <SiteFooterLink href={Constants.Sites.Awesome} text="Awesome Immich" />
34
37
  </Stack>
35
38
 
36
39
  <Stack>
@@ -26,6 +26,7 @@ export declare const Constants: {
26
26
  Get: string;
27
27
  My: string;
28
28
  Store: string;
29
+ Awesome: string;
29
30
  Ui: string;
30
31
  };
31
32
  Pages: {
@@ -1,5 +1,5 @@
1
1
  import { goto } from '$app/navigation';
2
- import { asText } from '../services/command-palette-manager.svelte.js';
2
+ import { asText } from '../utilities/common.js';
3
3
  import { mdiOpenInNew } from '@mdi/js';
4
4
  export const Constants = {
5
5
  Socials: {
@@ -29,6 +29,7 @@ export const Constants = {
29
29
  Get: 'https://get.immich.app/',
30
30
  My: 'https://my.immich.app/',
31
31
  Store: 'https://immich.store/',
32
+ Awesome: 'https://awesome.immich.app/',
32
33
  Ui: 'https://ui.immich.app/',
33
34
  },
34
35
  Pages: {
package/dist/types.d.ts CHANGED
@@ -28,6 +28,7 @@ export type IconLike = string | {
28
28
  path: string;
29
29
  };
30
30
  export type MaybeArray<T> = T | T[];
31
+ export type MaybePromise<T> = T | Promise<T>;
31
32
  export type IconProps = {
32
33
  icon: IconLike;
33
34
  title?: string;
@@ -31,3 +31,4 @@ export declare const resolveMetadata: (site: Metadata, page?: Metadata, article?
31
31
  tags: string[] | undefined;
32
32
  } | undefined;
33
33
  };
34
+ export declare const asText: (...items: unknown[]) => string;
@@ -24,7 +24,14 @@ export const resolveUrl = (url, currentHostname) => {
24
24
  }
25
25
  };
26
26
  export const isExternalLink = (href) => {
27
- return !(href.startsWith('/') || href.startsWith('#'));
27
+ try {
28
+ const current = new URL(globalThis.location.href);
29
+ const target = new URL(href, current);
30
+ return target.origin !== current.origin;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
28
35
  };
29
36
  export const isMenuItemType = (item) => {
30
37
  return item === MenuItemType.Divider;
@@ -53,3 +60,10 @@ export const resolveMetadata = (site, page, article) => {
53
60
  : undefined,
54
61
  };
55
62
  };
63
+ export const asText = (...items) => {
64
+ return items
65
+ .filter((item) => item !== undefined && item !== null)
66
+ .map((items) => String(items))
67
+ .join('|')
68
+ .toLowerCase();
69
+ };
@@ -1,4 +1,4 @@
1
- import type { Color, IconLike, IfLike, TextColor } from '../types.js';
1
+ import type { ActionItem, Color, IconLike, IfLike, MaybeArray, TextColor } from '../types.js';
2
2
  export declare const cleanClass: (...classNames: unknown[]) => string;
3
3
  export declare const withPrefix: (key: string) => string;
4
4
  export declare const generateId: () => string;
@@ -10,3 +10,5 @@ export declare const resolveIcon: ({ icons, color, override, fallback, }: {
10
10
  icons: Partial<Record<Color | TextColor, string>>;
11
11
  }) => IconLike | undefined;
12
12
  export declare const isEnabled: ({ $if }: IfLike) => boolean;
13
+ export declare const asArray: <T>(items?: MaybeArray<T>) => T[];
14
+ export declare const getSearchString: ({ title, description, type, searchText }: ActionItem) => string;
@@ -1,3 +1,4 @@
1
+ import { asText } from './common.js';
1
2
  import { twMerge } from 'tailwind-merge';
2
3
  export const cleanClass = (...classNames) => {
3
4
  return twMerge(classNames
@@ -25,3 +26,5 @@ export const resolveIcon = ({ icons, color, override, fallback, }) => {
25
26
  return icons[color] ?? fallback;
26
27
  };
27
28
  export const isEnabled = ({ $if }) => $if?.() ?? true;
29
+ export const asArray = (items) => (Array.isArray(items) ? items : items ? [items] : []);
30
+ export const getSearchString = ({ title, description, type, searchText }) => searchText ?? asText(title, description, type);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immich/ui",
3
- "version": "0.50.1",
3
+ "version": "0.52.0",
4
4
  "license": "GNU Affero General Public License version 3",
5
5
  "repository": {
6
6
  "type": "git",
@@ -51,7 +51,7 @@
51
51
  "@mdi/js": "^7.4.47",
52
52
  "bits-ui": "^2.9.8",
53
53
  "luxon": "^3.7.2",
54
- "simple-icons": "^15.14.0",
54
+ "simple-icons": "^16.0.0",
55
55
  "svelte-highlight": "^7.8.4",
56
56
  "tailwind-merge": "^3.0.0",
57
57
  "tailwind-variants": "^3.0.0",
@@ -1,18 +0,0 @@
1
- <script lang="ts">
2
- import { commandPaletteManager } from '../../services/command-palette-manager.svelte';
3
- import type { ActionItem } from '../../types.js';
4
- import { untrack } from 'svelte';
5
-
6
- type Props = {
7
- commands?: ActionItem[];
8
- };
9
-
10
- const { commands = [] }: Props = $props();
11
-
12
- $effect(() => {
13
- // prevent reactivity loop
14
- const addCommands = (commands: ActionItem[]) => untrack(() => commandPaletteManager.addCommands(commands));
15
-
16
- return addCommands(commands);
17
- });
18
- </script>
@@ -1,7 +0,0 @@
1
- import type { ActionItem } from '../../types.js';
2
- type Props = {
3
- commands?: ActionItem[];
4
- };
5
- declare const CommandPaletteContext: import("svelte").Component<Props, {}, "">;
6
- type CommandPaletteContext = ReturnType<typeof CommandPaletteContext>;
7
- export default CommandPaletteContext;