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:
Faton Ramadani
2023-03-24 17:30:04 +01:00
committed by GitHub
parent da24e9ab06
commit a527cb8222
2 changed files with 184 additions and 73 deletions
@@ -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>