mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 08:46:55 +08:00
1 parent
dfd4f733f9
commit
7bc31b6b84
6 files changed
+293
-97
No files matched your search
+34
-7
@@ -2961,6 +2961,8 @@ async function mountDocumentTab(tab) {
|
||||
|
||||
async function releaseDocumentTab(tab) {
|
||||
if (tab.closed) return;
|
||||
const clipboard = workspace.objectClipboard;
|
||||
if (clipboard?.tab === tab && !await prepareObjectClipboard(clipboard, tab) && workspace.objectClipboard === clipboard) workspace.objectClipboard = null;
|
||||
tab.closed = true;
|
||||
tab.print?.destroy();
|
||||
tab.print = null;
|
||||
@@ -6574,7 +6576,9 @@ function completePDF(tab) {
|
||||
throw new DOMException("文档已关闭", "AbortError");
|
||||
})();
|
||||
task.promise.catch(err => {
|
||||
if (current() && err.name !== "AbortError") {
|
||||
if (!current()) return;
|
||||
tab.pdfTask = null;
|
||||
if (err.name !== "AbortError") {
|
||||
tab.status = err.message;
|
||||
if (isForegroundDocument(tab)) showError(err, false);
|
||||
}
|
||||
@@ -10490,7 +10494,7 @@ function copyEditorSelection(event) {
|
||||
return true;
|
||||
}
|
||||
const index = items[0].index, ids = items.map(item => item.id);
|
||||
const clipboard = { token: crypto.randomUUID(), source: `${tab.id}:${state.wasmSeq}:${state.doc.documentIndex || 0}:${state.doc.documentVersion || ""}`, page: state.doc.pages[index].id, bounds: selectionBounds(items), x: 0, y: 0, cut };
|
||||
const clipboard = { tab, token: crypto.randomUUID(), source: `${tab.id}:${state.wasmSeq}:${state.doc.documentIndex || 0}:${state.doc.documentVersion || ""}`, page: state.doc.pages[index].id, bounds: selectionBounds(items), x: 0, y: 0, cut };
|
||||
if (canvasEditor.nudge) {
|
||||
clipboard.bounds.x += canvasEditor.nudge.x;
|
||||
clipboard.bounds.y += canvasEditor.nudge.y;
|
||||
@@ -10499,11 +10503,14 @@ function copyEditorSelection(event) {
|
||||
event.clipboardData.setData("text/plain", items.map(item => item.text || "").filter(Boolean).join("\n"));
|
||||
event.preventDefault();
|
||||
state.objectClipboardToken = clipboard.token;
|
||||
const previous = workspace.objectClipboard;
|
||||
workspace.objectClipboard = clipboard;
|
||||
if (previous?.tab) previous.ready.then(() => {
|
||||
if (previous.tab && !previous.tab.closed) return callTabWASM(previous.tab, "ofdgoDiscardObjects", previous.token);
|
||||
}).catch(() => {});
|
||||
const capture = () => {
|
||||
const openSeq = state.openSeq, revision = state.editorInfo.revision;
|
||||
return callTabWASM(tab, "ofdgoCaptureObjects", index, ids, clipboard.token).then(async result => {
|
||||
clipboard.data = result.bytes;
|
||||
return callTabWASM(tab, "ofdgoCaptureObjects", index, ids, clipboard.token).then(async () => {
|
||||
clipboard.cut = false;
|
||||
if (cut && isForegroundDocument(tab) && openSeq === state.openSeq && revision === state.editorInfo?.revision && index === state.pageIndex
|
||||
&& state.objectClipboardToken === clipboard.token && workspace.objectClipboard === clipboard
|
||||
@@ -10520,6 +10527,24 @@ function copyEditorSelection(event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
async function prepareObjectClipboard(clipboard, owner = documentTab()) {
|
||||
if (!await clipboard.ready) return false;
|
||||
if (clipboard.data) return true;
|
||||
if (!clipboard.portable) {
|
||||
const tab = clipboard.tab;
|
||||
clipboard.portable = callTabWASM(tab, "ofdgoExportObjects", clipboard.token).then(result => {
|
||||
clipboard.data = result.bytes;
|
||||
clipboard.tab = null;
|
||||
return true;
|
||||
}, err => {
|
||||
clipboard.portable = null;
|
||||
if (workspace.objectClipboard === clipboard && isForegroundDocument(owner)) showError(err, false);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return clipboard.portable;
|
||||
}
|
||||
|
||||
function visiblePageBounds(index) {
|
||||
const page = state.doc.pages[index], surface = pageView.pageShell(index)?.querySelector(".page-surface");
|
||||
const full = { x: 0, y: 0, width: page.width, height: page.height };
|
||||
@@ -10548,8 +10573,10 @@ async function pasteEditorContent(event) {
|
||||
setStatus("复制内容已失效,请重新复制");
|
||||
return;
|
||||
}
|
||||
const openSeq = state.openSeq;
|
||||
if (!await clipboard.ready || !isForegroundDocument(tab) || openSeq !== state.openSeq || index !== state.pageIndex || workspace.objectClipboard !== clipboard) return;
|
||||
const openSeq = state.openSeq, identity = `${state.wasmSeq}:${state.doc.documentIndex || 0}:${state.doc.documentVersion || ""}`;
|
||||
const local = state.objectClipboardToken === clipboard.token;
|
||||
if (!await (local ? clipboard.ready : prepareObjectClipboard(clipboard, tab)) || !isForegroundDocument(tab) || openSeq !== state.openSeq || index !== state.pageIndex || workspace.objectClipboard !== clipboard) return;
|
||||
if (identity !== `${state.wasmSeq}:${state.doc.documentIndex || 0}:${state.doc.documentVersion || ""}` || local && state.objectClipboardToken !== clipboard.token) return;
|
||||
if (!state.editing || formDialogOpen() || document.body.hasAttribute("data-busy") || canvasEditor.input || canvasEditor.crop || canvasEditor.drag || canvasEditor.nudge || canvasEditor.nudgeCommit) return;
|
||||
if (!pageCan("insert")) return;
|
||||
const visible = visiblePageBounds(index), bounds = clipboard.bounds;
|
||||
@@ -10562,7 +10589,7 @@ async function pasteEditorContent(event) {
|
||||
offset[axis] = Math.max(visible[axis] + margin, Math.min(position, visible[axis] + visible[size] - margin - bounds[size])) - bounds[axis];
|
||||
}
|
||||
}
|
||||
if (await changeDocument(state.objectClipboardToken === clipboard.token ? "ofdgoPasteObjects" : "ofdgoImportObjects", null, index, state.objectClipboardToken === clipboard.token ? token : clipboard.data, offset.x, offset.y)) {
|
||||
if (await changeDocument(local ? "ofdgoPasteObjects" : "ofdgoImportObjects", null, index, local ? token : clipboard.data, offset.x, offset.y)) {
|
||||
clipboard.target = target;
|
||||
clipboard.targetPage = page.id;
|
||||
clipboard.x = offset.x;
|
||||
|
||||
+60
-25
@@ -395,28 +395,47 @@ function resizeCorner(node, event) {
|
||||
return corner;
|
||||
}
|
||||
|
||||
function alignmentSnap(box, targets, tolerance) {
|
||||
const lines = [];
|
||||
const offset = { x: 0, y: 0 };
|
||||
for (const [axis, size, cross, span] of [["x", "width", "y", "height"], ["y", "height", "x", "width"]]) {
|
||||
let distance = Infinity, line;
|
||||
for (const target of targets) {
|
||||
for (const anchor of [0, 0.5, 1]) {
|
||||
const position = target[axis] + target[size] * anchor;
|
||||
for (const edge of [0, 0.5, 1]) {
|
||||
const delta = position - box[axis] - box[size] * edge;
|
||||
if (Math.abs(delta) > tolerance[axis] || Math.abs(delta) >= distance) continue;
|
||||
distance = Math.abs(delta);
|
||||
export class AlignmentIndex {
|
||||
constructor(targets) {
|
||||
for (const [axis, size] of [["x", "width"], ["y", "height"]]) {
|
||||
this[axis] = targets.flatMap((target, index) => [0, 0.5, 1].map((anchor, order) => ({
|
||||
position: target[axis] + target[size] * anchor, target, order: index * 3 + order,
|
||||
}))).sort((a, b) => a.position - b.position || a.order - b.order);
|
||||
}
|
||||
}
|
||||
|
||||
snap(box, tolerance) {
|
||||
const lines = [], offset = { x: 0, y: 0 };
|
||||
for (const [axis, size, cross, span] of [["x", "width", "y", "height"], ["y", "height", "x", "width"]]) {
|
||||
const anchors = this[axis];
|
||||
let best, distance = Infinity, rank = Infinity;
|
||||
for (let edge = 0; edge < 3; edge++) {
|
||||
const value = box[axis] + box[size] * edge / 2;
|
||||
let low = 0, high = anchors.length;
|
||||
while (low < high) {
|
||||
const middle = (low + high) >> 1;
|
||||
if (anchors[middle].position < value - tolerance[axis]) low = middle + 1;
|
||||
else high = middle;
|
||||
}
|
||||
for (let i = low; i < anchors.length && anchors[i].position <= value + tolerance[axis]; i++) {
|
||||
const anchor = anchors[i], delta = anchor.position - box[axis] - box[size] * edge / 2;
|
||||
const next = Math.abs(delta), order = anchor.order * 3 + edge;
|
||||
if (next > tolerance[axis] || next > distance || next === distance && order >= rank) continue;
|
||||
best = anchor;
|
||||
distance = next;
|
||||
rank = order;
|
||||
offset[axis] = delta;
|
||||
const from = Math.min(box[cross], target[cross]);
|
||||
const to = Math.max(box[cross] + box[span], target[cross] + target[span]);
|
||||
line = axis === "x" ? `M${position} ${from}V${to}` : `M${from} ${position}H${to}`;
|
||||
}
|
||||
}
|
||||
if (best) {
|
||||
const { position, target } = best;
|
||||
const from = Math.min(box[cross], target[cross]);
|
||||
const to = Math.max(box[cross] + box[span], target[cross] + target[span]);
|
||||
lines.push(axis === "x" ? `M${position} ${from}V${to}` : `M${from} ${position}H${to}`);
|
||||
}
|
||||
}
|
||||
if (line) lines.push(line);
|
||||
return { ...offset, path: lines.join("") };
|
||||
}
|
||||
return { ...offset, path: lines.join("") };
|
||||
}
|
||||
|
||||
function transformedBox(box, matrix) {
|
||||
@@ -548,6 +567,23 @@ export class ObjectIndex {
|
||||
this.root = build([...items]);
|
||||
}
|
||||
|
||||
queryBox(box) {
|
||||
const result = [];
|
||||
const intersects = node => node.x <= box.x + box.width && node.x + node.width >= box.x
|
||||
&& node.y <= box.y + box.height && node.y + node.height >= box.y;
|
||||
const visit = node => {
|
||||
if (!node || !intersects(node)) return;
|
||||
if (node.items) {
|
||||
for (const item of node.items) {
|
||||
if (!item.parent && item.x >= box.x && item.x + item.width <= box.x + box.width
|
||||
&& item.y >= box.y && item.y + item.height <= box.y + box.height) result.push(item);
|
||||
}
|
||||
} else { visit(node.left); visit(node.right); }
|
||||
};
|
||||
visit(this.root);
|
||||
return result.sort((a, b) => a.order - b.order);
|
||||
}
|
||||
|
||||
query(point, tolerance = 0) {
|
||||
const result = [];
|
||||
const contains = box => point.x >= box.x - tolerance && point.x <= box.x + box.width + tolerance
|
||||
@@ -950,9 +986,10 @@ export class CanvasEditor {
|
||||
};
|
||||
if (!this.drag.corner) {
|
||||
const selected = this.items();
|
||||
this.drag.targets = [...item.surface.querySelectorAll(".edit-object")]
|
||||
const targets = [...item.surface.querySelectorAll(".edit-object")]
|
||||
.map(node => this.nodes.get(node)).filter(member => !selected.some(item => item === member || item.id.startsWith(member.id + "/") || member.id.startsWith(item.id + "/")));
|
||||
this.drag.targets.push({ x: 0, y: 0, ...item.page });
|
||||
targets.push({ x: 0, y: 0, ...item.page });
|
||||
this.drag.alignment = new AlignmentIndex(targets);
|
||||
}
|
||||
if (lineShape(item.shape) && this.drag.corner) {
|
||||
Object.assign(this.drag, this.createPreview(item.surface, item.page, item.shape, {
|
||||
@@ -1133,7 +1170,7 @@ export class CanvasEditor {
|
||||
|
||||
snap(drag, bypass = false) {
|
||||
const { item, rect, rotation, change } = drag;
|
||||
const { x, y, path } = bypass ? { x: 0, y: 0, path: "" } : alignmentSnap({ ...item, x: item.x + change.x, y: item.y + change.y }, drag.targets, {
|
||||
const { x, y, path } = bypass ? { x: 0, y: 0, path: "" } : drag.alignment.snap({ ...item, x: item.x + change.x, y: item.y + change.y }, {
|
||||
x: drag.axis === "y" ? 0 : 4 * item.page.width / (rotation % 180 ? rect.height : rect.width),
|
||||
y: drag.axis === "x" ? 0 : 4 * item.page.height / (rotation % 180 ? rect.width : rect.height),
|
||||
});
|
||||
@@ -1362,11 +1399,9 @@ export class CanvasEditor {
|
||||
paintShape(drag.node, "rectangle", box);
|
||||
if (Math.hypot(event.clientX-drag.clientX, event.clientY-drag.clientY) < 3) return;
|
||||
const items = [...drag.base];
|
||||
for (const node of drag.surface.querySelectorAll(".edit-object")) {
|
||||
const item = this.nodes.get(node);
|
||||
if (!item.parent && !items.includes(item) && item.x >= box.x && item.x+item.width <= box.x+box.width && item.y >= box.y && item.y+item.height <= box.y+box.height) {
|
||||
items.push(item);
|
||||
}
|
||||
const selected = new Set(items);
|
||||
for (const item of drag.page.hitIndex.queryBox(box)) {
|
||||
if (!selected.has(item)) items.push(item);
|
||||
}
|
||||
this.setSelection(items);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user