Browse Source

refactor(nc-gui): use lazy load and cleanup imports

pull/3801/head
braks 2 years ago
parent
commit
b07f52bb40
  1. 38
      packages/nc-gui/components.d.ts
  2. 17
      packages/nc-gui/components/shared-view/AskPassword.vue
  3. 27
      packages/nc-gui/components/shared-view/Grid.vue
  4. 28
      packages/nc-gui/components/smartsheet/ApiSnippet.vue
  5. 48
      packages/nc-gui/components/smartsheet/Cell.vue
  6. 25
      packages/nc-gui/components/smartsheet/Form.vue
  7. 48
      packages/nc-gui/components/smartsheet/Gallery.vue
  8. 20
      packages/nc-gui/components/smartsheet/Grid.vue
  9. 3
      packages/nc-gui/components/smartsheet/Pagination.vue
  10. 20
      packages/nc-gui/components/smartsheet/Row.vue
  11. 25
      packages/nc-gui/components/smartsheet/Toolbar.vue
  12. 38
      packages/nc-gui/components/smartsheet/VirtualCell.vue
  13. 18
      packages/nc-gui/components/smartsheet/column/AdvancedOptions.vue
  14. 14
      packages/nc-gui/components/smartsheet/column/CheckboxOptions.vue
  15. 29
      packages/nc-gui/components/smartsheet/column/CurrencyOptions.vue
  16. 10
      packages/nc-gui/components/smartsheet/column/DateOptions.vue
  17. 11
      packages/nc-gui/components/smartsheet/column/DurationOptions.vue
  18. 47
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  19. 10
      packages/nc-gui/components/smartsheet/column/EditOrAddProvider.vue
  20. 15
      packages/nc-gui/components/smartsheet/column/FormulaOptions.vue
  21. 14
      packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue
  22. 11
      packages/nc-gui/components/smartsheet/column/LookupOptions.vue
  23. 10
      packages/nc-gui/components/smartsheet/column/PercentOptions.vue
  24. 14
      packages/nc-gui/components/smartsheet/column/RatingOptions.vue
  25. 12
      packages/nc-gui/components/smartsheet/column/RollupOptions.vue
  26. 27
      packages/nc-gui/components/smartsheet/column/SelectOptions.vue
  27. 13
      packages/nc-gui/components/smartsheet/expanded-form/Comments.vue
  28. 7
      packages/nc-gui/components/smartsheet/expanded-form/Header.vue
  29. 15
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  30. 10
      packages/nc-gui/components/smartsheet/header/Cell.vue
  31. 3
      packages/nc-gui/components/smartsheet/header/CellIcon.vue
  32. 14
      packages/nc-gui/components/smartsheet/header/Menu.vue
  33. 8
      packages/nc-gui/components/smartsheet/header/VirtualCell.vue
  34. 3
      packages/nc-gui/components/smartsheet/header/VirtualCellIcon.vue
  35. 3
      packages/nc-gui/components/smartsheet/sidebar/MenuBottom.vue
  36. 9
      packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue
  37. 15
      packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue
  38. 11
      packages/nc-gui/components/smartsheet/sidebar/index.vue
  39. 3
      packages/nc-gui/components/smartsheet/sidebar/toolbar/DebugMeta.vue
  40. 5
      packages/nc-gui/components/smartsheet/sidebar/toolbar/DeleteCache.vue
  41. 2
      packages/nc-gui/components/smartsheet/sidebar/toolbar/DeleteTable.vue
  42. 11
      packages/nc-gui/components/smartsheet/sidebar/toolbar/ExportCache.vue
  43. 4
      packages/nc-gui/components/smartsheet/sidebar/toolbar/ToggleDrawer.vue
  44. 30
      packages/nc-gui/components/smartsheet/sidebar/toolbar/index.vue
  45. 46
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  46. 21
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilterMenu.vue
  47. 2
      packages/nc-gui/components/smartsheet/toolbar/Export.vue
  48. 26
      packages/nc-gui/components/smartsheet/toolbar/ExportSubActions.vue
  49. 13
      packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue
  50. 19
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  51. 6
      packages/nc-gui/components/smartsheet/toolbar/LockType.vue
  52. 10
      packages/nc-gui/components/smartsheet/toolbar/MoreActions.vue
  53. 3
      packages/nc-gui/components/smartsheet/toolbar/Reload.vue
  54. 3
      packages/nc-gui/components/smartsheet/toolbar/SearchData.vue
  55. 14
      packages/nc-gui/components/smartsheet/toolbar/ShareView.vue
  56. 32
      packages/nc-gui/components/smartsheet/toolbar/SharedViewList.vue
  57. 9
      packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue
  58. 32
      packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue
  59. 9
      packages/nc-gui/components/smartsheet/toolbar/ViewInfo.vue
  60. 10
      packages/nc-gui/pages/[projectType]/view/[viewId].vue

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

@ -7,14 +7,12 @@ export {}
declare module '@vue/runtime-core' {
export interface GlobalComponents {
AAlert: typeof import('ant-design-vue/es')['Alert']
ABadgeRibbon: typeof import('ant-design-vue/es')['BadgeRibbon']
AButton: typeof import('ant-design-vue/es')['Button']
ACard: typeof import('ant-design-vue/es')['Card']
ACardMeta: typeof import('ant-design-vue/es')['CardMeta']
ACarousel: typeof import('ant-design-vue/es')['Carousel']
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup']
ACol: typeof import('ant-design-vue/es')['Col']
ACollapse: typeof import('ant-design-vue/es')['Collapse']
ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel']
@ -34,7 +32,6 @@ declare module '@vue/runtime-core' {
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
ALayoutFooter: typeof import('ant-design-vue/es')['LayoutFooter']
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
AList: typeof import('ant-design-vue/es')['List']
AListItem: typeof import('ant-design-vue/es')['ListItem']
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
@ -67,8 +64,6 @@ declare module '@vue/runtime-core' {
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
ATypographyTitle: typeof import('ant-design-vue/es')['TypographyTitle']
AUploadDragger: typeof import('ant-design-vue/es')['UploadDragger']
BiFiletypeJson: typeof import('~icons/bi/filetype-json')['default']
BiFiletypeXlsx: typeof import('~icons/bi/filetype-xlsx')['default']
CilFullscreen: typeof import('~icons/cil/fullscreen')['default']
CilFullscreenExit: typeof import('~icons/cil/fullscreen-exit')['default']
ClarityColorPickerSolid: typeof import('~icons/clarity/color-picker-solid')['default']
@ -77,66 +72,44 @@ declare module '@vue/runtime-core' {
EvaEmailOutline: typeof import('~icons/eva/email-outline')['default']
IcBaselineMoreVert: typeof import('~icons/ic/baseline-more-vert')['default']
IcOutlineInsertDriveFile: typeof import('~icons/ic/outline-insert-drive-file')['default']
IcRoundEdit: typeof import('~icons/ic/round-edit')['default']
IcRoundKeyboardArrowDown: typeof import('~icons/ic/round-keyboard-arrow-down')['default']
IcRoundSearch: typeof import('~icons/ic/round-search')['default']
IcTwotoneWidthFull: typeof import('~icons/ic/twotone-width-full')['default']
IcTwotoneWidthNormal: typeof import('~icons/ic/twotone-width-normal')['default']
LogosGoogleGmail: typeof import('~icons/logos/google-gmail')['default']
LogosRedditIcon: typeof import('~icons/logos/reddit-icon')['default']
LogosSwagger: typeof import('~icons/logos/swagger')['default']
MaterialSymbolsArrowCircleLeftRounded: typeof import('~icons/material-symbols/arrow-circle-left-rounded')['default']
MaterialSymbolsArrowCircleRightRounded: typeof import('~icons/material-symbols/arrow-circle-right-rounded')['default']
MaterialSymbolsAttachFile: typeof import('~icons/material-symbols/attach-file')['default']
MaterialSymbolsChevronRightRounded: typeof import('~icons/material-symbols/chevron-right-rounded')['default']
MaterialSymbolsCloseRounded: typeof import('~icons/material-symbols/close-rounded')['default']
MaterialSymbolsFileCopyOutline: typeof import('~icons/material-symbols/file-copy-outline')['default']
MaterialSymbolsRocketLaunchOutline: typeof import('~icons/material-symbols/rocket-launch-outline')['default']
MaterialSymbolsSendOutline: typeof import('~icons/material-symbols/send-outline')['default']
MaterialSymbolsTranslate: typeof import('~icons/material-symbols/translate')['default']
MaterialSymbolsWarning: typeof import('~icons/material-symbols/warning')['default']
MdiAccount: typeof import('~icons/mdi/account')['default']
MdiAccountCircle: typeof import('~icons/mdi/account-circle')['default']
MdiAccountOutline: typeof import('~icons/mdi/account-outline')['default']
MdiAccountPlusOutline: typeof import('~icons/mdi/account-plus-outline')['default']
MdiAlpha: typeof import('~icons/mdi/alpha')['default']
MdiAlphaA: typeof import('~icons/mdi/alpha-a')['default']
MdiApi: typeof import('~icons/mdi/api')['default']
MdiArrowExpand: typeof import('~icons/mdi/arrow-expand')['default']
MdiArrowLeftBold: typeof import('~icons/mdi/arrow-left-bold')['default']
MdiAt: typeof import('~icons/mdi/at')['default']
MdiBackburger: typeof import('~icons/mdi/backburger')['default']
MdiBookOpenOutline: typeof import('~icons/mdi/book-open-outline')['default']
MdiBugOutline: typeof import('~icons/mdi/bug-outline')['default']
MdiCalculator: typeof import('~icons/mdi/calculator')['default']
MdiCalendarMonth: typeof import('~icons/mdi/calendar-month')['default']
MdiCardsHeart: typeof import('~icons/mdi/cards-heart')['default']
MdiCellphoneMessage: typeof import('~icons/mdi/cellphone-message')['default']
MdiChat: typeof import('~icons/mdi/chat')['default']
MdiCheck: typeof import('~icons/mdi/check')['default']
MdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
MdiChevronLeft: typeof import('~icons/mdi/chevron-left')['default']
MdiClose: typeof import('~icons/mdi/close')['default']
MdiCloseBox: typeof import('~icons/mdi/close-box')['default']
MdiCloseCircle: typeof import('~icons/mdi/close-circle')['default']
MdiCloseCircleOutline: typeof import('~icons/mdi/close-circle-outline')['default']
MdiCloseThick: typeof import('~icons/mdi/close-thick')['default']
MdiCodeJson: typeof import('~icons/mdi/code-json')['default']
MdiCog: typeof import('~icons/mdi/cog')['default']
MdiCommentTextOutline: typeof import('~icons/mdi/comment-text-outline')['default']
MdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
MdiContentSave: typeof import('~icons/mdi/content-save')['default']
MdiCurrencyUsd: typeof import('~icons/mdi/currency-usd')['default']
MdiDatabaseOutline: typeof import('~icons/mdi/database-outline')['default']
MdiDelete: typeof import('~icons/mdi/delete')['default']
MdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
MdiDiscord: typeof import('~icons/mdi/discord')['default']
MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default']
MdiDownload: typeof import('~icons/mdi/download')['default']
MdiDownloadOutline: typeof import('~icons/mdi/download-outline')['default']
MdiDrag: typeof import('~icons/mdi/drag')['default']
MdiDragVertical: typeof import('~icons/mdi/drag-vertical')['default']
MdiDramaMasks: typeof import('~icons/mdi/drama-masks')['default']
MdiEditOutline: typeof import('~icons/mdi/edit-outline')['default']
MdiEmail: typeof import('~icons/mdi/email')['default']
MdiEmailArrowRightOutline: typeof import('~icons/mdi/email-arrow-right-outline')['default']
@ -144,14 +117,10 @@ declare module '@vue/runtime-core' {
MdiExport: typeof import('~icons/mdi/export')['default']
MdiEyeCircleOutline: typeof import('~icons/mdi/eye-circle-outline')['default']
MdiEyeOffOutline: typeof import('~icons/mdi/eye-off-outline')['default']
MdiFileDocumentOutline: typeof import('~icons/mdi/file-document-outline')['default']
MdiFileExcel: typeof import('~icons/mdi/file-excel')['default']
MdiFileEyeOutline: typeof import('~icons/mdi/file-eye-outline')['default']
MdiFilePlusOutline: typeof import('~icons/mdi/file-plus-outline')['default']
MdiFileUploadOutline: typeof import('~icons/mdi/file-upload-outline')['default']
MdiFilterOutline: typeof import('~icons/mdi/filter-outline')['default']
MdiFlag: typeof import('~icons/mdi/flag')['default']
MdiFolder: typeof import('~icons/mdi/folder')['default']
MdiFunction: typeof import('~icons/mdi/function')['default']
MdiGestureDoubleTap: typeof import('~icons/mdi/gesture-double-tap')['default']
MdiGithub: typeof import('~icons/mdi/github')['default']
@ -165,26 +134,21 @@ declare module '@vue/runtime-core' {
MdiLink: typeof import('~icons/mdi/link')['default']
MdiLinkVariant: typeof import('~icons/mdi/link-variant')['default']
MdiLinkVariantRemove: typeof import('~icons/mdi/link-variant-remove')['default']
MdiLoading: typeof import('~icons/mdi/loading')['default']
MdiLogin: typeof import('~icons/mdi/login')['default']
MdiLogout: typeof import('~icons/mdi/logout')['default']
MdiMagnify: typeof import('~icons/mdi/magnify')['default']
MdiMenu: typeof import('~icons/mdi/menu')['default']
MdiMenuDown: typeof import('~icons/mdi/menu-down')['default']
MdiMicrosoftTeams: typeof import('~icons/mdi/microsoft-teams')['default']
MdiMinusCircleOutline: typeof import('~icons/mdi/minus-circle-outline')['default']
MdiMoonFull: typeof import('~icons/mdi/moon-full')['default']
MdiNumeric: typeof import('~icons/mdi/numeric')['default']
MdiOpenInNew: typeof import('~icons/mdi/open-in-new')['default']
MdiOpenInNewIcon: typeof import('~icons/mdi/open-in-new-icon')['default']
MdiPencil: typeof import('~icons/mdi/pencil')['default']
MdiPlus: typeof import('~icons/mdi/plus')['default']
MdiPlusCircleOutline: typeof import('~icons/mdi/plus-circle-outline')['default']
MdiPlusOutline: typeof import('~icons/mdi/plus-outline')['default']
MdiRefresh: typeof import('~icons/mdi/refresh')['default']
MdiReload: typeof import('~icons/mdi/reload')['default']
MdiRocketLaunchOutline: typeof import('~icons/mdi/rocket-launch-outline')['default']
MdiScriptTextKeyOutline: typeof import('~icons/mdi/script-text-key-outline')['default']
MdiScriptTextOutline: typeof import('~icons/mdi/script-text-outline')['default']
MdiSlack: typeof import('~icons/mdi/slack')['default']
MdiSort: typeof import('~icons/mdi/sort')['default']
@ -197,7 +161,6 @@ declare module '@vue/runtime-core' {
MdiTableLarge: typeof import('~icons/mdi/table-large')['default']
MdiText: typeof import('~icons/mdi/text')['default']
MdiThumbUp: typeof import('~icons/mdi/thumb-up')['default']
MdiTrashCan: typeof import('~icons/mdi/trash-can')['default']
MdiTwitter: typeof import('~icons/mdi/twitter')['default']
MdiUpload: typeof import('~icons/mdi/upload')['default']
MdiUploadOutline: typeof import('~icons/mdi/upload-outline')['default']
@ -206,7 +169,6 @@ declare module '@vue/runtime-core' {
MdiWhatsapp: typeof import('~icons/mdi/whatsapp')['default']
MdiXml: typeof import('~icons/mdi/xml')['default']
MiCircleWarning: typeof import('~icons/mi/circle-warning')['default']
PhFileCsv: typeof import('~icons/ph/file-csv')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}

17
packages/nc-gui/components/shared-view/AskPassword.vue

@ -1,18 +1,19 @@
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { extractSdkResponseErrorMsg } from '~/utils'
import { extractSdkResponseErrorMsg, message, ref, useRoute, useSharedView, useVModel } from '#imports'
interface Props {
const props = defineProps<{
modelValue: boolean
}
const props = defineProps<Props>()
}>()
const emit = defineEmits(['update:modelValue'])
const vModel = useVModel(props, 'modelValue', emit)
const route = useRoute()
const { loadSharedView } = useSharedView()
const formState = ref({ password: undefined })
const vModel = useVModel(props, 'modelValue', emit)
const onFinish = async () => {
try {
@ -38,14 +39,14 @@ const onFinish = async () => {
>
<div class="w-full flex flex-col">
<a-typography-title :level="4">This shared view is protected</a-typography-title>
<a-form ref="formRef" :model="formState" class="mt-2" @finish="onFinish">
<a-form-item name="password" :rules="[{ required: true, message: 'Password is required' }]">
<a-input-password v-model:value="formState.password" placeholder="Enter password" />
</a-form-item>
<a-button type="primary" html-type="submit">Unlock</a-button>
</a-form>
</div>
</a-modal>
</template>
<style scoped lang="scss"></style>

27
packages/nc-gui/components/shared-view/Grid.vue

@ -1,12 +1,31 @@
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { ActiveViewInj, FieldsInj, IsPublicInj, MetaInj, ReadonlyInj, ReloadViewDataHookInj } from '#imports'
import {
ActiveViewInj,
FieldsInj,
IsPublicInj,
MetaInj,
ReadonlyInj,
ReloadViewDataHookInj,
createEventHook,
extractSdkResponseErrorMsg,
message,
provide,
ref,
useGlobal,
useProject,
useProvideSmartsheetStore,
useSharedView,
} from '#imports'
const { sharedView, meta, sorts, nestedFilters } = useSharedView()
const { signedIn } = useGlobal()
const { loadProject } = useProject(meta.value?.project_id)
const reloadEventHook = createEventHook<void>()
useProvideSmartsheetStore(sharedView, meta, true, sorts, nestedFilters)
const reloadEventHook = createEventHook()
provide(ReloadViewDataHookInj, reloadEventHook)
provide(ReadonlyInj, true)
@ -15,8 +34,6 @@ provide(ActiveViewInj, sharedView)
provide(FieldsInj, ref(meta.value?.columns || []))
provide(IsPublicInj, ref(true))
useProvideSmartsheetStore(sharedView, meta, true, sorts, nestedFilters)
if (signedIn.value) {
try {
await loadProject()

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

@ -1,10 +1,11 @@
<script setup lang="ts">
import HTTPSnippet from 'httpsnippet'
import { message } from 'ant-design-vue'
import {
ActiveViewInj,
MetaInj,
inject,
message,
ref,
useCopy,
useGlobal,
useI18n,
@ -12,16 +13,15 @@ import {
useSmartsheetStoreOrThrow,
useVModel,
useViewData,
watch,
} from '#imports'
const props = defineProps<Props>()
const props = defineProps<{
modelValue: boolean
}>()
const emits = defineEmits(['update:modelValue'])
interface Props {
modelValue: boolean
}
const { t } = useI18n()
const { project } = $(useProject())
@ -81,7 +81,7 @@ const selectedLangName = $ref(langs[0].name)
const apiUrl = $computed(
() =>
new URL(`/api/v1/db/data/noco/${project.id}/${meta.title}/views/${view.title}`, (appInfo && appInfo.ncSiteUrl) || '/').href,
new URL(`/api/v1/db/data/noco/${project.id}/${meta?.title}/views/${view?.title}`, (appInfo && appInfo.ncSiteUrl) || '/').href,
)
const snippet = $computed(
@ -114,8 +114,8 @@ const api = new Api({
api.dbViewRow.list(
"noco",
${JSON.stringify(project.title)},
${JSON.stringify(meta.title)},
${JSON.stringify(view.title)}, ${JSON.stringify(queryParams, null, 4)}).then(function (data) {
${JSON.stringify(meta?.title)},
${JSON.stringify(view?.title)}, ${JSON.stringify(queryParams, null, 4)}).then(function (data) {
console.log(data);
}).catch(function (error) {
console.error(error);
@ -145,7 +145,6 @@ watch($$(activeLang), (newLang) => {
<a-drawer
v-model:visible="vModel"
class="h-full relative nc-drawer-api-snippet"
style="color: red"
placement="right"
size="large"
:closable="false"
@ -154,6 +153,7 @@ watch($$(activeLang), (newLang) => {
<div class="flex flex-col w-full h-full p-4">
<!-- Code Snippet -->
<a-typography-title :level="4" class="pb-1">{{ $t('title.codeSnippet') }}</a-typography-title>
<a-tabs v-model:activeKey="selectedLangName" class="!h-full">
<a-tab-pane v-for="item in langs" :key="item.name" class="!h-full">
<template #tab>
@ -161,7 +161,8 @@ watch($$(activeLang), (newLang) => {
{{ item.name }}
</div>
</template>
<monaco-editor
<LazyMonacoEditor
class="h-[60vh] border-1 border-gray-100 py-4 rounded-sm"
:model-value="code"
:read-only="true"
@ -170,6 +171,7 @@ watch($$(activeLang), (newLang) => {
:disable-deep-compare="true"
hide-minimap
/>
<div v-if="activeLang.clients" class="flex flex-row w-full justify-end space-x-3 mt-4 uppercase">
<a-select
v-if="activeLang"
@ -181,6 +183,7 @@ watch($$(activeLang), (newLang) => {
{{ client }}
</a-select-option>
</a-select>
<a-button
v-e="[
'c:snippet:copy',
@ -188,7 +191,8 @@ watch($$(activeLang), (newLang) => {
]"
type="primary"
@click="onCopyToClipboard"
>{{ $t('general.copy') }}
>
{{ $t('general.copy') }}
</a-button>
</div>

48
packages/nc-gui/components/smartsheet/Cell.vue

@ -82,9 +82,7 @@ const isAutoSaved = $computed(() => {
].includes(column?.value?.uidt as UITypes)
})
const isManualSaved = $computed(() => {
return [UITypes.Currency, UITypes.Duration].includes(column?.value?.uidt as UITypes)
})
const isManualSaved = $computed(() => [UITypes.Currency, UITypes.Duration].includes(column?.value?.uidt as UITypes))
const vModel = computed({
get: () => props.modelValue,
@ -149,28 +147,28 @@ const syncAndNavigate = (dir: NavigateDir) => {
@keydown.stop.enter.exact="syncAndNavigate(NavigateDir.NEXT)"
@keydown.stop.shift.enter.exact="syncAndNavigate(NavigateDir.PREV)"
>
<CellTextArea v-if="isTextArea" v-model="vModel" />
<CellCheckbox v-else-if="isBoolean" v-model="vModel" />
<CellAttachment v-else-if="isAttachment" v-model="vModel" :row-index="props.rowIndex" />
<CellSingleSelect v-else-if="isSingleSelect" v-model="vModel" />
<CellMultiSelect v-else-if="isMultiSelect" v-model="vModel" />
<CellDatePicker v-else-if="isDate" v-model="vModel" />
<CellYearPicker v-else-if="isYear" v-model="vModel" />
<CellDateTimePicker v-else-if="isDateTime" v-model="vModel" />
<CellTimePicker v-else-if="isTime" v-model="vModel" />
<CellRating v-else-if="isRating" v-model="vModel" />
<CellDuration v-else-if="isDuration" v-model="vModel" />
<CellEmail v-else-if="isEmail" v-model="vModel" />
<CellUrl v-else-if="isURL" v-model="vModel" />
<CellPhoneNumber v-else-if="isPhoneNumber" v-model="vModel" />
<CellPercent v-else-if="isPercent" v-model="vModel" />
<CellCurrency v-else-if="isCurrency" v-model="vModel" />
<CellDecimal v-else-if="isDecimal" v-model="vModel" />
<CellInteger v-else-if="isInt" v-model="vModel" />
<CellFloat v-else-if="isFloat" v-model="vModel" />
<CellText v-else-if="isString" v-model="vModel" />
<CellJson v-else-if="isJSON" v-model="vModel" />
<CellText v-else v-model="vModel" />
<LazyCellTextArea v-if="isTextArea" v-model="vModel" />
<LazyCellCheckbox v-else-if="isBoolean" v-model="vModel" />
<LazyCellAttachment v-else-if="isAttachment" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellSingleSelect v-else-if="isSingleSelect" v-model="vModel" />
<LazyCellMultiSelect v-else-if="isMultiSelect" v-model="vModel" />
<LazyCellDatePicker v-else-if="isDate" v-model="vModel" />
<LazyCellYearPicker v-else-if="isYear" v-model="vModel" />
<LazyCellDateTimePicker v-else-if="isDateTime" v-model="vModel" />
<LazyCellTimePicker v-else-if="isTime" v-model="vModel" />
<LazyCellRating v-else-if="isRating" v-model="vModel" />
<LazyCellDuration v-else-if="isDuration" v-model="vModel" />
<LazyCellEmail v-else-if="isEmail" v-model="vModel" />
<LazyCellUrl v-else-if="isURL" v-model="vModel" />
<LazyCellPhoneNumber v-else-if="isPhoneNumber" v-model="vModel" />
<LazyCellPercent v-else-if="isPercent" v-model="vModel" />
<LazyCellCurrency v-else-if="isCurrency" v-model="vModel" />
<LazyCellDecimal v-else-if="isDecimal" v-model="vModel" />
<LazyCellInteger v-else-if="isInt" v-model="vModel" />
<LazyCellFloat v-else-if="isFloat" v-model="vModel" />
<LazyCellText v-else-if="isString" v-model="vModel" />
<LazyCellJson v-else-if="isJSON" v-model="vModel" />
<LazyCellText v-else v-model="vModel" />
<div v-if="(isLocked || (isPublic && readOnly && !isForm)) && !isAttachment" class="nc-locked-overlay" @click.stop.prevent />
</div>
</template>

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

@ -1,15 +1,17 @@
<script setup lang="ts">
import Draggable from 'vuedraggable'
import { RelationTypes, UITypes, getSystemColumns, isVirtualCol } from 'nocodb-sdk'
import { message } from 'ant-design-vue'
import {
ActiveViewInj,
IsFormInj,
IsGalleryInj,
MetaInj,
ReloadViewDataHookInj,
computed,
createEventHook,
extractSdkResponseErrorMsg,
inject,
message,
onClickOutside,
provide,
reactive,
@ -39,7 +41,7 @@ const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const formState: Record<any, any> = reactive({})
const formState = reactive({})
const secondsRemain = ref(0)
@ -51,7 +53,8 @@ const view = inject(ActiveViewInj, ref())
const { loadFormView, insertRow, formColumnData, formViewData, updateFormView } = useViewData(meta, view)
const reloadEventHook = createEventHook<void>()
const reloadEventHook = createEventHook<boolean | void>()
provide(ReloadViewDataHookInj, reloadEventHook)
reloadEventHook.on(async () => {
@ -446,13 +449,13 @@ onMounted(async () => {
>
<div class="flex">
<div class="flex flex-row flex-1">
<SmartsheetHeaderVirtualCell
<LazySmartsheetHeaderVirtualCell
v-if="isVirtualCol(element)"
:column="{ ...element, title: element.label || element.title }"
:required="isRequired(element, element.required)"
:hide-menu="true"
/>
<SmartsheetHeaderCell
<LazySmartsheetHeaderCell
v-else
class="w-full"
:column="{ ...element, title: element.label || element.title }"
@ -461,7 +464,7 @@ onMounted(async () => {
/>
</div>
<div class="flex flex-row">
<mdi-drag-vertical class="flex flex-1" />
<MdiDragVertical class="flex flex-1" />
</div>
</div>
</a-card>
@ -480,7 +483,7 @@ onMounted(async () => {
</div>
</a-button>
<template #overlay>
<SmartsheetColumnEditOrAddProvider
<LazySmartsheetColumnEditOrAddProvider
v-if="showColumnDropdown"
@submit="submitCallback"
@cancel="showColumnDropdown = false"
@ -625,13 +628,13 @@ onMounted(async () => {
</div>
</template>
<div>
<SmartsheetHeaderVirtualCell
<LazySmartsheetHeaderVirtualCell
v-if="isVirtualCol(element)"
:column="{ ...element, title: element.label || element.title }"
:required="isRequired(element, element.required)"
:hide-menu="true"
/>
<SmartsheetHeaderCell
<LazySmartsheetHeaderCell
v-else
:column="{ ...element, title: element.label || element.title }"
:required="isRequired(element, element.required)"
@ -645,7 +648,7 @@ onMounted(async () => {
:name="element.title"
:rules="[{ required: isRequired(element, element.required), message: `${element.title} is required` }]"
>
<SmartsheetVirtualCell
<LazySmartsheetVirtualCell
v-model="formState[element.title]"
:row="row"
class="nc-input"
@ -661,7 +664,7 @@ onMounted(async () => {
:name="element.title"
:rules="[{ required: isRequired(element, element.required), message: `${element.title} is required` }]"
>
<SmartsheetCell
<LazySmartsheetCell
v-model="formState[element.title]"
class="nc-input"
:class="`nc-form-input-${element.title.replaceAll(' ', '')}`"

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

@ -15,13 +15,17 @@ import {
ReloadRowDataHookInj,
ReloadViewMetaHookInj,
extractPkFromRow,
computed,
createEventHook,
inject,
nextTick,
provide,
ref,
useUIPermission,
useViewData,
watch,
} from '#imports'
import Row from '~/components/smartsheet/Row.vue'
import type { Row as RowType } from '~/composables'
import ImageIcon from '~icons/mdi/file-image-box'
interface Attachment {
url: string
@ -79,7 +83,7 @@ const isRowEmpty = (record: any, col: any) => {
return Array.isArray(val) && val.length === 0
}
const attachments = (record: any): Array<Attachment> => {
const attachments = (record: any): Attachment[] => {
try {
return coverImageColumn?.title && record.row[coverImageColumn.title] ? JSON.parse(record.row[coverImageColumn.title]) : []
} catch (e) {
@ -137,7 +141,9 @@ const reloadAttachments = ref(false)
reloadViewMetaHook?.on(async () => {
await loadGalleryData()
reloadAttachments.value = true
nextTick(() => {
reloadAttachments.value = false
})
@ -148,6 +154,7 @@ reloadViewDataHook?.on(async () => {
onMounted(async () => {
await loadData()
await loadGalleryData()
})
@ -159,7 +166,7 @@ provide(ReloadRowDataHookInj, reloadViewDataHook)
<div class="flex flex-col h-full w-full overflow-auto nc-gallery">
<div class="nc-gallery-container grid gap-2 my-4 px-3">
<div v-for="record in data" :key="`record-${record.row.id}`">
<Row :row="record">
<LazySmartsheetRow :row="record">
<a-card
hoverable
class="!rounded-lg h-full overflow-hidden break-all max-w-[450px]"
@ -180,14 +187,15 @@ provide(ReloadRowDataHookInj, reloadViewDataHook)
<template #nextArrow>
<div style="z-index: 1"></div>
</template>
<img
<nuxt-img
v-for="(attachment, index) in attachments(record)"
:key="`carousel-${record.row.id}-${index}`"
placeholder
class="h-52 object-cover"
:src="attachment.url"
/>
</a-carousel>
<ImageIcon v-else class="w-full h-48 my-4 text-cool-gray-200" />
<MdiFileImageBox v-else class="w-full h-48 my-4 text-cool-gray-200" />
</template>
<div
@ -197,26 +205,40 @@ provide(ReloadRowDataHookInj, reloadViewDataHook)
>
<div class="flex flex-row w-full justify-start border-b-1 border-gray-100 py-2.5">
<div class="w-full text-gray-600">
<SmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="true" />
<SmartsheetHeaderCell v-else :column="col" :hide-menu="true" />
<LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="true" />
<LazySmartsheetHeaderCell v-else :column="col" :hide-menu="true" />
</div>
</div>
<div class="flex flex-row w-full pb-3 pt-2 pl-2 items-center justify-start">
<div v-if="isRowEmpty(record, col)" class="h-3 bg-gray-200 px-5 rounded-lg"></div>
<template v-else>
<SmartsheetVirtualCell v-if="isVirtualCol(col)" v-model="record.row[col.title]" :column="col" :row="record" />
<SmartsheetCell v-else v-model="record.row[col.title]" :column="col" :edit-enabled="false" :read-only="true" />
<LazySmartsheetVirtualCell
v-if="isVirtualCol(col)"
v-model="record.row[col.title]"
:column="col"
:row="record"
/>
<LazySmartsheetCell
v-else
v-model="record.row[col.title]"
:column="col"
:edit-enabled="false"
:read-only="true"
/>
</template>
</div>
</div>
</a-card>
</Row>
</LazySmartsheetRow>
</div>
</div>
<div class="flex-1" />
<SmartsheetPagination />
<SmartsheetExpandedForm
<LazySmartsheetPagination />
<LazySmartsheetExpandedForm
v-if="expandedFormRow && expandedFormDlg"
v-model="expandedFormDlg"
:row="expandedFormRow"

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

@ -1,7 +1,6 @@
<script lang="ts" setup>
import type { ColumnType } from 'nocodb-sdk'
import { UITypes, isVirtualCol } from 'nocodb-sdk'
import { message } from 'ant-design-vue'
import {
ActiveViewInj,
CellUrlDisableOverlayInj,
@ -19,6 +18,7 @@ import {
createEventHook,
extractPkFromRow,
inject,
message,
onClickOutside,
onMounted,
provide,
@ -53,7 +53,7 @@ const reloadViewDataHook = inject(ReloadViewDataHookInj, createEventHook())
const openNewRecordFormHook = inject(OpenNewRecordFormHookInj, createEventHook())
const { isUIAllowed } = useUIPermission()
const hasEditPermission = isUIAllowed('xcDatatableEditable')
const hasEditPermission = $computed(() => isUIAllowed('xcDatatableEditable'))
const route = useRoute()
const router = useRouter()
@ -466,9 +466,9 @@ reloadViewDataHook.trigger()
@xcresized="resizingCol = null"
>
<div class="w-full h-full bg-gray-100 flex items-center">
<SmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="readOnly" />
<LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="readOnly" />
<SmartsheetHeaderCell v-else :column="col" :hide-menu="readOnly" />
<LazySmartsheetHeaderCell v-else :column="col" :hide-menu="readOnly" />
</div>
</th>
<th
@ -487,7 +487,7 @@ reloadViewDataHook.trigger()
</div>
<template #overlay>
<SmartsheetColumnEditOrAddProvider
<LazySmartsheetColumnEditOrAddProvider
v-if="addColumnDropdown"
@submit="addColumnDropdown = false"
@cancel="addColumnDropdown = false"
@ -500,7 +500,7 @@ reloadViewDataHook.trigger()
</tr>
</thead>
<tbody>
<SmartsheetRow v-for="(row, rowIndex) of data" ref="rowRefs" :key="rowIndex" :row="row">
<LazySmartsheetRow v-for="(row, rowIndex) of data" ref="rowRefs" :key="rowIndex" :row="row">
<template #default="{ state }">
<tr class="nc-grid-row">
<td key="row-index" class="caption nc-grid-cell pl-5 pr-1">
@ -558,7 +558,7 @@ reloadViewDataHook.trigger()
@contextmenu="showContextMenu($event, { row: rowIndex, col: colIndex })"
>
<div class="w-full h-full">
<SmartsheetVirtualCell
<LazySmartsheetVirtualCell
v-if="isVirtualCol(columnObj)"
v-model="row.row[columnObj.title]"
:column="columnObj"
@ -567,7 +567,7 @@ reloadViewDataHook.trigger()
@navigate="onNavigate"
/>
<SmartsheetCell
<LazySmartsheetCell
v-else
v-model="row.row[columnObj.title]"
:column="columnObj"
@ -588,7 +588,7 @@ reloadViewDataHook.trigger()
</td>
</tr>
</template>
</SmartsheetRow>
</LazySmartsheetRow>
<tr v-if="!isView && !isLocked && isUIAllowed('xcDatatableEditable') && !isSqlView">
<td
@ -641,7 +641,7 @@ reloadViewDataHook.trigger()
</a-dropdown>
</div>
<SmartsheetPagination />
<LazySmartsheetPagination />
<LazySmartsheetExpandedForm
v-if="expandedFormRow && expandedFormDlg"

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

@ -1,6 +1,5 @@
<script setup lang="ts">
import { computed, inject } from '#imports'
import { ChangePageInj, PaginationDataInj } from '~/context'
import { ChangePageInj, PaginationDataInj, computed, inject } from '#imports'
const paginatedData = inject(PaginationDataInj)!

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

@ -1,15 +1,25 @@
<script lang="ts" setup>
import type { Row } from '~/composables'
import { ReloadRowDataHookInj, useProvideSmartsheetRowStore, useSmartsheetStoreOrThrow } from '#imports'
interface Props {
import {
ReloadRowDataHookInj,
ReloadViewDataHookInj,
createEventHook,
inject,
provide,
toRef,
useProvideSmartsheetRowStore,
useSmartsheetStoreOrThrow,
watch,
} from '#imports'
const props = defineProps<{
row: Row
}
}>()
const props = defineProps<Props>()
const currentRow = toRef(props, 'row')
const { meta } = useSmartsheetStoreOrThrow()
const { isNew, state, syncLTARRefs } = useProvideSmartsheetRowStore(meta, currentRow)
// on changing isNew(new record insert) status sync LTAR cell values

25
packages/nc-gui/components/smartsheet/Toolbar.vue

@ -1,6 +1,5 @@
<script setup lang="ts">
import { IsPublicInj, useSharedView, useSidebar, useSmartsheetStoreOrThrow } from '#imports'
import ToggleDrawer from '~/components/smartsheet/sidebar/toolbar/ToggleDrawer.vue'
import { IsPublicInj, inject, ref, useSharedView, useSidebar, useSmartsheetStoreOrThrow, useUIPermission } from '#imports'
const { isGrid, isForm, isGallery, isSqlView } = useSmartsheetStoreOrThrow()
@ -18,34 +17,34 @@ const { allowCSVDownload } = useSharedView()
class="nc-table-toolbar w-full py-1 flex gap-1 items-center h-[var(--toolbar-height)] px-2 border-b overflow-x-hidden"
style="z-index: 7"
>
<SmartsheetToolbarViewActions
<LazySmartsheetToolbarViewActions
v-if="(isGrid || isGallery) && !isPublic && isUIAllowed('dataInsert')"
:show-system-fields="false"
class="ml-1"
/>
<SmartsheetToolbarViewInfo v-if="!isUIAllowed('dataInsert') && !isPublic" />
<LazySmartsheetToolbarViewInfo v-if="!isUIAllowed('dataInsert') && !isPublic" />
<SmartsheetToolbarFieldsMenu v-if="isGrid || isGallery" :show-system-fields="false" class="ml-1" />
<LazySmartsheetToolbarFieldsMenu v-if="isGrid || isGallery" :show-system-fields="false" class="ml-1" />
<SmartsheetToolbarColumnFilterMenu v-if="isGrid || isGallery" />
<LazySmartsheetToolbarColumnFilterMenu v-if="isGrid || isGallery" />
<SmartsheetToolbarSortListMenu v-if="isGrid || isGallery" />
<LazySmartsheetToolbarSortListMenu v-if="isGrid || isGallery" />
<SmartsheetToolbarShareView v-if="(isForm || isGrid) && !isPublic" />
<LazySmartsheetToolbarShareView v-if="(isForm || isGrid) && !isPublic" />
<SmartsheetToolbarExport v-if="(!isPublic && !isUIAllowed('dataInsert')) || (isPublic && allowCSVDownload)" />
<LazySmartsheetToolbarExport v-if="(!isPublic && !isUIAllowed('dataInsert')) || (isPublic && allowCSVDownload)" />
<div class="flex-1" />
<SmartsheetToolbarReload v-if="!isPublic && !isForm" class="mx-1" />
<LazySmartsheetToolbarReload v-if="!isPublic && !isForm" class="mx-1" />
<SmartsheetToolbarAddRow v-if="isUIAllowed('dataInsert') && !isPublic && !isForm && !isSqlView" class="mx-1" />
<LazySmartsheetToolbarAddRow v-if="isUIAllowed('dataInsert') && !isPublic && !isForm && !isSqlView" class="mx-1" />
<SmartsheetToolbarSearchData v-if="(isGrid || isGallery) && !isPublic" class="shrink mr-2 ml-2" />
<LazySmartsheetToolbarSearchData v-if="(isGrid || isGallery) && !isPublic" class="shrink mr-2 ml-2" />
<template v-if="!isOpen && !isPublic">
<div class="border-l-1 pl-3">
<ToggleDrawer class="mr-2" />
<LazySmartsheetSidebarToolbarToggleDrawer class="mr-2" />
</div>
</template>
</div>

38
packages/nc-gui/components/smartsheet/VirtualCell.vue

@ -4,30 +4,14 @@ import { ActiveCellInj, CellValueInj, ColumnInj, RowInj, provide, toRef, useVirt
import type { Row } from '~/composables'
import { NavigateDir } from '~/lib'
const props = defineProps<Props>()
const emit = defineEmits(['update:modelValue', 'navigate'])
const HasMany = defineAsyncComponent(() => import('../virtual-cell/HasMany.vue'))
const ManyToMany = defineAsyncComponent(() => import('../virtual-cell/ManyToMany.vue'))
const BelongsTo = defineAsyncComponent(() => import('../virtual-cell/BelongsTo.vue'))
const Rollup = defineAsyncComponent(() => import('../virtual-cell/Rollup.vue') as any)
const Formula = defineAsyncComponent(() => import('../virtual-cell/Formula.vue'))
const Count = defineAsyncComponent(() => import('../virtual-cell/Count.vue'))
const Lookup = defineAsyncComponent(() => import('../virtual-cell/Lookup.vue') as any)
interface Props {
const props = defineProps<{
column: ColumnType
modelValue: any
row: Row
active?: boolean
}
}>()
const emit = defineEmits(['update:modelValue', 'navigate'])
const column = toRef(props, 'column')
const active = toRef(props, 'active', false)
@ -47,12 +31,12 @@ const { isLookup, isBt, isRollup, isMm, isHm, isFormula, isCount } = useVirtualC
@keydown.stop.enter.exact="emit('navigate', NavigateDir.NEXT)"
@keydown.stop.shift.enter.exact="emit('navigate', NavigateDir.PREV)"
>
<HasMany v-if="isHm" />
<ManyToMany v-else-if="isMm" />
<BelongsTo v-else-if="isBt" />
<Rollup v-else-if="isRollup" />
<Formula v-else-if="isFormula" />
<Count v-else-if="isCount" />
<Lookup v-else-if="isLookup" />
<LazyVirtualCellHasMany v-if="isHm" />
<LazyVirtualCellManyToMany v-else-if="isMm" />
<LazyVirtualCellBelongsTo v-else-if="isBt" />
<LazyVirtualCellRollup v-else-if="isRollup" />
<LazyVirtualCellFormula v-else-if="isFormula" />
<LazyVirtualCellCount v-else-if="isCount" />
<LazyVirtualCellLookup v-else-if="isLookup" />
</div>
</template>

18
packages/nc-gui/components/smartsheet/column/AdvancedOptions.vue

@ -1,13 +1,13 @@
<script setup lang="ts">
import { UITypes } from 'nocodb-sdk'
import { computed } from '#imports'
import { computed, onBeforeMount, useColumnCreateStoreOrThrow, useProject, useVModel } from '#imports'
interface Props {
value: Record<string, any>
}
const props = defineProps<{
value: any
}>()
const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
const { sqlUi, isPg } = useProject()
@ -58,6 +58,7 @@ onBeforeMount(() => {
@change="onAlter"
/>
</a-form-item>
<a-form-item label="PK">
<a-checkbox
v-model:checked="vModel.pk"
@ -66,6 +67,7 @@ onBeforeMount(() => {
@change="onAlter"
/>
</a-form-item>
<a-form-item label="AI">
<a-checkbox
v-model:checked="vModel.ai"
@ -74,13 +76,16 @@ onBeforeMount(() => {
@change="onAlter"
/>
</a-form-item>
<a-form-item label="UN" :disabled="sqlUi.colPropUNDisabled(vModel) || !sqlUi.columnEditable(vModel)" @change="onAlter">
<a-checkbox v-model:checked="vModel.un" class="nc-column-checkbox-UN" />
</a-form-item>
<a-form-item label="AU" :disabled="sqlUi.colPropAuDisabled(vModel) || !sqlUi.columnEditable(vModel)" @change="onAlter">
<a-checkbox v-model:checked="vModel.au" class="nc-column-checkbox-AU" />
</a-form-item>
</div>
<a-form-item :label="$t('labels.databaseType')" v-bind="validateInfos.dt">
<a-select v-model:value="vModel.dt" dropdown-class-name="nc-dropdown-db-type" @change="onDataTypeChange">
<a-select-option v-for="type in dataTypes" :key="type" :value="type">
@ -88,6 +93,7 @@ onBeforeMount(() => {
</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="!hideLength" :label="$t('labels.lengthValue')">
<a-input
v-model:value="vModel.dtxp"
@ -95,9 +101,11 @@ onBeforeMount(() => {
@input="onAlter"
/>
</a-form-item>
<a-form-item v-if="sqlUi.showScale(vModel)" label="Scale">
<a-input v-model:value="vModel.dtxs" :disabled="!sqlUi.columnEditable(vModel)" @input="onAlter" />
</a-form-item>
<a-form-item :label="$t('placeholder.defaultValue')">
<a-textarea v-model:value="vModel.cdf" auto-size @input="onAlter(2, true)" />
<span class="text-gray-400 text-xs">{{ sampleValue }}</span>

14
packages/nc-gui/components/smartsheet/column/CheckboxOptions.vue

@ -1,12 +1,12 @@
<script setup lang="ts">
import { getMdiIcon } from '@/utils'
import { computed, getMdiIcon, useVModel, watch } from '#imports'
interface Props {
value: Record<string, any>
}
const props = defineProps<{
value: any
}>()
const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
// cater existing v1 cases
@ -88,6 +88,7 @@ watch(
color: vModel.meta.color,
}"
/>
<component
:is="getMdiIcon(icon.unchecked)"
:style="{
@ -100,8 +101,9 @@ watch(
</a-form-item>
</a-col>
</a-row>
<a-row class="w-full justify-center">
<GeneralColorPicker
<LazyGeneralColorPicker
v-model="picked"
:row-size="8"
:colors="['#fcb401', '#faa307', '#f48c06', '#e85d04', '#dc2f02', '#d00000', '#9d0208', '#777']"

29
packages/nc-gui/components/smartsheet/column/CurrencyOptions.vue

@ -1,20 +1,27 @@
<script setup lang="ts">
import { useProject } from '#imports'
import { currencyCodes, currencyLocales, validateCurrencyCode, validateCurrencyLocale } from '@/utils'
interface Props {
value: Record<string, any>
}
const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
import {
computed,
currencyCodes,
currencyLocales,
useProject,
useVModel,
validateCurrencyCode,
validateCurrencyLocale,
} from '#imports'
interface Option {
label: string
value: string
}
const props = defineProps<{
value: any
}>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
const validators = {
'meta.currency_locale': [
{
@ -91,6 +98,7 @@ vModel.value.meta = {
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item v-bind="validateInfos['meta.currency_code']" label="Currency Code">
<a-select
@ -107,6 +115,7 @@ vModel.value.meta = {
</a-select>
</a-form-item>
</a-col>
<a-col v-if="isMoney && isPg">
<span class="text-[#FB8C00]">{{ message }}</span>
</a-col>

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

@ -1,12 +1,12 @@
<script setup lang="ts">
import { dateFormats } from '~/utils'
import { dateFormats, useVModel } from '#imports'
interface Props {
value: Record<string, any>
}
const props = defineProps<{
value: any
}>()
const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
if (!vModel.value.meta?.date_format) {

11
packages/nc-gui/components/smartsheet/column/DurationOptions.vue

@ -1,12 +1,12 @@
<script setup lang="ts">
import { durationOptions } from '@/utils'
import { durationOptions, useVModel } from '#imports'
interface Props {
value: Record<string, any>
}
const props = defineProps<{
value: any
}>()
const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
const durationOptionList =
@ -28,6 +28,7 @@ vModel.value.meta = {
<a-col :span="24">
<span class="prose-sm mt-2">A duration of time in minutes or seconds (e.g. 1:23).</span>
</a-col>
<a-col :span="24">
<a-form-item label="Duration Format">
<a-select v-model:value="vModel.meta.duration" class="w-52" dropdown-class-name="nc-dropdown-duration-option">

47
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -1,7 +1,21 @@
<script lang="ts" setup>
import { UITypes, isVirtualCol } from 'nocodb-sdk'
import { message } from 'ant-design-vue'
import { IsFormInj, MetaInj, ReloadViewDataHookInj, computed, inject, uiTypes, useMetas, useNuxtApp, watchEffect } from '#imports'
import {
IsFormInj,
MetaInj,
ReloadViewDataHookInj,
computed,
inject,
message,
onMounted,
ref,
uiTypes,
useColumnCreateStoreOrThrow,
useI18n,
useMetas,
useNuxtApp,
watchEffect,
} from '#imports'
import MdiPlusIcon from '~icons/mdi/plus-circle-outline'
import MdiMinusIcon from '~icons/mdi/minus-circle-outline'
import MdiIdentifierIcon from '~icons/mdi/identifier'
@ -54,7 +68,7 @@ async function onSubmit() {
if (!saved) return
// add delay to complete the minimize transition
// add delay to complete minimize transition
setTimeout(() => {
advancedOptions.value = false
}, 500)
@ -79,7 +93,7 @@ watchEffect(() => {
})
onMounted(() => {
if (isEdit.value === false) {
if (!isEdit.value) {
generateNewColumnMeta()
} else {
if (formState.value.pk) {
@ -126,20 +140,21 @@ onMounted(() => {
</a-select-option>
</a-select>
</a-form-item>
<SmartsheetColumnFormulaOptions v-if="formState.uidt === UITypes.Formula" v-model:value="formState" />
<SmartsheetColumnCurrencyOptions v-if="formState.uidt === UITypes.Currency" v-model:value="formState" />
<SmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" v-model:value="formState" />
<SmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" v-model:value="formState" />
<SmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" v-model:value="formState" />
<SmartsheetColumnLookupOptions v-if="!isEdit && formState.uidt === UITypes.Lookup" v-model:value="formState" />
<SmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" v-model:value="formState" />
<SmartsheetColumnRollupOptions v-if="!isEdit && formState.uidt === UITypes.Rollup" v-model:value="formState" />
<SmartsheetColumnLinkedToAnotherRecordOptions
<LazySmartsheetColumnFormulaOptions v-if="formState.uidt === UITypes.Formula" v-model:value="formState" />
<LazySmartsheetColumnCurrencyOptions v-if="formState.uidt === UITypes.Currency" v-model:value="formState" />
<LazySmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" v-model:value="formState" />
<LazySmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" v-model:value="formState" />
<LazySmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" v-model:value="formState" />
<LazySmartsheetColumnLookupOptions v-if="!isEdit && formState.uidt === UITypes.Lookup" v-model:value="formState" />
<LazySmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" v-model:value="formState" />
<LazySmartsheetColumnRollupOptions v-if="!isEdit && formState.uidt === UITypes.Rollup" v-model:value="formState" />
<LazySmartsheetColumnLinkedToAnotherRecordOptions
v-if="!isEdit && formState.uidt === UITypes.LinkToAnotherRecord"
v-model:value="formState"
/>
<SmartsheetColumnSpecificDBTypeOptions v-if="formState.uidt === UITypes.SpecificDBType" />
<SmartsheetColumnSelectOptions
<LazySmartsheetColumnSpecificDBTypeOptions v-if="formState.uidt === UITypes.SpecificDBType" />
<LazySmartsheetColumnSelectOptions
v-if="formState.uidt === UITypes.SingleSelect || formState.uidt === UITypes.MultiSelect"
v-model:value="formState"
/>
@ -166,7 +181,7 @@ onMounted(() => {
</span>
</a-checkbox>
<SmartsheetColumnAdvancedOptions v-model:value="formState" />
<LazySmartsheetColumnAdvancedOptions v-model:value="formState" />
</div>
</Transition>

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

@ -1,10 +1,9 @@
<script lang="ts" setup>
import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { MetaInj, inject } from '#imports'
import { MetaInj, inject, ref, toRef, useProvideColumnCreateStore } from '#imports'
interface Props {
column?: Ref<ColumnType & { meta: any }>
column?: ColumnType & { meta: any }
}
const props = defineProps<Props>()
@ -14,9 +13,10 @@ const emit = defineEmits(['submit', 'cancel'])
const meta = inject(MetaInj, ref())
const column = toRef(props, 'column')
useProvideColumnCreateStore(meta, column as Ref<ColumnType | undefined>)
useProvideColumnCreateStore(meta, column)
</script>
<template>
<SmartsheetColumnEditOrAdd @submit="emit('submit')" @cancel="emit('cancel')"></SmartsheetColumnEditOrAdd>
<LazySmartsheetColumnEditOrAdd @submit="emit('submit')" @cancel="emit('cancel')" />
</template>

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

@ -16,15 +16,16 @@ import {
onMounted,
useColumnCreateStoreOrThrow,
useDebounceFn,
useVModel,
validateDateWithUnknownFormat,
} from '#imports'
interface Props {
value: Record<string, any>
}
const props = defineProps<{
value: any
}>()
const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
const { setAdditionalValidations, validateInfos, sqlUi, column } = useColumnCreateStoreOrThrow()
@ -619,6 +620,7 @@ onMounted(() => {
@change="handleInputDeb"
/>
</a-form-item>
<div class="text-gray-600 mt-2 mb-4 prose-sm">
Hint: Use {} to reference columns, e.g: {column_name}. For more, please check out
<a class="prose-sm" href="https://docs.nocodb.com/setup-and-usages/formulas#available-formula-features" target="_blank">
@ -644,16 +646,19 @@ onMounted(() => {
<a-col :span="6">
<span class="prose-sm text-gray-600">{{ item.text }}</span>
</a-col>
<a-col :span="18">
<div v-if="item.type === 'function'" class="text-xs text-gray-500">
{{ item.description }} <br /><br />
Syntax: <br />
{{ item.syntax }} <br /><br />
Examples: <br />
<div v-for="(example, idx) of item.examples" :key="idx">
<div>({{ idx + 1 }}): {{ example }}</div>
</div>
</div>
<div v-if="item.type === 'column'" class="float-right mr-5 -mt-2">
<a-badge-ribbon :text="item.uidt" color="gray" />
</div>
@ -663,7 +668,9 @@ onMounted(() => {
<template #avatar>
<mdi-function v-if="item.type === 'function'" class="text-lg" />
<mdi-calculator v-if="item.type === 'op'" class="text-lg" />
<component :is="item.icon" v-if="item.type === 'column'" class="text-lg" />
</template>
</a-list-item-meta>

14
packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue

@ -1,15 +1,15 @@
<script setup lang="ts">
import { ModelTypes, MssqlUi, SqliteUi } from 'nocodb-sdk'
import { MetaInj, inject, useProject } from '#imports'
import { MetaInj, inject, ref, useProject, useVModel } from '#imports'
import MdiPlusIcon from '~icons/mdi/plus-circle-outline'
import MdiMinusIcon from '~icons/mdi/minus-circle-outline'
interface Props {
value: Record<string, any>
}
const props = defineProps<{
value: any
}>()
const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
const meta = $(inject(MetaInj, ref()))
@ -57,6 +57,7 @@ const refTables = $computed(() => {
<a-radio value="mm">Many To Many</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
class="flex w-full pb-2 mt-4 nc-ltar-child-table"
:label="$t('labels.childTable')"
@ -81,6 +82,7 @@ const refTables = $computed(() => {
@click="advancedOptions = !advancedOptions"
>
{{ advancedOptions ? $t('general.hideAll') : $t('general.showMore') }}
<component :is="advancedOptions ? MdiMinusIcon : MdiPlusIcon" />
</div>
@ -99,6 +101,7 @@ const refTables = $computed(() => {
</a-select-option>
</a-select>
</a-form-item>
<a-form-item class="flex w-1/2" :label="$t('labels.onDelete')">
<a-select
v-model:value="vModel.onDelete"
@ -113,6 +116,7 @@ const refTables = $computed(() => {
</a-select>
</a-form-item>
</div>
<div class="flex flex-row">
<a-form-item>
<a-checkbox v-model:checked="vModel.virtual" name="virtual" @change="onDataTypeChange">Virtual Relation</a-checkbox>

11
packages/nc-gui/components/smartsheet/column/LookupOptions.vue

@ -1,14 +1,14 @@
<script setup lang="ts">
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { UITypes, isSystemColumn } from 'nocodb-sdk'
import { MetaInj } from '#imports'
import { MetaInj, inject, ref, useColumnCreateStoreOrThrow, useMetas, useProject, useVModel } from '#imports'
interface Props {
value: Record<string, any>
}
const props = defineProps<{
value: any
}>()
const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
const meta = $(inject(MetaInj, ref()))
@ -77,6 +77,7 @@ const columns = $computed(() => {
</a-select-option>
</a-select>
</a-form-item>
<a-form-item class="flex w-1/2" :label="$t('labels.childColumn')" v-bind="validateInfos.fk_lookup_column_id">
<a-select
v-model:value="vModel.fk_lookup_column_id"

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

@ -1,14 +1,14 @@
<!-- File not in use for now -->
<script setup lang="ts">
import { precisions } from '#imports'
import { precisions, useVModel } from '#imports'
interface Props {
value: Record<string, any>
}
const props = defineProps<{
value: any
}>()
const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
if (!vModel.value.meta) vModel.value.meta = {}

14
packages/nc-gui/components/smartsheet/column/RatingOptions.vue

@ -1,12 +1,12 @@
<script setup lang="ts">
import { getMdiIcon } from '#imports'
import { getMdiIcon, useVModel } from '#imports'
interface Props {
value: Record<string, any>
}
const props = defineProps<{
value: any
}>()
const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
// cater existing v1 cases
@ -82,6 +82,7 @@ watch(
color: vModel.meta.color,
}"
/>
<component
:is="getMdiIcon(icon.empty)"
:style="{
@ -103,8 +104,9 @@ watch(
</a-form-item>
</a-col>
</a-row>
<a-row class="w-full justify-center">
<GeneralColorPicker
<LazyGeneralColorPicker
v-model="picked"
:row-size="8"
:colors="['#fcb401', '#faa307', '#f48c06', '#e85d04', '#dc2f02', '#d00000', '#9d0208', '#777']"

12
packages/nc-gui/components/smartsheet/column/RollupOptions.vue

@ -1,13 +1,13 @@
<script setup lang="ts">
import { UITypes, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import { MetaInj, inject, useMetas, useProject } from '#imports'
import { MetaInj, inject, ref, useColumnCreateStoreOrThrow, useMetas, useProject, useVModel } from '#imports'
interface Props {
value: Record<string, any>
}
const props = defineProps<{
value: any
}>()
const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
const meta = $(inject(MetaInj, ref()))
@ -90,6 +90,7 @@ const columns = $computed(() => {
</a-select-option>
</a-select>
</a-form-item>
<a-form-item class="flex w-1/2" :label="$t('labels.childColumn')" v-bind="validateInfos.fk_rollup_column_id">
<a-select
v-model:value="vModel.fk_rollup_column_id"
@ -103,6 +104,7 @@ const columns = $computed(() => {
</a-select>
</a-form-item>
</div>
<a-form-item label="Aggregate function" v-bind="validateInfos.rollup_function">
<a-select
v-model:value="vModel.rollup_function"

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

@ -1,18 +1,14 @@
<script setup lang="ts">
import Draggable from 'vuedraggable'
import { UITypes } from 'nocodb-sdk'
import { enumColor } from '@/utils'
import MdiDragIcon from '~icons/mdi/drag-vertical'
import MdiArrowDownDropCircle from '~icons/mdi/arrow-down-drop-circle'
import MdiClose from '~icons/mdi/close'
import MdiPlusIcon from '~icons/mdi/plus'
interface Props {
value: Record<string, any>
}
import { enumColor, onMounted, useColumnCreateStoreOrThrow, useVModel, watch } from '#imports'
const props = defineProps<{
value: any
}>()
const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const vModel = useVModel(props, 'value', emit)
const { setAdditionalValidations, validateInfos } = useColumnCreateStoreOrThrow()
@ -96,19 +92,21 @@ watch(inputs, () => {
<Draggable :list="options" item-key="id" handle=".nc-child-draggable-icon">
<template #item="{ element, index }">
<div class="flex py-1 items-center">
<MdiDragIcon small class="nc-child-draggable-icon handle" />
<MdiDragVertical small class="nc-child-draggable-icon handle" />
<a-dropdown
v-model:visible="colorMenus[index]"
:trigger="['click']"
overlay-class-name="nc-dropdown-select-color-options"
>
<template #overlay>
<GeneralColorPicker v-model="element.color" :pick-button="true" @update:model-value="colorMenus[index] = false" />
<LazyGeneralColorPicker v-model="element.color" :pick-button="true" @update:model-value="colorMenus[index] = false" />
</template>
<MdiArrowDownDropCircle :style="{ 'font-size': '1.5em', 'color': element.color }" class="mr-2" />
</a-dropdown>
<a-input ref="inputs" v-model:value="element.title" class="caption" />
<MdiClose class="ml-2" :style="{ color: 'red' }" @click="removeOption(index)" />
</div>
</template>
@ -116,9 +114,10 @@ watch(inputs, () => {
<div v-if="validateInfos?.['colOptions.options']?.help?.[0]?.[0]" class="text-error text-[10px] my-2">
{{ validateInfos['colOptions.options'].help[0][0] }}
</div>
<a-button type="dashed" class="w-full caption mt-2" @click="addNewOption()">
<div class="flex items-center">
<MdiPlusIcon />
<MdiPlus />
<span class="flex-auto">Add option</span>
</div>
</a-button>
@ -126,5 +125,3 @@ watch(inputs, () => {
</Draggable>
</div>
</template>
<style scoped lang="scss"></style>

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

@ -25,7 +25,7 @@ watch(
<template>
<div class="h-full flex flex-col w-full bg-[#eceff1] p-2">
<div ref="commentsWrapperEl" class="flex-1 min-h-[100px] overflow-y-auto scrollbar-thin-dull p-2 space-y-2">
<v-skeleton-loader v-if="isCommentsLoading && !commentsAndLogs" type="list-item-avatar-two-line@8" />
<a-skeleton v-if="isCommentsLoading && !commentsAndLogs" type="list-item-avatar-two-line@8" />
<template v-else>
<div v-for="log of commentsAndLogs" :key="log.id" class="flex gap-1 text-xs">
@ -36,6 +36,7 @@ watch(
{{ isYou(log.user) ? 'You' : log.user == null ? 'Shared base' : log.user }}
{{ log.op_type === 'COMMENT' ? 'commented' : log.op_sub_type === 'INSERT' ? 'created' : 'edited' }}
</p>
<p
v-if="log.op_type === 'COMMENT'"
class="block caption my-2 nc-chip w-full min-h-20px p-2 rounded"
@ -53,14 +54,19 @@ watch(
</div>
</template>
</div>
<div class="border-1 my-2 w-full" />
<div class="p-0">
<div class="flex justify-center">
<!-- Comments only -->
<a-checkbox v-model:checked="commentsOnly" v-e="['c:row-expand:comment-only']" @change="loadCommentsAndLogs"
>{{ $t('labels.commentsOnly') }}<span class="text-[11px] text-gray-500"></span>
<a-checkbox v-model:checked="commentsOnly" v-e="['c:row-expand:comment-only']" @change="loadCommentsAndLogs">
{{ $t('labels.commentsOnly') }}
<span class="text-[11px] text-gray-500" />
</a-checkbox>
</div>
<div class="shrink mt-2 flex">
<a-input
v-model:value="comment"
@ -76,6 +82,7 @@ watch(
<mdi-account-circle class="text-lg text-pink-300" small @click="saveComment" />
</div>
</template>
<template #suffix>
<mdi-keyboard-return v-if="comment" class="text-sm" small @click="saveComment" />
</template>

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

@ -62,15 +62,11 @@ const copyRecordUrl = () => {
{{ meta.title }}
</template>
<!-- todo: table doesn't exist?
<template v-else>
{{ table }}
</template>
-->
<template v-if="primaryValue">: {{ primaryValue }}</template>
</h5>
<div class="flex-1" />
<a-tooltip placement="bottom">
<template #title>
<div class="text-center w-full">{{ $t('general.reload') }}</div>
@ -88,6 +84,7 @@ const copyRecordUrl = () => {
@click="copyRecordUrl"
/>
</a-tooltip>
<a-tooltip v-if="!isSqlView" placement="bottom">
<!-- Toggle comments draw -->
<template #title>

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

@ -1,13 +1,9 @@
<script setup lang="ts">
import { message } from 'ant-design-vue'
import type { TableType, ViewType } from 'nocodb-sdk'
import { UITypes, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import type { Ref } from 'vue'
import Cell from '../Cell.vue'
import VirtualCell from '../VirtualCell.vue'
import Comments from './Comments.vue'
import Header from './Header.vue'
import {
message,
FieldsInj,
IsFormInj,
MetaInj,
@ -128,7 +124,8 @@ export default {
:closable="false"
class="nc-drawer-expanded-form"
>
<Header :view="view" @cancel="onClose" />
<LazySmartsheetExpandedFormHeader :view="view" @cancel="onClose" />
<div class="!bg-gray-100 rounded flex-1">
<div class="flex h-full nc-form-wrapper items-stretch min-h-[max(70vh,100%)]">
<div class="flex-1 overflow-auto scrollbar-thin-dull nc-form-fields-container">
@ -145,9 +142,9 @@ export default {
<SmartsheetHeaderCell v-else :column="col" />
<div class="!bg-white rounded px-1 min-h-[35px] flex items-center mt-2">
<VirtualCell v-if="isVirtualCol(col)" v-model="row.row[col.title]" :row="row" :column="col" />
<LazySmartsheetVirtualCell v-if="isVirtualCol(col)" v-model="row.row[col.title]" :row="row" :column="col" />
<Cell
<LazySmartsheetCell
v-else
v-model="row.row[col.title]"
:column="col"
@ -161,7 +158,7 @@ export default {
<div v-if="!isNew" class="nc-comments-drawer min-w-0 min-h-full max-h-full" :class="{ active: commentsDrawer }">
<div class="h-full">
<Comments v-if="commentsDrawer" />
<LazySmartsheetExpandedFormComments v-if="commentsDrawer" />
</div>
</div>
</div>

10
packages/nc-gui/components/smartsheet/header/Cell.vue

@ -19,13 +19,16 @@ const editColumnDropdown = ref(false)
<template>
<div class="flex items-center w-full text-xs text-normal text-gray-500 font-weight-medium" :class="{ 'h-full': column }">
<SmartsheetHeaderCellIcon v-if="column" />
<LazySmartsheetHeaderCellIcon v-if="column" />
<span v-if="column" class="name" style="white-space: nowrap" :title="column.title">{{ column.title }}</span>
<span v-if="(column.rqd && !column.cdf) || required" class="text-red-500">&nbsp;*</span>
<template v-if="!hideMenu">
<div class="flex-1" />
<SmartsheetHeaderMenu v-if="!isForm && isUIAllowed('edit-column')" @edit="editColumnDropdown = true" />
<LazySmartsheetHeaderMenu v-if="!isForm && isUIAllowed('edit-column')" @edit="editColumnDropdown = true" />
</template>
<a-dropdown
@ -36,8 +39,9 @@ const editColumnDropdown = ref(false)
overlay-class-name="nc-dropdown-edit-column"
>
<div />
<template #overlay>
<SmartsheetColumnEditOrAddProvider
<LazySmartsheetColumnEditOrAddProvider
v-if="editColumnDropdown"
:column="column"
class="w-full"

3
packages/nc-gui/components/smartsheet/header/CellIcon.vue

@ -1,7 +1,7 @@
<script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { ColumnInj, toRef, useColumn } from '#imports'
import { ColumnInj, computed, inject, toRef, useColumn } from '#imports'
import FilePhoneIcon from '~icons/mdi/file-phone'
import KeyIcon from '~icons/mdi/key-variant'
import JSONIcon from '~icons/mdi/code-json'
@ -28,6 +28,7 @@ import DurationIcon from '~icons/mdi/timer-outline'
const props = defineProps<{ columnMeta?: ColumnType }>()
const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, columnMeta)
const additionalColMeta = useColumn(column as Ref<ColumnType>)

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

@ -1,8 +1,18 @@
<script lang="ts" setup>
import { Modal, message } from 'ant-design-vue'
import { Modal } from 'ant-design-vue'
import type { LinkToAnotherRecordType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import { ColumnInj, IsLockedInj, MetaInj, extractSdkResponseErrorMsg, inject, useI18n, useMetas, useNuxtApp } from '#imports'
import {
ColumnInj,
IsLockedInj,
MetaInj,
extractSdkResponseErrorMsg,
inject,
message,
useI18n,
useMetas,
useNuxtApp,
} from '#imports'
const { virtual = false } = defineProps<{ virtual?: boolean }>()

8
packages/nc-gui/components/smartsheet/header/VirtualCell.vue

@ -99,7 +99,7 @@ const tooltipMsg = computed(() => {
<template>
<div class="flex items-center w-full text-xs text-gray-500 font-weight-medium" :class="{ 'h-full': column }">
<SmartsheetHeaderVirtualCellIcon v-if="column" />
<LazySmartsheetHeaderVirtualCellIcon v-if="column" />
<a-tooltip placement="bottom">
<template #title>
@ -112,7 +112,8 @@ const tooltipMsg = computed(() => {
<template v-if="!hideMenu">
<div class="flex-1" />
<SmartsheetHeaderMenu v-if="!isForm && isUIAllowed('edit-column')" :virtual="true" @edit="editColumnDropdown = true" />
<LazySmartsheetHeaderMenu v-if="!isForm && isUIAllowed('edit-column')" :virtual="true" @edit="editColumnDropdown = true" />
</template>
<a-dropdown
@ -123,8 +124,9 @@ const tooltipMsg = computed(() => {
overlay-class-name="nc-dropdown-edit-column"
>
<div />
<template #overlay>
<SmartsheetColumnEditOrAddProvider
<LazySmartsheetColumnEditOrAddProvider
v-if="editColumnDropdown"
:column="column"
class="w-full"

3
packages/nc-gui/components/smartsheet/header/VirtualCellIcon.vue

@ -2,7 +2,7 @@
import type { ColumnType, LinkToAnotherRecordType, LookupType } from 'nocodb-sdk'
import { RelationTypes, UITypes } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { ColumnInj, toRef } from '#imports'
import { ColumnInj, inject, ref, toRef } from '#imports'
import GenericIcon from '~icons/mdi/square-rounded'
import HMIcon from '~icons/mdi/table-arrow-right'
import BTIcon from '~icons/mdi/table-arrow-left'
@ -14,6 +14,7 @@ import SpecificDBTypeIcon from '~icons/mdi/database-settings'
import TableColumnPlusBefore from '~icons/mdi/table-column-plus-before'
const props = defineProps<{ columnMeta?: ColumnType }>()
const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, ref(columnMeta)) as Ref<ColumnType & { colOptions: LookupType }>

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

@ -1,7 +1,6 @@
<script lang="ts" setup>
import { ViewTypes } from 'nocodb-sdk'
import { useNuxtApp } from '#app'
import { useSmartsheetStoreOrThrow, useUIPermission, viewIcons } from '#imports'
import { useNuxtApp, useSmartsheetStoreOrThrow, useUIPermission, viewIcons } from '#imports'
interface Emits {
(event: 'openModal', data: { type: ViewTypes; title?: string }): void

9
packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue

@ -2,17 +2,17 @@
import type { ViewType, ViewTypes } from 'nocodb-sdk'
import type { SortableEvent } from 'sortablejs'
import type { Menu as AntMenu } from 'ant-design-vue'
import { message } from 'ant-design-vue'
import type { Ref } from 'vue'
import Sortable from 'sortablejs'
import RenameableMenuItem from './RenameableMenuItem.vue'
import {
ActiveViewInj,
ViewListInj,
extractSdkResponseErrorMsg,
inject,
message,
onMounted,
ref,
resolveComponent,
useApi,
useDialog,
useI18n,
@ -22,7 +22,6 @@ import {
viewTypeAlias,
watch,
} from '#imports'
import DlgViewDelete from '~/components/dlg/ViewDelete.vue'
const emits = defineEmits<Emits>()
@ -190,7 +189,7 @@ async function onRename(view: ViewType) {
function openDeleteDialog(view: Record<string, any>) {
const isOpen = ref(true)
const { close } = useDialog(DlgViewDelete, {
const { close } = useDialog(resolveComponent('LazyDlgViewDelete') as any, {
'modelValue': isOpen,
'view': view,
'onUpdate:modelValue': closeDialog,
@ -219,7 +218,7 @@ function openDeleteDialog(view: Record<string, any>) {
<template>
<a-menu ref="menuRef" :class="{ dragging }" class="nc-views-menu flex-1" :selected-keys="selected">
<RenameableMenuItem
<LazySmartsheetSidebarRenameableMenuItem
v-for="(view, index) of views"
:id="view.id"
:key="view.id"

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

@ -1,8 +1,17 @@
<script lang="ts" setup>
import type { ViewType, ViewTypes } from 'nocodb-sdk'
import { message } from 'ant-design-vue'
import type { WritableComputedRef } from '@vue/reactivity'
import { IsLockedInj, inject, onKeyStroke, useDebounceFn, useNuxtApp, useUIPermission, useVModel, viewIcons } from '#imports'
import {
IsLockedInj,
inject,
message,
onKeyStroke,
useDebounceFn,
useNuxtApp,
useUIPermission,
useVModel,
viewIcons,
} from '#imports'
interface Props {
view: ViewType
@ -168,7 +177,7 @@ function onStopEdit() {
<a-input v-if="isEditing" :ref="focusInput" v-model:value="vModel.title" @blur="onCancel" @keydown="onKeyDown($event)" />
<div v-else>
<GeneralTruncateText>{{ vModel.alias || vModel.title }}</GeneralTruncateText>
<LazyGeneralTruncateText>{{ vModel.alias || vModel.title }}</LazyGeneralTruncateText>
</div>
<div class="flex-1" />

11
packages/nc-gui/components/smartsheet/sidebar/index.vue

@ -1,8 +1,5 @@
<script setup lang="ts">
import type { ViewType, ViewTypes } from 'nocodb-sdk'
import MenuTop from './MenuTop.vue'
import MenuBottom from './MenuBottom.vue'
import Toolbar from './toolbar/index.vue'
import {
ActiveViewInj,
MetaInj,
@ -110,19 +107,19 @@ function onCreate(view: ViewType) {
class="relative shadow-md h-full"
theme="light"
>
<Toolbar
<LazySmartsheetSidebarToolbar
v-if="isOpen"
class="min-h-[var(--toolbar-height)] max-h-[var(--toolbar-height)] flex items-center py-3 px-3 justify-between border-b-1"
/>
<div v-if="isOpen" class="flex-1 flex flex-col min-h-0">
<MenuTop @open-modal="openModal" @deleted="loadViews" />
<LazySmartsheetSidebarMenuTop @open-modal="openModal" @deleted="loadViews" />
<div v-if="isUIAllowed('virtualViewsCreateOrEdit')" class="!my-3 w-full border-b-1" />
<MenuBottom @open-modal="openModal" />
<LazySmartsheetSidebarMenuBottom @open-modal="openModal" />
</div>
<dlg-view-create
<LazyDlgViewCreate
v-if="views"
v-model="modalOpen"
:title="viewCreateTitle"

3
packages/nc-gui/components/smartsheet/sidebar/toolbar/DebugMeta.vue

@ -15,13 +15,14 @@ const localTables = tables.value.filter((t) => metas[t.id as string])
<template #title>
<span> Debug Meta </span>
</template>
<mdi-bug-outline class="cursor-pointer" @click="editorOpen = true" />
</a-tooltip>
<a-modal v-model:visible="editorOpen" :footer="null" width="80%" wrap-class-name="nc-modal-debug-meta">
<a-tabs v-model:activeKey="tabKey" type="card" closeable="false" class="shadow-sm">
<a-tab-pane v-for="table in localTables" :key="table.id" :tab="table.title">
<MonacoEditor v-model="metas[table.id]" class="h-max-[70vh]" :read-only="true" />
<LazyMonacoEditor v-model="metas[table.id]" class="h-max-[70vh]" :read-only="true" />
</a-tab-pane>
</a-tabs>
</a-modal>

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

@ -1,8 +1,8 @@
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
import { message, useApi, useI18n } from '#imports'
const { t } = useI18n()
const { api } = useApi()
async function deleteCache() {
@ -21,6 +21,7 @@ async function deleteCache() {
<template #title>
<span> Delete Cache </span>
</template>
<mdi-delete class="cursor-pointer" @click="deleteCache" />
</a-tooltip>
</template>

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

@ -1,5 +1,5 @@
<script setup lang="ts">
import { MetaInj, inject, useTable } from '#imports'
import { MetaInj, inject, ref, useSidebar, useTable } from '#imports'
const meta = inject(MetaInj, ref())

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

@ -1,12 +1,13 @@
<script setup lang="ts">
import { message } from 'ant-design-vue'
import FileSaver from 'file-saver'
import { useI18n } from 'vue-i18n'
import { message, useApi, useI18n } from '#imports'
const { t } = useI18n()
const { api } = useApi()
async function exportCache() {
const FileSaver = await import('file-saver')
try {
const data = await api.utils.cacheGet()
if (!data) {
@ -14,10 +15,13 @@ async function exportCache() {
message.info(t('msg.info.cacheEmpty'))
return
}
const blob = new Blob([JSON.stringify(data)], {
type: 'text/plain;charset=utf-8',
})
FileSaver.saveAs(blob, 'cache_exported.json')
// Exported Cache Successfully
message.info(t('msg.info.exportedCache'))
} catch (e: any) {
@ -31,6 +35,7 @@ async function exportCache() {
<template #title>
<span> Export Cache </span>
</template>
<mdi-export class="cursor-pointer" @click="exportCache" />
</a-tooltip>
</template>

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

@ -1,5 +1,6 @@
<script setup lang="ts">
/** Sidebar visible */
import { useSidebar } from '#imports'
const { isOpen, toggle } = useSidebar('nc-right-sidebar')
const onClick = () => {
@ -12,6 +13,7 @@ const onClick = () => {
<a-button size="small" class="nc-toggle-right-navbar" @click="onClick">
<div class="flex items-center gap-1 text-xs" :class="{ 'text-gray-500': !isOpen }">
<AntDesignMenuUnfoldOutlined v-if="isOpen" />
<AntDesignMenuFoldOutlined v-else />
{{ $t('objects.views') }}

30
packages/nc-gui/components/smartsheet/sidebar/toolbar/index.vue

@ -1,40 +1,32 @@
<script lang="ts" setup>
import ExportCache from './ExportCache.vue'
import DeleteCache from './DeleteCache.vue'
import DebugMeta from './DebugMeta.vue'
import ToggleDrawer from './ToggleDrawer.vue'
let debug = $ref(false)
const debug = $ref(false)
let clickCount = $ref(0)
const clickCount = $ref(0)
const onClick = () => {
clickCount = clickCount + 1
debug = clickCount >= 4
}
</script>
<template>
<div
class="flex gap-2 justify-start"
@click="
() => {
clickCount = clickCount + 1
debug = clickCount >= 4
}
"
>
<div class="flex gap-2 justify-start" @click="onClick">
<slot name="start" />
<ToggleDrawer />
<LazySmartsheetSidebarToolbarToggleDrawer />
<span></span>
<template v-if="debug">
<ExportCache />
<LazySmartsheetSidebarToolbarExportCache />
<div class="dot" />
<DeleteCache />
<LazySmartsheetSidebarToolbarDeleteCache />
<div class="dot" />
<DebugMeta />
<LazySmartsheetSidebarToolbarDebugMeta />
<div class="dot" />
</template>

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

@ -1,7 +1,6 @@
<script setup lang="ts">
import type { FilterType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import FieldListAutoCompleteDropdown from './FieldListAutoCompleteDropdown.vue'
import {
ActiveViewInj,
MetaInj,
@ -63,21 +62,6 @@ const filterUpdateCondition = (filter: FilterType, i: number) => {
})
}
// todo : filter based on type
// const filterComparisonOp = (f) =>
// comparisonOp.filter((op) => {
// // if (
// // f &&
// // f.fk_column_id &&
// // this.columnsById[f.fk_column_id] &&
// // this.columnsById[f.fk_column_id].uidt === UITypes.LinkToAnotherRecord &&
// // this.columnsById[f.fk_column_id].uidt === UITypes.Lookup
// // ) {
// // return !['notempty', 'empty', 'notnull', 'null'].includes(op.value)
// // }
// return true
// })
const columns = computed(() => meta.value?.columns)
const types = computed(() => {
@ -167,7 +151,7 @@ defineExpose({
</div>
<span class="col-span-3" />
<div class="col-span-5">
<SmartsheetToolbarColumnFilter
<LazySmartsheetToolbarColumnFilter
v-if="filter.id || filter.children"
ref="localNestedFilters"
v-model="filter.children"
@ -178,21 +162,12 @@ defineExpose({
</div>
</template>
<template v-else>
<!-- <v-icon
v-if="!filter.readOnly"
:key="`${i}_3`"
small
class="nc-filter-item-remove-btn"
@click.stop="deleteFilter(filter, i)"
>
mdi-close-box
</v-icon> -->
<MdiCloseBox
v-if="!filter.readOnly"
class="nc-filter-item-remove-btn text-grey self-center"
@click.stop="deleteFilter(filter, i)"
/>
<span v-else />
<span v-if="!i" class="flex items-center">{{ $t('labels.where') }}</span>
@ -213,7 +188,7 @@ defineExpose({
</a-select-option>
</a-select>
<FieldListAutoCompleteDropdown
<LazySmartsheetToolbarFieldListAutoCompleteDropdown
:key="`${i}_6`"
v-model="filter.fk_column_id"
class="nc-filter-field-select"
@ -239,18 +214,9 @@ defineExpose({
{{ compOp.text }}
</a-select-option>
</a-select>
<!--
todo: filter based on column type
item-value="value"
item-text="text"
:items="filterComparisonOp(filter)" -->
<!-- <template #item="{ item }"> -->
<!-- <span class="caption font-weight-regular">{{ item.text }}</span> -->
<!-- </template> -->
<!-- </v-select> -->
<span v-if="['null', 'notnull', 'empty', 'notempty'].includes(filter.comparison_op)" :key="`span${i}`" />
<a-checkbox
v-else-if="types[filter.field] === 'boolean'"
v-model:checked="filter.value"
@ -258,6 +224,7 @@ defineExpose({
:disabled="filter.readOnly"
@change="saveOrUpdate(filter, i)"
/>
<a-input
v-else
:key="`${i}_7`"
@ -275,15 +242,14 @@ defineExpose({
<div class="flex gap-2 mb-2 mt-4">
<a-button class="elevation-0 text-capitalize" type="primary" ghost @click.stop="addFilter">
<div class="flex items-center gap-1">
<!-- <v-icon small color="grey"> mdi-plus </v-icon> -->
<MdiPlus />
<!-- Add Filter -->
{{ $t('activity.addFilter') }}
</div>
</a-button>
<a-button v-if="!webHook" class="text-capitalize !text-gray-500" @click.stop="addFilterGroup">
<div class="flex items-center gap-1">
<!-- <v-icon small color="grey"> mdi-plus </v-icon> -->
<!-- Add Filter Group -->
<MdiPlus />
{{ $t('activity.addFilterGroup') }}

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

@ -1,7 +1,18 @@
<script setup lang="ts">
import { useNuxtApp } from 'nuxt/app'
import type ColumnFilter from './ColumnFilter.vue'
import { ActiveViewInj, IsLockedInj, IsPublicInj, computed, inject, ref, useGlobal, useViewFilters } from '#imports'
import {
ActiveViewInj,
IsLockedInj,
IsPublicInj,
computed,
inject,
ref,
useGlobal,
useNuxtApp,
useSmartsheetStoreOrThrow,
useViewFilters,
watch,
} from '#imports'
const isLocked = inject(IsLockedInj, ref(false))
@ -16,6 +27,7 @@ const filterComp = ref<typeof ColumnFilter>()
const { $e } = useNuxtApp()
const { nestedFilters } = useSmartsheetStoreOrThrow()
// todo: avoid duplicate api call by keeping a filter store
const { filters, loadFilters } = useViewFilters(
activeView,
@ -62,8 +74,9 @@ const filterAutoSaveLoc = computed({
</div>
</a-button>
</div>
<template #overlay>
<SmartsheetToolbarColumnFilter
<LazySmartsheetToolbarColumnFilter
ref="filterComp"
class="nc-table-toolbar-menu shadow-lg"
:auto-save="filterAutoSave"
@ -88,7 +101,7 @@ const filterAutoSaveLoc = computed({
Apply changes
</a-button>
</div>
</SmartsheetToolbarColumnFilter>
</LazySmartsheetToolbarColumnFilter>
</template>
</a-dropdown>
</template>

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

@ -10,7 +10,7 @@
<template #overlay>
<a-menu class="ml-6 !text-sm !px-0 !py-2 !rounded">
<SmartsheetToolbarExportSubActions />
<LazySmartsheetToolbarExportSubActions />
</a-menu>
</template>
</a-dropdown>

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

@ -1,10 +1,20 @@
<script setup lang="ts">
import type { RequestParams } from 'nocodb-sdk'
import { ExportTypes } from 'nocodb-sdk'
import FileSaver from 'file-saver'
import * as XLSX from 'xlsx'
import { message } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
import {
ActiveViewInj,
FieldsInj,
IsPublicInj,
MetaInj,
extractSdkResponseErrorMsg,
inject,
message,
ref,
useI18n,
useNuxtApp,
useProject,
useSmartsheetStoreOrThrow,
} from '#imports'
const { t } = useI18n()
@ -27,6 +37,9 @@ const exportFile = async (exportType: ExportTypes) => {
let c = 1
const responseType = exportType === ExportTypes.EXCEL ? 'base64' : 'blob'
const XLSX = await import('xlsx')
const FileSaver = await import('file-saver')
try {
while (!isNaN(offset) && offset > -1) {
let res
@ -51,14 +64,19 @@ const exportFile = async (exportType: ExportTypes) => {
} as RequestParams,
)
}
const { data, headers } = res
if (exportType === ExportTypes.EXCEL) {
const workbook = XLSX.read(data, { type: 'base64' })
XLSX.writeFile(workbook, `${meta.value?.title}_exported_${c++}.xlsx`)
} else if (exportType === ExportTypes.CSV) {
const blob = new Blob([data], { type: 'text/plain;charset=utf-8' })
FileSaver.saveAs(blob, `${meta.value?.title}_exported_${c++}.csv`)
}
offset = +headers['nc-export-offset']
if (offset > -1) {
// Downloading more files

13
packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue

@ -4,12 +4,10 @@ import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk'
import { MetaInj, computed, inject, ref, resolveComponent } from '#imports'
interface Props {
const { modelValue, isSort } = defineProps<{
modelValue?: string
isSort?: boolean
}
const { modelValue, isSort } = defineProps<Props>()
}>()
const emit = defineEmits(['update:modelValue'])
@ -21,7 +19,7 @@ const localValue = computed({
})
const options = computed<SelectProps['options']>(() =>
meta?.value?.columns
meta.value?.columns
?.filter((c: ColumnType) => {
/** ignore hasmany and manytomany relations if it's using within sort menu */
if (isSort) {
@ -37,7 +35,9 @@ const options = computed<SelectProps['options']>(() =>
value: c.id,
label: c.title,
icon: h(
isVirtualCol(c) ? resolveComponent('SmartsheetHeaderVirtualCellIcon') : resolveComponent('SmartsheetHeaderCellIcon'),
isVirtualCol(c)
? resolveComponent('LazySmartsheetHeaderVirtualCellIcon')
: resolveComponent('LazySmartsheetHeaderCellIcon'),
{
columnMeta: c,
},
@ -63,6 +63,7 @@ const filterOption = (input: string, option: any) => {
<a-select-option v-for="option in options" :key="option.value" :value="option.value">
<div class="flex gap-2 items-center items-center h-full">
<component :is="option.icon" class="min-w-5 !mx-0" />
<span class="min-w-0"> {{ option.label }}</span>
</div>
</a-select-option>

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

@ -111,9 +111,12 @@ const coverOptions = computed<SelectProps['options']>(() => {
})
const getIcon = (c: ColumnType) =>
h(isVirtualCol(c) ? resolveComponent('SmartsheetHeaderVirtualCellIcon') : resolveComponent('SmartsheetHeaderCellIcon'), {
columnMeta: c,
})
h(
isVirtualCol(c) ? resolveComponent('LazySmartsheetHeaderVirtualCellIcon') : resolveComponent('LazySmartsheetHeaderCellIcon'),
{
columnMeta: c,
},
)
</script>
<template>
@ -130,6 +133,7 @@ const getIcon = (c: ColumnType) =>
</div>
</a-button>
</div>
<template #overlay>
<div
class="p-3 min-w-[280px] bg-gray-50 shadow-lg nc-table-toolbar-menu max-h-[max(80vh,500px)] overflow-auto !border"
@ -142,11 +146,13 @@ const getIcon = (c: ColumnType) =>
:options="coverOptions"
dropdown-class-name="nc-dropdown-cover-image"
@click.stop
></a-select>
/>
</a-card>
<div class="p-1" @click.stop>
<a-input v-model:value="filterQuery" size="small" :placeholder="$t('placeholder.searchFields')" />
</div>
<div class="nc-fields-list py-1">
<Draggable v-model="fields" item-key="id" @change="onMove($event)">
<template #item="{ element: field, index: index }">
@ -159,10 +165,13 @@ const getIcon = (c: ColumnType) =>
>
<div class="flex items-center">
<component :is="getIcon(metaColumnById[field.fk_column_id])" />
<span>{{ field.title }}</span>
</div>
</a-checkbox>
<div class="flex-1" />
<MdiDrag class="cursor-move" />
</div>
</template>
@ -176,11 +185,13 @@ const getIcon = (c: ColumnType) =>
<span class="text-xs"> {{ $t('activity.showSystemFields') }}</span>
</a-checkbox>
</div>
<div class="p-2 flex gap-2" @click.stop>
<a-button size="small" class="!text-xs text-gray-500 text-capitalize" @click.stop="showAll()">
<!-- Show All -->
{{ $t('general.showAll') }}
</a-button>
<a-button size="small" class="!text-xs text-gray-500 text-capitalize" @click.stop="hideAll()">
<!-- Hide All -->
{{ $t('general.hideAll') }}

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

@ -2,8 +2,8 @@
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 { ActiveViewInj, inject } from '#imports'
const { type, hideTick } = defineProps<{ hideTick?: boolean; type: LockType }>()
@ -35,11 +35,15 @@ const selectedView = inject(ActiveViewInj)
<div :class="{ 'show-tick': !hideTick }">
<template v-if="!hideTick">
<MdiCheck v-if="selectedView?.lock_type === type" />
<span v-else />
</template>
<div>
<component :is="types[type].icon" class="text-gray-500" />
{{ $t(types[type].title) }}
<div class="nc-subtitle whitespace-normal">
{{ $t(types[type].subtitle) }}
</div>

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

@ -1,10 +1,6 @@
<script lang="ts" setup>
import * as XLSX from 'xlsx'
import type { RequestParams } from 'nocodb-sdk'
import { ExportTypes } from 'nocodb-sdk'
import FileSaver from 'file-saver'
import { message } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
import {
ActiveViewInj,
FieldsInj,
@ -13,11 +9,14 @@ import {
MetaInj,
extractSdkResponseErrorMsg,
inject,
message,
ref,
useI18n,
useNuxtApp,
useProject,
useUIPermission,
} from '#imports'
const { t } = useI18n()
const sharedViewListDlg = ref(false)
@ -51,6 +50,9 @@ const exportFile = async (exportType: ExportTypes) => {
let c = 1
const responseType = exportType === ExportTypes.EXCEL ? 'base64' : 'blob'
const XLSX = await import('xlsx')
const FileSaver = await import('file-saver')
try {
while (!isNaN(offset) && offset > -1) {
let res

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

@ -1,7 +1,8 @@
<script setup lang="ts">
import { ReloadViewDataHookInj, inject } from '#imports'
import { ReloadViewDataHookInj, inject, useNuxtApp } from '#imports'
const { $e } = useNuxtApp()
const reloadHook = inject(ReloadViewDataHookInj)!
const onClick = () => {

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

@ -5,7 +5,6 @@ const reloadData = inject(ReloadViewDataHookInj)!
const { search, meta } = useSmartsheetStoreOrThrow()
// todo: where is this value supposed to come from? it's not in the store
const isDropdownOpen = ref(false)
const searchDropdown = ref(null)
@ -33,6 +32,7 @@ function onPressEnter() {
@click="isDropdownOpen = !isDropdownOpen"
>
<MdiMagnify class="text-grey" />
<MdiMenuDown class="text-grey" />
<a-select
@ -45,6 +45,7 @@ function onPressEnter() {
class="!absolute top-0 left-0 w-full h-full z-10 !text-xs opacity-0"
/>
</div>
<a-input
v-model:value="search.query"
size="small"

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

@ -1,17 +1,19 @@
<script lang="ts" setup>
import { ViewTypes } from 'nocodb-sdk'
import { message } from 'ant-design-vue'
import {
computed,
extractSdkResponseErrorMsg,
message,
ref,
useCopy,
useDashboard,
useI18n,
useNuxtApp,
useProject,
useSmartsheetStoreOrThrow,
useUIPermission,
watch,
} from '#imports'
import MdiOpenInNewIcon from '~icons/mdi/open-in-new'
import MdiCopyIcon from '~icons/mdi/content-copy'
const { t } = useI18n()
@ -125,7 +127,7 @@ watch(passwordProtected, (value) => {
class="nc-btn-share-view nc-toolbar-btn"
>
<div class="flex items-center gap-1" @click="genShareLink">
<MdiOpenInNewIcon />
<MdiOpenInNew />
<!-- Share View -->
<span class="!text-sm font-weight-normal"> {{ $t('activity.shareView') }}</span>
</div>
@ -143,9 +145,9 @@ watch(passwordProtected, (value) => {
<div class="share-link-box nc-share-link-box bg-primary-50">
<div class="flex-1 h-min text-xs">{{ sharedViewUrl }}</div>
<a v-e="['c:view:share:open-url']" :href="sharedViewUrl" target="_blank">
<MdiOpenInNewIcon class="text-sm text-gray-500 mt-2" />
<MdiOpenInNew class="text-sm text-gray-500 mt-2" />
</a>
<MdiCopyIcon v-e="['c:view:share:copy-url']" class="text-gray-500 text-sm cursor-pointer" @click="copyLink" />
<MdiContentCopy v-e="['c:view:share:copy-url']" class="text-gray-500 text-sm cursor-pointer" @click="copyLink" />
</div>
<a-collapse ghost>

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

@ -1,11 +1,18 @@
<script lang="ts" setup>
import { ViewTypes } from 'nocodb-sdk'
import { Empty, message } from 'ant-design-vue'
import { extractSdkResponseErrorMsg, onMounted, useCopy, useI18n, useSmartsheetStoreOrThrow } from '#imports'
import { Empty } from 'ant-design-vue'
import {
extractSdkResponseErrorMsg,
message,
onMounted,
ref,
useCopy,
useDashboard,
useI18n,
useSmartsheetStoreOrThrow,
} from '#imports'
import MdiVisibilityOnIcon from '~icons/mdi/visibility'
import MdiVisibilityOffIcon from '~icons/mdi/visibility-off'
import MdiCopyIcon from '~icons/mdi/content-copy'
import MdiDeleteIcon from '~icons/mdi/delete-outline'
interface SharedViewType {
password: string
@ -27,12 +34,7 @@ const { dashboardUrl } = useDashboard()
const sharedViewList = ref<SharedViewType[]>()
const loadSharedViewsList = async () => {
const list = await $api.dbViewShare.list(meta.value?.id as string)
console.log(unref(sharedViewList))
console.log(list)
sharedViewList.value = list
sharedViewList.value = await $api.dbViewShare.list(meta.value?.id as string)
// todo: show active view in list separately
// const index = sharedViewList.value.findIndex((v) => {
@ -93,7 +95,6 @@ const deleteLink = async (id: string) => {
<template>
<div class="w-full">
<a-table
class=""
size="small"
:data-source="sharedViewList"
:pagination="{ position: ['bottomCenter'] }"
@ -104,6 +105,7 @@ const deleteLink = async (id: string) => {
<template #emptyText>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
</template>
<!-- View name -->
<a-table-column key="title" :title="$t('labels.viewName')" data-index="title">
<template #default="{ text }">
@ -112,6 +114,7 @@ const deleteLink = async (id: string) => {
</div>
</template>
</a-table-column>
<!-- View Link -->
<a-table-column key="title" :title="$t('labels.viewLink')" data-index="title">
<template #default="{ record }">
@ -120,6 +123,7 @@ const deleteLink = async (id: string) => {
</nuxt-link>
</template>
</a-table-column>
<!-- Password -->
<a-table-column key="password" :title="$t('labels.password')" data-index="title">
<template #default="{ record }">
@ -134,6 +138,7 @@ const deleteLink = async (id: string) => {
</div>
</template>
</a-table-column>
<a-table-column key="meta" :title="$t('labels.downloadAllowed')" data-index="title">
<template #default="{ record }">
<template v-if="'meta' in record">
@ -141,12 +146,13 @@ const deleteLink = async (id: string) => {
</template>
</template>
</a-table-column>
<!-- Actions -->
<a-table-column key="id" :title="$t('labels.actions')" data-index="title">
<template #default="{ record }">
<div class="text-sm flex gap-2" :title="text">
<MdiCopyIcon class="cursor-pointer" @click="copyLink(record)" />
<MdiDeleteIcon class="cursor-pointer" @click="deleteLink(record.id)" />
<MdiContentCopy class="cursor-pointer" @click="copyLink(record)" />
<MdiDeleteOutline class="cursor-pointer" @click="deleteLink(record.id)" />
</div>
</template>
</a-table-column>

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

@ -1,6 +1,5 @@
<script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk'
import FieldListAutoCompleteDropdown from './FieldListAutoCompleteDropdown.vue'
import {
ActiveViewInj,
IsLockedInj,
@ -43,9 +42,10 @@ watch(
<template>
<a-dropdown offset-y class="" :trigger="['click']" overlay-class-name="nc-dropdown-sort-menu">
<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"
><div class="flex items-center gap-1">
<a-button v-e="['c:sort']" class="nc-sort-menu-btn nc-toolbar-btn" :disabled="isLocked">
<div class="flex items-center gap-1">
<MdiSort />
<!-- Sort -->
<span class="text-capitalize !text-sm font-weight-normal">{{ $t('activity.sort') }}</span>
<MdiMenuDown class="text-grey" />
@ -58,7 +58,7 @@ watch(
<template v-for="(sort, i) in sorts || []" :key="i">
<MdiCloseBox class="nc-sort-item-remove-btn text-grey self-center" small @click.stop="deleteSort(sort, i)" />
<FieldListAutoCompleteDropdown
<LazySmartsheetToolbarFieldListAutoCompleteDropdown
v-model="sort.fk_column_id"
class="caption nc-sort-field-select"
:columns="columns"
@ -85,6 +85,7 @@ watch(
</a-select>
</template>
</div>
<a-button class="text-capitalize mb-1 mt-4" type="primary" ghost @click.stop="addSort">
<div class="flex gap-1 items-center">
<MdiPlus />

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

@ -1,11 +1,11 @@
<script lang="ts" setup>
import { message } from 'ant-design-vue'
import {
ActiveViewInj,
IsLockedInj,
IsPublicInj,
extractSdkResponseErrorMsg,
inject,
message,
ref,
useI18n,
useNuxtApp,
@ -92,10 +92,13 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
class="nc-view-icon group-hover:hidden"
:style="{ color: viewIcons[selectedView?.type].color }"
/>
<span class="!text-sm font-weight-normal"
><GeneralTruncateText>{{ selectedView?.title }}</GeneralTruncateText></span
>
<span class="!text-sm font-weight-normal">
<GeneralTruncateText>{{ selectedView?.title }}</GeneralTruncateText>
</span>
<component :is="Icon" class="text-gray-500" :class="`nc-icon-${selectedView?.lock_type}`" />
<MdiMenuDown class="text-grey" />
</div>
</a-button>
@ -122,14 +125,18 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
<a-menu-item @click="changeLockType(LockType.Collaborative)">
<SmartsheetToolbarLockType :type="LockType.Collaborative" />
</a-menu-item>
<a-menu-item @click="changeLockType(LockType.Locked)">
<SmartsheetToolbarLockType :type="LockType.Locked" />
</a-menu-item>
<a-menu-item @click="changeLockType(LockType.Personal)">
<SmartsheetToolbarLockType :type="LockType.Personal" />
</a-menu-item>
</a-sub-menu>
<a-menu-divider />
<a-sub-menu key="download">
<template #title>
<!-- Download -->
@ -145,8 +152,10 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
</template>
<template #expandIcon></template>
<SmartsheetToolbarExportSubActions />
<LazySmartsheetToolbarExportSubActions />
</a-sub-menu>
<template v-if="isUIAllowed('csvImport') && !isView && !isPublicView && !isSqlView">
<a-sub-menu key="upload">
<!-- Upload -->
@ -178,7 +187,9 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
</a-menu-item>
</a-sub-menu>
</template>
<a-menu-divider />
<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">
<MdiViewListOutline class="text-gray-500" />
@ -186,6 +197,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
{{ $t('activity.listSharedView') }}
</div>
</a-menu-item>
<a-menu-item v-if="!isSqlView">
<div
v-if="isUIAllowed('webhook') && !isView && !isPublicView"
@ -197,6 +209,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
{{ $t('objects.webhooks') }}
</div>
</a-menu-item>
<a-menu-item v-if="!isSharedBase && !isPublicView">
<div v-e="['c:snippet:open']" class="py-2 flex gap-2 items-center" @click="showApiSnippetDrawer = true">
<MdiXml class="text-gray-500" />
@ -215,9 +228,9 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
</template>
</a-dropdown>
<DlgQuickImport v-if="quickImportDialog" v-model="quickImportDialog" import-type="csv" :import-only="true" />
<LazyDlgQuickImport v-if="quickImportDialog" v-model="quickImportDialog" import-type="csv" :import-only="true" />
<WebhookDrawer v-if="showWebhookDrawer" v-model="showWebhookDrawer" />
<LazyWebhookDrawer v-if="showWebhookDrawer" v-model="showWebhookDrawer" />
<SmartsheetToolbarErd v-model="showErd" />
@ -228,9 +241,10 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
:footer="null"
wrap-class-name="nc-modal-shared-view-list"
>
<SmartsheetToolbarSharedViewList v-if="sharedViewListDlg" />
<LazySmartsheetToolbarSharedViewList v-if="sharedViewListDlg" />
</a-modal>
<SmartsheetApiSnippet v-model="showApiSnippetDrawer" />
<LazySmartsheetApiSnippet v-model="showApiSnippetDrawer" />
</div>
</template>

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

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ActiveViewInj, viewIcons } from '#imports'
import { ActiveViewInj, inject, viewIcons } from '#imports'
const selectedView = inject(ActiveViewInj)
</script>
@ -12,8 +12,9 @@ const selectedView = inject(ActiveViewInj)
class="nc-view-icon group-hover:hidden"
:style="{ color: viewIcons[selectedView?.type].color }"
/>
<span class="!text-sm font-medium max-w-36 overflow-ellipsis overflow-hidden whitespace-nowrap">{{
selectedView?.title
}}</span>
<span class="!text-sm font-medium max-w-36 overflow-ellipsis overflow-hidden whitespace-nowrap">
{{ selectedView?.title }}
</span>
</div>
</template>

10
packages/nc-gui/pages/[projectType]/view/[viewId].vue

@ -1,11 +1,11 @@
<script setup lang="ts">
import { message } from 'ant-design-vue'
import {
ReadonlyInj,
ReloadViewDataHookInj,
createEventHook,
definePageMeta,
extractSdkResponseErrorMsg,
message,
provide,
ref,
useRoute,
@ -20,11 +20,13 @@ definePageMeta({
const route = useRoute()
const reloadEventHook = createEventHook<void>()
const reloadEventHook = createEventHook()
provide(ReloadViewDataHookInj, reloadEventHook)
provide(ReadonlyInj, true)
const { loadSharedView } = useSharedView()
const showPassword = ref(false)
try {
@ -42,9 +44,9 @@ try {
<template>
<NuxtLayout id="content" class="flex" name="shared-view">
<div v-if="showPassword">
<SharedViewAskPassword v-model="showPassword" />
<LazySharedViewAskPassword v-model="showPassword" />
</div>
<SharedViewGrid v-else />
<LazySharedViewGrid v-else />
</NuxtLayout>
</template>

Loading…
Cancel
Save