diff --git a/packages/nc-gui/components/api-client/Headers.vue b/packages/nc-gui/components/api-client/Headers.vue
index ad4f826526..9b58046552 100644
--- a/packages/nc-gui/components/api-client/Headers.vue
+++ b/packages/nc-gui/components/api-client/Headers.vue
@@ -66,10 +66,10 @@ const filterOption = (input: string, option: Option) => option.value.toUpperCase
|
- Header Name
+ {{ $t('labels.headerName') }}
|
- Value
+ {{ $t('placeholder.value') }}
|
|
@@ -87,9 +87,9 @@ const filterOption = (input: string, option: Option) => option.value.toUpperCase
@@ -97,7 +97,11 @@ const filterOption = (input: string, option: Option) => option.value.toUpperCase
-
+
|
@@ -120,7 +124,7 @@ const filterOption = (input: string, option: Option) => option.value.toUpperCase
- Add Header
+ {{ $t('labels.addHeader') }}
diff --git a/packages/nc-gui/components/api-client/Params.vue b/packages/nc-gui/components/api-client/Params.vue
index 61aa3f1f2c..66a2e79336 100644
--- a/packages/nc-gui/components/api-client/Params.vue
+++ b/packages/nc-gui/components/api-client/Params.vue
@@ -24,11 +24,11 @@ const deleteParamRow = (i: number) => {
- Parameter Name
+ {{ $t('title.parameterName') }}
|
- Value
+ {{ $t('placeholder.value') }}
|
@@ -41,13 +41,13 @@ const deleteParamRow = (i: number) => {
|
-
+
|
-
+
|
@@ -69,7 +69,7 @@ const deleteParamRow = (i: number) => {
- Add Parameter
+ {{ $t('activity.addParameter') }}
diff --git a/packages/nc-gui/components/cell/Checkbox.vue b/packages/nc-gui/components/cell/Checkbox.vue
index 5445c3e983..c0bdd2fe73 100644
--- a/packages/nc-gui/components/cell/Checkbox.vue
+++ b/packages/nc-gui/components/cell/Checkbox.vue
@@ -8,7 +8,7 @@ import {
getMdiIcon,
inject,
parseProp,
- useProject,
+ useBase,
useSelectedCellKeyupListener,
} from '#imports'
@@ -28,7 +28,7 @@ const emits = defineEmits()
const active = inject(ActiveCellInj, ref(false))
-const { isMssql } = useProject()
+const { isMssql } = useBase()
const column = inject(ColumnInj)
@@ -53,7 +53,7 @@ const checkboxMeta = computed(() => {
const vModel = computed({
get: () => !!props.modelValue && props.modelValue !== '0' && props.modelValue !== 0 && props.modelValue !== 'false',
- set: (val: any) => emits('update:modelValue', isMssql(column?.value?.base_id) ? +val : val),
+ set: (val: any) => emits('update:modelValue', isMssql(column?.value?.source_id) ? +val : val),
})
function onClick(force?: boolean, event?: MouseEvent) {
@@ -80,7 +80,7 @@ useSelectedCellKeyupListener(active, (e) => {
-
diff --git a/packages/nc-gui/components/cell/Currency.vue b/packages/nc-gui/components/cell/Currency.vue
index fc67d9274a..1b5d363969 100644
--- a/packages/nc-gui/components/cell/Currency.vue
+++ b/packages/nc-gui/components/cell/Currency.vue
@@ -79,7 +79,7 @@ onMounted(() => {
v-model="vModel"
type="number"
class="w-full h-full text-sm border-none rounded-md outline-none"
- :placeholder="isEditColumn ? '(Optional)' : ''"
+ :placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="submitCurrency"
@keydown.down.stop
@keydown.left.stop
@@ -93,7 +93,7 @@ onMounted(() => {
@contextmenu.stop
/>
- NULL
+ {{ $t('general.null') }}
{{ currency }}
diff --git a/packages/nc-gui/components/cell/DatePicker.vue b/packages/nc-gui/components/cell/DatePicker.vue
index 991fa52903..e1f2f79433 100644
--- a/packages/nc-gui/components/cell/DatePicker.vue
+++ b/packages/nc-gui/components/cell/DatePicker.vue
@@ -25,6 +25,8 @@ const { modelValue, isPk } = defineProps ()
const emit = defineEmits(['update:modelValue'])
+const { t } = useI18n()
+
const { showNull } = useGlobal()
const columnMeta = inject(ColumnInj, null)!
@@ -84,11 +86,11 @@ watch(
const placeholder = computed(() => {
if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
- return '(Optional)'
+ return t('labels.optional')
} else if (modelValue === null && showNull.value) {
- return 'NULL'
+ return t('general.null')
} else if (isDateInvalid.value) {
- return 'Invalid date'
+ return t('msg.invalidDate')
} else {
return ''
}
diff --git a/packages/nc-gui/components/cell/DateTimePicker.vue b/packages/nc-gui/components/cell/DateTimePicker.vue
index c0a3960d23..984806908e 100644
--- a/packages/nc-gui/components/cell/DateTimePicker.vue
+++ b/packages/nc-gui/components/cell/DateTimePicker.vue
@@ -13,7 +13,7 @@ import {
parseProp,
ref,
timeFormats,
- useProject,
+ useBase,
useSelectedCellKeyupListener,
watch,
} from '#imports'
@@ -28,7 +28,7 @@ const { modelValue, isPk, isUpdatedFromCopyNPaste } = defineProps()
const emit = defineEmits(['update:modelValue'])
-const { isMssql, isXcdbBase } = useProject()
+const { isMssql, isXcdbBase } = useBase()
const { showNull } = useGlobal()
@@ -40,6 +40,8 @@ const editable = inject(EditModeInj, ref(false))
const isLockedMode = inject(IsLockedInj, ref(false))
+const { t } = useI18n()
+
const isEditColumn = inject(EditColumnInj, ref(false))
const column = inject(ColumnInj)!
@@ -65,7 +67,7 @@ const localState = computed({
return undefined
}
- const isXcDB = isXcdbBase(column.value.base_id)
+ const isXcDB = isXcdbBase(column.value.source_id)
// cater copy and paste
// when copying a datetime cell, the copied value would be local time
@@ -81,7 +83,7 @@ const localState = computed({
return /^\d+$/.test(modelValue) ? dayjs(+modelValue) : dayjs(modelValue)
}
- if (isMssql(column.value.base_id)) {
+ if (isMssql(column.value.source_id)) {
// e.g. 2023-04-29T11:41:53.000Z
return dayjs(modelValue)
}
@@ -137,11 +139,11 @@ watch(
const placeholder = computed(() => {
if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
- return '(Optional)'
+ return t('labels.optional')
} else if (modelValue === null && showNull.value) {
- return 'NULL'
+ return t('general.null')
} else if (isDateInvalid.value) {
- return 'Invalid date'
+ return t('msg.invalidDate')
} else {
return ''
}
diff --git a/packages/nc-gui/components/cell/Decimal.vue b/packages/nc-gui/components/cell/Decimal.vue
index 547f14a227..80a7e9fc01 100644
--- a/packages/nc-gui/components/cell/Decimal.vue
+++ b/packages/nc-gui/components/cell/Decimal.vue
@@ -97,7 +97,7 @@ watch(isExpandedFormOpen, () => {
class="outline-none !py-2 !px-1 border-none rounded-md w-full h-full !text-sm"
type="number"
:step="precision"
- :placeholder="isEditColumn ? '(Optional)' : ''"
+ :placeholder="isEditColumn ? $t('labels.optional') : ''"
style="letter-spacing: 0.06rem"
@blur="editEnabled = false"
@keydown.down.stop="onKeyDown"
@@ -110,7 +110,7 @@ watch(isExpandedFormOpen, () => {
@selectstart.capture.stop
@mousedown.stop
/>
- NULL
+ {{ $t('general.null') }}
{{ displayValue }}
diff --git a/packages/nc-gui/components/cell/Duration.vue b/packages/nc-gui/components/cell/Duration.vue
index 34cc923bf1..c3bc7bb3ea 100644
--- a/packages/nc-gui/components/cell/Duration.vue
+++ b/packages/nc-gui/components/cell/Duration.vue
@@ -23,6 +23,8 @@ const { modelValue, showValidationError = true } = defineProps()
const emit = defineEmits(['update:modelValue'])
+const { t } = useI18n()
+
const { showNull } = useGlobal()
const column = inject(ColumnInj)
@@ -39,7 +41,9 @@ const isEditColumn = inject(EditColumnInj, ref(false))
const durationType = computed(() => parseProp(column?.value?.meta)?.duration || 0)
-const durationPlaceholder = computed(() => (isEditColumn.value ? '(Optional)' : durationOptions[durationType.value].title))
+const durationPlaceholder = computed(() =>
+ isEditColumn.value ? `(${t('labels.optional')})` : durationOptions[durationType.value].title,
+)
const localState = computed({
get: () => convertMS2Duration(modelValue, durationType.value),
@@ -105,13 +109,12 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
@mousedown.stop
/>
- NULL
+ {{ $t('general.null') }}
{{ localState }}
-
- Please enter a number
+ {{ $t('msg.plsEnterANumber') }}
diff --git a/packages/nc-gui/components/cell/Email.vue b/packages/nc-gui/components/cell/Email.vue
index 434dd0a1fc..385fecb852 100644
--- a/packages/nc-gui/components/cell/Email.vue
+++ b/packages/nc-gui/components/cell/Email.vue
@@ -71,7 +71,7 @@ watch(
:ref="focus"
v-model="vModel"
class="w-full outline-none text-sm px-1 py-2"
- :placeholder="isEditColumn ? '(Optional)' : ''"
+ :placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="editEnabled = false"
@keydown.down.stop
@keydown.left.stop
@@ -84,7 +84,7 @@ watch(
@mousedown.stop
/>
- NULL
+ {{ $t('general.null') }}
!isExpandedFormOpen.value && !isEditColumn.value
class="outline-none px-1 border-none w-full h-full text-sm"
type="number"
step="0.1"
- :placeholder="isEditColumn ? '(Optional)' : ''"
+ :placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="editEnabled = false"
@keydown.down.stop
@keydown.left.stop
@@ -63,7 +63,7 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
@selectstart.capture.stop
@mousedown.stop
/>
- NULL
+ {{ $t('general.null') }}
{{ vModel }}
diff --git a/packages/nc-gui/components/cell/Integer.vue b/packages/nc-gui/components/cell/Integer.vue
index 477232670a..5c0bf8944a 100644
--- a/packages/nc-gui/components/cell/Integer.vue
+++ b/packages/nc-gui/components/cell/Integer.vue
@@ -87,8 +87,11 @@ function onKeyDown(e: any) {
v-model="vModel"
class="outline-none py-2 px-1 border-none w-full h-full text-sm"
type="number"
+ :class="{
+ 'pl-2': isExpandedFormOpen,
+ }"
style="letter-spacing: 0.06rem"
- :placeholder="isEditColumn ? '(Optional)' : ''"
+ :placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="editEnabled = false"
@keydown="onKeyDown"
@keydown.down.stop
@@ -99,7 +102,7 @@ function onKeyDown(e: any) {
@selectstart.capture.stop
@mousedown.stop
/>
- NULL
+ {{ $t('general.null') }}
{{ displayValue }}
diff --git a/packages/nc-gui/components/cell/Json.vue b/packages/nc-gui/components/cell/Json.vue
index 1206b4b5b2..fa28ad10a0 100644
--- a/packages/nc-gui/components/cell/Json.vue
+++ b/packages/nc-gui/components/cell/Json.vue
@@ -1,9 +1,10 @@
-
+
@@ -148,11 +149,13 @@ useSelectedCellKeyupListener(active, (e) => {
-
- Cancel
+
+ {{ $t('general.cancel') }}
- Save
+ {{ $t('general.save') }}
@@ -171,7 +174,7 @@ useSelectedCellKeyupListener(active, (e) => {
- NULL
+ {{ $t('general.null') }}
{{ vModel }}
diff --git a/packages/nc-gui/components/cell/MultiSelect.vue b/packages/nc-gui/components/cell/MultiSelect.vue
index ae68ab7aad..a93eebd236 100644
--- a/packages/nc-gui/components/cell/MultiSelect.vue
+++ b/packages/nc-gui/components/cell/MultiSelect.vue
@@ -23,9 +23,9 @@ import {
onMounted,
reactive,
ref,
+ useBase,
useEventListener,
useMetas,
- useProject,
useRoles,
useSelectedCellKeyupListener,
watch,
@@ -81,7 +81,7 @@ const { getMeta } = useMetas()
const { isUIAllowed } = useRoles()
-const { isPg, isMysql } = useProject()
+const { isPg, isMysql } = useBase()
// a variable to keep newly created options value
// temporary until it's add the option to column meta
@@ -133,7 +133,7 @@ const vModel = computed({
const selectedTitles = computed(() =>
modelValue
? typeof modelValue === 'string'
- ? isMysql(column.value.base_id)
+ ? isMysql(column.value.source_id)
? modelValue.split(',').sort((a, b) => {
const opa = options.value.find((el) => el.title === a)
const opb = options.value.find((el) => el.title === b)
@@ -247,7 +247,7 @@ async function addIfMissingAndSave() {
// todo: refactor and avoid repetition
if (updatedColMeta.cdf) {
// Postgres returns default value wrapped with single quotes & casted with type so we have to get value between single quotes to keep it unified for all databases
- if (isPg(column.value.base_id)) {
+ if (isPg(column.value.source_id)) {
updatedColMeta.cdf = updatedColMeta.cdf.substring(
updatedColMeta.cdf.indexOf(`'`) + 1,
updatedColMeta.cdf.lastIndexOf(`'`),
@@ -255,7 +255,7 @@ async function addIfMissingAndSave() {
}
// Mysql escapes single quotes with backslash so we keep quotes but others have to unescaped
- if (!isMysql(column.value.base_id)) {
+ if (!isMysql(column.value.source_id)) {
updatedColMeta.cdf = updatedColMeta.cdf.replace(/''/g, "'")
}
}
@@ -321,6 +321,8 @@ const handleClose = (e: MouseEvent) => {
!aselect.value.$el.contains(e.target) &&
!document.querySelector('.nc-dropdown-multi-select-cell.active')?.contains(e.target as Node)
) {
+ // loose focus when clicked outside
+ isEditable.value = false
isOpen.value = false
}
}
@@ -378,7 +380,7 @@ const selectedOpts = computed(() => {
v-model:value="vModel"
mode="multiple"
class="w-full overflow-hidden"
- :placeholder="isEditColumn ? '(Optional)' : ''"
+ :placeholder="isEditColumn ? $t('labels.optional') : ''"
:bordered="false"
clear-icon
show-search
@@ -421,7 +423,7 @@ const selectedOpts = computed(() => {
- Create new option named {{ searchVal }}
+ {{ $t('msg.selectOption.createNewOptionNamed') }} {{ searchVal }}
diff --git a/packages/nc-gui/components/cell/Percent.vue b/packages/nc-gui/components/cell/Percent.vue
index 5fec5b0412..55b6188e56 100644
--- a/packages/nc-gui/components/cell/Percent.vue
+++ b/packages/nc-gui/components/cell/Percent.vue
@@ -42,7 +42,7 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
class="w-full !text-sm !border-none !outline-none focus:ring-0 text-base p-1"
:class="{ '!px-2': editEnabled }"
type="number"
- :placeholder="isEditColumn ? '(Optional)' : ''"
+ :placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="editEnabled = false"
@keydown.down.stop
@keydown.left.stop
@@ -54,6 +54,6 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
@selectstart.capture.stop
@mousedown.stop
/>
- NULL
+ {{ $t('general.null') }}
{{ vModel }}
diff --git a/packages/nc-gui/components/cell/PhoneNumber.vue b/packages/nc-gui/components/cell/PhoneNumber.vue
index 42f2f37f25..24c3c8ef36 100644
--- a/packages/nc-gui/components/cell/PhoneNumber.vue
+++ b/packages/nc-gui/components/cell/PhoneNumber.vue
@@ -15,6 +15,8 @@ const rowHeight = inject(RowHeightInj, ref(undefined))
const { showNull } = useGlobal()
+const { t } = useI18n()
+
const editEnabled = inject(EditModeInj)!
const isEditColumn = inject(EditColumnInj, ref(false))
@@ -46,7 +48,7 @@ watch(
() => editEnabled.value,
() => {
if (parseProp(column.value.meta)?.validate && !editEnabled.value && localState.value && !isMobilePhone(localState.value)) {
- message.error('Invalid Phone Number')
+ message.error(t('msg.invalidPhoneNumber'))
localState.value = undefined
return
}
@@ -61,7 +63,7 @@ watch(
:ref="focus"
v-model="vModel"
class="w-full outline-none text-sm px-1 py-2"
- :placeholder="isEditColumn ? '(Optional)' : ''"
+ :placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="editEnabled = false"
@keydown.down.stop
@keydown.left.stop
@@ -74,7 +76,7 @@ watch(
@mousedown.stop
/>
- NULL
+ {{ $t('general.null') }}
diff --git a/packages/nc-gui/components/cell/SingleSelect.vue b/packages/nc-gui/components/cell/SingleSelect.vue
index 4e357d1cb2..f671553f7c 100644
--- a/packages/nc-gui/components/cell/SingleSelect.vue
+++ b/packages/nc-gui/components/cell/SingleSelect.vue
@@ -20,8 +20,8 @@ import {
inject,
isDrawerOrModalExist,
ref,
+ useBase,
useEventListener,
- useProject,
useRoles,
useSelectedCellKeyupListener,
watch,
@@ -71,7 +71,7 @@ const { getMeta } = useMetas()
const { isUIAllowed } = useRoles()
-const { isPg, isMysql } = useProject()
+const { isPg, isMysql } = useBase()
// a variable to keep newly created option value
// temporary until it's add the option to column meta
@@ -175,7 +175,7 @@ async function addIfMissingAndSave() {
// todo: refactor and avoid repetition
if (updatedColMeta.cdf) {
// Postgres returns default value wrapped with single quotes & casted with type so we have to get value between single quotes to keep it unified for all databases
- if (isPg(column.value.base_id)) {
+ if (isPg(column.value.source_id)) {
updatedColMeta.cdf = updatedColMeta.cdf.substring(
updatedColMeta.cdf.indexOf(`'`) + 1,
updatedColMeta.cdf.lastIndexOf(`'`),
@@ -183,7 +183,7 @@ async function addIfMissingAndSave() {
}
// Mysql escapes single quotes with backslash so we keep quotes but others have to unescaped
- if (!isMysql(column.value.base_id)) {
+ if (!isMysql(column.value.source_id)) {
updatedColMeta.cdf = updatedColMeta.cdf.replace(/''/g, "'")
}
}
@@ -217,6 +217,7 @@ const onKeydown = (e: KeyboardEvent) => {
const onSelect = () => {
isOpen.value = false
+ isEditable.value = false
}
const cellClickHook = inject(CellClickHookInj, null)
@@ -286,7 +287,7 @@ const selectedOpt = computed(() => {
v-model:value="vModel"
class="w-full overflow-hidden"
:class="{ 'caret-transparent': !hasEditRoles }"
- :placeholder="isEditColumn ? '(Optional)' : ''"
+ :placeholder="isEditColumn ? $t('labels.optional') : ''"
:allow-clear="!column.rqd && editAllowed"
:bordered="false"
:open="isOpen && editAllowed"
@@ -324,7 +325,7 @@ const selectedOpt = computed(() => {
- Create new option named {{ searchVal }}
+ {{ $t('msg.selectOption.createNewOptionNamed') }} {{ searchVal }}
diff --git a/packages/nc-gui/components/cell/Text.vue b/packages/nc-gui/components/cell/Text.vue
index 9fc0b9209e..e3ec8698a6 100644
--- a/packages/nc-gui/components/cell/Text.vue
+++ b/packages/nc-gui/components/cell/Text.vue
@@ -33,7 +33,10 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
:ref="focus"
v-model="vModel"
class="h-full w-full outline-none p-2 bg-transparent"
- :placeholder="isEditColumn ? '(Optional)' : ''"
+ :placeholder="isEditColumn ? $t('labels.optional') : ''"
+ :class="{
+ 'px-1': isExpandedFormOpen,
+ }"
@blur="editEnabled = false"
@keydown.down.stop
@keydown.left.stop
@@ -46,7 +49,7 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
@mousedown.stop
/>
- NULL
+ {{ $t('general.null') }}
diff --git a/packages/nc-gui/components/cell/TextArea.vue b/packages/nc-gui/components/cell/TextArea.vue
index 0ebd53ae86..6cf430bd22 100644
--- a/packages/nc-gui/components/cell/TextArea.vue
+++ b/packages/nc-gui/components/cell/TextArea.vue
@@ -28,6 +28,8 @@ const isEditColumn = inject(EditColumnInj, ref(false))
const rowHeight = inject(RowHeightInj, ref(1 as const))
+const isForm = inject(IsFormInj, ref(false))
+
const { showNull } = useGlobal()
const vModel = useVModel(props, 'modelValue', emits, { defaultValue: '' })
@@ -68,10 +70,11 @@ onClickOutside(inputWrapperRef, (e) => {
@@ -124,7 +126,7 @@ const handleFileDelete = (i: number) => {
class="text-white ring ring-accent ring-opacity-100 bg-gray-700/75 flex items-center justify-center gap-2 backdrop-blur-xl"
>
- Drop here
+ {{ $t('labels.dropHere') }}
@@ -138,7 +140,7 @@ const handleFileDelete = (i: number) => {
/>
- Remove File
+ {{ $t('title.removeFile') }}
{
- Download File
+ {{ $t('title.downloadFile') }}
@@ -156,7 +158,7 @@ const handleFileDelete = (i: number) => {
- Rename File
+ {{ $t('title.renameFile') }}
@@ -170,7 +172,7 @@ const handleFileDelete = (i: number) => {
diff --git a/packages/nc-gui/components/cell/attachment/RenameFile.vue b/packages/nc-gui/components/cell/attachment/RenameFile.vue
index f8408b224e..0eb8289627 100644
--- a/packages/nc-gui/components/cell/attachment/RenameFile.vue
+++ b/packages/nc-gui/components/cell/attachment/RenameFile.vue
@@ -48,7 +48,7 @@ onMounted(() => {
- Rename File
+ {{ $t('title.renameFile') }}
diff --git a/packages/nc-gui/components/cell/attachment/index.vue b/packages/nc-gui/components/cell/attachment/index.vue
index d5e2471b3e..b7565bec45 100644
--- a/packages/nc-gui/components/cell/attachment/index.vue
+++ b/packages/nc-gui/components/cell/attachment/index.vue
@@ -50,7 +50,9 @@ const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false))
const { isSharedForm } = useSmartsheetStoreOrThrow()!
-const { getPossibleAttachmentSrc, openAttachment } = useAttachment()
+const { isMobileMode } = useGlobal()
+
+const { getPossibleAttachmentSrc, openAttachment: _openAttachment } = useAttachment()
const {
isPublic,
@@ -61,7 +63,7 @@ const {
visibleItems,
onDrop,
isLoading,
- open,
+ open: _open,
FileIcon,
selectedImage,
isReadonly: _isReadonly,
@@ -136,7 +138,7 @@ watch(
useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e) => {
if (e.key === 'Enter' && !isReadonly.value) {
e.stopPropagation()
- if (!modalVisible.value) {
+ if (!modalVisible.value && !isMobileMode.value) {
modalVisible.value = true
} else {
// click Attach File button
@@ -146,6 +148,24 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e) => {
})
const rowHeight = inject(RowHeightInj, ref())
+
+const open = () => {
+ if (isMobileMode.value) return (isExpandedForm.value = true)
+
+ _open()
+}
+
+const openAttachment = (item: any) => {
+ if (isMobileMode.value) return
+
+ _openAttachment(item)
+}
+
+const onExpand = () => {
+ if (isMobileMode.value) return
+
+ modalVisible.value = true
+}
@@ -153,9 +173,9 @@ const rowHeight = inject(RowHeightInj, ref())
ref="attachmentCellRef"
tabindex="0"
:style="{
- height: isForm ? undefined : `max(${(rowHeight || 1) * 1.8}rem, 41px)`,
+ height: isForm || isExpandedForm ? undefined : `max(${(rowHeight || 1) * 1.8}rem, 41px)`,
}"
- class="nc-attachment-cell relative flex color-transition flex items-center"
+ class="nc-attachment-cell relative flex color-transition flex items-center w-full"
:class="{ 'justify-center': !active, 'justify-between': active }"
>
@@ -168,7 +188,7 @@ const rowHeight = inject(RowHeightInj, ref())
class="nc-attachment-cell-dropzone text-white text-lg ring ring-accent ring-opacity-100 bg-gray-700/75 flex items-center justify-center gap-2 backdrop-blur-xl"
>
- Drop here
+ {{ $t('labels.dropHere') }}
@@ -182,9 +202,9 @@ const rowHeight = inject(RowHeightInj, ref())
- Click or drop a file into cell
+ {{ $t('activity.attachmentDrop') }}
-
+
@@ -192,7 +212,7 @@ const rowHeight = inject(RowHeightInj, ref())
v-if="!visibleItems.length"
class="group-hover:text-primary text-gray-500 dark:text-gray-200 dark:group-hover:!text-white text-xs"
>
- Add file(s)
+ {{ $t('activity.addFiles') }}
@@ -206,7 +226,7 @@ const rowHeight = inject(RowHeightInj, ref())
:class="{ 'justify-center': !isExpandedForm && !isGallery }"
class="flex cursor-pointer w-full items-center flex-wrap gap-2 py-1.5 scrollbar-thin-dull overflow-hidden mt-0 items-start"
:style="{
- maxHeight: isForm ? undefined : `max(${(rowHeight || 1) * 1.8}rem, 41px)`,
+ maxHeight: isForm || isExpandedForm ? undefined : `max(${(rowHeight || 1) * 1.8}rem, 41px)`,
}"
>
@@ -220,18 +240,19 @@ const rowHeight = inject(RowHeightInj, ref())
:class="{ 'ml-2': active }"
@click="
() => {
- if (isGallery) return
+ if (isGallery || isMobileMode) return
selectedImage = item
}
"
>
@@ -252,18 +273,18 @@ const rowHeight = inject(RowHeightInj, ref())
- View attachments
+ {{ $t('activity.viewAttachment') }}
diff --git a/packages/nc-gui/components/cell/attachment/utils.ts b/packages/nc-gui/components/cell/attachment/utils.ts
index 2cfceac17c..ce2117816b 100644
--- a/packages/nc-gui/components/cell/attachment/utils.ts
+++ b/packages/nc-gui/components/cell/attachment/utils.ts
@@ -17,10 +17,10 @@ import {
storeToRefs,
useApi,
useAttachment,
+ useBase,
useFileDialog,
useI18n,
useInjectionState,
- useProject,
watch,
} from '#imports'
import MdiPdfBox from '~icons/mdi/pdf-box'
@@ -33,6 +33,8 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
(updateModelValue: (data: string | Record[]) => void) => {
const isReadonly = inject(ReadonlyInj, ref(false))
+ const { t } = useI18n()
+
const isPublic = inject(IsPublicInj, ref(false))
const isForm = inject(IsFormInj, ref(false))
@@ -53,7 +55,7 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
/** for image carousel */
const selectedImage = ref()
- const { project } = storeToRefs(useProject())
+ const { base } = storeToRefs(useBase())
const { api, isLoading } = useApi()
@@ -61,8 +63,6 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
const { appInfo } = useGlobal()
- const { t } = useI18n()
-
const { getAttachmentSrc } = useAttachment()
const defaultAttachmentMeta = {
@@ -138,7 +138,16 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
continue
}
}
-
+ // this prevent file with same names
+ const isFileNameAlreadyExist = attachments.value.some((el) => el.title === file.name)
+ if (isFileNameAlreadyExist) {
+ message.error(
+ t('labels.duplicateAttachment', {
+ filename: file.name,
+ }),
+ )
+ return
+ }
files.push(file)
}
@@ -182,7 +191,7 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
try {
const data = await api.storage.upload(
{
- path: [NOCO, project.value.title, meta.value?.title, column.value?.title].join('/'),
+ path: [NOCO, base.value.id, meta.value?.id, column.value?.id].join('/'),
},
{
files,
diff --git a/packages/nc-gui/components/dashboard/Sidebar.vue b/packages/nc-gui/components/dashboard/Sidebar.vue
index 164c11e554..e3e46b432e 100644
--- a/packages/nc-gui/components/dashboard/Sidebar.vue
+++ b/packages/nc-gui/components/dashboard/Sidebar.vue
@@ -3,13 +3,17 @@ const workspaceStore = useWorkspace()
const { isWorkspaceLoading } = storeToRefs(workspaceStore)
-const { isSharedBase } = storeToRefs(useProject())
+const { isSharedBase } = storeToRefs(useBase())
+
+const { isMobileMode } = useGlobal()
const treeViewDom = ref()
const isTreeViewOnScrollTop = ref(false)
const checkScrollTopMoreThanZero = () => {
+ if (isMobileMode.value) return
+
if (treeViewDom.value) {
if (treeViewDom.value.scrollTop > 0) {
isTreeViewOnScrollTop.value = true
@@ -43,7 +47,7 @@ onUnmounted(() => {
-
diff --git a/packages/nc-gui/components/dashboard/Sidebar/Header.vue b/packages/nc-gui/components/dashboard/Sidebar/Header.vue
index f6ff58e904..3c8be28dc6 100644
--- a/packages/nc-gui/components/dashboard/Sidebar/Header.vue
+++ b/packages/nc-gui/components/dashboard/Sidebar/Header.vue
@@ -4,11 +4,19 @@ const workspaceStore = useWorkspace()
const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
const { activeWorkspace, isWorkspaceLoading } = storeToRefs(workspaceStore)
+
+const { activeViewTitleOrId } = storeToRefs(useViewsStore())
+
+const { activeTableId } = storeToRefs(useTablesStore())
+
+const { isMobileMode } = useGlobal()
+
+const showSidebarBtn = computed(() => !(isMobileMode.value && !activeViewTitleOrId.value && !activeTableId.value))
@@ -106,7 +129,8 @@ function onOpenModal({
Kanban
-
+
+
diff --git a/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue b/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
index 0c75b6c682..ee25b2f441 100644
--- a/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
+++ b/packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
@@ -2,7 +2,7 @@
import { nextTick } from '@vue/runtime-core'
import { message } from 'ant-design-vue'
import { stringifyRolesObj } from 'nocodb-sdk'
-import type { BaseType, ProjectType, TableType } from 'nocodb-sdk'
+import type { BaseType, SourceType, TableType } from 'nocodb-sdk'
import { LoadingOutlined } from '@ant-design/icons-vue'
import { useTitle } from '@vueuse/core'
import {
@@ -10,10 +10,27 @@ import {
ProjectInj,
ProjectRoleInj,
ToggleDialogInj,
+ TreeViewInj,
+ computed,
extractSdkResponseErrorMsg,
+ h,
+ inject,
+ navigateTo,
openLink,
+ ref,
+ resolveComponent,
storeToRefs,
- useProjects,
+ useBase,
+ useBases,
+ useCopy,
+ useDialog,
+ useGlobal,
+ useI18n,
+ useRoles,
+ useRouter,
+ useTablesStore,
+ useTabs,
+ useToggle,
} from '#imports'
import type { NcProject } from '#imports'
import { useNuxtApp } from '#app'
@@ -27,18 +44,25 @@ const indicator = h(LoadingOutlined, {
})
const router = useRouter()
+
const route = router.currentRoute
+const { isSharedBase } = storeToRefs(useBase())
+
const { setMenuContext, openRenameTableDialog, duplicateTable, contextMenuTarget } = inject(TreeViewInj)!
-const project = inject(ProjectInj)!
+const base = inject(ProjectInj)!
+
+const basesStore = useBases()
-const projectsStore = useProjects()
+const { isMobileMode } = useGlobal()
-const { loadProject, loadProjects, createProject: _createProject, updateProject, getProjectMetaInfo } = projectsStore
-const { projects } = storeToRefs(projectsStore)
+const { loadProjects, createProject: _createProject, updateProject, getProjectMetaInfo } = basesStore
+
+const { bases } = storeToRefs(basesStore)
const { loadProjectTables } = useTablesStore()
+
const { activeTable } = storeToRefs(useTablesStore())
const { appInfo, navigateToProject } = useGlobal()
@@ -59,11 +83,11 @@ const { t } = useI18n()
const input = ref()
-const projectRole = inject(ProjectRoleInj)
+const baseRole = inject(ProjectRoleInj)
-const { activeProjectId } = storeToRefs(useProjects())
+const { activeProjectId } = storeToRefs(useBases())
-const { projectUrl } = useProject()
+const { baseUrl } = useBase()
const toggleDialog = inject(ToggleDialogInj, () => {})
@@ -79,9 +103,9 @@ const keys = ref>({})
const isTableDeleteDialogVisible = ref(false)
const isProjectDeleteDialogVisible = ref(false)
-// If only project is open, i.e in case of docs, project view is open and not the page view
-const projectViewOpen = computed(() => {
- const routeNameSplit = String(route.value?.name).split('projectId-index-index')
+// If only base is open, i.e in case of docs, base view is open and not the page view
+const baseViewOpen = computed(() => {
+ const routeNameSplit = String(route.value?.name).split('baseId-index-index')
if (routeNameSplit.length <= 1) return false
const routeNameAfterProjectView = routeNameSplit[routeNameSplit.length - 1]
@@ -94,7 +118,7 @@ const showBaseOption = computed(() => {
const enableEditMode = () => {
editMode.value = true
- tempTitle.value = project.value.title!
+ tempTitle.value = base.value.title!
nextTick(() => {
input.value?.focus()
input.value?.select()
@@ -106,15 +130,15 @@ const updateProjectTitle = async () => {
if (!tempTitle.value) return
try {
- await updateProject(project.value.id!, {
+ await updateProject(base.value.id!, {
title: tempTitle.value,
})
editMode.value = false
tempTitle.value = ''
- $e('a:project:rename')
+ $e('a:base:rename')
- useTitle(`${project.value?.title}`)
+ useTitle(`${base.value?.title}`)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
@@ -126,7 +150,7 @@ const copyProjectInfo = async () => {
try {
if (
await copy(
- Object.entries(await getProjectMetaInfo(project.value.id!)!)
+ Object.entries(await getProjectMetaInfo(base.value.id!)!)
.map(([k, v]) => `${k}: **${v}**`)
.join('\n'),
)
@@ -144,36 +168,34 @@ defineExpose({
enableEditMode,
})
-const setIcon = async (icon: string, project: ProjectType) => {
+const setIcon = async (icon: string, base: BaseType) => {
try {
const meta = {
- ...((project.meta as object) || {}),
+ ...((base.meta as object) || {}),
icon,
}
- projectsStore.updateProject(project.id!, { meta: JSON.stringify(meta) })
+ basesStore.updateProject(base.id!, { meta: JSON.stringify(meta) })
- $e('a:project:icon:navdraw', { icon })
+ $e('a:base:icon:navdraw', { icon })
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
-function openTableCreateDialog(baseIndex?: number | undefined) {
- $e('c:table:create:navdraw')
-
+function openTableCreateDialog(sourceIndex?: number | undefined) {
const isOpen = ref(true)
- let baseId = project.value!.bases?.[0].id
- if (typeof baseIndex === 'number') {
- baseId = project.value!.bases?.[baseIndex].id
+ let sourceId = base.value!.sources?.[0].id
+ if (typeof sourceIndex === 'number') {
+ sourceId = base.value!.sources?.[sourceIndex].id
}
- if (!baseId || !project.value?.id) return
+ if (!sourceId || !base.value?.id) return
const { close } = useDialog(resolveComponent('DlgTableCreate'), {
'modelValue': isOpen,
- baseId, // || bases.value[0].id,
- 'projectId': project.value!.id,
+ sourceId, // || sources.value[0].id,
+ 'baseId': base.value!.id,
'onCreate': closeDialog,
'onUpdate:modelValue': () => closeDialog(),
})
@@ -183,10 +205,10 @@ function openTableCreateDialog(baseIndex?: number | undefined) {
if (!table) return
- project.value.isExpanded = true
+ base.value.isExpanded = true
- if (!activeKey.value || !activeKey.value.includes(`collapse-${baseId}`)) {
- activeKey.value.push(`collapse-${baseId}`)
+ if (!activeKey.value || !activeKey.value.includes(`collapse-${sourceId}`)) {
+ activeKey.value.push(`collapse-${sourceId}`)
}
// TODO: Better way to know when the table node dom is available
@@ -207,86 +229,82 @@ const addNewProjectChildEntity = async () => {
isAddNewProjectChildEntityLoading.value = true
- const isProjectPopulated = projectsStore.isProjectPopulated(project.value.id!)
- if (!isProjectPopulated && project.value.type === NcProjectType.DB) {
+ const isProjectPopulated = basesStore.isProjectPopulated(base.value.id!)
+ if (!isProjectPopulated && base.value.type === NcProjectType.DB) {
// We do not wait for tables api, so that add new table is seamless.
// Only con would be while saving table duplicate table name FE validation might not work
// If the table list api takes time to load before the table name validation
- loadProjectTables(project.value.id!)
+ loadProjectTables(base.value.id!)
}
try {
openTableCreateDialog()
- if (!project.value.isExpanded && project.value.type !== NcProjectType.DB) {
- project.value.isExpanded = true
+ if (!base.value.isExpanded && base.value.type !== NcProjectType.DB) {
+ base.value.isExpanded = true
}
} finally {
isAddNewProjectChildEntityLoading.value = false
}
}
-// todo: temp
-const isSharedBase = ref(false)
-
-const onProjectClick = async (project: NcProject, ignoreNavigation?: boolean, toggleIsExpanded?: boolean) => {
- if (!project) {
+const onProjectClick = async (base: NcProject, ignoreNavigation?: boolean, toggleIsExpanded?: boolean) => {
+ if (!base) {
return
}
+ if (!toggleIsExpanded) $e('c:base:open')
+
+ ignoreNavigation = isMobileMode.value || ignoreNavigation
+ toggleIsExpanded = isMobileMode.value || toggleIsExpanded
+
if (toggleIsExpanded) {
- project.isExpanded = !project.isExpanded
+ base.isExpanded = !base.isExpanded
} else {
- project.isExpanded = true
+ base.isExpanded = true
}
- const isProjectPopulated = projectsStore.isProjectPopulated(project.id!)
-
- let isSharedBase = false
- // if shared base ignore navigation
- if (route.value.params.typeOrId === 'base') {
- isSharedBase = true
- }
+ const isProjectPopulated = basesStore.isProjectPopulated(base.id!)
- if (!isProjectPopulated) project.isLoading = true
+ if (!isProjectPopulated) base.isLoading = true
if (!ignoreNavigation) {
await navigateTo(
- projectUrl({
- id: project.id!,
+ baseUrl({
+ id: base.id!,
type: 'database',
- isSharedBase,
+ isSharedBase: isSharedBase.value,
}),
)
}
if (!isProjectPopulated) {
- await loadProjectTables(project.id!)
+ await loadProjectTables(base.id!)
}
if (!isProjectPopulated) {
- const updatedProject = projects.value.get(project.id!)!
+ const updatedProject = bases.value.get(base.id!)!
updatedProject.isLoading = false
}
}
-function openErdView(base: BaseType) {
- activeBaseId.value = base.id
- isErdModalOpen.value = !isErdModalOpen.value
-}
+function openErdView(source: SourceType) {
+ $e('c:project:relation')
-async function openProjectErdView(_project: ProjectType) {
- if (!_project.id) return
+ const isOpen = ref(true)
- if (!projectsStore.isProjectPopulated(_project.id)) {
- await loadProject(_project.id)
- }
+ const { close } = useDialog(resolveComponent('DlgProjectErd'), {
+ 'modelValue': isOpen,
+ 'sourceId': source!.id,
+ 'onUpdate:modelValue': () => closeDialog(),
+ 'baseId': base.value.id,
+ })
- const project = projects.value.get(_project.id)
+ function closeDialog() {
+ isOpen.value = false
- const base = project?.bases?.[0]
- if (!base) return
- openErdView(base)
+ close(1000)
+ }
}
const reloadTables = async () => {
@@ -296,11 +314,11 @@ const reloadTables = async () => {
}
const contextMenuBase = computed(() => {
- if (contextMenuTarget.type === 'base') {
+ if (contextMenuTarget.type === 'source') {
return contextMenuTarget.value
} else if (contextMenuTarget.type === 'table') {
- const base = project.value?.bases?.find((b) => b.id === contextMenuTarget.value.base_id)
- if (base) return base
+ const source = base.value?.sources?.find((b) => b.id === contextMenuTarget.value.source_id)
+ if (source) return source
}
return null
})
@@ -310,11 +328,11 @@ watch(
async () => {
if (!activeTable.value) return
- const baseId = activeTable.value.base_id
- if (!baseId) return
+ const sourceId = activeTable.value.source_id
+ if (!sourceId) return
- if (!activeKey.value.includes(`collapse-${baseId}`)) {
- activeKey.value.push(`collapse-${baseId}`)
+ if (!activeKey.value.includes(`collapse-${sourceId}`)) {
+ activeKey.value.push(`collapse-${sourceId}`)
}
},
{
@@ -335,87 +353,109 @@ onKeyStroke('Escape', () => {
const isDuplicateDlgOpen = ref(false)
const selectedProjectToDuplicate = ref()
-const duplicateProject = (project: ProjectType) => {
- selectedProjectToDuplicate.value = project
+const duplicateProject = (base: BaseType) => {
+ selectedProjectToDuplicate.value = base
isDuplicateDlgOpen.value = true
}
-const { $jobs } = useNuxtApp()
+const { $poller } = useNuxtApp()
-const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string }) => {
+const DlgProjectDuplicateOnOk = async (jobData: { id: string; base_id: string }) => {
await loadProjects('workspace')
- $jobs.subscribe({ id: jobData.id }, undefined, async (status: string) => {
- if (status === JobStatus.COMPLETED) {
- await loadProjects('workspace')
-
- const project = projects.value.get(jobData.project_id)
-
- // open project after duplication
- if (project) {
- await navigateToProject({
- projectId: project.id,
- type: project.type,
- })
+ $poller.subscribe(
+ { id: jobData.id },
+ async (data: {
+ id: string
+ status?: string
+ data?: {
+ error?: {
+ message: string
+ }
+ message?: string
+ result?: any
}
- } else if (status === JobStatus.FAILED) {
- message.error('Failed to duplicate project')
- await loadProjects('workspace')
- }
- })
+ }) => {
+ if (data.status !== 'close') {
+ if (data.status === JobStatus.COMPLETED) {
+ await loadProjects('workspace')
+
+ const base = bases.value.get(jobData.base_id)
+
+ // open base after duplication
+ if (base) {
+ await navigateToProject({
+ baseId: base.id,
+ type: base.type,
+ })
+ }
+ } else if (data.status === JobStatus.FAILED) {
+ message.error('Failed to duplicate base')
+ await loadProjects('workspace')
+ }
+ }
+ },
+ )
+ $e('a:base:duplicate')
+}
+
+const tableDelete = () => {
+ isTableDeleteDialogVisible.value = true
+ $e('c:table:delete')
+}
- $e('a:project:duplicate')
+const projectDelete = () => {
+ isProjectDeleteDialogVisible.value = true
+ $e('c:project:delete')
}
| |