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:
Azri Kahar
2022-04-14 05:56:29 +08:00
committed by GitHub
parent 360d5b64f5
commit 0dfbc65bad
6 changed files with 108 additions and 66 deletions

View File

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

View File

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

View File

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

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

View File

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

View File

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