mirror of
https://github.com/nocodb/nocodb.git
synced 2026-05-30 05:40:19 +00:00
feat: expanded record as right-side slide-in panel
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
// CE stub — EE override at ee/components/smartsheet/grid/ExpandedFormPanel.vue
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcSpanHidden />
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
// CE stub — EE override at ee/components/smartsheet/grid/ExpandedFormPanelActivity.vue
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NcSpanHidden />
|
||||
</template>
|
||||
@@ -20,10 +20,17 @@ const { xWhere, eventBus, isExternalSource } = useSmartsheetStoreOrThrow()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { isMobileMode } = useGlobal()
|
||||
|
||||
const { isFeatureEnabled } = useBetaFeatureToggle()
|
||||
|
||||
const { blockExternalSourceRecordVisibility, showUpgradeToSeeMoreRecordsModal } = useEeConfig()
|
||||
|
||||
// --- Expanded form panel (right-side slide-in) ---
|
||||
const expandedFormPanelStore = useProvideExpandedFormPanel()
|
||||
|
||||
const { isOpen: isExpandedFormPanelOpen, rowNavigator: expandedFormPanelRowNavigator } = expandedFormPanelStore
|
||||
|
||||
const bulkUpdateDlg = ref(false)
|
||||
|
||||
const routeQuery = computed(() => route.value.query as Record<string, string>)
|
||||
@@ -191,6 +198,29 @@ const skipRowRemovalOnCancel = ref(false)
|
||||
|
||||
function expandForm(row: Row, state?: Record<string, any>, fromToolbar = false, path: Array<number> = []) {
|
||||
const rowId = extractPkFromRow(row.row, meta.value?.columns as ColumnType[])
|
||||
|
||||
// EE desktop: open the right-side panel instead of modal
|
||||
if (isEeUI && !isMobileMode.value && !isPublic.value && rowId) {
|
||||
expandedFormPanelStore.openPanel(row, undefined, state)
|
||||
|
||||
// Update route for deep-linking
|
||||
const routeParams = {
|
||||
query: {
|
||||
...routeQuery.value,
|
||||
rowId,
|
||||
path: ncIsEmptyArray(path) ? undefined : path.join('-'),
|
||||
expand: undefined,
|
||||
},
|
||||
}
|
||||
if (routeQuery.value.expand) {
|
||||
router.replace(routeParams)
|
||||
} else {
|
||||
router.push(routeParams)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Fallback: existing modal behavior (CE, mobile, public, new rows)
|
||||
expandedFormRowState.value = state
|
||||
if (rowId && !isPublic.value) {
|
||||
expandedFormRow.value = undefined
|
||||
@@ -204,11 +234,9 @@ function expandForm(row: Row, state?: Record<string, any>, fromToolbar = false,
|
||||
colId: undefined,
|
||||
cellMode: undefined,
|
||||
path: ncIsEmptyArray(path) ? undefined : path.join('-'),
|
||||
// Remove expand from query to avoid triggering the expanded form on closing the dialog
|
||||
expand: undefined,
|
||||
},
|
||||
}
|
||||
// if expand is true, replace the route to avoid adding a new history entry
|
||||
if (routeQuery.value.expand) {
|
||||
router.replace(routeParams)
|
||||
} else {
|
||||
@@ -233,6 +261,8 @@ defineExpose({
|
||||
const expandedFormOnRowIdDlg = computed({
|
||||
get() {
|
||||
if (!routeQuery.value.rowId) return false
|
||||
// When the side panel is open, don't trigger the modal
|
||||
if (isExpandedFormPanelOpen.value) return false
|
||||
// When ?colId points at a SmartText column the SmartText panel claims
|
||||
// the URL — expanded record dialog stays closed.
|
||||
const colId = routeQuery.value.colId
|
||||
@@ -243,7 +273,11 @@ const expandedFormOnRowIdDlg = computed({
|
||||
return true
|
||||
},
|
||||
set(val) {
|
||||
if (!val)
|
||||
if (!val) {
|
||||
// Close panel if it's open
|
||||
if (isExpandedFormPanelOpen.value) {
|
||||
expandedFormPanelStore.closePanel()
|
||||
}
|
||||
router.push({
|
||||
query: {
|
||||
...routeQuery.value,
|
||||
@@ -251,9 +285,20 @@ const expandedFormOnRowIdDlg = computed({
|
||||
rowId: undefined,
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// Close panel when route rowId is cleared (e.g. browser back)
|
||||
watch(
|
||||
() => routeQuery.value.rowId,
|
||||
(newRowId) => {
|
||||
if (!newRowId && isExpandedFormPanelOpen.value) {
|
||||
expandedFormPanelStore.closePanel()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const addRowExpandOnClose = (row: Row) => {
|
||||
if (!skipRowRemovalOnCancel.value) {
|
||||
eventBus.emit(SmartsheetStoreEvents.CLEAR_NEW_ROW, row)
|
||||
@@ -306,6 +351,31 @@ const updateViewWidth = () => {
|
||||
|
||||
const isInfiniteScrollingEnabled = computed(() => isFeatureEnabled(FEATURE_FLAG.INFINITE_SCROLLING))
|
||||
|
||||
// Wire row navigator for the expanded form panel
|
||||
expandedFormPanelRowNavigator.value = {
|
||||
getRow: (index: number) => {
|
||||
if (isInfiniteScrollingEnabled.value) {
|
||||
const row = cachedRows.value.get(index)
|
||||
if (!row) return null
|
||||
const rowId = extractPkFromRow(row.row, meta.value?.columns as ColumnType[])
|
||||
if (!rowId) return null
|
||||
return { rowId, row }
|
||||
} else {
|
||||
const row = pData.value[index]
|
||||
if (!row) return null
|
||||
const rowId = extractPkFromRow(row.row, meta.value?.columns as ColumnType[])
|
||||
if (!rowId) return null
|
||||
return { rowId, row }
|
||||
}
|
||||
},
|
||||
totalRows: () => {
|
||||
if (isInfiniteScrollingEnabled.value) {
|
||||
return totalRows.value ?? 0
|
||||
}
|
||||
return pData.value.length
|
||||
},
|
||||
}
|
||||
|
||||
const isCanvasTableEnabled = computed(() => !ncIsPlaywright())
|
||||
|
||||
const isCanvasGroupByTableEnabled = computed(
|
||||
@@ -622,6 +692,9 @@ watch([() => view.value?.id, () => meta.value?.columns], async () => {
|
||||
</div>
|
||||
|
||||
<SmartsheetGridSmartTextPanel />
|
||||
|
||||
<!-- Right-side expanded form panel -->
|
||||
<SmartsheetGridExpandedFormPanel />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
57
packages/nc-gui/composables/useExpandedFormPanel.ts
Normal file
57
packages/nc-gui/composables/useExpandedFormPanel.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* CE stub for expanded form panel composable.
|
||||
* EE override at ee/composables/useExpandedFormPanel.ts provides the real implementation.
|
||||
*/
|
||||
|
||||
const [useProvideExpandedFormPanel, useExpandedFormPanel] = useInjectionState(() => {
|
||||
const isOpen = ref(false)
|
||||
const activeRowId = ref<string | null>(null)
|
||||
const activeRowIndex = ref<number | null>(null)
|
||||
const isFullscreen = ref(false)
|
||||
const panelWidth = ref(420)
|
||||
const isLoading = ref(false)
|
||||
const activityExpanded = ref(false)
|
||||
const activeActivityTab = ref<'comments' | 'audits'>('comments')
|
||||
|
||||
const hasPrev = computed(() => false)
|
||||
const hasNext = computed(() => false)
|
||||
const activeDisplayValue = computed(() => null)
|
||||
|
||||
const rowNavigator = ref(null)
|
||||
|
||||
const openPanel = (_row: Row, _rowIndex?: number, _state?: Record<string, any>) => {}
|
||||
const closePanel = () => {}
|
||||
const setFullscreen = (_val: boolean) => {}
|
||||
const navigatePrev = () => {}
|
||||
const navigateNext = () => {}
|
||||
const toggleActivity = (_tab?: 'comments' | 'audits') => {}
|
||||
|
||||
return {
|
||||
isOpen,
|
||||
activeRowId,
|
||||
activeRowIndex,
|
||||
isFullscreen,
|
||||
panelWidth,
|
||||
isLoading,
|
||||
activityExpanded,
|
||||
activeActivityTab,
|
||||
hasPrev,
|
||||
hasNext,
|
||||
activeDisplayValue,
|
||||
rowNavigator,
|
||||
openPanel,
|
||||
closePanel,
|
||||
setFullscreen,
|
||||
navigatePrev,
|
||||
navigateNext,
|
||||
toggleActivity,
|
||||
}
|
||||
}, 'expanded-form-panel-store')
|
||||
|
||||
export { useProvideExpandedFormPanel, useExpandedFormPanel }
|
||||
|
||||
export function useExpandedFormPanelOrThrow() {
|
||||
const store = useExpandedFormPanel()
|
||||
if (!store) throw new Error('useExpandedFormPanel must be used within a provider')
|
||||
return store
|
||||
}
|
||||
Reference in New Issue
Block a user