@jkwd/inbase 0.1.4 → 0.1.5
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/README.md +3 -1
- package/apps/explorer/scripts/session-store.d.ts +20 -0
- package/apps/explorer/scripts/session-store.mjs +318 -32
- package/apps/explorer/src/App.tsx +154 -85
- package/apps/explorer/src/agentIntent.ts +33 -1
- package/apps/explorer/src/index.css +217 -2
- package/apps/explorer/src/layout.ts +46 -0
- package/apps/explorer/src/scene/FileBlock.tsx +110 -145
- package/apps/explorer/src/scene/FolderArea.tsx +17 -4
- package/apps/explorer/src/scene/MapSelectBorder.tsx +3 -1
- package/apps/explorer/src/scene/MapView.tsx +10 -12
- package/apps/explorer/src/scene/Player.tsx +27 -0
- package/apps/explorer/src/scene/RelationLines.tsx +36 -32
- package/apps/explorer/src/scene/SelectionController.tsx +21 -3
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +451 -0
- package/apps/explorer/src/scene/World.tsx +16 -35
- package/apps/explorer/src/theme.ts +7 -1
- package/apps/explorer/src/types.ts +12 -0
- package/apps/explorer/src/ui/HUD.tsx +662 -358
- package/apps/explorer/vite.config.ts +33 -21
- package/bin/session.mjs +16 -2
- package/package.json +1 -1
- package/skill/inbase/SKILL.md +22 -14
|
@@ -7,6 +7,7 @@ type MapSelectBorderProps = {
|
|
|
7
7
|
depth: number
|
|
8
8
|
y?: number
|
|
9
9
|
stroke?: number
|
|
10
|
+
color?: string
|
|
10
11
|
userData?: Record<string, unknown>
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -15,6 +16,7 @@ export function MapSelectBorder({
|
|
|
15
16
|
depth,
|
|
16
17
|
y = 0.04,
|
|
17
18
|
stroke = MAP_SELECTION.blockPad,
|
|
19
|
+
color = MAP_SELECTION.color,
|
|
18
20
|
userData,
|
|
19
21
|
}: MapSelectBorderProps) {
|
|
20
22
|
const geometry = useMemo(() => {
|
|
@@ -47,7 +49,7 @@ export function MapSelectBorder({
|
|
|
47
49
|
userData={userData}
|
|
48
50
|
>
|
|
49
51
|
<meshBasicMaterial
|
|
50
|
-
color={
|
|
52
|
+
color={color}
|
|
51
53
|
toneMapped={false}
|
|
52
54
|
side={THREE.DoubleSide}
|
|
53
55
|
depthWrite={false}
|
|
@@ -49,6 +49,7 @@ export function MapView({
|
|
|
49
49
|
// Ortho up is -Z, so +Z is down the screen. Shift the view so the map sits
|
|
50
50
|
// above the bottom HUD instead of centering under it.
|
|
51
51
|
const cz = bounds.cz + hudReserve / 2 / zoom
|
|
52
|
+
const target = useMemo(() => [bounds.cx, 0, cz] as [number, number, number], [bounds.cx, cz])
|
|
52
53
|
|
|
53
54
|
useLayoutEffect(() => {
|
|
54
55
|
if (!enabled || !(camera instanceof THREE.OrthographicCamera)) return
|
|
@@ -66,8 +67,7 @@ export function MapView({
|
|
|
66
67
|
const element = gl.domElement
|
|
67
68
|
element.style.cursor = 'grab'
|
|
68
69
|
|
|
69
|
-
const isWalkClick = (event: PointerEvent | MouseEvent) =>
|
|
70
|
-
event.shiftKey || event.ctrlKey || event.metaKey
|
|
70
|
+
const isWalkClick = (event: PointerEvent | MouseEvent) => event.ctrlKey
|
|
71
71
|
|
|
72
72
|
const isWalkButton = (event: PointerEvent) =>
|
|
73
73
|
event.button === 0 || (event.ctrlKey && event.button === 2)
|
|
@@ -111,6 +111,7 @@ export function MapView({
|
|
|
111
111
|
const pick = pickAt(clientX, clientY)
|
|
112
112
|
if (!pick) return false
|
|
113
113
|
const { raycaster, relationHit, fileHit } = pick
|
|
114
|
+
if (fileHit) return false
|
|
114
115
|
|
|
115
116
|
const hit = new THREE.Vector3()
|
|
116
117
|
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
|
|
@@ -121,7 +122,7 @@ export function MapView({
|
|
|
121
122
|
landAt(hit.x, hit.z)
|
|
122
123
|
return true
|
|
123
124
|
}
|
|
124
|
-
if (relationHit
|
|
125
|
+
if (relationHit) return false
|
|
125
126
|
|
|
126
127
|
landAt(hit.x, hit.z)
|
|
127
128
|
return true
|
|
@@ -140,20 +141,17 @@ export function MapView({
|
|
|
140
141
|
const pick = pickAt(event.clientX, event.clientY)
|
|
141
142
|
if (!pick) return
|
|
142
143
|
const { relationHit, fileHit } = pick
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
if (fileHit) {
|
|
145
|
+
onSelect(fileHit.object.userData.fileId as string)
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
if (relationHit) {
|
|
147
149
|
onTravelTo(
|
|
148
150
|
relationHit.object.userData.relationFrom as string,
|
|
149
151
|
relationHit.object.userData.relationTo as string,
|
|
150
152
|
)
|
|
151
153
|
return
|
|
152
154
|
}
|
|
153
|
-
if (fileHit) {
|
|
154
|
-
onSelect(fileHit.object.userData.fileId as string)
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
155
|
|
|
158
156
|
const hit = new THREE.Vector3()
|
|
159
157
|
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
|
|
@@ -216,7 +214,7 @@ export function MapView({
|
|
|
216
214
|
zoomSpeed={1.15}
|
|
217
215
|
minZoom={Math.max(fitZoom * 0.35, 0.05)}
|
|
218
216
|
maxZoom={Math.max(fitZoom * 10, 20)}
|
|
219
|
-
target={
|
|
217
|
+
target={target}
|
|
220
218
|
/>
|
|
221
219
|
)}
|
|
222
220
|
{enabled &&
|
|
@@ -49,6 +49,7 @@ export function Player({
|
|
|
49
49
|
const [steering, setSteering] = useState(true)
|
|
50
50
|
const controlsRef = useRef<PointerLockControlsImpl>(null)
|
|
51
51
|
const capturedLook = useRef(false)
|
|
52
|
+
const lookHeld = useRef(false)
|
|
52
53
|
const flying = useRef<{
|
|
53
54
|
startPos: THREE.Vector3
|
|
54
55
|
endPos: THREE.Vector3
|
|
@@ -133,6 +134,31 @@ export function Player({
|
|
|
133
134
|
controls.lock()
|
|
134
135
|
}, [lockEnabled, onLockedChange, steering, walking])
|
|
135
136
|
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (!walking) return
|
|
139
|
+
|
|
140
|
+
const looking = () =>
|
|
141
|
+
Boolean(controlsRef.current?.isLocked || document.pointerLockElement)
|
|
142
|
+
|
|
143
|
+
const onClick = (event: MouseEvent) => {
|
|
144
|
+
if (event.button !== 0 || event.detail !== 1) return
|
|
145
|
+
lookHeld.current = looking()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const onDblClick = () => {
|
|
149
|
+
if (!lookHeld.current) return
|
|
150
|
+
controlsRef.current?.unlock()
|
|
151
|
+
document.exitPointerLock()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
document.addEventListener('click', onClick, true)
|
|
155
|
+
document.addEventListener('dblclick', onDblClick)
|
|
156
|
+
return () => {
|
|
157
|
+
document.removeEventListener('click', onClick, true)
|
|
158
|
+
document.removeEventListener('dblclick', onDblClick)
|
|
159
|
+
}
|
|
160
|
+
}, [walking])
|
|
161
|
+
|
|
136
162
|
useFrame((_, delta) => {
|
|
137
163
|
try {
|
|
138
164
|
if (walking && flying.current) {
|
|
@@ -198,6 +224,7 @@ export function Player({
|
|
|
198
224
|
<PointerLockControls
|
|
199
225
|
ref={controlsRef}
|
|
200
226
|
makeDefault
|
|
227
|
+
selector=".stage canvas"
|
|
201
228
|
enabled={steering && lockEnabled}
|
|
202
229
|
onLock={() => onLockedChange(true)}
|
|
203
230
|
onUnlock={() => onLockedChange(false)}
|
|
@@ -125,49 +125,53 @@ export function RelationLines({
|
|
|
125
125
|
const selectedRadius = fromAbove ? 0.2 : 0.07
|
|
126
126
|
const plannedRadius = fromAbove ? 0.26 : 0.1
|
|
127
127
|
const lines: LineMesh[] = []
|
|
128
|
-
const
|
|
128
|
+
const drawn = new Set<string>()
|
|
129
|
+
|
|
130
|
+
const addLine = (
|
|
131
|
+
fromId: string,
|
|
132
|
+
toId: string,
|
|
133
|
+
planned: boolean,
|
|
134
|
+
radius: number,
|
|
135
|
+
) => {
|
|
136
|
+
const key = `${fromId}->${toId}`
|
|
137
|
+
if (drawn.has(key)) return
|
|
138
|
+
const from = placed[fromId]
|
|
139
|
+
const to = placed[toId]
|
|
140
|
+
if (!from || !to) return
|
|
141
|
+
drawn.add(key)
|
|
142
|
+
lines.push(edgeMesh(from, to, toId, planned, radius, fromAbove, false))
|
|
143
|
+
}
|
|
129
144
|
|
|
130
145
|
for (const edge of plannedEdges) {
|
|
131
|
-
|
|
132
|
-
const to = placed[edge.to]
|
|
133
|
-
if (!from || !to) continue
|
|
134
|
-
const key = `${edge.from}->${edge.to}`
|
|
135
|
-
if (plannedKeys.has(key)) continue
|
|
136
|
-
plannedKeys.add(key)
|
|
137
|
-
lines.push(edgeMesh(from, to, edge.to, true, plannedRadius, fromAbove, false))
|
|
146
|
+
addLine(edge.from, edge.to, true, plannedRadius)
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
if (selectedId) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
} else {
|
|
154
|
-
const file = files.find((item) => item.id === selectedId)
|
|
155
|
-
if (file) {
|
|
156
|
-
for (const importId of file.imports) {
|
|
157
|
-
if (plannedKeys.has(`${file.id}->${importId}`)) continue
|
|
158
|
-
const target = placed[importId]
|
|
159
|
-
if (!target) continue
|
|
160
|
-
lines.push(
|
|
161
|
-
edgeMesh(selected, target, importId, false, selectedRadius, fromAbove, false),
|
|
162
|
-
)
|
|
163
|
-
}
|
|
150
|
+
if (importedBy) {
|
|
151
|
+
for (const file of files) {
|
|
152
|
+
if (file.id === selectedId || !file.imports.includes(selectedId)) continue
|
|
153
|
+
addLine(file.id, selectedId, false, selectedRadius)
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
const file = files.find((item) => item.id === selectedId)
|
|
157
|
+
if (file) {
|
|
158
|
+
for (const importId of file.imports) {
|
|
159
|
+
addLine(file.id, importId, false, selectedRadius)
|
|
164
160
|
}
|
|
165
161
|
}
|
|
166
162
|
}
|
|
167
163
|
}
|
|
168
164
|
|
|
169
165
|
return lines
|
|
170
|
-
}, [
|
|
166
|
+
}, [
|
|
167
|
+
extras,
|
|
168
|
+
files,
|
|
169
|
+
fromAbove,
|
|
170
|
+
importedBy,
|
|
171
|
+
layout.files,
|
|
172
|
+
plannedEdges,
|
|
173
|
+
selectedId,
|
|
174
|
+
])
|
|
171
175
|
|
|
172
176
|
if (meshes.length === 0) return null
|
|
173
177
|
|
|
@@ -68,21 +68,39 @@ export function SelectionController({
|
|
|
68
68
|
})
|
|
69
69
|
|
|
70
70
|
useEffect(() => {
|
|
71
|
-
|
|
71
|
+
let travelTimer: number | undefined
|
|
72
|
+
|
|
73
|
+
const onClick = (event: MouseEvent) => {
|
|
72
74
|
if (!locked) return
|
|
75
|
+
if (event.detail > 1) {
|
|
76
|
+
if (travelTimer) window.clearTimeout(travelTimer)
|
|
77
|
+
return
|
|
78
|
+
}
|
|
73
79
|
raycaster.setFromCamera(ndc, camera)
|
|
74
80
|
const hits = raycaster.intersectObjects(scene.children, true)
|
|
75
81
|
const aim = aimedRelation(hits, camera.position, files)
|
|
76
82
|
if (aim) {
|
|
77
|
-
|
|
83
|
+
travelTimer = window.setTimeout(() => {
|
|
84
|
+
travelTimer = undefined
|
|
85
|
+
onTravelTo(aim.from, aim.to)
|
|
86
|
+
}, 280)
|
|
78
87
|
return
|
|
79
88
|
}
|
|
80
89
|
const file = hits.find((item) => item.object.userData.fileId)
|
|
81
90
|
onSelect(file ? (file.object.userData.fileId as string) : null)
|
|
82
91
|
}
|
|
83
92
|
|
|
93
|
+
const onDblClick = () => {
|
|
94
|
+
if (travelTimer) window.clearTimeout(travelTimer)
|
|
95
|
+
}
|
|
96
|
+
|
|
84
97
|
window.addEventListener('click', onClick)
|
|
85
|
-
|
|
98
|
+
window.addEventListener('dblclick', onDblClick)
|
|
99
|
+
return () => {
|
|
100
|
+
if (travelTimer) window.clearTimeout(travelTimer)
|
|
101
|
+
window.removeEventListener('click', onClick)
|
|
102
|
+
window.removeEventListener('dblclick', onDblClick)
|
|
103
|
+
}
|
|
86
104
|
}, [camera, files, locked, onSelect, onTravelTo, scene])
|
|
87
105
|
|
|
88
106
|
return null
|