Skip to content

Commit 4c8bfe1

Browse files
author
Sviridov Konstantin 00565358
committed
File tree context menu
1 parent 9b6db08 commit 4c8bfe1

19 files changed

Lines changed: 91 additions & 8 deletions

File tree

packages/app/src/components/file-tree.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ beforeAll(async () => {
2929
mock.module("@opencode-ai/ui/file-icon", () => ({ FileIcon: () => null }))
3030
mock.module("@opencode-ai/ui/icon", () => ({ Icon: () => null }))
3131
mock.module("@opencode-ai/ui/tooltip", () => ({ Tooltip: (props: { children?: unknown }) => props.children }))
32+
mock.module("@opencode-ai/ui/context-menu", () => ({
33+
ContextMenu: Object.assign((props: { children?: unknown }) => props.children, {
34+
Trigger: (props: { children?: unknown }) => props.children,
35+
Portal: (props: { children?: unknown }) => props.children,
36+
Content: (props: { children?: unknown }) => props.children,
37+
Item: (props: { children?: unknown }) => props.children,
38+
ItemLabel: (props: { children?: unknown }) => props.children,
39+
}),
40+
}))
3241
const mod = await import("./file-tree")
3342
shouldListRoot = mod.shouldListRoot
3443
shouldListExpanded = mod.shouldListExpanded

packages/app/src/components/file-tree.tsx

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { useFile } from "@/context/file"
22
import { encodeFilePath } from "@/context/file/path"
3+
import { useLanguage } from "@/context/language"
34
import { Collapsible } from "@opencode-ai/ui/collapsible"
5+
import { ContextMenu } from "@opencode-ai/ui/context-menu"
46
import { FileIcon } from "@opencode-ai/ui/file-icon"
57
import { Icon } from "@opencode-ai/ui/icon"
68
import {
@@ -13,10 +15,8 @@ import {
1315
splitProps,
1416
Switch,
1517
untrack,
16-
type ComponentProps,
1718
type ParentProps,
1819
} from "solid-js"
19-
import { Dynamic } from "solid-js/web"
2020
import type { FileNode } from "@opencode-ai/sdk/v2"
2121

2222
const MAX_DEPTH = 128
@@ -108,9 +108,7 @@ const withFileDragImage = (event: DragEvent) => {
108108
}
109109

110110
const FileTreeNode = (
111-
p: ParentProps &
112-
ComponentProps<"div"> &
113-
ComponentProps<"button"> & {
111+
p: ParentProps & {
114112
node: FileNode
115113
level: number
116114
active?: string
@@ -119,8 +117,13 @@ const FileTreeNode = (
119117
kinds?: ReadonlyMap<string, Kind>
120118
marks?: Set<string>
121119
as?: "div" | "button"
120+
type?: "button"
121+
onClick?: (e: MouseEvent) => void
122+
class?: string
123+
classList?: { [k: string]: boolean | undefined }
122124
},
123125
) => {
126+
const language = useLanguage()
124127
const [local, rest] = splitProps(p, [
125128
"node",
126129
"level",
@@ -142,9 +145,18 @@ const FileTreeNode = (
142145
return kindTextColor(value)
143146
}
144147

148+
const handleCopyAbsolutePath = () => {
149+
navigator.clipboard.writeText(local.node.absolute).catch(() => {})
150+
}
151+
152+
const handleCopyRelativePath = () => {
153+
navigator.clipboard.writeText(local.node.path).catch(() => {})
154+
}
155+
145156
return (
146-
<Dynamic
147-
component={local.as ?? "div"}
157+
<ContextMenu modal={false} preventScroll={true}>
158+
<ContextMenu.Trigger
159+
as={local.as ?? "div"}
148160
classList={{
149161
"w-full min-w-0 h-6 flex items-center justify-start gap-x-1.5 rounded-md px-1.5 py-0 text-left hover:bg-surface-raised-base-hover active:bg-surface-base-active transition-colors cursor-pointer": true,
150162
"bg-surface-base-active": local.node.path === local.active,
@@ -186,7 +198,18 @@ const FileTreeNode = (
186198
}
187199
return <div class="shrink-0 size-1.5 mr-1.5 rounded-full" style={kindDotColor(value)} />
188200
})()}
189-
</Dynamic>
201+
</ContextMenu.Trigger>
202+
<ContextMenu.Portal>
203+
<ContextMenu.Content>
204+
<ContextMenu.Item onSelect={handleCopyAbsolutePath}>
205+
<ContextMenu.ItemLabel>{language.t("filetree.contextMenu.copyPath")}</ContextMenu.ItemLabel>
206+
</ContextMenu.Item>
207+
<ContextMenu.Item onSelect={handleCopyRelativePath}>
208+
<ContextMenu.ItemLabel>{language.t("filetree.contextMenu.copyRelativePath")}</ContextMenu.ItemLabel>
209+
</ContextMenu.Item>
210+
</ContextMenu.Content>
211+
</ContextMenu.Portal>
212+
</ContextMenu>
190213
)
191214
}
192215

packages/app/src/i18n/ar.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,4 +843,7 @@ export const dict = {
843843
"error.childStore.persistedProjectIconCreateFailed": "فشل إنشاء أيقونة المشروع الدائمة",
844844
"error.childStore.storeCreateFailed": "فشل إنشاء المخزن",
845845
"terminal.connectionLost.abnormalClose": "تم إغلاق WebSocket بشكل غير طبيعي: {{code}}",
846+
847+
"filetree.contextMenu.copyPath": "نسخ المسار",
848+
"filetree.contextMenu.copyRelativePath": "نسخ المسار النسبي",
846849
}

packages/app/src/i18n/br.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,4 +856,7 @@ export const dict = {
856856
"error.childStore.persistedProjectIconCreateFailed": "Falha ao criar ícone de projeto persistente",
857857
"error.childStore.storeCreateFailed": "Falha ao criar armazenamento",
858858
"terminal.connectionLost.abnormalClose": "WebSocket fechado anormalmente: {{code}}",
859+
860+
"filetree.contextMenu.copyPath": "Copiar caminho",
861+
"filetree.contextMenu.copyRelativePath": "Copiar caminho relativo",
859862
}

packages/app/src/i18n/bs.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,4 +932,7 @@ export const dict = {
932932
"error.childStore.persistedProjectIconCreateFailed": "Nije uspjelo kreiranje trajne ikone projekta",
933933
"error.childStore.storeCreateFailed": "Nije uspjelo kreiranje skladišta",
934934
"terminal.connectionLost.abnormalClose": "WebSocket zatvoren nenormalno: {{code}}",
935+
936+
"filetree.contextMenu.copyPath": "Kopiraj putanju",
937+
"filetree.contextMenu.copyRelativePath": "Kopiraj relativnu putanju",
935938
}

packages/app/src/i18n/da.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -926,4 +926,7 @@ export const dict = {
926926
"error.childStore.persistedProjectIconCreateFailed": "Kunne ikke oprette vedvarende projektikon",
927927
"error.childStore.storeCreateFailed": "Kunne ikke oprette lager",
928928
"terminal.connectionLost.abnormalClose": "WebSocket lukkede unormalt: {{code}}",
929+
930+
"filetree.contextMenu.copyPath": "Kopier sti",
931+
"filetree.contextMenu.copyRelativePath": "Kopier relativ sti",
929932
}

packages/app/src/i18n/de.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,4 +868,7 @@ export const dict = {
868868
"error.childStore.persistedProjectIconCreateFailed": "Dauerhaftes Projekticon konnte nicht erstellt werden",
869869
"error.childStore.storeCreateFailed": "Speicher konnte nicht erstellt werden",
870870
"terminal.connectionLost.abnormalClose": "WebSocket abnormal geschlossen: {{code}}",
871+
872+
"filetree.contextMenu.copyPath": "Pfad kopieren",
873+
"filetree.contextMenu.copyRelativePath": "Relativen pfad kopieren",
871874
} satisfies Partial<Record<Keys, string>>

packages/app/src/i18n/en.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,4 +952,7 @@ export const dict = {
952952
"workspace.reset.archived.one": "1 session will be archived.",
953953
"workspace.reset.archived.many": "{{count}} sessions will be archived.",
954954
"workspace.reset.note": "This will reset the workspace to match the default branch.",
955+
956+
"filetree.contextMenu.copyPath": "Copy path",
957+
"filetree.contextMenu.copyRelativePath": "Copy relative path",
955958
}

packages/app/src/i18n/es.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -939,4 +939,7 @@ export const dict = {
939939
"error.childStore.persistedProjectIconCreateFailed": "Error al crear icono de proyecto persistente",
940940
"error.childStore.storeCreateFailed": "Error al crear almacén",
941941
"terminal.connectionLost.abnormalClose": "WebSocket cerrado anormalmente: {{code}}",
942+
943+
"filetree.contextMenu.copyPath": "Copiar ruta",
944+
"filetree.contextMenu.copyRelativePath": "Copiar ruta relativa",
942945
}

packages/app/src/i18n/fr.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,4 +867,7 @@ export const dict = {
867867
"error.childStore.persistedProjectIconCreateFailed": "Échec de la création de l'icône de projet persistante",
868868
"error.childStore.storeCreateFailed": "Échec de la création du stockage",
869869
"terminal.connectionLost.abnormalClose": "WebSocket fermé anormalement : {{code}}",
870+
871+
"filetree.contextMenu.copyPath": "Copier le chemin",
872+
"filetree.contextMenu.copyRelativePath": "Copier le chemin relatif",
870873
}

0 commit comments

Comments
 (0)