mirror of
https://github.com/penxio/penx.git
synced 2026-05-12 03:03:12 -04:00
feat: can render list view
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
})
|
||||
|
||||
@@ -88,6 +88,7 @@ export interface IDatabaseNode extends INode {
|
||||
props: {
|
||||
name: string // database name, same with tag name
|
||||
color: string
|
||||
activeViewId: string
|
||||
viewIds: string[]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user