Merge pull request #44 from toeverything/feat/block-pendant
Feat/block pendant
This commit is contained in:
@@ -41,6 +41,7 @@ const getKanbanColor = (
|
|||||||
return DEFAULT_COLOR;
|
return DEFAULT_COLOR;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
group.type === PropertyType.Status ||
|
||||||
group.type === PropertyType.Select ||
|
group.type === PropertyType.Select ||
|
||||||
group.type === PropertyType.MultiSelect ||
|
group.type === PropertyType.MultiSelect ||
|
||||||
group.type === DEFAULT_GROUP_ID
|
group.type === DEFAULT_GROUP_ID
|
||||||
|
|||||||
@@ -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,22 +29,22 @@ 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 () => {
|
||||||
const currentBlockValues = getRecastItemValue(block).getAllValue();
|
const currentBlockValues = getRecastItemValue(block).getAllValue();
|
||||||
const allProperties = getProperties();
|
const missValues = getProperties().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 valueHistory = getValueHistory({
|
||||||
recastBlockId: recastBlock.id,
|
recastBlockId: recastBlock.id,
|
||||||
});
|
});
|
||||||
const historyMap = missProperties.reduce<{
|
const historyMap = missValues.reduce<{
|
||||||
[key: RecastPropertyId]: string;
|
[key: RecastPropertyId]: string[];
|
||||||
}>((history, property) => {
|
}>((history, property) => {
|
||||||
if (pendantHistory[property.id]) {
|
if (valueHistory[property.id]) {
|
||||||
history[property.id] = pendantHistory[property.id];
|
history[property.id] = valueHistory[property.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
return history;
|
return history;
|
||||||
@@ -54,18 +53,30 @@ export const PendantHistoryPanel = ({
|
|||||||
const blockHistory = (
|
const blockHistory = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.entries(historyMap).map(
|
Object.entries(historyMap).map(
|
||||||
async ([propertyId, blockId]) => {
|
async ([propertyId, blockIds]) => {
|
||||||
const latestValueBlock = (
|
const blocks = await groupBlock.children();
|
||||||
await groupBlock.children()
|
const latestChangeBlock = blockIds
|
||||||
).find((block: AsyncBlock) => block.id === blockId);
|
.reverse()
|
||||||
|
.reduce<AsyncBlock>((block, id) => {
|
||||||
|
if (!block) {
|
||||||
|
return blocks.find(
|
||||||
|
block => block.id === id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}, null);
|
||||||
|
|
||||||
return getRecastItemValue(
|
if (latestChangeBlock) {
|
||||||
latestValueBlock
|
return getRecastItemValue(
|
||||||
).getValue(propertyId as RecastPropertyId);
|
latestChangeBlock
|
||||||
|
).getValue(propertyId as RecastPropertyId);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).filter(v => v);
|
).filter(v => v);
|
||||||
|
|
||||||
setHistory(blockHistory);
|
setHistory(blockHistory);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ModifyPanelContentProps } from './types';
|
|||||||
import { StyledDivider, StyledPopoverSubTitle } from '../StyledComponent';
|
import { StyledDivider, StyledPopoverSubTitle } from '../StyledComponent';
|
||||||
import { BasicSelect } from './Select';
|
import { BasicSelect } from './Select';
|
||||||
import { InformationProperty, InformationValue } from '../../recast-block';
|
import { InformationProperty, InformationValue } from '../../recast-block';
|
||||||
import { genInitialOptions, getPendantIconsConfigByName } from '../utils';
|
import { generateInitialOptions, getPendantIconsConfigByName } from '../utils';
|
||||||
|
|
||||||
export default (props: ModifyPanelContentProps) => {
|
export default (props: ModifyPanelContentProps) => {
|
||||||
const { onPropertyChange, onValueChange, initialValue, property } = props;
|
const { onPropertyChange, onValueChange, initialValue, property } = props;
|
||||||
@@ -38,7 +38,7 @@ export default (props: ModifyPanelContentProps) => {
|
|||||||
}}
|
}}
|
||||||
initialOptions={
|
initialOptions={
|
||||||
propProperty?.emailOptions ||
|
propProperty?.emailOptions ||
|
||||||
genInitialOptions(
|
generateInitialOptions(
|
||||||
property?.type,
|
property?.type,
|
||||||
getPendantIconsConfigByName('Email')
|
getPendantIconsConfigByName('Email')
|
||||||
)
|
)
|
||||||
@@ -66,7 +66,7 @@ export default (props: ModifyPanelContentProps) => {
|
|||||||
}}
|
}}
|
||||||
initialOptions={
|
initialOptions={
|
||||||
propProperty?.phoneOptions ||
|
propProperty?.phoneOptions ||
|
||||||
genInitialOptions(
|
generateInitialOptions(
|
||||||
property?.type,
|
property?.type,
|
||||||
getPendantIconsConfigByName('Phone')
|
getPendantIconsConfigByName('Phone')
|
||||||
)
|
)
|
||||||
@@ -94,7 +94,7 @@ export default (props: ModifyPanelContentProps) => {
|
|||||||
}}
|
}}
|
||||||
initialOptions={
|
initialOptions={
|
||||||
propProperty?.locationOptions ||
|
propProperty?.locationOptions ||
|
||||||
genInitialOptions(
|
generateInitialOptions(
|
||||||
property?.type,
|
property?.type,
|
||||||
getPendantIconsConfigByName('Location')
|
getPendantIconsConfigByName('Location')
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
import { HighLightIconInput } from './IconInput';
|
import { HighLightIconInput } from './IconInput';
|
||||||
import { PendantConfig, IconNames, OptionIdType, OptionType } from '../types';
|
import { PendantConfig, IconNames, OptionIdType, OptionType } from '../types';
|
||||||
import { genBasicOption } from '../utils';
|
import { generateBasicOption } from '../utils';
|
||||||
|
|
||||||
type OptionItemType = {
|
type OptionItemType = {
|
||||||
option: OptionType;
|
option: OptionType;
|
||||||
@@ -66,7 +66,7 @@ export const BasicSelect = ({
|
|||||||
const [selectIds, setSelectIds] = useState<OptionIdType[]>(initialValue);
|
const [selectIds, setSelectIds] = useState<OptionIdType[]>(initialValue);
|
||||||
|
|
||||||
const insertOption = (insertId: OptionIdType) => {
|
const insertOption = (insertId: OptionIdType) => {
|
||||||
const newOption = genBasicOption({
|
const newOption = generateBasicOption({
|
||||||
index: options.length + 1,
|
index: options.length + 1,
|
||||||
iconConfig,
|
iconConfig,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { Input, Option, Select, Tooltip } from '@toeverything/components/ui';
|
import { Input, Option, Select, Tooltip } from '@toeverything/components/ui';
|
||||||
import { HelpCenterIcon } from '@toeverything/components/icons';
|
import { HelpCenterIcon } from '@toeverything/components/icons';
|
||||||
import { AsyncBlock } from '../../editor';
|
import { AsyncBlock } from '../../editor';
|
||||||
@@ -15,13 +14,13 @@ import {
|
|||||||
StyledPopoverSubTitle,
|
StyledPopoverSubTitle,
|
||||||
StyledPopoverWrapper,
|
StyledPopoverWrapper,
|
||||||
} from '../StyledComponent';
|
} from '../StyledComponent';
|
||||||
import { genInitialOptions, getPendantConfigByType } from '../utils';
|
import {
|
||||||
|
generateRandomFieldName,
|
||||||
|
generateInitialOptions,
|
||||||
|
getPendantConfigByType,
|
||||||
|
} from '../utils';
|
||||||
import { useOnCreateSure } from './hooks';
|
import { useOnCreateSure } from './hooks';
|
||||||
|
|
||||||
const upperFirst = (str: string) => {
|
|
||||||
return `${str[0].toUpperCase()}${str.slice(1)}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CreatePendantPanel = ({
|
export const CreatePendantPanel = ({
|
||||||
block,
|
block,
|
||||||
onSure,
|
onSure,
|
||||||
@@ -35,7 +34,7 @@ export const CreatePendantPanel = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedOption &&
|
selectedOption &&
|
||||||
setFieldName(upperFirst(`${selectedOption.type}#${nanoid(4)}`));
|
setFieldName(generateRandomFieldName(selectedOption.type));
|
||||||
}, [selectedOption]);
|
}, [selectedOption]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -45,7 +44,7 @@ export const CreatePendantPanel = ({
|
|||||||
<Select
|
<Select
|
||||||
width={284}
|
width={284}
|
||||||
placeholder="Search for a field type"
|
placeholder="Search for a field type"
|
||||||
value={selectedOption}
|
value={selectedOption ?? null}
|
||||||
onChange={(selectedValue: PendantOptions) => {
|
onChange={(selectedValue: PendantOptions) => {
|
||||||
setSelectedOption(selectedValue);
|
setSelectedOption(selectedValue);
|
||||||
}}
|
}}
|
||||||
@@ -93,7 +92,7 @@ export const CreatePendantPanel = ({
|
|||||||
<PendantModifyPanel
|
<PendantModifyPanel
|
||||||
type={selectedOption.type}
|
type={selectedOption.type}
|
||||||
// Select, MultiSelect, Status use this props as initial property
|
// Select, MultiSelect, Status use this props as initial property
|
||||||
initialOptions={genInitialOptions(
|
initialOptions={generateInitialOptions(
|
||||||
selectedOption.type,
|
selectedOption.type,
|
||||||
getPendantConfigByType(selectedOption.type)
|
getPendantConfigByType(selectedOption.type)
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,14 @@ 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
|
||||||
|
);
|
||||||
} else if (type === PendantTypes.Information) {
|
} else if (type === PendantTypes.Information) {
|
||||||
const emailOptions = genOptionWithId(newPropertyItem.emailOptions);
|
const emailOptions = genOptionWithId(newPropertyItem.emailOptions);
|
||||||
|
|
||||||
@@ -97,26 +111,33 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
|
|||||||
locationOptions,
|
locationOptions,
|
||||||
} as Omit<InformationProperty, 'id'>);
|
} as Omit<InformationProperty, 'id'>);
|
||||||
|
|
||||||
await setPendant(newProperty, {
|
await setValue(
|
||||||
email: getOfficialSelected({
|
{
|
||||||
isMulti: true,
|
id: newProperty.id,
|
||||||
options: emailOptions,
|
type: newProperty.type,
|
||||||
tempOptions: newPropertyItem.emailOptions,
|
value: {
|
||||||
tempSelectedId: newValue.email,
|
email: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
phone: getOfficialSelected({
|
options: emailOptions,
|
||||||
isMulti: true,
|
tempOptions: newPropertyItem.emailOptions,
|
||||||
options: phoneOptions,
|
tempSelectedId: newValue.email,
|
||||||
tempOptions: newPropertyItem.phoneOptions,
|
}),
|
||||||
tempSelectedId: newValue.phone,
|
phone: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
location: getOfficialSelected({
|
options: phoneOptions,
|
||||||
isMulti: true,
|
tempOptions: newPropertyItem.phoneOptions,
|
||||||
options: locationOptions,
|
tempSelectedId: newValue.phone,
|
||||||
tempOptions: newPropertyItem.locationOptions,
|
}),
|
||||||
tempSelectedId: newValue.location,
|
location: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
});
|
options: locationOptions,
|
||||||
|
tempOptions: newPropertyItem.locationOptions,
|
||||||
|
tempSelectedId: newValue.location,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
} as InformationValue,
|
||||||
|
recastBlock.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 +150,14 @@ 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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -144,8 +171,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 +227,14 @@ 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
|
||||||
|
);
|
||||||
} 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,28 +266,42 @@ export const useOnUpdateSure = ({
|
|||||||
locationOptions,
|
locationOptions,
|
||||||
} as InformationProperty);
|
} as InformationProperty);
|
||||||
|
|
||||||
await setPendant(newProperty, {
|
await setValue(
|
||||||
email: getOfficialSelected({
|
{
|
||||||
isMulti: true,
|
id: newProperty.id,
|
||||||
options: emailOptions as SelectOption[],
|
type: newProperty.type,
|
||||||
tempOptions: newPropertyItem.emailOptions,
|
value: {
|
||||||
tempSelectedId: newValue.email,
|
email: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
phone: getOfficialSelected({
|
options: emailOptions as SelectOption[],
|
||||||
isMulti: true,
|
tempOptions: newPropertyItem.emailOptions,
|
||||||
options: phoneOptions as SelectOption[],
|
tempSelectedId: newValue.email,
|
||||||
tempOptions: newPropertyItem.phoneOptions,
|
}),
|
||||||
tempSelectedId: newValue.phone,
|
phone: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
location: getOfficialSelected({
|
options: phoneOptions as SelectOption[],
|
||||||
isMulti: true,
|
tempOptions: newPropertyItem.phoneOptions,
|
||||||
options: locationOptions as SelectOption[],
|
tempSelectedId: newValue.phone,
|
||||||
tempOptions: newPropertyItem.locationOptions,
|
}),
|
||||||
tempSelectedId: newValue.location,
|
location: getOfficialSelected({
|
||||||
}),
|
isMulti: true,
|
||||||
});
|
options: locationOptions as SelectOption[],
|
||||||
|
tempOptions: newPropertyItem.locationOptions,
|
||||||
|
tempSelectedId: newValue.location,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
} as InformationValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await setPendant(property, newValue);
|
await setValue(
|
||||||
|
{
|
||||||
|
id: property.id,
|
||||||
|
type: property.type,
|
||||||
|
value: newValue,
|
||||||
|
} as TextValue | DateValue,
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fieldName !== property.name) {
|
if (fieldName !== property.name) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
MuiZoom,
|
MuiFade,
|
||||||
Popover,
|
Popover,
|
||||||
PopperHandler,
|
PopperHandler,
|
||||||
styled,
|
styled,
|
||||||
@@ -100,16 +100,15 @@ export const PendantRender = ({ block }: { block: AsyncBlock }) => {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{hasAddBtn ? (
|
{hasAddBtn ? (
|
||||||
<MuiZoom in={showAddBtn}>
|
<MuiFade in={showAddBtn}>
|
||||||
<div>
|
<div>
|
||||||
<AddPendantPopover
|
<AddPendantPopover
|
||||||
block={block}
|
block={block}
|
||||||
iconStyle={{ marginTop: 4 }}
|
iconStyle={{ marginTop: 4 }}
|
||||||
container={blockRenderContainerRef.current}
|
container={blockRenderContainerRef.current}
|
||||||
trigger="click"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</MuiZoom>
|
</MuiFade>
|
||||||
) : null}
|
) : null}
|
||||||
</BlockPendantContainer>
|
</BlockPendantContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
import { removePropertyValueRecord, setPendantHistory } from './utils';
|
|
||||||
import { AsyncBlock } from '../editor';
|
|
||||||
import {
|
|
||||||
getRecastItemValue,
|
|
||||||
RecastMetaProperty,
|
|
||||||
useRecastBlock,
|
|
||||||
} from '../recast-block';
|
|
||||||
|
|
||||||
export const usePendant = (block: AsyncBlock) => {
|
|
||||||
// const { getProperties, removeProperty } = useRecastBlockMeta();
|
|
||||||
const recastBlock = useRecastBlock();
|
|
||||||
const { getValue, setValue, removeValue } = getRecastItemValue(block);
|
|
||||||
// const { updateSelect } = useSelectProperty();
|
|
||||||
|
|
||||||
const setPendant = async (property: RecastMetaProperty, newValue: any) => {
|
|
||||||
const nv = {
|
|
||||||
id: property.id,
|
|
||||||
type: property.type,
|
|
||||||
value: newValue,
|
|
||||||
};
|
|
||||||
await setValue(nv);
|
|
||||||
setPendantHistory({
|
|
||||||
recastBlockId: recastBlock.id,
|
|
||||||
blockId: block.id,
|
|
||||||
propertyId: property.id,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const removePendant = async (property: RecastMetaProperty) => {
|
|
||||||
await removeValue(property.id);
|
|
||||||
removePropertyValueRecord({
|
|
||||||
recastBlockId: block.id,
|
|
||||||
propertyId: property.id,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
setPendant,
|
|
||||||
removePendant,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,84 +1,7 @@
|
|||||||
import {
|
import { PropertyType, SelectOption } from '../recast-block';
|
||||||
PropertyType,
|
import { OptionIdType, OptionType, PendantConfig, PendantTypes } from './types';
|
||||||
RecastBlockValue,
|
|
||||||
RecastPropertyId,
|
|
||||||
SelectOption,
|
|
||||||
} from '../recast-block';
|
|
||||||
import { OptionIdType, OptionType } from './types';
|
|
||||||
import { pendantConfig } from './config';
|
import { pendantConfig } from './config';
|
||||||
import { PendantConfig, PendantTypes } from './types';
|
import { nanoid } from 'nanoid';
|
||||||
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 removePropertyValueRecord = ({
|
|
||||||
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));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@@ -107,7 +30,7 @@ export const getOfficialSelected = ({
|
|||||||
.map(id => {
|
.map(id => {
|
||||||
return tempOptions.findIndex((o: OptionType) => o.id === id);
|
return tempOptions.findIndex((o: OptionType) => o.id === id);
|
||||||
})
|
})
|
||||||
.filter(index => index != -1);
|
.filter(index => index !== -1);
|
||||||
selectedId = selectedIndex.map((index: number) => {
|
selectedId = selectedIndex.map((index: number) => {
|
||||||
return options[index].id;
|
return options[index].id;
|
||||||
});
|
});
|
||||||
@@ -130,7 +53,7 @@ export const getPendantIconsConfigByName = (
|
|||||||
return pendantConfig[pendantName];
|
return pendantConfig[pendantName];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const genBasicOption = ({
|
export const generateBasicOption = ({
|
||||||
index,
|
index,
|
||||||
iconConfig,
|
iconConfig,
|
||||||
name = '',
|
name = '',
|
||||||
@@ -159,22 +82,22 @@ export const genBasicOption = ({
|
|||||||
/**
|
/**
|
||||||
* Status Pendant is a Select Pendant built-in some options
|
* Status Pendant is a Select Pendant built-in some options
|
||||||
* **/
|
* **/
|
||||||
export const genInitialOptions = (
|
export const generateInitialOptions = (
|
||||||
type: PendantTypes,
|
type: PendantTypes,
|
||||||
iconConfig: PendantConfig
|
iconConfig: PendantConfig
|
||||||
) => {
|
) => {
|
||||||
if (type === PendantTypes.Status) {
|
if (type === PendantTypes.Status) {
|
||||||
return [
|
return [
|
||||||
genBasicOption({ index: 0, iconConfig, name: 'No Started' }),
|
generateBasicOption({ index: 0, iconConfig, name: 'No Started' }),
|
||||||
genBasicOption({
|
generateBasicOption({
|
||||||
index: 1,
|
index: 1,
|
||||||
iconConfig,
|
iconConfig,
|
||||||
name: 'In Progress',
|
name: 'In Progress',
|
||||||
}),
|
}),
|
||||||
genBasicOption({ index: 2, iconConfig, name: 'Complete' }),
|
generateBasicOption({ index: 2, iconConfig, name: 'Complete' }),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return [genBasicOption({ index: 0, iconConfig })];
|
return [generateBasicOption({ index: 0, iconConfig })];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkPendantForm = (
|
export const checkPendantForm = (
|
||||||
@@ -222,3 +145,10 @@ export const checkPendantForm = (
|
|||||||
|
|
||||||
return { passed: true, message: 'Check passed !' };
|
return { passed: true, message: 'Check passed !' };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const upperFirst = (str: string) => {
|
||||||
|
return `${str[0].toUpperCase()}${str.slice(1)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateRandomFieldName = (type: PendantTypes) =>
|
||||||
|
upperFirst(`${type}#${nanoid(4)}`);
|
||||||
|
|||||||
@@ -35,20 +35,6 @@ export class KeyboardManager {
|
|||||||
}
|
}
|
||||||
this.handler_map = {};
|
this.handler_map = {};
|
||||||
|
|
||||||
// WARNING: Remove the filter of hotkeys, the input event of input/select/textarea will be filtered out by default
|
|
||||||
// When there is a problem with the input of the text component, you need to pay attention to this
|
|
||||||
const old_filter = HotKeys.filter;
|
|
||||||
HotKeys.filter = event => {
|
|
||||||
let parent = (event.target as Element).parentElement;
|
|
||||||
while (parent) {
|
|
||||||
if (parent === editor.container) {
|
|
||||||
return old_filter(event);
|
|
||||||
}
|
|
||||||
parent = parent.parentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
HotKeys.setScope('editor');
|
HotKeys.setScope('editor');
|
||||||
|
|
||||||
// this.init_common_shortcut_cb();
|
// this.init_common_shortcut_cb();
|
||||||
|
|||||||
@@ -6,10 +6,15 @@ import {
|
|||||||
PropertyType,
|
PropertyType,
|
||||||
RecastBlockValue,
|
RecastBlockValue,
|
||||||
RecastMetaProperty,
|
RecastMetaProperty,
|
||||||
RecastPropertyId,
|
|
||||||
} from '../recast-block/types';
|
} from '../recast-block/types';
|
||||||
import type { DefaultGroup, KanbanGroup } from './types';
|
import type { DefaultGroup, KanbanGroup } from './types';
|
||||||
import { DEFAULT_GROUP_ID } from './types';
|
import { DEFAULT_GROUP_ID } from './types';
|
||||||
|
import {
|
||||||
|
generateInitialOptions,
|
||||||
|
generateRandomFieldName,
|
||||||
|
getPendantIconsConfigByName,
|
||||||
|
} from '../block-pendant/utils';
|
||||||
|
import { SelectOption } from '../recast-block';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - If the `groupBy` is `SelectProperty` or `MultiSelectProperty`, return `(Multi)SelectProperty.options`.
|
* - If the `groupBy` is `SelectProperty` or `MultiSelectProperty`, return `(Multi)SelectProperty.options`.
|
||||||
@@ -23,6 +28,7 @@ export const getGroupOptions = async (
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
switch (groupBy.type) {
|
switch (groupBy.type) {
|
||||||
|
case PropertyType.Status:
|
||||||
case PropertyType.Select:
|
case PropertyType.Select:
|
||||||
case PropertyType.MultiSelect: {
|
case PropertyType.MultiSelect: {
|
||||||
return groupBy.options.map(option => ({
|
return groupBy.options.map(option => ({
|
||||||
@@ -51,12 +57,15 @@ const isValueBelongOption = (
|
|||||||
option: KanbanGroup
|
option: KanbanGroup
|
||||||
) => {
|
) => {
|
||||||
switch (propertyValue.type) {
|
switch (propertyValue.type) {
|
||||||
case PropertyType.Select: {
|
case PropertyType.Select || PropertyType.Status: {
|
||||||
return propertyValue.value === option.id;
|
return propertyValue.value === option.id;
|
||||||
}
|
}
|
||||||
case PropertyType.MultiSelect: {
|
case PropertyType.MultiSelect: {
|
||||||
return propertyValue.value.some(i => i === option.id);
|
return propertyValue.value.some(i => i === option.id);
|
||||||
}
|
}
|
||||||
|
// case PropertyType.Status: {
|
||||||
|
// return propertyValue.value === option.id;
|
||||||
|
// }
|
||||||
// case PropertyType.Text: {
|
// case PropertyType.Text: {
|
||||||
// TOTODO:DO support this type
|
// TOTODO:DO support this type
|
||||||
// }
|
// }
|
||||||
@@ -96,40 +105,67 @@ export const calcCardGroup = (
|
|||||||
/**
|
/**
|
||||||
* Set group value for the card block
|
* Set group value for the card block
|
||||||
*/
|
*/
|
||||||
export const moveCardToGroup = async (
|
export const moveCardToGroup = async ({
|
||||||
groupById: RecastPropertyId,
|
groupBy,
|
||||||
cardBlock: RecastItem,
|
cardBlock,
|
||||||
group: KanbanGroup
|
group,
|
||||||
) => {
|
recastBlock,
|
||||||
|
}: {
|
||||||
|
groupBy: RecastMetaProperty;
|
||||||
|
cardBlock: RecastItem;
|
||||||
|
group: KanbanGroup;
|
||||||
|
recastBlock: RecastBlock;
|
||||||
|
}) => {
|
||||||
const { setValue, removeValue } = getRecastItemValue(cardBlock);
|
const { setValue, removeValue } = getRecastItemValue(cardBlock);
|
||||||
let success = false;
|
let success = false;
|
||||||
if (group.id === DEFAULT_GROUP_ID) {
|
if (group.id === DEFAULT_GROUP_ID) {
|
||||||
success = await removeValue(groupById);
|
success = await removeValue(groupBy.id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (group.type) {
|
switch (group.type) {
|
||||||
case PropertyType.Select: {
|
case PropertyType.Select: {
|
||||||
success = await setValue({
|
success = await setValue(
|
||||||
id: groupById,
|
{
|
||||||
type: group.type,
|
id: groupBy.id,
|
||||||
value: group.id,
|
type: group.type,
|
||||||
});
|
value: group.id,
|
||||||
|
},
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PropertyType.Status: {
|
||||||
|
success = await setValue(
|
||||||
|
{
|
||||||
|
id: groupBy.id,
|
||||||
|
type: group.type,
|
||||||
|
value: group.id,
|
||||||
|
},
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PropertyType.MultiSelect: {
|
case PropertyType.MultiSelect: {
|
||||||
success = await setValue({
|
success = await setValue(
|
||||||
id: groupById,
|
{
|
||||||
type: group.type,
|
id: groupBy.id,
|
||||||
value: [group.id],
|
type: group.type,
|
||||||
});
|
value: [group.id],
|
||||||
|
},
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PropertyType.Text: {
|
case PropertyType.Text: {
|
||||||
success = await setValue({
|
success = await setValue(
|
||||||
id: groupById,
|
{
|
||||||
type: group.type,
|
id: groupBy.id,
|
||||||
value: group.id,
|
type: group.type,
|
||||||
});
|
value: group.id,
|
||||||
|
},
|
||||||
|
recastBlock.id
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -194,14 +230,18 @@ export const genDefaultGroup = (groupBy: RecastMetaProperty): DefaultGroup => ({
|
|||||||
items: [],
|
items: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DEFAULT_GROUP_BY_PROPERTY = {
|
export const generateDefaultGroupByProperty = (): {
|
||||||
name: 'Status',
|
name: string;
|
||||||
options: [
|
options: Omit<SelectOption, 'id'>[];
|
||||||
{ name: 'No Started', color: '#E53535', background: '#FFCECE' },
|
type: PropertyType.Status;
|
||||||
{ name: 'In Progress', color: '#A77F1A', background: '#FFF5AB' },
|
} => ({
|
||||||
{ name: 'Complete', color: '#3C8867', background: '#C5FBE0' },
|
name: generateRandomFieldName(PropertyType.Status),
|
||||||
],
|
type: PropertyType.Status,
|
||||||
};
|
options: generateInitialOptions(
|
||||||
|
PropertyType.Status,
|
||||||
|
getPendantIconsConfigByName(PropertyType.Status)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unwrap blocks from the grid recursively.
|
* Unwrap blocks from the grid recursively.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export const useKanbanGroup = (groupBy: RecastMetaProperty) => {
|
|||||||
const { updateSelect } = useSelectProperty();
|
const { updateSelect } = useSelectProperty();
|
||||||
|
|
||||||
switch (groupBy.type) {
|
switch (groupBy.type) {
|
||||||
|
case PropertyType.Status:
|
||||||
case PropertyType.MultiSelect:
|
case PropertyType.MultiSelect:
|
||||||
case PropertyType.Select: {
|
case PropertyType.Select: {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import {
|
|||||||
import { supportChildren } from '../utils';
|
import { supportChildren } from '../utils';
|
||||||
import {
|
import {
|
||||||
calcCardGroup,
|
calcCardGroup,
|
||||||
DEFAULT_GROUP_BY_PROPERTY,
|
|
||||||
genDefaultGroup,
|
genDefaultGroup,
|
||||||
|
generateDefaultGroupByProperty,
|
||||||
getCardGroup,
|
getCardGroup,
|
||||||
getGroupOptions,
|
getGroupOptions,
|
||||||
moveCardToAfter,
|
moveCardToAfter,
|
||||||
@@ -48,6 +48,7 @@ export const useRecastKanbanGroupBy = () => {
|
|||||||
// Add other type groupBy support
|
// Add other type groupBy support
|
||||||
const supportedGroupBy = getProperties().filter(
|
const supportedGroupBy = getProperties().filter(
|
||||||
prop =>
|
prop =>
|
||||||
|
prop.type === PropertyType.Status ||
|
||||||
prop.type === PropertyType.Select ||
|
prop.type === PropertyType.Select ||
|
||||||
prop.type === PropertyType.MultiSelect
|
prop.type === PropertyType.MultiSelect
|
||||||
);
|
);
|
||||||
@@ -88,7 +89,8 @@ export const useRecastKanbanGroupBy = () => {
|
|||||||
// TODO: support other property type
|
// TODO: support other property type
|
||||||
if (
|
if (
|
||||||
groupByProperty.type !== PropertyType.Select &&
|
groupByProperty.type !== PropertyType.Select &&
|
||||||
groupByProperty.type !== PropertyType.MultiSelect
|
groupByProperty.type !== PropertyType.MultiSelect &&
|
||||||
|
groupByProperty.type !== PropertyType.Status
|
||||||
) {
|
) {
|
||||||
console.warn('Not support groupBy type', groupByProperty);
|
console.warn('Not support groupBy type', groupByProperty);
|
||||||
|
|
||||||
@@ -134,7 +136,7 @@ export const useInitKanbanEffect = ():
|
|||||||
}
|
}
|
||||||
// 3. no group by, no properties
|
// 3. no group by, no properties
|
||||||
// create a new property and set it as group by
|
// create a new property and set it as group by
|
||||||
const prop = await createSelect(DEFAULT_GROUP_BY_PROPERTY);
|
const prop = await createSelect(generateDefaultGroupByProperty());
|
||||||
await setGroupBy(prop.id);
|
await setGroupBy(prop.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -197,7 +199,12 @@ export const useRecastKanban = () => {
|
|||||||
beforeBlock: string | null,
|
beforeBlock: string | null,
|
||||||
afterBlock: string | null
|
afterBlock: string | null
|
||||||
) => {
|
) => {
|
||||||
await moveCardToGroup(groupBy.id, child, kanbanMap[id]);
|
await moveCardToGroup({
|
||||||
|
groupBy,
|
||||||
|
cardBlock: child,
|
||||||
|
group: kanbanMap[id],
|
||||||
|
recastBlock,
|
||||||
|
});
|
||||||
if (beforeBlock) {
|
if (beforeBlock) {
|
||||||
const block = await editor.getBlockById(
|
const block = await editor.getBlockById(
|
||||||
beforeBlock
|
beforeBlock
|
||||||
@@ -286,7 +293,12 @@ export const useKanban = () => {
|
|||||||
);
|
);
|
||||||
if (isChangedGroup) {
|
if (isChangedGroup) {
|
||||||
// 1.2 Move to the target group
|
// 1.2 Move to the target group
|
||||||
await moveCardToGroup(groupBy.id, targetCard, targetGroup);
|
await moveCardToGroup({
|
||||||
|
groupBy,
|
||||||
|
cardBlock: targetCard,
|
||||||
|
group: targetGroup,
|
||||||
|
recastBlock,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Reorder the card
|
// 2. Reorder the card
|
||||||
@@ -324,7 +336,12 @@ export const useKanban = () => {
|
|||||||
}
|
}
|
||||||
recastBlock.append(newBlock);
|
recastBlock.append(newBlock);
|
||||||
const newCard = newBlock as unknown as RecastItem;
|
const newCard = newBlock as unknown as RecastItem;
|
||||||
await moveCardToGroup(groupBy.id, newCard, group);
|
await moveCardToGroup({
|
||||||
|
groupBy,
|
||||||
|
cardBlock: newCard,
|
||||||
|
group,
|
||||||
|
recastBlock,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[editor, groupBy.id, recastBlock]
|
[editor, groupBy.id, recastBlock]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ export type DefaultGroup = KanbanGroupBase & {
|
|||||||
|
|
||||||
type SelectGroup = KanbanGroupBase &
|
type SelectGroup = KanbanGroupBase &
|
||||||
SelectOption & {
|
SelectOption & {
|
||||||
type: PropertyType.Select | PropertyType.MultiSelect;
|
type:
|
||||||
|
| PropertyType.Select
|
||||||
|
| PropertyType.MultiSelect
|
||||||
|
| PropertyType.Status;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TextGroup = KanbanGroupBase & {
|
type TextGroup = KanbanGroupBase & {
|
||||||
|
|||||||
84
libs/components/editor-core/src/recast-block/history.ts
Normal file
84
libs/components/editor-core/src/recast-block/history.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
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({}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const ensureHistoryAtom = (
|
||||||
|
data: HistoryStorageMap,
|
||||||
|
recastBlockId: string,
|
||||||
|
propertyId: RecastPropertyId
|
||||||
|
): HistoryStorageMap => {
|
||||||
|
if (!data[recastBlockId]) {
|
||||||
|
data[recastBlockId] = {};
|
||||||
|
}
|
||||||
|
if (!data[recastBlockId][propertyId]) {
|
||||||
|
data[recastBlockId][propertyId] = [];
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setHistory = ({ recastBlockId, blockId, propertyId }: Props) => {
|
||||||
|
ensureLocalStorage();
|
||||||
|
const data: HistoryStorageMap = JSON.parse(
|
||||||
|
localStorage.getItem(LOCAL_STORAGE_NAME) as string
|
||||||
|
);
|
||||||
|
ensureHistoryAtom(data, recastBlockId, propertyId);
|
||||||
|
const propertyHistory = data[recastBlockId][propertyId];
|
||||||
|
|
||||||
|
if (propertyHistory.includes(blockId)) {
|
||||||
|
const idIndex = propertyHistory.findIndex(id => id === blockId);
|
||||||
|
propertyHistory.splice(idIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyHistory.push(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,
|
||||||
|
blockId,
|
||||||
|
propertyId,
|
||||||
|
}: Props) => {
|
||||||
|
ensureLocalStorage();
|
||||||
|
const data: HistoryStorageMap = JSON.parse(
|
||||||
|
localStorage.getItem(LOCAL_STORAGE_NAME) as string
|
||||||
|
);
|
||||||
|
ensureHistoryAtom(data, recastBlockId, propertyId);
|
||||||
|
|
||||||
|
const propertyHistory = data[recastBlockId][propertyId];
|
||||||
|
|
||||||
|
if (propertyHistory.includes(blockId)) {
|
||||||
|
const idIndex = propertyHistory.findIndex(id => id === blockId);
|
||||||
|
propertyHistory.splice(idIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data));
|
||||||
|
};
|
||||||
@@ -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,13 @@ export const getRecastItemValue = (block: RecastItem | AsyncBlock) => {
|
|||||||
return props[id];
|
return props[id];
|
||||||
};
|
};
|
||||||
|
|
||||||
const setValue = (newValue: RecastBlockValue) => {
|
const setValue = (newValue: RecastBlockValue, recastBlockId: string) => {
|
||||||
|
setHistory({
|
||||||
|
recastBlockId: recastBlockId,
|
||||||
|
blockId: block.id,
|
||||||
|
propertyId: newValue.id,
|
||||||
|
});
|
||||||
|
|
||||||
return recastItem.setProperty(TABLE_VALUES_KEY, {
|
return recastItem.setProperty(TABLE_VALUES_KEY, {
|
||||||
...props,
|
...props,
|
||||||
[newValue.id]: newValue,
|
[newValue.id]: newValue,
|
||||||
@@ -249,22 +256,30 @@ 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,
|
||||||
|
blockId: block.id,
|
||||||
|
});
|
||||||
|
|
||||||
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 = (
|
||||||
metaProperty?: RecastMetaProperty
|
metaProperty?: RecastMetaProperty
|
||||||
): metaProperty is SelectProperty | MultiSelectProperty => {
|
): metaProperty is SelectProperty | MultiSelectProperty | StatusProperty => {
|
||||||
if (
|
return (
|
||||||
!metaProperty ||
|
metaProperty &&
|
||||||
(metaProperty.type !== PropertyType.Select &&
|
(metaProperty.type === PropertyType.Status ||
|
||||||
metaProperty.type !== PropertyType.MultiSelect)
|
metaProperty.type === PropertyType.Select ||
|
||||||
) {
|
metaProperty.type === PropertyType.MultiSelect)
|
||||||
return false;
|
);
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -312,7 +327,7 @@ export const useSelectProperty = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateSelect = (
|
const updateSelect = (
|
||||||
selectProperty: SelectProperty | MultiSelectProperty
|
selectProperty: StatusProperty | SelectProperty | MultiSelectProperty
|
||||||
) => {
|
) => {
|
||||||
// if (typeof selectProperty === 'string') {
|
// if (typeof selectProperty === 'string') {
|
||||||
// const maybeSelectProperty = getProperty(selectProperty);
|
// const maybeSelectProperty = getProperty(selectProperty);
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import {
|
|||||||
tooltipClasses,
|
tooltipClasses,
|
||||||
Typography,
|
Typography,
|
||||||
Zoom,
|
Zoom,
|
||||||
|
Fade,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
export { alpha } from '@mui/system';
|
export { alpha } from '@mui/system';
|
||||||
@@ -233,6 +234,11 @@ export const MuiInput = Input;
|
|||||||
*/
|
*/
|
||||||
export const MuiZoom = Zoom;
|
export const MuiZoom = Zoom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||||
|
*/
|
||||||
|
export const MuiFade = Fade;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user