feat(frontend): App Schema Form component (#1533)

* feat(frontend): app static form

* feat(frontend): working schemaForm

* feat(frontend): add lightmode

* feat(frontend): sync values direclty

* feat(frontend): remove dev code
This commit is contained in:
Faton Ramadani
2023-05-05 16:40:07 +02:00
committed by GitHub
parent 1be4658150
commit 85c0d939f5
10 changed files with 125 additions and 22 deletions
+26 -17
View File
@@ -18,6 +18,7 @@
const dispatch = createEventDispatcher()
export let lightMode: boolean = false
export let schema: Schema = emptySchema()
if (!schema) {
schema = emptySchema()
@@ -162,7 +163,7 @@
<Button
variant="contained"
color="dark"
size="sm"
size={lightMode ? 'xs' : 'sm'}
startIcon={{ icon: faPlus }}
on:click={() => {
modalProperty = Object.assign({}, DEFAULT_PROPERTY)
@@ -179,6 +180,7 @@
options={{
right: 'As JSON'
}}
size={lightMode ? 'xs' : 'sm'}
/>
<div class="ml-2">
<Tooltip
@@ -199,9 +201,11 @@
<tr slot="header-row">
<th>Name</th>
<th>Type</th>
<th>Description</th>
<th>Default</th>
<th>Required</th>
{#if !lightMode}
<th>Description</th>
<th>Default</th>
<th>Required</th>
{/if}
<th />
</tr>
<tbody slot="body">
@@ -211,45 +215,50 @@
<td>
<SchemaEditorProperty {property} />
</td>
<td>{property.description ?? ''}</td>
<td>{property.default ? JSON.stringify(property.default) : ''}</td>
<td
>{#if schema.required.includes(name)}
<span class="text-red-600 font-bold text-lg">*</span>
{/if}</td
>
{#if !lightMode}
<td>{property.description ?? ''}</td>
<td>{property.default ? JSON.stringify(property.default) : ''}</td>
<td>
{#if schema.required.includes(name)}
<span class="text-red-600 font-bold text-lg">*</span>
{/if}
</td>
{/if}
<td class="justify-end flex">
{#if i > 0}
<button
on:click={() => changePosition(i, true)}
class="text-lg mr-2 {isAnimated ? 'invisible' : ''}"
>
&uparrow;</button
>
&uparrow;
</button>
{/if}
{#if i < Object.keys(schema.properties).length - 1}
<button
on:click={() => changePosition(i, false)}
class="text-lg mr-2 {isAnimated ? 'invisible' : ''}">&downarrow;</button
>
class="text-lg mr-2 {isAnimated ? 'invisible' : ''}"
>&downarrow;
</button>
{/if}
<Button
color="red"
variant="border"
btnClasses="mx-2"
size="sm"
size={lightMode ? 'xs' : 'sm'}
startIcon={{ icon: faTrash }}
on:click={() => handleDeleteArgument(name)}
iconOnly={lightMode}
>
Delete
</Button>
<Button
color="light"
variant="border"
size="sm"
size={lightMode ? 'xs' : 'sm'}
startIcon={{ icon: faPen }}
on:click={() => startEditArgument(name)}
iconOnly={lightMode}
>
Edit
</Button>
@@ -0,0 +1,54 @@
<script lang="ts">
import { getContext } from 'svelte'
import { initOutput, selectId } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { AppViewerContext } from '../../types'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import LightweightSchemaForm from '$lib/components/LightweightSchemaForm.svelte'
import type { Schema } from '$lib/common'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let render: boolean
const { worldStore, connectingInput, app, selectedComponent } =
getContext<AppViewerContext>('AppViewerContext')
const outputs = initOutput($worldStore, id, {
result: undefined,
loading: false,
values: {}
})
let result: Schema | undefined = undefined
let args: Record<string, unknown> = {}
function handleArgsChange() {
const newArgs: Record<string, unknown> = {}
for (const key in args) {
if (result?.properties[key]) {
newArgs[key] = args[key]
}
}
outputs.values.set(newArgs, true)
}
$: args && handleArgsChange()
</script>
<RunnableWrapper {outputs} {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
{#if result && Object.keys(result.properties).length > 0}
<div
class="m-2"
on:pointerdown|stopPropagation={(e) =>
!$connectingInput.opened && selectId(e, id, selectedComponent, $app)}
>
<LightweightSchemaForm schema={result} bind:args />
</div>
{:else}
<p class="m-2 italic"> Empty form </p>
{/if}
</RunnableWrapper>
@@ -39,6 +39,7 @@
AppPdf
} from '../../components'
import AppMultiSelect from '../../components/inputs/AppMultiSelect.svelte'
import AppSchemaForm from '../../components/buttons/AppSchemaForm.svelte'
export let component: AppComponent
export let selected: boolean
@@ -460,6 +461,13 @@
customCss={component.customCss}
{render}
/>
{:else if component.type === 'schemaformcomponent'}
<AppSchemaForm
id={component.id}
componentInput={component.componentInput}
{initializing}
{render}
/>
{/if}
</div>
</div>
@@ -105,6 +105,7 @@ export type HorizontalSplitPanesComponent = BaseComponent<'horizontalsplitpanesc
panes: number[]
}
export type PdfComponent = BaseComponent<'pdfcomponent'>
export type Schemaformcomponent = BaseComponent<'schemaformcomponent'>
export type TypedComponent =
| DisplayComponent
@@ -146,6 +147,7 @@ export type TypedComponent =
| VerticalSplitPanesComponent
| HorizontalSplitPanesComponent
| PdfComponent
| Schemaformcomponent
export type AppComponent = BaseAppComponent & TypedComponent
@@ -1656,6 +1658,27 @@ Hello \${ctx.username}
}
}
}
},
schemaformcomponent: {
name: 'Schema Form',
icon: FileText,
dims: '3:8-8:12' as AppComponentDimensions,
customCss: {
container: { class: '', style: '' }
},
initialData: {
componentInput: {
type: 'static',
fieldType: 'schema',
value: {
$schema: 'https://json-schema.org/draft/2020-12/schema',
properties: {},
required: [],
type: 'object'
}
},
configuration: {}
}
}
} as const
@@ -34,7 +34,8 @@ const inputs: ComponentSet = {
'checkboxcomponent',
'selectcomponent',
'resourceselectcomponent',
'multiselectcomponent'
'multiselectcomponent',
'schemaformcomponent'
]
} as const
@@ -550,6 +550,10 @@ export const quickStyleProperties: Record<
pdfcomponent: {
container: containerDefaultProps
},
schemaformcomponent: {
container: containerDefaultProps,
button: buttonDefaultProps
},
formcomponent: {
container: containerDefaultProps,
button: buttonDefaultProps
@@ -11,6 +11,7 @@
import { DollarSign } from 'lucide-svelte'
import Popover from '$lib/components/Popover.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import SchemaEditor from '$lib/components/SchemaEditor.svelte'
export let componentInput: StaticInput<any> | undefined
export let fieldType: InputType | undefined = undefined
@@ -110,6 +111,10 @@
{/if}
{:else if fieldType === 'array'}
<ArrayStaticInputEditor {subFieldType} bind:componentInput on:deleteArrayItem />
{:else if fieldType === 'schema'}
<div class="w-full">
<SchemaEditor bind:schema={componentInput.value} lightMode />
</div>
{:else}
<div class="flex gap-1 relative w-full">
<input
@@ -23,7 +23,6 @@
{isFrontend}
{triggerEvents}
{shoudlDisplayChangeEvents}
isTriggerable={isTriggerable(appComponent.type)}
/>
{:else}
<ScriptTriggers
@@ -31,6 +30,5 @@
{triggerEvents}
{isFrontend}
{shoudlDisplayChangeEvents}
isTriggerable={isTriggerable(appComponent.type)}
/>
{/if}
@@ -13,7 +13,6 @@
export let isFrontend: boolean = false
export let dependencies: string[] = []
export let shoudlDisplayChangeEvents: boolean = false
export let isTriggerable: boolean = false
$: changeEvents = isFrontend
? inlineScript?.refreshOn
@@ -57,7 +56,7 @@
}
</script>
{#if isFrontend && !isTriggerable}
{#if isFrontend && shoudlDisplayChangeEvents}
<div class="flex mb-4">
<Button
size="xs2"
@@ -20,6 +20,7 @@ export type InputType =
| 'labeledresource'
| 'labeledselect'
| 'tab-select'
| 'schema'
// Connection to an output of another component
// defined by the id of the component and the path of the output
@@ -160,6 +161,7 @@ export type AppInput =
| AppInputSpec<'labeledselect', object>
| AppInputSpec<'labeledresource', object>
| AppInputSpec<'array', object[], 'tab-select'>
| AppInputSpec<'schema', object>
export type RowAppInput = Extract<AppInput, { type: 'row' }>
export type StaticAppInput = Extract<AppInput, { type: 'static' }>