From a28f4ed908516b378bb56fe63fc0ee565830ce39 Mon Sep 17 00:00:00 2001 From: 0xzion <0xzion.penx@gmail.com> Date: Wed, 1 May 2024 16:42:01 +0800 Subject: [PATCH] refactor: refactor tag ui --- .../database/src/ui/tag/fields/CellProps.ts | 8 - .../database/src/ui/tag/fields/CreatedAt.tsx | 17 -- .../database/src/ui/tag/fields/Date.tsx | 50 ---- .../src/ui/tag/fields/MultipleSelect.tsx | 214 ------------------ .../database/src/ui/tag/fields/Number.tsx | 39 ---- .../database/src/ui/tag/fields/Password.tsx | 64 ------ .../src/ui/tag/fields/SingleSelect.tsx | 194 ---------------- .../database/src/ui/tag/fields/Text.tsx | 46 ---- .../database/src/ui/tag/fields/UpdatedAt.tsx | 17 -- .../database/src/ui/tag/fields/index.tsx | 60 ----- packages/cell-fields/src/fields/CellProps.ts | 2 + .../cell-fields/src/fields/MultipleSelect.tsx | 2 +- .../cell-fields/src/fields/SingleSelect.tsx | 13 +- packages/database-ui/package.json | 2 + packages/database-ui/src/Cell/CellProps.ts | 10 - packages/database-ui/src/Cell/CreatedAt.tsx | 17 -- packages/database-ui/src/Cell/Date.tsx | 48 ---- packages/database-ui/src/Cell/File.tsx | 64 ------ .../database-ui/src/Cell/LastUpdatedBy.tsx | 9 - .../database-ui/src/Cell/MultipleSelect.tsx | 203 ----------------- packages/database-ui/src/Cell/Number.tsx | 58 ----- packages/database-ui/src/Cell/Password.tsx | 74 ------ packages/database-ui/src/Cell/PrimaryCell.tsx | 2 +- .../database-ui/src/Cell/SingleSelect.tsx | 194 ---------------- packages/database-ui/src/Cell/Text.tsx | 64 ------ packages/database-ui/src/Cell/UpdatedAt.tsx | 17 -- packages/database-ui/src/Cell/index.tsx | 78 ------- packages/database-ui/src/RowForm.tsx | 2 +- packages/database-ui/src/tag/TagForm.tsx | 2 +- packages/database-ui/src/tag/TagSelector.tsx | 130 ----------- .../database-ui/src/tag/TagSelectorItem.tsx | 53 ----- .../database-ui/src/tag/fields/CellProps.ts | 8 - .../database-ui/src/tag/fields/CreatedAt.tsx | 17 -- packages/database-ui/src/tag/fields/Date.tsx | 50 ---- .../src/tag/fields/MultipleSelect.tsx | 214 ------------------ .../database-ui/src/tag/fields/Number.tsx | 39 ---- .../database-ui/src/tag/fields/Password.tsx | 64 ------ .../src/tag/fields/SingleSelect.tsx | 194 ---------------- packages/database-ui/src/tag/fields/Text.tsx | 46 ---- .../database-ui/src/tag/fields/UpdatedAt.tsx | 17 -- packages/database-ui/src/tag/fields/index.tsx | 60 ----- packages/database-ui/src/views/ListView.tsx | 12 - .../TableView/cells-common/select-cell.tsx | 2 +- .../TableView/cells/multiple-select-cell.tsx | 7 +- .../TableView/cells/single-select-cell.tsx | 10 +- .../src/shared => widget/src}/OptionTag.tsx | 0 packages/widget/src/index.ts | 1 + pnpm-lock.yaml | 6 + 48 files changed, 42 insertions(+), 2458 deletions(-) delete mode 100644 extensions/database/src/ui/tag/fields/CellProps.ts delete mode 100644 extensions/database/src/ui/tag/fields/CreatedAt.tsx delete mode 100644 extensions/database/src/ui/tag/fields/Date.tsx delete mode 100644 extensions/database/src/ui/tag/fields/MultipleSelect.tsx delete mode 100644 extensions/database/src/ui/tag/fields/Number.tsx delete mode 100644 extensions/database/src/ui/tag/fields/Password.tsx delete mode 100644 extensions/database/src/ui/tag/fields/SingleSelect.tsx delete mode 100644 extensions/database/src/ui/tag/fields/Text.tsx delete mode 100644 extensions/database/src/ui/tag/fields/UpdatedAt.tsx delete mode 100644 extensions/database/src/ui/tag/fields/index.tsx delete mode 100644 packages/database-ui/src/Cell/CellProps.ts delete mode 100644 packages/database-ui/src/Cell/CreatedAt.tsx delete mode 100644 packages/database-ui/src/Cell/Date.tsx delete mode 100644 packages/database-ui/src/Cell/File.tsx delete mode 100644 packages/database-ui/src/Cell/LastUpdatedBy.tsx delete mode 100644 packages/database-ui/src/Cell/MultipleSelect.tsx delete mode 100644 packages/database-ui/src/Cell/Number.tsx delete mode 100644 packages/database-ui/src/Cell/Password.tsx delete mode 100644 packages/database-ui/src/Cell/SingleSelect.tsx delete mode 100644 packages/database-ui/src/Cell/Text.tsx delete mode 100644 packages/database-ui/src/Cell/UpdatedAt.tsx delete mode 100644 packages/database-ui/src/Cell/index.tsx delete mode 100644 packages/database-ui/src/tag/TagSelector.tsx delete mode 100644 packages/database-ui/src/tag/TagSelectorItem.tsx delete mode 100644 packages/database-ui/src/tag/fields/CellProps.ts delete mode 100644 packages/database-ui/src/tag/fields/CreatedAt.tsx delete mode 100644 packages/database-ui/src/tag/fields/Date.tsx delete mode 100644 packages/database-ui/src/tag/fields/MultipleSelect.tsx delete mode 100644 packages/database-ui/src/tag/fields/Number.tsx delete mode 100644 packages/database-ui/src/tag/fields/Password.tsx delete mode 100644 packages/database-ui/src/tag/fields/SingleSelect.tsx delete mode 100644 packages/database-ui/src/tag/fields/Text.tsx delete mode 100644 packages/database-ui/src/tag/fields/UpdatedAt.tsx delete mode 100644 packages/database-ui/src/tag/fields/index.tsx rename packages/{database-ui/src/shared => widget/src}/OptionTag.tsx (100%) diff --git a/extensions/database/src/ui/tag/fields/CellProps.ts b/extensions/database/src/ui/tag/fields/CellProps.ts deleted file mode 100644 index f0455bd61..000000000 --- a/extensions/database/src/ui/tag/fields/CellProps.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ICellNode, IColumnNode } from '@penx/model-types' - -export interface CellProps { - index: number - cell: ICellNode - column: IColumnNode - updateCell: (data: any) => void -} diff --git a/extensions/database/src/ui/tag/fields/CreatedAt.tsx b/extensions/database/src/ui/tag/fields/CreatedAt.tsx deleted file mode 100644 index 0feb9b20d..000000000 --- a/extensions/database/src/ui/tag/fields/CreatedAt.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { FC, memo } from 'react' -import { Box } from '@fower/react' -import { format } from 'date-fns' -import { useDatabaseContext } from '@penx/database-context' -import { CellProps } from './CellProps' - -export const CreatedAtCell: FC = memo(function CreatedAtCell(props) { - const { cell } = props - const { rows } = useDatabaseContext() - const row = rows.find((r) => r.id === cell.props.rowId)! - - return ( - - {format(new Date(row.createdAt), 'yyyy-MM-dd HH:mm:ss')} - - ) -}) diff --git a/extensions/database/src/ui/tag/fields/Date.tsx b/extensions/database/src/ui/tag/fields/Date.tsx deleted file mode 100644 index e4af95cfc..000000000 --- a/extensions/database/src/ui/tag/fields/Date.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { FC, memo, useRef, useState } from 'react' -import DatePicker from 'react-datepicker' -import { Box } from '@fower/react' -import { CellProps } from './CellProps' - -export const DateCell: FC = memo(function DateCell(props) { - const { cell, updateCell } = props - const ref = useRef(null) - const [startDate, setStartDate] = useState(cell.props.data ?? null) - - return ( - - { - setStartDate(date!) - updateCell(date!) - }} - /> - - ) -}) diff --git a/extensions/database/src/ui/tag/fields/MultipleSelect.tsx b/extensions/database/src/ui/tag/fields/MultipleSelect.tsx deleted file mode 100644 index b9bc5e5e7..000000000 --- a/extensions/database/src/ui/tag/fields/MultipleSelect.tsx +++ /dev/null @@ -1,214 +0,0 @@ -import React, { - Dispatch, - FC, - memo, - SetStateAction, - useEffect, - useState, -} from 'react' -import isEqual from 'react-fast-compare' -import { Box } from '@fower/react' -import { useCombobox, useMultipleSelection, useSelect } from 'downshift' -import { - Input, - Popover, - PopoverContent, - PopoverTrigger, - usePopoverContext, -} from 'uikit' -import { useDatabaseContext } from '@penx/database-context' -import { IOptionNode } from '@penx/model-types' -import { OptionTag } from '../../shared/OptionTag' -import { CellProps } from './CellProps' - -export const MultipleSelectCell: FC = memo( - function MultipleSelectCell(props) { - const { cell } = props - const { options, deleteCellOption } = useDatabaseContext() - const [value, setValue] = useState( - Array.isArray(cell.props.data) ? cell.props.data : [], - ) - - const items = (Array.isArray(value) ? value : []) - .map((item) => options.find((o) => o.id === item)!) - .filter((o) => !!o) - - useEffect(() => { - if (isEqual(value, cell.props.data)) return - setValue(cell.props.data) - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [cell.props.data]) - - return ( - - - - {items.map((option) => ( - { - await deleteCellOption(cell.id, option.id) - }} - /> - ))} - - - - - - - ) - }, -) - -function Combobox( - props: CellProps & { - value: string[] - setValue: Dispatch> - }, -) { - const { cell, column, updateCell } = props - const { close } = usePopoverContext() - const { addOption, options } = useDatabaseContext() - const optionIds = column.props.optionIds || [] - const columnOptions = optionIds.map((o) => options.find((o2) => o2.id === o)!) - - function getOptionsFilter(inputValue: string) { - const lowerCasedInputValue = inputValue.toLowerCase() - return (item: IOptionNode) => { - return ( - !inputValue || - item.props.name.toLowerCase().includes(lowerCasedInputValue) - ) - } - } - - const [items, setItems] = useState(columnOptions) - const [inputValue, setInputValue] = useState('') - const { - isOpen, - getToggleButtonProps, - getLabelProps, - getMenuProps, - getInputProps, - highlightedIndex, - getItemProps, - selectedItem, - } = useCombobox({ - inputValue: inputValue, - onInputValueChange({ inputValue = '' }) { - setInputValue(inputValue!) - const find = columnOptions.find((o) => o.props.name === inputValue) - - const filteredItems = columnOptions.filter(getOptionsFilter(inputValue)) - - if (!find && inputValue) { - filteredItems.push({ - id: 'CREATE', - props: { name: inputValue }, - } as IOptionNode) - } - - setItems(filteredItems) - }, - items, - itemToString(item) { - return item ? item.props.name : '' - }, - async onSelectedItemChange({ selectedItem }) { - let id = selectedItem?.id as string - - if (selectedItem?.id === 'CREATE') { - const newOption = await addOption( - cell.props.columnId, - selectedItem.props.name, - ) - id = newOption.id - } - - setTimeout(() => { - const oldIds = cell.props.data || [] - const existed = oldIds.includes(id) - - if (!existed) { - const newIds = [...oldIds, id] - updateCell(newIds) - props.setValue(newIds) - } - - setInputValue('') - close() - }, 10) - }, - }) - - return ( - - { - setInputValue(e.target.value) - }, - })} - /> - - - {!items.length && ( - - No options - - )} - - {items.map((item, index) => ( - - {item.id === 'CREATE' && Create} - - - - ))} - - - - ) -} diff --git a/extensions/database/src/ui/tag/fields/Number.tsx b/extensions/database/src/ui/tag/fields/Number.tsx deleted file mode 100644 index 7cf1ed757..000000000 --- a/extensions/database/src/ui/tag/fields/Number.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { - ChangeEvent, - FC, - memo, - useEffect, - useRef, - useState, -} from 'react' -import { useDebouncedCallback } from 'use-debounce' -import { Input } from 'uikit' -import { matchNumber } from '@penx/shared' -import { CellProps } from './CellProps' - -export const NumberCell: FC = memo(function NumberCell(props) { - const { cell, updateCell, index } = props - const [value, setValue] = useState(cell.props.data || '') - const ref = useRef(null) - - useEffect(() => { - if (cell.props.ref) return - setValue(cell.props.data) - }, [cell]) - - const debouncedUpdate = useDebouncedCallback(async (value: any) => { - updateCell(Number(value)) - }, 500) - - const onChange = (e: ChangeEvent) => { - const data = e.target.value - if (!matchNumber(data) && data.length) { - // console.log('not a number', data) - return - } - setValue(data) - debouncedUpdate(data) - } - - return -}) diff --git a/extensions/database/src/ui/tag/fields/Password.tsx b/extensions/database/src/ui/tag/fields/Password.tsx deleted file mode 100644 index 3c57e0845..000000000 --- a/extensions/database/src/ui/tag/fields/Password.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { ChangeEvent, FC, memo, useEffect, useState } from 'react' -import { Box } from '@fower/react' -import { Copy, Eye, EyeOff } from 'lucide-react' -import { useDebouncedCallback } from 'use-debounce' -import { Input, InputElement, InputGroup, toast } from 'uikit' -import { useCopyToClipboard } from '@penx/shared' -import { CellProps } from './CellProps' - -export const PasswordCell: FC = memo(function PasswordCell(props) { - const { cell, updateCell } = props - const [value, setValue] = useState(cell.props.data || '') - - const [visible, setVisible] = useState(false) - const { copy } = useCopyToClipboard() - - useEffect(() => { - if (cell.props.ref) return - setValue(cell.props.data) - }, [cell]) - - const debouncedUpdate = useDebouncedCallback(async (value: any) => { - updateCell(value) - }, 500) - - const onChange = (e: ChangeEvent) => { - const data = e.target.value - setValue(data) - debouncedUpdate(data) - } - - return ( - - - - - { - copy(cell.props.data) - toast.info('Copied to clipboard') - }} - > - - - setVisible(!visible)} - > - {visible && } - {!visible && } - - - - - ) -}) diff --git a/extensions/database/src/ui/tag/fields/SingleSelect.tsx b/extensions/database/src/ui/tag/fields/SingleSelect.tsx deleted file mode 100644 index 05f855d4f..000000000 --- a/extensions/database/src/ui/tag/fields/SingleSelect.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import React, { - Dispatch, - FC, - memo, - SetStateAction, - useEffect, - useState, -} from 'react' -import isEqual from 'react-fast-compare' -import { Box } from '@fower/react' -import { useCombobox, useMultipleSelection, useSelect } from 'downshift' -import { - Input, - Popover, - PopoverContent, - PopoverTrigger, - usePopoverContext, -} from 'uikit' -import { useDatabaseContext } from '@penx/database-context' -import { IOptionNode } from '@penx/model-types' -import { OptionTag } from '../../shared/OptionTag' -import { CellProps } from './CellProps' - -export const SingleSelectCell: FC = memo( - function SingleSelectCell(props) { - const { cell } = props - const { options, deleteCellOption } = useDatabaseContext() - const [value, setValue] = useState( - Array.isArray(cell.props.data) ? cell.props.data : [], - ) - - const items = (Array.isArray(value) ? value : []) - .map((item) => options.find((o) => o.id === item)!) - .filter((o) => !!o) - - useEffect(() => { - if (isEqual(value, cell.props.data)) return - setValue(cell.props.data) - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [cell.props.data]) - - return ( - - - - {items.map((option) => ( - { - await deleteCellOption(cell.id, option.id) - }} - /> - ))} - - - - - - - ) - }, -) - -function Combobox( - props: CellProps & { - setValue: Dispatch> - }, -) { - const { cell, column, updateCell } = props - const { close } = usePopoverContext() - const { addOption, options } = useDatabaseContext() - const optionIds = column.props.optionIds || [] - const columnOptions = optionIds.map((o) => options.find((o2) => o2.id === o)!) - - function getOptionsFilter(inputValue: string) { - const lowerCasedInputValue = inputValue.toLowerCase() - return (item: IOptionNode) => { - return ( - !inputValue || - item.props.name.toLowerCase().includes(lowerCasedInputValue) - ) - } - } - - const [items, setItems] = useState(columnOptions) - const [inputValue, setInputValue] = useState('') - const { - isOpen, - getToggleButtonProps, - getLabelProps, - getMenuProps, - getInputProps, - highlightedIndex, - getItemProps, - selectedItem, - } = useCombobox({ - inputValue: inputValue, - onInputValueChange({ inputValue = '' }) { - setInputValue(inputValue!) - const find = columnOptions.find((o) => o.props.name === inputValue) - - const filteredItems = columnOptions.filter(getOptionsFilter(inputValue)) - - if (!find && inputValue) { - filteredItems.push({ - id: 'CREATE', - props: { name: inputValue }, - } as IOptionNode) - } - - setItems(filteredItems) - }, - items, - itemToString(item) { - return item ? item.props.name : '' - }, - async onSelectedItemChange({ selectedItem }) { - let id = selectedItem?.id as string - if (selectedItem?.id === 'CREATE') { - const newOption = await addOption( - cell.props.columnId, - selectedItem.props.name, - ) - id = newOption.id - } - - setTimeout(() => { - updateCell([id]) - props.setValue([id]) - setInputValue('') - close() - }, 10) - }, - }) - - return ( - - { - setInputValue(e.target.value) - }, - })} - /> - - - {!items.length && ( - - No options - - )} - - {items.map((item, index) => ( - - {item.id === 'CREATE' && Create} - - - - ))} - - - - ) -} diff --git a/extensions/database/src/ui/tag/fields/Text.tsx b/extensions/database/src/ui/tag/fields/Text.tsx deleted file mode 100644 index 7175318ce..000000000 --- a/extensions/database/src/ui/tag/fields/Text.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { ChangeEvent, FC, memo, useEffect, useState } from 'react' -import TextareaAutosize from 'react-textarea-autosize' -import { Box, css } from '@fower/react' -import { useDebouncedCallback } from 'use-debounce' -import { CellProps } from './CellProps' - -export const TextCell: FC = memo(function TextCell(props) { - const { cell, updateCell } = props - const [value, setValue] = useState(cell.props.data || '') - - useEffect(() => { - setValue(cell.props.data) - }, [cell]) - - const debouncedUpdate = useDebouncedCallback(async (value: any) => { - updateCell(value) - }, 500) - - const onChange = (e: ChangeEvent) => { - const data = e.target.value - - setValue(data) - debouncedUpdate(data) - } - - return ( - - - - ) -}) diff --git a/extensions/database/src/ui/tag/fields/UpdatedAt.tsx b/extensions/database/src/ui/tag/fields/UpdatedAt.tsx deleted file mode 100644 index 7a9c6d8f3..000000000 --- a/extensions/database/src/ui/tag/fields/UpdatedAt.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { FC, memo } from 'react' -import { Box } from '@fower/react' -import { format } from 'date-fns' -import { useDatabaseContext } from '@penx/database-context' -import { CellProps } from './CellProps' - -export const UpdatedAtCell: FC = memo(function UpdatedAtCell(props) { - const { cell } = props - const { rows } = useDatabaseContext() - const row = rows.find((r) => r.id === cell.props.rowId)! - - return ( - - {format(new Date(row.updatedAt), 'yyyy-MM-dd HH:mm:ss')} - - ) -}) diff --git a/extensions/database/src/ui/tag/fields/index.tsx b/extensions/database/src/ui/tag/fields/index.tsx deleted file mode 100644 index 6984685e8..000000000 --- a/extensions/database/src/ui/tag/fields/index.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { memo } from 'react' -import isEqual from 'react-fast-compare' -import { db } from '@penx/local-db' -import { FieldType, ICellNode, IColumnNode } from '@penx/model-types' -import { store } from '@penx/store' -import { CreatedAtCell } from './CreatedAt' -import { DateCell } from './Date' -import { MultipleSelectCell } from './MultipleSelect' -import { NumberCell } from './Number' -import { PasswordCell } from './Password' -import { SingleSelectCell } from './SingleSelect' -import { TextCell } from './Text' -import { UpdatedAtCell } from './UpdatedAt' - -const cellsMap: Record = { - [FieldType.TEXT]: TextCell, - [FieldType.NUMBER]: NumberCell, - [FieldType.PASSWORD]: PasswordCell, - [FieldType.SINGLE_SELECT]: SingleSelectCell, - [FieldType.MULTIPLE_SELECT]: MultipleSelectCell, - [FieldType.DATE]: DateCell, - [FieldType.CREATED_AT]: CreatedAtCell, - [FieldType.UPDATED_AT]: UpdatedAtCell, -} - -interface Props { - index: number - columns: IColumnNode[] - cell: ICellNode -} - -export const CellField = memo( - function TableCell({ columns, cell, index }: Props) { - const { rowId, columnId } = cell.props - const column = columns.find((c) => c.id === columnId)! - const fieldType = column.props.fieldType - const CellComponent = cellsMap[fieldType as FieldType] || TextCell - - async function updateCell(data: any) { - await db.updateNode(cell.id, { - props: { ...cell.props, data }, - }) - - const nodes = await db.listNodesBySpaceId(cell.spaceId) - store.node.setNodes(nodes) - } - - return ( - - ) - }, - (prev, next) => { - return isEqual({ cell: prev.cell }, { cell: next.cell }) - }, -) diff --git a/packages/cell-fields/src/fields/CellProps.ts b/packages/cell-fields/src/fields/CellProps.ts index f0455bd61..89b73fc74 100644 --- a/packages/cell-fields/src/fields/CellProps.ts +++ b/packages/cell-fields/src/fields/CellProps.ts @@ -2,7 +2,9 @@ import { ICellNode, IColumnNode } from '@penx/model-types' export interface CellProps { index: number + selected: boolean cell: ICellNode column: IColumnNode + width: number updateCell: (data: any) => void } diff --git a/packages/cell-fields/src/fields/MultipleSelect.tsx b/packages/cell-fields/src/fields/MultipleSelect.tsx index a2192bc1e..72e268870 100644 --- a/packages/cell-fields/src/fields/MultipleSelect.tsx +++ b/packages/cell-fields/src/fields/MultipleSelect.tsx @@ -18,7 +18,7 @@ import { } from 'uikit' import { useDatabaseContext } from '@penx/database-context' import { IOptionNode } from '@penx/model-types' -import { OptionTag } from '../ui/shared/OptionTag' +import { OptionTag } from '@penx/widget' import { CellProps } from './CellProps' export const MultipleSelectCell: FC = memo( diff --git a/packages/cell-fields/src/fields/SingleSelect.tsx b/packages/cell-fields/src/fields/SingleSelect.tsx index 3a9c15609..c8938be5f 100644 --- a/packages/cell-fields/src/fields/SingleSelect.tsx +++ b/packages/cell-fields/src/fields/SingleSelect.tsx @@ -18,7 +18,7 @@ import { } from 'uikit' import { useDatabaseContext } from '@penx/database-context' import { IOptionNode } from '@penx/model-types' -import { OptionTag } from '../ui/shared/OptionTag' +import { OptionTag } from '@penx/widget' import { CellProps } from './CellProps' export const SingleSelectCell: FC = memo( @@ -43,7 +43,16 @@ export const SingleSelectCell: FC = memo( return ( - + {items.map((option) => ( void -} diff --git a/packages/database-ui/src/Cell/CreatedAt.tsx b/packages/database-ui/src/Cell/CreatedAt.tsx deleted file mode 100644 index 1a6905383..000000000 --- a/packages/database-ui/src/Cell/CreatedAt.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { FC, memo } from 'react' -import { Box } from '@fower/react' -import { format } from 'date-fns' -import { useDatabaseContext } from '@penx/database-context' -import { CellProps } from './CellProps' - -export const CreatedAtCell: FC = memo(function CreatedAtCell(props) { - const { cell } = props - const { rows } = useDatabaseContext() - const row = rows.find((r) => r.id === cell.props.rowId)! - - return ( - - {format(new Date(row.createdAt), 'yyyy-MM-dd HH:mm:ss')} - - ) -}) diff --git a/packages/database-ui/src/Cell/Date.tsx b/packages/database-ui/src/Cell/Date.tsx deleted file mode 100644 index a983ffd43..000000000 --- a/packages/database-ui/src/Cell/Date.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { FC, memo, useRef, useState } from 'react' -import DatePicker from 'react-datepicker' -import { Box } from '@fower/react' -import { CellProps } from './CellProps' - -export const DateCell: FC = memo(function DateCell(props) { - const { cell, updateCell } = props - const ref = useRef(null) - const [startDate, setStartDate] = useState(cell.props.data ?? null) - - return ( - - { - setStartDate(date!) - updateCell(date!) - }} - /> - - ) -}) diff --git a/packages/database-ui/src/Cell/File.tsx b/packages/database-ui/src/Cell/File.tsx deleted file mode 100644 index 6aefb0d9a..000000000 --- a/packages/database-ui/src/Cell/File.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { - ChangeEvent, - FC, - memo, - useEffect, - useRef, - useState, -} from 'react' -import TextareaAutosize from 'react-textarea-autosize' -import { Box, css } from '@fower/react' -import { Maximize2 } from 'lucide-react' -import { useDebouncedCallback } from 'use-debounce' -import { CellProps } from './CellProps' -import { PrimaryCell } from './PrimaryCell' - -export const FileCell: FC = memo(function TextCell(props) { - const { cell, updateCell, selected, width, index } = props - const [value, setValue] = useState(cell.props.data || '') - const ref = useRef(null) - - useEffect(() => { - if (cell.props.ref) return - setValue(cell.props.data) - }, [cell]) - - const debouncedUpdate = useDebouncedCallback(async (value: any) => { - updateCell(value) - }, 500) - - const onChange = (e: ChangeEvent) => { - const data = e.target.value - setValue(data) - debouncedUpdate(data) - } - - if (cell.props.ref) { - return - } - - return ( - - - {selected && ( - - - - )} - - ) -}) diff --git a/packages/database-ui/src/Cell/LastUpdatedBy.tsx b/packages/database-ui/src/Cell/LastUpdatedBy.tsx deleted file mode 100644 index a52244951..000000000 --- a/packages/database-ui/src/Cell/LastUpdatedBy.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React, { FC, memo } from 'react' -import { CellProps } from './CellProps' - -// eslint-disable-next-line react/display-name -export const LastUpdatedByCell: FC = memo((props) => { - const { cell, selected, updateCell } = props - - return
-}) diff --git a/packages/database-ui/src/Cell/MultipleSelect.tsx b/packages/database-ui/src/Cell/MultipleSelect.tsx deleted file mode 100644 index c6c365d6d..000000000 --- a/packages/database-ui/src/Cell/MultipleSelect.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import React, { - Dispatch, - FC, - memo, - SetStateAction, - useEffect, - useState, -} from 'react' -import isEqual from 'react-fast-compare' -import { Box } from '@fower/react' -import { useCombobox, useMultipleSelection, useSelect } from 'downshift' -import { - Input, - Popover, - PopoverContent, - PopoverTrigger, - usePopoverContext, -} from 'uikit' -import { useDatabaseContext } from '@penx/database-context' -import { IOptionNode } from '@penx/model-types' -import { OptionTag } from '../shared/OptionTag' -import { CellProps } from './CellProps' - -export const MultipleSelectCell: FC = memo( - function MultipleSelectCell(props) { - const { cell } = props - const { options, deleteCellOption } = useDatabaseContext() - const [value, setValue] = useState( - Array.isArray(cell.props.data) ? cell.props.data : [], - ) - - const items = (Array.isArray(value) ? value : []) - .map((item) => options.find((o) => o.id === item)!) - .filter((o) => !!o) - - useEffect(() => { - if (isEqual(value, cell.props.data)) return - setValue(cell.props.data) - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [cell.props.data]) - - return ( - - - - {items.map((option) => ( - { - await deleteCellOption(cell.id, option.id) - }} - /> - ))} - - - - - - - ) - }, -) - -function Combobox( - props: CellProps & { - value: string[] - setValue: Dispatch> - }, -) { - const { cell, column, updateCell } = props - const { close } = usePopoverContext() - const { addOption, options } = useDatabaseContext() - const optionIds = column.props.optionIds || [] - const columnOptions = optionIds.map((o) => options.find((o2) => o2.id === o)!) - - function getOptionsFilter(inputValue: string) { - const lowerCasedInputValue = inputValue.toLowerCase() - return (item: IOptionNode) => { - return ( - !inputValue || - item.props.name.toLowerCase().includes(lowerCasedInputValue) - ) - } - } - - const [items, setItems] = useState(columnOptions) - const [inputValue, setInputValue] = useState('') - const { - isOpen, - getToggleButtonProps, - getLabelProps, - getMenuProps, - getInputProps, - highlightedIndex, - getItemProps, - selectedItem, - } = useCombobox({ - inputValue: inputValue, - onInputValueChange({ inputValue = '' }) { - setInputValue(inputValue!) - const find = columnOptions.find((o) => o.props.name === inputValue) - - const filteredItems = columnOptions.filter(getOptionsFilter(inputValue)) - - if (!find && inputValue) { - filteredItems.push({ - id: 'CREATE', - props: { name: inputValue }, - } as IOptionNode) - } - - setItems(filteredItems) - }, - items, - itemToString(item) { - return item ? item.props.name : '' - }, - async onSelectedItemChange({ selectedItem }) { - let id = selectedItem?.id as string - - if (selectedItem?.id === 'CREATE') { - const newOption = await addOption( - cell.props.columnId, - selectedItem.props.name, - ) - id = newOption.id - } - - setTimeout(() => { - const oldIds = cell.props.data || [] - const existed = oldIds.includes(id) - - if (!existed) { - const newIds = [...oldIds, id] - updateCell(newIds) - props.setValue(newIds) - } - - setInputValue('') - close() - }, 10) - }, - }) - - return ( - - { - setInputValue(e.target.value) - }, - })} - /> - - - {!items.length && ( - - No options - - )} - - {items.map((item, index) => ( - - {item.id === 'CREATE' && Create} - - - - ))} - - - - ) -} diff --git a/packages/database-ui/src/Cell/Number.tsx b/packages/database-ui/src/Cell/Number.tsx deleted file mode 100644 index 88ae95611..000000000 --- a/packages/database-ui/src/Cell/Number.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { - ChangeEvent, - FC, - memo, - useEffect, - useRef, - useState, -} from 'react' -import TextareaAutosize from 'react-textarea-autosize' -import { Box, css } from '@fower/react' -import { useDebouncedCallback } from 'use-debounce' -import { matchNumber } from '@penx/shared' -import { CellProps } from './CellProps' - -export const NumberCell: FC = memo(function NumberCell(props) { - const { cell, updateCell, selected, width, index } = props - const [value, setValue] = useState(cell.props.data || '') - const ref = useRef(null) - - useEffect(() => { - if (cell.props.ref) return - setValue(cell.props.data) - }, [cell]) - - const debouncedUpdate = useDebouncedCallback(async (value: any) => { - updateCell(Number(value)) - }, 500) - - const onChange = (e: ChangeEvent) => { - const data = e.target.value - if (!matchNumber(data) && data.length) { - // console.log('not a number', data) - return - } - setValue(data) - debouncedUpdate(data) - } - - return ( - - - - ) -}) diff --git a/packages/database-ui/src/Cell/Password.tsx b/packages/database-ui/src/Cell/Password.tsx deleted file mode 100644 index cfc51943c..000000000 --- a/packages/database-ui/src/Cell/Password.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { - ChangeEvent, - FC, - memo, - useEffect, - useRef, - useState, -} from 'react' -import { Box } from '@fower/react' -import { Copy, Eye, EyeOff } from 'lucide-react' -import { useDebouncedCallback } from 'use-debounce' -import { Input, InputElement, InputGroup, toast } from 'uikit' -import { useCopyToClipboard } from '@penx/shared' -import { CellProps } from './CellProps' - -export const PasswordCell: FC = memo(function PasswordCell(props) { - const { cell, updateCell } = props - const [value, setValue] = useState(cell.props.data || '') - const ref = useRef(null) - const [visible, setVisible] = useState(false) - const { copy } = useCopyToClipboard() - - useEffect(() => { - if (cell.props.ref) return - setValue(cell.props.data) - }, [cell]) - - const debouncedUpdate = useDebouncedCallback(async (value: any) => { - updateCell(value) - }, 500) - - const onChange = (e: ChangeEvent) => { - const data = e.target.value - setValue(data) - debouncedUpdate(data) - } - - return ( - - - - - { - copy(value) - toast.info('Copied to clipboard') - }} - > - - - setVisible(!visible)} - > - {visible && } - {!visible && } - - - - - ) -}) diff --git a/packages/database-ui/src/Cell/PrimaryCell.tsx b/packages/database-ui/src/Cell/PrimaryCell.tsx index 069673a58..5ba46e8fa 100644 --- a/packages/database-ui/src/Cell/PrimaryCell.tsx +++ b/packages/database-ui/src/Cell/PrimaryCell.tsx @@ -3,6 +3,7 @@ import isEqual from 'react-fast-compare' import { Box, css, FowerHTMLProps } from '@fower/react' import { Transforms } from 'slate' import { Editable, RenderElementProps, Slate, withReact } from 'slate-react' +import { CellProps } from '@penx/cell-fields' import { useCreateEditor } from '@penx/editor' import { TElement } from '@penx/editor-common' import { Leaf } from '@penx/editor-leaf' @@ -10,7 +11,6 @@ import { clearEditor } from '@penx/editor-transforms' import { db, emitter } from '@penx/local-db' import { Paragraph } from '@penx/paragraph' import { Tag } from '../tag/Tag' -import { CellProps } from './CellProps' interface Props extends Omit, 'column'> { editorAtomicStyle?: string diff --git a/packages/database-ui/src/Cell/SingleSelect.tsx b/packages/database-ui/src/Cell/SingleSelect.tsx deleted file mode 100644 index 55e221a5c..000000000 --- a/packages/database-ui/src/Cell/SingleSelect.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import React, { - Dispatch, - FC, - memo, - SetStateAction, - useEffect, - useState, -} from 'react' -import isEqual from 'react-fast-compare' -import { Box } from '@fower/react' -import { useCombobox, useMultipleSelection, useSelect } from 'downshift' -import { - Input, - Popover, - PopoverContent, - PopoverTrigger, - usePopoverContext, -} from 'uikit' -import { useDatabaseContext } from '@penx/database-context' -import { IOptionNode } from '@penx/model-types' -import { OptionTag } from '../shared/OptionTag' -import { CellProps } from './CellProps' - -export const SingleSelectCell: FC = memo( - function SingleSelectCell(props) { - const { cell } = props - const { options, deleteCellOption } = useDatabaseContext() - const [value, setValue] = useState( - Array.isArray(cell.props.data) ? cell.props.data : [], - ) - - const items = (Array.isArray(value) ? value : []) - .map((item) => options.find((o) => o.id === item)!) - .filter((o) => !!o) - - useEffect(() => { - if (isEqual(value, cell.props.data)) return - setValue(cell.props.data) - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [cell.props.data]) - - return ( - - - - {items.map((option) => ( - { - await deleteCellOption(cell.id, option.id) - }} - /> - ))} - - - - - - - ) - }, -) - -function Combobox( - props: CellProps & { - setValue: Dispatch> - }, -) { - const { cell, column, updateCell } = props - const { close } = usePopoverContext() - const { addOption, options } = useDatabaseContext() - const optionIds = column.props.optionIds || [] - const columnOptions = optionIds.map((o) => options.find((o2) => o2.id === o)!) - - function getOptionsFilter(inputValue: string) { - const lowerCasedInputValue = inputValue.toLowerCase() - return (item: IOptionNode) => { - return ( - !inputValue || - item.props.name.toLowerCase().includes(lowerCasedInputValue) - ) - } - } - - const [items, setItems] = useState(columnOptions) - const [inputValue, setInputValue] = useState('') - const { - isOpen, - getToggleButtonProps, - getLabelProps, - getMenuProps, - getInputProps, - highlightedIndex, - getItemProps, - selectedItem, - } = useCombobox({ - inputValue: inputValue, - onInputValueChange({ inputValue = '' }) { - setInputValue(inputValue!) - const find = columnOptions.find((o) => o.props.name === inputValue) - - const filteredItems = columnOptions.filter(getOptionsFilter(inputValue)) - - if (!find && inputValue) { - filteredItems.push({ - id: 'CREATE', - props: { name: inputValue }, - } as IOptionNode) - } - - setItems(filteredItems) - }, - items, - itemToString(item) { - return item ? item.props.name : '' - }, - async onSelectedItemChange({ selectedItem }) { - let id = selectedItem?.id as string - if (selectedItem?.id === 'CREATE') { - const newOption = await addOption( - cell.props.columnId, - selectedItem.props.name, - ) - id = newOption.id - } - - setTimeout(() => { - updateCell([id]) - props.setValue([id]) - setInputValue('') - close() - }, 10) - }, - }) - - return ( - - { - setInputValue(e.target.value) - }, - })} - /> - - - {!items.length && ( - - No options - - )} - - {items.map((item, index) => ( - - {item.id === 'CREATE' && Create} - - - - ))} - - - - ) -} diff --git a/packages/database-ui/src/Cell/Text.tsx b/packages/database-ui/src/Cell/Text.tsx deleted file mode 100644 index e913f5867..000000000 --- a/packages/database-ui/src/Cell/Text.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { - ChangeEvent, - FC, - memo, - useEffect, - useRef, - useState, -} from 'react' -import TextareaAutosize from 'react-textarea-autosize' -import { Box, css } from '@fower/react' -import { Maximize2 } from 'lucide-react' -import { useDebouncedCallback } from 'use-debounce' -import { CellProps } from './CellProps' -import { PrimaryCell } from './PrimaryCell' - -export const TextCell: FC = memo(function TextCell(props) { - const { cell, updateCell, selected, width, index } = props - const [value, setValue] = useState(cell.props.data || '') - const ref = useRef(null) - - useEffect(() => { - if (cell.props.ref) return - setValue(cell.props.data) - }, [cell]) - - const debouncedUpdate = useDebouncedCallback(async (value: any) => { - updateCell(value) - }, 500) - - const onChange = (e: ChangeEvent) => { - const data = e.target.value - setValue(data) - debouncedUpdate(data) - } - - if (cell.props.ref) { - return - } - - return ( - - - {selected && ( - - - - )} - - ) -}) diff --git a/packages/database-ui/src/Cell/UpdatedAt.tsx b/packages/database-ui/src/Cell/UpdatedAt.tsx deleted file mode 100644 index 595ef922a..000000000 --- a/packages/database-ui/src/Cell/UpdatedAt.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { FC, memo } from 'react' -import { Box } from '@fower/react' -import { format } from 'date-fns' -import { useDatabaseContext } from '@penx/database-context' -import { CellProps } from './CellProps' - -export const UpdatedAtCell: FC = memo(function UpdatedAtCell(props) { - const { cell } = props - const { rows } = useDatabaseContext() - const row = rows.find((r) => r.id === cell.props.rowId)! - - return ( - - {format(new Date(row.updatedAt), 'yyyy-MM-dd HH:mm:ss')} - - ) -}) diff --git a/packages/database-ui/src/Cell/index.tsx b/packages/database-ui/src/Cell/index.tsx deleted file mode 100644 index a8e3caae0..000000000 --- a/packages/database-ui/src/Cell/index.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { memo } from 'react' -import isEqual from 'react-fast-compare' -import { css } from '@fower/react' -import { motion } from 'framer-motion' -import { db } from '@penx/local-db' -import { FieldType, ICellNode, IColumnNode } from '@penx/model-types' -import { store } from '@penx/store' -import { columnWidthMotion } from '../columnWidthMotion' -import { CreatedAtCell } from './CreatedAt' -import { DateCell } from './Date' -import { MultipleSelectCell } from './MultipleSelect' -import { NumberCell } from './Number' -import { PasswordCell } from './Password' -import { SingleSelectCell } from './SingleSelect' -import { TextCell } from './Text' -import { UpdatedAtCell } from './UpdatedAt' - -const cellsMap: Record = { - [FieldType.TEXT]: TextCell, - [FieldType.NUMBER]: NumberCell, - [FieldType.PASSWORD]: PasswordCell, - [FieldType.SINGLE_SELECT]: SingleSelectCell, - [FieldType.MULTIPLE_SELECT]: MultipleSelectCell, - [FieldType.DATE]: DateCell, - [FieldType.CREATED_AT]: CreatedAtCell, - [FieldType.UPDATED_AT]: UpdatedAtCell, -} - -interface Props { - index: number - columns: IColumnNode[] - cell: ICellNode -} - -export const TableCell = memo( - function TableCell({ columns, cell, index }: Props) { - if (!cell) return null - - const className = css({ - inlineFlex: true, - bgWhite: true, - borderBottom: true, - borderRight: true, - }) - - const column = columns.find((c) => c.id === cell.props.columnId)! - const fieldType = column.props.fieldType - - const CellComponent = cellsMap[fieldType as FieldType] ?? TextCell - - // TODO: get width from store, see ColumnItem - const width = columnWidthMotion[column.id] - - async function updateCell(data: any) { - await db.updateCell(cell.id, { - props: { ...cell.props, data }, - }) - - const nodes = await db.listNodesBySpaceId(cell.spaceId) - store.node.setNodes(nodes) - } - - return ( - - - - ) - }, - (prev, next) => { - return isEqual(prev, next) - }, -) diff --git a/packages/database-ui/src/RowForm.tsx b/packages/database-ui/src/RowForm.tsx index 49cd2b91b..2a03ea007 100644 --- a/packages/database-ui/src/RowForm.tsx +++ b/packages/database-ui/src/RowForm.tsx @@ -1,9 +1,9 @@ import { forwardRef } from 'react' import { Box } from '@fower/react' +import { CellField } from '@penx/cell-fields' import { useDatabase } from '@penx/node-hooks' import { mappedByKey } from '@penx/shared' import { FieldIcon } from './shared/FieldIcon' -import { CellField } from './tag/fields' interface Props { databaseId: string diff --git a/packages/database-ui/src/tag/TagForm.tsx b/packages/database-ui/src/tag/TagForm.tsx index 21625c37a..70fa7a96b 100644 --- a/packages/database-ui/src/tag/TagForm.tsx +++ b/packages/database-ui/src/tag/TagForm.tsx @@ -1,13 +1,13 @@ import { forwardRef } from 'react' import { Box } from '@fower/react' import { Editor, Path } from 'slate' +import { CellField } from '@penx/cell-fields' import { useDatabaseContext } from '@penx/database-context' import { useEditorStatic } from '@penx/editor-common' import { isListContentElement, ListContentElement } from '@penx/list' import { useDatabase } from '@penx/node-hooks' import { mappedByKey } from '@penx/shared' import { FieldIcon } from '../shared/FieldIcon' -import { CellField } from './fields' interface Props { databaseId: string diff --git a/packages/database-ui/src/tag/TagSelector.tsx b/packages/database-ui/src/tag/TagSelector.tsx deleted file mode 100644 index bb36b7f5d..000000000 --- a/packages/database-ui/src/tag/TagSelector.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { useCallback, useEffect, useState } from 'react' -import { - autoUpdate, - flip, - FloatingPortal, - offset, - shift, - useClick, - useDismiss, - useFloating, - useInteractions, - useRole, -} from '@floating-ui/react' -import { Box } from '@fower/react' -import { Transforms } from 'slate' -import { useEditor, useEditorStatic } from '@penx/editor-common' -import { - findNodePath, - getCurrentNode, - getNodeByPath, -} from '@penx/editor-queries' -import { ElementProps } from '@penx/extension-typings' -import { TagSelectorElement } from '../../types' -import { TagSelectorContent } from './TagSelectorContent' - -export const TagSelector = ({ - element, - children, -}: ElementProps) => { - const editor = useEditor() - const path = findNodePath(editor, element)! - - const setIsOpen = useCallback( - (isOpen: boolean) => { - const node = getNodeByPath(editor, path) - - if (node) { - Transforms.setNodes( - editor, - { isOpen }, - { at: path }, - ) - } - }, - [editor, path], - ) - - const { isOpen } = element - - const { refs, floatingStyles, context } = useFloating({ - whileElementsMounted: autoUpdate, - open: isOpen, - onOpenChange: (open: boolean) => { - setIsOpen(open) - - if (!open) { - Transforms.unwrapNodes(editor, { - at: path, - }) - - editor.isTagSelectorOpened = false - } - }, - placement: 'bottom-start', - middleware: [ - offset(10), - flip({ fallbackAxisSideDirection: 'end' }), - shift(), - ], - }) - const click = useClick(context, {}) - const dismiss = useDismiss(context) - const role = useRole(context) - const { getReferenceProps, getFloatingProps } = useInteractions([ - click, - dismiss, - role, - ]) - - useEffect(() => { - const node = getCurrentNode(editor) - // only open on focus - if (node) { - editor.isTagSelectorOpened = true - setIsOpen(true) - } - }, [editor, setIsOpen]) - - return ( - <> - - {children} - - - {isOpen && ( - - - setIsOpen(false)} - element={element} - containerRef={refs.setFloating} - /> - - - )} - - ) -} diff --git a/packages/database-ui/src/tag/TagSelectorItem.tsx b/packages/database-ui/src/tag/TagSelectorItem.tsx deleted file mode 100644 index a508249c6..000000000 --- a/packages/database-ui/src/tag/TagSelectorItem.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useEffect } from 'react' -import { useInView } from 'react-intersection-observer' -import { Box } from '@fower/react' -import { useStore } from 'stook' -import { Node } from '@penx/model' - -interface TagSelectorItemProps { - id: string - isActive: boolean - name: string - node: Node - onClick: () => void -} - -export function TagSelectorItem({ - id, - isActive, - name, - node, - onClick, -}: TagSelectorItemProps) { - const [value, setValue] = useStore(id, false) - const root = document.getElementById('editor-block-selector') - const { ref, inView, entry } = useInView({ - /* Optional options */ - threshold: 0, - root: root ? root : null, - }) - - useEffect(() => { - if (value !== inView) setValue(inView) - }, [inView, value, setValue]) - - return ( - - # - {name} - - ) -} diff --git a/packages/database-ui/src/tag/fields/CellProps.ts b/packages/database-ui/src/tag/fields/CellProps.ts deleted file mode 100644 index f0455bd61..000000000 --- a/packages/database-ui/src/tag/fields/CellProps.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ICellNode, IColumnNode } from '@penx/model-types' - -export interface CellProps { - index: number - cell: ICellNode - column: IColumnNode - updateCell: (data: any) => void -} diff --git a/packages/database-ui/src/tag/fields/CreatedAt.tsx b/packages/database-ui/src/tag/fields/CreatedAt.tsx deleted file mode 100644 index 0feb9b20d..000000000 --- a/packages/database-ui/src/tag/fields/CreatedAt.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { FC, memo } from 'react' -import { Box } from '@fower/react' -import { format } from 'date-fns' -import { useDatabaseContext } from '@penx/database-context' -import { CellProps } from './CellProps' - -export const CreatedAtCell: FC = memo(function CreatedAtCell(props) { - const { cell } = props - const { rows } = useDatabaseContext() - const row = rows.find((r) => r.id === cell.props.rowId)! - - return ( - - {format(new Date(row.createdAt), 'yyyy-MM-dd HH:mm:ss')} - - ) -}) diff --git a/packages/database-ui/src/tag/fields/Date.tsx b/packages/database-ui/src/tag/fields/Date.tsx deleted file mode 100644 index e4af95cfc..000000000 --- a/packages/database-ui/src/tag/fields/Date.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { FC, memo, useRef, useState } from 'react' -import DatePicker from 'react-datepicker' -import { Box } from '@fower/react' -import { CellProps } from './CellProps' - -export const DateCell: FC = memo(function DateCell(props) { - const { cell, updateCell } = props - const ref = useRef(null) - const [startDate, setStartDate] = useState(cell.props.data ?? null) - - return ( - - { - setStartDate(date!) - updateCell(date!) - }} - /> - - ) -}) diff --git a/packages/database-ui/src/tag/fields/MultipleSelect.tsx b/packages/database-ui/src/tag/fields/MultipleSelect.tsx deleted file mode 100644 index 204fe2f85..000000000 --- a/packages/database-ui/src/tag/fields/MultipleSelect.tsx +++ /dev/null @@ -1,214 +0,0 @@ -import React, { - Dispatch, - FC, - memo, - SetStateAction, - useEffect, - useState, -} from 'react' -import isEqual from 'react-fast-compare' -import { Box } from '@fower/react' -import { useCombobox, useMultipleSelection, useSelect } from 'downshift' -import { - Input, - Popover, - PopoverContent, - PopoverTrigger, - usePopoverContext, -} from 'uikit' -import { useDatabaseContext } from '@penx/database-context' -import { IOptionNode } from '@penx/model-types' -import { OptionTag } from '../../shared/OptionTag' -import { CellProps } from './CellProps' - -export const MultipleSelectCell: FC = memo( - function MultipleSelectCell(props) { - const { cell } = props - const { options, deleteCellOption } = useDatabaseContext() - const [value, setValue] = useState( - Array.isArray(cell.props.data) ? cell.props.data : [], - ) - - const items = (Array.isArray(value) ? value : []) - .map((item) => options.find((o) => o.id === item)!) - .filter((o) => !!o) - - useEffect(() => { - if (isEqual(value, cell.props.data)) return - setValue(cell.props.data) - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [cell.props.data]) - - return ( - - - - {items.map((option) => ( - { - await deleteCellOption(cell.id, option.id) - }} - /> - ))} - - - - - - - ) - }, -) - -function Combobox( - props: CellProps & { - value: string[] - setValue: Dispatch> - }, -) { - const { cell, column, updateCell } = props - const { close } = usePopoverContext() - const { addOption, options } = useDatabaseContext() - const optionIds = column.props.optionIds || [] - const columnOptions = optionIds.map((o) => options.find((o2) => o2.id === o)!) - - function getOptionsFilter(inputValue: string) { - const lowerCasedInputValue = inputValue.toLowerCase() - return (item: IOptionNode) => { - return ( - !inputValue || - item.props.name.toLowerCase().includes(lowerCasedInputValue) - ) - } - } - - const [items, setItems] = useState(columnOptions) - const [inputValue, setInputValue] = useState('') - const { - isOpen, - getToggleButtonProps, - getLabelProps, - getMenuProps, - getInputProps, - highlightedIndex, - getItemProps, - selectedItem, - } = useCombobox({ - inputValue: inputValue, - onInputValueChange({ inputValue = '' }) { - setInputValue(inputValue!) - const find = columnOptions.find((o) => o.props.name === inputValue) - - const filteredItems = columnOptions.filter(getOptionsFilter(inputValue)) - - if (!find && inputValue) { - filteredItems.push({ - id: 'CREATE', - props: { name: inputValue }, - } as IOptionNode) - } - - setItems(filteredItems) - }, - items, - itemToString(item) { - return item ? item.props.name : '' - }, - async onSelectedItemChange({ selectedItem }) { - let id = selectedItem?.id as string - - if (selectedItem?.id === 'CREATE') { - const newOption = await addOption( - cell.props.columnId, - selectedItem.props.name, - ) - id = newOption.id - } - - setTimeout(() => { - const oldIds = cell.props.data || [] - const existed = oldIds.includes(id) - - if (!existed) { - const newIds = [...oldIds, id] - updateCell(newIds) - props.setValue(newIds) - } - - setInputValue('') - close() - }, 10) - }, - }) - - return ( - - { - setInputValue(e.target.value) - }, - })} - /> - - - {!items.length && ( - - No options - - )} - - {items.map((item, index) => ( - - {item.id === 'CREATE' && Create} - - - - ))} - - - - ) -} diff --git a/packages/database-ui/src/tag/fields/Number.tsx b/packages/database-ui/src/tag/fields/Number.tsx deleted file mode 100644 index 7cf1ed757..000000000 --- a/packages/database-ui/src/tag/fields/Number.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { - ChangeEvent, - FC, - memo, - useEffect, - useRef, - useState, -} from 'react' -import { useDebouncedCallback } from 'use-debounce' -import { Input } from 'uikit' -import { matchNumber } from '@penx/shared' -import { CellProps } from './CellProps' - -export const NumberCell: FC = memo(function NumberCell(props) { - const { cell, updateCell, index } = props - const [value, setValue] = useState(cell.props.data || '') - const ref = useRef(null) - - useEffect(() => { - if (cell.props.ref) return - setValue(cell.props.data) - }, [cell]) - - const debouncedUpdate = useDebouncedCallback(async (value: any) => { - updateCell(Number(value)) - }, 500) - - const onChange = (e: ChangeEvent) => { - const data = e.target.value - if (!matchNumber(data) && data.length) { - // console.log('not a number', data) - return - } - setValue(data) - debouncedUpdate(data) - } - - return -}) diff --git a/packages/database-ui/src/tag/fields/Password.tsx b/packages/database-ui/src/tag/fields/Password.tsx deleted file mode 100644 index 3c57e0845..000000000 --- a/packages/database-ui/src/tag/fields/Password.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { ChangeEvent, FC, memo, useEffect, useState } from 'react' -import { Box } from '@fower/react' -import { Copy, Eye, EyeOff } from 'lucide-react' -import { useDebouncedCallback } from 'use-debounce' -import { Input, InputElement, InputGroup, toast } from 'uikit' -import { useCopyToClipboard } from '@penx/shared' -import { CellProps } from './CellProps' - -export const PasswordCell: FC = memo(function PasswordCell(props) { - const { cell, updateCell } = props - const [value, setValue] = useState(cell.props.data || '') - - const [visible, setVisible] = useState(false) - const { copy } = useCopyToClipboard() - - useEffect(() => { - if (cell.props.ref) return - setValue(cell.props.data) - }, [cell]) - - const debouncedUpdate = useDebouncedCallback(async (value: any) => { - updateCell(value) - }, 500) - - const onChange = (e: ChangeEvent) => { - const data = e.target.value - setValue(data) - debouncedUpdate(data) - } - - return ( - - - - - { - copy(cell.props.data) - toast.info('Copied to clipboard') - }} - > - - - setVisible(!visible)} - > - {visible && } - {!visible && } - - - - - ) -}) diff --git a/packages/database-ui/src/tag/fields/SingleSelect.tsx b/packages/database-ui/src/tag/fields/SingleSelect.tsx deleted file mode 100644 index 4426e2ca6..000000000 --- a/packages/database-ui/src/tag/fields/SingleSelect.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import React, { - Dispatch, - FC, - memo, - SetStateAction, - useEffect, - useState, -} from 'react' -import isEqual from 'react-fast-compare' -import { Box } from '@fower/react' -import { useCombobox, useMultipleSelection, useSelect } from 'downshift' -import { - Input, - Popover, - PopoverContent, - PopoverTrigger, - usePopoverContext, -} from 'uikit' -import { useDatabaseContext } from '@penx/database-context' -import { IOptionNode } from '@penx/model-types' -import { OptionTag } from '../../shared/OptionTag' -import { CellProps } from './CellProps' - -export const SingleSelectCell: FC = memo( - function SingleSelectCell(props) { - const { cell } = props - const { options, deleteCellOption } = useDatabaseContext() - const [value, setValue] = useState( - Array.isArray(cell.props.data) ? cell.props.data : [], - ) - - const items = (Array.isArray(value) ? value : []) - .map((item) => options.find((o) => o.id === item)!) - .filter((o) => !!o) - - useEffect(() => { - if (isEqual(value, cell.props.data)) return - setValue(cell.props.data) - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [cell.props.data]) - - return ( - - - - {items.map((option) => ( - { - await deleteCellOption(cell.id, option.id) - }} - /> - ))} - - - - - - - ) - }, -) - -function Combobox( - props: CellProps & { - setValue: Dispatch> - }, -) { - const { cell, column, updateCell } = props - const { close } = usePopoverContext() - const { addOption, options } = useDatabaseContext() - const optionIds = column.props.optionIds || [] - const columnOptions = optionIds.map((o) => options.find((o2) => o2.id === o)!) - - function getOptionsFilter(inputValue: string) { - const lowerCasedInputValue = inputValue.toLowerCase() - return (item: IOptionNode) => { - return ( - !inputValue || - item.props.name.toLowerCase().includes(lowerCasedInputValue) - ) - } - } - - const [items, setItems] = useState(columnOptions) - const [inputValue, setInputValue] = useState('') - const { - isOpen, - getToggleButtonProps, - getLabelProps, - getMenuProps, - getInputProps, - highlightedIndex, - getItemProps, - selectedItem, - } = useCombobox({ - inputValue: inputValue, - onInputValueChange({ inputValue = '' }) { - setInputValue(inputValue!) - const find = columnOptions.find((o) => o.props.name === inputValue) - - const filteredItems = columnOptions.filter(getOptionsFilter(inputValue)) - - if (!find && inputValue) { - filteredItems.push({ - id: 'CREATE', - props: { name: inputValue }, - } as IOptionNode) - } - - setItems(filteredItems) - }, - items, - itemToString(item) { - return item ? item.props.name : '' - }, - async onSelectedItemChange({ selectedItem }) { - let id = selectedItem?.id as string - if (selectedItem?.id === 'CREATE') { - const newOption = await addOption( - cell.props.columnId, - selectedItem.props.name, - ) - id = newOption.id - } - - setTimeout(() => { - updateCell([id]) - props.setValue([id]) - setInputValue('') - close() - }, 10) - }, - }) - - return ( - - { - setInputValue(e.target.value) - }, - })} - /> - - - {!items.length && ( - - No options - - )} - - {items.map((item, index) => ( - - {item.id === 'CREATE' && Create} - - - - ))} - - - - ) -} diff --git a/packages/database-ui/src/tag/fields/Text.tsx b/packages/database-ui/src/tag/fields/Text.tsx deleted file mode 100644 index 7175318ce..000000000 --- a/packages/database-ui/src/tag/fields/Text.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { ChangeEvent, FC, memo, useEffect, useState } from 'react' -import TextareaAutosize from 'react-textarea-autosize' -import { Box, css } from '@fower/react' -import { useDebouncedCallback } from 'use-debounce' -import { CellProps } from './CellProps' - -export const TextCell: FC = memo(function TextCell(props) { - const { cell, updateCell } = props - const [value, setValue] = useState(cell.props.data || '') - - useEffect(() => { - setValue(cell.props.data) - }, [cell]) - - const debouncedUpdate = useDebouncedCallback(async (value: any) => { - updateCell(value) - }, 500) - - const onChange = (e: ChangeEvent) => { - const data = e.target.value - - setValue(data) - debouncedUpdate(data) - } - - return ( - - - - ) -}) diff --git a/packages/database-ui/src/tag/fields/UpdatedAt.tsx b/packages/database-ui/src/tag/fields/UpdatedAt.tsx deleted file mode 100644 index 7a9c6d8f3..000000000 --- a/packages/database-ui/src/tag/fields/UpdatedAt.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { FC, memo } from 'react' -import { Box } from '@fower/react' -import { format } from 'date-fns' -import { useDatabaseContext } from '@penx/database-context' -import { CellProps } from './CellProps' - -export const UpdatedAtCell: FC = memo(function UpdatedAtCell(props) { - const { cell } = props - const { rows } = useDatabaseContext() - const row = rows.find((r) => r.id === cell.props.rowId)! - - return ( - - {format(new Date(row.updatedAt), 'yyyy-MM-dd HH:mm:ss')} - - ) -}) diff --git a/packages/database-ui/src/tag/fields/index.tsx b/packages/database-ui/src/tag/fields/index.tsx deleted file mode 100644 index 6984685e8..000000000 --- a/packages/database-ui/src/tag/fields/index.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { memo } from 'react' -import isEqual from 'react-fast-compare' -import { db } from '@penx/local-db' -import { FieldType, ICellNode, IColumnNode } from '@penx/model-types' -import { store } from '@penx/store' -import { CreatedAtCell } from './CreatedAt' -import { DateCell } from './Date' -import { MultipleSelectCell } from './MultipleSelect' -import { NumberCell } from './Number' -import { PasswordCell } from './Password' -import { SingleSelectCell } from './SingleSelect' -import { TextCell } from './Text' -import { UpdatedAtCell } from './UpdatedAt' - -const cellsMap: Record = { - [FieldType.TEXT]: TextCell, - [FieldType.NUMBER]: NumberCell, - [FieldType.PASSWORD]: PasswordCell, - [FieldType.SINGLE_SELECT]: SingleSelectCell, - [FieldType.MULTIPLE_SELECT]: MultipleSelectCell, - [FieldType.DATE]: DateCell, - [FieldType.CREATED_AT]: CreatedAtCell, - [FieldType.UPDATED_AT]: UpdatedAtCell, -} - -interface Props { - index: number - columns: IColumnNode[] - cell: ICellNode -} - -export const CellField = memo( - function TableCell({ columns, cell, index }: Props) { - const { rowId, columnId } = cell.props - const column = columns.find((c) => c.id === columnId)! - const fieldType = column.props.fieldType - const CellComponent = cellsMap[fieldType as FieldType] || TextCell - - async function updateCell(data: any) { - await db.updateNode(cell.id, { - props: { ...cell.props, data }, - }) - - const nodes = await db.listNodesBySpaceId(cell.spaceId) - store.node.setNodes(nodes) - } - - return ( - - ) - }, - (prev, next) => { - return isEqual({ cell: prev.cell }, { cell: next.cell }) - }, -) diff --git a/packages/database-ui/src/views/ListView.tsx b/packages/database-ui/src/views/ListView.tsx index 36eb572be..e7c310366 100644 --- a/packages/database-ui/src/views/ListView.tsx +++ b/packages/database-ui/src/views/ListView.tsx @@ -4,7 +4,6 @@ import { useDatabaseContext } from '@penx/database-context' import { db } from '@penx/local-db' import { IRowNode } from '@penx/model-types' import { store } from '@penx/store' -import { PrimaryCell } from '../Table/Cell/PrimaryCell' export const ListView = () => { const { rows } = useDatabaseContext() @@ -50,17 +49,6 @@ function ListItem({ row }: ListItemProps) { }} onClick={clickBullet} /> - - {}} - editorAtomicStyle="py-2" - flex - />
) } diff --git a/packages/database-ui/src/views/TableView/cells-common/select-cell.tsx b/packages/database-ui/src/views/TableView/cells-common/select-cell.tsx index ce23e6f28..1bb44c2fc 100644 --- a/packages/database-ui/src/views/TableView/cells-common/select-cell.tsx +++ b/packages/database-ui/src/views/TableView/cells-common/select-cell.tsx @@ -10,7 +10,7 @@ import { Rectangle, } from '@glideapps/glide-data-grid' import { Check } from 'lucide-react' -import { OptionTag } from '../../../shared/OptionTag' +import { OptionTag } from '@penx/widget' import { roundedRect } from '../cells/draw-fns' interface TaskOptions { diff --git a/packages/database-ui/src/views/TableView/cells/multiple-select-cell.tsx b/packages/database-ui/src/views/TableView/cells/multiple-select-cell.tsx index 6226885aa..f051cfbc0 100644 --- a/packages/database-ui/src/views/TableView/cells/multiple-select-cell.tsx +++ b/packages/database-ui/src/views/TableView/cells/multiple-select-cell.tsx @@ -16,7 +16,7 @@ import { Check } from 'lucide-react' import { Input, Portal } from 'uikit' import { useDatabaseContext } from '@penx/database-context' import { IColumnNode, IOptionNode } from '@penx/model-types' -import { OptionTag } from '../../../shared/OptionTag' +import { OptionTag } from '@penx/widget' import { roundedRect } from './draw-fns' interface MultipleSelectCellProps { @@ -164,6 +164,7 @@ function Combobox({ getMenuProps, getInputProps, highlightedIndex, + setHighlightedIndex, getItemProps, selectedItem, } = useCombobox({ @@ -179,6 +180,10 @@ function Combobox({ } as IOptionNode) } setItems(filteredItems) + + if (filteredItems.length) { + setHighlightedIndex(0) + } }, items, itemToString(item) { diff --git a/packages/database-ui/src/views/TableView/cells/single-select-cell.tsx b/packages/database-ui/src/views/TableView/cells/single-select-cell.tsx index 642d77746..e73538ad0 100644 --- a/packages/database-ui/src/views/TableView/cells/single-select-cell.tsx +++ b/packages/database-ui/src/views/TableView/cells/single-select-cell.tsx @@ -16,7 +16,7 @@ import { Check } from 'lucide-react' import { Input } from 'uikit' import { useDatabaseContext } from '@penx/database-context' import { IColumnNode, IOptionNode } from '@penx/model-types' -import { OptionTag } from '../../../shared/OptionTag' +import { OptionTag } from '@penx/widget' import { roundedRect } from './draw-fns' interface SingleSelectCellProps { @@ -165,19 +165,27 @@ function Combobox({ highlightedIndex, getItemProps, selectedItem, + selectItem, + setHighlightedIndex, } = useCombobox({ inputValue: inputValue, onInputValueChange({ inputValue = '' }) { setInputValue(inputValue!) const find = columnOptions.find((o) => o.props.name === inputValue) const filteredItems = columnOptions.filter(getOptionsFilter(inputValue)) + if (!find && inputValue) { filteredItems.push({ id: 'CREATE', props: { name: inputValue }, } as IOptionNode) } + setItems(filteredItems) + + if (filteredItems.length) { + setHighlightedIndex(0) + } }, items, itemToString(item) { diff --git a/packages/database-ui/src/shared/OptionTag.tsx b/packages/widget/src/OptionTag.tsx similarity index 100% rename from packages/database-ui/src/shared/OptionTag.tsx rename to packages/widget/src/OptionTag.tsx diff --git a/packages/widget/src/index.ts b/packages/widget/src/index.ts index 7e590814a..104eb990e 100644 --- a/packages/widget/src/index.ts +++ b/packages/widget/src/index.ts @@ -5,3 +5,4 @@ export * from './RecoveryPhraseLogin/RecoveryPhraseLoginProvider' export * from './FirstLocalSpaceGenerator/FirstLocalSpaceGenerator' export * from './DailyShortcut' export * from './motion-components' +export * from './OptionTag' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d21a3937e..e358cc004 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5137,6 +5137,9 @@ importers: '@glideapps/glide-data-grid': specifier: ^6.0.3 version: 6.0.3(lodash@4.17.21)(marked@11.2.0)(react-dom@18.2.0(react@18.2.0))(react-responsive-carousel@3.2.23)(react@18.2.0) + '@penx/cell-fields': + specifier: workspace:* + version: link:../cell-fields '@penx/constants': specifier: workspace:* version: link:../constants @@ -5218,6 +5221,9 @@ importers: '@penx/trpc-client': specifier: workspace:* version: link:../trpc-client + '@penx/widget': + specifier: workspace:* + version: link:../widget '@tanstack/react-query': specifier: ^4.36.1 version: 4.36.1(react-dom@18.2.0(react@18.2.0))(react-native@0.73.6(@babel/core@7.24.3)(@babel/preset-env@7.24.3(@babel/core@7.24.3))(bufferutil@4.0.8)(react@18.2.0)(utf-8-validate@6.0.3))(react@18.2.0)