Various changes and fixes

This commit is contained in:
rijkvanzanten
2020-07-03 12:48:45 -04:00
parent e506b2dd95
commit be20289ae5
13 changed files with 249 additions and 147 deletions

View File

@@ -18,19 +18,21 @@ export function useCollection(collection: Ref<string>) {
const primaryKeyField = computed(() => {
// 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.primary_key === true)!;
return fields.value?.find(
(field) => field.collection === collection.value && field.database.is_primary_key === true
)!;
});
const userCreatedField = computed(() => {
return fields.value?.find((field) => field.type === 'user_created') || null;
return fields.value?.find((field) => field.system?.special === 'user_created') || null;
});
const statusField = computed(() => {
return fields.value?.find((field) => field.type === 'status') || null;
return fields.value?.find((field) => field.system?.special === 'status') || null;
});
const sortField = computed(() => {
return fields.value?.find((field) => field.type === 'sort') || null;
return fields.value?.find((field) => field.system?.special === 'sort') || null;
});
type Status = {
@@ -48,7 +50,7 @@ export function useCollection(collection: Ref<string>) {
const softDeleteStatus = computed<string | null>(() => {
if (statusField.value === null) return null;
const statuses = Object.values(statusField.value?.options?.status_mapping || {});
const statuses = Object.values(statusField.value?.system?.options?.status_mapping || {});
return (
(statuses.find((status) => (status as Status).soft_delete === true) as Status | undefined)?.value || null
);

View File

@@ -12,7 +12,10 @@ export default function useFieldTree(collection: Ref<string>) {
const tree = computed<FieldTree[]>(() => {
return fieldsStore
.getFieldsForCollection(collection.value)
.filter((field: Field) => field.hidden_browse === false && field.type.toLowerCase() !== 'alias')
.filter(
(field: Field) =>
field.system?.hidden_browse === false && field.system?.special?.toLowerCase() !== 'alias'
)
.map((field: Field) => parseField(field, []));
function parseField(field: Field, parents: Field[]) {
@@ -40,7 +43,9 @@ export default function useFieldTree(collection: Ref<string>) {
return fieldsStore
.getFieldsForCollection(relatedCollection)
.filter(
(field: Field) => field.hidden_browse === false && field.type.toLowerCase() !== 'alias'
(field: Field) =>
field.system?.hidden_browse === false &&
field.system?.special?.toLowerCase() !== 'alias'
);
})
.flat()

View File

@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { computed, Ref } from '@vue/composition-api';
import { isEmpty } from '@/utils/is-empty';
import getDefaultInterfaceForType from '@/utils/get-default-interface-for-type';
@@ -14,37 +16,36 @@ export default function useFormFields(fields: Ref<Field[]>) {
*
* This can be optimized by combining a bunch of these maps and filters
*/
// Filter out the fields that are marked hidden on detail
formFields = formFields.filter((field) => {
const hiddenDetail = field.hidden_detail;
const hiddenDetail = field.system?.hidden_detail;
if (isEmpty(hiddenDetail)) return true;
return hiddenDetail === false;
});
// Sort the fields on the sort column value
formFields = formFields.sort((a, b) => {
if (a.sort == b.sort) return 0;
if (a.sort === null || a.sort === undefined) return 1;
if (b.sort === null || b.sort === undefined) return -1;
return a.sort > b.sort ? 1 : -1;
if (a.system!.sort == b.system!.sort) return 0;
if (a.system!.sort === null || a.system!.sort === undefined) return 1;
if (b.system!.sort === null || b.system!.sort === undefined) return -1;
return a.system!.sort > b.system!.sort ? 1 : -1;
});
// Make sure all form fields have a width associated with it
formFields = formFields.map((field) => {
if (!field.width) {
field.width = 'full';
if (!field.system!.width) {
field.system!.width = 'full';
}
return field;
});
formFields = formFields.map((field) => {
const interfaceUsed = interfaces.find((int) => int.id === field.interface);
const interfaceUsed = interfaces.find((int) => int.id === field.system!.interface);
const interfaceExists = interfaceUsed !== undefined;
if (interfaceExists === false) {
field.interface = getDefaultInterfaceForType(field.type);
field.system!.interface = getDefaultInterfaceForType(field.system!.type);
}
if (interfaceUsed?.hideLabel === true) {
@@ -63,11 +64,11 @@ export default function useFormFields(fields: Ref<Field[]>) {
formFields = formFields.map((field, index, formFields) => {
if (index === 0) return field;
if (field.width === 'half') {
if (field.system!.width === 'half') {
const prevField = formFields[index - 1];
if (prevField.width === 'half') {
field.width = 'half-right';
if (prevField.system!.width === 'half') {
field.system!.width = 'half-right';
}
}

View File

@@ -227,7 +227,7 @@ export default defineComponent({
const { info, primaryKeyField, fields: fieldsInCollection, sortField } = useCollection(collection);
const availableFields = computed(() =>
fieldsInCollection.value.filter(({ hidden_browse }) => hidden_browse === false)
fieldsInCollection.value.filter((field) => field.system?.hidden_browse === false)
);
const { sort, limit, page, fields, fieldsWithRelational } = useItemOptions();

View File

@@ -14,11 +14,11 @@ export function defineModule(config: ModuleDefineParam | ((context: ModuleContex
if (options.routes !== undefined) {
options.routes = options.routes.map((route) => {
if (route.path) {
route.path = `/:project/${options.id}${route.path}`;
route.path = `/${options.id}${route.path}`;
}
if (route.redirect) {
route.redirect = `/:project/${options.id}${route.redirect}`;
route.redirect = `/${options.id}${route.redirect}`;
}
return route;

View File

@@ -10,7 +10,7 @@ const moduleRoutes = modules
replaceRoutes((routes) => insertBeforeProjectWildcard(routes, moduleRoutes));
export function insertBeforeProjectWildcard(currentRoutes: RouteConfig[], routesToBeAdded: RouteConfig[]) {
// Find the index of the /:project/* route, so we can insert the module routes right above that
const wildcardIndex = currentRoutes.findIndex((route) => route.path === '/:project/*');
// Find the index of the /* route, so we can insert the module routes right above that
const wildcardIndex = currentRoutes.findIndex((route) => route.path === '/*');
return [...currentRoutes.slice(0, wildcardIndex), ...routesToBeAdded, ...currentRoutes.slice(wildcardIndex)];
}

View File

@@ -8,37 +8,57 @@ import formatTitle from '@directus/format-title';
import notify from '@/utils/notify';
import useRelationsStore from '@/stores/relations';
import { Relation } from '@/stores/relations/types';
import getLocalType from '@/utils/get-local-type';
const fakeFilesField: Field = {
id: -1,
collection: 'directus_files',
field: '$file',
database: null,
name: i18n.t('file'),
datatype: null,
type: 'file',
unique: false,
primary_key: false,
default_value: null,
auto_increment: false,
note: null,
signed: false,
sort: 0,
interface: null,
options: null,
display: 'file',
display_options: null,
hidden_detail: true,
hidden_browse: false,
locked: true,
required: false,
translation: null,
readonly: true,
width: 'full',
validation: null,
group: null,
length: null,
type: 'integer',
system: {
id: -1,
collection: 'directus_files',
field: '$file',
sort: null,
special: null,
interface: null,
options: null,
display: 'file',
display_options: null,
hidden_detail: true,
hidden_browse: false,
locked: true,
required: false,
translation: null,
readonly: true,
width: 'full',
group: null,
},
};
function getSystemDefault(collection: string, field: string): Field['system'] {
return {
id: -1,
collection,
field,
group: null,
hidden_browse: false,
hidden_detail: false,
interface: null,
display: null,
display_options: null,
locked: false,
options: null,
readonly: false,
required: false,
sort: null,
special: null,
translation: null,
width: 'full',
};
}
export const useFieldsStore = createStore({
id: 'fieldsStore',
state: () => ({
@@ -62,17 +82,20 @@ export const useFieldsStore = createStore({
* is a fake m2o to itself.
*/
this.state.fields = [...fields.map(this.addTranslationsForField), fakeFilesField];
this.state.fields = [...fields.map(this.parseField), fakeFilesField];
},
async dehydrate() {
this.reset();
},
addTranslationsForField(field: Field) {
parseField(field: FieldRaw): Field {
let name: string | VueI18n.TranslateResult;
if (notEmpty(field.translation) && field.translation.length > 0) {
for (let i = 0; i < field.translation.length; i++) {
const { locale, translation } = field.translation[i];
const type = field.database === null ? 'alias' : getLocalType(field.database.type);
const system = field.system === null ? getSystemDefault(field.collection, field.field) : field.system;
if (notEmpty(system.translation) && system.translation.length > 0) {
for (let i = 0; i < system.translation.length; i++) {
const { locale, translation } = system.translation[i];
i18n.mergeLocaleMessage(locale, {
fields: {
@@ -89,6 +112,8 @@ export const useFieldsStore = createStore({
return {
...field,
name,
type,
system,
};
},
async createField(collectionKey: string, newField: Field) {
@@ -247,7 +272,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.primary_key === true
(field) => field.collection === collection && field.database?.is_primary_key === true
);
return primaryKeyField;

View File

@@ -7,98 +7,72 @@ type Translation = {
export type Width = 'half' | 'half-left' | 'half-right' | 'full' | 'fill';
export type Type =
export type LocalType =
| 'alias'
| 'array'
| 'boolean'
| 'bigInteger'
| 'binary'
| 'datetime'
| 'binary'
| 'boolean'
| 'date'
| 'time'
| 'file'
| 'files'
| 'hash'
| 'group'
| 'integer'
| 'datetime'
| 'decimal'
| 'json'
| 'lang'
| 'm2o'
| 'o2m'
| 'm2m'
| 'slug'
| 'sort'
| 'status'
| 'float'
| 'integer'
| 'string'
| 'translation'
| 'uuid'
| 'datetime_created'
| 'datetime_updated'
| 'user_created'
| 'user_updated'
| 'user';
| 'text'
| 'time'
| 'timestamp'
| 'unknown';
export const types: Type[] = [
'alias',
'array',
'boolean',
'binary',
'datetime',
'date',
'time',
'file',
'files',
'hash',
'group',
'integer',
'decimal',
'json',
'lang',
'm2o',
'o2m',
'm2m',
'slug',
'sort',
'status',
'string',
'translation',
'uuid',
'datetime_created',
'datetime_updated',
'user_created',
'user_updated',
'user',
];
export type DatabaseColumn = {
/** @todo import this from knex-schema-inspector when that's launched */
name: string;
table: string;
type: string;
default_value: any | null;
max_length: number | null;
is_nullable: boolean;
is_primary_key: boolean;
has_auto_increment: boolean;
foreign_key_column: string | null;
foreign_key_table: string | null;
comment: string | null;
export interface FieldRaw {
// Postgres Only
schema?: string;
foreign_key_schema?: string | null;
};
export type SystemField = {
id: number;
collection: string;
field: string;
datatype: string | null;
unique: boolean;
primary_key: boolean;
auto_increment: boolean;
default_value: any; // eslint-disable-line @typescript-eslint/no-explicit-any
note: string | TranslateResult | null;
signed: boolean;
type: Type;
sort: null | number;
interface: string | null;
options: null | { [key: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any
display: string | null;
display_options: null | { [key: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any
hidden_detail: boolean;
hidden_browse: boolean;
required: boolean;
locked: boolean;
translation: null | Translation[];
readonly: boolean;
width: null | Width;
validation: string | null;
group: number | null;
length: string | number | null;
hidden_browse: boolean;
hidden_detail: boolean;
locked: boolean;
interface: string | null;
display: string | null;
options: null | Record<string, any>;
display_options: null | Record<string, any>;
readonly: boolean;
required: boolean;
sort: number | null;
special: string | null;
translation: null | Translation[];
width: Width | null;
};
export interface FieldRaw {
collection: string;
field: string;
database: DatabaseColumn | null;
system: SystemField | null;
}
export interface Field extends FieldRaw {
name: string | TranslateResult;
type: LocalType;
system: SystemField;
}

View File

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

View File

@@ -0,0 +1,87 @@
import { LocalType } from '@/stores/fields/types';
/**
* Typemap graciously provided by @gpetrov
*/
const localTypeMap: Record<string, { type: LocalType; useTz?: boolean }> = {
// Shared
boolean: { type: 'boolean' },
tinyint: { type: 'boolean' },
smallint: { type: 'integer' },
mediumint: { type: 'integer' },
int: { type: 'integer' },
integer: { type: 'integer' },
serial: { type: 'integer' },
bigint: { type: 'bigInteger' },
bigserial: { type: 'bigInteger' },
clob: { type: 'text' },
tinytext: { type: 'text' },
mediumtext: { type: 'text' },
longtext: { type: 'text' },
text: { type: 'text' },
varchar: { type: 'string' },
longvarchar: { type: 'string' },
varchar2: { type: 'string' },
nvarchar: { type: 'string' },
image: { type: 'binary' },
ntext: { type: 'text' },
char: { type: 'string' },
date: { type: 'date' },
datetime: { type: 'datetime' },
timestamp: { type: 'timestamp' },
time: { type: 'time' },
float: { type: 'float' },
double: { type: 'float' },
'double precision': { type: 'float' },
real: { type: 'float' },
decimal: { type: 'decimal' },
numeric: { type: 'integer' },
// MySQL
string: { type: 'text' },
year: { type: 'integer' },
blob: { type: 'binary' },
mediumblob: { type: 'binary' },
// MS SQL
bit: { type: 'boolean' },
smallmoney: { type: 'float' },
money: { type: 'float' },
datetimeoffset: { type: 'datetime', useTz: true },
datetime2: { type: 'datetime' },
smalldatetime: { type: 'datetime' },
nchar: { type: 'text' },
binary: { type: 'binary' },
varbinary: { type: 'binary' },
// Postgres
int2: { type: 'integer' },
serial4: { type: 'integer' },
int4: { type: 'integer' },
serial8: { type: 'integer' },
int8: { type: 'integer' },
bool: { type: 'boolean' },
'character varying': { type: 'string' },
character: { type: 'string' },
interval: { type: 'string' },
_varchar: { type: 'string' },
bpchar: { type: 'string' },
timestamptz: { type: 'timestamp' },
'timestamp with time zone': { type: 'timestamp', useTz: true },
'timestamp without thime zone': { type: 'timestamp' },
timetz: { type: 'time' },
'time with time zone': { type: 'time', useTz: true },
'time without time zone': { type: 'time' },
float4: { type: 'float' },
float8: { type: 'float' },
};
export default function getLocalType(databaseType: string) {
const type = localTypeMap[databaseType];
if (type) {
return type.type;
}
return 'unknown';
}

View File

@@ -0,0 +1,4 @@
import getLocalType from './get-local-type';
export { getLocalType };
export default getLocalType;

View File

@@ -72,7 +72,10 @@ export default defineComponent({
const fieldTree = computed<FieldTree[]>(() => {
return fieldsStore
.getFieldsForCollection(props.collection)
.filter((field: Field) => field.hidden_browse === false && field.type.toLowerCase() !== 'alias')
.filter(
(field: Field) =>
field.system?.hidden_browse !== true && field.system?.special?.toLowerCase() !== 'alias'
)
.map((field: Field) => parseField(field, []));
function parseField(field: Field, parents: Field[]) {
@@ -101,7 +104,8 @@ export default defineComponent({
.getFieldsForCollection(relatedCollection)
.filter(
(field: Field) =>
field.hidden_browse === false && field.type.toLowerCase() !== 'alias'
field.system?.hidden_browse !== true &&
field.system?.special?.toLowerCase() !== 'alias'
);
})
.flat()

View File

@@ -64,9 +64,9 @@ export default defineComponent({
if (value === undefined) return '???';
// If no display is configured, we can render the raw value
if (field.display === null) return value;
if (field.system?.display === null) return value;
const displayInfo = displays.find((display) => display.id === field.display);
const displayInfo = displays.find((display) => display.id === field.system?.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.display_options);
return handler(value, field.system?.display_options);
}
return {
component: field.display,
options: field.display_options,
component: field.system?.display,
options: field.system?.display_options,
value: value,
interface: field.interface,
interfaceOptions: field.options,
type: field.type,
interface: field.system?.interface,
interfaceOptions: field.system?.options,
type: field.system?.special /** @todo check what this is used for */,
};
})
.map((p) => p)