From 171b9247fca6e9879052bf7a634356eb6cc997a2 Mon Sep 17 00:00:00 2001 From: vmonakhov Date: Wed, 4 Sep 2024 15:55:29 +0300 Subject: [PATCH] Perspective view better pagination -- https://github.com/ispras/lingvodoc-react/issues/1133 (#1138) * Perspective view better pagination -- https://github.com/ispras/lingvodoc-react/issues/1133 (#1134) --- src/components/LexicalEntry/index.js | 28 +-- src/components/LexicalEntryByIds/component.js | 4 +- src/components/Merge/Settings.js | 2 +- src/components/PerspectiveView/Cell.js | 13 +- src/components/PerspectiveView/Row.js | 11 +- src/components/PerspectiveView/TableBody.js | 9 +- src/components/PerspectiveView/index.js | 231 ++++++++---------- src/ducks/perspective.js | 6 +- src/pages/Perspective/component.js | 41 +++- src/pages/Perspective/index.js | 4 +- src/pages/Perspective/style.scss | 24 ++ 11 files changed, 205 insertions(+), 168 deletions(-) diff --git a/src/components/LexicalEntry/index.js b/src/components/LexicalEntry/index.js index 57ab86fc..49a8ee05 100644 --- a/src/components/LexicalEntry/index.js +++ b/src/components/LexicalEntry/index.js @@ -128,7 +128,7 @@ class Entities extends React.Component { update_check() { /* Checking if we need to manually update perspective data. */ - const { entry, client, perspectiveId, entitiesMode } = this.props; + const { entry, client, perspectiveId, entitiesMode, queryArgs } = this.props; const data_entities = client.readQuery({ query: lexicalEntryQuery, @@ -138,16 +138,15 @@ class Entities extends React.Component { } }); - const data_perspective = client.readQuery({ + const data_perspective = queryArgs + ? client.readQuery({ query: queryLexicalEntries, - variables: { - id: perspectiveId, - entitiesMode - } - }); + variables: queryArgs + }) + : { perspective: { perspective_page: { lexical_entries: [] }}}; const { - perspective: { lexical_entries } + perspective: { perspective_page: { lexical_entries } } } = data_perspective; const entry_id_str = id2str(entry.id); @@ -179,15 +178,12 @@ class Entities extends React.Component { /* If for some reason queryLexicalEntries failed to update (e.g. when there are several thousand * entries and Apollo GraphQL cache glitches), we update it manually. */ - if (change_flag) { + if (change_flag && queryArgs) { lexical_entry.entities = data_entities.lexicalentry.entities; client.writeQuery({ query: queryLexicalEntries, - variables: { - id: perspectiveId, - entitiesMode - }, + variables: queryArgs, data: data_perspective }); } @@ -481,11 +477,13 @@ Entities.propTypes = { resetCheckedRow: PropTypes.func, resetCheckedColumn: PropTypes.func, resetCheckedAll: PropTypes.func, - reRender: PropTypes.func + reRender: PropTypes.func, + queryArgs: PropTypes.object }; Entities.defaultProps = { - parentEntity: null + parentEntity: null, + queryArgs: null }; export default compose( diff --git a/src/components/LexicalEntryByIds/component.js b/src/components/LexicalEntryByIds/component.js index 50dfe8c5..41ec54ff 100644 --- a/src/components/LexicalEntryByIds/component.js +++ b/src/components/LexicalEntryByIds/component.js @@ -144,7 +144,7 @@ class Perspective extends PureComponent { mode={mode} id={id} entriesIds={entriesIds} - filter={perspective.filter} + filter={perspective.filter.value} submitFilter={submitFilter} toggleMode={this.toggleMode} /> @@ -154,7 +154,7 @@ class Perspective extends PureComponent { entitiesMode={entitiesMode} entriesIds={entriesIds} page={page} - filter={perspective.filter} + filter={perspective.filter.value} className="content" changePage={this.changePage} /> diff --git a/src/components/Merge/Settings.js b/src/components/Merge/Settings.js index 46853039..d0459fc5 100644 --- a/src/components/Merge/Settings.js +++ b/src/components/Merge/Settings.js @@ -99,7 +99,7 @@ class MergeSettings extends React.Component { const { dataLexicalEntries: { - perspective: { lexical_entries: entries } + perspective: { perspective_page: { lexical_entries: entries }} } } = this.props; diff --git a/src/components/PerspectiveView/Cell.js b/src/components/PerspectiveView/Cell.js index 9e3c2534..cab92369 100644 --- a/src/components/PerspectiveView/Cell.js +++ b/src/components/PerspectiveView/Cell.js @@ -24,7 +24,8 @@ const Cell = ({ mode, entitiesMode, disabled, - reRender + reRender, + queryArgs // eslint-disable-next-line arrow-body-style }) => { return ( @@ -46,6 +47,7 @@ const Cell = ({ entitiesMode={entitiesMode} disabled={disabled} reRender={reRender} + queryArgs={queryArgs} /> ); @@ -66,11 +68,13 @@ Cell.propTypes = { resetCheckedRow: PropTypes.func, resetCheckedColumn: PropTypes.func, resetCheckedAll: PropTypes.func, - reRender: PropTypes.func + reRender: PropTypes.func, + queryArgs: PropTypes.object }; Cell.defaultProps = { - disabled: undefined + disabled: undefined, + queryArgs: null }; export default onlyUpdateForKeys([ @@ -81,5 +85,6 @@ export default onlyUpdateForKeys([ "column", "checkedRow", "checkedColumn", - "checkedAll" + "checkedAll", + "queryArgs" ])(Cell); \ No newline at end of file diff --git a/src/components/PerspectiveView/Row.js b/src/components/PerspectiveView/Row.js index ce95ea3f..29f2b8d5 100644 --- a/src/components/PerspectiveView/Row.js +++ b/src/components/PerspectiveView/Row.js @@ -29,6 +29,7 @@ const Row = ({ resetCheckedColumn, resetCheckedAll, reRender, + queryArgs, /* eslint-disable react/prop-types */ showEntryId, selectDisabled, @@ -97,6 +98,7 @@ const Row = ({ entitiesMode={entitiesMode} disabled={disabled_flag} reRender={reRender} + queryArgs={queryArgs} /> ))} @@ -149,7 +151,8 @@ Row.propTypes = { resetCheckedRow: PropTypes.func, resetCheckedColumn: PropTypes.func, resetCheckedAll: PropTypes.func, - reRender: PropTypes.func + reRender: PropTypes.func, + queryArgs: PropTypes.object }; Row.defaultProps = { @@ -166,7 +169,8 @@ Row.defaultProps = { resetCheckedRow: () => {}, resetCheckedColumn: () => {}, resetCheckedAll: () => {}, - reRender: () => console.debug('Fake refetch') + reRender: () => console.debug('Fake refetch'), + queryArgs: null }; export default onlyUpdateForKeys([ @@ -178,5 +182,6 @@ export default onlyUpdateForKeys([ "checkedRow", "checkedColumn", "checkedAll", - "columns" + "columns", + "queryArgs" ])(Row); \ No newline at end of file diff --git a/src/components/PerspectiveView/TableBody.js b/src/components/PerspectiveView/TableBody.js index a87bcf5e..c625e641 100644 --- a/src/components/PerspectiveView/TableBody.js +++ b/src/components/PerspectiveView/TableBody.js @@ -32,7 +32,8 @@ TableBody.propTypes = { resetCheckedRow: PropTypes.func, resetCheckedColumn: PropTypes.func, resetCheckedAll: PropTypes.func, - reRender: PropTypes.func + reRender: PropTypes.func, + queryArgs: PropTypes.object }; TableBody.defaultProps = { @@ -49,7 +50,8 @@ TableBody.defaultProps = { resetCheckedRow: () => {}, resetCheckedColumn: () => {}, resetCheckedAll: () => {}, - reRender: () => console.log('Fake refetch') + reRender: () => console.log('Fake refetch'), + queryArgs: null }; export default onlyUpdateForKeys([ @@ -60,5 +62,6 @@ export default onlyUpdateForKeys([ "selectedRows", "checkedRow", "checkedColumn", - "checkedAll" + "checkedAll", + "queryArgs" ])(TableBody); \ No newline at end of file diff --git a/src/components/PerspectiveView/index.js b/src/components/PerspectiveView/index.js index d4a9bc92..ca94fc78 100644 --- a/src/components/PerspectiveView/index.js +++ b/src/components/PerspectiveView/index.js @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { connect } from "react-redux"; import { Button, Dimmer, Header, Icon, Table } from "semantic-ui-react"; import { gql } from "@apollo/client"; -import { graphql } from "@apollo/client/react/hoc"; +import { graphql, withApollo } from "@apollo/client/react/hoc"; import { drop, flow, isEqual, reverse, take } from "lodash"; import PropTypes from "prop-types"; import { branch, compose, renderComponent } from "recompose"; @@ -65,30 +65,56 @@ export const queryPerspective = gql` * src/components/GroupingTagModal/graphql.js accordingly, see comment there. */ export const queryLexicalEntries = gql` - query queryPerspective2($id: LingvodocID!, $entitiesMode: String!) { + query queryPerspective2( + $id: LingvodocID!, + $entitiesMode: String!, + $filter: String, + $sortingField: LingvodocID, + $isEditMode: Boolean, + $isCaseSens: Boolean, + $isAscending: Boolean, + $isRegexp: Boolean, + $offset: Int, + $limit: Int, + $createdEntries: [LingvodocID]) { + perspective(id: $id) { id translations - lexical_entries(mode: $entitiesMode) { - id - parent_id - created_at - marked_for_deletion - entities(mode: $entitiesMode) { + perspective_page( + mode: $entitiesMode, + filter: $filter, + sort_by_field: $sortingField, + is_edit_mode: $isEditMode, + is_case_sens: $isCaseSens, + is_ascending: $isAscending, + is_regexp: $isRegexp, + offset: $offset, + limit: $limit, + created_entries: $createdEntries) { + + entries_total + lexical_entries { id parent_id - field_id - link_id - self_id created_at - locale_id - content - published - accepted - additional_metadata { - link_perspective_id + marked_for_deletion + entities(mode: $entitiesMode) { + id + parent_id + field_id + link_id + self_id + created_at + locale_id + content + published + accepted + additional_metadata { + link_perspective_id + } + is_subject_for_parsing } - is_subject_for_parsing } } } @@ -321,9 +347,31 @@ class P extends React.Component { createdEntries, selectedEntries, user, - reRender + reRender, + client, + isEditMode, + isCaseSens, + isRegexp, + isAscending, + sortingField, + limit, + offset } = this.props; + const query_args = { + id, + entitiesMode, + filter, + isEditMode, + isCaseSens, + isRegexp, + isAscending, + sortingField, + limit, + offset, + createdEntries + } + const { loading, error } = data; if (loading || (!loading && !error && !data.perspective)) { @@ -336,7 +384,8 @@ class P extends React.Component { ); } - const lexicalEntries = !error ? data.perspective.lexical_entries : []; + const lexicalEntries = !error ? data.perspective.perspective_page.lexical_entries : []; + const entriesTotal = !error ? data.perspective.perspective_page.entries_total : 0; const addEntry = () => { createLexicalEntry({ @@ -347,10 +396,7 @@ class P extends React.Component { refetchQueries: [ { query: queryLexicalEntries, - variables: { - id, - entitiesMode - } + variables: query_args } ] }).then(({ data: d }) => { @@ -372,10 +418,7 @@ class P extends React.Component { refetchQueries: [ { query: queryLexicalEntries, - variables: { - id, - entitiesMode - } + variables: query_args } ] }).then(() => { @@ -391,10 +434,7 @@ class P extends React.Component { refetchQueries: [ { query: queryLexicalEntries, - variables: { - id, - entitiesMode - } + variables: query_args } ] }).then(() => { @@ -406,81 +446,6 @@ class P extends React.Component { openNewModal(ApproveModal, { perspectiveId: id, mode }); }; - /* Basic case-insensitive, case-sensitive compare. */ - const ci_cs_compare = (str_a, str_b) => { - const result = str_a.toLowerCase().localeCompare(str_b.toLowerCase(), undefined, { numeric: true }); - return result ? result : str_a.localeCompare(str_b, undefined, { numeric: true }); - }; - - const entitySortKeys = new Map(); - const processEntries = flow([ - // remove empty lexical entries, if not in edit mode - es => (mode !== "edit" ? es.filter(e => e.entities.length > 0) : es), - // apply filtering - es => - !!filter && filter.length > 0 - ? es.filter( - entry => - !!entry.entities.find( - entity => typeof entity.content === "string" && entity.content.indexOf(filter) >= 0 - ) - ) - : es, - // apply sorting - es => { - // no sorting required - if (!sortByField) { - return es; - } - const { field, order } = sortByField; - - /* Getting a sort key for each entry. */ - - for (const entry of es) { - const entities = entry.entities.filter(entity => isEqual(entity.field_id, field)); - - entities.sort( - (ea, eb) => ci_cs_compare(ea.content || "", eb.content || "") || ea.id[0] - eb.id[0] || ea.id[1] - eb.id[1] - ); - - entitySortKeys.set( - entry, - entities.length > 0 && entities[0].content ? entities[0].content : `${entities.length}` - ); - } - - es.sort( - (ea, eb) => - ci_cs_compare(entitySortKeys.get(ea), entitySortKeys.get(eb)) || ea.id[0] - eb.id[0] || ea.id[1] - eb.id[1] - ); - - return order === "a" ? es : reverse(es); - } - ]); - - const created_id_str_set = {}; - - for (const entry of createdEntries) { - created_id_str_set[id2str(entry.id)] = null; - } - - const newEntries = processEntries( - lexicalEntries.filter(e => Object.prototype.hasOwnProperty.call(created_id_str_set, id2str(e.id))) - ); - - const entries = processEntries(lexicalEntries.slice()); - - const pageEntries = - entries.length > ROWS_PER_PAGE ? take(drop(entries, ROWS_PER_PAGE * (page - 1)), ROWS_PER_PAGE) : entries; - - // Put newly created entries at the top of page. - const e = [ - ...newEntries, - ...pageEntries.filter( - pageEntry => !Object.prototype.hasOwnProperty.call(created_id_str_set, id2str(pageEntry.id)) - ) - ]; - // join fields and columns // { // column_id = column.id @@ -516,11 +481,7 @@ class P extends React.Component { const selectedRows = []; const selectedColumns = []; - const items = e; - - const checkedRow = this.state.checkedRow; - const checkedColumn = this.state.checkedColumn; - const checkedAll = this.state.checkedAll; + const { checkedRow, checkedColumn, checkedAll } = this.state; /* isTableLanguagesPublish */ if (isTableLanguagesPublish) { @@ -529,16 +490,16 @@ class P extends React.Component { if (checkedAll) { if (checkedAll.checkedAll) { - items.forEach(item => { + lexicalEntries.forEach(item => { selectedRowsSet.add(item.id); }); } else { - items.forEach(item => { + lexicalEntries.forEach(item => { selectedRowsSet.delete(item.id); }); } } else { - items.forEach(item => { + lexicalEntries.forEach(item => { const allRowsSelected = item.entities.every(i => { return i.published; }); @@ -566,7 +527,7 @@ class P extends React.Component { fields.forEach(column => { const elems = []; - items.forEach(item => { + lexicalEntries.forEach(item => { const columnEntities = item.entities.filter(i => { return JSON.stringify(i.field_id) === JSON.stringify(column.id); }); @@ -605,8 +566,7 @@ class P extends React.Component { /* /isTableLanguagesPublish */ function* allEntriesGenerator() { - yield* newEntries; - yield* entries; + yield* lexicalEntries; } return ( @@ -648,7 +608,7 @@ class P extends React.Component { +
+ + +
); @@ -1193,7 +1215,10 @@ const ModeSelector = compose( launchValency={launchValency} /> - + ); @@ -1349,7 +1374,9 @@ const Perspective = ({ mode={mode} id={id} baseUrl={baseUrl} - filter={perspective.filter} + filter={perspective.filter.value} + isCaseSens={perspective.filter.isCaseSens} + isRegexp={perspective.filter.isRegexp} submitFilter={submitFilter} openCognateAnalysisModal={openCognateAnalysisModal} openPhonemicAnalysisModal={openPhonemicAnalysisModal} @@ -1371,7 +1398,9 @@ const Perspective = ({ mode={mode} entitiesMode={info.entitiesMode} page={page} - filter={perspective.filter} + filter={perspective.filter.value} + isCaseSens={perspective.filter.isCaseSens} + isRegexp={perspective.filter.isRegexp} className="content" activeDndProvider={dndProvider} /> diff --git a/src/pages/Perspective/index.js b/src/pages/Perspective/index.js index 2c289e50..1046d2ed 100644 --- a/src/pages/Perspective/index.js +++ b/src/pages/Perspective/index.js @@ -16,8 +16,8 @@ function init({ location }) { return request(getParams(location)); } -function submitFilter(value) { - return setFilter(value); +function submitFilter(value, isCaseSens, isRegexp) { + return setFilter({value, isCaseSens, isRegexp}); } function openCognateAnalysisModal(perspectiveId, mode = "") { diff --git a/src/pages/Perspective/style.scss b/src/pages/Perspective/style.scss index a5ceb149..cbe9b621 100644 --- a/src/pages/Perspective/style.scss +++ b/src/pages/Perspective/style.scss @@ -693,3 +693,27 @@ } /* /lingvo perspective component text */ + +/* lingvo searching options */ +.lingvo-search-entities__checkboxes { + margin-left: 1.5em; + border: gray solid; + border-radius: 15px; + padding: 0.25em; + + .lingvo-checkbox_labeled { + margin-right: 1.5em; + padding: 0.25em; + + & label { + color: white !important; + font-size: 1.2em !important; + font-weight: bold; + + &:hover { + text-decoration: underline; + } + } + } +} +/* /lingvo searching options */