Browse Source

chore(gui-v2): base url correction (#3510)

* chore(gui-v2): base url correction

- support nocodb serving under a nested path

Signed-off-by: Pranav C <pranavxc@gmail.com>

* chore(gui-v2): initial base url correction

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix(gui-v2): redirect to dashboard with relative path

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix(gui-v2): make plugin path relative

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix(gui-v2): redirect path correction

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix: allow download option migration handling

Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>

* fix(gui-v2): redirect old base url to new path

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix(gui-v2): redirect old base ur with some view/table open

Signed-off-by: Pranav C <pranavxc@gmail.com>

* fix(gui-v2): handle old tab url and navigate to the new url

Signed-off-by: Pranav C <pranavxc@gmail.com>

* chore(gui-v2): cleanup

Signed-off-by: Pranav C <pranavxc@gmail.com>

Signed-off-by: Pranav C <pranavxc@gmail.com>
Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com>
Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
pull/3515/head
Pranav C 2 years ago committed by GitHub
parent
commit
21a306030c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .run/dev.run.xml
  2. 8
      packages/nc-gui/components/dashboard/TreeView.vue
  3. 2
      packages/nc-gui/components/dashboard/settings/AppStore.vue
  4. 2
      packages/nc-gui/components/dashboard/settings/Metadata.vue
  5. 4
      packages/nc-gui/components/dashboard/settings/Misc.vue
  6. 30
      packages/nc-gui/components/dashboard/settings/Modal.vue
  7. 2
      packages/nc-gui/components/general/HelpAndSupport.vue
  8. 11
      packages/nc-gui/components/general/PreviewAs.vue
  9. 37
      packages/nc-gui/components/general/SocialCard.vue
  10. 11
      packages/nc-gui/components/smartsheet-column/EditOrAdd.vue
  11. 1
      packages/nc-gui/components/smartsheet-header/Menu.vue
  12. 6
      packages/nc-gui/components/smartsheet-toolbar/AddRow.vue
  13. 25
      packages/nc-gui/components/smartsheet-toolbar/ColumnFilterMenu.vue
  14. 7
      packages/nc-gui/components/smartsheet-toolbar/FieldsMenu.vue
  15. 6
      packages/nc-gui/components/smartsheet-toolbar/Reload.vue
  16. 2
      packages/nc-gui/components/smartsheet-toolbar/ShareView.vue
  17. 2
      packages/nc-gui/components/smartsheet-toolbar/SharedViewList.vue
  18. 1
      packages/nc-gui/components/smartsheet/Form.vue
  19. 9
      packages/nc-gui/components/smartsheet/Grid.vue
  20. 4
      packages/nc-gui/components/smartsheet/Pagination.vue
  21. 2
      packages/nc-gui/components/smartsheet/expanded-form/Comments.vue
  22. 1
      packages/nc-gui/components/smartsheet/expanded-form/Header.vue
  23. 4
      packages/nc-gui/components/smartsheet/sidebar/MenuBottom.vue
  24. 4
      packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue
  25. 1
      packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue
  26. 15
      packages/nc-gui/components/smartsheet/sidebar/index.vue
  27. 15
      packages/nc-gui/components/tabs/auth/UserManagement.vue
  28. 8
      packages/nc-gui/components/webhook/List.vue
  29. 3
      packages/nc-gui/composables/useColumnCreateStore.ts
  30. 2
      packages/nc-gui/composables/useGlobal/state.ts
  31. 4
      packages/nc-gui/composables/useProject.ts
  32. 2
      packages/nc-gui/composables/useSharedView.ts
  33. 1
      packages/nc-gui/composables/useTable.ts
  34. 18
      packages/nc-gui/composables/useViewColumns.ts
  35. 3
      packages/nc-gui/composables/useViewData.ts
  36. 14
      packages/nc-gui/composables/useViewFilters.ts
  37. 8
      packages/nc-gui/composables/useViewSorts.ts
  38. 1
      packages/nc-gui/layouts/base.vue
  39. 2
      packages/nc-gui/nuxt.config.ts
  40. 25
      packages/nc-gui/pages/[projectType]/[projectId]/index.vue
  41. 1
      packages/nc-gui/pages/[projectType]/[projectId]/index/index.vue
  42. 2
      packages/nc-gui/pages/[projectType]/[projectId]/index/index/index.vue
  43. 15
      packages/nc-gui/pages/[projectType]/base/[baseId].vue
  44. 2
      packages/nc-gui/pages/index/index/[projectId].vue
  45. 807
      packages/nocodb-sdk/package-lock.json
  46. 14
      packages/nocodb/package-lock.json
  47. 2
      packages/nocodb/package.json
  48. 4
      packages/nocodb/src/lib/Noco.ts

4
.run/dev.run.xml

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run GUI v2" type="js.build_tools.npm">
<package-json value="$PROJECT_DIR$/packages/nc-gui-v2/package.json" />
<package-json value="$PROJECT_DIR$/packages/nc-gui/package.json" />
<command value="run" />
<scripts>
<script value="dev" />
@ -9,4 +9,4 @@
<envs />
<method v="2" />
</configuration>
</component>
</component>

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

@ -132,7 +132,7 @@ const setMenuContext = (type: 'table' | 'main', value?: any) => {
contextMenuTarget.type = type
contextMenuTarget.value = value
$e('c:table:create:navdraw:right-click')
// $e('c:table:create:navdraw:right-click')
}
const reloadTables = async () => {
@ -142,8 +142,6 @@ const reloadTables = async () => {
}
const addTableTab = (table: TableType) => {
$e('a:table:open')
addTab({ title: table.title, id: table.id, type: table.type as any })
}
@ -201,7 +199,7 @@ function openAirtableImportDialog() {
}
function openTableCreateDialog() {
$e('a:actions:create-table')
$e('c:table:create:navdraw')
const isOpen = ref(true)
@ -375,7 +373,7 @@ function openTableCreateDialog() {
<template v-if="!isLocked && !isSharedBase" #overlay>
<a-menu class="!py-0 rounded text-sm">
<template v-if="contextMenuTarget.type === 'table'">
<a-menu-item v-if="isUIAllowed('table-rename')" @click="openRenameTableDialog(contextMenuTarget.value)">
<a-menu-item v-if="isUIAllowed('table-rename')" @click="openRenameTableDialog(contextMenuTarget.value, true)">
<div class="nc-project-menu-item">
{{ $t('general.rename') }}
</div>

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

@ -129,7 +129,7 @@ onMounted(async () => {
:style="{
backgroundColor: app.title === 'SES' ? '#242f3e' : '',
}"
:src="`/${app.logo}`"
:src="app.logo"
/>
<div v-else />
</div>

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

@ -83,7 +83,7 @@ const columns = [
<div class="flex flex-col w-3/5">
<div class="flex flex-row justify-end items-center w-full mb-4">
<!-- Reload -->
<a-button class="self-start nc-btn-metasync-reload" @click="loadMetaDiff">
<a-button v-t="['a:proj-meta:meta-data:reload']" class="self-start nc-btn-metasync-reload" @click="loadMetaDiff">
<div class="flex items-center gap-2 text-gray-600 font-light">
<MdiReload :class="{ 'animate-infinite animate-spin !text-success': isLoading }" />
{{ $t('general.reload') }}

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

@ -10,7 +10,9 @@ watch(includeM2M, async () => await loadTables())
<div class="flex flex-col w-full">
<div class="flex flex-row items-center w-full mb-4 gap-2">
<!-- Show M2M Tables -->
<a-checkbox v-model:checked="includeM2M">{{ $t('msg.info.showM2mTables') }}</a-checkbox>
<a-checkbox v-model:checked="includeM2M" v-t="['c:themes:show-m2m-tables']">{{
$t('msg.info.showM2mTables')
}}</a-checkbox>
</div>
</div>
</div>

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

@ -5,6 +5,7 @@ import AppStore from './AppStore.vue'
import Metadata from './Metadata.vue'
import UIAcl from './UIAcl.vue'
import Misc from './Misc.vue'
import { useNuxtApp } from '#app'
import { useI18n, useUIPermission, useVModel, watch } from '#imports'
import ApiTokenManagement from '~/components/tabs/auth/ApiTokenManagement.vue'
import UserManagement from '~/components/tabs/auth/UserManagement.vue'
@ -22,6 +23,7 @@ interface SubTabGroup {
[key: string]: {
title: string
body: any
onClick?: () => void
}
}
@ -30,6 +32,7 @@ interface TabGroup {
title: string
icon: FunctionalComponent<SVGAttributes, {}>
subTabs: SubTabGroup
onClick?: () => void
}
}
@ -43,6 +46,8 @@ const { isUIAllowed } = useUIPermission()
const { t } = useI18n()
const { $e } = useNuxtApp()
const tabsInfo: TabGroup = {
teamAndAuth: {
title: t('title.teamAndAuth'),
@ -63,6 +68,9 @@ const tabsInfo: TabGroup = {
},
}),
},
onClick: () => {
$e('c:settings:team-auth')
},
},
appStore: {
// App Store
@ -74,6 +82,9 @@ const tabsInfo: TabGroup = {
body: AppStore,
},
},
onClick: () => {
$e('c:settings:appstore')
},
},
metaData: {
// Project Metadata
@ -89,12 +100,18 @@ const tabsInfo: TabGroup = {
// UI Access Control
title: t('title.uiACL'),
body: UIAcl,
onClick: () => {
$e('c:table:ui-acl')
},
},
misc: {
title: t('general.misc'),
body: Misc,
},
},
onClick: () => {
$e('c:settings:proj-metadata')
},
},
audit: {
// Audit
@ -107,9 +124,11 @@ const tabsInfo: TabGroup = {
body: AuditTab,
},
},
onClick: () => {
$e('c:settings:audit')
},
},
}
const firstKeyOfObject = (obj: object) => Object.keys(obj)[0]
// Array of keys of tabs which are selected. In our case will be only one.
@ -164,7 +183,7 @@ watch(
:key="key"
class="group active:(!ring-0) hover:(!bg-primary !bg-opacity-25)"
>
<div class="flex items-center space-x-2">
<div class="flex items-center space-x-2" @click="tab.onClick">
<component :is="tab.icon" class="group-hover:text-accent" />
<div class="select-none">
@ -178,7 +197,12 @@ watch(
<!-- Sub Tabs -->
<a-layout-content class="h-auto px-4 scrollbar-thumb-gray-500">
<a-menu v-model:selectedKeys="selectedSubTabKeys" :open-keys="[]" mode="horizontal">
<a-menu-item v-for="(tab, key) of selectedTab.subTabs" :key="key" class="active:(!ring-0) select-none">
<a-menu-item
v-for="(tab, key) of selectedTab.subTabs"
:key="key"
class="active:(!ring-0) select-none"
@click="tab.onClick"
>
{{ tab.title }}
</a-menu-item>
</a-menu>

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

@ -41,7 +41,7 @@ const openSwaggerLink = () => {
<template #before>
<a-list-item v-if="project">
<nuxt-link
v-t="['e:docs']"
v-t="['a:navbar:user:swagger']"
class="!no-underline !text-current py-4 font-semibold"
target="_blank"
@click="openSwaggerLink"

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

@ -1,5 +1,4 @@
<script lang="ts" setup>
import { useI18n } from 'vue-i18n'
import { onUnmounted, useEventListener, useGlobal, useState, watch } from '#imports'
import MdiAccountStar from '~icons/mdi/account-star'
import MdiAccountHardHat from '~icons/mdi/account-hard-hat'
@ -14,6 +13,7 @@ const position = useState('preview-as-position', () => ({
x: `${window.innerWidth / 2 - 250}px`,
}))
const { $e } = useNuxtApp()
const { t } = useI18n()
const roleList = [
@ -50,7 +50,10 @@ onUnmounted(() => {
})
/** reload page on previewas change */
watch(previewAs, () => window.location.reload())
watch(previewAs, (newRole) => {
$e('a:navdraw:preview', { role: newRole })
window.location.reload()
})
</script>
<template>
@ -69,8 +72,8 @@ watch(previewAs, () => window.location.reload())
<span>{{ $t('activity.previewAs') }}</span>
<a-radio-group v-model:value="previewAs" name="radioGroup">
<a-radio v-for="role of roleList" :key="role.value" class="capitalize !text-white" :value="role.value"
>{{ role.label }}
<a-radio v-for="role of roleList" :key="role.value" class="capitalize !text-white" :value="role.value">
{{ role.label }}
</a-radio>
</a-radio-group>

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

@ -26,7 +26,7 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</a-list-item>
<a-list-item>
<nuxt-link
v-t="['e:docs']"
v-t="['e:api-docs']"
class="text-primary !no-underline !text-current"
target="_blank"
to="https://apis.nocodb.com/"
@ -39,7 +39,12 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</nuxt-link>
</a-list-item>
<a-list-item>
<nuxt-link class="text-primary !no-underline !text-current" to="https://github.com/nocodb/nocodb" target="_blank">
<nuxt-link
v-t="['e:community:github']"
class="text-primary !no-underline !text-current"
to="https://github.com/nocodb/nocodb"
target="_blank"
>
<div class="flex items-center text-sm">
<mdi-github class="mx-3 text-lg" />
<div v-if="isRtlLang">
@ -60,7 +65,12 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</nuxt-link>
</a-list-item>
<a-list-item>
<nuxt-link class="!no-underline !text-current" to="https://calendly.com/nocodb-meeting" target="_blank">
<nuxt-link
v-t="['e:community:book-demo']"
class="!no-underline !text-current"
to="https://calendly.com/nocodb-meeting"
target="_blank"
>
<div class="flex items-center text-sm">
<mdi-calendar-month class="mx-3 text-lg" :color="colors.dark[3 % colors.dark.length]" />
<!-- Book a Free DEMO -->
@ -71,7 +81,12 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</nuxt-link>
</a-list-item>
<a-list-item>
<nuxt-link class="!no-underline !text-current" to="https://discord.gg/5RgZmkW" target="_blank">
<nuxt-link
v-t="['e:community:discord']"
class="!no-underline !text-current"
to="https://discord.gg/5RgZmkW"
target="_blank"
>
<div class="flex items-center text-sm">
<mdi-discord class="mx-3 text-lg" :color="colors.dark[0 % colors.dark.length]" />
<!-- Get your questions answered -->
@ -82,7 +97,12 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</nuxt-link>
</a-list-item>
<a-list-item>
<nuxt-link class="!no-underline !text-current" to="https://twitter.com/NocoDB" target="_blank">
<nuxt-link
v-t="['e:community:twitter']"
class="!no-underline !text-current"
to="https://twitter.com/NocoDB"
target="_blank"
>
<div class="flex items-center text-sm">
<mdi-twitter class="mx-3 text-lg" :color="colors.dark[1 % colors.dark.length]" />
<!-- Follow NocoDB -->
@ -103,7 +123,12 @@ const isRtlLang = $computed(() => ['fa'].includes(currentLang.value))
</nuxt-link>
</a-list-item>
<a-list-item>
<nuxt-link v-t="['e:reddit']" class="!no-underline !text-current" target="_blank" to="https://www.reddit.com/r/NocoDB/">
<nuxt-link
v-t="['e:community:reddit']"
class="!no-underline !text-current"
target="_blank"
to="https://www.reddit.com/r/NocoDB/"
>
<div class="ml-3 flex items-center text-sm">
<LogosRedditIcon />
<span class="ml-4">/r/NocoDB/</span>

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

@ -1,8 +1,9 @@
<script lang="ts" setup>
import { UITypes, isVirtualCol } from 'nocodb-sdk'
import { message } from 'ant-design-vue'
import { useNuxtApp } from '#app'
import { computed, inject, useMetas, watchEffect } from '#imports'
import { MetaInj, ReloadViewDataHookInj } from '~/context'
import { IsFormInj, MetaInj, ReloadViewDataHookInj } from '~/context'
import { uiTypes } from '~/utils/columnUtils'
import MdiPlusIcon from '~icons/mdi/plus-circle-outline'
import MdiMinusIcon from '~icons/mdi/minus-circle-outline'
@ -17,8 +18,12 @@ const { getMeta } = useMetas()
const { t } = useI18n()
const { $e } = useNuxtApp()
const meta = inject(MetaInj)
const isForm = inject(IsFormInj, ref(false))
const reloadDataTrigger = inject(ReloadViewDataHookInj)
const advancedOptions = ref(false)
@ -55,6 +60,10 @@ async function onSubmit() {
advancedOptions.value = false
}, 500)
emit('submit')
if (isForm.value) {
$e('a:form-view:add-new-field')
}
}
// focus and select the column name field

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

@ -29,6 +29,7 @@ const deleteColumn = () =>
await $api.dbTableColumn.delete(column?.value?.id as string)
await getMeta(meta?.value?.id as string, true)
$e('a:column:delete')
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}

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

@ -13,7 +13,11 @@ const onClick = () => {
<template>
<a-tooltip placement="bottom">
<template #title> {{ $t('activity.addRow') }} </template>
<div :class="{ 'group': !isLocked, 'disabled-ring': isLocked }" class="nc-add-new-row-btn flex align-center">
<div
v-t="['c:row:add:grid-top']"
:class="{ 'group': !isLocked, 'disabled-ring': isLocked }"
class="nc-add-new-row-btn flex align-center"
>
<MdiPlusOutline
:class="{ 'cursor-pointer text-gray-500 group-hover:(text-primary)': !isLocked, 'disabled': isLocked }"
@click="onClick"

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

@ -1,4 +1,5 @@
<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'
@ -12,6 +13,8 @@ const { filterAutoSave } = useGlobal()
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(
@ -35,6 +38,16 @@ watch(
)
const applyChanges = async () => await filterComp.value?.applyChanges()
const filterAutoSaveLoc = computed({
get() {
return filterAutoSave.value
},
set(val) {
$e('a:filter:auto-apply', { flag: val })
filterAutoSave.value = val
},
})
</script>
<template>
@ -57,7 +70,7 @@ const applyChanges = async () => await filterComp.value?.applyChanges()
@update:filters-length="filtersLength = $event"
>
<div v-if="!isPublic" class="flex items-end mt-2 min-h-[30px]" @click.stop>
<a-checkbox id="col-filter-checkbox" v-model:checked="filterAutoSave" class="col-filter-checkbox" hide-details dense>
<a-checkbox id="col-filter-checkbox" v-model:checked="filterAutoSaveLoc" class="col-filter-checkbox" hide-details dense>
<span class="text-grey text-xs">
{{ $t('msg.info.filterAutoApply') }}
<!-- Auto apply -->
@ -65,7 +78,15 @@ const applyChanges = async () => await filterComp.value?.applyChanges()
</a-checkbox>
<div class="flex-1" />
<a-button v-show="!filterAutoSave" size="small" class="text-xs ml-2" @click="applyChanges"> Apply changes </a-button>
<a-button
v-show="!filterAutoSave"
v-t="['a:filter:auto-apply']"
size="small"
class="text-xs ml-2"
@click="applyChanges"
>
Apply changes
</a-button>
</div>
</SmartsheetToolbarColumnFilter>
</template>

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

@ -144,7 +144,12 @@ const getIcon = (c: ColumnType) =>
<Draggable v-model="fields" item-key="id" @change="onMove($event)">
<template #item="{ element: field, index: index }">
<div v-show="filteredFieldList.includes(field)" :key="field.id" class="px-2 py-1 flex items-center" @click.stop>
<a-checkbox v-model:checked="field.show" class="shrink" @change="saveOrUpdate(field, index)">
<a-checkbox
v-model:checked="field.show"
v-t="['a:fields:show-hide']"
class="shrink"
@change="saveOrUpdate(field, index)"
>
<div class="flex items-center">
<component :is="getIcon(metaColumnById[field.fk_column_id])" />
<span>{{ field.title }}</span>

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

@ -1,9 +1,13 @@
<script setup lang="ts">
import { ReloadViewDataHookInj, inject } from '#imports'
const { $e } = useNuxtApp()
const reloadHook = inject(ReloadViewDataHookInj)!
const onClick = () => reloadHook.trigger()
const onClick = () => {
$e('a:table:reload:navbar')
reloadHook.trigger()
}
</script>
<template>

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

@ -139,7 +139,7 @@ watch(passwordProtected, (value) => {
<a v-t="['c:view:share:open-url']" :href="sharedViewUrl" target="_blank">
<MdiOpenInNewIcon class="text-sm text-gray-500 mt-2" />
</a>
<MdiCopyIcon class="text-gray-500 text-sm cursor-pointer" @click="copyLink" />
<MdiCopyIcon v-t="['c:view:share:copy-url']" class="text-gray-500 text-sm cursor-pointer" @click="copyLink" />
</div>
<a-collapse ghost>

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

@ -69,7 +69,7 @@ const sharedViewUrl = (view: SharedViewType) => {
const renderAllowCSVDownload = (view: SharedViewType) => {
if (view.type === ViewTypes.GRID) {
view.meta = (view.meta && typeof view.meta === 'string' ? JSON.parse(view.meta) : view.meta) as Record<string, any>
return view.meta.allowCSVDownload ? '✔' : '❌'
return view.meta?.allowCSVDownload ? '✔' : '❌'
} else {
return 'N/A'
}

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

@ -591,6 +591,7 @@ onMounted(async () => {
<a-switch
v-model:checked="element.required"
v-t="['a:form-view:field:mark-required']"
size="small"
class="ml-2"
@change="updateColMeta(element)"

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

@ -433,6 +433,7 @@ const showContextMenu = (e: MouseEvent, target?: { row: number; col: number }) =
class="cursor-pointer flex items-center border-1 active:ring rounded p-1 hover:(bg-primary bg-opacity-10)"
>
<MdiArrowExpand
v-t="['c:row-expand']"
class="select-none transform hover:(text-accent scale-120) nc-row-expand"
@click="expandForm(row, state)"
/>
@ -514,14 +515,14 @@ const showContextMenu = (e: MouseEvent, target?: { row: number; col: number }) =
<template v-if="!isLocked && isUIAllowed('xcDatatableEditable')" #overlay>
<a-menu class="shadow !rounded !py-0" @click="contextMenu = false">
<a-menu-item v-if="contextMenuTarget" @click="deleteRow(contextMenuTarget.row)">
<div class="nc-project-menu-item">
<div v-t="['a:row:delete']" class="nc-project-menu-item">
<!-- Delete Row -->
{{ $t('activity.deleteRow') }}
</div>
</a-menu-item>
<a-menu-item @click="deleteSelectedRows">
<div class="nc-project-menu-item">
<div v-t="['a:row:delete-bulk']" class="nc-project-menu-item">
<!-- Delete Selected Rows -->
{{ $t('activity.deleteSelectedRow') }}
</div>
@ -529,11 +530,11 @@ const showContextMenu = (e: MouseEvent, target?: { row: number; col: number }) =
<!-- Clear cell -->
<a-menu-item v-if="contextMenuTarget" @click="clearCell(contextMenuTarget)">
<div class="nc-project-menu-item">{{ $t('activity.clearCell') }}</div>
<div v-t="['a:row:clear']" class="nc-project-menu-item">{{ $t('activity.clearCell') }}</div>
</a-menu-item>
<a-menu-item v-if="contextMenuTarget" @click="addEmptyRow(contextMenuTarget.row + 1)">
<div class="nc-project-menu-item">
<div v-t="['a:row:insert']" class="nc-project-menu-item">
<!-- Insert New Row -->
{{ $t('activity.insertRow') }}
</div>

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

@ -12,7 +12,9 @@ const size = computed(() => paginatedData.value?.pageSize ?? 25)
const page = computed({
get: () => paginatedData?.value?.page ?? 1,
set: (p) => changePage?.(p),
set: (p) => {
changePage?.(p)
},
})
</script>

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

@ -57,7 +57,7 @@ watch(
<div class="p-0">
<div class="flex justify-center">
<!-- Comments only -->
<a-checkbox v-model:checked="commentsOnly" @change="loadCommentsAndLogs"
<a-checkbox v-model:checked="commentsOnly" v-t="['c:row-expand:comment-only']" @change="loadCommentsAndLogs"
>{{ $t('labels.commentsOnly') }}<span class="text-[11px] text-gray-500"></span>
</a-checkbox>
</div>

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

@ -55,6 +55,7 @@ const iconColor = '#1890ff'
</template>
<MdiCommentTextOutline
v-if="isUIAllowed('rowComments') && !isNew"
v-t="['c:row-expand:comment-toggle']"
class="cursor-pointer select-none nc-toggle-comments text-gray-500"
@click="commentsDrawer = !commentsDrawer"
/>

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

@ -1,5 +1,6 @@
<script lang="ts" setup>
import { ViewTypes } from 'nocodb-sdk'
import { useNuxtApp } from '#app'
import { useSmartsheetStoreOrThrow, useUIPermission, viewIcons } from '#imports'
interface Emits {
@ -8,11 +9,14 @@ interface Emits {
const emits = defineEmits<Emits>()
const { $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const { isSqlView } = useSmartsheetStoreOrThrow()
function onOpenModal(type: ViewTypes, title = '') {
$e('c:view:create', { view: type })
emits('openModal', { type, title })
}
</script>

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

@ -7,6 +7,7 @@ import type { Ref } from 'vue'
import Sortable from 'sortablejs'
import { useI18n } from 'vue-i18n'
import RenameableMenuItem from './RenameableMenuItem.vue'
import { useNuxtApp } from '#app'
import {
ActiveViewInj,
ViewListInj,
@ -35,6 +36,8 @@ interface Emits {
(event: 'sorted'): void
}
const { $e } = useNuxtApp()
const activeView = inject(ActiveViewInj, ref())
const views = inject<Ref<any[]>>(ViewListInj, ref([]))
@ -129,6 +132,7 @@ async function onSortEnd(evt: SortableEvent) {
await api.dbView.update(currentItem.id, { order: _nextOrder })
markItem(currentItem.id)
$e('a:view:reorder')
}
let sortable: Sortable

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

@ -50,6 +50,7 @@ function onDblClick() {
if (!isEditing) {
isEditing = true
originalTitle = vModel.value.title
$e('c:view:rename', { view: vModel.value?.type })
}
}

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

@ -29,6 +29,8 @@ const router = useRouter()
const route = useRoute()
const { $e } = useNuxtApp()
provide(ViewListInj, views)
/** Sidebar visible */
@ -56,9 +58,19 @@ watch(
[views, () => route.params.viewTitle],
([nextViews, viewTitle]) => {
if (viewTitle) {
const view = nextViews.find((v) => v.title === viewTitle)
let view = nextViews.find((v) => v.title === viewTitle)
if (view) {
activeView.value = view
} else {
/** search with view id and if found replace with title */
view = nextViews.find((v) => v.id === viewTitle)
if (view) {
router.replace({
params: {
viewTitle: view.title,
},
})
}
}
}
/** if active view is not found, set it to first view */
@ -83,6 +95,7 @@ function onCreate(view: ViewType) {
activeView.value = view
router.push({ params: { viewTitle: view.title || '' } })
modalOpen = false
$e('a:view:create', { view: view.type })
}
</script>

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

@ -147,6 +147,7 @@ const copyInviteUrl = (user: User) => {
// Invite URL copied to clipboard
message.success(t('msg.success.inviteURLCopied'))
$e('c:user:copy-url')
}
onMounted(() => {
@ -193,13 +194,21 @@ watchDebounced(searchText, () => loadUsers(), { debounce: 300, maxWait: 600 })
</div>
<div class="flex flex-row space-x-1">
<a-button size="middle" type="text" @click="loadUsers()">
<a-button v-t="['a:user:reload']" size="middle" type="text" @click="loadUsers()">
<div class="flex flex-row justify-center items-center caption capitalize space-x-1">
<MdiReload class="text-gray-500" />
<div class="text-gray-500">{{ $t('general.reload') }}</div>
</div>
</a-button>
<a-button v-if="isUIAllowed('newUser')" size="middle" type="primary" ghost class="nc-invite-team" @click="onInvite">
<a-button
v-if="isUIAllowed('newUser')"
v-t="['c:user:invite']"
size="middle"
type="primary"
ghost
class="nc-invite-team"
@click="onInvite"
>
<div class="flex flex-row justify-center items-center caption capitalize space-x-1">
<MdiAccountPlusOutline class="mr-1" />
<div>{{ $t('activity.inviteTeam') }}</div>
@ -266,7 +275,7 @@ watchDebounced(searchText, () => loadUsers(), { debounce: 300, maxWait: 600 })
<template #title>
<span>{{ $t('activity.deleteUser') }}</span>
</template>
<a-button type="text" class="!rounded-md nc-user-delete" @click="onDelete(user)">
<a-button v-t="['c:user:delete']" type="text" class="!rounded-md nc-user-delete" @click="onDelete(user)">
<template #icon>
<MdiDeleteOutline class="flex mx-auto h-[1.1rem] text-gray-500" />
</template>

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

@ -57,7 +57,13 @@ onMounted(() => {
<div class="">
<div class="mb-2">
<div class="float-left font-bold text-xl mt-2 mb-4">{{ meta.title }} : Webhooks</div>
<a-button class="float-right nc-btn-create-webhook" type="primary" size="large" @click="emit('add')">
<a-button
v-t="['c:webhook:add']"
class="float-right nc-btn-create-webhook"
type="primary"
size="large"
@click="emit('add')"
>
{{ $t('activity.addWebhook') }}
</a-button>
</div>

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

@ -17,6 +17,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
const { $api } = useNuxtApp()
const { getMeta } = useMetas()
const { t } = useI18n()
const { $e } = useNuxtApp()
const isEdit = computed(() => !!column?.value?.id)
@ -201,6 +202,8 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
// Column created
message.success(t('msg.success.columnCreated'))
$e('a:column:add', { datatype: formState.value.uidt })
}
onSuccess?.()
} catch (e: any) {

2
packages/nc-gui/composables/useGlobal/state.ts

@ -77,7 +77,7 @@ export function useGlobalState(storageKey = 'nocodb-gui-v2'): State {
})
const appInfo = ref<AppInfo>({
ncSiteUrl: process.env.NC_BACKEND_URL || (process.env.NODE_ENV === 'production' ? location.origin : 'http://localhost:8080'),
ncSiteUrl: process.env.NC_BACKEND_URL || (process.env.NODE_ENV === 'production' ? '..' : 'http://localhost:8080'),
authType: 'jwt',
connectToExternalDB: false,
defaultLimit: 0,

4
packages/nc-gui/composables/useProject.ts

@ -7,7 +7,7 @@ import type { ThemeConfig } from '@/composables/useTheme'
import { useInjectionState } from '#imports'
const [setup, use] = useInjectionState((_projectId?: MaybeRef<string>) => {
const { $api } = useNuxtApp()
const { $api, $e } = useNuxtApp()
const route = useRoute()
const { includeM2M } = useGlobal()
const { setTheme, theme } = useTheme()
@ -100,6 +100,8 @@ const [setup, use] = useInjectionState((_projectId?: MaybeRef<string>) => {
}
async function saveTheme(_theme: Partial<ThemeConfig>) {
$e('c:themes:change')
const fullTheme = {
primaryColor: theme.value.primaryColor,
accentColor: theme.value.accentColor,

2
packages/nc-gui/composables/useSharedView.ts

@ -35,7 +35,7 @@ export function useSharedView() {
},
})
allowCSVDownload.value = JSON.parse(viewMeta.meta).allowCSVDownload
allowCSVDownload.value = JSON.parse(viewMeta.meta)?.allowCSVDownload
if (localPassword) password.value = localPassword
sharedView.value = { ...viewMeta }

1
packages/nc-gui/composables/useTable.ts

@ -36,6 +36,7 @@ export function useTable(onTableCreate?: (tableMeta: TableType) => void) {
...table,
columns,
})
$e('a:table:create')
onTableCreate?.(tableMeta)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))

18
packages/nc-gui/composables/useViewColumns.ts

@ -13,7 +13,7 @@ export function useViewColumns(view: Ref<ViewType> | undefined, meta: ComputedRe
const filterQuery = ref('')
const { $api } = useNuxtApp()
const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
@ -90,6 +90,7 @@ export function useViewColumns(view: Ref<ViewType> | undefined, meta: ComputedRe
await loadViewColumns()
reloadData?.()
$e('a:fields:show-all')
}
const hideAll = async (ignoreIds?: any) => {
if (isLocalMode.value) {
@ -112,6 +113,7 @@ export function useViewColumns(view: Ref<ViewType> | undefined, meta: ComputedRe
await loadViewColumns()
reloadData?.()
$e('a:fields:show-all')
}
const saveOrUpdate = async (field: any, index: number) => {
@ -163,6 +165,7 @@ export function useViewColumns(view: Ref<ViewType> | undefined, meta: ComputedRe
}
;(view.value as any).show_system_fields = v
}
$e('a:fields:system-fields')
},
})
@ -202,14 +205,11 @@ export function useViewColumns(view: Ref<ViewType> | undefined, meta: ComputedRe
})
// reload view columns when table meta changes
watch(
meta,
async (newVal, oldVal) => {
if (newVal !== oldVal && meta.value) {
await loadViewColumns()
}
},
)
watch(meta, async (newVal, oldVal) => {
if (newVal !== oldVal && meta.value) {
await loadViewColumns()
}
})
return {
fields,

3
packages/nc-gui/composables/useViewData.ts

@ -53,7 +53,7 @@ export function useViewData(
const isPublic = inject(IsPublicInj, ref(false))
const { project, isSharedBase } = useProject()
const { fetchSharedViewData, paginationData: sharedPaginationData } = useSharedView()
const { $api } = useNuxtApp()
const { $api, $e } = useNuxtApp()
const { sorts, nestedFilters } = useSmartsheetStoreOrThrow()
const { isUIAllowed } = useUIPermission()
@ -260,6 +260,7 @@ export function useViewData(
async function changePage(page: number) {
paginationData.value.page = page
await loadData({ offset: (page - 1) * (paginationData.value.pageSize || 25), where: where?.value } as any)
$e('a:grid:pagination')
}
async function deleteRowById(id: string) {

14
packages/nc-gui/composables/useViewFilters.ts

@ -29,7 +29,7 @@ export function useViewFilters(
const isPublic = inject(IsPublicInj, ref(false))
const { $api } = useNuxtApp()
const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
@ -144,6 +144,7 @@ export function useViewFilters(
} else {
filters.value.splice(i, 1)
}
$e('a:filter:delete')
}
}
@ -161,6 +162,10 @@ export function useViewFilters(
...filter,
fk_parent_id: parentId,
})
$e('a:filter:update', {
logical: filter.logical_op,
comparison: filter.comparison_op,
})
} else {
// todo: return type of dbTableFilter is void?
filters.value[i] = (await $api.dbTableFilter.create(view?.value?.id as string, {
@ -176,7 +181,10 @@ export function useViewFilters(
reloadData?.()
}
const addFilter = () => filters.value.push(placeholderFilter)
const addFilter = () => {
filters.value.push(placeholderFilter)
$e('a:filter:add', { length: filters.value.length })
}
const addFilterGroup = async () => {
const child = placeholderFilter
@ -193,6 +201,8 @@ export function useViewFilters(
const index = filters.value.length - 1
await saveOrUpdate(filters.value[index], index, true)
$e('a:filter:add', { length: filters.value.length, group: true })
}
/** on column delete reload filters, identify by checking columns count */

8
packages/nc-gui/composables/useViewSorts.ts

@ -14,7 +14,7 @@ export function useViewSorts(
const isPublic = inject(IsPublicInj, ref(false))
const { $api } = useNuxtApp()
const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const { isSharedBase } = useProject()
@ -46,11 +46,13 @@ export function useViewSorts(
if (isUIAllowed('sortSync')) {
if (sort.id) {
await $api.dbTableSort.update(sort.id, sort)
$e('sort-updated')
} else {
sorts.value[i] = (await $api.dbTableSort.create(view?.value?.id as string, sort)) as any
}
}
reloadData?.()
$e('a:sort:dir', { direction: sort.direction })
} catch (e: any) {
console.error(e)
message.error(await extractSdkResponseErrorMsg(e))
@ -63,6 +65,8 @@ export function useViewSorts(
direction: 'asc',
},
]
$e('a:sort:add', { length: sorts?.value?.length })
}
const deleteSort = async (sort: SortType, i: number) => {
@ -72,6 +76,8 @@ export function useViewSorts(
}
sorts.value.splice(i, 1)
sorts.value = [...sorts.value]
$e('a:sort:delete')
} catch (e: any) {
console.error(e)
message.error(await extractSdkResponseErrorMsg(e))

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

@ -41,6 +41,7 @@ hooks.hook('page:finish', () => {
>
<div
v-if="!route.params.projectType"
v-t="['c:navbar:home']"
class="transition-all duration-200 p-2 cursor-pointer transform hover:scale-105 nc-noco-brand-icon"
@click="navigateTo('/')"
>

2
packages/nc-gui/nuxt.config.ts

@ -14,7 +14,7 @@ export default defineNuxtConfig({
ssr: false,
app: {
baseURL: process.env.NODE_ENV === 'production' ? '.' : undefined,
cdnURL: process.env.NODE_ENV === 'production' ? '.' : undefined,
},
css: [
'virtual:windi.css',

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

@ -27,6 +27,8 @@ definePageMeta({
const route = useRoute()
const router = useRouter()
const { appInfo, token, signOut, signedIn, user } = useGlobal()
const { project, loadProject, loadTables, isSharedBase, loadProjectMetaInfo, projectMetaInfo, saveTheme } = useProject()
@ -147,6 +149,15 @@ const copyAuthToken = async () => {
message.error(e.message)
}
}
/** If v1 url found navigate to corresponding new url*/
const { type, name, view } = route.query
if (type && name) {
router.replace(`/nc/${route.params.projectId}/${type}/${name}${view ? `/${view}` : ''}`)
}
</script>
<template>
@ -169,6 +180,7 @@ const copyAuthToken = async () => {
>
<div
v-if="isOpen && !isSharedBase"
v-t="['c:navbar:home']"
class="w-[40px] min-w-[40px] transition-all duration-200 p-1 cursor-pointer transform hover:scale-105 nc-noco-brand-icon"
@click="navigateTo('/')"
>
@ -230,7 +242,11 @@ const copyAuthToken = async () => {
<template v-if="!isSharedBase">
<!-- Copy Project Info -->
<a-menu-item key="copy">
<div class="nc-project-menu-item group" @click.stop="copyProjectInfo">
<div
v-t="['c:navbar:user:copy-proj-info']"
class="nc-project-menu-item group"
@click.stop="copyProjectInfo"
>
<MdiContentCopy class="group-hover:text-accent" />
{{ $t('activity.account.projInfo') }}
</div>
@ -253,7 +269,8 @@ const copyAuthToken = async () => {
<!-- Copy Auth Token -->
<a-menu-item key="copy">
<div v-t="['a:navbar:user:copy-auth-token']" class="nc-project-menu-item group" @click.stop="copyAuthToken">
<div v-t="['a:navbar:user:copy-auth-token']" class="nc-project-menu-item group"
@click.stop="copyAuthToken">
<MdiScriptTextKeyOutline class="group-hover:text-accent" />
{{ $t('activity.account.authToken') }}
</div>
@ -403,7 +420,8 @@ const copyAuthToken = async () => {
<template #expandIcon></template>
<a-menu-item key="0" class="!rounded-t">
<nuxt-link v-t="['c:navbar:user:email']" class="nc-project-menu-item group !no-underline" to="/user">
<nuxt-link v-t="['c:navbar:user:email']" class="nc-project-menu-item group !no-underline"
to="/user">
<MdiAt class="mt-1 group-hover:text-accent" />&nbsp;
<span class="prose-sm">{{ email }}</span>
@ -430,6 +448,7 @@ const copyAuthToken = async () => {
class="nc-sidebar-left-toggle-icon hover:after:(bg-primary bg-opacity-75) group nc-sidebar-add-row flex items-center px-2"
>
<MdiBackburger
v-t="['c:grid:toggle-navdraw']"
class="cursor-pointer transform transition-transform duration-500"
:class="{ 'rotate-180': !isOpen }"
@click="toggle(!isOpen)"

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

@ -39,6 +39,7 @@ function onEdit(targetKey: number, action: 'add' | 'remove' | string) {
class="nc-sidebar-left-toggle-icon hover:after:(bg-primary bg-opacity-75) group nc-sidebar-add-row py-2 px-3"
>
<MdiMenu
v-t="['c:grid:toggle-navdraw']"
class="cursor-pointer transform transition-transform duration-500 text-white"
:class="{ 'rotate-180': !isOpen }"
@click="toggle(!isOpen)"

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

@ -115,8 +115,6 @@ function openQuickImportDialog(type: QuickImportTypes, file: File) {
}
function openCreateTable() {
$e('a:table:open')
const isOpen = ref(true)
const { close } = useDialog(DlgTableCreate, {
'modelValue': isOpen,

15
packages/nc-gui/pages/[projectType]/base/[baseId].vue

@ -0,0 +1,15 @@
<script setup lang="ts">
/** A dummy page to redirect old shared base url from v1 to latest */
const route = useRoute()
const router = useRouter()
const { type, name, view } = route.query
if (type && name) {
router.replace(`/base/${route.params.baseId}/${type}/${name}${view ? `/${view}` : ''}`)
} else {
router.replace(`/base/${route.params.baseId}`)
}
</script>

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

@ -95,7 +95,7 @@ onMounted(async () => {
</a-form-item>
<div class="text-center">
<button type="submit" class="submit">
<button v-t="['a:project:edit:rename']" type="submit" class="submit">
<span class="flex items-center gap-2">
<MaterialSymbolsRocketLaunchOutline />
{{ $t('general.edit') }}

807
packages/nocodb-sdk/package-lock.json generated

File diff suppressed because it is too large Load Diff

14
packages/nocodb/package-lock.json generated

@ -70,7 +70,7 @@
"mysql2": "^2.2.5",
"nanoid": "^3.1.20",
"nc-common": "0.0.6",
"nc-help": "0.2.67",
"nc-help": "0.2.68",
"nc-lib-gui": "file:../nc-lib-gui",
"nc-plugin": "0.1.2",
"ncp": "^2.0.0",
@ -15245,9 +15245,9 @@
}
},
"node_modules/nc-help": {
"version": "0.2.67",
"resolved": "https://registry.npmjs.org/nc-help/-/nc-help-0.2.67.tgz",
"integrity": "sha512-O9eXHrpO0dBdFv6zUZAos+63JZEGhZ2lG+MduGZ+/BL7M5b0qU7d9b95Pmgq6Gd5wO3txT/7x7uPBHZxeSgvHQ==",
"version": "0.2.68",
"resolved": "https://registry.npmjs.org/nc-help/-/nc-help-0.2.68.tgz",
"integrity": "sha512-KaG+cykMPU165RDwcJbpJvXhhg631/pD+ubsF7L/w7NneOaYqE22zNb+jAAzF0M2jHPk65yXDp3tyXOILpz2Ig==",
"dependencies": {
"@rudderstack/rudder-sdk-node": "^1.1.3",
"axios": "^0.21.1",
@ -36644,9 +36644,9 @@
"integrity": "sha512-3AryS9uwa5NfISLxMciUonrH7YfXp+nlahB9T7girXIsLQrmwX4MdnuKs32akduCOGpKmjTJSWmATULbuMkbfw=="
},
"nc-help": {
"version": "0.2.67",
"resolved": "https://registry.npmjs.org/nc-help/-/nc-help-0.2.67.tgz",
"integrity": "sha512-O9eXHrpO0dBdFv6zUZAos+63JZEGhZ2lG+MduGZ+/BL7M5b0qU7d9b95Pmgq6Gd5wO3txT/7x7uPBHZxeSgvHQ==",
"version": "0.2.68",
"resolved": "https://registry.npmjs.org/nc-help/-/nc-help-0.2.68.tgz",
"integrity": "sha512-KaG+cykMPU165RDwcJbpJvXhhg631/pD+ubsF7L/w7NneOaYqE22zNb+jAAzF0M2jHPk65yXDp3tyXOILpz2Ig==",
"requires": {
"@rudderstack/rudder-sdk-node": "^1.1.3",
"axios": "^0.21.1",

2
packages/nocodb/package.json

@ -156,7 +156,7 @@
"mysql2": "^2.2.5",
"nanoid": "^3.1.20",
"nc-common": "0.0.6",
"nc-help": "0.2.67",
"nc-help": "0.2.68",
"nc-lib-gui": "file:../nc-lib-gui",
"nc-plugin": "0.1.2",
"ncp": "^2.0.0",

4
packages/nocodb/src/lib/Noco.ts

@ -252,9 +252,7 @@ export default class Noco {
// this.config.dashboardPath,
// await this.ncToolApi.expressMiddleware()
// );
this.router.use(NcToolGui.expressMiddleware(
this.config.dashboardPath,
));
this.router.use(NcToolGui.expressMiddleware(this.config.dashboardPath));
this.router.get('/', (_req, res) =>
res.redirect(this.config.dashboardPath)
);

Loading…
Cancel
Save