@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.
@@ -235,8 +235,8 @@ button {
235
235
  }
236
236
 
237
237
  .map-folder-label-selected {
238
- color: #f1f5fa;
239
- border: 3px solid #000000;
238
+ color: #f3e8ff;
239
+ border: 3px solid #c026ff;
240
240
  background: rgba(25, 28, 34, 0.94);
241
241
  }
242
242
 
@@ -324,6 +324,216 @@ button {
324
324
  pointer-events: auto;
325
325
  }
326
326
 
327
+ .hud-right-stack {
328
+ position: absolute;
329
+ top: 68px;
330
+ right: 20px;
331
+ bottom: 72px;
332
+ z-index: 5;
333
+ display: flex;
334
+ flex-direction: column;
335
+ gap: 12px;
336
+ width: min(320px, calc(100% - 40px));
337
+ pointer-events: none;
338
+ }
339
+
340
+ .hud-right-stack .hud-panel-info {
341
+ position: relative;
342
+ top: auto;
343
+ right: auto;
344
+ z-index: auto;
345
+ display: flex;
346
+ flex: 0 1 auto;
347
+ flex-direction: column;
348
+ width: 100%;
349
+ min-height: 0;
350
+ max-height: 100%;
351
+ overflow: hidden;
352
+ }
353
+
354
+ .hud-right-stack .hud-panel-info[data-minimized='true'] {
355
+ flex: 0 0 auto;
356
+ }
357
+
358
+ .hud-left-stack {
359
+ position: absolute;
360
+ top: 68px;
361
+ left: 20px;
362
+ bottom: 72px;
363
+ z-index: 5;
364
+ display: flex;
365
+ flex-direction: column;
366
+ gap: 12px;
367
+ width: min(360px, calc(100% - 40px));
368
+ pointer-events: none;
369
+ overflow-x: hidden;
370
+ overflow-y: auto;
371
+ }
372
+
373
+ .hud-left-stack .hud-panel-planned {
374
+ position: relative;
375
+ top: auto;
376
+ left: auto;
377
+ right: auto;
378
+ z-index: auto;
379
+ display: flex;
380
+ flex: 1 1 0;
381
+ flex-direction: column;
382
+ width: 100%;
383
+ min-height: 0;
384
+ max-height: 100%;
385
+ overflow-x: hidden;
386
+ overflow-y: auto;
387
+ }
388
+
389
+ .hud-left-stack .hud-panel-planned[data-minimized='true'] {
390
+ flex: 0 0 auto;
391
+ }
392
+
393
+ .hud-left-stack .hud-panel-planned[data-focused='false'] {
394
+ opacity: 0.86;
395
+ }
396
+
397
+ .hud-left-stack .hud-panel-planned[data-focused='true'] {
398
+ box-shadow: 0 0 0 1px #9ad8ff;
399
+ }
400
+
401
+ .hud-panel-body {
402
+ min-height: 0;
403
+ flex: 1 1 auto;
404
+ overflow-x: hidden;
405
+ overflow-y: auto;
406
+ overscroll-behavior: contain;
407
+ }
408
+
409
+ .hud-right-stack .hud-thumbnail {
410
+ position: relative;
411
+ right: auto;
412
+ bottom: auto;
413
+ left: auto;
414
+ z-index: auto;
415
+ width: 100%;
416
+ margin-top: auto;
417
+ flex: none;
418
+ }
419
+
420
+ .hud-thumbnail {
421
+ position: fixed;
422
+ right: 20px;
423
+ bottom: 72px;
424
+ left: auto;
425
+ z-index: 30;
426
+ width: min(320px, calc(100% - 40px));
427
+ pointer-events: auto;
428
+ overflow: hidden;
429
+ background: rgba(25, 28, 34, 0.96);
430
+ border: 1px solid #5d9ec4;
431
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
432
+ }
433
+
434
+ .hud-thumbnail-bar {
435
+ display: flex;
436
+ align-items: center;
437
+ justify-content: space-between;
438
+ gap: 8px;
439
+ padding: 4px 6px 4px 12px;
440
+ color: #e7ebf2;
441
+ font-size: 12px;
442
+ letter-spacing: 0.02em;
443
+ }
444
+
445
+ .hud-panel-chrome {
446
+ display: flex;
447
+ align-items: center;
448
+ justify-content: space-between;
449
+ gap: 8px;
450
+ margin: -4px 0 8px;
451
+ }
452
+
453
+ .hud-panel[data-minimized='true'] {
454
+ overflow: hidden;
455
+ padding-bottom: 12px;
456
+ }
457
+
458
+ .hud-panel[data-minimized='true'] .hud-panel-chrome {
459
+ margin-bottom: 0;
460
+ }
461
+
462
+ .hud-panel-chrome-title {
463
+ min-width: 0;
464
+ overflow: hidden;
465
+ color: #b7c0ce;
466
+ font-size: 11px;
467
+ font-weight: 600;
468
+ letter-spacing: 0.08em;
469
+ text-transform: uppercase;
470
+ text-overflow: ellipsis;
471
+ white-space: nowrap;
472
+ }
473
+
474
+ .hud-panel-info .hud-panel-chrome-title {
475
+ color: #e7ebf2;
476
+ font-size: 16px;
477
+ letter-spacing: 0.02em;
478
+ text-transform: none;
479
+ }
480
+
481
+ .hud-panel-controls {
482
+ display: flex;
483
+ flex: none;
484
+ gap: 4px;
485
+ }
486
+
487
+ .hud-panel-control {
488
+ width: 28px;
489
+ height: 28px;
490
+ padding: 0;
491
+ font-size: 16px;
492
+ line-height: 1;
493
+ }
494
+
495
+ .hud-thumbnail-hide {
496
+ width: 28px;
497
+ height: 28px;
498
+ font-size: 16px;
499
+ line-height: 1;
500
+ }
501
+
502
+ .hud-thumbnail-stage {
503
+ position: relative;
504
+ width: 100%;
505
+ height: 220px;
506
+ background: #000;
507
+ touch-action: none;
508
+ }
509
+
510
+ .hud-thumbnail-stage canvas {
511
+ display: block;
512
+ }
513
+
514
+ .thumbnail-block-label,
515
+ .thumbnail-folder-label {
516
+ pointer-events: none;
517
+ user-select: none;
518
+ white-space: nowrap;
519
+ color: #ffffff;
520
+ font-weight: 800;
521
+ letter-spacing: 0.02em;
522
+ line-height: 1.1;
523
+ text-shadow:
524
+ 0 0 4px #000,
525
+ 0 1px 2px #000,
526
+ 0 0 10px rgba(0, 0, 0, 0.85);
527
+ }
528
+
529
+ .thumbnail-block-label {
530
+ font-size: 13px;
531
+ }
532
+
533
+ .thumbnail-folder-label {
534
+ font-size: 15px;
535
+ }
536
+
327
537
  .hud-panel-planned {
328
538
  left: 20px;
329
539
  right: auto;
@@ -452,12 +662,17 @@ button {
452
662
  .hud-working {
453
663
  display: flex;
454
664
  align-items: center;
665
+ flex-wrap: wrap;
455
666
  gap: 9px;
456
667
  margin-top: 12px;
457
668
  color: #d7eef8;
458
669
  font-size: 12px;
459
670
  }
460
671
 
672
+ .hud-working .hud-button {
673
+ margin-left: auto;
674
+ }
675
+
461
676
  .hud-spinner {
462
677
  width: 14px;
463
678
  height: 14px;
@@ -1,4 +1,5 @@
1
1
  import { CONFIG, fileHeight } from './theme'
2
+ import type { ChangeKind } from './theme'
2
3
  import type {
3
4
  CodebaseGraph,
4
5
  FileNode,
@@ -506,3 +507,48 @@ export function worldBounds(layout: WorldLayout) {
506
507
  depth: maxZ - minZ,
507
508
  }
508
509
  }
510
+
511
+ export function fileChangeKind(
512
+ id: string,
513
+ planned: Set<string>,
514
+ created: Set<string>,
515
+ deleted: Set<string>,
516
+ ): ChangeKind | null {
517
+ if (deleted.has(id)) return 'remove'
518
+ if (created.has(id)) return 'add'
519
+ if (planned.has(id)) return 'edit'
520
+ return null
521
+ }
522
+
523
+ export function folderChangeHighlights(
524
+ planned: Set<string>,
525
+ created: Set<string>,
526
+ deleted: Set<string>,
527
+ folders: Record<string, PlacedFolder>,
528
+ ): Partial<Record<string, ChangeKind>> {
529
+ const highlighted: Partial<Record<string, ChangeKind>> = {}
530
+ const folderKinds = new Map<string, Set<ChangeKind>>()
531
+ const addFolderKind = (id: string, kind: ChangeKind) => {
532
+ const folder = folderOfFile(id)
533
+ const kinds = folderKinds.get(folder) ?? new Set<ChangeKind>()
534
+ kinds.add(kind)
535
+ folderKinds.set(folder, kinds)
536
+ }
537
+ for (const id of planned) {
538
+ addFolderKind(id, created.has(id) ? 'add' : 'edit')
539
+ }
540
+ for (const id of deleted) addFolderKind(id, 'remove')
541
+ for (const [folder, kinds] of folderKinds) {
542
+ if (kinds.size === 1) highlighted[folder] = [...kinds][0]
543
+ }
544
+ if (planned.size > 0 || deleted.size > 0) {
545
+ for (const folder of Object.values(folders)) {
546
+ if (!folder.added) continue
547
+ const kinds = folderKinds.get(folder.path)
548
+ if (!kinds || kinds.size === 1) {
549
+ highlighted[folder.path] ??= 'add'
550
+ }
551
+ }
552
+ }
553
+ return highlighted
554
+ }
@@ -1,12 +1,11 @@
1
1
  import { Suspense } from 'react'
2
2
  import { Billboard, Edges, Html, Text } from '@react-three/drei'
3
- import { CHANGE_HIGHLIGHT, CONFIG, dimColor, fileColor, type ChangeKind } from '../theme'
3
+ import { CHANGE_HIGHLIGHT, CONFIG, dimColor, fileColor, FILE_SELECTION, MAP_SELECTION, type ChangeKind } from '../theme'
4
4
  import { NameInput } from '../ui/NameInput'
5
5
  import { MapSelectBorder } from './MapSelectBorder'
6
6
  import type { FileNode } from '../types'
7
7
  import type { PlacedFile } from '../types'
8
8
 
9
- const BAR = 0.22
10
9
  const SIDE_LABEL_PAD = 0.05
11
10
  const SIDE_LABEL_MIN_HEIGHT = CONFIG.eyeHeight * 2.5
12
11
  const SIDE_LABELS: { rotationY: number; axis: 'x' | 'z'; sign: 1 | -1 }[] = [
@@ -28,51 +27,12 @@ type FileBlockProps = {
28
27
  dimmed: boolean
29
28
  naming?: boolean
30
29
  mapMode?: boolean
30
+ highlightMapChange?: boolean
31
+ previewLabels?: boolean
31
32
  onCommitName?: (name: string) => void
32
33
  onCancelName?: () => void
33
34
  }
34
35
 
35
- function PlannedFrame({
36
- width,
37
- height,
38
- depth,
39
- color,
40
- }: {
41
- width: number
42
- height: number
43
- depth: number
44
- color: string
45
- }) {
46
- const ox = width / 2 + BAR / 2
47
- const oy = height / 2 + BAR / 2
48
- const oz = depth / 2 + BAR / 2
49
- const bars: [number, number, number, number, number, number][] = [
50
- [ox, 0, oz, BAR, height + BAR * 2, BAR],
51
- [-ox, 0, oz, BAR, height + BAR * 2, BAR],
52
- [ox, 0, -oz, BAR, height + BAR * 2, BAR],
53
- [-ox, 0, -oz, BAR, height + BAR * 2, BAR],
54
- [0, oy, oz, width + BAR * 2, BAR, BAR],
55
- [0, oy, -oz, width + BAR * 2, BAR, BAR],
56
- [ox, oy, 0, BAR, BAR, depth],
57
- [-ox, oy, 0, BAR, BAR, depth],
58
- [0, -oy, oz, width + BAR * 2, BAR, BAR],
59
- [0, -oy, -oz, width + BAR * 2, BAR, BAR],
60
- [ox, -oy, 0, BAR, BAR, depth],
61
- [-ox, -oy, 0, BAR, BAR, depth],
62
- ]
63
-
64
- return (
65
- <group>
66
- {bars.map(([x, y, z, w, h, d], index) => (
67
- <mesh key={index} position={[x, y, z]}>
68
- <boxGeometry args={[w, h, d]} />
69
- <meshBasicMaterial color={color} toneMapped={false} />
70
- </mesh>
71
- ))}
72
- </group>
73
- )
74
- }
75
-
76
36
  function fileLabel(name: string, changeKind: ChangeKind | null, added: boolean) {
77
37
  if (changeKind === 'remove') return `- ${name}`
78
38
  if (changeKind === 'add' || added) return `+ ${name}`
@@ -91,15 +51,18 @@ export function FileBlock({
91
51
  dimmed,
92
52
  naming = false,
93
53
  mapMode = false,
54
+ highlightMapChange = mapMode,
55
+ previewLabels = false,
94
56
  onCommitName,
95
57
  onCancelName,
96
58
  }: FileBlockProps) {
97
59
  const [width, height, depth] = placed.size
98
- const highlight = changeKind && mapMode ? CHANGE_HIGHLIGHT[changeKind] : null
60
+ const highlight =
61
+ changeKind && highlightMapChange && !selected
62
+ ? CHANGE_HIGHLIGHT[changeKind]
63
+ : null
99
64
  const color = added || file.userCreated ? '#7ec8e8' : fileColor(file.language)
100
65
  const muted = dimColor(color, 0.32)
101
- const walkSelected = selected && !mapMode
102
- const mapSelected = selected && mapMode
103
66
  const label = fileLabel(file.name, changeKind, added || Boolean(file.userCreated))
104
67
  const labelColor = aimed
105
68
  ? '#9ad8ff'
@@ -112,62 +75,58 @@ export function FileBlock({
112
75
  ? '#9ad8ff'
113
76
  : highlight
114
77
  ? highlight.color
115
- : walkSelected
116
- ? '#5d9ec4'
78
+ : selected
79
+ ? FILE_SELECTION.color
117
80
  : related
118
81
  ? '#4e7f72'
119
82
  : dimmed
120
83
  ? muted
121
84
  : color
122
85
 
86
+ const showLabels = !naming && !mapMode
87
+
123
88
  return (
124
89
  <group position={placed.position}>
125
- {highlight && !mapSelected && (
126
- <mesh
127
- position={[0, height / 2 + 0.04, 0]}
128
- rotation={[-Math.PI / 2, 0, 0]}
129
- userData={{ fileId: file.id }}
130
- >
131
- <planeGeometry args={[width + 0.7, depth + 0.7]} />
132
- <meshBasicMaterial color={highlight.color} toneMapped={false} />
133
- </mesh>
134
- )}
135
- <mesh userData={{ fileId: file.id }}>
136
- <boxGeometry args={[width, height, depth]} />
137
- <meshLambertMaterial
138
- color={meshColor}
139
- emissive={
140
- aimed
141
- ? '#3a6a80'
142
- : highlight
143
- ? highlight.emissive
144
- : walkSelected
145
- ? '#2a4a5c'
146
- : related
147
- ? '#1f4a44'
148
- : added || file.userCreated
149
- ? '#2a5064'
150
- : dimmed
151
- ? muted
152
- : color
153
- }
154
- emissiveIntensity={
155
- aimed
156
- ? 0.45
157
- : highlight
158
- ? 0.95
159
- : walkSelected
160
- ? 0.28
161
- : related
162
- ? 0.18
163
- : added || file.userCreated
164
- ? 0.22
165
- : dimmed
166
- ? 0.08
167
- : 0.12
168
- }
169
- />
170
- {walkSelected && (
90
+ <mesh userData={{ fileId: file.id }} scale={[width, height, depth]}>
91
+ <boxGeometry args={[1, 1, 1]} />
92
+ {mapMode ? (
93
+ <meshBasicMaterial color={meshColor} toneMapped={false} />
94
+ ) : (
95
+ <meshLambertMaterial
96
+ color={meshColor}
97
+ emissive={
98
+ aimed
99
+ ? '#3a6a80'
100
+ : highlight
101
+ ? highlight.emissive
102
+ : selected
103
+ ? FILE_SELECTION.emissive
104
+ : related
105
+ ? '#1f4a44'
106
+ : added || file.userCreated
107
+ ? '#2a5064'
108
+ : dimmed
109
+ ? muted
110
+ : color
111
+ }
112
+ emissiveIntensity={
113
+ aimed
114
+ ? 0.45
115
+ : highlight
116
+ ? 0.95
117
+ : selected
118
+ ? 0.55
119
+ : related
120
+ ? 0.18
121
+ : added || file.userCreated
122
+ ? 0.22
123
+ : dimmed
124
+ ? 0.08
125
+ : 0.12
126
+ }
127
+ />
128
+ )}
129
+ {selected && !mapMode && (
171
130
  <Edges
172
131
  color="#000000"
173
132
  dashed
@@ -175,26 +134,18 @@ export function FileBlock({
175
134
  gapSize={0.1}
176
135
  lineWidth={2.5}
177
136
  toneMapped={false}
178
- scale={1.002}
179
137
  />
180
138
  )}
181
139
  </mesh>
182
- {mapSelected && (
140
+ {selected && mapMode && (
183
141
  <MapSelectBorder
184
142
  width={width}
185
143
  depth={depth}
186
- y={height / 2 + 0.05}
144
+ y={height / 2 + 0.03}
145
+ stroke={MAP_SELECTION.blockPad}
187
146
  userData={{ fileId: file.id }}
188
147
  />
189
148
  )}
190
- {highlight && !mapSelected && (
191
- <PlannedFrame
192
- width={width}
193
- height={height}
194
- depth={depth}
195
- color={highlight.color}
196
- />
197
- )}
198
149
  {naming && onCommitName && onCancelName && (
199
150
  <Html
200
151
  position={[0, height / 2 + 0.42, 0]}
@@ -210,47 +161,61 @@ export function FileBlock({
210
161
  />
211
162
  </Html>
212
163
  )}
213
- <Suspense fallback={null}>
214
- {!naming && (
215
- <Billboard position={[0, height / 2 + (planned || highlight ? 0.55 : 0.38), 0]}>
216
- <Text
217
- fontSize={0.28}
218
- color={labelColor}
219
- anchorX="center"
220
- anchorY="bottom"
221
- maxWidth={3.4}
164
+ {showLabels && (
165
+ <Suspense fallback={null}>
166
+ {previewLabels ? (
167
+ <Html
168
+ position={[0, height / 2 + 0.55, 0]}
169
+ center
170
+ occlude={false}
171
+ style={{ pointerEvents: 'none' }}
172
+ zIndexRange={[20, 0]}
222
173
  >
223
- {label}
224
- </Text>
225
- </Billboard>
226
- )}
227
- {height >= SIDE_LABEL_MIN_HEIGHT &&
228
- SIDE_LABELS.map((side) => {
229
- const face = side.axis === 'x' ? width : depth
230
- const x =
231
- side.axis === 'x' ? side.sign * (width / 2 + SIDE_LABEL_PAD) : 0
232
- const z =
233
- side.axis === 'z' ? side.sign * (depth / 2 + SIDE_LABEL_PAD) : 0
234
- return (
235
- <Text
236
- key={`${side.axis}:${side.sign}`}
237
- position={[x, -height / 2 + 0.22, z]}
238
- rotation={[0, side.rotationY, 0]}
239
- fontSize={0.22}
240
- color={labelColor}
241
- anchorX="center"
242
- anchorY="middle"
243
- maxWidth={face - 0.2}
244
- overflowWrap="break-word"
245
- outlineWidth={0.012}
246
- outlineColor="#11151c"
247
- depthOffset={-1}
248
- >
249
- {label}
250
- </Text>
251
- )
252
- })}
253
- </Suspense>
174
+ <div className="thumbnail-block-label">{label}</div>
175
+ </Html>
176
+ ) : (
177
+ <>
178
+ <Billboard position={[0, height / 2 + (planned || highlight ? 0.55 : 0.38), 0]}>
179
+ <Text
180
+ fontSize={0.28}
181
+ color={labelColor}
182
+ anchorX="center"
183
+ anchorY="bottom"
184
+ maxWidth={3.4}
185
+ >
186
+ {label}
187
+ </Text>
188
+ </Billboard>
189
+ {height >= SIDE_LABEL_MIN_HEIGHT &&
190
+ SIDE_LABELS.map((side) => {
191
+ const face = side.axis === 'x' ? width : depth
192
+ const x =
193
+ side.axis === 'x' ? side.sign * (width / 2 + SIDE_LABEL_PAD) : 0
194
+ const z =
195
+ side.axis === 'z' ? side.sign * (depth / 2 + SIDE_LABEL_PAD) : 0
196
+ return (
197
+ <Text
198
+ key={`${side.axis}:${side.sign}`}
199
+ position={[x, -height / 2 + 0.22, z]}
200
+ rotation={[0, side.rotationY, 0]}
201
+ fontSize={0.22}
202
+ color={labelColor}
203
+ anchorX="center"
204
+ anchorY="middle"
205
+ maxWidth={face - 0.2}
206
+ overflowWrap="break-word"
207
+ outlineWidth={0.012}
208
+ outlineColor="#11151c"
209
+ depthOffset={-1}
210
+ >
211
+ {label}
212
+ </Text>
213
+ )
214
+ })}
215
+ </>
216
+ )}
217
+ </Suspense>
218
+ )}
254
219
  </group>
255
220
  )
256
221
  }
@@ -1,5 +1,5 @@
1
1
  import { Suspense } from 'react'
2
- import { Text } from '@react-three/drei'
2
+ import { Html, Text } from '@react-three/drei'
3
3
  import { CHANGE_HIGHLIGHT, folderFloorColor, MAP_SELECTION, type ChangeKind } from '../theme'
4
4
  import type { PlacedFolder } from '../types'
5
5
  import { MapSelectBorder } from './MapSelectBorder'
@@ -10,6 +10,7 @@ type FolderAreaProps = {
10
10
  selected?: boolean
11
11
  mapMode?: boolean
12
12
  highlightKind?: ChangeKind | null
13
+ previewLabels?: boolean
13
14
  }
14
15
 
15
16
  export function FolderArea({
@@ -18,10 +19,10 @@ export function FolderArea({
18
19
  selected = false,
19
20
  mapMode = false,
20
21
  highlightKind = null,
22
+ previewLabels = false,
21
23
  }: FolderAreaProps) {
22
24
  const added = Boolean(folder.added)
23
- const highlight =
24
- mapMode && highlightKind ? CHANGE_HIGHLIGHT[highlightKind] : null
25
+ const highlight = highlightKind ? CHANGE_HIGHLIGHT[highlightKind] : null
25
26
  const addedOnly = added && !highlight
26
27
  const outline = highlight ? highlight.color : addedOnly ? '#7ec8e8' : null
27
28
  const color = highlight
@@ -57,6 +58,7 @@ export function FolderArea({
57
58
  depth={folder.depth}
58
59
  y={0.06}
59
60
  stroke={MAP_SELECTION.islandPad}
61
+ color={MAP_SELECTION.island}
60
62
  />
61
63
  )}
62
64
  <mesh position={[0, 0.02, 0]} rotation={[-Math.PI / 2, 0, 0]}>
@@ -79,7 +81,18 @@ export function FolderArea({
79
81
  </mesh>
80
82
  )}
81
83
  <Suspense fallback={null}>
82
- {!naming && (
84
+ {!naming && previewLabels && (
85
+ <Html
86
+ position={[0, 1.35, -folder.depth / 2 + 1.6]}
87
+ center
88
+ occlude={false}
89
+ style={{ pointerEvents: 'none' }}
90
+ zIndexRange={[20, 0]}
91
+ >
92
+ <div className="thumbnail-folder-label">{label}</div>
93
+ </Html>
94
+ )}
95
+ {!naming && !previewLabels && !mapMode && (
83
96
  <Text
84
97
  position={[0, 0.05, -folder.depth / 2 + 1.6]}
85
98
  rotation={[-Math.PI / 2, 0, 0]}