@jkwd/inbase 0.1.12 → 0.1.13
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 +1 -1
- package/apps/explorer/scripts/session-store.d.ts +2 -0
- package/apps/explorer/scripts/session-store.mjs +72 -10
- package/apps/explorer/src/App.tsx +6 -0
- package/apps/explorer/src/agentIntent.ts +7 -1
- package/apps/explorer/src/index.css +106 -1
- package/apps/explorer/src/scene/DistantFileBlocks.tsx +44 -0
- package/apps/explorer/src/scene/FileBlock.tsx +28 -37
- package/apps/explorer/src/scene/FolderArea.tsx +4 -2
- package/apps/explorer/src/scene/MapView.tsx +117 -36
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +9 -1
- package/apps/explorer/src/scene/WalkLodTracker.tsx +92 -0
- package/apps/explorer/src/scene/World.tsx +109 -24
- package/apps/explorer/src/scene/walkLod.ts +201 -0
- package/apps/explorer/src/types.ts +1 -0
- package/apps/explorer/src/ui/HUD.tsx +53 -27
- package/apps/explorer/vite.config.ts +2 -0
- package/bin/session.mjs +1 -1
- package/package.json +1 -1
- package/skill/commands/inbase.md +2 -2
- package/skill/inbase/SKILL.md +2 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { useEffect, useLayoutEffect, useMemo, useRef } from 'react'
|
|
1
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
+
import { createPortal } from 'react-dom'
|
|
2
3
|
import { useFrame, useThree } from '@react-three/fiber'
|
|
3
4
|
import { Html, MapControls, OrthographicCamera } from '@react-three/drei'
|
|
4
5
|
import * as THREE from 'three'
|
|
5
6
|
import { folderAt, worldBounds } from '../layout'
|
|
6
7
|
import type { ChangeKind } from '../theme'
|
|
7
|
-
import type { WorldLayout } from '../types'
|
|
8
|
+
import type { PlacedFolder, WorldLayout } from '../types'
|
|
8
9
|
|
|
9
10
|
export type MapBlueprintMenu = {
|
|
10
11
|
x: number
|
|
@@ -38,7 +39,10 @@ export function MapView({
|
|
|
38
39
|
onBlueprintMenu,
|
|
39
40
|
}: MapViewProps) {
|
|
40
41
|
const size = useThree((state) => state.size)
|
|
41
|
-
const
|
|
42
|
+
const camera = useThree((state) => state.camera)
|
|
43
|
+
const gl = useThree((state) => state.gl)
|
|
44
|
+
const invalidate = useThree((state) => state.invalidate)
|
|
45
|
+
const scene = useThree((state) => state.scene)
|
|
42
46
|
const bounds = useMemo(() => worldBounds(layout), [layout])
|
|
43
47
|
const drag = useRef({ x: 0, y: 0, moved: false, active: false })
|
|
44
48
|
const sized = size.width > 16 && size.height > 16
|
|
@@ -305,44 +309,121 @@ export function MapView({
|
|
|
305
309
|
maxZoom={Math.max(fitZoom * 10, 20)}
|
|
306
310
|
/>
|
|
307
311
|
)}
|
|
308
|
-
{enabled &&
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
style={{ pointerEvents: 'none' }}
|
|
316
|
-
>
|
|
317
|
-
<div
|
|
318
|
-
className={[
|
|
319
|
-
'map-folder-label',
|
|
320
|
-
highlightedFolders?.[folder.path]
|
|
321
|
-
? `map-folder-label-${highlightedFolders[folder.path]}`
|
|
322
|
-
: folder.added
|
|
323
|
-
? 'map-folder-label-added'
|
|
324
|
-
: selectedFolder === folder.path
|
|
325
|
-
? 'map-folder-label-selected'
|
|
326
|
-
: '',
|
|
327
|
-
]
|
|
328
|
-
.filter(Boolean)
|
|
329
|
-
.join(' ')}
|
|
330
|
-
>
|
|
331
|
-
<span className="map-folder-name">
|
|
332
|
-
{folderKindLabel(
|
|
333
|
-
folder.name,
|
|
334
|
-
highlightedFolders?.[folder.path] ?? null,
|
|
335
|
-
folder.added ?? false,
|
|
336
|
-
)}
|
|
337
|
-
</span>
|
|
338
|
-
</div>
|
|
339
|
-
</Html>
|
|
340
|
-
))}
|
|
312
|
+
{enabled && (
|
|
313
|
+
<MapFolderLabels
|
|
314
|
+
folders={layout.folders}
|
|
315
|
+
highlightedFolders={highlightedFolders}
|
|
316
|
+
selectedFolder={selectedFolder}
|
|
317
|
+
/>
|
|
318
|
+
)}
|
|
341
319
|
{enabled && marker && <LandMarker marker={marker} />}
|
|
342
320
|
</>
|
|
343
321
|
)
|
|
344
322
|
}
|
|
345
323
|
|
|
324
|
+
const PROJECT = new THREE.Vector3()
|
|
325
|
+
const MIN_FOLDER_LABEL_PX = 28
|
|
326
|
+
|
|
327
|
+
function folderLabelClass(
|
|
328
|
+
folder: PlacedFolder,
|
|
329
|
+
highlightedFolders: Partial<Record<string, ChangeKind>> | undefined,
|
|
330
|
+
selectedFolder: string | null,
|
|
331
|
+
) {
|
|
332
|
+
return [
|
|
333
|
+
'map-folder-label',
|
|
334
|
+
highlightedFolders?.[folder.path]
|
|
335
|
+
? `map-folder-label-${highlightedFolders[folder.path]}`
|
|
336
|
+
: folder.added
|
|
337
|
+
? 'map-folder-label-added'
|
|
338
|
+
: selectedFolder === folder.path
|
|
339
|
+
? 'map-folder-label-selected'
|
|
340
|
+
: '',
|
|
341
|
+
]
|
|
342
|
+
.filter(Boolean)
|
|
343
|
+
.join(' ')
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function MapFolderLabels({
|
|
347
|
+
folders,
|
|
348
|
+
highlightedFolders,
|
|
349
|
+
selectedFolder,
|
|
350
|
+
}: {
|
|
351
|
+
folders: Record<string, PlacedFolder>
|
|
352
|
+
highlightedFolders?: Partial<Record<string, ChangeKind>>
|
|
353
|
+
selectedFolder: string | null
|
|
354
|
+
}) {
|
|
355
|
+
const camera = useThree((state) => state.camera)
|
|
356
|
+
const gl = useThree((state) => state.gl)
|
|
357
|
+
const size = useThree((state) => state.size)
|
|
358
|
+
const layerRef = useRef<HTMLDivElement>(null)
|
|
359
|
+
const items = useMemo(() => Object.values(folders), [folders])
|
|
360
|
+
const [host, setHost] = useState<HTMLElement | null>(null)
|
|
361
|
+
|
|
362
|
+
useLayoutEffect(() => {
|
|
363
|
+
setHost(gl.domElement.parentElement)
|
|
364
|
+
}, [gl])
|
|
365
|
+
|
|
366
|
+
useFrame(() => {
|
|
367
|
+
const layer = layerRef.current
|
|
368
|
+
if (!layer) return
|
|
369
|
+
const zoom = 'zoom' in camera ? Number(camera.zoom) : 1
|
|
370
|
+
const nodes = layer.children
|
|
371
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
372
|
+
const el = nodes[i] as HTMLElement | undefined
|
|
373
|
+
const folder = items[i]
|
|
374
|
+
if (!el || !folder) continue
|
|
375
|
+
PROJECT.set(folder.x, 14, folder.z + 1.35).project(camera)
|
|
376
|
+
const x = (PROJECT.x * 0.5 + 0.5) * size.width
|
|
377
|
+
const y = (-PROJECT.y * 0.5 + 0.5) * size.height
|
|
378
|
+
const span = Math.max(folder.width, folder.depth) * zoom
|
|
379
|
+
const force =
|
|
380
|
+
selectedFolder === folder.path ||
|
|
381
|
+
Boolean(highlightedFolders?.[folder.path] || folder.added)
|
|
382
|
+
const onScreen =
|
|
383
|
+
PROJECT.z >= -1 &&
|
|
384
|
+
PROJECT.z <= 1 &&
|
|
385
|
+
x > -120 &&
|
|
386
|
+
x < size.width + 120 &&
|
|
387
|
+
y > -40 &&
|
|
388
|
+
y < size.height + 40
|
|
389
|
+
if (!onScreen || (!force && span < MIN_FOLDER_LABEL_PX)) {
|
|
390
|
+
if (el.style.visibility !== 'hidden') el.style.visibility = 'hidden'
|
|
391
|
+
continue
|
|
392
|
+
}
|
|
393
|
+
const tx = Math.round(x)
|
|
394
|
+
const ty = Math.round(y)
|
|
395
|
+
const next = `${tx},${ty}`
|
|
396
|
+
if (el.dataset.pos !== next) {
|
|
397
|
+
el.dataset.pos = next
|
|
398
|
+
el.style.transform = `translate3d(${tx}px, ${ty}px, 0) translate(-50%, -50%)`
|
|
399
|
+
}
|
|
400
|
+
if (el.style.visibility !== 'visible') el.style.visibility = 'visible'
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
if (!host) return null
|
|
405
|
+
|
|
406
|
+
return createPortal(
|
|
407
|
+
<div ref={layerRef} className="map-folder-label-layer">
|
|
408
|
+
{items.map((folder) => (
|
|
409
|
+
<div
|
|
410
|
+
key={folder.path}
|
|
411
|
+
className={folderLabelClass(folder, highlightedFolders, selectedFolder)}
|
|
412
|
+
>
|
|
413
|
+
<span className="map-folder-name">
|
|
414
|
+
{folderKindLabel(
|
|
415
|
+
folder.name,
|
|
416
|
+
highlightedFolders?.[folder.path] ?? null,
|
|
417
|
+
folder.added ?? false,
|
|
418
|
+
)}
|
|
419
|
+
</span>
|
|
420
|
+
</div>
|
|
421
|
+
))}
|
|
422
|
+
</div>,
|
|
423
|
+
host,
|
|
424
|
+
)
|
|
425
|
+
}
|
|
426
|
+
|
|
346
427
|
function folderKindLabel(
|
|
347
428
|
name: string,
|
|
348
429
|
kind: ChangeKind | null | undefined,
|
|
@@ -425,16 +425,20 @@ function CameraRig({
|
|
|
425
425
|
function ThumbnailLabelFilter({
|
|
426
426
|
candidates,
|
|
427
427
|
zoom,
|
|
428
|
+
frozenRef,
|
|
428
429
|
onChange,
|
|
429
430
|
}: {
|
|
430
431
|
candidates: LabelCandidate[]
|
|
431
432
|
zoom: number
|
|
433
|
+
frozenRef: { current: boolean }
|
|
432
434
|
onChange: (ids: Set<string>) => void
|
|
433
435
|
}) {
|
|
434
|
-
const
|
|
436
|
+
const camera = useThree((state) => state.camera)
|
|
437
|
+
const size = useThree((state) => state.size)
|
|
435
438
|
const visibleRef = useRef<Set<string>>(new Set())
|
|
436
439
|
|
|
437
440
|
useFrame(() => {
|
|
441
|
+
if (frozenRef.current) return
|
|
438
442
|
const next = visibleThumbnailLabels(
|
|
439
443
|
candidates,
|
|
440
444
|
camera,
|
|
@@ -464,6 +468,7 @@ function ThumbnailScene({
|
|
|
464
468
|
panRef,
|
|
465
469
|
orbitRef,
|
|
466
470
|
cameraRef,
|
|
471
|
+
frozenRef,
|
|
467
472
|
visibleLabelIds,
|
|
468
473
|
onVisibleLabels,
|
|
469
474
|
}: {
|
|
@@ -480,6 +485,7 @@ function ThumbnailScene({
|
|
|
480
485
|
panRef: { current: Vec3 }
|
|
481
486
|
orbitRef: { current: Orbit }
|
|
482
487
|
cameraRef: { current: THREE.Camera | null }
|
|
488
|
+
frozenRef: { current: boolean }
|
|
483
489
|
visibleLabelIds: Set<string>
|
|
484
490
|
onVisibleLabels: (ids: Set<string>) => void
|
|
485
491
|
}) {
|
|
@@ -541,6 +547,7 @@ function ThumbnailScene({
|
|
|
541
547
|
<ThumbnailLabelFilter
|
|
542
548
|
candidates={labelCandidates}
|
|
543
549
|
zoom={zoom}
|
|
550
|
+
frozenRef={frozenRef}
|
|
544
551
|
onChange={onVisibleLabels}
|
|
545
552
|
/>
|
|
546
553
|
<FolderArea
|
|
@@ -977,6 +984,7 @@ export function SelectionThumbnail({
|
|
|
977
984
|
panRef={panRef}
|
|
978
985
|
orbitRef={orbitRef}
|
|
979
986
|
cameraRef={cameraRef}
|
|
987
|
+
frozenRef={panningRef}
|
|
980
988
|
visibleLabelIds={visibleLabelIds}
|
|
981
989
|
onVisibleLabels={setVisibleLabelIds}
|
|
982
990
|
/>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from 'react'
|
|
2
|
+
import { useFrame, useThree } from '@react-three/fiber'
|
|
3
|
+
import * as THREE from 'three'
|
|
4
|
+
import type { PlacedBridge, PlacedFile, PlacedFolder } from '../types'
|
|
5
|
+
import {
|
|
6
|
+
computeWalkLod,
|
|
7
|
+
sameWalkLod,
|
|
8
|
+
walkLodCell,
|
|
9
|
+
type WalkLod,
|
|
10
|
+
} from './walkLod'
|
|
11
|
+
|
|
12
|
+
const look = new THREE.Vector3()
|
|
13
|
+
|
|
14
|
+
type WalkLodTrackerProps = {
|
|
15
|
+
files: Record<string, PlacedFile>
|
|
16
|
+
folders: Record<string, PlacedFolder>
|
|
17
|
+
bridges: PlacedBridge[]
|
|
18
|
+
keepFileIds: Set<string>
|
|
19
|
+
keepFolderPaths: Set<string>
|
|
20
|
+
origin: [number, number]
|
|
21
|
+
onChange: (lod: WalkLod) => void
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function WalkLodTracker({
|
|
25
|
+
files,
|
|
26
|
+
folders,
|
|
27
|
+
bridges,
|
|
28
|
+
keepFileIds,
|
|
29
|
+
keepFolderPaths,
|
|
30
|
+
origin,
|
|
31
|
+
onChange,
|
|
32
|
+
}: WalkLodTrackerProps) {
|
|
33
|
+
const { camera } = useThree()
|
|
34
|
+
const prev = useRef<WalkLod | null>(null)
|
|
35
|
+
const cell = useRef('')
|
|
36
|
+
const keepFilesRef = useRef(keepFileIds)
|
|
37
|
+
const keepFoldersRef = useRef(keepFolderPaths)
|
|
38
|
+
const filesRef = useRef(files)
|
|
39
|
+
const foldersRef = useRef(folders)
|
|
40
|
+
const bridgesRef = useRef(bridges)
|
|
41
|
+
const onChangeRef = useRef(onChange)
|
|
42
|
+
keepFilesRef.current = keepFileIds
|
|
43
|
+
keepFoldersRef.current = keepFolderPaths
|
|
44
|
+
filesRef.current = files
|
|
45
|
+
foldersRef.current = folders
|
|
46
|
+
bridgesRef.current = bridges
|
|
47
|
+
onChangeRef.current = onChange
|
|
48
|
+
|
|
49
|
+
const publish = (x: number, z: number, y: number, lookX: number, lookZ: number) => {
|
|
50
|
+
const next = computeWalkLod({
|
|
51
|
+
x,
|
|
52
|
+
z,
|
|
53
|
+
y,
|
|
54
|
+
lookX,
|
|
55
|
+
lookZ,
|
|
56
|
+
files: filesRef.current,
|
|
57
|
+
folders: foldersRef.current,
|
|
58
|
+
bridges: bridgesRef.current,
|
|
59
|
+
keepFileIds: keepFilesRef.current,
|
|
60
|
+
keepFolderPaths: keepFoldersRef.current,
|
|
61
|
+
prev: prev.current,
|
|
62
|
+
})
|
|
63
|
+
if (sameWalkLod(prev.current, next)) return
|
|
64
|
+
prev.current = next
|
|
65
|
+
onChangeRef.current(next)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const keepKey = `${[...keepFileIds].join('|')}|${[...keepFolderPaths].join('|')}`
|
|
69
|
+
|
|
70
|
+
useLayoutEffect(() => {
|
|
71
|
+
camera.getWorldDirection(look)
|
|
72
|
+
const lx = look.x
|
|
73
|
+
const lz = look.z
|
|
74
|
+
if (Math.hypot(lx, lz) < 0.001) {
|
|
75
|
+
publish(origin[0], origin[1], camera.position.y, 0, 1)
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
publish(camera.position.x, camera.position.z, camera.position.y, lx, lz)
|
|
79
|
+
}, [bridges, camera, files, folders, keepKey, origin])
|
|
80
|
+
|
|
81
|
+
useFrame(() => {
|
|
82
|
+
camera.getWorldDirection(look)
|
|
83
|
+
const lx = look.x
|
|
84
|
+
const lz = look.z
|
|
85
|
+
const nextCell = `${walkLodCell(camera.position.x, camera.position.z, camera.position.y, lx, lz)}:${keepKey}`
|
|
86
|
+
if (nextCell === cell.current) return
|
|
87
|
+
cell.current = nextCell
|
|
88
|
+
publish(camera.position.x, camera.position.z, camera.position.y, lx, lz)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react'
|
|
1
2
|
import { FolderArea } from './FolderArea'
|
|
2
3
|
import { FileBlock } from './FileBlock'
|
|
4
|
+
import { DistantFileBlocks } from './DistantFileBlocks'
|
|
3
5
|
import { Bridge } from './Bridge'
|
|
4
6
|
import { RelationLines } from './RelationLines'
|
|
5
7
|
import { Player } from './Player'
|
|
@@ -8,6 +10,8 @@ import { SelectionController } from './SelectionController'
|
|
|
8
10
|
import { UserContextTracker } from './UserContextTracker'
|
|
9
11
|
import { BlockPlacer } from './BlockPlacer'
|
|
10
12
|
import { IslandPlacer } from './IslandPlacer'
|
|
13
|
+
import { WalkLodTracker } from './WalkLodTracker'
|
|
14
|
+
import { computeWalkLod, type WalkLod } from './walkLod'
|
|
11
15
|
import {
|
|
12
16
|
fileChangeKind,
|
|
13
17
|
filesImporting,
|
|
@@ -152,6 +156,59 @@ export function World({
|
|
|
152
156
|
deleted,
|
|
153
157
|
viewLayout.folders,
|
|
154
158
|
)
|
|
159
|
+
const ghostKey = Object.keys(ghosts).join('|')
|
|
160
|
+
const keepFileIds = useMemo(() => {
|
|
161
|
+
const ids = new Set<string>()
|
|
162
|
+
if (selectedId) ids.add(selectedId)
|
|
163
|
+
if (namingId) ids.add(namingId)
|
|
164
|
+
if (aimedRelation?.flyTo) ids.add(aimedRelation.flyTo)
|
|
165
|
+
if (ghostKey) {
|
|
166
|
+
for (const id of ghostKey.split('|')) ids.add(id)
|
|
167
|
+
}
|
|
168
|
+
return ids
|
|
169
|
+
}, [aimedRelation?.flyTo, ghostKey, namingId, selectedId])
|
|
170
|
+
const keepFolderPaths = useMemo(() => {
|
|
171
|
+
const paths = new Set<string>()
|
|
172
|
+
if (selectedFolder) paths.add(selectedFolder)
|
|
173
|
+
if (namingIslandId) paths.add(namingIslandId)
|
|
174
|
+
return paths
|
|
175
|
+
}, [namingIslandId, selectedFolder])
|
|
176
|
+
const originLod = useMemo(() => {
|
|
177
|
+
if (mapping) return null
|
|
178
|
+
return computeWalkLod({
|
|
179
|
+
x: landAt[0],
|
|
180
|
+
z: landAt[1],
|
|
181
|
+
lookX: 0,
|
|
182
|
+
lookZ: 1,
|
|
183
|
+
files: layout.files,
|
|
184
|
+
folders: layout.folders,
|
|
185
|
+
bridges: layout.bridges,
|
|
186
|
+
keepFileIds,
|
|
187
|
+
keepFolderPaths,
|
|
188
|
+
prev: null,
|
|
189
|
+
})
|
|
190
|
+
}, [
|
|
191
|
+
keepFileIds,
|
|
192
|
+
keepFolderPaths,
|
|
193
|
+
landAt,
|
|
194
|
+
layout.bridges,
|
|
195
|
+
layout.files,
|
|
196
|
+
layout.folders,
|
|
197
|
+
mapping,
|
|
198
|
+
])
|
|
199
|
+
const landSig = `${landAt[0]},${landAt[1]}`
|
|
200
|
+
const [lodLand, setLodLand] = useState(landSig)
|
|
201
|
+
const [walkLod, setWalkLod] = useState<WalkLod | null>(null)
|
|
202
|
+
if (lodLand !== landSig) {
|
|
203
|
+
setLodLand(landSig)
|
|
204
|
+
setWalkLod(null)
|
|
205
|
+
}
|
|
206
|
+
const lod = mapping ? null : (walkLod ?? originLod)
|
|
207
|
+
const distantFiles: {
|
|
208
|
+
file: FileNode
|
|
209
|
+
placed: PlacedFile
|
|
210
|
+
dimmed: boolean
|
|
211
|
+
}[] = []
|
|
155
212
|
|
|
156
213
|
return (
|
|
157
214
|
<>
|
|
@@ -178,21 +235,28 @@ export function World({
|
|
|
178
235
|
}
|
|
179
236
|
/>
|
|
180
237
|
|
|
181
|
-
{Object.values(viewLayout.folders).map((folder) =>
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
mapping
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
238
|
+
{Object.values(viewLayout.folders).map((folder) => {
|
|
239
|
+
if (lod && !lod.folders.has(folder.path)) return null
|
|
240
|
+
return (
|
|
241
|
+
<FolderArea
|
|
242
|
+
key={folder.path}
|
|
243
|
+
folder={folder}
|
|
244
|
+
naming={folder.path === namingIslandId}
|
|
245
|
+
selected={folder.path === selectedFolder}
|
|
246
|
+
mapMode={mapping}
|
|
247
|
+
highlightKind={
|
|
248
|
+
mapping ? highlightedFolders[folder.path] ?? null : null
|
|
249
|
+
}
|
|
250
|
+
labelVisible={!lod || lod.folderLabels.has(folder.path)}
|
|
251
|
+
/>
|
|
252
|
+
)
|
|
253
|
+
})}
|
|
254
|
+
{viewLayout.bridges.map((bridge) => {
|
|
255
|
+
if (lod && !lod.bridges.has(bridge.id)) return null
|
|
256
|
+
return (
|
|
257
|
+
<Bridge key={bridge.id} bridge={bridge} folders={viewLayout.folders} />
|
|
258
|
+
)
|
|
259
|
+
})}
|
|
196
260
|
{viewGraph.files.map((file) => {
|
|
197
261
|
const placed = viewLayout.files[file.id]
|
|
198
262
|
if (!placed) return null
|
|
@@ -201,6 +265,21 @@ export function World({
|
|
|
201
265
|
const isPlanned = planned.has(file.id) || deleted.has(file.id)
|
|
202
266
|
const changeKind = fileChangeKind(file.id, planned, created, deleted)
|
|
203
267
|
const naming = file.id === namingId
|
|
268
|
+
const aimed = file.id === aimedRelation?.flyTo
|
|
269
|
+
const detailed =
|
|
270
|
+
selected || isRelated || isPlanned || naming || aimed || Boolean(changeKind)
|
|
271
|
+
if (lod && !lod.files.has(file.id)) return null
|
|
272
|
+
const dimmed =
|
|
273
|
+
hasFocus &&
|
|
274
|
+
!selected &&
|
|
275
|
+
!isRelated &&
|
|
276
|
+
!changeKind &&
|
|
277
|
+
!patchLinked.has(file.id) &&
|
|
278
|
+
!folderFileIds.has(file.id)
|
|
279
|
+
if (lod && !detailed && !lod.labels.has(file.id)) {
|
|
280
|
+
distantFiles.push({ file, placed, dimmed })
|
|
281
|
+
return null
|
|
282
|
+
}
|
|
204
283
|
return (
|
|
205
284
|
<FileBlock
|
|
206
285
|
key={file.id}
|
|
@@ -211,17 +290,11 @@ export function World({
|
|
|
211
290
|
planned={isPlanned}
|
|
212
291
|
changeKind={changeKind}
|
|
213
292
|
added={created.has(file.id) || file.userCreated}
|
|
214
|
-
aimed={
|
|
215
|
-
dimmed={
|
|
216
|
-
hasFocus &&
|
|
217
|
-
!selected &&
|
|
218
|
-
!isRelated &&
|
|
219
|
-
!changeKind &&
|
|
220
|
-
!patchLinked.has(file.id) &&
|
|
221
|
-
!folderFileIds.has(file.id)
|
|
222
|
-
}
|
|
293
|
+
aimed={aimed}
|
|
294
|
+
dimmed={dimmed}
|
|
223
295
|
naming={naming}
|
|
224
296
|
mapMode={mapping}
|
|
297
|
+
labelVisible={!lod || lod.labels.has(file.id) || naming}
|
|
225
298
|
onCommitName={
|
|
226
299
|
naming && onCommitName
|
|
227
300
|
? (name) => {
|
|
@@ -235,6 +308,7 @@ export function World({
|
|
|
235
308
|
/>
|
|
236
309
|
)
|
|
237
310
|
})}
|
|
311
|
+
<DistantFileBlocks items={distantFiles} />
|
|
238
312
|
{Object.values(ghosts).map((placed) => {
|
|
239
313
|
const file: FileNode = {
|
|
240
314
|
id: placed.id,
|
|
@@ -283,6 +357,17 @@ export function World({
|
|
|
283
357
|
onWalkPosition={onWalkPosition}
|
|
284
358
|
flyTo={flyTo}
|
|
285
359
|
/>
|
|
360
|
+
{!mapping && (
|
|
361
|
+
<WalkLodTracker
|
|
362
|
+
files={layout.files}
|
|
363
|
+
folders={layout.folders}
|
|
364
|
+
bridges={layout.bridges}
|
|
365
|
+
keepFileIds={keepFileIds}
|
|
366
|
+
keepFolderPaths={keepFolderPaths}
|
|
367
|
+
origin={landAt}
|
|
368
|
+
onChange={setWalkLod}
|
|
369
|
+
/>
|
|
370
|
+
)}
|
|
286
371
|
{onPlaceBlock && (
|
|
287
372
|
<BlockPlacer
|
|
288
373
|
enabled={!mapping && !placing}
|