mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
aec10e93a4
commit
bff3949027
4 files changed
+301
-39
No files matched your search
+44
-7
@@ -66,6 +66,15 @@
|
||||
<button id="insertImageButton" class="button" type="button" aria-label="添加图片">图片</button>
|
||||
<span>插入</span>
|
||||
</div>
|
||||
<div class="editor-group">
|
||||
<button id="addPageButton" class="button" type="button" aria-label="新增页面">新增</button>
|
||||
<button id="copyPageButton" class="button" type="button" aria-label="复制页面">复制</button>
|
||||
<button id="deletePageButton" class="button" type="button" aria-label="删除页面">删除</button>
|
||||
<button id="movePagePrevButton" class="button" type="button" aria-label="页面前移">前移</button>
|
||||
<button id="movePageNextButton" class="button" type="button" aria-label="页面后移">后移</button>
|
||||
<button id="pageSettingsButton" class="button" type="button" aria-label="页面设置" aria-haspopup="dialog" aria-controls="pagePanel">设置</button>
|
||||
<span>页面</span>
|
||||
</div>
|
||||
<div class="editor-group">
|
||||
<button id="saveButton" class="button" type="button" aria-label="保存OFD">保存</button>
|
||||
<button id="undoButton" class="button" type="button" disabled>撤销</button>
|
||||
@@ -79,13 +88,15 @@
|
||||
<label for="createName">名称</label>
|
||||
<input id="createName" type="text" value="未命名" required autofocus>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="createWidth">宽度</label>
|
||||
<div class="form-unit"><input id="createWidth" type="number" value="210" min="1" step="any" required><span>mm</span></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="createHeight">高度</label>
|
||||
<div class="form-unit"><input id="createHeight" type="number" value="297" min="1" step="any" required><span>mm</span></div>
|
||||
<div class="form-pair">
|
||||
<div class="form-row">
|
||||
<label for="createWidth">宽度</label>
|
||||
<div class="form-unit"><input id="createWidth" type="number" value="210" min="1" step="any" required><span>mm</span></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="createHeight">高度</label>
|
||||
<div class="form-unit"><input id="createHeight" type="number" value="297" min="1" step="any" required><span>mm</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<div id="createStatus" class="form-status" role="status"></div>
|
||||
@@ -94,6 +105,32 @@
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="pagePanel" class="form-dialog" aria-label="页面设置">
|
||||
<form id="pageForm">
|
||||
<div class="form-row">
|
||||
<span id="pageDirectionLabel">方向</span>
|
||||
<div class="form-options" role="radiogroup" aria-labelledby="pageDirectionLabel">
|
||||
<label><input id="pagePortrait" type="radio" name="direction" value="portrait" autofocus><span>竖向</span></label>
|
||||
<label><input id="pageLandscape" type="radio" name="direction" value="landscape"><span>横向</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-pair">
|
||||
<div class="form-row">
|
||||
<label for="pageWidth">宽度</label>
|
||||
<div class="form-unit"><input id="pageWidth" type="number" min="1" step="any" required><span>mm</span></div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="pageHeight">高度</label>
|
||||
<div class="form-unit"><input id="pageHeight" type="number" min="1" step="any" required><span>mm</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<div id="pageStatus" class="form-status" role="status"></div>
|
||||
<button id="pageCancel" class="button" type="button">取消</button>
|
||||
<button class="button" type="submit">确定</button>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="insertPanel" class="form-dialog" aria-label="添加文字">
|
||||
<form id="insertForm">
|
||||
<div id="insertTextRow" class="form-row">
|
||||
|
||||
+30
-1
@@ -97,13 +97,21 @@ a:focus-visible {
|
||||
|
||||
.editor-group {
|
||||
display: grid;
|
||||
grid-auto-columns: max-content;
|
||||
grid-template-columns: repeat(3, max-content);
|
||||
column-gap: 6px;
|
||||
row-gap: 4px;
|
||||
padding-right: 12px;
|
||||
border-right: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.editor-group:has(#insertTextButton) {
|
||||
grid-template-columns: repeat(2, max-content);
|
||||
}
|
||||
|
||||
.editor-group:has(#addPageButton) {
|
||||
grid-template-columns: repeat(6, max-content);
|
||||
}
|
||||
|
||||
.editor-group:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
@@ -351,6 +359,17 @@ button:not(:disabled):hover {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.form-pair {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.form-pair .form-unit {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.form-options {
|
||||
display: flex;
|
||||
}
|
||||
@@ -1744,3 +1763,13 @@ button.outline-link:hover {
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.editor-group:has(#addPageButton) {
|
||||
grid-template-columns: repeat(3, max-content);
|
||||
}
|
||||
|
||||
.editor-group:has(#addPageButton) > button {
|
||||
grid-row: auto;
|
||||
}
|
||||
}
|
||||
+173
-27
@@ -91,6 +91,20 @@ const el = {
|
||||
redoButton: document.querySelector("#redoButton"),
|
||||
insertTextButton: document.querySelector("#insertTextButton"),
|
||||
insertImageButton: document.querySelector("#insertImageButton"),
|
||||
addPageButton: document.querySelector("#addPageButton"),
|
||||
copyPageButton: document.querySelector("#copyPageButton"),
|
||||
deletePageButton: document.querySelector("#deletePageButton"),
|
||||
movePagePrevButton: document.querySelector("#movePagePrevButton"),
|
||||
movePageNextButton: document.querySelector("#movePageNextButton"),
|
||||
pageSettingsButton: document.querySelector("#pageSettingsButton"),
|
||||
pagePanel: document.querySelector("#pagePanel"),
|
||||
pageForm: document.querySelector("#pageForm"),
|
||||
pagePortrait: document.querySelector("#pagePortrait"),
|
||||
pageLandscape: document.querySelector("#pageLandscape"),
|
||||
pageWidth: document.querySelector("#pageWidth"),
|
||||
pageHeight: document.querySelector("#pageHeight"),
|
||||
pageStatus: document.querySelector("#pageStatus"),
|
||||
pageCancel: document.querySelector("#pageCancel"),
|
||||
saveButton: document.querySelector("#saveButton"),
|
||||
createPanel: document.querySelector("#createPanel"),
|
||||
createForm: document.querySelector("#createForm"),
|
||||
@@ -235,6 +249,31 @@ el.editTextButton.addEventListener("click", () => {
|
||||
});
|
||||
el.undoButton.addEventListener("click", () => changeDocument("ofdgoUndo"));
|
||||
el.redoButton.addEventListener("click", () => changeDocument("ofdgoRedo"));
|
||||
el.addPageButton.addEventListener("click", () => {
|
||||
const page = currentPageInfo();
|
||||
return changeDocument("ofdgoChangePage", null, "add", state.pageIndex, page.width, page.height);
|
||||
});
|
||||
el.copyPageButton.addEventListener("click", () => changeDocument("ofdgoChangePage", null, "copy", state.pageIndex));
|
||||
el.deletePageButton.addEventListener("click", () => changeDocument("ofdgoChangePage", null, "delete", state.pageIndex));
|
||||
el.movePagePrevButton.addEventListener("click", () => changeDocument("ofdgoChangePage", null, "move", state.pageIndex, state.pageIndex - 1));
|
||||
el.movePageNextButton.addEventListener("click", () => changeDocument("ofdgoChangePage", null, "move", state.pageIndex, state.pageIndex + 1));
|
||||
el.pageSettingsButton.addEventListener("click", openPagePanel);
|
||||
el.pageCancel.addEventListener("click", () => el.pagePanel.close());
|
||||
el.pageWidth.addEventListener("input", updatePageDirection);
|
||||
el.pageHeight.addEventListener("input", updatePageDirection);
|
||||
for (const radio of [el.pagePortrait, el.pageLandscape]) {
|
||||
radio.addEventListener("change", () => {
|
||||
[el.pageWidth.value, el.pageHeight.value] = [el.pageHeight.value, el.pageWidth.value];
|
||||
updatePageDirection();
|
||||
});
|
||||
}
|
||||
el.pageForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
el.pageStatus.textContent = "";
|
||||
if (await changeDocument("ofdgoChangePage", null, "resize", state.pageIndex, Number(el.pageWidth.value), Number(el.pageHeight.value))) {
|
||||
el.pagePanel.close();
|
||||
}
|
||||
});
|
||||
el.ofdButton.addEventListener("click", openOFDFile);
|
||||
el.newButton.addEventListener("click", openCreatePanel);
|
||||
el.createCancel.addEventListener("click", () => el.createPanel.close());
|
||||
@@ -455,7 +494,25 @@ function handleKeyDown(event) {
|
||||
}
|
||||
|
||||
function formDialogOpen() {
|
||||
return el.exportPanel.open || el.createPanel.open || el.insertPanel.open;
|
||||
return el.exportPanel.open || el.createPanel.open || el.insertPanel.open || el.pagePanel.open;
|
||||
}
|
||||
|
||||
function openPagePanel() {
|
||||
if (!state.editing || document.body.hasAttribute("aria-busy")) {
|
||||
return;
|
||||
}
|
||||
const page = currentPageInfo();
|
||||
el.pageWidth.value = String(page.width);
|
||||
el.pageHeight.value = String(page.height);
|
||||
el.pageStatus.textContent = "";
|
||||
updatePageDirection();
|
||||
el.pagePanel.showModal();
|
||||
}
|
||||
|
||||
function updatePageDirection() {
|
||||
const landscape = Number(el.pageWidth.value) > Number(el.pageHeight.value);
|
||||
el.pageLandscape.checked = landscape;
|
||||
el.pagePortrait.checked = !landscape;
|
||||
}
|
||||
|
||||
function warnUnsaved(event) {
|
||||
@@ -590,12 +647,12 @@ async function insertObject(event) {
|
||||
let openSeq = state.openSeq;
|
||||
const text = !el.insertTextRow.hidden;
|
||||
const item = state.textObject;
|
||||
const { scrollLeft, scrollTop } = el.viewerPanel;
|
||||
const page = state.doc.pages[state.pageIndex];
|
||||
const index = item?.index ?? state.pageIndex;
|
||||
const page = state.doc.pages[index];
|
||||
const x = Math.min(20, page.width / 10);
|
||||
const y = Math.min(20, page.height / 10);
|
||||
el.insertStatus.textContent = "";
|
||||
setBusy(true, item ? "正在修改文字" : text ? "正在添加文字" : "正在添加图片", null);
|
||||
setBusy(true);
|
||||
try {
|
||||
let data = null;
|
||||
if (text) {
|
||||
@@ -613,8 +670,8 @@ async function insertObject(event) {
|
||||
const size = item && points === Number((item.size * 72 / 25.4).toPrecision(12)) ? item.size : points * 25.4 / 72;
|
||||
const doc = item
|
||||
? await callWASM("ofdgoUpdateText", item.index, item.id, el.insertText.value, data, size)
|
||||
: text ? await callWASM("ofdgoInsertText", state.pageIndex, el.insertText.value, data, x, y, size)
|
||||
: await callWASM("ofdgoInsertImage", state.pageIndex, data, x, y, page.width - x * 2);
|
||||
: text ? await callWASM("ofdgoInsertText", index, el.insertText.value, data, x, y, size)
|
||||
: await callWASM("ofdgoInsertImage", index, data, x, y, page.width - x * 2);
|
||||
if (openSeq !== state.openSeq) {
|
||||
return;
|
||||
}
|
||||
@@ -624,15 +681,17 @@ async function insertObject(event) {
|
||||
state.selectObjects = true;
|
||||
setPan(false);
|
||||
openSeq = ++state.openSeq;
|
||||
await openDocument({ doc, openSeq, skipAutoFonts: true, pageIndex: item?.index ?? state.pageIndex, fitMode: state.fitMode, scale: state.scale });
|
||||
if (item && openSeq === state.openSeq) {
|
||||
el.viewerPanel.scrollLeft = scrollLeft;
|
||||
el.viewerPanel.scrollTop = scrollTop;
|
||||
await refreshEditorPage(doc, index, openSeq);
|
||||
if (openSeq === state.openSeq) {
|
||||
el.viewerPanel.focus({ preventScroll: true });
|
||||
}
|
||||
} catch (err) {
|
||||
if (openSeq === state.openSeq) {
|
||||
el.insertStatus.textContent = err.message;
|
||||
if (el.insertPanel.open) {
|
||||
el.insertStatus.textContent = err.message;
|
||||
} else {
|
||||
showError(err, false);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (openSeq === state.openSeq) {
|
||||
@@ -646,27 +705,51 @@ async function changeDocument(name, item, ...args) {
|
||||
return;
|
||||
}
|
||||
let openSeq = state.openSeq;
|
||||
const previous = currentPageInfo();
|
||||
const { scrollLeft, scrollTop } = el.viewerPanel;
|
||||
setBusy(true, "正在更新文档", null);
|
||||
setBusy(true);
|
||||
try {
|
||||
const doc = await callWASM(name, ...(item ? [item.index, item.id, ...args] : []));
|
||||
const doc = await callWASM(name, ...(item ? [item.index, item.id] : []), ...args);
|
||||
if (openSeq !== state.openSeq) {
|
||||
return;
|
||||
}
|
||||
if (doc.revision === state.editorInfo?.revision) {
|
||||
return true;
|
||||
}
|
||||
setEditorInfo(doc);
|
||||
if (!item || name === "ofdgoDeleteObject") {
|
||||
canvasEditor.clear();
|
||||
}
|
||||
openSeq = ++state.openSeq;
|
||||
await openDocument({ doc, openSeq, skipAutoFonts: true, pageIndex: item?.index ?? state.pageIndex, fitMode: state.fitMode, scale: state.scale });
|
||||
if (item) {
|
||||
await refreshEditorPage(doc, item.index, openSeq);
|
||||
} else {
|
||||
const samePage = doc.pages.findIndex((page) => page.id === previous.id);
|
||||
const pageIndex = doc.pageIndex ?? (samePage < 0 ? Math.min(state.pageIndex, doc.pageCount - 1) : samePage);
|
||||
const page = doc.pages[pageIndex];
|
||||
const keepScroll = doc.pageIndex === undefined && page.id === previous.id && pageIndex === state.pageIndex
|
||||
&& page.width === previous.width && page.height === previous.height;
|
||||
await openDocument({ doc, openSeq, skipAutoFonts: true, pageIndex, fitMode: state.fitMode, scale: state.scale });
|
||||
if (openSeq === state.openSeq) {
|
||||
if (keepScroll) {
|
||||
el.viewerPanel.scrollLeft = scrollLeft;
|
||||
el.viewerPanel.scrollTop = scrollTop;
|
||||
} else {
|
||||
scrollToPage(pageIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (openSeq === state.openSeq) {
|
||||
el.viewerPanel.scrollLeft = scrollLeft;
|
||||
el.viewerPanel.scrollTop = scrollTop;
|
||||
el.viewerPanel.focus({ preventScroll: true });
|
||||
return true;
|
||||
}
|
||||
} catch (err) {
|
||||
if (openSeq === state.openSeq) {
|
||||
showError(err, false);
|
||||
if (el.pagePanel.open) {
|
||||
el.pageStatus.textContent = err.message;
|
||||
} else {
|
||||
showError(err, false);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (openSeq === state.openSeq) {
|
||||
@@ -675,6 +758,57 @@ async function changeDocument(name, item, ...args) {
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshEditorPage(doc, index, openSeq) {
|
||||
state.doc = doc;
|
||||
resetSearch();
|
||||
state.documentSelection = null;
|
||||
resetPageLoading();
|
||||
try {
|
||||
const page = await loadPageData(index, { openSeq, priority: 0, refresh: true });
|
||||
if (openSeq !== state.openSeq) {
|
||||
return;
|
||||
}
|
||||
mountPageSVG(index, page, openSeq);
|
||||
updateThumbnail(index, openSeq);
|
||||
setStatus(pageStatus(state.pageIndex, doc.pageCount));
|
||||
} catch (err) {
|
||||
if (openSeq === state.openSeq) {
|
||||
state.pageCache.delete(index);
|
||||
canvasEditor.clear();
|
||||
const shell = pageShell(index);
|
||||
shell.querySelector(".page-surface").replaceChildren();
|
||||
shell.classList.remove("rendered");
|
||||
markFlowPageError(index, err);
|
||||
markThumbnailError(index);
|
||||
}
|
||||
throw err;
|
||||
} finally {
|
||||
if (openSeq === state.openSeq) {
|
||||
const pages = [...state.pageCache.values()];
|
||||
for (const [name, image] of state.svgImages) {
|
||||
if (!pages.some((page) => page.imageNames.includes(name))) {
|
||||
URL.revokeObjectURL(image.url);
|
||||
state.svgImages.delete(name);
|
||||
}
|
||||
}
|
||||
for (const [name, font] of state.svgFonts) {
|
||||
if (!pages.some((page) => page.fonts.some((item) => item.name === name))) {
|
||||
document.fonts.delete(font);
|
||||
state.svgFonts.delete(name);
|
||||
}
|
||||
}
|
||||
renderMeta();
|
||||
updateControls();
|
||||
loadDocumentDetails(openSeq);
|
||||
for (const page of doc.pages) {
|
||||
observeFlowPage(pageShell(page.index), page.index);
|
||||
const button = el.pageList.querySelector(`[data-page-index="${page.index}"]`);
|
||||
observeThumbnail(button, page.index, openSeq);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function openOFDFile() {
|
||||
if (document.body.hasAttribute("aria-busy")) {
|
||||
return;
|
||||
@@ -1116,6 +1250,7 @@ async function openOFD(file) {
|
||||
setDirty(false);
|
||||
el.createPanel.close();
|
||||
el.insertPanel.close();
|
||||
el.pagePanel.close();
|
||||
updateControls();
|
||||
await openDocument({ pageIndex: 0, resetScroll: true, openSeq });
|
||||
} catch (err) {
|
||||
@@ -1587,7 +1722,7 @@ async function loadDocumentDetails(openSeq) {
|
||||
}
|
||||
|
||||
async function renderPage(index, options = {}) {
|
||||
if (!state.doc) {
|
||||
if (!state.doc || (state.editing && document.body.hasAttribute("aria-busy") && !options.keepBusy)) {
|
||||
return null;
|
||||
}
|
||||
const openSeq = options.openSeq || state.openSeq;
|
||||
@@ -1835,6 +1970,10 @@ function resetPageFlow() {
|
||||
state.selectedPages.clear();
|
||||
state.documentSelection = null;
|
||||
state.visiblePages.clear();
|
||||
resetPageLoading();
|
||||
}
|
||||
|
||||
function resetPageLoading() {
|
||||
for (const task of state.pageInFlight.values()) {
|
||||
task.resolve(null);
|
||||
}
|
||||
@@ -1936,12 +2075,6 @@ function loadPageData(index, options = {}) {
|
||||
if (openSeq !== state.openSeq) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
if (state.pageCache.has(index)) {
|
||||
const page = state.pageCache.get(index);
|
||||
state.pageCache.delete(index);
|
||||
state.pageCache.set(index, page);
|
||||
return Promise.resolve(page);
|
||||
}
|
||||
const key = `${openSeq}:${index}`;
|
||||
const priority = options.priority ?? 3;
|
||||
const current = state.pageInFlight.get(key);
|
||||
@@ -1949,13 +2082,19 @@ function loadPageData(index, options = {}) {
|
||||
current.priority = Math.min(current.priority, priority);
|
||||
return current.promise;
|
||||
}
|
||||
if (!options.refresh && state.pageCache.has(index)) {
|
||||
const page = state.pageCache.get(index);
|
||||
state.pageCache.delete(index);
|
||||
state.pageCache.set(index, page);
|
||||
return Promise.resolve(page);
|
||||
}
|
||||
let resolve;
|
||||
let reject;
|
||||
const promise = new Promise((done, fail) => {
|
||||
resolve = done;
|
||||
reject = fail;
|
||||
});
|
||||
const task = { key, index, openSeq, priority, resolve, reject, promise };
|
||||
const task = { key, index, openSeq, priority, refresh: options.refresh, resolve, reject, promise };
|
||||
state.pageInFlight.set(key, task);
|
||||
state.pageRenderQueue.push(task);
|
||||
schedulePageRender();
|
||||
@@ -1981,7 +2120,7 @@ async function processPageRenderQueue() {
|
||||
task.resolve(null);
|
||||
continue;
|
||||
}
|
||||
if (state.pageCache.has(task.index)) {
|
||||
if (!task.refresh && state.pageCache.has(task.index)) {
|
||||
task.resolve(state.pageCache.get(task.index));
|
||||
continue;
|
||||
}
|
||||
@@ -2892,6 +3031,7 @@ function updateThumbnail(index, openSeq = state.openSeq) {
|
||||
function markThumbnailError(index) {
|
||||
const thumb = el.pageList.querySelector(`[data-page-index="${index}"] .thumb-paper`);
|
||||
if (thumb) {
|
||||
delete thumb.dataset.renderKey;
|
||||
thumb.classList.add("error");
|
||||
thumb.textContent = String(index + 1);
|
||||
}
|
||||
@@ -3617,6 +3757,11 @@ function updateControls() {
|
||||
function updateEditorTools() {
|
||||
el.editorTools.hidden = !state.editing;
|
||||
el.insertTextButton.disabled = el.insertImageButton.disabled = el.saveButton.disabled = !state.ready || state.exporting;
|
||||
const pagesDisabled = !state.editing || !state.ready || state.exporting;
|
||||
el.addPageButton.disabled = el.copyPageButton.disabled = el.pageSettingsButton.disabled = pagesDisabled;
|
||||
el.deletePageButton.disabled = pagesDisabled || state.doc?.pageCount <= 1;
|
||||
el.movePagePrevButton.disabled = pagesDisabled || state.pageIndex === 0;
|
||||
el.movePageNextButton.disabled = pagesDisabled || state.pageIndex === state.doc?.pageCount - 1;
|
||||
const enabled = state.editing && state.selectObjects && !state.panMode;
|
||||
canvasEditor.setEnabled(enabled);
|
||||
el.selectObjectButton.setAttribute("aria-pressed", String(enabled));
|
||||
@@ -3678,6 +3823,7 @@ function setBusy(busy, text = "", percent = 0, status = "") {
|
||||
document.body.toggleAttribute("aria-busy", busy);
|
||||
el.createForm.inert = busy;
|
||||
el.insertForm.inert = busy;
|
||||
el.pageForm.inert = busy;
|
||||
el.editorTools.inert = busy;
|
||||
el.fontList.inert = busy;
|
||||
if (!busy) {
|
||||
@@ -3689,7 +3835,7 @@ function setBusy(busy, text = "", percent = 0, status = "") {
|
||||
}
|
||||
el.exportPanel.close();
|
||||
endPan();
|
||||
el.progressPanel.hidden = false;
|
||||
el.progressPanel.hidden = !text;
|
||||
setProgress(text, percent, status);
|
||||
}
|
||||
|
||||
|
||||
+54
-4
@@ -96,6 +96,7 @@ func RunWASM() {
|
||||
registerExportCallback("ofdgoExportAttachment", exportAttachment)
|
||||
registerCallback("ofdgoMatchFontFiles", matchFontFiles)
|
||||
registerCallback("ofdgoCreateDocument", createDocument)
|
||||
registerCallback("ofdgoChangePage", changePage)
|
||||
registerCallback("ofdgoInsertText", insertText)
|
||||
registerCallback("ofdgoUpdateText", updateText)
|
||||
registerCallback("ofdgoInsertImage", insertImage)
|
||||
@@ -521,6 +522,12 @@ type editorInfo struct {
|
||||
CanRedo bool `json:"canRedo"`
|
||||
}
|
||||
|
||||
// editorPageInfo 页面操作结果与目标页面
|
||||
type editorPageInfo struct {
|
||||
editorInfo
|
||||
PageIndex int `json:"pageIndex"`
|
||||
}
|
||||
|
||||
// editorSummary 获取当前创作文档状态
|
||||
// 返回: editorInfo 文档信息
|
||||
func editorSummary() editorInfo {
|
||||
@@ -660,6 +667,49 @@ func deleteObject(args []js.Value) (any, error) {
|
||||
return previewEditor(currentEditor, currentSession.Renderer.RenderAnnotations)
|
||||
}
|
||||
|
||||
// changePage 管理创作文档的页面
|
||||
// 入参: args 操作、页面索引及尺寸或目标索引
|
||||
// 返回: any 文档信息和目标页面, error 错误信息
|
||||
func changePage(args []js.Value) (any, error) {
|
||||
if currentEditor == nil {
|
||||
return nil, fmt.Errorf("no document is being created")
|
||||
}
|
||||
index := args[1].Int()
|
||||
revision := currentEditor.Revision()
|
||||
var err error
|
||||
switch action := args[0].String(); action {
|
||||
case "add":
|
||||
index, err = currentEditor.AddPage(args[2].Float(), args[3].Float())
|
||||
case "copy":
|
||||
index, err = currentEditor.CopyPage(index)
|
||||
case "delete":
|
||||
if currentEditor.PageCount() == 1 {
|
||||
return nil, fmt.Errorf("the document must contain at least one page")
|
||||
}
|
||||
err = currentEditor.DeletePage(index)
|
||||
index = min(index, currentEditor.PageCount()-1)
|
||||
case "move":
|
||||
target := args[2].Int()
|
||||
err = currentEditor.MovePage(index, target)
|
||||
index = target
|
||||
case "resize":
|
||||
err = currentEditor.ResizePage(index, args[2].Float(), args[3].Float())
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported page action %q", action)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if currentEditor.Revision() == revision {
|
||||
return editorPageInfo{editorSummary(), index}, nil
|
||||
}
|
||||
info, err := previewEditor(currentEditor, currentSession.Renderer.RenderAnnotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return editorPageInfo{info, index}, nil
|
||||
}
|
||||
|
||||
// createDocument 新建单页文档
|
||||
// 入参: args 标题、纸张宽高和注解设置
|
||||
// 返回: any 文档信息, error 错误信息
|
||||
@@ -675,15 +725,15 @@ func createDocument(args []js.Value) (any, error) {
|
||||
|
||||
// previewEditor 通过内存快照更新预览,不生成中间压缩包
|
||||
// 入参: editor 新建文档, annotations 是否显示注解
|
||||
// 返回: any 文档信息, error 错误信息
|
||||
func previewEditor(editor *ofdgo.Editor, annotations bool) (any, error) {
|
||||
// 返回: editorInfo 文档信息, error 错误信息
|
||||
func previewEditor(editor *ofdgo.Editor, annotations bool) (editorInfo, error) {
|
||||
reader, err := editor.Reader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return editorInfo{}, err
|
||||
}
|
||||
session, err := newSession(reader, OpenOptions{RenderAnnotations: annotations})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return editorInfo{}, err
|
||||
}
|
||||
if currentSession != nil {
|
||||
_ = currentSession.Close()
|
||||
|
||||
Reference in new issue
Block a user