windmill-components 1.22.0 → 1.22.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  <script>import { browser, dev } from '$app/env';
2
2
  import { page } from '$app/stores';
3
- import { buildExtraLib } from '../utils';
3
+ import { buildExtraLib, sendUserToast } from '../utils';
4
4
  import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
5
5
  import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
6
6
  import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
@@ -19,6 +19,7 @@ export let websocketAlive = { pyright: false, black: false, deno: false };
19
19
  export let extraLib = lang == 'typescript' && !deno ? buildExtraLib() : '';
20
20
  export let extraLibPath = 'file:///node_modules/@types/windmill/index.d.ts';
21
21
  let websockets = [];
22
+ let websocketInterval;
22
23
  let uri = '';
23
24
  let disposeMethod;
24
25
  const dispatch = createEventDispatcher();
@@ -129,8 +130,7 @@ export async function reloadWebsocket() {
129
130
  const writer = new WebSocketMessageWriter(socket);
130
131
  const languageClient = createLanguageClient({ reader, writer }, name, options);
131
132
  languageClient.start();
132
- socket.onClose((_code, _reason) => {
133
- websocketAlive[name] = false;
133
+ reader.onClose(() => {
134
134
  try {
135
135
  languageClient.stop();
136
136
  }
@@ -138,6 +138,9 @@ export async function reloadWebsocket() {
138
138
  console.error(err);
139
139
  }
140
140
  });
141
+ socket.onClose((_code, _reason) => {
142
+ websocketAlive[name] = false;
143
+ });
141
144
  if (name == 'deno') {
142
145
  vscode.commands.getCommands().then((v) => {
143
146
  if (!v.includes('deno.cache')) {
@@ -212,6 +215,15 @@ export async function reloadWebsocket() {
212
215
  }
213
216
  });
214
217
  }
218
+ websocketInterval && clearInterval(websocketInterval);
219
+ websocketInterval = setInterval(() => {
220
+ if (document.visibilityState == 'visible') {
221
+ if (!websocketAlive.black && !websocketAlive.deno && !websocketAlive.pyright) {
222
+ sendUserToast('Smart assistant got disconnected. Reconnecting to windmill language server for smart assistance');
223
+ reloadWebsocket();
224
+ }
225
+ }
226
+ }, 5000);
215
227
  }
216
228
  }
217
229
  function closeWebsockets() {
@@ -224,6 +236,7 @@ function closeWebsockets() {
224
236
  }
225
237
  }
226
238
  websockets = [];
239
+ websocketInterval && clearInterval(websocketInterval);
227
240
  }
228
241
  async function loadMonaco() {
229
242
  const monaco = await import('monaco-editor');
@@ -0,0 +1,204 @@
1
+ <script>import { ResourceService, ScriptService, VariableService } from '../gen';
2
+ import { getScriptByPath, loadHubScripts, sendUserToast } from '../utils';
3
+ import Icon from 'svelte-awesome';
4
+ import { faSearch } from '@fortawesome/free-solid-svg-icons';
5
+ import { workspaceStore, hubScripts } from '../stores';
6
+ import ItemPicker from './ItemPicker.svelte';
7
+ import VariableEditor from './VariableEditor.svelte';
8
+ import ResourceEditor from './ResourceEditor.svelte';
9
+ import { Highlight } from 'svelte-highlight';
10
+ import { python, typescript } from 'svelte-highlight/languages';
11
+ import github from 'svelte-highlight/styles/github';
12
+ import Modal from './Modal.svelte';
13
+ export let lang;
14
+ export let editor;
15
+ export let websocketAlive;
16
+ let variablePicker;
17
+ let resourcePicker;
18
+ let scriptPicker;
19
+ let variableEditor;
20
+ let resourceEditor;
21
+ let codeViewer;
22
+ let codeLang = 'deno';
23
+ let codeContent = '';
24
+ async function loadVariables() {
25
+ let r = [];
26
+ const variables = (await VariableService.listVariable({ workspace: $workspaceStore ?? 'NO_W' })).map((x) => {
27
+ return { name: x.path, ...x };
28
+ });
29
+ const rvariables = await VariableService.listContextualVariables({
30
+ workspace: $workspaceStore ?? 'NO_W'
31
+ });
32
+ r = r.concat(variables).concat(rvariables);
33
+ return r;
34
+ }
35
+ async function loadScripts() {
36
+ const workspaceScripts = await ScriptService.listScripts({
37
+ workspace: $workspaceStore ?? 'NO_W'
38
+ });
39
+ await loadHubScripts();
40
+ const hubScripts_ = $hubScripts ?? [];
41
+ return workspaceScripts.concat(hubScripts_);
42
+ }
43
+ </script>
44
+
45
+ <svelte:head>
46
+ {@html github}
47
+ </svelte:head>
48
+
49
+ <ItemPicker
50
+ bind:this={scriptPicker}
51
+ pickCallback={async (path, _) => {
52
+ const { language, content } = await getScriptByPath(path ?? '')
53
+ codeContent = content
54
+ codeLang = language
55
+ codeViewer.openModal()
56
+ }}
57
+ closeOnClick={false}
58
+ itemName="script"
59
+ extraField="summary"
60
+ loadItems={loadScripts}
61
+ />
62
+
63
+ <Modal bind:this={codeViewer}>
64
+ <div slot="title">Code</div>
65
+ <div slot="content">
66
+ {#if codeLang == 'python3'}
67
+ <Highlight language={python} code={codeContent} />
68
+ {:else if codeLang == 'deno'}
69
+ <Highlight language={typescript} code={codeContent} />
70
+ {/if}
71
+ </div></Modal
72
+ >
73
+
74
+ <ItemPicker
75
+ bind:this={variablePicker}
76
+ pickCallback={(path, name) => {
77
+ if (!path) {
78
+ if (lang == 'deno') {
79
+ editor.insertAtCursor(`Deno.env.get('${name}')`)
80
+ } else {
81
+ if (!editor.getCode().includes('import os')) {
82
+ editor.insertAtBeginning('import os\n')
83
+ }
84
+ editor.insertAtCursor(`os.environ.get("${name}")`)
85
+ }
86
+ sendUserToast(`${name} inserted at cursor`)
87
+ } else {
88
+ if (lang == 'deno') {
89
+ if (!editor.getCode().includes('import * as wmill from')) {
90
+ editor.insertAtBeginning(
91
+ `import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/index.ts'\n`
92
+ )
93
+ }
94
+ editor.insertAtCursor(`(await wmill.getVariable('${path}'))`)
95
+ } else {
96
+ if (!editor.getCode().includes('import wmill')) {
97
+ editor.insertAtBeginning('import wmill\n')
98
+ }
99
+ editor.insertAtCursor(`wmill.get_variable("${path}")`)
100
+ }
101
+ sendUserToast(`${name} inserted at cursor`)
102
+ }
103
+ }}
104
+ itemName="Variable"
105
+ extraField="name"
106
+ loadItems={loadVariables}
107
+ >
108
+ <div slot="submission" class="flex flex-row">
109
+ <div class="text-xs mr-2 align-middle">
110
+ The variable you were looking for does not exist yet?
111
+ </div>
112
+ <button
113
+ class="default-button-secondary"
114
+ type="button"
115
+ on:click={() => {
116
+ variableEditor.initNew()
117
+ }}
118
+ >
119
+ Create a new variable
120
+ </button>
121
+ </div>
122
+ </ItemPicker>
123
+
124
+ <ItemPicker
125
+ bind:this={resourcePicker}
126
+ pickCallback={(path, _) => {
127
+ if (lang == 'deno') {
128
+ if (!editor.getCode().includes('import * as wmill from')) {
129
+ editor.insertAtBeginning(
130
+ `import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/index.ts'\n`
131
+ )
132
+ }
133
+ editor.insertAtCursor(`(await wmill.getResource('${path}'))`)
134
+ } else {
135
+ if (!editor.getCode().includes('import wmill')) {
136
+ editor.insertAtBeginning('import wmill\n')
137
+ }
138
+ editor.insertAtCursor(`wmill.get_resource("${path}")`)
139
+ }
140
+ sendUserToast(`${path} inserted at cursor`)
141
+ }}
142
+ itemName="Resource"
143
+ extraField="resource_type"
144
+ loadItems={async () =>
145
+ await ResourceService.listResource({ workspace: $workspaceStore ?? 'NO_W' })}
146
+ >
147
+ <div slot="submission" class="flex flex-row">
148
+ <div class="text-xs mr-2 align-middle">
149
+ The resource you were looking for does not exist yet?
150
+ </div>
151
+ <button
152
+ class="default-button-secondary"
153
+ type="button"
154
+ on:click={() => {
155
+ resourceEditor.initNew()
156
+ }}
157
+ >
158
+ Create a new resource
159
+ </button>
160
+ </div>
161
+ </ItemPicker>
162
+
163
+ <ResourceEditor bind:this={resourceEditor} on:refresh={resourcePicker.openModal} />
164
+
165
+ <VariableEditor bind:this={variableEditor} on:create={variablePicker.openModal} />
166
+
167
+ <div class="flex flex-row justify-around w-full">
168
+ <button
169
+ class="default-button-secondary font-semibold py-px mr-2 text-xs align-middle max-h-8"
170
+ on:click|stopPropagation={() => {
171
+ variablePicker.openModal()
172
+ }}
173
+ >Variable picker <Icon data={faSearch} scale={0.7} />
174
+ </button>
175
+
176
+ <button
177
+ class="default-button-secondary font-semibold py-px text-xs mr-2 align-middle max-h-8"
178
+ on:click|stopPropagation={() => {
179
+ resourcePicker.openModal()
180
+ }}
181
+ >Resource picker <Icon data={faSearch} scale={0.7} />
182
+ </button>
183
+ <button
184
+ class="default-button-secondary font-semibold py-px text-xs mr-2 align-middle max-h-8"
185
+ on:click|stopPropagation={() => {
186
+ scriptPicker.openModal()
187
+ }}
188
+ >Script explorer <Icon data={faSearch} scale={0.7} />
189
+ </button>
190
+
191
+ <button
192
+ class="default-button-secondary py-px max-h-8 text-xs"
193
+ on:click|stopPropagation={() => {
194
+ editor.reloadWebsocket()
195
+ }}
196
+ >
197
+ Reload assistants (status: {#if lang == 'deno'}<span
198
+ class={websocketAlive.deno ? 'text-green-600' : 'text-red-600'}>deno</span
199
+ >{:else if lang == 'python3'}<span
200
+ class={websocketAlive.pyright ? 'text-green-600' : 'text-red-600'}>pyright</span
201
+ >
202
+ <span class={websocketAlive.black ? 'text-green-600' : 'text-red-600'}> black</span>{/if})
203
+ </button>
204
+ </div>
@@ -0,0 +1,23 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ import type Editor from './Editor.svelte';
3
+ declare const __propDef: {
4
+ props: {
5
+ lang: 'python3' | 'deno';
6
+ editor: Editor;
7
+ websocketAlive: {
8
+ pyright: boolean;
9
+ black: boolean;
10
+ deno: boolean;
11
+ };
12
+ };
13
+ events: {
14
+ [evt: string]: CustomEvent<any>;
15
+ };
16
+ slots: {};
17
+ };
18
+ export declare type EditorBarProps = typeof __propDef.props;
19
+ export declare type EditorBarEvents = typeof __propDef.events;
20
+ export declare type EditorBarSlots = typeof __propDef.slots;
21
+ export default class EditorBar extends SvelteComponentTyped<EditorBarProps, EditorBarEvents, EditorBarSlots> {
22
+ }
23
+ export {};
@@ -2,7 +2,7 @@
2
2
  import { page } from '$app/stores';
3
3
  import { FlowService, ScriptService } from '../gen';
4
4
  import { clearPreviewResults, hubScripts, workspaceStore } from '../stores';
5
- import { sendUserToast, setQueryWithoutLoad } from '../utils';
5
+ import { loadHubScripts, sendUserToast, setQueryWithoutLoad } from '../utils';
6
6
  import { faFileExport, faFileImport } from '@fortawesome/free-solid-svg-icons';
7
7
  import { onMount } from 'svelte';
8
8
  import Icon from 'svelte-awesome';
@@ -14,15 +14,6 @@ export let initialPath = '';
14
14
  let pathError = '';
15
15
  let mode;
16
16
  $: step = Number($page.url.searchParams.get('step')) || 1;
17
- async function loadSearchData() {
18
- const scripts = await ScriptService.listHubScripts();
19
- $hubScripts = scripts.map((x) => ({
20
- path: `hub/${x.id}/${x.summary.toLowerCase().replaceAll(/\s+/g, '_')}`,
21
- summary: `${x.summary} (${x.app})`,
22
- approved: x.approved,
23
- is_trigger: x.is_trigger
24
- }));
25
- }
26
17
  async function saveFlow() {
27
18
  const newFlow = flowToMode($flowStore, mode);
28
19
  if (initialPath === '') {
@@ -60,7 +51,7 @@ flowStore.subscribe((flow) => {
60
51
  setQueryWithoutLoad($page.url, 'state', btoa(JSON.stringify(flowToMode(flow, mode))));
61
52
  });
62
53
  onMount(() => {
63
- loadSearchData();
54
+ loadHubScripts();
64
55
  clearPreviewResults();
65
56
  });
66
57
  </script>
@@ -7,6 +7,8 @@ import { FlowModuleValue } from '../gen';
7
7
  import github from 'svelte-highlight/styles/github';
8
8
  import { slide } from 'svelte/transition';
9
9
  import Tabs from './Tabs.svelte';
10
+ import SchemaViewer from './SchemaViewer.svelte';
11
+ import FieldHeader from './FieldHeader.svelte';
10
12
  export let flow;
11
13
  let flowFiltered = {
12
14
  summary: flow.summary,
@@ -27,22 +29,48 @@ let open = {};
27
29
  <Tabs
28
30
  tabs={[
29
31
  ['ui', 'Flow rendered'],
30
- ['json', 'JSON']
32
+ ['json', 'JSON'],
33
+ ['schema', 'Input schema of the flow']
31
34
  ]}
32
35
  bind:tab
33
36
  />
34
37
  {/if}
35
38
  {#if tab == 'ui'}
36
39
  <div class="flow-root w-full p-4">
37
- <p class="font-black text-lg mb-6 w-full ml-2">
38
- <span>{flow.value.modules.length} Steps </span>
40
+ <p class="font-black text-lg w-full ml-2">
41
+ <span>Inputs</span>
42
+ </p>
43
+ {#if flow.schema && flow.schema.properties && Object.keys(flow.schema.properties).length > 0 && flow.schema}
44
+ <ul class="my-4 ml-6">
45
+ {#each Object.entries(flow.schema.properties) as [inp, v]}
46
+ <li class="list-disc flex flex-row">
47
+ <FieldHeader
48
+ label={inp}
49
+ required={flow.schema.required?.includes(inp)}
50
+ type={v?.type}
51
+ contentEncoding={v?.contentEncoding}
52
+ format={v?.format}
53
+ itemsType={v?.itemsType}
54
+ /><span class="ml-4 mt-2 text-xs"
55
+ >{v.default != undefined ? 'default: ' + JSON.stringify(v.default) : ''}</span
56
+ >
57
+ </li>
58
+ {/each}
59
+ </ul>
60
+ {:else}
61
+ <div class="text-gray-700 text-xs italic mb-4">
62
+ This script has no argument or is ill-defined
63
+ </div>
64
+ {/if}
65
+ <p class="font-black text-lg my-6 w-full ml-2">
66
+ <span>{flow?.value?.modules?.length} Steps </span>
39
67
  <span class="mt-4" />
40
68
  </p>
41
69
  <ul class="-mb-8 w-full">
42
- {#each flow.value.modules ?? [] as mod, i}
70
+ {#each flow?.value?.modules ?? [] as mod, i}
43
71
  <li class="w-full">
44
72
  <div class="relative pb-8 w-full">
45
- {#if i < (flow.value.modules ?? []).length - 1}
73
+ {#if i < (flow?.value?.modules ?? []).length - 1}
46
74
  <span
47
75
  class="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200"
48
76
  aria-hidden="true"
@@ -58,33 +86,33 @@ let open = {};
58
86
  <div class="min-w-0 flex-1 pt-1.5 flex justify-between space-x-4 w-full">
59
87
  <div class="w-full">
60
88
  <p class="text-sm text-gray-500">
61
- {#if mod.value.type == FlowModuleValue.type.SCRIPT}
89
+ {#if mod?.value?.type == FlowModuleValue.type.SCRIPT}
62
90
  Script at path <a
63
91
  target="_blank"
64
- href={scriptPathToHref(mod.value.path ?? '')}
65
- class="font-medium text-gray-900">{mod.value.path}</a
92
+ href={scriptPathToHref(mod?.value?.path ?? '')}
93
+ class="font-medium text-gray-900">{mod?.value?.path}</a
66
94
  >
67
- {:else if mod.value.type == FlowModuleValue.type.RAWSCRIPT}
95
+ {:else if mod?.value?.type == FlowModuleValue.type.RAWSCRIPT}
68
96
  <button
69
97
  on:click={() => (open[i] = !open[i])}
70
98
  class="mb-2 underline text-black"
71
99
  >
72
- Raw {mod.value.language} script {open[i] ? '(-)' : '(+)'}</button
100
+ Raw {mod?.value?.language} script {open[i] ? '(-)' : '(+)'}</button
73
101
  >
74
102
 
75
103
  {#if open[i]}
76
104
  <div transition:slide class="border border-black p-2 bg-gray-50 w-full">
77
105
  <Highlight
78
- language={mod.value.language == 'deno' ? typescript : python}
79
- code={mod.value.content}
106
+ language={mod?.value?.language == 'deno' ? typescript : python}
107
+ code={mod?.value?.content}
80
108
  />
81
109
  </div>
82
110
  {/if}
83
- {:else if mod.value.type == FlowModuleValue.type.FLOW}
84
- Flow at path {mod.value.path}
85
- {:else if mod.value.type == FlowModuleValue.type.FORLOOPFLOW}
111
+ {:else if mod?.value?.type == FlowModuleValue.type.FLOW}
112
+ Flow at path {mod?.value?.path}
113
+ {:else if mod?.value?.type == FlowModuleValue.type.FORLOOPFLOW}
86
114
  For loop over step {i}'s result':
87
- <svelte:self flow={mod.value} embedded={true} />
115
+ <svelte:self flow={mod?.value} embedded={true} />
88
116
  {/if}
89
117
  </p>
90
118
  </div>
@@ -95,6 +123,14 @@ let open = {};
95
123
  {/each}
96
124
  </ul>
97
125
  </div>
98
- {:else}
126
+ {:else if tab == 'json'}
127
+ <button
128
+ on:click={async () => {
129
+ await navigator.clipboard.writeText(JSON.stringify(flowFiltered, null, 4))
130
+ }}
131
+ class="default-primary-button-v2 float-right my-2 ml-4">copy content</button
132
+ >
99
133
  <Highlight language={json} code={JSON.stringify(flowFiltered, null, 4)} />
134
+ {:else if tab == 'schema'}
135
+ <SchemaViewer schema={flow.schema} />
100
136
  {/if}
@@ -9,7 +9,7 @@ declare const __propDef: {
9
9
  schema?: any;
10
10
  };
11
11
  embedded?: boolean | undefined;
12
- tab?: "json" | "ui" | undefined;
12
+ tab?: "json" | "schema" | "ui" | undefined;
13
13
  };
14
14
  events: {
15
15
  [evt: string]: CustomEvent<any>;
@@ -4,6 +4,7 @@ import { buildExtraLib, objectToTsType, schemaToObject, schemaToTsType } from '.
4
4
  import { faRobot } from '@fortawesome/free-solid-svg-icons';
5
5
  import Icon from 'svelte-awesome';
6
6
  import Editor from './Editor.svelte';
7
+ import EditorBar from './EditorBar.svelte';
7
8
  import FlowPreview from './FlowPreview.svelte';
8
9
  import FlowInputs from './flows/FlowInputs.svelte';
9
10
  import FlowModuleHeader from './flows/FlowModuleHeader.svelte';
@@ -15,6 +16,8 @@ export let mode;
15
16
  export let i;
16
17
  export let mod;
17
18
  export let args = {};
19
+ let editor;
20
+ let websocketAlive = { pyright: false, black: false, deno: false };
18
21
  $: schema = $schemasStore[i];
19
22
  $: shouldPick = mod.value.path === '' && mod.value.language === undefined;
20
23
  $: previousSchema = i === 0 ? schemaToObject($flowStore?.schema) : $previewResults[i];
@@ -66,7 +69,12 @@ $: extraLib = buildExtraLib(i === 0 ? schemaToTsType($flowStore?.schema) : objec
66
69
  />
67
70
  {/if}
68
71
  {#if mod.value.type === FlowModuleValue.type.RAWSCRIPT}
72
+ <div class="p-1 overflow-hidden">
73
+ <EditorBar {editor} {websocketAlive} lang={mod.value.language ?? 'deno'} />
74
+ </div>
69
75
  <Editor
76
+ bind:websocketAlive
77
+ bind:this={editor}
70
78
  class="h-80 border p-2 rounded"
71
79
  bind:code={mod.value.content}
72
80
  deno={mod.value.language === FlowModuleValue.language.DENO}
@@ -4,7 +4,6 @@ import { GroupService } from '../gen';
4
4
  import Tooltip from './Tooltip.svelte';
5
5
  import { userStore, workspaceStore } from '../stores';
6
6
  import { sleep } from '../utils';
7
- import { workspace } from 'vscode';
8
7
  export let meta = {
9
8
  ownerKind: 'user',
10
9
  owner: '',
@@ -52,8 +52,8 @@ let scheduledForStr;
52
52
  {#if runnable?.schema}
53
53
  {#if detailed}
54
54
  <h2>
55
- Arguments<Tooltip class="px-2 mb-6"
56
- >Optioal fields, if left blank, will use the placeholder value as default.</Tooltip
55
+ Arguments <Tooltip
56
+ >The optional fields, if left blank, will use the placeholder value as default.</Tooltip
57
57
  >
58
58
  </h2>
59
59
  {/if}
@@ -67,7 +67,7 @@ let viewJsonSchema = false;
67
67
  </TableCustom>
68
68
  </div>
69
69
  {:else}
70
- <div class="text-gray-700 text-xs italic">This script has no argument</div>
70
+ <div class="text-gray-700 text-xs italic">This script has no arguments</div>
71
71
  {/if}
72
72
  </div>
73
73
  <div class={viewJsonSchema ? '' : 'hidden'}>
@@ -1,8 +1,8 @@
1
- <script>import { JobService, Job, CompletedJob, VariableService, ResourceService, ScriptService } from '../gen';
2
- import { sendUserToast, emptySchema, displayDate } from '../utils';
1
+ <script>import { JobService, Job, CompletedJob, VariableService, ScriptService } from '../gen';
2
+ import { emptySchema, displayDate } from '../utils';
3
3
  import { fade } from 'svelte/transition';
4
4
  import Icon from 'svelte-awesome';
5
- import { faCheck, faChevronDown, faChevronUp, faExclamationTriangle, faMagic, faSearch, faSpinner, faTimes } from '@fortawesome/free-solid-svg-icons';
5
+ import { faCheck, faChevronDown, faChevronUp, faExclamationTriangle, faMagic, faSpinner, faTimes } from '@fortawesome/free-solid-svg-icons';
6
6
  import Editor from './Editor.svelte';
7
7
  import Tooltip from './Tooltip.svelte';
8
8
  import { onDestroy, onMount } from 'svelte';
@@ -10,17 +10,14 @@ import { userStore, workspaceStore } from '../stores';
10
10
  import TableCustom from './TableCustom.svelte';
11
11
  import { check } from 'svelte-awesome/icons';
12
12
  import Modal from './Modal.svelte';
13
- import { Highlight } from 'svelte-highlight';
14
- import { json, python, typescript } from 'svelte-highlight/languages';
15
- import github from 'svelte-highlight/styles/github';
16
- import ItemPicker from './ItemPicker.svelte';
17
- import VariableEditor from './VariableEditor.svelte';
18
- import ResourceEditor from './ResourceEditor.svelte';
19
13
  import { inferArgs } from '../infer';
20
14
  // @ts-ignore
21
15
  import { VSplitPane } from 'svelte-split-pane';
22
16
  import SchemaForm from './SchemaForm.svelte';
23
17
  import DisplayResult from './DisplayResult.svelte';
18
+ import EditorBar from './EditorBar.svelte';
19
+ import { Highlight } from 'svelte-highlight';
20
+ import { json, python, typescript } from 'svelte-highlight/languages';
24
21
  // Exported
25
22
  export let schema = emptySchema();
26
23
  export let code;
@@ -30,6 +27,9 @@ export let lang;
30
27
  export let viewPreview = true;
31
28
  export let previewTab = 'logs';
32
29
  let websocketAlive = { pyright: false, black: false, deno: false };
30
+ let modalViewerTitle = '';
31
+ let modalViewerContent;
32
+ let modalViewerMode = 'logs';
33
33
  // Internal state
34
34
  let editor;
35
35
  // Preview args input
@@ -41,14 +41,6 @@ let previewIntervalId;
41
41
  let previewJob;
42
42
  let pastPreviews = [];
43
43
  let modalViewer;
44
- let modalViewerTitle = '';
45
- let modalViewerContent;
46
- let modalViewerMode = 'logs';
47
- let variablePicker;
48
- let resourcePicker;
49
- let scriptPicker;
50
- let variableEditor;
51
- let resourceEditor;
52
44
  let syncIteration = 0;
53
45
  let ITERATIONS_BEFORE_SLOW_REFRESH = 100;
54
46
  let lastSave;
@@ -158,20 +150,6 @@ function syncer(id) {
158
150
  loadPreviewJob(id);
159
151
  }
160
152
  }
161
- async function loadVariables() {
162
- let r = [];
163
- const variables = (await VariableService.listVariable({ workspace: $workspaceStore ?? 'NO_W' })).map((x) => {
164
- return { name: x.path, ...x };
165
- });
166
- const rvariables = await VariableService.listContextualVariables({
167
- workspace: $workspaceStore ?? 'NO_W'
168
- });
169
- r = r.concat(variables).concat(rvariables);
170
- return r;
171
- }
172
- async function loadScripts() {
173
- return await ScriptService.listScripts({ workspace: $workspaceStore ?? 'NO_W' });
174
- }
175
153
  let syncCode;
176
154
  onMount(() => {
177
155
  syncCode = setInterval(() => {
@@ -194,29 +172,6 @@ onDestroy(() => {
194
172
  });
195
173
  </script>
196
174
 
197
- <svelte:head>
198
- {@html github}
199
- </svelte:head>
200
-
201
- <ItemPicker
202
- bind:this={scriptPicker}
203
- pickCallback={async (path, _) => {
204
- modalViewerMode = 'code'
205
- modalViewerTitle = 'Script ' + path
206
- modalViewerContent = (
207
- await ScriptService.getScriptByPath({
208
- workspace: $workspaceStore ?? '',
209
- path
210
- })
211
- ).content
212
- modalViewer.openModal()
213
- }}
214
- closeOnClick={false}
215
- itemName="script"
216
- extraField="summary"
217
- loadItems={loadScripts}
218
- />
219
-
220
175
  <Modal bind:this={modalViewer}>
221
176
  <div slot="title">{modalViewerTitle}</div>
222
177
  <div slot="content">
@@ -236,101 +191,7 @@ onDestroy(() => {
236
191
  </div></Modal
237
192
  >
238
193
 
239
- <ItemPicker
240
- bind:this={variablePicker}
241
- pickCallback={(path, name) => {
242
- if (!path) {
243
- if (lang == 'deno') {
244
- getEditor().insertAtCursor(`Deno.env.get('${name}')`)
245
- } else {
246
- if (!getEditor().getCode().includes('import os')) {
247
- getEditor().insertAtBeginning('import os\n')
248
- }
249
- getEditor().insertAtCursor(`os.environ.get("${name}")`)
250
- }
251
- sendUserToast(`${name} inserted at cursor`)
252
- } else {
253
- if (lang == 'deno') {
254
- if (!getEditor().getCode().includes('import * as wmill from')) {
255
- getEditor().insertAtBeginning(
256
- `import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/index.ts'\n`
257
- )
258
- }
259
- getEditor().insertAtCursor(`(await wmill.getVariable('${path}'))`)
260
- } else {
261
- if (!getEditor().getCode().includes('import wmill')) {
262
- getEditor().insertAtBeginning('import wmill\n')
263
- }
264
- getEditor().insertAtCursor(`wmill.get_variable("${path}")`)
265
- }
266
- sendUserToast(`${name} inserted at cursor`)
267
- }
268
- }}
269
- itemName="Variable"
270
- extraField="name"
271
- loadItems={loadVariables}
272
- >
273
- <div slot="submission" class="flex flex-row">
274
- <div class="text-xs mr-2 align-middle">
275
- The variable you were looking for does not exist yet?
276
- </div>
277
- <button
278
- class="default-button-secondary"
279
- type="button"
280
- on:click={() => {
281
- variableEditor.initNew()
282
- }}
283
- >
284
- Create a new variable
285
- </button>
286
- </div>
287
- </ItemPicker>
288
-
289
- <ItemPicker
290
- bind:this={resourcePicker}
291
- pickCallback={(path, _) => {
292
- if (lang == 'deno') {
293
- if (!getEditor().getCode().includes('import * as wmill from')) {
294
- getEditor().insertAtBeginning(
295
- `import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/index.ts'\n`
296
- )
297
- }
298
- getEditor().insertAtCursor(`(await wmill.getResource('${path}'))`)
299
- } else {
300
- if (!getEditor().getCode().includes('import wmill')) {
301
- getEditor().insertAtBeginning('import wmill\n')
302
- }
303
- getEditor().insertAtCursor(`wmill.get_resource("${path}")`)
304
- }
305
- sendUserToast(`${path} inserted at cursor`)
306
- }}
307
- itemName="Resource"
308
- extraField="resource_type"
309
- loadItems={async () =>
310
- await ResourceService.listResource({ workspace: $workspaceStore ?? 'NO_W' })}
311
- >
312
- <div slot="submission" class="flex flex-row">
313
- <div class="text-xs mr-2 align-middle">
314
- The resource you were looking for does not exist yet?
315
- </div>
316
- <button
317
- class="default-button-secondary"
318
- type="button"
319
- on:click={() => {
320
- resourceEditor.initNew()
321
- }}
322
- >
323
- Create a new resource
324
- </button>
325
- </div>
326
- </ItemPicker>
327
-
328
- <ResourceEditor bind:this={resourceEditor} on:refresh={resourcePicker.openModal} />
329
-
330
- <VariableEditor bind:this={variableEditor} on:create={variablePicker.openModal} />
331
-
332
194
  <VSplitPane
333
- class="h-full"
334
195
  topPanelSize={viewPreview ? '75%' : '90%'}
335
196
  downPanelSize={viewPreview ? '25%' : '10%'}
336
197
  updateCallback={() => {
@@ -342,46 +203,7 @@ onDestroy(() => {
342
203
  <top slot="top">
343
204
  <div class="flex flex-col h-full">
344
205
  <div class="header">
345
- <div class="flex flex-row justify-around w-full">
346
- <button
347
- class="default-button-secondary font-semibold py-px mr-2 text-xs align-middle max-h-8"
348
- on:click|stopPropagation={() => {
349
- variablePicker.openModal()
350
- }}
351
- >Variable picker <Icon data={faSearch} scale={0.7} />
352
- </button>
353
-
354
- <button
355
- class="default-button-secondary font-semibold py-px text-xs mr-2 align-middle max-h-8"
356
- on:click|stopPropagation={() => {
357
- resourcePicker.openModal()
358
- }}
359
- >Resource picker <Icon data={faSearch} scale={0.7} />
360
- </button>
361
- <button
362
- class="default-button-secondary font-semibold py-px text-xs mr-2 align-middle max-h-8"
363
- on:click|stopPropagation={() => {
364
- scriptPicker.openModal()
365
- }}
366
- >Script explorer <Icon data={faSearch} scale={0.7} />
367
- </button>
368
-
369
- <button
370
- class="default-button-secondary py-px max-h-8 text-xs"
371
- on:click|stopPropagation={() => {
372
- editor.reloadWebsocket()
373
- }}
374
- >
375
- Reload assistants (status: {#if lang == 'deno'}<span
376
- class={websocketAlive.deno ? 'text-green-600' : 'text-red-600'}>deno</span
377
- >{:else if lang == 'python3'}<span
378
- class={websocketAlive.pyright ? 'text-green-600' : 'text-red-600'}>pyright</span
379
- >
380
- <span class={websocketAlive.black ? 'text-green-600' : 'text-red-600'}>
381
- black</span
382
- >{/if})
383
- </button>
384
- </div>
206
+ <EditorBar {editor} {lang} {websocketAlive} />
385
207
  </div>
386
208
  <div class="flex-1 overflow-hidden">
387
209
  <Editor
@@ -395,6 +217,7 @@ onDestroy(() => {
395
217
  formatAction={() => {
396
218
  code = getEditor().getCode()
397
219
  localStorage.setItem(path ?? 'last_save', code)
220
+ lastSave = code
398
221
  }}
399
222
  class="h-full"
400
223
  deno={lang == 'deno'}
@@ -9,6 +9,7 @@ import Modal from './Modal.svelte';
9
9
  import { Highlight } from 'svelte-highlight';
10
10
  import { python, typescript } from 'svelte-highlight/languages';
11
11
  import github from 'svelte-highlight/styles/github';
12
+ import { getScript, getScriptByPath } from '../utils';
12
13
  export let scriptPath = undefined;
13
14
  export let allowFlow = false;
14
15
  export let allowHub = false;
@@ -23,20 +24,6 @@ let options = [['Script', 'script']];
23
24
  allowHub && options.unshift(['Hub', 'hub']);
24
25
  allowFlow && options.push(['Flow', 'flow']);
25
26
  const dispatch = createEventDispatcher();
26
- async function getScript() {
27
- if (itemKind == 'hub') {
28
- code = await ScriptService.getHubScriptContentByPath({ path: scriptPath });
29
- lang = Script.language.DENO;
30
- }
31
- else {
32
- const script = await ScriptService.getScriptByPath({
33
- workspace: $workspaceStore,
34
- path: scriptPath
35
- });
36
- code = script.content;
37
- lang = script.language;
38
- }
39
- }
40
27
  async function loadItems() {
41
28
  if (itemKind == 'flow') {
42
29
  items = await FlowService.listFlows({ workspace: $workspaceStore });
@@ -86,7 +73,9 @@ $: {
86
73
  <button
87
74
  class="text-xs text-blue-500"
88
75
  on:click={async () => {
89
- await getScript()
76
+ const { language, content } = await getScriptByPath(scriptPath ?? '')
77
+ code = content
78
+ lang = language
90
79
  modalViewer.openModal()
91
80
  }}>show code</button
92
81
  >
@@ -1,4 +1,5 @@
1
1
  <script>import { FlowModuleValue, Script } from '../../gen';
2
+ import { getScriptByPath } from '../../utils';
2
3
  import { faCode, faCodeBranch, faSave, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
3
4
  import Icon from 'svelte-awesome';
4
5
  import { Highlight } from 'svelte-highlight';
@@ -6,14 +7,14 @@ import { python, typescript } from 'svelte-highlight/languages';
6
7
  import github from 'svelte-highlight/styles/github';
7
8
  import Modal from '../Modal.svelte';
8
9
  import { createScriptFromInlineScript, fork, removeModule } from './flowStore';
9
- import { getScriptByPath, scrollIntoView } from './utils';
10
+ import { scrollIntoView } from './utils';
10
11
  export let open;
11
12
  export let i;
12
13
  export let shouldPick = false;
13
14
  export let mod;
14
15
  let modalViewer;
15
16
  let modalViewerContent = '';
16
- let modalViewerLanguage = Script.language.DENO;
17
+ let modalViewerLanguage = 'deno';
17
18
  async function viewCode() {
18
19
  const { content, language } = await getScriptByPath(mod.value.path);
19
20
  modalViewerContent = content;
@@ -6,10 +6,6 @@ export declare function flattenForloopFlows(flow: Flow): Flow;
6
6
  export declare function getTypeAsString(arg: any): string;
7
7
  export declare function formatValue(arg: any): any;
8
8
  export declare function getFirstStepSchema(flow: Flow): Promise<Schema>;
9
- export declare function getScriptByPath(path: string): Promise<{
10
- content: string;
11
- language: FlowModuleValue.language;
12
- }>;
13
9
  export declare function createInlineScriptModuleFromPath(path: string): Promise<FlowModuleValue>;
14
10
  export declare function scrollIntoView(el: any): void;
15
11
  export declare function loadSchemaFromModule(module: FlowModule): Promise<{
@@ -1,9 +1,7 @@
1
1
  import { FlowModuleValue, InputTransform, ScriptService } from '../../gen';
2
2
  import { inferArgs } from '../../infer';
3
3
  import { loadSchema } from '../../scripts';
4
- import { workspaceStore } from '../../stores';
5
- import { emptySchema } from '../../utils';
6
- import { get } from 'svelte/store';
4
+ import { emptySchema, getScriptByPath } from '../../utils';
7
5
  export function flowToMode(flow, mode) {
8
6
  if (mode == 'pull') {
9
7
  const newFlow = JSON.parse(JSON.stringify(flow));
@@ -66,25 +64,6 @@ export async function getFirstStepSchema(flow) {
66
64
  }
67
65
  return emptySchema();
68
66
  }
69
- export async function getScriptByPath(path) {
70
- if (path.startsWith('hub/')) {
71
- const content = await ScriptService.getHubScriptContentByPath({ path });
72
- return {
73
- content,
74
- language: FlowModuleValue.language.DENO
75
- };
76
- }
77
- else {
78
- const script = await ScriptService.getScriptByPath({
79
- workspace: get(workspaceStore),
80
- path: path ?? ''
81
- });
82
- return {
83
- content: script.content,
84
- language: script.language
85
- };
86
- }
87
- }
88
67
  export async function createInlineScriptModuleFromPath(path) {
89
68
  const { content, language } = await getScriptByPath(path);
90
69
  return {
@@ -121,7 +100,7 @@ export async function loadSchemaFromModule(module) {
121
100
  input_transform = keys.reduce((accu, key) => {
122
101
  accu[key] = {
123
102
  type: 'static',
124
- value: ''
103
+ value: undefined
125
104
  };
126
105
  return accu;
127
106
  }, {});
@@ -1,6 +1,6 @@
1
1
  export const OpenAPI = {
2
2
  BASE: '/api',
3
- VERSION: '1.21.0',
3
+ VERSION: '1.22.0',
4
4
  WITH_CREDENTIALS: false,
5
5
  CREDENTIALS: 'include',
6
6
  TOKEN: undefined,
package/gen/index.d.ts CHANGED
@@ -30,6 +30,7 @@ export type { MainArgSignature } from './models/MainArgSignature';
30
30
  export type { NewSchedule } from './models/NewSchedule';
31
31
  export type { NewToken } from './models/NewToken';
32
32
  export type { NewUser } from './models/NewUser';
33
+ export type { OpenFlow } from './models/OpenFlow';
33
34
  export { Preview } from './models/Preview';
34
35
  export { QueuedJob } from './models/QueuedJob';
35
36
  export type { Resource } from './models/Resource';
@@ -0,0 +1,7 @@
1
+ import type { FlowValue } from './FlowValue';
2
+ export declare type OpenFlow = {
3
+ summary: string;
4
+ description?: string;
5
+ value: FlowValue;
6
+ schema?: any;
7
+ };
@@ -0,0 +1,4 @@
1
+ /* istanbul ignore file */
2
+ /* tslint:disable */
3
+ /* eslint-disable */
4
+ export {};
@@ -2,6 +2,19 @@ import type { Flow } from '../models/Flow';
2
2
  import type { FlowValue } from '../models/FlowValue';
3
3
  import type { CancelablePromise } from '../core/CancelablePromise';
4
4
  export declare class FlowService {
5
+ /**
6
+ * list all available hub flows
7
+ * @returns any hub flows list
8
+ * @throws ApiError
9
+ */
10
+ static listHubFlows(): CancelablePromise<Array<{
11
+ id: number;
12
+ flow_id: number;
13
+ summary: string;
14
+ apps: Array<string>;
15
+ approved: boolean;
16
+ votes: number;
17
+ }>>;
5
18
  /**
6
19
  * list all available flows
7
20
  * @returns Flow All available flow
@@ -1,6 +1,17 @@
1
1
  import { OpenAPI } from '../core/OpenAPI';
2
2
  import { request as __request } from '../core/request';
3
3
  export class FlowService {
4
+ /**
5
+ * list all available hub flows
6
+ * @returns any hub flows list
7
+ * @throws ApiError
8
+ */
9
+ static listHubFlows() {
10
+ return __request(OpenAPI, {
11
+ method: 'GET',
12
+ url: '/flows/hub/list',
13
+ });
14
+ }
4
15
  /**
5
16
  * list all available flows
6
17
  * @returns Flow All available flow
@@ -9,11 +9,22 @@ export declare class ScriptService {
9
9
  */
10
10
  static listHubScripts(): CancelablePromise<Array<{
11
11
  id: number;
12
+ ask_id: number;
12
13
  summary: string;
13
14
  app: string;
14
15
  approved: boolean;
15
16
  is_trigger: boolean;
17
+ votes: number;
18
+ views: number;
16
19
  }>>;
20
+ /**
21
+ * get hub flow by id
22
+ * @returns string flow
23
+ * @throws ApiError
24
+ */
25
+ static getHubFlowById({ id, }: {
26
+ id: number;
27
+ }): CancelablePromise<string>;
17
28
  /**
18
29
  * get hub script content by path
19
30
  * @returns string script details
@@ -12,6 +12,20 @@ export class ScriptService {
12
12
  url: '/scripts/hub/list',
13
13
  });
14
14
  }
15
+ /**
16
+ * get hub flow by id
17
+ * @returns string flow
18
+ * @throws ApiError
19
+ */
20
+ static getHubFlowById({ id, }) {
21
+ return __request(OpenAPI, {
22
+ method: 'GET',
23
+ url: '/flows/hub/get/{id}',
24
+ path: {
25
+ 'id': id,
26
+ },
27
+ });
28
+ }
15
29
  /**
16
30
  * get hub script content by path
17
31
  * @returns string script details
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windmill-components",
3
- "version": "1.22.0",
3
+ "version": "1.22.42",
4
4
  "devDependencies": {
5
5
  "@playwright/test": "^1.23.4",
6
6
  "@sveltejs/adapter-static": "^1.0.0-next.37",
@@ -17,6 +17,7 @@
17
17
  "eslint-config-prettier": "^8.3.0",
18
18
  "eslint-plugin-svelte3": "^4.0.0",
19
19
  "openapi-typescript-codegen": "^0.23.0",
20
+ "path-browserify": "^1.0.1",
20
21
  "postcss": "^8.4.5",
21
22
  "postcss-load-config": "^4.0.1",
22
23
  "prettier": "^2.7.1",
@@ -64,6 +65,7 @@
64
65
  "./components/DisplayResult.svelte": "./components/DisplayResult.svelte",
65
66
  "./components/Dropdown.svelte": "./components/Dropdown.svelte",
66
67
  "./components/Editor.svelte": "./components/Editor.svelte",
68
+ "./components/EditorBar.svelte": "./components/EditorBar.svelte",
67
69
  "./components/FieldHeader.svelte": "./components/FieldHeader.svelte",
68
70
  "./components/FlowBuilder.svelte": "./components/FlowBuilder.svelte",
69
71
  "./components/FlowEditor.svelte": "./components/FlowEditor.svelte",
@@ -164,6 +166,7 @@
164
166
  "./gen/models/NewSchedule": "./gen/models/NewSchedule.js",
165
167
  "./gen/models/NewToken": "./gen/models/NewToken.js",
166
168
  "./gen/models/NewUser": "./gen/models/NewUser.js",
169
+ "./gen/models/OpenFlow": "./gen/models/OpenFlow.js",
167
170
  "./gen/models/Preview": "./gen/models/Preview.js",
168
171
  "./gen/models/QueuedJob": "./gen/models/QueuedJob.js",
169
172
  "./gen/models/Resource": "./gen/models/Resource.js",
package/stores.d.ts CHANGED
@@ -18,6 +18,7 @@ export declare const hubScripts: import("svelte/store").Writable<{
18
18
  summary: string;
19
19
  approved: boolean;
20
20
  is_trigger: boolean;
21
+ app: string;
21
22
  }[] | undefined>;
22
23
  export declare function clearStores(): void;
23
24
  export declare const previewResults: import("svelte/store").Writable<Record<number, Object>>;
package/utils.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { User } from './gen';
1
+ import { type User } from './gen';
2
2
  import type { Schema } from './common';
3
- import type { UserExt } from './stores';
3
+ import { type UserExt } from './stores';
4
4
  export declare function isToday(someDate: Date): boolean;
5
5
  export declare function daysAgo(someDate: Date): number;
6
6
  export declare function secondsAgo(date: Date): number;
@@ -65,3 +65,16 @@ export declare function objectToTsType(object: Object): string;
65
65
  export declare type InputCat = "string" | "number" | "boolean" | "list" | "resource-object" | "enum" | "date" | "base64" | "resource-string" | "object";
66
66
  export declare function setInputCat(type: string | undefined, format: string | undefined, itemsType: string | undefined, enum_: any, contentEncoding: string | undefined): InputCat;
67
67
  export declare function scriptPathToHref(path: string): string;
68
+ export declare function getScriptByPath(path: string): Promise<{
69
+ content: string;
70
+ language: 'deno' | 'python3';
71
+ }>;
72
+ export declare function loadHubScripts(): Promise<void>;
73
+ export declare function loadHubFlows(): Promise<{
74
+ id: number;
75
+ flow_id: number;
76
+ summary: string;
77
+ apps: string[];
78
+ approved: boolean;
79
+ votes: number;
80
+ }[]>;
package/utils.js CHANGED
@@ -1,6 +1,9 @@
1
1
  /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
2
2
  import { goto } from '$app/navigation';
3
+ import { FlowService, Script, ScriptService } from './gen';
3
4
  import { toast } from '@zerodevx/svelte-toast';
5
+ import { get } from 'svelte/store';
6
+ import { hubScripts, workspaceStore } from './stores';
4
7
  export function isToday(someDate) {
5
8
  const today = new Date();
6
9
  return (someDate.getDate() == today.getDate() &&
@@ -351,9 +354,47 @@ export function setInputCat(type, format, itemsType, enum_, contentEncoding) {
351
354
  }
352
355
  export function scriptPathToHref(path) {
353
356
  if (path.startsWith('hub/')) {
354
- return 'https://hub.windmill.dev/scripts/get/' + path.substring(4);
357
+ return 'https://hub.windmill.dev/from_version/' + path.substring(4);
355
358
  }
356
359
  else {
357
360
  return `/scripts/get/${path}`;
358
361
  }
359
362
  }
363
+ export async function getScriptByPath(path) {
364
+ if (path.startsWith('hub/')) {
365
+ const content = await ScriptService.getHubScriptContentByPath({ path });
366
+ return {
367
+ content,
368
+ language: 'deno'
369
+ };
370
+ }
371
+ else {
372
+ const script = await ScriptService.getScriptByPath({
373
+ workspace: get(workspaceStore),
374
+ path: path ?? ''
375
+ });
376
+ return {
377
+ content: script.content,
378
+ language: script.language
379
+ };
380
+ }
381
+ }
382
+ export async function loadHubScripts() {
383
+ const scripts = await ScriptService.listHubScripts();
384
+ const processed = scripts.map((x) => ({
385
+ path: `hub/${x.id}/${x.summary.toLowerCase().replaceAll(/\s+/g, '_')}`,
386
+ summary: `${x.summary} (${x.app}) ${x.views} uses`,
387
+ approved: x.approved,
388
+ is_trigger: x.is_trigger,
389
+ app: x.app,
390
+ views: x.views,
391
+ votes: x.votes,
392
+ ask_id: x.ask_id,
393
+ })).sort((a, b) => b.views - a.views);
394
+ hubScripts.set(processed);
395
+ }
396
+ export async function loadHubFlows() {
397
+ const flows = await FlowService.listHubFlows();
398
+ const processed = flows.sort((a, b) => b.votes - a.votes);
399
+ return processed;
400
+ }