5106 lines
292 KiB
HTML
5106 lines
292 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>ZDDC Browse</title>
|
||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+CiAgPHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMTIiIGZpbGw9IiMxZTNhNWYiLz4KICA8ZyBmaWxsPSIjZmZmIj4KICAgIDxyZWN0IHg9IjE0IiB5PSIxOCIgd2lkdGg9IjM2IiBoZWlnaHQ9IjciLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iNDMsMjUgNTAsMjUgMjEsNDMgMTQsNDMiLz4KICAgIDxyZWN0IHg9IjE0IiB5PSI0MyIgd2lkdGg9IjM2IiBoZWlnaHQ9IjciLz4KICA8L2c+Cjwvc3ZnPgo=">
|
||
<style>
|
||
/* ==========================================================================
|
||
ZDDC Shared Base — single source of truth for tokens and primitives
|
||
Included first by every tool's build.sh via ../shared/base.css
|
||
========================================================================== */
|
||
|
||
/* ── CSS custom properties ────────────────────────────────────────────────── */
|
||
:root {
|
||
/* Brand / accent (matches zddc.varasys.io website --accent) */
|
||
--primary: #2a5a8a;
|
||
--primary-hover: #1d4060;
|
||
--primary-active: #163352;
|
||
--primary-light: #e8f0f7;
|
||
|
||
/* Semantic colours */
|
||
--success: #28a745;
|
||
--warning: #d97706;
|
||
--danger: #dc3545;
|
||
--info: #17a2b8;
|
||
|
||
/* Backgrounds */
|
||
--bg: #ffffff;
|
||
--bg-secondary: #f8f9fa;
|
||
--bg-hover: #f0f4f8;
|
||
--bg-selected: var(--primary-light);
|
||
|
||
/* Text */
|
||
--text: #212529;
|
||
--text-muted: #6c757d;
|
||
--text-light: #ffffff;
|
||
|
||
/* Borders */
|
||
--border: #dee2e6;
|
||
--border-dark: #adb5bd;
|
||
|
||
/* Shape */
|
||
--radius: 4px;
|
||
|
||
/* Typography */
|
||
--font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||
--font-mono: 'SF Mono', 'Fira Code', 'Consolas', 'Courier New', monospace;
|
||
}
|
||
|
||
/* ── Dark mode tokens ─────────────────────────────────────────────────────── */
|
||
/* Applied via: OS preference (auto) or [data-theme="dark"] on <html> */
|
||
/* The [data-theme="light"] selector locks light mode regardless of OS pref. */
|
||
@media (prefers-color-scheme: dark) {
|
||
:root:not([data-theme="light"]) {
|
||
--primary: #4a90c4;
|
||
--primary-hover: #5ba3d9;
|
||
--primary-active: #6ab5e8;
|
||
--primary-light: #1a3550;
|
||
|
||
--bg: #1e1e1e;
|
||
--bg-secondary: #252526;
|
||
--bg-hover: #2d2d30;
|
||
--bg-selected: #1a3550;
|
||
|
||
--text: #d4d4d4;
|
||
--text-muted: #9d9d9d;
|
||
--text-light: #ffffff;
|
||
|
||
--border: #3e3e42;
|
||
--border-dark: #6e6e72;
|
||
}
|
||
}
|
||
|
||
/* Manual dark override — wins over media query */
|
||
[data-theme="dark"] {
|
||
--primary: #4a90c4;
|
||
--primary-hover: #5ba3d9;
|
||
--primary-active: #6ab5e8;
|
||
--primary-light: #1a3550;
|
||
|
||
--bg: #1e1e1e;
|
||
--bg-secondary: #252526;
|
||
--bg-hover: #2d2d30;
|
||
--bg-selected: #1a3550;
|
||
|
||
--text: #d4d4d4;
|
||
--text-muted: #9d9d9d;
|
||
--text-light: #ffffff;
|
||
|
||
--border: #3e3e42;
|
||
--border-dark: #6e6e72;
|
||
}
|
||
|
||
/* ── Reset ────────────────────────────────────────────────────────────────── */
|
||
*, *::before, *::after {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
/* ── Base document ────────────────────────────────────────────────────────── */
|
||
html, body {
|
||
height: 100%;
|
||
font-family: var(--font);
|
||
font-size: 16px;
|
||
line-height: 1.5;
|
||
color: var(--text);
|
||
background-color: var(--bg-secondary);
|
||
}
|
||
|
||
/* ── Typography ───────────────────────────────────────────────────────────── */
|
||
h1, h2, h3, h4, h5, h6 {
|
||
font-weight: 600;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
a {
|
||
color: var(--primary);
|
||
text-decoration: none;
|
||
}
|
||
|
||
a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
/* ── Utility ──────────────────────────────────────────────────────────────── */
|
||
.hidden {
|
||
display: none !important;
|
||
}
|
||
|
||
.truncate {
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* ── Scrollbars (webkit) ──────────────────────────────────────────────────── */
|
||
::-webkit-scrollbar {
|
||
width: 7px;
|
||
height: 7px;
|
||
}
|
||
|
||
::-webkit-scrollbar-track {
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: #c1c1c1;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: #a0a0a0;
|
||
}
|
||
|
||
/* ── Button primitive ─────────────────────────────────────────────────────── */
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.25rem;
|
||
padding: 0.4rem 0.85rem;
|
||
font-family: var(--font);
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
line-height: 1.4;
|
||
text-align: center;
|
||
text-decoration: none;
|
||
white-space: nowrap;
|
||
vertical-align: middle;
|
||
cursor: pointer;
|
||
border: 1px solid transparent;
|
||
border-radius: var(--radius);
|
||
transition: background 0.15s, box-shadow 0.15s, border-color 0.15s, color 0.15s;
|
||
background: var(--bg-secondary);
|
||
color: var(--text);
|
||
}
|
||
|
||
.btn:disabled,
|
||
.btn[disabled] {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.btn:not(:disabled):hover {
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
|
||
}
|
||
|
||
.btn:not(:disabled):active {
|
||
box-shadow: none;
|
||
}
|
||
|
||
/* Variants */
|
||
.btn-primary {
|
||
background: var(--primary);
|
||
color: var(--text-light);
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.btn-primary:not(:disabled):hover {
|
||
background: var(--primary-hover);
|
||
border-color: var(--primary-hover);
|
||
color: var(--text-light);
|
||
}
|
||
|
||
.btn-primary:not(:disabled):active {
|
||
background: var(--primary-active);
|
||
border-color: var(--primary-active);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
border-color: var(--border);
|
||
}
|
||
|
||
.btn-secondary:not(:disabled):hover {
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
/* Subdued / de-emphasized variant.
|
||
Used on the "Add Local Directory" button when a tool is operating
|
||
in server (online) mode — the local-dir affordance is still
|
||
available but visually quieter, since the typical user already
|
||
has the directory loaded from the server. */
|
||
.btn.btn--subtle {
|
||
background: transparent;
|
||
color: var(--text-muted);
|
||
border-color: var(--border);
|
||
box-shadow: none;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.btn.btn--subtle:not(:disabled):hover {
|
||
color: var(--text);
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
.btn-success {
|
||
background: var(--success);
|
||
color: var(--text-light);
|
||
border-color: var(--success);
|
||
}
|
||
|
||
.btn-danger {
|
||
background: var(--danger);
|
||
color: var(--text-light);
|
||
border-color: var(--danger);
|
||
}
|
||
|
||
/* Sizes */
|
||
.btn-sm {
|
||
padding: 0.25rem 0.5rem;
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
.btn-lg {
|
||
padding: 0.6rem 1.4rem;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.btn-link {
|
||
background: transparent;
|
||
border-color: transparent;
|
||
color: var(--primary);
|
||
padding-left: 0;
|
||
padding-right: 0;
|
||
}
|
||
|
||
.btn-link:not(:disabled):hover {
|
||
text-decoration: underline;
|
||
box-shadow: none;
|
||
}
|
||
|
||
/* ── App header chrome ────────────────────────────────────────────────────── */
|
||
.app-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0.35rem 1rem;
|
||
background: var(--bg-secondary);
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Left and right groups inside .app-header. Both flex-row so their
|
||
children (logo, title, action button, theme icon, etc.) lay out
|
||
horizontally rather than stacking. Left side gets a slightly
|
||
larger gap because it carries the title group and an action
|
||
button; right side is just icon buttons. */
|
||
.header-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.header-right {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
/* Tool name inside the header */
|
||
.app-header__title {
|
||
font-size: 17px;
|
||
font-weight: 600;
|
||
color: var(--text);
|
||
letter-spacing: 0.01em;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Brand logo — sits left of the title in every tool's app-header.
|
||
Self-contained: the SVG provides its own dark blue rounded background,
|
||
so no extra wrapper styling is needed. */
|
||
.app-header__logo {
|
||
width: 26px;
|
||
height: 26px;
|
||
flex-shrink: 0;
|
||
display: block;
|
||
}
|
||
|
||
/* ── Build timestamp ──────────────────────────────────────────────────────── */
|
||
.build-timestamp {
|
||
font-size: 0.55rem;
|
||
color: var(--text-muted);
|
||
opacity: 0.7;
|
||
font-weight: 300;
|
||
white-space: nowrap;
|
||
padding-top: 0.15rem;
|
||
}
|
||
|
||
/* Title + timestamp stacked vertically on the left side of the header */
|
||
.header-title-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0;
|
||
line-height: 1;
|
||
}
|
||
|
||
/* ── Icon buttons (help, theme, refresh) ─────────────────────────────────── */
|
||
/* Square, centered — overrides the asymmetric text-button padding/line-height */
|
||
#help-btn,
|
||
#theme-btn,
|
||
#refreshHeaderBtn {
|
||
width: 2rem;
|
||
height: 2rem;
|
||
padding: 0;
|
||
line-height: 1;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
/* The refresh ⟳ glyph renders slightly smaller than ◐ / ? — bump to match. */
|
||
#refreshHeaderBtn {
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
/* Toast CSS lives in shared/toast.css; loaded by every tool's build. */
|
||
|
||
/* ── Empty state ──────────────────────────────────────────────────────────── */
|
||
/* The "nothing's loaded yet" screen. By default, centers its inner
|
||
content in whatever space the parent gives it (works inside a flex
|
||
column). Tools that need to overlay an existing layout (archive,
|
||
classifier) add .empty-state--overlay; the screen pins below the
|
||
app header and on top of whatever underlying layout already exists.
|
||
Inner content uses BEM-ish .empty-state__inner with two variants:
|
||
plain (left-aligned, doc-style) and --centered (centered card). */
|
||
|
||
.empty-state {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 2rem;
|
||
background: var(--bg);
|
||
}
|
||
|
||
.empty-state--overlay {
|
||
position: absolute;
|
||
top: 50px; /* clear the app-header */
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 10;
|
||
flex: none;
|
||
}
|
||
|
||
.empty-state__inner {
|
||
max-width: 640px;
|
||
color: var(--text-muted);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.empty-state__inner h2 {
|
||
color: var(--text);
|
||
margin: 0 0 1rem;
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.empty-state__inner p {
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.empty-state__inner ul,
|
||
.empty-state__inner ol {
|
||
margin: 1rem 0;
|
||
padding-left: 1.5rem;
|
||
}
|
||
|
||
.empty-state__inner li {
|
||
margin: 0.4rem 0;
|
||
}
|
||
|
||
.empty-state__inner .note {
|
||
font-size: 0.85rem;
|
||
font-style: italic;
|
||
}
|
||
|
||
/* Centered variant: tighter max-width + centered text. Used by tools
|
||
whose empty-state reads as a "welcome card" (archive, classifier)
|
||
rather than a doc-style page (browse). */
|
||
.empty-state__inner--centered {
|
||
max-width: 500px;
|
||
text-align: center;
|
||
padding: 2rem;
|
||
}
|
||
|
||
/* Bullet list inside an empty-state — keep the bullets left-aligned
|
||
even when the surrounding card is centered. */
|
||
.welcome-list {
|
||
text-align: left;
|
||
margin: 0.5rem auto;
|
||
max-width: 400px;
|
||
}
|
||
|
||
/* ── Theme and help icon buttons ─────────────────────────────────────────── */
|
||
#theme-btn,
|
||
#help-btn {
|
||
font-size: 1rem;
|
||
}
|
||
|
||
/* ── Help panel (shared slide-out drawer) ─────────────────────────────────── */
|
||
/* Used by all four tools. Toggle open/close via shared/help.js. */
|
||
|
||
.help-panel {
|
||
position: fixed;
|
||
top: 0;
|
||
right: 0;
|
||
width: min(420px, 85vw);
|
||
height: 100vh;
|
||
z-index: 1000;
|
||
background: var(--bg);
|
||
border-left: 1px solid var(--border);
|
||
box-shadow: -2px 0 12px rgba(0, 0, 0, 0.08);
|
||
display: flex;
|
||
flex-direction: column;
|
||
transform: translateX(100%);
|
||
transition: transform 0.25s ease;
|
||
}
|
||
|
||
.help-panel:not([hidden]) {
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.help-panel[hidden] {
|
||
display: flex;
|
||
transform: translateX(100%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.help-panel__header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0.75rem 1rem;
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
background: var(--bg);
|
||
}
|
||
|
||
.help-panel__title {
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
color: var(--text);
|
||
margin: 0;
|
||
}
|
||
|
||
.help-panel__close {
|
||
background: none;
|
||
border: none;
|
||
color: var(--text-muted);
|
||
font-size: 1.35rem;
|
||
cursor: pointer;
|
||
padding: 0.25rem 0.5rem;
|
||
border-radius: var(--radius);
|
||
line-height: 1;
|
||
transition: background 0.15s, color 0.15s;
|
||
}
|
||
|
||
.help-panel__close:hover {
|
||
color: var(--text);
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
.help-panel__body {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 1rem 1rem 2rem;
|
||
font-size: 0.85rem;
|
||
line-height: 1.6;
|
||
color: var(--text);
|
||
}
|
||
|
||
.help-panel__body h3 {
|
||
font-size: 0.95rem;
|
||
font-weight: 700;
|
||
margin: 1.25rem 0 0.35rem;
|
||
color: var(--text);
|
||
border-bottom: 1px solid var(--border);
|
||
padding-bottom: 0.15rem;
|
||
}
|
||
|
||
.help-panel__body h3:first-child {
|
||
margin-top: 0;
|
||
}
|
||
|
||
.help-panel__body h4 {
|
||
font-size: 0.7rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
margin: 1.25rem 0 0.3rem;
|
||
padding-left: 0.5rem;
|
||
border-left: 3px solid var(--border-dark);
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.help-panel__body p {
|
||
margin: 0 0 0.5rem;
|
||
}
|
||
|
||
.help-panel__body ol,
|
||
.help-panel__body ul {
|
||
padding-left: 1.5rem;
|
||
margin: 0.3rem 0 0.5rem;
|
||
}
|
||
|
||
.help-panel__body li {
|
||
margin-bottom: 0.3rem;
|
||
}
|
||
|
||
.help-panel__body dl {
|
||
margin: 0.3rem 0;
|
||
}
|
||
|
||
.help-panel__body dt {
|
||
font-weight: 600;
|
||
color: var(--text);
|
||
}
|
||
|
||
.help-panel__body dd {
|
||
margin: 0 0 0.5rem 1rem;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.help-panel__body code {
|
||
font-family: var(--font-mono);
|
||
font-size: 0.8em;
|
||
background: var(--bg-secondary);
|
||
padding: 0.1em 0.3em;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.help-badge {
|
||
font-size: 0.7rem;
|
||
font-weight: 600;
|
||
padding: 0.1rem 0.35rem;
|
||
border-radius: var(--radius);
|
||
vertical-align: middle;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
.help-badge--draft {
|
||
color: #2563eb;
|
||
background: #eff6ff;
|
||
}
|
||
|
||
.help-badge--published {
|
||
color: #7c3aed;
|
||
background: #f5f3ff;
|
||
}
|
||
|
||
/* Shrink main content when help panel is open */
|
||
body.help-open .app-header {
|
||
margin-right: min(420px, 85vw);
|
||
}
|
||
|
||
/* ── Column filter inputs (shared across archive, classifier, transmittal) ─── */
|
||
.column-filter {
|
||
display: block;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
margin-top: 0.25rem;
|
||
padding: 0.2rem 0.4rem;
|
||
font-size: 0.8rem;
|
||
font-family: var(--font);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
transition: border-color 0.15s;
|
||
}
|
||
|
||
.column-filter:focus {
|
||
border-color: var(--primary);
|
||
outline: none;
|
||
box-shadow: 0 0 0 1px rgba(42, 90, 138, 0.35);
|
||
}
|
||
|
||
.column-filter::placeholder {
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
/* shared/toast.css — single-toast notification styles paired with
|
||
shared/toast.js. Uses BEM-ish .zddc-toast prefix to avoid collisions
|
||
with tool-local .toast classes; the old classifier rules can stay
|
||
alongside until this file is concatenated above them in the build. */
|
||
|
||
.zddc-toast {
|
||
position: fixed;
|
||
bottom: 2rem;
|
||
right: 2rem;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
padding: 0.875rem 1.25rem;
|
||
border-radius: var(--radius);
|
||
border: 1px solid var(--border);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
z-index: 9000;
|
||
max-width: 400px;
|
||
font-size: 0.875rem;
|
||
cursor: pointer;
|
||
animation: zddc-toast-in 0.3s ease-out;
|
||
}
|
||
|
||
.zddc-toast--success { border-left: 4px solid var(--success); }
|
||
.zddc-toast--error { border-left: 4px solid var(--danger); }
|
||
.zddc-toast--info { border-left: 4px solid var(--info); }
|
||
.zddc-toast--warning { border-left: 4px solid var(--warning); }
|
||
|
||
.zddc-toast--fade {
|
||
animation: zddc-toast-out 0.3s ease-out forwards;
|
||
}
|
||
|
||
@keyframes zddc-toast-in {
|
||
from { transform: translateX(100%); opacity: 0; }
|
||
to { transform: translateX(0); opacity: 1; }
|
||
}
|
||
|
||
@keyframes zddc-toast-out {
|
||
from { transform: translateX(0); opacity: 1; }
|
||
to { transform: translateX(100%); opacity: 0; }
|
||
}
|
||
|
||
/* shared/nav.css — lateral project-stage strip paired with shared/nav.js.
|
||
Sits as a sibling immediately under .app-header (mounted by JS).
|
||
Rendered only in online mode when a project segment is in the URL. */
|
||
|
||
.zddc-stage-strip {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.3rem 1rem;
|
||
background: var(--bg);
|
||
border-bottom: 1px solid var(--border);
|
||
font-size: 0.8rem;
|
||
line-height: 1.3;
|
||
flex-shrink: 0;
|
||
overflow-x: auto;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.zddc-stage-strip__project {
|
||
color: var(--text);
|
||
font-weight: 600;
|
||
margin-right: 0.15rem;
|
||
}
|
||
|
||
.zddc-stage-strip__divider,
|
||
.zddc-stage-strip__sep {
|
||
color: var(--text-muted);
|
||
user-select: none;
|
||
}
|
||
|
||
.zddc-stage-strip__divider {
|
||
margin-right: 0.35rem;
|
||
}
|
||
|
||
.zddc-stage {
|
||
color: var(--text-muted);
|
||
text-decoration: none;
|
||
padding: 0.1rem 0.25rem;
|
||
border-radius: var(--radius);
|
||
transition: color 0.15s, background 0.15s;
|
||
}
|
||
|
||
.zddc-stage:hover {
|
||
color: var(--text);
|
||
background: var(--bg-secondary);
|
||
text-decoration: none;
|
||
}
|
||
|
||
.zddc-stage--active {
|
||
color: var(--primary);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.zddc-stage--active:hover {
|
||
color: var(--primary);
|
||
}
|
||
|
||
/* shared/logo.css — paired with shared/logo.js. The wrapping anchor
|
||
inherits the logo's box and adds a subtle hover/focus affordance
|
||
so it reads as clickable without altering the logo's visual weight. */
|
||
|
||
.app-header__logo-link {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
text-decoration: none;
|
||
border-radius: var(--radius);
|
||
transition: opacity 0.15s, box-shadow 0.15s;
|
||
}
|
||
|
||
.app-header__logo-link:hover .app-header__logo,
|
||
.app-header__logo-link:focus-visible .app-header__logo {
|
||
opacity: 0.82;
|
||
}
|
||
|
||
.app-header__logo-link:focus-visible {
|
||
outline: 2px solid var(--primary);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
/* browse-specific layout on top of shared/base.css */
|
||
|
||
html, body {
|
||
height: 100%;
|
||
margin: 0;
|
||
padding: 0;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
font-family: var(--font);
|
||
}
|
||
|
||
body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
#appMain {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
}
|
||
|
||
/* .empty-state / .empty-state__inner live in shared/base.css. */
|
||
|
||
/* .hidden lives in shared/base.css; no per-tool override needed. */
|
||
|
||
/* Status bar — shows transient errors/info */
|
||
.status-bar {
|
||
padding: 0.4rem 1rem;
|
||
background: var(--bg-secondary);
|
||
border-top: 1px solid var(--border);
|
||
font-size: 0.85rem;
|
||
color: var(--text-muted);
|
||
min-height: 1.6rem;
|
||
line-height: 1.6rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.status-bar--error { color: #b00020; }
|
||
.status-bar--info { color: var(--primary); }
|
||
|
||
/* Toolbar above the listing */
|
||
|
||
.browse-root {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.browse-table-wrap {
|
||
flex: 1;
|
||
overflow: auto;
|
||
min-height: 0;
|
||
}
|
||
|
||
.toolbar {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
padding: 0.6rem 1rem;
|
||
background: var(--bg-secondary);
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
/* Breadcrumb path. The root node is a 🏠 link to "/" (online) or
|
||
the FS handle name (offline). Each segment is a clickable link in
|
||
server mode that re-navigates the browser; in FS-API mode they
|
||
render as plain spans because we don't keep ancestor handles. */
|
||
.breadcrumbs {
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
white-space: nowrap;
|
||
font-family: Consolas, Monaco, monospace;
|
||
font-size: 0.9rem;
|
||
color: var(--text-muted);
|
||
padding: 0.1rem 0;
|
||
/* Hide the scrollbar but keep horizontal scroll for very deep paths */
|
||
scrollbar-width: thin;
|
||
}
|
||
|
||
.breadcrumbs .bc-link {
|
||
color: var(--primary);
|
||
text-decoration: none;
|
||
padding: 0.1rem 0.25rem;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.breadcrumbs .bc-link:hover {
|
||
background: var(--bg-hover, rgba(0,0,0,0.05));
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.breadcrumbs .bc-link--current {
|
||
color: var(--text);
|
||
font-weight: 500;
|
||
cursor: default;
|
||
}
|
||
|
||
.breadcrumbs .bc-link--current:hover {
|
||
background: transparent;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.breadcrumbs .bc-sep {
|
||
color: var(--text-muted);
|
||
margin: 0 0.05rem;
|
||
}
|
||
|
||
.breadcrumbs .bc-root {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
line-height: 1;
|
||
}
|
||
|
||
.bc-home-icon {
|
||
width: 1rem;
|
||
height: 1rem;
|
||
display: block;
|
||
color: currentColor;
|
||
}
|
||
|
||
.toolbar__count {
|
||
font-size: 0.8rem;
|
||
color: var(--text-muted);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* Table — folders + files in a tree */
|
||
|
||
.browse-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
font-size: 0.9rem;
|
||
background: var(--bg);
|
||
/* No flex:1 — tables don't reliably distribute extra height across
|
||
rows the way flex columns do. With few rows we'd get tall rows
|
||
that shrink as more children are loaded. The wrap div handles
|
||
scrolling instead. */
|
||
}
|
||
|
||
.browse-table tbody tr {
|
||
/* Pin rows to a deterministic height so table layout never
|
||
redistributes vertical space across them. */
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.browse-table thead th {
|
||
position: sticky;
|
||
top: 0;
|
||
background: var(--bg-secondary);
|
||
border-bottom: 1px solid var(--border);
|
||
text-align: left;
|
||
padding: 0.5rem 0.75rem;
|
||
font-weight: 600;
|
||
color: var(--text);
|
||
user-select: none;
|
||
z-index: 1;
|
||
}
|
||
|
||
.browse-table th.sortable {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.browse-table th.sortable:hover {
|
||
background: var(--bg-hover, #e8e8e8);
|
||
}
|
||
|
||
.sort-arrow {
|
||
display: inline-block;
|
||
width: 0.7rem;
|
||
color: var(--text-muted);
|
||
font-size: 0.7rem;
|
||
margin-left: 0.2rem;
|
||
}
|
||
|
||
.browse-table th.sort-asc .sort-arrow::after { content: "▲"; color: var(--text); }
|
||
.browse-table th.sort-desc .sort-arrow::after { content: "▼"; color: var(--text); }
|
||
|
||
.browse-table tbody td {
|
||
padding: 0.3rem 0.75rem;
|
||
border-bottom: 1px solid var(--border);
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.browse-table tbody tr:hover {
|
||
background: var(--bg-hover, #f6faff);
|
||
}
|
||
|
||
/* Tree-row — name cell with indent + chevron */
|
||
|
||
.tree-name {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
.tree-name__indent {
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.tree-name__chevron {
|
||
width: 1rem;
|
||
text-align: center;
|
||
color: var(--text-muted);
|
||
cursor: pointer;
|
||
user-select: none;
|
||
flex: 0 0 1rem;
|
||
line-height: 1;
|
||
}
|
||
|
||
.tree-name__chevron--leaf { visibility: hidden; }
|
||
.tree-name__chevron::before { content: "▶"; font-size: 0.65rem; }
|
||
.tree-row.expanded > td .tree-name__chevron::before { content: "▼"; }
|
||
|
||
.tree-name__icon {
|
||
flex: 0 0 1.1rem;
|
||
text-align: center;
|
||
color: var(--text-muted);
|
||
font-size: 1rem;
|
||
line-height: 1;
|
||
}
|
||
|
||
.tree-name__label {
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
color: var(--text);
|
||
}
|
||
|
||
.tree-name__label.is-folder {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.tree-name__label.is-file {
|
||
cursor: pointer;
|
||
color: var(--primary);
|
||
text-decoration: none;
|
||
}
|
||
|
||
.tree-name__label.is-file:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
/* Numeric columns right-aligned */
|
||
.col-size, .col-date {
|
||
text-align: right;
|
||
font-variant-numeric: tabular-nums;
|
||
white-space: nowrap;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.col-ext {
|
||
color: var(--text-muted);
|
||
font-family: Consolas, Monaco, monospace;
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
/* Loading row */
|
||
.tree-row--loading td {
|
||
color: var(--text-muted);
|
||
font-style: italic;
|
||
padding: 0.5rem 1rem 0.5rem calc(0.75rem + 2.4rem);
|
||
}
|
||
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header class="app-header">
|
||
<div class="header-left">
|
||
<svg class="app-header__logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" aria-hidden="true">
|
||
<rect width="64" height="64" rx="12" fill="#1e3a5f"/>
|
||
<g fill="#fff">
|
||
<rect x="14" y="18" width="36" height="7"/>
|
||
<polygon points="43,25 50,25 21,43 14,43"/>
|
||
<rect x="14" y="43" width="36" height="7"/>
|
||
</g>
|
||
</svg>
|
||
<div class="header-title-group">
|
||
<span class="app-header__title">ZDDC Browse</span>
|
||
<span class="build-timestamp"><span style="color:red;font-weight:bold">v0.0.17-beta · 2026-05-10 · mint-pelican-badge</span></span>
|
||
</div>
|
||
<button id="addDirectoryBtn" class="btn btn-primary">Add Local Directory</button>
|
||
<button id="refreshHeaderBtn" class="btn btn-secondary hidden" title="Refresh listing" aria-label="Refresh listing">⟳</button>
|
||
</div>
|
||
<div class="header-right">
|
||
<button id="theme-btn" class="btn btn-secondary" title="Theme: auto (follows OS)" aria-label="Theme: auto (follows OS)">◐</button>
|
||
<button id="help-btn" class="btn btn-secondary" title="Help" aria-label="Help">?</button>
|
||
</div>
|
||
</header>
|
||
|
||
<main id="appMain">
|
||
<div id="emptyState" class="empty-state">
|
||
<div class="empty-state__inner">
|
||
<h2>ZDDC Browse</h2>
|
||
<p>A simple directory listing for ZDDC archives — and any directory.
|
||
Pick how you want to browse:</p>
|
||
<ul>
|
||
<li><b>Online</b> — when this page is served by zddc-server, the
|
||
listing for the current directory loads automatically.</li>
|
||
<li><b>Local</b> — click <i>Add Local Directory</i> to pick any folder
|
||
on your computer (Chromium-based browsers).</li>
|
||
</ul>
|
||
<p>Once loaded: click a folder to expand it, <b>shift-click</b>
|
||
to expand its entire subtree (or collapse it again),
|
||
click column headers to sort. Click any file to open it.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="browseRoot" class="browse-root hidden">
|
||
<div class="toolbar">
|
||
<nav class="breadcrumbs" id="breadcrumbs" aria-label="Path"></nav>
|
||
<span class="toolbar__count" id="entryCount"></span>
|
||
</div>
|
||
<div class="browse-table-wrap">
|
||
<table class="browse-table" id="browseTable">
|
||
<thead>
|
||
<tr class="header-row">
|
||
<th data-sort="name" class="col-name sortable">Name <span class="sort-arrow"></span></th>
|
||
<th data-sort="size" class="col-size sortable">Size <span class="sort-arrow"></span></th>
|
||
<th data-sort="ext" class="col-ext sortable">Type <span class="sort-arrow"></span></th>
|
||
<th data-sort="date" class="col-date sortable">Modified <span class="sort-arrow"></span></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="browseTbody"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
|
||
<div id="statusBar" class="status-bar"></div>
|
||
|
||
<!-- Help Panel -->
|
||
<aside id="help-panel" class="help-panel" hidden aria-labelledby="help-panel-title">
|
||
<div class="help-panel__header">
|
||
<h2 id="help-panel-title" class="help-panel__title">Help — ZDDC Browse</h2>
|
||
<button type="button" class="help-panel__close" id="help-panel-close" aria-label="Close">×</button>
|
||
</div>
|
||
<div class="help-panel__body">
|
||
<h3>What is Browse?</h3>
|
||
<p>Browse is a directory listing for ZDDC archives — and any directory. It works in two modes:</p>
|
||
<dl>
|
||
<dt>Online</dt>
|
||
<dd>When the page is served by zddc-server, the listing for the current
|
||
URL directory loads automatically. Breadcrumbs link to ancestor folders.</dd>
|
||
<dt>Local</dt>
|
||
<dd>Click <strong>Add Local Directory</strong> to pick any folder on your
|
||
computer. Local mode requires a Chromium-based browser (File System
|
||
Access API).</dd>
|
||
</dl>
|
||
|
||
<h3>Tree navigation</h3>
|
||
<dl>
|
||
<dt>Click a folder</dt>
|
||
<dd>Toggle expand/collapse on that folder.</dd>
|
||
<dt>Double-click a folder</dt>
|
||
<dd>Navigate into the folder — it becomes the new root of the
|
||
view. Server mode loads the folder's URL; local mode re-roots
|
||
onto that folder's handle.</dd>
|
||
<dt>Shift-click a folder</dt>
|
||
<dd>Recursive expand or collapse — applies to the whole subtree.</dd>
|
||
<dt>Click a file</dt>
|
||
<dd>Open in the preview popup. Modifier-click (Ctrl/Cmd) or middle-click
|
||
opens in a new tab.</dd>
|
||
<dt>ZIP files</dt>
|
||
<dd>Behave as folders — click to inspect contents inline. JSZip is
|
||
bundled, so this works offline.</dd>
|
||
<dt>Column headers</dt>
|
||
<dd>Click to sort; click again to reverse.</dd>
|
||
<dt>Refresh</dt>
|
||
<dd>Re-fetches the current directory listing — works for both
|
||
local (re-enumerates the FS handle) and online (re-fetches the JSON).</dd>
|
||
</dl>
|
||
|
||
<h3>Header buttons</h3>
|
||
<dl>
|
||
<dt>Add Local Directory</dt>
|
||
<dd>Pick a folder from your computer. Works in both modes; in online
|
||
mode it's de-emphasized but still available.</dd>
|
||
<dt>⟳ Refresh</dt>
|
||
<dd>Re-load the current directory listing.</dd>
|
||
<dt>◐ Theme</dt>
|
||
<dd>Cycle auto / light / dark.</dd>
|
||
</dl>
|
||
</div>
|
||
</aside>
|
||
|
||
<script>
|
||
/*!
|
||
|
||
JSZip v3.10.1 - A JavaScript class for generating and reading zip files
|
||
<http://stuartk.com/jszip>
|
||
|
||
(c) 2009-2016 Stuart Knightley <stuart [at] stuartk.com>
|
||
Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/jszip/main/LICENSE.markdown.
|
||
|
||
JSZip uses the library pako released under the MIT license :
|
||
https://github.com/nodeca/pako/blob/main/LICENSE
|
||
*/
|
||
|
||
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).JSZip=e()}}(function(){return function s(a,o,h){function u(r,e){if(!o[r]){if(!a[r]){var t="function"==typeof require&&require;if(!e&&t)return t(r,!0);if(l)return l(r,!0);var n=new Error("Cannot find module '"+r+"'");throw n.code="MODULE_NOT_FOUND",n}var i=o[r]={exports:{}};a[r][0].call(i.exports,function(e){var t=a[r][1][e];return u(t||e)},i,i.exports,s,a,o,h)}return o[r].exports}for(var l="function"==typeof require&&require,e=0;e<h.length;e++)u(h[e]);return u}({1:[function(e,t,r){"use strict";var d=e("./utils"),c=e("./support"),p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";r.encode=function(e){for(var t,r,n,i,s,a,o,h=[],u=0,l=e.length,f=l,c="string"!==d.getTypeOf(e);u<e.length;)f=l-u,n=c?(t=e[u++],r=u<l?e[u++]:0,u<l?e[u++]:0):(t=e.charCodeAt(u++),r=u<l?e.charCodeAt(u++):0,u<l?e.charCodeAt(u++):0),i=t>>2,s=(3&t)<<4|r>>4,a=1<f?(15&r)<<2|n>>6:64,o=2<f?63&n:64,h.push(p.charAt(i)+p.charAt(s)+p.charAt(a)+p.charAt(o));return h.join("")},r.decode=function(e){var t,r,n,i,s,a,o=0,h=0,u="data:";if(e.substr(0,u.length)===u)throw new Error("Invalid base64 input, it looks like a data url.");var l,f=3*(e=e.replace(/[^A-Za-z0-9+/=]/g,"")).length/4;if(e.charAt(e.length-1)===p.charAt(64)&&f--,e.charAt(e.length-2)===p.charAt(64)&&f--,f%1!=0)throw new Error("Invalid base64 input, bad content length.");for(l=c.uint8array?new Uint8Array(0|f):new Array(0|f);o<e.length;)t=p.indexOf(e.charAt(o++))<<2|(i=p.indexOf(e.charAt(o++)))>>4,r=(15&i)<<4|(s=p.indexOf(e.charAt(o++)))>>2,n=(3&s)<<6|(a=p.indexOf(e.charAt(o++))),l[h++]=t,64!==s&&(l[h++]=r),64!==a&&(l[h++]=n);return l}},{"./support":30,"./utils":32}],2:[function(e,t,r){"use strict";var n=e("./external"),i=e("./stream/DataWorker"),s=e("./stream/Crc32Probe"),a=e("./stream/DataLengthProbe");function o(e,t,r,n,i){this.compressedSize=e,this.uncompressedSize=t,this.crc32=r,this.compression=n,this.compressedContent=i}o.prototype={getContentWorker:function(){var e=new i(n.Promise.resolve(this.compressedContent)).pipe(this.compression.uncompressWorker()).pipe(new a("data_length")),t=this;return e.on("end",function(){if(this.streamInfo.data_length!==t.uncompressedSize)throw new Error("Bug : uncompressed data size mismatch")}),e},getCompressedWorker:function(){return new i(n.Promise.resolve(this.compressedContent)).withStreamInfo("compressedSize",this.compressedSize).withStreamInfo("uncompressedSize",this.uncompressedSize).withStreamInfo("crc32",this.crc32).withStreamInfo("compression",this.compression)}},o.createWorkerFrom=function(e,t,r){return e.pipe(new s).pipe(new a("uncompressedSize")).pipe(t.compressWorker(r)).pipe(new a("compressedSize")).withStreamInfo("compression",t)},t.exports=o},{"./external":6,"./stream/Crc32Probe":25,"./stream/DataLengthProbe":26,"./stream/DataWorker":27}],3:[function(e,t,r){"use strict";var n=e("./stream/GenericWorker");r.STORE={magic:"\0\0",compressWorker:function(){return new n("STORE compression")},uncompressWorker:function(){return new n("STORE decompression")}},r.DEFLATE=e("./flate")},{"./flate":7,"./stream/GenericWorker":28}],4:[function(e,t,r){"use strict";var n=e("./utils");var o=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t){return void 0!==e&&e.length?"string"!==n.getTypeOf(e)?function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t[a])];return-1^e}(0|t,e,e.length,0):function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t.charCodeAt(a))];return-1^e}(0|t,e,e.length,0):0}},{"./utils":32}],5:[function(e,t,r){"use strict";r.base64=!1,r.binary=!1,r.dir=!1,r.createFolders=!0,r.date=null,r.compression=null,r.compressionOptions=null,r.comment=null,r.unixPermissions=null,r.dosPermissions=null},{}],6:[function(e,t,r){"use strict";var n=null;n="undefined"!=typeof Promise?Promise:e("lie"),t.exports={Promise:n}},{lie:37}],7:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,i=e("pako"),s=e("./utils"),a=e("./stream/GenericWorker"),o=n?"uint8array":"array";function h(e,t){a.call(this,"FlateWorker/"+e),this._pako=null,this._pakoAction=e,this._pakoOptions=t,this.meta={}}r.magic="\b\0",s.inherits(h,a),h.prototype.processChunk=function(e){this.meta=e.meta,null===this._pako&&this._createPako(),this._pako.push(s.transformTo(o,e.data),!1)},h.prototype.flush=function(){a.prototype.flush.call(this),null===this._pako&&this._createPako(),this._pako.push([],!0)},h.prototype.cleanUp=function(){a.prototype.cleanUp.call(this),this._pako=null},h.prototype._createPako=function(){this._pako=new i[this._pakoAction]({raw:!0,level:this._pakoOptions.level||-1});var t=this;this._pako.onData=function(e){t.push({data:e,meta:t.meta})}},r.compressWorker=function(e){return new h("Deflate",e)},r.uncompressWorker=function(){return new h("Inflate",{})}},{"./stream/GenericWorker":28,"./utils":32,pako:38}],8:[function(e,t,r){"use strict";function A(e,t){var r,n="";for(r=0;r<t;r++)n+=String.fromCharCode(255&e),e>>>=8;return n}function n(e,t,r,n,i,s){var a,o,h=e.file,u=e.compression,l=s!==O.utf8encode,f=I.transformTo("string",s(h.name)),c=I.transformTo("string",O.utf8encode(h.name)),d=h.comment,p=I.transformTo("string",s(d)),m=I.transformTo("string",O.utf8encode(d)),_=c.length!==h.name.length,g=m.length!==d.length,b="",v="",y="",w=h.dir,k=h.date,x={crc32:0,compressedSize:0,uncompressedSize:0};t&&!r||(x.crc32=e.crc32,x.compressedSize=e.compressedSize,x.uncompressedSize=e.uncompressedSize);var S=0;t&&(S|=8),l||!_&&!g||(S|=2048);var z=0,C=0;w&&(z|=16),"UNIX"===i?(C=798,z|=function(e,t){var r=e;return e||(r=t?16893:33204),(65535&r)<<16}(h.unixPermissions,w)):(C=20,z|=function(e){return 63&(e||0)}(h.dosPermissions)),a=k.getUTCHours(),a<<=6,a|=k.getUTCMinutes(),a<<=5,a|=k.getUTCSeconds()/2,o=k.getUTCFullYear()-1980,o<<=4,o|=k.getUTCMonth()+1,o<<=5,o|=k.getUTCDate(),_&&(v=A(1,1)+A(B(f),4)+c,b+="up"+A(v.length,2)+v),g&&(y=A(1,1)+A(B(p),4)+m,b+="uc"+A(y.length,2)+y);var E="";return E+="\n\0",E+=A(S,2),E+=u.magic,E+=A(a,2),E+=A(o,2),E+=A(x.crc32,4),E+=A(x.compressedSize,4),E+=A(x.uncompressedSize,4),E+=A(f.length,2),E+=A(b.length,2),{fileRecord:R.LOCAL_FILE_HEADER+E+f+b,dirRecord:R.CENTRAL_FILE_HEADER+A(C,2)+E+A(p.length,2)+"\0\0\0\0"+A(z,4)+A(n,4)+f+b+p}}var I=e("../utils"),i=e("../stream/GenericWorker"),O=e("../utf8"),B=e("../crc32"),R=e("../signature");function s(e,t,r,n){i.call(this,"ZipFileWorker"),this.bytesWritten=0,this.zipComment=t,this.zipPlatform=r,this.encodeFileName=n,this.streamFiles=e,this.accumulate=!1,this.contentBuffer=[],this.dirRecords=[],this.currentSourceOffset=0,this.entriesCount=0,this.currentFile=null,this._sources=[]}I.inherits(s,i),s.prototype.push=function(e){var t=e.meta.percent||0,r=this.entriesCount,n=this._sources.length;this.accumulate?this.contentBuffer.push(e):(this.bytesWritten+=e.data.length,i.prototype.push.call(this,{data:e.data,meta:{currentFile:this.currentFile,percent:r?(t+100*(r-n-1))/r:100}}))},s.prototype.openedSource=function(e){this.currentSourceOffset=this.bytesWritten,this.currentFile=e.file.name;var t=this.streamFiles&&!e.file.dir;if(t){var r=n(e,t,!1,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);this.push({data:r.fileRecord,meta:{percent:0}})}else this.accumulate=!0},s.prototype.closedSource=function(e){this.accumulate=!1;var t=this.streamFiles&&!e.file.dir,r=n(e,t,!0,this.currentSourceOffset,this.zipPlatform,this.encodeFileName);if(this.dirRecords.push(r.dirRecord),t)this.push({data:function(e){return R.DATA_DESCRIPTOR+A(e.crc32,4)+A(e.compressedSize,4)+A(e.uncompressedSize,4)}(e),meta:{percent:100}});else for(this.push({data:r.fileRecord,meta:{percent:0}});this.contentBuffer.length;)this.push(this.contentBuffer.shift());this.currentFile=null},s.prototype.flush=function(){for(var e=this.bytesWritten,t=0;t<this.dirRecords.length;t++)this.push({data:this.dirRecords[t],meta:{percent:100}});var r=this.bytesWritten-e,n=function(e,t,r,n,i){var s=I.transformTo("string",i(n));return R.CENTRAL_DIRECTORY_END+"\0\0\0\0"+A(e,2)+A(e,2)+A(t,4)+A(r,4)+A(s.length,2)+s}(this.dirRecords.length,r,e,this.zipComment,this.encodeFileName);this.push({data:n,meta:{percent:100}})},s.prototype.prepareNextSource=function(){this.previous=this._sources.shift(),this.openedSource(this.previous.streamInfo),this.isPaused?this.previous.pause():this.previous.resume()},s.prototype.registerPrevious=function(e){this._sources.push(e);var t=this;return e.on("data",function(e){t.processChunk(e)}),e.on("end",function(){t.closedSource(t.previous.streamInfo),t._sources.length?t.prepareNextSource():t.end()}),e.on("error",function(e){t.error(e)}),this},s.prototype.resume=function(){return!!i.prototype.resume.call(this)&&(!this.previous&&this._sources.length?(this.prepareNextSource(),!0):this.previous||this._sources.length||this.generatedError?void 0:(this.end(),!0))},s.prototype.error=function(e){var t=this._sources;if(!i.prototype.error.call(this,e))return!1;for(var r=0;r<t.length;r++)try{t[r].error(e)}catch(e){}return!0},s.prototype.lock=function(){i.prototype.lock.call(this);for(var e=this._sources,t=0;t<e.length;t++)e[t].lock()},t.exports=s},{"../crc32":4,"../signature":23,"../stream/GenericWorker":28,"../utf8":31,"../utils":32}],9:[function(e,t,r){"use strict";var u=e("../compressions"),n=e("./ZipFileWorker");r.generateWorker=function(e,a,t){var o=new n(a.streamFiles,t,a.platform,a.encodeFileName),h=0;try{e.forEach(function(e,t){h++;var r=function(e,t){var r=e||t,n=u[r];if(!n)throw new Error(r+" is not a valid compression method !");return n}(t.options.compression,a.compression),n=t.options.compressionOptions||a.compressionOptions||{},i=t.dir,s=t.date;t._compressWorker(r,n).withStreamInfo("file",{name:e,dir:i,date:s,comment:t.comment||"",unixPermissions:t.unixPermissions,dosPermissions:t.dosPermissions}).pipe(o)}),o.entriesCount=h}catch(e){o.error(e)}return o}},{"../compressions":3,"./ZipFileWorker":8}],10:[function(e,t,r){"use strict";function n(){if(!(this instanceof n))return new n;if(arguments.length)throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");this.files=Object.create(null),this.comment=null,this.root="",this.clone=function(){var e=new n;for(var t in this)"function"!=typeof this[t]&&(e[t]=this[t]);return e}}(n.prototype=e("./object")).loadAsync=e("./load"),n.support=e("./support"),n.defaults=e("./defaults"),n.version="3.10.1",n.loadAsync=function(e,t){return(new n).loadAsync(e,t)},n.external=e("./external"),t.exports=n},{"./defaults":5,"./external":6,"./load":11,"./object":15,"./support":30}],11:[function(e,t,r){"use strict";var u=e("./utils"),i=e("./external"),n=e("./utf8"),s=e("./zipEntries"),a=e("./stream/Crc32Probe"),l=e("./nodejsUtils");function f(n){return new i.Promise(function(e,t){var r=n.decompressed.getContentWorker().pipe(new a);r.on("error",function(e){t(e)}).on("end",function(){r.streamInfo.crc32!==n.decompressed.crc32?t(new Error("Corrupted zip : CRC32 mismatch")):e()}).resume()})}t.exports=function(e,o){var h=this;return o=u.extend(o||{},{base64:!1,checkCRC32:!1,optimizedBinaryString:!1,createFolders:!1,decodeFileName:n.utf8decode}),l.isNode&&l.isStream(e)?i.Promise.reject(new Error("JSZip can't accept a stream when loading a zip file.")):u.prepareContent("the loaded zip file",e,!0,o.optimizedBinaryString,o.base64).then(function(e){var t=new s(o);return t.load(e),t}).then(function(e){var t=[i.Promise.resolve(e)],r=e.files;if(o.checkCRC32)for(var n=0;n<r.length;n++)t.push(f(r[n]));return i.Promise.all(t)}).then(function(e){for(var t=e.shift(),r=t.files,n=0;n<r.length;n++){var i=r[n],s=i.fileNameStr,a=u.resolve(i.fileNameStr);h.file(a,i.decompressed,{binary:!0,optimizedBinaryString:!0,date:i.date,dir:i.dir,comment:i.fileCommentStr.length?i.fileCommentStr:null,unixPermissions:i.unixPermissions,dosPermissions:i.dosPermissions,createFolders:o.createFolders}),i.dir||(h.file(a).unsafeOriginalName=s)}return t.zipComment.length&&(h.comment=t.zipComment),h})}},{"./external":6,"./nodejsUtils":14,"./stream/Crc32Probe":25,"./utf8":31,"./utils":32,"./zipEntries":33}],12:[function(e,t,r){"use strict";var n=e("../utils"),i=e("../stream/GenericWorker");function s(e,t){i.call(this,"Nodejs stream input adapter for "+e),this._upstreamEnded=!1,this._bindStream(t)}n.inherits(s,i),s.prototype._bindStream=function(e){var t=this;(this._stream=e).pause(),e.on("data",function(e){t.push({data:e,meta:{percent:0}})}).on("error",function(e){t.isPaused?this.generatedError=e:t.error(e)}).on("end",function(){t.isPaused?t._upstreamEnded=!0:t.end()})},s.prototype.pause=function(){return!!i.prototype.pause.call(this)&&(this._stream.pause(),!0)},s.prototype.resume=function(){return!!i.prototype.resume.call(this)&&(this._upstreamEnded?this.end():this._stream.resume(),!0)},t.exports=s},{"../stream/GenericWorker":28,"../utils":32}],13:[function(e,t,r){"use strict";var i=e("readable-stream").Readable;function n(e,t,r){i.call(this,t),this._helper=e;var n=this;e.on("data",function(e,t){n.push(e)||n._helper.pause(),r&&r(t)}).on("error",function(e){n.emit("error",e)}).on("end",function(){n.push(null)})}e("../utils").inherits(n,i),n.prototype._read=function(){this._helper.resume()},t.exports=n},{"../utils":32,"readable-stream":16}],14:[function(e,t,r){"use strict";t.exports={isNode:"undefined"!=typeof Buffer,newBufferFrom:function(e,t){if(Buffer.from&&Buffer.from!==Uint8Array.from)return Buffer.from(e,t);if("number"==typeof e)throw new Error('The "data" argument must not be a number');return new Buffer(e,t)},allocBuffer:function(e){if(Buffer.alloc)return Buffer.alloc(e);var t=new Buffer(e);return t.fill(0),t},isBuffer:function(e){return Buffer.isBuffer(e)},isStream:function(e){return e&&"function"==typeof e.on&&"function"==typeof e.pause&&"function"==typeof e.resume}}},{}],15:[function(e,t,r){"use strict";function s(e,t,r){var n,i=u.getTypeOf(t),s=u.extend(r||{},f);s.date=s.date||new Date,null!==s.compression&&(s.compression=s.compression.toUpperCase()),"string"==typeof s.unixPermissions&&(s.unixPermissions=parseInt(s.unixPermissions,8)),s.unixPermissions&&16384&s.unixPermissions&&(s.dir=!0),s.dosPermissions&&16&s.dosPermissions&&(s.dir=!0),s.dir&&(e=g(e)),s.createFolders&&(n=_(e))&&b.call(this,n,!0);var a="string"===i&&!1===s.binary&&!1===s.base64;r&&void 0!==r.binary||(s.binary=!a),(t instanceof c&&0===t.uncompressedSize||s.dir||!t||0===t.length)&&(s.base64=!1,s.binary=!0,t="",s.compression="STORE",i="string");var o=null;o=t instanceof c||t instanceof l?t:p.isNode&&p.isStream(t)?new m(e,t):u.prepareContent(e,t,s.binary,s.optimizedBinaryString,s.base64);var h=new d(e,o,s);this.files[e]=h}var i=e("./utf8"),u=e("./utils"),l=e("./stream/GenericWorker"),a=e("./stream/StreamHelper"),f=e("./defaults"),c=e("./compressedObject"),d=e("./zipObject"),o=e("./generate"),p=e("./nodejsUtils"),m=e("./nodejs/NodejsStreamInputAdapter"),_=function(e){"/"===e.slice(-1)&&(e=e.substring(0,e.length-1));var t=e.lastIndexOf("/");return 0<t?e.substring(0,t):""},g=function(e){return"/"!==e.slice(-1)&&(e+="/"),e},b=function(e,t){return t=void 0!==t?t:f.createFolders,e=g(e),this.files[e]||s.call(this,e,null,{dir:!0,createFolders:t}),this.files[e]};function h(e){return"[object RegExp]"===Object.prototype.toString.call(e)}var n={load:function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},forEach:function(e){var t,r,n;for(t in this.files)n=this.files[t],(r=t.slice(this.root.length,t.length))&&t.slice(0,this.root.length)===this.root&&e(r,n)},filter:function(r){var n=[];return this.forEach(function(e,t){r(e,t)&&n.push(t)}),n},file:function(e,t,r){if(1!==arguments.length)return e=this.root+e,s.call(this,e,t,r),this;if(h(e)){var n=e;return this.filter(function(e,t){return!t.dir&&n.test(e)})}var i=this.files[this.root+e];return i&&!i.dir?i:null},folder:function(r){if(!r)return this;if(h(r))return this.filter(function(e,t){return t.dir&&r.test(e)});var e=this.root+r,t=b.call(this,e),n=this.clone();return n.root=t.name,n},remove:function(r){r=this.root+r;var e=this.files[r];if(e||("/"!==r.slice(-1)&&(r+="/"),e=this.files[r]),e&&!e.dir)delete this.files[r];else for(var t=this.filter(function(e,t){return t.name.slice(0,r.length)===r}),n=0;n<t.length;n++)delete this.files[t[n].name];return this},generate:function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},generateInternalStream:function(e){var t,r={};try{if((r=u.extend(e||{},{streamFiles:!1,compression:"STORE",compressionOptions:null,type:"",platform:"DOS",comment:null,mimeType:"application/zip",encodeFileName:i.utf8encode})).type=r.type.toLowerCase(),r.compression=r.compression.toUpperCase(),"binarystring"===r.type&&(r.type="string"),!r.type)throw new Error("No output type specified.");u.checkSupport(r.type),"darwin"!==r.platform&&"freebsd"!==r.platform&&"linux"!==r.platform&&"sunos"!==r.platform||(r.platform="UNIX"),"win32"===r.platform&&(r.platform="DOS");var n=r.comment||this.comment||"";t=o.generateWorker(this,r,n)}catch(e){(t=new l("error")).error(e)}return new a(t,r.type||"string",r.mimeType)},generateAsync:function(e,t){return this.generateInternalStream(e).accumulate(t)},generateNodeStream:function(e,t){return(e=e||{}).type||(e.type="nodebuffer"),this.generateInternalStream(e).toNodejsStream(t)}};t.exports=n},{"./compressedObject":2,"./defaults":5,"./generate":9,"./nodejs/NodejsStreamInputAdapter":12,"./nodejsUtils":14,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31,"./utils":32,"./zipObject":35}],16:[function(e,t,r){"use strict";t.exports=e("stream")},{stream:void 0}],17:[function(e,t,r){"use strict";var n=e("./DataReader");function i(e){n.call(this,e);for(var t=0;t<this.data.length;t++)e[t]=255&e[t]}e("../utils").inherits(i,n),i.prototype.byteAt=function(e){return this.data[this.zero+e]},i.prototype.lastIndexOfSignature=function(e){for(var t=e.charCodeAt(0),r=e.charCodeAt(1),n=e.charCodeAt(2),i=e.charCodeAt(3),s=this.length-4;0<=s;--s)if(this.data[s]===t&&this.data[s+1]===r&&this.data[s+2]===n&&this.data[s+3]===i)return s-this.zero;return-1},i.prototype.readAndCheckSignature=function(e){var t=e.charCodeAt(0),r=e.charCodeAt(1),n=e.charCodeAt(2),i=e.charCodeAt(3),s=this.readData(4);return t===s[0]&&r===s[1]&&n===s[2]&&i===s[3]},i.prototype.readData=function(e){if(this.checkOffset(e),0===e)return[];var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./DataReader":18}],18:[function(e,t,r){"use strict";var n=e("../utils");function i(e){this.data=e,this.length=e.length,this.index=0,this.zero=0}i.prototype={checkOffset:function(e){this.checkIndex(this.index+e)},checkIndex:function(e){if(this.length<this.zero+e||e<0)throw new Error("End of data reached (data length = "+this.length+", asked index = "+e+"). Corrupted zip ?")},setIndex:function(e){this.checkIndex(e),this.index=e},skip:function(e){this.setIndex(this.index+e)},byteAt:function(){},readInt:function(e){var t,r=0;for(this.checkOffset(e),t=this.index+e-1;t>=this.index;t--)r=(r<<8)+this.byteAt(t);return this.index+=e,r},readString:function(e){return n.transformTo("string",this.readData(e))},readData:function(){},lastIndexOfSignature:function(){},readAndCheckSignature:function(){},readDate:function(){var e=this.readInt(4);return new Date(Date.UTC(1980+(e>>25&127),(e>>21&15)-1,e>>16&31,e>>11&31,e>>5&63,(31&e)<<1))}},t.exports=i},{"../utils":32}],19:[function(e,t,r){"use strict";var n=e("./Uint8ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./Uint8ArrayReader":21}],20:[function(e,t,r){"use strict";var n=e("./DataReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.byteAt=function(e){return this.data.charCodeAt(this.zero+e)},i.prototype.lastIndexOfSignature=function(e){return this.data.lastIndexOf(e)-this.zero},i.prototype.readAndCheckSignature=function(e){return e===this.readData(4)},i.prototype.readData=function(e){this.checkOffset(e);var t=this.data.slice(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./DataReader":18}],21:[function(e,t,r){"use strict";var n=e("./ArrayReader");function i(e){n.call(this,e)}e("../utils").inherits(i,n),i.prototype.readData=function(e){if(this.checkOffset(e),0===e)return new Uint8Array(0);var t=this.data.subarray(this.zero+this.index,this.zero+this.index+e);return this.index+=e,t},t.exports=i},{"../utils":32,"./ArrayReader":17}],22:[function(e,t,r){"use strict";var n=e("../utils"),i=e("../support"),s=e("./ArrayReader"),a=e("./StringReader"),o=e("./NodeBufferReader"),h=e("./Uint8ArrayReader");t.exports=function(e){var t=n.getTypeOf(e);return n.checkSupport(t),"string"!==t||i.uint8array?"nodebuffer"===t?new o(e):i.uint8array?new h(n.transformTo("uint8array",e)):new s(n.transformTo("array",e)):new a(e)}},{"../support":30,"../utils":32,"./ArrayReader":17,"./NodeBufferReader":19,"./StringReader":20,"./Uint8ArrayReader":21}],23:[function(e,t,r){"use strict";r.LOCAL_FILE_HEADER="PK",r.CENTRAL_FILE_HEADER="PK",r.CENTRAL_DIRECTORY_END="PK",r.ZIP64_CENTRAL_DIRECTORY_LOCATOR="PK",r.ZIP64_CENTRAL_DIRECTORY_END="PK",r.DATA_DESCRIPTOR="PK\b"},{}],24:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../utils");function s(e){n.call(this,"ConvertWorker to "+e),this.destType=e}i.inherits(s,n),s.prototype.processChunk=function(e){this.push({data:i.transformTo(this.destType,e.data),meta:e.meta})},t.exports=s},{"../utils":32,"./GenericWorker":28}],25:[function(e,t,r){"use strict";var n=e("./GenericWorker"),i=e("../crc32");function s(){n.call(this,"Crc32Probe"),this.withStreamInfo("crc32",0)}e("../utils").inherits(s,n),s.prototype.processChunk=function(e){this.streamInfo.crc32=i(e.data,this.streamInfo.crc32||0),this.push(e)},t.exports=s},{"../crc32":4,"../utils":32,"./GenericWorker":28}],26:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataLengthProbe for "+e),this.propName=e,this.withStreamInfo(e,0)}n.inherits(s,i),s.prototype.processChunk=function(e){if(e){var t=this.streamInfo[this.propName]||0;this.streamInfo[this.propName]=t+e.data.length}i.prototype.processChunk.call(this,e)},t.exports=s},{"../utils":32,"./GenericWorker":28}],27:[function(e,t,r){"use strict";var n=e("../utils"),i=e("./GenericWorker");function s(e){i.call(this,"DataWorker");var t=this;this.dataIsReady=!1,this.index=0,this.max=0,this.data=null,this.type="",this._tickScheduled=!1,e.then(function(e){t.dataIsReady=!0,t.data=e,t.max=e&&e.length||0,t.type=n.getTypeOf(e),t.isPaused||t._tickAndRepeat()},function(e){t.error(e)})}n.inherits(s,i),s.prototype.cleanUp=function(){i.prototype.cleanUp.call(this),this.data=null},s.prototype.resume=function(){return!!i.prototype.resume.call(this)&&(!this._tickScheduled&&this.dataIsReady&&(this._tickScheduled=!0,n.delay(this._tickAndRepeat,[],this)),!0)},s.prototype._tickAndRepeat=function(){this._tickScheduled=!1,this.isPaused||this.isFinished||(this._tick(),this.isFinished||(n.delay(this._tickAndRepeat,[],this),this._tickScheduled=!0))},s.prototype._tick=function(){if(this.isPaused||this.isFinished)return!1;var e=null,t=Math.min(this.max,this.index+16384);if(this.index>=this.max)return this.end();switch(this.type){case"string":e=this.data.substring(this.index,t);break;case"uint8array":e=this.data.subarray(this.index,t);break;case"array":case"nodebuffer":e=this.data.slice(this.index,t)}return this.index=t,this.push({data:e,meta:{percent:this.max?this.index/this.max*100:0}})},t.exports=s},{"../utils":32,"./GenericWorker":28}],28:[function(e,t,r){"use strict";function n(e){this.name=e||"default",this.streamInfo={},this.generatedError=null,this.extraStreamInfo={},this.isPaused=!0,this.isFinished=!1,this.isLocked=!1,this._listeners={data:[],end:[],error:[]},this.previous=null}n.prototype={push:function(e){this.emit("data",e)},end:function(){if(this.isFinished)return!1;this.flush();try{this.emit("end"),this.cleanUp(),this.isFinished=!0}catch(e){this.emit("error",e)}return!0},error:function(e){return!this.isFinished&&(this.isPaused?this.generatedError=e:(this.isFinished=!0,this.emit("error",e),this.previous&&this.previous.error(e),this.cleanUp()),!0)},on:function(e,t){return this._listeners[e].push(t),this},cleanUp:function(){this.streamInfo=this.generatedError=this.extraStreamInfo=null,this._listeners=[]},emit:function(e,t){if(this._listeners[e])for(var r=0;r<this._listeners[e].length;r++)this._listeners[e][r].call(this,t)},pipe:function(e){return e.registerPrevious(this)},registerPrevious:function(e){if(this.isLocked)throw new Error("The stream '"+this+"' has already been used.");this.streamInfo=e.streamInfo,this.mergeStreamInfo(),this.previous=e;var t=this;return e.on("data",function(e){t.processChunk(e)}),e.on("end",function(){t.end()}),e.on("error",function(e){t.error(e)}),this},pause:function(){return!this.isPaused&&!this.isFinished&&(this.isPaused=!0,this.previous&&this.previous.pause(),!0)},resume:function(){if(!this.isPaused||this.isFinished)return!1;var e=this.isPaused=!1;return this.generatedError&&(this.error(this.generatedError),e=!0),this.previous&&this.previous.resume(),!e},flush:function(){},processChunk:function(e){this.push(e)},withStreamInfo:function(e,t){return this.extraStreamInfo[e]=t,this.mergeStreamInfo(),this},mergeStreamInfo:function(){for(var e in this.extraStreamInfo)Object.prototype.hasOwnProperty.call(this.extraStreamInfo,e)&&(this.streamInfo[e]=this.extraStreamInfo[e])},lock:function(){if(this.isLocked)throw new Error("The stream '"+this+"' has already been used.");this.isLocked=!0,this.previous&&this.previous.lock()},toString:function(){var e="Worker "+this.name;return this.previous?this.previous+" -> "+e:e}},t.exports=n},{}],29:[function(e,t,r){"use strict";var h=e("../utils"),i=e("./ConvertWorker"),s=e("./GenericWorker"),u=e("../base64"),n=e("../support"),a=e("../external"),o=null;if(n.nodestream)try{o=e("../nodejs/NodejsStreamOutputAdapter")}catch(e){}function l(e,o){return new a.Promise(function(t,r){var n=[],i=e._internalType,s=e._outputType,a=e._mimeType;e.on("data",function(e,t){n.push(e),o&&o(t)}).on("error",function(e){n=[],r(e)}).on("end",function(){try{var e=function(e,t,r){switch(e){case"blob":return h.newBlob(h.transformTo("arraybuffer",t),r);case"base64":return u.encode(t);default:return h.transformTo(e,t)}}(s,function(e,t){var r,n=0,i=null,s=0;for(r=0;r<t.length;r++)s+=t[r].length;switch(e){case"string":return t.join("");case"array":return Array.prototype.concat.apply([],t);case"uint8array":for(i=new Uint8Array(s),r=0;r<t.length;r++)i.set(t[r],n),n+=t[r].length;return i;case"nodebuffer":return Buffer.concat(t);default:throw new Error("concat : unsupported type '"+e+"'")}}(i,n),a);t(e)}catch(e){r(e)}n=[]}).resume()})}function f(e,t,r){var n=t;switch(t){case"blob":case"arraybuffer":n="uint8array";break;case"base64":n="string"}try{this._internalType=n,this._outputType=t,this._mimeType=r,h.checkSupport(n),this._worker=e.pipe(new i(n)),e.lock()}catch(e){this._worker=new s("error"),this._worker.error(e)}}f.prototype={accumulate:function(e){return l(this,e)},on:function(e,t){var r=this;return"data"===e?this._worker.on(e,function(e){t.call(r,e.data,e.meta)}):this._worker.on(e,function(){h.delay(t,arguments,r)}),this},resume:function(){return h.delay(this._worker.resume,[],this._worker),this},pause:function(){return this._worker.pause(),this},toNodejsStream:function(e){if(h.checkSupport("nodestream"),"nodebuffer"!==this._outputType)throw new Error(this._outputType+" is not supported by this method");return new o(this,{objectMode:"nodebuffer"!==this._outputType},e)}},t.exports=f},{"../base64":1,"../external":6,"../nodejs/NodejsStreamOutputAdapter":13,"../support":30,"../utils":32,"./ConvertWorker":24,"./GenericWorker":28}],30:[function(e,t,r){"use strict";if(r.base64=!0,r.array=!0,r.string=!0,r.arraybuffer="undefined"!=typeof ArrayBuffer&&"undefined"!=typeof Uint8Array,r.nodebuffer="undefined"!=typeof Buffer,r.uint8array="undefined"!=typeof Uint8Array,"undefined"==typeof ArrayBuffer)r.blob=!1;else{var n=new ArrayBuffer(0);try{r.blob=0===new Blob([n],{type:"application/zip"}).size}catch(e){try{var i=new(self.BlobBuilder||self.WebKitBlobBuilder||self.MozBlobBuilder||self.MSBlobBuilder);i.append(n),r.blob=0===i.getBlob("application/zip").size}catch(e){r.blob=!1}}}try{r.nodestream=!!e("readable-stream").Readable}catch(e){r.nodestream=!1}},{"readable-stream":16}],31:[function(e,t,s){"use strict";for(var o=e("./utils"),h=e("./support"),r=e("./nodejsUtils"),n=e("./stream/GenericWorker"),u=new Array(256),i=0;i<256;i++)u[i]=252<=i?6:248<=i?5:240<=i?4:224<=i?3:192<=i?2:1;u[254]=u[254]=1;function a(){n.call(this,"utf-8 decode"),this.leftOver=null}function l(){n.call(this,"utf-8 encode")}s.utf8encode=function(e){return h.nodebuffer?r.newBufferFrom(e,"utf-8"):function(e){var t,r,n,i,s,a=e.length,o=0;for(i=0;i<a;i++)55296==(64512&(r=e.charCodeAt(i)))&&i+1<a&&56320==(64512&(n=e.charCodeAt(i+1)))&&(r=65536+(r-55296<<10)+(n-56320),i++),o+=r<128?1:r<2048?2:r<65536?3:4;for(t=h.uint8array?new Uint8Array(o):new Array(o),i=s=0;s<o;i++)55296==(64512&(r=e.charCodeAt(i)))&&i+1<a&&56320==(64512&(n=e.charCodeAt(i+1)))&&(r=65536+(r-55296<<10)+(n-56320),i++),r<128?t[s++]=r:(r<2048?t[s++]=192|r>>>6:(r<65536?t[s++]=224|r>>>12:(t[s++]=240|r>>>18,t[s++]=128|r>>>12&63),t[s++]=128|r>>>6&63),t[s++]=128|63&r);return t}(e)},s.utf8decode=function(e){return h.nodebuffer?o.transformTo("nodebuffer",e).toString("utf-8"):function(e){var t,r,n,i,s=e.length,a=new Array(2*s);for(t=r=0;t<s;)if((n=e[t++])<128)a[r++]=n;else if(4<(i=u[n]))a[r++]=65533,t+=i-1;else{for(n&=2===i?31:3===i?15:7;1<i&&t<s;)n=n<<6|63&e[t++],i--;1<i?a[r++]=65533:n<65536?a[r++]=n:(n-=65536,a[r++]=55296|n>>10&1023,a[r++]=56320|1023&n)}return a.length!==r&&(a.subarray?a=a.subarray(0,r):a.length=r),o.applyFromCharCode(a)}(e=o.transformTo(h.uint8array?"uint8array":"array",e))},o.inherits(a,n),a.prototype.processChunk=function(e){var t=o.transformTo(h.uint8array?"uint8array":"array",e.data);if(this.leftOver&&this.leftOver.length){if(h.uint8array){var r=t;(t=new Uint8Array(r.length+this.leftOver.length)).set(this.leftOver,0),t.set(r,this.leftOver.length)}else t=this.leftOver.concat(t);this.leftOver=null}var n=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0?t:0===r?t:r+u[e[r]]>t?r:t}(t),i=t;n!==t.length&&(h.uint8array?(i=t.subarray(0,n),this.leftOver=t.subarray(n,t.length)):(i=t.slice(0,n),this.leftOver=t.slice(n,t.length))),this.push({data:s.utf8decode(i),meta:e.meta})},a.prototype.flush=function(){this.leftOver&&this.leftOver.length&&(this.push({data:s.utf8decode(this.leftOver),meta:{}}),this.leftOver=null)},s.Utf8DecodeWorker=a,o.inherits(l,n),l.prototype.processChunk=function(e){this.push({data:s.utf8encode(e.data),meta:e.meta})},s.Utf8EncodeWorker=l},{"./nodejsUtils":14,"./stream/GenericWorker":28,"./support":30,"./utils":32}],32:[function(e,t,a){"use strict";var o=e("./support"),h=e("./base64"),r=e("./nodejsUtils"),u=e("./external");function n(e){return e}function l(e,t){for(var r=0;r<e.length;++r)t[r]=255&e.charCodeAt(r);return t}e("setimmediate"),a.newBlob=function(t,r){a.checkSupport("blob");try{return new Blob([t],{type:r})}catch(e){try{var n=new(self.BlobBuilder||self.WebKitBlobBuilder||self.MozBlobBuilder||self.MSBlobBuilder);return n.append(t),n.getBlob(r)}catch(e){throw new Error("Bug : can't construct the Blob.")}}};var i={stringifyByChunk:function(e,t,r){var n=[],i=0,s=e.length;if(s<=r)return String.fromCharCode.apply(null,e);for(;i<s;)"array"===t||"nodebuffer"===t?n.push(String.fromCharCode.apply(null,e.slice(i,Math.min(i+r,s)))):n.push(String.fromCharCode.apply(null,e.subarray(i,Math.min(i+r,s)))),i+=r;return n.join("")},stringifyByChar:function(e){for(var t="",r=0;r<e.length;r++)t+=String.fromCharCode(e[r]);return t},applyCanBeUsed:{uint8array:function(){try{return o.uint8array&&1===String.fromCharCode.apply(null,new Uint8Array(1)).length}catch(e){return!1}}(),nodebuffer:function(){try{return o.nodebuffer&&1===String.fromCharCode.apply(null,r.allocBuffer(1)).length}catch(e){return!1}}()}};function s(e){var t=65536,r=a.getTypeOf(e),n=!0;if("uint8array"===r?n=i.applyCanBeUsed.uint8array:"nodebuffer"===r&&(n=i.applyCanBeUsed.nodebuffer),n)for(;1<t;)try{return i.stringifyByChunk(e,r,t)}catch(e){t=Math.floor(t/2)}return i.stringifyByChar(e)}function f(e,t){for(var r=0;r<e.length;r++)t[r]=e[r];return t}a.applyFromCharCode=s;var c={};c.string={string:n,array:function(e){return l(e,new Array(e.length))},arraybuffer:function(e){return c.string.uint8array(e).buffer},uint8array:function(e){return l(e,new Uint8Array(e.length))},nodebuffer:function(e){return l(e,r.allocBuffer(e.length))}},c.array={string:s,array:n,arraybuffer:function(e){return new Uint8Array(e).buffer},uint8array:function(e){return new Uint8Array(e)},nodebuffer:function(e){return r.newBufferFrom(e)}},c.arraybuffer={string:function(e){return s(new Uint8Array(e))},array:function(e){return f(new Uint8Array(e),new Array(e.byteLength))},arraybuffer:n,uint8array:function(e){return new Uint8Array(e)},nodebuffer:function(e){return r.newBufferFrom(new Uint8Array(e))}},c.uint8array={string:s,array:function(e){return f(e,new Array(e.length))},arraybuffer:function(e){return e.buffer},uint8array:n,nodebuffer:function(e){return r.newBufferFrom(e)}},c.nodebuffer={string:s,array:function(e){return f(e,new Array(e.length))},arraybuffer:function(e){return c.nodebuffer.uint8array(e).buffer},uint8array:function(e){return f(e,new Uint8Array(e.length))},nodebuffer:n},a.transformTo=function(e,t){if(t=t||"",!e)return t;a.checkSupport(e);var r=a.getTypeOf(t);return c[r][e](t)},a.resolve=function(e){for(var t=e.split("/"),r=[],n=0;n<t.length;n++){var i=t[n];"."===i||""===i&&0!==n&&n!==t.length-1||(".."===i?r.pop():r.push(i))}return r.join("/")},a.getTypeOf=function(e){return"string"==typeof e?"string":"[object Array]"===Object.prototype.toString.call(e)?"array":o.nodebuffer&&r.isBuffer(e)?"nodebuffer":o.uint8array&&e instanceof Uint8Array?"uint8array":o.arraybuffer&&e instanceof ArrayBuffer?"arraybuffer":void 0},a.checkSupport=function(e){if(!o[e.toLowerCase()])throw new Error(e+" is not supported by this platform")},a.MAX_VALUE_16BITS=65535,a.MAX_VALUE_32BITS=-1,a.pretty=function(e){var t,r,n="";for(r=0;r<(e||"").length;r++)n+="\\x"+((t=e.charCodeAt(r))<16?"0":"")+t.toString(16).toUpperCase();return n},a.delay=function(e,t,r){setImmediate(function(){e.apply(r||null,t||[])})},a.inherits=function(e,t){function r(){}r.prototype=t.prototype,e.prototype=new r},a.extend=function(){var e,t,r={};for(e=0;e<arguments.length;e++)for(t in arguments[e])Object.prototype.hasOwnProperty.call(arguments[e],t)&&void 0===r[t]&&(r[t]=arguments[e][t]);return r},a.prepareContent=function(r,e,n,i,s){return u.Promise.resolve(e).then(function(n){return o.blob&&(n instanceof Blob||-1!==["[object File]","[object Blob]"].indexOf(Object.prototype.toString.call(n)))&&"undefined"!=typeof FileReader?new u.Promise(function(t,r){var e=new FileReader;e.onload=function(e){t(e.target.result)},e.onerror=function(e){r(e.target.error)},e.readAsArrayBuffer(n)}):n}).then(function(e){var t=a.getTypeOf(e);return t?("arraybuffer"===t?e=a.transformTo("uint8array",e):"string"===t&&(s?e=h.decode(e):n&&!0!==i&&(e=function(e){return l(e,o.uint8array?new Uint8Array(e.length):new Array(e.length))}(e))),e):u.Promise.reject(new Error("Can't read the data of '"+r+"'. Is it in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?"))})}},{"./base64":1,"./external":6,"./nodejsUtils":14,"./support":30,setimmediate:54}],33:[function(e,t,r){"use strict";var n=e("./reader/readerFor"),i=e("./utils"),s=e("./signature"),a=e("./zipEntry"),o=e("./support");function h(e){this.files=[],this.loadOptions=e}h.prototype={checkSignature:function(e){if(!this.reader.readAndCheckSignature(e)){this.reader.index-=4;var t=this.reader.readString(4);throw new Error("Corrupted zip or bug: unexpected signature ("+i.pretty(t)+", expected "+i.pretty(e)+")")}},isSignature:function(e,t){var r=this.reader.index;this.reader.setIndex(e);var n=this.reader.readString(4)===t;return this.reader.setIndex(r),n},readBlockEndOfCentral:function(){this.diskNumber=this.reader.readInt(2),this.diskWithCentralDirStart=this.reader.readInt(2),this.centralDirRecordsOnThisDisk=this.reader.readInt(2),this.centralDirRecords=this.reader.readInt(2),this.centralDirSize=this.reader.readInt(4),this.centralDirOffset=this.reader.readInt(4),this.zipCommentLength=this.reader.readInt(2);var e=this.reader.readData(this.zipCommentLength),t=o.uint8array?"uint8array":"array",r=i.transformTo(t,e);this.zipComment=this.loadOptions.decodeFileName(r)},readBlockZip64EndOfCentral:function(){this.zip64EndOfCentralSize=this.reader.readInt(8),this.reader.skip(4),this.diskNumber=this.reader.readInt(4),this.diskWithCentralDirStart=this.reader.readInt(4),this.centralDirRecordsOnThisDisk=this.reader.readInt(8),this.centralDirRecords=this.reader.readInt(8),this.centralDirSize=this.reader.readInt(8),this.centralDirOffset=this.reader.readInt(8),this.zip64ExtensibleData={};for(var e,t,r,n=this.zip64EndOfCentralSize-44;0<n;)e=this.reader.readInt(2),t=this.reader.readInt(4),r=this.reader.readData(t),this.zip64ExtensibleData[e]={id:e,length:t,value:r}},readBlockZip64EndOfCentralLocator:function(){if(this.diskWithZip64CentralDirStart=this.reader.readInt(4),this.relativeOffsetEndOfZip64CentralDir=this.reader.readInt(8),this.disksCount=this.reader.readInt(4),1<this.disksCount)throw new Error("Multi-volumes zip are not supported")},readLocalFiles:function(){var e,t;for(e=0;e<this.files.length;e++)t=this.files[e],this.reader.setIndex(t.localHeaderOffset),this.checkSignature(s.LOCAL_FILE_HEADER),t.readLocalPart(this.reader),t.handleUTF8(),t.processAttributes()},readCentralDir:function(){var e;for(this.reader.setIndex(this.centralDirOffset);this.reader.readAndCheckSignature(s.CENTRAL_FILE_HEADER);)(e=new a({zip64:this.zip64},this.loadOptions)).readCentralPart(this.reader),this.files.push(e);if(this.centralDirRecords!==this.files.length&&0!==this.centralDirRecords&&0===this.files.length)throw new Error("Corrupted zip or bug: expected "+this.centralDirRecords+" records in central dir, got "+this.files.length)},readEndOfCentral:function(){var e=this.reader.lastIndexOfSignature(s.CENTRAL_DIRECTORY_END);if(e<0)throw!this.isSignature(0,s.LOCAL_FILE_HEADER)?new Error("Can't find end of central directory : is this a zip file ? If it is, see https://stuk.github.io/jszip/documentation/howto/read_zip.html"):new Error("Corrupted zip: can't find end of central directory");this.reader.setIndex(e);var t=e;if(this.checkSignature(s.CENTRAL_DIRECTORY_END),this.readBlockEndOfCentral(),this.diskNumber===i.MAX_VALUE_16BITS||this.diskWithCentralDirStart===i.MAX_VALUE_16BITS||this.centralDirRecordsOnThisDisk===i.MAX_VALUE_16BITS||this.centralDirRecords===i.MAX_VALUE_16BITS||this.centralDirSize===i.MAX_VALUE_32BITS||this.centralDirOffset===i.MAX_VALUE_32BITS){if(this.zip64=!0,(e=this.reader.lastIndexOfSignature(s.ZIP64_CENTRAL_DIRECTORY_LOCATOR))<0)throw new Error("Corrupted zip: can't find the ZIP64 end of central directory locator");if(this.reader.setIndex(e),this.checkSignature(s.ZIP64_CENTRAL_DIRECTORY_LOCATOR),this.readBlockZip64EndOfCentralLocator(),!this.isSignature(this.relativeOffsetEndOfZip64CentralDir,s.ZIP64_CENTRAL_DIRECTORY_END)&&(this.relativeOffsetEndOfZip64CentralDir=this.reader.lastIndexOfSignature(s.ZIP64_CENTRAL_DIRECTORY_END),this.relativeOffsetEndOfZip64CentralDir<0))throw new Error("Corrupted zip: can't find the ZIP64 end of central directory");this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir),this.checkSignature(s.ZIP64_CENTRAL_DIRECTORY_END),this.readBlockZip64EndOfCentral()}var r=this.centralDirOffset+this.centralDirSize;this.zip64&&(r+=20,r+=12+this.zip64EndOfCentralSize);var n=t-r;if(0<n)this.isSignature(t,s.CENTRAL_FILE_HEADER)||(this.reader.zero=n);else if(n<0)throw new Error("Corrupted zip: missing "+Math.abs(n)+" bytes.")},prepareReader:function(e){this.reader=n(e)},load:function(e){this.prepareReader(e),this.readEndOfCentral(),this.readCentralDir(),this.readLocalFiles()}},t.exports=h},{"./reader/readerFor":22,"./signature":23,"./support":30,"./utils":32,"./zipEntry":34}],34:[function(e,t,r){"use strict";var n=e("./reader/readerFor"),s=e("./utils"),i=e("./compressedObject"),a=e("./crc32"),o=e("./utf8"),h=e("./compressions"),u=e("./support");function l(e,t){this.options=e,this.loadOptions=t}l.prototype={isEncrypted:function(){return 1==(1&this.bitFlag)},useUTF8:function(){return 2048==(2048&this.bitFlag)},readLocalPart:function(e){var t,r;if(e.skip(22),this.fileNameLength=e.readInt(2),r=e.readInt(2),this.fileName=e.readData(this.fileNameLength),e.skip(r),-1===this.compressedSize||-1===this.uncompressedSize)throw new Error("Bug or corrupted zip : didn't get enough information from the central directory (compressedSize === -1 || uncompressedSize === -1)");if(null===(t=function(e){for(var t in h)if(Object.prototype.hasOwnProperty.call(h,t)&&h[t].magic===e)return h[t];return null}(this.compressionMethod)))throw new Error("Corrupted zip : compression "+s.pretty(this.compressionMethod)+" unknown (inner file : "+s.transformTo("string",this.fileName)+")");this.decompressed=new i(this.compressedSize,this.uncompressedSize,this.crc32,t,e.readData(this.compressedSize))},readCentralPart:function(e){this.versionMadeBy=e.readInt(2),e.skip(2),this.bitFlag=e.readInt(2),this.compressionMethod=e.readString(2),this.date=e.readDate(),this.crc32=e.readInt(4),this.compressedSize=e.readInt(4),this.uncompressedSize=e.readInt(4);var t=e.readInt(2);if(this.extraFieldsLength=e.readInt(2),this.fileCommentLength=e.readInt(2),this.diskNumberStart=e.readInt(2),this.internalFileAttributes=e.readInt(2),this.externalFileAttributes=e.readInt(4),this.localHeaderOffset=e.readInt(4),this.isEncrypted())throw new Error("Encrypted zip are not supported");e.skip(t),this.readExtraFields(e),this.parseZIP64ExtraField(e),this.fileComment=e.readData(this.fileCommentLength)},processAttributes:function(){this.unixPermissions=null,this.dosPermissions=null;var e=this.versionMadeBy>>8;this.dir=!!(16&this.externalFileAttributes),0==e&&(this.dosPermissions=63&this.externalFileAttributes),3==e&&(this.unixPermissions=this.externalFileAttributes>>16&65535),this.dir||"/"!==this.fileNameStr.slice(-1)||(this.dir=!0)},parseZIP64ExtraField:function(){if(this.extraFields[1]){var e=n(this.extraFields[1].value);this.uncompressedSize===s.MAX_VALUE_32BITS&&(this.uncompressedSize=e.readInt(8)),this.compressedSize===s.MAX_VALUE_32BITS&&(this.compressedSize=e.readInt(8)),this.localHeaderOffset===s.MAX_VALUE_32BITS&&(this.localHeaderOffset=e.readInt(8)),this.diskNumberStart===s.MAX_VALUE_32BITS&&(this.diskNumberStart=e.readInt(4))}},readExtraFields:function(e){var t,r,n,i=e.index+this.extraFieldsLength;for(this.extraFields||(this.extraFields={});e.index+4<i;)t=e.readInt(2),r=e.readInt(2),n=e.readData(r),this.extraFields[t]={id:t,length:r,value:n};e.setIndex(i)},handleUTF8:function(){var e=u.uint8array?"uint8array":"array";if(this.useUTF8())this.fileNameStr=o.utf8decode(this.fileName),this.fileCommentStr=o.utf8decode(this.fileComment);else{var t=this.findExtraFieldUnicodePath();if(null!==t)this.fileNameStr=t;else{var r=s.transformTo(e,this.fileName);this.fileNameStr=this.loadOptions.decodeFileName(r)}var n=this.findExtraFieldUnicodeComment();if(null!==n)this.fileCommentStr=n;else{var i=s.transformTo(e,this.fileComment);this.fileCommentStr=this.loadOptions.decodeFileName(i)}}},findExtraFieldUnicodePath:function(){var e=this.extraFields[28789];if(e){var t=n(e.value);return 1!==t.readInt(1)?null:a(this.fileName)!==t.readInt(4)?null:o.utf8decode(t.readData(e.length-5))}return null},findExtraFieldUnicodeComment:function(){var e=this.extraFields[25461];if(e){var t=n(e.value);return 1!==t.readInt(1)?null:a(this.fileComment)!==t.readInt(4)?null:o.utf8decode(t.readData(e.length-5))}return null}},t.exports=l},{"./compressedObject":2,"./compressions":3,"./crc32":4,"./reader/readerFor":22,"./support":30,"./utf8":31,"./utils":32}],35:[function(e,t,r){"use strict";function n(e,t,r){this.name=e,this.dir=r.dir,this.date=r.date,this.comment=r.comment,this.unixPermissions=r.unixPermissions,this.dosPermissions=r.dosPermissions,this._data=t,this._dataBinary=r.binary,this.options={compression:r.compression,compressionOptions:r.compressionOptions}}var s=e("./stream/StreamHelper"),i=e("./stream/DataWorker"),a=e("./utf8"),o=e("./compressedObject"),h=e("./stream/GenericWorker");n.prototype={internalStream:function(e){var t=null,r="string";try{if(!e)throw new Error("No output type specified.");var n="string"===(r=e.toLowerCase())||"text"===r;"binarystring"!==r&&"text"!==r||(r="string"),t=this._decompressWorker();var i=!this._dataBinary;i&&!n&&(t=t.pipe(new a.Utf8EncodeWorker)),!i&&n&&(t=t.pipe(new a.Utf8DecodeWorker))}catch(e){(t=new h("error")).error(e)}return new s(t,r,"")},async:function(e,t){return this.internalStream(e).accumulate(t)},nodeStream:function(e,t){return this.internalStream(e||"nodebuffer").toNodejsStream(t)},_compressWorker:function(e,t){if(this._data instanceof o&&this._data.compression.magic===e.magic)return this._data.getCompressedWorker();var r=this._decompressWorker();return this._dataBinary||(r=r.pipe(new a.Utf8EncodeWorker)),o.createWorkerFrom(r,e,t)},_decompressWorker:function(){return this._data instanceof o?this._data.getContentWorker():this._data instanceof h?this._data:new i(this._data)}};for(var u=["asText","asBinary","asNodeBuffer","asUint8Array","asArrayBuffer"],l=function(){throw new Error("This method has been removed in JSZip 3.0, please check the upgrade guide.")},f=0;f<u.length;f++)n.prototype[u[f]]=l;t.exports=n},{"./compressedObject":2,"./stream/DataWorker":27,"./stream/GenericWorker":28,"./stream/StreamHelper":29,"./utf8":31}],36:[function(e,l,t){(function(t){"use strict";var r,n,e=t.MutationObserver||t.WebKitMutationObserver;if(e){var i=0,s=new e(u),a=t.document.createTextNode("");s.observe(a,{characterData:!0}),r=function(){a.data=i=++i%2}}else if(t.setImmediate||void 0===t.MessageChannel)r="document"in t&&"onreadystatechange"in t.document.createElement("script")?function(){var e=t.document.createElement("script");e.onreadystatechange=function(){u(),e.onreadystatechange=null,e.parentNode.removeChild(e),e=null},t.document.documentElement.appendChild(e)}:function(){setTimeout(u,0)};else{var o=new t.MessageChannel;o.port1.onmessage=u,r=function(){o.port2.postMessage(0)}}var h=[];function u(){var e,t;n=!0;for(var r=h.length;r;){for(t=h,h=[],e=-1;++e<r;)t[e]();r=h.length}n=!1}l.exports=function(e){1!==h.push(e)||n||r()}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],37:[function(e,t,r){"use strict";var i=e("immediate");function u(){}var l={},s=["REJECTED"],a=["FULFILLED"],n=["PENDING"];function o(e){if("function"!=typeof e)throw new TypeError("resolver must be a function");this.state=n,this.queue=[],this.outcome=void 0,e!==u&&d(this,e)}function h(e,t,r){this.promise=e,"function"==typeof t&&(this.onFulfilled=t,this.callFulfilled=this.otherCallFulfilled),"function"==typeof r&&(this.onRejected=r,this.callRejected=this.otherCallRejected)}function f(t,r,n){i(function(){var e;try{e=r(n)}catch(e){return l.reject(t,e)}e===t?l.reject(t,new TypeError("Cannot resolve promise with itself")):l.resolve(t,e)})}function c(e){var t=e&&e.then;if(e&&("object"==typeof e||"function"==typeof e)&&"function"==typeof t)return function(){t.apply(e,arguments)}}function d(t,e){var r=!1;function n(e){r||(r=!0,l.reject(t,e))}function i(e){r||(r=!0,l.resolve(t,e))}var s=p(function(){e(i,n)});"error"===s.status&&n(s.value)}function p(e,t){var r={};try{r.value=e(t),r.status="success"}catch(e){r.status="error",r.value=e}return r}(t.exports=o).prototype.finally=function(t){if("function"!=typeof t)return this;var r=this.constructor;return this.then(function(e){return r.resolve(t()).then(function(){return e})},function(e){return r.resolve(t()).then(function(){throw e})})},o.prototype.catch=function(e){return this.then(null,e)},o.prototype.then=function(e,t){if("function"!=typeof e&&this.state===a||"function"!=typeof t&&this.state===s)return this;var r=new this.constructor(u);this.state!==n?f(r,this.state===a?e:t,this.outcome):this.queue.push(new h(r,e,t));return r},h.prototype.callFulfilled=function(e){l.resolve(this.promise,e)},h.prototype.otherCallFulfilled=function(e){f(this.promise,this.onFulfilled,e)},h.prototype.callRejected=function(e){l.reject(this.promise,e)},h.prototype.otherCallRejected=function(e){f(this.promise,this.onRejected,e)},l.resolve=function(e,t){var r=p(c,t);if("error"===r.status)return l.reject(e,r.value);var n=r.value;if(n)d(e,n);else{e.state=a,e.outcome=t;for(var i=-1,s=e.queue.length;++i<s;)e.queue[i].callFulfilled(t)}return e},l.reject=function(e,t){e.state=s,e.outcome=t;for(var r=-1,n=e.queue.length;++r<n;)e.queue[r].callRejected(t);return e},o.resolve=function(e){if(e instanceof this)return e;return l.resolve(new this(u),e)},o.reject=function(e){var t=new this(u);return l.reject(t,e)},o.all=function(e){var r=this;if("[object Array]"!==Object.prototype.toString.call(e))return this.reject(new TypeError("must be an array"));var n=e.length,i=!1;if(!n)return this.resolve([]);var s=new Array(n),a=0,t=-1,o=new this(u);for(;++t<n;)h(e[t],t);return o;function h(e,t){r.resolve(e).then(function(e){s[t]=e,++a!==n||i||(i=!0,l.resolve(o,s))},function(e){i||(i=!0,l.reject(o,e))})}},o.race=function(e){var t=this;if("[object Array]"!==Object.prototype.toString.call(e))return this.reject(new TypeError("must be an array"));var r=e.length,n=!1;if(!r)return this.resolve([]);var i=-1,s=new this(u);for(;++i<r;)a=e[i],t.resolve(a).then(function(e){n||(n=!0,l.resolve(s,e))},function(e){n||(n=!0,l.reject(s,e))});var a;return s}},{immediate:36}],38:[function(e,t,r){"use strict";var n={};(0,e("./lib/utils/common").assign)(n,e("./lib/deflate"),e("./lib/inflate"),e("./lib/zlib/constants")),t.exports=n},{"./lib/deflate":39,"./lib/inflate":40,"./lib/utils/common":41,"./lib/zlib/constants":44}],39:[function(e,t,r){"use strict";var a=e("./zlib/deflate"),o=e("./utils/common"),h=e("./utils/strings"),i=e("./zlib/messages"),s=e("./zlib/zstream"),u=Object.prototype.toString,l=0,f=-1,c=0,d=8;function p(e){if(!(this instanceof p))return new p(e);this.options=o.assign({level:f,method:d,chunkSize:16384,windowBits:15,memLevel:8,strategy:c,to:""},e||{});var t=this.options;t.raw&&0<t.windowBits?t.windowBits=-t.windowBits:t.gzip&&0<t.windowBits&&t.windowBits<16&&(t.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new s,this.strm.avail_out=0;var r=a.deflateInit2(this.strm,t.level,t.method,t.windowBits,t.memLevel,t.strategy);if(r!==l)throw new Error(i[r]);if(t.header&&a.deflateSetHeader(this.strm,t.header),t.dictionary){var n;if(n="string"==typeof t.dictionary?h.string2buf(t.dictionary):"[object ArrayBuffer]"===u.call(t.dictionary)?new Uint8Array(t.dictionary):t.dictionary,(r=a.deflateSetDictionary(this.strm,n))!==l)throw new Error(i[r]);this._dict_set=!0}}function n(e,t){var r=new p(t);if(r.push(e,!0),r.err)throw r.msg||i[r.err];return r.result}p.prototype.push=function(e,t){var r,n,i=this.strm,s=this.options.chunkSize;if(this.ended)return!1;n=t===~~t?t:!0===t?4:0,"string"==typeof e?i.input=h.string2buf(e):"[object ArrayBuffer]"===u.call(e)?i.input=new Uint8Array(e):i.input=e,i.next_in=0,i.avail_in=i.input.length;do{if(0===i.avail_out&&(i.output=new o.Buf8(s),i.next_out=0,i.avail_out=s),1!==(r=a.deflate(i,n))&&r!==l)return this.onEnd(r),!(this.ended=!0);0!==i.avail_out&&(0!==i.avail_in||4!==n&&2!==n)||("string"===this.options.to?this.onData(h.buf2binstring(o.shrinkBuf(i.output,i.next_out))):this.onData(o.shrinkBuf(i.output,i.next_out)))}while((0<i.avail_in||0===i.avail_out)&&1!==r);return 4===n?(r=a.deflateEnd(this.strm),this.onEnd(r),this.ended=!0,r===l):2!==n||(this.onEnd(l),!(i.avail_out=0))},p.prototype.onData=function(e){this.chunks.push(e)},p.prototype.onEnd=function(e){e===l&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=o.flattenChunks(this.chunks)),this.chunks=[],this.err=e,this.msg=this.strm.msg},r.Deflate=p,r.deflate=n,r.deflateRaw=function(e,t){return(t=t||{}).raw=!0,n(e,t)},r.gzip=function(e,t){return(t=t||{}).gzip=!0,n(e,t)}},{"./utils/common":41,"./utils/strings":42,"./zlib/deflate":46,"./zlib/messages":51,"./zlib/zstream":53}],40:[function(e,t,r){"use strict";var c=e("./zlib/inflate"),d=e("./utils/common"),p=e("./utils/strings"),m=e("./zlib/constants"),n=e("./zlib/messages"),i=e("./zlib/zstream"),s=e("./zlib/gzheader"),_=Object.prototype.toString;function a(e){if(!(this instanceof a))return new a(e);this.options=d.assign({chunkSize:16384,windowBits:0,to:""},e||{});var t=this.options;t.raw&&0<=t.windowBits&&t.windowBits<16&&(t.windowBits=-t.windowBits,0===t.windowBits&&(t.windowBits=-15)),!(0<=t.windowBits&&t.windowBits<16)||e&&e.windowBits||(t.windowBits+=32),15<t.windowBits&&t.windowBits<48&&0==(15&t.windowBits)&&(t.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new i,this.strm.avail_out=0;var r=c.inflateInit2(this.strm,t.windowBits);if(r!==m.Z_OK)throw new Error(n[r]);this.header=new s,c.inflateGetHeader(this.strm,this.header)}function o(e,t){var r=new a(t);if(r.push(e,!0),r.err)throw r.msg||n[r.err];return r.result}a.prototype.push=function(e,t){var r,n,i,s,a,o,h=this.strm,u=this.options.chunkSize,l=this.options.dictionary,f=!1;if(this.ended)return!1;n=t===~~t?t:!0===t?m.Z_FINISH:m.Z_NO_FLUSH,"string"==typeof e?h.input=p.binstring2buf(e):"[object ArrayBuffer]"===_.call(e)?h.input=new Uint8Array(e):h.input=e,h.next_in=0,h.avail_in=h.input.length;do{if(0===h.avail_out&&(h.output=new d.Buf8(u),h.next_out=0,h.avail_out=u),(r=c.inflate(h,m.Z_NO_FLUSH))===m.Z_NEED_DICT&&l&&(o="string"==typeof l?p.string2buf(l):"[object ArrayBuffer]"===_.call(l)?new Uint8Array(l):l,r=c.inflateSetDictionary(this.strm,o)),r===m.Z_BUF_ERROR&&!0===f&&(r=m.Z_OK,f=!1),r!==m.Z_STREAM_END&&r!==m.Z_OK)return this.onEnd(r),!(this.ended=!0);h.next_out&&(0!==h.avail_out&&r!==m.Z_STREAM_END&&(0!==h.avail_in||n!==m.Z_FINISH&&n!==m.Z_SYNC_FLUSH)||("string"===this.options.to?(i=p.utf8border(h.output,h.next_out),s=h.next_out-i,a=p.buf2string(h.output,i),h.next_out=s,h.avail_out=u-s,s&&d.arraySet(h.output,h.output,i,s,0),this.onData(a)):this.onData(d.shrinkBuf(h.output,h.next_out)))),0===h.avail_in&&0===h.avail_out&&(f=!0)}while((0<h.avail_in||0===h.avail_out)&&r!==m.Z_STREAM_END);return r===m.Z_STREAM_END&&(n=m.Z_FINISH),n===m.Z_FINISH?(r=c.inflateEnd(this.strm),this.onEnd(r),this.ended=!0,r===m.Z_OK):n!==m.Z_SYNC_FLUSH||(this.onEnd(m.Z_OK),!(h.avail_out=0))},a.prototype.onData=function(e){this.chunks.push(e)},a.prototype.onEnd=function(e){e===m.Z_OK&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=d.flattenChunks(this.chunks)),this.chunks=[],this.err=e,this.msg=this.strm.msg},r.Inflate=a,r.inflate=o,r.inflateRaw=function(e,t){return(t=t||{}).raw=!0,o(e,t)},r.ungzip=o},{"./utils/common":41,"./utils/strings":42,"./zlib/constants":44,"./zlib/gzheader":47,"./zlib/inflate":49,"./zlib/messages":51,"./zlib/zstream":53}],41:[function(e,t,r){"use strict";var n="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Int32Array;r.assign=function(e){for(var t=Array.prototype.slice.call(arguments,1);t.length;){var r=t.shift();if(r){if("object"!=typeof r)throw new TypeError(r+"must be non-object");for(var n in r)r.hasOwnProperty(n)&&(e[n]=r[n])}}return e},r.shrinkBuf=function(e,t){return e.length===t?e:e.subarray?e.subarray(0,t):(e.length=t,e)};var i={arraySet:function(e,t,r,n,i){if(t.subarray&&e.subarray)e.set(t.subarray(r,r+n),i);else for(var s=0;s<n;s++)e[i+s]=t[r+s]},flattenChunks:function(e){var t,r,n,i,s,a;for(t=n=0,r=e.length;t<r;t++)n+=e[t].length;for(a=new Uint8Array(n),t=i=0,r=e.length;t<r;t++)s=e[t],a.set(s,i),i+=s.length;return a}},s={arraySet:function(e,t,r,n,i){for(var s=0;s<n;s++)e[i+s]=t[r+s]},flattenChunks:function(e){return[].concat.apply([],e)}};r.setTyped=function(e){e?(r.Buf8=Uint8Array,r.Buf16=Uint16Array,r.Buf32=Int32Array,r.assign(r,i)):(r.Buf8=Array,r.Buf16=Array,r.Buf32=Array,r.assign(r,s))},r.setTyped(n)},{}],42:[function(e,t,r){"use strict";var h=e("./common"),i=!0,s=!0;try{String.fromCharCode.apply(null,[0])}catch(e){i=!1}try{String.fromCharCode.apply(null,new Uint8Array(1))}catch(e){s=!1}for(var u=new h.Buf8(256),n=0;n<256;n++)u[n]=252<=n?6:248<=n?5:240<=n?4:224<=n?3:192<=n?2:1;function l(e,t){if(t<65537&&(e.subarray&&s||!e.subarray&&i))return String.fromCharCode.apply(null,h.shrinkBuf(e,t));for(var r="",n=0;n<t;n++)r+=String.fromCharCode(e[n]);return r}u[254]=u[254]=1,r.string2buf=function(e){var t,r,n,i,s,a=e.length,o=0;for(i=0;i<a;i++)55296==(64512&(r=e.charCodeAt(i)))&&i+1<a&&56320==(64512&(n=e.charCodeAt(i+1)))&&(r=65536+(r-55296<<10)+(n-56320),i++),o+=r<128?1:r<2048?2:r<65536?3:4;for(t=new h.Buf8(o),i=s=0;s<o;i++)55296==(64512&(r=e.charCodeAt(i)))&&i+1<a&&56320==(64512&(n=e.charCodeAt(i+1)))&&(r=65536+(r-55296<<10)+(n-56320),i++),r<128?t[s++]=r:(r<2048?t[s++]=192|r>>>6:(r<65536?t[s++]=224|r>>>12:(t[s++]=240|r>>>18,t[s++]=128|r>>>12&63),t[s++]=128|r>>>6&63),t[s++]=128|63&r);return t},r.buf2binstring=function(e){return l(e,e.length)},r.binstring2buf=function(e){for(var t=new h.Buf8(e.length),r=0,n=t.length;r<n;r++)t[r]=e.charCodeAt(r);return t},r.buf2string=function(e,t){var r,n,i,s,a=t||e.length,o=new Array(2*a);for(r=n=0;r<a;)if((i=e[r++])<128)o[n++]=i;else if(4<(s=u[i]))o[n++]=65533,r+=s-1;else{for(i&=2===s?31:3===s?15:7;1<s&&r<a;)i=i<<6|63&e[r++],s--;1<s?o[n++]=65533:i<65536?o[n++]=i:(i-=65536,o[n++]=55296|i>>10&1023,o[n++]=56320|1023&i)}return l(o,n)},r.utf8border=function(e,t){var r;for((t=t||e.length)>e.length&&(t=e.length),r=t-1;0<=r&&128==(192&e[r]);)r--;return r<0?t:0===r?t:r+u[e[r]]>t?r:t}},{"./common":41}],43:[function(e,t,r){"use strict";t.exports=function(e,t,r,n){for(var i=65535&e|0,s=e>>>16&65535|0,a=0;0!==r;){for(r-=a=2e3<r?2e3:r;s=s+(i=i+t[n++]|0)|0,--a;);i%=65521,s%=65521}return i|s<<16|0}},{}],44:[function(e,t,r){"use strict";t.exports={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8}},{}],45:[function(e,t,r){"use strict";var o=function(){for(var e,t=[],r=0;r<256;r++){e=r;for(var n=0;n<8;n++)e=1&e?3988292384^e>>>1:e>>>1;t[r]=e}return t}();t.exports=function(e,t,r,n){var i=o,s=n+r;e^=-1;for(var a=n;a<s;a++)e=e>>>8^i[255&(e^t[a])];return-1^e}},{}],46:[function(e,t,r){"use strict";var h,c=e("../utils/common"),u=e("./trees"),d=e("./adler32"),p=e("./crc32"),n=e("./messages"),l=0,f=4,m=0,_=-2,g=-1,b=4,i=2,v=8,y=9,s=286,a=30,o=19,w=2*s+1,k=15,x=3,S=258,z=S+x+1,C=42,E=113,A=1,I=2,O=3,B=4;function R(e,t){return e.msg=n[t],t}function T(e){return(e<<1)-(4<e?9:0)}function D(e){for(var t=e.length;0<=--t;)e[t]=0}function F(e){var t=e.state,r=t.pending;r>e.avail_out&&(r=e.avail_out),0!==r&&(c.arraySet(e.output,t.pending_buf,t.pending_out,r,e.next_out),e.next_out+=r,t.pending_out+=r,e.total_out+=r,e.avail_out-=r,t.pending-=r,0===t.pending&&(t.pending_out=0))}function N(e,t){u._tr_flush_block(e,0<=e.block_start?e.block_start:-1,e.strstart-e.block_start,t),e.block_start=e.strstart,F(e.strm)}function U(e,t){e.pending_buf[e.pending++]=t}function P(e,t){e.pending_buf[e.pending++]=t>>>8&255,e.pending_buf[e.pending++]=255&t}function L(e,t){var r,n,i=e.max_chain_length,s=e.strstart,a=e.prev_length,o=e.nice_match,h=e.strstart>e.w_size-z?e.strstart-(e.w_size-z):0,u=e.window,l=e.w_mask,f=e.prev,c=e.strstart+S,d=u[s+a-1],p=u[s+a];e.prev_length>=e.good_match&&(i>>=2),o>e.lookahead&&(o=e.lookahead);do{if(u[(r=t)+a]===p&&u[r+a-1]===d&&u[r]===u[s]&&u[++r]===u[s+1]){s+=2,r++;do{}while(u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&u[++s]===u[++r]&&s<c);if(n=S-(c-s),s=c-S,a<n){if(e.match_start=t,o<=(a=n))break;d=u[s+a-1],p=u[s+a]}}}while((t=f[t&l])>h&&0!=--i);return a<=e.lookahead?a:e.lookahead}function j(e){var t,r,n,i,s,a,o,h,u,l,f=e.w_size;do{if(i=e.window_size-e.lookahead-e.strstart,e.strstart>=f+(f-z)){for(c.arraySet(e.window,e.window,f,f,0),e.match_start-=f,e.strstart-=f,e.block_start-=f,t=r=e.hash_size;n=e.head[--t],e.head[t]=f<=n?n-f:0,--r;);for(t=r=f;n=e.prev[--t],e.prev[t]=f<=n?n-f:0,--r;);i+=f}if(0===e.strm.avail_in)break;if(a=e.strm,o=e.window,h=e.strstart+e.lookahead,u=i,l=void 0,l=a.avail_in,u<l&&(l=u),r=0===l?0:(a.avail_in-=l,c.arraySet(o,a.input,a.next_in,l,h),1===a.state.wrap?a.adler=d(a.adler,o,l,h):2===a.state.wrap&&(a.adler=p(a.adler,o,l,h)),a.next_in+=l,a.total_in+=l,l),e.lookahead+=r,e.lookahead+e.insert>=x)for(s=e.strstart-e.insert,e.ins_h=e.window[s],e.ins_h=(e.ins_h<<e.hash_shift^e.window[s+1])&e.hash_mask;e.insert&&(e.ins_h=(e.ins_h<<e.hash_shift^e.window[s+x-1])&e.hash_mask,e.prev[s&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=s,s++,e.insert--,!(e.lookahead+e.insert<x)););}while(e.lookahead<z&&0!==e.strm.avail_in)}function Z(e,t){for(var r,n;;){if(e.lookahead<z){if(j(e),e.lookahead<z&&t===l)return A;if(0===e.lookahead)break}if(r=0,e.lookahead>=x&&(e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+x-1])&e.hash_mask,r=e.prev[e.strstart&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=e.strstart),0!==r&&e.strstart-r<=e.w_size-z&&(e.match_length=L(e,r)),e.match_length>=x)if(n=u._tr_tally(e,e.strstart-e.match_start,e.match_length-x),e.lookahead-=e.match_length,e.match_length<=e.max_lazy_match&&e.lookahead>=x){for(e.match_length--;e.strstart++,e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+x-1])&e.hash_mask,r=e.prev[e.strstart&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=e.strstart,0!=--e.match_length;);e.strstart++}else e.strstart+=e.match_length,e.match_length=0,e.ins_h=e.window[e.strstart],e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+1])&e.hash_mask;else n=u._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++;if(n&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=e.strstart<x-1?e.strstart:x-1,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}function W(e,t){for(var r,n,i;;){if(e.lookahead<z){if(j(e),e.lookahead<z&&t===l)return A;if(0===e.lookahead)break}if(r=0,e.lookahead>=x&&(e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+x-1])&e.hash_mask,r=e.prev[e.strstart&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=e.strstart),e.prev_length=e.match_length,e.prev_match=e.match_start,e.match_length=x-1,0!==r&&e.prev_length<e.max_lazy_match&&e.strstart-r<=e.w_size-z&&(e.match_length=L(e,r),e.match_length<=5&&(1===e.strategy||e.match_length===x&&4096<e.strstart-e.match_start)&&(e.match_length=x-1)),e.prev_length>=x&&e.match_length<=e.prev_length){for(i=e.strstart+e.lookahead-x,n=u._tr_tally(e,e.strstart-1-e.prev_match,e.prev_length-x),e.lookahead-=e.prev_length-1,e.prev_length-=2;++e.strstart<=i&&(e.ins_h=(e.ins_h<<e.hash_shift^e.window[e.strstart+x-1])&e.hash_mask,r=e.prev[e.strstart&e.w_mask]=e.head[e.ins_h],e.head[e.ins_h]=e.strstart),0!=--e.prev_length;);if(e.match_available=0,e.match_length=x-1,e.strstart++,n&&(N(e,!1),0===e.strm.avail_out))return A}else if(e.match_available){if((n=u._tr_tally(e,0,e.window[e.strstart-1]))&&N(e,!1),e.strstart++,e.lookahead--,0===e.strm.avail_out)return A}else e.match_available=1,e.strstart++,e.lookahead--}return e.match_available&&(n=u._tr_tally(e,0,e.window[e.strstart-1]),e.match_available=0),e.insert=e.strstart<x-1?e.strstart:x-1,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}function M(e,t,r,n,i){this.good_length=e,this.max_lazy=t,this.nice_length=r,this.max_chain=n,this.func=i}function H(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=v,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new c.Buf16(2*w),this.dyn_dtree=new c.Buf16(2*(2*a+1)),this.bl_tree=new c.Buf16(2*(2*o+1)),D(this.dyn_ltree),D(this.dyn_dtree),D(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new c.Buf16(k+1),this.heap=new c.Buf16(2*s+1),D(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new c.Buf16(2*s+1),D(this.depth),this.l_buf=0,this.lit_bufsize=0,this.last_lit=0,this.d_buf=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}function G(e){var t;return e&&e.state?(e.total_in=e.total_out=0,e.data_type=i,(t=e.state).pending=0,t.pending_out=0,t.wrap<0&&(t.wrap=-t.wrap),t.status=t.wrap?C:E,e.adler=2===t.wrap?0:1,t.last_flush=l,u._tr_init(t),m):R(e,_)}function K(e){var t=G(e);return t===m&&function(e){e.window_size=2*e.w_size,D(e.head),e.max_lazy_match=h[e.level].max_lazy,e.good_match=h[e.level].good_length,e.nice_match=h[e.level].nice_length,e.max_chain_length=h[e.level].max_chain,e.strstart=0,e.block_start=0,e.lookahead=0,e.insert=0,e.match_length=e.prev_length=x-1,e.match_available=0,e.ins_h=0}(e.state),t}function Y(e,t,r,n,i,s){if(!e)return _;var a=1;if(t===g&&(t=6),n<0?(a=0,n=-n):15<n&&(a=2,n-=16),i<1||y<i||r!==v||n<8||15<n||t<0||9<t||s<0||b<s)return R(e,_);8===n&&(n=9);var o=new H;return(e.state=o).strm=e,o.wrap=a,o.gzhead=null,o.w_bits=n,o.w_size=1<<o.w_bits,o.w_mask=o.w_size-1,o.hash_bits=i+7,o.hash_size=1<<o.hash_bits,o.hash_mask=o.hash_size-1,o.hash_shift=~~((o.hash_bits+x-1)/x),o.window=new c.Buf8(2*o.w_size),o.head=new c.Buf16(o.hash_size),o.prev=new c.Buf16(o.w_size),o.lit_bufsize=1<<i+6,o.pending_buf_size=4*o.lit_bufsize,o.pending_buf=new c.Buf8(o.pending_buf_size),o.d_buf=1*o.lit_bufsize,o.l_buf=3*o.lit_bufsize,o.level=t,o.strategy=s,o.method=r,K(e)}h=[new M(0,0,0,0,function(e,t){var r=65535;for(r>e.pending_buf_size-5&&(r=e.pending_buf_size-5);;){if(e.lookahead<=1){if(j(e),0===e.lookahead&&t===l)return A;if(0===e.lookahead)break}e.strstart+=e.lookahead,e.lookahead=0;var n=e.block_start+r;if((0===e.strstart||e.strstart>=n)&&(e.lookahead=e.strstart-n,e.strstart=n,N(e,!1),0===e.strm.avail_out))return A;if(e.strstart-e.block_start>=e.w_size-z&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):(e.strstart>e.block_start&&(N(e,!1),e.strm.avail_out),A)}),new M(4,4,8,4,Z),new M(4,5,16,8,Z),new M(4,6,32,32,Z),new M(4,4,16,16,W),new M(8,16,32,32,W),new M(8,16,128,128,W),new M(8,32,128,256,W),new M(32,128,258,1024,W),new M(32,258,258,4096,W)],r.deflateInit=function(e,t){return Y(e,t,v,15,8,0)},r.deflateInit2=Y,r.deflateReset=K,r.deflateResetKeep=G,r.deflateSetHeader=function(e,t){return e&&e.state?2!==e.state.wrap?_:(e.state.gzhead=t,m):_},r.deflate=function(e,t){var r,n,i,s;if(!e||!e.state||5<t||t<0)return e?R(e,_):_;if(n=e.state,!e.output||!e.input&&0!==e.avail_in||666===n.status&&t!==f)return R(e,0===e.avail_out?-5:_);if(n.strm=e,r=n.last_flush,n.last_flush=t,n.status===C)if(2===n.wrap)e.adler=0,U(n,31),U(n,139),U(n,8),n.gzhead?(U(n,(n.gzhead.text?1:0)+(n.gzhead.hcrc?2:0)+(n.gzhead.extra?4:0)+(n.gzhead.name?8:0)+(n.gzhead.comment?16:0)),U(n,255&n.gzhead.time),U(n,n.gzhead.time>>8&255),U(n,n.gzhead.time>>16&255),U(n,n.gzhead.time>>24&255),U(n,9===n.level?2:2<=n.strategy||n.level<2?4:0),U(n,255&n.gzhead.os),n.gzhead.extra&&n.gzhead.extra.length&&(U(n,255&n.gzhead.extra.length),U(n,n.gzhead.extra.length>>8&255)),n.gzhead.hcrc&&(e.adler=p(e.adler,n.pending_buf,n.pending,0)),n.gzindex=0,n.status=69):(U(n,0),U(n,0),U(n,0),U(n,0),U(n,0),U(n,9===n.level?2:2<=n.strategy||n.level<2?4:0),U(n,3),n.status=E);else{var a=v+(n.w_bits-8<<4)<<8;a|=(2<=n.strategy||n.level<2?0:n.level<6?1:6===n.level?2:3)<<6,0!==n.strstart&&(a|=32),a+=31-a%31,n.status=E,P(n,a),0!==n.strstart&&(P(n,e.adler>>>16),P(n,65535&e.adler)),e.adler=1}if(69===n.status)if(n.gzhead.extra){for(i=n.pending;n.gzindex<(65535&n.gzhead.extra.length)&&(n.pending!==n.pending_buf_size||(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending!==n.pending_buf_size));)U(n,255&n.gzhead.extra[n.gzindex]),n.gzindex++;n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),n.gzindex===n.gzhead.extra.length&&(n.gzindex=0,n.status=73)}else n.status=73;if(73===n.status)if(n.gzhead.name){i=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending===n.pending_buf_size)){s=1;break}s=n.gzindex<n.gzhead.name.length?255&n.gzhead.name.charCodeAt(n.gzindex++):0,U(n,s)}while(0!==s);n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),0===s&&(n.gzindex=0,n.status=91)}else n.status=91;if(91===n.status)if(n.gzhead.comment){i=n.pending;do{if(n.pending===n.pending_buf_size&&(n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),F(e),i=n.pending,n.pending===n.pending_buf_size)){s=1;break}s=n.gzindex<n.gzhead.comment.length?255&n.gzhead.comment.charCodeAt(n.gzindex++):0,U(n,s)}while(0!==s);n.gzhead.hcrc&&n.pending>i&&(e.adler=p(e.adler,n.pending_buf,n.pending-i,i)),0===s&&(n.status=103)}else n.status=103;if(103===n.status&&(n.gzhead.hcrc?(n.pending+2>n.pending_buf_size&&F(e),n.pending+2<=n.pending_buf_size&&(U(n,255&e.adler),U(n,e.adler>>8&255),e.adler=0,n.status=E)):n.status=E),0!==n.pending){if(F(e),0===e.avail_out)return n.last_flush=-1,m}else if(0===e.avail_in&&T(t)<=T(r)&&t!==f)return R(e,-5);if(666===n.status&&0!==e.avail_in)return R(e,-5);if(0!==e.avail_in||0!==n.lookahead||t!==l&&666!==n.status){var o=2===n.strategy?function(e,t){for(var r;;){if(0===e.lookahead&&(j(e),0===e.lookahead)){if(t===l)return A;break}if(e.match_length=0,r=u._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++,r&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}(n,t):3===n.strategy?function(e,t){for(var r,n,i,s,a=e.window;;){if(e.lookahead<=S){if(j(e),e.lookahead<=S&&t===l)return A;if(0===e.lookahead)break}if(e.match_length=0,e.lookahead>=x&&0<e.strstart&&(n=a[i=e.strstart-1])===a[++i]&&n===a[++i]&&n===a[++i]){s=e.strstart+S;do{}while(n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&n===a[++i]&&i<s);e.match_length=S-(s-i),e.match_length>e.lookahead&&(e.match_length=e.lookahead)}if(e.match_length>=x?(r=u._tr_tally(e,1,e.match_length-x),e.lookahead-=e.match_length,e.strstart+=e.match_length,e.match_length=0):(r=u._tr_tally(e,0,e.window[e.strstart]),e.lookahead--,e.strstart++),r&&(N(e,!1),0===e.strm.avail_out))return A}return e.insert=0,t===f?(N(e,!0),0===e.strm.avail_out?O:B):e.last_lit&&(N(e,!1),0===e.strm.avail_out)?A:I}(n,t):h[n.level].func(n,t);if(o!==O&&o!==B||(n.status=666),o===A||o===O)return 0===e.avail_out&&(n.last_flush=-1),m;if(o===I&&(1===t?u._tr_align(n):5!==t&&(u._tr_stored_block(n,0,0,!1),3===t&&(D(n.head),0===n.lookahead&&(n.strstart=0,n.block_start=0,n.insert=0))),F(e),0===e.avail_out))return n.last_flush=-1,m}return t!==f?m:n.wrap<=0?1:(2===n.wrap?(U(n,255&e.adler),U(n,e.adler>>8&255),U(n,e.adler>>16&255),U(n,e.adler>>24&255),U(n,255&e.total_in),U(n,e.total_in>>8&255),U(n,e.total_in>>16&255),U(n,e.total_in>>24&255)):(P(n,e.adler>>>16),P(n,65535&e.adler)),F(e),0<n.wrap&&(n.wrap=-n.wrap),0!==n.pending?m:1)},r.deflateEnd=function(e){var t;return e&&e.state?(t=e.state.status)!==C&&69!==t&&73!==t&&91!==t&&103!==t&&t!==E&&666!==t?R(e,_):(e.state=null,t===E?R(e,-3):m):_},r.deflateSetDictionary=function(e,t){var r,n,i,s,a,o,h,u,l=t.length;if(!e||!e.state)return _;if(2===(s=(r=e.state).wrap)||1===s&&r.status!==C||r.lookahead)return _;for(1===s&&(e.adler=d(e.adler,t,l,0)),r.wrap=0,l>=r.w_size&&(0===s&&(D(r.head),r.strstart=0,r.block_start=0,r.insert=0),u=new c.Buf8(r.w_size),c.arraySet(u,t,l-r.w_size,r.w_size,0),t=u,l=r.w_size),a=e.avail_in,o=e.next_in,h=e.input,e.avail_in=l,e.next_in=0,e.input=t,j(r);r.lookahead>=x;){for(n=r.strstart,i=r.lookahead-(x-1);r.ins_h=(r.ins_h<<r.hash_shift^r.window[n+x-1])&r.hash_mask,r.prev[n&r.w_mask]=r.head[r.ins_h],r.head[r.ins_h]=n,n++,--i;);r.strstart=n,r.lookahead=x-1,j(r)}return r.strstart+=r.lookahead,r.block_start=r.strstart,r.insert=r.lookahead,r.lookahead=0,r.match_length=r.prev_length=x-1,r.match_available=0,e.next_in=o,e.input=h,e.avail_in=a,r.wrap=s,m},r.deflateInfo="pako deflate (from Nodeca project)"},{"../utils/common":41,"./adler32":43,"./crc32":45,"./messages":51,"./trees":52}],47:[function(e,t,r){"use strict";t.exports=function(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1}},{}],48:[function(e,t,r){"use strict";t.exports=function(e,t){var r,n,i,s,a,o,h,u,l,f,c,d,p,m,_,g,b,v,y,w,k,x,S,z,C;r=e.state,n=e.next_in,z=e.input,i=n+(e.avail_in-5),s=e.next_out,C=e.output,a=s-(t-e.avail_out),o=s+(e.avail_out-257),h=r.dmax,u=r.wsize,l=r.whave,f=r.wnext,c=r.window,d=r.hold,p=r.bits,m=r.lencode,_=r.distcode,g=(1<<r.lenbits)-1,b=(1<<r.distbits)-1;e:do{p<15&&(d+=z[n++]<<p,p+=8,d+=z[n++]<<p,p+=8),v=m[d&g];t:for(;;){if(d>>>=y=v>>>24,p-=y,0===(y=v>>>16&255))C[s++]=65535&v;else{if(!(16&y)){if(0==(64&y)){v=m[(65535&v)+(d&(1<<y)-1)];continue t}if(32&y){r.mode=12;break e}e.msg="invalid literal/length code",r.mode=30;break e}w=65535&v,(y&=15)&&(p<y&&(d+=z[n++]<<p,p+=8),w+=d&(1<<y)-1,d>>>=y,p-=y),p<15&&(d+=z[n++]<<p,p+=8,d+=z[n++]<<p,p+=8),v=_[d&b];r:for(;;){if(d>>>=y=v>>>24,p-=y,!(16&(y=v>>>16&255))){if(0==(64&y)){v=_[(65535&v)+(d&(1<<y)-1)];continue r}e.msg="invalid distance code",r.mode=30;break e}if(k=65535&v,p<(y&=15)&&(d+=z[n++]<<p,(p+=8)<y&&(d+=z[n++]<<p,p+=8)),h<(k+=d&(1<<y)-1)){e.msg="invalid distance too far back",r.mode=30;break e}if(d>>>=y,p-=y,(y=s-a)<k){if(l<(y=k-y)&&r.sane){e.msg="invalid distance too far back",r.mode=30;break e}if(S=c,(x=0)===f){if(x+=u-y,y<w){for(w-=y;C[s++]=c[x++],--y;);x=s-k,S=C}}else if(f<y){if(x+=u+f-y,(y-=f)<w){for(w-=y;C[s++]=c[x++],--y;);if(x=0,f<w){for(w-=y=f;C[s++]=c[x++],--y;);x=s-k,S=C}}}else if(x+=f-y,y<w){for(w-=y;C[s++]=c[x++],--y;);x=s-k,S=C}for(;2<w;)C[s++]=S[x++],C[s++]=S[x++],C[s++]=S[x++],w-=3;w&&(C[s++]=S[x++],1<w&&(C[s++]=S[x++]))}else{for(x=s-k;C[s++]=C[x++],C[s++]=C[x++],C[s++]=C[x++],2<(w-=3););w&&(C[s++]=C[x++],1<w&&(C[s++]=C[x++]))}break}}break}}while(n<i&&s<o);n-=w=p>>3,d&=(1<<(p-=w<<3))-1,e.next_in=n,e.next_out=s,e.avail_in=n<i?i-n+5:5-(n-i),e.avail_out=s<o?o-s+257:257-(s-o),r.hold=d,r.bits=p}},{}],49:[function(e,t,r){"use strict";var I=e("../utils/common"),O=e("./adler32"),B=e("./crc32"),R=e("./inffast"),T=e("./inftrees"),D=1,F=2,N=0,U=-2,P=1,n=852,i=592;function L(e){return(e>>>24&255)+(e>>>8&65280)+((65280&e)<<8)+((255&e)<<24)}function s(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new I.Buf16(320),this.work=new I.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function a(e){var t;return e&&e.state?(t=e.state,e.total_in=e.total_out=t.total=0,e.msg="",t.wrap&&(e.adler=1&t.wrap),t.mode=P,t.last=0,t.havedict=0,t.dmax=32768,t.head=null,t.hold=0,t.bits=0,t.lencode=t.lendyn=new I.Buf32(n),t.distcode=t.distdyn=new I.Buf32(i),t.sane=1,t.back=-1,N):U}function o(e){var t;return e&&e.state?((t=e.state).wsize=0,t.whave=0,t.wnext=0,a(e)):U}function h(e,t){var r,n;return e&&e.state?(n=e.state,t<0?(r=0,t=-t):(r=1+(t>>4),t<48&&(t&=15)),t&&(t<8||15<t)?U:(null!==n.window&&n.wbits!==t&&(n.window=null),n.wrap=r,n.wbits=t,o(e))):U}function u(e,t){var r,n;return e?(n=new s,(e.state=n).window=null,(r=h(e,t))!==N&&(e.state=null),r):U}var l,f,c=!0;function j(e){if(c){var t;for(l=new I.Buf32(512),f=new I.Buf32(32),t=0;t<144;)e.lens[t++]=8;for(;t<256;)e.lens[t++]=9;for(;t<280;)e.lens[t++]=7;for(;t<288;)e.lens[t++]=8;for(T(D,e.lens,0,288,l,0,e.work,{bits:9}),t=0;t<32;)e.lens[t++]=5;T(F,e.lens,0,32,f,0,e.work,{bits:5}),c=!1}e.lencode=l,e.lenbits=9,e.distcode=f,e.distbits=5}function Z(e,t,r,n){var i,s=e.state;return null===s.window&&(s.wsize=1<<s.wbits,s.wnext=0,s.whave=0,s.window=new I.Buf8(s.wsize)),n>=s.wsize?(I.arraySet(s.window,t,r-s.wsize,s.wsize,0),s.wnext=0,s.whave=s.wsize):(n<(i=s.wsize-s.wnext)&&(i=n),I.arraySet(s.window,t,r-n,i,s.wnext),(n-=i)?(I.arraySet(s.window,t,r-n,n,0),s.wnext=n,s.whave=s.wsize):(s.wnext+=i,s.wnext===s.wsize&&(s.wnext=0),s.whave<s.wsize&&(s.whave+=i))),0}r.inflateReset=o,r.inflateReset2=h,r.inflateResetKeep=a,r.inflateInit=function(e){return u(e,15)},r.inflateInit2=u,r.inflate=function(e,t){var r,n,i,s,a,o,h,u,l,f,c,d,p,m,_,g,b,v,y,w,k,x,S,z,C=0,E=new I.Buf8(4),A=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];if(!e||!e.state||!e.output||!e.input&&0!==e.avail_in)return U;12===(r=e.state).mode&&(r.mode=13),a=e.next_out,i=e.output,h=e.avail_out,s=e.next_in,n=e.input,o=e.avail_in,u=r.hold,l=r.bits,f=o,c=h,x=N;e:for(;;)switch(r.mode){case P:if(0===r.wrap){r.mode=13;break}for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(2&r.wrap&&35615===u){E[r.check=0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0),l=u=0,r.mode=2;break}if(r.flags=0,r.head&&(r.head.done=!1),!(1&r.wrap)||(((255&u)<<8)+(u>>8))%31){e.msg="incorrect header check",r.mode=30;break}if(8!=(15&u)){e.msg="unknown compression method",r.mode=30;break}if(l-=4,k=8+(15&(u>>>=4)),0===r.wbits)r.wbits=k;else if(k>r.wbits){e.msg="invalid window size",r.mode=30;break}r.dmax=1<<k,e.adler=r.check=1,r.mode=512&u?10:12,l=u=0;break;case 2:for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(r.flags=u,8!=(255&r.flags)){e.msg="unknown compression method",r.mode=30;break}if(57344&r.flags){e.msg="unknown header flags set",r.mode=30;break}r.head&&(r.head.text=u>>8&1),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=3;case 3:for(;l<32;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.head&&(r.head.time=u),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,E[2]=u>>>16&255,E[3]=u>>>24&255,r.check=B(r.check,E,4,0)),l=u=0,r.mode=4;case 4:for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.head&&(r.head.xflags=255&u,r.head.os=u>>8),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0,r.mode=5;case 5:if(1024&r.flags){for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.length=u,r.head&&(r.head.extra_len=u),512&r.flags&&(E[0]=255&u,E[1]=u>>>8&255,r.check=B(r.check,E,2,0)),l=u=0}else r.head&&(r.head.extra=null);r.mode=6;case 6:if(1024&r.flags&&(o<(d=r.length)&&(d=o),d&&(r.head&&(k=r.head.extra_len-r.length,r.head.extra||(r.head.extra=new Array(r.head.extra_len)),I.arraySet(r.head.extra,n,s,d,k)),512&r.flags&&(r.check=B(r.check,n,d,s)),o-=d,s+=d,r.length-=d),r.length))break e;r.length=0,r.mode=7;case 7:if(2048&r.flags){if(0===o)break e;for(d=0;k=n[s+d++],r.head&&k&&r.length<65536&&(r.head.name+=String.fromCharCode(k)),k&&d<o;);if(512&r.flags&&(r.check=B(r.check,n,d,s)),o-=d,s+=d,k)break e}else r.head&&(r.head.name=null);r.length=0,r.mode=8;case 8:if(4096&r.flags){if(0===o)break e;for(d=0;k=n[s+d++],r.head&&k&&r.length<65536&&(r.head.comment+=String.fromCharCode(k)),k&&d<o;);if(512&r.flags&&(r.check=B(r.check,n,d,s)),o-=d,s+=d,k)break e}else r.head&&(r.head.comment=null);r.mode=9;case 9:if(512&r.flags){for(;l<16;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(u!==(65535&r.check)){e.msg="header crc mismatch",r.mode=30;break}l=u=0}r.head&&(r.head.hcrc=r.flags>>9&1,r.head.done=!0),e.adler=r.check=0,r.mode=12;break;case 10:for(;l<32;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}e.adler=r.check=L(u),l=u=0,r.mode=11;case 11:if(0===r.havedict)return e.next_out=a,e.avail_out=h,e.next_in=s,e.avail_in=o,r.hold=u,r.bits=l,2;e.adler=r.check=1,r.mode=12;case 12:if(5===t||6===t)break e;case 13:if(r.last){u>>>=7&l,l-=7&l,r.mode=27;break}for(;l<3;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}switch(r.last=1&u,l-=1,3&(u>>>=1)){case 0:r.mode=14;break;case 1:if(j(r),r.mode=20,6!==t)break;u>>>=2,l-=2;break e;case 2:r.mode=17;break;case 3:e.msg="invalid block type",r.mode=30}u>>>=2,l-=2;break;case 14:for(u>>>=7&l,l-=7&l;l<32;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if((65535&u)!=(u>>>16^65535)){e.msg="invalid stored block lengths",r.mode=30;break}if(r.length=65535&u,l=u=0,r.mode=15,6===t)break e;case 15:r.mode=16;case 16:if(d=r.length){if(o<d&&(d=o),h<d&&(d=h),0===d)break e;I.arraySet(i,n,s,d,a),o-=d,s+=d,h-=d,a+=d,r.length-=d;break}r.mode=12;break;case 17:for(;l<14;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(r.nlen=257+(31&u),u>>>=5,l-=5,r.ndist=1+(31&u),u>>>=5,l-=5,r.ncode=4+(15&u),u>>>=4,l-=4,286<r.nlen||30<r.ndist){e.msg="too many length or distance symbols",r.mode=30;break}r.have=0,r.mode=18;case 18:for(;r.have<r.ncode;){for(;l<3;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.lens[A[r.have++]]=7&u,u>>>=3,l-=3}for(;r.have<19;)r.lens[A[r.have++]]=0;if(r.lencode=r.lendyn,r.lenbits=7,S={bits:r.lenbits},x=T(0,r.lens,0,19,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg="invalid code lengths set",r.mode=30;break}r.have=0,r.mode=19;case 19:for(;r.have<r.nlen+r.ndist;){for(;g=(C=r.lencode[u&(1<<r.lenbits)-1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(b<16)u>>>=_,l-=_,r.lens[r.have++]=b;else{if(16===b){for(z=_+2;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(u>>>=_,l-=_,0===r.have){e.msg="invalid bit length repeat",r.mode=30;break}k=r.lens[r.have-1],d=3+(3&u),u>>>=2,l-=2}else if(17===b){for(z=_+3;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}l-=_,k=0,d=3+(7&(u>>>=_)),u>>>=3,l-=3}else{for(z=_+7;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}l-=_,k=0,d=11+(127&(u>>>=_)),u>>>=7,l-=7}if(r.have+d>r.nlen+r.ndist){e.msg="invalid bit length repeat",r.mode=30;break}for(;d--;)r.lens[r.have++]=k}}if(30===r.mode)break;if(0===r.lens[256]){e.msg="invalid code -- missing end-of-block",r.mode=30;break}if(r.lenbits=9,S={bits:r.lenbits},x=T(D,r.lens,0,r.nlen,r.lencode,0,r.work,S),r.lenbits=S.bits,x){e.msg="invalid literal/lengths set",r.mode=30;break}if(r.distbits=6,r.distcode=r.distdyn,S={bits:r.distbits},x=T(F,r.lens,r.nlen,r.ndist,r.distcode,0,r.work,S),r.distbits=S.bits,x){e.msg="invalid distances set",r.mode=30;break}if(r.mode=20,6===t)break e;case 20:r.mode=21;case 21:if(6<=o&&258<=h){e.next_out=a,e.avail_out=h,e.next_in=s,e.avail_in=o,r.hold=u,r.bits=l,R(e,c),a=e.next_out,i=e.output,h=e.avail_out,s=e.next_in,n=e.input,o=e.avail_in,u=r.hold,l=r.bits,12===r.mode&&(r.back=-1);break}for(r.back=0;g=(C=r.lencode[u&(1<<r.lenbits)-1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(g&&0==(240&g)){for(v=_,y=g,w=b;g=(C=r.lencode[w+((u&(1<<v+y)-1)>>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}u>>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,r.length=b,0===g){r.mode=26;break}if(32&g){r.back=-1,r.mode=12;break}if(64&g){e.msg="invalid literal/length code",r.mode=30;break}r.extra=15&g,r.mode=22;case 22:if(r.extra){for(z=r.extra;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.length+=u&(1<<r.extra)-1,u>>>=r.extra,l-=r.extra,r.back+=r.extra}r.was=r.length,r.mode=23;case 23:for(;g=(C=r.distcode[u&(1<<r.distbits)-1])>>>16&255,b=65535&C,!((_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(0==(240&g)){for(v=_,y=g,w=b;g=(C=r.distcode[w+((u&(1<<v+y)-1)>>v)])>>>16&255,b=65535&C,!(v+(_=C>>>24)<=l);){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}u>>>=v,l-=v,r.back+=v}if(u>>>=_,l-=_,r.back+=_,64&g){e.msg="invalid distance code",r.mode=30;break}r.offset=b,r.extra=15&g,r.mode=24;case 24:if(r.extra){for(z=r.extra;l<z;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}r.offset+=u&(1<<r.extra)-1,u>>>=r.extra,l-=r.extra,r.back+=r.extra}if(r.offset>r.dmax){e.msg="invalid distance too far back",r.mode=30;break}r.mode=25;case 25:if(0===h)break e;if(d=c-h,r.offset>d){if((d=r.offset-d)>r.whave&&r.sane){e.msg="invalid distance too far back",r.mode=30;break}p=d>r.wnext?(d-=r.wnext,r.wsize-d):r.wnext-d,d>r.length&&(d=r.length),m=r.window}else m=i,p=a-r.offset,d=r.length;for(h<d&&(d=h),h-=d,r.length-=d;i[a++]=m[p++],--d;);0===r.length&&(r.mode=21);break;case 26:if(0===h)break e;i[a++]=r.length,h--,r.mode=21;break;case 27:if(r.wrap){for(;l<32;){if(0===o)break e;o--,u|=n[s++]<<l,l+=8}if(c-=h,e.total_out+=c,r.total+=c,c&&(e.adler=r.check=r.flags?B(r.check,i,c,a-c):O(r.check,i,c,a-c)),c=h,(r.flags?u:L(u))!==r.check){e.msg="incorrect data check",r.mode=30;break}l=u=0}r.mode=28;case 28:if(r.wrap&&r.flags){for(;l<32;){if(0===o)break e;o--,u+=n[s++]<<l,l+=8}if(u!==(4294967295&r.total)){e.msg="incorrect length check",r.mode=30;break}l=u=0}r.mode=29;case 29:x=1;break e;case 30:x=-3;break e;case 31:return-4;case 32:default:return U}return e.next_out=a,e.avail_out=h,e.next_in=s,e.avail_in=o,r.hold=u,r.bits=l,(r.wsize||c!==e.avail_out&&r.mode<30&&(r.mode<27||4!==t))&&Z(e,e.output,e.next_out,c-e.avail_out)?(r.mode=31,-4):(f-=e.avail_in,c-=e.avail_out,e.total_in+=f,e.total_out+=c,r.total+=c,r.wrap&&c&&(e.adler=r.check=r.flags?B(r.check,i,c,e.next_out-c):O(r.check,i,c,e.next_out-c)),e.data_type=r.bits+(r.last?64:0)+(12===r.mode?128:0)+(20===r.mode||15===r.mode?256:0),(0==f&&0===c||4===t)&&x===N&&(x=-5),x)},r.inflateEnd=function(e){if(!e||!e.state)return U;var t=e.state;return t.window&&(t.window=null),e.state=null,N},r.inflateGetHeader=function(e,t){var r;return e&&e.state?0==(2&(r=e.state).wrap)?U:((r.head=t).done=!1,N):U},r.inflateSetDictionary=function(e,t){var r,n=t.length;return e&&e.state?0!==(r=e.state).wrap&&11!==r.mode?U:11===r.mode&&O(1,t,n,0)!==r.check?-3:Z(e,t,n,n)?(r.mode=31,-4):(r.havedict=1,N):U},r.inflateInfo="pako inflate (from Nodeca project)"},{"../utils/common":41,"./adler32":43,"./crc32":45,"./inffast":48,"./inftrees":50}],50:[function(e,t,r){"use strict";var D=e("../utils/common"),F=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,0,0],N=[16,16,16,16,16,16,16,16,17,17,17,17,18,18,18,18,19,19,19,19,20,20,20,20,21,21,21,21,16,72,78],U=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0],P=[16,16,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25,26,26,27,27,28,28,29,29,64,64];t.exports=function(e,t,r,n,i,s,a,o){var h,u,l,f,c,d,p,m,_,g=o.bits,b=0,v=0,y=0,w=0,k=0,x=0,S=0,z=0,C=0,E=0,A=null,I=0,O=new D.Buf16(16),B=new D.Buf16(16),R=null,T=0;for(b=0;b<=15;b++)O[b]=0;for(v=0;v<n;v++)O[t[r+v]]++;for(k=g,w=15;1<=w&&0===O[w];w--);if(w<k&&(k=w),0===w)return i[s++]=20971520,i[s++]=20971520,o.bits=1,0;for(y=1;y<w&&0===O[y];y++);for(k<y&&(k=y),b=z=1;b<=15;b++)if(z<<=1,(z-=O[b])<0)return-1;if(0<z&&(0===e||1!==w))return-1;for(B[1]=0,b=1;b<15;b++)B[b+1]=B[b]+O[b];for(v=0;v<n;v++)0!==t[r+v]&&(a[B[t[r+v]]++]=v);if(d=0===e?(A=R=a,19):1===e?(A=F,I-=257,R=N,T-=257,256):(A=U,R=P,-1),b=y,c=s,S=v=E=0,l=-1,f=(C=1<<(x=k))-1,1===e&&852<C||2===e&&592<C)return 1;for(;;){for(p=b-S,_=a[v]<d?(m=0,a[v]):a[v]>d?(m=R[T+a[v]],A[I+a[v]]):(m=96,0),h=1<<b-S,y=u=1<<x;i[c+(E>>S)+(u-=h)]=p<<24|m<<16|_|0,0!==u;);for(h=1<<b-1;E&h;)h>>=1;if(0!==h?(E&=h-1,E+=h):E=0,v++,0==--O[b]){if(b===w)break;b=t[r+a[v]]}if(k<b&&(E&f)!==l){for(0===S&&(S=k),c+=y,z=1<<(x=b-S);x+S<w&&!((z-=O[x+S])<=0);)x++,z<<=1;if(C+=1<<x,1===e&&852<C||2===e&&592<C)return 1;i[l=E&f]=k<<24|x<<16|c-s|0}}return 0!==E&&(i[c+E]=b-S<<24|64<<16|0),o.bits=k,0}},{"../utils/common":41}],51:[function(e,t,r){"use strict";t.exports={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"}},{}],52:[function(e,t,r){"use strict";var i=e("../utils/common"),o=0,h=1;function n(e){for(var t=e.length;0<=--t;)e[t]=0}var s=0,a=29,u=256,l=u+1+a,f=30,c=19,_=2*l+1,g=15,d=16,p=7,m=256,b=16,v=17,y=18,w=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],k=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],x=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],S=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],z=new Array(2*(l+2));n(z);var C=new Array(2*f);n(C);var E=new Array(512);n(E);var A=new Array(256);n(A);var I=new Array(a);n(I);var O,B,R,T=new Array(f);function D(e,t,r,n,i){this.static_tree=e,this.extra_bits=t,this.extra_base=r,this.elems=n,this.max_length=i,this.has_stree=e&&e.length}function F(e,t){this.dyn_tree=e,this.max_code=0,this.stat_desc=t}function N(e){return e<256?E[e]:E[256+(e>>>7)]}function U(e,t){e.pending_buf[e.pending++]=255&t,e.pending_buf[e.pending++]=t>>>8&255}function P(e,t,r){e.bi_valid>d-r?(e.bi_buf|=t<<e.bi_valid&65535,U(e,e.bi_buf),e.bi_buf=t>>d-e.bi_valid,e.bi_valid+=r-d):(e.bi_buf|=t<<e.bi_valid&65535,e.bi_valid+=r)}function L(e,t,r){P(e,r[2*t],r[2*t+1])}function j(e,t){for(var r=0;r|=1&e,e>>>=1,r<<=1,0<--t;);return r>>>1}function Z(e,t,r){var n,i,s=new Array(g+1),a=0;for(n=1;n<=g;n++)s[n]=a=a+r[n-1]<<1;for(i=0;i<=t;i++){var o=e[2*i+1];0!==o&&(e[2*i]=j(s[o]++,o))}}function W(e){var t;for(t=0;t<l;t++)e.dyn_ltree[2*t]=0;for(t=0;t<f;t++)e.dyn_dtree[2*t]=0;for(t=0;t<c;t++)e.bl_tree[2*t]=0;e.dyn_ltree[2*m]=1,e.opt_len=e.static_len=0,e.last_lit=e.matches=0}function M(e){8<e.bi_valid?U(e,e.bi_buf):0<e.bi_valid&&(e.pending_buf[e.pending++]=e.bi_buf),e.bi_buf=0,e.bi_valid=0}function H(e,t,r,n){var i=2*t,s=2*r;return e[i]<e[s]||e[i]===e[s]&&n[t]<=n[r]}function G(e,t,r){for(var n=e.heap[r],i=r<<1;i<=e.heap_len&&(i<e.heap_len&&H(t,e.heap[i+1],e.heap[i],e.depth)&&i++,!H(t,n,e.heap[i],e.depth));)e.heap[r]=e.heap[i],r=i,i<<=1;e.heap[r]=n}function K(e,t,r){var n,i,s,a,o=0;if(0!==e.last_lit)for(;n=e.pending_buf[e.d_buf+2*o]<<8|e.pending_buf[e.d_buf+2*o+1],i=e.pending_buf[e.l_buf+o],o++,0===n?L(e,i,t):(L(e,(s=A[i])+u+1,t),0!==(a=w[s])&&P(e,i-=I[s],a),L(e,s=N(--n),r),0!==(a=k[s])&&P(e,n-=T[s],a)),o<e.last_lit;);L(e,m,t)}function Y(e,t){var r,n,i,s=t.dyn_tree,a=t.stat_desc.static_tree,o=t.stat_desc.has_stree,h=t.stat_desc.elems,u=-1;for(e.heap_len=0,e.heap_max=_,r=0;r<h;r++)0!==s[2*r]?(e.heap[++e.heap_len]=u=r,e.depth[r]=0):s[2*r+1]=0;for(;e.heap_len<2;)s[2*(i=e.heap[++e.heap_len]=u<2?++u:0)]=1,e.depth[i]=0,e.opt_len--,o&&(e.static_len-=a[2*i+1]);for(t.max_code=u,r=e.heap_len>>1;1<=r;r--)G(e,s,r);for(i=h;r=e.heap[1],e.heap[1]=e.heap[e.heap_len--],G(e,s,1),n=e.heap[1],e.heap[--e.heap_max]=r,e.heap[--e.heap_max]=n,s[2*i]=s[2*r]+s[2*n],e.depth[i]=(e.depth[r]>=e.depth[n]?e.depth[r]:e.depth[n])+1,s[2*r+1]=s[2*n+1]=i,e.heap[1]=i++,G(e,s,1),2<=e.heap_len;);e.heap[--e.heap_max]=e.heap[1],function(e,t){var r,n,i,s,a,o,h=t.dyn_tree,u=t.max_code,l=t.stat_desc.static_tree,f=t.stat_desc.has_stree,c=t.stat_desc.extra_bits,d=t.stat_desc.extra_base,p=t.stat_desc.max_length,m=0;for(s=0;s<=g;s++)e.bl_count[s]=0;for(h[2*e.heap[e.heap_max]+1]=0,r=e.heap_max+1;r<_;r++)p<(s=h[2*h[2*(n=e.heap[r])+1]+1]+1)&&(s=p,m++),h[2*n+1]=s,u<n||(e.bl_count[s]++,a=0,d<=n&&(a=c[n-d]),o=h[2*n],e.opt_len+=o*(s+a),f&&(e.static_len+=o*(l[2*n+1]+a)));if(0!==m){do{for(s=p-1;0===e.bl_count[s];)s--;e.bl_count[s]--,e.bl_count[s+1]+=2,e.bl_count[p]--,m-=2}while(0<m);for(s=p;0!==s;s--)for(n=e.bl_count[s];0!==n;)u<(i=e.heap[--r])||(h[2*i+1]!==s&&(e.opt_len+=(s-h[2*i+1])*h[2*i],h[2*i+1]=s),n--)}}(e,t),Z(s,u,e.bl_count)}function X(e,t,r){var n,i,s=-1,a=t[1],o=0,h=7,u=4;for(0===a&&(h=138,u=3),t[2*(r+1)+1]=65535,n=0;n<=r;n++)i=a,a=t[2*(n+1)+1],++o<h&&i===a||(o<u?e.bl_tree[2*i]+=o:0!==i?(i!==s&&e.bl_tree[2*i]++,e.bl_tree[2*b]++):o<=10?e.bl_tree[2*v]++:e.bl_tree[2*y]++,s=i,u=(o=0)===a?(h=138,3):i===a?(h=6,3):(h=7,4))}function V(e,t,r){var n,i,s=-1,a=t[1],o=0,h=7,u=4;for(0===a&&(h=138,u=3),n=0;n<=r;n++)if(i=a,a=t[2*(n+1)+1],!(++o<h&&i===a)){if(o<u)for(;L(e,i,e.bl_tree),0!=--o;);else 0!==i?(i!==s&&(L(e,i,e.bl_tree),o--),L(e,b,e.bl_tree),P(e,o-3,2)):o<=10?(L(e,v,e.bl_tree),P(e,o-3,3)):(L(e,y,e.bl_tree),P(e,o-11,7));s=i,u=(o=0)===a?(h=138,3):i===a?(h=6,3):(h=7,4)}}n(T);var q=!1;function J(e,t,r,n){P(e,(s<<1)+(n?1:0),3),function(e,t,r,n){M(e),n&&(U(e,r),U(e,~r)),i.arraySet(e.pending_buf,e.window,t,r,e.pending),e.pending+=r}(e,t,r,!0)}r._tr_init=function(e){q||(function(){var e,t,r,n,i,s=new Array(g+1);for(n=r=0;n<a-1;n++)for(I[n]=r,e=0;e<1<<w[n];e++)A[r++]=n;for(A[r-1]=n,n=i=0;n<16;n++)for(T[n]=i,e=0;e<1<<k[n];e++)E[i++]=n;for(i>>=7;n<f;n++)for(T[n]=i<<7,e=0;e<1<<k[n]-7;e++)E[256+i++]=n;for(t=0;t<=g;t++)s[t]=0;for(e=0;e<=143;)z[2*e+1]=8,e++,s[8]++;for(;e<=255;)z[2*e+1]=9,e++,s[9]++;for(;e<=279;)z[2*e+1]=7,e++,s[7]++;for(;e<=287;)z[2*e+1]=8,e++,s[8]++;for(Z(z,l+1,s),e=0;e<f;e++)C[2*e+1]=5,C[2*e]=j(e,5);O=new D(z,w,u+1,l,g),B=new D(C,k,0,f,g),R=new D(new Array(0),x,0,c,p)}(),q=!0),e.l_desc=new F(e.dyn_ltree,O),e.d_desc=new F(e.dyn_dtree,B),e.bl_desc=new F(e.bl_tree,R),e.bi_buf=0,e.bi_valid=0,W(e)},r._tr_stored_block=J,r._tr_flush_block=function(e,t,r,n){var i,s,a=0;0<e.level?(2===e.strm.data_type&&(e.strm.data_type=function(e){var t,r=4093624447;for(t=0;t<=31;t++,r>>>=1)if(1&r&&0!==e.dyn_ltree[2*t])return o;if(0!==e.dyn_ltree[18]||0!==e.dyn_ltree[20]||0!==e.dyn_ltree[26])return h;for(t=32;t<u;t++)if(0!==e.dyn_ltree[2*t])return h;return o}(e)),Y(e,e.l_desc),Y(e,e.d_desc),a=function(e){var t;for(X(e,e.dyn_ltree,e.l_desc.max_code),X(e,e.dyn_dtree,e.d_desc.max_code),Y(e,e.bl_desc),t=c-1;3<=t&&0===e.bl_tree[2*S[t]+1];t--);return e.opt_len+=3*(t+1)+5+5+4,t}(e),i=e.opt_len+3+7>>>3,(s=e.static_len+3+7>>>3)<=i&&(i=s)):i=s=r+5,r+4<=i&&-1!==t?J(e,t,r,n):4===e.strategy||s===i?(P(e,2+(n?1:0),3),K(e,z,C)):(P(e,4+(n?1:0),3),function(e,t,r,n){var i;for(P(e,t-257,5),P(e,r-1,5),P(e,n-4,4),i=0;i<n;i++)P(e,e.bl_tree[2*S[i]+1],3);V(e,e.dyn_ltree,t-1),V(e,e.dyn_dtree,r-1)}(e,e.l_desc.max_code+1,e.d_desc.max_code+1,a+1),K(e,e.dyn_ltree,e.dyn_dtree)),W(e),n&&M(e)},r._tr_tally=function(e,t,r){return e.pending_buf[e.d_buf+2*e.last_lit]=t>>>8&255,e.pending_buf[e.d_buf+2*e.last_lit+1]=255&t,e.pending_buf[e.l_buf+e.last_lit]=255&r,e.last_lit++,0===t?e.dyn_ltree[2*r]++:(e.matches++,t--,e.dyn_ltree[2*(A[r]+u+1)]++,e.dyn_dtree[2*N(t)]++),e.last_lit===e.lit_bufsize-1},r._tr_align=function(e){P(e,2,3),L(e,m,z),function(e){16===e.bi_valid?(U(e,e.bi_buf),e.bi_buf=0,e.bi_valid=0):8<=e.bi_valid&&(e.pending_buf[e.pending++]=255&e.bi_buf,e.bi_buf>>=8,e.bi_valid-=8)}(e)}},{"../utils/common":41}],53:[function(e,t,r){"use strict";t.exports=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}},{}],54:[function(e,t,r){(function(e){!function(r,n){"use strict";if(!r.setImmediate){var i,s,t,a,o=1,h={},u=!1,l=r.document,e=Object.getPrototypeOf&&Object.getPrototypeOf(r);e=e&&e.setTimeout?e:r,i="[object process]"==={}.toString.call(r.process)?function(e){process.nextTick(function(){c(e)})}:function(){if(r.postMessage&&!r.importScripts){var e=!0,t=r.onmessage;return r.onmessage=function(){e=!1},r.postMessage("","*"),r.onmessage=t,e}}()?(a="setImmediate$"+Math.random()+"$",r.addEventListener?r.addEventListener("message",d,!1):r.attachEvent("onmessage",d),function(e){r.postMessage(a+e,"*")}):r.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){c(e.data)},function(e){t.port2.postMessage(e)}):l&&"onreadystatechange"in l.createElement("script")?(s=l.documentElement,function(e){var t=l.createElement("script");t.onreadystatechange=function(){c(e),t.onreadystatechange=null,s.removeChild(t),t=null},s.appendChild(t)}):function(e){setTimeout(c,0,e)},e.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),r=0;r<t.length;r++)t[r]=arguments[r+1];var n={callback:e,args:t};return h[o]=n,i(o),o++},e.clearImmediate=f}function f(e){delete h[e]}function c(e){if(u)setTimeout(c,0,e);else{var t=h[e];if(t){u=!0;try{!function(e){var t=e.callback,r=e.args;switch(r.length){case 0:t();break;case 1:t(r[0]);break;case 2:t(r[0],r[1]);break;case 3:t(r[0],r[1],r[2]);break;default:t.apply(n,r)}}(t)}finally{f(e),u=!1}}}}function d(e){e.source===r&&"string"==typeof e.data&&0===e.data.indexOf(a)&&c(+e.data.slice(a.length))}}("undefined"==typeof self?void 0===e?this:e:self)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[10])(10)});
|
||
|
||
|
||
|
||
|
||
;(function(){
|
||
var UTIF = {};
|
||
|
||
// Make available for import by `require()`
|
||
if (typeof module == "object") {module.exports = UTIF;}
|
||
else {self.UTIF = UTIF;}
|
||
|
||
var pako;
|
||
if (typeof require == "function") {pako = require("pako");}
|
||
else {pako = self.pako;}
|
||
|
||
function log() { if (typeof process=="undefined" || process.env.NODE_ENV=="development") console.log.apply(console, arguments); }
|
||
|
||
(function(UTIF, pako){
|
||
|
||
// Following lines add a JPEG decoder to UTIF.JpegDecoder
|
||
(function(){var V="function"===typeof Symbol&&"symbol"===typeof Symbol.iterator?function(g){return typeof g}:function(g){return g&&"function"===typeof Symbol&&g.constructor===Symbol&&g!==Symbol.prototype?"symbol":typeof g},D=function(){function g(g){this.message="JPEG error: "+g}g.prototype=Error();g.prototype.name="JpegError";return g.constructor=g}(),P=function(){function g(g,D){this.message=g;this.g=D}g.prototype=Error();g.prototype.name="DNLMarkerError";return g.constructor=g}();(function(){function g(){this.M=
|
||
null;this.B=-1}function W(a,d){for(var f=0,e=[],b,B,k=16;0<k&&!a[k-1];)k--;e.push({children:[],index:0});var l=e[0],r;for(b=0;b<k;b++){for(B=0;B<a[b];B++){l=e.pop();for(l.children[l.index]=d[f];0<l.index;)l=e.pop();l.index++;for(e.push(l);e.length<=b;)e.push(r={children:[],index:0}),l.children[l.index]=r.children,l=r;f++}b+1<k&&(e.push(r={children:[],index:0}),l.children[l.index]=r.children,l=r)}return e[0].children}function X(a,d,f,e,b,B,k,l,r){function n(){if(0<x)return x--,z>>x&1;z=a[d++];if(255===
|
||
z){var c=a[d++];if(c){if(220===c&&g){d+=2;var b=a[d++]<<8|a[d++];if(0<b&&b!==f.g)throw new P("Found DNL marker (0xFFDC) while parsing scan data",b);}throw new D("unexpected marker "+(z<<8|c).toString(16));}}x=7;return z>>>7}function q(a){for(;;){a=a[n()];if("number"===typeof a)return a;if("object"!==("undefined"===typeof a?"undefined":V(a)))throw new D("invalid huffman sequence");}}function h(a){for(var c=0;0<a;)c=c<<1|n(),a--;return c}function c(a){if(1===a)return 1===n()?1:-1;var c=h(a);return c>=
|
||
1<<a-1?c:c+(-1<<a)+1}function C(a,b){var d=q(a.D);d=0===d?0:c(d);a.a[b]=a.m+=d;for(d=1;64>d;){var h=q(a.o),k=h&15;h>>=4;if(0===k){if(15>h)break;d+=16}else d+=h,a.a[b+J[d]]=c(k),d++}}function w(a,d){var b=q(a.D);b=0===b?0:c(b)<<r;a.a[d]=a.m+=b}function p(a,c){a.a[c]|=n()<<r}function m(a,b){if(0<A)A--;else for(var d=B;d<=k;){var e=q(a.o),f=e&15;e>>=4;if(0===f){if(15>e){A=h(e)+(1<<e)-1;break}d+=16}else d+=e,a.a[b+J[d]]=c(f)*(1<<r),d++}}function t(a,d){for(var b=B,e=0,f;b<=k;){f=d+J[b];var l=0>a.a[f]?
|
||
-1:1;switch(E){case 0:e=q(a.o);f=e&15;e>>=4;if(0===f)15>e?(A=h(e)+(1<<e),E=4):(e=16,E=1);else{if(1!==f)throw new D("invalid ACn encoding");Q=c(f);E=e?2:3}continue;case 1:case 2:a.a[f]?a.a[f]+=l*(n()<<r):(e--,0===e&&(E=2===E?3:0));break;case 3:a.a[f]?a.a[f]+=l*(n()<<r):(a.a[f]=Q<<r,E=0);break;case 4:a.a[f]&&(a.a[f]+=l*(n()<<r))}b++}4===E&&(A--,0===A&&(E=0))}var g=9<arguments.length&&void 0!==arguments[9]?arguments[9]:!1,u=f.P,v=d,z=0,x=0,A=0,E=0,Q,K=e.length,F,L,M,I;var R=f.S?0===B?0===l?w:p:0===l?
|
||
m:t:C;var G=0;var O=1===K?e[0].c*e[0].l:u*f.O;for(var S,T;G<O;){var U=b?Math.min(O-G,b):O;for(F=0;F<K;F++)e[F].m=0;A=0;if(1===K){var y=e[0];for(I=0;I<U;I++)R(y,64*((y.c+1)*(G/y.c|0)+G%y.c)),G++}else for(I=0;I<U;I++){for(F=0;F<K;F++)for(y=e[F],S=y.h,T=y.j,L=0;L<T;L++)for(M=0;M<S;M++)R(y,64*((y.c+1)*((G/u|0)*y.j+L)+(G%u*y.h+M)));G++}x=0;(y=N(a,d))&&y.f&&((0,_util.warn)("decodeScan - unexpected MCU data, current marker is: "+y.f),d=y.offset);y=y&&y.F;if(!y||65280>=y)throw new D("marker was not found");
|
||
if(65488<=y&&65495>=y)d+=2;else break}(y=N(a,d))&&y.f&&((0,_util.warn)("decodeScan - unexpected Scan data, current marker is: "+y.f),d=y.offset);return d-v}function Y(a,d){for(var f=d.c,e=d.l,b=new Int16Array(64),B=0;B<e;B++)for(var k=0;k<f;k++){var l=64*((d.c+1)*B+k),r=b,n=d.G,q=d.a;if(!n)throw new D("missing required Quantization Table.");for(var h=0;64>h;h+=8){var c=q[l+h];var C=q[l+h+1];var w=q[l+h+2];var p=q[l+h+3];var m=q[l+h+4];var t=q[l+h+5];var g=q[l+h+6];var u=q[l+h+7];c*=n[h];if(0===(C|
|
||
w|p|m|t|g|u))c=5793*c+512>>10,r[h]=c,r[h+1]=c,r[h+2]=c,r[h+3]=c,r[h+4]=c,r[h+5]=c,r[h+6]=c,r[h+7]=c;else{C*=n[h+1];w*=n[h+2];p*=n[h+3];m*=n[h+4];t*=n[h+5];g*=n[h+6];u*=n[h+7];var v=5793*c+128>>8;var z=5793*m+128>>8;var x=w;var A=g;m=2896*(C-u)+128>>8;u=2896*(C+u)+128>>8;p<<=4;t<<=4;v=v+z+1>>1;z=v-z;c=3784*x+1567*A+128>>8;x=1567*x-3784*A+128>>8;A=c;m=m+t+1>>1;t=m-t;u=u+p+1>>1;p=u-p;v=v+A+1>>1;A=v-A;z=z+x+1>>1;x=z-x;c=2276*m+3406*u+2048>>12;m=3406*m-2276*u+2048>>12;u=c;c=799*p+4017*t+2048>>12;p=4017*
|
||
p-799*t+2048>>12;t=c;r[h]=v+u;r[h+7]=v-u;r[h+1]=z+t;r[h+6]=z-t;r[h+2]=x+p;r[h+5]=x-p;r[h+3]=A+m;r[h+4]=A-m}}for(n=0;8>n;++n)c=r[n],C=r[n+8],w=r[n+16],p=r[n+24],m=r[n+32],t=r[n+40],g=r[n+48],u=r[n+56],0===(C|w|p|m|t|g|u)?(c=5793*c+8192>>14,c=-2040>c?0:2024<=c?255:c+2056>>4,q[l+n]=c,q[l+n+8]=c,q[l+n+16]=c,q[l+n+24]=c,q[l+n+32]=c,q[l+n+40]=c,q[l+n+48]=c,q[l+n+56]=c):(v=5793*c+2048>>12,z=5793*m+2048>>12,x=w,A=g,m=2896*(C-u)+2048>>12,u=2896*(C+u)+2048>>12,v=(v+z+1>>1)+4112,z=v-z,c=3784*x+1567*A+2048>>
|
||
12,x=1567*x-3784*A+2048>>12,A=c,m=m+t+1>>1,t=m-t,u=u+p+1>>1,p=u-p,v=v+A+1>>1,A=v-A,z=z+x+1>>1,x=z-x,c=2276*m+3406*u+2048>>12,m=3406*m-2276*u+2048>>12,u=c,c=799*p+4017*t+2048>>12,p=4017*p-799*t+2048>>12,t=c,c=v+u,u=v-u,C=z+t,g=z-t,w=x+p,t=x-p,p=A+m,m=A-m,c=16>c?0:4080<=c?255:c>>4,C=16>C?0:4080<=C?255:C>>4,w=16>w?0:4080<=w?255:w>>4,p=16>p?0:4080<=p?255:p>>4,m=16>m?0:4080<=m?255:m>>4,t=16>t?0:4080<=t?255:t>>4,g=16>g?0:4080<=g?255:g>>4,u=16>u?0:4080<=u?255:u>>4,q[l+n]=c,q[l+n+8]=C,q[l+n+16]=w,q[l+n+24]=
|
||
p,q[l+n+32]=m,q[l+n+40]=t,q[l+n+48]=g,q[l+n+56]=u)}return d.a}function N(a,d){var f=2<arguments.length&&void 0!==arguments[2]?arguments[2]:d,e=a.length-1;f=f<d?f:d;if(d>=e)return null;var b=a[d]<<8|a[d+1];if(65472<=b&&65534>=b)return{f:null,F:b,offset:d};for(var B=a[f]<<8|a[f+1];!(65472<=B&&65534>=B);){if(++f>=e)return null;B=a[f]<<8|a[f+1]}return{f:b.toString(16),F:B,offset:f}}var J=new Uint8Array([0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,
|
||
57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63]);g.prototype={parse:function(a){function d(){var d=a[k]<<8|a[k+1];k+=2;return d}function f(){var b=d();b=k+b-2;var c=N(a,b,k);c&&c.f&&((0,_util.warn)("readDataBlock - incorrect length, current marker is: "+c.f),b=c.offset);b=a.subarray(k,b);k+=b.length;return b}function e(a){for(var b=Math.ceil(a.v/8/a.s),c=Math.ceil(a.g/8/a.u),d=0;d<a.b.length;d++){v=a.b[d];var e=Math.ceil(Math.ceil(a.v/8)*v.h/a.s),f=Math.ceil(Math.ceil(a.g/
|
||
8)*v.j/a.u);v.a=new Int16Array(64*c*v.j*(b*v.h+1));v.c=e;v.l=f}a.P=b;a.O=c}var b=(1<arguments.length&&void 0!==arguments[1]?arguments[1]:{}).N,B=void 0===b?null:b,k=0,l=null,r=0;b=[];var n=[],q=[],h=d();if(65496!==h)throw new D("SOI not found");for(h=d();65497!==h;){switch(h){case 65504:case 65505:case 65506:case 65507:case 65508:case 65509:case 65510:case 65511:case 65512:case 65513:case 65514:case 65515:case 65516:case 65517:case 65518:case 65519:case 65534:var c=f();65518===h&&65===c[0]&&100===
|
||
c[1]&&111===c[2]&&98===c[3]&&101===c[4]&&(l={version:c[5]<<8|c[6],Y:c[7]<<8|c[8],Z:c[9]<<8|c[10],W:c[11]});break;case 65499:h=d()+k-2;for(var g;k<h;){var w=a[k++],p=new Uint16Array(64);if(0===w>>4)for(c=0;64>c;c++)g=J[c],p[g]=a[k++];else if(1===w>>4)for(c=0;64>c;c++)g=J[c],p[g]=d();else throw new D("DQT - invalid table spec");b[w&15]=p}break;case 65472:case 65473:case 65474:if(m)throw new D("Only single frame JPEGs supported");d();var m={};m.X=65473===h;m.S=65474===h;m.precision=a[k++];h=d();m.g=
|
||
B||h;m.v=d();m.b=[];m.C={};c=a[k++];for(h=p=w=0;h<c;h++){g=a[k];var t=a[k+1]>>4;var H=a[k+1]&15;w<t&&(w=t);p<H&&(p=H);t=m.b.push({h:t,j:H,T:a[k+2],G:null});m.C[g]=t-1;k+=3}m.s=w;m.u=p;e(m);break;case 65476:g=d();for(h=2;h<g;){w=a[k++];p=new Uint8Array(16);for(c=t=0;16>c;c++,k++)t+=p[c]=a[k];H=new Uint8Array(t);for(c=0;c<t;c++,k++)H[c]=a[k];h+=17+t;(0===w>>4?q:n)[w&15]=W(p,H)}break;case 65501:d();var u=d();break;case 65498:c=1===++r&&!B;d();w=a[k++];g=[];for(h=0;h<w;h++){p=m.C[a[k++]];var v=m.b[p];
|
||
p=a[k++];v.D=q[p>>4];v.o=n[p&15];g.push(v)}h=a[k++];w=a[k++];p=a[k++];try{var z=X(a,k,m,g,u,h,w,p>>4,p&15,c);k+=z}catch(x){if(x instanceof P)return(0,_util.warn)('Attempting to re-parse JPEG image using "scanLines" parameter found in DNL marker (0xFFDC) segment.'),this.parse(a,{N:x.g});throw x;}break;case 65500:k+=4;break;case 65535:255!==a[k]&&k--;break;default:if(255===a[k-3]&&192<=a[k-2]&&254>=a[k-2])k-=3;else if((c=N(a,k-2))&&c.f)(0,_util.warn)("JpegImage.parse - unexpected data, current marker is: "+
|
||
c.f),k=c.offset;else throw new D("unknown marker "+h.toString(16));}h=d()}this.width=m.v;this.height=m.g;this.A=l;this.b=[];for(h=0;h<m.b.length;h++){v=m.b[h];if(u=b[v.T])v.G=u;this.b.push({R:Y(m,v),U:v.h/m.s,V:v.j/m.u,c:v.c,l:v.l})}this.i=this.b.length},L:function(a,d){var f=this.width/a,e=this.height/d,b,g,k=this.b.length,l=a*d*k,r=new Uint8ClampedArray(l),n=new Uint32Array(a);for(g=0;g<k;g++){var q=this.b[g];var h=q.U*f;var c=q.V*e;var C=g;var w=q.R;var p=q.c+1<<3;for(b=0;b<a;b++)q=0|b*h,n[b]=
|
||
(q&4294967288)<<3|q&7;for(h=0;h<d;h++)for(q=0|h*c,q=p*(q&4294967288)|(q&7)<<3,b=0;b<a;b++)r[C]=w[q+n[b]],C+=k}if(e=this.M)for(g=0;g<l;)for(f=q=0;q<k;q++,g++,f+=2)r[g]=(r[g]*e[f]>>8)+e[f+1];return r},w:function(){return this.A?!!this.A.W:3===this.i?0===this.B?!1:!0:1===this.B?!0:!1},I:function(a){for(var d,f,e,b=0,g=a.length;b<g;b+=3)d=a[b],f=a[b+1],e=a[b+2],a[b]=d-179.456+1.402*e,a[b+1]=d+135.459-.344*f-.714*e,a[b+2]=d-226.816+1.772*f;return a},K:function(a){for(var d,f,e,b,g=0,k=0,l=a.length;k<l;k+=
|
||
4)d=a[k],f=a[k+1],e=a[k+2],b=a[k+3],a[g++]=-122.67195406894+f*(-6.60635669420364E-5*f+4.37130475926232E-4*e-5.4080610064599E-5*d+4.8449797120281E-4*b-.154362151871126)+e*(-9.57964378445773E-4*e+8.17076911346625E-4*d-.00477271405408747*b+1.53380253221734)+d*(9.61250184130688E-4*d-.00266257332283933*b+.48357088451265)+b*(-3.36197177618394E-4*b+.484791561490776),a[g++]=107.268039397724+f*(2.19927104525741E-5*f-6.40992018297945E-4*e+6.59397001245577E-4*d+4.26105652938837E-4*b-.176491792462875)+e*(-7.78269941513683E-4*
|
||
e+.00130872261408275*d+7.70482631801132E-4*b-.151051492775562)+d*(.00126935368114843*d-.00265090189010898*b+.25802910206845)+b*(-3.18913117588328E-4*b-.213742400323665),a[g++]=-20.810012546947+f*(-5.70115196973677E-4*f-2.63409051004589E-5*e+.0020741088115012*d-.00288260236853442*b+.814272968359295)+e*(-1.53496057440975E-5*e-1.32689043961446E-4*d+5.60833691242812E-4*b-.195152027534049)+d*(.00174418132927582*d-.00255243321439347*b+.116935020465145)+b*(-3.43531996510555E-4*b+.24165260232407);return a.subarray(0,
|
||
g)},J:function(a){for(var d,f,e,b=0,g=a.length;b<g;b+=4)d=a[b],f=a[b+1],e=a[b+2],a[b]=434.456-d-1.402*e,a[b+1]=119.541-d+.344*f+.714*e,a[b+2]=481.816-d-1.772*f;return a},H:function(a){for(var d,f,e,b,g=0,k=1/255,l=0,r=a.length;l<r;l+=4)d=a[l]*k,f=a[l+1]*k,e=a[l+2]*k,b=a[l+3]*k,a[g++]=255+d*(-4.387332384609988*d+54.48615194189176*f+18.82290502165302*e+212.25662451639585*b-285.2331026137004)+f*(1.7149763477362134*f-5.6096736904047315*e-17.873870861415444*b-5.497006427196366)+e*(-2.5217340131683033*
|
||
e-21.248923337353073*b+17.5119270841813)-b*(21.86122147463605*b+189.48180835922747),a[g++]=255+d*(8.841041422036149*d+60.118027045597366*f+6.871425592049007*e+31.159100130055922*b-79.2970844816548)+f*(-15.310361306967817*f+17.575251261109482*e+131.35250912493976*b-190.9453302588951)+e*(4.444339102852739*e+9.8632861493405*b-24.86741582555878)-b*(20.737325471181034*b+187.80453709719578),a[g++]=255+d*(.8842522430003296*d+8.078677503112928*f+30.89978309703729*e-.23883238689178934*b-14.183576799673286)+
|
||
f*(10.49593273432072*f+63.02378494754052*e+50.606957656360734*b-112.23884253719248)+e*(.03296041114873217*e+115.60384449646641*b-193.58209356861505)-b*(22.33816807309886*b+180.12613974708367);return a.subarray(0,g)},getData:function(a,d,f){if(4<this.i)throw new D("Unsupported color mode");a=this.L(a,d);if(1===this.i&&f){f=a.length;d=new Uint8ClampedArray(3*f);for(var e=0,b=0;b<f;b++){var g=a[b];d[e++]=g;d[e++]=g;d[e++]=g}return d}if(3===this.i&&this.w())return this.I(a);if(4===this.i){if(this.w())return f?
|
||
this.K(a):this.J(a);if(f)return this.H(a)}return a}}; UTIF.JpegDecoder=g})()})();
|
||
|
||
//UTIF.JpegDecoder = PDFJS.JpegImage;
|
||
|
||
|
||
UTIF.encodeImage = function(rgba, w, h, metadata)
|
||
{
|
||
var idf = { "t256":[w], "t257":[h], "t258":[8,8,8,8], "t259":[1], "t262":[2], "t273":[1000], // strips offset
|
||
"t277":[4], "t278":[h], /* rows per strip */ "t279":[w*h*4], // strip byte counts
|
||
"t282":[1], "t283":[1], "t284":[1], "t286":[0], "t287":[0], "t296":[1], "t305": ["Photopea (UTIF.js)"], "t338":[1]
|
||
};
|
||
if (metadata) for (var i in metadata) idf[i] = metadata[i];
|
||
|
||
var prfx = new Uint8Array(UTIF.encode([idf]));
|
||
var img = new Uint8Array(rgba);
|
||
var data = new Uint8Array(1000+w*h*4);
|
||
for(var i=0; i<prfx.length; i++) data[i] = prfx[i];
|
||
for(var i=0; i<img .length; i++) data[1000+i] = img[i];
|
||
return data.buffer;
|
||
}
|
||
|
||
UTIF.encode = function(ifds)
|
||
{
|
||
var data = new Uint8Array(20000), offset = 4, bin = UTIF._binBE;
|
||
data[0]=77; data[1]=77; data[3]=42;
|
||
|
||
var ifdo = 8;
|
||
bin.writeUint(data, offset, ifdo); offset+=4;
|
||
for(var i=0; i<ifds.length; i++)
|
||
{
|
||
var noffs = UTIF._writeIFD(bin, data, ifdo, ifds[i]);
|
||
ifdo = noffs[1];
|
||
if(i<ifds.length-1) bin.writeUint(data, noffs[0], ifdo);
|
||
}
|
||
return data.slice(0, ifdo).buffer;
|
||
}
|
||
//UTIF.encode._writeIFD
|
||
|
||
UTIF.decode = function(buff)
|
||
{
|
||
UTIF.decode._decodeG3.allow2D = null;
|
||
var data = new Uint8Array(buff), offset = 0;
|
||
|
||
var id = UTIF._binBE.readASCII(data, offset, 2); offset+=2;
|
||
var bin = id=="II" ? UTIF._binLE : UTIF._binBE;
|
||
var num = bin.readUshort(data, offset); offset+=2;
|
||
|
||
var ifdo = bin.readUint(data, offset); offset+=4;
|
||
var ifds = [];
|
||
while(true) {
|
||
var noff = UTIF._readIFD(bin, data, ifdo, ifds, 0, false);
|
||
ifdo = bin.readUint(data, noff);
|
||
if(ifdo==0) break;
|
||
}
|
||
return ifds;
|
||
}
|
||
|
||
UTIF.decodeImage = function(buff, img, ifds)
|
||
{
|
||
var data = new Uint8Array(buff);
|
||
var id = UTIF._binBE.readASCII(data, 0, 2);
|
||
|
||
if(img["t256"]==null) return; // No width => probably not an image
|
||
img.isLE = id=="II";
|
||
img.width = img["t256"][0]; //delete img["t256"];
|
||
img.height = img["t257"][0]; //delete img["t257"];
|
||
|
||
var cmpr = img["t259"] ? img["t259"][0] : 1; //delete img["t259"];
|
||
var fo = img["t266"] ? img["t266"][0] : 1; //delete img["t266"];
|
||
if(img["t284"] && img["t284"][0]==2) log("PlanarConfiguration 2 should not be used!");
|
||
|
||
var bipp; // bits per pixel
|
||
if(img["t258"]) bipp = Math.min(32,img["t258"][0])*img["t258"].length;
|
||
else bipp = (img["t277"]?img["t277"][0]:1);
|
||
// Some .NEF files have t258==14, even though they use 16 bits per pixel
|
||
if(cmpr==1 && img["t279"]!=null && img["t278"] && img["t262"][0]==32803) {
|
||
bipp = Math.round((img["t279"][0]*8)/(img.width*img["t278"][0]));
|
||
}
|
||
var bipl = Math.ceil(img.width*bipp/8)*8;
|
||
var soff = img["t273"]; if(soff==null) soff = img["t324"];
|
||
var bcnt = img["t279"]; if(cmpr==1 && soff.length==1) bcnt = [img.height*(bipl>>>3)]; if(bcnt==null) bcnt = img["t325"];
|
||
var bytes = new Uint8Array(img.height*(bipl>>>3)), bilen = 0;
|
||
|
||
if(img["t322"]!=null) // tiled
|
||
{
|
||
var tw = img["t322"][0], th = img["t323"][0];
|
||
var tx = Math.floor((img.width + tw - 1) / tw);
|
||
var ty = Math.floor((img.height + th - 1) / th);
|
||
var tbuff = new Uint8Array(Math.ceil(tw*th*bipp/8)|0);
|
||
for(var y=0; y<ty; y++)
|
||
for(var x=0; x<tx; x++)
|
||
{
|
||
var i = y*tx+x; for(var j=0; j<tbuff.length; j++) tbuff[j]=0;
|
||
UTIF.decode._decompress(img,ifds, data, soff[i], bcnt[i], cmpr, tbuff, 0, fo);
|
||
// Might be required for 7 too. Need to check
|
||
if (cmpr==6) bytes = tbuff;
|
||
else UTIF._copyTile(tbuff, Math.ceil(tw*bipp/8)|0, th, bytes, Math.ceil(img.width*bipp/8)|0, img.height, Math.ceil(x*tw*bipp/8)|0, y*th);
|
||
}
|
||
bilen = bytes.length*8;
|
||
}
|
||
else // stripped
|
||
{
|
||
var rps = img["t278"] ? img["t278"][0] : img.height; rps = Math.min(rps, img.height);
|
||
for(var i=0; i<soff.length; i++)
|
||
{
|
||
UTIF.decode._decompress(img,ifds, data, soff[i], bcnt[i], cmpr, bytes, Math.ceil(bilen/8)|0, fo);
|
||
bilen += bipl * rps;
|
||
}
|
||
bilen = Math.min(bilen, bytes.length*8);
|
||
}
|
||
img.data = new Uint8Array(bytes.buffer, 0, Math.ceil(bilen/8)|0);
|
||
}
|
||
|
||
UTIF.decode._decompress = function(img,ifds, data, off, len, cmpr, tgt, toff, fo) // fill order
|
||
{
|
||
//console.log("compression", cmpr);
|
||
//var time = Date.now();
|
||
if(false) {}
|
||
else if(cmpr==1 || (len==tgt.length && cmpr!=32767)) for(var j=0; j<len; j++) tgt[toff+j] = data[off+j];
|
||
else if(cmpr==3) UTIF.decode._decodeG3 (data, off, len, tgt, toff, img.width, fo);
|
||
else if(cmpr==4) UTIF.decode._decodeG4 (data, off, len, tgt, toff, img.width, fo);
|
||
else if(cmpr==5) UTIF.decode._decodeLZW(data, off, tgt, toff);
|
||
else if(cmpr==6) UTIF.decode._decodeOldJPEG(img, data, off, len, tgt, toff);
|
||
else if(cmpr==7) UTIF.decode._decodeNewJPEG(img, data, off, len, tgt, toff);
|
||
else if(cmpr==8) { var src = new Uint8Array(data.buffer,off,len); var bin = pako["inflate"](src); for(var i=0; i<bin.length; i++) tgt[toff+i]=bin[i]; }
|
||
else if(cmpr==32767) UTIF.decode._decodeARW(img, data, off, len, tgt, toff);
|
||
else if(cmpr==32773) UTIF.decode._decodePackBits(data, off, len, tgt, toff);
|
||
else if(cmpr==32809) UTIF.decode._decodeThunder (data, off, len, tgt, toff);
|
||
else if(cmpr==34713) //for(var j=0; j<len; j++) tgt[toff+j] = data[off+j];
|
||
UTIF.decode._decodeNikon (img,ifds, data, off, len, tgt, toff);
|
||
else log("Unknown compression", cmpr);
|
||
|
||
//console.log(Date.now()-time);
|
||
|
||
var bps = (img["t258"]?Math.min(32,img["t258"][0]):1);
|
||
var noc = (img["t277"]?img["t277"][0]:1), bpp=(bps*noc)>>>3, h = (img["t278"] ? img["t278"][0] : img.height), bpl = Math.ceil(bps*noc*img.width/8);
|
||
|
||
// convert to Little Endian /*
|
||
if(bps==16 && !img.isLE && img["t33422"]==null) // not DNG
|
||
for(var y=0; y<h; y++) {
|
||
//console.log("fixing endianity");
|
||
var roff = toff+y*bpl;
|
||
for(var x=1; x<bpl; x+=2) { var t=tgt[roff+x]; tgt[roff+x]=tgt[roff+x-1]; tgt[roff+x-1]=t; }
|
||
} //*/
|
||
|
||
if(img["t317"] && img["t317"][0]==2)
|
||
{
|
||
for(var y=0; y<h; y++)
|
||
{
|
||
var ntoff = toff+y*bpl;
|
||
if(bps==16) for(var j=bpp; j<bpl; j+=2) {
|
||
var nv = ((tgt[ntoff+j+1]<<8)|tgt[ntoff+j]) + ((tgt[ntoff+j-bpp+1]<<8)|tgt[ntoff+j-bpp]);
|
||
tgt[ntoff+j] = nv&255; tgt[ntoff+j+1] = (nv>>>8)&255;
|
||
}
|
||
else if(noc==3) for(var j= 3; j<bpl; j+=3)
|
||
{
|
||
tgt[ntoff+j ] = (tgt[ntoff+j ] + tgt[ntoff+j-3])&255;
|
||
tgt[ntoff+j+1] = (tgt[ntoff+j+1] + tgt[ntoff+j-2])&255;
|
||
tgt[ntoff+j+2] = (tgt[ntoff+j+2] + tgt[ntoff+j-1])&255;
|
||
}
|
||
else for(var j=bpp; j<bpl; j++) tgt[ntoff+j] = (tgt[ntoff+j] + tgt[ntoff+j-bpp])&255;
|
||
}
|
||
}
|
||
}
|
||
|
||
UTIF.decode._ljpeg_diff = function(data, prm, huff) {
|
||
var getbithuff = UTIF.decode._getbithuff;
|
||
var len, diff;
|
||
len = getbithuff(data, prm, huff[0], huff);
|
||
diff = getbithuff(data, prm, len, 0);
|
||
if ((diff & (1 << (len-1))) == 0) diff -= (1 << len) - 1;
|
||
return diff;
|
||
}
|
||
UTIF.decode._decodeARW = function(img, inp, off, src_length, tgt, toff) {
|
||
var raw_width = img["t256"][0], height=img["t257"][0], tiff_bps=img["t258"][0];
|
||
var bin=(img.isLE ? UTIF._binLE : UTIF._binBE);
|
||
//console.log(raw_width, height, tiff_bps, raw_width*height, src_length);
|
||
var arw2 = (raw_width*height == src_length) || (raw_width*height*1.5 == src_length);
|
||
//arw2 = true;
|
||
//console.log("ARW2: ", arw2, raw_width*height, src_length, tgt.length);
|
||
if(!arw2) { //"sony_arw_load_raw"; // not arw2
|
||
height+=8;
|
||
var prm = [off,0,0,0];
|
||
var huff = new Uint16Array(32770);
|
||
var tab = [ 0xf11,0xf10,0xe0f,0xd0e,0xc0d,0xb0c,0xa0b,0x90a,0x809,
|
||
0x708,0x607,0x506,0x405,0x304,0x303,0x300,0x202,0x201 ];
|
||
var i, c, n, col, row, sum=0;
|
||
var ljpeg_diff = UTIF.decode._ljpeg_diff;
|
||
|
||
huff[0] = 15;
|
||
for (n=i=0; i < 18; i++) {
|
||
var lim = 32768 >>> (tab[i] >>> 8);
|
||
for(var c=0; c<lim; c++) huff[++n] = tab[i];
|
||
}
|
||
for (col = raw_width; col--; )
|
||
for (row=0; row < height+1; row+=2) {
|
||
if (row == height) row = 1;
|
||
sum += ljpeg_diff(inp, prm, huff);
|
||
if (row < height) {
|
||
var clr = (sum)&4095;
|
||
UTIF.decode._putsF(tgt, (row*raw_width+col)*tiff_bps, clr<<(16-tiff_bps));
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
if(raw_width*height*1.5==src_length) {
|
||
//console.log("weird compression");
|
||
for(var i=0; i<src_length; i+=3) { var b0=inp[off+i+0], b1=inp[off+i+1], b2=inp[off+i+2];
|
||
tgt[toff+i]=(b1<<4)|(b0>>>4); tgt[toff+i+1]=(b0<<4)|(b2>>>4); tgt[toff+i+2]=(b2<<4)|(b1>>>4); }
|
||
return;
|
||
}
|
||
|
||
var pix = new Uint16Array(16);
|
||
var row, col, val, max, min, imax, imin, sh, bit, i, dp;
|
||
|
||
var data = new Uint8Array(raw_width+1);
|
||
for (row=0; row < height; row++) {
|
||
//fread (data, 1, raw_width, ifp);
|
||
for(var j=0; j<raw_width; j++) data[j]=inp[off++];
|
||
for (dp=0, col=0; col < raw_width-30; dp+=16) {
|
||
max = 0x7ff & (val = bin.readUint(data,dp));
|
||
min = 0x7ff & (val >>> 11);
|
||
imax = 0x0f & (val >>> 22);
|
||
imin = 0x0f & (val >>> 26);
|
||
for (sh=0; sh < 4 && 0x80 << sh <= max-min; sh++);
|
||
for (bit=30, i=0; i < 16; i++)
|
||
if (i == imax) pix[i] = max;
|
||
else if (i == imin) pix[i] = min;
|
||
else {
|
||
pix[i] = ((bin.readUshort(data, dp+(bit >> 3)) >>> (bit & 7) & 0x7f) << sh) + min;
|
||
if (pix[i] > 0x7ff) pix[i] = 0x7ff;
|
||
bit += 7;
|
||
}
|
||
for (i=0; i < 16; i++, col+=2) {
|
||
//RAW(row,col) = curve[pix[i] << 1] >> 2;
|
||
var clr = pix[i]<<1; //clr = 0xffff;
|
||
UTIF.decode._putsF(tgt, (row*raw_width+col)*tiff_bps, clr<<(16-tiff_bps));
|
||
}
|
||
col -= col & 1 ? 1:31;
|
||
}
|
||
}
|
||
}
|
||
|
||
UTIF.decode._decodeNikon = function(img,imgs, data, off, src_length, tgt, toff)
|
||
{
|
||
var nikon_tree = [
|
||
[ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy */
|
||
5,4,3,6,2,7,1,0,8,9,11,10,12 ],
|
||
[ 0, 0,1,5,1,1,1,1,1,1,2,0,0,0,0,0,0, /* 12-bit lossy after split */
|
||
0x39,0x5a,0x38,0x27,0x16,5,4,3,2,1,0,11,12,12 ],
|
||
[ 0, 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0, /* 12-bit lossless */
|
||
5,4,6,3,7,2,8,1,9,0,10,11,12 ],
|
||
[ 0, 0,1,4,3,1,1,1,1,1,2,0,0,0,0,0,0, /* 14-bit lossy */
|
||
5,6,4,7,8,3,9,2,1,0,10,11,12,13,14 ],
|
||
[ 0, 0,1,5,1,1,1,1,1,1,1,2,0,0,0,0,0, /* 14-bit lossy after split */
|
||
8,0x5c,0x4b,0x3a,0x29,7,6,5,4,3,2,1,0,13,14 ],
|
||
[ 0, 0,1,4,2,2,3,1,2,0,0,0,0,0,0,0,0, /* 14-bit lossless */
|
||
7,6,8,5,9,4,10,3,11,12,2,0,1,13,14 ] ];
|
||
|
||
var raw_width = img["t256"][0], height=img["t257"][0], tiff_bps=img["t258"][0];
|
||
|
||
var tree = 0, split = 0;
|
||
var make_decoder = UTIF.decode._make_decoder;
|
||
var getbithuff = UTIF.decode._getbithuff;
|
||
|
||
var mn = imgs[0].exifIFD.makerNote, md = mn["t150"]?mn["t150"]:mn["t140"], mdo=0; //console.log(mn,md);
|
||
//console.log(md[0].toString(16), md[1].toString(16), tiff_bps);
|
||
var ver0 = md[mdo++], ver1 = md[mdo++];
|
||
if (ver0 == 0x49 || ver1 == 0x58) mdo+=2110;
|
||
if (ver0 == 0x46) tree = 2;
|
||
if (tiff_bps == 14) tree += 3;
|
||
|
||
var vpred = [[0,0],[0,0]], bin=(img.isLE ? UTIF._binLE : UTIF._binBE);
|
||
for(var i=0; i<2; i++) for(var j=0; j<2; j++) { vpred[i][j] = bin.readShort(md,mdo); mdo+=2; } // not sure here ... [i][j] or [j][i]
|
||
//console.log(vpred);
|
||
|
||
|
||
var max = 1 << tiff_bps & 0x7fff, step=0;
|
||
var csize = bin.readShort(md,mdo); mdo+=2;
|
||
if (csize > 1) step = Math.floor(max / (csize-1));
|
||
if (ver0 == 0x44 && ver1 == 0x20 && step > 0) split = bin.readShort(md,562);
|
||
|
||
|
||
var i;
|
||
var row, col;
|
||
var len, shl, diff;
|
||
var min_v = 0;
|
||
var hpred = [0,0];
|
||
var huff = make_decoder(nikon_tree[tree]);
|
||
|
||
//var g_input_offset=0, bitbuf=0, vbits=0, reset=0;
|
||
var prm = [off,0,0,0];
|
||
//console.log(split); split = 170;
|
||
|
||
for (min_v=row=0; row < height; row++) {
|
||
if (split && row == split) {
|
||
//free (huff);
|
||
huff = make_decoder (nikon_tree[tree+1]);
|
||
//max_v += (min_v = 16) << 1;
|
||
}
|
||
for (col=0; col < raw_width; col++) {
|
||
i = getbithuff(data,prm,huff[0],huff);
|
||
len = i & 15;
|
||
shl = i >>> 4;
|
||
diff = (((getbithuff(data,prm,len-shl,0) << 1) + 1) << shl) >>> 1;
|
||
if ((diff & (1 << (len-1))) == 0)
|
||
diff -= (1 << len) - (shl==0?1:0);
|
||
if (col < 2) hpred[col] = vpred[row & 1][col] += diff;
|
||
else hpred[col & 1] += diff;
|
||
|
||
var clr = Math.min(Math.max(hpred[col & 1],0),(1<<tiff_bps)-1);
|
||
var bti = (row*raw_width+col)*tiff_bps;
|
||
UTIF.decode._putsF(tgt, bti, clr<<(16-tiff_bps));
|
||
}
|
||
}
|
||
}
|
||
// put 16 bits
|
||
UTIF.decode._putsF= function(dt, pos, val) { val = val<<(8-(pos&7)); var o=(pos>>>3); dt[o]|=val>>>16; dt[o+1]|=val>>>8; dt[o+2]|=val; }
|
||
|
||
|
||
UTIF.decode._getbithuff = function(data,prm,nbits, huff) {
|
||
var zero_after_ff = 0;
|
||
var get_byte = UTIF.decode._get_byte;
|
||
var c;
|
||
|
||
var off=prm[0], bitbuf=prm[1], vbits=prm[2], reset=prm[3];
|
||
|
||
//if (nbits > 25) return 0;
|
||
//if (nbits < 0) return bitbuf = vbits = reset = 0;
|
||
if (nbits == 0 || vbits < 0) return 0;
|
||
while (!reset && vbits < nbits && (c = data[off++]) != -1 &&
|
||
!(reset = zero_after_ff && c == 0xff && data[off++])) {
|
||
//console.log("byte read into c");
|
||
bitbuf = (bitbuf << 8) + c;
|
||
vbits += 8;
|
||
}
|
||
c = (bitbuf << (32-vbits)) >>> (32-nbits);
|
||
if (huff) {
|
||
vbits -= huff[c+1] >>> 8; //console.log(c, huff[c]>>8);
|
||
c = huff[c+1]&255;
|
||
} else
|
||
vbits -= nbits;
|
||
if (vbits < 0) throw "e";
|
||
|
||
prm[0]=off; prm[1]=bitbuf; prm[2]=vbits; prm[3]=reset;
|
||
|
||
return c;
|
||
}
|
||
|
||
UTIF.decode._make_decoder = function(source) {
|
||
var max, len, h, i, j;
|
||
var huff = [];
|
||
|
||
for (max=16; max!=0 && !source[max]; max--);
|
||
var si=17;
|
||
|
||
huff[0] = max;
|
||
for (h=len=1; len <= max; len++)
|
||
for (i=0; i < source[len]; i++, ++si)
|
||
for (j=0; j < 1 << (max-len); j++)
|
||
if (h <= 1 << max)
|
||
huff[h++] = (len << 8) | source[si];
|
||
return huff;
|
||
}
|
||
|
||
UTIF.decode._decodeNewJPEG = function(img, data, off, len, tgt, toff)
|
||
{
|
||
var tables = img["t347"], tlen = tables ? tables.length : 0, buff = new Uint8Array(tlen + len);
|
||
|
||
if (tables) {
|
||
var SOI = 216, EOI = 217, boff = 0;
|
||
for (var i=0; i<(tlen-1); i++)
|
||
{
|
||
// Skip EOI marker from JPEGTables
|
||
if (tables[i]==255 && tables[i+1]==EOI) break;
|
||
buff[boff++] = tables[i];
|
||
}
|
||
|
||
// Skip SOI marker from data
|
||
var byte1 = data[off], byte2 = data[off + 1];
|
||
if (byte1!=255 || byte2!=SOI)
|
||
{
|
||
buff[boff++] = byte1;
|
||
buff[boff++] = byte2;
|
||
}
|
||
for (var i=2; i<len; i++) buff[boff++] = data[off+i];
|
||
}
|
||
else for (var i=0; i<len; i++) buff[i] = data[off+i];
|
||
|
||
if(img["t262"][0]==32803 || img["t262"][0]==34892) // lossless JPEG and lossy JPEG (used in DNG files)
|
||
{
|
||
var bps = img["t258"][0];//, dcdr = new LosslessJpegDecoder();
|
||
var out = UTIF.LosslessJpegDecode(buff), olen=out.length; //console.log(olen);
|
||
|
||
if(false) {}
|
||
else if(bps==16) {
|
||
if(img.isLE) for(var i=0; i<olen; i++ ) { tgt[toff+(i<<1)] = (out[i]&255); tgt[toff+(i<<1)+1] = (out[i]>>>8); }
|
||
else for(var i=0; i<olen; i++ ) { tgt[toff+(i<<1)] = (out[i]>>>8); tgt[toff+(i<<1)+1] = (out[i]&255); }
|
||
}
|
||
else if(bps==14 || bps==12) { // 4 * 14 == 56 == 7 * 8
|
||
var rst = 16-bps;
|
||
for(var i=0; i<olen; i++) UTIF.decode._putsF(tgt, i*bps, out[i]<<rst);
|
||
}
|
||
else throw new Error("unsupported bit depth "+bps);
|
||
}
|
||
else
|
||
{
|
||
var parser = new UTIF.JpegDecoder(); parser.parse(buff);
|
||
var decoded = parser.getData(parser.width, parser.height);
|
||
for (var i=0; i<decoded.length; i++) tgt[toff + i] = decoded[i];
|
||
}
|
||
|
||
// PhotometricInterpretation is 6 (YCbCr) for JPEG, but after decoding we populate data in
|
||
// RGB format, so updating the tag value
|
||
if(img["t262"][0] == 6) img["t262"][0] = 2;
|
||
}
|
||
|
||
UTIF.decode._decodeOldJPEGInit = function(img, data, off, len)
|
||
{
|
||
var SOI = 216, EOI = 217, DQT = 219, DHT = 196, DRI = 221, SOF0 = 192, SOS = 218;
|
||
var joff = 0, soff = 0, tables, sosMarker, isTiled = false, i, j, k;
|
||
var jpgIchgFmt = img["t513"], jifoff = jpgIchgFmt ? jpgIchgFmt[0] : 0;
|
||
var jpgIchgFmtLen = img["t514"], jiflen = jpgIchgFmtLen ? jpgIchgFmtLen[0] : 0;
|
||
var soffTag = img["t324"] || img["t273"] || jpgIchgFmt;
|
||
var ycbcrss = img["t530"], ssx = 0, ssy = 0;
|
||
var spp = img["t277"]?img["t277"][0]:1;
|
||
var jpgresint = img["t515"];
|
||
|
||
if(soffTag)
|
||
{
|
||
soff = soffTag[0];
|
||
isTiled = (soffTag.length > 1);
|
||
}
|
||
|
||
if(!isTiled)
|
||
{
|
||
if(data[off]==255 && data[off+1]==SOI) return { jpegOffset: off };
|
||
if(jpgIchgFmt!=null)
|
||
{
|
||
if(data[off+jifoff]==255 && data[off+jifoff+1]==SOI) joff = off+jifoff;
|
||
else log("JPEGInterchangeFormat does not point to SOI");
|
||
|
||
if(jpgIchgFmtLen==null) log("JPEGInterchangeFormatLength field is missing");
|
||
else if(jifoff >= soff || (jifoff+jiflen) <= soff) log("JPEGInterchangeFormatLength field value is invalid");
|
||
|
||
if(joff != null) return { jpegOffset: joff };
|
||
}
|
||
}
|
||
|
||
if(ycbcrss!=null) { ssx = ycbcrss[0]; ssy = ycbcrss[1]; }
|
||
|
||
if(jpgIchgFmt!=null)
|
||
if(jpgIchgFmtLen!=null)
|
||
if(jiflen >= 2 && (jifoff+jiflen) <= soff)
|
||
{
|
||
if(data[off+jifoff+jiflen-2]==255 && data[off+jifoff+jiflen-1]==SOI) tables = new Uint8Array(jiflen-2);
|
||
else tables = new Uint8Array(jiflen);
|
||
|
||
for(i=0; i<tables.length; i++) tables[i] = data[off+jifoff+i];
|
||
log("Incorrect JPEG interchange format: using JPEGInterchangeFormat offset to derive tables");
|
||
}
|
||
else log("JPEGInterchangeFormat+JPEGInterchangeFormatLength > offset to first strip or tile");
|
||
|
||
if(tables == null)
|
||
{
|
||
var ooff = 0, out = [];
|
||
out[ooff++] = 255; out[ooff++] = SOI;
|
||
|
||
var qtables = img["t519"];
|
||
if(qtables==null) throw new Error("JPEGQTables tag is missing");
|
||
for(i=0; i<qtables.length; i++)
|
||
{
|
||
out[ooff++] = 255; out[ooff++] = DQT; out[ooff++] = 0; out[ooff++] = 67; out[ooff++] = i;
|
||
for(j=0; j<64; j++) out[ooff++] = data[off+qtables[i]+j];
|
||
}
|
||
|
||
for(k=0; k<2; k++)
|
||
{
|
||
var htables = img[(k == 0) ? "t520" : "t521"];
|
||
if(htables==null) throw new Error(((k == 0) ? "JPEGDCTables" : "JPEGACTables") + " tag is missing");
|
||
for(i=0; i<htables.length; i++)
|
||
{
|
||
out[ooff++] = 255; out[ooff++] = DHT;
|
||
//out[ooff++] = 0; out[ooff++] = 67; out[ooff++] = i;
|
||
var nc = 19;
|
||
for(j=0; j<16; j++) nc += data[off+htables[i]+j];
|
||
|
||
out[ooff++] = (nc >>> 8); out[ooff++] = nc & 255;
|
||
out[ooff++] = (i | (k << 4));
|
||
for(j=0; j<16; j++) out[ooff++] = data[off+htables[i]+j];
|
||
for(j=0; j<nc; j++) out[ooff++] = data[off+htables[i]+16+j];
|
||
}
|
||
}
|
||
|
||
out[ooff++] = 255; out[ooff++] = SOF0;
|
||
out[ooff++] = 0; out[ooff++] = 8 + 3*spp; out[ooff++] = 8;
|
||
out[ooff++] = (img.height >>> 8) & 255; out[ooff++] = img.height & 255;
|
||
out[ooff++] = (img.width >>> 8) & 255; out[ooff++] = img.width & 255;
|
||
out[ooff++] = spp;
|
||
if(spp==1) { out[ooff++] = 1; out[ooff++] = 17; out[ooff++] = 0; }
|
||
else for(i=0; i<3; i++)
|
||
{
|
||
out[ooff++] = i + 1;
|
||
out[ooff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15));
|
||
out[ooff++] = i;
|
||
}
|
||
|
||
if(jpgresint!=null && jpgresint[0]!=0)
|
||
{
|
||
out[ooff++] = 255; out[ooff++] = DRI; out[ooff++] = 0; out[ooff++] = 4;
|
||
out[ooff++] = (jpgresint[0] >>> 8) & 255;
|
||
out[ooff++] = jpgresint[0] & 255;
|
||
}
|
||
|
||
tables = new Uint8Array(out);
|
||
}
|
||
|
||
var sofpos = -1;
|
||
i = 0;
|
||
while(i < (tables.length - 1)) {
|
||
if(tables[i]==255 && tables[i+1]==SOF0) { sofpos = i; break; }
|
||
i++;
|
||
}
|
||
|
||
if(sofpos == -1)
|
||
{
|
||
var tmptab = new Uint8Array(tables.length + 10 + 3*spp);
|
||
tmptab.set(tables);
|
||
var tmpoff = tables.length;
|
||
sofpos = tables.length;
|
||
tables = tmptab;
|
||
|
||
tables[tmpoff++] = 255; tables[tmpoff++] = SOF0;
|
||
tables[tmpoff++] = 0; tables[tmpoff++] = 8 + 3*spp; tables[tmpoff++] = 8;
|
||
tables[tmpoff++] = (img.height >>> 8) & 255; tables[tmpoff++] = img.height & 255;
|
||
tables[tmpoff++] = (img.width >>> 8) & 255; tables[tmpoff++] = img.width & 255;
|
||
tables[tmpoff++] = spp;
|
||
if(spp==1) { tables[tmpoff++] = 1; tables[tmpoff++] = 17; tables[tmpoff++] = 0; }
|
||
else for(i=0; i<3; i++)
|
||
{
|
||
tables[tmpoff++] = i + 1;
|
||
tables[tmpoff++] = (i != 0) ? 17 : (((ssx & 15) << 4) | (ssy & 15));
|
||
tables[tmpoff++] = i;
|
||
}
|
||
}
|
||
|
||
if(data[soff]==255 && data[soff+1]==SOS)
|
||
{
|
||
var soslen = (data[soff+2]<<8) | data[soff+3];
|
||
sosMarker = new Uint8Array(soslen+2);
|
||
sosMarker[0] = data[soff]; sosMarker[1] = data[soff+1]; sosMarker[2] = data[soff+2]; sosMarker[3] = data[soff+3];
|
||
for(i=0; i<(soslen-2); i++) sosMarker[i+4] = data[soff+i+4];
|
||
}
|
||
else
|
||
{
|
||
sosMarker = new Uint8Array(2 + 6 + 2*spp);
|
||
var sosoff = 0;
|
||
sosMarker[sosoff++] = 255; sosMarker[sosoff++] = SOS;
|
||
sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 6 + 2*spp; sosMarker[sosoff++] = spp;
|
||
if(spp==1) { sosMarker[sosoff++] = 1; sosMarker[sosoff++] = 0; }
|
||
else for(i=0; i<3; i++)
|
||
{
|
||
sosMarker[sosoff++] = i+1; sosMarker[sosoff++] = (i << 4) | i;
|
||
}
|
||
sosMarker[sosoff++] = 0; sosMarker[sosoff++] = 63; sosMarker[sosoff++] = 0;
|
||
}
|
||
|
||
return { jpegOffset: off, tables: tables, sosMarker: sosMarker, sofPosition: sofpos };
|
||
}
|
||
|
||
UTIF.decode._decodeOldJPEG = function(img, data, off, len, tgt, toff)
|
||
{
|
||
var i, dlen, tlen, buff, buffoff;
|
||
var jpegData = UTIF.decode._decodeOldJPEGInit(img, data, off, len);
|
||
|
||
if(jpegData.jpegOffset!=null)
|
||
{
|
||
dlen = off+len-jpegData.jpegOffset;
|
||
buff = new Uint8Array(dlen);
|
||
for(i=0; i<dlen; i++) buff[i] = data[jpegData.jpegOffset+i];
|
||
}
|
||
else
|
||
{
|
||
tlen = jpegData.tables.length;
|
||
buff = new Uint8Array(tlen + jpegData.sosMarker.length + len + 2);
|
||
buff.set(jpegData.tables);
|
||
buffoff = tlen;
|
||
|
||
buff[jpegData.sofPosition+5] = (img.height >>> 8) & 255; buff[jpegData.sofPosition+6] = img.height & 255;
|
||
buff[jpegData.sofPosition+7] = (img.width >>> 8) & 255; buff[jpegData.sofPosition+8] = img.width & 255;
|
||
|
||
if(data[off]!=255 || data[off+1]!=SOS)
|
||
{
|
||
buff.set(jpegData.sosMarker, buffoff);
|
||
buffoff += sosMarker.length;
|
||
}
|
||
for(i=0; i<len; i++) buff[buffoff++] = data[off+i];
|
||
buff[buffoff++] = 255; buff[buffoff++] = EOI;
|
||
}
|
||
|
||
var parser = new UTIF.JpegDecoder(); parser.parse(buff);
|
||
var decoded = parser.getData(parser.width, parser.height);
|
||
for (var i=0; i<decoded.length; i++) tgt[toff + i] = decoded[i];
|
||
|
||
// PhotometricInterpretation is 6 (YCbCr) for JPEG, but after decoding we populate data in
|
||
// RGB format, so updating the tag value
|
||
if(img["t262"] && img["t262"][0] == 6) img["t262"][0] = 2;
|
||
}
|
||
|
||
UTIF.decode._decodePackBits = function(data, off, len, tgt, toff)
|
||
{
|
||
var sa = new Int8Array(data.buffer), ta = new Int8Array(tgt.buffer), lim = off+len;
|
||
while(off<lim)
|
||
{
|
||
var n = sa[off]; off++;
|
||
if(n>=0 && n<128) for(var i=0; i< n+1; i++) { ta[toff]=sa[off]; toff++; off++; }
|
||
if(n>=-127 && n<0) { for(var i=0; i<-n+1; i++) { ta[toff]=sa[off]; toff++; } off++; }
|
||
}
|
||
}
|
||
|
||
UTIF.decode._decodeThunder = function(data, off, len, tgt, toff)
|
||
{
|
||
var d2 = [ 0, 1, 0, -1 ], d3 = [ 0, 1, 2, 3, 0, -3, -2, -1 ];
|
||
var lim = off+len, qoff = toff*2, px = 0;
|
||
while(off<lim)
|
||
{
|
||
var b = data[off], msk = (b>>>6), n = (b&63); off++;
|
||
if(msk==3) { px=(n&15); tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; }
|
||
if(msk==0) for(var i=0; i<n; i++) { tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; }
|
||
if(msk==2) for(var i=0; i<2; i++) { var d=(n>>>(3*(1-i)))&7; if(d!=4) { px+=d3[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } }
|
||
if(msk==1) for(var i=0; i<3; i++) { var d=(n>>>(2*(2-i)))&3; if(d!=2) { px+=d2[d]; tgt[qoff>>>1] |= (px<<(4*(1-qoff&1))); qoff++; } }
|
||
}
|
||
}
|
||
|
||
UTIF.decode._dmap = { "1":0,"011":1,"000011":2,"0000011":3, "010":-1,"000010":-2,"0000010":-3 };
|
||
UTIF.decode._lens = ( function()
|
||
{
|
||
var addKeys = function(lens, arr, i0, inc) { for(var i=0; i<arr.length; i++) lens[arr[i]] = i0 + i*inc; }
|
||
|
||
var termW = "00110101,000111,0111,1000,1011,1100,1110,1111,10011,10100,00111,01000,001000,000011,110100,110101," // 15
|
||
+ "101010,101011,0100111,0001100,0001000,0010111,0000011,0000100,0101000,0101011,0010011,0100100,0011000,00000010,00000011,00011010," // 31
|
||
+ "00011011,00010010,00010011,00010100,00010101,00010110,00010111,00101000,00101001,00101010,00101011,00101100,00101101,00000100,00000101,00001010," // 47
|
||
+ "00001011,01010010,01010011,01010100,01010101,00100100,00100101,01011000,01011001,01011010,01011011,01001010,01001011,00110010,00110011,00110100";
|
||
|
||
var termB = "0000110111,010,11,10,011,0011,0010,00011,000101,000100,0000100,0000101,0000111,00000100,00000111,000011000," // 15
|
||
+ "0000010111,0000011000,0000001000,00001100111,00001101000,00001101100,00000110111,00000101000,00000010111,00000011000,000011001010,000011001011,000011001100,000011001101,000001101000,000001101001," // 31
|
||
+ "000001101010,000001101011,000011010010,000011010011,000011010100,000011010101,000011010110,000011010111,000001101100,000001101101,000011011010,000011011011,000001010100,000001010101,000001010110,000001010111," // 47
|
||
+ "000001100100,000001100101,000001010010,000001010011,000000100100,000000110111,000000111000,000000100111,000000101000,000001011000,000001011001,000000101011,000000101100,000001011010,000001100110,000001100111";
|
||
|
||
var makeW = "11011,10010,010111,0110111,00110110,00110111,01100100,01100101,01101000,01100111,011001100,011001101,011010010,011010011,011010100,011010101,011010110,"
|
||
+ "011010111,011011000,011011001,011011010,011011011,010011000,010011001,010011010,011000,010011011";
|
||
|
||
var makeB = "0000001111,000011001000,000011001001,000001011011,000000110011,000000110100,000000110101,0000001101100,0000001101101,0000001001010,0000001001011,0000001001100,"
|
||
+ "0000001001101,0000001110010,0000001110011,0000001110100,0000001110101,0000001110110,0000001110111,0000001010010,0000001010011,0000001010100,0000001010101,0000001011010,"
|
||
+ "0000001011011,0000001100100,0000001100101";
|
||
|
||
var makeA = "00000001000,00000001100,00000001101,000000010010,000000010011,000000010100,000000010101,000000010110,000000010111,000000011100,000000011101,000000011110,000000011111";
|
||
|
||
termW = termW.split(","); termB = termB.split(","); makeW = makeW.split(","); makeB = makeB.split(","); makeA = makeA.split(",");
|
||
|
||
var lensW = {}, lensB = {};
|
||
addKeys(lensW, termW, 0, 1); addKeys(lensW, makeW, 64,64); addKeys(lensW, makeA, 1792,64);
|
||
addKeys(lensB, termB, 0, 1); addKeys(lensB, makeB, 64,64); addKeys(lensB, makeA, 1792,64);
|
||
return [lensW, lensB];
|
||
} )();
|
||
|
||
UTIF.decode._decodeG4 = function(data, off, slen, tgt, toff, w, fo)
|
||
{
|
||
var U = UTIF.decode, boff=off<<3, len=0, wrd=""; // previous starts with 1
|
||
var line=[], pline=[]; for(var i=0; i<w; i++) pline.push(0); pline=U._makeDiff(pline);
|
||
var a0=0, a1=0, a2=0, b1=0, b2=0, clr=0;
|
||
var y=0, mode="", toRead=0;
|
||
var bipl = Math.ceil(w/8)*8;
|
||
|
||
while((boff>>>3)<off+slen)
|
||
{
|
||
b1 = U._findDiff(pline, a0+(a0==0?0:1), 1-clr), b2 = U._findDiff(pline, b1, clr); // could be precomputed
|
||
var bit =0;
|
||
if(fo==1) bit = (data[boff>>>3]>>>(7-(boff&7)))&1;
|
||
if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1;
|
||
boff++; wrd+=bit;
|
||
if(mode=="H")
|
||
{
|
||
if(U._lens[clr][wrd]!=null)
|
||
{
|
||
var dl=U._lens[clr][wrd]; wrd=""; len+=dl;
|
||
if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; }
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; }
|
||
if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; }
|
||
if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; }
|
||
}
|
||
if(line.length==w && mode=="")
|
||
{
|
||
U._writeBits(line, tgt, toff*8+y*bipl);
|
||
clr=0; y++; a0=0;
|
||
pline=U._makeDiff(line); line=[];
|
||
}
|
||
//if(wrd.length>150) { log(wrd); break; throw "e"; }
|
||
}
|
||
}
|
||
|
||
UTIF.decode._findDiff = function(line, x, clr) { for(var i=0; i<line.length; i+=2) if(line[i]>=x && line[i+1]==clr) return line[i]; }
|
||
|
||
UTIF.decode._makeDiff = function(line)
|
||
{
|
||
var out = []; if(line[0]==1) out.push(0,1);
|
||
for(var i=1; i<line.length; i++) if(line[i-1]!=line[i]) out.push(i, line[i]);
|
||
out.push(line.length,0,line.length,1); return out;
|
||
}
|
||
|
||
UTIF.decode._decodeG3 = function(data, off, slen, tgt, toff, w, fo)
|
||
{
|
||
var U = UTIF.decode, boff=off<<3, len=0, wrd="";
|
||
var line=[], pline=[]; for(var i=0; i<w; i++) line.push(0);
|
||
var a0=0, a1=0, a2=0, b1=0, b2=0, clr=0;
|
||
var y=-1, mode="", toRead=0, is1D=false;
|
||
var bipl = Math.ceil(w/8)*8;
|
||
while((boff>>>3)<off+slen)
|
||
{
|
||
b1 = U._findDiff(pline, a0+(a0==0?0:1), 1-clr), b2 = U._findDiff(pline, b1, clr); // could be precomputed
|
||
var bit =0;
|
||
if(fo==1) bit = (data[boff>>>3]>>>(7-(boff&7)))&1;
|
||
if(fo==2) bit = (data[boff>>>3]>>>( (boff&7)))&1;
|
||
boff++; wrd+=bit;
|
||
|
||
if(is1D)
|
||
{
|
||
if(U._lens[clr][wrd]!=null)
|
||
{
|
||
var dl=U._lens[clr][wrd]; wrd=""; len+=dl;
|
||
if(dl<64) { U._addNtimes(line,len,clr); clr=1-clr; len=0; }
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if(mode=="H")
|
||
{
|
||
if(U._lens[clr][wrd]!=null)
|
||
{
|
||
var dl=U._lens[clr][wrd]; wrd=""; len+=dl;
|
||
if(dl<64) { U._addNtimes(line,len,clr); a0+=len; clr=1-clr; len=0; toRead--; if(toRead==0) mode=""; }
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if(wrd=="0001") { wrd=""; U._addNtimes(line,b2-a0,clr); a0=b2; }
|
||
if(wrd=="001" ) { wrd=""; mode="H"; toRead=2; }
|
||
if(U._dmap[wrd]!=null) { a1 = b1+U._dmap[wrd]; U._addNtimes(line, a1-a0, clr); a0=a1; wrd=""; clr=1-clr; }
|
||
}
|
||
}
|
||
if(wrd.endsWith("000000000001")) // needed for some files
|
||
{
|
||
if(y>=0) U._writeBits(line, tgt, toff*8+y*bipl);
|
||
if(fo==1) is1D = ((data[boff>>>3]>>>(7-(boff&7)))&1)==1;
|
||
if(fo==2) is1D = ((data[boff>>>3]>>>( (boff&7)))&1)==1;
|
||
boff++;
|
||
if(U._decodeG3.allow2D==null) U._decodeG3.allow2D=is1D;
|
||
if(!U._decodeG3.allow2D) { is1D = true; boff--; }
|
||
//log("EOL",y, "next 1D:", is1D);
|
||
wrd=""; clr=0; y++; a0=0;
|
||
pline=U._makeDiff(line); line=[];
|
||
}
|
||
}
|
||
if(line.length==w) U._writeBits(line, tgt, toff*8+y*bipl);
|
||
}
|
||
|
||
UTIF.decode._addNtimes = function(arr, n, val) { for(var i=0; i<n; i++) arr.push(val); }
|
||
|
||
UTIF.decode._writeBits = function(bits, tgt, boff)
|
||
{
|
||
for(var i=0; i<bits.length; i++) tgt[(boff+i)>>>3] |= (bits[i]<<(7-((boff+i)&7)));
|
||
}
|
||
|
||
UTIF.decode._decodeLZW = function(data, off, tgt, toff)
|
||
{
|
||
if(UTIF.decode._lzwTab==null)
|
||
{
|
||
var tb=new Uint32Array(0xffff), tn=new Uint16Array(0xffff), chr=new Uint8Array(2e6);
|
||
for(var i=0; i<256; i++) { chr[i<<2]=i; tb[i]=i<<2; tn[i]=1; }
|
||
UTIF.decode._lzwTab = [tb,tn,chr];
|
||
}
|
||
var copy = UTIF.decode._copyData;
|
||
var tab = UTIF.decode._lzwTab[0], tln=UTIF.decode._lzwTab[1], chr=UTIF.decode._lzwTab[2], totl = 258, chrl = 258<<2;
|
||
var bits = 9, boff = off<<3; // offset in bits
|
||
|
||
var ClearCode = 256, EoiCode = 257;
|
||
var v = 0, Code = 0, OldCode = 0;
|
||
while(true)
|
||
{
|
||
v = (data[boff>>>3]<<16) | (data[(boff+8)>>>3]<<8) | data[(boff+16)>>>3];
|
||
Code = ( v>>(24-(boff&7)-bits) ) & ((1<<bits)-1); boff+=bits;
|
||
|
||
if(Code==EoiCode) break;
|
||
if(Code==ClearCode)
|
||
{
|
||
bits=9; totl = 258; chrl = 258<<2;
|
||
|
||
v = (data[boff>>>3]<<16) | (data[(boff+8)>>>3]<<8) | data[(boff+16)>>>3];
|
||
Code = ( v>>(24-(boff&7)-bits) ) & ((1<<bits)-1); boff+=bits;
|
||
if(Code==EoiCode) break;
|
||
tgt[toff]=Code; toff++;
|
||
}
|
||
else if(Code<totl)
|
||
{
|
||
var cd = tab[Code], cl = tln[Code];
|
||
copy(chr,cd,tgt,toff,cl); toff += cl;
|
||
|
||
if(OldCode>=totl) { tab[totl] = chrl; chr[tab[totl]] = cd[0]; tln[totl]=1; chrl=(chrl+1+3)&~0x03; totl++; }
|
||
else
|
||
{
|
||
tab[totl] = chrl;
|
||
var nit = tab[OldCode], nil = tln[OldCode];
|
||
copy(chr,nit,chr,chrl,nil);
|
||
chr[chrl+nil]=chr[cd]; nil++;
|
||
tln[totl]=nil; totl++;
|
||
|
||
chrl=(chrl+nil+3)&~0x03;
|
||
}
|
||
if(totl+1==(1<<bits)) bits++;
|
||
}
|
||
else
|
||
{
|
||
if(OldCode>=totl) { tab[totl] = chrl; tln[totl]=0; totl++; }
|
||
else
|
||
{
|
||
tab[totl] = chrl;
|
||
var nit = tab[OldCode], nil = tln[OldCode];
|
||
copy(chr,nit,chr,chrl,nil);
|
||
chr[chrl+nil]=chr[chrl]; nil++;
|
||
tln[totl]=nil; totl++;
|
||
|
||
copy(chr,chrl,tgt,toff,nil); toff += nil;
|
||
chrl=(chrl+nil+3)&~0x03;
|
||
}
|
||
if(totl+1==(1<<bits)) bits++;
|
||
}
|
||
OldCode = Code;
|
||
}
|
||
}
|
||
|
||
UTIF.decode._copyData = function(s,so,t,to,l) { for(var i=0;i<l;i+=4) { t[to+i]=s[so+i]; t[to+i+1]=s[so+i+1]; t[to+i+2]=s[so+i+2]; t[to+i+3]=s[so+i+3]; } }
|
||
|
||
UTIF.tags = {};
|
||
UTIF.ttypes = { 256:3,257:3,258:3, 259:3, 262:3, 273:4, 274:3, 277:3,278:4,279:4, 282:5, 283:5, 284:3, 286:5,287:5, 296:3, 305:2, 306:2, 338:3, 513:4, 514:4, 34665:4 };
|
||
|
||
UTIF._readIFD = function(bin, data, offset, ifds, depth, debug)
|
||
{
|
||
var cnt = bin.readUshort(data, offset); offset+=2;
|
||
var ifd = {}; ifds.push(ifd);
|
||
|
||
if(debug) log(" ".repeat(depth),ifds.length-1,">>>----------------");
|
||
for(var i=0; i<cnt; i++)
|
||
{
|
||
var tag = bin.readUshort(data, offset); offset+=2;
|
||
var type = bin.readUshort(data, offset); offset+=2;
|
||
var num = bin.readUint (data, offset); offset+=4;
|
||
var voff = bin.readUint (data, offset); offset+=4;
|
||
//if(tag==33723) {type=1; num*=4;}//console.log(type,num,voff);//type = 1; // IPTC/NAA
|
||
|
||
var arr = [];
|
||
//ifd["t"+tag+"-"+UTIF.tags[tag]] = arr;
|
||
if(type== 1 || type==7) { arr = new Uint8Array(data.buffer, (num<5 ? offset-4 : voff), num); }
|
||
if(type== 2) { var o0 = (num<5 ? offset-4 : voff), c=data[o0];
|
||
if(c<128) arr.push( bin.readASCII(data, o0, num-1) );
|
||
else arr = new Uint8Array(data.buffer, o0, num-1); }
|
||
if(type== 3) { for(var j=0; j<num; j++) arr.push(bin.readUshort(data, (num<3 ? offset-4 : voff)+2*j)); }
|
||
if(type== 4) { for(var j=0; j<num; j++) arr.push(bin.readUint (data, (num<2 ? offset-4 : voff)+4*j)); }
|
||
if(type== 5) { for(var j=0; j<num; j++) arr.push(bin.readUint (data, voff+j*8) / bin.readUint(data,voff+j*8+4)); }
|
||
if(type== 8) { for(var j=0; j<num; j++) arr.push(bin.readShort (data, (num<3 ? offset-4 : voff)+2*j)); }
|
||
if(type== 9) { for(var j=0; j<num; j++) arr.push(bin.readInt (data, (num<2 ? offset-4 : voff)+4*j)); }
|
||
if(type==10) { for(var j=0; j<num; j++) arr.push(bin.readInt (data, voff+j*8) / bin.readInt (data,voff+j*8+4)); }
|
||
if(type==11) { for(var j=0; j<num; j++) arr.push(bin.readFloat (data, voff+j*4)); }
|
||
if(type==12) { for(var j=0; j<num; j++) arr.push(bin.readDouble(data, voff+j*8)); }
|
||
|
||
ifd["t"+tag] = arr;
|
||
|
||
if(num!=0 && arr.length==0) { log("unknown TIFF tag type: ", type, "num:",num); }
|
||
if(debug) log(" ".repeat(depth), tag, type, UTIF.tags[tag], arr);
|
||
|
||
if(tag==330 && ifd["t272"] && ifd["t272"][0]=="DSLR-A100") { }
|
||
// ifd["t258"]=[12]; ifd["t259"]=[32767]; ifd["t273"]=[offset+arr[0]]; ifd["t277"]=[1]; ifd["t279"]=[1]; ifd["t33421"]=[2,2]; ifd["t33422"]=[0,1,1,2];
|
||
else if(tag==330 || tag==34665 || (tag==50740 && bin.readUshort(data,bin.readUint(arr,0))<300 )) {
|
||
var oarr = tag==50740 ? [bin.readUint(arr,0)] : arr;
|
||
var subfd = [];
|
||
for(var j=0; j<oarr.length; j++) UTIF._readIFD(bin, data, oarr[j], subfd, depth+1, debug);
|
||
if(tag== 330) ifd.subIFD = subfd;
|
||
if(tag==34665) ifd.exifIFD = subfd[0];
|
||
if(tag==50740) ifd.dngPrvt = subfd[0];
|
||
}
|
||
if(tag==37500) {
|
||
var mn = arr;
|
||
//console.log(bin.readASCII(mn,0,mn.length), mn);
|
||
if(bin.readASCII(mn,0,5)=="Nikon") ifd.makerNote = UTIF["decode"](mn.slice(10).buffer)[0];
|
||
else if(bin.readUshort(data,voff)<300){
|
||
var subsub=[]; UTIF._readIFD(bin, data, voff, subsub, depth+1, debug);
|
||
ifd.makerNote = subsub[0];
|
||
}
|
||
}
|
||
}
|
||
if(debug) log(" ".repeat(depth),"<<<---------------");
|
||
return offset;
|
||
}
|
||
|
||
UTIF._writeIFD = function(bin, data, offset, ifd)
|
||
{
|
||
var keys = Object.keys(ifd);
|
||
bin.writeUshort(data, offset, keys.length); offset+=2;
|
||
|
||
var eoff = offset + keys.length*12 + 4;
|
||
|
||
for(var ki=0; ki<keys.length; ki++)
|
||
{
|
||
var key = keys[ki];
|
||
var tag = parseInt(key.slice(1)), type = UTIF.ttypes[tag]; if(type==null) throw new Error("unknown type of tag: "+tag);
|
||
var val = ifd[key]; if(type==2) val=val[0]+"\u0000"; var num = val.length;
|
||
bin.writeUshort(data, offset, tag ); offset+=2;
|
||
bin.writeUshort(data, offset, type); offset+=2;
|
||
bin.writeUint (data, offset, num ); offset+=4;
|
||
|
||
var dlen = [-1, 1, 1, 2, 4, 8, 0, 0, 0, 0, 0, 0, 8][type] * num;
|
||
var toff = offset;
|
||
if(dlen>4) { bin.writeUint(data, offset, eoff); toff=eoff; }
|
||
|
||
if(type==2) { bin.writeASCII(data, toff, val); }
|
||
if(type==3) { for(var i=0; i<num; i++) bin.writeUshort(data, toff+2*i, val[i]); }
|
||
if(type==4) { for(var i=0; i<num; i++) bin.writeUint (data, toff+4*i, val[i]); }
|
||
if(type==5) { for(var i=0; i<num; i++) { bin.writeUint(data, toff+8*i, Math.round(val[i]*10000)); bin.writeUint(data, toff+8*i+4, 10000); } }
|
||
if (type == 12) { for (var i = 0; i < num; i++) bin.writeDouble(data, toff + 8 * i, val[i]); }
|
||
|
||
if(dlen>4) { dlen += (dlen&1); eoff += dlen; }
|
||
offset += 4;
|
||
}
|
||
return [offset, eoff];
|
||
}
|
||
|
||
UTIF.toRGBA8 = function(out)
|
||
{
|
||
var w = out.width, h = out.height, area = w*h, qarea = area*4, data = out.data;
|
||
var img = new Uint8Array(area*4);
|
||
//console.log(out);
|
||
// 0: WhiteIsZero, 1: BlackIsZero, 2: RGB, 3: Palette color, 4: Transparency mask, 5: CMYK
|
||
var intp = (out["t262"] ? out["t262"][0]: 2), bps = (out["t258"]?Math.min(32,out["t258"][0]):1);
|
||
//log("interpretation: ", intp, "bps", bps, out);
|
||
if(false) {}
|
||
else if(intp==0)
|
||
{
|
||
var bpl = Math.ceil(bps*w/8);
|
||
for(var y=0; y<h; y++) {
|
||
var off = y*bpl, io = y*w;
|
||
if(bps== 1) for(var i=0; i<w; i++) { var qi=(io+i)<<2, px=((data[off+(i>>3)])>>(7- (i&7)))& 1; img[qi]=img[qi+1]=img[qi+2]=( 1-px)*255; img[qi+3]=255; }
|
||
if(bps== 4) for(var i=0; i<w; i++) { var qi=(io+i)<<2, px=((data[off+(i>>1)])>>(4-4*(i&1)))&15; img[qi]=img[qi+1]=img[qi+2]=(15-px)* 17; img[qi+3]=255; }
|
||
if(bps== 8) for(var i=0; i<w; i++) { var qi=(io+i)<<2, px=data[off+i]; img[qi]=img[qi+1]=img[qi+2]=255-px; img[qi+3]=255; }
|
||
}
|
||
}
|
||
else if(intp==1)
|
||
{
|
||
var bpl = Math.ceil(bps*w/8);
|
||
for(var y=0; y<h; y++) {
|
||
var off = y*bpl, io = y*w;
|
||
if(bps== 1) for(var i=0; i<w; i++) { var qi=(io+i)<<2, px=((data[off+(i>>3)])>>(7- (i&7)))&1; img[qi]=img[qi+1]=img[qi+2]=(px)*255; img[qi+3]=255; }
|
||
if(bps== 2) for(var i=0; i<w; i++) { var qi=(io+i)<<2, px=((data[off+(i>>2)])>>(6-2*(i&3)))&3; img[qi]=img[qi+1]=img[qi+2]=(px)* 85; img[qi+3]=255; }
|
||
if(bps== 8) for(var i=0; i<w; i++) { var qi=(io+i)<<2, px=data[off+i]; img[qi]=img[qi+1]=img[qi+2]= px; img[qi+3]=255; }
|
||
if(bps==16) for(var i=0; i<w; i++) { var qi=(io+i)<<2, px=data[off+(2*i+1)]; img[qi]=img[qi+1]=img[qi+2]= Math.min(255,px); img[qi+3]=255; } // ladoga.tif
|
||
}
|
||
}
|
||
else if(intp==2)
|
||
{
|
||
var smpls = out["t258"]?out["t258"].length : 3;
|
||
|
||
if(bps== 8)
|
||
{
|
||
if(smpls==4) for(var i=0; i<qarea; i++) img[i] = data[i];
|
||
if(smpls==3) for(var i=0; i< area; i++) { var qi=i<<2, ti=i*3; img[qi]=data[ti]; img[qi+1]=data[ti+1]; img[qi+2]=data[ti+2]; img[qi+3]=255; }
|
||
}
|
||
else{ // 3x 16-bit channel
|
||
if(smpls==4) for(var i=0; i<area; i++) { var qi=i<<2, ti=i*8+1; img[qi]=data[ti]; img[qi+1]=data[ti+2]; img[qi+2]=data[ti+4]; img[qi+3]=data[ti+6]; }
|
||
if(smpls==3) for(var i=0; i<area; i++) { var qi=i<<2, ti=i*6+1; img[qi]=data[ti]; img[qi+1]=data[ti+2]; img[qi+2]=data[ti+4]; img[qi+3]=255; }
|
||
}
|
||
}
|
||
else if(intp==3)
|
||
{
|
||
var map = out["t320"];
|
||
for(var i=0; i<area; i++) { var qi=i<<2, mi=data[i]; img[qi]=(map[mi]>>8); img[qi+1]=(map[256+mi]>>8); img[qi+2]=(map[512+mi]>>8); img[qi+3]=255; }
|
||
}
|
||
else if(intp==5)
|
||
{
|
||
var smpls = out["t258"]?out["t258"].length : 4;
|
||
var gotAlpha = smpls>4 ? 1 : 0;
|
||
for(var i=0; i<area; i++) {
|
||
var qi=i<<2, si=i*smpls; var C=255-data[si], M=255-data[si+1], Y=255-data[si+2], K=(255-data[si+3])*(1/255);
|
||
img[qi]=~~(C*K+0.5); img[qi+1]=~~(M*K+0.5); img[qi+2]=~~(Y*K+0.5); img[qi+3]=255*(1-gotAlpha)+data[si+4]*gotAlpha;
|
||
}
|
||
}
|
||
else log("Unknown Photometric interpretation: "+intp);
|
||
return img;
|
||
}
|
||
|
||
UTIF.replaceIMG = function(imgs)
|
||
{
|
||
if(imgs==null) imgs = document.getElementsByTagName("img");
|
||
var sufs = ["tif","tiff","dng","cr2","nef"]
|
||
for (var i=0; i<imgs.length; i++)
|
||
{
|
||
var img=imgs[i], src=img.getAttribute("src"); if(src==null) continue;
|
||
var suff=src.split(".").pop().toLowerCase();
|
||
if(sufs.indexOf(suff)==-1) continue;
|
||
var xhr = new XMLHttpRequest(); UTIF._xhrs.push(xhr); UTIF._imgs.push(img);
|
||
xhr.open("GET", src); xhr.responseType = "arraybuffer";
|
||
xhr.onload = UTIF._imgLoaded; xhr.send();
|
||
}
|
||
}
|
||
|
||
UTIF._xhrs = []; UTIF._imgs = [];
|
||
UTIF._imgLoaded = function(e)
|
||
{
|
||
var buff = e.target.response;
|
||
var ifds = UTIF.decode(buff); //console.log(ifds);
|
||
var vsns = ifds, ma=0, page=vsns[0]; if(ifds[0].subIFD) vsns = vsns.concat(ifds[0].subIFD);
|
||
for(var i=0; i<vsns.length; i++) {
|
||
var img = vsns[i];
|
||
if(img["t258"]==null || img["t258"].length<3) continue;
|
||
var ar = img["t256"]*img["t257"];
|
||
if(ar>ma) { ma=ar; page=img; }
|
||
}
|
||
UTIF.decodeImage(buff, page, ifds);
|
||
var rgba = UTIF.toRGBA8(page), w=page.width, h=page.height;
|
||
var ind = UTIF._xhrs.indexOf(e.target), img = UTIF._imgs[ind];
|
||
UTIF._xhrs.splice(ind,1); UTIF._imgs.splice(ind,1);
|
||
var cnv = document.createElement("canvas"); cnv.width=w; cnv.height=h;
|
||
var ctx = cnv.getContext("2d"), imgd = ctx.createImageData(w,h);
|
||
for(var i=0; i<rgba.length; i++) imgd.data[i]=rgba[i]; ctx.putImageData(imgd,0,0);
|
||
img.setAttribute("src",cnv.toDataURL());
|
||
}
|
||
|
||
|
||
UTIF._binBE =
|
||
{
|
||
nextZero : function(data, o) { while(data[o]!=0) o++; return o; },
|
||
readUshort : function(buff, p) { return (buff[p]<< 8) | buff[p+1]; },
|
||
readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+1]; a[1]=buff[p+0]; return UTIF._binBE. i16[0]; },
|
||
readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE. i32[0]; },
|
||
readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+3]; a[1]=buff[p+2]; a[2]=buff[p+1]; a[3]=buff[p+0]; return UTIF._binBE.ui32[0]; },
|
||
readASCII : function(buff, p, l) { var s = ""; for(var i=0; i<l; i++) s += String.fromCharCode(buff[p+i]); return s; },
|
||
readFloat : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<4;i++) a[i]=buff[p+3-i]; return UTIF._binBE.fl32[0]; },
|
||
readDouble : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<8;i++) a[i]=buff[p+7-i]; return UTIF._binBE.fl64[0]; },
|
||
|
||
writeUshort: function(buff, p, n) { buff[p] = (n>> 8)&255; buff[p+1] = n&255; },
|
||
writeUint : function(buff, p, n) { buff[p] = (n>>24)&255; buff[p+1] = (n>>16)&255; buff[p+2] = (n>>8)&255; buff[p+3] = (n>>0)&255; },
|
||
writeASCII : function(buff, p, s) { for(var i = 0; i < s.length; i++) buff[p+i] = s.charCodeAt(i); },
|
||
writeDouble: function(buff, p, n)
|
||
{
|
||
UTIF._binBE.fl64[0] = n;
|
||
for (var i = 0; i < 8; i++) buff[p + i] = UTIF._binBE.ui8[7 - i];
|
||
}
|
||
}
|
||
UTIF._binBE.ui8 = new Uint8Array (8);
|
||
UTIF._binBE.i16 = new Int16Array (UTIF._binBE.ui8.buffer);
|
||
UTIF._binBE.i32 = new Int32Array (UTIF._binBE.ui8.buffer);
|
||
UTIF._binBE.ui32 = new Uint32Array (UTIF._binBE.ui8.buffer);
|
||
UTIF._binBE.fl32 = new Float32Array(UTIF._binBE.ui8.buffer);
|
||
UTIF._binBE.fl64 = new Float64Array(UTIF._binBE.ui8.buffer);
|
||
|
||
UTIF._binLE =
|
||
{
|
||
nextZero : UTIF._binBE.nextZero,
|
||
readUshort : function(buff, p) { return (buff[p+1]<< 8) | buff[p]; },
|
||
readShort : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; return UTIF._binBE. i16[0]; },
|
||
readInt : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE. i32[0]; },
|
||
readUint : function(buff, p) { var a=UTIF._binBE.ui8; a[0]=buff[p+0]; a[1]=buff[p+1]; a[2]=buff[p+2]; a[3]=buff[p+3]; return UTIF._binBE.ui32[0]; },
|
||
readASCII : UTIF._binBE.readASCII,
|
||
readFloat : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<4;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl32[0]; },
|
||
readDouble : function(buff, p) { var a=UTIF._binBE.ui8; for(var i=0;i<8;i++) a[i]=buff[p+ i]; return UTIF._binBE.fl64[0]; }
|
||
}
|
||
UTIF._copyTile = function(tb, tw, th, b, w, h, xoff, yoff)
|
||
{
|
||
//log("copyTile", tw, th, w, h, xoff, yoff);
|
||
var xlim = Math.min(tw, w-xoff);
|
||
var ylim = Math.min(th, h-yoff);
|
||
for(var y=0; y<ylim; y++)
|
||
{
|
||
var tof = (yoff+y)*w+xoff;
|
||
var sof = y*tw;
|
||
for(var x=0; x<xlim; x++) b[tof+x] = tb[sof+x];
|
||
}
|
||
}
|
||
|
||
UTIF.LosslessJpegDecode = (function(){function t(Z){this.w=Z;this.N=0;this._=0;this.G=0}t.prototype={t:function(Z){this.N=Math.max(0,Math.min(this.w.length,Z))},i:function(){return this.w[this.N++]},l:function(){var Z=this.N;
|
||
this.N+=2;return this.w[Z]<<8|this.w[Z+1]},J:function(){if(this._==0){this.G=this.w[this.N];this.N+=1+(this.G+1>>>8);
|
||
this._=8}return this.G>>>--this._&1},Z:function(Z){var X=this._,s=this.G,E=Math.min(X,Z);Z-=E;X-=E;var Y=s>>>X&(1<<E)-1;
|
||
while(Z>0){s=this.w[this.N];this.N+=1+(s+1>>>8);E=Math.min(8,Z);Z-=E;X=8-E;Y<<=E;Y|=s>>>X&(1<<E)-1}this._=X;
|
||
this.G=s;return Y}};var i={};i.X=function(){return[0,0,-1]};i.s=function(Z,X,s){Z[i.Y(Z,0,s)+2]=X};i.Y=function(Z,X,s){if(Z[X+2]!=-1)return 0;
|
||
if(s==0)return X;for(var E=0;E<2;E++){if(Z[X+E]==0){Z[X+E]=Z.length;Z.push(0);Z.push(0);Z.push(-1)}var Y=i.Y(Z,Z[X+E],s-1);
|
||
if(Y!=0)return Y}return 0};i.B=function(Z,X){var s=0,E=0,Y=0,B=X._,$=X.G,e=X.N;while(!0){if(B==0){$=X.w[e];
|
||
e+=1+($+1>>>8);B=8}Y=$>>>--B&1;s=Z[s+Y];E=Z[s+2];if(E!=-1){X._=B;X.G=$;X.N=e;return E}}return-1};function l(Z){this.z=new t(Z);
|
||
this.D(this.z)}l.prototype={$:function(Z,X){this.Q=Z.i();this.F=Z.l();this.o=Z.l();var s=this.O=Z.i();
|
||
this.L=[];for(var E=0;E<s;E++){var Y=Z.i(),B=Z.i();Z.i();this.L[Y]=E}Z.t(Z.N+X-(6+s*3))},e:function(){var Z=0,X=this.z.i();
|
||
if(this.H==null)this.H={};var s=this.H[X]=i.X(),E=[];for(var Y=0;Y<16;Y++){E[Y]=this.z.i();Z+=E[Y]}for(var Y=0;
|
||
Y<16;Y++)for(var B=0;B<E[Y];B++)i.s(s,this.z.i(),Y+1);return Z+17},W:function(Z){while(Z>0)Z-=this.e()},p:function(Z,X){var s=Z.i();
|
||
if(!this.U){this.U=[]}for(var E=0;E<s;E++){var Y=Z.i(),B=Z.i();this.U[this.L[Y]]=this.H[B>>>4]}this.g=Z.i();
|
||
Z.t(Z.N+X-(2+s*2))},D:function(Z){var X=!1,s=Z.l();if(s!==l.q)return;do{var s=Z.l(),E=Z.l()-2;switch(s){case l.m:this.$(Z,E);
|
||
break;case l.K:this.W(E);break;case l.V:this.p(Z,E);X=!0;break;default:Z.t(Z.N+E);break}}while(!X)},I:function(Z,X){var s=i.B(X,Z);
|
||
if(s==16)return-32768;var E=Z.Z(s);if((E&1<<s-1)==0)E-=(1<<s)-1;return E},B:function(Z,X){var s=this.z,E=this.O,Y=this.F,B=this.I,$=this.g,e=this.o*E,W=this.U;
|
||
for(var p=0;p<E;p++){Z[p]=B(s,W[p])+(1<<this.Q-1)}for(var D=E;D<e;D+=E){for(var p=0;p<E;p++)Z[D+p]=B(s,W[p])+Z[D+p-E]}var I=X;
|
||
for(var m=1;m<Y;m++){for(var p=0;p<E;p++){Z[I+p]=B(s,W[p])+Z[I+p-X]}for(var D=E;D<e;D+=E){for(var p=0;
|
||
p<E;p++){var K=I+D+p,q=Z[K-E];if($==6)q=Z[K-X]+(q-Z[K-E-X]>>>1);Z[K]=q+B(s,W[p])}}I+=X}}};l.m=65475;
|
||
l.K=65476;l.q=65496;l.V=65498;function J(Z){var X=new l(Z),s=X.Q>8?Uint16Array:Uint8Array,E=new s(X.o*X.F*X.O),Y=X.o*X.O;
|
||
X.B(E,Y);return E}return J}())
|
||
|
||
|
||
|
||
|
||
})(UTIF, pako);
|
||
})();
|
||
/**
|
||
* ZDDC — shared naming convention library
|
||
*
|
||
* Canonical implementation of all ZDDC filename, folder name, tracking number,
|
||
* revision, and status logic. Included in every tool's build via shared/zddc.js.
|
||
*
|
||
* Exposed as window.zddc (plain global) so it works with every tool's module
|
||
* pattern (archive globals, classifier IIFE, transmittal IIFE, mdedit globals).
|
||
*
|
||
* Public API
|
||
* ----------
|
||
* zddc.parseFilename(str) → ParsedFile | null
|
||
* zddc.parseFolder(str) → ParsedFolder | null
|
||
* zddc.parseRevision(str) → ParsedRevision
|
||
* zddc.formatFilename(parts) → string
|
||
* zddc.formatFolder(parts) → string
|
||
* zddc.compareRevisions(a, b) → number (-1 | 0 | 1)
|
||
* zddc.isValidStatus(str) → boolean
|
||
* zddc.STATUSES → string[]
|
||
*
|
||
* ParsedFile { trackingNumber, revision, status, title, extension }
|
||
* ParsedFolder { date, trackingNumber, status, title }
|
||
* ParsedRevision { base, modifier, modifierType, modifierNumber, isDraft, full }
|
||
*/
|
||
|
||
(function (root) {
|
||
'use strict';
|
||
|
||
// ── Valid status codes ───────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Complete list of valid ZDDC document status codes.
|
||
* '---' denotes an unknown or not-yet-assigned status.
|
||
*/
|
||
var STATUSES = [
|
||
'---',
|
||
'IFA', 'IFB', 'IFC', 'IFD', 'IFI', 'IFP', 'IFR', 'IFU',
|
||
'REC',
|
||
'RSA', 'RSB', 'RSC', 'RSD', 'RSI',
|
||
];
|
||
|
||
var STATUS_SET = {};
|
||
for (var _i = 0; _i < STATUSES.length; _i++) {
|
||
STATUS_SET[STATUSES[_i]] = true;
|
||
}
|
||
|
||
function isValidStatus(str) {
|
||
return !!STATUS_SET[str];
|
||
}
|
||
|
||
// ── Filename parsing ─────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Canonical file regex.
|
||
* Matches: TRACKING_REVISION (STATUS) - TITLE.EXT
|
||
*
|
||
* Tracking number: no underscores, no whitespace.
|
||
* Revision: no whitespace, no parentheses.
|
||
* Status: anything inside parentheses (validated separately).
|
||
* Title: everything up to the last dot.
|
||
* Extension: after the last dot (lowercased by parseFilename).
|
||
*/
|
||
var FILE_RE = /^([^_\s]+)_([^\s()_]+)\s*\(([^)]+)\)\s*-\s*(\S.*\S|\S)\.\s*([^\s.]+)$/;
|
||
|
||
/**
|
||
* Parse a ZDDC filename.
|
||
*
|
||
* @param {string} filename
|
||
* @returns {{ trackingNumber: string, revision: string, status: string,
|
||
* title: string, extension: string, valid: boolean } | null}
|
||
* null only if filename is falsy.
|
||
* `valid` is true when all fields matched the ZDDC pattern.
|
||
*/
|
||
function parseFilename(filename) {
|
||
if (!filename) { return null; }
|
||
|
||
var match = filename.match(FILE_RE);
|
||
|
||
if (!match) {
|
||
var lastDot = filename.lastIndexOf('.');
|
||
return {
|
||
trackingNumber: '',
|
||
revision: '',
|
||
status: '',
|
||
title: lastDot > 0 ? filename.substring(0, lastDot) : filename,
|
||
extension: lastDot > 0 ? filename.substring(lastDot + 1).toLowerCase() : '',
|
||
valid: false,
|
||
};
|
||
}
|
||
|
||
return {
|
||
trackingNumber: match[1].trim(),
|
||
revision: match[2].trim(),
|
||
status: match[3].trim(),
|
||
title: match[4].trim(),
|
||
extension: match[5].toLowerCase(),
|
||
valid: true,
|
||
};
|
||
}
|
||
|
||
// ── Folder name parsing ──────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Transmittal folder regex.
|
||
* Matches: YYYY-MM-DD_TRACKING (STATUS) - TITLE
|
||
*/
|
||
var FOLDER_RE = /^(\d{4}-\d{2}-\d{2})_([^_\s(]+)\s*\(([^)]+)\)\s*-\s*(.+)$/;
|
||
|
||
/**
|
||
* Parse a ZDDC transmittal folder name.
|
||
*
|
||
* @param {string} foldername
|
||
* @returns {{ date: string, trackingNumber: string, status: string,
|
||
* title: string, valid: boolean } | null}
|
||
* null only if foldername is falsy.
|
||
*/
|
||
function parseFolder(foldername) {
|
||
if (!foldername) { return null; }
|
||
|
||
var match = foldername.match(FOLDER_RE);
|
||
|
||
if (!match) {
|
||
return {
|
||
date: '',
|
||
trackingNumber: '',
|
||
status: '',
|
||
title: foldername,
|
||
valid: false,
|
||
};
|
||
}
|
||
|
||
return {
|
||
date: match[1],
|
||
trackingNumber: match[2].trim(),
|
||
status: match[3].trim(),
|
||
title: match[4].trim(),
|
||
valid: true,
|
||
};
|
||
}
|
||
|
||
// ── Revision parsing ─────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Modifier sub-regex: +LETTER DIGITS e.g. +C1, +B2, +N1, +Q1
|
||
* The draft prefix (~) may appear inside the modifier: A+~C1
|
||
*/
|
||
var MODIFIER_RE = /^\+(~?)([A-Za-z])(\d+)$/;
|
||
|
||
/**
|
||
* Parse a ZDDC revision string.
|
||
*
|
||
* Revision grammar:
|
||
* revision = ['~'] base ['+' ['~'] modifier_letter modifier_number]
|
||
* base = letter(s) | digit(s) | date(YYYY-MM-DD)
|
||
* modifier = letter + digits e.g. C1, B2, N1, Q1
|
||
*
|
||
* @param {string} revision
|
||
* @returns {{
|
||
* base: string,
|
||
* modifier: string, full modifier string e.g. '+C1', '' if none
|
||
* modifierType: string, modifier letter e.g. 'C', '' if none
|
||
* modifierNumber: number, modifier number e.g. 1, 0 if none
|
||
* modifierIsDraft: boolean,
|
||
* isDraft: boolean, true if base revision starts with ~
|
||
* full: string, original input
|
||
* }}
|
||
*/
|
||
function parseRevision(revision) {
|
||
var raw = (revision || '').toString();
|
||
|
||
// Split on '+' to separate base from optional modifier
|
||
var plusIdx = raw.indexOf('+');
|
||
var basePart = plusIdx === -1 ? raw : raw.substring(0, plusIdx);
|
||
var modifierPart = plusIdx === -1 ? '' : raw.substring(plusIdx);
|
||
|
||
// Draft flag on the base part
|
||
var isDraft = basePart.startsWith('~');
|
||
var base = isDraft ? basePart.substring(1) : basePart;
|
||
|
||
// Parse modifier
|
||
var modifier = '';
|
||
var modifierType = '';
|
||
var modifierNumber = 0;
|
||
var modifierIsDraft = false;
|
||
|
||
if (modifierPart) {
|
||
var mMatch = modifierPart.match(MODIFIER_RE);
|
||
if (mMatch) {
|
||
modifierIsDraft = mMatch[1] === '~';
|
||
modifierType = mMatch[2].toUpperCase();
|
||
modifierNumber = parseInt(mMatch[3], 10);
|
||
modifier = modifierPart;
|
||
} else {
|
||
// Unrecognised modifier — preserve as-is
|
||
modifier = modifierPart;
|
||
}
|
||
}
|
||
|
||
return {
|
||
base: base,
|
||
modifier: modifier,
|
||
modifierType: modifierType,
|
||
modifierNumber: modifierNumber,
|
||
modifierIsDraft: modifierIsDraft,
|
||
isDraft: isDraft,
|
||
full: raw,
|
||
};
|
||
}
|
||
|
||
// ── Revision comparison ──────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Classify a base revision string into a sort tier:
|
||
* 0 = date (YYYY-MM-DD)
|
||
* 1 = letter(s) A, B, AA …
|
||
* 2 = number(s) 0, 1, 2, 1.5 …
|
||
* 3 = other
|
||
*/
|
||
function _baseTier(base) {
|
||
if (/^\d{4}-\d{2}-\d{2}$/.test(base)) { return 0; }
|
||
if (/^[A-Za-z]+$/.test(base)) { return 1; }
|
||
if (/^\d+(\.\d+)?$/.test(base)) { return 2; }
|
||
return 3;
|
||
}
|
||
|
||
/**
|
||
* Compare two base revision strings.
|
||
* Sort order: dates < letters < numbers < other.
|
||
*/
|
||
function _compareBase(a, b) {
|
||
var ta = _baseTier(a);
|
||
var tb = _baseTier(b);
|
||
if (ta !== tb) { return ta - tb; }
|
||
|
||
if (ta === 0) { return a < b ? -1 : a > b ? 1 : 0; } // date lexicographic = chronological
|
||
if (ta === 1) { return a.toUpperCase() < b.toUpperCase() ? -1 : a.toUpperCase() > b.toUpperCase() ? 1 : 0; }
|
||
if (ta === 2) { return parseFloat(a) - parseFloat(b); }
|
||
return a.localeCompare(b);
|
||
}
|
||
|
||
/**
|
||
* Compare two ZDDC revision strings for sort ordering.
|
||
*
|
||
* Canonical order (ascending = older → newer):
|
||
* ~A < A < A+B1 < A+C1 < A+~C2 < A+C2 < A+N1 < A+Q1
|
||
* < ~B < B < … < 0 < 1 < 2
|
||
*
|
||
* Rules:
|
||
* 1. Compare base revisions first (dates < letters < numbers).
|
||
* 2. For equal bases, draft (isDraft=true) comes before final.
|
||
* 3. For equal base+draft, no-modifier < has-modifier.
|
||
* 4. For equal base+draft+modifier presence:
|
||
* a. modifier draft comes before modifier final (modifierIsDraft).
|
||
* b. Sort modifier by type letter then by number.
|
||
*
|
||
* @param {string} a
|
||
* @param {string} b
|
||
* @returns {number} negative if a < b, 0 if equal, positive if a > b
|
||
*/
|
||
function compareRevisions(a, b) {
|
||
var pa = parseRevision(a);
|
||
var pb = parseRevision(b);
|
||
|
||
// 1. Base revision
|
||
var baseCmp = _compareBase(pa.base, pb.base);
|
||
if (baseCmp !== 0) { return baseCmp; }
|
||
|
||
// 2. Draft before final (for same base)
|
||
if (pa.isDraft !== pb.isDraft) { return pa.isDraft ? -1 : 1; }
|
||
|
||
// 3. No modifier before any modifier
|
||
var aHasMod = pa.modifier !== '';
|
||
var bHasMod = pb.modifier !== '';
|
||
if (aHasMod !== bHasMod) { return aHasMod ? 1 : -1; }
|
||
|
||
if (!aHasMod) { return 0; } // both have no modifier
|
||
|
||
// 4. Compare modifiers: type → number → draft (draft is a tie-breaker only)
|
||
// 4a. Modifier type letter (B < C < N < Q …)
|
||
if (pa.modifierType !== pb.modifierType) {
|
||
return pa.modifierType < pb.modifierType ? -1 : 1;
|
||
}
|
||
|
||
// 4b. Modifier number (1 < 2 …)
|
||
if (pa.modifierNumber !== pb.modifierNumber) {
|
||
return pa.modifierNumber - pb.modifierNumber;
|
||
}
|
||
|
||
// 4c. Draft of a modifier comes before the final modifier (same type+number)
|
||
if (pa.modifierIsDraft !== pb.modifierIsDraft) {
|
||
return pa.modifierIsDraft ? -1 : 1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
// ── Filename / folder formatting ─────────────────────────────────────────
|
||
|
||
/**
|
||
* Build a ZDDC filename from its components.
|
||
*
|
||
* @param {{ trackingNumber: string, revision: string, status: string,
|
||
* title: string, extension: string }} parts
|
||
* @returns {string} e.g. "123456-EL-SPC-2623_A (IFR) - Specification.pdf"
|
||
*/
|
||
function formatFilename(parts) {
|
||
var tn = (parts.trackingNumber || '').trim();
|
||
var rev = (parts.revision || '').trim();
|
||
var st = (parts.status || '').trim();
|
||
var ttl = (parts.title || '').trim();
|
||
var ext = (parts.extension || '').replace(/^\./, '');
|
||
|
||
if (!tn || !rev || !st || !ttl) { return ''; }
|
||
|
||
var name = tn + '_' + rev + ' (' + st + ') - ' + ttl;
|
||
return ext ? name + '.' + ext : name;
|
||
}
|
||
|
||
/**
|
||
* Build a ZDDC transmittal folder name from its components.
|
||
*
|
||
* @param {{ date: string, trackingNumber: string, status: string,
|
||
* title: string }} parts
|
||
* @returns {string} e.g. "2025-10-31_123456-EM-SUB-0001 (IFR) - Title"
|
||
*/
|
||
function formatFolder(parts) {
|
||
var dt = (parts.date || '').trim();
|
||
var tn = (parts.trackingNumber || '').trim();
|
||
var st = (parts.status || '').trim();
|
||
var ttl = (parts.title || '').trim();
|
||
|
||
if (!dt || !tn || !st || !ttl) { return ''; }
|
||
|
||
return dt + '_' + tn + ' (' + st + ') - ' + ttl;
|
||
}
|
||
|
||
// ── Filename / extension splitting ───────────────────────────────────────
|
||
|
||
/**
|
||
* Split a filename into its base name and extension (no leading dot).
|
||
* Treats leading dot ('.gitignore') as no extension.
|
||
*
|
||
* @param {string} filename
|
||
* @returns {{ name: string, extension: string }}
|
||
*/
|
||
function splitExtension(filename) {
|
||
if (!filename) { return { name: '', extension: '' }; }
|
||
var lastDot = filename.lastIndexOf('.');
|
||
if (lastDot <= 0) { return { name: filename, extension: '' }; }
|
||
return {
|
||
name: filename.substring(0, lastDot),
|
||
extension: filename.substring(lastDot + 1).toLowerCase(),
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Join a base name and extension. Tolerant of either form ('pdf' or '.pdf').
|
||
* Returns just the name when extension is empty.
|
||
*/
|
||
function joinExtension(name, extension) {
|
||
var ext = (extension || '').replace(/^\./, '');
|
||
return ext ? name + '.' + ext : name;
|
||
}
|
||
|
||
// ── Public API ───────────────────────────────────────────────────────────
|
||
|
||
root.zddc = {
|
||
STATUSES: STATUSES,
|
||
isValidStatus: isValidStatus,
|
||
parseFilename: parseFilename,
|
||
parseFolder: parseFolder,
|
||
parseRevision: parseRevision,
|
||
formatFilename: formatFilename,
|
||
formatFolder: formatFolder,
|
||
compareRevisions: compareRevisions,
|
||
splitExtension: splitExtension,
|
||
joinExtension: joinExtension,
|
||
};
|
||
|
||
}(typeof window !== 'undefined' ? window : this));
|
||
|
||
(function() {
|
||
'use strict';
|
||
|
||
// Escape a string for use in a RegExp (literal match)
|
||
function escapeRegex(str) {
|
||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||
}
|
||
|
||
// Build regex pattern at parse time based on anchors
|
||
function compilePattern(raw, anchorStart, anchorEnd) {
|
||
var src = (anchorStart ? '^' : '') + raw + (anchorEnd ? '$' : '');
|
||
try {
|
||
return new RegExp(src, 'i');
|
||
} catch (e) {
|
||
// Invalid regex — escape and retry (always succeeds)
|
||
var safe = (anchorStart ? '^' : '') + escapeRegex(raw) + (anchorEnd ? '$' : '');
|
||
return new RegExp(safe, 'i');
|
||
}
|
||
}
|
||
|
||
// Parse a single token string into a node
|
||
function parseToken(token) {
|
||
var s = token;
|
||
var negate = false;
|
||
var anchorStart = false;
|
||
var anchorEnd = false;
|
||
|
||
if (s.charAt(0) === '!') {
|
||
negate = true;
|
||
s = s.slice(1);
|
||
}
|
||
if (s.charAt(0) === '^') {
|
||
anchorStart = true;
|
||
s = s.slice(1);
|
||
}
|
||
if (s.length > 0 && s.charAt(s.length - 1) === '$') {
|
||
anchorEnd = true;
|
||
s = s.slice(0, -1);
|
||
}
|
||
|
||
if (s === '') return null;
|
||
|
||
// bare * (possibly after stripping !) → wildcard-all or wildcard-none
|
||
if (s === '*' && !anchorStart && !anchorEnd) {
|
||
return negate ? null : { type: 'wildcard-all' };
|
||
}
|
||
|
||
var re = compilePattern(s, anchorStart, anchorEnd);
|
||
return { type: negate ? 'no-match' : 'match', re: re };
|
||
}
|
||
|
||
// Parse expression string into AST array
|
||
function parse(expression) {
|
||
if (!expression || typeof expression !== 'string') return [];
|
||
var trimmed = expression.trim();
|
||
if (trimmed === '') return [];
|
||
if (trimmed === '*') return [{ type: 'wildcard-all' }];
|
||
|
||
var ast = [];
|
||
var i = 0;
|
||
var len = trimmed.length;
|
||
|
||
while (i < len) {
|
||
var ch = trimmed.charAt(i);
|
||
|
||
if (ch === '(') {
|
||
var depth = 1;
|
||
var j = i + 1;
|
||
while (j < len && depth > 0) {
|
||
if (trimmed.charAt(j) === '(') depth++;
|
||
else if (trimmed.charAt(j) === ')') depth--;
|
||
j++;
|
||
}
|
||
var innerAst = parse(trimmed.slice(i + 1, j - 1));
|
||
if (innerAst.length === 1) {
|
||
ast.push(innerAst[0]);
|
||
} else if (innerAst.length > 1) {
|
||
for (var k = 0; k < innerAst.length; k++) ast.push(innerAst[k]);
|
||
}
|
||
i = j;
|
||
} else if (ch === '|') {
|
||
ast.push({ type: 'pipe' });
|
||
i++;
|
||
} else if (ch === ' ') {
|
||
i++;
|
||
} else {
|
||
var j = i;
|
||
while (j < len) {
|
||
var c = trimmed.charAt(j);
|
||
if (c === ' ' || c === '(' || c === '|' || c === ')') break;
|
||
j++;
|
||
}
|
||
var token = trimmed.slice(i, j);
|
||
if (token.length > 0) {
|
||
var node = parseToken(token);
|
||
if (node !== null) ast.push(node);
|
||
}
|
||
i = j;
|
||
}
|
||
}
|
||
|
||
// Group pipes into OR nodes
|
||
var hasPipe = false;
|
||
var branches = [[]];
|
||
for (var l = 0; l < ast.length; l++) {
|
||
if (ast[l].type === 'pipe') {
|
||
hasPipe = true;
|
||
branches.push([]);
|
||
} else {
|
||
branches[branches.length - 1].push(ast[l]);
|
||
}
|
||
}
|
||
branches = branches.filter(function(b) { return b.length > 0; });
|
||
|
||
if (!hasPipe) {
|
||
return ast.filter(function(n) { return n.type !== 'pipe'; });
|
||
}
|
||
|
||
var orNodes = branches.map(function(branch) {
|
||
if (branch.length === 1) return branch[0];
|
||
return { type: 'and', nodes: branch };
|
||
});
|
||
return [{ type: 'or', nodes: orNodes }];
|
||
}
|
||
|
||
// Check if a single node matches the value
|
||
function nodeMatches(node, value) {
|
||
switch (node.type) {
|
||
case 'wildcard-all': return true;
|
||
case 'match': return node.re.test(value);
|
||
case 'no-match': return !node.re.test(value);
|
||
case 'or':
|
||
for (var i = 0; i < node.nodes.length; i++) {
|
||
if (nodeMatches(node.nodes[i], value)) return true;
|
||
}
|
||
return false;
|
||
case 'and':
|
||
for (var i = 0; i < node.nodes.length; i++) {
|
||
if (!nodeMatches(node.nodes[i], value)) return false;
|
||
}
|
||
return true;
|
||
default: return false;
|
||
}
|
||
}
|
||
|
||
// Evaluate AST against value
|
||
function matches(value, ast) {
|
||
if (!ast || ast.length === 0) return true;
|
||
var v = String(value); // no forced lowercase — regex has 'i' flag
|
||
for (var i = 0; i < ast.length; i++) {
|
||
if (!nodeMatches(ast[i], v)) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
if (!window.zddc) {
|
||
throw new Error('shared/zddc-filter.js: window.zddc must be loaded first');
|
||
}
|
||
window.zddc.filter = { parse: parse, matches: matches };
|
||
})();
|
||
|
||
/**
|
||
* ZDDC shared theme toggle — light / dark / auto.
|
||
* Persists choice to localStorage under 'zddc-theme'.
|
||
* Works with all four tools regardless of their module pattern.
|
||
* Expects: #theme-btn in the DOM (optional — skips gracefully if absent).
|
||
*
|
||
* Theme cycle: auto → light → dark → auto …
|
||
* 'auto' honours the OS prefers-color-scheme media query (CSS handles it).
|
||
* 'light' sets data-theme="light" on <html> (overrides dark media query).
|
||
* 'dark' sets data-theme="dark" on <html>.
|
||
*/
|
||
(function () {
|
||
'use strict';
|
||
|
||
var STORAGE_KEY = 'zddc-theme';
|
||
var THEMES = ['auto', 'light', 'dark'];
|
||
|
||
var LABELS = {
|
||
auto: '◐',
|
||
light: '☀',
|
||
dark: '☾'
|
||
};
|
||
|
||
var TITLES = {
|
||
auto: 'Theme: auto (follows OS)',
|
||
light: 'Theme: light',
|
||
dark: 'Theme: dark'
|
||
};
|
||
|
||
function load() {
|
||
var stored = localStorage.getItem(STORAGE_KEY);
|
||
return THEMES.indexOf(stored) !== -1 ? stored : 'auto';
|
||
}
|
||
|
||
function apply(theme) {
|
||
if (theme === 'dark') {
|
||
document.documentElement.setAttribute('data-theme', 'dark');
|
||
} else if (theme === 'light') {
|
||
document.documentElement.setAttribute('data-theme', 'light');
|
||
} else {
|
||
document.documentElement.removeAttribute('data-theme');
|
||
}
|
||
}
|
||
|
||
function save(theme) {
|
||
try { localStorage.setItem(STORAGE_KEY, theme); } catch (e) {}
|
||
}
|
||
|
||
function updateButton(btn, theme) {
|
||
btn.textContent = LABELS[theme];
|
||
btn.title = TITLES[theme];
|
||
btn.setAttribute('aria-label', TITLES[theme]);
|
||
}
|
||
|
||
function next(theme) {
|
||
return THEMES[(THEMES.indexOf(theme) + 1) % THEMES.length];
|
||
}
|
||
|
||
function init() {
|
||
var current = load();
|
||
apply(current);
|
||
|
||
var btn = document.getElementById('theme-btn');
|
||
if (!btn) { return; }
|
||
|
||
updateButton(btn, current);
|
||
|
||
btn.addEventListener('click', function () {
|
||
current = next(current);
|
||
apply(current);
|
||
save(current);
|
||
updateButton(btn, current);
|
||
});
|
||
}
|
||
|
||
/* Apply theme immediately (before DOM ready) to avoid flash */
|
||
apply(load());
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
} else {
|
||
init();
|
||
}
|
||
}());
|
||
|
||
// shared/toast.js — non-blocking notification helper available to every
|
||
// tool via window.zddc.toast(msg, level, opts). Originated as classifier's
|
||
// local showToast (classifier/js/excel.js); promoted here so tools that
|
||
// today use alert() or silent console.error can switch to a uniform
|
||
// non-blocking surface.
|
||
//
|
||
// Usage:
|
||
// window.zddc.toast('Saved.', 'success');
|
||
// window.zddc.toast('Could not load: ' + err.message, 'error');
|
||
// window.zddc.toast('Note', 'info', { durationMs: 3000 });
|
||
//
|
||
// Levels: 'info' (default) | 'success' | 'warning' | 'error'.
|
||
// Each tool may also expose app.notify(msg, level) as a thin wrapper —
|
||
// see ARCHITECTURE.md for the convention.
|
||
(function () {
|
||
'use strict';
|
||
|
||
if (!window.zddc) window.zddc = {};
|
||
// Don't overwrite if a tool defined its own first.
|
||
if (typeof window.zddc.toast === 'function') return;
|
||
|
||
var DEFAULT_DURATION_MS = 5000;
|
||
var FADE_MS = 300;
|
||
|
||
function toast(message, level, opts) {
|
||
opts = opts || {};
|
||
var lvl = (level === 'success' || level === 'error' ||
|
||
level === 'warning') ? level : 'info';
|
||
|
||
// Single-toast policy: dismiss any existing toast immediately
|
||
// so the new one is always the most recent. Matches the
|
||
// classifier's prior behavior and avoids stack-of-toasts UX.
|
||
var existing = document.querySelector('.zddc-toast');
|
||
if (existing) existing.remove();
|
||
|
||
var el = document.createElement('div');
|
||
el.className = 'zddc-toast zddc-toast--' + lvl;
|
||
// ARIA: errors get assertive (interrupts SR queue), others polite.
|
||
el.setAttribute('role', lvl === 'error' ? 'alert' : 'status');
|
||
el.setAttribute('aria-live', lvl === 'error' ? 'assertive' : 'polite');
|
||
el.textContent = message == null ? '' : String(message);
|
||
document.body.appendChild(el);
|
||
|
||
var dur = typeof opts.durationMs === 'number' ?
|
||
opts.durationMs : DEFAULT_DURATION_MS;
|
||
var timer = setTimeout(function () {
|
||
el.classList.add('zddc-toast--fade');
|
||
setTimeout(function () {
|
||
if (el.parentNode) el.parentNode.removeChild(el);
|
||
}, FADE_MS);
|
||
}, dur);
|
||
|
||
// Click-to-dismiss. Useful for sticky errors the user wants gone.
|
||
el.addEventListener('click', function () {
|
||
clearTimeout(timer);
|
||
if (el.parentNode) el.parentNode.removeChild(el);
|
||
});
|
||
|
||
return el;
|
||
}
|
||
|
||
window.zddc.toast = toast;
|
||
})();
|
||
|
||
// shared/nav.js — lateral navigation strip across the four canonical
|
||
// project stages (archive · working · staging · reviewing). Renders
|
||
// only when:
|
||
// 1. location.protocol is http: or https: (online — file:// has no
|
||
// project structure to navigate within), AND
|
||
// 2. a project segment can be detected from location.pathname (the
|
||
// first path segment, when it isn't a tool HTML file).
|
||
//
|
||
// The strip is inserted as a sibling of <header class="app-header">
|
||
// on DOMContentLoaded — no template changes required. Each tool just
|
||
// needs ../shared/nav.{js,css} in its build.sh.
|
||
//
|
||
// Stage URLs follow the canonical workflow folders documented at
|
||
// zddc.varasys.io/reference.html#transmittal-workflow:
|
||
// archive → <project>/archive.html (archive tool, project-root mode)
|
||
// working → <project>/working/ (directory listing → mdedit auto-serves)
|
||
// staging → <project>/staging/ (directory listing → transmittal auto-serves)
|
||
// reviewing → <project>/reviewing/ (directory listing)
|
||
//
|
||
// If a deployment doesn't have one of these folders the link will 404 —
|
||
// the strip is convention-driven, not probed. Operators on non-standard
|
||
// layouts can override by setting window.zddc.nav.disabled = true before
|
||
// DOMContentLoaded.
|
||
(function () {
|
||
'use strict';
|
||
|
||
if (!window.zddc) window.zddc = {};
|
||
if (window.zddc.nav) return; // already loaded
|
||
|
||
var STAGES = [
|
||
{ key: 'archive', label: 'Archive', target: 'archive.html' },
|
||
{ key: 'working', label: 'Working', target: 'working/' },
|
||
{ key: 'staging', label: 'Staging', target: 'staging/' },
|
||
{ key: 'reviewing', label: 'Reviewing', target: 'reviewing/' },
|
||
];
|
||
|
||
function projectSegment(pathname) {
|
||
var parts = pathname.split('/').filter(Boolean);
|
||
if (parts.length === 0) return null;
|
||
var first = parts[0];
|
||
// At deployment root (e.g. /archive.html?projects=A,B or
|
||
// /index.html) the first segment is a tool HTML — no single
|
||
// project to scope the strip to.
|
||
if (first.indexOf('.') !== -1) return null;
|
||
return first;
|
||
}
|
||
|
||
function currentStage(pathname) {
|
||
var parts = pathname.split('/').filter(Boolean);
|
||
if (parts.length < 2) return null;
|
||
var second = parts[1];
|
||
// <project>/working/... | staging/... | reviewing/... | archive/...
|
||
for (var i = 0; i < STAGES.length; i++) {
|
||
if (second === STAGES[i].key) return STAGES[i].key;
|
||
}
|
||
// <project>/archive.html → still the archive stage
|
||
if (second === 'archive.html') return 'archive';
|
||
return null;
|
||
}
|
||
|
||
function shouldRender() {
|
||
if (typeof location === 'undefined') return false;
|
||
if (location.protocol !== 'http:' && location.protocol !== 'https:') return false;
|
||
if (window.zddc.nav && window.zddc.nav.disabled) return false;
|
||
return projectSegment(location.pathname) !== null;
|
||
}
|
||
|
||
function buildStrip(project, active) {
|
||
var nav = document.createElement('nav');
|
||
nav.className = 'zddc-stage-strip';
|
||
nav.setAttribute('aria-label', 'Project stage');
|
||
|
||
var label = document.createElement('span');
|
||
label.className = 'zddc-stage-strip__project';
|
||
label.textContent = project;
|
||
nav.appendChild(label);
|
||
|
||
var sep0 = document.createElement('span');
|
||
sep0.className = 'zddc-stage-strip__divider';
|
||
sep0.setAttribute('aria-hidden', 'true');
|
||
sep0.textContent = '/';
|
||
nav.appendChild(sep0);
|
||
|
||
for (var i = 0; i < STAGES.length; i++) {
|
||
var s = STAGES[i];
|
||
var a = document.createElement('a');
|
||
a.className = 'zddc-stage';
|
||
a.href = '/' + encodeURIComponent(project) + '/' + s.target;
|
||
a.textContent = s.label;
|
||
if (s.key === active) {
|
||
a.classList.add('zddc-stage--active');
|
||
a.setAttribute('aria-current', 'page');
|
||
}
|
||
nav.appendChild(a);
|
||
|
||
if (i < STAGES.length - 1) {
|
||
var sep = document.createElement('span');
|
||
sep.className = 'zddc-stage-strip__sep';
|
||
sep.setAttribute('aria-hidden', 'true');
|
||
sep.textContent = '·';
|
||
nav.appendChild(sep);
|
||
}
|
||
}
|
||
|
||
return nav;
|
||
}
|
||
|
||
function mount() {
|
||
if (!shouldRender()) return;
|
||
var header = document.querySelector('.app-header');
|
||
if (!header) return;
|
||
// Don't double-mount if a tool's main.js calls us a second time.
|
||
if (header.previousElementSibling &&
|
||
header.previousElementSibling.classList &&
|
||
header.previousElementSibling.classList.contains('zddc-stage-strip')) {
|
||
return;
|
||
}
|
||
var project = projectSegment(location.pathname);
|
||
var active = currentStage(location.pathname);
|
||
var strip = buildStrip(project, active);
|
||
// Mount ABOVE the header — the strip is project-level chrome
|
||
// (where in the project), the header is tool-level chrome (which
|
||
// tool, theme, help). Reading order matches outer-to-inner scope.
|
||
header.parentNode.insertBefore(strip, header);
|
||
}
|
||
|
||
// Expose for tests + opt-out.
|
||
window.zddc.nav = {
|
||
mount: mount,
|
||
// Internals visible for unit tests; do not call from tools.
|
||
_projectSegment: projectSegment,
|
||
_currentStage: currentStage,
|
||
_stages: STAGES,
|
||
// Set to true before DOMContentLoaded to suppress mounting on
|
||
// deployments where the canonical folder layout doesn't apply.
|
||
disabled: false,
|
||
};
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', mount, { once: true });
|
||
} else {
|
||
mount();
|
||
}
|
||
})();
|
||
|
||
// shared/logo.js — turn the inert <svg class="app-header__logo"> on
|
||
// every tool's header into a clickable link. The destination is the
|
||
// nearest "home" the user can sensibly back out to:
|
||
//
|
||
// file:// → no wrap (no server home)
|
||
// http(s)://host/ → wrap, href = /
|
||
// http(s)://host/<tool>.html (deployment root)→ wrap, href = /
|
||
// http(s)://host/<project>/... → wrap, href = /<project>
|
||
//
|
||
// When inside a project, the logo takes the user to the project
|
||
// landing (synthetic page with the four lifecycle-stage cards + MDL
|
||
// instructions). When at the deployment root, the logo points at /
|
||
// (the project picker). Offline, the logo stays decorative — there's
|
||
// no real "home" to go to.
|
||
//
|
||
// Mounts as a sibling-replacement on DOMContentLoaded: wraps the
|
||
// existing logo SVG in an <a>, preserving classes and attributes.
|
||
// Idempotent: re-mounting on an already-wrapped logo is a no-op.
|
||
//
|
||
// Tools that want to override (e.g. a deployment that pins logo to
|
||
// an external URL) can set window.zddc.logo.disabled = true before
|
||
// DOMContentLoaded and inject their own anchor.
|
||
(function () {
|
||
'use strict';
|
||
|
||
if (!window.zddc) window.zddc = {};
|
||
if (window.zddc.logo) return;
|
||
|
||
function projectSegment(pathname) {
|
||
var parts = pathname.split('/').filter(Boolean);
|
||
if (parts.length === 0) return null;
|
||
var first = parts[0];
|
||
// Tool HTMLs at the deployment root (index.html, archive.html
|
||
// with ?projects=...) don't carry a project segment.
|
||
if (first.indexOf('.') !== -1) return null;
|
||
return first;
|
||
}
|
||
|
||
function targetHref() {
|
||
if (typeof location === 'undefined') return null;
|
||
if (location.protocol !== 'http:' && location.protocol !== 'https:') {
|
||
return null;
|
||
}
|
||
if (window.zddc.logo && window.zddc.logo.disabled) return null;
|
||
var seg = projectSegment(location.pathname);
|
||
return seg ? '/' + encodeURIComponent(seg) : '/';
|
||
}
|
||
|
||
function mount() {
|
||
var logo = document.querySelector('.app-header__logo');
|
||
if (!logo) return;
|
||
// Already wrapped (template-supplied anchor, or a previous mount).
|
||
if (logo.parentElement && logo.parentElement.tagName === 'A' &&
|
||
logo.parentElement.classList.contains('app-header__logo-link')) {
|
||
return;
|
||
}
|
||
var href = targetHref();
|
||
if (!href) return;
|
||
var a = document.createElement('a');
|
||
a.href = href;
|
||
a.className = 'app-header__logo-link';
|
||
var label = href === '/' ? 'ZDDC home' : 'Project home';
|
||
a.title = label;
|
||
a.setAttribute('aria-label', label);
|
||
logo.parentNode.insertBefore(a, logo);
|
||
a.appendChild(logo);
|
||
}
|
||
|
||
window.zddc.logo = {
|
||
mount: mount,
|
||
// Test seam.
|
||
_projectSegment: projectSegment,
|
||
_targetHref: targetHref,
|
||
disabled: false,
|
||
};
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', mount, { once: true });
|
||
} else {
|
||
mount();
|
||
}
|
||
})();
|
||
|
||
/**
|
||
* ZDDC shared help panel — open/close logic.
|
||
* Works with all four tools regardless of their module pattern.
|
||
* Expects: #help-btn, #help-panel, #help-panel-close in the DOM.
|
||
*/
|
||
(function () {
|
||
'use strict';
|
||
|
||
function init() {
|
||
var helpBtn = document.getElementById('help-btn');
|
||
var panel = document.getElementById('help-panel');
|
||
var closeBtn = document.getElementById('help-panel-close');
|
||
|
||
if (!helpBtn || !panel) { return; }
|
||
|
||
function isOpen() { return !panel.hidden; }
|
||
|
||
function openPanel() {
|
||
panel.hidden = false;
|
||
document.body.classList.add('help-open');
|
||
}
|
||
|
||
function closePanel() {
|
||
panel.hidden = true;
|
||
document.body.classList.remove('help-open');
|
||
}
|
||
|
||
helpBtn.addEventListener('click', function () {
|
||
if (isOpen()) { closePanel(); } else { openPanel(); }
|
||
});
|
||
|
||
if (closeBtn) {
|
||
closeBtn.addEventListener('click', closePanel);
|
||
}
|
||
|
||
document.addEventListener('keydown', function (e) {
|
||
if (e.key === 'Escape' && isOpen()) { closePanel(); }
|
||
});
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
} else {
|
||
init();
|
||
}
|
||
}());
|
||
|
||
/**
|
||
* ZDDC — shared preview helpers
|
||
*
|
||
* Cross-tool helpers for previewing file types that need a decoder:
|
||
* - TIFF (UTIF.js) — multi-page, browser-PDF-viewer-style toolbar
|
||
* - ZIP listing (JSZip) — sortable file-list view
|
||
*
|
||
* Renderers operate on any document (parent window or popup window), so the
|
||
* same code works for tools whose preview opens in a popup (classifier,
|
||
* archive, transmittal) and tools that render inline (mdedit).
|
||
*
|
||
* Public API on window.zddc.preview:
|
||
* loadLibrary(url) → Promise<void>
|
||
* renderTiff(doc, container, arrayBuffer, opts) → Promise<void>
|
||
* renderZipListing(doc, container, arrayBuffer, opts) → Promise<void>
|
||
* TIFF_EXTENSIONS, IMAGE_EXTENSIONS, TEXT_EXTENSIONS, OFFICE_EXTENSIONS
|
||
* isTiff(ext), isImage(ext), isText(ext), isZip(ext), isOffice(ext)
|
||
*
|
||
* Each tool keeps its own dispatcher; this lib only owns the heavy renderers.
|
||
*/
|
||
|
||
(function (root) {
|
||
'use strict';
|
||
|
||
var TIFF_EXTENSIONS = ['tif', 'tiff'];
|
||
var IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp', 'bmp', 'ico'];
|
||
var TEXT_EXTENSIONS = [
|
||
'txt', 'md', 'markdown', 'json', 'xml', 'csv', 'tsv', 'log',
|
||
'html', 'htm', 'css', 'js', 'mjs', 'ts', 'tsx', 'jsx',
|
||
'py', 'rb', 'sh', 'bash', 'zsh', 'bat', 'ps1',
|
||
'yaml', 'yml', 'ini', 'cfg', 'conf', 'toml',
|
||
'c', 'cc', 'cpp', 'h', 'hpp', 'go', 'rs', 'java', 'kt',
|
||
'sql', 'env'
|
||
];
|
||
var OFFICE_EXTENSIONS = ['docx', 'xlsx', 'xls'];
|
||
|
||
function lowerExt(ext) { return (ext || '').toLowerCase(); }
|
||
function isTiff(ext) { return TIFF_EXTENSIONS.indexOf(lowerExt(ext)) !== -1; }
|
||
function isImage(ext) { return IMAGE_EXTENSIONS.indexOf(lowerExt(ext)) !== -1; }
|
||
function isText(ext) { return TEXT_EXTENSIONS.indexOf(lowerExt(ext)) !== -1; }
|
||
function isZip(ext) { return lowerExt(ext) === 'zip'; }
|
||
function isOffice(ext) { return OFFICE_EXTENSIONS.indexOf(lowerExt(ext)) !== -1; }
|
||
|
||
// ── CDN library loader (parent window cache) ─────────────────────────────
|
||
|
||
var _libCache = new Map();
|
||
|
||
function loadLibrary(url) {
|
||
if (_libCache.has(url)) return _libCache.get(url);
|
||
var p = new Promise(function (resolve, reject) {
|
||
var s = document.createElement('script');
|
||
s.src = url;
|
||
s.onload = function () { resolve(); };
|
||
s.onerror = function () { reject(new Error('Failed to load: ' + url)); };
|
||
document.head.appendChild(s);
|
||
});
|
||
_libCache.set(url, p);
|
||
return p;
|
||
}
|
||
|
||
// ── Style injection (idempotent per-document) ────────────────────────────
|
||
|
||
function injectStyles(doc, id, css) {
|
||
if (doc.getElementById(id)) return;
|
||
var style = doc.createElement('style');
|
||
style.id = id;
|
||
style.textContent = css;
|
||
doc.head.appendChild(style);
|
||
}
|
||
|
||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||
|
||
function formatSize(bytes) {
|
||
if (bytes == null) return '';
|
||
if (bytes < 1024) return bytes + ' B';
|
||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||
if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
|
||
}
|
||
|
||
function formatDate(d) {
|
||
if (!d) return '';
|
||
var pad = function (n) { return n < 10 ? '0' + n : '' + n; };
|
||
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate())
|
||
+ ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
|
||
}
|
||
|
||
function escapeHtml(s) {
|
||
return String(s)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"');
|
||
}
|
||
|
||
// ── TIFF renderer ────────────────────────────────────────────────────────
|
||
|
||
var TIFF_CSS =
|
||
'.tiff-toolbar{display:flex;align-items:center;gap:.4rem;padding:.4rem .6rem;' +
|
||
'background:#f5f5f5;border-bottom:1px solid #ddd;flex-wrap:wrap;font-size:.85rem;}' +
|
||
'.tiff-toolbar .tiff-btn{padding:.25rem .55rem;border:1px solid #ccc;border-radius:3px;' +
|
||
'background:#fff;cursor:pointer;font-size:.85rem;line-height:1;min-width:1.8rem;}' +
|
||
'.tiff-toolbar .tiff-btn:hover:not(:disabled){background:#e8e8e8;}' +
|
||
'.tiff-toolbar .tiff-btn:disabled{opacity:.4;cursor:default;}' +
|
||
'.tiff-toolbar .tiff-page-info{display:inline-flex;align-items:center;gap:.3rem;}' +
|
||
'.tiff-toolbar .tiff-page-input{width:3.2rem;padding:.2rem .3rem;border:1px solid #ccc;' +
|
||
'border-radius:3px;text-align:center;font-size:.85rem;}' +
|
||
'.tiff-toolbar .tiff-zoom-select{padding:.2rem .3rem;border:1px solid #ccc;border-radius:3px;' +
|
||
'background:#fff;font-size:.85rem;}' +
|
||
'.tiff-toolbar .tiff-spacer{flex:1;}' +
|
||
'.tiff-viewport{flex:1;overflow:auto;background:#525659;display:flex;align-items:flex-start;' +
|
||
'justify-content:center;padding:1rem;}' +
|
||
'.tiff-canvas{background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.4);display:block;' +
|
||
'image-rendering:auto;}' +
|
||
'.tiff-error{flex:1;display:flex;align-items:center;justify-content:center;color:#900;' +
|
||
'padding:2rem;text-align:center;}';
|
||
|
||
function renderTiff(doc, container, arrayBuffer, opts) {
|
||
opts = opts || {};
|
||
injectStyles(doc, 'zddc-tiff-styles', TIFF_CSS);
|
||
|
||
// UTIF is bundled (shared/vendor/utif.min.js) — window.UTIF is
|
||
// available synchronously. Promise.resolve() keeps the existing
|
||
// .then() chain shape so callers don't need to change.
|
||
return Promise.resolve().then(function () {
|
||
var ifds;
|
||
try {
|
||
ifds = window.UTIF.decode(arrayBuffer);
|
||
} catch (e) {
|
||
container.innerHTML = '<div class="tiff-error">Failed to parse TIFF: '
|
||
+ escapeHtml(e.message || e) + '</div>';
|
||
return;
|
||
}
|
||
if (!ifds || !ifds.length) {
|
||
container.innerHTML = '<div class="tiff-error">No images found in TIFF.</div>';
|
||
return;
|
||
}
|
||
|
||
// Reset container to a flex column
|
||
container.innerHTML = '';
|
||
container.style.display = 'flex';
|
||
container.style.flexDirection = 'column';
|
||
container.style.minHeight = '0';
|
||
container.style.height = '100%';
|
||
container.style.overflow = 'hidden';
|
||
|
||
// Toolbar
|
||
var toolbar = doc.createElement('div');
|
||
toolbar.className = 'tiff-toolbar';
|
||
|
||
var btnPrev = doc.createElement('button');
|
||
btnPrev.className = 'tiff-btn'; btnPrev.type = 'button';
|
||
btnPrev.title = 'Previous page'; btnPrev.textContent = '◀';
|
||
|
||
var pageInfo = doc.createElement('span');
|
||
pageInfo.className = 'tiff-page-info';
|
||
var pageInput = doc.createElement('input');
|
||
pageInput.type = 'number'; pageInput.min = '1'; pageInput.value = '1';
|
||
pageInput.className = 'tiff-page-input';
|
||
var pageOf = doc.createElement('span');
|
||
pageOf.textContent = ' of ' + ifds.length;
|
||
pageInfo.appendChild(doc.createTextNode('Page '));
|
||
pageInfo.appendChild(pageInput);
|
||
pageInfo.appendChild(pageOf);
|
||
|
||
var btnNext = doc.createElement('button');
|
||
btnNext.className = 'tiff-btn'; btnNext.type = 'button';
|
||
btnNext.title = 'Next page'; btnNext.textContent = '▶';
|
||
|
||
var spacer = doc.createElement('span');
|
||
spacer.className = 'tiff-spacer';
|
||
|
||
var btnZoomOut = doc.createElement('button');
|
||
btnZoomOut.className = 'tiff-btn'; btnZoomOut.type = 'button';
|
||
btnZoomOut.title = 'Zoom out'; btnZoomOut.textContent = '−';
|
||
|
||
var zoomSelect = doc.createElement('select');
|
||
zoomSelect.className = 'tiff-zoom-select';
|
||
var zoomOptions = [
|
||
['fit-width', 'Fit width'],
|
||
['fit-page', 'Fit page'],
|
||
['0.5', '50%'],
|
||
['0.75', '75%'],
|
||
['1', '100%'],
|
||
['1.25', '125%'],
|
||
['1.5', '150%'],
|
||
['2', '200%'],
|
||
['3', '300%'],
|
||
['4', '400%']
|
||
];
|
||
zoomOptions.forEach(function (z) {
|
||
var o = doc.createElement('option');
|
||
o.value = z[0]; o.textContent = z[1];
|
||
zoomSelect.appendChild(o);
|
||
});
|
||
zoomSelect.value = 'fit-width';
|
||
|
||
var btnZoomIn = doc.createElement('button');
|
||
btnZoomIn.className = 'tiff-btn'; btnZoomIn.type = 'button';
|
||
btnZoomIn.title = 'Zoom in'; btnZoomIn.textContent = '+';
|
||
|
||
toolbar.appendChild(btnPrev);
|
||
toolbar.appendChild(pageInfo);
|
||
toolbar.appendChild(btnNext);
|
||
toolbar.appendChild(spacer);
|
||
toolbar.appendChild(btnZoomOut);
|
||
toolbar.appendChild(zoomSelect);
|
||
toolbar.appendChild(btnZoomIn);
|
||
|
||
// Viewport with canvas
|
||
var viewport = doc.createElement('div');
|
||
viewport.className = 'tiff-viewport';
|
||
var canvas = doc.createElement('canvas');
|
||
canvas.className = 'tiff-canvas';
|
||
viewport.appendChild(canvas);
|
||
|
||
container.appendChild(toolbar);
|
||
container.appendChild(viewport);
|
||
|
||
// Render state
|
||
var currentPage = 0;
|
||
var zoom = 1;
|
||
var fitMode = 'width'; // 'width' | 'page' | null
|
||
var decoded = new Array(ifds.length);
|
||
|
||
function decodePage(i) {
|
||
if (decoded[i]) return decoded[i];
|
||
var ifd = ifds[i];
|
||
window.UTIF.decodeImage(arrayBuffer, ifd);
|
||
var rgba = window.UTIF.toRGBA8(ifd);
|
||
decoded[i] = { rgba: rgba, w: ifd.width, h: ifd.height };
|
||
return decoded[i];
|
||
}
|
||
|
||
function applyZoom() {
|
||
var page = decoded[currentPage];
|
||
if (!page) return;
|
||
var availW = viewport.clientWidth - 32; // padding
|
||
var availH = viewport.clientHeight - 32;
|
||
var scale;
|
||
if (fitMode === 'width') {
|
||
scale = availW / page.w;
|
||
} else if (fitMode === 'page') {
|
||
scale = Math.min(availW / page.w, availH / page.h);
|
||
} else {
|
||
scale = zoom;
|
||
}
|
||
if (!isFinite(scale) || scale <= 0) scale = 1;
|
||
canvas.style.width = (page.w * scale) + 'px';
|
||
canvas.style.height = (page.h * scale) + 'px';
|
||
}
|
||
|
||
function renderPage() {
|
||
var page;
|
||
try {
|
||
page = decodePage(currentPage);
|
||
} catch (e) {
|
||
container.innerHTML = '<div class="tiff-error">Failed to decode page '
|
||
+ (currentPage + 1) + ': ' + escapeHtml(e.message || e) + '</div>';
|
||
return;
|
||
}
|
||
canvas.width = page.w;
|
||
canvas.height = page.h;
|
||
var ctx = canvas.getContext('2d');
|
||
var imgData = ctx.createImageData(page.w, page.h);
|
||
imgData.data.set(page.rgba);
|
||
ctx.putImageData(imgData, 0, 0);
|
||
applyZoom();
|
||
pageInput.value = String(currentPage + 1);
|
||
btnPrev.disabled = currentPage <= 0;
|
||
btnNext.disabled = currentPage >= ifds.length - 1;
|
||
}
|
||
|
||
function setZoomFromSelect() {
|
||
var v = zoomSelect.value;
|
||
if (v === 'fit-width') { fitMode = 'width'; }
|
||
else if (v === 'fit-page') { fitMode = 'page'; }
|
||
else { fitMode = null; zoom = parseFloat(v) || 1; }
|
||
applyZoom();
|
||
}
|
||
|
||
function nudgeZoom(factor) {
|
||
if (fitMode) {
|
||
// capture current effective scale before leaving fit mode
|
||
var page = decoded[currentPage];
|
||
if (page) {
|
||
var availW = viewport.clientWidth - 32;
|
||
var availH = viewport.clientHeight - 32;
|
||
zoom = fitMode === 'width'
|
||
? availW / page.w
|
||
: Math.min(availW / page.w, availH / page.h);
|
||
} else {
|
||
zoom = 1;
|
||
}
|
||
fitMode = null;
|
||
}
|
||
zoom = Math.max(0.1, Math.min(8, zoom * factor));
|
||
// Match select option if any are close, else show as percent
|
||
var matched = false;
|
||
for (var i = 0; i < zoomSelect.options.length; i++) {
|
||
var ov = zoomSelect.options[i].value;
|
||
if (ov !== 'fit-width' && ov !== 'fit-page' && Math.abs(parseFloat(ov) - zoom) < 0.001) {
|
||
zoomSelect.value = ov; matched = true; break;
|
||
}
|
||
}
|
||
if (!matched) {
|
||
// Nearest standard step
|
||
var best = '1', bestDiff = Infinity;
|
||
for (var j = 0; j < zoomSelect.options.length; j++) {
|
||
var v2 = zoomSelect.options[j].value;
|
||
if (v2 === 'fit-width' || v2 === 'fit-page') continue;
|
||
var diff = Math.abs(parseFloat(v2) - zoom);
|
||
if (diff < bestDiff) { bestDiff = diff; best = v2; }
|
||
}
|
||
zoom = parseFloat(best);
|
||
zoomSelect.value = best;
|
||
}
|
||
applyZoom();
|
||
}
|
||
|
||
btnPrev.addEventListener('click', function () {
|
||
if (currentPage > 0) { currentPage--; renderPage(); }
|
||
});
|
||
btnNext.addEventListener('click', function () {
|
||
if (currentPage < ifds.length - 1) { currentPage++; renderPage(); }
|
||
});
|
||
pageInput.addEventListener('change', function () {
|
||
var n = parseInt(pageInput.value, 10);
|
||
if (!isNaN(n) && n >= 1 && n <= ifds.length) {
|
||
currentPage = n - 1;
|
||
renderPage();
|
||
} else {
|
||
pageInput.value = String(currentPage + 1);
|
||
}
|
||
});
|
||
zoomSelect.addEventListener('change', setZoomFromSelect);
|
||
btnZoomIn.addEventListener('click', function () { nudgeZoom(1.25); });
|
||
btnZoomOut.addEventListener('click', function () { nudgeZoom(1 / 1.25); });
|
||
|
||
// Keyboard nav (only when toolbar/viewport in focus path)
|
||
container.tabIndex = 0;
|
||
container.addEventListener('keydown', function (e) {
|
||
if (e.target === pageInput) return;
|
||
if (e.key === 'ArrowLeft' || e.key === 'PageUp') {
|
||
if (currentPage > 0) { currentPage--; renderPage(); e.preventDefault(); }
|
||
} else if (e.key === 'ArrowRight' || e.key === 'PageDown' || e.key === ' ') {
|
||
if (currentPage < ifds.length - 1) { currentPage++; renderPage(); e.preventDefault(); }
|
||
}
|
||
});
|
||
|
||
// Re-fit on viewport resize
|
||
if (typeof (doc.defaultView && doc.defaultView.ResizeObserver) === 'function') {
|
||
var ro = new doc.defaultView.ResizeObserver(function () { applyZoom(); });
|
||
ro.observe(viewport);
|
||
} else if (doc.defaultView) {
|
||
doc.defaultView.addEventListener('resize', function () { applyZoom(); });
|
||
}
|
||
|
||
renderPage();
|
||
});
|
||
}
|
||
|
||
// ── ZIP listing renderer ─────────────────────────────────────────────────
|
||
|
||
var ZIP_CSS =
|
||
'.zip-header{padding:.4rem .8rem;background:#f5f5f5;border-bottom:1px solid #ddd;' +
|
||
'font-size:.85rem;color:#444;}' +
|
||
'.zip-table-wrap{flex:1;overflow:auto;}' +
|
||
'.zip-table{width:100%;border-collapse:collapse;font-size:.85rem;font-family:' +
|
||
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;}' +
|
||
'.zip-table thead th{position:sticky;top:0;background:#f0f0f0;text-align:left;' +
|
||
'padding:.4rem .6rem;border-bottom:1px solid #ccc;cursor:pointer;user-select:none;' +
|
||
'font-weight:600;}' +
|
||
'.zip-table thead th:hover{background:#e6e6e6;}' +
|
||
'.zip-table thead th.zip-sort-asc::after{content:" ▲";font-size:.7rem;color:#888;}' +
|
||
'.zip-table thead th.zip-sort-desc::after{content:" ▼";font-size:.7rem;color:#888;}' +
|
||
'.zip-table tbody td{padding:.3rem .6rem;border-bottom:1px solid #eee;}' +
|
||
'.zip-table tbody tr:hover{background:#f6faff;}' +
|
||
'.zip-table .zip-folder{color:#888;}' +
|
||
'.zip-table .zip-name{color:#222;}' +
|
||
'.zip-table .zip-size,.zip-table .zip-date{font-variant-numeric:tabular-nums;' +
|
||
'white-space:nowrap;color:#555;}' +
|
||
'.zip-table .zip-col-size,.zip-table .zip-col-date{text-align:right;}' +
|
||
'.zip-empty{padding:2rem;text-align:center;color:#888;}';
|
||
|
||
function renderZipListing(doc, container, arrayBuffer, opts) {
|
||
opts = opts || {};
|
||
injectStyles(doc, 'zddc-zip-styles', ZIP_CSS);
|
||
|
||
// JSZip is bundled in every tool that uses preview-lib (each
|
||
// tool's build.sh concatenates shared/vendor/jszip.min.js).
|
||
return window.JSZip.loadAsync(arrayBuffer).then(function (zip) {
|
||
var entries = [];
|
||
zip.forEach(function (relativePath, zipEntry) {
|
||
if (zipEntry.dir) return;
|
||
var size = (zipEntry._data && zipEntry._data.uncompressedSize) || 0;
|
||
entries.push({
|
||
path: relativePath,
|
||
name: relativePath.split('/').pop(),
|
||
size: size,
|
||
modified: zipEntry.date instanceof Date ? zipEntry.date : null
|
||
});
|
||
});
|
||
|
||
container.innerHTML = '';
|
||
container.style.display = 'flex';
|
||
container.style.flexDirection = 'column';
|
||
container.style.minHeight = '0';
|
||
container.style.height = '100%';
|
||
container.style.overflow = 'hidden';
|
||
|
||
var totalSize = entries.reduce(function (s, e) { return s + e.size; }, 0);
|
||
|
||
var header = doc.createElement('div');
|
||
header.className = 'zip-header';
|
||
header.textContent = entries.length + ' file' + (entries.length === 1 ? '' : 's')
|
||
+ (totalSize ? ' · ' + formatSize(totalSize) + ' uncompressed' : '');
|
||
container.appendChild(header);
|
||
|
||
if (!entries.length) {
|
||
var empty = doc.createElement('div');
|
||
empty.className = 'zip-empty';
|
||
empty.textContent = '(empty archive)';
|
||
container.appendChild(empty);
|
||
return;
|
||
}
|
||
|
||
var wrap = doc.createElement('div');
|
||
wrap.className = 'zip-table-wrap';
|
||
|
||
var table = doc.createElement('table');
|
||
table.className = 'zip-table';
|
||
var thead = doc.createElement('thead');
|
||
var trh = doc.createElement('tr');
|
||
var cols = [
|
||
{ key: 'path', label: 'Name', cls: 'zip-col-name' },
|
||
{ key: 'size', label: 'Size', cls: 'zip-col-size' },
|
||
{ key: 'modified', label: 'Modified', cls: 'zip-col-date' }
|
||
];
|
||
cols.forEach(function (c) {
|
||
var th = doc.createElement('th');
|
||
th.className = c.cls;
|
||
th.dataset.key = c.key;
|
||
th.textContent = c.label;
|
||
trh.appendChild(th);
|
||
});
|
||
thead.appendChild(trh);
|
||
table.appendChild(thead);
|
||
|
||
var tbody = doc.createElement('tbody');
|
||
table.appendChild(tbody);
|
||
|
||
wrap.appendChild(table);
|
||
container.appendChild(wrap);
|
||
|
||
var sortKey = 'path';
|
||
var sortDir = 1;
|
||
|
||
function render() {
|
||
var sorted = entries.slice().sort(function (a, b) {
|
||
var av, bv;
|
||
if (sortKey === 'size') { av = a.size; bv = b.size; }
|
||
else if (sortKey === 'modified') {
|
||
av = a.modified ? a.modified.getTime() : 0;
|
||
bv = b.modified ? b.modified.getTime() : 0;
|
||
} else {
|
||
av = a.path.toLowerCase(); bv = b.path.toLowerCase();
|
||
}
|
||
if (av < bv) return -1 * sortDir;
|
||
if (av > bv) return 1 * sortDir;
|
||
return 0;
|
||
});
|
||
|
||
tbody.innerHTML = '';
|
||
sorted.forEach(function (e) {
|
||
var tr = doc.createElement('tr');
|
||
var td1 = doc.createElement('td');
|
||
var slash = e.path.lastIndexOf('/');
|
||
if (slash >= 0) {
|
||
var folder = doc.createElement('span');
|
||
folder.className = 'zip-folder';
|
||
folder.textContent = e.path.substring(0, slash + 1);
|
||
td1.appendChild(folder);
|
||
}
|
||
var name = doc.createElement('span');
|
||
name.className = 'zip-name';
|
||
name.textContent = e.name;
|
||
td1.appendChild(name);
|
||
|
||
var td2 = doc.createElement('td');
|
||
td2.className = 'zip-size';
|
||
td2.textContent = formatSize(e.size);
|
||
|
||
var td3 = doc.createElement('td');
|
||
td3.className = 'zip-date';
|
||
td3.textContent = formatDate(e.modified);
|
||
|
||
tr.appendChild(td1); tr.appendChild(td2); tr.appendChild(td3);
|
||
tbody.appendChild(tr);
|
||
});
|
||
|
||
// Update sort arrows
|
||
var ths = thead.querySelectorAll('th');
|
||
for (var i = 0; i < ths.length; i++) {
|
||
ths[i].classList.remove('zip-sort-asc', 'zip-sort-desc');
|
||
if (ths[i].dataset.key === sortKey) {
|
||
ths[i].classList.add(sortDir > 0 ? 'zip-sort-asc' : 'zip-sort-desc');
|
||
}
|
||
}
|
||
}
|
||
|
||
thead.querySelectorAll('th').forEach(function (th) {
|
||
th.addEventListener('click', function () {
|
||
var k = th.dataset.key;
|
||
if (sortKey === k) sortDir = -sortDir;
|
||
else { sortKey = k; sortDir = 1; }
|
||
render();
|
||
});
|
||
});
|
||
|
||
render();
|
||
}).catch(function (err) {
|
||
container.innerHTML = '<div class="zip-empty">Failed to read ZIP: '
|
||
+ escapeHtml(err.message || err) + '</div>';
|
||
});
|
||
}
|
||
|
||
// ── Public API ───────────────────────────────────────────────────────────
|
||
|
||
if (!root.zddc) root.zddc = {};
|
||
root.zddc.preview = {
|
||
TIFF_EXTENSIONS: TIFF_EXTENSIONS,
|
||
IMAGE_EXTENSIONS: IMAGE_EXTENSIONS,
|
||
TEXT_EXTENSIONS: TEXT_EXTENSIONS,
|
||
OFFICE_EXTENSIONS: OFFICE_EXTENSIONS,
|
||
isTiff: isTiff,
|
||
isImage: isImage,
|
||
isText: isText,
|
||
isZip: isZip,
|
||
isOffice: isOffice,
|
||
loadLibrary: loadLibrary,
|
||
renderTiff: renderTiff,
|
||
renderZipListing: renderZipListing,
|
||
formatSize: formatSize,
|
||
formatDate: formatDate
|
||
};
|
||
})(typeof window !== 'undefined' ? window : this);
|
||
|
||
// Bootstrap window.app for the browse tool. Mirrors the convention
|
||
// used by every other ZDDC tool — ./build's CSS/JS concat order means
|
||
// this file runs FIRST inside the IIFE-of-IIFEs.
|
||
(function () {
|
||
'use strict';
|
||
|
||
if (!window.app) {
|
||
window.app = { modules: {}, state: {} };
|
||
}
|
||
|
||
window.app.state = {
|
||
// Source: 'server' | 'fs' | null. Determines how the loader
|
||
// resolves entries.
|
||
source: null,
|
||
|
||
// For server-source: the URL path of the directory currently
|
||
// being viewed. Always starts with '/' and ends with '/'.
|
||
// For fs-source: the displayed path string (no semantic
|
||
// meaning — just for the toolbar).
|
||
currentPath: '/',
|
||
|
||
// FileSystemAccessAPI root handle (null in server mode).
|
||
rootHandle: null,
|
||
|
||
// Sort state. key: 'name' | 'size' | 'ext' | 'date'. dir: 1 or -1.
|
||
sort: { key: 'name', dir: 1 },
|
||
|
||
// The tree's in-memory representation. Each node:
|
||
// { id, name, isDir, size, modTime, ext, url, depth,
|
||
// parentId, expanded, loaded, childIds, isZip, zipFile,
|
||
// zipPath }
|
||
// - isZip: set when the node IS a .zip file we know how to
|
||
// expand inline (server file or FS handle).
|
||
// - zipFile: cached JSZip instance for this archive (set
|
||
// after first expand).
|
||
// - zipPath: relative path WITHIN a zip (set on virtual
|
||
// children of an expanded zip; null otherwise).
|
||
// Stored flat in a Map keyed by id; render order derived
|
||
// from a depth-first walk.
|
||
nodes: new Map(),
|
||
rootIds: [],
|
||
nextId: 1,
|
||
|
||
// Single shared popup window for file preview (across
|
||
// multiple file clicks). Same pattern as archive's preview.
|
||
previewWindow: null
|
||
};
|
||
})();
|
||
|
||
// loader.js — fetches directory entries for either source mode.
|
||
//
|
||
// Server mode: GET <urlPath> with Accept: application/json. zddc-server
|
||
// (and Caddy's built-in browse, which we mirror) returns an array of
|
||
// FileInfo {name, size, url, mod_time, mode, is_dir, is_symlink}.
|
||
//
|
||
// FS-API mode: enumerate a FileSystemDirectoryHandle's children. No
|
||
// network involved; works on local folders the user picked.
|
||
(function () {
|
||
'use strict';
|
||
|
||
var state = window.app.state;
|
||
|
||
function splitExt(name) {
|
||
var i = name.lastIndexOf('.');
|
||
if (i <= 0 || i === name.length - 1) return '';
|
||
return name.substring(i + 1).toLowerCase();
|
||
}
|
||
|
||
// Build a raw entry from the server's FileInfo shape.
|
||
function fromServerEntry(e) {
|
||
// Server returns directory names with a trailing "/". Strip
|
||
// it for display; the is_dir flag is the canonical signal.
|
||
var displayName = e.is_dir ? e.name.replace(/\/$/, '') : e.name;
|
||
return {
|
||
name: displayName,
|
||
isDir: e.is_dir,
|
||
size: e.size || 0,
|
||
modTime: e.mod_time ? new Date(e.mod_time) : null,
|
||
ext: e.is_dir ? '' : splitExt(displayName),
|
||
url: e.url || null,
|
||
// FS-API specific (null in server mode):
|
||
handle: null
|
||
};
|
||
}
|
||
|
||
// Build a raw entry from a FileSystemHandle.
|
||
async function fromHandle(handle) {
|
||
var name = handle.name;
|
||
var isDir = handle.kind === 'directory';
|
||
var size = 0;
|
||
var modTime = null;
|
||
if (!isDir) {
|
||
try {
|
||
var f = await handle.getFile();
|
||
size = f.size;
|
||
modTime = new Date(f.lastModified);
|
||
} catch (_e) {
|
||
// permission lost; leave size/modTime defaults
|
||
}
|
||
}
|
||
return {
|
||
name: name,
|
||
isDir: isDir,
|
||
size: size,
|
||
modTime: modTime,
|
||
ext: isDir ? '' : splitExt(name),
|
||
url: null,
|
||
handle: handle
|
||
};
|
||
}
|
||
|
||
// Fetch children of a directory in server mode.
|
||
// path must end with '/' so the request hits the directory route.
|
||
//
|
||
// 404 is treated as "empty directory" rather than a hard error.
|
||
// A directory that doesn't exist on the server (e.g. a fresh
|
||
// project's working/ before any drafts have been created, or a
|
||
// dir deleted between listing and expand) is functionally
|
||
// indistinguishable from an empty one for tree-rendering purposes.
|
||
// Server-side, zddc-server already returns 200 + [] for canonical
|
||
// project folders that are missing on disk; this fallback covers
|
||
// the same UX for anything else and for non-zddc-server backends.
|
||
async function fetchServerChildren(path) {
|
||
if (!path.endsWith('/')) path += '/';
|
||
var resp = await fetch(path, {
|
||
headers: { 'Accept': 'application/json' },
|
||
credentials: 'same-origin'
|
||
});
|
||
if (resp.status === 404) {
|
||
return [];
|
||
}
|
||
if (!resp.ok) {
|
||
throw new Error('HTTP ' + resp.status + ' fetching ' + path);
|
||
}
|
||
var data = await resp.json();
|
||
if (!Array.isArray(data)) {
|
||
throw new Error('Unexpected response shape from ' + path);
|
||
}
|
||
return data.map(fromServerEntry);
|
||
}
|
||
|
||
// Enumerate a FileSystemDirectoryHandle's immediate children.
|
||
async function fetchFsChildren(dirHandle) {
|
||
var entries = [];
|
||
for await (var [_name, handle] of dirHandle.entries()) {
|
||
entries.push(await fromHandle(handle));
|
||
}
|
||
return entries;
|
||
}
|
||
|
||
// Probe whether THIS page is being served by zddc-server (or any
|
||
// server that responds to JSON listing requests). If so, switch to
|
||
// server mode automatically and load the current directory.
|
||
async function autoDetectServerMode() {
|
||
// Only attempt when running over http(s) and the location's
|
||
// path looks like a directory. Probing on file:// is pointless.
|
||
if (location.protocol !== 'http:' && location.protocol !== 'https:') {
|
||
return false;
|
||
}
|
||
// Strip any /<tool>.html from the path to get the directory.
|
||
var path = location.pathname;
|
||
// If the URL points at the browse.html itself, the directory
|
||
// is the parent. If it's a directory ending in '/', use it.
|
||
var dirPath;
|
||
if (path.endsWith('/')) {
|
||
dirPath = path;
|
||
} else {
|
||
// e.g. '/some/dir/browse.html' → '/some/dir/'
|
||
var slash = path.lastIndexOf('/');
|
||
dirPath = slash >= 0 ? path.substring(0, slash + 1) : '/';
|
||
}
|
||
|
||
try {
|
||
var entries = await fetchServerChildren(dirPath);
|
||
state.source = 'server';
|
||
state.currentPath = dirPath;
|
||
return { entries: entries, path: dirPath };
|
||
} catch (_e) {
|
||
// Not a server-backed page (e.g. opened via file://).
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// JSZip is vendored into the bundle (shared/vendor/jszip.min.js
|
||
// is concatenated ahead of init.js by build.sh), so it's always
|
||
// already attached to window.JSZip by the time any tree code runs.
|
||
// We keep the helper because tree.js calls it before reaching for
|
||
// window.JSZip; if the bundle is ever rebuilt without the vendor
|
||
// copy this will throw a clear error rather than silently failing.
|
||
function ensureJSZip() {
|
||
if (window.JSZip) return Promise.resolve();
|
||
return Promise.reject(new Error(
|
||
'JSZip not bundled — rebuild browse with shared/vendor/jszip.min.js'));
|
||
}
|
||
|
||
// Public API
|
||
window.app.modules.loader = {
|
||
fetchServerChildren: fetchServerChildren,
|
||
fetchFsChildren: fetchFsChildren,
|
||
autoDetectServerMode: autoDetectServerMode,
|
||
splitExt: splitExt,
|
||
ensureJSZip: ensureJSZip
|
||
};
|
||
})();
|
||
|
||
// tree.js — in-memory tree model + DOM rendering.
|
||
//
|
||
// Nodes are stored flat in state.nodes (Map by id). The visible
|
||
// render is a depth-first walk starting from state.rootIds, skipping
|
||
// children of unexpanded folders. This decouples model from DOM and
|
||
// keeps re-renders linear in the visible-row count.
|
||
(function () {
|
||
'use strict';
|
||
|
||
var state = window.app.state;
|
||
var loader = window.app.modules.loader;
|
||
|
||
// ── Model helpers ────────────────────────────────────────────────────
|
||
|
||
function newNode(raw, parentId, depth) {
|
||
var id = state.nextId++;
|
||
// ZIP files are treated as folders for tree purposes — the
|
||
// chevron lets the user expand them inline. The actual
|
||
// contents are loaded on first expand via JSZip.
|
||
var isZip = !raw.isDir && raw.ext === 'zip';
|
||
var node = {
|
||
id: id,
|
||
name: raw.name,
|
||
isDir: raw.isDir,
|
||
size: raw.size,
|
||
modTime: raw.modTime,
|
||
ext: raw.ext,
|
||
url: raw.url,
|
||
handle: raw.handle,
|
||
depth: depth,
|
||
parentId: parentId,
|
||
expanded: false,
|
||
loaded: false,
|
||
childIds: [],
|
||
isZip: isZip,
|
||
zipFile: null, // cached JSZip instance
|
||
zipPath: raw.zipPath || null, // path within zip (for virtual children)
|
||
zipParentId: raw.zipParentId || null // ancestor zip's node id (for nested entries)
|
||
};
|
||
state.nodes.set(id, node);
|
||
return node;
|
||
}
|
||
|
||
function clearTree() {
|
||
state.nodes.clear();
|
||
state.rootIds = [];
|
||
state.nextId = 1;
|
||
}
|
||
|
||
// Sort an array of nodes by current sort key. Folders always come
|
||
// first within a level (mimics common file managers).
|
||
function sortNodes(ids) {
|
||
var key = state.sort.key;
|
||
var dir = state.sort.dir;
|
||
ids.sort(function (a, b) {
|
||
var na = state.nodes.get(a);
|
||
var nb = state.nodes.get(b);
|
||
// Folders before files
|
||
if (na.isDir !== nb.isDir) return na.isDir ? -1 : 1;
|
||
var av, bv;
|
||
switch (key) {
|
||
case 'size':
|
||
av = na.size; bv = nb.size; break;
|
||
case 'ext':
|
||
av = na.ext; bv = nb.ext; break;
|
||
case 'date':
|
||
av = na.modTime ? na.modTime.getTime() : 0;
|
||
bv = nb.modTime ? nb.modTime.getTime() : 0;
|
||
break;
|
||
default:
|
||
av = na.name.toLowerCase();
|
||
bv = nb.name.toLowerCase();
|
||
}
|
||
if (av < bv) return -1 * dir;
|
||
if (av > bv) return 1 * dir;
|
||
return na.name.toLowerCase().localeCompare(nb.name.toLowerCase());
|
||
});
|
||
}
|
||
|
||
// Populate state with the root listing.
|
||
function setRoot(rawEntries) {
|
||
clearTree();
|
||
rawEntries.forEach(function (raw) {
|
||
var n = newNode(raw, null, 0);
|
||
state.rootIds.push(n.id);
|
||
});
|
||
sortNodes(state.rootIds);
|
||
}
|
||
|
||
// Populate a folder's children. Caller passes raw entries in any order.
|
||
function setChildren(parentId, rawEntries) {
|
||
var parent = state.nodes.get(parentId);
|
||
if (!parent) return;
|
||
// Drop any existing children first (re-load case).
|
||
parent.childIds.forEach(function (id) { state.nodes.delete(id); });
|
||
parent.childIds = [];
|
||
rawEntries.forEach(function (raw) {
|
||
var n = newNode(raw, parentId, parent.depth + 1);
|
||
parent.childIds.push(n.id);
|
||
});
|
||
sortNodes(parent.childIds);
|
||
parent.loaded = true;
|
||
}
|
||
|
||
// Walk nodes in render order. Skips the children of a collapsed
|
||
// expandable.
|
||
function visibleIds() {
|
||
var out = [];
|
||
function walk(ids) {
|
||
for (var i = 0; i < ids.length; i++) {
|
||
var n = state.nodes.get(ids[i]);
|
||
if (!n) continue;
|
||
out.push(ids[i]);
|
||
if ((n.isDir || n.isZip) && n.expanded) walk(n.childIds);
|
||
}
|
||
}
|
||
// Re-sort everything at all levels so a sort change reorders
|
||
// already-loaded children consistently.
|
||
sortNodes(state.rootIds);
|
||
state.nodes.forEach(function (n) {
|
||
if ((n.isDir || n.isZip) && n.loaded) sortNodes(n.childIds);
|
||
});
|
||
walk(state.rootIds);
|
||
return out;
|
||
}
|
||
|
||
// ── Rendering ────────────────────────────────────────────────────────
|
||
|
||
function fmtSize(bytes) {
|
||
if (bytes == null) return '';
|
||
if (bytes < 1024) return bytes + ' B';
|
||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||
if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
|
||
}
|
||
|
||
function fmtDate(d) {
|
||
if (!d) return '';
|
||
var pad = function (n) { return n < 10 ? '0' + n : '' + n; };
|
||
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate())
|
||
+ ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
|
||
}
|
||
|
||
function escapeHtml(s) {
|
||
return String(s).replace(/&/g, '&').replace(/</g, '<')
|
||
.replace(/>/g, '>').replace(/"/g, '"');
|
||
}
|
||
|
||
function rowHtml(node) {
|
||
var indent = node.depth * 1.2;
|
||
var expandable = node.isDir || node.isZip;
|
||
var iconChar = node.isDir ? '📁' : (node.isZip ? '🗜️' : '📄');
|
||
var chevronClass = 'tree-name__chevron'
|
||
+ (expandable ? '' : ' tree-name__chevron--leaf');
|
||
var nameInner;
|
||
if (node.isDir) {
|
||
nameInner = '<span class="tree-name__label is-folder">'
|
||
+ escapeHtml(node.name) + '</span>';
|
||
} else {
|
||
// File / zip: clickable. Plain click → preview popup.
|
||
// Modifier-click (ctrl/cmd) and middle-click → open in
|
||
// new tab (browser default for the href). Server mode
|
||
// gets the real URL (so right-click → save-link-as also
|
||
// works); FS mode and zip-virtual children get '#'.
|
||
var href = node.url || '#';
|
||
nameInner = '<a class="tree-name__label is-file"'
|
||
+ ' href="' + escapeHtml(href) + '"'
|
||
+ ' target="_blank" rel="noopener">' + escapeHtml(node.name) + '</a>';
|
||
}
|
||
return ''
|
||
+ '<tr class="tree-row ' + (node.expanded ? 'expanded' : '')
|
||
+ '" data-id="' + node.id
|
||
+ '" data-isdir="' + node.isDir
|
||
+ '" data-iszip="' + node.isZip + '">'
|
||
+ '<td class="col-name">'
|
||
+ '<span class="tree-name">'
|
||
+ '<span class="tree-name__indent" style="width:' + indent + 'rem;"></span>'
|
||
+ '<span class="' + chevronClass + '"></span>'
|
||
+ '<span class="tree-name__icon">' + iconChar + '</span>'
|
||
+ nameInner
|
||
+ '</span>'
|
||
+ '</td>'
|
||
+ '<td class="col-size">' + (node.isDir ? '' : fmtSize(node.size)) + '</td>'
|
||
+ '<td class="col-ext">' + (node.isDir ? '' : escapeHtml(node.ext)) + '</td>'
|
||
+ '<td class="col-date">' + fmtDate(node.modTime) + '</td>'
|
||
+ '</tr>';
|
||
}
|
||
|
||
function render() {
|
||
var tbody = document.getElementById('browseTbody');
|
||
if (!tbody) return;
|
||
var ids = visibleIds();
|
||
var html = '';
|
||
for (var i = 0; i < ids.length; i++) {
|
||
html += rowHtml(state.nodes.get(ids[i]));
|
||
}
|
||
tbody.innerHTML = html;
|
||
updateCount();
|
||
updateSortHeaders();
|
||
renderBreadcrumbs();
|
||
}
|
||
|
||
// Count nodes that render at the root + every expanded subtree.
|
||
function expandedSetSize() {
|
||
var n = 0;
|
||
function walk(ids) {
|
||
for (var i = 0; i < ids.length; i++) {
|
||
n++;
|
||
var node = state.nodes.get(ids[i]);
|
||
if (node && (node.isDir || node.isZip) && node.expanded) {
|
||
walk(node.childIds);
|
||
}
|
||
}
|
||
}
|
||
walk(state.rootIds);
|
||
return n;
|
||
}
|
||
|
||
function updateCount() {
|
||
var el = document.getElementById('entryCount');
|
||
if (!el) return;
|
||
var total = expandedSetSize();
|
||
el.textContent = total + ' item' + (total === 1 ? '' : 's');
|
||
}
|
||
|
||
// ── Breadcrumbs ──────────────────────────────────────────────────────
|
||
|
||
// Inline outline home icon. Stroke-based so it tints with the
|
||
// current text color rather than depending on emoji rendering.
|
||
var HOME_SVG = '<svg class="bc-home-icon" xmlns="http://www.w3.org/2000/svg" '
|
||
+ 'viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" '
|
||
+ 'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">'
|
||
+ '<path d="M3 12l9-9 9 9"/>'
|
||
+ '<path d="M5 10v10h14V10"/>'
|
||
+ '<path d="M10 20v-6h4v6"/></svg>';
|
||
|
||
function renderBreadcrumbs() {
|
||
var el = document.getElementById('breadcrumbs');
|
||
if (!el) return;
|
||
var html = '';
|
||
if (state.source === 'server') {
|
||
// Server mode: every segment links to its directory URL.
|
||
// The browser navigates → server returns embedded browse →
|
||
// the new instance auto-loads that directory's listing.
|
||
var path = state.currentPath || '/';
|
||
var parts = path.split('/').filter(Boolean);
|
||
html += '<a class="bc-link bc-root" href="/" title="Site root">'
|
||
+ HOME_SVG + '</a>';
|
||
var sofar = '';
|
||
for (var i = 0; i < parts.length; i++) {
|
||
sofar += '/' + parts[i];
|
||
var isLast = i === parts.length - 1;
|
||
html += '<span class="bc-sep">/</span>';
|
||
if (isLast) {
|
||
html += '<span class="bc-link bc-link--current">'
|
||
+ escapeHtml(parts[i]) + '</span>';
|
||
} else {
|
||
html += '<a class="bc-link" href="' + escapeHtml(sofar + '/') + '">'
|
||
+ escapeHtml(parts[i]) + '</a>';
|
||
}
|
||
}
|
||
html += '<span class="bc-sep">/</span>';
|
||
} else if (state.source === 'fs') {
|
||
// FS-API mode: ancestor handles weren't retained when the
|
||
// user picked the root, so we can't navigate up. Show the
|
||
// root icon + handle name without links.
|
||
var name = state.rootHandle ? state.rootHandle.name : '';
|
||
html += '<span class="bc-link bc-root" title="Local directory">'
|
||
+ HOME_SVG + '</span>';
|
||
if (name) {
|
||
html += '<span class="bc-sep">/</span>';
|
||
html += '<span class="bc-link bc-link--current">' + escapeHtml(name) + '</span>';
|
||
}
|
||
html += '<span class="bc-sep">/</span>';
|
||
}
|
||
el.innerHTML = html;
|
||
}
|
||
|
||
function updateSortHeaders() {
|
||
var ths = document.querySelectorAll('#browseTable thead th.sortable');
|
||
for (var i = 0; i < ths.length; i++) {
|
||
ths[i].classList.remove('sort-asc', 'sort-desc');
|
||
if (ths[i].dataset.sort === state.sort.key) {
|
||
ths[i].classList.add(state.sort.dir > 0 ? 'sort-asc' : 'sort-desc');
|
||
}
|
||
}
|
||
}
|
||
|
||
// Load a folder's children (lazy; idempotent re-loads). Dispatches
|
||
// by node kind:
|
||
// - regular folder → server JSON listing OR FS-API enumeration
|
||
// - zip file → fetch+JSZip; entries become virtual children
|
||
// - zip child dir → already-listed entries from the parent zip
|
||
// (zips are enumerated whole, so child dirs
|
||
// are pre-populated when the zip expands)
|
||
async function loadChildren(node) {
|
||
if (node.loaded) return;
|
||
try {
|
||
if (node.isZip) {
|
||
await loadZipChildren(node);
|
||
} else if (node._zipSyntheticDir) {
|
||
// Synthetic dir node materialized when a zip's entry
|
||
// list referenced "a/b/file" but had no "a/" entry.
|
||
// Re-walk the owning zip's flat entry list with the
|
||
// dir's full prefix.
|
||
var owner = state.nodes.get(node.zipParentId);
|
||
if (!owner || !owner.zipEntries) {
|
||
throw new Error('zip parent not loaded');
|
||
}
|
||
setZipDirChildren(node, owner, node.zipPath + '/');
|
||
} else if (node.isDir) {
|
||
var raw;
|
||
if (state.source === 'server') {
|
||
raw = await loader.fetchServerChildren(pathFor(node) + '/');
|
||
} else if (state.source === 'fs') {
|
||
raw = await loader.fetchFsChildren(node.handle);
|
||
} else {
|
||
return;
|
||
}
|
||
setChildren(node.id, raw);
|
||
}
|
||
} catch (e) {
|
||
window.app.modules.events.statusError(
|
||
'Failed to load ' + node.name + ': ' + e.message);
|
||
}
|
||
}
|
||
|
||
// Fetch a zip's bytes, parse with JSZip, and materialize its
|
||
// entries as a tree of virtual nodes. JSZip's entry list is flat
|
||
// (full paths); we reconstruct the directory hierarchy on top.
|
||
async function loadZipChildren(zipNode) {
|
||
await loader.ensureJSZip();
|
||
var arrayBuffer;
|
||
if (state.source === 'server' && zipNode.url) {
|
||
var resp = await fetch(zipNode.url);
|
||
if (!resp.ok) throw new Error('HTTP ' + resp.status + ' fetching ' + zipNode.url);
|
||
arrayBuffer = await resp.arrayBuffer();
|
||
} else if (zipNode.handle) {
|
||
// FS-API: top-level zip in a local folder.
|
||
var f = await zipNode.handle.getFile();
|
||
arrayBuffer = await f.arrayBuffer();
|
||
} else if (zipNode.zipParentId != null) {
|
||
// Nested zip inside another zip — read from parent JSZip.
|
||
var parent = state.nodes.get(zipNode.zipParentId);
|
||
if (!parent || !parent.zipFile) {
|
||
throw new Error('parent zip not loaded');
|
||
}
|
||
arrayBuffer = await parent.zipFile.file(zipNode.zipPath).async('arraybuffer');
|
||
} else {
|
||
throw new Error('cannot fetch zip bytes (no source)');
|
||
}
|
||
var zip = await window.JSZip.loadAsync(arrayBuffer);
|
||
zipNode.zipFile = zip;
|
||
|
||
// Build a path → raw-entry map. Entry paths are
|
||
// "dir/sub/file.ext" or "dir/" for directories. We slice
|
||
// to immediate children of zipNode (i.e. zero slashes after
|
||
// a leading prefix). For nested directories, we synthesize
|
||
// folder nodes that lazy-expand to the next level via the
|
||
// same raw-entry list — keep it on the zipNode for replay.
|
||
zipNode.zipEntries = []; // for re-walk on expand of subdirs
|
||
zip.forEach(function (relPath, entry) {
|
||
zipNode.zipEntries.push({
|
||
path: relPath.replace(/\/$/, ''),
|
||
isDir: entry.dir,
|
||
size: (entry._data && entry._data.uncompressedSize) || 0,
|
||
modTime: entry.date instanceof Date ? entry.date : null,
|
||
rawPath: relPath
|
||
});
|
||
});
|
||
|
||
// Now seed top-level children of the zip itself.
|
||
setZipDirChildren(zipNode, zipNode, '');
|
||
}
|
||
|
||
// Populate node's childIds with the entries directly under
|
||
// pathPrefix (relative to the owning zip). Directory entries
|
||
// become folder nodes whose own children are seeded on first
|
||
// expand by this same function (recursively descending zipPath).
|
||
function setZipDirChildren(node, zipOwner, pathPrefix) {
|
||
var seen = new Map(); // immediate child name → raw entry
|
||
zipOwner.zipEntries.forEach(function (e) {
|
||
if (!e.path.startsWith(pathPrefix)) return;
|
||
var rest = e.path.substring(pathPrefix.length);
|
||
if (rest === '') return;
|
||
// Take the FIRST segment of the remaining path
|
||
var slash = rest.indexOf('/');
|
||
var firstSeg = slash === -1 ? rest : rest.substring(0, slash);
|
||
var isImmediateFile = !e.isDir && slash === -1;
|
||
var isImmediateDir = e.isDir && slash === -1;
|
||
// For deeply-nested entries (rest contains a slash), we
|
||
// surface only the first segment as a synthetic folder
|
||
// entry. For immediate entries, we emit the entry as-is.
|
||
if (isImmediateFile || isImmediateDir) {
|
||
// Immediate entry — use the real metadata.
|
||
seen.set(firstSeg, {
|
||
name: firstSeg,
|
||
isDir: e.isDir,
|
||
size: e.size,
|
||
modTime: e.modTime,
|
||
ext: e.isDir ? '' : loader.splitExt(firstSeg),
|
||
url: null,
|
||
handle: null,
|
||
zipPath: e.path,
|
||
zipParentId: zipOwner.id
|
||
});
|
||
} else if (slash !== -1 && !seen.has(firstSeg)) {
|
||
// Deeper entry, no explicit dir entry yet — synthesize.
|
||
seen.set(firstSeg, {
|
||
name: firstSeg,
|
||
isDir: true,
|
||
size: 0,
|
||
modTime: null,
|
||
ext: '',
|
||
url: null,
|
||
handle: null,
|
||
zipPath: pathPrefix + firstSeg,
|
||
zipParentId: zipOwner.id
|
||
});
|
||
}
|
||
});
|
||
// Drop existing children (re-load case)
|
||
node.childIds.forEach(function (id) { state.nodes.delete(id); });
|
||
node.childIds = [];
|
||
seen.forEach(function (raw) {
|
||
var n = newNode(raw, node.id, node.depth + 1);
|
||
// Synthetic dir nodes inside zip don't have a dedicated
|
||
// load path — they re-walk zipEntries on expand. Mark
|
||
// them so the dispatcher knows.
|
||
if (raw.isDir && !n.isZip) {
|
||
n._zipSyntheticDir = true;
|
||
}
|
||
node.childIds.push(n.id);
|
||
});
|
||
sortNodes(node.childIds);
|
||
node.loaded = true;
|
||
}
|
||
|
||
// Toggle a folder's expanded state. Loads children on first expand.
|
||
// Treats "expandable" as either a real directory OR a zip file
|
||
// (zip files act like folders for tree purposes — the chevron
|
||
// expands them and the contents come from JSZip).
|
||
async function toggleFolder(nodeId) {
|
||
var n = state.nodes.get(nodeId);
|
||
if (!n || !(n.isDir || n.isZip)) return;
|
||
if (!n.expanded && !n.loaded) {
|
||
await loadChildren(n);
|
||
if (!n.loaded) return; // load failed (statusError already set)
|
||
}
|
||
n.expanded = !n.expanded;
|
||
render();
|
||
}
|
||
|
||
// Recursive expand: load + expand all descendants of nodeId. Used
|
||
// for Shift-click on a folder. Walks breadth-first, fanning out
|
||
// through children, grand-children, etc. until every reachable
|
||
// expandable node (folder OR zip) is loaded and marked expanded.
|
||
// Skips zip-EXPANSION recursion to avoid auto-loading every
|
||
// archive in the tree (those can be huge); plain folders only.
|
||
async function expandSubtree(nodeId) {
|
||
var root = state.nodes.get(nodeId);
|
||
if (!root || !(root.isDir || root.isZip)) return;
|
||
var status = window.app.modules.events.statusInfo;
|
||
status('Expanding subtree…');
|
||
var processed = 0;
|
||
var queue = [root];
|
||
while (queue.length) {
|
||
var batch = queue;
|
||
queue = [];
|
||
await Promise.all(batch.map(function (n) { return loadChildren(n); }));
|
||
for (var i = 0; i < batch.length; i++) {
|
||
var n = batch[i];
|
||
n.expanded = true;
|
||
processed++;
|
||
for (var j = 0; j < n.childIds.length; j++) {
|
||
var c = state.nodes.get(n.childIds[j]);
|
||
// Recurse into plain folders only — don't auto-
|
||
// expand zip archives during a subtree expand
|
||
// (they can be very large).
|
||
if (c && c.isDir && !c.isZip) queue.push(c);
|
||
}
|
||
}
|
||
render();
|
||
status('Expanding subtree… (' + processed + ' folders loaded)');
|
||
}
|
||
status('Expanded ' + processed + ' folder' + (processed === 1 ? '' : 's'));
|
||
}
|
||
|
||
function collapseSubtree(nodeId) {
|
||
var root = state.nodes.get(nodeId);
|
||
if (!root || !(root.isDir || root.isZip)) return;
|
||
function walk(n) {
|
||
n.expanded = false;
|
||
for (var i = 0; i < n.childIds.length; i++) {
|
||
var c = state.nodes.get(n.childIds[i]);
|
||
if (c && (c.isDir || c.isZip)) walk(c);
|
||
}
|
||
}
|
||
walk(root);
|
||
render();
|
||
}
|
||
|
||
// Compute the URL/path for a node by walking parents.
|
||
function pathFor(node) {
|
||
var parts = [];
|
||
var cur = node;
|
||
while (cur) {
|
||
parts.unshift(cur.name);
|
||
cur = cur.parentId == null ? null : state.nodes.get(cur.parentId);
|
||
}
|
||
if (state.source === 'server') {
|
||
// currentPath is the dir containing rootIds — root nodes
|
||
// sit DIRECTLY under it.
|
||
return state.currentPath.replace(/\/$/, '') + '/' + parts.join('/');
|
||
}
|
||
return parts.join('/');
|
||
}
|
||
|
||
// Public API
|
||
window.app.modules.tree = {
|
||
setRoot: setRoot,
|
||
setChildren: setChildren,
|
||
render: render,
|
||
toggleFolder: toggleFolder,
|
||
expandSubtree: expandSubtree,
|
||
collapseSubtree: collapseSubtree,
|
||
setSort: function (key) {
|
||
if (state.sort.key === key) {
|
||
state.sort.dir = -state.sort.dir;
|
||
} else {
|
||
state.sort.key = key;
|
||
state.sort.dir = 1;
|
||
}
|
||
render();
|
||
},
|
||
pathFor: pathFor
|
||
};
|
||
})();
|
||
|
||
// preview.js — file preview popup. Reuses shared/preview-lib.js for
|
||
// TIFF, ZIP listing, and image-rendering helpers; native iframe for
|
||
// PDF and HTML; <pre> for text; download button for everything else.
|
||
//
|
||
// Lifecycle: a single popup window is reused across multiple file
|
||
// clicks (state.previewWindow). Subsequent clicks rewrite its
|
||
// contents instead of spawning a new window — same UX as the archive
|
||
// tool.
|
||
(function () {
|
||
'use strict';
|
||
|
||
var state = window.app.state;
|
||
var loader = window.app.modules.loader;
|
||
var preview = window.zddc && window.zddc.preview;
|
||
if (!preview) {
|
||
// shared/preview-lib.js wasn't concatenated in. Bail loudly so
|
||
// the bug shows up in console rather than mysteriously failing.
|
||
console.error('[browse] zddc.preview not loaded — preview popup disabled.');
|
||
}
|
||
|
||
function escapeHtml(s) {
|
||
return String(s).replace(/&/g, '&').replace(/</g, '<')
|
||
.replace(/>/g, '>').replace(/"/g, '"');
|
||
}
|
||
|
||
var MIME = {
|
||
'pdf': 'application/pdf',
|
||
'html': 'text/html', 'htm': 'text/html',
|
||
'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png',
|
||
'gif': 'image/gif', 'webp': 'image/webp', 'svg': 'image/svg+xml',
|
||
'tif': 'image/tiff', 'tiff': 'image/tiff',
|
||
'zip': 'application/zip',
|
||
'txt': 'text/plain', 'md': 'text/markdown', 'json': 'application/json',
|
||
'xml': 'application/xml', 'csv': 'text/csv', 'log': 'text/plain',
|
||
'js': 'text/javascript', 'css': 'text/css'
|
||
};
|
||
|
||
// Pull bytes for a file node. Three sources:
|
||
// - server URL (zddc-server-backed file, including downloads
|
||
// of archived files served at real paths)
|
||
// - FS-API handle (local folder)
|
||
// - JSZip entry (file inside an expanded zip; reads from
|
||
// parent's cached JSZip instance)
|
||
async function getArrayBuffer(node) {
|
||
if (node.zipParentId != null) {
|
||
var owner = state.nodes.get(node.zipParentId);
|
||
if (!owner || !owner.zipFile) {
|
||
throw new Error('parent zip not loaded');
|
||
}
|
||
return await owner.zipFile.file(node.zipPath).async('arraybuffer');
|
||
}
|
||
if (state.source === 'server' && node.url) {
|
||
var resp = await fetch(node.url);
|
||
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
||
return await resp.arrayBuffer();
|
||
}
|
||
if (node.handle) {
|
||
var f = await node.handle.getFile();
|
||
return await f.arrayBuffer();
|
||
}
|
||
throw new Error('no source for file');
|
||
}
|
||
|
||
function getMime(ext) {
|
||
return MIME[ext] || 'application/octet-stream';
|
||
}
|
||
|
||
// Build a blob URL for the file's bytes. For server-mode regular
|
||
// files (not in a zip), prefer the live URL — relative links and
|
||
// server-side interception (e.g. .archive resolution) work then.
|
||
async function getBlobUrl(node) {
|
||
if (state.source === 'server' && node.url && node.zipParentId == null) {
|
||
return { url: node.url, fromServer: true };
|
||
}
|
||
var buf = await getArrayBuffer(node);
|
||
var blob = new Blob([buf], { type: getMime(node.ext) });
|
||
return { url: URL.createObjectURL(blob), fromServer: false };
|
||
}
|
||
|
||
function popupShell(node, primaryUrl) {
|
||
var safeName = escapeHtml(node.name);
|
||
var safeHref = escapeHtml(primaryUrl);
|
||
var ext = (node.ext || '').toLowerCase();
|
||
// Inline PDF and HTML previews load in iframes. HTML uses
|
||
// sandbox="allow-same-origin allow-popups
|
||
// allow-popups-to-escape-sandbox" — same posture as archive's
|
||
// preview: links navigate, scripts blocked, popups allowed.
|
||
var contentHtml;
|
||
if (ext === 'pdf') {
|
||
contentHtml = '<iframe src="' + safeHref + '"></iframe>';
|
||
} else if (ext === 'html' || ext === 'htm') {
|
||
contentHtml = '<iframe src="' + safeHref + '" sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox"></iframe>';
|
||
} else if (preview && preview.isImage(ext) && !preview.isTiff(ext)) {
|
||
contentHtml = '<img class="preview-image" src="' + safeHref + '" alt="' + safeName + '">';
|
||
} else {
|
||
contentHtml = '<div id="previewContent"><div class="loading">Loading preview…</div></div>';
|
||
}
|
||
return '<!DOCTYPE html><html><head><meta charset="UTF-8">'
|
||
+ '<title>' + safeName + ' — preview</title><style>'
|
||
+ '*{margin:0;padding:0;box-sizing:border-box;}'
|
||
+ 'body{display:flex;flex-direction:column;height:100vh;'
|
||
+ 'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;}'
|
||
+ '.toolbar{display:flex;align-items:center;gap:.5rem;padding:.5rem 1rem;'
|
||
+ 'background:#f5f5f5;border-bottom:1px solid #ddd;}'
|
||
+ '.toolbar h1{flex:1;font-size:.95rem;font-weight:500;overflow:hidden;'
|
||
+ 'text-overflow:ellipsis;white-space:nowrap;}'
|
||
+ '.btn{padding:.4rem .8rem;font-size:.85rem;border:1px solid #ccc;'
|
||
+ 'border-radius:4px;background:white;cursor:pointer;}'
|
||
+ '.btn:hover{background:#e8e8e8;}'
|
||
+ 'iframe{flex:1;width:100%;border:none;}'
|
||
+ '#previewContent{flex:1;overflow:auto;display:flex;flex-direction:column;}'
|
||
+ '.loading{display:flex;align-items:center;justify-content:center;height:100%;'
|
||
+ 'color:#666;font-size:1.1rem;}'
|
||
+ 'img.preview-image{max-width:100%;max-height:100%;object-fit:contain;'
|
||
+ 'margin:auto;display:block;}'
|
||
+ 'pre.preview-text{padding:1rem;font-family:Consolas,Monaco,monospace;'
|
||
+ 'font-size:.85rem;white-space:pre-wrap;word-wrap:break-word;}'
|
||
+ '</style></head><body>'
|
||
+ '<div class="toolbar"><h1>' + safeName + '</h1>'
|
||
+ '<button class="btn" onclick="downloadFile()">Download</button></div>'
|
||
+ contentHtml
|
||
+ '<script>'
|
||
+ 'var blobUrl=' + JSON.stringify(primaryUrl) + ';'
|
||
+ 'var fileName=' + JSON.stringify(node.name) + ';'
|
||
+ 'function downloadFile(){var a=document.createElement("a");'
|
||
+ 'a.href=blobUrl;a.download=fileName;document.body.appendChild(a);'
|
||
+ 'a.click();document.body.removeChild(a);}'
|
||
+ '</' + 'script></body></html>';
|
||
}
|
||
|
||
async function renderTextInWindow(node, win) {
|
||
var c = win.document.getElementById('previewContent');
|
||
if (!c) return;
|
||
try {
|
||
var buf = await getArrayBuffer(node);
|
||
var text = new TextDecoder('utf-8', { fatal: false }).decode(buf);
|
||
var MAX = 200000;
|
||
if (text.length > MAX) {
|
||
text = text.substring(0, MAX) + '\n\n... (truncated, '
|
||
+ (text.length - MAX) + ' more chars — Download for full file)';
|
||
}
|
||
var pre = win.document.createElement('pre');
|
||
pre.className = 'preview-text';
|
||
pre.textContent = text;
|
||
c.innerHTML = '';
|
||
c.appendChild(pre);
|
||
} catch (e) {
|
||
c.innerHTML = '<div class="loading">Error: ' + escapeHtml(e.message || e) + '</div>';
|
||
}
|
||
}
|
||
|
||
async function renderTiffInWindow(node, win) {
|
||
var c = win.document.getElementById('previewContent');
|
||
if (!c || !preview) return;
|
||
try {
|
||
var buf = await getArrayBuffer(node);
|
||
await preview.renderTiff(win.document, c, buf, { fileName: node.name });
|
||
} catch (e) {
|
||
c.innerHTML = '<div class="loading">Error rendering TIFF: '
|
||
+ escapeHtml(e.message || e) + '</div>';
|
||
}
|
||
}
|
||
|
||
async function renderZipInWindow(node, win) {
|
||
var c = win.document.getElementById('previewContent');
|
||
if (!c || !preview) return;
|
||
try {
|
||
var buf = await getArrayBuffer(node);
|
||
await preview.renderZipListing(win.document, c, buf, { fileName: node.name });
|
||
} catch (e) {
|
||
c.innerHTML = '<div class="loading">Error reading ZIP: '
|
||
+ escapeHtml(e.message || e) + '</div>';
|
||
}
|
||
}
|
||
|
||
async function showFilePreview(node) {
|
||
if (node.isDir) return;
|
||
|
||
var ext = (node.ext || '').toLowerCase();
|
||
var info;
|
||
try {
|
||
info = await getBlobUrl(node);
|
||
} catch (e) {
|
||
window.app.modules.events.statusError('Preview failed: ' + e.message);
|
||
return;
|
||
}
|
||
var html = popupShell(node, info.url);
|
||
|
||
var win = state.previewWindow;
|
||
if (win && !win.closed) {
|
||
win.document.open();
|
||
win.document.write(html);
|
||
win.document.close();
|
||
win.focus();
|
||
} else {
|
||
var w = Math.round(screen.width * 0.6);
|
||
var h = Math.round(screen.height * 0.8);
|
||
var left = Math.round((screen.width - w) / 2);
|
||
var top = Math.round((screen.height - h) / 2);
|
||
win = window.open('', 'browseFilePreview',
|
||
'width=' + w + ',height=' + h + ',left=' + left + ',top=' + top
|
||
+ ',resizable=yes,scrollbars=yes');
|
||
if (!win) {
|
||
// Popup blocked — fall back to opening the file directly.
|
||
window.open(info.url, '_blank', 'noopener');
|
||
return;
|
||
}
|
||
win.document.write(html);
|
||
win.document.close();
|
||
win.focus();
|
||
state.previewWindow = win;
|
||
}
|
||
|
||
// Async content rendering for the non-iframe types.
|
||
if (ext === 'pdf' || ext === 'html' || ext === 'htm') {
|
||
return; // iframe wired in popupShell
|
||
}
|
||
if (preview && preview.isImage(ext) && !preview.isTiff(ext)) {
|
||
return; // <img> wired in popupShell
|
||
}
|
||
if (preview && preview.isTiff(ext)) {
|
||
await renderTiffInWindow(node, win);
|
||
} else if (preview && preview.isZip(ext)) {
|
||
await renderZipInWindow(node, win);
|
||
} else if (preview && preview.isText(ext)) {
|
||
await renderTextInWindow(node, win);
|
||
} else {
|
||
// Unknown type — show a friendly "no preview, click
|
||
// download" placeholder.
|
||
var c = win.document.getElementById('previewContent');
|
||
if (c) {
|
||
c.innerHTML = '<div class="loading">No inline preview for .'
|
||
+ escapeHtml(ext) + ' — click Download.</div>';
|
||
}
|
||
}
|
||
}
|
||
|
||
window.app.modules.preview = { showFilePreview: showFilePreview };
|
||
})();
|
||
|
||
// events.js — wires up DOM listeners. Idempotent so app.js can call
|
||
// init() once on load.
|
||
(function () {
|
||
'use strict';
|
||
|
||
var state = window.app.state;
|
||
var tree = window.app.modules.tree;
|
||
var loader = window.app.modules.loader;
|
||
// preview module is loaded later (concat order); look it up at
|
||
// call time, not at IIFE-eval time.
|
||
function previewMod() { return window.app.modules.preview; }
|
||
|
||
function status(msg, kind) {
|
||
var el = document.getElementById('statusBar');
|
||
if (!el) return;
|
||
el.textContent = msg || '';
|
||
el.classList.remove('status-bar--error', 'status-bar--info');
|
||
if (kind === 'error') el.classList.add('status-bar--error');
|
||
if (kind === 'info') el.classList.add('status-bar--info');
|
||
}
|
||
|
||
function statusError(msg) { status(msg, 'error'); }
|
||
function statusInfo(msg) { status(msg, 'info'); }
|
||
function statusClear() { status('', null); }
|
||
|
||
async function pickLocalDir() {
|
||
if (typeof window.showDirectoryPicker !== 'function') {
|
||
statusError('Your browser does not support local folder selection. Use a recent Chromium-based browser, or open this page via zddc-server.');
|
||
return;
|
||
}
|
||
var handle;
|
||
try {
|
||
handle = await window.showDirectoryPicker({ mode: 'read' });
|
||
} catch (e) {
|
||
// User cancelled — silent
|
||
return;
|
||
}
|
||
state.source = 'fs';
|
||
state.rootHandle = handle;
|
||
state.currentPath = handle.name + '/';
|
||
var raw;
|
||
try {
|
||
raw = await loader.fetchFsChildren(handle);
|
||
} catch (e) {
|
||
statusError('Failed to read directory: ' + e.message);
|
||
return;
|
||
}
|
||
tree.setRoot(raw);
|
||
showBrowseRoot();
|
||
tree.render();
|
||
statusInfo('Loaded ' + raw.length + ' item' + (raw.length === 1 ? '' : 's'));
|
||
}
|
||
|
||
function showBrowseRoot() {
|
||
var empty = document.getElementById('emptyState');
|
||
var root = document.getElementById('browseRoot');
|
||
if (empty) empty.classList.add('hidden');
|
||
if (root) root.classList.remove('hidden');
|
||
applySourceUI();
|
||
}
|
||
|
||
// Visual state of the "Select Directory" button + the refresh
|
||
// button depends on the source. In server mode the user is
|
||
// already viewing a server-backed listing — Select Directory
|
||
// becomes a quiet "switch to local" affordance (subtle styling),
|
||
// and the refresh button is shown. In FS mode the button is
|
||
// primary (it's how you got here) and refresh is hidden (the
|
||
// listing was already a fresh enumeration).
|
||
function applySourceUI() {
|
||
var add = document.getElementById('addDirectoryBtn');
|
||
var refresh = document.getElementById('refreshHeaderBtn');
|
||
if (add) {
|
||
if (state.source === 'server') {
|
||
add.classList.remove('btn-primary');
|
||
add.classList.add('btn--subtle');
|
||
} else {
|
||
add.classList.add('btn-primary');
|
||
add.classList.remove('btn--subtle');
|
||
}
|
||
}
|
||
if (refresh) {
|
||
if (state.source) {
|
||
refresh.classList.remove('hidden');
|
||
} else {
|
||
refresh.classList.add('hidden');
|
||
}
|
||
}
|
||
}
|
||
|
||
async function refreshListing() {
|
||
if (state.source === 'server') {
|
||
var raw;
|
||
try {
|
||
raw = await loader.fetchServerChildren(state.currentPath);
|
||
} catch (e) {
|
||
statusError('Refresh failed: ' + e.message);
|
||
return;
|
||
}
|
||
tree.setRoot(raw);
|
||
tree.render();
|
||
statusInfo('Refreshed (' + raw.length + ' item'
|
||
+ (raw.length === 1 ? '' : 's') + ')');
|
||
} else if (state.source === 'fs' && state.rootHandle) {
|
||
var raw2;
|
||
try {
|
||
raw2 = await loader.fetchFsChildren(state.rootHandle);
|
||
} catch (e) {
|
||
statusError('Refresh failed: ' + e.message);
|
||
return;
|
||
}
|
||
tree.setRoot(raw2);
|
||
tree.render();
|
||
statusInfo('Refreshed');
|
||
}
|
||
}
|
||
|
||
function init() {
|
||
// Header buttons
|
||
var btn = document.getElementById('addDirectoryBtn');
|
||
if (btn) btn.addEventListener('click', pickLocalDir);
|
||
|
||
var refresh = document.getElementById('refreshHeaderBtn');
|
||
if (refresh) refresh.addEventListener('click', refreshListing);
|
||
|
||
// Sort headers
|
||
var ths = document.querySelectorAll('#browseTable thead th.sortable');
|
||
for (var i = 0; i < ths.length; i++) {
|
||
(function (th) {
|
||
th.addEventListener('click', function () {
|
||
tree.setSort(th.dataset.sort);
|
||
});
|
||
})(ths[i]);
|
||
}
|
||
|
||
// Tree-row clicks (event delegation on tbody).
|
||
// Click semantics on a folder row:
|
||
// - plain click → toggle just this folder
|
||
// - shift-click → recursive expand/collapse of the whole
|
||
// subtree (matches common file-explorer
|
||
// convention; e.g. Finder, VSCode tree,
|
||
// Windows Explorer)
|
||
// - alt-click → ALSO recursive (alt is sometimes the
|
||
// expand-all key on Linux DEs; bind both
|
||
// so muscle memory works either way)
|
||
// File rows: let the <a> tag's natural target=_blank do its
|
||
// job — don't intercept.
|
||
var tbody = document.getElementById('browseTbody');
|
||
if (tbody) {
|
||
tbody.addEventListener('click', function (e) {
|
||
var row = e.target.closest('tr.tree-row');
|
||
if (!row) return;
|
||
var id = parseInt(row.dataset.id, 10);
|
||
var node = state.nodes.get(id);
|
||
if (!node) return;
|
||
|
||
var isExpandable = row.dataset.isdir === 'true' || row.dataset.iszip === 'true';
|
||
var clickedChevron = !!e.target.closest('.tree-name__chevron');
|
||
|
||
if (isExpandable) {
|
||
// For folders + zips: click anywhere on the row
|
||
// toggles. Modifier-click → recursive expand.
|
||
e.preventDefault();
|
||
if (e.shiftKey || e.altKey) {
|
||
if (node.expanded) tree.collapseSubtree(id);
|
||
else tree.expandSubtree(id);
|
||
} else {
|
||
tree.toggleFolder(id);
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Plain file row.
|
||
// Modifier-click (ctrl/cmd) and middle-click → fall
|
||
// through to the <a> tag's natural target=_blank
|
||
// behavior (open in new tab). For server-backed
|
||
// files, that opens the real URL via zddc-server.
|
||
if (e.ctrlKey || e.metaKey || e.shiftKey || e.button === 1) {
|
||
return;
|
||
}
|
||
// Plain click → preview popup. Intercept default nav.
|
||
e.preventDefault();
|
||
var p = previewMod();
|
||
if (p) p.showFilePreview(node);
|
||
});
|
||
|
||
// Middle-click (auxclick) — same fall-through logic.
|
||
tbody.addEventListener('auxclick', function (e) {
|
||
if (e.button !== 1) return; // middle only
|
||
// Browser handles target=_blank natively for middle
|
||
// click; don't preventDefault, just don't intercept.
|
||
});
|
||
|
||
// Double-click on a folder → "navigate into" it. Distinct
|
||
// from single-click (which expands inline) so users keep
|
||
// both UX models. Server mode jumps to the folder URL —
|
||
// zddc-server returns a fresh browse instance scoped to
|
||
// that directory. FS-API mode swaps state.rootHandle to
|
||
// the folder's handle and re-loads, so the user sees
|
||
// only that subtree at the root level.
|
||
//
|
||
// Files: dblclick is left alone — the single-click preview
|
||
// is already a "look at this file" action; a separate
|
||
// navigate-into doesn't apply.
|
||
// ZIPs: skipped too — they're inspected via inline
|
||
// expansion (JSZip), not navigated into.
|
||
tbody.addEventListener('dblclick', function (e) {
|
||
var row = e.target.closest('tr.tree-row');
|
||
if (!row) return;
|
||
if (row.dataset.isdir !== 'true') return;
|
||
var id = parseInt(row.dataset.id, 10);
|
||
var node = state.nodes.get(id);
|
||
if (!node) return;
|
||
e.preventDefault();
|
||
navigateIntoFolder(node);
|
||
});
|
||
}
|
||
}
|
||
|
||
async function navigateIntoFolder(node) {
|
||
if (state.source === 'server') {
|
||
var url = window.app.modules.tree.pathFor(node);
|
||
if (!url.endsWith('/')) url += '/';
|
||
window.location.assign(url);
|
||
return;
|
||
}
|
||
if (state.source === 'fs') {
|
||
if (!node.handle || node.handle.kind !== 'directory') return;
|
||
state.rootHandle = node.handle;
|
||
state.currentPath = node.handle.name + '/';
|
||
var raw;
|
||
try {
|
||
raw = await loader.fetchFsChildren(node.handle);
|
||
} catch (e) {
|
||
statusError('Failed to enter ' + node.name + ': ' + e.message);
|
||
return;
|
||
}
|
||
tree.setRoot(raw);
|
||
tree.render();
|
||
statusInfo('Entered ' + node.name);
|
||
}
|
||
}
|
||
|
||
// Public API
|
||
window.app.modules.events = {
|
||
init: init,
|
||
statusError: statusError,
|
||
statusInfo: statusInfo,
|
||
statusClear: statusClear,
|
||
showBrowseRoot: showBrowseRoot
|
||
};
|
||
})();
|
||
|
||
// app.js — bootstrap. Runs after every other module's IIFE has
|
||
// registered its functions on window.app.modules.
|
||
(function () {
|
||
'use strict';
|
||
|
||
var state = window.app.state;
|
||
var loader = window.app.modules.loader;
|
||
var tree = window.app.modules.tree;
|
||
var events = window.app.modules.events;
|
||
|
||
async function bootstrap() {
|
||
events.init();
|
||
|
||
// Try server auto-detect. If this page is served by zddc-server
|
||
// (or any server with a Caddy-shaped JSON listing), load the
|
||
// current directory automatically. Otherwise show the empty
|
||
// state with the "Select Directory" button.
|
||
var detected = await loader.autoDetectServerMode();
|
||
if (detected) {
|
||
tree.setRoot(detected.entries);
|
||
events.showBrowseRoot();
|
||
tree.render();
|
||
events.statusInfo('Loaded ' + detected.entries.length + ' item'
|
||
+ (detected.entries.length === 1 ? '' : 's')
|
||
+ ' from ' + detected.path);
|
||
}
|
||
// Else: empty state stays visible; user can click Select Directory.
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', bootstrap);
|
||
} else {
|
||
bootstrap();
|
||
}
|
||
})();
|
||
|
||
</script>
|
||
</body>
|
||
</html>
|