mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:59:27 +08:00
Compare commits
12
Commits
4320b53d4a
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94157b10c3 | ||
|
|
aebcda51a3 | ||
|
|
19084bb40b | ||
|
|
545b697310 | ||
|
|
1a25472b43 | ||
|
|
af80fcc9ba | ||
|
|
838a769c5b | ||
|
|
5b4f2472f7 | ||
|
|
fb51fd3b07 | ||
|
|
80e548f93e | ||
|
|
9c3a04b7eb | ||
|
|
9bb6c9141a |
No files matched your search
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
# 基础镜像
|
# 基础镜像
|
||||||
FROM alpine:3.24.2
|
FROM alpine:3.24.3
|
||||||
|
|
||||||
# 作者信息
|
# 作者信息
|
||||||
LABEL authors="肖其顿 (XIAO QI DUN)"
|
LABEL authors="肖其顿 (XIAO QI DUN)"
|
||||||
|
|||||||
+69
-19
@@ -110,6 +110,7 @@ function createDocumentState() {
|
|||||||
styleOriginal: null,
|
styleOriginal: null,
|
||||||
objectClipboardToken: null,
|
objectClipboardToken: null,
|
||||||
styleClipboard: null,
|
styleClipboard: null,
|
||||||
|
objectDistribution: null,
|
||||||
outlineExpanded: new Map(),
|
outlineExpanded: new Map(),
|
||||||
textFonts: [],
|
textFonts: [],
|
||||||
textFontID: null,
|
textFontID: null,
|
||||||
@@ -683,9 +684,12 @@ function createPrintDialog(tab) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openPrintPanel() {
|
async function openPrintPanel() {
|
||||||
const tab = documentTab();
|
const tab = documentTab(), content = documentContentKey(state);
|
||||||
if (!tab.state.doc || !tab.state.ready || document.body.hasAttribute("data-busy") || formDialogOpen()) return;
|
if (!tab.state.doc || !tab.state.ready || document.body.hasAttribute("data-busy") || formDialogOpen()) return;
|
||||||
|
const current = () => isForegroundDocument(tab) && content === documentContentKey(state);
|
||||||
|
if (!await canvasEditor.commitNudge() || !current() || !await canvasEditor.commitText() || !current()
|
||||||
|
|| !await canvasEditor.commitCrop() || !current() || document.body.hasAttribute("data-busy") || formDialogOpen()) return;
|
||||||
tab.print ||= createPrintDialog(tab);
|
tab.print ||= createPrintDialog(tab);
|
||||||
tab.print.open({tab, document:documentEntryKey(tab.state.doc.documents?.[tab.state.doc.documentIndex || 0]), call:(name, ...args) => callTabWASM(tab, name, ...args), page:tab.state.pageIndex,
|
tab.print.open({tab, document:documentEntryKey(tab.state.doc.documents?.[tab.state.doc.documentIndex || 0]), call:(name, ...args) => callTabWASM(tab, name, ...args), page:tab.state.pageIndex,
|
||||||
selection:tab.state.pageSelection.size ? selectedPageIndexes() : [], annotations:tab.state.renderAnnotations,
|
selection:tab.state.pageSelection.size ? selectedPageIndexes() : [], annotations:tab.state.renderAnnotations,
|
||||||
@@ -1260,7 +1264,7 @@ el.objectAlign.addEventListener("change", () => {
|
|||||||
el.objectDistribute.addEventListener("change", () => {
|
el.objectDistribute.addEventListener("change", () => {
|
||||||
const axis = el.objectDistribute.value;
|
const axis = el.objectDistribute.value;
|
||||||
el.objectDistribute.value = "";
|
el.objectDistribute.value = "";
|
||||||
if (axis && canvasEditor.items().length >= 3) {
|
if (axis && !el.objectDistribute.disabled && state.objectDistribution?.axes?.includes(axis)) {
|
||||||
return changeDocument("ofdgoDistributeObjects", canvasEditor.selected, axis);
|
return changeDocument("ofdgoDistributeObjects", canvasEditor.selected, axis);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1307,7 +1311,10 @@ for (const [button, tool] of [[el.drawLineButton, "line"], [el.drawRectangleButt
|
|||||||
}
|
}
|
||||||
editorClick(el.eraseButton, () => toggleDrawingTool(el.eraseMode.value));
|
editorClick(el.eraseButton, () => toggleDrawingTool(el.eraseMode.value));
|
||||||
el.eraseMode.addEventListener("change", () => {
|
el.eraseMode.addEventListener("change", () => {
|
||||||
if (canvasEditor.tool.startsWith("erase-")) canvasEditor.setTool(el.eraseMode.value);
|
if (canvasEditor.tool.startsWith("erase-")) {
|
||||||
|
canvasEditor.setTool(el.eraseMode.value);
|
||||||
|
canvasEditor.focus();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
for (const input of [el.shapeFill, el.shapeStroke]) {
|
for (const input of [el.shapeFill, el.shapeStroke]) {
|
||||||
input.addEventListener("change", changeShapeStyle);
|
input.addEventListener("change", changeShapeStyle);
|
||||||
@@ -1407,9 +1414,11 @@ for (const [button, ungroup] of [[el.groupObjectsButton, false], [el.ungroupObje
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
el.arrowTool.addEventListener("change", async () => {
|
el.arrowTool.addEventListener("change", async () => {
|
||||||
|
const tab = documentTab(), content = documentContentKey(state);
|
||||||
|
const current = () => isForegroundDocument(tab) && state.editing && content === documentContentKey(state);
|
||||||
const tool = el.arrowTool.value;
|
const tool = el.arrowTool.value;
|
||||||
el.arrowTool.value = "";
|
el.arrowTool.value = "";
|
||||||
if (await canvasEditor.commitText() && await canvasEditor.commitCrop()) {
|
if (await canvasEditor.commitText() && current() && await canvasEditor.commitCrop() && current()) {
|
||||||
const item = canvasEditor.selected;
|
const item = canvasEditor.selected;
|
||||||
if (lineShape(item?.shape)) {
|
if (lineShape(item?.shape)) {
|
||||||
const box = item.geometry;
|
const box = item.geometry;
|
||||||
@@ -1420,6 +1429,7 @@ el.arrowTool.addEventListener("change", async () => {
|
|||||||
state.selectObjects = true;
|
state.selectObjects = true;
|
||||||
canvasEditor.setTool(tool);
|
canvasEditor.setTool(tool);
|
||||||
updateControls();
|
updateControls();
|
||||||
|
canvasEditor.focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
el.copyStyleButton.addEventListener("click", async () => {
|
el.copyStyleButton.addEventListener("click", async () => {
|
||||||
@@ -1807,6 +1817,7 @@ el.annotationButton.addEventListener("click", toggleAnnotations);
|
|||||||
document.addEventListener("copy", copySelection);
|
document.addEventListener("copy", copySelection);
|
||||||
document.addEventListener("cut", copySelection);
|
document.addEventListener("cut", copySelection);
|
||||||
el.viewerPanel.addEventListener("paste", pasteEditorContent);
|
el.viewerPanel.addEventListener("paste", pasteEditorContent);
|
||||||
|
el.editorTools.addEventListener("paste", pasteEditorContent);
|
||||||
document.addEventListener("selectionchange", syncSelection);
|
document.addEventListener("selectionchange", syncSelection);
|
||||||
el.exportFormat.addEventListener("change", () => updateDPIControl());
|
el.exportFormat.addEventListener("change", () => updateDPIControl());
|
||||||
el.outputCompression.addEventListener("change", () => {
|
el.outputCompression.addEventListener("change", () => {
|
||||||
@@ -1933,6 +1944,11 @@ el.documentNewTab.addEventListener("click", openEmptyDocumentTab);
|
|||||||
updateSidebarState();
|
updateSidebarState();
|
||||||
boot();
|
boot();
|
||||||
|
|
||||||
|
function editorShortcutTarget(target) {
|
||||||
|
const field = target.closest?.("input, textarea, select, [contenteditable]");
|
||||||
|
return !field || el.editorTools.contains(field) && field.matches('select, input[type="checkbox"], input[type="radio"], input[type="color"], input[type="range"]');
|
||||||
|
}
|
||||||
|
|
||||||
function handleKeyDown(event) {
|
function handleKeyDown(event) {
|
||||||
if (sealPreview) {
|
if (sealPreview) {
|
||||||
if (event.key === "Escape") { event.preventDefault(); finishSealPosition(); }
|
if (event.key === "Escape") { event.preventDefault(); finishSealPosition(); }
|
||||||
@@ -1965,10 +1981,11 @@ function handleKeyDown(event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((event.ctrlKey || event.metaKey) && !event.shiftKey && key.toLowerCase() === "a" && state.doc
|
if ((event.ctrlKey || event.metaKey) && !event.shiftKey && key.toLowerCase() === "a" && state.doc
|
||||||
&& el.viewerPanel.contains(target) && !target.closest("input, textarea, select, [contenteditable]")) {
|
&& (el.viewerPanel.contains(target) || state.editing && el.editorTools.contains(target)) && editorShortcutTarget(target)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!document.body.hasAttribute("data-busy") && !formDialogOpen()) {
|
if (!document.body.hasAttribute("data-busy") && !formDialogOpen()) {
|
||||||
selectDocumentText();
|
if (state.editing) canvasEditor.selectAll(state.pageIndex);
|
||||||
|
else selectDocumentText();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1994,7 +2011,7 @@ function handleKeyDown(event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (event.ctrlKey || event.metaKey) {
|
if (event.ctrlKey || event.metaKey) {
|
||||||
if (state.editing && !target.closest("input, textarea, select, [contenteditable]")
|
if (state.editing && editorShortcutTarget(target)
|
||||||
&& (key.toLowerCase() === "z" || (!event.shiftKey && key.toLowerCase() === "y"))) {
|
&& (key.toLowerCase() === "z" || (!event.shiftKey && key.toLowerCase() === "y"))) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
(key.toLowerCase() === "y" || event.shiftKey ? el.redoButton : el.undoButton).click();
|
(key.toLowerCase() === "y" || event.shiftKey ? el.redoButton : el.undoButton).click();
|
||||||
@@ -2010,6 +2027,11 @@ function handleKeyDown(event) {
|
|||||||
if (!state.doc) {
|
if (!state.doc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (state.editing && !event.shiftKey && el.editorTools.contains(target) && editorShortcutTarget(target)
|
||||||
|
&& (key === "Delete" || key === "F2")) {
|
||||||
|
canvasEditor.keyDown(event, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (key === "Escape" && !event.shiftKey && state.documentSelection && el.viewerPanel.contains(target)
|
if (key === "Escape" && !event.shiftKey && state.documentSelection && el.viewerPanel.contains(target)
|
||||||
&& !target.closest("input, textarea, select, [contenteditable]")) {
|
&& !target.closest("input, textarea, select, [contenteditable]")) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -2019,11 +2041,7 @@ function handleKeyDown(event) {
|
|||||||
}
|
}
|
||||||
if (key === "F3") {
|
if (key === "F3") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (state.searchMatches.length) {
|
focusSearch(event.shiftKey ? -1 : 1);
|
||||||
selectSearchMatch(event.shiftKey ? Math.max(0, state.searchIndex) - 1 : state.searchIndex + 1);
|
|
||||||
} else {
|
|
||||||
focusSearch();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (key === "Escape" && !event.shiftKey && !el.searchPanel.hidden && (el.pageListPanel.contains(target) || el.viewerPanel.contains(target))) {
|
if (key === "Escape" && !event.shiftKey && !el.searchPanel.hidden && (el.pageListPanel.contains(target) || el.viewerPanel.contains(target))) {
|
||||||
@@ -10851,8 +10869,9 @@ async function selectDocumentText() {
|
|||||||
|
|
||||||
function copyEditorSelection(event) {
|
function copyEditorSelection(event) {
|
||||||
const tab = documentTab(), state = tab.state;
|
const tab = documentTab(), state = tab.state;
|
||||||
if (!state.editing || !canvasEditor.enabled || !canvasEditor.selected || !el.viewerPanel.contains(event.target)
|
if (!state.editing || !canvasEditor.enabled || !canvasEditor.selected || !editorShortcutTarget(event.target)
|
||||||
|| canvasEditor.input || canvasEditor.crop || canvasEditor.drag || document.body.hasAttribute("data-busy")) return false;
|
|| !el.viewerPanel.contains(event.target) && !el.editorTools.contains(event.target)
|
||||||
|
|| canvasEditor.input || canvasEditor.crop || canvasEditor.drag || document.body.hasAttribute("data-busy") || formDialogOpen()) return false;
|
||||||
const items = canvasEditor.items().slice().sort((a, b) => a.order - b.order);
|
const items = canvasEditor.items().slice().sort((a, b) => a.order - b.order);
|
||||||
const cut = event.type === "cut";
|
const cut = event.type === "cut";
|
||||||
if (!canEditObject(canvasEditor.selected, "copy") || cut && !canEditObject(canvasEditor.selected, "delete")) {
|
if (!canEditObject(canvasEditor.selected, "copy") || cut && !canEditObject(canvasEditor.selected, "delete")) {
|
||||||
@@ -10928,7 +10947,7 @@ function visiblePageBounds(index) {
|
|||||||
|
|
||||||
async function pasteEditorContent(event) {
|
async function pasteEditorContent(event) {
|
||||||
const tab = documentTab(), state = tab.state;
|
const tab = documentTab(), state = tab.state;
|
||||||
if (!state.editing || event.target.closest?.("input, textarea, select, [contenteditable]") || formDialogOpen()) return;
|
if (!state.editing || !editorShortcutTarget(event.target) || formDialogOpen()) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const token = event.clipboardData.getData(OBJECT_CLIPBOARD_TYPE);
|
const token = event.clipboardData.getData(OBJECT_CLIPBOARD_TYPE);
|
||||||
const files = Array.from(event.clipboardData.files);
|
const files = Array.from(event.clipboardData.files);
|
||||||
@@ -10997,7 +11016,7 @@ async function pasteEditorContent(event) {
|
|||||||
|
|
||||||
function copySelection(event) {
|
function copySelection(event) {
|
||||||
if (sealPreview) return;
|
if (sealPreview) return;
|
||||||
if (event.target.closest?.("input, textarea, [contenteditable]")) {
|
if (!editorShortcutTarget(event.target)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const selection = document.getSelection();
|
const selection = document.getSelection();
|
||||||
@@ -11042,7 +11061,14 @@ function copySelection(event) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusSearch() {
|
async function focusSearch(direction = 0) {
|
||||||
|
if (canvasEditor.input || canvasEditor.crop || canvasEditor.nudge || canvasEditor.nudgeCommit) {
|
||||||
|
const tab = documentTab(), content = documentContentKey(state);
|
||||||
|
const current = () => isForegroundDocument(tab) && content === documentContentKey(state);
|
||||||
|
if (!await canvasEditor.commitNudge() || !current() || !await canvasEditor.commitText() || !current()
|
||||||
|
|| !await canvasEditor.commitCrop() || !current() || document.body.hasAttribute("data-busy") || formDialogOpen()) return;
|
||||||
|
}
|
||||||
|
if (direction && state.searchMatches.length) return selectSearchMatch((direction < 0 ? Math.max(0, state.searchIndex) : state.searchIndex) + direction);
|
||||||
if (!state.showPages) {
|
if (!state.showPages) {
|
||||||
toggleSidebar("pages");
|
toggleSidebar("pages");
|
||||||
}
|
}
|
||||||
@@ -12746,6 +12772,30 @@ function updateEditorTools() {
|
|||||||
updateObjectControls(canvasEditor.selected);
|
updateObjectControls(canvasEditor.selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateObjectDistribution(item, disabled) {
|
||||||
|
const tab = documentTab(), state = tab.state;
|
||||||
|
const ids = item?.items?.slice().sort((a, b) => a.order - b.order).map(member => member.id) || [];
|
||||||
|
if (disabled || ids.length < 3) state.objectDistribution = null;
|
||||||
|
else {
|
||||||
|
const { openSeq, wasmSeq } = state, revision = state.editorInfo?.revision;
|
||||||
|
const key = JSON.stringify([openSeq, wasmSeq, revision, item.index, ids]);
|
||||||
|
if (state.objectDistribution?.key !== key) {
|
||||||
|
const request = state.objectDistribution = { key, axes: [] };
|
||||||
|
const current = () => !tab.closed && state.objectDistribution === request && state.openSeq === openSeq && state.wasmSeq === wasmSeq && state.editorInfo?.revision === revision;
|
||||||
|
callTabWASM(tab, "ofdgoDistributionAxes", item.index, ids).then(axes => {
|
||||||
|
if (!current()) return;
|
||||||
|
request.axes = axes;
|
||||||
|
if (isForegroundDocument(tab)) updateObjectControls(canvasEditor.selected);
|
||||||
|
}).catch(error => {
|
||||||
|
if (current() && isForegroundDocument(tab)) showError(error, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const axes = state.objectDistribution?.axes || [];
|
||||||
|
el.objectDistribute.disabled = disabled || !axes.length;
|
||||||
|
for (const option of el.objectDistribute.options) if (option.value) option.disabled = !axes.includes(option.value);
|
||||||
|
}
|
||||||
|
|
||||||
function updateObjectControls(item, reset = false) {
|
function updateObjectControls(item, reset = false) {
|
||||||
const members = item?.items || (item ? [item] : []);
|
const members = item?.items || (item ? [item] : []);
|
||||||
const disabled = !item || Boolean(item.draft) || !state.ready || state.exporting;
|
const disabled = !item || Boolean(item.draft) || !state.ready || state.exporting;
|
||||||
@@ -12763,7 +12813,7 @@ function updateObjectControls(item, reset = false) {
|
|||||||
el.cropImageButton.setAttribute("data-pressed", String(cropping));
|
el.cropImageButton.setAttribute("data-pressed", String(cropping));
|
||||||
el.resetCropButton.textContent = cropping ? "取消" : "还原";
|
el.resetCropButton.textContent = cropping ? "取消" : "还原";
|
||||||
el.resetCropButton.disabled = canEditObject(item, "resetCrop") ? disabled : el.cropImageButton.disabled || !cropping && (item.scoped ? !item.cropped : !item.imageBounds || ["x", "y", "width", "height"].every(key => Math.abs(item[key] - item.imageBounds[key]) < 1e-9));
|
el.resetCropButton.disabled = canEditObject(item, "resetCrop") ? disabled : el.cropImageButton.disabled || !cropping && (item.scoped ? !item.cropped : !item.imageBounds || ["x", "y", "width", "height"].every(key => Math.abs(item[key] - item.imageBounds[key]) < 1e-9));
|
||||||
el.objectDistribute.disabled = el.objectAlign.disabled || !item.items || item.items.length < 3;
|
updateObjectDistribution(item, el.objectAlign.disabled);
|
||||||
el.editObjectButton.disabled = disabled || (item.items ? !item.items.every(member => member.type === "TextObject" && !member.richText)
|
el.editObjectButton.disabled = disabled || (item.items ? !item.items.every(member => member.type === "TextObject" && !member.richText)
|
||||||
: !canEditObject(item, "children") && (item.type === "PathObject"
|
: !canEditObject(item, "children") && (item.type === "PathObject"
|
||||||
|| !(item.type === "TextObject" && canEditObject(item, "rewriteText")) && !canEditObject(item, item.type === "TextObject" ? "textContent" : item.type === "ImageObject" ? "replaceImage" : "update")));
|
|| !(item.type === "TextObject" && canEditObject(item, "rewriteText")) && !canEditObject(item, item.type === "TextObject" ? "textContent" : item.type === "ImageObject" ? "replaceImage" : "update")));
|
||||||
|
|||||||
@@ -1122,6 +1122,14 @@ export class CanvasEditor {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectAll(index) {
|
||||||
|
if (!this.enabled || this.input || this.crop || this.drag || this.nudge || this.nudgeCommit || this.options.busy()) return;
|
||||||
|
this.setTool("");
|
||||||
|
document.getSelection()?.removeAllRanges();
|
||||||
|
this.setSelection(this.pageItems(index));
|
||||||
|
this.focus();
|
||||||
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.closeText();
|
this.closeText();
|
||||||
this.closeCrop();
|
this.closeCrop();
|
||||||
@@ -1204,6 +1212,10 @@ export class CanvasEditor {
|
|||||||
rect: item.surface.getBoundingClientRect(), rotation: this.options.rotation(),
|
rect: item.surface.getBoundingClientRect(), rotation: this.options.rotation(),
|
||||||
clientX: event.clientX, clientY: event.clientY,
|
clientX: event.clientX, clientY: event.clientY,
|
||||||
};
|
};
|
||||||
|
const extent = this.drag.extent = document.createElement("div");
|
||||||
|
Object.assign(extent.style, { position: "absolute", left: "0", top: "0", pointerEvents: "none",
|
||||||
|
width: `${this.viewer.scrollWidth}px`, height: `${this.viewer.scrollHeight}px` });
|
||||||
|
this.viewer.append(extent);
|
||||||
if (!this.drag.corner) {
|
if (!this.drag.corner) {
|
||||||
const selected = this.items();
|
const selected = this.items();
|
||||||
const targets = [...item.surface.querySelectorAll(".edit-object")]
|
const targets = [...item.surface.querySelectorAll(".edit-object")]
|
||||||
@@ -1477,6 +1489,7 @@ export class CanvasEditor {
|
|||||||
}
|
}
|
||||||
if (drag.corner && drag.box) {
|
if (drag.corner && drag.box) {
|
||||||
this.drag = null;
|
this.drag = null;
|
||||||
|
drag.extent?.remove();
|
||||||
this.viewer.releasePointerCapture(drag.pointerID);
|
this.viewer.releasePointerCapture(drag.pointerID);
|
||||||
const frame = drag.item.textFrame;
|
const frame = drag.item.textFrame;
|
||||||
const geometry = drag.item.oriented?.box || drag.item.geometry || (frame ? { x: drag.item.x, width: textFrameBox(drag.item).width } : drag.item);
|
const geometry = drag.item.oriented?.box || drag.item.geometry || (frame ? { x: drag.item.x, width: textFrameBox(drag.item).width } : drag.item);
|
||||||
@@ -1490,6 +1503,7 @@ export class CanvasEditor {
|
|||||||
}
|
}
|
||||||
if (drag.change && (drag.change.x || drag.change.y || drag.change.scale !== 1)) {
|
if (drag.change && (drag.change.x || drag.change.y || drag.change.scale !== 1)) {
|
||||||
this.drag = null;
|
this.drag = null;
|
||||||
|
drag.extent?.remove();
|
||||||
drag.guides?.preview.remove();
|
drag.guides?.preview.remove();
|
||||||
this.viewer.releasePointerCapture(drag.pointerID);
|
this.viewer.releasePointerCapture(drag.pointerID);
|
||||||
Promise.resolve(this.options.onTransform(drag.item, drag.change)).finally(() => this.place(drag.item));
|
Promise.resolve(this.options.onTransform(drag.item, drag.change)).finally(() => this.place(drag.item));
|
||||||
@@ -1500,6 +1514,7 @@ export class CanvasEditor {
|
|||||||
const drag = this.drag;
|
const drag = this.drag;
|
||||||
this.drag = null;
|
this.drag = null;
|
||||||
if (drag) {
|
if (drag) {
|
||||||
|
drag.extent?.remove();
|
||||||
for (const item of drag.erased || []) item.node.classList.remove("erasing");
|
for (const item of drag.erased || []) item.node.classList.remove("erasing");
|
||||||
if (drag.crop) {
|
if (drag.crop) {
|
||||||
this.crop.box = drag.box;
|
this.crop.box = drag.box;
|
||||||
@@ -1741,8 +1756,8 @@ export class CanvasEditor {
|
|||||||
return this.nudgeCommit;
|
return this.nudgeCommit;
|
||||||
}
|
}
|
||||||
|
|
||||||
keyDown(event) {
|
keyDown(event, toolbar = false) {
|
||||||
if (event.defaultPrevented || event.target?.closest("input, textarea, select, [contenteditable]")) return;
|
if (event.defaultPrevented || !toolbar && event.target?.closest("input, textarea, select, [contenteditable]")) return;
|
||||||
if (this.crop && !this.options.busy()) {
|
if (this.crop && !this.options.busy()) {
|
||||||
if (event.key === "Escape" || event.key === "Enter") {
|
if (event.key === "Escape" || event.key === "Enter") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -1952,7 +1967,9 @@ export class CanvasEditor {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
input.addEventListener("keydown", (event) => {
|
input.addEventListener("keydown", (event) => {
|
||||||
if ((event.ctrlKey || event.metaKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === "s") return;
|
if (event.key === "F3" && !event.ctrlKey && !event.metaKey && !event.altKey) return;
|
||||||
|
if ((event.ctrlKey || event.metaKey) && !event.altKey && (["s", "w", "tab"].includes(event.key.toLowerCase())
|
||||||
|
|| !event.shiftKey && ["f", "o", "p"].includes(event.key.toLowerCase()))) return;
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (event.isComposing || editing.composing || editing.saving) {
|
if (event.isComposing || editing.composing || editing.saving) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ export class FontPicker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
keyDown(event) {
|
keyDown(event) {
|
||||||
if (event.isComposing) return;
|
if (event.isComposing || event.key === "Tab" && (event.ctrlKey || event.metaKey || event.altKey)) return;
|
||||||
if (!["Escape", "Enter", "ArrowDown", "ArrowUp", "Tab"].includes(event.key)) return;
|
if (!["Escape", "Enter", "ArrowDown", "ArrowUp", "Tab"].includes(event.key)) return;
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
|
|||||||
@@ -292,6 +292,15 @@ export function createPageView(context) {
|
|||||||
unmountPage(index) {
|
unmountPage(index) {
|
||||||
const shell = pageView.pageShell(index);
|
const shell = pageView.pageShell(index);
|
||||||
if (!shell) return;
|
if (!shell) return;
|
||||||
|
if (context.state.editing && shell.classList.contains("rendered")) {
|
||||||
|
const bounds = shell.getBoundingClientRect(), viewport = context.el.viewerPanel.getBoundingClientRect();
|
||||||
|
const margin = context.state.pageWindow?.overscan ?? 600;
|
||||||
|
const bottom = Math.max(bounds.bottom, bounds.top + shell.scrollHeight);
|
||||||
|
if (bottom >= viewport.top - margin && bounds.top <= viewport.bottom + margin) {
|
||||||
|
context.state.selectedPages.add(index);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
const selection = document.getSelection();
|
const selection = document.getSelection();
|
||||||
const range = !selection.isCollapsed && selection.rangeCount ? selection.getRangeAt(0) : null;
|
const range = !selection.isCollapsed && selection.rangeCount ? selection.getRangeAt(0) : null;
|
||||||
if (context.canvasEditor.input?.item.index === index || context.canvasEditor.crop?.item.index === index
|
if (context.canvasEditor.input?.item.index === index || context.canvasEditor.crop?.item.index === index
|
||||||
|
|||||||
@@ -5703,6 +5703,20 @@ func (e *businessEngine) orderObjects(args []platformValue) (any, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// distributionAxes 获取画布选区可用的等间距分布方向
|
||||||
|
// 入参: args 页面索引和画布对象标识数组
|
||||||
|
// 返回: any 可用方向, error 路径或度量错误
|
||||||
|
func (e *businessEngine) distributionAxes(args []platformValue) (any, error) {
|
||||||
|
if e.currentEditor == nil {
|
||||||
|
return nil, fmt.Errorf("no document is being edited")
|
||||||
|
}
|
||||||
|
paths, err := e.editorTargetPaths(e.stringsFromJS(args[1]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e.currentEditor.DistributionAxes(args[0].Int(), paths)
|
||||||
|
}
|
||||||
|
|
||||||
// distributeObjects 按指定轴等距分布选区
|
// distributeObjects 按指定轴等距分布选区
|
||||||
// 入参: args 页面索引、对象标识数组和分布轴
|
// 入参: args 页面索引、对象标识数组和分布轴
|
||||||
// 返回: any 文档信息, error 错误信息
|
// 返回: any 文档信息, error 错误信息
|
||||||
@@ -8159,6 +8173,7 @@ func (e *businessEngine) businessCommands() map[string]func([]platformValue) (an
|
|||||||
"ofdgoImportObjects": e.importObjects,
|
"ofdgoImportObjects": e.importObjects,
|
||||||
"ofdgoOrderObjects": e.orderObjects,
|
"ofdgoOrderObjects": e.orderObjects,
|
||||||
"ofdgoDistributeObjects": e.distributeObjects,
|
"ofdgoDistributeObjects": e.distributeObjects,
|
||||||
|
"ofdgoDistributionAxes": e.distributionAxes,
|
||||||
"ofdgoRotateObjects": e.rotateObjects,
|
"ofdgoRotateObjects": e.rotateObjects,
|
||||||
"ofdgoFlipObjects": e.flipObjects,
|
"ofdgoFlipObjects": e.flipObjects,
|
||||||
"ofdgoCropImage": e.cropImage,
|
"ofdgoCropImage": e.cropImage,
|
||||||
|
|||||||
@@ -808,6 +808,40 @@ func (e *Editor) ResizeTargets(page int, paths []ObjectPath, box Box) error {
|
|||||||
return e.TransformTargets(page, paths, Matrix{a: sx, d: sy, e: box.X - bounds.X*sx, f: box.Y - bounds.Y*sy})
|
return e.TransformTargets(page, paths, Matrix{a: sx, d: sy, e: box.X - bounds.X*sx, f: box.Y - bounds.Y*sy})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DistributionAxes 获取选区可保持位置顺序的等间距分布方向,不修改文档或历史
|
||||||
|
// 入参: page 页面索引, paths 按绘制顺序排列的完整路径
|
||||||
|
// 返回: []string 可用的horizontal和vertical方向, error 路径或度量错误
|
||||||
|
func (e *Editor) DistributionAxes(page int, paths []ObjectPath) ([]string, error) {
|
||||||
|
objects, annotations, groups, err := editorTargetGroups(paths)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var boxes []Box
|
||||||
|
var positions []editorObjectPosition
|
||||||
|
if len(groups) == 0 {
|
||||||
|
boxes, positions, _, err = e.selectionGeometry(page, objects, annotations)
|
||||||
|
} else {
|
||||||
|
boxes, _, _, err = e.targetGeometry(page, paths)
|
||||||
|
positions = make([]editorObjectPosition, len(paths))
|
||||||
|
for i := range positions {
|
||||||
|
positions[i].index = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
axes := []string{}
|
||||||
|
if len(boxes) < 3 {
|
||||||
|
return axes, nil
|
||||||
|
}
|
||||||
|
for _, axis := range []string{"horizontal", "vertical"} {
|
||||||
|
if _, err := editorDistribution(boxes, positions, axis); err == nil {
|
||||||
|
axes = append(axes, axis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return axes, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ArrangeTargets 对齐或等距分布跨容器选区,单选对齐页面
|
// ArrangeTargets 对齐或等距分布跨容器选区,单选对齐页面
|
||||||
// 入参: page 页面索引, paths 按绘制顺序排列的完整路径, distribute 是否分布, direction 对齐方向或分布轴
|
// 入参: page 页面索引, paths 按绘制顺序排列的完整路径, distribute 是否分布, direction 对齐方向或分布轴
|
||||||
// 返回: error 路径、方向或编辑错误
|
// 返回: error 路径、方向或编辑错误
|
||||||
|
|||||||
Reference in new issue
Block a user