mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Interface one to many (#533)
* Extract edit modal to standalone component * Fix creating new item from m2o edit modal * Rename item-modal to modal-detail * Extract selection modal in standalone component * Add required primary-key prop to v-form * Add inline prop to table * Fetch items in o2m * Accept numbers for primary key in v-form * Use correct collection in render template in m2o * Render modal detail * Fix edit existing * Add add-new * Do things * Finish o2m
This commit is contained in:
4
src/views/private/components/modal-browse/index.ts
Normal file
4
src/views/private/components/modal-browse/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import ModalBrowse from './modal-browse.vue';
|
||||
|
||||
export { ModalBrowse };
|
||||
export default ModalBrowse;
|
||||
121
src/views/private/components/modal-browse/modal-browse.vue
Normal file
121
src/views/private/components/modal-browse/modal-browse.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<v-modal v-model="_active" :title="$t('select_item')" no-padding>
|
||||
<layout-tabular
|
||||
class="layout"
|
||||
:collection="collection"
|
||||
:selection="_selection"
|
||||
:filters="filters"
|
||||
@update:selection="onSelect"
|
||||
select-mode
|
||||
/>
|
||||
|
||||
<template #footer>
|
||||
<v-button @click="cancel" secondary>{{ $t('cancel') }}</v-button>
|
||||
<v-button @click="save">{{ $t('save') }}</v-button>
|
||||
</template>
|
||||
</v-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, computed } from '@vue/composition-api';
|
||||
import { Filter } from '@/stores/collection-presets/types';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
selection: {
|
||||
type: Array as PropType<(number | string)[]>,
|
||||
default: () => [],
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
filters: {
|
||||
type: Array as PropType<Filter[]>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { save, cancel } = useActions();
|
||||
const { _active } = useActiveState();
|
||||
const { _selection, onSelect } = useSelection();
|
||||
|
||||
return { save, cancel, _active, _selection, onSelect };
|
||||
|
||||
function useActiveState() {
|
||||
const localActive = ref(false);
|
||||
|
||||
const _active = computed({
|
||||
get() {
|
||||
return props.active === undefined ? localActive.value : props.active;
|
||||
},
|
||||
set(newActive: boolean) {
|
||||
localActive.value = newActive;
|
||||
emit('update:active', newActive);
|
||||
},
|
||||
});
|
||||
|
||||
return { _active };
|
||||
}
|
||||
|
||||
function useSelection() {
|
||||
const localSelection = ref<(string | number)[]>(null);
|
||||
|
||||
const _selection = computed({
|
||||
get() {
|
||||
if (localSelection.value === null) {
|
||||
return props.selection;
|
||||
}
|
||||
|
||||
return localSelection.value;
|
||||
},
|
||||
set(newSelection: (string | number)[]) {
|
||||
localSelection.value = newSelection;
|
||||
},
|
||||
});
|
||||
|
||||
return { _selection, onSelect };
|
||||
|
||||
function onSelect(newSelection: (string | number)[]) {
|
||||
if (newSelection.length === 0) {
|
||||
localSelection.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.multiple === true) {
|
||||
localSelection.value = newSelection;
|
||||
} else {
|
||||
localSelection.value = [newSelection[newSelection.length - 1]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useActions() {
|
||||
return { save, cancel };
|
||||
|
||||
function save() {
|
||||
emit('input', _selection.value);
|
||||
_active.value = false;
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
_active.value = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layout {
|
||||
--layout-offset-top: 0px;
|
||||
}
|
||||
</style>
|
||||
4
src/views/private/components/modal-detail/index.ts
Normal file
4
src/views/private/components/modal-detail/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import ModalDetail from './modal-detail.vue';
|
||||
|
||||
export { ModalDetail };
|
||||
export default ModalDetail;
|
||||
144
src/views/private/components/modal-detail/modal-detail.vue
Normal file
144
src/views/private/components/modal-detail/modal-detail.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<v-modal v-model="_active" :title="$t('editing_in', { collection })" persistent>
|
||||
<v-form
|
||||
:loading="loading"
|
||||
:initial-values="item"
|
||||
:collection="collection"
|
||||
:primary-key="primaryKey"
|
||||
v-model="_edits"
|
||||
/>
|
||||
|
||||
<template #footer>
|
||||
<v-button @click="cancel" secondary>{{ $t('cancel') }}</v-button>
|
||||
<v-button @click="save">{{ $t('save') }}</v-button>
|
||||
</template>
|
||||
</v-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, PropType, watch } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
export default defineComponent({
|
||||
model: {
|
||||
prop: 'edits',
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
primaryKey: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
edits: {
|
||||
type: Object as PropType<Record<string, any>>,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const { _active } = useActiveState();
|
||||
const { _edits, loading, error, item } = useItem();
|
||||
const { save, cancel } = useActions();
|
||||
|
||||
return { _active, _edits, loading, error, item, save, cancel };
|
||||
|
||||
function useActiveState() {
|
||||
const localActive = ref(false);
|
||||
|
||||
const _active = computed({
|
||||
get() {
|
||||
return props.active === undefined ? localActive.value : props.active;
|
||||
},
|
||||
set(newActive: boolean) {
|
||||
localActive.value = newActive;
|
||||
emit('update:active', newActive);
|
||||
},
|
||||
});
|
||||
|
||||
return { _active };
|
||||
}
|
||||
|
||||
function useItem() {
|
||||
const localEdits = ref<Record<string, any>>({});
|
||||
|
||||
const _edits = computed<Record<string, any>>({
|
||||
get() {
|
||||
if (props.edits !== undefined) {
|
||||
return {
|
||||
...props.edits,
|
||||
...localEdits.value,
|
||||
};
|
||||
}
|
||||
|
||||
return localEdits.value;
|
||||
},
|
||||
set(newEdits) {
|
||||
localEdits.value = newEdits;
|
||||
},
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
const item = ref<Record<string, any>>(null);
|
||||
|
||||
watch(
|
||||
() => props.active,
|
||||
(isActive) => {
|
||||
if (isActive === true) {
|
||||
if (props.primaryKey !== '+') fetchItem();
|
||||
} else {
|
||||
loading.value = false;
|
||||
error.value = null;
|
||||
item.value = null;
|
||||
localEdits.value = {};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return { _edits, loading, error, item, fetchItem };
|
||||
|
||||
async function fetchItem() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(
|
||||
`/${currentProjectKey}/items/${props.collection}/${props.primaryKey}`
|
||||
);
|
||||
|
||||
item.value = response.data.data;
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useActions() {
|
||||
return { save, cancel };
|
||||
|
||||
function save() {
|
||||
emit('input', _edits.value);
|
||||
_active.value = false;
|
||||
_edits.value = {};
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
_active.value = false;
|
||||
_edits.value = {};
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<value-null v-if="value === null || value === undefined" />
|
||||
<span v-else-if="!displayInfo">{{ value }}</span>
|
||||
<span v-else-if="displayInfo === null">{{ value }}</span>
|
||||
<span v-else-if="typeof displayInfo.handler === 'function'">
|
||||
{{ display.handler(value, options) }}
|
||||
</span>
|
||||
@@ -15,7 +15,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import { defineComponent, computed } from '@vue/composition-api';
|
||||
import displays from '@/displays';
|
||||
import ValueNull from '@/views/private/components/value-null';
|
||||
|
||||
@@ -44,7 +44,9 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const displayInfo = displays.find((display) => display.id === props.display) || null;
|
||||
const displayInfo = computed(
|
||||
() => displays.find((display) => display.id === props.display) || null
|
||||
);
|
||||
return { displayInfo };
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user