@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
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ Sessions start in the map: click **Setup LLM session**, type an **initial instru
|
|
|
53
53
|
|
|
54
54
|
A normal chat request does not open a session. Use `/inbase` after Setup LLM session, or `/skipinbase [request]` to work outside the map. `/inbase` starts the session immediately; `wait-for-blueprint` only reads the optional blueprint.
|
|
55
55
|
|
|
56
|
-
If several sessions are open,
|
|
56
|
+
If several sessions are open, `/inbase` attaches the newest session that is still waiting. Sessions that already have an LLM are skipped, and the map window does not need to be focused.
|
|
57
57
|
|
|
58
58
|
Turn on **Make LLM look where I look** if the agent should prefer the island you are standing on and the blocks you are facing. With **Step by step** on, click **Create proposal** to start a step, then **Accept proposal** when the patch is ready. With it off, the LLM implements the full plan; you can still walk Previous/Next over the diffs, then **Accept proposal**. Send an alternative instruction from the HUD to revise the remaining plan, or **Stop** to end the session.
|
|
59
59
|
|
|
@@ -130,6 +130,8 @@ export function setInitialInstruction(
|
|
|
130
130
|
instruction: string | null | undefined,
|
|
131
131
|
): DiffManifest
|
|
132
132
|
export function readAttachedSession(dataDir: string): string | null
|
|
133
|
+
export function listAttachQueue(dataDir: string): string[]
|
|
134
|
+
export function nextAttachSessionId(dataDir: string): string | null
|
|
133
135
|
export type SessionBlueprint = {
|
|
134
136
|
enabled: boolean
|
|
135
137
|
sent: boolean
|
|
@@ -341,6 +341,74 @@ export function focusSession(dataDir, sessionId) {
|
|
|
341
341
|
return safeId
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
function attachQueueFile(dataDir) {
|
|
345
|
+
return path.join(dataDir, 'attach-queue.json')
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function readStoredAttachQueue(dataDir) {
|
|
349
|
+
const value = readJson(attachQueueFile(dataDir), null)
|
|
350
|
+
const ids = Array.isArray(value?.sessionIds) ? value.sessionIds : []
|
|
351
|
+
const result = []
|
|
352
|
+
const seen = new Set()
|
|
353
|
+
for (const id of ids) {
|
|
354
|
+
try {
|
|
355
|
+
const safeId = assertSessionId(id)
|
|
356
|
+
if (seen.has(safeId)) continue
|
|
357
|
+
seen.add(safeId)
|
|
358
|
+
result.push(safeId)
|
|
359
|
+
} catch {
|
|
360
|
+
// Skip invalid ids.
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return result
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function writeAttachQueue(dataDir, sessionIds) {
|
|
367
|
+
atomicWrite(
|
|
368
|
+
attachQueueFile(dataDir),
|
|
369
|
+
`${JSON.stringify({ sessionIds }, null, 2)}\n`,
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function sessionIsWaitingToAttach(manifest) {
|
|
374
|
+
return Boolean(manifest?.awaitingAttach)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function listAttachQueue(dataDir) {
|
|
378
|
+
const recorded = readStoredAttachQueue(dataDir)
|
|
379
|
+
const waiting = new Set()
|
|
380
|
+
for (const sessionId of listOpenSessionIds(dataDir)) {
|
|
381
|
+
if (sessionIsWaitingToAttach(readManifest(dataDir, sessionId))) {
|
|
382
|
+
waiting.add(sessionId)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const queued = recorded.filter((sessionId) => waiting.has(sessionId))
|
|
386
|
+
const queuedSet = new Set(queued)
|
|
387
|
+
const missing = []
|
|
388
|
+
for (const sessionId of listOpenSessionIds(dataDir)) {
|
|
389
|
+
if (waiting.has(sessionId) && !queuedSet.has(sessionId)) {
|
|
390
|
+
missing.push(sessionId)
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
missing.reverse()
|
|
394
|
+
const next = [...queued, ...missing]
|
|
395
|
+
const unchanged =
|
|
396
|
+
next.length === recorded.length &&
|
|
397
|
+
next.every((sessionId, index) => sessionId === recorded[index])
|
|
398
|
+
if (!unchanged) writeAttachQueue(dataDir, next)
|
|
399
|
+
return next
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function nextAttachSessionId(dataDir) {
|
|
403
|
+
return listAttachQueue(dataDir)[0] ?? null
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function enqueueAttachSession(dataDir, sessionId) {
|
|
407
|
+
const safeId = assertSessionId(sessionId)
|
|
408
|
+
const rest = listAttachQueue(dataDir).filter((id) => id !== safeId)
|
|
409
|
+
writeAttachQueue(dataDir, [safeId, ...rest])
|
|
410
|
+
}
|
|
411
|
+
|
|
344
412
|
function sessionAllowsPlacement(manifest) {
|
|
345
413
|
return (
|
|
346
414
|
manifest.phase !== 'blueprint_ask' &&
|
|
@@ -912,6 +980,7 @@ export function setupSession(dataDir, input = {}) {
|
|
|
912
980
|
sent: false,
|
|
913
981
|
})
|
|
914
982
|
focusSession(dataDir, sessionId)
|
|
983
|
+
enqueueAttachSession(dataDir, sessionId)
|
|
915
984
|
return manifest
|
|
916
985
|
}
|
|
917
986
|
|
|
@@ -952,10 +1021,10 @@ export function readAttachedSession(dataDir) {
|
|
|
952
1021
|
export function attachSession(dataDir, sessionId) {
|
|
953
1022
|
const safeId = sessionId
|
|
954
1023
|
? assertSessionId(sessionId)
|
|
955
|
-
:
|
|
1024
|
+
: nextAttachSessionId(dataDir)
|
|
956
1025
|
if (!safeId) {
|
|
957
1026
|
throw new Error(
|
|
958
|
-
'No visualizer session is
|
|
1027
|
+
'No visualizer session is waiting to attach. Click Setup LLM session in the map, then /inbase.',
|
|
959
1028
|
)
|
|
960
1029
|
}
|
|
961
1030
|
const manifest = requireManifest(
|
|
@@ -966,14 +1035,6 @@ export function attachSession(dataDir, sessionId) {
|
|
|
966
1035
|
if (isTerminalSession(manifest)) {
|
|
967
1036
|
throw sessionStoppedError(safeId)
|
|
968
1037
|
}
|
|
969
|
-
const attached = readAttachedSession(dataDir)
|
|
970
|
-
if (attached && attached !== safeId) {
|
|
971
|
-
const other = readManifest(dataDir, attached)
|
|
972
|
-
const label = resolvedSessionName(other) || attached
|
|
973
|
-
throw new Error(
|
|
974
|
-
`An LLM is already attached to ${label}. Stop that session before attaching another.`,
|
|
975
|
-
)
|
|
976
|
-
}
|
|
977
1038
|
focusSession(dataDir, safeId)
|
|
978
1039
|
touchSessionConnection(dataDir, safeId)
|
|
979
1040
|
recordSessionAck(dataDir, safeId, 'attached', resolvedSessionName(manifest) || safeId)
|
|
@@ -1435,6 +1496,7 @@ export function clearDiffSessions(dataDir, targetRoot = null) {
|
|
|
1435
1496
|
}
|
|
1436
1497
|
writeActiveSession(dataDir, null)
|
|
1437
1498
|
writeBlueprintSession(dataDir, null)
|
|
1499
|
+
writeAttachQueue(dataDir, [])
|
|
1438
1500
|
|
|
1439
1501
|
const root = diffSessionsRoot(dataDir)
|
|
1440
1502
|
fs.mkdirSync(root, { recursive: true })
|
|
@@ -156,6 +156,9 @@ function Explorer({
|
|
|
156
156
|
}) {
|
|
157
157
|
const [intents, setIntents] = useState<AgentIntent[]>([])
|
|
158
158
|
const [focusedSessionId, setFocusedSessionId] = useState<string | null>(null)
|
|
159
|
+
const [nextAttachSessionId, setNextAttachSessionId] = useState<string | null>(
|
|
160
|
+
null,
|
|
161
|
+
)
|
|
159
162
|
const [wantBranchChanges, setWantBranchChanges] = useState(false)
|
|
160
163
|
const [branchChanges, setBranchChanges] = useState(emptyBranchChanges)
|
|
161
164
|
const intent =
|
|
@@ -1182,6 +1185,8 @@ function Explorer({
|
|
|
1182
1185
|
const poll = async () => {
|
|
1183
1186
|
try {
|
|
1184
1187
|
const bundle = await fetchAgentIntents()
|
|
1188
|
+
if (cancelled) return
|
|
1189
|
+
setNextAttachSessionId(bundle.nextAttachSessionId)
|
|
1185
1190
|
const merged: AgentIntent[] = []
|
|
1186
1191
|
for (const next of bundle.intents) {
|
|
1187
1192
|
const sessionId = next.sessionId
|
|
@@ -1328,6 +1333,7 @@ function Explorer({
|
|
|
1328
1333
|
intent={intent}
|
|
1329
1334
|
intents={intents}
|
|
1330
1335
|
focusedSessionId={focusedSessionId}
|
|
1336
|
+
nextAttachSessionId={nextAttachSessionId}
|
|
1331
1337
|
onFocusSession={focusSessionPanel}
|
|
1332
1338
|
onSetupSession={setupLlmSession}
|
|
1333
1339
|
onWorkflowAction={runWorkflowAction}
|
|
@@ -164,9 +164,10 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
|
|
|
164
164
|
export async function fetchAgentIntents(): Promise<AgentIntentBundle> {
|
|
165
165
|
const query = new URLSearchParams({ t: String(Date.now()) })
|
|
166
166
|
const response = await fetch(`/api/agent-intent?${query}`)
|
|
167
|
-
if (!response.ok) return { focusedSessionId: null, intents: [] }
|
|
167
|
+
if (!response.ok) return { focusedSessionId: null, nextAttachSessionId: null, intents: [] }
|
|
168
168
|
const data = (await response.json()) as {
|
|
169
169
|
focusedSessionId?: string | null
|
|
170
|
+
nextAttachSessionId?: string | null
|
|
170
171
|
intents?: unknown
|
|
171
172
|
sessionId?: string | null
|
|
172
173
|
} & Partial<AgentIntent>
|
|
@@ -174,6 +175,10 @@ export async function fetchAgentIntents(): Promise<AgentIntentBundle> {
|
|
|
174
175
|
return {
|
|
175
176
|
focusedSessionId:
|
|
176
177
|
typeof data.focusedSessionId === 'string' ? data.focusedSessionId : null,
|
|
178
|
+
nextAttachSessionId:
|
|
179
|
+
typeof data.nextAttachSessionId === 'string'
|
|
180
|
+
? data.nextAttachSessionId
|
|
181
|
+
: null,
|
|
177
182
|
intents: data.intents
|
|
178
183
|
.map((intent) => normalize(intent as Partial<AgentIntent>))
|
|
179
184
|
.filter((intent) => Boolean(intent.sessionId)),
|
|
@@ -182,6 +187,7 @@ export async function fetchAgentIntents(): Promise<AgentIntentBundle> {
|
|
|
182
187
|
const intent = normalize(data)
|
|
183
188
|
return {
|
|
184
189
|
focusedSessionId: intent.sessionId,
|
|
190
|
+
nextAttachSessionId: intent.awaitingAttach ? intent.sessionId : null,
|
|
185
191
|
intents: intent.sessionId ? [intent] : [],
|
|
186
192
|
}
|
|
187
193
|
}
|
|
@@ -202,7 +202,18 @@ button {
|
|
|
202
202
|
letter-spacing: 0.02em;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
.map-folder-label-layer {
|
|
206
|
+
position: absolute;
|
|
207
|
+
inset: 0;
|
|
208
|
+
overflow: hidden;
|
|
209
|
+
pointer-events: none;
|
|
210
|
+
z-index: 80;
|
|
211
|
+
}
|
|
212
|
+
|
|
205
213
|
.map-folder-label {
|
|
214
|
+
position: absolute;
|
|
215
|
+
top: 0;
|
|
216
|
+
left: 0;
|
|
206
217
|
display: grid;
|
|
207
218
|
min-width: max-content;
|
|
208
219
|
padding: 4px 7px;
|
|
@@ -213,6 +224,8 @@ button {
|
|
|
213
224
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
|
|
214
225
|
text-align: center;
|
|
215
226
|
white-space: nowrap;
|
|
227
|
+
visibility: hidden;
|
|
228
|
+
will-change: transform;
|
|
216
229
|
}
|
|
217
230
|
|
|
218
231
|
.map-folder-name {
|
|
@@ -561,14 +574,38 @@ button {
|
|
|
561
574
|
}
|
|
562
575
|
|
|
563
576
|
.hud-session-tab {
|
|
577
|
+
display: inline-flex;
|
|
578
|
+
align-items: center;
|
|
579
|
+
justify-content: center;
|
|
580
|
+
gap: 7px;
|
|
564
581
|
flex: 1 1 0;
|
|
565
582
|
min-width: 0;
|
|
566
583
|
overflow: hidden;
|
|
567
|
-
text-overflow: ellipsis;
|
|
568
584
|
white-space: nowrap;
|
|
569
585
|
font-size: 13px;
|
|
570
586
|
}
|
|
571
587
|
|
|
588
|
+
.hud-session-tab-label {
|
|
589
|
+
min-width: 0;
|
|
590
|
+
overflow: hidden;
|
|
591
|
+
text-overflow: ellipsis;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
.hud-session-tab[data-attached='true'] {
|
|
595
|
+
border-color: #6ee7b7;
|
|
596
|
+
color: #b6f3d6;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.hud-session-tab[data-attached='true'] .hud-attach-dot {
|
|
600
|
+
background: #6ee7b7;
|
|
601
|
+
box-shadow: 0 0 8px #6ee7b7;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
.hud-session-tab[data-attached='false'] .hud-attach-dot {
|
|
605
|
+
background: #6b7280;
|
|
606
|
+
box-shadow: none;
|
|
607
|
+
}
|
|
608
|
+
|
|
572
609
|
.hud-left-stack .hud-panel-planned {
|
|
573
610
|
position: relative;
|
|
574
611
|
top: auto;
|
|
@@ -593,6 +630,14 @@ button {
|
|
|
593
630
|
box-shadow: 0 0 0 1px #9ad8ff;
|
|
594
631
|
}
|
|
595
632
|
|
|
633
|
+
.hud-left-stack .hud-panel-planned[data-attached='true'] {
|
|
634
|
+
box-shadow: 0 0 0 1px #6ee7b7;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.hud-left-stack .hud-panel-planned[data-attached='true'][data-focused='true'] {
|
|
638
|
+
box-shadow: 0 0 0 1px #6ee7b7, 0 0 0 3px rgba(110, 231, 183, 0.22);
|
|
639
|
+
}
|
|
640
|
+
|
|
596
641
|
.hud-panel-body {
|
|
597
642
|
min-height: 0;
|
|
598
643
|
flex: 1 1 auto;
|
|
@@ -665,6 +710,65 @@ button {
|
|
|
665
710
|
flex: 1 1 auto;
|
|
666
711
|
}
|
|
667
712
|
|
|
713
|
+
.hud-panel-chrome-title-row {
|
|
714
|
+
display: flex;
|
|
715
|
+
align-items: center;
|
|
716
|
+
gap: 8px;
|
|
717
|
+
min-width: 0;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
.hud-attach-badge {
|
|
721
|
+
display: inline-flex;
|
|
722
|
+
align-items: center;
|
|
723
|
+
gap: 6px;
|
|
724
|
+
flex: none;
|
|
725
|
+
padding: 2px 7px 2px 6px;
|
|
726
|
+
border: 1px solid rgba(110, 231, 183, 0.45);
|
|
727
|
+
border-radius: 999px;
|
|
728
|
+
background: rgba(110, 231, 183, 0.12);
|
|
729
|
+
color: #b6f3d6;
|
|
730
|
+
font-size: 10px;
|
|
731
|
+
font-weight: 700;
|
|
732
|
+
letter-spacing: 0.06em;
|
|
733
|
+
text-transform: uppercase;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.hud-attach-badge[data-attached='false'] {
|
|
737
|
+
border-color: rgba(255, 255, 255, 0.16);
|
|
738
|
+
background: rgba(255, 255, 255, 0.04);
|
|
739
|
+
color: #8b95a5;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
.hud-attach-dot {
|
|
743
|
+
width: 7px;
|
|
744
|
+
height: 7px;
|
|
745
|
+
flex: none;
|
|
746
|
+
border-radius: 50%;
|
|
747
|
+
background: #6ee7b7;
|
|
748
|
+
box-shadow: 0 0 8px #6ee7b7;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
.hud-attach-badge[data-attached='true'] .hud-attach-dot {
|
|
752
|
+
animation: hud-attach-pulse 1.6s ease infinite;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
.hud-attach-badge[data-attached='false'] .hud-attach-dot {
|
|
756
|
+
background: #6b7280;
|
|
757
|
+
box-shadow: none;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
@keyframes hud-attach-pulse {
|
|
761
|
+
0%,
|
|
762
|
+
100% {
|
|
763
|
+
opacity: 1;
|
|
764
|
+
box-shadow: 0 0 8px #6ee7b7;
|
|
765
|
+
}
|
|
766
|
+
50% {
|
|
767
|
+
opacity: 0.55;
|
|
768
|
+
box-shadow: 0 0 2px #6ee7b7;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
668
772
|
.hud-panel[data-minimized='true'] {
|
|
669
773
|
overflow: hidden;
|
|
670
774
|
padding-bottom: 12px;
|
|
@@ -676,6 +780,7 @@ button {
|
|
|
676
780
|
|
|
677
781
|
.hud-panel-chrome-title {
|
|
678
782
|
min-width: 0;
|
|
783
|
+
flex: 1 1 auto;
|
|
679
784
|
overflow: hidden;
|
|
680
785
|
color: #b7c0ce;
|
|
681
786
|
font-size: 11px;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useRef } from 'react'
|
|
2
|
+
import { Instances, Instance } from '@react-three/drei'
|
|
3
|
+
import { dimColor, fileColor } from '../theme'
|
|
4
|
+
import type { FileNode, PlacedFile } from '../types'
|
|
5
|
+
|
|
6
|
+
type DistantFile = {
|
|
7
|
+
file: FileNode
|
|
8
|
+
placed: PlacedFile
|
|
9
|
+
dimmed: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function DistantFileBlocks({
|
|
13
|
+
items,
|
|
14
|
+
}: {
|
|
15
|
+
items: DistantFile[]
|
|
16
|
+
}) {
|
|
17
|
+
const cap = useRef(2048)
|
|
18
|
+
if (items.length > cap.current) cap.current = items.length
|
|
19
|
+
if (items.length === 0) return null
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Instances
|
|
23
|
+
limit={cap.current}
|
|
24
|
+
range={items.length}
|
|
25
|
+
frustumCulled
|
|
26
|
+
raycast={() => {}}
|
|
27
|
+
>
|
|
28
|
+
<boxGeometry args={[1, 1, 1]} />
|
|
29
|
+
<meshLambertMaterial />
|
|
30
|
+
{items.map(({ file, placed, dimmed }) => {
|
|
31
|
+
const color = file.userCreated ? '#7ec8e8' : fileColor(file.language)
|
|
32
|
+
return (
|
|
33
|
+
<Instance
|
|
34
|
+
key={file.id}
|
|
35
|
+
position={placed.position}
|
|
36
|
+
scale={placed.size}
|
|
37
|
+
color={dimmed ? dimColor(color, 0.32) : color}
|
|
38
|
+
userData={{ fileId: file.id }}
|
|
39
|
+
/>
|
|
40
|
+
)
|
|
41
|
+
})}
|
|
42
|
+
</Instances>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { memo, Suspense } from 'react'
|
|
2
2
|
import { Billboard, Edges, Html, Text } from '@react-three/drei'
|
|
3
|
-
import { useFrame, useThree } from '@react-three/fiber'
|
|
4
3
|
import { CHANGE_HIGHLIGHT, CONFIG, dimColor, fileColor, FILE_SELECTION, MAP_SELECTION, type ChangeKind } from '../theme'
|
|
5
4
|
import { NameInput } from '../ui/NameInput'
|
|
6
5
|
import { MapSelectBorder } from './MapSelectBorder'
|
|
@@ -59,33 +58,27 @@ function MapChangeMark({
|
|
|
59
58
|
depth: number
|
|
60
59
|
height: number
|
|
61
60
|
}) {
|
|
62
|
-
const
|
|
63
|
-
const markRef = useRef<HTMLDivElement>(null)
|
|
64
|
-
|
|
65
|
-
useFrame(() => {
|
|
66
|
-
const el = markRef.current
|
|
67
|
-
if (!el) return
|
|
68
|
-
const zoom = 'zoom' in camera ? Number(camera.zoom) : 1
|
|
69
|
-
const px = Math.min(width, depth) * 0.78 * zoom
|
|
70
|
-
el.style.fontSize = `${Math.max(px, 1)}px`
|
|
71
|
-
})
|
|
72
|
-
|
|
61
|
+
const size = Math.min(width, depth) * 0.78
|
|
73
62
|
return (
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
63
|
+
<Suspense fallback={null}>
|
|
64
|
+
<Text
|
|
65
|
+
position={[0, height / 2 + 0.12, 0]}
|
|
66
|
+
rotation={[-Math.PI / 2, 0, 0]}
|
|
67
|
+
fontSize={size}
|
|
68
|
+
color="#f4f7fb"
|
|
69
|
+
anchorX="center"
|
|
70
|
+
anchorY="middle"
|
|
71
|
+
outlineWidth={size * 0.09}
|
|
72
|
+
outlineColor="#11151c"
|
|
73
|
+
renderOrder={10}
|
|
74
|
+
>
|
|
82
75
|
{mark}
|
|
83
|
-
</
|
|
84
|
-
</
|
|
76
|
+
</Text>
|
|
77
|
+
</Suspense>
|
|
85
78
|
)
|
|
86
79
|
}
|
|
87
80
|
|
|
88
|
-
export function FileBlock({
|
|
81
|
+
export const FileBlock = memo(function FileBlock({
|
|
89
82
|
file,
|
|
90
83
|
placed,
|
|
91
84
|
selected,
|
|
@@ -132,7 +125,7 @@ export function FileBlock({
|
|
|
132
125
|
? muted
|
|
133
126
|
: color
|
|
134
127
|
|
|
135
|
-
const showLabels = !naming && !mapMode
|
|
128
|
+
const showLabels = !naming && !mapMode && labelVisible
|
|
136
129
|
|
|
137
130
|
return (
|
|
138
131
|
<group position={placed.position}>
|
|
@@ -216,17 +209,15 @@ export function FileBlock({
|
|
|
216
209
|
{showLabels && (
|
|
217
210
|
<Suspense fallback={null}>
|
|
218
211
|
{previewLabels ? (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
>
|
|
227
|
-
|
|
228
|
-
</Html>
|
|
229
|
-
)
|
|
212
|
+
<Html
|
|
213
|
+
position={[0, height / 2 + 0.4, 0]}
|
|
214
|
+
center
|
|
215
|
+
occlude={false}
|
|
216
|
+
style={{ pointerEvents: 'none' }}
|
|
217
|
+
zIndexRange={[20, 0]}
|
|
218
|
+
>
|
|
219
|
+
<div className="thumbnail-block-label">{label}</div>
|
|
220
|
+
</Html>
|
|
230
221
|
) : (
|
|
231
222
|
<>
|
|
232
223
|
<Billboard position={[0, height / 2 + (planned || highlight ? 0.55 : 0.38), 0]}>
|
|
@@ -272,4 +263,4 @@ export function FileBlock({
|
|
|
272
263
|
)}
|
|
273
264
|
</group>
|
|
274
265
|
)
|
|
275
|
-
}
|
|
266
|
+
})
|
|
@@ -18,6 +18,7 @@ type FolderAreaProps = {
|
|
|
18
18
|
mapMode?: boolean
|
|
19
19
|
highlightKind?: ChangeKind | null
|
|
20
20
|
previewLabels?: boolean
|
|
21
|
+
labelVisible?: boolean
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export function FolderArea({
|
|
@@ -27,6 +28,7 @@ export function FolderArea({
|
|
|
27
28
|
mapMode = false,
|
|
28
29
|
highlightKind = null,
|
|
29
30
|
previewLabels = false,
|
|
31
|
+
labelVisible = true,
|
|
30
32
|
}: FolderAreaProps) {
|
|
31
33
|
const added = Boolean(folder.added)
|
|
32
34
|
const highlight = highlightKind ? CHANGE_HIGHLIGHT[highlightKind] : null
|
|
@@ -87,7 +89,7 @@ export function FolderArea({
|
|
|
87
89
|
</mesh>
|
|
88
90
|
)}
|
|
89
91
|
<Suspense fallback={null}>
|
|
90
|
-
{!naming && previewLabels && (
|
|
92
|
+
{!naming && previewLabels && labelVisible && (
|
|
91
93
|
<Html
|
|
92
94
|
position={[0, 1.35, -folder.depth / 2 + 1.6]}
|
|
93
95
|
center
|
|
@@ -98,7 +100,7 @@ export function FolderArea({
|
|
|
98
100
|
<div className="thumbnail-folder-label">{label}</div>
|
|
99
101
|
</Html>
|
|
100
102
|
)}
|
|
101
|
-
{!naming && !previewLabels && !mapMode && (
|
|
103
|
+
{!naming && !previewLabels && !mapMode && labelVisible && (
|
|
102
104
|
<Text
|
|
103
105
|
position={[0, 0.05, -folder.depth / 2 + 1.6]}
|
|
104
106
|
rotation={[-Math.PI / 2, 0, 0]}
|