Skip to content

Commit

Permalink
Show loading spinner while loading views in List Table
Browse files Browse the repository at this point in the history
  • Loading branch information
jesstelford committed May 3, 2019
1 parent 061aedf commit 13b7d4e
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 91 deletions.
1 change: 1 addition & 0 deletions .changeset/f4c4c52c/changes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "releases": [{ "name": "@keystone-alpha/admin-ui", "type": "patch" }], "dependents": [] }
1 change: 1 addition & 0 deletions .changeset/f4c4c52c/changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Show loading spinner while loading views in List Table
101 changes: 79 additions & 22 deletions packages/admin-ui/client/components/ListTable.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/** @jsx jsx */
import { jsx } from '@emotion/core';
import React, { Component } from 'react';
import React, { Component, Suspense } from 'react';
import styled from '@emotion/styled';
import { Link } from 'react-router-dom';

import { captureSuspensePromises } from '@keystone-alpha/utils';
import { DiffIcon, KebabHorizontalIcon, LinkIcon, ShieldIcon, TrashcanIcon } from '@arch-ui/icons';
import { colors, gridSize } from '@arch-ui/theme';
import { alpha } from '@arch-ui/color-utils';
Expand All @@ -15,6 +16,10 @@ import { Card } from '@arch-ui/card';
import DeleteItemModal from './DeleteItemModal';
import { copyToClipboard } from '../util';
import { useListSort } from '../pages/List/dataHooks';
import PageLoading from './PageLoading';
import { NoResults } from './NoResults';

const Render = ({ children }) => children();

// Styled Components
const Table = styled('table')({
Expand Down Expand Up @@ -214,12 +219,6 @@ class ListRow extends Component {
{fields.map(field => {
const { path } = field;

const isLoading = !item.hasOwnProperty(path);

if (isLoading) {
return <BodyCell key={path} />; // TODO: Better loading state?
}

if (itemErrors[path] instanceof Error && itemErrors[path].name === 'AccessDeniedError') {
return (
<BodyCell key={path}>
Expand Down Expand Up @@ -292,6 +291,12 @@ class ListRow extends Component {
}
}

const SingleCell = ({ columns, children }) => (
<tr>
<td colSpan={columns}>{children}</td>
</tr>
);

export default function ListTable(props) {
const {
adminPath,
Expand All @@ -304,18 +309,24 @@ export default function ListTable(props) {
onChange,
onSelectChange,
selectedItems,
currentPage,
filters,
search,
} = props;

const [sortBy, onSortChange] = useListSort(list.key);

const handleSelectAll = () => {
const allSelected = items.length === selectedItems.length;
const allSelected = items && items.length === selectedItems.length;
const value = allSelected ? [] : items.map(i => i.id);
onSelectChange(value);
};

const cypressId = 'ks-list-table';

// +2 because of check-boxes on left, and overflow menu on right
const columns = fields.length + 2;

return (
<Card css={{ marginBottom: '3em' }}>
<Table id={cypressId} style={{ tableLayout: isFullWidth ? null : 'fixed' }}>
Expand All @@ -331,7 +342,7 @@ export default function ListTable(props) {
<HeaderCell>
<div css={{ position: 'relative', top: 3 }}>
<CheckboxPrimitive
checked={items.length === selectedItems.length}
checked={items && items.length === selectedItems.length}
onChange={handleSelectAll}
tabIndex="0"
/>
Expand All @@ -352,19 +363,65 @@ export default function ListTable(props) {
</tr>
</thead>
<tbody>
{items.map((item, itemIndex) => (
<ListRow
fields={fields}
isSelected={selectedItems.includes(item.id)}
item={item}
itemErrors={itemsErrors[itemIndex] || {}}
key={item.id}
link={({ path, id }) => `${adminPath}/${path}/${id}`}
list={list}
onDelete={onChange}
onSelectChange={onSelectChange}
/>
))}
<Suspense
fallback={
<SingleCell columns={columns}>
<PageLoading />
</SingleCell>
}
>
<Render>
{() => {
// Now that the network request for data has been triggered, we
// try to initialise the fields. They are Suspense capable, so may
// throw Promises which will be caught by the above <Suspense>
captureSuspensePromises(
fields
.filter(field => field.path !== '_label_')
.map(field => () => field.initCellView())
);

// NOTE: We don't check for isLoading here because we want to
// avoid showing the <PageLoading /> component when we already
// have (possibly stale) data to show.
// Instead, we show the loader when there's _no data at all_.
if (!items) {
return (
<SingleCell columns={columns}>
<PageLoading />
</SingleCell>
);
}

if (!items.length) {
return (
<SingleCell columns={columns}>
<NoResults
currentPage={currentPage}
filters={filters}
list={list}
search={search}
/>
</SingleCell>
);
}

return items.map((item, itemIndex) => (
<ListRow
fields={fields}
isSelected={selectedItems.includes(item.id)}
item={item}
itemErrors={itemsErrors[itemIndex] || {}}
key={item.id}
link={({ path, id }) => `${adminPath}/${path}/${id}`}
list={list}
onDelete={onChange}
onSelectChange={onSelectChange}
/>
));
}}
</Render>
</Suspense>
</tbody>
</Table>
</Card>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Button } from '@arch-ui/button';
import { InfoIcon } from '@arch-ui/icons';
import { colors } from '@arch-ui/theme';

import { useListPagination } from './dataHooks';
import { useListPagination } from '../pages/List/dataHooks';

const NoResultsWrapper = ({ children, ...props }) => (
<div
Expand All @@ -27,7 +27,7 @@ const NoResultsWrapper = ({ children, ...props }) => (
</div>
);

export const NoResults = ({ currentPage, filters, itemCount, list, search }) => {
export const NoResults = ({ currentPage, filters, list, search }) => {
const { onChange } = useListPagination(list.key);
const onResetPage = () => onChange(1);

Expand Down Expand Up @@ -64,9 +64,5 @@ export const NoResults = ({ currentPage, filters, itemCount, list, search }) =>
);
}

if (itemCount === 0) {
return <NoResultsWrapper>No {list.plural.toLowerCase()} to display yet...</NoResultsWrapper>;
}

return null;
return <NoResultsWrapper>No {list.plural.toLowerCase()} to display yet...</NoResultsWrapper>;
};
103 changes: 41 additions & 62 deletions packages/admin-ui/client/pages/List/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/** @jsx jsx */

import { jsx } from '@emotion/core';
import { Fragment, Suspense, useEffect, useRef, useState } from 'react';
import { Fragment, useEffect, useRef, useState } from 'react';
import { Query } from 'react-apollo';

import { IconButton } from '@arch-ui/button';
Expand All @@ -18,7 +18,6 @@ import CreateItemModal from '../../components/CreateItemModal';
import DocTitle from '../../components/DocTitle';
import ListTable from '../../components/ListTable';
import PageError from '../../components/PageError';
import PageLoading from '../../components/PageLoading';
import { DisclosureArrow } from '../../components/Popout';
import { deconstructErrorsToDataShape } from '../../util';

Expand All @@ -28,7 +27,6 @@ import SortPopout from './SortSelect';
import Pagination, { getPaginationLabel } from './Pagination';
import Search from './Search';
import Management, { ManageToolbar } from './Management';
import { NoResults } from './NoResults';
import { useListFilter, useListSelect, useListSort, useListUrlState } from './dataHooks';

const HeaderInset = props => (
Expand Down Expand Up @@ -204,66 +202,52 @@ function ListLayout(props: LayoutProps) {
/>

<Container isFullWidth>
{items ? (
<Suspense fallback={<PageLoading />}>
{items.length ? (
<ListTable
adminPath={adminPath}
columnControl={
<ColumnPopout
listKey={list.key}
target={handlers => (
<Tooltip placement="top" content="Columns">
{ref => (
<Button
variant="subtle"
css={{
background: 0,
border: 0,
color: colors.N40,
}}
{...handlers}
ref={applyRefs(handlers.ref, ref)}
>
<KebabHorizontalIcon />
</Button>
)}
</Tooltip>
)}
/>
}
fields={fields}
handleSortChange={handleSortChange}
isFullWidth
items={items}
itemsErrors={itemErrors}
list={list}
onChange={onDeleteItem}
onSelectChange={onSelectChange}
selectedItems={selectedItems}
sortBy={sortBy}
/>
) : (
<NoResults
currentPage={currentPage}
filters={filters}
itemCount={itemCount}
list={list}
search={search}
/>
)}
</Suspense>
) : (
<PageLoading />
)}
<ListTable
adminPath={adminPath}
columnControl={
<ColumnPopout
listKey={list.key}
target={handlers => (
<Tooltip placement="top" content="Columns">
{ref => (
<Button
variant="subtle"
css={{
background: 0,
border: 0,
color: colors.N40,
}}
{...handlers}
ref={applyRefs(handlers.ref, ref)}
>
<KebabHorizontalIcon />
</Button>
)}
</Tooltip>
)}
/>
}
fields={fields}
handleSortChange={handleSortChange}
isFullWidth
items={items}
itemsErrors={itemErrors}
list={list}
onChange={onDeleteItem}
onSelectChange={onSelectChange}
selectedItems={selectedItems}
sortBy={sortBy}
currentPage={currentPage}
filters={filters}
search={search}
/>
</Container>
</main>
);
}

function List(props: Props) {
const { adminMeta, list, query, routeProps } = props;
const { urlState } = useListUrlState(list.key);
const { list, query, routeProps } = props;

// get item data
let items;
Expand Down Expand Up @@ -296,11 +280,6 @@ function List(props: Props) {
}
}, []);

// TODO: put this in some effect to limit calls
// we want to preload the Field components
// so that we don't have a waterfall after the data loads
adminMeta.preloadViews(urlState.fields.map(({ views }) => views && views.Cell).filter(x => x));

// Error
// ------------------------------
// Only show error page if there is no data
Expand Down

0 comments on commit 13b7d4e

Please sign in to comment.