mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 16:02:14 +00:00
fix(frontend): add a modal that is always mounted to make sure compon… (#1328)
* fix(frontend): add a modal that is always mounted to make sure component binding are properly set * fix(frontend): remove uselss open prop
This commit is contained in:
@@ -1,21 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { Button, type ButtonType } from '$lib/components/common'
|
||||
import { Button } 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 { AppViewerContext, ComponentCustomCSS, RichConfigurations } 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'
|
||||
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initConfig, initOutput } from '../../editor/appUtils'
|
||||
import { components } from '../../editor/component'
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import AlwaysMountedModal, {
|
||||
getModal
|
||||
} from '$lib/components/common/modal/AlwaysMountedModal.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -42,9 +42,7 @@
|
||||
|
||||
let isLoading: boolean = false
|
||||
let ownClick: boolean = false
|
||||
|
||||
let errors: Record<string, string> = {}
|
||||
let open: boolean = false
|
||||
|
||||
$: errorsMessage = Object.values(errors)
|
||||
.filter((x) => x != '')
|
||||
@@ -82,73 +80,64 @@
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<Portal>
|
||||
<Modal
|
||||
{open}
|
||||
title={resolvedConfig.label ?? ''}
|
||||
class={css?.popup?.class}
|
||||
style={css?.popup?.style}
|
||||
on:canceled={() => {
|
||||
open = false
|
||||
}}
|
||||
on:confirmed={() => {
|
||||
open = false
|
||||
}}
|
||||
<AlwaysMountedModal
|
||||
title={resolvedConfig.label ?? ''}
|
||||
class={css?.popup?.class}
|
||||
style={css?.popup?.style}
|
||||
>
|
||||
<RunnableWrapper
|
||||
bind:this={runnableWrapper}
|
||||
{recomputeIds}
|
||||
{render}
|
||||
bind:runnableComponent
|
||||
{componentInput}
|
||||
{id}
|
||||
{extraQueryParams}
|
||||
autoRefresh={false}
|
||||
forceSchemaDisplay={true}
|
||||
runnableClass="!block"
|
||||
{outputs}
|
||||
doOnSuccess={resolvedConfig.onSuccess}
|
||||
>
|
||||
<RunnableWrapper
|
||||
bind:this={runnableWrapper}
|
||||
{recomputeIds}
|
||||
{render}
|
||||
bind:runnableComponent
|
||||
{componentInput}
|
||||
{id}
|
||||
{extraQueryParams}
|
||||
autoRefresh={false}
|
||||
forceSchemaDisplay={true}
|
||||
runnableClass="!block"
|
||||
{outputs}
|
||||
doOnSuccess={resolvedConfig.onSuccess}
|
||||
>
|
||||
<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 () => {
|
||||
if (!runnableComponent) {
|
||||
runnableWrapper.onSuccess()
|
||||
} else {
|
||||
await runnableComponent?.runComponent()
|
||||
}
|
||||
open = false
|
||||
}}
|
||||
size="xs"
|
||||
color="dark"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
<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>
|
||||
</RunnableWrapper>
|
||||
</Modal>
|
||||
</Portal>
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
{loading}
|
||||
btnClasses="my-1"
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
on:click={async () => {
|
||||
if (!runnableComponent) {
|
||||
runnableWrapper.onSuccess()
|
||||
} else {
|
||||
await runnableComponent?.runComponent()
|
||||
}
|
||||
getModal().close()
|
||||
}}
|
||||
size="xs"
|
||||
color="dark"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
</AlwaysMountedModal>
|
||||
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
{#if errorsMessage}
|
||||
@@ -161,7 +150,7 @@
|
||||
btnClasses={css?.button?.class ?? ''}
|
||||
style={css?.button?.style ?? ''}
|
||||
on:click={(e) => {
|
||||
open = true
|
||||
getModal().open()
|
||||
}}
|
||||
>
|
||||
{resolvedConfig.label}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
<script context="module" lang="ts">
|
||||
let onTop: HTMLDivElement
|
||||
const modals = {}
|
||||
|
||||
export function getModal(id = '') {
|
||||
return modals[id]
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { Badge } from '..'
|
||||
import Button from '../button/Button.svelte'
|
||||
|
||||
let topDiv: HTMLDivElement
|
||||
let visible: boolean = false
|
||||
let prevOnTop: HTMLDivElement
|
||||
|
||||
export let title: string
|
||||
export let style = ''
|
||||
|
||||
export let id = ''
|
||||
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
if (onTop == topDiv) {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
break
|
||||
case 'Escape':
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
close()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function open() {
|
||||
if (visible) {
|
||||
return
|
||||
}
|
||||
prevOnTop = onTop
|
||||
onTop = topDiv
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
document.body.style.overflow = 'hidden'
|
||||
visible = true
|
||||
document.body.appendChild(topDiv)
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (!visible) {
|
||||
return
|
||||
}
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
|
||||
onTop = prevOnTop
|
||||
if (onTop == null) {
|
||||
document.body.style.overflow = ''
|
||||
}
|
||||
visible = false
|
||||
}
|
||||
|
||||
modals[id] = { open, close }
|
||||
|
||||
onDestroy(() => {
|
||||
delete modals[id]
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div id="topModal" class:visible bind:this={topDiv} on:click={() => close()}>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="relative bg-white p-4 rounded-md" on:click|stopPropagation={() => {}}>
|
||||
<div class={twMerge('max-w-screen-lg max-h-screen-80 overflow-auto', $$props.class)} {style}>
|
||||
<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={() => {
|
||||
close()
|
||||
}}
|
||||
color="light"
|
||||
size="sm"
|
||||
>
|
||||
<span class="gap-2">Cancel <Badge color="dark-gray">Escape</Badge></span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#topModal {
|
||||
visibility: hidden;
|
||||
z-index: 9999;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: #4448;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.visible {
|
||||
visibility: visible !important;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user