Browse Source

Merge pull request #7067 from nocodb/nc-fix/locked-mode-fix-2

Locked mode
pull/7057/head
Raju Udava 1 year ago committed by GitHub
parent
commit
475af0bdef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      packages/nc-gui/assets/nc-icons/credit-card.svg
  2. 6
      packages/nc-gui/assets/nc-icons/layers.svg
  3. 6
      packages/nc-gui/components/cell/DatePicker.vue
  4. 6
      packages/nc-gui/components/cell/DateTimePicker.vue
  5. 12
      packages/nc-gui/components/cell/MultiSelect.vue
  6. 12
      packages/nc-gui/components/cell/SingleSelect.vue
  7. 8
      packages/nc-gui/components/cell/attachment/Modal.vue
  8. 10
      packages/nc-gui/components/cell/attachment/index.vue
  9. 8
      packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue
  10. 8
      packages/nc-gui/components/smartsheet/Cell.vue
  11. 33
      packages/nc-gui/components/smartsheet/Form.vue
  12. 114
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  13. 89
      packages/nc-gui/components/smartsheet/details/Fields.vue
  14. 68
      packages/nc-gui/components/smartsheet/grid/Table.vue
  15. 6
      packages/nc-gui/components/smartsheet/header/Cell.vue
  16. 6
      packages/nc-gui/components/smartsheet/header/VirtualCell.vue
  17. 28
      packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
  18. 5
      packages/nc-gui/components/virtual-cell/BelongsTo.vue
  19. 5
      packages/nc-gui/components/virtual-cell/HasMany.vue
  20. 12
      packages/nc-gui/components/virtual-cell/Links.vue
  21. 5
      packages/nc-gui/components/virtual-cell/ManyToMany.vue
  22. 11
      packages/nc-gui/components/virtual-cell/components/ItemChip.vue
  23. 1
      packages/nc-gui/lang/en.json
  24. 3
      packages/nc-gui/store/views.ts
  25. 2
      tests/playwright/pages/Dashboard/Grid/index.ts
  26. 2
      tests/playwright/pages/Dashboard/common/Footbar/index.ts
  27. 3
      tests/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts
  28. 4
      tests/playwright/tests/db/general/viewMenu.spec.ts

4
packages/nc-gui/assets/nc-icons/credit-card.svg

@ -1,4 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2.66602H1.99999C1.26361 2.66602 0.666656 3.26297 0.666656 3.99935V11.9993C0.666656 12.7357 1.26361 13.3327 1.99999 13.3327H14C14.7364 13.3327 15.3333 12.7357 15.3333 11.9993V3.99935C15.3333 3.26297 14.7364 2.66602 14 2.66602Z" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M0.666656 6.66602H15.3333" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 2.66602H1.99999C1.26361 2.66602 0.666656 3.26297 0.666656 3.99935V11.9993C0.666656 12.7357 1.26361 13.3327 1.99999 13.3327H14C14.7364 13.3327 15.3333 12.7357 15.3333 11.9993V3.99935C15.3333 3.26297 14.7364 2.66602 14 2.66602Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M0.666656 6.66602H15.3333" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 557 B

After

Width:  |  Height:  |  Size: 567 B

6
packages/nc-gui/assets/nc-icons/layers.svg

@ -1,8 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_656_42199)">
<path d="M1.33325 11.334L7.99992 14.6673L14.6666 11.334" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.33325 8L7.99992 11.3333L14.6666 8" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.99992 1.33398L1.33325 4.66732L7.99992 8.00065L14.6666 4.66732L7.99992 1.33398Z" stroke="#565B66" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.33325 11.334L7.99992 14.6673L14.6666 11.334" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.33325 8L7.99992 11.3333L14.6666 8" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.99992 1.33398L1.33325 4.66732L7.99992 8.00065L14.6666 4.66732L7.99992 1.33398Z" stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_656_42199">

Before

Width:  |  Height:  |  Size: 712 B

After

Width:  |  Height:  |  Size: 727 B

6
packages/nc-gui/components/cell/DatePicker.vue

@ -32,8 +32,6 @@ const columnMeta = inject(ColumnInj, null)!
const readOnly = inject(ReadonlyInj, ref(false))
const isLockedMode = inject(IsLockedInj, ref(false))
const isEditColumn = inject(EditColumnInj, ref(false))
const active = inject(ActiveCellInj, ref(false))
@ -188,9 +186,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
const isOpen = computed(() => {
if (readOnly.value) return false
return ((readOnly.value || (localState.value && isPk)) && !active.value && !editable.value) || isLockedMode.value
? false
: open.value
return (readOnly.value || (localState.value && isPk)) && !active.value && !editable.value ? false : open.value
})
// use the default date picker open sync only to close the picker

6
packages/nc-gui/components/cell/DateTimePicker.vue

@ -37,8 +37,6 @@ const active = inject(ActiveCellInj, ref(false))
const editable = inject(EditModeInj, ref(false))
const isLockedMode = inject(IsLockedInj, ref(false))
const { t } = useI18n()
const isEditColumn = inject(EditColumnInj, ref(false))
@ -126,9 +124,7 @@ const open = ref(false)
const isOpen = computed(() => {
if (readOnly.value) return false
return readOnly.value || (localState.value && isPk) || isLockedMode.value
? false
: open.value && (active.value || editable.value)
return readOnly.value || (localState.value && isPk) ? false : open.value && (active.value || editable.value)
})
const randomClass = `picker_${Math.floor(Math.random() * 99999)}`

12
packages/nc-gui/components/cell/MultiSelect.vue

@ -49,8 +49,6 @@ const column = inject(ColumnInj)!
const readOnly = inject(ReadonlyInj)!
const isLockedMode = inject(IsLockedInj, ref(false))
const isEditable = inject(EditModeInj, ref(false))
const activeCell = inject(ActiveCellInj, ref(false))
@ -343,11 +341,7 @@ const selectedOpts = computed(() => {
</script>
<template>
<div
class="nc-multi-select h-full w-full flex items-center"
:class="{ 'read-only': readOnly || isLockedMode }"
@click="toggleMenu"
>
<div class="nc-multi-select h-full w-full flex items-center" :class="{ 'read-only': readOnly }" @click="toggleMenu">
<div
v-if="!active"
class="flex flex-wrap"
@ -386,9 +380,9 @@ const selectedOpts = computed(() => {
:bordered="false"
clear-icon
:show-search="!isMobileMode"
:show-arrow="editAllowed && !(readOnly || isLockedMode)"
:show-arrow="editAllowed && !readOnly"
:open="isOpen && editAllowed"
:disabled="readOnly || !editAllowed || isLockedMode"
:disabled="readOnly || !editAllowed"
:class="{ 'caret-transparent': !hasEditRoles }"
:dropdown-class-name="`nc-dropdown-multi-select-cell ${isOpen ? 'active' : ''}`"
@search="search"

12
packages/nc-gui/components/cell/SingleSelect.vue

@ -43,8 +43,6 @@ const column = inject(ColumnInj)!
const readOnly = inject(ReadonlyInj)!
const isLockedMode = inject(IsLockedInj, ref(false))
const isEditable = inject(EditModeInj, ref(false))
const activeCell = inject(ActiveCellInj, ref(false))
@ -264,11 +262,7 @@ const selectedOpt = computed(() => {
</script>
<template>
<div
class="h-full w-full flex items-center nc-single-select"
:class="{ 'read-only': readOnly || isLockedMode }"
@click="toggleMenu"
>
<div class="h-full w-full flex items-center nc-single-select" :class="{ 'read-only': readOnly }" @click="toggleMenu">
<div v-if="!(active || isEditable)">
<a-tag v-if="selectedOpt" class="rounded-tag" :color="selectedOpt.color">
<span
@ -295,8 +289,8 @@ const selectedOpt = computed(() => {
:allow-clear="!column.rqd && editAllowed"
:bordered="false"
:open="isOpen && editAllowed"
:disabled="readOnly || !editAllowed || isLockedMode"
:show-arrow="hasEditRoles && !(readOnly || isLockedMode) && active && vModel === null"
:disabled="readOnly || !editAllowed"
:show-arrow="hasEditRoles && !readOnly && active && vModel === null"
:dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen && active ? 'active' : ''}`"
:show-search="!isMobileMode && isOpen && active"
@select="onSelect"

8
packages/nc-gui/components/cell/attachment/Modal.vue

@ -25,8 +25,6 @@ const {
renameFile,
} = useAttachmentCell()!
const isLocked = inject(IsLockedInj, ref(false))
const dropZoneRef = ref<HTMLDivElement>()
const sortableRef = ref<HTMLDivElement>()
@ -96,7 +94,7 @@ const handleFileDelete = (i: number) => {
<template #title>
<div class="flex gap-4">
<div
v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic && !isLocked)"
v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic)"
class="nc-attach-file group"
data-testid="attachment-expand-file-picker-button"
@click="open"
@ -143,7 +141,7 @@ const handleFileDelete = (i: number) => {
<template #title> {{ $t('title.removeFile') }} </template>
<component
:is="iconMap.closeCircle"
v-if="isSharedForm || (isUIAllowed('dataEdit') && !isPublic && !isLocked)"
v-if="isSharedForm || (isUIAllowed('dataEdit') && !isPublic)"
class="nc-attachment-remove"
@click.stop="onRemoveFileClick(item.title, i)"
/>
@ -157,7 +155,7 @@ const handleFileDelete = (i: number) => {
</div>
</a-tooltip>
<a-tooltip v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic && !isLocked)" placement="bottom">
<a-tooltip v-if="isSharedForm || (!readOnly && isUIAllowed('dataEdit') && !isPublic)" placement="bottom">
<template #title> {{ $t('title.renameFile') }} </template>
<div class="nc-attachment-download group-hover:(opacity-100) mr-[35px]">

10
packages/nc-gui/components/cell/attachment/index.vue

@ -43,8 +43,6 @@ const sortableRef = ref<HTMLDivElement>()
const currentCellRef = inject(CurrentCellInj, dropZoneInjection.value)
const isLockedMode = inject(IsLockedInj, ref(false))
const isGallery = inject(IsGalleryInj, ref(false))
const isKanban = inject(IsKanbanInj, ref(false))
@ -69,18 +67,14 @@ const {
open: _open,
FileIcon,
selectedImage,
isReadonly: _isReadonly,
isReadonly,
storedFiles,
} = useProvideAttachmentCell(updateModelValue)
const { dragging } = useSortable(sortableRef, visibleItems, updateModelValue, _isReadonly)
const { dragging } = useSortable(sortableRef, visibleItems, updateModelValue, isReadonly)
const active = inject(ActiveCellInj, ref(false))
const isReadonly = computed(() => {
return isLockedMode.value || _isReadonly.value
})
const { state: rowState } = useSmartsheetRowStoreOrThrow()
const { isOverDropZone } = useDropZone(currentCellRef as any, onDrop)

8
packages/nc-gui/components/dlg/share-and-collaborate/SharePage.vue

@ -16,6 +16,8 @@ const { metas } = useMetas()
const workspaceStore = useWorkspace()
const isLocked = inject(IsLockedInj, ref(false))
const isUpdating = ref({
public: false,
password: false,
@ -269,10 +271,6 @@ function onChangeTheme(color: string) {
const isPublicShared = computed(() => {
return !!activeView.value?.uuid
})
const isPublicShareDisabled = computed(() => {
return false
})
</script>
<template>
@ -286,7 +284,7 @@ const isPublicShareDisabled = computed(() => {
:checked="isPublicShared"
:loading="isUpdating.public"
class="share-view-toggle !mt-0.25"
:disabled="isPublicShareDisabled"
:disabled="isLocked"
@click="toggleShare"
/>
</div>

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

@ -87,8 +87,6 @@ const isGrid = inject(IsGridInj, ref(false))
const isPublic = inject(IsPublicInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false))
const isSurveyForm = inject(IsSurveyFormInj, ref(false))
const isEditColumnMenu = inject(EditColumnInj, ref(false))
@ -255,11 +253,7 @@ onUnmounted(() => {
<LazyCellJson v-else-if="isJSON(column)" v-model="vModel" />
<LazyCellText v-else v-model="vModel" />
<div
v-if="
(isLocked || (isPublic && readOnly && !isForm) || isSystemColumn(column)) &&
!isAttachment(column) &&
!isTextArea(column)
"
v-if="(isPublic && readOnly && !isForm) || (isSystemColumn(column) && !isAttachment(column) && !isTextArea(column))"
class="nc-locked-overlay"
/>
</template>

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

@ -48,6 +48,8 @@ const formState = reactive({})
const secondsRemain = ref(0)
const isLocked = inject(IsLockedInj, ref(false))
const isEditable = isUIAllowed('viewFieldEdit' as Permission)
const meta = inject(MetaInj, ref())
@ -347,6 +349,8 @@ watch(submitted, (v) => {
})
function handleMouseUp(col: Record<string, any>, hiddenColIndex: number) {
if (isLocked.value) return
if (!moved.value) {
const index = localColumns.value.length
col.order = (index ? localColumns.value[index - 1].order : 0) + 1
@ -378,6 +382,12 @@ watch(view, (nextView) => {
reloadEventHook.trigger()
}
})
const onFormItemClick = (element: any) => {
if (isLocked.value) return
activeRow.value = element.title
}
</script>
<template>
@ -427,6 +437,7 @@ watch(view, (nextView) => {
type="secondary"
class="nc-form-add-all"
data-testid="nc-form-add-all"
:disabled="isLocked"
@click="addAllColumns"
>
<!-- Add all -->
@ -438,6 +449,7 @@ watch(view, (nextView) => {
type="secondary"
class="nc-form-remove-all"
data-testid="nc-form-remove-all"
:disabled="isLocked"
@click="removeAllColumns"
>
<!-- Remove all -->
@ -451,6 +463,7 @@ watch(view, (nextView) => {
item-key="id"
draggable=".item"
group="form-inputs"
:disabled="isLocked"
class="flex flex-col gap-2"
@start="drag = true"
@end="drag = false"
@ -459,6 +472,9 @@ watch(view, (nextView) => {
<a-card
size="small"
class="!border-0 color-transition cursor-pointer item hover:(bg-primary ring-1 ring-accent ring-opacity-100) bg-opacity-10 !rounded !shadow-lg"
:class="{
'!bg-gray-50 !hover:bg-gray-50 !hover:ring-transparent !cursor-not-allowed': isLocked,
}"
:data-testid="`nc-form-hidden-column-${element.label || element.title}`"
@mousedown="moved = false"
@mousemove="moved = false"
@ -484,7 +500,7 @@ watch(view, (nextView) => {
</a-card>
</template>
<template #footer>
<template v-if="!isLocked" #footer>
<div
class="my-4 select-none border-dashed border-2 border-gray-400 py-3 text-gray-400 text-center nc-drag-n-drop-to-hide"
data-testid="nc-drag-n-drop-to-hide"
@ -553,6 +569,7 @@ watch(view, (nextView) => {
autosize
size="large"
hide-details
:disabled="isLocked"
placeholder="Form Title"
:bordered="false"
data-testid="nc-form-heading"
@ -580,7 +597,7 @@ watch(view, (nextView) => {
hide-details
:placeholder="$t('msg.info.formDesc')"
:bordered="false"
:disabled="!isEditable"
:disabled="!isEditable || isLocked"
data-testid="nc-form-sub-heading"
@blur="updateView"
@click="updateView"
@ -598,6 +615,7 @@ watch(view, (nextView) => {
group="form-inputs"
class="h-full"
:move="onMoveCallback"
:disabled="isLocked"
@change="onMove($event)"
@start="drag = true"
@end="drag = false"
@ -610,12 +628,15 @@ watch(view, (nextView) => {
{
'bg-primary bg-opacity-5 ring-0.5 ring-accent ring-opacity-100': activeRow === element.title,
},
{
'!hover:bg-white !ring-0 !cursor-auto': isLocked,
},
]"
data-testid="nc-form-fields"
@click="activeRow = element.title"
@click="onFormItemClick(element)"
>
<div
v-if="isUIAllowed('viewFieldEdit') && !isRequired(element, element.required)"
v-if="isUIAllowed('viewFieldEdit') && !isRequired(element, element.required) && !isLocked"
class="absolute flex top-2 right-2"
>
<component
@ -795,9 +816,9 @@ watch(view, (nextView) => {
</a-card>
</a-form>
<a-divider />
<a-divider v-if="!isLocked" />
<div v-if="isEditable" class="px-4 flex flex-col gap-2">
<div v-if="isEditable && !isLocked" class="px-4 flex flex-col gap-2">
<!-- After form is submitted -->
<div class="text-lg text-gray-700">
{{ $t('msg.info.afterFormSubmitted') }}

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

@ -37,6 +37,7 @@ const props = defineProps<{
hideType?: boolean
hideAdditionalOptions?: boolean
fromTableExplorer?: boolean
readonly?: boolean
}>()
const emit = defineEmits(['submit', 'cancel', 'mounted', 'add', 'update'])
@ -66,6 +67,8 @@ const isForm = inject(IsFormInj, ref(false))
const isKanban = inject(IsKanbanInj, ref(false))
const readOnly = computed(() => props.readonly)
const { isMysql, isMssql, isXcdbBase } = useBase()
const reloadDataTrigger = inject(ReloadViewDataHookInj)
@ -114,6 +117,8 @@ const reloadMetaAndData = async () => {
const saving = ref(false)
async function onSubmit() {
if (readOnly.value) return
saving.value = true
const saved = await addOrUpdate(reloadMetaAndData, props.columnPosition)
saving.value = false
@ -134,7 +139,7 @@ async function onSubmit() {
// focus and select the column name field
const antInput = ref()
watchEffect(() => {
if (antInput.value && formState.value) {
if (antInput.value && formState.value && !readOnly.value) {
// todo: replace setTimeout
setTimeout(() => {
// focus and select input only if active element is not an input or textarea
@ -232,6 +237,7 @@ if (props.fromTableExplorer) {
<input
ref="antInput"
v-model="formState.title"
:disabled="readOnly"
class="flex flex-grow nc-fields-input text-lg font-bold outline-none bg-inherit"
:contenteditable="true"
/>
@ -247,7 +253,7 @@ if (props.fromTableExplorer) {
ref="antInput"
v-model:value="formState.title"
class="nc-column-name-input !rounded !mt-1"
:disabled="isKanban"
:disabled="isKanban || readOnly"
@input="onAlter(8)"
/>
</a-form-item>
@ -263,7 +269,7 @@ if (props.fromTableExplorer) {
v-model:value="formState.uidt"
show-search
class="nc-column-type-input !rounded"
:disabled="isKanban"
:disabled="isKanban || readOnly"
dropdown-class-name="nc-dropdown-column-type "
@change="onUidtOrIdTypeChange"
@dblclick="showDeprecated = !showDeprecated"
@ -285,28 +291,30 @@ if (props.fromTableExplorer) {
</div> -->
</div>
<LazySmartsheetColumnFormulaOptions v-if="formState.uidt === UITypes.Formula" v-model:value="formState" />
<LazySmartsheetColumnQrCodeOptions v-if="formState.uidt === UITypes.QrCode" v-model="formState" />
<LazySmartsheetColumnBarcodeOptions v-if="formState.uidt === UITypes.Barcode" v-model="formState" />
<LazySmartsheetColumnCurrencyOptions v-if="formState.uidt === UITypes.Currency" v-model:value="formState" />
<LazySmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" v-model:value="formState" />
<LazySmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" v-model:value="formState" />
<LazySmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" v-model:value="formState" />
<LazySmartsheetColumnLookupOptions v-if="formState.uidt === UITypes.Lookup" v-model:value="formState" />
<LazySmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" v-model:value="formState" />
<LazySmartsheetColumnDecimalOptions v-if="formState.uidt === UITypes.Decimal" v-model:value="formState" />
<LazySmartsheetColumnDateTimeOptions v-if="formState.uidt === UITypes.DateTime" v-model:value="formState" />
<LazySmartsheetColumnRollupOptions v-if="formState.uidt === UITypes.Rollup" v-model:value="formState" />
<LazySmartsheetColumnLinkedToAnotherRecordOptions
v-if="!isEdit && (formState.uidt === UITypes.LinkToAnotherRecord || formState.uidt === UITypes.Links)"
v-model:value="formState"
/>
<LazySmartsheetColumnLinkOptions v-if="isEdit && formState.uidt === UITypes.Links" v-model:value="formState" />
<LazySmartsheetColumnSpecificDBTypeOptions v-if="formState.uidt === UITypes.SpecificDBType" />
<SmartsheetColumnSelectOptions
v-if="formState.uidt === UITypes.SingleSelect || formState.uidt === UITypes.MultiSelect"
v-model:value="formState"
/>
<template v-if="!readOnly">
<LazySmartsheetColumnFormulaOptions v-if="formState.uidt === UITypes.Formula" v-model:value="formState" />
<LazySmartsheetColumnQrCodeOptions v-if="formState.uidt === UITypes.QrCode" v-model="formState" />
<LazySmartsheetColumnBarcodeOptions v-if="formState.uidt === UITypes.Barcode" v-model="formState" />
<LazySmartsheetColumnCurrencyOptions v-if="formState.uidt === UITypes.Currency" v-model:value="formState" />
<LazySmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" v-model:value="formState" />
<LazySmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" v-model:value="formState" />
<LazySmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" v-model:value="formState" />
<LazySmartsheetColumnLookupOptions v-if="formState.uidt === UITypes.Lookup" v-model:value="formState" />
<LazySmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" v-model:value="formState" />
<LazySmartsheetColumnDecimalOptions v-if="formState.uidt === UITypes.Decimal" v-model:value="formState" />
<LazySmartsheetColumnDateTimeOptions v-if="formState.uidt === UITypes.DateTime" v-model:value="formState" />
<LazySmartsheetColumnRollupOptions v-if="formState.uidt === UITypes.Rollup" v-model:value="formState" />
<LazySmartsheetColumnLinkedToAnotherRecordOptions
v-if="!isEdit && (formState.uidt === UITypes.LinkToAnotherRecord || formState.uidt === UITypes.Links)"
v-model:value="formState"
/>
<LazySmartsheetColumnLinkOptions v-if="isEdit && formState.uidt === UITypes.Links" v-model:value="formState" />
<LazySmartsheetColumnSpecificDBTypeOptions v-if="formState.uidt === UITypes.SpecificDBType" />
<SmartsheetColumnSelectOptions
v-if="formState.uidt === UITypes.SingleSelect || formState.uidt === UITypes.MultiSelect"
v-model:value="formState"
/>
</template>
</div>
<a-checkbox
v-if="formState.meta && columnToValidate.includes(formState.uidt)"
@ -317,41 +325,43 @@ if (props.fromTableExplorer) {
{{ `${$t('msg.acceptOnlyValid')} ${formState.uidt}` }}
</span>
</a-checkbox>
<div class="!my-3">
<!--
Default Value for JSON & LongText is not supported in MySQL
Default Value is Disabled for MSSQL -->
<LazySmartsheetColumnDefaultValue
v-if="
<template v-if="!readOnly">
<div class="!my-3">
<!--
Default Value for JSON & LongText is not supported in MySQL
Default Value is Disabled for MSSQL -->
<LazySmartsheetColumnDefaultValue
v-if="
!isVirtualCol(formState) &&
!isAttachment(formState) &&
!isMssql(meta!.source_id) &&
!(isMysql(meta!.source_id) && (isJSON(formState) || isTextArea(formState)))
"
v-model:value="formState"
/>
</div>
<div
v-if="!props.hideAdditionalOptions && !isVirtualCol(formState.uidt) && (!appInfo.ee || (appInfo.ee && !isXcdbBase(meta!.source_id) && formState.uidt === UITypes.SpecificDBType))"
class="text-xs cursor-pointer text-gray-400 nc-more-options mb-1 mt-4 flex items-center gap-1 justify-end"
@click="advancedOptions = !advancedOptions"
>
{{ advancedOptions ? $t('general.hideAll') : $t('general.showMore') }}
<component :is="advancedOptions ? MdiMinusIcon : MdiPlusIcon" />
</div>
<Transition name="layout" mode="out-in">
<div v-if="advancedOptions" class="overflow-hidden">
<LazySmartsheetColumnAttachmentOptions v-if="appInfo.ee && isAttachment(formState)" v-model:value="formState" />
<LazySmartsheetColumnAdvancedOptions
v-if="formState.uidt !== UITypes.Attachment"
v-model:value="formState"
:advanced-db-options="advancedOptions || formState.uidt === UITypes.SpecificDBType"
/>
</div>
</Transition>
<div
v-if="!props.hideAdditionalOptions && !isVirtualCol(formState.uidt) && (!appInfo.ee || (appInfo.ee && !isXcdbBase(meta!.source_id) && formState.uidt === UITypes.SpecificDBType))"
class="text-xs cursor-pointer text-gray-400 nc-more-options mb-1 mt-4 flex items-center gap-1 justify-end"
@click="advancedOptions = !advancedOptions"
>
{{ advancedOptions ? $t('general.hideAll') : $t('general.showMore') }}
<component :is="advancedOptions ? MdiMinusIcon : MdiPlusIcon" />
</div>
<Transition name="layout" mode="out-in">
<div v-if="advancedOptions" class="overflow-hidden">
<LazySmartsheetColumnAttachmentOptions v-if="appInfo.ee && isAttachment(formState)" v-model:value="formState" />
<LazySmartsheetColumnAdvancedOptions
v-if="formState.uidt !== UITypes.Attachment"
v-model:value="formState"
:advanced-db-options="advancedOptions || formState.uidt === UITypes.SpecificDBType"
/>
</div>
</Transition>
</template>
<template v-if="props.fromTableExplorer">
<a-form-item></a-form-item>

89
packages/nc-gui/components/smartsheet/details/Fields.vue

@ -40,6 +40,8 @@ const { getMeta } = useMetas()
const { meta, view } = useSmartsheetStoreOrThrow()
const isLocked = inject(IsLockedInj, ref(false))
const { openedViewsTab } = storeToRefs(useViewsStore())
const moveOps = ref<moveOp[]>([])
@ -587,6 +589,8 @@ const toggleVisibility = async (checked: boolean, field: Field) => {
useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
if (isLocked.value) return
if (cmdOrCtrl && e.key.toLowerCase() === 's') {
if (openedViewsTab.value !== 'field') return
e.preventDefault()
@ -625,7 +629,11 @@ onKeyDown('ArrowUp', () => {
})
onKeyDown('Delete', () => {
if (isLocked.value) return
if (document.activeElement?.tagName === 'INPUT') return
if (document.activeElement?.tagName === 'TEXTAREA') return
const isDeletedField = fieldStatus(activeField.value) === 'delete'
if (!isDeletedField && activeField.value) {
onFieldDelete(activeField.value)
@ -633,7 +641,11 @@ onKeyDown('Delete', () => {
})
onKeyDown('Backspace', () => {
if (isLocked.value) return
if (document.activeElement?.tagName === 'INPUT') return
if (document.activeElement?.tagName === 'TEXTAREA') return
const isDeletedField = fieldStatus(activeField.value) === 'delete'
if (!isDeletedField && activeField.value) {
onFieldDelete(activeField.value)
@ -659,11 +671,15 @@ const onClickCopyFieldUrl = async (field: ColumnType) => {
const keys = useMagicKeys()
whenever(keys.meta_s, () => {
if (isLocked.value) return
if (!meta.value?.id) return
if (openedViewsTab.value === 'field') saveChanges()
})
whenever(keys.ctrl_s, () => {
if (isLocked.value) return
if (!meta.value?.id) return
if (openedViewsTab.value === 'field') saveChanges()
})
@ -718,9 +734,9 @@ const onFieldOptionUpdate = () => {
</template>
</a-input>
<div class="flex gap-2">
<NcTooltip>
<NcTooltip :disabled="isLocked">
<template #title> {{ `${renderAltOrOptlKey()} + C` }} </template>
<NcButton type="secondary" size="small" class="mr-1" :disabled="loading" @click="addField()">
<NcButton type="secondary" size="small" class="mr-1" :disabled="loading || isLocked" @click="addField()">
<div class="flex items-center gap-2">
<GeneralIcon icon="plus" class="w-3" />
New Field
@ -730,19 +746,22 @@ const onFieldOptionUpdate = () => {
<NcButton
type="secondary"
size="small"
:disabled="!loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1"
:disabled="(!loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1) || isLocked"
@click="clearChanges()"
>
Reset
</NcButton>
<NcTooltip>
<NcTooltip :disabled="isLocked">
<template #title> {{ `${renderCmdOrCtrlKey()} + S` }} </template>
<NcButton
type="primary"
size="small"
:loading="loading"
:disabled="isColumnsValid ? !loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1 : true"
:disabled="
(isColumnsValid ? !loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1 : true) ||
isLocked
"
@click="saveChanges()"
>
Save changes
@ -752,7 +771,7 @@ const onFieldOptionUpdate = () => {
</div>
<div class="flex flex-row rounded-lg border-1 border-gray-200">
<div ref="fieldsListWrapperDomRef" class="nc-scrollbar-md !overflow-auto w-full flex-grow-1 nc-fields-height">
<Draggable v-model="fields" item-key="id" @change="onMove($event)">
<Draggable v-model="fields" :disabled="isLocked" item-key="id" @change="onMove($event)">
<template #item="{ element: field }">
<div
v-if="field.title.toLowerCase().includes(searchQuery.toLowerCase()) && !field.pv"
@ -761,9 +780,16 @@ const onFieldOptionUpdate = () => {
@click="changeField(field, $event)"
>
<div class="flex items-center flex-1 py-2.5 gap-1 w-2/6">
<component :is="iconMap.drag" class="cursor-move !h-3.75 text-gray-600 mr-1" />
<component
:is="iconMap.drag"
class="cursor-move !h-3.75 text-gray-600 mr-1"
:class="{
'opacity-0 !cursor-default': isLocked,
}"
/>
<NcCheckbox
v-if="field.id && viewFieldsMap[field.id]"
:disabled="isLocked"
:checked="
visibilityOps.find((op) => op.column.fk_column_id === field.id)?.visible ?? viewFieldsMap[field.id].show
"
@ -882,27 +908,29 @@ const onFieldOptionUpdate = () => {
</NcButton>
</div>
</NcTooltip>
<a-menu-divider class="my-1.5" />
<a-menu-divider v-if="!isLocked" class="my-1.5" />
</template>
<NcMenuItem key="table-explorer-duplicate" @click="duplicateField(field)">
<Icon class="iconify text-gray-800" icon="lucide:copy" /><span>Duplicate</span>
</NcMenuItem>
<NcMenuItem v-if="!field.pv" key="table-explorer-insert-above" @click="addField(field, true)">
<Icon class="iconify text-gray-800" icon="lucide:arrow-up" /><span>Insert above</span>
</NcMenuItem>
<NcMenuItem key="table-explorer-insert-below" @click="addField(field)">
<Icon class="iconify text-gray-800" icon="lucide:arrow-down" /><span>Insert below</span>
</NcMenuItem>
<a-menu-divider class="my-1.5" />
<NcMenuItem key="table-explorer-delete" class="!hover:bg-red-50" @click="onFieldDelete(field)">
<div class="text-red-500">
<GeneralIcon icon="delete" class="group-hover:text-accent -ml-0.25 -mt-0.75 mr-0.5" />
Delete
</div>
</NcMenuItem>
<template v-if="!isLocked">
<NcMenuItem key="table-explorer-duplicate" @click="duplicateField(field)">
<Icon class="iconify text-gray-800" icon="lucide:copy" /><span>Duplicate</span>
</NcMenuItem>
<NcMenuItem v-if="!field.pv" key="table-explorer-insert-above" @click="addField(field, true)">
<Icon class="iconify text-gray-800" icon="lucide:arrow-up" /><span>Insert above</span>
</NcMenuItem>
<NcMenuItem key="table-explorer-insert-below" @click="addField(field)">
<Icon class="iconify text-gray-800" icon="lucide:arrow-down" /><span>Insert below</span>
</NcMenuItem>
<a-menu-divider class="my-1.5" />
<NcMenuItem key="table-explorer-delete" class="!hover:bg-red-50" @click="onFieldDelete(field)">
<div class="text-red-500">
<GeneralIcon icon="delete" class="group-hover:text-accent -ml-0.25 -mt-0.75 mr-0.5" />
Delete
</div>
</NcMenuItem>
</template>
</NcMenu>
</template>
</NcDropdown>
@ -927,7 +955,13 @@ const onFieldOptionUpdate = () => {
@click="changeField(displayColumn, $event)"
>
<div class="flex items-center flex-1 py-2.5 gap-1 w-2/6">
<component :is="iconMap.drag" class="cursor-move !h-3.75 text-gray-200 mr-1" />
<component
:is="iconMap.drag"
class="cursor-move !h-3.75 text-gray-200 mr-1"
:class="{
'opacity-0 !cursor-default': isLocked,
}"
/>
<NcCheckbox :disabled="true" :checked="true" />
<SmartsheetHeaderCellIcon
v-if="displayColumn"
@ -1046,6 +1080,7 @@ const onFieldOptionUpdate = () => {
:preload="fieldState(activeField)"
:table-explorer-columns="fields"
embed-mode
:readonly="isLocked"
from-table-explorer
@update="onFieldUpdate"
@add="onFieldAdd"

68
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -188,7 +188,7 @@ const isViewColumnsLoading = computed(() => _isViewColumnsLoading.value || !meta
// #Permissions
const { isUIAllowed } = useRoles()
const hasEditPermission = computed(() => isUIAllowed('dataEdit') && !isLocked.value)
const hasEditPermission = computed(() => isUIAllowed('dataEdit'))
const isAddingColumnAllowed = computed(() => !readOnly.value && !isLocked.value && isUIAllowed('fieldAdd') && !isSqlView.value)
const { onDrag, onDragStart, draggedCol, dragColPlaceholderDomRef, toBeDroppedColId } = useColumnDrag({
@ -351,7 +351,7 @@ async function clearCell(ctx: { row: number; col: number } | null, skipUpdate =
}
function makeEditable(row: Row, col: ColumnType) {
if (!hasEditPermission.value || editEnabled.value || isView || isLocked.value || readOnly.value || isSystemColumn(col)) {
if (!hasEditPermission.value || editEnabled.value || isView || readOnly.value || isSystemColumn(col)) {
return
}
@ -382,9 +382,7 @@ function makeEditable(row: Row, col: ColumnType) {
// #Computed
const isAddingEmptyRowAllowed = computed(
() => !isView && !isLocked.value && hasEditPermission.value && !isSqlView.value && !isPublicView.value,
)
const isAddingEmptyRowAllowed = computed(() => !isView && hasEditPermission.value && !isSqlView.value && !isPublicView.value)
const visibleColLength = computed(() => fields.value?.length)
@ -452,9 +450,7 @@ async function openNewRecordHandler() {
}
const onDraftRecordClick = () => {
if (!isLocked?.value) {
openNewRecordFormHook.trigger()
}
openNewRecordFormHook.trigger()
}
const onNewRecordToGridClick = () => {
@ -987,7 +983,6 @@ const refreshFillHandle = () => {
const showFillHandle = computed(
() =>
!readOnly.value &&
!isLocked.value &&
!editEnabled.value &&
(!selectedRange.isEmpty() || (activeCell.row !== null && activeCell.col !== null)) &&
!dataRef.value[(isNaN(selectedRange.end.row) ? activeCell.row : selectedRange.end.row) ?? -1]?.rowMeta?.new,
@ -1477,7 +1472,7 @@ onKeyStroke('ArrowDown', onDown)
>
<div class="items-center flex gap-1 min-w-[60px]">
<div
v-if="!readOnly || !isLocked || isMobileMode"
v-if="!readOnly || isMobileMode"
class="nc-row-no sm:min-w-4 text-xs text-gray-500"
:class="{ toggle: !readOnly, hidden: row.rowMeta.selected }"
>
@ -1503,29 +1498,28 @@ onKeyStroke('ArrowDown', onDown)
class="!flex items-center"
:data-testid="`row-save-spinner-${rowIndex}`"
/>
<template v-else-if="!isLocked">
<span
v-if="row.rowMeta?.commentCount && expandForm"
v-e="['c:expanded-form:open']"
class="py-1 px-3 rounded-full text-xs cursor-pointer select-none transform hover:(scale-110)"
:style="{ backgroundColor: enumColor.light[row.rowMeta.commentCount % enumColor.light.length] }"
<span
v-if="row.rowMeta?.commentCount && expandForm"
v-e="['c:expanded-form:open']"
class="py-1 px-3 rounded-full text-xs cursor-pointer select-none transform hover:(scale-110)"
:style="{ backgroundColor: enumColor.light[row.rowMeta.commentCount % enumColor.light.length] }"
@click="expandAndLooseFocus(row, state)"
>
{{ row.rowMeta.commentCount }}
</span>
<div
v-else-if="!row.rowMeta.saving"
class="cursor-pointer flex items-center border-1 border-gray-100 active:ring rounded p-1 hover:(bg-gray-50)"
>
<component
:is="iconMap.expand"
v-if="expandForm"
v-e="['c:row-expand:open']"
class="select-none transform hover:(text-black scale-120) nc-row-expand"
@click="expandAndLooseFocus(row, state)"
>
{{ row.rowMeta.commentCount }}
</span>
<div
v-else
class="cursor-pointer flex items-center border-1 border-gray-100 active:ring rounded p-1 hover:(bg-gray-50)"
>
<component
:is="iconMap.expand"
v-if="expandForm"
v-e="['c:row-expand:open']"
class="select-none transform hover:(text-black scale-120) nc-row-expand"
@click="expandAndLooseFocus(row, state)"
/>
</div>
</template>
/>
</div>
</div>
</div>
</td>
@ -1717,16 +1711,14 @@ onKeyStroke('ArrowDown', onDown)
{{ $t('general.clear') }}
</div>
</NcMenuItem>
<NcDivider />
<NcMenuItem
v-if="contextMenuTarget && !isLocked && selectedRange.isSingleCell() && isUIAllowed('commentEdit') && !isMobileMode"
v-if="contextMenuTarget && selectedRange.isSingleCell() && isUIAllowed('commentEdit') && !isMobileMode"
class="nc-base-menu-item"
@click="commentRow(contextMenuTarget.row)"
>
<div v-e="['a:row:comment']" class="flex gap-2 items-center">
<MdiMessageOutline class="h-4 w-4" />
{{ $t('general.comment') }}
</div>
</NcMenuItem>
@ -1810,8 +1802,7 @@ onKeyStroke('ArrowDown', onDown)
>
<div
v-e="['c:row:add:grid']"
:class="{ 'group': !isLocked, 'disabled-ring': isLocked }"
class="px-4 py-3 flex flex-col select-none gap-y-2 cursor-pointer hover:bg-gray-100 text-gray-600 nc-new-record-with-grid"
class="px-4 py-3 flex flex-col select-none gap-y-2 cursor-pointer hover:bg-gray-100 text-gray-600 nc-new-record-with-grid group"
@click="onNewRecordToGridClick"
>
<div class="flex flex-row items-center justify-between w-full">
@ -1827,8 +1818,7 @@ onKeyStroke('ArrowDown', onDown)
</div>
<div
v-e="['c:row:add:form']"
:class="{ 'group': !isLocked, 'disabled-ring': isLocked }"
class="px-4 py-3 flex flex-col select-none gap-y-2 cursor-pointer hover:bg-gray-100 text-gray-600 nc-new-record-with-form"
class="px-4 py-3 flex flex-col select-none gap-y-2 cursor-pointer hover:bg-gray-100 text-gray-600 nc-new-record-with-form group"
@click="onNewRecordToFormClick"
>
<div class="flex flex-row items-center justify-between w-full">

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

@ -17,6 +17,8 @@ const hideMenu = toRef(props, 'hideMenu')
const isForm = inject(IsFormInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false))
const isSurveyForm = inject(IsSurveyFormInj, ref(false))
const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false))
@ -46,12 +48,16 @@ const closeAddColumnDropdown = () => {
}
const openHeaderMenu = () => {
if (isLocked.value) return
if (!isForm.value && !isExpandedForm.value && isUIAllowed('fieldEdit') && !isMobileMode.value) {
editColumnDropdown.value = true
}
}
const openDropDown = (e: Event) => {
if (isLocked.value) return
if (isForm.value || isExpandedForm.value || (!isUIAllowed('fieldEdit') && !isMobileMode.value)) return
e.preventDefault()

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

@ -36,6 +36,8 @@ const editColumnDropdown = ref(false)
const isDropDownOpen = ref(false)
const isLocked = inject(IsLockedInj, ref(false))
provide(ColumnInj, column)
const { metas } = useMetas()
@ -124,12 +126,16 @@ const closeAddColumnDropdown = () => {
}
const openHeaderMenu = () => {
if (isLocked.value) return
if (!isForm.value && !isExpandedForm.value && isUIAllowed('fieldEdit') && !isMobileMode.value) {
editColumnDropdown.value = true
}
}
const openDropDown = (e: Event) => {
if (isLocked.value) return
if (isForm.value || isExpandedForm.value || (!isUIAllowed('fieldEdit') && !isMobileMode.value)) return
e.preventDefault()

28
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue

@ -18,8 +18,6 @@ const { isUIAllowed } = useRoles()
const isPublicView = inject(IsPublicInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false))
const { $api, $e } = useNuxtApp()
const { t } = useI18n()
@ -58,7 +56,7 @@ const quickImportDialogs: Record<(typeof quickImportDialogTypes)[number], Ref<bo
) as Record<QuickImportDialogType, Ref<boolean>>
const onImportClick = (dialog: any) => {
if (isLocked.value) return
if (lockType.value === LockType.Locked) return
emits('closeModal')
dialog.value = true
@ -163,10 +161,17 @@ const onDelete = async () => {
</NcTooltip>
<NcDivider />
<template v-if="!view?.is_default">
<NcMenuItem @click="onRenameMenuClick">
<NcMenuItem v-if="lockType !== LockType.Locked" @click="onRenameMenuClick">
<GeneralIcon icon="edit" />
{{ $t('activity.renameView') }}
</NcMenuItem>
<NcTooltip v-else>
<template #title> {{ $t('msg.info.disabledAsViewLocked') }} </template>
<NcMenuItem class="!cursor-not-allowed !text-gray-400">
<GeneralIcon icon="edit" />
{{ $t('activity.renameView') }}
</NcMenuItem>
</NcTooltip>
<NcMenuItem @click="onDuplicate">
<GeneralIcon icon="duplicate" class="nc-view-copy-icon" />
{{ $t('labels.duplicateView') }}
@ -205,7 +210,7 @@ const onDelete = async () => {
},
]"
class="nc-base-menu-item"
:class="{ disabled: isLocked }"
:class="{ disabled: lockType === LockType.Locked }"
>
<component :is="iconMap.upload" />
{{ `${$t('general.upload')} ${type.toUpperCase()}` }}
@ -274,7 +279,18 @@ const onDelete = async () => {
</NcSubMenu>
<template v-if="!view.is_default">
<NcDivider />
<NcMenuItem class="!hover:bg-red-50 !text-red-500" @click="onDelete">
<NcTooltip v-if="lockType === LockType.Locked">
<template #title> {{ $t('msg.info.disabledAsViewLocked') }} </template>
<NcMenuItem class="!cursor-not-allowed !text-gray-400">
<GeneralIcon icon="delete" class="nc-view-delete-icon" />
{{
$t('general.deleteEntity', {
entity: $t('objects.view'),
})
}}
</NcMenuItem>
</NcTooltip>
<NcMenuItem v-else class="!hover:bg-red-50 !text-red-500" @click="onDelete">
<GeneralIcon icon="delete" class="nc-view-delete-icon" />
{{
$t('general.deleteEntity', {

5
packages/nc-gui/components/virtual-cell/BelongsTo.vue

@ -6,7 +6,6 @@ import {
CellValueInj,
ColumnInj,
IsFormInj,
IsLockedInj,
IsUnderLookupInj,
ReadonlyInj,
ReloadRowDataHookInj,
@ -35,8 +34,6 @@ const readOnly = inject(ReadonlyInj, ref(false))
const isForm = inject(IsFormInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false))
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const { isUIAllowed } = useRoles()
@ -103,7 +100,7 @@ const belongsToColumn = computed(
</div>
<div
v-if="!readOnly && !isLocked && (isUIAllowed('dataEdit') || isForm) && !isUnderLookup"
v-if="!readOnly && (isUIAllowed('dataEdit') || isForm) && !isUnderLookup"
class="flex justify-end gap-1 min-h-[30px] items-center"
>
<GeneralIcon

5
packages/nc-gui/components/virtual-cell/HasMany.vue

@ -5,7 +5,6 @@ import {
CellValueInj,
ColumnInj,
IsFormInj,
IsLockedInj,
IsUnderLookupInj,
ReadonlyInj,
ReloadRowDataHookInj,
@ -31,8 +30,6 @@ const isForm = inject(IsFormInj)
const readOnly = inject(ReadonlyInj, ref(false))
const isLocked = inject(IsLockedInj)
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const listItemsDlg = ref(false)
@ -121,7 +118,7 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
</template>
</div>
<div v-if="!isLocked && !isUnderLookup" class="flex justify-end gap-1 min-h-[30px] items-center">
<div v-if="!isUnderLookup" class="flex justify-end gap-1 min-h-[30px] items-center">
<GeneralIcon
icon="expand"
class="select-none transform text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand"

12
packages/nc-gui/components/virtual-cell/Links.vue

@ -17,8 +17,6 @@ const isForm = inject(IsFormInj)
const readOnly = inject(ReadonlyInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false))
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const colTitle = computed(() => column.value?.title || '')
@ -79,15 +77,13 @@ const onAttachRecord = () => {
const openChildList = () => {
if (isUnderLookup.value) return
if (!isLocked.value) {
childListDlg.value = true
}
childListDlg.value = true
}
useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEvent) => {
switch (e.key) {
case 'Enter':
if (isLocked.value || listItemsDlg.value) return
if (listItemsDlg.value) return
childListDlg.value = true
e.stopPropagation()
break
@ -112,7 +108,7 @@ const openListDlg = () => {
<div class="flex w-full group items-center nc-links-wrapper" @dblclick.stop="openChildList">
<div class="block flex-shrink truncate">
<component
:is="isLocked || isUnderLookup ? 'span' : 'a'"
:is="isUnderLookup ? 'span' : 'a'"
v-e="['c:cell:links:modal:open']"
:title="textVal"
class="text-center nc-datatype-link underline-transparent"
@ -124,7 +120,7 @@ const openListDlg = () => {
</div>
<div class="flex-grow" />
<div v-if="!isLocked && !isUnderLookup" class="!xs:hidden flex justify-end hidden group-hover:flex items-center">
<div v-if="!isUnderLookup" class="!xs:hidden flex justify-end hidden group-hover:flex items-center">
<MdiPlus
v-if="(!readOnly && isUIAllowed('dataEdit')) || isForm"
class="select-none !text-md text-gray-700 nc-action-icon nc-plus"

5
packages/nc-gui/components/virtual-cell/ManyToMany.vue

@ -6,7 +6,6 @@ import {
CellValueInj,
ColumnInj,
IsFormInj,
IsLockedInj,
IsUnderLookupInj,
ReadonlyInj,
ReloadRowDataHookInj,
@ -33,8 +32,6 @@ const isForm = inject(IsFormInj)
const readOnly = inject(ReadonlyInj, ref(false))
const isLocked = inject(IsLockedInj)
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const listItemsDlg = ref(false)
@ -123,7 +120,7 @@ const m2mColumn = computed(
</template>
</div>
<div v-if="(!isLocked && !isUnderLookup) || isForm" class="flex justify-end gap-1 min-h-[30px] items-center">
<div v-if="!isUnderLookup || isForm" class="flex justify-end gap-1 min-h-[30px] items-center">
<GeneralIcon
icon="expand"
class="text-sm nc-action-icon text-gray-500/50 hover:text-gray-500 nc-arrow-expand"

11
packages/nc-gui/components/virtual-cell/components/ItemChip.vue

@ -4,7 +4,6 @@ import { UITypes, isVirtualCol } from 'nocodb-sdk'
import {
ActiveCellInj,
IsFormInj,
IsLockedInj,
ReadonlyInj,
iconMap,
inject,
@ -37,13 +36,11 @@ const active = inject(ActiveCellInj, ref(false))
const isForm = inject(IsFormInj)!
const isLocked = inject(IsLockedInj, ref(false))
const { open } = useExpandedFormDetached()
function openExpandedForm() {
const rowId = extractPkFromRow(item, relatedTableMeta.value.columns as ColumnType[])
if (!readOnly.value && !isLocked.value && !readonlyProp && rowId) {
if (!readOnly.value && !readonlyProp && rowId) {
open({
isOpen: true,
row: { row: item, rowMeta: {}, oldRow: { ...item } },
@ -98,11 +95,7 @@ export default {
</template>
</span>
<div
v-show="active || isForm"
v-if="showUnlinkButton && !readOnly && !isLocked && isUIAllowed('dataEdit')"
class="flex items-center"
>
<div v-show="active || isForm" v-if="showUnlinkButton && !readOnly && isUIAllowed('dataEdit')" class="flex items-center">
<component
:is="iconMap.closeThick"
class="nc-icon unlink-icon text-xs text-gray-500/50 group-hover:text-gray-500"

1
packages/nc-gui/lang/en.json

@ -1033,6 +1033,7 @@
"duplicateTable": "Are you sure you want to duplicate the table?"
},
"info": {
"disabledAsViewLocked": "Disabled as View is locked",
"basesMigrated": "Bases are migrated. Please try again.",
"pasteNotSupported": "Paste operation is not supported on the active cell",
"roles": {

3
packages/nc-gui/store/views.ts

@ -110,6 +110,8 @@ export const useViewsStore = defineStore('viewsStore', () => {
},
})
const isActiveViewLocked = computed(() => activeView.value?.lock_type === 'locked')
// Used for Grid View Pagination
const isPaginationLoading = ref(true)
@ -317,6 +319,7 @@ export const useViewsStore = defineStore('viewsStore', () => {
removeFromRecentViews,
activeSorts,
activeNestedFilters,
isActiveViewLocked,
}
})

2
tests/playwright/pages/Dashboard/Grid/index.ts

@ -55,7 +55,7 @@ export class GridPage extends BasePage {
async verifyLockMode() {
// add new row button
expect(await this.btn_addNewRow.count()).toBe(0);
expect(await this.btn_addNewRow.count()).toBe(1);
await this.toolbar.verifyLockMode();
await this.footbar.verifyLockMode();

2
tests/playwright/pages/Dashboard/common/Footbar/index.ts

@ -45,7 +45,7 @@ export class FootbarPage extends BasePage {
async verifyLockMode() {
// add record button
await expect(this.btn_addNewRow).toBeVisible({ visible: false });
await expect(this.btn_addNewRow).toBeVisible({ visible: true });
}
async verifyCollaborativeMode() {

3
tests/playwright/pages/Dashboard/common/Toolbar/ViewMenu.ts

@ -86,6 +86,9 @@ export class ToolbarViewMenuPage extends BasePage {
// todo: Move verification out of the click method
async click({ menu, subMenu, verificationInfo }: { menu: string; subMenu?: string; verificationInfo?: any }) {
await this.viewsMenuBtn.click();
await this.rootPage.waitForTimeout(1000);
await this.get().locator(`.ant-dropdown-menu-title-content:has-text("${menu}")`).first().click();
if (subMenu) {
// for CSV download, pass locator instead of clicking it here

4
tests/playwright/tests/db/general/viewMenu.spec.ts

@ -23,7 +23,7 @@ test.describe('Grid view locked', () => {
// enable view lock
await dashboard.grid.toolbar.viewsMenu.click({
menu: 'Collaborative',
menu: 'View Mode',
subMenu: 'Locked',
});
@ -32,7 +32,7 @@ test.describe('Grid view locked', () => {
// enable collaborative view
await dashboard.grid.toolbar.viewsMenu.click({
menu: 'Locked',
menu: 'View Mode',
subMenu: 'Collaborative',
});

Loading…
Cancel
Save