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 ce47f80e27..5fdd24788c 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 @@ -26,7 +26,6 @@ export const PendantHistoryPanel = ({ const { getProperties } = useRecastBlockMeta(); const { getProperty } = useRecastBlockMeta(); - const { getAllValue } = getRecastItemValue(block); const recastBlock = useRecastBlock(); const [history, setHistory] = useState([]); @@ -34,7 +33,7 @@ export const PendantHistoryPanel = ({ useEffect(() => { const init = async () => { - const currentBlockValues = getAllValue(); + const currentBlockValues = getRecastItemValue(block).getAllValue(); const allProperties = getProperties(); const missProperties = allProperties.filter( property => !currentBlockValues.find(v => v.id === property.id) @@ -52,24 +51,26 @@ export const PendantHistoryPanel = ({ return history; }, {}); - const blockHistory = await Promise.all( - Object.entries(historyMap).map( - async ([propertyId, blockId]) => { - const latestValueBlock = ( - await groupBlock.children() - ).find((block: AsyncBlock) => block.id === blockId); + const blockHistory = ( + await Promise.all( + Object.entries(historyMap).map( + async ([propertyId, blockId]) => { + const latestValueBlock = ( + await groupBlock.children() + ).find((block: AsyncBlock) => block.id === blockId); - return getRecastItemValue(latestValueBlock).getValue( - propertyId as RecastPropertyId - ); - } + return getRecastItemValue( + latestValueBlock + ).getValue(propertyId as RecastPropertyId); + } + ) ) - ); - + ).filter(v => v); setHistory(blockHistory); }; + init(); - }, [getAllValue, getProperties, groupBlock, recastBlock]); + }, [block, getProperties, groupBlock, recastBlock]); return ( diff --git a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx index 5458280f74..b9d29cebe7 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx @@ -1,11 +1,11 @@ -import React, { CSSProperties, useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import { nanoid } from 'nanoid'; import { Input, Option, Select, Tooltip } from '@toeverything/components/ui'; import { HelpCenterIcon } from '@toeverything/components/icons'; import { AsyncBlock } from '../../editor'; import { IconMap, pendantOptions } from '../config'; -import { OptionType, PendantOptions, PendantTypes } from '../types'; +import { PendantOptions } from '../types'; import { PendantModifyPanel } from '../pendant-modify-panel'; import { StyledDivider, @@ -15,23 +15,13 @@ import { StyledPopoverSubTitle, StyledPopoverWrapper, } from '../StyledComponent'; -import { - genSelectOptionId, - InformationProperty, - useRecastBlock, - useRecastBlockMeta, - useSelectProperty, -} from '../../recast-block'; -import { - genInitialOptions, - getOfficialSelected, - getPendantConfigByType, -} from '../utils'; -import { usePendant } from '../use-pendant'; +import { genInitialOptions, getPendantConfigByType } from '../utils'; +import { useOnCreateSure } from './hooks'; const upperFirst = (str: string) => { return `${str[0].toUpperCase()}${str.slice(1)}`; }; + export const CreatePendantPanel = ({ block, onSure, @@ -41,9 +31,7 @@ export const CreatePendantPanel = ({ }) => { const [selectedOption, setSelectedOption] = useState(); const [fieldName, setFieldName] = useState(''); - const { addProperty, removeProperty } = useRecastBlockMeta(); - const { createSelect } = useSelectProperty(); - const { setPendant } = usePendant(block); + const onCreateSure = useOnCreateSure({ block }); useEffect(() => { selectedOption && @@ -110,91 +98,13 @@ export const CreatePendantPanel = ({ getPendantConfigByType(selectedOption.type) )} iconConfig={getPendantConfigByType(selectedOption.type)} - // isStatusSelect={selectedOption.name === 'Status'} onSure={async (type, newPropertyItem, newValue) => { - if (!fieldName) { - return; - } - - if ( - type === PendantTypes.MultiSelect || - type === PendantTypes.Select || - type === PendantTypes.Status - ) { - const newProperty = await createSelect({ - name: fieldName, - options: newPropertyItem, - type, - }); - - const selectedId = getOfficialSelected({ - isMulti: type === PendantTypes.MultiSelect, - options: newProperty.options, - tempOptions: newPropertyItem, - tempSelectedId: newValue, - }); - - await setPendant(newProperty, selectedId); - } else if (type === PendantTypes.Information) { - const emailOptions = genOptionWithId( - newPropertyItem.emailOptions - ); - - const phoneOptions = genOptionWithId( - newPropertyItem.phoneOptions - ); - - const locationOptions = genOptionWithId( - newPropertyItem.locationOptions - ); - - const newProperty = await addProperty({ - type, - name: fieldName, - emailOptions, - phoneOptions, - 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, - }), - }); - } else { - // TODO: Color and background should use pendant config, but ui is not design now - const iconConfig = getPendantConfigByType(type); - // TODO: Color and background should be choose by user in the future - const newProperty = await addProperty({ - type: type, - name: fieldName, - background: - iconConfig.background as CSSProperties['background'], - color: iconConfig.color as CSSProperties['color'], - iconName: iconConfig.iconName, - }); - - await setPendant(newProperty, newValue); - } - + await onCreateSure({ + type, + newPropertyItem, + newValue, + fieldName, + }); onSure?.(); }} /> @@ -203,10 +113,3 @@ export const CreatePendantPanel = ({ ); }; - -const genOptionWithId = (options: OptionType[] = []) => { - return options.map((option: OptionType) => ({ - ...option, - id: genSelectOptionId(), - })); -}; 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 02b22e9f7e..bb51054964 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 @@ -1,27 +1,16 @@ import { useState } from 'react'; +import { Input, Tooltip } from '@toeverything/components/ui'; +import { HelpCenterIcon } from '@toeverything/components/icons'; import { PendantModifyPanel } from '../pendant-modify-panel'; import type { AsyncBlock } from '../../editor'; import { - genSelectOptionId, - InformationProperty, - type MultiSelectProperty, type RecastBlockValue, type RecastMetaProperty, - type SelectOption, - type SelectProperty, - useRecastBlockMeta, - useSelectProperty, } from '../../recast-block'; -import { OptionType, PendantTypes, TempInformationType } from '../types'; -import { - getOfficialSelected, - getPendantConfigByType, - // getPendantIconsConfigByNameOrType, -} from '../utils'; +import { getPendantConfigByType } from '../utils'; import { usePendant } from '../use-pendant'; import { StyledPopoverWrapper, - StyledOperationTitle, StyledOperationLabel, StyledInputEndAdornment, StyledDivider, @@ -29,10 +18,8 @@ import { StyledPopoverSubTitle, } from '../StyledComponent'; import { IconMap, pendantOptions } from '../config'; -import { Input, Tooltip } from '@toeverything/components/ui'; -import { HelpCenterIcon } from '@toeverything/components/icons'; -type SelectPropertyType = MultiSelectProperty | SelectProperty; +import { useOnUpdateSure } from './hooks'; type Props = { value: RecastBlockValue; @@ -53,13 +40,12 @@ export const UpdatePendantPanel = ({ onCancel, titleEditable = false, }: Props) => { - const { updateSelect } = useSelectProperty(); - const { setPendant, removePendant } = usePendant(block); const pendantOption = pendantOptions.find(v => v.type === property.type); const iconConfig = getPendantConfigByType(property.type); + const { removePendant } = usePendant(block); const Icon = IconMap[iconConfig.iconName]; - const { updateProperty } = useRecastBlockMeta(); - const [fieldTitle, setFieldTitle] = useState(property.name); + const [fieldName, setFieldName] = useState(property.name); + const onUpdateSure = useOnUpdateSure({ block, property }); return ( @@ -77,10 +63,10 @@ export const UpdatePendantPanel = ({ Field Title {titleEditable ? ( { - setFieldTitle(e.target.value); + setFieldName(e.target.value); }} endAdornment={ @@ -111,114 +97,12 @@ export const UpdatePendantPanel = ({ property={property} type={property.type} onSure={async (type, newPropertyItem, newValue) => { - if ( - type === PendantTypes.MultiSelect || - type === PendantTypes.Select || - type === PendantTypes.Status - ) { - const newOptions = newPropertyItem as OptionType[]; - let selectProperty = property as SelectPropertyType; - const deleteOptionIds = selectProperty.options - .filter(o => { - return !newOptions.find(no => no.id === o.id); - }) - .map(o => o.id); - const addOptions = newOptions.filter( - o => typeof o.id === 'number' - ); - - const { addSelectOptions, removeSelectOptions } = - updateSelect(selectProperty); - - deleteOptionIds.length && - (selectProperty = (await removeSelectOptions( - ...deleteOptionIds - )) as SelectPropertyType); - - addOptions.length && - (selectProperty = (await addSelectOptions( - ...(addOptions as unknown as Omit< - SelectOption, - 'id' - >[]) - )) as SelectPropertyType); - - const selectedId = getOfficialSelected({ - isMulti: type === PendantTypes.MultiSelect, - options: selectProperty.options, - tempOptions: newPropertyItem, - tempSelectedId: newValue, - }); - - await setPendant(selectProperty, selectedId); - } else if (type === PendantTypes.Information) { - // const { emailOptions, phoneOptions, locationOptions } = - // property as InformationProperty; - const optionGroup = - newPropertyItem as TempInformationType; - - const emailOptions = optionGroup.emailOptions.map( - option => { - if (typeof option.id === 'number') { - option.id = genSelectOptionId(); - } - return option; - } - ); - const phoneOptions = optionGroup.phoneOptions.map( - option => { - if (typeof option.id === 'number') { - option.id = genSelectOptionId(); - } - return option; - } - ); - const locationOptions = optionGroup.locationOptions.map( - option => { - if (typeof option.id === 'number') { - option.id = genSelectOptionId(); - } - return option; - } - ); - - const newProperty = await updateProperty({ - ...property, - emailOptions, - phoneOptions, - 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, - }), - }); - } else { - await setPendant(property, newValue); - } - - if (fieldTitle !== property.name) { - await updateProperty({ - ...property, - name: fieldTitle, - }); - } + await onUpdateSure({ + type, + newPropertyItem, + newValue, + fieldName, + }); onSure?.(); }} onDelete={ 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 new file mode 100644 index 0000000000..83889962ac --- /dev/null +++ b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts @@ -0,0 +1,265 @@ +import type { CSSProperties } from 'react'; +import { + genSelectOptionId, + type InformationProperty, + type MultiSelectProperty, + type RecastMetaProperty, + type SelectOption, + type SelectProperty, + useRecastBlockMeta, + useSelectProperty, +} from '../../recast-block'; +import { type AsyncBlock } from '../../editor'; +import { usePendant } from '../use-pendant'; +import { + type OptionType, + PendantTypes, + type TempInformationType, +} from '../types'; +import { + checkPendantForm, + getOfficialSelected, + getPendantConfigByType, +} from '../utils'; +import { message } from '@toeverything/components/ui'; + +type SelectPropertyType = MultiSelectProperty | SelectProperty; +type SureParams = { + fieldName: string; + type: PendantTypes; + newPropertyItem: any; + newValue: any; +}; + +const genOptionWithId = (options: OptionType[] = []) => { + return options.map((option: OptionType) => ({ + ...option, + id: genSelectOptionId(), + })); +}; +// Callback function for pendant create +export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => { + const { addProperty } = useRecastBlockMeta(); + const { createSelect } = useSelectProperty(); + const { setPendant } = usePendant(block); + + return async ({ + type, + fieldName, + newPropertyItem, + newValue, + }: SureParams) => { + const checkResult = checkPendantForm( + type, + fieldName, + newPropertyItem, + newValue + ); + + if (!checkResult.passed) { + await message.error(checkResult.message); + return; + } + + if ( + type === PendantTypes.MultiSelect || + type === PendantTypes.Select || + type === PendantTypes.Status + ) { + const newProperty = await createSelect({ + name: fieldName, + options: newPropertyItem, + type, + }); + + const selectedId = getOfficialSelected({ + isMulti: type === PendantTypes.MultiSelect, + options: newProperty.options, + tempOptions: newPropertyItem, + tempSelectedId: newValue, + }); + + await setPendant(newProperty, selectedId); + } else if (type === PendantTypes.Information) { + const emailOptions = genOptionWithId(newPropertyItem.emailOptions); + + const phoneOptions = genOptionWithId(newPropertyItem.phoneOptions); + + const locationOptions = genOptionWithId( + newPropertyItem.locationOptions + ); + + const newProperty = await addProperty({ + type, + name: fieldName, + emailOptions, + phoneOptions, + 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, + }), + }); + } else { + // TODO: Color and background should use pendant config, but ui is not design now + const iconConfig = getPendantConfigByType(type); + // TODO: Color and background should be choose by user in the future + const newProperty = await addProperty({ + type: type, + name: fieldName, + background: + iconConfig.background as CSSProperties['background'], + color: iconConfig.color as CSSProperties['color'], + iconName: iconConfig.iconName, + }); + + await setPendant(newProperty, newValue); + } + }; +}; + +// Callback function for pendant update +export const useOnUpdateSure = ({ + block, + property, +}: { + block: AsyncBlock; + property: RecastMetaProperty; +}) => { + const { updateSelect } = useSelectProperty(); + const { setPendant } = usePendant(block); + const { updateProperty } = useRecastBlockMeta(); + + return async ({ + type, + fieldName, + newPropertyItem, + newValue, + }: SureParams) => { + const checkResult = checkPendantForm( + type, + fieldName, + newPropertyItem, + newValue + ); + + if (!checkResult.passed) { + await message.error(checkResult.message); + return; + } + + if ( + type === PendantTypes.MultiSelect || + type === PendantTypes.Select || + type === PendantTypes.Status + ) { + const newOptions = newPropertyItem as OptionType[]; + let selectProperty = property as SelectPropertyType; + const deleteOptionIds = selectProperty.options + .filter(o => { + return !newOptions.find(no => no.id === o.id); + }) + .map(o => o.id); + const addOptions = newOptions.filter(o => typeof o.id === 'number'); + + const { addSelectOptions, removeSelectOptions } = + updateSelect(selectProperty); + + deleteOptionIds.length && + (selectProperty = (await removeSelectOptions( + ...deleteOptionIds + )) as SelectPropertyType); + + addOptions.length && + (selectProperty = (await addSelectOptions( + ...(addOptions as unknown as Omit[]) + )) as SelectPropertyType); + + const selectedId = getOfficialSelected({ + isMulti: type === PendantTypes.MultiSelect, + options: selectProperty.options, + tempOptions: newPropertyItem, + tempSelectedId: newValue, + }); + + await setPendant(selectProperty, selectedId); + } else if (type === PendantTypes.Information) { + // const { emailOptions, phoneOptions, locationOptions } = + // property as InformationProperty; + const optionGroup = newPropertyItem as TempInformationType; + + const emailOptions = optionGroup.emailOptions.map(option => { + if (typeof option.id === 'number') { + option.id = genSelectOptionId(); + } + return option; + }); + const phoneOptions = optionGroup.phoneOptions.map(option => { + if (typeof option.id === 'number') { + option.id = genSelectOptionId(); + } + return option; + }); + const locationOptions = optionGroup.locationOptions.map(option => { + if (typeof option.id === 'number') { + option.id = genSelectOptionId(); + } + return option; + }); + + const newProperty = await updateProperty({ + ...property, + emailOptions, + phoneOptions, + 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, + }), + }); + } else { + await setPendant(property, newValue); + } + + if (fieldName !== property.name) { + await updateProperty({ + ...property, + name: fieldName, + }); + } + }; +}; diff --git a/libs/components/editor-core/src/block-pendant/utils.ts b/libs/components/editor-core/src/block-pendant/utils.ts index 33b59fdc50..d2807dbb85 100644 --- a/libs/components/editor-core/src/block-pendant/utils.ts +++ b/libs/components/editor-core/src/block-pendant/utils.ts @@ -1,4 +1,5 @@ import { + PropertyType, RecastBlockValue, RecastPropertyId, SelectOption, @@ -175,3 +176,49 @@ export const genInitialOptions = ( } return [genBasicOption({ index: 0, iconConfig })]; }; + +export const checkPendantForm = ( + type: PropertyType, + fieldTitle: string, + newProperty: any, + newValue: any +): { passed: boolean; message: string } => { + if (!fieldTitle) { + return { passed: false, message: 'The field title cannot be empty !' }; + } + + if ( + type === PendantTypes.MultiSelect || + type === PendantTypes.Select || + type === PendantTypes.Status + ) { + if (!newProperty) { + return { + passed: false, + message: 'Ensure at least one non-empty option !', + }; + } + } + if (type === PendantTypes.Information) { + if (!newProperty) { + return { + passed: false, + message: 'Ensure at least one non-empty option !', + }; + } + } + if ( + type === PendantTypes.Text || + type === PendantTypes.Date || + type === PendantTypes.Mention + ) { + if (!newValue) { + return { + passed: false, + message: `The content of the input must not be empty !`, + }; + } + } + + return { passed: true, message: 'Check passed !' }; +};