@jkwd/inbase 0.1.18 → 0.1.20
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 -3
- package/apps/explorer/scripts/patch-lib.d.ts +2 -0
- package/apps/explorer/scripts/patch-lib.mjs +2 -0
- package/apps/explorer/scripts/session-store.d.ts +45 -0
- package/apps/explorer/scripts/session-store.mjs +281 -5
- package/apps/explorer/src/App.tsx +114 -4
- package/apps/explorer/src/agentIntent.ts +76 -1
- package/apps/explorer/src/index.css +65 -0
- package/apps/explorer/src/scene/FileBlock.tsx +27 -0
- package/apps/explorer/src/scene/FolderArea.tsx +21 -0
- package/apps/explorer/src/scene/MapView.tsx +1 -11
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +16 -3
- package/apps/explorer/src/scene/World.tsx +22 -4
- package/apps/explorer/src/types.ts +16 -0
- package/apps/explorer/src/ui/EyeIcon.tsx +26 -0
- package/apps/explorer/src/ui/HUD.tsx +377 -26
- package/apps/explorer/src/ui/MapContextMenu.tsx +18 -1
- package/apps/explorer/src/userCreated.ts +97 -0
- package/apps/explorer/vite.config.ts +18 -1
- package/bin/session.mjs +41 -11
- package/package.json +1 -1
- package/skill/commands/inbase.md +1 -1
- package/skill/inbase/SKILL.md +24 -23
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, useState, type ReactNode } from 'react'
|
|
2
|
-
import { persistInitialInstruction } from '../agentIntent'
|
|
2
|
+
import { persistAddContextFiles, persistInitialInstruction, persistRemoveContextFile } from '../agentIntent'
|
|
3
3
|
import { NameInput } from './NameInput'
|
|
4
4
|
import { SelectionThumbnail } from '../scene/SelectionThumbnail'
|
|
5
5
|
import {
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
type AimedRelation,
|
|
11
11
|
type BlueprintNote,
|
|
12
12
|
type BlueprintNoteKind,
|
|
13
|
+
type BlueprintPointer,
|
|
14
|
+
type BlueprintPointerKind,
|
|
13
15
|
type BranchChanges,
|
|
14
16
|
type CodebaseGraph,
|
|
15
17
|
type PatchImportAddition,
|
|
@@ -18,7 +20,8 @@ import {
|
|
|
18
20
|
type WorldLayout,
|
|
19
21
|
type WorkflowAction,
|
|
20
22
|
} from '../types'
|
|
21
|
-
import { findBlueprintNote } from '../userCreated'
|
|
23
|
+
import { findBlueprintNote, findBlueprintPointer } from '../userCreated'
|
|
24
|
+
import { EyeIcon } from './EyeIcon'
|
|
22
25
|
import { beginKeyboardIsolation, shouldIgnoreShortcut } from '../keyboard'
|
|
23
26
|
|
|
24
27
|
function reviewTitle(status: AgentIntentStatus) {
|
|
@@ -311,25 +314,43 @@ function BlueprintSymbolRow({
|
|
|
311
314
|
className,
|
|
312
315
|
hasNote,
|
|
313
316
|
noteOpen,
|
|
317
|
+
pointed,
|
|
314
318
|
canEdit,
|
|
315
319
|
canRemove,
|
|
316
320
|
onRemove,
|
|
317
321
|
onOpenNote,
|
|
322
|
+
onTogglePoint,
|
|
318
323
|
}: {
|
|
319
324
|
name: string
|
|
320
325
|
className?: string
|
|
321
326
|
hasNote: boolean
|
|
322
327
|
noteOpen?: boolean
|
|
328
|
+
pointed?: boolean
|
|
323
329
|
canEdit: boolean
|
|
324
330
|
canRemove?: boolean
|
|
325
331
|
onRemove?: () => void
|
|
326
332
|
onOpenNote: () => void
|
|
333
|
+
onTogglePoint?: () => void
|
|
327
334
|
}) {
|
|
328
335
|
return (
|
|
329
336
|
<li>
|
|
330
337
|
<span className={className}>{name}</span>
|
|
331
338
|
{canEdit && (
|
|
332
339
|
<div className="hud-item-actions">
|
|
340
|
+
{onTogglePoint && (
|
|
341
|
+
<button
|
|
342
|
+
className="hud-item-point"
|
|
343
|
+
type="button"
|
|
344
|
+
data-pointed={pointed ? 'true' : 'false'}
|
|
345
|
+
aria-label={
|
|
346
|
+
pointed ? `Stop pointing to ${name}` : `Point to ${name}`
|
|
347
|
+
}
|
|
348
|
+
aria-pressed={Boolean(pointed)}
|
|
349
|
+
onClick={onTogglePoint}
|
|
350
|
+
>
|
|
351
|
+
<EyeIcon size={13} />
|
|
352
|
+
</button>
|
|
353
|
+
)}
|
|
333
354
|
<button
|
|
334
355
|
className="hud-item-note"
|
|
335
356
|
type="button"
|
|
@@ -450,6 +471,119 @@ function InitialInstructionField({
|
|
|
450
471
|
)
|
|
451
472
|
}
|
|
452
473
|
|
|
474
|
+
function formatFileSize(bytes: number) {
|
|
475
|
+
if (bytes < 1024) return `${bytes} B`
|
|
476
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
|
477
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function fileToBase64(file: File) {
|
|
481
|
+
const bytes = new Uint8Array(await file.arrayBuffer())
|
|
482
|
+
const chunk = 0x8000
|
|
483
|
+
let binary = ''
|
|
484
|
+
for (let offset = 0; offset < bytes.length; offset += chunk) {
|
|
485
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + chunk))
|
|
486
|
+
}
|
|
487
|
+
return btoa(binary)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
type ContextFileInfo = {
|
|
491
|
+
id: string
|
|
492
|
+
name: string
|
|
493
|
+
mimeType: string
|
|
494
|
+
size: number
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function ContextFileDrop({
|
|
498
|
+
files,
|
|
499
|
+
busy,
|
|
500
|
+
error,
|
|
501
|
+
onAdd,
|
|
502
|
+
onRemove,
|
|
503
|
+
}: {
|
|
504
|
+
files: ContextFileInfo[]
|
|
505
|
+
busy: boolean
|
|
506
|
+
error: string | null
|
|
507
|
+
onAdd: (files: File[]) => void
|
|
508
|
+
onRemove: (fileId: string) => void
|
|
509
|
+
}) {
|
|
510
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
511
|
+
const [over, setOver] = useState(false)
|
|
512
|
+
|
|
513
|
+
const takeFiles = (list: FileList | File[] | null) => {
|
|
514
|
+
if (!list || busy) return
|
|
515
|
+
const next = [...list].filter((file) => file.size > 0)
|
|
516
|
+
if (next.length > 0) onAdd(next)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return (
|
|
520
|
+
<div className="hud-context">
|
|
521
|
+
<button
|
|
522
|
+
className="hud-context-drop"
|
|
523
|
+
type="button"
|
|
524
|
+
data-over={over}
|
|
525
|
+
data-busy={busy}
|
|
526
|
+
disabled={busy}
|
|
527
|
+
aria-label="Attach files for the LLM"
|
|
528
|
+
onDragEnter={(event) => {
|
|
529
|
+
event.preventDefault()
|
|
530
|
+
if (event.dataTransfer.types.includes('Files')) setOver(true)
|
|
531
|
+
}}
|
|
532
|
+
onDragOver={(event) => {
|
|
533
|
+
event.preventDefault()
|
|
534
|
+
event.dataTransfer.dropEffect = 'copy'
|
|
535
|
+
}}
|
|
536
|
+
onDragLeave={(event) => {
|
|
537
|
+
if (event.currentTarget.contains(event.relatedTarget as Node)) return
|
|
538
|
+
setOver(false)
|
|
539
|
+
}}
|
|
540
|
+
onDrop={(event) => {
|
|
541
|
+
event.preventDefault()
|
|
542
|
+
event.stopPropagation()
|
|
543
|
+
setOver(false)
|
|
544
|
+
takeFiles(event.dataTransfer.files)
|
|
545
|
+
}}
|
|
546
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
547
|
+
onClick={() => inputRef.current?.click()}
|
|
548
|
+
>
|
|
549
|
+
<input
|
|
550
|
+
ref={inputRef}
|
|
551
|
+
type="file"
|
|
552
|
+
multiple
|
|
553
|
+
hidden
|
|
554
|
+
onChange={(event) => {
|
|
555
|
+
takeFiles(event.target.files)
|
|
556
|
+
event.target.value = ''
|
|
557
|
+
}}
|
|
558
|
+
/>
|
|
559
|
+
{busy ? 'Attaching…' : 'Drop files or click to attach'}
|
|
560
|
+
</button>
|
|
561
|
+
{files.length > 0 && (
|
|
562
|
+
<ul className="hud-context-list">
|
|
563
|
+
{files.map((file) => (
|
|
564
|
+
<li key={file.id}>
|
|
565
|
+
<span className="hud-context-name" title={file.name}>
|
|
566
|
+
{file.name}
|
|
567
|
+
</span>
|
|
568
|
+
<span className="hud-context-size">{formatFileSize(file.size)}</span>
|
|
569
|
+
<button
|
|
570
|
+
className="hud-item-remove"
|
|
571
|
+
type="button"
|
|
572
|
+
aria-label={`Remove ${file.name}`}
|
|
573
|
+
disabled={busy}
|
|
574
|
+
onClick={() => onRemove(file.id)}
|
|
575
|
+
>
|
|
576
|
+
×
|
|
577
|
+
</button>
|
|
578
|
+
</li>
|
|
579
|
+
))}
|
|
580
|
+
</ul>
|
|
581
|
+
)}
|
|
582
|
+
{error ? <p className="hud-context-error">{error}</p> : null}
|
|
583
|
+
</div>
|
|
584
|
+
)
|
|
585
|
+
}
|
|
586
|
+
|
|
453
587
|
function blueprintIsDefined(intent: AgentIntent) {
|
|
454
588
|
return (
|
|
455
589
|
(intent.userCreatedBlocks?.length ?? 0) > 0 ||
|
|
@@ -457,31 +591,61 @@ function blueprintIsDefined(intent: AgentIntent) {
|
|
|
457
591
|
(intent.blueprintFunctions?.length ?? 0) > 0 ||
|
|
458
592
|
(intent.blueprintVariables?.length ?? 0) > 0 ||
|
|
459
593
|
(intent.blueprintImports?.length ?? 0) > 0 ||
|
|
460
|
-
(intent.blueprintNotes?.length ?? 0) > 0
|
|
594
|
+
(intent.blueprintNotes?.length ?? 0) > 0 ||
|
|
595
|
+
(intent.blueprintPointers?.length ?? 0) > 0
|
|
461
596
|
)
|
|
462
597
|
}
|
|
463
598
|
|
|
464
599
|
function HandshakeSetup({
|
|
465
600
|
instruction,
|
|
466
601
|
onInstructionChange,
|
|
602
|
+
contextFiles,
|
|
603
|
+
contextBusy,
|
|
604
|
+
contextError,
|
|
605
|
+
onAddContextFiles,
|
|
606
|
+
onRemoveContextFile,
|
|
467
607
|
blueprintDefined,
|
|
468
608
|
awaitingAttach,
|
|
469
609
|
nextAttachLabel,
|
|
470
610
|
}: {
|
|
471
611
|
instruction: string
|
|
472
612
|
onInstructionChange: (value: string) => void
|
|
613
|
+
contextFiles: ContextFileInfo[]
|
|
614
|
+
contextBusy: boolean
|
|
615
|
+
contextError: string | null
|
|
616
|
+
onAddContextFiles: (files: File[]) => void
|
|
617
|
+
onRemoveContextFile: (fileId: string) => void
|
|
473
618
|
blueprintDefined: boolean
|
|
474
619
|
awaitingAttach: boolean
|
|
475
620
|
nextAttachLabel: string | null
|
|
476
621
|
}) {
|
|
477
622
|
return (
|
|
478
623
|
<div className="hud-setup">
|
|
479
|
-
<section
|
|
624
|
+
<section
|
|
625
|
+
className="hud-setup-section"
|
|
626
|
+
onDragOver={(event) => {
|
|
627
|
+
if (event.dataTransfer.types.includes('Files')) event.preventDefault()
|
|
628
|
+
}}
|
|
629
|
+
onDrop={(event) => {
|
|
630
|
+
event.preventDefault()
|
|
631
|
+
const dropped = [...(event.dataTransfer.files ?? [])].filter(
|
|
632
|
+
(file) => file.size > 0,
|
|
633
|
+
)
|
|
634
|
+
if (dropped.length > 0) onAddContextFiles(dropped)
|
|
635
|
+
}}
|
|
636
|
+
>
|
|
480
637
|
<h2 className="hud-setup-heading">Instructions</h2>
|
|
481
638
|
<InitialInstructionField
|
|
482
639
|
value={instruction}
|
|
483
640
|
onChange={onInstructionChange}
|
|
484
641
|
/>
|
|
642
|
+
<ContextFileDrop
|
|
643
|
+
files={contextFiles}
|
|
644
|
+
busy={contextBusy}
|
|
645
|
+
error={contextError}
|
|
646
|
+
onAdd={onAddContextFiles}
|
|
647
|
+
onRemove={onRemoveContextFile}
|
|
648
|
+
/>
|
|
485
649
|
</section>
|
|
486
650
|
<section className="hud-setup-section">
|
|
487
651
|
<h2 className="hud-setup-heading">
|
|
@@ -538,6 +702,11 @@ function sessionLiveStatus(intent: AgentIntent) {
|
|
|
538
702
|
if (intent.awaitingAttach) {
|
|
539
703
|
return { text: 'Waiting for /inbase in Cursor', busy: false }
|
|
540
704
|
}
|
|
705
|
+
if (intent.status === 'pending') {
|
|
706
|
+
return intent.listening
|
|
707
|
+
? { text: 'LLM is listening — accept or send an instruction', busy: false }
|
|
708
|
+
: { text: 'Review this step', busy: false }
|
|
709
|
+
}
|
|
541
710
|
if (kind === 'execute') {
|
|
542
711
|
return { text: `LLM received ${detail}`, busy: true }
|
|
543
712
|
}
|
|
@@ -566,11 +735,6 @@ function sessionLiveStatus(intent: AgentIntent) {
|
|
|
566
735
|
? { text: 'LLM is listening — run the next step', busy: false }
|
|
567
736
|
: { text: 'Plan ready', busy: false }
|
|
568
737
|
}
|
|
569
|
-
if (intent.status === 'pending') {
|
|
570
|
-
return intent.listening
|
|
571
|
-
? { text: 'LLM is listening — accept or send an instruction', busy: false }
|
|
572
|
-
: { text: 'Review this step', busy: false }
|
|
573
|
-
}
|
|
574
738
|
if (
|
|
575
739
|
kind === 'attached' ||
|
|
576
740
|
intent.status === 'blueprint' ||
|
|
@@ -649,14 +813,22 @@ function SessionPanel({
|
|
|
649
813
|
const [initialInstruction, setInitialInstruction] = useState(
|
|
650
814
|
() => intent.initialInstruction ?? '',
|
|
651
815
|
)
|
|
816
|
+
const [contextFiles, setContextFiles] = useState<ContextFileInfo[]>(
|
|
817
|
+
() => intent.contextFiles ?? [],
|
|
818
|
+
)
|
|
819
|
+
const [contextBusy, setContextBusy] = useState(false)
|
|
820
|
+
const [contextError, setContextError] = useState<string | null>(null)
|
|
652
821
|
const sessionId = intent.sessionId
|
|
653
|
-
const
|
|
822
|
+
const latestEntry = intent.isActiveDiff ? intent.chain.at(-1) : null
|
|
823
|
+
const pending =
|
|
824
|
+
latestEntry?.status === 'pending' ||
|
|
825
|
+
(intent.status === 'pending' && intent.isActiveDiff)
|
|
654
826
|
const askingBlueprint = intent.status === 'blueprint_ask'
|
|
655
827
|
const sendingBlueprint = intent.status === 'blueprint'
|
|
656
828
|
const canPlace = Boolean(intent.creationMode)
|
|
657
829
|
const preparing = intent.status === 'preparing'
|
|
658
|
-
const planReady = intent.status === 'planned'
|
|
659
830
|
const working = intent.status === 'working' || intent.status === 'replanning'
|
|
831
|
+
const planReady = intent.status === 'planned' && !pending
|
|
660
832
|
const previewing = intent.preview
|
|
661
833
|
const chainIndex = intent.chainIndex ?? 0
|
|
662
834
|
const previousDiff = intent.chain[chainIndex - 1]
|
|
@@ -665,10 +837,6 @@ function SessionPanel({
|
|
|
665
837
|
intent.step && intent.steps?.length > 0
|
|
666
838
|
? `Step ${intent.step} of ${intent.steps.length}`
|
|
667
839
|
: 'Patch'
|
|
668
|
-
const lastStep =
|
|
669
|
-
typeof intent.step === 'number' &&
|
|
670
|
-
intent.steps.length > 0 &&
|
|
671
|
-
intent.step >= intent.steps.length
|
|
672
840
|
const stepByStep = intent.stepByStep !== false
|
|
673
841
|
const addedFunctions = intent.addedFunctions ?? []
|
|
674
842
|
const addedVariables = intent.addedVariables ?? []
|
|
@@ -679,25 +847,27 @@ function SessionPanel({
|
|
|
679
847
|
intent.status === 'finished'
|
|
680
848
|
? intent.steps.map((step) => step.index)
|
|
681
849
|
: intent.chain
|
|
682
|
-
.filter(
|
|
683
|
-
(entry) =>
|
|
684
|
-
entry.status === 'applied' || entry.status === 'extended',
|
|
685
|
-
)
|
|
850
|
+
.filter((entry) => entry.status === 'applied')
|
|
686
851
|
.map((entry) => entry.step),
|
|
687
852
|
)
|
|
688
853
|
if (intent.status === 'approved' && typeof intent.step === 'number') {
|
|
689
854
|
acceptedSteps.add(intent.step)
|
|
690
855
|
}
|
|
691
|
-
const proposalStep =
|
|
692
|
-
|
|
856
|
+
const proposalStep = pending
|
|
857
|
+
? (latestEntry?.status === 'pending' ? latestEntry.step : intent.step)
|
|
858
|
+
: null
|
|
693
859
|
const processingStep =
|
|
694
860
|
working && typeof intent.step === 'number' ? intent.step : null
|
|
695
861
|
const invokeStep =
|
|
696
|
-
planReady && !working
|
|
862
|
+
planReady && !working && proposalStep === null
|
|
697
863
|
? (intent.steps.find((step) => !acceptedSteps.has(step.index)) ?? null)
|
|
698
864
|
: null
|
|
699
865
|
const canRunNext = stepByStep && Boolean(invokeStep)
|
|
700
866
|
const canAcceptProposal = proposalStep !== null
|
|
867
|
+
const lastStep =
|
|
868
|
+
typeof proposalStep === 'number' &&
|
|
869
|
+
intent.steps.length > 0 &&
|
|
870
|
+
proposalStep >= intent.steps.length
|
|
701
871
|
const panelDone =
|
|
702
872
|
intent.status === 'finished' ||
|
|
703
873
|
intent.status === 'approved' ||
|
|
@@ -710,8 +880,15 @@ function SessionPanel({
|
|
|
710
880
|
|
|
711
881
|
useEffect(() => {
|
|
712
882
|
setInitialInstruction(intent.initialInstruction ?? '')
|
|
883
|
+
setContextFiles(intent.contextFiles ?? [])
|
|
884
|
+
setContextError(null)
|
|
713
885
|
}, [sessionId])
|
|
714
886
|
|
|
887
|
+
useEffect(() => {
|
|
888
|
+
if (contextBusy) return
|
|
889
|
+
setContextFiles(intent.contextFiles ?? [])
|
|
890
|
+
}, [contextBusy, intent.contextFiles])
|
|
891
|
+
|
|
715
892
|
if (!sessionId || !isReviewingIntent(intent.status)) return null
|
|
716
893
|
|
|
717
894
|
const updateInitialInstruction = (value: string) => {
|
|
@@ -719,6 +896,43 @@ function SessionPanel({
|
|
|
719
896
|
persistInitialInstruction(sessionId, value)
|
|
720
897
|
}
|
|
721
898
|
|
|
899
|
+
const addContextFiles = (files: File[]) => {
|
|
900
|
+
setContextBusy(true)
|
|
901
|
+
setContextError(null)
|
|
902
|
+
void Promise.all(
|
|
903
|
+
files.map(async (file) => ({
|
|
904
|
+
name: file.name,
|
|
905
|
+
mimeType: file.type || 'application/octet-stream',
|
|
906
|
+
contentBase64: await fileToBase64(file),
|
|
907
|
+
})),
|
|
908
|
+
)
|
|
909
|
+
.then((payload) => persistAddContextFiles(sessionId, payload))
|
|
910
|
+
.then((next) => {
|
|
911
|
+
setContextFiles(next.contextFiles ?? [])
|
|
912
|
+
})
|
|
913
|
+
.catch((caught) => {
|
|
914
|
+
setContextError(
|
|
915
|
+
caught instanceof Error ? caught.message : 'Could not attach files',
|
|
916
|
+
)
|
|
917
|
+
})
|
|
918
|
+
.finally(() => setContextBusy(false))
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const removeContextFile = (fileId: string) => {
|
|
922
|
+
setContextBusy(true)
|
|
923
|
+
setContextError(null)
|
|
924
|
+
void persistRemoveContextFile(sessionId, fileId)
|
|
925
|
+
.then((next) => {
|
|
926
|
+
setContextFiles(next.contextFiles ?? [])
|
|
927
|
+
})
|
|
928
|
+
.catch((caught) => {
|
|
929
|
+
setContextError(
|
|
930
|
+
caught instanceof Error ? caught.message : 'Could not remove file',
|
|
931
|
+
)
|
|
932
|
+
})
|
|
933
|
+
.finally(() => setContextBusy(false))
|
|
934
|
+
}
|
|
935
|
+
|
|
722
936
|
const showInitialInstruction =
|
|
723
937
|
Boolean(intent.awaitingAttach) &&
|
|
724
938
|
(askingBlueprint || sendingBlueprint || preparing)
|
|
@@ -800,6 +1014,11 @@ function SessionPanel({
|
|
|
800
1014
|
<HandshakeSetup
|
|
801
1015
|
instruction={initialInstruction}
|
|
802
1016
|
onInstructionChange={updateInitialInstruction}
|
|
1017
|
+
contextFiles={contextFiles}
|
|
1018
|
+
contextBusy={contextBusy}
|
|
1019
|
+
contextError={contextError}
|
|
1020
|
+
onAddContextFiles={addContextFiles}
|
|
1021
|
+
onRemoveContextFile={removeContextFile}
|
|
803
1022
|
blueprintDefined={blueprintIsDefined(intent)}
|
|
804
1023
|
awaitingAttach={Boolean(intent.awaitingAttach)}
|
|
805
1024
|
nextAttachLabel={queuedBehind}
|
|
@@ -875,7 +1094,7 @@ function SessionPanel({
|
|
|
875
1094
|
{intent.steps.map((step) => {
|
|
876
1095
|
const proposed = proposalStep === step.index
|
|
877
1096
|
const processing = processingStep === step.index
|
|
878
|
-
const accepted = acceptedSteps.has(step.index)
|
|
1097
|
+
const accepted = acceptedSteps.has(step.index) && !proposed
|
|
879
1098
|
const creating = processing && !proposed
|
|
880
1099
|
const showStepAction =
|
|
881
1100
|
creating ||
|
|
@@ -1074,7 +1293,7 @@ function SessionPanel({
|
|
|
1074
1293
|
value={instruction}
|
|
1075
1294
|
maxLength={4000}
|
|
1076
1295
|
rows={3}
|
|
1077
|
-
placeholder="Describe what should change in
|
|
1296
|
+
placeholder="Describe what should change in this proposal…"
|
|
1078
1297
|
onChange={(event) => setInstruction(event.target.value)}
|
|
1079
1298
|
onKeyDown={(event) => event.stopPropagation()}
|
|
1080
1299
|
/>
|
|
@@ -1350,6 +1569,11 @@ function explorerInstructions({
|
|
|
1350
1569
|
? [
|
|
1351
1570
|
{ id: 'space', keys: ['Space'], label: 'Place file' },
|
|
1352
1571
|
{ id: 'b-island', keys: ['B'], label: 'Place island' },
|
|
1572
|
+
{
|
|
1573
|
+
id: 'point-to',
|
|
1574
|
+
keys: ['Point to'],
|
|
1575
|
+
label: 'Keep a file, folder, or function in mind',
|
|
1576
|
+
},
|
|
1353
1577
|
]
|
|
1354
1578
|
: []),
|
|
1355
1579
|
...backspace,
|
|
@@ -1433,11 +1657,10 @@ function explorerInstructions({
|
|
|
1433
1657
|
{
|
|
1434
1658
|
id: 'add-file-folder',
|
|
1435
1659
|
keys: ['Right-click'],
|
|
1436
|
-
label: 'Add file or folder',
|
|
1660
|
+
label: 'Add file or folder, or point to a folder',
|
|
1437
1661
|
},
|
|
1438
1662
|
]
|
|
1439
1663
|
: []),
|
|
1440
|
-
{ id: 'click-line', keys: ['Click'], label: 'A line to fly there' },
|
|
1441
1664
|
{
|
|
1442
1665
|
id: 'option-click-walk',
|
|
1443
1666
|
keys: ['Option', 'Click'],
|
|
@@ -1544,6 +1767,7 @@ type HUDProps = {
|
|
|
1544
1767
|
blueprintVariables?: PatchSymbolAddition[]
|
|
1545
1768
|
blueprintImports?: PatchImportAddition[]
|
|
1546
1769
|
blueprintNotes?: BlueprintNote[]
|
|
1770
|
+
blueprintPointers?: BlueprintPointer[]
|
|
1547
1771
|
onAddBlueprintFunction?: (fileId: string, name: string) => boolean
|
|
1548
1772
|
onAddBlueprintVariable?: (fileId: string, name: string) => boolean
|
|
1549
1773
|
onAddBlueprintImport?: (fileId: string, raw: string) => boolean
|
|
@@ -1560,6 +1784,11 @@ type HUDProps = {
|
|
|
1560
1784
|
name?: string
|
|
1561
1785
|
note: string
|
|
1562
1786
|
}) => void
|
|
1787
|
+
onToggleBlueprintPointer?: (next: {
|
|
1788
|
+
kind: BlueprintPointerKind
|
|
1789
|
+
path: string
|
|
1790
|
+
name?: string
|
|
1791
|
+
}) => void
|
|
1563
1792
|
onMapAddFile?: (folderPath: string) => void
|
|
1564
1793
|
onMapAddFolder?: (folderPath: string) => void
|
|
1565
1794
|
onInspectFile?: (fileId: string) => void
|
|
@@ -1619,6 +1848,7 @@ export function HUD({
|
|
|
1619
1848
|
blueprintVariables = [],
|
|
1620
1849
|
blueprintImports = [],
|
|
1621
1850
|
blueprintNotes = [],
|
|
1851
|
+
blueprintPointers = [],
|
|
1622
1852
|
onAddBlueprintFunction,
|
|
1623
1853
|
onAddBlueprintVariable,
|
|
1624
1854
|
onAddBlueprintImport,
|
|
@@ -1626,6 +1856,7 @@ export function HUD({
|
|
|
1626
1856
|
onRemoveBlueprintVariable,
|
|
1627
1857
|
onRemoveBlueprintImport,
|
|
1628
1858
|
onSetBlueprintNote,
|
|
1859
|
+
onToggleBlueprintPointer,
|
|
1629
1860
|
onMapAddFile,
|
|
1630
1861
|
onMapAddFolder,
|
|
1631
1862
|
onInspectFile,
|
|
@@ -1755,6 +1986,16 @@ export function HUD({
|
|
|
1755
1986
|
const selectedFileNote = selected
|
|
1756
1987
|
? findBlueprintNote(blueprintNotes, selected.id, 'file')
|
|
1757
1988
|
: ''
|
|
1989
|
+
const selectedFilePointed = selected
|
|
1990
|
+
? findBlueprintPointer(blueprintPointers, 'file', selected.id)
|
|
1991
|
+
: false
|
|
1992
|
+
const selectedFolderPointed = selectedFolderNode
|
|
1993
|
+
? findBlueprintPointer(
|
|
1994
|
+
blueprintPointers,
|
|
1995
|
+
'folder',
|
|
1996
|
+
selectedFolderNode.path,
|
|
1997
|
+
)
|
|
1998
|
+
: false
|
|
1758
1999
|
const openFileNote = () => {
|
|
1759
2000
|
if (!selected || !onSetBlueprintNote) return
|
|
1760
2001
|
setInstructionsOpen(false)
|
|
@@ -2087,6 +2328,20 @@ export function HUD({
|
|
|
2087
2328
|
Inspect file
|
|
2088
2329
|
</button>
|
|
2089
2330
|
)}
|
|
2331
|
+
{canEditBlueprint && onToggleBlueprintPointer && (
|
|
2332
|
+
<button
|
|
2333
|
+
className="hud-button hud-inspect hud-point"
|
|
2334
|
+
type="button"
|
|
2335
|
+
data-pointed={selectedFilePointed ? 'true' : 'false'}
|
|
2336
|
+
aria-pressed={selectedFilePointed}
|
|
2337
|
+
onClick={() =>
|
|
2338
|
+
onToggleBlueprintPointer({ kind: 'file', path: selected.id })
|
|
2339
|
+
}
|
|
2340
|
+
>
|
|
2341
|
+
<EyeIcon size={15} />
|
|
2342
|
+
{selectedFilePointed ? 'Stop pointing' : 'Point to file'}
|
|
2343
|
+
</button>
|
|
2344
|
+
)}
|
|
2090
2345
|
{canEditBlueprint && onSetBlueprintNote && (
|
|
2091
2346
|
<button
|
|
2092
2347
|
className="hud-button hud-inspect"
|
|
@@ -2178,10 +2433,26 @@ export function HUD({
|
|
|
2178
2433
|
}
|
|
2179
2434
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2180
2435
|
canRemove={Boolean(canEditBlueprint && symbol.intended)}
|
|
2436
|
+
pointed={findBlueprintPointer(
|
|
2437
|
+
blueprintPointers,
|
|
2438
|
+
'function',
|
|
2439
|
+
selected.id,
|
|
2440
|
+
symbol.name,
|
|
2441
|
+
)}
|
|
2181
2442
|
onRemove={() =>
|
|
2182
2443
|
onRemoveBlueprintFunction?.(selected.id, symbol.name)
|
|
2183
2444
|
}
|
|
2184
2445
|
onOpenNote={() => openSymbolNote('function', symbol.name)}
|
|
2446
|
+
onTogglePoint={
|
|
2447
|
+
onToggleBlueprintPointer
|
|
2448
|
+
? () =>
|
|
2449
|
+
onToggleBlueprintPointer({
|
|
2450
|
+
kind: 'function',
|
|
2451
|
+
path: selected.id,
|
|
2452
|
+
name: symbol.name,
|
|
2453
|
+
})
|
|
2454
|
+
: undefined
|
|
2455
|
+
}
|
|
2185
2456
|
/>
|
|
2186
2457
|
))}
|
|
2187
2458
|
{extraAddedFunctions.map((item) => (
|
|
@@ -2203,7 +2474,23 @@ export function HUD({
|
|
|
2203
2474
|
noteEditor.name === item.name
|
|
2204
2475
|
}
|
|
2205
2476
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2477
|
+
pointed={findBlueprintPointer(
|
|
2478
|
+
blueprintPointers,
|
|
2479
|
+
'function',
|
|
2480
|
+
selected.id,
|
|
2481
|
+
item.name,
|
|
2482
|
+
)}
|
|
2206
2483
|
onOpenNote={() => openSymbolNote('function', item.name)}
|
|
2484
|
+
onTogglePoint={
|
|
2485
|
+
onToggleBlueprintPointer
|
|
2486
|
+
? () =>
|
|
2487
|
+
onToggleBlueprintPointer({
|
|
2488
|
+
kind: 'function',
|
|
2489
|
+
path: selected.id,
|
|
2490
|
+
name: item.name,
|
|
2491
|
+
})
|
|
2492
|
+
: undefined
|
|
2493
|
+
}
|
|
2207
2494
|
/>
|
|
2208
2495
|
))}
|
|
2209
2496
|
</ul>
|
|
@@ -2244,10 +2531,26 @@ export function HUD({
|
|
|
2244
2531
|
}
|
|
2245
2532
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2246
2533
|
canRemove={Boolean(canEditBlueprint && symbol.intended)}
|
|
2534
|
+
pointed={findBlueprintPointer(
|
|
2535
|
+
blueprintPointers,
|
|
2536
|
+
'variable',
|
|
2537
|
+
selected.id,
|
|
2538
|
+
symbol.name,
|
|
2539
|
+
)}
|
|
2247
2540
|
onRemove={() =>
|
|
2248
2541
|
onRemoveBlueprintVariable?.(selected.id, symbol.name)
|
|
2249
2542
|
}
|
|
2250
2543
|
onOpenNote={() => openSymbolNote('variable', symbol.name)}
|
|
2544
|
+
onTogglePoint={
|
|
2545
|
+
onToggleBlueprintPointer
|
|
2546
|
+
? () =>
|
|
2547
|
+
onToggleBlueprintPointer({
|
|
2548
|
+
kind: 'variable',
|
|
2549
|
+
path: selected.id,
|
|
2550
|
+
name: symbol.name,
|
|
2551
|
+
})
|
|
2552
|
+
: undefined
|
|
2553
|
+
}
|
|
2251
2554
|
/>
|
|
2252
2555
|
))}
|
|
2253
2556
|
{extraAddedVariables.map((item) => (
|
|
@@ -2269,7 +2572,23 @@ export function HUD({
|
|
|
2269
2572
|
noteEditor.name === item.name
|
|
2270
2573
|
}
|
|
2271
2574
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2575
|
+
pointed={findBlueprintPointer(
|
|
2576
|
+
blueprintPointers,
|
|
2577
|
+
'variable',
|
|
2578
|
+
selected.id,
|
|
2579
|
+
item.name,
|
|
2580
|
+
)}
|
|
2272
2581
|
onOpenNote={() => openSymbolNote('variable', item.name)}
|
|
2582
|
+
onTogglePoint={
|
|
2583
|
+
onToggleBlueprintPointer
|
|
2584
|
+
? () =>
|
|
2585
|
+
onToggleBlueprintPointer({
|
|
2586
|
+
kind: 'variable',
|
|
2587
|
+
path: selected.id,
|
|
2588
|
+
name: item.name,
|
|
2589
|
+
})
|
|
2590
|
+
: undefined
|
|
2591
|
+
}
|
|
2273
2592
|
/>
|
|
2274
2593
|
))}
|
|
2275
2594
|
</ul>
|
|
@@ -2413,6 +2732,24 @@ export function HUD({
|
|
|
2413
2732
|
))}
|
|
2414
2733
|
</ul>
|
|
2415
2734
|
)}
|
|
2735
|
+
{canPlace && onToggleBlueprintPointer && (
|
|
2736
|
+
<button
|
|
2737
|
+
className="hud-button hud-inspect hud-point"
|
|
2738
|
+
type="button"
|
|
2739
|
+
data-pointed={selectedFolderPointed ? 'true' : 'false'}
|
|
2740
|
+
aria-pressed={selectedFolderPointed}
|
|
2741
|
+
disabled={naming || selectedFolderNode.path.startsWith('draft:')}
|
|
2742
|
+
onClick={() =>
|
|
2743
|
+
onToggleBlueprintPointer({
|
|
2744
|
+
kind: 'folder',
|
|
2745
|
+
path: selectedFolderNode.path,
|
|
2746
|
+
})
|
|
2747
|
+
}
|
|
2748
|
+
>
|
|
2749
|
+
<EyeIcon size={15} />
|
|
2750
|
+
{selectedFolderPointed ? 'Stop pointing' : 'Point to folder'}
|
|
2751
|
+
</button>
|
|
2752
|
+
)}
|
|
2416
2753
|
{canPlace && mapping && onMapAddFile && onMapAddFolder && (
|
|
2417
2754
|
<div className="hud-decide hud-map-blueprint">
|
|
2418
2755
|
<button
|
|
@@ -2451,6 +2788,20 @@ export function HUD({
|
|
|
2451
2788
|
plannedIds={plannedIds}
|
|
2452
2789
|
createdIds={createdIds}
|
|
2453
2790
|
deletedIds={deletedIds}
|
|
2791
|
+
pointedFileIds={
|
|
2792
|
+
blueprintHidden
|
|
2793
|
+
? []
|
|
2794
|
+
: blueprintPointers.flatMap((item) =>
|
|
2795
|
+
item.kind === 'folder' ? [] : [item.path],
|
|
2796
|
+
)
|
|
2797
|
+
}
|
|
2798
|
+
pointedFolderPaths={
|
|
2799
|
+
blueprintHidden
|
|
2800
|
+
? []
|
|
2801
|
+
: blueprintPointers.flatMap((item) =>
|
|
2802
|
+
item.kind === 'folder' ? [item.path] : [],
|
|
2803
|
+
)
|
|
2804
|
+
}
|
|
2454
2805
|
onMinimize={() => {
|
|
2455
2806
|
setThumbnailMaximized(false)
|
|
2456
2807
|
setThumbnailMinimized((current) => !current)
|