@papyrus-sdk/ui-react 0.2.21 → 0.2.23
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/base.css +67 -3
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +529 -64
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +529 -64
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32,6 +32,8 @@ var import_react = require("react");
|
|
|
32
32
|
var import_react_dom = require("react-dom");
|
|
33
33
|
var import_core = require("@papyrus-sdk/core");
|
|
34
34
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
35
|
+
var MOBILE_LANDSCAPE_MAX_HEIGHT_PX = 500;
|
|
36
|
+
var MOBILE_VIEWPORT_QUERY = `(max-width: 639px), (orientation: landscape) and (max-height: ${MOBILE_LANDSCAPE_MAX_HEIGHT_PX}px)`;
|
|
35
37
|
var Topbar = ({
|
|
36
38
|
engine,
|
|
37
39
|
showBrand = false,
|
|
@@ -70,6 +72,8 @@ var Topbar = ({
|
|
|
70
72
|
const [isMobileViewport, setIsMobileViewport] = (0, import_react.useState)(false);
|
|
71
73
|
const pageDigits = Math.max(2, String(pageCount || 1).length);
|
|
72
74
|
const isDark = uiTheme === "dark";
|
|
75
|
+
const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
|
|
76
|
+
const isSingleViewportMode = renderTargetType === "element" || renderTargetType === "webview";
|
|
73
77
|
const canUseDOM = typeof document !== "undefined";
|
|
74
78
|
const hasMobileMenu = showZoomControls || showPageThemeSelector || showUIToggle || showUpload;
|
|
75
79
|
(0, import_react.useEffect)(() => {
|
|
@@ -86,7 +90,7 @@ var Topbar = ({
|
|
|
86
90
|
}, [hasMobileMenu]);
|
|
87
91
|
(0, import_react.useEffect)(() => {
|
|
88
92
|
if (!canUseDOM || typeof window.matchMedia !== "function") return;
|
|
89
|
-
const mediaQuery = window.matchMedia(
|
|
93
|
+
const mediaQuery = window.matchMedia(MOBILE_VIEWPORT_QUERY);
|
|
90
94
|
const updateViewport = () => setIsMobileViewport(mediaQuery.matches);
|
|
91
95
|
updateViewport();
|
|
92
96
|
if (typeof mediaQuery.addEventListener === "function") {
|
|
@@ -151,6 +155,10 @@ var Topbar = ({
|
|
|
151
155
|
if (pageCount <= 0) return;
|
|
152
156
|
const nextPage = Math.max(1, Math.min(pageCount, isNaN(page) ? 1 : page));
|
|
153
157
|
engine.goToPage(nextPage);
|
|
158
|
+
if (isSingleViewportMode) {
|
|
159
|
+
setDocumentState({ currentPage: nextPage, scrollToPageSignal: null });
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
154
162
|
triggerScrollToPage(nextPage - 1);
|
|
155
163
|
};
|
|
156
164
|
const handleFileUpload = async (event) => {
|
|
@@ -786,12 +794,20 @@ var withAlpha = (hex, alpha) => {
|
|
|
786
794
|
const b = parseInt(value.slice(4, 6), 16);
|
|
787
795
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
788
796
|
};
|
|
797
|
+
var isEpubDebugEnabled = () => {
|
|
798
|
+
try {
|
|
799
|
+
return Boolean(globalThis?.__PAPYRUS_EPUB_DEBUG__);
|
|
800
|
+
} catch {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
};
|
|
789
804
|
var Thumbnail = ({ engine, pageIndex, active, isDark, accentColor, onClick }) => {
|
|
790
805
|
const wrapperRef = (0, import_react2.useRef)(null);
|
|
791
806
|
const canvasRef = (0, import_react2.useRef)(null);
|
|
792
807
|
const htmlRef = (0, import_react2.useRef)(null);
|
|
793
808
|
const accentSoft = withAlpha(accentColor, 0.12);
|
|
794
809
|
const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
|
|
810
|
+
const isElementRender = renderTargetType === "element";
|
|
795
811
|
const [isVisible, setIsVisible] = (0, import_react2.useState)(false);
|
|
796
812
|
(0, import_react2.useEffect)(() => {
|
|
797
813
|
const target = wrapperRef.current;
|
|
@@ -816,14 +832,14 @@ var Thumbnail = ({ engine, pageIndex, active, isDark, accentColor, onClick }) =>
|
|
|
816
832
|
return () => observer.disconnect();
|
|
817
833
|
}, []);
|
|
818
834
|
(0, import_react2.useEffect)(() => {
|
|
819
|
-
if (
|
|
820
|
-
const target = canvasRef.current;
|
|
835
|
+
if (!isVisible || isElementRender) return;
|
|
836
|
+
const target = renderTargetType === "element" ? htmlRef.current : canvasRef.current;
|
|
821
837
|
if (target) {
|
|
822
838
|
engine.renderPage(pageIndex, target, 0.15).catch((err) => {
|
|
823
839
|
console.error("[Papyrus] Thumbnail render failed:", err);
|
|
824
840
|
});
|
|
825
841
|
}
|
|
826
|
-
}, [engine, pageIndex, renderTargetType, isVisible]);
|
|
842
|
+
}, [engine, pageIndex, renderTargetType, isVisible, isElementRender]);
|
|
827
843
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
828
844
|
"div",
|
|
829
845
|
{
|
|
@@ -837,13 +853,20 @@ var Thumbnail = ({ engine, pageIndex, active, isDark, accentColor, onClick }) =>
|
|
|
837
853
|
{
|
|
838
854
|
className: `shadow-lg rounded overflow-hidden mb-2 border ${isDark ? "border-[#333]" : "border-gray-200"}`,
|
|
839
855
|
children: [
|
|
856
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
857
|
+
"div",
|
|
858
|
+
{
|
|
859
|
+
className: `w-[90px] h-[120px] items-center justify-center text-[10px] font-black tracking-wider ${isElementRender ? "flex" : "hidden"} ${isDark ? "bg-[#1f1f1f] text-gray-300" : "bg-gray-100 text-gray-500"}`,
|
|
860
|
+
children: "CAP"
|
|
861
|
+
}
|
|
862
|
+
),
|
|
840
863
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
841
864
|
"canvas",
|
|
842
865
|
{
|
|
843
866
|
ref: canvasRef,
|
|
844
867
|
className: "max-w-full h-auto bg-white",
|
|
845
868
|
style: {
|
|
846
|
-
display:
|
|
869
|
+
display: isElementRender ? "none" : "block"
|
|
847
870
|
}
|
|
848
871
|
}
|
|
849
872
|
),
|
|
@@ -855,10 +878,9 @@ var Thumbnail = ({ engine, pageIndex, active, isDark, accentColor, onClick }) =>
|
|
|
855
878
|
style: {
|
|
856
879
|
width: 90,
|
|
857
880
|
height: 120,
|
|
858
|
-
display:
|
|
881
|
+
display: "none",
|
|
859
882
|
overflow: "hidden"
|
|
860
|
-
}
|
|
861
|
-
children: renderTargetType === "element" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-full h-full flex items-center justify-center text-[10px] font-semibold text-gray-500", children: "HTML" })
|
|
883
|
+
}
|
|
862
884
|
}
|
|
863
885
|
)
|
|
864
886
|
]
|
|
@@ -877,9 +899,11 @@ var Thumbnail = ({ engine, pageIndex, active, isDark, accentColor, onClick }) =>
|
|
|
877
899
|
);
|
|
878
900
|
};
|
|
879
901
|
var OutlineNode = ({ item, engine, isDark, accentColor, depth = 0 }) => {
|
|
880
|
-
const { triggerScrollToPage, outlineSearchQuery } = (0, import_core2.useViewerStore)();
|
|
902
|
+
const { triggerScrollToPage, outlineSearchQuery, setDocumentState } = (0, import_core2.useViewerStore)();
|
|
881
903
|
const [expanded, setExpanded] = (0, import_react2.useState)(true);
|
|
882
904
|
const accentSoft = withAlpha(accentColor, 0.2);
|
|
905
|
+
const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
|
|
906
|
+
const isSingleViewportMode = renderTargetType === "element" || renderTargetType === "webview";
|
|
883
907
|
const matchesSearch = outlineSearchQuery === "" || item.title.toLowerCase().includes(outlineSearchQuery.toLowerCase());
|
|
884
908
|
const hasMatchingChildren = item.children?.some(
|
|
885
909
|
(child) => child.title.toLowerCase().includes(outlineSearchQuery.toLowerCase())
|
|
@@ -887,10 +911,53 @@ var OutlineNode = ({ item, engine, isDark, accentColor, depth = 0 }) => {
|
|
|
887
911
|
if (!matchesSearch && !hasMatchingChildren && outlineSearchQuery !== "")
|
|
888
912
|
return null;
|
|
889
913
|
const handleClick = () => {
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
914
|
+
void (async () => {
|
|
915
|
+
if (item.pageIndex < 0 && !item.dest) return;
|
|
916
|
+
let targetPageIndex = item.pageIndex;
|
|
917
|
+
let navigatedByDestination = false;
|
|
918
|
+
const destinationEngine = engine;
|
|
919
|
+
if (isSingleViewportMode && item.dest && typeof destinationEngine.goToDestination === "function") {
|
|
920
|
+
try {
|
|
921
|
+
if (isEpubDebugEnabled()) {
|
|
922
|
+
console.log("[EPUBUI] toc-click", {
|
|
923
|
+
title: item.title,
|
|
924
|
+
dest: item.dest,
|
|
925
|
+
pageIndex: item.pageIndex
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
const resolved = await destinationEngine.goToDestination(item.dest);
|
|
929
|
+
if (isEpubDebugEnabled()) {
|
|
930
|
+
console.log("[EPUBUI] toc-resolved", {
|
|
931
|
+
title: item.title,
|
|
932
|
+
resolved
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
if (resolved != null) targetPageIndex = resolved;
|
|
936
|
+
navigatedByDestination = true;
|
|
937
|
+
} catch {
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (item.dest && (!navigatedByDestination || targetPageIndex < 0)) {
|
|
941
|
+
try {
|
|
942
|
+
const resolved = await engine.getPageIndex(item.dest);
|
|
943
|
+
if (resolved != null) targetPageIndex = resolved;
|
|
944
|
+
} catch {
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
if (navigatedByDestination && isSingleViewportMode) {
|
|
948
|
+
const page2 = targetPageIndex >= 0 ? targetPageIndex + 1 : engine.getCurrentPage();
|
|
949
|
+
setDocumentState({ currentPage: page2, scrollToPageSignal: null });
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
if (targetPageIndex < 0) return;
|
|
953
|
+
const page = targetPageIndex + 1;
|
|
954
|
+
engine.goToPage(page);
|
|
955
|
+
if (isSingleViewportMode) {
|
|
956
|
+
setDocumentState({ currentPage: page, scrollToPageSignal: null });
|
|
957
|
+
} else {
|
|
958
|
+
triggerScrollToPage(targetPageIndex);
|
|
959
|
+
}
|
|
960
|
+
})();
|
|
894
961
|
};
|
|
895
962
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col", children: [
|
|
896
963
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
@@ -972,6 +1039,24 @@ var SidebarLeft = ({ engine, style }) => {
|
|
|
972
1039
|
accentColor
|
|
973
1040
|
} = (0, import_core2.useViewerStore)();
|
|
974
1041
|
const isDark = uiTheme === "dark";
|
|
1042
|
+
const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
|
|
1043
|
+
const prefersSummaryByDefault = renderTargetType === "element" || renderTargetType === "webview";
|
|
1044
|
+
const autoSummaryKeyRef = (0, import_react2.useRef)(null);
|
|
1045
|
+
(0, import_react2.useEffect)(() => {
|
|
1046
|
+
if (!prefersSummaryByDefault) return;
|
|
1047
|
+
if (sidebarLeftTab !== "thumbnails") return;
|
|
1048
|
+
if (pageCount <= 0) return;
|
|
1049
|
+
const docKey = `${pageCount}:${outline.length}`;
|
|
1050
|
+
if (autoSummaryKeyRef.current === docKey) return;
|
|
1051
|
+
autoSummaryKeyRef.current = docKey;
|
|
1052
|
+
setSidebarLeftTab("summary");
|
|
1053
|
+
}, [
|
|
1054
|
+
prefersSummaryByDefault,
|
|
1055
|
+
sidebarLeftTab,
|
|
1056
|
+
pageCount,
|
|
1057
|
+
outline.length,
|
|
1058
|
+
setSidebarLeftTab
|
|
1059
|
+
]);
|
|
975
1060
|
if (!sidebarLeftOpen) return null;
|
|
976
1061
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
977
1062
|
"div",
|
|
@@ -1085,8 +1170,16 @@ var SidebarLeft = ({ engine, style }) => {
|
|
|
1085
1170
|
accentColor,
|
|
1086
1171
|
active: currentPage === idx + 1,
|
|
1087
1172
|
onClick: () => {
|
|
1088
|
-
|
|
1089
|
-
|
|
1173
|
+
const page = idx + 1;
|
|
1174
|
+
engine.goToPage(page);
|
|
1175
|
+
if (prefersSummaryByDefault) {
|
|
1176
|
+
setDocumentState({
|
|
1177
|
+
currentPage: page,
|
|
1178
|
+
scrollToPageSignal: null
|
|
1179
|
+
});
|
|
1180
|
+
} else {
|
|
1181
|
+
triggerScrollToPage(idx);
|
|
1182
|
+
}
|
|
1090
1183
|
}
|
|
1091
1184
|
},
|
|
1092
1185
|
idx
|
|
@@ -1138,10 +1231,14 @@ var SidebarRight = ({ engine, style }) => {
|
|
|
1138
1231
|
} = (0, import_core3.useViewerStore)();
|
|
1139
1232
|
const [query, setQuery] = (0, import_react3.useState)("");
|
|
1140
1233
|
const [isSearching, setIsSearching] = (0, import_react3.useState)(false);
|
|
1141
|
-
const [contentDrafts, setContentDrafts] = (0, import_react3.useState)(
|
|
1234
|
+
const [contentDrafts, setContentDrafts] = (0, import_react3.useState)(
|
|
1235
|
+
{}
|
|
1236
|
+
);
|
|
1142
1237
|
const [replyDrafts, setReplyDrafts] = (0, import_react3.useState)({});
|
|
1143
1238
|
const searchService = new import_core3.SearchService(engine);
|
|
1144
1239
|
const isDark = uiTheme === "dark";
|
|
1240
|
+
const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
|
|
1241
|
+
const isSingleViewportMode = renderTargetType === "element" || renderTargetType === "webview";
|
|
1145
1242
|
const accentSoft = withAlpha2(accentColor, 0.12);
|
|
1146
1243
|
const resultsCount = searchResults.length;
|
|
1147
1244
|
const handleSearch = async (e) => {
|
|
@@ -1158,9 +1255,13 @@ var SidebarRight = ({ engine, style }) => {
|
|
|
1158
1255
|
const jumpToAnnotation = (annotation) => {
|
|
1159
1256
|
const page = annotation.pageIndex + 1;
|
|
1160
1257
|
engine.goToPage(page);
|
|
1161
|
-
|
|
1258
|
+
if (isSingleViewportMode) {
|
|
1259
|
+
setDocumentState({ currentPage: page, scrollToPageSignal: null });
|
|
1260
|
+
} else {
|
|
1261
|
+
setDocumentState({ currentPage: page });
|
|
1262
|
+
triggerScrollToPage(annotation.pageIndex);
|
|
1263
|
+
}
|
|
1162
1264
|
setSelectedAnnotation(annotation.id);
|
|
1163
|
-
triggerScrollToPage(annotation.pageIndex);
|
|
1164
1265
|
};
|
|
1165
1266
|
const getContentDraft = (annotation) => {
|
|
1166
1267
|
if (Object.prototype.hasOwnProperty.call(contentDrafts, annotation.id)) {
|
|
@@ -1307,11 +1408,19 @@ var SidebarRight = ({ engine, style }) => {
|
|
|
1307
1408
|
onClick: () => {
|
|
1308
1409
|
const page = res.pageIndex + 1;
|
|
1309
1410
|
engine.goToPage(page);
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1411
|
+
if (isSingleViewportMode) {
|
|
1412
|
+
setDocumentState({
|
|
1413
|
+
activeSearchIndex: idx,
|
|
1414
|
+
currentPage: page,
|
|
1415
|
+
scrollToPageSignal: null
|
|
1416
|
+
});
|
|
1417
|
+
} else {
|
|
1418
|
+
setDocumentState({
|
|
1419
|
+
activeSearchIndex: idx,
|
|
1420
|
+
currentPage: page
|
|
1421
|
+
});
|
|
1422
|
+
triggerScrollToPage(res.pageIndex);
|
|
1423
|
+
}
|
|
1315
1424
|
},
|
|
1316
1425
|
className: `p-4 rounded-xl border-2 cursor-pointer transition-all group hover:scale-[1.02] ${idx === activeSearchIndex ? "shadow-lg" : isDark ? "border-[#333] hover:border-[#555] bg-[#222]" : "border-gray-50 hover:border-gray-200 bg-gray-50/50 hover:bg-white"}`,
|
|
1317
1426
|
style: idx === activeSearchIndex ? {
|
|
@@ -1398,7 +1507,9 @@ var SidebarRight = ({ engine, style }) => {
|
|
|
1398
1507
|
const replies = ann.replies ?? [];
|
|
1399
1508
|
const contentDraft = getContentDraft(ann);
|
|
1400
1509
|
const replyDraft = getReplyDraft(ann.id);
|
|
1401
|
-
const hasExistingContent = Boolean(
|
|
1510
|
+
const hasExistingContent = Boolean(
|
|
1511
|
+
(ann.content ?? "").trim()
|
|
1512
|
+
);
|
|
1402
1513
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1403
1514
|
"div",
|
|
1404
1515
|
{
|
|
@@ -1558,6 +1669,7 @@ var PageRenderer = ({
|
|
|
1558
1669
|
engine,
|
|
1559
1670
|
pageIndex,
|
|
1560
1671
|
availableWidth,
|
|
1672
|
+
availableHeight,
|
|
1561
1673
|
onMeasuredSize
|
|
1562
1674
|
}) => {
|
|
1563
1675
|
const containerRef = (0, import_react4.useRef)(null);
|
|
@@ -1599,6 +1711,11 @@ var PageRenderer = ({
|
|
|
1599
1711
|
} = (0, import_core4.useViewerStore)();
|
|
1600
1712
|
const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
|
|
1601
1713
|
const isElementRender = renderTargetType === "element";
|
|
1714
|
+
const isLandscape = typeof availableWidth === "number" && typeof availableHeight === "number" && availableWidth > availableHeight;
|
|
1715
|
+
const isLandscapeShort = isLandscape && typeof availableHeight === "number" && availableHeight <= 500;
|
|
1716
|
+
const isMobileElementViewport = isElementRender && typeof availableWidth === "number" && (availableWidth <= 768 || isLandscapeShort);
|
|
1717
|
+
const renderZoomDependency = isElementRender ? 1 : zoom;
|
|
1718
|
+
const renderRotationDependency = isElementRender ? 0 : rotation;
|
|
1602
1719
|
const textMarkupTools = /* @__PURE__ */ new Set([
|
|
1603
1720
|
"highlight",
|
|
1604
1721
|
"underline",
|
|
@@ -1628,6 +1745,10 @@ var PageRenderer = ({
|
|
|
1628
1745
|
},
|
|
1629
1746
|
[]
|
|
1630
1747
|
);
|
|
1748
|
+
(0, import_react4.useEffect)(() => {
|
|
1749
|
+
if (!isElementRender) return;
|
|
1750
|
+
setPageSize(null);
|
|
1751
|
+
}, [isElementRender, pageIndex]);
|
|
1631
1752
|
(0, import_react4.useEffect)(() => {
|
|
1632
1753
|
let active = true;
|
|
1633
1754
|
const loadSize = async () => {
|
|
@@ -1646,12 +1767,13 @@ var PageRenderer = ({
|
|
|
1646
1767
|
};
|
|
1647
1768
|
}, [engine, pageIndex]);
|
|
1648
1769
|
const fitScale = (0, import_react4.useMemo)(() => {
|
|
1770
|
+
if (isElementRender && isMobileElementViewport) return 1;
|
|
1649
1771
|
if (!availableWidth || !pageSize?.width) return 1;
|
|
1650
1772
|
const targetWidth = Math.max(0, availableWidth - 48);
|
|
1651
1773
|
if (!targetWidth) return 1;
|
|
1652
1774
|
const rawScale = Math.min(1, targetWidth / pageSize.width);
|
|
1653
1775
|
return Math.round(rawScale * SCALE_PRECISION) / SCALE_PRECISION;
|
|
1654
|
-
}, [availableWidth, pageSize]);
|
|
1776
|
+
}, [isElementRender, isMobileElementViewport, availableWidth, pageSize]);
|
|
1655
1777
|
const displaySize = (0, import_react4.useMemo)(() => {
|
|
1656
1778
|
if (!pageSize) return null;
|
|
1657
1779
|
const scale = zoom * fitScale;
|
|
@@ -1687,6 +1809,15 @@ var PageRenderer = ({
|
|
|
1687
1809
|
canvasRef.current.style.height = `${displaySize.height}px`;
|
|
1688
1810
|
}
|
|
1689
1811
|
await engine.renderPage(pageIndex, renderTarget, canvasRenderScale);
|
|
1812
|
+
const measuredSize = await engine.getPageDimensions(pageIndex);
|
|
1813
|
+
if (measuredSize.width > 0 && measuredSize.height > 0 && active) {
|
|
1814
|
+
setPageSize((prev) => {
|
|
1815
|
+
if (prev && prev.width === measuredSize.width && prev.height === measuredSize.height) {
|
|
1816
|
+
return prev;
|
|
1817
|
+
}
|
|
1818
|
+
return measuredSize;
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1690
1821
|
if (!isElementRender && !pageSize && canvasRef.current) {
|
|
1691
1822
|
const denom = canvasRenderScale * Math.max(zoom, 0.01);
|
|
1692
1823
|
if (denom > 0) {
|
|
@@ -1729,13 +1860,24 @@ var PageRenderer = ({
|
|
|
1729
1860
|
}, [
|
|
1730
1861
|
engine,
|
|
1731
1862
|
pageIndex,
|
|
1732
|
-
zoom,
|
|
1733
|
-
rotation,
|
|
1734
1863
|
isElementRender,
|
|
1864
|
+
availableWidth,
|
|
1735
1865
|
fitScale,
|
|
1736
1866
|
displaySize,
|
|
1737
|
-
pageSize
|
|
1867
|
+
pageSize,
|
|
1868
|
+
renderZoomDependency,
|
|
1869
|
+
renderRotationDependency
|
|
1738
1870
|
]);
|
|
1871
|
+
(0, import_react4.useEffect)(() => {
|
|
1872
|
+
if (!isElementRender || pageSize) return;
|
|
1873
|
+
const target = htmlLayerRef.current;
|
|
1874
|
+
if (!target) return;
|
|
1875
|
+
const measuredWidth = target.clientWidth || target.scrollWidth || 0;
|
|
1876
|
+
const measuredHeight = target.clientHeight || target.scrollHeight || 0;
|
|
1877
|
+
if (measuredWidth > 0 && measuredHeight > 0) {
|
|
1878
|
+
setPageSize({ width: measuredWidth, height: measuredHeight });
|
|
1879
|
+
}
|
|
1880
|
+
}, [isElementRender, pageSize, textLayerVersion]);
|
|
1739
1881
|
(0, import_react4.useEffect)(() => {
|
|
1740
1882
|
if (isElementRender) return;
|
|
1741
1883
|
const layer = textLayerRef.current;
|
|
@@ -1797,8 +1939,12 @@ var PageRenderer = ({
|
|
|
1797
1939
|
activeSearchIndex,
|
|
1798
1940
|
textLayerVersion
|
|
1799
1941
|
]);
|
|
1800
|
-
const
|
|
1801
|
-
const
|
|
1942
|
+
const getTouchPoint = (event) => {
|
|
1943
|
+
const touch = event.touches[0] ?? event.changedTouches[0];
|
|
1944
|
+
if (!touch) return null;
|
|
1945
|
+
return { x: touch.clientX, y: touch.clientY };
|
|
1946
|
+
};
|
|
1947
|
+
const handlePointerDown = (clientX, clientY, target) => {
|
|
1802
1948
|
const clickedInsideAnnotation = Boolean(
|
|
1803
1949
|
target?.closest("[data-papyrus-annotation-id]")
|
|
1804
1950
|
);
|
|
@@ -1812,8 +1958,8 @@ var PageRenderer = ({
|
|
|
1812
1958
|
if (activeTool === "ink") {
|
|
1813
1959
|
const rect2 = containerRef.current?.getBoundingClientRect();
|
|
1814
1960
|
if (!rect2) return;
|
|
1815
|
-
const x2 = (
|
|
1816
|
-
const y2 = (
|
|
1961
|
+
const x2 = (clientX - rect2.left) / rect2.width;
|
|
1962
|
+
const y2 = (clientY - rect2.top) / rect2.height;
|
|
1817
1963
|
setIsInkDrawing(true);
|
|
1818
1964
|
setInkPoints([{ x: x2, y: y2 }]);
|
|
1819
1965
|
return;
|
|
@@ -1822,25 +1968,25 @@ var PageRenderer = ({
|
|
|
1822
1968
|
const rect = containerRef.current?.getBoundingClientRect();
|
|
1823
1969
|
if (!rect) return;
|
|
1824
1970
|
setIsDragging(true);
|
|
1825
|
-
const x =
|
|
1826
|
-
const y =
|
|
1971
|
+
const x = clientX - rect.left;
|
|
1972
|
+
const y = clientY - rect.top;
|
|
1827
1973
|
setStartPos({ x, y });
|
|
1828
1974
|
setCurrentRect({ x, y, w: 0, h: 0 });
|
|
1829
1975
|
};
|
|
1830
|
-
const
|
|
1976
|
+
const handlePointerMove = (clientX, clientY) => {
|
|
1831
1977
|
if (isInkDrawing) {
|
|
1832
1978
|
const rect2 = containerRef.current?.getBoundingClientRect();
|
|
1833
1979
|
if (!rect2) return;
|
|
1834
|
-
const x = (
|
|
1835
|
-
const y = (
|
|
1980
|
+
const x = (clientX - rect2.left) / rect2.width;
|
|
1981
|
+
const y = (clientY - rect2.top) / rect2.height;
|
|
1836
1982
|
setInkPoints((prev) => [...prev, { x, y }]);
|
|
1837
1983
|
return;
|
|
1838
1984
|
}
|
|
1839
1985
|
if (!isDragging) return;
|
|
1840
1986
|
const rect = containerRef.current?.getBoundingClientRect();
|
|
1841
1987
|
if (!rect) return;
|
|
1842
|
-
const currentX =
|
|
1843
|
-
const currentY =
|
|
1988
|
+
const currentX = clientX - rect.left;
|
|
1989
|
+
const currentY = clientY - rect.top;
|
|
1844
1990
|
setCurrentRect({
|
|
1845
1991
|
x: Math.min(startPos.x, currentX),
|
|
1846
1992
|
y: Math.min(startPos.y, currentY),
|
|
@@ -1848,7 +1994,7 @@ var PageRenderer = ({
|
|
|
1848
1994
|
h: Math.abs(currentY - startPos.y)
|
|
1849
1995
|
});
|
|
1850
1996
|
};
|
|
1851
|
-
const
|
|
1997
|
+
const handlePointerUp = () => {
|
|
1852
1998
|
if (isInkDrawing) {
|
|
1853
1999
|
setIsInkDrawing(false);
|
|
1854
2000
|
if (inkPoints.length > 1) {
|
|
@@ -2013,6 +2159,37 @@ var PageRenderer = ({
|
|
|
2013
2159
|
}
|
|
2014
2160
|
}
|
|
2015
2161
|
};
|
|
2162
|
+
const handleMouseDown = (e) => {
|
|
2163
|
+
handlePointerDown(e.clientX, e.clientY, e.target);
|
|
2164
|
+
};
|
|
2165
|
+
const handleMouseMove = (e) => {
|
|
2166
|
+
handlePointerMove(e.clientX, e.clientY);
|
|
2167
|
+
};
|
|
2168
|
+
const handleMouseUp = () => {
|
|
2169
|
+
handlePointerUp();
|
|
2170
|
+
};
|
|
2171
|
+
const handleTouchStart = (event) => {
|
|
2172
|
+
if (event.touches.length > 1) return;
|
|
2173
|
+
const point = getTouchPoint(event);
|
|
2174
|
+
if (!point) return;
|
|
2175
|
+
handlePointerDown(point.x, point.y, event.target);
|
|
2176
|
+
if ((activeTool === "ink" || !canSelectText) && event.cancelable) {
|
|
2177
|
+
event.preventDefault();
|
|
2178
|
+
}
|
|
2179
|
+
};
|
|
2180
|
+
const handleTouchMove = (event) => {
|
|
2181
|
+
if (event.touches.length > 1) return;
|
|
2182
|
+
const point = getTouchPoint(event);
|
|
2183
|
+
if (!point) return;
|
|
2184
|
+
handlePointerMove(point.x, point.y);
|
|
2185
|
+
if ((isInkDrawing || isDragging) && event.cancelable) {
|
|
2186
|
+
event.preventDefault();
|
|
2187
|
+
}
|
|
2188
|
+
};
|
|
2189
|
+
const handleTouchEnd = (event) => {
|
|
2190
|
+
if (event.touches.length > 0) return;
|
|
2191
|
+
handlePointerUp();
|
|
2192
|
+
};
|
|
2016
2193
|
const getPageFilter = () => {
|
|
2017
2194
|
switch (pageTheme) {
|
|
2018
2195
|
case "sepia":
|
|
@@ -2025,15 +2202,35 @@ var PageRenderer = ({
|
|
|
2025
2202
|
return "none";
|
|
2026
2203
|
}
|
|
2027
2204
|
};
|
|
2205
|
+
const elementScale = zoom * fitScale;
|
|
2206
|
+
const elementBaseWidth = isElementRender ? isMobileElementViewport && availableWidth != null ? Math.max(260, Math.round(availableWidth)) : pageSize?.width ?? 640 : pageSize?.width ?? 640;
|
|
2207
|
+
const elementBaseHeight = pageSize?.height ?? (isElementRender ? 700 : 900);
|
|
2208
|
+
const elementContainerStyle = isElementRender ? {
|
|
2209
|
+
width: `${Math.max(1, Math.round(elementBaseWidth * elementScale))}px`,
|
|
2210
|
+
height: `${Math.max(
|
|
2211
|
+
1,
|
|
2212
|
+
Math.round(elementBaseHeight * elementScale)
|
|
2213
|
+
)}px`
|
|
2214
|
+
} : void 0;
|
|
2028
2215
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
2029
2216
|
"div",
|
|
2030
2217
|
{
|
|
2031
2218
|
ref: containerRef,
|
|
2032
|
-
className: `relative inline-block shadow-2xl bg-white mb-10 ${canSelectText ? "" : "no-select cursor-crosshair"}`,
|
|
2033
|
-
style: {
|
|
2219
|
+
className: `relative inline-block shadow-2xl bg-white ${isMobileElementViewport ? "mb-0" : "mb-10"} ${canSelectText ? "" : "no-select cursor-crosshair"}`,
|
|
2220
|
+
style: {
|
|
2221
|
+
scrollMarginTop: "20px",
|
|
2222
|
+
minHeight: "100px",
|
|
2223
|
+
overflow: "hidden",
|
|
2224
|
+
...elementContainerStyle,
|
|
2225
|
+
touchAction: activeTool === "ink" || activeTool === "text" || activeTool === "comment" ? "none" : "auto"
|
|
2226
|
+
},
|
|
2034
2227
|
onMouseDown: handleMouseDown,
|
|
2035
2228
|
onMouseMove: handleMouseMove,
|
|
2036
2229
|
onMouseUp: handleMouseUp,
|
|
2230
|
+
onTouchStart: handleTouchStart,
|
|
2231
|
+
onTouchMove: handleTouchMove,
|
|
2232
|
+
onTouchEnd: handleTouchEnd,
|
|
2233
|
+
onTouchCancel: handleTouchEnd,
|
|
2037
2234
|
children: [
|
|
2038
2235
|
loading && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute inset-0 bg-gray-50 flex items-center justify-center z-10 animate-pulse", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-[10px] font-black text-gray-400 uppercase tracking-widest", children: "Sincronizando..." }) }),
|
|
2039
2236
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -2054,7 +2251,11 @@ var PageRenderer = ({
|
|
|
2054
2251
|
className: "block",
|
|
2055
2252
|
style: {
|
|
2056
2253
|
filter: getPageFilter(),
|
|
2057
|
-
display: isElementRender ? "block" : "none"
|
|
2254
|
+
display: isElementRender ? "block" : "none",
|
|
2255
|
+
width: `${elementBaseWidth}px`,
|
|
2256
|
+
height: `${elementBaseHeight}px`,
|
|
2257
|
+
transform: `scale(${elementScale})`,
|
|
2258
|
+
transformOrigin: "top left"
|
|
2058
2259
|
}
|
|
2059
2260
|
}
|
|
2060
2261
|
),
|
|
@@ -2504,14 +2705,26 @@ var PageRenderer_default = PageRenderer;
|
|
|
2504
2705
|
|
|
2505
2706
|
// components/Viewer.tsx
|
|
2506
2707
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2708
|
+
var withAlpha3 = (hex, alpha) => {
|
|
2709
|
+
const normalized = hex.replace("#", "").trim();
|
|
2710
|
+
const value = normalized.length === 3 ? normalized.split("").map((c) => c + c).join("") : normalized;
|
|
2711
|
+
if (value.length !== 6) return hex;
|
|
2712
|
+
const r = parseInt(value.slice(0, 2), 16);
|
|
2713
|
+
const g = parseInt(value.slice(2, 4), 16);
|
|
2714
|
+
const b = parseInt(value.slice(4, 6), 16);
|
|
2715
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
2716
|
+
};
|
|
2507
2717
|
var BASE_OVERSCAN = 6;
|
|
2508
2718
|
var MIN_ZOOM = 0.2;
|
|
2509
2719
|
var MAX_ZOOM = 5;
|
|
2510
2720
|
var WIDTH_SNAP_PX = 4;
|
|
2511
2721
|
var WIDTH_HYSTERESIS_PX = 6;
|
|
2722
|
+
var HEIGHT_SNAP_PX = 4;
|
|
2723
|
+
var HEIGHT_HYSTERESIS_PX = 6;
|
|
2512
2724
|
var MOBILE_HEADER_HIDE_DELTA_PX = 28;
|
|
2513
2725
|
var MOBILE_HEADER_SHOW_DELTA_PX = 16;
|
|
2514
2726
|
var MOBILE_HEADER_TOP_RESET_PX = 12;
|
|
2727
|
+
var MOBILE_LANDSCAPE_MAX_HEIGHT_PX2 = 500;
|
|
2515
2728
|
var Viewer = ({ engine, style }) => {
|
|
2516
2729
|
const viewerState = (0, import_core5.useViewerStore)();
|
|
2517
2730
|
const {
|
|
@@ -2522,6 +2735,7 @@ var Viewer = ({ engine, style }) => {
|
|
|
2522
2735
|
uiTheme,
|
|
2523
2736
|
scrollToPageSignal,
|
|
2524
2737
|
setDocumentState,
|
|
2738
|
+
triggerScrollToPage,
|
|
2525
2739
|
accentColor,
|
|
2526
2740
|
annotationColor,
|
|
2527
2741
|
setAnnotationColor,
|
|
@@ -2529,7 +2743,10 @@ var Viewer = ({ engine, style }) => {
|
|
|
2529
2743
|
} = viewerState;
|
|
2530
2744
|
const mobileTopbarVisible = viewerState.mobileTopbarVisible ?? true;
|
|
2531
2745
|
const isDark = uiTheme === "dark";
|
|
2746
|
+
const renderTargetType = engine.getRenderTargetType?.() ?? "canvas";
|
|
2747
|
+
const isSingleViewportMode = renderTargetType === "element" || renderTargetType === "webview";
|
|
2532
2748
|
const viewerRef = (0, import_react5.useRef)(null);
|
|
2749
|
+
const singleNavInFlightRef = (0, import_react5.useRef)(false);
|
|
2533
2750
|
const colorPickerRef = (0, import_react5.useRef)(null);
|
|
2534
2751
|
const pageRefs = (0, import_react5.useRef)([]);
|
|
2535
2752
|
const intersectionRatiosRef = (0, import_react5.useRef)({});
|
|
@@ -2537,6 +2754,7 @@ var Viewer = ({ engine, style }) => {
|
|
|
2537
2754
|
const jumpRef = (0, import_react5.useRef)(false);
|
|
2538
2755
|
const jumpTimerRef = (0, import_react5.useRef)(null);
|
|
2539
2756
|
const lastWidthRef = (0, import_react5.useRef)(null);
|
|
2757
|
+
const lastHeightRef = (0, import_react5.useRef)(null);
|
|
2540
2758
|
const lastScrollTopRef = (0, import_react5.useRef)(0);
|
|
2541
2759
|
const scrollDownAccumulatorRef = (0, import_react5.useRef)(0);
|
|
2542
2760
|
const scrollUpAccumulatorRef = (0, import_react5.useRef)(0);
|
|
@@ -2550,12 +2768,16 @@ var Viewer = ({ engine, style }) => {
|
|
|
2550
2768
|
rafId: null
|
|
2551
2769
|
});
|
|
2552
2770
|
const [availableWidth, setAvailableWidth] = (0, import_react5.useState)(null);
|
|
2771
|
+
const [availableHeight, setAvailableHeight] = (0, import_react5.useState)(null);
|
|
2772
|
+
const [viewerBounds, setViewerBounds] = (0, import_react5.useState)(null);
|
|
2553
2773
|
const [basePageSize, setBasePageSize] = (0, import_react5.useState)(null);
|
|
2554
2774
|
const [pageSizes, setPageSizes] = (0, import_react5.useState)({});
|
|
2555
2775
|
const [colorPickerOpen, setColorPickerOpen] = (0, import_react5.useState)(false);
|
|
2556
|
-
const
|
|
2557
|
-
const
|
|
2558
|
-
const
|
|
2776
|
+
const isLandscape = availableWidth !== null && availableHeight !== null && availableWidth > availableHeight;
|
|
2777
|
+
const isLandscapeShort = isLandscape && availableHeight !== null && availableHeight <= MOBILE_LANDSCAPE_MAX_HEIGHT_PX2;
|
|
2778
|
+
const isCompact = availableWidth !== null && (availableWidth < 820 || isLandscapeShort);
|
|
2779
|
+
const isMobileViewport = availableWidth !== null && (availableWidth < 640 || isLandscapeShort);
|
|
2780
|
+
const paddingY = isSingleViewportMode && isMobileViewport ? "py-0" : isCompact ? "py-10" : "py-16";
|
|
2559
2781
|
const toolDockPosition = isCompact ? "bottom-4" : "bottom-8";
|
|
2560
2782
|
const colorPalette = [
|
|
2561
2783
|
"#fbbf24",
|
|
@@ -2567,6 +2789,45 @@ var Viewer = ({ engine, style }) => {
|
|
|
2567
2789
|
"#8b5cf6",
|
|
2568
2790
|
"#111827"
|
|
2569
2791
|
];
|
|
2792
|
+
const destinationNavEngine = engine;
|
|
2793
|
+
const canUseDestinationNavigation = isSingleViewportMode && typeof destinationNavEngine.goToAdjacentDestination === "function";
|
|
2794
|
+
const destinationNavigationState = isSingleViewportMode && typeof destinationNavEngine.getDestinationNavigationState === "function" ? destinationNavEngine.getDestinationNavigationState() : null;
|
|
2795
|
+
const canGoPrev = destinationNavigationState?.hasPrev ?? currentPage > 1;
|
|
2796
|
+
const canGoNext = destinationNavigationState?.hasNext ?? currentPage < pageCount;
|
|
2797
|
+
const viewerOverflowClass = isSingleViewportMode ? "overflow-hidden" : "overflow-y-scroll overflow-x-hidden";
|
|
2798
|
+
const navigateBy = (delta) => {
|
|
2799
|
+
if (pageCount <= 0) return;
|
|
2800
|
+
if (canUseDestinationNavigation) {
|
|
2801
|
+
if (singleNavInFlightRef.current) return;
|
|
2802
|
+
singleNavInFlightRef.current = true;
|
|
2803
|
+
void (async () => {
|
|
2804
|
+
try {
|
|
2805
|
+
const resolved = await destinationNavEngine.goToAdjacentDestination(
|
|
2806
|
+
delta
|
|
2807
|
+
);
|
|
2808
|
+
if (resolved == null) return;
|
|
2809
|
+
setDocumentState({
|
|
2810
|
+
currentPage: resolved + 1,
|
|
2811
|
+
scrollToPageSignal: null
|
|
2812
|
+
});
|
|
2813
|
+
} finally {
|
|
2814
|
+
singleNavInFlightRef.current = false;
|
|
2815
|
+
}
|
|
2816
|
+
})();
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
const enginePage = Number(engine.getCurrentPage?.());
|
|
2820
|
+
const normalizedEnginePage = Number.isFinite(enginePage) && enginePage >= 1 ? Math.floor(enginePage) : null;
|
|
2821
|
+
const basePage = normalizedEnginePage != null && Math.abs(normalizedEnginePage - currentPage) <= 1 ? normalizedEnginePage : currentPage;
|
|
2822
|
+
const clampedPage = Math.max(1, Math.min(pageCount, basePage + delta));
|
|
2823
|
+
if (clampedPage === basePage) return;
|
|
2824
|
+
engine.goToPage(clampedPage);
|
|
2825
|
+
if (isSingleViewportMode) {
|
|
2826
|
+
setDocumentState({ currentPage: clampedPage, scrollToPageSignal: null });
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
triggerScrollToPage(clampedPage - 1);
|
|
2830
|
+
};
|
|
2570
2831
|
const setMobileTopbarVisibility = (visible) => {
|
|
2571
2832
|
if (mobileTopbarVisibleRef.current === visible) return;
|
|
2572
2833
|
mobileTopbarVisibleRef.current = visible;
|
|
@@ -2605,21 +2866,32 @@ var Viewer = ({ engine, style }) => {
|
|
|
2605
2866
|
const measurementTarget = viewerElement.parentElement ?? viewerElement;
|
|
2606
2867
|
let rafId = null;
|
|
2607
2868
|
const normalizeWidth = (rawWidth) => Math.max(0, Math.floor(rawWidth / WIDTH_SNAP_PX) * WIDTH_SNAP_PX);
|
|
2608
|
-
const
|
|
2869
|
+
const normalizeHeight = (rawHeight) => Math.max(0, Math.floor(rawHeight / HEIGHT_SNAP_PX) * HEIGHT_SNAP_PX);
|
|
2870
|
+
const updateSize = () => {
|
|
2609
2871
|
const rawWidth = measurementTarget.getBoundingClientRect?.().width ?? measurementTarget.clientWidth ?? measurementTarget.offsetWidth;
|
|
2872
|
+
const rawHeight = measurementTarget.getBoundingClientRect?.().height ?? measurementTarget.clientHeight ?? measurementTarget.offsetHeight;
|
|
2610
2873
|
const nextWidth = normalizeWidth(rawWidth);
|
|
2611
|
-
|
|
2874
|
+
const nextHeight = normalizeHeight(rawHeight);
|
|
2875
|
+
if (nextWidth <= 0 || nextHeight <= 0) return;
|
|
2612
2876
|
const previousWidth = lastWidthRef.current;
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2877
|
+
const previousHeight = lastHeightRef.current;
|
|
2878
|
+
const widthChanged = previousWidth == null || Math.abs(nextWidth - previousWidth) >= WIDTH_HYSTERESIS_PX;
|
|
2879
|
+
const heightChanged = previousHeight == null || Math.abs(nextHeight - previousHeight) >= HEIGHT_HYSTERESIS_PX;
|
|
2880
|
+
if (!widthChanged && !heightChanged) return;
|
|
2881
|
+
if (widthChanged) {
|
|
2882
|
+
lastWidthRef.current = nextWidth;
|
|
2883
|
+
setAvailableWidth(nextWidth);
|
|
2884
|
+
}
|
|
2885
|
+
if (heightChanged) {
|
|
2886
|
+
lastHeightRef.current = nextHeight;
|
|
2887
|
+
setAvailableHeight(nextHeight);
|
|
2888
|
+
}
|
|
2617
2889
|
};
|
|
2618
2890
|
const scheduleWidthUpdate = () => {
|
|
2619
2891
|
if (rafId != null) cancelAnimationFrame(rafId);
|
|
2620
2892
|
rafId = requestAnimationFrame(() => {
|
|
2621
2893
|
rafId = null;
|
|
2622
|
-
|
|
2894
|
+
updateSize();
|
|
2623
2895
|
});
|
|
2624
2896
|
};
|
|
2625
2897
|
scheduleWidthUpdate();
|
|
@@ -2642,10 +2914,48 @@ var Viewer = ({ engine, style }) => {
|
|
|
2642
2914
|
observer.disconnect();
|
|
2643
2915
|
};
|
|
2644
2916
|
}, []);
|
|
2917
|
+
(0, import_react5.useEffect)(() => {
|
|
2918
|
+
if (!isSingleViewportMode) return;
|
|
2919
|
+
const viewerElement = viewerRef.current;
|
|
2920
|
+
if (!viewerElement) return;
|
|
2921
|
+
let rafId = null;
|
|
2922
|
+
const updateBounds = () => {
|
|
2923
|
+
const rect = viewerElement.getBoundingClientRect();
|
|
2924
|
+
setViewerBounds({
|
|
2925
|
+
left: rect.left,
|
|
2926
|
+
width: rect.width,
|
|
2927
|
+
top: rect.top,
|
|
2928
|
+
height: rect.height
|
|
2929
|
+
});
|
|
2930
|
+
};
|
|
2931
|
+
const scheduleUpdate = () => {
|
|
2932
|
+
if (rafId != null) cancelAnimationFrame(rafId);
|
|
2933
|
+
rafId = requestAnimationFrame(() => {
|
|
2934
|
+
rafId = null;
|
|
2935
|
+
updateBounds();
|
|
2936
|
+
});
|
|
2937
|
+
};
|
|
2938
|
+
updateBounds();
|
|
2939
|
+
viewerElement.addEventListener("scroll", scheduleUpdate, { passive: true });
|
|
2940
|
+
window.addEventListener("resize", scheduleUpdate);
|
|
2941
|
+
window.addEventListener("scroll", scheduleUpdate, { passive: true });
|
|
2942
|
+
let observer = null;
|
|
2943
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
2944
|
+
observer = new ResizeObserver(() => scheduleUpdate());
|
|
2945
|
+
observer.observe(viewerElement);
|
|
2946
|
+
}
|
|
2947
|
+
return () => {
|
|
2948
|
+
if (rafId != null) cancelAnimationFrame(rafId);
|
|
2949
|
+
viewerElement.removeEventListener("scroll", scheduleUpdate);
|
|
2950
|
+
window.removeEventListener("resize", scheduleUpdate);
|
|
2951
|
+
window.removeEventListener("scroll", scheduleUpdate);
|
|
2952
|
+
observer?.disconnect();
|
|
2953
|
+
};
|
|
2954
|
+
}, [isSingleViewportMode]);
|
|
2645
2955
|
(0, import_react5.useEffect)(() => {
|
|
2646
2956
|
const root = viewerRef.current;
|
|
2647
2957
|
if (!root) return;
|
|
2648
|
-
if (!isMobileViewport) {
|
|
2958
|
+
if (isSingleViewportMode || !isMobileViewport) {
|
|
2649
2959
|
lastScrollTopRef.current = root.scrollTop;
|
|
2650
2960
|
scrollDownAccumulatorRef.current = 0;
|
|
2651
2961
|
scrollUpAccumulatorRef.current = 0;
|
|
@@ -2689,7 +2999,7 @@ var Viewer = ({ engine, style }) => {
|
|
|
2689
2999
|
return () => {
|
|
2690
3000
|
root.removeEventListener("scroll", handleScroll);
|
|
2691
3001
|
};
|
|
2692
|
-
}, [isMobileViewport, setDocumentState]);
|
|
3002
|
+
}, [isSingleViewportMode, isMobileViewport, setDocumentState]);
|
|
2693
3003
|
(0, import_react5.useEffect)(() => {
|
|
2694
3004
|
const previousPage = previousCurrentPageRef.current;
|
|
2695
3005
|
previousCurrentPageRef.current = currentPage;
|
|
@@ -2718,6 +3028,19 @@ var Viewer = ({ engine, style }) => {
|
|
|
2718
3028
|
}, [engine, pageCount]);
|
|
2719
3029
|
(0, import_react5.useEffect)(() => {
|
|
2720
3030
|
if (scrollToPageSignal == null) return;
|
|
3031
|
+
if (isSingleViewportMode) {
|
|
3032
|
+
const nextPageIndex = Math.max(
|
|
3033
|
+
0,
|
|
3034
|
+
Math.min(Math.max(pageCount - 1, 0), scrollToPageSignal)
|
|
3035
|
+
);
|
|
3036
|
+
const root2 = viewerRef.current;
|
|
3037
|
+
if (root2) root2.scrollTop = 0;
|
|
3038
|
+
setDocumentState({
|
|
3039
|
+
currentPage: nextPageIndex + 1,
|
|
3040
|
+
scrollToPageSignal: null
|
|
3041
|
+
});
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
2721
3044
|
const root = viewerRef.current;
|
|
2722
3045
|
const target = pageRefs.current[scrollToPageSignal];
|
|
2723
3046
|
if (root) {
|
|
@@ -2754,16 +3077,57 @@ var Viewer = ({ engine, style }) => {
|
|
|
2754
3077
|
setDocumentState({ scrollToPageSignal: null });
|
|
2755
3078
|
}, [
|
|
2756
3079
|
scrollToPageSignal,
|
|
3080
|
+
isSingleViewportMode,
|
|
2757
3081
|
setDocumentState,
|
|
2758
3082
|
basePageSize,
|
|
2759
3083
|
availableWidth,
|
|
2760
3084
|
zoom,
|
|
2761
3085
|
pageCount
|
|
2762
3086
|
]);
|
|
3087
|
+
(0, import_react5.useEffect)(() => {
|
|
3088
|
+
if (!isSingleViewportMode) return;
|
|
3089
|
+
const root = viewerRef.current;
|
|
3090
|
+
if (!root) return;
|
|
3091
|
+
root.scrollTop = 0;
|
|
3092
|
+
}, [isSingleViewportMode, currentPage]);
|
|
2763
3093
|
(0, import_react5.useEffect)(() => {
|
|
2764
3094
|
setPageSizes({});
|
|
2765
3095
|
}, [zoom]);
|
|
2766
3096
|
(0, import_react5.useEffect)(() => {
|
|
3097
|
+
if (pageCount <= 1) return;
|
|
3098
|
+
const handleKeyNavigation = (event) => {
|
|
3099
|
+
if (event.defaultPrevented) return;
|
|
3100
|
+
if (event.altKey || event.ctrlKey || event.metaKey) return;
|
|
3101
|
+
const target = event.target;
|
|
3102
|
+
if (target) {
|
|
3103
|
+
const tag = target.tagName;
|
|
3104
|
+
const isEditable = tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable || target.getAttribute("contenteditable") === "true";
|
|
3105
|
+
if (isEditable) return;
|
|
3106
|
+
}
|
|
3107
|
+
if (event.key === "ArrowLeft") {
|
|
3108
|
+
event.preventDefault();
|
|
3109
|
+
if (!canGoPrev) return;
|
|
3110
|
+
navigateBy(-1);
|
|
3111
|
+
return;
|
|
3112
|
+
}
|
|
3113
|
+
if (event.key === "ArrowRight") {
|
|
3114
|
+
event.preventDefault();
|
|
3115
|
+
if (!canGoNext) return;
|
|
3116
|
+
navigateBy(1);
|
|
3117
|
+
}
|
|
3118
|
+
};
|
|
3119
|
+
window.addEventListener("keydown", handleKeyNavigation);
|
|
3120
|
+
return () => window.removeEventListener("keydown", handleKeyNavigation);
|
|
3121
|
+
}, [
|
|
3122
|
+
currentPage,
|
|
3123
|
+
pageCount,
|
|
3124
|
+
triggerScrollToPage,
|
|
3125
|
+
engine,
|
|
3126
|
+
canGoPrev,
|
|
3127
|
+
canGoNext
|
|
3128
|
+
]);
|
|
3129
|
+
(0, import_react5.useEffect)(() => {
|
|
3130
|
+
if (isSingleViewportMode) return;
|
|
2767
3131
|
const root = viewerRef.current;
|
|
2768
3132
|
if (!root) return;
|
|
2769
3133
|
const flushCurrentPage = () => {
|
|
@@ -2809,11 +3173,15 @@ var Viewer = ({ engine, style }) => {
|
|
|
2809
3173
|
pageElements.forEach((el) => observer.unobserve(el));
|
|
2810
3174
|
observer.disconnect();
|
|
2811
3175
|
};
|
|
2812
|
-
}, [pageCount, setDocumentState, currentPage]);
|
|
3176
|
+
}, [pageCount, setDocumentState, currentPage, isSingleViewportMode]);
|
|
3177
|
+
const safeCurrentPageIndex = Math.max(
|
|
3178
|
+
0,
|
|
3179
|
+
Math.min(Math.max(pageCount - 1, 0), currentPage - 1)
|
|
3180
|
+
);
|
|
2813
3181
|
const virtualOverscan = zoom > 1.35 ? 4 : BASE_OVERSCAN;
|
|
2814
|
-
const virtualAnchor =
|
|
2815
|
-
const virtualStart = Math.max(0, virtualAnchor - virtualOverscan);
|
|
2816
|
-
const virtualEnd = Math.min(pageCount - 1, virtualAnchor + virtualOverscan);
|
|
3182
|
+
const virtualAnchor = safeCurrentPageIndex;
|
|
3183
|
+
const virtualStart = isSingleViewportMode ? safeCurrentPageIndex : Math.max(0, virtualAnchor - virtualOverscan);
|
|
3184
|
+
const virtualEnd = isSingleViewportMode ? safeCurrentPageIndex : Math.min(pageCount - 1, virtualAnchor + virtualOverscan);
|
|
2817
3185
|
const fallbackSize = (0, import_react5.useMemo)(() => {
|
|
2818
3186
|
if (basePageSize && availableWidth) {
|
|
2819
3187
|
const fitScale = Math.min(
|
|
@@ -2837,7 +3205,17 @@ var Viewer = ({ engine, style }) => {
|
|
|
2837
3205
|
return availableWidth ? Math.max(680, availableWidth * 1.3) : 1100;
|
|
2838
3206
|
return Math.round(heights.reduce((sum, h) => sum + h, 0) / heights.length);
|
|
2839
3207
|
}, [pageSizes, availableWidth]);
|
|
2840
|
-
const pages = Array.from({ length: pageCount }).map((_, i) => i);
|
|
3208
|
+
const pages = isSingleViewportMode ? pageCount > 0 ? [safeCurrentPageIndex] : [] : Array.from({ length: pageCount }).map((_, i) => i);
|
|
3209
|
+
const viewerStyle = (0, import_react5.useMemo)(
|
|
3210
|
+
() => isSingleViewportMode ? {
|
|
3211
|
+
...style ?? {},
|
|
3212
|
+
overflow: "hidden",
|
|
3213
|
+
overflowY: "hidden",
|
|
3214
|
+
overflowX: "hidden",
|
|
3215
|
+
overscrollBehavior: "none"
|
|
3216
|
+
} : style ?? {},
|
|
3217
|
+
[isSingleViewportMode, style]
|
|
3218
|
+
);
|
|
2841
3219
|
const handlePageMeasured = (pageIndex, size) => {
|
|
2842
3220
|
setPageSizes((prev) => {
|
|
2843
3221
|
const current = prev[pageIndex];
|
|
@@ -2930,8 +3308,8 @@ var Viewer = ({ engine, style }) => {
|
|
|
2930
3308
|
onTouchMove: handleTouchMove,
|
|
2931
3309
|
onTouchEnd: handleTouchEnd,
|
|
2932
3310
|
onTouchCancel: handleTouchEnd,
|
|
2933
|
-
className: `papyrus-viewer papyrus-theme min-w-0 w-full flex-1
|
|
2934
|
-
style,
|
|
3311
|
+
className: `papyrus-viewer papyrus-theme min-h-0 min-w-0 w-full flex-1 ${viewerOverflowClass} flex flex-col items-center ${paddingY} relative custom-scrollbar scroll-smooth ${isDark ? "bg-[#121212]" : "bg-[#e9ecef]"}`,
|
|
3312
|
+
style: viewerStyle,
|
|
2935
3313
|
children: [
|
|
2936
3314
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex flex-col items-center gap-6 w-full min-w-0", children: pages.map((idx) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2937
3315
|
"div",
|
|
@@ -2940,13 +3318,14 @@ var Viewer = ({ engine, style }) => {
|
|
|
2940
3318
|
pageRefs.current[idx] = element;
|
|
2941
3319
|
},
|
|
2942
3320
|
"data-page-index": idx,
|
|
2943
|
-
className:
|
|
3321
|
+
className: `page-container ${isSingleViewportMode ? "relative" : ""}`,
|
|
2944
3322
|
children: idx >= virtualStart && idx <= virtualEnd ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2945
3323
|
PageRenderer_default,
|
|
2946
3324
|
{
|
|
2947
3325
|
engine,
|
|
2948
3326
|
pageIndex: idx,
|
|
2949
3327
|
availableWidth: availableWidth ?? void 0,
|
|
3328
|
+
availableHeight: availableHeight ?? void 0,
|
|
2950
3329
|
onMeasuredSize: handlePageMeasured
|
|
2951
3330
|
}
|
|
2952
3331
|
) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
@@ -2960,8 +3339,94 @@ var Viewer = ({ engine, style }) => {
|
|
|
2960
3339
|
}
|
|
2961
3340
|
)
|
|
2962
3341
|
},
|
|
2963
|
-
idx
|
|
3342
|
+
isSingleViewportMode ? "single-viewport" : idx
|
|
2964
3343
|
)) }),
|
|
3344
|
+
isSingleViewportMode && pageCount > 1 && viewerBounds && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
3345
|
+
"div",
|
|
3346
|
+
{
|
|
3347
|
+
className: "pointer-events-none fixed z-[75] flex items-center justify-between px-1.5 sm:px-2.5",
|
|
3348
|
+
style: {
|
|
3349
|
+
left: viewerBounds.left,
|
|
3350
|
+
width: viewerBounds.width,
|
|
3351
|
+
top: viewerBounds.top + viewerBounds.height / 2,
|
|
3352
|
+
transform: "translateY(-50%)"
|
|
3353
|
+
},
|
|
3354
|
+
children: [
|
|
3355
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3356
|
+
"button",
|
|
3357
|
+
{
|
|
3358
|
+
onClick: () => navigateBy(-1),
|
|
3359
|
+
disabled: !canGoPrev,
|
|
3360
|
+
className: `pointer-events-auto h-12 w-9 sm:h-14 sm:w-10 rounded-lg border backdrop-blur-md transition-all ${!canGoPrev ? "opacity-40 cursor-not-allowed" : "hover:scale-[1.03] active:scale-95"} ${isDark ? "bg-[#111827]/85 text-gray-100" : "bg-white/90 text-gray-700"}`,
|
|
3361
|
+
style: {
|
|
3362
|
+
borderColor: withAlpha3(accentColor, isDark ? 0.45 : 0.3),
|
|
3363
|
+
color: !canGoPrev ? void 0 : accentColor,
|
|
3364
|
+
boxShadow: `0 10px 24px ${withAlpha3(
|
|
3365
|
+
accentColor,
|
|
3366
|
+
isDark ? 0.18 : 0.12
|
|
3367
|
+
)}`
|
|
3368
|
+
},
|
|
3369
|
+
"aria-label": "Cap\xEDtulo anterior",
|
|
3370
|
+
title: "Cap\xEDtulo anterior",
|
|
3371
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3372
|
+
"svg",
|
|
3373
|
+
{
|
|
3374
|
+
className: "w-5 h-5 mx-auto",
|
|
3375
|
+
fill: "none",
|
|
3376
|
+
stroke: "currentColor",
|
|
3377
|
+
viewBox: "0 0 24 24",
|
|
3378
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3379
|
+
"path",
|
|
3380
|
+
{
|
|
3381
|
+
strokeLinecap: "round",
|
|
3382
|
+
strokeLinejoin: "round",
|
|
3383
|
+
strokeWidth: 2,
|
|
3384
|
+
d: "M15 19l-7-7 7-7"
|
|
3385
|
+
}
|
|
3386
|
+
)
|
|
3387
|
+
}
|
|
3388
|
+
)
|
|
3389
|
+
}
|
|
3390
|
+
),
|
|
3391
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3392
|
+
"button",
|
|
3393
|
+
{
|
|
3394
|
+
onClick: () => navigateBy(1),
|
|
3395
|
+
disabled: !canGoNext,
|
|
3396
|
+
className: `pointer-events-auto h-12 w-9 sm:h-14 sm:w-10 rounded-lg border backdrop-blur-md transition-all ${!canGoNext ? "opacity-40 cursor-not-allowed" : "hover:scale-[1.03] active:scale-95"} ${isDark ? "bg-[#111827]/85 text-gray-100" : "bg-white/90 text-gray-700"}`,
|
|
3397
|
+
style: {
|
|
3398
|
+
borderColor: withAlpha3(accentColor, isDark ? 0.45 : 0.3),
|
|
3399
|
+
color: !canGoNext ? void 0 : accentColor,
|
|
3400
|
+
boxShadow: `0 10px 24px ${withAlpha3(
|
|
3401
|
+
accentColor,
|
|
3402
|
+
isDark ? 0.18 : 0.12
|
|
3403
|
+
)}`
|
|
3404
|
+
},
|
|
3405
|
+
"aria-label": "Pr\xF3ximo cap\xEDtulo",
|
|
3406
|
+
title: "Pr\xF3ximo cap\xEDtulo",
|
|
3407
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3408
|
+
"svg",
|
|
3409
|
+
{
|
|
3410
|
+
className: "w-5 h-5 mx-auto",
|
|
3411
|
+
fill: "none",
|
|
3412
|
+
stroke: "currentColor",
|
|
3413
|
+
viewBox: "0 0 24 24",
|
|
3414
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3415
|
+
"path",
|
|
3416
|
+
{
|
|
3417
|
+
strokeLinecap: "round",
|
|
3418
|
+
strokeLinejoin: "round",
|
|
3419
|
+
strokeWidth: 2,
|
|
3420
|
+
d: "M9 5l7 7-7 7"
|
|
3421
|
+
}
|
|
3422
|
+
)
|
|
3423
|
+
}
|
|
3424
|
+
)
|
|
3425
|
+
}
|
|
3426
|
+
)
|
|
3427
|
+
]
|
|
3428
|
+
}
|
|
3429
|
+
),
|
|
2965
3430
|
toolDockOpen && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2966
3431
|
"div",
|
|
2967
3432
|
{
|