Browse Source

Merge branch 'develop' into refactor/webhooks

pull/5349/head
Wing-Kam Wong 2 years ago
parent
commit
a9ccf0122f
  1. 42
      packages/nc-gui/assets/style/fonts.css
  2. BIN
      packages/nc-gui/assets/style/material.woff2
  3. 23
      packages/nc-gui/components.d.ts
  4. 4
      packages/nc-gui/components/account/ResetPassword.vue
  5. 10
      packages/nc-gui/components/account/Token.vue
  6. 16
      packages/nc-gui/components/account/UserList.vue
  7. 7
      packages/nc-gui/components/account/UsersModal.vue
  8. 6
      packages/nc-gui/components/api-client/Headers.vue
  9. 6
      packages/nc-gui/components/api-client/Params.vue
  10. 23
      packages/nc-gui/components/cell/GeoData.vue
  11. 3
      packages/nc-gui/components/cell/MultiSelect.vue
  12. 3
      packages/nc-gui/components/cell/SingleSelect.vue
  13. 8
      packages/nc-gui/components/cell/attachment/Carousel.vue
  14. 2
      packages/nc-gui/components/cell/attachment/Image.vue
  15. 9
      packages/nc-gui/components/cell/attachment/Modal.vue
  16. 8
      packages/nc-gui/components/cell/attachment/index.vue
  17. 94
      packages/nc-gui/components/dashboard/TreeView.vue
  18. 6
      packages/nc-gui/components/dashboard/settings/AppStore.vue
  19. 4
      packages/nc-gui/components/dashboard/settings/AuditTab.vue
  20. 24
      packages/nc-gui/components/dashboard/settings/DataSources.vue
  21. 6
      packages/nc-gui/components/dashboard/settings/Metadata.vue
  22. 20
      packages/nc-gui/components/dashboard/settings/Modal.vue
  23. 7
      packages/nc-gui/components/dashboard/settings/UIAcl.vue
  24. 10
      packages/nc-gui/components/dashboard/settings/app-store/AppInstall.vue
  25. 9
      packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
  26. 11
      packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue
  27. 5
      packages/nc-gui/components/dlg/AirtableImport.vue
  28. 9
      packages/nc-gui/components/dlg/QuickImport.vue
  29. 18
      packages/nc-gui/components/dlg/TableCreate.vue
  30. 5
      packages/nc-gui/components/erd/HistogramPanel.vue
  31. 4
      packages/nc-gui/components/general/AddBaseButton.vue
  32. 4
      packages/nc-gui/components/general/HelpAndSupport.vue
  33. 11
      packages/nc-gui/components/general/Icon.vue
  34. 6
      packages/nc-gui/components/general/JoinCloud.vue
  35. 14
      packages/nc-gui/components/general/MiniSidebar.vue
  36. 19
      packages/nc-gui/components/general/PreviewAs.vue
  37. 4
      packages/nc-gui/components/general/ShareBaseButton.vue
  38. 26
      packages/nc-gui/components/general/Social.vue
  39. 18
      packages/nc-gui/components/general/SocialCard.vue
  40. 5
      packages/nc-gui/components/general/TableIcon.vue
  41. 6
      packages/nc-gui/components/smartsheet/Form.vue
  42. 3
      packages/nc-gui/components/smartsheet/Gallery.vue
  43. 8
      packages/nc-gui/components/smartsheet/Grid.vue
  44. 20
      packages/nc-gui/components/smartsheet/Kanban.vue
  45. 4
      packages/nc-gui/components/smartsheet/Map.vue
  46. 4
      packages/nc-gui/components/smartsheet/Pagination.vue
  47. 4
      packages/nc-gui/components/smartsheet/column/BarcodeOptions.vue
  48. 5
      packages/nc-gui/components/smartsheet/column/FormulaOptions.vue
  49. 10
      packages/nc-gui/components/smartsheet/column/SelectOptions.vue
  50. 12
      packages/nc-gui/components/smartsheet/expanded-form/Comments.vue
  51. 34
      packages/nc-gui/components/smartsheet/expanded-form/Header.vue
  52. 5
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  53. 74
      packages/nc-gui/components/smartsheet/header/CellIcon.ts
  54. 22
      packages/nc-gui/components/smartsheet/header/Menu.vue
  55. 60
      packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts
  56. 12
      packages/nc-gui/components/smartsheet/sidebar/MenuBottom.vue
  57. 24
      packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue
  58. 4
      packages/nc-gui/components/smartsheet/sidebar/toolbar/DeleteCache.vue
  59. 4
      packages/nc-gui/components/smartsheet/sidebar/toolbar/DeleteTable.vue
  60. 4
      packages/nc-gui/components/smartsheet/sidebar/toolbar/ExportCache.vue
  61. 8
      packages/nc-gui/components/smartsheet/toolbar/AddRow.vue
  62. 11
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  63. 5
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilterMenu.vue
  64. 4
      packages/nc-gui/components/smartsheet/toolbar/Erd.vue
  65. 8
      packages/nc-gui/components/smartsheet/toolbar/Export.vue
  66. 5
      packages/nc-gui/components/smartsheet/toolbar/ExportSubActions.vue
  67. 9
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  68. 5
      packages/nc-gui/components/smartsheet/toolbar/KanbanStackEditOrAdd.vue
  69. 20
      packages/nc-gui/components/smartsheet/toolbar/LockType.vue
  70. 3
      packages/nc-gui/components/smartsheet/toolbar/MappedBy.vue
  71. 4
      packages/nc-gui/components/smartsheet/toolbar/QrScannerButton.vue
  72. 9
      packages/nc-gui/components/smartsheet/toolbar/Reload.vue
  73. 15
      packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue
  74. 5
      packages/nc-gui/components/smartsheet/toolbar/SearchData.vue
  75. 14
      packages/nc-gui/components/smartsheet/toolbar/ShareView.vue
  76. 5
      packages/nc-gui/components/smartsheet/toolbar/SharedViewList.vue
  77. 10
      packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue
  78. 3
      packages/nc-gui/components/smartsheet/toolbar/StackedBy.vue
  79. 38
      packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue
  80. 20
      packages/nc-gui/components/tabs/auth/ApiTokenManagement.vue
  81. 17
      packages/nc-gui/components/tabs/auth/UserManagement.vue
  82. 11
      packages/nc-gui/components/tabs/auth/user-management/ShareBase.vue
  83. 7
      packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue
  84. 29
      packages/nc-gui/components/template/Editor.vue
  85. 8
      packages/nc-gui/components/virtual-cell/BelongsTo.vue
  86. 6
      packages/nc-gui/components/virtual-cell/HasMany.vue
  87. 6
      packages/nc-gui/components/virtual-cell/ManyToMany.vue
  88. 7
      packages/nc-gui/components/virtual-cell/components/ItemChip.vue
  89. 12
      packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
  90. 3
      packages/nc-gui/components/virtual-cell/components/ListItems.vue
  91. 8
      packages/nc-gui/components/webhook/Editor.vue
  92. 19
      packages/nc-gui/components/webhook/List.vue
  93. 11
      packages/nc-gui/layouts/base.vue
  94. 4
      packages/nc-gui/layouts/shared-view.vue
  95. 34
      packages/nc-gui/package-lock.json
  96. 3
      packages/nc-gui/package.json
  97. 31
      packages/nc-gui/pages/[projectType]/[projectId]/index.vue
  98. 13
      packages/nc-gui/pages/[projectType]/[projectId]/index/index.vue
  99. 4
      packages/nc-gui/pages/[projectType]/form/[viewId]/index/index.vue
  100. 15
      packages/nc-gui/pages/[projectType]/form/[viewId]/index/survey.vue
  101. Some files were not shown because too many files have changed in this diff Show More

42
packages/nc-gui/assets/style/fonts.css

@ -149,3 +149,45 @@
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
/* material-symbols-outlined-200 - latin */
@font-face {
font-family: 'Material Symbols Outlined';
font-style: normal;
font-weight: 200;
src: url('./material-symbols/material-symbols-outlined-v92-latin-200.eot'); /* IE9 Compat Modes */
src: url('./material-symbols/material-symbols-outlined-v92-latin-200.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('./material-symbols/material-symbols-outlined-v92-latin-200.woff2') format('woff2'), /* Super Modern Browsers */
url('./material-symbols/material-symbols-outlined-v92-latin-200.woff') format('woff'), /* Modern Browsers */
url('./material-symbols/material-symbols-outlined-v92-latin-200.ttf') format('truetype'), /* Safari, Android, iOS */
url('./material-symbols/material-symbols-outlined-v92-latin-200.svg#MaterialSymbolsOutlined') format('svg'); /* Legacy iOS */
}
/* // href: 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,200,0,-25',
*/
/* fallback */
@font-face {
font-family: 'Material Symbols Outlined';
font-style: normal;
font-weight: 200;
src: url(./material.woff2) format('woff2');
}
.material-symbols-outlined {
font-family: 'Material Symbols Outlined';
font-weight: normal;
font-style: normal;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
font-size: 22px !important;
user-select: none;
}

BIN
packages/nc-gui/assets/style/material.woff2

Binary file not shown.

23
packages/nc-gui/components.d.ts vendored

@ -259,10 +259,33 @@ declare module '@vue/runtime-core' {
NcIconsRowHeightMedium: typeof import('~icons/nc-icons/row-height-medium')['default'] NcIconsRowHeightMedium: typeof import('~icons/nc-icons/row-height-medium')['default']
NcIconsRowHeightShort: typeof import('~icons/nc-icons/row-height-short')['default'] NcIconsRowHeightShort: typeof import('~icons/nc-icons/row-height-short')['default']
NcIconsRowHeightTall: typeof import('~icons/nc-icons/row-height-tall')['default'] NcIconsRowHeightTall: typeof import('~icons/nc-icons/row-height-tall')['default']
PhArrowClockwiseThin: typeof import('~icons/ph/arrow-clockwise-thin')['default']
PhAtThin: typeof import('~icons/ph/at-thin')['default']
PhBracketsAngleThin: typeof import('~icons/ph/brackets-angle-thin')['default']
PhBracketsCurlyThin: typeof import('~icons/ph/brackets-curly-thin')['default']
PhCaretDoubleLeftThin: typeof import('~icons/ph/caret-double-left-thin')['default']
PhCaretDoubleRightThin: typeof import('~icons/ph/caret-double-right-thin')['default']
PhCaretDoubleThin: typeof import('~icons/ph/caret-double-thin')['default']
PhCaretDownThin: typeof import('~icons/ph/caret-down-thin')['default']
PhChatTextThin: typeof import('~icons/ph/chat-text-thin')['default'] PhChatTextThin: typeof import('~icons/ph/chat-text-thin')['default']
PhClockClockwiseThin: typeof import('~icons/ph/clock-clockwise-thin')['default']
PhCloudLightningDuotone: typeof import('~icons/ph/cloud-lightning-duotone')['default'] PhCloudLightningDuotone: typeof import('~icons/ph/cloud-lightning-duotone')['default']
PhCloudLightningThin: typeof import('~icons/ph/cloud-lightning-thin')['default'] PhCloudLightningThin: typeof import('~icons/ph/cloud-lightning-thin')['default']
PhEyeThin: typeof import('~icons/ph/eye-thin')['default']
PhFileCsv: typeof import('~icons/ph/file-csv')['default'] PhFileCsv: typeof import('~icons/ph/file-csv')['default']
PhFolderSimpleThin: typeof import('~icons/ph/folder-simple-thin')['default']
PhFolderThin: typeof import('~icons/ph/folder-thin')['default']
PhFunnelThin: typeof import('~icons/ph/funnel-thin')['default']
PhlistBulletsThin: typeof import('~icons/ph/list-bullets-thin')['default']
PhListBulletsThin: typeof import('~icons/ph/list-bullets-thin')['default']
PhMagnifyingGlassThin: typeof import('~icons/ph/magnifying-glass-thin')['default']
PhPlusThin: typeof import('~icons/ph/plus-thin')['default']
PhPresentationThin: typeof import('~icons/ph/presentation-thin')['default']
PhShareThin: typeof import('~icons/ph/share-thin')['default']
PhSignOutThin: typeof import('~icons/ph/sign-out-thin')['default']
PhSortAscendingThin: typeof import('~icons/ph/sort-ascending-thin')['default']
PhSplitVerticalThin: typeof import('~icons/ph/split-vertical-thin')['default']
PhTranslateThin: typeof import('~icons/ph/translate-thin')['default']
PhUserPlusThin: typeof import('~icons/ph/user-plus-thin')['default'] PhUserPlusThin: typeof import('~icons/ph/user-plus-thin')['default']
PhUsersThreeThin: typeof import('~icons/ph/users-three-thin')['default'] PhUsersThreeThin: typeof import('~icons/ph/users-three-thin')['default']
RiLineHeight: typeof import('~icons/ri/line-height')['default'] RiLineHeight: typeof import('~icons/ri/line-height')['default']

4
packages/nc-gui/components/account/ResetPassword.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { message, navigateTo, reactive, ref, useApi, useGlobal, useI18n } from '#imports' import { iconMap, message, navigateTo, reactive, ref, useApi, useGlobal, useI18n } from '#imports'
const { api, error } = useApi({ useGlobalInstance: true }) const { api, error } = useApi({ useGlobalInstance: true })
@ -121,7 +121,7 @@ const resetError = () => {
<div class="text-center"> <div class="text-center">
<button data-testid="nc-user-settings-form__submit" class="scaling-btn bg-opacity-100" type="submit"> <button data-testid="nc-user-settings-form__submit" class="scaling-btn bg-opacity-100" type="submit">
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
<MdiKeyChange /> <component :is="iconMap.passwordChange" />
{{ $t('activity.changePwd') }} {{ $t('activity.changePwd') }}
</span> </span>
</button> </button>

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

@ -2,7 +2,7 @@
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { Empty, Modal, message } from 'ant-design-vue' import { Empty, Modal, message } from 'ant-design-vue'
import type { ApiTokenType, RequestParams, UserType } from 'nocodb-sdk' import type { ApiTokenType, RequestParams, UserType } from 'nocodb-sdk'
import { extractSdkResponseErrorMsg, useApi, useCopy, useNuxtApp } from '#imports' import { extractSdkResponseErrorMsg, iconMap, useApi, useCopy, useNuxtApp } from '#imports'
const { api, isLoading } = useApi() const { api, isLoading } = useApi()
@ -104,10 +104,10 @@ const descriptionInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<div class="max-w-[900px] mx-auto p-4" data-testid="nc-token-list"> <div class="max-w-[900px] mx-auto p-4" data-testid="nc-token-list">
<div class="py-2 flex gap-4 items-center"> <div class="py-2 flex gap-4 items-center">
<div class="flex-grow"></div> <div class="flex-grow"></div>
<MdiReload class="cursor-pointer" @click="loadTokens" /> <component :is="iconMap.reload" class="cursor-pointer" @click="loadTokens" />
<a-button data-testid="nc-token-create" size="small" type="primary" @click="showNewTokenModal = true"> <a-button data-testid="nc-token-create" size="small" type="primary" @click="showNewTokenModal = true">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<MdiAdd /> <component :is="iconMap.plus" />
Add new token Add new token
</div> </div>
</a-button> </a-button>
@ -175,7 +175,7 @@ const descriptionInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<a-button type="text" class="!rounded-md" @click="copyToken(record.token)"> <a-button type="text" class="!rounded-md" @click="copyToken(record.token)">
<template #icon> <template #icon>
<MdiContentCopy class="flex mx-auto h-[1rem]" /> <component :is="iconMap.copy" class="flex mx-auto h-[1rem]" />
</template> </template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -198,7 +198,7 @@ const descriptionInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<a-menu data-testid="nc-token-row-action-icon"> <a-menu data-testid="nc-token-row-action-icon">
<a-menu-item> <a-menu-item>
<div class="flex flex-row items-center py-3 h-[1rem] nc-delete-token" @click="deleteToken(record.token)"> <div class="flex flex-row items-center py-3 h-[1rem] nc-delete-token" @click="deleteToken(record.token)">
<MdiDeleteOutline class="flex" /> <component :is="iconMap.delete" class="flex" />
<div class="text-xs pl-2">{{ $t('general.remove') }}</div> <div class="text-xs pl-2">{{ $t('general.remove') }}</div>
</div> </div>
</a-menu-item> </a-menu-item>

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

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Modal, message } from 'ant-design-vue' import { Modal, message } from 'ant-design-vue'
import type { OrgUserReqType, RequestParams, UserType } from 'nocodb-sdk' import type { OrgUserReqType, RequestParams, UserType } from 'nocodb-sdk'
import { Role, extractSdkResponseErrorMsg, useApi, useCopy, useDashboard, useNuxtApp } from '#imports' import { Role, extractSdkResponseErrorMsg, iconMap, useApi, useCopy, useDashboard, useNuxtApp } from '#imports'
import type { User } from '~/lib' import type { User } from '~/lib'
const { api, isLoading } = useApi() const { api, isLoading } = useApi()
@ -144,7 +144,7 @@ const copyPasswordResetUrl = async (user: User) => {
> >
</a-input-search> </a-input-search>
<div class="flex-grow"></div> <div class="flex-grow"></div>
<MdiReload class="cursor-pointer" @click="loadUsers" /> <component :is="iconMap.reload" class="cursor-pointer" @click="loadUsers" />
<a-button <a-button
data-testid="nc-super-user-invite" data-testid="nc-super-user-invite"
size="small" size="small"
@ -157,7 +157,7 @@ const copyPasswordResetUrl = async (user: User) => {
" "
> >
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<MdiAdd /> <component :is="iconMap.plus" />
Invite new user Invite new user
</div> </div>
</a-button> </a-button>
@ -239,7 +239,7 @@ const copyPasswordResetUrl = async (user: User) => {
<div class="flex flex-row items-center"> <div class="flex flex-row items-center">
<a-button type="text" class="!px-0"> <a-button type="text" class="!px-0">
<div class="flex flex-row items-center h-[1.2rem]"> <div class="flex flex-row items-center h-[1.2rem]">
<MdiDotsHorizontal class="nc-user-row-action" /> <component :is="iconMap.threeDotHorizontal" class="nc-user-row-action" />
</div> </div>
</a-button> </a-button>
</div> </div>
@ -250,26 +250,26 @@ const copyPasswordResetUrl = async (user: User) => {
<a-menu-item> <a-menu-item>
<!-- Resend invite Email --> <!-- Resend invite Email -->
<div class="flex flex-row items-center py-3" @click="resendInvite(record)"> <div class="flex flex-row items-center py-3" @click="resendInvite(record)">
<MdiEmailArrowRightOutline class="flex h-[1rem] text-gray-500" /> <component :is="iconMap.email" class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.resendInvite') }}</div> <div class="text-xs pl-2">{{ $t('activity.resendInvite') }}</div>
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item> <a-menu-item>
<div class="flex flex-row items-center py-3" @click="copyInviteUrl(record)"> <div class="flex flex-row items-center py-3" @click="copyInviteUrl(record)">
<MdiContentCopy class="flex h-[1rem] text-gray-500" /> <component :is="iconMap.copy" class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.copyInviteURL') }}</div> <div class="text-xs pl-2">{{ $t('activity.copyInviteURL') }}</div>
</div> </div>
</a-menu-item> </a-menu-item>
</template> </template>
<a-menu-item> <a-menu-item>
<div class="flex flex-row items-center py-3" @click="copyPasswordResetUrl(record)"> <div class="flex flex-row items-center py-3" @click="copyPasswordResetUrl(record)">
<MdiContentCopy class="flex h-[1rem] text-gray-500" /> <component :is="iconMap.copy" class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.copyPasswordResetURL') }}</div> <div class="text-xs pl-2">{{ $t('activity.copyPasswordResetURL') }}</div>
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item> <a-menu-item>
<div class="flex flex-row items-center py-3" @click="deleteUser(text)"> <div class="flex flex-row items-center py-3" @click="deleteUser(text)">
<MdiDeleteOutline data-testid="nc-super-user-delete" class="flex h-[1rem] text-gray-500" /> <component :is="iconMap.delete" data-testid="nc-super-user-delete" class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('general.delete') }}</div> <div class="text-xs pl-2">{{ $t('general.delete') }}</div>
</div> </div>
</a-menu-item> </a-menu-item>

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

@ -6,6 +6,7 @@ import {
computed, computed,
emailValidator, emailValidator,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
iconMap,
message, message,
ref, ref,
useCopy, useCopy,
@ -126,7 +127,7 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<template v-if="usersData.invitationToken"> <template v-if="usersData.invitationToken">
<div class="flex flex-col mt-1 border-b-1 pb-5"> <div class="flex flex-col mt-1 border-b-1 pb-5">
<div class="flex flex-row items-center pl-1.5 pb-1 h-[1.1rem]"> <div class="flex flex-row items-center pl-1.5 pb-1 h-[1.1rem]">
<MdiAccountOutline /> <component :is="iconMap.account" />
<div class="text-xs ml-0.5 mt-0.5">Copy Invite Token</div> <div class="text-xs ml-0.5 mt-0.5">Copy Invite Token</div>
</div> </div>
@ -139,7 +140,7 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<a-button type="text" class="!rounded-md -mt-0.5" @click="copyUrl"> <a-button type="text" class="!rounded-md -mt-0.5" @click="copyUrl">
<template #icon> <template #icon>
<MdiContentCopy class="flex mx-auto text-green-700 h-[1rem]" /> <component :is="iconMap.copy" class="flex mx-auto text-green-700 h-[1rem]" />
</template> </template>
</a-button> </a-button>
</div> </div>
@ -165,7 +166,7 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<div v-else class="flex flex-col pb-4"> <div v-else class="flex flex-col pb-4">
<div class="flex flex-row items-center pl-2 pb-1 h-[1rem]"> <div class="flex flex-row items-center pl-2 pb-1 h-[1rem]">
<MdiAccountOutline /> <component :is="iconMap.account" />
<div class="text-xs ml-0.5 mt-0.5">{{ $t('activity.inviteUser') }}</div> <div class="text-xs ml-0.5 mt-0.5">{{ $t('activity.inviteUser') }}</div>
</div> </div>

6
packages/nc-gui/components/api-client/Headers.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '#imports' import { iconMap, useVModel } from '#imports'
const props = defineProps<{ const props = defineProps<{
modelValue: any[] modelValue: any[]
@ -116,7 +116,7 @@ const filterOption = (input: string, option: Option) => {
<td class="relative"> <td class="relative">
<div v-if="idx !== 0" class="absolute flex flex-col justify-start mt-2 -right-6 top-0"> <div v-if="idx !== 0" class="absolute flex flex-col justify-start mt-2 -right-6 top-0">
<MdiDeleteOutline class="cursor-pointer" @click="deleteHeaderRow(idx)" /> <component :is="iconMap.delete" class="cursor-pointer" @click="deleteHeaderRow(idx)" />
</div> </div>
</td> </td>
</tr> </tr>
@ -125,7 +125,7 @@ const filterOption = (input: string, option: Option) => {
<td :colspan="12" class="text-center"> <td :colspan="12" class="text-center">
<a-button type="default" class="!bg-gray-100 rounded-md border-none mr-1" @click="addHeaderRow"> <a-button type="default" class="!bg-gray-100 rounded-md border-none mr-1" @click="addHeaderRow">
<template #icon> <template #icon>
<MdiPlus class="flex mx-auto" /> <component :is="iconMap.plus" class="flex mx-auto" />
</template> </template>
</a-button> </a-button>
</td> </td>

6
packages/nc-gui/components/api-client/Params.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '#imports' import { iconMap, useVModel } from '#imports'
const props = defineProps<{ const props = defineProps<{
modelValue: any[] modelValue: any[]
@ -59,7 +59,7 @@ const deleteParamRow = (i: number) => vModel.value.splice(i, 1)
<td class="relative"> <td class="relative">
<div v-if="idx !== 0" class="absolute flex flex-col justify-start mt-2 -right-6 top-0"> <div v-if="idx !== 0" class="absolute flex flex-col justify-start mt-2 -right-6 top-0">
<MdiDeleteOutline class="cursor-pointer" @click="deleteParamRow(idx)" /> <component :is="iconMap.delete" class="cursor-pointer" @click="deleteParamRow(idx)" />
</div> </div>
</td> </td>
</tr> </tr>
@ -68,7 +68,7 @@ const deleteParamRow = (i: number) => vModel.value.splice(i, 1)
<td :colspan="12" class="text-center"> <td :colspan="12" class="text-center">
<a-button type="default" class="!bg-gray-100 rounded-md border-none mr-1 mb-3" @click="addParamRow"> <a-button type="default" class="!bg-gray-100 rounded-md border-none mr-1 mb-3" @click="addParamRow">
<template #icon> <template #icon>
<MdiPlus class="flex mx-auto" /> <component :is="iconMap.plus" class="flex mx-auto" />
</template> </template>
</a-button> </a-button>
</td> </td>

23
packages/nc-gui/components/cell/GeoData.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { GeoLocationType } from 'nocodb-sdk' import type { GeoLocationType } from 'nocodb-sdk'
import { Modal as AModal, latLongToJoinedString, useVModel } from '#imports' import { Modal as AModal, iconMap, latLongToJoinedString, useVModel } from '#imports'
interface Props { interface Props {
modelValue?: string | null modelValue?: string | null
@ -89,7 +89,10 @@ const openInOSM = () => {
class="group cursor-pointer flex gap-1 items-center mx-auto max-w-64 justify-center active:(ring ring-accent ring-opacity-100) rounded border-1 p-1 shadow-sm hover:(bg-primary bg-opacity-10) dark:(!bg-slate-500)" class="group cursor-pointer flex gap-1 items-center mx-auto max-w-64 justify-center active:(ring ring-accent ring-opacity-100) rounded border-1 p-1 shadow-sm hover:(bg-primary bg-opacity-10) dark:(!bg-slate-500)"
> >
<div class="flex items-center gap-2" data-testid="nc-geo-data-set-location-button"> <div class="flex items-center gap-2" data-testid="nc-geo-data-set-location-button">
<MdiMapMarker class="transform dark:(!text-white) group-hover:(!text-accent scale-120) text-gray-500 text-[0.75rem]" /> <component
:is="iconMap.mapMarker"
class="transform dark:(!text-white) group-hover:(!text-accent scale-120) text-gray-500 text-[0.75rem]"
/>
<div class="group-hover:text-primary text-gray-500 dark:text-gray-200 dark:group-hover:!text-white text-xs"> <div class="group-hover:text-primary text-gray-500 dark:text-gray-200 dark:group-hover:!text-white text-xs">
{{ latLongStr }} {{ latLongStr }}
</div> </div>
@ -135,16 +138,24 @@ const openInOSM = () => {
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<div class="mr-2 flex flex-col items-end gap-1 text-left"> <div class="mr-2 flex flex-col items-end gap-1 text-left">
<MdiReload v-if="isLoading" :class="{ 'animate-infinite animate-spin text-gray-500': isLoading }" /> <component
:is="iconMap.reload"
v-if="isLoading"
:class="{ 'animate-infinite animate-spin text-gray-500': isLoading }"
/>
<a-button class="ml-2" @click="onClickSetCurrentLocation" <a-button class="ml-2" @click="onClickSetCurrentLocation"
><MdiGpsFixed class="mr-2" />{{ $t('labels.currentLocation') }}</a-button ><component :is="iconMap.currentLocation" class="mr-2" />{{ $t('labels.currentLocation') }}</a-button
> >
</div> </div>
</a-form-item> </a-form-item>
<a-form-item v-if="vModel"> <a-form-item v-if="vModel">
<div class="mr-2 flex flex-row items-end gap-1 text-left"> <div class="mr-2 flex flex-row items-end gap-1 text-left">
<a-button @click="openInOSM"><MdiOpenInNew class="mr-2" />{{ $t('activity.map.openInOpenStreetMap') }}</a-button> <a-button @click="openInOSM"
<a-button @click="openInGoogleMaps"><MdiOpenInNew class="mr-2" />{{ $t('activity.map.openInGoogleMaps') }}</a-button> ><component :is="iconMap.openInNew" class="mr-2" />{{ $t('activity.map.openInOpenStreetMap') }}</a-button
>
<a-button @click="openInGoogleMaps"
><component :is="iconMap.openInNew" class="mr-2" />{{ $t('activity.map.openInGoogleMaps') }}</a-button
>
</div> </div>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>

3
packages/nc-gui/components/cell/MultiSelect.vue

@ -14,6 +14,7 @@ import {
enumColor, enumColor,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
h, h,
iconMap,
inject, inject,
isDrawerOrModalExist, isDrawerOrModalExist,
onMounted, onMounted,
@ -367,7 +368,7 @@ useEventListener(document, 'click', handleClose, true)
:value="searchVal" :value="searchVal"
> >
<div class="flex gap-2 text-gray-500 items-center h-full"> <div class="flex gap-2 text-gray-500 items-center h-full">
<MdiPlusThick class="min-w-4" /> <component :is="iconMap.plusThick" class="min-w-4" />
<div class="text-xs whitespace-normal"> <div class="text-xs whitespace-normal">
Create new option named <strong>{{ searchVal }}</strong> Create new option named <strong>{{ searchVal }}</strong>
</div> </div>

3
packages/nc-gui/components/cell/SingleSelect.vue

@ -15,6 +15,7 @@ import {
computed, computed,
enumColor, enumColor,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
iconMap,
inject, inject,
isDrawerOrModalExist, isDrawerOrModalExist,
ref, ref,
@ -295,7 +296,7 @@ useEventListener(document, 'click', handleClose, true)
:value="searchVal" :value="searchVal"
> >
<div class="flex gap-2 text-gray-500 items-center h-full"> <div class="flex gap-2 text-gray-500 items-center h-full">
<MdiPlusThick class="min-w-4" /> <component :is="iconMap.plusThick" class="min-w-4" />
<div class="text-xs whitespace-normal"> <div class="text-xs whitespace-normal">
Create new option named <strong>{{ searchVal }}</strong> Create new option named <strong>{{ searchVal }}</strong>
</div> </div>

8
packages/nc-gui/components/cell/attachment/Carousel.vue

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onKeyDown } from '@vueuse/core' import { onKeyDown } from '@vueuse/core'
import { useAttachmentCell } from './utils' import { useAttachmentCell } from './utils'
import { computed, isImage, onClickOutside, ref, useAttachment } from '#imports' import { computed, iconMap, isImage, onClickOutside, ref, useAttachment } from '#imports'
const { selectedImage, visibleItems, downloadFile } = useAttachmentCell()! const { selectedImage, visibleItems, downloadFile } = useAttachmentCell()!
@ -53,7 +53,11 @@ onClickOutside(carouselRef, () => {
<template v-if="selectedImage"> <template v-if="selectedImage">
<div class="overflow-hidden p-12 text-center relative"> <div class="overflow-hidden p-12 text-center relative">
<div class="text-white group absolute top-5 right-5"> <div class="text-white group absolute top-5 right-5">
<MdiCloseCircle class="group-hover:text-red-500 cursor-pointer text-4xl" @click.stop="selectedImage = false" /> <component
:is="iconMap.closeCircle"
class="group-hover:text-red-500 cursor-pointer text-4xl"
@click.stop="selectedImage = false"
/>
</div> </div>
<div <div

2
packages/nc-gui/components/cell/attachment/Image.vue

@ -21,5 +21,5 @@ const onError = () => index.value++
quality="75" quality="75"
@error="onError" @error="onError"
/> />
<MdiFileImageBox v-else /> <component :is="iconMap.imagePlaceholder" v-else />
</template> </template>

9
packages/nc-gui/components/cell/attachment/Modal.vue

@ -2,7 +2,7 @@
import { onKeyDown } from '@vueuse/core' import { onKeyDown } from '@vueuse/core'
import { useAttachmentCell } from './utils' import { useAttachmentCell } from './utils'
import { useSortable } from './sort' import { useSortable } from './sort'
import { isImage, ref, useAttachment, useDropZone, useUIPermission, watch } from '#imports' import { iconMap, isImage, ref, useAttachment, useDropZone, useUIPermission, watch } from '#imports'
const { isUIAllowed } = useUIPermission() const { isUIAllowed } = useUIPermission()
@ -133,7 +133,8 @@ function onRemoveFileClick(title: any, i: number) {
<a-tooltip v-if="!readOnly"> <a-tooltip v-if="!readOnly">
<template #title> Remove File </template> <template #title> Remove File </template>
<MdiCloseCircle <component
:is="iconMap.closeCircle"
v-if="isSharedForm || (isUIAllowed('tableAttachment') && !isPublic && !isLocked)" v-if="isSharedForm || (isUIAllowed('tableAttachment') && !isPublic && !isLocked)"
class="nc-attachment-remove" class="nc-attachment-remove"
@click.stop="onRemoveFileClick(item.title, i)" @click.stop="onRemoveFileClick(item.title, i)"
@ -144,7 +145,7 @@ function onRemoveFileClick(title: any, i: number) {
<template #title> Download File </template> <template #title> Download File </template>
<div class="nc-attachment-download group-hover:(opacity-100)"> <div class="nc-attachment-download group-hover:(opacity-100)">
<MdiDownload @click.stop="downloadFile(item)" /> <component :is="iconMap.download" @click.stop="downloadFile(item)" />
</div> </div>
</a-tooltip> </a-tooltip>
@ -155,7 +156,7 @@ function onRemoveFileClick(title: any, i: number) {
<template #title> Rename File </template> <template #title> Rename File </template>
<div class="nc-attachment-download group-hover:(opacity-100) mr-[35px]"> <div class="nc-attachment-download group-hover:(opacity-100) mr-[35px]">
<MdiEditOutline @click.stop="renameFile(item, i)" /> <component :is="iconMap.edit" @click.stop="renameFile(item, i)" />
</div> </div>
</a-tooltip> </a-tooltip>

8
packages/nc-gui/components/cell/attachment/index.vue

@ -7,6 +7,7 @@ import {
DropZoneRef, DropZoneRef,
IsGalleryInj, IsGalleryInj,
IsKanbanInj, IsKanbanInj,
iconMap,
inject, inject,
isImage, isImage,
nextTick, nextTick,
@ -188,7 +189,7 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e) => {
data-testid="attachment-cell-file-picker-button" data-testid="attachment-cell-file-picker-button"
@click.stop="open" @click.stop="open"
> >
<MdiReload v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" /> <component :is="iconMap.reload" v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" />
<a-tooltip v-else placement="bottom"> <a-tooltip v-else placement="bottom">
<template #title> Click or drop a file into cell</template> <template #title> Click or drop a file into cell</template>
@ -238,12 +239,13 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e) => {
<div <div
class="group cursor-pointer flex gap-1 items-center active:(ring ring-accent ring-opacity-100) rounded border-1 p-1 shadow-sm hover:(bg-primary bg-opacity-10) dark:(!bg-slate-500)" class="group cursor-pointer flex gap-1 items-center active:(ring ring-accent ring-opacity-100) rounded border-1 p-1 shadow-sm hover:(bg-primary bg-opacity-10) dark:(!bg-slate-500)"
> >
<MdiReload v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" /> <component :is="iconMap.reload" v-if="isLoading" :class="{ 'animate-infinite animate-spin': isLoading }" />
<a-tooltip v-else placement="bottom"> <a-tooltip v-else placement="bottom">
<template #title> View attachments</template> <template #title> View attachments</template>
<MdiArrowExpand <component
:is="iconMap.expand"
class="transform dark:(!text-white) group-hover:(!text-accent scale-120) text-gray-500 text-[0.75rem]" class="transform dark:(!text-white) group-hover:(!text-accent scale-120) text-gray-500 text-[0.75rem]"
@click.stop="modalVisible = true" @click.stop="modalVisible = true"
/> />

94
packages/nc-gui/components/dashboard/TreeView.vue

@ -12,6 +12,7 @@ import {
TabType, TabType,
computed, computed,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
iconMap,
isDrawerOrModalExist, isDrawerOrModalExist,
isMac, isMac,
parseProp, parseProp,
@ -30,8 +31,6 @@ import {
useUIPermission, useUIPermission,
watchEffect, watchEffect,
} from '#imports' } from '#imports'
import MdiView from '~icons/mdi/eye-circle-outline'
import MdiTableLarge from '~icons/mdi/table-large'
const { isMobileMode } = useGlobal() const { isMobileMode } = useGlobal()
@ -152,10 +151,10 @@ watchEffect(() => {
const icon = (table: TableType) => { const icon = (table: TableType) => {
if (table.type === 'table') { if (table.type === 'table') {
return MdiTableLarge return iconMap.table
} }
if (table.type === 'view') { if (table.type === 'view') {
return MdiView return iconMap.view
} }
} }
@ -373,8 +372,13 @@ const setIcon = async (icon: string, table: TableType) => {
</Transition> </Transition>
<Transition name="layout" mode="out-in"> <Transition name="layout" mode="out-in">
<MdiClose v-if="searchActive" class="text-gray-500 text-lg mx-1 mt-0.5" @click="onSearchCloseIconClick" /> <GeneralIcon
<IcRoundSearch v-else class="text-gray-500 text-lg mx-1 mt-0.5" @click="toggleSearchActive(true)" /> v-if="searchActive"
icon="close"
class="text-gray-500 text-lg mx-1 mt-0.5"
@click="onSearchCloseIconClick"
/>
<GeneralIcon v-else icon="search" class="text-gray-500 text-lg mx-1 mt-0.5" @click="toggleSearchActive(true)" />
</Transition> </Transition>
</div> </div>
<div <div
@ -400,13 +404,18 @@ const setIcon = async (icon: string, table: TableType) => {
</Transition> </Transition>
<Transition name="slide-right" mode="out-in"> <Transition name="slide-right" mode="out-in">
<MdiClose v-if="searchActive" class="text-gray-500 text-lg mx-1 mt-0.5" @click="onSearchCloseIconClick" /> <GeneralIcon
<IcRoundSearch v-else class="text-gray-500 text-lg mx-1 mt-0.5" @click="onSearchIconClick" /> v-if="searchActive"
icon="close"
class="text-gray-500 text-lg mx-1 mt-0.5"
@click="onSearchCloseIconClick"
/>
<component :is="iconMap.search" v-else class="text-gray-500 text-lg mx-1 mt-0.5" @click="onSearchIconClick" />
</Transition> </Transition>
<a-dropdown v-if="!isSharedBase" :trigger="['click']" overlay-class-name="nc-dropdown-import-menu" @click.stop> <a-dropdown v-if="!isSharedBase" :trigger="['click']" overlay-class-name="nc-dropdown-import-menu" @click.stop>
<Transition name="slide-right" mode="out-in"> <Transition name="slide-right" mode="out-in">
<MdiDotsVertical v-if="!searchActive" class="hover:text-accent outline-0" /> <GeneralIcon v-if="!searchActive" icon="threeDotVertical" class="hover:text-accent outline-0" />
</Transition> </Transition>
<template #overlay> <template #overlay>
@ -457,7 +466,7 @@ const setIcon = async (icon: string, table: TableType) => {
target="_blank" target="_blank"
class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)" class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)"
> >
<MdiOpenInNew class="group-hover:text-accent" /> <GeneralIcon icon="openInNew" class="group-hover:text-accent" />
<!-- Request a data source you need? --> <!-- Request a data source you need? -->
{{ $t('labels.requestDataSource') }} {{ $t('labels.requestDataSource') }}
</a> </a>
@ -473,12 +482,15 @@ const setIcon = async (icon: string, table: TableType) => {
class="group flex items-center gap-2 pl-2 pr-3 py-2 text-primary/70 hover:(text-primary/100) cursor-pointer select-none" class="group flex items-center gap-2 pl-2 pr-3 py-2 text-primary/70 hover:(text-primary/100) cursor-pointer select-none"
@click="openTableCreateDialog(bases[0].id)" @click="openTableCreateDialog(bases[0].id)"
> >
<MdiPlus class="w-5" /> <GeneralIcon icon="plus" class="w-5" />
<span class="text-gray-500 group-hover:(text-primary/100) flex-1 nc-add-new-table">{{ $t('tooltip.addTable') }}</span> <span class="text-gray-500 group-hover:(text-primary/100) flex-1 nc-add-new-table">{{ $t('tooltip.addTable') }}</span>
<a-dropdown v-if="!isSharedBase" :trigger="['click']" overlay-class-name="nc-dropdown-import-menu" @click.stop> <a-dropdown v-if="!isSharedBase" :trigger="['click']" overlay-class-name="nc-dropdown-import-menu" @click.stop>
<MdiDotsVertical class="transition-opacity opacity-0 group-hover:opacity-100 nc-import-menu outline-0" /> <GeneralIcon
icon="threeDotVertical"
class="transition-opacity opacity-0 group-hover:opacity-100 nc-import-menu outline-0"
/>
<template #overlay> <template #overlay>
<a-menu class="!py-0 rounded text-sm"> <a-menu class="!py-0 rounded text-sm">
@ -490,7 +502,7 @@ const setIcon = async (icon: string, table: TableType) => {
@click="openAirtableImportDialog(bases[0].id)" @click="openAirtableImportDialog(bases[0].id)"
> >
<div class="color-transition nc-project-menu-item group"> <div class="color-transition nc-project-menu-item group">
<MdiTableLarge class="group-hover:text-accent" /> <GeneralIcon icon="table" class="group-hover:text-accent" />
Airtable Airtable
</div> </div>
</a-menu-item> </a-menu-item>
@ -501,7 +513,7 @@ const setIcon = async (icon: string, table: TableType) => {
@click="openQuickImportDialog('csv', bases[0].id)" @click="openQuickImportDialog('csv', bases[0].id)"
> >
<div class="color-transition nc-project-menu-item group"> <div class="color-transition nc-project-menu-item group">
<MdiFileDocumentOutline class="group-hover:text-accent" /> <GeneralIcon icon="csv" class="group-hover:text-accent" />
CSV file CSV file
</div> </div>
</a-menu-item> </a-menu-item>
@ -512,7 +524,7 @@ const setIcon = async (icon: string, table: TableType) => {
@click="openQuickImportDialog('json', bases[0].id)" @click="openQuickImportDialog('json', bases[0].id)"
> >
<div class="color-transition nc-project-menu-item group"> <div class="color-transition nc-project-menu-item group">
<MdiCodeJson class="group-hover:text-accent" /> <GeneralIcon icon="code" class="group-hover:text-accent" />
JSON file JSON file
</div> </div>
</a-menu-item> </a-menu-item>
@ -523,7 +535,7 @@ const setIcon = async (icon: string, table: TableType) => {
@click="openQuickImportDialog('excel', bases[0].id)" @click="openQuickImportDialog('excel', bases[0].id)"
> >
<div class="color-transition nc-project-menu-item group"> <div class="color-transition nc-project-menu-item group">
<MdiFileExcel class="group-hover:text-accent" /> <GeneralIcon icon="excel" class="group-hover:text-accent" />
Microsoft Excel Microsoft Excel
</div> </div>
</a-menu-item> </a-menu-item>
@ -577,7 +589,7 @@ const setIcon = async (icon: string, table: TableType) => {
target="_blank" target="_blank"
class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)" class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)"
> >
<MdiOpenInNew class="group-hover:text-accent" /> <GeneralIcon icon="openInNew" class="group-hover:text-accent" />
<!-- Request a data source you need? --> <!-- Request a data source you need? -->
{{ $t('labels.requestDataSource') }} {{ $t('labels.requestDataSource') }}
</a> </a>
@ -658,7 +670,10 @@ const setIcon = async (icon: string, table: TableType) => {
:trigger="['click']" :trigger="['click']"
@click.stop @click.stop
> >
<MdiDotsVertical class="transition-opacity opacity-0 group-hover:opacity-100 outline-0" /> <GeneralIcon
icon="threeDotVertical"
class="transition-opacity opacity-0 group-hover:opacity-100 outline-0"
/>
<template #overlay> <template #overlay>
<a-menu class="!py-0 rounded text-sm"> <a-menu class="!py-0 rounded text-sm">
@ -725,7 +740,7 @@ const setIcon = async (icon: string, table: TableType) => {
class="group flex items-center gap-2 pl-8 pr-3 py-2 text-primary/70 hover:(text-primary/100) cursor-pointer select-none" class="group flex items-center gap-2 pl-8 pr-3 py-2 text-primary/70 hover:(text-primary/100) cursor-pointer select-none"
@click="openTableCreateDialog(bases[0].id)" @click="openTableCreateDialog(bases[0].id)"
> >
<MdiPlus /> <component :is="iconMap.plus" />
<span class="text-gray-500 group-hover:(text-primary/100) flex-1 nc-add-new-table">{{ <span class="text-gray-500 group-hover:(text-primary/100) flex-1 nc-add-new-table">{{
$t('tooltip.addTable') $t('tooltip.addTable')
@ -737,7 +752,10 @@ const setIcon = async (icon: string, table: TableType) => {
overlay-class-name="nc-dropdown-import-menu" overlay-class-name="nc-dropdown-import-menu"
@click.stop @click.stop
> >
<MdiDotsVertical class="transition-opacity opacity-0 group-hover:opacity-100 nc-import-menu outline-0" /> <component
:is="iconMap.threeDotVertical"
class="transition-opacity opacity-0 group-hover:opacity-100 nc-import-menu outline-0"
/>
<template #overlay> <template #overlay>
<a-menu class="!py-0 rounded text-sm"> <a-menu class="!py-0 rounded text-sm">
@ -749,7 +767,7 @@ const setIcon = async (icon: string, table: TableType) => {
@click="openAirtableImportDialog(bases[0].id)" @click="openAirtableImportDialog(bases[0].id)"
> >
<div class="color-transition nc-project-menu-item group"> <div class="color-transition nc-project-menu-item group">
<MdiTableLarge class="group-hover:text-accent" /> <component :is="iconMap.airtable" class="group-hover:text-accent" />
Airtable Airtable
</div> </div>
</a-menu-item> </a-menu-item>
@ -760,7 +778,7 @@ const setIcon = async (icon: string, table: TableType) => {
@click="openQuickImportDialog('csv', bases[0].id)" @click="openQuickImportDialog('csv', bases[0].id)"
> >
<div class="color-transition nc-project-menu-item group"> <div class="color-transition nc-project-menu-item group">
<MdiFileDocumentOutline class="group-hover:text-accent" /> <component :is="iconMap.csv" class="group-hover:text-accent" />
CSV file CSV file
</div> </div>
</a-menu-item> </a-menu-item>
@ -771,7 +789,7 @@ const setIcon = async (icon: string, table: TableType) => {
@click="openQuickImportDialog('json', bases[0].id)" @click="openQuickImportDialog('json', bases[0].id)"
> >
<div class="color-transition nc-project-menu-item group"> <div class="color-transition nc-project-menu-item group">
<MdiCodeJson class="group-hover:text-accent" /> <component :is="iconMap.json" class="group-hover:text-accent" />
JSON file JSON file
</div> </div>
</a-menu-item> </a-menu-item>
@ -782,7 +800,7 @@ const setIcon = async (icon: string, table: TableType) => {
@click="openQuickImportDialog('excel', bases[0].id)" @click="openQuickImportDialog('excel', bases[0].id)"
> >
<div class="color-transition nc-project-menu-item group"> <div class="color-transition nc-project-menu-item group">
<MdiFileExcel class="group-hover:text-accent" /> <component :is="iconMap.excel" class="group-hover:text-accent" />
Microsoft Excel Microsoft Excel
</div> </div>
</a-menu-item> </a-menu-item>
@ -797,7 +815,7 @@ const setIcon = async (icon: string, table: TableType) => {
target="_blank" target="_blank"
class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)" class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)"
> >
<MdiOpenInNew class="group-hover:text-accent" /> <component :is="iconMap.share" class="group-hover:text-accent" />
<!-- Request a data source you need? --> <!-- Request a data source you need? -->
{{ $t('labels.requestDataSource') }} {{ $t('labels.requestDataSource') }}
</a> </a>
@ -811,7 +829,7 @@ const setIcon = async (icon: string, table: TableType) => {
class="group flex items-center gap-2 pl-8 pr-3 py-2 text-primary/70 hover:(text-primary/100) cursor-pointer select-none" class="group flex items-center gap-2 pl-8 pr-3 py-2 text-primary/70 hover:(text-primary/100) cursor-pointer select-none"
@click="openTableCreateDialog(base.id)" @click="openTableCreateDialog(base.id)"
> >
<MdiPlus /> <component :is="iconMap.plus" />
<span class="text-gray-500 group-hover:(text-primary/100) flex-1 nc-add-new-table">{{ <span class="text-gray-500 group-hover:(text-primary/100) flex-1 nc-add-new-table">{{
$t('tooltip.addTable') $t('tooltip.addTable')
@ -823,7 +841,10 @@ const setIcon = async (icon: string, table: TableType) => {
overlay-class-name="nc-dropdown-import-menu" overlay-class-name="nc-dropdown-import-menu"
@click.stop @click.stop
> >
<MdiDotsVertical class="transition-opacity opacity-0 group-hover:opacity-100 nc-import-menu outline-0" /> <component
:is="iconMap.threeDotVertical"
class="transition-opacity opacity-0 group-hover:opacity-100 nc-import-menu outline-0"
/>
<template #overlay> <template #overlay>
<a-menu class="!py-0 rounded text-sm"> <a-menu class="!py-0 rounded text-sm">
@ -835,7 +856,7 @@ const setIcon = async (icon: string, table: TableType) => {
@click="openAirtableImportDialog(base.id)" @click="openAirtableImportDialog(base.id)"
> >
<div class="color-transition nc-project-menu-item group"> <div class="color-transition nc-project-menu-item group">
<MdiTableLarge class="group-hover:text-accent" /> <component :is="iconMap.airtable" class="group-hover:text-accent" />
Airtable Airtable
</div> </div>
</a-menu-item> </a-menu-item>
@ -846,7 +867,7 @@ const setIcon = async (icon: string, table: TableType) => {
@click="openQuickImportDialog('csv', base.id)" @click="openQuickImportDialog('csv', base.id)"
> >
<div class="color-transition nc-project-menu-item group"> <div class="color-transition nc-project-menu-item group">
<MdiFileDocumentOutline class="group-hover:text-accent" /> <component :is="iconMap.csv" class="group-hover:text-accent" />
CSV file CSV file
</div> </div>
</a-menu-item> </a-menu-item>
@ -857,7 +878,7 @@ const setIcon = async (icon: string, table: TableType) => {
@click="openQuickImportDialog('json', base.id)" @click="openQuickImportDialog('json', base.id)"
> >
<div class="color-transition nc-project-menu-item group"> <div class="color-transition nc-project-menu-item group">
<MdiCodeJson class="group-hover:text-accent" /> <component :is="iconMap.json" class="group-hover:text-accent" />
JSON file JSON file
</div> </div>
</a-menu-item> </a-menu-item>
@ -868,7 +889,7 @@ const setIcon = async (icon: string, table: TableType) => {
@click="openQuickImportDialog('excel', base.id)" @click="openQuickImportDialog('excel', base.id)"
> >
<div class="color-transition nc-project-menu-item group"> <div class="color-transition nc-project-menu-item group">
<MdiFileExcel class="group-hover:text-accent" /> <component :is="iconMap.excel" class="group-hover:text-accent" />
Microsoft Excel Microsoft Excel
</div> </div>
</a-menu-item> </a-menu-item>
@ -883,7 +904,7 @@ const setIcon = async (icon: string, table: TableType) => {
target="_blank" target="_blank"
class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)" class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)"
> >
<MdiOpenInNew class="group-hover:text-accent" /> <component :is="iconMap.openInNew" class="group-hover:text-accent" />
<!-- Request a data source you need? --> <!-- Request a data source you need? -->
{{ $t('labels.requestDataSource') }} {{ $t('labels.requestDataSource') }}
</a> </a>
@ -957,7 +978,10 @@ const setIcon = async (icon: string, table: TableType) => {
:trigger="['click']" :trigger="['click']"
@click.stop @click.stop
> >
<MdiDotsVertical class="transition-opacity opacity-0 group-hover:opacity-100 outline-0" /> <component
:is="iconMap.threeDotVertical"
class="transition-opacity opacity-0 group-hover:opacity-100 outline-0"
/>
<template #overlay> <template #overlay>
<a-menu class="!py-0 rounded text-sm"> <a-menu class="!py-0 rounded text-sm">
@ -1027,9 +1051,7 @@ const setIcon = async (icon: string, table: TableType) => {
<a-divider class="!my-0" /> <a-divider class="!my-0" />
<div class="flex items-start flex-col justify-start px-2 py-3 gap-2"> <div class="flex items-start flex-col justify-start px-2 py-3 gap-2">
<LazyGeneralAddBaseButton <LazyGeneralAddBaseButton class="color-transition py-1.5 px-2 cursor-pointer select-none hover:text-primary" />
class="color-transition py-1.5 px-2 text-primary font-bold cursor-pointer select-none hover:text-accent"
/>
<LazyGeneralHelpAndSupport class="color-transition px-2 text-gray-500 cursor-pointer select-none hover:text-accent" /> <LazyGeneralHelpAndSupport class="color-transition px-2 text-gray-500 cursor-pointer select-none hover:text-accent" />

6
packages/nc-gui/components/dashboard/settings/AppStore.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { extractSdkResponseErrorMsg, message, onMounted, useI18n, useNuxtApp } from '#imports' import { extractSdkResponseErrorMsg, iconMap, message, onMounted, useI18n, useNuxtApp } from '#imports'
const { t } = useI18n() const { t } = useI18n()
@ -125,14 +125,14 @@ onMounted(async () => {
<a-button v-if="app.parsedInput" size="small" outlined @click="showResetPluginModal(app)"> <a-button v-if="app.parsedInput" size="small" outlined @click="showResetPluginModal(app)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-reset"> <div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-reset">
<MdiCloseCircleOutline /> <component :is="iconMap.closeCircle" />
<div class="flex ml-0.5">Reset</div> <div class="flex ml-0.5">Reset</div>
</div> </div>
</a-button> </a-button>
<a-button v-else size="small" type="primary" @click="showInstallPluginModal(app)"> <a-button v-else size="small" type="primary" @click="showInstallPluginModal(app)">
<div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-install"> <div class="flex flex-row justify-center items-center caption capitalize nc-app-store-card-install">
<MdiPlus /> <component :is="iconMap.plus" />
Install Install
</div> </div>
</a-button> </a-button>

4
packages/nc-gui/components/dashboard/settings/AuditTab.vue

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Tooltip as ATooltip, Empty } from 'ant-design-vue' import { Tooltip as ATooltip, Empty } from 'ant-design-vue'
import type { AuditType } from 'nocodb-sdk' import type { AuditType } from 'nocodb-sdk'
import { h, onMounted, storeToRefs, timeAgo, useGlobal, useI18n, useNuxtApp, useProject } from '#imports' import { h, iconMap, onMounted, storeToRefs, timeAgo, useGlobal, useI18n, useNuxtApp, useProject } from '#imports'
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
@ -94,7 +94,7 @@ const columns = [
<a-button class="self-start" @click="loadAudits"> <a-button class="self-start" @click="loadAudits">
<!-- Reload --> <!-- Reload -->
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<MdiReload :class="{ 'animate-infinite animate-spin !text-success': isLoading }" /> <component :is="iconMap.reload" :class="{ 'animate-infinite animate-spin !text-success': isLoading }" />
{{ $t('general.reload') }} {{ $t('general.reload') }}
</div> </div>

24
packages/nc-gui/components/dashboard/settings/DataSources.vue

@ -255,9 +255,9 @@ watch(
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<a-tooltip v-if="metadiffbases.includes(sources[0].id)"> <a-tooltip v-if="metadiffbases.includes(sources[0].id)">
<template #title>Out of sync</template> <template #title>Out of sync</template>
<MdiDatabaseAlert class="text-lg group-hover:text-accent text-primary" /> <GeneralIcon icon="warning" class="group-hover:text-accent text-primary" />
</a-tooltip> </a-tooltip>
<MdiDatabaseSync v-else class="text-lg group-hover:text-accent" /> <GeneralIcon v-else icon="sync" class="group-hover:text-accent" />
Sync Metadata Sync Metadata
</div> </div>
</a-button> </a-button>
@ -266,7 +266,7 @@ watch(
@click="baseAction(sources[0].id, DataSourcesSubTab.UIAcl)" @click="baseAction(sources[0].id, DataSourcesSubTab.UIAcl)"
> >
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<MdiDatabaseLockOutline class="text-lg group-hover:text-accent" /> <GeneralIcon icon="acl" class="group-hover:text-accent" />
UI ACL UI ACL
</div> </div>
</a-button> </a-button>
@ -275,7 +275,7 @@ watch(
@click="baseAction(sources[0].id, DataSourcesSubTab.ERD)" @click="baseAction(sources[0].id, DataSourcesSubTab.ERD)"
> >
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<MdiGraphOutline class="text-lg group-hover:text-accent" /> <GeneralIcon icon="erd" class="group-hover:text-accent" />
ERD ERD
</div> </div>
</a-button> </a-button>
@ -285,7 +285,7 @@ watch(
@click="baseAction(sources[0].id, DataSourcesSubTab.Edit)" @click="baseAction(sources[0].id, DataSourcesSubTab.Edit)"
> >
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<MdiEditOutline class="text-lg group-hover:text-accent" /> <GeneralIcon icon="edit" class="group-hover:text-accent" />
Edit Edit
</div> </div>
</a-button> </a-button>
@ -308,7 +308,7 @@ watch(
<template #item="{ element: base, index }"> <template #item="{ element: base, index }">
<div v-if="index !== 0" class="ds-table-row border-gray-200"> <div v-if="index !== 0" class="ds-table-row border-gray-200">
<div class="ds-table-col ds-table-name"> <div class="ds-table-col ds-table-name">
<MdiDragVertical v-if="sources.length > 2" small class="ds-table-handle" /> <GeneralIcon v-if="sources.length > 2" icon="dragVertical" small class="ds-table-handle" />
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<GeneralBaseLogo :base-type="base.type" /> <GeneralBaseLogo :base-type="base.type" />
{{ base.is_meta ? 'BASE' : base.alias }} <span class="text-gray-400 text-xs">({{ base.type }})</span> {{ base.is_meta ? 'BASE' : base.alias }} <span class="text-gray-400 text-xs">({{ base.type }})</span>
@ -324,9 +324,9 @@ watch(
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<a-tooltip v-if="metadiffbases.includes(base.id)"> <a-tooltip v-if="metadiffbases.includes(base.id)">
<template #title>Out of sync</template> <template #title>Out of sync</template>
<MdiDatabaseAlert class="text-lg group-hover:text-accent text-primary" /> <GeneralIcon icon="warning" class="group-hover:text-accent text-primary" />
</a-tooltip> </a-tooltip>
<MdiDatabaseSync v-else class="text-lg group-hover:text-accent" /> <GeneralIcon v-else icon="sync" class="group-hover:text-accent" />
Sync Metadata Sync Metadata
</div> </div>
</a-button> </a-button>
@ -335,13 +335,13 @@ watch(
@click="baseAction(base.id, DataSourcesSubTab.UIAcl)" @click="baseAction(base.id, DataSourcesSubTab.UIAcl)"
> >
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<MdiDatabaseLockOutline class="text-lg group-hover:text-accent" /> <GeneralIcon icon="acl" class="group-hover:text-accent" />
UI ACL UI ACL
</div> </div>
</a-button> </a-button>
<a-button class="nc-action-btn cursor-pointer outline-0" @click="baseAction(base.id, DataSourcesSubTab.ERD)"> <a-button class="nc-action-btn cursor-pointer outline-0" @click="baseAction(base.id, DataSourcesSubTab.ERD)">
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<MdiGraphOutline class="text-lg group-hover:text-accent" /> <GeneralIcon icon="erd" class="group-hover:text-accent" />
ERD ERD
</div> </div>
</a-button> </a-button>
@ -351,13 +351,13 @@ watch(
@click="baseAction(base.id, DataSourcesSubTab.Edit)" @click="baseAction(base.id, DataSourcesSubTab.Edit)"
> >
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<MdiEditOutline class="text-lg group-hover:text-accent" /> <GeneralIcon icon="edit" class="group-hover:text-accent" />
Edit Edit
</div> </div>
</a-button> </a-button>
<a-button v-if="!base.is_meta" class="nc-action-btn cursor-pointer outline-0" @click="deleteBase(base)"> <a-button v-if="!base.is_meta" class="nc-action-btn cursor-pointer outline-0" @click="deleteBase(base)">
<div class="flex items-center gap-2 text-red-500 font-light"> <div class="flex items-center gap-2 text-red-500 font-light">
<MdiDeleteOutline class="text-lg group-hover:text-accent" /> <GeneralIcon icon="delete" class="group-hover:text-accent" />
Delete Delete
</div> </div>
</a-button> </a-button>

6
packages/nc-gui/components/dashboard/settings/Metadata.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { Empty, extractSdkResponseErrorMsg, h, message, storeToRefs, useI18n, useNuxtApp, useProject } from '#imports' import { Empty, extractSdkResponseErrorMsg, h, iconMap, message, storeToRefs, useI18n, useNuxtApp, useProject } from '#imports'
const props = defineProps<{ const props = defineProps<{
baseId: string baseId: string
@ -92,7 +92,7 @@ const columns = [
<!-- Reload --> <!-- Reload -->
<a-button v-e="['a:proj-meta:meta-data:reload']" class="self-start nc-btn-metasync-reload" @click="loadMetaDiff"> <a-button v-e="['a:proj-meta:meta-data:reload']" class="self-start nc-btn-metasync-reload" @click="loadMetaDiff">
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<MdiReload :class="{ 'animate-infinite animate-spin !text-success': isLoading }" /> <component :is="iconMap.reload" :class="{ 'animate-infinite animate-spin !text-success': isLoading }" />
{{ $t('general.reload') }} {{ $t('general.reload') }}
</div> </div>
</a-button> </a-button>
@ -135,7 +135,7 @@ const columns = [
<div v-if="isDifferent"> <div v-if="isDifferent">
<a-button v-e="['a:proj-meta:meta-data:sync']" class="nc-btn-metasync-sync-now" type="primary" @click="syncMetaDiff"> <a-button v-e="['a:proj-meta:meta-data:sync']" class="nc-btn-metasync-sync-now" type="primary" @click="syncMetaDiff">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<MdiDatabaseSync /> <component :is="iconMap.databaseSync" />
{{ $t('activity.metaSync') }} {{ $t('activity.metaSync') }}
</div> </div>
</a-button> </a-button>

20
packages/nc-gui/components/dashboard/settings/Modal.vue

@ -2,11 +2,7 @@
import type { FunctionalComponent, SVGAttributes } from 'vue' import type { FunctionalComponent, SVGAttributes } from 'vue'
import DataSources from './DataSources.vue' import DataSources from './DataSources.vue'
import Misc from './Misc.vue' import Misc from './Misc.vue'
import { DataSourcesSubTab, useI18n, useNuxtApp, useUIPermission, useVModel, watch } from '#imports' import { DataSourcesSubTab, iconMap, useI18n, useNuxtApp, useUIPermission, useVModel, watch } from '#imports'
import TeamFillIcon from '~icons/ri/team-fill'
import MultipleTableIcon from '~icons/mdi/table-multiple'
import NotebookOutline from '~icons/mdi/notebook-outline'
import FolderCog from '~icons/mdi/folder-cog'
interface Props { interface Props {
modelValue: boolean modelValue: boolean
@ -54,7 +50,7 @@ const dataSourcesAwakened = ref(false)
const tabsInfo: TabGroup = { const tabsInfo: TabGroup = {
teamAndAuth: { teamAndAuth: {
title: t('title.teamAndAuth'), title: t('title.teamAndAuth'),
icon: TeamFillIcon, icon: iconMap.users,
subTabs: { subTabs: {
...(isUIAllowed('userMgmtTab') ...(isUIAllowed('userMgmtTab')
? { ? {
@ -82,7 +78,7 @@ const tabsInfo: TabGroup = {
dataSources: { dataSources: {
// Data Sources // Data Sources
title: 'Data Sources', title: 'Data Sources',
icon: MultipleTableIcon, icon: iconMap.datasource,
subTabs: { subTabs: {
dataSources: { dataSources: {
title: 'Data Sources', title: 'Data Sources',
@ -97,7 +93,7 @@ const tabsInfo: TabGroup = {
audit: { audit: {
// Audit // Audit
title: t('title.audit'), title: t('title.audit'),
icon: NotebookOutline, icon: iconMap.book,
subTabs: { subTabs: {
audit: { audit: {
// Audit // Audit
@ -112,7 +108,7 @@ const tabsInfo: TabGroup = {
projectSettings: { projectSettings: {
// Project Settings // Project Settings
title: 'Project Settings', title: 'Project Settings',
icon: FolderCog, icon: iconMap.settings,
subTabs: { subTabs: {
misc: { misc: {
// Misc // Misc
@ -174,7 +170,7 @@ watch(
data-testid="settings-modal-close-button" data-testid="settings-modal-close-button"
@click="vModel = false" @click="vModel = false"
> >
<MdiClose class="cursor-pointer nc-modal-close w-4" /> <component :is="iconMap.close" class="cursor-pointer nc-modal-close w-4" />
</a-button> </a-button>
</div> </div>
@ -231,7 +227,7 @@ watch(
@click="vDataState = DataSourcesSubTab.New" @click="vDataState = DataSourcesSubTab.New"
> >
<div v-if="vDataState === ''" class="flex items-center gap-2 text-primary font-light"> <div v-if="vDataState === ''" class="flex items-center gap-2 text-primary font-light">
<MdiDatabasePlusOutline class="text-lg group-hover:text-accent" /> <component :is="iconMap.plusCircle" class="text-lg group-hover:text-accent" />
New New
</div> </div>
</a-button> </a-button>
@ -242,7 +238,7 @@ watch(
@click="dataSourcesReload = true" @click="dataSourcesReload = true"
> >
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<MdiReload :class="{ 'animate-infinite animate-spin !text-success': dataSourcesReload }" /> <component :is="iconMap.reload" :class="{ 'animate-infinite animate-spin !text-success': dataSourcesReload }" />
{{ $t('general.reload') }} {{ $t('general.reload') }}
</div> </div>
</a-button> </a-button>

7
packages/nc-gui/components/dashboard/settings/UIAcl.vue

@ -4,6 +4,7 @@ import {
computed, computed,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
h, h,
iconMap,
message, message,
onMounted, onMounted,
storeToRefs, storeToRefs,
@ -120,20 +121,20 @@ const columns = [
<div class="flex flex-row items-center w-full mb-4 gap-2"> <div class="flex flex-row items-center w-full mb-4 gap-2">
<a-input v-model:value="searchInput" placeholder="Search models" class="nc-acl-search"> <a-input v-model:value="searchInput" placeholder="Search models" class="nc-acl-search">
<template #prefix> <template #prefix>
<MdiMagnify /> <component :is="iconMap.search" />
</template> </template>
</a-input> </a-input>
<a-button class="self-start nc-acl-reload" @click="loadTableList"> <a-button class="self-start nc-acl-reload" @click="loadTableList">
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<MdiReload :class="{ 'animate-infinite animate-spin !text-success': isLoading }" /> <component :is="iconMap.reload" :class="{ 'animate-infinite animate-spin !text-success': isLoading }" />
Reload Reload
</div> </div>
</a-button> </a-button>
<a-button class="self-start nc-acl-save" @click="saveUIAcl"> <a-button class="self-start nc-acl-save" @click="saveUIAcl">
<div class="flex items-center gap-2 text-gray-600 font-light"> <div class="flex items-center gap-2 text-gray-600 font-light">
<MdiContentSave /> <component :is="iconMap.save" />
Save Save
</div> </div>
</a-button> </a-button>

10
packages/nc-gui/components/dashboard/settings/app-store/AppInstall.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { PluginTestReqType, PluginType } from 'nocodb-sdk' import type { PluginTestReqType, PluginType } from 'nocodb-sdk'
import { extractSdkResponseErrorMsg, message, onMounted, ref, useI18n, useNuxtApp } from '#imports' import { extractSdkResponseErrorMsg, iconMap, message, onMounted, ref, useI18n, useNuxtApp } from '#imports'
const { id } = defineProps<{ const { id } = defineProps<{
id: string id: string
@ -209,7 +209,11 @@ onMounted(async () => {
v-if="itemIndex !== 0 && columnIndex === plugin.formDetails.items.length - 1" v-if="itemIndex !== 0 && columnIndex === plugin.formDetails.items.length - 1"
class="absolute flex flex-col justify-start mt-2 -right-6 top-0" class="absolute flex flex-col justify-start mt-2 -right-6 top-0"
> >
<MdiDeleteOutline class="hover:text-red-400 cursor-pointer" @click="deleteFormRow(itemIndex)" /> <component
:is="iconMap.delete"
class="hover:text-red-400 cursor-pointer"
@click="deleteFormRow(itemIndex)"
/>
</div> </div>
</a-form-item> </a-form-item>
</td> </td>
@ -220,7 +224,7 @@ onMounted(async () => {
<td :colspan="plugin.formDetails.items.length" class="text-center"> <td :colspan="plugin.formDetails.items.length" class="text-center">
<a-button type="default" class="!bg-gray-100 rounded-md border-none mr-1" @click="addSetting"> <a-button type="default" class="!bg-gray-100 rounded-md border-none mr-1" @click="addSetting">
<template #icon> <template #icon>
<MdiPlus class="flex mx-auto" /> <component :is="iconMap.plus" class="flex mx-auto" />
</template> </template>
</a-button> </a-button>
</td> </td>

9
packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue

@ -15,6 +15,7 @@ import {
generateUniqueName, generateUniqueName,
getDefaultConnectionConfig, getDefaultConnectionConfig,
getTestDatabaseName, getTestDatabaseName,
iconMap,
nextTick, nextTick,
onMounted, onMounted,
projectTitleValidator, projectTitleValidator,
@ -565,11 +566,15 @@ watch(
<a-input v-model:value="item.value" /> <a-input v-model:value="item.value" />
<MdiClose :style="{ 'font-size': '1.5em', 'color': 'red' }" @click="removeParam(index)" /> <component
:is="iconMap.close"
:style="{ 'font-size': '1.5em', 'color': 'red' }"
@click="removeParam(index)"
/>
</div> </div>
</div> </div>
<a-button type="dashed" class="w-full caption mt-2" @click="addNewParam"> <a-button type="dashed" class="w-full caption mt-2" @click="addNewParam">
<div class="flex items-center justify-center"><MdiPlus /></div> <div class="flex items-center justify-center"><component :is="iconMap.plus" /></div>
</a-button> </a-button>
</a-card> </a-card>
</a-form-item> </a-form-item>

11
packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue

@ -15,6 +15,7 @@ import {
fieldRequiredValidator, fieldRequiredValidator,
getDefaultConnectionConfig, getDefaultConnectionConfig,
getTestDatabaseName, getTestDatabaseName,
iconMap,
onMounted, onMounted,
projectTitleValidator, projectTitleValidator,
readFile, readFile,
@ -537,11 +538,15 @@ onMounted(async () => {
<a-input v-model:value="item.value" /> <a-input v-model:value="item.value" />
<MdiClose :style="{ 'font-size': '1.5em', 'color': 'red' }" @click="removeParam(index)" /> <component
:is="iconMap.close"
:style="{ 'font-size': '1.5em', 'color': 'red' }"
@click="removeParam(index)"
/>
</div> </div>
</div> </div>
<a-button type="dashed" class="w-full caption mt-2" @click="addNewParam"> <a-button type="dashed" class="w-full caption mt-2" @click="addNewParam">
<div class="flex items-center justify-center"><MdiPlus /></div> <div class="flex items-center justify-center"><component :is="iconMap.plus" /></div>
</a-button> </a-button>
</a-card> </a-card>
</a-form-item> </a-form-item>
@ -588,7 +593,7 @@ onMounted(async () => {
</div> </div>
</a-form-item> </a-form-item>
<div class="w-full flex items-center mt-2 text-[#e65100]"> <div class="w-full flex items-center mt-2 text-[#e65100]">
<MdiWarning class="mr-1" /> <component :is="iconMap.warning" class="mr-1" />
Please make sure database you are trying to connect is valid! This operation can cause schema loss!! Please make sure database you are trying to connect is valid! This operation can cause schema loss!!
</div> </div>
</a-form> </a-form>

5
packages/nc-gui/components/dlg/AirtableImport.vue

@ -7,6 +7,7 @@ import {
computed, computed,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
fieldRequiredValidator, fieldRequiredValidator,
iconMap,
message, message,
nextTick, nextTick,
onBeforeUnmount, onBeforeUnmount,
@ -407,7 +408,7 @@ onBeforeUnmount(() => {
<a-card ref="logRef" :body-style="{ backgroundColor: '#000000', height: '400px', overflow: 'auto' }"> <a-card ref="logRef" :body-style="{ backgroundColor: '#000000', height: '400px', overflow: 'auto' }">
<div v-for="({ msg, status }, i) in progress" :key="i"> <div v-for="({ msg, status }, i) in progress" :key="i">
<div v-if="status === 'FAILED'" class="flex items-center"> <div v-if="status === 'FAILED'" class="flex items-center">
<MdiCloseCircleOutline class="text-red-500" /> <component :is="iconMap.closeCircle" class="text-red-500" />
<span class="text-red-500 ml-2">{{ msg }}</span> <span class="text-red-500 ml-2">{{ msg }}</span>
</div> </div>
@ -428,7 +429,7 @@ onBeforeUnmount(() => {
class="flex items-center" class="flex items-center"
> >
<!-- Importing --> <!-- Importing -->
<MdiLoading class="text-green-500 animate-spin" /> <component :is="iconMap.loading" class="text-green-500 animate-spin" />
<span class="text-green-500 ml-2"> {{ $t('labels.importing') }}</span> <span class="text-green-500 ml-2"> {{ $t('labels.importing') }}</span>
</div> </div>
</a-card> </a-card>

9
packages/nc-gui/components/dlg/QuickImport.vue

@ -12,6 +12,7 @@ import {
computed, computed,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
fieldRequiredValidator, fieldRequiredValidator,
iconMap,
importCsvUrlValidator, importCsvUrlValidator,
importExcelUrlValidator, importExcelUrlValidator,
importUrlValidator, importUrlValidator,
@ -379,7 +380,7 @@ const beforeUpload = (file: UploadFile) => {
<template #tab> <template #tab>
<!-- Upload --> <!-- Upload -->
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<MdiFileUploadOutline /> <component :is="iconMap.fileUpload" />
{{ $t('general.upload') }} {{ $t('general.upload') }}
</div> </div>
</template> </template>
@ -398,7 +399,7 @@ const beforeUpload = (file: UploadFile) => {
@change="handleChange" @change="handleChange"
@reject="rejectDrop" @reject="rejectDrop"
> >
<MdiFilePlusOutline size="large" /> <component :is="iconMap.plusCircle" size="large" />
<!-- Click or drag file to this area to upload --> <!-- Click or drag file to this area to upload -->
<p class="ant-upload-text">{{ $t('msg.info.import.clickOrDrag') }}</p> <p class="ant-upload-text">{{ $t('msg.info.import.clickOrDrag') }}</p>
@ -413,7 +414,7 @@ const beforeUpload = (file: UploadFile) => {
<a-tab-pane v-if="isImportTypeJson" key="jsonEditorTab" :closable="false"> <a-tab-pane v-if="isImportTypeJson" key="jsonEditorTab" :closable="false">
<template #tab> <template #tab>
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
<MdiCodeJson /> <component :is="iconMap.json" />
JSON Editor JSON Editor
</span> </span>
</template> </template>
@ -426,7 +427,7 @@ const beforeUpload = (file: UploadFile) => {
<a-tab-pane v-else key="urlTab" :closable="false"> <a-tab-pane v-else key="urlTab" :closable="false">
<template #tab> <template #tab>
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
<MdiLinkVariant /> <component :is="iconMap.link" />
URL URL
</span> </span>
</template> </template>

18
packages/nc-gui/components/dlg/TableCreate.vue

@ -1,5 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { Form, computed, nextTick, onMounted, ref, useProject, useTable, useTabs, useVModel, validateTableName } from '#imports' import {
Form,
computed,
iconMap,
nextTick,
onMounted,
ref,
useProject,
useTable,
useTabs,
useVModel,
validateTableName,
} from '#imports'
import { TabType } from '~/lib' import { TabType } from '~/lib'
const props = defineProps<{ const props = defineProps<{
@ -143,8 +155,8 @@ onMounted(() => {
<div class="pointer flex flex-row items-center gap-x-1" @click="isAdvanceOptVisible = !isAdvanceOptVisible"> <div class="pointer flex flex-row items-center gap-x-1" @click="isAdvanceOptVisible = !isAdvanceOptVisible">
{{ isAdvanceOptVisible ? $t('general.hideAll') : $t('general.showMore') }} {{ isAdvanceOptVisible ? $t('general.hideAll') : $t('general.showMore') }}
<MdiMinusCircleOutline v-if="isAdvanceOptVisible" class="text-gray-500" /> <component :is="iconMap.minusCircle" v-if="isAdvanceOptVisible" class="text-gray-500" />
<MdiPlusCircleOutline v-else class="text-gray-500" /> <component :is="iconMap.plusCircle" v-else class="text-gray-500" />
</div> </div>
</div> </div>
<div class="nc-table-advanced-options" :class="{ active: isAdvanceOptVisible }"> <div class="nc-table-advanced-options" :class="{ active: isAdvanceOptVisible }">

5
packages/nc-gui/components/erd/HistogramPanel.vue

@ -1,17 +1,18 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Panel, PanelPosition } from '@vue-flow/additional-components' import { Panel, PanelPosition } from '@vue-flow/additional-components'
import { iconMap } from '#imports'
</script> </script>
<template> <template>
<Panel class="text-xs bg-white border-1 rounded border-gray-200 z-50 nc-erd-histogram" :position="PanelPosition.BottomRight"> <Panel class="text-xs bg-white border-1 rounded border-gray-200 z-50 nc-erd-histogram" :position="PanelPosition.BottomRight">
<div class="flex flex-col divide-y-1"> <div class="flex flex-col divide-y-1">
<div class="flex items-center gap-1 p-2"> <div class="flex items-center gap-1 p-2">
<MdiTableLarge class="text-primary" /> <component :is="iconMap.table" class="text-primary" />
<div>{{ $t('objects.table') }}</div> <div>{{ $t('objects.table') }}</div>
</div> </div>
<div class="flex items-center gap-1 p-2"> <div class="flex items-center gap-1 p-2">
<MdiEyeCircleOutline class="text-primary" /> <component :is="iconMap.eye" class="text-primary" />
<div>{{ $t('objects.sqlVIew') }}</div> <div>{{ $t('objects.sqlVIew') }}</div>
</div> </div>
</div> </div>

4
packages/nc-gui/components/general/AddBaseButton.vue

@ -1,4 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { iconMap } from '#imports'
const { isUIAllowed } = useUIPermission() const { isUIAllowed } = useUIPermission()
const { t } = useI18n() const { t } = useI18n()
@ -14,7 +16,7 @@ const toggleDialog = inject(ToggleDialogInj, () => {})
> >
<div> <div>
<div class="flex items-center space-x-1"> <div class="flex items-center space-x-1">
<RiTeamFill class="mr-1 nc-new-base" /> <component :is="iconMap.users" class="mr-1 nc-new-base" />
<div>{{ t('title.teamAndSettings') }}</div> <div>{{ t('title.teamAndSettings') }}</div>
</div> </div>
</div> </div>

4
packages/nc-gui/components/general/HelpAndSupport.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, storeToRefs, useGlobal, useProject, useRoute } from '#imports' import { iconMap, ref, storeToRefs, useGlobal, useProject, useRoute } from '#imports'
const showDrawer = ref(false) const showDrawer = ref(false)
@ -19,7 +19,7 @@ const openSwaggerLink = () => {
class="flex items-center space-x-1 w-full cursor-pointer pl-3 py-1.5 hover:(text-primary bg-primary bg-opacity-5)" class="flex items-center space-x-1 w-full cursor-pointer pl-3 py-1.5 hover:(text-primary bg-primary bg-opacity-5)"
@click="showDrawer = true" @click="showDrawer = true"
> >
<MdiCommentTextOutline class="mr-1" /> <component :is="iconMap.apiAndSupport" class="mr-1" />
<!-- APIs & Support --> <!-- APIs & Support -->
<div>{{ $t('title.APIsAndSupport') }}</div> <div>{{ $t('title.APIsAndSupport') }}</div>

11
packages/nc-gui/components/general/Icon.vue

@ -0,0 +1,11 @@
<script lang="ts" setup>
import { iconMap } from '#imports'
const props = defineProps<{
icon: keyof typeof iconMap
}>()
</script>
<template>
<component :is="iconMap[props.icon]" />
</template>

6
packages/nc-gui/components/general/JoinCloud.vue

@ -1,3 +1,7 @@
<script lang="ts" setup>
import { iconMap } from '#imports'
</script>
<template> <template>
<a <a
v-e="['c:navbar:join-cloud']" v-e="['c:navbar:join-cloud']"
@ -5,7 +9,7 @@
href="https://docs.google.com/forms/d/e/1FAIpQLSfKLe8Rcrq0uo2_jM5W1kbVBbzDiQ3IvlP8Iov61FTekVAvzA/viewform?usp=pp_url" href="https://docs.google.com/forms/d/e/1FAIpQLSfKLe8Rcrq0uo2_jM5W1kbVBbzDiQ3IvlP8Iov61FTekVAvzA/viewform?usp=pp_url"
target="_blank" target="_blank"
> >
<PhCloudLightningDuotone class="mr-1" /> <component :is="iconMap.cloud" class="mr-1" />
Join NocoDB Cloud Join NocoDB Cloud
</a> </a>
</template> </template>

14
packages/nc-gui/components/general/MiniSidebar.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, navigateTo, storeToRefs, useGlobal, useProject, useRoute, useSidebar } from '#imports' import { computed, iconMap, navigateTo, storeToRefs, useGlobal, useProject, useRoute, useSidebar } from '#imports'
const { signOut, signedIn, user, currentVersion } = useGlobal() const { signOut, signedIn, user, currentVersion } = useGlobal()
@ -42,7 +42,7 @@ const logout = async () => {
<a-menu-item-group title="User Settings"> <a-menu-item-group title="User Settings">
<a-menu-item key="email" class="!rounded-t"> <a-menu-item key="email" class="!rounded-t">
<nuxt-link v-e="['c:navbar:user:email']" class="group flex items-center no-underline py-2" to="/user"> <nuxt-link v-e="['c:navbar:user:email']" class="group flex items-center no-underline py-2" to="/user">
<MdiAt class="mt-1 group-hover:text-success" /> <component :is="iconMap.at" class="mt-1 group-hover:text-success" />
&nbsp; &nbsp;
<span class="prose group-hover:text-black nc-user-menu-email">{{ email }}</span> <span class="prose group-hover:text-black nc-user-menu-email">{{ email }}</span>
</nuxt-link> </nuxt-link>
@ -52,7 +52,7 @@ const logout = async () => {
<a-menu-item key="signout" class="!rounded-b"> <a-menu-item key="signout" class="!rounded-b">
<div v-e="['a:navbar:user:sign-out']" class="group flex items-center py-2" @click="logout"> <div v-e="['a:navbar:user:sign-out']" class="group flex items-center py-2" @click="logout">
<MdiLogout class="group-hover:(!text-red-500)" />&nbsp; <component :is="iconMap.signout" class="group-hover:(!text-red-500)" />&nbsp;
<span class="prose font-semibold text-gray-500 group-hover:text-black nc-user-menu-signout"> <span class="prose font-semibold text-gray-500 group-hover:text-black nc-user-menu-signout">
{{ $t('general.signOut') }} {{ $t('general.signOut') }}
</span> </span>
@ -66,7 +66,7 @@ const logout = async () => {
<div id="sidebar" ref="sidebar" class="text-white flex-auto flex flex-col items-center w-full"> <div id="sidebar" ref="sidebar" class="text-white flex-auto flex flex-col items-center w-full">
<a-dropdown :trigger="['contextmenu']" placement="right" overlay-class-name="nc-dropdown"> <a-dropdown :trigger="['contextmenu']" placement="right" overlay-class-name="nc-dropdown">
<div :class="[route.name === 'index' ? 'active' : '']" class="nc-mini-sidebar-item" @click="navigateTo('/')"> <div :class="[route.name === 'index' ? 'active' : '']" class="nc-mini-sidebar-item" @click="navigateTo('/')">
<MdiFolder class="cursor-pointer transform hover:scale-105 text-2xl" /> <component :is="iconMap.folder" class="cursor-pointer transform hover:scale-105 text-2xl" />
</div> </div>
<template #overlay> <template #overlay>
@ -84,7 +84,7 @@ const logout = async () => {
class="group flex items-center gap-2 py-2 hover:text-primary" class="group flex items-center gap-2 py-2 hover:text-primary"
@click="navigateTo('/project/create')" @click="navigateTo('/project/create')"
> >
<MdiPlus class="text-lg group-hover:text-accent" /> <component :is="iconMap.plus" class="text-lg group-hover:text-accent" />
{{ $t('activity.createProject') }} {{ $t('activity.createProject') }}
</div> </div>
</a-menu-item> </a-menu-item>
@ -95,7 +95,7 @@ const logout = async () => {
class="group flex items-center gap-2 py-2 hover:text-primary" class="group flex items-center gap-2 py-2 hover:text-primary"
@click="navigateTo('/project/create-external')" @click="navigateTo('/project/create-external')"
> >
<MdiDatabaseOutline class="text-lg group-hover:text-accent" /> <component :is="iconMap.database" class="text-lg group-hover:text-accent" />
<div v-html="$t('activity.createProjectExtended.extDB')" /> <div v-html="$t('activity.createProjectExtended.extDB')" />
</div> </div>
</a-menu-item> </a-menu-item>
@ -112,7 +112,7 @@ const logout = async () => {
class="nc-mini-sidebar-item" class="nc-mini-sidebar-item"
@click="navigateTo(`/${route.params.projectType}/${route.params.projectId}`)" @click="navigateTo(`/${route.params.projectType}/${route.params.projectId}`)"
> >
<MdiDatabase class="cursor-pointer transform hover:scale-105 text-2xl" /> <component :is="iconMap.database" class="cursor-pointer transform hover:scale-105 text-2xl" />
</div> </div>
</a-tooltip> </a-tooltip>
</div> </div>

19
packages/nc-gui/components/general/PreviewAs.vue

@ -1,10 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onUnmounted, ref, useEventListener, useGlobal, useI18n, useNuxtApp, watch } from '#imports' import { iconMap, onUnmounted, ref, useEventListener, useGlobal, useI18n, useNuxtApp, watch } from '#imports'
import MdiAccountStar from '~icons/mdi/account-star' import MdiAccountStar from '~icons/mdi/account-star'
import MdiAccountHardHat from '~icons/mdi/account-hard-hat' import MdiAccountHardHat from '~icons/mdi/account-hard-hat'
import MdiAccountEdit from '~icons/mdi/account-edit' import PhPencilCircleThin from '~icons/ph/pencil-circle-thin'
import MdiEyeOutline from '~icons/mdi/eye-outline' import PhChtTeardropTextThin from '~icons/ph/chat-teardrop-text-thin'
import MdiCommentAccountOutline from '~icons/mdi/comment-account-outline'
import { ProjectRole } from '~/lib' import { ProjectRole } from '~/lib'
const { float } = defineProps<{ float?: boolean }>() const { float } = defineProps<{ float?: boolean }>()
@ -24,9 +23,9 @@ const roleList = [
const roleIcon = { const roleIcon = {
owner: MdiAccountStar, owner: MdiAccountStar,
creator: MdiAccountHardHat, creator: MdiAccountHardHat,
editor: MdiAccountEdit, editor: PhPencilCircleThin,
viewer: MdiEyeOutline, viewer: iconMap.eye,
commenter: MdiCommentAccountOutline, commenter: PhChtTeardropTextThin,
} }
const position = ref({ const position = ref({
@ -65,7 +64,7 @@ watch(previewAs, (newRole) => {
class="floating-reset-btn nc-floating-preview-btn p-4" class="floating-reset-btn nc-floating-preview-btn p-4"
:style="{ top: position.y, left: position.x }" :style="{ top: position.y, left: position.x }"
> >
<MdiDrag class="cursor-move text-white" @mousedown="mouseDown" /> <component :is="iconMap.drag" class="cursor-move text-white" @mousedown="mouseDown" />
<div class="divider" /> <div class="divider" />
@ -83,7 +82,7 @@ watch(previewAs, (newRole) => {
<!-- Close --> <!-- Close -->
<div class="flex items-center gap-2 cursor-pointer nc-preview-btn-exit-to-app" @click="previewAs = null"> <div class="flex items-center gap-2 cursor-pointer nc-preview-btn-exit-to-app" @click="previewAs = null">
<MdiExitToApp /> <component :is="iconMap.exit" />
{{ $t('general.close') }} {{ $t('general.close') }}
</div> </div>
</div> </div>
@ -105,7 +104,7 @@ watch(previewAs, (newRole) => {
<template v-if="previewAs"> <template v-if="previewAs">
<a-menu-item @click="previewAs = null"> <a-menu-item @click="previewAs = null">
<div class="nc-project-menu-item group"> <div class="nc-project-menu-item group">
<MdiClose class="group-hover:text-accent" /> <component :is="iconMap.close" class="group-hover:text-accent" />
<!-- Reset Preview --> <!-- Reset Preview -->
<span class="text-capitalize text-xs whitespace-nowrap"> <span class="text-capitalize text-xs whitespace-nowrap">
{{ $t('activity.resetReview') }} {{ $t('activity.resetReview') }}

4
packages/nc-gui/components/general/ShareBaseButton.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { isDrawerOrModalExist, isMac, useNuxtApp, useRoute, useUIPermission } from '#imports' import { iconMap, isDrawerOrModalExist, isMac, useNuxtApp, useRoute, useUIPermission } from '#imports'
const route = useRoute() const route = useRoute()
@ -45,7 +45,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
</template> </template>
<a-button type="primary" class="!rounded-md mr-1" size="medium"> <a-button type="primary" class="!rounded-md mr-1" size="medium">
<div class="flex items-center space-x-1 cursor-pointer text-xs font-weight-bold"> <div class="flex items-center space-x-1 cursor-pointer text-xs font-weight-bold">
<MdiAccountPlusOutline class="mr-1 nc-share-base hover:text-accent text-sm" /> <component :is="iconMap.accountPlus" class="mr-1 nc-share-base hover:text-accent text-sm" />
{{ $t('activity.share') }} {{ $t('activity.share') }}
</div> </div>
</a-button> </a-button>

26
packages/nc-gui/components/general/Social.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useI18n } from '#imports' import { iconMap, useI18n } from '#imports'
const { locale } = useI18n() const { locale } = useI18n()
@ -19,7 +19,12 @@ const isZhLang = $computed(() => locale.value.startsWith('zh'))
/> />
<div v-else class="flex justify-between gap-1 w-full px-2"> <div v-else class="flex justify-between gap-1 w-full px-2">
<MdiDiscord v-e="['e:community:discord']" class="icon text-[#7289DA]" @click="open('https://discord.gg/5RgZmkW')" /> <component
:is="iconMap.discord"
v-e="['e:community:discord']"
class="icon text-[#7289DA]"
@click="open('https://discord.gg/5RgZmkW')"
/>
<div <div
v-e="['e:community:discourse']" v-e="['e:community:discourse']"
@ -29,11 +34,22 @@ const isZhLang = $computed(() => locale.value.startsWith('zh'))
<div class="discourse" /> <div class="discourse" />
</div> </div>
<MdiReddit v-e="['e:community:reddit']" class="icon text-[#FF4600]" @click="open('https://www.reddit.com/r/NocoDB/')" /> <component
:is="iconMap.reddit"
v-e="['e:community:reddit']"
class="icon text-[#FF4600]"
@click="open('https://www.reddit.com/r/NocoDB/')"
/>
<MdiTwitter v-e="['e:community:twitter']" class="icon text-[#1DA1F2]" @click="open('https://twitter.com/NocoDB')" /> <component
:is="iconMap.twitter"
v-e="['e:community:twitter']"
class="icon text-[#1DA1F2]"
@click="open('https://twitter.com/NocoDB')"
/>
<MdiCalendarMonth <component
:is="iconMap.calendar"
v-e="['e:community:book-demo']" v-e="['e:community:book-demo']"
class="icon text-green-500" class="icon text-green-500"
@click="open('https://calendly.com/nocodb-meeting')" @click="open('https://calendly.com/nocodb-meeting')"

18
packages/nc-gui/components/general/SocialCard.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { enumColor as colors, useDialog, useGlobal, useNuxtApp } from '#imports' import { enumColor as colors, iconMap, useDialog, useGlobal, useNuxtApp } from '#imports'
const { $e } = useNuxtApp() const { $e } = useNuxtApp()
@ -39,7 +39,7 @@ function openKeyboardShortcutDialog() {
to="https://docs.nocodb.com/" to="https://docs.nocodb.com/"
> >
<div class="ml-3 flex items-center text-sm"> <div class="ml-3 flex items-center text-sm">
<MdiBookOpenOutline class="text-lg text-accent" /> <component :is="iconMap.book" class="text-lg text-accent" />
<span class="ml-3">{{ $t('labels.documentation') }}</span> <span class="ml-3">{{ $t('labels.documentation') }}</span>
</div> </div>
</nuxt-link> </nuxt-link>
@ -55,7 +55,7 @@ function openKeyboardShortcutDialog() {
to="https://apis.nocodb.com/" to="https://apis.nocodb.com/"
> >
<div class="ml-3 flex items-center text-sm"> <div class="ml-3 flex items-center text-sm">
<MdiJson class="text-lg text-green-500" /> <component :is="iconMap.json" class="text-lg text-green-500" />
<!-- todo: i18n --> <!-- todo: i18n -->
<span class="ml-3">API {{ $t('labels.documentation') }}</span> <span class="ml-3">API {{ $t('labels.documentation') }}</span>
</div> </div>
@ -72,7 +72,7 @@ function openKeyboardShortcutDialog() {
target="_blank" target="_blank"
> >
<div class="flex items-center text-sm"> <div class="flex items-center text-sm">
<mdi-github class="mx-3 text-lg" /> <component :is="iconMap.github" class="mx-3 text-lg" />
<div v-if="isRtlLang"> <div v-if="isRtlLang">
<!-- us on Github --> <!-- us on Github -->
{{ $t('labels.community.starUs2') }} {{ $t('labels.community.starUs2') }}
@ -101,7 +101,7 @@ function openKeyboardShortcutDialog() {
target="_blank" target="_blank"
> >
<div class="flex items-center text-sm"> <div class="flex items-center text-sm">
<mdi-calendar-month class="mx-3 text-lg" :color="colors.dark[3 % colors.dark.length]" /> <component :is="iconMap.calendar" class="mx-3 text-lg" :color="colors.dark[3 % colors.dark.length]" />
<!-- Book a Free DEMO --> <!-- Book a Free DEMO -->
<div> <div>
{{ $t('labels.community.bookDemo') }} {{ $t('labels.community.bookDemo') }}
@ -120,7 +120,7 @@ function openKeyboardShortcutDialog() {
target="_blank" target="_blank"
> >
<div class="flex items-center text-sm"> <div class="flex items-center text-sm">
<mdi-discord class="mx-3 text-lg" :color="colors.dark[0 % colors.dark.length]" /> <component :is="iconMap.discord" class="mx-3 text-lg" :color="colors.dark[0 % colors.dark.length]" />
<!-- Get your questions answered --> <!-- Get your questions answered -->
<div> <div>
{{ $t('labels.community.getAnswered') }} {{ $t('labels.community.getAnswered') }}
@ -139,7 +139,7 @@ function openKeyboardShortcutDialog() {
target="_blank" target="_blank"
> >
<div class="flex items-center text-sm"> <div class="flex items-center text-sm">
<mdi-twitter class="mx-3 text-lg" :color="colors.dark[1 % colors.dark.length]" /> <component :is="iconMap.twitter" class="mx-3 text-lg" :color="colors.dark[1 % colors.dark.length]" />
<!-- Follow NocoDB --> <!-- Follow NocoDB -->
<div> <div>
{{ $t('labels.community.followNocodb') }} {{ $t('labels.community.followNocodb') }}
@ -176,7 +176,7 @@ function openKeyboardShortcutDialog() {
to="https://www.reddit.com/r/NocoDB/" to="https://www.reddit.com/r/NocoDB/"
> >
<div class="ml-3 flex items-center text-sm"> <div class="ml-3 flex items-center text-sm">
<LogosRedditIcon /> <component :is="iconMap.reddit" color="red" />
<span class="ml-4">/r/NocoDB/</span> <span class="ml-4">/r/NocoDB/</span>
</div> </div>
</nuxt-link> </nuxt-link>
@ -184,7 +184,7 @@ function openKeyboardShortcutDialog() {
<a-list-item @click="openKeyboardShortcutDialog"> <a-list-item @click="openKeyboardShortcutDialog">
<div class="ml-3 flex items-center text-sm"> <div class="ml-3 flex items-center text-sm">
<MdiKeyboard class="text-lg text-primary" /> <component :is="iconMap.keyboard" class="text-lg text-primary" />
<span class="ml-4">{{ $t('title.keyboardShortcut') }}</span> <span class="ml-4">{{ $t('title.keyboardShortcut') }}</span>
</div> </div>
</a-list-item> </a-list-item>

5
packages/nc-gui/components/general/TableIcon.vue

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Icon as IcIcon } from '@iconify/vue' import { Icon as IcIcon } from '@iconify/vue'
import type { TableType } from 'nocodb-sdk' import type { TableType } from 'nocodb-sdk'
import { iconMap } from '#imports'
const { meta: tableMeta } = defineProps<{ const { meta: tableMeta } = defineProps<{
meta: TableType meta: TableType
@ -15,6 +16,6 @@ const { meta: tableMeta } = defineProps<{
:icon="tableMeta.meta?.icon" :icon="tableMeta.meta?.icon"
/> />
<MdiEyeCircleOutline v-else-if="tableMeta?.type === 'view'" class="w-5" /> <component :is="iconMap.eye" v-else-if="tableMeta?.type === 'view'" class="w-5" />
<MdiTableLarge v-else class="w-5" /> <component :is="iconMap.table" v-else class="w-5" />
</template> </template>

6
packages/nc-gui/components/smartsheet/Form.vue

@ -10,6 +10,7 @@ import {
computed, computed,
createEventHook, createEventHook,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
iconMap,
inject, inject,
message, message,
onClickOutside, onClickOutside,
@ -490,7 +491,7 @@ watch(view, (nextView) => {
<a-dropdown v-model:visible="showColumnDropdown" :trigger="['click']" overlay-class-name="nc-dropdown-form-add-column"> <a-dropdown v-model:visible="showColumnDropdown" :trigger="['click']" overlay-class-name="nc-dropdown-form-add-column">
<button type="button" class="group w-full mt-2" @click.stop="showColumnDropdown = true"> <button type="button" class="group w-full mt-2" @click.stop="showColumnDropdown = true">
<span class="flex items-center flex-wrap justify-center gap-2 prose-sm text-gray-400"> <span class="flex items-center flex-wrap justify-center gap-2 prose-sm text-gray-400">
<MdiPlus class="color-transition transform group-hover:(text-accent scale-125)" /> <component :is="iconMap.plus" class="color-transition transform group-hover:(text-accent scale-125)" />
<!-- Add new field to this table --> <!-- Add new field to this table -->
<span class="color-transition group-hover:text-primary break-words"> <span class="color-transition group-hover:text-primary break-words">
{{ $t('activity.addField') }} {{ $t('activity.addField') }}
@ -596,7 +597,8 @@ watch(view, (nextView) => {
v-if="isUIAllowed('editFormView') && !isRequired(element, element.required)" v-if="isUIAllowed('editFormView') && !isRequired(element, element.required)"
class="absolute flex top-2 right-2" class="absolute flex top-2 right-2"
> >
<MdiEyeOffOutline <component
:is="iconMap.eyeSlash"
class="opacity-0 nc-field-remove-icon" class="opacity-0 nc-field-remove-icon"
data-testid="nc-field-remove-icon" data-testid="nc-field-remove-icon"
@click.stop="hideColumn(index)" @click.stop="hideColumn(index)"

3
packages/nc-gui/components/smartsheet/Gallery.vue

@ -17,6 +17,7 @@ import {
computed, computed,
createEventHook, createEventHook,
extractPkFromRow, extractPkFromRow,
iconMap,
inject, inject,
isImage, isImage,
isLTAR, isLTAR,
@ -262,7 +263,7 @@ watch(view, async (nextView) => {
</template> </template>
</a-carousel> </a-carousel>
<MdiFileImageBox v-else class="w-full h-48 my-4 text-cool-gray-200" /> <component :is="iconMap.imagePlaceholder" v-else class="w-full h-48 my-4 text-cool-gray-200" />
</template> </template>
<div v-for="col in fieldsWithoutCover" :key="`record-${record.row.id}-${col.id}`"> <div v-for="col in fieldsWithoutCover" :key="`record-${record.row.id}-${col.id}`">

8
packages/nc-gui/components/smartsheet/Grid.vue

@ -23,6 +23,7 @@ import {
createEventHook, createEventHook,
enumColor, enumColor,
extractPkFromRow, extractPkFromRow,
iconMap,
inject, inject,
isColumnRequiredAndNull, isColumnRequiredAndNull,
isDrawerOrModalExist, isDrawerOrModalExist,
@ -762,7 +763,7 @@ const closeAddColumnDropdown = () => {
overlay-class-name="nc-dropdown-grid-add-column" overlay-class-name="nc-dropdown-grid-add-column"
> >
<div class="h-full w-[60px] flex items-center justify-center"> <div class="h-full w-[60px] flex items-center justify-center">
<MdiPlus class="text-sm nc-column-add" /> <component :is="iconMap.plus" class="text-sm nc-column-add" />
</div> </div>
<template #overlay> <template #overlay>
@ -829,7 +830,8 @@ const closeAddColumnDropdown = () => {
v-else v-else
class="cursor-pointer flex items-center border-1 active:ring rounded p-1 hover:(bg-primary bg-opacity-10)" class="cursor-pointer flex items-center border-1 active:ring rounded p-1 hover:(bg-primary bg-opacity-10)"
> >
<MdiArrowExpand <component
:is="iconMap.expand"
v-e="['c:row-expand']" v-e="['c:row-expand']"
class="select-none transform hover:(text-accent scale-120) nc-row-expand" class="select-none transform hover:(text-accent scale-120) nc-row-expand"
@click="expandForm(row, state)" @click="expandForm(row, state)"
@ -900,7 +902,7 @@ const closeAddColumnDropdown = () => {
@click="addEmptyRow()" @click="addEmptyRow()"
> >
<div class="px-2 w-full flex items-center text-gray-500"> <div class="px-2 w-full flex items-center text-gray-500">
<MdiPlus class="text-pint-500 text-xs ml-2 text-primary" /> <component :is="iconMap.plus" class="text-pint-500 text-xs ml-2 text-primary" />
<span class="ml-1"> <span class="ml-1">
{{ $t('activity.addRow') }} {{ $t('activity.addRow') }}

20
packages/nc-gui/components/smartsheet/Kanban.vue

@ -12,6 +12,7 @@ import {
IsPublicInj, IsPublicInj,
MetaInj, MetaInj,
OpenNewRecordFormHookInj, OpenNewRecordFormHookInj,
iconMap,
inject, inject,
isImage, isImage,
isLTAR, isLTAR,
@ -366,7 +367,7 @@ watch(view, async (nextView) => {
> >
<LazyGeneralTruncateText>{{ stack.title ?? 'uncategorized' }}</LazyGeneralTruncateText> <LazyGeneralTruncateText>{{ stack.title ?? 'uncategorized' }}</LazyGeneralTruncateText>
<span v-if="!isLocked" class="w-full flex w-[15px]"> <span v-if="!isLocked" class="w-full flex w-[15px]">
<mdi-menu-down class="text-grey text-lg ml-auto" /> <component :is="iconMap.arrowDown" class="text-grey text-lg ml-auto" />
</span> </span>
</div> </div>
<template v-if="!isLocked" #overlay> <template v-if="!isLocked" #overlay>
@ -382,13 +383,13 @@ watch(view, async (nextView) => {
" "
> >
<div class="py-2 flex gap-2 items-center"> <div class="py-2 flex gap-2 items-center">
<mdi-plus class="text-gray-500" /> <component :is="iconMap.plus" class="text-gray-500" />
{{ $t('activity.addNewRecord') }} {{ $t('activity.addNewRecord') }}
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item v-e="['c:kanban:collapse-stack']" @click="handleCollapseStack(stackIdx)"> <a-menu-item v-e="['c:kanban:collapse-stack']" @click="handleCollapseStack(stackIdx)">
<div class="py-2 flex gap-2 items-center"> <div class="py-2 flex gap-2 items-center">
<mdi-arrow-collapse class="text-gray-500" /> <component :is="iconMap.arrowCollapse" class="text-gray-500" />
{{ $t('activity.kanban.collapseStack') }} {{ $t('activity.kanban.collapseStack') }}
</div> </div>
</a-menu-item> </a-menu-item>
@ -398,7 +399,7 @@ watch(view, async (nextView) => {
@click="handleDeleteStackClick(stack.title, stackIdx)" @click="handleDeleteStackClick(stack.title, stackIdx)"
> >
<div class="py-2 flex gap-2 items-center"> <div class="py-2 flex gap-2 items-center">
<mdi-delete class="text-gray-500" /> <component :is="iconMap.delete" class="text-gray-500" />
{{ $t('activity.kanban.deleteStack') }} {{ $t('activity.kanban.deleteStack') }}
</div> </div>
</a-menu-item> </a-menu-item>
@ -470,7 +471,7 @@ watch(view, async (nextView) => {
</template> </template>
</a-carousel> </a-carousel>
<MdiFileImageBox v-else class="w-full h-48 my-4 text-cool-gray-200" /> <component :is="iconMap.imagePlaceholder" v-else class="w-full h-48 my-4 text-cool-gray-200" />
</template> </template>
<div <div
v-for="col in fieldsWithoutCover" v-for="col in fieldsWithoutCover"
@ -524,7 +525,8 @@ watch(view, async (nextView) => {
<a-layout-footer> <a-layout-footer>
<div v-if="formattedData.get(stack.title) && countByStack.get(stack.title) >= 0" class="mt-5 text-center"> <div v-if="formattedData.get(stack.title) && countByStack.get(stack.title) >= 0" class="mt-5 text-center">
<!-- Stack Title --> <!-- Stack Title -->
<mdi-plus <component
:is="iconMap.plus"
v-if="!isPublic && !isLocked" v-if="!isPublic && !isLocked"
class="text-pint-500 text-lg text-primary cursor-pointer" class="text-pint-500 text-lg text-primary cursor-pointer"
@click=" @click="
@ -570,7 +572,7 @@ watch(view, async (nextView) => {
:class="{ capitalize: stack.title === null }" :class="{ capitalize: stack.title === null }"
> >
<LazyGeneralTruncateText>{{ stack.title ?? 'uncategorized' }}</LazyGeneralTruncateText> <LazyGeneralTruncateText>{{ stack.title ?? 'uncategorized' }}</LazyGeneralTruncateText>
<mdi-menu-down class="text-grey text-lg" /> <component :is="iconMap.arrowDown" class="text-grey text-lg" />
</div> </div>
<!-- Record Count --> <!-- Record Count -->
{{ formattedData.get(stack.title).length }} / {{ countByStack.get(stack.title) }} {{ formattedData.get(stack.title).length }} / {{ countByStack.get(stack.title) }}
@ -586,7 +588,7 @@ watch(view, async (nextView) => {
<a-menu class="shadow !rounded !py-0" @click="contextMenu = false"> <a-menu class="shadow !rounded !py-0" @click="contextMenu = false">
<a-menu-item v-if="contextMenuTarget" @click="expandForm(contextMenuTarget)"> <a-menu-item v-if="contextMenuTarget" @click="expandForm(contextMenuTarget)">
<div v-e="['a:kanban:expand-record']" class="nc-project-menu-item nc-kanban-context-menu-item"> <div v-e="['a:kanban:expand-record']" class="nc-project-menu-item nc-kanban-context-menu-item">
<MdiArrowExpand class="flex" /> <component :is="iconMap.expand" class="flex" />
<!-- Expand Record --> <!-- Expand Record -->
{{ $t('activity.expandRecord') }} {{ $t('activity.expandRecord') }}
</div> </div>
@ -594,7 +596,7 @@ watch(view, async (nextView) => {
<a-divider class="!m-0 !p-0" /> <a-divider class="!m-0 !p-0" />
<a-menu-item v-if="contextMenuTarget" @click="deleteRow(contextMenuTarget)"> <a-menu-item v-if="contextMenuTarget" @click="deleteRow(contextMenuTarget)">
<div v-e="['a:kanban:delete-record']" class="nc-project-menu-item nc-kanban-context-menu-item"> <div v-e="['a:kanban:delete-record']" class="nc-project-menu-item nc-kanban-context-menu-item">
<MdiDeleteOutline class="flex" /> <component :is="iconMap.delete" class="flex" />
<!-- Delete Record --> <!-- Delete Record -->
{{ $t('activity.deleteRecord') }} {{ $t('activity.deleteRecord') }}
</div> </div>

4
packages/nc-gui/components/smartsheet/Map.vue

@ -3,7 +3,7 @@ import 'leaflet/dist/leaflet.css'
import L, { LatLng } from 'leaflet' import L, { LatLng } from 'leaflet'
import 'leaflet.markercluster' import 'leaflet.markercluster'
import { ViewTypes } from 'nocodb-sdk' import { ViewTypes } from 'nocodb-sdk'
import { IsPublicInj, OpenNewRecordFormHookInj, latLongToJoinedString, onMounted, provide, ref } from '#imports' import { IsPublicInj, OpenNewRecordFormHookInj, iconMap, latLongToJoinedString, onMounted, provide, ref } from '#imports'
import type { Row } from '~/lib' import type { Row } from '~/lib'
const route = useRoute() const route = useRoute()
@ -228,7 +228,7 @@ const count = computed(() => paginationData.value.totalRows)
<div v-if="count > 900" class="nc-warning-info flex min-w-32px h-32px items-center gap-1 px-2 bg-white"> <div v-if="count > 900" class="nc-warning-info flex min-w-32px h-32px items-center gap-1 px-2 bg-white">
<div>{{ count }} {{ $t('objects.records') }}</div> <div>{{ count }} {{ $t('objects.records') }}</div>
<mdi-map-marker-alert /> <component :is="iconMap.markerAlert" />
</div> </div>
</a-tooltip> </a-tooltip>
</div> </div>

4
packages/nc-gui/components/smartsheet/Pagination.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ChangePageInj, PaginationDataInj, computed, inject } from '#imports' import { ChangePageInj, PaginationDataInj, computed, iconMap, inject } from '#imports'
const paginatedData = inject(PaginationDataInj)! const paginatedData = inject(PaginationDataInj)!
@ -39,7 +39,7 @@ const page = computed({
<span class="text-xs" style="white-space: nowrap"> Change page:</span> <span class="text-xs" style="white-space: nowrap"> Change page:</span>
<a-input :value="page" size="small" class="ml-1 !text-xs" type="number" @keydown.enter="changePage(page)"> <a-input :value="page" size="small" class="ml-1 !text-xs" type="number" @keydown.enter="changePage(page)">
<template #suffix> <template #suffix>
<MdiKeyboardReturn class="mt-1" @click="changePage(page)" /> <component :is="iconMap.returnKey" class="mt-1" @click="changePage(page)" />
</template> </template>
</a-input> </a-input>
</div> </div>

4
packages/nc-gui/components/smartsheet/column/BarcodeOptions.vue

@ -2,7 +2,7 @@
import type { UITypes } from 'nocodb-sdk' import type { UITypes } from 'nocodb-sdk'
import { AllowedColumnTypesForQrAndBarcodes } from 'nocodb-sdk' import { AllowedColumnTypesForQrAndBarcodes } from 'nocodb-sdk'
import type { SelectProps } from 'ant-design-vue' import type { SelectProps } from 'ant-design-vue'
import { onMounted, useVModel, watch } from '#imports' import { iconMap, onMounted, useVModel, watch } from '#imports'
const props = defineProps<{ const props = defineProps<{
modelValue: any modelValue: any
@ -98,7 +98,7 @@ const showBarcodeValueColumnInfoIcon = computed(() => !columnsAllowedAsBarcodeVa
Decimal. Please create one first. Decimal. Please create one first.
</span> </span>
</template> </template>
<mdi-information class="cursor-pointer" /> <component :is="iconMap.info" class="cursor-pointer" />
</a-tooltip> </a-tooltip>
</div> </div>
</div> </div>

5
packages/nc-gui/components/smartsheet/column/FormulaOptions.vue

@ -12,6 +12,7 @@ import {
formulas, formulas,
getUIDTIcon, getUIDTIcon,
getWordUntilCaret, getWordUntilCaret,
iconMap,
insertAtCursor, insertAtCursor,
onMounted, onMounted,
useColumnCreateStoreOrThrow, useColumnCreateStoreOrThrow,
@ -742,9 +743,9 @@ onMounted(() => {
</template> </template>
<template #avatar> <template #avatar>
<mdi-function v-if="item.type === 'function'" class="text-lg" /> <component :is="iconMap.function" v-if="item.type === 'function'" class="text-lg" />
<mdi-calculator v-if="item.type === 'op'" class="text-lg" /> <component :is="iconMap.calculator" v-if="item.type === 'op'" class="text-lg" />
<component :is="item.icon" v-if="item.type === 'column'" class="text-lg" /> <component :is="item.icon" v-if="item.type === 'column'" class="text-lg" />
</template> </template>

10
packages/nc-gui/components/smartsheet/column/SelectOptions.vue

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import Draggable from 'vuedraggable' import Draggable from 'vuedraggable'
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import { IsKanbanInj, enumColor, onMounted, useColumnCreateStoreOrThrow, useVModel, watch } from '#imports' import { IsKanbanInj, enumColor, iconMap, onMounted, useColumnCreateStoreOrThrow, useVModel, watch } from '#imports'
interface Option { interface Option {
color: string color: string
@ -172,7 +172,8 @@ watch(inputs, () => {
:data-testid="`select-column-option-${index}`" :data-testid="`select-column-option-${index}`"
:class="{ removed: element.status === 'remove' }" :class="{ removed: element.status === 'remove' }"
> >
<MdiDragVertical <component
:is="iconMap.dragVertical"
v-if="!isKanban" v-if="!isKanban"
small small
class="nc-child-draggable-icon handle" class="nc-child-draggable-icon handle"
@ -208,7 +209,8 @@ watch(inputs, () => {
/> />
</div> </div>
<MdiClose <component
:is="iconMap.close"
v-if="element.status !== 'remove'" v-if="element.status !== 'remove'"
class="ml-2 hover:!text-black-500 text-gray-500 cursor-pointer" class="ml-2 hover:!text-black-500 text-gray-500 cursor-pointer"
:data-testid="`select-column-option-remove-${index}`" :data-testid="`select-column-option-remove-${index}`"
@ -231,7 +233,7 @@ watch(inputs, () => {
</div> </div>
<a-button type="dashed" class="w-full caption mt-2" @click="addNewOption()"> <a-button type="dashed" class="w-full caption mt-2" @click="addNewOption()">
<div class="flex items-center"> <div class="flex items-center">
<MdiPlus /> <component :is="iconMap.plus" />
<span class="flex-auto">Add option</span> <span class="flex-auto">Add option</span>
</div> </div>
</a-button> </a-button>

12
packages/nc-gui/components/smartsheet/expanded-form/Comments.vue

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import type { AuditType } from 'nocodb-sdk' import type { AuditType } from 'nocodb-sdk'
import { enumColor, ref, timeAgo, useCopy, useExpandedFormStoreOrThrow, useGlobal, useI18n, watch } from '#imports' import { enumColor, iconMap, ref, timeAgo, useCopy, useExpandedFormStoreOrThrow, useGlobal, useI18n, watch } from '#imports'
const { loadCommentsAndLogs, commentsAndLogs, isCommentsLoading, commentsOnly, saveComment, isYou, comment, updateComment } = const { loadCommentsAndLogs, commentsAndLogs, isCommentsLoading, commentsOnly, saveComment, isYou, comment, updateComment } =
useExpandedFormStoreOrThrow() useExpandedFormStoreOrThrow()
@ -133,7 +133,11 @@ watch(
<div v-for="(log, idx) of commentsAndLogs" :key="log.id"> <div v-for="(log, idx) of commentsAndLogs" :key="log.id">
<a-dropdown :trigger="['contextmenu']" :overlay-class-name="`nc-dropdown-comment-context-menu-${idx}`"> <a-dropdown :trigger="['contextmenu']" :overlay-class-name="`nc-dropdown-comment-context-menu-${idx}`">
<div class="flex gap-1 text-xs"> <div class="flex gap-1 text-xs">
<MdiAccountCircle class="row-span-2" :class="isYou(log.user) ? 'text-pink-300' : 'text-blue-300 '" /> <component
:is="iconMap.accountCircle"
class="row-span-2"
:class="isYou(log.user) ? 'text-pink-300' : 'text-blue-300 '"
/>
<div class="flex-1"> <div class="flex-1">
<p class="mb-1 caption edited-text text-[10px] text-gray-500"> <p class="mb-1 caption edited-text text-[10px] text-gray-500">
@ -208,12 +212,12 @@ watch(
> >
<template #addonBefore> <template #addonBefore>
<div class="flex items-center"> <div class="flex items-center">
<mdi-account-circle class="text-lg text-pink-300" small @click="saveComment" /> <component :is="iconMap.accountCircle" class="text-lg text-pink-300" small @click="saveComment" />
</div> </div>
</template> </template>
<template #suffix> <template #suffix>
<mdi-keyboard-return v-if="comment" class="text-sm" small @click="saveComment" /> <component :is="iconMap.returnKey" v-if="comment" class="text-sm" small @click="saveComment" />
</template> </template>
</a-input> </a-input>
</div> </div>

34
packages/nc-gui/components/smartsheet/expanded-form/Header.vue

@ -3,6 +3,7 @@ import { message } from 'ant-design-vue'
import type { ViewType } from 'nocodb-sdk' import type { ViewType } from 'nocodb-sdk'
import { import {
ReloadRowDataHookInj, ReloadRowDataHookInj,
iconMap,
isMac, isMac,
useExpandedFormStoreOrThrow, useExpandedFormStoreOrThrow,
useSmartsheetRowStoreOrThrow, useSmartsheetRowStoreOrThrow,
@ -106,7 +107,8 @@ const onConfirmDeleteRowClick = async () => {
<!-- todo: i18n --> <!-- todo: i18n -->
<div class="text-center w-full">Copy record URL</div> <div class="text-center w-full">Copy record URL</div>
</template> </template>
<mdi-link <component
:is="iconMap.link"
v-if="!isNew" v-if="!isNew"
class="nc-icon-transition cursor-pointer select-none text-gray-500 mx-1 nc-copy-row-url min-w-4" class="nc-icon-transition cursor-pointer select-none text-gray-500 mx-1 nc-copy-row-url min-w-4"
@click="copyRecordUrl" @click="copyRecordUrl"
@ -118,7 +120,8 @@ const onConfirmDeleteRowClick = async () => {
<template #title> <template #title>
<div class="text-center w-full">{{ $t('activity.toggleCommentsDraw') }}</div> <div class="text-center w-full">{{ $t('activity.toggleCommentsDraw') }}</div>
</template> </template>
<MdiCommentTextOutline <component
:is="iconMap.comment"
v-if="isUIAllowed('rowComments') && !isNew" v-if="isUIAllowed('rowComments') && !isNew"
v-e="['c:row-expand:comment-toggle']" v-e="['c:row-expand:comment-toggle']"
class="nc-icon-transition cursor-pointer select-none nc-toggle-comments text-gray-500 mx-1 min-w-4" class="nc-icon-transition cursor-pointer select-none nc-toggle-comments text-gray-500 mx-1 min-w-4"
@ -127,59 +130,66 @@ const onConfirmDeleteRowClick = async () => {
</a-tooltip> </a-tooltip>
<a-dropdown-button class="nc-expand-form-save-btn" type="primary" :disabled="!isUIAllowed('tableRowUpdate')" @click="save"> <a-dropdown-button class="nc-expand-form-save-btn" type="primary" :disabled="!isUIAllowed('tableRowUpdate')" @click="save">
<template #icon><MdiMenuDown /></template> <template #icon><component :is="iconMap.arrowDown" /></template>
<template #overlay> <template #overlay>
<a-menu class="nc-expand-form-save-dropdown-menu"> <a-menu class="nc-expand-form-save-dropdown-menu">
<a-menu-item key="0" class="!py-2 flex gap-2" @click="saveRowAndStay = 0"> <a-menu-item key="0" class="!py-2 flex gap-2" @click="saveRowAndStay = 0">
<div class="flex items-center"> <div class="flex items-center">
<MdiContentSave class="mr-1" /> <component :is="iconMap.contentSaveExit" class="mr-1" />
{{ $t('activity.saveAndExit') }} {{ $t('activity.saveAndExit') }}
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item key="1" class="!py-2 flex gap-2 items-center" @click="saveRowAndStay = 1"> <a-menu-item key="1" class="!py-2 flex gap-2 items-center" @click="saveRowAndStay = 1">
<div class="flex items-center"> <div class="flex items-center">
<MdiContentSaveEdit class="mr-1" /> <component :is="iconMap.contentSaveStay" class="mr-1" />
{{ $t('activity.saveAndStay') }} {{ $t('activity.saveAndStay') }}
</div> </div>
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
</template> </template>
<div v-if="saveRowAndStay === 0" class="flex items-center"> <div v-if="saveRowAndStay === 0" class="flex items-center">
<MdiContentSave class="mr-1" /> <component :is="iconMap.contentSaveExit" class="mr-1" />
{{ $t('activity.saveAndExit') }} {{ $t('activity.saveAndExit') }}
</div> </div>
<div v-if="saveRowAndStay === 1" class="flex items-center"> <div v-if="saveRowAndStay === 1" class="flex items-center">
<MdiContentSaveEdit class="mr-1" /> <component :is="iconMap.contentSaveStay" class="mr-1" />
{{ $t('activity.saveAndStay') }} {{ $t('activity.saveAndStay') }}
</div> </div>
</a-dropdown-button> </a-dropdown-button>
<a-dropdown> <a-dropdown>
<MdiDotsVertical class="nc-icon-transition" /> <component :is="iconMap.threeDotVertical" class="nc-icon-transition" />
<template #overlay> <template #overlay>
<a-menu> <a-menu>
<a-menu-item v-if="!isNew" @click="loadRow"> <a-menu-item v-if="!isNew" @click="loadRow">
<div v-e="['c:row-expand:reload']" class="py-2 flex gap-2 items-center"> <div v-e="['c:row-expand:reload']" class="py-2 flex gap-2 items-center">
<mdi-reload class="nc-icon-transition cursor-pointer select-none text-gray-500 mx-1 min-w-4" /> <component :is="iconMap.reload" class="nc-icon-transition cursor-pointer select-none text-gray-500 mx-1 min-w-4" />
{{ $t('general.reload') }} {{ $t('general.reload') }}
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item v-if="isUIAllowed('xcDatatableEditable') && !isNew" @click="!isNew && emit('duplicateRow')"> <a-menu-item v-if="isUIAllowed('xcDatatableEditable') && !isNew" @click="!isNew && emit('duplicateRow')">
<div v-e="['c:row-expand:duplicate']" class="py-2 flex gap-2 a"> <div v-e="['c:row-expand:duplicate']" class="py-2 flex gap-2 a">
<MdiContentCopy class="nc-icon-transition cursor-pointer select-none nc-duplicate-row text-gray-500 mx-1 min-w-4" /> <component
:is="iconMap.copy"
class="nc-icon-transition cursor-pointer select-none nc-duplicate-row text-gray-500 mx-1 min-w-4"
/>
{{ $t('activity.duplicateRow') }} {{ $t('activity.duplicateRow') }}
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item v-if="isUIAllowed('xcDatatableEditable') && !isNew" @click="!isNew && onDeleteRowClick()"> <a-menu-item v-if="isUIAllowed('xcDatatableEditable') && !isNew" @click="!isNew && onDeleteRowClick()">
<div v-e="['c:row-expand:delete']" class="py-2 flex gap-2 items-center"> <div v-e="['c:row-expand:delete']" class="py-2 flex gap-2 items-center">
<MdiDeleteOutline class="nc-icon-transition cursor-pointer select-none nc-delete-row text-gray-500 mx-1 min-w-4" /> <component
:is="iconMap.delete"
class="nc-icon-transition cursor-pointer select-none nc-delete-row text-gray-500 mx-1 min-w-4"
/>
{{ $t('activity.deleteRow') }} {{ $t('activity.deleteRow') }}
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item @click="emit('cancel')"> <a-menu-item @click="emit('cancel')">
<div v-e="['c:row-expand:delete']" class="py-2 flex gap-2 items-center"> <div v-e="['c:row-expand:delete']" class="py-2 flex gap-2 items-center">
<MdiCloseCircleOutline <component
:is="iconMap.closeCircle"
class="nc-icon-transition cursor-pointer select-none nc-delete-row text-gray-500 mx-1 min-w-4" class="nc-icon-transition cursor-pointer select-none nc-delete-row text-gray-500 mx-1 min-w-4"
/> />
{{ $t('general.close') }} {{ $t('general.close') }}

5
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -11,6 +11,7 @@ import {
ReloadRowDataHookInj, ReloadRowDataHookInj,
computedInject, computedInject,
createEventHook, createEventHook,
iconMap,
inject, inject,
message, message,
provide, provide,
@ -301,7 +302,7 @@ export default {
<GeneralShortcutLabel class="justify-center" :keys="['Alt', '←']" /> <GeneralShortcutLabel class="justify-center" :keys="['Alt', '←']" />
</template> </template>
<MdiChevronLeft class="cursor-pointer nc-prev-arrow" @click="$emit('prev')" /> <component :is="iconMap.chevronLeft" class="cursor-pointer nc-prev-arrow" @click="$emit('prev')" />
</a-tooltip> </a-tooltip>
<a-tooltip v-if="!props.lastRow" placement="bottom"> <a-tooltip v-if="!props.lastRow" placement="bottom">
@ -309,7 +310,7 @@ export default {
{{ $t('labels.nextRow') }} {{ $t('labels.nextRow') }}
<GeneralShortcutLabel class="justify-center" :keys="['Alt', '→']" /> <GeneralShortcutLabel class="justify-center" :keys="['Alt', '→']" />
</template> </template>
<MdiChevronRight class="cursor-pointer nc-next-arrow" @click="onNext" /> <component :is="iconMap.chevronRight" class="cursor-pointer nc-next-arrow" @click="onNext" />
</a-tooltip> </a-tooltip>
</template> </template>
<div class="w-[500px] mx-auto"> <div class="w-[500px] mx-auto">

74
packages/nc-gui/components/smartsheet/header/CellIcon.ts

@ -5,6 +5,7 @@ import {
computed, computed,
defineComponent, defineComponent,
h, h,
iconMap,
inject, inject,
isAttachment, isAttachment,
isBoolean, isBoolean,
@ -34,79 +35,56 @@ import {
toRef, toRef,
useProject, useProject,
} from '#imports' } from '#imports'
import FilePhoneIcon from '~icons/mdi/file-phone'
import KeyIcon from '~icons/mdi/key-variant'
import JSONIcon from '~icons/mdi/code-json'
import ClockIcon from '~icons/mdi/clock-time-five'
import WebIcon from '~icons/mdi/web'
import TextAreaIcon from '~icons/mdi/card-text-outline'
import StringIcon from '~icons/mdi/alpha-a-box-outline'
import BooleanIcon from '~icons/mdi/check-box-outline'
import CalendarIcon from '~icons/mdi/calendar'
import SingleSelectIcon from '~icons/mdi/arrow-down-drop-circle'
import MultiSelectIcon from '~icons/mdi/format-list-bulleted-square'
import DatetimeIcon from '~icons/mdi/calendar-clock'
import GeoDataIcon from '~icons/mdi/map-marker'
import RatingIcon from '~icons/mdi/star'
import GenericIcon from '~icons/mdi/square-rounded'
import NumericIcon from '~icons/mdi/numeric'
import AttachmentIcon from '~icons/mdi/image-multiple-outline'
import EmailIcon from '~icons/mdi/email'
import CurrencyIcon from '~icons/mdi/currency-usd-circle-outline'
import PercentIcon from '~icons/mdi/percent-outline'
import DecimalIcon from '~icons/mdi/decimal'
import SpecificDBTypeIcon from '~icons/mdi/database-settings'
import DurationIcon from '~icons/mdi/timer-outline'
const renderIcon = (column: ColumnType, abstractType: any) => { const renderIcon = (column: ColumnType, abstractType: any) => {
if (isPrimaryKey(column)) { if (isPrimaryKey(column)) {
return KeyIcon return iconMap.key
} else if (isSpecificDBType(column)) { } else if (isSpecificDBType(column)) {
return SpecificDBTypeIcon return iconMap.specificDbType
} else if (isJSON(column)) { } else if (isJSON(column)) {
return JSONIcon return iconMap.json
} else if (isDate(column, abstractType)) { } else if (isDate(column, abstractType)) {
return CalendarIcon return iconMap.calendar
} else if (isDateTime(column, abstractType)) { } else if (isDateTime(column, abstractType)) {
return DatetimeIcon return iconMap.datetime
} else if (isGeoData(column)) { } else if (isGeoData(column)) {
return GeoDataIcon return iconMap.geoData
} else if (isSet(column)) { } else if (isSet(column)) {
return MultiSelectIcon return iconMap.multiSelect
} else if (isSingleSelect(column)) { } else if (isSingleSelect(column)) {
return SingleSelectIcon return iconMap.singleSelect
} else if (isBoolean(column, abstractType)) { } else if (isBoolean(column, abstractType)) {
return BooleanIcon return iconMap.boolean
} else if (isTextArea(column)) { } else if (isTextArea(column)) {
return TextAreaIcon return iconMap.longText
} else if (isEmail(column)) { } else if (isEmail(column)) {
return EmailIcon return iconMap.email
} else if (isYear(column, abstractType)) { } else if (isYear(column, abstractType)) {
return CalendarIcon return iconMap.calendar
} else if (isTime(column, abstractType)) { } else if (isTime(column, abstractType)) {
return ClockIcon return iconMap.calendar
} else if (isRating(column)) { } else if (isRating(column)) {
return RatingIcon return iconMap.rating
} else if (isAttachment(column)) { } else if (isAttachment(column)) {
return AttachmentIcon return iconMap.image
} else if (isDecimal(column)) { } else if (isDecimal(column)) {
return DecimalIcon return iconMap.decimal
} else if (isPhoneNumber(column)) { } else if (isPhoneNumber(column)) {
return FilePhoneIcon return iconMap.phone
} else if (isURL(column)) { } else if (isURL(column)) {
return WebIcon return iconMap.web
} else if (isCurrency(column)) { } else if (isCurrency(column)) {
return CurrencyIcon return iconMap.currency
} else if (isDuration(column)) { } else if (isDuration(column)) {
return DurationIcon return iconMap.duration
} else if (isPercent(column)) { } else if (isPercent(column)) {
return PercentIcon return iconMap.percent
} else if (isInt(column, abstractType) || isFloat(column, abstractType)) { } else if (isInt(column, abstractType) || isFloat(column, abstractType)) {
return NumericIcon return iconMap.number
} else if (isString(column, abstractType)) { } else if (isString(column, abstractType)) {
return StringIcon return iconMap.text
} else { } else {
return GenericIcon return iconMap.generic
} }
} }
@ -133,7 +111,7 @@ export default defineComponent({
return () => { return () => {
if (!column.value) return null if (!column.value) return null
return h(renderIcon(column.value, abstractType.value), { class: 'text-grey mx-1 !text-xs' }) return h(renderIcon(column.value, abstractType.value), { class: 'text-gray-500 mx-1' })
} }
}, },
}) })

22
packages/nc-gui/components/smartsheet/header/Menu.vue

@ -11,6 +11,7 @@ import {
SmartsheetStoreEvents, SmartsheetStoreEvents,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
getUniqueColumnName, getUniqueColumnName,
iconMap,
inject, inject,
message, message,
useI18n, useI18n,
@ -213,13 +214,12 @@ const hideField = async () => {
<template> <template>
<a-dropdown v-if="!isLocked" placement="bottomRight" :trigger="['click']" overlay-class-name="nc-dropdown-column-operations"> <a-dropdown v-if="!isLocked" placement="bottomRight" :trigger="['click']" overlay-class-name="nc-dropdown-column-operations">
<MdiMenuDown class="h-full text-grey nc-ui-dt-dropdown cursor-pointer outline-0" /> <div><GeneralIcon icon="arrowDown" class="text-grey h-full text-grey nc-ui-dt-dropdown cursor-pointer outline-0 mr-2" /></div>
<template #overlay> <template #overlay>
<a-menu class="shadow bg-white"> <a-menu class="shadow bg-white">
<a-menu-item @click="emit('edit')"> <a-menu-item @click="emit('edit')">
<div class="nc-column-edit nc-header-menu-item"> <div class="nc-column-edit nc-header-menu-item">
<MdiPencil class="text-primary" /> <component :is="iconMap.edit" class="text-primary" />
<!-- Edit --> <!-- Edit -->
{{ $t('general.edit') }} {{ $t('general.edit') }}
</div> </div>
@ -228,14 +228,14 @@ const hideField = async () => {
<a-divider class="!my-0" /> <a-divider class="!my-0" />
<a-menu-item @click="sortByColumn('asc')"> <a-menu-item @click="sortByColumn('asc')">
<div v-e="['a:field:sort', { dir: 'asc' }]" class="nc-column-insert-after nc-header-menu-item"> <div v-e="['a:field:sort', { dir: 'asc' }]" class="nc-column-insert-after nc-header-menu-item">
<MdiSortAscending class="text-primary" /> <component :is="iconMap.sortAsc" class="text-primary" />
<!-- Sort Ascending --> <!-- Sort Ascending -->
{{ $t('general.sortAsc') }} {{ $t('general.sortAsc') }}
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item @click="sortByColumn('desc')"> <a-menu-item @click="sortByColumn('desc')">
<div v-e="['a:field:sort', { dir: 'desc' }]" class="nc-column-insert-before nc-header-menu-item"> <div v-e="['a:field:sort', { dir: 'desc' }]" class="nc-column-insert-before nc-header-menu-item">
<MdiSortDescending class="text-primary" /> <component :is="iconMap.sortDesc" class="text-primary" />
<!-- Sort Descending --> <!-- Sort Descending -->
{{ $t('general.sortDesc') }} {{ $t('general.sortDesc') }}
</div> </div>
@ -244,7 +244,7 @@ const hideField = async () => {
<a-divider class="!my-0" /> <a-divider class="!my-0" />
<a-menu-item v-if="!column?.pv" @click="hideField"> <a-menu-item v-if="!column?.pv" @click="hideField">
<div v-e="['a:field:hide']" class="nc-column-insert-before nc-header-menu-item"> <div v-e="['a:field:hide']" class="nc-column-insert-before nc-header-menu-item">
<MdiEyeOffOutline class="text-primary" /> <component :is="iconMap.eye" class="text-primary" />
<!-- Hide Field --> <!-- Hide Field -->
{{ $t('general.hideField') }} {{ $t('general.hideField') }}
</div> </div>
@ -257,21 +257,21 @@ const hideField = async () => {
@click="duplicateColumn" @click="duplicateColumn"
> >
<div v-e="['a:field:duplicate']" class="nc-column-duplicate nc-header-menu-item"> <div v-e="['a:field:duplicate']" class="nc-column-duplicate nc-header-menu-item">
<MdiFileReplaceOutline class="text-primary" /> <component :is="iconMap.duplicate" class="text-primary" />
<!-- Duplicate --> <!-- Duplicate -->
{{ t('general.duplicate') }} {{ t('general.duplicate') }}
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item @click="addColumn()"> <a-menu-item @click="addColumn()">
<div v-e="['a:field:insert:after']" class="nc-column-insert-after nc-header-menu-item"> <div v-e="['a:field:insert:after']" class="nc-column-insert-after nc-header-menu-item">
<MdiTableColumnPlusAfter class="text-primary" /> <component :is="iconMap.colInsertAfter" class="text-primary" />
<!-- Insert After --> <!-- Insert After -->
{{ t('general.insertAfter') }} {{ t('general.insertAfter') }}
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item v-if="!column?.pv" @click="addColumn(true)"> <a-menu-item v-if="!column?.pv" @click="addColumn(true)">
<div v-e="['a:field:insert:before']" class="nc-column-insert-before nc-header-menu-item"> <div v-e="['a:field:insert:before']" class="nc-column-insert-before nc-header-menu-item">
<MdiTableColumnPlusBefore class="text-primary" /> <component :is="iconMap.colInsertBefore" class="text-primary" />
<!-- Insert Before --> <!-- Insert Before -->
{{ t('general.insertBefore') }} {{ t('general.insertBefore') }}
</div> </div>
@ -280,7 +280,7 @@ const hideField = async () => {
<a-menu-item v-if="(!virtual || column?.uidt === UITypes.Formula) && !column?.pv" @click="setAsDisplayValue"> <a-menu-item v-if="(!virtual || column?.uidt === UITypes.Formula) && !column?.pv" @click="setAsDisplayValue">
<div class="nc-column-set-primary nc-header-menu-item"> <div class="nc-column-set-primary nc-header-menu-item">
<MdiStar class="text-primary" /> <GeneralIcon icon="star" class="text-primary" />
<!-- todo : tooltip --> <!-- todo : tooltip -->
<!-- Set as Display value --> <!-- Set as Display value -->
@ -290,7 +290,7 @@ const hideField = async () => {
<a-menu-item v-if="!column?.pv" @click="deleteColumn"> <a-menu-item v-if="!column?.pv" @click="deleteColumn">
<div class="nc-column-delete nc-header-menu-item"> <div class="nc-column-delete nc-header-menu-item">
<MdiDeleteOutline class="text-error" /> <component :is="iconMap.delete" class="text-error" />
<!-- Delete --> <!-- Delete -->
{{ $t('general.delete') }} {{ $t('general.delete') }}
</div> </div>

60
packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts

@ -2,64 +2,68 @@ import type { PropType } from '@vue/runtime-core'
import type { ColumnType, LinkToAnotherRecordType, LookupType, RollupType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType, LookupType, RollupType } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { RelationTypes, UITypes } from 'nocodb-sdk' import { RelationTypes, UITypes } from 'nocodb-sdk'
import { ColumnInj, MetaInj, defineComponent, h, inject, isBt, isHm, isLookup, isMm, isRollup, ref, toRef } from '#imports' import {
import GenericIcon from '~icons/mdi/square-rounded' ColumnInj,
import HMIcon from '~icons/mdi/table-arrow-right' MetaInj,
import BTIcon from '~icons/mdi/table-arrow-left' defineComponent,
import MMIcon from '~icons/mdi/table-network' h,
import FormulaIcon from '~icons/mdi/math-integral' iconMap,
import QrCodeScan from '~icons/mdi/qrcode-scan' inject,
import BarcodeScan from '~icons/mdi/barcode-scan' isBt,
import RollupIcon from '~icons/mdi/movie-roll' isHm,
isLookup,
isMm,
isRollup,
ref,
toRef,
} from '#imports'
import CountIcon from '~icons/mdi/counter' import CountIcon from '~icons/mdi/counter'
import SpecificDBTypeIcon from '~icons/mdi/database-settings'
import MdiTextSearchVariant from '~icons/mdi/text-search-variant'
const renderIcon = (column: ColumnType, relationColumn?: ColumnType) => { const renderIcon = (column: ColumnType, relationColumn?: ColumnType) => {
switch (column.uidt) { switch (column.uidt) {
case UITypes.LinkToAnotherRecord: case UITypes.LinkToAnotherRecord:
switch ((column.colOptions as LinkToAnotherRecordType)?.type) { switch ((column.colOptions as LinkToAnotherRecordType)?.type) {
case RelationTypes.MANY_TO_MANY: case RelationTypes.MANY_TO_MANY:
return { icon: MMIcon, color: 'text-accent' } return { icon: iconMap.mm, color: 'text-accent' }
case RelationTypes.HAS_MANY: case RelationTypes.HAS_MANY:
return { icon: HMIcon, color: 'text-yellow-500' } return { icon: iconMap.hm, color: 'text-yellow-500' }
case RelationTypes.BELONGS_TO: case RelationTypes.BELONGS_TO:
return { icon: BTIcon, color: 'text-sky-500' } return { icon: iconMap.bt, color: 'text-sky-500' }
} }
break break
case UITypes.SpecificDBType: case UITypes.SpecificDBType:
return { icon: SpecificDBTypeIcon, color: 'text-grey' } return { icon: iconMap.specificDbType, color: 'text-grey' }
case UITypes.Formula: case UITypes.Formula:
return { icon: FormulaIcon, color: 'text-grey' } return { icon: iconMap.formula, color: 'text-grey' }
case UITypes.QrCode: case UITypes.QrCode:
return { icon: QrCodeScan, color: 'text-grey' } return { icon: iconMap.qrCode, color: 'text-grey' }
case UITypes.Barcode: case UITypes.Barcode:
return { icon: BarcodeScan, color: 'text-grey' } return { icon: iconMap.qrCode, color: 'text-grey' }
case UITypes.Lookup: case UITypes.Lookup:
switch ((relationColumn?.colOptions as LinkToAnotherRecordType)?.type) { switch ((relationColumn?.colOptions as LinkToAnotherRecordType)?.type) {
case RelationTypes.MANY_TO_MANY: case RelationTypes.MANY_TO_MANY:
return { icon: MdiTextSearchVariant, color: 'text-accent' } return { icon: iconMap.lookup, color: 'text-accent' }
case RelationTypes.HAS_MANY: case RelationTypes.HAS_MANY:
return { icon: MdiTextSearchVariant, color: 'text-yellow-500' } return { icon: iconMap.lookup, color: 'text-yellow-500' }
case RelationTypes.BELONGS_TO: case RelationTypes.BELONGS_TO:
return { icon: MdiTextSearchVariant, color: 'text-sky-500' } return { icon: iconMap.lookup, color: 'text-sky-500' }
} }
return { icon: MdiTextSearchVariant, color: 'text-grey' } return { icon: iconMap.lookup, color: 'text-grey' }
case UITypes.Rollup: case UITypes.Rollup:
switch ((relationColumn?.colOptions as LinkToAnotherRecordType)?.type) { switch ((relationColumn?.colOptions as LinkToAnotherRecordType)?.type) {
case RelationTypes.MANY_TO_MANY: case RelationTypes.MANY_TO_MANY:
return { icon: RollupIcon, color: 'text-accent' } return { icon: iconMap, color: 'text-accent' }
case RelationTypes.HAS_MANY: case RelationTypes.HAS_MANY:
return { icon: RollupIcon, color: 'text-yellow-500' } return { icon: iconMap, color: 'text-yellow-500' }
case RelationTypes.BELONGS_TO: case RelationTypes.BELONGS_TO:
return { icon: RollupIcon, color: 'text-sky-500' } return { icon: iconMap, color: 'text-sky-500' }
} }
return { icon: RollupIcon, color: 'text-grey' } return { icon: iconMap, color: 'text-grey' }
case UITypes.Count: case UITypes.Count:
return { icon: CountIcon, color: 'text-grey' } return { icon: CountIcon, color: 'text-grey' }
} }
return { icon: GenericIcon, color: 'text-grey' } return { icon: iconMap.generic, color: 'text-grey' }
} }
export default defineComponent({ export default defineComponent({
@ -91,7 +95,7 @@ export default defineComponent({
const { icon: Icon, color } = renderIcon(column.value, relationColumn) const { icon: Icon, color } = renderIcon(column.value, relationColumn)
return h(Icon, { class: `${color} mx-1 !text-xs` }) return h(Icon, { class: `${color} mx-1` })
} }
}, },
}) })

12
packages/nc-gui/components/smartsheet/sidebar/MenuBottom.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ViewTypes } from 'nocodb-sdk' import { ViewTypes } from 'nocodb-sdk'
import { useNuxtApp, useSmartsheetStoreOrThrow, viewIcons } from '#imports' import { iconMap, useNuxtApp, useSmartsheetStoreOrThrow, viewIcons } from '#imports'
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
@ -43,7 +43,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<div class="flex-1" /> <div class="flex-1" />
<mdi-plus class="group-hover:text-primary" /> <component :is="iconMap.plus" class="group-hover:text-primary" />
</div> </div>
</a-tooltip> </a-tooltip>
</a-menu-item> </a-menu-item>
@ -65,7 +65,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<div class="flex-1" /> <div class="flex-1" />
<mdi-plus class="group-hover:text-primary" /> <component :is="iconMap.plus" class="group-hover:text-primary" />
</div> </div>
</a-tooltip> </a-tooltip>
</a-menu-item> </a-menu-item>
@ -88,7 +88,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<div class="flex-1" /> <div class="flex-1" />
<mdi-plus class="group-hover:text-primary" /> <component :is="iconMap.plus" class="group-hover:text-primary" />
</div> </div>
</a-tooltip> </a-tooltip>
</a-menu-item> </a-menu-item>
@ -110,7 +110,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<div class="flex-1" /> <div class="flex-1" />
<mdi-plus class="group-hover:text-primary" /> <component :is="iconMap.plus" class="group-hover:text-primary" />
</div> </div>
</a-tooltip> </a-tooltip>
</a-menu-item> </a-menu-item>
@ -132,7 +132,7 @@ function onOpenModal(type: ViewTypes, title = '') {
<div class="flex-1" /> <div class="flex-1" />
<mdi-plus class="group-hover:text-primary" /> <component :is="iconMap.plus" class="group-hover:text-primary" />
</div> </div>
</a-tooltip> </a-tooltip>
</a-menu-item> </a-menu-item>

24
packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue

@ -3,7 +3,17 @@ import type { VNodeRef } from '@vue/runtime-core'
import type { KanbanType, ViewType, ViewTypes } from 'nocodb-sdk' import type { KanbanType, ViewType, ViewTypes } from 'nocodb-sdk'
import type { WritableComputedRef } from '@vue/reactivity' import type { WritableComputedRef } from '@vue/reactivity'
import { Tooltip } from 'ant-design-vue' import { Tooltip } from 'ant-design-vue'
import { IsLockedInj, inject, message, onKeyStroke, useDebounceFn, useNuxtApp, useUIPermission, useVModel } from '#imports' import {
IsLockedInj,
iconMap,
inject,
message,
onKeyStroke,
useDebounceFn,
useNuxtApp,
useUIPermission,
useVModel,
} from '#imports'
interface Props { interface Props {
view: ViewType view: ViewType
@ -199,7 +209,11 @@ function onStopEdit() {
{{ $t('activity.copyView') }} {{ $t('activity.copyView') }}
</template> </template>
<MdiContentCopy class="!hidden !group-hover:block text-gray-500 nc-view-copy-icon" @click.stop="onDuplicate" /> <component
:is="iconMap.copy"
class="!hidden !group-hover:block text-gray-500 nc-view-copy-icon"
@click.stop="onDuplicate"
/>
</a-tooltip> </a-tooltip>
<template v-if="!vModel.is_default"> <template v-if="!vModel.is_default">
@ -208,7 +222,11 @@ function onStopEdit() {
{{ $t('activity.deleteView') }} {{ $t('activity.deleteView') }}
</template> </template>
<MdiTrashCan class="!hidden !group-hover:block text-red-500 nc-view-delete-icon" @click.stop="onDelete" /> <component
:is="iconMap.delete"
class="!hidden !group-hover:block text-red-500 nc-view-delete-icon"
@click.stop="onDelete"
/>
</a-tooltip> </a-tooltip>
</template> </template>
</div> </div>

4
packages/nc-gui/components/smartsheet/sidebar/toolbar/DeleteCache.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { message, useApi, useI18n } from '#imports' import { iconMap, message, useApi, useI18n } from '#imports'
const { t } = useI18n() const { t } = useI18n()
@ -22,6 +22,6 @@ async function deleteCache() {
<span> Delete Cache </span> <span> Delete Cache </span>
</template> </template>
<mdi-delete class="cursor-pointer" @click="deleteCache" /> <component :is="iconMap.delete" class="cursor-pointer" @click="deleteCache" />
</a-tooltip> </a-tooltip>
</template> </template>

4
packages/nc-gui/components/smartsheet/sidebar/toolbar/DeleteTable.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { MetaInj, inject, useSidebar, useTable } from '#imports' import { MetaInj, iconMap, inject, useSidebar, useTable } from '#imports'
const meta = inject(MetaInj)! const meta = inject(MetaInj)!
@ -13,7 +13,7 @@ const { isOpen } = useSidebar('nc-right-sidebar')
<template #title> {{ $t('activity.deleteTable') }} </template> <template #title> {{ $t('activity.deleteTable') }} </template>
<div class="nc-sidebar-right-item hover:after:bg-red-500 group"> <div class="nc-sidebar-right-item hover:after:bg-red-500 group">
<MdiDeleteOutline class="cursor-pointer" @click="deleteTable(meta)" /> <component :is="iconMap.delete" class="cursor-pointer" @click="deleteTable(meta)" />
</div> </div>
</a-tooltip> </a-tooltip>
</template> </template>

4
packages/nc-gui/components/smartsheet/sidebar/toolbar/ExportCache.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { message, useApi, useI18n } from '#imports' import { iconMap, message, useApi, useI18n } from '#imports'
const { t } = useI18n() const { t } = useI18n()
@ -36,6 +36,6 @@ async function exportCache() {
<span> Export Cache </span> <span> Export Cache </span>
</template> </template>
<mdi-export class="cursor-pointer" @click="exportCache" /> <component :is="iconMap.export" class="cursor-pointer" @click="exportCache" />
</a-tooltip> </a-tooltip>
</template> </template>

8
packages/nc-gui/components/smartsheet/toolbar/AddRow.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { OpenNewRecordFormHookInj, inject } from '#imports' import { IsLockedInj, OpenNewRecordFormHookInj, iconMap, inject } from '#imports'
const isLocked = inject(IsLockedInj) const isLocked = inject(IsLockedInj)
@ -13,12 +13,14 @@ const onClick = () => {
<template> <template>
<a-tooltip placement="bottom"> <a-tooltip placement="bottom">
<template #title> {{ $t('activity.addRow') }} </template> <template #title> {{ $t('activity.addRow') }} </template>
<IonImageOutline />
<div <div
v-e="['c:row:add:grid-top']" v-e="['c:row:add:grid-top']"
:class="{ 'group': !isLocked, 'disabled-ring': isLocked }" :class="{ 'group': !isLocked, 'disabled-ring': isLocked }"
class="nc-add-new-row-btn nc-toolbar-btn flex min-w-32px w-32px h-32px items-center" class="nc-add-new-row-btn nc-toolbar-btn flex min-w-32px w-32px h-32px items-center justify-center !px-0 select-none"
> >
<MdiPlusOutline <component
:is="iconMap.plus"
:class="{ 'cursor-pointer text-gray-500 group-hover:(text-primary)': !isLocked, 'disabled': isLocked }" :class="{ 'cursor-pointer text-gray-500 group-hover:(text-primary)': !isLocked, 'disabled': isLocked }"
@click="onClick" @click="onClick"
/> />

11
packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue

@ -8,6 +8,7 @@ import {
comparisonOpList, comparisonOpList,
comparisonSubOpList, comparisonSubOpList,
computed, computed,
iconMap,
inject, inject,
ref, ref,
useNuxtApp, useNuxtApp,
@ -213,7 +214,8 @@ defineExpose({
<template v-for="(filter, i) in filters" :key="i"> <template v-for="(filter, i) in filters" :key="i">
<template v-if="filter.status !== 'delete'"> <template v-if="filter.status !== 'delete'">
<template v-if="filter.is_group"> <template v-if="filter.is_group">
<MdiCloseBox <component
:is="iconMap.closeBox"
v-if="!filter.readOnly" v-if="!filter.readOnly"
:key="i" :key="i"
small small
@ -251,7 +253,8 @@ defineExpose({
</div> </div>
</template> </template>
<template v-else> <template v-else>
<MdiCloseBox <component
:is="iconMap.closeBox"
v-if="!filter.readOnly" v-if="!filter.readOnly"
class="nc-filter-item-remove-btn text-grey self-center" class="nc-filter-item-remove-btn text-grey self-center"
@click.stop="deleteFilter(filter, i)" @click.stop="deleteFilter(filter, i)"
@ -363,7 +366,7 @@ defineExpose({
<div class="flex gap-2 mb-2 mt-4"> <div class="flex gap-2 mb-2 mt-4">
<a-button class="elevation-0 text-capitalize" type="primary" ghost @click.stop="addFilter"> <a-button class="elevation-0 text-capitalize" type="primary" ghost @click.stop="addFilter">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<MdiPlus /> <component :is="iconMap.plus" />
<!-- Add Filter --> <!-- Add Filter -->
{{ $t('activity.addFilter') }} {{ $t('activity.addFilter') }}
</div> </div>
@ -372,7 +375,7 @@ defineExpose({
<a-button v-if="!webHook" class="text-capitalize !text-gray-500" @click.stop="addFilterGroup"> <a-button v-if="!webHook" class="text-capitalize !text-gray-500" @click.stop="addFilterGroup">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<!-- Add Filter Group --> <!-- Add Filter Group -->
<MdiPlus /> <component :is="iconMap.plus" />
{{ $t('activity.addFilterGroup') }} {{ $t('activity.addFilterGroup') }}
</div> </div>
</a-button> </a-button>

5
packages/nc-gui/components/smartsheet/toolbar/ColumnFilterMenu.vue

@ -5,6 +5,7 @@ import {
IsLockedInj, IsLockedInj,
IsPublicInj, IsPublicInj,
computed, computed,
iconMap,
inject, inject,
ref, ref,
useGlobal, useGlobal,
@ -74,10 +75,10 @@ useMenuCloseOnEsc(open)
<div :class="{ 'nc-active-btn': filtersLength }"> <div :class="{ 'nc-active-btn': filtersLength }">
<a-button v-e="['c:filter']" class="nc-filter-menu-btn nc-toolbar-btn txt-sm" :disabled="isLocked"> <a-button v-e="['c:filter']" class="nc-filter-menu-btn nc-toolbar-btn txt-sm" :disabled="isLocked">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<MdiFilterOutline /> <component :is="iconMap.filter" />
<!-- Filter --> <!-- Filter -->
<span v-if="!isMobileMode" class="text-capitalize !text-xs font-weight-normal">{{ $t('activity.filter') }}</span> <span v-if="!isMobileMode" class="text-capitalize !text-xs font-weight-normal">{{ $t('activity.filter') }}</span>
<MdiMenuDown class="text-grey" /> <component :is="iconMap.arrowDown" class="text-grey" />
<span v-if="filtersLength" class="nc-count-badge">{{ filtersLength }}</span> <span v-if="filtersLength" class="nc-count-badge">{{ filtersLength }}</span>
</div> </div>

4
packages/nc-gui/components/smartsheet/toolbar/Erd.vue

@ -1,4 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { iconMap } from '#imports'
interface Props { interface Props {
modelValue: boolean modelValue: boolean
} }
@ -32,7 +34,7 @@ const selectedView = inject(ActiveViewInj)
</div> </div>
<div class="flex h-full items-center justify-center rounded group" @click="vModel = false"> <div class="flex h-full items-center justify-center rounded group" @click="vModel = false">
<MdiClose class="cursor-pointer mt-1 nc-modal-close group-hover:text-accent text-opacity-100" /> <component :is="iconMap.close" class="cursor-pointer mt-1 nc-modal-close group-hover:text-accent text-opacity-100" />
</div> </div>
</div> </div>

8
packages/nc-gui/components/smartsheet/toolbar/Export.vue

@ -1,10 +1,14 @@
<script lang="ts" setup>
import { iconMap } from '#imports'
</script>
<template> <template>
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-actions-menu"> <a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-actions-menu">
<a-button v-e="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn"> <a-button v-e="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn">
<div class="flex gap-2 items-center"> <div class="flex gap-2 items-center">
<MdiDownload class="group-hover:text-accent text-gray-500" /> <component :is="iconMap.download" class="group-hover:text-accent text-gray-500" />
<span class="text-capitalize !text-sm font-weight-normal">{{ $t('general.download') }}</span> <span class="text-capitalize !text-sm font-weight-normal">{{ $t('general.download') }}</span>
<MdiMenuDown class="text-grey" /> <component :is="iconMap.arrowDown" class="text-grey" />
</div> </div>
</a-button> </a-button>

5
packages/nc-gui/components/smartsheet/toolbar/ExportSubActions.vue

@ -7,6 +7,7 @@ import {
IsPublicInj, IsPublicInj,
MetaInj, MetaInj,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
iconMap,
inject, inject,
message, message,
ref, ref,
@ -99,7 +100,7 @@ const exportFile = async (exportType: ExportTypes) => {
<template> <template>
<a-menu-item> <a-menu-item>
<div v-e="['a:actions:download-csv']" class="nc-project-menu-item" @click="exportFile(ExportTypes.CSV)"> <div v-e="['a:actions:download-csv']" class="nc-project-menu-item" @click="exportFile(ExportTypes.CSV)">
<MdiDownloadOutline class="text-gray-500" /> <component :is="iconMap.csv" class="text-gray-500" />
<!-- Download as CSV --> <!-- Download as CSV -->
{{ $t('activity.downloadCSV') }} {{ $t('activity.downloadCSV') }}
</div> </div>
@ -107,7 +108,7 @@ const exportFile = async (exportType: ExportTypes) => {
<a-menu-item> <a-menu-item>
<div v-e="['a:actions:download-excel']" class="nc-project-menu-item" @click="exportFile(ExportTypes.EXCEL)"> <div v-e="['a:actions:download-excel']" class="nc-project-menu-item" @click="exportFile(ExportTypes.EXCEL)">
<MdiDownloadOutline class="text-gray-500" /> <component :is="iconMap.excel" class="text-gray-500" />
<!-- Download as XLSX --> <!-- Download as XLSX -->
{{ $t('activity.downloadExcel') }} {{ $t('activity.downloadExcel') }}
</div> </div>

9
packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue

@ -11,6 +11,7 @@ import {
MetaInj, MetaInj,
ReloadViewDataHookInj, ReloadViewDataHookInj,
computed, computed,
iconMap,
inject, inject,
ref, ref,
resolveComponent, resolveComponent,
@ -156,12 +157,12 @@ useMenuCloseOnEsc(open)
<div :class="{ 'nc-active-btn': numberOfHiddenFields }"> <div :class="{ 'nc-active-btn': numberOfHiddenFields }">
<a-button v-e="['c:fields']" class="nc-fields-menu-btn nc-toolbar-btn" :disabled="isLocked"> <a-button v-e="['c:fields']" class="nc-fields-menu-btn nc-toolbar-btn" :disabled="isLocked">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<MdiEyeOffOutline /> <component :is="iconMap.eye" />
<!-- Fields --> <!-- Fields -->
<span v-if="!isMobileMode" class="text-capitalize !text-xs font-weight-normal">{{ $t('objects.fields') }}</span> <span v-if="!isMobileMode" class="text-capitalize !text-xs font-weight-normal">{{ $t('objects.fields') }}</span>
<MdiMenuDown class="text-grey" /> <component :is="iconMap.arrowDown" class="text-grey" />
<span v-if="numberOfHiddenFields" class="nc-count-badge">{{ numberOfHiddenFields }}</span> <span v-if="numberOfHiddenFields" class="nc-count-badge">{{ numberOfHiddenFields }}</span>
</div> </div>
@ -218,7 +219,7 @@ useMenuCloseOnEsc(open)
<div class="flex-1" /> <div class="flex-1" />
<MdiDrag class="cursor-move" /> <component :is="iconMap.drag" class="cursor-move" />
</div> </div>
</template> </template>
<template v-if="activeView?.type === ViewTypes.GRID" #header> <template v-if="activeView?.type === ViewTypes.GRID" #header>
@ -234,7 +235,7 @@ useMenuCloseOnEsc(open)
<span class="text-sm">Display Value</span> <span class="text-sm">Display Value</span>
</template> </template>
<MdiTableKey class="text-xs" /> <component :is="iconMap.tableKey" class="text-xs" />
</a-tooltip> </a-tooltip>
<div class="flex items-center px-[8px]"> <div class="flex items-center px-[8px]">

5
packages/nc-gui/components/smartsheet/toolbar/KanbanStackEditOrAdd.vue

@ -3,6 +3,7 @@ import {
IsKanbanInj, IsKanbanInj,
IsLockedInj, IsLockedInj,
IsPublicInj, IsPublicInj,
iconMap,
inject, inject,
provide, provide,
ref, ref,
@ -44,11 +45,11 @@ provide(IsKanbanInj, ref(true))
:disabled="isLocked" :disabled="isLocked"
> >
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<MdiPlusCircleOutline /> <component :is="iconMap.plusCircle" />
<span class="text-capitalize !text-sm font-weight-normal"> <span class="text-capitalize !text-sm font-weight-normal">
{{ $t('activity.kanban.addOrEditStack') }} {{ $t('activity.kanban.addOrEditStack') }}
</span> </span>
<MdiMenuDown class="text-grey" /> <component :is="iconMap.arrowDown" class="text-grey" />
</div> </div>
</a-button> </a-button>
</div> </div>

20
packages/nc-gui/components/smartsheet/toolbar/LockType.vue

@ -1,9 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import MdiLockOutlineIcon from '~icons/mdi/lock-outline'
import MdiAccountIcon from '~icons/mdi/account'
import MdiAccountGroupIcon from '~icons/mdi/account-group'
import { LockType } from '~/lib' import { LockType } from '~/lib'
import { ActiveViewInj, inject } from '#imports' import { ActiveViewInj, iconMap, inject } from '#imports'
const { type, hideTick } = defineProps<{ hideTick?: boolean; type: LockType }>() const { type, hideTick } = defineProps<{ hideTick?: boolean; type: LockType }>()
@ -12,17 +9,17 @@ const emit = defineEmits(['select'])
const types = { const types = {
[LockType.Personal]: { [LockType.Personal]: {
title: 'title.personalView', title: 'title.personalView',
icon: MdiAccountIcon, icon: iconMap.account,
subtitle: 'msg.info.personalView', subtitle: 'msg.info.personalView',
}, },
[LockType.Collaborative]: { [LockType.Collaborative]: {
title: 'title.collabView', title: 'title.collabView',
icon: MdiAccountGroupIcon, icon: iconMap.users,
subtitle: 'msg.info.collabView', subtitle: 'msg.info.collabView',
}, },
[LockType.Locked]: { [LockType.Locked]: {
title: 'title.lockedView', title: 'title.lockedView',
icon: MdiLockOutlineIcon, icon: iconMap.lock,
subtitle: 'msg.info.lockedView', subtitle: 'msg.info.lockedView',
}, },
} }
@ -34,16 +31,17 @@ const selectedView = inject(ActiveViewInj)
<div class="nc-locked-menu-item" @click="emit('select', type)"> <div class="nc-locked-menu-item" @click="emit('select', type)">
<div :class="{ 'show-tick': !hideTick }"> <div :class="{ 'show-tick': !hideTick }">
<template v-if="!hideTick"> <template v-if="!hideTick">
<MdiCheck v-if="selectedView?.lock_type === type" /> <GeneralIcon v-if="selectedView?.lock_type === type" icon="check" />
<span v-else /> <span v-else />
</template> </template>
<div> <div>
<component :is="types[type].icon" class="text-gray-500" /> <div class="flex items-center gap-1">
<component :is="types[type].icon" class="text-gray-500" />
{{ $t(types[type].title) }}
{{ $t(types[type].title) }}
</div>
<div class="nc-subtitle whitespace-normal"> <div class="nc-subtitle whitespace-normal">
{{ $t(types[type].subtitle) }} {{ $t(types[type].subtitle) }}
</div> </div>

3
packages/nc-gui/components/smartsheet/toolbar/MappedBy.vue

@ -9,6 +9,7 @@ import {
MetaInj, MetaInj,
ReloadViewDataHookInj, ReloadViewDataHookInj,
computed, computed,
iconMap,
inject, inject,
ref, ref,
useViewColumns, useViewColumns,
@ -84,7 +85,7 @@ const handleChange = () => {
{{ $t('activity.map.mappedBy') }} {{ $t('activity.map.mappedBy') }}
<span class="font-bold">{{ geoDataFieldColumn?.title }}</span> <span class="font-bold">{{ geoDataFieldColumn?.title }}</span>
</span> </span>
<MdiMenuDown class="text-grey" /> <component :is="iconMap.arrowDown" class="text-grey" />
</div> </div>
</a-button> </a-button>
</div> </div>

4
packages/nc-gui/components/smartsheet/toolbar/QrScannerButton.vue

@ -4,7 +4,7 @@ import { ref } from 'vue'
import { StreamBarcodeReader } from 'vue-barcode-reader' import { StreamBarcodeReader } from 'vue-barcode-reader'
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import { NOCO, storeToRefs } from '#imports' import { NOCO, iconMap, storeToRefs } from '#imports'
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
@ -125,7 +125,7 @@ const onDecode = async (codeValue: string) => {
<div> <div>
<a-button class="nc-btn-find-row-by-scan nc-toolbar-btn" @click="showCodeScannerOverlay = true"> <a-button class="nc-btn-find-row-by-scan nc-toolbar-btn" @click="showCodeScannerOverlay = true">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<MdiQrcodeScan /> <component :is="iconMap.qrCode" />
<span v-if="!isMobileMode" class="!text-xs font-weight-normal"> {{ $t('activity.findRowByCodeScan') }}</span> <span v-if="!isMobileMode" class="!text-xs font-weight-normal"> {{ $t('activity.findRowByCodeScan') }}</span>
</div> </div>
</a-button> </a-button>

9
packages/nc-gui/components/smartsheet/toolbar/Reload.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ReloadViewDataHookInj, inject, ref, useNuxtApp, watch } from '#imports' import { ReloadViewDataHookInj, iconMap, inject, ref, useNuxtApp, watch } from '#imports'
const { $e, $state } = useNuxtApp() const { $e, $state } = useNuxtApp()
@ -25,9 +25,10 @@ const onClick = () => {
<a-tooltip placement="bottom"> <a-tooltip placement="bottom">
<template #title> {{ $t('general.reload') }} </template> <template #title> {{ $t('general.reload') }} </template>
<div class="nc-toolbar-btn flex min-w-32px w-32px h-32px items-center"> <div class="nc-toolbar-btn flex min-w-32px w-32px h-32px items-center justify-center select-none">
<MdiReload <component
class="w-full h-full cursor-pointer text-gray-500 group-hover:(text-primary) nc-toolbar-reload-btn" :is="iconMap.reload"
class="cursor-pointer text-gray-500 group-hover:(text-primary) nc-toolbar-reload-btn"
:class="isReloading ? 'animate-spin' : ''" :class="isReloading ? 'animate-spin' : ''"
@click="onClick" @click="onClick"
/> />

15
packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { GridType } from 'nocodb-sdk' import type { GridType } from 'nocodb-sdk'
import { ActiveViewInj, IsLockedInj, inject, ref, storeToRefs, useMenuCloseOnEsc } from '#imports' import { ActiveViewInj, IsLockedInj, iconMap, inject, ref, storeToRefs, useMenuCloseOnEsc } from '#imports'
const { isSharedBase } = storeToRefs(useProject()) const { isSharedBase } = storeToRefs(useProject())
@ -43,10 +43,9 @@ useMenuCloseOnEsc(open)
<div> <div>
<a-button v-e="['c:row-height']" class="nc-height-menu-btn nc-toolbar-btn" :disabled="isLocked"> <a-button v-e="['c:row-height']" class="nc-height-menu-btn nc-toolbar-btn" :disabled="isLocked">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<RiLineHeight /> <component :is="iconMap.rowHeight" />
<!-- Row Height --> <!-- Row Height -->
<MdiMenuDown class="text-grey" /> <component :is="iconMap.arrowDown" class="text-grey" />
</div> </div>
</a-button> </a-button>
</div> </div>
@ -55,19 +54,19 @@ useMenuCloseOnEsc(open)
<div class="text-gray-500 !text-xs px-4 py-2">Select a row height</div> <div class="text-gray-500 !text-xs px-4 py-2">Select a row height</div>
<div class="flex flex-col w-full text-sm" @click.stop> <div class="flex flex-col w-full text-sm" @click.stop>
<div class="nc-row-height-option" @click="updateRowHeight(0)"> <div class="nc-row-height-option" @click="updateRowHeight(0)">
<NcIconsRowHeightShort class="nc-row-height-icon" /> <GeneralIcon icon="heightShort" class="nc-row-height-icon" />
Short Short
</div> </div>
<div class="nc-row-height-option" @click="updateRowHeight(1)"> <div class="nc-row-height-option" @click="updateRowHeight(1)">
<NcIconsRowHeightMedium class="nc-row-height-icon" /> <GeneralIcon icon="heightMedium" class="nc-row-height-icon" />
Medium Medium
</div> </div>
<div class="nc-row-height-option" @click="updateRowHeight(2)"> <div class="nc-row-height-option" @click="updateRowHeight(2)">
<NcIconsRowHeightTall class="nc-row-height-icon" /> <GeneralIcon icon="heightTall" class="nc-row-height-icon" />
Tall Tall
</div> </div>
<div class="nc-row-height-option" @click="updateRowHeight(3)"> <div class="nc-row-height-option" @click="updateRowHeight(3)">
<NcIconsRowHeightExtraTall class="nc-row-height-icon" /> <GeneralIcon icon="heightExtra" class="nc-row-height-icon" />
Extra Extra
</div> </div>
</div> </div>

5
packages/nc-gui/components/smartsheet/toolbar/SearchData.vue

@ -4,6 +4,7 @@ import {
ActiveViewInj, ActiveViewInj,
ReloadViewDataHookInj, ReloadViewDataHookInj,
computed, computed,
iconMap,
inject, inject,
onClickOutside, onClickOutside,
ref, ref,
@ -55,9 +56,9 @@ function onPressEnter() {
:class="{ '!bg-gray-100 ': isDropdownOpen }" :class="{ '!bg-gray-100 ': isDropdownOpen }"
@click="isDropdownOpen = !isDropdownOpen" @click="isDropdownOpen = !isDropdownOpen"
> >
<MdiMagnify class="text-grey" /> <component :is="iconMap.search" class="text-grey" />
<MdiMenuDown class="text-grey" /> <component :is="iconMap.arrowDown" class="text-grey" />
<a-select <a-select
v-model:value="search.field" v-model:value="search.field"

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

@ -5,6 +5,7 @@ import tinycolor from 'tinycolor2'
import { import {
computed, computed,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
iconMap,
isRtlLang, isRtlLang,
message, message,
projectThemeColors, projectThemeColors,
@ -249,7 +250,7 @@ const copyIframeCode = async () => {
@click="genShareLink" @click="genShareLink"
> >
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<MdiOpenInNew /> <component :is="iconMap.share" />
<!-- Share View --> <!-- Share View -->
<span v-if="!isMobileMode" class="!text-xs font-weight-normal"> {{ $t('activity.shareView') }}</span> <span v-if="!isMobileMode" class="!text-xs font-weight-normal"> {{ $t('activity.shareView') }}</span>
</div> </div>
@ -272,17 +273,22 @@ const copyIframeCode = async () => {
<div class="flex-1 h-min text-xs text-gray-500">{{ sharedViewUrl }}</div> <div class="flex-1 h-min text-xs text-gray-500">{{ sharedViewUrl }}</div>
<a v-e="['c:view:share:open-url']" :href="sharedViewUrl" target="_blank"> <a v-e="['c:view:share:open-url']" :href="sharedViewUrl" target="_blank">
<MdiOpenInNew class="text-sm text-gray-500" /> <component :is="iconMap.share" class="text-sm text-gray-500" />
</a> </a>
<MdiContentCopy v-e="['c:view:share:copy-url']" class="text-gray-500 text-sm cursor-pointer" @click="copyLink" /> <component
:is="iconMap.copy"
v-e="['c:view:share:copy-url']"
class="text-gray-500 text-sm cursor-pointer"
@click="copyLink"
/>
</div> </div>
<div <div
class="flex gap-1 items-center pb-1 text-gray-500 cursor-pointer font-weight-medium mb-2 mt-4 pl-1" class="flex gap-1 items-center pb-1 text-gray-500 cursor-pointer font-weight-medium mb-2 mt-4 pl-1"
@click="copyIframeCode" @click="copyIframeCode"
> >
<MdiCodeTags class="text-gray-500" /> <component :is="iconMap.embed" class="text-gray-500" />
Embed this view in your site Embed this view in your site
</div> </div>

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

@ -3,6 +3,7 @@ import { ViewTypes } from 'nocodb-sdk'
import { import {
Empty, Empty,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
iconMap,
message, message,
onMounted, onMounted,
parseProp, parseProp,
@ -162,8 +163,8 @@ const deleteLink = async (id: string) => {
<a-table-column key="id" :title="$t('labels.actions')" data-index="title"> <a-table-column key="id" :title="$t('labels.actions')" data-index="title">
<template #default="{ record }"> <template #default="{ record }">
<div class="text-sm flex gap-2" :title="text"> <div class="text-sm flex gap-2" :title="text">
<MdiContentCopy class="cursor-pointer" @click="copyLink(record)" /> <component :is="iconMap.copy" class="cursor-pointer" @click="copyLink(record)" />
<MdiDeleteOutline class="cursor-pointer" @click="deleteLink(record.id)" /> <component :is="iconMap.delete" class="cursor-pointer" @click="deleteLink(record.id)" />
</div> </div>
</template> </template>
</a-table-column> </a-table-column>

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

@ -8,6 +8,7 @@ import {
ReloadViewDataHookInj, ReloadViewDataHookInj,
computed, computed,
getSortDirectionOptions, getSortDirectionOptions,
iconMap,
inject, inject,
ref, ref,
useMenuCloseOnEsc, useMenuCloseOnEsc,
@ -75,11 +76,11 @@ useMenuCloseOnEsc(open)
<div :class="{ 'nc-badge nc-active-btn': sorts?.length }"> <div :class="{ 'nc-badge nc-active-btn': sorts?.length }">
<a-button v-e="['c:sort']" class="nc-sort-menu-btn nc-toolbar-btn" :disabled="isLocked"> <a-button v-e="['c:sort']" class="nc-sort-menu-btn nc-toolbar-btn" :disabled="isLocked">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<MdiSort /> <component :is="iconMap.sort" />
<!-- Sort --> <!-- Sort -->
<span v-if="!isMobileMode" class="text-capitalize !text-xs font-weight-normal">{{ $t('activity.sort') }}</span> <span v-if="!isMobileMode" class="text-capitalize !text-xs font-weight-normal">{{ $t('activity.sort') }}</span>
<MdiMenuDown class="text-grey" /> <component :is="iconMap.arrowDown" class="text-grey" />
<span v-if="sorts?.length" class="nc-count-badge">{{ sorts.length }}</span> <span v-if="sorts?.length" class="nc-count-badge">{{ sorts.length }}</span>
</div> </div>
@ -93,7 +94,8 @@ useMenuCloseOnEsc(open)
> >
<div v-if="sorts?.length" class="sort-grid mb-2 max-h-420px overflow-y-auto" @click.stop> <div v-if="sorts?.length" class="sort-grid mb-2 max-h-420px overflow-y-auto" @click.stop>
<template v-for="(sort, i) of sorts" :key="i"> <template v-for="(sort, i) of sorts" :key="i">
<MdiCloseBox <component
:is="iconMap.closeBox"
ref="removeIcon" ref="removeIcon"
class="nc-sort-item-remove-btn text-grey self-center" class="nc-sort-item-remove-btn text-grey self-center"
small small
@ -131,7 +133,7 @@ useMenuCloseOnEsc(open)
<a-button class="text-capitalize mb-1 mt-4" type="primary" ghost @click.stop="addSort"> <a-button class="text-capitalize mb-1 mt-4" type="primary" ghost @click.stop="addSort">
<div class="flex gap-1 items-center"> <div class="flex gap-1 items-center">
<MdiPlus /> <component :is="iconMap.plus" />
<!-- Add Sort Option --> <!-- Add Sort Option -->
{{ $t('activity.addSort') }} {{ $t('activity.addSort') }}
</div> </div>

3
packages/nc-gui/components/smartsheet/toolbar/StackedBy.vue

@ -9,6 +9,7 @@ import {
MetaInj, MetaInj,
ReloadViewDataHookInj, ReloadViewDataHookInj,
computed, computed,
iconMap,
inject, inject,
ref, ref,
useKanbanViewStoreOrThrow, useKanbanViewStoreOrThrow,
@ -94,7 +95,7 @@ const handleChange = () => {
{{ $t('activity.kanban.stackedBy') }} {{ $t('activity.kanban.stackedBy') }}
<span class="font-bold">{{ groupingField }}</span> <span class="font-bold">{{ groupingField }}</span>
</span> </span>
<MdiMenuDown class="text-grey" /> <component :is="iconMap.arrowDown" class="text-grey" />
</div> </div>
</a-button> </a-button>
</div> </div>

38
packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue

@ -6,6 +6,7 @@ import {
IsPublicInj, IsPublicInj,
MetaInj, MetaInj,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
iconMap,
inject, inject,
message, message,
ref, ref,
@ -18,9 +19,6 @@ import {
useUIPermission, useUIPermission,
} from '#imports' } from '#imports'
import { LockType } from '~/lib' import { LockType } from '~/lib'
import MdiLockOutlineIcon from '~icons/mdi/lock-outline'
import MdiAccountIcon from '~icons/mdi/account'
import MdiAccountGroupIcon from '~icons/mdi/account-group'
const { t } = useI18n() const { t } = useI18n()
@ -70,12 +68,12 @@ const currentBaseId = computed(() => meta.value?.base_id)
const Icon = computed(() => { const Icon = computed(() => {
switch (selectedView.value?.lock_type) { switch (selectedView.value?.lock_type) {
case LockType.Personal: case LockType.Personal:
return MdiAccountIcon return iconMap.account
case LockType.Locked: case LockType.Locked:
return MdiLockOutlineIcon return iconMap.lock
case LockType.Collaborative: case LockType.Collaborative:
default: default:
return MdiAccountGroupIcon return iconMap.users
} }
}) })
@ -120,7 +118,7 @@ useMenuCloseOnEsc(open)
<component :is="Icon" class="text-gray-500" :class="`nc-icon-${selectedView?.lock_type}`" /> <component :is="Icon" class="text-gray-500" :class="`nc-icon-${selectedView?.lock_type}`" />
<MdiMenuDown class="text-grey" /> <component :is="iconMap.arrowDown" class="text-grey" />
</div> </div>
</a-button> </a-button>
@ -136,9 +134,7 @@ useMenuCloseOnEsc(open)
<div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group px-0 !py-0"> <div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group px-0 !py-0">
<LazySmartsheetToolbarLockType hide-tick :type="lockType" /> <LazySmartsheetToolbarLockType hide-tick :type="lockType" />
<MaterialSymbolsChevronRightRounded <component :is="iconMap.arrowRight" class="transform group-hover:(scale-115 text-accent) text-gray-400" />
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400"
/>
</div> </div>
</template> </template>
@ -162,13 +158,11 @@ useMenuCloseOnEsc(open)
<template #title> <template #title>
<!-- Download --> <!-- Download -->
<div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group"> <div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group">
<MdiDownload class="group-hover:text-accent text-gray-500" /> <component :is="iconMap.download" class="group-hover:text-accent text-gray-500" />
{{ $t('general.download') }} {{ $t('general.download') }}
<div class="flex-1" /> <div class="flex-1" />
<MaterialSymbolsChevronRightRounded <component :is="iconMap.arrowRight" class="transform group-hover:(scale-115 text-accent) text-gray-400" />
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400"
/>
</div> </div>
</template> </template>
@ -182,13 +176,11 @@ useMenuCloseOnEsc(open)
<!-- Upload --> <!-- Upload -->
<template #title> <template #title>
<div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group"> <div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group">
<MdiUpload class="group-hover:text-accent text-gray-500" /> <component :is="iconMap.upload" class="group-hover:text-accent text-gray-500" />
{{ $t('general.upload') }} {{ $t('general.upload') }}
<div class="flex-1" /> <div class="flex-1" />
<MaterialSymbolsChevronRightRounded <component :is="iconMap.arrowRight" class="transform group-hover:(scale-115 text-accent) text-gray-400" />
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400"
/>
</div> </div>
</template> </template>
@ -201,7 +193,7 @@ useMenuCloseOnEsc(open)
:class="{ disabled: isLocked }" :class="{ disabled: isLocked }"
@click="!isLocked ? (dialog.value = true) : {}" @click="!isLocked ? (dialog.value = true) : {}"
> >
<MdiUploadOutline class="text-gray-500" /> <component :is="iconMap.upload" class="text-gray-500" />
{{ `${$t('general.upload')} ${type.toUpperCase()}` }} {{ `${$t('general.upload')} ${type.toUpperCase()}` }}
<div class="flex items-center text-gray-400"><MdiAlpha />version</div> <div class="flex items-center text-gray-400"><MdiAlpha />version</div>
</div> </div>
@ -214,7 +206,7 @@ useMenuCloseOnEsc(open)
<a-menu-item v-if="isUIAllowed('SharedViewList') && !isView && !isPublicView"> <a-menu-item v-if="isUIAllowed('SharedViewList') && !isView && !isPublicView">
<div v-e="['a:actions:shared-view-list']" class="py-2 flex gap-2 items-center" @click="sharedViewListDlg = true"> <div v-e="['a:actions:shared-view-list']" class="py-2 flex gap-2 items-center" @click="sharedViewListDlg = true">
<MdiViewListOutline class="text-gray-500" /> <component :is="iconMap.list" class="text-gray-500" />
<!-- Shared View List --> <!-- Shared View List -->
{{ $t('activity.listSharedView') }} {{ $t('activity.listSharedView') }}
</div> </div>
@ -227,14 +219,14 @@ useMenuCloseOnEsc(open)
class="py-2 flex gap-2 items-center" class="py-2 flex gap-2 items-center"
@click="showWebhookDrawer = true" @click="showWebhookDrawer = true"
> >
<MdiHook class="text-gray-500" /> <component :is="iconMap.hook" class="text-gray-500" />
{{ $t('objects.webhooks') }} {{ $t('objects.webhooks') }}
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item v-if="!isSharedBase && !isPublicView && !isMobileMode"> <a-menu-item v-if="!isSharedBase && !isPublicView && !isMobileMode">
<div v-e="['c:snippet:open']" class="py-2 flex gap-2 items-center" @click="showApiSnippetDrawer = true"> <div v-e="['c:snippet:open']" class="py-2 flex gap-2 items-center" @click="showApiSnippetDrawer = true">
<MdiXml class="text-gray-500" /> <component :is="iconMap.snippet" class="text-gray-500" />
<!-- Get API Snippet --> <!-- Get API Snippet -->
{{ $t('activity.getApiSnippet') }} {{ $t('activity.getApiSnippet') }}
</div> </div>
@ -247,7 +239,7 @@ useMenuCloseOnEsc(open)
class="py-2 flex gap-2 items-center nc-view-action-erd" class="py-2 flex gap-2 items-center nc-view-action-erd"
@click="showErd = true" @click="showErd = true"
> >
<MaterialSymbolsAccountTreeRounded class="text-gray-500" /> <component :is="iconMap.erd" class="text-gray-500" />
{{ $t('title.erdView') }} {{ $t('title.erdView') }}
</div> </div>
</a-menu-item> </a-menu-item>

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

@ -1,6 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ApiTokenType } from 'nocodb-sdk' import type { ApiTokenType } from 'nocodb-sdk'
import { extractSdkResponseErrorMsg, message, onMounted, storeToRefs, useCopy, useI18n, useNuxtApp, useProject } from '#imports' import {
extractSdkResponseErrorMsg,
iconMap,
message,
onMounted,
storeToRefs,
useCopy,
useI18n,
useNuxtApp,
useProject,
} from '#imports'
interface ApiToken extends ApiTokenType { interface ApiToken extends ApiTokenType {
show?: boolean show?: boolean
@ -159,7 +169,7 @@ onMounted(() => {
<div class="flex flex-row space-x-1"> <div class="flex flex-row space-x-1">
<a-button size="middle" type="text" @click="loadApiTokens()"> <a-button size="middle" type="text" @click="loadApiTokens()">
<div class="flex flex-row justify-center items-center caption capitalize space-x-1"> <div class="flex flex-row justify-center items-center caption capitalize space-x-1">
<MdiReload class="text-gray-500" /> <component :is="iconMap.reload" class="text-gray-500" />
<div class="text-gray-500">{{ $t('general.reload') }}</div> <div class="text-gray-500">{{ $t('general.reload') }}</div>
</div> </div>
</a-button> </a-button>
@ -167,7 +177,7 @@ onMounted(() => {
<!-- Add New Token --> <!-- Add New Token -->
<a-button size="middle" type="primary" ghost @click="openNewTokenModal"> <a-button size="middle" type="primary" ghost @click="openNewTokenModal">
<div class="flex flex-row justify-center items-center caption capitalize space-x-1"> <div class="flex flex-row justify-center items-center caption capitalize space-x-1">
<MdiPlus /> <component :is="iconMap.plus" />
<div>{{ $t('activity.newToken') }}</div> <div>{{ $t('activity.newToken') }}</div>
</div> </div>
</a-button> </a-button>
@ -212,7 +222,7 @@ onMounted(() => {
<a-button type="text" class="!rounded-md" @click="copyToken(item.token)"> <a-button type="text" class="!rounded-md" @click="copyToken(item.token)">
<template #icon> <template #icon>
<MdiContentCopy class="flex mx-auto h-[1rem]" /> <component :is="iconMap.copy" class="flex mx-auto h-[1rem]" />
</template> </template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -235,7 +245,7 @@ onMounted(() => {
<a-menu> <a-menu>
<a-menu-item> <a-menu-item>
<div class="flex flex-row items-center py-3 h-[1rem]" @click="openDeleteModal(item)"> <div class="flex flex-row items-center py-3 h-[1rem]" @click="openDeleteModal(item)">
<MdiDeleteOutline class="flex" /> <component :is="iconMap.delete" class="flex" />
<div class="text-xs pl-2">{{ $t('general.remove') }}</div> <div class="text-xs pl-2">{{ $t('general.remove') }}</div>
</div> </div>
</a-menu-item> </a-menu-item>

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

@ -3,6 +3,7 @@ import { OrgUserRoles } from 'nocodb-sdk'
import type { ProjectUserReqType, RequestParams } from 'nocodb-sdk' import type { ProjectUserReqType, RequestParams } from 'nocodb-sdk'
import { import {
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
iconMap,
message, message,
onBeforeMount, onBeforeMount,
projectRoleTagColors, projectRoleTagColors,
@ -229,7 +230,7 @@ const isSuperAdmin = (user: { main_roles?: string }) => {
<div class="flex flex-row space-x-1"> <div class="flex flex-row space-x-1">
<a-button v-e="['a:user:reload']" size="middle" type="text" @click="loadUsers()"> <a-button v-e="['a:user:reload']" size="middle" type="text" @click="loadUsers()">
<div class="flex flex-row justify-center items-center caption capitalize space-x-1"> <div class="flex flex-row justify-center items-center caption capitalize space-x-1">
<MdiReload class="text-gray-500" /> <component :is="iconMap.reload" class="text-gray-500" />
<div class="text-gray-500">{{ $t('general.reload') }}</div> <div class="text-gray-500">{{ $t('general.reload') }}</div>
</div> </div>
</a-button> </a-button>
@ -244,7 +245,7 @@ const isSuperAdmin = (user: { main_roles?: string }) => {
@click="onInvite" @click="onInvite"
> >
<div class="flex flex-row justify-center items-center caption capitalize space-x-1"> <div class="flex flex-row justify-center items-center caption capitalize space-x-1">
<MdiAccountPlusOutline class="mr-1" /> <component :is="iconMap.accountPlus" class="mr-1" />
<div>{{ $t('activity.inviteTeam') }}</div> <div>{{ $t('activity.inviteTeam') }}</div>
</div> </div>
</a-button> </a-button>
@ -254,12 +255,12 @@ const isSuperAdmin = (user: { main_roles?: string }) => {
<div class="px-5"> <div class="px-5">
<div class="flex flex-row border-b-1 pb-2 px-2"> <div class="flex flex-row border-b-1 pb-2 px-2">
<div class="flex flex-row w-4/6 space-x-1 items-center pl-1"> <div class="flex flex-row w-4/6 space-x-1 items-center pl-1">
<EvaEmailOutline class="flex text-gray-500 -mt-0.5" /> <component :is="iconMap.email" class="flex text-gray-500 -mt-0.5" />
<div class="text-gray-600 text-xs space-x-1">{{ $t('labels.email') }}</div> <div class="text-gray-600 text-xs space-x-1">{{ $t('labels.email') }}</div>
</div> </div>
<div class="flex flex-row justify-center w-1/6 space-x-1 items-center pl-1"> <div class="flex flex-row justify-center w-1/6 space-x-1 items-center pl-1">
<MdiDramaMasks class="flex text-gray-500 -mt-0.5" /> <component :is="iconMap.role" class="flex text-gray-500 -mt-0.5" />
<div class="text-gray-600 text-xs">{{ $t('objects.role') }}</div> <div class="text-gray-600 text-xs">{{ $t('objects.role') }}</div>
</div> </div>
@ -311,7 +312,7 @@ const isSuperAdmin = (user: { main_roles?: string }) => {
<a-button type="text" class="!rounded-md nc-user-invite" @click="inviteUser(user)"> <a-button type="text" class="!rounded-md nc-user-invite" @click="inviteUser(user)">
<template #icon> <template #icon>
<MdiPlus class="flex mx-auto h-[1.1rem] text-gray-500" /> <component :is="iconMap.plus" class="flex mx-auto h-[1.1rem] text-gray-500" />
</template> </template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -324,7 +325,7 @@ const isSuperAdmin = (user: { main_roles?: string }) => {
<a-button v-e="['c:user:delete']" type="text" class="!rounded-md nc-user-delete" @click="onDelete(user)"> <a-button v-e="['c:user:delete']" type="text" class="!rounded-md nc-user-delete" @click="onDelete(user)">
<template #icon> <template #icon>
<MdiDeleteOutline class="flex mx-auto h-[1.1rem] text-gray-500" /> <component :is="iconMap.delete" class="flex mx-auto h-[1.1rem] text-gray-500" />
</template> </template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -349,13 +350,13 @@ const isSuperAdmin = (user: { main_roles?: string }) => {
<a-menu-item> <a-menu-item>
<!-- Resend invite Email --> <!-- Resend invite Email -->
<div class="flex flex-row items-center py-3" @click="resendInvite(user)"> <div class="flex flex-row items-center py-3" @click="resendInvite(user)">
<MdiEmailArrowRightOutline class="flex h-[1rem] text-gray-500" /> <component :is="iconMap.sendEmail" class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.resendInvite') }}</div> <div class="text-xs pl-2">{{ $t('activity.resendInvite') }}</div>
</div> </div>
</a-menu-item> </a-menu-item>
<a-menu-item> <a-menu-item>
<div class="flex flex-row items-center py-3" @click="copyInviteUrl(user)"> <div class="flex flex-row items-center py-3" @click="copyInviteUrl(user)">
<MdiContentCopy class="flex h-[1rem] text-gray-500" /> <component :is="iconMap.copy" class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.copyInviteURL') }}</div> <div class="text-xs pl-2">{{ $t('activity.copyInviteURL') }}</div>
</div> </div>
</a-menu-item> </a-menu-item>

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

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
iconMap,
message, message,
onMounted, onMounted,
storeToRefs, storeToRefs,
@ -153,7 +154,7 @@ onMounted(() => {
<template> <template>
<div class="flex flex-col w-full" data-testid="nc-share-base-sub-modal"> <div class="flex flex-col w-full" data-testid="nc-share-base-sub-modal">
<div class="flex flex-row items-center space-x-0.5 pl-2 h-[0.8rem]"> <div class="flex flex-row items-center space-x-0.5 pl-2 h-[0.8rem]">
<MdiOpenInNew /> <component :is="iconMap.share" />
<div class="text-xs">{{ $t('activity.shareBase.link') }}</div> <div class="text-xs">{{ $t('activity.shareBase.link') }}</div>
</div> </div>
@ -169,7 +170,7 @@ onMounted(() => {
<a-button type="text" class="!rounded-md mr-1 -mt-1.5 h-[1rem]" @click="recreate"> <a-button type="text" class="!rounded-md mr-1 -mt-1.5 h-[1rem]" @click="recreate">
<template #icon> <template #icon>
<MdiReload class="flex mx-auto text-gray-600" /> <component :is="iconMap.reload" class="flex mx-auto text-gray-600" />
</template> </template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -181,7 +182,7 @@ onMounted(() => {
<a-button type="text" class="!rounded-md mr-1 -mt-1.5 h-[1rem]" @click="copyUrl"> <a-button type="text" class="!rounded-md mr-1 -mt-1.5 h-[1rem]" @click="copyUrl">
<template #icon> <template #icon>
<MdiContentCopy class="flex mx-auto text-gray-600" /> <component :is="iconMap.copy" class="flex mx-auto text-gray-600" />
</template> </template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -193,7 +194,7 @@ onMounted(() => {
<a-button type="text" class="!rounded-md mr-1 -mt-1.5 h-[1rem]" @click="navigateToSharedBase"> <a-button type="text" class="!rounded-md mr-1 -mt-1.5 h-[1rem]" @click="navigateToSharedBase">
<template #icon> <template #icon>
<MdiOpenInNew class="flex mx-auto text-gray-600" /> <component :is="iconMap.share" class="flex mx-auto text-gray-600" />
</template> </template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -205,7 +206,7 @@ onMounted(() => {
<a-button type="text" class="!rounded-md mr-1 -mt-1.5 h-[1rem]" @click="generateEmbeddableIframe"> <a-button type="text" class="!rounded-md mr-1 -mt-1.5 h-[1rem]" @click="generateEmbeddableIframe">
<template #icon> <template #icon>
<MdiXml class="flex mx-auto text-gray-600" /> <component :is="iconMap.xml" class="flex mx-auto text-gray-600" />
</template> </template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>

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

@ -6,6 +6,7 @@ import {
computed, computed,
emailValidator, emailValidator,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
iconMap,
message, message,
onMounted, onMounted,
projectRoleTagColors, projectRoleTagColors,
@ -191,7 +192,7 @@ watch(
<template v-if="usersData.invitationToken"> <template v-if="usersData.invitationToken">
<div class="flex flex-col mt-1 border-b-1 pb-5"> <div class="flex flex-col mt-1 border-b-1 pb-5">
<div class="flex flex-row items-center pl-1.5 pb-1 h-[1.1rem]"> <div class="flex flex-row items-center pl-1.5 pb-1 h-[1.1rem]">
<MdiAccountOutline /> <component :is="iconMap.account" />
<div class="text-xs ml-0.5 mt-0.5">Copy Invite Token</div> <div class="text-xs ml-0.5 mt-0.5">Copy Invite Token</div>
</div> </div>
@ -204,7 +205,7 @@ watch(
<a-button type="text" class="!rounded-md -mt-0.5" @click="copyUrl"> <a-button type="text" class="!rounded-md -mt-0.5" @click="copyUrl">
<template #icon> <template #icon>
<MdiContentCopy class="flex mx-auto text-green-700 h-[1rem]" /> <component :is="iconMap.copy" class="flex mx-auto text-green-700 h-[1rem]" />
</template> </template>
</a-button> </a-button>
</div> </div>
@ -230,7 +231,7 @@ watch(
<div v-else class="flex flex-col pb-4"> <div v-else class="flex flex-col pb-4">
<div class="flex flex-row items-center pl-2 pb-1 h-[1rem]"> <div class="flex flex-row items-center pl-2 pb-1 h-[1rem]">
<MdiAccountOutline /> <component :is="iconMap.account" />
<div class="text-xs ml-0.5 mt-0.5">{{ selectedUser ? $t('activity.editUser') : $t('activity.inviteTeam') }}</div> <div class="text-xs ml-0.5 mt-0.5">{{ selectedUser ? $t('activity.editUser') : $t('activity.inviteTeam') }}</div>
</div> </div>

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

@ -17,6 +17,7 @@ import {
getDateFormat, getDateFormat,
getDateTimeFormat, getDateTimeFormat,
getUIDTIcon, getUIDTIcon,
iconMap,
inject, inject,
message, message,
nextTick, nextTick,
@ -635,7 +636,7 @@ function isSelectDisabled(uidt: string, disableSelect = false) {
<a-collapse-panel v-for="(table, tableIdx) of data.tables" :key="tableIdx"> <a-collapse-panel v-for="(table, tableIdx) of data.tables" :key="tableIdx">
<template #header> <template #header>
<span class="font-weight-bold text-lg flex items-center gap-2"> <span class="font-weight-bold text-lg flex items-center gap-2">
<mdi-table class="text-primary" /> <component :is="iconMap.table" class="text-primary" />
{{ table.table_name }} {{ table.table_name }}
</span> </span>
</template> </template>
@ -646,7 +647,12 @@ function isSelectDisabled(uidt: string, disableSelect = false) {
<!-- TODO: i18n --> <!-- TODO: i18n -->
<span>Delete Table</span> <span>Delete Table</span>
</template> </template>
<mdi-delete-outline v-if="data.tables.length > 1" class="text-lg mr-8" @click.stop="deleteTable(tableIdx)" /> <component
:is="iconMap.delete"
v-if="data.tables.length > 1"
class="text-lg mr-8"
@click.stop="deleteTable(tableIdx)"
/>
</a-tooltip> </a-tooltip>
</template> </template>
@ -726,7 +732,7 @@ function isSelectDisabled(uidt: string, disableSelect = false) {
/> />
</a-form-item> </a-form-item>
<span v-else class="font-weight-bold text-lg flex items-center gap-2" @click="setEditableTn(tableIdx, true)"> <span v-else class="font-weight-bold text-lg flex items-center gap-2" @click="setEditableTn(tableIdx, true)">
<mdi-table class="text-primary" /> <component :is="iconMap.table" class="text-primary" />
{{ table.table_name }} {{ table.table_name }}
</span> </span>
</template> </template>
@ -737,7 +743,12 @@ function isSelectDisabled(uidt: string, disableSelect = false) {
<!-- TODO: i18n --> <!-- TODO: i18n -->
<span>Delete Table</span> <span>Delete Table</span>
</template> </template>
<mdi-delete-outline v-if="data.tables.length > 1" class="text-lg mr-8" @click.stop="deleteTable(tableIdx)" /> <component
:is="iconMap.delete"
v-if="data.tables.length > 1"
class="text-lg mr-8"
@click.stop="deleteTable(tableIdx)"
/>
</a-tooltip> </a-tooltip>
</template> </template>
<a-table <a-table
@ -832,7 +843,7 @@ function isSelectDisabled(uidt: string, disableSelect = false) {
<a-button type="text" @click="deleteTableColumn(tableIdx, record.key)"> <a-button type="text" @click="deleteTableColumn(tableIdx, record.key)">
<div class="flex items-center"> <div class="flex items-center">
<mdi-delete-outline class="text-lg" /> <component :is="iconMap.delete" class="text-lg" />
</div> </div>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -849,7 +860,7 @@ function isSelectDisabled(uidt: string, disableSelect = false) {
<a-button class="group" @click="addNewColumnRow(tableIdx, 'Number')"> <a-button class="group" @click="addNewColumnRow(tableIdx, 'Number')">
<div class="flex items-center"> <div class="flex items-center">
<mdi-numeric class="group-hover:!text-accent flex text-lg" /> <component :is="iconMap.number" class="group-hover:!text-accent flex text-lg" />
</div> </div>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -862,7 +873,7 @@ function isSelectDisabled(uidt: string, disableSelect = false) {
<a-button class="group" @click="addNewColumnRow(tableIdx, 'SingleLineText')"> <a-button class="group" @click="addNewColumnRow(tableIdx, 'SingleLineText')">
<div class="flex items-center"> <div class="flex items-center">
<mdi-alpha-a class="group-hover:!text-accent text-lg" /> <component :is="iconMap.text" class="group-hover:!text-accent text-lg" />
</div> </div>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -875,7 +886,7 @@ function isSelectDisabled(uidt: string, disableSelect = false) {
<a-button class="group" @click="addNewColumnRow(tableIdx, 'LongText')"> <a-button class="group" @click="addNewColumnRow(tableIdx, 'LongText')">
<div class="flex items-center"> <div class="flex items-center">
<mdi-text class="group-hover:!text-accent text-lg" /> <component :is="iconMap.longText" class="group-hover:!text-accent text-lg" />
</div> </div>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -888,7 +899,7 @@ function isSelectDisabled(uidt: string, disableSelect = false) {
<a-button class="group" @click="addNewColumnRow(tableIdx, 'SingleLineText')"> <a-button class="group" @click="addNewColumnRow(tableIdx, 'SingleLineText')">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<mdi-plus class="group-hover:!text-accent text-lg" /> <component :is="iconMap.plus" class="group-hover:!text-accent text-lg" />
</div> </div>
</a-button> </a-button>
</a-tooltip> </a-tooltip>

8
packages/nc-gui/components/virtual-cell/BelongsTo.vue

@ -19,8 +19,6 @@ import {
useSmartsheetRowStoreOrThrow, useSmartsheetRowStoreOrThrow,
useUIPermission, useUIPermission,
} from '#imports' } from '#imports'
import MdiArrowExpand from '~icons/mdi/arrow-expand'
import MdiPlus from '~icons/mdi/plus'
const column = inject(ColumnInj)! const column = inject(ColumnInj)!
@ -53,7 +51,7 @@ const { loadRelatedTableMeta, relatedTableDisplayValueProp, unlink } = useProvid
await loadRelatedTableMeta() await loadRelatedTableMeta()
const addIcon = computed(() => (cellValue?.value ? MdiArrowExpand : MdiPlus)) const addIcon = computed(() => (cellValue?.value ? 'expand' : 'plus'))
const value = computed(() => { const value = computed(() => {
if (cellValue?.value) { if (cellValue?.value) {
@ -94,8 +92,8 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
v-if="!readOnly && !isLocked && (isUIAllowed('xcDatatableEditable') || isForm)" v-if="!readOnly && !isLocked && (isUIAllowed('xcDatatableEditable') || isForm)"
class="flex justify-end gap-1 min-h-[30px] items-center" class="flex justify-end gap-1 min-h-[30px] items-center"
> >
<component <GeneralIcon
:is="addIcon" :icon="addIcon"
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 select-none group-hover:(text-gray-500) nc-plus" class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 select-none group-hover:(text-gray-500) nc-plus"
@click.stop="listItemsDlg = true" @click.stop="listItemsDlg = true"
/> />

6
packages/nc-gui/components/virtual-cell/HasMany.vue

@ -113,13 +113,15 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
</div> </div>
<div v-if="!isLocked" class="flex justify-end gap-1 min-h-[30px] items-center"> <div v-if="!isLocked" class="flex justify-end gap-1 min-h-[30px] items-center">
<MdiArrowExpand <GeneralIcon
icon="expand"
class="select-none transform text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand" class="select-none transform text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand"
@click.stop="childListDlg = true" @click.stop="childListDlg = true"
/> />
<MdiPlus <GeneralIcon
v-if="!readOnly && isUIAllowed('xcDatatableEditable')" v-if="!readOnly && isUIAllowed('xcDatatableEditable')"
icon="plus"
class="select-none text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-plus" class="select-none text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-plus"
@click.stop="listItemsDlg = true" @click.stop="listItemsDlg = true"
/> />

6
packages/nc-gui/components/virtual-cell/ManyToMany.vue

@ -115,13 +115,15 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
</div> </div>
<div v-if="!isLocked" class="flex justify-end gap-1 min-h-[30px] items-center"> <div v-if="!isLocked" class="flex justify-end gap-1 min-h-[30px] items-center">
<MdiArrowExpand <GeneralIcon
icon="expand"
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand" class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand"
@click.stop="childListDlg = true" @click.stop="childListDlg = true"
/> />
<MdiPlus <GeneralIcon
v-if="!readOnly && isUIAllowed('xcDatatableEditable')" v-if="!readOnly && isUIAllowed('xcDatatableEditable')"
icon="plus"
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-plus" class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-plus"
@click.stop="listItemsDlg = true" @click.stop="listItemsDlg = true"
/> />

7
packages/nc-gui/components/virtual-cell/components/ItemChip.vue

@ -4,6 +4,7 @@ import {
IsFormInj, IsFormInj,
IsLockedInj, IsLockedInj,
ReadonlyInj, ReadonlyInj,
iconMap,
inject, inject,
ref, ref,
useExpandedFormDetached, useExpandedFormDetached,
@ -62,7 +63,11 @@ export default {
<span class="name">{{ value }}</span> <span class="name">{{ value }}</span>
<div v-show="active || isForm" v-if="!readOnly && !isLocked && isUIAllowed('xcDatatableEditable')" class="flex items-center"> <div v-show="active || isForm" v-if="!readOnly && !isLocked && isUIAllowed('xcDatatableEditable')" class="flex items-center">
<MdiCloseThick class="unlink-icon text-xs text-gray-500/50 group-hover:text-gray-500" @click.stop="emit('unlink')" /> <component
:is="iconMap.closeThick"
class="unlink-icon text-xs text-gray-500/50 group-hover:text-gray-500"
@click.stop="emit('unlink')"
/>
</div> </div>
</div> </div>
</template> </template>

12
packages/nc-gui/components/virtual-cell/components/ListChildItems.vue

@ -10,6 +10,7 @@ import {
ReadonlyInj, ReadonlyInj,
computed, computed,
h, h,
iconMap,
inject, inject,
ref, ref,
useLTARStoreOrThrow, useLTARStoreOrThrow,
@ -111,7 +112,8 @@ const onClick = (row: Row) => {
<div class="max-h-[max(calc(100vh_-_300px)_,500px)] flex flex-col py-6"> <div class="max-h-[max(calc(100vh_-_300px)_,500px)] flex flex-col py-6">
<div class="flex mb-4 items-center gap-2 px-12"> <div class="flex mb-4 items-center gap-2 px-12">
<div class="flex-1" /> <div class="flex-1" />
<MdiReload <component
:is="iconMap.reload"
v-if="!isForm" v-if="!isForm"
class="cursor-pointer text-gray-500" class="cursor-pointer text-gray-500"
data-testid="nc-child-list-reload" data-testid="nc-child-list-reload"
@ -128,7 +130,7 @@ const onClick = (row: Row) => {
@click="emit('attachRecord')" @click="emit('attachRecord')"
> >
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<MdiLinkVariant class="text-xs" type="primary" /> <component :is="iconMap.link" class="text-xs" type="primary" />
Link to ' Link to '
<GeneralTableIcon :meta="relatedTableMeta" class="-mx-1 w-5" /> <GeneralTableIcon :meta="relatedTableMeta" class="-mx-1 w-5" />
{{ relatedTableMeta.title }}' {{ relatedTableMeta.title }}'
@ -151,12 +153,14 @@ const onClick = (row: Row) => {
</div> </div>
<div v-if="!readonly" class="flex gap-2"> <div v-if="!readonly" class="flex gap-2">
<MdiLinkVariantRemove <component
:is="iconMap.linkRemove"
class="text-xs text-grey hover:(!text-red-500) cursor-pointer" class="text-xs text-grey hover:(!text-red-500) cursor-pointer"
data-testid="nc-child-list-icon-unlink" data-testid="nc-child-list-icon-unlink"
@click.stop="unlinkRow(row)" @click.stop="unlinkRow(row)"
/> />
<MdiDeleteOutline <component
:is="iconMap.delete"
v-if="!readonly && !isPublic" v-if="!readonly && !isPublic"
class="text-xs text-grey hover:(!text-red-500) cursor-pointer" class="text-xs text-grey hover:(!text-red-500) cursor-pointer"
data-testid="nc-child-list-icon-delete" data-testid="nc-child-list-icon-delete"

3
packages/nc-gui/components/virtual-cell/components/ListItems.vue

@ -8,6 +8,7 @@ import {
IsPublicInj, IsPublicInj,
SaveRowInj, SaveRowInj,
computed, computed,
iconMap,
inject, inject,
isDrawerExist, isDrawerExist,
ref, ref,
@ -210,7 +211,7 @@ watch(vModel, (nextVal) => {
<div class="flex-1" /> <div class="flex-1" />
<MdiReload class="cursor-pointer text-gray-500 nc-reload" @click="loadChildrenExcludedList" /> <component :is="iconMap.reload" class="cursor-pointer text-gray-500 nc-reload" @click="loadChildrenExcludedList" />
<!-- Add new record --> <!-- Add new record -->
<a-button v-if="!isPublic" type="primary" size="small" @click="expandedFormDlg = true"> <a-button v-if="!isPublic" type="primary" size="small" @click="expandedFormDlg = true">

8
packages/nc-gui/components/webhook/Editor.vue

@ -453,7 +453,7 @@ onMounted(async () => {
<a-button class="nc-btn-webhook-save" type="primary" size="large" @click.prevent="saveHooks"> <a-button class="nc-btn-webhook-save" type="primary" size="large" @click.prevent="saveHooks">
<div class="flex items-center"> <div class="flex items-center">
<MdiContentSave class="mr-2" /> <component :is="iconMap.save" class="mr-2" />
<!-- Save --> <!-- Save -->
{{ $t('general.save') }} {{ $t('general.save') }}
</div> </div>
@ -530,9 +530,9 @@ onMounted(async () => {
> >
<a-select-option v-for="(notificationOption, i) in notificationList" :key="i" :value="notificationOption.type"> <a-select-option v-for="(notificationOption, i) in notificationList" :key="i" :value="notificationOption.type">
<div class="flex items-center"> <div class="flex items-center">
<MdiLink v-if="notificationOption.type === 'URL'" class="mr-2" /> <component :is="iconMap.link" v-if="notificationOption.type === 'URL'" class="mr-2" />
<MdiEmail v-if="notificationOption.type === 'Email'" class="mr-2" /> <component :is="iconMap.email" v-if="notificationOption.type === 'Email'" class="mr-2" />
<MdiSlack v-if="notificationOption.type === 'Slack'" class="mr-2" /> <MdiSlack v-if="notificationOption.type === 'Slack'" class="mr-2" />
@ -722,4 +722,4 @@ onMounted(async () => {
<LazyWebhookCallLog :hook="hook" /> <LazyWebhookCallLog :hook="hook" />
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</template> </template>

19
packages/nc-gui/components/webhook/List.vue

@ -1,6 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import type { FilterReqType, HookReqType, HookType } from 'nocodb-sdk' import type { FilterReqType, HookReqType, HookType } from 'nocodb-sdk'
import { MetaInj, extractSdkResponseErrorMsg, inject, message, onMounted, parseProp, ref, useI18n, useNuxtApp } from '#imports' import {
MetaInj,
extractSdkResponseErrorMsg,
iconMap,
inject,
message,
onMounted,
parseProp,
ref,
useI18n,
useNuxtApp,
} from '#imports'
const emit = defineEmits(['edit', 'add']) const emit = defineEmits(['edit', 'add'])
@ -125,7 +136,7 @@ onMounted(() => {
<template #avatar> <template #avatar>
<div class="my-1 px-2"> <div class="my-1 px-2">
<MdiHook class="text-xl" /> <component :is="iconMap.hook" class="text-xl" />
</div> </div>
<div class="px-2 text-white rounded" :class="{ 'bg-green-500': item.active, 'bg-gray-500': !item.active }"> <div class="px-2 text-white rounded" :class="{ 'bg-green-500': item.active, 'bg-gray-500': !item.active }">
{{ item.active ? 'ON' : 'OFF' }} {{ item.active ? 'ON' : 'OFF' }}
@ -143,14 +154,14 @@ onMounted(() => {
<template #title> <template #title>
{{ $t('activity.copyWebhook') }} {{ $t('activity.copyWebhook') }}
</template> </template>
<MdiContentCopy class="text-xl nc-hook-copy-icon" @click.stop="copyHook(item)" /> <component :is="iconMap.copy" class="text-xl nc-hook-copy-icon" @click.stop="copyHook(item)" />
</a-tooltip> </a-tooltip>
<a-tooltip placement="left"> <a-tooltip placement="left">
<template #title> <template #title>
{{ $t('activity.deleteWebhook') }} {{ $t('activity.deleteWebhook') }}
</template> </template>
<MdiDeleteOutline class="text-xl nc-hook-delete-icon" @click.stop="deleteHook(item, index)" /> <component :is="iconMap.delete" class="text-xl nc-hook-delete-icon" @click.stop="deleteHook(item, index)" />
</a-tooltip> </a-tooltip>
</div> </div>
</div> </div>

11
packages/nc-gui/layouts/base.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, navigateTo, ref, useGlobal, useNuxtApp, useRoute, useSidebar } from '#imports' import { computed, iconMap, navigateTo, ref, useGlobal, useNuxtApp, useRoute, useSidebar } from '#imports'
const { signOut, signedIn, isLoading, user, currentVersion } = useGlobal() const { signOut, signedIn, isLoading, user, currentVersion } = useGlobal()
@ -60,7 +60,7 @@ hooks.hook('page:finish', () => {
<div v-show="isLoading" class="flex items-center gap-2 ml-3" data-testid="nc-loading"> <div v-show="isLoading" class="flex items-center gap-2 ml-3" data-testid="nc-loading">
{{ $t('general.loading') }} {{ $t('general.loading') }}
<MdiReload :class="{ 'animate-infinite animate-spin': isLoading }" /> <component :is="iconMap.reload" :class="{ 'animate-infinite animate-spin': isLoading }" />
</div> </div>
</div> </div>
@ -78,7 +78,8 @@ hooks.hook('page:finish', () => {
<template v-if="signedIn"> <template v-if="signedIn">
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-user-accounts-menu"> <a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-user-accounts-menu">
<MdiDotsVertical <component
:is="iconMap.threeDotVertical"
data-testid="nc-menu-accounts" data-testid="nc-menu-accounts"
class="md:text-xl cursor-pointer hover:text-accent nc-menu-accounts" class="md:text-xl cursor-pointer hover:text-accent nc-menu-accounts"
@click.prevent @click.prevent
@ -88,7 +89,7 @@ hooks.hook('page:finish', () => {
<a-menu class="!py-0 leading-8 !rounded"> <a-menu class="!py-0 leading-8 !rounded">
<a-menu-item key="0" data-testid="nc-menu-accounts__user-settings" class="!rounded-t"> <a-menu-item key="0" data-testid="nc-menu-accounts__user-settings" class="!rounded-t">
<nuxt-link v-e="['c:navbar:user:email']" class="nc-project-menu-item group !no-underline" to="/account/users"> <nuxt-link v-e="['c:navbar:user:email']" class="nc-project-menu-item group !no-underline" to="/account/users">
<MdiAccountCircleOutline class="mt-1 group-hover:text-accent" />&nbsp; <component :is="iconMap.accountCircle" class="mt-1 group-hover:text-accent" />&nbsp;
<div class="prose group-hover:text-primary"> <div class="prose group-hover:text-primary">
<div>Account</div> <div>Account</div>
<div class="text-xs text-gray-500">{{ email }}</div> <div class="text-xs text-gray-500">{{ email }}</div>
@ -114,7 +115,7 @@ hooks.hook('page:finish', () => {
<a-menu-item key="1" class="!rounded-b group"> <a-menu-item key="1" class="!rounded-b group">
<div v-e="['a:navbar:user:sign-out']" class="nc-project-menu-item group" @click="logout"> <div v-e="['a:navbar:user:sign-out']" class="nc-project-menu-item group" @click="logout">
<MdiLogout class="group-hover:text-accent" />&nbsp; <component :is="iconMap.signout" class="group-hover:text-accent" />&nbsp;
<span class="prose group-hover:text-primary"> <span class="prose group-hover:text-primary">
{{ $t('general.signOut') }} {{ $t('general.signOut') }}

4
packages/nc-gui/layouts/shared-view.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { navigateTo, useEventListener, useRouter } from '#imports' import { iconMap, navigateTo, useEventListener, useRouter } from '#imports'
const { isLoading, appInfo } = useGlobal() const { isLoading, appInfo } = useGlobal()
@ -65,7 +65,7 @@ export default {
<template v-if="isLoading"> <template v-if="isLoading">
<span class="text-white" data-testid="nc-loading">{{ $t('general.loading') }}</span> <span class="text-white" data-testid="nc-loading">{{ $t('general.loading') }}</span>
<MdiReload :class="{ 'animate-infinite animate-spin ': isLoading }" /> <component :is="iconMap.reload" :class="{ 'animate-infinite animate-spin ': isLoading }" />
</template> </template>
<div v-else class="text-xl font-semibold truncate text-white nc-shared-view-title flex gap-2 items-center"> <div v-else class="text-xl font-semibold truncate text-white nc-shared-view-title flex gap-2 items-center">

34
packages/nc-gui/package-lock.json generated

@ -11,7 +11,6 @@
"@ckpack/vue-color": "^1.2.0", "@ckpack/vue-color": "^1.2.0",
"@iconify/vue": "^4.0.1", "@iconify/vue": "^4.0.1",
"@pinia/nuxt": "^0.4.7", "@pinia/nuxt": "^0.4.7",
"@types/file-saver": "^2.0.5",
"@vue-flow/additional-components": "^1.2.0", "@vue-flow/additional-components": "^1.2.0",
"@vue-flow/core": "^1.3.0", "@vue-flow/core": "^1.3.0",
"@vuelidate/core": "^2.0.0-alpha.44", "@vuelidate/core": "^2.0.0-alpha.44",
@ -61,13 +60,14 @@
"@iconify-json/clarity": "^1.1.4", "@iconify-json/clarity": "^1.1.4",
"@iconify-json/eva": "^1.1.2", "@iconify-json/eva": "^1.1.2",
"@iconify-json/ic": "^1.1.7", "@iconify-json/ic": "^1.1.7",
"@iconify-json/ion": "^1.1.8",
"@iconify-json/la": "^1.1.2", "@iconify-json/la": "^1.1.2",
"@iconify-json/logos": "^1.1.14", "@iconify-json/logos": "^1.1.14",
"@iconify-json/lucide": "^1.1.36", "@iconify-json/lucide": "^1.1.36",
"@iconify-json/material-symbols": "^1.1.8", "@iconify-json/material-symbols": "^1.1.8",
"@iconify-json/mdi": "^1.1.25", "@iconify-json/mdi": "^1.1.25",
"@iconify-json/mi": "^1.1.2", "@iconify-json/mi": "^1.1.2",
"@iconify-json/ph": "^1.1.2", "@iconify-json/ph": "^1.1.5",
"@iconify-json/ri": "^1.1.3", "@iconify-json/ri": "^1.1.3",
"@iconify-json/simple-icons": "^1.1.29", "@iconify-json/simple-icons": "^1.1.29",
"@iconify-json/system-uicons": "^1.1.4", "@iconify-json/system-uicons": "^1.1.4",
@ -1116,6 +1116,15 @@
"@iconify/types": "*" "@iconify/types": "*"
} }
}, },
"node_modules/@iconify-json/ion": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/@iconify-json/ion/-/ion-1.1.8.tgz",
"integrity": "sha512-RRyuDG3Sf3ggpK3MV/uSfMXAWlQUpquvt/jHtDg8Duprb/zE8NNXHhEFoSVGVRu7533nNEzUzN4KmYRdxJ790A==",
"dev": true,
"dependencies": {
"@iconify/types": "*"
}
},
"node_modules/@iconify-json/la": { "node_modules/@iconify-json/la": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@iconify-json/la/-/la-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@iconify-json/la/-/la-1.1.2.tgz",
@ -1171,9 +1180,9 @@
} }
}, },
"node_modules/@iconify-json/ph": { "node_modules/@iconify-json/ph": {
"version": "1.1.2", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@iconify-json/ph/-/ph-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@iconify-json/ph/-/ph-1.1.5.tgz",
"integrity": "sha512-NuTdtt/UmuxIHS4hfdyv3BP5JiWikNkr81hFHXDScXlH0GUMdRSY/B5T9vDvbXDY/esMLFnIAXoFVDLsGinhpw==", "integrity": "sha512-iLXq3nohfGge22gL2tZmQ2WHBKkKkIbGWrkuyC8arckS4PWaONyh4A+uDPtSek9QbYDvi9AE2+NxWkNZhANotA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@iconify/types": "*" "@iconify/types": "*"
@ -19077,6 +19086,15 @@
"@iconify/types": "*" "@iconify/types": "*"
} }
}, },
"@iconify-json/ion": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/@iconify-json/ion/-/ion-1.1.8.tgz",
"integrity": "sha512-RRyuDG3Sf3ggpK3MV/uSfMXAWlQUpquvt/jHtDg8Duprb/zE8NNXHhEFoSVGVRu7533nNEzUzN4KmYRdxJ790A==",
"dev": true,
"requires": {
"@iconify/types": "*"
}
},
"@iconify-json/la": { "@iconify-json/la": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@iconify-json/la/-/la-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@iconify-json/la/-/la-1.1.2.tgz",
@ -19132,9 +19150,9 @@
} }
}, },
"@iconify-json/ph": { "@iconify-json/ph": {
"version": "1.1.2", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/@iconify-json/ph/-/ph-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@iconify-json/ph/-/ph-1.1.5.tgz",
"integrity": "sha512-NuTdtt/UmuxIHS4hfdyv3BP5JiWikNkr81hFHXDScXlH0GUMdRSY/B5T9vDvbXDY/esMLFnIAXoFVDLsGinhpw==", "integrity": "sha512-iLXq3nohfGge22gL2tZmQ2WHBKkKkIbGWrkuyC8arckS4PWaONyh4A+uDPtSek9QbYDvi9AE2+NxWkNZhANotA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@iconify/types": "*" "@iconify/types": "*"

3
packages/nc-gui/package.json

@ -84,13 +84,14 @@
"@iconify-json/clarity": "^1.1.4", "@iconify-json/clarity": "^1.1.4",
"@iconify-json/eva": "^1.1.2", "@iconify-json/eva": "^1.1.2",
"@iconify-json/ic": "^1.1.7", "@iconify-json/ic": "^1.1.7",
"@iconify-json/ion": "^1.1.8",
"@iconify-json/la": "^1.1.2", "@iconify-json/la": "^1.1.2",
"@iconify-json/logos": "^1.1.14", "@iconify-json/logos": "^1.1.14",
"@iconify-json/lucide": "^1.1.36", "@iconify-json/lucide": "^1.1.36",
"@iconify-json/material-symbols": "^1.1.8", "@iconify-json/material-symbols": "^1.1.8",
"@iconify-json/mdi": "^1.1.25", "@iconify-json/mdi": "^1.1.25",
"@iconify-json/mi": "^1.1.2", "@iconify-json/mi": "^1.1.2",
"@iconify-json/ph": "^1.1.2", "@iconify-json/ph": "^1.1.5",
"@iconify-json/ri": "^1.1.3", "@iconify-json/ri": "^1.1.3",
"@iconify-json/simple-icons": "^1.1.29", "@iconify-json/simple-icons": "^1.1.29",
"@iconify-json/system-uicons": "^1.1.4", "@iconify-json/system-uicons": "^1.1.4",

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

@ -5,6 +5,7 @@ import {
computed, computed,
definePageMeta, definePageMeta,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
iconMap,
isDrawerOrModalExist, isDrawerOrModalExist,
isMac, isMac,
message, message,
@ -315,12 +316,11 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
</template> </template>
</a-tooltip> </a-tooltip>
<div v-else class="text-md font-semibold truncate capitalize">{{ project.title }}</div> <div v-else class="text-md font-semibold truncate capitalize">{{ project.title }}</div>
<component :is="iconMap.arrowDown" class="min-w-[17px] group-hover:text-accent text-md" />
<MdiChevronDown class="min-w-[17px] group-hover:text-accent text-md" />
</template> </template>
<template v-else> <template v-else>
<MdiFolder class="text-primary cursor-pointer transform hover:scale-105 text-2xl" /> <component :is="iconMap.folder" class="text-primary cursor-pointer transform hover:scale-105 text-2xl" />
</template> </template>
</div> </div>
@ -329,7 +329,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
<a-menu-item-group> <a-menu-item-group>
<template #title> <template #title>
<div class="group select-none flex items-center gap-4 py-1"> <div class="group select-none flex items-center gap-4 py-1">
<MdiFolder class="group-hover:text-accent text-xl" /> <component :is="iconMap.folder" class="group-hover:text-accent text-xl" />
<div class="flex flex-col"> <div class="flex flex-col">
<div class="text-lg group-hover:(!text-primary) font-semibold capitalize"> <div class="text-lg group-hover:(!text-primary) font-semibold capitalize">
@ -352,7 +352,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
class="nc-project-menu-item group" class="nc-project-menu-item group"
@click.stop="copyProjectInfo" @click.stop="copyProjectInfo"
> >
<MdiContentCopy class="group-hover:text-accent" /> <component :is="iconMap.copy" class="group-hover:text-accent" />
{{ $t('activity.account.projInfo') }} {{ $t('activity.account.projInfo') }}
</div> </div>
</a-menu-item> </a-menu-item>
@ -367,7 +367,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
class="nc-project-menu-item group" class="nc-project-menu-item group"
@click.stop="openLink(`/api/v1/db/meta/projects/${route.params.projectId}/swagger`, appInfo.ncSiteUrl)" @click.stop="openLink(`/api/v1/db/meta/projects/${route.params.projectId}/swagger`, appInfo.ncSiteUrl)"
> >
<MdiApi class="group-hover:text-accent" /> <component :is="iconMap.json" class="group-hover:text-accent" />
{{ $t('activity.account.swagger') }} {{ $t('activity.account.swagger') }}
</div> </div>
</a-menu-item> </a-menu-item>
@ -380,7 +380,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
class="nc-project-menu-item group" class="nc-project-menu-item group"
@click.stop="copyAuthToken" @click.stop="copyAuthToken"
> >
<MdiScriptTextKeyOutline class="group-hover:text-accent" /> <component :is="iconMap.copy" class="group-hover:text-accent" />
{{ $t('activity.account.authToken') }} {{ $t('activity.account.authToken') }}
</div> </div>
</a-menu-item> </a-menu-item>
@ -395,7 +395,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
class="nc-project-menu-item group" class="nc-project-menu-item group"
@click="toggleDialog(true, 'teamAndAuth')" @click="toggleDialog(true, 'teamAndAuth')"
> >
<MdiCog class="group-hover:text-accent" /> <component :is="iconMap.settings" class="group-hover:text-accent" />
{{ $t('title.teamAndSettings') }} {{ $t('title.teamAndSettings') }}
</div> </div>
</a-menu-item> </a-menu-item>
@ -417,7 +417,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
<a-sub-menu key="theme"> <a-sub-menu key="theme">
<template #title> <template #title>
<div class="nc-project-menu-item group"> <div class="nc-project-menu-item group">
<ClarityImageLine class="group-hover:text-accent" /> <component :is="iconMap.image" class="group-hover:text-accent" />
{{ $t('activity.account.themes') }} {{ $t('activity.account.themes') }}
<div class="flex-1" /> <div class="flex-1" />
@ -492,7 +492,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
<a-sub-menu v-if="isUIAllowed('previewAs') && !isMobileMode" key="preview-as"> <a-sub-menu v-if="isUIAllowed('previewAs') && !isMobileMode" key="preview-as">
<template #title> <template #title>
<div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group"> <div v-e="['c:navdraw:preview-as']" class="nc-project-menu-item group">
<MdiFileEyeOutline class="group-hover:text-accent" /> <component :is="iconMap.preview" class="group-hover:text-accent" />
{{ $t('activity.previewAs') }} {{ $t('activity.previewAs') }}
<div class="flex-1" /> <div class="flex-1" />
@ -516,7 +516,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
> >
<template #title> <template #title>
<div class="nc-project-menu-item group"> <div class="nc-project-menu-item group">
<MaterialSymbolsTranslate class="group-hover:text-accent nc-language" /> <component :is="iconMap.translate" class="group-hover:text-accent nc-language" />
{{ $t('labels.language') }} {{ $t('labels.language') }}
<div class="flex items-center text-gray-400 text-xs">(Community Translated)</div> <div class="flex items-center text-gray-400 text-xs">(Community Translated)</div>
<div class="flex-1" /> <div class="flex-1" />
@ -537,7 +537,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
<a-sub-menu key="account"> <a-sub-menu key="account">
<template #title> <template #title>
<div class="nc-project-menu-item group"> <div class="nc-project-menu-item group">
<MdiAccount class="group-hover:text-accent" /> <component :is="iconMap.account" class="group-hover:text-accent" />
{{ $t('labels.account') }} {{ $t('labels.account') }}
<div class="flex-1" /> <div class="flex-1" />
@ -555,7 +555,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
class="nc-project-menu-item group !no-underline" class="nc-project-menu-item group !no-underline"
to="/account/users" to="/account/users"
> >
<MdiAt class="mt-1 group-hover:text-accent" />&nbsp; <component :is="iconMap.at" class="mt-1 group-hover:text-accent" />&nbsp;
<div class="prose-sm group-hover:text-primary"> <div class="prose-sm group-hover:text-primary">
<div>Account</div> <div>Account</div>
<div class="text-xs text-gray-500">{{ email }}</div> <div class="text-xs text-gray-500">{{ email }}</div>
@ -565,7 +565,7 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
<a-menu-item key="1" class="!rounded-b"> <a-menu-item key="1" class="!rounded-b">
<div v-e="['a:navbar:user:sign-out']" class="nc-project-menu-item group" @click="logout"> <div v-e="['a:navbar:user:sign-out']" class="nc-project-menu-item group" @click="logout">
<MdiLogout class="group-hover:(!text-accent)" />&nbsp; <component :is="iconMap.signout" class="group-hover:(!text-accent)" />&nbsp;
<span class="prose-sm nc-user-menu-signout"> <span class="prose-sm nc-user-menu-signout">
{{ $t('general.signOut') }} {{ $t('general.signOut') }}
@ -582,7 +582,8 @@ useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
<div <div
class="nc-sidebar-left-toggle-icon hover:after:(bg-primary bg-opacity-75) group nc-sidebar-add-row flex items-center px-2" class="nc-sidebar-left-toggle-icon hover:after:(bg-primary bg-opacity-75) group nc-sidebar-add-row flex items-center px-2"
> >
<MdiBackburger <component
:is="iconMap.sidebarMinimise"
v-e="['c:grid:toggle-navdraw']" v-e="['c:grid:toggle-navdraw']"
class="cursor-pointer transform transition-transform duration-500" class="cursor-pointer transform transition-transform duration-500"
:class="{ 'rotate-180': !isOpen }" :class="{ 'rotate-180': !isOpen }"

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

@ -15,11 +15,11 @@ provide(TabMetaInj, activeTab)
const icon = (tab: TabItem) => { const icon = (tab: TabItem) => {
switch (tab.type) { switch (tab.type) {
case TabType.TABLE: case TabType.TABLE:
return iconMap['mdi-table-large'] return iconMap.table
case TabType.VIEW: case TabType.VIEW:
return iconMap['mdi-eye-circle-outline'] return iconMap.eye
case TabType.AUTH: case TabType.AUTH:
return iconMap['mdi-account-group'] return iconMap.users
} }
} }
@ -42,9 +42,10 @@ const hideSidebarOnClickOrTouchIfMobileMode = () => {
<div class="flex items-end !min-h-[var(--header-height)] !bg-white-500 nc-tab-bar"> <div class="flex items-end !min-h-[var(--header-height)] !bg-white-500 nc-tab-bar">
<div <div
v-if="!isOpen" v-if="!isOpen"
class="nc-sidebar-left-toggle-icon hover:after:(bg-primary bg-opacity-75) group nc-sidebar-add-row py-2 px-3 mb-1" class="nc-sidebar-left-toggle-icon hover:after:(bg-primary bg-opacity-75) group nc-sidebar-add-row py-2 px-3"
> >
<MdiMenu <component
:is="iconMap.sidebarMinimise"
v-e="['c:grid:toggle-navdraw']" v-e="['c:grid:toggle-navdraw']"
class="cursor-pointer transform transition-transform duration-500 text-gray-500/80 hover:text-gray-500" class="cursor-pointer transform transition-transform duration-500 text-gray-500/80 hover:text-gray-500"
:class="{ 'rotate-180': !isOpen }" :class="{ 'rotate-180': !isOpen }"
@ -82,7 +83,7 @@ const hideSidebarOnClickOrTouchIfMobileMode = () => {
<div v-if="isLoading" class="flex items-center gap-2 ml-3 text-gray-200" data-testid="nc-loading"> <div v-if="isLoading" class="flex items-center gap-2 ml-3 text-gray-200" data-testid="nc-loading">
{{ $t('general.loading') }} {{ $t('general.loading') }}
<MdiLoading class="animate-infinite animate-spin" /> <component :is="iconMap.loading" class="animate-infinite animate-spin" />
</div> </div>
</div> </div>

4
packages/nc-gui/pages/[projectType]/form/[viewId]/index/index.vue

@ -3,7 +3,7 @@ import type { ColumnType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk' import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk'
import { ref } from 'vue' import { ref } from 'vue'
import { StreamBarcodeReader } from 'vue-barcode-reader' import { StreamBarcodeReader } from 'vue-barcode-reader'
import { useSharedFormStoreOrThrow } from '#imports' import { iconMap, useSharedFormStoreOrThrow } from '#imports'
const { sharedFormView, submitForm, v$, formState, notFound, formColumns, submitted, secondsRemain, isLoading } = const { sharedFormView, submitForm, v$, formState, notFound, formColumns, submitted, secondsRemain, isLoading } =
useSharedFormStoreOrThrow() useSharedFormStoreOrThrow()
@ -175,7 +175,7 @@ const onDecode = async (scannedCodeValue: string) => {
@click="showCodeScannerForFieldTitle(field.title)" @click="showCodeScannerForFieldTitle(field.title)"
> >
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<mdi-qrcode-scan class="h-5 w-5" /> <component :is="iconMap.qrCodeScan" class="h-5 w-5" />
</div> </div>
</a-button> </a-button>
</div> </div>

15
packages/nc-gui/pages/[projectType]/form/[viewId]/index/survey.vue

@ -6,6 +6,7 @@ import {
DropZoneRef, DropZoneRef,
IsSurveyFormInj, IsSurveyFormInj,
computed, computed,
iconMap,
isValidURL, isValidURL,
onKeyStroke, onKeyStroke,
onMounted, onMounted,
@ -365,11 +366,12 @@ onMounted(() => {
</Transition> </Transition>
<Transition name="slide-right" mode="out-in"> <Transition name="slide-right" mode="out-in">
<MdiCloseCircleOutline <component
:is="iconMap.closeCircle"
v-if="v$.localState[field.title]?.$error || columnValidationError" v-if="v$.localState[field.title]?.$error || columnValidationError"
class="text-red-500 md:text-md" class="text-red-500 md:text-md"
/> />
<MdiCheck v-else class="text-white md:text-md" /> <component :is="iconMap.check" v-else class="text-white md:text-md" />
</Transition> </Transition>
</button> </button>
</a-tooltip> </a-tooltip>
@ -443,7 +445,11 @@ onMounted(() => {
data-testid="nc-survey-form__icon-prev" data-testid="nc-survey-form__icon-prev"
@click="goPrevious()" @click="goPrevious()"
> >
<MdiChevronLeft :class="isFirst ? 'text-gray-300' : 'group-hover:text-accent'" class="text-2xl md:text-md" /> <component
:is="iconMap.chevronLeft"
:class="isFirst ? 'text-gray-300' : 'group-hover:text-accent'"
class="text-2xl md:text-md"
/>
</button> </button>
</a-tooltip> </a-tooltip>
@ -462,7 +468,8 @@ onMounted(() => {
data-testid="nc-survey-form__icon-next" data-testid="nc-survey-form__icon-next"
@click="goNext()" @click="goNext()"
> >
<MdiChevronRight <component
:is="iconMap.chevronRight"
:class="[isLast || v$.localState[field.title]?.$error ? 'text-gray-300' : 'group-hover:text-accent']" :class="[isLast || v$.localState[field.title]?.$error ? 'text-gray-300' : 'group-hover:text-accent']"
class="text-2xl md:text-md" class="text-2xl md:text-md"
/> />

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save