Merge pull request #68 from directus/schema-meta

Schema meta
This commit is contained in:
Rijk van Zanten
2020-08-06 13:54:08 -04:00
committed by GitHub
58 changed files with 249 additions and 1376 deletions

View File

@@ -59,14 +59,14 @@ const newFieldSchema = Joi.object({
collection: Joi.string().optional(),
field: Joi.string().required(),
type: Joi.string().valid(...types),
database: Joi.object({
schema: Joi.object({
comment: Joi.string(),
default_value: Joi.any(),
max_length: [Joi.number(), Joi.string()],
is_nullable: Joi.bool(),
}),
/** @todo base this on default validation */
system: Joi.any(),
meta: Joi.any(),
});
router.post(

View File

@@ -27,9 +27,9 @@ export default class CollectionsService {
if (!collection.fields) collection.fields = [];
collection.fields = collection.fields.map((field) => {
if (field.system) {
field.system = {
...field.system,
if (field.meta) {
field.meta = {
...field.meta,
field: field.field,
collection: collection.collection!,
};
@@ -76,8 +76,8 @@ export default class CollectionsService {
await collectionItemsService.create(collectionInfo);
const fieldPayloads = payload
.fields!.filter((field) => field.system)
.map((field) => field.system);
.fields!.filter((field) => field.meta)
.map((field) => field.meta);
await fieldItemsService.create(fieldPayloads);
@@ -122,7 +122,7 @@ export default class CollectionsService {
const tablesInDatabase = await schemaInspector.tableInfo();
const tables = tablesInDatabase.filter((table) => collectionKeys.includes(table.name));
const system: any[] = await collectionItemsService.readByQuery({
const meta: any[] = await collectionItemsService.readByQuery({
filter: { collection: { _in: collectionKeys } },
});
@@ -131,8 +131,8 @@ export default class CollectionsService {
for (const table of tables) {
const collection: Collection = {
collection: table.name,
system: system.find((systemInfo) => systemInfo.collection === table.name) || null,
database: table,
meta: meta.find((systemInfo) => systemInfo.collection === table.name) || null,
schema: table,
};
collections.push(collection);
@@ -160,7 +160,7 @@ export default class CollectionsService {
}
const tablesToFetchInfoFor = tablesInDatabase.map((table) => table.name);
const system: any[] = await collectionItemsService.readByQuery({
const meta: any[] = await collectionItemsService.readByQuery({
filter: { collection: { _in: tablesToFetchInfoFor } },
});
@@ -169,8 +169,8 @@ export default class CollectionsService {
for (const table of tablesInDatabase) {
const collection: Collection = {
collection: table.name,
system: system.find((systemInfo) => systemInfo.collection === table.name) || null,
database: table,
meta: meta.find((systemInfo) => systemInfo.collection === table.name) || null,
schema: table,
};
collections.push(collection);
@@ -198,11 +198,11 @@ export default class CollectionsService {
if (data && key) {
const payload = data as Partial<Collection>;
if (!payload.system) {
if (!payload.meta) {
throw new InvalidPayloadException(`"system" key is required`);
}
return (await collectionItemsService.update(payload.system!, key as any)) as
return (await collectionItemsService.update(payload.meta!, key as any)) as
| string
| string[];
}
@@ -211,7 +211,7 @@ export default class CollectionsService {
const collectionUpdates = payloads.map((collection) => {
return {
...collection.system,
...collection.meta,
collection: collection.collection,
};
});

View File

@@ -1,7 +1,7 @@
import database, { schemaInspector } from '../database';
import { Field } from '../types/field';
import { uniq } from 'lodash';
import { Accountability, AbstractServiceOptions, System } from '../types';
import { Accountability, AbstractServiceOptions, FieldMeta } from '../types';
import ItemsService from '../services/items';
import { ColumnBuilder } from 'knex';
import getLocalType from '../utils/get-local-type';
@@ -44,17 +44,17 @@ export default class FieldsService {
}
async readAll(collection?: string) {
let fields: System[];
let fields: FieldMeta[];
if (collection) {
fields = (await this.itemsService.readByQuery({
filter: { collection: { _eq: collection } },
})) as System[];
})) as FieldMeta[];
} else {
fields = (await this.itemsService.readByQuery({})) as System[];
fields = (await this.itemsService.readByQuery({})) as FieldMeta[];
}
fields = (await this.payloadService.processValues('read', fields)) as System[];
fields = (await this.payloadService.processValues('read', fields)) as FieldMeta[];
let columns = await schemaInspector.columnInfo(collection);
@@ -74,27 +74,27 @@ export default class FieldsService {
collection: column.table,
field: column.name,
type: column ? getLocalType(column.type) : 'alias',
database: column,
system: field || null,
schema: column,
meta: field || null,
};
return data as Field;
});
let aliasFields = await this.knex
.select<System[]>('*')
.select<FieldMeta[]>('*')
.from('directus_fields')
.whereIn('special', ['alias', 'o2m']);
aliasFields = (await this.payloadService.processValues('read', aliasFields)) as System[];
aliasFields = (await this.payloadService.processValues('read', aliasFields)) as FieldMeta[];
const aliasFieldsAsField = aliasFields.map((field) => {
const data = {
collection: field.collection,
field: field.field,
type: field.special,
database: null,
system: field,
schema: null,
meta: field,
};
return data;
@@ -112,7 +112,7 @@ export default class FieldsService {
.where({ collection, field })
.first();
fieldInfo = (await this.payloadService.processValues('read', fieldInfo)) as System[];
fieldInfo = (await this.payloadService.processValues('read', fieldInfo)) as FieldMeta[];
try {
column = await schemaInspector.columnInfo(collection, field);
@@ -140,7 +140,7 @@ export default class FieldsService {
* Check if table / directus_fields row already exists
*/
if (field.database) {
if (field.schema) {
if (table) {
this.addColumnToTable(table, field as Field);
} else {
@@ -150,9 +150,9 @@ export default class FieldsService {
}
}
if (field.system) {
if (field.meta) {
await this.itemsService.create({
...field.system,
...field.meta,
collection: collection,
field: field.field,
});
@@ -162,16 +162,16 @@ export default class FieldsService {
/** @todo research how to make this happen in SQLite / Redshift */
async updateField(collection: string, field: RawField, accountability?: Accountability) {
if (field.database) {
await database.schema.alterTable(collection, (table) => {
if (field.schema) {
await this.knex.schema.alterTable(collection, (table) => {
let column: ColumnBuilder;
if (!field.database) return;
if (!field.schema) return;
if (field.type === 'string') {
column = table.string(
field.field,
field.database.max_length !== null ? field.database.max_length : undefined
field.schema.max_length !== null ? field.schema.max_length : undefined
);
} else if (['float', 'decimal'].includes(field.type)) {
const type = field.type as 'float' | 'decimal';
@@ -181,14 +181,11 @@ export default class FieldsService {
column = table[field.type](field.field);
}
if (field.database.default_value) {
column.defaultTo(field.database.default_value);
if (field.schema.default_value) {
column.defaultTo(field.schema.default_value);
}
if (
field.database.is_nullable !== undefined &&
field.database.is_nullable === false
) {
if (field.schema.is_nullable !== undefined && field.schema.is_nullable === false) {
column.notNullable();
} else {
column.nullable();
@@ -198,7 +195,7 @@ export default class FieldsService {
});
}
if (field.system) {
if (field.meta) {
const record = await database
.select<{ id: number }>('id')
.from('directus_fields')
@@ -206,7 +203,7 @@ export default class FieldsService {
.first();
if (!record) throw new FieldNotFoundException(collection, field.field);
await database('directus_fields')
.update(field.system)
.update(field.meta)
.where({ collection, field: field.field });
}
@@ -225,10 +222,10 @@ export default class FieldsService {
public addColumnToTable(table: CreateTableBuilder, field: Field) {
let column: ColumnBuilder;
if (field.database?.has_auto_increment) {
if (field.schema?.has_auto_increment) {
column = table.increments(field.field);
} else if (field.type === 'string') {
column = table.string(field.field, field.database?.max_length || undefined);
column = table.string(field.field, field.schema?.max_length || undefined);
} else if (['float', 'decimal'].includes(field.type)) {
const type = field.type as 'float' | 'decimal';
/** @todo add precision and scale support */
@@ -237,17 +234,17 @@ export default class FieldsService {
column = table[field.type](field.field);
}
if (field.database?.default_value) {
column.defaultTo(field.database.default_value);
if (field.schema?.default_value) {
column.defaultTo(field.schema.default_value);
}
if (field.database.is_nullable !== undefined && field.database.is_nullable === false) {
if (field.schema.is_nullable !== undefined && field.schema.is_nullable === false) {
column.notNullable();
} else {
column.nullable();
}
if (field.database?.is_primary_key) {
if (field.schema?.is_primary_key) {
column.primary();
}
}

View File

@@ -3,7 +3,7 @@
* handled correctly.
*/
import { System } from '../types/field';
import { FieldMeta } from '../types/field';
import argon2 from 'argon2';
import { v4 as uuidv4 } from 'uuid';
import database from '../database';
@@ -104,7 +104,7 @@ export default class PayloadService {
const specialFieldsQuery = this.knex
.select('field', 'special')
.from<System>('directus_fields')
.from<FieldMeta>('directus_fields')
.where({ collection: this.collection })
.whereNotNull('special');
@@ -143,7 +143,7 @@ export default class PayloadService {
}
async processField(
field: Pick<System, 'field' | 'special'>,
field: Pick<FieldMeta, 'field' | 'special'>,
payload: Partial<Item>,
operation: Operation
) {

View File

@@ -4,7 +4,7 @@ import { Table } from 'knex-schema-inspector/lib/types/table';
export type Collection = {
collection: string;
fields?: Field[];
system: {
meta: {
collection: string;
note: string | null;
hidden: boolean;
@@ -12,5 +12,5 @@ export type Collection = {
icon: string | null;
translation: Record<string, string>;
} | null;
database: Table;
schema: Table;
};

View File

@@ -17,7 +17,7 @@ export const types = [
'uuid',
] as const;
export type System = {
export type FieldMeta = {
id: number;
collection: string;
field: string;
@@ -40,6 +40,6 @@ export type Field = {
collection: string;
field: string;
type: typeof types[number];
database: Column;
system: System | null;
schema: Column;
meta: FieldMeta | null;
};

View File

@@ -9,11 +9,11 @@
<component
v-if="interfaceExists"
:is="`interface-${field.system.interface}`"
v-bind="field.system.options"
:is="`interface-${field.meta.interface}`"
v-bind="field.meta.options"
:disabled="disabled"
:value="value === undefined ? field.database.default_value : value"
:width="field.system.width"
:value="value === undefined ? field.schema.default_value : value"
:width="field.meta.width"
:type="field.type"
:collection="field.collection"
:field="field.field"
@@ -66,7 +66,7 @@ export default defineComponent({
},
setup(props) {
const interfaceExists = computed(() => {
return !!interfaces.find((inter) => inter.id === props.field.system.interface);
return !!interfaces.find((inter) => inter.id === props.field.meta.interface);
});
return { interfaceExists };

View File

@@ -1,5 +1,5 @@
<template>
<div class="field" :key="field.field" :class="field.system.width">
<div class="field" :key="field.field" :class="field.meta.width">
<v-menu
v-if="field.hideLabel !== true"
placement="bottom-start"
@@ -39,7 +39,7 @@
@input="$emit('input', $event)"
/>
<small class="note" v-if="field.system.note" v-html="marked(field.system.note)" />
<small class="note" v-if="field.meta.note" v-html="marked(field.meta.note)" />
</div>
</template>
@@ -90,7 +90,7 @@ export default defineComponent({
setup(props) {
const isDisabled = computed(() => {
if (props.disabled) return true;
if (props.field.system.readonly) return true;
if (props.field.meta.readonly) return true;
if (props.batchMode && props.batchActive === false) return true;
return false;
});
@@ -98,7 +98,7 @@ export default defineComponent({
const _value = computed(() => {
if (props.value !== undefined) return props.value;
if (props.initialValue !== undefined) return props.initialValue;
return props.field.database?.default_value;
return props.field.schema?.default_value;
});
return { isDisabled, marked, _value };

View File

@@ -130,7 +130,7 @@ export default defineComponent({
return (
props.loading ||
props.disabled === true ||
field.system.readonly === true ||
field.meta.readonly === true ||
(props.batchMode && batchActiveFields.value.includes(field.field) === false)
);
}

View File

@@ -21,20 +21,20 @@ export function useCollection(collectionKey: string | Ref<string>) {
// Every collection has a primary key; rules of the land
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return fields.value?.find(
(field) => field.collection === collection.value && field.database?.is_primary_key === true
(field) => field.collection === collection.value && field.schema?.is_primary_key === true
)!;
});
const userCreatedField = computed(() => {
return fields.value?.find((field) => field.system?.special === 'user_created') || null;
return fields.value?.find((field) => field.meta?.special === 'user_created') || null;
});
const statusField = computed(() => {
return fields.value?.find((field) => field.system?.special === 'status') || null;
return fields.value?.find((field) => field.meta?.special === 'status') || null;
});
const sortField = computed(() => {
return fields.value?.find((field) => field.system?.special === 'sort') || null;
return fields.value?.find((field) => field.meta?.special === 'sort') || null;
});
type Status = {
@@ -52,7 +52,7 @@ export function useCollection(collectionKey: string | Ref<string>) {
const softDeleteStatus = computed<string | null>(() => {
if (statusField.value === null) return null;
const statuses = Object.values(statusField.value?.system?.options?.status_mapping || {});
const statuses = Object.values(statusField.value?.meta?.options?.status_mapping || {});
return (
(statuses.find((status) => (status as Status).soft_delete === true) as Status | undefined)?.value || null
);

View File

@@ -13,7 +13,7 @@ export default function useFieldTree(collection: Ref<string>) {
return fieldsStore
.getFieldsForCollection(collection.value)
.filter(
(field: Field) => field.system?.hidden === false && field.system?.special?.toLowerCase() !== 'alias'
(field: Field) => field.meta?.hidden === false && field.meta?.special?.toLowerCase() !== 'alias'
)
.map((field: Field) => parseField(field, []));
@@ -43,7 +43,7 @@ export default function useFieldTree(collection: Ref<string>) {
.getFieldsForCollection(relatedCollection)
.filter(
(field: Field) =>
field.system?.hidden === false && field.system?.special?.toLowerCase() !== 'alias'
field.meta?.hidden === false && field.meta?.special?.toLowerCase() !== 'alias'
);
})
.flat()

View File

@@ -13,8 +13,8 @@ export default function useFormFields(fields: Ref<Field[]>) {
// Sort the fields on the sort column value
formFields = formFields.sort((a, b) => {
const aSort = a.system?.sort || null;
const bSort = b.system?.sort || null;
const aSort = a.meta?.sort || null;
const bSort = b.meta?.sort || null;
if (aSort === bSort) return 0;
if (aSort === null) return 1;
@@ -23,8 +23,8 @@ export default function useFormFields(fields: Ref<Field[]>) {
});
formFields = formFields.map((field, index) => {
if (!field.system) {
field.system = {
if (!field.meta) {
field.meta = {
id: -1,
collection: field.collection,
field: field.field,
@@ -45,18 +45,18 @@ export default function useFormFields(fields: Ref<Field[]>) {
};
}
if (!field.system.width) {
field.system.width = 'full';
if (!field.meta.width) {
field.meta.width = 'full';
}
let interfaceUsed = interfaces.find((int) => int.id === field.system.interface);
let interfaceUsed = interfaces.find((int) => int.id === field.meta.interface);
const interfaceExists = interfaceUsed !== undefined;
if (interfaceExists === false) {
field.system.interface = getDefaultInterfaceForType(field.type);
field.meta.interface = getDefaultInterfaceForType(field.type);
}
interfaceUsed = interfaces.find((int) => int.id === field.system.interface);
interfaceUsed = interfaces.find((int) => int.id === field.meta.interface);
if (interfaceUsed?.hideLabel === true) {
(field as FormField).hideLabel = true;
@@ -66,11 +66,11 @@ export default function useFormFields(fields: Ref<Field[]>) {
(field as FormField).hideLoader = true;
}
if (index !== 0 && field.system!.width === 'half') {
if (index !== 0 && field.meta!.width === 'half') {
const prevField = formFields[index - 1];
if (prevField.system.width === 'half') {
field.system.width = 'half-right';
if (prevField.meta.width === 'half') {
field.meta.width = 'half-right';
}
}
@@ -79,7 +79,7 @@ export default function useFormFields(fields: Ref<Field[]>) {
// Filter out the fields that are marked hidden on detail
formFields = formFields.filter((field) => {
const hidden = field.system?.hidden;
const hidden = field.meta?.hidden;
const systemFake = field.field.startsWith('$');
return hidden !== true && systemFake === false;
});

View File

@@ -12,7 +12,7 @@ export default defineInterface(({ i18n }) => ({
field: 'choices',
type: 'json',
name: i18n.t('choices'),
system: {
meta: {
width: 'full',
interface: 'repeater',
options: {
@@ -22,7 +22,7 @@ export default defineInterface(({ i18n }) => ({
field: 'text',
type: 'string',
name: i18n.t('text'),
system: {
meta: {
interface: 'text-input',
}
},
@@ -30,7 +30,7 @@ export default defineInterface(({ i18n }) => ({
field: 'value',
type: 'string',
name: i18n.t('value'),
system: {
meta: {
interface: 'text-input',
options: {
font: 'monospace'
@@ -45,11 +45,11 @@ export default defineInterface(({ i18n }) => ({
field: 'allowOther',
name: i18n.t('allow_other'),
type: 'boolean',
system: {
meta: {
width: 'half',
interface: 'toggle',
},
database: {
schema: {
default_value: false,
}
},
@@ -57,11 +57,11 @@ export default defineInterface(({ i18n }) => ({
field: 'iconOff',
name: i18n.t('icon_off'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
},
database: {
schema: {
default_value: 'check_box_outline_blank',
}
},
@@ -69,11 +69,11 @@ export default defineInterface(({ i18n }) => ({
field: 'iconOn',
name: i18n.t('icon_on'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
},
database: {
schema: {
default_value: 'check_box',
}
},
@@ -81,11 +81,11 @@ export default defineInterface(({ i18n }) => ({
field: 'color',
name: i18n.t('color'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'color',
},
database: {
schema: {
default_value: '#2f80ed',
}
},

View File

@@ -21,14 +21,14 @@ export default defineInterface(({ i18n }) => ({
field: 'template',
name: i18n.t('template'),
type: 'text',
system: {
meta: {
width: 'full',
interface: 'code',
options: {
language: 'text/plain'
}
},
database: {
schema: {
default_value: null,
}
},
@@ -36,11 +36,11 @@ export default defineInterface(({ i18n }) => ({
field: 'lineNumber',
name: i18n.t('line_number'),
type: 'boolean',
system: {
meta: {
width: 'half',
interface: 'toggle',
},
database: {
schema: {
default_value: false,
}
},
@@ -48,7 +48,7 @@ export default defineInterface(({ i18n }) => ({
field: 'language',
name: i18n.t('language'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'dropdown',
options: { choices },

View File

@@ -16,7 +16,7 @@ export default defineComponent({
type: Boolean,
default: false,
},
includeSystem: {
includeMeta: {
type: Boolean,
default: false,
},
@@ -25,7 +25,7 @@ export default defineComponent({
const collectionsStore = useCollectionsStore();
const collections = computed(() => {
if (props.includeSystem) return collectionsStore.state.collections;
if (props.includeMeta) return collectionsStore.state.collections;
return collectionsStore.state.collections.filter(
(collection) => collection.collection.startsWith('directus_') === false

View File

@@ -12,14 +12,14 @@ export default defineInterface(({ i18n }) => ({
field: 'includeSystem',
name: i18n.t('system'),
type: 'boolean',
system: {
meta: {
width: 'half',
interface: 'toggle',
options: {
label: i18n.t('include_system_collections'),
},
},
database: {
schema: {
default_value: false,
}
},

View File

@@ -12,7 +12,7 @@ export default defineInterface(({ i18n }) => ({
field: 'presets',
name: i18n.t('preset_colors'),
type: 'string',
system: {
meta: {
width: 'full',
interface: 'repeater',
options: {
@@ -22,7 +22,7 @@ export default defineInterface(({ i18n }) => ({
field: 'name',
type: 'string',
name: i18n.t('name'),
system: {
meta: {
interface: 'text-input',
width: 'half',
}
@@ -31,7 +31,7 @@ export default defineInterface(({ i18n }) => ({
field: 'color',
type: 'string',
name: i18n.t('color'),
system: {
meta: {
interface: 'color',
width: 'half'
}

View File

@@ -11,11 +11,11 @@ export default defineInterface(({ i18n }) => ({
{
field: 'includeSeconds',
name: i18n.t('include_seconds'),
system: {
meta: {
width: 'half',
interface: 'toggle',
},
database: {
schema: {
default_value: false,
},
},

View File

@@ -14,7 +14,7 @@ export default defineInterface(({ i18n }) => ({
field: 'color',
name: i18n.t('color'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'color',
}
@@ -23,7 +23,7 @@ export default defineInterface(({ i18n }) => ({
field: 'icon',
name: i18n.t('icon'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
}
@@ -32,7 +32,7 @@ export default defineInterface(({ i18n }) => ({
field: 'title',
name: i18n.t('title'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'text-input',
}

View File

@@ -12,7 +12,7 @@ export default defineInterface(({ i18n }) => ({
field: 'choices',
type: 'json',
name: i18n.t('choices'),
system: {
meta: {
width: 'full',
interface: 'repeater',
options: {
@@ -22,7 +22,7 @@ export default defineInterface(({ i18n }) => ({
field: 'text',
type: 'string',
name: i18n.t('text'),
system: {
meta: {
interface: 'text-input',
}
},
@@ -30,7 +30,7 @@ export default defineInterface(({ i18n }) => ({
field: 'value',
type: 'string',
name: i18n.t('value'),
system: {
meta: {
interface: 'text-input',
options: {
font: 'monospace'
@@ -45,7 +45,7 @@ export default defineInterface(({ i18n }) => ({
field: 'allowOther',
name: i18n.t('allow_other'),
type: 'boolean',
system: {
meta: {
width: 'half',
interface: 'toggle',
default_value: false,
@@ -55,7 +55,7 @@ export default defineInterface(({ i18n }) => ({
field: 'allowNone',
name: i18n.t('allow_none'),
type: 'boolean',
system: {
meta: {
width: 'half',
interface: 'toggle',
default_value: false,
@@ -65,7 +65,7 @@ export default defineInterface(({ i18n }) => ({
field: 'icon',
name: i18n.t('icon'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
}

View File

@@ -12,7 +12,7 @@ export default defineInterface(({ i18n }) => ({
field: 'choices',
type: 'json',
name: i18n.t('choices'),
system: {
meta: {
width: 'full',
interface: 'repeater',
options: {
@@ -22,7 +22,7 @@ export default defineInterface(({ i18n }) => ({
field: 'text',
type: 'string',
name: i18n.t('text'),
system: {
meta: {
interface: 'text-input',
}
},
@@ -30,7 +30,7 @@ export default defineInterface(({ i18n }) => ({
field: 'value',
type: 'string',
name: i18n.t('value'),
system: {
meta: {
interface: 'text-input',
options: {
font: 'monospace'
@@ -45,7 +45,7 @@ export default defineInterface(({ i18n }) => ({
field: 'allowOther',
name: i18n.t('allow_other'),
type: 'boolean',
system: {
meta: {
width: 'half',
interface: 'toggle',
default_value: false,
@@ -55,7 +55,7 @@ export default defineInterface(({ i18n }) => ({
field: 'allowNone',
name: i18n.t('allow_none'),
type: 'boolean',
system: {
meta: {
width: 'half',
interface: 'toggle',
default_value: false,
@@ -65,7 +65,7 @@ export default defineInterface(({ i18n }) => ({
field: 'icon',
name: i18n.t('icon'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
}
@@ -74,7 +74,7 @@ export default defineInterface(({ i18n }) => ({
field: 'placeholder',
name: i18n.t('placeholder'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'text-input',
}

View File

@@ -12,7 +12,7 @@ export default defineInterface(({ i18n }) => ({
field: 'placeholder',
name: i18n.t('placeholder'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'text-input',
}
@@ -21,7 +21,7 @@ export default defineInterface(({ i18n }) => ({
field: 'masked',
name: i18n.t('masked'),
type: 'boolean',
system: {
meta: {
width: 'half',
interface: 'toggle',
}

View File

@@ -13,7 +13,7 @@ export default defineInterface(({ i18n }) => ({
field: 'template',
name: i18n.t('display_template'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'text-input',
}

View File

@@ -14,7 +14,7 @@ export default defineInterface(({ i18n }) => ({
field: 'color',
name: i18n.t('color'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'dropdown',
default_value: 'normal',
@@ -33,7 +33,7 @@ export default defineInterface(({ i18n }) => ({
field: 'icon',
name: i18n.t('icon'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
}
@@ -42,7 +42,7 @@ export default defineInterface(({ i18n }) => ({
field: 'text',
name: i18n.t('text'),
type: 'string',
system: {
meta: {
width: 'full',
interface: 'textarea',
}

View File

@@ -12,7 +12,7 @@ export default defineInterface(({ i18n }) => ({
field: 'placeholder',
name: i18n.t('placeholder'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'text-input',
}
@@ -21,7 +21,7 @@ export default defineInterface(({ i18n }) => ({
field: 'min',
name: i18n.t('minimum_value'),
type: 'integer',
system: {
meta: {
width: 'half',
interface: 'numeric',
}
@@ -30,7 +30,7 @@ export default defineInterface(({ i18n }) => ({
field: 'max',
name: i18n.t('maximum_value'),
type: 'integer',
system: {
meta: {
width: 'half',
interface: 'numeric',
}
@@ -39,7 +39,7 @@ export default defineInterface(({ i18n }) => ({
field: 'step',
name: i18n.t('step_interval'),
type: 'integer',
system: {
meta: {
width: 'half',
interface: 'numeric',
}
@@ -48,7 +48,7 @@ export default defineInterface(({ i18n }) => ({
field: 'iconLeft',
name: i18n.t('icon_left'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
}
@@ -57,7 +57,7 @@ export default defineInterface(({ i18n }) => ({
field: 'iconRight',
name: i18n.t('icon_right'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
}
@@ -66,7 +66,7 @@ export default defineInterface(({ i18n }) => ({
field: 'font',
name: i18n.t('font'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'dropdown',
options: {

View File

@@ -12,7 +12,7 @@ export default defineInterface(({ i18n }) => ({
field: 'choices',
type: 'json',
name: i18n.t('choices'),
system: {
meta: {
width: 'full',
interface: 'repeater',
options: {
@@ -22,7 +22,7 @@ export default defineInterface(({ i18n }) => ({
field: 'text',
type: 'string',
name: i18n.t('text'),
system: {
meta: {
interface: 'text-input',
}
},
@@ -30,7 +30,7 @@ export default defineInterface(({ i18n }) => ({
field: 'value',
type: 'string',
name: i18n.t('value'),
system: {
meta: {
interface: 'text-input',
options: {
font: 'monospace'
@@ -45,7 +45,7 @@ export default defineInterface(({ i18n }) => ({
field: 'allowOther',
name: i18n.t('allow_other'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'toggle',
default_value: false,
@@ -55,7 +55,7 @@ export default defineInterface(({ i18n }) => ({
field: 'iconOff',
name: i18n.t('icon_off'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
default_value: 'check_box_outline_blank',
@@ -65,7 +65,7 @@ export default defineInterface(({ i18n }) => ({
field: 'iconOn',
name: i18n.t('icon_on'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
default_value: 'check_box',
@@ -75,7 +75,7 @@ export default defineInterface(({ i18n }) => ({
field: 'color',
name: i18n.t('color'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'color',
default_value: 'var(--primary)',

View File

@@ -99,7 +99,7 @@ export default defineComponent({
props.fields.forEach((field) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
newDefaults[field.field!] = field.database?.default_value;
newDefaults[field.field!] = field.schema?.default_value;
});
if (props.value !== null) {

View File

@@ -12,7 +12,7 @@ export default defineInterface(({ i18n }) => ({
field: 'minValue',
name: i18n.t('minimum_value'),
type: 'integer',
system: {
meta: {
width: 'half',
interface: 'numeric',
}
@@ -21,7 +21,7 @@ export default defineInterface(({ i18n }) => ({
field: 'maxValue',
name: i18n.t('maximum_value'),
type: 'integer',
system: {
meta: {
width: 'half',
interface: 'numeric',
}
@@ -30,7 +30,7 @@ export default defineInterface(({ i18n }) => ({
field: 'stepInterval',
name: i18n.t('step_interval'),
type: 'integer',
system: {
meta: {
width: 'half',
interface: 'numeric',
}

View File

@@ -13,7 +13,7 @@ export default defineInterface(({ i18n }) => ({
field: 'status_mapping',
name: i18n.t('status_mapping'),
type: 'json',
system: {
meta: {
width: 'full',
interface: 'code',
options: {

View File

@@ -12,7 +12,7 @@ export default defineInterface(({ i18n }) => ({
field: 'placeholder',
name: i18n.t('placeholder'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'text-input',
}
@@ -21,7 +21,7 @@ export default defineInterface(({ i18n }) => ({
field: 'lowercase',
name: i18n.t('lowercase'),
type: 'boolean',
system: {
meta: {
width: 'half',
interface: 'toggle',
}
@@ -30,7 +30,7 @@ export default defineInterface(({ i18n }) => ({
field: 'alphabetize',
name: i18n.t('alphabetize'),
type: 'boolean',
system: {
meta: {
width: 'half',
interface: 'toggle',
}
@@ -39,7 +39,7 @@ export default defineInterface(({ i18n }) => ({
field: 'iconLeft',
name: i18n.t('icon_left'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
}
@@ -48,7 +48,7 @@ export default defineInterface(({ i18n }) => ({
field: 'iconRight',
name: i18n.t('icon_right'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
}
@@ -57,7 +57,7 @@ export default defineInterface(({ i18n }) => ({
field: 'presets',
name: i18n.t('presets'),
type: 'string',
system: {
meta: {
width: 'full',
interface: 'text-input',
}
@@ -66,7 +66,7 @@ export default defineInterface(({ i18n }) => ({
field: 'allowCustom',
name: i18n.t('allow_custom'),
type: 'boolean',
system: {
meta: {
width: 'half',
interface: 'toggle',
}

View File

@@ -11,7 +11,7 @@ export default defineInterface(({ i18n }) => ({
{
field: 'placeholder',
name: i18n.t('placeholder'),
system: {
meta: {
width: 'half',
interface: 'text-input',
},
@@ -19,7 +19,7 @@ export default defineInterface(({ i18n }) => ({
{
field: 'iconLeft',
name: i18n.t('icon_left'),
system: {
meta: {
width: 'half',
interface: 'icon',
},
@@ -27,7 +27,7 @@ export default defineInterface(({ i18n }) => ({
{
field: 'iconRight',
name: i18n.t('icon_right'),
system: {
meta: {
width: 'half',
interface: 'icon',
},
@@ -35,7 +35,7 @@ export default defineInterface(({ i18n }) => ({
{
field: 'font',
name: i18n.t('font'),
system: {
meta: {
width: 'half',
interface: 'dropdown',
default: 'sans-serif',

View File

@@ -12,7 +12,7 @@ export default defineInterface(({ i18n }) => ({
field: 'iconOff',
name: i18n.t('icon_off'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
default_value: 'check_box_outline_blank',
@@ -22,7 +22,7 @@ export default defineInterface(({ i18n }) => ({
field: 'iconOn',
name: i18n.t('icon_on'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'icon',
default_value: 'check_box',
@@ -32,7 +32,7 @@ export default defineInterface(({ i18n }) => ({
field: 'label',
name: i18n.t('label'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'text-input',
default_value: i18n.t('active'),
@@ -42,7 +42,7 @@ export default defineInterface(({ i18n }) => ({
field: 'color',
name: i18n.t('color'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'color',
default_value: 'var(--primary)',

View File

@@ -12,7 +12,7 @@ export default defineInterface(({ i18n }) => ({
field: 'toolbar',
name: i18n.t('toolbar'),
type: 'json',
system: {
meta: {
width: 'full',
interface: 'checkboxes',
default_value: [
@@ -219,7 +219,7 @@ export default defineInterface(({ i18n }) => ({
field: 'font',
name: i18n.t('font'),
type: 'string',
system: {
meta: {
width: 'half',
interface: 'dropdown',
default: 'sans-serif',
@@ -236,7 +236,7 @@ export default defineInterface(({ i18n }) => ({
field: 'customFormats',
name: i18n.t('custom_formats'),
type: 'json',
system: {
meta: {
interface: 'code',
options: {
language: 'json',
@@ -254,7 +254,7 @@ export default defineInterface(({ i18n }) => ({
field: 'tinymceOverrides',
name: i18n.t('tinymce_options_override'),
type: 'json',
system: {
meta: {
interface: 'code',
options: {
language: 'json',

View File

@@ -222,7 +222,7 @@ export default defineComponent({
const { info, primaryKeyField, fields: fieldsInCollection } = useCollection(collection);
const availableFields = computed(() =>
fieldsInCollection.value.filter((field) => field.system.hidden !== true)
fieldsInCollection.value.filter((field) => field.meta.hidden !== true)
);
const fileFields = computed(() => {

View File

@@ -235,7 +235,7 @@ export default defineComponent({
const { info, primaryKeyField, fields: fieldsInCollection, sortField } = useCollection(collection);
const availableFields = computed(() =>
fieldsInCollection.value.filter((field) => field.system?.hidden === false)
fieldsInCollection.value.filter((field) => field.meta?.hidden === false)
);
const { sort, limit, page, fields, fieldsWithRelational } = useItemOptions();
@@ -377,7 +377,7 @@ export default defineComponent({
_viewQuery.value?.fields ||
availableFields.value
.filter((field: Field) => {
return field.database?.is_primary_key === false && field.system.special !== 'sort';
return field.schema?.is_primary_key === false && field.meta.special !== 'sort';
})
.slice(0, 4)
.map(({ field }) => field);
@@ -433,10 +433,10 @@ export default defineComponent({
value: field.field,
width: localWidths.value[field.field] || _viewOptions.value?.widths?.[field.field] || null,
field: {
display: field.system.display,
displayOptions: field.system.display_options,
interface: field.system.interface,
interfaceOptions: field.system.options,
display: field.meta.display,
displayOptions: field.meta.display_options,
interface: field.meta.interface,
interfaceOptions: field.meta.options,
type: field.type,
field: field.field,
},
@@ -524,11 +524,11 @@ export default defineComponent({
const field = availableFields.value.find((field) => field.field === fieldKey);
if (field === undefined) return null;
if (!field.system.display) return null;
if (!field.meta.display) return null;
return {
display: field.system.display,
options: field.system.display_options,
display: field.meta.display,
options: field.meta.display_options,
};
}
}

View File

@@ -87,7 +87,7 @@ export default defineComponent({
// Make sure the selected field matches the type of primary key of the current
// collection. Otherwise you aren't able to properly save the primary key
if (!field.database || field.type !== currentCollectionPrimaryKey.value.type) return false;
if (!field.schema || field.type !== currentCollectionPrimaryKey.value.type) return false;
return true;
})

View File

@@ -52,8 +52,8 @@ function initLocalStore(
state.fieldData.field = existingField.field;
state.fieldData.type = existingField.type;
state.fieldData.database = existingField.database;
state.fieldData.system = existingField.system;
state.fieldData.database = existingField.schema;
state.fieldData.system = existingField.meta;
state.relations = relationsStore.getRelationsForField(collection, field);
}

View File

@@ -1,5 +1,5 @@
<template>
<div :class="field.system.width || 'full'">
<div :class="field.meta.width || 'full'">
<v-menu attached close-on-content-click>
<template #activator="{ toggle, active }">
<v-input class="field" :class="{ hidden, active }" readonly @click="toggle">
@@ -35,15 +35,15 @@
<v-list-item-content>{{ $t('duplicate_field') }}</v-list-item-content>
</v-list-item>
<v-divider />
<v-list-item @click="setWidth('half')" :disabled="hidden || field.system.width === 'half'">
<v-list-item @click="setWidth('half')" :disabled="hidden || field.meta.width === 'half'">
<v-list-item-icon><v-icon name="border_vertical" /></v-list-item-icon>
<v-list-item-content>{{ $t('half_width') }}</v-list-item-content>
</v-list-item>
<v-list-item @click="setWidth('full')" :disabled="hidden || field.system.width === 'full'">
<v-list-item @click="setWidth('full')" :disabled="hidden || field.meta.width === 'full'">
<v-list-item-icon><v-icon name="border_right" /></v-list-item-icon>
<v-list-item-content>{{ $t('full_width') }}</v-list-item-content>
</v-list-item>
<v-list-item @click="setWidth('fill')" :disabled="hidden || field.system.width === 'fill'">
<v-list-item @click="setWidth('fill')" :disabled="hidden || field.meta.width === 'fill'">
<v-list-item-icon><v-icon name="aspect_ratio" /></v-list-item-icon>
<v-list-item-content>{{ $t('fill_width') }}</v-list-item-content>
</v-list-item>
@@ -128,7 +128,7 @@ export default defineComponent({
const { duplicateActive, duplicateName, collections, duplicateTo, saveDuplicate, duplicating } = useDuplicate();
const interfaceName = computed(() => {
return interfaces.find((inter) => inter.id === props.field.system.interface)?.name;
return interfaces.find((inter) => inter.id === props.field.meta.interface)?.name;
});
return {
@@ -194,8 +194,8 @@ export default defineComponent({
collection: duplicateTo.value,
};
delete newField.system.id;
delete newField.system.sort;
delete newField.meta.id;
delete newField.meta.sort;
delete newField.name;
duplicating.value = true;

View File

@@ -115,15 +115,15 @@ export default defineComponent({
const sortedVisibleFields = computed(() =>
sortBy(
[...fields.value].filter((field) => field.system.hidden === false),
(field) => field.system.sort || Infinity
[...fields.value].filter((field) => field.meta.hidden === false),
(field) => field.meta.sort || Infinity
)
);
const sortedHiddenFields = computed(() =>
sortBy(
[...fields.value].filter((field) => field.system.hidden === true),
(field) => field.system.sort || Infinity
[...fields.value].filter((field) => field.meta.hidden === true),
(field) => field.meta.sort || Infinity
)
);
@@ -197,7 +197,7 @@ export default defineComponent({
const updates: DeepPartial<Field>[] = fieldsInGroup.slice(newIndex).map((field) => {
const sortValue =
field.system.sort ||
field.meta.sort ||
fieldsInGroup.findIndex((existingField) => existingField.field === field.field);
return {
@@ -208,11 +208,11 @@ export default defineComponent({
const addedToEnd = newIndex === fieldsInGroup.length;
let newSortValue = fieldsInGroup[newIndex]?.system.sort;
let newSortValue = fieldsInGroup[newIndex]?.meta.sort;
if (!newSortValue && addedToEnd) {
const previousItem = fieldsInGroup[newIndex - 1];
if (previousItem && previousItem.system.sort) newSortValue = previousItem.system.sort + 1;
if (previousItem && previousItem.meta.sort) newSortValue = previousItem.meta.sort + 1;
}
if (!newSortValue) {
@@ -221,7 +221,7 @@ export default defineComponent({
updates.push({
field: element.field,
system: {
meta: {
hidden: location === 'hidden',
sort: newSortValue,
},
@@ -242,20 +242,20 @@ export default defineComponent({
// If field.sort isn't set yet, base it on the index of the array. That way, the
// new sort value will match what's visible on the screen
const sortValue =
field.system.sort || fields.findIndex((existingField) => existingField.field === field.field);
field.meta.sort || fields.findIndex((existingField) => existingField.field === field.field);
return {
field: field.field,
system: {
meta: {
sort: move === 'down' ? sortValue - 1 : sortValue + 1,
},
};
});
const sortOfItemOnNewIndex = fields[newIndex].system.sort || newIndex;
const sortOfItemOnNewIndex = fields[newIndex].meta.sort || newIndex;
updates.push({
field: element.field,
system: {
meta: {
sort: sortOfItemOnNewIndex,
},
});

View File

@@ -212,12 +212,12 @@ export default defineComponent({
const field: DeepPartial<Field> = {
field: primaryKeyFieldName.value,
type: 'integer',
system: {
meta: {
hidden: true,
interface: 'numeric',
readonly: true,
},
database: {
schema: {
has_auto_increment: true,
is_primary_key: true,
},
@@ -227,13 +227,13 @@ export default defineComponent({
return {
...field,
type: 'uuid',
system: {
...field.system,
meta: {
...field.meta,
interface: 'text-input',
special: 'uuid',
},
database: {
...field.database,
schema: {
...field.schema,
length: 36,
has_auto_increment: false,
}
@@ -242,14 +242,14 @@ export default defineComponent({
return {
...field,
type: 'string',
system: {
...field.system,
meta: {
...field.meta,
interface: 'text-input',
readonly: false,
hidden: false,
},
database: {
...field.database,
schema: {
...field.schema,
length: 255,
auto_increment: false,
}
@@ -267,7 +267,7 @@ export default defineComponent({
fields.push({
field: systemFields[0].name,
type: 'string',
system: {
meta: {
width: 'full',
required: true,
options: {
@@ -292,7 +292,7 @@ export default defineComponent({
},
interface: 'status',
},
database: {
schema: {
default_value: 'draft',
},
});
@@ -302,13 +302,13 @@ export default defineComponent({
fields.push({
field: systemFields[1].name,
type: 'integer',
system: {
meta: {
interface: 'sort',
hidden: true,
width: 'full',
special: 'sort',
},
database: {},
schema: {},
});
}
@@ -316,7 +316,7 @@ export default defineComponent({
// fields.push({
// field: systemFields[2].name,
// type: 'uuid',
// system: {
// meta: {
// special: 'user_created',
// interface: 'owner',
// options: {
@@ -327,7 +327,7 @@ export default defineComponent({
// hidden: true,
// width: 'full',
// },
// database: {},
// schema: {},
// });
// }
@@ -335,7 +335,7 @@ export default defineComponent({
// fields.push({
// field: systemFields[3].name,
// type: 'timestamp',
// system: {
// meta: {
// special: 'datetime_created',
// interface: 'datetime-created',
// readonly: true,
@@ -349,7 +349,7 @@ export default defineComponent({
// fields.push({
// field: systemFields[4].name,
// type: 'uuid',
// system: {
// meta: {
// special: 'user_updated',
// interface: 'user-updated',
// options: {
@@ -367,7 +367,7 @@ export default defineComponent({
// fields.push({
// field: systemFields[5].name,
// type: 'timestamp',
// system: {
// meta: {
// special: 'datetime_updated',
// interface: 'datetime-updated',
// readonly: true,

View File

@@ -1,4 +0,0 @@
import PermissionsFields from './permissions-fields.vue';
export { PermissionsFields };
export default PermissionsFields;

View File

@@ -1,210 +0,0 @@
<template>
<v-modal v-model="modalActive" :title="$t('select_fields')" persistent>
<template #activator="{ on }">
<span class="activator" @click="on" :class="{ limited: allAllowed === false }">
{{ allAllowed ? $t('all') : $t('limited') }}
</span>
</template>
<div class="fields">
<div class="read">
<p class="type-label">{{ $t('readable_fields') }}</p>
<v-checkbox
v-model="readableFields"
v-for="field in fields"
:value="field.field"
:key="field.field"
:indeterminate="readIndeterminate.includes(field.field)"
@update:indeterminate="readIndeterminate = readIndeterminate.filter((f) => f !== field.field)"
:label="field.name"
/>
</div>
<div class="write">
<p class="type-label">{{ $t('writable_fields') }}</p>
<v-checkbox
v-model="writableFields"
v-for="field in fields"
:value="field.field"
:key="field.field"
:label="field.name"
/>
</div>
</div>
<template #footer="{ close }">
<v-button secondary @click="close" :disabled="saving">{{ $t('cancel') }}</v-button>
<v-button @click="save" :loading="saving">{{ $t('save') }}</v-button>
</template>
</v-modal>
</template>
<script lang="ts">
import { defineComponent, ref, toRefs, computed, watch, PropType } from '@vue/composition-api';
import useCollection from '@/composables/use-collection';
import { Permission } from '../../composables/use-permissions';
import { intersection } from 'lodash';
export default defineComponent({
props: {
permissionId: {
type: Number,
default: undefined,
},
collection: {
type: String,
required: true,
},
role: {
type: Number,
required: true,
},
status: {
type: String,
default: null,
},
readBlacklist: {
type: Array as PropType<string[] | string[][]>,
required: true,
},
writeBlacklist: {
type: Array as PropType<string[] | string[][]>,
required: true,
},
savePermission: {
type: Function as PropType<(value: Partial<Permission>) => void>,
required: true,
},
combined: {
type: Boolean,
default: false,
},
},
setup(props) {
const { collection } = toRefs(props);
const { fields } = useCollection(collection);
const fieldKeys = computed(() => fields.value.map((field) => field.field));
const modalActive = ref(false);
const readableFields = ref<string[]>([]);
const writableFields = ref<string[]>([]);
const readIndeterminate = ref<string[]>([]);
const writeIndeterminate = ref<string[]>([]);
const allAllowed = computed(() => {
let blacklist = [...props.readBlacklist, ...props.writeBlacklist];
if (props.combined === true) {
blacklist = blacklist.flat();
}
return blacklist.length === 0;
});
watch(modalActive, (newVal) => {
if (newVal !== true) return;
if (props.combined === true) {
readableFields.value = invertBlacklist(intersection(...(props.readBlacklist as string[][])));
readIndeterminate.value = [...new Set(props.readBlacklist.flat())].filter((k) =>
readableFields.value.includes(k)
);
} else {
readableFields.value = invertBlacklist(props.readBlacklist as string[]);
}
if (props.combined === true) {
writableFields.value = invertBlacklist(intersection(...(props.writeBlacklist as string[][])));
writeIndeterminate.value = [...new Set(props.writeBlacklist.flat())].filter((k) =>
writableFields.value.includes(k)
);
} else {
writableFields.value = invertBlacklist(props.writeBlacklist as string[]);
}
});
const saving = ref(false);
return {
save,
fields,
modalActive,
readableFields,
writableFields,
saving,
allAllowed,
readIndeterminate,
writeIndeterminate,
};
async function save() {
saving.value = true;
const values: Partial<Permission> = {
collection: props.collection,
status: props.status,
role: props.role,
read_field_blacklist: fieldKeys.value.filter((key) => readableFields.value.includes(key) === false),
write_field_blacklist: fieldKeys.value.filter((key) => writableFields.value.includes(key) === false),
};
if (props.permissionId) {
values.id = props.permissionId;
}
await props.savePermission(values);
modalActive.value = false;
saving.value = false;
}
function invertBlacklist(blacklist: string[]) {
return fieldKeys.value.filter((key) => blacklist.includes(key) === false);
}
},
});
</script>
<style lang="scss" scoped>
.read,
.write {
display: grid;
grid-gap: 0 32px;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
.type-label {
grid-column: 1 / -1;
margin-bottom: 16px;
}
}
.read {
margin-bottom: 32px;
}
.limited {
color: var(--warning);
}
.activator {
position: relative;
width: max-content;
margin: -4px -8px;
margin-left: 32px;
padding: 4px 8px;
border-radius: var(--border-radius);
cursor: pointer;
&:hover {
background-color: var(--background-normal);
}
&:active {
background-color: var(--background-normal-alt);
}
}
</style>

View File

@@ -1,4 +0,0 @@
import PermissionsHeader from './permissions-header.vue';
export { PermissionsHeader };
export default PermissionsHeader;

View File

@@ -1,40 +0,0 @@
<template>
<div class="permissions-header">
<div class="name">{{ $tc('collection', 2) }}</div>
<v-icon name="add_circle" v-tooltip="$t('create')" />
<v-icon name="visibility" v-tooltip="$t('read')" />
<v-icon name="edit" v-tooltip="$t('update')" />
<v-icon name="delete" v-tooltip="$t('delete')" />
<v-icon name="comment" v-tooltip="$t('comment')" />
<div class="name fields">{{ $tc('field', 2) }}</div>
<div class="name">{{ $t('statuses') }}</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
export default defineComponent({});
</script>
<style lang="scss" scoped>
.permissions-header {
display: grid;
grid-gap: var(--grid-gap);
grid-template-columns: var(--grid-template-columns);
align-items: center;
padding: 8px 12px;
.v-icon {
--v-icon-color: var(--foreground-subdued);
}
.fields {
margin-left: 40px;
}
.name {
font-weight: 600;
}
}
</style>

View File

@@ -1,4 +0,0 @@
import PermissionsManagement from './permissions-management.vue';
export { PermissionsManagement };
export default PermissionsManagement;

View File

@@ -1,134 +0,0 @@
<template>
<div class="permissions-management">
<div
class="loading"
v-if="loading && permissions === null"
:style="{
'--rows': collectionKeys.normal.length,
}"
>
<v-progress-circular indeterminate />
</div>
<template v-else>
<permissions-header />
<permissions-row
v-for="key in collectionKeys.normal"
:key="key"
:collection="key"
:role="role"
:saved-permissions="getPermissionsForCollection(key)"
:save-permission="savePermission"
:save-all="saveAll"
/>
<div class="system" v-if="systemActive">
<permissions-row
v-for="key in collectionKeys.system"
system
:key="key"
:collection="key"
:role="role"
:saved-permissions="getPermissionsForCollection(key)"
:save-permission="savePermission"
:save-all="saveAll"
/>
</div>
<button @click="systemActive = !systemActive" class="system-toggle">
{{ systemActive ? $t('hide_system_collections') : $t('show_system_collections') }}
</button>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, ref, toRefs } from '@vue/composition-api';
import useCollectionsStore from '@/stores/collections';
import { orderBy } from 'lodash';
import PermissionsRow from '../permissions-row';
import usePermissions from '../../composables/use-permissions';
import PermissionsHeader from '../permissions-header';
export default defineComponent({
components: { PermissionsRow, PermissionsHeader },
props: {
role: {
type: Number,
required: true,
},
},
setup(props) {
const collectionsStore = useCollectionsStore();
const { role } = toRefs(props);
const collectionKeys = computed(() => {
const keys = orderBy(
collectionsStore.state.collections.map((collection) => collection.collection),
['collection'],
['asc']
);
return {
normal: keys.filter((key) => key.startsWith('directus_') === false),
system: keys.filter((key) => key.startsWith('directus_') === true),
};
});
const systemActive = ref(false);
const { loading, error, permissions, savePermission, saveAll } = usePermissions(role);
return {
collectionKeys,
systemActive,
loading,
error,
permissions,
getPermissionsForCollection,
savePermission,
saveAll,
};
function getPermissionsForCollection(key: string) {
return permissions.value?.filter((permission) => permission.collection === key);
}
},
});
</script>
<style lang="scss" scoped>
.permissions-management {
--grid-template-columns: 2fr repeat(5, 24px) repeat(2, 1fr) 24px;
--grid-gap: 0 8px;
max-width: 800px; // same as fields setup
border: 2px solid var(--border-normal);
border-radius: var(--border-radius);
}
.loading {
display: flex;
align-items: center;
justify-content: center;
height: calc((var(--rows) * (40px + 2px)) + 38px);
}
.system {
border-top: 2px solid var(--border-subdued);
}
.system-toggle {
display: block;
width: 100%;
padding: 8px 0;
color: var(--foreground-subdued);
background-color: var(--background-subdued);
border-bottom-right-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
transition: color var(--fast) var(--transition);
&:hover {
color: var(--foreground-normal);
}
}
</style>

View File

@@ -1,4 +0,0 @@
import PermissionsRow from './permissions-row.vue';
export { PermissionsRow };
export default PermissionsRow;

View File

@@ -1,378 +0,0 @@
<template>
<div class="permissions-row">
<div class="row">
<div class="name">{{ info.name }}</div>
<permissions-toggle
type="create"
:options="['none', 'full']"
:value="getCombinedPermission('create')"
:save-permission="saveForAllStatuses"
:collection="collection"
:role="role"
/>
<permissions-toggle
type="read"
:options="userCreatedField ? ['none', 'mine', 'role', 'full'] : ['none', 'full']"
:value="getCombinedPermission('read')"
:save-permission="saveForAllStatuses"
:collection="collection"
:role="role"
/>
<permissions-toggle
type="update"
:options="userCreatedField ? ['none', 'mine', 'role', 'full'] : ['none', 'full']"
:value="getCombinedPermission('update')"
:save-permission="saveForAllStatuses"
:collection="collection"
:role="role"
/>
<permissions-toggle
type="delete"
:options="userCreatedField ? ['none', 'mine', 'role', 'full'] : ['none', 'full']"
:value="getCombinedPermission('delete')"
:save-permission="saveForAllStatuses"
:collection="collection"
:role="role"
/>
<permissions-toggle
type="comment"
:options="['none', 'read', 'create', 'update', 'full']"
:value="getCombinedPermission('comment')"
:save-permission="saveForAllStatuses"
:collection="collection"
:role="role"
/>
<permissions-fields
:collection="collection"
:role="role"
:save-permission="saveForAllStatuses"
:read-blacklist="getCombinedPermission('read_field_blacklist')"
:write-blacklist="getCombinedPermission('write_field_blacklist')"
combined
/>
<permissions-statuses
v-if="statuses"
:collection="collection"
:role="role"
:save-permission="saveForAllStatuses"
:status-blacklist="getCombinedPermission('status_blacklist')"
:statuses="statuses"
combined
/>
<div class="spacer" v-else>--</div>
<v-icon @click="detailsOpen = !detailsOpen" :name="detailsOpen ? 'expand_less' : 'expand_more'" />
</div>
<div class="details" v-if="detailsOpen">
<div class="row">
<div class="name">
<v-icon class="sub-indicator" name="subdirectory_arrow_right" />
{{ $t('on_create') }}
</div>
<v-icon v-for="n in 5" :key="n" class="spacer" name="block" />
<permissions-fields
:collection="collection"
:role="role"
:save-permission="savePermission"
status="$create"
:permission-id="getPermissionValue('id', '$create')"
:read-blacklist="getPermissionValue('read_field_blacklist', '$create')"
:write-blacklist="getPermissionValue('write_field_blacklist', '$create')"
/>
<permissions-statuses
v-if="statuses"
:collection="collection"
:role="role"
:save-permission="savePermission"
status="$create"
:statuses="statuses"
:permission-id="getPermissionValue('id', '$create')"
:status-blacklist="getPermissionValue('status_blacklist', '$create')"
/>
<div class="spacer" v-else>--</div>
</div>
<template v-if="statuses">
<div class="row" v-for="status in statuses" :key="status.value">
<div class="name">
<v-icon class="sub-indicator" name="subdirectory_arrow_right" />
{{ status.name }}
</div>
<permissions-toggle
type="create"
:options="['none', 'full']"
:value="getPermissionValue('create', status.value)"
:status="status.value"
:save-permission="savePermission"
:permission-id="getPermissionValue('id', status.value)"
:collection="collection"
:role="role"
/>
<permissions-toggle
type="read"
:options="userCreatedField ? ['none', 'mine', 'role', 'full'] : ['none', 'full']"
:value="getPermissionValue('read', status.value)"
:status="status.value"
:save-permission="savePermission"
:permission-id="getPermissionValue('id', status.value)"
:collection="collection"
:role="role"
/>
<permissions-toggle
type="update"
:options="userCreatedField ? ['none', 'mine', 'role', 'full'] : ['none', 'full']"
:value="getPermissionValue('update', status.value)"
:status="status.value"
:save-permission="savePermission"
:permission-id="getPermissionValue('id', status.value)"
:collection="collection"
:role="role"
/>
<permissions-toggle
type="delete"
:options="userCreatedField ? ['none', 'mine', 'role', 'full'] : ['none', 'full']"
:value="getPermissionValue('delete', status.value)"
:status="status.value"
:save-permission="savePermission"
:permission-id="getPermissionValue('id', status.value)"
:collection="collection"
:role="role"
/>
<permissions-toggle
type="comment"
:options="['none', 'read', 'create', 'update', 'full']"
:value="getPermissionValue('comment', status.value)"
:status="status.value"
:save-permission="savePermission"
:permission-id="getPermissionValue('id', status.value)"
:collection="collection"
:role="role"
/>
<permissions-fields
:collection="collection"
:role="role"
:save-permission="savePermission"
:status="status.value"
:permission-id="getPermissionValue('id', status.value)"
:read-blacklist="getPermissionValue('read_field_blacklist', status.value)"
:write-blacklist="getPermissionValue('write_field_blacklist', status.value)"
/>
<permissions-statuses
v-if="statuses"
:collection="collection"
:role="role"
:save-permission="savePermission"
:status="status.value"
:statuses="statuses"
:permission-id="getPermissionValue('id', status.value)"
:status-blacklist="getPermissionValue('status_blacklist', status.value)"
/>
<div class="spacer" v-else>--</div>
<div class="spacer" />
</div>
</template>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, toRefs, ref, computed, PropType } from '@vue/composition-api';
import useCollection from '@/composables/use-collection';
import PermissionsToggle from '../permissions-toggle';
import PermissionsFields from '../permissions-fields';
import PermissionsStatuses from '../permissions-statuses';
import { Permission } from '../../composables/use-permissions';
function getDefaultPermission(collection: string, role: number, status?: string) {
const defaultPermission: Permission = {
collection: collection,
role: role,
create: 'none',
read: 'none',
update: 'none',
delete: 'none',
comment: 'none',
read_field_blacklist: [],
write_field_blacklist: [],
status_blacklist: [],
status: status || null,
};
return defaultPermission;
}
export default defineComponent({
components: { PermissionsToggle, PermissionsFields, PermissionsStatuses },
props: {
role: {
type: Number,
required: true,
},
collection: {
type: String,
required: true,
},
system: {
type: Boolean,
default: false,
},
savedPermissions: {
type: Array as PropType<Permission[]>,
required: true,
},
savePermission: {
type: Function,
required: true,
},
saveAll: {
type: Function as PropType<(create: Partial<Permission>[], update: Partial<Permission>[]) => void>,
required: true,
},
},
setup(props) {
const { collection } = toRefs(props);
const { fields, info, statusField, userCreatedField } = useCollection(collection);
const detailsOpen = ref(false);
type Status = {
value: string;
name: string;
};
const statuses = computed<Status[] | null>(() => {
if (statusField.value && statusField.value.system.options) {
return Object.keys(statusField.value.system.options.status_mapping).map((key: string) => ({
...statusField.value?.system.options?.status_mapping[key],
value: key,
}));
}
return null;
});
const permissions = computed<Permission[]>(() => {
const createPermission =
props.savedPermissions?.find((permission) => permission.status === '$create') ||
getDefaultPermission(props.collection, props.role, '$create');
if (statusField.value && statuses.value) {
const statusPermissions = statuses.value.map((status) => {
const existing = props.savedPermissions.find((permission) => permission.status === status.value);
return existing || getDefaultPermission(props.collection, props.role, status.value);
});
return [...statusPermissions, createPermission];
} else {
const collectionPermission =
props.savedPermissions.find((permission) => permission.status === null) ||
getDefaultPermission(props.collection, props.role);
return [collectionPermission, createPermission];
}
});
return {
info,
fields,
statusField,
statuses,
detailsOpen,
permissions,
userCreatedField,
getPermissionValue,
getCombinedPermission,
saveForAllStatuses,
};
function getPermissionValue(type: keyof Permission, status: string | null = null) {
const permission = permissions.value.find((permission) => permission.status === status);
return permission?.[type];
}
function getCombinedPermission(type: keyof Permission) {
if (type.endsWith('_blacklist')) {
return permissions.value.map((permission) => permission[type]);
}
if (statusField.value) {
let value = permissions.value[0][type];
for (const permission of permissions.value.filter(({ status }) => status !== '$create')) {
if (value !== permission[type]) {
value = 'indeterminate';
break;
}
}
return value;
} else {
const permission = permissions.value.find((permission) => permission.status === null);
return permission?.[type];
}
}
async function saveForAllStatuses(updates: Partial<Permission>) {
const create: Partial<Permission>[] = [];
const update: Partial<Permission>[] = [];
permissions.value.forEach((permission) => {
if (permission.id) {
update.push({
...updates,
id: permission.id,
status: permission.status,
});
} else {
create.push({
...updates,
status: permission.status,
});
}
});
await props.saveAll(create, update);
}
},
});
</script>
<style lang="scss" scoped>
.row {
display: grid;
grid-gap: var(--grid-gap);
grid-template-columns: var(--grid-template-columns);
align-items: center;
padding: 8px 12px;
}
.permissions-row:not(:first-child),
.details .row:first-child {
border-top: 2px solid var(--border-subdued);
}
.details {
grid-column: 1 / -1;
background-color: var(--background-subdued);
}
.sub-indicator {
--v-icon-color: var(--foreground-subdued);
}
.spacer {
color: var(--foreground-subdued);
}
</style>

View File

@@ -1,4 +0,0 @@
import PermissionsStatuses from './permissions-statuses.vue';
export { PermissionsStatuses };
export default PermissionsStatuses;

View File

@@ -1,158 +0,0 @@
<template>
<v-modal v-model="modalActive" :title="$t('select_statuses')" persistent>
<template #activator="{ on }">
<span class="activator" @click="on" :class="{ limited: allAllowed === false }">
{{ allAllowed ? $t('all') : $t('limited') }}
</span>
</template>
<div class="statuses">
<v-checkbox
v-model="allowedStatuses"
v-for="status in statuses"
:value="status.value"
:key="status.value"
:indeterminate="indeterminate.includes(status.value)"
@update:indeterminate="indeterminate = indeterminate.filter((s) => s !== status.value)"
:label="status.name"
/>
</div>
<template #footer="{ close }">
<v-button secondary @click="close" :disabled="saving">{{ $t('cancel') }}</v-button>
<v-button @click="save" :loading="saving">{{ $t('save') }}</v-button>
</template>
</v-modal>
</template>
<script lang="ts">
import { defineComponent, ref, computed, watch, PropType } from '@vue/composition-api';
import { Permission } from '../../composables/use-permissions';
import { intersection } from 'lodash';
export default defineComponent({
props: {
permissionId: {
type: Number,
default: undefined,
},
collection: {
type: String,
required: true,
},
role: {
type: Number,
required: true,
},
status: {
type: String,
default: null,
},
statuses: {
type: Array,
required: true,
},
statusBlacklist: {
type: Array as PropType<string[] | string[][]>,
required: true,
},
savePermission: {
type: Function as PropType<(value: Partial<Permission>) => void>,
required: true,
},
combined: {
type: Boolean,
default: false,
},
},
setup(props) {
const modalActive = ref(false);
const allowedStatuses = ref<string[]>([]);
const indeterminate = ref<string[]>([]);
const statusKeys = computed(() => props.statuses.map((status: any) => status.value));
const allAllowed = computed(() => {
let blacklist = [...props.statusBlacklist];
if (props.combined === true) {
blacklist = blacklist.flat();
}
return blacklist.length === 0;
});
watch(modalActive, (newVal) => {
if (newVal !== true) return;
if (props.combined === true) {
allowedStatuses.value = invertBlacklist(intersection(...(props.statusBlacklist as string[][])));
allowedStatuses.value = [...new Set(props.statusBlacklist.flat())].filter((k) =>
allowedStatuses.value.includes(k)
);
} else {
allowedStatuses.value = invertBlacklist(props.statusBlacklist as string[]);
}
});
const saving = ref(false);
return {
save,
modalActive,
saving,
allAllowed,
allowedStatuses,
indeterminate,
};
async function save() {
saving.value = true;
const values: Partial<Permission> = {
collection: props.collection,
status: props.status,
role: props.role,
status_blacklist: statusKeys.value.filter((key) => allowedStatuses.value.includes(key) === false),
};
if (props.permissionId) {
values.id = props.permissionId;
}
await props.savePermission(values);
modalActive.value = false;
saving.value = false;
}
function invertBlacklist(blacklist: string[]) {
return statusKeys.value.filter((key) => blacklist.includes(key) === false);
}
},
});
</script>
<style lang="scss" scoped>
.limited {
color: var(--warning);
}
.activator {
position: relative;
width: max-content;
margin: -4px -8px;
padding: 4px 8px;
border-radius: var(--border-radius);
cursor: pointer;
&:hover {
background-color: var(--background-normal);
}
&:active {
background-color: var(--background-normal-alt);
}
}
</style>

View File

@@ -1,4 +0,0 @@
import PermissionsToggle from './permissions-toggle.vue';
export { PermissionsToggle };
export default PermissionsToggle;

View File

@@ -1,179 +0,0 @@
<template>
<v-menu show-arrow placement="left-start" class="permissions-toggle" close-on-content-click :disabled="saving">
<template #activator="{ toggle }">
<span>
<v-progress-circular class="spinner" indeterminate small v-if="saving" />
<div class="box" :class="value" v-else @click="toggle">
<v-icon v-if="iconName" :name="iconName" />
</div>
</span>
</template>
<v-list dense>
<v-list-item
v-for="option in _options"
:key="option.value"
:active="value === option.value"
@click="save(option.value)"
>
<v-list-item-icon>
<div class="box" :class="option.value">
<v-icon v-if="option.icon" :name="option.icon" />
</div>
</v-list-item-icon>
<v-list-item-content>{{ option.name }}</v-list-item-content>
</v-list-item>
</v-list>
</v-menu>
</template>
<script lang="ts">
import { defineComponent, PropType, computed, ref } from '@vue/composition-api';
import i18n from '@/lang';
import { Permission } from '../../composables/use-permissions';
export default defineComponent({
props: {
value: {
type: String,
required: true,
},
options: {
type: Array as PropType<string[]>,
required: true,
},
loading: {
type: Boolean,
default: false,
},
status: {
type: String,
default: null,
},
savePermission: {
type: Function as PropType<(values: Partial<Permission>) => {}>,
required: true,
},
type: {
type: String,
required: true,
},
permissionId: {
type: Number,
default: undefined,
},
collection: {
type: String,
required: true,
},
role: {
type: Number,
required: true,
},
},
setup(props) {
const iconName = computed(() => {
return getIconForValue(props.value);
});
const _options = computed(() => {
return props.options.map((option) => ({
value: option,
name: i18n.t(option),
icon: getIconForValue(option),
}));
});
const saving = ref(false);
return { iconName, _options, save, saving };
async function save(newValue: string) {
saving.value = true;
const values: Partial<Permission> = {
[props.type]: newValue,
collection: props.collection,
status: props.status,
role: props.role,
};
if (props.permissionId) {
values.id = props.permissionId;
}
await props.savePermission(values);
saving.value = false;
}
function getIconForValue(value: string) {
switch (value) {
case 'indeterminate':
return 'remove';
case 'mine':
return 'person';
case 'role':
return 'group';
case 'full':
return 'check';
case 'read':
return 'remove_red_eye';
case 'create':
return 'add';
case 'update':
return 'edit';
case 'none':
return null;
default:
return 'check_box_outline_blank';
}
}
},
});
</script>
<style lang="scss" scoped>
.box {
--color: var(--foreground-subdued);
position: relative;
left: 3px; // aligns it better with regular material icons
display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
background-color: var(--color);
border: 2px solid transparent;
border-radius: 2px;
cursor: pointer;
&.none {
--color: transparent;
border-color: var(--foreground-subdued);
}
&.indeterminate {
--color: var(--foreground-subdued);
}
&.mine {
--color: #ff9800;
}
&.role {
--color: #fbc02d;
}
&.full {
--color: var(--success);
}
.v-icon {
--v-icon-size: 14px;
--v-icon-color: var(--foreground-inverted);
}
}
</style>

View File

@@ -92,7 +92,6 @@ import router from '@/router';
import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail';
import useItem from '@/composables/use-item';
import SaveOptions from '@/views/private/components/save-options';
import PermissionsManagement from './components/permissions-management';
import marked from 'marked';
import useUserStore from '@/stores/user';
import RoleInfoDrawerDetail from './components/role-info-drawer-detail';
@@ -103,7 +102,7 @@ type Values = {
export default defineComponent({
name: 'roles-detail',
components: { SettingsNavigation, RevisionsDrawerDetail, SaveOptions, PermissionsManagement, RoleInfoDrawerDetail },
components: { SettingsNavigation, RevisionsDrawerDetail, SaveOptions, RoleInfoDrawerDetail },
props: {
primaryKey: {
type: String,

View File

@@ -13,10 +13,10 @@ import { merge } from 'lodash';
const fakeFilesField: Field = {
collection: 'directus_files',
field: '$file',
database: null,
schema: null,
name: i18n.t('file'),
type: 'integer',
system: {
meta: {
id: -1,
collection: 'directus_files',
field: '$file',
@@ -37,7 +37,7 @@ const fakeFilesField: Field = {
},
};
function getSystemDefault(collection: string, field: string): Field['system'] {
function getMetaDefault(collection: string, field: string): Field['meta'] {
return {
id: -1,
collection,
@@ -88,13 +88,13 @@ export const useFieldsStore = createStore({
parseField(field: FieldRaw): Field {
let name: string | VueI18n.TranslateResult;
const system = field.system === null ? getSystemDefault(field.collection, field.field) : field.system;
const meta = field.meta === null ? getMetaDefault(field.collection, field.field) : field.meta;
if (i18n.te(`fields.${field.collection}.${field.field}`)) {
name = i18n.t(`fields.${field.collection}.${field.field}`);
} else if (notEmpty(system.translation) && system.translation.length > 0) {
for (let i = 0; i < system.translation.length; i++) {
const { locale, translation } = system.translation[i];
} else if (notEmpty(meta.translation) && meta.translation.length > 0) {
for (let i = 0; i < meta.translation.length; i++) {
const { locale, translation } = meta.translation[i];
i18n.mergeLocaleMessage(locale, {
fields: {
@@ -113,7 +113,7 @@ export const useFieldsStore = createStore({
return {
...field,
name,
system,
meta,
};
},
async createField(collectionKey: string, newField: Field) {
@@ -268,7 +268,7 @@ export const useFieldsStore = createStore({
/** @NOTE it's safe to assume every collection has a primary key */
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const primaryKeyField = this.state.fields.find(
(field) => field.collection === collection && field.database?.is_primary_key === true
(field) => field.collection === collection && field.schema?.is_primary_key === true
);
return primaryKeyField;

View File

@@ -26,7 +26,7 @@ export const types = [
'unknown',
] as const;
export type DatabaseColumn = {
export type FieldSchema = {
/** @todo import this from knex-schema-inspector when that's launched */
name: string;
table: string;
@@ -45,7 +45,7 @@ export type DatabaseColumn = {
foreign_key_schema?: string | null;
};
export type SystemField = {
export type FieldMeta = {
id: number;
collection: string;
field: string;
@@ -70,12 +70,12 @@ export interface FieldRaw {
field: string;
type: typeof types[number];
database: DatabaseColumn | null;
system: SystemField | null;
schema: FieldSchema | null;
meta: FieldMeta | null;
}
export interface Field extends FieldRaw {
name: string | TranslateResult;
type: typeof types[number];
system: SystemField;
meta: FieldMeta;
}

View File

@@ -10,9 +10,9 @@ export default function adjustFieldsForDisplays(fields: readonly string[], paren
const field: Field = fieldsStore.getField(parentCollection, fieldKey);
if (!field) return fieldKey;
if (field.system?.display === null) return fieldKey;
if (field.meta?.display === null) return fieldKey;
const display = displays.find((d) => d.id === field.system?.display);
const display = displays.find((d) => d.id === field.meta?.display);
if (!display) return fieldKey;
if (!display?.fields) return fieldKey;
@@ -23,7 +23,7 @@ export default function adjustFieldsForDisplays(fields: readonly string[], paren
if (typeof display.fields === 'function') {
return display
.fields(field.system?.display_options, {
.fields(field.meta?.display_options, {
collection: field.collection,
field: field.field,
type: field.type,

View File

@@ -73,7 +73,7 @@ export default defineComponent({
return fieldsStore
.getFieldsForCollection(props.collection)
.filter(
(field: Field) => field.system?.hidden !== true && field.system?.special?.toLowerCase() !== 'alias'
(field: Field) => field.meta?.hidden !== true && field.meta?.special?.toLowerCase() !== 'alias'
)
.map((field: Field) => parseField(field, []));
@@ -103,8 +103,8 @@ export default defineComponent({
.getFieldsForCollection(relatedCollection)
.filter(
(field: Field) =>
field.system?.hidden !== true &&
field.system?.special?.toLowerCase() !== 'alias'
field.meta?.hidden !== true &&
field.meta?.special?.toLowerCase() !== 'alias'
);
})
.flat()

View File

@@ -64,9 +64,9 @@ export default defineComponent({
if (value === undefined) return '???';
// If no display is configured, we can render the raw value
if (field.system?.display === null) return value;
if (field.meta?.display === null) return value;
const displayInfo = displays.find((display) => display.id === field.system?.display);
const displayInfo = displays.find((display) => display.id === field.meta?.display);
// If used display doesn't exist in the current project, return raw value
if (!displayInfo) return value;
@@ -74,16 +74,16 @@ export default defineComponent({
// If the display handler is a function, we parse the value and return the result
if (typeof displayInfo.handler === 'function') {
const handler = displayInfo.handler as Function;
return handler(value, field.system?.display_options);
return handler(value, field.meta?.display_options);
}
return {
component: field.system?.display,
options: field.system?.display_options,
component: field.meta?.display,
options: field.meta?.display_options,
value: value,
interface: field.system?.interface,
interfaceOptions: field.system?.options,
type: field.system?.special /** @todo check what this is used for */,
interface: field.meta?.interface,
interfaceOptions: field.meta?.options,
type: field.meta?.special /** @todo check what this is used for */,
};
})
.map((p) => p)