diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml
index 388cb7c8a6..adfbbc756f 100644
--- a/.github/workflows/ci-cd.yml
+++ b/.github/workflows/ci-cd.yml
@@ -45,6 +45,8 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
+ - name: remove use-node-version from .npmrc
+ run: sed -i '/^use-node-version/d' .npmrc
- name: Get pnpm store directory
shell: bash
run: |
@@ -78,6 +80,8 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
+ - name: remove use-node-version from .npmrc
+ run: sed -i '/^use-node-version/d' .npmrc
- name: Get pnpm store directory
shell: bash
run: |
diff --git a/.github/workflows/playwright-test-workflow.yml b/.github/workflows/playwright-test-workflow.yml
index f753fec7f8..fbe739b217 100644
--- a/.github/workflows/playwright-test-workflow.yml
+++ b/.github/workflows/playwright-test-workflow.yml
@@ -18,31 +18,21 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
- - name: Check node,pnpm Installation and set Path
- shell: bash
- working-directory: scripts/self-hosted-gh-runner
- timeout-minutes: 1
- run: |
- ./node-pnpm-check.sh
- echo "make sure below mentioned versions are expected versions"
- echo "If you are expecting the node and pnpm versions to be updated. Please update the node-pnpm-check.sh script"
- env
+ - name: remove use-node-version from .npmrc
+ run: sed -i '/^use-node-version/d' .npmrc
- name: Setup Node
- if: ${{ env.SETUP_NODE != 'false' }}
uses: actions/setup-node@v3
with:
- node-version: ${{ env.NC_REQ_NODE_V }}
+ node-version: 18.14.0
- name: Setup pnpm
- if: ${{ env.SETUP_PNPM != 'false' }}
uses: pnpm/action-setup@v2
with:
- version: ${{ env.NC_REQ_PNPM_V }}
+ version: 8
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=/root/setup-pnpm/node_modules/.bin/store/v3" >> $GITHUB_ENV
- uses: actions/cache@v3
- if: env.IS_NPM_CACHE_DOWNLOAD_REQUIRED == 'true'
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
diff --git a/.github/workflows/pre-build-for-playwright.yml b/.github/workflows/pre-build-for-playwright.yml
index 079137dfff..59b4a81ce8 100644
--- a/.github/workflows/pre-build-for-playwright.yml
+++ b/.github/workflows/pre-build-for-playwright.yml
@@ -2,13 +2,7 @@ name: pre-build-for-playwright
on:
workflow_call:
- inputs:
- FORCE_RUN_PRERQUISITE_STEPS:
- description: 'FORCE_RUN_PRERQUISITE_STEPS'
- required: false
- type: string
- default: 'false'
-
+
jobs:
playwright:
runs-on: [self-hosted, v3]
@@ -16,31 +10,21 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
- - name: Check node,pnpm Installation and set Path
- shell: bash
- working-directory: scripts/self-hosted-gh-runner
- timeout-minutes: 1
- run: |
- ./node-pnpm-check.sh
- echo "make sure below mentioned versions are expected versions"
- echo "If you are expecting the node and pnpm versions to be updated. Please update the node-pnpm-check.sh script"
- env
- name: Setup Node
- if: ${{ env.SETUP_NODE != 'false' }}
uses: actions/setup-node@v3
with:
- node-version: ${{ env.NC_REQ_NODE_V }}
+ node-version: 18.14.0
- name: Setup pnpm
- if: ${{ env.SETUP_PNPM != 'false' }}
uses: pnpm/action-setup@v2
with:
- version: ${{ env.NC_REQ_PNPM_V }}
+ version: 8
+ - name: remove use-node-version from .npmrc
+ run: sed -i '/^use-node-version/d' .npmrc
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=/root/setup-pnpm/node_modules/.bin/store/v3" >> $GITHUB_ENV
- uses: actions/cache@v3
- if: env.IS_NPM_CACHE_DOWNLOAD_REQUIRED == 'true'
name: Setup pnpm cache
with:
path: ${{ env.STORE_PATH }}
diff --git a/.github/workflows/release-executables.yml b/.github/workflows/release-executables.yml
index e98c0eb2b9..7b85111c5b 100644
--- a/.github/workflows/release-executables.yml
+++ b/.github/workflows/release-executables.yml
@@ -25,6 +25,7 @@ jobs:
- name: Get pnpm store directory
shell: bash
run: |
+ sed -i '/^use-node-version/d' .npmrc
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- uses: actions/cache@v3
name: Setup pnpm cache
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
index 369ad4c629..00498b7928 100644
--- a/.github/workflows/unit-test.yml
+++ b/.github/workflows/unit-test.yml
@@ -34,6 +34,8 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
+ - name: remove use-node-version from .npmrc
+ run: sed -i '/^use-node-version/d' .npmrc
- name: install dependencies
run: pnpm bootstrap
- name: run unit tests
diff --git a/.github/workflows/update-sdk-path.yml b/.github/workflows/update-sdk-path.yml
index 4ac8edb0a1..44d0519a6f 100644
--- a/.github/workflows/update-sdk-path.yml
+++ b/.github/workflows/update-sdk-path.yml
@@ -20,9 +20,9 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
with:
- fetch-depth: 0
-
+ fetch-depth: 0
- run: |
+ sed -i '/^use-node-version/d' .npmrc
pnpm bootstrap
- name: Create Pull Request
diff --git a/README.md b/README.md
index 6ac3a9a388..fbeab2db7d 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ Turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart spreadshe
Documentation
-![All Views](https://user-images.githubusercontent.com/35857179/194825053-3aa3373d-3e0f-4b42-b3f1-42928332054a.gif)
+![video avi](https://github.com/nocodb/nocodb/assets/86527202/e2fad786-f211-4dcb-9bd3-aaece83a6783)
@@ -106,6 +106,8 @@ nocodb/nocodb:latest
> If you plan to input some special characters, you may need to change the character set and collation yourself when creating the database. Please check out the examples for [MySQL Docker](https://github.com/nocodb/nocodb/issues/1340#issuecomment-1049481043).
+> Different commands just indicate the database that NocoDB will use internally for metadata storage, but that doesn't influence the ability to connect to a different database type.
+
## Binaries
##### MacOS (x64)
@@ -305,4 +307,4 @@ Thank you for your contributions! We appreciate all the contributions from the c
-
\ No newline at end of file
+
diff --git a/packages/nc-gui/assets/nc-icons/check.svg b/packages/nc-gui/assets/nc-icons/check.svg
index 314287cb8e..21d49df996 100644
--- a/packages/nc-gui/assets/nc-icons/check.svg
+++ b/packages/nc-gui/assets/nc-icons/check.svg
@@ -1,5 +1,5 @@
diff --git a/packages/nc-gui/assets/nc-icons/file-image.svg b/packages/nc-gui/assets/nc-icons/file-image.svg
new file mode 100644
index 0000000000..a0d907df5e
--- /dev/null
+++ b/packages/nc-gui/assets/nc-icons/file-image.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/packages/nc-gui/assets/nc-icons/lock.svg b/packages/nc-gui/assets/nc-icons/lock.svg
index 45946a1cf5..05f258b22a 100644
--- a/packages/nc-gui/assets/nc-icons/lock.svg
+++ b/packages/nc-gui/assets/nc-icons/lock.svg
@@ -1,4 +1,4 @@
diff --git a/packages/nc-gui/assets/nc-icons/sort.svg b/packages/nc-gui/assets/nc-icons/sort.svg
index 9acb6113c7..513c55c68b 100644
--- a/packages/nc-gui/assets/nc-icons/sort.svg
+++ b/packages/nc-gui/assets/nc-icons/sort.svg
@@ -1,5 +1,5 @@
diff --git a/packages/nc-gui/assets/style.scss b/packages/nc-gui/assets/style.scss
index c317f29f0c..7d9c703132 100644
--- a/packages/nc-gui/assets/style.scss
+++ b/packages/nc-gui/assets/style.scss
@@ -2,6 +2,10 @@
@import '@vue-flow/core/dist/style.css';
@import '@vue-flow/core/dist/theme-default.css';
+html {
+ overflow: hidden;
+}
+
body {
line-height: 1.3125rem;
}
@@ -54,7 +58,7 @@ main {
}
.mobile {
- .nc-scrollbar-md, nc-scrollbar-dark-md, nc-scrollbar-dark-md, nc-scrollbar-sm-dark, nc-scrollbar-x-md {
+ .nc-scrollbar-md, .nc-scrollbar-x-md, .nc-scrollbar-dark-md, .nc-scrollbar-x-md-dark, .nc-scrollbar-x-lg {
&::-webkit-scrollbar {
width: 0px;
}
@@ -64,6 +68,7 @@ main {
.nc-scrollbar-md {
overflow-y: scroll;
overflow-x: hidden;
+ scrollbar-width: thin !important;
&::-webkit-scrollbar {
width: 4px;
@@ -77,16 +82,46 @@ main {
}
&::-webkit-scrollbar-thumb {
width: 4px;
+ @apply bg-gray-200;
+ }
+ &::-webkit-scrollbar-thumb:hover {
@apply bg-gray-300;
}
+}
+
+.nc-scrollbar-x-md {
+ overflow-x: scroll;
+ scrollbar-width: thin !important;
+
+
+ &::-webkit-scrollbar {
+ width: 4px;
+ height: 4px;
+ }
+ &::-webkit-scrollbar-track-piece {
+ width: 0px;
+ }
+ &::-webkit-scrollbar {
+ @apply bg-transparent;
+ }
+ &::-webkit-scrollbar-thumb {
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+
+
+ width: 4px;
+ @apply bg-gray-200;
+ }
&::-webkit-scrollbar-thumb:hover {
- @apply bg-gray-400;
+ @apply bg-gray-300;
}
}
.nc-scrollbar-dark-md {
overflow-y: scroll;
overflow-x: hidden;
+ scrollbar-width: thin !important;
+
&::-webkit-scrollbar {
width: 4px;
@@ -115,13 +150,14 @@ main {
}
}
-.nc-scrollbar-sm-dark {
- overflow-y: scroll;
- overflow-x: hidden;
+.nc-scrollbar-x-md-dark {
+ overflow-x: scroll;
+ scrollbar-width: thin !important;
+
&::-webkit-scrollbar {
- width: 2px;
- height: 2px;
+ width: 4px;
+ height: 4px;
}
&::-webkit-scrollbar-track-piece {
width: 0px;
@@ -130,20 +166,26 @@ main {
@apply bg-transparent;
}
&::-webkit-scrollbar-thumb {
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+
+
width: 4px;
- @apply bg-gray-300;
+ background-color: rgba(0, 0, 0, 0.3)
+
}
&::-webkit-scrollbar-thumb:hover {
- @apply bg-gray-400;
+ background-color: rgba(0, 0, 0, 0.4)
+
}
}
-.nc-scrollbar-x-md {
+.nc-scrollbar-x-lg {
overflow-x: scroll;
&::-webkit-scrollbar {
- width: 4px;
- height: 4px;
+ width: 8px;
+ height: 8px;
}
&::-webkit-scrollbar-track-piece {
width: 0px;
@@ -152,7 +194,11 @@ main {
@apply bg-transparent;
}
&::-webkit-scrollbar-thumb {
- width: 4px;
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+
+
+ width: 8px;
@apply bg-gray-200;
}
&::-webkit-scrollbar-thumb:hover {
@@ -160,10 +206,6 @@ main {
}
}
-html {
- overflow-y: auto !important;
-}
-
main {
@apply flex-0 w-full relative scrollbar-thin-dull;
overflow-x: hidden;
@@ -194,7 +236,7 @@ a {
}
.nc-base-menu-item {
- @apply cursor-pointer flex items-center gap-2 py-2 after:(content-[''] absolute top-0 left-0 bottom-0 right-0 w-full h-full bg-current opacity-0 transition transition-opacity duration-100) hover:(after:(opacity-5));
+ @apply cursor-pointer flex items-center gap-2 py-2;
// &:hover {
// .nc-icon {
@@ -437,6 +479,9 @@ a {
.nc-toolbar-btn {
@apply !shadow-none rounded hover:(ring-1 ring-gray-200 ring-opacity-100 bg-gray-100 !text-gray-800) focus:(ring-1 ring-gray-300 ring-opacity-100 !text-gray-800 bg-gray-100) text-gray-600 text-xs font-medium px-2 border-0;
}
+.nc-toolbar-btn[disabled] {
+ @apply !text-gray-400 !cursor-not-allowed !hover:ring-0;
+}
.nc-warning-info {
@apply !shadow-none rounded ring-1 ring-red-600;
@@ -633,3 +678,7 @@ input[type='number'] {
@apply xs:(visible opacity-100 !text-gray-500)
}
}
+
+.ant-message-notice-content {
+ @apply !rounded-md;
+}
\ No newline at end of file
diff --git a/packages/nc-gui/components/account/License.vue b/packages/nc-gui/components/account/License.vue
index e4d780bc17..cfd1f684b4 100644
--- a/packages/nc-gui/components/account/License.vue
+++ b/packages/nc-gui/components/account/License.vue
@@ -1,5 +1,5 @@
diff --git a/packages/nc-gui/components/cell/Json.vue b/packages/nc-gui/components/cell/Json.vue
index 18326296f9..b4eaf90dac 100644
--- a/packages/nc-gui/components/cell/Json.vue
+++ b/packages/nc-gui/components/cell/Json.vue
@@ -72,13 +72,7 @@ const clear = () => {
const formatJson = (json: string) => {
try {
- json = json
- .trim()
- .replace(/^\{\s*|\s*\}$/g, '')
- .replace(/\n\s*/g, '')
- json = `{${json}}`
-
- return json
+ return JSON.stringify(JSON.parse(json))
} catch (e) {
console.log(e)
return json
diff --git a/packages/nc-gui/components/cell/MultiSelect.vue b/packages/nc-gui/components/cell/MultiSelect.vue
index 7a64c35a1a..d8fe577a99 100644
--- a/packages/nc-gui/components/cell/MultiSelect.vue
+++ b/packages/nc-gui/components/cell/MultiSelect.vue
@@ -144,14 +144,14 @@ const selectedTitles = computed(() =>
}
return 0
})
- : modelValue.split(',').map((el) => el.trim())
- : modelValue.map((el) => el.trim())
+ : modelValue.split(',')
+ : modelValue
: [],
)
onMounted(() => {
selectedIds.value = selectedTitles.value.flatMap((el) => {
- const item = options.value.find((op) => op.title === el)
+ const item = options.value.find((op) => op.title === el || op.title === el?.trim())
const itemIdOrTitle = item?.id || item?.title
if (itemIdOrTitle) {
return [itemIdOrTitle]
@@ -165,7 +165,7 @@ watch(
() => modelValue,
() => {
selectedIds.value = selectedTitles.value.flatMap((el) => {
- const item = options.value.find((op) => op.title === el)
+ const item = options.value.find((op) => op.title === el || op.title === el?.trim())
if (item && (item.id || item.title)) {
return [(item.id || item.title)!]
}
diff --git a/packages/nc-gui/components/cell/Percent.vue b/packages/nc-gui/components/cell/Percent.vue
index 55b6188e56..29c2efe3f9 100644
--- a/packages/nc-gui/components/cell/Percent.vue
+++ b/packages/nc-gui/components/cell/Percent.vue
@@ -49,8 +49,6 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
@keydown.right.stop
@keydown.up.stop
@keydown.delete.stop
- @keydown.ctrl.z.stop
- @keydown.meta.z.stop
@selectstart.capture.stop
@mousedown.stop
/>
diff --git a/packages/nc-gui/components/cell/PhoneNumber.vue b/packages/nc-gui/components/cell/PhoneNumber.vue
index 24c3c8ef36..6a74c90caa 100644
--- a/packages/nc-gui/components/cell/PhoneNumber.vue
+++ b/packages/nc-gui/components/cell/PhoneNumber.vue
@@ -70,15 +70,19 @@ watch(
@keydown.right.stop
@keydown.up.stop
@keydown.delete.stop
- @keydown.ctrl.z.stop
- @keydown.meta.z.stop
@selectstart.capture.stop
@mousedown.stop
/>
{{ $t('general.null') }}
-
+
diff --git a/packages/nc-gui/components/cell/SingleSelect.vue b/packages/nc-gui/components/cell/SingleSelect.vue
index 6d132cf0b0..0afc0e4c4b 100644
--- a/packages/nc-gui/components/cell/SingleSelect.vue
+++ b/packages/nc-gui/components/cell/SingleSelect.vue
@@ -104,7 +104,7 @@ const hasEditRoles = computed(() => isUIAllowed('dataEdit'))
const editAllowed = computed(() => (hasEditRoles.value || isForm.value) && active.value)
const vModel = computed({
- get: () => tempSelectedOptState.value ?? modelValue?.trim(),
+ get: () => tempSelectedOptState.value ?? modelValue,
set: (val) => {
if (val && isNewOptionCreateEnabled.value && (options.value ?? []).every((op) => op.title !== val)) {
tempSelectedOptState.value = val
@@ -259,7 +259,7 @@ const handleClose = (e: MouseEvent) => {
useEventListener(document, 'click', handleClose, true)
const selectedOpt = computed(() => {
- return options.value.find((o) => o.value === vModel.value)
+ return options.value.find((o) => o.value === vModel.value || o.value === vModel.value?.trim())
})
diff --git a/packages/nc-gui/components/cell/Text.vue b/packages/nc-gui/components/cell/Text.vue
index e3ec8698a6..2e4f6f2cde 100644
--- a/packages/nc-gui/components/cell/Text.vue
+++ b/packages/nc-gui/components/cell/Text.vue
@@ -43,8 +43,6 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
@keydown.right.stop
@keydown.up.stop
@keydown.delete.stop
- @keydown.ctrl.z.stop
- @keydown.meta.z.stop
@selectstart.capture.stop
@mousedown.stop
/>
diff --git a/packages/nc-gui/components/cell/TextArea.vue b/packages/nc-gui/components/cell/TextArea.vue
index 6cf430bd22..90581d7e85 100644
--- a/packages/nc-gui/components/cell/TextArea.vue
+++ b/packages/nc-gui/components/cell/TextArea.vue
@@ -16,6 +16,7 @@ import {
const props = defineProps<{
modelValue?: string | number
isFocus?: boolean
+ virtual?: boolean
}>()
const emits = defineEmits(['update:modelValue'])
@@ -65,6 +66,13 @@ onClickOutside(inputWrapperRef, (e) => {
isVisible.value = false
})
+
+const onDblClick = () => {
+ if (!props.virtual) return
+
+ isVisible.value = true
+ editEnabled.value = true
+}
@@ -100,15 +108,23 @@ onClickOutside(inputWrapperRef, (e) => {
@keydown.right.stop
@keydown.up.stop
@keydown.delete.stop
- @keydown.ctrl.z.stop
- @keydown.meta.z.stop
@selectstart.capture.stop
@mousedown.stop
/>
{{ $t('general.null') }}
-
+
{{ vModel }}
@@ -142,7 +158,7 @@ onClickOutside(inputWrapperRef, (e) => {
{
}
})
+const isOpen = computed(() => {
+ if (readOnly.value) return false
+
+ return (readOnly.value || (localState.value && isPk)) && !active.value && !editable.value ? false : open.value
+})
+
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) {
case 'Enter':
@@ -129,7 +135,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
:placeholder="placeholder"
:allow-clear="!readOnly && !localState && !isPk"
:input-read-only="true"
- :open="(readOnly || (localState && isPk)) && !active && !editable ? false : open"
+ :open="isOpen"
:popup-class-name="`${randomClass} nc-picker-time ${open ? 'active' : ''}`"
@click="open = (active || editable) && !open"
@ok="open = !open"
diff --git a/packages/nc-gui/components/cell/Url.vue b/packages/nc-gui/components/cell/Url.vue
index cd6d15077e..71f7c57563 100644
--- a/packages/nc-gui/components/cell/Url.vue
+++ b/packages/nc-gui/components/cell/Url.vue
@@ -99,8 +99,6 @@ watch(
@keydown.right.stop
@keydown.up.stop
@keydown.delete.stop
- @keydown.ctrl.z.stop
- @keydown.meta.z.stop
@selectstart.capture.stop
@mousedown.stop
/>
diff --git a/packages/nc-gui/components/cell/YearPicker.vue b/packages/nc-gui/components/cell/YearPicker.vue
index b2204fff9f..f5c5c80709 100644
--- a/packages/nc-gui/components/cell/YearPicker.vue
+++ b/packages/nc-gui/components/cell/YearPicker.vue
@@ -88,6 +88,12 @@ const placeholder = computed(() => {
}
})
+const isOpen = computed(() => {
+ if (readOnly.value) return false
+
+ return (readOnly.value || (localState.value && isPk)) && !active.value && !editable.value ? false : open.value
+})
+
useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
switch (e.key) {
case 'Enter':
@@ -114,7 +120,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
:placeholder="placeholder"
:allow-clear="(!readOnly && !localState && !isPk) || isEditColumn"
:input-read-only="true"
- :open="(readOnly || (localState && isPk)) && !active && !editable ? false : open"
+ :open="isOpen"
:dropdown-class-name="`${randomClass} nc-picker-year ${open ? 'active' : ''}`"
@click="open = (active || editable) && !open"
@change="open = (active || editable) && !open"
diff --git a/packages/nc-gui/components/cell/attachment/RenameFile.vue b/packages/nc-gui/components/cell/attachment/RenameFile.vue
index 0eb8289627..ed50dfb5e8 100644
--- a/packages/nc-gui/components/cell/attachment/RenameFile.vue
+++ b/packages/nc-gui/components/cell/attachment/RenameFile.vue
@@ -1,5 +1,5 @@
-
+
@@ -151,3 +167,17 @@ async function onOpenModal({
@apply text-brand-400;
}
+
+
diff --git a/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue b/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
index 49bb750e80..d4d340b142 100644
--- a/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
+++ b/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
@@ -26,6 +26,7 @@ import {
useDialog,
useGlobal,
useI18n,
+ useNuxtApp,
useRoles,
useRouter,
useTablesStore,
@@ -33,7 +34,6 @@ import {
useToggle,
} from '#imports'
import type { NcProject } from '#imports'
-import { useNuxtApp } from '#app'
const indicator = h(LoadingOutlined, {
class: '!text-gray-400',
@@ -514,7 +514,7 @@ const projectDelete = () => {
@click.stop="
() => {
$e('c:base:api-docs')
- openLink(`/api/v1/db/meta/projects/${base.id}/swagger`, appInfo.ncSiteUrl)
+ openLink(`/api/v2/meta/bases/${base.id}/swagger`, appInfo.ncSiteUrl)
}
"
>
diff --git a/packages/nc-gui/components/dashboard/TreeView/TableList.vue b/packages/nc-gui/components/dashboard/TreeView/TableList.vue
index 6ece28f941..88bb45c3c8 100644
--- a/packages/nc-gui/components/dashboard/TreeView/TableList.vue
+++ b/packages/nc-gui/components/dashboard/TreeView/TableList.vue
@@ -3,8 +3,7 @@ import type { BaseType, TableType } from 'nocodb-sdk'
import { storeToRefs } from 'pinia'
import Sortable from 'sortablejs'
import TableNode from './TableNode.vue'
-import { useNuxtApp } from '#app'
-import { toRef } from '#imports'
+import { toRef, useNuxtApp } from '#imports'
const props = withDefaults(
defineProps<{
diff --git a/packages/nc-gui/components/dashboard/TreeView/TableNode.vue b/packages/nc-gui/components/dashboard/TreeView/TableNode.vue
index 35e2e65bc4..6b45508c38 100644
--- a/packages/nc-gui/components/dashboard/TreeView/TableNode.vue
+++ b/packages/nc-gui/components/dashboard/TreeView/TableNode.vue
@@ -4,8 +4,7 @@ import { toRef } from '@vue/reactivity'
import { message } from 'ant-design-vue'
import { storeToRefs } from 'pinia'
-import { useNuxtApp } from '#app'
-import { ProjectRoleInj, TreeViewInj, useRoles, useTabs } from '#imports'
+import { ProjectRoleInj, TreeViewInj, useNuxtApp, useRoles, useTabs } from '#imports'
const props = withDefaults(
defineProps<{
@@ -169,7 +168,6 @@ const isTableOpened = computed(() => {
>
{
:class="{ '!rotate-180': isExpanded }"
/>
-
{
+ const source = base.value?.sources?.find((b) => b.id === table.value.source_id)
+ if (!source) return false
+
+ return isDefaultBase(source)
+})
+
/** validate view title */
function validate(view: ViewType) {
if (!view.title || view.title.trim().length < 0) {
@@ -367,31 +379,62 @@ function onOpenModal({
diff --git a/packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue b/packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue
index 0b417de825..57c5fb1afb 100644
--- a/packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue
+++ b/packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue
@@ -1,6 +1,6 @@
@@ -234,7 +233,7 @@ function onStopEdit() {
-
+
diff --git a/packages/nc-gui/components/dashboard/settings/Erd.vue b/packages/nc-gui/components/dashboard/settings/Erd.vue
index 547a9a138f..3a7cef91fb 100644
--- a/packages/nc-gui/components/dashboard/settings/Erd.vue
+++ b/packages/nc-gui/components/dashboard/settings/Erd.vue
@@ -1,11 +1,12 @@
-
+
diff --git a/packages/nc-gui/components/dashboard/settings/UIAcl.vue b/packages/nc-gui/components/dashboard/settings/UIAcl.vue
index 5321768132..a48c20372a 100644
--- a/packages/nc-gui/components/dashboard/settings/UIAcl.vue
+++ b/packages/nc-gui/components/dashboard/settings/UIAcl.vue
@@ -16,6 +16,8 @@ import {
useNuxtApp,
} from '#imports'
+type Role = 'editor' | 'commenter' | 'viewer'
+
const props = defineProps<{
sourceId: string
}>()
@@ -39,6 +41,12 @@ const tables = ref
([])
const searchInput = ref('')
+const selectAll = ref({
+ editor: false,
+ commenter: false,
+ viewer: false,
+})
+
const filteredTables = computed(() =>
tables.value.filter(
(el) =>
@@ -80,15 +88,21 @@ async function saveUIAcl() {
$e('a:proj-meta:ui-acl')
}
-const onRoleCheck = (record: any, role: string) => {
+const onRoleCheck = (record: any, role: Role) => {
record.disabled[role] = !record.disabled[role]
record.edited = true
+
+ selectAll.value[role as Role] = filteredTables.value.every((t) => !t.disabled[role])
}
onMounted(async () => {
if (tables.value.length === 0) {
await loadTableList()
}
+
+ for (const role of roles.value) {
+ selectAll.value[role as Role] = filteredTables.value.every((t) => !t.disabled[role])
+ }
})
const tableHeaderRenderer = (label: string) => () => h('div', { class: 'text-gray-500' }, label)
@@ -96,11 +110,11 @@ const tableHeaderRenderer = (label: string) => () => h('div', { class: 'text-gra
const columns = [
{
title: tableHeaderRenderer(t('labels.tableName')),
- name: 'table_name',
+ name: 'Table Name',
},
{
title: tableHeaderRenderer(t('labels.viewName')),
- name: 'view_name',
+ name: 'View Name',
},
{
title: tableHeaderRenderer(t('objects.roleType.editor')),
@@ -118,6 +132,16 @@ const columns = [
width: 120,
},
]
+
+const toggleSelectAll = (role: Role) => {
+ selectAll.value[role] = !selectAll.value[role]
+ const enabled = selectAll.value[role]
+
+ filteredTables.value.forEach((t) => {
+ t.disabled[role] = !enabled
+ t.edited = true
+ })
+}
@@ -163,12 +187,23 @@ const columns = [
})
"
>
+
+
+
+
toggleSelectAll(column.name)" />
+
+ {{ column.name }}
+
+
+
+ {{ column.name }}
+
-
+
@@ -179,7 +214,7 @@ const columns = [
-
+
@@ -202,10 +237,10 @@ const columns = [
>
-
diff --git a/packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue b/packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
index 3b73ef38a1..a62f6b0940 100644
--- a/packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
+++ b/packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
@@ -66,8 +66,8 @@ const formState = ref
({
title: '',
dataSource: { ...getDefaultConnectionConfig(ClientType.MYSQL) },
inflection: {
- inflectionColumn: 'camelize',
- inflectionTable: 'camelize',
+ inflectionColumn: 'none',
+ inflectionTable: 'none',
},
sslUse: SSLUsage.No,
extraParameters: [],
@@ -77,8 +77,8 @@ const customFormState = ref({
title: '',
dataSource: { ...getDefaultConnectionConfig(ClientType.MYSQL) },
inflection: {
- inflectionColumn: 'camelize',
- inflectionTable: 'camelize',
+ inflectionColumn: 'none',
+ inflectionTable: 'none',
},
sslUse: SSLUsage.No,
extraParameters: [],
@@ -126,7 +126,7 @@ const validators = computed(() => {
'title': [
{
required: true,
- message: 'Source name is required',
+ message: t('labels.sourceNameRequired'),
},
baseTitleValidator,
],
@@ -614,7 +614,6 @@ const toggleModal = (val: boolean) => {
-
({
title: '',
dataSource: { ...getDefaultConnectionConfig(ClientType.MYSQL) },
inflection: {
- inflectionColumn: 'camelize',
- inflectionTable: 'camelize',
+ inflectionColumn: 'none',
+ inflectionTable: 'none',
},
sslUse: SSLUsage.No,
extraParameters: [],
@@ -72,8 +72,8 @@ const customFormState = ref({
title: '',
dataSource: { ...getDefaultConnectionConfig(ClientType.MYSQL) },
inflection: {
- inflectionColumn: 'camelize',
- inflectionTable: 'camelize',
+ inflectionColumn: 'none',
+ inflectionTable: 'none',
},
sslUse: SSLUsage.No,
extraParameters: [],
diff --git a/packages/nc-gui/components/dlg/AirtableImport.vue b/packages/nc-gui/components/dlg/AirtableImport.vue
index 1358a7438c..9b8ec341eb 100644
--- a/packages/nc-gui/components/dlg/AirtableImport.vue
+++ b/packages/nc-gui/components/dlg/AirtableImport.vue
@@ -90,8 +90,10 @@ const onStatus = async (status: JobStatus, data?: any) => {
refreshCommandPalette()
// TODO: add tab of the first table
} else if (status === JobStatus.FAILED) {
+ await loadTables()
goBack.value = true
pushProgress(data.error.message, status)
+ refreshCommandPalette()
}
}
@@ -115,7 +117,10 @@ const { validateInfos } = useForm(syncSource, validators)
const disableImportButton = computed(() => !syncSource.value.details.apiKey || !syncSource.value.details.syncSourceUrlOrId)
+const isLoading = ref(false)
+
async function saveAndSync() {
+ isLoading.value = true
await createOrUpdate()
await sync()
}
@@ -178,6 +183,7 @@ async function listenForUpdates(id?: string) {
}
} else {
listeningForUpdates.value = false
+ isLoading.value = false
}
},
)
@@ -309,6 +315,9 @@ onMounted(async () => {
{
{{ $t('general.credentials') }}
{{ $t('msg.info.airtable.credentials') }}
@@ -346,7 +356,7 @@ onMounted(async () => {
@@ -411,7 +421,7 @@ onMounted(async () => {
-
+
{{ $t('general.questions') }} / {{ $t('general.help') }} - {{ $t('general.reachOut') }}
@@ -419,7 +429,12 @@ onMounted(async () => {
{{ $t('general.betaNote') }}
-
+
{{ $t('general.moreInfo') }}
.
@@ -485,6 +500,7 @@ onMounted(async () => {
v-e="['c:sync-airtable:save-and-sync']"
type="primary"
class="nc-btn-airtable-import"
+ :loading="isLoading"
:disabled="disableImportButton"
@click="saveAndSync"
>
diff --git a/packages/nc-gui/components/dlg/ColumnDuplicate.vue b/packages/nc-gui/components/dlg/ColumnDuplicate.vue
new file mode 100644
index 0000000000..2c87a60ca0
--- /dev/null
+++ b/packages/nc-gui/components/dlg/ColumnDuplicate.vue
@@ -0,0 +1,136 @@
+
+
+
+
+
+
{{ $t('general.duplicate') }} {{ $t('objects.column') }}
+
+
Are you sure you want to duplicate the field?
+
+
{{ $t('title.advancedSettings') }}
+
+
+
+
+
{{ $t('labels.includeData') }}
+
+
+
+ {{ $t('general.cancel') }}
+ {{ $t('general.confirm') }}
+
+
+
diff --git a/packages/nc-gui/components/dlg/KeyboardShortcuts.vue b/packages/nc-gui/components/dlg/KeyboardShortcuts.vue
index d3cf53b122..cc9ec08a89 100644
--- a/packages/nc-gui/components/dlg/KeyboardShortcuts.vue
+++ b/packages/nc-gui/components/dlg/KeyboardShortcuts.vue
@@ -52,6 +52,22 @@ const shortcutList = [
{
title: 'Grid View',
shortcuts: [
+ {
+ keys: [renderAltOrOptlKey(), '←'],
+ behaviour: 'Jump to previous page in this view',
+ },
+ {
+ keys: [renderAltOrOptlKey(), '→'],
+ behaviour: 'Jump to next page in this view',
+ },
+ {
+ keys: [renderAltOrOptlKey(), '↑'],
+ behaviour: 'Jump to last page in this view',
+ },
+ {
+ keys: [renderAltOrOptlKey(), '↓'],
+ behaviour: 'Jump to first page in this view',
+ },
{
keys: [renderCmdOrCtrlKey(), '←'],
behaviour: 'Jump to leftmost column in this row',
diff --git a/packages/nc-gui/components/dlg/ProjectErd.vue b/packages/nc-gui/components/dlg/ProjectErd.vue
index 5c18099f25..aeac8bc522 100644
--- a/packages/nc-gui/components/dlg/ProjectErd.vue
+++ b/packages/nc-gui/components/dlg/ProjectErd.vue
@@ -46,7 +46,7 @@ onMounted(async () => {
-
+
diff --git a/packages/nc-gui/components/dlg/QuickImport.vue b/packages/nc-gui/components/dlg/QuickImport.vue
index b60ddacd00..6d98a74341 100644
--- a/packages/nc-gui/components/dlg/QuickImport.vue
+++ b/packages/nc-gui/components/dlg/QuickImport.vue
@@ -31,12 +31,12 @@ import {
useBase,
useGlobal,
useI18n,
+ useNuxtApp,
useVModel,
} from '#imports'
// import worker script according to the doc of Vite
import importWorkerUrl from '~/workers/importWorker?worker&url'
-import { useNuxtApp } from '#app'
interface Props {
modelValue: boolean
@@ -172,7 +172,9 @@ const disablePreImportButton = computed(() => {
}
})
-const disableImportButton = computed(() => !templateEditorRef.value?.isValid)
+const isError = ref(false)
+
+const disableImportButton = computed(() => !templateEditorRef.value?.isValid || isError.value)
const disableFormatJsonButton = computed(() => !jsonEditorRef.value?.isValid)
@@ -530,6 +532,14 @@ async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
preImportLoading.value = false
}
}
+
+const onError = () => {
+ isError.value = true
+}
+
+const onChange = () => {
+ isError.value = false
+}
@@ -558,6 +568,8 @@ async function parseAndExtractData(val: UploadFile[] | ArrayBuffer | string) {
:import-worker="importWorker"
class="nc-quick-import-template-editor"
@import="handleImport"
+ @error="onError"
+ @change="onChange"
/>
diff --git a/packages/nc-gui/components/dlg/ViewCreate.vue b/packages/nc-gui/components/dlg/ViewCreate.vue
index 2c8307bdd1..524c1d66f0 100644
--- a/packages/nc-gui/components/dlg/ViewCreate.vue
+++ b/packages/nc-gui/components/dlg/ViewCreate.vue
@@ -110,11 +110,11 @@ watch(
)
function init() {
- form.title = `Untitled ${capitalize(typeAlias.value)}`
+ form.title = `${capitalize(typeAlias.value)}`
const repeatCount = views.value.filter((v) => v.title.startsWith(form.title)).length
if (repeatCount) {
- form.title = `${form.title} ${repeatCount}`
+ form.title = `${form.title}-${repeatCount}`
}
if (selectedViewId.value) {
@@ -308,7 +308,7 @@ onMounted(async () => {
{
v-model:value="form.fk_geo_data_col_id"
class="w-full"
:options="viewSelectFieldOptions"
- :disabled="groupingFieldColumnId || isMetaLoading"
+ :disabled="isMetaLoading"
:loading="isMetaLoading"
:placeholder="$t('placeholder.selectGeoField')"
:not-found-content="$t('placeholder.selectGeoFieldNotFound')"
diff --git a/packages/nc-gui/components/dlg/share-and-collaborate/Collaborate.vue b/packages/nc-gui/components/dlg/share-and-collaborate/Collaborate.vue
index aa149774f2..863c8fabe4 100644
--- a/packages/nc-gui/components/dlg/share-and-collaborate/Collaborate.vue
+++ b/packages/nc-gui/components/dlg/share-and-collaborate/Collaborate.vue
@@ -58,7 +58,7 @@ watch(
v-bind="validateInfos.emails"
validate-trigger="onBlur"
name="emails"
- :rules="[{ required: true, message: 'Please input email' }]"
+ :rules="[{ required: true, message: t('msg.plsInputEmail') }]"
>
-
{{ $t('activity.surveyMode') }}
-
-
{{ $t('activity.rtlOrientation') }}
-
-
{{ $t('activity.useTheme') }}
{
>
- Copied invite link
+ {{ $t('activity.copiedInviteLink') }}
- Copy invite link
+ {{ $t('activity.copyInviteLink') }}
diff --git a/packages/nc-gui/components/erd/View.vue b/packages/nc-gui/components/erd/View.vue
index bb99dfcc4a..69bee40db7 100644
--- a/packages/nc-gui/components/erd/View.vue
+++ b/packages/nc-gui/components/erd/View.vue
@@ -59,11 +59,14 @@ const loadMetaOfTablesNotInMetas = async (localTables: TableType[]) => {
const populateTables = async () => {
let localTables: TableType[] = []
if (props.table) {
+ // use getMeta method to load meta since it will get meta if not loaded already
+ const tableMeta = await getMeta(props.table!.id!)
+
// if table is provided only get the table and its related tables
localTables = baseTables.value.filter(
(t) =>
t.id === props.table?.id ||
- metas.value[props.table!.id!].columns?.find((column) => {
+ tableMeta.columns?.find((column) => {
return isLinksOrLTAR(column.uidt) && (column.colOptions as LinkToAnotherRecordType)?.fk_related_model_id === t.id
}),
)
diff --git a/packages/nc-gui/components/general/CopyUrl.vue b/packages/nc-gui/components/general/CopyUrl.vue
index 43d516a4db..c51f6a037e 100644
--- a/packages/nc-gui/components/general/CopyUrl.vue
+++ b/packages/nc-gui/components/general/CopyUrl.vue
@@ -13,7 +13,7 @@ const isCopied = ref({
})
const openUrl = async () => {
- window.open(url.value, '_blank')
+ window.open(url.value, '_blank', 'noopener,noreferrer')
}
const embedHtml = async () => {
@@ -40,18 +40,29 @@ const copyUrl = async () => {
{{ url }}
-
-
-
-
-
-
+
+
+ {{ $t('activity.openInANewTab') }}
+
+
+
+
+
+
+
+
+ {{ $t('activity.copyIFrameCode') }}
+
+
+
+
+