hyperbook 0.96.2 → 0.97.0
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/dist/assets/directive-openscad/client.js +203 -2
- package/dist/assets/directive-openscad/openscad.js +5 -7
- package/dist/assets/directive-openscad/openscad.wasm +0 -0
- package/dist/assets/directive-openscad/style.css +120 -0
- package/dist/assets/directive-openscad/worker.js +49 -0
- package/dist/assets/directive-pyide/client.js +45 -11
- package/dist/index.js +118 -1
- package/dist/locales/de.json +5 -0
- package/dist/locales/en.json +5 -0
- package/package.json +4 -4
|
@@ -19,6 +19,72 @@ hyperbook.openscad = (function () {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
const i18nGet = (key, fallback = key) => hyperbook.i18n?.get(key) || fallback;
|
|
22
|
+
const ABSOLUTE_URL_PATTERN = /^(?:[a-z]+:)?\/\//i;
|
|
23
|
+
|
|
24
|
+
const normalizePath = (path) => {
|
|
25
|
+
if (!path) return "/";
|
|
26
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const constructUrl = (path, basePath, pagePath) => {
|
|
30
|
+
if (path.startsWith("/")) {
|
|
31
|
+
return basePath ? `${basePath}${path}`.replace(/\/+/g, "/") : path;
|
|
32
|
+
}
|
|
33
|
+
return pagePath ? `${pagePath}/${path}`.replace(/\/+/g, "/") : path;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const resolveImportFsPath = (assetPath) => {
|
|
37
|
+
try {
|
|
38
|
+
return new URL(assetPath, "file:///tmp/model.scad").pathname || null;
|
|
39
|
+
} catch (_) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const extractImportAssetPaths = (code) => {
|
|
45
|
+
const paths = new Set();
|
|
46
|
+
const pattern = /import\s*\(\s*(['"])([^'"]+)\1/g;
|
|
47
|
+
let match;
|
|
48
|
+
while ((match = pattern.exec(code || "")) !== null) {
|
|
49
|
+
const path = match[2];
|
|
50
|
+
if (!path) continue;
|
|
51
|
+
if (
|
|
52
|
+
ABSOLUTE_URL_PATTERN.test(path) ||
|
|
53
|
+
path.startsWith("data:") ||
|
|
54
|
+
path.startsWith("blob:")
|
|
55
|
+
) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
paths.add(path);
|
|
59
|
+
}
|
|
60
|
+
return [...paths];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const buildAutoBinaryFiles = (code, basePath, pagePath) => {
|
|
64
|
+
return extractImportAssetPaths(code)
|
|
65
|
+
.map((assetPath) => {
|
|
66
|
+
const dest = resolveImportFsPath(assetPath);
|
|
67
|
+
if (!dest) return null;
|
|
68
|
+
return {
|
|
69
|
+
dest,
|
|
70
|
+
url: constructUrl(assetPath, basePath, pagePath),
|
|
71
|
+
};
|
|
72
|
+
})
|
|
73
|
+
.filter(Boolean);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const mergeBinaryFiles = (baseFiles = [], autoFiles = []) => {
|
|
77
|
+
const merged = new Map();
|
|
78
|
+
for (const file of autoFiles) {
|
|
79
|
+
if (!file?.dest || !file?.url) continue;
|
|
80
|
+
merged.set(file.dest, file);
|
|
81
|
+
}
|
|
82
|
+
for (const file of baseFiles) {
|
|
83
|
+
if (!file?.dest || !file?.url) continue;
|
|
84
|
+
merged.set(file.dest, file);
|
|
85
|
+
}
|
|
86
|
+
return [...merged.values()];
|
|
87
|
+
};
|
|
22
88
|
|
|
23
89
|
const getWorker = async (slot) => {
|
|
24
90
|
if (!window.Worker) {
|
|
@@ -93,11 +159,18 @@ hyperbook.openscad = (function () {
|
|
|
93
159
|
.map((entry) => entry.stderr);
|
|
94
160
|
|
|
95
161
|
// Build parameter UI metadata/markup in the worker to minimize main-thread work.
|
|
96
|
-
const buildParamUiInWorker = async (
|
|
162
|
+
const buildParamUiInWorker = async (
|
|
163
|
+
code,
|
|
164
|
+
libraryNames = [],
|
|
165
|
+
binaryFiles = [],
|
|
166
|
+
currentOverrides = {},
|
|
167
|
+
id = "",
|
|
168
|
+
) => {
|
|
97
169
|
try {
|
|
98
170
|
const result = await callWorker("param", "buildParamForm", {
|
|
99
171
|
code,
|
|
100
172
|
libraryNames,
|
|
173
|
+
binaryFiles,
|
|
101
174
|
currentOverrides,
|
|
102
175
|
id,
|
|
103
176
|
});
|
|
@@ -658,6 +731,16 @@ hyperbook.openscad = (function () {
|
|
|
658
731
|
const id = elem.getAttribute("data-id");
|
|
659
732
|
const libraryNames = (elem.getAttribute("data-library") || "")
|
|
660
733
|
.split(",").map(s => s.trim()).filter(Boolean);
|
|
734
|
+
const binaryFilesData = elem.getAttribute("data-binary-files");
|
|
735
|
+
const basePath = normalizePath(elem.getAttribute("data-base-path") || "/");
|
|
736
|
+
const pagePath = normalizePath(elem.getAttribute("data-page-path") || "");
|
|
737
|
+
const decodeBase64 = (str) => {
|
|
738
|
+
const binaryStr = atob(str);
|
|
739
|
+
const bytes = Uint8Array.from(binaryStr, (c) => c.charCodeAt(0));
|
|
740
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
741
|
+
};
|
|
742
|
+
const initialBinaryFiles = binaryFilesData ? JSON.parse(decodeBase64(binaryFilesData)) : [];
|
|
743
|
+
let userBinaryFiles = Array.isArray(initialBinaryFiles) ? [...initialBinaryFiles] : [];
|
|
661
744
|
|
|
662
745
|
const previewContainer = elem.querySelector(".preview-container");
|
|
663
746
|
const leftSide = elem.querySelector(".left-side");
|
|
@@ -682,6 +765,8 @@ hyperbook.openscad = (function () {
|
|
|
682
765
|
const downloadBtn = elem.querySelector("button.download-stl");
|
|
683
766
|
const resetBtn = elem.querySelector("button.reset");
|
|
684
767
|
const fullscreenBtn = elem.querySelector("button.fullscreen");
|
|
768
|
+
const addBinaryFileBtn = elem.querySelector("button.add-binary-file");
|
|
769
|
+
const binaryFilesList = elem.querySelector(".binary-files-list");
|
|
685
770
|
const bottomButtons = elem.querySelector(".buttons.bottom");
|
|
686
771
|
let downloadFormatSelect = bottomButtons?.querySelector("select.download-format");
|
|
687
772
|
if (!downloadFormatSelect && bottomButtons && downloadBtn) {
|
|
@@ -701,6 +786,97 @@ hyperbook.openscad = (function () {
|
|
|
701
786
|
downloadBtn.textContent = i18nGet("openscad-download", "Download");
|
|
702
787
|
}
|
|
703
788
|
|
|
789
|
+
const normalizeBinaryDest = (dest) => {
|
|
790
|
+
if (typeof dest !== "string") return null;
|
|
791
|
+
const trimmed = dest.trim();
|
|
792
|
+
if (!trimmed) return null;
|
|
793
|
+
return normalizePath(trimmed).replace(/\/+/g, "/");
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
const updateBinaryFilesList = () => {
|
|
797
|
+
if (!binaryFilesList) return;
|
|
798
|
+
binaryFilesList.innerHTML = "";
|
|
799
|
+
|
|
800
|
+
if (userBinaryFiles.length === 0) {
|
|
801
|
+
const emptyMsg = document.createElement("div");
|
|
802
|
+
emptyMsg.className = "binary-files-empty";
|
|
803
|
+
emptyMsg.textContent = i18nGet("openscad-no-binary-files", "No binary files");
|
|
804
|
+
binaryFilesList.appendChild(emptyMsg);
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
userBinaryFiles.forEach((file) => {
|
|
809
|
+
const item = document.createElement("div");
|
|
810
|
+
item.className = "binary-file-item";
|
|
811
|
+
|
|
812
|
+
const icon = document.createElement("span");
|
|
813
|
+
icon.className = "binary-file-icon";
|
|
814
|
+
icon.textContent = "📎";
|
|
815
|
+
item.appendChild(icon);
|
|
816
|
+
|
|
817
|
+
const name = document.createElement("span");
|
|
818
|
+
name.className = "binary-file-name";
|
|
819
|
+
name.textContent = file.dest;
|
|
820
|
+
item.appendChild(name);
|
|
821
|
+
|
|
822
|
+
const deleteBtn = document.createElement("button");
|
|
823
|
+
deleteBtn.className = "binary-file-delete";
|
|
824
|
+
deleteBtn.textContent = "×";
|
|
825
|
+
deleteBtn.title = i18nGet("typst-delete-file", "Delete file");
|
|
826
|
+
deleteBtn.addEventListener("click", () => {
|
|
827
|
+
const confirmMsg = `${i18nGet("typst-delete-confirm", "Delete")} ${file.dest}?`;
|
|
828
|
+
if (!window.confirm(confirmMsg)) return;
|
|
829
|
+
userBinaryFiles = userBinaryFiles.filter((f) => f.dest !== file.dest);
|
|
830
|
+
updateBinaryFilesList();
|
|
831
|
+
scheduleSave();
|
|
832
|
+
void renderPreview();
|
|
833
|
+
});
|
|
834
|
+
item.appendChild(deleteBtn);
|
|
835
|
+
|
|
836
|
+
binaryFilesList.appendChild(item);
|
|
837
|
+
});
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
const handleAddBinaryFile = (event) => {
|
|
841
|
+
event.preventDefault();
|
|
842
|
+
event.stopPropagation();
|
|
843
|
+
|
|
844
|
+
const input = document.createElement("input");
|
|
845
|
+
input.type = "file";
|
|
846
|
+
input.accept = "*/*";
|
|
847
|
+
|
|
848
|
+
input.addEventListener("change", (changeEvent) => {
|
|
849
|
+
const file = changeEvent.target.files?.[0];
|
|
850
|
+
if (!file) return;
|
|
851
|
+
|
|
852
|
+
const dest = normalizeBinaryDest(`/${file.name}`);
|
|
853
|
+
if (!dest) return;
|
|
854
|
+
|
|
855
|
+
if (userBinaryFiles.some((f) => f.dest === dest)) {
|
|
856
|
+
if (!window.confirm(i18nGet("openscad-file-replace", `Replace existing ${dest}?`))) {
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
userBinaryFiles = userBinaryFiles.filter((f) => f.dest !== dest);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const reader = new FileReader();
|
|
863
|
+
reader.onload = (loadEvent) => {
|
|
864
|
+
const url = loadEvent.target?.result;
|
|
865
|
+
if (typeof url !== "string") return;
|
|
866
|
+
userBinaryFiles.push({ dest, url });
|
|
867
|
+
updateBinaryFilesList();
|
|
868
|
+
scheduleSave();
|
|
869
|
+
void renderPreview();
|
|
870
|
+
};
|
|
871
|
+
reader.readAsDataURL(file);
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
input.click();
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
addBinaryFileBtn?.addEventListener("click", handleAddBinaryFile);
|
|
878
|
+
updateBinaryFilesList();
|
|
879
|
+
|
|
704
880
|
// --- Canvas overlay ---
|
|
705
881
|
let overlayDismissTimer = null;
|
|
706
882
|
|
|
@@ -800,6 +976,7 @@ hyperbook.openscad = (function () {
|
|
|
800
976
|
id,
|
|
801
977
|
code: cm?.getValue() || "",
|
|
802
978
|
params: params?.value || "{}",
|
|
979
|
+
binaryFiles: userBinaryFiles,
|
|
803
980
|
...(Number.isFinite(splitHorizontal) && splitHorizontal > 0
|
|
804
981
|
? { splitHorizontal: Math.round(splitHorizontal) }
|
|
805
982
|
: {}),
|
|
@@ -822,6 +999,15 @@ hyperbook.openscad = (function () {
|
|
|
822
999
|
if (params && typeof result.params === "string") {
|
|
823
1000
|
params.value = result.params;
|
|
824
1001
|
}
|
|
1002
|
+
if (Array.isArray(result.binaryFiles)) {
|
|
1003
|
+
userBinaryFiles = result.binaryFiles
|
|
1004
|
+
.map((file) => ({
|
|
1005
|
+
dest: normalizeBinaryDest(file?.dest),
|
|
1006
|
+
url: typeof file?.url === "string" ? file.url : "",
|
|
1007
|
+
}))
|
|
1008
|
+
.filter((file) => file.dest && file.url);
|
|
1009
|
+
updateBinaryFilesList();
|
|
1010
|
+
}
|
|
825
1011
|
if (Number.isFinite(result.splitHorizontal) && result.splitHorizontal > 0) {
|
|
826
1012
|
elem.dataset.splitHorizontal = String(Math.round(result.splitHorizontal));
|
|
827
1013
|
}
|
|
@@ -946,7 +1132,17 @@ hyperbook.openscad = (function () {
|
|
|
946
1132
|
|
|
947
1133
|
// Code is the source of truth — param changes are always synced back to the
|
|
948
1134
|
// code, so we never need stored overrides to win over the code's own values.
|
|
949
|
-
const
|
|
1135
|
+
const resolvedBinaryFiles = mergeBinaryFiles(
|
|
1136
|
+
userBinaryFiles,
|
|
1137
|
+
buildAutoBinaryFiles(code, basePath, pagePath),
|
|
1138
|
+
);
|
|
1139
|
+
const result = await buildParamUiInWorker(
|
|
1140
|
+
code,
|
|
1141
|
+
libraryNames,
|
|
1142
|
+
resolvedBinaryFiles,
|
|
1143
|
+
{},
|
|
1144
|
+
id || "model",
|
|
1145
|
+
);
|
|
950
1146
|
if (buildToken !== latestParamBuildToken) {
|
|
951
1147
|
return false;
|
|
952
1148
|
}
|
|
@@ -1035,10 +1231,15 @@ hyperbook.openscad = (function () {
|
|
|
1035
1231
|
// itself is the single source of truth. Passing -D overrides is both
|
|
1036
1232
|
// redundant and causes stale-value renders when the render fires before
|
|
1037
1233
|
// the next param-build cycle completes.
|
|
1234
|
+
const resolvedBinaryFiles = mergeBinaryFiles(
|
|
1235
|
+
userBinaryFiles,
|
|
1236
|
+
buildAutoBinaryFiles(cm?.getValue() || "", basePath, pagePath),
|
|
1237
|
+
);
|
|
1038
1238
|
const result = await callWorker("render", "render", {
|
|
1039
1239
|
code: cm?.getValue() || "",
|
|
1040
1240
|
format,
|
|
1041
1241
|
libraryNames,
|
|
1242
|
+
binaryFiles: resolvedBinaryFiles,
|
|
1042
1243
|
paramDefinitions: [],
|
|
1043
1244
|
isPreview,
|
|
1044
1245
|
});
|