@preference-sl/pref-viewer 2.16.2 → 2.17.0-beta.1
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/README.md +184 -22
- package/index.d.ts +11 -1
- package/package.json +1 -1
- package/src/babylonjs-animation-controller.js +12 -163
- package/src/babylonjs-animation-opening-menu.js +8 -1
- package/src/babylonjs-animation-opening.js +21 -1
- package/src/babylonjs-animation-overlay-controller.js +145 -0
- package/src/babylonjs-camera-framing.js +171 -0
- package/src/babylonjs-camera-zoom-controller.js +272 -0
- package/src/babylonjs-controller.js +640 -378
- package/src/babylonjs-selection-controller.js +215 -0
- package/src/babylonjs-view-cube-controller.js +810 -0
- package/src/file-storage.js +13 -0
- package/src/gltf-storage.js +97 -0
- package/src/localization/i18n.js +147 -74
- package/src/localization/translations.js +18 -0
- package/src/material-utils.js +67 -60
- package/src/panzoom-controller.js +14 -1
- package/src/pref-viewer-2d.js +16 -4
- package/src/pref-viewer-3d-data.js +166 -0
- package/src/pref-viewer-3d.js +40 -5
- package/src/pref-viewer-dialog.js +7 -1
- package/src/pref-viewer-menu-3d.js +57 -37
- package/src/pref-viewer.js +155 -44
- package/src/render-math.js +130 -117
- package/src/styles.js +94 -3
- package/src/svg-resolver.js +5 -4
package/src/file-storage.js
CHANGED
|
@@ -130,6 +130,12 @@ export default class FileStorage {
|
|
|
130
130
|
#maxQuotaEvictionRetries = 5; // max retries after quota error
|
|
131
131
|
#lastCleanupAt = 0; // timestamp of last automatic cleanup run
|
|
132
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Configures the file-cache database and object store; opening is deferred until storage is used.
|
|
135
|
+
* @public
|
|
136
|
+
* @param {string} [dbName="FilesDB"] - IndexedDB cache database name.
|
|
137
|
+
* @param {string} [osName="FilesObjectStore"] - Object store for cached files.
|
|
138
|
+
*/
|
|
133
139
|
constructor(dbName = "FilesDB", osName = "FilesObjectStore") {
|
|
134
140
|
this.dbName = dbName; // database name
|
|
135
141
|
this.osName = osName; // object store name used for file cache
|
|
@@ -447,6 +453,7 @@ export default class FileStorage {
|
|
|
447
453
|
* @private
|
|
448
454
|
* @param {IDBDatabase} db IndexedDB database instance
|
|
449
455
|
* @param {String} osName Object store name
|
|
456
|
+
* @returns {void}
|
|
450
457
|
*/
|
|
451
458
|
#createObjectStore(db, osName) {
|
|
452
459
|
if (!db.objectStoreNames.contains(osName)) {
|
|
@@ -466,6 +473,12 @@ export default class FileStorage {
|
|
|
466
473
|
const self = this;
|
|
467
474
|
try {
|
|
468
475
|
return await openDB(this.dbName, this.#dbVersion, {
|
|
476
|
+
/**
|
|
477
|
+
* Creates the configured cache object store during an IndexedDB schema upgrade.
|
|
478
|
+
* @private
|
|
479
|
+
* @param {IDBDatabase} db - Database being upgraded.
|
|
480
|
+
* @returns {void}
|
|
481
|
+
*/
|
|
469
482
|
upgrade(db) {
|
|
470
483
|
self.#createObjectStore(db, self.osName);
|
|
471
484
|
},
|
package/src/gltf-storage.js
CHANGED
|
@@ -7,6 +7,12 @@ PC.db = PC.db ?? null;
|
|
|
7
7
|
PC.dbName = PC.dbName ?? null;
|
|
8
8
|
PC.storeName = PC.storeName ?? null;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Returns the active IndexedDB connection, throwing if initDb has not initialized it.
|
|
12
|
+
* @private
|
|
13
|
+
* @returns {IDBDatabase}
|
|
14
|
+
* @throws {Error} If initDb has not established a database connection.
|
|
15
|
+
*/
|
|
10
16
|
function _getDbOrThrow() {
|
|
11
17
|
const db = PC.db;
|
|
12
18
|
if (!db) throw new Error("Database not initialized. Call initDb(...) first.");
|
|
@@ -14,6 +20,13 @@ function _getDbOrThrow() {
|
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
// --- internal helpers -------------------------------------------------------
|
|
23
|
+
/**
|
|
24
|
+
* Opens database version 5 and creates the model store and expiration index during upgrade.
|
|
25
|
+
* @private
|
|
26
|
+
* @param {string} dbName - IndexedDB database name.
|
|
27
|
+
* @param {string} storeName - Object store name.
|
|
28
|
+
* @returns {Promise<IDBDatabase>}
|
|
29
|
+
*/
|
|
17
30
|
function _openEnsuringStore(dbName, storeName) {
|
|
18
31
|
return new Promise((resolve, reject) => {
|
|
19
32
|
const open = indexedDB.open(dbName, 5);
|
|
@@ -41,6 +54,12 @@ function _openEnsuringStore(dbName, storeName) {
|
|
|
41
54
|
});
|
|
42
55
|
}
|
|
43
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Identifies missing-store or closing-connection errors that allow one database reinitialization.
|
|
59
|
+
* @private
|
|
60
|
+
* @param {unknown} err - Error raised by an IndexedDB operation.
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
44
63
|
function _isRecoverableDbError(err) {
|
|
45
64
|
const msg = String(err?.message ?? err ?? "");
|
|
46
65
|
if (!msg) return false;
|
|
@@ -50,6 +69,12 @@ function _isRecoverableDbError(err) {
|
|
|
50
69
|
msg.includes("NotFoundError");
|
|
51
70
|
}
|
|
52
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Executes a database operation and retries once after reopening a recoverable connection.
|
|
74
|
+
* @private
|
|
75
|
+
* @param {function(IDBDatabase): Promise<*>} work - Operation using the active database.
|
|
76
|
+
* @returns {Promise<*>}
|
|
77
|
+
*/
|
|
53
78
|
async function _withDbRetry(work) {
|
|
54
79
|
try {
|
|
55
80
|
return await work(_getDbOrThrow());
|
|
@@ -65,6 +90,13 @@ async function _withDbRetry(work) {
|
|
|
65
90
|
// --- public API -------------------------------------------------------------
|
|
66
91
|
|
|
67
92
|
// Inicializar IndexedDB y dejar el handle en PrefConfigurator.db (público)
|
|
93
|
+
/**
|
|
94
|
+
* Opens the model database, replaces the previous connection and closes on version changes.
|
|
95
|
+
* @public
|
|
96
|
+
* @param {string} dbName - IndexedDB database name.
|
|
97
|
+
* @param {string} storeName - Object store to create on upgrade.
|
|
98
|
+
* @returns {Promise<void>}
|
|
99
|
+
*/
|
|
68
100
|
export async function initDb(dbName, storeName) {
|
|
69
101
|
PC.dbName = dbName;
|
|
70
102
|
PC.storeName = storeName;
|
|
@@ -83,6 +115,14 @@ export async function initDb(dbName, storeName) {
|
|
|
83
115
|
}
|
|
84
116
|
|
|
85
117
|
// Guardar modelo
|
|
118
|
+
/**
|
|
119
|
+
* Parses a JSON model record and stores it with its base64 data length as the size.
|
|
120
|
+
* @public
|
|
121
|
+
* @param {string} modelDataStr - JSON record containing id, data and optional metadata or timestamps.
|
|
122
|
+
* @param {string} storeName - Object store receiving the record.
|
|
123
|
+
* @returns {Promise<void>}
|
|
124
|
+
* @throws {Error} Rejects when JSON parsing or the IndexedDB request fails.
|
|
125
|
+
*/
|
|
86
126
|
export async function saveModel(modelDataStr, storeName) {
|
|
87
127
|
return _withDbRetry((db) => new Promise((resolve, reject) => {
|
|
88
128
|
let modelData;
|
|
@@ -105,6 +145,13 @@ export async function saveModel(modelDataStr, storeName) {
|
|
|
105
145
|
}
|
|
106
146
|
|
|
107
147
|
// Cargar modelo
|
|
148
|
+
/**
|
|
149
|
+
* Reads a complete model record by its IndexedDB key, returning null when absent.
|
|
150
|
+
* @public
|
|
151
|
+
* @param {IDBValidKey} modelId - Stored model key.
|
|
152
|
+
* @param {string} storeName - Object store containing the record.
|
|
153
|
+
* @returns {Promise<object|null>}
|
|
154
|
+
*/
|
|
108
155
|
export function loadModel(modelId, storeName) {
|
|
109
156
|
return _withDbRetry((db) => new Promise((resolve, reject) => {
|
|
110
157
|
const tx = db.transaction([storeName], "readonly");
|
|
@@ -117,6 +164,14 @@ export function loadModel(modelId, storeName) {
|
|
|
117
164
|
}
|
|
118
165
|
|
|
119
166
|
// Descargar archivo desde base64
|
|
167
|
+
/**
|
|
168
|
+
* Triggers a browser download using a base64 data URI and removes the temporary link.
|
|
169
|
+
* @public
|
|
170
|
+
* @param {string} fileName - Suggested download filename.
|
|
171
|
+
* @param {string} bytesBase64 - Base64-encoded file contents.
|
|
172
|
+
* @param {string} mimeType - MIME type used in the data URI.
|
|
173
|
+
* @returns {void}
|
|
174
|
+
*/
|
|
120
175
|
export function downloadFileFromBytes(fileName, bytesBase64, mimeType) {
|
|
121
176
|
const link = document.createElement("a");
|
|
122
177
|
link.download = fileName;
|
|
@@ -127,6 +182,12 @@ export function downloadFileFromBytes(fileName, bytesBase64, mimeType) {
|
|
|
127
182
|
}
|
|
128
183
|
|
|
129
184
|
// Obtener todos los modelos (solo metadata)
|
|
185
|
+
/**
|
|
186
|
+
* Returns JSON metadata for all models, excluding their stored data payloads.
|
|
187
|
+
* @public
|
|
188
|
+
* @param {string} storeName - Object store to read.
|
|
189
|
+
* @returns {Promise<string>}
|
|
190
|
+
*/
|
|
130
191
|
export async function getAllModels(storeName) {
|
|
131
192
|
return _withDbRetry((db) => new Promise((resolve, reject) => {
|
|
132
193
|
const tx = db.transaction([storeName], "readonly");
|
|
@@ -149,6 +210,13 @@ export async function getAllModels(storeName) {
|
|
|
149
210
|
}
|
|
150
211
|
|
|
151
212
|
// Eliminar modelo por id
|
|
213
|
+
/**
|
|
214
|
+
* Deletes the record with the requested key.
|
|
215
|
+
* @public
|
|
216
|
+
* @param {IDBValidKey} modelId - Stored model key.
|
|
217
|
+
* @param {string} storeName - Object store to update.
|
|
218
|
+
* @returns {Promise<void>}
|
|
219
|
+
*/
|
|
152
220
|
export async function deleteModel(modelId, storeName) {
|
|
153
221
|
return _withDbRetry((db) => new Promise((resolve, reject) => {
|
|
154
222
|
const tx = db.transaction([storeName], "readwrite");
|
|
@@ -161,6 +229,12 @@ export async function deleteModel(modelId, storeName) {
|
|
|
161
229
|
}
|
|
162
230
|
|
|
163
231
|
// Limpiar toda la store
|
|
232
|
+
/**
|
|
233
|
+
* Deletes every model record in the requested object store.
|
|
234
|
+
* @public
|
|
235
|
+
* @param {string} storeName - Object store to clear.
|
|
236
|
+
* @returns {Promise<void>}
|
|
237
|
+
*/
|
|
164
238
|
export async function clearAll(storeName) {
|
|
165
239
|
return _withDbRetry((db) => new Promise((resolve, reject) => {
|
|
166
240
|
const tx = db.transaction([storeName], "readwrite");
|
|
@@ -173,6 +247,12 @@ export async function clearAll(storeName) {
|
|
|
173
247
|
}
|
|
174
248
|
|
|
175
249
|
// Borrar modelos expirados usando el índice "expirationTimeStamp"
|
|
250
|
+
/**
|
|
251
|
+
* Deletes records whose expirationTimeStamp is at or before the current time and waits for transaction completion.
|
|
252
|
+
* @public
|
|
253
|
+
* @param {string} storeName - Object store with an expirationTimeStamp index.
|
|
254
|
+
* @returns {Promise<void>}
|
|
255
|
+
*/
|
|
176
256
|
export async function cleanExpiredModels(storeName) {
|
|
177
257
|
return _withDbRetry((db) => new Promise((resolve, reject) => {
|
|
178
258
|
const tx = db.transaction([storeName], "readwrite");
|
|
@@ -198,6 +278,11 @@ export async function cleanExpiredModels(storeName) {
|
|
|
198
278
|
}
|
|
199
279
|
|
|
200
280
|
// Utilidades opcionales y visibles (por si las quieres usar en consola)
|
|
281
|
+
/**
|
|
282
|
+
* Closes the active database connection and clears the shared handle.
|
|
283
|
+
* @public
|
|
284
|
+
* @returns {void}
|
|
285
|
+
*/
|
|
201
286
|
export function closeDb() {
|
|
202
287
|
if (PC.db) {
|
|
203
288
|
try { PC.db.close(); } catch { }
|
|
@@ -205,6 +290,12 @@ export function closeDb() {
|
|
|
205
290
|
}
|
|
206
291
|
}
|
|
207
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Closes the matching active connection and deletes the database, rejecting blocked or failed requests.
|
|
295
|
+
* @public
|
|
296
|
+
* @param {string} dbName - IndexedDB database to delete.
|
|
297
|
+
* @returns {Promise<void>}
|
|
298
|
+
*/
|
|
208
299
|
export function deleteDatabase(dbName) {
|
|
209
300
|
return new Promise((resolve, reject) => {
|
|
210
301
|
// Close current handle if points to this DB
|
|
@@ -220,6 +311,12 @@ export function deleteDatabase(dbName) {
|
|
|
220
311
|
}
|
|
221
312
|
|
|
222
313
|
// Attach a frozen, public API (no private state)
|
|
314
|
+
/**
|
|
315
|
+
* Attaches the frozen storage API to the shared PrefConfigurator namespace.
|
|
316
|
+
* @private
|
|
317
|
+
* @param {typeof globalThis} global - Global object exposing the initialized namespace.
|
|
318
|
+
* @returns {void}
|
|
319
|
+
*/
|
|
223
320
|
(function attachPublicAPI(global) {
|
|
224
321
|
const storage = {
|
|
225
322
|
initDb,
|
package/src/localization/i18n.js
CHANGED
|
@@ -1,94 +1,167 @@
|
|
|
1
1
|
import translations from "./translations.js";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Shared localization service with encapsulated locale and subscriber state.
|
|
5
|
+
* Static methods preserve the single locale shared by all viewer components.
|
|
6
|
+
* @class I18n
|
|
7
|
+
*/
|
|
8
|
+
export default class I18n {
|
|
9
|
+
static DEFAULT_LOCALE = "en-EN";
|
|
10
|
+
static SUPPORTED_LOCALES = Object.keys(translations);
|
|
11
|
+
static #listeners = new Set();
|
|
12
|
+
static #currentLocale = I18n.DEFAULT_LOCALE;
|
|
4
13
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Matches a supported locale without case sensitivity and falls back to DEFAULT_LOCALE.
|
|
16
|
+
* @private
|
|
17
|
+
* @static
|
|
18
|
+
* @param {unknown} localeId - Requested locale identifier.
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
static #findLocaleKey(localeId) {
|
|
22
|
+
if (typeof localeId !== "string") {
|
|
23
|
+
return I18n.DEFAULT_LOCALE;
|
|
24
|
+
}
|
|
25
|
+
const normalized = localeId.trim().toLowerCase();
|
|
26
|
+
const match = I18n.SUPPORTED_LOCALES.find((locale) => locale.toLowerCase() === normalized);
|
|
27
|
+
return match ?? I18n.DEFAULT_LOCALE;
|
|
12
28
|
}
|
|
13
|
-
const normalized = localeId.trim().toLowerCase();
|
|
14
|
-
const match = SUPPORTED_LOCALES.find((locale) => locale.toLowerCase() === normalized);
|
|
15
|
-
return match ?? DEFAULT_LOCALE;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const getBundle = (localeId = currentLocale) => {
|
|
19
|
-
const key = findLocaleKey(localeId);
|
|
20
|
-
return translations[key] ?? translations[DEFAULT_LOCALE];
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const resolveValue = (bundle, parts) => parts.reduce((acc, part) => (
|
|
24
|
-
acc && Object.prototype.hasOwnProperty.call(acc, part) ? acc[part] : undefined
|
|
25
|
-
), bundle);
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Returns the requested translation bundle or the default bundle.
|
|
32
|
+
* @private
|
|
33
|
+
* @static
|
|
34
|
+
* @param {string} [localeId=currentLocale] - Locale to resolve.
|
|
35
|
+
* @returns {object}
|
|
36
|
+
*/
|
|
37
|
+
static #getBundle(localeId = I18n.#currentLocale) {
|
|
38
|
+
const key = I18n.#findLocaleKey(localeId);
|
|
39
|
+
return translations[key] ?? translations[I18n.DEFAULT_LOCALE];
|
|
40
|
+
}
|
|
28
41
|
|
|
29
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Traverses own translation properties, returning undefined when a path is absent.
|
|
44
|
+
* @private
|
|
45
|
+
* @static
|
|
46
|
+
* @param {object} bundle - Translation bundle.
|
|
47
|
+
* @param {string[]} parts - Nested translation-key segments.
|
|
48
|
+
* @returns {*}
|
|
49
|
+
*/
|
|
50
|
+
static #resolveValue(bundle, parts) {
|
|
51
|
+
return parts.reduce((acc, part) => (
|
|
52
|
+
acc && Object.prototype.hasOwnProperty.call(acc, part) ? acc[part] : undefined
|
|
53
|
+
), bundle);
|
|
54
|
+
}
|
|
30
55
|
|
|
31
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Returns a copy of the supported locale identifiers.
|
|
58
|
+
* @public
|
|
59
|
+
* @static
|
|
60
|
+
* @returns {string[]}
|
|
61
|
+
*/
|
|
62
|
+
static availableLocales() {
|
|
63
|
+
return [...I18n.SUPPORTED_LOCALES];
|
|
64
|
+
}
|
|
32
65
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Returns the active shared locale.
|
|
68
|
+
* @public
|
|
69
|
+
* @static
|
|
70
|
+
* @returns {string}
|
|
71
|
+
*/
|
|
72
|
+
static getLocale() {
|
|
73
|
+
return I18n.#currentLocale;
|
|
37
74
|
}
|
|
38
|
-
currentLocale = resolved;
|
|
39
|
-
listeners.forEach((listener) => {
|
|
40
|
-
try {
|
|
41
|
-
listener(currentLocale);
|
|
42
|
-
} catch (error) {
|
|
43
|
-
console.warn("PrefViewer: i18n listener failed", error);
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
return currentLocale;
|
|
47
|
-
};
|
|
48
75
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Resolves a requested locale to a supported identifier or the default locale.
|
|
78
|
+
* @public
|
|
79
|
+
* @static
|
|
80
|
+
* @param {unknown} localeId - Requested locale.
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
static resolveLocale(localeId) {
|
|
84
|
+
return I18n.#findLocaleKey(localeId);
|
|
52
85
|
}
|
|
53
|
-
listeners.add(listener);
|
|
54
|
-
return () => listeners.delete(listener);
|
|
55
|
-
};
|
|
56
86
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Updates the shared locale and notifies subscribers only when it changes; subscriber errors are logged.
|
|
89
|
+
* @public
|
|
90
|
+
* @static
|
|
91
|
+
* @param {unknown} localeId - Requested locale.
|
|
92
|
+
* @returns {string}
|
|
93
|
+
*/
|
|
94
|
+
static setLocale(localeId) {
|
|
95
|
+
const resolved = I18n.#findLocaleKey(localeId);
|
|
96
|
+
if (resolved === I18n.#currentLocale) {
|
|
97
|
+
return I18n.#currentLocale;
|
|
98
|
+
}
|
|
99
|
+
I18n.#currentLocale = resolved;
|
|
100
|
+
I18n.#listeners.forEach((listener) => {
|
|
101
|
+
try {
|
|
102
|
+
listener(I18n.#currentLocale);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.warn("PrefViewer: i18n listener failed", error);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
return I18n.#currentLocale;
|
|
60
108
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Registers a locale-change subscriber and returns its unsubscribe function.
|
|
112
|
+
* @public
|
|
113
|
+
* @static
|
|
114
|
+
* @param {function(string): void} listener - Callback receiving the resolved locale.
|
|
115
|
+
* @returns {function(): void}
|
|
116
|
+
*/
|
|
117
|
+
static onLocaleChange(listener) {
|
|
118
|
+
if (typeof listener !== "function") {
|
|
119
|
+
return () => {};
|
|
120
|
+
}
|
|
121
|
+
I18n.#listeners.add(listener);
|
|
122
|
+
return () => I18n.#listeners.delete(listener);
|
|
67
123
|
}
|
|
68
|
-
|
|
69
|
-
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Resolves a dotted or segmented key, then the default locale, then the supplied fallback or an empty string.
|
|
127
|
+
* @public
|
|
128
|
+
* @static
|
|
129
|
+
* @param {string|string[]} key - Translation key or nested path.
|
|
130
|
+
* @param {{locale?:string,fallback?:*}} [options={}] - Locale override and missing-key fallback.
|
|
131
|
+
* @returns {*}
|
|
132
|
+
*/
|
|
133
|
+
static translate(key, options = {}) {
|
|
134
|
+
if (!key) {
|
|
135
|
+
return "";
|
|
136
|
+
}
|
|
137
|
+
const { locale, fallback } = options;
|
|
138
|
+
const parts = Array.isArray(key) ? key : key.split(".");
|
|
139
|
+
const bundle = I18n.#getBundle(locale);
|
|
140
|
+
let value = I18n.#resolveValue(bundle, parts);
|
|
70
141
|
if (value !== undefined) {
|
|
71
142
|
return value;
|
|
72
143
|
}
|
|
144
|
+
if (bundle !== translations[I18n.DEFAULT_LOCALE]) {
|
|
145
|
+
value = I18n.#resolveValue(translations[I18n.DEFAULT_LOCALE], parts);
|
|
146
|
+
if (value !== undefined) {
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return fallback ?? "";
|
|
73
151
|
}
|
|
74
|
-
return fallback ?? "";
|
|
75
|
-
};
|
|
76
152
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Returns an entire locale bundle or resolves a namespace through the translation fallback rules.
|
|
155
|
+
* @public
|
|
156
|
+
* @static
|
|
157
|
+
* @param {string} namespace - Nested namespace; an empty value returns the bundle.
|
|
158
|
+
* @param {{locale?:string,fallback?:*}} [options={}] - Locale override and fallback.
|
|
159
|
+
* @returns {*}
|
|
160
|
+
*/
|
|
161
|
+
static getNamespace(namespace, options = {}) {
|
|
162
|
+
if (!namespace) {
|
|
163
|
+
return I18n.#getBundle(options.locale);
|
|
164
|
+
}
|
|
165
|
+
return I18n.translate(namespace, options);
|
|
80
166
|
}
|
|
81
|
-
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
export default {
|
|
85
|
-
availableLocales,
|
|
86
|
-
DEFAULT_LOCALE,
|
|
87
|
-
getLocale,
|
|
88
|
-
getNamespace,
|
|
89
|
-
onLocaleChange,
|
|
90
|
-
resolveLocale,
|
|
91
|
-
setLocale,
|
|
92
|
-
SUPPORTED_LOCALES,
|
|
93
|
-
translate,
|
|
94
|
-
};
|
|
167
|
+
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
export const translations = {
|
|
2
2
|
"en-EN": {
|
|
3
3
|
prefViewer: {
|
|
4
|
+
viewCube: {
|
|
5
|
+
title: "View orientation",
|
|
6
|
+
front: "Inner",
|
|
7
|
+
back: "Outer",
|
|
8
|
+
top: "Top",
|
|
9
|
+
bottom: "Bottom",
|
|
10
|
+
left: "Left",
|
|
11
|
+
right: "Right"
|
|
12
|
+
},
|
|
4
13
|
menu3d: {
|
|
5
14
|
title: "3D render settings",
|
|
6
15
|
subtitle: "Modify the visual effects.",
|
|
@@ -62,6 +71,15 @@ export const translations = {
|
|
|
62
71
|
},
|
|
63
72
|
"es-ES": {
|
|
64
73
|
prefViewer: {
|
|
74
|
+
viewCube: {
|
|
75
|
+
title: "Orientación de vista",
|
|
76
|
+
front: "Interior",
|
|
77
|
+
back: "Exterior",
|
|
78
|
+
top: "Arriba",
|
|
79
|
+
bottom: "Abajo",
|
|
80
|
+
left: "Izquierda",
|
|
81
|
+
right: "Derecha"
|
|
82
|
+
},
|
|
65
83
|
menu3d: {
|
|
66
84
|
title: "Ajustes de render 3D",
|
|
67
85
|
subtitle: "Modifica los efectos visuales.",
|
package/src/material-utils.js
CHANGED
|
@@ -1,72 +1,79 @@
|
|
|
1
1
|
import { PBRMaterial } from "@babylonjs/core";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Classifies and tags materials for transmission and transparent render ordering.
|
|
5
|
+
* @class MaterialUtils
|
|
6
6
|
*/
|
|
7
|
+
export default class MaterialUtils {
|
|
8
|
+
static #SIMPLIFIED_TRANSMISSION_KEY = "prefViewerSimplifiedTransmission";
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Tags a PBR material as having been converted from physical KHR_materials_transmission to simple
|
|
12
|
+
* alpha blending. The tag is stored in `mat.metadata` under an internal key so that downstream
|
|
13
|
+
* render-order passes can still identify these materials as transparent candidates even after
|
|
14
|
+
* their `subSurface.isRefractionEnabled` flag has been cleared.
|
|
15
|
+
*
|
|
16
|
+
* Returns the same material instance (or the original value when `mat` is falsy) so calls can be
|
|
17
|
+
* chained.
|
|
18
|
+
* @public
|
|
19
|
+
* @static
|
|
20
|
+
* @param {import("@babylonjs/core").PBRMaterial|null|undefined} mat - Material to tag.
|
|
21
|
+
* @returns {import("@babylonjs/core").PBRMaterial|null|undefined} The same `mat` value that was passed in.
|
|
22
|
+
*/
|
|
23
|
+
static markPrefViewerSimplifiedTransmissionMaterial(mat) {
|
|
24
|
+
if (!mat) {
|
|
25
|
+
return mat;
|
|
26
|
+
}
|
|
9
27
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* their `subSurface.isRefractionEnabled` flag has been cleared.
|
|
15
|
-
*
|
|
16
|
-
* Returns the same material instance (or the original value when `mat` is falsy) so calls can be
|
|
17
|
-
* chained.
|
|
18
|
-
* @param {import("@babylonjs/core").PBRMaterial|null|undefined} mat - Material to tag.
|
|
19
|
-
* @returns {import("@babylonjs/core").PBRMaterial|null|undefined} The same `mat` value that was passed in.
|
|
20
|
-
*/
|
|
21
|
-
export function markPrefViewerSimplifiedTransmissionMaterial(mat) {
|
|
22
|
-
if (!mat) {
|
|
28
|
+
mat.metadata = {
|
|
29
|
+
...(mat.metadata ?? {}),
|
|
30
|
+
[MaterialUtils.#SIMPLIFIED_TRANSMISSION_KEY]: true,
|
|
31
|
+
};
|
|
23
32
|
return mat;
|
|
24
33
|
}
|
|
25
|
-
|
|
26
|
-
mat.metadata = {
|
|
27
|
-
...(mat.metadata ?? {}),
|
|
28
|
-
[PREF_VIEWER_SIMPLIFIED_TRANSMISSION_METADATA_KEY]: true,
|
|
29
|
-
};
|
|
30
|
-
return mat;
|
|
31
|
-
}
|
|
32
34
|
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Returns true when a material should be placed in the transparent rendering group (renderingGroupId=1).
|
|
48
|
-
*
|
|
49
|
-
* A material qualifies when any of the following is true:
|
|
50
|
-
* - It is a physical glass material (KHR_materials_transmission / translucency).
|
|
51
|
-
* - It has been tagged as simplified environment transmission via `markPrefViewerSimplifiedTransmissionMaterial`.
|
|
52
|
-
* - Its effective alpha is below 1, its `transparencyMode` is alpha-blend or alpha-test-and-blend,
|
|
53
|
-
* or it self-reports as needing blending/testing through the Babylon.js material API.
|
|
54
|
-
* @param {import("@babylonjs/core").Material|null|undefined} mat - Material to test.
|
|
55
|
-
* @returns {boolean}
|
|
56
|
-
*/
|
|
57
|
-
export function isPrefViewerTransparentRenderOrderMaterial(mat) {
|
|
58
|
-
if (!mat) {
|
|
59
|
-
return false;
|
|
35
|
+
/**
|
|
36
|
+
* Returns true when the material uses Babylon.js physical glass rendering — i.e. either
|
|
37
|
+
* KHR_materials_transmission (refraction) or KHR_materials_translucency is active.
|
|
38
|
+
*
|
|
39
|
+
* This is intentionally narrower than `isPrefViewerTransparentRenderOrderMaterial`: it only
|
|
40
|
+
* matches full physical transmission, not plain alpha blending or simplified environment glass.
|
|
41
|
+
* @public
|
|
42
|
+
* @static
|
|
43
|
+
* @param {import("@babylonjs/core").Material|null|undefined} mat - Material to test.
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
static isPrefViewerPhysicalGlassMaterial(mat) {
|
|
47
|
+
return Boolean(mat?.subSurface?.isRefractionEnabled || mat?.subSurface?.isTranslucencyEnabled);
|
|
60
48
|
}
|
|
61
|
-
const isTransmissive = isPrefViewerPhysicalGlassMaterial(mat);
|
|
62
|
-
const isSimplifiedTransmission = mat.metadata?.[PREF_VIEWER_SIMPLIFIED_TRANSMISSION_METADATA_KEY] === true;
|
|
63
|
-
const alpha = typeof mat.alpha === "number" ? mat.alpha : 1;
|
|
64
|
-
const isAlphaBlended =
|
|
65
|
-
alpha < 1 ||
|
|
66
|
-
mat.transparencyMode === PBRMaterial.PBRMATERIAL_ALPHABLEND ||
|
|
67
|
-
mat.transparencyMode === PBRMaterial.PBRMATERIAL_ALPHATESTANDBLEND ||
|
|
68
|
-
mat.needAlphaBlending?.() === true ||
|
|
69
|
-
mat.needAlphaTesting?.() === true;
|
|
70
49
|
|
|
71
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Returns true when a material should be placed in the transparent rendering group (renderingGroupId=1).
|
|
52
|
+
*
|
|
53
|
+
* A material qualifies when any of the following is true:
|
|
54
|
+
* - It is a physical glass material (KHR_materials_transmission / translucency).
|
|
55
|
+
* - It has been tagged as simplified environment transmission via `markPrefViewerSimplifiedTransmissionMaterial`.
|
|
56
|
+
* - Its effective alpha is below 1, its `transparencyMode` is alpha-blend or alpha-test-and-blend,
|
|
57
|
+
* or it self-reports as needing blending/testing through the Babylon.js material API.
|
|
58
|
+
* @public
|
|
59
|
+
* @static
|
|
60
|
+
* @param {import("@babylonjs/core").Material|null|undefined} mat - Material to test.
|
|
61
|
+
* @returns {boolean}
|
|
62
|
+
*/
|
|
63
|
+
static isPrefViewerTransparentRenderOrderMaterial(mat) {
|
|
64
|
+
if (!mat) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const isTransmissive = MaterialUtils.isPrefViewerPhysicalGlassMaterial(mat);
|
|
68
|
+
const isSimplifiedTransmission = mat.metadata?.[MaterialUtils.#SIMPLIFIED_TRANSMISSION_KEY] === true;
|
|
69
|
+
const alpha = typeof mat.alpha === "number" ? mat.alpha : 1;
|
|
70
|
+
const isAlphaBlended =
|
|
71
|
+
alpha < 1 ||
|
|
72
|
+
mat.transparencyMode === PBRMaterial.PBRMATERIAL_ALPHABLEND ||
|
|
73
|
+
mat.transparencyMode === PBRMaterial.PBRMATERIAL_ALPHATESTANDBLEND ||
|
|
74
|
+
mat.needAlphaBlending?.() === true ||
|
|
75
|
+
mat.needAlphaTesting?.() === true;
|
|
76
|
+
|
|
77
|
+
return Boolean(isTransmissive || isSimplifiedTransmission || isAlphaBlended);
|
|
78
|
+
}
|
|
72
79
|
}
|
|
@@ -105,6 +105,14 @@ export default class PanzoomController {
|
|
|
105
105
|
wheel: null,
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Stores the SVG wrapper, Panzoom options and callbacks, and binds reusable input handlers.
|
|
110
|
+
* @public
|
|
111
|
+
* @param {HTMLElement|SVGElement} wrapper - Element transformed by Panzoom.
|
|
112
|
+
* @param {object} [options] - Panzoom configuration.
|
|
113
|
+
* @param {function(object): void} [changedCallback] - Receives the current viewport state.
|
|
114
|
+
* @param {function(Event): void} [clickCallback] - Receives the original event for a recognized click.
|
|
115
|
+
*/
|
|
108
116
|
constructor(wrapper = null, options = {}, changedCallback = null, clickCallback = null) {
|
|
109
117
|
this.#wrapper = wrapper;
|
|
110
118
|
Object.assign(this.#options, options);
|
|
@@ -161,7 +169,12 @@ export default class PanzoomController {
|
|
|
161
169
|
}
|
|
162
170
|
}
|
|
163
171
|
|
|
164
|
-
/**
|
|
172
|
+
/**
|
|
173
|
+
* Reset transient pointer data after a completed or interrupted gesture.
|
|
174
|
+
* This includes clearing the last pointer down position and resetting counters.
|
|
175
|
+
* @private
|
|
176
|
+
* @returns {void}
|
|
177
|
+
*/
|
|
165
178
|
#resetPointerEvents() {
|
|
166
179
|
this.#pointerEvents.lastPointerDownPosition = null;
|
|
167
180
|
this.#pointerEvents.partialCounter = 0;
|