@kupcad/ui 0.0.0-stage → 0.1.0

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.
Files changed (45) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +281 -2
  3. package/package.json +65 -3
  4. package/src/ConsolePanel.svelte +139 -0
  5. package/src/ExportMenu.svelte +36 -0
  6. package/src/ParamField.svelte +390 -0
  7. package/src/ParamsPanel.svelte +216 -0
  8. package/src/PreviewPanel.svelte +372 -0
  9. package/src/ShortcutsDialog.svelte +170 -0
  10. package/src/Splitter.svelte +148 -0
  11. package/src/Viewer.svelte +175 -0
  12. package/src/ViewerPane.svelte +745 -0
  13. package/src/base64.js +16 -0
  14. package/src/console.js +78 -0
  15. package/src/controls.css +183 -0
  16. package/src/index.js +40 -0
  17. package/src/measures.js +52 -0
  18. package/src/params.js +348 -0
  19. package/src/range.js +19 -0
  20. package/src/recent.js +44 -0
  21. package/src/run-status.js +78 -0
  22. package/src/settings.js +82 -0
  23. package/src/shortcuts.js +59 -0
  24. package/src/theme.js +131 -0
  25. package/src/tokens.css +70 -0
  26. package/src/values.svelte.js +13 -0
  27. package/src/view-settings.js +81 -0
  28. package/src/viewer/appearance.js +90 -0
  29. package/src/viewer/camera.js +100 -0
  30. package/src/viewer/dispose.js +30 -0
  31. package/src/viewer/environment.js +145 -0
  32. package/src/viewer/faces.js +311 -0
  33. package/src/viewer/hover-mark.js +125 -0
  34. package/src/viewer/index.js +7 -0
  35. package/src/viewer/model.js +293 -0
  36. package/src/viewer/motion.js +96 -0
  37. package/src/viewer/section.js +166 -0
  38. package/src/viewer/testing.js +90 -0
  39. package/src/viewer/theme.js +32 -0
  40. package/src/viewer/types.d.ts +136 -0
  41. package/src/viewer/view-cube.js +314 -0
  42. package/src/viewer/viewer.js +1027 -0
  43. package/src/viewer/wheel.js +55 -0
  44. package/src/viewport-socket.js +64 -0
  45. package/src/vscode.css +49 -0
@@ -0,0 +1,175 @@
1
+ <!--
2
+ @component
3
+ The viewer for Svelte: a canvas filling its box, showing `model` in the state its props give,
4
+ with a view cube in its corner (`cube`). `selected` is bindable (a click on a part sets it),
5
+ and so is `viewer`, the Viewer itself, for what isn't state: framing a view
6
+ (`setCamera({ view })`), `fit()`, `screenshot()`.
7
+ -->
8
+ <script>
9
+ import { onMount, untrack } from 'svelte';
10
+ import { ViewCube } from './viewer/view-cube.js';
11
+ import { Viewer } from './viewer/viewer.js';
12
+
13
+ /** @import { Model } from '@kupcad/protocol/types' */
14
+ /** @import { Environment, Projection, RenderMode, Section, Theme, ViewerOptions } from './viewer/types.js' */
15
+
16
+ /**
17
+ * @typedef {object} Props
18
+ * @property {Model | null} [model]
19
+ * @property {Theme} [theme]
20
+ * @property {Environment} [environment]
21
+ * @property {RenderMode} [renderMode]
22
+ * @property {boolean} [edges] the shapes' edges are drawn (by default; a wireframe's always are)
23
+ * @property {boolean} [axes] the axes through the origin are drawn (by default)
24
+ * @property {Projection} [projection]
25
+ * @property {Section | null} [section]
26
+ * @property {string | null} [selected] the part selected; bindable
27
+ * @property {Viewer | null} [viewer] the Viewer, once mounted; bindable
28
+ * @property {ViewerOptions} [options] what the Viewer is made with (its renderer, Draco's path)
29
+ * @property {string} [label] what the canvas is, for a screen reader
30
+ * @property {boolean} [cube] the view cube is shown in the corner (by default)
31
+ * @property {(viewer: Viewer) => void} [onmodel] a model is shown: what follows it (its
32
+ * bounds) can be read. Not for one a later model replaced before it was shown
33
+ * @property {(error: unknown) => void} [onerror] a model it couldn't show; without it, the
34
+ * error is thrown on, unhandled
35
+ */
36
+
37
+ /** @type {Props} */
38
+ let {
39
+ model = null,
40
+ theme,
41
+ environment = 'grid',
42
+ renderMode = 'solid',
43
+ edges = true,
44
+ axes = true,
45
+ projection = 'perspective',
46
+ section = null,
47
+ selected = $bindable(null),
48
+ viewer = $bindable(null),
49
+ options = {},
50
+ label = 'The model',
51
+ cube = true,
52
+ onmodel,
53
+ onerror,
54
+ } = $props();
55
+
56
+ /** @type {HTMLCanvasElement} */
57
+ let canvas;
58
+ /** @type {HTMLCanvasElement | undefined} */
59
+ let cubeCanvas = $state();
60
+ /** @type {ViewCube | null} */
61
+ let viewCube = null;
62
+ /** The Viewer made, which the effects follow: `viewer` is the host's to bind, and may be set. */
63
+ let made = $state(/** @type {Viewer | null} */ (null));
64
+
65
+ onMount(() => {
66
+ const shown = new Viewer(canvas, { ...options, theme: theme ?? options.theme, environment });
67
+ const resized = new ResizeObserver(() => shown.resize());
68
+ resized.observe(canvas);
69
+ shown.addEventListener('select', (event) => {
70
+ selected = /** @type {CustomEvent<{ part: string | null }>} */ (event).detail.part;
71
+ });
72
+ made = shown;
73
+ viewer = shown;
74
+ return () => {
75
+ resized.disconnect();
76
+ shown.dispose();
77
+ made = null;
78
+ viewer = null;
79
+ };
80
+ });
81
+
82
+ // The cube follows the viewer: made with it, on its own canvas, in the theme then (another
83
+ // theme is drawn in by the effect below, not by a cube made again)
84
+ $effect(() => {
85
+ const shown = made;
86
+ const on = cubeCanvas;
87
+ if (!shown || !on) return;
88
+ const shownCube = untrack(
89
+ () => new ViewCube(on, shown, { theme: theme ?? options.theme, renderer: options.renderer }),
90
+ );
91
+ const resized = new ResizeObserver(() => shownCube.resize());
92
+ resized.observe(on);
93
+ viewCube = shownCube;
94
+ return () => {
95
+ resized.disconnect();
96
+ shownCube.dispose();
97
+ viewCube = null;
98
+ };
99
+ });
100
+
101
+ $effect(() => {
102
+ const shown = made;
103
+ shown?.setModel(model).then(
104
+ (isShown) => {
105
+ if (isShown) onmodel?.(shown);
106
+ },
107
+ (error) => {
108
+ if (!onerror) throw error;
109
+ onerror(error);
110
+ },
111
+ );
112
+ });
113
+ $effect(() => {
114
+ if (!theme) return;
115
+ made?.setTheme(theme);
116
+ viewCube?.setTheme(theme);
117
+ });
118
+ $effect(() => {
119
+ made?.setEnvironment(environment);
120
+ });
121
+ $effect(() => {
122
+ made?.setRenderMode(renderMode);
123
+ });
124
+ $effect(() => {
125
+ made?.setEdges(edges);
126
+ });
127
+ $effect(() => {
128
+ made?.setAxes(axes);
129
+ });
130
+ $effect(() => {
131
+ made?.setCamera({ projection });
132
+ });
133
+ $effect(() => {
134
+ made?.setSection(section);
135
+ });
136
+ $effect(() => {
137
+ if (made && made.selected !== selected) made.select(selected);
138
+ });
139
+ </script>
140
+
141
+ <div class="kc-viewer">
142
+ <canvas bind:this={canvas} class="scene" aria-label={label}></canvas>
143
+ {#if cube}
144
+ <canvas
145
+ bind:this={cubeCanvas}
146
+ class="cube"
147
+ aria-label="View cube: a face, an edge or a corner clicked looks from there"
148
+ ></canvas>
149
+ {/if}
150
+ </div>
151
+
152
+ <style>
153
+ .kc-viewer {
154
+ position: relative;
155
+ width: 100%;
156
+ height: 100%;
157
+ }
158
+ /* Laid over the box, not in it: the canvas's drawing, which `resize` sizes from the canvas,
159
+ never sizes the box (in a page of no definite height it did, and grew past the window) */
160
+ .scene {
161
+ position: absolute;
162
+ inset: 0;
163
+ display: block;
164
+ width: 100%;
165
+ height: 100%;
166
+ touch-action: none;
167
+ }
168
+ .cube {
169
+ position: absolute;
170
+ top: 8px;
171
+ right: 8px;
172
+ width: 96px;
173
+ height: 96px;
174
+ }
175
+ </style>