feat: resource type picker in schema modal + proper initialization of raw javascript editor when applicable

This commit is contained in:
Ruben Fiszel
2022-07-29 14:13:44 +02:00
parent d2347dd5b9
commit 01bb107a0f
16 changed files with 189 additions and 124 deletions
+1
View File
@@ -56,6 +56,7 @@
}
.item-button {
@apply cursor-pointer;
@apply py-1;
@apply border border-gray-300;
@apply rounded-sm;
+1 -1
View File
@@ -290,7 +290,7 @@
on:input={() => dispatch('input', { rawValue: value, isRaw: false })}
/>
{/if}
{#if !required}
{#if !required && inputCat != 'resource-object'}
<div class="flex flex-row-reverse">
<button
{disabled}
@@ -20,10 +20,11 @@
export let width = '24px'
</script>
<div class="flex flex-row gap-2">
<div class="flex flex-row gap-2 justify-center -pl-2 whitespace-nowrap">
{#if !after}
{name}
{/if}
{#if name === 'postgresql'}
<PostgresIcon {height} {width} />
{:else if name === 'mysql'}
@@ -4,7 +4,7 @@
import ResourcePicker from './ResourcePicker.svelte'
import { workspaceStore } from '$lib/stores'
import SchemaForm from './SchemaForm.svelte'
import RadioButton from './RadioButton.svelte'
import RadioButtonV3 from './RadioButtonV3.svelte'
export let format: string
export let value: any
@@ -44,30 +44,34 @@
$: format.startsWith('resource-') && loadSchema(format)
</script>
<div class="max-w-lg">
<RadioButton
options={[
[`Resource (${resourceTypeName})`, 'resource'],
[`Raw object value`, 'raw']
]}
small={true}
bind:value={option}
/>
</div>
<div class="mt-1" />
{#if option == 'resource'}
<ResourcePicker
bind:value={path}
resourceType={format.split('-').length > 1 ? format.substring('resource-'.length) : undefined}
/>
{:else}
<div class="border rounded p-5">
<h2 class="mb-5">
Object of <a target="_blank" href="/resources">resource type</a>
{resourceTypeName}
</h2>
{#if !isString(args)}
<SchemaForm {schema} bind:isValid bind:args />
<div class="flex flex-row w-full gap-2">
<div class="shrink w-40">
<RadioButtonV3
options={[
[`Resource (${resourceTypeName})`, 'resource'],
[`Raw object value`, 'raw']
]}
bind:value={option}
/>
</div>
<div class="grow">
{#if option == 'resource'}
<ResourcePicker
bind:value={path}
resourceType={format.split('-').length > 1
? format.substring('resource-'.length)
: undefined}
/>
{:else}
<div class="border rounded p-5 w-full">
<h2 class="mb-5">
Object of <a target="_blank" href="/resources">resource type</a>
{resourceTypeName}
</h2>
{#if !isString(args)}
<SchemaForm {schema} bind:isValid bind:args />
{/if}
</div>
{/if}
</div>
{/if}
</div>
@@ -1,30 +1,14 @@
<script lang="ts">
import RadioButton from './RadioButton.svelte'
import ResourceTypePicker from './ResourceTypePicker.svelte'
export let format: string | undefined
let kind: 'resource' | 'none' = format?.startsWith('resource') ? 'resource' : 'none'
let resource: string | undefined = format?.startsWith('resource-')
? format.substring('resource-'.length)
: undefined
$: format =
kind == 'resource' ? (resource != undefined ? `resource-${resource}` : 'resource') : undefined
$: format = resource != undefined ? `resource-${resource}` : undefined
</script>
<RadioButton
label="Kind"
small={true}
options={[
['None', 'none'],
['Resource Type', 'resource']
]}
bind:value={kind}
/>
{#if kind == 'resource'}
<div class="mt-1" />
<ResourceTypePicker bind:value={resource} />
{/if}
<div class="mt-2" />
<ResourceTypePicker bind:value={resource} nonePickable={true} />
@@ -1,39 +0,0 @@
<script lang="ts">
export let label = ''
export let options: [string, any][]
export let value: any
export let small = false
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
</script>
<fieldset>
<legend class="sr-only">{label}</legend>
<div class="flex flex-row gap-2">
{#each options as [label, val]}
<label class:selected={val == value} class={`item-button ${small ? 'py-0' : 'py-3'}`}>
<input
type="radio"
value={val}
class="sr-only"
bind:group={value}
aria-labelledby="memory-option-0-label"
on:click={() => dispatch('change', val)}
/>
<p>{label}</p>
</label>
{/each}
</div>
</fieldset>
<style>
label {
@apply rounded-md px-3 flex items-center justify-center text-sm font-medium uppercase sm:flex-1 cursor-pointer focus:outline-none;
}
label.selected {
@apply bg-blue-500/70 text-white rounded-md px-3 flex items-center justify-center text-sm font-medium uppercase sm:flex-1 cursor-pointer focus:outline-none;
}
</style>
@@ -0,0 +1,31 @@
<script lang="ts">
export let label = ''
export let options: ([string, any] | string)[]
export let value: any
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
</script>
<fieldset class="w-full">
<legend class="sr-only">{label}</legend>
<div class="flex flex-row flex-wrap gap-2 items-center mb-2 w-full">
{#each options as [label, val]}
<label
class:item-button-selected={val == value}
class="item-button text-center text-sm h-full p-2 grow"
>
<input
type="radio"
value={val}
class="sr-only"
bind:group={value}
aria-labelledby="memory-option-0-label"
on:click={() => dispatch('change', val)}
/>
<p>{label}</p>
</label>
{/each}
</div>
</fieldset>
@@ -10,6 +10,7 @@
export let value: string | undefined
export let notPickable = false
export let nonePickable = false
async function loadResources() {
resources = await ResourceService.listResourceTypeNames({ workspace: $workspaceStore! })
@@ -25,9 +26,24 @@
</script>
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-x-2 gap-y-1 items-center mb-2">
{#if nonePickable}
<button
class="px-4 h-8 text-center {undefined == value
? 'item-button-selected'
: notPickable
? 'item-button-disabled'
: 'item-button'}"
on:click={() => {
value = undefined
dispatch('click')
}}
>
None
</button>
{/if}
{#each resources as r}
<button
class="px-4 h-8 {r == value
class="px-4 h-8 text-center {r == value
? 'item-button-selected'
: notPickable
? 'item-button-disabled'
@@ -1,7 +1,7 @@
<script lang="ts">
import SchemaModal, { DEFAULT_PROPERTY, modalToSchema, schemaToModal } from './SchemaModal.svelte'
import SchemaModal, { DEFAULT_PROPERTY, schemaToModal } from './SchemaModal.svelte'
import type { ModalSchemaProperty } from './SchemaModal.svelte'
import type { Schema } from '$lib/common'
import type { Schema, SchemaProperty } from '$lib/common'
import Editor from './Editor.svelte'
import { emptySchema, sendUserToast, truncate } from '$lib/utils'
import Tooltip from './Tooltip.svelte'
@@ -38,6 +38,18 @@
}
}
function modalToSchema(schema: ModalSchemaProperty): SchemaProperty {
return {
type: schema.selectedType,
description: schema.description,
pattern: schema.pattern,
default: schema.default,
enum: schema.enum_,
items: schema.items,
contentEncoding: schema.contentEncoding,
format: schema.format
}
}
function handleAddOrEditArgument(): void {
// If editing the arg's name, oldName containing the old argument name must be provided
argError = ''
@@ -46,6 +58,7 @@
} else if (Object.keys(schema.properties).includes(modalProperty.name) && !editing) {
argError = 'There is already an argument with this name'
} else {
console.log('XXXXXX')
schema.properties[modalProperty.name] = modalToSchema(modalProperty)
if (modalProperty.required) {
schema.required = [...schema.required, modalProperty.name]
@@ -72,7 +85,6 @@
function startEditArgument(argName: string): void {
argError = ''
if (Object.keys(schema.properties).includes(argName)) {
schemaModal.openModal()
editing = true
modalProperty = schemaToModal(
schema.properties[argName],
@@ -80,6 +92,7 @@
schema.required.includes(argName)
)
oldArgName = argName
schemaModal.openModal()
} else {
sendUserToast(`This argument does not exist and can't be edited`, true)
}
@@ -136,7 +149,7 @@
>
</button>
<button
class="default-button-secondary grow"
class="default-button-secondary grow mb-1"
on:click={() => {
modalProperty = Object.assign({}, DEFAULT_PROPERTY)
schemaModal.openModal()
+43 -7
View File
@@ -12,7 +12,7 @@
export let inputTransform = false
export let schema: Schema
export let args: Record<string, InputTransform> = {}
export let args: Record<string, InputTransform | any> = {}
export let editableSchema = false
export let extraLib: string = 'missing extraLib'
export let isValid: boolean = true
@@ -24,9 +24,38 @@
let overlays: { [id: string]: OverlayPropertyPicker } = {}
$: isValid = allTrue(inputCheck) ?? false
let propertiesTypes: { [id: string]: InputTransform.type } = {}
let propertiesTypes: { [id: string]: InputTransform.type } = inputTransform
? Object.keys(args).reduce((acc, key) => {
let type = args[key].type
if (type == InputTransform.type.JAVASCRIPT) {
if (codeToStaticTemplate(args[key].expr)) {
type = InputTransform.type.STATIC
}
}
acc[key] = type
return acc
}, {})
: {}
let inputCats: { [id: string]: InputCat } = {}
function codeToStaticTemplate(code?: string): string | undefined {
if (!code) return undefined
const lines = code
.split('\n')
.slice(1)
.filter((x) => x != '')
if (lines.length == 1) {
const line = lines[0].trim()
if (line[0] == '`' && line.charAt(line.length - 1) == '`') {
return line.slice(1, line.length - 1)
}
}
return undefined
}
function setPropertyType(id: string, rawValue: string, isRaw: boolean) {
const arg = args[id]
if (!arg) {
@@ -53,7 +82,7 @@
}
}
function hasOverlay(inputCat: InputCat) {
function isStaticTemplate(inputCat: InputCat) {
return inputCat === 'string' || inputCat === 'number'
}
</script>
@@ -89,12 +118,19 @@
}}
bind:value={propertiesTypes[argName]}
on:change={(e) => {
const staticTemplate = isStaticTemplate(inputCats[argName])
if (e.detail === InputTransform.type.JAVASCRIPT) {
args[argName].expr = getDefaultExpr(i ?? -1, argName, args[argName].value)
args[argName].expr = getDefaultExpr(
i ?? -1,
argName,
staticTemplate ? args[argName].value : undefined
)
args[argName].value = undefined
} else {
args[argName].value = staticTemplate
? codeToStaticTemplate(args[argName].expr)
: undefined
args[argName].expr = undefined
args[argName].value = undefined
}
args[argName].type = e.detail
@@ -107,7 +143,7 @@
<OverlayPropertyPicker
bind:this={overlays[argName]}
bind:pickableProperties
disabled={!hasOverlay(inputCats[argName])}
disabled={!isStaticTemplate(inputCats[argName])}
on:select={(event) => {
const toAppend = `\$\{${event.detail}}`
args[argName].value = `${args[argName].value ?? ''}${toAppend}`
@@ -140,7 +176,7 @@
bind:inputCat={inputCats[argName]}
numberAsString={true}
on:input={(e) => {
if (hasOverlay(inputCats[argName])) {
if (isStaticTemplate(inputCats[argName])) {
setPropertyType(argName, e.detail.rawValue, e.detail.isRaw)
}
}}
+21 -14
View File
@@ -18,18 +18,6 @@
contentEncoding?: 'base64' | 'binary'
}
export function modalToSchema(schema: ModalSchemaProperty): SchemaProperty {
return {
type: schema.selectedType,
description: schema.description,
pattern: schema.pattern,
default: schema.default,
enum: schema.enum_,
items: schema.items,
contentEncoding: schema.contentEncoding
}
}
export function schemaToModal(
schema: SchemaProperty,
name: string,
@@ -42,6 +30,7 @@
pattern: schema.pattern,
default: schema.default,
contentEncoding: schema.contentEncoding,
format: schema.format,
required
}
}
@@ -60,17 +49,22 @@
import ArgInput from './ArgInput.svelte'
import StringTypeNarrowing from './StringTypeNarrowing.svelte'
import Required from './Required.svelte'
import ResourceTypePicker from './ResourceTypePicker.svelte'
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte'
export let property: ModalSchemaProperty = DEFAULT_PROPERTY
export let error = ''
export let editing = false
export let oldArgName: string | undefined = undefined
let resource_type: string | undefined = undefined
const dispatch = createEventDispatcher()
let modal: Modal
export function openModal(): void {
modal.openModal()
resource_type = property.format?.substring(5)
}
export function closeModal(): void {
@@ -86,6 +80,12 @@
property.description = DEFAULT_PROPERTY.description
property.required = DEFAULT_PROPERTY.required
property.selectedType = DEFAULT_PROPERTY.selectedType
property.format = undefined
resource_type = undefined
}
$: if (property.selectedType == 'object' && resource_type) {
property.format = resource_type ? `$res:${resource_type}` : undefined
}
</script>
@@ -109,12 +109,16 @@
/>
</label>
<h3 class="font-semibold text-gray-700">Type<Required required={true} /></h3>
<div class="grid sm:grid-cols-3 md:grid-cols-4 gap-x-2 gap-y-1 items-center mb-2">
<div class="grid sm:grid-cols-3 md:grid-cols-4 gap-x-2 gap-y-1 items-center mb-2 w-full">
{#each ARG_TYPES as argType}
<button
class={argType == property.selectedType ? 'item-button-selected' : 'item-button'}
on:click={() => {
property.selectedType = argType
property.format = undefined
property.contentEncoding = undefined
property.enum_ = undefined
property.pattern = undefined
}}>{argType}</button
>
{/each}
@@ -138,7 +142,7 @@
pattern={property.pattern}
/>
{#if property.selectedType !== 'boolean'}
<h2 class="mb-2">Advanced</h2>
<h2 class="mb-2 mt-4">Advanced</h2>
{#if property.selectedType == 'string'}
<StringTypeNarrowing
@@ -153,6 +157,9 @@
<option value={{ type: 'string' }}> Items are strings</option>
<option value={{ type: 'number' }}>Items are numbers</option>
</select>
{:else if property.selectedType == 'object'}
<h3 class="mb-2 font-bold mt-4">Resource type</h3>
<ObjectTypeNarrowing bind:format={property.format} />
{:else}
<p>No advanced configuration for this type</p>
{/if}
@@ -12,8 +12,8 @@
import ScriptSchema from './ScriptSchema.svelte'
import { inferArgs } from '$lib/infer'
import Required from './Required.svelte'
import RadioButton from './RadioButton.svelte'
import { DENO_INIT_CODE, DENO_INIT_CODE_TRIGGER, initialCode } from '$lib/script_helpers'
import RadioButtonV3 from './RadioButtonV3.svelte'
let editor: ScriptEditor
let scriptSchema: ScriptSchema
@@ -179,9 +179,8 @@
</Path>
<h3 class="text-gray-700 pb-1 border-b">Language</h3>
<div class="max-w-md">
<RadioButton
<RadioButtonV3
label="Language"
small={true}
options={[
['Typescript (Deno)', 'deno'],
['Python 3.10', 'python3']
@@ -6,13 +6,13 @@
import { hubScripts, workspaceStore } from '$lib/stores'
import { createEventDispatcher } from 'svelte'
import ItemPicker from './ItemPicker.svelte'
import RadioButton from './RadioButton.svelte'
import Modal from './Modal.svelte'
import { Highlight } from 'svelte-highlight'
import { python, typescript } from 'svelte-highlight/languages'
import github from 'svelte-highlight/styles/github'
import { getScriptByPath } from '$lib/utils'
import RadioButtonV3 from './RadioButtonV3.svelte'
export let scriptPath: string | undefined = undefined
export let allowFlow = false
@@ -67,9 +67,11 @@
/>
<div class="flex flex-row items-center space-x-5">
{#if options.length > 1}
<RadioButton bind:value={itemKind} {options} />
{/if}
<div class="w-80">
{#if options.length > 1}
<RadioButtonV3 bind:value={itemKind} {options} />
{/if}
</div>
<input type="text" value={scriptPath ?? 'No path chosen yet'} disabled />
<button class="default-button text-gray-100" on:click={() => itemPicker.openModal()}
@@ -1,5 +1,5 @@
<script lang="ts">
import RadioButton from './RadioButton.svelte'
import RadioButtonV3 from './RadioButtonV3.svelte'
import ResourceTypePicker from './ResourceTypePicker.svelte'
export let pattern: string | undefined
@@ -31,9 +31,8 @@
$: contentEncoding = kind == 'base64' ? 'base64' : undefined
</script>
<RadioButton
<RadioButtonV3
label="Kind"
small={true}
options={[
['None', 'none'],
['File (base64)', 'base64'],
@@ -44,6 +43,7 @@
]}
bind:value={kind}
/>
<div class="my-2" />
{#if kind == 'pattern'}
<label for="input" class="mb-2 text-gray-700 text-xs"
@@ -9,6 +9,16 @@ import type { FlowMode } from './flowStore'
export function flowToMode(flow: Flow | any, mode: FlowMode): Flow {
if (mode == 'pull') {
const newFlow: Flow = JSON.parse(JSON.stringify(flow))
newFlow.value.modules.forEach((mod) => {
Object.values(mod.input_transform).forEach((inp) => {
if (inp.type == InputTransform.type.JAVASCRIPT) {
inp.value = undefined
} else {
inp.expr = undefined
}
})
})
const triggerModule = newFlow.value.modules[0]
const oldModules = newFlow.value.modules.slice(1)
+2 -2
View File
@@ -8,7 +8,7 @@
<script lang="ts">
import { page } from '$app/stores'
import { sendUserToast, displayDate, formatCron } from '$lib/utils'
import { sendUserToast, formatCron } from '$lib/utils'
import { ScriptService, type Script, ScheduleService, type Flow, FlowService } from '$lib/gen'
import PageHeader from '$lib/components/PageHeader.svelte'
@@ -135,7 +135,7 @@
{/if}
<h2 class="my-2 md:mt-6">Script</h2>
<p class="text-xs text-gray-600">
<p class="text-xs mb-1 text-gray-600">
Pick a script or flow to be triggered by the schedule<Required required={true} />
</p>
<ScriptPicker isTrigger={false} allowFlow={true} bind:itemKind bind:scriptPath={script_path} />