mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Collection presets (#538)
* Fix table select all * Fix defaulting + staging changes on new preset * Navigate to bookmark after creation * Support exact prop on v-list * Use exact links in browse * Show bookmark title when on bookmark * Force layout refresh on navigation change * Allow numbers in v-badge * Add edit bookmark string * Rename add-bookmark to bookmark-add * Add bookmark-edit * Save name changes to bookmark
This commit is contained in:
@@ -15,7 +15,7 @@ import { defineComponent } from '@vue/composition-api';
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
type: [String, Number],
|
||||
default: null,
|
||||
},
|
||||
dot: {
|
||||
|
||||
@@ -67,6 +67,7 @@ A wrapper for list items that formats children nicely. Can be used on its own or
|
||||
| `to` | Render as vue router-link with to link | `null` |
|
||||
| `disabled` | Disable the list item | `false` |
|
||||
| `active` | Enable the list item's active state | `false` |
|
||||
| `exact` | Set the `exact` prop on router-link. Used with `to` | `false` |
|
||||
|
||||
## Events
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
:is="component"
|
||||
active-class="active"
|
||||
class="v-list-item"
|
||||
:exact="exact"
|
||||
:to="to"
|
||||
:class="{
|
||||
active,
|
||||
@@ -50,6 +51,10 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
exact: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { listeners }) {
|
||||
const component = computed<string>(() => (props.to ? 'router-link' : 'li'));
|
||||
|
||||
@@ -325,7 +325,14 @@ export default defineComponent({
|
||||
|
||||
function onToggleSelectAll(value: boolean) {
|
||||
if (value === true) {
|
||||
emit('select', clone(props.items));
|
||||
if (props.selectionUseKeys) {
|
||||
emit(
|
||||
'select',
|
||||
clone(props.items).map((item) => item[props.itemKey])
|
||||
);
|
||||
} else {
|
||||
emit('select', clone(props.items));
|
||||
}
|
||||
} else {
|
||||
emit('select', []);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,15 @@ export function useCollectionPreset(
|
||||
const localPreset = ref<CollectionPreset>({});
|
||||
initLocalPreset();
|
||||
|
||||
const savePreset = async (preset?: Partial<CollectionPreset>) =>
|
||||
await collectionPresetsStore.savePreset(preset ? preset : localPreset.value);
|
||||
const savePreset = async (preset?: Partial<CollectionPreset>) => {
|
||||
const updatedValues = await collectionPresetsStore.savePreset(
|
||||
preset ? preset : localPreset.value
|
||||
);
|
||||
|
||||
localPreset.value.id = updatedValues.id;
|
||||
|
||||
return updatedValues;
|
||||
};
|
||||
|
||||
const autoSave = debounce(async () => {
|
||||
if (!bookmark || bookmark.value === null) {
|
||||
@@ -116,8 +123,19 @@ export function useCollectionPreset(
|
||||
},
|
||||
});
|
||||
|
||||
const title = computed(() => {
|
||||
return localPreset.value?.title;
|
||||
const title = computed<string | null>({
|
||||
get() {
|
||||
return localPreset.value?.title || null;
|
||||
},
|
||||
set(newTitle: string | null) {
|
||||
localPreset.value = {
|
||||
...localPreset.value,
|
||||
title: newTitle,
|
||||
};
|
||||
|
||||
// This'll save immediately
|
||||
savePreset();
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -166,6 +184,6 @@ export function useCollectionPreset(
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
data.user = userStore.state.currentUser!.id;
|
||||
|
||||
await savePreset(data);
|
||||
return await savePreset(data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +219,8 @@
|
||||
"create": "Create Bookmark",
|
||||
"personal": "Personal (Current User Only)",
|
||||
"global": "Global (Every User)",
|
||||
"role": "For role {role}"
|
||||
"role": "For role {role}",
|
||||
"edit": "Edit Bookmark"
|
||||
},
|
||||
|
||||
"unexpected_error": "An unexpected error occured",
|
||||
|
||||
@@ -142,7 +142,15 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { defineComponent, PropType, ref, computed, inject, toRefs } from '@vue/composition-api';
|
||||
import {
|
||||
defineComponent,
|
||||
PropType,
|
||||
ref,
|
||||
computed,
|
||||
inject,
|
||||
toRefs,
|
||||
Ref,
|
||||
} from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import { HeaderRaw, Item } from '@/components/v-table/types';
|
||||
import { Field } from '@/stores/fields/types';
|
||||
@@ -183,11 +191,11 @@ export default defineComponent({
|
||||
},
|
||||
viewOptions: {
|
||||
type: Object as PropType<ViewOptions>,
|
||||
default: null,
|
||||
default: () => ({}),
|
||||
},
|
||||
viewQuery: {
|
||||
type: Object as PropType<ViewQuery>,
|
||||
default: null,
|
||||
default: () => ({}),
|
||||
},
|
||||
filters: {
|
||||
type: Array as PropType<Filter[]>,
|
||||
@@ -217,8 +225,8 @@ export default defineComponent({
|
||||
const mainElement = inject('main-element', ref<Element>(null));
|
||||
|
||||
const _selection = useSync(props, 'selection', emit);
|
||||
const _viewOptions = useSync(props, 'viewOptions', emit);
|
||||
const _viewQuery = useSync(props, 'viewQuery', emit);
|
||||
const _viewOptions: Ref<any> = useSync(props, 'viewOptions', emit);
|
||||
const _viewQuery: Ref<any> = useSync(props, 'viewQuery', emit);
|
||||
const _filters = useSync(props, 'filters', emit);
|
||||
const _searchQuery = useSync(props, 'searchQuery', emit);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<v-list nav>
|
||||
<v-list-item v-for="navItem in navItems" :key="navItem.to" :to="navItem.to">
|
||||
<v-list-item :exact="exact" v-for="navItem in navItems" :key="navItem.to" :to="navItem.to">
|
||||
<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>
|
||||
@@ -8,7 +8,7 @@
|
||||
<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 exact 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>
|
||||
@@ -23,7 +23,12 @@ import useCollectionPresetsStore from '@/stores/collection-presets';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
export default defineComponent({
|
||||
props: {},
|
||||
props: {
|
||||
exact: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const collectionPresetsStore = useCollectionPresetsStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<collections-not-found v-if="!currentCollection || collection.startsWith('directus_')" />
|
||||
<private-view v-else :title="currentCollection.name">
|
||||
<private-view v-else :title="bookmark ? bookmarkName : currentCollection.name">
|
||||
<template #title-outer:prepend>
|
||||
<v-button class="header-icon" rounded icon secondary :to="collectionsLink">
|
||||
<v-icon :name="currentCollection.icon" />
|
||||
@@ -8,20 +8,34 @@
|
||||
</template>
|
||||
|
||||
<template #title-outer:append>
|
||||
<add-bookmark
|
||||
class="add-bookmark"
|
||||
v-model="addBookmarkActive"
|
||||
<bookmark-add
|
||||
v-if="!bookmark"
|
||||
class="bookmark-add"
|
||||
v-model="bookmarkDialogActive"
|
||||
@save="createBookmark"
|
||||
:saving="creatingBookmark"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-icon class="toggle" name="bookmark_outline" @click="on" />
|
||||
</template>
|
||||
</add-bookmark>
|
||||
</bookmark-add>
|
||||
|
||||
<bookmark-edit
|
||||
v-else
|
||||
class="bookmark-edit"
|
||||
v-model="bookmarkDialogActive"
|
||||
:saving="editingBookmark"
|
||||
:name="bookmarkName"
|
||||
@save="editBookmark"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-icon class="toggle" name="bookmark" @click="on" />
|
||||
</template>
|
||||
</bookmark-edit>
|
||||
</template>
|
||||
|
||||
<template #drawer>
|
||||
<layout-drawer-detail @input="viewType = $event" :value="viewType || 'tabular'" />
|
||||
<layout-drawer-detail @input="viewType = $event" :value="viewType" />
|
||||
<portal-target name="drawer" />
|
||||
</template>
|
||||
|
||||
@@ -63,7 +77,7 @@
|
||||
</template>
|
||||
|
||||
<template #navigation>
|
||||
<collections-navigation />
|
||||
<collections-navigation exact />
|
||||
</template>
|
||||
|
||||
<v-info
|
||||
@@ -85,6 +99,7 @@
|
||||
v-else
|
||||
class="layout"
|
||||
ref="layout"
|
||||
:key="$route.fullPath"
|
||||
:is="`layout-${viewType || 'tabular'}`"
|
||||
:collection="collection"
|
||||
:selection.sync="selection"
|
||||
@@ -110,7 +125,9 @@ import useCollection from '@/composables/use-collection';
|
||||
import useCollectionPreset from '@/composables/use-collection-preset';
|
||||
import LayoutDrawerDetail from '@/views/private/components/layout-drawer-detail';
|
||||
import SearchInput from '@/views/private/components/search-input';
|
||||
import AddBookmark from '@/views/private/components/add-bookmark';
|
||||
import BookmarkAdd from '@/views/private/components/bookmark-add';
|
||||
import BookmarkEdit from '@/views/private/components/bookmark-edit';
|
||||
import router from '@/router';
|
||||
|
||||
const redirectIfNeeded: NavigationGuard = async (to, from, next) => {
|
||||
const collectionsStore = useCollectionsStore();
|
||||
@@ -152,7 +169,8 @@ export default defineComponent({
|
||||
CollectionsNotFound,
|
||||
LayoutDrawerDetail,
|
||||
SearchInput,
|
||||
AddBookmark,
|
||||
BookmarkAdd,
|
||||
BookmarkEdit,
|
||||
},
|
||||
props: {
|
||||
collection: {
|
||||
@@ -184,10 +202,21 @@ export default defineComponent({
|
||||
savePreset,
|
||||
bookmarkExists,
|
||||
saveCurrentAsBookmark,
|
||||
title: bookmarkName,
|
||||
} = useCollectionPreset(collection, bookmarkID);
|
||||
const { confirmDelete, deleting, batchDelete } = useBatchDelete();
|
||||
|
||||
const { addBookmarkActive, creatingBookmark, createBookmark } = useBookmarks();
|
||||
const {
|
||||
bookmarkDialogActive,
|
||||
creatingBookmark,
|
||||
createBookmark,
|
||||
editingBookmark,
|
||||
editBookmark,
|
||||
} = useBookmarks();
|
||||
|
||||
if (viewType.value === null) {
|
||||
viewType.value = 'tabular';
|
||||
}
|
||||
|
||||
return {
|
||||
addNewLink,
|
||||
@@ -207,9 +236,12 @@ export default defineComponent({
|
||||
savePreset,
|
||||
bookmarkExists,
|
||||
currentCollectionLink,
|
||||
addBookmarkActive,
|
||||
bookmarkDialogActive,
|
||||
creatingBookmark,
|
||||
createBookmark,
|
||||
bookmarkName,
|
||||
editingBookmark,
|
||||
editBookmark,
|
||||
};
|
||||
|
||||
function useSelection() {
|
||||
@@ -282,24 +314,41 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function useBookmarks() {
|
||||
const addBookmarkActive = ref(false);
|
||||
const bookmarkDialogActive = ref(false);
|
||||
const creatingBookmark = ref(false);
|
||||
const editingBookmark = ref(false);
|
||||
|
||||
return { addBookmarkActive, creatingBookmark, createBookmark };
|
||||
return {
|
||||
bookmarkDialogActive,
|
||||
creatingBookmark,
|
||||
createBookmark,
|
||||
editingBookmark,
|
||||
editBookmark,
|
||||
};
|
||||
|
||||
async function createBookmark(name: string) {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
creatingBookmark.value = true;
|
||||
|
||||
try {
|
||||
await saveCurrentAsBookmark({ title: name });
|
||||
const newBookmark = await saveCurrentAsBookmark({ title: name });
|
||||
router.push(
|
||||
`/${currentProjectKey}/collections/${newBookmark.collection}?bookmark=${newBookmark.id}`
|
||||
);
|
||||
|
||||
addBookmarkActive.value = false;
|
||||
bookmarkDialogActive.value = false;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
creatingBookmark.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function editBookmark(name: string) {
|
||||
bookmarkName.value = name;
|
||||
bookmarkDialogActive.value = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -334,7 +383,8 @@ export default defineComponent({
|
||||
margin: 20vh 0;
|
||||
}
|
||||
|
||||
.add-bookmark .toggle {
|
||||
.bookmark-add .toggle,
|
||||
.bookmark-edit .toggle {
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -140,14 +140,12 @@ export default defineComponent({
|
||||
viewType.value = 'cards';
|
||||
}
|
||||
|
||||
if (viewOptions.value === null) {
|
||||
if (viewType.value === 'cards') {
|
||||
viewOptions.value = {
|
||||
icon: 'insert_drive_file',
|
||||
title: '{{title}}',
|
||||
subtitle: '{{type}} • {{filesize}}',
|
||||
};
|
||||
}
|
||||
if (viewOptions.value === null && viewType.value === 'cards') {
|
||||
viewOptions.value = {
|
||||
icon: 'insert_drive_file',
|
||||
title: '{{title}}',
|
||||
subtitle: '{{type}} • {{filesize}}',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -52,6 +52,8 @@ export const useCollectionPresetsStore = createStore({
|
||||
const response = await api.post(`/${currentProjectKey}/collection_presets`, newPreset);
|
||||
|
||||
this.state.collectionPresets.push(response.data.data);
|
||||
|
||||
return response.data.data;
|
||||
},
|
||||
async update(id: number, updates: Partial<CollectionPreset>) {
|
||||
const { currentProjectKey } = useProjectsStore().state;
|
||||
@@ -69,6 +71,8 @@ export const useCollectionPresetsStore = createStore({
|
||||
|
||||
return preset;
|
||||
});
|
||||
|
||||
return response.data.data;
|
||||
},
|
||||
async delete(id: number) {
|
||||
const { currentProjectKey } = useProjectsStore().state;
|
||||
@@ -145,7 +149,7 @@ export const useCollectionPresetsStore = createStore({
|
||||
preset = { ...preset };
|
||||
|
||||
if (preset.id === undefined || preset.id === null) {
|
||||
return this.create({
|
||||
return await this.create({
|
||||
...preset,
|
||||
user: userID,
|
||||
});
|
||||
@@ -154,14 +158,14 @@ export const useCollectionPresetsStore = createStore({
|
||||
if (preset.user !== userID) {
|
||||
if (preset.hasOwnProperty('id')) delete preset.id;
|
||||
|
||||
return this.create({
|
||||
return await this.create({
|
||||
...preset,
|
||||
user: userID,
|
||||
});
|
||||
} else {
|
||||
const id = preset.id;
|
||||
delete preset.id;
|
||||
return this.update(id, preset);
|
||||
return await this.update(id, preset);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import AddBookmark from './add-bookmark.vue';
|
||||
|
||||
export { AddBookmark };
|
||||
export default AddBookmark;
|
||||
4
src/views/private/components/bookmark-add/index.ts
Normal file
4
src/views/private/components/bookmark-add/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import BookmarkAdd from './bookmark-add.vue';
|
||||
|
||||
export { BookmarkAdd };
|
||||
export default BookmarkAdd;
|
||||
67
src/views/private/components/bookmark-edit/bookmark-edit.vue
Normal file
67
src/views/private/components/bookmark-edit/bookmark-edit.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<v-dialog :active="active" @toggle="$listeners.toggle" persistent>
|
||||
<template #activator="slotBinding">
|
||||
<slot name="activator" v-bind="slotBinding" />
|
||||
</template>
|
||||
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('bookmarks.edit') }}</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<v-input v-model="bookmarkName" :placeholder="$t('bookmarks.name')" />
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-button @click="cancel" secondary>
|
||||
{{ $t('cancel') }}
|
||||
</v-button>
|
||||
<v-button
|
||||
:disabled="bookmarkName === null || bookmarkName.length === 0"
|
||||
@click="$emit('save', bookmarkName)"
|
||||
:loading="saving"
|
||||
>
|
||||
{{ $t('save') }}
|
||||
</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from '@vue/composition-api';
|
||||
|
||||
export default defineComponent({
|
||||
model: {
|
||||
prop: 'active',
|
||||
event: 'toggle',
|
||||
},
|
||||
props: {
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
saving: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const bookmarkName = ref(props.name);
|
||||
|
||||
watch(
|
||||
() => props.name,
|
||||
(newName: string) => (bookmarkName.value = newName)
|
||||
);
|
||||
|
||||
return { bookmarkName, cancel };
|
||||
|
||||
function cancel() {
|
||||
emit('toggle', false);
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
4
src/views/private/components/bookmark-edit/index.ts
Normal file
4
src/views/private/components/bookmark-edit/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import BookmarkEdit from './bookmark-edit.vue';
|
||||
|
||||
export { BookmarkEdit };
|
||||
export default BookmarkEdit;
|
||||
Reference in New Issue
Block a user