Skip to content

Commit adeff02

Browse files
author
Sviridov Konstantin 00565358
committed
File tree context menu
1 parent 9730008 commit adeff02

19 files changed

Lines changed: 91 additions & 6 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 & 6 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 {
@@ -108,9 +110,7 @@ const withFileDragImage = (event: DragEvent) => {
108110
}
109111

110112
const FileTreeNode = (
111-
p: ParentProps &
112-
ComponentProps<"div"> &
113-
ComponentProps<"button"> & {
113+
p: ParentProps & {
114114
node: FileNode
115115
level: number
116116
active?: string
@@ -119,8 +119,13 @@ const FileTreeNode = (
119119
kinds?: ReadonlyMap<string, Kind>
120120
marks?: Set<string>
121121
as?: "div" | "button"
122+
type?: "button"
123+
onClick?: (e: MouseEvent) => void
124+
class?: string
125+
classList?: Record<string, boolean | undefined>
122126
},
123127
) => {
128+
const language = useLanguage()
124129
const [local, rest] = splitProps(p, [
125130
"node",
126131
"level",
@@ -142,9 +147,18 @@ const FileTreeNode = (
142147
return kindTextColor(value)
143148
}
144149

150+
const handleCopyAbsolutePath = () => {
151+
navigator.clipboard.writeText(local.node.absolute).catch(() => {})
152+
}
153+
154+
const handleCopyRelativePath = () => {
155+
navigator.clipboard.writeText(local.node.path).catch(() => {})
156+
}
157+
145158
return (
146-
<Dynamic
147-
component={local.as ?? "div"}
159+
<ContextMenu modal={false} preventScroll={true}>
160+
<ContextMenu.Trigger
161+
as={local.as ?? "div"}
148162
classList={{
149163
"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,
150164
"bg-surface-base-active": local.node.path === local.active,
@@ -186,7 +200,18 @@ const FileTreeNode = (
186200
}
187201
return <div class="shrink-0 size-1.5 mr-1.5 rounded-full" style={kindDotColor(value)} />
188202
})()}
189-
</Dynamic>
203+
</ContextMenu.Trigger>
204+
<ContextMenu.Portal>
205+
<ContextMenu.Content>
206+
<ContextMenu.Item onSelect={handleCopyAbsolutePath}>
207+
<ContextMenu.ItemLabel>{language.t("filetree.contextMenu.copyPath")}</ContextMenu.ItemLabel>
208+
</ContextMenu.Item>
209+
<ContextMenu.Item onSelect={handleCopyRelativePath}>
210+
<ContextMenu.ItemLabel>{language.t("filetree.contextMenu.copyRelativePath")}</ContextMenu.ItemLabel>
211+
</ContextMenu.Item>
212+
</ContextMenu.Content>
213+
</ContextMenu.Portal>
214+
</ContextMenu>
190215
)
191216
}
192217

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)