mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 08:00:45 +00:00
feat(frontend): App initial loading animations (#1176)
* feat(frontend): App initial loading animations * fix(frontend): Optional prop
This commit is contained in:
@@ -109,12 +109,10 @@
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
console.debug('cancelled')
|
||||
}
|
||||
|
||||
export async function clearCurrentJob() {
|
||||
if (currentId) {
|
||||
console.debug('clear')
|
||||
job = undefined
|
||||
await cancelJob()
|
||||
}
|
||||
@@ -180,7 +178,6 @@
|
||||
|
||||
async function syncer(id: string): Promise<void> {
|
||||
if (currentId != id) {
|
||||
console.debug('stop')
|
||||
return
|
||||
}
|
||||
syncIteration++
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing
|
||||
|
||||
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
|
||||
let result: any = undefined
|
||||
@@ -14,7 +15,7 @@
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
</script>
|
||||
|
||||
<RunnableWrapper flexWrap bind:result bind:componentInput {id}>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<div class="w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm">
|
||||
Results
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Bar, Line } from 'svelte-chartjs'
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
Title,
|
||||
@@ -12,7 +11,6 @@
|
||||
CategoryScale,
|
||||
BarElement
|
||||
} from 'chart.js'
|
||||
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
@@ -20,6 +18,7 @@
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
@@ -71,7 +70,7 @@
|
||||
<InputValue {id} input={configuration.theme} bind:value={theme} />
|
||||
<InputValue {id} input={configuration.line} bind:value={lineChart} />
|
||||
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:result>
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if result}
|
||||
{#if lineChart}
|
||||
<Line {data} {options} />
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
@@ -20,7 +21,7 @@
|
||||
bind:clientHeight={h}
|
||||
bind:clientWidth={w}
|
||||
>
|
||||
<RunnableWrapper autoRefresh flexWrap bind:componentInput {id} bind:result>
|
||||
<RunnableWrapper autoRefresh flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
{#key result}
|
||||
<iframe
|
||||
frameborder="0"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Doughnut, Pie } from 'svelte-chartjs'
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
Title,
|
||||
@@ -19,6 +18,7 @@
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<InputValue {id} input={configuration.theme} bind:value={theme} />
|
||||
<InputValue {id} input={configuration.doughnutStyle} bind:value={doughnut} />
|
||||
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:result>
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if result}
|
||||
{#if doughnut}
|
||||
<Doughnut {data} {options} />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import zoomPlugin from 'chartjs-plugin-zoom'
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
Title,
|
||||
@@ -12,7 +11,6 @@
|
||||
CategoryScale,
|
||||
BarElement
|
||||
} from 'chart.js'
|
||||
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import Scatter from 'svelte-chartjs/Scatter.svelte'
|
||||
@@ -21,6 +19,7 @@
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing
|
||||
|
||||
let zoomable = false
|
||||
let pannable = false
|
||||
@@ -70,7 +69,7 @@
|
||||
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
|
||||
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
|
||||
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:result>
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if result}
|
||||
<Scatter {data} {options} />
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Clipboard } from 'lucide-svelte'
|
||||
import { copyToClipboard } from '../../../../utils'
|
||||
import { copyToClipboard } from '../../../../utils'
|
||||
import Button from '../../../common/button/Button.svelte'
|
||||
import Popover from '../../../Popover.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
@@ -13,6 +13,7 @@ import { copyToClipboard } from '../../../../utils'
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = 'left'
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
@@ -59,7 +60,7 @@ import { copyToClipboard } from '../../../../utils'
|
||||
<InputValue {id} input={configuration.style} bind:value={style} />
|
||||
<InputValue {id} input={configuration.copyButton} bind:value={copyButton} />
|
||||
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:result>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
{#if !result || result === ''}
|
||||
<div class="text-gray-400 bg-gray-100 flex justify-center items-center h-full w-full">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import zoomPlugin from 'chartjs-plugin-zoom'
|
||||
import 'chartjs-adapter-date-fns'
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
Title,
|
||||
@@ -15,7 +14,6 @@
|
||||
TimeScale,
|
||||
LogarithmicScale
|
||||
} from 'chart.js'
|
||||
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import Scatter from 'svelte-chartjs/Scatter.svelte'
|
||||
@@ -24,6 +22,7 @@
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
@@ -85,7 +84,7 @@
|
||||
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
|
||||
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
|
||||
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:result>
|
||||
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if result}
|
||||
<Scatter {data} {options} />
|
||||
{/if}
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:result>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if !Plotly}
|
||||
<div class="p-2">
|
||||
<Loader2 class="animate-spin" />
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
@@ -52,7 +53,7 @@
|
||||
<InputValue {id} input={configuration.canvas} bind:value={canvas} />
|
||||
|
||||
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:result>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if !vegaEmbed}
|
||||
<div class="p-2">
|
||||
<Loader2 class="animate-spin" />
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
export let defaultUserInput = false
|
||||
export let flexWrap = false
|
||||
export let wrapperClass = ''
|
||||
export let initializing: boolean | undefined = undefined
|
||||
|
||||
const {
|
||||
worldStore,
|
||||
@@ -305,7 +306,7 @@
|
||||
/>
|
||||
|
||||
<div class="h-full flex relative flex-row flex-wrap {wrapperClass}">
|
||||
{#if autoRefresh === true}
|
||||
{#if !initializing && autoRefresh === true}
|
||||
<div class="flex absolute top-1 right-1">
|
||||
<RefreshButton componentId={id} />
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
export let componentInput: AppInput | undefined
|
||||
export let id: string
|
||||
export let result: any = undefined
|
||||
export let initializing = true
|
||||
|
||||
export let extraQueryParams: Record<string, any> = {}
|
||||
export let autoRefresh: boolean = true
|
||||
@@ -19,6 +20,10 @@
|
||||
export let runnableClass = ''
|
||||
|
||||
const { staticExporter, noBackend } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
$: if(initializing && result) {
|
||||
initializing = false
|
||||
}
|
||||
|
||||
if (noBackend && componentInput?.type == 'runnable') {
|
||||
result = componentInput?.['value']
|
||||
@@ -46,6 +51,7 @@
|
||||
{id}
|
||||
{extraQueryParams}
|
||||
{forceSchemaDisplay}
|
||||
{initializing}
|
||||
wrapperClass={runnableClass}
|
||||
>
|
||||
<slot />
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let actionButtons: (BaseAppComponent & ButtonComponent)[]
|
||||
export let initializing
|
||||
|
||||
export const staticOutputs: string[] = [
|
||||
'selectedRow',
|
||||
@@ -135,7 +136,7 @@
|
||||
|
||||
<InputValue {id} input={configuration.search} bind:value={search} />
|
||||
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:result>
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if Array.isArray(result) && result.every(isObject)}
|
||||
<div class="border border-gray-300 shadow-sm divide-y divide-gray-300 flex flex-col h-full">
|
||||
{#if search !== 'Disabled'}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { getContext } from 'svelte'
|
||||
import { fade } from 'svelte/transition';
|
||||
import { classNames } from '$lib/utils'
|
||||
import type { AppComponent, AppEditorContext } from '../types'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import BarChartComponent from '../components/dataDisplay/AppBarChart.svelte'
|
||||
import DisplayComponent from '../components/DisplayComponent.svelte'
|
||||
import TableComponent from '../components/table/AppTable.svelte'
|
||||
import TextComponent from '../components/dataDisplay/AppText.svelte'
|
||||
import type { AppComponent, AppEditorContext } from '../types'
|
||||
import ButtonComponent from '../components/buttons/AppButton.svelte'
|
||||
import PieChartComponent from '../components/dataDisplay/AppPieChart.svelte'
|
||||
import SelectComponent from '../components/selectInputs/AppSelect.svelte'
|
||||
@@ -28,8 +30,9 @@
|
||||
export let locked: boolean = false
|
||||
export let pointerdown: boolean = false
|
||||
|
||||
let hover = false
|
||||
const { staticOutputs, mode, connectingInput } = getContext<AppEditorContext>('AppEditorContext')
|
||||
let hover = false
|
||||
let initializing
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -64,54 +67,63 @@
|
||||
{#if component.type === 'displaycomponent'}
|
||||
<DisplayComponent
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'barchartcomponent'}
|
||||
<BarChartComponent
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'timeseriescomponent'}
|
||||
<AppTimeseries
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'htmlcomponent'}
|
||||
<AppHtml
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'vegalitecomponent'}
|
||||
<VegaLiteHtml
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'plotlycomponent'}
|
||||
<PlotlyHtml
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'scatterchartcomponent'}
|
||||
<AppScatterChart
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'piechartcomponent'}
|
||||
<PieChartComponent
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
bind:componentInput={component.componentInput}
|
||||
/>
|
||||
{:else if component.type === 'tablecomponent'}
|
||||
<TableComponent
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:actionButtons={component.actionButtons}
|
||||
@@ -119,6 +131,7 @@
|
||||
{:else if component.type === 'textcomponent'}
|
||||
<TextComponent
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
@@ -165,3 +178,12 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if initializing}
|
||||
<div
|
||||
out:fade={{ duration: 200 }}
|
||||
class="absolute inset-0 center-center flex-col bg-white text-gray-600 border"
|
||||
>
|
||||
<Loader2 class="animate-spin" size={16} />
|
||||
<span class="text-xs mt-1">Loading</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -110,49 +110,44 @@ const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'pytho
|
||||
},
|
||||
vegalitecomponent: {
|
||||
deno:
|
||||
`
|
||||
export async function main() {
|
||||
return {
|
||||
data: {
|
||||
values: [
|
||||
{ a: "A", b: 28 },
|
||||
{ a: "B", b: 55 },
|
||||
{ a: "C", b: 43 },
|
||||
{ a: "D", b: 91 },
|
||||
],
|
||||
},
|
||||
mark: "bar",
|
||||
encoding: {
|
||||
x: { field: "a", type: "ordinal" },
|
||||
y: { field: "b", type: "quantitative" },
|
||||
},
|
||||
};
|
||||
}
|
||||
`,
|
||||
`export async function main() {
|
||||
return {
|
||||
data: {
|
||||
values: [
|
||||
{ a: "A", b: 28 },
|
||||
{ a: "B", b: 55 },
|
||||
{ a: "C", b: 43 },
|
||||
{ a: "D", b: 91 },
|
||||
],
|
||||
},
|
||||
mark: "bar",
|
||||
encoding: {
|
||||
x: { field: "a", type: "ordinal" },
|
||||
y: { field: "b", type: "quantitative" },
|
||||
},
|
||||
}
|
||||
}`,
|
||||
python3:
|
||||
`
|
||||
def main():
|
||||
`def main():
|
||||
return {
|
||||
"data": {
|
||||
"values": [
|
||||
{ "a": "A", "b": 28 },
|
||||
{ "a": "B", "b": 55 },
|
||||
{ "a": "C", "b": 43 },
|
||||
{ "a": "D", "b": 91 },
|
||||
],
|
||||
"values": [
|
||||
{ "a": "A", "b": 28 },
|
||||
{ "a": "B", "b": 55 },
|
||||
{ "a": "C", "b": 43 },
|
||||
{ "a": "D", "b": 91 },
|
||||
],
|
||||
},
|
||||
"mark": "bar",
|
||||
"mark": "bar",
|
||||
"encoding": {
|
||||
"x": { "field": "a", "type": "ordinal" },
|
||||
"y": { "field": "b", "type": "quantitative" },
|
||||
},
|
||||
}
|
||||
`
|
||||
}`
|
||||
},
|
||||
plotlycomponent: {
|
||||
deno:
|
||||
`
|
||||
export async function main() {
|
||||
`export async function main() {
|
||||
return {
|
||||
type: 'bar',
|
||||
x: [1, 2, 3, 4],
|
||||
@@ -164,11 +159,9 @@ export async function main() {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
`,
|
||||
}`,
|
||||
python3:
|
||||
`
|
||||
def main():
|
||||
`def main():
|
||||
return {
|
||||
"type": "bar",
|
||||
"x": [1, 2, 3, 4],
|
||||
@@ -179,8 +172,7 @@ def main():
|
||||
"width": 2.5
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
}`
|
||||
},
|
||||
piechartcomponent: {
|
||||
deno:
|
||||
|
||||
@@ -115,7 +115,7 @@ export const displayData: Record<AppComponent['type'], { name: string; icon: any
|
||||
icon: BarChart4
|
||||
},
|
||||
htmlcomponent: {
|
||||
name: 'Html',
|
||||
name: 'HTML',
|
||||
icon: Code2
|
||||
},
|
||||
vegalitecomponent: {
|
||||
@@ -143,7 +143,7 @@ export const displayData: Record<AppComponent['type'], { name: string; icon: any
|
||||
icon: ToggleLeft
|
||||
},
|
||||
textinputcomponent: {
|
||||
name: 'Text input',
|
||||
name: 'Text Input',
|
||||
icon: TextCursorInput
|
||||
},
|
||||
imagecomponent: {
|
||||
@@ -155,11 +155,11 @@ export const displayData: Record<AppComponent['type'], { name: string; icon: any
|
||||
icon: FormInput
|
||||
},
|
||||
radiocomponent: {
|
||||
name: 'Radio button',
|
||||
name: 'Radio Button',
|
||||
icon: CircleDot
|
||||
},
|
||||
runformcomponent: {
|
||||
name: 'Run form',
|
||||
name: 'Run Form',
|
||||
icon: Play
|
||||
},
|
||||
selectcomponent: {
|
||||
@@ -179,7 +179,7 @@ export const displayData: Record<AppComponent['type'], { name: string; icon: any
|
||||
icon: Lock
|
||||
},
|
||||
dateinputcomponent: {
|
||||
name: 'Date input',
|
||||
name: 'Date Input',
|
||||
icon: Calendar
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user