mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-26 08:01:38 +00:00
feat(frontend): add fork/save buttons + consistent styling for slider/range
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
import { setContext } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import CenteredPage from './CenteredPage.svelte'
|
||||
import { Button } from './common'
|
||||
import { Button, ButtonPopup, ButtonPopupItem } from './common'
|
||||
import { dirtyStore } from './common/confirmationModal/dirtyStore'
|
||||
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
|
||||
import { OFFSET } from './CronInput.svelte'
|
||||
@@ -26,8 +26,6 @@
|
||||
export let initialArgs: Record<string, any> = {}
|
||||
export let loading = false
|
||||
|
||||
let pathError = ''
|
||||
|
||||
async function createSchedule(path: string) {
|
||||
const { cron, args, enabled } = $scheduleStore
|
||||
|
||||
@@ -49,7 +47,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function saveFlow(): Promise<void> {
|
||||
let loadingSave = false
|
||||
|
||||
async function saveFlow(leave: boolean): Promise<void> {
|
||||
loadingSave = true
|
||||
const flow = cleanInputs($flowStore)
|
||||
const { cron, args, enabled } = $scheduleStore
|
||||
$dirtyStore = false
|
||||
@@ -111,7 +112,12 @@
|
||||
await createSchedule(flow.path)
|
||||
}
|
||||
}
|
||||
goto(`/flows/get/${$flowStore.path}?workspace_id=${$workspaceStore}`)
|
||||
loadingSave = false
|
||||
if (leave) {
|
||||
goto(`/flows/get/${$flowStore.path}?workspace_id=${$workspaceStore}`)
|
||||
} else if (initialPath !== $flowStore.path) {
|
||||
goto(`/flows/edit/${$flowStore.path}?workspace_id=${$workspaceStore}`)
|
||||
}
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
@@ -139,7 +145,7 @@
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const selectedIdStore = writable<string>(selectedId)
|
||||
const selectedIdStore = writable<string>(selectedId ?? 'settings-metadata')
|
||||
|
||||
const scheduleStore = writable<Schedule>({ args: {}, cron: '', enabled: false })
|
||||
const previewArgsStore = writable<Record<string, any>>(initialArgs)
|
||||
@@ -237,12 +243,22 @@
|
||||
<div class="flex flex-row space-x-2">
|
||||
<FlowPreviewButtons />
|
||||
<div class="center-center">
|
||||
<Button
|
||||
disabled={pathError != ''}
|
||||
startIcon={{ icon: faSave }}
|
||||
<ButtonPopup
|
||||
loading={loadingSave}
|
||||
size="sm"
|
||||
on:click={saveFlow}>Save</Button
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={() => saveFlow(false)}
|
||||
>
|
||||
<svelte:fragment slot="main">Save</svelte:fragment>
|
||||
<ButtonPopupItem on:click={() => saveFlow(true)}>Save and exit</ButtonPopupItem>
|
||||
{#if initialPath != ''}
|
||||
<ButtonPopupItem
|
||||
on:click={() => {
|
||||
window.open(`/flows/add?template=${initialPath}`)
|
||||
}}>Fork</ButtonPopupItem
|
||||
>
|
||||
{/if}
|
||||
</ButtonPopup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
{#if showOptions}
|
||||
<ul
|
||||
class="options"
|
||||
transition:fly={{ duration: 200, y: 5 }}
|
||||
transition:fly|local={{ duration: 200, y: 5 }}
|
||||
on:mousedown|preventDefault={handleOptionMousedown}
|
||||
>
|
||||
{#each filtered as option}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import CenteredPage from './CenteredPage.svelte'
|
||||
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
|
||||
import { dirtyStore } from './common/confirmationModal/dirtyStore'
|
||||
import { Button, Kbd } from './common'
|
||||
import { Button, ButtonPopup, ButtonPopupItem, Kbd } from './common'
|
||||
import { faChevronDown, faChevronUp, faPen, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import Breadcrumb from './common/breadcrumb/Breadcrumb.svelte'
|
||||
import LanguageIcon from './common/languageIcons/LanguageIcon.svelte'
|
||||
@@ -54,7 +54,9 @@
|
||||
script.content = initialCode(language, kind, template)
|
||||
}
|
||||
|
||||
async function editScript(): Promise<void> {
|
||||
let loadingSave = false
|
||||
async function editScript(leave: boolean): Promise<void> {
|
||||
loadingSave = true
|
||||
try {
|
||||
$dirtyStore = false
|
||||
localStorage.removeItem(script.path)
|
||||
@@ -83,11 +85,17 @@
|
||||
kind: script.kind
|
||||
}
|
||||
})
|
||||
history.replaceState(history.state, '', `/scripts/edit/${newHash}?step=2`)
|
||||
goto(`/scripts/get/${newHash}?workspace_id=${$workspaceStore}`)
|
||||
if (leave) {
|
||||
history.replaceState(history.state, '', `/scripts/edit/${newHash}?step=2`)
|
||||
goto(`/scripts/get/${newHash}?workspace_id=${$workspaceStore}`)
|
||||
} else {
|
||||
await goto(`/scripts/edit/${newHash}?step=2`)
|
||||
script.hash = newHash
|
||||
}
|
||||
} catch (error) {
|
||||
sendUserToast(`Impossible to save the script: ${error.body}`, true)
|
||||
sendUserToast(`Impossible to save the script: ${error.body || error.message}`, true)
|
||||
}
|
||||
loadingSave = false
|
||||
}
|
||||
|
||||
async function changeStep(step: number) {
|
||||
@@ -182,14 +190,24 @@
|
||||
>
|
||||
Next {#if step == 1}<Kbd>Enter</Kbd>{/if}
|
||||
</Button>
|
||||
<Button
|
||||
<ButtonPopup
|
||||
loading={loadingSave}
|
||||
size="sm"
|
||||
variant={step == 1 ? 'border' : 'contained'}
|
||||
disabled={step === 1 && pathError !== ''}
|
||||
btnClasses={step == 1 && initialPath == '' ? 'invisible' : ''}
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={editScript}>Save</Button
|
||||
on:click={() => editScript(false)}
|
||||
>
|
||||
<svelte:fragment slot="main">Save</svelte:fragment>
|
||||
<ButtonPopupItem on:click={() => editScript(true)}>Save and exit</ButtonPopupItem>
|
||||
{#if initialPath != ''}
|
||||
<ButtonPopupItem
|
||||
on:click={() => {
|
||||
window.open(`/scripts/add?template=${initialPath}`)
|
||||
}}>Fork</ButtonPopupItem
|
||||
>
|
||||
{/if}
|
||||
</ButtonPopup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
{#if !view}<ChevronDown />{:else}<ChevronUp />{/if}</Button
|
||||
>
|
||||
{#if view}
|
||||
<div class="my-4 px-2" transition:slide><slot /></div>
|
||||
<div class="my-4 px-2" transition:slide|local><slot /></div>
|
||||
{/if}
|
||||
|
||||
@@ -54,7 +54,12 @@
|
||||
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
|
||||
{+min}
|
||||
</span>
|
||||
<div class="grow" on:pointerdown|stopPropagation>
|
||||
<div
|
||||
class="grow"
|
||||
style="--range-handle-focus: {'#7e9abd'}; --range-handle: {'#7e9abd'}; {css?.bar?.style ??
|
||||
''}"
|
||||
on:pointerdown|stopPropagation
|
||||
>
|
||||
<RangeSlider
|
||||
bind:slider
|
||||
bind:values
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export const staticOutputs: string[] = ['result']
|
||||
export let customCss: ComponentCustomCSS<'handle' | 'limits' | 'value'> | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'handle' | 'limits' | 'value' | 'bar'> | undefined =
|
||||
undefined
|
||||
|
||||
const { app, worldStore, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
let min = 0
|
||||
@@ -60,7 +61,12 @@
|
||||
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
|
||||
{+min}
|
||||
</span>
|
||||
<div class="grow" on:pointerdown|stopPropagation={() => ($selectedComponent = id)}>
|
||||
<div
|
||||
class="grow"
|
||||
style="--range-handle-focus: {'#7e9abd'}; --range-handle: {'#7e9abd'}; {css?.bar?.style ??
|
||||
''}"
|
||||
on:pointerdown|stopPropagation={() => ($selectedComponent = id)}
|
||||
>
|
||||
<RangeSlider bind:slider bind:values {step} min={+min} max={+max} />
|
||||
</div>
|
||||
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
import { page } from '$app/stores'
|
||||
import { Alert, Badge, Drawer, DrawerContent } from '$lib/components/common'
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
ButtonPopup,
|
||||
ButtonPopupItem,
|
||||
Drawer,
|
||||
DrawerContent
|
||||
} from '$lib/components/common'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
|
||||
import Skeleton from '$lib/components/common/skeleton/Skeleton.svelte'
|
||||
@@ -537,14 +544,31 @@
|
||||
>
|
||||
<span class="hidden md:inline">Publish</span>
|
||||
</Button>
|
||||
<Button
|
||||
loading={loading.save}
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={save}
|
||||
color="dark"
|
||||
size="xs"
|
||||
>
|
||||
<span class="hidden md:inline">Save</span>
|
||||
</Button>
|
||||
{#if appPath == ''}
|
||||
<Button
|
||||
loading={loading.save}
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={save}
|
||||
color="dark"
|
||||
size="xs"
|
||||
>
|
||||
<span class="hidden md:inline">Save</span>
|
||||
</Button>
|
||||
{:else}
|
||||
<ButtonPopup
|
||||
loading={loading.save}
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={save}
|
||||
color="dark"
|
||||
size="xs"
|
||||
>
|
||||
<svelte:fragment slot="main">Save</svelte:fragment>
|
||||
<ButtonPopupItem
|
||||
on:click={() => {
|
||||
window.open(`/apps/add?template=${appPath}`)
|
||||
}}>Fork</ButtonPopupItem
|
||||
>
|
||||
</ButtonPopup>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1052,6 +1052,7 @@ Hello \${ctx.username}
|
||||
}
|
||||
},
|
||||
customCss: {
|
||||
bar: { style: '' },
|
||||
handle: { style: '' },
|
||||
limits: { class: '', style: '' },
|
||||
value: { class: '', style: '' }
|
||||
@@ -1098,6 +1099,7 @@ Hello \${ctx.username}
|
||||
},
|
||||
customCss: {
|
||||
handles: { style: '' },
|
||||
bar: { style: '' },
|
||||
limits: { class: '', style: '' },
|
||||
values: { class: '', style: '' }
|
||||
} as const,
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<input type="text" class="!pr-7" bind:value={value.style} on:focus />
|
||||
{#if value?.style}
|
||||
<button
|
||||
transition:fade={{ duration: 100 }}
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="absolute z-10 top-1.5 right-1 rounded-full p-1 duration-200 hover:bg-gray-200"
|
||||
aria-label="Remove styles"
|
||||
on:click|preventDefault|stopPropagation={() => reset('style')}
|
||||
@@ -44,7 +44,7 @@
|
||||
<input type="text" class="!pr-7" bind:value={value.class} on:focus />
|
||||
{#if value?.class}
|
||||
<button
|
||||
transition:fade={{ duration: 100 }}
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="absolute z-10 top-1.5 right-1 rounded-full p-1 duration-200 hover:bg-gray-200"
|
||||
aria-label="Remove classes"
|
||||
on:click|preventDefault|stopPropagation={() => reset('class')}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
export let startIcon: ButtonType.Icon | undefined = undefined
|
||||
export let endIcon: ButtonType.Icon | undefined = undefined
|
||||
export let spacingSize: ButtonType.Size = size
|
||||
export let loading = false
|
||||
|
||||
let ref: ButtonType.Element
|
||||
|
||||
@@ -33,6 +34,7 @@
|
||||
<div class="flex justy-start items-center">
|
||||
{#if $$slots.main}
|
||||
<Button
|
||||
{loading}
|
||||
{...commonProps}
|
||||
{href}
|
||||
{target}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
{#if retry}
|
||||
<Popover notClickable>
|
||||
<div
|
||||
transition:fade={{ duration: 200 }}
|
||||
transition:fade|local={{ duration: 200 }}
|
||||
class="center-center rounded border bg-white border-gray-400 text-gray-700 px-1 py-0.5"
|
||||
>
|
||||
<Repeat size={14} />
|
||||
@@ -47,7 +47,7 @@
|
||||
{#if earlyStop}
|
||||
<Popover notClickable>
|
||||
<div
|
||||
transition:fade={{ duration: 200 }}
|
||||
transition:fade|local={{ duration: 200 }}
|
||||
class="center-center bg-white rounded border border-gray-400 text-gray-700 px-1 py-0.5"
|
||||
>
|
||||
<Square size={14} />
|
||||
@@ -58,7 +58,7 @@
|
||||
{#if suspend}
|
||||
<Popover notClickable>
|
||||
<div
|
||||
transition:fade={{ duration: 200 }}
|
||||
transition:fade|local={{ duration: 200 }}
|
||||
class="center-center bg-white rounded border border-gray-400 text-gray-700 px-1 py-0.5"
|
||||
>
|
||||
<PhoneIncoming size={14} />
|
||||
@@ -69,7 +69,7 @@
|
||||
{#if sleep}
|
||||
<Popover notClickable>
|
||||
<div
|
||||
transition:fade={{ duration: 200 }}
|
||||
transition:fade|local={{ duration: 200 }}
|
||||
class="center-center bg-white rounded border border-gray-400 text-gray-700 px-1 py-0.5"
|
||||
>
|
||||
<Bed size={14} />
|
||||
|
||||
@@ -92,7 +92,9 @@
|
||||
return (
|
||||
pathname.startsWith('/apps') ||
|
||||
pathname.startsWith('/flows/add') ||
|
||||
pathname.startsWith('/flows/edit')
|
||||
pathname.startsWith('/flows/edit') ||
|
||||
pathname.startsWith('/scripts/add') ||
|
||||
pathname.startsWith('/scripts/edit')
|
||||
)
|
||||
}
|
||||
afterNavigate((n) => {
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
})
|
||||
Object.assign(flow, template)
|
||||
const oldPath = flow.path.split('/')
|
||||
flow.path = `u/${$userStore?.username}/${oldPath[oldPath.length - 1]}`
|
||||
flow.path = `u/${$userStore?.username.split('@')[0]}/${oldPath[oldPath.length - 1]}_fork`
|
||||
flow = flow
|
||||
goto('?', { replaceState: true })
|
||||
selectedId = 'settings-metadata'
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { page } from '$app/stores'
|
||||
import FlowBuilder from '$lib/components/FlowBuilder.svelte'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { decodeArgs, decodeState, emptySchema, sendUserToast } from '$lib/utils'
|
||||
import { decodeArgs, decodeState, sendUserToast } from '$lib/utils'
|
||||
import { initFlow } from '$lib/components/flows/flowStore'
|
||||
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
|
||||
import { goto } from '$app/navigation'
|
||||
|
||||
Reference in New Issue
Block a user