@jkwd/inbase 0.1.1 → 0.1.4
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/apps/explorer/scripts/open-editor.d.ts +6 -0
- package/apps/explorer/scripts/open-editor.mjs +219 -0
- package/apps/explorer/scripts/session-store.d.ts +31 -1
- package/apps/explorer/scripts/session-store.mjs +153 -26
- package/apps/explorer/src/App.tsx +35 -4
- package/apps/explorer/src/agentIntent.ts +21 -0
- package/apps/explorer/src/index.css +81 -8
- package/apps/explorer/src/scene/FileBlock.tsx +1 -1
- package/apps/explorer/src/scene/FolderArea.tsx +5 -2
- package/apps/explorer/src/scene/MapView.tsx +61 -29
- package/apps/explorer/src/scene/World.tsx +4 -7
- package/apps/explorer/src/theme.ts +3 -0
- package/apps/explorer/src/ui/HUD.tsx +144 -109
- package/apps/explorer/vite.config.ts +39 -1
- package/bin/session.mjs +1 -1
- package/package.json +3 -1
|
@@ -21,6 +21,8 @@ body,
|
|
|
21
21
|
.stage {
|
|
22
22
|
position: absolute;
|
|
23
23
|
inset: 0;
|
|
24
|
+
z-index: 0;
|
|
25
|
+
background: #000;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
* {
|
|
@@ -35,7 +37,7 @@ button {
|
|
|
35
37
|
position: fixed;
|
|
36
38
|
inset: 0;
|
|
37
39
|
pointer-events: none;
|
|
38
|
-
z-index:
|
|
40
|
+
z-index: 10;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
.hud-top,
|
|
@@ -243,6 +245,7 @@ button {
|
|
|
243
245
|
left: 0;
|
|
244
246
|
right: 0;
|
|
245
247
|
bottom: 0;
|
|
248
|
+
z-index: 3;
|
|
246
249
|
display: flex;
|
|
247
250
|
align-items: center;
|
|
248
251
|
justify-content: space-between;
|
|
@@ -306,11 +309,15 @@ button {
|
|
|
306
309
|
position: absolute;
|
|
307
310
|
top: 68px;
|
|
308
311
|
right: 20px;
|
|
312
|
+
z-index: 4;
|
|
309
313
|
width: min(320px, calc(100% - 40px));
|
|
310
|
-
|
|
311
|
-
|
|
314
|
+
height: auto;
|
|
315
|
+
max-height: calc(100dvh - 168px);
|
|
316
|
+
overflow-x: hidden;
|
|
317
|
+
overflow-y: auto;
|
|
312
318
|
overscroll-behavior: contain;
|
|
313
|
-
padding: 16px;
|
|
319
|
+
padding: 16px 16px 20px;
|
|
320
|
+
background: var(--vc-chrome);
|
|
314
321
|
}
|
|
315
322
|
|
|
316
323
|
.hud-panel-info {
|
|
@@ -352,18 +359,31 @@ button {
|
|
|
352
359
|
|
|
353
360
|
.hud-steps {
|
|
354
361
|
margin: 10px 0 0;
|
|
355
|
-
padding
|
|
362
|
+
padding: 0;
|
|
363
|
+
list-style: none;
|
|
356
364
|
color: #8b95a5;
|
|
357
365
|
}
|
|
358
366
|
|
|
359
367
|
.hud-steps li {
|
|
360
|
-
|
|
368
|
+
display: grid;
|
|
369
|
+
grid-template-columns: 1.6em minmax(0, 1fr);
|
|
370
|
+
align-items: start;
|
|
371
|
+
justify-content: start;
|
|
372
|
+
gap: 8px;
|
|
373
|
+
margin: 0 0 8px;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.hud-step-index {
|
|
377
|
+
font-variant-numeric: tabular-nums;
|
|
378
|
+
line-height: 1.4;
|
|
361
379
|
}
|
|
362
380
|
|
|
363
381
|
.hud-step-main {
|
|
364
|
-
display:
|
|
382
|
+
display: flex;
|
|
383
|
+
flex-wrap: wrap;
|
|
365
384
|
align-items: baseline;
|
|
366
385
|
gap: 8px;
|
|
386
|
+
min-width: 0;
|
|
367
387
|
}
|
|
368
388
|
|
|
369
389
|
.hud-steps li[data-next='true'] {
|
|
@@ -488,6 +508,39 @@ button {
|
|
|
488
508
|
opacity: 0.35;
|
|
489
509
|
}
|
|
490
510
|
|
|
511
|
+
.hud-fold {
|
|
512
|
+
margin-top: 12px;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.hud-fold-summary {
|
|
516
|
+
cursor: pointer;
|
|
517
|
+
list-style: none;
|
|
518
|
+
display: flex;
|
|
519
|
+
align-items: center;
|
|
520
|
+
gap: 8px;
|
|
521
|
+
user-select: none;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.hud-fold-summary::-webkit-details-marker,
|
|
525
|
+
.hud-fold-summary::marker {
|
|
526
|
+
display: none;
|
|
527
|
+
content: '';
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.hud-fold-summary::before {
|
|
531
|
+
content: '';
|
|
532
|
+
width: 0;
|
|
533
|
+
height: 0;
|
|
534
|
+
border-top: 4px solid transparent;
|
|
535
|
+
border-bottom: 4px solid transparent;
|
|
536
|
+
border-left: 6px solid currentColor;
|
|
537
|
+
transition: transform 0.12s ease;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.hud-fold[open] > .hud-fold-summary::before {
|
|
541
|
+
transform: rotate(90deg);
|
|
542
|
+
}
|
|
543
|
+
|
|
491
544
|
.hud-button-approve {
|
|
492
545
|
border-color: #9ad8ff;
|
|
493
546
|
color: #d7eef8;
|
|
@@ -514,12 +567,17 @@ button {
|
|
|
514
567
|
word-break: break-all;
|
|
515
568
|
}
|
|
516
569
|
|
|
570
|
+
.hud-inspect {
|
|
571
|
+
margin: 10px 0 0;
|
|
572
|
+
width: 100%;
|
|
573
|
+
}
|
|
574
|
+
|
|
517
575
|
.hud-panel ul {
|
|
518
576
|
margin: 8px 0 0;
|
|
519
577
|
padding-left: 18px;
|
|
520
578
|
}
|
|
521
579
|
|
|
522
|
-
.hud-panel li {
|
|
580
|
+
.hud-panel ul li {
|
|
523
581
|
margin: 0 0 4px;
|
|
524
582
|
display: flex;
|
|
525
583
|
align-items: center;
|
|
@@ -547,6 +605,21 @@ button {
|
|
|
547
605
|
border-color: #8a4a4a;
|
|
548
606
|
}
|
|
549
607
|
|
|
608
|
+
.hud-item-inspect {
|
|
609
|
+
flex: none;
|
|
610
|
+
padding: 2px 6px;
|
|
611
|
+
color: #d7eef8;
|
|
612
|
+
background: transparent;
|
|
613
|
+
border: 1px solid #3a4250;
|
|
614
|
+
cursor: pointer;
|
|
615
|
+
font-size: 11px;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
.hud-item-inspect:hover,
|
|
619
|
+
.hud-item-inspect:focus-visible {
|
|
620
|
+
border-color: #9ad8ff;
|
|
621
|
+
}
|
|
622
|
+
|
|
550
623
|
.hud-add-row {
|
|
551
624
|
display: flex;
|
|
552
625
|
gap: 6px;
|
|
@@ -95,7 +95,7 @@ export function FileBlock({
|
|
|
95
95
|
onCancelName,
|
|
96
96
|
}: FileBlockProps) {
|
|
97
97
|
const [width, height, depth] = placed.size
|
|
98
|
-
const highlight = changeKind ? CHANGE_HIGHLIGHT[changeKind] : null
|
|
98
|
+
const highlight = changeKind && mapMode ? CHANGE_HIGHLIGHT[changeKind] : null
|
|
99
99
|
const color = added || file.userCreated ? '#7ec8e8' : fileColor(file.language)
|
|
100
100
|
const muted = dimColor(color, 0.32)
|
|
101
101
|
const walkSelected = selected && !mapMode
|
|
@@ -8,6 +8,7 @@ type FolderAreaProps = {
|
|
|
8
8
|
folder: PlacedFolder
|
|
9
9
|
naming?: boolean
|
|
10
10
|
selected?: boolean
|
|
11
|
+
mapMode?: boolean
|
|
11
12
|
highlightKind?: ChangeKind | null
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -15,10 +16,12 @@ export function FolderArea({
|
|
|
15
16
|
folder,
|
|
16
17
|
naming = false,
|
|
17
18
|
selected = false,
|
|
19
|
+
mapMode = false,
|
|
18
20
|
highlightKind = null,
|
|
19
21
|
}: FolderAreaProps) {
|
|
20
22
|
const added = Boolean(folder.added)
|
|
21
|
-
const highlight =
|
|
23
|
+
const highlight =
|
|
24
|
+
mapMode && highlightKind ? CHANGE_HIGHLIGHT[highlightKind] : null
|
|
22
25
|
const addedOnly = added && !highlight
|
|
23
26
|
const outline = highlight ? highlight.color : addedOnly ? '#7ec8e8' : null
|
|
24
27
|
const color = highlight
|
|
@@ -48,7 +51,7 @@ export function FolderArea({
|
|
|
48
51
|
<planeGeometry args={[folder.width, folder.depth]} />
|
|
49
52
|
<meshBasicMaterial color={color} />
|
|
50
53
|
</mesh>
|
|
51
|
-
{selected && (
|
|
54
|
+
{selected && mapMode && (
|
|
52
55
|
<MapSelectBorder
|
|
53
56
|
width={folder.width}
|
|
54
57
|
depth={folder.depth}
|
|
@@ -34,33 +34,46 @@ export function MapView({
|
|
|
34
34
|
const bounds = useMemo(() => worldBounds(layout), [layout])
|
|
35
35
|
const drag = useRef({ x: 0, y: 0, moved: false, active: false })
|
|
36
36
|
const sized = size.width > 16 && size.height > 16
|
|
37
|
+
const hudReserve = 88
|
|
38
|
+
const topReserve = 28
|
|
39
|
+
const viewWidth = Math.max(size.width, 1)
|
|
40
|
+
const viewHeight = Math.max(size.height - hudReserve - topReserve, 1)
|
|
37
41
|
|
|
38
42
|
const fitZoom = sized
|
|
39
43
|
? Math.min(
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
viewWidth / Math.max(bounds.width + 36, 1),
|
|
45
|
+
viewHeight / Math.max(bounds.depth + 36, 1),
|
|
42
46
|
)
|
|
43
47
|
: 8
|
|
44
|
-
const zoom = Math.max(fitZoom * 0.92,
|
|
48
|
+
const zoom = Math.max(fitZoom * 0.92, 0.08)
|
|
49
|
+
// Ortho up is -Z, so +Z is down the screen. Shift the view so the map sits
|
|
50
|
+
// above the bottom HUD instead of centering under it.
|
|
51
|
+
const cz = bounds.cz + hudReserve / 2 / zoom
|
|
45
52
|
|
|
46
53
|
useLayoutEffect(() => {
|
|
47
54
|
if (!enabled || !(camera instanceof THREE.OrthographicCamera)) return
|
|
48
55
|
camera.up.set(0, 0, -1)
|
|
49
|
-
camera.position.set(bounds.cx, 120,
|
|
50
|
-
camera.lookAt(bounds.cx, 0,
|
|
56
|
+
camera.position.set(bounds.cx, 120, cz)
|
|
57
|
+
camera.lookAt(bounds.cx, 0, cz)
|
|
51
58
|
camera.near = 1
|
|
52
59
|
camera.far = 2000
|
|
53
60
|
camera.zoom = zoom
|
|
54
61
|
camera.updateProjectionMatrix()
|
|
55
|
-
}, [bounds.cx,
|
|
62
|
+
}, [bounds.cx, camera, cz, enabled, zoom])
|
|
56
63
|
|
|
57
64
|
useEffect(() => {
|
|
58
65
|
if (!enabled) return
|
|
59
66
|
const element = gl.domElement
|
|
60
67
|
element.style.cursor = 'grab'
|
|
61
68
|
|
|
69
|
+
const isWalkClick = (event: PointerEvent | MouseEvent) =>
|
|
70
|
+
event.shiftKey || event.ctrlKey || event.metaKey
|
|
71
|
+
|
|
72
|
+
const isWalkButton = (event: PointerEvent) =>
|
|
73
|
+
event.button === 0 || (event.ctrlKey && event.button === 2)
|
|
74
|
+
|
|
62
75
|
const onDown = (event: PointerEvent) => {
|
|
63
|
-
if (event
|
|
76
|
+
if (!isWalkButton(event)) return
|
|
64
77
|
drag.current = { x: event.clientX, y: event.clientY, moved: false, active: true }
|
|
65
78
|
element.style.cursor = 'grabbing'
|
|
66
79
|
}
|
|
@@ -86,11 +99,43 @@ export function MapView({
|
|
|
86
99
|
return { raycaster, relationHit, fileHit }
|
|
87
100
|
}
|
|
88
101
|
|
|
102
|
+
const landAt = (x: number, z: number) => {
|
|
103
|
+
const lock = element.requestPointerLock()
|
|
104
|
+
if (lock && typeof lock.catch === 'function') {
|
|
105
|
+
void lock.catch(() => {})
|
|
106
|
+
}
|
|
107
|
+
onLand(x, z)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const landAtPointer = (clientX: number, clientY: number, allowIsland: boolean) => {
|
|
111
|
+
const pick = pickAt(clientX, clientY)
|
|
112
|
+
if (!pick) return false
|
|
113
|
+
const { raycaster, relationHit, fileHit } = pick
|
|
114
|
+
|
|
115
|
+
const hit = new THREE.Vector3()
|
|
116
|
+
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
|
|
117
|
+
if (!raycaster.ray.intersectPlane(plane, hit)) return false
|
|
118
|
+
|
|
119
|
+
const folder = folderAt(hit.x, hit.z, layout)
|
|
120
|
+
if (allowIsland && folder) {
|
|
121
|
+
landAt(hit.x, hit.z)
|
|
122
|
+
return true
|
|
123
|
+
}
|
|
124
|
+
if (relationHit || fileHit) return false
|
|
125
|
+
|
|
126
|
+
landAt(hit.x, hit.z)
|
|
127
|
+
return true
|
|
128
|
+
}
|
|
129
|
+
|
|
89
130
|
const onUp = (event: PointerEvent) => {
|
|
90
131
|
element.style.cursor = 'grab'
|
|
91
132
|
const startedOnCanvas = drag.current.active
|
|
92
133
|
drag.current.active = false
|
|
93
|
-
if (!startedOnCanvas || event
|
|
134
|
+
if (!startedOnCanvas || !isWalkButton(event) || drag.current.moved) return
|
|
135
|
+
|
|
136
|
+
if (isWalkClick(event) && landAtPointer(event.clientX, event.clientY, true)) {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
94
139
|
|
|
95
140
|
const pick = pickAt(event.clientX, event.clientY)
|
|
96
141
|
if (!pick) return
|
|
@@ -123,34 +168,20 @@ export function MapView({
|
|
|
123
168
|
onSelectFolder(null)
|
|
124
169
|
}
|
|
125
170
|
|
|
126
|
-
const
|
|
127
|
-
if (event.
|
|
128
|
-
const pick = pickAt(event.clientX, event.clientY)
|
|
129
|
-
if (!pick) return
|
|
130
|
-
const { raycaster, relationHit, fileHit } = pick
|
|
131
|
-
if (relationHit || fileHit) return
|
|
132
|
-
|
|
133
|
-
const hit = new THREE.Vector3()
|
|
134
|
-
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
|
|
135
|
-
if (raycaster.ray.intersectPlane(plane, hit)) {
|
|
136
|
-
const lock = element.requestPointerLock()
|
|
137
|
-
if (lock && typeof lock.catch === 'function') {
|
|
138
|
-
void lock.catch(() => {})
|
|
139
|
-
}
|
|
140
|
-
onLand(hit.x, hit.z)
|
|
141
|
-
}
|
|
171
|
+
const onContextMenu = (event: MouseEvent) => {
|
|
172
|
+
if (event.ctrlKey) event.preventDefault()
|
|
142
173
|
}
|
|
143
174
|
|
|
144
175
|
element.addEventListener('pointerdown', onDown)
|
|
145
176
|
window.addEventListener('pointermove', onMove)
|
|
146
177
|
window.addEventListener('pointerup', onUp)
|
|
147
|
-
element.addEventListener('
|
|
178
|
+
element.addEventListener('contextmenu', onContextMenu)
|
|
148
179
|
return () => {
|
|
149
180
|
element.style.cursor = ''
|
|
150
181
|
element.removeEventListener('pointerdown', onDown)
|
|
151
182
|
window.removeEventListener('pointermove', onMove)
|
|
152
183
|
window.removeEventListener('pointerup', onUp)
|
|
153
|
-
element.removeEventListener('
|
|
184
|
+
element.removeEventListener('contextmenu', onContextMenu)
|
|
154
185
|
}
|
|
155
186
|
}, [
|
|
156
187
|
camera,
|
|
@@ -169,7 +200,7 @@ export function MapView({
|
|
|
169
200
|
{enabled && (
|
|
170
201
|
<OrthographicCamera
|
|
171
202
|
makeDefault
|
|
172
|
-
position={[bounds.cx, 120,
|
|
203
|
+
position={[bounds.cx, 120, cz]}
|
|
173
204
|
zoom={zoom}
|
|
174
205
|
near={1}
|
|
175
206
|
far={2000}
|
|
@@ -183,9 +214,9 @@ export function MapView({
|
|
|
183
214
|
dampingFactor={0.12}
|
|
184
215
|
screenSpacePanning
|
|
185
216
|
zoomSpeed={1.15}
|
|
186
|
-
minZoom={Math.max(fitZoom * 0.
|
|
217
|
+
minZoom={Math.max(fitZoom * 0.35, 0.05)}
|
|
187
218
|
maxZoom={Math.max(fitZoom * 10, 20)}
|
|
188
|
-
target={[bounds.cx, 0,
|
|
219
|
+
target={[bounds.cx, 0, cz]}
|
|
189
220
|
/>
|
|
190
221
|
)}
|
|
191
222
|
{enabled &&
|
|
@@ -194,6 +225,7 @@ export function MapView({
|
|
|
194
225
|
key={folder.path}
|
|
195
226
|
position={[folder.x, 14, folder.z + 1.35]}
|
|
196
227
|
center
|
|
228
|
+
zIndexRange={[1, 0]}
|
|
197
229
|
style={{ pointerEvents: 'none' }}
|
|
198
230
|
>
|
|
199
231
|
<div
|
|
@@ -9,7 +9,7 @@ import { UserContextTracker } from './UserContextTracker'
|
|
|
9
9
|
import { BlockPlacer } from './BlockPlacer'
|
|
10
10
|
import { IslandPlacer } from './IslandPlacer'
|
|
11
11
|
import { folderOfFile, filesImporting } from '../layout'
|
|
12
|
-
import {
|
|
12
|
+
import { WORLD_VOID } from '../theme'
|
|
13
13
|
import type { ChangeKind } from '../theme'
|
|
14
14
|
import type {
|
|
15
15
|
CodebaseGraph,
|
|
@@ -161,18 +161,14 @@ export function World({
|
|
|
161
161
|
|
|
162
162
|
return (
|
|
163
163
|
<>
|
|
164
|
-
<color attach="background" args={[
|
|
165
|
-
{!mapping && <fog attach="fog" args={[
|
|
164
|
+
<color attach="background" args={[WORLD_VOID]} />
|
|
165
|
+
{!mapping && <fog attach="fog" args={[WORLD_VOID, 38, 160]} />}
|
|
166
166
|
<hemisphereLight args={['#d7e2ee', '#2a3038', mapping ? 1.1 : 0.85]} />
|
|
167
167
|
<directionalLight
|
|
168
168
|
position={mapping ? [8, 60, 8] : [12, 22, 8]}
|
|
169
169
|
intensity={mapping ? 1.35 : 0.55}
|
|
170
170
|
/>
|
|
171
171
|
<ambientLight intensity={mapping ? 0.7 : 0.42} />
|
|
172
|
-
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.06, 40]}>
|
|
173
|
-
<planeGeometry args={[400, 400]} />
|
|
174
|
-
<meshBasicMaterial color={EDITOR_GREY.chrome} />
|
|
175
|
-
</mesh>
|
|
176
172
|
<MapView
|
|
177
173
|
layout={layout}
|
|
178
174
|
enabled={mapping}
|
|
@@ -191,6 +187,7 @@ export function World({
|
|
|
191
187
|
folder={folder}
|
|
192
188
|
naming={folder.path === namingIslandId}
|
|
193
189
|
selected={folder.path === selectedFolder}
|
|
190
|
+
mapMode={mapping}
|
|
194
191
|
highlightKind={highlightedFolders[folder.path] ?? null}
|
|
195
192
|
/>
|
|
196
193
|
))}
|
|
@@ -25,6 +25,9 @@ export const EDITOR_GREY = {
|
|
|
25
25
|
surface: '#272c36',
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** Infinite map/walk backdrop. Keep distinct from chrome HUD greys. */
|
|
29
|
+
export const WORLD_VOID = '#000000'
|
|
30
|
+
|
|
28
31
|
export type ChangeKind = 'add' | 'edit' | 'remove'
|
|
29
32
|
|
|
30
33
|
export const CHANGE_HIGHLIGHT: Record<
|