@ai-matrx/office 0.8.45 → 0.8.46
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/CHANGELOG.md +6 -0
- package/dist/pdf-model.cjs +232 -0
- package/dist/pdf-model.cjs.map +1 -0
- package/dist/pdf-model.d.cts +248 -0
- package/dist/pdf-model.d.ts +248 -0
- package/dist/pdf-model.js +209 -0
- package/dist/pdf-model.js.map +1 -0
- package/dist/pdf.cjs +1127 -0
- package/dist/pdf.cjs.map +1 -0
- package/dist/pdf.d.cts +255 -0
- package/dist/pdf.d.ts +255 -0
- package/dist/pdf.js +1133 -0
- package/dist/pdf.js.map +1 -0
- package/dist/univer-collab.cjs +63 -16
- package/dist/univer-collab.cjs.map +1 -1
- package/dist/univer-collab.d.cts +29 -9
- package/dist/univer-collab.d.ts +29 -9
- package/dist/univer-collab.js +63 -16
- package/dist/univer-collab.js.map +1 -1
- package/package.json +26 -6
package/dist/pdf.cjs
ADDED
|
@@ -0,0 +1,1127 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/pdf/index.ts
|
|
32
|
+
var pdf_exports = {};
|
|
33
|
+
__export(pdf_exports, {
|
|
34
|
+
HISTORY_LIMIT: () => HISTORY_LIMIT,
|
|
35
|
+
PDF_COMING_SOON: () => PDF_COMING_SOON,
|
|
36
|
+
PDF_SHORTCUTS: () => PDF_SHORTCUTS,
|
|
37
|
+
PDF_TOOLS: () => PDF_TOOLS,
|
|
38
|
+
PdfWorkspace: () => PdfWorkspace,
|
|
39
|
+
canRedo: () => canRedo,
|
|
40
|
+
canUndo: () => canUndo,
|
|
41
|
+
comingSoonIdOf: () => comingSoonIdOf,
|
|
42
|
+
compilePageOps: () => compilePageOps,
|
|
43
|
+
deletePages: () => deletePages,
|
|
44
|
+
formatKeys: () => formatKeys,
|
|
45
|
+
freshDecisions: () => freshDecisions,
|
|
46
|
+
isDirty: () => isDirty,
|
|
47
|
+
keptOrder: () => keptOrder,
|
|
48
|
+
matchShortcut: () => matchShortcut,
|
|
49
|
+
pdfTool: () => pdfTool,
|
|
50
|
+
pushStep: () => pushStep,
|
|
51
|
+
redo: () => redo,
|
|
52
|
+
redoLabel: () => redoLabel,
|
|
53
|
+
reorderPages: () => reorderPages,
|
|
54
|
+
restorePages: () => restorePages,
|
|
55
|
+
startHistory: () => startHistory,
|
|
56
|
+
turnPages: () => turnPages,
|
|
57
|
+
undo: () => undo,
|
|
58
|
+
undoLabel: () => undoLabel
|
|
59
|
+
});
|
|
60
|
+
module.exports = __toCommonJS(pdf_exports);
|
|
61
|
+
|
|
62
|
+
// src/pdf/PdfWorkspace.tsx
|
|
63
|
+
var import_react3 = require("react");
|
|
64
|
+
var import_cn = require("@ai-matrx/design-system/cn");
|
|
65
|
+
var import_controls4 = require("@ai-matrx/design-system/controls");
|
|
66
|
+
var import_icons2 = require("@ai-matrx/design-system/icons");
|
|
67
|
+
var import_RenderedFindBar = require("@ai-matrx/rich-content/rich-document/search/RenderedFindBar");
|
|
68
|
+
|
|
69
|
+
// src/pdf/page-decisions.ts
|
|
70
|
+
function freshDecisions(pageCount) {
|
|
71
|
+
return { order: Array.from({ length: Math.max(0, pageCount) }, (_, i) => i + 1), turns: {}, deleted: [] };
|
|
72
|
+
}
|
|
73
|
+
var norm = (t) => (t % 360 + 360) % 360;
|
|
74
|
+
function turnPages(d, pages, by) {
|
|
75
|
+
const turns = { ...d.turns };
|
|
76
|
+
for (const p of pages) {
|
|
77
|
+
const next = norm((turns[p] ?? 0) + by);
|
|
78
|
+
if (next === 0) delete turns[p];
|
|
79
|
+
else turns[p] = next;
|
|
80
|
+
}
|
|
81
|
+
return { ...d, turns };
|
|
82
|
+
}
|
|
83
|
+
function deletePages(d, pages) {
|
|
84
|
+
const set = /* @__PURE__ */ new Set([...d.deleted, ...pages.filter((p) => d.order.includes(p))]);
|
|
85
|
+
if (set.size >= d.order.length) return d;
|
|
86
|
+
return { ...d, deleted: [...set].sort((a, b) => a - b) };
|
|
87
|
+
}
|
|
88
|
+
function restorePages(d, pages) {
|
|
89
|
+
const gone = new Set(pages);
|
|
90
|
+
return { ...d, deleted: d.deleted.filter((p) => !gone.has(p)) };
|
|
91
|
+
}
|
|
92
|
+
function reorderPages(d, order) {
|
|
93
|
+
if (order.length !== d.order.length || [...order].sort((a, b) => a - b).some((p, i) => p !== i + 1)) return d;
|
|
94
|
+
return { ...d, order: [...order] };
|
|
95
|
+
}
|
|
96
|
+
function isDirty(d) {
|
|
97
|
+
const moved = d.order.some((p, i) => p !== i + 1);
|
|
98
|
+
return moved || d.deleted.length > 0 || Object.keys(d.turns).length > 0;
|
|
99
|
+
}
|
|
100
|
+
function keptOrder(d) {
|
|
101
|
+
const gone = new Set(d.deleted);
|
|
102
|
+
return d.order.filter((p) => !gone.has(p));
|
|
103
|
+
}
|
|
104
|
+
function compilePageOps(d) {
|
|
105
|
+
const ops = [];
|
|
106
|
+
const gone = new Set(d.deleted);
|
|
107
|
+
for (const rotation of [90, 180, 270]) {
|
|
108
|
+
const pages = Object.entries(d.turns).filter(([p, t]) => t === rotation && !gone.has(Number(p))).map(([p]) => Number(p)).sort((a, b) => a - b);
|
|
109
|
+
if (pages.length) ops.push({ kind: "rotate", pages, rotation });
|
|
110
|
+
}
|
|
111
|
+
if (d.deleted.length) ops.push({ kind: "delete", pages: [...d.deleted] });
|
|
112
|
+
const kept = keptOrder(d);
|
|
113
|
+
const renumber = new Map([...kept].sort((a, b) => a - b).map((p, i) => [p, i + 1]));
|
|
114
|
+
const newOrder = kept.map((p) => renumber.get(p));
|
|
115
|
+
if (newOrder.some((p, i) => p !== i + 1)) ops.push({ kind: "reorder", newOrder });
|
|
116
|
+
return ops;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/pdf/left-panel.tsx
|
|
120
|
+
var import_react = require("react");
|
|
121
|
+
var import_controls = require("@ai-matrx/design-system/controls");
|
|
122
|
+
var import_thumbnail_grid = require("@ai-matrx/design-system/thumbnail-grid");
|
|
123
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
124
|
+
var THUMB_W = 88;
|
|
125
|
+
function LeftPanel(p) {
|
|
126
|
+
const items = p.decisions.order.map((n) => ({ id: String(n) }));
|
|
127
|
+
const gone = new Set(p.decisions.deleted);
|
|
128
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex h-full min-h-0 w-full flex-col", children: [
|
|
129
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "shrink-0 border-b border-border p-2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
130
|
+
import_controls.SegmentedControl,
|
|
131
|
+
{
|
|
132
|
+
fill: true,
|
|
133
|
+
value: p.tab,
|
|
134
|
+
onValueChange: (v) => v === "layers" && !p.host.layers ? p.onLayersSoon() : p.onTab(v),
|
|
135
|
+
data: [
|
|
136
|
+
{ value: "pages", label: "Pages" },
|
|
137
|
+
{ value: "layers", label: "Layers" }
|
|
138
|
+
],
|
|
139
|
+
"aria-label": "Left panel"
|
|
140
|
+
}
|
|
141
|
+
) }),
|
|
142
|
+
p.tab === "pages" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "min-h-0 flex-1 overflow-y-auto p-2", "data-pdf-thumbnails": "", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
143
|
+
import_thumbnail_grid.ThumbnailGrid,
|
|
144
|
+
{
|
|
145
|
+
"aria-label": "Pages",
|
|
146
|
+
items,
|
|
147
|
+
columns: 2,
|
|
148
|
+
aspectRatio: "3 / 4",
|
|
149
|
+
selectedIds: p.selectedIds,
|
|
150
|
+
onSelectedIdsChange: p.onSelect,
|
|
151
|
+
onReorder: (ids) => p.onReorder(ids.map(Number)),
|
|
152
|
+
onOpen: (item) => p.onOpenPage(Number(item.id)),
|
|
153
|
+
itemLabel: (item) => `Page ${item.id}${gone.has(Number(item.id)) ? ", deleted" : ""}`,
|
|
154
|
+
renderCaption: (item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: Number(item.id) === p.currentPage ? "font-medium text-foreground" : "text-muted-foreground", children: item.id }),
|
|
155
|
+
renderItem: (item) => {
|
|
156
|
+
const page = Number(item.id);
|
|
157
|
+
const turn = p.decisions.turns[page] ?? 0;
|
|
158
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex h-full w-full items-center justify-center overflow-hidden bg-card", children: [
|
|
159
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { transform: turn ? `rotate(${turn}deg)` : void 0 }, className: "flex h-full w-full items-center justify-center", children: p.host.renderThumbnail ? p.host.renderThumbnail(page, THUMB_W) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs text-muted-foreground", children: page }) }),
|
|
160
|
+
page === p.currentPage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "pointer-events-none absolute inset-0 border-2 border-primary" }) : null,
|
|
161
|
+
gone.has(page) ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute inset-0 flex items-center justify-center bg-background/80 text-xs font-medium text-destructive", children: "Deleted" }) : null
|
|
162
|
+
] });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LayersList, { host: p.host })
|
|
166
|
+
] });
|
|
167
|
+
}
|
|
168
|
+
function LayersList({ host }) {
|
|
169
|
+
const [layers, setLayers] = (0, import_react.useState)(null);
|
|
170
|
+
const [error, setError] = (0, import_react.useState)(null);
|
|
171
|
+
(0, import_react.useEffect)(() => {
|
|
172
|
+
host.layers?.list().then(setLayers, (e) => setError(e instanceof Error ? e.message : "The layers did not load."));
|
|
173
|
+
}, [host]);
|
|
174
|
+
if (error) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "alert", className: "p-3 text-xs text-destructive", children: error });
|
|
175
|
+
if (!layers) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "p-3 text-xs text-muted-foreground", "aria-busy": "true", children: "Loading" });
|
|
176
|
+
if (!layers.length) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "p-3 text-xs text-muted-foreground", children: "This document has no layers." });
|
|
177
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "min-h-0 flex-1 overflow-y-auto p-2", children: layers.map((l) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: "flex items-center justify-between gap-2 py-1 text-xs", children: [
|
|
178
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "truncate", children: l.name }),
|
|
179
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
180
|
+
import_controls.Switch,
|
|
181
|
+
{
|
|
182
|
+
checked: l.visible,
|
|
183
|
+
"aria-label": `Show ${l.name}`,
|
|
184
|
+
onCheckedChange: (v) => {
|
|
185
|
+
setLayers((prev) => prev?.map((x) => x.id === l.id ? { ...x, visible: v } : x) ?? prev);
|
|
186
|
+
host.layers.set(l.id, v).catch((e) => {
|
|
187
|
+
setLayers((prev) => prev?.map((x) => x.id === l.id ? { ...x, visible: !v } : x) ?? prev);
|
|
188
|
+
setError(e instanceof Error ? e.message : "The layer did not change.");
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
] }, l.id)) });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/pdf/panels.tsx
|
|
197
|
+
var import_react2 = require("react");
|
|
198
|
+
var import_controls2 = require("@ai-matrx/design-system/controls");
|
|
199
|
+
var import_controls3 = require("@ai-matrx/design-system/controls");
|
|
200
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
201
|
+
function Row({ label, children }) {
|
|
202
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
203
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs font-medium text-muted-foreground", children: label }),
|
|
204
|
+
children
|
|
205
|
+
] });
|
|
206
|
+
}
|
|
207
|
+
function useRangeText(selected, currentPage) {
|
|
208
|
+
const synced = (0, import_react2.useRef)("");
|
|
209
|
+
const [text, setText] = (0, import_react2.useState)("");
|
|
210
|
+
const follow = selected.length ? (0, import_controls3.formatPageRanges)(selected) : String(currentPage);
|
|
211
|
+
(0, import_react2.useEffect)(() => {
|
|
212
|
+
setText((prev) => prev === synced.current ? follow : prev);
|
|
213
|
+
synced.current = follow;
|
|
214
|
+
}, [follow]);
|
|
215
|
+
return [text, setText];
|
|
216
|
+
}
|
|
217
|
+
function RunButton({ busy, online, disabled, children, onClick }) {
|
|
218
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Button, { variant: "primary", disabled: busy || !online || disabled, onClick, children: busy ? "Working" : children });
|
|
219
|
+
}
|
|
220
|
+
function Result({ result, host, error }) {
|
|
221
|
+
const [copied, setCopied] = (0, import_react2.useState)(false);
|
|
222
|
+
const csv = (0, import_react2.useMemo)(
|
|
223
|
+
() => result?.kind === "tables" ? result.tables.map((t) => t.rows.map((r) => r.map((c) => `"${c.replace(/"/g, '""')}"`).join(",")).join("\n")).join("\n\n") : "",
|
|
224
|
+
[result]
|
|
225
|
+
);
|
|
226
|
+
const copy = async (text) => {
|
|
227
|
+
await navigator.clipboard.writeText(text);
|
|
228
|
+
setCopied(true);
|
|
229
|
+
setTimeout(() => setCopied(false), 1500);
|
|
230
|
+
};
|
|
231
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex min-h-[3rem] flex-col gap-2", "aria-live": "polite", children: [
|
|
232
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { role: "alert", className: "rounded-md border border-destructive bg-destructive/10 p-2 text-xs text-destructive", children: error }) : null,
|
|
233
|
+
result?.kind === "replaced" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs text-muted-foreground", children: "Saved as a new version of this file." }) : null,
|
|
234
|
+
result?.kind === "file" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_controls2.Button, { variant: "outline", onClick: () => host.openFile(result.fileId), children: [
|
|
235
|
+
"Open ",
|
|
236
|
+
result.name ?? "the new file"
|
|
237
|
+
] }) : null,
|
|
238
|
+
result?.kind === "text" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
239
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
240
|
+
"textarea",
|
|
241
|
+
{
|
|
242
|
+
readOnly: true,
|
|
243
|
+
value: result.text,
|
|
244
|
+
"aria-label": "Extracted text",
|
|
245
|
+
className: "h-48 w-full resize-none rounded-md border border-border bg-background p-2 text-xs text-foreground"
|
|
246
|
+
}
|
|
247
|
+
),
|
|
248
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Button, { variant: "outline", onClick: () => void copy(result.text), children: copied ? "Copied" : "Copy text" })
|
|
249
|
+
] }) : null,
|
|
250
|
+
result?.kind === "tables" ? result.tables.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-xs text-muted-foreground", children: "No tables found on these pages." }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
251
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex max-h-48 flex-col gap-2 overflow-auto", children: result.tables.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rounded-md border border-border", children: [
|
|
252
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "border-b border-border px-2 py-1 text-xs font-medium", children: [
|
|
253
|
+
"Page ",
|
|
254
|
+
t.page
|
|
255
|
+
] }),
|
|
256
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("table", { className: "w-full text-xs", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: t.rows.slice(0, 12).map((r, ri) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tr", { className: "border-b border-border last:border-b-0", children: r.map((c, ci) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "px-2 py-1", children: c }, ci)) }, ri)) }) })
|
|
257
|
+
] }, i)) }),
|
|
258
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Button, { variant: "outline", onClick: () => void copy(csv), children: copied ? "Copied" : "Copy as CSV" })
|
|
259
|
+
] }) : null
|
|
260
|
+
] });
|
|
261
|
+
}
|
|
262
|
+
var LEVELS = [
|
|
263
|
+
{ value: "1", label: "1" },
|
|
264
|
+
{ value: "2", label: "2" },
|
|
265
|
+
{ value: "3", label: "3" },
|
|
266
|
+
{ value: "4", label: "4" },
|
|
267
|
+
{ value: "5", label: "5" }
|
|
268
|
+
];
|
|
269
|
+
function ToolPanel(p) {
|
|
270
|
+
const [range, setRange] = useRangeText(p.selected, p.currentPage);
|
|
271
|
+
const parsed = (0, import_react2.useMemo)(() => (0, import_controls3.parsePageRanges)(range, p.pageCount), [range, p.pageCount]);
|
|
272
|
+
const [perPart, setPerPart] = (0, import_react2.useState)("10");
|
|
273
|
+
const [margins, setMargins] = (0, import_react2.useState)({ top: "0", right: "0", bottom: "0", left: "0" });
|
|
274
|
+
const [pattern, setPattern] = (0, import_react2.useState)("");
|
|
275
|
+
const [reason, setReason] = (0, import_react2.useState)("");
|
|
276
|
+
const [level, setLevel] = (0, import_react2.useState)("3");
|
|
277
|
+
const [patterns, setPatterns] = (0, import_react2.useState)(null);
|
|
278
|
+
const [patternsError, setPatternsError] = (0, import_react2.useState)(null);
|
|
279
|
+
const loadPatterns = p.host.redactPatterns;
|
|
280
|
+
(0, import_react2.useEffect)(() => {
|
|
281
|
+
if (p.tool.id !== "redact" || !loadPatterns || patterns) return;
|
|
282
|
+
loadPatterns().then(setPatterns, (e) => setPatternsError(e instanceof Error ? e.message : "The pattern list did not load."));
|
|
283
|
+
}, [p.tool.id, loadPatterns, patterns]);
|
|
284
|
+
const rangeField = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Row, { label: "Pages", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls3.PageRangeInput, { value: range, onChange: (t) => setRange(t), pageCount: p.pageCount, "aria-label": "Pages" }) });
|
|
285
|
+
const cropBox = () => {
|
|
286
|
+
const n = (s) => Math.max(0, Number(s) || 0);
|
|
287
|
+
const groups = /* @__PURE__ */ new Map();
|
|
288
|
+
for (const page of parsed.pages) {
|
|
289
|
+
const size = p.pageSize(page);
|
|
290
|
+
if (!size) continue;
|
|
291
|
+
const box = { x0: n(margins.left), y0: n(margins.top), x1: size.w - n(margins.right), y1: size.h - n(margins.bottom) };
|
|
292
|
+
const key = `${box.x0}:${box.y0}:${box.x1}:${box.y1}`;
|
|
293
|
+
const g = groups.get(key) ?? { pages: [], box };
|
|
294
|
+
g.pages.push(page);
|
|
295
|
+
groups.set(key, g);
|
|
296
|
+
}
|
|
297
|
+
return [...groups.values()];
|
|
298
|
+
};
|
|
299
|
+
let body = null;
|
|
300
|
+
switch (p.tool.id) {
|
|
301
|
+
case "browse": {
|
|
302
|
+
const kept = keptOrder(p.decisions).length;
|
|
303
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-1 text-xs", children: [
|
|
304
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex justify-between", children: [
|
|
305
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-muted-foreground", children: "Pages" }),
|
|
306
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: kept })
|
|
307
|
+
] }),
|
|
308
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex justify-between", children: [
|
|
309
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-muted-foreground", children: "Selected" }),
|
|
310
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: p.selected.length })
|
|
311
|
+
] }),
|
|
312
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex justify-between", children: [
|
|
313
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-muted-foreground", children: "Unsaved changes" }),
|
|
314
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: p.dirty ? "Yes" : "None" })
|
|
315
|
+
] })
|
|
316
|
+
] });
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
case "organize":
|
|
320
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-3", children: [
|
|
321
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
322
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Button, { variant: "outline", disabled: !p.selected.length, onClick: () => p.onTurn(-90), children: "Rotate left" }),
|
|
323
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Button, { variant: "outline", disabled: !p.selected.length, onClick: () => p.onTurn(90), children: "Rotate right" }),
|
|
324
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Button, { variant: "outline", disabled: !p.selected.length, onClick: p.onDelete, children: "Delete" }),
|
|
325
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Button, { variant: "outline", disabled: !p.selected.length, onClick: p.onRestore, children: "Restore" })
|
|
326
|
+
] }),
|
|
327
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
|
|
328
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Button, { variant: "primary", disabled: !p.dirty || p.busy || !p.online, onClick: p.onApply, children: p.busy ? "Working" : "Apply changes" }),
|
|
329
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Button, { variant: "outline", disabled: !p.dirty || p.busy, onClick: p.onDiscard, children: "Discard" })
|
|
330
|
+
] })
|
|
331
|
+
] });
|
|
332
|
+
break;
|
|
333
|
+
case "extract-pages":
|
|
334
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
335
|
+
rangeField,
|
|
336
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RunButton, { busy: p.busy, online: p.online, disabled: !parsed.valid, onClick: () => p.onRun({ tool: "extract-pages", pages: parsed.pages }), children: "Extract to a new file" })
|
|
337
|
+
] });
|
|
338
|
+
break;
|
|
339
|
+
case "split":
|
|
340
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
341
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Row, { label: "Pages in each part", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Field, { type: "number", min: 1, value: perPart, onChange: (e) => setPerPart(e.target.value), "aria-label": "Pages in each part" }) }),
|
|
342
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RunButton, { busy: p.busy, online: p.online, disabled: !(Number(perPart) >= 1), onClick: () => p.onRun({ tool: "split", maxPagesPerPart: Math.floor(Number(perPart)) }), children: "Split into new files" })
|
|
343
|
+
] });
|
|
344
|
+
break;
|
|
345
|
+
case "crop": {
|
|
346
|
+
const groups = cropBox();
|
|
347
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
348
|
+
rangeField,
|
|
349
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "grid grid-cols-2 gap-2", children: ["top", "right", "bottom", "left"].map((side) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Row, { label: side[0].toUpperCase() + side.slice(1), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Field, { type: "number", min: 0, value: margins[side], onChange: (e) => setMargins((m) => ({ ...m, [side]: e.target.value })), "aria-label": `Crop ${side}` }) }, side)) }),
|
|
350
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
351
|
+
RunButton,
|
|
352
|
+
{
|
|
353
|
+
busy: p.busy,
|
|
354
|
+
online: p.online,
|
|
355
|
+
disabled: !parsed.valid || groups.length === 0,
|
|
356
|
+
onClick: () => {
|
|
357
|
+
for (const g of groups) p.onRun({ tool: "crop", pages: g.pages, box: g.box });
|
|
358
|
+
},
|
|
359
|
+
children: "Crop to a new file"
|
|
360
|
+
}
|
|
361
|
+
)
|
|
362
|
+
] });
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
case "redact":
|
|
366
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
367
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Row, { label: "Find", children: [
|
|
368
|
+
patterns ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
369
|
+
import_controls2.Select,
|
|
370
|
+
{
|
|
371
|
+
value: pattern,
|
|
372
|
+
onValueChange: setPattern,
|
|
373
|
+
options: patterns.map((x) => ({ value: x.id, label: x.label })),
|
|
374
|
+
"aria-label": "What to redact"
|
|
375
|
+
}
|
|
376
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Field, { value: pattern, onChange: (e) => setPattern(e.target.value), "aria-label": "What to redact" }),
|
|
377
|
+
patternsError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs text-destructive", children: patternsError }) : null
|
|
378
|
+
] }),
|
|
379
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Row, { label: "Reason", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Field, { value: reason, onChange: (e) => setReason(e.target.value), "aria-label": "Reason" }) }),
|
|
380
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RunButton, { busy: p.busy, online: p.online, disabled: !pattern.trim() || !reason.trim(), onClick: () => p.onRun({ tool: "redact", pattern: pattern.trim(), reason: reason.trim() }), children: "Redact to a new file" })
|
|
381
|
+
] });
|
|
382
|
+
break;
|
|
383
|
+
case "compress":
|
|
384
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
385
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Row, { label: "Strength", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.SegmentedControl, { fill: true, value: level, onValueChange: setLevel, data: LEVELS, "aria-label": "Compression strength" }) }),
|
|
386
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RunButton, { busy: p.busy, online: p.online, onClick: () => p.onRun({ tool: "compress", level: Number(level) }), children: "Compress to a new file" })
|
|
387
|
+
] });
|
|
388
|
+
break;
|
|
389
|
+
case "extract-data":
|
|
390
|
+
body = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
391
|
+
rangeField,
|
|
392
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
393
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Button, { variant: "outline", disabled: p.busy || !p.online, onClick: () => p.onRun({ tool: "extract-text" }), children: "Get text" }),
|
|
394
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_controls2.Button, { variant: "outline", disabled: p.busy || !p.online || !parsed.valid, onClick: () => p.onRun({ tool: "extract-tables", pages: parsed.pages }), children: "Get tables" })
|
|
395
|
+
] })
|
|
396
|
+
] });
|
|
397
|
+
break;
|
|
398
|
+
default:
|
|
399
|
+
body = null;
|
|
400
|
+
}
|
|
401
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col gap-3", children: [
|
|
402
|
+
body,
|
|
403
|
+
p.tool.id !== "browse" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Result, { result: p.result, host: p.host, error: p.error }) : null
|
|
404
|
+
] });
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/pdf/session-history.ts
|
|
408
|
+
var HISTORY_LIMIT = 100;
|
|
409
|
+
function startHistory(value) {
|
|
410
|
+
return { past: [], present: { label: "", value }, future: [] };
|
|
411
|
+
}
|
|
412
|
+
function pushStep(h, label, value, limit = HISTORY_LIMIT) {
|
|
413
|
+
if (Object.is(h.present.value, value)) return h;
|
|
414
|
+
const past = [...h.past, h.present].slice(-limit);
|
|
415
|
+
return { past, present: { label, value }, future: [] };
|
|
416
|
+
}
|
|
417
|
+
function undo(h) {
|
|
418
|
+
const prev = h.past[h.past.length - 1];
|
|
419
|
+
if (!prev) return h;
|
|
420
|
+
return { past: h.past.slice(0, -1), present: prev, future: [h.present, ...h.future] };
|
|
421
|
+
}
|
|
422
|
+
function redo(h) {
|
|
423
|
+
const next = h.future[0];
|
|
424
|
+
if (!next) return h;
|
|
425
|
+
return { past: [...h.past, h.present], present: next, future: h.future.slice(1) };
|
|
426
|
+
}
|
|
427
|
+
var canUndo = (h) => h.past.length > 0;
|
|
428
|
+
var canRedo = (h) => h.future.length > 0;
|
|
429
|
+
var undoLabel = (h) => h.present.label;
|
|
430
|
+
var redoLabel = (h) => h.future[0]?.label ?? "";
|
|
431
|
+
|
|
432
|
+
// src/pdf/tools.ts
|
|
433
|
+
var PDF_TOOLS = [
|
|
434
|
+
{ id: "browse", label: "Browse", key: "v", status: "ready", opens: "none", group: 0 },
|
|
435
|
+
{ id: "organize", label: "Organize pages", key: "o", status: "ready", capability: "rotate-pages,delete-pages,reorder-pages", opens: "panel", group: 0 },
|
|
436
|
+
{ id: "extract-pages", label: "Extract pages", key: "e", status: "ready", capability: "extract-pages", opens: "panel", group: 0 },
|
|
437
|
+
{ id: "split", label: "Split", key: "s", status: "ready", capability: "split", opens: "panel", group: 0 },
|
|
438
|
+
{ id: "crop", label: "Crop pages", key: "c", status: "ready", capability: "crop-pages", opens: "panel", group: 0 },
|
|
439
|
+
{ id: "redact", label: "Redact", key: "r", status: "ready", capability: "redact-pattern", opens: "panel", group: 1 },
|
|
440
|
+
{ id: "compress", label: "Compress", key: "z", status: "ready", capability: "compress", opens: "panel", group: 1 },
|
|
441
|
+
{ id: "extract-data", label: "Text and tables", key: "t", status: "ready", capability: "extract-text-remote,extract-tables", opens: "panel", group: 1 },
|
|
442
|
+
{ id: "scan", label: "Scan", status: "ready", capability: "route:/tools/scanner", opens: "page", group: 1 },
|
|
443
|
+
{ id: "sign", label: "Sign", status: "ready", capability: "route:/esign/new", opens: "page", group: 1 },
|
|
444
|
+
{ id: "edit", label: "Edit text and images", status: "soon", opens: "none", group: 2 },
|
|
445
|
+
{ id: "forms", label: "Fill forms", status: "soon", opens: "none", group: 2 },
|
|
446
|
+
{ id: "protect", label: "Protect", status: "soon", opens: "none", group: 2 },
|
|
447
|
+
{ id: "ocr", label: "Recognize text", status: "soon", opens: "none", group: 2 },
|
|
448
|
+
{ id: "convert", label: "Convert", status: "soon", opens: "none", group: 2 },
|
|
449
|
+
{ id: "compare", label: "Compare", status: "soon", opens: "none", group: 2 },
|
|
450
|
+
{ id: "stamp", label: "Stamp and number", status: "soon", opens: "none", group: 2 }
|
|
451
|
+
];
|
|
452
|
+
var pdfTool = (id) => PDF_TOOLS.find((t) => t.id === id);
|
|
453
|
+
var comingSoonIdOf = (id) => `pdf.${id}`;
|
|
454
|
+
var PROMISES = {
|
|
455
|
+
edit: "Change text and swap images right on the page.",
|
|
456
|
+
forms: "Fill in a form and keep the values in the file.",
|
|
457
|
+
protect: "Lock a PDF with a password and limit what others can do.",
|
|
458
|
+
ocr: "Turn a scanned PDF into one you can search and copy from.",
|
|
459
|
+
convert: "Turn a PDF into Word, Excel or PowerPoint, and back.",
|
|
460
|
+
compare: "Put two versions side by side and see what changed.",
|
|
461
|
+
stamp: "Add watermarks, page numbers and Bates numbers."
|
|
462
|
+
};
|
|
463
|
+
var PANEL_PROMISES = {
|
|
464
|
+
"pdf.layers": {
|
|
465
|
+
id: "pdf.layers",
|
|
466
|
+
label: "Layers",
|
|
467
|
+
owner: "pdf",
|
|
468
|
+
promise: "Show or hide the layers inside a PDF.",
|
|
469
|
+
stage: "planned",
|
|
470
|
+
surfaces: ["PDF workspace page panel"]
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
var PDF_COMING_SOON = {
|
|
474
|
+
...Object.fromEntries(
|
|
475
|
+
PDF_TOOLS.filter((t) => t.status === "soon").map((t) => {
|
|
476
|
+
const id = comingSoonIdOf(t.id);
|
|
477
|
+
return [id, { id, label: t.label, owner: "pdf", promise: PROMISES[t.id] ?? "", stage: "planned", surfaces: ["PDF workspace tool rail"] }];
|
|
478
|
+
})
|
|
479
|
+
),
|
|
480
|
+
...PANEL_PROMISES
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// src/pdf/shortcuts.ts
|
|
484
|
+
var PDF_SHORTCUTS = [
|
|
485
|
+
{ action: "undo", label: "Undo", keys: "Mod+Z", section: "Edit" },
|
|
486
|
+
{ action: "redo", label: "Redo", keys: "Mod+Shift+Z", section: "Edit" },
|
|
487
|
+
{ action: "save", label: "Apply page changes", keys: "Mod+S", section: "Edit" },
|
|
488
|
+
{ action: "find", label: "Find", keys: "Mod+F", section: "Edit" },
|
|
489
|
+
{ action: "print", label: "Print", keys: "Mod+P", section: "Edit" },
|
|
490
|
+
{ action: "zoom-in", label: "Zoom in", keys: "Mod+=", section: "View" },
|
|
491
|
+
{ action: "zoom-out", label: "Zoom out", keys: "Mod+-", section: "View" },
|
|
492
|
+
{ action: "zoom-fit-width", label: "Fit width", keys: "Mod+1", section: "View" },
|
|
493
|
+
{ action: "zoom-fit-page", label: "Fit page", keys: "Mod+0", section: "View" },
|
|
494
|
+
{ action: "two-up", label: "Two pages side by side", keys: "Mod+Shift+2", section: "View" },
|
|
495
|
+
{ action: "read-mode", label: "Reading mode", keys: "Mod+Shift+H", section: "View" },
|
|
496
|
+
{ action: "toggle-pages", label: "Page thumbnails", keys: "Mod+Shift+T", section: "View" },
|
|
497
|
+
{ action: "toggle-properties", label: "Properties panel", keys: "Mod+Shift+P", section: "View" },
|
|
498
|
+
{ action: "shortcuts", label: "Keyboard shortcuts", keys: "?", section: "View" },
|
|
499
|
+
{ action: "page-next", label: "Next page", keys: "ArrowRight", section: "Pages" },
|
|
500
|
+
{ action: "page-prev", label: "Previous page", keys: "ArrowLeft", section: "Pages" },
|
|
501
|
+
{ action: "page-first", label: "First page", keys: "Home", section: "Pages" },
|
|
502
|
+
{ action: "page-last", label: "Last page", keys: "End", section: "Pages" },
|
|
503
|
+
{ action: "select-all-pages", label: "Select all pages", keys: "Mod+A", section: "Pages" },
|
|
504
|
+
{ action: "rotate-right", label: "Rotate selected pages right", keys: "Mod+]", section: "Pages" },
|
|
505
|
+
{ action: "rotate-left", label: "Rotate selected pages left", keys: "Mod+[", section: "Pages" },
|
|
506
|
+
{ action: "delete-pages", label: "Delete selected pages", keys: "Delete", section: "Pages" },
|
|
507
|
+
...PDF_TOOLS.filter((t) => t.key && t.status === "ready").map((t) => ({
|
|
508
|
+
action: `tool:${t.id}`,
|
|
509
|
+
label: t.label,
|
|
510
|
+
keys: t.key.toUpperCase(),
|
|
511
|
+
section: "Tools"
|
|
512
|
+
}))
|
|
513
|
+
];
|
|
514
|
+
function parse(keys) {
|
|
515
|
+
const parts = keys.split("+");
|
|
516
|
+
const key = parts[parts.length - 1] === "" ? "+" : parts[parts.length - 1];
|
|
517
|
+
return { mod: parts.includes("Mod"), shift: parts.includes("Shift"), key };
|
|
518
|
+
}
|
|
519
|
+
var TYPING_PASSES = /* @__PURE__ */ new Set(["find", "save", "print"]);
|
|
520
|
+
function matchShortcut(e, typing) {
|
|
521
|
+
const mod = e.ctrlKey || e.metaKey;
|
|
522
|
+
const pressed = e.key.length === 1 ? e.key.toLowerCase() : e.key;
|
|
523
|
+
for (const s of PDF_SHORTCUTS) {
|
|
524
|
+
const want = parse(s.keys);
|
|
525
|
+
const wantKey = want.key.length === 1 ? want.key.toLowerCase() : want.key;
|
|
526
|
+
if (want.mod !== mod || e.altKey) continue;
|
|
527
|
+
const shiftOk = want.key === "?" ? true : want.shift === e.shiftKey || want.key === "=" && e.shiftKey && pressed === "+";
|
|
528
|
+
if (!shiftOk) continue;
|
|
529
|
+
if (pressed === wantKey || want.key === "=" && pressed === "+") {
|
|
530
|
+
if (typing && !TYPING_PASSES.has(s.action)) return null;
|
|
531
|
+
return s.action;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
function formatKeys(keys, mac) {
|
|
537
|
+
return keys.split("+").map((k) => k === "Mod" ? mac ? "\u2318" : "Ctrl" : k === "Shift" ? mac ? "\u21E7" : "Shift" : k === "ArrowRight" ? "\u2192" : k === "ArrowLeft" ? "\u2190" : k).join(mac ? "" : "+");
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/pdf/tool-icons.tsx
|
|
541
|
+
var import_icons = require("@ai-matrx/design-system/icons");
|
|
542
|
+
var PDF_TOOL_ICONS = {
|
|
543
|
+
browse: import_icons.HandIcon,
|
|
544
|
+
organize: import_icons.LayoutGridIcon,
|
|
545
|
+
"extract-pages": import_icons.ArrowUpFromLineIcon,
|
|
546
|
+
split: import_icons.SplitIcon,
|
|
547
|
+
crop: import_icons.ProportionsIcon,
|
|
548
|
+
redact: import_icons.EraserIcon,
|
|
549
|
+
compress: import_icons.ChevronsDownUpIcon,
|
|
550
|
+
scan: import_icons.CameraIcon,
|
|
551
|
+
sign: import_icons.PenToolIcon,
|
|
552
|
+
"extract-data": import_icons.TypeIcon,
|
|
553
|
+
edit: import_icons.PencilIcon,
|
|
554
|
+
forms: import_icons.ListChecksIcon,
|
|
555
|
+
protect: import_icons.LockIcon,
|
|
556
|
+
ocr: import_icons.CaptionsIcon,
|
|
557
|
+
convert: import_icons.ArrowLeftRightIcon,
|
|
558
|
+
compare: import_icons.Columns2Icon,
|
|
559
|
+
stamp: import_icons.FlagIcon
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// src/pdf/PdfWorkspace.tsx
|
|
563
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
564
|
+
var ZOOM_STEPS = [0.5, 0.75, 1, 1.25, 1.5, 2, 3];
|
|
565
|
+
var COMPACT_BELOW_PX = 768;
|
|
566
|
+
var BAR = "flex h-10 shrink-0 items-center gap-1 border-b border-border bg-background px-2";
|
|
567
|
+
var ALL_TOOLS = "all-tools";
|
|
568
|
+
var isMac = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform);
|
|
569
|
+
var clampZoom = (z) => Math.min(3, Math.max(0.5, z));
|
|
570
|
+
async function announceSoon(id) {
|
|
571
|
+
const { announceComingSoon } = await import("@ai-matrx/kit/coming-soon");
|
|
572
|
+
await announceComingSoon(id);
|
|
573
|
+
}
|
|
574
|
+
function useWidth(ref) {
|
|
575
|
+
const [size, setSize] = (0, import_react3.useState)({ w: 1200, h: 800 });
|
|
576
|
+
(0, import_react3.useLayoutEffect)(() => {
|
|
577
|
+
const node = ref.current;
|
|
578
|
+
if (!node) return void 0;
|
|
579
|
+
const measure = () => setSize({ w: node.clientWidth || 1200, h: node.clientHeight || 800 });
|
|
580
|
+
measure();
|
|
581
|
+
if (typeof ResizeObserver === "undefined") return void 0;
|
|
582
|
+
const ro = new ResizeObserver(measure);
|
|
583
|
+
ro.observe(node);
|
|
584
|
+
return () => ro.disconnect();
|
|
585
|
+
}, [ref]);
|
|
586
|
+
return size;
|
|
587
|
+
}
|
|
588
|
+
function ToolRows({ tool, onPick }) {
|
|
589
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: PDF_TOOLS.filter((t) => t.id !== "browse").map((t) => {
|
|
590
|
+
const Icon = PDF_TOOL_ICONS[t.id];
|
|
591
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { role: "none", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
592
|
+
"button",
|
|
593
|
+
{
|
|
594
|
+
type: "button",
|
|
595
|
+
role: "menuitem",
|
|
596
|
+
"aria-current": t.id === tool ? "true" : void 0,
|
|
597
|
+
className: (0, import_cn.cn)("flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-xs hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", t.id === tool && "bg-accent"),
|
|
598
|
+
onClick: () => onPick(t.id),
|
|
599
|
+
children: [
|
|
600
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { className: "size-4 shrink-0" }),
|
|
601
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex-1 whitespace-nowrap", children: t.label }),
|
|
602
|
+
t.status === "soon" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-muted-foreground", children: "Coming soon" }) : null
|
|
603
|
+
]
|
|
604
|
+
}
|
|
605
|
+
) }, t.id);
|
|
606
|
+
}) });
|
|
607
|
+
}
|
|
608
|
+
function PdfWorkspace({ host, className }) {
|
|
609
|
+
const rootRef = (0, import_react3.useRef)(null);
|
|
610
|
+
const scrollerRef = (0, import_react3.useRef)(null);
|
|
611
|
+
const rootSize = useWidth(rootRef);
|
|
612
|
+
const viewSize = useWidth(scrollerRef);
|
|
613
|
+
const compact = rootSize.w < COMPACT_BELOW_PX;
|
|
614
|
+
const online = host.online !== false;
|
|
615
|
+
const [pageCount, setPageCount] = (0, import_react3.useState)(0);
|
|
616
|
+
const [history, setHistory] = (0, import_react3.useState)(() => startHistory(freshDecisions(0)));
|
|
617
|
+
const decisions = history.present.value;
|
|
618
|
+
const dirty = isDirty(decisions);
|
|
619
|
+
const [tool, setTool] = (0, import_react3.useState)("browse");
|
|
620
|
+
const [leftOpen, setLeftOpen] = (0, import_react3.useState)(true);
|
|
621
|
+
const [rightOpen, setRightOpen] = (0, import_react3.useState)(true);
|
|
622
|
+
const [leftTab, setLeftTab] = (0, import_react3.useState)("pages");
|
|
623
|
+
const [read, setRead] = (0, import_react3.useState)(false);
|
|
624
|
+
const [spread, setSpread] = (0, import_react3.useState)("single");
|
|
625
|
+
const [zoomMode, setZoomMode] = (0, import_react3.useState)("fit-width");
|
|
626
|
+
const [customZoom, setCustomZoom] = (0, import_react3.useState)(1);
|
|
627
|
+
const [selected, setSelected] = (0, import_react3.useState)([]);
|
|
628
|
+
const [currentPage, setCurrentPage] = (0, import_react3.useState)(1);
|
|
629
|
+
const [pageDraft, setPageDraft] = (0, import_react3.useState)("1");
|
|
630
|
+
const [findOpen, setFindOpen] = (0, import_react3.useState)(false);
|
|
631
|
+
const [findFocus, setFindFocus] = (0, import_react3.useState)(0);
|
|
632
|
+
const [pop, setPop] = (0, import_react3.useState)(null);
|
|
633
|
+
const [sheet, setSheet] = (0, import_react3.useState)(null);
|
|
634
|
+
const [toolQuery, setToolQuery] = (0, import_react3.useState)("");
|
|
635
|
+
const [busy, setBusy] = (0, import_react3.useState)(false);
|
|
636
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
637
|
+
const [result, setResult] = (0, import_react3.useState)(null);
|
|
638
|
+
const [reloadKey, setReloadKey] = (0, import_react3.useState)(0);
|
|
639
|
+
const [sizeVersion, setSizeVersion] = (0, import_react3.useState)(0);
|
|
640
|
+
const sizes = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
|
|
641
|
+
const [editingName, setEditingName] = (0, import_react3.useState)(false);
|
|
642
|
+
const [nameDraft, setNameDraft] = (0, import_react3.useState)(host.file.name);
|
|
643
|
+
const [printMode, setPrintMode] = (0, import_react3.useState)("all");
|
|
644
|
+
const [printRange, setPrintRange] = (0, import_react3.useState)("");
|
|
645
|
+
const selectedPages = (0, import_react3.useMemo)(() => selected.map(Number).sort((a, b) => a - b), [selected]);
|
|
646
|
+
const toolDef = pdfTool(tool);
|
|
647
|
+
const first = sizes.current.get(1);
|
|
648
|
+
const fitPage = (0, import_react3.useMemo)(() => {
|
|
649
|
+
void sizeVersion;
|
|
650
|
+
if (!first) return 1;
|
|
651
|
+
const ratio = first.h / first.w;
|
|
652
|
+
const cols = spread === "two-up" ? 2 : 1;
|
|
653
|
+
const availH = Math.max(120, viewSize.h - 32);
|
|
654
|
+
return clampZoom(availH * cols / (Math.max(1, viewSize.w) * ratio));
|
|
655
|
+
}, [first, spread, viewSize.h, viewSize.w, sizeVersion]);
|
|
656
|
+
const zoom = zoomMode === "fit-width" ? 1 : zoomMode === "fit-page" ? fitPage : customZoom;
|
|
657
|
+
const stepZoom = (0, import_react3.useCallback)(
|
|
658
|
+
(dir) => {
|
|
659
|
+
const cur = zoomMode === "fit-width" ? 1 : zoomMode === "fit-page" ? fitPage : customZoom;
|
|
660
|
+
const next = dir > 0 ? ZOOM_STEPS.find((s) => s > cur + 0.01) : [...ZOOM_STEPS].reverse().find((s) => s < cur - 0.01);
|
|
661
|
+
if (next === void 0) return;
|
|
662
|
+
setCustomZoom(next);
|
|
663
|
+
setZoomMode("custom");
|
|
664
|
+
},
|
|
665
|
+
[zoomMode, fitPage, customZoom]
|
|
666
|
+
);
|
|
667
|
+
const goToPage = (0, import_react3.useCallback)(
|
|
668
|
+
(n) => {
|
|
669
|
+
const page = Math.min(Math.max(1, Math.round(n)), Math.max(1, pageCount));
|
|
670
|
+
setCurrentPage(page);
|
|
671
|
+
setPageDraft(String(page));
|
|
672
|
+
const el = scrollerRef.current?.querySelector(`[data-pdf-page="${page}"]`);
|
|
673
|
+
el?.scrollIntoView?.({ block: "start" });
|
|
674
|
+
},
|
|
675
|
+
[pageCount]
|
|
676
|
+
);
|
|
677
|
+
(0, import_react3.useEffect)(() => {
|
|
678
|
+
const node = scrollerRef.current;
|
|
679
|
+
if (!node) return void 0;
|
|
680
|
+
let raf = 0;
|
|
681
|
+
const onScroll = () => {
|
|
682
|
+
cancelAnimationFrame(raf);
|
|
683
|
+
raf = requestAnimationFrame(() => {
|
|
684
|
+
const top = node.getBoundingClientRect().top + 8;
|
|
685
|
+
const els = node.querySelectorAll("[data-pdf-page]");
|
|
686
|
+
let lo = 0;
|
|
687
|
+
let hi = els.length - 1;
|
|
688
|
+
while (lo < hi) {
|
|
689
|
+
const mid = lo + hi + 1 >> 1;
|
|
690
|
+
if (els[mid].getBoundingClientRect().top <= top) lo = mid;
|
|
691
|
+
else hi = mid - 1;
|
|
692
|
+
}
|
|
693
|
+
const n = Number(els[lo]?.dataset.pdfPage ?? 1);
|
|
694
|
+
setCurrentPage((prev) => prev === n ? prev : n);
|
|
695
|
+
setPageDraft(String(n));
|
|
696
|
+
});
|
|
697
|
+
};
|
|
698
|
+
node.addEventListener("scroll", onScroll, { passive: true });
|
|
699
|
+
return () => {
|
|
700
|
+
node.removeEventListener("scroll", onScroll);
|
|
701
|
+
cancelAnimationFrame(raf);
|
|
702
|
+
};
|
|
703
|
+
}, [pageCount]);
|
|
704
|
+
const commit = (0, import_react3.useCallback)((label, next) => setHistory((h) => pushStep(h, label, next)), []);
|
|
705
|
+
const targets = (0, import_react3.useCallback)(() => selectedPages.length ? selectedPages : [currentPage], [selectedPages, currentPage]);
|
|
706
|
+
const turn = (0, import_react3.useCallback)(
|
|
707
|
+
(by) => {
|
|
708
|
+
const t = targets();
|
|
709
|
+
commit(`Rotate ${t.length === 1 ? `page ${t[0]}` : `${t.length} pages`}`, turnPages(decisions, t, by));
|
|
710
|
+
},
|
|
711
|
+
[targets, commit, decisions]
|
|
712
|
+
);
|
|
713
|
+
const del = (0, import_react3.useCallback)(() => {
|
|
714
|
+
const t = targets();
|
|
715
|
+
commit(`Delete ${t.length === 1 ? `page ${t[0]}` : `${t.length} pages`}`, deletePages(decisions, t));
|
|
716
|
+
}, [targets, commit, decisions]);
|
|
717
|
+
const restore = (0, import_react3.useCallback)(() => {
|
|
718
|
+
const t = targets();
|
|
719
|
+
commit(`Restore ${t.length === 1 ? `page ${t[0]}` : `${t.length} pages`}`, restorePages(decisions, t));
|
|
720
|
+
}, [targets, commit, decisions]);
|
|
721
|
+
const onDocumentLoad = (0, import_react3.useCallback)((n) => {
|
|
722
|
+
setPageCount(n);
|
|
723
|
+
setHistory((h) => h.present.value.order.length === n ? h : startHistory(freshDecisions(n)));
|
|
724
|
+
}, []);
|
|
725
|
+
const onPageSize = (0, import_react3.useCallback)((page, w, h) => {
|
|
726
|
+
const prev = sizes.current.get(page);
|
|
727
|
+
if (prev && prev.w === w && prev.h === h) return;
|
|
728
|
+
sizes.current.set(page, { w, h });
|
|
729
|
+
if (page === 1) queueMicrotask(() => setSizeVersion((v) => v + 1));
|
|
730
|
+
}, []);
|
|
731
|
+
const fail = (0, import_react3.useCallback)(
|
|
732
|
+
(e) => {
|
|
733
|
+
const message = e instanceof Error ? e.message : "That did not work.";
|
|
734
|
+
setError(message);
|
|
735
|
+
host.notify?.(message, "error");
|
|
736
|
+
},
|
|
737
|
+
[host]
|
|
738
|
+
);
|
|
739
|
+
const runTool = (0, import_react3.useCallback)(
|
|
740
|
+
async (request) => {
|
|
741
|
+
if (!online) {
|
|
742
|
+
setError("You are offline. Changes are not queued; try again when you are back online.");
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
setBusy(true);
|
|
746
|
+
setError(null);
|
|
747
|
+
setResult(null);
|
|
748
|
+
try {
|
|
749
|
+
const out = await host.run(request);
|
|
750
|
+
setResult(out);
|
|
751
|
+
if (out.kind === "replaced") {
|
|
752
|
+
setHistory(startHistory(freshDecisions(pageCount)));
|
|
753
|
+
setSelected([]);
|
|
754
|
+
sizes.current.clear();
|
|
755
|
+
setReloadKey((k) => k + 1);
|
|
756
|
+
}
|
|
757
|
+
} catch (e) {
|
|
758
|
+
fail(e);
|
|
759
|
+
} finally {
|
|
760
|
+
setBusy(false);
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
[host, online, pageCount, fail]
|
|
764
|
+
);
|
|
765
|
+
const apply = (0, import_react3.useCallback)(() => {
|
|
766
|
+
const ops = compilePageOps(decisions);
|
|
767
|
+
if (ops.length) void runTool({ tool: "apply-pages", ops });
|
|
768
|
+
}, [decisions, runTool]);
|
|
769
|
+
const discard = (0, import_react3.useCallback)(() => setHistory(startHistory(freshDecisions(pageCount))), [pageCount]);
|
|
770
|
+
const chooseTool = (0, import_react3.useCallback)(
|
|
771
|
+
(id) => {
|
|
772
|
+
const def = pdfTool(id);
|
|
773
|
+
if (!def) return;
|
|
774
|
+
if (def.status === "soon") {
|
|
775
|
+
announceSoon(comingSoonIdOf(def.id)).catch(fail);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
if (def.opens === "page") {
|
|
779
|
+
host.openTool(def.id === "scan" ? "scan" : "sign");
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
setTool(def.id);
|
|
783
|
+
setResult(null);
|
|
784
|
+
setError(null);
|
|
785
|
+
setRightOpen(true);
|
|
786
|
+
if (compact) setSheet("tools");
|
|
787
|
+
},
|
|
788
|
+
[host, compact, fail]
|
|
789
|
+
);
|
|
790
|
+
const railGroups = (0, import_react3.useMemo)(() => {
|
|
791
|
+
const groups = [[], [], []];
|
|
792
|
+
for (const t of PDF_TOOLS) {
|
|
793
|
+
if (t.status !== "ready") continue;
|
|
794
|
+
groups[t.group].push({ id: t.id, label: t.label, icon: PDF_TOOL_ICONS[t.id], ...t.key ? { shortcut: t.key.toUpperCase() } : null });
|
|
795
|
+
}
|
|
796
|
+
groups[2].push({ id: ALL_TOOLS, label: "All tools", icon: import_icons2.MoreHorizontalIcon });
|
|
797
|
+
return groups.filter((g) => g.length);
|
|
798
|
+
}, []);
|
|
799
|
+
const runAction = (0, import_react3.useCallback)(
|
|
800
|
+
(action) => {
|
|
801
|
+
if (action.startsWith("tool:")) return chooseTool(action.slice(5));
|
|
802
|
+
switch (action) {
|
|
803
|
+
case "undo":
|
|
804
|
+
return setHistory((h) => undo(h));
|
|
805
|
+
case "redo":
|
|
806
|
+
return setHistory((h) => redo(h));
|
|
807
|
+
case "save":
|
|
808
|
+
return apply();
|
|
809
|
+
case "find":
|
|
810
|
+
setFindOpen(true);
|
|
811
|
+
setFindFocus((n) => n + 1);
|
|
812
|
+
return;
|
|
813
|
+
case "print":
|
|
814
|
+
return host.print ? setPop("print") : void 0;
|
|
815
|
+
case "zoom-in":
|
|
816
|
+
return stepZoom(1);
|
|
817
|
+
case "zoom-out":
|
|
818
|
+
return stepZoom(-1);
|
|
819
|
+
case "zoom-fit-width":
|
|
820
|
+
return setZoomMode("fit-width");
|
|
821
|
+
case "zoom-fit-page":
|
|
822
|
+
return setZoomMode("fit-page");
|
|
823
|
+
case "page-next":
|
|
824
|
+
return goToPage(currentPage + 1);
|
|
825
|
+
case "page-prev":
|
|
826
|
+
return goToPage(currentPage - 1);
|
|
827
|
+
case "page-first":
|
|
828
|
+
return goToPage(1);
|
|
829
|
+
case "page-last":
|
|
830
|
+
return goToPage(pageCount);
|
|
831
|
+
case "rotate-right":
|
|
832
|
+
return turn(90);
|
|
833
|
+
case "rotate-left":
|
|
834
|
+
return turn(-90);
|
|
835
|
+
case "delete-pages":
|
|
836
|
+
return del();
|
|
837
|
+
case "select-all-pages":
|
|
838
|
+
return setSelected(decisions.order.map(String));
|
|
839
|
+
case "toggle-pages":
|
|
840
|
+
return setLeftOpen((v) => !v);
|
|
841
|
+
case "toggle-properties":
|
|
842
|
+
return setRightOpen((v) => !v);
|
|
843
|
+
case "read-mode":
|
|
844
|
+
return setRead((v) => !v);
|
|
845
|
+
case "two-up":
|
|
846
|
+
return setSpread((s) => s === "two-up" ? "single" : "two-up");
|
|
847
|
+
case "shortcuts":
|
|
848
|
+
return setPop((p) => p === "shortcuts" ? null : "shortcuts");
|
|
849
|
+
default:
|
|
850
|
+
return void 0;
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
[chooseTool, apply, host.print, stepZoom, goToPage, currentPage, pageCount, turn, del, decisions.order]
|
|
854
|
+
);
|
|
855
|
+
const onKeyDown = (e) => {
|
|
856
|
+
const t = e.target;
|
|
857
|
+
const typing = t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.tagName === "SELECT" || t.isContentEditable;
|
|
858
|
+
if (e.key === "Escape") {
|
|
859
|
+
if (pop) setPop(null);
|
|
860
|
+
else if (findOpen) setFindOpen(false);
|
|
861
|
+
else if (read) setRead(false);
|
|
862
|
+
else if (sheet) setSheet(null);
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const action = matchShortcut(e.nativeEvent, typing);
|
|
866
|
+
if (!action) return;
|
|
867
|
+
if ((action === "page-next" || action === "page-prev") && t !== scrollerRef.current && t !== rootRef.current) return;
|
|
868
|
+
if (action === "delete-pages" && t.closest("[data-pdf-thumbnails]") === null && t !== scrollerRef.current) return;
|
|
869
|
+
e.preventDefault();
|
|
870
|
+
runAction(action);
|
|
871
|
+
};
|
|
872
|
+
const saveState = !online ? "Offline" : busy ? "Saving" : error && dirty ? "Not saved" : dirty ? "Unsaved" : "Saved";
|
|
873
|
+
const savePill = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
874
|
+
"span",
|
|
875
|
+
{
|
|
876
|
+
role: "status",
|
|
877
|
+
className: (0, import_cn.cn)(
|
|
878
|
+
"inline-flex h-6 shrink-0 items-center justify-center whitespace-nowrap rounded-full border px-2 text-xs",
|
|
879
|
+
saveState === "Unsaved" || saveState === "Offline" || saveState === "Not saved" ? "border-border text-foreground" : "border-border text-muted-foreground",
|
|
880
|
+
busy && "animate-pulse"
|
|
881
|
+
),
|
|
882
|
+
children: saveState
|
|
883
|
+
}
|
|
884
|
+
);
|
|
885
|
+
const iconBtn = (label, icon, onClick, opts) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls4.Button, { variant: "quiet", icon, "aria-label": label, title: label, onClick, ...opts?.disabled !== void 0 ? { disabled: opts.disabled } : null, ...opts?.pressed !== void 0 ? { pressed: opts.pressed } : null });
|
|
886
|
+
const toolHits = toolQuery.trim() ? PDF_TOOLS.filter((t) => t.label.toLowerCase().includes(toolQuery.trim().toLowerCase())) : [];
|
|
887
|
+
const nameNode = editingName && host.rename ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
888
|
+
import_controls4.Field,
|
|
889
|
+
{
|
|
890
|
+
autoFocus: true,
|
|
891
|
+
value: nameDraft,
|
|
892
|
+
"aria-label": "File name",
|
|
893
|
+
onChange: (e) => setNameDraft(e.target.value),
|
|
894
|
+
onBlur: () => {
|
|
895
|
+
setEditingName(false);
|
|
896
|
+
if (nameDraft.trim() && nameDraft !== host.file.name) host.rename(nameDraft.trim()).catch(fail);
|
|
897
|
+
},
|
|
898
|
+
onKeyDown: (e) => {
|
|
899
|
+
if (e.key === "Enter") e.target.blur();
|
|
900
|
+
if (e.key === "Escape") {
|
|
901
|
+
setNameDraft(host.file.name);
|
|
902
|
+
setEditingName(false);
|
|
903
|
+
}
|
|
904
|
+
},
|
|
905
|
+
width: "lg"
|
|
906
|
+
}
|
|
907
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
908
|
+
"button",
|
|
909
|
+
{
|
|
910
|
+
type: "button",
|
|
911
|
+
className: "min-w-0 max-w-[16rem] truncate rounded-md px-2 text-sm font-medium text-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
912
|
+
title: host.rename ? "Rename" : host.file.name,
|
|
913
|
+
onClick: () => host.rename && (setNameDraft(host.file.name), setEditingName(true)),
|
|
914
|
+
children: host.file.name
|
|
915
|
+
}
|
|
916
|
+
);
|
|
917
|
+
const rightPanel = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
918
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "shrink-0 border-b border-border px-3 py-2", children: [
|
|
919
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-sm font-medium", children: toolDef.label }),
|
|
920
|
+
toolDef.hint ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "text-xs text-muted-foreground", children: toolDef.hint }) : null
|
|
921
|
+
] }),
|
|
922
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "min-h-0 flex-1 overflow-y-auto p-3", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
923
|
+
ToolPanel,
|
|
924
|
+
{
|
|
925
|
+
tool: toolDef,
|
|
926
|
+
host,
|
|
927
|
+
pageCount,
|
|
928
|
+
selected: selectedPages,
|
|
929
|
+
currentPage,
|
|
930
|
+
pageSize: (p) => sizes.current.get(p),
|
|
931
|
+
decisions,
|
|
932
|
+
dirty,
|
|
933
|
+
busy,
|
|
934
|
+
online,
|
|
935
|
+
result,
|
|
936
|
+
error,
|
|
937
|
+
onRun: (r) => void runTool(r),
|
|
938
|
+
onApply: apply,
|
|
939
|
+
onDiscard: discard,
|
|
940
|
+
onTurn: turn,
|
|
941
|
+
onDelete: del,
|
|
942
|
+
onRestore: restore
|
|
943
|
+
}
|
|
944
|
+
) })
|
|
945
|
+
] });
|
|
946
|
+
const leftPanel = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
947
|
+
LeftPanel,
|
|
948
|
+
{
|
|
949
|
+
host,
|
|
950
|
+
tab: leftTab,
|
|
951
|
+
onTab: setLeftTab,
|
|
952
|
+
decisions,
|
|
953
|
+
selectedIds: selected,
|
|
954
|
+
currentPage,
|
|
955
|
+
onSelect: (ids) => {
|
|
956
|
+
setSelected(ids);
|
|
957
|
+
if (ids.length === 1) goToPage(Number(ids[0]));
|
|
958
|
+
},
|
|
959
|
+
onReorder: (order) => commit("Move pages", reorderPages(decisions, order)),
|
|
960
|
+
onOpenPage: goToPage,
|
|
961
|
+
onLayersSoon: () => void announceSoon("pdf.layers").catch(fail)
|
|
962
|
+
}
|
|
963
|
+
);
|
|
964
|
+
const viewer = (0, import_react3.useMemo)(
|
|
965
|
+
() => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react3.Fragment, { children: host.renderViewer({ zoom, spread, rotations: decisions.turns, reloadKey, onDocumentLoad, onPageSize }) }, reloadKey),
|
|
966
|
+
[host, reloadKey, zoom, spread, decisions.turns, onDocumentLoad, onPageSize]
|
|
967
|
+
);
|
|
968
|
+
const showChrome = !read;
|
|
969
|
+
const popCard = "absolute right-2 top-11 z-30 rounded-lg border border-border bg-popover p-3 text-popover-foreground shadow-lg";
|
|
970
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
971
|
+
"div",
|
|
972
|
+
{
|
|
973
|
+
ref: rootRef,
|
|
974
|
+
tabIndex: -1,
|
|
975
|
+
"data-pdf-workspace": "",
|
|
976
|
+
"data-compact": compact ? "" : void 0,
|
|
977
|
+
onKeyDown,
|
|
978
|
+
className: (0, import_cn.cn)("relative flex h-full min-h-0 w-full flex-col overflow-hidden bg-background text-foreground outline-none", className),
|
|
979
|
+
children: [
|
|
980
|
+
showChrome ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: BAR, "data-pdf-topbar": "", children: [
|
|
981
|
+
iconBtn("Page thumbnails", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.PanelLeftIcon, {}), () => compact ? setSheet("pages") : setLeftOpen((v) => !v), { pressed: !compact && leftOpen }),
|
|
982
|
+
nameNode,
|
|
983
|
+
savePill,
|
|
984
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-1" }),
|
|
985
|
+
!compact ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative w-56", children: [
|
|
986
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
987
|
+
import_controls4.Field,
|
|
988
|
+
{
|
|
989
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.SearchIcon, {}),
|
|
990
|
+
value: toolQuery,
|
|
991
|
+
"aria-label": "Search tools",
|
|
992
|
+
placeholder: "Search tools",
|
|
993
|
+
onFocus: () => setPop("tool-search"),
|
|
994
|
+
onChange: (e) => {
|
|
995
|
+
setToolQuery(e.target.value);
|
|
996
|
+
setPop("tool-search");
|
|
997
|
+
},
|
|
998
|
+
onKeyDown: (e) => {
|
|
999
|
+
if (e.key === "Enter" && toolHits[0]) {
|
|
1000
|
+
chooseTool(toolHits[0].id);
|
|
1001
|
+
setToolQuery("");
|
|
1002
|
+
setPop(null);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
),
|
|
1007
|
+
pop === "tool-search" && toolHits.length ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { role: "listbox", className: "absolute left-0 right-0 top-9 z-30 rounded-lg border border-border bg-popover p-1 shadow-lg", children: toolHits.map((t) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { role: "option", "aria-selected": false, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1008
|
+
"button",
|
|
1009
|
+
{
|
|
1010
|
+
type: "button",
|
|
1011
|
+
className: "flex w-full items-center justify-between rounded-md px-2 py-1 text-left text-xs hover:bg-accent",
|
|
1012
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
1013
|
+
onClick: () => {
|
|
1014
|
+
chooseTool(t.id);
|
|
1015
|
+
setToolQuery("");
|
|
1016
|
+
setPop(null);
|
|
1017
|
+
},
|
|
1018
|
+
children: [
|
|
1019
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: t.label }),
|
|
1020
|
+
t.status === "soon" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-muted-foreground", children: "Coming soon" }) : null
|
|
1021
|
+
]
|
|
1022
|
+
}
|
|
1023
|
+
) }, t.id)) }) : null
|
|
1024
|
+
] }) : null,
|
|
1025
|
+
iconBtn(undoLabel(history) ? `Undo ${undoLabel(history)}` : "Undo", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.Undo2Icon, {}), () => runAction("undo"), { disabled: !canUndo(history) }),
|
|
1026
|
+
iconBtn(redoLabel(history) ? `Redo ${redoLabel(history)}` : "Redo", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.Undo2Icon, { className: "-scale-x-100" }), () => runAction("redo"), { disabled: !canRedo(history) }),
|
|
1027
|
+
!compact && iconBtn("Find", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.SearchIcon, {}), () => runAction("find"), { pressed: findOpen }),
|
|
1028
|
+
host.share && !compact ? iconBtn("Share", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.Share2Icon, {}), () => host.share()) : null,
|
|
1029
|
+
iconBtn("Download", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.DownloadIcon, {}), () => host.download()),
|
|
1030
|
+
host.print && !compact ? iconBtn("Print", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.PrinterIcon, {}), () => setPop(pop === "print" ? null : "print"), { pressed: pop === "print" }) : null,
|
|
1031
|
+
!compact ? iconBtn("Keyboard shortcuts", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.KeyboardIcon, {}), () => runAction("shortcuts"), { pressed: pop === "shortcuts" }) : null,
|
|
1032
|
+
compact ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls4.Button, { variant: "outline", onClick: () => setSheet("tools"), children: "Tools" }) : iconBtn("Properties panel", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.PanelRightIcon, {}), () => setRightOpen((v) => !v), { pressed: rightOpen })
|
|
1033
|
+
] }) : null,
|
|
1034
|
+
showChrome ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: BAR, "data-pdf-viewbar": "", children: [
|
|
1035
|
+
iconBtn("Previous page", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.ChevronLeftIcon, {}), () => goToPage(currentPage - 1), { disabled: currentPage <= 1 }),
|
|
1036
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1037
|
+
import_controls4.Field,
|
|
1038
|
+
{
|
|
1039
|
+
type: "number",
|
|
1040
|
+
min: 1,
|
|
1041
|
+
max: Math.max(1, pageCount),
|
|
1042
|
+
value: pageDraft,
|
|
1043
|
+
"aria-label": "Page",
|
|
1044
|
+
onChange: (e) => setPageDraft(e.target.value),
|
|
1045
|
+
onBlur: () => goToPage(Number(pageDraft) || currentPage),
|
|
1046
|
+
onKeyDown: (e) => e.key === "Enter" && goToPage(Number(pageDraft) || currentPage),
|
|
1047
|
+
className: compact ? "w-12" : "w-16"
|
|
1048
|
+
}
|
|
1049
|
+
),
|
|
1050
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "whitespace-nowrap text-xs text-muted-foreground", children: [
|
|
1051
|
+
"of ",
|
|
1052
|
+
pageCount || "-"
|
|
1053
|
+
] }),
|
|
1054
|
+
iconBtn("Next page", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.ChevronRightIcon, {}), () => goToPage(currentPage + 1), { disabled: currentPage >= pageCount }),
|
|
1055
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-1 h-5 w-px bg-border" }),
|
|
1056
|
+
iconBtn("Zoom out", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.MinusIcon, {}), () => stepZoom(-1)),
|
|
1057
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: (0, import_cn.cn)("min-w-[3rem] text-center text-xs tabular-nums", compact && "hidden"), "data-pdf-zoom": "", children: `${Math.round(zoom * 100)}%` }),
|
|
1058
|
+
iconBtn("Zoom in", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.PlusIcon, {}), () => stepZoom(1)),
|
|
1059
|
+
!compact ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1060
|
+
import_controls4.SegmentedControl,
|
|
1061
|
+
{
|
|
1062
|
+
value: zoomMode === "custom" ? null : zoomMode,
|
|
1063
|
+
onValueChange: (v) => setZoomMode(v),
|
|
1064
|
+
data: [{ value: "fit-width", label: "Width" }, { value: "fit-page", label: "Page" }],
|
|
1065
|
+
"aria-label": "Fit"
|
|
1066
|
+
}
|
|
1067
|
+
) : null,
|
|
1068
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-1" }),
|
|
1069
|
+
!compact ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
1070
|
+
iconBtn("Rotate left", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.RotateCcwIcon, {}), () => turn(-90)),
|
|
1071
|
+
iconBtn("Rotate right", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.RotateCwIcon, {}), () => turn(90)),
|
|
1072
|
+
iconBtn("Delete page", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.Trash2Icon, {}), del, { disabled: decisions.order.length - decisions.deleted.length <= 1 }),
|
|
1073
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-1 h-5 w-px bg-border" }),
|
|
1074
|
+
iconBtn("Two pages side by side", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.Columns2Icon, {}), () => runAction("two-up"), { pressed: spread === "two-up" })
|
|
1075
|
+
] }) : null,
|
|
1076
|
+
iconBtn("Reading mode", /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.ExpandIcon, {}), () => setRead(true))
|
|
1077
|
+
] }) : null,
|
|
1078
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative flex min-h-0 flex-1", children: [
|
|
1079
|
+
showChrome && !compact ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex w-10 shrink-0 flex-col items-center border-r border-border bg-background py-2", "data-pdf-rail": "", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls4.ToolRail, { groups: railGroups, value: pop === "all-tools" ? ALL_TOOLS : tool, onValueChange: (id) => id === ALL_TOOLS ? setPop((p) => p === "all-tools" ? null : "all-tools") : id && chooseTool(id), "aria-label": "PDF tools" }) }) : null,
|
|
1080
|
+
showChrome && !compact && leftOpen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("aside", { className: "w-56 shrink-0 border-r border-border bg-background", "aria-label": "Pages", "data-pdf-left": "", children: leftPanel }) : null,
|
|
1081
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("main", { className: "relative flex min-w-0 flex-1 flex-col", children: [
|
|
1082
|
+
findOpen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute right-4 top-2 z-20", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_RenderedFindBar.RenderedFindBar, { rootRef: scrollerRef, onClose: () => setFindOpen(false), label: "Find in document", focusRequest: findFocus, compact: true }) }) : null,
|
|
1083
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref: scrollerRef, tabIndex: 0, "data-pdf-scroller": "", className: "min-h-0 flex-1 overflow-auto bg-muted outline-none", children: viewer }),
|
|
1084
|
+
read ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute right-3 top-3 z-20", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls4.Button, { variant: "outline", icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons2.XIcon, {}), onClick: () => setRead(false), children: "Exit reading mode" }) }) : null
|
|
1085
|
+
] }),
|
|
1086
|
+
showChrome && !compact && rightOpen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("aside", { className: "w-72 shrink-0 border-l border-border bg-background", "aria-label": "Properties", "data-pdf-right": "", children: rightPanel }) : null,
|
|
1087
|
+
pop === "all-tools" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { role: "menu", "aria-label": "All tools", className: "absolute left-12 top-2 z-30 w-72 rounded-lg border border-border bg-popover p-1 shadow-lg", "data-pdf-all-tools": "", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ToolRows, { tool, onPick: (id) => {
|
|
1088
|
+
setPop(null);
|
|
1089
|
+
chooseTool(id);
|
|
1090
|
+
} }) }) : null,
|
|
1091
|
+
pop === "print" && host.print ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: (0, import_cn.cn)(popCard, "w-72"), role: "dialog", "aria-label": "Print", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-3", children: [
|
|
1092
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls4.SegmentedControl, { fill: true, value: printMode, onValueChange: setPrintMode, data: [{ value: "all", label: "All pages" }, { value: "pages", label: "Pages" }], "aria-label": "What to print" }),
|
|
1093
|
+
printMode === "pages" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls4.PageRangeInput, { value: printRange, onChange: (t) => setPrintRange(t), pageCount }) : null,
|
|
1094
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1095
|
+
import_controls4.Button,
|
|
1096
|
+
{
|
|
1097
|
+
variant: "primary",
|
|
1098
|
+
disabled: busy || printMode === "pages" && !(0, import_controls4.parsePageRanges)(printRange, pageCount).valid,
|
|
1099
|
+
onClick: () => {
|
|
1100
|
+
const pages = printMode === "pages" ? (0, import_controls4.parsePageRanges)(printRange, pageCount).pages : null;
|
|
1101
|
+
setBusy(true);
|
|
1102
|
+
host.print(pages).then(() => setPop(null), fail).finally(() => setBusy(false));
|
|
1103
|
+
},
|
|
1104
|
+
children: "Print"
|
|
1105
|
+
}
|
|
1106
|
+
)
|
|
1107
|
+
] }) }) : null,
|
|
1108
|
+
pop === "shortcuts" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: (0, import_cn.cn)(popCard, "max-h-[70%] w-80 overflow-y-auto"), role: "dialog", "aria-label": "Keyboard shortcuts", "data-pdf-shortcuts": "", children: ["Edit", "View", "Pages", "Tools"].map((section) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mb-3 last:mb-0", children: [
|
|
1109
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mb-1 text-xs font-medium text-muted-foreground", children: section }),
|
|
1110
|
+
PDF_SHORTCUTS.filter((s) => s.section === section).map((s) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-between gap-2 py-0.5 text-xs", children: [
|
|
1111
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: s.label }),
|
|
1112
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("kbd", { className: "rounded border border-border px-1 text-muted-foreground", children: formatKeys(s.keys, isMac()) })
|
|
1113
|
+
] }, s.action))
|
|
1114
|
+
] }, section)) }) : null,
|
|
1115
|
+
compact && sheet ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
1116
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", "aria-label": "Close", className: "absolute inset-0 z-30 bg-background/60", onClick: () => setSheet(null) }),
|
|
1117
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute inset-x-0 bottom-0 z-40 flex max-h-[75%] flex-col rounded-t-xl border-t border-border bg-background", role: "dialog", "aria-label": sheet === "pages" ? "Pages" : "Tools", children: sheet === "pages" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "h-[60vh] min-h-0", children: leftPanel }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex min-h-0 flex-col overflow-y-auto", children: [
|
|
1118
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "p-2", role: "menu", "aria-label": "Tools", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ToolRows, { tool, onPick: chooseTool }) }),
|
|
1119
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "border-t border-border", children: rightPanel })
|
|
1120
|
+
] }) })
|
|
1121
|
+
] }) : null
|
|
1122
|
+
] })
|
|
1123
|
+
]
|
|
1124
|
+
}
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
//# sourceMappingURL=pdf.cjs.map
|