Skip to content

Commit e20fce2

Browse files
committed
transferred rest of files and copied in components
1 parent f9bec85 commit e20fce2

File tree

23 files changed

+974
-258
lines changed

23 files changed

+974
-258
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
---
2+
const {
3+
label = 'Cube',
4+
type = 'geojson',
5+
data = '',
6+
width = 320,
7+
height = 220,
8+
frameless = false,
9+
showLabel = true,
10+
showSource = false,
11+
} = Astro.props;
12+
const id = `cube-${Math.random().toString(36).slice(2)}`;
13+
---
14+
15+
<div class={`cube-preview ${frameless ? 'is-frameless' : ''}`} id={id} data-type={type}>
16+
{showLabel && <div class="cube-preview-header">{label}</div>}
17+
<canvas width={width} height={height} aria-label={`${label} preview`}></canvas>
18+
<script type="application/json" data-cube set:html={data}></script>
19+
{showSource && (
20+
<details class="cube-preview-source">
21+
<summary>Show source</summary>
22+
<pre><code>{data}</code></pre>
23+
</details>
24+
)}
25+
</div>
26+
27+
<style>
28+
.cube-preview {
29+
border: 1px solid var(--sl-color-hairline);
30+
border-radius: 12px;
31+
background: color-mix(in srgb, var(--bp-bg) 92%, transparent);
32+
padding: 10px;
33+
max-width: 520px;
34+
}
35+
.cube-preview.is-frameless {
36+
border: none;
37+
background: transparent;
38+
padding: 0;
39+
}
40+
.cube-preview-header {
41+
font-size: 0.85rem;
42+
font-weight: 600;
43+
margin: 0 0 8px;
44+
color: var(--bp-text);
45+
letter-spacing: 0.01em;
46+
}
47+
.cube-preview.is-frameless .cube-preview-header {
48+
margin-bottom: 6px;
49+
}
50+
.cube-preview canvas {
51+
width: 100%;
52+
height: auto;
53+
display: block;
54+
background: transparent;
55+
border-radius: 8px;
56+
}
57+
.cube-preview.is-frameless canvas {
58+
border-radius: 0;
59+
}
60+
.cube-preview-source {
61+
margin-top: 10px;
62+
}
63+
.cube-preview-source summary {
64+
cursor: pointer;
65+
color: var(--sl-color-text-accent);
66+
font-weight: 600;
67+
}
68+
</style>
69+
70+
<script>
71+
(() => {
72+
73+
const rotate = (p, rx, ry) => {
74+
const [x, y, z] = p;
75+
const cosy = Math.cos(ry);
76+
const siny = Math.sin(ry);
77+
const cosx = Math.cos(rx);
78+
const sinx = Math.sin(rx);
79+
const x1 = x * cosy + z * siny;
80+
const z1 = -x * siny + z * cosy;
81+
const y1 = y * cosx - z1 * sinx;
82+
const z2 = y * sinx + z1 * cosx;
83+
return [x1, y1, z2];
84+
};
85+
86+
const parseGeoJSON = (raw) => {
87+
const data = JSON.parse(raw);
88+
const edges = [];
89+
const points = [];
90+
const addEdge = (a, b) => edges.push([a, b]);
91+
const addPoint = (p) => {
92+
points.push(p);
93+
return points.length - 1;
94+
};
95+
for (const f of data.features || []) {
96+
const rings = f.geometry?.coordinates || [];
97+
for (const ring of rings) {
98+
let prev = null;
99+
for (const coord of ring) {
100+
const idx = addPoint(coord);
101+
if (prev !== null) addEdge(prev, idx);
102+
prev = idx;
103+
}
104+
}
105+
}
106+
return { points, edges };
107+
};
108+
109+
const parseTopoJSON = (raw) => {
110+
const data = JSON.parse(raw);
111+
const arcs = data.arcs || [];
112+
const points = [];
113+
const edges = [];
114+
const addPoint = (p) => {
115+
points.push(p);
116+
return points.length - 1;
117+
};
118+
const addEdge = (a, b) => edges.push([a, b]);
119+
const resolveArc = (arcIndex) => {
120+
const reversed = arcIndex < 0;
121+
const arc = arcs[Math.abs(arcIndex)];
122+
const coords = reversed ? [...arc].reverse() : arc;
123+
return coords;
124+
};
125+
const geoms = data.objects?.cube?.geometries || [];
126+
for (const g of geoms) {
127+
for (const ring of g.arcs || []) {
128+
const arc = resolveArc(ring[0]);
129+
let prev = null;
130+
for (const coord of arc) {
131+
const idx = addPoint(coord);
132+
if (prev !== null) addEdge(prev, idx);
133+
prev = idx;
134+
}
135+
}
136+
}
137+
return { points, edges };
138+
};
139+
140+
const parseSTL = (raw) => {
141+
const points = [];
142+
const edges = [];
143+
const addPoint = (p) => {
144+
points.push(p);
145+
return points.length - 1;
146+
};
147+
const addEdge = (a, b) => edges.push([a, b]);
148+
const vertices = [];
149+
raw.split('\n').forEach((line) => {
150+
const m = line.trim().match(/^vertex\\s+([-\\d.eE]+)\\s+([-\\d.eE]+)\\s+([-\\d.eE]+)$/);
151+
if (m) vertices.push([Number(m[1]), Number(m[2]), Number(m[3])]);
152+
});
153+
for (let i = 0; i + 2 < vertices.length; i += 3) {
154+
const a = addPoint(vertices[i]);
155+
const b = addPoint(vertices[i + 1]);
156+
const c = addPoint(vertices[i + 2]);
157+
addEdge(a, b);
158+
addEdge(b, c);
159+
addEdge(c, a);
160+
}
161+
if (!edges.length) return parseMermaid();
162+
return { points, edges };
163+
};
164+
165+
const parseMermaid = () => {
166+
const points = [
167+
[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0],
168+
[0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1],
169+
];
170+
const edges = [
171+
[0,1],[1,2],[2,3],[3,0],
172+
[4,5],[5,6],[6,7],[7,4],
173+
[0,4],[1,5],[2,6],[3,7],
174+
];
175+
return { points, edges };
176+
};
177+
178+
const render = (el) => {
179+
const canvas = el.querySelector('canvas');
180+
const ctx = canvas.getContext('2d');
181+
const raw = el.querySelector('[data-cube]')?.textContent?.trim() || '';
182+
const type = el.dataset.type || 'geojson';
183+
let model;
184+
if (type === 'geojson') model = parseGeoJSON(raw);
185+
else if (type === 'topojson') model = parseTopoJSON(raw);
186+
else if (type === 'stl') model = parseSTL(raw);
187+
else model = parseMermaid();
188+
189+
const rotated = model.points.map((p) => rotate(p, 0.6, -0.7));
190+
const xs = rotated.map((p) => p[0]);
191+
const ys = rotated.map((p) => p[1]);
192+
const minX = Math.min(...xs);
193+
const maxX = Math.max(...xs);
194+
const minY = Math.min(...ys);
195+
const maxY = Math.max(...ys);
196+
const pad = 18;
197+
const scale = Math.min(
198+
(canvas.width - pad * 2) / (maxX - minX || 1),
199+
(canvas.height - pad * 2) / (maxY - minY || 1)
200+
);
201+
202+
const project = (p) => {
203+
const x = (p[0] - minX) * scale + pad;
204+
const y = (p[1] - minY) * scale + pad;
205+
return [x, canvas.height - y];
206+
};
207+
208+
ctx.clearRect(0, 0, canvas.width, canvas.height);
209+
const colorMap = {
210+
mermaid: 'rgba(64, 255, 200, 0.95)',
211+
geojson: 'rgba(59, 130, 246, 0.95)',
212+
topojson: 'rgba(16, 185, 129, 0.95)',
213+
stl: 'rgba(249, 115, 22, 0.95)',
214+
};
215+
ctx.lineWidth = 2;
216+
ctx.strokeStyle = colorMap[type] || 'rgba(64, 255, 200, 0.95)';
217+
for (const [a, b] of model.edges) {
218+
const [x1, y1] = project(rotated[a]);
219+
const [x2, y2] = project(rotated[b]);
220+
ctx.beginPath();
221+
ctx.moveTo(x1, y1);
222+
ctx.lineTo(x2, y2);
223+
ctx.stroke();
224+
}
225+
};
226+
227+
const init = () => document.querySelectorAll('.cube-preview').forEach(render);
228+
229+
if (document.readyState === 'loading') {
230+
document.addEventListener('DOMContentLoaded', init, { once: true });
231+
} else {
232+
init();
233+
}
234+
})();
235+
</script>
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<script>
2+
export let node;
3+
4+
const rootFiles = [
5+
{
6+
type: 'folder',
7+
name: 'public',
8+
expanded: true,
9+
files: [
10+
{
11+
type: 'folder',
12+
name: 'project',
13+
expanded: true,
14+
files: [
15+
{ type: 'file', name: 'main.js' },
16+
{ type: 'file', name: 'package.json' },
17+
],
18+
},
19+
],
20+
},
21+
{ type: 'file', name: '.env' },
22+
{ type: 'file', name: 'index.html' },
23+
{ type: 'file', name: 'package.json' },
24+
{
25+
type: 'folder',
26+
name: 'src',
27+
expanded: true,
28+
files: [
29+
{ type: 'file', name: 'main.js' },
30+
{ type: 'file', name: 'style.css' },
31+
{ type: 'file', name: 'utils.js' },
32+
],
33+
},
34+
];
35+
36+
if (!node) {
37+
node = { type: 'folder', name: '<project>', files: rootFiles, expanded: true };
38+
}
39+
40+
let open = !!node.expanded;
41+
const toggle = () => (open = !open);
42+
43+
const extIcon = (name) => {
44+
if (name.startsWith('.') && name.length > 1) return name.slice(1).toLowerCase();
45+
const parts = name.split('.');
46+
const ext = parts.length > 1 ? parts.pop().toLowerCase() : '';
47+
return ext;
48+
};
49+
</script>
50+
51+
{#if node.type === 'folder'}
52+
{#if node.name === '<project>' || node.name === '/'}
53+
<div class="bp-tree-card">
54+
<button type="button" class="bp-tree-row" on:click={toggle} aria-expanded={open}>
55+
<span class="bp-tree-caret">{open ? '' : ''}</span>
56+
<span class="bp-tree-icon folder" aria-hidden="true">
57+
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
58+
<path d="M3 7a2 2 0 0 1 2-2h5l2 2h9a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z" />
59+
</svg>
60+
</span>
61+
<span class="bp-tree-name">{node.name}</span>
62+
</button>
63+
{#if open}
64+
<ul class="bp-tree-children">
65+
{#each node.files as child}
66+
<li>
67+
<svelte:self node={child} />
68+
</li>
69+
{/each}
70+
</ul>
71+
{/if}
72+
</div>
73+
{:else}
74+
<button type="button" class="bp-tree-row" on:click={toggle} aria-expanded={open}>
75+
<span class="bp-tree-caret">{open ? '' : ''}</span>
76+
<span class="bp-tree-icon folder" aria-hidden="true">
77+
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
78+
<path d="M3 7a2 2 0 0 1 2-2h5l2 2h9a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z" />
79+
</svg>
80+
</span>
81+
<span class="bp-tree-name">{node.name}</span>
82+
</button>
83+
{#if open}
84+
<ul class="bp-tree-children">
85+
{#each node.files as child}
86+
<li>
87+
<svelte:self node={child} />
88+
</li>
89+
{/each}
90+
</ul>
91+
{/if}
92+
{/if}
93+
{:else}
94+
<div class="bp-tree-row bp-tree-file">
95+
<span class="bp-tree-caret" aria-hidden="true"></span>
96+
<span class="bp-tree-icon file" data-ext={extIcon(node.name)} aria-hidden="true"></span>
97+
<span class="bp-tree-name">{node.name}</span>
98+
</div>
99+
{/if}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script>
2+
export let name = '';
3+
export let files = [];
4+
export let expanded = false;
5+
6+
let open = expanded;
7+
const toggle = () => (open = !open);
8+
</script>
9+
10+
<div class="bp-tree-folder">
11+
<button type="button" class="bp-tree-row" on:click={toggle} aria-expanded={open}>
12+
<span class="bp-tree-caret">{open ? '' : ''}</span>
13+
<span class="bp-tree-name">{name}</span>
14+
</button>
15+
16+
{#if open}
17+
<ul class="bp-tree-children">
18+
{#each files as file}
19+
{#if file.type === 'folder'}
20+
<li>
21+
<Folder name={file.name} files={file.files} expanded={file.expanded} />
22+
</li>
23+
{:else}
24+
<li class="bp-tree-row bp-tree-file">
25+
<span class="bp-tree-bullet">•</span>
26+
<span class="bp-tree-name">{file.name}</span>
27+
</li>
28+
{/if}
29+
{/each}
30+
</ul>
31+
{/if}
32+
</div>
File renamed without changes.

0 commit comments

Comments
 (0)