hyperbook 0.99.0 → 0.100.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.
@@ -249,3 +249,26 @@
249
249
  .directive-abc-music .buttons button:hover {
250
250
  background-color: var(--color-spacer);
251
251
  }
252
+
253
+ /* Icon-only toolbar: the written labels made this row wrap on narrow screens,
254
+ so the wording moved into title/aria-label. */
255
+ .directive-abc-music .buttons.bottom button {
256
+ flex: 0 0 auto;
257
+ min-width: 42px;
258
+ width: 42px;
259
+ padding: 8px 0;
260
+ display: flex;
261
+ align-items: center;
262
+ justify-content: center;
263
+ }
264
+
265
+ .directive-abc-music .buttons.bottom button .icon {
266
+ width: 18px;
267
+ height: 18px;
268
+ display: block;
269
+ }
270
+
271
+ /* Fullscreen sits apart, at the trailing edge of the toolbar. */
272
+ .directive-abc-music .buttons.bottom button.fullscreen {
273
+ margin-inline-start: auto;
274
+ }
@@ -12,7 +12,7 @@
12
12
  background: transparent;
13
13
  }
14
14
 
15
- .directive-archive>.icon {
15
+ .directive-archive > .icon {
16
16
  display: flex;
17
17
  border-right-color: var(--color-spacer);
18
18
  align-items: center;
@@ -22,11 +22,17 @@
22
22
  border-right-width: 1px;
23
23
  }
24
24
 
25
- .directive-archive>.label {
25
+ .directive-archive > .label {
26
26
  flex: 1;
27
27
  padding: 12px 16px;
28
28
  }
29
29
 
30
- .directive-archive>.offline {
30
+ .directive-archive > .offline {
31
31
  text-decoration: line-through;
32
32
  }
33
+
34
+ .directive-archive > .icon .icon {
35
+ width: 26px;
36
+ height: 26px;
37
+ display: block;
38
+ }
@@ -12,7 +12,7 @@
12
12
  background: transparent;
13
13
  }
14
14
 
15
- .directive-download>.icon {
15
+ .directive-download > .icon {
16
16
  display: flex;
17
17
  border-right-color: var(--color-spacer);
18
18
  align-items: center;
@@ -22,11 +22,17 @@
22
22
  border-right-width: 1px;
23
23
  }
24
24
 
25
- .directive-download>.label {
25
+ .directive-download > .label {
26
26
  flex: 1;
27
27
  padding: 12px 16px;
28
28
  }
29
29
 
30
- .directive-download>.offline {
30
+ .directive-download > .offline {
31
31
  text-decoration: line-through;
32
32
  }
33
+
34
+ .directive-download > .icon .icon {
35
+ width: 26px;
36
+ height: 26px;
37
+ display: block;
38
+ }
@@ -34436,12 +34436,68 @@ void main() {
34436
34436
  }
34437
34437
  return { vertices, faces, colors };
34438
34438
  };
34439
+ var splitByColor = (vertices, faces, colorCount) => {
34440
+ const groups = Array.from({ length: colorCount }, () => ({ faceIndices: [] }));
34441
+ faces.forEach((face, faceIdx) => {
34442
+ groups[face.colorIndex].faceIndices.push(faceIdx);
34443
+ });
34444
+ return groups.filter((g) => g.faceIndices.length > 0).map(({ faceIndices }, groupIdx) => {
34445
+ const oldToNew = /* @__PURE__ */ new Map();
34446
+ const groupVertices = [];
34447
+ for (const faceIdx of faceIndices) {
34448
+ for (const vIdx of faces[faceIdx].vertices) {
34449
+ if (!oldToNew.has(vIdx)) {
34450
+ oldToNew.set(vIdx, groupVertices.length);
34451
+ groupVertices.push(vertices[vIdx]);
34452
+ }
34453
+ }
34454
+ }
34455
+ const groupFaces = faceIndices.map((faceIdx) => ({
34456
+ ...faces[faceIdx],
34457
+ vertices: faces[faceIdx].vertices.map((vIdx) => oldToNew.get(vIdx))
34458
+ }));
34459
+ return { vertices: groupVertices, faces: groupFaces };
34460
+ });
34461
+ };
34439
34462
  var exportIndexedPolyhedronTo3mf = (polyhedron) => {
34440
- const objectUuid = createUuid();
34441
34463
  const buildUuid = createUuid();
34442
34464
  const extruderIndexByColorIndex = polyhedron.colors.map(
34443
34465
  (_, idx) => idx % PAINT_COLOR_MAP.length
34444
34466
  );
34467
+ const materialsId = 1;
34468
+ const components = splitByColor(polyhedron.vertices, polyhedron.faces, polyhedron.colors.length);
34469
+ const objectXmls = components.map((comp, compIdx) => {
34470
+ const objectId = compIdx + 2;
34471
+ const objectUuid = createUuid();
34472
+ const colorIndex = comp.faces[0]?.colorIndex ?? 0;
34473
+ const paintColor = PAINT_COLOR_MAP[extruderIndexByColorIndex[colorIndex]];
34474
+ return [
34475
+ `<object id="${objectId}" name="OpenSCAD Model ${compIdx + 1}" type="model" p:UUID="${objectUuid}" pid="${materialsId}" pindex="${colorIndex}">`,
34476
+ "<mesh>",
34477
+ "<vertices>",
34478
+ ...comp.vertices.map(
34479
+ (vertex2) => `<vertex x="${vertex2.x}" y="${vertex2.y}" z="${vertex2.z}" />`
34480
+ ),
34481
+ "</vertices>",
34482
+ "<triangles>",
34483
+ ...comp.faces.map((face) => {
34484
+ const [v1, v2, v3] = face.vertices;
34485
+ const attrs = [`v1="${v1}"`, `v2="${v2}"`, `v3="${v3}"`];
34486
+ if (paintColor) {
34487
+ attrs.push(`paint_color="${paintColor}"`);
34488
+ }
34489
+ return `<triangle ${attrs.join(" ")} />`;
34490
+ }),
34491
+ "</triangles>",
34492
+ "</mesh>",
34493
+ "</object>"
34494
+ ].join("\n");
34495
+ });
34496
+ const buildItems = components.map((_, compIdx) => {
34497
+ const objectId = compIdx + 2;
34498
+ const objectUuid = createUuid();
34499
+ return `<item objectid="${objectId}" p:UUID="${objectUuid}"/>`;
34500
+ });
34445
34501
  const modelXml = [
34446
34502
  '<?xml version="1.0" encoding="utf-8"?>',
34447
34503
  '<model unit="millimeter" xml:lang="en-US" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02" xmlns:p="http://schemas.microsoft.com/3dmanufacturing/production/2015/06">',
@@ -34449,37 +34505,15 @@ void main() {
34449
34505
  '<meta name="slic3rpe:Version3mf" value="1"/>',
34450
34506
  '<meta name="slic3rpe:MmPaintingVersion" value="1"/>',
34451
34507
  "<resources>",
34452
- '<basematerials id="2">',
34508
+ `<basematerials id="${materialsId}">`,
34453
34509
  ...polyhedron.colors.map(
34454
34510
  (color, i) => `<base name="color_${i}" displaycolor="${colorToDisplayColor(color)}"/>`
34455
34511
  ),
34456
34512
  "</basematerials>",
34457
- `<object id="1" name="OpenSCAD Model" type="model" p:UUID="${objectUuid}" pid="2" pindex="0">`,
34458
- "<mesh>",
34459
- "<vertices>",
34460
- ...polyhedron.vertices.map(
34461
- (vertex2) => `<vertex x="${vertex2.x}" y="${vertex2.y}" z="${vertex2.z}" />`
34462
- ),
34463
- "</vertices>",
34464
- "<triangles>",
34465
- ...polyhedron.faces.map((face) => {
34466
- const [v1, v2, v3] = face.vertices;
34467
- const attrs = [`v1="${v1}"`, `v2="${v2}"`, `v3="${v3}"`];
34468
- if (face.colorIndex > 0) {
34469
- attrs.push(`pid="2"`, `p1="${face.colorIndex}"`);
34470
- }
34471
- const paintColor = PAINT_COLOR_MAP[extruderIndexByColorIndex[face.colorIndex]];
34472
- if (paintColor) {
34473
- attrs.push(`paint_color="${paintColor}"`);
34474
- }
34475
- return `<triangle ${attrs.join(" ")} />`;
34476
- }),
34477
- "</triangles>",
34478
- "</mesh>",
34479
- "</object>",
34513
+ ...objectXmls,
34480
34514
  "</resources>",
34481
34515
  `<build p:UUID="${buildUuid}">`,
34482
- `<item objectid="1" p:UUID="${objectUuid}"/>`,
34516
+ ...buildItems,
34483
34517
  "</build>",
34484
34518
  "</model>"
34485
34519
  ].join("\n");
@@ -34661,10 +34695,7 @@ void main() {
34661
34695
  const applySplitSize = (rawSize, isHorizontal) => {
34662
34696
  const total = isHorizontal ? elem.clientWidth : elem.clientHeight;
34663
34697
  const splitterSize = isHorizontal ? splitter.offsetWidth : splitter.offsetHeight;
34664
- const maxSize = Math.max(
34665
- minPanelSize,
34666
- total - splitterSize - minPanelSize
34667
- );
34698
+ const maxSize = Math.max(minPanelSize, total - splitterSize - minPanelSize);
34668
34699
  const clamped = Math.max(minPanelSize, Math.min(rawSize, maxSize));
34669
34700
  previewContainer.style.flex = `0 0 ${clamped}px`;
34670
34701
  return clamped;
@@ -34722,7 +34753,6 @@ void main() {
34722
34753
  if (!elem || !button) return;
34723
34754
  const isFullscreen = document.fullscreenElement === elem;
34724
34755
  const label = i18nGet("ide-fullscreen-enter", "Fullscreen");
34725
- button.textContent = "\u26F6";
34726
34756
  button.title = label;
34727
34757
  button.setAttribute("aria-label", label);
34728
34758
  button.classList.toggle("active", isFullscreen);
@@ -34802,7 +34832,9 @@ void main() {
34802
34832
  bottomButtons.insertBefore(downloadFormatSelect, downloadBtn);
34803
34833
  }
34804
34834
  if (downloadBtn) {
34805
- downloadBtn.textContent = i18nGet("openscad-download", "Download");
34835
+ const downloadLabel = i18nGet("openscad-download", "Download");
34836
+ downloadBtn.title = downloadLabel;
34837
+ downloadBtn.setAttribute("aria-label", downloadLabel);
34806
34838
  }
34807
34839
  const normalizeBinaryDest = (dest) => {
34808
34840
  if (typeof dest !== "string") return null;
@@ -35222,14 +35254,19 @@ void main() {
35222
35254
  userBinaryFiles,
35223
35255
  buildAutoBinaryFiles(cm?.getValue() || "", basePath, pagePath)
35224
35256
  );
35225
- const result = await callWorker("render", "render", {
35226
- code: cm?.getValue() || "",
35227
- format,
35228
- libraryNames: libraryNames2,
35229
- binaryFiles: resolvedBinaryFiles,
35230
- paramDefinitions: [],
35231
- isPreview
35232
- }, _scriptBase);
35257
+ const result = await callWorker(
35258
+ "render",
35259
+ "render",
35260
+ {
35261
+ code: cm?.getValue() || "",
35262
+ format,
35263
+ libraryNames: libraryNames2,
35264
+ binaryFiles: resolvedBinaryFiles,
35265
+ paramDefinitions: [],
35266
+ isPreview
35267
+ },
35268
+ _scriptBase
35269
+ );
35233
35270
  const stderr = getInvocationStderr(result);
35234
35271
  if (result?.error || result?.exitCode !== 0) {
35235
35272
  const error = new Error(
@@ -131,7 +131,9 @@
131
131
  }
132
132
 
133
133
  @keyframes openscad-spin {
134
- to { transform: rotate(360deg); }
134
+ to {
135
+ transform: rotate(360deg);
136
+ }
135
137
  }
136
138
 
137
139
  /* Splitter between canvas and parameters card */
@@ -246,6 +248,31 @@
246
248
  border-top-right-radius: 0;
247
249
  }
248
250
 
251
+ /* Icon-only toolbar: the written labels (Reset/Copy/Download) made this row
252
+ wrap on narrow screens, so the wording moved into title/aria-label. */
253
+ .directive-openscad .buttons.bottom button {
254
+ flex: 0 0 auto;
255
+ min-width: 42px;
256
+ width: 42px;
257
+ padding: 8px 0;
258
+ display: flex;
259
+ align-items: center;
260
+ justify-content: center;
261
+ }
262
+
263
+ .directive-openscad .buttons.bottom button .icon {
264
+ width: 18px;
265
+ height: 18px;
266
+ display: block;
267
+ }
268
+
269
+ /* Fullscreen sits apart, at the trailing edge of the toolbar. */
270
+ .directive-openscad .buttons.bottom button.fullscreen {
271
+ margin-inline-start: auto;
272
+ border-right: none;
273
+ border-left: 1px solid var(--color-spacer);
274
+ }
275
+
249
276
  .directive-openscad button {
250
277
  flex: 1;
251
278
  padding: 8px 16px;
@@ -303,8 +330,14 @@
303
330
 
304
331
  .directive-openscad .binary-files-section summary .summary-indicator {
305
332
  transition: transform 0.2s;
306
- display: inline-block;
307
- font-size: 0.75rem;
333
+ display: inline-flex;
334
+ align-items: center;
335
+ }
336
+
337
+ .directive-openscad .binary-files-section summary .summary-indicator .icon {
338
+ width: 14px;
339
+ height: 14px;
340
+ display: block;
308
341
  }
309
342
 
310
343
  .directive-openscad .binary-files-section[open] summary .summary-indicator {
@@ -29,8 +29,13 @@ hyperbook.p5 = (function () {
29
29
 
30
30
  const applySplitSize = (rawSize, isHorizontal) => {
31
31
  const total = isHorizontal ? elem.clientWidth : elem.clientHeight;
32
- const splitterSize = isHorizontal ? splitter.offsetWidth : splitter.offsetHeight;
33
- const maxSize = Math.max(minPanelSize, total - splitterSize - minPanelSize);
32
+ const splitterSize = isHorizontal
33
+ ? splitter.offsetWidth
34
+ : splitter.offsetHeight;
35
+ const maxSize = Math.max(
36
+ minPanelSize,
37
+ total - splitterSize - minPanelSize,
38
+ );
34
39
  const clamped = Math.max(minPanelSize, Math.min(rawSize, maxSize));
35
40
  container.style.flex = `0 0 ${clamped}px`;
36
41
  return clamped;
@@ -90,7 +95,6 @@ hyperbook.p5 = (function () {
90
95
  if (!elem || !button) return;
91
96
  const isFullscreen = document.fullscreenElement === elem;
92
97
  const label = hyperbook.i18n.get("ide-fullscreen-enter");
93
- button.textContent = "⛶";
94
98
  button.title = label;
95
99
  button.setAttribute("aria-label", label);
96
100
  button.classList.toggle("active", isFullscreen);
@@ -219,10 +223,7 @@ hyperbook.p5 = (function () {
219
223
  const observer = new MutationObserver((mutations) => {
220
224
  mutations.forEach((mutation) => {
221
225
  mutation.addedNodes.forEach((node) => {
222
- if (
223
- node.nodeType === 1 &&
224
- node.classList.contains("directive-p5")
225
- ) {
226
+ if (node.nodeType === 1 && node.classList.contains("directive-p5")) {
226
227
  initElement(node);
227
228
  }
228
229
  });
@@ -100,6 +100,31 @@ code-input {
100
100
  border-top-right-radius: 0;
101
101
  }
102
102
 
103
+ /* Icon-only toolbar: the written labels (Reset/Copy/Download) made this row
104
+ wrap on narrow screens, so the wording moved into title/aria-label. */
105
+ .directive-p5 .buttons.bottom button {
106
+ flex: 0 0 auto;
107
+ min-width: 42px;
108
+ width: 42px;
109
+ padding: 8px 0;
110
+ display: flex;
111
+ align-items: center;
112
+ justify-content: center;
113
+ }
114
+
115
+ .directive-p5 .buttons.bottom button .icon {
116
+ width: 18px;
117
+ height: 18px;
118
+ display: block;
119
+ }
120
+
121
+ /* Fullscreen sits apart, at the trailing edge of the toolbar. */
122
+ .directive-p5 .buttons.bottom button.fullscreen {
123
+ margin-inline-start: auto;
124
+ border-right: none;
125
+ border-left: 1px solid var(--color-spacer);
126
+ }
127
+
103
128
  .directive-p5 button {
104
129
  flex: 1;
105
130
  padding: 8px 16px;
@@ -12,7 +12,7 @@
12
12
  border-width: 1px;
13
13
  }
14
14
 
15
- .directive-protect .password-input>input {
15
+ .directive-protect .password-input > input {
16
16
  text-align: center;
17
17
  border-radius: 10px;
18
18
  font-size: 1.125rem;
@@ -20,7 +20,7 @@
20
20
  border-style: solid;
21
21
  }
22
22
 
23
- .directive-protect .password-input>.icon {
23
+ .directive-protect .password-input > .icon {
24
24
  position: absolute;
25
25
  user-select: none;
26
26
  left: -14px;
@@ -33,17 +33,17 @@
33
33
  margin-bottom: 10px;
34
34
  }
35
35
 
36
- .directive-protect .password-input>.description {
36
+ .directive-protect .password-input > .description {
37
37
  margin-bottom: 10px;
38
38
  }
39
39
 
40
- .directive-protect .password-input>input {
40
+ .directive-protect .password-input > input {
41
41
  color: var(--color-text);
42
42
  background: var(--color-nav);
43
43
  border-color: var(--color-spacer);
44
44
  }
45
45
 
46
- .directive-protect .password-input>.icon {
46
+ .directive-protect .password-input > .icon {
47
47
  background: white;
48
48
  border-color: var(--color-spacer);
49
49
  }
@@ -52,3 +52,9 @@
52
52
  visibility: hidden;
53
53
  display: none;
54
54
  }
55
+
56
+ .directive-protect .password-input > .icon .icon {
57
+ width: 16px;
58
+ height: 16px;
59
+ display: block;
60
+ }