@littlecarlito/blorktools 0.50.4 → 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,76 @@
1
+ /**
2
+ * HTML Formatter
3
+ *
4
+ * This module provides HTML formatting capabilities.
5
+ * It uses js-beautify for HTML formatting.
6
+ */
7
+
8
+ import jsBeautify from 'js-beautify';
9
+ const { html: beautifyHtml, css: beautifyCss } = jsBeautify;
10
+
11
+ /**
12
+ * Format HTML code with proper indentation
13
+ * @param {string} html - The HTML code to format
14
+ * @param {Object} options - Formatting options (tabWidth, useTabs)
15
+ * @returns {string} The formatted HTML
16
+ */
17
+ export function formatHtml(html, options = {}) {
18
+ if (!html || html.trim() === '') return '';
19
+
20
+ try {
21
+ const tabWidth = options.tabWidth || 2;
22
+ const useTabs = options.useTabs || false;
23
+
24
+ // Check if input is partial CSS (not complete HTML)
25
+ const isPartialCss = html.trim().startsWith('body') ||
26
+ html.trim().startsWith('.') ||
27
+ html.trim().startsWith('#') ||
28
+ (html.includes('{') && !html.includes('<'));
29
+
30
+ // Beautify options
31
+ const beautifyOptions = {
32
+ indent_size: tabWidth,
33
+ indent_with_tabs: useTabs,
34
+ preserve_newlines: true,
35
+ max_preserve_newlines: 2,
36
+ wrap_line_length: 0,
37
+ end_with_newline: true,
38
+ indent_inner_html: true,
39
+ extra_liners: []
40
+ };
41
+
42
+ // Format based on content type
43
+ if (isPartialCss) {
44
+ return beautifyCss(html, beautifyOptions);
45
+ } else {
46
+ return beautifyHtml(html, beautifyOptions);
47
+ }
48
+ } catch (error) {
49
+ console.error('Error formatting HTML:', error);
50
+ // Fallback to returning the original HTML if formatting fails
51
+ return html;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Check if external formatter is available
57
+ * @returns {boolean} Always true since we use js-beautify
58
+ */
59
+ export function hasExternalFormatter() {
60
+ return true;
61
+ }
62
+
63
+ /**
64
+ * Initialize the HTML formatter
65
+ * @returns {Promise<void>}
66
+ */
67
+ export async function initHtmlFormatter() {
68
+ console.log('HTML formatter initialized (js-beautify)');
69
+ return Promise.resolve();
70
+ }
71
+
72
+ export default {
73
+ formatHtml,
74
+ initHtmlFormatter,
75
+ hasExternalFormatter
76
+ };
@@ -0,0 +1,276 @@
1
+ /**
2
+ * HTML Linter
3
+ *
4
+ * This module provides HTML linting capabilities using a built-in implementation.
5
+ * No external dependencies are used.
6
+ */
7
+
8
+
9
+ // Store current lint errors
10
+ let currentLintErrors = [];
11
+
12
+ /**
13
+ * Initialize the HTML linter (no-op since we don't need initialization)
14
+ * @returns {Promise<void>}
15
+ */
16
+ export async function initHtmlLinter() {
17
+ console.log('HTML linter initialized (built-in)');
18
+ return Promise.resolve();
19
+ }
20
+
21
+ /**
22
+ * Lint the HTML content in the editor
23
+ */
24
+ export async function lintHtmlContent() {
25
+ const modal = document.getElementById('html-editor-modal');
26
+ const textarea = modal ? modal.querySelector('#html-editor-textarea') : null;
27
+ const errorContainer = modal ? modal.querySelector('#html-editor-errors') : null;
28
+
29
+ if (!textarea) return;
30
+
31
+ const html = textarea.value;
32
+
33
+ try {
34
+ // Run the linter
35
+ const errors = await lintHtml(html);
36
+ currentLintErrors = errors;
37
+
38
+ // Clear previous error indicators
39
+ clearErrorIndicators();
40
+
41
+ // Create error container if it doesn't exist
42
+ const container = errorContainer || createErrorContainer();
43
+
44
+ // Display errors if any
45
+ if (errors && errors.length > 0) {
46
+ displayLintErrors(errors);
47
+ container.style.display = 'block';
48
+ } else {
49
+ if (container) container.style.display = 'none';
50
+ }
51
+ } catch (error) {
52
+ console.error('Error linting HTML:', error);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Built-in HTML validation
58
+ * @param {string} html - The HTML to validate
59
+ * @returns {Array} Array of lint errors/warnings
60
+ */
61
+ function validateHtml(html) {
62
+ const errors = [];
63
+
64
+ if (!html || html.trim() === '') {
65
+ return errors;
66
+ }
67
+
68
+ // Split into lines for better error reporting
69
+ const lines = html.split('\n');
70
+
71
+ // Check for basic HTML structure
72
+ if (!html.includes('<')) {
73
+ errors.push({
74
+ line: 1,
75
+ col: 1,
76
+ message: 'No HTML tags found',
77
+ rule: 'tag-required',
78
+ severity: 'error'
79
+ });
80
+ }
81
+
82
+ // Track open tags to check for proper nesting and closure
83
+ const openTags = [];
84
+ let lineNum = 0;
85
+
86
+ // Process each line
87
+ for (let i = 0; i < lines.length; i++) {
88
+ const line = lines[i];
89
+ lineNum = i + 1; // 1-based line numbers
90
+
91
+ // Extract tags from the line
92
+ const tagMatches = line.match(/<\/?[a-z][a-z0-9]*(?:\s+[^>]*)?>/gi) || [];
93
+
94
+ // Process each tag in the line
95
+ for (const tag of tagMatches) {
96
+ const colNum = line.indexOf(tag) + 1; // 1-based column numbers
97
+
98
+ // Check if it's a closing tag
99
+ if (tag.match(/<\//)) {
100
+ const tagName = tag.match(/<\/([a-z][a-z0-9]*)/i)?.[1]?.toLowerCase();
101
+
102
+ if (!tagName) {
103
+ errors.push({
104
+ line: lineNum,
105
+ col: colNum,
106
+ message: 'Invalid closing tag format',
107
+ rule: 'tag-format',
108
+ severity: 'error'
109
+ });
110
+ continue;
111
+ }
112
+
113
+ // Check if this closing tag matches the last opened tag
114
+ if (openTags.length === 0) {
115
+ errors.push({
116
+ line: lineNum,
117
+ col: colNum,
118
+ message: `Closing tag </${tagName}> without matching opening tag`,
119
+ rule: 'tag-pair',
120
+ severity: 'error'
121
+ });
122
+ } else if (openTags[openTags.length - 1].name !== tagName) {
123
+ const lastOpenTag = openTags[openTags.length - 1].name;
124
+ errors.push({
125
+ line: lineNum,
126
+ col: colNum,
127
+ message: `Expected closing tag </${lastOpenTag}>, found </${tagName}>`,
128
+ rule: 'tag-pair',
129
+ severity: 'error'
130
+ });
131
+ } else {
132
+ // Correct closing tag, remove from stack
133
+ openTags.pop();
134
+ }
135
+ }
136
+ // Check if it's a self-closing tag
137
+ else if (tag.endsWith('/>') ||
138
+ tag.match(/<(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)(\s|>)/i)) {
139
+ // Self-closing tags are fine, no need to track
140
+ }
141
+ // It's an opening tag
142
+ else {
143
+ const tagName = tag.match(/<([a-z][a-z0-9]*)/i)?.[1]?.toLowerCase();
144
+
145
+ if (!tagName) {
146
+ errors.push({
147
+ line: lineNum,
148
+ col: colNum,
149
+ message: 'Invalid opening tag format',
150
+ rule: 'tag-format',
151
+ severity: 'error'
152
+ });
153
+ continue;
154
+ }
155
+
156
+ // Check for attribute format issues
157
+ const attributeText = tag.slice(tagName.length + 1, -1).trim();
158
+ if (attributeText) {
159
+ const attrMatches = attributeText.match(/([a-z][a-z0-9\-_]*)(?:=(?:"[^"]*"|'[^']*'|[^\s>]+))?/gi) || [];
160
+
161
+ // Check for duplicate attributes
162
+ const attrNames = [];
163
+ for (const attr of attrMatches) {
164
+ const attrName = attr.split('=')[0].toLowerCase();
165
+ if (attrNames.includes(attrName)) {
166
+ errors.push({
167
+ line: lineNum,
168
+ col: colNum,
169
+ message: `Duplicate attribute '${attrName}' in tag`,
170
+ rule: 'attr-no-duplication',
171
+ severity: 'error'
172
+ });
173
+ }
174
+ attrNames.push(attrName);
175
+ }
176
+ }
177
+
178
+ // Add to open tags stack
179
+ openTags.push({
180
+ name: tagName,
181
+ line: lineNum,
182
+ col: colNum
183
+ });
184
+ }
185
+ }
186
+ }
187
+
188
+ // Check for unclosed tags
189
+ for (const tag of openTags) {
190
+ errors.push({
191
+ line: tag.line,
192
+ col: tag.col,
193
+ message: `Unclosed tag <${tag.name}>`,
194
+ rule: 'tag-pair',
195
+ severity: 'error'
196
+ });
197
+ }
198
+
199
+ return errors;
200
+ }
201
+
202
+ /**
203
+ * Lint HTML code to find errors and warnings
204
+ * @param {string} html - The HTML to lint
205
+ * @returns {Promise<Array>} Array of lint errors/warnings
206
+ */
207
+ async function lintHtml(html) {
208
+ if (!html) return [];
209
+
210
+ try {
211
+ return validateHtml(html);
212
+ } catch (error) {
213
+ console.error('Error linting HTML:', error);
214
+ return [{
215
+ line: 1,
216
+ col: 1,
217
+ message: `Linting error: ${error.message}`,
218
+ rule: 'internal-error',
219
+ severity: 'error'
220
+ }];
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Clear error indicators from the editor
226
+ */
227
+ function clearErrorIndicators() {
228
+ const modal = document.getElementById('html-editor-modal');
229
+ const textarea = modal ? modal.querySelector('#html-editor-textarea') : null;
230
+ if (!textarea) return;
231
+
232
+ // Remove any existing error styling
233
+ textarea.classList.remove('has-errors');
234
+
235
+ // Clear the error container
236
+ const errorContainer = modal ? modal.querySelector('#html-editor-errors') : null;
237
+ if (errorContainer) {
238
+ errorContainer.innerHTML = '';
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Display lint errors in the editor
244
+ * @param {Array} errors - The lint errors to display
245
+ */
246
+ function displayLintErrors(errors) {
247
+ const modal = document.getElementById('html-editor-modal');
248
+ const textarea = modal ? modal.querySelector('#html-editor-textarea') : null;
249
+ const errorContainer = modal ? modal.querySelector('#html-editor-errors') : null;
250
+
251
+ if (!textarea || !errorContainer) return;
252
+
253
+ // Add error class to textarea
254
+ textarea.classList.add('has-errors');
255
+
256
+ // Create error messages
257
+ const errorList = document.createElement('ul');
258
+ errorList.style.margin = '0';
259
+ errorList.style.padding = '0 0 0 20px';
260
+
261
+ errors.forEach(error => {
262
+ const errorItem = document.createElement('li');
263
+ errorItem.textContent = `Line ${error.line}, Col ${error.col}: ${error.message}`;
264
+ errorItem.style.cursor = 'pointer';
265
+
266
+ // Add click handler to navigate to the error position
267
+ errorItem.addEventListener('click', () => {
268
+ navigateToErrorPosition(textarea, error.line, error.col);
269
+ });
270
+
271
+ errorList.appendChild(errorItem);
272
+ });
273
+
274
+ errorContainer.innerHTML = '';
275
+ errorContainer.appendChild(errorList);
276
+ }
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Utilities for working with localStorage in the Asset Debugger
3
+ */
4
+
5
+ // Storage keys
6
+ const SETTINGS_KEY = 'assetDebuggerSettings';
7
+ const CURRENT_SESSION_KEY = 'assetDebuggerCurrentState';
8
+ const SESSION_HISTORY_KEY = 'assetDebuggerSessionHistory';
9
+ const MAX_HISTORY_SESSIONS = 10;
10
+
11
+ /**
12
+ * Save asset debugger settings to localStorage
13
+ * @param {Object} settings - The settings object to save
14
+ */
15
+ export function saveSettings(settings) {
16
+ localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
17
+ }
18
+
19
+ /**
20
+ * Load asset debugger settings from localStorage
21
+ * @returns {Object|null} The parsed settings object or null if no settings found
22
+ */
23
+ export function loadSettings() {
24
+ const savedSettings = localStorage.getItem(SETTINGS_KEY);
25
+
26
+ if (savedSettings) {
27
+ try {
28
+ const settings = JSON.parse(savedSettings);
29
+ console.log('Loaded settings:', settings);
30
+ return settings;
31
+ } catch (e) {
32
+ console.error('Error loading saved settings:', e);
33
+ return null;
34
+ }
35
+ }
36
+
37
+ return null;
38
+ }
39
+
40
+ /**
41
+ * Get default asset debugger settings
42
+ * @returns {Object} The default settings object
43
+ */
44
+ export function getDefaultSettings() {
45
+ return {
46
+ pinned: true, // Default pin state is enabled/pinned
47
+ tabPanelHidden: false, // Default tab panel state is visible
48
+ axisIndicator: {
49
+ type: 'embedded',
50
+ intensity: 0.7
51
+ },
52
+ rigOptions: {
53
+ displayRig: true,
54
+ forceZ: true,
55
+ wireframe: false,
56
+ primaryColor: 0x4CAF50,
57
+ secondaryColor: 0xFFFF00,
58
+ jointColor: 0x00FFFF,
59
+ showJointLabels: false,
60
+ normalColor: 0xFF0000,
61
+ hoverColor: 0x00FF00,
62
+ activeColor: 0x0000FF
63
+ }
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Serialize a state object for storage
69
+ * @param {Object} state - The state object to serialize
70
+ * @returns {Object} Serialized state
71
+ */
72
+ function serializeState(state) {
73
+ const serialized = { ...state };
74
+
75
+ // Handle File objects in textureFiles
76
+ if (serialized.textureFiles) {
77
+ Object.keys(serialized.textureFiles).forEach(key => {
78
+ const file = serialized.textureFiles[key];
79
+ if (file instanceof File) {
80
+ serialized.textureFiles[key] = {
81
+ name: file.name,
82
+ type: file.type,
83
+ size: file.size,
84
+ lastModified: file.lastModified
85
+ };
86
+ }
87
+ });
88
+ }
89
+
90
+ // Handle model file
91
+ if (serialized.modelFile instanceof File) {
92
+ serialized.modelFile = {
93
+ name: serialized.modelFile.name,
94
+ type: serialized.modelFile.type,
95
+ size: serialized.modelFile.size,
96
+ lastModified: serialized.modelFile.lastModified
97
+ };
98
+ }
99
+
100
+ // Handle lighting file
101
+ if (serialized.lightingFile instanceof File) {
102
+ serialized.lightingFile = {
103
+ name: serialized.lightingFile.name,
104
+ type: serialized.lightingFile.type,
105
+ size: serialized.lightingFile.size,
106
+ lastModified: serialized.lightingFile.lastModified
107
+ };
108
+ }
109
+
110
+ // Handle background file
111
+ if (serialized.backgroundFile instanceof File) {
112
+ serialized.backgroundFile = {
113
+ name: serialized.backgroundFile.name,
114
+ type: serialized.backgroundFile.type,
115
+ size: serialized.backgroundFile.size,
116
+ lastModified: serialized.backgroundFile.lastModified
117
+ };
118
+ }
119
+
120
+ return serialized;
121
+ }
122
+
123
+ /**
124
+ * Save current session state
125
+ * @param {Object} state - The current session state
126
+ */
127
+ export function saveCurrentSession(state) {
128
+ const serialized = serializeState(state);
129
+ localStorage.setItem(CURRENT_SESSION_KEY, JSON.stringify(serialized));
130
+ addToHistory(serialized);
131
+ }
132
+
133
+ /**
134
+ * Load current session state
135
+ * @returns {Object|null} The current session state or null if none exists
136
+ */
137
+ export function loadCurrentSession() {
138
+ const savedState = localStorage.getItem(CURRENT_SESSION_KEY);
139
+ if (!savedState) return null;
140
+
141
+ try {
142
+ const state = JSON.parse(savedState);
143
+ console.debug('Loaded state from localStorage:', state);
144
+ return state;
145
+ } catch (e) {
146
+ console.error('Error parsing saved state:', e);
147
+ return null;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Add a session to the history
153
+ * @param {Object} sessionState - The session state to add
154
+ */
155
+ function addToHistory(sessionState) {
156
+ const history = getSessionHistory();
157
+
158
+ // Add new session to the beginning of the array
159
+ history.unshift({
160
+ sessionId: sessionState.sessionId,
161
+ timestamp: sessionState.timestamp,
162
+ state: sessionState
163
+ });
164
+
165
+ // Keep only the most recent MAX_HISTORY_SESSIONS
166
+ if (history.length > MAX_HISTORY_SESSIONS) {
167
+ history.pop();
168
+ }
169
+
170
+ // Save updated history
171
+ localStorage.setItem(SESSION_HISTORY_KEY, JSON.stringify(history));
172
+ }
173
+
174
+ /**
175
+ * Get the session history
176
+ * @returns {Array} Array of session objects
177
+ */
178
+ export function getSessionHistory() {
179
+ const history = localStorage.getItem(SESSION_HISTORY_KEY);
180
+ return history ? JSON.parse(history) : [];
181
+ }
182
+
183
+ /**
184
+ * Clear all session data from localStorage
185
+ */
186
+ export function clearSessionData() {
187
+ localStorage.removeItem(CURRENT_SESSION_KEY);
188
+ localStorage.removeItem(SESSION_HISTORY_KEY);
189
+ console.debug('Cleared all session data from localStorage');
190
+ }
191
+
192
+ /**
193
+ * NEW: Clean up localStorage when quota is exceeded
194
+ * Removes old sessions and keeps only the most recent ones
195
+ */
196
+ export function cleanupLocalStorage() {
197
+ try {
198
+ console.log('Cleaning up localStorage to free space...');
199
+
200
+ // Get all keys from localStorage
201
+ const keys = Object.keys(localStorage);
202
+
203
+ // Find session keys (they typically start with a timestamp or contain 'session')
204
+ const sessionKeys = keys.filter(key =>
205
+ key.includes('session') ||
206
+ key.includes('assetDebugger') ||
207
+ /^\d+$/.test(key) // Keys that are just numbers (timestamps)
208
+ );
209
+
210
+ // Sort by key name (newer timestamps will be larger)
211
+ sessionKeys.sort();
212
+
213
+ // Keep only the 3 most recent sessions, remove the rest
214
+ const keysToRemove = sessionKeys.slice(0, -3);
215
+
216
+ keysToRemove.forEach(key => {
217
+ try {
218
+ localStorage.removeItem(key);
219
+ console.log(`Removed old session: ${key}`);
220
+ } catch (error) {
221
+ console.warn(`Could not remove key ${key}:`, error);
222
+ }
223
+ });
224
+
225
+ console.log(`Cleaned up ${keysToRemove.length} old sessions from localStorage`);
226
+
227
+ } catch (error) {
228
+ console.error('Error during localStorage cleanup:', error);
229
+ // If all else fails, clear everything
230
+ try {
231
+ localStorage.clear();
232
+ console.log('Cleared all localStorage due to cleanup failure');
233
+ } catch (clearError) {
234
+ console.error('Cannot clear localStorage:', clearError);
235
+ }
236
+ }
237
+ }
238
+
239
+ /**
240
+ * NEW: Safe save function that handles quota exceeded errors
241
+ */
242
+ export function safeSaveCurrentSession(sessionData) {
243
+ try {
244
+ saveCurrentSession(sessionData);
245
+ return true;
246
+ } catch (error) {
247
+ if (error.name === 'QuotaExceededError' || error.message.includes('quota')) {
248
+ console.warn('localStorage quota exceeded, attempting cleanup...');
249
+ cleanupLocalStorage();
250
+
251
+ // Try saving again after cleanup
252
+ try {
253
+ saveCurrentSession(sessionData);
254
+ console.log('Successfully saved session after cleanup');
255
+ return true;
256
+ } catch (secondError) {
257
+ console.error('Still cannot save after cleanup:', secondError);
258
+ return false;
259
+ }
260
+ } else {
261
+ console.error('Error saving session:', error);
262
+ return false;
263
+ }
264
+ }
265
+ }