Skip to content

Commit c49ce0e

Browse files
committed
feat: editor tabs show file save state
1 parent 5029f32 commit c49ce0e

File tree

9 files changed

+131
-63
lines changed

9 files changed

+131
-63
lines changed

apps/linebyline/src/components/EditorArea/Editor.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { EditorViewType } from '@linebyline/editor/types'
44
import { invoke } from '@tauri-apps/api'
55
import { emit } from '@tauri-apps/api/event'
66
import { appWindow } from '@tauri-apps/api/window'
7-
import { useCallback, useEffect, useMemo, useState } from 'react'
7+
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
88
import styled, { css } from 'styled-components'
99
import { useEditorStore } from '@/stores'
1010
import { getFileObject } from '@/helper/files'
@@ -131,4 +131,4 @@ export interface EditorProps {
131131
onSave?: () => void
132132
}
133133

134-
export default Editor
134+
export default memo(Editor)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { getFileObject } from "@/helper/files"
2+
import type { IFile } from "@/helper/filesys"
3+
import { useGlobalTheme } from "@/hooks"
4+
import { useEditorStore, useEditorStateStore } from "@/stores"
5+
import { memo } from "react"
6+
import { TabItem, Dot } from "./styles"
7+
8+
const EditorAreaTabs = memo(() => {
9+
const { opened, activeId, setActiveId, delOpenedFile } = useEditorStore()
10+
const { idStateMap } = useEditorStateStore()
11+
const { themeColors } = useGlobalTheme()
12+
13+
const onSelectItem = (id: string) => {
14+
setActiveId(id)
15+
}
16+
17+
const close = (ev: React.MouseEvent<HTMLElement, MouseEvent>, id: string) => {
18+
ev.stopPropagation()
19+
const curIndex = opened.findIndex((openedId) => openedId === id)
20+
if (curIndex < 0) return
21+
22+
if (activeId === id) {
23+
if (opened.length > 0) {
24+
setActiveId(curIndex === 0 ? opened[curIndex + 1] : opened[curIndex - 1])
25+
}
26+
}
27+
28+
delOpenedFile(id)
29+
}
30+
31+
return opened.length > 1 ? (
32+
<div className='tab-items'>
33+
{opened.map((id) => {
34+
const file = getFileObject(id) as IFile
35+
const active = activeId === id
36+
const editorState = idStateMap.get(id)
37+
38+
return (
39+
<TabItem
40+
active={active}
41+
onClick={() => onSelectItem(file.id)}
42+
className={'tab-item'}
43+
key={id}
44+
>
45+
<i className={'ri-file-3-line tab-items__icon'} />
46+
<span style={{ color: active ? themeColors.accentColor : '' }}>{file.name}</span>
47+
48+
{editorState?.hasUnsavedChanges ? (
49+
<Dot />
50+
) : (
51+
<i
52+
className='ri-close-line tab-items__icon close'
53+
onClick={(ev: React.MouseEvent<HTMLElement, MouseEvent>) => close(ev, id)}
54+
/>
55+
)}
56+
</TabItem>
57+
)
58+
})}
59+
</div>
60+
) : null
61+
})
62+
63+
export default EditorAreaTabs
Lines changed: 10 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,22 @@
11
import Editor from './Editor'
2-
import { Container, TabItem } from './styles'
3-
import { getFileObject } from '@/helper/files'
4-
import type { IFile } from '@/helper/filesys'
5-
import { useGlobalTheme } from '@/hooks'
2+
import { Container } from './styles'
63
import { useEditorStore } from '@/stores'
4+
import { memo } from 'react'
5+
import EditorAreaTabs from './EditorAreaTabs'
76

8-
export default function EditorArea() {
9-
const { opened, activeId, setActiveId, delOpenedFile } = useEditorStore()
10-
const { themeColors } = useGlobalTheme()
11-
const onSelectItem = (id: string) => {
12-
setActiveId(id)
13-
}
14-
15-
const close = (ev: React.MouseEvent<HTMLElement, MouseEvent>, id: string) => {
16-
ev.stopPropagation()
17-
const curIndex = opened.findIndex(openedId => openedId === id)
18-
if (curIndex < 0)
19-
return
20-
21-
if (activeId === id) {
22-
if (opened.length > 0) {
23-
setActiveId(
24-
curIndex === 0 ? opened[curIndex + 1] : opened[curIndex - 1],
25-
)
26-
}
27-
}
28-
29-
delOpenedFile(id)
30-
}
7+
function EditorArea() {
8+
const { opened, activeId } = useEditorStore()
319

3210
return (
33-
<Container className="w-full h-full">
34-
{opened.length > 1 ? <div className="tab-items">
35-
{opened.map((id) => {
36-
const file = getFileObject(id) as IFile
37-
const active = activeId === id
38-
39-
return (
40-
<TabItem
41-
active={active}
42-
onClick={() => onSelectItem(file.id)}
43-
className={'tab-item'}
44-
key={id}
45-
>
46-
<i className={'ri-file-3-line tab-items__icon'} />
47-
<span style={{ color: active ? themeColors.accentColor : '' }}>
48-
{file.name}
49-
</span>
50-
<i
51-
className="ri-close-line tab-items__icon close"
52-
onClick={(ev: React.MouseEvent<HTMLElement, MouseEvent>) =>
53-
close(ev, id)
54-
}
55-
/>
56-
</TabItem>
57-
)
58-
})}
59-
</div> : null}
60-
<div className="code-contents">
11+
<Container className='w-full h-full'>
12+
<EditorAreaTabs />
13+
<div className='code-contents'>
6114
{opened.map((id) => {
6215
return <Editor key={id} id={id} active={id === activeId} />
6316
})}
6417
</div>
6518
</Container>
6619
)
6720
}
21+
22+
export default memo(EditorArea)

apps/linebyline/src/components/EditorArea/styles.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,18 @@ export const TabItem = styled.div<TabItemProps>`
6060
}}
6161
`
6262

63+
type DotProps = {
64+
color?: string
65+
}
66+
67+
export const Dot = styled.div<DotProps>`
68+
width: 0.4rem;
69+
height: 0.4rem;
70+
border-radius: 50%;
71+
background-color: ${(props) => props.color || props.theme.labelFontColor};
72+
margin: 0 0.25rem;
73+
`
74+
6375
interface TabItemProps {
6476
active: boolean
6577
}

apps/linebyline/src/editorHooks/EditorState/editor-state.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import { IFile } from '@/helper/filesys'
2-
import { EditorState, Note } from '@linebyline/editor/types'
3-
import { EditContentAction, editContent } from './reducers/edit-content'
4-
import { EditorInitAction, initEditor } from './reducers/init-content'
5-
import { SaveContentAction, saveContent } from './reducers/save-content'
1+
import type { IFile } from '@/helper/filesys'
2+
import type { EditorState, Note } from '@linebyline/editor/types'
3+
import type { EditContentAction} from './reducers/edit-content'
4+
import { editContent } from './reducers/edit-content'
5+
import type { EditorInitAction} from './reducers/init-content'
6+
import { initEditor } from './reducers/init-content'
7+
import type { SaveContentAction} from './reducers/save-content'
8+
import { saveContent } from './reducers/save-content'
69

710
type EditorAction = EditorInitAction | SaveContentAction | EditContentAction
811

apps/linebyline/src/editorHooks/EditorState/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import { useCallback, useEffect, useReducer } from 'react'
99

1010
import { editorReducer, initializeState } from './editor-state'
1111
import type { IFile } from '@/helper/filesys'
12-
import { useEditorStore } from '@/stores'
12+
import { useEditorStateStore, useEditorStore } from '@/stores'
1313
import { useTitleEffect } from '@/hooks/useTitleEffect'
1414
import type { KeyBindingProps } from '@remirror/core'
1515

1616
export const useEditorState: FC<EditorStateProps> = ({ active, file }) => {
1717
const ctx = useRemirrorContext()
1818
const helpers = useHelpers()
1919
const { getEditorDelegate, getEditorContent } = useEditorStore()
20+
const { setIdStateMap } = useEditorStateStore()
2021
const curDelegate = getEditorDelegate(file.id)
2122
const [state, dispatch] = useReducer(
2223
editorReducer,
@@ -25,6 +26,12 @@ export const useEditorState: FC<EditorStateProps> = ({ active, file }) => {
2526
)
2627
useTitleEffect(state, active)
2728

29+
useEffect(() => {
30+
if (active) {
31+
setIdStateMap(file.id, state)
32+
}
33+
}, [state, file.id, setIdStateMap, active])
34+
2835
const saveHandler = useCallback(async (editorContent?: string) => {
2936
if (!active) return
3037

apps/linebyline/src/main.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import ReactDOM from 'react-dom/client'
55
import { BrowserRouter } from 'react-router-dom'
66
import App from './App'
77
import { FallBackContainer } from './components/FallBack'
8+
import { enableMapSet } from 'immer'
89
import './normalize.css'
910

11+
enableMapSet()
12+
1013
ReactDOM.createRoot(document.getElementById('root')!).render(
1114
<StrictMode>
1215
<HoxRoot>

apps/linebyline/src/stores/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { default as useEditorStore } from './useEditorStore'
22
export { default as useCommandStore } from './useCommandStore'
3+
export { default as useEditorStateStore } from './useEditorStateStore'
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { create } from 'zustand'
2+
import { immer } from 'zustand/middleware/immer'
3+
4+
type EditorStateStoreState = {
5+
idStateMap: Map<string, any>
6+
}
7+
8+
type EditorStateStoreAction = {
9+
setIdStateMap: (id: string, editorState: any) => void
10+
}
11+
12+
const useEditorStateStore = create(
13+
immer<EditorStateStoreState & EditorStateStoreAction>((set) => ({
14+
idStateMap: new Map(),
15+
16+
setIdStateMap: (id, editorState) => {
17+
set((state) => {
18+
state.idStateMap?.set(id, editorState)
19+
})
20+
},
21+
})),
22+
)
23+
24+
export default useEditorStateStore

0 commit comments

Comments
 (0)