Skip to content

Commit e41cc70

Browse files
authored
[UI] Fix bugs for file tree (#662)
1 parent 4e08143 commit e41cc70

8 files changed

Lines changed: 133 additions & 78 deletions

File tree

web/client/src/context/editor.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export interface Dialect {
1111

1212
interface EditorStore {
1313
storedTabsIds: ID[]
14-
tabs: Map<ID, EditorTab>
14+
tabs: Map<ModelFile, EditorTab>
1515
tab: EditorTab
1616
engine: Worker
1717
dialects: Dialect[]
@@ -20,7 +20,7 @@ interface EditorStore {
2020
previewConsole?: string
2121
selectTab: (tab: EditorTab) => void
2222
addTab: (tab: EditorTab) => void
23-
closeTab: (id: ID) => void
23+
closeTab: (file: ModelFile) => void
2424
createTab: (file?: ModelFile) => EditorTab
2525
setDialects: (dialects: Dialect[]) => void
2626
refreshTab: () => void
@@ -48,7 +48,7 @@ const [getStoredTabs, setStoredTabs] = useLocalStorage<{ ids: ID[] }>('tabs')
4848

4949
const initialFile = createLocalFile()
5050
const initialTab: EditorTab = createTab(initialFile, true)
51-
const initialTabs = new Map([[initialFile.id, initialTab]])
51+
const initialTabs = new Map([[initialFile, initialTab]])
5252

5353
export const useStoreEditor = create<EditorStore>((set, get) => ({
5454
storedTabsIds: getStoredTabsIds(),
@@ -70,7 +70,7 @@ export const useStoreEditor = create<EditorStore>((set, get) => ({
7070
get().addTab(tab)
7171
},
7272
addTab(tab) {
73-
const tabs = new Map([...get().tabs, [tab.file.id, tab]])
73+
const tabs = new Map([...get().tabs, [tab.file, tab]])
7474

7575
setStoredTabs({
7676
ids: Array.from(tabs.values())
@@ -83,17 +83,17 @@ export const useStoreEditor = create<EditorStore>((set, get) => ({
8383
tabs,
8484
}))
8585
},
86-
closeTab(id) {
86+
closeTab(file) {
8787
const s = get()
8888

89-
if (isTrue(s.tabs.get(id)?.isInitial)) return
89+
if (isTrue(s.tabs.get(file)?.isInitial)) return
9090

9191
const tabs = Array.from(get().tabs.values())
92-
const indexAt = tabs.findIndex(tab => tab.file.id === id)
92+
const indexAt = tabs.findIndex(tab => tab.file === file)
9393

94-
s.tabs.delete(id)
94+
s.tabs.delete(file)
9595

96-
if (id === s.tab.file.id) {
96+
if (file.id === s.tab.file.id) {
9797
s.selectTab(tabs.at(indexAt - 1) as EditorTab)
9898
}
9999

web/client/src/context/fileTree.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ interface FileTreeStore {
88
selectedFile?: ModelFile
99
selectFile: (selectedFile: ModelFile) => void
1010
setFiles: (files: ModelFile[]) => void
11-
refreshProject: () => void
1211
setProject: (project?: Directory) => void
12+
refreshProject: () => void
1313
}
1414

1515
export const useStoreFileTree = create<FileTreeStore>((set, get) => ({

web/client/src/library/components/editor/Editor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default function Editor(): JSX.Element {
7777
}, [tab])
7878

7979
useEffect(() => {
80-
if (selectedFile == null) return
80+
if (selectedFile == null || tab.file === selectedFile) return
8181

8282
selectTab(createTab(selectedFile))
8383
}, [selectedFile])

web/client/src/library/components/editor/EditorCode.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,13 @@ export default function CodeEditor(): JSX.Element {
104104
key: 'Mod-Alt-]',
105105
preventDefault: true,
106106
run() {
107-
closeTab(tab.file.id)
107+
closeTab(tab.file)
108108

109109
return true
110110
},
111111
},
112112
],
113-
[closeTab, selectTab, createTab, tab.file.id],
113+
[closeTab, selectTab, createTab, tab.file],
114114
)
115115

116116
useEffect(() => {

web/client/src/library/components/editor/EditorTabs.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ function Tab({
8181
const closeTab = useStoreEditor(s => s.closeTab)
8282

8383
function closeEditorTab(tab: EditorTab): void {
84-
closeTab(tab.file.id)
84+
closeTab(tab.file)
8585
}
8686

8787
return (

web/client/src/library/components/fileTree/Directory.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export default function Directory({
9797

9898
setIsLoading(true)
9999

100-
const extension = '.py'
100+
const extension = directory.isModels ? '.sql' : '.py'
101101
const name = toUniqueName('new_file', extension)
102102

103103
writeFileApiFilesPathPost(`${directory.path}/${name}`, { content: '' })
@@ -109,7 +109,6 @@ export default function Directory({
109109
}
110110

111111
directory.addFile(new ModelFile(created, directory))
112-
113112
directory.open()
114113
})
115114
.catch(error => {
@@ -141,7 +140,7 @@ export default function Directory({
141140
const files = getAllFilesInDirectory(directory)
142141

143142
files.forEach(file => {
144-
closeTab(file.id)
143+
closeTab(file)
145144
})
146145
}
147146

web/client/src/library/components/fileTree/File.tsx

Lines changed: 113 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, type MouseEvent } from 'react'
1+
import { useState, type MouseEvent, useEffect } from 'react'
22
import {
33
DocumentIcon,
44
XCircleIcon,
@@ -19,8 +19,6 @@ interface PropsFile extends WithConfirmation {
1919
file: ModelFile
2020
}
2121

22-
const CSS_ICON_SIZE = 'w-4 h-4'
23-
2422
export default function File({
2523
file,
2624
setConfirmation,
@@ -29,12 +27,17 @@ export default function File({
2927
const tabs = useStoreEditor(s => s.tabs)
3028
const closeTab = useStoreEditor(s => s.closeTab)
3129

30+
const selectedFile = useStoreFileTree(s => s.selectedFile)
3231
const selectFile = useStoreFileTree(s => s.selectFile)
3332
const refreshProject = useStoreFileTree(s => s.refreshProject)
3433

3534
const [isLoading, setIsLoading] = useState(false)
3635
const [newName, setNewName] = useState<string>()
3736

37+
useEffect(() => {
38+
selectFile(tab.file)
39+
}, [tab])
40+
3841
function remove(): void {
3942
if (isLoading) return
4043

@@ -43,7 +46,7 @@ export default function File({
4346
deleteFileApiFilesPathDelete(file.path)
4447
.then(response => {
4548
if ((response as unknown as { ok: boolean }).ok) {
46-
closeTab(file.id)
49+
closeTab(file)
4750

4851
file.parent?.removeFile(file)
4952

@@ -107,14 +110,14 @@ export default function File({
107110
<span
108111
className={clsx(
109112
'whitespace-nowrap group/file pl-3 pr-2 py-[0.125rem] flex rounded-md',
110-
'hover:bg-neutral-100 dark:hover:bg-dark-lighter ',
113+
'hover:bg-neutral-100 dark:hover:bg-dark-lighter',
111114
file.is_supported &&
112115
'group hover:bg-neutral-100 dark:hover:bg-dark-lighter',
113116
isFalse(isStringEmptyOrNil(newName)) && 'bg-primary-800',
114-
tabs.has(file.id)
117+
tabs.has(file)
115118
? 'text-brand-500'
116119
: 'text-neutral-500 dark:text-neutral-100',
117-
file === tab.file && 'bg-neutral-100 dark:bg-dark-lighter',
120+
file === selectedFile && 'bg-neutral-100 dark:bg-dark-lighter',
118121
)}
119122
>
120123
<span
@@ -125,71 +128,120 @@ export default function File({
125128
<div className="flex items-center">
126129
<DocumentIcon
127130
className={clsx(
128-
`inline-block ${CSS_ICON_SIZE} mr-2`,
129-
file === tab.file
131+
`inline-block w-4 h-4 mr-2`,
132+
file === selectedFile
130133
? 'text-brand-500'
131134
: 'text-neutral-500 dark:text-neutral-100',
132135
)}
133136
/>
134137
</div>
135138
{isStringEmptyOrNil(newName) ? (
136139
<>
137-
<span
138-
onClick={(e: MouseEvent) => {
139-
e.stopPropagation()
140-
141-
file.is_supported && file !== tab.file && selectFile(file)
142-
}}
143-
onDoubleClick={(e: MouseEvent) => {
144-
e.stopPropagation()
145-
146-
setNewName(file.name)
147-
}}
148-
className={clsx(
149-
'w-full overflow-hidden overflow-ellipsis cursor-default',
150-
!file.is_supported && 'opacity-50 cursor-not-allowed',
151-
)}
152-
>
153-
{file.name}
154-
</span>
155-
<span
156-
className="flex items-center invisible group-hover/file:visible min-w-8"
157-
onClick={(e: MouseEvent) => {
158-
e.stopPropagation()
159-
160-
removeWithConfirmation()
161-
}}
162-
>
163-
<XCircleIcon
164-
className={`inline-block ${CSS_ICON_SIZE} ml-2 text-danger-500 cursor-pointer`}
165-
/>
166-
</span>
140+
<FileName
141+
file={file}
142+
setNewName={setNewName}
143+
/>
144+
<FileActions removeWithConfirmation={removeWithConfirmation} />
167145
</>
168146
) : (
169-
<div className="w-full flex items-center">
170-
<input
171-
type="text"
172-
className="w-full overflow-hidden overflow-ellipsis bg-primary-900 text-primary-100"
173-
value={newName === '' ? file.name : newName}
174-
onInput={(e: any) => {
175-
e.stopPropagation()
176-
177-
setNewName(e.target.value)
178-
}}
179-
/>
180-
<div className="flex">
181-
<CheckCircleIcon
182-
className={`inline-block ${CSS_ICON_SIZE} ml-2 text-success-500 cursor-pointer`}
183-
onClick={(e: MouseEvent) => {
184-
e.stopPropagation()
185-
186-
rename()
187-
}}
188-
/>
189-
</div>
190-
</div>
147+
<FileRename
148+
file={file}
149+
newName={newName}
150+
setNewName={setNewName}
151+
rename={rename}
152+
/>
191153
)}
192154
</span>
193155
</span>
194156
)
195157
}
158+
159+
function FileName({
160+
file,
161+
setNewName,
162+
}: {
163+
file: ModelFile
164+
setNewName: (name: string) => void
165+
}): JSX.Element {
166+
const selectedFile = useStoreFileTree(s => s.selectedFile)
167+
const selectFile = useStoreFileTree(s => s.selectFile)
168+
169+
return (
170+
<span
171+
onClick={(e: MouseEvent) => {
172+
e.stopPropagation()
173+
174+
file.is_supported && file !== selectedFile && selectFile(file)
175+
}}
176+
onDoubleClick={(e: MouseEvent) => {
177+
e.stopPropagation()
178+
179+
setNewName(file.name)
180+
}}
181+
className={clsx(
182+
'w-full overflow-hidden overflow-ellipsis cursor-default',
183+
!file.is_supported && 'opacity-50 cursor-not-allowed',
184+
)}
185+
>
186+
{file.name}
187+
</span>
188+
)
189+
}
190+
191+
function FileRename({
192+
file,
193+
newName,
194+
setNewName,
195+
rename,
196+
}: {
197+
file: ModelFile
198+
newName?: string
199+
setNewName: (name: string) => void
200+
rename: () => void
201+
}): JSX.Element {
202+
return (
203+
<div className="w-full flex items-center">
204+
<input
205+
type="text"
206+
className="w-full overflow-hidden overflow-ellipsis bg-primary-900 text-primary-100"
207+
value={newName === '' ? file.name : newName}
208+
onInput={(e: any) => {
209+
e.stopPropagation()
210+
211+
setNewName(e.target.value)
212+
}}
213+
/>
214+
<div className="flex">
215+
<CheckCircleIcon
216+
className={`inline-block w-4 h-4 ml-2 text-success-500 cursor-pointer`}
217+
onClick={(e: MouseEvent) => {
218+
e.stopPropagation()
219+
220+
rename()
221+
}}
222+
/>
223+
</div>
224+
</div>
225+
)
226+
}
227+
228+
function FileActions({
229+
removeWithConfirmation,
230+
}: {
231+
removeWithConfirmation: () => void
232+
}): JSX.Element {
233+
return (
234+
<span
235+
className="flex items-center invisible group-hover/file:visible min-w-8"
236+
onClick={(e: MouseEvent) => {
237+
e.stopPropagation()
238+
239+
removeWithConfirmation()
240+
}}
241+
>
242+
<XCircleIcon
243+
className={`inline-block w-4 h-4 ml-2 text-danger-500 cursor-pointer`}
244+
/>
245+
</span>
246+
)
247+
}

web/client/src/models/directory.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ export class ModelDirectory extends ModelArtifact<InitialDirectory> {
9393
return !this.isOpen && this.allDirectories.every(d => d.isCollapsed)
9494
}
9595

96+
get isModels(): boolean {
97+
return this.path.startsWith('models')
98+
}
99+
96100
open(): void {
97101
this._isOpen = true
98102

0 commit comments

Comments
 (0)