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 { getPendantHistory } from '../utils';
import {
getRecastItemValue,
RecastMetaProperty,
@@ -30,6 +29,7 @@ export const PendantHistoryPanel = ({
const [history, setHistory] = useState<RecastBlockValue[]>([]);
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<{

View File

@@ -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 (

View File

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

View File

@@ -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<InformationProperty, 'id'>);
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) {

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 {
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

View File

@@ -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:

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,
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 = (