Skip to content

Commit 5156438

Browse files
committed
more refactor
1 parent 76ec9e3 commit 5156438

File tree

3 files changed

+106
-66
lines changed

3 files changed

+106
-66
lines changed

web/src/lib/components/workflows/SchemaFormFields.svelte

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { formatLabel, getComponentFromSchema } from '$lib/utils/workflow';
2+
import { formatLabel, getComponentFromSchema, type ComponentConfig } from '$lib/utils/workflow';
33
import { Field, Input, MultiSelect, Select, Switch, Text, type SelectItem } from '@immich/ui';
44
import WorkflowPickerField from './WorkflowPickerField.svelte';
55
@@ -25,59 +25,78 @@
2525
config = configKey ? { ...config, [configKey]: { ...actualConfig, ...updates } } : { ...config, ...updates };
2626
};
2727
28+
// Helper to determine default value for a component based on its type
29+
const getDefaultValue = (component: ComponentConfig): unknown => {
30+
if (component.defaultValue !== undefined) {
31+
return component.defaultValue;
32+
}
33+
34+
// Initialize with appropriate empty value based on component type
35+
if (component.type === 'multiselect' || (component.type === 'text' && component.subType === 'people-picker')) {
36+
return [];
37+
}
38+
39+
if (component.type === 'switch') {
40+
return false;
41+
}
42+
43+
return '';
44+
};
45+
46+
// Derive which keys need initialization (missing from actualConfig)
47+
const uninitializedKeys = $derived.by(() => {
48+
if (!components) {
49+
return [];
50+
}
51+
52+
return Object.entries(components)
53+
.filter(([key]) => actualConfig[key] === undefined)
54+
.map(([key, component]) => ({ key, component, defaultValue: getDefaultValue(component) }));
55+
});
56+
57+
// Derive the batch updates needed
58+
const pendingUpdates = $derived.by(() => {
59+
const updates: Record<string, unknown> = {};
60+
for (const { key, defaultValue } of uninitializedKeys) {
61+
updates[key] = defaultValue;
62+
}
63+
return updates;
64+
});
65+
2866
let selectValue = $state<SelectItem>();
2967
let switchValue = $state<boolean>(false);
3068
let multiSelectValue = $state<SelectItem[]>([]);
3169
70+
// Initialize config namespace if needed
3271
$effect(() => {
33-
// Initialize config for actions/filters with empty schemas
3472
if (configKey && !config[configKey]) {
3573
config = { ...config, [configKey]: {} };
3674
}
75+
});
76+
77+
// Apply pending config updates
78+
$effect(() => {
79+
if (Object.keys(pendingUpdates).length > 0) {
80+
updateConfigBatch(pendingUpdates);
81+
}
82+
});
83+
84+
// Sync UI state for components with default values
85+
$effect(() => {
86+
for (const { component } of uninitializedKeys) {
87+
if (component.defaultValue === undefined) {
88+
continue;
89+
}
3790
38-
if (components) {
39-
const updates: Record<string, unknown> = {};
40-
41-
for (const [key, component] of Object.entries(components)) {
42-
// Only initialize if the key doesn't exist in config yet
43-
if (actualConfig[key] === undefined) {
44-
// Use default value if available, otherwise use appropriate empty value based on type
45-
const hasDefault = component.defaultValue !== undefined;
46-
47-
if (hasDefault) {
48-
updates[key] = component.defaultValue;
49-
} else {
50-
// Initialize with appropriate empty value based on component type
51-
if (
52-
component.type === 'multiselect' ||
53-
(component.type === 'text' && component.subType === 'people-picker')
54-
) {
55-
updates[key] = [];
56-
} else if (component.type === 'switch') {
57-
updates[key] = false;
58-
} else {
59-
updates[key] = '';
60-
}
61-
}
62-
63-
// Update UI state for components with default values
64-
if (hasDefault) {
65-
if (component.type === 'select') {
66-
selectValue = {
67-
label: formatLabel(String(component.defaultValue)),
68-
value: String(component.defaultValue),
69-
};
70-
}
71-
72-
if (component.type === 'switch') {
73-
switchValue = Boolean(component.defaultValue);
74-
}
75-
}
76-
}
91+
if (component.type === 'select') {
92+
selectValue = {
93+
label: formatLabel(String(component.defaultValue)),
94+
value: String(component.defaultValue),
95+
};
7796
}
7897
79-
if (Object.keys(updates).length > 0) {
80-
updateConfigBatch(updates);
98+
if (component.type === 'switch') {
99+
switchValue = Boolean(component.defaultValue);
81100
}
82101
}
83102
});

web/src/lib/components/workflows/WorkflowPickerField.svelte

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
import WorkflowPickerItemCard from '$lib/components/workflows/WorkflowPickerItemCard.svelte';
33
import AlbumPickerModal from '$lib/modals/AlbumPickerModal.svelte';
44
import PeoplePickerModal from '$lib/modals/PeoplePickerModal.svelte';
5+
import { fetchPickerMetadata, type PickerMetadata } from '$lib/services/workflow.service';
56
import type { ComponentConfig } from '$lib/utils/workflow';
6-
import { getAlbumInfo, getPerson, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
7+
import type { AlbumResponseDto, PersonResponseDto } from '@immich/sdk';
78
import { Button, Field, modalManager } from '@immich/ui';
89
import { mdiPlus } from '@mdi/js';
910
import { t } from 'svelte-i18n';
@@ -22,42 +23,28 @@
2223
const isAlbum = $derived(subType === 'album-picker');
2324
const multiple = $derived(component.type === 'multiselect' || Array.isArray(value));
2425
25-
let pickerMetadata = $state<AlbumResponseDto | PersonResponseDto | AlbumResponseDto[] | PersonResponseDto[]>();
26+
let pickerMetadata = $state<PickerMetadata | undefined>();
2627
2728
$effect(() => {
2829
if (!value) {
2930
pickerMetadata = undefined;
3031
return;
3132
}
3233
33-
void fetchMetadata();
34-
});
35-
36-
const fetchMetadata = async () => {
37-
if (!value || pickerMetadata) {
38-
return;
34+
if (!pickerMetadata) {
35+
void loadMetadata();
3936
}
37+
});
4038
41-
try {
42-
if (Array.isArray(value) && value.length > 0) {
43-
// Multiple selection
44-
pickerMetadata = await (isAlbum
45-
? Promise.all(value.map((id) => getAlbumInfo({ id })))
46-
: Promise.all(value.map((id) => getPerson({ id }))));
47-
} else if (typeof value === 'string' && value) {
48-
// Single selection
49-
pickerMetadata = await (isAlbum ? getAlbumInfo({ id: value }) : getPerson({ id: value }));
50-
}
51-
} catch (error) {
52-
console.error(`Failed to fetch metadata for ${configKey}:`, error);
53-
}
39+
const loadMetadata = async () => {
40+
pickerMetadata = await fetchPickerMetadata(value, subType);
5441
};
5542
5643
const handlePicker = async () => {
5744
if (isAlbum) {
5845
const albums = await modalManager.show(AlbumPickerModal, { shared: false });
5946
if (albums && albums.length > 0) {
60-
const newValue = multiple ? albums.map((a) => a.id) : albums[0].id;
47+
const newValue = multiple ? albums.map((album) => album.id) : albums[0].id;
6148
onchange(newValue);
6249
pickerMetadata = multiple ? albums : albums[0];
6350
}
@@ -66,7 +53,7 @@
6653
const excludedIds = multiple ? currentIds : [];
6754
const people = await modalManager.show(PeoplePickerModal, { multiple, excludedIds });
6855
if (people && people.length > 0) {
69-
const newValue = multiple ? people.map((p) => p.id) : people[0].id;
56+
const newValue = multiple ? people.map((person) => person.id) : people[0].id;
7057
onchange(newValue);
7158
pickerMetadata = multiple ? people : people[0];
7259
}

web/src/lib/services/workflow.service.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ import { getFormatter } from '$lib/utils/i18n';
66
import {
77
createWorkflow,
88
deleteWorkflow,
9+
getAlbumInfo,
10+
getPerson,
911
PluginTriggerType,
1012
updateWorkflow,
13+
type AlbumResponseDto,
14+
type PersonResponseDto,
1115
type PluginActionResponseDto,
1216
type PluginContextType,
1317
type PluginFilterResponseDto,
@@ -21,6 +25,9 @@ import { modalManager, toastManager, type ActionItem } from '@immich/ui';
2125
import { mdiCodeJson, mdiDelete, mdiPause, mdiPencil, mdiPlay } from '@mdi/js';
2226
import type { MessageFormatter } from 'svelte-i18n';
2327

28+
export type PickerSubType = 'album-picker' | 'people-picker';
29+
export type PickerMetadata = AlbumResponseDto | PersonResponseDto | AlbumResponseDto[] | PersonResponseDto[];
30+
2431
export interface WorkflowPayload {
2532
name: string;
2633
description: string;
@@ -412,3 +419,30 @@ export const handleDeleteWorkflow = async (workflow: WorkflowResponseDto): Promi
412419
export const handleNavigateToWorkflow = async (workflow: WorkflowResponseDto): Promise<void> => {
413420
await goto(`${AppRoute.WORKFLOWS}/${workflow.id}`);
414421
};
422+
423+
export const fetchPickerMetadata = async (
424+
value: string | string[] | undefined,
425+
subType: PickerSubType,
426+
): Promise<PickerMetadata | undefined> => {
427+
if (!value) {
428+
return undefined;
429+
}
430+
431+
const isAlbum = subType === 'album-picker';
432+
433+
try {
434+
if (Array.isArray(value) && value.length > 0) {
435+
// Multiple selection
436+
return isAlbum
437+
? await Promise.all(value.map((id) => getAlbumInfo({ id })))
438+
: await Promise.all(value.map((id) => getPerson({ id })));
439+
} else if (typeof value === 'string' && value) {
440+
// Single selection
441+
return isAlbum ? await getAlbumInfo({ id: value }) : await getPerson({ id: value });
442+
}
443+
} catch (error) {
444+
console.error(`Failed to fetch picker metadata:`, error);
445+
}
446+
447+
return undefined;
448+
};

0 commit comments

Comments
 (0)