Skip to content

Commit eb41c3a

Browse files
feat: add file renaming (#63)
Co-authored-by: Evan You <[email protected]>
1 parent 7049ae0 commit eb41c3a

File tree

2 files changed

+87
-32
lines changed

2 files changed

+87
-32
lines changed

src/editor/FileSelector.vue

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { computed, inject, ref, VNode, Ref } from 'vue'
44
55
const store = inject('store') as Store
66
7-
const pending = ref(false)
7+
const pending = ref<boolean | string>(false)
88
const pendingFilename = ref('Comp.vue')
99
const importMapFile = 'import-map.json'
1010
const showImportMap = inject('import-map') as Ref<boolean>
@@ -36,17 +36,18 @@ function startAddFile() {
3636
pending.value = true
3737
}
3838
39-
function cancelAddFile() {
39+
function cancelNameFile() {
4040
pending.value = false
4141
}
4242
4343
function focus({ el }: VNode) {
4444
;(el as HTMLInputElement).focus()
4545
}
4646
47-
function doneAddFile() {
47+
function doneNameFile() {
4848
if (!pending.value) return
4949
const filename = pendingFilename.value
50+
const oldFilename = pending.value === true ? '' : pending.value
5051
5152
if (!/\.(vue|js|ts|css|json)$/.test(filename)) {
5253
store.state.errors = [
@@ -55,14 +56,28 @@ function doneAddFile() {
5556
return
5657
}
5758
58-
if (filename in store.state.files) {
59+
if (filename !== oldFilename && filename in store.state.files) {
5960
store.state.errors = [`File "${filename}" already exists.`]
6061
return
6162
}
6263
6364
store.state.errors = []
64-
cancelAddFile()
65-
store.addFile(filename)
65+
cancelNameFile()
66+
67+
if (filename === oldFilename) {
68+
return
69+
}
70+
71+
if (oldFilename) {
72+
store.renameFile(oldFilename, filename)
73+
} else {
74+
store.addFile(filename)
75+
}
76+
}
77+
78+
function editFileName(file: string) {
79+
pendingFilename.value = file
80+
pending.value = file
6681
}
6782
6883
const fileSel = ref(null)
@@ -85,32 +100,35 @@ function horizontalScroll(e: WheelEvent) {
85100
@wheel="horizontalScroll"
86101
ref="fileSel"
87102
>
88-
<div
89-
v-for="(file, i) in files"
90-
class="file"
91-
:class="{ active: store.state.activeFile.filename === file }"
92-
@click="store.setActive(file)"
93-
>
94-
<span class="label">{{
95-
file === importMapFile ? 'Import Map' : file
96-
}}</span>
97-
<span v-if="i > 0" class="remove" @click.stop="store.deleteFile(file)">
98-
<svg class="icon" width="12" height="12" viewBox="0 0 24 24">
99-
<line stroke="#999" x1="18" y1="6" x2="6" y2="18"></line>
100-
<line stroke="#999" x1="6" y1="6" x2="18" y2="18"></line>
101-
</svg>
102-
</span>
103-
</div>
104-
<div v-if="pending" class="file pending">
105-
<input
106-
v-model="pendingFilename"
107-
spellcheck="false"
108-
@blur="doneAddFile"
109-
@keyup.enter="doneAddFile"
110-
@keyup.esc="cancelAddFile"
111-
@vue:mounted="focus"
112-
/>
113-
</div>
103+
<template v-for="(file, i) in files">
104+
<div
105+
v-if="pending !== file"
106+
class="file"
107+
:class="{ active: store.state.activeFile.filename === file }"
108+
@click="store.setActive(file)"
109+
@dblclick="i > 0 && editFileName(file)"
110+
>
111+
<span class="label">{{
112+
file === importMapFile ? 'Import Map' : file
113+
}}</span>
114+
<span v-if="i > 0" class="remove" @click.stop="store.deleteFile(file)">
115+
<svg class="icon" width="12" height="12" viewBox="0 0 24 24">
116+
<line stroke="#999" x1="18" y1="6" x2="6" y2="18"></line>
117+
<line stroke="#999" x1="6" y1="6" x2="18" y2="18"></line>
118+
</svg>
119+
</span>
120+
</div>
121+
<div v-if="(pending === true && i === files.length - 1) || (pending === file)" class="file pending">
122+
<input
123+
v-model="pendingFilename"
124+
spellcheck="false"
125+
@blur="doneNameFile"
126+
@keyup.enter="doneNameFile"
127+
@keyup.esc="cancelNameFile"
128+
@vue:mounted="focus"
129+
/>
130+
</div>
131+
</template>
114132
<button class="add" @click="startAddFile">+</button>
115133

116134
<div v-if="showImportMap" class="import-map-wrapper">

src/store.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export interface Store {
6767
setActive: (filename: string) => void
6868
addFile: (filename: string | File) => void
6969
deleteFile: (filename: string) => void
70+
renameFile: (oldFilename: string, newFilename: string) => void
7071
getImportMap: () => any
7172
initialShowOutput: boolean
7273
initialOutputMode: OutputModes
@@ -167,6 +168,42 @@ export class ReplStore implements Store {
167168
}
168169
}
169170

171+
renameFile(oldFilename: string, newFilename: string) {
172+
const { files } = this.state
173+
const file = files[oldFilename]
174+
175+
if (!file) {
176+
this.state.errors = [`Could not rename "${oldFilename}", file not found`]
177+
return
178+
}
179+
180+
if (!newFilename || oldFilename === newFilename) {
181+
this.state.errors = [`Cannot rename "${oldFilename}" to "${newFilename}"`]
182+
return
183+
}
184+
185+
file.filename = newFilename
186+
187+
const newFiles: Record<string, File> = {}
188+
189+
// Preserve iteration order for files
190+
for (const name in files) {
191+
if (name === oldFilename) {
192+
newFiles[newFilename] = file
193+
} else {
194+
newFiles[name] = files[name]
195+
}
196+
}
197+
198+
this.state.files = newFiles
199+
200+
if (this.state.mainFile === oldFilename) {
201+
this.state.mainFile = newFilename
202+
}
203+
204+
compileFile(this, file)
205+
}
206+
170207
serialize() {
171208
const files = this.getFiles()
172209
const importMap = files['import-map.json']

0 commit comments

Comments
 (0)