From afd8269d05c14c294553f01ef96d69d8069b1eb4 Mon Sep 17 00:00:00 2001 From: Artyom Date: Thu, 16 Sep 2021 09:23:08 +0300 Subject: [PATCH 1/7] [ADD] SearchComponent --- src/controls/index.ts | 2 + src/controls/query/CreateFilterModal.tsx | 245 +++++++++++ src/controls/query/Filter.tsx | 402 ++++++++++++++++++ src/controls/query/Query.tsx | 218 ++++++++++ src/controls/query/QueryRenderer.tsx | 11 + src/controls/query/SearchableList.tsx | 116 +++++ .../filtersBody/BodyOfDateTimeFilter.tsx | 188 ++++++++ .../query/filtersBody/BodyOfIntegerFilter.tsx | 57 +++ .../query/filtersBody/BodyOfListFilter.tsx | 140 ++++++ .../query/filtersBody/BodyOfStringFilter.tsx | 56 +++ src/controls/query/type.ts | 18 + src/util/ContextToProps.tsx | 27 ++ 12 files changed, 1480 insertions(+) create mode 100644 src/controls/query/CreateFilterModal.tsx create mode 100644 src/controls/query/Filter.tsx create mode 100644 src/controls/query/Query.tsx create mode 100644 src/controls/query/QueryRenderer.tsx create mode 100644 src/controls/query/SearchableList.tsx create mode 100644 src/controls/query/filtersBody/BodyOfDateTimeFilter.tsx create mode 100644 src/controls/query/filtersBody/BodyOfIntegerFilter.tsx create mode 100644 src/controls/query/filtersBody/BodyOfListFilter.tsx create mode 100644 src/controls/query/filtersBody/BodyOfStringFilter.tsx create mode 100644 src/controls/query/type.ts diff --git a/src/controls/index.ts b/src/controls/index.ts index 1838419..d33f9a8 100644 --- a/src/controls/index.ts +++ b/src/controls/index.ts @@ -17,6 +17,7 @@ import { antdRadioGroupControlTester, AntdRadioGroupControlWithStore } from './A import { antdSliderControlTester, AntdSliderControlWithStore } from './AntdSliderControl'; import { antdTextControlTester, AntdTextControlWithStore } from './AntdTextControl'; import { tinyMCEControlTester, TinyMCEControlWithStore } from './TinyMCEControl'; +import QueryRenderer, { antdQueryTester } from './query/QueryRenderer'; export const antdControlRenderers: RendererRegistryEntry[] = [ { tester: antdBooleanControlTester, renderer: AntdBooleanControlWithStore }, @@ -32,6 +33,7 @@ export const antdControlRenderers: RendererRegistryEntry[] = [ { tester: antdTextControlTester, renderer: AntdTextControlWithStore }, { tester: tinyMCEControlTester, renderer: TinyMCEControlWithStore }, + { tester: antdQueryTester, renderer: QueryRenderer }, ]; export * from './AntdBooleanControl'; diff --git a/src/controls/query/CreateFilterModal.tsx b/src/controls/query/CreateFilterModal.tsx new file mode 100644 index 0000000..95c3762 --- /dev/null +++ b/src/controls/query/CreateFilterModal.tsx @@ -0,0 +1,245 @@ +import React, { useState, useEffect, FunctionComponent } from 'react'; +import { Modal, Row, Col, Button, Typography } from 'antd'; +import { CheckCircleTwoTone, ExclamationCircleTwoTone } from '@ant-design/icons'; + +import { SearchableList } from './SearchableList'; + +import { FilterType } from './type'; +import { JSONSchema6DefinitionForRdfProperty, JsObject } from '@agentlab/sparql-jsld-client'; +import { Filter } from './Filter'; +import { MstContext } from '../../MstContext'; + +interface CreateFilterModalProps { + loading: boolean; + handleCancel: () => void; + handleOk: (filter: FilterType | undefined) => void; + handleAdd: (filter: FilterType | undefined) => void; + tags: FilterType[]; +} + +const localeRus = { + title: 'Создание фильтра', + add: 'Добавить', + addAndClose: 'Добавить и закрыть', + close: 'Отмены', + artifactType: 'Тип требования', + emptyFilter: 'Пусто', + choiceAtribute: 'Выберите атрибут', + choiceValue: 'Выберите значение', + resultFilter: 'Итоговый фильтр', +}; + +export const CreateFilterModal: FunctionComponent = ({ + loading, + handleCancel, + handleOk, + handleAdd, + tags, +}) => { + const [filter, setFilter] = useState({ + value: [], + valueName: [], + relation: { title: '', predicate: '' }, + property: '', + title: '', + }); + const [isValidFilter, setIsValidFilter] = useState(false); + + useEffect(() => { + if (filter && filter.title && filter.property && filter.relation && filter.relation.predicate) { + if ((filter.relation.title && filter.property !== 'assetFolder') || filter.property === 'assetFolder') { + if (filter.relation.type !== 'noValue' && (!filter.value || filter.value.length === 0)) { + setIsValidFilter(false); + } else { + setIsValidFilter(true); + } + } + } else { + setIsValidFilter(false); + } + }, [filter]); + + return ( + handleOk(filter)} + onCancel={handleCancel} + footer={[ + , + , + , + ]}> + + + ); +}; + +interface CreateFilterProps { + setFilter: (newVal: FilterType) => void; + filter: FilterType; + isValidFilter: boolean; + setIsValidFilter: (newVal: boolean) => void; + tags: FilterType[]; +} + +const CreateFilter: FunctionComponent = ({ + setFilter, + filter, + isValidFilter, + setIsValidFilter, + tags, +}) => { + const { store } = React.useContext(MstContext); + const [selectedSchema] = useState(); + + // useEffect(() => { + // setSelectedSchema(artifactTypes[0]); + // }, [artifactTypes]); + + const artifactTypeFilterItem = { key: '@type', title: localeRus.artifactType }; + const [selectedFilterType, setSelectedFilterType] = useState(artifactTypeFilterItem.key); + const [modalIsMount, setModalIsMount] = useState(false); + + const [allProperties, setAllProperties] = useState<{ + properties: { [key: string]: JSONSchema6DefinitionForRdfProperty }; + contexts: JsObject; + }>({ properties: {}, contexts: {} }); + + useEffect(() => { + const loadProperties = async () => { + const artifactTypes: JsObject[] = await store.getData('rm:ArtifactClasses'); + const [properties, contexts] = await store.getAllProperties('rm:Artifact'); + if (properties['@id']) { + delete properties['@id']; + } + properties['@type'] = { + type: 'string', + enum: artifactTypes.map((schema) => { + return { value: schema['@id'], alias: schema.title || schema['@id'] }; + }), + format: 'iri', + '@id': 'rdf:type', + title: localeRus.artifactType, + }; + setAllProperties({ properties, contexts }); + }; + loadProperties(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const [filterTypes, setFilterTypes] = useState<{ [key: string]: string }[]>([ + { key: '@type', title: localeRus.artifactType }, + ]); + + useEffect(() => { + const newFilterTypes: { [key: string]: string }[] = []; + Object.keys(allProperties.properties).forEach((val: string) => { + if (val !== '@type') { + newFilterTypes.push({ key: val, title: allProperties.properties[val].title || val }); + } + }); + setFilterTypes([...filterTypes, ...newFilterTypes]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [allProperties]); + + useEffect(() => { + setFilter({ + ...filter, + property: filterTypes[0].key, + title: filterTypes[0].title, + }); + setModalIsMount(true); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const parseFilter = () => { + let result = {localeRus.emptyFilter}; + + if (filter) { + const { title } = filter; + const relation = filter.relation ? filter.relation.title : null; + const value = + filter.valueName && filter.valueName.length > 0 ? filter.valueName.join(', ') : filter.value.join(', '); + + if (title) { + result = {`${title} ${relation || ''} ${value || ''}`}; + } + } + + let icon = null; + + if (isValidFilter) { + icon = ; + } else { + icon = ; + } + + return ( + + {result} + {icon} + + ); + }; + + return ( + <> + + +

{localeRus.choiceAtribute}

+ { + setSelectedFilterType(val.key); + setFilter({ ...filter, property: val.key, title: val.title }); + }} + /> + + + +

{localeRus.choiceValue}

+ {allProperties.properties[selectedFilterType] && ( + tag.property === selectedFilterType)} + propName={selectedFilterType} + property={allProperties.properties[selectedFilterType]} + context={allProperties.contexts[selectedFilterType]} + isRequired={ + selectedSchema && selectedSchema.required ? selectedSchema.required.includes(selectedFilterType) : false + } + modalIsMount={modalIsMount} + /> + )} + +
+ + {localeRus.resultFilter}: + + + {parseFilter()} + + + ); +}; diff --git a/src/controls/query/Filter.tsx b/src/controls/query/Filter.tsx new file mode 100644 index 0000000..7bbe6ea --- /dev/null +++ b/src/controls/query/Filter.tsx @@ -0,0 +1,402 @@ +import React, { FunctionComponent, useEffect, useState } from 'react'; +import { Row, Col, Select, Typography } from 'antd'; +import { FilterType, Relation, ValueOfFilter } from './type'; +import { JSONSchema6DefinitionForRdfProperty, JsObject } from '@agentlab/sparql-jsld-client'; +import moment from 'moment'; +import { JSONSchema6, JSONSchema6Definition } from 'json-schema'; +import { BodyOfListFilter } from './filtersBody/BodyOfListFilter'; +import { BodyOfIntegerFilter } from './filtersBody/BodyOfIntegerFilter'; +import { BodyOfDateTimeFilter } from './filtersBody/BodyOfDateTimeFilter'; +import { BodyOfStringFilter } from './filtersBody/BodyOfStringFilter'; + +export const genFormattedTypeName = (propName: string, type?: string, format?: string): string => { + let resultType = 'iri'; + + if (propName === 'assetFolder') { + return 'assetFolder'; + } + + if (type === 'integer') { + resultType = 'integer'; + } else if (type === 'string') { + if (format === 'date-time') { + resultType = 'date-time'; + } else if (format === 'iri') { + resultType = 'object'; + } else { + resultType = 'string'; + } + } else if (type === 'object') { + resultType = 'object'; + } else { + resultType = 'iri'; + } + + // if (resultType === 'iri' && propName === 'assetFolder') { + // resultType = 'assetFolder'; + // } + + return resultType; +}; + +interface TypeOfFilter { + type: string; + isArray: boolean; +} + +export const genFormattedTypeOfFilter = ( + propName: string, + property: JSONSchema6DefinitionForRdfProperty, +): TypeOfFilter => { + const formattedType: TypeOfFilter = { + type: 'none', + isArray: false, + }; + + if (property.type === 'array' && property.items) { + formattedType.isArray = true; + + const items = property.items as JSONSchema6Definition as JSONSchema6; + formattedType.type = genFormattedTypeName(propName, items.type as string, items.format); + } else { + formattedType.type = genFormattedTypeName(propName, property.type as string, property.format); + } + + return formattedType; +}; + +export const equalTypesOfRelations = (typeA?: string, typeB?: string): boolean => { + const keyWord = 'hidden'; + + let indexOfKeyword = typeA && typeA.indexOf(keyWord) !== -1 ? typeA.indexOf(keyWord) + keyWord.length : 0; + const firstType = typeA && typeA.substring(indexOfKeyword); + + indexOfKeyword = typeB && typeB.indexOf(keyWord) !== -1 ? typeB.indexOf(keyWord) + keyWord.length : 0; + const secondType = typeB && typeB.substring(indexOfKeyword); + + return firstType === secondType; +}; + +export const getTypeOfRelation = (currentRelations: Relation[], nameOfSelectRelation: string): string | undefined => { + const foundRelation = currentRelations.find((relation: Relation) => relation.predicate === nameOfSelectRelation); + + return foundRelation && foundRelation.type; +}; + +const localeRus = { + contains: 'содержит', + notContains: 'не содержит', + equal: 'равняется', + startWith: 'начинается с', + endWith: 'заканчивается на', + regEx: 'регулярное выражение', + exists: 'сущестует', + notExists: 'не сущестует', + unassigned: 'не назначен', + any: 'любой из', + notAny: 'ни один из', + each: 'каждый из', + notEach: 'все кроме', + dateEqual: 'в', + dateNotEqual: 'не в', + after: 'после (включая)', + before: 'до (включая)', + between: 'между', + today: 'Сегодня', + fromYesterdayToToday: 'Со вчера на сегодня', +}; + +interface FilterProp { + filterData: FilterType; + setFilter: (filter: FilterType) => void; + prevFilterData?: FilterType; + propName: string; + property: JSONSchema6DefinitionForRdfProperty; + context: JsObject; + isRequired: boolean; + modalIsMount?: boolean; +} + +export const Filter: FunctionComponent = ({ + filterData, + setFilter, + prevFilterData, + propName, + property, + context, + isRequired, + modalIsMount = false, +}) => { + const relations: { [key: string]: Relation[] } = { + object: [ + { title: localeRus.any, predicate: 'any', type: 'enumerate' }, + { title: localeRus.notAny, predicate: 'notAny', type: 'enumerate' }, + ], + array: [ + { title: localeRus.any, predicate: 'any', type: 'enumerate' }, + { title: localeRus.notAny, predicate: 'notAny', type: 'enumerate' }, + { title: localeRus.each, predicate: 'each', type: 'enumerate' }, + { title: localeRus.notEach, predicate: 'notEach', type: 'enumerate' }, + ], + string: [ + { title: localeRus.contains, predicate: 'contains', type: 'singleString' }, + { title: localeRus.notContains, predicate: 'notContains', type: 'singleString' }, + { title: localeRus.equal, predicate: 'equal', type: 'singleString' }, + { title: localeRus.startWith, predicate: 'startWith', type: 'singleString' }, + { title: localeRus.endWith, predicate: 'endWith', type: 'singleString' }, + { title: localeRus.regEx, predicate: 'regEx', type: 'regEx' }, + ], + 'date-time': [ + { title: localeRus.dateEqual, predicate: 'equal', type: 'singleDate' }, + { title: localeRus.dateNotEqual, predicate: 'notEqual', type: 'singleDate' }, + { title: localeRus.after, predicate: 'after', type: 'chooseVariant' }, + { title: localeRus.before, predicate: 'before', type: 'chooseVariant' }, + { title: localeRus.between, predicate: 'between', type: 'twoDate' }, + ], + integer: [ + { title: localeRus.equal, predicate: 'equal', type: 'singleNumber' }, + { title: localeRus.any, predicate: 'any', type: 'hiddenSingleNumber' }, + ], + isNotRequired: [ + { title: localeRus.exists, predicate: 'exists', type: 'noValue' }, + { title: localeRus.notExists, predicate: 'notExists', type: 'noValue' }, + // { title: t('CreateFilter.unassigned'), predicate: 'unassigned', type: 'noValue' }, + ], + assetFolder: [{ title: '', predicate: 'equal', type: 'singleString' }], + noRelations: [{ title: localeRus.equal, predicate: 'equal', type: 'singleString' }], + }; + + const getDefaultValue = ( + typeOfFilter: TypeOfFilter, + prevFilter?: FilterType, + typeOfCurrentRelation?: string, + typeOfPrevRelation?: string, + ): ValueOfFilter => { + let defaultValue: ValueOfFilter = { value: [], valueName: [] }; + + if (typeOfFilter.type === 'date-time') { + if (equalTypesOfRelations(typeOfCurrentRelation, 'twoDate')) { + defaultValue = { + value: [moment().subtract(1, 'days'), moment()], + valueName: [localeRus.fromYesterdayToToday], + }; + } else { + defaultValue = { + value: [moment()], + valueName: [localeRus.today], + }; + } + } else if (typeOfFilter.type === 'integer') { + defaultValue = { value: [0] }; + } + + if (prevFilter) { + if (typeOfPrevRelation && equalTypesOfRelations(typeOfPrevRelation, typeOfCurrentRelation)) { + defaultValue.value = prevFilter.value; + defaultValue.valueName = prevFilter.valueName || []; + + if (typeOfFilter.type === 'date-time') { + defaultValue.value = prevFilter.value.map((value) => moment(value, 'YYYY-MM-DDThh:mm:ss')); + } + } + } + + if (typeOfCurrentRelation && equalTypesOfRelations(typeOfCurrentRelation, 'noValue')) { + defaultValue = { value: [], valueName: [] }; + } + + return defaultValue; + }; + + const getDefaultRelation = (currentRelations: Relation[], prevRelation?: Relation): Relation => { + let defaultRelation = currentRelations[0]; + + if (prevRelation) { + const foundRelation = currentRelations.find( + (relation: Relation) => relation.predicate === prevRelation.predicate, + ); + if (foundRelation) { + defaultRelation = { + title: foundRelation.title, + predicate: prevRelation.predicate, + type: foundRelation.type, + }; + } + } + + return defaultRelation; + }; + + const getCurrentRelations = (typeOfFilter: TypeOfFilter, isRequired: boolean): Relation[] => { + let currentRelations: Relation[]; + if (typeOfFilter.isArray) { + currentRelations = relations['array']; + } else { + currentRelations = relations[typeOfFilter.type] || relations['noRelations']; + } + + if (!isRequired) { + currentRelations = currentRelations.concat(relations['isNotRequired']); + } + + return currentRelations; + }; + + const typeOfFilter = genFormattedTypeOfFilter(propName, property); + const currentRelations = getCurrentRelations(typeOfFilter, isRequired); + const prevRelation = prevFilterData && prevFilterData.relation; + const prevRelationType = prevRelation && getTypeOfRelation(currentRelations, prevRelation.predicate); + + const [defaultRelation, setDefaultRelation] = useState(getDefaultRelation(currentRelations, prevRelation)); + const [defaultValues, setDefaultValues] = useState( + getDefaultValue(typeOfFilter, prevFilterData, defaultRelation.type, prevRelationType), + ); + + useEffect(() => { + const newRelation = getDefaultRelation(currentRelations, prevRelation); + const newValue = getDefaultValue(typeOfFilter, prevFilterData, newRelation.type, prevRelationType); + setDefaultRelation(newRelation); + setDefaultValues(newValue); + setFilter({ + ...filterData, + relation: newRelation, + value: newValue.value, + valueName: newValue.valueName, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [propName]); + + useEffect(() => { + setFilter({ + ...filterData, + relation: defaultRelation, + value: defaultValues.value, + valueName: defaultValues.valueName, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [modalIsMount]); + + const handleChangeRelation = (changedRelation: Relation): void => { + if (changedRelation && changedRelation.predicate !== filterData.relation.predicate) { + let newValue = getDefaultValue(typeOfFilter, prevFilterData, changedRelation.type, prevRelationType); + + if ( + filterData.relation && + changedRelation && + equalTypesOfRelations(filterData.relation.type, changedRelation.type) + ) { + newValue = { value: filterData.value, valueName: filterData.valueName }; + } else { + setDefaultValues(newValue); + } + + setFilter({ + ...filterData, + relation: changedRelation, + value: newValue.value, + valueName: newValue.valueName, + }); + + setDefaultRelation(changedRelation); + } + }; + + return ( + <> + + +
+ {property.title} {' '} +
+ + {currentRelations.filter((rel) => rel.type && !rel.type.startsWith('hidden')).length > 1 ? ( + + + + ) : ( + <> + )} +
+ {propName === '@type' ? ( + { + setFilter({ ...filterData, value: value.value, valueName: value.valueName }); + }} + defaultValues={defaultValues} + property={property} + context={context} + type={defaultRelation.type} + /> + ) : // ) : typeOfFilter.type === 'assetFolder' ? ( + // { + // setFilter({ ...filterData, value: value.value, valueName: value.valueName }); + // }} + // defaultValues={defaultValues} + // /> + typeOfFilter.type === 'object' ? ( + { + setFilter({ ...filterData, value: value.value, valueName: value.valueName }); + }} + defaultValues={defaultValues} + property={property} + context={context} + type={defaultRelation.type} + /> + ) : typeOfFilter.isArray ? ( + <> + + {defaultValues.valueName} + + {defaultValues.value.toString()} + + ) : typeOfFilter.type === 'integer' ? ( + { + setFilter({ ...filterData, ...value }); + }} + defaultValues={defaultValues} + type={defaultRelation.type} + /> + ) : typeOfFilter.type === 'date-time' ? ( + { + setFilter({ ...filterData, ...value }); + }} + defaultValues={defaultValues} + type={defaultRelation.type} + /> + ) : ( + { + setFilter({ ...filterData, value: value.value, valueName: value.valueName }); + }} + defaultValues={defaultValues} + type={defaultRelation.type} + /> + )} + + ); +}; diff --git a/src/controls/query/Query.tsx b/src/controls/query/Query.tsx new file mode 100644 index 0000000..9679fa4 --- /dev/null +++ b/src/controls/query/Query.tsx @@ -0,0 +1,218 @@ +import React, { useState, useRef, useEffect, ChangeEvent } from 'react'; +import { Tag, Input, Tooltip, Button } from 'antd'; +import { PlusOutlined, UpOutlined, DownOutlined } from '@ant-design/icons'; +import { CreateFilterModal } from './CreateFilterModal'; +import { FilterType } from './type'; + +const localeRus = { + add: 'Добавить', + fullTextSearchValue: 'Содержит текст', +}; + +interface FilterByTagProps { + expanded?: boolean; + onExpand?: (isExpand?: boolean) => void; + addFilter?: (filter: any, location?: string) => void; + removeFilter?: (filter: any) => void; + tags: FilterType[]; + loading?: boolean; + fullTextSearchString?: string; + setFullTextSearchString?: (newVal: string) => void; +} + +export const Query: React.FC = ({ + expanded = false, + onExpand = () => {}, + addFilter = () => {}, + removeFilter = () => {}, + tags, + loading = false, + fullTextSearchString = '', + setFullTextSearchString = () => {}, +}) => { + const [inputValue, setInputValue] = useState(fullTextSearchString); + const [tagsArray, setTags] = useState([...tags]); + const [isExpanded, setIsExpanded] = useState(expanded); + + const [modalVisible, setModalVisible] = useState(false); + + const inputRef = useRef(null); + + useEffect(() => { + let newTags = [...tags]; + const fullTextFilter = tagsArray.find((tag) => tag.title === localeRus.fullTextSearchValue); + if (fullTextFilter) { + newTags = newTags.concat(fullTextFilter); + } + setTags(newTags); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tags]); + + const handleCloseTag = (removedTag: any) => { + if (removedTag.property && removedTag.title !== localeRus.fullTextSearchValue) { + removeFilter(removedTag); + } else { + const newTags = tagsArray.filter((tag) => tag.title !== localeRus.fullTextSearchValue); + setTags(newTags); + setFullTextSearchString(''); + } + }; + + const showModal = () => { + setModalVisible(true); + }; + + const handleAddFilter = (filter: any) => { + if (filter.property === 'assetFolder') { + addFilter(filter, 'front'); + } else { + addFilter(filter); + } + }; + + const hideModal = () => { + setModalVisible(false); + }; + + const handleInputChange = (event: ChangeEvent) => { + setInputValue(event.target.value); + }; + + const handleInputConfirm = () => { + let newTags = tagsArray; + if (inputValue) { + newTags = [ + ...newTags.filter((tag) => tag.title !== localeRus.fullTextSearchValue), + { + title: localeRus.fullTextSearchValue, + value: [inputValue], + property: 'search', + relation: { + predicate: 'contains', + title: '', + }, + }, + ]; + setFullTextSearchString(inputValue); + } + setTags(newTags); + // setInputVisible(false); + setInputValue(''); + }; + return ( +
+ {tagsArray && + tagsArray.map((tag) => { + const isLongTag = tag.value.length > 20; + + let tagStyle = {}; + if (isExpanded) { + tagStyle = { + width: '100%', + margin: '5px', + }; + } + const tagElem = ( + handleCloseTag(tag)}> + {tag.title} {tag.relation && {tag.relation.title} }{' '} + {isLongTag + ? `${tag.value.slice(0, 20)}...` + : tag.valueName && tag.valueName.length > 0 + ? tag.valueName.join(', ') + : tag.value.join(', ')} + + ); + return isLongTag ? ( + + {tagElem} + + ) : ( + tagElem + ); + })} + + + {localeRus.add} + + + {modalVisible && ( + hideModal()} + handleOk={(filter) => { + if (filter) { + handleAddFilter(filter); + } + hideModal(); + }} + handleAdd={(filter) => { + if (filter) { + handleAddFilter(filter); + } + }} + tags={tagsArray} + /> + )} + + {tagsArray && + (!isExpanded ? ( +
+ ); +}; + +export const defaultTags = [ + { + property: 'assetFolder', + title: 'По директории', + relation: { + title: '', + predicate: 'equal', + }, + value: ['ЧТЗ Управление требованиями.'], + }, + { + property: 'type', + title: 'Тип артефакта', + relation: { + title: 'любой из', + predicate: 'any', + }, + value: ['Фича'], + }, + { + property: 'artifactFormat', + title: 'Формат', + relation: { + title: '', + predicate: 'equal', + }, + value: ['Модуль'], + }, +]; diff --git a/src/controls/query/QueryRenderer.tsx b/src/controls/query/QueryRenderer.tsx new file mode 100644 index 0000000..77a4862 --- /dev/null +++ b/src/controls/query/QueryRenderer.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { withStoreToQueryProps } from '../../util/ContextToProps'; +import { Query } from './Query'; +import { rankWith, RankedTester, uiTypeIs } from '../../testers'; + +export const QueryRenderer = (props: any) => { + return ; +}; + +export const antdQueryTester: RankedTester = rankWith(2, uiTypeIs('Query')); +export default withStoreToQueryProps(QueryRenderer); diff --git a/src/controls/query/SearchableList.tsx b/src/controls/query/SearchableList.tsx new file mode 100644 index 0000000..ff3e4ed --- /dev/null +++ b/src/controls/query/SearchableList.tsx @@ -0,0 +1,116 @@ +import React, { useState, useEffect, ChangeEvent } from 'react'; +import { Input, Table } from 'antd'; +import { JsObject } from '@agentlab/sparql-jsld-client'; + +const positionIndependentComp = (lowval: string) => (a: any, b: any) => { + if (a === b) { + return 0; + } + const bind = b.toLowerCase().indexOf(lowval); + const aind = a.toLowerCase().indexOf(lowval); + if (bind === -1 && aind === -1) { + return a.localeCompare(b); + } + if (bind === -1) { + return -1; + } + if (aind === -1) { + return 1; + } + if (aind < bind) { + return -1; + } + if (aind > bind) { + return 1; + } + return 0; +}; + +interface SearchableListProps { + dataSource: { [key: string]: string }[]; + defaultSelectedKey: string; + onClick?: (val: JsObject, rowIndex?: number) => void; +} + +export const SearchableList: React.FC = ({ + dataSource, + defaultSelectedKey, + onClick = () => {}, +}) => { + const [optionList, setOptionList] = useState<{ [key: string]: string }[]>(dataSource); + const [selectedItem, setSelectedItem] = useState(defaultSelectedKey); + const [previousSreachValue, setPreviousSreachValue] = useState(null); + + const columns = [ + { + title: 'title', + dataIndex: 'title', + key: 'key', + render: (item: string) => { + return { + props: { + style: item === selectedItem ? { background: 'rgba(24, 144, 255, 0.35)' } : {}, + }, + children:
{item}
, + }; + }, + }, + ]; + + const onInputSearch = (event: ChangeEvent) => { + const lowval = event.target.value.toLowerCase(); + if (previousSreachValue == null) { + setPreviousSreachValue(lowval); + } + + let sortedOptionList = []; + if (previousSreachValue && (!lowval.startsWith(previousSreachValue) || lowval === previousSreachValue)) { + sortedOptionList = dataSource; + } else { + sortedOptionList = optionList.slice(); + } + + sortedOptionList = sortedOptionList + .filter((word) => { + return word.title.toLowerCase().indexOf(lowval) !== -1; + }) + .sort((a, b) => positionIndependentComp(lowval)(a.title, b.title)); + + if (sortedOptionList && sortedOptionList.length) { + setPreviousSreachValue(lowval); + } + setOptionList(sortedOptionList); + }; + + useEffect(() => { + setOptionList(dataSource); + }, [dataSource]); + + return ( +
+ + { + return { + onClick: () => { + setSelectedItem(record.item); + onClick(record, rowIndex); + }, + }; + }} + columns={columns} + style={{ width: '100%' }} + pagination={false} + showHeader={false} + scroll={{ y: 240 }} + /> + + ); +}; diff --git a/src/controls/query/filtersBody/BodyOfDateTimeFilter.tsx b/src/controls/query/filtersBody/BodyOfDateTimeFilter.tsx new file mode 100644 index 0000000..23e96fe --- /dev/null +++ b/src/controls/query/filtersBody/BodyOfDateTimeFilter.tsx @@ -0,0 +1,188 @@ +import React, { useEffect, useState } from 'react'; +import { DatePicker, Select, Radio, InputNumber } from 'antd'; +import moment, { Moment } from 'moment'; +import { RadioChangeEvent } from 'antd/es/radio'; +import { ValueOfFilter } from '../type'; + +const localeRus = { + daysAgo: 'Дней назад', + monthsAgo: 'Месяцев назад', + yearsAgo: 'Лет назад', + today: 'Сегодня', + yesterday: 'Вчера', + date: 'Дата', +}; + +interface BodyOfDateTimeFilterProps { + handleChange: (value: any) => void; + defaultValues: ValueOfFilter; + type?: string; +} + +export const BodyOfDateTimeFilter: React.FC = ({ handleChange, defaultValues, type }) => { + const timeValue = [localeRus.daysAgo, localeRus.monthsAgo, localeRus.yearsAgo]; + + const [checkedOption, setCheckedOption] = useState(null); + const [firstValueInThreeVariant, setFirstValueInThreeVariant] = useState(null); + const [secondValueInThreeVariant, setSecondValueInThreeVariant] = useState(timeValue[0]); + + useEffect(() => { + let newValue: any = {}; + if (firstValueInThreeVariant && secondValueInThreeVariant) { + let timeOption: moment.unitOfTime.DurationConstructor = 'days'; + if (secondValueInThreeVariant.localeCompare(timeValue[0]) === 0) { + timeOption = 'days'; + } else if (secondValueInThreeVariant.localeCompare(timeValue[1]) === 0) { + timeOption = 'months'; + } else if (secondValueInThreeVariant.localeCompare(timeValue[2]) === 0) { + timeOption = 'years'; + } + const date = moment().subtract(firstValueInThreeVariant, timeOption); + const foundDate = date.format('YYYY-MM-DD'); + newValue = { + value: [`${foundDate}T00:00:00`], + valueName: [moment(foundDate, 'YYYY-MM-DD').fromNow()], + }; + } else { + newValue = { value: [], valueName: [] }; + } + handleChange(newValue); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [firstValueInThreeVariant, secondValueInThreeVariant]); + + const onChangeRatioButton = (e: RadioChangeEvent) => { + const { value } = e.target; + setCheckedOption(value); + if (value === 1) { + const date = moment(); + const today = `${date.format('YYYY-MM-DD')}T00:00:00`; + handleChange({ value: [today], valueName: [localeRus.today] }); + } else if (value === 2) { + const date = moment().subtract(1, 'days'); + const yesterday = `${date.format('YYYY-MM-DD')}T00:00:00`; + handleChange({ value: [yesterday], valueName: [localeRus.yesterday] }); + } else if (value === 3) { + if (firstValueInThreeVariant && secondValueInThreeVariant) { + let timeOption: moment.unitOfTime.DurationConstructor = 'days'; + if (secondValueInThreeVariant.localeCompare(timeValue[0]) === 0) { + timeOption = 'days'; + } else if (secondValueInThreeVariant.localeCompare(timeValue[1]) === 0) { + timeOption = 'months'; + } else if (secondValueInThreeVariant.localeCompare(timeValue[2]) === 0) { + timeOption = 'years'; + } + const date = moment().subtract(firstValueInThreeVariant, timeOption); + const foundDate = date.format('YYYY-MM-DD'); + + handleChange({ + value: [`${foundDate}T00:00:00`], + valueName: [moment(foundDate, 'YYYY-MM-DD').fromNow()], + }); + } else { + handleChange({ value: [], valueName: [] }); + } + } else { + handleChange({ value: [], valueName: [] }); + } + }; + + if (type === 'singleDate') { + return ( + { + handleChange({ + value: dateString === '' ? [] : [moment(dateString, moment.defaultFormat).format('YYYY-MM-DDThh:mm:ss')], + valueName: [dateString], + }); + }} + /> + ); + } + if (type === 'chooseVariant') { + const radioStyle = { + display: 'block', + height: '40px', + lineHeight: '30px', + }; + return ( +
+ + + {localeRus.today} + + + {localeRus.yesterday} + + + { + if (value) { + let newValue: string | null = (value as any).toString(); + newValue = newValue !== '' ? newValue : null; + setFirstValueInThreeVariant(newValue); + } + }} + /> + + + + {localeRus.date}: + {checkedOption === 4 || defaultValues.value[0] ? ( + { + handleChange({ + value: + dateString === '' ? [] : [moment(dateString, moment.defaultFormat).format('YYYY-MM-DDThh:mm:ss')], + valueName: [], + }); + }} + /> + ) : null} + + +
+ ); + } + if (type === 'twoDate') { + return ( + { + handleChange({ + value: + dateString[0] === '' || dateString[1] === '' + ? [] + : dateString.map((rangeDate) => moment(rangeDate, moment.defaultFormat).format('YYYY-MM-DDThh:mm:ss')), + valueName: [], + }); + }} + /> + ); + } + return <>; +}; diff --git a/src/controls/query/filtersBody/BodyOfIntegerFilter.tsx b/src/controls/query/filtersBody/BodyOfIntegerFilter.tsx new file mode 100644 index 0000000..ca195eb --- /dev/null +++ b/src/controls/query/filtersBody/BodyOfIntegerFilter.tsx @@ -0,0 +1,57 @@ +import React, { ChangeEvent, useEffect, useState } from 'react'; +import { Input, Tooltip } from 'antd'; +import { equalTypesOfRelations } from '../Filter'; +import { ValueOfFilter } from '../type'; + +const localeRus = { + inputOneOrMoreNumbers: 'Введите одно или более чисел (через символ [Пробел] или запятая)', + equal: 'равняется', + any: 'любой из', +}; + +interface BodyOfIntegerFilterProps { + handleChange: (value: any) => void; + defaultValues: ValueOfFilter; + type?: string; +} + +export const BodyOfIntegerFilter: React.FC = ({ handleChange, defaultValues, type }) => { + const relationForOneNumber = { title: localeRus.equal, predicate: 'equal', type: 'singleNumber' }; + const relationForSomeNumbers = { title: localeRus.any, predicate: 'any', type: 'hiddenSingleNumber' }; + + const [inputValue, setInputValue] = useState(defaultValues.value.join(', ')); + + useEffect(() => { + setInputValue(defaultValues.value.join(', ')); + }, [defaultValues]); + + const onChange = (e: ChangeEvent) => { + const { value } = e.target; + const reg = /^([1-9][0-9]*)([,\s][0-9]*)*$/; + if (reg.test(value) || value === '') { + setInputValue(value); + handleChange({ value: value ? [value] : [], valueName: [] }); + } + }; + + const onBlur = () => { + const arrayOfValue = inputValue.split(/[\s,]/).filter((val) => val !== ''); + setInputValue(arrayOfValue.join(', ')); + const newValue: any = { value: arrayOfValue, valueName: [] }; + if (arrayOfValue.length > 1) { + newValue['relation'] = relationForSomeNumbers; + } else { + newValue['relation'] = relationForOneNumber; + } + handleChange(newValue); + }; + + if (equalTypesOfRelations(type, 'singleNumber')) { + return ( + + + + ); + } + return <>; +}; diff --git a/src/controls/query/filtersBody/BodyOfListFilter.tsx b/src/controls/query/filtersBody/BodyOfListFilter.tsx new file mode 100644 index 0000000..f8b259d --- /dev/null +++ b/src/controls/query/filtersBody/BodyOfListFilter.tsx @@ -0,0 +1,140 @@ +import React, { ChangeEvent, useEffect, useState } from 'react'; +import { Input, Tree } from 'antd'; +import { JSONSchema6DefinitionForRdfProperty, JsObject } from '@agentlab/sparql-jsld-client'; +import { ValueOfFilter } from '../type'; +import { MstContext } from '../../../MstContext'; + +function renderTreeNode(listOfValues: string[], searchValue: string) { + return listOfValues.map((value, index) => { + let foundIndex = -1; + let beforeStr = ''; + let middleStr = ''; + let afterStr = ''; + let title: JSX.Element | string; + if (searchValue !== '') { + foundIndex = value.toLowerCase().indexOf(searchValue); + + if (foundIndex !== -1) { + beforeStr = value.substr(0, foundIndex); + middleStr = value.substr(foundIndex, searchValue.length); + afterStr = value.substr(foundIndex + searchValue.length); + title = ( + + {beforeStr} + {middleStr} + {afterStr} + + ); + } else { + title = ''; + } + } else { + title = {value}; + } + + return { + title, + key: index.toString(), + }; + // return ; + }); +} + +const localeRus = { + searchValues: 'Поиск значения', +}; + +interface BodyOfListFilterProps { + handleChange: (value: JsObject) => void; + defaultValues: ValueOfFilter; + property: JSONSchema6DefinitionForRdfProperty; + context: JsObject | string; + type?: string; +} + +export const BodyOfListFilter: React.FC = ({ + handleChange, + defaultValues, + property, + context, + type, +}) => { + const [searchValue, setSearchValue] = useState(''); + const [checkedKeys, setCheckedKeys] = useState([]); + const [listOfAliasOfEnum, setListOfAliasOfEnum] = useState([]); + const [listOfValuesOfEnum, setListOfValuesOfEnum] = useState([]); + const [valuesIsLoad, setValuesIsLoad] = useState(true); + + const { store } = React.useContext(MstContext); + + useEffect(() => { + const loadEnumValues = async (iri: string) => { + const values = await store.getData(iri); + setListOfValuesOfEnum(values.map((val: JsObject) => val['@id'])); + setListOfAliasOfEnum(values.map((val: JsObject) => val.title || val.name)); + setValuesIsLoad(true); + }; + setValuesIsLoad(false); + if (context !== undefined && typeof context === 'object' && context['@type'] !== undefined) { + loadEnumValues(context['@type']); + } else if ( + context === 'rdf:type' && + property.enum !== undefined && + property.enum.length > 0 && + typeof property.enum[0] === 'object' + ) { + setListOfValuesOfEnum(property.enum.map((item) => item && (item as any).value)); + setListOfAliasOfEnum(property.enum.map((item) => item && (item as any).alias)); + setValuesIsLoad(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [property]); + + const findKeysItemByValueNames = (values: any[]): string[] => { + const keys: string[] = []; + listOfValuesOfEnum.forEach((val, index) => { + if (values.includes(val)) { + keys.push(index.toString()); + } + }); + return keys; + }; + + const searchValueInTree = (e: ChangeEvent) => { + const value = e.target.value.toLowerCase(); + setSearchValue(value); + }; + + useEffect(() => { + setCheckedKeys(findKeysItemByValueNames(defaultValues.value)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [defaultValues, listOfValuesOfEnum]); + + if (type === 'enumerate') { + return ( +
+ + {valuesIsLoad && (property.type === 'object' || (property.type === 'string' && property.format === 'iri')) && ( + { + setCheckedKeys(keys as string[]); + if ((keys as string[]).length > 0) { + handleChange({ + value: (keys as string[]).map((key) => listOfValuesOfEnum[parseInt(key)]), + valueName: listOfAliasOfEnum ? (keys as string[]).map((key) => listOfAliasOfEnum[parseInt(key)]) : [], + }); + } else { + handleChange({ value: [], valueName: [] }); + } + }} + /> + )} +
+ ); + } + return <>; +}; diff --git a/src/controls/query/filtersBody/BodyOfStringFilter.tsx b/src/controls/query/filtersBody/BodyOfStringFilter.tsx new file mode 100644 index 0000000..6cf44a5 --- /dev/null +++ b/src/controls/query/filtersBody/BodyOfStringFilter.tsx @@ -0,0 +1,56 @@ +import React, { ChangeEvent, useEffect, useState } from 'react'; +import { Input, Typography } from 'antd'; +import { ValueOfFilter } from '../type'; + +const localeRus = { + regExExample: 'Пример регулярного выражения: "^Привет"', + regExStartString: '^ - Начало строки', + regExEndString: '$ - Конец строки', + regExDocTitle: 'Докуументация', + regExDoc: 'Подробная инструкция по использованию на', + regExDocLink: 'сайте документации', +}; + +interface BodyOfStringFilterProps { + handleChange: (value: any) => void; + defaultValues: ValueOfFilter; + type?: string; +} + +export const BodyOfStringFilter: React.FC = ({ handleChange, defaultValues, type }) => { + const [inputValue, setInputValue] = useState(defaultValues.value[0]); + + useEffect(() => { + setInputValue(defaultValues.value[0]); + }, [defaultValues]); + + const onChange = (e: ChangeEvent) => { + const { value } = e.target; + setInputValue(value); + handleChange({ value: value ? [value] : [], valueName: [] }); + }; + + if (type === 'singleString') { + return ; + } + if (type === 'regEx') { + return ( + + + {localeRus.regExExample} +
+ {localeRus.regExStartString} +
+ {localeRus.regExEndString} +
+ + {`${localeRus.regExDoc} `} + + {localeRus.regExDocLink} + + +
+ ); + } + return <>; +}; diff --git a/src/controls/query/type.ts b/src/controls/query/type.ts new file mode 100644 index 0000000..9fec23b --- /dev/null +++ b/src/controls/query/type.ts @@ -0,0 +1,18 @@ +export interface Relation { + title: string; + predicate: string; + type?: string; +} + +export interface FilterType { + value: any[]; + valueName?: string[]; + title: string; + property: string; + relation: Relation; +} + +export interface ValueOfFilter { + value: any[]; + valueName?: string[]; +} diff --git a/src/util/ContextToProps.tsx b/src/util/ContextToProps.tsx index fdf2192..8d9abcf 100644 --- a/src/util/ContextToProps.tsx +++ b/src/util/ContextToProps.tsx @@ -21,6 +21,7 @@ import { compareByIri, ControlComponent, processViewKindOverride, RenderProps } //import { FilterType } from '../complex/Query'; import { validators } from '../validation'; import { MstContext } from '../MstContext'; +import { FilterType } from '../controls/query/type'; declare type Property = 'editable' | 'visible'; declare type JsObject = { [key: string]: any }; @@ -588,6 +589,32 @@ export const withStoreToSaveDialogProps = (Component: React.FC): Rea return ; }); +export const withStoreToQueryProps = (Component: any): any => + observer(({ ...props }: any) => { + const { schema } = props; + const { store } = useContext(MstContext); + const [collIriOverride] = processViewKindOverride(props, store); + return ( + { + //return store.selectData('nav:folder', { '@id': 'folders:samples_module', title: ''}); + /*store.queryFilter(uri, data);*/ + }} + removeFilter={(data: FilterType) => { + /*store.removeFilter(uri, data.property);*/ + }} + fullTextSearchString={store.fullTextSearchString} + setFullTextSearchString={(newValue: string) => { + store.fullTextSearchString = newValue; + }} + /> + ); + }); + const mapStateToControlProps = ({ id, schema, viewKindElement, viewKind, enabled }: ToControlProps) => { const pathSegments = id.split('/'); const path = pathSegments.join('.properties.'); From dfc1e1f8832e8f3162b1c8d1926e44d169f47744 Mon Sep 17 00:00:00 2001 From: Artyom Date: Wed, 22 Sep 2021 13:21:53 +0300 Subject: [PATCH 2/7] ADD SearchComponentWithTable story --- stories/SearchComponentWithTable.stories.tsx | 321 +++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 stories/SearchComponentWithTable.stories.tsx diff --git a/stories/SearchComponentWithTable.stories.tsx b/stories/SearchComponentWithTable.stories.tsx new file mode 100644 index 0000000..53d6567 --- /dev/null +++ b/stories/SearchComponentWithTable.stories.tsx @@ -0,0 +1,321 @@ +/******************************************************************************** + * Copyright (c) 2021 Agentlab and others. + * + * This program and the accompanying materials are made available under the + * terms of the GNU General Public License v. 3.0 which is available at + * https://www.gnu.org/licenses/gpl-3.0.html. + * + * SPDX-License-Identifier: GPL-3.0-only + ********************************************************************************/ +import moment from 'moment'; +import { variable } from '@rdfjs/data-model'; +import React from 'react'; +import { Meta, Story } from '@storybook/react'; + +import { Provider } from 'react-redux'; +import { asReduxStore, connectReduxDevtools } from 'mst-middlewares'; +import { rootModelInitialState, CollState, SparqlClientImpl } from '@agentlab/sparql-jsld-client'; +import { + antdCells, + antdControlRenderers, + antdLayoutRenderers, + createUiModelFromState, + Form, + MstContextProvider, + RendererRegistryEntry, + viewKindCollConstr, + viewDescrCollConstr, +} from '../src'; + +import { tableRenderers } from '../src'; + +const viewKinds = [ + { + '@id': 'rm:TableViewKind', + '@type': 'aldkg:ViewKind', + title: 'Карточки', + description: 'Big table View with form', + collsConstrs: [ + { + '@id': 'rm:ProductCard_Coll', + '@type': 'aldkg:CollConst', + entConstrs: [ + { + '@id': 'rm:ProductCard_Coll_Shape0', + '@type': 'aldkg:EntConstr', + schema: 'hs:ProductCardShape', + /*conditions: { + '@id': 'rm:CollectionView_Artifacts_Coll_Shape0_Condition', + '@type': 'rm:QueryCondition', + assetFolder: 'folders:samples_collection', //'folders:root', + },*/ + }, + ], + //orderBy: [{ expression: variable('identifier0'), descending: false }], + //limit: 50, + }, + ], + elements: [ + { + '@id': 'rm:_934jHd67', + '@type': 'aldkg:VerticalLayout', + options: { + height: 'all-empty-space', + }, + elements: [ + { + '@id': 'SearchComponents', + '@type': 'Query', + }, + { + '@id': 'ProductCardTable', + '@type': 'aldkg:Array', + resultsScope: 'rm:ProductCard_Coll', + options: { + draggable: true, + resizeableHeader: true, + height: 'all-empty-space', + style: { height: '100%' }, + order: [ + 'imageUrl', + 'name', + 'price', + 'saleValue', + 'categoryPopularity', + 'commentsCount', + 'starsValue', + 'questionsCount', + 'lastMonthSalesAmount', + 'lastMonthSalesValue', + 'perMonthSalesAmount', + 'perMonthSalesValue', + 'prevMonthSalesAmount', + 'prevMonthSalesValue', + 'salesAmountDiff', + 'totalSales', + 'totalSalesDiff', + 'stocks', + 'stocksDiffOrders', + 'stocksDiffReturns', + 'country', + 'brand', + 'seller', + 'identifier', + 'rootId', + 'photosCount', + 'firstParsedAt', + 'lastMonthParsedAt', + 'parsedAt', + 'prevParsedAt', + ], + imageUrl: { + width: 60, + formatter: 'image', + editable: false, + }, + identifier: { + formatter: 'link', + //dataToFormatter: { link: 'identifier' }, + sortable: true, + editable: false, + }, + name: { + width: 340, + formatter: 'link', + dataToFormatter: { link: '@id' }, + sortable: true, + editable: false, + }, + country: { + width: 60, + sortable: true, + editable: false, + }, + brand: { + formatter: 'link', + sortable: true, + editable: false, + }, + price: { + width: 60, + sortable: true, + editable: false, + }, + saleValue: { + width: 60, + sortable: true, + editable: false, + }, + seller: { + formatter: 'link', + sortable: true, + editable: false, + }, + categoryPopularity: { + width: 100, + editable: false, + }, + commentsCount: { + width: 100, + sortable: true, + editable: false, + }, + starsValue: { + width: 100, + sortable: true, + editable: false, + }, + questionsCount: { + width: 100, + sortable: true, + editable: false, + }, + lastMonthSalesAmount: { + width: 150, + sortable: true, + editable: false, + }, + lastMonthSalesValue: { + width: 150, + sortable: true, + editable: false, + }, + perMonthSalesAmount: { + width: 150, + sortable: true, + editable: false, + }, + perMonthSalesValue: { + width: 150, + sortable: true, + editable: false, + }, + prevMonthSalesAmount: { + width: 150, + sortable: true, + editable: false, + }, + prevMonthSalesValue: { + width: 150, + sortable: true, + editable: false, + }, + salesAmountDiff: { + width: 150, + sortable: true, + editable: false, + }, + totalSales: { + width: 100, + sortable: true, + editable: false, + }, + totalSalesDiff: { + width: 150, + sortable: true, + editable: false, + }, + stocks: { + width: 100, + sortable: true, + editable: false, + }, + stocksDiffOrders: { + width: 100, + sortable: true, + editable: false, + }, + stocksDiffReturns: { + width: 100, + sortable: true, + editable: false, + }, + rootId: { + editable: false, + }, + photosCount: { + editable: false, + }, + firstParsedAt: { + editable: false, + }, + lastMonthParsedAt: { + editable: false, + }, + parsedAt: { + editable: false, + }, + prevParsedAt: { + editable: false, + }, + }, + }, + ], + }, + ], + }, +]; + +const viewDescrs = [ + { + '@id': 'rm:TableViewDescr', + '@type': 'aldkg:ViewDescr', + viewKind: 'rm:TableViewKind', + title: 'CardCellGrid', + description: 'CardCellGrid', + collsConstrs: [], + // child ui elements configs + elements: [], + }, +]; + +const additionalColls: CollState[] = [ + // ViewKinds Collection + { + constr: viewKindCollConstr, + data: viewKinds, + opt: { + updPeriod: undefined, + lastSynced: moment.now(), + //resolveCollConstrs: false, // disable data loading from the server for viewKinds.collConstrs + }, + }, + // ViewDescrs Collection + { + constr: viewDescrCollConstr, + data: viewDescrs, + opt: { + updPeriod: undefined, + lastSynced: moment.now(), + //resolveCollConstrs: false, // 'true' here (by default) triggers data loading from the server + // for viewDescrs.collConstrs (it loads lazily -- after the first access) + }, + }, +]; + +export default { + title: 'Complex control/Search component and Table', + component: Form, +} as Meta; + +const Template: Story = (args: any) => { + const antdRenderers: RendererRegistryEntry[] = [...antdControlRenderers, ...antdLayoutRenderers, ...tableRenderers]; + + const client = new SparqlClientImpl('https://rdf4j.agentlab.ru/rdf4j-server'); + const rootStore = createUiModelFromState('mktp', client, rootModelInitialState, additionalColls); + const store: any = asReduxStore(rootStore); + // eslint-disable-next-line @typescript-eslint/no-var-requires + connectReduxDevtools(require('remotedev'), rootStore); + return ( +
+ + +
+
+
+
+ ); +}; + +export const RemoteData = Template.bind({}); +RemoteData.args = {}; From efb6c38b9305b60a901611f237f1bcb7d6108c36 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Mon, 18 Oct 2021 17:01:23 +0300 Subject: [PATCH 3/7] [deps] sparql client, router, devdeps --- package.json | 20 ++++++------ yarn.lock | 86 ++++++++++++++++++++++++++-------------------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index 8ea9908..75ef07c 100644 --- a/package.json +++ b/package.json @@ -51,10 +51,10 @@ "lint-staged": "lint-staged" }, "peerDependencies": { - "@agentlab/sparql-jsld-client": ">=5.0.0-rc.16", + "@agentlab/sparql-jsld-client": ">=5.0.0-rc.17", "@ant-design/icons": ">=4.7.0", "@rdfjs/data-model": ">=1.3.4", - "@tinymce/tinymce-react": ">=3.12.6", + "@tinymce/tinymce-react": ">=3.13.0", "antd": ">=4.16.13", "history": ">=5.0.1", "lodash-es": ">=4.17.21", @@ -70,8 +70,8 @@ "react-dom": ">=17.0.2", "react-error-boundary": ">=3.1.3", "react-horizontal-scrolling-menu": ">=2.4.4", - "react-router": ">=6.0.0-beta.6", - "react-router-dom": ">=6.0.0-beta.6", + "react-router": ">=6.0.0-beta.7", + "react-router-dom": ">=6.0.0-beta.7", "react-sortable-hoc": ">=2.0.0", "react-split-pane": ">=2.0.3", "react-virtualized": ">=9.22.3", @@ -81,7 +81,7 @@ "uuid62": ">=1.0.1" }, "dependencies": { - "@agentlab/sparql-jsld-client": "^5.0.0-rc.16", + "@agentlab/sparql-jsld-client": "^5.0.0-rc.17", "@ant-design/icons": "^4.7.0", "@tinymce/tinymce-react": "^3.13.0", "@types/react": "^17.0.30", @@ -104,8 +104,8 @@ "react-horizontal-scrolling-menu": "^2.4.4", "react-is": "^17.0.2", "react-redux": "^7.2.5", - "react-router": "^6.0.0-beta.6", - "react-router-dom": "^6.0.0-beta.6", + "react-router": "^6.0.0-beta.7", + "react-router-dom": "^6.0.0-beta.7", "react-sortable-hoc": "^2.0.0", "react-split-pane": "^2.0.3", "react-virtualized": "^9.22.3", @@ -119,7 +119,7 @@ "@babel/core": "^7.15.8", "@rollup/plugin-commonjs": "^21.0.0", "@rollup/plugin-node-resolve": "^13.0.5", - "@rollup/plugin-typescript": "^8.2.5", + "@rollup/plugin-typescript": "^8.3.0", "@storybook/addon-actions": "^6.3.12", "@storybook/addon-essentials": "^6.3.12", "@storybook/addon-links": "^6.3.12", @@ -146,7 +146,7 @@ "eslint-plugin-cypress": "^2.12.1", "eslint-plugin-flowtype": "^6.1.0", "eslint-plugin-import": "^2.25.2", - "eslint-plugin-jest": "^25.2.1", + "eslint-plugin-jest": "^25.2.2", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.26.1", @@ -168,7 +168,7 @@ "sass-loader": "10.1.1", "storybook-css-modules-preset": "^1.1.1", "style-loader": "^2.0.0", - "ts-jest": "^27.0.6", + "ts-jest": "^27.0.7", "tslib": "^2.3.1", "typescript": "4.4.4", "typescript-plugin-css-modules": "^3.4.0" diff --git a/yarn.lock b/yarn.lock index 00b2c47..909e02d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,21 +2,21 @@ # yarn lockfile v1 -"@agentlab/sparql-jsld-client@^5.0.0-rc.16": - version "5.0.0-rc.16" - resolved "https://registry.yarnpkg.com/@agentlab/sparql-jsld-client/-/sparql-jsld-client-5.0.0-rc.16.tgz#715d19557e7b3db2597c3658523d807af752ec48" - integrity sha512-/FsBLJRKwM57K+izk3Pr5qTY9tkH+YSRGaZYrSsGLOtfQ7M3A8sAa81WQnzSGz7w+WIl76riTjO5+WdWW22wEA== +"@agentlab/sparql-jsld-client@^5.0.0-rc.17": + version "5.0.0-rc.17" + resolved "https://registry.yarnpkg.com/@agentlab/sparql-jsld-client/-/sparql-jsld-client-5.0.0-rc.17.tgz#741752a506d273b68e5fb97606c2a10a7851577d" + integrity sha512-272rjl15/Vncfc+Rg1QPnszdWz2l8aHVoXUxnjkjwtndEpinatEzWkbl1f7U7gZ01FtJJrgLYBjhhMAtDlpwFA== dependencies: - "@rdfjs/data-model" "^1.3.3" + "@rdfjs/data-model" "^1.3.4" "@types/json-schema" "^7.0.9" "@types/lodash-es" "^4.17.5" "@types/rdf-js" "^4.0.2" "@types/sparqljs" "^3.1.3" - axios "^0.22.0" + axios "^0.23.0" is-url "^1.2.4" jsonld "^5.2.0" lodash-es "^4.17.21" - mobx "^6.3.3" + mobx "^6.3.5" mobx-state-tree "^5.0.3" moment "^2.29.1" rdf-literal "^1.3.0" @@ -1956,14 +1956,14 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353" integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q== -"@rdfjs/data-model@^1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@rdfjs/data-model/-/data-model-1.3.3.tgz#be4b078915acac989f1848904f183afcb0b81e0a" - integrity sha512-oo9U3nEowTxxML7CZybujL3e/bty4aXHDSWanWXQxfnJQYKU6p5SCgkjeRbuVfQ9lAVfdvz68Qq5D4LtGWuKug== +"@rdfjs/data-model@^1.3.4": + version "1.3.4" + resolved "https://registry.yarnpkg.com/@rdfjs/data-model/-/data-model-1.3.4.tgz#2b1b3e52755ab1283bf66aa2d3ac97fd8a0332c2" + integrity sha512-iKzNcKvJotgbFDdti7GTQDCYmL7GsGldkYStiP0K8EYtN7deJu5t7U11rKTz+nR7RtesUggT+lriZ7BakFv8QQ== dependencies: - "@types/rdf-js" "*" + "@rdfjs/types" ">=1.0.1" -"@rdfjs/types@*": +"@rdfjs/types@*", "@rdfjs/types@>=1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@rdfjs/types/-/types-1.0.1.tgz#92908a13bc88c71b349b332f3db19178282d6f4e" integrity sha512-YxVkH0XrCNG3MWeZxfg596GFe+oorTVusmNxRP6ZHTsGczZ8AGvG3UchRNkg3Fy4MyysI7vBAA5YZbESL+VmHQ== @@ -2020,10 +2020,10 @@ is-module "^1.0.0" resolve "^1.19.0" -"@rollup/plugin-typescript@^8.2.5": - version "8.2.5" - resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.2.5.tgz#e0319761b2b5105615e5a0c371ae05bc2984b7de" - integrity sha512-QL/LvDol/PAGB2O0S7/+q2HpSUNodpw7z6nGn9BfoVCPOZ0r4EALrojFU29Bkoi2Hr2jgTocTejJ5GGWZfOxbQ== +"@rollup/plugin-typescript@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.3.0.tgz#bc1077fa5897b980fc27e376c4e377882c63e68b" + integrity sha512-I5FpSvLbtAdwJ+naznv+B4sjXZUcIvLLceYpITAn7wAP8W0wqc5noLdGIp9HGVntNhRWXctwPYrSSFQxtl0FPA== dependencies: "@rollup/pluginutils" "^3.1.0" resolve "^1.17.0" @@ -3270,7 +3270,7 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1" integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA== -"@types/rdf-js@*", "@types/rdf-js@^4.0.0", "@types/rdf-js@^4.0.2": +"@types/rdf-js@^4.0.0", "@types/rdf-js@^4.0.2": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/rdf-js/-/rdf-js-4.0.2.tgz#a7eb0ff009915db2c980df97533d3ba7acad0c4b" integrity sha512-soR/+RMogGiDU1lrpuQl5ZL55/L1eq/JlR2dWx052Uh/RYs9okh3XZHFlIJXHZqjqyjEn4WdbOMfBj7vvc2WVQ== @@ -4277,10 +4277,10 @@ axe-core@^4.0.2: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.2.3.tgz#2a3afc332f0031b42f602f4a3de03c211ca98f72" integrity sha512-pXnVMfJKSIWU2Ml4JHP7pZEPIrgBO1Fd3WGx+fPBsS+KRGhE4vxooD8XBGWbQOIVSZsVK7pUDBBkCicNu80yzQ== -axios@^0.22.0: - version "0.22.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.22.0.tgz#bf702c41fb50fbca4539589d839a077117b79b25" - integrity sha512-Z0U3uhqQeg1oNcihswf4ZD57O3NrR1+ZXhxaROaWpDmsDTx7T2HNBV2ulBtie2hwJptu8UvgnJoK+BIqdzh/1w== +axios@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.23.0.tgz#b0fa5d0948a8d1d75e3d5635238b6c4625b05149" + integrity sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg== dependencies: follow-redirects "^1.14.4" @@ -6757,10 +6757,10 @@ eslint-plugin-import@^2.25.2: resolve "^1.20.0" tsconfig-paths "^3.11.0" -eslint-plugin-jest@^25.2.1: - version "25.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-25.2.1.tgz#998b8a770b816534674a2df72b6165a0a42c1f61" - integrity sha512-fC6T95lqgWHsdVFd+f0kTHH32NxbIzIm1fJ/3kGaCFcQP1fJc5khV7DzUHjNQSTOHd5Toa7ccEBptab4uFqbNQ== +eslint-plugin-jest@^25.2.2: + version "25.2.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-25.2.2.tgz#aada85113268e79d4e7423f8ad4e1b740f112e71" + integrity sha512-frn5yhOF60U4kcqozO3zKTNZQUk+mfx037XOy2iiYL8FhorEkuCuL3/flzKcY1ECDP2WYT9ydmvlO3fRW9o4mg== dependencies: "@typescript-eslint/experimental-utils" "^5.0.0" @@ -10574,10 +10574,10 @@ mobx-state-tree@^5.0.3: resolved "https://registry.yarnpkg.com/mobx-state-tree/-/mobx-state-tree-5.0.3.tgz#f026a26be39ba4ef71e4256b9f27d6176a3b2072" integrity sha512-68VKZb30bLWN6MFyYvpvwM93VMEqyJLJsbjTDrYmbRAK594n5LJqQXa7N3N00L/WjcujzMtqkT7CcbM0zqTIcA== -mobx@^6.3.3: - version "6.3.3" - resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.3.tgz#a3006c56243b1c7ea4ee671a66f963b9f43cf1af" - integrity sha512-JoNU50rO6d1wHwKPJqKq4rmUMbYnI9CsJmBo+Cu4exBYenFvIN77LWrZENpzW6reZPADtXMmB1DicbDSfy8Clw== +mobx@^6.3.5: + version "6.3.5" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.3.5.tgz#4464565bf2fa0f74f5dc2e4d6a9713bcee0ddf9c" + integrity sha512-MeDfqtiSbhVoJgXqQsrJwvq2klj7Xk9pPdMThCdFiwFt33vgWJe82ATppPwVzQoz0AI3QpSSwQzcp3TBDK4syg== moment@^2.24.0, moment@^2.25.3, moment@^2.29.1: version "2.29.1" @@ -12792,17 +12792,17 @@ react-refresh@^0.8.3: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== -react-router-dom@^6.0.0-beta.6: - version "6.0.0-beta.6" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.0.0-beta.6.tgz#326c39382674c337b27f5d183026cdbab6373034" - integrity sha512-8pca4/YgQi+azEoY4KtfQMuhkIgRurmqa+mBXIICZRp71nPQX/x1lAnTw6Q2/J5dFu2rIRx1V+HA4hZ1AV0x2Q== +react-router-dom@^6.0.0-beta.7: + version "6.0.0-beta.7" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.0.0-beta.7.tgz#896acf6002a57507cccf09ed5cc6cf9d5554e07d" + integrity sha512-XW50WW6YBYbRJwQPIt8SAcXf8Jo47sY5QuEI8Y/TkckekoMVOHcax4CAArrbONV8MTNyZ4jUfDU776G8ToNDgw== dependencies: - react-router "6.0.0-beta.6" + react-router "6.0.0-beta.7" -react-router@6.0.0-beta.6, react-router@^6.0.0-beta.6: - version "6.0.0-beta.6" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.0.0-beta.6.tgz#147e8313d3ce5b4a53b8fc1dae6f20664f5df69a" - integrity sha512-wuwkFWb464LwWcGuO75kYfzWPE9f5Wgj9/cRVVh8kh/IjOIDpM+8nYCs11ukMJwnwT+3pIps9t/lB9Hnk67zpw== +react-router@6.0.0-beta.7, react-router@^6.0.0-beta.7: + version "6.0.0-beta.7" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.0.0-beta.7.tgz#8c22236c46977e53696ed779ebec31080ab2d3ee" + integrity sha512-n5eIqqY724yW6v2G2DXwcTNwz+NlowV1bOg/OKLz6EtJWD8/lez/5whyfyp9dPkA3ICqYuDZhZxnhk7A/u0aFQ== react-sizeme@^3.0.1: version "3.0.1" @@ -14676,10 +14676,10 @@ ts-essentials@^2.0.3: resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" integrity sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w== -ts-jest@^27.0.6: - version "27.0.6" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.0.6.tgz#9960dbbdb33792b79c5ee24d1c62514fd7b60052" - integrity sha512-XWkEBbrkyUWJdK9FwiCVdBZ7ZmT7sxcKtyVEZNmo7u8eQw6NHmtYEM2WpkX9VfnRI0DjSr6INfEHC9vCFhsFnQ== +ts-jest@^27.0.7: + version "27.0.7" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.0.7.tgz#fb7c8c8cb5526ab371bc1b23d06e745652cca2d0" + integrity sha512-O41shibMqzdafpuP+CkrOL7ykbmLh+FqQrXEmV9CydQ5JBk0Sj0uAEF5TNNe94fZWKm3yYvWa/IbyV4Yg1zK2Q== dependencies: bs-logger "0.x" fast-json-stable-stringify "2.x" From e0e6d3431fd5fa9964426f364561e87920ac72d1 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Mon, 18 Oct 2021 17:09:09 +0300 Subject: [PATCH 4/7] [refactor] RendererRegistryEntry, del FormRenderers --- src/DispatchCell.tsx | 5 +++-- src/Form.tsx | 10 +--------- src/MstContext.tsx | 18 ++++++++++++++---- src/models/MstViewDescr.ts | 2 +- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/DispatchCell.tsx b/src/DispatchCell.tsx index b3ef721..3124bb0 100644 --- a/src/DispatchCell.tsx +++ b/src/DispatchCell.tsx @@ -11,8 +11,9 @@ import { isEqual, maxBy } from 'lodash-es'; import React, { useContext } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; +import { CellRendererRegistryEntry } from './renderers'; import { UnknownRenderer } from './UnknownRenderer'; -import { ErrorFallback, DispatchCellProps, FormsCell, RenderCellProps } from './Form'; +import { ErrorFallback, DispatchCellProps, RenderCellProps } from './Form'; import { MstContext } from './MstContext'; /** @@ -47,7 +48,7 @@ export const DispatchCell: React.FC = React.memo( ); } else { - const Render: React.FC = (renderer as FormsCell).cell; + const Render: React.FC = (renderer as CellRendererRegistryEntry).cell; return ( {}}> ; -} -export interface FormsCell { - tester: RankedTester; - cell: React.FC; -} export interface FormsInitStateProps { viewDescrCollId: string; viewDescrId: string; diff --git a/src/MstContext.tsx b/src/MstContext.tsx index 51313e8..6eea572 100644 --- a/src/MstContext.tsx +++ b/src/MstContext.tsx @@ -8,12 +8,13 @@ * SPDX-License-Identifier: GPL-3.0-only ********************************************************************************/ import React, { createContext, PropsWithChildren } from 'react'; -import { FormsCell, FormsRenderer } from './Form'; +import { CellRendererRegistryEntry, RendererRegistryEntry } from './renderers'; +import { registerMstViewKindSchema } from './models/MstViewDescr'; export interface MstContextProps { store: any; - renderers: FormsRenderer[]; - cells: FormsCell[]; + renderers: RendererRegistryEntry[]; + cells: CellRendererRegistryEntry[]; } export const MstContext = createContext({ @@ -22,6 +23,15 @@ export const MstContext = createContext({ cells: [], }); -export const MstContextProvider = ({ store, renderers, cells = [], children }: PropsWithChildren): JSX.Element => { +export const MstContextProvider = ({ + store, + renderers, + cells = [], + children, +}: PropsWithChildren<{ + store: any; + renderers: RendererRegistryEntry[]; + cells?: CellRendererRegistryEntry[]; +}>): JSX.Element => { return {children}; }; diff --git a/src/models/MstViewDescr.ts b/src/models/MstViewDescr.ts index e752324..587e697 100644 --- a/src/models/MstViewDescr.ts +++ b/src/models/MstViewDescr.ts @@ -257,7 +257,7 @@ export const createUiModelFromState = ( repId: string, client: SparqlClient, initialState: any, - additionalColls: CollState[] | undefined = undefined, + additionalColls?: CollState[], ): any => { registerMstCollSchema(MstViewKind); registerMstCollSchema(MstViewDescr); From 8e5719ad715a5a215c5f67ed124ac2a239cd54cd Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Mon, 18 Oct 2021 17:15:04 +0300 Subject: [PATCH 5/7] [refactor] move MstVkeTypes to controls, add types to the registry --- src/MstContext.tsx | 5 + src/data-controls/index.ts | 4 +- src/index.ts | 1 - src/layouts/AntdVerticalLayout.tsx | 12 +- src/layouts/index.ts | 4 +- src/models/MstViewSchemas.ts | 20 --- src/renderers.ts | 8 +- stories/CardsGridList.stories.tsx | 6 +- stories/CardsHorizontalScroller.stories.tsx | 6 +- stories/Form.stories.tsx | 12 +- stories/FormColumnsAndTable.stories.tsx | 2 + stories/FormOverride.stories.tsx | 2 + stories/TableLocalArtifacts.stories.tsx | 2 + stories/TableRemoteArtifacts.stories.tsx | 16 ++- stories/TableRemoteMktp.stories.tsx | 2 + stories/Tree.stories.tsx | 2 + stories/TreeAndFormArtifact.stories.tsx | 2 + stories/TreeTableFormMktp.stories.tsx | 6 +- stories/TwoTables.stories.tsx | 6 +- stories/TwoTablesBig.stories.tsx | 149 +++++++++++--------- 20 files changed, 153 insertions(+), 114 deletions(-) delete mode 100644 src/models/MstViewSchemas.ts diff --git a/src/MstContext.tsx b/src/MstContext.tsx index 6eea572..9e29610 100644 --- a/src/MstContext.tsx +++ b/src/MstContext.tsx @@ -33,5 +33,10 @@ export const MstContextProvider = ({ renderers: RendererRegistryEntry[]; cells?: CellRendererRegistryEntry[]; }>): JSX.Element => { + renderers.forEach((r) => { + if ((r as any).mstVkeType) { + registerMstViewKindSchema((r as any).mstVkeType); + } + }); return {children}; }; diff --git a/src/data-controls/index.ts b/src/data-controls/index.ts index d3af4a2..358ba93 100644 --- a/src/data-controls/index.ts +++ b/src/data-controls/index.ts @@ -11,13 +11,13 @@ import { RendererRegistryEntry } from '../renderers'; import { antdDataControlTester, AntdDataControlWithStore } from './DataControl'; import { saveControlTester, AntdSaveControlWithStore } from './SaveControl'; import { antdSelectControlTester, AntdSelectControlWithStore } from './SelectControl'; -import { antdTabControlTester, AntdTabControlWithStore } from './TabControlRenderer'; +import { antdTabControlTester, AntdTabControlWithStore, MstVkeTabControl } from './TabControlRenderer'; export const antdDataControlRenderers: RendererRegistryEntry[] = [ { tester: antdDataControlTester, renderer: AntdDataControlWithStore }, { tester: saveControlTester, renderer: AntdSaveControlWithStore }, { tester: antdSelectControlTester, renderer: AntdSelectControlWithStore }, - { tester: antdTabControlTester, renderer: AntdTabControlWithStore }, + { tester: antdTabControlTester, renderer: AntdTabControlWithStore, mstVkeType: MstVkeTabControl }, ]; export * from './DataControl'; diff --git a/src/index.ts b/src/index.ts index b6697aa..6a82809 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,6 @@ export * from './layouts'; export * from './models/uischema'; export * from './models/MstViewDescr'; -export * from './models/MstViewSchemas'; export * from './models/ViewCollConstrs'; export * from './models/ViewShapeSchema'; diff --git a/src/layouts/AntdVerticalLayout.tsx b/src/layouts/AntdVerticalLayout.tsx index 0e990e2..ed96bf8 100644 --- a/src/layouts/AntdVerticalLayout.tsx +++ b/src/layouts/AntdVerticalLayout.tsx @@ -9,7 +9,9 @@ ********************************************************************************/ import React from 'react'; import { Row, Col } from 'antd'; -import { getSnapshot } from 'mobx-state-tree'; +import { getSnapshot, types } from 'mobx-state-tree'; + +import { MstViewKindElement } from '../models/MstViewDescr'; import { FormsDispatchProps, FormsDispatch } from '../Form'; import { rankWith, uiTypeIs, RankedTester } from '../testers'; import { withLayoutProps } from '../util/ContextToProps'; @@ -18,6 +20,14 @@ import { renderLayoutElements } from '../util/layout'; import { Idx } from '../util/layout'; import { LayoutComponent } from './LayoutComponent'; +export const MstVkeVerticalLayout = types.compose( + 'MstVerticalLayout', + MstViewKindElement, + types.model({ + '@type': types.literal('aldkg:VerticalLayout'), + }), +); + export const AntdVerticalLayoutRenderer: React.FC = ({ viewKind, viewKindElement, diff --git a/src/layouts/index.ts b/src/layouts/index.ts index faa67cd..5713d3c 100644 --- a/src/layouts/index.ts +++ b/src/layouts/index.ts @@ -10,7 +10,7 @@ import { RendererRegistryEntry } from '../renderers'; import { antdFormLayoutTester, AntdFormLayoutWithStore } from './AntdFormLayout'; import { antdHorizontalLayoutTester, AntdHorizontalLayoutWithStore } from './AntdHorizontalLayout'; -import { antdVerticalLayoutTester, AntdVerticalLayoutWithStore } from './AntdVerticalLayout'; +import { antdVerticalLayoutTester, AntdVerticalLayoutWithStore, MstVkeVerticalLayout } from './AntdVerticalLayout'; import { splitPaneLayoutTester, SplitPaneLayoutWithStore } from './SplitPaneLayout'; import { antdTabsLayoutTester, AntdTabsLayoutWithStore } from './TabsLayout'; @@ -19,7 +19,7 @@ export const antdLayoutRenderers: RendererRegistryEntry[] = [ tester: antdHorizontalLayoutTester, renderer: AntdHorizontalLayoutWithStore, }, - { tester: antdVerticalLayoutTester, renderer: AntdVerticalLayoutWithStore }, + { tester: antdVerticalLayoutTester, renderer: AntdVerticalLayoutWithStore, mstVkeType: MstVkeVerticalLayout }, { tester: antdFormLayoutTester, renderer: AntdFormLayoutWithStore, diff --git a/src/models/MstViewSchemas.ts b/src/models/MstViewSchemas.ts deleted file mode 100644 index a7f38f2..0000000 --- a/src/models/MstViewSchemas.ts +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2021 Agentlab and others. - * - * This program and the accompanying materials are made available under the - * terms of the GNU General Public License v. 3.0 which is available at - * https://www.gnu.org/licenses/gpl-3.0.html. - * - * SPDX-License-Identifier: GPL-3.0-only - ********************************************************************************/ -import { types } from 'mobx-state-tree'; - -import { MstViewKindElement } from './MstViewDescr'; - -export const MstVerticalLayout = types.compose( - 'MstVerticalLayout', - MstViewKindElement, - types.model({ - '@type': types.literal('aldkg:VerticalLayout'), - }), -); diff --git a/src/renderers.ts b/src/renderers.ts index 80aef4c..37e3c1f 100644 --- a/src/renderers.ts +++ b/src/renderers.ts @@ -11,12 +11,16 @@ import { RankedTester } from './testers'; export interface RendererRegistryEntry { tester: RankedTester; - renderer: any; + renderer: React.FC; + mstVkeType?: any; + mstVdeType?: any; } export interface CellRendererRegistryEntry { tester: RankedTester; - cell: any; + cell: React.FC; + mstVkeType?: any; + mstVdeType?: any; } /** diff --git a/stories/CardsGridList.stories.tsx b/stories/CardsGridList.stories.tsx index c12be5e..67dd6ea 100644 --- a/stories/CardsGridList.stories.tsx +++ b/stories/CardsGridList.stories.tsx @@ -25,8 +25,7 @@ import { RendererRegistryEntry, } from '../src'; import { viewKindCollConstr, viewDescrCollConstr } from '../src/models/ViewCollConstrs'; -import { createUiModelFromState, registerMstViewKindSchema } from '../src/models/MstViewDescr'; -import { MstVerticalLayout } from '../src/models/MstViewSchemas'; +import { createUiModelFromState } from '../src/models/MstViewDescr'; const viewKinds = [ { @@ -293,6 +292,8 @@ export default { argTypes: { backgroundColor: { control: 'color' }, }, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, } as Meta; export const Full: Story<{}> = () => { @@ -301,7 +302,6 @@ export const Full: Story<{}> = () => { ...antdLayoutRenderers, ...antdDataControlRenderers, ]; - registerMstViewKindSchema(MstVerticalLayout); const client = new SparqlClientImpl( 'https://rdf4j.agentlab.ru/rdf4j-server', diff --git a/stories/CardsHorizontalScroller.stories.tsx b/stories/CardsHorizontalScroller.stories.tsx index b1bc37b..53bb29f 100644 --- a/stories/CardsHorizontalScroller.stories.tsx +++ b/stories/CardsHorizontalScroller.stories.tsx @@ -25,8 +25,7 @@ import { RendererRegistryEntry, } from '../src'; import { viewKindCollConstr, viewDescrCollConstr } from '../src/models/ViewCollConstrs'; -import { createUiModelFromState, registerMstViewKindSchema } from '../src/models/MstViewDescr'; -import { MstVerticalLayout } from '../src/models/MstViewSchemas'; +import { createUiModelFromState } from '../src/models/MstViewDescr'; import { variable } from '@rdfjs/data-model'; const viewKinds = [ @@ -289,6 +288,8 @@ export default { argTypes: { backgroundColor: { control: 'color' }, }, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, } as Meta; export const Full: Story<{}> = () => { @@ -297,7 +298,6 @@ export const Full: Story<{}> = () => { ...antdLayoutRenderers, ...antdDataControlRenderers, ]; - registerMstViewKindSchema(MstVerticalLayout); const client = new SparqlClientImpl( 'https://rdf4j.agentlab.ru/rdf4j-server', diff --git a/stories/Form.stories.tsx b/stories/Form.stories.tsx index f102fb2..0d5f088 100644 --- a/stories/Form.stories.tsx +++ b/stories/Form.stories.tsx @@ -36,11 +36,11 @@ const viewKinds = [ collsConstrs: [ { - '@id': 'rm:FormView_Artifacts_Coll', + '@id': 'rm:Artifacts_Coll', '@type': 'aldkg:CollConstr', entConstrs: [ { - '@id': 'rm:FormView_Artifacts_Coll_Ent0', + '@id': 'rm:Artifacts_Coll_Ent', '@type': 'aldkg:EntConstr', schema: 'rm:ArtifactShape', }, @@ -59,17 +59,17 @@ const viewKinds = [ { '@id': 'rm:_17Gj78', '@type': 'aldkg:Control', - resultsScope: 'rm:FormView_Artifacts_Coll/creator', + resultsScope: 'rm:Artifacts_Coll/creator', }, { '@id': 'rm:_297Hgf56', '@type': 'aldkg:Control', - resultsScope: 'rm:FormView_Artifacts_Coll/assetFolder', + resultsScope: 'rm:Artifacts_Coll/assetFolder', }, { '@id': 'rm:_934jHd67', '@type': 'aldkg:Control', - resultsScope: 'rm:FormView_Artifacts_Coll/description', + resultsScope: 'rm:Artifacts_Coll/description', options: { validation: [ { @@ -147,6 +147,8 @@ export default { argTypes: { backgroundColor: { control: 'color' }, }, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, } as Meta; const Template: Story = (args: any) => { diff --git a/stories/FormColumnsAndTable.stories.tsx b/stories/FormColumnsAndTable.stories.tsx index 4ce4de1..f68a444 100644 --- a/stories/FormColumnsAndTable.stories.tsx +++ b/stories/FormColumnsAndTable.stories.tsx @@ -34,6 +34,8 @@ export default { argTypes: { backgroundColor: { control: 'color' }, }, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, } as Meta; export const Empty: Story<{}> = () => { diff --git a/stories/FormOverride.stories.tsx b/stories/FormOverride.stories.tsx index a1a1ab6..e873a40 100644 --- a/stories/FormOverride.stories.tsx +++ b/stories/FormOverride.stories.tsx @@ -146,6 +146,8 @@ export default { argTypes: { backgroundColor: { control: 'color' }, }, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, } as Meta; const Template: Story = (args: any) => { diff --git a/stories/TableLocalArtifacts.stories.tsx b/stories/TableLocalArtifacts.stories.tsx index e5d662a..e777215 100644 --- a/stories/TableLocalArtifacts.stories.tsx +++ b/stories/TableLocalArtifacts.stories.tsx @@ -260,6 +260,8 @@ const fakeData = [ export default { title: 'Table/LocalArtifacts', component: BaseTableControl, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, } as Meta; const Template: Story = (args: any) => { diff --git a/stories/TableRemoteArtifacts.stories.tsx b/stories/TableRemoteArtifacts.stories.tsx index d7f9b42..cdee82f 100644 --- a/stories/TableRemoteArtifacts.stories.tsx +++ b/stories/TableRemoteArtifacts.stories.tsx @@ -52,7 +52,7 @@ const viewKinds = [ '@type': 'aldkg:CollConstr', entConstrs: [ { - '@id': 'rm:Users_Shape0', + '@id': 'rm:Users_Ent', '@type': 'aldkg:EntConstr', schema: 'pporoles:UserShape', }, @@ -63,7 +63,7 @@ const viewKinds = [ '@type': 'aldkg:CollConstr', entConstrs: [ { - '@id': 'rm:ArtifactClasses_Coll_Shape0', + '@id': 'rm:ArtifactClasses_Coll_Ent', '@type': 'aldkg:EntConstr', schema: 'rm:ArtifactClassesShape', }, @@ -74,22 +74,22 @@ const viewKinds = [ '@type': 'aldkg:CollConstr', entConstrs: [ { - '@id': 'rm:ArtifactFormats_Coll_Shape0', + '@id': 'rm:ArtifactFormats_Coll_Ent', '@type': 'aldkg:EntConstr', schema: 'rmUserTypes:_YwcOsRmREemK5LEaKhoOowShape', }, ], }, { - '@id': 'rm:CollectionView_Artifacts_Coll', + '@id': 'rm:Artifacts_Coll', '@type': 'aldkg:CollConstr', entConstrs: [ { - '@id': 'rm:CollectionView_Artifacts_Coll_Shape0', + '@id': 'rm:Artifacts_Coll_Ent', '@type': 'aldkg:EntConstr', schema: 'rm:ArtifactShape', conditions: { - '@id': 'rm:CollectionView_Artifacts_Coll_Shape0_Condition', + '@id': 'rm:Artifacts_Coll_Cond', '@type': 'aldkg:Condition', assetFolder: 'folders:samples_collection', //'folders:root', }, @@ -110,7 +110,7 @@ const viewKinds = [ { '@id': 'ArtifactTable', '@type': 'aldkg:Array', - resultsScope: 'rm:CollectionView_Artifacts_Coll', + resultsScope: 'rm:Artifacts_Coll', options: { draggable: true, resizeableHeader: true, @@ -229,6 +229,8 @@ const additionalColls: CollState[] = [ export default { title: 'Table/Remote Artifacts', component: Form, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, } as Meta; const Template: Story = (args: any) => { diff --git a/stories/TableRemoteMktp.stories.tsx b/stories/TableRemoteMktp.stories.tsx index a5810a6..5ce8886 100644 --- a/stories/TableRemoteMktp.stories.tsx +++ b/stories/TableRemoteMktp.stories.tsx @@ -32,6 +32,8 @@ import { tableRenderers } from '../src'; export default { title: 'Table/Remote Mktp', component: Form, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, } as Meta; const Template: Story = (args: any) => { diff --git a/stories/Tree.stories.tsx b/stories/Tree.stories.tsx index cece893..264b1f3 100644 --- a/stories/Tree.stories.tsx +++ b/stories/Tree.stories.tsx @@ -133,6 +133,8 @@ export default { argTypes: { backgroundColor: { control: 'color' }, }, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, } as Meta; export const Empty: Story<{}> = () => ( diff --git a/stories/TreeAndFormArtifact.stories.tsx b/stories/TreeAndFormArtifact.stories.tsx index a510765..8363603 100644 --- a/stories/TreeAndFormArtifact.stories.tsx +++ b/stories/TreeAndFormArtifact.stories.tsx @@ -170,6 +170,8 @@ export default { argTypes: { backgroundColor: { control: 'color' }, }, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, } as Meta; export const Empty: Story<{}> = () => { diff --git a/stories/TreeTableFormMktp.stories.tsx b/stories/TreeTableFormMktp.stories.tsx index 3b3ce38..c6f07f5 100644 --- a/stories/TreeTableFormMktp.stories.tsx +++ b/stories/TreeTableFormMktp.stories.tsx @@ -42,6 +42,8 @@ export default { argTypes: { backgroundColor: { control: 'color' }, }, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, } as Meta; const Template: Story = (args: any) => { @@ -186,7 +188,7 @@ const viewKinds = [ title: 'WildBerries', treeNodeTitleKey: 'name', treeNodeParentKey: 'SubcatInCatLink', - connections: [{ to: 'mktp:ProductCards_in_Category_Coll_Ent0_con', by: 'CardInCatLink' }], + connections: [{ toObj: 'mktp:ProductCards_in_Category_Coll_Ent0_con', toProp: 'CardInCatLink' }], }, }, { @@ -218,7 +220,7 @@ const viewKinds = [ '@type': 'aldkg:Array', resultsScope: 'mktp:ProductCards_in_Category_Coll', options: { - connections: [{ to: 'mktp:Cards_Coll_Ent0_con', by: '@_id' }], + connections: [{ toObj: 'mktp:Cards_Coll_Ent0_con', toProp: '@_id' }], draggable: true, resizeableHeader: true, height: 'all-empty-space', diff --git a/stories/TwoTables.stories.tsx b/stories/TwoTables.stories.tsx index 3d6bd26..eb7b95f 100644 --- a/stories/TwoTables.stories.tsx +++ b/stories/TwoTables.stories.tsx @@ -34,6 +34,8 @@ import { tableRenderers } from '../src'; export default { title: 'Several Controls/TwoTables RemoteData', component: Form, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, } as Meta; const Template: Story = (args: any) => { @@ -169,7 +171,7 @@ const viewKinds = [ title: 'WildBerries', treeNodeTitleKey: 'name', treeNodeParentKey: 'SubcatInCatLink', - connections: [{ to: 'mktp:ProductCards_in_Category_Coll_Ent0_con', by: 'CardInCatLink' }], + connections: [{ toObj: 'mktp:ProductCards_in_Category_Coll_Ent0_con', toProp: 'CardInCatLink' }], }, }, { @@ -576,7 +578,7 @@ const viewKinds = [ title: 'Продукты', treeNodeTitleKey: 'title', treeNodeParentKey: 'SubProdInProdLink', - connections: [{ to: 'mktp:ProductCards_in_Product_Coll_Ent0_Cond', by: 'CardInProdLink' }], + connections: [{ toObj: 'mktp:ProductCards_in_Product_Coll_Ent0_Cond', toProp: 'CardInProdLink' }], }, }, ], diff --git a/stories/TwoTablesBig.stories.tsx b/stories/TwoTablesBig.stories.tsx index 800bf32..ac25315 100644 --- a/stories/TwoTablesBig.stories.tsx +++ b/stories/TwoTablesBig.stories.tsx @@ -33,35 +33,37 @@ import { export default { title: 'Several Controls/TwoTablesBig', component: Form, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, } as Meta; -const Template: Story = (args: any) => { - const antdRenderers: RendererRegistryEntry[] = [ - ...antdControlRenderers, - ...antdLayoutRenderers, - ...antdDataControlRenderers, - ...tableRenderers, - ]; - +const Template: Story = (args) => { const client = new SparqlClientImpl( 'https://rdf4j.agentlab.ru/rdf4j-server', 'https://rdf4j.agentlab.ru/rdf4j-server/repositories/mktp/namespaces', ); - const rootStore = createUiModelFromState('mktp-fed', client, rootModelInitialState, additionalColls); + const rootStore = createUiModelFromState('mktp-fed', client, rootModelInitialState, args.additionalColls); const store: any = asReduxStore(rootStore); // eslint-disable-next-line @typescript-eslint/no-var-requires connectReduxDevtools(require('remotedev'), rootStore); return (
- - + +
); }; +const antdRenderers: RendererRegistryEntry[] = [ + ...antdControlRenderers, + ...antdLayoutRenderers, + ...antdDataControlRenderers, + ...tableRenderers, +]; + const mktpSchemaRepoIri = 'https://rdf4j.agentlab.ru/rdf4j-server/repositories/mktp-schema'; const mktpOntopRepoIri = 'http://192.168.1.33:8090/sparql'; @@ -72,12 +74,26 @@ const viewKinds = [ title: 'TwoTables', description: 'Big table View with TwoTables', collsConstrs: [ + { + '@id': 'mktp:Marketplaces_Coll', + '@type': 'aldkg:CollConstr', + entConstrs: [ + { + '@id': 'mktp:Marketplaces_Coll_Ent', + '@type': 'aldkg:EntConstr', + schema: 'mktp:MarketplaceShape', + service: mktpSchemaRepoIri, + }, + ], + orderBy: [{ expression: variable('rank0'), descending: false }], + }, + /// Marketplace products { '@id': 'mktp:Categories_Coll', '@type': 'aldkg:CollConstr', entConstrs: [ { - '@id': 'mktp:Categories_Coll_Shape0', + '@id': 'mktp:Categories_Coll_Ent', '@type': 'aldkg:EntConstr', schema: 'hs:CategoryShape', service: mktpSchemaRepoIri, @@ -89,41 +105,42 @@ const viewKinds = [ '@type': 'aldkg:CollConstr', entConstrs: [ { - '@id': 'mktp:ProductCards_in_Category_Coll_Ent0', + '@id': 'mktp:ProductCards_in_Category_Coll_Ent', '@type': 'aldkg:EntConstr', schema: 'hs:ProductCardShape', conditions: { - '@id': 'mktp:ProductCards_in_Category_Coll_Ent0_con', + '@id': 'mktp:ProductCards_in_Category_Coll_Ent_con', CardInCatLink: undefined, //'https://www.wildberries.ru/catalog/zdorove/ozdorovlenie?sort=popular&page=1&xsubject=594', }, service: mktpSchemaRepoIri, }, ], }, + //// Mktp Products { - '@id': 'mktp:ProductCards_in_Product_Coll', + '@id': 'mktp:Products_Coll', '@type': 'aldkg:CollConstr', entConstrs: [ { - '@id': 'mktp:ProductCards_in_Product_Coll_Ent0', + '@id': 'mktp:Products_Coll_Ent', '@type': 'aldkg:EntConstr', - schema: 'hs:ProductCardShape', - conditions: { - '@id': 'mktp:ProductCards_in_Product_Coll_Ent0_Cond', - CardInProdLink: undefined, //'mktp_d:Massager', - }, + schema: 'mktp:ProductShape', service: mktpSchemaRepoIri, }, ], }, { - '@id': 'mktp:Products_Coll', + '@id': 'mktp:ProductCards_in_Product_Coll', '@type': 'aldkg:CollConstr', entConstrs: [ { - '@id': 'mktp:Products_Coll_Shape0', + '@id': 'mktp:ProductCards_in_Product_Coll_Ent', '@type': 'aldkg:EntConstr', - schema: 'mktp:ProductShape', + schema: 'hs:ProductCardShape', + conditions: { + '@id': 'mktp:ProductCards_in_Product_Coll_Ent_Cond', + CardInProdLink: undefined, //'mktp_d:Massager', + }, service: mktpSchemaRepoIri, }, ], @@ -137,6 +154,32 @@ const viewKinds = [ // height: 'all-empty-space', ///}, elements: [ + { + '@id': 'mktp:_df7eds', + '@type': 'aldkg:TabControl', + // by this resultsScope TabControl could have read access to the results, selected by Query with @id='rm:ProjectViewClass_ArtifactFormats_Query' + resultsScope: 'mktp:Marketplaces_Coll', // bind to results data by query @id + options: { + title: 'Маркетплейсы', + style: { + margin: '0 0 0 24px', + }, + contentSize: true, + // by this connection TabControl could have read/write access to the property 'artifactFormat' in condition object with @id='rm:ProjectViewClass_Artifacts_Query_Shape0_Condition' + connections: [ + { + toObj: 'mktp:Categories_Coll_Ent', + toProp: 'schema', + fromProp: 'categoryShape', + }, + { + toObj: 'mktp:ProductCards_in_Category_Coll_Ent', + toProp: 'schema', + fromProp: 'productCardShape', + }, + ], + }, + }, { '@id': 'mktp:_97hFH67', '@type': 'aldkg:SplitPaneLayout', @@ -157,44 +200,16 @@ const viewKinds = [ // child ui elements configs elements: [ { - '@id': 'mktp:MarketplacesTabs', - '@type': 'aldkg:TabsLayout', - elements: [ - { - '@id': 'mktp:_23sLhd67', - '@type': 'aldkg:DataControl', - resultsScope: 'mktp:Categories_Coll', - options: { - renderType: 'tree', - title: 'WildBerries', - treeNodeTitleKey: 'name', - treeNodeParentKey: 'SubcatInCatLink', - connections: [{ to: 'mktp:ProductCards_in_Category_Coll_Ent0_con', by: 'CardInCatLink' }], - }, - }, - { - '@id': 'mktp:_90Syd67', - '@type': 'aldkg:DataControl', - resultsScope: 'mktp:Categories_Coll_Amzn', - options: { - renderType: 'tree', - title: 'Amazon', - treeNodeTitleKey: 'name', - treeNodeParentKey: 'SubcatInCatLink', - }, - }, - { - '@id': 'mktp:_20dAy80', - '@type': 'aldkg:DataControl', - resultsScope: 'mktp:Categories_Coll_1688', - options: { - renderType: 'tree', - title: '1688', - treeNodeTitleKey: 'name', - treeNodeParentKey: 'SubcatInCatLink', - }, - }, - ], + '@id': 'mktp:_23sLhd67', + '@type': 'aldkg:DataControl', + resultsScope: 'mktp:Categories_Coll', + options: { + renderType: 'tree', + title: 'Категории маркетплейса', + treeNodeTitleKey: 'name', + treeNodeParentKey: 'SubcatInCatLink', + connections: [{ toObj: 'mktp:ProductCards_in_Category_Coll_Ent_con', toProp: 'CardInCatLink' }], + }, }, { '@id': 'mktp:CategoryCardsTable', @@ -575,7 +590,7 @@ const viewKinds = [ title: 'Продукты', treeNodeTitleKey: 'title', treeNodeParentKey: 'SubProdInProdLink', - connections: [{ to: 'mktp:ProductCards_in_Product_Coll_Ent0_Cond', by: 'CardInProdLink' }], + connections: [{ toObj: 'mktp:ProductCards_in_Product_Coll_Ent_Cond', toProp: 'CardInProdLink' }], }, }, ], @@ -624,4 +639,10 @@ const additionalColls: CollState[] = [ ]; export const RemoteData = Template.bind({}); -RemoteData.args = {}; +RemoteData.args = { + viewDescrId: viewDescrs[0]['@id'], + viewDescrCollId: viewDescrCollConstr['@id'], + additionalColls: additionalColls, + renderers: antdRenderers, + cells: antdCells, +}; From 0fd7704e1e2fa6568c408ead281a588be9dfde4c Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Mon, 18 Oct 2021 17:18:00 +0300 Subject: [PATCH 6/7] [refactor] Query reacts to CollConstr --- src/controls/index.ts | 5 +- src/controls/query/CreateFilterModal.tsx | 2 +- src/controls/query/Query.tsx | 133 +++--- src/controls/query/QueryRenderer.tsx | 11 - src/util/ContextToProps.tsx | 32 +- ...ble.stories.tsx => QueryTable.stories.tsx} | 54 +-- stories/QueryTreeTableArtifacts.stories.tsx | 391 ++++++++++++++++++ 7 files changed, 505 insertions(+), 123 deletions(-) delete mode 100644 src/controls/query/QueryRenderer.tsx rename stories/{SearchComponentWithTable.stories.tsx => QueryTable.stories.tsx} (97%) create mode 100644 stories/QueryTreeTableArtifacts.stories.tsx diff --git a/src/controls/index.ts b/src/controls/index.ts index c78d53b..94d9382 100644 --- a/src/controls/index.ts +++ b/src/controls/index.ts @@ -18,7 +18,7 @@ import { antdSliderControlTester, AntdSliderControlWithStore } from './AntdSlide import { antdTextControlTester, AntdTextControlWithStore } from './AntdTextControl'; import { tinyMCEControlTester, TinyMCEControlWithStore } from './TinyMCEControl'; import { antdImageControlTester, AntdImageControlWithStore } from './AntdImageControl'; -import QueryRenderer, { antdQueryTester } from './query/QueryRenderer'; +import { antdQueryTester, AntQueryWithStore } from './query/Query'; export const antdControlRenderers: RendererRegistryEntry[] = [ { tester: antdBooleanControlTester, renderer: AntdBooleanControlWithStore }, @@ -38,7 +38,7 @@ export const antdControlRenderers: RendererRegistryEntry[] = [ { tester: antdTextControlTester, renderer: AntdTextControlWithStore }, { tester: tinyMCEControlTester, renderer: TinyMCEControlWithStore }, - { tester: antdQueryTester, renderer: QueryRenderer }, + { tester: antdQueryTester, renderer: AntQueryWithStore }, ]; export * from './AntdBooleanControl'; @@ -52,3 +52,4 @@ export * from './AntdSliderControl'; export * from './AntdTextControl'; export * from './TinyMCEControl'; export * from './AntdImageControl'; +export * from './query/Query'; diff --git a/src/controls/query/CreateFilterModal.tsx b/src/controls/query/CreateFilterModal.tsx index 95c3762..ff35aa0 100644 --- a/src/controls/query/CreateFilterModal.tsx +++ b/src/controls/query/CreateFilterModal.tsx @@ -21,7 +21,7 @@ const localeRus = { title: 'Создание фильтра', add: 'Добавить', addAndClose: 'Добавить и закрыть', - close: 'Отмены', + close: 'Отмена', artifactType: 'Тип требования', emptyFilter: 'Пусто', choiceAtribute: 'Выберите атрибут', diff --git a/src/controls/query/Query.tsx b/src/controls/query/Query.tsx index 9679fa4..aa6c23d 100644 --- a/src/controls/query/Query.tsx +++ b/src/controls/query/Query.tsx @@ -1,8 +1,16 @@ -import React, { useState, useRef, useEffect, ChangeEvent } from 'react'; +import React, { ChangeEvent, useContext, useEffect, useRef, useState } from 'react'; import { Tag, Input, Tooltip, Button } from 'antd'; import { PlusOutlined, UpOutlined, DownOutlined } from '@ant-design/icons'; +import { observer } from 'mobx-react-lite'; +import { JsObject } from '@agentlab/sparql-jsld-client'; + +import { MstContext } from '../../MstContext'; +import { processViewKindOverride } from '../../Form'; +import { rankWith, RankedTester, uiTypeIs } from '../../testers'; + import { CreateFilterModal } from './CreateFilterModal'; import { FilterType } from './type'; +import { isArray } from 'lodash-es'; const localeRus = { add: 'Добавить', @@ -20,33 +28,77 @@ interface FilterByTagProps { setFullTextSearchString?: (newVal: string) => void; } -export const Query: React.FC = ({ - expanded = false, - onExpand = () => {}, - addFilter = () => {}, - removeFilter = () => {}, - tags, - loading = false, - fullTextSearchString = '', - setFullTextSearchString = () => {}, -}) => { +export const AntQueryWithStore = observer((props) => { + const { viewKind, viewDescr, schema } = props; + + const { store } = useContext(MstContext); + const fullTextSearchString = store.fullTextSearchString; + const setFullTextSearchString = (newValue: string) => { + store.fullTextSearchString = newValue; + }; + + const [id, collIri, collIriOverride, inCollPath, viewKindElement, viewDescrElement] = processViewKindOverride( + props, + store, + ); + + const coll = store.getColl(collIriOverride); + const conditionsJs = coll?.collConstr.entConstrs[0].conditionsJs; + + const loading = false; + const onExpand = (isExpand?: boolean) => {}; + const addFilter = (filter: JsObject, location?: string) => {}; + const removeFilter = (filter: JsObject) => { + if (filter?.property && filter?.relation) { + store.editConn( + { toObj: collIriOverride, toProp: filter.property }, + { value: filter.value, relation: filter.relation.predicate }, + ); + } else if (filter?.property) { + store.editConn({ toObj: collIriOverride, toProp: filter.property }, filter.value); + } + }; + const [inputValue, setInputValue] = useState(fullTextSearchString); - const [tagsArray, setTags] = useState([...tags]); - const [isExpanded, setIsExpanded] = useState(expanded); + const [tagsArray, setTags] = useState([]); + const [isExpanded, setIsExpanded] = useState(false); const [modalVisible, setModalVisible] = useState(false); const inputRef = useRef(null); useEffect(() => { - let newTags = [...tags]; + let tags: FilterType[] = []; + const filters: any = {}; + if (conditionsJs) { + Object.keys(conditionsJs).forEach((k) => { + if (k !== '@id' && k !== '@type') { + let value = conditionsJs[k]; + let valueName: any[] = []; + if (typeof value === 'object' && value['@id'] !== undefined && value['@type'] !== undefined) { + valueName = [value.title]; + value = value['@id']; + } + const newFilter = { + title: schema.properties[k].title, + property: k, + relation: { title: '', predicate: 'equal', type: 'singleString' }, + value, + valueName, + }; + filters[k] = newFilter; + } + }); + tags = Object.values(filters); + } + const fullTextFilter = tagsArray.find((tag) => tag.title === localeRus.fullTextSearchValue); if (fullTextFilter) { - newTags = newTags.concat(fullTextFilter); + tags = tags.concat(fullTextFilter); } - setTags(newTags); + setTags(tags); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [tags]); + }, [conditionsJs]); const handleCloseTag = (removedTag: any) => { if (removedTag.property && removedTag.title !== localeRus.fullTextSearchValue) { @@ -103,8 +155,6 @@ export const Query: React.FC = ({
{tagsArray && tagsArray.map((tag) => { - const isLongTag = tag.value.length > 20; - let tagStyle = {}; if (isExpanded) { tagStyle = { @@ -112,14 +162,17 @@ export const Query: React.FC = ({ margin: '5px', }; } + const value = + tag.valueName && tag.valueName.length > 0 + ? tag.valueName.join(', ') + : isArray(tag.value) + ? tag.value.join(', ') + : tag.value; + const isLongTag = value.length > 200; const tagElem = ( handleCloseTag(tag)}> {tag.title} {tag.relation && {tag.relation.title} }{' '} - {isLongTag - ? `${tag.value.slice(0, 20)}...` - : tag.valueName && tag.valueName.length > 0 - ? tag.valueName.join(', ') - : tag.value.join(', ')} + {isLongTag ? `${value.slice(0, 20)}...` : value} ); return isLongTag ? ( @@ -185,34 +238,6 @@ export const Query: React.FC = ({ ))}
); -}; +}); -export const defaultTags = [ - { - property: 'assetFolder', - title: 'По директории', - relation: { - title: '', - predicate: 'equal', - }, - value: ['ЧТЗ Управление требованиями.'], - }, - { - property: 'type', - title: 'Тип артефакта', - relation: { - title: 'любой из', - predicate: 'any', - }, - value: ['Фича'], - }, - { - property: 'artifactFormat', - title: 'Формат', - relation: { - title: '', - predicate: 'equal', - }, - value: ['Модуль'], - }, -]; +export const antdQueryTester: RankedTester = rankWith(2, uiTypeIs('aldkg:Query')); diff --git a/src/controls/query/QueryRenderer.tsx b/src/controls/query/QueryRenderer.tsx deleted file mode 100644 index 77a4862..0000000 --- a/src/controls/query/QueryRenderer.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import { withStoreToQueryProps } from '../../util/ContextToProps'; -import { Query } from './Query'; -import { rankWith, RankedTester, uiTypeIs } from '../../testers'; - -export const QueryRenderer = (props: any) => { - return ; -}; - -export const antdQueryTester: RankedTester = rankWith(2, uiTypeIs('Query')); -export default withStoreToQueryProps(QueryRenderer); diff --git a/src/util/ContextToProps.tsx b/src/util/ContextToProps.tsx index 716b89c..8e8981b 100644 --- a/src/util/ContextToProps.tsx +++ b/src/util/ContextToProps.tsx @@ -324,8 +324,8 @@ export const withStoreToSelectControlProps = (Component: React.FC): React.F withConnections && options.connections.forEach((e: any) => { const condition: any = {}; - condition[e.by] = data['@id']; - //view2.editCondition(e.to, condition, scope, e.by, data); + condition[e.toProp] = data['@id']; + //view2.editCondition(e.toObj, condition, scope, e.toProp, data); }); }; return ( @@ -541,7 +541,7 @@ export const withLayoutProps = (Component: React.FC): React.FC< const visible = checkProperty('visible', id, viewKindElement, viewKind); const { store } = useContext(MstContext); if (viewKindElement.options && viewKindElement.options.connections) { - viewKindElement.options.connections.forEach((e: any) => store.setSaveLogic(e.from, e.to)); + viewKindElement.options.connections.forEach((e: any) => store.setSaveLogic(e.from, e.toObj)); } return ( ): Rea return ; }); -export const withStoreToQueryProps = (Component: any): any => - observer(({ ...props }: any) => { - const { schema } = props; - const { store } = useContext(MstContext); - const [collIriOverride] = processViewKindOverride(props, store); - return ( - { - //return store.selectData('nav:folder', { '@id': 'folders:samples_module', title: ''}); - /*store.queryFilter(uri, data);*/ - }} - removeFilter={(data: FilterType) => { - /*store.removeFilter(uri, data.property);*/ - }} - fullTextSearchString={store.fullTextSearchString} - setFullTextSearchString={(newValue: string) => { - store.fullTextSearchString = newValue; - }} - /> - ); - }); - const mapStateToControlProps = ({ id, schema, viewKindElement, viewKind, data }: ToControlProps & { data: any }) => { const pathSegments = viewKindElement?.resultsScope?.split('/') || []; const path = pathSegments.join('.properties.'); diff --git a/stories/SearchComponentWithTable.stories.tsx b/stories/QueryTable.stories.tsx similarity index 97% rename from stories/SearchComponentWithTable.stories.tsx rename to stories/QueryTable.stories.tsx index 53d6567..dd0fae5 100644 --- a/stories/SearchComponentWithTable.stories.tsx +++ b/stories/QueryTable.stories.tsx @@ -29,6 +29,32 @@ import { import { tableRenderers } from '../src'; +export default { + title: 'Several Controls/QueryTable', + component: Form, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, +} as Meta; + +const Template: Story = (args: any) => { + const antdRenderers: RendererRegistryEntry[] = [...antdControlRenderers, ...antdLayoutRenderers, ...tableRenderers]; + + const client = new SparqlClientImpl('https://rdf4j.agentlab.ru/rdf4j-server'); + const rootStore = createUiModelFromState('mktp', client, rootModelInitialState, additionalColls); + const store: any = asReduxStore(rootStore); + // eslint-disable-next-line @typescript-eslint/no-var-requires + connectReduxDevtools(require('remotedev'), rootStore); + return ( +
+ + + + + +
+ ); +}; + const viewKinds = [ { '@id': 'rm:TableViewKind', @@ -64,8 +90,8 @@ const viewKinds = [ }, elements: [ { - '@id': 'SearchComponents', - '@type': 'Query', + '@id': 'mktp:QueryComponent', + '@type': 'aldkg:Query', }, { '@id': 'ProductCardTable', @@ -293,29 +319,5 @@ const additionalColls: CollState[] = [ }, ]; -export default { - title: 'Complex control/Search component and Table', - component: Form, -} as Meta; - -const Template: Story = (args: any) => { - const antdRenderers: RendererRegistryEntry[] = [...antdControlRenderers, ...antdLayoutRenderers, ...tableRenderers]; - - const client = new SparqlClientImpl('https://rdf4j.agentlab.ru/rdf4j-server'); - const rootStore = createUiModelFromState('mktp', client, rootModelInitialState, additionalColls); - const store: any = asReduxStore(rootStore); - // eslint-disable-next-line @typescript-eslint/no-var-requires - connectReduxDevtools(require('remotedev'), rootStore); - return ( -
- - - - - -
- ); -}; - export const RemoteData = Template.bind({}); RemoteData.args = {}; diff --git a/stories/QueryTreeTableArtifacts.stories.tsx b/stories/QueryTreeTableArtifacts.stories.tsx new file mode 100644 index 0000000..8148839 --- /dev/null +++ b/stories/QueryTreeTableArtifacts.stories.tsx @@ -0,0 +1,391 @@ +/******************************************************************************** + * Copyright (c) 2021 Agentlab and others. + * + * This program and the accompanying materials are made available under the + * terms of the GNU General Public License v. 3.0 which is available at + * https://www.gnu.org/licenses/gpl-3.0.html. + * + * SPDX-License-Identifier: GPL-3.0-only + ********************************************************************************/ +import moment from 'moment'; +import { variable } from '@rdfjs/data-model'; +import React from 'react'; +import { Meta, Story } from '@storybook/react'; + +import { Provider } from 'react-redux'; +import { asReduxStore, connectReduxDevtools } from 'mst-middlewares'; +import { rootModelInitialState, CollState, SparqlClientImpl } from '@agentlab/sparql-jsld-client'; +import { + antdCells, + antdControlRenderers, + antdDataControlRenderers, + antdLayoutRenderers, + createUiModelFromState, + Form, + MstContextProvider, + RendererRegistryEntry, + viewKindCollConstr, + viewDescrCollConstr, +} from '../src'; + +import { tableRenderers } from '../src'; + +export default { + title: 'Several Controls/QueryTreeTableArtifacts', + component: Form, + // Due to Storybook bug https://github.com/storybookjs/storybook/issues/12747 + parameters: { docs: { source: { type: 'code' } } }, +} as Meta; + +const Template: Story = (args: any) => { + const antdRenderers: RendererRegistryEntry[] = [ + ...antdControlRenderers, + ...antdLayoutRenderers, + ...antdDataControlRenderers, + ...tableRenderers, + ]; + + const client = new SparqlClientImpl('https://rdf4j.agentlab.ru/rdf4j-server'); + const rootStore = createUiModelFromState('reqs2', client, rootModelInitialState, additionalColls); + const store: any = asReduxStore(rootStore); + // eslint-disable-next-line @typescript-eslint/no-var-requires + connectReduxDevtools(require('remotedev'), rootStore); + return ( +
+ + + + + +
+ ); +}; + +const viewKinds = [ + { + '@id': 'rm:ProjectViewKind', + '@type': 'aldkg:ViewKind', + title: 'Project View', + description: 'Project View', + collsConstrs: [ + { + '@id': 'rm:Artifacts_Coll', + '@type': 'aldkg:CollConst', + entConstrs: [ + { + '@id': 'rm:Artifacts_Coll_Ent', + '@type': 'aldkg:EntConstr', + schema: 'rm:ArtifactShape', + conditions: { + '@id': 'rm:Artifacts_Coll_Cond', + '@type': 'aldkg:Condition', + assetFolder: 'folders:samples_collection', //'folders:root', + }, + }, + ], + //orderBy: [{ expression: variable('identifier0'), descending: false }], + //limit: 50, + }, + { + '@id': 'rm:Folders_Coll', + '@type': 'aldkg:CollConstr', + entConstrs: [ + { + '@id': 'rm:Folders_Coll_Ent', + '@type': 'raldkgm:EntConstr', + schema: 'nav:folderShape', + }, + ], + }, + { + '@id': 'rm:Users_Coll', + '@type': 'aldkg:CollConstr', + entConstrs: [ + { + '@id': 'rm:Users_Ent', + '@type': 'aldkg:EntConstr', + schema: 'pporoles:UserShape', + }, + ], + }, + { + '@id': 'rm:ArtifactFormats_Coll', + '@type': 'aldkg:CollConstr', + entConstrs: [ + { + '@id': 'rm:ArtifactFormats_Coll_Ent', + '@type': 'aldkg:EntConstr', + schema: 'rmUserTypes:_YwcOsRmREemK5LEaKhoOowShape', + }, + ], + }, + { + '@id': 'rm:ArtifactClasses_Coll', + '@type': 'aldkg:CollConstr', + entConstrs: [ + { + '@id': 'rm:ArtifactClasses_Coll_Ent', + '@type': 'aldkg:EntConstr', + schema: 'rm:ArtifactClassesShape', + }, + ], + }, + ], + elements: [ + { + '@id': 'rm:_934jHd67', + '@type': 'aldkg:VerticalLayout', + options: { + height: 'all-empty-space', + }, + elements: [ + { + '@id': 'rm:_485Jdf7', + '@type': 'aldkg:VerticalLayout', + elements: [ + { + '@id': 'rm:_23fJd7', + '@type': 'aldkg:VerticalLayout', + options: { + justify: 'start', // start end center space-between space-around + contentSize: true, + style: { + flexGrow: '5', + }, + }, + elements: [ + { + '@id': 'rm:_903Fds1', + '@type': 'aldkg:TabControl', + // by this resultsScope TabControl could have read access to the results, selected by Query with @id='rm:ProjectViewClass_ArtifactFormats_Query' + resultsScope: 'rm:ArtifactFormats_Coll', // bind to results data by query @id + tabs: [ + { + '@id': 'mktp:_4eos3', + '@type': 'aldkg:Tab', + title: 'Все', + value: undefined, + rank: 0, + }, + ], + options: { + title: 'Требования', + style: { + margin: '0 0 0 24px', + }, + contentSize: true, + // by this connection TabControl could have read/write access to the property 'artifactFormat' in condition object with @id='rm:ProjectViewClass_Artifacts_Query_Shape0_Condition' + connections: [ + { + toObj: 'rm:Artifacts_Coll_Cond', + toProp: 'artifactFormat', + }, + ], + }, + }, + ], + }, + /*{ + '@type': 'aldkg:VerticalLayout', + options: { + contentSize: true, + justify: 'end', + }, + elements: [ + { + '@type': 'aldkg:Button', + options: { + contentSize: true, + icon: 'sync', + }, + }, + { + '@type': 'aldkg:MenuControl', + resultsScope: 'rm:Artifacts_Coll', + options: { + contentSize: true, + style: { + margin: '0 24px 0 5px', + }, + }, + elements: [ + { + '@id': 'attr-types-and-links-settings', + '@type': 'aldkg:View', + resultsScope: 'rm:dataModelView', + options: { + height: 'all-empty-space', + modal: true, + }, + }, + ], + }, + ], + },*/ + ], + }, + { + '@id': 'rm:QueryComponent', + '@type': 'aldkg:Query', + resultsScope: 'rm:Artifacts_Coll', // bind to json-ld object by '@id' + options: { + style: { + margin: '0 0 0 16px', + }, + }, + }, + { + '@id': 'rm:_we34U8', + '@type': 'aldkg:SplitPaneLayout', + options: { + style: { + width: '100%', + height: '100%', + }, + height: 'all-empty-space', + //width: 'all-empty-space', + defaultSize: { + 'rm:_901hHft': '17%', + }, + }, + elements: [ + { + '@id': 'rm:_901hHft', + '@type': 'aldkg:DataControl', + resultsScope: 'rm:Folders_Coll', + options: { + renderType: 'tree', + connections: [ + { + //from: 'selector', // inner UI component variable name in case it has several variables? e.g. drag, moveX/moveY, width/height? + toObj: 'rm:Artifacts_Coll_Cond', + toProp: 'assetFolder', + }, + ], + }, + }, + { + '@id': 'rm:ArtifactTable', + '@type': 'aldkg:Array', + resultsScope: 'rm:Artifacts_Coll', + options: { + draggable: true, + resizeableHeader: true, + order: [ + 'identifier', + 'title', + '@type', + 'artifactFormat', + 'description', + 'xhtmlText', + 'modified', + 'modifiedBy', + '@id', + 'assetFolder', + ], + identifier: { + width: 140, + sortable: true, + formatter: 'link', + editable: false, + dataToFormatter: { + link: '@id', + }, + }, + title: { + formatter: 'artifactTitle', + dataToFormatter: { + type: 'artifactFormat', + }, + }, + '@type': { + width: 140, + formatter: 'dataFormatter', + query: 'rm:ArtifactClasses_Coll', + }, + artifactFormat: { + formatter: 'dataFormatter', + query: 'rm:ArtifactFormats_Coll', + }, + description: { + //formatter: 'tinyMCE', + sortable: true, + }, + xhtmlText: { + formatter: 'tinyMCE', + tinyWidth: 'emptySpace', // emptySpace, content, + width: 300, + }, + modified: { + width: 140, + formatter: 'dateTime', + sortable: true, + }, + modifiedBy: { + formatter: 'dataFormatter', + query: 'rm:Users_Coll', + key: 'name', + }, + '@id': { + width: 220, + }, + assetFolder: { + formatter: 'dataFormatter', + query: 'rm:Folders_Coll', + }, + //creator: { + // formatter: 'userName', + //}, + //created: { + // width: 140, + // formatter: 'dateTime', + //}, + }, + }, + ], + }, + ], + }, + ], + }, +]; + +const viewDescrs = [ + { + '@id': 'rm:ProjectViewDescr', + '@type': 'aldkg:ViewDescr', + viewKind: 'rm:ProjectViewKind', + title: 'Project View', + description: 'Project View', + collsConstrs: [], + // child ui elements configs + elements: [], + }, +]; + +const additionalColls: CollState[] = [ + // ViewKinds Collection + { + constr: viewKindCollConstr, + data: viewKinds, + opt: { + updPeriod: undefined, + lastSynced: moment.now(), + //resolveCollConstrs: false, // disable data loading from the server for viewKinds.collConstrs + }, + }, + // ViewDescrs Collection + { + constr: viewDescrCollConstr, + data: viewDescrs, + opt: { + updPeriod: undefined, + lastSynced: moment.now(), + //resolveCollConstrs: false, // 'true' here (by default) triggers data loading from the server + // for viewDescrs.collConstrs (it loads lazily -- after the first access) + }, + }, +]; + +export const RemoteData = Template.bind({}); +RemoteData.args = {}; From c05178348e5fce1e9aca8fd2277af5f481c0bb9e Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Mon, 18 Oct 2021 17:19:10 +0300 Subject: [PATCH 7/7] [refactor] Tree & Tabs to MST --- src/data-controls/DataControl.tsx | 4 +- src/data-controls/TabControlRenderer.tsx | 79 +++++++++++++++++------- src/data-controls/TreeRenderer.tsx | 5 ++ src/util/ContextToProps.tsx | 59 ++---------------- 4 files changed, 70 insertions(+), 77 deletions(-) diff --git a/src/data-controls/DataControl.tsx b/src/data-controls/DataControl.tsx index c8bc1b5..23dea64 100644 --- a/src/data-controls/DataControl.tsx +++ b/src/data-controls/DataControl.tsx @@ -68,10 +68,10 @@ export const AntdDataLayout: React.FC = React.memo( /> ); }, - (prev, next) => { + /*(prev, next) => { console.log('PREV', prev, 'NEXT', next, prev === next); return true; - }, + },*/ ); export const antdDataControlTester: RankedTester = rankWith(2, uiTypeIs('aldkg:DataControl')); diff --git a/src/data-controls/TabControlRenderer.tsx b/src/data-controls/TabControlRenderer.tsx index f37d70d..872791a 100644 --- a/src/data-controls/TabControlRenderer.tsx +++ b/src/data-controls/TabControlRenderer.tsx @@ -7,29 +7,72 @@ * * SPDX-License-Identifier: GPL-3.0-only ********************************************************************************/ -import React from 'react'; -import { Tabs, Col } from 'antd'; +import React, { useContext } from 'react'; +import { Tabs, Col, Spin } from 'antd'; +import { getSnapshot, types } from 'mobx-state-tree'; +import { observer } from 'mobx-react-lite'; import { JsObject } from '@agentlab/sparql-jsld-client'; +import { MstViewKindElement } from '../models/MstViewDescr'; + import { rankWith, RankedTester, uiTypeIs } from '../testers'; -import { withStoreToTabProps } from '../util/ContextToProps'; +import { MstContext } from '../MstContext'; +import { processViewKindOverride } from '../Form'; +import { MstJsObject } from '@agentlab/sparql-jsld-client/es/models/MstCollConstr'; +import { IViewKindElement } from '../models/uischema'; -interface TabControlProps { +export type IMstVkeTabControl = IViewKindElement & { tabs: JsObject[]; - handleChange?: (data: JsObject) => void; - options: JsObject; -} - -const localeRus = { - all: 'Все', }; -export const TabControl: React.FC = ({ tabs = [], handleChange = () => {}, options = {} }) => { +export const MstVkeTabControl = types.compose( + 'MstVkeTabControl', + MstViewKindElement, + types.model({ + '@type': types.literal('aldkg:TabControl'), + tabs: types.maybe(types.array(MstJsObject)), + }), +); + +export const AntdTabControlWithStore = observer((props) => { + const { schema, viewKind, viewDescr } = props; + const { store } = useContext(MstContext); + //if (viewKindElement.resultsScope && !store.saveLogicTree[viewKindElement.resultsScope]) { + // store.setSaveLogic(viewKindElement.resultsScope); + //} + + const [id, collIri, collIriOverride, inCollPath, viewKindElement, viewDescrElement] = processViewKindOverride( + props, + store, + ); + const options = viewKindElement.options || {}; + + const coll = store.getColl(collIriOverride); + let data = coll?.data; + if (!data) { + return ; + } + data = getSnapshot(data); + const withConnections = options.connections; + let tabs: JsObject[] = data; + + let additionalTabs = (viewKindElement as IMstVkeTabControl).tabs; + if (additionalTabs) { + additionalTabs = additionalTabs.slice().sort((t1, t2) => t2.rank - t1.rank); + tabs = [...additionalTabs.filter((t) => t.rank <= 100), ...tabs, ...additionalTabs.filter((t) => t.rank > 100)]; + } + + const handleChange = (data: any) => { + store.setSelectedData(collIriOverride, data); + if (withConnections) store.editConn(withConnections, data); + }; + const onSelect = (key: string) => { - if (key === 'all') { - handleChange({}); + const v = tabs[Number(key)]; + if (v['@type'] === 'aldkg:Tab') { + handleChange(v.value); } else { - handleChange(tabs[Number(key)]); + handleChange(v); } }; return ( @@ -43,7 +86,6 @@ export const TabControl: React.FC = ({ tabs = [], handleChange )}
- {tabs.map((tab, index) => ( ))} @@ -51,11 +93,6 @@ export const TabControl: React.FC = ({ tabs = [], handleChange ); -}; - -export const TabControlRenderer = (props: any): JSX.Element => { - return ; -}; +}); export const antdTabControlTester: RankedTester = rankWith(2, uiTypeIs('aldkg:TabControl')); -export const AntdTabControlWithStore = withStoreToTabProps(TabControlRenderer); diff --git a/src/data-controls/TreeRenderer.tsx b/src/data-controls/TreeRenderer.tsx index 36567d6..0371b6c 100644 --- a/src/data-controls/TreeRenderer.tsx +++ b/src/data-controls/TreeRenderer.tsx @@ -46,6 +46,11 @@ export const TreeRenderer: React.FC = (props) => { const [searchValue, setSearchValue] = useState(''); const [autoExpandParent, setAutoExpandParent] = useState(true); const [beforeSearchExpand, setBeforeSearchExpand] = useState([]); + + useEffect(() => { + setTreeData(child); + }, [child]); + useEffect(() => { onSelect(selected); }, [selected, onSelect]); diff --git a/src/util/ContextToProps.tsx b/src/util/ContextToProps.tsx index 8e8981b..fe76f0a 100644 --- a/src/util/ContextToProps.tsx +++ b/src/util/ContextToProps.tsx @@ -249,14 +249,12 @@ export const withStoreToDataControlProps = (Component: React.FC): React.FC< store, ); const coll = store.getColl(collIriOverride); - let data = coll?.data; - if (!data || data.length === 0) { - //if (store.data[scope] === undefined) { - //store.loadData(scope); - return ; + if (!coll) return ; + + let data: any[] = []; + if (!coll.isLoading) { + data = cloneDeep(coll.dataJs); } - //const scope = viewKindElement.resultsScope; - data = cloneDeep(getSnapshot(data)); const options = viewKindElement?.options || {}; const withConnections = options.connections; const onSelect = (data: any) => { @@ -342,53 +340,6 @@ export const withStoreToSelectControlProps = (Component: React.FC): React.F ); }); -export const withStoreToTabProps = (Component: React.FC): React.FC => - observer(({ ...props }: any) => { - const { schema, viewKind, viewDescr } = props; - const { store } = useContext(MstContext); - //if (viewKindElement.resultsScope && !store.saveLogicTree[viewKindElement.resultsScope]) { - // store.setSaveLogic(viewKindElement.resultsScope); - //} - - const [id, collIri, collIriOverride, inCollPath, viewKindElement, viewDescrElement] = processViewKindOverride( - props, - store, - ); - const options = viewKindElement.options || {}; - - const coll = store.getColl(collIriOverride); - let data = coll?.data; - if (!data) { - return ; - } - data = getSnapshot(data); - const withConnections = options.connections; - const onChange = (data: any) => { - store.setSelectedData(collIriOverride, data); - if (withConnections) { - store.editConn(withConnections, data['@id']); - // options.connections.forEach((e: any) => { - // const condition: any = {}; - // condition[e.by] = data['@id']; - // store.editCondition(e.to, condition, scope, e.by, data); - // }); - } - }; - return ( - - ); - }); - export const withStoreToMenuProps = (Component: React.FC): React.FC => observer(({ ...props }: any) => { const { schema, viewKind, viewDescr } = props;