mirror of
https://github.com/directus/directus.git
synced 2026-04-03 03:00:39 -04:00
New collection modal (#333)
* WIP start on collections modal * Add titles / system optional fields * Finish add collection modal * Fix readonly state in text input * Fix default value in v-form
This commit is contained in:
@@ -49,10 +49,14 @@
|
||||
<div class="interface">
|
||||
<v-skeleton-loader v-if="loading" />
|
||||
<interface-text-input
|
||||
:disabled="field.readonly"
|
||||
:value="values[field.field] || field.default_value || null"
|
||||
@input="setValue(field, $event)"
|
||||
v-bind="field.options"
|
||||
:disabled="field.readonly"
|
||||
:value="
|
||||
values[field.field] === undefined
|
||||
? field.default_value
|
||||
: values[field.field]
|
||||
"
|
||||
@input="setValue(field, $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
<v-input
|
||||
:monospace="monospace"
|
||||
:value="value"
|
||||
@input="$listeners.input"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
full-width
|
||||
@input="$listeners.input"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -84,6 +84,25 @@
|
||||
|
||||
"submit": "Submit",
|
||||
|
||||
"name": "Name",
|
||||
"primary_key_field": "Primary Key Field",
|
||||
"type": "Type",
|
||||
"number": "Number",
|
||||
"string": "String",
|
||||
"creating_new_collection": "Creating New Collection",
|
||||
"status": "Status",
|
||||
"sort": "Sort",
|
||||
"created_by_owner": "Created By (Owner)",
|
||||
"created_on": "Created On",
|
||||
"modified_by": "Modified By",
|
||||
"modified_on": "Modified On",
|
||||
"creating_collection_info": "Name the collection and setup its unique “key” field...",
|
||||
"creating_collection_system": "Would you like to add any system fields?",
|
||||
"auto_increment_integer": "Auto-incremented integer",
|
||||
"generated_uuid": "Generated UUID",
|
||||
"manual_string": "Manually entered string",
|
||||
|
||||
|
||||
"about_directus": "About Directus",
|
||||
"activity": "Activity",
|
||||
"activity_log": "Activity Log",
|
||||
@@ -100,8 +119,6 @@
|
||||
"api_installed": "API Successfully Installed",
|
||||
"api_url": "API URL",
|
||||
"asc": "asc",
|
||||
"auto_generate": "Auto-Generate",
|
||||
"auto_generated": "Automatically generated...",
|
||||
"back": "Back",
|
||||
"batch": "Batch",
|
||||
"batch_delete": "Batch Delete",
|
||||
@@ -202,9 +219,6 @@
|
||||
"create_collection": "Create Collection",
|
||||
"create_field": "Create Field",
|
||||
"create_role": "Create Role",
|
||||
"created_by": "Created By",
|
||||
"owner": "Owner",
|
||||
"created_on": "Created On",
|
||||
"create_new_project": "Create New Project",
|
||||
"create_new_project_copy": "Make sure you have your database information handy, then enter your API's Super-Admin password to continue.",
|
||||
"creating_item": "Creating Item",
|
||||
@@ -412,8 +426,6 @@
|
||||
"max_one_primary_key": "You can only have 1 primary key field per collection",
|
||||
"max_size": "Max Size: {size}",
|
||||
"mixed": "Mixed",
|
||||
"modified_by": "Modified By",
|
||||
"modified_on": "Modified On",
|
||||
"more_options": "More options",
|
||||
"months": {
|
||||
"january": "January",
|
||||
@@ -431,7 +443,6 @@
|
||||
},
|
||||
"my_activity": "My Activity",
|
||||
"my_profile": "My Profile: {name}",
|
||||
"name": "Name",
|
||||
"name_bookmark": "What would you like to name this bookmark?",
|
||||
"navigate_changes": "Are you sure you want to leave this page? The changes you made will be lost if you navigate away from this page.",
|
||||
"new": "New",
|
||||
@@ -565,11 +576,9 @@
|
||||
"single": "Single",
|
||||
"something_went_wrong": "Something went wrong.",
|
||||
"something_went_wrong_body": "Trouble processing request. Try again after refreshing the page.",
|
||||
"sort": "Sort",
|
||||
"sort_by": "Sort By",
|
||||
"sort_direction": "Sort Direction",
|
||||
"spacing": "Spacing",
|
||||
"status": "Status",
|
||||
"statuses": "Statuses",
|
||||
"text": "Text",
|
||||
"translation": "Translation",
|
||||
|
||||
@@ -202,7 +202,7 @@ export default defineComponent({
|
||||
value: field.field,
|
||||
width:
|
||||
localWidths.value[field.field] ||
|
||||
_viewOptions.value.widths?.[field.field] ||
|
||||
_viewOptions.value?.widths?.[field.field] ||
|
||||
null,
|
||||
}));
|
||||
},
|
||||
|
||||
@@ -7,15 +7,9 @@
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<v-dialog v-model="addNewActive" persistent>
|
||||
<template #activator="{ on }">
|
||||
<v-button rounded icon @click="on">
|
||||
<v-icon name="add" />
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<new-collection @cancel="addNewActive = false" />
|
||||
</v-dialog>
|
||||
<v-button rounded icon @click="addNewActive = true">
|
||||
<v-icon name="add" />
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<template #navigation>
|
||||
@@ -44,6 +38,8 @@
|
||||
</span>
|
||||
</template>
|
||||
</v-table>
|
||||
|
||||
<new-collection v-model="addNewActive" />
|
||||
</private-view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,22 +1,425 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title>Add collection modal</v-card-title>
|
||||
<v-card-actions>
|
||||
<v-button secondary @click="cancel">Cancel</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
<v-modal
|
||||
:title="$t('creating_new_collection')"
|
||||
:active="active"
|
||||
@toggle="$emit('toggle', $event)"
|
||||
class="new-collection"
|
||||
persistent
|
||||
>
|
||||
<v-dialog :active="saveError !== null" @toggle="saveError = null">
|
||||
<v-card class="selectable">
|
||||
<v-card-title>
|
||||
{{ saveError && saveError.message }}
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
{{ saveError && saveError.response.data.error.message }}
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-button @click="saveError = null">{{ $t('dismiss') }}</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<template #sidebar>
|
||||
<v-tabs vertical v-model="currentTab">
|
||||
<v-tab value="collection">Collection Setup</v-tab>
|
||||
<v-tab value="system">Optional System Fields</v-tab>
|
||||
</v-tabs>
|
||||
</template>
|
||||
|
||||
<v-tabs-items v-model="currentTab">
|
||||
<v-tab-item value="collection">
|
||||
<h2 class="type-title">{{ $t('creating_collection_info') }}</h2>
|
||||
<div class="type-label">{{ $t('name') }}</div>
|
||||
<v-input full-width monospace v-model="collectionName" />
|
||||
<v-divider />
|
||||
<div class="grid">
|
||||
<div>
|
||||
<div class="type-label">{{ $t('primary_key_field') }}</div>
|
||||
<v-input full-width monospace v-model="primaryKeyFieldName" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="type-label">{{ $t('type') }}</div>
|
||||
<v-select
|
||||
full-width
|
||||
:items="[
|
||||
{
|
||||
text: $t('auto_increment_integer'),
|
||||
value: 'auto_int',
|
||||
},
|
||||
{
|
||||
text: $t('generated_uuid'),
|
||||
value: 'uuid',
|
||||
},
|
||||
{
|
||||
text: $t('manual_string'),
|
||||
value: 'manual',
|
||||
},
|
||||
]"
|
||||
v-model="primaryKeyFieldType"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</v-tab-item>
|
||||
<v-tab-item value="system">
|
||||
<h2 class="type-title">{{ $t('creating_collection_system') }}</h2>
|
||||
<div class="grid system">
|
||||
<div class="field" v-for="field in systemFields" :key="field.id">
|
||||
<div class="type-label">{{ $t(field.label) }}</div>
|
||||
<v-input v-model="field.name" monospace>
|
||||
<template #prepend>
|
||||
<v-checkbox v-model="field.enabled" />
|
||||
</template>
|
||||
|
||||
<template #append>
|
||||
<v-icon :name="field.icon" />
|
||||
</template>
|
||||
</v-input>
|
||||
</div>
|
||||
</div>
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
|
||||
<template #footer>
|
||||
<v-button secondary outlined @click="$emit('toggle', false)">
|
||||
{{ $t('cancel') }}
|
||||
</v-button>
|
||||
<div class="spacer" />
|
||||
<v-button
|
||||
secondary
|
||||
@click="currentTab = ['collection']"
|
||||
:disabled="currentTab[0] === 'collection'"
|
||||
>
|
||||
{{ $t('previous') }}
|
||||
</v-button>
|
||||
<v-button
|
||||
:disabled="!collectionName || collectionName.length === 0"
|
||||
v-if="currentTab[0] === 'collection'"
|
||||
@click="currentTab = ['system']"
|
||||
>
|
||||
{{ $t('next') }}
|
||||
</v-button>
|
||||
<v-button v-if="currentTab[0] === 'system'" @click="save" :loading="saving">
|
||||
{{ $t('finish_setup') }}
|
||||
</v-button>
|
||||
</template>
|
||||
</v-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import { defineComponent, ref, reactive } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import { Field } from '@/stores/fields/types';
|
||||
import useCollectionsStore from '@/stores/collections';
|
||||
import useFieldsStore from '@/stores/fields';
|
||||
import notify from '@/utils/notify';
|
||||
|
||||
export default defineComponent({
|
||||
model: {
|
||||
prop: 'active',
|
||||
event: 'toggle',
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
return { cancel };
|
||||
const projectsStore = useProjectsStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
function cancel() {
|
||||
emit('cancel');
|
||||
const currentTab = ref(['collection']);
|
||||
|
||||
const collectionName = ref(null);
|
||||
const primaryKeyFieldName = ref('id');
|
||||
const primaryKeyFieldType = ref<'auto_int' | 'uuid' | 'manual'>('auto_int');
|
||||
|
||||
const systemFields = reactive([
|
||||
{
|
||||
id: 'status',
|
||||
enabled: false,
|
||||
name: 'status',
|
||||
label: 'status',
|
||||
icon: 'flag',
|
||||
},
|
||||
{
|
||||
id: 'sort',
|
||||
enabled: false,
|
||||
name: 'sort',
|
||||
label: 'sort',
|
||||
icon: 'low_priority',
|
||||
},
|
||||
{
|
||||
id: 'owner',
|
||||
enabled: false,
|
||||
name: 'created_by',
|
||||
label: 'created_by_owner',
|
||||
icon: 'account_circle',
|
||||
},
|
||||
{
|
||||
id: 'created_on',
|
||||
enabled: false,
|
||||
name: 'created_on',
|
||||
label: 'created_on',
|
||||
icon: 'access_time',
|
||||
},
|
||||
{
|
||||
id: 'modified_by',
|
||||
enabled: false,
|
||||
name: 'modified_by',
|
||||
label: 'modified_by',
|
||||
icon: 'account_circle',
|
||||
},
|
||||
{
|
||||
id: 'modified_on',
|
||||
enabled: false,
|
||||
name: 'modified_on',
|
||||
label: 'modified_on',
|
||||
icon: 'access_time',
|
||||
},
|
||||
]);
|
||||
|
||||
const saving = ref(false);
|
||||
const saveError = ref(null);
|
||||
|
||||
return {
|
||||
currentTab,
|
||||
save,
|
||||
systemFields,
|
||||
primaryKeyFieldName,
|
||||
primaryKeyFieldType,
|
||||
collectionName,
|
||||
saveError,
|
||||
saving,
|
||||
};
|
||||
|
||||
async function save() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
await api.post(`/${currentProjectKey}/collections`, {
|
||||
collection: collectionName.value,
|
||||
fields: [getPrimaryKeyField(), ...getSystemFields()],
|
||||
});
|
||||
|
||||
await collectionsStore.hydrate();
|
||||
await fieldsStore.hydrate();
|
||||
|
||||
notify({
|
||||
title: 'Collection Created',
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
emit('toggle', false);
|
||||
} catch (error) {
|
||||
saveError.value = error;
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getPrimaryKeyField() {
|
||||
const field: Partial<Field> = {
|
||||
auto_increment: true,
|
||||
field: primaryKeyFieldName.value,
|
||||
hidden_browse: false,
|
||||
hidden_detail: false,
|
||||
interface: 'numeric',
|
||||
length: 15,
|
||||
primary_key: true,
|
||||
type: 'integer',
|
||||
datatype: 'INT',
|
||||
readonly: true,
|
||||
};
|
||||
|
||||
if (primaryKeyFieldType.value === 'uuid') {
|
||||
return {
|
||||
...field,
|
||||
interface: 'text-input',
|
||||
type: 'string',
|
||||
datatype: 'VARCHAR',
|
||||
length: 36,
|
||||
auto_increment: false,
|
||||
};
|
||||
} else if (primaryKeyFieldType.value === 'manual') {
|
||||
return {
|
||||
...field,
|
||||
interface: 'text-input',
|
||||
type: 'string',
|
||||
datatype: 'VARCHAR',
|
||||
length: 255,
|
||||
auto_increment: false,
|
||||
readonly: false,
|
||||
};
|
||||
} else {
|
||||
// auto_int
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
function getSystemFields() {
|
||||
const fields: Partial<Field>[] = [];
|
||||
|
||||
if (systemFields[0].enabled === true) {
|
||||
fields.push({
|
||||
type: 'status',
|
||||
datatype: 'VARCHAR',
|
||||
length: 20,
|
||||
field: systemFields[0].name,
|
||||
interface: 'status',
|
||||
default_value: 'draft',
|
||||
width: 'full',
|
||||
required: true,
|
||||
options: {
|
||||
status_mapping: {
|
||||
published: {
|
||||
name: 'Published',
|
||||
value: 'published',
|
||||
text_color: 'white',
|
||||
background_color: 'accent',
|
||||
browse_subdued: false,
|
||||
browse_badge: true,
|
||||
soft_delete: false,
|
||||
published: true,
|
||||
required_fields: true,
|
||||
},
|
||||
draft: {
|
||||
name: 'Draft',
|
||||
value: 'draft',
|
||||
text_color: 'white',
|
||||
background_color: 'blue-grey-100',
|
||||
browse_subdued: true,
|
||||
browse_badge: true,
|
||||
soft_delete: false,
|
||||
published: false,
|
||||
required_fields: false,
|
||||
},
|
||||
deleted: {
|
||||
name: 'Deleted',
|
||||
value: 'deleted',
|
||||
text_color: 'white',
|
||||
background_color: 'red',
|
||||
browse_subdued: true,
|
||||
browse_badge: true,
|
||||
soft_delete: true,
|
||||
published: false,
|
||||
required_fields: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (systemFields[1].enabled === true) {
|
||||
fields.push({
|
||||
type: 'sort',
|
||||
datatype: 'INT',
|
||||
field: systemFields[1].name,
|
||||
interface: 'sort',
|
||||
hidden_detail: true,
|
||||
hidden_browse: true,
|
||||
width: 'full',
|
||||
});
|
||||
}
|
||||
|
||||
if (systemFields[2].enabled === true) {
|
||||
fields.push({
|
||||
type: 'owner',
|
||||
datatype: 'INT',
|
||||
field: systemFields[2].name,
|
||||
interface: 'owner',
|
||||
options: {
|
||||
template: '{{first_name}} {{last_name}}',
|
||||
display: 'both',
|
||||
},
|
||||
readonly: true,
|
||||
hidden_detail: true,
|
||||
hidden_browse: true,
|
||||
width: 'full',
|
||||
});
|
||||
}
|
||||
|
||||
if (systemFields[3].enabled === true) {
|
||||
fields.push({
|
||||
type: 'datetime_created',
|
||||
datatype: 'DATETIME',
|
||||
field: systemFields[3].name,
|
||||
interface: 'datetime-created',
|
||||
readonly: true,
|
||||
hidden_detail: true,
|
||||
hidden_browse: true,
|
||||
width: 'full',
|
||||
});
|
||||
}
|
||||
|
||||
if (systemFields[4].enabled === true) {
|
||||
fields.push({
|
||||
type: 'user_updated',
|
||||
datatype: 'INT',
|
||||
field: systemFields[4].name,
|
||||
interface: 'user-updated',
|
||||
options: {
|
||||
template: '{{first_name}} {{last_name}}',
|
||||
display: 'both',
|
||||
},
|
||||
readonly: true,
|
||||
hidden_detail: true,
|
||||
hidden_browse: true,
|
||||
width: 'full',
|
||||
});
|
||||
}
|
||||
|
||||
if (systemFields[5].enabled === true) {
|
||||
fields.push({
|
||||
type: 'datetime_updated',
|
||||
datatype: 'DATETIME',
|
||||
field: systemFields[5].name,
|
||||
interface: 'datetime-updated',
|
||||
readonly: true,
|
||||
hidden_detail: true,
|
||||
hidden_browse: true,
|
||||
width: 'full',
|
||||
});
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.type-title {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.type-label {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.v-divider {
|
||||
margin: 48px 0;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-gap: 48px 36px;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.system {
|
||||
.v-icon {
|
||||
--v-icon-color: var(--foreground-subdued);
|
||||
}
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user