mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 00:01:55 +00:00
fix(frontend): fix recompute all (#4161)
* fix(frontend): wip * fix(frontend): wip * fix(frontend): fix recompute all * fix(frontend): fix recompute all * fix(frontend): clean up * fix(frontend): fix how the onClick callback is called
This commit is contained in:
@@ -168,7 +168,13 @@
|
||||
previewTheme,
|
||||
debuggingComponents: writable({}),
|
||||
replaceStateFn: (path) => replaceState(path, $page.state),
|
||||
policy: policy
|
||||
policy: policy,
|
||||
recomputeAllContext: writable({
|
||||
loading: false,
|
||||
componentNumber: 0,
|
||||
refreshing: [],
|
||||
progress: 100
|
||||
})
|
||||
})
|
||||
|
||||
let scale = writable(100)
|
||||
|
||||
@@ -118,7 +118,13 @@
|
||||
debuggingComponents: writable({}),
|
||||
replaceStateFn,
|
||||
gotoFn,
|
||||
policy
|
||||
policy,
|
||||
recomputeAllContext: writable({
|
||||
loading: false,
|
||||
componentNumber: 0,
|
||||
refreshing: [],
|
||||
progress: 100
|
||||
})
|
||||
})
|
||||
|
||||
let previousSelectedIds: string[] | undefined = undefined
|
||||
@@ -200,11 +206,13 @@
|
||||
: 'max-w-7xl'} mx-auto"
|
||||
id="app-content"
|
||||
>
|
||||
{#if $appStore.grid && $appStore.hideLegacyTopBar !== true}
|
||||
{#if $appStore.grid}
|
||||
<div
|
||||
class={twMerge(
|
||||
'mx-auto',
|
||||
hideRefreshBar || $appStore?.norefreshbar ? 'invisible h-0 overflow-hidden' : ''
|
||||
hideRefreshBar || $appStore?.norefreshbar || $appStore.hideLegacyTopBar === true
|
||||
? 'invisible h-0 overflow-hidden'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full z-[1000] overflow-visible h-full">
|
||||
{#if $app.hideLegacyTopBar !== true}
|
||||
<div class={$app.hideLegacyTopBar ? 'hidden' : ''}>
|
||||
<div
|
||||
class="w-full sticky top-0 flex justify-between border-b {$componentActive
|
||||
? 'invisible'
|
||||
@@ -94,7 +94,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
style={$app.css?.['app']?.['grid']?.style}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<script lang="ts">
|
||||
import { Loader2, RefreshCw, TimerReset } from 'lucide-svelte'
|
||||
import Button from '../../common/button/Button.svelte'
|
||||
import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte'
|
||||
import { MenuItem } from '@rgossiaux/svelte-headlessui'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let componentNumber: number = 0
|
||||
export let interval: number | undefined = undefined
|
||||
export let refreshing: string[] = []
|
||||
export let progress: number = 100
|
||||
export let loading: boolean | undefined = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const items = [
|
||||
{
|
||||
displayName: 'Once',
|
||||
action: () => dispatch('setInter', undefined)
|
||||
},
|
||||
...[1, 2, 3, 4, 5, 6].map((i) => ({
|
||||
displayName: `Every ${i * 5} seconds`,
|
||||
action: () => dispatch('setInter', i * 5000)
|
||||
}))
|
||||
]
|
||||
</script>
|
||||
|
||||
<div class="border rounded-md overflow-hidden">
|
||||
<div class={twMerge('flex items-center')}>
|
||||
<Button
|
||||
disabled={componentNumber == 0}
|
||||
on:click
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
btnClasses={twMerge(
|
||||
'!rounded-none text-tertiary !text-2xs !border-r border-y-0 border-l-0 group'
|
||||
)}
|
||||
title="Refresh {componentNumber} component{componentNumber > 1 ? 's' : ''} {interval
|
||||
? `every ${interval / 1000} seconds`
|
||||
: 'Once'} {refreshing?.length > 0 ? `(live: ${refreshing?.join(', ')}))` : ''}"
|
||||
>
|
||||
<div class="z-10 flex flex-row items-center gap-2">
|
||||
{#if !loading}
|
||||
<RefreshCw size={14} />
|
||||
{:else}
|
||||
<Loader2 class="animate-spin text-blue-500" size={14} />
|
||||
{/if}
|
||||
|
||||
({componentNumber})
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<ButtonDropdown hasPadding={false}>
|
||||
<slot:fragment slot="buttonReplacement">
|
||||
<div class="flex flex-row gap-2 text-xs hover:bg-surface-hover px-2 items-center h-7">
|
||||
{#if interval}
|
||||
<Badge color="blue" small>
|
||||
{interval ? `Every ${interval / 1000}s` : 'Once'}
|
||||
</Badge>
|
||||
{/if}
|
||||
|
||||
<div class="flex justify-center items-center">
|
||||
<TimerReset size={14} />
|
||||
</div>
|
||||
</div>
|
||||
</slot:fragment>
|
||||
<svelte:fragment slot="label">
|
||||
<span
|
||||
class={twMerge('text-xs min-w-[2rem] ', interval ? 'text-blue-500' : 'text-tertiary')}
|
||||
>
|
||||
{interval ? `${interval / 1000}s` : 'Once'}
|
||||
</span>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="items">
|
||||
{#each items ?? [] as { }, index}
|
||||
<MenuItem
|
||||
on:click={() => {
|
||||
if (index === 0) {
|
||||
dispatch('setInter', undefined)
|
||||
} else {
|
||||
dispatch('setInter', index * 5000)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class={classNames(
|
||||
'!text-tertiary text-left px-4 py-2 gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
|
||||
)}
|
||||
>
|
||||
{#if index === 0}
|
||||
Once
|
||||
{:else}
|
||||
{`Every ${index * 5} seconds`}
|
||||
{/if}
|
||||
</div>
|
||||
</MenuItem>
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
</ButtonDropdown>
|
||||
</div>
|
||||
{#if interval}
|
||||
<div class="w-full bg-gray-200 rounded-full h-0.5 dark:bg-gray-700">
|
||||
<div
|
||||
class="bg-blue-300 h-0.5 rounded-full dark:bg-blue-500 transition-all"
|
||||
style="width: {progress}%"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -1,22 +1,11 @@
|
||||
<script lang="ts" context="module">
|
||||
let loading: Writable<boolean> = writable(false)
|
||||
let progress: Writable<number> = writable(100)
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Loader2, RefreshCw, TimerReset } from 'lucide-svelte'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import Button from '../../common/button/Button.svelte'
|
||||
import type { AppEditorContext, AppViewerContext } from '../types'
|
||||
import { allItems } from '../utils'
|
||||
import ButtonDropdown from '$lib/components/common/button/ButtonDropdown.svelte'
|
||||
import { MenuItem } from '@rgossiaux/svelte-headlessui'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import RecomputeAllButton from './RecomputeAllButton.svelte'
|
||||
|
||||
const { runnableComponents, app, initialized } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { runnableComponents, app, initialized, recomputeAllContext } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
const appEditorContext = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
@@ -29,7 +18,9 @@
|
||||
$initialized.initializedComponents?.length ==
|
||||
allItems($app.grid, $app.subgrids).length + ($app.hiddenInlineScripts?.length ?? 0) &&
|
||||
refresh()
|
||||
$: componentNumber = Object.values($runnableComponents).filter((x) => x.autoRefresh).length
|
||||
|
||||
$: $recomputeAllContext.componentNumber =
|
||||
Object.values($runnableComponents).filter((x) => x.autoRefresh).length ?? 0
|
||||
|
||||
onMount(() => {
|
||||
if (appEditorContext) {
|
||||
@@ -56,6 +47,7 @@
|
||||
if (stopAfterClear) return
|
||||
}
|
||||
refresh()
|
||||
|
||||
if (interval) {
|
||||
shouldRefresh = true
|
||||
timeout = setInterval(refresh, interval)
|
||||
@@ -64,21 +56,23 @@
|
||||
}
|
||||
|
||||
function startProgress() {
|
||||
progress.set(100)
|
||||
$recomputeAllContext.progress = 100
|
||||
if (progressTimer) clearInterval(progressTimer)
|
||||
progressTimer = setInterval(() => {
|
||||
progress.update((n) => {
|
||||
const newProgress = n - 100 / ((interval ?? 1000) / 100)
|
||||
if ($recomputeAllContext.progress) {
|
||||
const newProgress = $recomputeAllContext.progress - 100 / ((interval ?? 1000) / 100)
|
||||
if (newProgress <= 0) {
|
||||
return 0
|
||||
}
|
||||
return newProgress
|
||||
})
|
||||
|
||||
$recomputeAllContext.progress = newProgress
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
function setInter(inter: number | undefined) {
|
||||
interval = inter
|
||||
$recomputeAllContext.interval = inter
|
||||
onClick(!inter)
|
||||
}
|
||||
|
||||
@@ -90,8 +84,8 @@
|
||||
firstLoad = true
|
||||
isFirstLoad = true
|
||||
}
|
||||
$loading = true
|
||||
$progress = 100
|
||||
$recomputeAllContext.loading = true
|
||||
$recomputeAllContext.progress = 100
|
||||
|
||||
console.log('refresh all')
|
||||
refreshing = []
|
||||
@@ -118,13 +112,16 @@
|
||||
console.error('refresh error', id)
|
||||
refreshing = refreshing.filter((x) => x !== id)
|
||||
})
|
||||
.finally(() => {
|
||||
$recomputeAllContext.refreshing = refreshing
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
|
||||
Promise.all(promises).finally(() => {
|
||||
$loading = false
|
||||
$recomputeAllContext.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
@@ -141,108 +138,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
let items = [
|
||||
{
|
||||
displayName: 'Once',
|
||||
action: () => setInter(undefined)
|
||||
},
|
||||
...[1, 2, 3, 4, 5, 6].map((i) => ({
|
||||
displayName: `Every ${i * 5} seconds`,
|
||||
action: () => setInter(i * 5000)
|
||||
}))
|
||||
]
|
||||
onMount(() => {
|
||||
$recomputeAllContext = {
|
||||
onClick,
|
||||
setInter
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- {$initialized.initializedComponents?.join(', ')}
|
||||
{allItems($app.grid, $app.subgrids).length + $app.hiddenInlineScripts.length} -->
|
||||
<!-- {$initialized.initializedComponents} -->
|
||||
<!-- {allItems($app.grid, $app.subgrids)
|
||||
.map((x) => x.id)
|
||||
.filter((x) => !$initialized.initializedComponents?.includes(x))
|
||||
.sort()
|
||||
.join(', ')} -->
|
||||
<!-- {allItems($app.grid, $app.subgrids).map((x) => x.id)} -->
|
||||
|
||||
<div class=" border rounded-md overflow-hidden">
|
||||
<div class={twMerge('flex items-center')}>
|
||||
<Button
|
||||
disabled={componentNumber == 0}
|
||||
on:click={() => onClick()}
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
btnClasses={twMerge(
|
||||
'!rounded-none text-tertiary !text-2xs !border-r border-y-0 border-l-0 group'
|
||||
)}
|
||||
title="Refresh {componentNumber} component{componentNumber > 1 ? 's' : ''} {interval
|
||||
? `every ${interval / 1000} seconds`
|
||||
: 'Once'} {refreshing.length > 0 ? `(live: ${refreshing.join(', ')}))` : ''}"
|
||||
>
|
||||
<div class="z-10 flex flex-row items-center gap-2">
|
||||
{#if !$loading}
|
||||
<RefreshCw size={14} />
|
||||
{:else}
|
||||
<Loader2 class="animate-spin text-blue-500" size={14} />
|
||||
{/if}
|
||||
|
||||
({componentNumber})
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<ButtonDropdown hasPadding={false}>
|
||||
<slot:fragment slot="buttonReplacement">
|
||||
<div class="flex flex-row gap-2 text-xs hover:bg-surface-hover px-2 items-center h-7">
|
||||
{#if interval}
|
||||
<Badge color="blue" small>
|
||||
{interval ? `Every ${interval / 1000}s` : 'Once'}
|
||||
</Badge>
|
||||
{/if}
|
||||
|
||||
<div class="flex justify-center items-center">
|
||||
<TimerReset size={14} />
|
||||
</div>
|
||||
</div>
|
||||
</slot:fragment>
|
||||
<svelte:fragment slot="label">
|
||||
<span
|
||||
class={twMerge('text-xs min-w-[2rem] ', interval ? 'text-blue-500' : 'text-tertiary')}
|
||||
>
|
||||
{interval ? `${interval / 1000}s` : 'Once'}
|
||||
</span>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="items">
|
||||
{#each items ?? [] as { }, index}
|
||||
<MenuItem
|
||||
on:click={() => {
|
||||
if (index === 0) {
|
||||
setInter(undefined)
|
||||
} else {
|
||||
setInter(index * 5000)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class={classNames(
|
||||
'!text-tertiary text-left px-4 py-2 gap-2 cursor-pointer hover:bg-surface-hover !text-xs font-semibold'
|
||||
)}
|
||||
>
|
||||
{#if index === 0}
|
||||
Once
|
||||
{:else}
|
||||
{`Every ${index * 5} seconds`}
|
||||
{/if}
|
||||
</div>
|
||||
</MenuItem>
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
</ButtonDropdown>
|
||||
</div>
|
||||
{#if interval}
|
||||
<div class="w-full bg-gray-200 rounded-full h-0.5 dark:bg-gray-700">
|
||||
<div
|
||||
class="bg-blue-300 h-0.5 rounded-full dark:bg-blue-500 transition-all"
|
||||
style="width: {$progress}%"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<RecomputeAllButton
|
||||
on:click={() => onClick()}
|
||||
{interval}
|
||||
{refreshing}
|
||||
componentNumber={$recomputeAllContext.componentNumber ?? 0}
|
||||
loading={$recomputeAllContext.loading}
|
||||
progress={$recomputeAllContext.progress}
|
||||
on:setInter={(e) => {
|
||||
setInter(e.detail)
|
||||
onClick(false)
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '../types'
|
||||
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
|
||||
import { dfs } from './appUtils'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import RecomputeAllButton from './RecomputeAllButton.svelte'
|
||||
|
||||
export let containerClass: string | undefined = undefined
|
||||
export let containerStyle: string | undefined = undefined
|
||||
|
||||
const { selectedComponent, app, connectingInput, allIdsInPath, bgRuns } =
|
||||
const { selectedComponent, app, connectingInput, allIdsInPath, bgRuns, recomputeAllContext } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let previousSelectedIds: string[] | undefined = undefined
|
||||
@@ -47,7 +47,17 @@
|
||||
</div>
|
||||
<div>
|
||||
{#if !$connectingInput.opened}
|
||||
<RecomputeAllComponents />
|
||||
<RecomputeAllButton
|
||||
interval={$recomputeAllContext.interval}
|
||||
componentNumber={$recomputeAllContext.componentNumber ?? 0}
|
||||
on:click={() => $recomputeAllContext.onClick?.()}
|
||||
on:setInter={(e) => {
|
||||
$recomputeAllContext.setInter?.(e.detail)
|
||||
}}
|
||||
refreshing={$recomputeAllContext.refreshing}
|
||||
progress={$recomputeAllContext.progress}
|
||||
loading={$recomputeAllContext.loading}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -274,6 +274,16 @@ export type AppViewerContext = {
|
||||
replaceStateFn?: ((url: string) => void) | undefined
|
||||
gotoFn?: ((url: string, opt?: Record<string, any> | undefined) => void) | undefined
|
||||
policy: Policy
|
||||
|
||||
recomputeAllContext: Writable<{
|
||||
onClick?: () => void
|
||||
componentNumber?: number | undefined
|
||||
interval?: number | undefined
|
||||
refreshing?: string[] | undefined
|
||||
setInter?: (interval: number) => void | undefined
|
||||
progress?: number | undefined
|
||||
loading?: boolean | undefined
|
||||
}>
|
||||
}
|
||||
|
||||
export type AppEditorContext = {
|
||||
|
||||
Reference in New Issue
Block a user