Compare commits

...

30 Commits

Author SHA1 Message Date
Ruben Fiszel
4b1bfed090 push 2023-02-24 19:16:11 +01:00
Faton Ramadani
2125d119ff fix(frontend): Fix findGridItem 2023-02-24 19:04:33 +01:00
Faton Ramadani
d3f1ef92ea Merge branch 'rf/flatten' of https://github.com/windmill-labs/windmill into rf/flatten 2023-02-24 19:01:01 +01:00
Faton Ramadani
26296ee05c fix(frontend): Fix findGridItem 2023-02-24 19:00:41 +01:00
Ruben Fiszel
7e4179a741 flatten 2023-02-24 18:52:05 +01:00
Faton Ramadani
52adaa4cb5 fix(frontend): Fix next id 2023-02-24 18:51:50 +01:00
Faton Ramadani
a94f266743 fix(frontend): wip 2023-02-24 18:49:06 +01:00
Faton Ramadani
9d680e6744 Merge branch 'rf/flatten' of https://github.com/windmill-labs/windmill into rf/flatten 2023-02-24 18:44:19 +01:00
Faton Ramadani
5060c37fed fix(frontend): gridtab 2023-02-24 18:44:16 +01:00
Ruben Fiszel
15c2bdcdae pushed 2023-02-24 18:43:44 +01:00
Faton Ramadani
45ce630a75 fix(frontend): duplicate 2023-02-24 18:04:31 +01:00
Faton Ramadani
d50734cbb3 fix(frontend): delete grid item 2023-02-24 17:56:47 +01:00
Ruben Fiszel
b08b559eec script editor panel 2023-02-24 17:53:26 +01:00
Ruben Fiszel
5742bf2235 settings panel 2023-02-24 17:49:16 +01:00
Ruben Fiszel
c56176ec91 Merge branch 'main' into rf/flatten 2023-02-24 17:38:19 +01:00
Ruben Fiszel
dd7e8c742c fix(deno): remove mysql support waiting for deno fix 2023-02-24 17:19:13 +01:00
Faton Ramadani
640242b713 fix(frontend): rewrote utils 2023-02-24 17:17:11 +01:00
Ruben Fiszel
2f78132e08 fix(deno): remove mysql support waiting for deno fix 2023-02-24 17:11:53 +01:00
Ruben Fiszel
c696f3b6cf flatten 2023-02-24 15:09:38 +01:00
Ruben Fiszel
0041411c06 subgrids editor panel iter 1 2023-02-24 14:48:28 +01:00
Ruben Fiszel
54955b710c fix subgrids final2 2023-02-24 13:11:06 +01:00
Ruben Fiszel
1eb5a0d1d3 fix subgrids final 2023-02-24 11:59:28 +01:00
Ruben Fiszel
ddda14c52b app fixes 2023-02-24 11:39:50 +01:00
Ruben Fiszel
5123c9365c fix subgrids final 2023-02-24 07:27:22 +01:00
Ruben Fiszel
834e7b1d1c todo: improve deleting tab 2023-02-24 01:04:26 +01:00
Ruben Fiszel
6e9a5b026e tab rework v2 2023-02-23 23:39:57 +01:00
Ruben Fiszel
7ad8879b09 fix binding 2023-02-23 22:14:23 +01:00
Ruben Fiszel
27cac3ffe6 fix(frontend): containers and tab fixes v1 2023-02-23 21:42:21 +01:00
Ruben Fiszel
705703a5e2 fix(frontend): containers and tab fixes v1 2023-02-23 21:01:47 +01:00
Ruben Fiszel
fac31c6628 fix(frontend): containers and tab fixes v1 2023-02-23 19:59:23 +01:00
31 changed files with 1459 additions and 344 deletions

View File

@@ -14,7 +14,6 @@ BigInt.prototype.toJSON = function () {
};
export { pgSql, pgClient } from './pg.ts'
export { mySql, mysqlClient } from './mysql.ts'
export type Sql = string
export type Email = string

View File

@@ -396,7 +396,7 @@
bind:error={pathError}
bind:path
initialPath=""
namePlaceholder="my_{resource_type}"
namePlaceholder="{resource_type}"
kind="resource"
/>
@@ -433,7 +433,7 @@
{:else}
<Path
initialPath=""
namePlaceholder="my_{resource_type}"
namePlaceholder="{resource_type}"
bind:error={pathError}
bind:path
kind="resource"

View File

@@ -22,6 +22,7 @@
import Tooltip from './Tooltip.svelte'
import FolderEditor from './FolderEditor.svelte'
import GroupEditor from './GroupEditor.svelte'
import { random_adj } from './random_positive_adjetive'
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app'
let meta: Meta | undefined = undefined
@@ -69,15 +70,23 @@
export async function reset() {
if (path == '' || path == 'u//') {
meta = { ownerKind: 'user', name: namePlaceholder, owner: '' }
meta = {
ownerKind: 'user',
name: random_adj() + '_' + namePlaceholder,
owner: ''
}
meta.owner = $userStore!.username.split('@')[0]
let i = 1
while (await pathExists(metaToPath(meta), kind)) {
meta.name = `${namePlaceholder}_${i}`
i += 1
let newMeta = { ...meta }
while (await pathExists(metaToPath(newMeta), kind)) {
disabled = true
error = 'finding an available name...'
newMeta.name = random_adj() + '_' + namePlaceholder
}
error = ''
disabled = false
meta = newMeta
path = metaToPath(meta)
} else {
meta = pathToMeta(path)

View File

@@ -144,7 +144,7 @@
disabled={!can_write}
bind:path
{initialPath}
namePlaceholder="my_resource"
namePlaceholder="resource"
kind="resource"
/>
</div>

View File

@@ -180,7 +180,7 @@
bind:error={pathError}
bind:path
{initialPath}
namePlaceholder={'my_schedule'}
namePlaceholder="schedule"
kind="schedule"
/>
<div class="mb-8" />

View File

@@ -204,7 +204,7 @@
bind:path={script.path}
{initialPath}
on:enter={() => changeStep(2)}
namePlaceholder="my_script"
namePlaceholder="script"
kind="script"
/>
<h2 class="border-b pb-1 mt-12 mb-4">Summary</h2>
@@ -245,7 +245,7 @@
>
<LanguageIcon lang="pgsql" /><span class="ml-2 py-4">PostgreSQL</span>
</Button>
<Button
<!-- <Button
size="sm"
variant="border"
color={template == 'mysql' ? 'blue' : 'dark'}
@@ -257,7 +257,7 @@
}}
>
<LanguageIcon lang="mysql" /><span class="ml-2 py-4">MySQL</span>
</Button>
</Button> -->
</div>
<div class="mt-16 mb-4">
<Button

View File

@@ -148,7 +148,7 @@
bind:error={pathError}
bind:path
{initialPath}
namePlaceholder="my_variable"
namePlaceholder="variable"
kind="variable"
/>
</div>

View File

@@ -4,19 +4,16 @@
import type { AppInput } from '../../inputType'
import type { AppEditorContext, GridItem } from '../../types'
import InputValue from '../helpers/InputValue.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let configuration: Record<string, AppInput>
export let subGrids: GridItem[][] | undefined = undefined
export let componentContainerHeight: number
export const staticOutputs: string[] = ['loading', 'result']
const { focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
let noPadding: boolean | undefined = undefined
export const staticOutputs: string[] = []
const { app, focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
let result: string[] | undefined = undefined
let gridContent: string[] | undefined = undefined
function onFocus() {
@@ -29,15 +26,16 @@
$: $selectedComponent === id && onFocus()
</script>
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
{#if subGrids && subGrids[0]}
<SubGridEditor
bind:subGrid={subGrids[0]}
containerHeight={componentContainerHeight}
on:focus={() => {
$selectedComponent = id
}}
/>
{/if}
</RunnableWrapper>
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
{noPadding}
bind:subGrid={$app.subgrids[`${id}-0`]}
containerHeight={componentContainerHeight}
on:focus={() => {
$selectedComponent = id
}}
/>
{/if}

View File

@@ -6,27 +6,24 @@
import type { Output } from '../../rx'
import type { AppEditorContext, GridItem } from '../../types'
import InputValue from '../helpers/InputValue.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let configuration: Record<string, AppInput>
export let subGrids: GridItem[][] | undefined = undefined
export let componentContainerHeight: number
export let tabs: string[]
export const staticOutputs: string[] = ['loading', 'result', 'selectedTabIndex']
const { worldStore, focusedGrid, selectedComponent } =
export const staticOutputs: string[] = ['selectedTabIndex']
const { app, worldStore, focusedGrid, selectedComponent } =
getContext<AppEditorContext>('AppEditorContext')
let result: string[] | undefined = undefined
let gridContent: string[] | undefined = undefined
let selected: string = ''
let noPadding: boolean | undefined = undefined
$: selectedIndex = result?.indexOf(selected) ?? -1
$: selectedIndex = tabs?.indexOf(selected) ?? -1
$: if (result && selected === '') {
selected = result[0]
$: if ((tabs && selected === '') || !tabs.includes(selected)) {
selected = tabs[0]
}
$: outputs = $worldStore?.outputsById[id] as {
@@ -38,6 +35,7 @@
}
$: selectedIndex >= 0 && handleTabSelection()
let tabHeight: number = 0
function onFocus() {
@@ -50,24 +48,27 @@
$: $selectedComponent === id && selectedIndex >= 0 && onFocus()
</script>
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
<InputValue {id} input={configuration.tabs} bind:value={tabs} />
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<div>
<div bind:clientHeight={tabHeight}>
<Tabs bind:selected>
{#each result ?? [] as res}
{#each tabs ?? [] as res}
<Tab value={res}>
<span class="font-semibold">{res}</span>
</Tab>
{/each}
</Tabs>
</div>
{#if subGrids && subGrids[selectedIndex]}
{#if $app.subgrids?.[`${id}-${selectedIndex}`]}
<SubGridEditor
bind:subGrid={subGrids[selectedIndex]}
bind:subGrid={$app.subgrids[`${id}-${selectedIndex}`]}
{noPadding}
containerHeight={componentContainerHeight - tabHeight}
on:focus={() => {
$selectedComponent = id
}}
/>
{/if}
</RunnableWrapper>
</div>

View File

@@ -36,6 +36,7 @@
import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte'
import { page } from '$app/stores'
import CssSettings from './componentsPanel/CssSettings.svelte'
import { findGridItem } from './appUtils'
export let app: App
export let path: string

View File

@@ -241,7 +241,7 @@
bind:error={pathError}
bind:path={newPath}
initialPath=""
namePlaceholder="my_app"
namePlaceholder="app"
kind="app"
/>
@@ -467,8 +467,8 @@
<div>{`Sub grid: ${$focusedGrid.parentComponentId} (${$focusedGrid.subGridIndex})`}</div>
<button
on:click={() => {
$focusedGrid = undefined
$selectedComponent = undefined
$focusedGrid = undefined
}}
>
<X size={14} />

View File

@@ -37,8 +37,8 @@
const disabledGridItem = fct(gridItem)
if (disabledGridItem.data.subGrids) {
disabledGridItem.data.subGrids = disabledGridItem.data.subGrids.map((subgrid: GridItem[]) =>
subgrid.map((subgridItem: GridItem) => fct(subgridItem))
disabledGridItem.data.subGrids = disabledGridItem.data.subGrids.map(
(subgrid: GridItem[]) => subgrid?.map((subgridItem: GridItem) => fct(subgridItem)) ?? []
)
}
@@ -87,6 +87,7 @@
function selectComponent(id: string) {
if (!$connectingInput.opened) {
$selectedComponent = id
$focusedGrid = undefined
}
}

View File

@@ -1,36 +1,36 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppEditorContext } from '../types'
import { allItems, allItemsWithParent } from '../utils'
import PanelSection from './settingsPanel/common/PanelSection.svelte'
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
import TablePanel from './TablePanel.svelte'
const { selectedComponent, lazyGrid, app } = getContext<AppEditorContext>('AppEditorContext')
const { selectedComponent, app } = getContext<AppEditorContext>('AppEditorContext')
$: subgridKeys = Object.keys($app.subgrids ?? {})
</script>
{#each $lazyGrid as gridItem (gridItem.data.id)}
{#each $app.grid as gridItem (gridItem.data.id)}
{#if gridItem.data.id === $selectedComponent}
<ComponentPanel bind:component={gridItem.data} />
<ComponentPanel parent={undefined} bind:component={gridItem.data} />
{:else if gridItem.data.type === 'tablecomponent'}
<TablePanel bind:component={gridItem.data} />
{:else if gridItem.data.subGrids}
{#each gridItem.data.subGrids as subGrid}
{#each subGrid as subGridItem, index (subGridItem.data.id)}
{#if subGridItem.data.id === $selectedComponent}
<ComponentPanel
bind:component={subGridItem.data}
onDelete={() => {
subGrid.splice(index, 1)
subGrid = subGrid
}}
/>
{/if}
{/each}
{/each}
{/if}
{/each}
{#if $app.subgrids}
{#each subgridKeys as key (key)}
{#each $app.subgrids[key] as gridItem (gridItem.data.id)}
{#if gridItem.data.id === $selectedComponent}
<ComponentPanel parent={key} bind:component={gridItem.data} />
{:else if gridItem.data.type === 'tablecomponent'}
<TablePanel bind:component={gridItem.data} />
{/if}
{/each}
{/each}
{/if}
{#each $app?.hiddenInlineScripts ?? [] as script, index (script.name)}
{#if $selectedComponent === `bg_${index}`}
<PanelSection title={`Background script inputs`}>

View File

@@ -6,8 +6,10 @@
import type { AppEditorContext, GridItem } from '../types'
import Component from './component/Component.svelte'
export let subGrid: GridItem[]
export let containerHeight: number
export let noPadding = false
//export let id: string
export let subGrid: GridItem[] = []
const dispatch = createEventDispatcher()
@@ -35,6 +37,12 @@
onComponent = id
if (!$connectingInput.opened) {
$selectedComponent = id
/*
$focusedGrid = {
parentComponentId: parentId,
subGridIndex: index
}
*/
}
}
@@ -47,33 +55,12 @@
// @ts-ignore
let container
$: if ($focusedGrid?.parentComponentId !== $selectedComponent) {
const gridItemIds = $app.grid
.map((gridItem: GridItem) => {
if (gridItem.data.id === $focusedGrid?.parentComponentId) {
const subGrids = gridItem.data.subGrids ?? []
return [
gridItem.data.id,
...subGrids.map((subGrid: GridItem[]) =>
subGrid.map((gridItem: GridItem) => gridItem.data.id)
)
]
} else {
return []
}
})
.flat(2)
if (!gridItemIds.includes($selectedComponent)) {
$focusedGrid = undefined
}
}
</script>
<div class="relative w-full subgrid " bind:this={container}>
<div
class="px-4 pt-4 overflow-auto {$connectingInput?.opened ? '' : ''}"
class:px-2={!noPadding}
class="py-2 overflow-auto {$connectingInput?.opened ? '' : ''}"
on:pointerdown|stopPropagation={onpointerdown}
on:pointerleave={onpointerup}
on:pointerup={onpointerup}

View File

@@ -11,6 +11,8 @@
{#each component.actionButtons as actionButton (actionButton.id)}
{#if actionButton.id === $selectedComponent}
<ComponentPanel
parent={undefined}
noGrid
rowColumns
bind:component={actionButton}
onDelete={() => {

View File

@@ -0,0 +1,182 @@
import { getNextId } from '$lib/components/flows/flowStateUtils'
import type { App, FocusedGrid, GridItem } from '../types'
import { Component, getRecommendedDimensionsByComponent, type AppComponent } from './component'
import gridHelp from '@windmill-labs/svelte-grid/src/utils/helper'
import { gridColumns } from '../gridUtils'
import { allItems } from '../utils'
function findGridItemById(
root: GridItem[],
subGrids: Record<string, GridItem[]> | undefined,
id: string
): GridItem | undefined {
for (const gridItem of root) {
if (gridItem.id === id) {
return gridItem
}
if (subGrids) {
const numberOfSubgrids = gridItem.data.numberOfSubgrids
if (numberOfSubgrids) {
for (let i = 0; i < numberOfSubgrids; i++) {
const subgrid = subGrids[`${gridItem.id}-${i}`] ?? []
const found = findGridItemById(subgrid, subGrids, id)
if (found) {
return found
}
}
}
}
}
return undefined
}
export function findGridItem(app: App, id: string): GridItem | undefined {
return findGridItemById(app.grid, app.subgrids, id)
}
export function getNextGridItemId(app: App): string {
const subgridsKeys = allItems(app.grid, app.subgrids).map((x) => x.id)
const withoutDash = subgridsKeys.map((element) => element.split('-')[0])
const id = getNextId([...new Set(withoutDash)])
return id
}
export function createNewGridItem(grid: GridItem[], id: string, data: AppComponent): GridItem {
const appComponent = data
appComponent.id = id
const newComponent = {
fixed: false,
resizable: true,
draggable: true,
customDragger: false,
customResizer: false,
x: 0,
y: 0
}
let newData: AppComponent = JSON.parse(JSON.stringify(appComponent))
const newItem: GridItem = {
data: newData,
id: id
}
gridColumns.forEach((column) => {
const rec = getRecommendedDimensionsByComponent(appComponent.type, column)
newItem[column] = {
...newComponent,
min: { w: 1, h: 1 },
max: { w: column, h: 100 },
w: rec.w,
h: rec.h
}
const position = gridHelp.findSpace(newItem, grid, column) as { x: number; y: number }
newItem[column] = { ...newItem[column], ...position }
})
return newItem
}
export function insertNewGridItem(
app: App,
data: AppComponent,
focusedGrid: FocusedGrid | undefined
) {
const id = getNextGridItemId(app)
if (!focusedGrid) {
const newItem = createNewGridItem(app.grid, id, data)
app.grid.push(newItem)
} else {
const { parentComponentId, subGridIndex } = focusedGrid
if (!app.subgrids) {
app.subgrids = {}
}
const subGrid = app.subgrids[`${parentComponentId}-${subGridIndex}`] ?? []
const newItem = createNewGridItem(subGrid, id, data)
const key = `${parentComponentId}-${subGridIndex ?? 0}`
if (!app.subgrids[key]) {
app.subgrids[key] = [newItem]
} else {
app.subgrids[key].push(newItem)
}
}
return id
}
export function getAllSubgridsAndComponentIds(
app: App,
component: AppComponent
): [string[], string[]] {
const subgrids: string[] = []
let components: string[] = [component.id]
if (app.subgrids && component.numberOfSubgrids) {
for (let i = 0; i < component.numberOfSubgrids; i++) {
const subgrid = app.subgrids[`${component.id}-${i}`]
if (subgrid) {
for (const item of subgrid) {
let [recSubgrids, recComponents] = getAllSubgridsAndComponentIds(app, item.data)
subgrids.push(...recSubgrids)
components.push(...recComponents)
}
}
}
}
return [subgrids, components]
}
export function deleteGridItem(
app: App,
component: AppComponent,
parent: string | undefined
): string[] {
let [subgrids, components] = getAllSubgridsAndComponentIds(app, component)
if (app.subgrids) {
subgrids.forEach((id) => {
delete app.subgrids![id]
})
}
if (!parent) {
let index = app.grid.findIndex((x) => x.id == component.id)
if (index > -1) {
app.grid.splice(index, 1)
}
} else {
let grid = app.subgrids![parent]
let index = grid.findIndex((x) => x.id == component.id)
if (index > -1) {
grid.splice(index, 1)
}
}
return components
}
export function duplicateGridItem(
app: App,
focusedGrid: FocusedGrid | undefined,
id: string
): string | undefined {
const gridItem = findGridItem(app, id)
if (gridItem) {
const newId = getNextGridItemId(app)
const newItem = JSON.parse(JSON.stringify(gridItem))
newItem.id = newId
newItem.data.id = newId
return insertNewGridItem(app, newItem.data, focusedGrid)
}
return undefined
}

View File

@@ -73,7 +73,9 @@ export type IconComponent = BaseComponent<'iconcomponent'>
export type HorizontalDividerComponent = BaseComponent<'horizontaldividercomponent'>
export type VerticalDividerComponent = BaseComponent<'verticaldividercomponent'>
export type FileInputComponent = BaseComponent<'fileinputcomponent'>
export type TabsComponent = BaseComponent<'tabscomponent'>
export type TabsComponent = BaseComponent<'tabscomponent'> & {
tabs: string[]
}
export type ContainerComponent = BaseComponent<'containercomponent'>
export type AppComponent = BaseAppComponent &
@@ -150,7 +152,7 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
configuration: {},
customCss: {
header: { class: '', style: '' },
container: { class: '', style: '' },
container: { class: '', style: '' }
} as const,
card: false
}
@@ -161,18 +163,19 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
dims: '2:8-6:8',
data: {
softWrap: true,
verticalAlignment: 'center',
id: '',
type: 'containercomponent',
configuration: {},
componentInput: {
type: 'static',
fieldType: 'array',
subFieldType: 'text',
value: ['First Tab', 'Second Tab']
configuration: {
noPadding: {
type: 'static',
fieldType: 'boolean',
value: false,
onlyStatic: true
}
},
componentInput: undefined,
card: false,
subGrids: [[]]
numberOfSubgrids: 1
}
},
textcomponent: {
@@ -1012,18 +1015,20 @@ Hello \${ctx.username}
dims: '2:8-6:8',
data: {
softWrap: true,
verticalAlignment: 'center',
id: '',
type: 'tabscomponent',
configuration: {},
componentInput: {
type: 'static',
fieldType: 'array',
subFieldType: 'text',
value: ['First Tab', 'Second Tab']
configuration: {
noPadding: {
type: 'static',
fieldType: 'boolean',
value: false,
onlyStatic: true
}
},
componentInput: undefined,
card: false,
subGrids: [[], []]
numberOfSubgrids: 2,
tabs: ['First tab', 'Second tab']
}
},
iconcomponent: {
@@ -1195,4 +1200,4 @@ Hello \${ctx.username}
card: false
}
}
}
}

View File

@@ -1,34 +1,20 @@
<script lang="ts">
import type { AppEditorContext, GridItem } from '../../types'
import type { AppEditorContext } from '../../types'
import { getContext, onMount } from 'svelte'
import { getNextId } from '$lib/components/flows/flowStateUtils'
import { isOpenStore } from './store'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
import { components as componentsRecord, COMPONENT_SETS, type AppComponent } from '../component'
import ListItem from './ListItem.svelte'
import { insertNewGridItem, createNewGridItem, getNextGridItemId } from '../../utils'
import { insertNewGridItem } from '../appUtils'
const TITLE_PREFIX = 'Component.' as const
const { app, selectedComponent, focusedGrid } = getContext<AppEditorContext>('AppEditorContext')
function addComponent(appComponentType: AppComponent['type']): void {
// When a new component is added, we need to mark the app as dirty,
// so a confirmation modal will appear if the user tries to leave the page
$dirtyStore = true
const grid = $app.grid ?? []
const id = getNextGridItemId(grid)
const data = componentsRecord[appComponentType].data
if ($focusedGrid) {
const { parentComponentId, subGridIndex } = $focusedGrid
$app.grid = insertNewGridItem($app.grid, parentComponentId, subGridIndex, id, data)
} else {
const newItem = createNewGridItem(grid, id, data)
$app.grid = [...grid, newItem]
}
const id = insertNewGridItem($app, data, $focusedGrid)
$selectedComponent = id
}

View File

@@ -2,6 +2,7 @@
import { classNames } from '$lib/utils'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import { findGridItem } from '../appUtils'
import { components } from '../component'
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
import ComponentOutputViewer from './ComponentOutputViewer.svelte'
@@ -26,7 +27,7 @@
}
function getComponentNameById(componentId: string) {
const component = $app.grid.find((c) => c?.data?.id === componentId)
const component = findGridItem($app, componentId)
if (component?.data.type) {
return components[component?.data.type].name

View File

@@ -164,13 +164,13 @@
createInlineScriptByLanguage(Script.language.DENO, name, 'pgsql')
}}
/>
<FlowScriptPicker
<!-- <FlowScriptPicker
label={`MySQL`}
lang="mysql"
on:click={() => {
createInlineScriptByLanguage(Script.language.DENO, name, 'mysql')
}}
/>
/> -->
</div>
<div class="mt-4">

View File

@@ -8,7 +8,7 @@
import { emptySchema, getScriptByPath } from '$lib/utils'
import { faCodeBranch, faExternalLinkAlt, faEye, faPen } from '@fortawesome/free-solid-svg-icons'
import type { AppInput, RunnableByPath } from '../../inputType'
import { clearResultAppInput, schemaToInputsSpec } from '../../utils'
import { clearResultAppInput } from '../../utils'
import EmptyInlineScript from './EmptyInlineScript.svelte'
import InlineScriptEditor from './InlineScriptEditor.svelte'
import { computeFields } from './utils'

View File

@@ -4,11 +4,12 @@
import SplitPanesWrapper from '$lib/components/splitPanes/SplitPanesWrapper.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import InlineScriptsPanelList from './InlineScriptsPanelList.svelte'
import InlineScriptEditorPanel from './InlineScriptEditorPanel.svelte'
import InlineScriptEditor from './InlineScriptEditor.svelte'
import EmptyInlineScript from './EmptyInlineScript.svelte'
import { allItems } from '../../utils'
import InlineScriptEditorPanel from './InlineScriptEditorPanel.svelte'
const { lazyGrid, app, staticOutputs, runnableComponents } =
const { app, staticOutputs, runnableComponents } =
getContext<AppEditorContext>('AppEditorContext')
let selectedScriptComponentId: string | undefined = undefined
@@ -23,16 +24,17 @@
{#if !selectedScriptComponentId}
<span class="p-2 text-sm text-gray-600">Select a script on the left panel</span>
{/if}
{#each $lazyGrid as gridComponent, index (index)}
{#if gridComponent.data.id === selectedScriptComponentId}
{#each allItems($app.grid, $app.subgrids) as gridItem (gridItem.data.id)}
{#if gridItem.data.id === selectedScriptComponentId}
<InlineScriptEditorPanel
id={gridComponent.data.id}
bind:componentInput={gridComponent.data.componentInput}
id={gridItem.data.id}
bind:componentInput={gridItem.data.componentInput}
/>
{/if}
{#if gridComponent.data.type === 'tablecomponent'}
{#each gridComponent.data.actionButtons as actionButton, index (index)}
{#if gridItem.data.type === 'tablecomponent'}
{#each gridItem.data.actionButtons as actionButton, index (index)}
{#if actionButton.id === selectedScriptComponentId}
<InlineScriptEditorPanel
id={actionButton.id}

View File

@@ -2,7 +2,7 @@
import Button from '$lib/components/common/button/Button.svelte'
import { faCopy, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import type { AppEditorContext, GridItem } from '../../types'
import PanelSection from './common/PanelSection.svelte'
import InputsSpecsEditor from './InputsSpecsEditor.svelte'
import TableActions from './TableActions.svelte'
@@ -10,14 +10,7 @@
import ConnectedInputEditor from './inputEditor/ConnectedInputEditor.svelte'
import Badge from '$lib/components/common/badge/Badge.svelte'
import { capitalize, classNames } from '$lib/utils'
import {
buildExtraLib,
createNewGridItem,
fieldTypeToTsType,
findParent,
getNextGridItemId,
insertNewGridItem
} from '../../utils'
import { buildExtraLib, fieldTypeToTsType } from '../../utils'
import Recompute from './Recompute.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import ComponentInputTypeEditor from './ComponentInputTypeEditor.svelte'
@@ -27,81 +20,43 @@
import type { AppComponent } from '../component'
import CssProperty from '../componentsPanel/CssProperty.svelte'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
import GridTab from './GridTab.svelte'
import { duplicateGridItem } from '../appUtils'
import { deleteGridItem } from '../appUtils'
export let component: AppComponent | undefined
export let onDelete: (() => void) | undefined = undefined
export let component: AppComponent
export let rowColumns = false
export let onDelete: (() => void) | undefined = undefined
export let parent: string | undefined
export let noGrid = false
const { app, staticOutputs, runnableComponents, selectedComponent, worldStore, focusedGrid } =
getContext<AppEditorContext>('AppEditorContext')
function duplicateElement(id: string) {
const parent = findParent($app.grid, id)
if (!parent) {
return
}
const data: AppComponent = JSON.parse(JSON.stringify(parent.data))
$dirtyStore = true
const grid = $app.grid ?? []
const newId = getNextGridItemId(grid)
if ($focusedGrid) {
const { parentComponentId, subGridIndex } = $focusedGrid
$app.grid = insertNewGridItem($app.grid, parentComponentId, subGridIndex, newId, data)
} else {
const newItem = createNewGridItem(grid, newId, data)
$app.grid = [...grid, newItem]
}
const newId = duplicateGridItem($app, $focusedGrid, id)
$selectedComponent = newId
}
function removeGridElement() {
$selectedComponent = undefined
if (onDelete && component) {
delete $staticOutputs[component.id]
$staticOutputs = $staticOutputs
delete $runnableComponents[component.id]
$runnableComponents = $runnableComponents
onDelete()
// Delete static inputs
} else {
if (component) {
$app.grid = $app.grid.filter((gridComponent) => gridComponent.data.id !== component?.id)
// Delete static inputs
delete $staticOutputs[component.id]
$staticOutputs = $staticOutputs
delete $runnableComponents[component.id]
$runnableComponents = $runnableComponents
$focusedGrid = undefined
if (component && !noGrid) {
let ids = deleteGridItem($app, component, parent)
for (const key in ids) {
delete $staticOutputs[key]
delete $runnableComponents[key]
}
}
if (
component &&
component.componentInput?.type === 'runnable' &&
component.componentInput?.runnable?.type === 'runnableByName'
) {
const { name, inlineScript } = component.componentInput.runnable
delete $staticOutputs[component.id]
delete $runnableComponents[component.id]
$app = $app
$staticOutputs = $staticOutputs
$runnableComponents = $runnableComponents
if (inlineScript) {
if (!$app.unusedInlineScripts) {
$app.unusedInlineScripts = []
}
$app.unusedInlineScripts.push({
name,
inlineScript
})
}
}
onDelete?.()
}
$: extraLib =
@@ -140,18 +95,7 @@
<div class="flex flex-col w-full gap-2 my-2">
{#if component.componentInput.type === 'static'}
<StaticInputEditor
bind:componentInput={component.componentInput}
on:deleteArrayItem={(e) => {
if (component?.type === 'tabscomponent') {
const deletedIndex = e.detail.index
if (component.subGrids) {
component.subGrids.splice(deletedIndex, 1)
}
}
}}
/>
<StaticInputEditor bind:componentInput={component.componentInput} />
{:else if component.componentInput.type === 'template' && component.componentInput !== undefined}
<div class="py-1 min-h-[28px] rounded border border-1 border-gray-500">
<TemplateEditor fontSize={12} bind:code={component.componentInput.eval} {extraLib} />
@@ -204,6 +148,10 @@
</PanelSection>
{/if}
{#if component.type === 'tabscomponent'}
<GridTab bind:tabs={component.tabs} {component} />
{/if}
{#if component.type === 'tablecomponent' && Array.isArray(component.actionButtons)}
<TableActions id={component.id} bind:components={component.actionButtons} />
{/if}

View File

@@ -0,0 +1,76 @@
<script lang="ts">
import Button from '$lib/components/common/button/Button.svelte'
import { faPlus, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import { deleteGridItem } from '../appUtils'
import type { AppComponent } from '../component'
import PanelSection from './common/PanelSection.svelte'
export let tabs: string[]
export let component: AppComponent
const { app, staticOutputs, runnableComponents } =
getContext<AppEditorContext>('AppEditorContext')
function addTab() {
const numberOfTabs = tabs.length
tabs = [...tabs, `Tab ${numberOfTabs + 1}`]
if (!$app.subgrids) {
$app.subgrids = {}
}
$app.subgrids[`${component.id}-${numberOfTabs}`] = []
component.numberOfSubgrids = tabs.length
}
function deleteSubgrid(index: number) {
tabs.splice(index, 1)
tabs = tabs
let subgrid = `${component.id}-${index}`
for (const item of $app!.subgrids![subgrid]) {
const components = deleteGridItem($app, item.data, subgrid)
for (const key in components) {
delete $staticOutputs[key]
delete $runnableComponents[key]
}
}
delete $app!.subgrids![subgrid]
}
</script>
<PanelSection title={`Tabs ${tabs.length > 0 ? `(${tabs.length})` : ''}`}>
{#if tabs.length == 0}
<span class="text-xs text-gray-500">No Tabs</span>
{/if}
<div class="flex gap-2 flex-col mt-2">
{#each tabs as value, index (index)}
<div class="flex flex-row gap-2 items-center relative">
<input type="text" bind:value />
<div class="absolute top-1 right-1">
<Button
size="xs"
color="light"
variant="border"
on:click={() => {
deleteSubgrid(index)
}}
iconOnly
btnClasses="!text-red-500"
startIcon={{ icon: faTrashAlt }}
/>
</div>
</div>
{/each}
<Button
size="xs"
color="light"
variant="border"
startIcon={{ icon: faPlus }}
on:click={addTab}
iconOnly
/>
</div>
</PanelSection>

View File

@@ -95,17 +95,28 @@
}}
on:keypress
>
<Badge color="dark-indigo">
{component.id}
</Badge>
<div>
<TableActionLabel componentInput={component.configuration.label} />
</div>
<div>
<Button variant="border" color="red" on:click={() => deleteComponent(component.id)}>
<Icon class="h-3" data={faTrashAlt} />
</Button>
</div>
<div>
<TableActionLabel componentInput={component.configuration.label} />
</div>
<Badge color="dark-indigo">
{component.id}
</Badge>
</div>
{/each}
<div class="w-full">
<Button
btnClasses="w-full"
color="light"
variant="border"
startIcon={{ icon: faPlus }}
on:click={addComponent}
iconOnly
/>
</div>
</PanelSection>

View File

@@ -59,7 +59,8 @@ export interface BaseAppComponent extends Partial<Aligned> {
* *For example when the component has a popup like `Select`*
*/
softWrap?: boolean
subGrids?: GridItem[][]
// Number of subgrids
numberOfSubgrids?: number
}
export type ComponentSet = {
@@ -99,6 +100,7 @@ export type App = {
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
}>
css?: Record<'viewer' | 'grid' | AppComponent['type'], ComponentCustomCSS>
subgrids?: Record<string, GridItem[]>
}
export type ConnectingInput = {
@@ -132,7 +134,6 @@ export type AppEditorContext = {
}
export type FocusedGrid = { parentComponentId: string; subGridIndex: number }
export type EditorMode = 'dnd' | 'preview'
export type EditorBreakpoint = 'sm' | 'lg'

View File

@@ -2,19 +2,65 @@ import type { Schema } from '$lib/common'
import { FlowService, ScriptService } from '$lib/gen'
import { inferArgs } from '$lib/infer'
import { emptySchema } from '$lib/utils'
import type { AppComponent, AppComponentConfig } from './editor/component'
import {
components as componentsRecord,
getRecommendedDimensionsByComponent
} from './editor/component'
import { gridColumns } from './gridUtils'
import gridHelp from '@windmill-labs/svelte-grid/src/utils/helper'
import type { AppComponent } from './editor/component'
import type { AppInput, InputType, ResultAppInput, StaticAppInput } from './inputType'
import type { Output } from './rx'
import type { App, GridItem } from './types'
import { getNextId } from '../flows/flowStateUtils'
/*
export function deleteComponent(
subgrid: string | undefined,
component: AppComponent,
app: App,
staticOutputs: Record<string, any>,
runnableComponents: Record<string, any>
) {
if (parentItems) {
let index = parentItems.findIndex((item) => item.data?.id === component.id)
if (index != -1) {
parentItems.splice(index, 1)
}
}
delete staticOutputs[component.id]
delete runnableComponents[component.id]
if (
component.componentInput?.type === 'runnable' &&
component.componentInput?.runnable?.type === 'runnableByName'
) {
const { name, inlineScript } = component.componentInput.runnable
if (inlineScript) {
if (!app.unusedInlineScripts) {
app.unusedInlineScripts = []
}
app.unusedInlineScripts.push({
name,
inlineScript
})
}
}
}
*/
export function allItems(grid: GridItem[], subgrids: Record<string, GridItem[]> | undefined): GridItem[] {
if (subgrids == undefined) {
return grid
}
return [...grid, ...Object.values(subgrids).flat()]
}
export function allItemsWithParent(grid: GridItem[], subgrids: Record<string, GridItem[]> | undefined): [GridItem, string | undefined][] {
const items: [GridItem, string | undefined][] = grid.map((item) => [item, undefined])
if (subgrids == undefined) {
return items
}
return [...items, ...Object.entries(subgrids).flatMap(([k, v]) => v.map((g) => [g, k] as [GridItem, string]))]
}
export async function loadSchema(
workspace: string,
@@ -218,101 +264,3 @@ export function toPascalCase(text: string) {
export function toKebabCase(text: string) {
return text.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
}
export function findParent(root: GridItem[], id: string): GridItem | undefined {
for (const a of root) {
if (a.id === id) {
return a
}
if (a.data.subGrids) {
// Recursively search the sub-grids
for (const subGrid of a.data.subGrids) {
const result = findParent(subGrid, id)
if (result) {
return result
}
}
}
}
return undefined
}
export function insertNewGridItem(
root: GridItem[],
id: string,
subGridIndex: number,
newId: string,
data: AppComponent
): GridItem[] {
const parentA = findParent(root, id)
if (!parentA) {
throw new Error(`Parent A object with ID ${id} not found.`)
}
const subGrid = parentA.data.subGrids[subGridIndex]
if (!subGrid) {
throw new Error(`Sub-grid with index ${subGridIndex} not found.`)
}
const newItem = createNewGridItem(subGrid ?? [], newId, data)
subGrid.push(newItem)
return root
}
// The grid is needed to find a space for the new component
export function createNewGridItem(grid: GridItem[], id: string, data: AppComponent): GridItem {
const appComponent = data
appComponent.id = id
const newComponent = {
fixed: false,
resizable: true,
draggable: true,
customDragger: false,
customResizer: false,
x: 0,
y: 0
}
let newData: AppComponent = JSON.parse(JSON.stringify(appComponent))
const newItem: GridItem = {
data: newData,
id: id
}
gridColumns.forEach((column) => {
const rec = getRecommendedDimensionsByComponent(appComponent.type, column)
newItem[column] = {
...newComponent,
min: { w: 1, h: 1 },
max: { w: column, h: 100 },
w: rec.w,
h: rec.h
}
const position = gridHelp.findSpace(newItem, grid, column) as { x: number; y: number }
newItem[column] = { ...newItem[column], ...position }
})
return newItem
}
function recursiveGetIds(gridItem: GridItem): string[] {
const subGrids = gridItem.data.subGrids ?? []
const subGridIds = subGrids
.map((subGrid: GridItem[]) => subGrid.map(recursiveGetIds))
.flat(Infinity)
return [gridItem.data.id, ...subGridIds]
}
export function getNextGridItemId(grid: GridItem[] = []): string {
const gridItemIds = grid.map(recursiveGetIds).flat()
const id = getNextId(gridItemIds)
return id
}

View File

@@ -142,12 +142,12 @@
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'pgsql' })}
/>
<FlowScriptPicker
<!-- <FlowScriptPicker
label={`MySQL`}
lang="mysql"
on:click={() =>
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'mysql' })}
/>
/> -->
{/if}
{/if}
</div>

View File

@@ -42,12 +42,7 @@
<svelte:fragment slot="content">
<TabContent value="settings-metadata" class="p-4 h-full">
<div class="overflow-auto h-full">
<Path
bind:path={$flowStore.path}
{initialPath}
namePlaceholder="my_flow"
kind="flow"
/>
<Path bind:path={$flowStore.path} {initialPath} namePlaceholder="flow" kind="flow" />
<label class="block my-4">
<span class="text-gray-700 text-sm">Summary <Required required={false} /></span>

View File

@@ -0,0 +1,962 @@
const adjectives = ["abundant",
"accessible",
"accommodative",
"accomplished",
"accurate",
"achievable",
"adaptable",
"adaptive",
"adequate",
"adjustable",
"admirable",
"admiring",
"adorable",
"adored",
"adoring",
"adroit",
"adulatory",
"advanced",
"advantageous",
"adventuresome",
"adventurous",
"affable",
"affectionate",
"affirmative",
"affluent",
"affordable",
"agile",
"agreeable",
"alluring",
"altruistic",
"amazed",
"amazing",
"ambitious",
"amenable",
"amiable",
"amicable",
"ample",
"amusing",
"angelic",
"appealing",
"appreciable",
"appreciated",
"appreciative",
"appropriate",
"ardent",
"articulate",
"artistic",
"assuring",
"astonished",
"astonishing",
"astounded",
"astounding",
"athletic",
"attentive",
"attractive",
"audible",
"auspicious",
"authentic",
"authoritative",
"autonomous",
"available",
"avid",
"awed",
"awesome",
"awestruck",
"balanced",
"beauteous",
"beautiful",
"believable",
"beloved",
"beneficent",
"beneficial",
"beneficiary",
"benevolent",
"best",
"best_known",
"best_performing",
"best_selling",
"better",
"better_known",
"better_than_expected",
"blameless",
"blissful",
"blithe",
"bonny",
"booming",
"boundless",
"bountiful",
"brainiest",
"brainy",
"brand_new",
"brave",
"breathtaking",
"bright",
"brighter",
"brightest",
"brilliant",
"brisk",
"brotherly",
"bullish",
"buoyant",
"calm",
"calming",
"capable",
"captivating",
"carefree",
"catchy",
"celebrated",
"celebratory",
"champion",
"charismatic",
"charitable",
"charming",
"chaste",
"cheaper",
"cheapest",
"cheerful",
"cheery",
"cherished",
"chivalrous",
"classic",
"classy",
"clean",
"cleaner",
"cleanest",
"cleanly",
"clear",
"clear_cut",
"cleared",
"clearer",
"clever",
"coherent",
"cohesive",
"colorful",
"comely",
"comfortable",
"comforting",
"comfy",
"commendable",
"commodious",
"compact",
"compassionate",
"compatible",
"competitive",
"complementary",
"compliant",
"complimentary",
"comprehensive",
"conciliatory",
"concise",
"confident",
"congenial",
"congratulatory",
"conscientious",
"considerate",
"consistent",
"constructive",
"consummate",
"contrasty",
"convenient",
"convincing",
"cool",
"coolest",
"cooperative",
"correct",
"cost_effective",
"cost_saving",
"courageous",
"courteous",
"courtly",
"cozy",
"creative",
"credible",
"crisp",
"crisper",
"cure_all",
"cushy",
"cute",
"daring",
"darling",
"dashing",
"dauntless",
"dazzled",
"dazzling",
"dead_cheap",
"dead_on",
"decent",
"decisive",
"dedicated",
"defeated",
"deft",
"delectable",
"delicate",
"delicious",
"delighted",
"delightful",
"dependable",
"deserving",
"desirable",
"desirous",
"detachable",
"devout",
"dexterous",
"dextrous",
"dignified",
"diligent",
"diplomatic",
"dirt_cheap",
"distinctive",
"distinguished",
"diversified",
"divine",
"dominated",
"dumbfounded",
"dumbfounding",
"dummy_proof",
"durable",
"dynamic",
"eager",
"earnest",
"eased",
"easier",
"easiest",
"easy",
"easy_to_use",
"easygoing",
"ebullient",
"economical",
"ecstatic",
"educated",
"effective",
"effectual",
"efficacious",
"efficient",
"effortless",
"effusive",
"elated",
"elegant",
"elite",
"eloquent",
"eminent",
"enchanted",
"enchanting",
"encouraging",
"endearing",
"endorsed",
"energetic",
"energy_efficient",
"energy_saving",
"engaging",
"engrossing",
"enhanced",
"enjoyable",
"enough",
"enraptured",
"enterprising",
"entertaining",
"enthralled",
"enthusiastic",
"enticing",
"entranced",
"entrancing",
"enviable",
"envious",
"equitable",
"err_free",
"erudite",
"ethical",
"euphoric",
"evaluative",
"eventful",
"everlasting",
"evocative",
"exalted",
"exalting",
"exceeding",
"excellent",
"exceptional",
"excited",
"exciting",
"exemplary",
"exhilarating",
"expansive",
"exquisite",
"extraordinary",
"exuberant",
"exultant",
"eye_catching",
"fabulous",
"fair",
"faithful",
"famed",
"famous",
"fancier",
"fancy",
"fantastic",
"fascinating",
"fashionable",
"fast",
"fast_growing",
"fast_paced",
"faster",
"fastest",
"fastest_growing",
"faultless",
"favorable",
"favored",
"favorite",
"fearless",
"feasible",
"feature_rich",
"feisty",
"felicitous",
"fertile",
"fervent",
"fervid",
"festive",
"fiery",
"fine",
"fine_looking",
"finer",
"finest",
"firmer",
"first_class",
"first_in_class",
"first_rate",
"flashy",
"flatter",
"flattering",
"flawless",
"flexible",
"flourishing",
"fluent",
"fond",
"foolproof",
"foremost",
"formidable",
"fortuitous",
"fortunate",
"fragrant",
"free",
"freed",
"fresh",
"fresher",
"freshest",
"friendly",
"frugal",
"fruitful",
"fun",
"funny",
"futuristic",
"gainful",
"gallant",
"galore",
"generous",
"genial",
"gentle",
"gentlest",
"genuine",
"gifted",
"glad",
"glamorous",
"gleeful",
"glimmering",
"glistening",
"glorious",
"glowing",
"god_given",
"godlike",
"gold",
"golden",
"good",
"goodly",
"gorgeous",
"graceful",
"gracious",
"grand",
"grateful",
"gratified",
"gratifying",
"great",
"greatest",
"groundbreaking",
"guiltless",
"gutsy",
"halcyon",
"hale",
"hallowed",
"handier",
"hands_down",
"handsome",
"handy",
"happier",
"happy",
"hard_working",
"hardier",
"hardy",
"harmless",
"harmonious",
"healthful",
"healthy",
"heartening",
"heartfelt",
"heartwarming",
"heavenly",
"helpful",
"heroic",
"high_quality",
"high_spirited",
"hilarious",
"holy",
"honest",
"honorable",
"honored",
"hopeful",
"hospitable",
"hot",
"hottest",
"humane",
"humble",
"humorous",
"humourous",
"ideal",
"idolized",
"idyllic",
"illuminating",
"illustrious",
"imaginative",
"immaculate",
"immense",
"impartial",
"impassioned",
"impeccable",
"important",
"impressed",
"impressive",
"improved",
"improving",
"incredible",
"indebted",
"individualized",
"indulgent",
"industrious",
"inestimable",
"inexpensive",
"infallible",
"influential",
"ingenious",
"ingenuous",
"innocuous",
"innovative",
"insightful",
"inspirational",
"inspiring",
"instructive",
"instrumental",
"integral",
"integrated",
"intelligent",
"intelligible",
"interesting",
"intimate",
"intricate",
"intriguing",
"intuitive",
"invaluable",
"inventive",
"invigorating",
"invincible",
"inviolable",
"inviolate",
"invulnerable",
"irreplaceable",
"irreproachable",
"irresistible",
"issue_free",
"jaw_dropping",
"jolly",
"jovial",
"joyful",
"joyous",
"jubilant",
"judicious",
"keen",
"kid_friendly",
"kindly",
"knowledgeable",
"large_capacity",
"laudable",
"lavish",
"law_abiding",
"lawful",
"leading",
"lean",
"legendary",
"light_hearted",
"lighter",
"likable",
"like",
"liked",
"lionhearted",
"lively",
"logical",
"long_lasting",
"lovable",
"loved",
"lovely",
"loving",
"low_cost",
"low_price",
"low_priced",
"low_risk",
"lower_priced",
"loyal",
"lucid",
"luckier",
"luckiest",
"lucky",
"lucrative",
"luminous",
"lush",
"lustrous",
"luxuriant",
"luxurious",
"luxury",
"lyrical",
"magic",
"magical",
"magnanimous",
"magnificent",
"majestic",
"manageable",
"maneuverable",
"marvellous",
"marvelous",
"masterful",
"matchless",
"mature",
"meaningful",
"memorable",
"merciful",
"meritorious",
"merry",
"mesmerized",
"mesmerizing",
"meticulous",
"mighty",
"mind_blowing",
"miraculous",
"modern",
"modest",
"momentous",
"monumental",
"motivated",
"multi_purpose",
"navigable",
"neat",
"neatest",
"nice",
"nicer",
"nicest",
"nifty",
"nimble",
"noble",
"noiseless",
"non_violent",
"noteworthy",
"nourishing",
"observant",
"obtainable",
"optimal",
"optimistic",
"opulent",
"orderly",
"organized",
"outstanding",
"overjoyed",
"pain_free",
"painless",
"palatial",
"pampered",
"panoramic",
"paramount",
"passionate",
"patient",
"patri",
"otic",
"peaceable",
"peaceful",
"peerless",
"peppy",
"perfect",
"permissible",
"personalized",
"phenomenal",
"picturesque",
"playful",
"pleasant",
"pleased",
"pleasing",
"pleasurable",
"plentiful",
"plush",
"poetic",
"poignant",
"poised",
"polished",
"polite",
"popular",
"portable",
"posh",
"positive",
"powerful",
"praiseworthy",
"praising",
"pre_eminent",
"precious",
"precise",
"preeminent",
"preferable",
"premier",
"prestigious",
"pretty",
"priceless",
"principled",
"privileged",
"prize",
"proactive",
"problem_free",
"prodigious",
"productive",
"proficient",
"profound",
"profuse",
"progressive",
"prolific",
"prominent",
"promised",
"promising",
"prompt",
"proper",
"propitious",
"prosperous",
"protective",
"proud",
"proven",
"prudent",
"punctual",
"pure",
"purposeful",
"quaint",
"qualified",
"quicker",
"quiet",
"quieter",
"radiant",
"rapid",
"rapt",
"rapturous",
"rational",
"razor_sharp",
"reachable",
"readable",
"ready",
"realistic",
"realizable",
"reasonable",
"reasoned",
"receptive",
"recommended",
"record_setting",
"redeeming",
"refined",
"reformed",
"refreshed",
"refreshing",
"regal",
"rejoicing",
"relaxed",
"reliable",
"remarkable",
"renewed",
"renowned",
"replaceable",
"reputable",
"resilient",
"resolute",
"resounding",
"resourceful",
"respectable",
"respectful",
"resplendent",
"responsive",
"restful",
"restored",
"retractable",
"reverent",
"revolutionary",
"rewarding",
"rich",
"richer",
"right",
"righteous",
"rightful",
"risk_free",
"robust",
"rock_star",
"romantic",
"roomier",
"roomy",
"rosy",
"safe",
"saintly",
"salutary",
"sane",
"satisfactory",
"satisfied",
"satisfying",
"scenic",
"seamless",
"seasoned",
"secure",
"selective",
"self_determination",
"self_respect",
"self_satisfaction",
"self_sufficiency",
"self_sufficient",
"sensational",
"sensible",
"sensitive",
"serene",
"sexy",
"sharp",
"sharper",
"sharpest",
"shiny",
"significant",
"silent",
"simpler",
"simplest",
"simplified",
"sincere",
"skilled",
"skillful",
"sleek",
"slick",
"smart",
"smarter",
"smartest",
"smiling",
"smitten",
"smooth",
"smoother",
"smoothest",
"snappy",
"snazzy",
"sociable",
"soft",
"softer",
"solicitous",
"solid",
"sophisticated",
"soulful",
"spacious",
"sparkling",
"spectacular",
"speedy",
"spellbinding",
"spellbound",
"spirited",
"spiritual",
"splendid",
"spontaneous",
"sporty",
"spotless",
"sprightly",
"stable",
"stainless",
"state_of_the_art",
"stately",
"statuesque",
"staunch",
"steadfast",
"steadiest",
"steady",
"stellar",
"stimulating",
"stimulative",
"straightforward",
"streamlined",
"striking",
"strong",
"stronger",
"strongest",
"stunned",
"stunning",
"stupendous",
"sturdier",
"sturdy",
"stylish",
"stylized",
"suave",
"sublime",
"subsidized",
"substantive",
"succeeding",
"successful",
"sufficient",
"suitable",
"sumptuous",
"super",
"superb",
"superior",
"supple",
"supported",
"supporting",
"supportive",
"supreme",
"surreal",
"sustainable",
"swank",
"swankier",
"swankiest",
"swanky",
"sweeping",
"sweet",
"sweetheart",
"swift",
"talented",
"tantalizing",
"tempting",
"tenacious",
"tender",
"terrific",
"thankful",
"thinner",
"thoughtful",
"thrifty",
"thrilled",
"thrilling",
"thriving",
"thumb_up",
"thumbs_up",
"tidy",
"time_honored",
"timely",
"titillating",
"tolerable",
"toll_free",
"top",
"top_notch",
"top_quality",
"topnotch",
"tops",
"tough",
"tougher",
"toughest",
"tranquil",
"transparent",
"trendy",
"triumphal",
"triumphant",
"trouble_free",
"trusted",
"trusting",
"trustworthy",
"trusty",
"truthful",
"twinkly",
"ultra_crisp",
"unabashed",
"unaffected",
"unassailable",
"unbeatable",
"unbiased",
"unbound",
"uncomplicated",
"unconditional",
"undamaged",
"undaunted",
"understandable",
"undisputable",
"undisputed",
"unencumbered",
"unequivocal",
"unfettered",
"unforgettable",
"unlimited",
"unmatched",
"unparalleled",
"unquestionable",
"unreal",
"unrestricted",
"unrivaled",
"unselfish",
"unwavering",
"upbeat",
"upscale",
"usable",
"useable",
"useful",
"user_friendly",
"user_replaceable",
"valiant",
"valuable",
"verifiable",
"veritable",
"versatile",
"vibrant",
"victorious",
"viewable",
"vigilant",
"virtuous",
"visionary",
"vivacious",
"vivid",
"warm",
"warmer",
"warmhearted",
"wealthy",
"welcome",
"well",
"well_backlit",
"well_balanced",
"well_behaved",
"well_being",
"well_bred",
"well_connected",
"well_educated",
"well_established",
"well_informed",
"well_intentioned",
"well_known",
"well_made",
"well_managed",
"well_mannered",
"well_positioned",
"well_received",
"well_regarded",
"well_rounded",
"well_run",
"well_wishers",
"wholesome",
"wieldy",
"willing",
"winning",
"wise",
"witty",
"won",
"wonderful",
"wondrous",
"workable",
"world_famous",
"worth",
"worthwhile",
"worthy",
"young",
"youthful",
"zippy"]
export function random_adj() { return adjectives[Math.floor(Math.random() * adjectives.length)] }

View File

@@ -98,7 +98,7 @@ export async function main(
export const MYSQL_INIT_CODE = `import {
mySql,
type Resource
} from "https://deno.land/x/windmill@v${__pkg__.version}/mod.ts";
} from "https://deno.land/x/windmill@v${__pkg__.version}/mysql.ts";
// MySQL parameterized statement. No SQL injection is possible.
export async function main(