mirror of
https://github.com/directus/directus.git
synced 2026-02-02 08:35:17 -05:00
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
) {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export default defineInterface(({ i18n }) => ({
|
||||
field: 'template',
|
||||
name: i18n.t('display_template'),
|
||||
type: 'string',
|
||||
system: {
|
||||
meta: {
|
||||
width: 'half',
|
||||
interface: 'text-input',
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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)',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
})
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import PermissionsFields from './permissions-fields.vue';
|
||||
|
||||
export { PermissionsFields };
|
||||
export default PermissionsFields;
|
||||
@@ -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>
|
||||
@@ -1,4 +0,0 @@
|
||||
import PermissionsHeader from './permissions-header.vue';
|
||||
|
||||
export { PermissionsHeader };
|
||||
export default PermissionsHeader;
|
||||
@@ -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>
|
||||
@@ -1,4 +0,0 @@
|
||||
import PermissionsManagement from './permissions-management.vue';
|
||||
|
||||
export { PermissionsManagement };
|
||||
export default PermissionsManagement;
|
||||
@@ -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>
|
||||
@@ -1,4 +0,0 @@
|
||||
import PermissionsRow from './permissions-row.vue';
|
||||
|
||||
export { PermissionsRow };
|
||||
export default PermissionsRow;
|
||||
@@ -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>
|
||||
@@ -1,4 +0,0 @@
|
||||
import PermissionsStatuses from './permissions-statuses.vue';
|
||||
|
||||
export { PermissionsStatuses };
|
||||
export default PermissionsStatuses;
|
||||
@@ -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>
|
||||
@@ -1,4 +0,0 @@
|
||||
import PermissionsToggle from './permissions-toggle.vue';
|
||||
|
||||
export { PermissionsToggle };
|
||||
export default PermissionsToggle;
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user