update sequence nav to use new ui-sequence-nav component
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
const sidebarControllers = new WeakMap();
|
||||
const sidebarControllerRegistry = new Map();
|
||||
const detailControllers = new WeakMap();
|
||||
const detailExportInProgress = new WeakSet();
|
||||
const AUTO_COLLAPSE_ENTRY_SELECTOR = [
|
||||
".planet-list-item",
|
||||
".tarot-list-item",
|
||||
@@ -403,6 +404,7 @@
|
||||
const sectionId = layout.closest("section")?.id || `layout-${index + 1}`;
|
||||
const panelId = detailPanel.id || `${sectionId}-detail-panel`;
|
||||
detailPanel.id = panelId;
|
||||
ensureDetailPaneExportControl(detailPanel, sectionId);
|
||||
|
||||
const detailStorageKey = `${DETAIL_COLLAPSE_STORAGE_PREFIX}${sectionId}`;
|
||||
const sidebarStorageKey = `${SIDEBAR_COLLAPSE_STORAGE_PREFIX}${sectionId}`;
|
||||
@@ -439,6 +441,166 @@
|
||||
});
|
||||
}
|
||||
|
||||
function sanitizeExportToken(value, fallback = "detail") {
|
||||
const normalized = String(value || "")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
return normalized || fallback;
|
||||
}
|
||||
|
||||
function canvasToWebpBlob(canvas, quality = 1) {
|
||||
return new Promise((resolve, reject) => {
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
resolve(blob);
|
||||
return;
|
||||
}
|
||||
reject(new Error("Detail pane export failed."));
|
||||
}, "image/webp", quality);
|
||||
});
|
||||
}
|
||||
|
||||
async function exportDetailPaneAsWebp(detailPanel, sectionId, buttonEl = null) {
|
||||
if (!(detailPanel instanceof HTMLElement) || detailExportInProgress.has(detailPanel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
detailExportInProgress.add(detailPanel);
|
||||
const originalButtonLabel = buttonEl instanceof HTMLButtonElement ? buttonEl.textContent : "";
|
||||
if (buttonEl instanceof HTMLButtonElement) {
|
||||
buttonEl.disabled = true;
|
||||
buttonEl.textContent = "Exporting...";
|
||||
}
|
||||
|
||||
let exportBlobUrl = "";
|
||||
let sandboxEl = null;
|
||||
|
||||
try {
|
||||
const html2canvas = window.html2canvas;
|
||||
if (typeof html2canvas !== "function") {
|
||||
throw new Error("Detail export library is unavailable. Refresh the page and try again.");
|
||||
}
|
||||
|
||||
const exportClone = detailPanel.cloneNode(true);
|
||||
if (!(exportClone instanceof HTMLElement)) {
|
||||
throw new Error("Detail pane is not ready to export.");
|
||||
}
|
||||
|
||||
exportClone.querySelectorAll(".detail-pane-export-controls").forEach((node) => node.remove());
|
||||
|
||||
const contentWidth = Math.max(
|
||||
320,
|
||||
Math.ceil(detailPanel.clientWidth || detailPanel.getBoundingClientRect().width || 0)
|
||||
);
|
||||
const contentHeight = Math.max(
|
||||
240,
|
||||
Math.ceil(detailPanel.scrollHeight || detailPanel.getBoundingClientRect().height || 0)
|
||||
);
|
||||
|
||||
sandboxEl = document.createElement("div");
|
||||
sandboxEl.style.position = "fixed";
|
||||
sandboxEl.style.left = "-100000px";
|
||||
sandboxEl.style.top = "0";
|
||||
sandboxEl.style.width = `${contentWidth}px`;
|
||||
sandboxEl.style.height = `${contentHeight}px`;
|
||||
sandboxEl.style.overflow = "hidden";
|
||||
sandboxEl.style.pointerEvents = "none";
|
||||
sandboxEl.style.opacity = "0";
|
||||
|
||||
exportClone.style.width = `${contentWidth}px`;
|
||||
exportClone.style.minWidth = `${contentWidth}px`;
|
||||
exportClone.style.maxWidth = `${contentWidth}px`;
|
||||
exportClone.style.height = `${contentHeight}px`;
|
||||
exportClone.style.minHeight = `${contentHeight}px`;
|
||||
exportClone.style.maxHeight = `${contentHeight}px`;
|
||||
exportClone.style.overflow = "visible";
|
||||
|
||||
sandboxEl.appendChild(exportClone);
|
||||
document.body.appendChild(sandboxEl);
|
||||
|
||||
const exportScale = Math.max(2, Math.min(2.5, Number(window.devicePixelRatio) || 1));
|
||||
const canvas = await html2canvas(exportClone, {
|
||||
backgroundColor: null,
|
||||
scale: exportScale,
|
||||
useCORS: true,
|
||||
allowTaint: false,
|
||||
logging: false,
|
||||
width: contentWidth,
|
||||
height: contentHeight,
|
||||
windowWidth: contentWidth,
|
||||
windowHeight: contentHeight,
|
||||
scrollX: 0,
|
||||
scrollY: 0,
|
||||
imageTimeout: 12000,
|
||||
onclone(clonedDocument) {
|
||||
clonedDocument.querySelectorAll(".detail-pane-export-controls").forEach((node) => node.remove());
|
||||
}
|
||||
});
|
||||
|
||||
const exportBlob = await canvasToWebpBlob(canvas, 1);
|
||||
exportBlobUrl = URL.createObjectURL(exportBlob);
|
||||
|
||||
const headingEl = detailPanel.querySelector("h1, h2, h3");
|
||||
const labelToken = sanitizeExportToken(headingEl?.textContent || detailPanel.id || "detail-pane", "detail-pane");
|
||||
const sectionToken = sanitizeExportToken(sectionId, "section");
|
||||
const dateToken = new Date().toISOString().slice(0, 10);
|
||||
|
||||
const downloadLink = document.createElement("a");
|
||||
downloadLink.href = exportBlobUrl;
|
||||
downloadLink.download = `${sectionToken}-${labelToken}-${dateToken}.webp`;
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
downloadLink.remove();
|
||||
} catch (error) {
|
||||
window.alert(error instanceof Error ? error.message : "Unable to export this detail pane.");
|
||||
} finally {
|
||||
detailExportInProgress.delete(detailPanel);
|
||||
if (buttonEl instanceof HTMLButtonElement) {
|
||||
buttonEl.disabled = false;
|
||||
buttonEl.textContent = originalButtonLabel || "Export WebP";
|
||||
}
|
||||
|
||||
if (sandboxEl instanceof HTMLElement) {
|
||||
sandboxEl.remove();
|
||||
}
|
||||
|
||||
if (exportBlobUrl) {
|
||||
URL.revokeObjectURL(exportBlobUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensureDetailPaneExportControl(detailPanel, sectionId) {
|
||||
if (!(detailPanel instanceof HTMLElement) || detailPanel.querySelector(".detail-pane-export-controls")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hostEl = detailPanel.querySelector(".detail-sequence-nav")
|
||||
|| detailPanel.querySelector(".planet-detail-heading")
|
||||
|| detailPanel.querySelector(".tarot-detail-heading")
|
||||
|| detailPanel.querySelector(".kab-detail-heading")
|
||||
|| detailPanel;
|
||||
if (!(hostEl instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const controlsEl = document.createElement("div");
|
||||
controlsEl.className = "detail-pane-export-controls";
|
||||
|
||||
const exportButtonEl = document.createElement("button");
|
||||
exportButtonEl.type = "button";
|
||||
exportButtonEl.className = "detail-sequence-btn detail-export-btn";
|
||||
exportButtonEl.textContent = "Export WebP";
|
||||
exportButtonEl.addEventListener("click", () => {
|
||||
exportDetailPaneAsWebp(detailPanel, sectionId, exportButtonEl);
|
||||
});
|
||||
|
||||
controlsEl.appendChild(exportButtonEl);
|
||||
hostEl.appendChild(controlsEl);
|
||||
}
|
||||
|
||||
function setTopbarDropdownOpen(dropdownEl, isOpen) {
|
||||
if (!(dropdownEl instanceof HTMLElement)) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user