veslx 0.1.2 → 0.1.4
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/bin/lib/build.ts +2 -0
- package/bin/lib/serve.ts +2 -0
- package/dist/client/App.js +17 -0
- package/dist/client/App.js.map +1 -0
- package/dist/client/components/front-matter.js +33 -0
- package/dist/client/components/front-matter.js.map +1 -0
- package/dist/client/components/gallery/components/figure-caption.js +14 -0
- package/dist/client/components/gallery/components/figure-caption.js.map +1 -0
- package/dist/client/components/gallery/components/figure-header.js +13 -0
- package/dist/client/components/gallery/components/figure-header.js.map +1 -0
- package/dist/client/components/gallery/components/lightbox.js +96 -0
- package/dist/client/components/gallery/components/lightbox.js.map +1 -0
- package/dist/client/components/gallery/components/loading-image.js +46 -0
- package/dist/client/components/gallery/components/loading-image.js.map +1 -0
- package/dist/client/components/gallery/hooks/use-gallery-images.js +84 -0
- package/dist/client/components/gallery/hooks/use-gallery-images.js.map +1 -0
- package/dist/client/components/gallery/hooks/use-lightbox.js +37 -0
- package/dist/client/components/gallery/hooks/use-lightbox.js.map +1 -0
- package/dist/client/components/gallery/index.js +96 -0
- package/dist/client/components/gallery/index.js.map +1 -0
- package/dist/client/components/gallery/lib/render-math-in-text.js +42 -0
- package/dist/client/components/gallery/lib/render-math-in-text.js.map +1 -0
- package/dist/client/components/header.js +60 -0
- package/dist/client/components/header.js.map +1 -0
- package/dist/client/components/loading.js +15 -0
- package/dist/client/components/loading.js.map +1 -0
- package/dist/client/components/mdx-components.js +134 -0
- package/dist/client/components/mdx-components.js.map +1 -0
- package/dist/client/components/mode-toggle.js +39 -0
- package/dist/client/components/mode-toggle.js.map +1 -0
- package/dist/client/components/page-error.js +39 -0
- package/dist/client/components/page-error.js.map +1 -0
- package/dist/client/components/parameter-badge.js +48 -0
- package/dist/client/components/parameter-badge.js.map +1 -0
- package/dist/client/components/parameter-table.js +301 -0
- package/dist/client/components/parameter-table.js.map +1 -0
- package/dist/client/components/post-list.js +119 -0
- package/dist/client/components/post-list.js.map +1 -0
- package/dist/client/components/running-bar.js +15 -0
- package/dist/client/components/running-bar.js.map +1 -0
- package/dist/client/components/slides-renderer.js +75 -0
- package/dist/client/components/slides-renderer.js.map +1 -0
- package/dist/client/components/theme-provider.js +9 -0
- package/dist/client/components/theme-provider.js.map +1 -0
- package/dist/client/components/ui/button.js +49 -0
- package/dist/client/components/ui/button.js.map +1 -0
- package/dist/client/components/ui/carousel.js +195 -0
- package/dist/client/components/ui/carousel.js.map +1 -0
- package/dist/client/components/ui/dropdown-menu.js +132 -0
- package/dist/client/components/ui/dropdown-menu.js.map +1 -0
- package/dist/client/hooks/use-key-bindings.js +40 -0
- package/dist/client/hooks/use-key-bindings.js.map +1 -0
- package/dist/client/hooks/use-mdx-content.js +78 -0
- package/dist/client/hooks/use-mdx-content.js.map +1 -0
- package/dist/client/lib/constants.js +5 -0
- package/dist/client/lib/constants.js.map +1 -0
- package/dist/client/lib/format-date.js +8 -0
- package/dist/client/lib/format-date.js.map +1 -0
- package/dist/client/lib/parameter-utils.js +110 -0
- package/dist/client/lib/parameter-utils.js.map +1 -0
- package/dist/client/lib/utils.js +9 -0
- package/dist/client/lib/utils.js.map +1 -0
- package/dist/client/logo_dark.png +0 -0
- package/dist/client/logo_light.png +0 -0
- package/dist/client/main.js +9 -0
- package/dist/client/main.js.map +1 -0
- package/dist/client/pages/home.js +30 -0
- package/dist/client/pages/home.js.map +1 -0
- package/dist/client/pages/post.js +53 -0
- package/dist/client/pages/post.js.map +1 -0
- package/dist/client/pages/slides.js +137 -0
- package/dist/client/pages/slides.js.map +1 -0
- package/dist/client/plugin/src/client.js +185 -0
- package/dist/client/plugin/src/client.js.map +1 -0
- package/package.json +8 -4
- package/src/main.tsx +1 -1
- package/vite.config.ts +22 -3
- package/vite.lib.config.ts +47 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { load } from "js-yaml";
|
|
2
|
+
function extractPath(data, path) {
|
|
3
|
+
if (!path || path === ".") return data;
|
|
4
|
+
const cleanPath = path.startsWith(".") ? path.slice(1) : path;
|
|
5
|
+
if (!cleanPath) return data;
|
|
6
|
+
const segments = [];
|
|
7
|
+
let current = "";
|
|
8
|
+
let i = 0;
|
|
9
|
+
while (i < cleanPath.length) {
|
|
10
|
+
const char = cleanPath[i];
|
|
11
|
+
if (char === ".") {
|
|
12
|
+
if (current) {
|
|
13
|
+
segments.push({ type: "key", value: current });
|
|
14
|
+
current = "";
|
|
15
|
+
}
|
|
16
|
+
i++;
|
|
17
|
+
} else if (char === "[") {
|
|
18
|
+
if (current) {
|
|
19
|
+
segments.push({ type: "key", value: current });
|
|
20
|
+
current = "";
|
|
21
|
+
}
|
|
22
|
+
const closeIdx = cleanPath.indexOf("]", i);
|
|
23
|
+
if (closeIdx === -1) return void 0;
|
|
24
|
+
const inner = cleanPath.slice(i + 1, closeIdx);
|
|
25
|
+
if (inner === "") {
|
|
26
|
+
segments.push({ type: "all", value: 0 });
|
|
27
|
+
} else {
|
|
28
|
+
const idx = parseInt(inner, 10);
|
|
29
|
+
if (isNaN(idx)) return void 0;
|
|
30
|
+
segments.push({ type: "index", value: idx });
|
|
31
|
+
}
|
|
32
|
+
i = closeIdx + 1;
|
|
33
|
+
} else {
|
|
34
|
+
current += char;
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (current) {
|
|
39
|
+
segments.push({ type: "key", value: current });
|
|
40
|
+
}
|
|
41
|
+
let result = data;
|
|
42
|
+
for (const seg of segments) {
|
|
43
|
+
if (result === null || result === void 0) return void 0;
|
|
44
|
+
if (seg.type === "key") {
|
|
45
|
+
if (typeof result !== "object" || Array.isArray(result)) return void 0;
|
|
46
|
+
result = result[seg.value];
|
|
47
|
+
} else if (seg.type === "index") {
|
|
48
|
+
if (!Array.isArray(result)) return void 0;
|
|
49
|
+
result = result[seg.value];
|
|
50
|
+
} else if (seg.type === "all") {
|
|
51
|
+
if (!Array.isArray(result)) return void 0;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
function getValueType(value) {
|
|
57
|
+
if (value === null) return "null";
|
|
58
|
+
if (Array.isArray(value)) return "array";
|
|
59
|
+
if (typeof value === "object") return "object";
|
|
60
|
+
if (typeof value === "boolean") return "boolean";
|
|
61
|
+
if (typeof value === "number") return "number";
|
|
62
|
+
return "string";
|
|
63
|
+
}
|
|
64
|
+
function formatNumber(value) {
|
|
65
|
+
if (Math.abs(value) < 1e-4 && value !== 0) return value.toExponential(1);
|
|
66
|
+
if (Math.abs(value) >= 1e4) return value.toExponential(1);
|
|
67
|
+
if (Number.isInteger(value)) return value.toString();
|
|
68
|
+
const str = value.toString();
|
|
69
|
+
if (str.length > 6) return value.toPrecision(4);
|
|
70
|
+
return str;
|
|
71
|
+
}
|
|
72
|
+
function formatValue(value) {
|
|
73
|
+
if (value === null) return "null";
|
|
74
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
75
|
+
if (typeof value === "number") return formatNumber(value);
|
|
76
|
+
if (Array.isArray(value)) return `[${value.length}]`;
|
|
77
|
+
if (typeof value === "object") return "{...}";
|
|
78
|
+
return String(value);
|
|
79
|
+
}
|
|
80
|
+
function parseConfigFile(content, path) {
|
|
81
|
+
if (path.endsWith(".yaml") || path.endsWith(".yml")) {
|
|
82
|
+
try {
|
|
83
|
+
return load(content);
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (path.endsWith(".json")) {
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(content);
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
function deriveLabelFromPath(keyPath) {
|
|
98
|
+
const cleanPath = keyPath.startsWith(".") ? keyPath.slice(1) : keyPath;
|
|
99
|
+
const parts = cleanPath.split(".");
|
|
100
|
+
return parts[parts.length - 1].replace(/\[\d+\]/g, "");
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
deriveLabelFromPath,
|
|
104
|
+
extractPath,
|
|
105
|
+
formatNumber,
|
|
106
|
+
formatValue,
|
|
107
|
+
getValueType,
|
|
108
|
+
parseConfigFile
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=parameter-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parameter-utils.js","sources":["../../../src/lib/parameter-utils.ts"],"sourcesContent":["import { load } from \"js-yaml\";\n\nexport type ParameterValue = string | number | boolean | null | ParameterValue[] | { [key: string]: ParameterValue };\n\n/**\n * Extract a value from nested data using a jq-like path.\n * Supports:\n * - .foo.bar → nested keys\n * - .foo[0] → array index\n * - .foo[] → all array elements (returns array)\n */\nexport function extractPath(data: ParameterValue, path: string): ParameterValue | undefined {\n if (!path || path === \".\") return data;\n\n const cleanPath = path.startsWith(\".\") ? path.slice(1) : path;\n if (!cleanPath) return data;\n\n const segments: Array<{ type: \"key\" | \"index\" | \"all\"; value: string | number }> = [];\n let current = \"\";\n let i = 0;\n\n while (i < cleanPath.length) {\n const char = cleanPath[i];\n\n if (char === \".\") {\n if (current) {\n segments.push({ type: \"key\", value: current });\n current = \"\";\n }\n i++;\n } else if (char === \"[\") {\n if (current) {\n segments.push({ type: \"key\", value: current });\n current = \"\";\n }\n const closeIdx = cleanPath.indexOf(\"]\", i);\n if (closeIdx === -1) return undefined;\n const inner = cleanPath.slice(i + 1, closeIdx);\n if (inner === \"\") {\n segments.push({ type: \"all\", value: 0 });\n } else {\n const idx = parseInt(inner, 10);\n if (isNaN(idx)) return undefined;\n segments.push({ type: \"index\", value: idx });\n }\n i = closeIdx + 1;\n } else {\n current += char;\n i++;\n }\n }\n if (current) {\n segments.push({ type: \"key\", value: current });\n }\n\n let result: ParameterValue | undefined = data;\n\n for (const seg of segments) {\n if (result === null || result === undefined) return undefined;\n\n if (seg.type === \"key\") {\n if (typeof result !== \"object\" || Array.isArray(result)) return undefined;\n result = (result as Record<string, ParameterValue>)[seg.value as string];\n } else if (seg.type === \"index\") {\n if (!Array.isArray(result)) return undefined;\n result = result[seg.value as number];\n } else if (seg.type === \"all\") {\n if (!Array.isArray(result)) return undefined;\n // Return the array itself for further processing\n }\n }\n\n return result;\n}\n\nexport function getValueType(value: ParameterValue): \"string\" | \"number\" | \"boolean\" | \"null\" | \"array\" | \"object\" {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"array\";\n if (typeof value === \"object\") return \"object\";\n if (typeof value === \"boolean\") return \"boolean\";\n if (typeof value === \"number\") return \"number\";\n return \"string\";\n}\n\nexport function formatNumber(value: number): string {\n if (Math.abs(value) < 0.0001 && value !== 0) return value.toExponential(1);\n if (Math.abs(value) >= 10000) return value.toExponential(1);\n if (Number.isInteger(value)) return value.toString();\n const str = value.toString();\n if (str.length > 6) return value.toPrecision(4);\n return str;\n}\n\nexport function formatValue(value: ParameterValue): string {\n if (value === null) return \"null\";\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n if (typeof value === \"number\") return formatNumber(value);\n if (Array.isArray(value)) return `[${value.length}]`;\n if (typeof value === \"object\") return \"{...}\";\n return String(value);\n}\n\n/**\n * Parse YAML or JSON content based on file extension.\n */\nexport function parseConfigFile(content: string, path: string): Record<string, ParameterValue> | null {\n if (path.endsWith(\".yaml\") || path.endsWith(\".yml\")) {\n try {\n return load(content) as Record<string, ParameterValue>;\n } catch {\n return null;\n }\n }\n\n if (path.endsWith(\".json\")) {\n try {\n return JSON.parse(content) as Record<string, ParameterValue>;\n } catch {\n return null;\n }\n }\n\n return null;\n}\n\n/**\n * Derive a label from a jq-like keyPath.\n * E.g., \".base.N_E\" → \"N_E\"\n */\nexport function deriveLabelFromPath(keyPath: string): string {\n const cleanPath = keyPath.startsWith(\".\") ? keyPath.slice(1) : keyPath;\n const parts = cleanPath.split(\".\");\n return parts[parts.length - 1].replace(/\\[\\d+\\]/g, \"\");\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../../src/lib/utils.ts"],"sourcesContent":["import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n"],"names":[],"mappings":";;AAGO,SAAS,MAAM,QAAsB;AAC1C,SAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B;"}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { StrictMode } from "react";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import "./src/index.css";
|
|
5
|
+
import App from "./App.js";
|
|
6
|
+
createRoot(document.getElementById("root")).render(
|
|
7
|
+
/* @__PURE__ */ jsx(StrictMode, { children: /* @__PURE__ */ jsx(App, {}) })
|
|
8
|
+
);
|
|
9
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sources":["../../src/main.tsx"],"sourcesContent":["import { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport './index.css'\nimport App from '@/App'\n\ncreateRoot(document.getElementById('root')!).render(\n <StrictMode>\n <App />\n </StrictMode>,\n)\n"],"names":[],"mappings":";;;;;AAKA,WAAW,SAAS,eAAe,MAAM,CAAE,EAAE;AAAA,EAC3C,oBAAC,YAAA,EACC,UAAA,oBAAC,KAAA,CAAA,CAAI,EAAA,CACP;AACF;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useParams } from "react-router-dom";
|
|
3
|
+
import { useDirectory } from "../plugin/src/client.js";
|
|
4
|
+
import Loading from "../components/loading.js";
|
|
5
|
+
import PostList from "../components/post-list.js";
|
|
6
|
+
import { ErrorDisplay } from "../components/page-error.js";
|
|
7
|
+
import { RunningBar } from "../components/running-bar.js";
|
|
8
|
+
import { Header } from "../components/header.js";
|
|
9
|
+
function Home() {
|
|
10
|
+
const { "*": path = "." } = useParams();
|
|
11
|
+
const { directory, loading, error } = useDirectory(path);
|
|
12
|
+
if (error) {
|
|
13
|
+
return /* @__PURE__ */ jsx(ErrorDisplay, { error, path });
|
|
14
|
+
}
|
|
15
|
+
if (loading) {
|
|
16
|
+
return /* @__PURE__ */ jsx(Loading, {});
|
|
17
|
+
}
|
|
18
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen flex-col bg-background noise-overlay", children: [
|
|
19
|
+
/* @__PURE__ */ jsx(RunningBar, {}),
|
|
20
|
+
/* @__PURE__ */ jsx(Header, {}),
|
|
21
|
+
/* @__PURE__ */ jsxs("main", { className: "flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]", children: [
|
|
22
|
+
/* @__PURE__ */ jsx("title", { children: `Pinglab ${path}` }),
|
|
23
|
+
/* @__PURE__ */ jsx("main", { className: "flex flex-col gap-6 mb-32 mt-32", children: directory && /* @__PURE__ */ jsx("div", { className: "animate-fade-in", children: /* @__PURE__ */ jsx(PostList, { directory }) }) })
|
|
24
|
+
] })
|
|
25
|
+
] });
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
Home
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=home.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"home.js","sources":["../../../src/pages/home.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\"\nimport { useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport PostList from \"@/components/post-list\";\nimport { ErrorDisplay } from \"@/components/page-error\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\n\nexport function Home() {\n const { \"*\": path = \".\" } = useParams();\n const { directory, loading, error } = useDirectory(path)\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <RunningBar />\n <Header />\n <main className=\"flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]\">\n <title>{`Pinglab ${path}`}</title>\n <main className=\"flex flex-col gap-6 mb-32 mt-32\">\n {directory && (\n <div className=\"animate-fade-in\">\n <PostList directory={directory}/>\n </div>\n )}\n </main>\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;AAQO,SAAS,OAAO;AACrB,QAAM,EAAE,KAAK,OAAO,IAAA,IAAQ,UAAA;AAC5B,QAAM,EAAE,WAAW,SAAS,MAAA,IAAU,aAAa,IAAI;AAEvD,MAAI,OAAO;AACT,WAAO,oBAAC,cAAA,EAAa,OAAc,KAAA,CAAY;AAAA,EACjD;AAEA,MAAI,SAAS;AACX,+BACG,SAAA,EAAQ;AAAA,EAEb;AAEA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,YAAA,EAAW;AAAA,wBACX,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,+EACd,UAAA;AAAA,MAAA,oBAAC,SAAA,EAAO,UAAA,WAAW,IAAI,IAAG;AAAA,MAC1B,oBAAC,QAAA,EAAK,WAAU,mCACb,UAAA,aACC,oBAAC,OAAA,EAAI,WAAU,mBACb,UAAA,oBAAC,UAAA,EAAS,UAAA,CAAqB,GACjC,EAAA,CAEJ;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useParams } from "react-router-dom";
|
|
3
|
+
import { useDirectory, isSimulationRunning, findSlides } from "../plugin/src/client.js";
|
|
4
|
+
import Loading from "../components/loading.js";
|
|
5
|
+
import { FrontMatter } from "../components/front-matter.js";
|
|
6
|
+
import { RunningBar } from "../components/running-bar.js";
|
|
7
|
+
import { Header } from "../components/header.js";
|
|
8
|
+
import { useMDXContent } from "../hooks/use-mdx-content.js";
|
|
9
|
+
import { mdxComponents } from "../components/mdx-components.js";
|
|
10
|
+
function Post() {
|
|
11
|
+
const { "path": path = "." } = useParams();
|
|
12
|
+
const filePath = `${path}/README.mdx`;
|
|
13
|
+
const { directory, loading: dirLoading } = useDirectory(filePath);
|
|
14
|
+
const { Content, frontmatter, loading: mdxLoading, error } = useMDXContent(path);
|
|
15
|
+
const isRunning = isSimulationRunning();
|
|
16
|
+
let slides = null;
|
|
17
|
+
if (directory) {
|
|
18
|
+
slides = findSlides(directory);
|
|
19
|
+
}
|
|
20
|
+
const loading = dirLoading || mdxLoading;
|
|
21
|
+
if (loading) return /* @__PURE__ */ jsx(Loading, {});
|
|
22
|
+
if (error) {
|
|
23
|
+
return /* @__PURE__ */ jsx("main", { className: "min-h-screen bg-background container mx-auto max-w-4xl py-12", children: /* @__PURE__ */ jsx("p", { className: "text-center text-red-600", children: error.message }) });
|
|
24
|
+
}
|
|
25
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen flex-col bg-background noise-overlay", children: [
|
|
26
|
+
/* @__PURE__ */ jsx("title", { children: frontmatter == null ? void 0 : frontmatter.title }),
|
|
27
|
+
/* @__PURE__ */ jsx(RunningBar, {}),
|
|
28
|
+
/* @__PURE__ */ jsx(Header, {}),
|
|
29
|
+
/* @__PURE__ */ jsxs("main", { className: "flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]", children: [
|
|
30
|
+
isRunning && /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-50 px-[var(--page-padding)] py-2 bg-red-500 text-primary-foreground font-mono text-xs text-center tracking-wide", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-3", children: [
|
|
31
|
+
/* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-current animate-pulse" }),
|
|
32
|
+
/* @__PURE__ */ jsx("span", { className: "uppercase tracking-widest", children: "simulation running" }),
|
|
33
|
+
/* @__PURE__ */ jsx("span", { className: "text-primary-foreground/60", children: "Page will auto-refresh on completion" })
|
|
34
|
+
] }) }),
|
|
35
|
+
Content && /* @__PURE__ */ jsxs("article", { className: "my-24 prose dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline max-w-[var(--prose-width)] animate-fade-in", children: [
|
|
36
|
+
/* @__PURE__ */ jsx(
|
|
37
|
+
FrontMatter,
|
|
38
|
+
{
|
|
39
|
+
title: frontmatter == null ? void 0 : frontmatter.title,
|
|
40
|
+
date: frontmatter == null ? void 0 : frontmatter.date,
|
|
41
|
+
description: frontmatter == null ? void 0 : frontmatter.description,
|
|
42
|
+
slides
|
|
43
|
+
}
|
|
44
|
+
),
|
|
45
|
+
/* @__PURE__ */ jsx(Content, { components: mdxComponents })
|
|
46
|
+
] })
|
|
47
|
+
] })
|
|
48
|
+
] });
|
|
49
|
+
}
|
|
50
|
+
export {
|
|
51
|
+
Post
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=post.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post.js","sources":["../../../src/pages/post.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { findSlides, isSimulationRunning, useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport { FileEntry } from \"plugin/src/lib\";\nimport { FrontMatter } from \"@/components/front-matter\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { useMDXContent } from \"@/hooks/use-mdx-content\";\nimport { mdxComponents } from \"@/components/mdx-components\";\n\n\nexport function Post() {\n const { \"path\": path = \".\" } = useParams();\n const filePath = `${path}/README.mdx`\n const { directory, loading: dirLoading } = useDirectory(filePath)\n const { Content, frontmatter, loading: mdxLoading, error } = useMDXContent(path);\n const isRunning = isSimulationRunning();\n\n let slides: FileEntry | null = null;\n if (directory) {\n slides = findSlides(directory);\n }\n\n const loading = dirLoading || mdxLoading;\n\n if (loading) return <Loading />\n\n if (error) {\n return (\n <main className=\"min-h-screen bg-background container mx-auto max-w-4xl py-12\">\n <p className=\"text-center text-red-600\">{error.message}</p>\n </main>\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <title>{frontmatter?.title}</title>\n <RunningBar />\n <Header />\n <main className=\"flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]\">\n\n {isRunning && (\n <div className=\"sticky top-0 z-50 px-[var(--page-padding)] py-2 bg-red-500 text-primary-foreground font-mono text-xs text-center tracking-wide\">\n <span className=\"inline-flex items-center gap-3\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current animate-pulse\" />\n <span className=\"uppercase tracking-widest\">simulation running</span>\n <span className=\"text-primary-foreground/60\">Page will auto-refresh on completion</span>\n </span>\n </div>\n )}\n\n {Content && (\n <article className=\"my-24 prose dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline max-w-[var(--prose-width)] animate-fade-in\">\n <FrontMatter\n title={frontmatter?.title}\n date={frontmatter?.date}\n description={frontmatter?.description}\n slides={slides}\n />\n <Content components={mdxComponents} />\n </article>\n )}\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAWO,SAAS,OAAO;AACrB,QAAM,EAAE,QAAQ,OAAO,IAAA,IAAQ,UAAA;AAC/B,QAAM,WAAW,GAAG,IAAI;AACxB,QAAM,EAAE,WAAW,SAAS,WAAA,IAAe,aAAa,QAAQ;AAChE,QAAM,EAAE,SAAS,aAAa,SAAS,YAAY,MAAA,IAAU,cAAc,IAAI;AAC/E,QAAM,YAAY,oBAAA;AAElB,MAAI,SAA2B;AAC/B,MAAI,WAAW;AACb,aAAS,WAAW,SAAS;AAAA,EAC/B;AAEA,QAAM,UAAU,cAAc;AAE9B,MAAI,QAAS,QAAO,oBAAC,SAAA,CAAA,CAAQ;AAE7B,MAAI,OAAO;AACT,WACE,oBAAC,QAAA,EAAK,WAAU,gEACd,UAAA,oBAAC,OAAE,WAAU,4BAA4B,UAAA,MAAM,QAAA,CAAQ,GACzD;AAAA,EAEJ;AAEA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAO,qDAAa,MAAA,CAAM;AAAA,wBAC1B,YAAA,EAAW;AAAA,wBACX,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,+EAEb,UAAA;AAAA,MAAA,iCACE,OAAA,EAAI,WAAU,kIACb,UAAA,qBAAC,QAAA,EAAK,WAAU,kCACd,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,oDAAA,CAAoD;AAAA,QACpE,oBAAC,QAAA,EAAK,WAAU,6BAA4B,UAAA,sBAAkB;AAAA,QAC9D,oBAAC,QAAA,EAAK,WAAU,8BAA6B,UAAA,uCAAA,CAAoC;AAAA,MAAA,EAAA,CACnF,EAAA,CACF;AAAA,MAGD,WACC,qBAAC,WAAA,EAAQ,WAAU,oMACjB,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,OAAO,2CAAa;AAAA,YACpB,MAAM,2CAAa;AAAA,YACnB,aAAa,2CAAa;AAAA,YAC1B;AAAA,UAAA;AAAA,QAAA;AAAA,QAEF,oBAAC,SAAA,EAAQ,YAAY,cAAA,CAAe;AAAA,MAAA,EAAA,CACtC;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useRef, useCallback, useEffect } from "react";
|
|
3
|
+
import { FULLSCREEN_DATA_ATTR } from "../lib/constants.js";
|
|
4
|
+
import { useParams, useSearchParams } from "react-router-dom";
|
|
5
|
+
import Loading from "../components/loading.js";
|
|
6
|
+
import { FrontMatter } from "../components/front-matter.js";
|
|
7
|
+
import { RunningBar } from "../components/running-bar.js";
|
|
8
|
+
import { Header } from "../components/header.js";
|
|
9
|
+
import { useMDXSlides } from "../hooks/use-mdx-content.js";
|
|
10
|
+
import { useSlidesFromMDX, SlideContent } from "../components/slides-renderer.js";
|
|
11
|
+
function SlidesPage() {
|
|
12
|
+
const { "path": path = "." } = useParams();
|
|
13
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
14
|
+
const { Content, frontmatter, loading, error } = useMDXSlides(path);
|
|
15
|
+
const { slides } = useSlidesFromMDX({ Content, frontmatter });
|
|
16
|
+
const totalSlides = slides.length + 1;
|
|
17
|
+
const initialSlide = Math.max(0, Math.min(
|
|
18
|
+
parseInt(searchParams.get("slide") || "0", 10),
|
|
19
|
+
totalSlides - 1
|
|
20
|
+
));
|
|
21
|
+
const [currentSlide, setCurrentSlide] = useState(initialSlide);
|
|
22
|
+
const slideRefs = useRef([]);
|
|
23
|
+
const goToSlide = useCallback((index) => {
|
|
24
|
+
const clampedIndex = Math.max(0, Math.min(index, totalSlides - 1));
|
|
25
|
+
const target = slideRefs.current[clampedIndex];
|
|
26
|
+
if (target) {
|
|
27
|
+
const targetTop = target.getBoundingClientRect().top + window.scrollY;
|
|
28
|
+
window.scrollTo({ top: targetTop, behavior: "smooth" });
|
|
29
|
+
}
|
|
30
|
+
}, [totalSlides]);
|
|
31
|
+
const goToPrevious = useCallback(() => {
|
|
32
|
+
goToSlide(currentSlide - 1);
|
|
33
|
+
}, [currentSlide, goToSlide]);
|
|
34
|
+
const goToNext = useCallback(() => {
|
|
35
|
+
goToSlide(currentSlide + 1);
|
|
36
|
+
}, [currentSlide, goToSlide]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const handleKeyDown = (e) => {
|
|
39
|
+
if (e.key === "ArrowUp" || e.key === "k") {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
goToPrevious();
|
|
42
|
+
} else if (e.key === "ArrowDown" || e.key === "j") {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
goToNext();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
48
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
49
|
+
}, [goToPrevious, goToNext]);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
let observer = null;
|
|
52
|
+
const timeoutId = setTimeout(() => {
|
|
53
|
+
observer = new IntersectionObserver(
|
|
54
|
+
(entries) => {
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
if (entry.isIntersecting) {
|
|
57
|
+
const index = slideRefs.current.findIndex((ref) => ref === entry.target);
|
|
58
|
+
if (index !== -1) {
|
|
59
|
+
setCurrentSlide(index);
|
|
60
|
+
setSearchParams(index > 0 ? { slide: String(index) } : {}, { replace: true });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{ threshold: 0.5 }
|
|
66
|
+
);
|
|
67
|
+
slideRefs.current.forEach((ref) => {
|
|
68
|
+
if (ref) observer.observe(ref);
|
|
69
|
+
});
|
|
70
|
+
}, 100);
|
|
71
|
+
return () => {
|
|
72
|
+
clearTimeout(timeoutId);
|
|
73
|
+
observer == null ? void 0 : observer.disconnect();
|
|
74
|
+
};
|
|
75
|
+
}, [slides.length, setSearchParams]);
|
|
76
|
+
if (loading) {
|
|
77
|
+
return /* @__PURE__ */ jsx(Loading, {});
|
|
78
|
+
}
|
|
79
|
+
if (error) {
|
|
80
|
+
return /* @__PURE__ */ jsx("main", { className: "min-h-screen bg-background container mx-auto max-w-4xl py-12", children: /* @__PURE__ */ jsx("p", { className: "text-center text-red-600", children: error.message }) });
|
|
81
|
+
}
|
|
82
|
+
if (slides.length === 0) {
|
|
83
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-12 text-muted-foreground font-mono text-sm", children: 'no slides found — use "---" to separate slides' });
|
|
84
|
+
}
|
|
85
|
+
return /* @__PURE__ */ jsxs("main", { className: "slides-container", children: [
|
|
86
|
+
/* @__PURE__ */ jsx("title", { children: frontmatter == null ? void 0 : frontmatter.title }),
|
|
87
|
+
/* @__PURE__ */ jsx(RunningBar, {}),
|
|
88
|
+
/* @__PURE__ */ jsx(
|
|
89
|
+
Header,
|
|
90
|
+
{
|
|
91
|
+
slideControls: {
|
|
92
|
+
current: currentSlide,
|
|
93
|
+
total: totalSlides,
|
|
94
|
+
onPrevious: goToPrevious,
|
|
95
|
+
onNext: goToNext
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
),
|
|
99
|
+
/* @__PURE__ */ jsxs("div", { ...{ [FULLSCREEN_DATA_ATTR]: "true" }, children: [
|
|
100
|
+
/* @__PURE__ */ jsx(
|
|
101
|
+
"div",
|
|
102
|
+
{
|
|
103
|
+
ref: (el) => {
|
|
104
|
+
slideRefs.current[0] = el;
|
|
105
|
+
},
|
|
106
|
+
className: "slide-page max-w-xl min-h-[50vh] sm:min-h-[70vh] md:min-h-screen flex items-center justify-center py-8 sm:py-12 md:py-16 px-4 mx-auto",
|
|
107
|
+
children: /* @__PURE__ */ jsx(
|
|
108
|
+
FrontMatter,
|
|
109
|
+
{
|
|
110
|
+
title: frontmatter == null ? void 0 : frontmatter.title,
|
|
111
|
+
date: frontmatter == null ? void 0 : frontmatter.date,
|
|
112
|
+
description: frontmatter == null ? void 0 : frontmatter.description
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
),
|
|
117
|
+
/* @__PURE__ */ jsx("hr", { className: "print:hidden" }),
|
|
118
|
+
slides.map((slideContent, index) => /* @__PURE__ */ jsxs("div", { children: [
|
|
119
|
+
/* @__PURE__ */ jsx(
|
|
120
|
+
"div",
|
|
121
|
+
{
|
|
122
|
+
ref: (el) => {
|
|
123
|
+
slideRefs.current[index + 1] = el;
|
|
124
|
+
},
|
|
125
|
+
className: "slide-page min-h-[50vh] sm:min-h-[70vh] md:min-h-screen flex items-center justify-center py-8 sm:py-12 md:py-16 px-4 mx-auto",
|
|
126
|
+
children: /* @__PURE__ */ jsx(SlideContent, { children: slideContent })
|
|
127
|
+
}
|
|
128
|
+
),
|
|
129
|
+
index < slides.length - 1 && /* @__PURE__ */ jsx("hr", { className: "print:hidden" })
|
|
130
|
+
] }, index))
|
|
131
|
+
] })
|
|
132
|
+
] });
|
|
133
|
+
}
|
|
134
|
+
export {
|
|
135
|
+
SlidesPage
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=slides.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slides.js","sources":["../../../src/pages/slides.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { FULLSCREEN_DATA_ATTR } from \"@/lib/constants\";\nimport { useParams, useSearchParams } from \"react-router-dom\"\nimport Loading from \"@/components/loading\";\nimport { FrontMatter } from \"@/components/front-matter\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { useMDXSlides } from \"@/hooks/use-mdx-content\";\nimport { useSlidesFromMDX, SlideContent } from \"@/components/slides-renderer\";\n\n\nexport function SlidesPage() {\n const { \"path\": path = \".\" } = useParams();\n const [searchParams, setSearchParams] = useSearchParams();\n\n // Load the compiled MDX module\n const { Content, frontmatter, loading, error } = useMDXSlides(path);\n\n // Split the MDX content into slides by <hr> elements\n const { slides } = useSlidesFromMDX({ Content, frontmatter });\n\n // Total slides = 1 (title) + content slides\n const totalSlides = slides.length + 1;\n\n // Get initial slide from query param\n const initialSlide = Math.max(0, Math.min(\n parseInt(searchParams.get(\"slide\") || \"0\", 10),\n totalSlides - 1\n ));\n\n const [currentSlide, setCurrentSlide] = useState(initialSlide);\n const slideRefs = useRef<(HTMLDivElement | null)[]>([]);\n\n // Scroll to slide and update query param\n const goToSlide = useCallback((index: number) => {\n const clampedIndex = Math.max(0, Math.min(index, totalSlides - 1));\n const target = slideRefs.current[clampedIndex];\n if (target) {\n const targetTop = target.getBoundingClientRect().top + window.scrollY;\n window.scrollTo({ top: targetTop, behavior: \"smooth\" });\n }\n }, [totalSlides]);\n\n const goToPrevious = useCallback(() => {\n goToSlide(currentSlide - 1);\n }, [currentSlide, goToSlide]);\n\n const goToNext = useCallback(() => {\n goToSlide(currentSlide + 1);\n }, [currentSlide, goToSlide]);\n\n // Keyboard navigation\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowUp\" || e.key === \"k\") {\n e.preventDefault();\n goToPrevious();\n } else if (e.key === \"ArrowDown\" || e.key === \"j\") {\n e.preventDefault();\n goToNext();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [goToPrevious, goToNext]);\n\n // Update query param on scroll (delayed to avoid interference on load)\n useEffect(() => {\n let observer: IntersectionObserver | null = null;\n\n const timeoutId = setTimeout(() => {\n observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n const index = slideRefs.current.findIndex((ref) => ref === entry.target);\n if (index !== -1) {\n setCurrentSlide(index);\n setSearchParams(index > 0 ? { slide: String(index) } : {}, { replace: true });\n }\n }\n }\n },\n { threshold: 0.5 }\n );\n\n slideRefs.current.forEach((ref) => {\n if (ref) observer!.observe(ref);\n });\n }, 100);\n\n return () => {\n clearTimeout(timeoutId);\n observer?.disconnect();\n };\n }, [slides.length, setSearchParams]);\n\n if (loading) {\n return <Loading />\n }\n\n if (error) {\n return (\n <main className=\"min-h-screen bg-background container mx-auto max-w-4xl py-12\">\n <p className=\"text-center text-red-600\">{error.message}</p>\n </main>\n )\n }\n\n if (slides.length === 0) {\n return (\n <div className=\"flex items-center justify-center p-12 text-muted-foreground font-mono text-sm\">\n no slides found — use \"---\" to separate slides\n </div>\n );\n }\n\n return (\n <main className=\"slides-container\">\n <title>{frontmatter?.title}</title>\n <RunningBar />\n <Header\n slideControls={{\n current: currentSlide,\n total: totalSlides,\n onPrevious: goToPrevious,\n onNext: goToNext,\n }}\n />\n <div {...{[FULLSCREEN_DATA_ATTR]: \"true\"}}>\n {/* Title slide */}\n <div\n ref={(el) => { slideRefs.current[0] = el; }}\n className=\"slide-page max-w-xl min-h-[50vh] sm:min-h-[70vh] md:min-h-screen flex items-center justify-center py-8 sm:py-12 md:py-16 px-4 mx-auto\"\n >\n <FrontMatter\n title={frontmatter?.title}\n date={frontmatter?.date}\n description={frontmatter?.description}\n />\n </div>\n <hr className=\"print:hidden\" />\n\n {/* Content slides */}\n {slides.map((slideContent, index) => (\n <div key={index}>\n <div\n ref={(el) => { slideRefs.current[index + 1] = el; }}\n className=\"slide-page min-h-[50vh] sm:min-h-[70vh] md:min-h-screen flex items-center justify-center py-8 sm:py-12 md:py-16 px-4 mx-auto\"\n >\n <SlideContent>{slideContent}</SlideContent>\n </div>\n {index < slides.length - 1 && <hr className=\"print:hidden\" />}\n </div>\n ))}\n </div>\n </main>\n )\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
async function parsePath(directory, path) {
|
|
3
|
+
const parts = path === "." ? [] : path.split("/").filter(Boolean);
|
|
4
|
+
let file = null;
|
|
5
|
+
let currentDir = directory;
|
|
6
|
+
for (let i = 0; i < parts.length; i++) {
|
|
7
|
+
const part = parts[i];
|
|
8
|
+
const isLastPart = i === parts.length - 1;
|
|
9
|
+
if (isLastPart) {
|
|
10
|
+
const matchedFile = currentDir.children.find(
|
|
11
|
+
(child) => child.type === "file" && child.name === part
|
|
12
|
+
);
|
|
13
|
+
if (matchedFile) {
|
|
14
|
+
file = matchedFile;
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const nextDir = currentDir.children.find(
|
|
19
|
+
(child) => child.type === "directory" && child.name === part
|
|
20
|
+
);
|
|
21
|
+
if (!nextDir) {
|
|
22
|
+
throw new Error(`Path not found: ${path}`);
|
|
23
|
+
}
|
|
24
|
+
currentDir = nextDir;
|
|
25
|
+
}
|
|
26
|
+
return { directory: currentDir, file };
|
|
27
|
+
}
|
|
28
|
+
function findReadme(directory) {
|
|
29
|
+
const readme = directory.children.find(
|
|
30
|
+
(child) => child.type === "file" && [
|
|
31
|
+
"README.md",
|
|
32
|
+
"Readme.md",
|
|
33
|
+
"readme.md",
|
|
34
|
+
"README.mdx",
|
|
35
|
+
"Readme.mdx",
|
|
36
|
+
"readme.mdx"
|
|
37
|
+
].includes(child.name)
|
|
38
|
+
);
|
|
39
|
+
return readme || null;
|
|
40
|
+
}
|
|
41
|
+
function findSlides(directory) {
|
|
42
|
+
const readme = directory.children.find(
|
|
43
|
+
(child) => child.type === "file" && [
|
|
44
|
+
"SLIDES.md",
|
|
45
|
+
"Slides.md",
|
|
46
|
+
"slides.md",
|
|
47
|
+
"SLIDES.mdx",
|
|
48
|
+
"Slides.mdx",
|
|
49
|
+
"slides.mdx"
|
|
50
|
+
].includes(child.name)
|
|
51
|
+
);
|
|
52
|
+
return readme || null;
|
|
53
|
+
}
|
|
54
|
+
function useDirectory(path = ".") {
|
|
55
|
+
const [directory, setDirectory] = useState(null);
|
|
56
|
+
const [file, setFile] = useState(null);
|
|
57
|
+
const [loading, setLoading] = useState(true);
|
|
58
|
+
const [error, setError] = useState(null);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
setLoading(true);
|
|
61
|
+
setError(null);
|
|
62
|
+
(async () => {
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(`/raw/.veslx.json`);
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
if (response.status === 404) {
|
|
67
|
+
setError({ type: "config_not_found", message: ".veslx.json not found" });
|
|
68
|
+
} else {
|
|
69
|
+
setError({ type: "fetch_error", message: `Failed to fetch: ${response.status} ${response.statusText}` });
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let json;
|
|
74
|
+
try {
|
|
75
|
+
json = await response.json();
|
|
76
|
+
} catch {
|
|
77
|
+
setError({ type: "parse_error", message: "Failed to parse .veslx.json" });
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
let parsed;
|
|
81
|
+
try {
|
|
82
|
+
parsed = await parsePath(json, path);
|
|
83
|
+
} catch {
|
|
84
|
+
setError({ type: "path_not_found", message: `Path not found: ${path}`, status: 404 });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
parsed.directory.children.sort((a, b) => {
|
|
88
|
+
let aDate, bDate;
|
|
89
|
+
if (a.children) {
|
|
90
|
+
const readme = findReadme(a);
|
|
91
|
+
if (readme && readme.frontmatter && readme.frontmatter.date) {
|
|
92
|
+
aDate = new Date(readme.frontmatter.date);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (b.children) {
|
|
96
|
+
const readme = findReadme(b);
|
|
97
|
+
if (readme && readme.frontmatter && readme.frontmatter.date) {
|
|
98
|
+
bDate = new Date(readme.frontmatter.date);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (aDate && bDate) {
|
|
102
|
+
return bDate.getTime() - aDate.getTime();
|
|
103
|
+
}
|
|
104
|
+
return 0;
|
|
105
|
+
});
|
|
106
|
+
setDirectory(parsed.directory);
|
|
107
|
+
setFile(parsed.file);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
setError({ type: "fetch_error", message: err.message || "Unknown error" });
|
|
110
|
+
} finally {
|
|
111
|
+
setLoading(false);
|
|
112
|
+
}
|
|
113
|
+
})();
|
|
114
|
+
return () => {
|
|
115
|
+
};
|
|
116
|
+
}, [path]);
|
|
117
|
+
return { directory, file, loading, error };
|
|
118
|
+
}
|
|
119
|
+
function useFileContent(path) {
|
|
120
|
+
const [blob, setBlob] = useState(null);
|
|
121
|
+
const [content, setContent] = useState(null);
|
|
122
|
+
const [loading, setLoading] = useState(true);
|
|
123
|
+
const [error, setError] = useState(null);
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
setLoading(true);
|
|
127
|
+
setError(null);
|
|
128
|
+
(async () => {
|
|
129
|
+
try {
|
|
130
|
+
const res = await fetch(`/raw/${path}`, {
|
|
131
|
+
signal: controller.signal
|
|
132
|
+
});
|
|
133
|
+
if (!res.ok) {
|
|
134
|
+
throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);
|
|
135
|
+
}
|
|
136
|
+
const fetchedBlob = await res.blob();
|
|
137
|
+
setBlob(fetchedBlob);
|
|
138
|
+
try {
|
|
139
|
+
const text = await fetchedBlob.text();
|
|
140
|
+
setContent(text);
|
|
141
|
+
} catch {
|
|
142
|
+
setContent(null);
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
setError(err instanceof Error ? err.message : "Unknown error");
|
|
149
|
+
} finally {
|
|
150
|
+
setLoading(false);
|
|
151
|
+
}
|
|
152
|
+
})();
|
|
153
|
+
return () => controller.abort();
|
|
154
|
+
}, [path]);
|
|
155
|
+
return { blob, content, loading, error };
|
|
156
|
+
}
|
|
157
|
+
function isSimulationRunning() {
|
|
158
|
+
const [running, setRunning] = useState(false);
|
|
159
|
+
useEffect(() => {
|
|
160
|
+
let interval;
|
|
161
|
+
const fetchStatus = async () => {
|
|
162
|
+
const response = await fetch(`/raw/.running`);
|
|
163
|
+
const text = await response.text();
|
|
164
|
+
if (text === "") {
|
|
165
|
+
setRunning(true);
|
|
166
|
+
} else {
|
|
167
|
+
setRunning(false);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
fetchStatus();
|
|
171
|
+
interval = setInterval(fetchStatus, 1e3);
|
|
172
|
+
return () => {
|
|
173
|
+
clearInterval(interval);
|
|
174
|
+
};
|
|
175
|
+
}, []);
|
|
176
|
+
return running;
|
|
177
|
+
}
|
|
178
|
+
export {
|
|
179
|
+
findReadme,
|
|
180
|
+
findSlides,
|
|
181
|
+
isSimulationRunning,
|
|
182
|
+
useDirectory,
|
|
183
|
+
useFileContent
|
|
184
|
+
};
|
|
185
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sources":["../../../../plugin/src/client.tsx"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { DirectoryEntry, FileEntry } from \"./lib\";\n\nasync function parsePath(directory: DirectoryEntry, path: string): Promise<{ directory: DirectoryEntry; file: FileEntry | null }> {\n const parts = path === \".\" ? [] : path.split(\"/\").filter(Boolean);\n\n let file = null;\n let currentDir = directory;\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i];\n const isLastPart = i === parts.length - 1;\n\n // Check if this part matches a file (only on last part)\n if (isLastPart) {\n const matchedFile = currentDir.children.find(\n (child) => child.type === \"file\" && child.name === part\n ) as FileEntry | undefined;\n\n if (matchedFile) {\n file = matchedFile;\n break;\n }\n }\n\n // Otherwise, look for a directory\n const nextDir = currentDir.children.find(\n (child) => child.type === \"directory\" && child.name === part\n ) as DirectoryEntry | undefined;\n\n if (!nextDir) {\n throw new Error(`Path not found: ${path}`);\n }\n\n currentDir = nextDir;\n }\n\n return { directory: currentDir, file };\n}\n\nexport function findReadme(directory: DirectoryEntry): FileEntry | null {\n const readme = directory.children.find((child) => \n child.type === \"file\" && \n [\n \"README.md\", \"Readme.md\", \"readme.md\",\n \"README.mdx\", \"Readme.mdx\", \"readme.mdx\"\n ].includes(child.name)\n ) as FileEntry | undefined;\n\n return readme || null;\n}\n\nexport function findSlides(directory: DirectoryEntry): FileEntry | null {\n const readme = directory.children.find((child) =>\n child.type === \"file\" &&\n [\n \"SLIDES.md\", \"Slides.md\", \"slides.md\",\n \"SLIDES.mdx\", \"Slides.mdx\", \"slides.mdx\"\n ].includes(child.name)\n ) as FileEntry | undefined;\n\n return readme || null;\n}\n\n\nexport type DirectoryError =\n | { type: 'config_not_found'; message: string }\n | { type: 'path_not_found'; message: string; status: 404 }\n | { type: 'fetch_error'; message: string }\n | { type: 'parse_error'; message: string };\n\nexport function useDirectory(path: string = \".\") {\n const [directory, setDirectory] = useState<DirectoryEntry | null>(null);\n const [file, setFile] = useState<FileEntry | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<DirectoryError | null>(null);\n\n useEffect(() => {\n setLoading(true);\n setError(null);\n\n (async () => {\n try {\n const response = await fetch(`/raw/.veslx.json`);\n\n if (!response.ok) {\n if (response.status === 404) {\n setError({ type: 'config_not_found', message: '.veslx.json not found' });\n } else {\n setError({ type: 'fetch_error', message: `Failed to fetch: ${response.status} ${response.statusText}` });\n }\n return;\n }\n\n let json;\n try {\n json = await response.json();\n } catch {\n setError({ type: 'parse_error', message: 'Failed to parse .veslx.json' });\n return;\n }\n\n let parsed: { directory: DirectoryEntry; file: FileEntry | null };\n try {\n parsed = await parsePath(json, path);\n } catch {\n setError({ type: 'path_not_found', message: `Path not found: ${path}`, status: 404 });\n return;\n }\n\n parsed.directory.children.sort((a: any, b: any) => {\n let aDate, bDate;\n if (a.children) {\n const readme = findReadme(a);\n if (readme && readme.frontmatter && readme.frontmatter.date) {\n aDate = new Date(readme.frontmatter.date as string | number | Date)\n }\n }\n if (b.children) {\n const readme = findReadme(b);\n if (readme && readme.frontmatter && readme.frontmatter.date) {\n bDate = new Date(readme.frontmatter.date as string | number | Date)\n }\n }\n if (aDate && bDate) {\n return bDate.getTime() - aDate.getTime()\n }\n return 0;\n });\n\n setDirectory(parsed.directory);\n setFile(parsed.file);\n } catch (err: any) {\n setError({ type: 'fetch_error', message: err.message || 'Unknown error' });\n } finally {\n setLoading(false);\n }\n })();\n\n return () => {};\n }, [path]);\n\n return { directory, file, loading, error };\n}\n\nexport function useFileContent(path: string) {\n const [blob, setBlob] = useState<Blob | null>(null);\n const [content, setContent] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const controller = new AbortController();\n setLoading(true);\n setError(null);\n\n (async () => {\n try {\n const res = await fetch(`/raw/${path}`, {\n signal: controller.signal,\n });\n\n if (!res.ok) {\n throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);\n }\n\n const fetchedBlob = await res.blob();\n setBlob(fetchedBlob);\n\n // Try to read as text - some binary files may fail\n try {\n const text = await fetchedBlob.text();\n setContent(text);\n } catch {\n // Binary file - text content not available\n setContent(null);\n }\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n return; // Ignore abort errors\n }\n setError(err instanceof Error ? err.message : 'Unknown error');\n } finally {\n setLoading(false);\n }\n })();\n\n return () => controller.abort();\n }, [path]);\n\n return { blob, content, loading, error };\n}\n\nexport function isSimulationRunning() {\n const [running, setRunning] = useState<boolean>(false);\n\n useEffect(() => {\n let interval: ReturnType<typeof setInterval>;\n\n const fetchStatus = async () => {\n const response = await fetch(`/raw/.running`);\n\n // this is an elaborate workaround to stop devtools logging errors on 404s\n const text = await response.text()\n if (text === \"\") {\n setRunning(true);\n } else {\n setRunning(false);\n }\n };\n\n // Initial fetch\n fetchStatus();\n\n // Poll every second\n interval = setInterval(fetchStatus, 1000);\n\n return () => {\n clearInterval(interval);\n };\n }, []);\n\n return running;\n}"],"names":[],"mappings":"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"}
|