feat: can create database

This commit is contained in:
0xzion
2023-11-04 22:09:01 +08:00
parent fe1c701958
commit 8b05bbdeda
20 changed files with 292 additions and 134 deletions

View File

@@ -33,6 +33,7 @@
"@penx/icons": "workspace:*",
"@penx/local-db": "workspace:*",
"@penx/shared": "workspace:*",
"@penx/types": "workspace:*",
"@penx/store": "workspace:*",
"@udecode/plate-common": "^24.4.0",
"jotai": "^2.4.2",

View File

@@ -5,6 +5,7 @@ import { ListsEditor } from 'slate-lists'
import { TElement, useEditorStatic } from '@penx/editor-common'
import { selectEditor } from '@penx/editor-transforms'
import { useExtensionStore } from '@penx/hooks'
import { INode, NodeType } from '@penx/types'
import { isBlockSelector } from '../isBlockSelector'
import { useKeyDownList } from '../useKeyDownList'
import { BlockSelectorItem } from './BlockSelectorItem'
@@ -36,8 +37,9 @@ export const BlockSelectorContent = ({ close, element }: Props) => {
* TODO: need refactor
*/
const selectType = useCallback(
(elementType: any) => {
async (elementType: any) => {
const elementInfo = extensionStore.elementMaps[elementType]
const { slashCommand } = elementInfo
if (!elementInfo) return // TODO
close()
@@ -57,11 +59,7 @@ export const BlockSelectorContent = ({ close, element }: Props) => {
*/
Transforms.removeNodes(editor, { at })
Transforms.insertNodes(
editor,
elementInfo.slashCommand?.defaultNode as any,
{ at },
)
Transforms.insertNodes(editor, slashCommand?.defaultNode as any, { at })
Transforms.select(editor, Editor.start(editor, at))
@@ -75,14 +73,19 @@ export const BlockSelectorContent = ({ close, element }: Props) => {
if (elementInfo.isVoid) {
Transforms.removeNodes(editor, { at })
Transforms.insertNodes(
editor,
{
type: elementType,
children: [{ text: '' }],
} as TElement,
{ at },
)
const node: INode = await slashCommand?.beforeInvokeCommand?.(editor)
const props = {
type: elementType,
children: [{ text: '' }],
} as TElement & { [key: string]: any }
if (node.type === NodeType.DATABASE) {
props.id = node.id
props.databaseId = node.id
}
Transforms.insertNodes(editor, props, { at })
const next = Path.next(Path.parent(at))
@@ -107,7 +110,7 @@ export const BlockSelectorContent = ({ close, element }: Props) => {
editor,
{
type: elementType,
...elementInfo.slashCommand?.defaultNode,
...slashCommand?.defaultNode,
} as TElement,
{
// mode: 'lowest',

View File

@@ -1,10 +1,10 @@
import { Element } from 'slate'
import { getEmptyRowNode } from './getEmptyRowNode'
import { ELEMENT_TABLE } from './types'
import { ELEMENT_DATABASE } from './types'
export function getEmptyTableNode(rowCount = 2, columnCount = 2): Element {
return {
type: ELEMENT_TABLE,
type: ELEMENT_DATABASE,
colWidths: Array(columnCount).fill(120),
children: Array(rowCount)
.fill({})

View File

@@ -1,14 +1,14 @@
import {
ELEMENT_TABLE,
DatabaseElement,
ELEMENT_DATABASE,
ELEMENT_TD,
ELEMENT_TR,
TableCellElement,
TableElement,
TableRowElement,
} from './types'
export function isTable(node: any): node is TableElement {
return node?.type === ELEMENT_TABLE
export function isTable(node: any): node is DatabaseElement {
return node?.type === ELEMENT_DATABASE
}
export function isTableRow(node: any): node is TableRowElement {

View File

@@ -1,8 +1,9 @@
import { TableIcon } from 'lucide-react'
import { ExtensionContext } from '@penx/extension-typings'
import { db } from '@penx/local-db'
import { getEmptyTableNode } from './getEmptyTableNode'
import { ELEMENT_TABLE, ELEMENT_TD, ELEMENT_TR } from './types'
import { Table } from './ui/Table/Table'
import { ELEMENT_DATABASE, ELEMENT_TD, ELEMENT_TR } from './types'
import { Database } from './ui/Table/Database'
import { TableCell } from './ui/TableCell'
import { TableRow } from './ui/TableRow'
import { withTable } from './withTable'
@@ -12,23 +13,18 @@ export function activate(ctx: ExtensionContext) {
with: withTable,
elements: [
{
shouldNested: true,
type: ELEMENT_TABLE,
component: Table,
isVoid: true,
type: ELEMENT_DATABASE,
component: Database,
slashCommand: {
name: 'Database',
icon: TableIcon,
defaultNode: getEmptyTableNode(),
async beforeInvokeCommand(editor) {
console.log('xooeeo.......')
return db.createDatabase({})
},
},
},
{
type: ELEMENT_TR,
component: TableRow,
},
{
type: ELEMENT_TD,
component: TableCell,
},
],
})
}

View File

@@ -1,6 +1,6 @@
import { Editor, Node, Path, Transforms } from 'slate'
import { findNodePath } from '@penx/editor-queries'
import { TableCellElement, TableElement, TableRowElement } from '../types'
import { DatabaseElement, TableCellElement, TableRowElement } from '../types'
export class CellNode {
constructor(
@@ -29,7 +29,7 @@ export class CellNode {
}
get tableElement() {
return Node.parent(this.editor, this.path.slice(0, -1)) as TableElement
return Node.parent(this.editor, this.path.slice(0, -1)) as DatabaseElement
}
get isInHeader() {

View File

@@ -3,12 +3,12 @@ import { Editor, Path, Transforms } from 'slate'
import { findNodePath } from '@penx/editor-queries'
import { getEmptyCellNode } from '../getEmptyCellNode'
import { getEmptyRowNode } from '../getEmptyRowNode'
import { TableCellElement, TableElement } from '../types'
import { DatabaseElement, TableCellElement } from '../types'
export class TableNode {
constructor(
private editor: Editor,
private element: TableElement,
private element: DatabaseElement,
) {}
get firstRowElement() {

View File

@@ -1,6 +1,6 @@
import { BaseElement } from 'slate'
export const ELEMENT_TABLE = 'table'
export const ELEMENT_DATABASE = 'database'
export const ELEMENT_TR = 'tr'
export const ELEMENT_TD = 'td'
export const ELEMENT_TH = 'th'
@@ -9,11 +9,12 @@ export interface BaseCustomElement extends BaseElement {
id?: string
}
export interface TableElement extends BaseCustomElement {
type: typeof ELEMENT_TABLE
export interface DatabaseElement extends BaseCustomElement {
type: typeof ELEMENT_DATABASE
colWidths: number[] // table col widths
isHeaderRow: boolean
isHeaderColumn: boolean
databaseId: string
children: TableRowElement[]
}

View File

@@ -2,10 +2,10 @@ import { Box } from '@fower/react'
import { Plus } from 'lucide-react'
import { useSlateStatic } from 'slate-react'
import { TableNode } from '../../nodes/TableNode'
import { TableElement } from '../../types'
import { DatabaseElement } from '../../types'
interface Props {
element: TableElement
element: DatabaseElement
}
export const AddColumnBar = ({ element }: Props) => {

View File

@@ -2,10 +2,10 @@ import { Box } from '@fower/react'
import { Plus } from 'lucide-react'
import { useSlateStatic } from 'slate-react'
import { TableNode } from '../../nodes/TableNode'
import { TableElement } from '../../types'
import { DatabaseElement } from '../../types'
interface Props {
element: TableElement
element: DatabaseElement
}
export const AddRowBar = ({ element }: Props) => {

View File

@@ -0,0 +1,30 @@
import { useCallback, useEffect } from 'react'
import { Box } from '@fower/react'
import { Button } from 'uikit'
import { ElementProps } from '@penx/extension-typings'
import { db } from '@penx/local-db'
import { DatabaseElement } from '../../types'
export const Database = ({
attributes,
element,
children,
}: ElementProps<DatabaseElement>) => {
const initTable = useCallback(async (id: string) => {
const database = await db.getDatabase(id)
console.log('database.....:', database)
}, [])
useEffect(() => {
initTable(element.databaseId)
}, [element, initTable])
return (
<Box flex-1 mb8 mt8 {...attributes}>
<Box relative inlineBlock>
<Button>FOO</Button>
{children}
</Box>
</Box>
)
}

View File

@@ -3,7 +3,7 @@ import { Box, css } from '@fower/react'
import { motion, useMotionValue } from 'framer-motion'
import { Transforms } from 'slate'
import { useSlateStatic } from 'slate-react'
import { TableElement } from '../../types'
import { DatabaseElement } from '../../types'
export function DraglineItem({
id,
@@ -66,7 +66,7 @@ export function DraglineItem({
const offset = info.offset.x
const newWidth = Number((width + offset).toFixed(0))
const widths = colWidths.map((w, i) => (i === index ? newWidth : w))
Transforms.setNodes<TableElement>(
Transforms.setNodes<DatabaseElement>(
editor,
{ colWidths: widths },
{ at: [], match: (n: any) => n.id === id },

View File

@@ -1,7 +1,7 @@
import { TableElement } from '../../types'
import { DatabaseElement } from '../../types'
import { DraglineItem } from './DraglineItem'
export const DraglineList = ({ element }: { element: TableElement }) => {
export const DraglineList = ({ element }: { element: DatabaseElement }) => {
const { colWidths = [] } = element
return (
<>

View File

@@ -1,45 +0,0 @@
import { Box } from '@fower/react'
import { ElementProps } from '@penx/extension-typings'
import { TableElement } from '../../types'
import { AddColumnBar } from './AddColumnBar'
import { AddRowBar } from './AddRowBar'
import { DraglineList } from './DraglineList'
import { TableOptions } from './TableOptions'
export const Table = ({
attributes,
element,
children,
}: ElementProps<TableElement>) => {
return (
<Box flex-1 mb8 mt8>
<Box relative inlineBlock>
<DraglineList element={element} />
<TableOptions element={element} />
<Box
as="table"
id={`table-${element.id}`}
relative
css={{ borderCollapse: 'collapse', border: true }}
>
<Box
as="tbody"
relative
{...attributes}
css={{
'tr:first-child': {
'.tableCellHandler': {
display: 'block',
},
},
}}
>
{children}
</Box>
</Box>
<AddColumnBar element={element} />
<AddRowBar element={element} />
</Box>
</Box>
)
}

View File

@@ -1,7 +1,7 @@
import { Box } from '@fower/react'
import { MoreHorizontal } from 'lucide-react'
import { Transforms } from 'slate'
import { ReactEditor, useSelected, useSlate, useSlateStatic } from 'slate-react'
import { ReactEditor, useSelected } from 'slate-react'
import {
MenuItem,
Popover,
@@ -11,9 +11,9 @@ import {
} from 'uikit'
import { useEditorStatic } from '@penx/editor-common'
import { findNodePath } from '@penx/editor-queries'
import { TableElement } from '../../types'
import { DatabaseElement } from '../../types'
export const TableOptions = ({ element }: { element: TableElement }) => {
export const TableOptions = ({ element }: { element: DatabaseElement }) => {
const editor = useEditorStatic()
const selected = useSelected()
const { isHeaderColumn = false, isHeaderRow = false } = element
@@ -53,7 +53,7 @@ export const TableOptions = ({ element }: { element: TableElement }) => {
toBetween
checked={isHeaderColumn}
onChange={(e) => {
Transforms.setNodes<TableElement>(
Transforms.setNodes<DatabaseElement>(
editor,
{ isHeaderColumn: e.target.checked },
{ at: path },
@@ -72,7 +72,7 @@ export const TableOptions = ({ element }: { element: TableElement }) => {
toBetween
checked={isHeaderRow}
onChange={(e) => {
Transforms.setNodes<TableElement>(
Transforms.setNodes<DatabaseElement>(
editor,
{ isHeaderRow: e.target.checked },
{ at: path },

View File

@@ -92,6 +92,7 @@ export interface BlockElement {
description?: string
icon?: any
defaultNode?: Element
beforeInvokeCommand?: (editor: PenxEditor) => Promise<any>
afterInvokeCommand?: (editor: PenxEditor) => void
}
}

View File

@@ -1,6 +1,6 @@
import { db } from '@penx/local-db'
import { sleep } from '@penx/shared'
import { INode } from '@penx/types'
import { INode, NodeType } from '@penx/types'
const INTERVAL = 10 * 1000
@@ -22,6 +22,19 @@ async function clearDeletedNode() {
}
for (const node of nodes) {
// TODO: need improvement
if (
[
NodeType.DATABASE,
NodeType.COLUMN,
NodeType.ROW,
NodeType.VIEW,
NodeType.CELL,
].includes(node.type)
) {
continue
}
// if (!Reflect.has(node, 'parentId')) continue
if (!node.parentId) continue

View File

@@ -1,7 +1,20 @@
import { nanoid } from 'nanoid'
import { Database } from '@penx/indexeddb'
import { Node, Space } from '@penx/model'
import { IExtension, IFile, INode, ISpace, NodeType } from '@penx/types'
import {
FieldType,
ICellNode,
IColumnNode,
IDatabaseNode,
IExtension,
IFile,
INode,
IRowNode,
ISpace,
IViewNode,
NodeType,
ViewType,
} from '@penx/types'
import { getNewNode } from './getNewNode'
import { getNewSpace } from './getNewSpace'
import { tableSchema } from './table-schema'
@@ -207,13 +220,15 @@ class DB {
return inboxNode
}
createNode = async (node: Partial<INode> & { spaceId: string }) => {
createNode = async <T = INode>(
node: Partial<T> & { spaceId: string },
): Promise<T> => {
const newNode = await this.node.insert({
...getNewNode({ spaceId: node.spaceId! }),
...node,
})
return newNode
return newNode as T
}
createTextNode = async (spaceId: string, text: string) => {
@@ -325,8 +340,161 @@ class DB {
return newNode
}
createDatabase = async () => {
//
createDatabase = async (data: Partial<INode>) => {
// const { id = '' } = data
const space = await this.getActiveSpace()
const database = await this.createNode<IDatabaseNode>({
// id,
...data,
spaceId: space.id,
type: NodeType.DATABASE,
})
console.log('create database....')
// Create view
const view = await this.createNode<IViewNode>({
spaceId: space.id,
databaseId: database.id,
parentId: database.id,
type: NodeType.VIEW,
props: {
name: 'Table',
type: ViewType.Grid,
},
})
const [columns, rows] = await Promise.all([
this.initColumns(space.id, database.id),
this.initRows(space.id, database.id),
])
await this.initCells(space.id, database.id, columns, rows)
return database
}
initColumns = async (spaceId: string, databaseId: string) => {
return Promise.all([
this.createNode<IColumnNode>({
spaceId,
parentId: databaseId,
databaseId,
type: NodeType.COLUMN,
props: {
name: 'Column 1',
description: '',
fieldType: FieldType.Text,
isPrimary: true,
config: {},
},
}),
this.createNode<IColumnNode>({
spaceId,
databaseId,
parentId: databaseId,
type: NodeType.COLUMN,
props: {
name: 'Column 2',
description: '',
fieldType: FieldType.Text,
isPrimary: false,
config: {},
},
}),
])
}
initRows = async (spaceId: string, databaseId: string) => {
return Promise.all([
this.createNode<IRowNode>({
spaceId,
databaseId,
parentId: databaseId,
type: NodeType.ROW,
props: {},
}),
this.createNode<IRowNode>({
spaceId,
parentId: databaseId,
type: NodeType.ROW,
databaseId,
props: {},
}),
])
}
initCells = async (
spaceId: string,
databaseId: string,
columns: IColumnNode[],
rows: IRowNode[],
) => {
const cellNodes = rows.reduce<ICellNode[]>((result, row) => {
const cells: ICellNode[] = columns.map(
(column) =>
({
spaceId,
databaseId,
parentId: databaseId,
type: NodeType.CELL,
props: {
columnId: column.id,
rowId: row.id,
fieldType: column.props.fieldType,
options: [],
data: '',
},
}) as ICellNode,
)
return [...result, ...cells]
}, [])
for (const node of cellNodes) {
await this.createNode(node)
}
}
getDatabase = async (id: string) => {
const space = await this.getActiveSpace()
const database = await this.getNode(id)
const columns = await this.node.select({
where: {
type: NodeType.COLUMN,
spaceId: space.id,
databaseId: id,
},
})
const rows = await this.node.select({
where: {
type: NodeType.ROW,
spaceId: space.id,
databaseId: id,
},
})
const views = await this.node.select({
where: {
type: NodeType.VIEW,
spaceId: space.id,
databaseId: id,
},
})
const cells = await this.node.select({
where: {
type: NodeType.CELL,
spaceId: space.id,
databaseId: id,
},
})
return {
database,
views,
columns,
rows,
cells,
}
}
}

View File

@@ -10,7 +10,7 @@ export enum NodeType {
// Database
DATABASE = 'DATABASE',
CELL = 'CEL',
CELL = 'CELL',
ROW = 'ROW',
COLUMN = 'COLUMN',
VIEW = 'VIEW',
@@ -24,6 +24,8 @@ export interface INode {
spaceId: string
databaseId?: string
type: NodeType
element: any
@@ -56,10 +58,14 @@ export enum FieldType {
LastUpdatedBy = 'LastUpdatedBy',
}
export interface IDatabaseNode extends INode {
type: NodeType.DATABASE
}
export interface IColumnNode extends INode {
parentId: string // should be database id
type: NodeType.COLUMN
props: {
databaseId: string
name: string
description: string
fieldType: FieldType
@@ -70,34 +76,23 @@ export interface IColumnNode extends INode {
export interface IRowNode extends INode {
parentId: string // should be database id
props: {
databaseId: string
}
type: NodeType.ROW
props: {}
}
export interface ICellNode extends INode {
parentId: string // should be database id
type: NodeType.CELL
props: {
databaseId: string
columnId: string
rowId: string
fieldType: FieldType
// options: Option[]
options: any
data: any
}
}
export interface ICellNode extends INode {
parentId: string // should be database id
props: {
databaseId: string
columnId: string
rowId: string
fieldType: FieldType
// options: Option[]
options: any
}
}
export enum ViewType {
Grid = 'Grid',
Calendar = 'Calendar',
@@ -105,28 +100,20 @@ export enum ViewType {
Kanban = 'Kanban',
}
export enum LeadingType {
Short = 'Short',
Medium = 'Medium',
Tall = 'Tall',
ExtraTall = 'ExtraTall',
}
export interface IViewNode extends INode {
parentId: string // should be database id
type: NodeType.VIEW
props: {
databaseId: string
name: string
type: ViewType
stackedColumnId: string
leading: LeadingType
// stackedColumnId: string
}
}
export interface IFilterNode extends INode {
parentId: string // should be database id
type: NodeType.FILTER
props: {
databaseId: string
columnId: string
viewId: string
}

3
pnpm-lock.yaml generated
View File

@@ -989,6 +989,9 @@ importers:
'@penx/store':
specifier: workspace:*
version: link:../../packages/store
'@penx/types':
specifier: workspace:*
version: link:../../packages/types
'@udecode/plate-common':
specifier: ^24.4.0
version: 24.4.0(@babel/core@7.22.17)(@types/react@18.2.22)(immer@10.0.2)(react-dom@18.2.0)(react@18.2.0)(scheduler@0.23.0)(slate-history@0.93.0)(slate-hyperscript@0.77.0)(slate-react@0.98.4)(slate@0.94.1)