Browse Source

Merge pull request #5065 from nocodb/fix/3598-copy-to-clipboard

fix: if copy failed show error message and show content in dialog(optional)
pull/5071/head
Raju Udava 2 years ago committed by GitHub
parent
commit
7f4991f77c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      packages/nc-gui/assets/style.scss
  2. 14
      packages/nc-gui/components/account/Token.vue
  3. 15
      packages/nc-gui/components/account/UserList.vue
  4. 12
      packages/nc-gui/components/account/UsersModal.vue
  5. 12
      packages/nc-gui/components/smartsheet/ApiSnippet.vue
  6. 23
      packages/nc-gui/components/smartsheet/toolbar/ShareView.vue
  7. 10
      packages/nc-gui/components/smartsheet/toolbar/SharedViewList.vue
  8. 19
      packages/nc-gui/components/tabs/auth/ApiTokenManagement.vue
  9. 13
      packages/nc-gui/components/tabs/auth/UserManagement.vue
  10. 25
      packages/nc-gui/components/tabs/auth/user-management/ShareBase.vue
  11. 12
      packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue
  12. 16
      packages/nc-gui/components/template/Editor.vue
  13. 60
      packages/nc-gui/composables/useCopy.ts
  14. 29
      packages/nc-gui/pages/[projectType]/[projectId]/index.vue
  15. 10
      packages/nc-gui/pages/index/index/index.vue

7
packages/nc-gui/assets/style.scss

@ -311,3 +311,10 @@ a {
@apply block bg-red-500
}
}
.nc-copy-failed-modal .ant-modal-confirm-content{
@apply pt-4 overflow-auto;
white-space: pre;
user-select: auto;
}

14
packages/nc-gui/components/account/Token.vue

@ -81,14 +81,18 @@ const generateToken = async () => {
$e('a:api-token:generate')
}
const copyToken = (token: string | undefined) => {
const copyToken = async (token: string | undefined) => {
if (!token) return
copy(token)
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
try {
await copy(token)
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
$e('c:api-token:copy')
$e('c:api-token:copy')
} catch (e) {
message.error(e.message)
}
}
const descriptionInput = (el) => {

15
packages/nc-gui/components/account/UserList.vue

@ -99,13 +99,16 @@ const resendInvite = async (user: User) => {
$e('a:org-user:resend-invite')
}
const copyInviteUrl = (user: User) => {
const copyInviteUrl = async (user: User) => {
if (!user.invite_token) return
try {
await copy(`${dashboardUrl}#/signup/${user.invite_token}`)
copy(`${dashboardUrl}#/signup/${user.invite_token}`)
// Invite URL copied to clipboard
message.success(t('msg.success.inviteURLCopied'))
// Invite URL copied to clipboard
message.success(t('msg.success.inviteURLCopied'))
} catch (e) {
message.error(e.message)
}
$e('c:user:copy-url')
}
@ -113,7 +116,7 @@ const copyPasswordResetUrl = async (user: User) => {
try {
const { reset_password_url } = await api.orgUsers.generatePasswordResetToken(user.id)
copy(reset_password_url)
await copy(reset_password_url!)
// Invite URL copied to clipboard
message.success(t('msg.success.passwordResetURLCopied'))

12
packages/nc-gui/components/account/UsersModal.vue

@ -93,12 +93,14 @@ const inviteUrl = $computed(() => (usersData.invitationToken ? `${dashboardUrl}#
const copyUrl = async () => {
if (!inviteUrl) return
try {
await copy(inviteUrl)
await copy(inviteUrl)
// Copied shareable base url to clipboard!
message.success(t('msg.success.shareableURLCopied'))
// Copied shareable base url to clipboard!
message.success(t('msg.success.shareableURLCopied'))
} catch (e) {
message.error(e.message)
}
$e('c:shared-base:copy-url')
}

12
packages/nc-gui/components/smartsheet/ApiSnippet.vue

@ -126,10 +126,14 @@ api.dbViewRow.list(
return snippet.convert(activeLang?.name, selectedClient || (activeLang?.clients && activeLang?.clients[0]), {})
})
const onCopyToClipboard = () => {
copy(code)
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
const onCopyToClipboard = async () => {
try {
await copy(code)
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
} catch (e) {
message.error(e.message)
}
}
const afterVisibleChange = (visible: boolean) => {

23
packages/nc-gui/components/smartsheet/toolbar/ShareView.vue

@ -186,10 +186,14 @@ function onChangeTheme(color: string) {
const copyLink = async () => {
if (sharedViewUrl.value) {
await copy(sharedViewUrl.value)
try {
await copy(sharedViewUrl.value)
// Copied to clipboard
message.success(t('msg.info.copiedToClipboard'))
// Copied to clipboard
message.success(t('msg.info.copiedToClipboard'))
} catch (e) {
message.error(e.message)
}
}
}
@ -217,10 +221,14 @@ const iframeCode = computed(() => {
const copyIframeCode = async () => {
if (iframeCode.value) {
await copy(iframeCode.value)
try {
await copy(iframeCode.value)
// Copied to clipboard
message.success(t('msg.info.copiedToClipboard'))
// Copied to clipboard
message.success(t('msg.info.copiedToClipboard'))
} catch (e) {
message.error(e.message)
}
}
}
</script>
@ -268,7 +276,8 @@ const copyIframeCode = async () => {
class="flex gap-1 items-center pb-1 text-gray-500 cursor-pointer font-weight-medium mb-2 mt-4 pl-1"
@click="copyIframeCode"
>
<MdiCodeTags class="text-gray-500" /> Embed this view in your site
<MdiCodeTags class="text-gray-500" />
Embed this view in your site
</div>
<div class="px-1 mt-2 flex flex-col gap-3">

10
packages/nc-gui/components/smartsheet/toolbar/SharedViewList.vue

@ -75,9 +75,13 @@ const renderAllowCSVDownload = (view: SharedViewType) => {
}
const copyLink = (view: SharedViewType) => {
copy(`${dashboardUrl?.value as string}#${sharedViewUrl(view)}`)
// Copied to clipboard
message.success(t('msg.info.copiedToClipboard'))
try {
copy(`${dashboardUrl?.value as string}#${sharedViewUrl(view)}`)
// Copied to clipboard
message.success(t('msg.info.copiedToClipboard'))
} catch (e) {
message.error(e.message)
}
}
const deleteLink = async (id: string) => {

19
packages/nc-gui/components/tabs/auth/ApiTokenManagement.vue

@ -33,13 +33,16 @@ const openNewTokenModal = () => {
$e('c:api-token:generate')
}
const copyToken = (token: string | undefined) => {
const copyToken = async (token: string | undefined) => {
if (!token) return
copy(token)
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
try {
await copy(token)
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
} catch (e) {
message.error(e.message)
}
$e('c:api-token:copy')
}
@ -145,8 +148,8 @@ onMounted(() => {
<div class="flex flex-row justify-center mt-2 text-center w-full text-base">This action will remove this API Token</div>
<div class="flex mt-6 justify-center space-x-2">
<a-button @click="showDeleteTokenModal = false"> {{ $t('general.cancel') }} </a-button>
<a-button type="primary" danger @click="deleteToken()"> {{ $t('general.confirm') }} </a-button>
<a-button @click="showDeleteTokenModal = false"> {{ $t('general.cancel') }}</a-button>
<a-button type="primary" danger @click="deleteToken()"> {{ $t('general.confirm') }}</a-button>
</div>
</div>
</a-modal>
@ -205,7 +208,7 @@ onMounted(() => {
</a-tooltip>
<a-tooltip placement="bottom">
<template #title> {{ $t('general.copy') }} </template>
<template #title> {{ $t('general.copy') }}</template>
<a-button type="text" class="!rounded-md" @click="copyToken(item.token)">
<template #icon>

13
packages/nc-gui/components/tabs/auth/UserManagement.vue

@ -140,13 +140,16 @@ const resendInvite = async (user: User) => {
$e('a:user:resend-invite')
}
const copyInviteUrl = (user: User) => {
const copyInviteUrl = async (user: User) => {
if (!user.invite_token) return
try {
await copy(`${dashboardUrl}#/signup/${user.invite_token}`)
copy(`${dashboardUrl}#/signup/${user.invite_token}`)
// Invite URL copied to clipboard
message.success(t('msg.success.inviteURLCopied'))
// Invite URL copied to clipboard
message.success(t('msg.success.inviteURLCopied'))
} catch (e) {
message.error(e.message)
}
$e('c:user:copy-url')
}

25
packages/nc-gui/components/tabs/auth/user-management/ShareBase.vue

@ -94,11 +94,14 @@ const recreate = async () => {
const copyUrl = async () => {
if (!url) return
try {
await copy(url)
await copy(url)
// Copied shareable base url to clipboard!
message.success(t('msg.success.shareableURLCopied'))
// Copied shareable base url to clipboard!
message.success(t('msg.success.shareableURLCopied'))
} catch (e) {
message.error(e.message)
}
$e('c:shared-base:copy-url')
}
@ -111,10 +114,10 @@ const navigateToSharedBase = () => {
$e('c:shared-base:open-url')
}
const generateEmbeddableIframe = () => {
const generateEmbeddableIframe = async () => {
if (!url) return
copy(`<iframe
try {
await copy(`<iframe
class="nc-embed"
src="${url}?embed"
frameborder="0"
@ -122,9 +125,11 @@ width="100%"
height="700"
style="background: transparent; border: 1px solid #ddd"></iframe>`)
// Copied embeddable html code!
message.success(t('msg.success.embeddableHTMLCodeCopied'))
// Copied embeddable html code!
message.success(t('msg.success.embeddableHTMLCodeCopied'))
} catch (e) {
message.error(e.message)
}
$e('c:shared-base:copy-embed-frame')
}

12
packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue

@ -125,12 +125,14 @@ const inviteUrl = $computed(() => (usersData.invitationToken ? `${dashboardUrl}#
const copyUrl = async () => {
if (!inviteUrl) return
try {
await copy(inviteUrl)
await copy(inviteUrl)
// Copied shareable base url to clipboard!
message.success(t('msg.success.shareableURLCopied'))
// Copied shareable base url to clipboard!
message.success(t('msg.success.shareableURLCopied'))
} catch (e) {
message.error(e.message)
}
$e('c:shared-base:copy-url')
}

16
packages/nc-gui/components/template/Editor.vue

@ -501,16 +501,12 @@ async function importTemplate() {
}
}
}
const createdTable = await $api.base.tableCreate(
project.value?.id as string,
(baseId || project.value?.bases?.[0].id)!,
{
table_name: table.table_name,
// leave title empty to get a generated one based on table_name
title: '',
columns: table.columns || [],
},
)
const createdTable = await $api.base.tableCreate(project.value?.id as string, (baseId || project.value?.bases?.[0].id)!, {
table_name: table.table_name,
// leave title empty to get a generated one based on table_name
title: '',
columns: table.columns || [],
})
table.id = createdTable.id
table.title = createdTable.title

60
packages/nc-gui/composables/useCopy.ts

@ -1,15 +1,46 @@
import { useClipboard } from '#imports'
import { Modal } from 'ant-design-vue'
import { useClipboard, useI18n } from '#imports'
export const useCopy = (showDialogIfFailed = false) => {
const { t } = useI18n()
export const useCopy = () => {
/** fallback for copy if clipboard api is not supported */
const copyFallback = (text: string) => {
const textAreaEl = document.createElement('textarea')
textAreaEl.innerHTML = text
document.body.appendChild(textAreaEl)
textAreaEl.select()
const result = document.execCommand('copy')
document.body.removeChild(textAreaEl)
return result
const copyFallback = async (text: string, retryCount = 0): Promise<boolean> => {
try {
const textAreaEl = document.createElement('textarea')
textAreaEl.innerHTML = text
document.body.appendChild(textAreaEl)
textAreaEl.select()
const result = document.execCommand('copy')
document.body.removeChild(textAreaEl)
if (!result && retryCount < 3) {
// retry if copy failed
return new Promise((resolve, reject) =>
setTimeout(
() =>
copyFallback(text, retryCount + 1)
.then(resolve)
.catch(reject),
100,
),
)
}
if (!result) {
throw new Error('failed')
}
return result
} catch (e) {
if (!showDialogIfFailed) throw new Error(t('msg.error.copyToClipboardError'))
Modal.info({
title: 'Copy failed, please manually copy it from here',
content: text,
class: 'nc-copy-failed-modal',
width: '550px',
})
return false
}
}
const { copy: _copy, isSupported } = useClipboard()
@ -18,12 +49,11 @@ export const useCopy = () => {
try {
if (isSupported.value) {
await _copy(text)
} else {
copyFallback(text)
return true
}
} catch (e) {
copyFallback(text)
}
} catch {}
return copyFallback(text)
}
return { copy }

29
packages/nc-gui/pages/[projectType]/[projectId]/index.vue

@ -52,7 +52,7 @@ const { clearTabs, addTab } = useTabs()
const { isUIAllowed } = useUIPermission()
const { copy } = useCopy()
const { copy } = useCopy(true)
// create a new sidebar state
const { isOpen, toggle, toggleHasSidebar } = useSidebar('nc-left-sidebar', { hasSidebar: false, isOpen: false })
@ -128,15 +128,17 @@ const copyProjectInfo = async () => {
try {
await loadProjectMetaInfo()
await copy(
Object.entries(projectMetaInfo.value!)
.map(([k, v]) => `${k}: **${v}**`)
.join('\n'),
)
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
} catch (e: any) {
if (
await copy(
Object.entries(projectMetaInfo.value!)
.map(([k, v]) => `${k}: **${v}**`)
.join('\n'),
)
) {
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
}
} catch (e) {
console.error(e)
message.error(e.message)
}
@ -144,9 +146,10 @@ const copyProjectInfo = async () => {
const copyAuthToken = async () => {
try {
await copy(token.value!)
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
if (await copy(token.value!)) {
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
}
} catch (e: any) {
console.error(e)
message.error(e.message)

10
packages/nc-gui/pages/index/index/index.vue

@ -132,9 +132,13 @@ onBeforeMount(loadProjects)
const { copy } = useCopy()
const copyProjectMeta = async () => {
const aggregatedMetaInfo = await $api.utils.aggregatedMetaInfo()
copy(JSON.stringify(aggregatedMetaInfo))
message.info('Copied aggregated project meta to clipboard')
try {
const aggregatedMetaInfo = await $api.utils.aggregatedMetaInfo()
await copy(JSON.stringify(aggregatedMetaInfo))
message.info('Copied aggregated project meta to clipboard')
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
</script>

Loading…
Cancel
Save