refactor: remove local tree

This commit is contained in:
liqingwei 2021-03-07 14:53:29 +08:00
parent b6edd1a5d5
commit 2f10fe2292
15 changed files with 256 additions and 158 deletions

View File

@ -1,43 +1,46 @@
import SidebarListItem from './sidebar-list-item'
import { NoteTreeState, TreeModel } from 'containers/tree'
import { NoteTreeState } from 'containers/tree'
import Tree, {
ItemId,
mutateTree,
TreeDestinationPosition,
TreeSourcePosition,
} from '@atlaskit/tree'
import { useEffect, useCallback } from 'react'
import { NoteState } from 'containers/note'
import IconPlus from 'heroicons/react/outline/Plus'
import router from 'next/router'
import HotkeyTooltip from 'components/hotkey-tooltip'
import SidebarItemButton from './sidebar-item-button'
const SideBarList = () => {
const { tree, updateTree, initTree, moveTree } = NoteTreeState.useContainer()
const { initAllNotes } = NoteState.useContainer()
const { tree, initTree, moveItem, mutateItem } = NoteTreeState.useContainer()
useEffect(() => {
initTree().then(() => initAllNotes())
}, [initAllNotes, initTree])
initTree()
}, [initTree])
const onExpand = useCallback(
(itemId: ItemId) => {
updateTree(mutateTree(tree, itemId, { isExpanded: true }) as TreeModel)
(id: ItemId) => {
mutateItem({
id,
isExpanded: true,
})
},
[tree, updateTree]
[mutateItem]
)
const onCollapse = useCallback(
(itemId: ItemId) => {
updateTree(mutateTree(tree, itemId, { isExpanded: false }) as TreeModel)
(id: ItemId) => {
mutateItem({
id,
isExpanded: false,
})
},
[tree, updateTree]
[mutateItem]
)
const onDragEnd = useCallback(
(source: TreeSourcePosition, destination?: TreeDestinationPosition) => {
moveTree({
moveItem({
source,
destination,
}).catch((e) => {
@ -45,7 +48,7 @@ const SideBarList = () => {
console.error('更新错误', e)
})
},
[moveTree]
[moveItem]
)
return (

View File

@ -1,4 +1,4 @@
import { FC } from 'react'
import { FC, useEffect } from 'react'
import FilterModal from 'components/filter-modal/filter-modal'
import FilterModalInput from 'components/filter-modal/filter-modal-input'
import FilterModalList from 'components/filter-modal/filter-modal-list'
@ -12,9 +12,14 @@ const Trash: FC = () => {
closeModal,
filterNotes,
keyword,
list,
initTrash,
filterData,
} = TrashState.useContainer()
useEffect(() => {
initTrash()
}, [initTrash])
return (
<FilterModal open={isOpen} onClose={closeModal}>
<FilterModalInput
@ -23,7 +28,7 @@ const Trash: FC = () => {
keyword={keyword}
/>
<FilterModalList
items={list}
items={filterData}
ItemComponent={(item: NoteModel) => (
<TrashItem note={item} keyword={keyword} key={item.id} />
)}

View File

@ -96,10 +96,6 @@ const useNote = () => {
[cache, post]
)
const initAllNotes = useCallback(() => {
noteWorker.current?.checkAllNotes()
}, [noteWorker])
const removeNote = useCallback(
async (id: string) => {
await post(`${id}/meta`, {
@ -117,7 +113,6 @@ const useNote = () => {
removeNote,
setNote,
updateNoteMeta,
initAllNotes,
loading,
}
}

View File

@ -2,32 +2,45 @@ import { useState, useCallback } from 'react'
import { createContainer } from 'unstated-next'
import { noteStore, NoteStoreItem } from 'utils/local-store'
import escapeStringRegexp from 'escape-string-regexp'
import useFetch from 'use-http'
import { map } from 'lodash'
function useTrashData() {
const [list, setList] = useState<NoteStoreItem[]>()
const [noteIds, setNoteIds] = useState<string[]>()
const [keyword, setKeyword] = useState<string>()
const { get, data } = useFetch('/api/trash')
const [filterData, setFilterData] = useState<NoteStoreItem[]>()
const filterNotes = useCallback(async (keyword?: string) => {
setKeyword(keyword)
const initTrash = useCallback(async () => {
await get()
setNoteIds(data)
}, [data, get])
if (!keyword) {
setList([])
return
}
const filterNotes = useCallback(
async (keyword?: string) => {
setKeyword(keyword)
const data = [] as NoteStoreItem[]
const re = new RegExp(escapeStringRegexp(keyword))
const data = [] as NoteStoreItem[]
const re = keyword ? new RegExp(escapeStringRegexp(keyword)) : false
await noteStore.iterate<NoteStoreItem, void>((note) => {
if (re.test(note.rawContent || '') || re.test(note.title || '')) {
data.push(note)
}
})
map(noteIds, async (id) => {
const note = await noteStore.getItem<NoteStoreItem>(id)
if (!note) return
if (
!re ||
re.test(note.rawContent || '') ||
re.test(note.title || '')
) {
data.push(note)
}
})
setList(data)
}, [])
setFilterData(data)
},
[noteIds]
)
return { list, keyword, filterNotes }
return { filterData, keyword, filterNotes, initTrash }
}
function useFilterModal() {

View File

@ -6,15 +6,14 @@ import {
TreeItem,
TreeSourcePosition,
} from '@atlaskit/tree'
import { isEmpty, forEach, union, map } from 'lodash'
import { isEmpty, forEach, union, map, without } from 'lodash'
import { genId } from 'packages/shared'
import { useCallback, useEffect, useRef, useState } from 'react'
import { NOTE_DELETED } from 'shared/meta'
import { createContainer } from 'unstated-next'
import { uiStore } from 'utils/local-store'
import { NoteModel } from './note'
import { useNoteWorker } from 'workers/note'
import useFetch from 'use-http'
import { TreeItemMutation } from '@atlaskit/tree/dist/types/utils/tree'
export interface TreeItemModel extends TreeItem {
id: string
@ -36,22 +35,6 @@ export const DEFAULT_TREE: TreeModel = {
},
}
const saveLocalTree = (data: TreeModel) => {
const items: any = {}
forEach(data.items, (item) => {
items[item.id] = {
isExpanded: item.isExpanded,
id: item.id,
}
})
uiStore.setItem('tree_items', {
...data,
items,
})
}
const useNoteTree = (initData: TreeModel = DEFAULT_TREE) => {
const { post } = useFetch('/api/tree')
const [tree, setTree] = useState<TreeModel>(initData)
@ -62,84 +45,71 @@ const useNoteTree = (initData: TreeModel = DEFAULT_TREE) => {
treeRef.current = tree
}, [tree])
const updateTree = useCallback((data: TreeModel) => {
setTree(data)
saveLocalTree(data)
}, [])
const initTree = useCallback(async () => {
const localTree =
(await uiStore.getItem<TreeModel>('tree_items')) || DEFAULT_TREE
if (!noteWorker) return
const curTree = treeRef.current
const newItems = {} as TreeModel['items']
await Promise.all(
map(curTree.items, async (item) => {
if (!item.isExpanded && localTree.items[item.id]?.isExpanded) {
item.isExpanded = true
}
newItems[item.id] = {
...item,
data: await noteWorker.current?.fetchNote(item.id),
data: await noteWorker?.fetchNote(item.id),
}
})
)
updateTree({
setTree({
...curTree,
items: newItems,
})
}, [noteWorker, updateTree])
noteWorker?.checkAllNotes(newItems)
}, [noteWorker])
const addToTree = useCallback(
(item: NoteModel) => {
const newItems: TreeModel['items'] = {}
const curTree = treeRef.current
const curItem = curTree.items[item.id]
const parentItem = treeRef.current.items[item.pid || 'root']
const addToTree = useCallback((item: NoteModel) => {
const newItems: TreeModel['items'] = {}
const curTree = treeRef.current
const curItem = curTree.items[item.id]
const parentItem = treeRef.current.items[item.pid || 'root']
parentItem.children = union(parentItem.children, [item.id])
parentItem.children = union(parentItem.children, [item.id])
if (!curItem) {
newItems[item.id] = {
id: item.id,
data: item,
children: [],
}
} else if (curItem.data?.title !== item.title) {
newItems[item.id] = {
...curItem,
data: item,
}
if (!curItem) {
newItems[item.id] = {
id: item.id,
data: item,
children: [],
}
if (!isEmpty(newItems)) {
updateTree({
...curTree,
items: {
...curTree.items,
...newItems,
},
})
} else if (curItem.data?.title !== item.title) {
newItems[item.id] = {
...curItem,
data: item,
}
},
[updateTree]
)
}
const removeFromTree = useCallback(
(itemId: string) => {
updateTree(
mutateTree(treeRef.current, itemId, {
data: {
...treeRef.current.items[itemId].data,
deleted: NOTE_DELETED.DELETED,
},
}) as TreeModel
)
},
[updateTree]
)
if (!isEmpty(newItems)) {
setTree({
...curTree,
items: {
...curTree.items,
...newItems,
},
})
}
}, [])
const removeFromTree = useCallback((id: string) => {
forEach(treeRef.current.items, (item) => {
if (item.children.includes(id)) {
setTree(
mutateTree(treeRef.current, item.id, {
children: without(item.children, id),
}) as TreeModel
)
return false
}
})
}, [])
const genNewId = useCallback(() => {
let newId = genId()
@ -149,7 +119,7 @@ const useNoteTree = (initData: TreeModel = DEFAULT_TREE) => {
return newId
}, [])
const moveTree = useCallback(
const moveItem = useCallback(
async (data: {
source: TreeSourcePosition
destination?: TreeDestinationPosition
@ -162,24 +132,43 @@ const useNoteTree = (initData: TreeModel = DEFAULT_TREE) => {
treeRef.current,
data.source,
data.destination
)
) as TreeModel
updateTree(newTree as TreeModel)
await post('move', data)
setTree(newTree)
await post({
action: 'move',
data,
})
return
},
[post, updateTree]
[post]
)
const mutateItem = useCallback(
async (data: TreeItemMutation) => {
const tree = mutateTree(
treeRef.current,
data.id as string,
data
) as TreeModel
setTree(tree)
await post({
action: 'mutate',
data,
})
},
[post]
)
return {
tree,
addToTree,
removeFromTree,
updateTree,
initTree,
genNewId,
moveTree,
moveItem,
mutateItem,
}
}

View File

@ -3,6 +3,7 @@ import { jsonToMeta, metaToJson } from 'services/meta'
import { useAuth } from 'services/middlewares/auth'
import { useStore } from 'services/middlewares/store'
import { getPathNoteById } from 'services/note-path'
import { NOTE_DELETED } from 'shared/meta'
export default api()
.use(useAuth)
@ -18,6 +19,16 @@ export default api()
if (oldMeta) {
meta = new Map([...oldMeta, ...meta])
// 处理删除情况
const { deleted } = req.body
if (oldMeta.get('deleted') !== deleted) {
if (deleted === NOTE_DELETED.DELETED) {
await req.treeStore.removeItem(id)
} else if (deleted === NOTE_DELETED.NORMAL) {
await req.treeStore.addItem(id, req.body.pid)
}
}
}
await req.store.copyObject(notePath, notePath, {

25
pages/api/trash.ts Normal file
View File

@ -0,0 +1,25 @@
import { api } from 'services/api'
import { useAuth } from 'services/middlewares/auth'
import { useStore } from 'services/middlewares/store'
export default api()
.use(useAuth)
.use(useStore)
.get(async (req, res) => {
res.json(await req.treeStore.trash.get())
})
.post(async (req, res) => {
const { action, data } = req.body
switch (action) {
case 'delete':
// todo 真删除
console.log(data)
break
default:
return res.APIError.NOT_SUPPORTED.throw('action not found')
}
res.end()
})

28
pages/api/tree.ts Normal file
View File

@ -0,0 +1,28 @@
import { api } from 'services/api'
import { useAuth } from 'services/middlewares/auth'
import { useStore } from 'services/middlewares/store'
export default api()
.use(useAuth)
.use(useStore)
.get(async (req, res) => {
res.json(await req.treeStore.get())
})
.post(async (req, res) => {
const { action, data } = req.body
switch (action) {
case 'move':
await req.treeStore.moveItem(data.source, data.destination)
break
case 'mutate':
await req.treeStore.mutateItem(data.id, data)
break
default:
return res.APIError.NOT_SUPPORTED.throw('action not found')
}
res.end()
})

View File

@ -1,10 +0,0 @@
import { api } from 'services/api'
import { useAuth } from 'services/middlewares/auth'
import { useStore } from 'services/middlewares/store'
export default api()
.use(useAuth)
.use(useStore)
.get(async (req, res) => {
res.json(await req.treeStore.get())
})

View File

@ -1,14 +0,0 @@
import { api } from 'services/api'
import { useAuth } from 'services/middlewares/auth'
import { useStore } from 'services/middlewares/store'
export default api()
.use(useAuth)
.use(useStore)
.post(async (req, res) => {
const { source, destination } = req.body
await req.treeStore.moveItem(source, destination)
res.end()
})

View File

@ -2,6 +2,10 @@ export function getPathTree() {
return `tree`
}
export function getPathTrash() {
return `trash`
}
export function getPathNoteById(id: string) {
return `notes/${id}`
}

45
services/trash.ts Normal file
View File

@ -0,0 +1,45 @@
import { TreeItemModel, TreeModel } from 'containers/tree'
import { StoreProvider } from 'packages/store/src'
import { getPathTrash } from './note-path'
export class TrashStore {
store: StoreProvider
trashPath: string
constructor(store: StoreProvider) {
this.store = store
this.trashPath = getPathTrash()
}
async get() {
const res = await this.store.getObject(this.trashPath)
if (!res) {
return this.set({})
}
return JSON.parse(res) as TreeModel['items']
}
async set(items: TreeModel['items']) {
await this.store.putObject(this.trashPath, JSON.stringify(items))
return items
}
async addItem(item: TreeItemModel) {
const items = await this.get()
items[item.id] = item
return this.set(items)
}
async removeItem(id: string) {
const items = await this.get()
delete items[id]
return this.set(items)
}
}

View File

@ -1,8 +1,10 @@
import { moveItemOnTree } from '@atlaskit/tree'
import { moveItemOnTree, mutateTree } from '@atlaskit/tree'
import { TreeItemMutation } from '@atlaskit/tree/dist/types/utils/tree'
import { DEFAULT_TREE, TreeModel } from 'containers/tree'
import { forEach, pull, union } from 'lodash'
import { StoreProvider } from 'packages/store/src'
import { getPathTree } from './note-path'
import { TrashStore } from './trash'
interface movePosition {
parentId: string
@ -12,10 +14,12 @@ interface movePosition {
export class TreeStore {
store: StoreProvider
treePath: string
trash: TrashStore
constructor(store: StoreProvider) {
this.store = store
this.treePath = getPathTree()
this.trash = new TrashStore(store)
}
async get() {
@ -71,4 +75,10 @@ export class TreeStore {
return this.set(tree)
}
async mutateItem(id: string, data: TreeItemMutation) {
const tree = await this.get()
return this.set(mutateTree(tree, id, data) as TreeModel)
}
}

View File

@ -16,5 +16,5 @@ export function useNoteWorker() {
}
}, [])
return NoteWorkerApiRef
return NoteWorkerApiRef.current
}

View File

@ -1,5 +1,5 @@
import { expose } from 'comlink'
import { noteStore, NoteStoreItem, uiStore } from 'utils/local-store'
import { noteStore, NoteStoreItem } from 'utils/local-store'
import { keys, pull } from 'lodash'
import { NoteModel } from 'containers/note'
import removeMarkdown from 'remove-markdown'
@ -20,14 +20,8 @@ const noteWorker: NoteWorkerApi = {
/**
* 使 note
*/
async function checkAllNotes() {
const tree = await uiStore.getItem<TreeModel>('tree_items')
if (!tree) return
delete tree.items.root
const noteIds = keys(tree.items)
async function checkAllNotes(items: TreeModel['items']) {
const noteIds = keys(items)
const localNoteIds = await noteStore.keys()
const unusedNoteIds = pull(localNoteIds, ...noteIds)