@ericchen1990/pano-view 3.7.0 → 3.9.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/dist/data.cjs CHANGED
@@ -24,8 +24,10 @@ __export(data_exports, {
24
24
  hotspotAction: () => hotspotAction,
25
25
  loadPanoProject: () => loadPanoProject,
26
26
  loadSceneFromJson: () => loadSceneFromJson,
27
+ parsePanoMap: () => parsePanoMap,
27
28
  parsePanoProject: () => parsePanoProject,
28
29
  projectHotspotTriggers: () => projectHotspotTriggers,
30
+ resolvePanoMapAssets: () => resolvePanoMapAssets,
29
31
  resolveProjectHotspotAssets: () => resolveHotspotAssets,
30
32
  resolveProjectPopupAssets: () => resolveProjectPopupAssets,
31
33
  sceneFromViewerConfig: () => sceneFromViewerConfig,
@@ -844,10 +846,231 @@ function resolveHotspotAssets(hotspot, baseUrl, sceneId, resolver) {
844
846
  return result;
845
847
  }
846
848
 
847
- // src/data/project.ts
849
+ // src/data/map.ts
850
+ var CORNERS = /* @__PURE__ */ new Set([
851
+ "top-left",
852
+ "top-right",
853
+ "bottom-left",
854
+ "bottom-right"
855
+ ]);
856
+ function optionalNumber(record, key, options = {}) {
857
+ const value = record[key];
858
+ if (value === void 0) return void 0;
859
+ if (typeof value !== "number" || !Number.isFinite(value))
860
+ throw new Error(`${key}: expected a finite number`);
861
+ if (options.min !== void 0 && value < options.min)
862
+ throw new Error(`${key}: expected a number >= ${options.min}`);
863
+ if (options.max !== void 0 && value > options.max)
864
+ throw new Error(`${key}: expected a number <= ${options.max}`);
865
+ if (options.exclusiveMin !== void 0 && value <= options.exclusiveMin)
866
+ throw new Error(`${key}: expected a number > ${options.exclusiveMin}`);
867
+ if (options.exclusiveMax !== void 0 && value >= options.exclusiveMax)
868
+ throw new Error(`${key}: expected a number < ${options.exclusiveMax}`);
869
+ return value;
870
+ }
871
+ function requiredNumber(record, key, options = {}) {
872
+ const value = optionalNumber(record, key, options);
873
+ if (value === void 0) throw new Error(`${key}: expected a finite number`);
874
+ return value;
875
+ }
848
876
  function optionalString(record, key) {
849
877
  return record[key] === void 0 ? void 0 : requiredString(record[key], key);
850
878
  }
879
+ function parseGradientStop(value, field) {
880
+ if (!isRecord(value)) throw new Error(`${field}: expected an object`);
881
+ const offset = requiredNumber(value, "offset", { min: 0, max: 1 });
882
+ const color = requiredString(value.color, `${field}.color`);
883
+ const opacity = optionalNumber(value, "opacity", { min: 0, max: 1 });
884
+ return {
885
+ offset,
886
+ color,
887
+ ...opacity === void 0 ? {} : { opacity }
888
+ };
889
+ }
890
+ function parseSector(value, field) {
891
+ if (!isRecord(value)) throw new Error(`${field}: expected an object`);
892
+ const angle = requiredNumber(value, "angle", { exclusiveMin: 0, exclusiveMax: 360 });
893
+ const radius = requiredNumber(value, "radius", { exclusiveMin: 0 });
894
+ const followFov = value.followFov === void 0 ? void 0 : value.followFov === true ? true : value.followFov === false ? false : (() => {
895
+ throw new Error(`${field}.followFov: expected a boolean`);
896
+ })();
897
+ const color = optionalString(value, "color");
898
+ const opacity = optionalNumber(value, "opacity", { min: 0, max: 1 });
899
+ const strokeColor = optionalString(value, "strokeColor");
900
+ const strokeWidth = optionalNumber(value, "strokeWidth", { min: 0 });
901
+ const strokeOpacity = optionalNumber(value, "strokeOpacity", { min: 0, max: 1 });
902
+ let gradient;
903
+ if (value.gradient !== void 0) {
904
+ if (!isRecord(value.gradient))
905
+ throw new Error(`${field}.gradient: expected an object`);
906
+ const type = value.gradient.type;
907
+ if (type !== "radial" && type !== "linear")
908
+ throw new Error(`${field}.gradient.type: expected "radial" or "linear"`);
909
+ if (!Array.isArray(value.gradient.stops) || value.gradient.stops.length < 2)
910
+ throw new Error(`${field}.gradient.stops: expected at least two stops`);
911
+ gradient = {
912
+ type,
913
+ stops: value.gradient.stops.map(
914
+ (stop, index) => parseGradientStop(stop, `${field}.gradient.stops[${index}]`)
915
+ )
916
+ };
917
+ }
918
+ return {
919
+ angle,
920
+ radius,
921
+ ...followFov === void 0 ? {} : { followFov },
922
+ ...color === void 0 ? {} : { color },
923
+ ...opacity === void 0 ? {} : { opacity },
924
+ ...strokeColor === void 0 ? {} : { strokeColor },
925
+ ...strokeWidth === void 0 ? {} : { strokeWidth },
926
+ ...strokeOpacity === void 0 ? {} : { strokeOpacity },
927
+ ...gradient === void 0 ? {} : { gradient }
928
+ };
929
+ }
930
+ function parsePresentation(value, field) {
931
+ if (!isRecord(value)) throw new Error(`${field}: expected an object`);
932
+ const mode = value.mode;
933
+ if (mode !== "docked" && mode !== "dialog")
934
+ throw new Error(`${field}.mode: expected "docked" or "dialog"`);
935
+ const cornerValue = value.corner;
936
+ const corner = cornerValue === void 0 ? void 0 : typeof cornerValue === "string" && CORNERS.has(cornerValue) ? cornerValue : (() => {
937
+ throw new Error(
938
+ `${field}.corner: expected top-left, top-right, bottom-left, or bottom-right`
939
+ );
940
+ })();
941
+ const width = optionalNumber(value, "width", { exclusiveMin: 0 });
942
+ const height = optionalNumber(value, "height", { exclusiveMin: 0 });
943
+ const offsetX = optionalNumber(value, "offsetX", { min: 0 });
944
+ const offsetY = optionalNumber(value, "offsetY", { min: 0 });
945
+ return {
946
+ mode,
947
+ ...corner === void 0 ? {} : { corner },
948
+ ...width === void 0 ? {} : { width },
949
+ ...height === void 0 ? {} : { height },
950
+ ...offsetX === void 0 ? {} : { offsetX },
951
+ ...offsetY === void 0 ? {} : { offsetY }
952
+ };
953
+ }
954
+ function parseOptionalBoolean(value, field) {
955
+ if (value === void 0) return void 0;
956
+ if (value === true) return true;
957
+ if (value === false) return false;
958
+ throw new Error(`${field}: expected a boolean`);
959
+ }
960
+ function parseDragMotion(value, field) {
961
+ if (value === false) return false;
962
+ if (!isRecord(value)) throw new Error(`${field}: expected an object or false`);
963
+ const inertia = parseOptionalBoolean(value.inertia, `${field}.inertia`);
964
+ const bounce = parseOptionalBoolean(value.bounce, `${field}.bounce`);
965
+ const motion = {
966
+ ...inertia === void 0 ? {} : { inertia },
967
+ ...bounce === void 0 ? {} : { bounce }
968
+ };
969
+ return motion;
970
+ }
971
+ function parseControls(value, field) {
972
+ if (!isRecord(value)) throw new Error(`${field}: expected an object`);
973
+ const collapse = value.collapse === void 0 ? void 0 : value.collapse === true ? true : value.collapse === false ? false : (() => {
974
+ throw new Error(`${field}.collapse: expected a boolean`);
975
+ })();
976
+ const zoom = value.zoom === void 0 ? void 0 : value.zoom === true ? true : value.zoom === false ? false : (() => {
977
+ throw new Error(`${field}.zoom: expected a boolean`);
978
+ })();
979
+ return {
980
+ ...collapse === void 0 ? {} : { collapse },
981
+ ...zoom === void 0 ? {} : { zoom }
982
+ };
983
+ }
984
+ function parseSpot(value, field, sceneIds) {
985
+ if (!isRecord(value)) throw new Error(`${field}: expected an object`);
986
+ const id = requiredString(value.id, `${field}.id`);
987
+ const sceneId = requiredString(value.sceneId, `${field}.sceneId`);
988
+ if (!sceneIds.has(sceneId))
989
+ throw new Error(`${field}.sceneId: scene does not exist`);
990
+ const x = requiredNumber(value, "x", { min: 0, max: 1 });
991
+ const y = requiredNumber(value, "y", { min: 0, max: 1 });
992
+ const heading = requiredNumber(value, "heading");
993
+ const icon = optionalString(value, "icon");
994
+ if (icon) resolveResource("https://map.invalid/", icon);
995
+ return {
996
+ id,
997
+ sceneId,
998
+ x,
999
+ y,
1000
+ heading,
1001
+ ...icon === void 0 ? {} : { icon }
1002
+ };
1003
+ }
1004
+ function parsePanoMap(value, sceneIds, options = {}) {
1005
+ if (value === void 0) return void 0;
1006
+ try {
1007
+ if (!isRecord(value)) throw new Error("map: expected an object");
1008
+ if (typeof value.image !== "string" || !options.draft && !value.image.trim())
1009
+ throw new Error("map.image: expected a nonempty string");
1010
+ const image = value.image;
1011
+ if (image.trim()) resolveResource("https://map.invalid/", image);
1012
+ const interaction = value.interaction === void 0 ? void 0 : value.interaction === "fixed" || value.interaction === "zoom" ? value.interaction : (() => {
1013
+ throw new Error('map.interaction: expected "fixed" or "zoom"');
1014
+ })();
1015
+ const presentation = value.presentation === void 0 ? void 0 : parsePresentation(value.presentation, "map.presentation");
1016
+ const controls = value.controls === void 0 ? void 0 : parseControls(value.controls, "map.controls");
1017
+ const iconSize = optionalNumber(value, "iconSize", { exclusiveMin: 0 });
1018
+ const maxScale = optionalNumber(value, "maxScale", { min: 1 });
1019
+ const dragMotion = value.dragMotion === void 0 ? void 0 : parseDragMotion(value.dragMotion, "map.dragMotion");
1020
+ if (!Array.isArray(value.spots)) throw new Error("map.spots: expected an array");
1021
+ const spotIds = /* @__PURE__ */ new Set();
1022
+ const spots = value.spots.map((spot, index) => {
1023
+ const parsed = parseSpot(spot, `map.spots[${index}]`, sceneIds);
1024
+ if (spotIds.has(parsed.id))
1025
+ throw new Error(`map.spots[${index}].id: duplicate spot ID`);
1026
+ spotIds.add(parsed.id);
1027
+ return parsed;
1028
+ });
1029
+ const sector = value.sector === void 0 ? void 0 : parseSector(value.sector, "map.sector");
1030
+ return {
1031
+ image,
1032
+ ...interaction === void 0 ? {} : { interaction },
1033
+ ...presentation === void 0 ? {} : { presentation },
1034
+ ...controls === void 0 ? {} : { controls },
1035
+ ...iconSize === void 0 ? {} : { iconSize },
1036
+ ...maxScale === void 0 ? {} : { maxScale },
1037
+ ...dragMotion === void 0 ? {} : { dragMotion },
1038
+ spots,
1039
+ ...sector === void 0 ? {} : { sector }
1040
+ };
1041
+ } catch (cause) {
1042
+ if (cause instanceof PanoDataError) throw cause;
1043
+ const message = cause instanceof Error ? cause.message : "Invalid map";
1044
+ throw new PanoDataError("INVALID_PROJECT", message, {
1045
+ field: message.split(":")[0],
1046
+ cause
1047
+ });
1048
+ }
1049
+ }
1050
+ function resolvePanoMapAssets(map, baseUrl, resolveAssetUrl) {
1051
+ const resolve = (path, spotId) => mapResource(
1052
+ path,
1053
+ {
1054
+ defaultUrl: resolveResource(baseUrl, path),
1055
+ kind: "map",
1056
+ sceneId: spotId ?? "",
1057
+ hotspotId: spotId
1058
+ },
1059
+ resolveAssetUrl
1060
+ );
1061
+ return {
1062
+ ...map,
1063
+ image: resolve(map.image),
1064
+ spots: map.spots.map(
1065
+ (spot) => spot.icon ? { ...spot, icon: resolve(spot.icon, spot.id) } : { ...spot }
1066
+ )
1067
+ };
1068
+ }
1069
+
1070
+ // src/data/project.ts
1071
+ function optionalString2(record, key) {
1072
+ return record[key] === void 0 ? void 0 : requiredString(record[key], key);
1073
+ }
851
1074
  function parsePanoProject(value, options = {}) {
852
1075
  if (!isRecord(value))
853
1076
  throw new PanoDataError("INVALID_PROJECT", "Expected a project object");
@@ -878,7 +1101,7 @@ function parsePanoProject(value, options = {}) {
878
1101
  throw new Error(
879
1102
  "source: provide exactly one of viewerUrl or viewerConfig"
880
1103
  );
881
- const baseUrl = optionalString(src, "baseUrl");
1104
+ const baseUrl = optionalString2(src, "baseUrl");
882
1105
  const source = src.viewerUrl !== void 0 ? {
883
1106
  viewerUrl: requiredString(src.viewerUrl, "source.viewerUrl"),
884
1107
  baseUrl
@@ -918,7 +1141,7 @@ function parsePanoProject(value, options = {}) {
918
1141
  );
919
1142
  return {
920
1143
  id,
921
- title: optionalString(entry, "title"),
1144
+ title: optionalString2(entry, "title"),
922
1145
  source,
923
1146
  initialView,
924
1147
  ...viewLimits === void 0 ? {} : { viewLimits },
@@ -939,7 +1162,7 @@ function parsePanoProject(value, options = {}) {
939
1162
  }
940
1163
  }
941
1164
  );
942
- const initialSceneId = optionalString(value, "initialSceneId") ?? scenes[0]?.id;
1165
+ const initialSceneId = optionalString2(value, "initialSceneId") ?? scenes[0]?.id;
943
1166
  if (initialSceneId !== void 0 && !ids.has(initialSceneId))
944
1167
  throw new PanoDataError(
945
1168
  "INVALID_REFERENCE",
@@ -967,12 +1190,14 @@ function parsePanoProject(value, options = {}) {
967
1190
  }
968
1191
  }
969
1192
  }
1193
+ const map = parsePanoMap(value.map, ids, options);
970
1194
  return {
971
1195
  schemaVersion: 1,
972
- title: optionalString(value, "title"),
1196
+ title: optionalString2(value, "title"),
973
1197
  initialSceneId,
974
1198
  scenes,
975
- ...popups === void 0 ? {} : { popups }
1199
+ ...popups === void 0 ? {} : { popups },
1200
+ ...map === void 0 ? {} : { map }
976
1201
  };
977
1202
  } catch (cause) {
978
1203
  if (cause instanceof PanoDataError) throw cause;
@@ -1071,7 +1296,14 @@ async function loadPanoProject(source, options = {}) {
1071
1296
  title: definition.title,
1072
1297
  initialSceneId: definition.initialSceneId,
1073
1298
  scenes,
1074
- ...definition.popups === void 0 ? {} : { popups: definition.popups.map((popup) => resolveProjectPopupAssets(popup, root, options.resolvePopupAssetUrl)) }
1299
+ ...definition.popups === void 0 ? {} : { popups: definition.popups.map((popup) => resolveProjectPopupAssets(popup, root, options.resolvePopupAssetUrl)) },
1300
+ ...definition.map === void 0 ? {} : {
1301
+ map: resolvePanoMapAssets(
1302
+ definition.map,
1303
+ root,
1304
+ options.resolveAssetUrl
1305
+ )
1306
+ }
1075
1307
  };
1076
1308
  } finally {
1077
1309
  signal?.removeEventListener("abort", cancel);