Bookmarks (#494)

* Add support for bookmarks in collection preset composition

* Add strings for bookmark 404

* Use bookmarks prop in collections browse

* Use exact for active link in navs

* Show bookmarks in navigation

* Make bookmark optional in collection preset
This commit is contained in:
Rijk van Zanten
2020-04-27 18:59:59 -04:00
committed by GitHub
parent 12b24b9115
commit 6ed64a9667
8 changed files with 159 additions and 30 deletions

View File

@@ -3,6 +3,7 @@
:is="component"
active-class="active"
class="v-list-item"
exact
:to="to"
:class="{
active,

View File

@@ -4,3 +4,18 @@ export type Filter = {
operator: string;
value: string | number;
};
export type CollectionPreset = {
id: number;
collection: string;
filters: null | Filter[];
role: number | null;
search_query: string | null;
title: string | null;
user: number | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
view_options: Record<string, any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
view_query: Record<string, any>;
view_type: string | null;
};

View File

@@ -2,32 +2,44 @@ import useCollectionPresetStore from '@/stores/collection-presets';
import { ref, Ref, computed, watch } from '@vue/composition-api';
import { debounce } from 'lodash';
import { Filter } from './types';
import { Filter, CollectionPreset } from './types';
export function useCollectionPreset(collection: Ref<string>) {
export function useCollectionPreset(
collection: Ref<string>,
bookmark: Ref<number | null> = ref(null)
) {
const collectionPresetsStore = useCollectionPresetStore();
const localPreset = ref({
...collectionPresetsStore.getPresetForCollection(collection.value),
});
const bookmarkExists = computed(() => {
if (!bookmark.value) return false;
const savePreset = debounce(async (preset) => {
await collectionPresetsStore.savePreset(preset);
localPreset.value = collectionPresetsStore.getPresetForCollection(collection.value);
return !!collectionPresetsStore.state.collectionPresets.find(
(preset) => preset.id === bookmark.value
);
});
const localPreset = ref<CollectionPreset>({});
initLocalPreset();
const savePreset = async () => await collectionPresetsStore.savePreset(localPreset.value);
const autoSave = debounce(async () => {
if (!bookmark || bookmark.value === null) {
savePreset();
}
}, 450);
watch(collection, () => {
localPreset.value = {
...collectionPresetsStore.getPresetForCollection(collection.value),
};
});
watch(collection, initLocalPreset);
watch(bookmark, initLocalPreset);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const viewOptions = computed<Record<string, any>>({
get() {
if (!localPreset.value.view_type) return null;
return localPreset.value.view_options?.[localPreset.value.view_type] || null;
},
set(val) {
if (!localPreset.value.view_type) return null;
localPreset.value = {
...localPreset.value,
view_options: {
@@ -35,16 +47,19 @@ export function useCollectionPreset(collection: Ref<string>) {
[localPreset.value.view_type]: val,
},
};
savePreset(localPreset.value);
autoSave();
},
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const viewQuery = computed<Record<string, any>>({
get() {
if (!localPreset.value.view_type) return null;
return localPreset.value.view_query?.[localPreset.value.view_type] || null;
},
set(val) {
if (!localPreset.value.view_type) return null;
localPreset.value = {
...localPreset.value,
view_query: {
@@ -52,11 +67,12 @@ export function useCollectionPreset(collection: Ref<string>) {
[localPreset.value.view_type]: val,
},
};
savePreset(localPreset.value);
autoSave();
},
});
const viewType = computed<string>({
const viewType = computed<string | null>({
get() {
return localPreset.value.view_type || null;
},
@@ -65,7 +81,8 @@ export function useCollectionPreset(collection: Ref<string>) {
...localPreset.value,
view_type: val,
};
savePreset(localPreset.value);
autoSave();
},
});
@@ -78,11 +95,12 @@ export function useCollectionPreset(collection: Ref<string>) {
...localPreset.value,
filters: val,
};
savePreset(localPreset.value);
autoSave();
},
});
const searchQuery = computed<Filter[]>({
const searchQuery = computed<string | null>({
get() {
return localPreset.value.search_query || null;
},
@@ -91,9 +109,24 @@ export function useCollectionPreset(collection: Ref<string>) {
...localPreset.value,
search_query: val,
};
savePreset(localPreset.value);
autoSave();
},
});
return { viewType, viewOptions, viewQuery, filters, searchQuery };
return { bookmarkExists, viewType, viewOptions, viewQuery, filters, searchQuery, savePreset };
function initLocalPreset() {
if (bookmark.value === null) {
localPreset.value = {
...collectionPresetsStore.getPresetForCollection(collection.value),
};
} else {
if (bookmarkExists.value === false) return;
localPreset.value = {
...collectionPresetsStore.getBookmark(+bookmark.value),
};
}
}
}

View File

@@ -146,6 +146,10 @@
"toggle_manual_sorting": "Toggle Manual Sorting",
"bookmark_doesnt_exist": "Bookmark Doesn't Exist",
"bookmark_doesnt_exist_copy": "The bookmark you're trying to open couldn't be found.",
"bookmark_doesnt_exist_cta": "Return to collection",
"errors": {
"3": "Only super admins have access to this",
"4": "Super Admin Token not provided",
@@ -802,7 +806,6 @@
"update_confirm": "Are you sure you want to update {count} items?",
"update_field": "Update Field",
"upload_exceeds_max_size": "{filename} can't be uploaded. Your server is not configured to handle uploads of this size.",
"user_directory": "User Directory",
"user_edit_warning": "{first_name} {last_name} is editing this item too. Please coordinate with them so you don't lose your changes.",
"validation": "Validation",
"value": "Value",

View File

@@ -4,19 +4,49 @@
<v-list-item-icon><v-icon :name="navItem.icon" /></v-list-item-icon>
<v-list-item-content>{{ navItem.name }}</v-list-item-content>
</v-list-item>
<template v-if="bookmarks.length > 0">
<v-divider />
<v-list-item v-for="bookmark in bookmarks" :key="bookmark.id" :to="bookmark.to">
<v-list-item-icon><v-icon name="bookmark" /></v-list-item-icon>
<v-list-item-content>{{ bookmark.title }}</v-list-item-content>
</v-list-item>
</template>
</v-list>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api';
import { defineComponent, computed } from '@vue/composition-api';
import useNavigation from '../../compositions/use-navigation';
import useCollectionPresetsStore from '@/stores/collection-presets';
import useProjectsStore from '@/stores/projects';
export default defineComponent({
props: {},
setup() {
const collectionPresetsStore = useCollectionPresetsStore();
const projectsStore = useProjectsStore();
const { navItems } = useNavigation();
return { navItems };
const bookmarks = computed(() => {
const { currentProjectKey } = projectsStore.state;
return collectionPresetsStore.state.collectionPresets
.filter((preset) => {
return (
preset.title !== null && preset.collection.startsWith('directus_') === false
);
})
.map((preset) => {
return {
...preset,
to: `/${currentProjectKey}/collections/${preset.collection}?bookmark=${preset.id}`,
};
});
});
return { navItems, bookmarks };
},
});
</script>

View File

@@ -18,7 +18,10 @@ export default defineModule(({ i18n }) => ({
name: 'collections-browse',
path: '/:collection',
component: CollectionsBrowse,
props: true,
props: (route) => ({
collection: route.params.collection,
bookmark: route.query.bookmark,
}),
},
{
name: 'collections-detail',

View File

@@ -53,7 +53,23 @@
<collections-navigation />
</template>
<v-info
type="warning"
v-if="bookmark && bookmarkExists === false"
:title="$t('bookmark_doesnt_exist')"
icon="bookmark"
>
{{ $t('bookmark_doesnt_exist_copy') }}
<template #append>
<v-button :to="currentCollectionLink">
{{ $t('bookmark_doesnt_exist_cta') }}
</v-button>
</template>
</v-info>
<component
v-else
class="layout"
ref="layout"
:is="`layout-${viewType}`"
@@ -129,20 +145,31 @@ export default defineComponent({
type: String,
required: true,
},
bookmark: {
type: String,
default: null,
},
},
setup(props) {
const layout = ref<LayoutComponent>(null);
const { collection } = toRefs(props);
const bookmarkID = computed(() => (props.bookmark ? +props.bookmark : null));
const projectsStore = useProjectsStore();
const { selection } = useSelection();
const { info: currentCollection, primaryKeyField } = useCollection(collection);
const { addNewLink, batchLink, collectionsLink } = useLinks();
const { viewType, viewOptions, viewQuery, filters, searchQuery } = useCollectionPreset(
collection
);
const { addNewLink, batchLink, collectionsLink, currentCollectionLink } = useLinks();
const {
viewType,
viewOptions,
viewQuery,
filters,
searchQuery,
savePreset,
bookmarkExists,
} = useCollectionPreset(collection, bookmarkID);
const { confirmDelete, deleting, batchDelete } = useBatchDelete();
if (viewType.value === null) {
@@ -164,6 +191,9 @@ export default defineComponent({
viewQuery,
viewType,
searchQuery,
savePreset,
bookmarkExists,
currentCollectionLink,
};
function useSelection() {
@@ -229,7 +259,13 @@ export default defineComponent({
return `/${currentProjectKey}/collections`;
});
return { addNewLink, batchLink, collectionsLink };
const currentCollectionLink = computed<string>(() => {
const currentProjectKey = projectsStore.state.currentProjectKey;
return `/${currentProjectKey}/collections/${props.collection}`;
});
return { addNewLink, batchLink, collectionsLink, currentCollectionLink };
}
},
});
@@ -259,4 +295,8 @@ export default defineComponent({
.layout {
--layout-offset-top: 64px;
}
.v-info {
margin: 20vh 0;
}
</style>

View File

@@ -127,6 +127,10 @@ export const useCollectionPresetsStore = createStore({
return collectionPreset;
},
getBookmark(bookmarkID: number) {
return this.state.collectionPresets.find((preset) => preset.id === bookmarkID) || null;
},
/**
* Saves the given preset. If it's the default preset, it saves it as a new preset. If the
* preset already exists, but doesn't have a user associated, it will create a preset for