mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-12 00:06:14 +00:00
App UI improvements (#1072)
* fix(frontend): Improve App UI * fix(frontend):Align typing * fix(frontend): Improve App UI * fix(frontend): remove debug string * fix(frontend): remove debug string * fix(frontend): restore code from main
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
import Popover from './Popover.svelte'
|
||||
</script>
|
||||
|
||||
<Popover notClickable>
|
||||
<Popover notClickable class="flex">
|
||||
<Icon
|
||||
class="text-gray-500 font-thin inline-block align-middle w-4"
|
||||
data={faInfoCircle}
|
||||
|
||||
@@ -60,9 +60,13 @@
|
||||
<div class="text-gray-400 bg-gray-100 flex justify-center items-center h-full w-full">
|
||||
No text
|
||||
</div>
|
||||
{:else}<svelte:element this={component} class="whitespace-pre {classes}" style={extraStyle}
|
||||
>{String(result)}</svelte:element
|
||||
{:else}<svelte:element
|
||||
this={component}
|
||||
class="whitespace-pre-wrap {classes}"
|
||||
style={extraStyle}
|
||||
>
|
||||
{String(result)}
|
||||
</svelte:element>
|
||||
{/if}
|
||||
</AlignWrapper>
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Button, type ButtonType } from '$lib/components/common'
|
||||
import { faArrowRight, faRefresh } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
@@ -66,10 +65,10 @@
|
||||
<div class="flex flex-col gap-2 px-4 w-full">
|
||||
<div>
|
||||
{#if componentInput?.type != 'runnable' || Object.values(componentInput?.fields ?? {}).filter((x) => x.type == 'user').length == 0}
|
||||
<span class="text-gray-600 italic text-sm py-2"
|
||||
>Run forms are meant to be associated with a runnable with some user inputs. Pick a
|
||||
runnable and set some 'Runnable Inputs' to 'User Input'</span
|
||||
>
|
||||
<span class="text-gray-600 italic text-sm py-2">
|
||||
Run forms are meant to be associated with a runnable with some user inputs. Pick a
|
||||
runnable and set some 'Runnable Inputs' to 'User Input'
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
@@ -81,6 +80,7 @@
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
on:click={() => {
|
||||
ownClick = true
|
||||
runnableComponent?.runComponent()
|
||||
|
||||
if (recomputeIds) {
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
$runnableComponents[id] = async () => {
|
||||
await executeComponent()
|
||||
}
|
||||
executeComponent()
|
||||
}
|
||||
executeComponent()
|
||||
})
|
||||
|
||||
let args: Record<string, any> = {}
|
||||
@@ -61,7 +61,10 @@
|
||||
$: fields && (currentStaticValues = computeStaticValues())
|
||||
$: if (JSON.stringify(currentStaticValues) != JSON.stringify(lazyStaticValues)) {
|
||||
lazyStaticValues = currentStaticValues
|
||||
setDebouncedExecute()
|
||||
|
||||
if (autoRefresh) {
|
||||
setDebouncedExecute()
|
||||
}
|
||||
}
|
||||
|
||||
$: fields && (lazyStaticValues = computeStaticValues())
|
||||
@@ -288,6 +291,7 @@
|
||||
<Alert type="error" title="Error during execution">
|
||||
<pre title={result.error} class="text-2xs whitespace-pre-wrap">{result.error}</pre>
|
||||
</Alert>
|
||||
<slot />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grow min-w-1/2 min-h-[66%]">
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
import JobArgs from '$lib/components/JobArgs.svelte'
|
||||
import LogViewer from '$lib/components/LogViewer.svelte'
|
||||
import Path from '$lib/components/Path.svelte'
|
||||
import SplitPanesWrapper from '$lib/components/splitPanes/SplitPanesWrapper.svelte'
|
||||
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
@@ -276,8 +275,9 @@
|
||||
<Drawer bind:open={publishDrawerOpen} size="800px">
|
||||
<DrawerContent title="Publish an App" on:close={() => (publishDrawerOpen = false)}>
|
||||
{#if appPath == ''}
|
||||
<Alert title="Require saving" type="error">Save this app once before you can publish it</Alert
|
||||
>
|
||||
<Alert title="Require saving" type="error">
|
||||
Save this app once before you can publish it
|
||||
</Alert>
|
||||
{:else}
|
||||
<Alert title="App executed on behalf of publisher">
|
||||
A viewer of the app will execute the runnables of the app on behalf of the publisher
|
||||
@@ -285,8 +285,8 @@
|
||||
guarantee tight security, a policy is computed at time of saving of the app which only allow
|
||||
the scripts/flows referred to in the app to be called on behalf of. Furthermore, static
|
||||
parameters are not overridable. Hence, users will only be able to use the app as intended by
|
||||
the publisher without risk for leaking resources not used in the app.</Alert
|
||||
>
|
||||
the publisher without risk for leaking resources not used in the app.
|
||||
</Alert>
|
||||
<div class="mt-4" />
|
||||
<Toggle
|
||||
options={{
|
||||
@@ -348,8 +348,8 @@
|
||||
</div>
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="preview" size="xs">
|
||||
<div class="inline-flex gap-1 items-center"> <Eye size={14} /> Preview</div></ToggleButton
|
||||
>
|
||||
<div class="inline-flex gap-1 items-center"> <Eye size={14} /> Preview</div>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
<div>
|
||||
@@ -357,23 +357,30 @@
|
||||
<ToggleButton position="left" value="sm" size="xs">
|
||||
<Smartphone size={14} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="lg" size="xs"><Laptop2 size={14} /></ToggleButton>
|
||||
<ToggleButton position="right" value="lg" size="xs">
|
||||
<Laptop2 size={14} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
|
||||
<span class="hidden lg:block">
|
||||
<ToggleButtonGroup bind:selected={$app.fullscreen}>
|
||||
<ToggleButton position="left" value={false} size="xs"
|
||||
><AlignHorizontalSpaceAround size={14} /> <Tooltip
|
||||
>The max width is 1168px and the content stay centered instead of taking the full page
|
||||
width</Tooltip
|
||||
></ToggleButton
|
||||
>
|
||||
<ToggleButton position="right" value={true} size="xs"><Expand size={14} /></ToggleButton>
|
||||
<ToggleButton position="left" value={false} size="xs">
|
||||
<div class="flex gap-1 justify-start">
|
||||
<AlignHorizontalSpaceAround size={14} />
|
||||
<Tooltip>
|
||||
The max width is 1168px and the content stay centered instead of taking the full page
|
||||
width
|
||||
</Tooltip>
|
||||
</div>
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value={true} size="xs">
|
||||
<Expand size={14} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-row grow gap-4 justify-end ">
|
||||
<div class="flex flex-row grow gap-2 justify-end items-center">
|
||||
<Button
|
||||
on:click={() => (jobsDrawerOpen = true)}
|
||||
color="light"
|
||||
@@ -389,7 +396,7 @@
|
||||
|
||||
<Button
|
||||
on:click={() => (publishDrawerOpen = true)}
|
||||
color="dark"
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
startIcon={{ icon: faExternalLink }}
|
||||
@@ -401,7 +408,9 @@
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={save}
|
||||
color="dark"
|
||||
size="xs">Save</Button
|
||||
size="xs"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
export let app: App
|
||||
</script>
|
||||
|
||||
<Button size="sm" variant="border" color="light" on:click={() => jsonViewerDrawer.toggleDrawer()}>
|
||||
<Button size="xs" variant="border" color="light" on:click={() => jsonViewerDrawer.toggleDrawer()}>
|
||||
<Icon data={faFileExport} scale={0.6} class="inline mr-2" />
|
||||
JSON
|
||||
</Button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import type { AppComponent } from '../types'
|
||||
import { Anchor, Move, X } from 'lucide-svelte'
|
||||
import { Anchor, Move } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let component: AppComponent
|
||||
@@ -16,7 +16,7 @@
|
||||
<span
|
||||
title={`Id: ${component.id}`}
|
||||
class={classNames(
|
||||
'px-2 text-2xs font-bold rounded-t-sm w-fit absolute shadow -top-1 -left-2 border z-50',
|
||||
'px-2 text-2xs font-bold w-fit absolute shadow -top-1 -left-2 border z-50',
|
||||
selected
|
||||
? 'bg-indigo-500/90 border-blue-500 text-white'
|
||||
: 'bg-gray-200/90 border-gray-300 text-gray-500'
|
||||
@@ -30,13 +30,11 @@
|
||||
<button
|
||||
title="Position locking"
|
||||
class={classNames(
|
||||
'text-gray-800 px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit shadow border border-gray-300 absolute -top-1 right-[2.5rem] z-50 cursor-pointer',
|
||||
'text-gray-800 px-1 text-2xs py-0.5 font-bold w-fit shadow border border-gray-300 absolute -top-1 right-[2.5rem] z-50 cursor-pointer',
|
||||
' hover:bg-gray-300',
|
||||
selected ? 'bg-gray-200/80' : 'bg-gray-200/80'
|
||||
)}
|
||||
on:click={() => {
|
||||
dispatch('lock')
|
||||
}}
|
||||
on:click={() => dispatch('lock')}
|
||||
>
|
||||
{#if locked}
|
||||
<Anchor aria-label="Unlock position" size={14} class="text-orange-500" />
|
||||
@@ -53,6 +51,8 @@
|
||||
class={classNames(
|
||||
'text-gray-600 px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute border border-gray-300 -top-1 shadow right-[4.5rem] z-50 cursor-move',
|
||||
'bg-gray-200/80'
|
||||
)}><Move size={14} /></span
|
||||
)}
|
||||
>
|
||||
<Move size={14} />
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onDestroy, onMount } from 'svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../types'
|
||||
import Grid from 'svelte-grid'
|
||||
import ComponentEditor from './ComponentEditor.svelte'
|
||||
@@ -146,6 +146,7 @@
|
||||
>{dataItem.data.id}</div
|
||||
>
|
||||
{/if}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
on:pointerdown={() => {
|
||||
selectComponent(dataItem.data.id)
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
|
||||
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
|
||||
>
|
||||
<svelte:component this={displayData[item.type].icon} class="text-blue-800" />
|
||||
<svelte:component this={displayData[item.type].icon} />
|
||||
<div class="text-xs w-full text-center ellipsize">
|
||||
{displayData[item.type].name}
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
const { runnableComponents } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let editor: Editor
|
||||
|
||||
let validCode = false
|
||||
|
||||
async function inferInlineScriptSchema(
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
>
|
||||
<div class="flex justify-center flex-col items-center gap-2">
|
||||
<MousePointerClick size={18} />
|
||||
|
||||
<span class="text-xs">Pick</span>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Alert } from '$lib/components/common'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
|
||||
import { faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import type { AppSection } from '../../types'
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
|
||||
export let section: AppSection | undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let deleteConfirmedCallback: (() => void) | undefined = undefined
|
||||
$: open = Boolean(deleteConfirmedCallback)
|
||||
|
||||
function deleteSection(event: CustomEvent<PointerEvent>) {
|
||||
if (
|
||||
section &&
|
||||
Array.isArray(section.components) &&
|
||||
section?.components.length > 0 &&
|
||||
!event.detail.shiftKey
|
||||
) {
|
||||
deleteConfirmedCallback = () => {
|
||||
dispatch('remove')
|
||||
deleteConfirmedCallback = undefined
|
||||
}
|
||||
} else {
|
||||
dispatch('remove')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if section}
|
||||
<div class="flex flex-col w-full divide-y">
|
||||
<span class="text-sm border-y w-full py-1 px-2 bg-gray-800 text-white">Section editor</span>
|
||||
|
||||
<PanelSection title="Danger zone">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="border"
|
||||
color="red"
|
||||
startIcon={{ icon: faTrashAlt }}
|
||||
on:click={deleteSection}
|
||||
>
|
||||
Delete section
|
||||
</Button>
|
||||
</PanelSection>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<ConfirmationModal
|
||||
{open}
|
||||
title="Remove section"
|
||||
confirmationText="Remove"
|
||||
on:canceled={() => {
|
||||
deleteConfirmedCallback = undefined
|
||||
}}
|
||||
on:confirmed={() => {
|
||||
if (deleteConfirmedCallback) {
|
||||
deleteConfirmedCallback()
|
||||
}
|
||||
deleteConfirmedCallback = undefined
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col w-full space-y-4">
|
||||
<span>Are you sure you want to remove this section?</span>
|
||||
<Alert type="info" title="Bypass confirmation">
|
||||
<div>
|
||||
You can press
|
||||
<Badge color="dark-gray">SHIFT</Badge>
|
||||
while removing a resource to bypass confirmation.
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
@@ -54,11 +54,12 @@
|
||||
<Button size="xs" color="light" variant="border" startIcon={{ icon: faEdit }} on:click={edit}>
|
||||
Edit
|
||||
</Button>
|
||||
<Button size="xs" color="light" variant="border" on:click={detach}
|
||||
>Detach <Tooltip
|
||||
>Detaching an inline script keep it for later to be reused by another component</Tooltip
|
||||
></Button
|
||||
>
|
||||
<Button size="xs" color="light" variant="border" on:click={detach}>
|
||||
Detach
|
||||
<Tooltip>
|
||||
Detaching an inline script keep it for later to be reused by another component
|
||||
</Tooltip>
|
||||
</Button>
|
||||
{/if}
|
||||
<Button size="xs" color="red" variant="border" startIcon={{ icon: faClose }} on:click={clear}>
|
||||
Clear
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
<script lang="ts">
|
||||
</script>
|
||||
+56
-51
@@ -3,9 +3,9 @@
|
||||
import { Badge, Button } from '$lib/components/common'
|
||||
import { faArrowRight, faClose } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../../inputType'
|
||||
import type { ConnectedAppInput } from '../../../inputType'
|
||||
|
||||
export let componentInput: AppInput
|
||||
export let componentInput: ConnectedAppInput
|
||||
|
||||
const { connectingInput } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
if (
|
||||
!$connectingInput.opened &&
|
||||
$connectingInput.input !== undefined &&
|
||||
componentInput.type === 'connected' &&
|
||||
!componentInput.connection
|
||||
) {
|
||||
componentInput.connection = $connectingInput.input.connection
|
||||
@@ -25,56 +24,62 @@
|
||||
}
|
||||
}
|
||||
|
||||
function startConnecting() {
|
||||
$connectingInput = {
|
||||
opened: true,
|
||||
input: undefined,
|
||||
hoveredComponent: undefined
|
||||
}
|
||||
}
|
||||
|
||||
$: $connectingInput && applyConnection()
|
||||
</script>
|
||||
|
||||
{#if componentInput.type === 'connected'}
|
||||
{#if componentInput.connection}
|
||||
<div class="flex justify-between w-full gap-1">
|
||||
<span class="text-xs">Status</span>
|
||||
<Badge color="green">Connected</Badge>
|
||||
</div>
|
||||
<div class="flex justify-between w-full">
|
||||
<span class="text-xs">Component</span>
|
||||
<Badge color="indigo">{componentInput.connection.componentId}</Badge>
|
||||
</div>
|
||||
<div class="flex justify-between w-full">
|
||||
<span class="text-xs">Path</span>
|
||||
<Badge color="indigo">{componentInput.connection.path}</Badge>
|
||||
</div>
|
||||
<Button
|
||||
size="xs"
|
||||
startIcon={{ icon: faClose }}
|
||||
color="red"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
if (componentInput.type === 'connected') {
|
||||
componentInput.connection = undefined
|
||||
{#if componentInput.connection}
|
||||
<div class="flex justify-between w-full gap-1">
|
||||
<span class="text-xs">Status</span>
|
||||
<Badge color="green">Connected</Badge>
|
||||
</div>
|
||||
<div class="flex justify-between w-full">
|
||||
<span class="text-xs">Component</span>
|
||||
<Badge color="indigo">{componentInput.connection.componentId}</Badge>
|
||||
</div>
|
||||
<div class="flex justify-between w-full">
|
||||
<span class="text-xs">Path</span>
|
||||
<Badge color="indigo">{componentInput.connection.path}</Badge>
|
||||
</div>
|
||||
<Button
|
||||
size="xs"
|
||||
startIcon={{ icon: faClose }}
|
||||
color="red"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
if (componentInput.type === 'connected') {
|
||||
componentInput.connection = undefined
|
||||
}
|
||||
}}
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
{:else}
|
||||
<div class="flex justify-between w-full gap-1">
|
||||
<span class="text-xs">Status</span>
|
||||
<Badge color="yellow">Not connected</Badge>
|
||||
</div>
|
||||
<Button
|
||||
size="xs"
|
||||
endIcon={{ icon: faArrowRight }}
|
||||
color="blue"
|
||||
on:click={() => {
|
||||
if (componentInput.type === 'connected') {
|
||||
$connectingInput = {
|
||||
opened: true,
|
||||
input: undefined,
|
||||
hoveredComponent: undefined
|
||||
}
|
||||
}}
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
{:else}
|
||||
<div class="flex justify-between w-full gap-1">
|
||||
<span class="text-xs">Status</span>
|
||||
<Badge color="yellow">Not connected</Badge>
|
||||
</div>
|
||||
<Button
|
||||
size="xs"
|
||||
endIcon={{ icon: faArrowRight }}
|
||||
color="blue"
|
||||
on:click={() => {
|
||||
if (componentInput.type === 'connected') {
|
||||
$connectingInput = {
|
||||
opened: true,
|
||||
input: undefined,
|
||||
hoveredComponent: undefined
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
{/if}
|
||||
}
|
||||
}}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
+6
-4
@@ -46,10 +46,12 @@
|
||||
: undefined}
|
||||
/>
|
||||
{:else}
|
||||
<JsonEditor
|
||||
bind:value={componentInput.value}
|
||||
code={JSON.stringify(componentInput.value, null, 2)}
|
||||
/>
|
||||
<div class="flex w-full flex-col">
|
||||
<JsonEditor
|
||||
bind:value={componentInput.value}
|
||||
code={JSON.stringify(componentInput.value, null, 2)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if componentInput.fieldType === 'array'}
|
||||
<ArrayStaticInputEditor bind:componentInput />
|
||||
|
||||
@@ -10,16 +10,16 @@
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
export let selected: any
|
||||
const dispatch = createEventDispatcher()
|
||||
export let col = false
|
||||
|
||||
$: setSelected(selected)
|
||||
const dispatch = createEventDispatcher()
|
||||
const selectedContent = writable(selected)
|
||||
|
||||
function setSelected(selected: any) {
|
||||
selectedContent.set(selected)
|
||||
}
|
||||
|
||||
const selectedContent = writable(selected)
|
||||
$: setSelected(selected)
|
||||
|
||||
$: $selectedContent && dispatch('selected', $selectedContent)
|
||||
setContext<ToggleButtonContext>('ToggleButtonGroup', {
|
||||
|
||||
Reference in New Issue
Block a user