@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.
- 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,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
|
+
}
|