Skip to content

Commit 73c6a2e

Browse files
authored
[UI] Split editor component (#635)
1 parent de30f53 commit 73c6a2e

File tree

26 files changed

+1580
-1165
lines changed

26 files changed

+1580
-1165
lines changed

web/client/src/api/index.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,13 @@ export function useApiDag(): UseQueryResult<DagApiCommandsDagGet200> {
3535
})
3636
}
3737

38-
export function useApiFileByPath(path?: string): UseQueryResult<File> {
38+
export function useApiFileByPath(path: string): UseQueryResult<File> {
3939
return useQuery({
4040
queryKey: [`/api/files`, path],
4141
queryFn: async ({ signal }) =>
42-
path != null &&
43-
path !== '' &&
44-
(await getFileApiFilesPathGet(path, { signal })),
45-
enabled: false,
42+
await getFileApiFilesPathGet(path, { signal }),
4643
cacheTime: 0,
44+
enabled: false,
4745
})
4846
}
4947

web/client/src/context/context.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { create } from 'zustand'
22
import {
3+
type Model,
4+
type ModelsModels,
35
type ContextEnvironmentEnd,
46
type ContextEnvironmentStart,
57
type Environment,
@@ -16,6 +18,8 @@ interface ContextStore {
1618
environments: Set<ModelEnvironment>
1719
initialStartDate?: ContextEnvironmentStart
1820
initialEndDate?: ContextEnvironmentEnd
21+
models: Map<string, Model>
22+
setModels: (models?: ModelsModels) => void
1923
isExistingEnvironment: (
2024
environment: ModelEnvironment | EnvironmentName,
2125
) => boolean
@@ -41,21 +45,29 @@ export const useStoreContext = create<ContextStore>((set, get) => ({
4145
environments,
4246
initialStartDate: undefined,
4347
initialEndDate: undefined,
44-
getNextEnvironment(): ModelEnvironment {
48+
models: new Map(),
49+
setModels(models = {}) {
50+
set(() => {
51+
return {
52+
models: Object.values(models).reduce((acc, model) => {
53+
acc.set(model.name, model)
54+
acc.set(model.path, model)
55+
56+
return acc
57+
}, new Map()),
58+
}
59+
})
60+
},
61+
getNextEnvironment() {
4562
return get().environments.values().next().value
4663
},
47-
setInitialDates(
48-
initialStartDate?: ContextEnvironmentStart,
49-
initialEndDate?: ContextEnvironmentEnd,
50-
): void {
64+
setInitialDates(initialStartDate, initialEndDate) {
5165
set({
5266
initialStartDate,
5367
initialEndDate,
5468
})
5569
},
56-
isExistingEnvironment(
57-
environment: ModelEnvironment | EnvironmentName,
58-
): boolean {
70+
isExistingEnvironment(environment) {
5971
const s = get()
6072

6173
if ((environment as ModelEnvironment).isModel)
@@ -71,7 +83,7 @@ export const useStoreContext = create<ContextStore>((set, get) => ({
7183

7284
return hasEnvironment
7385
},
74-
setEnvironment(environment: ModelEnvironment): void {
86+
setEnvironment(environment) {
7587
set(() => {
7688
ModelEnvironment.save({
7789
environment,
@@ -82,10 +94,7 @@ export const useStoreContext = create<ContextStore>((set, get) => ({
8294
}
8395
})
8496
},
85-
addLocalEnvironment(
86-
localEnvironment: EnvironmentName,
87-
created_from?: EnvironmentName,
88-
): void {
97+
addLocalEnvironment(localEnvironment, created_from) {
8998
set(s => {
9099
if (isStringEmptyOrNil(localEnvironment)) return s
91100

@@ -110,7 +119,7 @@ export const useStoreContext = create<ContextStore>((set, get) => ({
110119
}
111120
})
112121
},
113-
removeLocalEnvironment(localEnvironment: ModelEnvironment) {
122+
removeLocalEnvironment(localEnvironment) {
114123
set(s => {
115124
s.environments.delete(localEnvironment)
116125

@@ -123,7 +132,7 @@ export const useStoreContext = create<ContextStore>((set, get) => ({
123132
}
124133
})
125134
},
126-
addSyncronizedEnvironments(envs: Environment[] = []) {
135+
addSyncronizedEnvironments(envs = []) {
127136
set(s => {
128137
const environments = Array.from(s.environments)
129138

web/client/src/context/editor.ts

Lines changed: 132 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,142 @@
11
import { create } from 'zustand'
2+
import useLocalStorage from '~/hooks/useLocalStorage'
3+
import { ModelFile } from '~/models'
4+
import { isTrue } from '~/utils'
5+
import { sqlglotWorker } from '~/workers'
6+
7+
export interface Dialect {
8+
dialect_title: string
9+
dialect_name: string
10+
}
211

312
interface EditorStore {
4-
tabQueryPreviewContent?: string
5-
tabTableContent?: any[]
6-
tabTerminalContent?: string
7-
setTabQueryPreviewContent: (tabQueryPreviewContent?: string) => void
8-
setTabTableContent: (tabTableContent?: any[]) => void
9-
setTabTerminalContent: (tabTerminalContent?: string) => void
13+
storedTabsIds: ID[]
14+
tabs: Map<ID, EditorTab>
15+
tab: EditorTab
16+
engine: Worker
17+
dialects: Dialect[]
18+
previewQuery?: string
19+
previewTable?: any[]
20+
previewConsole?: string
21+
selectTab: (tab: EditorTab) => void
22+
addTab: (tab: EditorTab) => void
23+
closeTab: (id: ID) => void
24+
createTab: (file?: ModelFile) => EditorTab
25+
setDialects: (dialects: Dialect[]) => void
26+
refreshTab: () => void
27+
setPreviewQuery: (previewQuery?: string) => void
28+
setPreviewTable: (previewTable?: any[]) => void
29+
setPreviewConsole: (previewConsole?: string) => void
30+
}
31+
32+
interface EditorPreview<TTable = any> {
33+
queryPreview?: string
34+
table?: TTable[]
35+
terminal?: string
1036
}
1137

38+
export interface EditorTab {
39+
file: ModelFile
40+
isValid: boolean
41+
isSaved: boolean
42+
isInitial: boolean
43+
dialect?: string
44+
preview?: EditorPreview
45+
}
46+
47+
const [getStoredTabs, setStoredTabs] = useLocalStorage<{ ids: ID[] }>('tabs')
48+
49+
const initialFile = createLocalFile()
50+
const initialTab: EditorTab = createTab(initialFile, true)
51+
const initialTabs = new Map([[initialFile.id, initialTab]])
52+
1253
export const useStoreEditor = create<EditorStore>((set, get) => ({
13-
tabQueryPreviewContent: undefined,
14-
tabTableContent: undefined,
15-
tabTerminalContent: undefined,
16-
setTabQueryPreviewContent: (tabQueryPreviewContent?: string) => {
17-
set(() => ({ tabQueryPreviewContent }))
54+
storedTabsIds: getStoredTabsIds(),
55+
tab: initialTab,
56+
tabs: initialTabs,
57+
engine: sqlglotWorker,
58+
dialects: [],
59+
refreshTab() {
60+
get().selectTab({ ...get().tab })
1861
},
19-
setTabTableContent: (tabTableContent?: any[]) => {
20-
set(() => ({ tabTableContent }))
62+
setDialects(dialects) {
63+
set(() => ({
64+
dialects,
65+
}))
2166
},
22-
setTabTerminalContent: (tabTerminalContent?: string) => {
23-
set(() => ({ tabTerminalContent }))
67+
selectTab(tab) {
68+
set(() => ({ tab }))
69+
70+
get().addTab(tab)
71+
},
72+
addTab(tab) {
73+
const tabs = new Map([...get().tabs, [tab.file.id, tab]])
74+
75+
setStoredTabs({
76+
ids: Array.from(tabs.values())
77+
.filter(tab => tab.file.isRemote)
78+
.map(tab => tab.file.id),
79+
})
80+
81+
set(() => ({
82+
tabsIds: getStoredTabsIds(),
83+
tabs,
84+
}))
85+
},
86+
closeTab(id) {
87+
const s = get()
88+
89+
if (isTrue(s.tabs.get(id)?.isInitial)) return
90+
91+
const tabs = Array.from(get().tabs.values())
92+
const indexAt = tabs.findIndex(tab => tab.file.id === id)
93+
94+
s.tabs.delete(id)
95+
96+
if (id === s.tab.file.id) {
97+
s.selectTab(tabs.at(indexAt - 1) as EditorTab)
98+
}
99+
100+
set(() => ({
101+
tabs: new Map(s.tabs),
102+
}))
103+
},
104+
createTab,
105+
previewQuery: undefined,
106+
previewTable: undefined,
107+
previewConsole: undefined,
108+
setPreviewQuery(previewQuery) {
109+
set(() => ({ previewQuery }))
110+
},
111+
setPreviewTable(previewTable) {
112+
set(() => ({ previewTable }))
113+
},
114+
setPreviewConsole(previewConsole) {
115+
set(() => ({ previewConsole }))
24116
},
25117
}))
118+
119+
function createTab(
120+
file: ModelFile = createLocalFile(),
121+
isInitial = false,
122+
): EditorTab {
123+
return {
124+
file,
125+
isInitial,
126+
isValid: true,
127+
isSaved: true,
128+
}
129+
}
130+
131+
function createLocalFile(): ModelFile {
132+
return new ModelFile({
133+
name: '',
134+
path: '',
135+
content:
136+
'-- Create arbitrary SQL queries\n-- and execute them against different environments\n\n',
137+
})
138+
}
139+
140+
function getStoredTabsIds(): ID[] {
141+
return getStoredTabs()?.ids ?? []
142+
}

web/client/src/context/fileTree.ts

Lines changed: 22 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,37 @@
11
import { create } from 'zustand'
2-
import useLocalStorage from '~/hooks/useLocalStorage'
3-
import { isArrayNotEmpty, isFalse } from '~/utils'
4-
import { ModelFile } from '../models'
2+
import { ModelDirectory, type ModelFile } from '../models'
3+
import { type Directory } from '~/api/client'
54

65
interface FileTreeStore {
6+
project?: ModelDirectory
77
files: Map<ID, ModelFile>
8-
activeFile: ModelFile
9-
openedFiles: Set<ModelFile>
10-
setOpenedFiles: (files: Set<ModelFile>) => void
11-
selectFile: (file: ModelFile) => void
12-
getNextOpenedFile: () => ModelFile
8+
selectedFile?: ModelFile
9+
selectFile: (selectedFile: ModelFile) => void
1310
setFiles: (files: ModelFile[]) => void
11+
refreshProject: () => void
12+
setProject: (project?: Directory) => void
1413
}
1514

16-
const initialFile = new ModelFile()
17-
18-
const [getOpenedFilesIds, setOpenedFilesIds] = useLocalStorage<{ ids: ID[] }>(
19-
'openedFiles',
20-
)
21-
2215
export const useStoreFileTree = create<FileTreeStore>((set, get) => ({
16+
project: undefined,
2317
files: new Map(),
24-
activeFile: initialFile,
25-
openedFiles: new Set([initialFile]),
26-
setFiles(files: ModelFile[]) {
27-
set(() => {
28-
const openedFilesIds = getOpenedFilesIds()?.ids ?? []
29-
const openedFiles = new Set<ModelFile>([initialFile])
30-
const output = new Map()
31-
32-
if (isArrayNotEmpty(openedFilesIds)) {
33-
files.forEach(file => {
34-
if (openedFilesIds.includes(file.id)) {
35-
openedFiles.add(file)
36-
}
37-
38-
output.set(file.id, file)
39-
})
40-
}
41-
42-
return {
43-
files: output,
44-
openedFiles,
45-
}
46-
})
47-
},
48-
setOpenedFiles(files: Set<ModelFile>) {
49-
set(() => {
50-
const openedFiles = new Set(files)
51-
52-
setOpenedFilesIds({
53-
ids: Array.from(openedFiles.values())
54-
.filter(file => isFalse(file.isLocal))
55-
.map(file => file.id),
56-
})
57-
58-
return { openedFiles }
59-
})
18+
selectedFile: undefined,
19+
setProject(project) {
20+
set(() => ({
21+
project: new ModelDirectory(project),
22+
}))
6023
},
61-
getNextOpenedFile() {
62-
return get().openedFiles.values().next().value
24+
setFiles(files) {
25+
set(() => ({
26+
files: files.reduce((acc, file) => acc.set(file.id, file), new Map()),
27+
}))
6328
},
64-
selectFile(file: ModelFile) {
65-
const s = get()
66-
67-
if (isFalse(s.openedFiles.has(file))) {
68-
s.openedFiles.add(file)
69-
}
70-
29+
selectFile(selectedFile) {
7130
set(() => ({
72-
activeFileId: file.id,
73-
activeFile: file,
31+
selectedFile,
7432
}))
7533
},
34+
refreshProject() {
35+
get().setProject(get().project)
36+
},
7637
}))

0 commit comments

Comments
 (0)