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.
Files changed (85) hide show
  1. package/dist/agent.d.ts +1 -2
  2. package/dist/agent.js +22 -13
  3. package/dist/api.js +16 -1
  4. package/dist/imports.js +19 -4
  5. package/dist/localPaths.d.ts +2 -0
  6. package/dist/localPaths.js +6 -0
  7. package/dist/serve.js +5 -1
  8. package/dist/serveSecurity.d.ts +12 -0
  9. package/dist/serveSecurity.js +42 -0
  10. package/dist/shell/assets/{index-CG9TrvCm.js → index-C8M-p00y.js} +36 -36
  11. package/dist/shell/index.html +1 -1
  12. package/kits/base/castle.json +64 -2
  13. package/kits/base/editors/ImageViewer.js +259 -0
  14. package/kits/base/editors/MediaPlayer.js +90 -0
  15. package/kits/base/editors/imageViewer.css +95 -0
  16. package/kits/base/editors/mediaPlayer.css +58 -0
  17. package/kits/multiplayer-2d/castle.json +1 -1
  18. package/kits/multiplayer-2d/package-lock.json +4 -1
  19. package/kits/multiplayer-3d/castle.json +1 -1
  20. package/kits/multiplayer-3d/package-lock.json +1 -1
  21. package/kits/physics-2d/behaviors/Joints.jsx +11 -5
  22. package/kits/physics-2d/behaviors/Sprite.jsx +6 -21
  23. package/kits/physics-2d/castle.json +1 -61
  24. package/kits/physics-2d/editors/BlueprintLibrary.jsx +8 -68
  25. package/kits/physics-2d/editors/SingleEditor.jsx +3 -10
  26. package/kits/physics-2d/editors/editorRegistry.jsx +36 -2
  27. package/kits/physics-2d/engine/art.js +25 -1
  28. package/kits/physics-2d/engine/artThumbnail.jsx +92 -0
  29. package/kits/physics-2d/engine/files.js +4 -15
  30. package/kits/physics-2d/engine/spriteField.jsx +176 -0
  31. package/kits/physics-2d/engine/ui.module.css +136 -0
  32. package/kits/physics-2d/package-lock.json +5 -0
  33. package/kits/physics-2d/pnpm-lock.yaml +96 -3
  34. package/kits/physics-3d/castle.json +1 -1
  35. package/kits/physics-3d/package-lock.json +1 -1
  36. package/kits/physics-3d/pnpm-lock.yaml +88 -3
  37. package/kits/real-time/castle.json +1 -1
  38. package/kits/real-time/package.json +3 -0
  39. package/kits/turn-based/castle.json +1 -1
  40. package/kits/turn-based/package-lock.json +1 -1
  41. package/package.json +1 -1
  42. package/kits/base/node_modules/.package-lock.json +0 -28
  43. package/kits/base/node_modules/castle-web-fonts/README.md +0 -114
  44. package/kits/base/node_modules/castle-web-fonts/dist/catalog.d.ts +0 -18
  45. package/kits/base/node_modules/castle-web-fonts/dist/catalog.js +0 -142
  46. package/kits/base/node_modules/castle-web-fonts/dist/face.d.ts +0 -1
  47. package/kits/base/node_modules/castle-web-fonts/dist/face.js +0 -52
  48. package/kits/base/node_modules/castle-web-fonts/dist/faces/Bore.d.ts +0 -3
  49. package/kits/base/node_modules/castle-web-fonts/dist/faces/Bore.js +0 -10
  50. package/kits/base/node_modules/castle-web-fonts/dist/faces/CourierPrime.d.ts +0 -3
  51. package/kits/base/node_modules/castle-web-fonts/dist/faces/CourierPrime.js +0 -10
  52. package/kits/base/node_modules/castle-web-fonts/dist/faces/DMSans.d.ts +0 -3
  53. package/kits/base/node_modules/castle-web-fonts/dist/faces/DMSans.js +0 -10
  54. package/kits/base/node_modules/castle-web-fonts/dist/faces/Glacier.d.ts +0 -3
  55. package/kits/base/node_modules/castle-web-fonts/dist/faces/Glacier.js +0 -10
  56. package/kits/base/node_modules/castle-web-fonts/dist/faces/HelicoCentrica.d.ts +0 -3
  57. package/kits/base/node_modules/castle-web-fonts/dist/faces/HelicoCentrica.js +0 -10
  58. package/kits/base/node_modules/castle-web-fonts/dist/faces/Piazzolla.d.ts +0 -3
  59. package/kits/base/node_modules/castle-web-fonts/dist/faces/Piazzolla.js +0 -10
  60. package/kits/base/node_modules/castle-web-fonts/dist/faces/Synco.d.ts +0 -3
  61. package/kits/base/node_modules/castle-web-fonts/dist/faces/Synco.js +0 -10
  62. package/kits/base/node_modules/castle-web-fonts/dist/faces/Tektur.d.ts +0 -3
  63. package/kits/base/node_modules/castle-web-fonts/dist/faces/Tektur.js +0 -10
  64. package/kits/base/node_modules/castle-web-fonts/dist/faces/YatraOne.d.ts +0 -3
  65. package/kits/base/node_modules/castle-web-fonts/dist/faces/YatraOne.js +0 -10
  66. package/kits/base/node_modules/castle-web-fonts/dist/index.d.ts +0 -16
  67. package/kits/base/node_modules/castle-web-fonts/dist/index.js +0 -15
  68. package/kits/base/node_modules/castle-web-fonts/licenses/CC-BY-ND-4.0.txt +0 -393
  69. package/kits/base/node_modules/castle-web-fonts/licenses/FONTS.md +0 -64
  70. package/kits/base/node_modules/castle-web-fonts/licenses/OFL-1.1.txt +0 -95
  71. package/kits/base/node_modules/castle-web-fonts/package.json +0 -47
  72. package/kits/base/node_modules/castle-web-fonts/woff2/Bore.woff2 +0 -0
  73. package/kits/base/node_modules/castle-web-fonts/woff2/CourierPrime.woff2 +0 -0
  74. package/kits/base/node_modules/castle-web-fonts/woff2/DMSans.woff2 +0 -0
  75. package/kits/base/node_modules/castle-web-fonts/woff2/Glacier.woff2 +0 -0
  76. package/kits/base/node_modules/castle-web-fonts/woff2/HelicoCentrica.woff2 +0 -0
  77. package/kits/base/node_modules/castle-web-fonts/woff2/Piazzolla.woff2 +0 -0
  78. package/kits/base/node_modules/castle-web-fonts/woff2/SOURCE.txt +0 -2
  79. package/kits/base/node_modules/castle-web-fonts/woff2/Synco.woff2 +0 -0
  80. package/kits/base/node_modules/castle-web-fonts/woff2/Tektur.woff2 +0 -0
  81. package/kits/base/node_modules/castle-web-fonts/woff2/YatraOne.woff2 +0 -0
  82. package/kits/physics-2d/editors/ImageViewer.jsx +0 -206
  83. package/kits/physics-2d/editors/MediaPlayer.jsx +0 -57
  84. package/kits/physics-2d/editors/mediaFile.js +0 -31
  85. package/kits/physics-2d/editors/mediaViewer.module.css +0 -128
@@ -1,206 +0,0 @@
1
- // Read-only image viewer. Opened for the image extensions the deck declares as
2
- // kit-edited (castle.json `fileTypes`), so an uploaded .png/.jpg/.svg opens as a
3
- // picture instead of a wall of binary in the code editor.
4
- //
5
- // It VIEWS: fit / zoom / pan, at 1:1 when you want to count pixels. Nothing here
6
- // writes -- editing an image is the pixel editor's job (.pxart), and a photo the
7
- // user uploaded is not something this kit knows how to modify.
8
-
9
- import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
10
- import { EditorBody, IconButton, styles as uiStyles } from '../engine/ui';
11
- import { useTwoFingerZoomPan } from '../engine/gestureZoomPan';
12
- import { basename } from '../engine/files';
13
- import { useMediaUrl } from './mediaFile';
14
- import styles from './mediaViewer.module.css';
15
-
16
- const MIN_SCALE = 0.05;
17
- const MAX_SCALE = 64;
18
- // Past this the image is being inspected, not previewed: drop smoothing so
19
- // pixels read as pixels.
20
- const PIXELATE_ABOVE = 2.5;
21
- const WHEEL_ZOOM_PER_PX = 0.0016;
22
-
23
- const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
24
-
25
- // Zoom `view` by `factor` about a point in STAGE coordinates measured from the
26
- // stage centre (the transform's origin), holding whatever is under that point
27
- // still. `x`/`y` are the image centre's offset from the stage centre.
28
- function zoomAbout(view, factor, px, py) {
29
- const scale = clamp(view.scale * factor, MIN_SCALE, MAX_SCALE);
30
- const k = scale / view.scale;
31
- return { scale, x: px - (px - view.x) * k, y: py - (py - view.y) * k };
32
- }
33
-
34
- // Scale that fits the whole image in the stage, with a little breathing room.
35
- // Small images are scaled UP to fill: at 16x16 a "true size" preview is a speck.
36
- function fitScale(stage, natural) {
37
- if (!stage.width || !stage.height || !natural.width || !natural.height) return 1;
38
- const margin = 24;
39
- const sx = (stage.width - margin) / natural.width;
40
- const sy = (stage.height - margin) / natural.height;
41
- return clamp(Math.min(sx, sy), MIN_SCALE, MAX_SCALE);
42
- }
43
-
44
- // The pan/zoom state for the stage, plus the input handlers that drive it:
45
- // wheel to zoom about the cursor, drag to pan, two-finger pinch/pan on touch.
46
- // Refits whenever the stage resizes or a different image loads, until the user
47
- // takes over (`touched`) -- then their framing is theirs to keep.
48
- function useImageView(stageRef, natural) {
49
- const [view, setView] = useState({ scale: 1, x: 0, y: 0 });
50
- const [dragging, setDragging] = useState(false);
51
- const touched = useRef(false);
52
- const stageSize = useRef({ width: 0, height: 0 });
53
-
54
- const fit = useCallback(() => {
55
- touched.current = false;
56
- setView({ scale: fitScale(stageSize.current, natural), x: 0, y: 0 });
57
- }, [natural]);
58
-
59
- useLayoutEffect(() => {
60
- const stage = stageRef.current;
61
- if (!stage || typeof ResizeObserver === 'undefined') return undefined;
62
- const measure = () => {
63
- stageSize.current = { width: stage.clientWidth, height: stage.clientHeight };
64
- if (!touched.current) fit();
65
- };
66
- measure();
67
- const observer = new ResizeObserver(measure);
68
- observer.observe(stage);
69
- return () => observer.disconnect();
70
- }, [stageRef, fit]);
71
-
72
- // Zoom about a client-space point (cursor, pinch centroid).
73
- const zoomAt = useCallback((factor, clientX, clientY) => {
74
- const rect = stageRef.current?.getBoundingClientRect();
75
- if (!rect) return;
76
- touched.current = true;
77
- const px = clientX - rect.left - rect.width / 2;
78
- const py = clientY - rect.top - rect.height / 2;
79
- setView((current) => zoomAbout(current, factor, px, py));
80
- }, [stageRef]);
81
-
82
- // Zoom about the stage centre, for the toolbar buttons.
83
- const zoomBy = useCallback((factor) => {
84
- touched.current = true;
85
- setView((current) => zoomAbout(current, factor, 0, 0));
86
- }, []);
87
-
88
- const setScale = useCallback((scale) => {
89
- touched.current = true;
90
- setView((current) => zoomAbout(current, scale / current.scale, 0, 0));
91
- }, []);
92
-
93
- const panBy = useCallback((dx, dy) => {
94
- touched.current = true;
95
- setView((current) => ({ ...current, x: current.x + dx, y: current.y + dy }));
96
- }, []);
97
-
98
- useEffect(() => {
99
- const stage = stageRef.current;
100
- if (!stage) return undefined;
101
- const onWheel = (event) => {
102
- event.preventDefault();
103
- zoomAt(Math.exp(-event.deltaY * WHEEL_ZOOM_PER_PX), event.clientX, event.clientY);
104
- };
105
- stage.addEventListener('wheel', onWheel, { passive: false });
106
- return () => stage.removeEventListener('wheel', onWheel);
107
- }, [stageRef, zoomAt]);
108
-
109
- // Mouse/pen drag to pan. Touch drags belong to the pinch gesture below, which
110
- // stops their propagation before they reach this.
111
- const onPointerDown = (event) => {
112
- if (event.pointerType === 'touch' || event.button !== 0) return;
113
- event.currentTarget.setPointerCapture(event.pointerId);
114
- setDragging(true);
115
- };
116
- const onPointerMove = (event) => {
117
- if (!dragging) return;
118
- panBy(event.movementX, event.movementY);
119
- };
120
- const endDrag = () => setDragging(false);
121
-
122
- useTwoFingerZoomPan({
123
- targetRef: stageRef,
124
- onGestureStart: endDrag,
125
- onGestureMove: ({ factor, cx, cy, dx, dy }) => {
126
- zoomAt(factor, cx, cy);
127
- panBy(dx, dy);
128
- },
129
- });
130
-
131
- return {
132
- view,
133
- dragging,
134
- fit,
135
- zoomBy,
136
- setScale,
137
- stageProps: {
138
- onPointerDown,
139
- onPointerMove,
140
- onPointerUp: endDrag,
141
- onPointerCancel: endDrag,
142
- onDoubleClick: fit,
143
- },
144
- };
145
- }
146
-
147
- export function ImageViewer({ path }) {
148
- const url = useMediaUrl(path);
149
- const stageRef = useRef(null);
150
- const [natural, setNatural] = useState({ width: 0, height: 0 });
151
- const [status, setStatus] = useState('loading');
152
- const { view, dragging, fit, zoomBy, setScale, stageProps } = useImageView(stageRef, natural);
153
-
154
- // A new URL (different file, or the same one re-uploaded) starts over.
155
- useEffect(() => setStatus('loading'), [url]);
156
-
157
- const onLoad = (event) => {
158
- const image = event.currentTarget;
159
- // SVGs without intrinsic dimensions report 0; fall back so fit has numbers.
160
- setNatural({ width: image.naturalWidth || 300, height: image.naturalHeight || 300 });
161
- setStatus('ready');
162
- };
163
-
164
- const percent = Math.round(view.scale * 100);
165
- return (
166
- <EditorBody>
167
- <div className={styles.viewer}>
168
- <div className={styles.toolbar}>
169
- <IconButton icon="zoom-out" label="Zoom out" onClick={() => zoomBy(1 / 1.4)} />
170
- <IconButton icon="zoom-in" label="Zoom in" onClick={() => zoomBy(1.4)} />
171
- <IconButton icon="fit" label="Fit to panel" onClick={fit} />
172
- <button type="button" className={uiStyles.button} onClick={() => setScale(1)}>
173
- 1:1
174
- </button>
175
- <span className={styles.readout}>
176
- {status === 'ready' ? `${natural.width}×${natural.height} · ${percent}%` : ''}
177
- </span>
178
- </div>
179
- <div
180
- ref={stageRef}
181
- className={`${styles.stage}${dragging ? ` ${styles.dragging}` : ''}`}
182
- {...stageProps}>
183
- <img
184
- src={url}
185
- alt={basename(path)}
186
- className={`${styles.image}${view.scale >= PIXELATE_ABOVE ? ` ${styles.pixelated}` : ''}`}
187
- style={{
188
- width: natural.width || undefined,
189
- height: natural.height || undefined,
190
- transform: `translate(-50%, -50%) translate(${view.x}px, ${view.y}px) scale(${view.scale})`,
191
- visibility: status === 'ready' ? 'visible' : 'hidden',
192
- }}
193
- onLoad={onLoad}
194
- onError={() => setStatus('error')}
195
- />
196
- {status === 'error' ? (
197
- <div className={`${styles.message} ${styles.messageError}`}>
198
- Could not load {basename(path)}.
199
- </div>
200
- ) : null}
201
- {status === 'loading' ? <div className={styles.message}>Loading…</div> : null}
202
- </div>
203
- </div>
204
- </EditorBody>
205
- );
206
- }
@@ -1,57 +0,0 @@
1
- // Read-only audio / video player. Opened for the sound and video extensions the
2
- // deck declares as kit-edited (castle.json `fileTypes`), so an uploaded .mp3 or
3
- // .mp4 can be played in place instead of being a file you can only guess at from
4
- // its name.
5
- //
6
- // The transport is the BROWSER'S: `<audio controls>` / `<video controls>`. Play,
7
- // pause, scrub, volume, fullscreen, and picture-in-picture all come with it,
8
- // they match what the platform's users already know, and they stay accessible
9
- // and touch-sized without us reimplementing any of it. This file is the frame
10
- // around that element, not a player.
11
- //
12
- // Nothing here writes: trimming or converting a media file is not something this
13
- // kit does, and a viewer that can't damage the file needs no save state.
14
-
15
- import { useEffect, useState } from 'react';
16
- import { EditorBody } from '../engine/ui';
17
- import { basename } from '../engine/files';
18
- import { useMediaUrl } from './mediaFile';
19
- import styles from './mediaViewer.module.css';
20
-
21
- export function MediaPlayer({ path, kind }) {
22
- const url = useMediaUrl(path);
23
- const [failed, setFailed] = useState(false);
24
- // A new file (or a re-upload of this one) starts over.
25
- useEffect(() => setFailed(false), [url]);
26
-
27
- const isVideo = kind === 'video';
28
- const Media = isVideo ? 'video' : 'audio';
29
- return (
30
- <EditorBody>
31
- <div className={styles.viewer}>
32
- <div className={styles.mediaStage}>
33
- <div className={isVideo ? styles.videoPanel : styles.audioPanel}>
34
- <div className={styles.fileName}>{basename(path)}</div>
35
- <Media
36
- key={url}
37
- className={isVideo ? styles.video : styles.audio}
38
- src={url}
39
- controls
40
- // Metadata only: opening a file shouldn't pull the whole thing
41
- // down until someone presses play. Video shows its first frame as
42
- // the poster so the panel isn't a black box.
43
- preload={isVideo ? 'auto' : 'metadata'}
44
- playsInline
45
- onError={() => setFailed(true)}
46
- />
47
- {failed ? (
48
- <div className={styles.inlineError}>
49
- Could not play {basename(path)} — this browser may not support its format.
50
- </div>
51
- ) : null}
52
- </div>
53
- </div>
54
- </div>
55
- </EditorBody>
56
- );
57
- }
@@ -1,31 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- import * as sdk from 'castle-web-sdk';
3
-
4
- // `fileUrl` arrived in castle-web-sdk 0.4.12, and a deck takes a kit update
5
- // (`castle-web update-import`) without reinstalling its packages -- so a deck
6
- // still holding an older SDK would meet a kit that imports a name its SDK does
7
- // not export, which is a hard build failure, not an undefined. Hence the
8
- // namespace import and the fallback: taking a kit update can't break a deck over
9
- // a version skew it never asked about. Delete both once no deck can be older
10
- // than 0.4.12.
11
- const fileUrl =
12
- sdk.fileUrl ?? ((path) => `/__castle/files/raw?path=${encodeURIComponent(path)}`);
13
- const { onFilesChanged } = sdk;
14
-
15
- // The URL a media viewer points at, re-minted whenever the file itself changes
16
- // on disk (a re-upload over the same name, an agent regenerating an asset). The
17
- // serve sends the bytes `no-store`, but an <img>/<audio> already showing the old
18
- // ones won't re-request on its own -- a changed URL is what makes it.
19
- export function useMediaUrl(path) {
20
- const [version, setVersion] = useState(0);
21
- useEffect(
22
- () =>
23
- onFilesChanged((event) => {
24
- if (event.changes.some((change) => change.path === path)) {
25
- setVersion((current) => current + 1);
26
- }
27
- }),
28
- [path]
29
- );
30
- return version === 0 ? fileUrl(path) : `${fileUrl(path)}&v=${version}`;
31
- }
@@ -1,128 +0,0 @@
1
- /* Shared chrome for the read-only media viewers (image, audio): a toolbar row
2
- over a stage that fills the panel. Neither viewer edits anything, so there is
3
- no inspector column and no save state -- just controls and the file. */
4
-
5
- /* `.editorBody` is a flex row, so claim the whole panel rather than sizing to
6
- the toolbar's content width. */
7
- .viewer {
8
- display: flex;
9
- flex: 1 1 auto;
10
- flex-direction: column;
11
- min-width: 0;
12
- min-height: 0;
13
- width: 100%;
14
- height: 100%;
15
- color: var(--castle-inspector-text, #e6e6ea);
16
- }
17
-
18
- .toolbar {
19
- flex: 0 0 auto;
20
- display: flex;
21
- align-items: center;
22
- gap: 8px;
23
- padding: 6px 10px;
24
- border-bottom: 1px solid var(--castle-border, #2a2a33);
25
- }
26
-
27
- /* Pushed-right readout: dimensions / zoom / time. Tabular figures so a ticking
28
- clock or a changing zoom doesn't jitter the row. */
29
- .readout {
30
- margin-left: auto;
31
- font-size: 12px;
32
- opacity: 0.7;
33
- font-variant-numeric: tabular-nums;
34
- white-space: nowrap;
35
- }
36
-
37
- .stage {
38
- position: relative;
39
- flex: 1 1 auto;
40
- min-height: 0;
41
- overflow: hidden;
42
- background:
43
- repeating-conic-gradient(#1b1b21 0% 25%, #23232b 0% 50%) 50% / 20px 20px;
44
- /* The pan/pinch gestures own this surface. */
45
- touch-action: none;
46
- cursor: grab;
47
- }
48
- .stage.dragging {
49
- cursor: grabbing;
50
- }
51
-
52
- .image {
53
- position: absolute;
54
- top: 50%;
55
- left: 50%;
56
- transform-origin: center;
57
- /* No smoothing at high zoom: this is a pixel inspector as much as a preview. */
58
- user-select: none;
59
- -webkit-user-drag: none;
60
- pointer-events: none;
61
- }
62
- .pixelated {
63
- image-rendering: pixelated;
64
- }
65
-
66
- .message {
67
- position: absolute;
68
- inset: 0;
69
- display: flex;
70
- align-items: center;
71
- justify-content: center;
72
- padding: 16px;
73
- text-align: center;
74
- font-size: 13px;
75
- opacity: 0.7;
76
- }
77
- .messageError {
78
- color: #e86a73;
79
- opacity: 1;
80
- }
81
-
82
- /* Audio / video: the native element, centered, with the filename above it. The
83
- browser draws the transport, so this only has to give it room. */
84
- .mediaStage {
85
- display: flex;
86
- flex: 1 1 auto;
87
- min-height: 0;
88
- align-items: center;
89
- justify-content: center;
90
- padding: 20px;
91
- }
92
- .audioPanel,
93
- .videoPanel {
94
- display: flex;
95
- flex-direction: column;
96
- gap: 10px;
97
- min-width: 0;
98
- width: 100%;
99
- }
100
- .audioPanel {
101
- max-width: 420px;
102
- }
103
- /* Video takes the panel it's given, capped so the controls stay a sane size. */
104
- .videoPanel {
105
- max-width: 100%;
106
- max-height: 100%;
107
- min-height: 0;
108
- }
109
- .audio {
110
- width: 100%;
111
- }
112
- .video {
113
- min-height: 0;
114
- max-width: 100%;
115
- max-height: 100%;
116
- background: #000;
117
- border-radius: 4px;
118
- }
119
- .inlineError {
120
- font-size: 12px;
121
- color: #e86a73;
122
- }
123
- .fileName {
124
- font-size: 13px;
125
- overflow: hidden;
126
- text-overflow: ellipsis;
127
- white-space: nowrap;
128
- }