mirror of
https://github.com/directus/directus.git
synced 2026-04-03 03:00:39 -04:00
Add users module (#360)
* Install micromustache * Add useCollectionPreset composition * Add detailRoute prop to layouts Allows for overriding where the detail view is located from the parent * Add locale translations for users/files/activity * Update collections module to use new composition / layout prop * Update useItem useItems to allow for directus_ collections * Add default width to all fields with no width * Only fetch comment,create,update,delete activity on detail * Fix out-transition on sign-out button * Add users module
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
"date-fns": "^2.11.1",
|
||||
"lodash": "^4.17.15",
|
||||
"marked": "^0.8.2",
|
||||
"micromustache": "^7.1.0",
|
||||
"nanoid": "^3.0.2",
|
||||
"pinia": "0.0.5",
|
||||
"portal-vue": "^2.1.7",
|
||||
|
||||
@@ -190,6 +190,15 @@ export default defineComponent({
|
||||
return a.sort > b.sort ? 1 : -1;
|
||||
});
|
||||
|
||||
// Make sure all form fields have a width associated with it
|
||||
formFields = formFields.map((field) => {
|
||||
if (!field.width) {
|
||||
field.width = 'full';
|
||||
}
|
||||
|
||||
return field;
|
||||
});
|
||||
|
||||
// Change the class to half-right if the current element is preceded by another half width field
|
||||
// this makes them align side by side
|
||||
formFields = formFields.map((field, index, formFields) => {
|
||||
|
||||
4
src/compositions/use-collection-preset/index.ts
Normal file
4
src/compositions/use-collection-preset/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { useCollectionPreset } from './use-collection-preset';
|
||||
|
||||
export { useCollectionPreset };
|
||||
export default useCollectionPreset;
|
||||
@@ -0,0 +1,52 @@
|
||||
import useCollectionPresetStore from '@/stores/collection-presets';
|
||||
import { ref, Ref, computed, watch } from '@vue/composition-api';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
export function useCollectionPreset(collection: Ref<string>) {
|
||||
const collectionPresetsStore = useCollectionPresetStore();
|
||||
|
||||
const savePreset = debounce(collectionPresetsStore.savePreset, 450);
|
||||
const localPreset = ref({
|
||||
...collectionPresetsStore.getPresetForCollection(collection.value),
|
||||
});
|
||||
|
||||
watch(collection, () => {
|
||||
localPreset.value = {
|
||||
...collectionPresetsStore.getPresetForCollection(collection.value),
|
||||
};
|
||||
});
|
||||
|
||||
const viewOptions = computed({
|
||||
get() {
|
||||
return localPreset.value.view_options?.[localPreset.value.view_type] || null;
|
||||
},
|
||||
set(val) {
|
||||
localPreset.value = {
|
||||
...localPreset.value,
|
||||
view_options: {
|
||||
...localPreset.value.view_options,
|
||||
[localPreset.value.view_type]: val,
|
||||
},
|
||||
};
|
||||
savePreset(localPreset.value);
|
||||
},
|
||||
});
|
||||
|
||||
const viewQuery = computed({
|
||||
get() {
|
||||
return localPreset.value.view_query?.[localPreset.value.view_type] || null;
|
||||
},
|
||||
set(val) {
|
||||
localPreset.value = {
|
||||
...localPreset.value,
|
||||
view_query: {
|
||||
...localPreset.value.view_query,
|
||||
[localPreset.value.view_type]: val,
|
||||
},
|
||||
};
|
||||
savePreset(localPreset.value);
|
||||
},
|
||||
});
|
||||
|
||||
return { viewOptions, viewQuery };
|
||||
}
|
||||
@@ -20,6 +20,13 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
() => typeof primaryKey.value === 'string' && primaryKey.value.includes(',')
|
||||
);
|
||||
|
||||
const endpoint = computed(() => {
|
||||
const currentProjectKey = useProjectsStore().state.currentProjectKey;
|
||||
return collection.value.startsWith('directus_')
|
||||
? `/${currentProjectKey}/${collection.value.substring(9)}`
|
||||
: `/${currentProjectKey}/items/${collection.value}`;
|
||||
});
|
||||
|
||||
watch([collection, primaryKey], refresh);
|
||||
|
||||
return {
|
||||
@@ -38,13 +45,10 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
};
|
||||
|
||||
async function getItem() {
|
||||
const currentProjectKey = useProjectsStore().state.currentProjectKey;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(
|
||||
`/${currentProjectKey}/items/${collection.value}/${primaryKey.value}`
|
||||
);
|
||||
const response = await api.get(`${endpoint.value}/${primaryKey.value}`);
|
||||
|
||||
setItemValueToResponse(response);
|
||||
} catch (err) {
|
||||
@@ -55,17 +59,13 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
}
|
||||
|
||||
async function save() {
|
||||
const currentProjectKey = useProjectsStore().state.currentProjectKey;
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
let response;
|
||||
|
||||
if (isNew.value === true) {
|
||||
response = await api.post(
|
||||
`/${currentProjectKey}/items/${collection.value}`,
|
||||
edits.value
|
||||
);
|
||||
response = await api.post(endpoint.value, edits.value);
|
||||
|
||||
notify({
|
||||
title: i18n.tc('item_create_success', isBatch.value ? 2 : 1),
|
||||
@@ -78,10 +78,7 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
type: 'success',
|
||||
});
|
||||
} else {
|
||||
response = await api.patch(
|
||||
`/${currentProjectKey}/items/${collection.value}/${primaryKey.value}`,
|
||||
edits.value
|
||||
);
|
||||
response = await api.patch(`${endpoint.value}/${primaryKey.value}`, edits.value);
|
||||
|
||||
notify({
|
||||
title: i18n.tc('item_update_success', isBatch.value ? 2 : 1),
|
||||
@@ -129,7 +126,6 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
}
|
||||
|
||||
async function saveAsCopy() {
|
||||
const currentProjectKey = useProjectsStore().state.currentProjectKey;
|
||||
saving.value = true;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -144,10 +140,7 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.post(
|
||||
`/${currentProjectKey}/items/${collection.value}`,
|
||||
newItem
|
||||
);
|
||||
const response = await api.post(endpoint.value, newItem);
|
||||
|
||||
notify({
|
||||
title: i18n.t('item_create_success'),
|
||||
@@ -180,11 +173,10 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
}
|
||||
|
||||
async function remove() {
|
||||
const currentProjectKey = useProjectsStore().state.currentProjectKey;
|
||||
deleting.value = true;
|
||||
|
||||
try {
|
||||
await api.delete(`/${currentProjectKey}/items/${collection.value}/${primaryKey.value}`);
|
||||
await api.delete(`${endpoint.value}/${primaryKey.value}`);
|
||||
|
||||
item.value = null;
|
||||
|
||||
|
||||
@@ -85,7 +85,11 @@ export function useItems(collection: Ref<string>, options: Options) {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/items/${collection.value}`, {
|
||||
const endpoint = collection.value.startsWith('directus_')
|
||||
? `/${currentProjectKey}/${collection.value.substring(9)}`
|
||||
: `/${currentProjectKey}/items/${collection.value}`;
|
||||
|
||||
const response = await api.get(endpoint, {
|
||||
params: {
|
||||
limit: limit.value,
|
||||
fields: fieldsToFetch,
|
||||
|
||||
@@ -118,6 +118,10 @@
|
||||
|
||||
"item_count": "No Items | One Item | {count} Items",
|
||||
|
||||
"users": "Users",
|
||||
"files": "Files",
|
||||
"Activity": "Activity",
|
||||
|
||||
|
||||
"about_directus": "About Directus",
|
||||
"activity": "Activity",
|
||||
@@ -165,12 +169,6 @@
|
||||
"collection_updated": "Collection Updated",
|
||||
"collections_and_fields": "Collection & Fields",
|
||||
|
||||
"collections": {
|
||||
"directus_activity": "Activity",
|
||||
"directus_files": "Files",
|
||||
"directus_users": "Users"
|
||||
},
|
||||
|
||||
"fields": {
|
||||
"directus_activity": {
|
||||
"action": "Action",
|
||||
|
||||
@@ -100,6 +100,7 @@ import { debounce } from 'lodash';
|
||||
import Draggable from 'vuedraggable';
|
||||
import useCollection from '@/compositions/use-collection';
|
||||
import useItems from '@/compositions/use-items';
|
||||
import { render } from 'micromustache';
|
||||
|
||||
type ViewOptions = {
|
||||
widths?: {
|
||||
@@ -125,10 +126,6 @@ export default defineComponent({
|
||||
type: Array as PropType<Item[]>,
|
||||
default: () => [],
|
||||
},
|
||||
selectMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
viewOptions: {
|
||||
type: Object as PropType<ViewOptions>,
|
||||
default: null,
|
||||
@@ -137,9 +134,17 @@ export default defineComponent({
|
||||
type: Object as PropType<ViewQuery>,
|
||||
default: null,
|
||||
},
|
||||
selectMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
detailRoute: {
|
||||
type: String,
|
||||
default: `/{{project}}/collections/{{collection}}/{{primaryKey}}`,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { currentProjectKey } = toRefs(useProjectsStore().state);
|
||||
|
||||
const table = ref<Vue>(null);
|
||||
const mainElement = inject('main-element', ref<Element>(null));
|
||||
@@ -360,7 +365,11 @@ export default defineComponent({
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const primaryKey = item[primaryKeyField.value!.field];
|
||||
router.push(
|
||||
`/${projectsStore.state.currentProjectKey}/collections/${props.collection}/${primaryKey}`
|
||||
render(props.detailRoute, {
|
||||
project: currentProjectKey.value,
|
||||
collection: collection.value,
|
||||
primaryKey,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-list nav>
|
||||
<v-list nav dense>
|
||||
<v-list-item 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>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<private-view v-if="currentCollection" :title="currentCollection.name">
|
||||
<collections-not-found v-if="!currentCollection || collection.startsWith('directus_')" />
|
||||
<private-view v-else :title="currentCollection.name">
|
||||
<template #title-outer:prepend>
|
||||
<v-button rounded disabled icon secondary>
|
||||
<v-icon :name="currentCollection.icon" />
|
||||
@@ -62,11 +63,10 @@
|
||||
:view-query.sync="viewQuery"
|
||||
/>
|
||||
</private-view>
|
||||
<collections-not-found v-else />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref, watch } from '@vue/composition-api';
|
||||
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';
|
||||
@@ -75,9 +75,9 @@ import useProjectsStore from '@/stores/projects';
|
||||
import { i18n } from '@/lang';
|
||||
import api from '@/api';
|
||||
import { LayoutComponent } from '@/layouts/types';
|
||||
import useCollectionPresetsStore from '@/stores/collection-presets';
|
||||
import { debounce } from 'lodash';
|
||||
import CollectionsNotFound from '../not-found/';
|
||||
import useCollection from '@/compositions/use-collection';
|
||||
import useCollectionPreset from '@/compositions/use-collection-preset';
|
||||
|
||||
const redirectIfNeeded: NavigationGuard = async (to, from, next) => {
|
||||
const collectionsStore = useCollectionsStore();
|
||||
@@ -125,15 +125,14 @@ export default defineComponent({
|
||||
setup(props) {
|
||||
const layout = ref<LayoutComponent>(null);
|
||||
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const { collection } = toRefs(props);
|
||||
|
||||
const projectsStore = useProjectsStore();
|
||||
const collectionPresetsStore = useCollectionPresetsStore();
|
||||
|
||||
const { selection } = useSelection();
|
||||
const { currentCollection, primaryKeyField } = useCollectionInfo();
|
||||
const { info: currentCollection, primaryKeyField } = useCollection(collection);
|
||||
const { addNewLink, batchLink } = useLinks();
|
||||
const { viewOptions, viewQuery } = useCollectionPreset();
|
||||
const { viewOptions, viewQuery } = useCollectionPreset(collection);
|
||||
const { confirmDelete, deleting, batchDelete } = useBatchDelete();
|
||||
const { breadcrumb } = useBreadcrumb();
|
||||
|
||||
@@ -163,17 +162,6 @@ export default defineComponent({
|
||||
return { selection };
|
||||
}
|
||||
|
||||
function useCollectionInfo() {
|
||||
const currentCollection = computed(() =>
|
||||
collectionsStore.getCollection(props.collection)
|
||||
);
|
||||
const primaryKeyField = computed(() =>
|
||||
fieldsStore.getPrimaryKeyFieldForCollection(props.collection)
|
||||
);
|
||||
|
||||
return { currentCollection, primaryKeyField };
|
||||
}
|
||||
|
||||
function useBatchDelete() {
|
||||
const confirmDelete = ref(false);
|
||||
const deleting = ref(false);
|
||||
@@ -188,7 +176,8 @@ export default defineComponent({
|
||||
confirmDelete.value = false;
|
||||
|
||||
const batchPrimaryKeys = selection.value
|
||||
.map((item) => item[primaryKeyField.value.field])
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
.map((item) => item[primaryKeyField.value!.field])
|
||||
.join();
|
||||
|
||||
await api.delete(
|
||||
@@ -212,7 +201,8 @@ export default defineComponent({
|
||||
const batchLink = computed<string>(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
const batchPrimaryKeys = selection.value
|
||||
.map((item) => item[primaryKeyField.value.field])
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
.map((item) => item[primaryKeyField.value!.field])
|
||||
.join();
|
||||
return `/${currentProjectKey}/collections/${props.collection}/${batchPrimaryKeys}`;
|
||||
});
|
||||
@@ -220,61 +210,6 @@ export default defineComponent({
|
||||
return { addNewLink, batchLink };
|
||||
}
|
||||
|
||||
function useCollectionPreset() {
|
||||
const savePreset = debounce(collectionPresetsStore.savePreset, 450);
|
||||
const localPreset = ref({
|
||||
...collectionPresetsStore.getPresetForCollection(props.collection),
|
||||
});
|
||||
|
||||
watch(
|
||||
() => localPreset.value,
|
||||
(newPreset) => {
|
||||
savePreset(newPreset);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.collection,
|
||||
() => {
|
||||
localPreset.value = {
|
||||
...collectionPresetsStore.getPresetForCollection(props.collection),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const viewOptions = computed({
|
||||
get() {
|
||||
return localPreset.value.view_options?.[localPreset.value.view_type] || null;
|
||||
},
|
||||
set(val) {
|
||||
localPreset.value = {
|
||||
...localPreset.value,
|
||||
view_options: {
|
||||
...localPreset.value.view_options,
|
||||
[localPreset.value.view_type]: val,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const viewQuery = computed({
|
||||
get() {
|
||||
return localPreset.value.view_query?.[localPreset.value.view_type] || null;
|
||||
},
|
||||
set(val) {
|
||||
localPreset.value = {
|
||||
...localPreset.value,
|
||||
view_query: {
|
||||
...localPreset.value.view_query,
|
||||
[localPreset.value.view_type]: val,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
return { viewOptions, viewQuery };
|
||||
}
|
||||
|
||||
function useBreadcrumb() {
|
||||
const breadcrumb = computed(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import CollectionsModule from './collections/';
|
||||
import UsersModule from './users/';
|
||||
import SettingsModule from './settings/';
|
||||
export const modules = [CollectionsModule, SettingsModule];
|
||||
|
||||
export const modules = [CollectionsModule, UsersModule, SettingsModule];
|
||||
export default modules;
|
||||
|
||||
4
src/modules/users/components/navigation/index.ts
Normal file
4
src/modules/users/components/navigation/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import UsersNavigation from './navigation.vue';
|
||||
|
||||
export { UsersNavigation };
|
||||
export default UsersNavigation;
|
||||
3
src/modules/users/components/navigation/navigation.vue
Normal file
3
src/modules/users/components/navigation/navigation.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<div>users nav</div>
|
||||
</template>
|
||||
23
src/modules/users/index.ts
Normal file
23
src/modules/users/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { defineModule } from '@/modules/define';
|
||||
import UsersBrowse from './routes/browse/';
|
||||
import UsersDetail from './routes/detail/';
|
||||
|
||||
export default defineModule(({ i18n }) => ({
|
||||
id: 'users',
|
||||
name: i18n.tc('user', 2),
|
||||
icon: 'people',
|
||||
routes: [
|
||||
{
|
||||
name: 'users-browse',
|
||||
path: '/',
|
||||
component: UsersBrowse,
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
name: 'users-detail',
|
||||
path: '/:primaryKey',
|
||||
component: UsersDetail,
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
}));
|
||||
178
src/modules/users/routes/browse/browse.vue
Normal file
178
src/modules/users/routes/browse/browse.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<private-view :title="$t('users')">
|
||||
<template #title-outer:prepend>
|
||||
<v-button rounded disabled icon secondary>
|
||||
<v-icon name="people" />
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<template #drawer><portal-target name="drawer" /></template>
|
||||
|
||||
<template #actions>
|
||||
<v-dialog v-model="confirmDelete">
|
||||
<template #activator="{ on }">
|
||||
<v-button
|
||||
rounded
|
||||
icon
|
||||
class="action-delete"
|
||||
v-if="selection.length > 0"
|
||||
@click="on"
|
||||
>
|
||||
<v-icon name="delete" />
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<v-card>
|
||||
<v-card-title>{{ $tc('batch_delete_confirm', selection.length) }}</v-card-title>
|
||||
|
||||
<v-card-actions>
|
||||
<v-button @click="confirmDelete = false" secondary>
|
||||
{{ $t('cancel') }}
|
||||
</v-button>
|
||||
<v-button @click="batchDelete" class="action-delete" :loading="deleting">
|
||||
{{ $t('delete') }}
|
||||
</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-button rounded icon class="action-batch" v-if="selection.length > 1" :to="batchLink">
|
||||
<v-icon name="edit" />
|
||||
</v-button>
|
||||
|
||||
<v-button rounded icon :to="addNewLink">
|
||||
<v-icon name="add" />
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<template #navigation>
|
||||
<users-navigation />
|
||||
</template>
|
||||
|
||||
<layout-tabular
|
||||
class="layout"
|
||||
ref="layout"
|
||||
collection="directus_users"
|
||||
:selection.sync="selection"
|
||||
:view-options.sync="viewOptions"
|
||||
:view-query.sync="viewQuery"
|
||||
:detail-route="'/{{project}}/users/{{primaryKey}}'"
|
||||
/>
|
||||
</private-view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from '@vue/composition-api';
|
||||
import UsersNavigation from '../../components/navigation/';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import { i18n } from '@/lang';
|
||||
import api from '@/api';
|
||||
import { LayoutComponent } from '@/layouts/types';
|
||||
import useCollectionPreset from '@/compositions/use-collection-preset';
|
||||
|
||||
type Item = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[field: string]: any;
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'users-browse',
|
||||
components: { UsersNavigation },
|
||||
props: {},
|
||||
setup() {
|
||||
const layout = ref<LayoutComponent>(null);
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const selection = ref<Item[]>([]);
|
||||
|
||||
const { viewOptions, viewQuery } = useCollectionPreset(ref('directus_users'));
|
||||
const { addNewLink, batchLink } = useLinks();
|
||||
const { confirmDelete, deleting, batchDelete } = useBatchDelete();
|
||||
const { breadcrumb } = useBreadcrumb();
|
||||
|
||||
return {
|
||||
addNewLink,
|
||||
batchLink,
|
||||
selection,
|
||||
breadcrumb,
|
||||
confirmDelete,
|
||||
batchDelete,
|
||||
deleting,
|
||||
layout,
|
||||
viewOptions,
|
||||
viewQuery,
|
||||
};
|
||||
|
||||
function useBatchDelete() {
|
||||
const confirmDelete = ref(false);
|
||||
const deleting = ref(false);
|
||||
|
||||
return { confirmDelete, deleting, batchDelete };
|
||||
|
||||
async function batchDelete() {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
deleting.value = true;
|
||||
|
||||
confirmDelete.value = false;
|
||||
|
||||
const batchPrimaryKeys = selection.value.map((item) => item.id).join();
|
||||
|
||||
await api.delete(`/${currentProjectKey}/users/${batchPrimaryKeys}`);
|
||||
|
||||
await layout.value?.refresh();
|
||||
|
||||
selection.value = [];
|
||||
deleting.value = false;
|
||||
confirmDelete.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function useLinks() {
|
||||
const addNewLink = computed<string>(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
return `/${currentProjectKey}/users/+`;
|
||||
});
|
||||
|
||||
const batchLink = computed<string>(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
const batchPrimaryKeys = selection.value.map((item) => item.id).join();
|
||||
return `/${currentProjectKey}/users/${batchPrimaryKeys}`;
|
||||
});
|
||||
|
||||
return { addNewLink, batchLink };
|
||||
}
|
||||
|
||||
function useBreadcrumb() {
|
||||
const breadcrumb = computed(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
return [
|
||||
{
|
||||
name: i18n.tc('collection', 2),
|
||||
to: `/${currentProjectKey}/collections`,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
return { breadcrumb };
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.action-delete {
|
||||
--v-button-background-color: var(--danger);
|
||||
--v-button-background-color-hover: var(--danger-dark);
|
||||
}
|
||||
|
||||
.action-batch {
|
||||
--v-button-background-color: var(--warning);
|
||||
--v-button-background-color-hover: var(--warning-150);
|
||||
}
|
||||
|
||||
.layout {
|
||||
--layout-offset-top: 64px;
|
||||
}
|
||||
</style>
|
||||
4
src/modules/users/routes/browse/index.ts
Normal file
4
src/modules/users/routes/browse/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import UsersBrowse from './browse.vue';
|
||||
|
||||
export { UsersBrowse };
|
||||
export default UsersBrowse;
|
||||
197
src/modules/users/routes/detail/detail.vue
Normal file
197
src/modules/users/routes/detail/detail.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<private-view :title="$t('editing', { collection: $t('users') })">
|
||||
<template #title-outer:prepend>
|
||||
<v-button rounded icon secondary exact :to="breadcrumb[0].to">
|
||||
<v-icon name="arrow_back" />
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<template #headline>
|
||||
<v-breadcrumb :items="breadcrumb" />
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<v-dialog v-model="confirmDelete">
|
||||
<template #activator="{ on }">
|
||||
<v-button
|
||||
rounded
|
||||
icon
|
||||
class="action-delete"
|
||||
:disabled="item === null"
|
||||
@click="on"
|
||||
>
|
||||
<v-icon name="delete" />
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('delete_are_you_sure') }}</v-card-title>
|
||||
|
||||
<v-card-actions>
|
||||
<v-button @click="confirmDelete = false" secondary>
|
||||
{{ $t('cancel') }}
|
||||
</v-button>
|
||||
<v-button @click="deleteAndQuit" class="action-delete" :loading="deleting">
|
||||
{{ $t('delete') }}
|
||||
</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-button
|
||||
rounded
|
||||
icon
|
||||
:loading="saving"
|
||||
:disabled="hasEdits === false"
|
||||
@click="saveAndQuit"
|
||||
>
|
||||
<v-icon name="check" />
|
||||
|
||||
<template #append-outer>
|
||||
<save-options
|
||||
:disabled="hasEdits === false"
|
||||
@save-and-stay="saveAndStay"
|
||||
@save-and-add-new="saveAndAddNew"
|
||||
@save-as-copy="saveAsCopyAndNavigate"
|
||||
/>
|
||||
</template>
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<template #navigation>
|
||||
<users-navigation />
|
||||
</template>
|
||||
|
||||
<v-form
|
||||
:loading="loading"
|
||||
:initial-values="item"
|
||||
collection="directus_users"
|
||||
:batch-mode="isBatch"
|
||||
v-model="edits"
|
||||
/>
|
||||
|
||||
<template #drawer>
|
||||
<activity-drawer-detail
|
||||
v-if="isNew === false"
|
||||
collection="directus_users"
|
||||
:primary-key="primaryKey"
|
||||
/>
|
||||
</template>
|
||||
</private-view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, toRefs, ref } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import UsersNavigation from '../../components/navigation/';
|
||||
import { i18n } from '@/lang';
|
||||
import router from '@/router';
|
||||
import ActivityDrawerDetail from '@/views/private/components/activity-drawer-detail';
|
||||
import useItem from '@/compositions/use-item';
|
||||
import SaveOptions from '@/views/private/components/save-options';
|
||||
|
||||
type Values = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[field: string]: any;
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: 'collections-detail',
|
||||
components: { UsersNavigation, ActivityDrawerDetail, SaveOptions },
|
||||
props: {
|
||||
primaryKey: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { currentProjectKey } = toRefs(projectsStore.state);
|
||||
const { primaryKey } = toRefs(props);
|
||||
const { breadcrumb } = useBreadcrumb();
|
||||
|
||||
const {
|
||||
isNew,
|
||||
edits,
|
||||
item,
|
||||
saving,
|
||||
loading,
|
||||
error,
|
||||
save,
|
||||
remove,
|
||||
deleting,
|
||||
saveAsCopy,
|
||||
isBatch,
|
||||
} = useItem(ref('directus_users'), primaryKey);
|
||||
|
||||
const hasEdits = computed<boolean>(() => Object.keys(edits.value).length > 0);
|
||||
|
||||
const confirmDelete = ref(false);
|
||||
|
||||
return {
|
||||
item,
|
||||
loading,
|
||||
error,
|
||||
isNew,
|
||||
breadcrumb,
|
||||
edits,
|
||||
hasEdits,
|
||||
saving,
|
||||
saveAndQuit,
|
||||
deleteAndQuit,
|
||||
confirmDelete,
|
||||
deleting,
|
||||
saveAndStay,
|
||||
saveAndAddNew,
|
||||
saveAsCopyAndNavigate,
|
||||
isBatch,
|
||||
};
|
||||
|
||||
function useBreadcrumb() {
|
||||
const breadcrumb = computed(() => [
|
||||
{
|
||||
name: i18n.t('users'),
|
||||
to: `/${currentProjectKey.value}/users/`,
|
||||
},
|
||||
]);
|
||||
|
||||
return { breadcrumb };
|
||||
}
|
||||
|
||||
async function saveAndQuit() {
|
||||
await save();
|
||||
router.push(`/${currentProjectKey.value}/users`);
|
||||
}
|
||||
|
||||
async function saveAndStay() {
|
||||
await save();
|
||||
}
|
||||
|
||||
async function saveAndAddNew() {
|
||||
await save();
|
||||
router.push(`/${currentProjectKey.value}/users/+`);
|
||||
}
|
||||
|
||||
async function saveAsCopyAndNavigate() {
|
||||
const newPrimaryKey = await saveAsCopy();
|
||||
router.push(`/${currentProjectKey.value}/users/${newPrimaryKey}`);
|
||||
}
|
||||
|
||||
async function deleteAndQuit() {
|
||||
await remove();
|
||||
router.push(`/${currentProjectKey.value}/users`);
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.action-delete {
|
||||
--v-button-background-color: var(--danger);
|
||||
--v-button-background-color-hover: var(--danger-dark);
|
||||
}
|
||||
|
||||
.v-form {
|
||||
padding: var(--content-padding);
|
||||
}
|
||||
</style>
|
||||
4
src/modules/users/routes/detail/index.ts
Normal file
4
src/modules/users/routes/detail/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import UsersDetail from './detail.vue';
|
||||
|
||||
export { UsersDetail };
|
||||
export default UsersDetail;
|
||||
@@ -129,6 +129,7 @@ export default defineComponent({
|
||||
params: {
|
||||
'filter[collection][eq]': collection,
|
||||
'filter[item][eq]': primaryKey,
|
||||
'filter[action][in]': 'comment,create,update,delete',
|
||||
sort: '-id', // directus_activity has auto increment and is therefore in chronological order
|
||||
fields: [
|
||||
'id',
|
||||
|
||||
@@ -84,10 +84,10 @@ export default defineComponent({
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transition: transform var(--fast) var(--transition);
|
||||
|
||||
&.show {
|
||||
transform: translateY(-100%);
|
||||
transition: transform var(--fast) var(--transition);
|
||||
}
|
||||
|
||||
.v-icon {
|
||||
|
||||
@@ -9715,6 +9715,11 @@ micromatch@^4.0.0, micromatch@^4.0.2:
|
||||
braces "^3.0.1"
|
||||
picomatch "^2.0.5"
|
||||
|
||||
micromustache@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/micromustache/-/micromustache-7.1.0.tgz#53eebe9a4fe0ac9b9990535e090f797e2934d5e1"
|
||||
integrity sha512-DXUYQI8qPsfOx3AkiGzyOx0cn7NgCqFYsV0Asa/ZQUna2Er4mwpAdA9iANA92WYvUowHf+jBsVvIZxiRe1z1Ig==
|
||||
|
||||
miller-rabin@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
|
||||
|
||||
Reference in New Issue
Block a user