@stevejtrettel/shader-sandbox 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 (106) hide show
  1. package/README.md +391 -0
  2. package/bin/cli.js +389 -0
  3. package/dist-lib/app/App.d.ts +134 -0
  4. package/dist-lib/app/App.d.ts.map +1 -0
  5. package/dist-lib/app/App.js +570 -0
  6. package/dist-lib/app/types.d.ts +32 -0
  7. package/dist-lib/app/types.d.ts.map +1 -0
  8. package/dist-lib/app/types.js +6 -0
  9. package/dist-lib/editor/EditorPanel.d.ts +39 -0
  10. package/dist-lib/editor/EditorPanel.d.ts.map +1 -0
  11. package/dist-lib/editor/EditorPanel.js +274 -0
  12. package/dist-lib/editor/prism-editor.css +99 -0
  13. package/dist-lib/editor/prism-editor.d.ts +19 -0
  14. package/dist-lib/editor/prism-editor.d.ts.map +1 -0
  15. package/dist-lib/editor/prism-editor.js +96 -0
  16. package/dist-lib/embed.d.ts +17 -0
  17. package/dist-lib/embed.d.ts.map +1 -0
  18. package/dist-lib/embed.js +35 -0
  19. package/dist-lib/engine/ShadertoyEngine.d.ts +160 -0
  20. package/dist-lib/engine/ShadertoyEngine.d.ts.map +1 -0
  21. package/dist-lib/engine/ShadertoyEngine.js +704 -0
  22. package/dist-lib/engine/glHelpers.d.ts +79 -0
  23. package/dist-lib/engine/glHelpers.d.ts.map +1 -0
  24. package/dist-lib/engine/glHelpers.js +298 -0
  25. package/dist-lib/engine/types.d.ts +77 -0
  26. package/dist-lib/engine/types.d.ts.map +1 -0
  27. package/dist-lib/engine/types.js +7 -0
  28. package/dist-lib/index.d.ts +12 -0
  29. package/dist-lib/index.d.ts.map +1 -0
  30. package/dist-lib/index.js +9 -0
  31. package/dist-lib/layouts/DefaultLayout.d.ts +17 -0
  32. package/dist-lib/layouts/DefaultLayout.d.ts.map +1 -0
  33. package/dist-lib/layouts/DefaultLayout.js +27 -0
  34. package/dist-lib/layouts/FullscreenLayout.d.ts +17 -0
  35. package/dist-lib/layouts/FullscreenLayout.d.ts.map +1 -0
  36. package/dist-lib/layouts/FullscreenLayout.js +27 -0
  37. package/dist-lib/layouts/SplitLayout.d.ts +26 -0
  38. package/dist-lib/layouts/SplitLayout.d.ts.map +1 -0
  39. package/dist-lib/layouts/SplitLayout.js +61 -0
  40. package/dist-lib/layouts/TabbedLayout.d.ts +38 -0
  41. package/dist-lib/layouts/TabbedLayout.d.ts.map +1 -0
  42. package/dist-lib/layouts/TabbedLayout.js +305 -0
  43. package/dist-lib/layouts/index.d.ts +24 -0
  44. package/dist-lib/layouts/index.d.ts.map +1 -0
  45. package/dist-lib/layouts/index.js +36 -0
  46. package/dist-lib/layouts/split.css +196 -0
  47. package/dist-lib/layouts/tabbed.css +345 -0
  48. package/dist-lib/layouts/types.d.ts +48 -0
  49. package/dist-lib/layouts/types.d.ts.map +1 -0
  50. package/dist-lib/layouts/types.js +4 -0
  51. package/dist-lib/main.d.ts +15 -0
  52. package/dist-lib/main.d.ts.map +1 -0
  53. package/dist-lib/main.js +102 -0
  54. package/dist-lib/project/generatedLoader.d.ts +3 -0
  55. package/dist-lib/project/generatedLoader.d.ts.map +1 -0
  56. package/dist-lib/project/generatedLoader.js +17 -0
  57. package/dist-lib/project/loadProject.d.ts +22 -0
  58. package/dist-lib/project/loadProject.d.ts.map +1 -0
  59. package/dist-lib/project/loadProject.js +350 -0
  60. package/dist-lib/project/loaderHelper.d.ts +7 -0
  61. package/dist-lib/project/loaderHelper.d.ts.map +1 -0
  62. package/dist-lib/project/loaderHelper.js +240 -0
  63. package/dist-lib/project/types.d.ts +192 -0
  64. package/dist-lib/project/types.d.ts.map +1 -0
  65. package/dist-lib/project/types.js +7 -0
  66. package/dist-lib/styles/base.css +29 -0
  67. package/package.json +48 -0
  68. package/src/app/App.ts +699 -0
  69. package/src/app/app.css +208 -0
  70. package/src/app/types.ts +36 -0
  71. package/src/editor/EditorPanel.ts +340 -0
  72. package/src/editor/editor-panel.css +175 -0
  73. package/src/editor/prism-editor.css +99 -0
  74. package/src/editor/prism-editor.ts +124 -0
  75. package/src/embed.ts +55 -0
  76. package/src/engine/ShadertoyEngine.ts +929 -0
  77. package/src/engine/glHelpers.ts +432 -0
  78. package/src/engine/types.ts +118 -0
  79. package/src/index.ts +13 -0
  80. package/src/layouts/DefaultLayout.ts +40 -0
  81. package/src/layouts/FullscreenLayout.ts +40 -0
  82. package/src/layouts/SplitLayout.ts +81 -0
  83. package/src/layouts/TabbedLayout.ts +371 -0
  84. package/src/layouts/default.css +22 -0
  85. package/src/layouts/fullscreen.css +15 -0
  86. package/src/layouts/index.ts +44 -0
  87. package/src/layouts/split.css +196 -0
  88. package/src/layouts/tabbed.css +345 -0
  89. package/src/layouts/types.ts +58 -0
  90. package/src/main.ts +114 -0
  91. package/src/project/generatedLoader.ts +23 -0
  92. package/src/project/loadProject.ts +421 -0
  93. package/src/project/loaderHelper.ts +300 -0
  94. package/src/project/types.ts +243 -0
  95. package/src/styles/base.css +29 -0
  96. package/src/styles/embed.css +14 -0
  97. package/src/vite-env.d.ts +1 -0
  98. package/templates/index.html +28 -0
  99. package/templates/main.ts +126 -0
  100. package/templates/package.json +12 -0
  101. package/templates/shaders/example-buffer/bufferA.glsl +14 -0
  102. package/templates/shaders/example-buffer/config.json +10 -0
  103. package/templates/shaders/example-buffer/image.glsl +5 -0
  104. package/templates/shaders/example-gradient/config.json +4 -0
  105. package/templates/shaders/example-gradient/image.glsl +7 -0
  106. package/templates/vite.config.js +35 -0
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Editor Panel - Shared component for code editing in layouts
3
+ *
4
+ * Provides:
5
+ * - CodeMirror editor (dynamically loaded)
6
+ * - Recompile button with keyboard shortcut
7
+ * - Error display
8
+ * - Tab management for multiple passes
9
+ */
10
+ import './editor-panel.css';
11
+ export class EditorPanel {
12
+ constructor(container, project) {
13
+ this.recompileHandler = null;
14
+ this.tabs = [];
15
+ this.activeTabIndex = 0;
16
+ // Editor instance (null if not in editor mode or viewing image)
17
+ this.editorInstance = null;
18
+ // Track modified sources (passName -> modified source)
19
+ this.modifiedSources = new Map();
20
+ this.container = container;
21
+ this.project = project;
22
+ // Build tabs
23
+ this.buildTabs();
24
+ // Create tab bar
25
+ this.tabBar = document.createElement('div');
26
+ this.tabBar.className = 'editor-tab-bar';
27
+ this.buildTabBar();
28
+ // Create content area
29
+ this.contentArea = document.createElement('div');
30
+ this.contentArea.className = 'editor-content-area';
31
+ // Create copy button (icon only)
32
+ this.copyButton = document.createElement('button');
33
+ this.copyButton.className = 'editor-copy-button';
34
+ this.copyButton.innerHTML = `
35
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
36
+ <path d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V2z" opacity="0.4"/>
37
+ <path d="M2 5a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h7a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H2zm0 1h7a1 1 0 0 1 1 1v7a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z"/>
38
+ </svg>
39
+ `;
40
+ this.copyButton.title = 'Copy code to clipboard';
41
+ this.copyButton.addEventListener('click', () => this.copyToClipboard());
42
+ // Create recompile button
43
+ this.recompileButton = document.createElement('button');
44
+ this.recompileButton.className = 'editor-recompile-button';
45
+ this.recompileButton.innerHTML = `
46
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor">
47
+ <path d="M4 3v10l8-5-8-5z"/>
48
+ </svg>
49
+ Recompile
50
+ `;
51
+ this.recompileButton.title = 'Recompile shader (Ctrl+Enter)';
52
+ this.recompileButton.addEventListener('click', () => this.recompile());
53
+ // Create error display
54
+ this.errorDisplay = document.createElement('div');
55
+ this.errorDisplay.className = 'editor-error-display';
56
+ this.errorDisplay.style.display = 'none';
57
+ // Assemble panel
58
+ const toolbar = document.createElement('div');
59
+ toolbar.className = 'editor-toolbar';
60
+ toolbar.appendChild(this.tabBar);
61
+ toolbar.appendChild(this.copyButton);
62
+ toolbar.appendChild(this.recompileButton);
63
+ this.container.appendChild(toolbar);
64
+ this.container.appendChild(this.contentArea);
65
+ this.container.appendChild(this.errorDisplay);
66
+ // Set up keyboard shortcut
67
+ this.setupKeyboardShortcut();
68
+ // Load editor for first tab
69
+ this.showTab(0);
70
+ }
71
+ setRecompileHandler(handler) {
72
+ this.recompileHandler = handler;
73
+ }
74
+ dispose() {
75
+ if (this.editorInstance) {
76
+ this.editorInstance.destroy();
77
+ this.editorInstance = null;
78
+ }
79
+ this.container.innerHTML = '';
80
+ }
81
+ buildTabs() {
82
+ this.tabs = [];
83
+ // 1. Common first (if exists)
84
+ if (this.project.commonSource) {
85
+ this.tabs.push({
86
+ kind: 'code',
87
+ name: 'common.glsl',
88
+ passName: 'common',
89
+ source: this.project.commonSource,
90
+ });
91
+ }
92
+ // 2. Buffers in order (A, B, C, D)
93
+ const bufferOrder = [
94
+ 'BufferA', 'BufferB', 'BufferC', 'BufferD',
95
+ ];
96
+ for (const bufferName of bufferOrder) {
97
+ const pass = this.project.passes[bufferName];
98
+ if (pass) {
99
+ this.tabs.push({
100
+ kind: 'code',
101
+ name: `${bufferName.toLowerCase()}.glsl`,
102
+ passName: bufferName,
103
+ source: pass.glslSource,
104
+ });
105
+ }
106
+ }
107
+ // 3. Image pass
108
+ const imagePass = this.project.passes.Image;
109
+ this.tabs.push({
110
+ kind: 'code',
111
+ name: 'image.glsl',
112
+ passName: 'Image',
113
+ source: imagePass.glslSource,
114
+ });
115
+ // 4. Textures (images) - not editable
116
+ for (const texture of this.project.textures) {
117
+ this.tabs.push({
118
+ kind: 'image',
119
+ name: texture.filename || texture.name,
120
+ url: texture.source,
121
+ });
122
+ }
123
+ }
124
+ buildTabBar() {
125
+ this.tabBar.innerHTML = '';
126
+ this.tabs.forEach((tab, index) => {
127
+ const tabButton = document.createElement('button');
128
+ tabButton.className = 'editor-tab-button';
129
+ if (tab.kind === 'image') {
130
+ tabButton.classList.add('image-tab');
131
+ }
132
+ tabButton.textContent = tab.name;
133
+ if (index === this.activeTabIndex) {
134
+ tabButton.classList.add('active');
135
+ }
136
+ tabButton.addEventListener('click', () => this.showTab(index));
137
+ this.tabBar.appendChild(tabButton);
138
+ });
139
+ }
140
+ async showTab(index) {
141
+ // Save current editor content before switching
142
+ this.saveCurrentEditorContent();
143
+ this.activeTabIndex = index;
144
+ const tab = this.tabs[index];
145
+ // Update tab bar active state
146
+ this.tabBar.querySelectorAll('.editor-tab-button').forEach((btn, i) => {
147
+ btn.classList.toggle('active', i === index);
148
+ });
149
+ // Clear content area
150
+ this.contentArea.innerHTML = '';
151
+ // Destroy previous editor instance
152
+ if (this.editorInstance) {
153
+ this.editorInstance.destroy();
154
+ this.editorInstance = null;
155
+ }
156
+ if (tab.kind === 'code') {
157
+ // Show buttons
158
+ this.copyButton.style.display = '';
159
+ this.recompileButton.style.display = '';
160
+ // Get source (use modified if available, otherwise original)
161
+ const source = this.modifiedSources.get(tab.passName) ?? tab.source;
162
+ // Create editor container
163
+ const editorContainer = document.createElement('div');
164
+ editorContainer.className = 'editor-prism-container';
165
+ this.contentArea.appendChild(editorContainer);
166
+ // Dynamically load editor and create instance
167
+ try {
168
+ const { createEditor } = await import('./prism-editor');
169
+ this.editorInstance = createEditor(editorContainer, source, (newSource) => {
170
+ // Track modifications
171
+ this.modifiedSources.set(tab.passName, newSource);
172
+ });
173
+ }
174
+ catch (err) {
175
+ console.error('Failed to load editor:', err);
176
+ // Fallback to textarea
177
+ const textarea = document.createElement('textarea');
178
+ textarea.className = 'editor-fallback-textarea';
179
+ textarea.value = source;
180
+ textarea.addEventListener('input', () => {
181
+ this.modifiedSources.set(tab.passName, textarea.value);
182
+ });
183
+ editorContainer.appendChild(textarea);
184
+ }
185
+ }
186
+ else {
187
+ // Hide buttons for image tabs
188
+ this.copyButton.style.display = 'none';
189
+ this.recompileButton.style.display = 'none';
190
+ // Show image
191
+ const imgContainer = document.createElement('div');
192
+ imgContainer.className = 'editor-image-viewer';
193
+ const img = document.createElement('img');
194
+ img.src = tab.url;
195
+ img.alt = tab.name;
196
+ imgContainer.appendChild(img);
197
+ this.contentArea.appendChild(imgContainer);
198
+ }
199
+ }
200
+ saveCurrentEditorContent() {
201
+ if (this.editorInstance) {
202
+ const tab = this.tabs[this.activeTabIndex];
203
+ if (tab.kind === 'code') {
204
+ const source = this.editorInstance.getSource();
205
+ this.modifiedSources.set(tab.passName, source);
206
+ }
207
+ }
208
+ }
209
+ recompile() {
210
+ if (!this.recompileHandler) {
211
+ console.warn('No recompile handler set');
212
+ return;
213
+ }
214
+ // Save current content first
215
+ this.saveCurrentEditorContent();
216
+ const tab = this.tabs[this.activeTabIndex];
217
+ if (tab.kind !== 'code') {
218
+ return;
219
+ }
220
+ const source = this.modifiedSources.get(tab.passName) ?? tab.source;
221
+ const result = this.recompileHandler(tab.passName, source);
222
+ if (result.success) {
223
+ this.hideError();
224
+ // Update the original source in the tab
225
+ tab.source = source;
226
+ }
227
+ else {
228
+ this.showError(result.error || 'Compilation failed');
229
+ }
230
+ }
231
+ showError(message) {
232
+ this.errorDisplay.textContent = message;
233
+ this.errorDisplay.style.display = 'block';
234
+ }
235
+ hideError() {
236
+ this.errorDisplay.style.display = 'none';
237
+ }
238
+ async copyToClipboard() {
239
+ const tab = this.tabs[this.activeTabIndex];
240
+ if (tab.kind !== 'code')
241
+ return;
242
+ // Get current source (modified or original)
243
+ const source = this.editorInstance
244
+ ? this.editorInstance.getSource()
245
+ : (this.modifiedSources.get(tab.passName) ?? tab.source);
246
+ try {
247
+ await navigator.clipboard.writeText(source);
248
+ // Show checkmark feedback
249
+ const originalHTML = this.copyButton.innerHTML;
250
+ this.copyButton.innerHTML = `
251
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
252
+ <path d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"/>
253
+ </svg>
254
+ `;
255
+ this.copyButton.classList.add('copied');
256
+ setTimeout(() => {
257
+ this.copyButton.innerHTML = originalHTML;
258
+ this.copyButton.classList.remove('copied');
259
+ }, 1500);
260
+ }
261
+ catch (err) {
262
+ console.error('Failed to copy:', err);
263
+ }
264
+ }
265
+ setupKeyboardShortcut() {
266
+ // Listen for Ctrl+Enter / Cmd+Enter
267
+ this.container.addEventListener('keydown', (e) => {
268
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
269
+ e.preventDefault();
270
+ this.recompile();
271
+ }
272
+ });
273
+ }
274
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Prism Editor Styles
3
+ * Textarea + syntax highlighting overlay
4
+ */
5
+
6
+ .prism-editor-wrapper {
7
+ display: flex;
8
+ height: 100%;
9
+ background: #ffffff;
10
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
11
+ font-size: 13px;
12
+ line-height: 1.6;
13
+ }
14
+
15
+ /* Line numbers column */
16
+ .prism-editor-line-numbers {
17
+ flex-shrink: 0;
18
+ padding: 16px 12px 16px 16px;
19
+ text-align: right;
20
+ color: #999;
21
+ border-right: 1px solid #e0e0e0;
22
+ user-select: none;
23
+ overflow: hidden;
24
+ }
25
+
26
+ .prism-editor-line-numbers span {
27
+ display: block;
28
+ }
29
+
30
+ /* Editor area containing textarea and highlight overlay */
31
+ .prism-editor-area {
32
+ flex: 1;
33
+ position: relative;
34
+ overflow: hidden;
35
+ }
36
+
37
+ /* Shared styles for textarea and highlight */
38
+ .prism-editor-textarea,
39
+ .prism-editor-highlight {
40
+ position: absolute;
41
+ top: 0;
42
+ left: 0;
43
+ width: 100%;
44
+ height: 100%;
45
+ padding: 16px;
46
+ margin: 0;
47
+ border: none;
48
+ outline: none;
49
+ font-family: inherit;
50
+ font-size: inherit;
51
+ line-height: inherit;
52
+ white-space: pre-wrap;
53
+ word-wrap: break-word;
54
+ overflow: auto;
55
+ box-sizing: border-box;
56
+ }
57
+
58
+ /* Textarea - user types here */
59
+ .prism-editor-textarea {
60
+ background: transparent;
61
+ color: transparent;
62
+ caret-color: #000;
63
+ resize: none;
64
+ z-index: 1;
65
+ -webkit-text-fill-color: transparent;
66
+ }
67
+
68
+ .prism-editor-textarea::selection {
69
+ background: rgba(173, 214, 255, 0.4);
70
+ }
71
+
72
+ .prism-editor-textarea::-moz-selection {
73
+ background: rgba(173, 214, 255, 0.4);
74
+ }
75
+
76
+ /* Highlighted code overlay */
77
+ .prism-editor-highlight {
78
+ background: #ffffff;
79
+ color: #000;
80
+ pointer-events: none;
81
+ z-index: 0;
82
+ }
83
+
84
+ .prism-editor-highlight code {
85
+ font-family: inherit;
86
+ font-size: inherit;
87
+ background: none;
88
+ padding: 0;
89
+ }
90
+
91
+ /* Prism syntax highlighting colors */
92
+ .prism-editor-highlight .token.comment { color: #6a9955; }
93
+ .prism-editor-highlight .token.keyword { color: #0000ff; }
94
+ .prism-editor-highlight .token.string { color: #a31515; }
95
+ .prism-editor-highlight .token.number { color: #098658; }
96
+ .prism-editor-highlight .token.operator { color: #000000; }
97
+ .prism-editor-highlight .token.function { color: #795e26; }
98
+ .prism-editor-highlight .token.class-name { color: #267f99; }
99
+ .prism-editor-highlight .token.punctuation { color: #000000; }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Lightweight Prism Editor
3
+ *
4
+ * Simple textarea with Prism syntax highlighting overlay.
5
+ * Uses the existing Prism.js dependency - adds ~0 extra bytes to bundle.
6
+ */
7
+ import 'prismjs/components/prism-c';
8
+ import 'prismjs/components/prism-cpp';
9
+ import './prism-editor.css';
10
+ export interface EditorInstance {
11
+ getSource: () => string;
12
+ setSource: (source: string) => void;
13
+ destroy: () => void;
14
+ }
15
+ /**
16
+ * Create a lightweight editor with Prism syntax highlighting.
17
+ */
18
+ export declare function createEditor(container: HTMLElement, initialSource: string, onChange?: (source: string) => void): EditorInstance;
19
+ //# sourceMappingURL=prism-editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prism-editor.d.ts","sourceRoot":"","sources":["../../src/editor/prism-editor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,4BAA4B,CAAC;AACpC,OAAO,8BAA8B,CAAC;AAEtC,OAAO,oBAAoB,CAAC;AAE5B,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,WAAW,EACtB,aAAa,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAClC,cAAc,CAiGhB"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Lightweight Prism Editor
3
+ *
4
+ * Simple textarea with Prism syntax highlighting overlay.
5
+ * Uses the existing Prism.js dependency - adds ~0 extra bytes to bundle.
6
+ */
7
+ import * as Prism from 'prismjs';
8
+ import 'prismjs/components/prism-c';
9
+ import 'prismjs/components/prism-cpp';
10
+ import './prism-editor.css';
11
+ /**
12
+ * Create a lightweight editor with Prism syntax highlighting.
13
+ */
14
+ export function createEditor(container, initialSource, onChange) {
15
+ // Create wrapper
16
+ const wrapper = document.createElement('div');
17
+ wrapper.className = 'prism-editor-wrapper';
18
+ // Create line numbers
19
+ const lineNumbers = document.createElement('div');
20
+ lineNumbers.className = 'prism-editor-line-numbers';
21
+ // Create editor area (textarea + highlighted overlay)
22
+ const editorArea = document.createElement('div');
23
+ editorArea.className = 'prism-editor-area';
24
+ // Create textarea (user types here)
25
+ const textarea = document.createElement('textarea');
26
+ textarea.className = 'prism-editor-textarea';
27
+ textarea.value = initialSource;
28
+ textarea.spellcheck = false;
29
+ textarea.autocapitalize = 'off';
30
+ textarea.autocomplete = 'off';
31
+ // Create highlighted code overlay
32
+ const highlight = document.createElement('pre');
33
+ highlight.className = 'prism-editor-highlight';
34
+ const code = document.createElement('code');
35
+ code.className = 'language-cpp';
36
+ highlight.appendChild(code);
37
+ // Assemble
38
+ editorArea.appendChild(textarea);
39
+ editorArea.appendChild(highlight);
40
+ wrapper.appendChild(lineNumbers);
41
+ wrapper.appendChild(editorArea);
42
+ container.appendChild(wrapper);
43
+ // Update highlighting and line numbers
44
+ function update() {
45
+ const source = textarea.value;
46
+ // Update highlighted code
47
+ // Add a trailing newline to prevent layout shift when typing at end
48
+ code.textContent = source + '\n';
49
+ Prism.highlightElement(code);
50
+ // Update line numbers
51
+ const lines = source.split('\n');
52
+ lineNumbers.innerHTML = lines.map((_, i) => `<span>${i + 1}</span>`).join('');
53
+ // Notify listener
54
+ if (onChange) {
55
+ onChange(source);
56
+ }
57
+ }
58
+ // Sync scroll position
59
+ function syncScroll() {
60
+ highlight.scrollTop = textarea.scrollTop;
61
+ highlight.scrollLeft = textarea.scrollLeft;
62
+ lineNumbers.scrollTop = textarea.scrollTop;
63
+ }
64
+ // Handle tab key for indentation
65
+ function handleKeydown(e) {
66
+ if (e.key === 'Tab') {
67
+ e.preventDefault();
68
+ const start = textarea.selectionStart;
69
+ const end = textarea.selectionEnd;
70
+ const value = textarea.value;
71
+ // Insert 2 spaces
72
+ textarea.value = value.substring(0, start) + ' ' + value.substring(end);
73
+ textarea.selectionStart = textarea.selectionEnd = start + 2;
74
+ update();
75
+ }
76
+ }
77
+ // Set up event listeners
78
+ textarea.addEventListener('input', update);
79
+ textarea.addEventListener('scroll', syncScroll);
80
+ textarea.addEventListener('keydown', handleKeydown);
81
+ // Initial render
82
+ update();
83
+ return {
84
+ getSource: () => textarea.value,
85
+ setSource: (source) => {
86
+ textarea.value = source;
87
+ update();
88
+ },
89
+ destroy: () => {
90
+ textarea.removeEventListener('input', update);
91
+ textarea.removeEventListener('scroll', syncScroll);
92
+ textarea.removeEventListener('keydown', handleKeydown);
93
+ container.removeChild(wrapper);
94
+ },
95
+ };
96
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Embeddable Entry Point
3
+ */
4
+ import './styles/embed.css';
5
+ import { App } from './app/App';
6
+ import { DEMO_NAME } from './project/generatedLoader';
7
+ export interface EmbedOptions {
8
+ container: HTMLElement | string;
9
+ pixelRatio?: number;
10
+ }
11
+ export interface EmbedResult {
12
+ app: App;
13
+ destroy: () => void;
14
+ }
15
+ export declare function embed(options: EmbedOptions): Promise<EmbedResult>;
16
+ export { DEMO_NAME };
17
+ //# sourceMappingURL=embed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embed.d.ts","sourceRoot":"","sources":["../src/embed.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,oBAAoB,CAAC;AAE5B,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEhC,OAAO,EAAmB,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAEvE,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,WAAW,GAAG,MAAM,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,GAAG,CAAC;IACT,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAgCvE;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Embeddable Entry Point
3
+ */
4
+ import './styles/embed.css'; // <-- changed from base.css
5
+ import { App } from './app/App';
6
+ import { createLayout } from './layouts';
7
+ import { loadDemoProject, DEMO_NAME } from './project/generatedLoader';
8
+ export async function embed(options) {
9
+ const container = typeof options.container === 'string'
10
+ ? document.querySelector(options.container)
11
+ : options.container;
12
+ if (!container || !(container instanceof HTMLElement)) {
13
+ throw new Error(`Container not found: ${options.container}`);
14
+ }
15
+ const project = await loadDemoProject();
16
+ const layout = createLayout(project.layout, {
17
+ container,
18
+ project,
19
+ });
20
+ const app = new App({
21
+ container: layout.getCanvasContainer(),
22
+ project,
23
+ pixelRatio: options.pixelRatio ?? window.devicePixelRatio,
24
+ });
25
+ if (!app.hasErrors()) {
26
+ app.start();
27
+ }
28
+ return {
29
+ app,
30
+ destroy: () => {
31
+ app.stop?.();
32
+ },
33
+ };
34
+ }
35
+ export { DEMO_NAME };