Browse Source

Merge pull request #3044 from nocodb/chore/type-issues

pull/3056/head
Braks 2 years ago committed by GitHub
parent
commit
2f8f0fcbb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      packages/nc-gui-v2/.eslintrc.js
  2. 27
      packages/nc-gui-v2/components.d.ts
  3. 16
      packages/nc-gui-v2/components/cell/Currency.vue
  4. 9
      packages/nc-gui-v2/components/cell/Decimal.vue
  5. 9
      packages/nc-gui-v2/components/cell/Email.vue
  6. 3
      packages/nc-gui-v2/components/cell/Float.vue
  7. 3
      packages/nc-gui-v2/components/cell/Integer.vue
  8. 55
      packages/nc-gui-v2/components/cell/Json.vue
  9. 28
      packages/nc-gui-v2/components/cell/Rating.vue
  10. 8
      packages/nc-gui-v2/components/cell/SingleSelect.vue
  11. 3
      packages/nc-gui-v2/components/cell/Text.vue
  12. 12
      packages/nc-gui-v2/components/cell/TextArea.vue
  13. 11
      packages/nc-gui-v2/components/cell/Url.vue
  14. 5
      packages/nc-gui-v2/components/cell/attachment/Modal.vue
  15. 14
      packages/nc-gui-v2/components/dashboard/settings/UIAcl.vue
  16. 11
      packages/nc-gui-v2/components/general/Language.vue
  17. 10
      packages/nc-gui-v2/components/smartsheet-column/AdvancedOptions.vue
  18. 2
      packages/nc-gui-v2/components/smartsheet-column/CheckboxOptions.vue
  19. 38
      packages/nc-gui-v2/components/smartsheet-column/FormulaOptions.vue
  20. 7
      packages/nc-gui-v2/components/smartsheet-column/LinkedToAnotherRecordOptions.vue
  21. 21
      packages/nc-gui-v2/components/smartsheet-column/LookupOptions.vue
  22. 28
      packages/nc-gui-v2/components/smartsheet-column/RollupOptions.vue
  23. 2
      packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue
  24. 54
      packages/nc-gui-v2/components/smartsheet-toolbar/FieldsMenu.vue
  25. 23
      packages/nc-gui-v2/components/smartsheet-toolbar/SearchData.vue
  26. 11
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  27. 8
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue
  28. 5
      packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/DeleteTable.vue
  29. 11
      packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/LockMenu.vue
  30. 9
      packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/Reload.vue
  31. 49
      packages/nc-gui-v2/components/tabs/auth/user-management/ShareBase.vue
  32. 25
      packages/nc-gui-v2/components/virtual-cell/BelongsTo.vue
  33. 12
      packages/nc-gui-v2/components/virtual-cell/Formula.vue
  34. 40
      packages/nc-gui-v2/components/virtual-cell/HasMany.vue
  35. 23
      packages/nc-gui-v2/components/virtual-cell/Lookup.vue
  36. 43
      packages/nc-gui-v2/components/virtual-cell/ManyToMany.vue
  37. 5
      packages/nc-gui-v2/components/virtual-cell/components/ItemChip.vue
  38. 23
      packages/nc-gui-v2/components/virtual-cell/components/ListChildItems.vue
  39. 13
      packages/nc-gui-v2/components/virtual-cell/components/ListItems.vue
  40. 94
      packages/nc-gui-v2/components/webhook/Editor.vue
  41. 8
      packages/nc-gui-v2/components/webhook/List.vue
  42. 3
      packages/nc-gui-v2/composables/useColumnCreateStore.ts
  43. 4
      packages/nc-gui-v2/composables/useLTARStore.ts
  44. 66
      packages/nc-gui-v2/composables/useTableCreate.ts
  45. 667
      packages/nc-gui-v2/package-lock.json
  46. 2
      packages/nc-gui-v2/package.json
  47. 4
      packages/nc-gui-v2/vue-color-shims.d.ts

5
packages/nc-gui-v2/.eslintrc.js

@ -2,6 +2,11 @@ const baseRules = {
'vue/no-setup-props-destructure': 0,
'no-console': 0,
'antfu/if-newline': 0,
'no-unused-vars': 0,
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
],
'prettier/prettier': ['error', {}, { usePrettierrc: true }],
}

27
packages/nc-gui-v2/components.d.ts vendored

@ -61,12 +61,39 @@ 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']
CilFullscreen: typeof import('~icons/cil/fullscreen')['default']
CilFullscreenExit: typeof import('~icons/cil/fullscreen-exit')['default']
IcOutlineInsertDriveFile: typeof import('~icons/ic/outline-insert-drive-file')['default']
IcRoundKeyboardArrowDown: typeof import('~icons/ic/round-keyboard-arrow-down')['default']
MaterialSymbolsAttachFile: typeof import('~icons/material-symbols/attach-file')['default']
MaterialSymbolsFileCopyOutline: typeof import('~icons/material-symbols/file-copy-outline')['default']
MaterialSymbolsMenu: typeof import('~icons/material-symbols/menu')['default']
MaterialSymbolsTranslate: typeof import('~icons/material-symbols/translate')['default']
MdiArrowExpand: typeof import('~icons/mdi/arrow-expand')['default']
MdiAt: typeof import('~icons/mdi/at')['default']
MdiCloseCircle: typeof import('~icons/mdi/close-circle')['default']
MdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
MdiContentSave: typeof import('~icons/mdi/content-save')['default']
MdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default']
MdiDownload: typeof import('~icons/mdi/download')['default']
MdiDrag: typeof import('~icons/mdi/drag')['default']
MdiEyeOffOutline: typeof import('~icons/mdi/eye-off-outline')['default']
MdiFlag: typeof import('~icons/mdi/flag')['default']
MdiHeart: typeof import('~icons/mdi/heart')['default']
MdiHook: typeof import('~icons/mdi/hook')['default']
MdiLinkVariantRemove: typeof import('~icons/mdi/link-variant-remove')['default']
MdiLogout: typeof import('~icons/mdi/logout')['default']
MdiMagnify: typeof import('~icons/mdi/magnify')['default']
MdiMenuDown: typeof import('~icons/mdi/menu-down')['default']
MdiMoonFull: typeof import('~icons/mdi/moon-full')['default']
MdiOpenInNew: typeof import('~icons/mdi/open-in-new')['default']
MdiPlus: typeof import('~icons/mdi/plus')['default']
MdiReload: typeof import('~icons/mdi/reload')['default']
MdiStar: typeof import('~icons/mdi/star')['default']
MdiTableArrowRight: typeof import('~icons/mdi/table-arrow-right')['default']
MdiThumbUp: typeof import('~icons/mdi/thumb-up')['default']
MdiXml: typeof import('~icons/mdi/xml')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}

16
packages/nc-gui-v2/components/cell/Currency.vue

@ -1,4 +1,5 @@
<script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core'
import { computed, inject, ref, useVModel } from '#imports'
import { ColumnInj, EditModeInj } from '~/context'
@ -10,35 +11,34 @@ const props = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const column = inject(ColumnInj)
const column = inject(ColumnInj)!
const editEnabled = inject(EditModeInj, ref(false))
const root = ref<HTMLInputElement>()
const vModel = useVModel(props, 'modelValue', emit)
const currencyMeta = computed(() => {
return {
currency_locale: 'en-US',
currency_code: 'USD',
...(column?.value?.meta ? column?.value?.meta : {}),
...(column.value.meta ? column.value.meta : {}),
}
})
const currency = computed(() => {
try {
return isNaN(vModel.value)
return !vModel.value || isNaN(vModel.value)
? vModel.value
: new Intl.NumberFormat(currencyMeta?.value?.currency_locale || 'en-US', {
: new Intl.NumberFormat(currencyMeta.value.currency_locale || 'en-US', {
style: 'currency',
currency: currencyMeta?.value?.currency_code || 'USD',
currency: currencyMeta.value.currency_code || 'USD',
}).format(vModel.value)
} catch (e) {
return vModel.value
}
})
const focus = (el: HTMLInputElement) => el?.focus()
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</script>
<template>

9
packages/nc-gui-v2/components/cell/Decimal.vue

@ -1,5 +1,6 @@
<script lang="ts" setup>
import { computed, inject, onMounted, ref } from '#imports'
import type { VNodeRef } from '@vue/runtime-core'
import { inject, ref, useVModel } from '#imports'
import { EditModeInj } from '~/context'
interface Props {
@ -14,13 +15,11 @@ const props = defineProps<Props>()
const emits = defineEmits<Emits>()
const editEnabled = inject<boolean>(EditModeInj)
const root = ref<HTMLInputElement>()
const editEnabled = inject(EditModeInj, ref(false))
const vModel = useVModel(props, 'modelValue', emits)
const focus = (el: HTMLInputElement) => el?.focus()
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</script>
<template>

9
packages/nc-gui-v2/components/cell/Email.vue

@ -1,5 +1,6 @@
<script lang="ts" setup>
import { computed, inject, useVModel } from '#imports'
import type { VNodeRef } from '@vue/runtime-core'
import { computed, inject, ref, useVModel } from '#imports'
import { isEmail } from '~/utils'
import { EditModeInj } from '~/context'
@ -19,13 +20,13 @@ const editEnabled = inject(EditModeInj, ref(false))
const vModel = useVModel(props, 'modelValue', emits)
const validEmail = computed(() => isEmail(vModel.value))
const validEmail = computed(() => vModel.value && isEmail(vModel.value))
const focus = (el: HTMLInputElement) => el?.focus()
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</script>
<template>
<input v-if="editEnabled" ref="root" v-model="vModel" class="outline-none prose-sm" @blur="editEnabled = false" />
<input v-if="editEnabled" :ref="focus" v-model="vModel" class="outline-none prose-sm" @blur="editEnabled = false" />
<a v-else-if="validEmail" class="prose-sm underline hover:opacity-75" :href="`mailto:${vModel}`" target="_blank">
{{ vModel }}
</a>

3
packages/nc-gui-v2/components/cell/Float.vue

@ -1,4 +1,5 @@
<script lang="ts" setup>
import type { VNodeRef } from '@vue/runtime-core'
import { inject, ref, useVModel } from '#imports'
import { EditModeInj } from '~/context'
@ -18,7 +19,7 @@ const editEnabled = inject(EditModeInj, ref(false))
const vModel = useVModel(props, 'modelValue', emits)
const focus = (el: HTMLInputElement) => el?.focus()
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</script>
<template>

3
packages/nc-gui-v2/components/cell/Integer.vue

@ -1,4 +1,5 @@
<script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core'
import { inject, ref, useVModel } from '#imports'
import { EditModeInj } from '~/context'
@ -18,7 +19,7 @@ const editEnabled = inject(EditModeInj, ref(false))
const vModel = useVModel(props, 'modelValue', emits)
const focus = (el: HTMLInputElement) => el?.focus()
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
function onKeyDown(evt: KeyboardEvent) {
return evt.key === '.' && evt.preventDefault()

55
packages/nc-gui-v2/components/cell/Json.vue

@ -1,9 +1,7 @@
<script setup lang="ts">
import { Modal as AModal } from 'ant-design-vue'
import Editor from '~/components/monaco/Editor.vue'
import FullScreenIcon from '~icons/cil/fullscreen'
import FullScreenExitIcon from '~icons/cil/fullscreen-exit'
import { inject } from '#imports'
import { computed, inject, ref, useVModel, watch } from '#imports'
import { EditModeInj } from '~/context'
interface Props {
@ -20,27 +18,29 @@ const emits = defineEmits<Emits>()
const editEnabled = inject(EditModeInj, ref(false))
let vModel = $(useVModel(props, 'modelValue', emits))
const vModel = useVModel(props, 'modelValue', emits)
let localValueState = $ref<string | undefined>(undefined)
let localValue = $(
computed<string | undefined>({
get: () => localValueState,
const localValueState = ref<string | undefined>()
const localValue = computed<string | Record<string, any> | undefined>({
get: () => localValueState.value,
set: (val: undefined | string | Record<string, any>) => {
localValueState = typeof val === 'object' ? JSON.stringify(val, null, 2) : val
localValueState.value = typeof val === 'object' ? JSON.stringify(val, null, 2) : val
},
}),
)
})
let error = $ref<string | undefined>()
let error = $ref<string | undefined>(undefined)
let isExpanded = $ref(false)
const clear = () => {
error = undefined
isExpanded = false
editEnabled.value = false
localValue = vModel
localValue.value = vModel.value
}
const formatJson = (json: string) => {
@ -53,22 +53,26 @@ const formatJson = (json: string) => {
const onSave = () => {
isExpanded = false
editEnabled.value = false
localValue = localValue ? formatJson(localValue) : localValue
vModel = localValue
localValue.value = localValue ? formatJson(localValue.value as string) : localValue
vModel.value = localValue.value
}
watch(
$$(vModel),
vModel,
(val) => {
localValue = val
localValue.value = val
},
{ immediate: true },
)
watch($$(localValue), (val) => {
watch(localValue, (val) => {
try {
JSON.parse(val)
JSON.parse(val as string)
error = undefined
} catch (e: any) {
error = e
@ -77,7 +81,8 @@ watch($$(localValue), (val) => {
watch(editEnabled, () => {
isExpanded = false
localValue = vModel
localValue.value = vModel.value
})
</script>
@ -86,16 +91,20 @@ watch(editEnabled, () => {
<div v-if="editEnabled" class="flex flex-col w-full">
<div class="flex flex-row justify-between pt-1 pb-2">
<a-button type="text" size="small" @click="isExpanded = !isExpanded">
<FullScreenExitIcon v-if="isExpanded" class="h-2.5" />
<FullScreenIcon v-else class="h-2.5" />
<CilFullscreenExit v-if="isExpanded" class="h-2.5" />
<CilFullscreen v-else class="h-2.5" />
</a-button>
<div class="flex flex-row">
<a-button type="text" size="small" :onclick="clear"><div class="text-xs">Cancel</div></a-button>
<a-button type="primary" size="small" :disabled="!!error || localValue === vModel">
<div class="text-xs" :onclick="onSave">Save</div>
</a-button>
</div>
</div>
<Editor
:model-value="localValue"
class="min-w-full w-80"
@ -104,10 +113,12 @@ watch(editEnabled, () => {
:disable-deep-compare="true"
@update:model-value="localValue = $event"
/>
<span v-if="error" class="text-xs w-full py-1 text-red-500">
{{ error.toString() }}
</span>
</div>
<span v-else>{{ vModel }}</span>
</component>
</template>

28
packages/nc-gui-v2/components/cell/Rating.vue

@ -1,22 +1,17 @@
<script setup lang="ts">
import { computed, inject } from '#imports'
import { ColumnInj } from '~/context'
import MdiStarIcon from '~icons/mdi/star'
import MdiHeartIcon from '~icons/mdi/heart'
import MdiMoonFullIcon from '~icons/mdi/moon-full'
import MdiThumbUpIcon from '~icons/mdi/thumb-up'
import MdiFlagIcon from '~icons/mdi/flag'
interface Props {
modelValue?: number | null
readOnly?: boolean
}
const props = defineProps<Props>()
const { modelValue, readOnly } = defineProps<Props>()
const emits = defineEmits(['update:modelValue'])
const column = inject(ColumnInj)
const column = inject(ColumnInj)!
const ratingMeta = computed(() => {
return {
@ -26,21 +21,24 @@ const ratingMeta = computed(() => {
},
color: '#fcb401',
max: 5,
...(column?.value?.meta || {}),
...(column.value?.meta || {}),
}
})
const vModel = useVModel(props, 'modelValue', emits)
const vModel = computed({
get: () => modelValue ?? NaN,
set: (val) => emits('update:modelValue', val),
})
</script>
<template>
<a-rate v-model:value="vModel" :count="ratingMeta.max" :style="`color: ${ratingMeta.color}`" :disabled="props.readOnly">
<a-rate v-model:value="vModel" :count="ratingMeta.max" :style="`color: ${ratingMeta.color}`" :disabled="readOnly">
<template #character>
<MdiStarIcon v-if="ratingMeta.icon.full === 'mdi-star'" class="text-sm" />
<MdiHeartIcon v-if="ratingMeta.icon.full === 'mdi-heart'" class="text-sm" />
<MdiMoonFullIcon v-if="ratingMeta.icon.full === 'mdi-moon-full'" class="text-sm" />
<MdiThumbUpIcon v-if="ratingMeta.icon.full === 'mdi-thumb-up'" class="text-sm" />
<MdiFlagIcon v-if="ratingMeta.icon.full === 'mdi-flag'" class="text-sm" />
<MdiStar v-if="ratingMeta.icon.full === 'mdi-star'" class="text-sm" />
<MdiHeart v-if="ratingMeta.icon.full === 'mdi-heart'" class="text-sm" />
<MdiMoonFull v-if="ratingMeta.icon.full === 'mdi-moon-full'" class="text-sm" />
<MdiThumbUp v-if="ratingMeta.icon.full === 'mdi-thumb-up'" class="text-sm" />
<MdiFlag v-if="ratingMeta.icon.full === 'mdi-flag'" class="text-sm" />
</template>
</a-rate>
</template>

8
packages/nc-gui-v2/components/cell/SingleSelect.vue

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { computed, inject } from '#imports'
import { ColumnInj, EditModeInj } from '~/context'
import { ColumnInj } from '~/context'
interface Props {
modelValue: string | null
@ -10,16 +10,14 @@ const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const column = inject(ColumnInj)
const isForm = inject<boolean>('isForm', false)
const editEnabled = inject(EditModeInj, ref(false))
const column = inject(ColumnInj)!
const vModel = computed({
get: () => modelValue?.replace(/\\'/g, "'").replace(/^'|'$/g, ''),
set: (val) => emit('update:modelValue', val),
})
const options = computed(() => column?.value?.dtxp?.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) || [])
const options = computed(() => column.value.dtxp?.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) || [])
</script>
<template>

3
packages/nc-gui-v2/components/cell/Text.vue

@ -1,4 +1,5 @@
<script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core'
import { inject, ref, useVModel } from '#imports'
import { EditModeInj } from '~/context'
@ -14,7 +15,7 @@ const editEnabled = inject(EditModeInj, ref(false))
const vModel = useVModel(props, 'modelValue', emits)
const focus = (el: HTMLInputElement) => el?.focus()
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</script>
<template>

12
packages/nc-gui-v2/components/cell/TextArea.vue

@ -1,20 +1,24 @@
<script setup lang="ts">
import { inject, ref, useVModel } from '#imports'
import type { VNodeRef } from '@vue/runtime-core'
import { computed, inject, ref } from '#imports'
import { EditModeInj } from '~/context'
interface Props {
modelValue: string | null
}
const props = defineProps<Props>()
const { modelValue } = defineProps<Props>()
const emits = defineEmits(['update:modelValue'])
const editEnabled = inject(EditModeInj, ref(false))
const vModel = useVModel(props, 'modelValue', emits)
const vModel = computed({
get: () => modelValue ?? '',
set: (value) => emits('update:modelValue', value),
})
const focus = (el: HTMLTextAreaElement) => el?.focus()
const focus: VNodeRef = (el) => (el as HTMLTextAreaElement)?.focus()
</script>
<template>

11
packages/nc-gui-v2/components/cell/Url.vue

@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, inject, onMounted, ref } from '#imports'
import type { VNodeRef } from '@vue/runtime-core'
import { computed, inject, ref } from '#imports'
import { ColumnInj, EditModeInj } from '~/context'
import { isValidURL } from '~/utils'
@ -11,14 +12,14 @@ const { modelValue: value } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const column = inject(ColumnInj)
const column = inject(ColumnInj)!
const editEnabled = inject(EditModeInj, ref(false))
const vModel = computed({
get: () => value,
set: (val) => {
if (!column?.value?.meta?.validate || isValidURL(val)) {
if (!column.value.meta?.validate || (val && isValidURL(val))) {
emit('update:modelValue', val)
}
},
@ -26,12 +27,12 @@ const vModel = computed({
const isValid = computed(() => value && isValidURL(value))
const focus = (el: HTMLInputElement) => el?.focus()
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</script>
<template>
<input v-if="editEnabled" :ref="focus" v-model="vModel" class="outline-none" @blur="editEnabled = false" />
<nuxt-link v-else-if="isValid" class="py-2 underline hover:opacity-75" :to="value" target="_blank">{{ value }}</nuxt-link>
<nuxt-link v-else-if="isValid" class="py-2 underline hover:opacity-75" :to="value || ''" target="_blank">{{ value }}</nuxt-link>
<span v-else>{{ value }}</span>
</template>

5
packages/nc-gui-v2/components/cell/attachment/Modal.vue

@ -4,11 +4,6 @@ import { useAttachmentCell } from './utils'
import { useSortable } from './sort'
import { ref, useDropZone, useUIPermission } from '#imports'
import { isImage, openLink } from '~/utils'
import MaterialSymbolsAttachFile from '~icons/material-symbols/attach-file'
import MdiCloseCircle from '~icons/mdi/close-circle'
import MdiDownload from '~icons/mdi/download'
import MaterialSymbolsFileCopyOutline from '~icons/material-symbols/file-copy-outline'
import IcOutlineInsertDriveFile from '~icons/ic/outline-insert-drive-file'
const { isUIAllowed } = useUIPermission()

14
packages/nc-gui-v2/components/dashboard/settings/UIAcl.vue

@ -1,18 +1,20 @@
<script setup lang="ts">
import { useToast } from 'vue-toastification'
import { viewIcons } from '~/utils/viewUtils'
import { h, useNuxtApp, useProject } from '#imports'
import MdiReload from '~icons/mdi/reload'
import MdiContentSave from '~icons/mdi/content-save'
import MdiMagnify from '~icons/mdi/magnify'
import { viewIcons } from '~/utils'
import { computed, h, useNuxtApp, useProject } from '#imports'
const { $api, $e } = useNuxtApp()
const { project } = useProject()
const toast = useToast()
const roles = $ref<string[]>(['editor', 'commenter', 'viewer'])
let isLoading = $ref(false)
let tables = $ref<any[]>([])
const searchInput = $ref('')
const filteredTables = computed(() =>
@ -30,7 +32,7 @@ async function loadTableList() {
isLoading = true
// TODO includeM2M
tables = await $api.project.modelVisibilityList(project.value?.id, {
includeM2M: '',
includeM2M: true,
})
} catch (e) {
console.error(e)

11
packages/nc-gui-v2/components/general/Language.vue

@ -1,15 +1,13 @@
<script lang="ts" setup>
import { useI18n } from 'vue-i18n'
import MaterialSymbolsTranslate from '~icons/material-symbols/translate'
import { Language } from '~/lib/enums'
import { Language } from '~/lib'
import { useNuxtApp } from '#imports'
const { $e, $state } = useNuxtApp()
const { availableLocales = ['en'], locale } = useI18n()
const languages = $computed(() => {
return availableLocales.sort()
})
const languages = $computed(() => availableLocales.sort())
const isRtlLang = $computed(() => ['fa'].includes($state.lang.value))
@ -38,10 +36,11 @@ onMounted(() => {
<template #activator="{ props }">
<MaterialSymbolsTranslate class="md:text-xl cursor-pointer nc-menu-translate" @click="props.onClick" />
</template>
<v-list class="scrollbar min-w-50 max-h-90vh overflow-auto !py-0 dark:(!bg-gray-800 !text-white)">
<v-list-item
v-for="lang of languages"
:key="lang.value"
:key="lang"
:class="lang === locale ? '!bg-primary/10 text-primary dark:(!bg-gray-700 !text-secondary)' : ''"
class="!min-h-8 group"
:value="lang"

10
packages/nc-gui-v2/components/smartsheet-column/AdvancedOptions.vue

@ -1,9 +1,11 @@
<script setup lang="ts">
import { useColumnCreateStoreOrThrow } from '#imports'
import type { UITypes } from 'nocodb-sdk'
import { computed, useColumnCreateStoreOrThrow } from '#imports'
const { formState, validateInfos, setAdditionalValidations, sqlUi, onDataTypeChange, onAlter } = useColumnCreateStoreOrThrow()
const { formState, validateInfos, sqlUi, onDataTypeChange, onAlter } = useColumnCreateStoreOrThrow()!
const dataTypes = computed(() => sqlUi?.value?.getDataTypeListForUiType(formState))
// todo: 2nd argument of `getDataTypeListForUiType` is missing!
const dataTypes = computed(() => sqlUi?.value?.getDataTypeListForUiType(formState.value as { uidt: UITypes }, '' as any))
// to avoid type error with checkbox
formState.value.rqd = !!formState.value.rqd
@ -82,5 +84,3 @@ formState.value.au = !!formState.value.au
</a-form-item>
</div>
</template>
<style scoped></style>

2
packages/nc-gui-v2/components/smartsheet-column/CheckboxOptions.vue

@ -3,7 +3,7 @@ import { Sketch } from '@ckpack/vue-color'
import { useColumnCreateStoreOrThrow } from '#imports'
import { enumColor, getMdiIcon } from '@/utils'
const { formState, validateInfos, setAdditionalValidations, sqlUi, onDataTypeChange, onAlter } = useColumnCreateStoreOrThrow()
const { formState } = useColumnCreateStoreOrThrow()
// cater existing v1 cases
const iconList = [

38
packages/nc-gui-v2/components/smartsheet-column/FormulaOptions.vue

@ -4,7 +4,7 @@ import type { ListItem as AntListItem } from 'ant-design-vue'
import jsep from 'jsep'
import type { ColumnType } from 'nocodb-sdk'
import { UITypes, jsepCurlyHook } from 'nocodb-sdk'
import { useColumnCreateStoreOrThrow, useDebounceFn } from '#imports'
import { onMounted, useColumnCreateStoreOrThrow, useDebounceFn } from '#imports'
import { MetaInj } from '~/context'
import {
NcAutocompleteTree,
@ -16,8 +16,6 @@ import {
insertAtCursor,
validateDateWithUnknownFormat,
} from '@/utils'
import MdiFunctionIcon from '~icons/mdi/function'
import MdiOperatorIcon from '~icons/mdi/calculator'
enum JSEPNode {
COMPOUND = 'Compound',
@ -31,8 +29,7 @@ enum JSEPNode {
ARRAY_EXP = 'ArrayExpression',
}
const { formState, validateInfos, setAdditionalValidations, sqlUi, onDataTypeChange, onAlter, column } =
useColumnCreateStoreOrThrow()
const { formState, validateInfos, setAdditionalValidations, sqlUi, column } = useColumnCreateStoreOrThrow()
const meta = inject(MetaInj)
@ -72,8 +69,6 @@ const wordToComplete = ref<string | undefined>('')
const selected = ref(0)
const tooltip = ref(true)
const sortOrder: Record<string, number> = {
column: 0,
function: 1,
@ -95,7 +90,7 @@ const suggestionsList = computed(() => {
...columns.value
.filter(
(c: Record<string, any>) =>
!column || (column.id !== c.id && !(c.uidt === UITypes.LinkToAnotherRecord && c.system === 1)),
!column || (column.value.id !== c.id && !(c.uidt === UITypes.LinkToAnotherRecord && c.system === 1)),
)
.map((c: any) => ({
text: c.title,
@ -230,7 +225,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
} else if (parsedTree.type === JSEPNode.IDENTIFIER) {
if (
columns.value
.filter((c: Record<string, any>) => !column || column.id !== c.id)
.filter((c: Record<string, any>) => !column || column.value.id !== c.id)
.every((c: Record<string, any>) => c.title !== parsedTree.name)
) {
errors.add(`Column '${parsedTree.name}' is not available`)
@ -241,7 +236,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
// get all formula columns excluding itself
const formulaPaths = columns.value
.filter((c: Record<string, any>) => c.id !== column?.id && c.uidt === UITypes.Formula)
.filter((c: Record<string, any>) => c.id !== column?.value.id && c.uidt === UITypes.Formula)
.reduce((res: Record<string, any>[], c: Record<string, any>) => {
// in `formula`, get all the target neighbours
// i.e. all column id (e.g. cl_xxxxxxxxxxxxxx) with formula type
@ -256,9 +251,10 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
}, [])
// include target formula column (i.e. the one to be saved if applicable)
const targetFormulaCol = columns.value.find((c: ColumnType) => c.title === parsedTree.name && c.uidt === UITypes.Formula)
if (targetFormulaCol) {
if (targetFormulaCol && column?.value.id) {
formulaPaths.push({
[column.id]: [targetFormulaCol.id],
[column.value.id]: [targetFormulaCol.id],
})
}
const vertices = formulaPaths.length
@ -267,6 +263,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
const adj = new Map()
const inDegrees = new Map()
// init adjacency list & indegree
for (const [_, v] of Object.entries(formulaPaths)) {
const src = Object.keys(v)[0]
const neighbours = v[src]
@ -346,12 +343,14 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
}
}
} else if (parsedTree.type === JSEPNode.IDENTIFIER) {
const col = columns.value.find((c) => c.title === parsedTree.name) as Record<string, any>
const col = columns.value.find((c) => c.title === parsedTree.name)
if (col === undefined) {
return
}
if (col.uidt === UITypes.Formula) {
const foundType = getRootDataType(jsep(col?.formula_raw))
const foundType = getRootDataType(jsep((col as any).formula_raw))
if (foundType === 'N/A') {
typeErrors.add(`Not supported to reference column ${col.title}`)
} else if (expectedType !== foundType) {
@ -594,7 +593,7 @@ function getFormulaTypeName(type: string) {
}
// set default value
formState.value.formula_raw = (column?.colOptions as Record<string, any>)?.formula_raw || ''
formState.value.formula_raw = (column?.value?.colOptions as Record<string, any>)?.formula_raw || ''
// set additional validations
setAdditionalValidations({
@ -659,19 +658,24 @@ onMounted(() => {
<div>({{ idx + 1 }}): {{ example }}</div>
</div>
</template>
<template #title>
<div class="flex">
<div class="flex-1">
{{ item.text }}
</div>
<div class="">
{{ getFormulaTypeName(item.type) }}
</div>
</div>
</template>
<template #avatar>
<MdiFunctionIcon v-if="item.type === 'function'" class="text-lg" />
<MdiOperatorIcon v-if="item.type === 'op'" class="text-lg" />
<MdiFunction v-if="item.type === 'function'" class="text-lg" />
<MdiCalculator 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>

7
packages/nc-gui-v2/components/smartsheet-column/LinkedToAnotherRecordOptions.vue

@ -1,14 +1,13 @@
<script setup lang="ts">
import type { TableType } from 'nocodb-sdk'
import { ModelTypes, MssqlUi, SqliteUi } from 'nocodb-sdk'
import { useColumnCreateStoreOrThrow } from '#imports'
import { inject, useColumnCreateStoreOrThrow, useProject } from '#imports'
import { MetaInj } from '~/context'
import MdiPlusIcon from '~icons/mdi/plus-circle-outline'
import MdiMinusIcon from '~icons/mdi/minus-circle-outline'
const { formState, validateInfos, onDataTypeChange, setAdditionalValidations } = $(useColumnCreateStoreOrThrow())
const { tables, sqlUi } = $(useProject())
const meta: TableType = $(inject(MetaInj))
const meta = $(inject(MetaInj)!)
setAdditionalValidations({
childId: [{ required: true, message: 'Required' }],
@ -103,5 +102,3 @@ const refTables = $computed(() => {
</div>
</div>
</template>
<style scoped></style>

21
packages/nc-gui-v2/components/smartsheet-column/LookupOptions.vue

@ -5,7 +5,7 @@ import { MetaInj } from '~/context'
const { formState, validateInfos, onDataTypeChange, setAdditionalValidations } = $(useColumnCreateStoreOrThrow())
const { tables } = $(useProject())
const meta = $(inject(MetaInj))
const meta = $(inject(MetaInj)!)
const { metas } = $(useMetas())
setAdditionalValidations({
@ -27,14 +27,17 @@ const refTables = $computed(() => {
return []
}
return meta.columns
.filter((c) => c.uidt === UITypes.LinkToAnotherRecord && c.colOptions.type !== 'bt' && !c.system)
// todo: type issues with ColumnType so we have to cast to any
return (
meta.columns
?.filter((c: any) => c.uidt === UITypes.LinkToAnotherRecord && c.colOptions?.type !== 'bt' && !c.system)
.map((c) => ({
col: c.colOptions,
column: c,
...tables.find((t) => t.id === c.colOptions.fk_related_model_id),
...tables.find((t) => t.id === (c.colOptions as any)?.fk_related_model_id),
}))
.filter((table) => table.col.fk_related_model_id === table.id && !table.mm)
.filter((table: any) => table.col?.fk_related_model_id === table.id && !table.mm) ?? []
)
})
const columns = $computed(() => {
@ -43,7 +46,7 @@ const columns = $computed(() => {
return []
}
return metas[selectedTable.id].columns.filter((c) => !isSystemColumn(c))
return metas[selectedTable.id].columns.filter((c: any) => !isSystemColumn(c))
})
</script>
@ -52,14 +55,16 @@ const columns = $computed(() => {
<div class="w-full flex flex-row space-x-2">
<a-form-item class="flex w-1/2 pb-2" :label="$t('labels.childTable')" v-bind="validateInfos.fk_relation_column_id">
<a-select v-model:value="formState.fk_relation_column_id" size="small" @change="onDataTypeChange">
<a-select-option v-for="(table, index) in refTables" :key="index" :value="table.col.fk_column_id">
<a-select-option v-for="(table, index) of refTables" :key="index" :value="table.col.fk_column_id">
<div class="flex flex-row items-center space-x-0.5 h-full">
<div class="font-weight-bold text-[0.7rem]">{{ table.column.title }}</div>
<div class="text-[0.5rem]">({{ relationNames[table.col.type] }} {{ table.title || table.table_name }})</div>
</div>
</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="formState.fk_lookup_column_id"
@ -67,7 +72,7 @@ const columns = $computed(() => {
size="small"
@change="onDataTypeChange"
>
<a-select-option v-for="(column, index) in columns" :key="index" :value="column.id">
<a-select-option v-for="(column, index) of columns" :key="index" :value="column.id">
{{ column.title }}
</a-select-option>
</a-select>

28
packages/nc-gui-v2/components/smartsheet-column/RollupOptions.vue

@ -1,11 +1,14 @@
<script setup lang="ts">
import { UITypes, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import { useColumnCreateStoreOrThrow } from '#imports'
import { inject, useColumnCreateStoreOrThrow, useMetas, useProject } from '#imports'
import { MetaInj } from '~/context'
const { formState, validateInfos, onDataTypeChange, setAdditionalValidations } = $(useColumnCreateStoreOrThrow())
const { tables } = $(useProject())
const meta = $(inject(MetaInj))
const meta = $(inject(MetaInj)!)
const { metas } = $(useMetas())
setAdditionalValidations({
@ -40,22 +43,25 @@ const refTables = $computed(() => {
return []
}
return meta.columns
.filter((c) => c.uidt === UITypes.LinkToAnotherRecord && c.colOptions.type !== 'bt' && !c.system)
return (
meta.columns
?.filter((c: any) => c.uidt === UITypes.LinkToAnotherRecord && c.colOptions.type !== 'bt' && !c.system)
.map((c) => ({
col: c.colOptions,
column: c,
...tables.find((t) => t.id === c.colOptions.fk_related_model_id),
}))
...tables.find((t) => t.id === (c.colOptions as any)?.fk_related_model_id),
})) ?? []
)
})
const columns = $computed(() => {
const selectedTable = refTables.find((t) => t.column.id === formState.fk_relation_column_id)
if (!selectedTable?.id) {
return []
}
return metas[selectedTable.id].columns.filter((c) => !isVirtualCol(c.uidt) && !isSystemColumn(c))
return metas[selectedTable.id].columns.filter((c: any) => !isVirtualCol(c.uidt) && !isSystemColumn(c))
})
</script>
@ -64,7 +70,7 @@ const columns = $computed(() => {
<div class="w-full flex flex-row space-x-2">
<a-form-item class="flex w-1/2 pb-2" :label="$t('labels.childTable')" v-bind="validateInfos.fk_relation_column_id">
<a-select v-model:value="formState.fk_relation_column_id" size="small" @change="onDataTypeChange">
<a-select-option v-for="(table, index) in refTables" :key="index" :value="table.col.fk_column_id">
<a-select-option v-for="(table, index) of refTables" :key="index" :value="table.col.fk_column_id">
<div class="flex flex-row items-center space-x-0.5">
<div class="font-weight-bold text-xs">{{ table.column.title }}</div>
<div class="text-[0.45rem]">({{ relationNames[table.col.type] }} {{ table.title || table.table_name }})</div>
@ -79,7 +85,7 @@ const columns = $computed(() => {
size="small"
@change="onDataTypeChange"
>
<a-select-option v-for="(column, index) in columns" :key="index" :value="column.id">
<a-select-option v-for="(column, index) of columns" :key="index" :value="column.id">
{{ column.title }}
</a-select-option>
</a-select>
@ -87,12 +93,10 @@ const columns = $computed(() => {
</div>
<a-form-item label="Aggregate function" v-bind="validateInfos.rollup_function">
<a-select v-model:value="formState.rollup_function" size="small" @change="onDataTypeChange">
<a-select-option v-for="(func, index) in aggrFunctionsList" :key="index" :value="func.value">
<a-select-option v-for="(func, index) of aggrFunctionsList" :key="index" :value="func.value">
{{ func.text }}
</a-select-option>
</a-select>
</a-form-item>
</div>
</template>
<style scoped></style>

2
packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue

@ -289,6 +289,6 @@ defineExpose({
}
:deep(.ant-select-item-option) {
@apply "!min-w-min";
@apply "!min-w-full";
}
</style>

54
packages/nc-gui-v2/components/smartsheet-toolbar/FieldsMenu.vue

@ -1,21 +1,11 @@
<script setup lang="ts">
import { computed, inject } from 'vue'
import Draggable from 'vuedraggable'
import { ActiveViewInj, FieldsInj, IsLockedInj, MetaInj, ReloadViewDataHookInj } from '~/context'
import { useViewColumns } from '#imports'
import MdiMenuDownIcon from '~icons/mdi/menu-down'
import MdiEyeIcon from '~icons/mdi/eye-off-outline'
import MdiDragIcon from '~icons/mdi/drag'
const { fieldsOrder, coverImageField, modelValue } = defineProps<{
coverImageField?: string
fieldsOrder?: string[]
modelValue?: Record<string, boolean>
}>()
const meta = inject(MetaInj)
const activeView = inject(ActiveViewInj)
const reloadDataHook = inject(ReloadViewDataHookInj)
import { computed, inject, useNuxtApp, useViewColumns, watch } from '#imports'
const meta = inject(MetaInj)!
const activeView = inject(ActiveViewInj)!
const reloadDataHook = inject(ReloadViewDataHookInj)!
const rootFields = inject(FieldsInj)
const isLocked = inject(IsLockedInj)
@ -31,46 +21,45 @@ const {
showAll,
hideAll,
saveOrUpdate,
// sortedFields,
} = useViewColumns(activeView, meta, false, () => reloadDataHook?.trigger())
} = useViewColumns(activeView, meta, false, () => reloadDataHook.trigger())
watch(
() => (activeView?.value as any)?.id,
() => (activeView.value as any)?.id,
async (newVal, oldVal) => {
if (newVal !== oldVal && meta?.value) {
if (newVal !== oldVal && meta.value) {
await loadViewColumns()
}
},
{ immediate: true },
)
watch(
() => sortedAndFilteredFields.value,
sortedAndFilteredFields,
(v) => {
if (rootFields) rootFields.value = v || []
},
{ immediate: true },
)
const isAnyFieldHidden = computed(() => {
return fields?.value?.some((f) => !(!showSystemFields && f.system) && !f.show)
})
const isAnyFieldHidden = computed(() => fields.value?.some((field) => !(!showSystemFields && field.system) && !field.show))
const onMove = (event: { moved: { newIndex: number } }) => {
// todo : sync with server
if (!fields?.value) return
if (!fields.value) return
if (fields.value.length < 2) return
if (fields?.value.length - 1 === event.moved.newIndex) {
if (fields.value.length - 1 === event.moved.newIndex) {
fields.value[event.moved.newIndex].order = (fields.value[event.moved.newIndex - 1].order || 1) + 1
} else if (event.moved.newIndex === 0) {
fields.value[event.moved.newIndex].order = (fields?.value[1].order || 1) / 2
fields.value[event.moved.newIndex].order = (fields.value[1].order || 1) / 2
} else {
fields.value[event.moved.newIndex].order =
((fields?.value[event.moved.newIndex - 1].order || 1) + (fields?.value[event.moved.newIndex + 1].order || 1)) / 2
// );
((fields.value[event.moved.newIndex - 1].order || 1) + (fields.value[event.moved.newIndex + 1].order || 1)) / 2
}
saveOrUpdate(fields.value[event.moved.newIndex], event.moved.newIndex)
$e('a:fields:reorder')
}
</script>
@ -80,11 +69,12 @@ const onMove = (event: { moved: { newIndex: number } }) => {
<div :class="{ 'nc-badge nc-active-btn': isAnyFieldHidden }">
<a-button v-t="['c:fields']" class="nc-fields-menu-btn nc-toolbar-btn" :disabled="isLocked" size="small">
<div class="flex align-center gap-1">
<!-- <v-icon small class="mr-1" color="#777"> mdi-eye-off-outline </v-icon> -->
<MdiEyeIcon class="text-grey"></MdiEyeIcon>
<MdiEyeOffOutline class="text-grey" />
<!-- Fields -->
<span class="text-xs text-capitalize">{{ $t('objects.fields') }}</span>
<MdiMenuDownIcon class="text-grey"></MdiMenuDownIcon>
<MdiMenuDown class="text-grey" />
</div>
</a-button>
</div>
@ -101,7 +91,7 @@ const onMove = (event: { moved: { newIndex: number } }) => {
<span class="text-xs">{{ field.title }}</span>
</a-checkbox>
<div class="flex-1" />
<MdiDragIcon class="cursor-move" />
<MdiDrag class="cursor-move" />
</div>
</template>
</Draggable>

23
packages/nc-gui-v2/components/smartsheet-toolbar/SearchData.vue

@ -1,14 +1,16 @@
<script lang="ts" setup>
import { useProvideSmartsheetStore, useSmartsheetStoreOrThrow } from '~/composables/useSmartsheetStore'
import { MetaInj, ReloadViewDataHookInj } from '~/context'
import MdiSearchIcon from '~icons/mdi/magnify'
import MdiMenuDownIcon from '~icons/mdi/menu-down'
import { computed, inject, ref, useSmartsheetStoreOrThrow } from '#imports'
import { ReloadViewDataHookInj } from '~/context'
const reloadData = inject(ReloadViewDataHookInj)!
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 columns = computed(() =>
meta?.value?.columns?.map((c) => ({
meta.value?.columns?.map((c) => ({
value: c.id,
label: c.title,
})),
@ -21,12 +23,13 @@ const columns = computed(() =>
size="small"
class="max-w-[200px]"
placeholder="Filter query"
@press-enter="reloadData.trigger()"
@press-enter="reloadData.trigger(null)"
>
<template #addonBefore>
<div class="flex align-center relative" @click="isDropdownOpen = true">
<MdiSearchIcon class="text-grey" />
<MdiMenuDownIcon class="text-grey" />
<MdiMagnify class="text-grey" />
<MdiMenuDown class="text-grey" />
<a-select
v-model:value="search.field"
size="small"
@ -39,5 +42,3 @@ const columns = computed(() =>
</template>
</a-input>
</template>
<style scoped></style>

11
packages/nc-gui-v2/components/smartsheet/Grid.vue

@ -25,8 +25,6 @@ import {
ReloadViewDataHookInj,
} from '~/context'
import { NavigateDir } from '~/lib'
import MdiArrowExpandIcon from '~icons/mdi/arrow-expand'
import MdiPlusIcon from '~icons/mdi/plus'
const meta = inject(MetaInj)
const view = inject(ActiveViewInj)
@ -40,7 +38,6 @@ const isView = false
const selected = reactive<{ row: number | null; col: number | null }>({ row: null, col: null })
let editEnabled = $ref(false)
const { sqlUi } = useProject()
const { xWhere, isPkAvail } = useSmartsheetStoreOrThrow()
const addColumnDropdown = ref(false)
const contextMenu = ref(false)
@ -288,7 +285,7 @@ const onNavigate = (dir: NavigateDir) => {
<th v-t="['c:column:add']" @click="addColumnDropdown = true">
<a-dropdown v-model:visible="addColumnDropdown" :trigger="['click']">
<div class="h-full w-[60px] flex align-center justify-center">
<MdiPlusIcon class="text-sm" />
<MdiPlus class="text-sm" />
</div>
<template #overlay>
<SmartsheetColumnEditOrAdd @click.stop @cancel="addColumnDropdown = false" />
@ -308,7 +305,7 @@ const onNavigate = (dir: NavigateDir) => {
>
<a-checkbox v-model:checked="row.rowMeta.selected" />
<span class="flex-1" />
<MdiArrowExpandIcon class="text-sm text-pink hidden group-hover:inline-block" />
<MdiArrowExpand class="text-sm text-pink hidden group-hover:inline-block" />
</div>
</div>
</td>
@ -357,8 +354,8 @@ const onNavigate = (dir: NavigateDir) => {
@click="addEmptyRow()"
>
<a-tooltip top left>
<div class="w-min flex align-center">
<MdiPlusIcon class="text-pint-500 text-xs" />
<div class="w-full flex align-center">
<MdiPlus class="text-pint-500 text-xs" />
<span class="ml-1 caption grey--text">
{{ $t('activity.addRow') }}
</span>

8
packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue

@ -6,9 +6,9 @@ import { notification } from 'ant-design-vue'
import type { Ref } from 'vue'
import Sortable from 'sortablejs'
import RenameableMenuItem from './RenameableMenuItem.vue'
import { inject, onMounted, ref, useApi, useTabs, watch } from '#imports'
import { inject, onMounted, ref, useApi, useRouter, watch } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils'
import { ActiveViewInj, MetaInj, ViewListInj } from '~/context'
import { ActiveViewInj, ViewListInj } from '~/context'
interface Emits {
(event: 'openModal', data: { type: ViewTypes; title?: string }): void
@ -22,10 +22,6 @@ const activeView = inject(ActiveViewInj, ref())
const views = inject<Ref<any[]>>(ViewListInj, ref([]))
const meta = inject(MetaInj)
const { addTab } = useTabs()
const { api } = useApi()
const router = useRouter()

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

@ -1,9 +1,8 @@
<script setup lang="ts">
import { inject, ref, useTable } from '#imports'
import { MetaInj, RightSidebarInj } from '~/context'
import MdiDeleteIcon from '~icons/mdi/delete-outline'
const meta = inject(MetaInj)
const meta = inject(MetaInj)!
const { deleteTable } = useTable()
@ -15,7 +14,7 @@ const sidebarOpen = inject(RightSidebarInj, ref(true))
<template #title> {{ $t('activity.deleteTable') }} </template>
<div class="nc-sidebar-right-item hover:after:bg-red-500 group">
<MdiDeleteIcon class="group-hover:(!text-white)" @click="deleteTable(meta)" />
<MdiDeleteOutline class="group-hover:(!text-white)" @click="deleteTable(meta)" />
</div>
</a-tooltip>
</template>

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

@ -1,8 +1,7 @@
<script lang="ts" setup>
import { computed } from '@vue/reactivity'
import { useToast } from 'vue-toastification'
import { useSmartsheetStoreOrThrow } from '~/composables/useSmartsheetStore'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { computed, useSmartsheetStoreOrThrow } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils'
import MdiLockOutlineIcon from '~icons/mdi/lock-outline'
import MdiAccountIcon from '~icons/mdi/account'
import MdiAccountGroupIcon from '~icons/mdi/account-group'
@ -25,19 +24,19 @@ function changeLockType(type: LockType) {
return toast.info('Coming soon', { timeout: 3000 })
}
try {
view.value.lock_type = type
;(view.value as any).lock_type = type
$api.dbView.update(view.value.id as string, {
lock_type: type,
})
toast.success(`Successfully Switched to ${type} view`, { timeout: 3000 })
} catch (e) {
} catch (e: any) {
toast.error(extractSdkResponseErrorMsg(e))
}
}
const Icon = computed(() => {
switch (view?.value?.lock_type) {
switch ((view.value as any)?.lock_type) {
case LockType.Personal:
return MdiAccountIcon
case LockType.Locked:

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

@ -1,11 +1,12 @@
<script setup lang="ts">
import { ReloadViewDataHookInj, RightSidebarInj } from '~/context'
import MdiReloadIcon from '~icons/mdi/reload'
import { inject, ref } from '#imports'
const reloadTri = inject(ReloadViewDataHookInj)
const reloadHook = inject(ReloadViewDataHookInj)!
const sidebarOpen = inject(RightSidebarInj, ref(true))
const onClick = () => reloadHook.trigger()
</script>
<template>
@ -13,9 +14,7 @@ const sidebarOpen = inject(RightSidebarInj, ref(true))
<template #title> {{ $t('general.reload') }} </template>
<div class="nc-sidebar-right-item hover:after:bg-green-500 group">
<MdiReloadIcon class="group-hover:(!text-white)" @click="reloadTri.trigger()" />
<MdiReload class="group-hover:(!text-white)" @click="onClick" />
</div>
</a-tooltip>
</template>
<style scoped></style>

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

@ -1,13 +1,8 @@
<script setup lang="ts">
import { useToast } from 'vue-toastification'
import { useClipboard } from '@vueuse/core'
import OpenInNewIcon from '~icons/mdi/open-in-new'
import { dashboardUrl } from '~/utils/urlUtils'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import MdiReload from '~icons/mdi/reload'
import DownIcon from '~icons/ic/round-keyboard-arrow-down'
import ContentCopyIcon from '~icons/mdi/content-copy'
import MdiXmlIcon from '~icons/mdi/xml'
import { onMounted, useClipboard, useNuxtApp, useProject } from '#imports'
import { dashboardUrl, extractSdkResponseErrorMsg } from '~/utils'
const toast = useToast()
interface ShareBase {
@ -22,9 +17,13 @@ enum ShareBaseRole {
}
const { $api, $e } = useNuxtApp()
let base = $ref<null | ShareBase>(null)
const showEditBaseDropdown = $ref(false)
const { project } = useProject()
const { copy } = useClipboard()
const url = $computed(() => (base && base.uuid ? `${dashboardUrl()}#/nc/base/${base.uuid}` : null))
@ -33,7 +32,8 @@ const loadBase = async () => {
try {
if (!project.value.id) return
const res = await $api.project.sharedBaseGet(project.value.id)
// todo: result is missing roles in return-type
const res: any = await $api.project.sharedBaseGet(project.value.id)
base = {
uuid: res.uuid,
url: res.url,
@ -41,6 +41,7 @@ const loadBase = async () => {
}
} catch (e: any) {
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
}
}
@ -49,16 +50,19 @@ const createShareBase = async (role = ShareBaseRole.Viewer) => {
try {
if (!project.value.id) return
const res = await $api.project.sharedBaseUpdate(project.value.id, {
// todo: returns void?
const res: any = await $api.project.sharedBaseUpdate(project.value.id, {
roles: role,
})
base = res || {}
base.role = role
base = res ?? {}
base!.role = role
} catch (e: any) {
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
}
$e('a:shared-base:enable', { role })
}
@ -83,10 +87,13 @@ const recreate = async () => {
const sharedBase = await $api.project.sharedBaseCreate(project.value.id, {
roles: base?.role || ShareBaseRole.Viewer,
})
const newBase = sharedBase || {}
base = { ...newBase, role: base?.role }
} catch (e: any) {
console.error(e)
toast.error(await extractSdkResponseErrorMsg(e))
}
@ -96,7 +103,8 @@ const recreate = async () => {
const copyUrl = async () => {
if (!url) return
copy(url)
await copy(url)
toast.success('Copied shareable base url to clipboard!')
$e('c:shared-base:copy-url')
@ -135,7 +143,8 @@ onMounted(() => {
<template>
<div class="flex flex-col w-full">
<div class="flex flex-row items-center space-x-0.5 pl-2 h-[0.8rem]">
<OpenInNewIcon />
<MdiOpenInNew />
<div class="text-xs">Shared Base Link</div>
</div>
<div v-if="base?.uuid" class="flex flex-row mt-2 bg-red-50 py-4 mx-1 px-2 items-center rounded-sm w-full justify-between">
@ -157,7 +166,7 @@ onMounted(() => {
</template>
<a-button type="text" class="!rounded-md mr-1 -mt-1.5 h-[1rem]" @click="copyUrl">
<template #icon>
<ContentCopyIcon class="flex mx-auto text-gray-600" />
<MdiContentCopy class="flex mx-auto text-gray-600" />
</template>
</a-button>
</a-tooltip>
@ -167,7 +176,7 @@ onMounted(() => {
</template>
<a-button type="text" class="!rounded-md mr-1 -mt-1.5 h-[1rem]" @click="navigateToSharedBase">
<template #icon>
<OpenInNewIcon class="flex mx-auto text-gray-600" />
<MdiOpenInNew class="flex mx-auto text-gray-600" />
</template>
</a-button>
</a-tooltip>
@ -177,7 +186,7 @@ onMounted(() => {
</template>
<a-button type="text" class="!rounded-md mr-1 -mt-1.5 h-[1rem]" @click="generateEmbeddableIframe">
<template #icon>
<MdiXmlIcon class="flex mx-auto text-gray-600" />
<MdiXml class="flex mx-auto text-gray-600" />
</template>
</a-button>
</a-tooltip>
@ -190,7 +199,7 @@ onMounted(() => {
<div class="flex flex-row items-center space-x-2">
<div v-if="base?.uuid">Anyone with the link</div>
<div v-else>Disable shared base</div>
<DownIcon class="h-[1rem]" />
<IcRoundKeyboardArrowDown class="h-[1rem]" />
</div>
</a-button>
@ -207,7 +216,7 @@ onMounted(() => {
<a-select v-if="base?.uuid" v-model:value="base.role" class="flex">
<template #suffixIcon>
<div class="flex flex-row">
<DownIcon class="text-black -mt-0.5 h-[1rem]" />
<IcRoundKeyboardArrowDown class="text-black -mt-0.5 h-[1rem]" />
</div>
</template>
<a-select-option
@ -225,5 +234,3 @@ onMounted(() => {
</div>
</div>
</template>
<style scoped></style>

25
packages/nc-gui-v2/components/virtual-cell/BelongsTo.vue

@ -3,39 +3,44 @@ import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import ItemChip from './components/ItemChip.vue'
import ListItems from './components/ListItems.vue'
import { useProvideLTARStore } from '#imports'
import { inject, ref, useProvideLTARStore } from '#imports'
import { CellValueInj, ColumnInj, ReloadViewDataHookInj, RowInj } from '~/context'
import MdiExpandIcon from '~icons/mdi/arrow-expand'
const column = inject(ColumnInj)
const reloadTrigger = inject(ReloadViewDataHookInj)
const cellValue = inject(CellValueInj)
const reloadTrigger = inject(ReloadViewDataHookInj)!
const cellValue = inject(CellValueInj, ref<any>(null))
const row = inject(RowInj)
const active = false
const localState = null
const listItemsDlg = ref(false)
const { relatedTableMeta, loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore(
const { loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore(
column as Ref<Required<ColumnType>>,
row,
() => reloadTrigger?.trigger(),
reloadTrigger.trigger,
)
await loadRelatedTableMeta()
</script>
<template>
<div class="flex w-full chips-wrapper align-center" :class="{ active }">
<div class="chips d-flex align-center flex-grow">
<template v-if="cellValue || localState">
<ItemChip :item="cellValue" :value="cellValue[relatedTablePrimaryValueProp]" @unlink="unlink(cellValue || localState)" />
<template v-if="cellValue">
<ItemChip :item="cellValue" :value="cellValue[relatedTablePrimaryValueProp]" @unlink="unlink(cellValue)" />
</template>
</div>
<div class="flex-1 flex justify-end gap-1 min-h-[30px] align-center">
<MdiExpandIcon
<MdiArrowExpand
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 select-none group-hover:(text-gray-500)"
@click="listItemsDlg = true"
/>
</div>
<ListItems v-model="listItemsDlg" />
</div>
</template>

12
packages/nc-gui-v2/components/virtual-cell/Formula.vue

@ -1,10 +1,10 @@
<script lang="ts" setup>
import { computed, useProject } from '#imports'
import { computed, inject, ref, useProject } from '#imports'
import { CellValueInj, ColumnInj } from '~/context'
import { handleTZ } from '~/utils/dateTimeUtils'
import { replaceUrlsWithLink } from '~/utils/urlUtils'
import { handleTZ, replaceUrlsWithLink } from '~/utils'
const column = inject(ColumnInj)
// todo: column type doesn't have required property `error` - throws in typecheck
const column: any = inject(ColumnInj)
const value = inject(CellValueInj)
@ -14,6 +14,7 @@ const showEditFormulaWarning = ref(false)
const showEditFormulaWarningMessage = () => {
showEditFormulaWarning.value = true
setTimeout(() => {
showEditFormulaWarning.value = false
}, 3000)
@ -32,6 +33,7 @@ const urls = computed(() => replaceUrlsWithLink(result.value))
</template>
<span>ERR!</span>
</a-tooltip>
<div class="pa-2" @dblclick="showEditFormulaWarningMessage">
<div v-if="urls" v-html="urls" />
<div v-else>{{ result }}</div>
@ -42,5 +44,3 @@ const urls = computed(() => replaceUrlsWithLink(result.value))
</div>
</div>
</template>
<style scoped></style>

40
packages/nc-gui-v2/components/virtual-cell/HasMany.vue

@ -4,41 +4,57 @@ import type { Ref } from 'vue'
import ItemChip from './components/ItemChip.vue'
import ListChildItems from './components/ListChildItems.vue'
import ListItems from './components/ListItems.vue'
import { useProvideLTARStore } from '#imports'
import { computed, inject, ref, useProvideLTARStore } from '#imports'
import { CellValueInj, ColumnInj, ReloadViewDataHookInj, RowInj } from '~/context'
import MdiExpandIcon from '~icons/mdi/arrow-expand'
import MdiPlusIcon from '~icons/mdi/plus'
const column = inject(ColumnInj)
const cellValue = inject(CellValueInj)
const row = inject(RowInj)
const reloadTrigger = inject(ReloadViewDataHookInj)
const column = inject(ColumnInj)!
const cellValue = inject(CellValueInj)!
const row = inject(RowInj)!
const reloadTrigger = inject(ReloadViewDataHookInj)!
const listItemsDlg = ref(false)
const childListDlg = ref(false)
const { relatedTableMeta, loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore(
const { loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore(
column as Ref<Required<ColumnType>>,
row,
() => reloadTrigger?.trigger(),
reloadTrigger.trigger,
)
await loadRelatedTableMeta()
const cells = computed(() =>
cellValue.value.reduce((acc: any[], curr: any) => {
if (!relatedTablePrimaryValueProp.value) return acc
const value = curr[relatedTablePrimaryValueProp.value]
if (!value) return acc
return [...acc, { value, item: curr }]
}, [] as any[]),
)
</script>
<template>
<div class="flex align-center items-center gap-1 w-full chips-wrapper">
<div class="chips flex align-center img-container flex-grow hm-items flex-nowrap min-w-0 overflow-hidden">
<template v-if="cellValue">
<ItemChip v-for="(ch, i) in cellValue" :key="i" :value="ch[relatedTablePrimaryValueProp]" @unlink="unlink(ch)" />
<ItemChip v-for="(cell, i) of cells" :key="i" :value="cell.value" @unlink="unlink(cell.item)" />
<span v-if="cellValue?.length === 10" class="caption pointer ml-1 grey--text" @click="childListDlg = true">more... </span>
</template>
</div>
<div class="flex-grow flex justify-end gap-1 min-h-[30px] align-center">
<MdiExpandIcon
<MdiArrowExpand
class="select-none transform text-sm nc-action-icon text-gray-500/50 hover:text-gray-500"
@click="childListDlg = true"
/>
<MdiPlusIcon class="select-none text-sm nc-action-icon text-gray-500/50 hover:text-gray-500" @click="listItemsDlg = true" />
<MdiPlus class="select-none text-sm nc-action-icon text-gray-500/50 hover:text-gray-500" @click="listItemsDlg = true" />
</div>
<ListItems v-model="listItemsDlg" />
<ListChildItems v-model="childListDlg" @attach-record=";(childListDlg = false), (listItemsDlg = true)" />

23
packages/nc-gui-v2/components/virtual-cell/Lookup.vue

@ -1,26 +1,35 @@
<script lang="ts" setup>
import type { ColumnType, LinkToAnotherRecordType, LookupType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk'
import { useColumn } from '~/composables'
import type { Ref } from 'vue'
import { CellValueInj, ColumnInj, MetaInj, ReadonlyInj } from '~/context'
import { computed, inject, provide, useColumn, useMetas } from '#imports'
const { metas, getMeta } = useMetas()
provide(ReadonlyInj, true)
const column = inject(ColumnInj) as ColumnType & { colOptions: LookupType }
const column = inject(ColumnInj)! as Ref<ColumnType & { colOptions: LookupType }>
const meta = inject(MetaInj)
const value = inject(CellValueInj)
const arrValue = computed(() => (Array.isArray(value?.value) ? value?.value : [value?.value]))
const relationColumn = meta?.value.columns?.find((c) => c.id === column.colOptions.fk_relation_column_id) as ColumnType & {
const relationColumn = meta?.value.columns?.find((c) => c.id === column.value.colOptions?.fk_relation_column_id) as ColumnType & {
colOptions: LinkToAnotherRecordType
}
await getMeta(relationColumn.colOptions.fk_related_model_id as string)
const lookupTableMeta = metas?.value[relationColumn.colOptions.fk_related_model_id as string]
const lookupColumn = lookupTableMeta?.columns?.find((c) => c.id === column.colOptions.fk_lookup_column_id) as ColumnType
provide(MetaInj, ref(lookupTableMeta))
await getMeta(relationColumn.colOptions.fk_related_model_id!)
const lookupTableMeta = computed(() => metas.value[relationColumn.colOptions.fk_related_model_id!])
const lookupColumn = computed(
() =>
lookupTableMeta.value.columns?.find(
(c: Record<string, any>) => c.id === column.value.colOptions?.fk_lookup_column_id,
) as ColumnType,
)
provide(MetaInj, lookupTableMeta)
const lookupColumnMetaProps = useColumn(lookupColumn)
</script>

43
packages/nc-gui-v2/components/virtual-cell/ManyToMany.vue

@ -4,43 +4,60 @@ import type { Ref } from 'vue'
import ItemChip from './components/ItemChip.vue'
import ListChildItems from './components/ListChildItems.vue'
import ListItems from './components/ListItems.vue'
import { useProvideLTARStore } from '#imports'
import { computed, inject, ref, useProvideLTARStore } from '#imports'
import { CellValueInj, ColumnInj, ReloadViewDataHookInj, RowInj } from '~/context'
import MdiExpandIcon from '~icons/mdi/arrow-expand'
import MdiPlusIcon from '~icons/mdi/plus'
const column = inject(ColumnInj)
const row = inject(RowInj)
const cellValue = inject(CellValueInj)
const reloadTrigger = inject(ReloadViewDataHookInj)
const column = inject(ColumnInj)!
const row = inject(RowInj)!
const cellValue = inject(CellValueInj)!
const reloadTrigger = inject(ReloadViewDataHookInj)!
const listItemsDlg = ref(false)
const childListDlg = ref(false)
const { relatedTableMeta, loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore(
const { loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore(
column as Ref<Required<ColumnType>>,
row,
() => reloadTrigger?.trigger(),
reloadTrigger.trigger,
)
await loadRelatedTableMeta()
const cells = computed(() =>
cellValue.value.reduce((acc: any[], curr: any) => {
if (!relatedTablePrimaryValueProp.value) return acc
const value = curr[relatedTablePrimaryValueProp.value]
if (!value) return acc
return [...acc, { value, item: curr }]
}, [] as any[]),
)
</script>
<template>
<div class="flex align-center gap-1 w-full h-full chips-wrapper">
<!-- <template v-if="!isForm"> -->
<div class="chips flex align-center img-container flex-grow hm-items flex-nowrap min-w-0 overflow-hidden">
<template v-if="cellValue">
<ItemChip v-for="(ch, i) in cellValue" :key="i" :value="ch[relatedTablePrimaryValueProp]" @unlink="unlink(ch)" />
<ItemChip v-for="(cell, i) of cells" :key="i" :value="cell.value" @unlink="unlink(cell.item)" />
<span v-if="cellValue?.length === 10" class="caption pointer ml-1 grey--text" @click="childListDlg = true">more... </span>
</template>
</div>
<div class="flex-1 flex justify-end gap-1 min-h-[30px] align-center">
<MdiExpandIcon class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500" @click="childListDlg = true" />
<MdiPlusIcon class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500" @click="listItemsDlg = true" />
<MdiArrowExpand class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500" @click="childListDlg = true" />
<MdiPlus class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500" @click="listItemsDlg = true" />
</div>
<ListItems v-model="listItemsDlg" />
<ListChildItems v-model="childListDlg" @attach-record=";(childListDlg = false), (listItemsDlg = true)" />
</div>
</template>

5
packages/nc-gui-v2/components/virtual-cell/components/ItemChip.vue

@ -4,13 +4,12 @@ import MdiCloseThickIcon from '~icons/mdi/close-thick'
interface Props {
value?: string | number | boolean
item?: any
}
const { value, item } = defineProps<Props>()
const { value } = defineProps<Props>()
const emit = defineEmits(['unlink'])
const readonly = inject(ReadonlyInj, false)
const active = inject(ActiveCellInj, false)
const active = inject(ActiveCellInj, ref(false))
</script>
<template>

23
packages/nc-gui-v2/components/virtual-cell/components/ListChildItems.vue

@ -1,10 +1,8 @@
<script lang="ts" setup>
import { useLTARStoreOrThrow, useVModel } from '#imports'
import MdiReloadIcon from '~icons/mdi/reload'
import MdiDeleteIcon from '~icons/mdi/delete-outline'
import MdiUnlinkIcon from '~icons/mdi/link-variant-remove'
import { useLTARStoreOrThrow, useVModel, watch } from '#imports'
const props = defineProps<{ modelValue?: boolean }>()
const emit = defineEmits(['update:modelValue', 'attachRecord'])
const vModel = useVModel(props, 'modelValue', emit)
@ -20,14 +18,15 @@ const {
getRelatedTableRowId,
} = useLTARStoreOrThrow()
watch(vModel, () => {
if (vModel.value) {
watch(vModel, (nextVal) => {
if (nextVal) {
loadChildrenList()
}
})
const unlinkRow = async (row: Record<string, any>) => {
await unlink(row)
await loadChildrenList()
}
</script>
@ -36,12 +35,14 @@ const unlinkRow = async (row: Record<string, any>) => {
<a-modal v-model:visible="vModel" :footer="null" title="Child list">
<div class="max-h-[max(calc(100vh_-_300px)_,500px)] flex flex-col">
<div class="flex mb-4 align-center gap-2">
<!-- <a-input v-model:value="childrenListPagination.query" class="max-w-[200px]" size="small"></a-input> -->
<div class="flex-1" />
<MdiReloadIcon class="cursor-pointer text-gray-500" @click="loadChildrenList" />
<MdiReload class="cursor-pointer text-gray-500" @click="loadChildrenList" />
<a-button type="primary" size="small" @click="emit('attachRecord')">
<div class="flex align-center gap-1">
<MdiUnlinkIcon class="text-xs text-white" @click="unlinkRow(row)" />
<!-- todo: row is not defined? @click="unlinkRow(row)" -->
<MdiLinkVariantRemove class="text-xs text-white" />
Link to '{{ meta.title }}'
</div>
</a-button>
@ -56,8 +57,8 @@ const unlinkRow = async (row: Record<string, any>) => {
</div>
<div class="flex-1"></div>
<div class="flex gap-2">
<MdiUnlinkIcon class="text-xs text-grey hover:(!text-red-500) cursor-pointer" @click="unlinkRow(row)" />
<MdiDeleteIcon class="text-xs text-grey hover:(!text-red-500) cursor-pointer" @click="deleteRelatedRow(row)" />
<MdiLinkVariantRemove class="text-xs text-grey hover:(!text-red-500) cursor-pointer" @click="unlinkRow(row)" />
<MdiDeleteOutline class="text-xs text-grey hover:(!text-red-500) cursor-pointer" @click="deleteRelatedRow(row)" />
</div>
</div>
</a-card>

13
packages/nc-gui-v2/components/virtual-cell/components/ListItems.vue

@ -1,9 +1,9 @@
<script lang="ts" setup>
import { useLTARStoreOrThrow, useVModel } from '#imports'
import MdiReloadIcon from '~icons/mdi/reload'
import { useLTARStoreOrThrow, useVModel, watch } from '#imports'
const props = defineProps<{ modelValue: boolean }>()
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits(['update:modelValue', 'addNewRecord'])
const vModel = useVModel(props, 'modelValue', emit)
@ -16,8 +16,8 @@ const {
getRelatedTableRowId,
} = useLTARStoreOrThrow()
watch(vModel, () => {
if (vModel.value) {
watch(vModel, (nextVal) => {
if (nextVal) {
loadChildrenExcludedList()
}
})
@ -25,7 +25,6 @@ watch(vModel, () => {
const linkRow = async (row: Record<string, any>) => {
await link(row)
vModel.value = false
// await loadChildrenExcludedList()
}
</script>
@ -40,7 +39,7 @@ const linkRow = async (row: Record<string, any>) => {
size="small"
></a-input>
<div class="flex-1" />
<MdiReloadIcon class="cursor-pointer text-gray-500" @click="loadChildrenExcludedList" />
<MdiReload class="cursor-pointer text-gray-500" @click="loadChildrenExcludedList" />
<a-button type="primary" size="small" @click="emit('addNewRecord')">Add new record</a-button>
</div>
<template v-if="childrenExcludedList?.pageInfo?.totalRows">

94
packages/nc-gui-v2/components/webhook/Editor.vue

@ -2,20 +2,8 @@
import { Form } from 'ant-design-vue'
import { useToast } from 'vue-toastification'
import { MetaInj } from '~/context'
import MdiContentSaveIcon from '~icons/mdi/content-save'
import MdiLinkIcon from '~icons/mdi/link'
import MdiEmailIcon from '~icons/mdi/email'
import MdiSlackIcon from '~icons/mdi/slack'
import MdiMicrosoftTeamsIcon from '~icons/mdi/microsoft-teams'
import MdiDiscordIcon from '~icons/mdi/discord'
import MdiChatIcon from '~icons/mdi/chat'
import MdiWhatsAppIcon from '~icons/mdi/whatsapp'
import MdiCellPhoneMessageIcon from '~icons/mdi/cellphone-message'
import MdiGestureDoubleTapIcon from '~icons/mdi/gesture-double-tap'
import MdiInformationIcon from '~icons/mdi/information'
import MdiArrowLeftBoldIcon from '~icons/mdi/arrow-left-bold'
import { fieldRequiredValidator } from '~/utils/validation'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { extractSdkResponseErrorMsg, fieldRequiredValidator } from '~/utils'
import { inject, reactive, useApi, useNuxtApp } from '#imports'
interface Option {
label: string
@ -24,7 +12,9 @@ interface Option {
const emit = defineEmits(['backToList', 'editOrAdd'])
const { $state, $api, $e } = useNuxtApp()
const { $e } = useNuxtApp()
const { api, isLoading: loading } = useApi()
const toast = useToast()
@ -32,12 +22,12 @@ const meta = inject(MetaInj)
const useForm = Form.useForm
const hook = reactive({
const hook = reactive<Record<string, any>>({
id: '',
title: '',
event: '',
operation: '',
eventOperation: undefined,
eventOperation: '',
notification: {
type: 'URL',
payload: {
@ -45,7 +35,7 @@ const hook = reactive({
body: '{{ json data }}',
headers: [{}],
parameters: [{}],
} as any,
},
},
condition: false,
})
@ -64,8 +54,6 @@ const discordChannels = ref<Record<string, any>[]>([])
const mattermostChannels = ref<Record<string, any>[]>([])
const loading = ref(false)
const filters = ref([])
const formInput = ref({
@ -218,7 +206,7 @@ const validators = computed(() => {
}),
}
})
const { resetFields, validate, validateInfos } = useForm(hook, validators)
const { validate, validateInfos } = useForm(hook, validators)
function onNotTypeChange() {
hook.notification.payload = {} as any
@ -258,7 +246,7 @@ function setHook(newHook: any) {
}
async function onEventChange() {
const { notification: { payload = {}, type = {} } = {}, ...rest } = hook
const { notification: { payload = {}, type = {} } = {} } = hook
Object.assign(hook, {
...hook,
@ -305,7 +293,7 @@ async function onEventChange() {
async function loadPluginList() {
try {
const plugins = (await $api.plugin.list()).list as any
const plugins = (await api.plugin.list()).list as any
apps.value = plugins.reduce((o: Record<string, any>[], p: Record<string, any>) => {
p.tags = p.tags ? p.tags.split(',') : []
p.parsedInput = p.input && JSON.parse(p.input)
@ -327,31 +315,30 @@ async function saveHooks() {
await validate()
} catch (_: any) {
toast.error('Invalid Form')
loading.value = false
return
}
try {
let res
if (hook.id) {
res = await $api.dbTableWebhook.update(hook.id, {
res = await api.dbTableWebhook.update(hook.id, {
...hook,
notification: {
...hook.notification,
payload: hook.notification.payload,
},
} as any)
})
} else {
res = await $api.dbTableWebhook.create(
meta?.value.id as string,
{
res = await api.dbTableWebhook.create(meta!.value.id!, {
...hook,
notification: {
...hook.notification,
payload: hook.notification.payload,
},
} as any,
)
} as any)
}
if (!hook.id && res) {
@ -371,6 +358,7 @@ async function saveHooks() {
} finally {
loading.value = false
}
$e('a:webhook:add', {
operation: hook.operation,
condition: hook.condition,
@ -389,7 +377,9 @@ defineExpose({
watch(
() => hook.eventOperation,
(v) => {
() => {
if (!hook.eventOperation) return
const [event, operation] = hook.eventOperation.split(' ')
hook.event = event
hook.operation = operation
@ -405,21 +395,21 @@ onMounted(() => {
<div class="mb-4">
<div class="float-left mt-2">
<div class="flex items-center">
<MdiArrowLeftBoldIcon class="mr-3 text-xl cursor-pointer" @click="emit('backToList')" />
<MdiArrowLeftBold class="mr-3 text-xl cursor-pointer" @click="emit('backToList')" />
<span class="inline text-xl font-bold">{{ meta.title }} : {{ hook.title || 'Webhooks' }} </span>
</div>
</div>
<div class="float-right mb-5">
<a-button class="mr-3" size="large" @click="testWebhook">
<div class="flex items-center">
<MdiGestureDoubleTapIcon class="mr-2" />
<MdiGestureDoubleTap class="mr-2" />
<!-- TODO: i18n -->
Test Webhook
</div>
</a-button>
<a-button type="primary" size="large" @click.prevent="saveHooks">
<div class="flex items-center">
<MdiContentSaveIcon class="mr-2" />
<MdiContentSave class="mr-2" />
<!-- Save -->
{{ $t('general.save') }}
</div>
@ -456,14 +446,22 @@ onMounted(() => {
>
<a-select-option v-for="(notificationOption, i) in notificationList" :key="i" :value="notificationOption.type">
<div class="flex items-center">
<MdiLinkIcon v-if="notificationOption.type === 'URL'" class="mr-2" />
<MdiEmailIcon v-if="notificationOption.type === 'Email'" class="mr-2" />
<MdiSlackIcon v-if="notificationOption.type === 'Slack'" class="mr-2" />
<MdiMicrosoftTeamsIcon v-if="notificationOption.type === 'Microsoft Teams'" class="mr-2" />
<MdiDiscordIcon v-if="notificationOption.type === 'Discord'" class="mr-2" />
<MdiChatIcon v-if="notificationOption.type === 'Mattermost'" class="mr-2" />
<MdiWhatsAppIcon v-if="notificationOption.type === 'Whatsapp Twilio'" class="mr-2" />
<MdiCellPhoneMessageIcon v-if="notificationOption.type === 'Twilio'" class="mr-2" />
<MdiLink v-if="notificationOption.type === 'URL'" class="mr-2" />
<MdiEmail v-if="notificationOption.type === 'Email'" class="mr-2" />
<MdiSlack v-if="notificationOption.type === 'Slack'" class="mr-2" />
<MdiMicrosoftTeams v-if="notificationOption.type === 'Microsoft Teams'" class="mr-2" />
<MdiDiscord v-if="notificationOption.type === 'Discord'" class="mr-2" />
<MdiChat v-if="notificationOption.type === 'Mattermost'" class="mr-2" />
<MdiWhatsapp v-if="notificationOption.type === 'Whatsapp Twilio'" class="mr-2" />
<MdiCellphoneMessage v-if="notificationOption.type === 'Twilio'" class="mr-2" />
{{ notificationOption.type }}
</div>
</a-select-option>
@ -471,17 +469,20 @@ onMounted(() => {
</a-form-item>
</a-col>
</a-row>
<a-row v-if="hook.notification.type === 'URL'" class="mb-5" type="flex" :gutter="[16, 0]">
<a-col :span="6">
<a-select v-model:value="hook.notification.payload.method" size="large">
<a-select-option v-for="(method, i) in methodList" :key="i" :value="method.title">{{ method.title }}</a-select-option>
</a-select>
</a-col>
<a-col :span="18">
<a-form-item v-bind="validateInfos['notification.payload.path']">
<a-input v-model:value="hook.notification.payload.path" size="large" placeholder="http://example.com" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-tabs v-model:activeKey="urlTabKey" centered>
<a-tab-pane key="body" tab="Body">
@ -503,6 +504,7 @@ onMounted(() => {
</a-tabs>
</a-col>
</a-row>
<a-row v-if="hook.notification.type === 'Slack'" type="flex">
<a-col :span="24">
<a-form-item v-bind="validateInfos['notification.channels']">
@ -516,6 +518,7 @@ onMounted(() => {
</a-form-item>
</a-col>
</a-row>
<a-row v-if="hook.notification.type === 'Microsoft Teams'" type="flex">
<a-col :span="24">
<a-form-item v-bind="validateInfos['notification.channels']">
@ -529,6 +532,7 @@ onMounted(() => {
</a-form-item>
</a-col>
</a-row>
<a-row v-if="hook.notification.type === 'Discord'" type="flex">
<a-col :span="24">
<a-form-item v-bind="validateInfos['notification.channels']">
@ -542,6 +546,7 @@ onMounted(() => {
</a-form-item>
</a-col>
</a-row>
<a-row v-if="hook.notification.type === 'Mattermost'" type="flex">
<a-col :span="24">
<a-form-item v-bind="validateInfos['notification.channels']">
@ -555,6 +560,7 @@ onMounted(() => {
</a-form-item>
</a-col>
</a-row>
<a-row v-if="formInput[hook.notification.type] && hook.notification.payload" type="flex">
<a-col v-for="(input, i) in formInput[hook.notification.type]" :key="i" :span="24">
<a-form-item v-if="input.type === 'LongText'" v-bind="validateInfos[`notification.payload.${input.key}`]">
@ -565,6 +571,7 @@ onMounted(() => {
</a-form-item>
</a-col>
</a-row>
<a-row class="mb-5" type="flex">
<a-col :span="24">
<a-card>
@ -573,6 +580,7 @@ onMounted(() => {
</a-card>
</a-col>
</a-row>
<a-row>
<a-col :span="24">
<div class="text-gray-600">
@ -582,7 +590,7 @@ onMounted(() => {
<template #title>
<span> <strong>data</strong> : Row data <br /> </span>
</template>
<MdiInformationIcon class="ml-2" />
<MdiInformation class="ml-2" />
</a-tooltip>
<div class="mt-3">

8
packages/nc-gui-v2/components/webhook/List.vue

@ -1,9 +1,7 @@
<script setup lang="ts">
import { useToast } from 'vue-toastification'
import { onMounted } from '@vue/runtime-core'
import { MetaInj } from '~/context'
import MdiHookIcon from '~icons/mdi/hook'
import MdiDeleteOutlineIcon from '~icons/mdi/delete-outline'
import { inject, onMounted, ref, useNuxtApp } from '#imports'
const emit = defineEmits(['edit'])
@ -75,7 +73,7 @@ onMounted(() => {
</template>
<template #avatar>
<div class="mt-4">
<MdiHookIcon class="text-xl" />
<MdiHook class="text-xl" />
</div>
</template>
</a-list-item-meta>
@ -84,7 +82,7 @@ onMounted(() => {
<!-- Notify Via -->
<div class="mr-2">{{ $t('labels.notifyVia') }} : {{ item?.notification?.type }}</div>
<div class="float-right pt-2 pr-1">
<MdiDeleteOutlineIcon class="text-xl" @click.stop="deleteHook(item, index)" />
<MdiDeleteOutline class="text-xl" @click.stop="deleteHook(item, index)" />
</div>
</div>
</template>

3
packages/nc-gui-v2/composables/useColumnCreateStore.ts

@ -31,7 +31,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
// state
// todo: give proper type - ColumnType
const formState = ref<Partial<Record<string, any>>>({
const formState = ref<Record<string, any>>({
title: 'title',
uidt: UITypes.SingleLineText,
...(column?.value || {}),
@ -168,6 +168,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
formState.value.altered = formState.value.altered || 2
}
// todo: type of onAlter is wrong, the first argument is `CheckboxChangeEvent` not a number.
const onAlter = (val = 2, cdf = false) => {
formState.value.altered = formState.value.altered || val
if (cdf) formState.value.cdf = formState.value.cdf || null

4
packages/nc-gui-v2/composables/useLTARStore.ts

@ -160,7 +160,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
column?.value?.title,
getRelatedTableRowId(row) as string,
)
} catch (e) {
} catch (e: any) {
notification.error({
message: 'Unlink failed',
description: await extractSdkResponseErrorMsg(e),
@ -198,7 +198,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
column?.value?.title,
getRelatedTableRowId(row) as string,
)
} catch (e) {
} catch (e: any) {
notification.error({
message: 'Linking failed',
description: await extractSdkResponseErrorMsg(e),

66
packages/nc-gui-v2/composables/useTableCreate.ts

@ -1,66 +0,0 @@
import type { TableType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import { useToast } from 'vue-toastification'
import { useProject } from './useProject'
import { useNuxtApp } from '#app'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
export function useTableCreate(onTableCreate?: (tableMeta: TableType) => void) {
const table = reactive<{ title: string; table_name: string; columns: string[] }>({
title: '',
table_name: '',
columns: {
id: true,
title: true,
created_at: true,
updated_at: true,
},
})
const { sqlUi, project, tables } = useProject()
const toast = useToast()
const { $api } = useNuxtApp()
const createTable = async () => {
try {
if (!sqlUi?.value) return
const columns = sqlUi?.value?.getNewTableColumns().filter((col) => {
if (col.column_name === 'id' && table.columns.id_ag) {
Object.assign(col, sqlUi?.value?.getDataTypeForUiType({ uidt: UITypes.ID }, 'AG'))
col.dtxp = sqlUi?.value?.getDefaultLengthForDatatype(col.dt)
col.dtxs = sqlUi?.value?.getDefaultScaleForDatatype(col.dt)
return true
}
return !!table.columns[col.column_name]
})
const tableMeta = await $api.dbTable.create(project?.value?.id as string, {
...table,
columns,
})
onTableCreate?.(tableMeta)
} catch (e: any) {
toast.error(await extractSdkResponseErrorMsg(e))
}
}
watch(
() => table.title,
(title) => {
table.table_name = `${project?.value?.prefix || ''}${title}`
},
)
const generateUniqueTitle = () => {
let c = 1
while (tables?.value?.some((t) => t.title === `Sheet${c}`)) {
c++
}
table.title = `Sheet${c}`
}
return { table, createTable, generateUniqueTitle, tables, project }
}

667
packages/nc-gui-v2/package-lock.json generated

File diff suppressed because it is too large Load Diff

2
packages/nc-gui-v2/package.json

@ -33,7 +33,7 @@
"xlsx": "^0.17.3"
},
"devDependencies": {
"@antfu/eslint-config": "^0.25.2",
"@antfu/eslint-config": "^0.26.0",
"@iconify-json/cil": "^1.1.2",
"@iconify-json/clarity": "^1.1.4",
"@iconify-json/eva": "^1.1.2",

4
packages/nc-gui-v2/vue-color-shims.d.ts vendored

@ -0,0 +1,4 @@
declare module '@ckpack/vue-color' {
import type { Component } from '@vue/runtime-core'
const Sketch: Component
}
Loading…
Cancel
Save