Support singleton on the browse level (#781)

* Support singleton on the browse level

* Remove broken test
This commit is contained in:
Rijk van Zanten
2020-07-10 12:00:21 -04:00
committed by GitHub
parent 9a107f915e
commit 4bd844853d
10 changed files with 94 additions and 135 deletions

View File

@@ -1,79 +0,0 @@
import { useCollection } from './use-collection';
import Vue from 'vue';
import VueCompositionAPI, { ref } from '@vue/composition-api';
import useCollectionsStore from '@/stores/collections/';
import useFieldsStore from '@/stores/fields/';
describe('Composables / useCollection', () => {
let req: any = {};
beforeAll(() => {
Vue.use(VueCompositionAPI);
});
beforeEach(() => {
req = {};
});
it('Gets the collection info from the collections store', () => {
useCollectionsStore(req).state.collections = [
{
collection: 'files',
test: true,
},
{
collection: 'another-collection',
test: false,
},
] as any;
useFieldsStore(req).state.fields = [
{
collection: 'files',
field: 'id',
primary_key: true,
test: true,
},
{
collection: 'another-collection',
field: 'id',
primary_key: true,
test: false,
},
{
collection: 'files',
field: 'title',
primary_key: false,
test: true,
},
] as any;
const { info, fields, primaryKeyField } = useCollection(ref('files'));
expect(info.value).toEqual({
collection: 'files',
test: true,
});
expect(fields.value).toEqual([
{
collection: 'files',
field: 'id',
primary_key: true,
test: true,
},
{
collection: 'files',
field: 'title',
primary_key: false,
test: true,
},
]);
expect(primaryKeyField.value).toEqual({
collection: 'files',
field: 'id',
primary_key: true,
test: true,
});
});
});

View File

@@ -5,8 +5,8 @@ import i18n from '@/lang';
import useCollection from '@/composables/use-collection';
import { AxiosResponse } from 'axios';
export function useItem(collection: Ref<string>, primaryKey: Ref<string | number>) {
const { primaryKeyField, softDeleteStatus, statusField } = useCollection(collection);
export function useItem(collection: Ref<string>, primaryKey: Ref<string | number | null>) {
const { info: collectionInfo, primaryKeyField, softDeleteStatus, statusField } = useCollection(collection);
const item = ref<any>(null);
const error = ref(null);
@@ -17,6 +17,7 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
const edits = ref({});
const isNew = computed(() => primaryKey.value === '+');
const isBatch = computed(() => typeof primaryKey.value === 'string' && primaryKey.value.includes(','));
const isSingle = computed(() => !!collectionInfo.value?.single);
const endpoint = computed(() => {
return collection.value.startsWith('directus_')
@@ -24,6 +25,14 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
: `/items/${collection.value}`;
});
const itemEndpoint = computed(() => {
if (isSingle.value) {
return endpoint.value;
}
return `${endpoint.value}/${primaryKey.value}`;
});
watch([collection, primaryKey], refresh, { immediate: true });
return {
@@ -47,8 +56,7 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
loading.value = true;
try {
const response = await api.get(`${endpoint.value}/${primaryKey.value}`);
const response = await api.get(itemEndpoint.value);
setItemValueToResponse(response);
} catch (err) {
error.value = err;
@@ -77,7 +85,7 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
type: 'success',
});
} else {
response = await api.patch(`${endpoint.value}/${primaryKey.value}`, edits.value);
response = await api.patch(itemEndpoint.value, edits.value);
notify({
title: i18n.tc('item_update_success', isBatch.value ? 2 : 1),
@@ -180,11 +188,11 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
throw new Error('[useItem] You cant soft-delete without a status field');
}
await api.patch(`${endpoint.value}/${primaryKey.value}`, {
await api.patch(itemEndpoint.value, {
[statusField.value.field]: softDeleteStatus.value,
});
} else {
await api.delete(`${endpoint.value}/${primaryKey.value}`);
await api.delete(itemEndpoint.value);
}
item.value = null;

View File

@@ -1,6 +1,6 @@
import { defineModule } from '@/modules/define';
import CollectionsOverview from './routes/overview/';
import CollectionsBrowse from './routes/browse/';
import CollectionsBrowseOrDetail from './routes/browse-or-detail/';
import CollectionsDetail from './routes/detail/';
import CollectionsItemNotFound from './routes/not-found';
@@ -17,7 +17,7 @@ export default defineModule(({ i18n }) => ({
{
name: 'collections-browse',
path: '/:collection',
component: CollectionsBrowse,
component: CollectionsBrowseOrDetail,
props: (route) => ({
collection: route.params.collection,
bookmark: route.query.bookmark,

View File

@@ -0,0 +1,49 @@
<template>
<component ref="component" :is="isSingle ? 'collections-detail' : 'collections-browse'" :collection="collection" />
</template>
<script lang="ts">
import { defineComponent, ref, computed } from '@vue/composition-api';
import Vue from 'vue';
import CollectionsBrowse from '../browse';
import CollectionsDetail from '../detail';
import useCollectionsStore from '@/stores/collections';
export default defineComponent({
components: {
CollectionsBrowse,
CollectionsDetail,
},
props: {
collection: {
type: String,
required: true,
},
},
setup(props) {
const collectionsStore = useCollectionsStore();
const component = ref<Vue>();
const isSingle = computed(() => {
const collectionInfo = collectionsStore.getCollection(props.collection);
return !!collectionInfo?.single === true;
});
return { component, isSingle };
},
beforeRouteLeave(to, from, next) {
if ((this as any).$refs?.component?.navigationGuard) {
return (this as any).$refs.component.navigationGuard(to, from, next);
}
return next();
},
beforeRouteUpdate(to, from, next) {
if ((this as any).$refs?.component?.navigationGuard) {
return (this as any).$refs.component.navigationGuard(to, from, next);
}
return next();
},
});
</script>

View File

@@ -0,0 +1,4 @@
import CollectionsBrowseOrDetail from './browse-or-detail.vue';
export { CollectionsBrowseOrDetail };
export default CollectionsBrowseOrDetail;

View File

@@ -0,0 +1,3 @@
# Browse or Detail
Renders either the browse page or the detail page depending on whether or not the collection is a singleton

View File

@@ -133,10 +133,7 @@
<script lang="ts">
import { defineComponent, computed, ref, watch, toRefs } from '@vue/composition-api';
import { NavigationGuard } from 'vue-router';
import CollectionsNavigation from '../../components/navigation/';
import useCollectionsStore from '@/stores/collections';
import useFieldsStore from '@/stores/fields';
import api from '@/api';
import { LayoutComponent } from '@/layouts/types';
import CollectionsNotFound from '../not-found/';
@@ -149,40 +146,11 @@ import BookmarkEdit from '@/views/private/components/bookmark-edit';
import router from '@/router';
import marked from 'marked';
const redirectIfNeeded: NavigationGuard = async (to, from, next) => {
const collectionsStore = useCollectionsStore();
const collectionInfo = collectionsStore.getCollection(to.params.collection);
if (collectionInfo === null) return next();
if (collectionInfo.single === true) {
const fieldsStore = useFieldsStore();
const primaryKeyField = fieldsStore.getPrimaryKeyFieldForCollection(to.params.collection);
const item = await api.get(`/items/${to.params.collection}`, {
params: {
limit: 1,
fields: primaryKeyField.field,
single: true,
},
});
const primaryKey = item.data.data[primaryKeyField.field];
return next(`/collections/${to.params.collection}/${primaryKey}`);
}
return next();
};
type Item = {
[field: string]: any;
};
export default defineComponent({
beforeRouteEnter: redirectIfNeeded,
beforeRouteUpdate: redirectIfNeeded,
name: 'collections-browse',
components: {
CollectionsNavigation,

View File

@@ -1,5 +1,5 @@
<template>
<collections-not-found v-if="error && error.code === 404" />
<collections-not-found v-if="error || (collectionInfo.single === true && primaryKey !== null)" />
<private-view v-else :title="title">
<template #title v-if="isNew === false && isBatch === false && collectionInfo.display_template">
<v-skeleton-loader class="title-loader" type="text" v-if="loading" />
@@ -178,6 +178,7 @@ import SaveOptions from '@/views/private/components/save-options';
import i18n from '@/lang';
import marked from 'marked';
import useShortcut from '@/composables/use-shortcut';
import { NavigationGuard } from 'vue-router';
type Values = {
[field: string]: any;
@@ -185,18 +186,6 @@ type Values = {
export default defineComponent({
name: 'collections-detail',
beforeRouteLeave(to, from, next) {
const self = this as any;
const hasEdits = Object.keys(self.edits).length > 0;
if (hasEdits) {
self.confirmLeave = true;
self.leaveTo = to.fullPath;
return next(false);
}
return next();
},
components: {
CollectionsNavigation,
CollectionsNotFound,
@@ -211,7 +200,7 @@ export default defineComponent({
},
primaryKey: {
type: String,
required: true,
default: null,
},
},
setup(props) {
@@ -269,6 +258,18 @@ export default defineComponent({
useShortcut('mod+s', saveAndStay);
useShortcut('mod+shift+s', saveAndAddNew);
const navigationGuard: NavigationGuard = (to, from, next) => {
const hasEdits = Object.keys(edits.value).length > 0;
if (hasEdits) {
confirmLeave.value = true;
leaveTo.value = to.fullPath;
return next(false);
}
return next();
};
return {
item,
loading,
@@ -299,6 +300,7 @@ export default defineComponent({
confirmLeave,
leaveTo,
discardAndLeave,
navigationGuard,
};
function useBreadcrumb() {

View File

@@ -14,5 +14,9 @@ export const useLatencyStore = createStore({
async dehydrate() {
this.reset();
},
save(latency: Latency) {
this.state.latency.push(latency);
this.state.latency = this.state.latency.slice(-20);
},
},
});

View File

@@ -53,7 +53,7 @@ export const useUserStore = createStore({
const end = performance.now();
latencyStore.state.latency.push({
latencyStore.save({
timestamp: new Date(),
latency: end - start,
});