mirror of
https://github.com/directus/directus.git
synced 2026-01-27 07:08:17 -05:00
Add copy button to role ID in role permissions page's sidebar detail (#12706)
* refactor to useClipboard composable * add copy button to role permissions page
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<div class="v-error selectable">
|
||||
<output>[{{ code }}] {{ message }}</output>
|
||||
<v-icon
|
||||
v-if="showCopy"
|
||||
v-if="isCopySupported"
|
||||
v-tooltip="t('copy_details')"
|
||||
small
|
||||
class="copy-error"
|
||||
@@ -17,6 +17,7 @@
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed, PropType, ref } from 'vue';
|
||||
import { isPlainObject } from 'lodash';
|
||||
import useClipboard from '@/composables/use-clipboard';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -44,15 +45,16 @@ export default defineComponent({
|
||||
|
||||
const copied = ref(false);
|
||||
|
||||
const showCopy = computed(() => !!navigator.clipboard?.writeText);
|
||||
const { isCopySupported, copyToClipboard } = useClipboard();
|
||||
|
||||
return { t, code, copyError, showCopy, copied, message };
|
||||
return { t, code, copyError, isCopySupported, copied, message };
|
||||
|
||||
async function copyError() {
|
||||
const error = props.error?.response?.data || props.error;
|
||||
await navigator.clipboard.writeText(
|
||||
const isCopied = await copyToClipboard(
|
||||
JSON.stringify(error, isPlainObject(error) ? null : Object.getOwnPropertyNames(error), 2)
|
||||
);
|
||||
if (!isCopied) return;
|
||||
copied.value = true;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, PropType, computed } from 'vue';
|
||||
import { Field } from '@directus/shared/types';
|
||||
import useClipboard from '@/composables/use-clipboard';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -75,6 +76,8 @@ export default defineComponent({
|
||||
setup(props) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const { isCopySupported, isPasteSupported } = useClipboard();
|
||||
|
||||
const defaultValue = computed(() => {
|
||||
const savedValue = props.field?.schema?.default_value;
|
||||
return savedValue !== undefined ? savedValue : null;
|
||||
@@ -84,14 +87,6 @@ export default defineComponent({
|
||||
return props.field?.schema?.is_nullable === false;
|
||||
});
|
||||
|
||||
const isCopySupported = computed(() => {
|
||||
return !!navigator?.clipboard?.writeText;
|
||||
});
|
||||
|
||||
const isPasteSupported = computed(() => {
|
||||
return !!navigator?.clipboard?.readText;
|
||||
});
|
||||
|
||||
return { t, defaultValue, isRequired, isCopySupported, isPasteSupported };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -69,7 +69,6 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getJSType } from '@/utils/get-js-type';
|
||||
import { notify } from '@/utils/notify';
|
||||
import { Field, ValidationError } from '@directus/shared/types';
|
||||
import { isEqual } from 'lodash';
|
||||
import { computed, ref } from 'vue';
|
||||
@@ -78,6 +77,7 @@ import FormFieldInterface from './form-field-interface.vue';
|
||||
import FormFieldLabel from './form-field-label.vue';
|
||||
import FormFieldMenu from './form-field-menu.vue';
|
||||
import { formatFieldFunction } from '@/utils/format-field-function';
|
||||
import useClipboard from '@/composables/use-clipboard';
|
||||
|
||||
interface Props {
|
||||
field: Field;
|
||||
@@ -170,6 +170,8 @@ function emitValue(value: any) {
|
||||
function useRaw() {
|
||||
const showRaw = ref(false);
|
||||
|
||||
const { copyToClipboard, pasteFromClipboard } = useClipboard();
|
||||
|
||||
const type = computed(() => {
|
||||
return getJSType(props.field);
|
||||
});
|
||||
@@ -208,32 +210,13 @@ function useRaw() {
|
||||
});
|
||||
|
||||
async function copyRaw() {
|
||||
try {
|
||||
await navigator?.clipboard?.writeText(rawValue.value);
|
||||
notify({
|
||||
title: t('copy_raw_value_success'),
|
||||
});
|
||||
} catch (err: any) {
|
||||
notify({
|
||||
type: 'error',
|
||||
title: t('copy_raw_value_fail'),
|
||||
});
|
||||
}
|
||||
await copyToClipboard(rawValue.value);
|
||||
}
|
||||
|
||||
async function pasteRaw() {
|
||||
try {
|
||||
const pasteValue = await navigator?.clipboard?.readText();
|
||||
rawValue.value = pasteValue;
|
||||
notify({
|
||||
title: t('paste_raw_value_success'),
|
||||
});
|
||||
} catch (err: any) {
|
||||
notify({
|
||||
type: 'error',
|
||||
title: t('paste_raw_value_fail'),
|
||||
});
|
||||
}
|
||||
const pastedValue = await pasteFromClipboard();
|
||||
if (!pastedValue) return;
|
||||
rawValue.value = pastedValue;
|
||||
}
|
||||
|
||||
return { showRaw, rawValue, copyRaw, pasteRaw };
|
||||
|
||||
54
app/src/composables/use-clipboard.ts
Normal file
54
app/src/composables/use-clipboard.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { notify } from '@/utils/notify';
|
||||
|
||||
type Message = {
|
||||
success?: string;
|
||||
fail?: string;
|
||||
};
|
||||
|
||||
export default function useClipboard() {
|
||||
const { t } = useI18n();
|
||||
|
||||
const isCopySupported = computed(() => {
|
||||
return !!navigator?.clipboard?.writeText;
|
||||
});
|
||||
|
||||
const isPasteSupported = computed(() => {
|
||||
return !!navigator?.clipboard?.readText;
|
||||
});
|
||||
|
||||
return { isCopySupported, isPasteSupported, copyToClipboard, pasteFromClipboard };
|
||||
|
||||
async function copyToClipboard(value: string, message?: Message): Promise<boolean> {
|
||||
try {
|
||||
await navigator?.clipboard?.writeText(value);
|
||||
notify({
|
||||
title: message?.success ?? t('copy_raw_value_success'),
|
||||
});
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
notify({
|
||||
type: 'error',
|
||||
title: message?.fail ?? t('copy_raw_value_fail'),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function pasteFromClipboard(message?: Message): Promise<string | null> {
|
||||
try {
|
||||
const pasteValue = await navigator?.clipboard?.readText();
|
||||
notify({
|
||||
title: message?.success ?? t('paste_raw_value_success'),
|
||||
});
|
||||
return pasteValue;
|
||||
} catch (err: any) {
|
||||
notify({
|
||||
type: 'error',
|
||||
title: message?.fail ?? t('paste_raw_value_fail'),
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,17 @@
|
||||
<sidebar-detail icon="info_outline" :title="t('information')" close>
|
||||
<template v-if="!isNew && role">
|
||||
<dl>
|
||||
<div>
|
||||
<div class="description-list">
|
||||
<dt>{{ t('primary_key') }}</dt>
|
||||
<dd>{{ role.id }}</dd>
|
||||
<v-icon
|
||||
v-if="isCopySupported"
|
||||
name="copy"
|
||||
small
|
||||
clickable
|
||||
class="clipboard-icon"
|
||||
@click="copyToClipboard(role.id)"
|
||||
/>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
@@ -14,31 +22,39 @@
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import useClipboard from '@/composables/use-clipboard';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
isNew: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
role: {
|
||||
type: Object as PropType<any>,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { t } = useI18n();
|
||||
interface Props {
|
||||
isNew: boolean;
|
||||
role?: Record<string, any> | null;
|
||||
}
|
||||
|
||||
return { t };
|
||||
},
|
||||
withDefaults(defineProps<Props>(), {
|
||||
isNew: false,
|
||||
role: () => null,
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const { isCopySupported, copyToClipboard } = useClipboard();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-divider {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.description-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.clipboard-icon {
|
||||
--v-icon-color: var(--foreground-subdued);
|
||||
--v-icon-color-hover: var(--foreground-normal);
|
||||
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -80,8 +80,8 @@ import { defineComponent, ref, computed } from 'vue';
|
||||
import DrawerItem from '@/views/private/components/drawer-item';
|
||||
import { getRootPath } from '@/utils/get-root-path';
|
||||
import { unexpectedError } from '@/utils/unexpected-error';
|
||||
import { notify } from '@/utils/notify';
|
||||
import { Share } from '@directus/shared/types';
|
||||
import useClipboard from '@/composables/use-clipboard';
|
||||
|
||||
import api from '@/api';
|
||||
import ShareItem from './share-item.vue';
|
||||
@@ -105,6 +105,8 @@ export default defineComponent({
|
||||
setup(props) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const { copyToClipboard } = useClipboard();
|
||||
|
||||
const shares = ref<Share[] | null>(null);
|
||||
const count = ref(0);
|
||||
const error = ref(null);
|
||||
@@ -168,17 +170,7 @@ export default defineComponent({
|
||||
|
||||
async function copy(id: string) {
|
||||
const url = window.location.origin + getRootPath() + 'admin/shared/' + id;
|
||||
try {
|
||||
await navigator?.clipboard?.writeText(url);
|
||||
notify({
|
||||
title: t('share_copy_link_success'),
|
||||
});
|
||||
} catch (err: any) {
|
||||
notify({
|
||||
type: 'error',
|
||||
title: t('share_copy_link_error'),
|
||||
});
|
||||
}
|
||||
await copyToClipboard(url, { success: t('share_copy_link_success'), fail: t('share_copy_link_error') });
|
||||
}
|
||||
|
||||
function select(id: string) {
|
||||
|
||||
Reference in New Issue
Block a user