From 96c99590ad74e4f30d167b958c277239c1db62ce Mon Sep 17 00:00:00 2001 From: vmonakhov Date: Sun, 11 Aug 2024 17:23:32 +0300 Subject: [PATCH] Perspective view better pagination -- https://github.com/ispras/lingvodoc-react/issues/1133 (#1134) * init * next steps * PerspectivePage methods moved to DictionaryPerspective * next steps * refactoring * regexp * some workaround * some workaround * fixes and cleanup * more correct filtering * next steps * refactoring * created entries calculating --- src/components/LexicalEntry/index.js | 22 +-- src/components/PerspectiveView/index.js | 246 ++++++++++++------------ 2 files changed, 129 insertions(+), 139 deletions(-) diff --git a/src/components/LexicalEntry/index.js b/src/components/LexicalEntry/index.js index 57ab86fc..f2983426 100644 --- a/src/components/LexicalEntry/index.js +++ b/src/components/LexicalEntry/index.js @@ -7,7 +7,7 @@ import PropTypes from "prop-types"; import { compose, pure } from "recompose"; import { queryCounter } from "backend"; -import { queryLexicalEntries } from "components/PerspectiveView"; +import { queryLexicalEntries, fragmentPerspectivePageVariables } from "components/PerspectiveView"; import { compositeIdToString, compositeIdToString as id2str } from "utils/compositeId"; import GroupingTag from "./GroupingTag"; @@ -138,17 +138,20 @@ class Entities extends React.Component { } }); + // Current variables for queryLexicalEntries are stored as fragment + const data_perspective_variables = client.readFragment(fragmentPerspectivePageVariables); + const data_perspective = client.readQuery({ query: queryLexicalEntries, - variables: { - id: perspectiveId, - entitiesMode - } + variables: data_perspective_variables ?? {} }); const { - perspective: { lexical_entries } - } = data_perspective; + perspective: { perspective_page: { lexical_entries } } + } = data_perspective ? data_perspective : { + // TODO: here is a dirty workaround, readQuery above should have + // full set of variables and work correctly + perspective: { perspective_page: { lexical_entries: [] } } }; const entry_id_str = id2str(entry.id); @@ -184,10 +187,7 @@ class Entities extends React.Component { client.writeQuery({ query: queryLexicalEntries, - variables: { - id: perspectiveId, - entitiesMode - }, + variables: data_perspective_variables ?? {}, data: data_perspective }); } diff --git a/src/components/PerspectiveView/index.js b/src/components/PerspectiveView/index.js index d4a9bc92..e48a8429 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,36 +65,79 @@ 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 } } } } `; +export const fragmentPerspectivePageVariables = { + id: 'PerspectivePageVariables:', + fragment: gql` + fragment current on PerspectivePageVariables { + id + entitiesMode + filter + isRegexp + isCaseSens + isEditMode + isAscending + sortingField + limit + offset + }` +}; + const createLexicalEntryMutation = gql` mutation createLexicalEntry($id: LingvodocID!, $entitiesMode: String!) { create_lexicalentry(perspective_id: $id) { @@ -321,9 +364,37 @@ 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 + } + + // TODO: doesn't work yet + client.writeFragment({ + ...fragmentPerspectivePageVariables, + data: query_args + }); + const { loading, error } = data; if (loading || (!loading && !error && !data.perspective)) { @@ -336,7 +407,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 +419,7 @@ class P extends React.Component { refetchQueries: [ { query: queryLexicalEntries, - variables: { - id, - entitiesMode - } + variables: query_args } ] }).then(({ data: d }) => { @@ -372,10 +441,7 @@ class P extends React.Component { refetchQueries: [ { query: queryLexicalEntries, - variables: { - id, - entitiesMode - } + variables: query_args } ] }).then(() => { @@ -391,10 +457,7 @@ class P extends React.Component { refetchQueries: [ { query: queryLexicalEntries, - variables: { - id, - entitiesMode - } + variables: query_args } ] }).then(() => { @@ -406,81 +469,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 +504,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 +513,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 +550,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 +589,7 @@ class P extends React.Component { /* /isTableLanguagesPublish */ function* allEntriesGenerator() { - yield* newEntries; - yield* entries; + yield* lexicalEntries; } return ( @@ -648,7 +631,7 @@ class P extends React.Component {