mirror of
https://github.com/directus/directus.git
synced 2026-02-05 16:04:55 -05:00
Merge branch 'main' into room-cleaning
This commit is contained in:
165
app/src/modules/collections/components/navigation-bookmark.vue
Normal file
165
app/src/modules/collections/components/navigation-bookmark.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<v-list-item exact :to="bookmark.to" class="bookmark" @contextmenu.native.prevent.stop="$refs.contextMenu.activate">
|
||||
<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-icon v-if="bookmark.scope !== 'user'" class="bookmark-scope">
|
||||
<v-icon :name="bookmark.scope === 'role' ? 'people' : 'public'" />
|
||||
</v-list-item-icon>
|
||||
|
||||
<v-menu ref="contextMenu" show-arrow placement="bottom-start">
|
||||
<v-list dense>
|
||||
<v-list-item @click="renameActive = true" :disabled="isMine === false">
|
||||
<v-list-item-icon>
|
||||
<v-icon name="edit" outline />
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
{{ $t('rename_bookmark') }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item @click="deleteActive = true" class="danger" :disabled="isMine === false">
|
||||
<v-list-item-icon>
|
||||
<v-icon name="delete" outline />
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
{{ $t('delete_bookmark') }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<v-dialog v-model="renameActive" persistent>
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('rename_bookmark') }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-input v-model="renameValue" autofocus @keyup.enter="renameSave" />
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-button secondary @click="renameActive = false">{{ $t('cancel') }}</v-button>
|
||||
<v-button @click="renameSave" :loading="renameSaving">{{ $t('save') }}</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="deleteActive" persistent>
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('delete_bookmark_copy', { bookmark: bookmark.title }) }}</v-card-title>
|
||||
<v-card-actions>
|
||||
<v-button secondary @click="deleteActive = false">{{ $t('cancel') }}</v-button>
|
||||
<v-button @click="deleteSave" :loading="deleteSaving" class="action-delete">
|
||||
{{ $t('delete') }}
|
||||
</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, computed } from '@vue/composition-api';
|
||||
import { Preset } from '@/types';
|
||||
import { useUserStore, usePresetsStore } from '@/stores';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
bookmark: {
|
||||
type: Object as PropType<Preset>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const contextMenu = ref();
|
||||
const userStore = useUserStore();
|
||||
const presetsStore = usePresetsStore();
|
||||
|
||||
const isMine = computed(() => props.bookmark.user === userStore.state.currentUser!.id);
|
||||
|
||||
const { renameActive, renameValue, renameSave, renameSaving } = useRenameBookmark();
|
||||
const { deleteActive, deleteValue, deleteSave, deleteSaving } = useDeleteBookmark();
|
||||
|
||||
return {
|
||||
contextMenu,
|
||||
isMine,
|
||||
renameActive,
|
||||
renameValue,
|
||||
renameSave,
|
||||
renameSaving,
|
||||
deleteActive,
|
||||
deleteValue,
|
||||
deleteSave,
|
||||
deleteSaving,
|
||||
};
|
||||
|
||||
function useRenameBookmark() {
|
||||
const renameActive = ref(false);
|
||||
const renameValue = ref(props.bookmark.title);
|
||||
const renameSaving = ref(false);
|
||||
|
||||
return { renameActive, renameValue, renameSave, renameSaving };
|
||||
|
||||
async function renameSave() {
|
||||
renameSaving.value = true;
|
||||
|
||||
try {
|
||||
await presetsStore.savePreset({
|
||||
...props.bookmark,
|
||||
title: renameValue.value,
|
||||
});
|
||||
|
||||
renameActive.value = false;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
renameSaving.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useDeleteBookmark() {
|
||||
const deleteActive = ref(false);
|
||||
const deleteValue = ref(props.bookmark.title);
|
||||
const deleteSaving = ref(false);
|
||||
|
||||
return { deleteActive, deleteValue, deleteSave, deleteSaving };
|
||||
|
||||
async function deleteSave() {
|
||||
deleteSaving.value = true;
|
||||
|
||||
try {
|
||||
await presetsStore.savePreset(props.bookmark.id);
|
||||
|
||||
deleteActive.value = false;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
deleteSaving.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bookmark-scope {
|
||||
--v-icon-color: var(--foreground-subdued);
|
||||
|
||||
opacity: 0;
|
||||
transition: opacity var(--fast) var(--transition);
|
||||
}
|
||||
|
||||
.bookmark:hover .bookmark-scope {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.danger {
|
||||
--v-list-item-color: var(--danger);
|
||||
--v-list-item-icon-color: var(--danger);
|
||||
}
|
||||
|
||||
.action-delete {
|
||||
--v-button-background-color: var(--danger-25);
|
||||
--v-button-color: var(--danger);
|
||||
--v-button-background-color-hover: var(--danger-50);
|
||||
--v-button-color-hover: var(--danger);
|
||||
}
|
||||
</style>
|
||||
@@ -19,10 +19,7 @@
|
||||
<template v-if="bookmarks.length > 0">
|
||||
<v-divider />
|
||||
|
||||
<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>
|
||||
<navigation-bookmark v-for="bookmark of bookmarks" :key="bookmark.id" :bookmark="bookmark" />
|
||||
</template>
|
||||
|
||||
<div v-if="!customNavItems && !navItems.length && !bookmarks.length" class="empty">
|
||||
@@ -33,17 +30,18 @@
|
||||
{{ $t('no_collections_copy') }}
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</v-list>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from '@vue/composition-api';
|
||||
import useNavigation from '../composables/use-navigation';
|
||||
import { usePresetsStore } from '@/stores/';
|
||||
import { useUserStore } from '@/stores';
|
||||
import { usePresetsStore, useUserStore } from '@/stores/';
|
||||
import { orderBy } from 'lodash';
|
||||
import NavigationBookmark from './navigation-bookmark.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { NavigationBookmark },
|
||||
props: {
|
||||
exact: {
|
||||
type: Boolean,
|
||||
@@ -57,16 +55,25 @@ export default defineComponent({
|
||||
const isAdmin = computed(() => userStore.state.currentUser?.role.admin === true);
|
||||
|
||||
const bookmarks = computed(() => {
|
||||
return presetsStore.state.collectionPresets
|
||||
.filter((preset) => {
|
||||
return preset.title !== null && preset.collection.startsWith('directus_') === false;
|
||||
})
|
||||
.map((preset) => {
|
||||
return {
|
||||
...preset,
|
||||
to: `/collections/${preset.collection}?bookmark=${preset.id}`,
|
||||
};
|
||||
});
|
||||
return orderBy(
|
||||
presetsStore.state.collectionPresets
|
||||
.filter((preset) => {
|
||||
return preset.title !== null && preset.collection.startsWith('directus_') === false;
|
||||
})
|
||||
.map((preset) => {
|
||||
let scope = 'global';
|
||||
if (!!preset.role) scope = 'role';
|
||||
if (!!preset.user) scope = 'user';
|
||||
|
||||
return {
|
||||
...preset,
|
||||
to: `/collections/${preset.collection}?bookmark=${preset.id}`,
|
||||
scope,
|
||||
};
|
||||
}),
|
||||
['title'],
|
||||
['asc']
|
||||
);
|
||||
});
|
||||
|
||||
return { navItems, bookmarks, customNavItems, isAdmin };
|
||||
|
||||
@@ -13,30 +13,45 @@
|
||||
</template>
|
||||
|
||||
<template #title-outer:append>
|
||||
<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>
|
||||
</bookmark-add>
|
||||
<div class="bookmark-controls">
|
||||
<bookmark-add
|
||||
v-if="!bookmark"
|
||||
class="add"
|
||||
v-model="bookmarkDialogActive"
|
||||
@save="createBookmark"
|
||||
:saving="creatingBookmark"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-icon class="toggle" name="bookmark_outline" @click="on" />
|
||||
</template>
|
||||
</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" />
|
||||
<v-icon class="saved" name="bookmark" v-else-if="bookmarkSaved" />
|
||||
|
||||
<template v-else-if="bookmarkIsMine">
|
||||
<v-icon class="save" @click="savePreset()" name="bookmark_save" v-tooltip.bottom="$t('update_bookmark')" />
|
||||
</template>
|
||||
</bookmark-edit>
|
||||
|
||||
<bookmark-add
|
||||
v-else
|
||||
class="add"
|
||||
v-model="bookmarkDialogActive"
|
||||
@save="createBookmark"
|
||||
:saving="creatingBookmark"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-icon class="toggle" name="bookmark_outline" @click="on" />
|
||||
</template>
|
||||
</bookmark-add>
|
||||
|
||||
<v-icon
|
||||
v-if="bookmark && !bookmarkSaving && bookmarkSaved === false"
|
||||
name="settings_backup_restore"
|
||||
@click="clearLocalSave"
|
||||
class="clear"
|
||||
v-tooltip.bottom="$t('reset_bookmark')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #actions:prepend>
|
||||
@@ -277,6 +292,10 @@ export default defineComponent({
|
||||
saveCurrentAsBookmark,
|
||||
title: bookmarkName,
|
||||
resetPreset,
|
||||
bookmarkSaved,
|
||||
bookmarkIsMine,
|
||||
busy: bookmarkSaving,
|
||||
clearLocalSave,
|
||||
} = usePreset(collection, bookmarkID);
|
||||
|
||||
const {
|
||||
@@ -345,6 +364,10 @@ export default defineComponent({
|
||||
deleteError,
|
||||
createAllowed,
|
||||
resetPreset,
|
||||
bookmarkSaved,
|
||||
bookmarkIsMine,
|
||||
bookmarkSaving,
|
||||
clearLocalSave,
|
||||
};
|
||||
|
||||
function useBreadcrumb() {
|
||||
@@ -559,25 +582,50 @@ export default defineComponent({
|
||||
--layout-offset-top: 64px;
|
||||
}
|
||||
|
||||
.bookmark-add .toggle,
|
||||
.bookmark-edit .toggle {
|
||||
margin-left: 8px;
|
||||
transition: color var(--fast) var(--transition);
|
||||
}
|
||||
|
||||
.bookmark-add {
|
||||
color: var(--foreground-subdued);
|
||||
|
||||
&:hover {
|
||||
color: var(--foreground-normal);
|
||||
}
|
||||
}
|
||||
|
||||
.bookmark-edit {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
--v-button-color-disabled: var(--foreground-normal);
|
||||
}
|
||||
|
||||
.bookmark-controls {
|
||||
.add,
|
||||
.save,
|
||||
.saved,
|
||||
.clear {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.add,
|
||||
.save,
|
||||
.clear {
|
||||
cursor: pointer;
|
||||
color: var(--foreground-subdued);
|
||||
transition: color var(--fast) var(--transition);
|
||||
|
||||
&:hover {
|
||||
color: var(--foreground-normal);
|
||||
}
|
||||
}
|
||||
|
||||
.save {
|
||||
color: var(--warning);
|
||||
|
||||
&:hover {
|
||||
color: var(--warning-125);
|
||||
}
|
||||
}
|
||||
|
||||
.saved {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.clear {
|
||||
color: var(--foreground-subdued);
|
||||
margin-left: 4px;
|
||||
|
||||
&:hover {
|
||||
color: var(--warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('rename_folder') }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-input v-model="renameValue" />
|
||||
<v-input v-model="renameValue" autofocus @keyup.enter="renameSave" />
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-button secondary @click="renameActive = false">{{ $t('cancel') }}</v-button>
|
||||
|
||||
@@ -98,6 +98,40 @@
|
||||
<v-checkbox v-model="fieldData.schema.is_nullable" :label="$t('allow_null_label')" block />
|
||||
</div>
|
||||
|
||||
<div class="field full">
|
||||
<div class="label type-label">{{ $t('translation') }}</div>
|
||||
<interface-repeater
|
||||
v-model="fieldData.meta.translation"
|
||||
:template="'{{ translation }} ({{ locale }})'"
|
||||
:fields="[
|
||||
{
|
||||
field: 'locale',
|
||||
type: 'string',
|
||||
name: $t('language'),
|
||||
meta: {
|
||||
interface: 'system-language',
|
||||
width: 'half',
|
||||
},
|
||||
schema: {
|
||||
default_value: 'en-US'
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'translation',
|
||||
type: 'string',
|
||||
name: $t('translation'),
|
||||
meta: {
|
||||
interface: 'text-input',
|
||||
width: 'half',
|
||||
options: {
|
||||
placeholder: 'Enter a translation...'
|
||||
},
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
@todo add unique when the API supports it
|
||||
|
||||
|
||||
@@ -143,30 +143,30 @@ export default defineComponent({
|
||||
label: 'sort',
|
||||
icon: 'low_priority',
|
||||
},
|
||||
userCreated: {
|
||||
enabled: false,
|
||||
name: 'user_created',
|
||||
label: 'created_by',
|
||||
icon: 'account_circle',
|
||||
},
|
||||
userUpdated: {
|
||||
enabled: false,
|
||||
name: 'user_updated',
|
||||
label: 'updated_by',
|
||||
icon: 'account_circle',
|
||||
},
|
||||
dateCreated: {
|
||||
enabled: false,
|
||||
name: 'date_created',
|
||||
label: 'created_on',
|
||||
icon: 'access_time',
|
||||
},
|
||||
userCreated: {
|
||||
enabled: false,
|
||||
name: 'user_created',
|
||||
label: 'created_by',
|
||||
icon: 'account_circle',
|
||||
},
|
||||
dateUpdated: {
|
||||
enabled: false,
|
||||
name: 'date_updated',
|
||||
label: 'updated_on',
|
||||
icon: 'access_time',
|
||||
},
|
||||
userUpdated: {
|
||||
enabled: false,
|
||||
name: 'user_updated',
|
||||
label: 'updated_by',
|
||||
icon: 'account_circle',
|
||||
},
|
||||
});
|
||||
|
||||
const saving = ref(false);
|
||||
@@ -332,7 +332,7 @@ export default defineComponent({
|
||||
},
|
||||
readonly: true,
|
||||
hidden: true,
|
||||
width: 'full',
|
||||
width: 'half',
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
@@ -347,7 +347,7 @@ export default defineComponent({
|
||||
interface: 'datetime',
|
||||
readonly: true,
|
||||
hidden: true,
|
||||
width: 'full',
|
||||
width: 'half',
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
@@ -366,7 +366,7 @@ export default defineComponent({
|
||||
},
|
||||
readonly: true,
|
||||
hidden: true,
|
||||
width: 'full',
|
||||
width: 'half',
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
@@ -381,7 +381,7 @@ export default defineComponent({
|
||||
interface: 'datetime',
|
||||
readonly: true,
|
||||
hidden: true,
|
||||
width: 'full',
|
||||
width: 'half',
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
|
||||
@@ -169,7 +169,7 @@ import CommentsDrawerDetail from '@/views/private/components/comments-drawer-det
|
||||
import useItem from '@/composables/use-item';
|
||||
import SaveOptions from '@/views/private/components/save-options';
|
||||
import api from '@/api';
|
||||
import { useFieldsStore } from '@/stores/';
|
||||
import { useFieldsStore, useUserStore } from '@/stores/';
|
||||
import useFormFields from '@/composables/use-form-fields';
|
||||
import { Field } from '@/types';
|
||||
import UserInfoDrawerDetail from '../components/user-info-drawer-detail.vue';
|
||||
@@ -209,6 +209,7 @@ export default defineComponent({
|
||||
},
|
||||
setup(props) {
|
||||
const fieldsStore = useFieldsStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const { primaryKey } = toRefs(props);
|
||||
const { breadcrumb } = useBreadcrumb();
|
||||
@@ -344,6 +345,7 @@ export default defineComponent({
|
||||
|
||||
async function saveAndQuit() {
|
||||
await save();
|
||||
await refreshCurrentUser();
|
||||
router.push(`/users`);
|
||||
}
|
||||
|
||||
@@ -361,6 +363,7 @@ export default defineComponent({
|
||||
|
||||
async function saveAndAddNew() {
|
||||
await save();
|
||||
await refreshCurrentUser();
|
||||
router.push(`/users/+`);
|
||||
}
|
||||
|
||||
@@ -374,6 +377,12 @@ export default defineComponent({
|
||||
router.push(`/users`);
|
||||
}
|
||||
|
||||
async function refreshCurrentUser() {
|
||||
if (userStore.state.currentUser!.id === item.value.id) {
|
||||
await userStore.hydrate();
|
||||
}
|
||||
}
|
||||
|
||||
function useUserPreview() {
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
Reference in New Issue
Block a user