@ericchen1990/pano-view 3.6.7 → 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/README.md +6 -0
- package/README.zh-CN.md +6 -0
- package/dist/data.cjs +220 -7
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.mts +104 -2
- package/dist/data.d.ts +104 -2
- package/dist/data.mjs +220 -7
- package/dist/data.mjs.map +1 -1
- package/dist/index.cjs +2935 -1119
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +194 -2
- package/dist/index.d.ts +194 -2
- package/dist/index.mjs +2461 -638
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +179 -0
- package/dist/ui.cjs +1 -1
- package/dist/ui.cjs.map +1 -1
- package/dist/ui.mjs +1 -1
- package/dist/ui.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -677,6 +677,12 @@ function FullscreenExample() {
|
|
|
677
677
|
}
|
|
678
678
|
```
|
|
679
679
|
|
|
680
|
+
iPhone Safari does not implement the HTML5 Fullscreen API. When `enterFullscreen()` or MobileVR runs on an iPhone, the viewer covers the visual viewport and scrolls the page in landscape so Safari’s URL and toolbar can collapse. If the bars stay visible, an on-screen prompt asks the user to swipe. Host pages should set:
|
|
681
|
+
|
|
682
|
+
```html
|
|
683
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
684
|
+
```
|
|
685
|
+
|
|
680
686
|
### Imperative scene switching
|
|
681
687
|
|
|
682
688
|
Use `usePanoViewer()` to shorten parent-side commands to `viewer.setScene("roof")`. Keep using controlled `activeSceneId` when the host UI needs to render the current scene id reactively. For uncontrolled mode, `defaultActiveSceneId` still lets imperative calls update the internal state directly.
|
package/README.zh-CN.md
CHANGED
|
@@ -650,6 +650,12 @@ function FullscreenExample() {
|
|
|
650
650
|
}
|
|
651
651
|
```
|
|
652
652
|
|
|
653
|
+
iPhone Safari 不支持 HTML5 Fullscreen API。在 iPhone 上调用 `enterFullscreen()` 或进入 MobileVR 时,查看器会铺满 visual viewport,并在横屏滚动页面,以便收起 Safari 的地址栏和工具栏。若栏仍可见,界面会提示用户滑动。宿主页面应设置:
|
|
654
|
+
|
|
655
|
+
```html
|
|
656
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
657
|
+
```
|
|
658
|
+
|
|
653
659
|
### 命令式切换场景
|
|
654
660
|
|
|
655
661
|
使用 `usePanoViewer()` 可将父组件侧命令缩短为 `viewer.setScene("roof")`。当宿主 UI 需要响应式显示当前场景 id 时,仍建议继续使用受控 `activeSceneId`。若使用非受控模式,`defaultActiveSceneId` 依然允许命令式调用直接更新内部状态。
|
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/
|
|
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 =
|
|
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:
|
|
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 =
|
|
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:
|
|
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);
|