mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
1 parent
2e5241733a
commit
eaefd730ae
4 files changed
+89
-20
No files matched your search
@@ -12,7 +12,8 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="app">
|
<div class="app">
|
||||||
<header class="toolbar app-bar">
|
<header class="toolbar-frame">
|
||||||
|
<div class="toolbar app-bar">
|
||||||
<input id="ofdInput" class="file-input" type="file" accept=".ofd,.pdf">
|
<input id="ofdInput" class="file-input" type="file" accept=".ofd,.pdf">
|
||||||
<input id="fontInput" class="file-input" type="file" accept=".ttf,.otf,.ttc,font/*" multiple>
|
<input id="fontInput" class="file-input" type="file" accept=".ttf,.otf,.ttc,font/*" multiple>
|
||||||
<input id="fontDirectoryInput" class="file-input" type="file" webkitdirectory multiple>
|
<input id="fontDirectoryInput" class="file-input" type="file" webkitdirectory multiple>
|
||||||
@@ -62,6 +63,8 @@
|
|||||||
<button id="exportPageButton" class="button" type="button" data-short="单" disabled>单页</button>
|
<button id="exportPageButton" class="button" type="button" data-short="单" disabled>单页</button>
|
||||||
<button id="exportButton" class="button" type="button" data-short="多" disabled>文档</button>
|
<button id="exportButton" class="button" type="button" data-short="多" disabled>文档</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<button id="refreshAppButton" class="button app-update" type="button" hidden>更新</button>
|
||||||
</header>
|
</header>
|
||||||
<div id="editorTools" class="editor-toolbar" hidden>
|
<div id="editorTools" class="editor-toolbar" hidden>
|
||||||
<div class="editor-group editor-actions">
|
<div class="editor-group editor-actions">
|
||||||
@@ -708,7 +711,6 @@
|
|||||||
<div id="appPanelTitle" class="panel-title compact">应用</div>
|
<div id="appPanelTitle" class="panel-title compact">应用</div>
|
||||||
<div class="panel-actions">
|
<div class="panel-actions">
|
||||||
<span id="offlineStatus" class="app-status">准备中</span>
|
<span id="offlineStatus" class="app-status">准备中</span>
|
||||||
<button id="refreshAppButton" class="small-button app-refresh" type="button" title="刷新应用" disabled>↻</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
+32
-8
@@ -445,6 +445,38 @@ a:focus-visible {
|
|||||||
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.68) inset;
|
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.68) inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toolbar-frame {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
background: var(--bar-bg);
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-frame > .toolbar {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-update {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
align-self: center;
|
||||||
|
width: 62px;
|
||||||
|
margin: 0 10px 0 0;
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: #e4f3ef;
|
||||||
|
color: var(--accent-strong);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-update:not(:disabled):hover {
|
||||||
|
background: #f2faf7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-update[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
--tool-gap: 8px;
|
--tool-gap: 8px;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -2281,14 +2313,6 @@ body[data-hide-meta] .meta-panel {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-refresh {
|
|
||||||
width: 26px;
|
|
||||||
padding: 0;
|
|
||||||
color: var(--accent-strong);
|
|
||||||
font-size: 18px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navigation-tabs {
|
.navigation-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|||||||
+51
-9
@@ -4194,16 +4194,18 @@ async function prepareOffline() {
|
|||||||
|
|
||||||
function watchApplicationUpdates(checksum) {
|
function watchApplicationUpdates(checksum) {
|
||||||
let checking = false;
|
let checking = false;
|
||||||
|
let checked = 0;
|
||||||
async function check() {
|
async function check() {
|
||||||
if (checking || document.hidden || !navigator.onLine) {
|
if (checking || document.hidden || !navigator.onLine || Date.now() - checked < 60000) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
checking = true;
|
checking = true;
|
||||||
|
checked = Date.now();
|
||||||
try {
|
try {
|
||||||
const response = await fetch("./", { method: "HEAD", cache: "no-store", signal: AbortSignal.timeout(10000) });
|
const response = await fetch("./", { method: "HEAD", cache: "no-store", signal: AbortSignal.timeout(10000) });
|
||||||
const latest = response.headers.get("X-OFDGo-Checksum");
|
const latest = response.headers.get("X-OFDGo-Checksum");
|
||||||
if (response.ok && !response.redirected && latest) {
|
if (response.ok && !response.redirected && latest) {
|
||||||
el.offlineStatus.textContent = latest === checksum ? "可离线" : "可更新";
|
el.refreshAppButton.hidden = latest === checksum;
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
} finally {
|
} finally {
|
||||||
@@ -4212,16 +4214,27 @@ function watchApplicationUpdates(checksum) {
|
|||||||
}
|
}
|
||||||
window.addEventListener("online", check);
|
window.addEventListener("online", check);
|
||||||
document.addEventListener("visibilitychange", check);
|
document.addEventListener("visibilitychange", check);
|
||||||
|
window.setInterval(check, 300000);
|
||||||
check();
|
check();
|
||||||
}
|
}
|
||||||
|
|
||||||
function waitForWorker(worker, target) {
|
function waitForWorker(worker, target) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!worker) {
|
||||||
|
reject(new Error("离线服务未就绪"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
worker.removeEventListener("statechange", changed);
|
||||||
|
reject(new Error("更新等待超时"));
|
||||||
|
}, 30000);
|
||||||
function changed() {
|
function changed() {
|
||||||
if (worker.state === target || worker.state === "activated") {
|
if (worker.state === target || worker.state === "activated") {
|
||||||
|
clearTimeout(timer);
|
||||||
worker.removeEventListener("statechange", changed);
|
worker.removeEventListener("statechange", changed);
|
||||||
resolve();
|
resolve();
|
||||||
} else if (worker.state === "redundant") {
|
} else if (worker.state === "redundant") {
|
||||||
|
clearTimeout(timer);
|
||||||
worker.removeEventListener("statechange", changed);
|
worker.removeEventListener("statechange", changed);
|
||||||
reject(new Error("离线启动失败"));
|
reject(new Error("离线启动失败"));
|
||||||
}
|
}
|
||||||
@@ -4233,8 +4246,17 @@ function waitForWorker(worker, target) {
|
|||||||
|
|
||||||
function requestOffline(worker, type) {
|
function requestOffline(worker, type) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!worker) {
|
||||||
|
reject(new Error("离线服务未就绪"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
const channel = new MessageChannel();
|
const channel = new MessageChannel();
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
close();
|
||||||
|
reject(new Error("更新等待超时"));
|
||||||
|
}, 180000);
|
||||||
function close() {
|
function close() {
|
||||||
|
clearTimeout(timer);
|
||||||
channel.port1.close();
|
channel.port1.close();
|
||||||
worker.removeEventListener("statechange", changed);
|
worker.removeEventListener("statechange", changed);
|
||||||
}
|
}
|
||||||
@@ -4253,13 +4275,23 @@ function requestOffline(worker, type) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
worker.addEventListener("statechange", changed);
|
worker.addEventListener("statechange", changed);
|
||||||
|
try {
|
||||||
worker.postMessage({ type }, [channel.port2]);
|
worker.postMessage({ type }, [channel.port2]);
|
||||||
|
} catch (err) {
|
||||||
|
close();
|
||||||
|
channel.port2.close();
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
changed();
|
changed();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshApplication() {
|
async function refreshApplication() {
|
||||||
if (document.body.hasAttribute("data-busy")) {
|
if (nativeHost || state.exporting || document.body.hasAttribute("data-busy")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!navigator.onLine) {
|
||||||
|
setStatus("离线,联网后重试");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const temporaryFonts = fontManager.userFonts.some((font) => font.source === "upload");
|
const temporaryFonts = fontManager.userFonts.some((font) => font.source === "upload");
|
||||||
@@ -4267,21 +4299,29 @@ async function refreshApplication() {
|
|||||||
if ((state.ofdBytes || state.doc || temporaryFonts) && !window.confirm(message)) {
|
if ((state.ofdBytes || state.doc || temporaryFonts) && !window.confirm(message)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setBusy(true, "正在刷新应用", null, "正在刷新应用");
|
setBusy(true, "正在更新应用", null, "正在更新应用");
|
||||||
el.refreshAppButton.disabled = true;
|
el.refreshAppButton.disabled = true;
|
||||||
const panels = document.querySelectorAll(".toolbar, .workspace");
|
el.refreshAppButton.textContent = "更新中";
|
||||||
|
const panels = document.querySelectorAll(".toolbar-frame, .workspace");
|
||||||
panels.forEach((panel) => { panel.inert = true; });
|
panels.forEach((panel) => { panel.inert = true; });
|
||||||
try {
|
try {
|
||||||
if (navigator.storage?.persist) {
|
if (navigator.storage?.persist) {
|
||||||
await navigator.storage.persist().catch(() => false);
|
navigator.storage.persist().catch(() => false);
|
||||||
}
|
}
|
||||||
const scope = new URL("./", location.href).href;
|
const scope = new URL("./", location.href).href;
|
||||||
await navigator.locks.request(`ofdgo:${scope}:refresh`, { ifAvailable: true }, async (lock) => {
|
await navigator.locks.request(`ofdgo:${scope}:refresh`, { ifAvailable: true }, async (lock) => {
|
||||||
if (!lock) {
|
if (!lock) {
|
||||||
throw new Error("应用正在刷新");
|
throw new Error("其他页面正在更新");
|
||||||
}
|
}
|
||||||
const registration = await registerOffline();
|
const registration = await registerOffline();
|
||||||
await registration.update();
|
let timer;
|
||||||
|
try {
|
||||||
|
await Promise.race([registration.update(), new Promise((resolve, reject) => {
|
||||||
|
timer = setTimeout(() => reject(new Error("更新等待超时")), 30000);
|
||||||
|
})]);
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
if (registration.installing) {
|
if (registration.installing) {
|
||||||
await waitForWorker(registration.installing, "installed");
|
await waitForWorker(registration.installing, "installed");
|
||||||
}
|
}
|
||||||
@@ -4292,10 +4332,11 @@ async function refreshApplication() {
|
|||||||
location.reload();
|
location.reload();
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setStatus(err.message === "应用正在刷新" ? err.message : "应用刷新失败");
|
setStatus(err.message === "其他页面正在更新" ? err.message : "更新失败,请重试");
|
||||||
} finally {
|
} finally {
|
||||||
panels.forEach((panel) => { panel.inert = false; });
|
panels.forEach((panel) => { panel.inert = false; });
|
||||||
el.refreshAppButton.disabled = false;
|
el.refreshAppButton.disabled = false;
|
||||||
|
el.refreshAppButton.textContent = "更新";
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12019,6 +12060,7 @@ function showError(err, empty = !state.doc) {
|
|||||||
|
|
||||||
function setBusy(busy, text = "", percent = 0, status = "") {
|
function setBusy(busy, text = "", percent = 0, status = "") {
|
||||||
document.body.toggleAttribute("data-busy", busy);
|
document.body.toggleAttribute("data-busy", busy);
|
||||||
|
el.refreshAppButton.disabled = busy;
|
||||||
updateStructureControls();
|
updateStructureControls();
|
||||||
updateDocumentControls();
|
updateDocumentControls();
|
||||||
renderSecurity();
|
renderSecurity();
|
||||||
|
|||||||
@@ -106,8 +106,9 @@ async function readResource(event, navigation) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function updateBundle(meta) {
|
async function updateBundle(meta) {
|
||||||
|
const signal = AbortSignal.timeout(120000);
|
||||||
const resources = await Promise.all(ASSETS.map(async (url) => {
|
const resources = await Promise.all(ASSETS.map(async (url) => {
|
||||||
const response = await fetch(new Request(url, { cache: "no-store" }));
|
const response = await fetch(new Request(url, { cache: "no-store", signal }));
|
||||||
if (!response.ok || response.redirected) {
|
if (!response.ok || response.redirected) {
|
||||||
throw new Error("资源下载失败");
|
throw new Error("资源下载失败");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user