From b7b2230f0a857680be77ee59f80072e3e3077054 Mon Sep 17 00:00:00 2001 From: the-good-boy Date: Fri, 28 Jul 2023 19:19:18 +0530 Subject: [PATCH 01/24] feat(RelationshipType): Relationship Type Editor --- .../forms/type-editor/relationship-type.tsx | 362 ++++++++++++++++++ .../forms/type-editor/typeUtils.tsx | 74 ++++ .../type-editor/relationship-type.tsx | 51 +++ src/server/helpers/middleware.ts | 2 + src/server/routes.js | 2 + .../routes/type-editor/relationship-type.tsx | 51 +++ .../routes/type-editor/relationship-types.tsx | 90 +++++ webpack.client.js | 3 +- 8 files changed, 634 insertions(+), 1 deletion(-) create mode 100644 src/client/components/forms/type-editor/relationship-type.tsx create mode 100644 src/client/components/forms/type-editor/typeUtils.tsx create mode 100644 src/client/controllers/type-editor/relationship-type.tsx create mode 100644 src/server/routes/type-editor/relationship-type.tsx create mode 100644 src/server/routes/type-editor/relationship-types.tsx diff --git a/src/client/components/forms/type-editor/relationship-type.tsx b/src/client/components/forms/type-editor/relationship-type.tsx new file mode 100644 index 0000000000..5897168688 --- /dev/null +++ b/src/client/components/forms/type-editor/relationship-type.tsx @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2023 Shivam Awasthi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import {Button, Card, Col, Form, Modal, Row} from 'react-bootstrap'; +import React, {ChangeEvent, FormEvent, useCallback, useState} from 'react'; +import {RelationshipTypeDataT, RelationshipTypeEditorPropsT, defaultRelationshipTypeData, entityTypeOptions, renderSelectedParent} from './typeUtils'; +import {faPencilAlt, faPlus, faTimes} from '@fortawesome/free-solid-svg-icons'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import ReactSelect from 'react-select'; + + +function RelationshipTypeEditor({relationshipTypeData, parentTypes}: RelationshipTypeEditorPropsT) { + const [formData, setFormData] = useState(relationshipTypeData); + + // State for the ParentType modal + const [showModal, setShowModal] = useState(false); + const [selectedParentType, setSelectedParentType] = useState(formData.parentId); + const [childOrder, setChildOrder] = useState(formData.childOrder); + + // Callback function for opening the modal + const handleAddParent = useCallback(() => { + setShowModal(true); + }, []); + + // Callback function for closing the modal, the state of the modal should alse be reset + const handleModalClose = useCallback(() => { + setSelectedParentType(null); + setChildOrder(0); + setShowModal(false); + }, []); + + // Function to handle parent type selection in ParentType modal + const handleParentTypeChange = useCallback((selectedOption) => { + if (selectedOption) { + setSelectedParentType(selectedOption.id); + } + else { + setSelectedParentType(null); + } + }, [selectedParentType]); + + // Function to handle child order input in ParentType modal + const handleChildOrderChange = useCallback((event: ChangeEvent) => { + const value = parseInt(event.target.value, 10); + setChildOrder(isNaN(value) ? 0 : value); + }, [formData, childOrder]); + + // Function to handle parent removal using useCallback + const handleRemoveParent = useCallback(() => { + setFormData((prevFormData) => ({ + ...prevFormData, + childOrder: 0, parentId: null + })); + setChildOrder(0); + setSelectedParentType(null); + }, [formData]); + + const handleEditParent = useCallback(() => { + setShowModal(true); + }, []); + + // Function to handle parent type and child order edit submission + const handleModalSubmit = useCallback(() => { + if (selectedParentType !== null) { + setFormData({ + ...formData, + childOrder, parentId: selectedParentType + }); + setShowModal(false); + } + }, [formData, childOrder, selectedParentType]); + + const handleInputChange = useCallback((event: ChangeEvent) => { + const {name, value} = event.target; + setFormData((prevFormData) => ({ + ...prevFormData, + [name]: value + })); + }, [formData]); + + const handleDeprecatedChange = useCallback((event: ChangeEvent) => { + const {value} = event.target; + setFormData((prevFormData) => ({ + ...prevFormData, + deprecated: value === 'true' + })); + }, [formData]); + + const getEntityTypeLabel = useCallback(option => option.name, []); + + const getEntityTypeValue = useCallback(option => option.name, []); + + const getParentTypeValue = useCallback(option => option.id, []); + + // Callback function to format the option label to include both forwardStatement and reverseStatement + const formatParentTypeOptionLabel = useCallback(option => ( +
+
{option.sourceEntityType} {option.linkPhrase} {option.targetEntityType}
+
{option.targetEntityType} {option.reverseLinkPhrase} {option.sourceEntityType}
+
+ ), []); + + const handleSourceEntityTypeChange = useCallback((selectedOption) => { + if (selectedOption) { + setFormData({...formData, sourceEntityType: selectedOption.name}); + } + else { + setFormData({...formData, sourceEntityType: null}); + } + }, [formData]); + + const handleTargetEntityTypeChange = useCallback((selectedOption) => { + if (selectedOption) { + setFormData({...formData, targetEntityType: selectedOption.name}); + } + else { + setFormData({...formData, targetEntityType: null}); + } + }, [formData]); + + const handleSubmit = useCallback((event: FormEvent) => { + event.preventDefault(); + // console.log(formData); + }, [formData]); + + const lgCol = {offset: 3, span: 6}; + + return ( +
+ + + Add Relationship Type + + + + + + Label + + + + + + + + Description + + + + + + + + Link Phrase + + + + + + + + Reverse Link Phrase + + + + + + + + Source Entity Type + option.name === relationshipTypeData.sourceEntityType)} + getOptionLabel={getEntityTypeLabel} + getOptionValue={getEntityTypeValue} + instanceId="sourceEntityType" + options={entityTypeOptions} + placeholder="Select Source Entity Type" + onChange={handleSourceEntityTypeChange} + /> + + + + + + + Target Entity Type + option.name === relationshipTypeData.targetEntityType)} + getOptionLabel={getEntityTypeLabel} + getOptionValue={getEntityTypeValue} + instanceId="targetEntityType" + options={entityTypeOptions} + placeholder="Select Target Entity Type" + onChange={handleTargetEntityTypeChange} + /> + + + + + + + Parent Relationship + {!formData.parentId ? ( + + + + + + ) : ( + + + {formData.parentId && renderSelectedParent(formData.parentId, formData.childOrder, parentTypes)} +
+ + +
+ +
+ )} +
+ +
+ + + + + Deprecated: + + + + + + + + + + + + + {/* Modal for selecting parent type */} + + + {formData.parentId ? 'Edit Parent' : 'Add a Parent'} + + + + Parent Type: + option.id === selectedParentType)} + onChange={handleParentTypeChange} + /> + + + Child Order: + + + + + + + + +
+
+
+ ); +} + +RelationshipTypeEditor.defaultProps = { + relationshipTypeData: defaultRelationshipTypeData +}; + +export default RelationshipTypeEditor; diff --git a/src/client/components/forms/type-editor/typeUtils.tsx b/src/client/components/forms/type-editor/typeUtils.tsx new file mode 100644 index 0000000000..812eae3507 --- /dev/null +++ b/src/client/components/forms/type-editor/typeUtils.tsx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 Shivam Awasthi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import type {EntityTypeString} from 'bookbrainz-data/lib/types/entity'; +import React from 'react'; + + +export interface RelationshipTypeDataT { + id?: number; + label: string; + description: string; + linkPhrase: string; + reverseLinkPhrase: string; + deprecated: boolean; + parentId: number | null; + childOrder: number; + sourceEntityType: EntityTypeString; + targetEntityType: EntityTypeString; +} + +export interface RelationshipTypeEditorPropsT { + relationshipTypeData: RelationshipTypeDataT; + parentTypes: RelationshipTypeDataT[]; + attributeTypes: any; + user: any; +} + +export const defaultRelationshipTypeData: RelationshipTypeDataT = { + childOrder: 0, + deprecated: false, + description: '', + label: '', + linkPhrase: '', + parentId: null, + reverseLinkPhrase: '', + sourceEntityType: null, + targetEntityType: null +}; + +export const entityTypeOptions = ['Author', 'Work', 'Series', 'Edition', 'Edition-Group', 'Publisher'].map((entity) => ({ + name: entity +})); + +export function renderSelectedParent(selectedParentID: number, childOrder: number, parentTypes: RelationshipTypeDataT[]) { + const parent = parentTypes.find(relationship => relationship.id === selectedParentID); + if (parent) { + return ( +
+
+ Forward Phrase: {parent.sourceEntityType} {parent.linkPhrase} {parent.targetEntityType} +
+
+ Reverse Phrase: {parent.targetEntityType} {parent.reverseLinkPhrase} {parent.sourceEntityType} +
+
Child Order: {childOrder}
+
+ ); + } + return null; +} diff --git a/src/client/controllers/type-editor/relationship-type.tsx b/src/client/controllers/type-editor/relationship-type.tsx new file mode 100644 index 0000000000..e04265a076 --- /dev/null +++ b/src/client/controllers/type-editor/relationship-type.tsx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 Shivam Awasthi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import {extractChildProps, extractLayoutProps} from '../../helpers/props'; +import {AppContainer} from 'react-hot-loader'; +import Layout from '../../containers/layout'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import RelationshipTypeEditor from '../../components/forms/type-editor/relationship-type'; + + +const propsTarget = document.getElementById('props'); +const props = propsTarget ? JSON.parse(propsTarget.innerHTML) : {}; + +ReactDOM.hydrate( + + + + + , + document.getElementById('target') +); + + +/* + * As we are not exporting a component, + * we cannot use the react-hot-loader module wrapper, + * but instead directly use webpack Hot Module Replacement API + */ + +if (module.hot) { + module.hot.accept(); +} + diff --git a/src/server/helpers/middleware.ts b/src/server/helpers/middleware.ts index 0349ec2bfe..3cc31ea731 100644 --- a/src/server/helpers/middleware.ts +++ b/src/server/helpers/middleware.ts @@ -71,6 +71,8 @@ export const loadSeriesOrderingTypes = makeLoader('SeriesOrderingType', 'seriesOrderingTypes'); export const loadRelationshipTypes = makeLoader('RelationshipType', 'relationshipTypes', null, ['attributeTypes']); +export const loadParentRelationshipTypes = + makeLoader('RelationshipType', 'parentTypes'); export const loadGenders = makeLoader('Gender', 'genders', (a, b) => a.id > b.id); diff --git a/src/server/routes.js b/src/server/routes.js index 999b1e1fa1..3974439375 100644 --- a/src/server/routes.js +++ b/src/server/routes.js @@ -31,6 +31,7 @@ import indexRouter from './routes/index'; import mergeRouter from './routes/merge'; import publisherRouter from './routes/entity/publisher'; import registerRouter from './routes/register'; +import relationshipTypeRouter from './routes/type-editor/relationship-type'; import reviewsRouter from './routes/reviews'; import revisionRouter from './routes/revision'; import revisionsRouter from './routes/revisions'; @@ -56,6 +57,7 @@ function initRootRoutes(app) { app.use('/external-service', externalServiceRouter); app.use('/admin-panel', adminPanelRouter); app.use('/admin-logs', adminLogsRouter); + app.use('/relationship-type', relationshipTypeRouter); } function initEditionGroupRoutes(app) { diff --git a/src/server/routes/type-editor/relationship-type.tsx b/src/server/routes/type-editor/relationship-type.tsx new file mode 100644 index 0000000000..18154f8a2e --- /dev/null +++ b/src/server/routes/type-editor/relationship-type.tsx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2023 Shivam Awasthi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import * as auth from '../../helpers/auth'; +import * as middleware from '../../helpers/middleware'; +import * as propHelpers from '../../../client/helpers/props'; +import {escapeProps, generateProps} from '../../helpers/props'; +import Layout from '../../../client/containers/layout'; +import React from 'react'; +import ReactDOMServer from 'react-dom/server'; +import RelationshipTypeEditor from '../../../client/components/forms/type-editor/relationship-type'; +import express from 'express'; +import target from '../../templates/target'; + + +const router = express.Router(); + +router.get('/create', auth.isAuthenticated, middleware.loadParentRelationshipTypes, (req, res) => { + const {parentTypes} = res.locals; + const props = generateProps(req, res, { + parentTypes + }); + const script = '/js/relationship-type/create.js'; + const markup = ReactDOMServer.renderToString( + + + + ); + res.send(target({ + markup, + props: escapeProps(props), + script + })); +}); + +export default router; diff --git a/src/server/routes/type-editor/relationship-types.tsx b/src/server/routes/type-editor/relationship-types.tsx new file mode 100644 index 0000000000..aa6e5a2303 --- /dev/null +++ b/src/server/routes/type-editor/relationship-types.tsx @@ -0,0 +1,90 @@ +// /* +// * Copyright (C) 2023 Shivam Awasthi +// * +// * This program is free software; you can redistribute it and/or modify +// * it under the terms of the GNU General Public License as published by +// * the Free Software Foundation; either version 2 of the License, or +// * (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU General Public License for more details. +// * +// * You should have received a copy of the GNU General Public License along +// * with this program; if not, write to the Free Software Foundation, Inc., +// * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// */ + +// import * as propHelpers from '../../../client/helpers/props'; +// import {escapeProps, generateProps} from '../../helpers/props'; +// import {getIntFromQueryParams, parseQuery} from '../../helpers/utils'; +// import Layout from '../../../client/containers/layout'; +// import React from 'react'; +// import ReactDOMServer from 'react-dom/server'; +// import express from 'express'; +// import {getNextEnabledAndResultsArray} from '../../../common/helpers/utils'; +// import {getOrderedAdminLogs} from '../../helpers/adminLogs'; +// import target from '../../templates/target'; +// import RelationshipTypesPage from '../../../client/components/pages/relationshipTypes'; + + +// const router = express.Router(); + +// router.get('/', async (req, res, next) => { +// const {orm} = req.app.locals; +// const query = parseQuery(req.url); +// const size = getIntFromQueryParams(query, 'size', 20); +// const from = getIntFromQueryParams(query, 'from'); + +// function render(results, nextEnabled) { +// const props = generateProps(req, res, { +// from, +// nextEnabled, +// results, +// size +// }); + +// const markup = ReactDOMServer.renderToString( +// +// +// +// ); + +// res.send(target({ +// markup, +// props: escapeProps(props), +// script: '/js/relationshipTypes.js', +// title: 'Relationship Types' +// })); +// } + +// try { +// // fetch 1 more relationship type than required to check nextEnabled +// const orderedRelationshipTypes = await getOrderedRelationshipTypes(from, size + 1, orm); +// const {newResultsArray, nextEnabled} = getNextEnabledAndResultsArray(orderedRelationshipTypes, size); +// return render(newResultsArray, nextEnabled); +// } +// catch (err) { +// return next(err); +// } +// }); + + +// // eslint-disable-next-line consistent-return +// router.get('/admin-logs', async (req, res, next) => { +// const {orm} = req.app.locals; +// const query = parseQuery(req.url); +// const size = getIntFromQueryParams(query, 'size', 20); +// const from = getIntFromQueryParams(query, 'from'); + +// try { +// const orderedLogs = await getOrderedAdminLogs(from, size, orm); +// res.json(orderedLogs); +// } +// catch (err) { +// return next(err); +// } +// }); + +// export default router; diff --git a/webpack.client.js b/webpack.client.js index 9bcfc4e5ac..3a19173e05 100644 --- a/webpack.client.js +++ b/webpack.client.js @@ -41,7 +41,8 @@ const clientConfig = { 'entity-editor': ['./entity-editor/controller.js'], 'unified-form':['./unified-form/controller.js'], 'entity-merge': ['./entity-editor/entity-merge.tsx'], - style: './stylesheets/style.scss' + style: './stylesheets/style.scss', + 'relationship-type/create': ['./controllers/type-editor/relationship-type.tsx'] }, externals: { moment: 'moment' From a675445f37f07bb3c79f93ad7bc5610c43ec0028 Mon Sep 17 00:00:00 2001 From: the-good-boy Date: Mon, 31 Jul 2023 20:35:39 +0530 Subject: [PATCH 02/24] feat(RelationshipTypes): Add a Relationship Types page --- .../pages/parts/relationship-types-tree.tsx | 104 ++++++++++++++++++ .../components/pages/relationshipTypes.tsx | 43 ++++++++ src/client/controllers/relationshipTypes.tsx | 49 +++++++++ src/server/helpers/typeRouteUtils.ts | 104 ++++++++++++++++++ src/server/routes.js | 2 + src/server/routes/relationship-types.tsx | 49 +++++++++ .../routes/type-editor/relationship-types.tsx | 90 --------------- webpack.client.js | 1 + 8 files changed, 352 insertions(+), 90 deletions(-) create mode 100644 src/client/components/pages/parts/relationship-types-tree.tsx create mode 100644 src/client/components/pages/relationshipTypes.tsx create mode 100644 src/client/controllers/relationshipTypes.tsx create mode 100644 src/server/helpers/typeRouteUtils.ts create mode 100644 src/server/routes/relationship-types.tsx delete mode 100644 src/server/routes/type-editor/relationship-types.tsx diff --git a/src/client/components/pages/parts/relationship-types-tree.tsx b/src/client/components/pages/parts/relationship-types-tree.tsx new file mode 100644 index 0000000000..db0a646422 --- /dev/null +++ b/src/client/components/pages/parts/relationship-types-tree.tsx @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2023 Shivam Awasthi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import React, {useCallback, useState} from 'react'; +import {Button} from 'react-bootstrap'; +import {RelationshipTypeDataT} from '../../forms/type-editor/typeUtils'; + + +type RelationshipTypeTreePropsT = { + relationshipTypes: RelationshipTypeDataT[], + parentId?: number | null, + indentLevel?: number +}; + +function RelationshipTypeTree({relationshipTypes, parentId, indentLevel}: RelationshipTypeTreePropsT) { + const [expandedRelationshipTypeIds, setExpandedRelationshipTypeIds] = useState([]); + + function toggleExpand(relTypeId) { + setExpandedRelationshipTypeIds((prevExpandedIds) => { + if (prevExpandedIds.includes(relTypeId)) { + return prevExpandedIds.filter((id) => id !== relTypeId); + } + return [...prevExpandedIds, relTypeId]; + }); + } + + const handleClick = useCallback((event) => { + const relationshipTypeId = parseInt(event.target.value, 10); + toggleExpand(relationshipTypeId); + }, [expandedRelationshipTypeIds]); + + const filteredRelationshipTypes = relationshipTypes.filter((relType) => relType.parentId === parentId); + + return ( +
    + {filteredRelationshipTypes.map(relType => { + let relOuterClass = `margin-left-d${indentLevel * 20}`; + if (relType.deprecated) { + relOuterClass = `margin-left-d${indentLevel * 20} text-muted`; + } + return ( +
  • + + {relType.label} + + + {expandedRelationshipTypeIds.includes(relType.id) && ( +
    +
    Forward link phrase: {relType.linkPhrase}
    +
    Reverse link phrase: {relType.reverseLinkPhrase}
    +
    Source Entity Type: {relType.sourceEntityType}
    +
    Target Entity Type: {relType.targetEntityType}
    +
    Description: {relType.description}
    +
    Child Order: {relType.childOrder}
    +
    Deprecated: {relType.deprecated ? 'Yes' : 'No'}
    +
    + +
    +
    + )} + +
  • + ); + })} +
+ ); +} + +RelationshipTypeTree.defaultProps = { + indentLevel: 0, + parentId: null +}; + +export default RelationshipTypeTree; diff --git a/src/client/components/pages/relationshipTypes.tsx b/src/client/components/pages/relationshipTypes.tsx new file mode 100644 index 0000000000..d589261587 --- /dev/null +++ b/src/client/components/pages/relationshipTypes.tsx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 Shivam Awasthi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import {Card} from 'react-bootstrap'; +import React from 'react'; +import {RelationshipTypeDataT} from '../forms/type-editor/typeUtils'; +import RelationshipTypeTree from './parts/relationship-types-tree'; + + +type Props = { + relationshipTypes: RelationshipTypeDataT[] +}; + +function RelationshipTypesPage({relationshipTypes}: Props) { + return ( + + + Relationship Types + + + + + + ); +} + +export default RelationshipTypesPage; diff --git a/src/client/controllers/relationshipTypes.tsx b/src/client/controllers/relationshipTypes.tsx new file mode 100644 index 0000000000..fd01373c9d --- /dev/null +++ b/src/client/controllers/relationshipTypes.tsx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 Shivam Awasthi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import {AppContainer} from 'react-hot-loader'; +import Layout from '../containers/layout'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import RelationshipTypesPage from '../components/pages/relationshipTypes'; +import {extractLayoutProps} from '../helpers/props'; + + +const propsTarget = document.getElementById('props'); +const props = propsTarget ? JSON.parse(propsTarget.innerHTML) : {}; +const markup = ( + + + + + +); + +ReactDOM.hydrate(markup, document.getElementById('target')); + +/* + * As we are not exporting a component, + * we cannot use the react-hot-loader module wrapper, + * but instead directly use webpack Hot Module Replacement API + */ + +if (module.hot) { + module.hot.accept(); +} diff --git a/src/server/helpers/typeRouteUtils.ts b/src/server/helpers/typeRouteUtils.ts new file mode 100644 index 0000000000..42d77f9c8e --- /dev/null +++ b/src/server/helpers/typeRouteUtils.ts @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2023 Shivam Awasthi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +function getChangedAttributeTypes(oldAttributes, newAttributes) { + const attributesCommon = oldAttributes.filter(value => newAttributes.includes(value)); + const attributesToBeRemoved = oldAttributes.filter(value => !attributesCommon.includes(value)); + const attributesToBeAdded = newAttributes.filter(value => !attributesCommon.includes(value)); + + return {attributesToBeAdded, attributesToBeRemoved}; +} + +/** + * A handler for create or edit actions on relationship types. + * @param {object} req - request object + * @param {object} res - response object + * @param {object} next - next object + * @returns {promise} res.send promise + * @description + * Creates a new reationship type or updates an existing relationship type + */ +export async function relationshipTypeCreateOrEditHandler(req, res, next) { + try { + const {RelationshipType, RelationshipTypeAttributeType, bookshelf} = req.app.locals.orm; + const trx = await bookshelf.transaction(); + let newRelationshipType; + let method; + if (!req.params.id) { + newRelationshipType = await RelationshipType.forge(); + method = 'insert'; + } + else { + newRelationshipType = await RelationshipType.forge({id: parseInt(req.params.id, 10)}).fetch({ + require: true + }); + method = 'update'; + } + const { + attributeTypes, + childOrder, + deprecated, + description, + label, + linkPhrase, + oldAttributeTypes, + parentId, + reverseLinkPhrase, + sourceEntityType, + targetEntityType + } = req.body; + + + newRelationshipType.set('description', description); + newRelationshipType.set('label', label); + newRelationshipType.set('deprecated', deprecated); + newRelationshipType.set('linkPhrase', linkPhrase); + newRelationshipType.set('reverseLinkPhrase', reverseLinkPhrase); + newRelationshipType.set('childOrder', childOrder); + newRelationshipType.set('parentId', parentId); + newRelationshipType.set('sourceEntityType', sourceEntityType); + newRelationshipType.set('targetEntityType', targetEntityType); + + const relationshipType = await newRelationshipType.save(null, {method}, {transacting: trx}); + // Attributes + const {attributesToBeAdded, attributesToBeRemoved} = getChangedAttributeTypes(oldAttributeTypes, attributeTypes); + + attributesToBeRemoved.map(async attributeID => { + await new RelationshipTypeAttributeType() + .query((qb) => { + qb.where('relationship_type', newRelationshipType.id); + qb.where('attribute_type', attributeID); + }).destroy(); + }); + + attributesToBeAdded.map(async attributeID => { + const newRelTypeAttrType = await new RelationshipTypeAttributeType(); + newRelTypeAttrType.set('relationshipType', relationshipType.id); + newRelTypeAttrType.set('attributeType', attributeID); + + await newRelTypeAttrType.save(null, {method: 'insert'}, {transacting: trx}); + }); + + await trx.commit(); + + return res.send(relationshipType.toJSON()); + } + catch (err) { + return next(err); + } +} diff --git a/src/server/routes.js b/src/server/routes.js index 3974439375..a4c3d66981 100644 --- a/src/server/routes.js +++ b/src/server/routes.js @@ -32,6 +32,7 @@ import mergeRouter from './routes/merge'; import publisherRouter from './routes/entity/publisher'; import registerRouter from './routes/register'; import relationshipTypeRouter from './routes/type-editor/relationship-type'; +import relationshipTypesRouter from './routes/relationship-types'; import reviewsRouter from './routes/reviews'; import revisionRouter from './routes/revision'; import revisionsRouter from './routes/revisions'; @@ -58,6 +59,7 @@ function initRootRoutes(app) { app.use('/admin-panel', adminPanelRouter); app.use('/admin-logs', adminLogsRouter); app.use('/relationship-type', relationshipTypeRouter); + app.use('/relationship-types', relationshipTypesRouter); } function initEditionGroupRoutes(app) { diff --git a/src/server/routes/relationship-types.tsx b/src/server/routes/relationship-types.tsx new file mode 100644 index 0000000000..01c4aaff3d --- /dev/null +++ b/src/server/routes/relationship-types.tsx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 Shivam Awasthi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import * as middleware from '../helpers/middleware'; +import * as propHelpers from '../../client/helpers/props'; +import {escapeProps, generateProps} from '../helpers/props'; +import Layout from '../../client/containers/layout'; +import React from 'react'; +import ReactDOMServer from 'react-dom/server'; +import RelationshipTypesPage from '../../client/components/pages/relationshipTypes'; +import express from 'express'; +import target from '../templates/target'; + + +const router = express.Router(); + +router.get('/', middleware.loadRelationshipTypes, (req, res) => { + const {relationshipTypes} = res.locals; + const props = generateProps(req, res, { + relationshipTypes + }); + const markup = ReactDOMServer.renderToString( + + + + ); + res.send(target({ + markup, + props: escapeProps(props), + script: '/js/relationshipTypes.js', + title: 'Relationship Types' + })); +}); + +export default router; diff --git a/src/server/routes/type-editor/relationship-types.tsx b/src/server/routes/type-editor/relationship-types.tsx deleted file mode 100644 index aa6e5a2303..0000000000 --- a/src/server/routes/type-editor/relationship-types.tsx +++ /dev/null @@ -1,90 +0,0 @@ -// /* -// * Copyright (C) 2023 Shivam Awasthi -// * -// * This program is free software; you can redistribute it and/or modify -// * it under the terms of the GNU General Public License as published by -// * the Free Software Foundation; either version 2 of the License, or -// * (at your option) any later version. -// * -// * This program is distributed in the hope that it will be useful, -// * but WITHOUT ANY WARRANTY; without even the implied warranty of -// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// * GNU General Public License for more details. -// * -// * You should have received a copy of the GNU General Public License along -// * with this program; if not, write to the Free Software Foundation, Inc., -// * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -// */ - -// import * as propHelpers from '../../../client/helpers/props'; -// import {escapeProps, generateProps} from '../../helpers/props'; -// import {getIntFromQueryParams, parseQuery} from '../../helpers/utils'; -// import Layout from '../../../client/containers/layout'; -// import React from 'react'; -// import ReactDOMServer from 'react-dom/server'; -// import express from 'express'; -// import {getNextEnabledAndResultsArray} from '../../../common/helpers/utils'; -// import {getOrderedAdminLogs} from '../../helpers/adminLogs'; -// import target from '../../templates/target'; -// import RelationshipTypesPage from '../../../client/components/pages/relationshipTypes'; - - -// const router = express.Router(); - -// router.get('/', async (req, res, next) => { -// const {orm} = req.app.locals; -// const query = parseQuery(req.url); -// const size = getIntFromQueryParams(query, 'size', 20); -// const from = getIntFromQueryParams(query, 'from'); - -// function render(results, nextEnabled) { -// const props = generateProps(req, res, { -// from, -// nextEnabled, -// results, -// size -// }); - -// const markup = ReactDOMServer.renderToString( -// -// -// -// ); - -// res.send(target({ -// markup, -// props: escapeProps(props), -// script: '/js/relationshipTypes.js', -// title: 'Relationship Types' -// })); -// } - -// try { -// // fetch 1 more relationship type than required to check nextEnabled -// const orderedRelationshipTypes = await getOrderedRelationshipTypes(from, size + 1, orm); -// const {newResultsArray, nextEnabled} = getNextEnabledAndResultsArray(orderedRelationshipTypes, size); -// return render(newResultsArray, nextEnabled); -// } -// catch (err) { -// return next(err); -// } -// }); - - -// // eslint-disable-next-line consistent-return -// router.get('/admin-logs', async (req, res, next) => { -// const {orm} = req.app.locals; -// const query = parseQuery(req.url); -// const size = getIntFromQueryParams(query, 'size', 20); -// const from = getIntFromQueryParams(query, 'from'); - -// try { -// const orderedLogs = await getOrderedAdminLogs(from, size, orm); -// res.json(orderedLogs); -// } -// catch (err) { -// return next(err); -// } -// }); - -// export default router; diff --git a/webpack.client.js b/webpack.client.js index 3a19173e05..8cef258b61 100644 --- a/webpack.client.js +++ b/webpack.client.js @@ -30,6 +30,7 @@ const clientConfig = { externalService: ['./controllers/externalService.js'], index: ['./controllers/index.js'], registrationDetails: ['./controllers/registrationDetails.js'], + relationshipTypes: ['./controllers/relationshipTypes.tsx'], revision: ['./controllers/revision.js'], revisions: ['./controllers/revisions.js'], search: ['./controllers/search.js'], From 1c49e91b565683c12067679facd34cdccbe00244 Mon Sep 17 00:00:00 2001 From: the-good-boy Date: Mon, 31 Jul 2023 20:36:07 +0530 Subject: [PATCH 03/24] Privilege Dropdown improvements --- src/client/containers/layout.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/client/containers/layout.js b/src/client/containers/layout.js index 58386ca9d0..24da9048f7 100644 --- a/src/client/containers/layout.js +++ b/src/client/containers/layout.js @@ -23,7 +23,7 @@ import * as bootstrap from 'react-bootstrap'; import { - faChartLine, faGripVertical, faLink, faListUl, faPlus, faQuestionCircle, + faChartLine, faGripVertical, faLink, faListUl, faNewspaper, faPlus, faQuestionCircle, faSearch, faShieldHalved, faSignInAlt, faSignOutAlt, faTrophy, faUserCircle, faUserGear } from '@fortawesome/free-solid-svg-icons'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; @@ -149,6 +149,17 @@ class Layout extends React.Component { Admin Panel + + + Admin Logs + + + + Relationship Type Editor + + + Relationship Types + Date: Mon, 31 Jul 2023 20:37:48 +0530 Subject: [PATCH 04/24] feat(RelationshipTypeEditor): Add Attribute Types and other improvements --- .../forms/type-editor/relationship-type.tsx | 100 ++++++++++++++++-- .../forms/type-editor/typeUtils.tsx | 16 ++- src/server/helpers/middleware.ts | 10 ++ .../routes/type-editor/relationship-type.tsx | 49 ++++++++- 4 files changed, 159 insertions(+), 16 deletions(-) diff --git a/src/client/components/forms/type-editor/relationship-type.tsx b/src/client/components/forms/type-editor/relationship-type.tsx index 5897168688..ec131ca8ca 100644 --- a/src/client/components/forms/type-editor/relationship-type.tsx +++ b/src/client/components/forms/type-editor/relationship-type.tsx @@ -15,15 +15,17 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import {Button, Card, Col, Form, Modal, Row} from 'react-bootstrap'; +import {Alert, Button, Card, Col, Form, Modal, Row} from 'react-bootstrap'; import React, {ChangeEvent, FormEvent, useCallback, useState} from 'react'; import {RelationshipTypeDataT, RelationshipTypeEditorPropsT, defaultRelationshipTypeData, entityTypeOptions, renderSelectedParent} from './typeUtils'; import {faPencilAlt, faPlus, faTimes} from '@fortawesome/free-solid-svg-icons'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import ReactSelect from 'react-select'; +import classNames from 'classnames'; +import request from 'superagent'; -function RelationshipTypeEditor({relationshipTypeData, parentTypes}: RelationshipTypeEditorPropsT) { +function RelationshipTypeEditor({relationshipTypeData, parentTypes, attributeTypes}: RelationshipTypeEditorPropsT) { const [formData, setFormData] = useState(relationshipTypeData); // State for the ParentType modal @@ -31,6 +33,19 @@ function RelationshipTypeEditor({relationshipTypeData, parentTypes}: Relationshi const [selectedParentType, setSelectedParentType] = useState(formData.parentId); const [childOrder, setChildOrder] = useState(formData.childOrder); + const [showError, setShowError] = useState(false); + + const handleAttributeTypesChange = useCallback( + (selectedOptions) => { + const selectedHouseTypeIds = selectedOptions.map((option) => option.id); + setFormData((prevFormData) => ({ + ...prevFormData, + attributeTypes: selectedHouseTypeIds + })); + }, + [formData.attributeTypes] + ); + // Callback function for opening the modal const handleAddParent = useCallback(() => { setShowModal(true); @@ -59,7 +74,7 @@ function RelationshipTypeEditor({relationshipTypeData, parentTypes}: Relationshi setChildOrder(isNaN(value) ? 0 : value); }, [formData, childOrder]); - // Function to handle parent removal using useCallback + // Function to handle parent removal const handleRemoveParent = useCallback(() => { setFormData((prevFormData) => ({ ...prevFormData, @@ -76,10 +91,10 @@ function RelationshipTypeEditor({relationshipTypeData, parentTypes}: Relationshi // Function to handle parent type and child order edit submission const handleModalSubmit = useCallback(() => { if (selectedParentType !== null) { - setFormData({ - ...formData, + setFormData((prevFormData) => ({ + ...prevFormData, childOrder, parentId: selectedParentType - }); + })); setShowModal(false); } }, [formData, childOrder, selectedParentType]); @@ -106,6 +121,9 @@ function RelationshipTypeEditor({relationshipTypeData, parentTypes}: Relationshi const getParentTypeValue = useCallback(option => option.id, []); + const getAttributeTypeOptionValue = useCallback(option => option.id, []); + const getAttributeTypeOptionLabel = useCallback(option => option.name, []); + // Callback function to format the option label to include both forwardStatement and reverseStatement const formatParentTypeOptionLabel = useCallback(option => (
@@ -114,9 +132,14 @@ function RelationshipTypeEditor({relationshipTypeData, parentTypes}: Relationshi
), []); + const formatAttributeTypeOptionLabel = useCallback(option =>
{option.name}
, []); + const handleSourceEntityTypeChange = useCallback((selectedOption) => { if (selectedOption) { setFormData({...formData, sourceEntityType: selectedOption.name}); + if (formData.targetEntityType) { + setShowError(false); + } } else { setFormData({...formData, sourceEntityType: null}); @@ -126,16 +149,45 @@ function RelationshipTypeEditor({relationshipTypeData, parentTypes}: Relationshi const handleTargetEntityTypeChange = useCallback((selectedOption) => { if (selectedOption) { setFormData({...formData, targetEntityType: selectedOption.name}); + if (formData.sourceEntityType) { + setShowError(false); + } } else { setFormData({...formData, targetEntityType: null}); } }, [formData]); - const handleSubmit = useCallback((event: FormEvent) => { - event.preventDefault(); - // console.log(formData); - }, [formData]); + const errorAlertClass = classNames('text-center', 'margin-top-1', {'d-none': !showError}); + + function isValid() { + return Boolean(formData.sourceEntityType && formData.targetEntityType); + } + + const handleSubmit = useCallback(async (event: FormEvent) => { + event.preventDefault(); + if (!isValid()) { + setShowError(true); + return; + } + + let submissionURL; + if (relationshipTypeData.id) { + submissionURL = `/relationship-type/${relationshipTypeData.id}/edit/handler`; + } + else { + submissionURL = '/relationship-type/create/handler'; + } + + try { + await request.post(submissionURL) + .send({oldAttributeTypes: relationshipTypeData.attributeTypes, ...formData}); + window.location.href = '/relationship-types'; + } + catch (err) { + throw new Error(err); + } + }, [formData, showError]); const lgCol = {offset: 3, span: 6}; @@ -304,6 +356,33 @@ function RelationshipTypeEditor({relationshipTypeData, parentTypes}: Relationshi + + + + Attribute Types: + formData.attributeTypes.includes(attributeType.id))} + onChange={handleAttributeTypesChange} + /> + + + + + + { + showError && +
+ Error: Incomplete form! Select Source and Target Parent Types. +
+ } + +
@@ -318,7 +397,6 @@ function RelationshipTypeEditor({relationshipTypeData, parentTypes}: Relationshi Parent Type: ({ +export const entityTypeOptions = ['Author', 'Work', 'Series', 'Edition', 'EditionGroup', 'Publisher'].map((entity) => ({ name: entity })); diff --git a/src/server/helpers/middleware.ts b/src/server/helpers/middleware.ts index 3cc31ea731..b918e3d8f3 100644 --- a/src/server/helpers/middleware.ts +++ b/src/server/helpers/middleware.ts @@ -73,6 +73,8 @@ export const loadRelationshipTypes = makeLoader('RelationshipType', 'relationshipTypes', null, ['attributeTypes']); export const loadParentRelationshipTypes = makeLoader('RelationshipType', 'parentTypes'); +export const loadRelationshipAttributeTypes = + makeLoader('RelationshipAttributeType', 'attributeTypes'); export const loadGenders = makeLoader('Gender', 'genders', (a, b) => a.id > b.id); @@ -242,6 +244,14 @@ export function checkValidRevisionId(req: $Request, res: $Response, next: NextFu return next(); } +export function checkValidRelationshipTypeId(req: $Request, res: $Response, next: NextFunction, id: string) { + const idToNumber = _.toNumber(id); + if (!_.isInteger(idToNumber) || (_.isInteger(idToNumber) && idToNumber <= 0)) { + return next(new error.BadRequestError(`Invalid Relationship Type id: ${req.params.id}`, req)); + } + return next(); +} + export async function redirectedBbid(req: $Request, res: $Response, next: NextFunction, bbid: string) { if (!commonUtils.isValidBBID(bbid)) { return next(new error.BadRequestError(`Invalid bbid: ${req.params.bbid}`, req)); diff --git a/src/server/routes/type-editor/relationship-type.tsx b/src/server/routes/type-editor/relationship-type.tsx index 18154f8a2e..30aa983081 100644 --- a/src/server/routes/type-editor/relationship-type.tsx +++ b/src/server/routes/type-editor/relationship-type.tsx @@ -17,6 +17,7 @@ */ import * as auth from '../../helpers/auth'; +import * as error from '../../../common/helpers/error'; import * as middleware from '../../helpers/middleware'; import * as propHelpers from '../../../client/helpers/props'; import {escapeProps, generateProps} from '../../helpers/props'; @@ -25,15 +26,16 @@ import React from 'react'; import ReactDOMServer from 'react-dom/server'; import RelationshipTypeEditor from '../../../client/components/forms/type-editor/relationship-type'; import express from 'express'; +import {relationshipTypeCreateOrEditHandler} from '../../helpers/typeRouteUtils'; import target from '../../templates/target'; const router = express.Router(); -router.get('/create', auth.isAuthenticated, middleware.loadParentRelationshipTypes, (req, res) => { - const {parentTypes} = res.locals; +router.get('/create', auth.isAuthenticated, middleware.loadParentRelationshipTypes, middleware.loadRelationshipAttributeTypes, (req, res) => { + const {parentTypes, attributeTypes} = res.locals; const props = generateProps(req, res, { - parentTypes + attributeTypes, parentTypes }); const script = '/js/relationship-type/create.js'; const markup = ReactDOMServer.renderToString( @@ -48,4 +50,45 @@ router.get('/create', auth.isAuthenticated, middleware.loadParentRelationshipTyp })); }); +router.post('/create/handler', auth.isAuthenticated, relationshipTypeCreateOrEditHandler); + +router.param( + 'id', + middleware.checkValidRelationshipTypeId +); + +router.get('/:id/edit', auth.isAuthenticated, middleware.loadParentRelationshipTypes, middleware.loadRelationshipAttributeTypes, async (req, res) => { + const {RelationshipType} = req.app.locals.orm; + const {parentTypes, attributeTypes} = res.locals; + const relationshipType = await new RelationshipType({id: req.params.id}) + .fetch({withRelated: ['attributeTypes']}) + .catch(RelationshipType.NotFoundError, () => { + throw new error.NotFoundError(`RelationshipType with id ${req.params.id} not found`, req); + }); + const relationshipTypeData = relationshipType.toJSON(); + const attributeTypesIds = relationshipTypeData.attributeTypes.map(attributeTypeData => attributeTypeData.id); + + relationshipTypeData.attributeTypes = attributeTypesIds; + + const props = generateProps(req, res, { + attributeTypes, parentTypes, relationshipTypeData + }); + const script = '/js/relationship-type/create.js'; + const markup = ReactDOMServer.renderToString( + + + + ); + res.send(target({ + markup, + props: escapeProps(props), + script + })); +}); + +router.post('/:id/edit/handler', auth.isAuthenticated, relationshipTypeCreateOrEditHandler); + export default router; From cb5dec372acd7cb9ac69ca74e1c249526ccebf8c Mon Sep 17 00:00:00 2001 From: the-good-boy Date: Mon, 31 Jul 2023 20:40:02 +0530 Subject: [PATCH 05/24] fix(RelationshipTypeEditor): Uniformize Label names of fields --- .../components/forms/type-editor/relationship-type.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/components/forms/type-editor/relationship-type.tsx b/src/client/components/forms/type-editor/relationship-type.tsx index ec131ca8ca..e91ee0ece9 100644 --- a/src/client/components/forms/type-editor/relationship-type.tsx +++ b/src/client/components/forms/type-editor/relationship-type.tsx @@ -342,7 +342,7 @@ function RelationshipTypeEditor({relationshipTypeData, parentTypes, attributeTyp - Deprecated: + Deprecated - Attribute Types: + Attribute Types - Child Order: + Child Order Date: Mon, 31 Jul 2023 21:16:35 +0530 Subject: [PATCH 06/24] fix(AdminSystem): Add authorization to all the new routes --- src/client/components/footer.js | 11 +-- src/client/containers/layout.js | 68 ++++++++----- src/common/helpers/privileges-utils.ts | 11 +++ src/server/routes/adminLogs.tsx | 9 +- src/server/routes/relationship-types.tsx | 6 +- .../routes/type-editor/relationship-type.tsx | 97 ++++++++++--------- 6 files changed, 118 insertions(+), 84 deletions(-) diff --git a/src/client/components/footer.js b/src/client/components/footer.js index dbebc79fac..a5bbefc83a 100644 --- a/src/client/components/footer.js +++ b/src/client/components/footer.js @@ -60,8 +60,8 @@ function Footer(props) { - - Admin logs + + Privacy & Terms @@ -79,13 +79,6 @@ function Footer(props) { - - - - Privacy & Terms - - - diff --git a/src/client/containers/layout.js b/src/client/containers/layout.js index 24da9048f7..45fee3a683 100644 --- a/src/client/containers/layout.js +++ b/src/client/containers/layout.js @@ -5,6 +5,7 @@ * 2016 Sean Burke * 2016 Ohm Patel * 2015 Leo Verto + * 2023 Shivam Awasthi * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,6 +23,7 @@ */ import * as bootstrap from 'react-bootstrap'; +import {PrivilegeType, checkPrivilege} from '../../common/helpers/privileges-utils'; import { faChartLine, faGripVertical, faLink, faListUl, faNewspaper, faPlus, faQuestionCircle, faSearch, faShieldHalved, faSignInAlt, faSignOutAlt, faTrophy, faUserCircle, faUserGear @@ -130,37 +132,53 @@ class Layout extends React.Component { ); + const showPrivilegeDropdown = user.privs > 1; + const adminOptions = ( + <> + + + Admin Panel + + + + Admin Logs + + + ); + + const relationshipTypeEditorOptions = ( + <> + + Relationship Type Editor + + + Relationship Types + + + ); + + const privilegeDropDown = ( + + {checkPrivilege(user.privs, PrivilegeType.ADMIN) && adminOptions} + {checkPrivilege(user.privs, PrivilegeType.RELATIONSHIP_TYPE_EDITOR) && relationshipTypeEditorOptions} + + ); + const disableSignUp = this.props.disableSignUp ? {disabled: true} : {}; return ( - + {this.renderDocsDropdown()} { user && user.id ? this.renderLoggedInDropdown() : this.renderGuestDropdown() diff --git a/src/client/controllers/index.js b/src/client/controllers/index.js index 0df230aa37..84cf739be2 100644 --- a/src/client/controllers/index.js +++ b/src/client/controllers/index.js @@ -25,6 +25,7 @@ import AboutPage from '../../client/components/pages/about'; import {AppContainer} from 'react-hot-loader'; import ContributePage from '../../client/components/pages/contribute'; import DevelopPage from '../../client/components/pages/develop'; +import FAQPage from '../components/pages/faq'; import HelpPage from '../../client/components/pages/help'; import Index from '../components/pages/index'; import Layout from '../containers/layout'; @@ -44,6 +45,7 @@ const pageMap = { About: AboutPage, Contribute: ContributePage, Develop: DevelopPage, + FAQs: FAQPage, Help: HelpPage, Index, Licensing: LicensingPage, diff --git a/src/server/routes/index.js b/src/server/routes/index.js index ba47b739e4..d62f767e69 100644 --- a/src/server/routes/index.js +++ b/src/server/routes/index.js @@ -23,6 +23,7 @@ import {escapeProps, generateProps} from '../helpers/props'; import AboutPage from '../../client/components/pages/about'; import ContributePage from '../../client/components/pages/contribute'; import DevelopPage from '../../client/components/pages/develop'; +import FAQPage from '../../client/components/pages/faq'; import HelpPage from '../../client/components/pages/help'; import Index from '../../client/components/pages/index'; import Layout from '../../client/containers/layout'; @@ -107,5 +108,6 @@ _createStaticRoute('/develop', 'Develop', DevelopPage); _createStaticRoute('/help', 'Help', HelpPage); _createStaticRoute('/licensing', 'Licensing', LicensingPage); _createStaticRoute('/privacy', 'Privacy', PrivacyPage); +_createStaticRoute('/faq', 'FAQs', FAQPage); export default router; From 6b38d032217b48f9c3f880724fec8c1ad3bff782 Mon Sep 17 00:00:00 2001 From: the-good-boy Date: Sat, 12 Aug 2023 01:09:45 +0530 Subject: [PATCH 13/24] fix relationship types routes and other misc improvements --- .../pages/parts/relationship-types-tree.tsx | 40 ++++++++++++------- .../pages/relationship-type-matrix.tsx | 6 ++- src/client/controllers/relationshipTypes.tsx | 19 +++++++-- src/server/helpers/typeRouteUtils.ts | 6 +-- src/server/routes/relationship-types.tsx | 5 ++- 5 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/client/components/pages/parts/relationship-types-tree.tsx b/src/client/components/pages/parts/relationship-types-tree.tsx index db0a646422..412789804a 100644 --- a/src/client/components/pages/parts/relationship-types-tree.tsx +++ b/src/client/components/pages/parts/relationship-types-tree.tsx @@ -18,6 +18,7 @@ import React, {useCallback, useState} from 'react'; import {Button} from 'react-bootstrap'; import {RelationshipTypeDataT} from '../../forms/type-editor/typeUtils'; +import {genEntityIconHTMLElement} from '../../../helpers/entity'; type RelationshipTypeTreePropsT = { @@ -41,39 +42,48 @@ function RelationshipTypeTree({relationshipTypes, parentId, indentLevel}: Relati const handleClick = useCallback((event) => { const relationshipTypeId = parseInt(event.target.value, 10); toggleExpand(relationshipTypeId); - }, [expandedRelationshipTypeIds]); + }, []); const filteredRelationshipTypes = relationshipTypes.filter((relType) => relType.parentId === parentId); return (
    {filteredRelationshipTypes.map(relType => { - let relOuterClass = `margin-left-d${indentLevel * 20}`; + let relOuterClass = `margin-left-d${indentLevel * 10}`; if (relType.deprecated) { - relOuterClass = `margin-left-d${indentLevel * 20} text-muted`; + relOuterClass = `margin-left-d${indentLevel * 10} text-muted`; } + const sourceIconElement = genEntityIconHTMLElement(relType.sourceEntityType); + const targetIconElement = genEntityIconHTMLElement(relType.targetEntityType); + const relInnerElementsClass = `margin-left-d${(indentLevel + 1) * 10} small`; return (
  • - - {relType.label} +

    + {relType.label}:  + {sourceIconElement}{relType.sourceEntityType}  + {relType.linkPhrase} {targetIconElement} {relType.targetEntityType} - +

    + {relType.description} +

    +

    {expandedRelationshipTypeIds.includes(relType.id) && ( -
    -
    Forward link phrase: {relType.linkPhrase}
    -
    Reverse link phrase: {relType.reverseLinkPhrase}
    -
    Source Entity Type: {relType.sourceEntityType}
    -
    Target Entity Type: {relType.targetEntityType}
    -
    Description: {relType.description}
    -
    Child Order: {relType.childOrder}
    -
    Deprecated: {relType.deprecated ? 'Yes' : 'No'}
    -
    +
    +
    Forward link phrase: {relType.linkPhrase}
    +
    Reverse link phrase: {relType.reverseLinkPhrase}
    +
    Source Entity Type: {relType.sourceEntityType}
    +
    Target Entity Type: {relType.targetEntityType}
    +
    Description: {relType.description}
    +
    Child Order: {relType.childOrder}
    +
    Deprecated: {relType.deprecated ? 'Yes' : 'No'}
    +