xlsxrb 0.1.2 → 0.1.4
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.
- checksums.yaml +4 -4
- data/.devcontainer/Dockerfile +1 -0
- data/README.md +57 -264
- data/Rakefile +164 -17
- data/benchmark.rb +24 -14
- data/docs/DEVELOPMENT.md +67 -0
- data/docs/coi-serviceworker.js +82 -0
- data/docs/office_thread.js +77 -0
- data/docs/preview.html +719 -0
- data/docs/visual/VisualGallery.md +0 -158
- data/docs/wasm/wasm_doc_helper.css +256 -94
- data/docs/wasm/wasm_doc_helper.js +261 -142
- data/docs/zeta.js +1107 -0
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +178 -24
- data/lib/xlsxrb/ooxml.rb +1 -1
- data/lib/xlsxrb/version.rb +1 -1
- metadata +6 -29
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
document.addEventListener("DOMContentLoaded", () => {
|
|
5
5
|
const codeBlocks = document.querySelectorAll("pre.ruby");
|
|
6
6
|
codeBlocks.forEach((block, index) => {
|
|
7
|
-
|
|
7
|
+
setupLazyPlayground(block, index);
|
|
8
8
|
});
|
|
9
9
|
});
|
|
10
10
|
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
}
|
|
22
22
|
isInitializing = true;
|
|
23
23
|
|
|
24
|
-
// Load browser.umd.js
|
|
25
|
-
|
|
24
|
+
// Load browser.umd.js locally using the relative path prefix
|
|
25
|
+
const prefix = window.rdoc_rel_prefix || "./";
|
|
26
|
+
await loadScript(`${prefix}wasm/browser.umd.js`);
|
|
26
27
|
|
|
27
28
|
// Fetch and compile the custom prepackaged ruby.wasm module
|
|
28
|
-
const prefix = window.rdoc_rel_prefix || "./";
|
|
29
29
|
const response = await fetch(`${prefix}wasm/ruby.wasm?cb=${Date.now()}`);
|
|
30
30
|
const buffer = await response.arrayBuffer();
|
|
31
31
|
const module = await WebAssembly.compile(buffer);
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
throw new Error("DefaultRubyVM is not available");
|
|
62
62
|
}
|
|
63
63
|
const { vm } = await vmInit;
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
rubyVM = vm;
|
|
66
66
|
|
|
67
67
|
isInitializing = false;
|
|
@@ -86,172 +86,268 @@
|
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
// Setup
|
|
90
|
-
function
|
|
89
|
+
// Setup lightweight wrapper and action buttons, deferring full editor instantiation until hover or click
|
|
90
|
+
function setupLazyPlayground(block, index) {
|
|
91
91
|
const originalCode = block.textContent;
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
btn.className = "wasm-try-btn";
|
|
95
|
-
btn.innerHTML = `
|
|
96
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 6px; vertical-align: middle;">
|
|
97
|
-
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
|
98
|
-
</svg>Try it in Browser
|
|
99
|
-
`;
|
|
100
|
-
|
|
93
|
+
// Create wrapper container
|
|
101
94
|
const wrapper = document.createElement("div");
|
|
102
95
|
wrapper.className = "wasm-playground-wrapper";
|
|
103
|
-
|
|
104
96
|
block.parentNode.insertBefore(wrapper, block);
|
|
105
97
|
wrapper.appendChild(block);
|
|
106
|
-
wrapper.insertBefore(btn, block);
|
|
107
|
-
|
|
108
|
-
btn.addEventListener("click", async () => {
|
|
109
|
-
btn.style.display = "none";
|
|
110
|
-
|
|
111
|
-
const loader = document.createElement("div");
|
|
112
|
-
loader.className = "wasm-loader";
|
|
113
|
-
loader.innerHTML = `
|
|
114
|
-
<span class="wasm-loader-text">Loading Ruby Wasm Engine...</span>
|
|
115
|
-
<div class="wasm-spinner"></div>
|
|
116
|
-
`;
|
|
117
|
-
wrapper.insertBefore(loader, block);
|
|
118
98
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
activatePlayground(wrapper, block, originalCode, vm);
|
|
123
|
-
} catch (e) {
|
|
124
|
-
loader.innerHTML = `<span class="wasm-error">Error loading Wasm: ${e.message}</span>`;
|
|
125
|
-
console.error(e);
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Auto-trigger load under E2E testing mode
|
|
130
|
-
if (window.location.hash === "#test-mode" && index === 0) {
|
|
131
|
-
setTimeout(() => {
|
|
132
|
-
console.log("[Script Mock] Test mode detected. Triggering playground load...");
|
|
133
|
-
btn.click();
|
|
134
|
-
|
|
135
|
-
const interval = setInterval(() => {
|
|
136
|
-
const runBtn = wrapper.querySelector(".wasm-run-btn");
|
|
137
|
-
if (runBtn && !runBtn.disabled) {
|
|
138
|
-
clearInterval(interval);
|
|
139
|
-
console.log("[Script Mock] Playground loaded. Triggering run...");
|
|
140
|
-
setTimeout(() => {
|
|
141
|
-
runBtn.click();
|
|
142
|
-
}, 1000);
|
|
143
|
-
}
|
|
144
|
-
}, 500);
|
|
145
|
-
}, 1000);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
99
|
+
// Create action button bar (always visible)
|
|
100
|
+
const btnBar = document.createElement("div");
|
|
101
|
+
btnBar.className = "wasm-quick-action-bar";
|
|
148
102
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
103
|
+
const previewBtn = document.createElement("button");
|
|
104
|
+
previewBtn.className = "wasm-quick-btn wasm-quick-preview-btn";
|
|
105
|
+
previewBtn.innerHTML = `
|
|
106
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 6px; vertical-align: middle;">
|
|
107
|
+
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
|
108
|
+
</svg>Live Preview
|
|
109
|
+
`;
|
|
152
110
|
|
|
153
|
-
const
|
|
154
|
-
|
|
111
|
+
const downloadBtn = document.createElement("button");
|
|
112
|
+
downloadBtn.className = "wasm-quick-btn wasm-quick-download-btn";
|
|
113
|
+
downloadBtn.innerHTML = `
|
|
114
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 6px; vertical-align: middle;">
|
|
115
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
116
|
+
<polyline points="7 10 12 15 17 10"></polyline>
|
|
117
|
+
<line x1="12" y1="15" x2="12" y2="3"></line>
|
|
118
|
+
</svg>Download XLSX
|
|
119
|
+
`;
|
|
155
120
|
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
121
|
+
const resetBtn = document.createElement("button");
|
|
122
|
+
resetBtn.className = "wasm-quick-btn wasm-quick-reset-btn";
|
|
123
|
+
resetBtn.innerHTML = `
|
|
124
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 6px; vertical-align: middle;">
|
|
125
|
+
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
|
|
126
|
+
<polyline points="3 3 3 8 8 8"></polyline>
|
|
127
|
+
</svg>Reset
|
|
128
|
+
`;
|
|
160
129
|
|
|
161
|
-
|
|
162
|
-
|
|
130
|
+
btnBar.appendChild(previewBtn);
|
|
131
|
+
btnBar.appendChild(downloadBtn);
|
|
132
|
+
btnBar.appendChild(resetBtn);
|
|
133
|
+
|
|
134
|
+
// Insert button bar right below the wrapper in the DOM
|
|
135
|
+
wrapper.parentNode.insertBefore(btnBar, wrapper.nextSibling);
|
|
136
|
+
|
|
137
|
+
let isEditorInitialized = false;
|
|
138
|
+
let textarea = null;
|
|
139
|
+
|
|
140
|
+
// Full initialization of the transparent overlay editor (Deferred)
|
|
141
|
+
function initializeEditorOnDemand() {
|
|
142
|
+
if (isEditorInitialized) return;
|
|
143
|
+
isEditorInitialized = true;
|
|
144
|
+
|
|
145
|
+
// Create transparent textarea overlay
|
|
146
|
+
textarea = document.createElement("textarea");
|
|
147
|
+
textarea.className = "wasm-inline-editor";
|
|
148
|
+
textarea.value = originalCode;
|
|
149
|
+
textarea.spellcheck = false;
|
|
150
|
+
wrapper.insertBefore(textarea, block);
|
|
151
|
+
|
|
152
|
+
// Copy exact layout styles from pre block to align overlay perfectly
|
|
153
|
+
const preStyle = window.getComputedStyle(block);
|
|
154
|
+
const stylesToCopy = [
|
|
155
|
+
"fontFamily", "fontSize", "lineHeight", "fontWeight",
|
|
156
|
+
"paddingTop", "paddingBottom", "paddingLeft", "paddingRight",
|
|
157
|
+
"marginTop", "marginBottom", "marginLeft", "marginRight",
|
|
158
|
+
"textAlign"
|
|
159
|
+
];
|
|
160
|
+
stylesToCopy.forEach(prop => {
|
|
161
|
+
textarea.style[prop] = preStyle[prop];
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Enforce overlapping styles
|
|
165
|
+
block.style.position = "relative";
|
|
166
|
+
block.style.pointerEvents = "none"; // Clicks pass to textarea
|
|
167
|
+
block.style.whiteSpace = "pre-wrap";
|
|
168
|
+
block.style.wordBreak = "break-all";
|
|
169
|
+
block.style.zIndex = "1";
|
|
170
|
+
block.style.margin = "0";
|
|
171
|
+
|
|
172
|
+
textarea.style.position = "absolute";
|
|
173
|
+
textarea.style.top = "0";
|
|
174
|
+
textarea.style.left = "0";
|
|
175
|
+
textarea.style.width = "100%";
|
|
176
|
+
textarea.style.height = "100%";
|
|
177
|
+
textarea.style.background = "transparent";
|
|
178
|
+
textarea.style.color = "transparent";
|
|
179
|
+
textarea.style.caretColor = "#2563eb"; // Sleek blue cursor
|
|
180
|
+
textarea.style.border = "none";
|
|
181
|
+
textarea.style.resize = "none";
|
|
182
|
+
textarea.style.overflow = "hidden";
|
|
183
|
+
textarea.style.whiteSpace = "pre-wrap";
|
|
184
|
+
textarea.style.wordBreak = "break-all";
|
|
185
|
+
textarea.style.zIndex = "2";
|
|
186
|
+
textarea.style.outline = "none";
|
|
187
|
+
|
|
188
|
+
textarea.addEventListener("input", updateHighlight);
|
|
189
|
+
updateHighlight();
|
|
190
|
+
}
|
|
163
191
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
192
|
+
function updateHighlight() {
|
|
193
|
+
if (!textarea) return;
|
|
194
|
+
let code = textarea.value;
|
|
195
|
+
if (code.endsWith("\n")) {
|
|
196
|
+
code += " ";
|
|
197
|
+
}
|
|
198
|
+
block.innerHTML = highlightRuby(code);
|
|
167
199
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
200
|
+
// Auto-grow height dynamically to fit text
|
|
201
|
+
textarea.style.height = "auto";
|
|
202
|
+
textarea.style.height = textarea.scrollHeight + "px";
|
|
203
|
+
block.style.height = textarea.scrollHeight + "px";
|
|
204
|
+
wrapper.style.height = textarea.scrollHeight + "px";
|
|
205
|
+
}
|
|
171
206
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
207
|
+
// Trigger full initialization on mouse hover or click
|
|
208
|
+
wrapper.addEventListener("mouseenter", initializeEditorOnDemand, { once: true });
|
|
209
|
+
wrapper.addEventListener("click", initializeEditorOnDemand);
|
|
210
|
+
|
|
211
|
+
// RDoc-compatible Ruby Syntax Highlighter (Safe placeholder method)
|
|
212
|
+
function highlightRuby(code) {
|
|
213
|
+
let html = code
|
|
214
|
+
.replace(/&/g, "&")
|
|
215
|
+
.replace(/</g, "<")
|
|
216
|
+
.replace(/>/g, ">");
|
|
217
|
+
|
|
218
|
+
const placeholders = [];
|
|
219
|
+
function addPlaceholder(val, className) {
|
|
220
|
+
const id = `__WASM_HL_${placeholders.length}__`;
|
|
221
|
+
placeholders.push({ id, val, className });
|
|
222
|
+
return id;
|
|
223
|
+
}
|
|
175
224
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
225
|
+
// 1. Strings (double quotes)
|
|
226
|
+
html = html.replace(/"[^"\\]*(?:\\.[^"\\]*)*"/g, (match) => {
|
|
227
|
+
return addPlaceholder(match, "ruby-string");
|
|
228
|
+
});
|
|
229
|
+
// 2. Strings (single quotes)
|
|
230
|
+
html = html.replace(/'[^'\\]*(?:\\.[^'\\]*)*'/g, (match) => {
|
|
231
|
+
return addPlaceholder(match, "ruby-string");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// 3. Comments (safe from backtracking, only comments starting after whitespace or at line start)
|
|
235
|
+
html = html.replace(/(^|\s)(#[^\n]*)$/gm, (match, p1, p2) => {
|
|
236
|
+
return p1 + addPlaceholder(p2, "ruby-comment");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// 4. Keywords
|
|
240
|
+
const keywords = /\b(require|do|end|if|else|elsif|unless|while|until|for|in|class|module|def|return|true|false|nil)\b/g;
|
|
241
|
+
html = html.replace(keywords, (match) => {
|
|
242
|
+
return addPlaceholder(match, "ruby-keyword");
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// 5. Constants (Capitalized names)
|
|
246
|
+
const constants = /\b([A-Z][a-zA-Z0-9_]*)\b/g;
|
|
247
|
+
html = html.replace(constants, (match) => {
|
|
248
|
+
return addPlaceholder(match, "ruby-constant");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// 6. Simple numeric values
|
|
252
|
+
const numericValues = /\b(\d+)\b/g;
|
|
253
|
+
html = html.replace(numericValues, (match) => {
|
|
254
|
+
return addPlaceholder(match, "ruby-value");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// 7. Specific xlsxrb APIs
|
|
258
|
+
const apis = /\b(generate|add_style|add_sheet|add_row|set_column|set_print_option|border_all|border_bottom|border_top|border_left|border_right|align_horizontal|align_vertical|bold|italic|size|fill_color|number_format)\b/g;
|
|
259
|
+
html = html.replace(apis, (match) => {
|
|
260
|
+
return addPlaceholder(match, "ruby-identifier");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Rehydrate placeholders in reverse order to avoid nested conflicts
|
|
264
|
+
for (let i = placeholders.length - 1; i >= 0; i--) {
|
|
265
|
+
const item = placeholders[i];
|
|
266
|
+
html = html.replace(item.id, `<span class="${item.className}">${item.val}</span>`);
|
|
267
|
+
}
|
|
179
268
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
consoleArea.textContent = "Ready to run. Output and status will be shown here.";
|
|
269
|
+
return html;
|
|
270
|
+
}
|
|
183
271
|
|
|
184
|
-
playgroundContainer.appendChild(textarea);
|
|
185
|
-
playgroundContainer.appendChild(actionBar);
|
|
186
|
-
playgroundContainer.appendChild(consoleArea);
|
|
187
|
-
wrapper.appendChild(playgroundContainer);
|
|
188
272
|
|
|
189
|
-
resetBtn.addEventListener("click", () => {
|
|
190
|
-
textarea.value = code;
|
|
191
|
-
consoleArea.textContent = "Reset to original code.";
|
|
192
|
-
consoleArea.className = "wasm-console";
|
|
193
|
-
});
|
|
194
273
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
274
|
+
// Click: Live Preview (1-click auto run)
|
|
275
|
+
previewBtn.addEventListener("click", () => {
|
|
276
|
+
const codeToRun = textarea ? textarea.value : originalCode;
|
|
277
|
+
triggerLivePreview(codeToRun);
|
|
199
278
|
});
|
|
200
279
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
consoleBuffer = "";
|
|
207
|
-
|
|
208
|
-
// Clear any preexisting XLSX files in the virtual FS root to avoid collision/leak
|
|
209
|
-
clearXlsxFiles(vm, "/");
|
|
280
|
+
// Click: Download XLSX directly
|
|
281
|
+
downloadBtn.addEventListener("click", async () => {
|
|
282
|
+
downloadBtn.disabled = true;
|
|
283
|
+
const originalText = downloadBtn.innerHTML;
|
|
284
|
+
downloadBtn.innerHTML = `Running Wasm...`;
|
|
210
285
|
|
|
211
286
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
287
|
+
const vm = await ensureRubyWasm();
|
|
288
|
+
clearXlsxFiles(vm, "/");
|
|
289
|
+
let userCode = textarea ? textarea.value : originalCode;
|
|
290
|
+
if (!/require\s+['"]xlsxrb['"]/.test(userCode)) {
|
|
214
291
|
userCode = 'require "xlsxrb"\n' + userCode;
|
|
215
292
|
}
|
|
216
|
-
// Force include the current directory in load path
|
|
217
293
|
userCode = '$LOAD_PATH << Dir.pwd unless $LOAD_PATH.include?(Dir.pwd)\n' + userCode;
|
|
218
294
|
|
|
219
|
-
// Evaluate the user code in Wasm VM
|
|
220
295
|
vm.eval(userCode);
|
|
221
|
-
|
|
222
|
-
// Scan virtual FS for new XLSX output files
|
|
223
296
|
const generatedFiles = scanXlsxFiles(vm, "/");
|
|
224
|
-
|
|
225
|
-
consoleArea.className = "wasm-console success";
|
|
226
|
-
let output = consoleBuffer;
|
|
227
|
-
|
|
228
297
|
if (generatedFiles.length > 0) {
|
|
229
|
-
|
|
230
|
-
output += `\n[Success] Generated file: ${mainFile}\nTriggering download...`;
|
|
231
|
-
downloadFileFromVfs(vm, mainFile);
|
|
298
|
+
downloadFileFromVfs(vm, generatedFiles[0]);
|
|
232
299
|
} else {
|
|
233
|
-
|
|
300
|
+
alert("Error: No XLSX file was generated.");
|
|
234
301
|
}
|
|
235
|
-
|
|
236
|
-
consoleArea.textContent = output || "Execution completed successfully (no stdout).";
|
|
237
|
-
|
|
238
302
|
} catch (err) {
|
|
239
|
-
|
|
240
|
-
consoleArea.textContent = `Error during execution:\n${err.message}\n\nConsole output:\n${consoleBuffer}`;
|
|
303
|
+
alert("Error generating XLSX: " + err.message);
|
|
241
304
|
console.error(err);
|
|
242
|
-
|
|
243
|
-
// Send runtime errors back to test server under E2E testing
|
|
244
|
-
if (window.location.hash === "#test-mode") {
|
|
245
|
-
fetch("http://localhost:9001/error", {
|
|
246
|
-
method: "POST",
|
|
247
|
-
body: `Error during execution:\n${err.message}\n\nConsole output:\n${consoleBuffer}`
|
|
248
|
-
}).catch(e => console.error("Failed to post back error:", e));
|
|
249
|
-
}
|
|
250
305
|
} finally {
|
|
251
|
-
|
|
252
|
-
|
|
306
|
+
downloadBtn.disabled = false;
|
|
307
|
+
downloadBtn.innerHTML = originalText;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Click: Reset Code
|
|
312
|
+
resetBtn.addEventListener("click", () => {
|
|
313
|
+
if (textarea) {
|
|
314
|
+
textarea.value = originalCode;
|
|
315
|
+
updateHighlight();
|
|
253
316
|
}
|
|
254
317
|
});
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Open the sliding drawer panel and automatically execute code preview inside the iframe
|
|
324
|
+
function triggerLivePreview(code) {
|
|
325
|
+
const drawer = ensurePreviewDrawer();
|
|
326
|
+
const iframe = document.getElementById("wasmPreviewIframe");
|
|
327
|
+
const prefix = window.rdoc_rel_prefix || "./";
|
|
328
|
+
|
|
329
|
+
// Show the drawer
|
|
330
|
+
drawer.classList.add("open");
|
|
331
|
+
|
|
332
|
+
// Initialize or load the iframe
|
|
333
|
+
const targetSrc = `${prefix}preview.html`;
|
|
334
|
+
if (!iframe.src || !iframe.src.includes("preview.html")) {
|
|
335
|
+
iframe.src = targetSrc;
|
|
336
|
+
iframe.onload = () => {
|
|
337
|
+
sendCodeToIframe();
|
|
338
|
+
};
|
|
339
|
+
} else {
|
|
340
|
+
sendCodeToIframe();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function sendCodeToIframe() {
|
|
344
|
+
if (iframe.contentWindow) {
|
|
345
|
+
iframe.contentWindow.postMessage({
|
|
346
|
+
action: "load_code",
|
|
347
|
+
code: code
|
|
348
|
+
}, "*");
|
|
349
|
+
}
|
|
350
|
+
}
|
|
255
351
|
}
|
|
256
352
|
|
|
257
353
|
// Delete all .xlsx files recursively under the virtual directory
|
|
@@ -305,16 +401,39 @@
|
|
|
305
401
|
document.body.removeChild(a);
|
|
306
402
|
setTimeout(() => URL.revokeObjectURL(url), 10000);
|
|
307
403
|
|
|
308
|
-
// Post binary data back to test server under E2E testing
|
|
309
|
-
if (window.location.hash === "#test-mode") {
|
|
310
|
-
console.log("[Script Mock] Test mode detected. Sending generated file back to test server...");
|
|
311
|
-
fetch("http://localhost:9001/upload", {
|
|
312
|
-
method: "POST",
|
|
313
|
-
body: bytes
|
|
314
|
-
}).catch(err => console.error("Failed to post back test file:", err));
|
|
315
|
-
}
|
|
316
404
|
} catch (e) {
|
|
317
405
|
console.error("Error downloading file from Vfs:", e);
|
|
318
406
|
}
|
|
319
407
|
}
|
|
408
|
+
|
|
409
|
+
// Create or retrieve the side drawer preview panel
|
|
410
|
+
function ensurePreviewDrawer() {
|
|
411
|
+
let drawer = document.getElementById("wasmPreviewDrawer");
|
|
412
|
+
if (drawer) return drawer;
|
|
413
|
+
|
|
414
|
+
drawer = document.createElement("div");
|
|
415
|
+
drawer.id = "wasmPreviewDrawer";
|
|
416
|
+
drawer.className = "wasm-preview-drawer";
|
|
417
|
+
drawer.innerHTML = `
|
|
418
|
+
<div class="drawer-header">
|
|
419
|
+
<span class="drawer-title">Live Spreadsheet Preview</span>
|
|
420
|
+
<button class="drawer-close-btn">×</button>
|
|
421
|
+
</div>
|
|
422
|
+
<div class="drawer-body">
|
|
423
|
+
<iframe id="wasmPreviewIframe" class="drawer-iframe" src=""></iframe>
|
|
424
|
+
</div>
|
|
425
|
+
`;
|
|
426
|
+
document.body.appendChild(drawer);
|
|
427
|
+
|
|
428
|
+
// Setup close action
|
|
429
|
+
const closeBtn = drawer.querySelector(".drawer-close-btn");
|
|
430
|
+
closeBtn.addEventListener("click", () => {
|
|
431
|
+
drawer.classList.remove("open");
|
|
432
|
+
// Clear iframe src on close to stop background Wasm threads and free memory
|
|
433
|
+
const iframe = document.getElementById("wasmPreviewIframe");
|
|
434
|
+
if (iframe) iframe.src = "";
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
return drawer;
|
|
438
|
+
}
|
|
320
439
|
})();
|