From 97c8f763abd23b3a67863f65c6708731ffc9329f Mon Sep 17 00:00:00 2001 From: Rijk van Zanten Date: Tue, 14 Apr 2020 13:28:39 -0400 Subject: [PATCH] Finish settings > datamodel > collections (#414) * Add missing slot to readme * Show and order all collections * Render correct icon for each collection type * Add ctx menu on collections overview * Use subdued colors in icons by default * Add delete collection flow * Add delete flow * Move options to separate component * Add manage / unmanage toggles * Add collections filter * Make non-nav list item icons subdued * Tweak where loader shows up when managing * Pass item key to v-table --- src/components/v-list/v-list-item-icon.vue | 5 + .../circular/v-progress-circular.vue | 4 +- src/components/v-table/readme.md | 9 +- .../v-table/table-row/table-row.vue | 3 +- src/lang/en-US/index.json | 16 +- .../data-model/collections/collections.vue | 191 +++++++++++++++--- .../collection-options/collection-options.vue | 129 ++++++++++++ .../components/collection-options/index.ts | 4 + .../collections-filter/collections-filter.vue | 40 ++++ .../components/collections-filter/index.ts | 4 + src/stores/collections/collections.ts | 41 ++++ 11 files changed, 407 insertions(+), 39 deletions(-) create mode 100644 src/modules/settings/routes/data-model/collections/components/collection-options/collection-options.vue create mode 100644 src/modules/settings/routes/data-model/collections/components/collection-options/index.ts create mode 100644 src/modules/settings/routes/data-model/collections/components/collections-filter/collections-filter.vue create mode 100644 src/modules/settings/routes/data-model/collections/components/collections-filter/index.ts diff --git a/src/components/v-list/v-list-item-icon.vue b/src/components/v-list/v-list-item-icon.vue index 8aca952a6f..e226e37971 100644 --- a/src/components/v-list/v-list-item-icon.vue +++ b/src/components/v-list/v-list-item-icon.vue @@ -46,6 +46,7 @@ export default defineComponent({ } } } + &.dense { #{$this} { margin-top: 4px; @@ -60,6 +61,10 @@ export default defineComponent({ } } } + + &.dense:not(.nav) #{$this} { + color: var(--foreground-subdued); + } } } } diff --git a/src/components/v-progress/circular/v-progress-circular.vue b/src/components/v-progress/circular/v-progress-circular.vue index 8b1253f671..5a350c8ccd 100644 --- a/src/components/v-progress/circular/v-progress-circular.vue +++ b/src/components/v-progress/circular/v-progress-circular.vue @@ -67,8 +67,10 @@ export default defineComponent({ } &.small { - --v-progress-circular-size: 16px; + --v-progress-circular-size: 20px; --v-progress-circular-line-size: 3px; + + margin: 2px; } &.large { diff --git a/src/components/v-table/readme.md b/src/components/v-table/readme.md index 76182e8055..d4a0eee634 100644 --- a/src/components/v-table/readme.md +++ b/src/components/v-table/readme.md @@ -137,10 +137,11 @@ export default defineComponent({ | `select` | Emitted when selected items change | `any[]` | ## Slots -| Slot | Description | -|------------------|----------------------------------| -| `header.[value]` | Override individual header cells | -| `item.[value]` | Override individual row cells | +| Slot | Description | +|------------------|----------------------------------------------------| +| `header.[value]` | Override individual header cells | +| `item.[value]` | Override individual row cells | +| `item-append` | Append content at the end of each row in the table | ## CSS Variables | Variable | Default | diff --git a/src/components/v-table/table-row/table-row.vue b/src/components/v-table/table-row/table-row.vue index 2159e0f221..717e2c6462 100644 --- a/src/components/v-table/table-row/table-row.vue +++ b/src/components/v-table/table-row/table-row.vue @@ -25,7 +25,7 @@ - + @@ -146,6 +146,7 @@ export default defineComponent({ .append { display: flex; align-items: center; + justify-content: flex-end; } } diff --git a/src/lang/en-US/index.json b/src/lang/en-US/index.json index 7343ce0733..ce616939bc 100644 --- a/src/lang/en-US/index.json +++ b/src/lang/en-US/index.json @@ -179,6 +179,21 @@ "all_users": "All Users", + "delete_collection": "Delete Collection", + "unmanage_collection": "Unmanage Collection", + + "update_collection_success": "Updated Collection", + "update_collection_failed": "Couldn't Update Collection", + "delete_collection_success": "Deleted Collection", + "delete_collection_failed": "Couldn't Delete Collection", + + "delete_collection_are_you_sure": "Are you sure you want to delete this collection? This will delete the collection and all items in it. This action is permanent.", + + "collections_shown": "Collections Shown", + "visible_collections": "Visible Collections", + "hidden_collections": "Hidden Collections", + "unmanaged_collections": "Unmanaged Collections", + "system_collections": "System Collections", "about_directus": "About Directus", "activity_log": "Activity Log", @@ -326,7 +341,6 @@ "delete_are_you_sure": "This action is permanent and can not be undone. Are you sure you would like to proceed?", "delete_bookmark": "Delete Bookmark", "delete_bookmark_body": "Are you sure you want to delete this bookmark? This action cannot be undone.", - "delete_collection_are_you_sure": "Are you sure you want to delete this collection? This action can not be undone.", "delete_confirmation": "Delete Confirmation", "delete_field_are_you_sure": "Are you sure you want to delete the field \"{field}\"? This action can not be undone.", "delete_role_are_you_sure": "Are you sure to delete the role \"{name}\"? This action cannot be undone.", diff --git a/src/modules/settings/routes/data-model/collections/collections.vue b/src/modules/settings/routes/data-model/collections/collections.vue index 9a7587c525..8d90cc90dd 100644 --- a/src/modules/settings/routes/data-model/collections/collections.vue +++ b/src/modules/settings/routes/data-model/collections/collections.vue @@ -16,28 +16,59 @@ - - + - +
+ + - - + + + + + + +
@@ -53,11 +84,16 @@ import useCollectionsStore from '@/stores/collections'; import { Collection } from '@/stores/collections/types'; import useProjectsStore from '@/stores/projects'; import router from '@/router'; +import { sortBy } from 'lodash'; +import CollectionOptions from './components/collection-options'; +import CollectionsFilter from './components/collections-filter'; export default defineComponent({ - components: { SettingsNavigation, NewCollection }, + components: { SettingsNavigation, NewCollection, CollectionOptions, CollectionsFilter }, setup() { const addNewActive = ref(false); + const activeTypes = ref(['visible', 'hidden', 'unmanaged']); + const collectionsStore = useCollectionsStore(); const tableHeaders = ref([ @@ -67,27 +103,104 @@ export default defineComponent({ sortable: false, }, { - text: i18n.tc('collection', 0), + text: i18n.t('name'), value: 'collection', }, { - text: i18n.t('note'), + text: i18n.t('description'), value: 'note', }, ]); - const items = computed(() => { - return collectionsStore.state.collections.filter( - ({ collection }) => collection.startsWith('directus_') === false - ); - }); - - return { addNewActive, tableHeaders, items, openCollection }; - function openCollection({ collection }: Collection) { const { currentProjectKey } = useProjectsStore().state; router.push(`/${currentProjectKey}/settings/data-model/${collection}`); } + + const { items } = useItems(); + + return { + addNewActive, + tableHeaders, + items, + openCollection, + activeTypes, + }; + + function useItems() { + const visible = computed(() => { + return sortBy( + collectionsStore.state.collections.filter( + (collection) => + collection.collection.startsWith('directus_') === false && + collection.managed === true && + collection.hidden === false + ), + 'collection' + ); + }); + + const hidden = computed(() => { + return sortBy( + collectionsStore.state.collections + .filter( + (collection) => + collection.collection.startsWith('directus_') === false && + collection.managed === true && + collection.hidden === true + ) + .map((collection) => ({ ...collection, icon: 'visibility_off' })), + 'collection' + ); + }); + + const system = computed(() => { + return sortBy( + collectionsStore.state.collections + .filter( + (collection) => collection.collection.startsWith('directus_') === true + ) + .map((collection) => ({ ...collection, icon: 'settings' })), + 'collection' + ); + }); + + const unmanaged = computed(() => { + return sortBy( + collectionsStore.state.collections + .filter( + (collection) => collection.collection.startsWith('directus_') === false + ) + .filter((collection) => collection.managed === false) + .map((collection) => ({ ...collection, icon: 'block' })), + 'collection' + ); + }); + + const items = computed(() => { + const items = []; + + if (activeTypes.value.includes('visible')) { + items.push(visible.value); + } + + if (activeTypes.value.includes('hidden')) { + items.push(hidden.value); + } + + if (activeTypes.value.includes('unmanaged')) { + items.push(unmanaged.value); + } + + if (activeTypes.value.includes('system')) { + items.push(system.value); + } + + return items.flat(); + }); + + return { items }; + } }, }); @@ -101,10 +214,24 @@ export default defineComponent({ color: var(--foreground-subdued); } -.v-table { +.system { + color: var(--primary); +} + +.unmanaged { + color: var(--warning); +} + +.padding-box { padding: var(--content-padding); } +.v-table { + --v-table-sticky-offset-top: 64px; + + display: contents; +} + .header-icon { --v-button-color-disabled: var(--warning); --v-button-background-color-disabled: var(--warning-alt); diff --git a/src/modules/settings/routes/data-model/collections/components/collection-options/collection-options.vue b/src/modules/settings/routes/data-model/collections/components/collection-options/collection-options.vue new file mode 100644 index 0000000000..3ee55921dd --- /dev/null +++ b/src/modules/settings/routes/data-model/collections/components/collection-options/collection-options.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/src/modules/settings/routes/data-model/collections/components/collection-options/index.ts b/src/modules/settings/routes/data-model/collections/components/collection-options/index.ts new file mode 100644 index 0000000000..f0ccedb809 --- /dev/null +++ b/src/modules/settings/routes/data-model/collections/components/collection-options/index.ts @@ -0,0 +1,4 @@ +import CollectionOptions from './collection-options.vue'; + +export { CollectionOptions }; +export default CollectionOptions; diff --git a/src/modules/settings/routes/data-model/collections/components/collections-filter/collections-filter.vue b/src/modules/settings/routes/data-model/collections/components/collections-filter/collections-filter.vue new file mode 100644 index 0000000000..c7952c556f --- /dev/null +++ b/src/modules/settings/routes/data-model/collections/components/collections-filter/collections-filter.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/modules/settings/routes/data-model/collections/components/collections-filter/index.ts b/src/modules/settings/routes/data-model/collections/components/collections-filter/index.ts new file mode 100644 index 0000000000..913f2c0c42 --- /dev/null +++ b/src/modules/settings/routes/data-model/collections/components/collections-filter/index.ts @@ -0,0 +1,4 @@ +import CollectionsFilter from './collections-filter.vue'; + +export { CollectionsFilter }; +export default CollectionsFilter; diff --git a/src/stores/collections/collections.ts b/src/stores/collections/collections.ts index 4d340a82b1..b17454210f 100644 --- a/src/stores/collections/collections.ts +++ b/src/stores/collections/collections.ts @@ -6,6 +6,7 @@ import i18n from '@/lang/'; import { notEmpty } from '@/utils/is-empty/'; import VueI18n from 'vue-i18n'; import formatTitle from '@directus/format-title'; +import notify from '@/utils/notify'; export const useCollectionsStore = createStore({ id: 'collectionsStore', @@ -58,6 +59,46 @@ export const useCollectionsStore = createStore({ async dehydrate() { this.reset(); }, + async updateCollection(collection: string, updates: Partial) { + const { currentProjectKey } = useProjectsStore().state; + + try { + await api.patch(`${currentProjectKey}/collections/${collection}`, updates); + await this.hydrate(); + notify({ + type: 'success', + title: i18n.t('update_collection_success'), + text: collection, + }); + } catch (error) { + notify({ + type: 'error', + title: i18n.t('update_collection_failed'), + text: collection, + }); + throw error; + } + }, + async deleteCollection(collection: string) { + const { currentProjectKey } = useProjectsStore().state; + + try { + await api.delete(`${currentProjectKey}/collections/${collection}`); + await this.hydrate(); + notify({ + type: 'success', + title: i18n.t('delete_collection_success'), + text: collection, + }); + } catch (error) { + notify({ + type: 'error', + title: i18n.t('delete_collection_failed'), + text: collection, + }); + throw error; + } + }, getCollection(collectionKey: string) { return ( this.state.collections.find(