Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First pass of field selection in new interfaces #3753

Merged
merged 6 commits into from
Sep 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 83 additions & 16 deletions packages-next/admin-ui/src/pages/ListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import { useQuery, gql } from '../apollo';
import { jsx } from '@keystone-ui/core';
import Link from 'next/link';
import { useMemo } from 'react';

import { Fragment, useMemo } from 'react';
import { LinkIcon } from '@keystone-ui/icons/icons/LinkIcon';
import { PageContainer } from '../components/PageContainer';
import { useList } from '../KeystoneContext';
import { useRouter, Link } from '../router';

type ListPageProps = {
listKey: string;
Expand All @@ -15,9 +15,32 @@ type ListPageProps = {
export const ListPage = ({ listKey }: ListPageProps) => {
const list = useList(listKey);

const { query } = useRouter();
const selectedFieldsFromUrl = query.fields || '';
const selectedFields = useMemo(() => {
if (!query.fields) {
return {
includeLabel: true,
fields: list.initialColumns,
};
}
let includeLabel = false;
let fields = (selectedFieldsFromUrl as string).split(',').filter(field => {
if (field === '_label_') {
includeLabel = true;
return false;
}
return true;
});
return {
fields,
includeLabel,
};
}, [list.initialColumns, selectedFieldsFromUrl]);

let { data, error } = useQuery(
useMemo(() => {
let selectedFields = Object.keys(list.fields)
let selectedGqlFields = selectedFields.fields
.map(fieldPath => {
return list.fields[fieldPath].controller.graphqlSelection;
})
Expand All @@ -26,25 +49,45 @@ export const ListPage = ({ listKey }: ListPageProps) => {
query {
items: ${list.gqlNames.listQueryName} {
id
_label_
${selectedFields}
${selectedFields.includeLabel ? '_label_' : ''}
${selectedGqlFields}
}
}
`;
}, [list])
}, [list, selectedFields])
);

const shouldShowNonCellLink =
!selectedFields.includeLabel &&
!list.fields[selectedFields.fields[0]].views.Cell.supportsLinkTo;

return (
<PageContainer>
<h2>List: {list.label}</h2>
<p>
{data
? (() => {
const selectedFieldCount =
selectedFields.fields.length + Number(selectedFields.includeLabel);
return (
<Fragment>
Showing {data.items.length}{' '}
{data.items.length === 1 ? list.singular : list.plural} with {selectedFieldCount}{' '}
column{selectedFieldCount === 1 ? '' : 's'}
</Fragment>
);
})()
: ' '}
</p>
{error ? (
'Error...'
) : data ? (
<table>
<thead>
<tr>
<th>Label</th>
{Object.keys(list.fields).map(key => {
{selectedFields.includeLabel && <th>Label</th>}
{shouldShowNonCellLink && <th />}
{selectedFields.fields.map(key => {
return <th key={key}>{list.fields[key].label}</th>;
})}
</tr>
Expand All @@ -53,16 +96,40 @@ export const ListPage = ({ listKey }: ListPageProps) => {
{data.items.map((item: any) => {
return (
<tr key={item.id}>
<td>
<Link href={`/${list.path}/[id]`} as={`/${list.path}/${item.id}`}>
<a>{item._label_}</a>
</Link>
</td>
{Object.keys(list.fields).map(fieldKey => {
{selectedFields.includeLabel && (
<td>
<Link href={`/${list.path}/[id]`} as={`/${list.path}/${item.id}`}>
{item._label_}
</Link>
</td>
)}
{shouldShowNonCellLink && (
<td>
<Link
css={{ textDecoration: 'none' }}
href={`/${list.path}/[id]`}
as={`/${list.path}/${item.id}`}
>
<LinkIcon aria-label="Go to item" />
</Link>
</td>
)}
{selectedFields.fields.map((fieldKey, i) => {
let { Cell } = list.fields[fieldKey].views;
return (
<td key={fieldKey}>
<Cell item={item} path={fieldKey} />
<Cell
item={item}
path={fieldKey}
linkTo={
i === 0 && !selectedFields.includeLabel && Cell.supportsLinkTo
? {
href: `/${list.path}/[id]`,
as: `/${list.path}/${item.id}`,
}
: undefined
}
/>
</td>
);
})}
Expand Down
20 changes: 20 additions & 0 deletions packages-next/admin-ui/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,23 @@
*/

export * from 'next/router';
import NextLink, { LinkProps as NextLinkProps } from 'next/link';
import React, { AnchorHTMLAttributes } from 'react';

export type LinkProps = Omit<NextLinkProps, 'passHref'> &
Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>;

export const Link = ({ href, as, replace, scroll, shallow, prefetch, ...props }: LinkProps) => {
return (
<NextLink
href={href}
as={as}
replace={replace}
scroll={scroll}
shallow={shallow}
prefetch={prefetch}
>
<a {...props} />
</NextLink>
);
};
1 change: 1 addition & 0 deletions packages-next/admin-ui/src/utils/useAdminMeta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export function useAdminMeta(
Object.keys(adminMeta.lists).forEach(key => {
const list = adminMeta.lists[key];
runtimeAdminMeta.lists[key] = {
initialColumns: list.initialColumns,
gqlNames: list.gqlNames,
key,
fields: {},
Expand Down
1 change: 1 addition & 0 deletions packages-next/fields/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"@babel/runtime": "^7.11.2",
"@emotion/core": "^10.0.35",
"@keystone-spike/admin-ui": "*",
"@keystone-spike/types": "*",
"@keystone-ui/core": "*",
"@keystone-ui/fields": "*",
Expand Down
7 changes: 4 additions & 3 deletions packages-next/fields/src/types/checkbox/views/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import { jsx, useTheme } from '@keystone-ui/core';
import { FieldContainer, Checkbox } from '@keystone-ui/fields';
import { CellProps, FieldProps, makeController } from '@keystone-spike/types';
import { CellComponent, FieldProps, makeController } from '@keystone-spike/types';
import { Fragment } from 'react';

export const Field = ({ field, value, onChange }: FieldProps<typeof controller>) => {
const { fields, typography } = useTheme();
Expand All @@ -22,8 +23,8 @@ export const Field = ({ field, value, onChange }: FieldProps<typeof controller>)
);
};

export const Cell = ({ item, path }: CellProps) => {
return item[path] + '';
export const Cell: CellComponent = ({ item, path }) => {
return <Fragment>{item[path] + ''}</Fragment>;
};

export const controller = makeController(config => {
Expand Down
7 changes: 4 additions & 3 deletions packages-next/fields/src/types/password/views/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import { jsx } from '@keystone-ui/core';
import { FieldContainer, FieldLabel, TextInput } from '@keystone-ui/fields';

import { CellProps, FieldProps, makeController } from '@keystone-spike/types';
import { CellComponent, FieldProps, makeController } from '@keystone-spike/types';
import { Fragment } from 'react';

export const Field = ({ field, value, onChange }: FieldProps<typeof controller>) => (
<FieldContainer>
Expand All @@ -18,8 +19,8 @@ export const Field = ({ field, value, onChange }: FieldProps<typeof controller>)
</FieldContainer>
);

export const Cell = ({ item, path }: CellProps) => {
return item[`${path}_is_set`] ? 'Is set' : 'Is not set';
export const Cell: CellComponent = ({ item, path }) => {
return <Fragment>{item[`${path}_is_set`] ? 'Is set' : 'Is not set'}</Fragment>;
};

export const controller = makeController(config => {
Expand Down
10 changes: 7 additions & 3 deletions packages-next/fields/src/types/text/views/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { jsx } from '@keystone-ui/core';
import { FieldContainer, FieldLabel, TextInput, TextArea } from '@keystone-ui/fields';

import {
CellProps,
CellComponent,
FieldControllerConfig,
FieldProps,
makeController,
} from '@keystone-spike/types';
import { Link } from '@keystone-spike/admin-ui/router';
import { Fragment } from 'react';

export const Field = ({ field, value, onChange }: FieldProps<typeof controller>) => (
<FieldContainer>
Expand All @@ -31,9 +33,11 @@ export const Field = ({ field, value, onChange }: FieldProps<typeof controller>)
</FieldContainer>
);

export const Cell = ({ item, path }: CellProps) => {
return item[path] + '';
export const Cell: CellComponent = ({ item, path, linkTo }) => {
let value = item[path] + '';
return linkTo ? <Link {...linkTo}>{value}</Link> : <Fragment>{value}</Fragment>;
};
Cell.supportsLinkTo = true;

type Config = FieldControllerConfig<{ isMultiline: boolean }>;

Expand Down
6 changes: 3 additions & 3 deletions packages-next/keystone/src/classes/Keystone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,15 @@ export function createKeystone(config: KeystoneConfig): Keystone {
} as any) as any;
adminMeta.lists[key] = {
key,
// TODO: This should look for the admin description, not the graphQL description...
// This really all needs to be reviewed.
description: listConfig.graphql?.description ?? listConfig.description,
description: listConfig.admin?.description ?? listConfig.description,
label: list.adminUILabels.label,
singular: list.adminUILabels.singular,
plural: list.adminUILabels.plural,
path: list.adminUILabels.path,
fields: {},
gqlNames: list.gqlNames,
initialColumns:
listConfig.admin?.listView?.initialColumns ?? Object.keys(listConfig.fields).slice(0, 2),
};
for (const fieldKey of Object.keys(listConfig.fields)) {
const field = listConfig.fields[fieldKey];
Expand Down
14 changes: 10 additions & 4 deletions packages-next/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,14 @@ export type KeystoneConfig = {
admin?: KeystoneAdminConfig;
} & SchemaConfig;

export type CellProps = {
item: Record<string, any>;
path: string;
export type CellComponent = {
(props: {
item: Record<string, any>;
path: string;
linkTo: { href: string; as: string } | undefined;
}): ReactElement;

supportsLinkTo?: boolean;
};

type AllModes = 'edit' | 'read' | 'hidden';
Expand Down Expand Up @@ -166,6 +171,7 @@ type BaseListMeta = {
plural: string;
description?: string;
gqlNames: GqlNames;
initialColumns: string[];
};

export type SerializedListMeta = BaseListMeta & {
Expand Down Expand Up @@ -223,7 +229,7 @@ export type Keystone = {
export type FieldViews = {
[type: string]: {
Field: (props: FieldProps<any>) => ReactElement;
Cell: (props: CellProps) => ReactElement;
Cell: CellComponent;
controller: (args: FieldControllerConfig<any>) => FieldController<unknown>;
};
};
Expand Down