From 13b608277d9b223d7a97407d69e664bd3cde99cf Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Wed, 3 Aug 2022 18:00:00 +0800 Subject: [PATCH] refactor: abstract the logic of the value history to the recastBlock --- .../PendantHistoryPanel.tsx | 4 +- .../pendant-modify-panel/Mention.tsx | 4 +- .../UpdatePendantPanel.tsx | 7 +- .../pendant-operation-panel/hooks.ts | 153 ++++++++++++------ .../src/block-pendant/use-pendant.ts | 10 -- .../editor-core/src/block-pendant/utils.ts | 119 +------------- .../components/editor-core/src/kanban/atom.ts | 48 ++++-- .../editor-core/src/recast-block/history.ts | 67 ++++++++ .../editor-core/src/recast-block/property.ts | 24 ++- 9 files changed, 241 insertions(+), 195 deletions(-) delete mode 100644 libs/components/editor-core/src/block-pendant/use-pendant.ts create mode 100644 libs/components/editor-core/src/recast-block/history.ts diff --git a/libs/components/editor-core/src/block-pendant/pendant-history-panel/PendantHistoryPanel.tsx b/libs/components/editor-core/src/block-pendant/pendant-history-panel/PendantHistoryPanel.tsx index 5fdd24788c..8cc24237b8 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-history-panel/PendantHistoryPanel.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-history-panel/PendantHistoryPanel.tsx @@ -1,5 +1,4 @@ import React, { ReactNode, useRef, useEffect, useState } from 'react'; -import { getPendantHistory } from '../utils'; import { getRecastItemValue, RecastMetaProperty, @@ -30,6 +29,7 @@ export const PendantHistoryPanel = ({ const [history, setHistory] = useState([]); const popoverHandlerRef = useRef<{ [key: string]: PopperHandler }>({}); + const { getValueHistory } = getRecastItemValue(block); useEffect(() => { const init = async () => { @@ -38,7 +38,7 @@ export const PendantHistoryPanel = ({ const missProperties = allProperties.filter( property => !currentBlockValues.find(v => v.id === property.id) ); - const pendantHistory = getPendantHistory({ + const pendantHistory = getValueHistory({ recastBlockId: recastBlock.id, }); const historyMap = missProperties.reduce<{ diff --git a/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Mention.tsx b/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Mention.tsx index 0b741ecbdc..8ce2bab003 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Mention.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Mention.tsx @@ -18,7 +18,9 @@ export default ({ user: { username, nickname, photo }, } = useUserAndSpaces(); - const [selectedValue, setSelectedValue] = useState(initialValue?.value); + const [selectedValue, setSelectedValue] = useState( + initialValue?.value || '' + ); const [focus, setFocus] = useState(false); const theme = useTheme(); return ( diff --git a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/UpdatePendantPanel.tsx b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/UpdatePendantPanel.tsx index bb51054964..40ef97631a 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/UpdatePendantPanel.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/UpdatePendantPanel.tsx @@ -4,11 +4,11 @@ import { HelpCenterIcon } from '@toeverything/components/icons'; import { PendantModifyPanel } from '../pendant-modify-panel'; import type { AsyncBlock } from '../../editor'; import { + getRecastItemValue, type RecastBlockValue, type RecastMetaProperty, } from '../../recast-block'; import { getPendantConfigByType } from '../utils'; -import { usePendant } from '../use-pendant'; import { StyledPopoverWrapper, StyledOperationLabel, @@ -42,7 +42,8 @@ export const UpdatePendantPanel = ({ }: Props) => { const pendantOption = pendantOptions.find(v => v.type === property.type); const iconConfig = getPendantConfigByType(property.type); - const { removePendant } = usePendant(block); + const { removeValue } = getRecastItemValue(block); + const Icon = IconMap[iconConfig.iconName]; const [fieldName, setFieldName] = useState(property.name); const onUpdateSure = useOnUpdateSure({ block, property }); @@ -108,7 +109,7 @@ export const UpdatePendantPanel = ({ onDelete={ hasDelete ? async () => { - await removePendant(property); + await removeValue(property.id); } : null } diff --git a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts index 83889962ac..2e5e6d658f 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts +++ b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts @@ -1,16 +1,23 @@ import type { CSSProperties } from 'react'; import { genSelectOptionId, + getRecastItemValue, type InformationProperty, type MultiSelectProperty, type RecastMetaProperty, type SelectOption, type SelectProperty, + useRecastBlock, useRecastBlockMeta, useSelectProperty, + SelectValue, + MultiSelectValue, + StatusValue, + InformationValue, + TextValue, + DateValue, } from '../../recast-block'; import { type AsyncBlock } from '../../editor'; -import { usePendant } from '../use-pendant'; import { type OptionType, PendantTypes, @@ -41,8 +48,8 @@ const genOptionWithId = (options: OptionType[] = []) => { export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => { const { addProperty } = useRecastBlockMeta(); const { createSelect } = useSelectProperty(); - const { setPendant } = usePendant(block); - + const recastBlock = useRecastBlock(); + const { setValue } = getRecastItemValue(block); return async ({ type, fieldName, @@ -79,7 +86,15 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => { tempSelectedId: newValue, }); - await setPendant(newProperty, selectedId); + await setValue( + { + id: newProperty.id, + type: newProperty.type, + value: selectedId, + } as SelectValue | MultiSelectValue | StatusValue, + recastBlock.id, + newProperty.id + ); } else if (type === PendantTypes.Information) { const emailOptions = genOptionWithId(newPropertyItem.emailOptions); @@ -97,26 +112,34 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => { locationOptions, } as Omit); - await setPendant(newProperty, { - email: getOfficialSelected({ - isMulti: true, - options: emailOptions, - tempOptions: newPropertyItem.emailOptions, - tempSelectedId: newValue.email, - }), - phone: getOfficialSelected({ - isMulti: true, - options: phoneOptions, - tempOptions: newPropertyItem.phoneOptions, - tempSelectedId: newValue.phone, - }), - location: getOfficialSelected({ - isMulti: true, - options: locationOptions, - tempOptions: newPropertyItem.locationOptions, - tempSelectedId: newValue.location, - }), - }); + await setValue( + { + id: newProperty.id, + type: newProperty.type, + value: { + email: getOfficialSelected({ + isMulti: true, + options: emailOptions, + tempOptions: newPropertyItem.emailOptions, + tempSelectedId: newValue.email, + }), + phone: getOfficialSelected({ + isMulti: true, + options: phoneOptions, + tempOptions: newPropertyItem.phoneOptions, + tempSelectedId: newValue.phone, + }), + location: getOfficialSelected({ + isMulti: true, + options: locationOptions, + tempOptions: newPropertyItem.locationOptions, + tempSelectedId: newValue.location, + }), + }, + } as InformationValue, + recastBlock.id, + newProperty.id + ); } else { // TODO: Color and background should use pendant config, but ui is not design now const iconConfig = getPendantConfigByType(type); @@ -129,8 +152,15 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => { color: iconConfig.color as CSSProperties['color'], iconName: iconConfig.iconName, }); - - await setPendant(newProperty, newValue); + await setValue( + { + id: newProperty.id, + type: newProperty.type, + value: newValue, + } as TextValue | DateValue, + recastBlock.id, + newProperty.id + ); } }; }; @@ -144,8 +174,9 @@ export const useOnUpdateSure = ({ property: RecastMetaProperty; }) => { const { updateSelect } = useSelectProperty(); - const { setPendant } = usePendant(block); const { updateProperty } = useRecastBlockMeta(); + const { setValue } = getRecastItemValue(block); + const recastBlock = useRecastBlock(); return async ({ type, @@ -199,7 +230,15 @@ export const useOnUpdateSure = ({ tempSelectedId: newValue, }); - await setPendant(selectProperty, selectedId); + await setValue( + { + id: selectProperty.id, + type: selectProperty.type, + value: selectedId, + } as SelectValue | MultiSelectValue | StatusValue, + recastBlock.id, + selectProperty.id + ); } else if (type === PendantTypes.Information) { // const { emailOptions, phoneOptions, locationOptions } = // property as InformationProperty; @@ -231,28 +270,44 @@ export const useOnUpdateSure = ({ locationOptions, } as InformationProperty); - await setPendant(newProperty, { - email: getOfficialSelected({ - isMulti: true, - options: emailOptions as SelectOption[], - tempOptions: newPropertyItem.emailOptions, - tempSelectedId: newValue.email, - }), - phone: getOfficialSelected({ - isMulti: true, - options: phoneOptions as SelectOption[], - tempOptions: newPropertyItem.phoneOptions, - tempSelectedId: newValue.phone, - }), - location: getOfficialSelected({ - isMulti: true, - options: locationOptions as SelectOption[], - tempOptions: newPropertyItem.locationOptions, - tempSelectedId: newValue.location, - }), - }); + await setValue( + { + id: newProperty.id, + type: newProperty.type, + value: { + email: getOfficialSelected({ + isMulti: true, + options: emailOptions as SelectOption[], + tempOptions: newPropertyItem.emailOptions, + tempSelectedId: newValue.email, + }), + phone: getOfficialSelected({ + isMulti: true, + options: phoneOptions as SelectOption[], + tempOptions: newPropertyItem.phoneOptions, + tempSelectedId: newValue.phone, + }), + location: getOfficialSelected({ + isMulti: true, + options: locationOptions as SelectOption[], + tempOptions: newPropertyItem.locationOptions, + tempSelectedId: newValue.location, + }), + }, + } as InformationValue, + recastBlock.id, + newProperty.id + ); } else { - await setPendant(property, newValue); + await setValue( + { + id: property.id, + type: property.type, + value: newValue, + } as TextValue | DateValue, + recastBlock.id, + property.id + ); } if (fieldName !== property.name) { diff --git a/libs/components/editor-core/src/block-pendant/use-pendant.ts b/libs/components/editor-core/src/block-pendant/use-pendant.ts deleted file mode 100644 index da66906db2..0000000000 --- a/libs/components/editor-core/src/block-pendant/use-pendant.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { getPendantController } from './utils'; -import { AsyncBlock } from '../editor'; -import { useRecastBlock } from '../recast-block'; - -export const usePendant = (block: AsyncBlock) => { - // const { getProperties, removeProperty } = useRecastBlockMeta(); - const recastBlock = useRecastBlock(); - - return getPendantController(recastBlock, block); -}; diff --git a/libs/components/editor-core/src/block-pendant/utils.ts b/libs/components/editor-core/src/block-pendant/utils.ts index b55e0a9175..63a3344a91 100644 --- a/libs/components/editor-core/src/block-pendant/utils.ts +++ b/libs/components/editor-core/src/block-pendant/utils.ts @@ -1,124 +1,7 @@ -import { - getRecastItemValue, - PropertyType, - RecastBlock, - RecastItem, - RecastMetaProperty, - RecastPropertyId, - SelectOption, -} from '../recast-block'; +import { PropertyType, SelectOption } from '../recast-block'; import { OptionIdType, OptionType, PendantConfig, PendantTypes } from './types'; import { pendantConfig } from './config'; import { nanoid } from 'nanoid'; -import { AsyncBlock } from '../editor'; -type Props = { - recastBlockId: string; - blockId: string; - propertyId: RecastPropertyId; -}; - -type StorageMap = { - [recastBlockId: string]: { - [propertyId: RecastPropertyId]: string; - }; -}; - -const LOCAL_STORAGE_NAME = 'TEMPORARY_PENDANT_DATA'; - -const ensureLocalStorage = () => { - const data = localStorage.getItem(LOCAL_STORAGE_NAME); - if (!data) { - localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify({})); - } -}; - -export const setPendantHistory = ({ - recastBlockId, - blockId, - propertyId, -}: Props) => { - ensureLocalStorage(); - const data: StorageMap = JSON.parse( - localStorage.getItem(LOCAL_STORAGE_NAME) as string - ); - - if (!data[recastBlockId]) { - data[recastBlockId] = {}; - } - const propertyValueRecord = data[recastBlockId]; - propertyValueRecord[propertyId] = blockId; - - localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data)); -}; - -export const getPendantHistory = ({ - recastBlockId, -}: { - recastBlockId: string; -}) => { - ensureLocalStorage(); - const data: StorageMap = JSON.parse( - localStorage.getItem(LOCAL_STORAGE_NAME) as string - ); - - return data[recastBlockId] ?? {}; -}; - -export const removePendantHistory = ({ - recastBlockId, - propertyId, -}: { - recastBlockId: string; - propertyId: RecastPropertyId; -}) => { - ensureLocalStorage(); - const data: StorageMap = JSON.parse( - localStorage.getItem(LOCAL_STORAGE_NAME) as string - ); - if (!data[recastBlockId]) { - return; - } - - delete data[recastBlockId][propertyId]; - - localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data)); -}; - -export const getPendantController = ( - recastBlock: RecastBlock, - block: AsyncBlock | RecastItem -) => { - const { setValue, removeValue } = getRecastItemValue(block); - - const setPendant = async (property: RecastMetaProperty, newValue: any) => { - const nv = { - id: property.id, - type: property.type, - value: newValue, - }; - - setPendantHistory({ - recastBlockId: recastBlock.id, - blockId: block.id, - propertyId: property.id, - }); - - return await setValue(nv); - }; - - const removePendant = async (property: RecastMetaProperty) => { - removePendantHistory({ - recastBlockId: block.id, - propertyId: property.id, - }); - return await removeValue(property.id); - }; - - return { - setPendant, - removePendant, - }; -}; /** * In select pendant panel, use mock options instead of use `createSelect` when add or delete option diff --git a/libs/components/editor-core/src/kanban/atom.ts b/libs/components/editor-core/src/kanban/atom.ts index 5bb3e652a6..cc69aff7e8 100644 --- a/libs/components/editor-core/src/kanban/atom.ts +++ b/libs/components/editor-core/src/kanban/atom.ts @@ -13,7 +13,6 @@ import { generateInitialOptions, generateRandomFieldName, getPendantIconsConfigByName, - getPendantController, } from '../block-pendant/utils'; import { SelectOption } from '../recast-block'; @@ -117,31 +116,60 @@ export const moveCardToGroup = async ({ group: KanbanGroup; recastBlock: RecastBlock; }) => { - const { setPendant, removePendant } = getPendantController( - recastBlock, - cardBlock - ); + const { setValue, removeValue } = getRecastItemValue(cardBlock); let success = false; if (group.id === DEFAULT_GROUP_ID) { - success = await removePendant(groupBy); + success = await removeValue(groupBy.id); return false; } switch (group.type) { case PropertyType.Select: { - success = await setPendant(groupBy, group.id); + success = await setValue( + { + id: groupBy.id, + type: group.type, + value: group.id, + }, + recastBlock.id, + groupBy.id + ); break; } case PropertyType.Status: { - success = await setPendant(groupBy, group.id); + success = await setValue( + { + id: groupBy.id, + type: group.type, + value: group.id, + }, + recastBlock.id, + groupBy.id + ); break; } case PropertyType.MultiSelect: { - success = await setPendant(groupBy, [group.id]); + success = await setValue( + { + id: groupBy.id, + type: group.type, + value: [group.id], + }, + recastBlock.id, + groupBy.id + ); break; } case PropertyType.Text: { - success = await setPendant(groupBy, group.id); + success = await setValue( + { + id: groupBy.id, + type: group.type, + value: group.id, + }, + recastBlock.id, + groupBy.id + ); break; } default: diff --git a/libs/components/editor-core/src/recast-block/history.ts b/libs/components/editor-core/src/recast-block/history.ts new file mode 100644 index 0000000000..e6a7bb7253 --- /dev/null +++ b/libs/components/editor-core/src/recast-block/history.ts @@ -0,0 +1,67 @@ +import { RecastPropertyId } from './types'; + +// TODO: The logic for keeping history should be supported by the network layer +type Props = { + recastBlockId: string; + blockId: string; + propertyId: RecastPropertyId; +}; + +type HistoryStorageMap = { + [recastBlockId: string]: { + [propertyId: RecastPropertyId]: string; + }; +}; + +const LOCAL_STORAGE_NAME = 'TEMPORARY_HISTORY_DATA'; + +const ensureLocalStorage = () => { + const data = localStorage.getItem(LOCAL_STORAGE_NAME); + if (!data) { + localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify({})); + } +}; + +export const setHistory = ({ recastBlockId, blockId, propertyId }: Props) => { + ensureLocalStorage(); + const data: HistoryStorageMap = JSON.parse( + localStorage.getItem(LOCAL_STORAGE_NAME) as string + ); + + if (!data[recastBlockId]) { + data[recastBlockId] = {}; + } + const propertyValueRecord = data[recastBlockId]; + propertyValueRecord[propertyId] = blockId; + + localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data)); +}; + +export const getHistory = ({ recastBlockId }: { recastBlockId: string }) => { + ensureLocalStorage(); + const data: HistoryStorageMap = JSON.parse( + localStorage.getItem(LOCAL_STORAGE_NAME) as string + ); + + return data[recastBlockId] ?? {}; +}; + +export const removeHistory = ({ + recastBlockId, + propertyId, +}: { + recastBlockId: string; + propertyId: RecastPropertyId; +}) => { + ensureLocalStorage(); + const data: HistoryStorageMap = JSON.parse( + localStorage.getItem(LOCAL_STORAGE_NAME) as string + ); + if (!data[recastBlockId]) { + return; + } + + delete data[recastBlockId][propertyId]; + + localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data)); +}; diff --git a/libs/components/editor-core/src/recast-block/property.ts b/libs/components/editor-core/src/recast-block/property.ts index 1452bba849..7d63eeff4c 100644 --- a/libs/components/editor-core/src/recast-block/property.ts +++ b/libs/components/editor-core/src/recast-block/property.ts @@ -15,6 +15,7 @@ import { SelectProperty, TABLE_VALUES_KEY, } from './types'; +import { getHistory, removeHistory, setHistory } from './history'; /** * Generate a unique id for a property @@ -240,7 +241,17 @@ export const getRecastItemValue = (block: RecastItem | AsyncBlock) => { return props[id]; }; - const setValue = (newValue: RecastBlockValue) => { + const setValue = ( + newValue: RecastBlockValue, + recastBlockId: string, + propertyId: RecastPropertyId + ) => { + setHistory({ + recastBlockId: recastBlockId, + blockId: block.id, + propertyId: propertyId, + }); + return recastItem.setProperty(TABLE_VALUES_KEY, { ...props, [newValue.id]: newValue, @@ -249,9 +260,18 @@ export const getRecastItemValue = (block: RecastItem | AsyncBlock) => { const removeValue = (propertyId: RecastPropertyId) => { const { [propertyId]: omitted, ...restProps } = props; + + removeHistory({ + recastBlockId: block.id, + propertyId: propertyId, + }); + return recastItem.setProperty(TABLE_VALUES_KEY, restProps); }; - return { getAllValue, getValue, setValue, removeValue }; + + const getValueHistory = getHistory; + + return { getAllValue, getValue, setValue, removeValue, getValueHistory }; }; const isSelectLikeProperty = (