From ded54f2e68da09618c377cd699e0a2eaa53a63a8 Mon Sep 17 00:00:00 2001 From: Guilhem Date: Fri, 27 Jun 2025 16:26:03 +0100 Subject: [PATCH] fix(frontend): improve flow editor settings bar UX (#6049) * move settings and static inputs into top node * Move test button in the top nodes * Revert "Move test button in the top nodes" This reverts commit 1c8648a5383d79a6ec578568f682ef789aff514a. * Add error handler to top toolbar * nit * polishing * add flow settings to topbar dropdown * remove unused files * progress * progress * fixes * fix * fix * fix * fix --------- Co-authored-by: Ruben Fiszel Co-authored-by: Ruben Fiszel --- .../components/ChangeInstanceUsername.svelte | 18 +- .../src/lib/components/CronBuilder.svelte | 15 +- frontend/src/lib/components/CronInput.svelte | 402 +++++++++--------- frontend/src/lib/components/DBTable.svelte | 4 +- .../src/lib/components/DefaultTags.svelte | 19 +- .../components/DisplayResultControlBar.svelte | 37 +- .../components/ErrorOrRecoveryHandler.svelte | 2 +- .../src/lib/components/FlowBuilder.svelte | 32 +- .../src/lib/components/FolderEditor.svelte | 245 ++++++----- frontend/src/lib/components/FolderInfo.svelte | 10 +- frontend/src/lib/components/GroupInfo.svelte | 16 +- frontend/src/lib/components/ModuleTest.svelte | 25 +- .../src/lib/components/NoMainFuncBadge.svelte | 4 +- frontend/src/lib/components/Popover.svelte | 72 +++- .../src/lib/components/ResultJobLoader.svelte | 39 +- .../src/lib/components/SharedBadge.svelte | 54 ++- .../src/lib/components/TestJobLoader.svelte | 79 ++-- .../src/lib/components/TimelineBar.svelte | 48 ++- frontend/src/lib/components/Tooltip.svelte | 38 +- .../apps/components/display/AppNavbar.svelte | 50 ++- .../display/dbtable/AppDbExplorer.svelte | 150 ++++--- .../table/AppAggridExplorerTable.svelte | 127 +++--- .../display/table/AppAggridTable.svelte | 153 ++++--- .../display/table/AppTableFooter.svelte | 51 ++- .../display/table/SyncColumnDefs.svelte | 21 +- .../helpers/ComponentErrorHandler.svelte | 31 +- .../helpers/RefreshIndicator.svelte | 10 +- .../helpers/RunnableComponent.svelte | 2 +- .../components/inputs/AppMultiSelect.svelte | 3 - .../apps/editor/RecomputeAllWrapper.svelte | 28 +- .../apps/editor/SettingsPanel.svelte | 21 +- .../editor/component/ComponentInner.svelte | 8 +- .../componentsPanel/ComponentList.svelte | 77 ++-- .../settingsPanel/ContextVariables.svelte | 12 +- .../apps/editor/settingsPanel/DocLink.svelte | 14 +- .../inputEditor/StaticInputEditor.svelte | 24 +- .../shared/ScriptSettingsActions.svelte | 12 +- .../lib/components/common/alert/Alert.svelte | 76 ++-- .../common/alert/ConnectionIndicator.svelte | 16 +- .../common/button/RefreshButton.svelte | 12 +- .../common/drawer/DrawerContent.svelte | 18 +- .../waitTimeWarning/WaitTimeWarning.svelte | 24 +- .../copilot/chat/flow/FlowAIButton.svelte | 26 +- .../chat/flow/ModuleAcceptReject.svelte | 14 +- frontend/src/lib/components/custom_ui.ts | 1 + .../lib/components/flows/FlowEditor.svelte | 9 + .../flows/content/FlowInputs.svelte | 8 +- .../flows/content/FlowInputsQuick.svelte | 109 +++-- .../flows/content/FlowModuleHeader.svelte | 52 ++- .../flows/map/FlowConstantsItem.svelte | 23 - .../flows/map/FlowErrorHandlerItem.svelte | 124 +++--- .../flows/map/FlowModuleSchemaMap.svelte | 36 +- .../flows/map/FlowSettingsItem.svelte | 33 -- .../flows/map/FlowStickyNode.svelte | 89 ++++ .../flows/map/InsertModuleButton.svelte | 4 +- .../flows/pickers/FlowScriptPicker.svelte | 16 +- .../flows/pickers/PickHubScript.svelte | 58 +-- .../flows/pickers/PickHubScriptQuick.svelte | 84 ++-- .../pickers/WorkspaceScriptPickerQuick.svelte | 98 +++-- .../lib/components/graph/FlowGraphV2.svelte | 3 +- .../src/lib/components/jobs/JobPreview.svelte | 46 +- .../src/lib/components/runs/JobPreview.svelte | 64 +-- .../runs/NoWorkerWithTagWarning.svelte | 12 +- .../search/GlobalSearchModal.svelte | 61 +-- .../lib/components/search/RunsSearch.svelte | 8 +- .../lib/components/sidebar/MenuButton.svelte | 178 ++++---- .../lib/components/sidebar/MenuLink.svelte | 145 ++++--- .../lib/components/table/AutoDataTable.svelte | 64 +-- .../components/wizards/AgChartWizard.svelte | 35 +- .../components/wizards/AgGridWizard.svelte | 96 +++-- .../components/wizards/ChartJSWizard.svelte | 17 +- .../wizards/DBExplorerWizard.svelte | 62 +-- .../components/wizards/PlotlyWizard.svelte | 25 +- .../wizards/TableColumnWizard.svelte | 25 +- .../(root)/(logged)/folders/+page.svelte | 33 +- .../(root)/(logged)/run/[...run]/+page.svelte | 8 +- .../(logged)/runs/[...path]/+page.svelte | 1 - .../(root)/(logged)/service_logs/+page.svelte | 20 +- .../(root)/(logged)/variables/+page.svelte | 134 +++--- 79 files changed, 2297 insertions(+), 1623 deletions(-) delete mode 100644 frontend/src/lib/components/flows/map/FlowConstantsItem.svelte delete mode 100644 frontend/src/lib/components/flows/map/FlowSettingsItem.svelte create mode 100644 frontend/src/lib/components/flows/map/FlowStickyNode.svelte diff --git a/frontend/src/lib/components/ChangeInstanceUsername.svelte b/frontend/src/lib/components/ChangeInstanceUsername.svelte index 38ae001816..1fcd726cea 100644 --- a/frontend/src/lib/components/ChangeInstanceUsername.svelte +++ b/frontend/src/lib/components/ChangeInstanceUsername.svelte @@ -4,9 +4,13 @@ import { autoPlacement } from '@floating-ui/core' import ChangeInstanceUsernameInner from './ChangeInstanceUsernameInner.svelte' - export let email: string - export let username: string - export let isConflict = false + interface Props { + email: string + username: string + isConflict?: boolean + } + + let { email, username, isConflict = false }: Props = $props() - + {#snippet trigger()} - - + {/snippet} + {#snippet content()} close()} on:renamed /> - + {/snippet} diff --git a/frontend/src/lib/components/CronBuilder.svelte b/frontend/src/lib/components/CronBuilder.svelte index 89a4065b86..69b53463ff 100644 --- a/frontend/src/lib/components/CronBuilder.svelte +++ b/frontend/src/lib/components/CronBuilder.svelte @@ -3,19 +3,24 @@ import { Button } from './common' import { Clock } from 'lucide-svelte' import Popover from './meltComponents/Popover.svelte' + interface Props { + children?: import('svelte').Snippet<[any]> + } + + let { children }: Props = $props() - + {#snippet trigger()} - - + {/snippet} + {#snippet content({ close })}
- + {@render children?.({ close })}
-
+ {/snippet}
diff --git a/frontend/src/lib/components/CronInput.svelte b/frontend/src/lib/components/CronInput.svelte index 210e821c4d..a8bf9c7023 100644 --- a/frontend/src/lib/components/CronInput.svelte +++ b/frontend/src/lib/components/CronInput.svelte @@ -10,23 +10,35 @@ import Select from './select/Select.svelte' import MultiSelect from './select/MultiSelect.svelte' import { safeSelectItems } from './select/utils.svelte' + import { untrack } from 'svelte' - export let schedule: string - // export let offset: number = -60 * Math.floor(new Date().getTimezoneOffset() / 60) - export let timezone: string // = Intl.DateTimeFormat().resolvedOptions().timeZone - export let disabled = false - export let validCRON = true - export let cronVersion: string = 'v2' + interface Props { + schedule: string + // export let offset: number = -60 * Math.floor(new Date().getTimezoneOffset() / 60) + timezone: string // = Intl.DateTimeFormat().resolvedOptions().timeZone + disabled?: boolean + validCRON?: boolean + cronVersion?: string + } - let preview: string[] = [] + let { + schedule = $bindable(), + timezone = $bindable(), + disabled = false, + validCRON = $bindable(true), + cronVersion = $bindable('v2') + }: Props = $props() + + let preview: string[] = $state([]) // If the user has already entered a cron string, switching to the basic tab will override it. - let executeEvery: 'second' | 'minute' | 'hour' | 'day-month' | 'month' | 'day-week' = 'minute' + let executeEvery: 'second' | 'minute' | 'hour' | 'day-month' | 'month' | 'day-week' = + $state('minute') - let seconds = 30 - let minutes = 30 - let hours = 1 + let seconds = $state(30) + let minutes = $state(30) + let hours = $state(1) const daysOfMonthOptions: number[] = Array.from(Array(31).keys()).map((i) => i + 1) - let daysOfMonth: number[] = [] + let daysOfMonth: number[] = $state([]) // let lastDayOfMonth = false const monthsOfYearOptions: string[] = [ 'January', @@ -42,7 +54,7 @@ 'November', 'December' ] - let monthsOfYear: string[] = [] + let monthsOfYear: string[] = $state([]) const daysOfWeekOptions: string[] = [ 'Sunday', 'Monday', @@ -52,10 +64,8 @@ 'Friday', 'Saturday' ] - let daysOfWeek: string[] = [] - let UTCTime: string = '' - - $: !emptyString(schedule) && handleScheduleInput(schedule, timezone) + let daysOfWeek: string[] = $state([]) + let UTCTime: string = $state('') async function handleScheduleInput(input: string, timezone: string): Promise { try { @@ -73,9 +83,60 @@ } } - let nschedule = '' + let nschedule = $state('') - $: { + function formatDate(timezone) { + try { + return new Intl.DateTimeFormat('en-GB', { + weekday: 'short', + day: '2-digit', + month: 'short', + year: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + timeZone: timezone, + timeZoneName: 'short' + }).format + } catch (ee) { + sendUserToast( + `Invalid timezone: ${timezone}. Update your browser's timezone preference`, + true + ) + return new Intl.DateTimeFormat('en-GB', { + weekday: 'short', + day: '2-digit', + month: 'short', + year: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + timeZone: 'Europe/Paris', + timeZoneName: 'short' + }).format + } + } + + const items = Object.keys(timezones) + .map((key) => { + return Object.keys(timezones[key]) + .map((subKey) => { + return { + value: subKey, + label: subKey, + group: timezones[key][subKey][1] as string + } + }) + .flat() + }) + .flat() + $effect(() => { + schedule + untrack(() => { + !emptyString(schedule) && handleScheduleInput(schedule, timezone) + }) + }) + $effect(() => { // CRON string format // sec min hour day of month month day of week year // 0 30 9,12,15 1,15 May-Aug Mon,Wed,Fri 2018/2 @@ -133,65 +194,18 @@ } else if (executeEvery === 'day-week') { nschedule = `0 ${s_AtUTCMinutes} ${s_AtUTCHours} * * ${s_daysOfWeek}` } - } - - $: dateFormatter = formatDate(timezone) - - function formatDate(timezone) { - try { - return new Intl.DateTimeFormat('en-GB', { - weekday: 'short', - day: '2-digit', - month: 'short', - year: 'numeric', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - timeZone: timezone, - timeZoneName: 'short' - }).format - } catch (ee) { - sendUserToast( - `Invalid timezone: ${timezone}. Update your browser's timezone preference`, - true - ) - return new Intl.DateTimeFormat('en-GB', { - weekday: 'short', - day: '2-digit', - month: 'short', - year: 'numeric', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - timeZone: 'Europe/Paris', - timeZoneName: 'short' - }).format - } - } - - const items = Object.keys(timezones) - .map((key) => { - return Object.keys(timezones[key]) - .map((subKey) => { - return { - value: subKey, - label: subKey, - group: timezones[key][subKey][1] as string - } - }) - .flat() - }) - .flat() + }) + let dateFormatter = $derived(formatDate(timezone))