more refactor

This commit is contained in:
Alex Tran
2025-12-05 20:24:37 +00:00
parent 76ec9e3ebf
commit 5156438336
3 changed files with 105 additions and 65 deletions

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { formatLabel, getComponentFromSchema } from '$lib/utils/workflow';
import { formatLabel, getComponentFromSchema, type ComponentConfig } from '$lib/utils/workflow';
import { Field, Input, MultiSelect, Select, Switch, Text, type SelectItem } from '@immich/ui';
import WorkflowPickerField from './WorkflowPickerField.svelte';
@@ -25,59 +25,78 @@
config = configKey ? { ...config, [configKey]: { ...actualConfig, ...updates } } : { ...config, ...updates };
};
// Helper to determine default value for a component based on its type
const getDefaultValue = (component: ComponentConfig): unknown => {
if (component.defaultValue !== undefined) {
return component.defaultValue;
}
// Initialize with appropriate empty value based on component type
if (component.type === 'multiselect' || (component.type === 'text' && component.subType === 'people-picker')) {
return [];
}
if (component.type === 'switch') {
return false;
}
return '';
};
// Derive which keys need initialization (missing from actualConfig)
const uninitializedKeys = $derived.by(() => {
if (!components) {
return [];
}
return Object.entries(components)
.filter(([key]) => actualConfig[key] === undefined)
.map(([key, component]) => ({ key, component, defaultValue: getDefaultValue(component) }));
});
// Derive the batch updates needed
const pendingUpdates = $derived.by(() => {
const updates: Record<string, unknown> = {};
for (const { key, defaultValue } of uninitializedKeys) {
updates[key] = defaultValue;
}
return updates;
});
let selectValue = $state<SelectItem>();
let switchValue = $state<boolean>(false);
let multiSelectValue = $state<SelectItem[]>([]);
// Initialize config namespace if needed
$effect(() => {
// Initialize config for actions/filters with empty schemas
if (configKey && !config[configKey]) {
config = { ...config, [configKey]: {} };
}
});
if (components) {
const updates: Record<string, unknown> = {};
// Apply pending config updates
$effect(() => {
if (Object.keys(pendingUpdates).length > 0) {
updateConfigBatch(pendingUpdates);
}
});
for (const [key, component] of Object.entries(components)) {
// Only initialize if the key doesn't exist in config yet
if (actualConfig[key] === undefined) {
// Use default value if available, otherwise use appropriate empty value based on type
const hasDefault = component.defaultValue !== undefined;
if (hasDefault) {
updates[key] = component.defaultValue;
} else {
// Initialize with appropriate empty value based on component type
if (
component.type === 'multiselect' ||
(component.type === 'text' && component.subType === 'people-picker')
) {
updates[key] = [];
} else if (component.type === 'switch') {
updates[key] = false;
} else {
updates[key] = '';
}
}
// Update UI state for components with default values
if (hasDefault) {
if (component.type === 'select') {
selectValue = {
label: formatLabel(String(component.defaultValue)),
value: String(component.defaultValue),
};
}
if (component.type === 'switch') {
switchValue = Boolean(component.defaultValue);
}
}
}
// Sync UI state for components with default values
$effect(() => {
for (const { component } of uninitializedKeys) {
if (component.defaultValue === undefined) {
continue;
}
if (Object.keys(updates).length > 0) {
updateConfigBatch(updates);
if (component.type === 'select') {
selectValue = {
label: formatLabel(String(component.defaultValue)),
value: String(component.defaultValue),
};
}
if (component.type === 'switch') {
switchValue = Boolean(component.defaultValue);
}
}
});

View File

@@ -2,8 +2,9 @@
import WorkflowPickerItemCard from '$lib/components/workflows/WorkflowPickerItemCard.svelte';
import AlbumPickerModal from '$lib/modals/AlbumPickerModal.svelte';
import PeoplePickerModal from '$lib/modals/PeoplePickerModal.svelte';
import { fetchPickerMetadata, type PickerMetadata } from '$lib/services/workflow.service';
import type { ComponentConfig } from '$lib/utils/workflow';
import { getAlbumInfo, getPerson, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
import type { AlbumResponseDto, PersonResponseDto } from '@immich/sdk';
import { Button, Field, modalManager } from '@immich/ui';
import { mdiPlus } from '@mdi/js';
import { t } from 'svelte-i18n';
@@ -22,7 +23,7 @@
const isAlbum = $derived(subType === 'album-picker');
const multiple = $derived(component.type === 'multiselect' || Array.isArray(value));
let pickerMetadata = $state<AlbumResponseDto | PersonResponseDto | AlbumResponseDto[] | PersonResponseDto[]>();
let pickerMetadata = $state<PickerMetadata | undefined>();
$effect(() => {
if (!value) {
@@ -30,34 +31,20 @@
return;
}
void fetchMetadata();
if (!pickerMetadata) {
void loadMetadata();
}
});
const fetchMetadata = async () => {
if (!value || pickerMetadata) {
return;
}
try {
if (Array.isArray(value) && value.length > 0) {
// Multiple selection
pickerMetadata = await (isAlbum
? Promise.all(value.map((id) => getAlbumInfo({ id })))
: Promise.all(value.map((id) => getPerson({ id }))));
} else if (typeof value === 'string' && value) {
// Single selection
pickerMetadata = await (isAlbum ? getAlbumInfo({ id: value }) : getPerson({ id: value }));
}
} catch (error) {
console.error(`Failed to fetch metadata for ${configKey}:`, error);
}
const loadMetadata = async () => {
pickerMetadata = await fetchPickerMetadata(value, subType);
};
const handlePicker = async () => {
if (isAlbum) {
const albums = await modalManager.show(AlbumPickerModal, { shared: false });
if (albums && albums.length > 0) {
const newValue = multiple ? albums.map((a) => a.id) : albums[0].id;
const newValue = multiple ? albums.map((album) => album.id) : albums[0].id;
onchange(newValue);
pickerMetadata = multiple ? albums : albums[0];
}
@@ -66,7 +53,7 @@
const excludedIds = multiple ? currentIds : [];
const people = await modalManager.show(PeoplePickerModal, { multiple, excludedIds });
if (people && people.length > 0) {
const newValue = multiple ? people.map((p) => p.id) : people[0].id;
const newValue = multiple ? people.map((person) => person.id) : people[0].id;
onchange(newValue);
pickerMetadata = multiple ? people : people[0];
}

View File

@@ -6,8 +6,12 @@ import { getFormatter } from '$lib/utils/i18n';
import {
createWorkflow,
deleteWorkflow,
getAlbumInfo,
getPerson,
PluginTriggerType,
updateWorkflow,
type AlbumResponseDto,
type PersonResponseDto,
type PluginActionResponseDto,
type PluginContextType,
type PluginFilterResponseDto,
@@ -21,6 +25,9 @@ import { modalManager, toastManager, type ActionItem } from '@immich/ui';
import { mdiCodeJson, mdiDelete, mdiPause, mdiPencil, mdiPlay } from '@mdi/js';
import type { MessageFormatter } from 'svelte-i18n';
export type PickerSubType = 'album-picker' | 'people-picker';
export type PickerMetadata = AlbumResponseDto | PersonResponseDto | AlbumResponseDto[] | PersonResponseDto[];
export interface WorkflowPayload {
name: string;
description: string;
@@ -412,3 +419,30 @@ export const handleDeleteWorkflow = async (workflow: WorkflowResponseDto): Promi
export const handleNavigateToWorkflow = async (workflow: WorkflowResponseDto): Promise<void> => {
await goto(`${AppRoute.WORKFLOWS}/${workflow.id}`);
};
export const fetchPickerMetadata = async (
value: string | string[] | undefined,
subType: PickerSubType,
): Promise<PickerMetadata | undefined> => {
if (!value) {
return undefined;
}
const isAlbum = subType === 'album-picker';
try {
if (Array.isArray(value) && value.length > 0) {
// Multiple selection
return isAlbum
? await Promise.all(value.map((id) => getAlbumInfo({ id })))
: await Promise.all(value.map((id) => getPerson({ id })));
} else if (typeof value === 'string' && value) {
// Single selection
return isAlbum ? await getAlbumInfo({ id: value }) : await getPerson({ id: value });
}
} catch (error) {
console.error(`Failed to fetch picker metadata:`, error);
}
return undefined;
};