mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 16:02:14 +00:00
feat(frontend): App drawer (#1246)
* feat(frontend): app drawer * feat(frontend): app drawer * feat(frontend): app drawer * feat(frontend): app drawer wip * feat(frontend): drawer wip * feat(frontend): drawer wip * feat(frontend): app missing prop * feat(frontend): revert drawer changes * feat(frontend): highlight subgrid
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
{#if $app.subgrids?.[`${id}-0`]}
|
||||
<SubGridEditor
|
||||
{noPadding}
|
||||
{id}
|
||||
class={css?.container.class}
|
||||
style={css?.container.style}
|
||||
bind:subGrid={$app.subgrids[`${id}-0`]}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import Portal from 'svelte-portal'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { Button, ButtonType, Drawer, DrawerContent } from '$lib/components/common'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { AlignWrapper } from '../helpers'
|
||||
|
||||
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let noWFull = false
|
||||
|
||||
const { app, focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let gridContent: string[] | undefined = undefined
|
||||
let noPadding: boolean | undefined = undefined
|
||||
let drawerTitle: string | undefined = undefined
|
||||
let appDrawer: Drawer
|
||||
|
||||
let labelValue: string
|
||||
let color: ButtonType.Color
|
||||
let size: ButtonType.Size
|
||||
let disabled: boolean | undefined = undefined
|
||||
let fillContainer: boolean | undefined = undefined
|
||||
|
||||
$: css = concatCustomCss($app.css?.containercomponent, customCss)
|
||||
</script>
|
||||
|
||||
<div class="h-full w-full">
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
|
||||
<Button
|
||||
btnClasses={twMerge(
|
||||
$app.css?.['buttoncomponent']?.['button']?.class,
|
||||
fillContainer ? 'w-full h-full' : ''
|
||||
)}
|
||||
{disabled}
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
on:click={async (e) => {
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: 0
|
||||
}
|
||||
appDrawer.toggleDrawer()
|
||||
}}
|
||||
{size}
|
||||
{color}
|
||||
>
|
||||
<div>{labelValue}</div>
|
||||
</Button>
|
||||
</AlignWrapper>
|
||||
</div>
|
||||
|
||||
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
|
||||
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
|
||||
<InputValue {id} input={configuration.drawerTitle} bind:value={drawerTitle} />
|
||||
<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 {id} input={configuration.fillContainer} bind:value={fillContainer} />
|
||||
|
||||
<Portal target="#app-editor-top-level-drawer">
|
||||
<Drawer bind:this={appDrawer} size="800px" alwaysOpen positionClass="!absolute">
|
||||
<DrawerContent title={drawerTitle} on:close={appDrawer.toggleDrawer}>
|
||||
{#if $app.subgrids?.[`${id}-0`]}
|
||||
<SubGridEditor
|
||||
{noPadding}
|
||||
{id}
|
||||
class={css?.container.class}
|
||||
style={css?.container.style}
|
||||
bind:subGrid={$app.subgrids[`${id}-0`]}
|
||||
containerHeight={1200}
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</Portal>
|
||||
@@ -72,6 +72,7 @@
|
||||
{#if $app.subgrids}
|
||||
{#each tabs ?? [] as res, i}
|
||||
<SubGridEditor
|
||||
{id}
|
||||
visible={i === selectedIndex}
|
||||
bind:subGrid={$app.subgrids[`${id}-${i}`]}
|
||||
{noPadding}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faCode, faPlus, faSliders } from '@fortawesome/free-solid-svg-icons'
|
||||
import ContextPanel from './contextPanel/ContextPanel.svelte'
|
||||
import { encodeState } from '$lib/utils'
|
||||
import { classNames, encodeState } from '$lib/utils'
|
||||
import AppPreview from './AppPreview.svelte'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
|
||||
@@ -59,7 +59,6 @@
|
||||
|
||||
const runnableComponents = writable<Record<string, () => Promise<void>>>({})
|
||||
const errorByComponent = writable<Record<string, { error: string; componentId: string }>>({})
|
||||
|
||||
const focusedGrid = writable<FocusedGrid | undefined>(undefined)
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
@@ -126,7 +125,7 @@
|
||||
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
class="absolute w-full h-screen bg-black border-2 bg-opacity-25 z-20 flex justify-center items-center"
|
||||
class="absolute w-full h-screen bg-black border-2 bg-opacity-25 z-20 flex justify-center items-center"
|
||||
/>
|
||||
{/if}
|
||||
{#if !$userStore?.operator}
|
||||
@@ -172,14 +171,17 @@
|
||||
style={$appStore.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<div
|
||||
class="relative mx-auto w-full h-full overflow-auto {app.fullscreen
|
||||
? ''
|
||||
: 'max-w-6xl'}"
|
||||
class={classNames(
|
||||
'relative mx-auto w-full h-full overflow-auto',
|
||||
app.fullscreen ? '' : 'max-w-6xl'
|
||||
)}
|
||||
>
|
||||
{#if $appStore.grid}
|
||||
<div class={width}>
|
||||
<GridEditor {policy} />
|
||||
</div>
|
||||
|
||||
<div id="app-editor-top-level-drawer" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -76,6 +76,8 @@
|
||||
$: lockedClasses = isLocked ? '!max-h-[400px] overflow-hidden pointer-events-none' : ''
|
||||
</script>
|
||||
|
||||
<div id="app-editor-top-level-drawer" />
|
||||
|
||||
<div class="relative">
|
||||
<div
|
||||
class="{$$props.class} {lockedClasses} h-full
|
||||
@@ -87,6 +89,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if isLocked}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
|
||||
@@ -135,7 +135,7 @@
|
||||
|
||||
<div class="relative w-full z-20 overflow-visible">
|
||||
<div
|
||||
class="w-full sticky top-0 flex justify-between border-l border-r border-b {$connectingInput?.opened
|
||||
class="w-full sticky top-0 flex justify-between border-l border-r border-b {$connectingInput?.opened
|
||||
? ''
|
||||
: 'bg-gray-50 '} px-4 py-2 items-center gap-4"
|
||||
style="z-index: 1000;"
|
||||
@@ -149,9 +149,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
style={$app.css?.['app']?.['grid']?.style}
|
||||
class={twMerge('px-4 pt-4 pb-2 overflow-visible', $app.css?.['app']?.['grid']?.class ?? '')}
|
||||
class={twMerge('px-4 pt-4 pb-2 overflow-visible', $app.css?.['app']?.['grid']?.class ?? '')}
|
||||
on:pointerdown={onpointerdown}
|
||||
on:pointerleave={onpointerup}
|
||||
on:pointerup={onpointerup}
|
||||
|
||||
@@ -13,17 +13,18 @@
|
||||
export { classes as class }
|
||||
export let style = ''
|
||||
export let noPadding = false
|
||||
//export let id: string
|
||||
export let subGrid: GridItem[] = []
|
||||
export let visible: boolean = true
|
||||
export let id: string
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const { app, connectingInput, selectedComponent, focusedGrid } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let pointerdown = false
|
||||
$: highlight = id === $focusedGrid?.parentComponentId && $selectedComponent !== id
|
||||
|
||||
let pointerdown = false
|
||||
let onComponent: string | undefined = undefined
|
||||
|
||||
const onpointerdown = (e) => {
|
||||
@@ -64,11 +65,16 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="relative w-full subgrid {visible ? 'visible' : 'invisible h-0 overflow-hidden'}"
|
||||
class="relative w-full subgrid {visible ? 'visible' : 'invisible h-0 overflow-hidden'} "
|
||||
bind:this={container}
|
||||
>
|
||||
<div
|
||||
class={twMerge('py-2 overflow-auto', classes ?? '', noPadding ? 'px-0' : 'px-2')}
|
||||
class={twMerge(
|
||||
'py-2 overflow-auto',
|
||||
classes ?? '',
|
||||
noPadding ? 'px-0' : 'px-2',
|
||||
highlight ? 'border-indigo-500 border border-dashed' : ''
|
||||
)}
|
||||
on:pointerdown|stopPropagation={onpointerdown}
|
||||
on:pointerleave={onpointerup}
|
||||
on:pointerup={onpointerup}
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
} from '../../components'
|
||||
import type { AppComponent } from './components'
|
||||
import AppAggridTable from '../../components/display/table/AppAggridTable.svelte'
|
||||
import AppDrawer from '../../components/layout/AppDrawer.svelte'
|
||||
|
||||
export let pComponent: AppComponent
|
||||
export let selected: boolean
|
||||
@@ -231,6 +232,8 @@
|
||||
<AppFileInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'imagecomponent'}
|
||||
<AppImage {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'drawercomponent'}
|
||||
<AppDrawer {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,8 @@ import {
|
||||
SeparatorHorizontal,
|
||||
SeparatorVertical,
|
||||
Paperclip,
|
||||
Image
|
||||
Image,
|
||||
SidebarClose
|
||||
} from 'lucide-svelte'
|
||||
import type { BaseAppComponent } from '../../types'
|
||||
|
||||
@@ -78,6 +79,7 @@ export type TabsComponent = BaseComponent<'tabscomponent'> & {
|
||||
tabs: string[]
|
||||
}
|
||||
export type ContainerComponent = BaseComponent<'containercomponent'>
|
||||
export type DrawerComponent = BaseComponent<'drawercomponent'>
|
||||
|
||||
export type AppComponent = BaseAppComponent &
|
||||
(
|
||||
@@ -112,6 +114,7 @@ export type AppComponent = BaseAppComponent &
|
||||
| FileInputComponent
|
||||
| ImageComponent
|
||||
| AggridComponent
|
||||
| DrawerComponent
|
||||
)
|
||||
|
||||
export type AppComponentDimensions = `${IntRange<
|
||||
@@ -1274,5 +1277,64 @@ Hello \${ctx.username}
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
drawercomponent: {
|
||||
name: 'Drawer',
|
||||
icon: SidebarClose,
|
||||
dims: '1:1-2:1',
|
||||
data: {
|
||||
softWrap: true,
|
||||
id: '',
|
||||
type: 'drawercomponent',
|
||||
horizontalAlignment: 'center',
|
||||
verticalAlignment: 'center',
|
||||
configuration: {
|
||||
noPadding: {
|
||||
type: 'static',
|
||||
fieldType: 'boolean',
|
||||
value: false,
|
||||
onlyStatic: true
|
||||
},
|
||||
drawerTitle: {
|
||||
type: 'static',
|
||||
fieldType: 'text',
|
||||
value: 'Drawer title',
|
||||
onlyStatic: true
|
||||
},
|
||||
label: {
|
||||
type: 'static',
|
||||
fieldType: 'text',
|
||||
value: 'Press me'
|
||||
},
|
||||
color: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
onlyStatic: true,
|
||||
optionValuesKey: 'buttonColorOptions',
|
||||
value: 'blue'
|
||||
},
|
||||
size: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
onlyStatic: true,
|
||||
optionValuesKey: 'buttonSizeOptions',
|
||||
value: 'xs'
|
||||
},
|
||||
fillContainer: {
|
||||
fieldType: 'boolean',
|
||||
type: 'static',
|
||||
onlyStatic: true,
|
||||
value: false
|
||||
},
|
||||
disabled: {
|
||||
fieldType: 'boolean',
|
||||
type: 'eval',
|
||||
expr: 'false'
|
||||
}
|
||||
},
|
||||
componentInput: undefined,
|
||||
card: false,
|
||||
numberOfSubgrids: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ const layout: ComponentSet = {
|
||||
'tabscomponent',
|
||||
'containercomponent',
|
||||
'horizontaldividercomponent',
|
||||
'verticaldividercomponent'
|
||||
'verticaldividercomponent',
|
||||
'drawercomponent'
|
||||
]
|
||||
} as const
|
||||
|
||||
@@ -46,8 +47,8 @@ const display: ComponentSet = {
|
||||
'plotlycomponent',
|
||||
'scatterchartcomponent',
|
||||
'timeseriescomponent',
|
||||
'displaycomponent',
|
||||
'displaycomponent'
|
||||
]
|
||||
} as const
|
||||
|
||||
export const COMPONENT_SETS = [layout, buttons, inputs, display] as const
|
||||
export const COMPONENT_SETS = [layout, buttons, inputs, display] as const
|
||||
|
||||
@@ -76,9 +76,15 @@
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
<aside class="drawer {$$props.class}" class:open class:close={!open && timeout} {style}>
|
||||
<div class="overlay" on:click={handleClickAway} />
|
||||
<div class="panel {placement}" class:size>
|
||||
<aside
|
||||
class="drawer {$$props.class} {$$props.positionClass}"
|
||||
class:open
|
||||
class:close={!open && timeout}
|
||||
{style}
|
||||
>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="overlay {$$props.positionClass}" on:click={handleClickAway} />
|
||||
<div class="panel {placement} {$$props.positionClass}" class:size>
|
||||
{#if open || !timeout || alwaysOpen}
|
||||
<slot />
|
||||
{/if}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { faClose } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import Button from '../button/Button.svelte'
|
||||
|
||||
export let title: string | undefined = undefined
|
||||
export let overflow_y = true
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"lib": ["esnext", "DOM"],
|
||||
"target": "esnext",
|
||||
"preserveValueImports": true,
|
||||
|
||||
"outDir": "build",
|
||||
|
||||
/**
|
||||
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
|
||||
to enforce using \`import type\` instead of \`import\` for Types.
|
||||
|
||||
Reference in New Issue
Block a user