feature(frontend): Prop picker panel (#605)

* feature(frontend): WIP Prop picker v2

* feature(frontend): add PropPicker context

* feature(frontend): prop picker working

* feature(frontend): restore connect button

* feature(frontend): Fix wording + fix height to avoid content shift

* feature(frontend): Fix upTo preview + adapt style

* feature(frontend): Preview results cleanup

* feature(frontend): Remove test logs

* feature(frontend): Revert wrong changes

* feature(frontend): Restore shared preview args

* feature(frontend): Reduce ObjectViewer font size + remove useless space

* feature(frontend): Wording

* feature(frontend): Fix Split panel scrolling issues
This commit is contained in:
Faton Ramadani
2022-09-20 23:59:48 +02:00
committed by GitHub
parent 17e480f3be
commit 9ef6663dc5
21 changed files with 330 additions and 255 deletions
@@ -140,6 +140,7 @@
const selectedIdStore = writable<string>('settings')
const scheduleStore = writable<Schedule>(undefined)
const previewArgsStore = writable<Record<string, any>>(undefined)
function select(selectedId: string) {
selectedIdStore.set(selectedId)
@@ -149,7 +150,8 @@
selectedId: selectedIdStore,
schedule: scheduleStore,
select,
path: initialPath
path: initialPath,
previewArgs: previewArgsStore
})
$: {
@@ -13,7 +13,7 @@
export let flow: Flow
export let schema: Schema
const [i, j] = indexes.split('-').map(Number)
const [i, j] = indexes.split('-').filter(Boolean).map(Number)
let stepArgs: Record<string, any> = {}
let jobId: string
@@ -11,18 +11,16 @@
import { flowStore } from './flows/flowStore'
import type { FlowEditorContext } from './flows/types'
import { runFlowPreview } from './flows/utils'
import FlowStatusViewer from './FlowStatusViewer.svelte'
import SchemaForm from './SchemaForm.svelte'
import FlowStatusViewer from '../components/FlowStatusViewer.svelte'
export let previewMode: 'upTo' | 'whole'
let args: Record<string, any> = {}
let jobId: string | undefined = undefined
let isValid: boolean = false
let intervalState: 'idle' | 'canceled' | 'done' | 'running' = 'idle'
const { selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
const { selectedId, previewArgs } = getContext<FlowEditorContext>('FlowEditorContext')
function extractFlow(previewMode: 'upTo' | 'whole') {
if (previewMode === 'whole') {
@@ -104,7 +102,7 @@
</div>
<div class="pb-4 h-full max-h-1/2 overflow-auto">
<div class="mt-4">
<SchemaForm schema={$flowStore.schema} bind:isValid bind:args />
<SchemaForm schema={$flowStore.schema} bind:isValid bind:args={$previewArgs} />
</div>
</div>
{#if intervalState === 'running'}
@@ -128,7 +126,12 @@
Cancel
</Button>
{:else}
<Button disabled={!isValid} class="blue-button" on:click={() => runPreview(args)} size="md">
<Button
disabled={!isValid}
class="blue-button"
on:click={() => runPreview($previewArgs)}
size="md"
>
{`Run${intervalState === 'done' ? ' again' : ''}`}
</Button>
{/if}
@@ -140,9 +143,11 @@
on:jobsLoaded={(e) => {
intervalState = 'done'
const [parentIndex] = $selectedId.split('-')
const configIndex =
previewMode === 'upTo' ? Number(parentIndex) : $flowStateStore.modules.length
mapJobResultsToFlowState(e.detail, 'upto', configIndex, undefined)
const upToIndex =
previewMode === 'upTo' ? Number(parentIndex) + 1 : $flowStateStore.modules.length
mapJobResultsToFlowState(e.detail, 'upto', upToIndex, undefined)
}}
root={true}
/>
@@ -127,5 +127,5 @@
{/if}
</div>
{:else}
No script selected
Job starting...
{/if}
@@ -2,16 +2,18 @@
import type { Schema } from '$lib/common'
import type { InputTransform } from '$lib/gen'
import type { InputCat } from '$lib/utils'
import { faChain } from '@fortawesome/free-solid-svg-icons'
import { Button, Tooltip } from 'flowbite-svelte'
import Icon from 'svelte-awesome'
import { getContext } from 'svelte'
import ArgInput from './ArgInput.svelte'
import FieldHeader from './FieldHeader.svelte'
import DynamicInputHelpBox from './flows/content/DynamicInputHelpBox.svelte'
import type { PropPickerWrapperContext } from './flows/propPicker/PropPickerWrapper.svelte'
import { codeToStaticTemplate, getDefaultExpr, isCodeInjection } from './flows/utils'
import OverlayPropertyPicker from './propertyPicker/OverlayPropertyPicker.svelte'
import SimpleEditor from './SimpleEditor.svelte'
import Toggle from './Toggle.svelte'
import { Button, Tooltip } from 'flowbite-svelte'
import Icon from 'svelte-awesome'
import { faChain } from '@fortawesome/free-solid-svg-icons'
export let schema: Schema
export let arg: InputTransform | any
@@ -19,9 +21,7 @@
export let extraLib: string = 'missing extraLib'
export let inputCheck: { [id: string]: boolean }
export let importPath: string | undefined = undefined
export let pickableProperties: Object | undefined = undefined
let overlays: { [id: string]: OverlayPropertyPicker } = {}
let monacos: { [id: string]: SimpleEditor } = {}
let inputCats: { [id: string]: InputCat } = {}
@@ -60,16 +60,6 @@
return inputCat === 'string' || inputCat === 'sql'
}
function focusProp(argName: string, monacoEditor: boolean = false) {
Object.keys(overlays).forEach((k) => {
if (k == argName && (isStaticTemplate(inputCats[argName]) || monacoEditor)) {
overlays[k].focus()
} else {
overlays[k].unfocus()
}
})
}
function connectProperty(argName: string, rawValue: string) {
if (isStaticTemplate(inputCats[argName])) {
arg.value = `\$\{${rawValue}}`
@@ -86,6 +76,8 @@
}
$: checked = propertyType == 'javascript'
const { focusProp } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
</script>
{#if arg != undefined}
@@ -137,14 +129,10 @@
<div class="max-w-xs" />
{#if propertyType === undefined || !checked}
<OverlayPropertyPicker
{pickableProperties}
bind:this={overlays[argName]}
on:select={({ detail }) => {
if (detail.pickerVariation === 'connect') {
connectProperty(argName, detail.propPath)
} else {
const toAppend = `\$\{${detail.propPath}}`
<ArgInput
on:focus={() => {
focusProp(argName, 'append', (path) => {
const toAppend = `\$\{${path}}`
arg.value = `${arg.value ?? ''}${toAppend}`
if (monacos[argName]) {
monacos[argName].setCode(arg.value)
@@ -152,66 +140,64 @@
if (isStaticTemplate(inputCats[argName])) {
setPropertyType(arg.value)
}
})
}}
label={argName}
bind:editor={monacos[argName]}
bind:description={schema.properties[argName].description}
bind:value={arg.value}
type={schema.properties[argName].type}
required={schema.required.includes(argName)}
bind:pattern={schema.properties[argName].pattern}
bind:valid={inputCheck[argName]}
defaultValue={schema.properties[argName].default}
bind:enum_={schema.properties[argName].enum}
bind:format={schema.properties[argName].format}
contentEncoding={schema.properties[argName].contentEncoding}
bind:itemsType={schema.properties[argName].items}
properties={schema.properties[argName].properties}
displayHeader={false}
bind:inputCat={inputCats[argName]}
on:input={(e) => {
if (isStaticTemplate(inputCats[argName])) {
setPropertyType(e.detail.rawValue)
}
}}
>
<ArgInput
on:focus={() => focusProp(argName)}
label={argName}
bind:editor={monacos[argName]}
bind:description={schema.properties[argName].description}
bind:value={arg.value}
type={schema.properties[argName].type}
required={schema.required.includes(argName)}
bind:pattern={schema.properties[argName].pattern}
bind:valid={inputCheck[argName]}
defaultValue={schema.properties[argName].default}
bind:enum_={schema.properties[argName].enum}
bind:format={schema.properties[argName].format}
contentEncoding={schema.properties[argName].contentEncoding}
bind:itemsType={schema.properties[argName].items}
properties={schema.properties[argName].properties}
displayHeader={false}
bind:inputCat={inputCats[argName]}
on:input={(e) => {
if (isStaticTemplate(inputCats[argName])) {
setPropertyType(e.detail.rawValue)
}
}}
>
<div slot="actions">
<div on:click={() => overlays[argName]?.focus('connect')}>
<Tooltip placement="bottom" content="Input connect">
<Button size="sm" class="blue-button h-8">
<Icon data={faChain} />
</Button>
</Tooltip>
</div>
<div slot="actions">
<div
on:click={() => {
focusProp(argName, 'connect', (path) => {
connectProperty(argName, path)
})
}}
>
<Tooltip placement="bottom" content="Input connect">
<Button size="sm" class="blue-button h-8">
<Icon data={faChain} />
</Button>
</Tooltip>
</div>
</ArgInput>
</OverlayPropertyPicker>
</div>
</ArgInput>
{:else if checked}
{#if arg.expr != undefined}
<OverlayPropertyPicker
bind:this={overlays[argName]}
{pickableProperties}
on:select={(event) => {
monacos[argName].insertAtCursor(event.detail.propPath)
}}
>
<div class="border rounded p-2 mt-2 border-gray-300">
<SimpleEditor
bind:this={monacos[argName]}
on:focus={() => focusProp(argName, true)}
bind:code={arg.expr}
lang="javascript"
class="few-lines-editor"
{extraLib}
extraLibPath="file:///node_modules/@types/windmill@{importPath}/index.d.ts"
shouldBindKey={false}
/>
</div>
</OverlayPropertyPicker>
<div class="border rounded p-2 mt-2 border-gray-300">
<SimpleEditor
bind:this={monacos[argName]}
on:focus={() => {
focusProp(argName, 'insert', (path) => {
monacos[argName].insertAtCursor(path)
})
}}
bind:code={arg.expr}
lang="javascript"
class="few-lines-editor"
{extraLib}
extraLibPath="file:///node_modules/@types/windmill@{importPath}/index.d.ts"
shouldBindKey={false}
/>
</div>
<DynamicInputHelpBox {importPath} />
{/if}
{:else}
+15 -2
View File
@@ -2,7 +2,9 @@
import type { Schema } from '$lib/common'
import type { InputTransform } from '$lib/gen'
import { allTrue } from '$lib/utils'
import { getContext, hasContext } from 'svelte'
import ArgInput from './ArgInput.svelte'
import type { PropPickerWrapperContext } from './flows/propPicker/PropPickerWrapper.svelte'
import InputTransformForm from './InputTransformForm.svelte'
export let inputTransform = false
@@ -10,7 +12,6 @@
export let args: Record<string, InputTransform | any> = {}
export let editableSchema = false
export let isValid: boolean = true
export let pickableProperties: Object | undefined = undefined
export let extraLib: string = 'missing extraLib'
export let importPath: string | undefined = undefined
@@ -31,6 +32,16 @@
}
$: schema?.properties && removeExtraKey()
function getFocusFunction() {
if (hasContext('PropPickerWrapper')) {
const { focus } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
return focus
}
return (value: string | undefined) => {}
}
const focus = getFocusFunction()
</script>
<div class="w-full">
@@ -42,7 +53,6 @@
bind:schema
bind:argName
bind:inputCheck
bind:pickableProperties
bind:extraLib
bind:importPath
/>
@@ -61,6 +71,9 @@
contentEncoding={schema.properties[argName].contentEncoding}
properties={schema.properties[argName].properties}
bind:itemsType={schema.properties[argName].items}
on:focus={() => {
focus && focus(argName)
}}
{editableSchema}
/>
{/if}
@@ -22,7 +22,7 @@
})
</script>
<div class="border-b border-gray-200 flex flex-row mb-4">
<div class="border-b border-gray-200 flex flex-row">
<slot />
</div>
<slot name="content" />
@@ -0,0 +1,24 @@
<script lang="ts">
import { classNames } from '$lib/utils'
import { getContext } from 'svelte'
import type { ToggleButtonContext } from './ToggleButtonGroup.svelte'
export let value: string
export let position: 'left' | 'center' | 'right'
const { select, selected } = getContext<ToggleButtonContext>('ToggleButtonGroup')
</script>
<button
type="button"
on:click={() => select(value)}
class={classNames(
'py-1 px-2 text-sm font-medium border-gray-200 ',
position === 'left' ? 'rounded-l-lg border' : '',
position === 'center' ? 'border-t border-b' : '',
position === 'right' ? 'rounded-r-md border border-l-0' : '',
$selected.includes(value) ? 'text-white bg-gray-900 ' : 'text-gray-900 bg-white '
)}
>
<slot />
</button>
@@ -0,0 +1,39 @@
<script context="module" lang="ts">
export type ToggleButtonContext = {
selected: Writable<string[]>
select: (value: string) => void
}
</script>
<script lang="ts">
import { setContext } from 'svelte'
import { writable, type Writable } from 'svelte/store'
export let exclusive: boolean = true
export let selected: string[] = []
const selectedContent = writable(selected)
setContext<ToggleButtonContext>('ToggleButtonGroup', {
selected: selectedContent,
select: (value: string) => {
if (exclusive) {
selectedContent.set([value])
} else {
selectedContent.update((selected) => {
const index = selected.findIndex((val: string) => val === value)
if (index !== -1) {
selected.splice(index, 1)
return selected
} else {
return [value, ...selected]
}
})
}
}
})
</script>
<div class="inline-flex rounded-md shadow-sm" role="group">
<slot />
</div>
@@ -3,7 +3,6 @@
import type { FlowEditorContext } from '../types'
import FlowModuleWrapper from './FlowModuleWrapper.svelte'
import FlowSchedules from './FlowSchedules.svelte'
import FlowSettings from './FlowSettings.svelte'
import FlowInput from './FlowInput.svelte'
import FlowLoop from './FlowLoop.svelte'
@@ -27,9 +27,9 @@
import FlowModuleHeader from './FlowModuleHeader.svelte'
import { flowStateStore, type FlowModuleSchema } from '../flowState'
import { scriptLangToEditorLang } from '$lib/utils'
import PropPickerWrapper from '../propPicker/PropPickerWrapper.svelte'
import { getContext } from 'svelte'
import type { FlowEditorContext } from '../types'
import Toggle from '$lib/components/Toggle.svelte'
import FlowModuleAdvancedSettings from './FlowModuleAdvancedSettings.svelte'
export let indexes: string
@@ -97,7 +97,6 @@
shouldDisableLoopCreation={indexes.length > 1 || parentIndex == 0}
on:loop={() => {
applyCreateLoop()
select(['loop', $selectedId].join('-'))
}}
on:pick={(e) => apply(pickScript, e.detail.path)}
@@ -119,6 +118,8 @@
<VSplitPane
topPanelSize={flowModule.value.type === 'rawscript' ? '50%' : '0%'}
downPanelSize={flowModule.value.type === 'rawscript' ? '50%' : '100%'}
minTopPaneSize="20%"
minDownPaneSize="20%"
>
<top slot="top">
{#if flowModule.value.type === 'rawscript'}
@@ -137,46 +138,36 @@
{/if}
</top>
<down slot="down">
<down slot="down" class="flex flex-col flex-1 h-full">
<Tabs selected="inputs">
<Tab value="inputs">Inputs</Tab>
<Tab value="preview">Test</Tab>
<Tab value="test">Test</Tab>
<Tab value="advanced">Advanced</Tab>
<svelte:fragment slot="content">
<div class="h-full pb-16 overflow-y-scroll bg-white">
<div class="p-4 overflow-hidden">
<TabContent value="inputs">
<div class="w-11/12">
<SchemaForm
{schema}
inputTransform={true}
importPath={indexes}
bind:pickableProperties={stepPropPicker.pickableProperties}
bind:args={flowModule.input_transforms}
bind:extraLib={stepPropPicker.extraLib}
/>
</div>
</TabContent>
<TabContent value="preview">
<div class="overflow-hidden bg-white" style="height:calc(100% - 32px);">
<TabContent value="inputs" class="flex flex-col flex-1 h-full">
<PropPickerWrapper bind:pickableProperties={stepPropPicker.pickableProperties}>
<SchemaForm
{schema}
inputTransform={true}
importPath={indexes}
bind:args={flowModule.input_transforms}
bind:extraLib={stepPropPicker.extraLib}
/>
</PropPickerWrapper>
</TabContent>
<TabContent value="test" class="flex flex-col flex-1 h-full">
<div class="p-4 overflow-y-auto">
<FlowPreview flow={$flowStore} {indexes} {schema} />
</TabContent>
<TabContent value="settings">
{#if ('path' in flowModule.value && flowModule.value.path) || ('language' in flowModule.value && flowModule.value.language)}
<input
on:click|stopPropagation={() => undefined}
class="overflow-x-auto"
type="text"
bind:value={flowModule.summary}
placeholder="Summary"
/>
{/if}
</TabContent>
</div>
</TabContent>
<TabContent value="advanced">
<TabContent value="advanced" class="flex flex-col flex-1 h-full">
<div class="p-4 overflow-y-auto">
<FlowModuleAdvancedSettings bind:flowModule />
</TabContent>
</div>
</div>
</TabContent>
</div>
</svelte:fragment>
</Tabs>
@@ -4,7 +4,7 @@
import type { FlowEditorContext } from '../types'
import FlowModule from './FlowModule.svelte'
const { selectedId } = getContext<FlowEditorContext>('FlowEditorContext')
const { selectedId, previewArgs } = getContext<FlowEditorContext>('FlowEditorContext')
function selectedIdToIndexes(selectedId: string): number[] {
return selectedId.split('-').map(Number)
@@ -17,8 +17,8 @@
{#each $flowStateStore.modules[parentIndex].childFlowModules ?? [] as fa, index}
{#if index === childIndex}
<FlowModule
args={previewArgs}
indexes={$selectedId}
args={{}}
bind:flowModule={fa.flowModule}
bind:schema={fa.schema}
bind:childFlowModules={fa.childFlowModules}
@@ -33,8 +33,8 @@
{#each $flowStateStore.modules ?? [] as fa, index}
{#if index === parentIndex}
<FlowModule
args={$previewArgs}
indexes={$selectedId}
args={{}}
bind:flowModule={fa.flowModule}
bind:schema={fa.schema}
bind:childFlowModules={fa.childFlowModules}
@@ -21,7 +21,7 @@
<Tab value="configuration">Configuration</Tab>
<Tab value="schedule">Schedule</Tab>
<svelte:fragment slot="content">
<TabContent value="configuration" class="px-4">
<TabContent value="configuration" class="p-4">
<Path bind:path={$flowStore.path} initialPath={path} namePlaceholder="my_flow" kind="flow">
<div slot="ownerToolkit">
Flow permissions depend on their path. Select the group <span class="font-mono"
@@ -42,7 +42,7 @@
/>
</label>
</TabContent>
<TabContent value="schedule" class="px-4">
<TabContent value="schedule" class="p-4">
<FlowSchedules />
</TabContent>
</svelte:fragment>
@@ -1,13 +1,5 @@
import type { Schema } from '$lib/common'
import {
CompletedJob,
Job,
Script,
ScriptService,
type Flow,
type FlowModule,
type RawScript
} from '$lib/gen'
import { CompletedJob, Job, Script, ScriptService, type FlowModule, type RawScript } from '$lib/gen'
import { initialCode } from '$lib/script_helpers'
import { userStore, workspaceStore } from '$lib/stores'
import {
@@ -208,10 +200,11 @@ export function getStepPropPicker(
args: Record<string, any>
): StepPropPicker {
const isInsideLoop: boolean = indexes.length > 1
const [parentIndex] = indexes
const [parentIndex, childIndex] = indexes
const flowInput = schemaToObject(flowInputSchema, args)
const results = getPreviousResults(flowState.modules, parentIndex)
const lastResult = results.length > 0 ? results[results.length - 1] : undefined
if (isInsideLoop) {
@@ -281,25 +274,30 @@ export type JobResult = {
export function mapJobResultsToFlowState(
jobs: JobResult,
config: 'upto' | 'justthis',
i: number,
parentIndex: number,
j: number | undefined
): void {
if (!Array.isArray(jobs.innerJobs) || jobs.innerJobs.length === 0) {
return
}
if (config === 'justthis') {
const job = jobs.job as CompletedJob
flowStateStore.update((flowState: FlowState) => {
if (j) {
flowState[i].childFlowModules[j].previewResult = job.result
} else {
flowState[i].previewResult = job.result
if (flowState.modules) {
const childFlowModules = flowState.modules[parentIndex].childFlowModules
if (j && childFlowModules) {
childFlowModules[j].previewResult = job.result
flowState.modules[parentIndex].childFlowModules = childFlowModules
} else {
flowState.modules[parentIndex].previewResult = job.result
}
}
return flowState
})
} else {
if (!Array.isArray(jobs.innerJobs) || jobs.innerJobs.length === 0) {
return
}
const results = jobs.innerJobs.map(({ job, loopJobs }) => {
if (Array.isArray(loopJobs) && loopJobs.length > 0) {
return loopJobs.map(({ job }) => {
@@ -315,12 +313,12 @@ export function mapJobResultsToFlowState(
})
flowStateStore.update((flowState: FlowState) => {
if (!Array.isArray(flowState)) {
if (!Array.isArray(flowState.modules)) {
return flowState
}
const modules = flowState.modules.map((flowModuleSchema: FlowModuleSchema, index) => {
if (index <= i) {
const modules = flowState.modules.map((flowModuleSchema: FlowModuleSchema, index: number) => {
if (index <= parentIndex) {
flowModuleSchema.previewResult = results[index]
}
@@ -0,0 +1,61 @@
<script context="module" lang="ts">
type InsertionMode = 'append' | 'connect' | 'insert'
type SelectCallback = (path: string) => void
type PropPickerConfig = {
insertionMode: InsertionMode
propName: string
onSelect: SelectCallback
}
export type PropPickerWrapperContext = {
propPickerConfig: Writable<PropPickerConfig | undefined>
focusProp: (propName: string, insertionMode: InsertionMode, onSelect: SelectCallback) => void
clearFocus: () => void
}
</script>
<script lang="ts">
import PropPicker from '$lib/components/propertyPicker/PropPicker.svelte'
import { setContext } from 'svelte'
import { HSplitPane } from 'svelte-split-pane'
import { writable, type Writable } from 'svelte/store'
export let pickableProperties: Object = {}
const propPickerConfig = writable<PropPickerConfig | undefined>(undefined)
setContext<PropPickerWrapperContext>('PropPickerWrapper', {
propPickerConfig,
focusProp: (propName, insertionMode, onSelect) => {
propPickerConfig.set({
propName,
insertionMode,
onSelect
})
},
clearFocus: () => {
propPickerConfig.set(undefined)
}
})
</script>
<HSplitPane leftPaneSize="50%" rightPaneSize="50%" minLeftPaneSize="20%" minRightPaneSize="20%">
<left slot="left" class="relative">
<div class=" overflow-auto h-full p-4">
<slot />
</div>
</left>
<right slot="right">
<div class="overflow-auto h-full">
<PropPicker
{pickableProperties}
on:select={({ detail }) => {
$propPickerConfig?.onSelect(detail)
propPickerConfig.set(undefined)
}}
/>
</div>
</right>
</HSplitPane>
@@ -6,4 +6,5 @@ export type FlowEditorContext = {
select: (id: string) => void
schedule: Writable<Schedule>
path: string
previewArgs: Writable<Record<string, any>>
}
@@ -39,15 +39,19 @@
{#if keys.length > 0}
<span class:hidden={collapsed}>
{#if level != 0}<span class="cursor-pointer hover:bg-slate-200 px-1 rounded" on:click={collapse}
>(-)</span
>{/if}
{#if level != 0}<span
class="cursor-pointer hover:bg-slate-200 px-1 rounded"
on:click={collapse}
>
(-)
</span>
{/if}
<ul class="w-full">
{#each keys as key, index}
<li class="pt-1">
<button on:click={() => selectProp(key)} class="key rounded px-1 hover:bg-sky-100"
>{!isArray ? key : index}:</button
>
<button on:click={() => selectProp(key)} class="key rounded px-1 hover:bg-sky-100">
{!isArray ? key : index}:
</button>
{#if getTypeAsString(json[key]) === 'object'}
<svelte:self
@@ -90,6 +94,7 @@
padding-left: 1rem;
border-left: 1px dotted lightgray;
@apply text-black;
@apply text-sm;
}
.val {
@@ -1,78 +0,0 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { createPopperActions } from 'svelte-popperjs'
import PropPicker from './PropPicker.svelte'
const [popperRef, popperContent] = createPopperActions({
placement: 'bottom',
strategy: 'fixed'
})
export let pickableProperties: Object | undefined
let isOpen = false
let isFocused = false
let timeout: NodeJS.Timeout
type PickerVariation = 'append' | 'connect'
let pickerVariation: PickerVariation = 'append'
export function unfocus() {
isFocused = false
close()
}
export function focus(newPickerVariation?: PickerVariation) {
if (newPickerVariation) {
pickerVariation = newPickerVariation
}
isFocused = true
open()
}
function open() {
if (isFocused) {
clearTimeout(timeout)
!isOpen && (isOpen = true)
}
}
function close() {
if (pickerVariation === 'append') {
timeout = setTimeout(() => (isOpen = false), 50)
}
}
function closePropertyPicker() {
timeout = setTimeout(() => (isOpen = false), 50)
}
const dispatch = createEventDispatcher()
</script>
<div class="w-full">
<div use:popperRef on:click={open} on:input={open} on:mouseleave={close}>
<slot />
</div>
{#if isOpen}
<div class="content" use:popperContent on:mouseenter={open} on:mouseleave={closePropertyPicker}>
<PropPicker
bind:pickableProperties
on:select={(event) => {
dispatch('select', { propPath: event.detail, pickerVariation })
isOpen = false
pickerVariation = 'append'
}}
/>
</div>
{/if}
</div>
<style>
.content {
@apply drop-shadow-xl;
@apply w-full;
@apply max-w-4xl;
@apply px-6;
@apply z-50;
}
</style>
@@ -1,4 +1,9 @@
<script lang="ts">
import { faClose } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import Icon from 'svelte-awesome'
import type { PropPickerWrapperContext } from '../flows/propPicker/PropPickerWrapper.svelte'
import ObjectViewer from './ObjectViewer.svelte'
import { keepByKey } from './utils'
@@ -7,20 +12,44 @@
const EMPTY_STRING = ''
let search = ''
const { propPickerConfig, clearFocus } = getContext<PropPickerWrapperContext>('PropPickerWrapper')
$: propsFiltered =
search === EMPTY_STRING ? pickableProperties : keepByKey(pickableProperties, search)
</script>
<div
class="relative p-3 bg-white rounded-lg border border-gray-200 shadow-md max-h-max overflow-y-scroll"
>
<div class="overflow-y-auto max-h-96">
<input
type="text"
bind:value={search}
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg block p-2 mb-2"
placeholder="Search prop..."
/>
<ObjectViewer json={propsFiltered} on:select />
<div class="px-4 pt-4">
<div class="flex justify-between items-center h-8">
<span class="font-bold text-sm">Context</span>
<div class="flex space-x-2 items-center">
{#if $propPickerConfig}
<span
class="flex items-center bg-blue-100 text-blue-800 text-xs font-semibold px-2 py-1 rounded dark:bg-green-200 dark:text-green-900"
>
{`Selected input: ${$propPickerConfig?.propName}`}
</span>
<span
class="flex items-center bg-blue-100 text-blue-800 text-xs font-semibold px-2 py-1 rounded dark:bg-green-200 dark:text-green-900"
>
{`Mode: ${$propPickerConfig?.insertionMode}`}
</span>
<button
class="border px-2 py-1 text-xs rounded-md flex items-center hover:bg-gray-50 hover:text-gray-900"
on:click={() => clearFocus()}
>
<Icon data={faClose} class="mr-2" scale={0.8} />
Deselect
</button>
{/if}
</div>
</div>
<input
type="text"
bind:value={search}
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg block p-2 mb-2"
placeholder="Search prop..."
/>
</div>
<div class="overflow-y-auto mb-8">
<ObjectViewer json={propsFiltered} on:select />
</div>
@@ -65,7 +65,7 @@
<Tab value="last_save"><span class="text-xs">Last save</span></Tab>
<svelte:fragment slot="content">
<div class="px-4">
<div class="p-4">
<TabContent value="logs">
<pre
class="whitespace-pre-wrap break-all relative h-full bg-gray-50 text-xs p-2">{#if previewJob && previewJob.logs}{previewJob.logs}
+4 -4
View File
@@ -54,8 +54,9 @@ export function displayDate(dateString: string | undefined): string {
if (date.toString() === 'Invalid Date') {
return ''
} else {
return `${date.getFullYear()}/${date.getMonth() + 1
}/${date.getDate()} at ${date.toLocaleTimeString()}`
return `${date.getFullYear()}/${
date.getMonth() + 1
}/${date.getDate()} at ${date.toLocaleTimeString()}`
}
}
@@ -561,7 +562,6 @@ export function classNames(...classes: string[]): string {
return classes.filter(Boolean).join(' ')
}
export function scriptLangToEditorLang(lang: Script.language): 'typescript' | 'python' | 'go' {
if (lang == 'deno') {
return 'typescript'
@@ -570,4 +570,4 @@ export function scriptLangToEditorLang(lang: Script.language): 'typescript' | 'p
} else {
return lang
}
}
}