apps UX nits

This commit is contained in:
Ruben Fiszel
2023-01-04 10:11:22 +01:00
parent 03da654714
commit aaaece99e7
19 changed files with 157 additions and 120 deletions
@@ -28,6 +28,7 @@
export let noVariablePicker = false
export let filter: string[] | undefined = undefined
export let noDynamicToggle = false
export let flexWrap = false
let clazz: string = ''
export { clazz as class }
@@ -55,7 +56,7 @@
let variableEditor: VariableEditor | undefined = undefined
</script>
<div class="w-full {clazz}">
<div class="w-full {clazz} {flexWrap ? 'flex flex-row flex-wrap gap-x-4' : ''}">
{#if Object.keys(schema?.properties ?? {}).length > 0}
{#each Object.keys(schema?.properties ?? {}) as argName, i (argName)}
{#if !filter || filter.includes(argName)}
@@ -13,6 +13,7 @@
} from '$lib/editorUtils'
import { languages, editor as meditor, Uri as mUri, Range } from 'monaco-editor'
import libStdContent from '$lib/es5.d.ts.txt?raw'
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
languages.typescript.javascriptDefaults.setCompilerOptions({
target: languages.typescript.ScriptTarget.Latest,
@@ -386,7 +387,11 @@
// @ts-ignore
self.MonacoEnvironment = {
getWorker: function (_moduleId: any, label: string) {
return new tsWorker()
if (label == 'typescript') {
return new tsWorker()
} else {
return new editorWorker()
}
}
}
}
@@ -6,7 +6,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = 'left'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let configuration: Record<string, AppInput>
@@ -63,28 +63,38 @@
forceSchemaDisplay={true}
>
<AlignWrapper {horizontalAlignment}>
<Button
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
on:click={() => {
runnableComponent?.runComponent()
<div class="flex flex-col gap-2 px-4 w-full">
<div>
{#if componentInput?.type != 'runnable' || Object.values(componentInput?.fields ?? {}).filter((x) => x.type == 'user').length == 0}
<span class="text-gray-600 italic text-sm py-2"
>Run forms are meant to be associated with a runnable with some user inputs. Pick a
runnable and set some 'Runnable Inputs' to 'User Input'</span
>
{/if}
</div>
<div class="flex justify-end">
<Button
{loading}
btnClasses="mt-1"
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
on:click={() => {
runnableComponent?.runComponent()
if (recomputeIds) {
recomputeIds.forEach((id) => {
$runnableComponents[id]?.()
})
}
}}
{size}
{color}
endIcon={{
icon: loading ? faRefresh : faArrowRight,
classes: loading ? 'animate-spin w-4' : 'w-4'
}}
>
{labelValue}
</Button>
if (recomputeIds) {
recomputeIds.forEach((id) => {
$runnableComponents[id]?.()
})
}
}}
{size}
{color}
>
{labelValue}
</Button>
</div>
</div>
</AlignWrapper>
</RunnableWrapper>
@@ -253,10 +253,17 @@
bind:this={testJobLoader}
/>
<div class="h-full flex flex-col">
{#if schemaStripped !== undefined && (autoRefresh || forceSchemaDisplay)}
<div class="px-2">
<div class="h-full flex relative flex-row flex-wrap">
{#if autoRefresh === true}
<div class="flex absolute top-1 right-1">
<RefreshButton componentId={id} />
</div>
{/if}
{#if schemaStripped && Object.keys(schemaStripped?.properties ?? {}).length > 0 && (autoRefresh || forceSchemaDisplay)}
<div class="px-2 h-fit min-h-0">
<SchemaForm
compact
flexWrap
schema={schemaStripped}
bind:args
{disabledArgs}
@@ -276,13 +283,9 @@
<pre title={result.error} class="text-2xs whitespace-pre-wrap">{result.error}</pre>
</Alert>
</div>
{:else if autoRefresh === true}
<div class="flex absolute top-1 right-1">
<RefreshButton componentId={id} />
</div>
<slot />
{:else}
<slot />
<div class="grow min-w-1/2 min-h-[66%]">
<slot />
</div>
{/if}
</div>
@@ -37,9 +37,9 @@
}
const options = writable<TableOptions<T>>({
...tableOptions,
data: [],
columns: [],
...tableOptions
columns: []
})
let table = createSvelteTable(options)
@@ -67,14 +67,14 @@
const headers = Array.from(new Set(result.flatMap((row) => Object.keys(row))))
$options = {
...tableOptions,
data: filteredResult,
columns: headers.map((header) => {
return {
accessorKey: header,
cell: (info) => info.getValue()
}
}),
...tableOptions
})
}
}
@@ -177,7 +177,7 @@
{@const component = renderCell(cell.column.columnDef.cell, context)}
<td
on:click={() => toggleRow(row, rowIndex)}
class="p-4 whitespace-nowrap text-xs text-gray-900"
class="p-4 whitespace-pre-wrap truncate text-xs text-gray-900"
>
{#if typeof cell.column.columnDef.cell != 'string' && cellIsObject(cell.column.columnDef.cell, context)}
{JSON.stringify(cell.column.columnDef.cell(context), null, 4)}
@@ -1,13 +1,16 @@
import { getCoreRowModel, getPaginationRowModel } from '@tanstack/svelte-table'
import { getCoreRowModel, getPaginationRowModel, type TableOptions } from '@tanstack/svelte-table'
const tableOptions = {
const tableOptions: TableOptions<Record<string, any>> = {
data: [],
columns: [],
enableColumnResizing: false,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
initialState: {
pagination: {
pageSize: 10
pageSize: 25
}
}
},
}
export { tableOptions }
@@ -14,9 +14,12 @@
</script>
<span
title={`Id: ${component.id}`}
class={classNames(
'px-2 text-2xs font-bold rounded-t-sm w-fit absolute z-50',
selected ? 'bg-indigo-500/90 text-white' : 'bg-gray-200/60 text-gray-500'
'px-2 text-2xs font-bold rounded-t-sm w-fit absolute shadow -top-1 -left-2 border z-50',
selected
? 'bg-indigo-500/90 border-blue-500 text-white'
: 'bg-gray-200/90 border-gray-300 text-gray-500'
)}
style="padding-top: 1px; padding-bottom: 1px;"
>
@@ -25,29 +28,31 @@
{#if pointerdown || selected || hover}
<button
title="Position locking"
class={classNames(
'text-gray-800 px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute right-10 z-50 cursor-pointer',
'text-gray-800 px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit shadow border border-gray-300 absolute -top-1 right-[2.5rem] z-50 cursor-pointer',
' hover:bg-gray-300',
selected ? 'bg-gray-200/80' : 'bg-gray-200/60'
selected ? 'bg-gray-200/80' : 'bg-gray-200/80'
)}
on:click={() => {
dispatch('lock')
}}
>
{#if locked}
<Anchor size={14} class="text-orange-500" />
<Anchor aria-label="Unlock position" size={14} class="text-orange-500" />
{:else}
<Anchor size={14} />
<Anchor aria-label="Lock position" size={14} />
{/if}
</button>
{/if}
{#if selected || hover}
<span
title="Move"
on:mousedown|stopPropagation|capture
class={classNames(
'text-gray-600 px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute right-16 z-50 cursor-move',
'bg-gray-200/60'
'text-gray-600 px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute border border-gray-300 -top-1 shadow right-[4.5rem] z-50 cursor-move',
'bg-gray-200/80'
)}><Move size={14} /></span
>
{/if}
@@ -116,7 +116,7 @@
>
</div>
<div
class="px-4 pt-4 {$connectingInput?.opened ? '' : 'bg-gray-50/70'}"
class="px-4 pt-4 {$connectingInput?.opened ? '' : ''}"
on:pointerdown={onpointerdown}
on:pointerleave={onpointerup}
on:pointerup={onpointerup}
@@ -124,7 +124,7 @@
<Grid
bind:items={$app.grid}
let:dataItem
rowHeight={23}
rowHeight={30}
cols={columnConfiguration}
fastStart={true}
on:pointerup={({ detail }) => selectComponent(detail.id)}
@@ -19,9 +19,9 @@
function getMinDimensionsByComponent(componentType: AppComponent['type'], column: number): Size {
// Dimensions key formula: <mobile width>:<mobile height>-<desktop width>:<desktop height>
const dimensions: Record<`${number}:${number}-${number}:${number}`, AppComponent['type'][]> = {
'4:1-4:1': ['textcomponent'],
'2:1-2:1': ['buttoncomponent'],
'4:2-4:2': [
'buttoncomponent',
'textcomponent',
'checkboxcomponent',
'textinputcomponent',
'numberinputcomponent',
@@ -29,7 +29,8 @@
'passwordinputcomponent',
'dateinputcomponent'
],
'4:12-4:12': ['barchartcomponent', 'piechartcomponent', 'formcomponent', 'displaycomponent'],
'3:5-6:5': ['formcomponent'],
'4:12-4:12': ['barchartcomponent', 'piechartcomponent', 'displaycomponent'],
'3:10-6:12': ['tablecomponent']
}
// Finds the key that is associated with the component type and extracts the dimensions from it
@@ -14,6 +14,7 @@ const inputs: ComponentSet = {
type: 'static',
value: 'Type...',
fieldType: 'text',
onlyStatic: true,
},
},
card: false
@@ -100,6 +101,7 @@ const buttons: ComponentSet = {
components: [
{
...defaultAlignement,
softWrap: true,
id: 'buttoncomponent',
type: 'buttoncomponent',
componentInput: {
@@ -120,14 +122,14 @@ const buttons: ComponentSet = {
color: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'buttonColorOptions',
value: 'blue'
},
size: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'buttonSizeOptions',
value: 'xs'
}
@@ -156,6 +158,7 @@ const buttons: ComponentSet = {
color: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
value: 'dark',
optionValuesKey: 'buttonColorOptions',
},
@@ -163,6 +166,7 @@ const buttons: ComponentSet = {
fieldType: 'select',
type: 'static',
value: 'xs',
onlyStatic: true,
optionValuesKey: 'buttonSizeOptions',
}
},
@@ -176,20 +180,21 @@ const display: ComponentSet = {
title: 'Display',
components: [
{
...defaultAlignement,
softWrap: true,
horizontalAlignment: 'left',
verticalAlignment: 'top',
id: 'textcomponent',
type: 'textcomponent',
componentInput: {
type: 'static',
fieldType: 'textarea',
value: 'Lorem ipsum',
value: 'Hello ${ctx.username}',
},
configuration: {
style: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'textStyleOptions',
value: 'Body'
},
@@ -205,21 +210,22 @@ const display: ComponentSet = {
card: false
},
{
id: 'tablecomponent',
type: 'tablecomponent',
configuration: {
search: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'tableSearchOptions',
value: 'Disabled'
},
pagination: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false
value: true
}
},
componentInput: {
@@ -249,7 +255,7 @@ const display: ComponentSet = {
configuration: {
theme: {
type: 'static',
onlyStatic: true,
fieldType: 'select',
optionValuesKey: 'chartThemeOptions',
value: 'theme1'
@@ -276,7 +282,7 @@ const display: ComponentSet = {
configuration: {
theme: {
type: 'static',
onlyStatic: true,
fieldType: 'select',
optionValuesKey: 'chartThemeOptions',
value: 'theme1'
@@ -30,13 +30,13 @@
if (component?.data.type) {
return displayData[component?.data.type].name
} else if (componentId == 'context') {
} else if (componentId == 'ctx') {
return 'Context'
} else {
return 'Table action'
}
}
$: panels = [['context', ['email', 'username']] as [string, string[]]].concat(
$: panels = [['ctx', ['email', 'username']] as [string, string[]]].concat(
Object.entries($staticOutputs)
)
</script>
@@ -58,7 +58,7 @@
? undefined
: () => ($selectedComponent = componentId)}
class={classNames(
'px-2 text-2xs py-0.5 font-bold rounded-t-sm w-fit',
'px-2 text-2xs py-0.5 border border-gray-300 font-bold rounded-t-sm w-fit',
$selectedComponent === componentId
? ' bg-indigo-500 text-white'
: 'bg-gray-200 text-gray-500'
@@ -80,7 +80,7 @@
<div
class={classNames(
$connectingInput?.opened ? 'bg-white z-50' : '',
`w-full py-2 grow border relative overflow-x-auto`,
`w-full py-2 grow border relative `,
$selectedComponent === componentId ? 'border border-blue-500 ' : '',
$connectingInput.hoveredComponent === componentId ? 'outline outline-blue-500' : ''
)}
@@ -57,8 +57,8 @@
<InlineScriptEditorDrawer {editor} bind:this={inlineScriptEditorDrawer} bind:inlineScript />
<div class="h-full p-2 flex flex-col gap-2" transition:fly={{ duration: 50 }}>
<div class="flex justify-between w-full gap-1 flex-row items-center">
<div class="h-full flex flex-col gap-1" transition:fly={{ duration: 50 }}>
<div class="flex justify-between w-full gap-1 px-2 pt-1 flex-row items-center">
{#if name !== undefined}
<input bind:value={name} placeholder="Inline script name" />
{/if}
@@ -13,6 +13,7 @@
$: if (componentInput.fieldType == 'textarea' && componentInput.type == 'static') {
//@ts-ignore
componentInput.type = 'template'
componentInput['eval'] = componentInput.value
}
const brackets = '${}'
@@ -37,7 +38,7 @@
disable={disableStatic}
>
{#if clientWidth > 250}
<span class="hidden lg:block"> Static </span>
<span class="hidden lg:block">Static</span>
{/if}
</ToggleButton>
{/if}
@@ -50,7 +51,7 @@
size="xs"
>
{#if clientWidth > 250}
<span class="hidden lg:block"> Connect </span>
<span class="hidden lg:block">Connect</span>
{/if}
</ToggleButton>
<ToggleButton
@@ -61,7 +62,7 @@
size="xs"
>
{#if clientWidth > 250}
<span class="hidden lg:block"> Compute </span>
<span class="hidden lg:block">Compute</span>
{/if}
</ToggleButton>
</ToggleButtonGroup>
@@ -13,7 +13,7 @@
export let inputSpecs: Record<
string,
StaticAppInput | ConnectedAppInput | UserAppInput | RowAppInput
(StaticAppInput | ConnectedAppInput | UserAppInput | RowAppInput) & { onlyStatic?: boolean }
>
export let userInputEnabled: boolean = true
export let staticOnly: boolean = false
@@ -31,54 +31,56 @@
{shouldCapitalize ? capitalize(inputSpecKey) : inputSpecKey}
</span>
<div class="flex gap-2 items-center">
<div class="flex gap-2 flex-wrap items-center">
<Badge color="blue">
{input.fieldType === 'array' && input.subFieldType
? `${capitalize(fieldTypeToTsType(input.subFieldType))}[]`
: capitalize(fieldTypeToTsType(input.fieldType))}
</Badge>
<ToggleButtonGroup bind:selected={inputSpecs[inputSpecKey].type}>
<ToggleButton
title="Static"
position="left"
value="static"
startIcon={{ icon: faPen }}
size="xs"
iconOnly
/>
{#if rowColumns}
{#if !inputSpecs[inputSpecKey].onlyStatic}
<ToggleButtonGroup bind:selected={inputSpecs[inputSpecKey].type}>
<ToggleButton
title="From Row"
position="center"
value="row"
startIcon={{ icon: faTableCells }}
title="Static"
position="left"
value="static"
startIcon={{ icon: faPen }}
size="xs"
iconOnly
/>
{#if rowColumns}
<ToggleButton
title="From Row"
position="center"
value="row"
startIcon={{ icon: faTableCells }}
size="xs"
iconOnly
disabled={staticOnly}
/>
{/if}
{#if userInputEnabled && (!input.format?.startsWith('resource-') || true)}
<ToggleButton
title="User Input"
position="center"
value="user"
startIcon={{ icon: faUser }}
size="xs"
iconOnly
disabled={staticOnly}
/>
{/if}
<ToggleButton
title="Connected"
position="right"
value="connected"
startIcon={{ icon: faArrowRight }}
size="xs"
iconOnly
disabled={staticOnly}
/>
{/if}
{#if userInputEnabled && (!input.format?.startsWith('resource-') || true)}
<ToggleButton
title="User Input"
position="center"
value="user"
startIcon={{ icon: faUser }}
size="xs"
iconOnly
disabled={staticOnly}
/>
{/if}
<ToggleButton
title="Connected"
position="right"
value="connected"
startIcon={{ icon: faArrowRight }}
size="xs"
iconOnly
disabled={staticOnly}
/>
</ToggleButtonGroup>
</ToggleButtonGroup>
{/if}
</div>
</div>
@@ -40,7 +40,7 @@
{#each Object.keys($runnableComponents ?? {}).filter((id) => id !== ownId) as id}
<tr>
<td class="whitespace-nowrap px-4 py-2 text-xs">
<Badge color="blue">{id}</Badge>
<Badge color="dark-indigo">{id}</Badge>
</td>
<td class="relative whitespace-nowrap px-4 py-2 ">
<input
@@ -68,7 +68,7 @@ export type Runnable = RunnableByPath | RunnableByName | undefined
// Runnable input, set by the developer in the component panel
export type ResultInput = {
runnable: Runnable
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput>
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
type: 'runnable'
}
+2 -2
View File
@@ -32,8 +32,8 @@ export function buildWorld(
const state = writable(0)
const outputsById: Record<string, Record<string, Output<any>>> = {
context: Object.fromEntries(Object.entries(context).map(([k, v]) => {
return [k, newWorld.newOutput('context', k, state, v)]
ctx: Object.fromEntries(Object.entries(context).map(([k, v]) => {
return [k, newWorld.newOutput('ctx', k, state, v)]
}))
}
for (const [k, outputs] of Object.entries(components)) {
+1 -1
View File
@@ -53,7 +53,7 @@ export type Aligned = {
export interface BaseAppComponent extends Partial<Aligned> {
id: ComponentID
componentInput: AppInput | undefined
configuration: Record<string, StaticAppInput | ConnectedAppInput | UserAppInput>
configuration: Record<string, (StaticAppInput | ConnectedAppInput | UserAppInput) & { onlyStatic?: boolean }>
card: boolean | undefined
/**
* If `true` then the wrapper will allow items to flow outside of it's borders.