mirror of https://github.com/nocodb/nocodb
Browse Source
* feat(nc-gui): form field in right pannel setup * fix(nc-gui): inline form field reorder issue * fix(nc-gui): make edit form field right panel scrollable * fix(nc-gui): form field limit option hide btn focus box shadow style issue * fix(nc-gui): add support to edit form column in form view builder * feat(nc-gui): added form field header menu dropdown * fix(nc-gui): tab issue in form builder * feat(nc-gui): add support to edit column from form builder itself * fix(nc-gui): wrong virtual cell icon in column delete modal * feat(nc-gui): column edit, hide, delete option in form builder field settings * fix(nc-gui): move all form field settings radio btns to the right side * chore(nc-gui): lint * chore(nc-gui): lint errors * chore(nc-gui): lint * fix(nc-gui): update 'change icon color' text case * fix(nc-gui): small changes * fix(nc-gui): form builder side panel field div key issue * fix(nc-gui): form view outsideclick fild toggle issue * chore(nc-gui): lint * fix(nc-gui): hide select dropdown in value is selected and show if value is not selected * fix(nc-gui): suggested review changes * fix(nc-gui): make form field rich text options sticky at bottom * chore(nc-gui): lint * fix(nc-gui): small changes * fix(nc-gui): lazy import richtext component * fix(nc-gui): set the max height for form rich text fields * fix(nc-gui): move form settings switch inputs to the right side * fix(nc-gui): move form select type field layout option to appearance settings section * fix(nc-gui): select form active field text on focus * fix(nc-gui): form rich text element menu option tabindex issue * fix(nc-gui): form search field input autofill issue * fix(nc-gui): update position of rich text menu option of form description * feat(nc-gui): adjustable form view sidebar width * chore(nc-gui): lint * fix(nc-gui): typo mistake * fix(nc-gui): PR review changesnc-enable-calendar-view
Ramesh Mane
8 months ago
committed by
GitHub
15 changed files with 1603 additions and 1039 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,248 @@
|
||||
<script lang="ts" setup> |
||||
import type { ColumnReqType, ColumnType } from 'nocodb-sdk' |
||||
import { UITypes } from 'nocodb-sdk' |
||||
import { computed } from 'vue' |
||||
import { |
||||
ActiveViewInj, |
||||
ColumnInj, |
||||
IsLockedInj, |
||||
MetaInj, |
||||
ReloadViewDataHookInj, |
||||
SmartsheetStoreEvents, |
||||
iconMap, |
||||
inject, |
||||
message, |
||||
toRefs, |
||||
useI18n, |
||||
useMetas, |
||||
useNuxtApp, |
||||
useSmartsheetStoreOrThrow, |
||||
} from '#imports' |
||||
|
||||
const props = defineProps<{ |
||||
column: ColumnType |
||||
formColumn: Record<string, any> |
||||
isRequired?: boolean |
||||
isOpen: boolean |
||||
onDelete: () => void |
||||
}>() |
||||
|
||||
const emit = defineEmits(['hideField', 'update:isOpen', 'delete']) |
||||
|
||||
const { column, isRequired } = toRefs(props) |
||||
|
||||
const isOpen = useVModel(props, 'isOpen', emit) |
||||
|
||||
const { eventBus } = useSmartsheetStoreOrThrow() |
||||
|
||||
const reloadDataHook = inject(ReloadViewDataHookInj) |
||||
|
||||
const meta = inject(MetaInj, ref()) |
||||
|
||||
const view = inject(ActiveViewInj, ref()) |
||||
|
||||
const isLocked = inject(IsLockedInj) |
||||
|
||||
provide(ColumnInj, column) |
||||
|
||||
const { $api } = useNuxtApp() |
||||
|
||||
const { t } = useI18n() |
||||
|
||||
const { getMeta } = useMetas() |
||||
|
||||
const showDeleteColumnModal = ref(false) |
||||
|
||||
const isDuplicateDlgOpen = ref(false) |
||||
const selectedColumnExtra = ref<any>() |
||||
const duplicateDialogRef = ref<any>() |
||||
|
||||
const duplicateVirtualColumn = async () => { |
||||
let columnCreatePayload = {} |
||||
|
||||
// generate duplicate column title |
||||
const duplicateColumnTitle = getUniqueColumnName(`${column!.value.title} copy`, meta!.value!.columns!) |
||||
|
||||
columnCreatePayload = { |
||||
...column!.value!, |
||||
...(column!.value.colOptions ?? {}), |
||||
title: duplicateColumnTitle, |
||||
column_name: duplicateColumnTitle.replace(/\s/g, '_'), |
||||
id: undefined, |
||||
colOptions: undefined, |
||||
order: undefined, |
||||
system: false, |
||||
} |
||||
|
||||
try { |
||||
const gridViewColumnList = (await $api.dbViewColumn.list(view.value?.id as string)).list |
||||
|
||||
const currentColumnIndex = gridViewColumnList.findIndex((f) => f.fk_column_id === column!.value.id) |
||||
let newColumnOrder |
||||
if (currentColumnIndex === gridViewColumnList.length - 1) { |
||||
newColumnOrder = gridViewColumnList[currentColumnIndex].order! + 1 |
||||
} else { |
||||
newColumnOrder = (gridViewColumnList[currentColumnIndex].order! + gridViewColumnList[currentColumnIndex + 1].order!) / 2 |
||||
} |
||||
|
||||
await $api.dbTableColumn.create(meta!.value!.id!, { |
||||
...columnCreatePayload, |
||||
pv: false, |
||||
view_id: view.value!.id as string, |
||||
column_order: { |
||||
order: newColumnOrder, |
||||
view_id: view.value!.id as string, |
||||
}, |
||||
} as ColumnReqType) |
||||
await getMeta(meta!.value!.id!, true) |
||||
|
||||
eventBus.emit(SmartsheetStoreEvents.FIELD_RELOAD) |
||||
reloadDataHook?.trigger() |
||||
|
||||
// message.success(t('msg.success.columnDuplicated')) |
||||
} catch (e) { |
||||
message.error(await extractSdkResponseErrorMsg(e)) |
||||
} |
||||
// closing dropdown |
||||
isOpen.value = false |
||||
} |
||||
|
||||
const openDuplicateDlg = async () => { |
||||
if (!column?.value) return |
||||
if ( |
||||
column.value.uidt && |
||||
[ |
||||
UITypes.Lookup, |
||||
UITypes.Rollup, |
||||
UITypes.CreatedTime, |
||||
UITypes.LastModifiedTime, |
||||
UITypes.CreatedBy, |
||||
UITypes.LastModifiedBy, |
||||
].includes(column.value.uidt as UITypes) |
||||
) { |
||||
duplicateVirtualColumn() |
||||
} else { |
||||
const gridViewColumnList = (await $api.dbViewColumn.list(view.value?.id as string)).list |
||||
|
||||
const currentColumnIndex = gridViewColumnList.findIndex((f) => f.fk_column_id === column!.value.id) |
||||
let newColumnOrder |
||||
if (currentColumnIndex === gridViewColumnList.length - 1) { |
||||
newColumnOrder = gridViewColumnList[currentColumnIndex].order! + 1 |
||||
} else { |
||||
newColumnOrder = (gridViewColumnList[currentColumnIndex].order! + gridViewColumnList[currentColumnIndex + 1].order!) / 2 |
||||
} |
||||
|
||||
selectedColumnExtra.value = { |
||||
pv: false, |
||||
view_id: view.value!.id as string, |
||||
column_order: { |
||||
order: newColumnOrder, |
||||
view_id: view.value!.id as string, |
||||
}, |
||||
} |
||||
|
||||
if (column.value.uidt === UITypes.Formula) { |
||||
nextTick(() => { |
||||
duplicateDialogRef?.value?.duplicate() |
||||
}) |
||||
} else { |
||||
isDuplicateDlgOpen.value = true |
||||
} |
||||
|
||||
isOpen.value = false |
||||
} |
||||
} |
||||
|
||||
// hide the field in view |
||||
const hideField = async () => { |
||||
if (isRequired.value) return |
||||
isOpen.value = false |
||||
emit('hideField') |
||||
} |
||||
|
||||
const handleDelete = () => { |
||||
// closing the dropdown |
||||
// when modal opens |
||||
isOpen.value = false |
||||
showDeleteColumnModal.value = true |
||||
} |
||||
|
||||
const isDeleteAllowed = computed(() => { |
||||
return column?.value && !column.value.system |
||||
}) |
||||
const isDuplicateAllowed = computed(() => { |
||||
return column?.value && !column.value.system |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<a-dropdown |
||||
v-if="!isLocked" |
||||
v-model:visible="isOpen" |
||||
:trigger="['click']" |
||||
placement="bottomLeft" |
||||
overlay-class-name="nc-dropdown-form-column-operations !border-1 rounded-lg !shadow-xl" |
||||
@click.stop="isOpen = !isOpen" |
||||
> |
||||
<NcButton |
||||
type="secondary" |
||||
size="small" |
||||
class="nc-form-add-field" |
||||
data-testid="nc-form-add-field" |
||||
@click.stop="showAddColumnDropdown = true" |
||||
> |
||||
<component :is="iconMap.threeDotVertical" class="flex-none w-4 h-4" /> |
||||
</NcButton> |
||||
<template #overlay> |
||||
<NcMenu class="flex flex-col gap-1 border-gray-200 nc-column-options"> |
||||
<!-- Todo: Duplicate column with form column settings --> |
||||
<!-- eslint-disable vue/no-constant-condition --> |
||||
<NcMenuItem v-if="false" :disabled="!isDuplicateAllowed" @click="openDuplicateDlg"> |
||||
<div class="nc-column-duplicate nc-form-header-menu-item"> |
||||
<component :is="iconMap.duplicate" /> |
||||
<!-- Duplicate --> |
||||
{{ t('general.duplicate') }} |
||||
</div> |
||||
</NcMenuItem> |
||||
|
||||
<NcMenuItem :disabled="isRequired" @click="hideField"> |
||||
<div class="nc-column-insert-before nc-form-header-menu-item"> |
||||
<component :is="iconMap.eye" class="!w-3.75 !h-3.75" /> |
||||
<!-- Hide Field --> |
||||
{{ $t('general.hideField') }} |
||||
</div> |
||||
</NcMenuItem> |
||||
|
||||
<template v-if="!column?.pv"> |
||||
<a-divider class="!my-0" /> |
||||
|
||||
<NcMenuItem :disabled="!isDeleteAllowed" class="!hover:bg-red-50" @click="handleDelete"> |
||||
<div class="nc-column-delete nc-form-header-menu-item text-red-600"> |
||||
<component :is="iconMap.delete" /> |
||||
<!-- Delete --> |
||||
{{ $t('general.delete') }} |
||||
</div> |
||||
</NcMenuItem> |
||||
</template> |
||||
</NcMenu> |
||||
</template> |
||||
</a-dropdown> |
||||
<SmartsheetHeaderDeleteColumnModal |
||||
v-model:visible="showDeleteColumnModal" |
||||
class="nc-form-column-delete-dropdown" |
||||
:on-delete-column="onDelete" |
||||
/> |
||||
<DlgColumnDuplicate |
||||
v-if="column" |
||||
ref="duplicateDialogRef" |
||||
v-model="isDuplicateDlgOpen" |
||||
:column="column" |
||||
:extra="selectedColumnExtra" |
||||
/> |
||||
</template> |
||||
|
||||
<style scoped> |
||||
.nc-form-header-menu-item { |
||||
@apply flex items-center gap-2; |
||||
} |
||||
</style> |
@ -0,0 +1,94 @@
|
||||
<script lang="ts" setup> |
||||
import { Pane, Splitpanes } from 'splitpanes' |
||||
import 'splitpanes/dist/splitpanes.css' |
||||
|
||||
const { leftSidebarWidth, windowSize, formRightSidebarState, formRightSidebarWidthPercent } = storeToRefs(useSidebarStore()) |
||||
|
||||
const formPreviewSize = computed(() => 100 - formRightSidebarWidthPercent.value) |
||||
|
||||
function onResize(widthPercent: any) { |
||||
const sidebarWidth = (widthPercent * (windowSize.value - leftSidebarWidth.value)) / 100 |
||||
|
||||
if (sidebarWidth > formRightSidebarState.value.maxWidth) { |
||||
formRightSidebarState.value.width = formRightSidebarState.value.maxWidth |
||||
} else if (sidebarWidth < formRightSidebarState.value.minWidth) { |
||||
formRightSidebarState.value.width = formRightSidebarState.value.minWidth |
||||
} else { |
||||
formRightSidebarState.value.width = sidebarWidth |
||||
} |
||||
} |
||||
|
||||
const normalizeSidebarWidth = computed(() => { |
||||
if (formRightSidebarState.value.width > formRightSidebarState.value.maxWidth) { |
||||
return formRightSidebarState.value.maxWidth |
||||
} else if (formRightSidebarState.value.width < formRightSidebarState.value.minWidth) { |
||||
return formRightSidebarState.value.minWidth |
||||
} else { |
||||
return formRightSidebarState.value.width |
||||
} |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<Splitpanes |
||||
class="nc-form-right-sidebar-content-resizable-wrapper w-full h-full" |
||||
@resize="(event: any) => onResize(event[1].size)" |
||||
> |
||||
<Pane :size="formPreviewSize" class="flex-1 h-full"> |
||||
<slot name="preview" /> |
||||
</Pane> |
||||
<Pane |
||||
min-size="15%" |
||||
class="nc-sidebar-splitpane relative" |
||||
:size="formRightSidebarWidthPercent" |
||||
:style="{ |
||||
minWidth: `${formRightSidebarState.minWidth}px !important`, |
||||
maxWidth: `${normalizeSidebarWidth}px !important`, |
||||
}" |
||||
> |
||||
<slot name="sidebar" /> |
||||
</Pane> |
||||
</Splitpanes> |
||||
</template> |
||||
|
||||
<style lang="scss"> |
||||
/** Split pane CSS */ |
||||
|
||||
.nc-form-right-sidebar-content-resizable-wrapper > { |
||||
.splitpanes__splitter { |
||||
@apply !w-0 relative overflow-visible; |
||||
} |
||||
.splitpanes__splitter:before { |
||||
@apply bg-gray-200 w-0.25 absolute left-0 top-0 h-full z-40; |
||||
content: ''; |
||||
} |
||||
|
||||
.splitpanes__splitter:hover:before { |
||||
@apply bg-scrollbar; |
||||
width: 3px !important; |
||||
left: 0px; |
||||
} |
||||
|
||||
.splitpanes--dragging .splitpanes__splitter:before { |
||||
@apply bg-scrollbar; |
||||
width: 3px !important; |
||||
left: 0px; |
||||
} |
||||
|
||||
.splitpanes--dragging .splitpanes__splitter { |
||||
@apply w-1 mr-0; |
||||
} |
||||
} |
||||
|
||||
.splitpanes__pane { |
||||
transition: width 0.15s ease-in-out !important; |
||||
} |
||||
|
||||
.splitpanes--dragging { |
||||
cursor: col-resize; |
||||
|
||||
> .splitpanes__pane { |
||||
transition: none !important; |
||||
} |
||||
} |
||||
</style> |
Loading…
Reference in new issue