@dirwell/theme-crosswave 0.0.0-stage → 0.0.1-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +16 -2
- package/dist/THIRD_PARTY_NOTICES.txt +166 -0
- package/dist/client.js +231 -0
- package/dist/component.css +0 -0
- package/dist/index.d.mts +34 -0
- package/dist/index.mjs +972 -0
- package/dist/index.mjs.map +1 -0
- package/dist/model-D4-K54I9.d.mts +53 -0
- package/dist/model.d.mts +2 -0
- package/dist/model.mjs +1 -0
- package/dist/source/Crosswave.vue +569 -0
- package/dist/source/component.d.mts +4 -0
- package/dist/source/shared/icons.mjs +12 -0
- package/dist/source/shared/navigation.mjs +294 -0
- package/dist/source/shared/records.mjs +60 -0
- package/dist/source/wave.mjs +132 -0
- package/dist/styles.css +23 -0
- package/package.json +49 -3
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { copyJsonObject, escapeHtml, projectListingEntry } from "@dirwell/core";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import mime from "mime";
|
|
6
|
+
import { computed, createSSRApp, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, unref, watch, watchEffect } from "vue";
|
|
7
|
+
import { renderToString, ssrIncludeBooleanAttr, ssrInterpolate, ssrLooseContain, ssrLooseEqual, ssrRenderAttr, ssrRenderClass, ssrRenderDynamicAttr, ssrRenderList, ssrRenderSlot } from "vue/server-renderer";
|
|
8
|
+
import { useGamepad } from "@vueuse/core";
|
|
9
|
+
import { ListingDataError, loadListing, parseListingEntries } from "@dirwell/core/client";
|
|
10
|
+
//#region ../shared-theme/src/page.ts
|
|
11
|
+
const pageAsset = (theme, directory, page = 1) => `${theme}-page-${createHash("sha256").update(directory).digest("hex").slice(0, 24)}-${page}.json`;
|
|
12
|
+
function folderDataHref(entry, context, theme) {
|
|
13
|
+
if (entry.exitsExplorer || entry.href === null || !(entry.kind === "directory" || entry.link?.targetKind === "directory")) return null;
|
|
14
|
+
const fake = "https://dirwell.invalid/";
|
|
15
|
+
const base = new URL(context.documentBaseHref ?? `${context.directoryPath ? context.directoryPath + "/" : ""}`, fake);
|
|
16
|
+
const root = new URL(context.hrefForDirectory(""), base);
|
|
17
|
+
const target = new URL(entry.href, base);
|
|
18
|
+
const rootPath = root.pathname.endsWith("/") ? root.pathname : root.pathname.slice(0, root.pathname.lastIndexOf("/") + 1);
|
|
19
|
+
const directoryPath = target.pathname.endsWith("/") ? target.pathname : target.pathname.slice(0, target.pathname.lastIndexOf("/") + 1);
|
|
20
|
+
const logical = directoryPath.slice(rootPath.length).split("/").filter(Boolean).map(decodeURIComponent).join("/");
|
|
21
|
+
const filename = pageAsset(theme, logical);
|
|
22
|
+
if (context.pageDataHref !== void 0) return context.pageDataHref(theme, logical, 1);
|
|
23
|
+
if (context.mode === "mpa") return context.assetHref(filename);
|
|
24
|
+
const href = new URL(filename, new URL(directoryPath, target));
|
|
25
|
+
if (href.origin !== new URL(fake).origin) return href.href;
|
|
26
|
+
if (context.documentBaseHref === null && !entry.href.startsWith("/")) return path.posix.relative(context.directoryPath || ".", path.posix.join(logical, filename)).split("/").map((segment) => segment === ".." || segment === "." ? segment : encodeURIComponent(segment)).join("/");
|
|
27
|
+
return href.pathname;
|
|
28
|
+
}
|
|
29
|
+
function createThemePage(context, theme, title) {
|
|
30
|
+
const directoryPath = context.directory.current.relativePath;
|
|
31
|
+
const safeContext = {
|
|
32
|
+
...context.pageDataHref === void 0 ? {} : { pageDataHref: context.pageDataHref },
|
|
33
|
+
mode: context.mode,
|
|
34
|
+
outputName: context.outputName,
|
|
35
|
+
assetHref: context.assetHref,
|
|
36
|
+
hrefForDirectory: context.hrefForDirectory,
|
|
37
|
+
documentBaseHref: context.documentBaseHref,
|
|
38
|
+
directoryPath
|
|
39
|
+
};
|
|
40
|
+
const dataForDirectory = (logicalPath) => folderDataHref({
|
|
41
|
+
id: logicalPath,
|
|
42
|
+
name: logicalPath,
|
|
43
|
+
kind: "directory",
|
|
44
|
+
href: context.hrefForDirectory(logicalPath),
|
|
45
|
+
exitsExplorer: false,
|
|
46
|
+
size: 0,
|
|
47
|
+
modifiedAt: "",
|
|
48
|
+
link: null
|
|
49
|
+
}, safeContext, theme.name);
|
|
50
|
+
const entries = context.directory.entries.map((entry) => {
|
|
51
|
+
const record = projectListingEntry(entry, context);
|
|
52
|
+
return theme.entryData === void 0 ? record : {
|
|
53
|
+
...record,
|
|
54
|
+
presentation: copyJsonObject(theme.entryData(record, safeContext))
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
version: 1,
|
|
59
|
+
...context.metadata === void 0 ? {} : { metadataHead: context.metadata.head },
|
|
60
|
+
theme: theme.name,
|
|
61
|
+
title: context.metadata?.title ?? (directoryPath ? `${directoryPath} · ${title}` : title),
|
|
62
|
+
path: directoryPath,
|
|
63
|
+
rootHref: context.hrefForDirectory(""),
|
|
64
|
+
parentHref: context.directory.parent === null ? null : context.hrefForDirectory(context.directory.parent.relativePath),
|
|
65
|
+
rootDataHref: dataForDirectory(""),
|
|
66
|
+
parentDataHref: context.directory.parent === null ? null : dataForDirectory(context.directory.parent.relativePath),
|
|
67
|
+
dataHref: context.pageDataHref?.(theme.name, directoryPath, context.listing?.page.number ?? 1) ?? context.assetHref(pageAsset(theme.name, directoryPath, context.listing?.page.number ?? 1)),
|
|
68
|
+
manifestHref: context.listing?.manifestHref ?? null,
|
|
69
|
+
...context.listing ? { listingRevision: context.listing.revision } : {},
|
|
70
|
+
updatesHref: context.updatesHref ?? null,
|
|
71
|
+
total: context.listing?.page.total ?? entries.length,
|
|
72
|
+
entries,
|
|
73
|
+
sort: context.sort,
|
|
74
|
+
page: context.listing?.page ?? null,
|
|
75
|
+
breadcrumbs: directoryPath.split("/").filter(Boolean).map((name, index) => ({
|
|
76
|
+
name,
|
|
77
|
+
dataHref: dataForDirectory(directoryPath.split("/").slice(0, index + 1).join("/")),
|
|
78
|
+
href: context.hrefForDirectory(directoryPath.split("/").slice(0, index + 1).join("/"))
|
|
79
|
+
}))
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region src/icons.ts
|
|
84
|
+
const paths$1 = {
|
|
85
|
+
all: "<rect x=\"3\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"14\" y=\"3\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"3\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/><rect x=\"14\" y=\"14\" width=\"7\" height=\"7\" rx=\"1\"/>",
|
|
86
|
+
folder: "<path d=\"M3 7V5a1 1 0 0 1 1-1h5l2 3h9a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1Z\"/><path d=\"M3 9h18\"/>",
|
|
87
|
+
image: "<rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"1.5\"/><circle cx=\"8\" cy=\"9\" r=\"1.5\"/><path d=\"m3 17 5-5 4 4 4-6 5 7\"/>",
|
|
88
|
+
audio: "<path d=\"M9 18V5l11-2v13M9 8l11-2\"/><ellipse cx=\"6\" cy=\"18\" rx=\"3\" ry=\"2.5\"/><ellipse cx=\"17\" cy=\"16\" rx=\"3\" ry=\"2.5\"/>",
|
|
89
|
+
video: "<rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"1.5\"/><path d=\"m10 8 6 4-6 4Z\"/>",
|
|
90
|
+
document: "<path d=\"M5 3h9l5 5v13H5ZM14 3v5h5M8 12h8M8 16h6\"/>",
|
|
91
|
+
other: "<path d=\"m12 3 9 5v9l-9 5-9-5V8ZM3 8l9 5 9-5M12 13v9\"/>",
|
|
92
|
+
link: "<path d=\"m9 15 6-6M8 10l-3 3a4 4 0 0 0 6 6l3-3M10 8l3-3a4 4 0 0 1 6 6l-3 3\"/>",
|
|
93
|
+
back: "<path d=\"m10 5-7 7 7 7M3 12h18\"/>",
|
|
94
|
+
search: "<circle cx=\"10.5\" cy=\"10.5\" r=\"6.5\"/><path d=\"m16 16 5 5\"/>",
|
|
95
|
+
pause: "<path d=\"M8 5v14M16 5v14\"/>",
|
|
96
|
+
play: "<path d=\"m8 4 12 8-12 8Z\"/>",
|
|
97
|
+
controller: "<path d=\"M7 7h10c3 0 4 3 5 9 0 3-2 4-4 1l-2-2H8l-2 2c-2 3-4 2-4-1 1-6 2-9 5-9ZM6 11h4M8 9v4M16 10h.01M19 13h.01\"/>"
|
|
98
|
+
};
|
|
99
|
+
/** Original geometric icons, not copied console artwork. */
|
|
100
|
+
function crosswaveIcon(name) {
|
|
101
|
+
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.45" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths$1[name]}</svg>`;
|
|
102
|
+
}
|
|
103
|
+
/** Default rail definitions. Supply a mapped/filtered copy to change labels, order, or visibility. */
|
|
104
|
+
const defaultCrosswaveCategories = Object.freeze([
|
|
105
|
+
{
|
|
106
|
+
id: "all",
|
|
107
|
+
label: "All files",
|
|
108
|
+
icon: "all",
|
|
109
|
+
match: "all"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "folder",
|
|
113
|
+
label: "Folders",
|
|
114
|
+
icon: "folder",
|
|
115
|
+
match: "folders"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "image",
|
|
119
|
+
label: "Photos",
|
|
120
|
+
icon: "image",
|
|
121
|
+
match: { mimeTypes: ["image/*"] }
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: "audio",
|
|
125
|
+
label: "Music",
|
|
126
|
+
icon: "audio",
|
|
127
|
+
match: { mimeTypes: ["audio/*", "application/vnd.apple.mpegurl"] }
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "video",
|
|
131
|
+
label: "Videos",
|
|
132
|
+
icon: "video",
|
|
133
|
+
match: { mimeTypes: ["video/*"] }
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "document",
|
|
137
|
+
label: "Documents",
|
|
138
|
+
icon: "document",
|
|
139
|
+
match: { mimeTypes: [
|
|
140
|
+
"text/*",
|
|
141
|
+
"application/pdf",
|
|
142
|
+
"application/json",
|
|
143
|
+
"application/*+json",
|
|
144
|
+
"application/xml",
|
|
145
|
+
"application/*+xml",
|
|
146
|
+
"application/msword",
|
|
147
|
+
"application/rtf",
|
|
148
|
+
"application/sql",
|
|
149
|
+
"application/epub+zip",
|
|
150
|
+
"application/vnd.ms-excel",
|
|
151
|
+
"application/vnd.ms-powerpoint",
|
|
152
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
153
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
154
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
155
|
+
] }
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "other",
|
|
159
|
+
label: "Other",
|
|
160
|
+
icon: "other",
|
|
161
|
+
match: "other"
|
|
162
|
+
}
|
|
163
|
+
].map((category) => Object.freeze({
|
|
164
|
+
...category,
|
|
165
|
+
match: typeof category.match === "string" ? category.match : Object.freeze({
|
|
166
|
+
...category.match.extensions ? { extensions: Object.freeze([...category.match.extensions]) } : {},
|
|
167
|
+
...category.match.mimeTypes ? { mimeTypes: Object.freeze([...category.match.mimeTypes]) } : {}
|
|
168
|
+
})
|
|
169
|
+
})));
|
|
170
|
+
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
171
|
+
function strings(value, normalize) {
|
|
172
|
+
if (value === void 0) return void 0;
|
|
173
|
+
if (!Array.isArray(value)) throw new TypeError("Crosswave category rules must be arrays of strings");
|
|
174
|
+
return Object.freeze([...new Set(Array.from(value, (item) => {
|
|
175
|
+
if (typeof item !== "string") throw new TypeError("Crosswave category rules must be arrays of strings");
|
|
176
|
+
return normalize(item);
|
|
177
|
+
}))]);
|
|
178
|
+
}
|
|
179
|
+
function extension(value) {
|
|
180
|
+
const normalized = value.toLowerCase().replace(/^\./, "");
|
|
181
|
+
if (!/^[a-z0-9+_-]+(?:\.[a-z0-9+_-]+)*$/.test(normalized)) throw new TypeError(`Invalid Crosswave extension: ${value}`);
|
|
182
|
+
return normalized;
|
|
183
|
+
}
|
|
184
|
+
function mediaType(value) {
|
|
185
|
+
const normalized = value.toLowerCase();
|
|
186
|
+
if (!/^[a-z0-9!#$&^_.+-]+\/(?:[a-z0-9!#$&^_.+-]+|\*|\*\+[a-z0-9!#$&^_.-]+)$/.test(normalized)) throw new TypeError(`Invalid Crosswave MIME pattern: ${value}`);
|
|
187
|
+
return normalized;
|
|
188
|
+
}
|
|
189
|
+
function match(value) {
|
|
190
|
+
if (value === "all" || value === "folders" || value === "other") return value;
|
|
191
|
+
if (!record(value) || Object.keys(value).some((key) => !["extensions", "mimeTypes"].includes(key))) throw new TypeError("Crosswave match must be all, folders, other, or extension/MIME rules");
|
|
192
|
+
const extensions = strings(value.extensions, extension);
|
|
193
|
+
const mimeTypes = strings(value.mimeTypes, mediaType);
|
|
194
|
+
if (!extensions?.length && !mimeTypes?.length) throw new TypeError("Crosswave category needs at least one matching rule");
|
|
195
|
+
return Object.freeze({
|
|
196
|
+
...extensions ? { extensions } : {},
|
|
197
|
+
...mimeTypes ? { mimeTypes } : {}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function icon(value, rule) {
|
|
201
|
+
if (value === void 0) return crosswaveIcon(rule === "all" ? "all" : rule === "folders" ? "folder" : "other");
|
|
202
|
+
if (typeof value === "string" && isCrosswaveIcon(value)) return crosswaveIcon(value);
|
|
203
|
+
if (record(value) && typeof value.svg === "string" && /^\s*<svg\b/i.test(value.svg) && /<\/svg>\s*$/i.test(value.svg)) return value.svg.replace(/<svg\b/i, "<svg aria-hidden=\"true\"");
|
|
204
|
+
throw new TypeError("Crosswave icon must be a built-in icon name or trusted SVG markup");
|
|
205
|
+
}
|
|
206
|
+
function isCrosswaveIcon(value) {
|
|
207
|
+
return [
|
|
208
|
+
"all",
|
|
209
|
+
"folder",
|
|
210
|
+
"image",
|
|
211
|
+
"audio",
|
|
212
|
+
"video",
|
|
213
|
+
"document",
|
|
214
|
+
"other",
|
|
215
|
+
"link",
|
|
216
|
+
"back",
|
|
217
|
+
"search",
|
|
218
|
+
"pause",
|
|
219
|
+
"play",
|
|
220
|
+
"controller"
|
|
221
|
+
].includes(value);
|
|
222
|
+
}
|
|
223
|
+
function resolveCrosswaveCategories(value) {
|
|
224
|
+
const input = value === void 0 ? defaultCrosswaveCategories : value;
|
|
225
|
+
if (!Array.isArray(input) || !input.length) throw new TypeError("Crosswave categories must be a nonempty array");
|
|
226
|
+
const ids = /* @__PURE__ */ new Set();
|
|
227
|
+
return Object.freeze(Array.from(input).map((category) => {
|
|
228
|
+
if (!record(category) || typeof category.id !== "string" || !/^[a-z][a-z0-9_-]{0,63}$/.test(category.id)) throw new TypeError("Crosswave category id must be a lowercase identifier");
|
|
229
|
+
if (ids.has(category.id)) throw new TypeError(`Duplicate Crosswave category id: ${category.id}`);
|
|
230
|
+
ids.add(category.id);
|
|
231
|
+
if (typeof category.label !== "string" || !category.label.trim()) throw new TypeError("Crosswave category label must be nonempty");
|
|
232
|
+
const rule = match(category.match);
|
|
233
|
+
return Object.freeze({
|
|
234
|
+
id: category.id,
|
|
235
|
+
label: category.label,
|
|
236
|
+
svg: icon(category.icon, rule),
|
|
237
|
+
match: rule
|
|
238
|
+
});
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
241
|
+
function matchesMimeType(pattern, type) {
|
|
242
|
+
if (pattern === type) return true;
|
|
243
|
+
const [group, subtype] = pattern.split("/");
|
|
244
|
+
if (!subtype || !type.startsWith(`${group}/`)) return false;
|
|
245
|
+
return subtype === "*" || subtype.startsWith("*+") && type.endsWith(subtype.slice(1));
|
|
246
|
+
}
|
|
247
|
+
function entryMimeType(entry) {
|
|
248
|
+
if (entry.kind === "directory" || entry.link?.targetKind === "directory") return null;
|
|
249
|
+
return mime.getType(`./${entry.name}`) ?? (entry.link?.targetKind === "file" && !entry.link.unavailable && entry.href ? mime.getType(new URL(entry.href, "https://dirwell.invalid/").pathname) : null);
|
|
250
|
+
}
|
|
251
|
+
function entryCategories(entry, type, categories) {
|
|
252
|
+
const folder = entry.kind === "directory" || entry.link?.targetKind === "directory";
|
|
253
|
+
const filename = entry.name.toLowerCase();
|
|
254
|
+
const specific = categories.filter((category) => typeof category.match !== "string" && !folder && (category.match.extensions?.some((ext) => filename.endsWith(`.${ext}`)) || type && category.match.mimeTypes?.some((pattern) => matchesMimeType(pattern, type))));
|
|
255
|
+
return categories.filter((category) => category.match === "all" || folder && category.match === "folders" || !folder && category.match === "other" && !specific.length || specific.includes(category));
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
258
|
+
//#region ../shared-theme/src/navigation.ts
|
|
259
|
+
/** Folder snapshots resolve relative URLs before History changes the document URL. */
|
|
260
|
+
async function fetchThemePage(dataHref, pageHref, theme, signal, fresh = false, attempt = 0) {
|
|
261
|
+
const source = new URL(dataHref, document.baseURI);
|
|
262
|
+
if (source.origin !== location.origin) throw new ListingDataError("Folder leaves the theme origin");
|
|
263
|
+
const response = await fetch(source, {
|
|
264
|
+
signal,
|
|
265
|
+
cache: fresh ? "no-store" : "default"
|
|
266
|
+
});
|
|
267
|
+
if (!response.ok) throw new ListingDataError("Unavailable folder");
|
|
268
|
+
const value = await response.json();
|
|
269
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new ListingDataError("Invalid folder snapshot");
|
|
270
|
+
const data = value;
|
|
271
|
+
if (data.version !== 1 || data.theme !== theme || typeof data.title !== "string" || typeof data.path !== "string" || !Number.isSafeInteger(data.total) || Number(data.total) < 0 || !Array.isArray(data.breadcrumbs)) throw new ListingDataError("Unsupported folder snapshot");
|
|
272
|
+
if (data.metadataHead !== void 0 && typeof data.metadataHead !== "string") throw new ListingDataError("Invalid folder metadata");
|
|
273
|
+
const base = document.querySelector("base")?.href ?? pageHref;
|
|
274
|
+
const link = (raw) => {
|
|
275
|
+
if (raw === null) return null;
|
|
276
|
+
if (typeof raw !== "string") throw new ListingDataError("Invalid folder link");
|
|
277
|
+
const url = new URL(raw, base);
|
|
278
|
+
if (!["http:", "https:"].includes(url.protocol) || url.origin !== location.origin) throw new ListingDataError("Unsupported folder link");
|
|
279
|
+
return url.href;
|
|
280
|
+
};
|
|
281
|
+
const entries = parseListingEntries(data.entries, base).map((entry) => ({
|
|
282
|
+
...entry,
|
|
283
|
+
...entry.presentation ? { presentation: {
|
|
284
|
+
...entry.presentation,
|
|
285
|
+
folderDataHref: link(entry.presentation.folderDataHref ?? null)
|
|
286
|
+
} } : {}
|
|
287
|
+
}));
|
|
288
|
+
if (entries.length > Number(data.total)) throw new ListingDataError("Invalid folder count");
|
|
289
|
+
const result = {
|
|
290
|
+
...data,
|
|
291
|
+
updatesHref: link(data.updatesHref ?? null),
|
|
292
|
+
entries,
|
|
293
|
+
dataHref: source.href,
|
|
294
|
+
manifestHref: link(data.manifestHref),
|
|
295
|
+
rootHref: link(data.rootHref),
|
|
296
|
+
rootDataHref: link(data.rootDataHref),
|
|
297
|
+
parentHref: link(data.parentHref),
|
|
298
|
+
parentDataHref: link(data.parentDataHref),
|
|
299
|
+
breadcrumbs: data.breadcrumbs.map((raw) => {
|
|
300
|
+
if (typeof raw !== "object" || raw === null || !("name" in raw) || !("href" in raw) || !("dataHref" in raw) || typeof raw.name !== "string") throw new ListingDataError("Invalid breadcrumb");
|
|
301
|
+
return {
|
|
302
|
+
name: raw.name,
|
|
303
|
+
href: link(raw.href),
|
|
304
|
+
dataHref: link(raw.dataHref)
|
|
305
|
+
};
|
|
306
|
+
})
|
|
307
|
+
};
|
|
308
|
+
if (fresh && result.manifestHref) {
|
|
309
|
+
const loaded = await loadListing(result.manifestHref, {
|
|
310
|
+
signal,
|
|
311
|
+
cache: "no-store"
|
|
312
|
+
});
|
|
313
|
+
if (loaded.manifest.total !== data.total || data.listingRevision !== void 0 && loaded.manifest.revision !== data.listingRevision) {
|
|
314
|
+
if (attempt < 2) return fetchThemePage(dataHref, pageHref, theme, signal, true, attempt + 1);
|
|
315
|
+
throw new ListingDataError("Folder changed while refreshing");
|
|
316
|
+
}
|
|
317
|
+
result.entries = [...loaded.entries];
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
const hotModels = /* @__PURE__ */ new WeakMap();
|
|
322
|
+
function restoreThemePage(initial, hot) {
|
|
323
|
+
if (!hot || typeof document === "undefined") return initial;
|
|
324
|
+
const root = document.querySelector("[data-dirwell-theme]");
|
|
325
|
+
if (!root) return initial;
|
|
326
|
+
const snapshot = hotModels.get(root) ?? initial;
|
|
327
|
+
hotModels.set(root, snapshot);
|
|
328
|
+
return snapshot;
|
|
329
|
+
}
|
|
330
|
+
/** Copy only inert metadata elements; never insert snapshot HTML or event attributes. */
|
|
331
|
+
function applyThemeMetadata(page, pageHref) {
|
|
332
|
+
const root = document.querySelector("[data-dirwell-theme]");
|
|
333
|
+
if (root && hotModels.has(root)) hotModels.set(root, page);
|
|
334
|
+
document.title = typeof page.title === "string" ? page.title : "";
|
|
335
|
+
const template = document.createElement("template");
|
|
336
|
+
template.innerHTML = typeof page.metadataHead === "string" ? page.metadataHead : "";
|
|
337
|
+
document.head.querySelectorAll("[data-dirwell-metadata]").forEach((element) => element.remove());
|
|
338
|
+
const base = document.querySelector("base")?.href ?? pageHref;
|
|
339
|
+
const url = (value) => {
|
|
340
|
+
try {
|
|
341
|
+
const target = new URL(value, base);
|
|
342
|
+
return ["http:", "https:"].includes(target.protocol) ? target.href : null;
|
|
343
|
+
} catch {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
for (const element of template.content.children) {
|
|
348
|
+
if (!element.hasAttribute("data-dirwell-metadata")) continue;
|
|
349
|
+
let next;
|
|
350
|
+
if (element.tagName === "META") {
|
|
351
|
+
const attribute = element.hasAttribute("property") ? "property" : "name";
|
|
352
|
+
const key = element.getAttribute(attribute) ?? "";
|
|
353
|
+
if (!/^(?:description|og:[a-z_:]+|twitter:[a-z_:]+)$/i.test(key)) continue;
|
|
354
|
+
let content = element.getAttribute("content") ?? "";
|
|
355
|
+
if ([
|
|
356
|
+
"og:image",
|
|
357
|
+
"og:url",
|
|
358
|
+
"twitter:image"
|
|
359
|
+
].includes(key)) {
|
|
360
|
+
const resolved = url(content);
|
|
361
|
+
if (resolved === null) continue;
|
|
362
|
+
content = resolved;
|
|
363
|
+
}
|
|
364
|
+
next = document.createElement("meta");
|
|
365
|
+
next.setAttribute(attribute, key);
|
|
366
|
+
next.setAttribute("content", content);
|
|
367
|
+
} else if (element.tagName === "LINK" && element.getAttribute("rel") === "canonical") {
|
|
368
|
+
const href = url(element.getAttribute("href") ?? "");
|
|
369
|
+
if (href === null) continue;
|
|
370
|
+
next = document.createElement("link");
|
|
371
|
+
next.setAttribute("rel", "canonical");
|
|
372
|
+
next.setAttribute("href", href);
|
|
373
|
+
} else continue;
|
|
374
|
+
next.setAttribute("data-dirwell-metadata", "");
|
|
375
|
+
document.head.append(next);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region ../shared-theme/src/icons.ts
|
|
380
|
+
const paths = {
|
|
381
|
+
directory: "<path d=\"M3 7V5h6l2 3h10v12H3Z\"/><path d=\"M3 10h18\"/>",
|
|
382
|
+
file: "<path d=\"M5 3h9l5 5v13H5ZM14 3v5h5M8 12h8M8 16h6\"/>",
|
|
383
|
+
link: "<path d=\"m9 15 6-6M8 10l-3 3a4 4 0 0 0 6 6l3-3M10 8l3-3a4 4 0 0 1 6 6l-3 3\"/>"
|
|
384
|
+
};
|
|
385
|
+
/** Fixed authored geometry; source names and paths never enter SVG markup. */
|
|
386
|
+
function entryIcon(kind) {
|
|
387
|
+
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.45" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths[kind]}</svg>`;
|
|
388
|
+
}
|
|
389
|
+
//#endregion
|
|
390
|
+
//#region ../shared-theme/src/records.ts
|
|
391
|
+
const directoryLike = (entry) => entry.kind === "directory" || entry.link?.targetKind === "directory";
|
|
392
|
+
function size(bytes) {
|
|
393
|
+
const units = [
|
|
394
|
+
"B",
|
|
395
|
+
"KiB",
|
|
396
|
+
"MiB",
|
|
397
|
+
"GiB",
|
|
398
|
+
"TiB"
|
|
399
|
+
];
|
|
400
|
+
let value = bytes, index = 0;
|
|
401
|
+
while (value >= 1024 && index < 4) {
|
|
402
|
+
value /= 1024;
|
|
403
|
+
index++;
|
|
404
|
+
}
|
|
405
|
+
return `${value.toFixed(index === 0 || value >= 100 ? 0 : 1)} ${units[index]}`;
|
|
406
|
+
}
|
|
407
|
+
function modified(value, local = false) {
|
|
408
|
+
const date = new Date(value);
|
|
409
|
+
if (Number.isNaN(date.getTime())) return "Unknown";
|
|
410
|
+
return local ? date.toLocaleString(void 0, {
|
|
411
|
+
dateStyle: "short",
|
|
412
|
+
timeStyle: "short"
|
|
413
|
+
}) : `${date.toISOString().slice(0, 16).replace("T", " ")} UTC`;
|
|
414
|
+
}
|
|
415
|
+
//#endregion
|
|
416
|
+
//#region .generated/wave.mjs
|
|
417
|
+
function createWave(canvas, colors, root, moving, onReady) {
|
|
418
|
+
let gl;
|
|
419
|
+
try {
|
|
420
|
+
gl = canvas.getContext("webgl", {
|
|
421
|
+
alpha: false,
|
|
422
|
+
antialias: false,
|
|
423
|
+
powerPreference: "low-power"
|
|
424
|
+
});
|
|
425
|
+
} catch {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
if (!gl) return null;
|
|
429
|
+
const vertex = "attribute vec2 p;void main(){gl_Position=vec4(p,0.,1.);}";
|
|
430
|
+
const fragment = `precision highp float;uniform vec2 resolution;uniform float time;uniform vec3 tint;
|
|
431
|
+
void main(){vec2 uv=gl_FragCoord.xy/resolution;float x=uv.x;float y=uv.y;float t=time;
|
|
432
|
+
vec3 ground=mix(tint*.22,tint,clamp(y*.85+x*.1,0.,1.));ground+=vec3(.035)*pow(max(0.,1.-distance(uv,vec2(.2,.9))),3.);
|
|
433
|
+
float light=0.;for(int i=0;i<3;i++){float k=float(i);float center=.40+.07*sin(x*4.2+t*.17+k*.8)+.035*cos(x*7.1-t*.11+k*1.4)+k*.024;
|
|
434
|
+
float breadth=.012+.037*(.5+.5*sin(x*2.7+t*.09+k));float d=y-center;float sheet=exp(-pow(d/breadth,2.));float edge=exp(-abs(d-breadth*.65)*430.);
|
|
435
|
+
float fold=exp(-abs(d+breadth*.35)*170.);float taper=pow(sin(clamp(x,0.,1.)*3.14159),.6);light+=(sheet*.07+edge*.23+fold*.09)*taper;}
|
|
436
|
+
ground+=mix(vec3(.6,.8,1.),vec3(1.),.75)*min(light,.12);gl_FragColor=vec4(ground,1.);}`;
|
|
437
|
+
const shader = (type, source) => {
|
|
438
|
+
const s = gl.createShader(type);
|
|
439
|
+
if (!s) return null;
|
|
440
|
+
gl.shaderSource(s, source);
|
|
441
|
+
gl.compileShader(s);
|
|
442
|
+
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
|
|
443
|
+
gl.deleteShader(s);
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
return s;
|
|
447
|
+
};
|
|
448
|
+
const vs = shader(gl.VERTEX_SHADER, vertex), fs = shader(gl.FRAGMENT_SHADER, fragment);
|
|
449
|
+
if (!vs || !fs) {
|
|
450
|
+
if (vs) gl.deleteShader(vs);
|
|
451
|
+
if (fs) gl.deleteShader(fs);
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
const program = gl.createProgram();
|
|
455
|
+
if (!program) return null;
|
|
456
|
+
gl.attachShader(program, vs);
|
|
457
|
+
gl.attachShader(program, fs);
|
|
458
|
+
gl.linkProgram(program);
|
|
459
|
+
gl.deleteShader(vs);
|
|
460
|
+
gl.deleteShader(fs);
|
|
461
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
462
|
+
gl.deleteProgram(program);
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
const buffer = gl.createBuffer();
|
|
466
|
+
if (!buffer) {
|
|
467
|
+
gl.deleteProgram(program);
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
471
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
|
472
|
+
-1,
|
|
473
|
+
-1,
|
|
474
|
+
1,
|
|
475
|
+
-1,
|
|
476
|
+
-1,
|
|
477
|
+
1,
|
|
478
|
+
1,
|
|
479
|
+
1
|
|
480
|
+
]), gl.STATIC_DRAW);
|
|
481
|
+
gl.useProgram(program);
|
|
482
|
+
const p = gl.getAttribLocation(program, "p");
|
|
483
|
+
gl.enableVertexAttribArray(p);
|
|
484
|
+
gl.vertexAttribPointer(p, 2, gl.FLOAT, false, 0, 0);
|
|
485
|
+
const r = gl.getUniformLocation(program, "resolution"), t = gl.getUniformLocation(program, "time"), c = gl.getUniformLocation(program, "tint");
|
|
486
|
+
let frame = 0, last = 0, lost = false, frozenTime = 17;
|
|
487
|
+
const draw = (now) => {
|
|
488
|
+
if (lost) return;
|
|
489
|
+
const ratio = Math.min(devicePixelRatio || 1, 1.5, 1600 / innerWidth, 1e3 / innerHeight), width = Math.round(innerWidth * ratio), height = Math.round(innerHeight * ratio);
|
|
490
|
+
if (canvas.width !== width || canvas.height !== height) {
|
|
491
|
+
canvas.width = width;
|
|
492
|
+
canvas.height = height;
|
|
493
|
+
gl.viewport(0, 0, width, height);
|
|
494
|
+
}
|
|
495
|
+
if (moving()) frozenTime = Date.now() % 864e5 / 1e3;
|
|
496
|
+
gl.uniform2f(r, width, height);
|
|
497
|
+
gl.uniform1f(t, frozenTime);
|
|
498
|
+
gl.uniform3fv(c, colors[root.dataset.cwColor] ?? colors.azure);
|
|
499
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
500
|
+
onReady(true);
|
|
501
|
+
last = now;
|
|
502
|
+
};
|
|
503
|
+
const tick = (now) => {
|
|
504
|
+
frame = 0;
|
|
505
|
+
if (!moving() || lost) return;
|
|
506
|
+
if (now - last >= 33) draw(now);
|
|
507
|
+
frame = requestAnimationFrame(tick);
|
|
508
|
+
};
|
|
509
|
+
const stop = () => {
|
|
510
|
+
cancelAnimationFrame(frame);
|
|
511
|
+
frame = 0;
|
|
512
|
+
};
|
|
513
|
+
const sync = () => {
|
|
514
|
+
stop();
|
|
515
|
+
if (lost) return;
|
|
516
|
+
draw(0);
|
|
517
|
+
if (moving()) frame = requestAnimationFrame(tick);
|
|
518
|
+
};
|
|
519
|
+
const contextLost = (event) => {
|
|
520
|
+
event.preventDefault();
|
|
521
|
+
lost = true;
|
|
522
|
+
stop();
|
|
523
|
+
onReady(false);
|
|
524
|
+
};
|
|
525
|
+
canvas.addEventListener("webglcontextlost", contextLost);
|
|
526
|
+
const contextRestored = () => {
|
|
527
|
+
onReady(false);
|
|
528
|
+
lost = true;
|
|
529
|
+
};
|
|
530
|
+
canvas.addEventListener("webglcontextrestored", contextRestored);
|
|
531
|
+
const resized = () => draw(0);
|
|
532
|
+
addEventListener("resize", resized);
|
|
533
|
+
return {
|
|
534
|
+
dispose() {
|
|
535
|
+
stop();
|
|
536
|
+
removeEventListener("resize", resized);
|
|
537
|
+
canvas.removeEventListener("webglcontextlost", contextLost);
|
|
538
|
+
canvas.removeEventListener("webglcontextrestored", contextRestored);
|
|
539
|
+
gl.deleteBuffer(buffer);
|
|
540
|
+
gl.deleteProgram(program);
|
|
541
|
+
},
|
|
542
|
+
draw,
|
|
543
|
+
sync,
|
|
544
|
+
stop,
|
|
545
|
+
get available() {
|
|
546
|
+
return !lost;
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
//#endregion
|
|
551
|
+
//#region .generated/App-server.mjs
|
|
552
|
+
var App_server_default = {
|
|
553
|
+
__name: "Crosswave",
|
|
554
|
+
__ssrInlineRender: true,
|
|
555
|
+
props: { model: {
|
|
556
|
+
type: Object,
|
|
557
|
+
required: true
|
|
558
|
+
} },
|
|
559
|
+
setup(__props) {
|
|
560
|
+
const props = __props, model = shallowRef(restoreThemePage(props.model, Boolean(import.meta.hot))), entries = shallowRef(model.value.entries);
|
|
561
|
+
const query = ref(""), category = ref("all"), selected = ref(null), ready = ref(false), busy = ref(false), error = ref(""), color = ref(props.model.color), paused = ref(!props.model.backgroundMotion), clock = ref(""), pad = ref(false), waveReady = ref(false), pendingUpdates = ref(null), canvas = ref(), search = ref();
|
|
562
|
+
const complete = computed(() => entries.value.length === model.value.total), membership = (entry) => Array.isArray(entry.presentation?.categories) ? entry.presentation.categories : [];
|
|
563
|
+
const rows = computed(() => entries.value.filter((entry) => (category.value === "all" || membership(entry).includes(category.value)) && `${entry.name} ${entry.link?.target ?? ""}`.toLocaleLowerCase().includes(query.value.trim().toLocaleLowerCase()))), active = computed(() => rows.value.find((entry) => entry.id === selected.value) ?? rows.value[0]), heading = computed(() => model.value.categories.find((item) => item.id === category.value)?.label ?? "All files");
|
|
564
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
565
|
+
let wave, gamepad, abort, navigation = 0, timer, lastPad = 0, pressed = /* @__PURE__ */ new Set(), disposed = false, padIdentity = null, motionQuery;
|
|
566
|
+
function safeHref(entry) {
|
|
567
|
+
return typeof location !== "undefined" && location.protocol === "file:" && directoryLike(entry) && !entry.exitsExplorer && entry.href?.endsWith("/") ? entry.href + "index.html" : entry.href;
|
|
568
|
+
}
|
|
569
|
+
async function loadComplete() {
|
|
570
|
+
if (complete.value || !model.value.manifestHref || location.protocol === "file:") return;
|
|
571
|
+
const local = abort, token = navigation;
|
|
572
|
+
busy.value = true;
|
|
573
|
+
try {
|
|
574
|
+
const data = await loadListing(new URL(model.value.manifestHref, document.baseURI).href, {
|
|
575
|
+
signal: local.signal,
|
|
576
|
+
cache: model.value.updatesHref ? "no-store" : "default"
|
|
577
|
+
});
|
|
578
|
+
if (token === navigation && !local.signal.aborted && !disposed) entries.value = data.entries;
|
|
579
|
+
} catch (cause) {
|
|
580
|
+
if (token === navigation && !local.signal.aborted) error.value = "Could not load complete directory data. Native page links remain available.";
|
|
581
|
+
} finally {
|
|
582
|
+
if (token === navigation) busy.value = false;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
async function openFolder(dataHref, url, push, refresh = false) {
|
|
586
|
+
const token = ++navigation;
|
|
587
|
+
abort?.abort();
|
|
588
|
+
const local = abort = new AbortController();
|
|
589
|
+
busy.value = true;
|
|
590
|
+
error.value = "";
|
|
591
|
+
try {
|
|
592
|
+
const next = await fetchThemePage(dataHref, url, "crosswave", local.signal, refresh);
|
|
593
|
+
let nextEntries = next.entries;
|
|
594
|
+
if (refresh && next.manifestHref && nextEntries.length !== next.total) nextEntries = (await loadListing(next.manifestHref, {
|
|
595
|
+
signal: local.signal,
|
|
596
|
+
cache: "no-store"
|
|
597
|
+
})).entries;
|
|
598
|
+
if (token !== navigation || local.signal.aborted || disposed) return;
|
|
599
|
+
model.value = next;
|
|
600
|
+
entries.value = nextEntries;
|
|
601
|
+
if (!refresh) {
|
|
602
|
+
query.value = "";
|
|
603
|
+
selected.value = null;
|
|
604
|
+
category.value = "all";
|
|
605
|
+
}
|
|
606
|
+
if (push) history.pushState({ dataHref: next.dataHref }, "", url);
|
|
607
|
+
applyThemeMetadata(next, url);
|
|
608
|
+
await loadComplete();
|
|
609
|
+
} catch (cause) {
|
|
610
|
+
if (token === navigation && !local.signal.aborted) error.value = "Could not open the folder. Its native link remains available.";
|
|
611
|
+
} finally {
|
|
612
|
+
if (token === navigation) busy.value = false;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
async function move(index) {
|
|
616
|
+
const entry = rows.value[Math.max(0, Math.min(rows.value.length - 1, index))];
|
|
617
|
+
if (!entry) return;
|
|
618
|
+
selected.value = entry.id;
|
|
619
|
+
await nextTick();
|
|
620
|
+
const node = nodes.get(entry.id);
|
|
621
|
+
node?.querySelector("a")?.focus({ preventScroll: true });
|
|
622
|
+
node?.scrollIntoView({ block: "nearest" });
|
|
623
|
+
}
|
|
624
|
+
function stepCategory(delta) {
|
|
625
|
+
const items = model.value.categories, index = items.findIndex((item) => item.id === category.value);
|
|
626
|
+
category.value = items[(index + delta + items.length) % items.length].id;
|
|
627
|
+
selected.value = null;
|
|
628
|
+
}
|
|
629
|
+
function confirm() {
|
|
630
|
+
if (!active.value || busy.value) return;
|
|
631
|
+
(nodes.get(active.value.id)?.querySelector("a"))?.click();
|
|
632
|
+
}
|
|
633
|
+
function parent() {
|
|
634
|
+
if (model.value.parentHref) {
|
|
635
|
+
if (location.protocol === "file:") location.href = model.value.parentHref;
|
|
636
|
+
else openFolder(model.value.parentDataHref, new URL(model.value.parentHref, document.baseURI).href, true);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function keyboard(event) {
|
|
640
|
+
if (event.isComposing || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
641
|
+
if (/INPUT|TEXTAREA|SELECT/.test(event.target?.tagName ?? "")) {
|
|
642
|
+
if (event.key === "Escape") query.value = "";
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const index = rows.value.findIndex((entry) => entry.id === (selected.value ?? active.value?.id));
|
|
646
|
+
if (event.key === "/") {
|
|
647
|
+
event.preventDefault();
|
|
648
|
+
search.value?.focus();
|
|
649
|
+
} else if ([
|
|
650
|
+
"ArrowDown",
|
|
651
|
+
"ArrowUp",
|
|
652
|
+
"Home",
|
|
653
|
+
"End"
|
|
654
|
+
].includes(event.key)) {
|
|
655
|
+
event.preventDefault();
|
|
656
|
+
move(event.key === "Home" ? 0 : event.key === "End" ? rows.value.length - 1 : index + (event.key === "ArrowDown" ? 1 : -1));
|
|
657
|
+
} else if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
658
|
+
event.preventDefault();
|
|
659
|
+
stepCategory(event.key === "ArrowRight" ? 1 : -1);
|
|
660
|
+
} else if (event.key === "Enter") {
|
|
661
|
+
event.preventDefault();
|
|
662
|
+
confirm();
|
|
663
|
+
} else if (event.key === "Backspace") {
|
|
664
|
+
event.preventDefault();
|
|
665
|
+
parent();
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function controller(value) {
|
|
669
|
+
pad.value = !!value;
|
|
670
|
+
if (!value) {
|
|
671
|
+
pressed.clear();
|
|
672
|
+
padIdentity = null;
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const buttons = value.buttons ?? [], next = new Set(buttons.map((button, index) => button.pressed || button.value > .5 ? index : -1).filter((index) => index >= 0)), time = performance.now(), axis = value.axes ?? [];
|
|
676
|
+
const identity = `${value.index}:${value.id}`;
|
|
677
|
+
if (padIdentity !== identity) {
|
|
678
|
+
padIdentity = identity;
|
|
679
|
+
pressed = next;
|
|
680
|
+
lastPad = time;
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (document.hidden || disposed || busy.value || !complete.value || /INPUT|TEXTAREA|SELECT/.test(document.activeElement?.tagName ?? "")) {
|
|
684
|
+
pressed = next;
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (time - lastPad > 180) {
|
|
688
|
+
const index = rows.value.findIndex((entry) => entry.id === (selected.value ?? active.value?.id));
|
|
689
|
+
if (next.has(13) || (axis[1] ?? 0) > .35) {
|
|
690
|
+
move(index + 1);
|
|
691
|
+
lastPad = time;
|
|
692
|
+
} else if (next.has(12) || (axis[1] ?? 0) < -.35) {
|
|
693
|
+
move(index - 1);
|
|
694
|
+
lastPad = time;
|
|
695
|
+
} else if (next.has(15) || (axis[0] ?? 0) > .35) {
|
|
696
|
+
stepCategory(1);
|
|
697
|
+
lastPad = time;
|
|
698
|
+
} else if (next.has(14) || (axis[0] ?? 0) < -.35) {
|
|
699
|
+
stepCategory(-1);
|
|
700
|
+
lastPad = time;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (next.has(0) && !pressed.has(0)) confirm();
|
|
704
|
+
if (next.has(1) && !pressed.has(1)) parent();
|
|
705
|
+
pressed = next;
|
|
706
|
+
}
|
|
707
|
+
const colors = {
|
|
708
|
+
azure: [
|
|
709
|
+
.17,
|
|
710
|
+
.31,
|
|
711
|
+
.52
|
|
712
|
+
],
|
|
713
|
+
violet: [
|
|
714
|
+
.36,
|
|
715
|
+
.25,
|
|
716
|
+
.49
|
|
717
|
+
],
|
|
718
|
+
amber: [
|
|
719
|
+
.44,
|
|
720
|
+
.3,
|
|
721
|
+
.1
|
|
722
|
+
],
|
|
723
|
+
rose: [
|
|
724
|
+
.44,
|
|
725
|
+
.25,
|
|
726
|
+
.33
|
|
727
|
+
],
|
|
728
|
+
jade: [
|
|
729
|
+
.13,
|
|
730
|
+
.37,
|
|
731
|
+
.32
|
|
732
|
+
],
|
|
733
|
+
graphite: [
|
|
734
|
+
.26,
|
|
735
|
+
.3,
|
|
736
|
+
.38
|
|
737
|
+
]
|
|
738
|
+
};
|
|
739
|
+
const onPop = (event) => event.state?.dataHref ? openFolder(event.state.dataHref, location.href, false) : location.reload();
|
|
740
|
+
const suspend = () => {
|
|
741
|
+
gamepad?.pause();
|
|
742
|
+
controller(null);
|
|
743
|
+
wave?.stop();
|
|
744
|
+
};
|
|
745
|
+
const syncMotion = () => wave?.sync();
|
|
746
|
+
const visibility = () => {
|
|
747
|
+
if (document.hidden) {
|
|
748
|
+
gamepad?.pause();
|
|
749
|
+
controller(null);
|
|
750
|
+
wave?.stop();
|
|
751
|
+
} else {
|
|
752
|
+
gamepad?.resume();
|
|
753
|
+
wave?.sync();
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
onMounted(() => {
|
|
757
|
+
ready.value = true;
|
|
758
|
+
document.body.setAttribute("data-cw-enhanced", "");
|
|
759
|
+
abort = new AbortController();
|
|
760
|
+
history.replaceState({ dataHref: new URL(model.value.dataHref, document.baseURI).href }, "");
|
|
761
|
+
try {
|
|
762
|
+
const stored = localStorage.getItem("dirwell-crosswave-color");
|
|
763
|
+
if (Object.hasOwn(colors, stored)) color.value = stored;
|
|
764
|
+
} catch {}
|
|
765
|
+
wave = createWave(canvas.value, colors, document.documentElement, () => !paused.value && !document.hidden && !matchMedia("(prefers-reduced-motion: reduce)").matches, (value) => waveReady.value = value);
|
|
766
|
+
wave?.sync();
|
|
767
|
+
if (model.value.gamepad && typeof navigator.getGamepads === "function") {
|
|
768
|
+
const safeNavigator = { getGamepads() {
|
|
769
|
+
try {
|
|
770
|
+
const pads = navigator.getGamepads();
|
|
771
|
+
if (gamepad) {
|
|
772
|
+
const connected = Array.from(pads).filter((value) => value?.connected);
|
|
773
|
+
gamepad.gamepads.value = gamepad.gamepads.value.filter((value) => connected.some((current) => current.index === value.index));
|
|
774
|
+
for (const value of connected) if (!gamepad.gamepads.value.some((current) => current.index === value.index)) gamepad.gamepads.value.push(value);
|
|
775
|
+
}
|
|
776
|
+
return pads;
|
|
777
|
+
} catch {
|
|
778
|
+
if (gamepad) gamepad.gamepads.value = [];
|
|
779
|
+
controller(null);
|
|
780
|
+
return [];
|
|
781
|
+
}
|
|
782
|
+
} };
|
|
783
|
+
gamepad = useGamepad({ navigator: safeNavigator });
|
|
784
|
+
safeNavigator.getGamepads();
|
|
785
|
+
gamepad.resume();
|
|
786
|
+
watchEffect(() => controller(gamepad.gamepads.value.find((value) => value.connected && value.mapping === "standard") ?? null), { flush: "sync" });
|
|
787
|
+
}
|
|
788
|
+
const updateClock = () => clock.value = (/* @__PURE__ */ new Date()).toLocaleTimeString([], {
|
|
789
|
+
hour: "2-digit",
|
|
790
|
+
minute: "2-digit"
|
|
791
|
+
});
|
|
792
|
+
updateClock();
|
|
793
|
+
timer = setInterval(updateClock, 1e3);
|
|
794
|
+
window.addEventListener("keydown", keyboard);
|
|
795
|
+
window.addEventListener("popstate", onPop);
|
|
796
|
+
document.addEventListener("visibilitychange", visibility);
|
|
797
|
+
window.addEventListener("pagehide", suspend);
|
|
798
|
+
window.addEventListener("pageshow", visibility);
|
|
799
|
+
motionQuery = matchMedia("(prefers-reduced-motion: reduce)");
|
|
800
|
+
motionQuery.addEventListener("change", syncMotion);
|
|
801
|
+
loadComplete();
|
|
802
|
+
});
|
|
803
|
+
watch(() => ready.value && model.value.updatesHref ? new URL(model.value.updatesHref, document.baseURI).href : null, (href, _old, onCleanup) => {
|
|
804
|
+
if (!href || typeof EventSource === "undefined") return;
|
|
805
|
+
const updates = new EventSource(href);
|
|
806
|
+
updates.onmessage = () => pendingUpdates.value = href;
|
|
807
|
+
updates.onerror = () => error.value = "Live updates disconnected. Refresh to retry.";
|
|
808
|
+
updates.onopen = () => {
|
|
809
|
+
if (error.value.startsWith("Live updates")) error.value = "";
|
|
810
|
+
};
|
|
811
|
+
onCleanup(() => updates.close());
|
|
812
|
+
});
|
|
813
|
+
watchEffect(() => {
|
|
814
|
+
if (ready.value && !busy.value && pendingUpdates.value) {
|
|
815
|
+
const href = pendingUpdates.value;
|
|
816
|
+
pendingUpdates.value = null;
|
|
817
|
+
if (href === new URL(model.value.updatesHref, document.baseURI).href && !disposed) openFolder(model.value.dataHref, location.href, false, true);
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
watchEffect(() => {
|
|
821
|
+
if (ready.value) {
|
|
822
|
+
document.documentElement.dataset.cwColor = color.value;
|
|
823
|
+
document.documentElement.dataset.cwMotion = String(!paused.value);
|
|
824
|
+
try {
|
|
825
|
+
localStorage.setItem("dirwell-crosswave-color", color.value);
|
|
826
|
+
} catch {}
|
|
827
|
+
wave?.sync();
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
onBeforeUnmount(() => {
|
|
831
|
+
disposed = true;
|
|
832
|
+
document.body.removeAttribute("data-cw-enhanced");
|
|
833
|
+
abort?.abort();
|
|
834
|
+
gamepad?.pause();
|
|
835
|
+
wave?.dispose();
|
|
836
|
+
clearInterval(timer);
|
|
837
|
+
window.removeEventListener("keydown", keyboard);
|
|
838
|
+
window.removeEventListener("popstate", onPop);
|
|
839
|
+
document.removeEventListener("visibilitychange", visibility);
|
|
840
|
+
window.removeEventListener("pagehide", suspend);
|
|
841
|
+
window.removeEventListener("pageshow", visibility);
|
|
842
|
+
motionQuery?.removeEventListener("change", syncMotion);
|
|
843
|
+
});
|
|
844
|
+
return (_ctx, _push, _parent, _attrs) => {
|
|
845
|
+
_push(`<!--[--><div class="cw-background"${ssrRenderAttr("data-ready", waveReady.value ? true : void 0)}><canvas aria-hidden="true"></canvas><svg class="cw-fallback-wave" aria-hidden="true" viewBox="0 0 1200 600"><path d="M-100 400 Q400 150 1300 340"></path></svg></div><header class="cw-top"><div class="cw-brand">`);
|
|
846
|
+
ssrRenderSlot(_ctx.$slots, "header", { model: model.value }, () => {
|
|
847
|
+
_push(`<a${ssrRenderAttr("href", model.value.rootHref)}>${ssrInterpolate(model.value.title)}</a>`);
|
|
848
|
+
}, _push, _parent);
|
|
849
|
+
_push(`</div><nav class="cw-location" aria-label="Breadcrumb"><a${ssrRenderAttr("href", model.value.rootHref)}>Home</a><!--[-->`);
|
|
850
|
+
ssrRenderList(model.value.breadcrumbs, (crumb) => {
|
|
851
|
+
_push(`<!--[--> / <a${ssrRenderAttr("href", crumb.href)}>${ssrInterpolate(crumb.name)}</a><!--]-->`);
|
|
852
|
+
});
|
|
853
|
+
_push(`<!--]--></nav>`);
|
|
854
|
+
if (ready.value) _push(`<div class="cw-clock">${ssrInterpolate(clock.value)}</div>`);
|
|
855
|
+
else _push(`<!---->`);
|
|
856
|
+
_push(`</header><main class="cw-stage" data-framework="vue"${ssrRenderAttr("aria-busy", busy.value)}><h1 class="cw-sr">Files in ${ssrInterpolate(model.value.path || "/")}</h1><nav class="cw-rail" aria-label="File categories"${ssrRenderDynamicAttr("hidden", !ready.value)}><div class="cw-categories" role="tablist"><!--[-->`);
|
|
857
|
+
ssrRenderList(model.value.categories, (item) => {
|
|
858
|
+
_push(`<button role="tab"${ssrRenderAttr("id", "cw-tab-" + item.id)} aria-controls="cw-panel"${ssrRenderAttr("aria-selected", category.value === item.id)}${ssrRenderAttr("tabindex", category.value === item.id ? 0 : -1)} class="${ssrRenderClass(["cw-category", { "cw-active": category.value === item.id }])}"${ssrIncludeBooleanAttr(!complete.value) ? " disabled" : ""}><span>${item.svg ?? ""}</span><span>${ssrInterpolate(item.label)}</span><small>${ssrInterpolate(entries.value.filter((entry) => membership(entry).includes(item.id)).length)}</small></button>`);
|
|
859
|
+
});
|
|
860
|
+
_push(`<!--]--></div></nav><section class="cw-browser" id="cw-panel" role="tabpanel"${ssrRenderAttr("aria-labelledby", "cw-tab-" + category.value)}><div class="cw-list-head"><div class="cw-directory"><h2>${ssrInterpolate(heading.value)}</h2><span data-cw-count>${ssrInterpolate(complete.value ? rows.value.length : entries.value.length)} items</span></div><label class="cw-search">Search<input type="search"${ssrRenderAttr("value", query.value)}${ssrIncludeBooleanAttr(!ready.value || !complete.value) ? " disabled" : ""}></label></div><div class="cw-list-scroll"><ul class="cw-files">`);
|
|
861
|
+
if (model.value.parentHref) _push(`<li class="cw-row"><a${ssrRenderAttr("href", model.value.parentHref)} class="cw-entry">../</a></li>`);
|
|
862
|
+
else _push(`<!---->`);
|
|
863
|
+
_push(`<!--[-->`);
|
|
864
|
+
ssrRenderList(rows.value, (entry) => {
|
|
865
|
+
_push(`<li class="${ssrRenderClass(["cw-row", { "cw-selected": active.value?.id === entry.id }])}" data-cw-entry${ssrRenderAttr("data-id", entry.id)}>`);
|
|
866
|
+
if (entry.href) {
|
|
867
|
+
_push(`<a class="cw-entry"${ssrRenderAttr("href", safeHref(entry))}${ssrRenderAttr("target", entry.exitsExplorer ? "_blank" : void 0)}${ssrRenderAttr("rel", entry.exitsExplorer ? "noopener" : void 0)}>`);
|
|
868
|
+
ssrRenderSlot(_ctx.$slots, "row", {
|
|
869
|
+
model: model.value,
|
|
870
|
+
entry,
|
|
871
|
+
selected: selected.value === entry.id
|
|
872
|
+
}, () => {
|
|
873
|
+
_push(`<span class="cw-entry-icon" aria-hidden="true">${unref(entryIcon)(entry.kind === "symlink" ? "link" : unref(directoryLike)(entry) ? "directory" : "file") ?? ""}</span><span class="cw-entry-label"><span class="cw-name">${ssrInterpolate(entry.name)}${ssrInterpolate(unref(directoryLike)(entry) ? "/" : "")}</span>`);
|
|
874
|
+
if (entry.link) _push(`<span class="cw-target">${ssrInterpolate(entry.link.target)}</span>`);
|
|
875
|
+
else _push(`<!---->`);
|
|
876
|
+
_push(`<span class="cw-row-meta">${ssrInterpolate(entry.link?.unavailable ? "Target unavailable" : entry.kind)} · ${ssrInterpolate(unref(directoryLike)(entry) ? "Folder" : unref(size)(entry.size))}</span><span class="cw-row-date">Modified ${ssrInterpolate(unref(modified)(entry.modifiedAt))}</span></span>`);
|
|
877
|
+
}, _push, _parent);
|
|
878
|
+
_push(`</a>`);
|
|
879
|
+
} else {
|
|
880
|
+
_push(`<span class="cw-entry cw-unavailable">`);
|
|
881
|
+
ssrRenderSlot(_ctx.$slots, "row", {
|
|
882
|
+
model: model.value,
|
|
883
|
+
entry,
|
|
884
|
+
selected: selected.value === entry.id
|
|
885
|
+
}, () => {
|
|
886
|
+
_push(`${ssrInterpolate(entry.name)}`);
|
|
887
|
+
}, _push, _parent);
|
|
888
|
+
_push(`</span>`);
|
|
889
|
+
}
|
|
890
|
+
_push(`</li>`);
|
|
891
|
+
});
|
|
892
|
+
_push(`<!--]--></ul>`);
|
|
893
|
+
if (!rows.value.length) _push(`<p class="cw-empty">No matching files.</p>`);
|
|
894
|
+
else _push(`<!---->`);
|
|
895
|
+
_push(`</div></section>`);
|
|
896
|
+
if (ready.value && active.value) {
|
|
897
|
+
_push(`<aside class="cw-detail"><h2>${ssrInterpolate(active.value.name)}</h2><p>${ssrInterpolate(active.value.kind)} · ${ssrInterpolate(unref(directoryLike)(active.value) ? "Folder" : unref(size)(active.value.size))}</p><p>${ssrInterpolate(unref(modified)(active.value.modifiedAt))}</p>`);
|
|
898
|
+
if (active.value.link) _push(`<p>${ssrInterpolate(active.value.link.target)}</p>`);
|
|
899
|
+
else _push(`<!---->`);
|
|
900
|
+
_push(`</aside>`);
|
|
901
|
+
} else _push(`<!---->`);
|
|
902
|
+
_push(`<p role="status" aria-live="polite">${ssrInterpolate(busy.value ? "Loading…" : error.value)}</p>`);
|
|
903
|
+
if (model.value.page?.count > 1 && !complete.value) {
|
|
904
|
+
_push(`<nav aria-label="Pagination">`);
|
|
905
|
+
if (model.value.page.previousHref) _push(`<a${ssrRenderAttr("href", model.value.page.previousHref)} rel="prev">Previous</a>`);
|
|
906
|
+
else _push(`<!---->`);
|
|
907
|
+
_push(` Page ${ssrInterpolate(model.value.page.number)} of ${ssrInterpolate(model.value.page.count)} `);
|
|
908
|
+
if (model.value.page.nextHref) _push(`<a${ssrRenderAttr("href", model.value.page.nextHref)} rel="next">Next</a>`);
|
|
909
|
+
else _push(`<!---->`);
|
|
910
|
+
_push(`</nav>`);
|
|
911
|
+
} else _push(`<!---->`);
|
|
912
|
+
_push(`</main><footer class="cw-bottom"><div class="cw-key-help">`);
|
|
913
|
+
ssrRenderSlot(_ctx.$slots, "footer", { model: model.value }, () => {
|
|
914
|
+
_push(`Arrow keys navigate · Enter confirms · Backspace returns`);
|
|
915
|
+
}, _push, _parent);
|
|
916
|
+
_push(`</div>`);
|
|
917
|
+
if (pad.value) _push(`<div class="cw-controller">Controller · Confirm / Back</div>`);
|
|
918
|
+
else _push(`<!---->`);
|
|
919
|
+
_push(`<label class="cw-appearance">Color<select${ssrIncludeBooleanAttr(!ready.value) ? " disabled" : ""}><!--[-->`);
|
|
920
|
+
ssrRenderList(Object.keys(colors), (name) => {
|
|
921
|
+
_push(`<option${ssrIncludeBooleanAttr(Array.isArray(color.value) ? ssrLooseContain(color.value, null) : ssrLooseEqual(color.value, null)) ? " selected" : ""}>${ssrInterpolate(name)}</option>`);
|
|
922
|
+
});
|
|
923
|
+
_push(`<!--]--></select></label><button class="cw-motion"${ssrIncludeBooleanAttr(!ready.value) ? " disabled" : ""}>${ssrInterpolate(paused.value ? "Resume motion" : "Pause motion")}</button></footer><!--]-->`);
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
//#endregion
|
|
928
|
+
//#region render-generated.mjs
|
|
929
|
+
const renderBody = (model) => renderToString(createSSRApp(App_server_default, { model }));
|
|
930
|
+
//#endregion
|
|
931
|
+
//#region src/index.ts
|
|
932
|
+
function createCrosswaveTheme(options = {}, renderer) {
|
|
933
|
+
const categories = resolveCrosswaveCategories(options.categories);
|
|
934
|
+
const theme = {
|
|
935
|
+
name: "crosswave",
|
|
936
|
+
searchIndex: false,
|
|
937
|
+
listing: { pagination: true },
|
|
938
|
+
entryData: (entry, context) => ({
|
|
939
|
+
mimeType: entryMimeType(entry),
|
|
940
|
+
categories: entryCategories(entry, entryMimeType(entry), categories).map((item) => item.id),
|
|
941
|
+
folderDataHref: folderDataHref(entry, context, "crosswave")
|
|
942
|
+
}),
|
|
943
|
+
async render(context) {
|
|
944
|
+
const model = {
|
|
945
|
+
...createThemePage(context, theme, options.title ?? "Dirwell"),
|
|
946
|
+
categories: categories.map(({ id, label, svg }) => ({
|
|
947
|
+
id,
|
|
948
|
+
label,
|
|
949
|
+
svg
|
|
950
|
+
})),
|
|
951
|
+
color: options.color ?? "azure",
|
|
952
|
+
gamepad: options.gamepad ?? true,
|
|
953
|
+
backgroundMotion: options.backgroundMotion ?? true
|
|
954
|
+
};
|
|
955
|
+
const script = renderer?.clientScript ?? await readFile(new URL("./client.js", import.meta.url), "utf8"), styles = await readFile(new URL("./styles.css", import.meta.url), "utf8");
|
|
956
|
+
const scriptName = `crosswave-client-${createHash("sha256").update(script).digest("hex").slice(0, 16)}.js`;
|
|
957
|
+
const json = JSON.stringify(model).replaceAll("<", "\\u003c");
|
|
958
|
+
return {
|
|
959
|
+
html: `<!doctype html><html lang="en" data-cw-color="${escapeHtml(model.color)}" data-cw-motion="${model.backgroundMotion}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">${context.documentBaseHref === null ? "" : `<base href="${escapeHtml(context.documentBaseHref)}">`}${context.metadata?.head ?? `<title>${escapeHtml(model.title)}</title>`}<style>${styles}${renderer?.styles ?? ""}</style></head><body><div data-dirwell-theme="crosswave">${await (renderer?.renderBody ?? renderBody)(model)}</div><script type="application/json" id="dirwell-page-data">${json}<\/script><script ${renderer?.clientType === "module" ? "type=\"module\"" : "defer"} src="${escapeHtml(context.assetHref(scriptName))}"><\/script></body></html>`,
|
|
960
|
+
assets: {
|
|
961
|
+
[scriptName]: script,
|
|
962
|
+
[pageAsset("crosswave", model.path, model.page?.number ?? 1)]: JSON.stringify(model)
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
return theme;
|
|
968
|
+
}
|
|
969
|
+
//#endregion
|
|
970
|
+
export { createCrosswaveTheme, defaultCrosswaveCategories };
|
|
971
|
+
|
|
972
|
+
//# sourceMappingURL=index.mjs.map
|