mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 04:48:53 +00:00
refactor: abstract the logic of the value history to the recastBlock
This commit is contained in:
@@ -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<{
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
67
libs/components/editor-core/src/recast-block/history.ts
Normal file
67
libs/components/editor-core/src/recast-block/history.ts
Normal 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));
|
||||
};
|
||||
@@ -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 = (
|
||||
|
||||
Reference in New Issue
Block a user