mirror of
https://github.com/immich-app/immich.git
synced 2025-12-23 09:15:05 +03:00
Merge branch 'main' of github.com:immich-app/immich into workflow-ui
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import PeoplePickerModal from '$lib/modals/PeoplePickerModal.svelte';
|
||||
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
||||
import { formatLabel, getComponentFromSchema } from '$lib/utils/workflow';
|
||||
import type { AlbumResponseDto, PersonResponseDto } from '@immich/sdk';
|
||||
import { getAlbumInfo, getPerson, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
|
||||
import { Button, Field, Input, MultiSelect, Select, Switch, modalManager, type SelectItem } from '@immich/ui';
|
||||
import { mdiPlus } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
@@ -37,6 +37,64 @@
|
||||
Record<string, AlbumResponseDto | PersonResponseDto | AlbumResponseDto[] | PersonResponseDto[]>
|
||||
>({});
|
||||
|
||||
// Fetch metadata for existing picker values (albums/people)
|
||||
$effect(() => {
|
||||
if (!components) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchMetadata = async () => {
|
||||
const metadataUpdates: Record<
|
||||
string,
|
||||
AlbumResponseDto | PersonResponseDto | AlbumResponseDto[] | PersonResponseDto[]
|
||||
> = {};
|
||||
|
||||
for (const [key, component] of Object.entries(components)) {
|
||||
const value = actualConfig[key];
|
||||
if (!value || pickerMetadata[key]) {
|
||||
continue; // Skip if no value or already loaded
|
||||
}
|
||||
|
||||
const isAlbumPicker = component.subType === 'album-picker';
|
||||
const isPeoplePicker = component.subType === 'people-picker';
|
||||
|
||||
if (!isAlbumPicker && !isPeoplePicker) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (Array.isArray(value) && value.length > 0) {
|
||||
// Multiple selection
|
||||
if (isAlbumPicker) {
|
||||
const albums = await Promise.all(value.map((id) => getAlbumInfo({ id })));
|
||||
metadataUpdates[key] = albums;
|
||||
} else if (isPeoplePicker) {
|
||||
const people = await Promise.all(value.map((id) => getPerson({ id })));
|
||||
metadataUpdates[key] = people;
|
||||
}
|
||||
} else if (typeof value === 'string' && value) {
|
||||
// Single selection
|
||||
if (isAlbumPicker) {
|
||||
const album = await getAlbumInfo({ id: value });
|
||||
metadataUpdates[key] = album;
|
||||
} else if (isPeoplePicker) {
|
||||
const person = await getPerson({ id: value });
|
||||
metadataUpdates[key] = person;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch metadata for ${key}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(metadataUpdates).length > 0) {
|
||||
pickerMetadata = { ...pickerMetadata, ...metadataUpdates };
|
||||
}
|
||||
};
|
||||
|
||||
void fetchMetadata();
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
// Initialize config for actions/filters with empty schemas
|
||||
if (configKey && !config[configKey]) {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
{#if animated}
|
||||
<div class="absolute inset-0 bg-linear-to-b from-transparent via-primary to-transparent flow-pulse"></div>
|
||||
{/if}
|
||||
<!-- Connection nodes -->
|
||||
<div class="absolute left-1/2 top-0 -translate-x-1/2 -translate-y-1/2">
|
||||
<div class="h-2 w-2 rounded-full bg-primary shadow-sm shadow-primary/50"></div>
|
||||
</div>
|
||||
|
||||
71
web/src/lib/components/workflows/workflow-json-editor.svelte
Normal file
71
web/src/lib/components/workflows/workflow-json-editor.svelte
Normal file
@@ -0,0 +1,71 @@
|
||||
<script lang="ts">
|
||||
import { themeManager } from '$lib/managers/theme-manager.svelte';
|
||||
import type { WorkflowPayload } from '$lib/services/workflow.service';
|
||||
import { Button, Card, CardBody, CardDescription, CardHeader, CardTitle, Icon, VStack } from '@immich/ui';
|
||||
import { mdiCodeJson } from '@mdi/js';
|
||||
import { JSONEditor, Mode, type Content, type OnChangeStatus } from 'svelte-jsoneditor';
|
||||
|
||||
interface Props {
|
||||
jsonContent: WorkflowPayload;
|
||||
onApply: () => void;
|
||||
onContentChange: (content: WorkflowPayload) => void;
|
||||
}
|
||||
|
||||
let { jsonContent, onApply, onContentChange }: Props = $props();
|
||||
|
||||
let content: Content = $derived({ json: jsonContent });
|
||||
let canApply = $state(false);
|
||||
let editorClass = $derived(themeManager.isDark ? 'jse-theme-dark' : '');
|
||||
|
||||
const handleChange = (updated: Content, _: Content, status: OnChangeStatus) => {
|
||||
if (status.contentErrors) {
|
||||
return;
|
||||
}
|
||||
|
||||
canApply = true;
|
||||
|
||||
if ('text' in updated && updated.text !== undefined) {
|
||||
try {
|
||||
const parsed = JSON.parse(updated.text);
|
||||
onContentChange(parsed);
|
||||
} catch (error_) {
|
||||
console.error('Invalid JSON in text mode:', error_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleApply = () => {
|
||||
onApply();
|
||||
canApply = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<VStack gap={4}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex items-start gap-3">
|
||||
<Icon icon={mdiCodeJson} size="20" class="mt-1" />
|
||||
<div class="flex flex-col">
|
||||
<CardTitle>Workflow JSON</CardTitle>
|
||||
<CardDescription>Edit the workflow configuration directly in JSON format</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
<Button size="small" color="primary" onclick={handleApply} disabled={!canApply}>Apply Changes</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<VStack gap={2}>
|
||||
<div
|
||||
class="w-full h-[600px] rounded-lg overflow-hidden border border-gray-300 dark:border-gray-600 {editorClass}"
|
||||
>
|
||||
<JSONEditor {content} onChange={handleChange} mainMenuBar={false} mode={Mode.text} />
|
||||
</div>
|
||||
</VStack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</VStack>
|
||||
|
||||
<style>
|
||||
@import 'svelte-jsoneditor/themes/jse-theme-dark.css';
|
||||
</style>
|
||||
Reference in New Issue
Block a user