@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.
Files changed (114) hide show
  1. package/bin/cli.js +69 -0
  2. package/package.json +13 -7
  3. package/src/asset_debugger/axis-indicator/axis-indicator.css +6 -0
  4. package/src/asset_debugger/axis-indicator/axis-indicator.html +20 -0
  5. package/src/asset_debugger/axis-indicator/axis-indicator.js +822 -0
  6. package/src/asset_debugger/debugger-scene/debugger-scene.css +142 -0
  7. package/src/asset_debugger/debugger-scene/debugger-scene.html +80 -0
  8. package/src/asset_debugger/debugger-scene/debugger-scene.js +791 -0
  9. package/src/asset_debugger/header/header.css +73 -0
  10. package/src/asset_debugger/header/header.html +24 -0
  11. package/src/asset_debugger/header/header.js +224 -0
  12. package/src/asset_debugger/index.html +76 -0
  13. package/src/asset_debugger/landing-page/landing-page.css +396 -0
  14. package/src/asset_debugger/landing-page/landing-page.html +81 -0
  15. package/src/asset_debugger/landing-page/landing-page.js +611 -0
  16. package/src/asset_debugger/loading-splash/loading-splash.css +195 -0
  17. package/src/asset_debugger/loading-splash/loading-splash.html +22 -0
  18. package/src/asset_debugger/loading-splash/loading-splash.js +59 -0
  19. package/src/asset_debugger/loading-splash/preview-loading-splash.js +66 -0
  20. package/src/asset_debugger/main.css +14 -0
  21. package/src/asset_debugger/modals/examples-modal/examples-modal.css +41 -0
  22. package/src/asset_debugger/modals/examples-modal/examples-modal.html +18 -0
  23. package/src/asset_debugger/modals/examples-modal/examples-modal.js +111 -0
  24. package/src/asset_debugger/modals/examples-modal/examples.js +125 -0
  25. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.css +452 -0
  26. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.html +87 -0
  27. package/src/asset_debugger/modals/html-editor-modal/html-editor-modal.js +675 -0
  28. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.css +219 -0
  29. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.html +20 -0
  30. package/src/asset_debugger/modals/mesh-info-modal/mesh-info-modal.js +548 -0
  31. package/src/asset_debugger/modals/settings-modal/settings-modal.css +103 -0
  32. package/src/asset_debugger/modals/settings-modal/settings-modal.html +158 -0
  33. package/src/asset_debugger/modals/settings-modal/settings-modal.js +475 -0
  34. package/src/asset_debugger/panels/asset-panel/asset-panel.css +263 -0
  35. package/src/asset_debugger/panels/asset-panel/asset-panel.html +123 -0
  36. package/src/asset_debugger/panels/asset-panel/asset-panel.js +136 -0
  37. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.css +94 -0
  38. package/src/asset_debugger/panels/asset-panel/atlas-heading/atlas-heading.js +312 -0
  39. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.css +129 -0
  40. package/src/asset_debugger/panels/asset-panel/mesh-heading/mesh-heading.js +486 -0
  41. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.css +545 -0
  42. package/src/asset_debugger/panels/asset-panel/rig-heading/rig-heading.js +538 -0
  43. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.css +70 -0
  44. package/src/asset_debugger/panels/asset-panel/uv-heading/uv-heading.js +586 -0
  45. package/src/asset_debugger/panels/world-panel/world-panel.css +364 -0
  46. package/src/asset_debugger/panels/world-panel/world-panel.html +173 -0
  47. package/src/asset_debugger/panels/world-panel/world-panel.js +1891 -0
  48. package/src/asset_debugger/router.js +190 -0
  49. package/src/asset_debugger/util/animation/playback/animation-playback-controller.js +150 -0
  50. package/src/asset_debugger/util/animation/playback/animation-preview-controller.js +316 -0
  51. package/src/asset_debugger/util/animation/playback/css3d-bounce-controller.js +400 -0
  52. package/src/asset_debugger/util/animation/playback/css3d-reversal-controller.js +821 -0
  53. package/src/asset_debugger/util/animation/render/css3d-prerender-controller.js +696 -0
  54. package/src/asset_debugger/util/animation/render/debug-texture-factory.js +0 -0
  55. package/src/asset_debugger/util/animation/render/iframe2texture-render-controller.js +199 -0
  56. package/src/asset_debugger/util/animation/render/image2texture-prerender-controller.js +461 -0
  57. package/src/asset_debugger/util/animation/render/pbr-material-factory.js +82 -0
  58. package/src/asset_debugger/util/common.css +280 -0
  59. package/src/asset_debugger/util/data/animation-classifier.js +323 -0
  60. package/src/asset_debugger/util/data/duplicate-handler.js +20 -0
  61. package/src/asset_debugger/util/data/glb-buffer-manager.js +407 -0
  62. package/src/asset_debugger/util/data/glb-classifier.js +290 -0
  63. package/src/asset_debugger/util/data/html-formatter.js +76 -0
  64. package/src/asset_debugger/util/data/html-linter.js +276 -0
  65. package/src/asset_debugger/util/data/localstorage-manager.js +265 -0
  66. package/src/asset_debugger/util/data/mesh-html-manager.js +295 -0
  67. package/src/asset_debugger/util/data/string-serder.js +303 -0
  68. package/src/asset_debugger/util/data/texture-classifier.js +663 -0
  69. package/src/asset_debugger/util/data/upload/background-file-handler.js +292 -0
  70. package/src/asset_debugger/util/data/upload/dropzone-preview-controller.js +396 -0
  71. package/src/asset_debugger/util/data/upload/file-upload-manager.js +495 -0
  72. package/src/asset_debugger/util/data/upload/glb-file-handler.js +36 -0
  73. package/src/asset_debugger/util/data/upload/glb-preview-controller.js +317 -0
  74. package/src/asset_debugger/util/data/upload/lighting-file-handler.js +194 -0
  75. package/src/asset_debugger/util/data/upload/model-file-manager.js +104 -0
  76. package/src/asset_debugger/util/data/upload/texture-file-handler.js +166 -0
  77. package/src/asset_debugger/util/data/upload/zip-handler.js +686 -0
  78. package/src/asset_debugger/util/loaders/html2canvas-loader.js +107 -0
  79. package/src/asset_debugger/util/rig/bone-kinematics.js +403 -0
  80. package/src/asset_debugger/util/rig/rig-constraint-manager.js +618 -0
  81. package/src/asset_debugger/util/rig/rig-controller.js +612 -0
  82. package/src/asset_debugger/util/rig/rig-factory.js +628 -0
  83. package/src/asset_debugger/util/rig/rig-handle-factory.js +46 -0
  84. package/src/asset_debugger/util/rig/rig-label-factory.js +441 -0
  85. package/src/asset_debugger/util/rig/rig-mouse-handler.js +377 -0
  86. package/src/asset_debugger/util/rig/rig-state-manager.js +175 -0
  87. package/src/asset_debugger/util/rig/rig-tooltip-manager.js +267 -0
  88. package/src/asset_debugger/util/rig/rig-ui-factory.js +700 -0
  89. package/src/asset_debugger/util/scene/background-manager.js +284 -0
  90. package/src/asset_debugger/util/scene/camera-controller.js +243 -0
  91. package/src/asset_debugger/util/scene/css3d-debug-controller.js +406 -0
  92. package/src/asset_debugger/util/scene/css3d-frame-factory.js +113 -0
  93. package/src/asset_debugger/util/scene/css3d-scene-manager.js +529 -0
  94. package/src/asset_debugger/util/scene/glb-controller.js +208 -0
  95. package/src/asset_debugger/util/scene/lighting-manager.js +690 -0
  96. package/src/asset_debugger/util/scene/threejs-model-manager.js +437 -0
  97. package/src/asset_debugger/util/scene/threejs-preview-manager.js +207 -0
  98. package/src/asset_debugger/util/scene/threejs-preview-setup.js +478 -0
  99. package/src/asset_debugger/util/scene/threejs-scene-controller.js +286 -0
  100. package/src/asset_debugger/util/scene/ui-manager.js +107 -0
  101. package/src/asset_debugger/util/state/animation-state.js +128 -0
  102. package/src/asset_debugger/util/state/css3d-state.js +83 -0
  103. package/src/asset_debugger/util/state/glb-preview-state.js +31 -0
  104. package/src/asset_debugger/util/state/log-util.js +197 -0
  105. package/src/asset_debugger/util/state/scene-state.js +452 -0
  106. package/src/asset_debugger/util/state/threejs-state.js +54 -0
  107. package/src/asset_debugger/util/workers/lighting-worker.js +61 -0
  108. package/src/asset_debugger/util/workers/model-worker.js +109 -0
  109. package/src/asset_debugger/util/workers/texture-worker.js +54 -0
  110. package/src/asset_debugger/util/workers/worker-manager.js +212 -0
  111. package/src/asset_debugger/widgets/mesh-info-widget.js +280 -0
  112. package/src/index.html +261 -0
  113. package/src/index.js +8 -0
  114. 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
+ };