castle-web-cli 0.4.166 → 0.4.167
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/agent.d.ts +1 -2
- package/dist/agent.js +22 -13
- package/dist/api.js +16 -1
- package/dist/imports.js +19 -4
- package/dist/localPaths.d.ts +2 -0
- package/dist/localPaths.js +6 -0
- package/dist/serve.js +5 -1
- package/dist/serveSecurity.d.ts +12 -0
- package/dist/serveSecurity.js +42 -0
- package/dist/shell/assets/{index-CG9TrvCm.js → index-C8M-p00y.js} +36 -36
- package/dist/shell/index.html +1 -1
- package/kits/base/castle.json +64 -2
- package/kits/base/editors/ImageViewer.js +259 -0
- package/kits/base/editors/MediaPlayer.js +90 -0
- package/kits/base/editors/imageViewer.css +95 -0
- package/kits/base/editors/mediaPlayer.css +58 -0
- package/kits/multiplayer-2d/castle.json +1 -1
- package/kits/multiplayer-2d/package-lock.json +4 -1
- package/kits/multiplayer-3d/castle.json +1 -1
- package/kits/multiplayer-3d/package-lock.json +1 -1
- package/kits/physics-2d/behaviors/Joints.jsx +11 -5
- package/kits/physics-2d/behaviors/Sprite.jsx +6 -21
- package/kits/physics-2d/castle.json +1 -61
- package/kits/physics-2d/editors/BlueprintLibrary.jsx +8 -68
- package/kits/physics-2d/editors/SingleEditor.jsx +3 -10
- package/kits/physics-2d/editors/editorRegistry.jsx +36 -2
- package/kits/physics-2d/engine/art.js +25 -1
- package/kits/physics-2d/engine/artThumbnail.jsx +92 -0
- package/kits/physics-2d/engine/files.js +4 -15
- package/kits/physics-2d/engine/spriteField.jsx +176 -0
- package/kits/physics-2d/engine/ui.module.css +136 -0
- package/kits/physics-2d/package-lock.json +5 -0
- package/kits/physics-2d/pnpm-lock.yaml +96 -3
- package/kits/physics-3d/castle.json +1 -1
- package/kits/physics-3d/package-lock.json +1 -1
- package/kits/physics-3d/pnpm-lock.yaml +88 -3
- package/kits/real-time/castle.json +1 -1
- package/kits/real-time/package.json +3 -0
- package/kits/turn-based/castle.json +1 -1
- package/kits/turn-based/package-lock.json +1 -1
- package/package.json +1 -1
- package/kits/base/node_modules/.package-lock.json +0 -28
- package/kits/base/node_modules/castle-web-fonts/README.md +0 -114
- package/kits/base/node_modules/castle-web-fonts/dist/catalog.d.ts +0 -18
- package/kits/base/node_modules/castle-web-fonts/dist/catalog.js +0 -142
- package/kits/base/node_modules/castle-web-fonts/dist/face.d.ts +0 -1
- package/kits/base/node_modules/castle-web-fonts/dist/face.js +0 -52
- package/kits/base/node_modules/castle-web-fonts/dist/faces/Bore.d.ts +0 -3
- package/kits/base/node_modules/castle-web-fonts/dist/faces/Bore.js +0 -10
- package/kits/base/node_modules/castle-web-fonts/dist/faces/CourierPrime.d.ts +0 -3
- package/kits/base/node_modules/castle-web-fonts/dist/faces/CourierPrime.js +0 -10
- package/kits/base/node_modules/castle-web-fonts/dist/faces/DMSans.d.ts +0 -3
- package/kits/base/node_modules/castle-web-fonts/dist/faces/DMSans.js +0 -10
- package/kits/base/node_modules/castle-web-fonts/dist/faces/Glacier.d.ts +0 -3
- package/kits/base/node_modules/castle-web-fonts/dist/faces/Glacier.js +0 -10
- package/kits/base/node_modules/castle-web-fonts/dist/faces/HelicoCentrica.d.ts +0 -3
- package/kits/base/node_modules/castle-web-fonts/dist/faces/HelicoCentrica.js +0 -10
- package/kits/base/node_modules/castle-web-fonts/dist/faces/Piazzolla.d.ts +0 -3
- package/kits/base/node_modules/castle-web-fonts/dist/faces/Piazzolla.js +0 -10
- package/kits/base/node_modules/castle-web-fonts/dist/faces/Synco.d.ts +0 -3
- package/kits/base/node_modules/castle-web-fonts/dist/faces/Synco.js +0 -10
- package/kits/base/node_modules/castle-web-fonts/dist/faces/Tektur.d.ts +0 -3
- package/kits/base/node_modules/castle-web-fonts/dist/faces/Tektur.js +0 -10
- package/kits/base/node_modules/castle-web-fonts/dist/faces/YatraOne.d.ts +0 -3
- package/kits/base/node_modules/castle-web-fonts/dist/faces/YatraOne.js +0 -10
- package/kits/base/node_modules/castle-web-fonts/dist/index.d.ts +0 -16
- package/kits/base/node_modules/castle-web-fonts/dist/index.js +0 -15
- package/kits/base/node_modules/castle-web-fonts/licenses/CC-BY-ND-4.0.txt +0 -393
- package/kits/base/node_modules/castle-web-fonts/licenses/FONTS.md +0 -64
- package/kits/base/node_modules/castle-web-fonts/licenses/OFL-1.1.txt +0 -95
- package/kits/base/node_modules/castle-web-fonts/package.json +0 -47
- package/kits/base/node_modules/castle-web-fonts/woff2/Bore.woff2 +0 -0
- package/kits/base/node_modules/castle-web-fonts/woff2/CourierPrime.woff2 +0 -0
- package/kits/base/node_modules/castle-web-fonts/woff2/DMSans.woff2 +0 -0
- package/kits/base/node_modules/castle-web-fonts/woff2/Glacier.woff2 +0 -0
- package/kits/base/node_modules/castle-web-fonts/woff2/HelicoCentrica.woff2 +0 -0
- package/kits/base/node_modules/castle-web-fonts/woff2/Piazzolla.woff2 +0 -0
- package/kits/base/node_modules/castle-web-fonts/woff2/SOURCE.txt +0 -2
- package/kits/base/node_modules/castle-web-fonts/woff2/Synco.woff2 +0 -0
- package/kits/base/node_modules/castle-web-fonts/woff2/Tektur.woff2 +0 -0
- package/kits/base/node_modules/castle-web-fonts/woff2/YatraOne.woff2 +0 -0
- package/kits/physics-2d/editors/ImageViewer.jsx +0 -206
- package/kits/physics-2d/editors/MediaPlayer.jsx +0 -57
- package/kits/physics-2d/editors/mediaFile.js +0 -31
- package/kits/physics-2d/editors/mediaViewer.module.css +0 -128
package/dist/shell/index.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<link rel="icon" type="image/png" sizes="32x32" href="/__castle/ide/favicon-32x32.png" />
|
|
11
11
|
<link rel="icon" type="image/png" sizes="16x16" href="/__castle/ide/favicon-16x16.png" />
|
|
12
12
|
<link rel="icon" href="/__castle/ide/favicon.ico" sizes="any" />
|
|
13
|
-
<script type="module" crossorigin src="/__castle/ide/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/__castle/ide/assets/index-C8M-p00y.js"></script>
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/__castle/ide/assets/index-CIRBt1qf.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
package/kits/base/castle.json
CHANGED
|
@@ -1,13 +1,75 @@
|
|
|
1
1
|
{
|
|
2
2
|
"editor": {
|
|
3
3
|
"hiddenPaths": [],
|
|
4
|
-
"visiblePaths": []
|
|
4
|
+
"visiblePaths": [],
|
|
5
|
+
"fileTypes": [
|
|
6
|
+
{
|
|
7
|
+
"ext": ".png",
|
|
8
|
+
"label": "Image",
|
|
9
|
+
"icon": "image",
|
|
10
|
+
"editor": "editors/ImageViewer.js"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"ext": ".jpg",
|
|
14
|
+
"label": "Image",
|
|
15
|
+
"icon": "image",
|
|
16
|
+
"editor": "editors/ImageViewer.js"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"ext": ".jpeg",
|
|
20
|
+
"label": "Image",
|
|
21
|
+
"icon": "image",
|
|
22
|
+
"editor": "editors/ImageViewer.js"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"ext": ".gif",
|
|
26
|
+
"label": "Image",
|
|
27
|
+
"icon": "image",
|
|
28
|
+
"editor": "editors/ImageViewer.js"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"ext": ".webp",
|
|
32
|
+
"label": "Image",
|
|
33
|
+
"icon": "image",
|
|
34
|
+
"editor": "editors/ImageViewer.js"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"ext": ".svg",
|
|
38
|
+
"label": "Image",
|
|
39
|
+
"icon": "image",
|
|
40
|
+
"editor": "editors/ImageViewer.js"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"ext": ".mp3",
|
|
44
|
+
"label": "Audio",
|
|
45
|
+
"icon": "music",
|
|
46
|
+
"editor": "editors/MediaPlayer.js"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"ext": ".wav",
|
|
50
|
+
"label": "Audio",
|
|
51
|
+
"icon": "music",
|
|
52
|
+
"editor": "editors/MediaPlayer.js"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"ext": ".m4a",
|
|
56
|
+
"label": "Audio",
|
|
57
|
+
"icon": "music",
|
|
58
|
+
"editor": "editors/MediaPlayer.js"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"ext": ".mp4",
|
|
62
|
+
"label": "Video",
|
|
63
|
+
"icon": "film",
|
|
64
|
+
"editor": "editors/MediaPlayer.js"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
5
67
|
},
|
|
6
68
|
"autoUpdateWhenImported": true,
|
|
7
69
|
"title": "base",
|
|
8
70
|
"deckId": "yRcmH4_aYllE",
|
|
9
71
|
"cardId": "zkFdingmIm3m",
|
|
10
|
-
"publishedVersion": "2026-09-
|
|
72
|
+
"publishedVersion": "2026-09-05T00:07:27.320Z",
|
|
11
73
|
"provides": [
|
|
12
74
|
"castle-web-sdk"
|
|
13
75
|
]
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// Read-only image viewer for the base kit. Opened for the image extensions
|
|
2
|
+
// declared in this kit's castle.json, so an uploaded .png/.jpg/.svg opens as a
|
|
3
|
+
// picture instead of a wall of binary in the code editor — including on bare
|
|
4
|
+
// (`--kit none`) decks that have no framework kit.
|
|
5
|
+
//
|
|
6
|
+
// Framework-free mount editor (see editorHost.js): default export is
|
|
7
|
+
// `mount(el, props)`. Physics-2d's DeclaredEditor wraps that shape when it
|
|
8
|
+
// loads an import's module.
|
|
9
|
+
//
|
|
10
|
+
// It VIEWS: fit / zoom / pan, at 1:1 when you want to count pixels. Nothing
|
|
11
|
+
// here writes.
|
|
12
|
+
|
|
13
|
+
import { fileUrl, onFilesChanged } from 'castle-web-sdk';
|
|
14
|
+
import './imageViewer.css';
|
|
15
|
+
|
|
16
|
+
const MIN_SCALE = 0.05;
|
|
17
|
+
const MAX_SCALE = 64;
|
|
18
|
+
const PIXELATE_ABOVE = 2.5;
|
|
19
|
+
const WHEEL_ZOOM_PER_PX = 0.0016;
|
|
20
|
+
|
|
21
|
+
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
22
|
+
|
|
23
|
+
function basename(path) {
|
|
24
|
+
const i = path.lastIndexOf('/');
|
|
25
|
+
return i < 0 ? path : path.slice(i + 1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function fitScale(stage, natural) {
|
|
29
|
+
if (!stage.width || !stage.height || !natural.width || !natural.height) return 1;
|
|
30
|
+
const margin = 24;
|
|
31
|
+
const sx = (stage.width - margin) / natural.width;
|
|
32
|
+
const sy = (stage.height - margin) / natural.height;
|
|
33
|
+
return clamp(Math.min(sx, sy), MIN_SCALE, MAX_SCALE);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function zoomAbout(view, factor, px, py) {
|
|
37
|
+
const scale = clamp(view.scale * factor, MIN_SCALE, MAX_SCALE);
|
|
38
|
+
const k = scale / view.scale;
|
|
39
|
+
return { scale, x: px - (px - view.x) * k, y: py - (py - view.y) * k };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function mediaUrl(path, version) {
|
|
43
|
+
const base = fileUrl(path);
|
|
44
|
+
return version === 0 ? base : `${base}&v=${version}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {HTMLElement} el
|
|
49
|
+
* @param {{ path: string }} props
|
|
50
|
+
*/
|
|
51
|
+
export default function mount(el, { path }) {
|
|
52
|
+
el.textContent = '';
|
|
53
|
+
el.style.cssText = 'display:flex;flex:1;width:100%;height:100%;min-height:100vh;';
|
|
54
|
+
|
|
55
|
+
const root = document.createElement('div');
|
|
56
|
+
root.className = 'castle-image-viewer';
|
|
57
|
+
root.innerHTML = `
|
|
58
|
+
<div class="castle-image-viewer-toolbar">
|
|
59
|
+
<button type="button" data-act="zoom-out" aria-label="Zoom out">−</button>
|
|
60
|
+
<button type="button" data-act="zoom-in" aria-label="Zoom in">+</button>
|
|
61
|
+
<button type="button" data-act="fit">Fit</button>
|
|
62
|
+
<button type="button" data-act="one">1:1</button>
|
|
63
|
+
<span class="castle-image-viewer-readout" data-readout></span>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="castle-image-viewer-stage" data-stage>
|
|
66
|
+
<img class="castle-image-viewer-image" alt="" data-image />
|
|
67
|
+
<div class="castle-image-viewer-message" data-message>Loading…</div>
|
|
68
|
+
</div>
|
|
69
|
+
`;
|
|
70
|
+
el.appendChild(root);
|
|
71
|
+
|
|
72
|
+
const stage = root.querySelector('[data-stage]');
|
|
73
|
+
const image = root.querySelector('[data-image]');
|
|
74
|
+
const message = root.querySelector('[data-message]');
|
|
75
|
+
const readout = root.querySelector('[data-readout]');
|
|
76
|
+
|
|
77
|
+
let view = { scale: 1, x: 0, y: 0 };
|
|
78
|
+
let natural = { width: 0, height: 0 };
|
|
79
|
+
let touched = false;
|
|
80
|
+
let dragging = false;
|
|
81
|
+
let version = 0;
|
|
82
|
+
let status = 'loading';
|
|
83
|
+
const stageSize = { width: 0, height: 0 };
|
|
84
|
+
|
|
85
|
+
const applyTransform = () => {
|
|
86
|
+
image.style.width = natural.width ? `${natural.width}px` : '';
|
|
87
|
+
image.style.height = natural.height ? `${natural.height}px` : '';
|
|
88
|
+
image.style.transform = `translate(-50%, -50%) translate(${view.x}px, ${view.y}px) scale(${view.scale})`;
|
|
89
|
+
image.style.visibility = status === 'ready' ? 'visible' : 'hidden';
|
|
90
|
+
image.classList.toggle('pixelated', view.scale >= PIXELATE_ABOVE);
|
|
91
|
+
stage.classList.toggle('dragging', dragging);
|
|
92
|
+
readout.textContent =
|
|
93
|
+
status === 'ready' ? `${natural.width}×${natural.height} · ${Math.round(view.scale * 100)}%` : '';
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const setStatus = (next) => {
|
|
97
|
+
status = next;
|
|
98
|
+
if (next === 'loading') {
|
|
99
|
+
message.textContent = 'Loading…';
|
|
100
|
+
message.className = 'castle-image-viewer-message';
|
|
101
|
+
message.hidden = false;
|
|
102
|
+
} else if (next === 'error') {
|
|
103
|
+
message.textContent = `Could not load ${basename(path)}.`;
|
|
104
|
+
message.className = 'castle-image-viewer-message error';
|
|
105
|
+
message.hidden = false;
|
|
106
|
+
} else {
|
|
107
|
+
message.hidden = true;
|
|
108
|
+
}
|
|
109
|
+
applyTransform();
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const fit = () => {
|
|
113
|
+
touched = false;
|
|
114
|
+
view = { scale: fitScale(stageSize, natural), x: 0, y: 0 };
|
|
115
|
+
applyTransform();
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const zoomAt = (factor, clientX, clientY) => {
|
|
119
|
+
const rect = stage.getBoundingClientRect();
|
|
120
|
+
const px = clientX - rect.left - rect.width / 2;
|
|
121
|
+
const py = clientY - rect.top - rect.height / 2;
|
|
122
|
+
touched = true;
|
|
123
|
+
view = zoomAbout(view, factor, px, py);
|
|
124
|
+
applyTransform();
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const zoomBy = (factor) => {
|
|
128
|
+
touched = true;
|
|
129
|
+
view = zoomAbout(view, factor, 0, 0);
|
|
130
|
+
applyTransform();
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const setScale = (scale) => {
|
|
134
|
+
touched = true;
|
|
135
|
+
view = zoomAbout(view, scale / view.scale, 0, 0);
|
|
136
|
+
applyTransform();
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const measure = () => {
|
|
140
|
+
stageSize.width = stage.clientWidth;
|
|
141
|
+
stageSize.height = stage.clientHeight;
|
|
142
|
+
if (!touched && status === 'ready') fit();
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const load = () => {
|
|
146
|
+
setStatus('loading');
|
|
147
|
+
image.alt = basename(path);
|
|
148
|
+
image.src = mediaUrl(path, version);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
image.onload = () => {
|
|
152
|
+
natural = {
|
|
153
|
+
width: image.naturalWidth || 300,
|
|
154
|
+
height: image.naturalHeight || 300,
|
|
155
|
+
};
|
|
156
|
+
setStatus('ready');
|
|
157
|
+
fit();
|
|
158
|
+
};
|
|
159
|
+
image.onerror = () => setStatus('error');
|
|
160
|
+
|
|
161
|
+
root.querySelector('[data-act="zoom-out"]').onclick = () => zoomBy(1 / 1.4);
|
|
162
|
+
root.querySelector('[data-act="zoom-in"]').onclick = () => zoomBy(1.4);
|
|
163
|
+
root.querySelector('[data-act="fit"]').onclick = () => fit();
|
|
164
|
+
root.querySelector('[data-act="one"]').onclick = () => setScale(1);
|
|
165
|
+
|
|
166
|
+
const onWheel = (event) => {
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
zoomAt(Math.exp(-event.deltaY * WHEEL_ZOOM_PER_PX), event.clientX, event.clientY);
|
|
169
|
+
};
|
|
170
|
+
stage.addEventListener('wheel', onWheel, { passive: false });
|
|
171
|
+
|
|
172
|
+
stage.onpointerdown = (event) => {
|
|
173
|
+
if (event.pointerType === 'touch' || event.button !== 0) return;
|
|
174
|
+
stage.setPointerCapture(event.pointerId);
|
|
175
|
+
dragging = true;
|
|
176
|
+
applyTransform();
|
|
177
|
+
};
|
|
178
|
+
stage.onpointermove = (event) => {
|
|
179
|
+
if (!dragging) return;
|
|
180
|
+
touched = true;
|
|
181
|
+
view = { ...view, x: view.x + event.movementX, y: view.y + event.movementY };
|
|
182
|
+
applyTransform();
|
|
183
|
+
};
|
|
184
|
+
const endDrag = () => {
|
|
185
|
+
dragging = false;
|
|
186
|
+
applyTransform();
|
|
187
|
+
};
|
|
188
|
+
stage.onpointerup = endDrag;
|
|
189
|
+
stage.onpointercancel = endDrag;
|
|
190
|
+
stage.ondblclick = () => fit();
|
|
191
|
+
|
|
192
|
+
// Two-finger pinch / pan on touch.
|
|
193
|
+
const active = new Map();
|
|
194
|
+
let lastPinch = null;
|
|
195
|
+
stage.addEventListener(
|
|
196
|
+
'pointerdown',
|
|
197
|
+
(event) => {
|
|
198
|
+
if (event.pointerType !== 'touch') return;
|
|
199
|
+
active.set(event.pointerId, { x: event.clientX, y: event.clientY });
|
|
200
|
+
if (active.size === 2) {
|
|
201
|
+
const [a, b] = [...active.values()];
|
|
202
|
+
lastPinch = {
|
|
203
|
+
dist: Math.hypot(b.x - a.x, b.y - a.y),
|
|
204
|
+
cx: (a.x + b.x) / 2,
|
|
205
|
+
cy: (a.y + b.y) / 2,
|
|
206
|
+
};
|
|
207
|
+
endDrag();
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
true,
|
|
211
|
+
);
|
|
212
|
+
stage.addEventListener(
|
|
213
|
+
'pointermove',
|
|
214
|
+
(event) => {
|
|
215
|
+
if (!active.has(event.pointerId)) return;
|
|
216
|
+
active.set(event.pointerId, { x: event.clientX, y: event.clientY });
|
|
217
|
+
if (active.size !== 2 || !lastPinch) return;
|
|
218
|
+
event.stopPropagation();
|
|
219
|
+
const [a, b] = [...active.values()];
|
|
220
|
+
const dist = Math.hypot(b.x - a.x, b.y - a.y);
|
|
221
|
+
const cx = (a.x + b.x) / 2;
|
|
222
|
+
const cy = (a.y + b.y) / 2;
|
|
223
|
+
const factor = lastPinch.dist > 0 ? dist / lastPinch.dist : 1;
|
|
224
|
+
zoomAt(factor, cx, cy);
|
|
225
|
+
touched = true;
|
|
226
|
+
view = { ...view, x: view.x + (cx - lastPinch.cx), y: view.y + (cy - lastPinch.cy) };
|
|
227
|
+
applyTransform();
|
|
228
|
+
lastPinch = { dist, cx, cy };
|
|
229
|
+
},
|
|
230
|
+
true,
|
|
231
|
+
);
|
|
232
|
+
const endTouch = (event) => {
|
|
233
|
+
active.delete(event.pointerId);
|
|
234
|
+
if (active.size < 2) lastPinch = null;
|
|
235
|
+
};
|
|
236
|
+
stage.addEventListener('pointerup', endTouch, true);
|
|
237
|
+
stage.addEventListener('pointercancel', endTouch, true);
|
|
238
|
+
|
|
239
|
+
const resizeObserver =
|
|
240
|
+
typeof ResizeObserver !== 'undefined' ? new ResizeObserver(measure) : null;
|
|
241
|
+
resizeObserver?.observe(stage);
|
|
242
|
+
measure();
|
|
243
|
+
|
|
244
|
+
const offFiles = onFilesChanged((event) => {
|
|
245
|
+
if (event.changes.some((change) => change.path === path)) {
|
|
246
|
+
version += 1;
|
|
247
|
+
load();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
load();
|
|
252
|
+
|
|
253
|
+
return () => {
|
|
254
|
+
offFiles?.();
|
|
255
|
+
resizeObserver?.disconnect();
|
|
256
|
+
stage.removeEventListener('wheel', onWheel);
|
|
257
|
+
el.textContent = '';
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Read-only audio / video player for the base kit. Opened for the sound and
|
|
2
|
+
// video extensions declared in this kit's castle.json, so an uploaded .mp3 or
|
|
3
|
+
// .mp4 can be played in place — including on bare (`--kit none`) decks.
|
|
4
|
+
//
|
|
5
|
+
// Framework-free mount editor (see editorHost.js). The transport is the
|
|
6
|
+
// browser's `<audio controls>` / `<video controls>`; this file is the frame
|
|
7
|
+
// around that element. Nothing here writes.
|
|
8
|
+
|
|
9
|
+
import { fileUrl, onFilesChanged } from 'castle-web-sdk';
|
|
10
|
+
import './mediaPlayer.css';
|
|
11
|
+
|
|
12
|
+
const VIDEO_EXTS = ['.mp4'];
|
|
13
|
+
|
|
14
|
+
function basename(path) {
|
|
15
|
+
const i = path.lastIndexOf('/');
|
|
16
|
+
return i < 0 ? path : path.slice(i + 1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isVideoPath(path) {
|
|
20
|
+
const lower = path.toLowerCase();
|
|
21
|
+
return VIDEO_EXTS.some((ext) => lower.endsWith(ext));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function mediaUrl(path, version) {
|
|
25
|
+
const base = fileUrl(path);
|
|
26
|
+
return version === 0 ? base : `${base}&v=${version}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {HTMLElement} el
|
|
31
|
+
* @param {{ path: string }} props
|
|
32
|
+
*/
|
|
33
|
+
export default function mount(el, { path }) {
|
|
34
|
+
el.textContent = '';
|
|
35
|
+
el.style.cssText = 'display:flex;flex:1;width:100%;height:100%;min-height:100vh;';
|
|
36
|
+
|
|
37
|
+
const video = isVideoPath(path);
|
|
38
|
+
const root = document.createElement('div');
|
|
39
|
+
root.className = 'castle-media-player';
|
|
40
|
+
root.innerHTML = `
|
|
41
|
+
<div class="castle-media-player-panel ${video ? 'video' : 'audio'}">
|
|
42
|
+
<div class="castle-media-player-name" data-name></div>
|
|
43
|
+
<${video ? 'video' : 'audio'}
|
|
44
|
+
class="castle-media-player-${video ? 'video' : 'audio'}"
|
|
45
|
+
data-media
|
|
46
|
+
controls
|
|
47
|
+
playsinline
|
|
48
|
+
></${video ? 'video' : 'audio'}>
|
|
49
|
+
<div class="castle-media-player-error" data-error hidden></div>
|
|
50
|
+
</div>
|
|
51
|
+
`;
|
|
52
|
+
el.appendChild(root);
|
|
53
|
+
|
|
54
|
+
const nameEl = root.querySelector('[data-name]');
|
|
55
|
+
const media = root.querySelector('[data-media]');
|
|
56
|
+
const errorEl = root.querySelector('[data-error]');
|
|
57
|
+
let version = 0;
|
|
58
|
+
|
|
59
|
+
// Metadata only for audio: opening shouldn't pull the whole file until play.
|
|
60
|
+
// Video preloads enough for a first-frame poster so the panel isn't black.
|
|
61
|
+
media.preload = video ? 'auto' : 'metadata';
|
|
62
|
+
|
|
63
|
+
const load = () => {
|
|
64
|
+
nameEl.textContent = basename(path);
|
|
65
|
+
errorEl.hidden = true;
|
|
66
|
+
errorEl.textContent = '';
|
|
67
|
+
media.src = mediaUrl(path, version);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
media.onerror = () => {
|
|
71
|
+
errorEl.textContent = `Could not play ${basename(path)} — this browser may not support its format.`;
|
|
72
|
+
errorEl.hidden = false;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const offFiles = onFilesChanged((event) => {
|
|
76
|
+
if (event.changes.some((change) => change.path === path)) {
|
|
77
|
+
version += 1;
|
|
78
|
+
load();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
load();
|
|
83
|
+
|
|
84
|
+
return () => {
|
|
85
|
+
offFiles?.();
|
|
86
|
+
media.removeAttribute('src');
|
|
87
|
+
media.load();
|
|
88
|
+
el.textContent = '';
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/* Read-only image viewer chrome: toolbar over a stage that fills the panel. */
|
|
2
|
+
|
|
3
|
+
.castle-image-viewer {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
width: 100%;
|
|
7
|
+
height: 100%;
|
|
8
|
+
min-height: 100vh;
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
color: #e6e6ea;
|
|
11
|
+
background: #161616;
|
|
12
|
+
font: 13px system-ui, sans-serif;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.castle-image-viewer-toolbar {
|
|
16
|
+
flex: 0 0 auto;
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 8px;
|
|
20
|
+
padding: 6px 10px;
|
|
21
|
+
border-bottom: 1px solid #2a2a33;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.castle-image-viewer-toolbar button {
|
|
25
|
+
appearance: none;
|
|
26
|
+
border: 1px solid #3a3a44;
|
|
27
|
+
background: #22222a;
|
|
28
|
+
color: inherit;
|
|
29
|
+
border-radius: 6px;
|
|
30
|
+
padding: 6px 10px;
|
|
31
|
+
font: inherit;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.castle-image-viewer-toolbar button:active {
|
|
36
|
+
background: #2c2c36;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.castle-image-viewer-readout {
|
|
40
|
+
margin-left: auto;
|
|
41
|
+
font-size: 12px;
|
|
42
|
+
opacity: 0.7;
|
|
43
|
+
font-variant-numeric: tabular-nums;
|
|
44
|
+
white-space: nowrap;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.castle-image-viewer-stage {
|
|
48
|
+
position: relative;
|
|
49
|
+
flex: 1 1 auto;
|
|
50
|
+
min-height: 0;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
background: repeating-conic-gradient(#1b1b21 0% 25%, #23232b 0% 50%) 50% / 20px 20px;
|
|
53
|
+
touch-action: none;
|
|
54
|
+
cursor: grab;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.castle-image-viewer-stage.dragging {
|
|
58
|
+
cursor: grabbing;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.castle-image-viewer-image {
|
|
62
|
+
position: absolute;
|
|
63
|
+
top: 50%;
|
|
64
|
+
left: 50%;
|
|
65
|
+
transform-origin: center;
|
|
66
|
+
user-select: none;
|
|
67
|
+
-webkit-user-drag: none;
|
|
68
|
+
pointer-events: none;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.castle-image-viewer-image.pixelated {
|
|
72
|
+
image-rendering: pixelated;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.castle-image-viewer-message {
|
|
76
|
+
position: absolute;
|
|
77
|
+
inset: 0;
|
|
78
|
+
display: flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
justify-content: center;
|
|
81
|
+
padding: 16px;
|
|
82
|
+
text-align: center;
|
|
83
|
+
opacity: 0.7;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Author `display: flex` above beats the UA `[hidden] { display: none }`, so
|
|
87
|
+
the loading label stayed painted after the image was ready. */
|
|
88
|
+
.castle-image-viewer-message[hidden] {
|
|
89
|
+
display: none;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.castle-image-viewer-message.error {
|
|
93
|
+
color: #e86a73;
|
|
94
|
+
opacity: 1;
|
|
95
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* Read-only audio / video player chrome. Native <audio>/<video controls>
|
|
2
|
+
handle transport; this only frames them. */
|
|
3
|
+
|
|
4
|
+
.castle-media-player {
|
|
5
|
+
display: flex;
|
|
6
|
+
flex: 1;
|
|
7
|
+
width: 100%;
|
|
8
|
+
height: 100%;
|
|
9
|
+
min-height: 100vh;
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
padding: 20px;
|
|
14
|
+
color: #e6e6ea;
|
|
15
|
+
background: #161616;
|
|
16
|
+
font: 13px system-ui, sans-serif;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.castle-media-player-panel {
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
gap: 10px;
|
|
23
|
+
min-width: 0;
|
|
24
|
+
width: 100%;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.castle-media-player-panel.audio {
|
|
28
|
+
max-width: 420px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.castle-media-player-panel.video {
|
|
32
|
+
max-width: 100%;
|
|
33
|
+
max-height: 100%;
|
|
34
|
+
min-height: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.castle-media-player-name {
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
text-overflow: ellipsis;
|
|
40
|
+
white-space: nowrap;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.castle-media-player-audio {
|
|
44
|
+
width: 100%;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.castle-media-player-video {
|
|
48
|
+
min-height: 0;
|
|
49
|
+
max-width: 100%;
|
|
50
|
+
max-height: 100%;
|
|
51
|
+
background: #000;
|
|
52
|
+
border-radius: 4px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.castle-media-player-error {
|
|
56
|
+
font-size: 12px;
|
|
57
|
+
color: #e86a73;
|
|
58
|
+
}
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"../../sdk": {
|
|
31
31
|
"name": "castle-web-sdk",
|
|
32
|
-
"version": "0.4.
|
|
32
|
+
"version": "0.4.26",
|
|
33
33
|
"dev": true,
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"eslint": "^9.0.0",
|
|
@@ -151,6 +151,7 @@
|
|
|
151
151
|
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
|
152
152
|
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
|
153
153
|
"license": "MIT",
|
|
154
|
+
"peer": true,
|
|
154
155
|
"dependencies": {
|
|
155
156
|
"@dnd-kit/accessibility": "^3.1.1",
|
|
156
157
|
"@dnd-kit/utilities": "^3.2.2",
|
|
@@ -307,6 +308,7 @@
|
|
|
307
308
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
|
|
308
309
|
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
|
|
309
310
|
"license": "MIT",
|
|
311
|
+
"peer": true,
|
|
310
312
|
"engines": {
|
|
311
313
|
"node": ">=0.10.0"
|
|
312
314
|
}
|
|
@@ -316,6 +318,7 @@
|
|
|
316
318
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
|
|
317
319
|
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
|
|
318
320
|
"license": "MIT",
|
|
321
|
+
"peer": true,
|
|
319
322
|
"dependencies": {
|
|
320
323
|
"scheduler": "^0.27.0"
|
|
321
324
|
},
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"autoUpdateWhenImported": true,
|
|
34
34
|
"deckId": "ZwA_P_-VO-Qh",
|
|
35
35
|
"cardId": "bvRmCzyUpt_a",
|
|
36
|
-
"publishedVersion": "2026-09-
|
|
36
|
+
"publishedVersion": "2026-09-05T00:07:32.934Z",
|
|
37
37
|
"main": "main.jsx",
|
|
38
38
|
"server": {
|
|
39
39
|
"main": "code/server/index.js",
|