@littlecarlito/blorktools 0.50.3 → 0.51.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.
- package/bin/cli.js +69 -0
- package/package.json +13 -7
- package/src/asset_debugger/axis-indicator/axis-indicator.css +6 -0
- package/src/asset_debugger/axis-indicator/axis-indicator.html +20 -0
- package/src/asset_debugger/axis-indicator/axis-indicator.js +822 -0
- package/src/asset_debugger/debugger-scene/debugger-scene.css +142 -0
- package/src/asset_debugger/debugger-scene/debugger-scene.html +80 -0
- package/src/asset_debugger/debugger-scene/debugger-scene.js +791 -0
- package/src/asset_debugger/header/header.css +73 -0
- package/src/asset_debugger/header/header.html +24 -0
- package/src/asset_debugger/header/header.js +224 -0
- package/src/asset_debugger/index.html +76 -0
- package/src/asset_debugger/landing-page/landing-page.css +396 -0
- package/src/asset_debugger/landing-page/landing-page.html +81 -0
- package/src/asset_debugger/landing-page/landing-page.js +611 -0
- package/src/asset_debugger/loading-splash/loading-splash.css +195 -0
- package/src/asset_debugger/loading-splash/loading-splash.html +22 -0
- package/src/asset_debugger/loading-splash/loading-splash.js +59 -0
- package/src/asset_debugger/loading-splash/preview-loading-splash.js +66 -0
- package/src/asset_debugger/main.css +14 -0
- package/src/asset_debugger/modals/examples-modal/examples-modal.css +41 -0
- package/src/asset_debugger/modals/examples-modal/examples-modal.html +18 -0
- package/src/asset_debugger/modals/examples-modal/examples-modal.js +111 -0
- package/src/asset_debugger/modals/examples-modal/examples.js +125 -0
- package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.css +452 -0
- package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.html +87 -0
- package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.js +675 -0
- package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.css +219 -0
- package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.html +20 -0
- package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.js +548 -0
- package/src/asset_debugger/modals/settings-modal/settings-modal.css +103 -0
- package/src/asset_debugger/modals/settings-modal/settings-modal.html +158 -0
- package/src/asset_debugger/modals/settings-modal/settings-modal.js +475 -0
- package/src/asset_debugger/panels/asset-panel/asset-panel.css +263 -0
- package/src/asset_debugger/panels/asset-panel/asset-panel.html +123 -0
- package/src/asset_debugger/panels/asset-panel/asset-panel.js +136 -0
- package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.css +94 -0
- package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.js +312 -0
- package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.css +129 -0
- package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.js +486 -0
- package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.css +545 -0
- package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.js +538 -0
- package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.css +70 -0
- package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.js +586 -0
- package/src/asset_debugger/panels/world-panel/world-panel.css +364 -0
- package/src/asset_debugger/panels/world-panel/world-panel.html +173 -0
- package/src/asset_debugger/panels/world-panel/world-panel.js +1891 -0
- package/src/asset_debugger/router.js +190 -0
- package/src/asset_debugger/util/animation/playback/animation-playback-controller.js +150 -0
- package/src/asset_debugger/util/animation/playback/animation-preview-controller.js +316 -0
- package/src/asset_debugger/util/animation/playback/css3d-bounce-controller.js +400 -0
- package/src/asset_debugger/util/animation/playback/css3d-reversal-controller.js +821 -0
- package/src/asset_debugger/util/animation/render/css3d-prerender-controller.js +696 -0
- package/src/asset_debugger/util/animation/render/debug-texture-factory.js +0 -0
- package/src/asset_debugger/util/animation/render/iframe2texture-render-controller.js +199 -0
- package/src/asset_debugger/util/animation/render/image2texture-prerender-controller.js +461 -0
- package/src/asset_debugger/util/animation/render/pbr-material-factory.js +82 -0
- package/src/asset_debugger/util/common.css +280 -0
- package/src/asset_debugger/util/data/animation-classifier.js +323 -0
- package/src/asset_debugger/util/data/duplicate-handler.js +20 -0
- package/src/asset_debugger/util/data/glb-buffer-manager.js +407 -0
- package/src/asset_debugger/util/data/glb-classifier.js +290 -0
- package/src/asset_debugger/util/data/html-formatter.js +76 -0
- package/src/asset_debugger/util/data/html-linter.js +276 -0
- package/src/asset_debugger/util/data/localstorage-manager.js +265 -0
- package/src/asset_debugger/util/data/mesh-html-manager.js +295 -0
- package/src/asset_debugger/util/data/string-serder.js +303 -0
- package/src/asset_debugger/util/data/texture-classifier.js +663 -0
- package/src/asset_debugger/util/data/upload/background-file-handler.js +292 -0
- package/src/asset_debugger/util/data/upload/dropzone-preview-controller.js +396 -0
- package/src/asset_debugger/util/data/upload/file-upload-manager.js +495 -0
- package/src/asset_debugger/util/data/upload/glb-file-handler.js +36 -0
- package/src/asset_debugger/util/data/upload/glb-preview-controller.js +317 -0
- package/src/asset_debugger/util/data/upload/lighting-file-handler.js +194 -0
- package/src/asset_debugger/util/data/upload/model-file-manager.js +104 -0
- package/src/asset_debugger/util/data/upload/texture-file-handler.js +166 -0
- package/src/asset_debugger/util/data/upload/zip-handler.js +686 -0
- package/src/asset_debugger/util/loaders/html2canvas-loader.js +107 -0
- package/src/asset_debugger/util/rig/bone-kinematics.js +403 -0
- package/src/asset_debugger/util/rig/rig-constraint-manager.js +618 -0
- package/src/asset_debugger/util/rig/rig-controller.js +612 -0
- package/src/asset_debugger/util/rig/rig-factory.js +628 -0
- package/src/asset_debugger/util/rig/rig-handle-factory.js +46 -0
- package/src/asset_debugger/util/rig/rig-label-factory.js +441 -0
- package/src/asset_debugger/util/rig/rig-mouse-handler.js +377 -0
- package/src/asset_debugger/util/rig/rig-state-manager.js +175 -0
- package/src/asset_debugger/util/rig/rig-tooltip-manager.js +267 -0
- package/src/asset_debugger/util/rig/rig-ui-factory.js +700 -0
- package/src/asset_debugger/util/scene/background-manager.js +284 -0
- package/src/asset_debugger/util/scene/camera-controller.js +243 -0
- package/src/asset_debugger/util/scene/css3d-debug-controller.js +406 -0
- package/src/asset_debugger/util/scene/css3d-frame-factory.js +113 -0
- package/src/asset_debugger/util/scene/css3d-scene-manager.js +529 -0
- package/src/asset_debugger/util/scene/glb-controller.js +208 -0
- package/src/asset_debugger/util/scene/lighting-manager.js +690 -0
- package/src/asset_debugger/util/scene/threejs-model-manager.js +437 -0
- package/src/asset_debugger/util/scene/threejs-preview-manager.js +207 -0
- package/src/asset_debugger/util/scene/threejs-preview-setup.js +478 -0
- package/src/asset_debugger/util/scene/threejs-scene-controller.js +286 -0
- package/src/asset_debugger/util/scene/ui-manager.js +107 -0
- package/src/asset_debugger/util/state/animation-state.js +128 -0
- package/src/asset_debugger/util/state/css3d-state.js +83 -0
- package/src/asset_debugger/util/state/glb-preview-state.js +31 -0
- package/src/asset_debugger/util/state/log-util.js +197 -0
- package/src/asset_debugger/util/state/scene-state.js +452 -0
- package/src/asset_debugger/util/state/threejs-state.js +54 -0
- package/src/asset_debugger/util/workers/lighting-worker.js +61 -0
- package/src/asset_debugger/util/workers/model-worker.js +109 -0
- package/src/asset_debugger/util/workers/texture-worker.js +54 -0
- package/src/asset_debugger/util/workers/worker-manager.js +212 -0
- package/src/asset_debugger/widgets/mesh-info-widget.js +280 -0
- package/src/index.html +261 -0
- package/src/index.js +8 -0
- package/vite.config.js +66 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { getState } from "../state/scene-state";
|
|
2
|
+
import { deserializeStringFromBinary, serializeStringWithSettingsToBinary } from "./string-serder";
|
|
3
|
+
import { defaultSettings, getSettingsFromForm } from "../../modals/html-editor-modal/html-editor-modal";
|
|
4
|
+
import { associateBinaryBufferWithMesh, getBinaryBufferForMesh } from "./glb-buffer-manager";
|
|
5
|
+
import { updateGlbFile, getCurrentGlbBuffer } from "../scene/glb-controller";
|
|
6
|
+
import { meshesWithHtml } from "../../panels/asset-panel/mesh-heading/mesh-heading";
|
|
7
|
+
|
|
8
|
+
const meshHtmlContent = new Map();
|
|
9
|
+
const meshHtmlSettings = new Map();
|
|
10
|
+
|
|
11
|
+
export function saveSettingsForMesh(meshId, settings) {
|
|
12
|
+
meshHtmlSettings.set(meshId, settings);
|
|
13
|
+
|
|
14
|
+
const state = getState();
|
|
15
|
+
if (state.meshes && state.meshes[meshId]) {
|
|
16
|
+
if (!state.meshes[meshId].userData) {
|
|
17
|
+
state.meshes[meshId].userData = {};
|
|
18
|
+
}
|
|
19
|
+
state.meshes[meshId].userData.htmlSettings = settings;
|
|
20
|
+
console.log(`Saved HTML settings for mesh: ${state.meshes[meshId].name}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function loadSettingsForMesh(meshId) {
|
|
25
|
+
return new Promise(resolve => {
|
|
26
|
+
const state = getState();
|
|
27
|
+
const glbBuffer = getCurrentGlbBuffer();
|
|
28
|
+
|
|
29
|
+
let settings = meshHtmlSettings.get(meshId);
|
|
30
|
+
|
|
31
|
+
if (!settings && glbBuffer) {
|
|
32
|
+
getBinaryBufferForMesh(glbBuffer, meshId).then(binaryBuffer => {
|
|
33
|
+
if (binaryBuffer && binaryBuffer.byteLength > 0) {
|
|
34
|
+
const result = deserializeStringFromBinary(binaryBuffer);
|
|
35
|
+
if (result.settings) {
|
|
36
|
+
settings = result.settings;
|
|
37
|
+
meshHtmlSettings.set(meshId, settings);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
finishLoading();
|
|
42
|
+
}).catch(err => {
|
|
43
|
+
console.error('Error loading settings from buffer:', err);
|
|
44
|
+
finishLoading();
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
finishLoading();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function finishLoading() {
|
|
51
|
+
settings = settings || { ...defaultSettings };
|
|
52
|
+
console.log('Using settings for UI update:', settings);
|
|
53
|
+
|
|
54
|
+
const renderTypeSelect = document.getElementById('html-render-type');
|
|
55
|
+
if (renderTypeSelect) {
|
|
56
|
+
if (settings.previewMode && ['threejs', 'css3d', 'longExposure'].includes(settings.previewMode)) {
|
|
57
|
+
renderTypeSelect.value = settings.previewMode;
|
|
58
|
+
} else {
|
|
59
|
+
renderTypeSelect.value = defaultSettings.previewMode;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const playbackSpeedSelect = document.getElementById('html-playback-speed');
|
|
64
|
+
if (playbackSpeedSelect) {
|
|
65
|
+
const playbackSpeed = settings.playbackSpeed !== undefined ? settings.playbackSpeed : defaultSettings.playbackSpeed;
|
|
66
|
+
|
|
67
|
+
let found = false;
|
|
68
|
+
for (let i = 0; i < playbackSpeedSelect.options.length; i++) {
|
|
69
|
+
const optionValue = parseFloat(playbackSpeedSelect.options[i].value);
|
|
70
|
+
if (Math.abs(optionValue - playbackSpeed) < 0.01) {
|
|
71
|
+
playbackSpeedSelect.value = playbackSpeedSelect.options[i].value;
|
|
72
|
+
found = true;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!found) {
|
|
78
|
+
console.log(`No matching option found for playback speed: ${playbackSpeed}, using default`);
|
|
79
|
+
playbackSpeedSelect.value = "1.0";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const animationTypeSelect = document.getElementById('html-animation-type');
|
|
84
|
+
if (animationTypeSelect) {
|
|
85
|
+
const animationType = settings.animation && settings.animation.type !== undefined
|
|
86
|
+
? settings.animation.type
|
|
87
|
+
: 'none';
|
|
88
|
+
animationTypeSelect.value = animationType;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const showWireframeCheckbox = document.getElementById('show-wireframe');
|
|
92
|
+
if (showWireframeCheckbox && settings.display) {
|
|
93
|
+
showWireframeCheckbox.checked = settings.display.showBorders !== undefined
|
|
94
|
+
? settings.display.showBorders
|
|
95
|
+
: true;
|
|
96
|
+
window.showPreviewBorders = showWireframeCheckbox.checked;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const renderTypeValue = renderTypeSelect ? renderTypeSelect.value : 'threejs';
|
|
100
|
+
const dropdownsContainer = document.getElementById('editor-dropdowns-container');
|
|
101
|
+
if (dropdownsContainer) {
|
|
102
|
+
if (renderTypeValue === 'longExposure') {
|
|
103
|
+
dropdownsContainer.classList.add('long-exposure-mode');
|
|
104
|
+
} else {
|
|
105
|
+
dropdownsContainer.classList.remove('long-exposure-mode');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
resolve(settings);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function getHtmlSettingsForMesh(meshId) {
|
|
115
|
+
const glbBuffer = getCurrentGlbBuffer();
|
|
116
|
+
if (!glbBuffer) {
|
|
117
|
+
console.warn('No GLB buffer available to load settings from');
|
|
118
|
+
return { ...defaultSettings };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const binaryBuffer = await getBinaryBufferForMesh(glbBuffer, meshId);
|
|
123
|
+
|
|
124
|
+
if (!binaryBuffer || binaryBuffer.byteLength === 0) {
|
|
125
|
+
return { ...defaultSettings };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const result = deserializeStringFromBinary(binaryBuffer);
|
|
129
|
+
return result.settings || { ...defaultSettings };
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('Error loading settings from GLB binary buffer:', error);
|
|
132
|
+
return { ...defaultSettings };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function loadHtmlForMesh(meshId, forceReload = false) {
|
|
137
|
+
const cachedHtml = meshHtmlContent.get(meshId);
|
|
138
|
+
if (!forceReload && cachedHtml !== undefined) {
|
|
139
|
+
return cachedHtml;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const glbBuffer = getCurrentGlbBuffer();
|
|
143
|
+
if (!glbBuffer) {
|
|
144
|
+
console.warn('No GLB buffer available to load HTML from');
|
|
145
|
+
return '';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const binaryBuffer = await getBinaryBufferForMesh(glbBuffer, meshId);
|
|
150
|
+
|
|
151
|
+
if (!binaryBuffer) {
|
|
152
|
+
return '';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const result = deserializeStringFromBinary(binaryBuffer);
|
|
156
|
+
let htmlContent = result.content || '';
|
|
157
|
+
const settings = result.settings;
|
|
158
|
+
|
|
159
|
+
if (settings) {
|
|
160
|
+
console.log(`Loaded settings for mesh ID ${meshId}:`, settings);
|
|
161
|
+
meshHtmlSettings.set(meshId, settings);
|
|
162
|
+
|
|
163
|
+
const state = getState();
|
|
164
|
+
if (state.meshes && state.meshes[meshId]) {
|
|
165
|
+
if (!state.meshes[meshId].userData) {
|
|
166
|
+
state.meshes[meshId].userData = {};
|
|
167
|
+
}
|
|
168
|
+
state.meshes[meshId].userData.htmlSettings = settings;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (htmlContent.trim() === '') {
|
|
173
|
+
htmlContent = '';
|
|
174
|
+
} else {
|
|
175
|
+
console.log(`Loaded content for mesh ID ${meshId}: ${htmlContent.substring(0, 50)}${htmlContent.length > 50 ? '...' : ''}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (htmlContent && htmlContent.trim() !== '') {
|
|
179
|
+
meshHtmlContent.set(meshId, htmlContent);
|
|
180
|
+
console.log(`Successfully loaded content for mesh ID ${meshId}`);
|
|
181
|
+
} else {
|
|
182
|
+
meshHtmlContent.delete(meshId);
|
|
183
|
+
console.log(`No valid content found for mesh ID ${meshId}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return htmlContent;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('Error loading content from binary buffer:', error);
|
|
189
|
+
throw new Error(`Failed to load data: ${error.message}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function saveHtmlForMesh(meshId, content) {
|
|
194
|
+
const isEmpty = !content || content.trim() === '';
|
|
195
|
+
|
|
196
|
+
const glbBuffer = getCurrentGlbBuffer();
|
|
197
|
+
|
|
198
|
+
if (!glbBuffer) {
|
|
199
|
+
console.warn(`No GLB buffer available, content for mesh ID ${meshId} saved in memory only`);
|
|
200
|
+
throw new Error('No GLB buffer available to save content. Your changes are saved in memory but will be lost when you reload.');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const settings = getSettingsFromForm();
|
|
205
|
+
|
|
206
|
+
// Check if settings are different from defaults
|
|
207
|
+
const hasCustomSettings = JSON.stringify(settings) !== JSON.stringify(defaultSettings);
|
|
208
|
+
|
|
209
|
+
if (isEmpty && !hasCustomSettings) {
|
|
210
|
+
console.log(`Removing content and default settings for mesh ID ${meshId}...`);
|
|
211
|
+
|
|
212
|
+
meshHtmlContent.delete(meshId);
|
|
213
|
+
meshHtmlSettings.delete(meshId);
|
|
214
|
+
|
|
215
|
+
const emptyBuffer = new ArrayBuffer(0);
|
|
216
|
+
|
|
217
|
+
const updatedGlb = await associateBinaryBufferWithMesh(
|
|
218
|
+
glbBuffer,
|
|
219
|
+
meshId,
|
|
220
|
+
emptyBuffer
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
await updateGlbFile(updatedGlb);
|
|
224
|
+
|
|
225
|
+
console.log(`Successfully removed content for mesh ID ${meshId}`);
|
|
226
|
+
|
|
227
|
+
window.removeMeshHtmlFlag(meshId);
|
|
228
|
+
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log(`Serializing content and settings for mesh ID ${meshId}...`);
|
|
233
|
+
|
|
234
|
+
meshHtmlContent.set(meshId, content);
|
|
235
|
+
saveSettingsForMesh(meshId, settings);
|
|
236
|
+
|
|
237
|
+
const binaryData = serializeStringWithSettingsToBinary(content || '', settings);
|
|
238
|
+
|
|
239
|
+
console.log(`Associating binary data with mesh ID ${meshId} in GLB...`);
|
|
240
|
+
const updatedGlb = await associateBinaryBufferWithMesh(
|
|
241
|
+
glbBuffer,
|
|
242
|
+
meshId,
|
|
243
|
+
binaryData
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
await updateGlbFile(updatedGlb);
|
|
247
|
+
|
|
248
|
+
console.log(`Successfully saved content and settings for mesh ID ${meshId} to GLB`);
|
|
249
|
+
|
|
250
|
+
const state = getState();
|
|
251
|
+
if (state.meshes && state.meshes[meshId]) {
|
|
252
|
+
console.log(`Saved content for mesh: ${state.meshes[meshId].name}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return true;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error('Error saving content to GLB:', error);
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function clearMeshHtmlSettings(meshId) {
|
|
263
|
+
meshHtmlSettings.delete(meshId);
|
|
264
|
+
console.log(`Cleared cached HTML settings for mesh ID ${meshId}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Check if a mesh has binary content
|
|
269
|
+
* @param {number} meshIndex - The index of the mesh to check
|
|
270
|
+
* @returns {Promise<boolean>} Promise that resolves to true if the mesh has any binary content
|
|
271
|
+
*/
|
|
272
|
+
export async function checkMeshHasHtmlContent(meshIndex) {
|
|
273
|
+
if (window._forcedHtmlStates && meshIndex in window._forcedHtmlStates) {
|
|
274
|
+
console.log(`Using forced HTML state for mesh ${meshIndex}: ${window._forcedHtmlStates[meshIndex]}`);
|
|
275
|
+
return window._forcedHtmlStates[meshIndex];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const glbBuffer = getCurrentGlbBuffer();
|
|
279
|
+
if (!glbBuffer) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const binaryBuffer = await getBinaryBufferForMesh(glbBuffer, meshIndex);
|
|
285
|
+
|
|
286
|
+
if (binaryBuffer && binaryBuffer.byteLength > 0) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return false;
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.error('Error checking if mesh has binary content:', error);
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Serialization/Deserialization Utility
|
|
3
|
+
*
|
|
4
|
+
* This module handles conversion between strings and binary data,
|
|
5
|
+
* as well as validation and formatting of string content.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Serialize string content to binary format
|
|
10
|
+
* @param {string} stringContent - The string content to serialize
|
|
11
|
+
* @returns {ArrayBuffer} The serialized binary data
|
|
12
|
+
*/
|
|
13
|
+
export function serializeStringToBinary(stringContent) {
|
|
14
|
+
console.info("Serializing: " + stringContent);
|
|
15
|
+
|
|
16
|
+
// Create a UTF-8 encoder
|
|
17
|
+
const encoder = new TextEncoder();
|
|
18
|
+
|
|
19
|
+
// Simply encode the string as UTF-8
|
|
20
|
+
// This is the most reliable way to handle all characters
|
|
21
|
+
return encoder.encode(stringContent).buffer;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Serialize string content with HTML settings to binary format
|
|
26
|
+
* @param {string} stringContent - The string content to serialize
|
|
27
|
+
* @param {Object} settings - The HTML settings to include
|
|
28
|
+
* @returns {ArrayBuffer} The serialized binary data with settings metadata
|
|
29
|
+
*/
|
|
30
|
+
export function serializeStringWithSettingsToBinary(stringContent, settings) {
|
|
31
|
+
console.info("Serializing with settings: ", settings);
|
|
32
|
+
|
|
33
|
+
// Create a UTF-8 encoder
|
|
34
|
+
const encoder = new TextEncoder();
|
|
35
|
+
|
|
36
|
+
// Convert settings to JSON string
|
|
37
|
+
const settingsJson = JSON.stringify(settings || {});
|
|
38
|
+
const settingsBytes = encoder.encode(settingsJson);
|
|
39
|
+
|
|
40
|
+
// Create a header with a magic number and settings length
|
|
41
|
+
const MAGIC = 0x48544D4C; // "HTML" in ASCII
|
|
42
|
+
const VERSION = 1; // Version 1 of our format
|
|
43
|
+
|
|
44
|
+
// Calculate total buffer size:
|
|
45
|
+
// 4 bytes for magic + 4 bytes for version + 4 bytes for settings length +
|
|
46
|
+
// settings bytes + content bytes
|
|
47
|
+
const totalSize = 4 + 4 + 4 + settingsBytes.byteLength + encoder.encode(stringContent).byteLength;
|
|
48
|
+
|
|
49
|
+
// Create the buffer
|
|
50
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
51
|
+
const view = new DataView(buffer);
|
|
52
|
+
|
|
53
|
+
// Write magic number
|
|
54
|
+
view.setUint32(0, MAGIC, true);
|
|
55
|
+
|
|
56
|
+
// Write version
|
|
57
|
+
view.setUint32(4, VERSION, true);
|
|
58
|
+
|
|
59
|
+
// Write settings length
|
|
60
|
+
view.setUint32(8, settingsBytes.byteLength, true);
|
|
61
|
+
|
|
62
|
+
// Write settings bytes
|
|
63
|
+
const settingsView = new Uint8Array(buffer, 12, settingsBytes.byteLength);
|
|
64
|
+
settingsView.set(settingsBytes);
|
|
65
|
+
|
|
66
|
+
// Write content bytes
|
|
67
|
+
const contentView = new Uint8Array(buffer, 12 + settingsBytes.byteLength);
|
|
68
|
+
contentView.set(encoder.encode(stringContent));
|
|
69
|
+
|
|
70
|
+
return buffer;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Deserialize binary data to string content
|
|
75
|
+
* @param {ArrayBuffer} binaryData - The binary data to deserialize
|
|
76
|
+
* @returns {Object} Object containing the deserialized content and settings if available
|
|
77
|
+
*/
|
|
78
|
+
export function deserializeStringFromBinary(binaryData) {
|
|
79
|
+
try {
|
|
80
|
+
// Simple check for valid buffer
|
|
81
|
+
if (!binaryData || binaryData.byteLength === 0) {
|
|
82
|
+
console.warn("Empty binary data received");
|
|
83
|
+
return { content: "", settings: null };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check if this is our enhanced format with settings
|
|
87
|
+
if (binaryData.byteLength >= 12) {
|
|
88
|
+
const view = new DataView(binaryData);
|
|
89
|
+
const magic = view.getUint32(0, true);
|
|
90
|
+
const MAGIC = 0x48544D4C; // "HTML" in ASCII
|
|
91
|
+
|
|
92
|
+
if (magic === MAGIC) {
|
|
93
|
+
// This is our enhanced format
|
|
94
|
+
const version = view.getUint32(4, true);
|
|
95
|
+
const settingsLength = view.getUint32(8, true);
|
|
96
|
+
|
|
97
|
+
// Extract settings
|
|
98
|
+
const settingsBytes = new Uint8Array(binaryData, 12, settingsLength);
|
|
99
|
+
const settingsJson = new TextDecoder('utf-8').decode(settingsBytes);
|
|
100
|
+
let settings = {};
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
settings = JSON.parse(settingsJson);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
console.warn("Error parsing settings JSON:", e);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Extract content
|
|
109
|
+
const contentBytes = new Uint8Array(binaryData, 12 + settingsLength);
|
|
110
|
+
const content = new TextDecoder('utf-8').decode(contentBytes);
|
|
111
|
+
|
|
112
|
+
console.info("Deserialized with settings: ", settings);
|
|
113
|
+
return { content, settings };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// If not our enhanced format, treat as legacy format (just content)
|
|
118
|
+
// Create a view of the binary data to check for null terminators
|
|
119
|
+
const dataView = new Uint8Array(binaryData);
|
|
120
|
+
|
|
121
|
+
// Find the actual length of the string (stop at first null byte if any)
|
|
122
|
+
let actualLength = dataView.length;
|
|
123
|
+
for (let i = 0; i < dataView.length; i++) {
|
|
124
|
+
if (dataView[i] === 0) {
|
|
125
|
+
actualLength = i;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Create a clean buffer without any padding bytes
|
|
131
|
+
const cleanBuffer = binaryData.slice(0, actualLength);
|
|
132
|
+
|
|
133
|
+
// Convert binary to string using UTF-8 decoder
|
|
134
|
+
const decoder = new TextDecoder('utf-8');
|
|
135
|
+
const content = decoder.decode(cleanBuffer);
|
|
136
|
+
|
|
137
|
+
// Log a preview of the content
|
|
138
|
+
const previewLength = Math.min(content.length, 50);
|
|
139
|
+
const contentPreview = content.substring(0, previewLength) +
|
|
140
|
+
(content.length > previewLength ? "..." : "");
|
|
141
|
+
console.info("Deserialized to: " + contentPreview);
|
|
142
|
+
|
|
143
|
+
return { content, settings: null };
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error("Error deserializing binary data:", error);
|
|
146
|
+
return { content: "", settings: null };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Check if a string appears to be valid HTML
|
|
152
|
+
* @param {string} content - The string to check
|
|
153
|
+
* @returns {boolean} True if the string appears to be valid HTML
|
|
154
|
+
*/
|
|
155
|
+
export function isValidHtml(content) {
|
|
156
|
+
if (!content || typeof content !== 'string') {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// If it's empty or just whitespace, it's not valid HTML
|
|
161
|
+
if (content.trim() === '') {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check for common HTML markers
|
|
166
|
+
const hasHtmlTags = content.includes('<') && content.includes('>');
|
|
167
|
+
|
|
168
|
+
// Check for binary data (a high percentage of non-printable characters)
|
|
169
|
+
const nonPrintableCount = (content.match(/[^\x20-\x7E]/g) || []).length;
|
|
170
|
+
const nonPrintablePercentage = nonPrintableCount / content.length;
|
|
171
|
+
|
|
172
|
+
// If more than 20% of the characters are non-printable, it's probably binary data
|
|
173
|
+
const isBinaryData = nonPrintablePercentage > 0.2;
|
|
174
|
+
|
|
175
|
+
return hasHtmlTags && !isBinaryData;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Format HTML code with proper indentation
|
|
180
|
+
* @param {string} html - The HTML code to format
|
|
181
|
+
* @returns {string} The formatted HTML
|
|
182
|
+
*/
|
|
183
|
+
export function formatHtml(html) {
|
|
184
|
+
if (!html || html.trim() === '') return '';
|
|
185
|
+
|
|
186
|
+
// Simple HTML formatting logic
|
|
187
|
+
let formatted = '';
|
|
188
|
+
let indent = 0;
|
|
189
|
+
const lines = html.replace(/>\s*</g, '>\n<').split('\n');
|
|
190
|
+
|
|
191
|
+
lines.forEach(line => {
|
|
192
|
+
line = line.trim();
|
|
193
|
+
if (!line) return;
|
|
194
|
+
|
|
195
|
+
// Check if line contains closing tag
|
|
196
|
+
if (line.match(/^<\/.*>$/)) {
|
|
197
|
+
indent -= 2; // Reduce indent for closing tag
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Add indentation
|
|
201
|
+
formatted += ' '.repeat(Math.max(0, indent)) + line + '\n';
|
|
202
|
+
|
|
203
|
+
// Check if line contains opening tag and not self-closing
|
|
204
|
+
if (line.match(/^<[^/].*[^/]>$/) && !line.match(/<.*\/.*>/)) {
|
|
205
|
+
indent += 2; // Increase indent for next line
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return formatted;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Enhanced HTML sanitization that preserves styling and safe scripts
|
|
214
|
+
* @param {string} html - The HTML to sanitize
|
|
215
|
+
* @returns {string} The sanitized HTML
|
|
216
|
+
*/
|
|
217
|
+
export function sanitizeHtml(html) {
|
|
218
|
+
if (!html || typeof html !== 'string') {
|
|
219
|
+
return '';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Resizer script to help with iframe sizing
|
|
223
|
+
const resizerScript = `
|
|
224
|
+
<script>
|
|
225
|
+
// Helper function to adjust iframe height
|
|
226
|
+
function notifySize() {
|
|
227
|
+
try {
|
|
228
|
+
const height = document.body.scrollHeight;
|
|
229
|
+
window.parent.postMessage({ type: 'resize', height: height }, '*');
|
|
230
|
+
|
|
231
|
+
// Observe DOM changes to adjust size dynamically
|
|
232
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
233
|
+
const updatedHeight = document.body.scrollHeight;
|
|
234
|
+
window.parent.postMessage({ type: 'resize', height: updatedHeight }, '*');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
resizeObserver.observe(document.body);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.error('Error in size notification:', error);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Run on load
|
|
244
|
+
window.addEventListener('load', notifySize);
|
|
245
|
+
</script>`;
|
|
246
|
+
|
|
247
|
+
// For full document structures with doctype, html and body tags
|
|
248
|
+
if (html.includes('<!DOCTYPE') || html.includes('<html')) {
|
|
249
|
+
// Ensure the HTML has a transparent background
|
|
250
|
+
// Check if there's a head section to add the transparent background style
|
|
251
|
+
if (!html.includes('background-color: transparent') && !html.includes('background:transparent')) {
|
|
252
|
+
// Add transparent background style if not already present
|
|
253
|
+
if (html.includes('</head>')) {
|
|
254
|
+
html = html.replace('</head>', '<style>body { background-color: transparent; }</style></head>');
|
|
255
|
+
} else if (html.includes('<body')) {
|
|
256
|
+
// Add style attribute to body tag if no head section
|
|
257
|
+
html = html.replace(/<body([^>]*)>/i, '<body$1 style="background-color: transparent;">');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Insert resizer script before the closing body tag
|
|
262
|
+
if (html.includes('</body>')) {
|
|
263
|
+
return html.replace('</body>', `${resizerScript}</body>`);
|
|
264
|
+
} else if (html.includes('</html>')) {
|
|
265
|
+
return html.replace('</html>', `${resizerScript}</html>`);
|
|
266
|
+
} else {
|
|
267
|
+
// If no closing tags found, just append
|
|
268
|
+
return html + resizerScript;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// For HTML fragments, wrap them in a basic document structure
|
|
273
|
+
// This helps ensure styles and scripts work properly
|
|
274
|
+
return `<!DOCTYPE html>
|
|
275
|
+
<html>
|
|
276
|
+
<head>
|
|
277
|
+
<meta charset="UTF-8">
|
|
278
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
279
|
+
<style>
|
|
280
|
+
body {
|
|
281
|
+
margin: 0;
|
|
282
|
+
padding: 0;
|
|
283
|
+
background-color: transparent;
|
|
284
|
+
}
|
|
285
|
+
</style>
|
|
286
|
+
<title>HTML Preview</title>
|
|
287
|
+
</head>
|
|
288
|
+
<body>
|
|
289
|
+
${html}
|
|
290
|
+
${resizerScript}
|
|
291
|
+
</body>
|
|
292
|
+
</html>`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Export default for convenience
|
|
296
|
+
export default {
|
|
297
|
+
serializeStringToBinary,
|
|
298
|
+
serializeStringWithSettingsToBinary,
|
|
299
|
+
deserializeStringFromBinary,
|
|
300
|
+
isValidHtml,
|
|
301
|
+
formatHtml,
|
|
302
|
+
sanitizeHtml
|
|
303
|
+
};
|