@jkwd/inbase 0.1.5 → 0.1.6
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 +4 -1
- package/apps/explorer/scripts/patch-lib.d.ts +13 -0
- package/apps/explorer/scripts/patch-lib.mjs +162 -25
- package/apps/explorer/scripts/session-store.d.ts +18 -1
- package/apps/explorer/scripts/session-store.mjs +102 -26
- package/apps/explorer/src/App.tsx +153 -3
- package/apps/explorer/src/agentIntent.ts +9 -0
- package/apps/explorer/src/index.css +103 -11
- package/apps/explorer/src/layout.ts +65 -1
- package/apps/explorer/src/scene/Bridge.tsx +23 -5
- package/apps/explorer/src/scene/FileBlock.tsx +13 -9
- package/apps/explorer/src/scene/FolderArea.tsx +18 -12
- package/apps/explorer/src/scene/MapView.tsx +69 -12
- package/apps/explorer/src/scene/Player.tsx +9 -1
- package/apps/explorer/src/scene/SelectionController.tsx +36 -13
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +477 -33
- package/apps/explorer/src/scene/World.tsx +27 -10
- package/apps/explorer/src/theme.ts +10 -3
- package/apps/explorer/src/types.ts +5 -0
- package/apps/explorer/src/ui/HUD.tsx +294 -37
- package/apps/explorer/vite.config.ts +11 -1
- package/bin/session.mjs +18 -6
- package/package.json +1 -1
- package/skill/inbase/SKILL.md +13 -8
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
standInFront,
|
|
9
9
|
relationTravelTarget,
|
|
10
10
|
withPreviewGraph,
|
|
11
|
+
filterGraphToChangePaths,
|
|
12
|
+
mapPointOntoFolder,
|
|
11
13
|
} from './layout'
|
|
12
14
|
import { World } from './scene/World'
|
|
13
15
|
import { HUD } from './ui/HUD'
|
|
@@ -49,6 +51,7 @@ function intentSignature(intent: AgentIntent) {
|
|
|
49
51
|
updatedAt: intent.updatedAt,
|
|
50
52
|
status: intent.status,
|
|
51
53
|
phase: intent.phase,
|
|
54
|
+
stalledWait: intent.stalledWait,
|
|
52
55
|
sessionId: intent.sessionId,
|
|
53
56
|
creationMode: intent.creationMode,
|
|
54
57
|
diffId: intent.diffId,
|
|
@@ -61,6 +64,8 @@ function intentSignature(intent: AgentIntent) {
|
|
|
61
64
|
addedFunctions: intent.addedFunctions,
|
|
62
65
|
addedVariables: intent.addedVariables,
|
|
63
66
|
addedImports: intent.addedImports,
|
|
67
|
+
changedFunctions: intent.changedFunctions,
|
|
68
|
+
changedVariables: intent.changedVariables,
|
|
64
69
|
})
|
|
65
70
|
}
|
|
66
71
|
|
|
@@ -166,6 +171,59 @@ function Explorer({
|
|
|
166
171
|
if (previewing) markCreatedFolders(world, intent.createFolders ?? [])
|
|
167
172
|
return withUserCreatedLayout(world, userBlocks, userIslands)
|
|
168
173
|
}, [intent.createFolders, previewGraph, previewing, userBlocks, userIslands])
|
|
174
|
+
const changeFileIds = useMemo(() => {
|
|
175
|
+
const ids = new Set<string>()
|
|
176
|
+
for (const id of intent.files) ids.add(id)
|
|
177
|
+
for (const id of intent.creates) ids.add(id)
|
|
178
|
+
for (const id of intent.deletes) ids.add(id)
|
|
179
|
+
for (const block of userBlocks) ids.add(block.id)
|
|
180
|
+
for (const item of blueprintFunctions) ids.add(item.file)
|
|
181
|
+
for (const item of blueprintVariables) ids.add(item.file)
|
|
182
|
+
for (const item of blueprintImports) ids.add(item.file)
|
|
183
|
+
return [...ids]
|
|
184
|
+
}, [
|
|
185
|
+
blueprintFunctions,
|
|
186
|
+
blueprintImports,
|
|
187
|
+
blueprintVariables,
|
|
188
|
+
intent.creates,
|
|
189
|
+
intent.deletes,
|
|
190
|
+
intent.files,
|
|
191
|
+
userBlocks,
|
|
192
|
+
])
|
|
193
|
+
const changeFolderPaths = useMemo(() => {
|
|
194
|
+
const paths = new Set<string>()
|
|
195
|
+
for (const path of intent.createFolders ?? []) paths.add(path)
|
|
196
|
+
for (const island of userIslands) {
|
|
197
|
+
if (island.path) paths.add(island.path)
|
|
198
|
+
else if (island.id) paths.add(island.id)
|
|
199
|
+
}
|
|
200
|
+
return [...paths]
|
|
201
|
+
}, [intent.createFolders, userIslands])
|
|
202
|
+
const hasChangeSet =
|
|
203
|
+
changeFileIds.length > 0 || changeFolderPaths.length > 0
|
|
204
|
+
const changePathGraph = useMemo(() => {
|
|
205
|
+
if (!hasChangeSet) return displayGraph
|
|
206
|
+
return filterGraphToChangePaths(
|
|
207
|
+
displayGraph,
|
|
208
|
+
changeFileIds,
|
|
209
|
+
changeFolderPaths,
|
|
210
|
+
)
|
|
211
|
+
}, [changeFileIds, changeFolderPaths, displayGraph, hasChangeSet])
|
|
212
|
+
const changePathLayout = useMemo(() => {
|
|
213
|
+
if (!hasChangeSet) return layout
|
|
214
|
+
const world = layoutWorld(changePathGraph)
|
|
215
|
+
markCreatedFolders(world, [
|
|
216
|
+
...(intent.createFolders ?? []),
|
|
217
|
+
...userIslands.map((island) => island.path || island.id),
|
|
218
|
+
])
|
|
219
|
+
return world
|
|
220
|
+
}, [
|
|
221
|
+
changePathGraph,
|
|
222
|
+
hasChangeSet,
|
|
223
|
+
intent.createFolders,
|
|
224
|
+
layout,
|
|
225
|
+
userIslands,
|
|
226
|
+
])
|
|
169
227
|
const [mode, setMode] = useState<ViewMode>('map')
|
|
170
228
|
const [landAt, setLandAt] = useState<[number, number]>([
|
|
171
229
|
layout.spawn[0],
|
|
@@ -176,11 +234,14 @@ function Explorer({
|
|
|
176
234
|
const [selectedTick, setSelectedTick] = useState(0)
|
|
177
235
|
const [selectedFolder, setSelectedFolder] = useState<string | null>(null)
|
|
178
236
|
const [aimedRelation, setAimedRelation] = useState<AimedRelation | null>(null)
|
|
237
|
+
const [aimedFileId, setAimedFileId] = useState<string | null>(null)
|
|
238
|
+
const [inspectTick, setInspectTick] = useState(0)
|
|
179
239
|
const [flyTo, setFlyTo] = useState<FlyTo | null>(null)
|
|
180
240
|
const [locked, setLocked] = useState(false)
|
|
181
241
|
const [currentFolder, setCurrentFolder] = useState(graph.targetName)
|
|
182
242
|
const [followLook, setFollowLook] = useState(false)
|
|
183
243
|
const [importedBy, setImportedBy] = useState(false)
|
|
244
|
+
const [changePathsOnly, setChangePathsOnly] = useState(false)
|
|
184
245
|
const lastIntentSig = useRef<string | null>(null)
|
|
185
246
|
const viewedDiffId = useRef<Record<string, string | null>>({})
|
|
186
247
|
const browsingHistory = useRef<Record<string, boolean>>({})
|
|
@@ -241,6 +302,22 @@ function Explorer({
|
|
|
241
302
|
setMode('walk')
|
|
242
303
|
}, [])
|
|
243
304
|
|
|
305
|
+
const landFromMap = useCallback(
|
|
306
|
+
(x: number, z: number) => {
|
|
307
|
+
if (!changePathsOnly || !hasChangeSet) {
|
|
308
|
+
land(x, z)
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
const mapped = mapPointOntoFolder(x, z, changePathLayout, layout, [
|
|
312
|
+
layout.spawn[0],
|
|
313
|
+
layout.spawn[2],
|
|
314
|
+
])
|
|
315
|
+
if (!mapped) return
|
|
316
|
+
land(mapped[0], mapped[1])
|
|
317
|
+
},
|
|
318
|
+
[changePathLayout, changePathsOnly, hasChangeSet, land, layout],
|
|
319
|
+
)
|
|
320
|
+
|
|
244
321
|
const travelToFile = useCallback(
|
|
245
322
|
(fileId: string, fly: boolean) => {
|
|
246
323
|
const placed = layout.files[fileId]
|
|
@@ -274,7 +351,7 @@ function Explorer({
|
|
|
274
351
|
async (
|
|
275
352
|
sessionId: string,
|
|
276
353
|
action: WorkflowAction,
|
|
277
|
-
options: { instruction?: string; step?: number } = {},
|
|
354
|
+
options: { instruction?: string; step?: number; stepByStep?: boolean } = {},
|
|
278
355
|
) => {
|
|
279
356
|
const current = intents.find((item) => item.sessionId === sessionId)
|
|
280
357
|
if (!current?.sessionId) return
|
|
@@ -305,7 +382,12 @@ function Explorer({
|
|
|
305
382
|
browsingHistory.current[sessionId] = false
|
|
306
383
|
lastIntentSig.current = null
|
|
307
384
|
applyIntent(next, sessionId)
|
|
308
|
-
if (
|
|
385
|
+
if (
|
|
386
|
+
action === 'invoke' ||
|
|
387
|
+
action === 'continue' ||
|
|
388
|
+
action === 'stop' ||
|
|
389
|
+
action === 'set_step_by_step'
|
|
390
|
+
) {
|
|
309
391
|
await onRefreshGraph()
|
|
310
392
|
}
|
|
311
393
|
} catch {
|
|
@@ -631,11 +713,39 @@ function Explorer({
|
|
|
631
713
|
[creationMode],
|
|
632
714
|
)
|
|
633
715
|
|
|
716
|
+
const inspectBlock = useCallback(
|
|
717
|
+
(fileId: string) => {
|
|
718
|
+
selectFile(fileId)
|
|
719
|
+
setInspectTick((tick) => tick + 1)
|
|
720
|
+
},
|
|
721
|
+
[selectFile],
|
|
722
|
+
)
|
|
723
|
+
|
|
634
724
|
const selectFolder = useCallback((folderPath: string | null) => {
|
|
635
725
|
setSelectedFolder(folderPath)
|
|
636
726
|
if (folderPath) setSelectedId(null)
|
|
637
727
|
}, [])
|
|
638
728
|
|
|
729
|
+
useEffect(() => {
|
|
730
|
+
if (!changePathsOnly || !hasChangeSet) return
|
|
731
|
+
if (selectedFolder && !changePathLayout.folders[selectedFolder]) {
|
|
732
|
+
setSelectedFolder(null)
|
|
733
|
+
}
|
|
734
|
+
if (
|
|
735
|
+
selectedId &&
|
|
736
|
+
!changePathGraph.files.some((file) => file.id === selectedId)
|
|
737
|
+
) {
|
|
738
|
+
setSelectedId(null)
|
|
739
|
+
}
|
|
740
|
+
}, [
|
|
741
|
+
changePathGraph.files,
|
|
742
|
+
changePathLayout.folders,
|
|
743
|
+
changePathsOnly,
|
|
744
|
+
hasChangeSet,
|
|
745
|
+
selectedFolder,
|
|
746
|
+
selectedId,
|
|
747
|
+
])
|
|
748
|
+
|
|
639
749
|
const addBlueprintFunction = useCallback(
|
|
640
750
|
(fileId: string, rawName: string) => {
|
|
641
751
|
if (!creationMode || fileId.startsWith('draft:')) return false
|
|
@@ -850,6 +960,11 @@ function Explorer({
|
|
|
850
960
|
setImportedBy((current) => !current)
|
|
851
961
|
}, [])
|
|
852
962
|
|
|
963
|
+
const toggleChangePathsOnly = useCallback(() => {
|
|
964
|
+
if (!hasChangeSet) return
|
|
965
|
+
setChangePathsOnly((current) => !current)
|
|
966
|
+
}, [hasChangeSet])
|
|
967
|
+
|
|
853
968
|
useEffect(() => {
|
|
854
969
|
const onKey = (event: KeyboardEvent) => {
|
|
855
970
|
if (event.repeat || event.code !== 'KeyK') return
|
|
@@ -870,6 +985,27 @@ function Explorer({
|
|
|
870
985
|
return () => window.removeEventListener('keydown', onKey)
|
|
871
986
|
}, [toggleImportedBy])
|
|
872
987
|
|
|
988
|
+
useEffect(() => {
|
|
989
|
+
if (mode !== 'map' || !hasChangeSet) return
|
|
990
|
+
const onKey = (event: KeyboardEvent) => {
|
|
991
|
+
if (event.repeat || event.code !== 'KeyC') return
|
|
992
|
+
const target = event.target
|
|
993
|
+
if (
|
|
994
|
+
target instanceof HTMLElement &&
|
|
995
|
+
(target.tagName === 'TEXTAREA' ||
|
|
996
|
+
target.tagName === 'INPUT' ||
|
|
997
|
+
target.tagName === 'SELECT' ||
|
|
998
|
+
target.isContentEditable)
|
|
999
|
+
) {
|
|
1000
|
+
return
|
|
1001
|
+
}
|
|
1002
|
+
event.preventDefault()
|
|
1003
|
+
toggleChangePathsOnly()
|
|
1004
|
+
}
|
|
1005
|
+
window.addEventListener('keydown', onKey)
|
|
1006
|
+
return () => window.removeEventListener('keydown', onKey)
|
|
1007
|
+
}, [hasChangeSet, mode, toggleChangePathsOnly])
|
|
1008
|
+
|
|
873
1009
|
useEffect(() => {
|
|
874
1010
|
let cancelled = false
|
|
875
1011
|
const poll = async () => {
|
|
@@ -964,7 +1100,7 @@ function Explorer({
|
|
|
964
1100
|
onSelectFolder={selectFolder}
|
|
965
1101
|
onLockedChange={setLocked}
|
|
966
1102
|
onFolderChange={setCurrentFolder}
|
|
967
|
-
onLand={
|
|
1103
|
+
onLand={landFromMap}
|
|
968
1104
|
onWalkPosition={rememberWalk}
|
|
969
1105
|
onContext={persistUserContext}
|
|
970
1106
|
plannedIds={plannedIds}
|
|
@@ -976,6 +1112,8 @@ function Explorer({
|
|
|
976
1112
|
flyTo={flyTo}
|
|
977
1113
|
aimedRelation={aimedRelation}
|
|
978
1114
|
onAimRelation={setAimedRelation}
|
|
1115
|
+
onAimFile={setAimedFileId}
|
|
1116
|
+
onInspect={inspectBlock}
|
|
979
1117
|
onTravelTo={flyAlongRelation}
|
|
980
1118
|
importedBy={importedBy}
|
|
981
1119
|
namingId={namingId}
|
|
@@ -986,6 +1124,12 @@ function Explorer({
|
|
|
986
1124
|
onCancelName={cancelBlockName}
|
|
987
1125
|
userCreatedBlocks={userBlocks}
|
|
988
1126
|
userCreatedIslands={userIslands}
|
|
1127
|
+
mapGraph={
|
|
1128
|
+
changePathsOnly && hasChangeSet ? changePathGraph : null
|
|
1129
|
+
}
|
|
1130
|
+
mapLayout={
|
|
1131
|
+
changePathsOnly && hasChangeSet ? changePathLayout : null
|
|
1132
|
+
}
|
|
989
1133
|
/>
|
|
990
1134
|
</Canvas>
|
|
991
1135
|
</div>
|
|
@@ -996,9 +1140,11 @@ function Explorer({
|
|
|
996
1140
|
locked={locked}
|
|
997
1141
|
selectedId={selectedId}
|
|
998
1142
|
selectedTick={selectedTick}
|
|
1143
|
+
inspectTick={inspectTick}
|
|
999
1144
|
selectedFolder={selectedFolder}
|
|
1000
1145
|
landAt={landAt}
|
|
1001
1146
|
aimedRelation={aimedRelation}
|
|
1147
|
+
aimedFileId={aimedFileId}
|
|
1002
1148
|
currentFolder={currentFolder}
|
|
1003
1149
|
intent={intent}
|
|
1004
1150
|
intents={intents}
|
|
@@ -1012,6 +1158,9 @@ function Explorer({
|
|
|
1012
1158
|
onToggleFollowLook={toggleFollowLook}
|
|
1013
1159
|
importedBy={importedBy}
|
|
1014
1160
|
onToggleImportedBy={toggleImportedBy}
|
|
1161
|
+
changePathsOnly={changePathsOnly}
|
|
1162
|
+
hasChangeSet={hasChangeSet}
|
|
1163
|
+
onToggleChangePathsOnly={toggleChangePathsOnly}
|
|
1015
1164
|
naming={naming}
|
|
1016
1165
|
namingIsland={Boolean(namingIslandId)}
|
|
1017
1166
|
onCommitIslandName={(name) => {
|
|
@@ -1032,6 +1181,7 @@ function Explorer({
|
|
|
1032
1181
|
onMapAddFile={placeBlockOnFolder}
|
|
1033
1182
|
onMapAddFolder={placeIslandOnFolder}
|
|
1034
1183
|
onInspectFile={inspectFile}
|
|
1184
|
+
onInspectBlock={inspectBlock}
|
|
1035
1185
|
plannedIds={plannedIds}
|
|
1036
1186
|
createdIds={plannedCreates}
|
|
1037
1187
|
deletedIds={deletedIds}
|
|
@@ -52,6 +52,7 @@ export const emptyIntent: AgentIntent = {
|
|
|
52
52
|
feature: null,
|
|
53
53
|
steps: [],
|
|
54
54
|
step: null,
|
|
55
|
+
stepByStep: true,
|
|
55
56
|
files: [],
|
|
56
57
|
creates: [],
|
|
57
58
|
deletes: [],
|
|
@@ -61,6 +62,8 @@ export const emptyIntent: AgentIntent = {
|
|
|
61
62
|
addedFunctions: [],
|
|
62
63
|
addedVariables: [],
|
|
63
64
|
addedImports: [],
|
|
65
|
+
changedFunctions: [],
|
|
66
|
+
changedVariables: [],
|
|
64
67
|
reason: null,
|
|
65
68
|
sessionId: null,
|
|
66
69
|
diffId: null,
|
|
@@ -71,6 +74,7 @@ export const emptyIntent: AgentIntent = {
|
|
|
71
74
|
preview: false,
|
|
72
75
|
phase: null,
|
|
73
76
|
working: false,
|
|
77
|
+
stalledWait: false,
|
|
74
78
|
creationMode: false,
|
|
75
79
|
canEnterBlueprint: false,
|
|
76
80
|
blueprintSessionId: null,
|
|
@@ -89,6 +93,7 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
|
|
|
89
93
|
feature: data?.feature ?? null,
|
|
90
94
|
steps: Array.isArray(data?.steps) ? data.steps : [],
|
|
91
95
|
step: typeof data?.step === 'number' ? data.step : null,
|
|
96
|
+
stepByStep: data?.stepByStep !== false,
|
|
92
97
|
files: Array.isArray(data?.files) ? data.files : [],
|
|
93
98
|
creates: Array.isArray(data?.creates) ? data.creates : [],
|
|
94
99
|
deletes: Array.isArray(data?.deletes) ? data.deletes : [],
|
|
@@ -101,6 +106,8 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
|
|
|
101
106
|
addedFunctions: normalizeSymbolAdditions(data?.addedFunctions),
|
|
102
107
|
addedVariables: normalizeSymbolAdditions(data?.addedVariables),
|
|
103
108
|
addedImports: normalizeImportAdditions(data?.addedImports),
|
|
109
|
+
changedFunctions: normalizeSymbolAdditions(data?.changedFunctions),
|
|
110
|
+
changedVariables: normalizeSymbolAdditions(data?.changedVariables),
|
|
104
111
|
reason: data?.reason ?? null,
|
|
105
112
|
sessionId: data?.sessionId ?? null,
|
|
106
113
|
diffId: data?.diffId ?? null,
|
|
@@ -111,6 +118,7 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
|
|
|
111
118
|
preview: Boolean(data?.preview),
|
|
112
119
|
phase: data?.phase ?? null,
|
|
113
120
|
working: Boolean(data?.working),
|
|
121
|
+
stalledWait: Boolean(data?.stalledWait),
|
|
114
122
|
creationMode: Boolean(data?.creationMode),
|
|
115
123
|
canEnterBlueprint: Boolean(data?.canEnterBlueprint),
|
|
116
124
|
blueprintSessionId:
|
|
@@ -171,6 +179,7 @@ export async function performAgentAction(
|
|
|
171
179
|
diffId?: string
|
|
172
180
|
instruction?: string
|
|
173
181
|
step?: number
|
|
182
|
+
stepByStep?: boolean
|
|
174
183
|
userCreatedBlocks?: UserCreatedBlock[]
|
|
175
184
|
userCreatedIslands?: UserCreatedIsland[]
|
|
176
185
|
addedFunctions?: PatchSymbolAddition[]
|
|
@@ -471,6 +471,58 @@ button {
|
|
|
471
471
|
white-space: nowrap;
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
+
.hud-mode-switch {
|
|
475
|
+
display: flex;
|
|
476
|
+
align-items: center;
|
|
477
|
+
justify-content: space-between;
|
|
478
|
+
gap: 10px;
|
|
479
|
+
margin: 0 0 10px;
|
|
480
|
+
color: #b7c0ce;
|
|
481
|
+
font-size: 12px;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
.hud-mode-hint {
|
|
485
|
+
margin: -4px 0 10px;
|
|
486
|
+
color: #8b95a5;
|
|
487
|
+
font-size: 11px;
|
|
488
|
+
line-height: 1.4;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.hud-switch {
|
|
492
|
+
position: relative;
|
|
493
|
+
flex: none;
|
|
494
|
+
width: 32px;
|
|
495
|
+
height: 18px;
|
|
496
|
+
padding: 0;
|
|
497
|
+
cursor: pointer;
|
|
498
|
+
pointer-events: auto;
|
|
499
|
+
border-radius: 999px;
|
|
500
|
+
background: #2a313c;
|
|
501
|
+
border: 1px solid #3a4250;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.hud-switch::after {
|
|
505
|
+
content: '';
|
|
506
|
+
position: absolute;
|
|
507
|
+
top: 2px;
|
|
508
|
+
left: 2px;
|
|
509
|
+
width: 12px;
|
|
510
|
+
height: 12px;
|
|
511
|
+
border-radius: 50%;
|
|
512
|
+
background: #8b95a5;
|
|
513
|
+
transition: transform 0.12s ease;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.hud-switch[aria-checked='true'] {
|
|
517
|
+
background: #1d4a3a;
|
|
518
|
+
border-color: #3dff78;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
.hud-switch[aria-checked='true']::after {
|
|
522
|
+
transform: translateX(14px);
|
|
523
|
+
background: #3dff78;
|
|
524
|
+
}
|
|
525
|
+
|
|
474
526
|
.hud-panel-info .hud-panel-chrome-title {
|
|
475
527
|
color: #e7ebf2;
|
|
476
528
|
font-size: 16px;
|
|
@@ -505,33 +557,73 @@ button {
|
|
|
505
557
|
height: 220px;
|
|
506
558
|
background: #000;
|
|
507
559
|
touch-action: none;
|
|
560
|
+
user-select: none;
|
|
561
|
+
cursor: grab;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.hud-thumbnail-stage[data-panning='true'] {
|
|
565
|
+
cursor: grabbing;
|
|
508
566
|
}
|
|
509
567
|
|
|
510
568
|
.hud-thumbnail-stage canvas {
|
|
511
569
|
display: block;
|
|
512
570
|
}
|
|
513
571
|
|
|
572
|
+
.hud-thumbnail-hint {
|
|
573
|
+
position: absolute;
|
|
574
|
+
left: 8px;
|
|
575
|
+
bottom: 6px;
|
|
576
|
+
z-index: 2;
|
|
577
|
+
pointer-events: none;
|
|
578
|
+
color: rgba(231, 235, 242, 0.55);
|
|
579
|
+
font-size: 9px;
|
|
580
|
+
letter-spacing: 0.02em;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.hud-thumbnail-nav {
|
|
584
|
+
position: absolute;
|
|
585
|
+
right: 6px;
|
|
586
|
+
bottom: 6px;
|
|
587
|
+
z-index: 2;
|
|
588
|
+
display: flex;
|
|
589
|
+
gap: 4px;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
.hud-thumbnail-nav .hud-button {
|
|
593
|
+
width: 24px;
|
|
594
|
+
height: 24px;
|
|
595
|
+
padding: 0;
|
|
596
|
+
color: #e7ebf2;
|
|
597
|
+
font-size: 14px;
|
|
598
|
+
line-height: 1;
|
|
599
|
+
background: rgba(18, 20, 24, 0.88);
|
|
600
|
+
}
|
|
601
|
+
|
|
514
602
|
.thumbnail-block-label,
|
|
515
603
|
.thumbnail-folder-label {
|
|
516
604
|
pointer-events: none;
|
|
517
605
|
user-select: none;
|
|
518
|
-
|
|
606
|
+
overflow: hidden;
|
|
607
|
+
max-width: 72px;
|
|
519
608
|
color: #ffffff;
|
|
520
|
-
font-weight:
|
|
521
|
-
letter-spacing: 0.
|
|
522
|
-
line-height: 1.
|
|
609
|
+
font-weight: 600;
|
|
610
|
+
letter-spacing: 0.01em;
|
|
611
|
+
line-height: 1.15;
|
|
612
|
+
text-overflow: ellipsis;
|
|
613
|
+
white-space: nowrap;
|
|
523
614
|
text-shadow:
|
|
524
|
-
0 0
|
|
615
|
+
0 0 3px #000,
|
|
525
616
|
0 1px 2px #000,
|
|
526
|
-
0 0
|
|
617
|
+
0 0 8px rgba(0, 0, 0, 0.85);
|
|
527
618
|
}
|
|
528
619
|
|
|
529
620
|
.thumbnail-block-label {
|
|
530
|
-
font-size:
|
|
621
|
+
font-size: 9px;
|
|
531
622
|
}
|
|
532
623
|
|
|
533
624
|
.thumbnail-folder-label {
|
|
534
|
-
font-size:
|
|
625
|
+
font-size: 10px;
|
|
626
|
+
max-width: 96px;
|
|
535
627
|
}
|
|
536
628
|
|
|
537
629
|
.hud-panel-planned {
|
|
@@ -870,19 +962,19 @@ button {
|
|
|
870
962
|
}
|
|
871
963
|
|
|
872
964
|
.hud-file-add,
|
|
873
|
-
.hud-
|
|
965
|
+
.hud-section-title.hud-section-title-add {
|
|
874
966
|
color: #3dff78;
|
|
875
967
|
font-weight: 600;
|
|
876
968
|
}
|
|
877
969
|
|
|
878
970
|
.hud-file-edit,
|
|
879
|
-
.hud-
|
|
971
|
+
.hud-section-title.hud-section-title-edit {
|
|
880
972
|
color: #5cb8ff;
|
|
881
973
|
font-weight: 600;
|
|
882
974
|
}
|
|
883
975
|
|
|
884
976
|
.hud-file-remove,
|
|
885
|
-
.hud-
|
|
977
|
+
.hud-section-title.hud-section-title-remove {
|
|
886
978
|
color: #ff5a72;
|
|
887
979
|
font-weight: 600;
|
|
888
980
|
}
|
|
@@ -170,6 +170,51 @@ export function folderParent(folderPath: string) {
|
|
|
170
170
|
return folderPath.includes('/') ? folderPath.split('/').slice(0, -1).join('/') : '.'
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
export function changeRelatedFolderPaths(
|
|
174
|
+
graph: CodebaseGraph,
|
|
175
|
+
fileIds: Iterable<string>,
|
|
176
|
+
extraFolders: Iterable<string> = [],
|
|
177
|
+
) {
|
|
178
|
+
const root = graph.root || '.'
|
|
179
|
+
const paths = new Set<string>([root])
|
|
180
|
+
const filesById = new Map(graph.files.map((file) => [file.id, file]))
|
|
181
|
+
|
|
182
|
+
const addAncestors = (start: string | null | undefined) => {
|
|
183
|
+
let current = start
|
|
184
|
+
while (current) {
|
|
185
|
+
paths.add(current)
|
|
186
|
+
current = folderParent(current)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const id of fileIds) {
|
|
191
|
+
const file = filesById.get(id)
|
|
192
|
+
addAncestors(file?.folder ?? folderOfFile(id))
|
|
193
|
+
}
|
|
194
|
+
for (const folder of extraFolders) addAncestors(folder)
|
|
195
|
+
return paths
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function filterGraphToChangePaths(
|
|
199
|
+
graph: CodebaseGraph,
|
|
200
|
+
fileIds: Iterable<string>,
|
|
201
|
+
extraFolders: Iterable<string> = [],
|
|
202
|
+
): CodebaseGraph {
|
|
203
|
+
const keepFiles = new Set(fileIds)
|
|
204
|
+
const keepFolders = changeRelatedFolderPaths(graph, fileIds, extraFolders)
|
|
205
|
+
return {
|
|
206
|
+
...graph,
|
|
207
|
+
files: graph.files.filter((file) => keepFiles.has(file.id)),
|
|
208
|
+
folders: graph.folders
|
|
209
|
+
.filter((folder) => keepFolders.has(folder.path))
|
|
210
|
+
.map((folder) => ({
|
|
211
|
+
...folder,
|
|
212
|
+
files: folder.files.filter((id) => keepFiles.has(id)),
|
|
213
|
+
children: folder.children.filter((path) => keepFolders.has(path)),
|
|
214
|
+
})),
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
173
218
|
function folderName(folderPath: string, rootName: string) {
|
|
174
219
|
if (!folderPath || folderPath === '.') return rootName
|
|
175
220
|
return folderPath.split('/').pop() ?? folderPath
|
|
@@ -434,7 +479,7 @@ export function layoutWorld(graph: CodebaseGraph): WorldLayout {
|
|
|
434
479
|
export function folderAt(
|
|
435
480
|
x: number,
|
|
436
481
|
z: number,
|
|
437
|
-
layout: WorldLayout,
|
|
482
|
+
layout: Pick<WorldLayout, 'folders'>,
|
|
438
483
|
): PlacedFolder | null {
|
|
439
484
|
let current: PlacedFolder | null = null
|
|
440
485
|
for (const folder of Object.values(layout.folders)) {
|
|
@@ -445,6 +490,25 @@ export function folderAt(
|
|
|
445
490
|
return current
|
|
446
491
|
}
|
|
447
492
|
|
|
493
|
+
export function mapPointOntoFolder(
|
|
494
|
+
x: number,
|
|
495
|
+
z: number,
|
|
496
|
+
fromLayout: Pick<WorldLayout, 'folders'>,
|
|
497
|
+
toLayout: Pick<WorldLayout, 'folders'>,
|
|
498
|
+
fallback: [number, number] | null = null,
|
|
499
|
+
): [number, number] | null {
|
|
500
|
+
const folder = folderAt(x, z, fromLayout)
|
|
501
|
+
if (!folder) return fallback
|
|
502
|
+
const next = toLayout.folders[folder.path]
|
|
503
|
+
if (!next) return fallback
|
|
504
|
+
const relX = folder.width ? (x - folder.x) / folder.width : 0
|
|
505
|
+
const relZ = folder.depth ? (z - folder.z) / folder.depth : 0
|
|
506
|
+
return [
|
|
507
|
+
next.x + Math.min(Math.max(relX, -0.45), 0.45) * next.width,
|
|
508
|
+
next.z + Math.min(Math.max(relZ, 0), 1) * next.depth,
|
|
509
|
+
]
|
|
510
|
+
}
|
|
511
|
+
|
|
448
512
|
function distanceToSegment(
|
|
449
513
|
px: number,
|
|
450
514
|
pz: number,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Suspense } from 'react'
|
|
2
2
|
import { Text } from '@react-three/drei'
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
3
|
+
import { folderAt } from '../layout'
|
|
4
|
+
import { CONFIG, folderAisleColor } from '../theme'
|
|
5
|
+
import type { PlacedBridge, PlacedFolder } from '../types'
|
|
5
6
|
|
|
6
7
|
const WALK_Y = 0.02
|
|
7
|
-
const LAND_COLOR = '
|
|
8
|
+
const LAND_COLOR = 'hsl(210, 6%, 40%)'
|
|
8
9
|
const SPAN_COLOR = '#232e3a'
|
|
9
10
|
const CORNER_SIZE = CONFIG.bridgeWidth
|
|
10
11
|
|
|
@@ -22,6 +23,19 @@ const SIGN_COLOR = '#323a46'
|
|
|
22
23
|
|
|
23
24
|
type BridgeProps = {
|
|
24
25
|
bridge: PlacedBridge
|
|
26
|
+
folders?: Record<string, PlacedFolder>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function landColorAt(
|
|
30
|
+
x: number,
|
|
31
|
+
z: number,
|
|
32
|
+
folders: Record<string, PlacedFolder> | undefined,
|
|
33
|
+
) {
|
|
34
|
+
if (!folders) return LAND_COLOR
|
|
35
|
+
const folder = folderAt(x, z, { folders })
|
|
36
|
+
if (!folder) return LAND_COLOR
|
|
37
|
+
if (folder.added) return '#23485a'
|
|
38
|
+
return folderAisleColor(folder.path)
|
|
25
39
|
}
|
|
26
40
|
|
|
27
41
|
type Segment = {
|
|
@@ -233,7 +247,7 @@ function Gate({
|
|
|
233
247
|
)
|
|
234
248
|
}
|
|
235
249
|
|
|
236
|
-
export function Bridge({ bridge }: BridgeProps) {
|
|
250
|
+
export function Bridge({ bridge, folders }: BridgeProps) {
|
|
237
251
|
const segments = segmentsFromPoints(bridge.points)
|
|
238
252
|
const corners = cornersFromPoints(bridge.points)
|
|
239
253
|
const start = bridge.points[0]
|
|
@@ -252,7 +266,11 @@ export function Bridge({ bridge }: BridgeProps) {
|
|
|
252
266
|
z={piece.z}
|
|
253
267
|
width={piece.width}
|
|
254
268
|
depth={piece.depth}
|
|
255
|
-
color={
|
|
269
|
+
color={
|
|
270
|
+
piece.color === LAND_COLOR
|
|
271
|
+
? landColorAt(piece.x, piece.z, folders)
|
|
272
|
+
: piece.color
|
|
273
|
+
}
|
|
256
274
|
/>
|
|
257
275
|
))}
|
|
258
276
|
|
|
@@ -29,6 +29,7 @@ type FileBlockProps = {
|
|
|
29
29
|
mapMode?: boolean
|
|
30
30
|
highlightMapChange?: boolean
|
|
31
31
|
previewLabels?: boolean
|
|
32
|
+
labelVisible?: boolean
|
|
32
33
|
onCommitName?: (name: string) => void
|
|
33
34
|
onCancelName?: () => void
|
|
34
35
|
}
|
|
@@ -53,6 +54,7 @@ export function FileBlock({
|
|
|
53
54
|
mapMode = false,
|
|
54
55
|
highlightMapChange = mapMode,
|
|
55
56
|
previewLabels = false,
|
|
57
|
+
labelVisible = true,
|
|
56
58
|
onCommitName,
|
|
57
59
|
onCancelName,
|
|
58
60
|
}: FileBlockProps) {
|
|
@@ -164,15 +166,17 @@ export function FileBlock({
|
|
|
164
166
|
{showLabels && (
|
|
165
167
|
<Suspense fallback={null}>
|
|
166
168
|
{previewLabels ? (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
169
|
+
labelVisible && (
|
|
170
|
+
<Html
|
|
171
|
+
position={[0, height / 2 + 0.4, 0]}
|
|
172
|
+
center
|
|
173
|
+
occlude={false}
|
|
174
|
+
style={{ pointerEvents: 'none' }}
|
|
175
|
+
zIndexRange={[20, 0]}
|
|
176
|
+
>
|
|
177
|
+
<div className="thumbnail-block-label">{label}</div>
|
|
178
|
+
</Html>
|
|
179
|
+
)
|
|
176
180
|
) : (
|
|
177
181
|
<>
|
|
178
182
|
<Billboard position={[0, height / 2 + (planned || highlight ? 0.55 : 0.38), 0]}>
|