@kupcad/playground 0.1.3 → 0.1.5

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.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @kupcad/playground
2
2
 
3
3
  KupCAD in a page: a script's editor beside its model and its parameters, all run in the browser
4
- by the core compiled to WebAssembly, in a worker. One Svelte component, which the site (and later
5
- the PWA) embeds; it keeps nothing of the page's.
4
+ by the core compiled to WebAssembly, in a worker. One Svelte component, which the site and the
5
+ browser IDE embed; it keeps nothing of the page's.
6
6
 
7
7
  ```svelte
8
8
  <script>
@@ -18,7 +18,7 @@ the PWA) embeds; it keeps nothing of the page's.
18
18
  with itself), `wasm` (that worker's module, when not the one `@kupcad/wasm` ships),
19
19
  `viewerOptions`, `settings`, `shortcuts` (a page's own, listed before the viewer's in the
20
20
  pane's shortcuts dialog) and `shortcutsOpen` (bindable).
21
- * **For a host that shares or saves the script** (a PWA later; nothing of it here): `onsource`
21
+ * **For a host that shares or saves the script** (the site's Share, the browser IDE): `onsource`
22
22
  is called with the script at each of the person's edits (not with the `source` it was given),
23
23
  and an `actions` snippet puts the host's own buttons in the model's toolbar, after Export
24
24
  (`.kc-control` gives them its look). It keeps no draft: a page loaded again shows `source`.
@@ -26,6 +26,15 @@ the PWA) embeds; it keeps nothing of the page's.
26
26
  component's, by `bind:this`) puts one in the editor in place of the person's, which the
27
27
  editor's undo takes back, and runs it at once on its own values, its model framed whole (an
28
28
  edit's keeps the camera); the core, the console and the layout stay as they are.
29
+ * **For a host of several scripts** (the browser IDE's project): `open(script, restore)` opens
30
+ one as another document, the editor made again on it with an undo history of its own (none of
31
+ the last one's), run as `load` runs one; `snapshot()` is the editor as it is, its undo history
32
+ with it, for `open` to give back when the host opens that script again; `run()` runs the
33
+ script again as it is (what it imports changed), and `focus()` puts the focus in the editor
34
+ (a script the host just made). `files` is what the
35
+ script's imports read, by the paths the script names them by (`import { } from "./lib.kup"`
36
+ reads `lib.kup`), given with each run and export as the core's calls take them. `download`
37
+ (exported) saves bytes or a Blob as a file, as the exports are saved.
29
38
  * **When it runs** (`runner.js`, `Runner`): once the script stays as it is for a moment after an
30
39
  edit (`DELAY_MS`, 300), at once when a parameter's value changes or it starts. A run under
31
40
  way when the next starts is stopped (the worker drops or ends its call), so what is shown is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kupcad/playground",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "KupCAD in the browser: a script's editor, its model and its parameters, running on the core compiled to WebAssembly in a worker. One Svelte component a page embeds.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -24,10 +24,10 @@
24
24
  "access": "public"
25
25
  },
26
26
  "dependencies": {
27
- "@kupcad/editor": "0.1.3",
28
- "@kupcad/protocol": "0.1.3",
29
- "@kupcad/ui": "0.1.3",
30
- "@kupcad/wasm": "0.1.3"
27
+ "@kupcad/editor": "0.1.5",
28
+ "@kupcad/protocol": "0.1.5",
29
+ "@kupcad/ui": "0.1.5",
30
+ "@kupcad/wasm": "0.1.5"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@codemirror/lint": "^6.9.7",
@@ -10,8 +10,10 @@
10
10
  model's width); the sizes, what is hidden or folded, and how the view looks are kept in
11
11
  `settings` (the page's localStorage by default), and follow them as they are set. It keeps
12
12
  nothing else of the page's: the page gives the script it starts with (`source`) and another
13
- in its place (`load`), and is told of each edit of it (`onsource`), for its own buttons in the
14
- toolbar (`actions`) to share or save it.
13
+ in its place (`load`, or `open` for another document, with an undo history of its own), and
14
+ is told of each edit of it (`onsource`), for its own buttons in the toolbar (`actions`) to
15
+ share or save it. The files the script imports, a page of several scripts gives with them
16
+ (`files`).
15
17
  -->
16
18
  <script>
17
19
  import { onMount, untrack } from 'svelte';
@@ -42,7 +44,7 @@
42
44
  /** @import { ConsoleLine, Problem, Settings, ShortcutGroup } from '@kupcad/ui' */
43
45
  /** @import { Snippet } from 'svelte' */
44
46
  /** @import { ViewerOptions } from '@kupcad/ui/viewer/types' */
45
- /** @import { KupcadApi, WorkerWasmSource } from '@kupcad/wasm/types' */
47
+ /** @import { KupcadApi, PreviewOptions, WorkerWasmSource } from '@kupcad/wasm/types' */
46
48
 
47
49
  /**
48
50
  * @typedef {object} Props
@@ -57,6 +59,9 @@
57
59
  * by default the page's localStorage
58
60
  * @property {(source: string) => void} [onsource] the person's edit changed the script: it now
59
61
  * is `source` (for a host to share it, or save it)
62
+ * @property {PreviewOptions['files']} [files] what the script's imports read, by the paths it
63
+ * names them by (`import { } from "./lib.kup"` reads `lib.kup`), given with each run and
64
+ * export
60
65
  * @property {Snippet} [actions] a host's own buttons in the model's toolbar, after Export
61
66
  * (`.kc-control` gives them its look)
62
67
  * @property {readonly ShortcutGroup[]} [shortcuts] the host's own, listed before the viewer's
@@ -72,6 +77,7 @@
72
77
  viewerOptions = {},
73
78
  settings = localSettings(),
74
79
  onsource,
80
+ files,
75
81
  actions: hostActions,
76
82
  shortcuts = [],
77
83
  shortcutsOpen = $bindable(false),
@@ -109,6 +115,10 @@
109
115
  * view frames each one's model whole (another subject), where an edit's keeps the camera.
110
116
  */
111
117
  let loads = $state(0);
118
+ /** How many other documents the page opened: the editor is made again for each. */
119
+ let documents = $state(0);
120
+ /** The editor's snapshot the document opened last starts from, if it has one. */
121
+ let restored = $state.raw(/** @type {unknown} */ (undefined));
112
122
  let editor = $state(/** @type {EditorClass | null} */ (null));
113
123
  /** `check`'s findings of the script as it is, none while it changed since. */
114
124
  let findings = $state(/** @type {Finding[]} */ ([]));
@@ -147,7 +157,7 @@
147
157
  },
148
158
  onerror: (error) => (notice = errorText(error)),
149
159
  });
150
- runner.run(value, {}, { now: true });
160
+ runner.run(value, {}, { now: true, files });
151
161
  return () => {
152
162
  runner?.dispose();
153
163
  made?.terminate();
@@ -165,7 +175,38 @@
165
175
  findings = [];
166
176
  overrides = {};
167
177
  loads += 1;
168
- runner?.run(script, {}, { now: true });
178
+ runner?.run(script, {}, { now: true, files });
179
+ }
180
+
181
+ /**
182
+ * Opens `script` in the editor in place of the one there as another document: the editor made
183
+ * again on it, its undo history its own, not the last one's (`restore`, an earlier
184
+ * `snapshot()` of it, gives it back), and run at once as `load` runs one.
185
+ * @param {string} script one of the page's scripts
186
+ * @param {unknown} [restore] the snapshot the page kept when it opened another
187
+ */
188
+ export function open(script, restore) {
189
+ restored = restore;
190
+ documents += 1;
191
+ load(script);
192
+ }
193
+
194
+ /** The editor as it is now, its undo history with it, for `open` to give back: plain JSON. */
195
+ export function snapshot() {
196
+ return editor?.snapshot();
197
+ }
198
+
199
+ /**
200
+ * Runs the script again at once, as it is, on the panel's values: what it imports changed
201
+ * (`files`), which the page knows of and it doesn't.
202
+ */
203
+ export function run() {
204
+ runner?.run(value, plainValues(overrides), { now: true, files });
205
+ }
206
+
207
+ /** Puts the focus in the editor: a script the page made, to be written. */
208
+ export function focus() {
209
+ editor?.focus();
169
210
  }
170
211
 
171
212
  /** @param {string} line of the core's log, which goes to the console */
@@ -176,13 +217,13 @@
176
217
  /** @param {string} next the user's script */
177
218
  function edited(next) {
178
219
  findings = [];
179
- runner?.run(next, plainValues(overrides));
220
+ runner?.run(next, plainValues(overrides), { files });
180
221
  onsource?.(next);
181
222
  }
182
223
 
183
224
  /** @param {ParamValues} values the panel's */
184
225
  function valuesChanged(values) {
185
- runner?.run(value, values, { now: true });
226
+ runner?.run(value, values, { now: true, files });
186
227
  }
187
228
 
188
229
  /** @param {Problem | ConsoleLine} place a problem or a console line with a line, clicked */
@@ -194,7 +235,7 @@
194
235
  async function exportChosen(format) {
195
236
  if (!core) return;
196
237
  try {
197
- const bytes = await core.build(value, { format, params: plainValues(overrides) });
238
+ const bytes = await core.build(value, { format, params: plainValues(overrides), files });
198
239
  download(bytes, `${name}.${format}`);
199
240
  notice = '';
200
241
  } catch (error) {
@@ -215,7 +256,16 @@
215
256
  style:--params-width="{paramsWidth}px"
216
257
  >
217
258
  <section class="code" aria-label="Script">
218
- <Editor bind:value bind:editor findings={marks} label={name} onchange={edited} />
259
+ {#key documents}
260
+ <Editor
261
+ bind:value
262
+ bind:editor
263
+ findings={marks}
264
+ label={name}
265
+ restore={restored}
266
+ onchange={edited}
267
+ />
268
+ {/key}
219
269
  {#if notice}<p class="notice" role="alert">{notice}</p>{/if}
220
270
  </section>
221
271
  <Splitter
package/src/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // @kupcad/playground: KupCAD in a page, a script's editor, model and parameters on the core in a
2
- // worker (Playground.svelte), and when it runs the script (Runner), for a host of its own.
2
+ // worker (Playground.svelte), when it runs the script (Runner), for a host of its own, and how
3
+ // it gives the person a file (download).
3
4
 
4
5
  export { default as Playground } from './Playground.svelte';
6
+ export { download } from './download.js';
5
7
  export { DELAY_MS, Runner } from './runner.js';
package/src/runner.js CHANGED
@@ -2,10 +2,11 @@
2
2
  // edit, and at once when a parameter's value changes or the playground starts. A run under way
3
3
  // when the next starts is stopped (the core's worker drops or ends its call), so what comes is
4
4
  // the last script's. A run is the script's findings (`check`, quick: the editor marks them
5
- // first), then its preview (the model, its parameters, why it failed).
5
+ // first), then its preview (the model, its parameters, why it failed), the files its imports
6
+ // read given with it.
6
7
 
7
8
  /** @import { Finding, ParamValues, Preview } from '@kupcad/protocol/types' */
8
- /** @import { KupcadApi } from '@kupcad/wasm/types' */
9
+ /** @import { KupcadApi, PreviewOptions } from '@kupcad/wasm/types' */
9
10
 
10
11
  /** How long the script stays as it is after an edit before it runs. */
11
12
  export const DELAY_MS = 300;
@@ -42,15 +43,15 @@ export class Runner {
42
43
 
43
44
  /**
44
45
  * Runs `source` with `values` once it stays so for the delay (a later call waits again), or at
45
- * once.
46
+ * once; with `files`, what its imports read.
46
47
  * @param {string} source
47
48
  * @param {ParamValues} values
48
- * @param {{ now?: boolean }} [when]
49
+ * @param {{ now?: boolean, files?: PreviewOptions['files'] }} [options]
49
50
  */
50
- run(source, values, { now = false } = {}) {
51
+ run(source, values, { now = false, files } = {}) {
51
52
  clearTimeout(this.#waiting);
52
- if (now) void this.#start(source, values);
53
- else this.#waiting = setTimeout(() => void this.#start(source, values), this.#delayMs);
53
+ if (now) void this.#start(source, values, files);
54
+ else this.#waiting = setTimeout(() => void this.#start(source, values, files), this.#delayMs);
54
55
  }
55
56
 
56
57
  /** Runs nothing more, and stops the run under way. */
@@ -63,8 +64,9 @@ export class Runner {
63
64
  /**
64
65
  * @param {string} source
65
66
  * @param {ParamValues} values
67
+ * @param {PreviewOptions['files']} files
66
68
  */
67
- async #start(source, values) {
69
+ async #start(source, values, files) {
68
70
  this.#running?.abort();
69
71
  const running = new AbortController();
70
72
  this.#running = running;
@@ -73,7 +75,7 @@ export class Runner {
73
75
  const findings = await this.#kupcad.check(source, { signal });
74
76
  if (signal.aborted) return;
75
77
  this.#handlers.onfindings(findings, source);
76
- const preview = await this.#kupcad.preview(source, { params: values, signal });
78
+ const preview = await this.#kupcad.preview(source, { params: values, files, signal });
77
79
  if (signal.aborted) return;
78
80
  this.#handlers.onpreview(preview, source);
79
81
  } catch (error) {