refactor: abstract the logic of the value history to the recastBlock

This commit is contained in:
QiShaoXuan
2022-08-03 18:00:00 +08:00
parent fe9a4470fc
commit 13b608277d
9 changed files with 241 additions and 195 deletions

View File

@@ -1,5 +1,4 @@
import React, { ReactNode, useRef, useEffect, useState } from 'react'; import React, { ReactNode, useRef, useEffect, useState } from 'react';
import { getPendantHistory } from '../utils';
import { import {
getRecastItemValue, getRecastItemValue,
RecastMetaProperty, RecastMetaProperty,
@@ -30,6 +29,7 @@ export const PendantHistoryPanel = ({
const [history, setHistory] = useState<RecastBlockValue[]>([]); const [history, setHistory] = useState<RecastBlockValue[]>([]);
const popoverHandlerRef = useRef<{ [key: string]: PopperHandler }>({}); const popoverHandlerRef = useRef<{ [key: string]: PopperHandler }>({});
const { getValueHistory } = getRecastItemValue(block);
useEffect(() => { useEffect(() => {
const init = async () => { const init = async () => {
@@ -38,7 +38,7 @@ export const PendantHistoryPanel = ({
const missProperties = allProperties.filter( const missProperties = allProperties.filter(
property => !currentBlockValues.find(v => v.id === property.id) property => !currentBlockValues.find(v => v.id === property.id)
); );
const pendantHistory = getPendantHistory({ const pendantHistory = getValueHistory({
recastBlockId: recastBlock.id, recastBlockId: recastBlock.id,
}); });
const historyMap = missProperties.reduce<{ const historyMap = missProperties.reduce<{

View File

@@ -18,7 +18,9 @@ export default ({
user: { username, nickname, photo }, user: { username, nickname, photo },
} = useUserAndSpaces(); } = useUserAndSpaces();
const [selectedValue, setSelectedValue] = useState(initialValue?.value); const [selectedValue, setSelectedValue] = useState(
initialValue?.value || ''
);
const [focus, setFocus] = useState(false); const [focus, setFocus] = useState(false);
const theme = useTheme(); const theme = useTheme();
return ( return (

View File

@@ -4,11 +4,11 @@ import { HelpCenterIcon } from '@toeverything/components/icons';
import { PendantModifyPanel } from '../pendant-modify-panel'; import { PendantModifyPanel } from '../pendant-modify-panel';
import type { AsyncBlock } from '../../editor'; import type { AsyncBlock } from '../../editor';
import { import {
getRecastItemValue,
type RecastBlockValue, type RecastBlockValue,
type RecastMetaProperty, type RecastMetaProperty,
} from '../../recast-block'; } from '../../recast-block';
import { getPendantConfigByType } from '../utils'; import { getPendantConfigByType } from '../utils';
import { usePendant } from '../use-pendant';
import { import {
StyledPopoverWrapper, StyledPopoverWrapper,
StyledOperationLabel, StyledOperationLabel,
@@ -42,7 +42,8 @@ export const UpdatePendantPanel = ({
}: Props) => { }: Props) => {
const pendantOption = pendantOptions.find(v => v.type === property.type); const pendantOption = pendantOptions.find(v => v.type === property.type);
const iconConfig = getPendantConfigByType(property.type); const iconConfig = getPendantConfigByType(property.type);
const { removePendant } = usePendant(block); const { removeValue } = getRecastItemValue(block);
const Icon = IconMap[iconConfig.iconName]; const Icon = IconMap[iconConfig.iconName];
const [fieldName, setFieldName] = useState(property.name); const [fieldName, setFieldName] = useState(property.name);
const onUpdateSure = useOnUpdateSure({ block, property }); const onUpdateSure = useOnUpdateSure({ block, property });
@@ -108,7 +109,7 @@ export const UpdatePendantPanel = ({
onDelete={ onDelete={
hasDelete hasDelete
? async () => { ? async () => {
await removePendant(property); await removeValue(property.id);
} }
: null : null
} }

View File

@@ -1,16 +1,23 @@
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import { import {
genSelectOptionId, genSelectOptionId,
getRecastItemValue,
type InformationProperty, type InformationProperty,
type MultiSelectProperty, type MultiSelectProperty,
type RecastMetaProperty, type RecastMetaProperty,
type SelectOption, type SelectOption,
type SelectProperty, type SelectProperty,
useRecastBlock,
useRecastBlockMeta, useRecastBlockMeta,
useSelectProperty, useSelectProperty,
SelectValue,
MultiSelectValue,
StatusValue,
InformationValue,
TextValue,
DateValue,
} from '../../recast-block'; } from '../../recast-block';
import { type AsyncBlock } from '../../editor'; import { type AsyncBlock } from '../../editor';
import { usePendant } from '../use-pendant';
import { import {
type OptionType, type OptionType,
PendantTypes, PendantTypes,
@@ -41,8 +48,8 @@ const genOptionWithId = (options: OptionType[] = []) => {
export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => { export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
const { addProperty } = useRecastBlockMeta(); const { addProperty } = useRecastBlockMeta();
const { createSelect } = useSelectProperty(); const { createSelect } = useSelectProperty();
const { setPendant } = usePendant(block); const recastBlock = useRecastBlock();
const { setValue } = getRecastItemValue(block);
return async ({ return async ({
type, type,
fieldName, fieldName,
@@ -79,7 +86,15 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
tempSelectedId: newValue, 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) { } else if (type === PendantTypes.Information) {
const emailOptions = genOptionWithId(newPropertyItem.emailOptions); const emailOptions = genOptionWithId(newPropertyItem.emailOptions);
@@ -97,7 +112,11 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
locationOptions, locationOptions,
} as Omit<InformationProperty, 'id'>); } as Omit<InformationProperty, 'id'>);
await setPendant(newProperty, { await setValue(
{
id: newProperty.id,
type: newProperty.type,
value: {
email: getOfficialSelected({ email: getOfficialSelected({
isMulti: true, isMulti: true,
options: emailOptions, options: emailOptions,
@@ -116,7 +135,11 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
tempOptions: newPropertyItem.locationOptions, tempOptions: newPropertyItem.locationOptions,
tempSelectedId: newValue.location, tempSelectedId: newValue.location,
}), }),
}); },
} as InformationValue,
recastBlock.id,
newProperty.id
);
} else { } else {
// TODO: Color and background should use pendant config, but ui is not design now // TODO: Color and background should use pendant config, but ui is not design now
const iconConfig = getPendantConfigByType(type); const iconConfig = getPendantConfigByType(type);
@@ -129,8 +152,15 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
color: iconConfig.color as CSSProperties['color'], color: iconConfig.color as CSSProperties['color'],
iconName: iconConfig.iconName, iconName: iconConfig.iconName,
}); });
await setValue(
await setPendant(newProperty, newValue); {
id: newProperty.id,
type: newProperty.type,
value: newValue,
} as TextValue | DateValue,
recastBlock.id,
newProperty.id
);
} }
}; };
}; };
@@ -144,8 +174,9 @@ export const useOnUpdateSure = ({
property: RecastMetaProperty; property: RecastMetaProperty;
}) => { }) => {
const { updateSelect } = useSelectProperty(); const { updateSelect } = useSelectProperty();
const { setPendant } = usePendant(block);
const { updateProperty } = useRecastBlockMeta(); const { updateProperty } = useRecastBlockMeta();
const { setValue } = getRecastItemValue(block);
const recastBlock = useRecastBlock();
return async ({ return async ({
type, type,
@@ -199,7 +230,15 @@ export const useOnUpdateSure = ({
tempSelectedId: newValue, 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) { } else if (type === PendantTypes.Information) {
// const { emailOptions, phoneOptions, locationOptions } = // const { emailOptions, phoneOptions, locationOptions } =
// property as InformationProperty; // property as InformationProperty;
@@ -231,7 +270,11 @@ export const useOnUpdateSure = ({
locationOptions, locationOptions,
} as InformationProperty); } as InformationProperty);
await setPendant(newProperty, { await setValue(
{
id: newProperty.id,
type: newProperty.type,
value: {
email: getOfficialSelected({ email: getOfficialSelected({
isMulti: true, isMulti: true,
options: emailOptions as SelectOption[], options: emailOptions as SelectOption[],
@@ -250,9 +293,21 @@ export const useOnUpdateSure = ({
tempOptions: newPropertyItem.locationOptions, tempOptions: newPropertyItem.locationOptions,
tempSelectedId: newValue.location, tempSelectedId: newValue.location,
}), }),
}); },
} as InformationValue,
recastBlock.id,
newProperty.id
);
} else { } 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) { if (fieldName !== property.name) {

View File

@@ -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);
};

View File

@@ -1,124 +1,7 @@
import { import { PropertyType, SelectOption } from '../recast-block';
getRecastItemValue,
PropertyType,
RecastBlock,
RecastItem,
RecastMetaProperty,
RecastPropertyId,
SelectOption,
} from '../recast-block';
import { OptionIdType, OptionType, PendantConfig, PendantTypes } from './types'; import { OptionIdType, OptionType, PendantConfig, PendantTypes } from './types';
import { pendantConfig } from './config'; import { pendantConfig } from './config';
import { nanoid } from 'nanoid'; 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 * In select pendant panel, use mock options instead of use `createSelect` when add or delete option

View File

@@ -13,7 +13,6 @@ import {
generateInitialOptions, generateInitialOptions,
generateRandomFieldName, generateRandomFieldName,
getPendantIconsConfigByName, getPendantIconsConfigByName,
getPendantController,
} from '../block-pendant/utils'; } from '../block-pendant/utils';
import { SelectOption } from '../recast-block'; import { SelectOption } from '../recast-block';
@@ -117,31 +116,60 @@ export const moveCardToGroup = async ({
group: KanbanGroup; group: KanbanGroup;
recastBlock: RecastBlock; recastBlock: RecastBlock;
}) => { }) => {
const { setPendant, removePendant } = getPendantController( const { setValue, removeValue } = getRecastItemValue(cardBlock);
recastBlock,
cardBlock
);
let success = false; let success = false;
if (group.id === DEFAULT_GROUP_ID) { if (group.id === DEFAULT_GROUP_ID) {
success = await removePendant(groupBy); success = await removeValue(groupBy.id);
return false; return false;
} }
switch (group.type) { switch (group.type) {
case PropertyType.Select: { 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; break;
} }
case PropertyType.Status: { 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; break;
} }
case PropertyType.MultiSelect: { 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; break;
} }
case PropertyType.Text: { 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; break;
} }
default: default:

View File

@@ -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));
};

View File

@@ -15,6 +15,7 @@ import {
SelectProperty, SelectProperty,
TABLE_VALUES_KEY, TABLE_VALUES_KEY,
} from './types'; } from './types';
import { getHistory, removeHistory, setHistory } from './history';
/** /**
* Generate a unique id for a property * Generate a unique id for a property
@@ -240,7 +241,17 @@ export const getRecastItemValue = (block: RecastItem | AsyncBlock) => {
return props[id]; 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, { return recastItem.setProperty(TABLE_VALUES_KEY, {
...props, ...props,
[newValue.id]: newValue, [newValue.id]: newValue,
@@ -249,9 +260,18 @@ export const getRecastItemValue = (block: RecastItem | AsyncBlock) => {
const removeValue = (propertyId: RecastPropertyId) => { const removeValue = (propertyId: RecastPropertyId) => {
const { [propertyId]: omitted, ...restProps } = props; const { [propertyId]: omitted, ...restProps } = props;
removeHistory({
recastBlockId: block.id,
propertyId: propertyId,
});
return recastItem.setProperty(TABLE_VALUES_KEY, restProps); return recastItem.setProperty(TABLE_VALUES_KEY, restProps);
}; };
return { getAllValue, getValue, setValue, removeValue };
const getValueHistory = getHistory;
return { getAllValue, getValue, setValue, removeValue, getValueHistory };
}; };
const isSelectLikeProperty = ( const isSelectLikeProperty = (