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:
Rijk van Zanten
2020-05-08 15:33:54 -04:00
committed by GitHub
parent 9e5c0f0078
commit 828d81e044
16 changed files with 215 additions and 47 deletions

View File

@@ -15,7 +15,7 @@ import { defineComponent } from '@vue/composition-api';
export default defineComponent({
props: {
value: {
type: String,
type: [String, Number],
default: null,
},
dot: {

View File

@@ -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

View File

@@ -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'));

View File

@@ -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', []);
}

View File

@@ -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);
}
}

View File

@@ -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",

View File

@@ -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);

View File

@@ -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();

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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);
}
},
},

View File

@@ -1,4 +0,0 @@
import AddBookmark from './add-bookmark.vue';
export { AddBookmark };
export default AddBookmark;

View File

@@ -0,0 +1,4 @@
import BookmarkAdd from './bookmark-add.vue';
export { BookmarkAdd };
export default BookmarkAdd;

View 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>

View File

@@ -0,0 +1,4 @@
import BookmarkEdit from './bookmark-edit.vue';
export { BookmarkEdit };
export default BookmarkEdit;