mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Update/tweak groups (#7229)
* Split detail/raw groups, tweak accordion * Add update groups migration
This commit is contained in:
35
api/src/database/migrations/20210805A-update-groups.ts
Normal file
35
api/src/database/migrations/20210805A-update-groups.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const groups = await knex.select('*').from('directus_fields').where({ interface: 'group-standard' });
|
||||
|
||||
const raw = [];
|
||||
const detail = [];
|
||||
|
||||
for (const group of groups) {
|
||||
const options = typeof group.options === 'string' ? JSON.parse(group.options) : group.options || {};
|
||||
|
||||
if (options.showHeader === true) {
|
||||
detail.push(group);
|
||||
} else {
|
||||
raw.push(group);
|
||||
}
|
||||
}
|
||||
|
||||
for (const field of raw) {
|
||||
await knex('directus_fields').update({ interface: 'group-raw' }).where({ id: field.id });
|
||||
}
|
||||
|
||||
for (const field of detail) {
|
||||
await knex('directus_fields').update({ interface: 'group-detail' }).where({ id: field.id });
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex('directus_fields')
|
||||
.update({
|
||||
interface: 'group-standard',
|
||||
})
|
||||
.where({ interface: 'group-detail' })
|
||||
.orWhere({ interface: 'group-raw' });
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-item-group v-model="selection" scope="group-accordion" class="group-accordion" :multiple="multiple">
|
||||
<v-item-group v-model="selection" scope="group-accordion" class="group-accordion" :multiple="accordionMode === false">
|
||||
<accordion-section
|
||||
v-for="accordionField in rootFields"
|
||||
:key="accordionField.field"
|
||||
@@ -14,7 +14,7 @@
|
||||
:loading="loading"
|
||||
:validation-errors="validationErrors"
|
||||
:group="field.meta.id"
|
||||
:multiple="multiple"
|
||||
:multiple="accordionMode === false"
|
||||
@apply="$emit('apply', $event)"
|
||||
@toggleAll="toggleAll"
|
||||
/>
|
||||
@@ -72,9 +72,9 @@ export default defineComponent({
|
||||
default: () => [],
|
||||
},
|
||||
|
||||
multiple: {
|
||||
accordionMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
default: true,
|
||||
},
|
||||
start: {
|
||||
type: String,
|
||||
@@ -107,7 +107,7 @@ export default defineComponent({
|
||||
return { rootFields, selection, toggleAll };
|
||||
|
||||
function toggleAll() {
|
||||
if (props.multiple === false) return;
|
||||
if (props.accordionMode === true) return;
|
||||
|
||||
if (selection.value.length === rootFields.value.length) {
|
||||
selection.value = [];
|
||||
|
||||
@@ -13,24 +13,24 @@ export default defineInterface({
|
||||
groups: ['group'],
|
||||
options: [
|
||||
{
|
||||
field: 'multiple',
|
||||
field: 'accordionMode',
|
||||
type: 'boolean',
|
||||
name: '$t:allow_multiple',
|
||||
name: '$t:interfaces.group-accordion.accordion_mode',
|
||||
meta: {
|
||||
interface: 'boolean',
|
||||
options: {
|
||||
label: '$t:allow_multiple_to_be_open',
|
||||
label: '$t:interfaces.group-accordion.max_one_section_open',
|
||||
},
|
||||
width: 'half',
|
||||
},
|
||||
schema: {
|
||||
default_value: false,
|
||||
default_value: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'start',
|
||||
type: 'string',
|
||||
name: '$t:interfaces.group-accordion.start',
|
||||
name: '$t:start',
|
||||
schema: {
|
||||
default_value: 'closed',
|
||||
},
|
||||
@@ -52,8 +52,8 @@ export default defineInterface({
|
||||
conditions: [
|
||||
{
|
||||
rule: {
|
||||
multiple: {
|
||||
_eq: true,
|
||||
accordionMode: {
|
||||
_eq: false,
|
||||
},
|
||||
},
|
||||
options: {
|
||||
|
||||
125
app/src/interfaces/group-detail/group-detail.vue
Normal file
125
app/src/interfaces/group-detail/group-detail.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<v-detail class="group-detail">
|
||||
<template #activator="{ toggle, active }">
|
||||
<v-divider
|
||||
:style="{
|
||||
'--v-divider-label-color': headerColor,
|
||||
}"
|
||||
:class="{ active }"
|
||||
:inline-title="false"
|
||||
:start-open="start === 'open'"
|
||||
large
|
||||
@click="toggle"
|
||||
>
|
||||
<template v-if="headerIcon" #icon><v-icon :name="headerIcon" class="header-icon" /></template>
|
||||
<template v-if="field.name">
|
||||
<span class="title">{{ field.name }}</span>
|
||||
</template>
|
||||
<v-icon class="expand-icon" name="expand_more" />
|
||||
</v-divider>
|
||||
</template>
|
||||
|
||||
<v-form
|
||||
:initial-values="initialValues"
|
||||
:fields="fields"
|
||||
:model-value="values"
|
||||
:primary-key="primaryKey"
|
||||
:group="field.meta.id"
|
||||
:validation-errors="validationErrors"
|
||||
:loading="loading"
|
||||
:batch-mode="batchMode"
|
||||
@update:model-value="$emit('apply', $event)"
|
||||
/>
|
||||
</v-detail>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Field } from '@directus/shared/types';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { ValidationError } from '@directus/shared/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'InterfaceGroupRaw',
|
||||
props: {
|
||||
field: {
|
||||
type: Object as PropType<Field>,
|
||||
required: true,
|
||||
},
|
||||
fields: {
|
||||
type: Array as PropType<Field[]>,
|
||||
required: true,
|
||||
},
|
||||
values: {
|
||||
type: Object as PropType<Record<string, unknown>>,
|
||||
required: true,
|
||||
},
|
||||
initialValues: {
|
||||
type: Object as PropType<Record<string, unknown>>,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
batchMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
batchActiveFields: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => [],
|
||||
},
|
||||
primaryKey: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
validationErrors: {
|
||||
type: Array as PropType<ValidationError[]>,
|
||||
default: () => [],
|
||||
},
|
||||
|
||||
start: {
|
||||
type: String,
|
||||
enum: ['open', 'closed'],
|
||||
default: 'open',
|
||||
},
|
||||
headerIcon: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
headerColor: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['apply'],
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-form {
|
||||
padding-top: calc(var(--form-vertical-gap) / 2);
|
||||
}
|
||||
|
||||
.v-divider {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.v-divider .expand-icon {
|
||||
float: right;
|
||||
transform: rotate(90deg) !important;
|
||||
transition: transform var(--fast) var(--transition);
|
||||
}
|
||||
|
||||
.v-divider.active .expand-icon {
|
||||
transform: rotate(0) !important;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
margin-right: 12px !important;
|
||||
}
|
||||
</style>
|
||||
56
app/src/interfaces/group-detail/index.ts
Normal file
56
app/src/interfaces/group-detail/index.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { defineInterface } from '@directus/shared/utils';
|
||||
import InterfaceGroupDetail from './group-detail.vue';
|
||||
|
||||
export default defineInterface({
|
||||
id: 'group-detail',
|
||||
name: '$t:interfaces.group-detail.name',
|
||||
description: '$t:interfaces.group-detail.description',
|
||||
icon: 'menu_open',
|
||||
component: InterfaceGroupDetail,
|
||||
groups: ['group'],
|
||||
types: ['alias'],
|
||||
options: [
|
||||
{
|
||||
field: 'start',
|
||||
name: '$t:start',
|
||||
type: 'string',
|
||||
schema: {
|
||||
default_value: 'open',
|
||||
},
|
||||
meta: {
|
||||
interface: 'select-dropdown',
|
||||
width: 'full',
|
||||
options: {
|
||||
choices: [
|
||||
{
|
||||
text: '$t:interfaces.group-detail.start_open',
|
||||
value: 'open',
|
||||
},
|
||||
{
|
||||
text: '$t:interfaces.group-detail.start_closed',
|
||||
value: 'closed',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'headerIcon',
|
||||
name: '$t:interfaces.group-detail.header_icon',
|
||||
type: 'string',
|
||||
meta: {
|
||||
interface: 'select-icon',
|
||||
width: 'half',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'headerColor',
|
||||
name: '$t:interfaces.group-detail.header_color',
|
||||
type: 'string',
|
||||
meta: {
|
||||
interface: 'select-color',
|
||||
width: 'half',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1,19 +1,5 @@
|
||||
<template>
|
||||
<div class="group-standard">
|
||||
<v-divider
|
||||
v-if="showHeader"
|
||||
:style="{
|
||||
'--v-divider-label-color': headerColor,
|
||||
}"
|
||||
:inline-title="false"
|
||||
large
|
||||
>
|
||||
<template v-if="headerIcon" #icon><v-icon :name="headerIcon" /></template>
|
||||
<template v-if="field.name">
|
||||
<span class="title">{{ field.name }}</span>
|
||||
</template>
|
||||
</v-divider>
|
||||
|
||||
<div class="group-raw">
|
||||
<v-form
|
||||
:initial-values="initialValues"
|
||||
:fields="fields"
|
||||
@@ -22,7 +8,6 @@
|
||||
:group="field.meta.id"
|
||||
:validation-errors="validationErrors"
|
||||
:loading="loading"
|
||||
:batch-mode="batchMode"
|
||||
@update:model-value="$emit('apply', $event)"
|
||||
/>
|
||||
</div>
|
||||
@@ -31,8 +16,7 @@
|
||||
<script lang="ts">
|
||||
import { Field } from '@directus/shared/types';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { ValidationError } from '@directus/shared/types';
|
||||
|
||||
import { ValidationError } from '@/types';
|
||||
export default defineComponent({
|
||||
name: 'InterfaceGroupRaw',
|
||||
props: {
|
||||
@@ -76,26 +60,7 @@ export default defineComponent({
|
||||
type: Array as PropType<ValidationError[]>,
|
||||
default: () => [],
|
||||
},
|
||||
|
||||
showHeader: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
headerIcon: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
headerColor: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['apply'],
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-divider {
|
||||
margin-bottom: calc(var(--form-vertical-gap) / 2);
|
||||
}
|
||||
</style>
|
||||
13
app/src/interfaces/group-raw/index.ts
Normal file
13
app/src/interfaces/group-raw/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineInterface } from '@directus/shared/utils';
|
||||
import InterfaceGroupRaw from './group-raw.vue';
|
||||
|
||||
export default defineInterface({
|
||||
id: 'group-raw',
|
||||
name: '$t:interfaces.group-raw.name',
|
||||
description: '$t:interfaces.group-raw.description',
|
||||
icon: 'view_in_ar',
|
||||
component: InterfaceGroupRaw,
|
||||
groups: ['group'],
|
||||
types: ['alias'],
|
||||
options: [],
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
import { defineInterface } from '@directus/shared/utils';
|
||||
import InterfaceGroupStandard from './group-standard.vue';
|
||||
|
||||
export default defineInterface({
|
||||
id: 'group-standard',
|
||||
name: '$t:interfaces.group-standard.name',
|
||||
description: '$t:interfaces.group-standard.description',
|
||||
icon: 'view_in_ar',
|
||||
component: InterfaceGroupStandard,
|
||||
groups: ['group'],
|
||||
types: ['alias'],
|
||||
options: [
|
||||
{
|
||||
field: 'showHeader',
|
||||
name: '$t:interfaces.group-standard.show_header',
|
||||
type: 'boolean',
|
||||
meta: {
|
||||
interface: 'boolean',
|
||||
width: 'half',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'headerIcon',
|
||||
name: '$t:interfaces.group-standard.header_icon',
|
||||
type: 'string',
|
||||
meta: {
|
||||
interface: 'select-icon',
|
||||
width: 'half',
|
||||
readonly: true,
|
||||
conditions: [
|
||||
{
|
||||
rule: {
|
||||
showHeader: {
|
||||
_eq: true,
|
||||
},
|
||||
},
|
||||
readonly: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'headerColor',
|
||||
name: '$t:interfaces.group-standard.header_color',
|
||||
type: 'string',
|
||||
meta: {
|
||||
interface: 'select-color',
|
||||
width: 'half',
|
||||
readonly: true,
|
||||
conditions: [
|
||||
{
|
||||
rule: {
|
||||
showHeader: {
|
||||
_eq: true,
|
||||
},
|
||||
},
|
||||
readonly: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -912,14 +912,16 @@ value: Value
|
||||
view_project: View Project
|
||||
weeks: {}
|
||||
report_error: Report Error
|
||||
start: Start
|
||||
interfaces:
|
||||
group-accordion:
|
||||
name: Accordion
|
||||
description: Display fields or groups as accordion sections
|
||||
start: Start
|
||||
all_closed: All Closed
|
||||
first_opened: First Opened
|
||||
all_opened: All Opened
|
||||
accordion_mode: Accordion Mode
|
||||
max_one_section_open: Max 1 Section Open
|
||||
presentation-links:
|
||||
presentation-links: Button Links
|
||||
links: Links
|
||||
@@ -1126,12 +1128,17 @@ interfaces:
|
||||
value_path: Value Path
|
||||
trigger: Trigger
|
||||
rate: Rate
|
||||
group-standard:
|
||||
name: Standard Group
|
||||
description: Show the fields as normal with an optional group header.
|
||||
group-raw:
|
||||
name: Raw Group
|
||||
description: Render the fields as-is
|
||||
group-detail:
|
||||
name: Detail Group
|
||||
description: Render the fields as a collapsable section
|
||||
show_header: Show Group Header
|
||||
header_icon: Header Icon
|
||||
header_color: Header Color
|
||||
start_open: Start Open
|
||||
start_closed: Start Closed
|
||||
displays:
|
||||
boolean:
|
||||
boolean: Boolean
|
||||
|
||||
Reference in New Issue
Block a user