feat: add yaml for import/export/diff (#2631)

* feat: add yaml for import/export/diff

* fix: speeeeeeed
This commit is contained in:
HugoCasa
2023-11-15 18:44:16 +01:00
committed by GitHub
parent 9c684dac24
commit 34abe01c2f
13 changed files with 243 additions and 100 deletions
+40 -16
View File
@@ -27,6 +27,7 @@
"driver.js": "^1.3.0",
"esm-env": "^1.0.0",
"fast-equals": "^5.0.1",
"fsevents": "*",
"graphql": "^16.7.1",
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
@@ -52,6 +53,7 @@
"windmill-parser-wasm": "^1.202.1",
"y-monaco": "^0.1.4",
"y-websocket": "^1.5.0",
"yaml": "^2.3.4",
"yjs": "^13.6.7"
},
"devDependencies": {
@@ -109,6 +111,9 @@
"vite-plugin-monaco-editor": "^1.1.0",
"yootils": "^0.3.1"
},
"optionalDependencies": {
"fsevents": "^2.3.3"
},
"peerDependencies": {
"@sveltejs/kit": "^1.25.0",
"svelte": "^4.0.0"
@@ -1198,6 +1203,20 @@
"fsevents": "2.3.2"
}
},
"node_modules/@playwright/test/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/@polka/url": {
"version": "1.0.0-next.21",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
@@ -4332,10 +4351,9 @@
"dev": true
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true,
"optional": true,
"os": [
@@ -10226,10 +10244,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yaml": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
"integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==",
"dev": true,
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
"engines": {
"node": ">= 14"
}
@@ -11004,6 +11021,15 @@
"@types/node": "*",
"fsevents": "2.3.2",
"playwright-core": "1.34.3"
},
"dependencies": {
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
}
}
},
"@polka/url": {
@@ -13295,10 +13321,9 @@
"dev": true
},
"fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"optional": true
},
"function-bind": {
@@ -17372,10 +17397,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yaml": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
"integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==",
"dev": true
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA=="
},
"yargs-parser": {
"version": "20.2.9",
+6 -2
View File
@@ -39,6 +39,7 @@
"eslint": "^8.47.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-svelte": "^2.33.1",
"monaco-editor-workers": "~0.44.0",
"ol": "^7.4.0",
"openapi-typescript-codegen": "^0.25.0",
"path-browserify": "^1.0.1",
@@ -68,8 +69,7 @@
"vite": "^4.5.0",
"vite-plugin-circular-dependency": "^0.2.1",
"vite-plugin-monaco-editor": "^1.1.0",
"yootils": "^0.3.1",
"monaco-editor-workers": "~0.44.0"
"yootils": "^0.3.1"
},
"overrides": {
"@rgossiaux/svelte-headlessui": {
@@ -136,6 +136,7 @@
"windmill-parser-wasm": "^1.202.1",
"y-monaco": "^0.1.4",
"y-websocket": "^1.5.0",
"yaml": "^2.3.4",
"yjs": "^13.6.7"
},
"peerDependencies": {
@@ -390,5 +391,8 @@
"./package/gen/core/OpenAPI.d.ts"
]
}
},
"optionalDependencies": {
"fsevents": "^2.3.3"
}
}
@@ -6,7 +6,12 @@
import Tabs from './common/tabs/Tabs.svelte'
import Tab from './common/tabs/Tab.svelte'
import { cloneDeep } from 'lodash'
import { cleanValueProperties, orderedJsonStringify, type Value } from '$lib/utils'
import {
cleanValueProperties,
orderedJsonStringify,
orderedYamlStringify,
type Value
} from '$lib/utils'
import type { Script } from '$lib/gen'
type DiffData = {
@@ -60,7 +65,7 @@
return {
lang: data.language ? scriptLangToEditorLang(data.language as Script.language) : undefined,
content,
metadata: orderedJsonStringify(metadata, 2)
metadata: orderedYamlStringify(metadata)
}
}
@@ -217,7 +222,7 @@
<DiffEditor
automaticLayout
class="h-full"
defaultLang="json"
defaultLang="yaml"
defaultOriginal={metadata}
defaultModified={data.current.metadata}
readOnly
+37 -20
View File
@@ -13,6 +13,8 @@
type SvelvetSettingsContext
} from './graph/svelvet/container/models'
import { Clipboard } from 'lucide-svelte'
import YAML from 'yaml'
import { yaml } from 'svelte-highlight/languages'
setContext<SvelvetSettingsContext>(SVELVET_CONTEXT_KEY, {
fullHeight: true
@@ -36,9 +38,11 @@
export let noGraph: boolean = false
export let tab: 'ui' | 'json' | 'schema' = noGraph ? 'schema' : 'ui'
export let tab: 'ui' | 'raw' | 'schema' = noGraph ? 'schema' : 'ui'
export let noSummary = false
let rawType: 'json' | 'yaml' = 'yaml'
let open: { [id: number]: boolean } = {}
if (initialOpen) {
open[initialOpen] = true
@@ -53,7 +57,7 @@
{#if !noGraph}
<Tab value="ui">Graph</Tab>
{/if}
<Tab value="json">JSON</Tab>
<Tab value="raw">Raw</Tab>
<Tab value="schema">Input Schema</Tab>
<svelte:fragment slot="content">
@@ -91,24 +95,37 @@
<FlowGraphViewer download {noSide} {flow} overflowAuto />
</div>
</TabContent>
<TabContent value="json">
<div class="relative pt-2">
<Button
on:click={() => copyToClipboard(JSON.stringify(flowFiltered, null, 4))}
color="light"
variant="border"
size="xs"
startIcon={{ icon: Clipboard }}
btnClasses="absolute top-2 right-2 w-min"
>
Copy content
</Button>
<Highlight
language={json}
code={JSON.stringify(flowFiltered, null, 4)}
class="overflow-auto"
/>
</div>
<TabContent value="raw"
><Tabs bind:selected={rawType} wrapperClass="mt-4">
<Tab value="yaml">YAML</Tab>
<Tab value="json">JSON</Tab>
<svelte:fragment slot="content">
<div class="relative pt-2">
<Button
on:click={() =>
copyToClipboard(
rawType === 'yaml'
? YAML.stringify(flowFiltered)
: JSON.stringify(flowFiltered, null, 4)
)}
color="light"
variant="border"
size="xs"
startIcon={{ icon: Clipboard }}
btnClasses="absolute top-2 right-2 w-min"
>
Copy content
</Button>
<Highlight
class="overflow-auto px-1"
language={rawType === 'yaml' ? yaml : json}
code={rawType === 'yaml'
? YAML.stringify(flowFiltered)
: JSON.stringify(flowFiltered, null, 4)}
/>
</div>
</svelte:fragment>
</Tabs>
</TabContent>
<TabContent value="schema">
<div class="my-4" />
@@ -47,8 +47,8 @@
classNames,
cleanValueProperties,
copyToClipboard,
orderedJsonStringify,
truncateRev
truncateRev,
orderedJsonStringify
} from '../../../utils'
import type {
AppInput,
@@ -484,7 +484,7 @@
}
},
{
displayName: 'JSON',
displayName: 'Export',
icon: FileJson,
action: () => {
appExport.open($app)
@@ -9,11 +9,17 @@
import { Button } from '../../common'
import type { App } from '../types'
import { Clipboard } from 'lucide-svelte'
import { yaml } from 'svelte-highlight/languages'
import YAML from 'yaml'
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
import Tab from '$lib/components/common/tabs/Tab.svelte'
let jsonViewerDrawer: Drawer
let app: App | undefined = undefined
let rawType: 'json' | 'yaml' = 'yaml'
export function open(app_l: App) {
app = app_l
jsonViewerDrawer?.toggleDrawer()
@@ -21,19 +27,38 @@
</script>
<Drawer bind:this={jsonViewerDrawer} size="800px">
<DrawerContent title="App JSON" on:close={() => jsonViewerDrawer.toggleDrawer()}>
<div class="relative">
<Button
on:click={() => copyToClipboard(JSON.stringify(app, null, 4))}
color="dark"
variant="border"
size="sm"
startIcon={{ icon: Clipboard }}
btnClasses="absolute top-2 right-2 w-min"
>
Copy content
</Button>
<Highlight language={json} code={JSON.stringify(app ?? {}, null, 4)} />
</div>
</DrawerContent>
<DrawerContent title="App Export" on:close={() => jsonViewerDrawer.toggleDrawer()}>
<div>
<Tabs bind:selected={rawType}>
<Tab value="yaml">YAML</Tab>
<Tab value="json">JSON</Tab>
<svelte:fragment slot="content">
<div class="relative pt-2">
<Button
on:click={() =>
copyToClipboard(
rawType === 'yaml'
? YAML.stringify(app ?? {})
: JSON.stringify(app ?? {}, null, 4)
)}
color="dark"
variant="border"
size="sm"
startIcon={{ icon: Clipboard }}
btnClasses="absolute top-2 right-2 w-min"
>
Copy content
</Button>
<Highlight
class="overflow-auto px-1 flex-1"
language={rawType === 'yaml' ? yaml : json}
code={rawType === 'yaml'
? YAML.stringify(app ?? {})
: JSON.stringify(app ?? {}, null, 4)}
/>
</div>
</svelte:fragment>
</Tabs>
</div></DrawerContent
>
</Drawer>
@@ -3,8 +3,10 @@
import { copyToClipboard } from '$lib/utils'
import { CalendarCheck2, Clipboard, Terminal, Webhook } from 'lucide-svelte'
import { Highlight } from 'svelte-highlight'
import { yaml } from 'svelte-highlight/languages'
import json from 'svelte-highlight/languages/json'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import YAML from 'yaml'
let triggerSelected: 'webhooks' | 'schedule' | 'cli' = 'webhooks'
export let flow_json: any | undefined = undefined
@@ -13,6 +15,7 @@
export let isOperator: boolean = false
let selected = 'saved_inputs'
let rawType: 'json' | 'yaml' = 'yaml'
$: if (hasStepDetails) {
selected = 'flow_step'
@@ -30,7 +33,7 @@
<Tab value="details">Details & Triggers</Tab>
{/if}
{#if flow_json}
<Tab value="flow_json">JSON</Tab>
<Tab value="raw">Raw</Tab>
{/if}
{#if hasStepDetails}
<Tab value="flow_step">Step</Tab>
@@ -81,20 +84,37 @@
</Pane>
</Splitpanes>
</TabContent>
<TabContent value="flow_json" class="flex flex-col flex-1 h-full overflow-auto">
<div class="relative pt-2">
<Button
on:click={() => copyToClipboard(JSON.stringify(flow_json, null, 4))}
color="light"
variant="border"
size="xs"
startIcon={{ icon: Clipboard }}
btnClasses="absolute top-2 right-2 w-min"
>
Copy content
</Button>
<Highlight language={json} code={JSON.stringify(flow_json, null, 4)} />
</div>
<TabContent value="raw" class="flex flex-col flex-1 h-full overflow-auto">
<Tabs bind:selected={rawType} wrapperClass="mt-4">
<Tab value="yaml">YAML</Tab>
<Tab value="json">JSON</Tab>
<svelte:fragment slot="content">
<div class="relative pt-2">
<Button
on:click={() =>
copyToClipboard(
rawType === 'yaml'
? YAML.stringify(flow_json)
: JSON.stringify(flow_json, null, 4)
)}
color="light"
variant="border"
size="xs"
startIcon={{ icon: Clipboard }}
btnClasses="absolute top-2 right-2 w-min"
>
Copy content
</Button>
<Highlight
class="overflow-auto px-1"
language={rawType === 'yaml' ? yaml : json}
code={rawType === 'yaml'
? YAML.stringify(flow_json)
: JSON.stringify(flow_json, null, 4)}
/>
</div>
</svelte:fragment>
</Tabs>
</TabContent>
<TabContent value="flow_step" class="flex flex-col flex-1 h-full">
<slot name="flow_step" />
@@ -11,17 +11,20 @@
import { workspaceStore } from '$lib/stores'
import Path from '../Path.svelte'
import Tooltip from '../Tooltip.svelte'
import YAML from 'yaml'
let drawer: Drawer | undefined = undefined
let rawAppDrawer: Drawer | undefined = undefined
let pendingJson: string = ''
let pendingRaw: string = ''
let pendingCode: string = ''
let summary: string = ''
let path: string = ''
let pathError: string = ''
async function importJson() {
$importStore = JSON.parse(pendingJson)
let importType: 'yaml' | 'json' = 'yaml'
async function importRaw() {
$importStore = importType === 'yaml' ? YAML.parse(pendingRaw) : JSON.parse(pendingRaw)
await goto('/apps/add?nodraft=true')
drawer?.closeDrawer?.()
}
@@ -48,9 +51,19 @@
startIcon={{ icon: Plus }}
href="/apps/add?nodraft=true"
dropdownItems={[
{
label: 'Import low-code app from YAML',
onClick: () => {
drawer?.toggleDrawer?.()
importType = 'yaml'
}
},
{
label: 'Import low-code app from JSON',
onClick: () => drawer?.toggleDrawer?.()
onClick: () => {
drawer?.toggleDrawer?.()
importType = 'json'
}
},
{
label: 'Import app in React/Vue/Svelte',
@@ -66,10 +79,18 @@
<!-- Raw JSON -->
<Drawer bind:this={drawer} size="800px">
<DrawerContent title="Import low-code app from JSON" on:close={() => drawer?.toggleDrawer?.()}>
<SimpleEditor bind:code={pendingJson} lang="json" class="h-full" fixedOverflowWidgets={false} />
<DrawerContent
title={'Import low-code app from ' + (importType === 'yaml' ? 'YAML' : 'JSON')}
on:close={() => drawer?.toggleDrawer?.()}
>
<SimpleEditor
bind:code={pendingRaw}
lang={importType}
class="h-full"
fixedOverflowWidgets={false}
/>
<svelte:fragment slot="actions">
<Button size="sm" on:click={importJson}>Import</Button>
<Button size="sm" on:click={importRaw}>Import</Button>
</svelte:fragment>
</DrawerContent>
</Drawer>
@@ -8,12 +8,14 @@
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import { importFlowStore } from '$lib/components/flows/flowStore'
import { Plus } from 'lucide-svelte'
import YAML from 'yaml'
let drawer: Drawer | undefined = undefined
let pendingJson: string
let pendingRaw: string
let importType: 'yaml' | 'json' = 'yaml'
async function importJson() {
$importFlowStore = JSON.parse(pendingJson)
async function importRaw() {
$importFlowStore = importType === 'yaml' ? YAML.parse(pendingRaw) : JSON.parse(pendingRaw)
await goto('/flows/add')
drawer?.closeDrawer?.()
}
@@ -28,9 +30,19 @@
endIcon={{ icon: BarsStaggered }}
href="/flows/add?nodraft=true"
dropdownItems={[
{
label: 'Import from YAML',
onClick: () => {
drawer?.toggleDrawer?.()
importType = 'yaml'
}
},
{
label: 'Import from JSON',
onClick: () => drawer?.toggleDrawer?.()
onClick: () => {
drawer?.toggleDrawer?.()
importType = 'json'
}
}
]}
>
@@ -40,10 +52,18 @@
<!-- Raw JSON -->
<Drawer bind:this={drawer} size="800px">
<DrawerContent title="Import flow from JSON" on:close={() => drawer?.toggleDrawer?.()}>
<SimpleEditor bind:code={pendingJson} lang="json" class="h-full" fixedOverflowWidgets={false} />
<DrawerContent
title={'Import flow from ' + (importType === 'yaml' ? 'YAML' : 'JSON')}
on:close={() => drawer?.toggleDrawer?.()}
>
<SimpleEditor
bind:code={pendingRaw}
lang={importType}
class="h-full"
fixedOverflowWidgets={false}
/>
<svelte:fragment slot="actions">
<Button size="sm" on:click={importJson}>Import</Button>
<Button size="sm" on:click={importRaw}>Import</Button>
</svelte:fragment>
</DrawerContent>
</Drawer>
@@ -21,13 +21,13 @@
on:click={() => jsonViewerDrawer.toggleDrawer()}
startIcon={{ icon: FileJson }}
>
JSON
Export
</Button>
<Drawer bind:this={jsonViewerDrawer} size="800px">
<DrawerContent title="OpenFlow JSON" on:close={() => jsonViewerDrawer.toggleDrawer()}>
<DrawerContent title="OpenFlow" on:close={() => jsonViewerDrawer.toggleDrawer()}>
{#if $flowStore}
<FlowViewer flow={cleanInputs($flowStore)} tab="json" />
<FlowViewer flow={cleanInputs($flowStore)} tab="raw" />
{/if}
</DrawerContent>
</Drawer>
+7
View File
@@ -8,6 +8,7 @@
// import { get } from 'svelte/store'
import { deepEqual } from 'fast-equals'
import YAML from 'yaml'
import type { UserExt } from './stores'
import { sendUserToast } from './toast'
import type { Script } from './gen'
@@ -692,3 +693,9 @@ export function orderedJsonStringify(obj: any, space?: string | number) {
JSON.stringify(obj, (key, value) => (allKeys.add(key), value))
return JSON.stringify(obj, (Array.from(allKeys) as string[]).sort(), space)
}
export function orderedYamlStringify(obj: any) {
const allKeys = new Set()
YAML.stringify(obj, (key, value) => (allKeys.add(key), value))
return YAML.stringify(obj, (Array.from(allKeys) as string[]).sort())
}
@@ -16,7 +16,7 @@
const templatePath = $page.url.searchParams.get('template')
const templateId = $page.url.searchParams.get('template_id')
const importJson = $importStore
const importRaw = $importStore
if ($importStore) {
$importStore = undefined
}
@@ -50,14 +50,14 @@
loadApp()
async function loadApp() {
if (importJson) {
sendUserToast('Loaded from JSON')
if ('value' in importJson) {
summary = importJson.summary
value = importJson.value
policy = importJson.policy
if (importRaw) {
sendUserToast('Loaded from YAML/JSON')
if ('value' in importRaw) {
summary = importRaw.summary
value = importRaw.value
policy = importRaw.policy
} else {
value = importJson
value = importRaw
}
} else if (templatePath) {
const template = await AppService.getAppByPath({
@@ -56,7 +56,7 @@
if ($importFlowStore) {
flow = $importFlowStore
$importFlowStore = undefined
sendUserToast('Flow loaded from JSON')
sendUserToast('Flow loaded from YAML/JSON')
} else if (!templatePath && !hubId && state) {
sendUserToast('Flow restored from draft', false, [
{