mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-09 08:02:25 +00:00
feat(frontend): add support for background scripts + add FormButtonCo… (#1124)
* feat(frontend): add support for background scripts + add FormButtonComponnet * feat(frontend): add background script fields * feat(frontend): Handle deletion + display of results * feat(frontend): Fix background script ids: Runnable ids needs to be valid js expression, because they are used in the evaluator. * feat(frontend): add a portal to display modal * feat(frontend): fix build * feat(frontend): Add missing props * feat(frontend): Fix run button + add tooltip + fix icon
This commit is contained in:
Generated
+11
@@ -24,6 +24,7 @@
|
||||
"monaco-yaml": "^4.0.2",
|
||||
"svelte-autosize": "^1.0.1",
|
||||
"svelte-chartjs": "^3.1.0",
|
||||
"svelte-portal": "^2.2.0",
|
||||
"svelte-select": "^5.0.2",
|
||||
"vscode-ws-jsonrpc": "^2.0.1"
|
||||
},
|
||||
@@ -6256,6 +6257,11 @@
|
||||
"svelte": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-portal": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-portal/-/svelte-portal-2.2.0.tgz",
|
||||
"integrity": "sha512-jhtZWtD6cUE2nMw46dJ5VXWYiqnER+JH+V/BmNBQ5fNP/YdsJCpJi+DemUy9msklqGb0f+wLhJPBtKHLWQvzjg=="
|
||||
},
|
||||
"node_modules/svelte-preprocess": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.0.tgz",
|
||||
@@ -11548,6 +11554,11 @@
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"svelte-portal": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-portal/-/svelte-portal-2.2.0.tgz",
|
||||
"integrity": "sha512-jhtZWtD6cUE2nMw46dJ5VXWYiqnER+JH+V/BmNBQ5fNP/YdsJCpJi+DemUy9msklqGb0f+wLhJPBtKHLWQvzjg=="
|
||||
},
|
||||
"svelte-preprocess": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.0.tgz",
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
"@sveltejs/package": "^1.0.1",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@types/vscode": "~1.74.0",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/vscode": "~1.74.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"@windmill-labs/svelvet": "^4.0.20",
|
||||
@@ -77,6 +77,7 @@
|
||||
"monaco-yaml": "^4.0.2",
|
||||
"svelte-autosize": "^1.0.1",
|
||||
"svelte-chartjs": "^3.1.0",
|
||||
"svelte-portal": "^2.2.0",
|
||||
"svelte-select": "^5.0.2",
|
||||
"vscode-ws-jsonrpc": "^2.0.1"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../inputType'
|
||||
import type { InlineScript } from '../types'
|
||||
import RunnableComponent from './helpers/RunnableComponent.svelte'
|
||||
|
||||
export let id: string
|
||||
export let name: string
|
||||
export let inlineScript: InlineScript | undefined
|
||||
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
|
||||
let result: any = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
</script>
|
||||
|
||||
<RunnableComponent
|
||||
{id}
|
||||
bind:fields
|
||||
bind:result
|
||||
runnable={{
|
||||
name,
|
||||
inlineScript,
|
||||
type: 'runnableByName'
|
||||
}}
|
||||
wrapperClass="hidden"
|
||||
>
|
||||
<slot />
|
||||
</RunnableComponent>
|
||||
@@ -86,11 +86,11 @@
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
on:click={(e) => {
|
||||
on:click={async (e) => {
|
||||
e?.stopPropagation()
|
||||
e?.preventDefault()
|
||||
ownClick = true
|
||||
runnableComponent?.runComponent()
|
||||
await runnableComponent?.runComponent()
|
||||
|
||||
if (recomputeIds) {
|
||||
recomputeIds.forEach((id) => {
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
<script lang="ts">
|
||||
import { Button, type ButtonType } from '$lib/components/common'
|
||||
import { faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import Portal from 'svelte-portal'
|
||||
import Modal from '$lib/components/common/modal/Modal.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let recomputeIds: string[] | undefined = undefined
|
||||
export let extraQueryParams: Record<string, any> = {}
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
const { runnableComponents, worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let labelValue: string = 'Default label'
|
||||
let color: ButtonType.Color
|
||||
let size: ButtonType.Size
|
||||
let runnableComponent: RunnableComponent
|
||||
let disabled: boolean | undefined = undefined
|
||||
|
||||
let isLoading: boolean = false
|
||||
let ownClick: boolean = false
|
||||
|
||||
let errors: Record<string, string> = {}
|
||||
$: errorsMessage = Object.values(errors)
|
||||
.filter((x) => x != '')
|
||||
.join('\n')
|
||||
|
||||
$: noInputs =
|
||||
componentInput?.type != 'runnable' || Object.keys(componentInput?.fields ?? {}).length == 0
|
||||
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<Array<any>>
|
||||
loading: Output<boolean>
|
||||
}
|
||||
|
||||
$: if (outputs?.loading != undefined) {
|
||||
outputs.loading.set(false, true)
|
||||
}
|
||||
|
||||
$: outputs?.loading.subscribe({
|
||||
next: (value) => {
|
||||
isLoading = value
|
||||
if (ownClick && !value) {
|
||||
ownClick = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
$: loading = isLoading && ownClick
|
||||
|
||||
let open: boolean = false
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue {id} input={configuration.color} bind:value={color} />
|
||||
<InputValue {id} input={configuration.size} bind:value={size} />
|
||||
<InputValue
|
||||
row={extraQueryParams['row']}
|
||||
{id}
|
||||
input={configuration.disabled}
|
||||
bind:value={disabled}
|
||||
bind:error={errors.disabled}
|
||||
/>
|
||||
|
||||
<Portal>
|
||||
<Modal
|
||||
{open}
|
||||
title={labelValue}
|
||||
on:canceled={() => {
|
||||
open = false
|
||||
}}
|
||||
on:confirmed={() => {
|
||||
open = false
|
||||
}}
|
||||
>
|
||||
<RunnableWrapper
|
||||
defaultUserInput
|
||||
noMinH
|
||||
bind:runnableComponent
|
||||
bind:componentInput
|
||||
{id}
|
||||
{extraQueryParams}
|
||||
autoRefresh={false}
|
||||
forceSchemaDisplay={true}
|
||||
runnableClass="!block"
|
||||
>
|
||||
<div class="flex flex-col gap-2 px-4 w-full">
|
||||
<div>
|
||||
{#if noInputs}
|
||||
<div class="text-gray-600 italic text-sm my-4">
|
||||
Run forms are associated with a runnable that has user inputs.
|
||||
<br />
|
||||
Once a script or flow is chosen, set some <strong>Runnable Inputs</strong> to
|
||||
<strong>
|
||||
User Input
|
||||
<Icon data={faUser} scale={1.3} class="rounded-sm bg-gray-200 p-1 ml-0.5" />
|
||||
</strong>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
{loading}
|
||||
btnClasses="my-1"
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
on:click={async () => {
|
||||
await runnableComponent?.runComponent()
|
||||
|
||||
if (recomputeIds) {
|
||||
recomputeIds.forEach((id) => {
|
||||
$runnableComponents[id]?.()
|
||||
})
|
||||
}
|
||||
|
||||
open = false
|
||||
}}
|
||||
size="xs"
|
||||
color="dark"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
</Modal>
|
||||
</Portal>
|
||||
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
{#if errorsMessage}
|
||||
<div class="text-red-500 text-xs">{errorsMessage}</div>
|
||||
{/if}
|
||||
<Button
|
||||
{disabled}
|
||||
{size}
|
||||
{color}
|
||||
on:click={(e) => {
|
||||
open = true
|
||||
}}
|
||||
>
|
||||
{labelValue}
|
||||
</Button>
|
||||
</AlignWrapper>
|
||||
@@ -19,6 +19,7 @@
|
||||
import AppTimeseries from '../components/dataDisplay/AppTimeseries.svelte'
|
||||
import AppHtml from '../components/dataDisplay/AppHtml.svelte'
|
||||
import AppSliderInputs from '../components/numberInputs/AppSliderInputs.svelte'
|
||||
import AppFormButton from '../components/form/AppFormButton.svelte'
|
||||
import VegaLiteHtml from '../components/dataDisplay/VegaLiteHtml.svelte'
|
||||
|
||||
export let component: AppComponent
|
||||
@@ -124,6 +125,12 @@
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'formbuttoncomponent'}
|
||||
<AppFormButton
|
||||
{...component}
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'checkboxcomponent'}
|
||||
<CheckboxComponent {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'textinputcomponent'}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
|
||||
import type { Policy } from '$lib/gen'
|
||||
import HiddenComponent from '../components/HiddenComponent.svelte'
|
||||
|
||||
export let policy: Policy
|
||||
|
||||
@@ -176,6 +177,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#each $app.hiddenInlineScripts as script, index}
|
||||
{#if script}
|
||||
<HiddenComponent
|
||||
id={`bg_${index}`}
|
||||
inlineScript={script.inlineScript}
|
||||
name={script.name}
|
||||
bind:fields={script.fields}
|
||||
bind:staticOutputs={$staticOutputs[`bg_${index}`]}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<style>
|
||||
:global(.svlt-grid-shadow) {
|
||||
/* Back shadow */
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../types'
|
||||
import PanelSection from './settingsPanel/common/PanelSection.svelte'
|
||||
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
|
||||
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
|
||||
import TablePanel from './TablePanel.svelte'
|
||||
|
||||
const { selectedComponent, lazyGrid } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { selectedComponent, lazyGrid, app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
</script>
|
||||
|
||||
{#each $lazyGrid as gridItem (gridItem.data.id)}
|
||||
@@ -14,3 +16,16 @@
|
||||
<TablePanel bind:component={gridItem.data} />
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
{#each $app.hiddenInlineScripts as script, index (script.name)}
|
||||
{#if $selectedComponent === `bg_${index}`}
|
||||
<PanelSection title={`Background script inputs`}>
|
||||
<InputsSpecsEditor
|
||||
id={`bg_${index}`}
|
||||
shouldCapitalize={false}
|
||||
bind:inputSpecs={script.fields}
|
||||
userInputEnabled={false}
|
||||
/>
|
||||
</PanelSection>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -15,8 +15,8 @@ const inputs: ComponentSet = {
|
||||
type: 'static',
|
||||
value: 'Type...',
|
||||
fieldType: 'text',
|
||||
onlyStatic: true,
|
||||
},
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
card: false
|
||||
},
|
||||
@@ -31,8 +31,8 @@ const inputs: ComponentSet = {
|
||||
type: 'static',
|
||||
value: 'Password',
|
||||
fieldType: 'text',
|
||||
onlyStatic: true,
|
||||
},
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
card: false
|
||||
},
|
||||
@@ -47,8 +47,8 @@ const inputs: ComponentSet = {
|
||||
type: 'static',
|
||||
value: 'Type...',
|
||||
fieldType: 'text',
|
||||
onlyStatic: true,
|
||||
},
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
card: false
|
||||
},
|
||||
@@ -63,14 +63,14 @@ const inputs: ComponentSet = {
|
||||
type: 'static',
|
||||
value: 0,
|
||||
fieldType: 'number',
|
||||
onlyStatic: true,
|
||||
onlyStatic: true
|
||||
},
|
||||
max: {
|
||||
type: 'static',
|
||||
value: 42,
|
||||
fieldType: 'number',
|
||||
onlyStatic: true,
|
||||
},
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
card: false
|
||||
},
|
||||
@@ -84,15 +84,15 @@ const inputs: ComponentSet = {
|
||||
minDate: {
|
||||
type: 'static',
|
||||
value: '',
|
||||
fieldType: 'date',
|
||||
fieldType: 'date'
|
||||
},
|
||||
maxDate: {
|
||||
type: 'static',
|
||||
value: '',
|
||||
fieldType: 'date',
|
||||
fieldType: 'date'
|
||||
}
|
||||
},
|
||||
card: false,
|
||||
card: false
|
||||
},
|
||||
{
|
||||
...defaultAlignement,
|
||||
@@ -104,7 +104,7 @@ const inputs: ComponentSet = {
|
||||
label: {
|
||||
type: 'static',
|
||||
value: 'Label',
|
||||
fieldType: 'text',
|
||||
fieldType: 'text'
|
||||
}
|
||||
},
|
||||
card: false
|
||||
@@ -148,8 +148,7 @@ const buttons: ComponentSet = {
|
||||
type: 'runnable',
|
||||
fieldType: 'any',
|
||||
fields: {},
|
||||
runnable: undefined,
|
||||
|
||||
runnable: undefined
|
||||
},
|
||||
recomputeIds: undefined,
|
||||
configuration: {
|
||||
@@ -189,29 +188,64 @@ const buttons: ComponentSet = {
|
||||
type: 'runnable',
|
||||
fieldType: 'any',
|
||||
fields: {},
|
||||
runnable: undefined,
|
||||
|
||||
runnable: undefined
|
||||
},
|
||||
recomputeIds: undefined,
|
||||
configuration: {
|
||||
label: {
|
||||
type: 'static',
|
||||
value: 'Submit',
|
||||
fieldType: 'text',
|
||||
fieldType: 'text'
|
||||
},
|
||||
color: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
onlyStatic: true,
|
||||
value: 'dark',
|
||||
optionValuesKey: 'buttonColorOptions',
|
||||
optionValuesKey: 'buttonColorOptions'
|
||||
},
|
||||
size: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
value: 'xs',
|
||||
onlyStatic: true,
|
||||
optionValuesKey: 'buttonSizeOptions',
|
||||
optionValuesKey: 'buttonSizeOptions'
|
||||
}
|
||||
},
|
||||
|
||||
card: true
|
||||
},
|
||||
{
|
||||
horizontalAlignment: 'center',
|
||||
verticalAlignment: 'center',
|
||||
id: 'formbuttoncomponent',
|
||||
type: 'formbuttoncomponent',
|
||||
componentInput: {
|
||||
type: 'runnable',
|
||||
fieldType: 'any',
|
||||
fields: {},
|
||||
runnable: undefined
|
||||
},
|
||||
recomputeIds: undefined,
|
||||
configuration: {
|
||||
label: {
|
||||
type: 'static',
|
||||
value: 'Open popup',
|
||||
fieldType: 'text'
|
||||
},
|
||||
color: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
onlyStatic: true,
|
||||
value: 'dark',
|
||||
optionValuesKey: 'buttonColorOptions'
|
||||
},
|
||||
size: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
value: 'xs',
|
||||
onlyStatic: true,
|
||||
optionValuesKey: 'buttonSizeOptions'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -235,7 +269,7 @@ const display: ComponentSet = {
|
||||
>
|
||||
<h1 class="absolute top-4 left-2 text-white">
|
||||
Hello \${ctx.username}
|
||||
</h1>`,
|
||||
</h1>`
|
||||
},
|
||||
configuration: {},
|
||||
card: false
|
||||
@@ -253,7 +287,7 @@ const display: ComponentSet = {
|
||||
{ a: 'A', b: 28 },
|
||||
{ a: 'B', b: 55 },
|
||||
{ a: 'C', b: 43 },
|
||||
{ a: 'D', b: 91 },
|
||||
{ a: 'D', b: 91 }
|
||||
]
|
||||
},
|
||||
mark: 'bar',
|
||||
@@ -269,9 +303,8 @@ const display: ComponentSet = {
|
||||
onlyStatic: true,
|
||||
fieldType: 'boolean',
|
||||
value: false,
|
||||
tooltip: "use the canvas renderer instead of the svg one for more interactive plots"
|
||||
tooltip: 'use the canvas renderer instead of the svg one for more interactive plots'
|
||||
}
|
||||
|
||||
},
|
||||
card: false
|
||||
},
|
||||
@@ -284,7 +317,7 @@ const display: ComponentSet = {
|
||||
componentInput: {
|
||||
type: 'static',
|
||||
fieldType: 'template',
|
||||
value: 'Hello ${ctx.username}',
|
||||
value: 'Hello ${ctx.username}'
|
||||
},
|
||||
configuration: {
|
||||
style: {
|
||||
@@ -298,19 +331,18 @@ const display: ComponentSet = {
|
||||
type: 'static',
|
||||
fieldType: 'text',
|
||||
value: '',
|
||||
tooltip: 'CSS rules like "color: blue;"',
|
||||
tooltip: 'CSS rules like "color: blue;"'
|
||||
},
|
||||
copyButton: {
|
||||
type: 'static',
|
||||
value: false,
|
||||
fieldType: 'boolean',
|
||||
onlyStatic: true,
|
||||
},
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
card: false
|
||||
},
|
||||
{
|
||||
|
||||
id: 'tablecomponent',
|
||||
type: 'tablecomponent',
|
||||
configuration: {
|
||||
@@ -337,8 +369,7 @@ const display: ComponentSet = {
|
||||
name: 'A briefer cell',
|
||||
age: 84
|
||||
}
|
||||
],
|
||||
|
||||
]
|
||||
},
|
||||
card: true,
|
||||
actionButtons: []
|
||||
@@ -385,7 +416,6 @@ const display: ComponentSet = {
|
||||
fieldType: 'boolean',
|
||||
value: false
|
||||
}
|
||||
|
||||
},
|
||||
componentInput: {
|
||||
type: 'static',
|
||||
@@ -409,30 +439,32 @@ const display: ComponentSet = {
|
||||
onlyStatic: true,
|
||||
fieldType: 'boolean',
|
||||
value: false
|
||||
},
|
||||
}
|
||||
},
|
||||
componentInput: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'object',
|
||||
value: [{
|
||||
label: 'foo',
|
||||
data: [
|
||||
{ x: 25, y: 50 },
|
||||
{ x: 23, y: 23 },
|
||||
{ x: 12, y: 37 }
|
||||
],
|
||||
backgroundColor: 'rgb(255, 12, 137)'
|
||||
},
|
||||
{
|
||||
label: 'foobar',
|
||||
data: [
|
||||
{ x: 32, y: 32 },
|
||||
{ x: 25, y: 42 },
|
||||
{ x: 3, y: 27 }
|
||||
],
|
||||
backgroundColor: 'orange'
|
||||
}]
|
||||
value: [
|
||||
{
|
||||
label: 'foo',
|
||||
data: [
|
||||
{ x: 25, y: 50 },
|
||||
{ x: 23, y: 23 },
|
||||
{ x: 12, y: 37 }
|
||||
],
|
||||
backgroundColor: 'rgb(255, 12, 137)'
|
||||
},
|
||||
{
|
||||
label: 'foobar',
|
||||
data: [
|
||||
{ x: 32, y: 32 },
|
||||
{ x: 25, y: 42 },
|
||||
{ x: 3, y: 27 }
|
||||
],
|
||||
backgroundColor: 'orange'
|
||||
}
|
||||
]
|
||||
},
|
||||
card: true
|
||||
},
|
||||
@@ -457,40 +489,50 @@ const display: ComponentSet = {
|
||||
onlyStatic: true,
|
||||
fieldType: 'boolean',
|
||||
value: false
|
||||
},
|
||||
}
|
||||
},
|
||||
componentInput: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'object',
|
||||
value: [{
|
||||
label: 'foo',
|
||||
data: [{
|
||||
x: '2021-11-06 23:39:30',
|
||||
y: 50
|
||||
}, {
|
||||
x: '2021-11-07 01:00:28',
|
||||
y: 60
|
||||
}, {
|
||||
x: '2021-11-07 09:00:28',
|
||||
y: 20
|
||||
}],
|
||||
backgroundColor: 'rgb(255, 12, 137)'
|
||||
},
|
||||
{
|
||||
label: 'foobar',
|
||||
data: [{
|
||||
x: '2021-11-06 23:39:30',
|
||||
y: 20
|
||||
}, {
|
||||
x: '2021-11-07 01:00:28',
|
||||
y: 13
|
||||
}, {
|
||||
x: '2021-11-07 09:00:28',
|
||||
y: 45
|
||||
}],
|
||||
backgroundColor: 'orange'
|
||||
}]
|
||||
value: [
|
||||
{
|
||||
label: 'foo',
|
||||
data: [
|
||||
{
|
||||
x: '2021-11-06 23:39:30',
|
||||
y: 50
|
||||
},
|
||||
{
|
||||
x: '2021-11-07 01:00:28',
|
||||
y: 60
|
||||
},
|
||||
{
|
||||
x: '2021-11-07 09:00:28',
|
||||
y: 20
|
||||
}
|
||||
],
|
||||
backgroundColor: 'rgb(255, 12, 137)'
|
||||
},
|
||||
{
|
||||
label: 'foobar',
|
||||
data: [
|
||||
{
|
||||
x: '2021-11-06 23:39:30',
|
||||
y: 20
|
||||
},
|
||||
{
|
||||
x: '2021-11-07 01:00:28',
|
||||
y: 13
|
||||
},
|
||||
{
|
||||
x: '2021-11-07 09:00:28',
|
||||
y: 45
|
||||
}
|
||||
],
|
||||
backgroundColor: 'orange'
|
||||
}
|
||||
]
|
||||
},
|
||||
card: true
|
||||
},
|
||||
@@ -500,13 +542,11 @@ const display: ComponentSet = {
|
||||
componentInput: {
|
||||
type: 'static',
|
||||
fieldType: 'object',
|
||||
value: { "foo": 42 },
|
||||
|
||||
value: { foo: 42 }
|
||||
},
|
||||
configuration: {},
|
||||
card: false
|
||||
},
|
||||
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
return displayData[component?.data.type].name
|
||||
} else if (componentId == 'ctx') {
|
||||
return 'Context'
|
||||
} else if (componentId.startsWith('bg_')) {
|
||||
return 'Background'
|
||||
} else {
|
||||
return 'Table action'
|
||||
}
|
||||
|
||||
+3
-2
@@ -82,7 +82,7 @@
|
||||
>
|
||||
Format <Tooltip>Ctrl+S</Tooltip>
|
||||
</Button>
|
||||
{#if id.startsWith('unused-')}
|
||||
{#if id.startsWith('unused-') || id.startsWith('bg_')}
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
@@ -91,7 +91,8 @@
|
||||
startIcon={{ icon: faTrash }}
|
||||
on:click={() => dispatch('delete')}
|
||||
/>
|
||||
{:else if $runnableComponents[id] != undefined}
|
||||
{/if}
|
||||
{#if $runnableComponents[id] != undefined}
|
||||
<Button
|
||||
loading={runLoading}
|
||||
size="xs"
|
||||
|
||||
+29
-1
@@ -6,8 +6,9 @@
|
||||
import InlineScriptsPanelList from './InlineScriptsPanelList.svelte'
|
||||
import InlineScriptEditorPanel from './InlineScriptEditorPanel.svelte'
|
||||
import InlineScriptEditor from './InlineScriptEditor.svelte'
|
||||
import EmptyInlineScript from './EmptyInlineScript.svelte'
|
||||
|
||||
const { lazyGrid, app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { lazyGrid, app, staticOutputs } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let selectedScriptComponentId: string | undefined = undefined
|
||||
</script>
|
||||
@@ -51,6 +52,33 @@
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{#each $app.hiddenInlineScripts as hiddenInlineScript, index (index)}
|
||||
{#if `bg_${index}` === selectedScriptComponentId}
|
||||
{#if hiddenInlineScript.inlineScript}
|
||||
<InlineScriptEditor
|
||||
id={`bg_${index}`}
|
||||
bind:inlineScript={hiddenInlineScript.inlineScript}
|
||||
bind:name={hiddenInlineScript.name}
|
||||
on:delete={() => {
|
||||
// remove the script from the array at the index
|
||||
$app.hiddenInlineScripts.splice(index, 1)
|
||||
$app.hiddenInlineScripts = [...$app.hiddenInlineScripts]
|
||||
|
||||
delete $staticOutputs[`bg_${index}`]
|
||||
$staticOutputs = $staticOutputs
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<EmptyInlineScript
|
||||
id={`b_${index}`}
|
||||
name={hiddenInlineScript.name}
|
||||
on:new={(e) => {
|
||||
hiddenInlineScript.inlineScript = e.detail
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</SplitPanesWrapper>
|
||||
|
||||
+61
-2
@@ -1,8 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Badge } from '$lib/components/common'
|
||||
import { Badge, Button } from '$lib/components/common'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import { getAllScriptNames } from '../../utils'
|
||||
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
|
||||
import { getAppScripts } from './utils'
|
||||
|
||||
@@ -11,7 +14,7 @@
|
||||
|
||||
function selectInlineScript(id: string) {
|
||||
selectedScriptComponentId = id
|
||||
if (!id.startsWith('unused-')) {
|
||||
if (!id.startsWith('unused-') || !id.startsWith('bg_')) {
|
||||
$selectedComponent = selectedScriptComponentId
|
||||
}
|
||||
}
|
||||
@@ -24,6 +27,29 @@
|
||||
selectedScriptComponentId = $selectedComponent
|
||||
}
|
||||
}
|
||||
|
||||
function createBackgroundScript() {
|
||||
let index = 0
|
||||
let newScriptPath = `Background Script ${index}`
|
||||
|
||||
const names = getAllScriptNames($app)
|
||||
|
||||
// Find a name that is not used by any other inline script
|
||||
while (names.includes(newScriptPath)) {
|
||||
newScriptPath = `Background Script ${++index}`
|
||||
}
|
||||
|
||||
if (!$app.hiddenInlineScripts) {
|
||||
$app.hiddenInlineScripts = []
|
||||
}
|
||||
|
||||
$app.hiddenInlineScripts.push({
|
||||
name: newScriptPath,
|
||||
inlineScript: undefined,
|
||||
fields: {}
|
||||
})
|
||||
$app.hiddenInlineScripts = $app.hiddenInlineScripts
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="min-h-full flex flex-col gap-4">
|
||||
@@ -96,4 +122,37 @@
|
||||
{/if}
|
||||
</div>
|
||||
</PanelSection>
|
||||
|
||||
<PanelSection title="Background scripts" smallPadding>
|
||||
<svelte:fragment slot="action">
|
||||
<Button size="xs" color="dark" startIcon={{ icon: faPlus }} on:click={createBackgroundScript}>
|
||||
Add
|
||||
</Button>
|
||||
<Tooltip>
|
||||
Background scripts are triggered upon global refresh or when their input changes. The result
|
||||
of a background script can be shared among many components.
|
||||
</Tooltip>
|
||||
</svelte:fragment>
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
{#if $app.hiddenInlineScripts?.length > 0}
|
||||
<div class="flex gap-2 flex-col ">
|
||||
{#each $app.hiddenInlineScripts as { name }, index (index)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="{classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
|
||||
selectedScriptComponentId === `bg_${index}` ? 'bg-blue-100 text-blue-600' : ''
|
||||
)},"
|
||||
on:click={() => selectInlineScript(`bg_${index}`)}
|
||||
>
|
||||
<span class="text-xs truncate">{name}</span>
|
||||
<Badge color="yellow">Background</Badge>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-gray-500">No items</div>
|
||||
{/if}
|
||||
</div>
|
||||
</PanelSection>
|
||||
</div>
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
{/if}
|
||||
|
||||
<AlignmentEditor bind:component />
|
||||
{#if component.type === 'buttoncomponent' || component.type === 'formcomponent'}
|
||||
{#if component.type === 'buttoncomponent' || component.type === 'formcomponent' || component.type === 'formbuttoncomponent'}
|
||||
<Recompute bind:recomputeIds={component.recomputeIds} ownId={component.id} />
|
||||
{/if}
|
||||
|
||||
|
||||
+3
-26
@@ -10,7 +10,7 @@
|
||||
import type { AppEditorContext, GridItem, InlineScript } from '$lib/components/apps/types'
|
||||
import { getContext } from 'svelte'
|
||||
import type { Schema } from '$lib/common'
|
||||
import { loadSchema, schemaToInputsSpec } from '$lib/components/apps/utils'
|
||||
import { getAllScriptNames, loadSchema, schemaToInputsSpec } from '$lib/components/apps/utils'
|
||||
import { emptySchema } from '$lib/utils'
|
||||
|
||||
type Tab = 'hubscripts' | 'workspacescripts' | 'workspaceflows' | 'inlinescripts'
|
||||
@@ -94,33 +94,10 @@
|
||||
let index = 0
|
||||
let newScriptPath = `Inline Script ${index}`
|
||||
|
||||
const names = $app.grid.reduce((acc, gridItem: GridItem) => {
|
||||
const { componentInput } = gridItem.data
|
||||
|
||||
if (
|
||||
componentInput?.type === 'runnable' &&
|
||||
componentInput?.runnable?.type === 'runnableByName'
|
||||
) {
|
||||
acc.push(componentInput.runnable.name)
|
||||
}
|
||||
|
||||
if (componentInput?.type === 'tablecomponent') {
|
||||
componentInput.actionButtons.forEach((actionButton) => {
|
||||
if (actionButton.componentInput?.type === 'runnable') {
|
||||
if (actionButton.componentInput.runnable?.type === 'runnableByName') {
|
||||
acc.push(actionButton.componentInput.runnable.name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [] as string[])
|
||||
|
||||
const unusedNames = $app.unusedInlineScripts.map((x) => x.name)
|
||||
const names = getAllScriptNames($app)
|
||||
|
||||
// Find a name that is not used by any other inline script
|
||||
while (names.includes(newScriptPath) || unusedNames.includes(newScriptPath)) {
|
||||
while (names.includes(newScriptPath)) {
|
||||
newScriptPath = `Inline Script ${++index}`
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,10 @@ export type FormComponent = BaseComponent<'formcomponent'> & {
|
||||
recomputeIds: string[] | undefined
|
||||
}
|
||||
|
||||
export type FormButtonComponent = BaseComponent<'formbuttoncomponent'> & {
|
||||
recomputeIds: string[] | undefined
|
||||
}
|
||||
|
||||
export type RunFormComponent = BaseComponent<'runformcomponent'>
|
||||
export type BarChartComponent = BaseComponent<'barchartcomponent'>
|
||||
export type PieChartComponent = BaseComponent<'piechartcomponent'>
|
||||
@@ -60,11 +64,14 @@ export type Aligned = {
|
||||
export interface BaseAppComponent extends Partial<Aligned> {
|
||||
id: ComponentID
|
||||
componentInput: AppInput | undefined
|
||||
configuration: Record<string, (StaticAppInput | ConnectedAppInput | UserAppInput | RowAppInput | EvalAppInput) & {
|
||||
onlyStatic?: boolean,
|
||||
evaluatedValue?: boolean,
|
||||
tooltip?: string
|
||||
}>
|
||||
configuration: Record<
|
||||
string,
|
||||
(StaticAppInput | ConnectedAppInput | UserAppInput | RowAppInput | EvalAppInput) & {
|
||||
onlyStatic?: boolean
|
||||
evaluatedValue?: boolean
|
||||
tooltip?: string
|
||||
}
|
||||
>
|
||||
card: boolean | undefined
|
||||
/**
|
||||
* If `true` then the wrapper will allow items to flow outside of it's borders.
|
||||
@@ -98,6 +105,7 @@ export type AppComponent = BaseAppComponent &
|
||||
| CheckboxComponent
|
||||
| RadioComponent
|
||||
| FormComponent
|
||||
| FormButtonComponent
|
||||
| VegaLiteComponent
|
||||
)
|
||||
|
||||
@@ -132,6 +140,11 @@ export type App = {
|
||||
name: string
|
||||
inlineScript: InlineScript
|
||||
}>
|
||||
hiddenInlineScripts: Array<{
|
||||
name: string
|
||||
inlineScript: InlineScript | undefined
|
||||
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
}>
|
||||
}
|
||||
|
||||
export type ConnectingInput = {
|
||||
@@ -143,21 +156,21 @@ export type ConnectingInput = {
|
||||
|
||||
export type AppEditorContext = {
|
||||
worldStore: Writable<World | undefined>
|
||||
staticOutputs: Writable<Record<string, string[]>>,
|
||||
lazyGrid: Writable<GridItem[]>,
|
||||
app: Writable<App>,
|
||||
summary: Writable<string>,
|
||||
staticOutputs: Writable<Record<string, string[]>>
|
||||
lazyGrid: Writable<GridItem[]>
|
||||
app: Writable<App>
|
||||
summary: Writable<string>
|
||||
selectedComponent: Writable<string | undefined>
|
||||
mode: Writable<EditorMode>
|
||||
connectingInput: Writable<ConnectingInput>
|
||||
breakpoint: Writable<EditorBreakpoint>
|
||||
runnableComponents: Writable<Record<string, () => Promise<void>>>
|
||||
staticExporter: Writable<Record<string, () => any>>
|
||||
appPath: string,
|
||||
workspace: string,
|
||||
onchange: (() => void) | undefined,
|
||||
isEditor: boolean,
|
||||
jobs: Writable<{ job: string, component: string }[]>,
|
||||
appPath: string
|
||||
workspace: string
|
||||
onchange: (() => void) | undefined
|
||||
isEditor: boolean
|
||||
jobs: Writable<{ job: string; component: string }[]>
|
||||
noBackend: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -21,11 +21,12 @@ import {
|
||||
ToggleLeft,
|
||||
GripHorizontal,
|
||||
Code2,
|
||||
SlidersHorizontal
|
||||
SlidersHorizontal,
|
||||
PlusSquare
|
||||
} from 'lucide-svelte'
|
||||
import type { AppInput, InputType, ResultAppInput, StaticAppInput } from './inputType'
|
||||
import type { Output } from './rx'
|
||||
import type { App, AppComponent } from './types'
|
||||
import type { App, AppComponent, GridItem } from './types'
|
||||
|
||||
export async function loadSchema(
|
||||
workspace: string,
|
||||
@@ -50,7 +51,11 @@ export async function loadSchema(
|
||||
const script = await ScriptService.getHubScriptByPath({
|
||||
path
|
||||
})
|
||||
if (script.schema == undefined || Object.keys(script.schema).length == 0 || typeof script.schema != 'object') {
|
||||
if (
|
||||
script.schema == undefined ||
|
||||
Object.keys(script.schema).length == 0 ||
|
||||
typeof script.schema != 'object'
|
||||
) {
|
||||
script.schema = emptySchema()
|
||||
}
|
||||
|
||||
@@ -59,14 +64,16 @@ export async function loadSchema(
|
||||
}
|
||||
}
|
||||
|
||||
export function schemaToInputsSpec(schema: Schema, defaultUserInput: boolean): Record<string, StaticAppInput> {
|
||||
export function schemaToInputsSpec(
|
||||
schema: Schema,
|
||||
defaultUserInput: boolean
|
||||
): Record<string, StaticAppInput> {
|
||||
if (schema?.properties == undefined) {
|
||||
return {}
|
||||
}
|
||||
return Object.keys(schema.properties).reduce((accu, key) => {
|
||||
const property = schema.properties[key]
|
||||
|
||||
|
||||
accu[key] = {
|
||||
type: defaultUserInput && !property.format?.startsWith('resource-') ? 'user' : 'static',
|
||||
value: property.default,
|
||||
@@ -74,7 +81,6 @@ export function schemaToInputsSpec(schema: Schema, defaultUserInput: boolean): R
|
||||
format: property.format
|
||||
}
|
||||
|
||||
|
||||
return accu
|
||||
}, {})
|
||||
}
|
||||
@@ -96,6 +102,10 @@ export const displayData: Record<AppComponent['type'], { name: string; icon: any
|
||||
name: 'Form',
|
||||
icon: FormInput
|
||||
},
|
||||
formbuttoncomponent: {
|
||||
name: 'Modal Form',
|
||||
icon: PlusSquare
|
||||
},
|
||||
piechartcomponent: {
|
||||
name: 'Pie Chart',
|
||||
icon: PieChart
|
||||
@@ -239,8 +249,11 @@ export function clearResultAppInput(appInput: ResultAppInput): ResultAppInput {
|
||||
return appInput
|
||||
}
|
||||
|
||||
|
||||
export function toStatic(app: App, staticExporter: Record<string, () => any>, summary: string): { app: App; summary: string } {
|
||||
export function toStatic(
|
||||
app: App,
|
||||
staticExporter: Record<string, () => any>,
|
||||
summary: string
|
||||
): { app: App; summary: string } {
|
||||
const newApp: App = JSON.parse(JSON.stringify(app))
|
||||
newApp.grid.forEach((x) => {
|
||||
let c: AppComponent = x.data
|
||||
@@ -251,16 +264,52 @@ export function toStatic(app: App, staticExporter: Record<string, () => any>, su
|
||||
return { app: newApp, summary }
|
||||
}
|
||||
|
||||
export function buildExtraLib(components: Record<string, Record<string, Output<any>>>, idToExclude: string, hasRows: boolean): string {
|
||||
return Object.entries(components)
|
||||
.filter(([k, v]) => k != idToExclude)
|
||||
.map(([k, v]) => [k, Object.fromEntries(Object.entries(v).map(([k, v]) => [k, v.peak()]))])
|
||||
.map(
|
||||
([k, v]) => `
|
||||
export function buildExtraLib(
|
||||
components: Record<string, Record<string, Output<any>>>,
|
||||
idToExclude: string,
|
||||
hasRows: boolean
|
||||
): string {
|
||||
return (
|
||||
Object.entries(components)
|
||||
.filter(([k, v]) => k != idToExclude)
|
||||
.map(([k, v]) => [k, Object.fromEntries(Object.entries(v).map(([k, v]) => [k, v.peak()]))])
|
||||
.map(
|
||||
([k, v]) => `
|
||||
|
||||
declare const ${k} = ${JSON.stringify(v)};
|
||||
|
||||
`
|
||||
)
|
||||
.join('\n') + (hasRows ? 'declare const row: Record<string, any>;' : '')
|
||||
}
|
||||
)
|
||||
.join('\n') + (hasRows ? 'declare const row: Record<string, any>;' : '')
|
||||
)
|
||||
}
|
||||
|
||||
export function getAllScriptNames(app: App): string[] {
|
||||
const names = app.grid.reduce((acc, gridItem: GridItem) => {
|
||||
const { componentInput } = gridItem.data
|
||||
|
||||
if (
|
||||
componentInput?.type === 'runnable' &&
|
||||
componentInput?.runnable?.type === 'runnableByName'
|
||||
) {
|
||||
acc.push(componentInput.runnable.name)
|
||||
}
|
||||
|
||||
if (componentInput?.type === 'tablecomponent') {
|
||||
componentInput.actionButtons.forEach((actionButton) => {
|
||||
if (actionButton.componentInput?.type === 'runnable') {
|
||||
if (actionButton.componentInput.runnable?.type === 'runnableByName') {
|
||||
acc.push(actionButton.componentInput.runnable.name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [] as string[])
|
||||
|
||||
const unusedNames = app.unusedInlineScripts.map((x) => x.name)
|
||||
const backgroundNames = app.hiddenInlineScripts?.map((x) => x.name) ?? []
|
||||
|
||||
return [...names, ...unusedNames, ...backgroundNames]
|
||||
}
|
||||
|
||||
@@ -28,12 +28,19 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
function fadeFast(node: HTMLElement) {
|
||||
return fade(node, { duration: 100 })
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
{#if open}
|
||||
<div transition:fade={{ duration: 100 }} class={'relative z-50'} role="dialog">
|
||||
<div
|
||||
transition:fadeFast|local
|
||||
class={'absolute top-0 bottom-0 left-0 right-0 z-50'}
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class={classNames(
|
||||
'fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity',
|
||||
|
||||
+4
-4
@@ -48,9 +48,9 @@
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col w-full space-y-4">
|
||||
<span
|
||||
>Are you sure you want to discard change you have made? (A draft has been temporarily and
|
||||
locally saved)</span
|
||||
>
|
||||
<span>
|
||||
Are you sure you want to discard change you have made? (A draft has been temporarily and
|
||||
locally saved)
|
||||
</span>
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import Button from '../button/Button.svelte'
|
||||
import Badge from '../badge/Badge.svelte'
|
||||
|
||||
export let title: string
|
||||
export let open: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
if (open) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
dispatch('confirmed')
|
||||
break
|
||||
case 'Escape':
|
||||
dispatch('canceled')
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
function fadeFast(node: HTMLElement) {
|
||||
return fade(node, { duration: 100 })
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
{#if open}
|
||||
<div
|
||||
transition:fadeFast|local
|
||||
class={'absolute top-0 bottom-0 left-0 right-0 z-50'}
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class={classNames(
|
||||
'fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity',
|
||||
open ? 'ease-out duration-300 opacity-100' : 'ease-in duration-200 opacity-0'
|
||||
)}
|
||||
/>
|
||||
|
||||
<div class="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div class="flex min-h-full items-center justify-center p-4">
|
||||
<div
|
||||
class={classNames(
|
||||
'relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6',
|
||||
open
|
||||
? 'ease-out duration-300 opacity-100 translate-y-0 sm:scale-100'
|
||||
: 'ease-in duration-200 opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95'
|
||||
)}
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="ml-4 text-left flex-1">
|
||||
<h3 class="text-lg font-medium text-gray-900">
|
||||
{title}
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2 flex-row-reverse space-x-reverse mt-4">
|
||||
<Button on:click={() => dispatch('canceled')} color="light" size="sm">
|
||||
<span class="gap-2">Cancel <Badge color="dark-gray">Escape</Badge></span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -25,7 +25,8 @@
|
||||
let value: App = {
|
||||
grid: [],
|
||||
fullscreen: false,
|
||||
unusedInlineScripts: []
|
||||
unusedInlineScripts: [],
|
||||
hiddenInlineScripts: []
|
||||
}
|
||||
|
||||
if (nodraft) {
|
||||
|
||||
Reference in New Issue
Block a user