Skip to content

Instantly share code, notes, and snippets.

@acomagu
Last active June 8, 2023 12:25
Show Gist options
  • Save acomagu/db91e95b2084a529c090f4e9abd71ac2 to your computer and use it in GitHub Desktop.
Save acomagu/db91e95b2084a529c090f4e9abd71ac2 to your computer and use it in GitHub Desktop.
import { list } from '@keystone-6/core';
import { graphql } from '@keystone-6/core';
import { allowAll } from '@keystone-6/core/access';
import { checkbox, relationship, text, timestamp } from '@keystone-6/core/fields';
import { select } from '@keystone-6/core/fields';
import { BaseListTypeInfo, CommonFieldConfig, FieldTypeFunc, fieldType } from '@keystone-6/core/types';
interface TranslatedFieldConfig<
ListTypeInfo extends BaseListTypeInfo,
LanguageTag extends string,
> extends CommonFieldConfig<ListTypeInfo> {
languages: LanguageTag[];
}
type TranslatedFieldArgs = Record<string, graphql.Arg<graphql.ScalarType<string>>>;
type TranslatedField = Record<string, string | undefined | null>;
function translated<ListTypeInfo extends BaseListTypeInfo, LanguageTag extends string>(
config: TranslatedFieldConfig<ListTypeInfo, LanguageTag>,
): FieldTypeFunc<ListTypeInfo> {
const translatedFieldInput = graphql.inputObject<TranslatedFieldArgs>({
name: 'TranslatedFieldInput',
fields: Object.fromEntries([
[config.languages[0], graphql.arg({ type: graphql.nonNull(graphql.String) })],
...config.languages.slice(1).map(tag => [tag, graphql.arg({ type: graphql.String })]),
]),
});
const translatedFieldOutput = graphql.object<TranslatedField>()({
name: 'TranslatedFieldOutput',
fields: Object.fromEntries([
[config.languages[0], graphql.field({
type: graphql.nonNull(graphql.String),
resolve: v => (v as any)[config.languages[0]],
})],
...config.languages.slice(1).map(tag => [tag, graphql.field({
type: graphql.String,
resolve: v => (v as any)[tag],
})]),
]),
});
function resolveInput(value?: TranslatedField | null) {
if (value?.[config.languages[0]] == undefined) return {};
const res = {
[config.languages[0]]: value?.[config.languages[0]],
...Object.fromEntries(config.languages.slice(1).map(tag =>
[tag, value?.[tag] ?? null],
)),
};
return res;
}
return () => fieldType({
kind: 'multi',
fields: {
[config.languages[0]]: {
kind: 'scalar',
mode: 'required',
scalar: 'String',
},
...Object.fromEntries(config.languages.slice(1).map(tag => [tag, {
kind: 'scalar',
mode: 'optional',
scalar: 'String',
}])),
},
})({
...config,
getAdminMeta: () => ({
languages: config.languages,
}),
input: {
create: {
arg: graphql.arg({ type: translatedFieldInput }),
resolve: resolveInput,
},
update: {
arg: graphql.arg({ type: translatedFieldInput }),
resolve: resolveInput,
},
},
output: graphql.field({
type: translatedFieldOutput,
}),
views: './views',
});
}
export const lists = {
Person: list({
access: allowAll,
fields: {
label: text({ validation: { isRequired: true } }),
name: translated({ languages: ['ja', 'en', 'ch'] }),
},
}),
};
{
"dependencies": {
"@keystone-6/auth": "^7.0.0",
"@keystone-6/core": "^5.0.0",
"@keystone-6/fields-document": "^7.0.0",
"typescript": "^4.9.5"
}
}
import React from 'react';
import { FieldContainer, FieldDescription, FieldLabel, TextInput } from '@keystone-ui/fields';
import { CellLink, CellContainer } from '@keystone-6/core/admin-ui/components';
import {
CardValueComponent,
FieldController,
CellComponent,
FieldControllerConfig,
FieldProps,
} from '@keystone-6/core/types';
export type AdminTranslatedFieldMeta = {
languages: string[];
};
export function Field({ field, value, onChange, autoFocus }: FieldProps<typeof controller>) {
const disabled = onChange === undefined;
return Object.keys(value ?? {}).map(tag => <>
<FieldContainer as="fieldset">
<FieldLabel>{field.label} ({tag})</FieldLabel>
<FieldDescription id={`${field.path}-description-${tag}`}>
{field.description}
</FieldDescription>
<div>
<TextInput
type="text"
onChange={event => {
onChange?.({ ...value, [tag]: event.target.value });
}}
disabled={disabled}
value={value?.[tag]}
autoFocus={autoFocus}
/>
</div>
</FieldContainer>
</>);
}
export const Cell: CellComponent = ({ item, field, linkTo }) => {
let value = item[field.path] + '';
return linkTo ? <CellLink {...linkTo}>{value}</CellLink> : <CellContainer>{value}</CellContainer>;
};
Cell.supportsLinkTo = true;
export const CardValue: CardValueComponent = ({ item, field }) => {
return (
<FieldContainer>
<FieldLabel>{field.label}</FieldLabel>
{item[field.path]}
</FieldContainer>
);
};
export const controller = (
config: FieldControllerConfig<AdminTranslatedFieldMeta>,
): FieldController<
Record<string, string> | null,
string
> => {
return {
path: config.path,
label: config.label,
description: config.description,
graphqlSelection: `${config.path} {
${config.fieldMeta.languages.join('\n')}
}`,
defaultValue: null,
deserialize: data => {
return config.fieldMeta.languages.reduce((sum, tag) => ({
...sum,
[tag]: data[config.path]?.[tag] ?? null,
}), {});
},
serialize: value => ({ [config.path]: value }),
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment