@ericchen1990/pano-view 3.5.0 → 3.6.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/README.md +93 -0
- package/README.zh-CN.md +93 -0
- package/dist/data.cjs +202 -25
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.mts +90 -8
- package/dist/data.d.ts +90 -8
- package/dist/data.mjs +202 -25
- package/dist/data.mjs.map +1 -1
- package/dist/index.cjs +1638 -1281
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +148 -11
- package/dist/index.d.ts +148 -11
- package/dist/index.mjs +1244 -883
- package/dist/index.mjs.map +1 -1
- package/dist/ui/styles.css +70 -0
- package/dist/ui.cjs +195 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.d.mts +63 -0
- package/dist/ui.d.ts +63 -0
- package/dist/ui.mjs +152 -0
- package/dist/ui.mjs.map +1 -0
- package/docs/dialogs.md +606 -0
- package/docs/dialogs.zh-CN.md +606 -0
- package/package.json +11 -3
package/dist/data.d.ts
CHANGED
|
@@ -45,6 +45,22 @@ type ViewerDocument = ViewerConfig & {
|
|
|
45
45
|
schemaVersion: 1;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
/** Camera-center limits in degrees. A yaw min greater than max crosses ±180°. */
|
|
49
|
+
type PanoViewLimits = {
|
|
50
|
+
yaw?: {
|
|
51
|
+
min: number;
|
|
52
|
+
max: number;
|
|
53
|
+
};
|
|
54
|
+
pitch?: {
|
|
55
|
+
min: number;
|
|
56
|
+
max: number;
|
|
57
|
+
};
|
|
58
|
+
fov?: {
|
|
59
|
+
min: number;
|
|
60
|
+
max: number;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
48
64
|
type CubeFaceCode = "f" | "r" | "b" | "l" | "u" | "d";
|
|
49
65
|
type TileAddress = {
|
|
50
66
|
face: CubeFaceCode;
|
|
@@ -179,11 +195,14 @@ type SphereTileProps = {
|
|
|
179
195
|
onLevelChange?: (level: number) => void;
|
|
180
196
|
};
|
|
181
197
|
|
|
182
|
-
type
|
|
198
|
+
type SceneViewOptions = {
|
|
199
|
+
viewLimits?: PanoViewLimits;
|
|
200
|
+
};
|
|
201
|
+
type TileScene = Omit<TileProps, "loadMode" | "visible" | "onReady" | "onPreviewError" | "onLoadProgress" | "onTileError" | "onLevelChange"> & SceneViewOptions & {
|
|
183
202
|
id: string;
|
|
184
203
|
type: "tile";
|
|
185
204
|
};
|
|
186
|
-
type SphereTileScene = Omit<SphereTileProps, "loadMode" | "visible" | "onReady" | "onPreviewError" | "onLoadProgress" | "onTileError" | "onLevelChange"> & {
|
|
205
|
+
type SphereTileScene = Omit<SphereTileProps, "loadMode" | "visible" | "onReady" | "onPreviewError" | "onLoadProgress" | "onTileError" | "onLevelChange"> & SceneViewOptions & {
|
|
187
206
|
id: string;
|
|
188
207
|
type: "sphereTile";
|
|
189
208
|
};
|
|
@@ -209,6 +228,37 @@ type LoadSceneFromJsonOptions = Omit<SceneFromViewerConfigOptions, "baseUrl"> &
|
|
|
209
228
|
declare function sceneFromViewerConfig(value: unknown, options: SceneFromViewerConfigOptions): TileScene | SphereTileScene;
|
|
210
229
|
declare function loadSceneFromJson(url: string, options: LoadSceneFromJsonOptions): Promise<TileScene | SphereTileScene>;
|
|
211
230
|
|
|
231
|
+
type ProjectPopupBlock = {
|
|
232
|
+
id: string;
|
|
233
|
+
} & ({
|
|
234
|
+
type: "text";
|
|
235
|
+
text: string;
|
|
236
|
+
} | {
|
|
237
|
+
type: "image";
|
|
238
|
+
src: string;
|
|
239
|
+
alt?: string;
|
|
240
|
+
} | {
|
|
241
|
+
type: "video";
|
|
242
|
+
src: string;
|
|
243
|
+
poster?: string;
|
|
244
|
+
} | {
|
|
245
|
+
type: "audio";
|
|
246
|
+
src: string;
|
|
247
|
+
});
|
|
248
|
+
type ProjectPopupDefinition = {
|
|
249
|
+
id: string;
|
|
250
|
+
title: string;
|
|
251
|
+
blocks: ProjectPopupBlock[];
|
|
252
|
+
};
|
|
253
|
+
type PopupAssetUrlContext = {
|
|
254
|
+
defaultUrl: string;
|
|
255
|
+
popupId: string;
|
|
256
|
+
blockId: string;
|
|
257
|
+
field: "src" | "poster";
|
|
258
|
+
};
|
|
259
|
+
type PopupAssetUrlResolver = (path: string, context: PopupAssetUrlContext) => string;
|
|
260
|
+
declare function resolveProjectPopupAssets(popup: ProjectPopupDefinition, baseUrl: string, resolver?: PopupAssetUrlResolver): ProjectPopupDefinition;
|
|
261
|
+
|
|
212
262
|
type HotspotPosition = {
|
|
213
263
|
/** Horizontal panorama angle in degrees. Positive values look right. */
|
|
214
264
|
yaw: number;
|
|
@@ -398,6 +448,7 @@ type VideoHotspotDefinition = PointHotspotDefinitionBase & {
|
|
|
398
448
|
type AudioHotspotDefinition = PointHotspotDefinitionBase & {
|
|
399
449
|
type: "audio";
|
|
400
450
|
src: string | string[];
|
|
451
|
+
format?: string | string[];
|
|
401
452
|
playing?: boolean;
|
|
402
453
|
loop?: boolean;
|
|
403
454
|
muted?: boolean;
|
|
@@ -449,12 +500,36 @@ type PointHotspotDefinition = ImageHotspotDefinition | GraphicHotspotDefinition
|
|
|
449
500
|
/** A serializable, discriminated hotspot collection item. */
|
|
450
501
|
type HotspotDefinition = PointHotspotDefinition | PolygonHotspotDefinition | PolylineHotspotDefinition;
|
|
451
502
|
|
|
503
|
+
type JsonValue = null | boolean | number | string | JsonValue[] | JsonObject;
|
|
504
|
+
type JsonObject = {
|
|
505
|
+
[key: string]: JsonValue;
|
|
506
|
+
};
|
|
507
|
+
declare const projectHotspotTriggers: readonly ["click", "pointerenter", "pointerleave"];
|
|
508
|
+
type ProjectHotspotTrigger = typeof projectHotspotTriggers[number];
|
|
509
|
+
type ProjectCustomHotspotAction = {
|
|
510
|
+
type: "custom";
|
|
511
|
+
actionId: string;
|
|
512
|
+
params?: JsonObject;
|
|
513
|
+
};
|
|
514
|
+
declare function hotspotAction(hotspot: ProjectHotspotDefinition, trigger: ProjectHotspotTrigger): ProjectHotspotAction | undefined;
|
|
515
|
+
declare function setHotspotAction(hotspot: ProjectHotspotDefinition, trigger: ProjectHotspotTrigger, action?: ProjectHotspotAction): ProjectHotspotDefinition;
|
|
516
|
+
type ProjectHotspotAction = ProjectCustomHotspotAction | {
|
|
517
|
+
type: "scene";
|
|
518
|
+
sceneId: string;
|
|
519
|
+
} | {
|
|
520
|
+
type: "view";
|
|
521
|
+
view: PanoViewerState;
|
|
522
|
+
duration?: number;
|
|
523
|
+
} | {
|
|
524
|
+
type: "popup";
|
|
525
|
+
popupId: string;
|
|
526
|
+
};
|
|
452
527
|
type ProjectHotspotDefinition = HotspotDefinition & {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
};
|
|
528
|
+
/** Legacy click fallback. events.click takes precedence. */
|
|
529
|
+
action?: ProjectHotspotAction;
|
|
530
|
+
events?: Partial<Record<ProjectHotspotTrigger, ProjectHotspotAction>>;
|
|
457
531
|
};
|
|
532
|
+
declare function resolveHotspotAssets(hotspot: ProjectHotspotDefinition, baseUrl: string, sceneId: string, resolver?: AssetUrlResolver): ProjectHotspotDefinition;
|
|
458
533
|
|
|
459
534
|
type PanoProjectSceneDefinition = {
|
|
460
535
|
id: string;
|
|
@@ -469,6 +544,7 @@ type PanoProjectSceneDefinition = {
|
|
|
469
544
|
baseUrl?: string;
|
|
470
545
|
};
|
|
471
546
|
initialView?: Partial<PanoViewerState>;
|
|
547
|
+
viewLimits?: PanoViewLimits;
|
|
472
548
|
hotspots?: ProjectHotspotDefinition[];
|
|
473
549
|
};
|
|
474
550
|
type PanoProjectDefinition = {
|
|
@@ -476,6 +552,7 @@ type PanoProjectDefinition = {
|
|
|
476
552
|
title?: string;
|
|
477
553
|
initialSceneId?: string;
|
|
478
554
|
scenes: PanoProjectSceneDefinition[];
|
|
555
|
+
popups?: ProjectPopupDefinition[];
|
|
479
556
|
};
|
|
480
557
|
type LoadedProjectScene = {
|
|
481
558
|
scene: TileScene | SphereTileScene;
|
|
@@ -487,13 +564,18 @@ type LoadedPanoProject = {
|
|
|
487
564
|
title?: string;
|
|
488
565
|
initialSceneId: string;
|
|
489
566
|
scenes: LoadedProjectScene[];
|
|
567
|
+
popups?: ProjectPopupDefinition[];
|
|
490
568
|
};
|
|
491
569
|
type LoadPanoProjectOptions = JsonRequestOptions & {
|
|
492
570
|
/** Directory used for project-relative references. Required for object input. */
|
|
493
571
|
baseUrl?: string;
|
|
494
572
|
resolveAssetUrl?: AssetUrlResolver;
|
|
573
|
+
resolvePopupAssetUrl?: PopupAssetUrlResolver;
|
|
495
574
|
};
|
|
496
|
-
|
|
575
|
+
/** Drafts permit empty scenes and unfinished popup titles/media; loading and export always validate strictly. */
|
|
576
|
+
declare function parsePanoProject(value: unknown, options?: {
|
|
577
|
+
draft?: boolean;
|
|
578
|
+
}): PanoProjectDefinition;
|
|
497
579
|
declare function loadPanoProject(source: string | PanoProjectDefinition, options?: LoadPanoProjectOptions): Promise<LoadedPanoProject>;
|
|
498
580
|
|
|
499
|
-
export { type AssetUrlContext, type AssetUrlResolver, type CubemapViewerConfig, type JsonRequestOptions, type LoadPanoProjectOptions, type LoadSceneFromJsonOptions, type LoadedPanoProject, type LoadedProjectScene, PanoDataError, type PanoDataErrorCode, type PanoProjectDefinition, type PanoProjectSceneDefinition, type ProjectHotspotDefinition, type SceneFromViewerConfigOptions, type SphereViewerConfig, type ViewerConfig, type ViewerDocument, loadPanoProject, loadSceneFromJson, parsePanoProject, sceneFromViewerConfig };
|
|
581
|
+
export { type AssetUrlContext, type AssetUrlResolver, type CubemapViewerConfig, type JsonObject, type JsonRequestOptions, type JsonValue, type LoadPanoProjectOptions, type LoadSceneFromJsonOptions, type LoadedPanoProject, type LoadedProjectScene, PanoDataError, type PanoDataErrorCode, type PanoProjectDefinition, type PanoProjectSceneDefinition, type PanoViewLimits, type PopupAssetUrlContext, type PopupAssetUrlResolver, type ProjectCustomHotspotAction, type ProjectHotspotAction, type ProjectHotspotDefinition, type ProjectHotspotTrigger, type ProjectPopupBlock, type ProjectPopupDefinition, type SceneFromViewerConfigOptions, type SphereViewerConfig, type ViewerConfig, type ViewerDocument, hotspotAction, loadPanoProject, loadSceneFromJson, parsePanoProject, projectHotspotTriggers, resolveHotspotAssets as resolveProjectHotspotAssets, resolveProjectPopupAssets, sceneFromViewerConfig, setHotspotAction };
|
package/dist/data.mjs
CHANGED
|
@@ -271,7 +271,152 @@ async function loadSceneFromJson(url, options) {
|
|
|
271
271
|
});
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
// src/view-limits.ts
|
|
275
|
+
function parseViewLimits(value) {
|
|
276
|
+
if (value === void 0) return void 0;
|
|
277
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
278
|
+
throw new Error("viewLimits: expected an object");
|
|
279
|
+
const result = {};
|
|
280
|
+
for (const axis of ["yaw", "pitch", "fov"]) {
|
|
281
|
+
const input = value[axis];
|
|
282
|
+
if (input === void 0) continue;
|
|
283
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
284
|
+
throw new Error(`viewLimits.${axis}: expected min and max`);
|
|
285
|
+
const { min, max } = input;
|
|
286
|
+
const lower = axis === "yaw" ? -180 : axis === "pitch" ? -90 : 1;
|
|
287
|
+
const upper = axis === "yaw" ? 180 : axis === "pitch" ? 90 : 179;
|
|
288
|
+
if (typeof min !== "number" || typeof max !== "number" || !Number.isFinite(min) || !Number.isFinite(max) || min < lower || min > upper || max < lower || max > upper)
|
|
289
|
+
throw new Error(`viewLimits.${axis}: expected finite min and max from ${lower} to ${upper}`);
|
|
290
|
+
if (axis !== "yaw" && min > max)
|
|
291
|
+
throw new Error(`viewLimits.${axis}: min must not exceed max`);
|
|
292
|
+
result[axis] = { min, max };
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/data/popups.ts
|
|
298
|
+
function parsePopups(value, draft = false) {
|
|
299
|
+
if (value === void 0) return void 0;
|
|
300
|
+
try {
|
|
301
|
+
if (!Array.isArray(value)) throw new Error("popups: expected an array");
|
|
302
|
+
const ids = /* @__PURE__ */ new Set();
|
|
303
|
+
const string = (input, field, allowEmpty = false) => {
|
|
304
|
+
if (typeof input !== "string" || !allowEmpty && !input.trim())
|
|
305
|
+
throw new Error(`${field}: expected a ${allowEmpty ? "string" : "nonempty string"}`);
|
|
306
|
+
return input;
|
|
307
|
+
};
|
|
308
|
+
return value.map((entry, index) => {
|
|
309
|
+
const field = `popups[${index}]`;
|
|
310
|
+
if (!isRecord(entry)) throw new Error(`${field}: expected an object`);
|
|
311
|
+
const id = requiredString(entry.id, `${field}.id`);
|
|
312
|
+
if (ids.has(id)) throw new Error(`${field}.id: duplicate popup ID`);
|
|
313
|
+
ids.add(id);
|
|
314
|
+
const title = string(entry.title, `${field}.title`, draft);
|
|
315
|
+
if (!Array.isArray(entry.blocks)) throw new Error(`${field}.blocks: expected an array`);
|
|
316
|
+
const blockIds = /* @__PURE__ */ new Set();
|
|
317
|
+
const blocks = entry.blocks.map((block, i) => {
|
|
318
|
+
const path = `${field}.blocks[${i}]`;
|
|
319
|
+
if (!isRecord(block)) throw new Error(`${path}: expected an object`);
|
|
320
|
+
const id2 = requiredString(block.id, `${path}.id`);
|
|
321
|
+
if (blockIds.has(id2)) throw new Error(`${path}.id: duplicate block ID`);
|
|
322
|
+
blockIds.add(id2);
|
|
323
|
+
if (block.type === "text") return { id: id2, type: "text", text: string(block.text, `${path}.text`, true) };
|
|
324
|
+
const src = string(block.src, `${path}.src`, draft);
|
|
325
|
+
if (src) resolveResource("https://popup.invalid/", src);
|
|
326
|
+
if (block.type === "image") return {
|
|
327
|
+
id: id2,
|
|
328
|
+
type: "image",
|
|
329
|
+
src,
|
|
330
|
+
...block.alt === void 0 ? {} : { alt: string(block.alt, `${path}.alt`, true) }
|
|
331
|
+
};
|
|
332
|
+
if (block.type === "audio") return { id: id2, type: "audio", src };
|
|
333
|
+
if (block.type === "video") {
|
|
334
|
+
const poster = block.poster === void 0 ? void 0 : string(block.poster, `${path}.poster`, true);
|
|
335
|
+
if (poster) resolveResource("https://popup.invalid/", poster);
|
|
336
|
+
return { id: id2, type: "video", src, ...poster ? { poster } : {} };
|
|
337
|
+
}
|
|
338
|
+
throw new Error(`${path}.type: unsupported content block`);
|
|
339
|
+
});
|
|
340
|
+
return { id, title, blocks };
|
|
341
|
+
});
|
|
342
|
+
} catch (cause) {
|
|
343
|
+
const message = cause instanceof Error ? cause.message : "Invalid popup content";
|
|
344
|
+
throw new PanoDataError("INVALID_PROJECT", message, { field: message.split(":")[0], cause });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function resolveProjectPopupAssets(popup, baseUrl, resolver) {
|
|
348
|
+
return { ...popup, blocks: popup.blocks.map((block) => {
|
|
349
|
+
if (block.type === "text") return { ...block };
|
|
350
|
+
const resolve = (path, field) => {
|
|
351
|
+
if (!path) throw new PanoDataError("INVALID_PROJECT", `${popup.id}/${block.id}.${field}: choose a resource`);
|
|
352
|
+
const context = { defaultUrl: resolveResource(baseUrl, path), popupId: popup.id, blockId: block.id, field };
|
|
353
|
+
const result = resolver ? resolver(path, context) : context.defaultUrl;
|
|
354
|
+
if (typeof result !== "string" || !result.startsWith("/") && !/^(https?:|blob:|data:)/i.test(result))
|
|
355
|
+
throw new PanoDataError("INVALID_CONFIG", "resolvePopupAssetUrl must return an absolute URL or a site-root path");
|
|
356
|
+
return result;
|
|
357
|
+
};
|
|
358
|
+
return { ...block, src: resolve(block.src, "src"), ...block.type === "video" && block.poster ? { poster: resolve(block.poster, "poster") } : {} };
|
|
359
|
+
}) };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ../../shared/cursor-assets.ts
|
|
363
|
+
function mapCursorAssets(cursor, resolve) {
|
|
364
|
+
return cursor.replace(
|
|
365
|
+
/url\(\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|((?:\\.|[^)\\])*?))\s*\)/gi,
|
|
366
|
+
(_match, double, single, bare) => {
|
|
367
|
+
const path = (double ?? single ?? bare ?? "").replace(
|
|
368
|
+
/\\([\da-f]{1,6}\s?|.)/gi,
|
|
369
|
+
(_escape, value) => /^[\da-f]/i.test(value) ? String.fromCodePoint(
|
|
370
|
+
Math.min(parseInt(value.trim(), 16), 1114111)
|
|
371
|
+
) : value
|
|
372
|
+
);
|
|
373
|
+
return `url(${JSON.stringify(resolve(path))})`;
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
274
378
|
// src/data/hotspots.ts
|
|
379
|
+
var projectHotspotTriggers = ["click", "pointerenter", "pointerleave"];
|
|
380
|
+
function hotspotAction(hotspot, trigger) {
|
|
381
|
+
return hotspot.events?.[trigger] ?? (trigger === "click" ? hotspot.action : void 0);
|
|
382
|
+
}
|
|
383
|
+
function setHotspotAction(hotspot, trigger, action) {
|
|
384
|
+
const events = { ...hotspot.events };
|
|
385
|
+
const next = { ...hotspot, events };
|
|
386
|
+
if (trigger === "click") delete next.action;
|
|
387
|
+
if (action) events[trigger] = action;
|
|
388
|
+
else delete events[trigger];
|
|
389
|
+
if (!Object.keys(events).length) delete next.events;
|
|
390
|
+
return next;
|
|
391
|
+
}
|
|
392
|
+
function jsonValue(value, seen = /* @__PURE__ */ new Set()) {
|
|
393
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
394
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
395
|
+
if (typeof value !== "object" || !value || seen.has(value)) throw new Error("params: expected JSON data");
|
|
396
|
+
if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) throw new Error("params: expected JSON data");
|
|
397
|
+
seen.add(value);
|
|
398
|
+
const result = Array.isArray(value) ? value.map((item) => jsonValue(item, seen)) : Object.fromEntries(Object.entries(value).map(([key, item]) => [key, jsonValue(item, seen)]));
|
|
399
|
+
seen.delete(value);
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
function parseAction(action) {
|
|
403
|
+
if (!isRecord(action)) throw new Error("action: expected an object");
|
|
404
|
+
if (action.type === "scene") return { type: "scene", sceneId: requiredString(action.sceneId, "action.sceneId") };
|
|
405
|
+
if (action.type === "popup") return { type: "popup", popupId: requiredString(action.popupId, "action.popupId") };
|
|
406
|
+
if (action.type === "custom") {
|
|
407
|
+
if (action.params !== void 0 && (!isRecord(action.params) || Array.isArray(action.params))) throw new Error("action.params: expected an object");
|
|
408
|
+
return { type: "custom", actionId: requiredString(action.actionId, "action.actionId"), ...action.params === void 0 ? {} : { params: jsonValue(action.params) } };
|
|
409
|
+
}
|
|
410
|
+
if (action.type === "view") {
|
|
411
|
+
const position = parsePosition(action.view, "action.view");
|
|
412
|
+
const fov = finite(action.view.fov, "action.view.fov");
|
|
413
|
+
if (fov <= 0 || fov >= 180) throw new Error("action.view.fov: expected 0 < fov < 180");
|
|
414
|
+
const duration = action.duration === void 0 ? void 0 : finite(action.duration, "action.duration");
|
|
415
|
+
if (duration !== void 0 && duration < 0) throw new Error("action.duration: expected a nonnegative number");
|
|
416
|
+
return { type: "view", view: { ...position, fov }, ...duration === void 0 ? {} : { duration } };
|
|
417
|
+
}
|
|
418
|
+
throw new Error("action.type: unsupported action");
|
|
419
|
+
}
|
|
275
420
|
var common = [
|
|
276
421
|
"label",
|
|
277
422
|
"visible",
|
|
@@ -314,6 +459,7 @@ var fields = {
|
|
|
314
459
|
],
|
|
315
460
|
audio: [
|
|
316
461
|
"src",
|
|
462
|
+
"format",
|
|
317
463
|
"playing",
|
|
318
464
|
"loop",
|
|
319
465
|
"muted",
|
|
@@ -582,9 +728,10 @@ function parseHotspot(value, sceneId, field) {
|
|
|
582
728
|
result[key] = n;
|
|
583
729
|
} else if (key === "fontWeight" && typeof item === "number")
|
|
584
730
|
result[key] = finite(item, key);
|
|
585
|
-
else if (key === "src" && kind === "audio" && Array.isArray(item)) {
|
|
586
|
-
if (!item.length)
|
|
587
|
-
|
|
731
|
+
else if ((key === "src" || key === "format") && kind === "audio" && Array.isArray(item)) {
|
|
732
|
+
if (!item.length)
|
|
733
|
+
throw new Error(`${key}: expected audio ${key === "src" ? "sources" : "formats"}`);
|
|
734
|
+
result[key] = item.map((entry, i) => requiredString(entry, `${key}[${i}]`));
|
|
588
735
|
} else {
|
|
589
736
|
if (typeof item !== "string")
|
|
590
737
|
throw new Error(`${key}: expected a string`);
|
|
@@ -603,13 +750,19 @@ function parseHotspot(value, sceneId, field) {
|
|
|
603
750
|
throw new Error("text: required");
|
|
604
751
|
if (kind === "sequence" && !result.frameCount)
|
|
605
752
|
throw new Error("frameCount: required");
|
|
606
|
-
if (value.action !== void 0)
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
753
|
+
if (value.action !== void 0) result.action = parseAction(value.action);
|
|
754
|
+
if (value.events !== void 0) {
|
|
755
|
+
if (!isRecord(value.events)) throw new Error("events: expected an object");
|
|
756
|
+
const events = {};
|
|
757
|
+
for (const key of Object.keys(value.events)) {
|
|
758
|
+
if (!projectHotspotTriggers.includes(key)) throw new Error(`events.${key}: unsupported trigger`);
|
|
759
|
+
try {
|
|
760
|
+
events[key] = parseAction(value.events[key]);
|
|
761
|
+
} catch (error) {
|
|
762
|
+
throw new Error(`events.${key}.${error instanceof Error ? error.message : "invalid action"}`);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
result.events = events;
|
|
613
766
|
}
|
|
614
767
|
return result;
|
|
615
768
|
} catch (cause) {
|
|
@@ -633,6 +786,11 @@ function resolveHotspotAssets(hotspot, baseUrl, sceneId, resolver) {
|
|
|
633
786
|
resolver
|
|
634
787
|
) : path;
|
|
635
788
|
const result = { ...hotspot };
|
|
789
|
+
if (result.cursor) result.cursor = mapCursorAssets(result.cursor, resolve);
|
|
790
|
+
if (result.tooltipAppearance?.background) result.tooltipAppearance = {
|
|
791
|
+
...result.tooltipAppearance,
|
|
792
|
+
background: mapCursorAssets(result.tooltipAppearance.background, resolve)
|
|
793
|
+
};
|
|
636
794
|
if ("src" in result)
|
|
637
795
|
result.src = Array.isArray(result.src) ? result.src.map(resolve) : resolve(result.src);
|
|
638
796
|
if ("poster" in result && result.poster)
|
|
@@ -655,7 +813,7 @@ function resolveHotspotAssets(hotspot, baseUrl, sceneId, resolver) {
|
|
|
655
813
|
function optionalString(record, key) {
|
|
656
814
|
return record[key] === void 0 ? void 0 : requiredString(record[key], key);
|
|
657
815
|
}
|
|
658
|
-
function parsePanoProject(value) {
|
|
816
|
+
function parsePanoProject(value, options = {}) {
|
|
659
817
|
if (!isRecord(value))
|
|
660
818
|
throw new PanoDataError("INVALID_PROJECT", "Expected a project object");
|
|
661
819
|
if (value.schemaVersion !== 1)
|
|
@@ -665,7 +823,8 @@ function parsePanoProject(value) {
|
|
|
665
823
|
{ field: "schemaVersion" }
|
|
666
824
|
);
|
|
667
825
|
try {
|
|
668
|
-
|
|
826
|
+
const popups = parsePopups(value.popups, options.draft);
|
|
827
|
+
if (!Array.isArray(value.scenes) || !options.draft && !value.scenes.length)
|
|
669
828
|
throw new Error("scenes: expected a nonempty array");
|
|
670
829
|
const ids = /* @__PURE__ */ new Set();
|
|
671
830
|
const scenes = value.scenes.map(
|
|
@@ -689,6 +848,7 @@ function parsePanoProject(value) {
|
|
|
689
848
|
viewerUrl: requiredString(src.viewerUrl, "source.viewerUrl"),
|
|
690
849
|
baseUrl
|
|
691
850
|
} : { viewerConfig: parseConfig(src.viewerConfig), baseUrl };
|
|
851
|
+
const viewLimits = parseViewLimits(entry.viewLimits);
|
|
692
852
|
let initialView;
|
|
693
853
|
if (entry.initialView !== void 0) {
|
|
694
854
|
if (!isRecord(entry.initialView))
|
|
@@ -726,6 +886,7 @@ function parsePanoProject(value) {
|
|
|
726
886
|
title: optionalString(entry, "title"),
|
|
727
887
|
source,
|
|
728
888
|
initialView,
|
|
889
|
+
...viewLimits === void 0 ? {} : { viewLimits },
|
|
729
890
|
hotspots
|
|
730
891
|
};
|
|
731
892
|
} catch (cause) {
|
|
@@ -743,8 +904,8 @@ function parsePanoProject(value) {
|
|
|
743
904
|
}
|
|
744
905
|
}
|
|
745
906
|
);
|
|
746
|
-
const initialSceneId = optionalString(value, "initialSceneId") ?? scenes[0]
|
|
747
|
-
if (!ids.has(initialSceneId))
|
|
907
|
+
const initialSceneId = optionalString(value, "initialSceneId") ?? scenes[0]?.id;
|
|
908
|
+
if (initialSceneId !== void 0 && !ids.has(initialSceneId))
|
|
748
909
|
throw new PanoDataError(
|
|
749
910
|
"INVALID_REFERENCE",
|
|
750
911
|
"initialSceneId: scene does not exist",
|
|
@@ -752,22 +913,31 @@ function parsePanoProject(value) {
|
|
|
752
913
|
);
|
|
753
914
|
for (const [index, scene] of scenes.entries()) {
|
|
754
915
|
for (const [i, hotspot] of (scene.hotspots ?? []).entries()) {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
"Hotspot target
|
|
759
|
-
{
|
|
916
|
+
for (const trigger of projectHotspotTriggers) {
|
|
917
|
+
const action = hotspotAction(hotspot, trigger);
|
|
918
|
+
if (action?.type === "popup" && !popups?.some((popup) => popup.id === action.popupId))
|
|
919
|
+
throw new PanoDataError("INVALID_REFERENCE", "Hotspot target popup does not exist", {
|
|
760
920
|
sceneId: scene.id,
|
|
761
|
-
field: `scenes[${index}].hotspots[${i}].action.
|
|
762
|
-
}
|
|
763
|
-
)
|
|
921
|
+
field: `scenes[${index}].hotspots[${i}].${hotspot.events?.[trigger] ? `events.${trigger}` : "action"}.popupId`
|
|
922
|
+
});
|
|
923
|
+
if (action?.type === "scene" && !ids.has(action.sceneId))
|
|
924
|
+
throw new PanoDataError(
|
|
925
|
+
"INVALID_REFERENCE",
|
|
926
|
+
"Hotspot target scene does not exist",
|
|
927
|
+
{
|
|
928
|
+
sceneId: scene.id,
|
|
929
|
+
field: `scenes[${index}].hotspots[${i}].${hotspot.events?.[trigger] ? `events.${trigger}` : "action"}.sceneId`
|
|
930
|
+
}
|
|
931
|
+
);
|
|
932
|
+
}
|
|
764
933
|
}
|
|
765
934
|
}
|
|
766
935
|
return {
|
|
767
936
|
schemaVersion: 1,
|
|
768
937
|
title: optionalString(value, "title"),
|
|
769
938
|
initialSceneId,
|
|
770
|
-
scenes
|
|
939
|
+
scenes,
|
|
940
|
+
...popups === void 0 ? {} : { popups }
|
|
771
941
|
};
|
|
772
942
|
} catch (cause) {
|
|
773
943
|
if (cause instanceof PanoDataError) throw cause;
|
|
@@ -828,6 +998,7 @@ async function loadPanoProject(source, options = {}) {
|
|
|
828
998
|
baseUrl: sourceBase ?? root
|
|
829
999
|
});
|
|
830
1000
|
aborted(controller.signal);
|
|
1001
|
+
if (item.viewLimits !== void 0) scene.viewLimits = item.viewLimits;
|
|
831
1002
|
scenes[index] = {
|
|
832
1003
|
scene,
|
|
833
1004
|
title: item.title,
|
|
@@ -864,7 +1035,8 @@ async function loadPanoProject(source, options = {}) {
|
|
|
864
1035
|
return {
|
|
865
1036
|
title: definition.title,
|
|
866
1037
|
initialSceneId: definition.initialSceneId,
|
|
867
|
-
scenes
|
|
1038
|
+
scenes,
|
|
1039
|
+
...definition.popups === void 0 ? {} : { popups: definition.popups.map((popup) => resolveProjectPopupAssets(popup, root, options.resolvePopupAssetUrl)) }
|
|
868
1040
|
};
|
|
869
1041
|
} finally {
|
|
870
1042
|
signal?.removeEventListener("abort", cancel);
|
|
@@ -872,9 +1044,14 @@ async function loadPanoProject(source, options = {}) {
|
|
|
872
1044
|
}
|
|
873
1045
|
export {
|
|
874
1046
|
PanoDataError,
|
|
1047
|
+
hotspotAction,
|
|
875
1048
|
loadPanoProject,
|
|
876
1049
|
loadSceneFromJson,
|
|
877
1050
|
parsePanoProject,
|
|
878
|
-
|
|
1051
|
+
projectHotspotTriggers,
|
|
1052
|
+
resolveHotspotAssets as resolveProjectHotspotAssets,
|
|
1053
|
+
resolveProjectPopupAssets,
|
|
1054
|
+
sceneFromViewerConfig,
|
|
1055
|
+
setHotspotAction
|
|
879
1056
|
};
|
|
880
1057
|
//# sourceMappingURL=data.mjs.map
|