feat: can render list view

This commit is contained in:
0xzion
2023-12-19 23:16:09 +08:00
parent d86b04a321
commit e550cf84c3
8 changed files with 195 additions and 108 deletions

View File

@@ -36,10 +36,10 @@ export interface IDatabaseContext {
currentView: IViewNode
viewIndex: number
setViewIndex: Dispatch<SetStateAction<number>>
activeViewId: string
setActiveViewId: Dispatch<SetStateAction<string>>
addView(viewType: ViewType): Promise<void>
addView(viewType: ViewType): Promise<IViewNode>
updateView(viewId: string, props: Partial<IViewNode['props']>): Promise<void>
deleteView(viewId: string): Promise<void>
@@ -91,17 +91,24 @@ export const DatabaseProvider = ({
databaseId,
}: PropsWithChildren<DatabaseProviderProps>) => {
const { Provider } = databaseContext
const [viewIndex, setViewIndex] = useState(0)
const database = useDatabase(databaseId)
const [activeViewId, setActiveViewId] = useState(() => {
const view = database.views.find(
(v) => v.id === database.database.props.activeViewId,
)
return view?.id || database.views[0].id
})
async function reloadNodes() {
const nodes = await db.listNodesBySpaceId(database.database.spaceId)
store.node.setNodes(nodes)
}
async function addView(viewType: ViewType) {
await db.addView(databaseId, viewType)
const view = await db.addView(databaseId, viewType)
reloadNodes()
return view
}
async function updateView(
@@ -147,7 +154,7 @@ export const DatabaseProvider = ({
}
async function moveColumn(fromIndex: number, toIndex: number) {
const view = database.views[viewIndex]
const view = database.views.find((v) => v.id === activeViewId)!
await db.moveColumn(databaseId, view.id, fromIndex, toIndex)
reloadNodes()
}
@@ -211,18 +218,17 @@ export const DatabaseProvider = ({
}
const currentView = useMemo(() => {
const { viewIds = [] } = database.database.props
const viewId = viewIds[viewIndex]
return database.views.find((view) => view.id === viewId)!
}, [database, viewIndex])
return database.views.find((view) => view.id === activeViewId)!
}, [database, activeViewId])
return (
<Provider
value={{
...database,
viewIndex,
currentView,
setViewIndex,
activeViewId,
setActiveViewId,
addView,
deleteView,

View File

@@ -1,6 +1,6 @@
import { FC, memo, useCallback, useEffect, useRef, useState } from 'react'
import isEqual from 'react-fast-compare'
import { Box, css } from '@fower/react'
import { Box, css, FowerHTMLProps } from '@fower/react'
import { createEditor, Editor, Transforms } from 'slate'
import { withHistory } from 'slate-history'
import { Editable, RenderElementProps, Slate, withReact } from 'slate-react'
@@ -27,95 +27,112 @@ function withCell(editor: Editor) {
return editor
}
export const PrimaryCell: FC<CellProps> = memo(function PrimaryCell(props) {
const { cell, updateCell } = props
const [value, setValue] = useState<any>(null)
const editorRef = useRef(withCell(withReact(withHistory(createEditor()))))
interface Props extends Omit<FowerHTMLProps<'div'>, 'column'> {
editorAtomicStyle?: string
}
const parentEditor = useEditor()
export const PrimaryCell: FC<CellProps & Props> = memo(
function PrimaryCell(props) {
const {
cell,
index,
column,
width,
selected,
updateCell,
editorAtomicStyle = '',
...rest
} = props
const [value, setValue] = useState<any>(null)
const editorRef = useRef(withCell(withReact(withHistory(createEditor()))))
const nodeId = cell.props.ref
const parentEditor = useEditor()
useEffect(() => {
db.getNode(nodeId).then((node) => {
if (!node) {
return setValue([])
const nodeId = cell.props.ref
useEffect(() => {
db.getNode(nodeId).then((node) => {
if (!node) {
return setValue([])
}
if (!isEqual(editorRef.current.children, node.element)) {
setValue(Array.isArray(node.element) ? node.element : [node.element])
}
})
}, [nodeId])
useEffect(() => {
emitter.on('REF_NODE_UPDATED', (node) => {
if (node.id === nodeId) {
if (isEqual(editorRef.current.children, node.element)) return
clearEditor(editorRef.current)
Transforms.insertNodes(
editorRef.current,
Array.isArray(node.element) ? node.element : [node.element],
)
}
})
return () => emitter.off('REF_NODE_UPDATED')
}, [nodeId])
const renderElement = useCallback((props: RenderElementProps) => {
const element = props.element as TElement
if (element.type === 'p') {
return <Paragraph {...props} />
}
if (!isEqual(editorRef.current.children, node.element)) {
setValue(Array.isArray(node.element) ? node.element : [node.element])
}
})
}, [nodeId])
useEffect(() => {
emitter.on('REF_NODE_UPDATED', (node) => {
if (node.id === nodeId) {
if (isEqual(editorRef.current.children, node.element)) return
clearEditor(editorRef.current)
Transforms.insertNodes(
editorRef.current,
Array.isArray(node.element) ? node.element : [node.element],
)
if (element.type === 'tag') {
return <Tag {...(props as any)} />
}
})
return () => emitter.off('REF_NODE_UPDATED')
}, [nodeId])
const renderElement = useCallback((props: RenderElementProps) => {
const element = props.element as TElement
if (element.type === 'p') {
return <Paragraph {...props} />
return <div {...props}>{props.children}</div>
}, [])
function updateParentEditor(element: any) {
const entry = getNodeById(parentEditor, element.id)
if (!entry) return
const [node, path] = entry
if (!isEqual(node, element)) return
Transforms.removeNodes(parentEditor, { at: path })
Transforms.insertNodes(parentEditor, element, {
at: path,
select: true,
})
}
if (element.type === 'tag') {
return <Tag {...(props as any)} />
}
if (!value) return null
return <div {...props}>{props.children}</div>
}, [])
return (
<Box w-100p h-100p relative inlineFlex {...rest}>
<Slate
editor={editorRef.current as any}
initialValue={value}
onChange={async (value) => {
const element: any = value[0]
function updateParentEditor(element: any) {
const entry = getNodeById(parentEditor, element.id)
if (!entry) return
const [node, path] = entry
if (!isEqual(node, element)) return
Transforms.removeNodes(parentEditor, { at: path })
Transforms.insertNodes(parentEditor, element, {
at: path,
select: true,
})
}
if (!value) return null
return (
<Box w-100p h-100p relative inlineFlex>
<Slate
editor={editorRef.current as any}
initialValue={value}
onChange={async (value) => {
const element: any = value[0]
db.updateNode(nodeId, { element })
db.updateCell(cell.id, {}) // update updatedAt
updateParentEditor(element)
}}
>
{/* <HoveringToolbar /> */}
<Editable
className={css('black p2 outlineNone h-100p w-100p')}
renderLeaf={(props) => <Leaf {...props} />}
renderElement={renderElement}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault()
}
db.updateNode(nodeId, { element })
db.updateCell(cell.id, {}) // update updatedAt
updateParentEditor(element)
}}
/>
</Slate>
</Box>
)
})
>
{/* <HoveringToolbar /> */}
<Editable
className={css(
'black px2 py2 outlineNone h-100p w-100p ' + editorAtomicStyle,
)}
renderLeaf={(props) => <Leaf {...props} />}
renderElement={renderElement}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault()
}
}}
/>
</Slate>
</Box>
)
},
)

View File

@@ -20,8 +20,8 @@ function Item({ children, viewType, ...rest }: ItemProps) {
const { close } = usePopoverContext()
const ctx = useDatabaseContext()
async function addColumn() {
await ctx.addView(viewType)
ctx.setViewIndex(ctx.views.length)
const view = await ctx.addView(viewType)
ctx.setActiveViewId(view.id)
close()
}

View File

@@ -1,12 +1,14 @@
import { Box } from '@fower/react'
import { db } from '@penx/local-db'
import { IDatabaseNode } from '@penx/model-types'
import { mappedByKey } from '@penx/shared'
import { useDatabaseContext } from '../DatabaseContext'
import { ViewIcon } from './ViewIcon'
import { ViewMenu } from './ViewMenu'
export const ViewList = () => {
const { views, database, viewIndex, setViewIndex } = useDatabaseContext()
const { views, database, activeViewId, setActiveViewId } =
useDatabaseContext()
const { viewIds = [] } = database.props
const viewMap = mappedByKey(views, 'id')
const sortedViews = viewIds.map((viewId) => viewMap[viewId])
@@ -14,7 +16,7 @@ export const ViewList = () => {
return (
<Box toCenterY gap1>
{sortedViews.map((view, index) => {
const active = index === viewIndex
const active = activeViewId === view.id
return (
<Box
key={view.id}
@@ -29,7 +31,15 @@ export const ViewList = () => {
pr={active ? 4 : 12}
gray900={active}
bgGray100={active}
onClick={() => setViewIndex(index)}
onClick={async () => {
setActiveViewId(view.id)
await db.updateNode<IDatabaseNode>(database.id, {
props: {
...database.props,
activeViewId: view.id,
},
})
}}
>
<ViewIcon viewType={view.props.viewType} />
<Box flexShrink-0>{view.props.name}</Box>

View File

@@ -37,7 +37,8 @@ export const ViewMenu = ({ view, index }: ViewMenuProps) => {
function Content({ view, index }: ViewMenuProps) {
const { close } = usePopoverContext()
const { updateView, deleteView, setViewIndex } = useDatabaseContext()
const { database, updateView, deleteView, views, setActiveViewId } =
useDatabaseContext()
const [name, setName] = useState(view.props.name)
return (
@@ -76,12 +77,12 @@ function Content({ view, index }: ViewMenuProps) {
</MenuItem>
<MenuItem
// disabled={index === 0}
disabled={index === 0}
gap2
onClick={async () => {
// if (index === 0) return
if (index === 0) return
await deleteView(view.id)
setViewIndex(0)
setActiveViewId(database.props.viewIds[0])
close()
}}
>

View File

@@ -1,12 +1,62 @@
import { PropsWithChildren } from 'react'
import { Box } from '@fower/react'
import { Bullet } from 'uikit'
import { db } from '@penx/local-db'
import { IRowNode } from '@penx/model-types'
import { store } from '@penx/store'
import { useDatabaseContext } from '../DatabaseContext'
import { PrimaryCell } from '../Table/Cell/PrimaryCell'
interface TableViewProps {}
export const ListView = ({ children }: PropsWithChildren<TableViewProps>) => {
export const ListView = () => {
const { rows } = useDatabaseContext()
return (
<Box>
<Box>List view, coming soon...</Box>
{rows.map((row) => (
<ListItem key={row.id} row={row} />
))}
</Box>
)
}
interface ListItemProps {
row: IRowNode
}
function ListItem({ row }: ListItemProps) {
const { cells, columns } = useDatabaseContext()
const primaryCell = cells.find(
(cell) => !!cell.props.ref && cell.props.rowId === row.id,
)!
const column = columns.find(
(column) => column.id === primaryCell.props.columnId,
)!
async function clickBullet() {
const node = await db.getNode(primaryCell?.props.ref!)
if (node) store.node.selectNode(node)
}
return (
<Box toCenterY>
<Bullet
dashed
outlineColor="transparent"
borderNeutral400
style={{
flexShrink: 0,
}}
onClick={clickBullet}
/>
<PrimaryCell
index={0}
cell={primaryCell}
column={column}
width={0}
selected={false}
updateCell={() => {}}
editorAtomicStyle="py-2"
flex
/>
</Box>
)
}

View File

@@ -490,6 +490,7 @@ class DB {
props: {
color: getRandomColor(),
name,
activeViewId: '',
viewIds: [],
},
})
@@ -543,6 +544,7 @@ class DB {
await this.updateNode(database.id, {
props: {
...database.props,
activeViewId: tableView.id,
viewIds: [tableView.id, listView.id],
},
})

View File

@@ -88,6 +88,7 @@ export interface IDatabaseNode extends INode {
props: {
name: string // database name, same with tag name
color: string
activeViewId: string
viewIds: string[]
}
}