@ericchen1990/pano-view 3.7.0 → 3.8.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,212 @@ 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 parseControls(value, field) {
955
+ if (!isRecord(value)) throw new Error(`${field}: expected an object`);
956
+ const collapse = value.collapse === void 0 ? void 0 : value.collapse === true ? true : value.collapse === false ? false : (() => {
957
+ throw new Error(`${field}.collapse: expected a boolean`);
958
+ })();
959
+ const zoom = value.zoom === void 0 ? void 0 : value.zoom === true ? true : value.zoom === false ? false : (() => {
960
+ throw new Error(`${field}.zoom: expected a boolean`);
961
+ })();
962
+ return {
963
+ ...collapse === void 0 ? {} : { collapse },
964
+ ...zoom === void 0 ? {} : { zoom }
965
+ };
966
+ }
967
+ function parseSpot(value, field, sceneIds) {
968
+ if (!isRecord(value)) throw new Error(`${field}: expected an object`);
969
+ const id = requiredString(value.id, `${field}.id`);
970
+ const sceneId = requiredString(value.sceneId, `${field}.sceneId`);
971
+ if (!sceneIds.has(sceneId))
972
+ throw new Error(`${field}.sceneId: scene does not exist`);
973
+ const x = requiredNumber(value, "x", { min: 0, max: 1 });
974
+ const y = requiredNumber(value, "y", { min: 0, max: 1 });
975
+ const heading = requiredNumber(value, "heading");
976
+ const icon = optionalString(value, "icon");
977
+ if (icon) resolveResource("https://map.invalid/", icon);
978
+ return {
979
+ id,
980
+ sceneId,
981
+ x,
982
+ y,
983
+ heading,
984
+ ...icon === void 0 ? {} : { icon }
985
+ };
986
+ }
987
+ function parsePanoMap(value, sceneIds, options = {}) {
988
+ if (value === void 0) return void 0;
989
+ try {
990
+ if (!isRecord(value)) throw new Error("map: expected an object");
991
+ if (typeof value.image !== "string" || !options.draft && !value.image.trim())
992
+ throw new Error("map.image: expected a nonempty string");
993
+ const image = value.image;
994
+ if (image.trim()) resolveResource("https://map.invalid/", image);
995
+ const interaction = value.interaction === void 0 ? void 0 : value.interaction === "fixed" || value.interaction === "zoom" ? value.interaction : (() => {
996
+ throw new Error('map.interaction: expected "fixed" or "zoom"');
997
+ })();
998
+ const presentation = value.presentation === void 0 ? void 0 : parsePresentation(value.presentation, "map.presentation");
999
+ const controls = value.controls === void 0 ? void 0 : parseControls(value.controls, "map.controls");
1000
+ const iconSize = optionalNumber(value, "iconSize", { exclusiveMin: 0 });
1001
+ const maxScale = optionalNumber(value, "maxScale", { min: 1 });
1002
+ if (!Array.isArray(value.spots)) throw new Error("map.spots: expected an array");
1003
+ const spotIds = /* @__PURE__ */ new Set();
1004
+ const spots = value.spots.map((spot, index) => {
1005
+ const parsed = parseSpot(spot, `map.spots[${index}]`, sceneIds);
1006
+ if (spotIds.has(parsed.id))
1007
+ throw new Error(`map.spots[${index}].id: duplicate spot ID`);
1008
+ spotIds.add(parsed.id);
1009
+ return parsed;
1010
+ });
1011
+ const sector = value.sector === void 0 ? void 0 : parseSector(value.sector, "map.sector");
1012
+ return {
1013
+ image,
1014
+ ...interaction === void 0 ? {} : { interaction },
1015
+ ...presentation === void 0 ? {} : { presentation },
1016
+ ...controls === void 0 ? {} : { controls },
1017
+ ...iconSize === void 0 ? {} : { iconSize },
1018
+ ...maxScale === void 0 ? {} : { maxScale },
1019
+ spots,
1020
+ ...sector === void 0 ? {} : { sector }
1021
+ };
1022
+ } catch (cause) {
1023
+ if (cause instanceof PanoDataError) throw cause;
1024
+ const message = cause instanceof Error ? cause.message : "Invalid map";
1025
+ throw new PanoDataError("INVALID_PROJECT", message, {
1026
+ field: message.split(":")[0],
1027
+ cause
1028
+ });
1029
+ }
1030
+ }
1031
+ function resolvePanoMapAssets(map, baseUrl, resolveAssetUrl) {
1032
+ const resolve = (path, spotId) => mapResource(
1033
+ path,
1034
+ {
1035
+ defaultUrl: resolveResource(baseUrl, path),
1036
+ kind: "map",
1037
+ sceneId: spotId ?? "",
1038
+ hotspotId: spotId
1039
+ },
1040
+ resolveAssetUrl
1041
+ );
1042
+ return {
1043
+ ...map,
1044
+ image: resolve(map.image),
1045
+ spots: map.spots.map(
1046
+ (spot) => spot.icon ? { ...spot, icon: resolve(spot.icon, spot.id) } : { ...spot }
1047
+ )
1048
+ };
1049
+ }
1050
+
1051
+ // src/data/project.ts
1052
+ function optionalString2(record, key) {
1053
+ return record[key] === void 0 ? void 0 : requiredString(record[key], key);
1054
+ }
851
1055
  function parsePanoProject(value, options = {}) {
852
1056
  if (!isRecord(value))
853
1057
  throw new PanoDataError("INVALID_PROJECT", "Expected a project object");
@@ -878,7 +1082,7 @@ function parsePanoProject(value, options = {}) {
878
1082
  throw new Error(
879
1083
  "source: provide exactly one of viewerUrl or viewerConfig"
880
1084
  );
881
- const baseUrl = optionalString(src, "baseUrl");
1085
+ const baseUrl = optionalString2(src, "baseUrl");
882
1086
  const source = src.viewerUrl !== void 0 ? {
883
1087
  viewerUrl: requiredString(src.viewerUrl, "source.viewerUrl"),
884
1088
  baseUrl
@@ -918,7 +1122,7 @@ function parsePanoProject(value, options = {}) {
918
1122
  );
919
1123
  return {
920
1124
  id,
921
- title: optionalString(entry, "title"),
1125
+ title: optionalString2(entry, "title"),
922
1126
  source,
923
1127
  initialView,
924
1128
  ...viewLimits === void 0 ? {} : { viewLimits },
@@ -939,7 +1143,7 @@ function parsePanoProject(value, options = {}) {
939
1143
  }
940
1144
  }
941
1145
  );
942
- const initialSceneId = optionalString(value, "initialSceneId") ?? scenes[0]?.id;
1146
+ const initialSceneId = optionalString2(value, "initialSceneId") ?? scenes[0]?.id;
943
1147
  if (initialSceneId !== void 0 && !ids.has(initialSceneId))
944
1148
  throw new PanoDataError(
945
1149
  "INVALID_REFERENCE",
@@ -967,12 +1171,14 @@ function parsePanoProject(value, options = {}) {
967
1171
  }
968
1172
  }
969
1173
  }
1174
+ const map = parsePanoMap(value.map, ids, options);
970
1175
  return {
971
1176
  schemaVersion: 1,
972
- title: optionalString(value, "title"),
1177
+ title: optionalString2(value, "title"),
973
1178
  initialSceneId,
974
1179
  scenes,
975
- ...popups === void 0 ? {} : { popups }
1180
+ ...popups === void 0 ? {} : { popups },
1181
+ ...map === void 0 ? {} : { map }
976
1182
  };
977
1183
  } catch (cause) {
978
1184
  if (cause instanceof PanoDataError) throw cause;
@@ -1071,7 +1277,14 @@ async function loadPanoProject(source, options = {}) {
1071
1277
  title: definition.title,
1072
1278
  initialSceneId: definition.initialSceneId,
1073
1279
  scenes,
1074
- ...definition.popups === void 0 ? {} : { popups: definition.popups.map((popup) => resolveProjectPopupAssets(popup, root, options.resolvePopupAssetUrl)) }
1280
+ ...definition.popups === void 0 ? {} : { popups: definition.popups.map((popup) => resolveProjectPopupAssets(popup, root, options.resolvePopupAssetUrl)) },
1281
+ ...definition.map === void 0 ? {} : {
1282
+ map: resolvePanoMapAssets(
1283
+ definition.map,
1284
+ root,
1285
+ options.resolveAssetUrl
1286
+ )
1287
+ }
1075
1288
  };
1076
1289
  } finally {
1077
1290
  signal?.removeEventListener("abort", cancel);