From 4df2970f9f2d411ff7aee1f8b0b60b7dc0c85faa Mon Sep 17 00:00:00 2001 From: Jess Telford Date: Thu, 2 May 2019 10:36:57 +1000 Subject: [PATCH] Load Content Blocks in parallel to data fetching on Field view --- packages/admin-ui/client/pages/Item/index.js | 179 +++++++++++------- packages/admin-ui/package.json | 1 + packages/fields/src/Controller.js | 4 + .../src/types/Content/views/Controller.js | 2 + 4 files changed, 115 insertions(+), 71 deletions(-) diff --git a/packages/admin-ui/client/pages/Item/index.js b/packages/admin-ui/client/pages/Item/index.js index 3a1178b1005..ac21d5b4768 100644 --- a/packages/admin-ui/client/pages/Item/index.js +++ b/packages/admin-ui/client/pages/Item/index.js @@ -1,11 +1,12 @@ /** @jsx jsx */ import { jsx } from '@emotion/core'; -import { Component, Fragment, useMemo, useCallback } from 'react'; +import { Component, Fragment, Suspense, useMemo, useCallback } from 'react'; import styled from '@emotion/styled'; import { Mutation, Query } from 'react-apollo'; import { withRouter } from 'react-router-dom'; import { withToastManager } from 'react-toast-notifications'; import memoizeOne from 'memoize-one'; +import isPromise from 'p-is-promise'; import { Container } from '@arch-ui/layout'; import { Button } from '@arch-ui/button'; @@ -274,6 +275,37 @@ const ItemNotFound = ({ adminPath, errorMessage, list }) => ( ); +const PreLoadFields = ({ children, list, loadField }) => ( + }> + + {() => { + const initialisationPromises = list.fields + .map((item, index) => { + try { + loadField(item, index, list); + } catch (loadingPromiseOrError) { + // An actual error was thrown, so we want to bubble that up + if (!isPromise(loadingPromiseOrError)) { + throw loadingPromiseOrError; + } + // Return a Suspense promise + return loadingPromiseOrError; + } + }) + .filter(Boolean); + + if (initialisationPromises.length) { + // Trigger the suspense boundary and wait for all fields in + // parallel. + throw Promise.all(initialisationPromises); + } + + return children(); + }} + + +); + const ItemPage = ({ list, itemId, adminPath, getListByKey, toastManager }) => { const itemQuery = list.getItemQuery(itemId); return ( @@ -281,76 +313,81 @@ const ItemPage = ({ list, itemId, adminPath, getListByKey, toastManager }) => { {/* network-only because the data we mutate with is important for display in the UI, and may be different than what's in the cache */} - {({ loading, error, data, refetch }) => { - if (loading) return ; - - // Only show error page if there is no data - // (ie; there could be partial data + partial errors) - if ( - error && - (!data || - !data[list.gqlNames.itemQueryName] || - !Object.keys(data[list.gqlNames.itemQueryName]).length) - ) { - return ( - - {list.singular} not found - - - ); - } - - const item = list.deserializeItemData(data[list.gqlNames.itemQueryName]); - const itemErrors = deconstructErrorsToDataShape(error)[list.gqlNames.itemQueryName] || {}; - - return item ? ( -
- - {item._label_} - {list.singular} - - - { - const [title, ...rest] = updateError.message.split(/\:/); - const toastContent = rest.length ? ( -
- {title.trim()} -
{rest.join('').trim()}
-
- ) : ( - updateError.message - ); - - toastManager.add(toastContent, { - appearance: 'error', - }); - }} - > - {(updateItem, { loading: updateInProgress, error: updateError }) => { - return ( - - ); - }} -
-
-
- ) : ( - - ); - }} + {({ loading, error, data, refetch }) => ( + field.initFieldView()}> + {() => { + if (loading) return ; + + // Only show error page if there is no data + // (ie; there could be partial data + partial errors) + if ( + error && + (!data || + !data[list.gqlNames.itemQueryName] || + !Object.keys(data[list.gqlNames.itemQueryName]).length) + ) { + return ( + + {list.singular} not found + + + ); + } + + const item = list.deserializeItemData(data[list.gqlNames.itemQueryName]); + const itemErrors = + deconstructErrorsToDataShape(error)[list.gqlNames.itemQueryName] || {}; + + return item ? ( +
+ + {item._label_} - {list.singular} + + + { + const [title, ...rest] = updateError.message.split(/\:/); + const toastContent = rest.length ? ( +
+ {title.trim()} +
{rest.join('').trim()}
+
+ ) : ( + updateError.message + ); + + toastManager.add(toastContent, { + appearance: 'error', + }); + }} + > + {(updateItem, { loading: updateInProgress, error: updateError }) => { + return ( + + ); + }} +
+
+
+ ) : ( + + ); + }} +
+ )}
); diff --git a/packages/admin-ui/package.json b/packages/admin-ui/package.json index feeb00ee1b5..5c7fc4db45c 100644 --- a/packages/admin-ui/package.json +++ b/packages/admin-ui/package.json @@ -70,6 +70,7 @@ "lodash.debounce": "^4.0.8", "lodash.set": "^4.3.2", "memoize-one": "^4.0.2", + "p-is-promise": "^2.1.0", "preconstruct": "0.0.60", "prop-types": "^15.7.2", "raf-schd": "^4.0.0", diff --git a/packages/fields/src/Controller.js b/packages/fields/src/Controller.js index 4012858f817..f5bb2745810 100644 --- a/packages/fields/src/Controller.js +++ b/packages/fields/src/Controller.js @@ -45,4 +45,8 @@ export default class FieldController { // eslint-disable-next-line no-unused-vars getDefaultValue = data => this.config.defaultValue || ''; + + initCellView = () => {}; + initFieldView = () => {}; + initFilterView = () => {}; } diff --git a/packages/fields/src/types/Content/views/Controller.js b/packages/fields/src/types/Content/views/Controller.js index a5e03cfac30..24155bd3a4a 100644 --- a/packages/fields/src/types/Content/views/Controller.js +++ b/packages/fields/src/types/Content/views/Controller.js @@ -124,4 +124,6 @@ export default class ContentController extends TextController { } `; }; + + initFieldView = () => this.getBlocks(); }