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