mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-22 00:01:34 +00:00
feat(frontend): app modal (#1518)
* feat(frontend): app modal wip * feat(frontend): Modal * feat(frontend): fix modal title * feat(frontend): Merge main * feat(frontend): fix build * feat(frontend): use ResolveConfig + rename prop * feat(frontend): use ResolveConfig + rename prop
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { Button } from '$lib/components/common'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { AlignWrapper } from '../helpers'
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import { getModal } from '$lib/components/common/modal/AlwaysMountedModal.svelte'
|
||||
import Portal from 'svelte-portal'
|
||||
import { clickOutside } from '$lib/utils'
|
||||
import { X } from 'lucide-svelte'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
|
||||
export let customCss: ComponentCustomCSS<'drawercomponent'> | undefined = undefined
|
||||
export let id: string
|
||||
export let configuration: RichConfigurations
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let noWFull = false
|
||||
export let render: boolean
|
||||
|
||||
const { app, focusedGrid, selectedComponent, worldStore, connectingInput, mode } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
//used so that we can count number of outputs setup for first refresh
|
||||
initOutput($worldStore, id, {})
|
||||
|
||||
$: css = concatCustomCss($app.css?.containercomponent, customCss)
|
||||
let open = false
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent): void {
|
||||
const key = event.key
|
||||
if (key === 'Escape' || key === 'Esc') {
|
||||
if (open) {
|
||||
event.preventDefault()
|
||||
closeDrawer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeDrawer(): void {
|
||||
open = false
|
||||
}
|
||||
|
||||
let resolvedConfig = initConfig(
|
||||
components['modalcomponent'].initialData.configuration,
|
||||
configuration
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="h-full w-full">
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
|
||||
<Button
|
||||
btnClasses={twMerge(
|
||||
$app.css?.['buttoncomponent']?.['button']?.class,
|
||||
resolvedConfig.buttonFillContainer ? 'w-full h-full' : ''
|
||||
)}
|
||||
disabled={resolvedConfig.buttonDisabled}
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
}}
|
||||
on:click={async (e) => {
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: 0
|
||||
}
|
||||
getModal(id)?.open()
|
||||
open = true
|
||||
}}
|
||||
size={resolvedConfig.buttonSize}
|
||||
color={resolvedConfig.buttonColor}
|
||||
>
|
||||
<div>{resolvedConfig.buttonLabel}</div>
|
||||
</Button>
|
||||
</AlignWrapper>
|
||||
</div>
|
||||
|
||||
{#each Object.keys(components['modalcomponent'].initialData.configuration) as key (key)}
|
||||
<ResolveConfig
|
||||
{id}
|
||||
{key}
|
||||
bind:resolvedConfig={resolvedConfig[key]}
|
||||
configuration={configuration[key]}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<InitializeComponent {id} />
|
||||
|
||||
<svelte:window on:keyup={handleKeyUp} />
|
||||
|
||||
<Portal target="#app-editor-top-level-drawer">
|
||||
<div
|
||||
class={twMerge(
|
||||
'absolute top-0 bottom-0 left-0 right-0 transition-all duration-50 overflow-hidden',
|
||||
open ? 'z-50 bg-black bg-opacity-60' : 'hidden'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
class="m-24 h-[80%] bg-white overflow-y-auto rounded-lg relative"
|
||||
use:clickOutside
|
||||
on:click_outside={() => {
|
||||
if ($mode !== 'dnd') {
|
||||
closeDrawer()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="p-4 border-b flex justify-between items-center">
|
||||
<div>{resolvedConfig.modalTitle}</div>
|
||||
<div class="w-8">
|
||||
<button
|
||||
on:click={() => {
|
||||
open = false
|
||||
}}
|
||||
class="hover:bg-gray-200 bg-gray-100 rounded-full w-8 h-8 flex items-center justify-center transition-all"
|
||||
>
|
||||
<X class="text-gray-500" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
if (!$connectingInput.opened) {
|
||||
$selectedComponent = [id]
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: 0
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if $app.subgrids?.[`${id}-0`]}
|
||||
<SubGridEditor
|
||||
visible={open && render}
|
||||
{id}
|
||||
class={css?.container?.class}
|
||||
style={css?.container?.style}
|
||||
subGridId={`${id}-0`}
|
||||
on:focus={() => {
|
||||
if (!$connectingInput.opened) {
|
||||
$selectedComponent = [id]
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: 0
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Portal>
|
||||
@@ -39,8 +39,9 @@
|
||||
AppPdf
|
||||
} from '../../components'
|
||||
import AppMultiSelect from '../../components/inputs/AppMultiSelect.svelte'
|
||||
import AppStepper from '../../components/layout/AppStepper.svelte'
|
||||
import AppModal from '../../components/layout/AppModal.svelte'
|
||||
import AppSchemaForm from '../../components/buttons/AppSchemaForm.svelte'
|
||||
import AppStepper from '../../components/layout/AppStepper.svelte'
|
||||
|
||||
export let component: AppComponent
|
||||
export let selected: boolean
|
||||
@@ -471,6 +472,15 @@
|
||||
customCss={component.customCss}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'modalcomponent'}
|
||||
<AppModal
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'schemaformcomponent'}
|
||||
<AppSchemaForm
|
||||
id={component.id}
|
||||
|
||||
@@ -105,6 +105,7 @@ export type HorizontalSplitPanesComponent = BaseComponent<'horizontalsplitpanesc
|
||||
panes: number[]
|
||||
}
|
||||
export type PdfComponent = BaseComponent<'pdfcomponent'>
|
||||
export type ModalComponent = BaseComponent<'modalcomponent'>
|
||||
export type StepperComponent = BaseComponent<'steppercomponent'> & {
|
||||
tabs: string[]
|
||||
}
|
||||
@@ -150,6 +151,7 @@ export type TypedComponent =
|
||||
| VerticalSplitPanesComponent
|
||||
| HorizontalSplitPanesComponent
|
||||
| PdfComponent
|
||||
| ModalComponent
|
||||
| StepperComponent
|
||||
| Schemaformcomponent
|
||||
|
||||
@@ -1687,6 +1689,60 @@ Hello \${ctx.username}
|
||||
}
|
||||
}
|
||||
},
|
||||
modalcomponent: {
|
||||
name: 'Modal',
|
||||
icon: SidebarClose,
|
||||
dims: '1:1-2:1' as AppComponentDimensions,
|
||||
customCss: {
|
||||
button: { class: '', style: '' },
|
||||
popup: { class: '', style: '' }
|
||||
},
|
||||
initialData: {
|
||||
horizontalAlignment: 'center',
|
||||
verticalAlignment: 'center',
|
||||
configuration: {
|
||||
modalTitle: {
|
||||
type: 'static',
|
||||
fieldType: 'text',
|
||||
value: 'Modal title',
|
||||
onlyStatic: true
|
||||
},
|
||||
buttonLabel: {
|
||||
type: 'static',
|
||||
fieldType: 'text',
|
||||
value: 'Press me'
|
||||
},
|
||||
buttonColor: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
onlyStatic: true,
|
||||
selectOptions: buttonColorOptions,
|
||||
value: 'blue'
|
||||
},
|
||||
buttonSize: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
onlyStatic: true,
|
||||
selectOptions: selectOptions.buttonSizeOptions,
|
||||
value: 'xs'
|
||||
},
|
||||
buttonFillContainer: {
|
||||
fieldType: 'boolean',
|
||||
type: 'static',
|
||||
onlyStatic: true,
|
||||
value: false
|
||||
},
|
||||
buttonDisabled: {
|
||||
fieldType: 'boolean',
|
||||
type: 'static',
|
||||
value: false
|
||||
}
|
||||
},
|
||||
componentInput: undefined,
|
||||
|
||||
numberOfSubgrids: 1
|
||||
}
|
||||
},
|
||||
schemaformcomponent: {
|
||||
name: 'Schema Form',
|
||||
icon: FileText,
|
||||
|
||||
@@ -10,6 +10,7 @@ const layout: ComponentSet = {
|
||||
'drawercomponent',
|
||||
'verticalsplitpanescomponent',
|
||||
'horizontalsplitpanescomponent',
|
||||
'modalcomponent',
|
||||
'steppercomponent'
|
||||
]
|
||||
} as const
|
||||
|
||||
@@ -556,10 +556,6 @@ export const quickStyleProperties: Record<
|
||||
pdfcomponent: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
schemaformcomponent: {
|
||||
container: containerDefaultProps,
|
||||
button: buttonDefaultProps
|
||||
},
|
||||
formcomponent: {
|
||||
container: containerDefaultProps,
|
||||
button: buttonDefaultProps
|
||||
@@ -616,6 +612,9 @@ export const quickStyleProperties: Record<
|
||||
drawercomponent: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
modalcomponent: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
plotlycomponent: {},
|
||||
selectcomponent: {
|
||||
input: inputDefaultProps
|
||||
@@ -696,5 +695,9 @@ export const quickStyleProperties: Record<
|
||||
},
|
||||
horizontalsplitpanescomponent: {
|
||||
container: containerDefaultProps
|
||||
},
|
||||
schemaformcomponent: {
|
||||
container: containerDefaultProps,
|
||||
button: buttonDefaultProps
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user