From ed0a1fd4e387d53219c570f0cac739363bf4df33 Mon Sep 17 00:00:00 2001 From: Bowen Tan Date: Mon, 4 Sep 2023 17:49:52 +0800 Subject: [PATCH] feat(editor): support use tags to filter data source --- packages/core/src/application.ts | 4 +- packages/core/src/metadata.ts | 4 ++ .../ComponentForm/ComponentForm.tsx | 5 +++ .../src/components/ComponentForm/TagForm.tsx | 40 +++++++++++++++++++ .../ComponentsList/ComponentFilter.tsx | 34 +++++++++------- .../ComponentsList/ComponentList.tsx | 6 +-- .../components/DataSource/DataSourceList.tsx | 25 ++++++++++-- .../StructureTree/ComponentSearch.tsx | 36 ++++++++++++----- .../StructureTree/StructureTree.tsx | 3 ++ packages/editor/src/services/AppStorage.ts | 15 +++++++ 10 files changed, 139 insertions(+), 33 deletions(-) create mode 100644 packages/editor/src/components/ComponentForm/TagForm.tsx diff --git a/packages/core/src/application.ts b/packages/core/src/application.ts index 2ea423793..dbc6d9218 100644 --- a/packages/core/src/application.ts +++ b/packages/core/src/application.ts @@ -1,4 +1,4 @@ -import { Metadata } from './metadata'; +import { ApplicationMetadata } from './metadata'; import { parseVersion, Version } from './version'; import { type PropsBeforeEvaled } from './schema'; // spec @@ -6,7 +6,7 @@ import { type PropsBeforeEvaled } from './schema'; export type Application = { version: string; kind: 'Application'; - metadata: Metadata; + metadata: ApplicationMetadata; spec: { components: ComponentSchema[]; }; diff --git a/packages/core/src/metadata.ts b/packages/core/src/metadata.ts index c37b3fb8d..77caa6d41 100644 --- a/packages/core/src/metadata.ts +++ b/packages/core/src/metadata.ts @@ -10,6 +10,10 @@ export type Metadata< isDataSource?: boolean; }; +export type ApplicationMetadata = Metadata<{ + componentsTagMap?: Record; +}>; + type ComponentCategory = | (string & {}) | 'Layout' diff --git a/packages/editor/src/components/ComponentForm/ComponentForm.tsx b/packages/editor/src/components/ComponentForm/ComponentForm.tsx index 1cfd864c6..ef6918800 100644 --- a/packages/editor/src/components/ComponentForm/ComponentForm.tsx +++ b/packages/editor/src/components/ComponentForm/ComponentForm.tsx @@ -12,6 +12,7 @@ import ErrorBoundary from '../ErrorBoundary'; import { StyleTraitForm } from './StyleTraitForm'; import { EditorServices } from '../../types'; import { FormSection } from './FormSection'; +import { TagForm } from './TagForm'; // avoid the expression tip would be covered const ComponentFormStyle = css` @@ -127,6 +128,10 @@ export const ComponentForm: React.FC = observer(props => { title: 'Traits', node: , }, + { + title: 'Tags', + node: , + }, ]; return ( diff --git a/packages/editor/src/components/ComponentForm/TagForm.tsx b/packages/editor/src/components/ComponentForm/TagForm.tsx new file mode 100644 index 000000000..b4afcd519 --- /dev/null +++ b/packages/editor/src/components/ComponentForm/TagForm.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Text, VStack, Checkbox } from '@chakra-ui/react'; +import { EditorServices } from '../../types'; + +type Props = { + services: EditorServices; +}; + +export const TagForm: React.FC = ({ services }) => { + const { editorStore } = services; + const tagMap = editorStore.app.metadata.annotations?.componentsTagMap; + const componentId = editorStore.selectedComponentId; + + if (!tagMap) return null; + + const tagItems = Object.keys(tagMap).map(tag => { + const checked = tagMap[tag].includes(editorStore.selectedComponentId); + + const onChange = (e: React.ChangeEvent) => { + const newTagMap = tagMap; + const nextValue = e.target.checked; + if (nextValue) { + newTagMap[tag].push(componentId); + } else { + newTagMap[tag] = tagMap[tag].filter(id => id !== componentId); + } + services.editorStore.appStorage.saveAppAnnotations({ + componentsTagMap: newTagMap, + }); + }; + + return ( + + {tag} + + ); + }); + + return {tagItems}; +}; diff --git a/packages/editor/src/components/ComponentsList/ComponentFilter.tsx b/packages/editor/src/components/ComponentsList/ComponentFilter.tsx index 256165fb4..49369b620 100644 --- a/packages/editor/src/components/ComponentsList/ComponentFilter.tsx +++ b/packages/editor/src/components/ComponentsList/ComponentFilter.tsx @@ -57,37 +57,43 @@ const FilterIcon = createIcon({ }); type FilterProps = { - versions: string[]; - checkedVersions: string[]; - setCheckedVersions: React.Dispatch>; + options: string[]; + checkedOptions: string[]; + onChange: (v: string[]) => void; }; export const ComponentFilter: React.FC = ({ - versions, - checkedVersions, - setCheckedVersions, + options, + checkedOptions, + onChange, }) => { const handleChange = (e: React.ChangeEvent) => { const checked = e.target.checked; const value = e.target.value; - const newCheckedVersions = [...checkedVersions]; + const newCheckedOptions = [...checkedOptions]; if (checked) { - newCheckedVersions.push(value); + newCheckedOptions.push(value); } else { - const idx = newCheckedVersions.findIndex(c => c === value); - newCheckedVersions.splice(idx, 1); + const idx = newCheckedOptions.findIndex(c => c === value); + newCheckedOptions.splice(idx, 1); } - setCheckedVersions(newCheckedVersions); + onChange(newCheckedOptions); }; const checkVersion = (version: string) => { - return checkedVersions.includes(version); + return checkedOptions.includes(version); }; return ( - @@ -110,7 +116,7 @@ export const ComponentFilter: React.FC = ({ width="200px" _focus={{ boxShadow: 'none' }} > - {versions.map(version => { + {options.map(version => { return ( = ({ services }) => { /> diff --git a/packages/editor/src/components/DataSource/DataSourceList.tsx b/packages/editor/src/components/DataSource/DataSourceList.tsx index 1ceaa35ff..80e17934d 100644 --- a/packages/editor/src/components/DataSource/DataSourceList.tsx +++ b/packages/editor/src/components/DataSource/DataSourceList.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { VStack, Spacer, @@ -21,6 +21,7 @@ import { generateDefaultValueFromSpec } from '@sunmao-ui/shared'; import { JSONSchema7 } from 'json-schema'; import { ToolMenuTabs } from '../../constants/enum'; import { ComponentSearch } from '../StructureTree/ComponentSearch'; +import { ComponentSchema } from '@sunmao-ui/core'; interface Props { services: EditorServices; @@ -30,8 +31,23 @@ export const DataSourceList: React.FC = props => { const { services } = props; const { editorStore, eventBus, registry } = services; const { dataSources, setSelectedComponentId, setToolMenuTab } = editorStore; - const tDataSources = dataSources.filter(ds => ds.type === 'core/v1/dummy'); - const cDataSources = dataSources.filter(ds => ds.type !== 'core/v1/dummy'); + const [checkedTags, setCheckedTags] = useState([]); + const tagMap = services.editorStore.app.metadata.annotations?.componentsTagMap; + // filter datasources by tag + const filteredDataSources = useMemo(() => { + if (!tagMap || checkedTags.length === 0) { + return dataSources; + } + + const dataSourceIds = checkedTags.reduce((result, curr) => { + return result.concat(tagMap[curr]); + }, []); + + return dataSources.filter(d => dataSourceIds.includes(d.id)) as ComponentSchema[]; + }, [checkedTags, dataSources, tagMap]); + + const tDataSources = filteredDataSources.filter(ds => ds.type === 'core/v1/dummy'); + const cDataSources = filteredDataSources.filter(ds => ds.type !== 'core/v1/dummy'); const cdsMap = groupBy(cDataSources, c => c.type); const tdsMap = groupBy(tDataSources, c => c.traits[0]?.type || ''); const cdsGroups = Object.keys(cdsMap).map(type => { @@ -165,6 +181,9 @@ export const DataSourceList: React.FC = props => { components={dataSources} onChange={id => setSelectedComponentId(id)} services={props.services} + tags={tagMap ? Object.keys(tagMap) : []} + checkedTags={checkedTags} + onTagsChange={v => setCheckedTags(v)} /> void; + tags: string[]; + checkedTags: string[]; + onTagsChange: (v: string[]) => void; services: EditorServices; }; @@ -28,7 +33,7 @@ const SelectStyle = css` `; export const ComponentSearch: React.FC = observer(props => { - const { components, onChange } = props; + const { components, onChange, tags, checkedTags, onTagsChange } = props; const options = useMemo(() => { return components.map(c => ({ @@ -38,15 +43,24 @@ export const ComponentSearch: React.FC = observer(props => { }, [components]); return ( - + {tags.length > 0 ? ( + onTagsChange(v)} + /> + ) : null} + ); }); diff --git a/packages/editor/src/components/StructureTree/StructureTree.tsx b/packages/editor/src/components/StructureTree/StructureTree.tsx index c76a78ea5..d344069a4 100644 --- a/packages/editor/src/components/StructureTree/StructureTree.tsx +++ b/packages/editor/src/components/StructureTree/StructureTree.tsx @@ -102,6 +102,9 @@ export const StructureTree: React.FC = observer(props => { Components null} components={components} onChange={id => setSelectedComponentId(id)} services={props.services} diff --git a/packages/editor/src/services/AppStorage.ts b/packages/editor/src/services/AppStorage.ts index 07c6c77e8..cf0a1556d 100644 --- a/packages/editor/src/services/AppStorage.ts +++ b/packages/editor/src/services/AppStorage.ts @@ -1,6 +1,7 @@ import { observable, makeObservable, action, toJS } from 'mobx'; import { Application, + ApplicationMetadata, ComponentSchema, Module, ModuleMethodSpec, @@ -159,6 +160,20 @@ export class AppStorage { this.saveApplication(); } + saveAppAnnotations(annotations: ApplicationMetadata['annotations']) { + if (!annotations) return; + const newApp = produce(toJS(this.app), draft => { + if (!draft.metadata.annotations) { + draft.metadata.annotations = {}; + } + Object.keys(annotations).forEach(key => { + draft.metadata.annotations![key] = annotations[key]; + }); + }); + this.setApp(newApp); + this.saveApplication(); + } + saveModuleMetaData( { originName, originVersion }: { originName: string; originVersion: string }, {