mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 16:02:28 +00:00
feat(frontend): Component control (#1293)
* fix(frontend): fix app tabs * fix(frontend): app controls * fix(frontend): app controls * fix(frontend): align output panel * fix(frontend): clean up * fix(frontend): refactor events * fix(frontend): fix display * fix(frontend): fix indentation * fix(frontend): merge main
This commit is contained in:
@@ -70,7 +70,6 @@
|
||||
{#if $app.subgrids?.[`${id}-${index}`]}
|
||||
<SubGridEditor
|
||||
visible={render}
|
||||
noYPadding
|
||||
{id}
|
||||
shouldHighlight={$focusedGrid?.subGridIndex === index}
|
||||
class={css?.container.class}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Tab, Tabs } from '$lib/components/common'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
@@ -80,9 +80,7 @@
|
||||
}
|
||||
|
||||
$: selected && handleTabSelection()
|
||||
|
||||
$: selectedIndex = tabs?.indexOf(selected) ?? -1
|
||||
|
||||
$: css = concatCustomCss($app.css?.tabscomponent, customCss)
|
||||
|
||||
let subgridWidth: number | undefined = undefined
|
||||
@@ -131,7 +129,8 @@
|
||||
|
||||
<div bind:clientWidth={subgridWidth} class="w-full">
|
||||
{#if $app.subgrids}
|
||||
{#each tabs ?? [] as res, i}<SubGridEditor
|
||||
{#each tabs ?? [] as res, i}
|
||||
<SubGridEditor
|
||||
{id}
|
||||
visible={render && i === selectedIndex}
|
||||
bind:subGrid={$app.subgrids[`${id}-${i}`]}
|
||||
@@ -141,7 +140,8 @@
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
/>{/each}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -93,7 +93,8 @@
|
||||
stateId: writable(0),
|
||||
parentWidth: writable(0),
|
||||
state: writable({}),
|
||||
componentControl: writable({})
|
||||
componentControl: writable({}),
|
||||
hoverStore: writable(undefined)
|
||||
})
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
|
||||
@@ -67,7 +67,8 @@
|
||||
stateId: writable(0),
|
||||
parentWidth,
|
||||
state: writable({}),
|
||||
componentControl: writable({})
|
||||
componentControl: writable({}),
|
||||
hoverStore: writable(undefined)
|
||||
})
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import { Alert, Button } from '$lib/components/common'
|
||||
import type { AppComponent } from './component'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let component: AppComponent
|
||||
export let selected: boolean
|
||||
@@ -26,63 +27,61 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<span
|
||||
title={`Id: ${component.id}`}
|
||||
class={classNames(
|
||||
'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'
|
||||
)}
|
||||
style="padding-top: 1px; padding-bottom: 1px;"
|
||||
>
|
||||
{component.id}
|
||||
</span>
|
||||
|
||||
{#if pointerdown || selected || hover}
|
||||
<button
|
||||
title="Lock Position"
|
||||
class={classNames(
|
||||
'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:pointerdown|stopPropagation
|
||||
>
|
||||
{#if locked}
|
||||
<Anchor aria-label="Unlock position" size={14} class="text-orange-500" />
|
||||
{:else}
|
||||
<Anchor aria-label="Lock position" size={14} />
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
title="Expand"
|
||||
class={classNames(
|
||||
'text-gray-800 px-1 text-2xs py-0.5 font-bold w-fit shadow border border-gray-300 absolute -top-1 right-[4.5rem] z-50 cursor-pointer',
|
||||
' hover:bg-gray-300',
|
||||
selected ? 'bg-gray-200/80' : 'bg-gray-200/80'
|
||||
)}
|
||||
on:click={() => dispatch('expand')}
|
||||
on:pointerdown|stopPropagation
|
||||
>
|
||||
<Expand aria-label="Expand position" size={14} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if selected || hover}
|
||||
<span
|
||||
title="Move"
|
||||
on:mousedown|stopPropagation|capture
|
||||
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-[0.5rem] z-50 cursor-move',
|
||||
'bg-gray-200/80'
|
||||
title={`Id: ${component.id}`}
|
||||
class={twMerge(
|
||||
'px-2 text-2xs font-semibold w-fit absolute shadow -top-[9px] -left-[8px] border rounded-sm z-50',
|
||||
selected
|
||||
? 'bg-indigo-500/90 border-indigo-600 text-white'
|
||||
: 'bg-blue-500/90 border-blue-600 text-white'
|
||||
)}
|
||||
>
|
||||
<Move size={14} />
|
||||
{component.id}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if selected}
|
||||
<div class="top-[-9px] -right-[8px] flex flex-row absolute gap-1.5 z-50">
|
||||
<button
|
||||
title="Expand"
|
||||
class={classNames(
|
||||
'px-1 text-2xs py-0.5 font-bold w-fit border cursor-pointer rounded-sm',
|
||||
'bg-indigo-100 text-indigo-600 border-indigo-500 hover:bg-indigo-200 hover:text-indigo-800'
|
||||
)}
|
||||
on:click={() => dispatch('expand')}
|
||||
on:pointerdown|stopPropagation
|
||||
>
|
||||
<Expand aria-label="Expand position" size={14} />
|
||||
</button>
|
||||
<button
|
||||
title="Lock Position"
|
||||
class={classNames(
|
||||
'px-1 text-2xs py-0.5 font-bold w-fit border rounded-sm cursor-pointer',
|
||||
'bg-indigo-100 text-indigo-600 border-indigo-500 hover:bg-indigo-200 hover:text-indigo-800'
|
||||
)}
|
||||
on:click={() => dispatch('lock')}
|
||||
on:pointerdown|stopPropagation
|
||||
>
|
||||
{#if locked}
|
||||
<Anchor aria-label="Unlock position" size={14} class="text-orange-500" />
|
||||
{:else}
|
||||
<Anchor aria-label="Lock position" size={14} />
|
||||
{/if}
|
||||
</button>
|
||||
<span
|
||||
title="Move"
|
||||
on:mousedown|stopPropagation|capture
|
||||
class={classNames(
|
||||
'px-1 text-2xs py-0.5 font-bold w-fit border cursor-move rounded-sm',
|
||||
'bg-indigo-100 text-indigo-600 border-indigo-500 hover:bg-indigo-200 hover:text-indigo-800'
|
||||
)}
|
||||
>
|
||||
<Move size={14} />
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if error}
|
||||
{@const json = JSON.parse(JSON.stringify(error.error))}
|
||||
<span
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
style="height: {containerHeight}px; {style ?? ''}"
|
||||
>
|
||||
{#if $mode !== 'preview'}
|
||||
<div class={highlight ? 'border-gray-400 border border-dashed' : ''}>
|
||||
<div class={highlight ? 'border-gray-400 border border-dashed min-h-full' : ''}>
|
||||
<Grid
|
||||
items={subGrid}
|
||||
on:redraw={(e) => {
|
||||
|
||||
@@ -47,9 +47,8 @@
|
||||
export let pointerdown: boolean = false
|
||||
export let render: boolean
|
||||
|
||||
const { mode, connectingInput, app, errorByComponent } =
|
||||
const { mode, app, errorByComponent, hoverStore } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
let hover = false
|
||||
let initializing: boolean | undefined = undefined
|
||||
let componentContainerHeight: number = 0
|
||||
|
||||
@@ -57,14 +56,23 @@
|
||||
$: hasError = componentWithErrors.includes(component.id)
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<div
|
||||
on:pointerenter={() => (hover = true)}
|
||||
on:pointerleave={() => (hover = false)}
|
||||
on:mouseover|stopPropagation={() => {
|
||||
if (component.id !== $hoverStore) {
|
||||
$hoverStore = component.id
|
||||
}
|
||||
}}
|
||||
on:mouseout|stopPropagation={() => {
|
||||
if ($hoverStore !== undefined) {
|
||||
$hoverStore = undefined
|
||||
}
|
||||
}}
|
||||
class="h-full flex flex-col w-full component {initializing ? 'overflow-hidden h-0' : ''}"
|
||||
>
|
||||
{#if $mode !== 'preview'}
|
||||
<ComponentHeader
|
||||
{hover}
|
||||
hover={$hoverStore === component.id}
|
||||
{pointerdown}
|
||||
{component}
|
||||
{selected}
|
||||
@@ -77,10 +85,9 @@
|
||||
|
||||
<div
|
||||
class={twMerge(
|
||||
'h-full bg-white/40',
|
||||
selected && $mode !== 'preview' ? 'border border-blue-500' : '',
|
||||
!selected && $mode !== 'preview' ? 'border-gray-100' : '',
|
||||
$mode !== 'preview' && !$connectingInput.opened ? 'hover:border-blue-500' : '',
|
||||
'h-full bg-white/40 outline-1',
|
||||
$hoverStore === component.id && $mode !== 'preview' ? 'outline outline-blue-600' : '',
|
||||
selected && $mode !== 'preview' ? 'outline outline-indigo-600' : '',
|
||||
component.softWrap || hasError ? '' : 'overflow-auto',
|
||||
$mode != 'preview' ? 'cursor-pointer' : '',
|
||||
'relative z-auto',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import ObjectViewer from '$lib/components/propertyPicker/ObjectViewer.svelte'
|
||||
import { X } from 'lucide-svelte'
|
||||
import { getContext, setContext } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
@@ -33,7 +32,7 @@
|
||||
<div class="relative">
|
||||
<input
|
||||
bind:value={$search}
|
||||
class="px-2 py-1 border border-gray-300 rounded-sm {search ? 'pr-8' : ''}"
|
||||
class="px-2 pb-1 border border-gray-300 rounded-sm {search ? 'pr-8' : ''}"
|
||||
placeholder="Search outputs..."
|
||||
/>
|
||||
{#if search}
|
||||
@@ -53,7 +52,7 @@
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<span class="text-sm font-bold p-2">State & Context</span>
|
||||
<span class="text-xs font-bold p-2">State & Context</span>
|
||||
|
||||
<OutputHeader id={'ctx'} name={'App Context'} first color="blue">
|
||||
<ComponentOutputViewer
|
||||
|
||||
-2
@@ -4,8 +4,6 @@
|
||||
import BackgroundScriptOutput from './BackgroundScriptOutput.svelte'
|
||||
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
export let expanded: boolean = false
|
||||
</script>
|
||||
|
||||
{#each $app.hiddenInlineScripts as action, index}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import StaticInputEditor from './inputEditor/StaticInputEditor.svelte'
|
||||
import ConnectedInputEditor from './inputEditor/ConnectedInputEditor.svelte'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import { capitalize, classNames } from '$lib/utils'
|
||||
import { capitalize, classNames, getModifierKey } from '$lib/utils'
|
||||
import { buildExtraLib, fieldTypeToTsType } from '../../utils'
|
||||
import Recompute from './Recompute.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
@@ -222,34 +222,23 @@
|
||||
<Kbd>Del</Kbd>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
><span class="text-gray-600 text-xs mr-2">Copy:</span><Kbd>CTRL+C</Kbd> + <Kbd
|
||||
>CTRL+V</Kbd
|
||||
></div
|
||||
>
|
||||
<!-- <Button
|
||||
size="xs"
|
||||
color="dark"
|
||||
startIcon={{ icon: faCopy }}
|
||||
on:click={() => {
|
||||
if (component) {
|
||||
duplicateElement(component.id)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Duplicate component: {component.id}
|
||||
</Button> -->
|
||||
<div
|
||||
><span class="text-gray-600 text-xs mr-2">Move:</span><Kbd>CTRL+X</Kbd> + <Kbd
|
||||
>CTRL+V</Kbd
|
||||
></div
|
||||
>
|
||||
<div
|
||||
><span class="text-gray-600 text-xs mr-2">Navigate:</span><Kbd>←</Kbd><Kbd
|
||||
>↑</Kbd
|
||||
><Kbd>→</Kbd><Kbd>ESC</Kbd></div
|
||||
>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div>
|
||||
<span class="text-gray-600 text-xs mr-2"> Copy:</span>
|
||||
<Kbd>{getModifierKey()}</Kbd>+<Kbd>C</Kbd>,
|
||||
<Kbd>{getModifierKey()}</Kbd>+<Kbd>V</Kbd>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-600 text-xs mr-2">Move: </span>
|
||||
<Kbd>{getModifierKey()}</Kbd>+<Kbd>X</Kbd>,
|
||||
<Kbd>{getModifierKey()}</Kbd>+<Kbd>V</Kbd>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-gray-600 text-xs mr-2">Navigate:</span>
|
||||
<Kbd>←</Kbd>
|
||||
<Kbd>↑</Kbd><Kbd>→</Kbd>
|
||||
<Kbd>ESC</Kbd>
|
||||
</div>
|
||||
</div>
|
||||
</PanelSection>
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getContainerHeight } from './utils/container'
|
||||
import { moveItemsAroundItem, moveItem, getItemById, specifyUndefinedColumns } from './utils/item'
|
||||
import { moveItem, getItemById, specifyUndefinedColumns } from './utils/item'
|
||||
import { onMount, createEventDispatcher } from 'svelte'
|
||||
import { getColumn, throttle } from './utils/other'
|
||||
import MoveResize from './MoveResize.svelte'
|
||||
|
||||
@@ -141,6 +141,7 @@ export type AppViewerContext = {
|
||||
}
|
||||
>
|
||||
>
|
||||
hoverStore: Writable<string | undefined>
|
||||
}
|
||||
|
||||
export type AppEditorContext = {
|
||||
|
||||
@@ -705,3 +705,11 @@ export function throttle<T>(func: (...args: any[]) => T, wait: number) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isMac(): boolean {
|
||||
return navigator.userAgent.indexOf('Mac OS X') !== -1
|
||||
}
|
||||
|
||||
export function getModifierKey(): string {
|
||||
return isMac() ? '⌘' : 'CTRL'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user