Skip to content
/ web Public

Commit f18767a

Browse files
authored
feature: file manager (#203)
1 parent e1c1039 commit f18767a

40 files changed

+2716
-322
lines changed

.claude/settings.local.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
"Read(*)",
88
"Search(*)",
99
"Edit(*)",
10-
"Write(*)"
10+
"Write(*)",
11+
"Bash(npx vue-tsc:*)",
12+
"Bash(timeout 15 npm run dev:*)"
1113
]
1214
}
1315
}

components/SpotlightPlayerSearch.vue

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,9 @@ const closeOnOverlayClick = (event: MouseEvent) => {
153153
class="w-full max-w-2xl bg-background rounded-xl shadow-2xl border border-border/50 overflow-hidden"
154154
>
155155
<!-- Search Input -->
156-
<div class="flex items-center gap-3 px-4 py-4 border-b border-border/50">
156+
<div
157+
class="flex items-center gap-3 px-4 py-4 border-b border-border/50"
158+
>
157159
<MagnifyingGlassIcon
158160
class="size-5 text-muted-foreground flex-shrink-0"
159161
/>
@@ -167,7 +169,10 @@ const closeOnOverlayClick = (event: MouseEvent) => {
167169

168170
<!-- Results -->
169171
<div class="max-h-[400px] overflow-y-auto">
170-
<div v-if="loading" class="px-4 py-8 text-center text-muted-foreground text-sm">
172+
<div
173+
v-if="loading"
174+
class="px-4 py-8 text-center text-muted-foreground text-sm"
175+
>
171176
Searching...
172177
</div>
173178

@@ -178,10 +183,7 @@ const closeOnOverlayClick = (event: MouseEvent) => {
178183
No players found
179184
</div>
180185

181-
<div
182-
v-else-if="!query"
183-
class="px-4 py-8 text-center"
184-
>
186+
<div v-else-if="!query" class="px-4 py-8 text-center">
185187
<div class="text-muted-foreground text-sm">
186188
Start typing to search players
187189
</div>
@@ -193,9 +195,7 @@ const closeOnOverlayClick = (event: MouseEvent) => {
193195
:key="`player-${player.steam_id}`"
194196
:class="[
195197
'px-4 py-3 cursor-pointer flex items-center gap-3 transition-colors',
196-
selectedIndex === index
197-
? 'bg-accent'
198-
: 'hover:bg-accent/50',
198+
selectedIndex === index ? 'bg-accent' : 'hover:bg-accent/50',
199199
]"
200200
@click="selectPlayer(player)"
201201
@mouseenter="selectedIndex = index"
@@ -204,7 +204,6 @@ const closeOnOverlayClick = (event: MouseEvent) => {
204204
</div>
205205
</div>
206206
</div>
207-
208207
</div>
209208
</Transition>
210209
</div>

components/file-manager/CreateDirectoryDialog.vue

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@
2626
</div>
2727

2828
<DialogFooter>
29-
<Button variant="outline" @click="handleCancel">
30-
Cancel
31-
</Button>
29+
<Button variant="outline" @click="handleCancel"> Cancel </Button>
3230
<Button @click="handleCreate" :disabled="!dirName || store.isLoading">
3331
Create
3432
</Button>
@@ -71,7 +69,7 @@ watch(
7169
dirName.value = "";
7270
store.clearError();
7371
}
74-
}
72+
},
7573
);
7674
7775
async function handleCreate() {
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<template>
2+
<Dialog :open="open" @update:open="$emit('update:open', $event)">
3+
<DialogContent>
4+
<DialogHeader>
5+
<DialogTitle>Create New File</DialogTitle>
6+
<DialogDescription> Enter a name for the new file </DialogDescription>
7+
</DialogHeader>
8+
9+
<div class="space-y-4">
10+
<div class="space-y-2">
11+
<Label for="file-name">File Name</Label>
12+
<Input
13+
id="file-name"
14+
v-model="fileName"
15+
placeholder="my-file.txt"
16+
@keyup.enter="handleCreate"
17+
/>
18+
</div>
19+
20+
<Alert v-if="localError" variant="destructive">
21+
<AlertTriangle class="w-4 h-4" />
22+
<AlertDescription>{{ localError }}</AlertDescription>
23+
</Alert>
24+
</div>
25+
26+
<DialogFooter>
27+
<Button variant="outline" @click="handleCancel"> Cancel </Button>
28+
<Button @click="handleCreate" :disabled="!fileName || isCreating">
29+
{{ isCreating ? "Creating..." : "Create" }}
30+
</Button>
31+
</DialogFooter>
32+
</DialogContent>
33+
</Dialog>
34+
</template>
35+
36+
<script setup lang="ts">
37+
import { ref, watch } from "vue";
38+
import { Button } from "@/components/ui/button";
39+
import { Input } from "@/components/ui/input";
40+
import { Label } from "@/components/ui/label";
41+
import { Alert, AlertDescription } from "@/components/ui/alert";
42+
import { AlertTriangle } from "lucide-vue-next";
43+
import {
44+
Dialog,
45+
DialogContent,
46+
DialogDescription,
47+
DialogFooter,
48+
DialogHeader,
49+
DialogTitle,
50+
} from "@/components/ui/dialog";
51+
52+
const props = defineProps<{
53+
open: boolean;
54+
parentPath?: string;
55+
}>();
56+
57+
const emit = defineEmits<{
58+
"update:open": [value: boolean];
59+
}>();
60+
61+
const store = useFileManagerStore();
62+
const fileName = ref("");
63+
const localError = ref<string | null>(null);
64+
const isCreating = ref(false);
65+
66+
watch(
67+
() => props.open,
68+
(isOpen) => {
69+
if (!isOpen) {
70+
fileName.value = "";
71+
localError.value = null;
72+
}
73+
},
74+
);
75+
76+
async function handleCreate() {
77+
if (!fileName.value) return;
78+
79+
isCreating.value = true;
80+
localError.value = null;
81+
82+
try {
83+
// Build full path using parentPath if provided
84+
const filePath = props.parentPath
85+
? `${props.parentPath}/${fileName.value}`
86+
: fileName.value;
87+
88+
const success = await store.saveFile(filePath, "");
89+
if (success) {
90+
emit("update:open", false);
91+
} else {
92+
localError.value = store.error || "Failed to create file";
93+
}
94+
} catch (error: any) {
95+
localError.value = error.message || "Failed to create file";
96+
} finally {
97+
isCreating.value = false;
98+
}
99+
}
100+
101+
function handleCancel() {
102+
emit("update:open", false);
103+
}
104+
</script>

components/file-manager/DeleteConfirmDialog.vue

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22
<Dialog :open="open" @update:open="$emit('update:open', $event)">
33
<DialogContent>
44
<DialogHeader>
5-
<DialogTitle>Delete {{ item?.isDirectory ? "Directory" : "File" }}</DialogTitle>
5+
<DialogTitle
6+
>Delete {{ item?.isDirectory ? "Directory" : "File" }}</DialogTitle
7+
>
68
<DialogDescription>
79
Are you sure you want to delete "{{ item?.name }}"?
8-
{{ item?.isDirectory ? "All contents will be permanently deleted." : "This action cannot be undone." }}
10+
{{
11+
item?.isDirectory
12+
? "All contents will be permanently deleted."
13+
: "This action cannot be undone."
14+
}}
915
</DialogDescription>
1016
</DialogHeader>
1117

@@ -15,9 +21,7 @@
1521
</Alert>
1622

1723
<DialogFooter>
18-
<Button variant="outline" @click="handleCancel">
19-
Cancel
20-
</Button>
24+
<Button variant="outline" @click="handleCancel"> Cancel </Button>
2125
<Button
2226
variant="destructive"
2327
@click="handleDelete"
@@ -62,7 +66,7 @@ watch(
6266
if (!isOpen) {
6367
store.clearError();
6468
}
65-
}
69+
},
6670
);
6771
6872
async function handleDelete() {

components/file-manager/FileActionsMenu.vue

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,9 @@
2121
</DropdownMenuContent>
2222
</DropdownMenu>
2323

24-
<FileEditorDialog
25-
v-model:open="editorDialogOpen"
26-
:file-path="item.path"
27-
/>
28-
<RenameDialog
29-
v-model:open="renameDialogOpen"
30-
:item="item"
31-
/>
32-
<DeleteConfirmDialog
33-
v-model:open="deleteDialogOpen"
34-
:item="item"
35-
/>
24+
<FileEditorDialog v-model:open="editorDialogOpen" :file-path="item.path" />
25+
<RenameDialog v-model:open="renameDialogOpen" :item="item" />
26+
<DeleteConfirmDialog v-model:open="deleteDialogOpen" :item="item" />
3627
</template>
3728

3829
<script setup lang="ts">

0 commit comments

Comments
 (0)