From 080f12128e487882655ef904a05eb43d8f184baf Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 26 Aug 2022 13:16:58 +0300 Subject: [PATCH 01/37] Added relationships tab on side panel --- app/react/ConnectionsList/actions/actions.js | 6 ++-- .../components/ConnectionsGroups.js | 28 +++++++++++-------- .../Documents/components/DocumentSidePanel.js | 18 ++++++------ app/react/Library/actions/libraryActions.js | 2 ++ .../Library/components/ViewMetadataPanel.js | 2 ++ app/react/Library/reducers/reducer.js | 1 + .../Viewer/components/ViewerComponent.js | 23 +++++++++++++-- 7 files changed, 57 insertions(+), 23 deletions(-) diff --git a/app/react/ConnectionsList/actions/actions.js b/app/react/ConnectionsList/actions/actions.js index ea65c7d7e2..d719b9152d 100644 --- a/app/react/ConnectionsList/actions/actions.js +++ b/app/react/ConnectionsList/actions/actions.js @@ -31,10 +31,12 @@ export function searchReferences() { }; } -export function connectionsChanged() { +export function connectionsChanged(sharedId) { return (dispatch, getState) => { const relationshipsList = getState().relationships.list; - const { sharedId } = relationshipsList; + if (!sharedId) { + sharedId = relationshipsList.sharedId; + } return referencesAPI .getGroupedByConnection(new RequestParams({ sharedId })) diff --git a/app/react/ConnectionsList/components/ConnectionsGroups.js b/app/react/ConnectionsList/components/ConnectionsGroups.js index b01063edeb..5023179d4b 100644 --- a/app/react/ConnectionsList/components/ConnectionsGroups.js +++ b/app/react/ConnectionsList/components/ConnectionsGroups.js @@ -20,17 +20,21 @@ class ConnectionsGroupsComponent extends Component { ); if (connectionsGroups.size) { - Results = ( -
-
-
    - {connectionsGroups.map(group => ( - - ))} -
+ if (this.props.sidePanelTrigger === 'library') { + Results =
Library triggered this side panel
; + } else { + Results = ( +
+
+
    + {connectionsGroups.map(group => ( + + ))} +
+
-
- ); + ); + } } return Results; @@ -39,11 +43,13 @@ class ConnectionsGroupsComponent extends Component { ConnectionsGroupsComponent.propTypes = { connectionsGroups: PropTypes.instanceOf(Immutable.List).isRequired, + sidePanelTrigger: PropTypes.string.isRequired, }; -function mapStateToProps({ relationships }) { +function mapStateToProps({ relationships, library }) { return { connectionsGroups: relationships.list.connectionsGroups, + sidePanelTrigger: library.sidepanel.trigger, }; } diff --git a/app/react/Documents/components/DocumentSidePanel.js b/app/react/Documents/components/DocumentSidePanel.js index 3a171f2437..633bde5cdb 100644 --- a/app/react/Documents/components/DocumentSidePanel.js +++ b/app/react/Documents/components/DocumentSidePanel.js @@ -27,6 +27,7 @@ import SearchText from './SearchText'; import ShowToc from './ShowToc'; import SnippetsTab from './SnippetsTab'; import helpers from '../helpers'; +import { getDocumentReferences } from 'app/Library/actions/libraryActions'; class DocumentSidePanel extends Component { constructor(props) { @@ -39,17 +40,16 @@ class DocumentSidePanel extends Component { this.toggleSharing = this.toggleSharing.bind(this); } - componentDidUpdate(prevProps) { + async componentDidUpdate(prevProps) { + const sharedId = this.props.doc.get('sharedId'); if ( this.props.doc.get('_id') && prevProps.doc.get('_id') !== this.props.doc.get('_id') && - this.props.getDocumentReferences + this.props.connectionsChanged && + getDocumentReferences ) { - this.props.getDocumentReferences( - this.props.doc.get('sharedId'), - this.props.file._id, - this.props.storeKey - ); + this.props.getDocumentReferences(sharedId, this.props.file._id, this.props.storeKey); + this.props.connectionsChanged(sharedId); } } @@ -486,6 +486,7 @@ DocumentSidePanel.defaultProps = { isTargetDoc: false, readOnly: false, getDocumentReferences: undefined, + connectionsChanged: undefined, tocFormComponent: () => false, EntityForm: () => false, raw: false, @@ -518,6 +519,7 @@ DocumentSidePanel.propTypes = { editToc: PropTypes.func, leaveEditMode: PropTypes.func, searchSnippets: PropTypes.func, + connectionsChanged: PropTypes.func, getDocumentReferences: PropTypes.func, removeFromToc: PropTypes.func, indentTocElement: PropTypes.func, @@ -550,7 +552,7 @@ const mapStateToProps = (state, ownProps) => { return { references, - excludeConnectionsTab: Boolean(ownProps.references), + excludeConnectionsTab: Boolean(state.relationships.list.connectionsGroups.length), connectionsGroups: state.relationships.list.connectionsGroups, relationships: ownProps.references, defaultLanguage, diff --git a/app/react/Library/actions/libraryActions.js b/app/react/Library/actions/libraryActions.js index a4aaa03e01..f0f6a2e076 100644 --- a/app/react/Library/actions/libraryActions.js +++ b/app/react/Library/actions/libraryActions.js @@ -32,6 +32,7 @@ function selectDocument(_doc) { if (showingSemanticSearch && !doc.semanticSearch) { dispatch(actions.set('library.sidepanel.tab', '')); } + dispatch(actions.set('library.sidepanel.trigger', 'library')); await dispatch(maybeSaveQuickLabels()); dispatch({ type: types.SELECT_DOCUMENT, doc }); dispatch(selectedDocumentsChanged()); @@ -378,6 +379,7 @@ function getDocumentReferences(sharedId, fileId, storeKey) { .get(new RequestParams({ sharedId, file: fileId, onlyTextReferences: true })) .then(references => { dispatch(actions.set(`${storeKey}.sidepanel.references`, references)); + dispatch(actions.set('relationships/list/sharedId', sharedId)); }); } diff --git a/app/react/Library/components/ViewMetadataPanel.js b/app/react/Library/components/ViewMetadataPanel.js index b0469bdb12..2c410918e1 100644 --- a/app/react/Library/components/ViewMetadataPanel.js +++ b/app/react/Library/components/ViewMetadataPanel.js @@ -12,6 +12,7 @@ import { wrapDispatch } from 'app/Multireducer'; import { entityDefaultDocument } from 'shared/entityDefaultDocument'; import modals from 'app/Modals'; +import * as connectionsActions from 'app/ConnectionsList/actions/actions'; import { getDocumentReferences, unselectAllDocuments, @@ -52,6 +53,7 @@ function mapDispatchToProps(dispatch, props) { { loadInReduxForm: actions.loadInReduxForm, getDocumentReferences, + connectionsChanged: connectionsActions.connectionsChanged, closePanel: unselectAllDocuments, resetForm: () => _dispatch => { _dispatch(formActions.setInitial(`${props.storeKey}.sidepanel.metadata`)); diff --git a/app/react/Library/reducers/reducer.js b/app/react/Library/reducers/reducer.js index 86fb422eec..e709c5d603 100644 --- a/app/react/Library/reducers/reducer.js +++ b/app/react/Library/reducers/reducer.js @@ -57,5 +57,6 @@ export default storeKey => fullText: [], }), tab: createReducer(`${storeKey}.sidepanel.tab`, ''), + trigger: createReducer(`${storeKey}.sidepanel.trigger`, ''), }), }); diff --git a/app/react/Viewer/components/ViewerComponent.js b/app/react/Viewer/components/ViewerComponent.js index 6980db376a..77371d4201 100644 --- a/app/react/Viewer/components/ViewerComponent.js +++ b/app/react/Viewer/components/ViewerComponent.js @@ -2,13 +2,19 @@ import { Map } from 'immutable'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import EntityView from 'app/Viewer/EntityView'; import Loader from 'app/components/Elements/Loader'; - +import { actions } from 'app/BasicReducer'; import PDFView from '../PDFView'; export class ViewerComponent extends Component { + constructor(props, context) { + super(props, context); + props.setSidepanelTrigger('entityViewer'); + } + render() { const { entity } = this.props; @@ -22,8 +28,12 @@ export class ViewerComponent extends Component { ViewerComponent.propTypes = { entity: PropTypes.instanceOf(Map).isRequired, + setSidepanelTrigger: PropTypes.func.isRequired, }; +const setSidepanelTrigger = name => dispatch => + dispatch(actions.set('library.sidepanel.trigger', name)); + const mapStateToProps = state => { const entity = state.documentViewer.doc.get('_id') ? state.documentViewer.doc @@ -34,4 +44,13 @@ const mapStateToProps = state => { }; }; -export default connect(mapStateToProps)(ViewerComponent); +function mapDispatchToProps(dispatch) { + return bindActionCreators( + { + setSidepanelTrigger, + }, + dispatch + ); +} + +export default connect(mapStateToProps, mapDispatchToProps)(ViewerComponent); From 1b37ba612c43b05baa4fba1184731d00d2423d61 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 26 Aug 2022 19:18:28 +0300 Subject: [PATCH 02/37] Added small UI --- .../ConnectionsList/components/ConnectionsGroups.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/react/ConnectionsList/components/ConnectionsGroups.js b/app/react/ConnectionsList/components/ConnectionsGroups.js index 5023179d4b..3d17155993 100644 --- a/app/react/ConnectionsList/components/ConnectionsGroups.js +++ b/app/react/ConnectionsList/components/ConnectionsGroups.js @@ -21,7 +21,15 @@ class ConnectionsGroupsComponent extends Component { if (connectionsGroups.size) { if (this.props.sidePanelTrigger === 'library') { - Results =
Library triggered this side panel
; + Results = ( +
+
+ {connectionsGroups.map(group => ( +
{group.get('connectionLabel')}
+ ))} +
+
+ ); } else { Results = (
From 396064a463b711ff7b39bce409a2449820242e80 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 1 Sep 2022 12:21:56 +0300 Subject: [PATCH 03/37] Updated tests --- .../components/ConnectionsGroups.js | 11 +- .../components/LibraryViewRelationships.tsx | 115 ++++++++++++++++++ .../specs/ConnectionsGroups.spec.js | 1 + .../specs/DocumentSidePanel.spec.js | 7 +- .../__snapshots__/libraryActions.spec.js.snap | 4 + .../actions/specs/libraryActions.spec.js | 1 + .../Viewer/components/ViewerComponent.js | 5 +- 7 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 app/react/ConnectionsList/components/LibraryViewRelationships.tsx diff --git a/app/react/ConnectionsList/components/ConnectionsGroups.js b/app/react/ConnectionsList/components/ConnectionsGroups.js index 3d17155993..b267bbd4ed 100644 --- a/app/react/ConnectionsList/components/ConnectionsGroups.js +++ b/app/react/ConnectionsList/components/ConnectionsGroups.js @@ -6,6 +6,7 @@ import { connect } from 'react-redux'; import { t } from 'app/I18N'; import ConnectionsGroup from './ConnectionsGroup'; +import { LibraryViewRelationships } from './LibraryViewRelationships'; class ConnectionsGroupsComponent extends Component { render() { @@ -21,15 +22,7 @@ class ConnectionsGroupsComponent extends Component { if (connectionsGroups.size) { if (this.props.sidePanelTrigger === 'library') { - Results = ( -
-
- {connectionsGroups.map(group => ( -
{group.get('connectionLabel')}
- ))} -
-
- ); + Results = ; } else { Results = (
diff --git a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx new file mode 100644 index 0000000000..c858f5c8d4 --- /dev/null +++ b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx @@ -0,0 +1,115 @@ +import React, { useEffect } from 'react'; +import { connect, ConnectedProps } from 'react-redux'; +import PropTypes from 'prop-types'; +import { Map, List } from 'immutable'; +import { bindActionCreators } from 'redux'; +import { Icon } from 'app/UI'; +import * as actions from '../../Relationships/actions/actions'; +import HubRelationshipMetadata from '../../Relationships/components/HubRelationshipMetadata'; + +function mapStateToProps(state: any) { + const { relationships, library } = state; + return { + parentEntity: library.ui.getIn(['selectedDocuments', 0]), + searchResults: relationships.list.searchResults, + search: relationships.list.sort, + hubs: relationships.hubs, + relationTypes: actions.selectRelationTypes(state), + }; +} + +function mapDispatchToProps(dispatch: any) { + return bindActionCreators( + { + parseResults: actions.parseResults, + }, + dispatch + ); +} + +const connector = connect(mapStateToProps, mapDispatchToProps); + +type ComponentProps = ConnectedProps; + +const createRightRelationshipGroups = (rightRelationships: any, relationTypes: any[]) => ( +
+ {rightRelationships.map((relationship: any, index: number) => ( + <> +
+ + {' '} + {(() => { + const relationshipTemplateId = relationship.get('template'); + const relationType = relationTypes.find(r => r._id === relationshipTemplateId); + if (relationType) { + return relationshipTemplateId ? ( + relationTypes.find(r => r._id === relationshipTemplateId).name + ) : ( + + ); + } + return null; + })()} + +
+ {(() => { + return relationship.get('relationships').map((rel: any, indexI: number) => ( +
+
+
+
+ {rel.getIn(['entityData', 'title'])} +
+
+ +
+
+ )); + })()} + + ))} +
+); + +const createLabelGroups = (hub: any, relationTypes: any[]) => { + const template = hub.getIn(['leftRelationship', 'template']); + return ( +
+ + {template &&
{relationTypes.find(r => r._id === template).name}
} +
+ {createRightRelationshipGroups(hub.get('rightRelationships'), relationTypes)} +
+ ); +}; + +const LibraryViewRelationshipsComp = ({ + hubs, + searchResults, + parentEntity, + parseResults, + relationTypes, +}: ComponentProps) => { + useEffect(() => { + if (parentEntity) { + parseResults(searchResults, parentEntity, false); + } + }, [searchResults, parentEntity]); + return ( +
+ {hubs.map((hub: any) => createLabelGroups(hub, relationTypes))} +
+ ); +}; + +LibraryViewRelationshipsComp.propTypes = { + parentEntity: PropTypes.instanceOf(Map), + hubs: PropTypes.instanceOf(List).isRequired, + searchResults: PropTypes.instanceOf(Map).isRequired, + parseResults: PropTypes.func.isRequired, + relationTypes: PropTypes.instanceOf(Array).isRequired, +}; + +const LibraryViewRelationships = connector(LibraryViewRelationshipsComp); + +export { LibraryViewRelationshipsComp, LibraryViewRelationships }; diff --git a/app/react/ConnectionsList/components/specs/ConnectionsGroups.spec.js b/app/react/ConnectionsList/components/specs/ConnectionsGroups.spec.js index 3469c4ee12..b771b2ab09 100644 --- a/app/react/ConnectionsList/components/specs/ConnectionsGroups.spec.js +++ b/app/react/ConnectionsList/components/specs/ConnectionsGroups.spec.js @@ -21,6 +21,7 @@ describe('ConnectionsGroups', () => { ], }, ]), + sidePanelTrigger: 'entityView', }; }); diff --git a/app/react/Documents/components/specs/DocumentSidePanel.spec.js b/app/react/Documents/components/specs/DocumentSidePanel.spec.js index bbda780244..2b8c604270 100644 --- a/app/react/Documents/components/specs/DocumentSidePanel.spec.js +++ b/app/react/Documents/components/specs/DocumentSidePanel.spec.js @@ -213,7 +213,7 @@ describe('DocumentSidePanel', () => { beforeEach(() => { state = { documentViewer: { targetDoc: Immutable.fromJS({ _id: null }) }, - relationships: { list: { connectionsGroups: 'connectionsGroups' } }, + relationships: { list: { connectionsGroups: ['connectionsGroups'] } }, relationTypes: Immutable.fromJS(['a', 'b']), settings: { collection: Immutable.fromJS({ languages }) }, library: { sidepanel: { metadata: {} } }, @@ -241,12 +241,13 @@ describe('DocumentSidePanel', () => { expect(mapStateToProps(state, ownProps).references).toBe( 'References selector used correctly' ); - expect(mapStateToProps(state, ownProps).excludeConnectionsTab).toBe(false); + expect(mapStateToProps(state, ownProps).excludeConnectionsTab).toBe(true); }); it('should map selected target references from viewer when no ownProps and targetDoc', () => { const ownProps = { storeKey: 'library' }; state.documentViewer.targetDoc = Immutable.fromJS({ _id: 'targetDocId' }); + state.relationships.list.connectionsGroups = []; expect(mapStateToProps(state, ownProps).references).toBe( 'Target references selector used correctly' ); @@ -255,7 +256,7 @@ describe('DocumentSidePanel', () => { it('should map connectionsGroups', () => { const ownProps = { storeKey: 'library' }; - expect(mapStateToProps(state, ownProps).connectionsGroups).toBe('connectionsGroups'); + expect(mapStateToProps(state, ownProps).connectionsGroups).toEqual(['connectionsGroups']); }); it('should map default language', () => { diff --git a/app/react/Library/actions/specs/__snapshots__/libraryActions.spec.js.snap b/app/react/Library/actions/specs/__snapshots__/libraryActions.spec.js.snap index c7e44fc732..fe0cae06db 100644 --- a/app/react/Library/actions/specs/__snapshots__/libraryActions.spec.js.snap +++ b/app/react/Library/actions/specs/__snapshots__/libraryActions.spec.js.snap @@ -6,6 +6,10 @@ Array [ "type": "library.sidepanel.tab/SET", "value": "", }, + Object { + "type": "library.sidepanel.trigger/SET", + "value": "library", + }, Object { "doc": Object { "sharedId": "doc", diff --git a/app/react/Library/actions/specs/libraryActions.spec.js b/app/react/Library/actions/specs/libraryActions.spec.js index c5139c9299..935975334a 100644 --- a/app/react/Library/actions/specs/libraryActions.spec.js +++ b/app/react/Library/actions/specs/libraryActions.spec.js @@ -463,6 +463,7 @@ describe('libraryActions', () => { const expectedActions = [ { type: 'library.sidepanel.references/SET', value: 'referencesResponse' }, + { type: 'relationships/list/sharedId/SET', value: 'id' }, ]; const store = mockStore({ locale: 'es' }); diff --git a/app/react/Viewer/components/ViewerComponent.js b/app/react/Viewer/components/ViewerComponent.js index 77371d4201..9aeab6e923 100644 --- a/app/react/Viewer/components/ViewerComponent.js +++ b/app/react/Viewer/components/ViewerComponent.js @@ -12,7 +12,7 @@ import PDFView from '../PDFView'; export class ViewerComponent extends Component { constructor(props, context) { super(props, context); - props.setSidepanelTrigger('entityViewer'); + props.setSidepanelTrigger(); } render() { @@ -31,8 +31,7 @@ ViewerComponent.propTypes = { setSidepanelTrigger: PropTypes.func.isRequired, }; -const setSidepanelTrigger = name => dispatch => - dispatch(actions.set('library.sidepanel.trigger', name)); +const setSidepanelTrigger = () => actions.set('library.sidepanel.trigger', 'entityViewer'); const mapStateToProps = state => { const entity = state.documentViewer.doc.get('_id') From 32a2effd00ee58892177f0f1727f95baa56a30bd Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 26 Aug 2022 13:16:58 +0300 Subject: [PATCH 04/37] Added relationships tab on side panel --- app/react/ConnectionsList/actions/actions.js | 6 ++-- .../components/ConnectionsGroups.js | 28 +++++++++++-------- .../Documents/components/DocumentSidePanel.js | 18 ++++++------ app/react/Library/actions/libraryActions.js | 2 ++ .../Library/components/ViewMetadataPanel.js | 2 ++ app/react/Library/reducers/reducer.js | 1 + .../Viewer/components/ViewerComponent.js | 23 +++++++++++++-- 7 files changed, 57 insertions(+), 23 deletions(-) diff --git a/app/react/ConnectionsList/actions/actions.js b/app/react/ConnectionsList/actions/actions.js index ea65c7d7e2..d719b9152d 100644 --- a/app/react/ConnectionsList/actions/actions.js +++ b/app/react/ConnectionsList/actions/actions.js @@ -31,10 +31,12 @@ export function searchReferences() { }; } -export function connectionsChanged() { +export function connectionsChanged(sharedId) { return (dispatch, getState) => { const relationshipsList = getState().relationships.list; - const { sharedId } = relationshipsList; + if (!sharedId) { + sharedId = relationshipsList.sharedId; + } return referencesAPI .getGroupedByConnection(new RequestParams({ sharedId })) diff --git a/app/react/ConnectionsList/components/ConnectionsGroups.js b/app/react/ConnectionsList/components/ConnectionsGroups.js index b01063edeb..5023179d4b 100644 --- a/app/react/ConnectionsList/components/ConnectionsGroups.js +++ b/app/react/ConnectionsList/components/ConnectionsGroups.js @@ -20,17 +20,21 @@ class ConnectionsGroupsComponent extends Component { ); if (connectionsGroups.size) { - Results = ( -
-
-
    - {connectionsGroups.map(group => ( - - ))} -
+ if (this.props.sidePanelTrigger === 'library') { + Results =
Library triggered this side panel
; + } else { + Results = ( +
+
+
    + {connectionsGroups.map(group => ( + + ))} +
+
-
- ); + ); + } } return Results; @@ -39,11 +43,13 @@ class ConnectionsGroupsComponent extends Component { ConnectionsGroupsComponent.propTypes = { connectionsGroups: PropTypes.instanceOf(Immutable.List).isRequired, + sidePanelTrigger: PropTypes.string.isRequired, }; -function mapStateToProps({ relationships }) { +function mapStateToProps({ relationships, library }) { return { connectionsGroups: relationships.list.connectionsGroups, + sidePanelTrigger: library.sidepanel.trigger, }; } diff --git a/app/react/Documents/components/DocumentSidePanel.js b/app/react/Documents/components/DocumentSidePanel.js index 3a171f2437..633bde5cdb 100644 --- a/app/react/Documents/components/DocumentSidePanel.js +++ b/app/react/Documents/components/DocumentSidePanel.js @@ -27,6 +27,7 @@ import SearchText from './SearchText'; import ShowToc from './ShowToc'; import SnippetsTab from './SnippetsTab'; import helpers from '../helpers'; +import { getDocumentReferences } from 'app/Library/actions/libraryActions'; class DocumentSidePanel extends Component { constructor(props) { @@ -39,17 +40,16 @@ class DocumentSidePanel extends Component { this.toggleSharing = this.toggleSharing.bind(this); } - componentDidUpdate(prevProps) { + async componentDidUpdate(prevProps) { + const sharedId = this.props.doc.get('sharedId'); if ( this.props.doc.get('_id') && prevProps.doc.get('_id') !== this.props.doc.get('_id') && - this.props.getDocumentReferences + this.props.connectionsChanged && + getDocumentReferences ) { - this.props.getDocumentReferences( - this.props.doc.get('sharedId'), - this.props.file._id, - this.props.storeKey - ); + this.props.getDocumentReferences(sharedId, this.props.file._id, this.props.storeKey); + this.props.connectionsChanged(sharedId); } } @@ -486,6 +486,7 @@ DocumentSidePanel.defaultProps = { isTargetDoc: false, readOnly: false, getDocumentReferences: undefined, + connectionsChanged: undefined, tocFormComponent: () => false, EntityForm: () => false, raw: false, @@ -518,6 +519,7 @@ DocumentSidePanel.propTypes = { editToc: PropTypes.func, leaveEditMode: PropTypes.func, searchSnippets: PropTypes.func, + connectionsChanged: PropTypes.func, getDocumentReferences: PropTypes.func, removeFromToc: PropTypes.func, indentTocElement: PropTypes.func, @@ -550,7 +552,7 @@ const mapStateToProps = (state, ownProps) => { return { references, - excludeConnectionsTab: Boolean(ownProps.references), + excludeConnectionsTab: Boolean(state.relationships.list.connectionsGroups.length), connectionsGroups: state.relationships.list.connectionsGroups, relationships: ownProps.references, defaultLanguage, diff --git a/app/react/Library/actions/libraryActions.js b/app/react/Library/actions/libraryActions.js index a4aaa03e01..f0f6a2e076 100644 --- a/app/react/Library/actions/libraryActions.js +++ b/app/react/Library/actions/libraryActions.js @@ -32,6 +32,7 @@ function selectDocument(_doc) { if (showingSemanticSearch && !doc.semanticSearch) { dispatch(actions.set('library.sidepanel.tab', '')); } + dispatch(actions.set('library.sidepanel.trigger', 'library')); await dispatch(maybeSaveQuickLabels()); dispatch({ type: types.SELECT_DOCUMENT, doc }); dispatch(selectedDocumentsChanged()); @@ -378,6 +379,7 @@ function getDocumentReferences(sharedId, fileId, storeKey) { .get(new RequestParams({ sharedId, file: fileId, onlyTextReferences: true })) .then(references => { dispatch(actions.set(`${storeKey}.sidepanel.references`, references)); + dispatch(actions.set('relationships/list/sharedId', sharedId)); }); } diff --git a/app/react/Library/components/ViewMetadataPanel.js b/app/react/Library/components/ViewMetadataPanel.js index b0469bdb12..2c410918e1 100644 --- a/app/react/Library/components/ViewMetadataPanel.js +++ b/app/react/Library/components/ViewMetadataPanel.js @@ -12,6 +12,7 @@ import { wrapDispatch } from 'app/Multireducer'; import { entityDefaultDocument } from 'shared/entityDefaultDocument'; import modals from 'app/Modals'; +import * as connectionsActions from 'app/ConnectionsList/actions/actions'; import { getDocumentReferences, unselectAllDocuments, @@ -52,6 +53,7 @@ function mapDispatchToProps(dispatch, props) { { loadInReduxForm: actions.loadInReduxForm, getDocumentReferences, + connectionsChanged: connectionsActions.connectionsChanged, closePanel: unselectAllDocuments, resetForm: () => _dispatch => { _dispatch(formActions.setInitial(`${props.storeKey}.sidepanel.metadata`)); diff --git a/app/react/Library/reducers/reducer.js b/app/react/Library/reducers/reducer.js index 86fb422eec..e709c5d603 100644 --- a/app/react/Library/reducers/reducer.js +++ b/app/react/Library/reducers/reducer.js @@ -57,5 +57,6 @@ export default storeKey => fullText: [], }), tab: createReducer(`${storeKey}.sidepanel.tab`, ''), + trigger: createReducer(`${storeKey}.sidepanel.trigger`, ''), }), }); diff --git a/app/react/Viewer/components/ViewerComponent.js b/app/react/Viewer/components/ViewerComponent.js index 6980db376a..77371d4201 100644 --- a/app/react/Viewer/components/ViewerComponent.js +++ b/app/react/Viewer/components/ViewerComponent.js @@ -2,13 +2,19 @@ import { Map } from 'immutable'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import EntityView from 'app/Viewer/EntityView'; import Loader from 'app/components/Elements/Loader'; - +import { actions } from 'app/BasicReducer'; import PDFView from '../PDFView'; export class ViewerComponent extends Component { + constructor(props, context) { + super(props, context); + props.setSidepanelTrigger('entityViewer'); + } + render() { const { entity } = this.props; @@ -22,8 +28,12 @@ export class ViewerComponent extends Component { ViewerComponent.propTypes = { entity: PropTypes.instanceOf(Map).isRequired, + setSidepanelTrigger: PropTypes.func.isRequired, }; +const setSidepanelTrigger = name => dispatch => + dispatch(actions.set('library.sidepanel.trigger', name)); + const mapStateToProps = state => { const entity = state.documentViewer.doc.get('_id') ? state.documentViewer.doc @@ -34,4 +44,13 @@ const mapStateToProps = state => { }; }; -export default connect(mapStateToProps)(ViewerComponent); +function mapDispatchToProps(dispatch) { + return bindActionCreators( + { + setSidepanelTrigger, + }, + dispatch + ); +} + +export default connect(mapStateToProps, mapDispatchToProps)(ViewerComponent); From 4e72bd80e009f34e0b761d38d55063030eb8338b Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 26 Aug 2022 19:18:28 +0300 Subject: [PATCH 05/37] Added small UI --- .../ConnectionsList/components/ConnectionsGroups.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/react/ConnectionsList/components/ConnectionsGroups.js b/app/react/ConnectionsList/components/ConnectionsGroups.js index 5023179d4b..3d17155993 100644 --- a/app/react/ConnectionsList/components/ConnectionsGroups.js +++ b/app/react/ConnectionsList/components/ConnectionsGroups.js @@ -21,7 +21,15 @@ class ConnectionsGroupsComponent extends Component { if (connectionsGroups.size) { if (this.props.sidePanelTrigger === 'library') { - Results =
Library triggered this side panel
; + Results = ( +
+
+ {connectionsGroups.map(group => ( +
{group.get('connectionLabel')}
+ ))} +
+
+ ); } else { Results = (
From 515ddc1955c6f89939899e44c96374893c080b38 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 1 Sep 2022 12:21:56 +0300 Subject: [PATCH 06/37] Updated tests --- .../components/ConnectionsGroups.js | 11 +- .../components/LibraryViewRelationships.tsx | 115 ++++++++++++++++++ .../specs/ConnectionsGroups.spec.js | 1 + .../specs/DocumentSidePanel.spec.js | 7 +- .../__snapshots__/libraryActions.spec.js.snap | 4 + .../actions/specs/libraryActions.spec.js | 1 + .../Viewer/components/ViewerComponent.js | 5 +- 7 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 app/react/ConnectionsList/components/LibraryViewRelationships.tsx diff --git a/app/react/ConnectionsList/components/ConnectionsGroups.js b/app/react/ConnectionsList/components/ConnectionsGroups.js index 3d17155993..b267bbd4ed 100644 --- a/app/react/ConnectionsList/components/ConnectionsGroups.js +++ b/app/react/ConnectionsList/components/ConnectionsGroups.js @@ -6,6 +6,7 @@ import { connect } from 'react-redux'; import { t } from 'app/I18N'; import ConnectionsGroup from './ConnectionsGroup'; +import { LibraryViewRelationships } from './LibraryViewRelationships'; class ConnectionsGroupsComponent extends Component { render() { @@ -21,15 +22,7 @@ class ConnectionsGroupsComponent extends Component { if (connectionsGroups.size) { if (this.props.sidePanelTrigger === 'library') { - Results = ( -
-
- {connectionsGroups.map(group => ( -
{group.get('connectionLabel')}
- ))} -
-
- ); + Results = ; } else { Results = (
diff --git a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx new file mode 100644 index 0000000000..c858f5c8d4 --- /dev/null +++ b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx @@ -0,0 +1,115 @@ +import React, { useEffect } from 'react'; +import { connect, ConnectedProps } from 'react-redux'; +import PropTypes from 'prop-types'; +import { Map, List } from 'immutable'; +import { bindActionCreators } from 'redux'; +import { Icon } from 'app/UI'; +import * as actions from '../../Relationships/actions/actions'; +import HubRelationshipMetadata from '../../Relationships/components/HubRelationshipMetadata'; + +function mapStateToProps(state: any) { + const { relationships, library } = state; + return { + parentEntity: library.ui.getIn(['selectedDocuments', 0]), + searchResults: relationships.list.searchResults, + search: relationships.list.sort, + hubs: relationships.hubs, + relationTypes: actions.selectRelationTypes(state), + }; +} + +function mapDispatchToProps(dispatch: any) { + return bindActionCreators( + { + parseResults: actions.parseResults, + }, + dispatch + ); +} + +const connector = connect(mapStateToProps, mapDispatchToProps); + +type ComponentProps = ConnectedProps; + +const createRightRelationshipGroups = (rightRelationships: any, relationTypes: any[]) => ( +
+ {rightRelationships.map((relationship: any, index: number) => ( + <> +
+ + {' '} + {(() => { + const relationshipTemplateId = relationship.get('template'); + const relationType = relationTypes.find(r => r._id === relationshipTemplateId); + if (relationType) { + return relationshipTemplateId ? ( + relationTypes.find(r => r._id === relationshipTemplateId).name + ) : ( + + ); + } + return null; + })()} + +
+ {(() => { + return relationship.get('relationships').map((rel: any, indexI: number) => ( +
+
+
+
+ {rel.getIn(['entityData', 'title'])} +
+
+ +
+
+ )); + })()} + + ))} +
+); + +const createLabelGroups = (hub: any, relationTypes: any[]) => { + const template = hub.getIn(['leftRelationship', 'template']); + return ( +
+ + {template &&
{relationTypes.find(r => r._id === template).name}
} +
+ {createRightRelationshipGroups(hub.get('rightRelationships'), relationTypes)} +
+ ); +}; + +const LibraryViewRelationshipsComp = ({ + hubs, + searchResults, + parentEntity, + parseResults, + relationTypes, +}: ComponentProps) => { + useEffect(() => { + if (parentEntity) { + parseResults(searchResults, parentEntity, false); + } + }, [searchResults, parentEntity]); + return ( +
+ {hubs.map((hub: any) => createLabelGroups(hub, relationTypes))} +
+ ); +}; + +LibraryViewRelationshipsComp.propTypes = { + parentEntity: PropTypes.instanceOf(Map), + hubs: PropTypes.instanceOf(List).isRequired, + searchResults: PropTypes.instanceOf(Map).isRequired, + parseResults: PropTypes.func.isRequired, + relationTypes: PropTypes.instanceOf(Array).isRequired, +}; + +const LibraryViewRelationships = connector(LibraryViewRelationshipsComp); + +export { LibraryViewRelationshipsComp, LibraryViewRelationships }; diff --git a/app/react/ConnectionsList/components/specs/ConnectionsGroups.spec.js b/app/react/ConnectionsList/components/specs/ConnectionsGroups.spec.js index 3469c4ee12..b771b2ab09 100644 --- a/app/react/ConnectionsList/components/specs/ConnectionsGroups.spec.js +++ b/app/react/ConnectionsList/components/specs/ConnectionsGroups.spec.js @@ -21,6 +21,7 @@ describe('ConnectionsGroups', () => { ], }, ]), + sidePanelTrigger: 'entityView', }; }); diff --git a/app/react/Documents/components/specs/DocumentSidePanel.spec.js b/app/react/Documents/components/specs/DocumentSidePanel.spec.js index bbda780244..2b8c604270 100644 --- a/app/react/Documents/components/specs/DocumentSidePanel.spec.js +++ b/app/react/Documents/components/specs/DocumentSidePanel.spec.js @@ -213,7 +213,7 @@ describe('DocumentSidePanel', () => { beforeEach(() => { state = { documentViewer: { targetDoc: Immutable.fromJS({ _id: null }) }, - relationships: { list: { connectionsGroups: 'connectionsGroups' } }, + relationships: { list: { connectionsGroups: ['connectionsGroups'] } }, relationTypes: Immutable.fromJS(['a', 'b']), settings: { collection: Immutable.fromJS({ languages }) }, library: { sidepanel: { metadata: {} } }, @@ -241,12 +241,13 @@ describe('DocumentSidePanel', () => { expect(mapStateToProps(state, ownProps).references).toBe( 'References selector used correctly' ); - expect(mapStateToProps(state, ownProps).excludeConnectionsTab).toBe(false); + expect(mapStateToProps(state, ownProps).excludeConnectionsTab).toBe(true); }); it('should map selected target references from viewer when no ownProps and targetDoc', () => { const ownProps = { storeKey: 'library' }; state.documentViewer.targetDoc = Immutable.fromJS({ _id: 'targetDocId' }); + state.relationships.list.connectionsGroups = []; expect(mapStateToProps(state, ownProps).references).toBe( 'Target references selector used correctly' ); @@ -255,7 +256,7 @@ describe('DocumentSidePanel', () => { it('should map connectionsGroups', () => { const ownProps = { storeKey: 'library' }; - expect(mapStateToProps(state, ownProps).connectionsGroups).toBe('connectionsGroups'); + expect(mapStateToProps(state, ownProps).connectionsGroups).toEqual(['connectionsGroups']); }); it('should map default language', () => { diff --git a/app/react/Library/actions/specs/__snapshots__/libraryActions.spec.js.snap b/app/react/Library/actions/specs/__snapshots__/libraryActions.spec.js.snap index c7e44fc732..fe0cae06db 100644 --- a/app/react/Library/actions/specs/__snapshots__/libraryActions.spec.js.snap +++ b/app/react/Library/actions/specs/__snapshots__/libraryActions.spec.js.snap @@ -6,6 +6,10 @@ Array [ "type": "library.sidepanel.tab/SET", "value": "", }, + Object { + "type": "library.sidepanel.trigger/SET", + "value": "library", + }, Object { "doc": Object { "sharedId": "doc", diff --git a/app/react/Library/actions/specs/libraryActions.spec.js b/app/react/Library/actions/specs/libraryActions.spec.js index c5139c9299..935975334a 100644 --- a/app/react/Library/actions/specs/libraryActions.spec.js +++ b/app/react/Library/actions/specs/libraryActions.spec.js @@ -463,6 +463,7 @@ describe('libraryActions', () => { const expectedActions = [ { type: 'library.sidepanel.references/SET', value: 'referencesResponse' }, + { type: 'relationships/list/sharedId/SET', value: 'id' }, ]; const store = mockStore({ locale: 'es' }); diff --git a/app/react/Viewer/components/ViewerComponent.js b/app/react/Viewer/components/ViewerComponent.js index 77371d4201..9aeab6e923 100644 --- a/app/react/Viewer/components/ViewerComponent.js +++ b/app/react/Viewer/components/ViewerComponent.js @@ -12,7 +12,7 @@ import PDFView from '../PDFView'; export class ViewerComponent extends Component { constructor(props, context) { super(props, context); - props.setSidepanelTrigger('entityViewer'); + props.setSidepanelTrigger(); } render() { @@ -31,8 +31,7 @@ ViewerComponent.propTypes = { setSidepanelTrigger: PropTypes.func.isRequired, }; -const setSidepanelTrigger = name => dispatch => - dispatch(actions.set('library.sidepanel.trigger', name)); +const setSidepanelTrigger = () => actions.set('library.sidepanel.trigger', 'entityViewer'); const mapStateToProps = state => { const entity = state.documentViewer.doc.get('_id') From bd2689d318c47a21b31d88faf0c100749d094520 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 1 Sep 2022 12:27:01 +0300 Subject: [PATCH 07/37] Updated an action name --- app/react/ConnectionsList/components/ConnectionsGroups.js | 2 +- app/react/Library/actions/libraryActions.js | 2 +- .../actions/specs/__snapshots__/libraryActions.spec.js.snap | 2 +- app/react/Library/reducers/reducer.js | 2 +- app/react/Viewer/components/ViewerComponent.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/react/ConnectionsList/components/ConnectionsGroups.js b/app/react/ConnectionsList/components/ConnectionsGroups.js index b267bbd4ed..73eec403e4 100644 --- a/app/react/ConnectionsList/components/ConnectionsGroups.js +++ b/app/react/ConnectionsList/components/ConnectionsGroups.js @@ -50,7 +50,7 @@ ConnectionsGroupsComponent.propTypes = { function mapStateToProps({ relationships, library }) { return { connectionsGroups: relationships.list.connectionsGroups, - sidePanelTrigger: library.sidepanel.trigger, + sidePanelTrigger: library.sidepanel.view, }; } diff --git a/app/react/Library/actions/libraryActions.js b/app/react/Library/actions/libraryActions.js index f0f6a2e076..7890388ffa 100644 --- a/app/react/Library/actions/libraryActions.js +++ b/app/react/Library/actions/libraryActions.js @@ -32,7 +32,7 @@ function selectDocument(_doc) { if (showingSemanticSearch && !doc.semanticSearch) { dispatch(actions.set('library.sidepanel.tab', '')); } - dispatch(actions.set('library.sidepanel.trigger', 'library')); + dispatch(actions.set('library.sidepanel.view', 'library')); await dispatch(maybeSaveQuickLabels()); dispatch({ type: types.SELECT_DOCUMENT, doc }); dispatch(selectedDocumentsChanged()); diff --git a/app/react/Library/actions/specs/__snapshots__/libraryActions.spec.js.snap b/app/react/Library/actions/specs/__snapshots__/libraryActions.spec.js.snap index fe0cae06db..789be862ed 100644 --- a/app/react/Library/actions/specs/__snapshots__/libraryActions.spec.js.snap +++ b/app/react/Library/actions/specs/__snapshots__/libraryActions.spec.js.snap @@ -7,7 +7,7 @@ Array [ "value": "", }, Object { - "type": "library.sidepanel.trigger/SET", + "type": "library.sidepanel.view/SET", "value": "library", }, Object { diff --git a/app/react/Library/reducers/reducer.js b/app/react/Library/reducers/reducer.js index e709c5d603..ee3780ae0d 100644 --- a/app/react/Library/reducers/reducer.js +++ b/app/react/Library/reducers/reducer.js @@ -57,6 +57,6 @@ export default storeKey => fullText: [], }), tab: createReducer(`${storeKey}.sidepanel.tab`, ''), - trigger: createReducer(`${storeKey}.sidepanel.trigger`, ''), + view: createReducer(`${storeKey}.sidepanel.view`, ''), }), }); diff --git a/app/react/Viewer/components/ViewerComponent.js b/app/react/Viewer/components/ViewerComponent.js index 9aeab6e923..b10b04cb50 100644 --- a/app/react/Viewer/components/ViewerComponent.js +++ b/app/react/Viewer/components/ViewerComponent.js @@ -31,7 +31,7 @@ ViewerComponent.propTypes = { setSidepanelTrigger: PropTypes.func.isRequired, }; -const setSidepanelTrigger = () => actions.set('library.sidepanel.trigger', 'entityViewer'); +const setSidepanelTrigger = () => actions.set('library.sidepanel.view', 'entity'); const mapStateToProps = state => { const entity = state.documentViewer.doc.get('_id') From 6762b308a049bc3998328eeb108b3d60ce9e6df5 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 5 Sep 2022 11:38:07 +0300 Subject: [PATCH 08/37] Added collapsible component --- app/react/App/Collapsible.tsx | 31 +++++++++++++ app/react/App/specs/Collapsible.spec.tsx | 57 ++++++++++++++++++++++++ app/react/UI/Icon/library.js | 2 + 3 files changed, 90 insertions(+) create mode 100644 app/react/App/Collapsible.tsx create mode 100644 app/react/App/specs/Collapsible.spec.tsx diff --git a/app/react/App/Collapsible.tsx b/app/react/App/Collapsible.tsx new file mode 100644 index 0000000000..de7b24f633 --- /dev/null +++ b/app/react/App/Collapsible.tsx @@ -0,0 +1,31 @@ +import { Icon } from 'UI'; +import React, { ReactElement, useEffect, useState } from 'react'; + +interface CollapsibleProps { + className?: string; + header: string | HTMLElement; + headerInfo?: string; + children: ReactElement; + collapse?: boolean; +} + +const Collapsible = ({ header, children, className, headerInfo, collapse }: CollapsibleProps) => { + const [collapsed, setCollapsed] = useState(collapse); + useEffect(() => { + setCollapsed(collapse); + }, [collapse]); + return ( +
setCollapsed(!collapsed)}> +
+ + + + {header} + {headerInfo && {headerInfo}} +
+ {!collapsed &&
{children}
} +
+ ); +}; + +export { Collapsible }; diff --git a/app/react/App/specs/Collapsible.spec.tsx b/app/react/App/specs/Collapsible.spec.tsx new file mode 100644 index 0000000000..2db95cbb97 --- /dev/null +++ b/app/react/App/specs/Collapsible.spec.tsx @@ -0,0 +1,57 @@ +/** + * @jest-environment jsdom + */ + +import { mount, CommonWrapper } from 'enzyme'; +import React from 'react'; +import { Provider } from 'react-redux'; +import thunk from 'redux-thunk'; +import configureStore, { MockStoreCreator } from 'redux-mock-store'; +import { Collapsible } from '../Collapsible'; + +describe('Collapsible', () => { + let component: CommonWrapper; + const middlewares = [thunk]; + const mockStoreCreator: MockStoreCreator = configureStore(middlewares); + + const render = (props = {}) => + mount( + + +
+ + + ); + + it('should render children', () => { + component = render(); + expect(component.contains(
)).toBe(true); + }); + + it('should hide children when clicked', () => { + const mountComp = render(); + mountComp.simulate('click'); + expect(mountComp.contains(
)).toBe(false); + }); + + it('should collapse if collapse prop is set', () => { + const mountComp = render({ collapse: true }); + expect(mountComp.contains(
)).toBe(false); + }); + + it('should collapse if collapse prop is not set', () => { + const mountComp = render(); + expect(mountComp.contains(
)).toBe(true); + }); + + it('should react to collapse prop', () => { + const props = { + collapse: false, + }; + component = render(props); + expect(component.contains(
)).toBe(true); + props.collapse = true; + component.update(); + // expect(component.contains(
)).toBe(false); + }); +}); diff --git a/app/react/UI/Icon/library.js b/app/react/UI/Icon/library.js index 1204f38785..2c8fe3071a 100644 --- a/app/react/UI/Icon/library.js +++ b/app/react/UI/Icon/library.js @@ -14,6 +14,7 @@ import { faBullhorn } from '@fortawesome/free-solid-svg-icons/faBullhorn'; import { faCalculator } from '@fortawesome/free-solid-svg-icons/faCalculator'; import { faCalendar } from '@fortawesome/free-solid-svg-icons/faCalendar'; import { faCaretDown } from '@fortawesome/free-solid-svg-icons/faCaretDown'; +import { faCaretRight } from '@fortawesome/free-solid-svg-icons/faCaretRight'; import { faCaretSquareDown } from '@fortawesome/free-solid-svg-icons/faCaretSquareDown'; import { faCaretUp } from '@fortawesome/free-solid-svg-icons/faCaretUp'; import { faChartBar } from '@fortawesome/free-solid-svg-icons/faChartBar'; @@ -133,6 +134,7 @@ const icons = { faCalculator, faCalendar, faCaretDown, + faCaretRight, faCaretSquareDown, faCaretUp, faChartBar, From fbd87931ccc3b70e261795c0d0177bbe4485b96a Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 5 Sep 2022 11:38:20 +0300 Subject: [PATCH 09/37] Added footer --- app/react/App/scss/layout/_footer.scss | 12 +++++ app/react/App/scss/layout/_sidepanel.scss | 49 +++++++++++++++++++ .../Documents/components/DocumentSidePanel.js | 46 +++++++++++++++-- 3 files changed, 103 insertions(+), 4 deletions(-) diff --git a/app/react/App/scss/layout/_footer.scss b/app/react/App/scss/layout/_footer.scss index b3a1cdd24a..5aef8043c7 100644 --- a/app/react/App/scss/layout/_footer.scss +++ b/app/react/App/scss/layout/_footer.scss @@ -1,3 +1,12 @@ +@mixin relationshipsFooterButtons { + .relationships-right-buttons { + float: right; + } + .relationships-left-buttons { + width: 50%; + } +} + footer { margin: 30px 0 0; padding: 20px 0 15px; @@ -237,6 +246,8 @@ footer { .btn:disabled .btn-label { color: $c-grey !important; } + + @include relationshipsFooterButtons(); } @media (max-width: 1023px) { @@ -259,6 +270,7 @@ footer { .sidepanel-footer { width: 400px; + @include relationshipsFooterButtons(); } .entity-footer, diff --git a/app/react/App/scss/layout/_sidepanel.scss b/app/react/App/scss/layout/_sidepanel.scss index 477b2781e8..b8dd642a84 100644 --- a/app/react/App/scss/layout/_sidepanel.scss +++ b/app/react/App/scss/layout/_sidepanel.scss @@ -234,6 +234,55 @@ $c-sidebar: $c-white; // // firefox hack (related issue #378) // padding-bottom: $header-height * 3; // } + .sidepanel-relationships { + display: flex; + flex-direction: column; + .sidepanel-relationship { + display: flex; + flex-direction: column; + .sidepanel-relationship-left-label { + font-weight: bold; + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 8px 16px; + gap: 9px; + background: #eeeeee; + border: 1px solid #d7d7dc; + } + .sidepanel-relationship-right { + padding: 7px 10px 7px 10px; + .sidepanel-relationship-collapsible { + .header { + cursor: pointer; + font-weight: bold; + padding: 6px 12px; + gap: 12px; + background: #eeeeee; + border: 1px solid #d7d7dc; + margin-bottom: 7px; + .header-info { + float: right; + } + .header-icon { + margin-right: 6px; + font-size: large; + } + } + .content { + .sidepanel-relationship-right-entity { + margin-bottom: 7px; + border: 1px solid #d7d7dc; + box-sizing: border-box; + + padding: 8px 16px; + gap: 9px; + } + } + } + } + } + } } .sidepanel-header { diff --git a/app/react/Documents/components/DocumentSidePanel.js b/app/react/Documents/components/DocumentSidePanel.js index ff7cc0304e..6502d5d771 100644 --- a/app/react/Documents/components/DocumentSidePanel.js +++ b/app/react/Documents/components/DocumentSidePanel.js @@ -33,7 +33,7 @@ class DocumentSidePanel extends Component { constructor(props) { super(props); this.selectTab = this.selectTab.bind(this); - this.state = { copyFrom: false, copyFromProps: [] }; + this.state = { copyFrom: false, copyFromProps: [], relationshipsExpanded: false }; this.toggleCopyFrom = this.toggleCopyFrom.bind(this); this.onCopyFromSelect = this.onCopyFromSelect.bind(this); this.deleteDocument = this.deleteDocument.bind(this); @@ -123,6 +123,14 @@ class DocumentSidePanel extends Component { })); } + collapseRelationships() { + this.setState({ relationshipsExpanded: false }); + } + + expandRelationships() { + this.setState({ relationshipsExpanded: true }); + } + renderHeader(tab, doc, isEntity) { if (this.state.copyFrom) { return ( @@ -255,7 +263,7 @@ class DocumentSidePanel extends Component { return (
  • + +
    +
    + +
    +
    + + +
    +
    +
    {this.props.tab === 'toc' && this.props.tocBeingEdited && (
    @@ -460,8 +498,8 @@ class DocumentSidePanel extends Component { readOnly={readOnly} /> - - + + From 7b60bff0bc5d311c911b38debae6f37aa5bdb549 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 5 Sep 2022 11:38:37 +0300 Subject: [PATCH 10/37] Refactored a little bit --- .../components/ConnectionsGroups.js | 9 +- .../components/LibraryViewRelationships.tsx | 105 ++++++++++-------- 2 files changed, 66 insertions(+), 48 deletions(-) diff --git a/app/react/ConnectionsList/components/ConnectionsGroups.js b/app/react/ConnectionsList/components/ConnectionsGroups.js index 73eec403e4..a7ace632c5 100644 --- a/app/react/ConnectionsList/components/ConnectionsGroups.js +++ b/app/react/ConnectionsList/components/ConnectionsGroups.js @@ -10,7 +10,7 @@ import { LibraryViewRelationships } from './LibraryViewRelationships'; class ConnectionsGroupsComponent extends Component { render() { - const { connectionsGroups } = this.props; + const { connectionsGroups, expanded } = this.props; let Results = (
    @@ -22,7 +22,7 @@ class ConnectionsGroupsComponent extends Component { if (connectionsGroups.size) { if (this.props.sidePanelTrigger === 'library') { - Results = ; + Results = ; } else { Results = (
    @@ -42,9 +42,14 @@ class ConnectionsGroupsComponent extends Component { } } +ConnectionsGroupsComponent.defaultProps = { + expanded: false, +}; + ConnectionsGroupsComponent.propTypes = { connectionsGroups: PropTypes.instanceOf(Immutable.List).isRequired, sidePanelTrigger: PropTypes.string.isRequired, + expanded: PropTypes.bool, }; function mapStateToProps({ relationships, library }) { diff --git a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx index c858f5c8d4..bc7bf632bb 100644 --- a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx +++ b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx @@ -4,8 +4,13 @@ import PropTypes from 'prop-types'; import { Map, List } from 'immutable'; import { bindActionCreators } from 'redux'; import { Icon } from 'app/UI'; +import { Item } from 'app/Layout'; +import { Collapsible } from 'app/App/Collapsible'; import * as actions from '../../Relationships/actions/actions'; -import HubRelationshipMetadata from '../../Relationships/components/HubRelationshipMetadata'; + +interface LibraryViewRelationshipsProps { + expanded: boolean; +} function mapStateToProps(state: any) { const { relationships, library } = state; @@ -29,56 +34,62 @@ function mapDispatchToProps(dispatch: any) { const connector = connect(mapStateToProps, mapDispatchToProps); -type ComponentProps = ConnectedProps; +type ComponentProps = ConnectedProps & LibraryViewRelationshipsProps; + +const createRightRelationshipGroups = ( + rightRelationships: any, + relationTypes: any[], + expanded: boolean +) => ( +
    + {rightRelationships.map((relationship: any) => { + let header; + const relationshipTemplateId = relationship.get('template'); + const relationType = relationTypes.find(r => r._id === relationshipTemplateId); + if (relationType) { + header = relationshipTemplateId ? ( + relationTypes.find(r => r._id === relationshipTemplateId).name + ) : ( + + ); + } -const createRightRelationshipGroups = (rightRelationships: any, relationTypes: any[]) => ( -
    - {rightRelationships.map((relationship: any, index: number) => ( - <> -
    - - {' '} - {(() => { - const relationshipTemplateId = relationship.get('template'); - const relationType = relationTypes.find(r => r._id === relationshipTemplateId); - if (relationType) { - return relationshipTemplateId ? ( - relationTypes.find(r => r._id === relationshipTemplateId).name - ) : ( - - ); - } - return null; - })()} - -
    - {(() => { - return relationship.get('relationships').map((rel: any, indexI: number) => ( -
    -
    -
    -
    - {rel.getIn(['entityData', 'title'])} -
    -
    - + const entityRelationships = relationship.get('relationships'); + return ( + + <> + {entityRelationships.map((rel: any, indexI: number) => ( +
    +
    -
    - )); - })()} - - ))} + ))} + + + ); + })}
    ); -const createLabelGroups = (hub: any, relationTypes: any[]) => { +const createLabelGroups = (hub: any, relationTypes: any[], expanded: boolean) => { const template = hub.getIn(['leftRelationship', 'template']); return ( -
    - - {template &&
    {relationTypes.find(r => r._id === template).name}
    } -
    - {createRightRelationshipGroups(hub.get('rightRelationships'), relationTypes)} +
    + {template && ( + + {`${relationTypes.find(r => r._id === template).name}(Label)`} + + )} + {createRightRelationshipGroups(hub.get('rightRelationships'), relationTypes, expanded)}
    ); }; @@ -89,6 +100,7 @@ const LibraryViewRelationshipsComp = ({ parentEntity, parseResults, relationTypes, + expanded, }: ComponentProps) => { useEffect(() => { if (parentEntity) { @@ -96,8 +108,8 @@ const LibraryViewRelationshipsComp = ({ } }, [searchResults, parentEntity]); return ( -
    - {hubs.map((hub: any) => createLabelGroups(hub, relationTypes))} +
    + {hubs.map((hub: any) => createLabelGroups(hub, relationTypes, expanded))}
    ); }; @@ -108,6 +120,7 @@ LibraryViewRelationshipsComp.propTypes = { searchResults: PropTypes.instanceOf(Map).isRequired, parseResults: PropTypes.func.isRequired, relationTypes: PropTypes.instanceOf(Array).isRequired, + expanded: PropTypes.bool, }; const LibraryViewRelationships = connector(LibraryViewRelationshipsComp); From f2df38fc04ee6c50d9595494fd0d5b0567af6be6 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 5 Sep 2022 19:51:50 +0300 Subject: [PATCH 11/37] Updated scroll event --- app/react/App/StickyHeader.tsx | 38 +++++++++++++++++++ app/react/App/scss/layout/_sidepanel.scss | 3 ++ .../components/LibraryViewRelationships.tsx | 22 +++++++---- .../Documents/components/DocumentSidePanel.js | 9 +---- app/react/Library/components/ViewDocButton.js | 6 ++- 5 files changed, 61 insertions(+), 17 deletions(-) create mode 100644 app/react/App/StickyHeader.tsx diff --git a/app/react/App/StickyHeader.tsx b/app/react/App/StickyHeader.tsx new file mode 100644 index 0000000000..81177a4e5a --- /dev/null +++ b/app/react/App/StickyHeader.tsx @@ -0,0 +1,38 @@ +import React, { ReactElement, Component } from 'react'; + +interface StickyHeaderProps { + children: ReactElement; + scrollElementSelector: string; +} + +class StickyHeader extends Component { + element: Element | null = null; + + constructor(props: StickyHeaderProps) { + super(props); + this.onElementScroll = this.onElementScroll.bind(this); + } + + componentDidMount() { + this.element = document.querySelector(this.props.scrollElementSelector); + if (this.element) { + console.log(this.element); + this.element.addEventListener('onscroll', () => console.log('Scrolled...')); + } + } + + componentWillUnmount(): void { + this.element?.removeEventListener('onscroll', () => console.log('Scrolled...')); + } + + // eslint-disable-next-line class-methods-use-this + onElementScroll(event: Event) { + console.log('Scroll has happened...', event); + } + + render() { + return <>{this.props.children}; + } +} + +export { StickyHeader }; diff --git a/app/react/App/scss/layout/_sidepanel.scss b/app/react/App/scss/layout/_sidepanel.scss index b8dd642a84..6694a61b15 100644 --- a/app/react/App/scss/layout/_sidepanel.scss +++ b/app/react/App/scss/layout/_sidepanel.scss @@ -277,6 +277,9 @@ $c-sidebar: $c-white; padding: 8px 16px; gap: 9px; + .item-document { + z-index: -1; + } } } } diff --git a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx index bc7bf632bb..e35ed5a841 100644 --- a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx +++ b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx @@ -6,6 +6,7 @@ import { bindActionCreators } from 'redux'; import { Icon } from 'app/UI'; import { Item } from 'app/Layout'; import { Collapsible } from 'app/App/Collapsible'; +import { StickyHeader } from 'app/App/StickyHeader'; import * as actions from '../../Relationships/actions/actions'; interface LibraryViewRelationshipsProps { @@ -42,7 +43,7 @@ const createRightRelationshipGroups = ( expanded: boolean ) => (
    - {rightRelationships.map((relationship: any) => { + {rightRelationships.map((relationship: any, relationshipIndex: number) => { let header; const relationshipTemplateId = relationship.get('template'); const relationType = relationTypes.find(r => r._id === relationshipTemplateId); @@ -61,10 +62,11 @@ const createRightRelationshipGroups = ( className="sidepanel-relationship-collapsible" headerInfo={`(${entityRelationships.size})`} collapse={!expanded} + key={relationshipIndex} > <> - {entityRelationships.map((rel: any, indexI: number) => ( -
    + {entityRelationships.map((rel: any, entityRelationshipsIndex: number) => ( +
    ); -const createLabelGroups = (hub: any, relationTypes: any[], expanded: boolean) => { +const createLabelGroups = (hub: any, relationTypes: any[], expanded: boolean, index: number) => { const template = hub.getIn(['leftRelationship', 'template']); return ( -
    +
    {template && ( {`${relationTypes.find(r => r._id === template).name}(Label)`} @@ -108,9 +110,13 @@ const LibraryViewRelationshipsComp = ({ } }, [searchResults, parentEntity]); return ( -
    - {hubs.map((hub: any) => createLabelGroups(hub, relationTypes, expanded))} -
    + +
    + {hubs.map((hub: any, index: number) => + createLabelGroups(hub, relationTypes, expanded, index) + )} +
    +
    ); }; diff --git a/app/react/Documents/components/DocumentSidePanel.js b/app/react/Documents/components/DocumentSidePanel.js index 6502d5d771..0f0557f908 100644 --- a/app/react/Documents/components/DocumentSidePanel.js +++ b/app/react/Documents/components/DocumentSidePanel.js @@ -23,6 +23,7 @@ import { Icon } from 'UI'; import * as viewerModule from 'app/Viewer'; import { entityDefaultDocument } from 'shared/entityDefaultDocument'; +import ViewDocButton from 'app/Library/components/ViewDocButton'; import SearchText from './SearchText'; import ShowToc from './ShowToc'; import SnippetsTab from './SnippetsTab'; @@ -336,13 +337,7 @@ class DocumentSidePanel extends Component {
    - +
    )} -
    +
    Date: Thu, 8 Sep 2022 14:35:27 +0300 Subject: [PATCH 13/37] Improved sticky header implementation --- app/react/App/Collapsible.tsx | 4 +- app/react/App/StickyHeader.tsx | 39 +-- app/react/App/scss/layout/_sidepanel.scss | 7 +- app/react/App/scss/modules/_entity.scss | 1 + .../components/LibraryViewRelationships.tsx | 60 ++-- .../Documents/components/DocumentSidePanel.js | 264 +++++++++--------- 6 files changed, 209 insertions(+), 166 deletions(-) diff --git a/app/react/App/Collapsible.tsx b/app/react/App/Collapsible.tsx index de7b24f633..e33f4d711b 100644 --- a/app/react/App/Collapsible.tsx +++ b/app/react/App/Collapsible.tsx @@ -15,8 +15,8 @@ const Collapsible = ({ header, children, className, headerInfo, collapse }: Coll setCollapsed(collapse); }, [collapse]); return ( -
    setCollapsed(!collapsed)}> -
    +
    +
    setCollapsed(!collapsed)}> diff --git a/app/react/App/StickyHeader.tsx b/app/react/App/StickyHeader.tsx index 2d882f19c9..655168dfc8 100644 --- a/app/react/App/StickyHeader.tsx +++ b/app/react/App/StickyHeader.tsx @@ -1,38 +1,45 @@ -import React, { LegacyRef, useEffect } from 'react'; +import React, { LegacyRef, ReactElement, useEffect } from 'react'; interface StickyHeaderProps { - children: Function; + children: ReactElement; scrollElementSelector: string; + stickyElementSelector: string; } // eslint-disable-next-line max-statements -const stickHeader = (self: any, event: Event) => { - if (self) { - self.current?.classList.remove('sticky'); - // @ts-ignore - const scrollerTop = event.target?.scrollTop || 0; - const stickyTop = self.current?.offsetTop || 0; - const stickyBottom = stickyTop + (self.current?.offsetHeight || 0); +const stickHeader = (self: any, stickyElementSelector: string, event: Event) => { + if (self && self.current && event.target) { + const stickyElement = self.current.querySelector(stickyElementSelector) as HTMLElement; + const parentTop = (event.target as HTMLElement).getBoundingClientRect().top || 0; + self.current.classList.remove('sticky'); + const scrollerTop = (event.target as HTMLElement).scrollTop || 0; + const stickyTop = self.current.offsetTop || 0; + const stickyBottom = stickyTop + (self.current.offsetHeight || 0); - if (stickyTop <= scrollerTop && stickyBottom > scrollerTop) { - self.current?.classList.add('sticky'); + if (stickyTop < scrollerTop && stickyBottom > scrollerTop) { + self.current.classList.add('sticky'); + if (stickyElement) { + stickyElement.style.top = `${parentTop}px`; + } } } }; const StickyHeader = (props: StickyHeaderProps) => { - const { children, scrollElementSelector } = props; + const { children, scrollElementSelector, stickyElementSelector } = props; const self: LegacyRef = React.createRef(); const body = document.querySelector(scrollElementSelector); - const parentTop = ((body?.offsetParent as HTMLElement).offsetTop || 0) + (body?.offsetTop || 0); useEffect(() => { - body?.addEventListener('scroll', event => stickHeader(self, event)); + body?.addEventListener('scroll', event => { + stickHeader(self, stickyElementSelector, event); + }); + return () => { - body?.removeEventListener('scroll', event => stickHeader(self, event)); + body?.removeEventListener('scroll', event => stickHeader(self, stickyElementSelector, event)); }; }); - return
    {children({ style: { top: parentTop } })}
    ; + return
    {children}
    ; }; export { StickyHeader }; diff --git a/app/react/App/scss/layout/_sidepanel.scss b/app/react/App/scss/layout/_sidepanel.scss index 98c769da0f..0f0d0ac8af 100644 --- a/app/react/App/scss/layout/_sidepanel.scss +++ b/app/react/App/scss/layout/_sidepanel.scss @@ -5,6 +5,7 @@ $c-sidebar: darken($c-background, 0%); $c-sidebar: $c-white; + .side-panel { z-index: 4; display: flex; @@ -241,12 +242,12 @@ $c-sidebar: $c-white; height: 100%; .sticky { + padding-top: 46px; .sidepanel-relationship { - padding-top: 46px; .sidepanel-relationship-left-label { position: fixed; top: 0; - z-index: 10; + z-index: 1; width: 100%; color: blue; } @@ -286,6 +287,8 @@ $c-sidebar: $c-white; } .content { .sidepanel-relationship-right-entity { + cursor: pointer; + height: 100%; margin-bottom: 7px; border: 1px solid #d7d7dc; box-sizing: border-box; diff --git a/app/react/App/scss/modules/_entity.scss b/app/react/App/scss/modules/_entity.scss index d05686718e..94b4d534a4 100644 --- a/app/react/App/scss/modules/_entity.scss +++ b/app/react/App/scss/modules/_entity.scss @@ -47,6 +47,7 @@ .connections-metadata, .entity-create-connection-panel { top: 0; + z-index: 7; @media (min-width: 1024px) { top: $header-height + 1 !important; } diff --git a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx index 6e38c2ecd6..c42aa0cba4 100644 --- a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx +++ b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx @@ -7,6 +7,7 @@ import { Icon } from 'app/UI'; import { Item } from 'app/Layout'; import { Collapsible } from 'app/App/Collapsible'; import { StickyHeader } from 'app/App/StickyHeader'; +import RelationshipMetadata from 'app/Relationships/components/RelationshipMetadata'; import * as actions from '../../Relationships/actions/actions'; interface LibraryViewRelationshipsProps { @@ -27,6 +28,7 @@ function mapStateToProps(state: any) { function mapDispatchToProps(dispatch: any) { return bindActionCreators( { + selectConnection: actions.selectConnection, parseResults: actions.parseResults, }, dispatch @@ -40,6 +42,7 @@ type ComponentProps = ConnectedProps & LibraryViewRelationship const createRightRelationshipGroups = ( rightRelationships: any, relationTypes: any[], + selectConnection: Function, expanded: boolean ) => (
    @@ -66,7 +69,11 @@ const createRightRelationshipGroups = ( > <> {entityRelationships.map((rel: any, entityRelationshipsIndex: number) => ( -
    +
    selectConnection(rel.get('entityData'))} + > ); -const createLabelGroups = (hub: any, relationTypes: any[], expanded: boolean, index: number) => { +const createLabelGroups = ( + hub: any, + relationTypes: any[], + selectConnection: Function, + expanded: boolean, + index: number +) => { const template = hub.getIn(['leftRelationship', 'template']); return ( - - {({ style }: { style: any }) => ( -
    - {template && ( - - {`${relationTypes.find(r => r._id === template).name}(Label)`} - - )} - {createRightRelationshipGroups(hub.get('rightRelationships'), relationTypes, expanded)} -
    - )} + +
    + {template && ( + + {`${relationTypes.find(r => r._id === template).name}`} + + )} + {createRightRelationshipGroups( + hub.get('rightRelationships'), + relationTypes, + selectConnection, + expanded + )} +
    ); }; @@ -107,6 +126,7 @@ const LibraryViewRelationshipsComp = ({ parseResults, relationTypes, expanded, + selectConnection, }: ComponentProps) => { useEffect(() => { if (parentEntity) { @@ -114,15 +134,19 @@ const LibraryViewRelationshipsComp = ({ } }, [searchResults, parentEntity]); return ( -
    - {hubs.map((hub: any, index: number) => - createLabelGroups(hub, relationTypes, expanded, index) - )} -
    + <> +
    + {hubs.map((hub: any, index: number) => + createLabelGroups(hub, relationTypes, selectConnection, expanded, index) + )} +
    + + ); }; LibraryViewRelationshipsComp.propTypes = { + selectConnection: PropTypes.func.isRequired, parentEntity: PropTypes.instanceOf(Map), hubs: PropTypes.instanceOf(List).isRequired, searchResults: PropTypes.instanceOf(Map).isRequired, diff --git a/app/react/Documents/components/DocumentSidePanel.js b/app/react/Documents/components/DocumentSidePanel.js index bde26c82e2..06d364991c 100644 --- a/app/react/Documents/components/DocumentSidePanel.js +++ b/app/react/Documents/components/DocumentSidePanel.js @@ -21,6 +21,7 @@ import { CopyFromEntity } from 'app/Metadata/components/CopyFromEntity'; import { TocGeneratedLabel, ReviewTocButton } from 'app/ToggledFeatures/tocGeneration'; import { Icon } from 'UI'; +import { Item } from 'app/Layout'; import * as viewerModule from 'app/Viewer'; import { entityDefaultDocument } from 'shared/entityDefaultDocument'; import ViewDocButton from 'app/Library/components/ViewDocButton'; @@ -153,134 +154,141 @@ class DocumentSidePanel extends Component { { totalConnections: 0 } ); return ( -
    - - -
      - {(() => { - if (!this.props.raw && doc.get('semanticSearch')) { - return ( -
    • - - - - Semantic search results - - -
    • - ); - } - })()} - {(() => { - if (!this.props.raw) { - return ( -
    • - - - -
    • - ); - } - })()} - {(() => { - if (!isEntity && !this.props.raw) { - return ( -
    • - - - {t('System', 'Table of Contents')} - -
    • - ); - } - return ; - })()} - {(() => { - if (!isEntity && !this.props.raw) { - return ( -
    • - - - {references.size} - {t('System', 'References')} - -
    • - ); - } - return ; - })()} - {(() => { - if (!this.props.raw) { - return
    • ; - } - return ; - })()} -
    • - - - {t('System', 'Info')} - -
    • - {(() => { - if (!isTargetDoc && !excludeConnectionsTab) { - return ( -
    • - - - {summary.totalConnections} - {t('System', 'Relationships')} - -
    • - ); - } - })()} -
    -
    -
    + <> +
    + + +
      + {(() => { + if (!this.props.raw && doc.get('semanticSearch')) { + return ( +
    • + + + + Semantic search results + + +
    • + ); + } + })()} + {(() => { + if (!this.props.raw) { + return ( +
    • + + + +
    • + ); + } + })()} + {(() => { + if (!isEntity && !this.props.raw) { + return ( +
    • + + + {t('System', 'Table of Contents')} + +
    • + ); + } + return ; + })()} + {(() => { + if (!isEntity && !this.props.raw) { + return ( +
    • + + + {references.size} + {t('System', 'References')} + +
    • + ); + } + return ; + })()} + {(() => { + if (!this.props.raw) { + return
    • ; + } + return ; + })()} +
    • + + + {t('System', 'Info')} + +
    • + {(() => { + if (!isTargetDoc && !excludeConnectionsTab) { + return ( +
    • + + + {summary.totalConnections} + {t('System', 'Relationships')} + +
    • + ); + } + })()} +
    +
    +
    + +
    + +
    +
    + ); } From d03966be7ac85bf09314e7c5a9be2886d18f1e98 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 8 Sep 2022 15:24:16 +0300 Subject: [PATCH 14/37] Fixed collapsible tes --- app/react/App/specs/Collapsible.spec.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/react/App/specs/Collapsible.spec.tsx b/app/react/App/specs/Collapsible.spec.tsx index 2db95cbb97..e27d24ad58 100644 --- a/app/react/App/specs/Collapsible.spec.tsx +++ b/app/react/App/specs/Collapsible.spec.tsx @@ -31,6 +31,7 @@ describe('Collapsible', () => { it('should hide children when clicked', () => { const mountComp = render(); mountComp.simulate('click'); + mountComp.find('.header').simulate('click'); expect(mountComp.contains(
    )).toBe(false); }); From b81192f76538a0ff79d560e82cde6f3f5c71ef84 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 8 Sep 2022 15:25:38 +0300 Subject: [PATCH 15/37] Fixed error in collapsible test --- app/react/App/specs/Collapsible.spec.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/react/App/specs/Collapsible.spec.tsx b/app/react/App/specs/Collapsible.spec.tsx index e27d24ad58..30137ad52e 100644 --- a/app/react/App/specs/Collapsible.spec.tsx +++ b/app/react/App/specs/Collapsible.spec.tsx @@ -14,7 +14,7 @@ describe('Collapsible', () => { const middlewares = [thunk]; const mockStoreCreator: MockStoreCreator = configureStore(middlewares); - const render = (props = {}) => + const render = (props = { collapse: false }) => mount( From cea8bf07e8610f4fd7e06ddc99a63be4eea68455 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 8 Sep 2022 15:33:43 +0300 Subject: [PATCH 16/37] Fixed a few codeclimate issues --- app/react/ConnectionsList/actions/actions.js | 7 ++++--- app/react/Documents/components/DocumentSidePanel.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/react/ConnectionsList/actions/actions.js b/app/react/ConnectionsList/actions/actions.js index d719b9152d..b2923cc982 100644 --- a/app/react/ConnectionsList/actions/actions.js +++ b/app/react/ConnectionsList/actions/actions.js @@ -34,12 +34,13 @@ export function searchReferences() { export function connectionsChanged(sharedId) { return (dispatch, getState) => { const relationshipsList = getState().relationships.list; - if (!sharedId) { - sharedId = relationshipsList.sharedId; + let innerSharedId = sharedId; + if (!innerSharedId) { + innerSharedId = relationshipsList.sharedId; } return referencesAPI - .getGroupedByConnection(new RequestParams({ sharedId })) + .getGroupedByConnection(new RequestParams({ sharedId: innerSharedId })) .then(connectionsGroups => { const filteredTemplates = connectionsGroups.reduce( (templateIds, group) => templateIds.concat(group.templates.map(t => t._id.toString())), diff --git a/app/react/Documents/components/DocumentSidePanel.js b/app/react/Documents/components/DocumentSidePanel.js index 06d364991c..947539d119 100644 --- a/app/react/Documents/components/DocumentSidePanel.js +++ b/app/react/Documents/components/DocumentSidePanel.js @@ -25,11 +25,11 @@ import { Item } from 'app/Layout'; import * as viewerModule from 'app/Viewer'; import { entityDefaultDocument } from 'shared/entityDefaultDocument'; import ViewDocButton from 'app/Library/components/ViewDocButton'; +import { getDocumentReferences } from 'app/Library/actions/libraryActions'; import SearchText from './SearchText'; import ShowToc from './ShowToc'; import SnippetsTab from './SnippetsTab'; import helpers from '../helpers'; -import { getDocumentReferences } from 'app/Library/actions/libraryActions'; class DocumentSidePanel extends Component { constructor(props) { From 5c04ff60ca2d76b65b0e1b7c515e1f8b1f87fc95 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 9 Sep 2022 12:23:36 +0300 Subject: [PATCH 17/37] Added load more button, and filter limit --- app/react/ConnectionsList/actions/actions.js | 1 + .../components/LibraryViewRelationships.tsx | 21 +++++++++++-------- .../components/LoadMoreRelationshipsButton.js | 9 +++++++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/app/react/ConnectionsList/actions/actions.js b/app/react/ConnectionsList/actions/actions.js index b2923cc982..358bb8b9b7 100644 --- a/app/react/ConnectionsList/actions/actions.js +++ b/app/react/ConnectionsList/actions/actions.js @@ -33,6 +33,7 @@ export function searchReferences() { export function connectionsChanged(sharedId) { return (dispatch, getState) => { + dispatch(actions.set('relationships/list/filters', { limit: 10 })); const relationshipsList = getState().relationships.list; let innerSharedId = sharedId; if (!innerSharedId) { diff --git a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx index c42aa0cba4..7e53aa8e0a 100644 --- a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx +++ b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx @@ -9,6 +9,7 @@ import { Collapsible } from 'app/App/Collapsible'; import { StickyHeader } from 'app/App/StickyHeader'; import RelationshipMetadata from 'app/Relationships/components/RelationshipMetadata'; import * as actions from '../../Relationships/actions/actions'; +import LoadMoreRelationshipsButton from 'app/Relationships/components/LoadMoreRelationshipsButton'; interface LibraryViewRelationshipsProps { expanded: boolean; @@ -119,15 +120,16 @@ const createLabelGroups = ( ); }; -const LibraryViewRelationshipsComp = ({ - hubs, - searchResults, - parentEntity, - parseResults, - relationTypes, - expanded, - selectConnection, -}: ComponentProps) => { +const LibraryViewRelationshipsComp = (props: ComponentProps) => { + const { + hubs, + searchResults, + parentEntity, + parseResults, + relationTypes, + expanded, + selectConnection, + } = props; useEffect(() => { if (parentEntity) { parseResults(searchResults, parentEntity, false); @@ -140,6 +142,7 @@ const LibraryViewRelationshipsComp = ({ createLabelGroups(hub, relationTypes, selectConnection, expanded, index) )} + ); diff --git a/app/react/Relationships/components/LoadMoreRelationshipsButton.js b/app/react/Relationships/components/LoadMoreRelationshipsButton.js index 33e74b00a7..852d1ab8eb 100644 --- a/app/react/Relationships/components/LoadMoreRelationshipsButton.js +++ b/app/react/Relationships/components/LoadMoreRelationshipsButton.js @@ -18,13 +18,20 @@ export const LoadMoreRelationshipsButton = ({ action(requestedHubs + loadMoreAmmount); }; + let loadMoreAmmountDisplay = loadMoreAmmount; + + const hubsLeft = totalHubs - requestedHubs; + if (hubsLeft < loadMoreAmmount) { + loadMoreAmmountDisplay = hubsLeft; + } + return (

    {requestedHubs} {t('System', 'of')} {totalHubs} {t('System', 'hubs')}

    ); From c963e04f82d6d6d2b82adda36ac1435be3c66eb8 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 9 Sep 2022 12:46:28 +0300 Subject: [PATCH 18/37] Updated tests --- app/react/App/scss/modules/_entity.scss | 2 +- .../components/specs/LoadMoreRelationshipsButton.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/react/App/scss/modules/_entity.scss b/app/react/App/scss/modules/_entity.scss index 94b4d534a4..b7d834ea39 100644 --- a/app/react/App/scss/modules/_entity.scss +++ b/app/react/App/scss/modules/_entity.scss @@ -47,7 +47,7 @@ .connections-metadata, .entity-create-connection-panel { top: 0; - z-index: 7; + z-index: 6; @media (min-width: 1024px) { top: $header-height + 1 !important; } diff --git a/app/react/Relationships/components/specs/LoadMoreRelationshipsButton.spec.js b/app/react/Relationships/components/specs/LoadMoreRelationshipsButton.spec.js index 1a27a0b86c..e7dd43429b 100644 --- a/app/react/Relationships/components/specs/LoadMoreRelationshipsButton.spec.js +++ b/app/react/Relationships/components/specs/LoadMoreRelationshipsButton.spec.js @@ -34,7 +34,7 @@ describe('LoadMoreRelationshipsButton', () => { it('should render a button when partial loaded hubs', () => { expect(component.find('button').length).toBe(1); - expect(component.find('button').text()).toBe('2 x more'); + expect(component.find('button').text()).toBe('1 x more'); }); it('should call on the passed function upon click with previously requestedHubs', () => { From 54b524ff2cc9ba6ba5eb71aba006620883c85bde Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 9 Sep 2022 13:10:07 +0300 Subject: [PATCH 19/37] Refactored sticky header --- app/react/App/StickyHeader.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/react/App/StickyHeader.tsx b/app/react/App/StickyHeader.tsx index 655168dfc8..1b53be21b3 100644 --- a/app/react/App/StickyHeader.tsx +++ b/app/react/App/StickyHeader.tsx @@ -7,14 +7,14 @@ interface StickyHeaderProps { } // eslint-disable-next-line max-statements -const stickHeader = (self: any, stickyElementSelector: string, event: Event) => { +const eventHandler = (self: any, stickyElementSelector: string, event: Event) => { if (self && self.current && event.target) { const stickyElement = self.current.querySelector(stickyElementSelector) as HTMLElement; - const parentTop = (event.target as HTMLElement).getBoundingClientRect().top || 0; + const parentTop = (event.target as HTMLElement).getBoundingClientRect().top; self.current.classList.remove('sticky'); - const scrollerTop = (event.target as HTMLElement).scrollTop || 0; + const scrollerTop = (event.target as HTMLElement).scrollTop; const stickyTop = self.current.offsetTop || 0; - const stickyBottom = stickyTop + (self.current.offsetHeight || 0); + const stickyBottom = stickyTop + self.current.offsetHeight; if (stickyTop < scrollerTop && stickyBottom > scrollerTop) { self.current.classList.add('sticky'); @@ -31,11 +31,13 @@ const StickyHeader = (props: StickyHeaderProps) => { const body = document.querySelector(scrollElementSelector); useEffect(() => { body?.addEventListener('scroll', event => { - stickHeader(self, stickyElementSelector, event); + eventHandler(self, stickyElementSelector, event); }); return () => { - body?.removeEventListener('scroll', event => stickHeader(self, stickyElementSelector, event)); + body?.removeEventListener('scroll', event => + eventHandler(self, stickyElementSelector, event) + ); }; }); From 6359d854328a3390f521e6821bd6ef4efe94adaf Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 9 Sep 2022 13:19:33 +0300 Subject: [PATCH 20/37] Refactored sticky header --- app/react/App/StickyHeader.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/react/App/StickyHeader.tsx b/app/react/App/StickyHeader.tsx index 1b53be21b3..b2d1156538 100644 --- a/app/react/App/StickyHeader.tsx +++ b/app/react/App/StickyHeader.tsx @@ -8,16 +8,18 @@ interface StickyHeaderProps { // eslint-disable-next-line max-statements const eventHandler = (self: any, stickyElementSelector: string, event: Event) => { - if (self && self.current && event.target) { - const stickyElement = self.current.querySelector(stickyElementSelector) as HTMLElement; - const parentTop = (event.target as HTMLElement).getBoundingClientRect().top; - self.current.classList.remove('sticky'); - const scrollerTop = (event.target as HTMLElement).scrollTop; - const stickyTop = self.current.offsetTop || 0; - const stickyBottom = stickyTop + self.current.offsetHeight; + if (self && self.current && event.target && event.target instanceof Element) { + const { target } = event; + const { current } = self; + const stickyElement: HTMLElement = current.querySelector(stickyElementSelector); + const parentTop = target.getBoundingClientRect().top; + current.classList.remove('sticky'); + const scrollerTop = target.scrollTop; + const stickyTop = current.offsetTop || 0; + const stickyBottom = stickyTop + current.offsetHeight; if (stickyTop < scrollerTop && stickyBottom > scrollerTop) { - self.current.classList.add('sticky'); + current.classList.add('sticky'); if (stickyElement) { stickyElement.style.top = `${parentTop}px`; } From 45bdf02ad80b99211d816f0a8e0695ef3bcb8042 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 9 Sep 2022 13:26:13 +0300 Subject: [PATCH 21/37] Added a key translations migration --- .../107-add_system_key_translations/index.js | 60 +++++++++++++ .../107-add_system_key_translations.spec.js | 65 ++++++++++++++ .../specs/fixtures.js | 90 +++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 app/api/migrations/migrations/107-add_system_key_translations/index.js create mode 100644 app/api/migrations/migrations/107-add_system_key_translations/specs/107-add_system_key_translations.spec.js create mode 100644 app/api/migrations/migrations/107-add_system_key_translations/specs/fixtures.js diff --git a/app/api/migrations/migrations/107-add_system_key_translations/index.js b/app/api/migrations/migrations/107-add_system_key_translations/index.js new file mode 100644 index 0000000000..006bddc683 --- /dev/null +++ b/app/api/migrations/migrations/107-add_system_key_translations/index.js @@ -0,0 +1,60 @@ +/* +This migration is meant to be repeatable. +After copy pasting: + - change the contents of system_keys.csv to the new keyset + - change the file location in the readCsvToSystemKeys call + - change the tests, if necessary +*/ + +async function insertSystemKeys(db, newKeys) { + const translations = await db.collection('translations').find().toArray(); + const locales = translations.map(tr => tr.locale); + + const locToSystemContext = {}; + translations.forEach(tr => { + locToSystemContext[tr.locale] = tr.contexts.find(c => c.id === 'System'); + }); + const locToKeys = {}; + Object.entries(locToSystemContext).forEach(([loc, context]) => { + locToKeys[loc] = new Set(context.values.map(v => v.key)); + }); + + newKeys.forEach(row => { + const { key, value: optionalValue } = row; + + locales.forEach(loc => { + if (!locToKeys[loc].has(key)) { + const newValue = optionalValue || key; + locToSystemContext[loc].values.push({ key, value: newValue }); + locToKeys[loc].add(key); + } + }); + }); + + await Promise.all( + translations.map(tr => db.collection('translations').replaceOne({ _id: tr._id }, tr)) + ); +} + +export default { + delta: 107, + + reindex: false, + + name: 'add_system_key_translations', + + description: 'Adding missing translations for system keys.', + + async up(db) { + process.stdout.write(`${this.name}...\r\n`); + const systemKeys = [ + { + key: 'Collapse all', + }, + { + key: 'Expand all', + }, + ]; + await insertSystemKeys(db, systemKeys); + }, +}; diff --git a/app/api/migrations/migrations/107-add_system_key_translations/specs/107-add_system_key_translations.spec.js b/app/api/migrations/migrations/107-add_system_key_translations/specs/107-add_system_key_translations.spec.js new file mode 100644 index 0000000000..b6d86191d9 --- /dev/null +++ b/app/api/migrations/migrations/107-add_system_key_translations/specs/107-add_system_key_translations.spec.js @@ -0,0 +1,65 @@ +import { testingDB } from 'api/utils/testing_db'; +import migration from '../index.js'; +import { fixtures, templateId, defaultTemplateName, defaultTemplateTitle } from './fixtures.js'; + +const locales = ['en', 'es', 'hu']; +const newKeyValues = [ + { + key: 'Collapse all', + value: 'Collapse all', + }, + { key: 'Expand all', value: 'Expand all' }, +]; +const alreadyInAllContexts = { + key: 'Duplicated label', + en: 'Duplicated label', + es: 'Nombre duplicado', + hu: 'Ismétlődő címke', +}; + +describe('migration add_system_key_translations', () => { + beforeEach(async () => { + spyOn(process.stdout, 'write'); + await testingDB.setupFixturesAndContext(fixtures); + }); + + afterAll(async () => { + await testingDB.disconnect(); + }); + + it('should have a delta number', () => { + expect(migration.delta).toBe(107); + }); + + it('should append new keys, leave existing keys intact.', async () => { + await migration.up(testingDB.mongodb); + + const allTranslations = await testingDB.mongodb.collection('translations').find().toArray(); + function testKeyValue(key, value, locale, contextId) { + expect( + allTranslations + .find(tr => tr.locale === locale) + .contexts.find(c => c.id === contextId) + .values.find(v => v.key === key).value + ).toBe(value); + } + + newKeyValues.forEach(({ key, value }) => { + locales.forEach(loc => { + testKeyValue(key, value, loc, 'System'); + }); + }); + locales.forEach(loc => { + testKeyValue(alreadyInAllContexts.key, alreadyInAllContexts[loc], loc, 'System'); + }); + locales.forEach(loc => { + expect( + allTranslations + .find(tr => tr.locale === loc) + .contexts.find(c => c.id === templateId.toString()).values + ).toHaveLength(2); + testKeyValue(defaultTemplateName, defaultTemplateName, loc, templateId.toString()); + testKeyValue(defaultTemplateTitle, defaultTemplateTitle, loc, templateId.toString()); + }); + }); +}); diff --git a/app/api/migrations/migrations/107-add_system_key_translations/specs/fixtures.js b/app/api/migrations/migrations/107-add_system_key_translations/specs/fixtures.js new file mode 100644 index 0000000000..fd0204ac02 --- /dev/null +++ b/app/api/migrations/migrations/107-add_system_key_translations/specs/fixtures.js @@ -0,0 +1,90 @@ +import db from 'api/utils/testing_db'; + +const templateId = db.id(); +const defaultTemplateName = 'default template'; +const defaultTemplateTitle = 'Title'; + +//contexts +const commonContext = { + id: 'System', + label: 'User Interface', + type: 'Uwazi UI', + values: [ + { + key: 'existing-key-in-system', + value: 'existing-key-in-system', + }, + ], +}; +const templateContext = { + id: templateId.toString(), + label: defaultTemplateName, + type: 'Entity', + values: [ + { + key: defaultTemplateName, + value: defaultTemplateName, + }, + { + key: defaultTemplateTitle, + value: defaultTemplateTitle, + }, + ], +}; + +const fixtures = { + templates: [ + //default template name - correct + { + _id: templateId, + name: defaultTemplateName, + commonProperties: [{ name: 'title', label: defaultTemplateTitle, type: 'text' }], + properties: [], + }, + ], + translations: [ + { + _id: db.id(), + locale: 'es', + contexts: [ + { + ...commonContext, + values: commonContext.values.concat([ + { key: 'Drag properties here', value: 'Arrastra propiedades aquí' }, + { key: 'Duplicated label', value: 'Nombre duplicado' }, + ]), + }, + templateContext, + ], + }, + { + _id: db.id(), + locale: 'en', + contexts: [ + { + ...commonContext, + values: commonContext.values.concat([ + { key: 'Priority sorting', value: 'Priority sort' }, + { key: 'Duplicated label', value: 'Duplicated label' }, + ]), + }, + templateContext, + ], + }, + { + _id: db.id(), + locale: 'hu', + contexts: [ + { + ...commonContext, + values: commonContext.values.concat([ + { key: 'Duplicated label', value: 'Ismétlődő címke' }, + ]), + }, + templateContext, + ], + }, + ], +}; + +export { fixtures, templateId, defaultTemplateName, defaultTemplateTitle }; From 25685d93c35fa11cda019906bfb7016ee7423c8a Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 9 Sep 2022 13:29:26 +0300 Subject: [PATCH 22/37] Translate --- app/react/Documents/components/DocumentSidePanel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/react/Documents/components/DocumentSidePanel.js b/app/react/Documents/components/DocumentSidePanel.js index 947539d119..3709ab2401 100644 --- a/app/react/Documents/components/DocumentSidePanel.js +++ b/app/react/Documents/components/DocumentSidePanel.js @@ -354,14 +354,14 @@ class DocumentSidePanel extends Component { style={{ marginRight: '10px' }} onClick={() => this.collapseRelationships()} > - Collapse all + Collapse all From 5288278f75edcaca898164b724a347387fbc640d Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 9 Sep 2022 13:38:50 +0300 Subject: [PATCH 23/37] Refactored sticky header --- app/react/App/StickyHeader.tsx | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/app/react/App/StickyHeader.tsx b/app/react/App/StickyHeader.tsx index b2d1156538..0fd603dad7 100644 --- a/app/react/App/StickyHeader.tsx +++ b/app/react/App/StickyHeader.tsx @@ -6,17 +6,29 @@ interface StickyHeaderProps { stickyElementSelector: string; } +const getMeasurements = (target: HTMLElement, current: HTMLElement) => { + const parentTop = target.getBoundingClientRect().top; + const scrollerTop = target.scrollTop; + const stickyTop = current.offsetTop || 0; + const stickyBottom = stickyTop + current.offsetHeight; + return { + scrollerTop, + stickyTop, + stickyBottom, + parentTop, + }; +}; + // eslint-disable-next-line max-statements const eventHandler = (self: any, stickyElementSelector: string, event: Event) => { if (self && self.current && event.target && event.target instanceof Element) { - const { target } = event; const { current } = self; const stickyElement: HTMLElement = current.querySelector(stickyElementSelector); - const parentTop = target.getBoundingClientRect().top; current.classList.remove('sticky'); - const scrollerTop = target.scrollTop; - const stickyTop = current.offsetTop || 0; - const stickyBottom = stickyTop + current.offsetHeight; + const { scrollerTop, stickyTop, stickyBottom, parentTop } = getMeasurements( + event.target as HTMLElement, + current + ); if (stickyTop < scrollerTop && stickyBottom > scrollerTop) { current.classList.add('sticky'); From ab531252765ed0194eeeac299329ccbc6b8d35fb Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 9 Sep 2022 13:39:09 +0300 Subject: [PATCH 24/37] Removed comment --- app/react/App/StickyHeader.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/react/App/StickyHeader.tsx b/app/react/App/StickyHeader.tsx index 0fd603dad7..37a9c3ff8d 100644 --- a/app/react/App/StickyHeader.tsx +++ b/app/react/App/StickyHeader.tsx @@ -19,7 +19,6 @@ const getMeasurements = (target: HTMLElement, current: HTMLElement) => { }; }; -// eslint-disable-next-line max-statements const eventHandler = (self: any, stickyElementSelector: string, event: Event) => { if (self && self.current && event.target && event.target instanceof Element) { const { current } = self; From cacc374d07e1ffb5998595805966c5f3eceb949c Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 9 Sep 2022 15:27:35 +0300 Subject: [PATCH 25/37] Added a test for LibraryViewRelationships --- .../components/LibraryViewRelationships.tsx | 5 +- .../specs/LibraryViewRelationships.spec.tsx | 92 +++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 app/react/ConnectionsList/components/specs/LibraryViewRelationships.spec.tsx diff --git a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx index 7e53aa8e0a..ec138e9c9f 100644 --- a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx +++ b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx @@ -8,8 +8,8 @@ import { Item } from 'app/Layout'; import { Collapsible } from 'app/App/Collapsible'; import { StickyHeader } from 'app/App/StickyHeader'; import RelationshipMetadata from 'app/Relationships/components/RelationshipMetadata'; -import * as actions from '../../Relationships/actions/actions'; import LoadMoreRelationshipsButton from 'app/Relationships/components/LoadMoreRelationshipsButton'; +import * as actions from '../../Relationships/actions/actions'; interface LibraryViewRelationshipsProps { expanded: boolean; @@ -102,8 +102,9 @@ const createLabelGroups = ( -
    +
    {template && ( {`${relationTypes.find(r => r._id === template).name}`} diff --git a/app/react/ConnectionsList/components/specs/LibraryViewRelationships.spec.tsx b/app/react/ConnectionsList/components/specs/LibraryViewRelationships.spec.tsx new file mode 100644 index 0000000000..d5dab33ef7 --- /dev/null +++ b/app/react/ConnectionsList/components/specs/LibraryViewRelationships.spec.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { fromJS as Immutable } from 'immutable'; +import { Collapsible } from 'app/App/Collapsible'; +import { LibraryViewRelationshipsComp } from '../LibraryViewRelationships'; + +describe('LibraryViewRelationships', () => { + let component: ShallowWrapper; + let props: any; + const entityData = { + _id: 'entityid', + title: 'Some entity title', + sharedId: 'entitysharedid', + }; + + beforeEach(() => { + props = { + expanded: false, + parentEntity: Immutable({}), + searchResults: Immutable({}), + search: { order: 'desc', sort: 'creationDate', treatAs: 'number' }, + hubs: Immutable([ + { + hub: 'hubid', + leftRelationship: { template: 'tempId', hub: 'hubid', entity: 'entityid', entityData }, + rightRelationships: [ + { + template: 'tempId', + relationships: [ + { + entityData, + _id: 'rightrelationshipsid', + entity: entityData._id, + hub: 'hubid', + template: 'tempId', + }, + ], + }, + ], + }, + ]), + parseResults: () => {}, + relationTypes: [{ name: 'Some name', _id: 'tempId' }], + selectConnection: () => {}, + }; + }); + + const render = (innerProps = props) => { + component = shallow(); + }; + + it('should show labels if available', () => { + render(); + expect(component.find('.sidepanel-relationship-left-label').text()).toEqual('Some name'); + }); + + it('should render right relationships as collapsibles', () => { + render(); + const collapsibleProps = component.find(Collapsible).props(); + expect(collapsibleProps.header).toEqual('Some name'); + expect(collapsibleProps.headerInfo).toEqual('(1)'); + expect(collapsibleProps.collapse).toEqual(true); + }); + + it('should not show labels if none is available', () => { + const customProps = { + ...props, + hubs: Immutable([ + { + hub: 'hubid', + leftRelationship: { template: null, hub: 'hubid', entity: 'entityid', entityData }, + rightRelationships: [ + { + template: 'tempId', + relationships: [ + { + entityData, + _id: 'rightrelationshipsid', + entity: entityData._id, + hub: 'hubid', + template: 'tempId', + }, + ], + }, + ], + }, + ]), + }; + render(customProps); + expect(component.find('.sidepanel-relationship-left-label').exists()).toEqual(false); + }); +}); From af3b28cbd9303de8ccd1f1de4be4fb4155103f3e Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 12 Sep 2022 13:45:55 +0300 Subject: [PATCH 26/37] Fixed collapse and expand all bug --- app/react/Documents/components/DocumentSidePanel.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/react/Documents/components/DocumentSidePanel.js b/app/react/Documents/components/DocumentSidePanel.js index 3709ab2401..1aa47fc65d 100644 --- a/app/react/Documents/components/DocumentSidePanel.js +++ b/app/react/Documents/components/DocumentSidePanel.js @@ -126,11 +126,17 @@ class DocumentSidePanel extends Component { } collapseRelationships() { - this.setState({ relationshipsExpanded: false }); + // Toggles the states to force re-rendering + this.setState({ relationshipsExpanded: true }, () => + this.setState({ relationshipsExpanded: false }) + ); } expandRelationships() { - this.setState({ relationshipsExpanded: true }); + // Toggles the states to force re-rendering + this.setState({ relationshipsExpanded: false }, () => + this.setState({ relationshipsExpanded: true }) + ); } renderHeader(tab, doc, isEntity) { From 78fd60e64e4f7da7664b98d73db960a93a1c9984 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 12 Sep 2022 18:20:16 +0300 Subject: [PATCH 27/37] Refactored a little --- app/react/App/scss/layout/_sidepanel.scss | 1 - .../components/LibraryViewRelationships.tsx | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/react/App/scss/layout/_sidepanel.scss b/app/react/App/scss/layout/_sidepanel.scss index 0f0d0ac8af..3cfc77471c 100644 --- a/app/react/App/scss/layout/_sidepanel.scss +++ b/app/react/App/scss/layout/_sidepanel.scss @@ -249,7 +249,6 @@ $c-sidebar: $c-white; top: 0; z-index: 1; width: 100%; - color: blue; } } } diff --git a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx index ec138e9c9f..ca4ced07d9 100644 --- a/app/react/ConnectionsList/components/LibraryViewRelationships.tsx +++ b/app/react/ConnectionsList/components/LibraryViewRelationships.tsx @@ -90,6 +90,17 @@ const createRightRelationshipGroups = (
    ); +const renderLabel = (template: any, relationTypes: any) => + template ? ( + + {`${relationTypes.find(r => r._id === template).name}`} + + ) : ( + + + + ); + const createLabelGroups = ( hub: any, relationTypes: any[], @@ -105,11 +116,7 @@ const createLabelGroups = ( key={index} >
    - {template && ( - - {`${relationTypes.find(r => r._id === template).name}`} - - )} + {renderLabel(template, relationTypes)} {createRightRelationshipGroups( hub.get('rightRelationships'), relationTypes, From 3676494f1ad5a1f95376303a5af285a19a5f58ea Mon Sep 17 00:00:00 2001 From: Alberto Casado Torres Date: Tue, 13 Sep 2022 13:16:53 +0200 Subject: [PATCH 28/37] Replaced t function with Translate component --- app/react/Library/components/ViewDocButton.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/react/Library/components/ViewDocButton.js b/app/react/Library/components/ViewDocButton.js index 47a6265625..e765ee592d 100644 --- a/app/react/Library/components/ViewDocButton.js +++ b/app/react/Library/components/ViewDocButton.js @@ -5,7 +5,7 @@ import React, { Component } from 'react'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { Map } from 'immutable'; -import { t, I18NLink } from 'app/I18N'; +import { Translate, I18NLink } from 'app/I18N'; import { Icon } from 'UI'; import { actions } from 'app/BasicReducer'; import url from 'url'; @@ -56,7 +56,7 @@ export class ViewDocButton extends Component { className="btn btn-default btn-xs view-doc" onClick={this.onClick} > - {t('System', 'View')} + View ); } From d05c9a89ef96b23521371a10f5c62752faad42f2 Mon Sep 17 00:00:00 2001 From: Alberto Casado Torres Date: Tue, 13 Sep 2022 13:17:16 +0200 Subject: [PATCH 29/37] css tweaks for sidepanel relationships --- app/react/App/scss/layout/_sidepanel.scss | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/app/react/App/scss/layout/_sidepanel.scss b/app/react/App/scss/layout/_sidepanel.scss index 3cfc77471c..25669e7147 100644 --- a/app/react/App/scss/layout/_sidepanel.scss +++ b/app/react/App/scss/layout/_sidepanel.scss @@ -178,6 +178,7 @@ $c-sidebar: $c-white; border-top: 0; border-left: 0; border-right: 0; + border-bottom: 0; border-radius: 0; box-shadow: none; &:before { @@ -262,8 +263,8 @@ $c-sidebar: $c-white; align-items: flex-start; padding: 8px 16px; gap: 9px; - background: #eeeeee; - border: 1px solid #d7d7dc; + background: #d7d7d7; + border: 1px solid #d7d7d7; } .sidepanel-relationship-right { padding: 7px 10px 7px 10px; @@ -296,6 +297,23 @@ $c-sidebar: $c-white; gap: 9px; .item-document { z-index: -1; + padding-bottom: 0; + min-height: 0; + + .item-info { + padding: 5px 10px; + } + .item-metadata { + display: none; + } + .item-actions { + padding: 0; + height: auto; + padding: 0 10px 10px; + -webkit-align-items: center; + align-items: center; + position: relative; + } } } } From e609a216279ce83cd8082054aeea92703e11d32a Mon Sep 17 00:00:00 2001 From: Alberto Casado Torres Date: Tue, 13 Sep 2022 13:17:50 +0200 Subject: [PATCH 30/37] Showing View button and hidding edit/delete for the metadata panel of relationships --- .../components/RelationshipMetadata.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/app/react/Relationships/components/RelationshipMetadata.js b/app/react/Relationships/components/RelationshipMetadata.js index 85bde12b4e..6d49e08179 100644 --- a/app/react/Relationships/components/RelationshipMetadata.js +++ b/app/react/Relationships/components/RelationshipMetadata.js @@ -8,6 +8,7 @@ import { createSelector } from 'reselect'; import { Icon } from 'UI'; import { ShowMetadata, MetadataFormButtons, MetadataForm, actions } from 'app/Metadata'; +import ViewDocButton from 'app/Library/components/ViewDocButton'; import SidePanel from 'app/Layout/SidePanel'; import { CopyFromEntity } from 'app/Metadata/components/CopyFromEntity'; import { api as entitiesAPI } from 'app/Entities'; @@ -132,17 +133,7 @@ class RelationshipMetadata extends Component { )}
    {this.renderBody()}
    - {!this.state.copyFrom && ( - - )} + {!this.state.copyFrom && }
    ); From b59e96e6a626e78cc796556f8d5a9e6188af22b7 Mon Sep 17 00:00:00 2001 From: Alberto Casado Torres Date: Tue, 13 Sep 2022 13:28:41 +0200 Subject: [PATCH 31/37] Propper iccons for the view buttons --- .../Documents/components/DocumentSidePanel.js | 2 +- .../components/RelationshipMetadata.js | 21 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/react/Documents/components/DocumentSidePanel.js b/app/react/Documents/components/DocumentSidePanel.js index 1aa47fc65d..fd23181e4d 100644 --- a/app/react/Documents/components/DocumentSidePanel.js +++ b/app/react/Documents/components/DocumentSidePanel.js @@ -351,7 +351,7 @@ class DocumentSidePanel extends Component {
    - +