mirror of
https://github.com/immich-app/immich.git
synced 2025-12-05 20:40:29 -08:00
Compare commits
2 Commits
12ecd65e61
...
91f2b5a387
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91f2b5a387 | ||
|
|
288ba44825 |
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"get_people_error": "Error getting people",
|
||||
"about": "About",
|
||||
"account": "Account",
|
||||
"account_settings": "Account Settings",
|
||||
@@ -1160,6 +1159,7 @@
|
||||
"general": "General",
|
||||
"geolocation_instruction_location": "Click on an asset with GPS coordinates to use its location, or select a location directly from the map",
|
||||
"get_help": "Get Help",
|
||||
"get_people_error": "Error getting people",
|
||||
"get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network",
|
||||
"getting_started": "Getting Started",
|
||||
"go_back": "Go back",
|
||||
@@ -2256,6 +2256,7 @@
|
||||
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
||||
"viewer_unstack": "Un-Stack",
|
||||
"visibility_changed": "Visibility changed for {count, plural, one {# person} other {# people}}",
|
||||
"visual": "Visual",
|
||||
"visual_builder": "Visual builder",
|
||||
"waiting": "Waiting",
|
||||
"warning": "Warning",
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
<script lang="ts">
|
||||
import AlbumPickerModal from '$lib/modals/AlbumPickerModal.svelte';
|
||||
import PeoplePickerModal from '$lib/modals/PeoplePickerModal.svelte';
|
||||
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
||||
import { formatLabel, getComponentFromSchema, type ComponentConfig } from '$lib/utils/workflow';
|
||||
import { getAlbumInfo, getPerson, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
|
||||
import { Button, Field, Input, MultiSelect, Select, Switch, Text, modalManager, type SelectItem } from '@immich/ui';
|
||||
import { mdiPlus } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { formatLabel, getComponentFromSchema } from '$lib/utils/workflow';
|
||||
import { Field, Input, MultiSelect, Select, Switch, Text, type SelectItem } from '@immich/ui';
|
||||
import WorkflowPickerField from './WorkflowPickerField.svelte';
|
||||
|
||||
interface Props {
|
||||
schema: object | null;
|
||||
@@ -33,18 +28,6 @@
|
||||
let selectValue = $state<SelectItem>();
|
||||
let switchValue = $state<boolean>(false);
|
||||
let multiSelectValue = $state<SelectItem[]>([]);
|
||||
let pickerMetadata = $state<
|
||||
Record<string, AlbumResponseDto | PersonResponseDto | AlbumResponseDto[] | PersonResponseDto[]>
|
||||
>({});
|
||||
|
||||
// Fetch metadata for existing picker values (albums/people)
|
||||
$effect(() => {
|
||||
if (!components) {
|
||||
return;
|
||||
}
|
||||
|
||||
void fetchMetadata(components);
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
// Initialize config for actions/filters with empty schemas
|
||||
@@ -99,212 +82,24 @@
|
||||
}
|
||||
});
|
||||
|
||||
const fetchMetadata = async (components: Record<string, ComponentConfig>) => {
|
||||
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 };
|
||||
}
|
||||
};
|
||||
|
||||
const handleAlbumPicker = async (key: string, multiple: boolean) => {
|
||||
const albums = await modalManager.show(AlbumPickerModal, { shared: false });
|
||||
if (albums && albums.length > 0) {
|
||||
const value = multiple ? albums.map((a) => a.id) : albums[0].id;
|
||||
updateConfig(key, value);
|
||||
pickerMetadata = {
|
||||
...pickerMetadata,
|
||||
[key]: multiple ? albums : albums[0],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const handlePeoplePicker = async (key: string, multiple: boolean) => {
|
||||
const currentIds = (actualConfig[key] as string[] | undefined) ?? [];
|
||||
const excludedIds = multiple ? currentIds : [];
|
||||
const people = await modalManager.show(PeoplePickerModal, { multiple, excludedIds });
|
||||
if (people && people.length > 0) {
|
||||
const value = multiple ? people.map((p) => p.id) : people[0].id;
|
||||
updateConfig(key, value);
|
||||
pickerMetadata = {
|
||||
...pickerMetadata,
|
||||
[key]: multiple ? people : people[0],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const removeSelection = (key: string) => {
|
||||
const { [key]: _, ...rest } = actualConfig;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { [key]: _removed, ...restMetadata } = pickerMetadata;
|
||||
|
||||
config = configKey ? { ...config, [configKey]: rest } : rest;
|
||||
pickerMetadata = restMetadata;
|
||||
};
|
||||
|
||||
const removeItemFromSelection = (key: string, itemId: string) => {
|
||||
const currentIds = actualConfig[key] as string[];
|
||||
const currentMetadata = pickerMetadata[key] as (AlbumResponseDto | PersonResponseDto)[];
|
||||
|
||||
updateConfig(
|
||||
key,
|
||||
currentIds.filter((id) => id !== itemId),
|
||||
);
|
||||
pickerMetadata = {
|
||||
...pickerMetadata,
|
||||
[key]: currentMetadata.filter((item) => item.id !== itemId) as AlbumResponseDto[] | PersonResponseDto[],
|
||||
};
|
||||
};
|
||||
|
||||
const renderPicker = (subType: 'album-picker' | 'people-picker', multiple: boolean) => {
|
||||
const isAlbum = subType === 'album-picker';
|
||||
const handler = isAlbum ? handleAlbumPicker : handlePeoplePicker;
|
||||
const selectSingleLabel = isAlbum ? 'select_album' : 'select_person';
|
||||
const selectMultiLabel = isAlbum ? 'select_albums' : 'select_people';
|
||||
|
||||
const buttonText = multiple ? $t(selectMultiLabel) : $t(selectSingleLabel);
|
||||
|
||||
return { handler, buttonText };
|
||||
};
|
||||
const isPickerField = (subType: string | undefined) => subType === 'album-picker' || subType === 'people-picker';
|
||||
</script>
|
||||
|
||||
{#snippet pickerItemCard(
|
||||
item: AlbumResponseDto | PersonResponseDto,
|
||||
isAlbum: boolean,
|
||||
size: 'large' | 'small',
|
||||
onRemove: () => void,
|
||||
)}
|
||||
{@const sizeClass = size === 'large' ? 'h-16 w-16' : 'h-12 w-12'}
|
||||
{@const textSizeClass = size === 'large' ? 'font-medium' : 'font-medium text-sm'}
|
||||
{@const iconSizeClass = size === 'large' ? 'h-5 w-5' : 'h-4 w-4'}
|
||||
{@const countSizeClass = size === 'large' ? 'text-sm' : 'text-xs'}
|
||||
|
||||
<div
|
||||
class="flex items-center gap-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 p-3 shadow-sm"
|
||||
>
|
||||
<div class="shrink-0">
|
||||
{#if isAlbum && 'albumThumbnailAssetId' in item}
|
||||
{#if item.albumThumbnailAssetId}
|
||||
<img
|
||||
src={getAssetThumbnailUrl(item.albumThumbnailAssetId)}
|
||||
alt={item.albumName}
|
||||
class="{sizeClass} rounded-lg object-cover"
|
||||
/>
|
||||
{:else}
|
||||
<div class="{sizeClass} rounded-lg bg-gray-200 dark:bg-gray-700"></div>
|
||||
{/if}
|
||||
{:else if !isAlbum && 'name' in item}
|
||||
<img src={getPeopleThumbnailUrl(item)} alt={item.name} class="{sizeClass} rounded-full object-cover" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="{textSizeClass} text-gray-900 dark:text-gray-100 truncate">
|
||||
{isAlbum && 'albumName' in item ? item.albumName : 'name' in item ? item.name : ''}
|
||||
</p>
|
||||
{#if isAlbum && 'assetCount' in item}
|
||||
<p class="{countSizeClass} text-gray-500 dark:text-gray-400">
|
||||
{$t('items_count', { values: { count: item.assetCount } })}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onclick={onRemove}
|
||||
class="shrink-0 rounded-full p-1.5 text-gray-500 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
|
||||
aria-label={$t('remove')}
|
||||
>
|
||||
<svg class={iconSizeClass} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet pickerField(
|
||||
subType: string,
|
||||
key: string,
|
||||
label: string,
|
||||
component: { required?: boolean; description?: string },
|
||||
multiple: boolean,
|
||||
)}
|
||||
{@const picker = renderPicker(subType as 'album-picker' | 'people-picker', multiple)}
|
||||
{@const metadata = pickerMetadata[key]}
|
||||
{@const isAlbum = subType === 'album-picker'}
|
||||
|
||||
<Field
|
||||
{label}
|
||||
required={component.required}
|
||||
description={component.description}
|
||||
requiredIndicator={component.required}
|
||||
>
|
||||
<div class="flex flex-col gap-3">
|
||||
{#if metadata && !Array.isArray(metadata)}
|
||||
{@render pickerItemCard(metadata, isAlbum, 'large', () => removeSelection(key))}
|
||||
{:else if metadata && Array.isArray(metadata) && metadata.length > 0}
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each metadata as item (item.id)}
|
||||
{@render pickerItemCard(item, isAlbum, 'small', () => removeItemFromSelection(key, item.id))}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<Button size="small" variant="outline" leadingIcon={mdiPlus} onclick={() => picker.handler(key, multiple)}>
|
||||
{picker.buttonText}
|
||||
</Button>
|
||||
</div>
|
||||
</Field>
|
||||
{/snippet}
|
||||
|
||||
{#if components}
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each Object.entries(components) as [key, component] (key)}
|
||||
{@const label = component.title || component.label || key}
|
||||
|
||||
<div class="flex flex-col gap-1 bg-light-50 border p-4 rounded-xl">
|
||||
<div class="flex flex-col gap-1 border bg-light p-4 rounded-xl">
|
||||
<!-- Select component -->
|
||||
{#if component.type === 'select'}
|
||||
{#if component.subType === 'album-picker' || component.subType === 'people-picker'}
|
||||
{@render pickerField(component.subType, key, label, component, false)}
|
||||
{#if isPickerField(component.subType)}
|
||||
<WorkflowPickerField
|
||||
{component}
|
||||
configKey={key}
|
||||
value={actualConfig[key] as string | string[]}
|
||||
onchange={(value) => updateConfig(key, value)}
|
||||
/>
|
||||
{:else}
|
||||
{@const options = component.options?.map((opt) => {
|
||||
return { label: opt.label, value: String(opt.value) };
|
||||
@@ -322,8 +117,13 @@
|
||||
|
||||
<!-- MultiSelect component -->
|
||||
{:else if component.type === 'multiselect'}
|
||||
{#if component.subType === 'album-picker' || component.subType === 'people-picker'}
|
||||
{@render pickerField(component.subType, key, label, component, true)}
|
||||
{#if isPickerField(component.subType)}
|
||||
<WorkflowPickerField
|
||||
{component}
|
||||
configKey={key}
|
||||
value={actualConfig[key] as string | string[]}
|
||||
onchange={(value) => updateConfig(key, value)}
|
||||
/>
|
||||
{:else}
|
||||
{@const options = component.options?.map((opt) => {
|
||||
return { label: opt.label, value: String(opt.value) };
|
||||
@@ -359,8 +159,13 @@
|
||||
</Field>
|
||||
|
||||
<!-- Text input -->
|
||||
{:else if component.subType === 'album-picker' || component.subType === 'people-picker'}
|
||||
{@render pickerField(component.subType, key, label, component, false)}
|
||||
{:else if isPickerField(component.subType)}
|
||||
<WorkflowPickerField
|
||||
{component}
|
||||
configKey={key}
|
||||
value={actualConfig[key] as string | string[]}
|
||||
onchange={(value) => updateConfig(key, value)}
|
||||
/>
|
||||
{:else}
|
||||
<Field
|
||||
{label}
|
||||
|
||||
162
web/src/lib/components/workflows/WorkflowPickerField.svelte
Normal file
162
web/src/lib/components/workflows/WorkflowPickerField.svelte
Normal file
@@ -0,0 +1,162 @@
|
||||
<script lang="ts">
|
||||
import AlbumPickerModal from '$lib/modals/AlbumPickerModal.svelte';
|
||||
import PeoplePickerModal from '$lib/modals/PeoplePickerModal.svelte';
|
||||
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
||||
import type { ComponentConfig } from '$lib/utils/workflow';
|
||||
import { getAlbumInfo, getPerson, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
|
||||
import { Button, Card, CardBody, Field, IconButton, modalManager, Text } from '@immich/ui';
|
||||
import { mdiClose, mdiPlus } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
interface Props {
|
||||
component: ComponentConfig;
|
||||
configKey: string;
|
||||
value: string | string[] | undefined;
|
||||
onchange: (value: string | string[]) => void;
|
||||
}
|
||||
|
||||
let { component, configKey, value = $bindable(), onchange }: Props = $props();
|
||||
|
||||
const label = $derived(component.title || component.label || configKey);
|
||||
const subType = $derived(component.subType as 'album-picker' | 'people-picker');
|
||||
const isAlbum = $derived(subType === 'album-picker');
|
||||
const multiple = $derived(component.type === 'multiselect' || Array.isArray(value));
|
||||
|
||||
let pickerMetadata = $state<AlbumResponseDto | PersonResponseDto | AlbumResponseDto[] | PersonResponseDto[]>();
|
||||
|
||||
// Fetch metadata for existing picker values (albums/people)
|
||||
$effect(() => {
|
||||
if (!value) {
|
||||
pickerMetadata = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
void fetchMetadata();
|
||||
});
|
||||
|
||||
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 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;
|
||||
onchange(newValue);
|
||||
pickerMetadata = multiple ? albums : albums[0];
|
||||
}
|
||||
} else {
|
||||
const currentIds = (Array.isArray(value) ? value : []) as string[];
|
||||
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;
|
||||
onchange(newValue);
|
||||
pickerMetadata = multiple ? people : people[0];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const removeSelection = () => {
|
||||
onchange(multiple ? [] : '');
|
||||
pickerMetadata = undefined;
|
||||
};
|
||||
|
||||
const removeItemFromSelection = (itemId: string) => {
|
||||
if (!Array.isArray(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newValue = value.filter((id) => id !== itemId);
|
||||
onchange(newValue);
|
||||
|
||||
if (Array.isArray(pickerMetadata)) {
|
||||
pickerMetadata = pickerMetadata.filter((item) => item.id !== itemId) as AlbumResponseDto[] | PersonResponseDto[];
|
||||
}
|
||||
};
|
||||
|
||||
const getButtonText = () => {
|
||||
if (isAlbum) {
|
||||
return multiple ? $t('select_albums') : $t('select_album');
|
||||
}
|
||||
return multiple ? $t('select_people') : $t('select_person');
|
||||
};
|
||||
</script>
|
||||
|
||||
{#snippet pickerItemCard(item: AlbumResponseDto | PersonResponseDto, onRemove: () => void)}
|
||||
<Card color="secondary">
|
||||
<CardBody class="flex items-center gap-3">
|
||||
<div class="shrink-0">
|
||||
{#if isAlbum && 'albumThumbnailAssetId' in item}
|
||||
{#if item.albumThumbnailAssetId}
|
||||
<img
|
||||
src={getAssetThumbnailUrl(item.albumThumbnailAssetId)}
|
||||
alt={item.albumName}
|
||||
class="h-12 w-12 rounded-lg object-cover"
|
||||
/>
|
||||
{:else}
|
||||
<div class="h-12 w-12 rounded-lg"></div>
|
||||
{/if}
|
||||
{:else if !isAlbum && 'name' in item}
|
||||
<img src={getPeopleThumbnailUrl(item)} alt={item.name} class="h-12 w-12 rounded-full object-cover" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<Text class="font-semibold truncate">
|
||||
{isAlbum && 'albumName' in item ? item.albumName : 'name' in item ? item.name : ''}
|
||||
</Text>
|
||||
{#if isAlbum && 'assetCount' in item}
|
||||
<Text size="small" color="muted">
|
||||
{$t('items_count', { values: { count: item.assetCount } })}
|
||||
</Text>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<IconButton
|
||||
type="button"
|
||||
onclick={onRemove}
|
||||
class="shrink-0"
|
||||
shape="round"
|
||||
aria-label={$t('remove')}
|
||||
icon={mdiClose}
|
||||
size="small"
|
||||
variant="ghost"
|
||||
color="secondary"
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
{/snippet}
|
||||
|
||||
<Field {label} required={component.required} description={component.description} requiredIndicator={component.required}>
|
||||
<div class="flex flex-col gap-3">
|
||||
{#if pickerMetadata && !Array.isArray(pickerMetadata)}
|
||||
{@render pickerItemCard(pickerMetadata, removeSelection)}
|
||||
{:else if pickerMetadata && Array.isArray(pickerMetadata) && pickerMetadata.length > 0}
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each pickerMetadata as item (item.id)}
|
||||
{@render pickerItemCard(item, () => removeItemFromSelection(item.id))}
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<Button size="small" variant="outline" leadingIcon={mdiPlus} onclick={handlePicker}>
|
||||
{getButtonText()}
|
||||
</Button>
|
||||
</div>
|
||||
</Field>
|
||||
@@ -1,16 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ConfirmModal } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
type Props = {
|
||||
onClose: (confirmed: boolean) => void;
|
||||
};
|
||||
|
||||
let { onClose }: Props = $props();
|
||||
</script>
|
||||
|
||||
<ConfirmModal
|
||||
confirmColor="danger"
|
||||
prompt={$t('workflow_delete_prompt')}
|
||||
onClose={(confirmed) => (confirmed ? onClose(true) : onClose(false))}
|
||||
/>
|
||||
@@ -1,16 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ConfirmModal } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
type Props = {
|
||||
onClose: (confirmed: boolean) => void;
|
||||
};
|
||||
|
||||
let { onClose }: Props = $props();
|
||||
</script>
|
||||
|
||||
<ConfirmModal
|
||||
confirmColor="primary"
|
||||
prompt={$t('workflow_navigation_prompt')}
|
||||
onClose={(confirmed) => (confirmed ? onClose(true) : onClose(false))}
|
||||
/>
|
||||
@@ -1,19 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ConfirmModal } from '@immich/ui';
|
||||
import { mdiLightningBolt } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
type Props = {
|
||||
onClose: (confirmed: boolean) => void;
|
||||
};
|
||||
|
||||
let { onClose }: Props = $props();
|
||||
</script>
|
||||
|
||||
<ConfirmModal
|
||||
confirmColor="primary"
|
||||
title={$t('change_trigger')}
|
||||
icon={mdiLightningBolt}
|
||||
prompt={$t('change_trigger_prompt')}
|
||||
onClose={(confirmed) => (confirmed ? onClose(true) : onClose(false))}
|
||||
/>
|
||||
@@ -2,7 +2,6 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import WorkflowDeleteConfirmModal from '$lib/modals/WorkflowDeleteConfirmModal.svelte';
|
||||
import type { WorkflowPayload } from '$lib/services/workflow.service';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import {
|
||||
@@ -109,7 +108,11 @@
|
||||
|
||||
const handleDeleteWorkflow = async (workflow: WorkflowResponseDto) => {
|
||||
try {
|
||||
const confirmed = await modalManager.show(WorkflowDeleteConfirmModal);
|
||||
const confirmed = await modalManager.showDialog({
|
||||
prompt: $t('workflow_delete_prompt'),
|
||||
confirmColor: 'danger',
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
import WorkflowTriggerCard from '$lib/components/workflows/WorkflowTriggerCard.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import AddWorkflowStepModal from '$lib/modals/AddWorkflowStepModal.svelte';
|
||||
import WorkflowNavigationConfirmModal from '$lib/modals/WorkflowNavigationConfirmModal.svelte';
|
||||
import WorkflowTriggerUpdateConfirmModal from '$lib/modals/WorkflowTriggerUpdateConfirmModal.svelte';
|
||||
import {
|
||||
buildWorkflowPayload,
|
||||
getActionsByContext,
|
||||
@@ -287,7 +285,11 @@
|
||||
};
|
||||
|
||||
const handleTriggerChange = async (newTrigger: PluginTriggerResponseDto) => {
|
||||
const confirmed = await modalManager.show(WorkflowTriggerUpdateConfirmModal);
|
||||
const confirmed = await modalManager.showDialog({
|
||||
prompt: $t('change_trigger_prompt'),
|
||||
title: $t('change_trigger'),
|
||||
confirmColor: 'primary',
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
@@ -303,7 +305,10 @@
|
||||
cancel();
|
||||
|
||||
modalManager
|
||||
.show(WorkflowNavigationConfirmModal)
|
||||
.showDialog({
|
||||
prompt: $t('workflow_navigation_prompt'),
|
||||
confirmColor: 'primary',
|
||||
})
|
||||
.then((isConfirmed) => {
|
||||
if (isConfirmed && to) {
|
||||
allowNavigation = true;
|
||||
@@ -440,7 +445,7 @@
|
||||
isDragging: draggedFilterIndex === index,
|
||||
isDragOver: dragOverFilterIndex === index,
|
||||
}}
|
||||
class="mb-4 cursor-move rounded-lg border-2 p-4 transition-all bg-light-50 border-dashed border-transparent hover:border-light-300"
|
||||
class="mb-4 cursor-move rounded-2xl border-2 p-4 transition-all bg-light-50 border-dashed hover:border-light-300"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
{@render cardOrder(index)}
|
||||
@@ -472,7 +477,7 @@
|
||||
leadingIcon={mdiPlus}
|
||||
onclick={() => handleAddStep('filter')}
|
||||
>
|
||||
Add more
|
||||
{$t('add_filter')}
|
||||
</Button>
|
||||
{/if}
|
||||
</CardBody>
|
||||
@@ -509,7 +514,7 @@
|
||||
isDragging: draggedActionIndex === index,
|
||||
isDragOver: dragOverActionIndex === index,
|
||||
}}
|
||||
class="mb-4 cursor-move rounded-lg border-2 p-4 transition-all bg-light-50 border-dashed border-transparent hover:border-light-300"
|
||||
class="mb-4 cursor-move rounded-2xl border-2 p-4 transition-all bg-light-50 border-dashed hover:border-light-300"
|
||||
>
|
||||
<div class="flex items-start gap-4">
|
||||
{@render cardOrder(index)}
|
||||
@@ -540,7 +545,7 @@
|
||||
leadingIcon={mdiPlus}
|
||||
onclick={() => handleAddStep('action')}
|
||||
>
|
||||
Add more
|
||||
{$t('add_action')}
|
||||
</Button>
|
||||
{/if}
|
||||
</CardBody>
|
||||
@@ -567,7 +572,7 @@
|
||||
leadingIcon={mdiViewDashboard}
|
||||
onclick={() => (viewMode = 'visual')}
|
||||
>
|
||||
Visual
|
||||
{$t('visual')}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
|
||||
Reference in New Issue
Block a user