diff --git a/extensions/block-selector/package.json b/extensions/block-selector/package.json index 9b530208..25b5a502 100644 --- a/extensions/block-selector/package.json +++ b/extensions/block-selector/package.json @@ -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", diff --git a/extensions/block-selector/src/ui/BlockSelectorContent.tsx b/extensions/block-selector/src/ui/BlockSelectorContent.tsx index ca68e7b0..a6fd0491 100644 --- a/extensions/block-selector/src/ui/BlockSelectorContent.tsx +++ b/extensions/block-selector/src/ui/BlockSelectorContent.tsx @@ -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', diff --git a/extensions/database/src/getEmptyTableNode.ts b/extensions/database/src/getEmptyTableNode.ts index fae592c4..d97b8f55 100644 --- a/extensions/database/src/getEmptyTableNode.ts +++ b/extensions/database/src/getEmptyTableNode.ts @@ -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({}) diff --git a/extensions/database/src/guard.ts b/extensions/database/src/guard.ts index 52d44879..c38b4e51 100644 --- a/extensions/database/src/guard.ts +++ b/extensions/database/src/guard.ts @@ -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 { diff --git a/extensions/database/src/index.ts b/extensions/database/src/index.ts index 770889c7..58276766 100644 --- a/extensions/database/src/index.ts +++ b/extensions/database/src/index.ts @@ -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, - }, ], }) } diff --git a/extensions/database/src/nodes/CellNode.ts b/extensions/database/src/nodes/CellNode.ts index 0af6bab3..b8a4ad22 100644 --- a/extensions/database/src/nodes/CellNode.ts +++ b/extensions/database/src/nodes/CellNode.ts @@ -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() { diff --git a/extensions/database/src/nodes/TableNode.ts b/extensions/database/src/nodes/TableNode.ts index 7d027da4..bb6864a5 100644 --- a/extensions/database/src/nodes/TableNode.ts +++ b/extensions/database/src/nodes/TableNode.ts @@ -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() { diff --git a/extensions/database/src/types.ts b/extensions/database/src/types.ts index fc004072..3ecfc459 100644 --- a/extensions/database/src/types.ts +++ b/extensions/database/src/types.ts @@ -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[] } diff --git a/extensions/database/src/ui/Table/AddColumnBar.tsx b/extensions/database/src/ui/Table/AddColumnBar.tsx index 8b4949bc..70105721 100644 --- a/extensions/database/src/ui/Table/AddColumnBar.tsx +++ b/extensions/database/src/ui/Table/AddColumnBar.tsx @@ -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) => { diff --git a/extensions/database/src/ui/Table/AddRowBar.tsx b/extensions/database/src/ui/Table/AddRowBar.tsx index ab056cf8..b5046528 100644 --- a/extensions/database/src/ui/Table/AddRowBar.tsx +++ b/extensions/database/src/ui/Table/AddRowBar.tsx @@ -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) => { diff --git a/extensions/database/src/ui/Table/Database.tsx b/extensions/database/src/ui/Table/Database.tsx new file mode 100644 index 00000000..fafeba62 --- /dev/null +++ b/extensions/database/src/ui/Table/Database.tsx @@ -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) => { + const initTable = useCallback(async (id: string) => { + const database = await db.getDatabase(id) + console.log('database.....:', database) + }, []) + + useEffect(() => { + initTable(element.databaseId) + }, [element, initTable]) + + return ( + + + + {children} + + + ) +} diff --git a/extensions/database/src/ui/Table/DraglineItem.tsx b/extensions/database/src/ui/Table/DraglineItem.tsx index 464d0461..6aafb38b 100644 --- a/extensions/database/src/ui/Table/DraglineItem.tsx +++ b/extensions/database/src/ui/Table/DraglineItem.tsx @@ -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( + Transforms.setNodes( editor, { colWidths: widths }, { at: [], match: (n: any) => n.id === id }, diff --git a/extensions/database/src/ui/Table/DraglineList.tsx b/extensions/database/src/ui/Table/DraglineList.tsx index e17e9647..4bcca57f 100644 --- a/extensions/database/src/ui/Table/DraglineList.tsx +++ b/extensions/database/src/ui/Table/DraglineList.tsx @@ -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 ( <> diff --git a/extensions/database/src/ui/Table/Table.tsx b/extensions/database/src/ui/Table/Table.tsx deleted file mode 100644 index 6b25af3a..00000000 --- a/extensions/database/src/ui/Table/Table.tsx +++ /dev/null @@ -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) => { - return ( - - - - - - - {children} - - - - - - - ) -} diff --git a/extensions/database/src/ui/Table/TableOptions.tsx b/extensions/database/src/ui/Table/TableOptions.tsx index c14f795e..3a8d7cf1 100644 --- a/extensions/database/src/ui/Table/TableOptions.tsx +++ b/extensions/database/src/ui/Table/TableOptions.tsx @@ -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( + Transforms.setNodes( editor, { isHeaderColumn: e.target.checked }, { at: path }, @@ -72,7 +72,7 @@ export const TableOptions = ({ element }: { element: TableElement }) => { toBetween checked={isHeaderRow} onChange={(e) => { - Transforms.setNodes( + Transforms.setNodes( editor, { isHeaderRow: e.target.checked }, { at: path }, diff --git a/packages/extension-typings/src/index.ts b/packages/extension-typings/src/index.ts index 46a9c4bf..a29edceb 100644 --- a/packages/extension-typings/src/index.ts +++ b/packages/extension-typings/src/index.ts @@ -92,6 +92,7 @@ export interface BlockElement { description?: string icon?: any defaultNode?: Element + beforeInvokeCommand?: (editor: PenxEditor) => Promise afterInvokeCommand?: (editor: PenxEditor) => void } } diff --git a/packages/hooks/src/useWorkers/clearNodes.ts b/packages/hooks/src/useWorkers/clearNodes.ts index 3f4797f4..77a83bff 100644 --- a/packages/hooks/src/useWorkers/clearNodes.ts +++ b/packages/hooks/src/useWorkers/clearNodes.ts @@ -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 diff --git a/packages/local-db/src/db.ts b/packages/local-db/src/db.ts index 83baa5d8..74dbdd73 100644 --- a/packages/local-db/src/db.ts +++ b/packages/local-db/src/db.ts @@ -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 & { spaceId: string }) => { + createNode = async ( + node: Partial & { spaceId: string }, + ): Promise => { 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) => { + // const { id = '' } = data + const space = await this.getActiveSpace() + const database = await this.createNode({ + // id, + ...data, + spaceId: space.id, + type: NodeType.DATABASE, + }) + + console.log('create database....') + + // Create view + const view = await this.createNode({ + 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({ + spaceId, + parentId: databaseId, + databaseId, + type: NodeType.COLUMN, + props: { + name: 'Column 1', + description: '', + fieldType: FieldType.Text, + isPrimary: true, + config: {}, + }, + }), + this.createNode({ + 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({ + spaceId, + databaseId, + parentId: databaseId, + type: NodeType.ROW, + props: {}, + }), + this.createNode({ + spaceId, + parentId: databaseId, + type: NodeType.ROW, + databaseId, + props: {}, + }), + ]) + } + + initCells = async ( + spaceId: string, + databaseId: string, + columns: IColumnNode[], + rows: IRowNode[], + ) => { + const cellNodes = rows.reduce((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, + } } } diff --git a/packages/types/src/interfaces/INode.ts b/packages/types/src/interfaces/INode.ts index fd930952..54f89689 100644 --- a/packages/types/src/interfaces/INode.ts +++ b/packages/types/src/interfaces/INode.ts @@ -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 } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da734f90..d5366350 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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)