多维表格
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1451 lines
48 KiB

<script setup lang="ts">
import { diff } from 'deep-object-diff'
import { message } from 'ant-design-vue'
import { UITypes, isLinksOrLTAR, isSystemColumn, isVirtualCol, readonlyMetaAllowedTypes } from 'nocodb-sdk'
Nc feat/links view filter (#8646) * feat(nocodb): add support for limiting selection to specific views * test: fix failing tests * fix: failing playwright tests * feat: allow updating static view filter from both sides * fix: remove console logs * refactor: rename migration name * fix: corrections in ui and update api * fix: apply same behaviour for LTAR column(bt) * refactor: rename view id column in relation to avoid confusion * fix: option to disable view filter(switch) * refactor: some minor ui spacing corrections * fix: avoid setting target view id for bt relation when creating hm relation * feat: links - record selection based on custom filters * fix: corrections * feat: add edit support for conditions * feat: option to switch between dynamic and static value * fix: backend corrections * feat: apis for links filter * feat: filter api integration with ui * feat: filter with save and update * feat: dynamic filter * feat: shared form filter * feat: expanded form * fix: missing imports and corrections * fix: pass correct column list * fix: nested filter bug * fix: corrections in actions and swagger * fix: missing add button menu * fix: expanded form bug * test: playwright test - WIP * test: playwright - link with filters/view * chore: lint * refactor: ui corrections * fix: remove unnecessary filtering from hm/mm list and count * fix: filter ui correction * fix: lable correction * fix: skip view filter for rollup * fix: ui corrections * fix: extract correct column id * fix: duplicate LTAR - missing target view * feat: add duplicate support for link with filters/view * fix: height issue and nested filter creation bug * fix: pass metadata to nested filter component * fix: filter on column creation * fix: filter getting cloned under group * fix: exclude deleted filters when deciding locked state * fix: update state when switching to dynamic filter * fix: unlink view on delete and handle undefined values as null * fix: filter based on unsaved data * fix: handle overflow * fix: multi-field editor - filter UI correction * fix: duplicate link column with dynamic field ref * fix: remove virtual column support * fix: add support to link filter in normal list method * fix: apply filter on count query * fix: pass correct column list * feat: add link filter support in multifield column creation * feat: add link filter support in multifield column creation * Merge branch 'develop' into feat/links-view-filter * fix: dynamic value column export * fix: review comments * test: kludge for groupby tests * fix: extract updated status correctly * test: try waitFor for links * test: kludge * refactor: exclude attachment & rating from dynamic filter and treat float and integer as number * test: label correction * refactor: replace try...catch and use if condition * fix: apply conditions only if enabled * fix: MFE bugs * refactor: show radio button active border only when focused * fix: proper state handling * fix: view delete - unlink from link column * fix: duplicate Link with filter view id * refactor: column filter section padding * fix: exclude system columns * fix: dynamic column filter logic correction * refactor: cleanup * test: kludge with delay for groupby test * refactor: add missing placeholder method * docs: limit link record selection * refactor: add missing placeholder method * chore: lint --------- Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
7 months ago
import type { ColumnType, FilterType, SelectOptionsType } from 'nocodb-sdk'
import Draggable from 'vuedraggable'
import { onKeyDown, useMagicKeys } from '@vueuse/core'
import { generateUniqueColumnName } from '~/helpers/parsers/parserHelpers'
interface TableExplorerColumn extends ColumnType {
id?: string
temp_id?: string
column_order?: {
order: number
view_id: string
}
view_id?: string
userHasChangedTitle?: boolean
}
interface op {
op: 'add' | 'update' | 'delete'
column: TableExplorerColumn
}
interface fieldsVisibilityOps {
visible: boolean
column: TableExplorerColumn
}
interface moveOp {
op: 'move'
column: TableExplorerColumn
index: number
order: number
}
const { t } = useI18n()
const { $api } = useNuxtApp()
const { getMeta } = useMetas()
const { meta, view } = useSmartsheetStoreOrThrow()
const isLocked = inject(IsLockedInj, ref(false))
const { openedViewsTab } = storeToRefs(useViewsStore())
const localMetaColumns = ref<ColumnType[] | undefined>([])
const moveOps = ref<moveOp[]>([])
const visibilityOps = ref<fieldsVisibilityOps[]>([])
const fieldsListWrapperDomRef = ref<HTMLElement>()
const { copy } = useClipboard()
const { fields: viewFields, toggleFieldVisibility, loadViewColumns, isViewColumnsLoading } = useViewColumnsOrThrow()
const loading = ref(false)
const columnsHash = ref<string>()
const newFields = ref<TableExplorerColumn[]>([])
const isFieldIdCopied = ref(false)
const compareCols = (a?: TableExplorerColumn, b?: TableExplorerColumn) => {
if (a?.id && b?.id) {
return a.id === b.id
} else if (a?.temp_id && b?.temp_id) {
return a.temp_id === b.temp_id
}
return false
}
const viewFieldsMap = computed<Record<string, Field>>(() => {
const temp: Record<string, Field> = {}
if (viewFields.value) {
for (const field of viewFields.value) {
if (field.fk_column_id) temp[field.fk_column_id] = field
}
}
return temp
})
const getFieldOrder = (field?: TableExplorerColumn) => {
if (!field) return -1
const mop = moveOps.value.find((op) => compareCols(op.column, field))
if (mop) {
return mop.order
} else if (field.id) {
const viewField = viewFieldsMap.value[field.id]
if (viewField) {
return viewField.order
}
}
return -1
}
const fields = computed<TableExplorerColumn[]>({
get: () => {
const x = ((localMetaColumns.value as ColumnType[]) ?? [])
.filter((field) => !field.fk_column_id && !isSystemColumn(field))
.concat(newFields.value)
.map((field) => updateDefaultColumnValues(field))
.sort((a, b) => {
return getFieldOrder(a) - getFieldOrder(b)
})
return x
},
set: (val) => {
localMetaColumns.value = localMetaColumns.value?.map((col) => {
const field = val.find((f) => compareCols(f, col))
if (field) {
return field
}
return col
})
},
})
// Current Selected Field
const activeField = ref()
const searchQuery = ref<string>('')
const calculateOrderForIndex = (index: number, fromAbove = false) => {
if (!viewFields.value) return -1
if (index <= 0) {
const pv = fields.value.find((f) => f.pv)
if (pv) {
return pv.order || 0
}
return -1
}
if (index >= fields.value.length - 1) {
const fieldOrders = fields.value.map((f) => getFieldOrder(f))
return Math.max(...fieldOrders) + 1
}
let orderBefore = -1
let orderAfter = -1
const fieldBefore = fields.value[index + (fromAbove ? -1 : 0)]
const fieldAfter = fields.value[index + (fromAbove ? 0 : 1)]
if (fieldBefore) {
orderBefore = getFieldOrder(fieldBefore)
}
if (fieldAfter) {
orderAfter = getFieldOrder(fieldAfter)
if (orderAfter === -1) {
orderAfter = orderBefore + 1
}
}
const order = (orderBefore + orderAfter) / 2
return order
}
// Update, Delete and New Column operations are tracked here
const ops = ref<op[]>([])
const temporaryAddCount = ref(0)
const changingField = ref(false)
const addFieldMoveHook = ref<number>()
const duplicateFieldHook = ref<TableExplorerColumn>()
const setFieldMoveHook = (field: TableExplorerColumn, before = false) => {
const index = fields.value.findIndex((f) => compareCols(f, field))
if (index !== -1) {
addFieldMoveHook.value = before ? index : index + 1
}
}
const { isMetaReadOnly } = useRoles()
const isColumnUpdateAllowed = (column: ColumnType) => {
if (isMetaReadOnly.value && !readonlyMetaAllowedTypes.includes(column?.uidt)) return false
return true
}
const changeField = (field?: TableExplorerColumn, event?: MouseEvent) => {
if (!isColumnUpdateAllowed(field)) {
return message.info(t('msg.info.schemaReadOnly'))
}
Nc feat/new field modal (#8578) * fix(nc-gui): remove field modal title & type selector label * fix(nc-gui): hide default value input initially * fix(nc-gui): remove clear icon from default value input * fix(nc-gui): update email, phone, url column validation settings ui * fix(nc-gui): add missing validate field condition * fix(nc-gui): update long text field modal ui * fix(nc-gui): update default field modal width & enable rich text option * fix(nc-gui): small changes * fix(nc-gui): hide default value input only if user clicks on delete icon * fix(nc-gui): decimal field option ui * fix(nc-gui): update percent option ui * fix(nc-gui): small changes * fix(nc-gui): update field modal switch option alignment * fix(nc-gui): update date & dateTime field modal options * feat(nc-gui): add 12, 24 hrs time format option in field modal * feat(nc-gui): add 12hr time cell format support * fix(nc-gui): update time cell placeholder according to time format * fix(nc-gui): field modal default value option visibility issue * fix(nc-gui): update barcode, qr code field modal option * fix(nc-gui): field modal expanded json input modal overlay click issue * fix(nc-gui): currency code option from field modal * fix(nc-gui): udpate duration option * fix(nc-gui): date time cell clear icon visibility issue in link record dropdown * fix(nc-gui): update field modal lookup options * fix(nc-gui): update user option from field modal * fix(nc-gui): update rollup option from field modal * fix(nc-gui): update select field type ui for create column * fix(nc-gui): update field modal cancel & save btn alignment * fix(nc-gui): update formula option margin * fic(nc-gui): update select type option * fix(nc-gui): small changes * fix(nc-gui): update links field options * fix(nc-gui): small changes * fix(nc-gui): select option border issue * fix(nc-gui): add new color picker * fix(nc-gui): update rating field options * fix(nc-gui): update geodata field options * fix(nc-gui): geodata option small changes * fix(nc-gui): add new color picker for select type options * fix(nc-gui): show only title & field type list if uidt is null * feat(nc-gui): add 12hrs time support in dateTime cell * fix(nc-gui): formula suggestion list visibility issue * fix(nc-gui): reduce formaula suggestion fields icon size * fix(nc-gui): rich text default value visibility issue in field modal * fix(nc-gui): update rich text default value bubble menu option * fix(nc-gui): some pr review changes * fix(nc-gui): remove example from duration format * feat(nc-gui): add keyboard navigation support for field list * fix(nc-gui): update email, url, phone validate text * fix(nc-gui): update qr & barcode value select input * fix(nc-gui): pr review changes * test: update create column test cases * fix(nc-gui): remove required symbol from field modal inputs * fix(nc-gui): remove delete default value icon and add cross icon in default input itself * test: update duration field type test * fix(nc-gui): update column name & type input shadow * fix(nc-gui): add hover effect on selected type * fix(nc-gui): enabel rich text case update * fix(nc-gui): update select options * fix(nc-gui): show full time format in edit modal default value * fix(nc-gui): remove optional placeholder of default value * fix(nc-gui): instead of removing field type option disable it if it is onlyNameUpdateOnEditColumns * fix(nc-gui): update links field icons from field modal * fix(nc-gui): add links options in edit modal * fix(nc-gui): show links config and disable if it is not editable in edit column mode * fix(nc-gui): add support to configure date time format for create & last modified type field * fix(nc-gui): virtual field icon visibility issue if it is edit mode * fix(nc-gui): disabled edit option from field context menu if column is pk or system column * fix(nc-gui): update field modal submit btn text * fix(nc-gui): add shdow on input field in field modal * fix(nc-gui): disable submit btn if field modal has some warnings * test: update field add/edit save test case * test: update links column add/edit test cases * test: uncomment code * test: update user field default value update test cases * test: update select type option default value * test: update multi field editor test cases * test: update kanban view add option test cases * test: update multifield editor test cases * test: update create column keyboard shortcut test case * chore(nc-gui): lint * fix(nc-gui): field modal redio option shadow issue * fix(nc-gui): update field modal select option color picker btn border radius * fix(nc-gui): checkbox & rating icon alignment issue * fix(nc-gui): update field modal formula field * fix(nc-gui): field modal padding and gap issue * fix(nc-gui): update set default value font case & font color * fix(nc-gui): update field modal formula suggestion list ui * fix(nc-gui): removecolumn create field search list from multifield editor * fix(nc-gui): add placeholder for lookup & rollup options * fix: label * fix(nc-gui): remove placeholder from select type * fix(nc-gui): remove link type from link field select option * fix(nc-gui): qr, barcode value field icon issue * fix(nc-gui): set color picker tab according to active color * fix(nc-gui): json editor save btn ui changes in edit modal * fix(nc-gui): disable editing primary key col * chore(nc-gui): lint --------- Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
7 months ago
if (field && field?.pk) {
// Editing primary key not supported
message.info(t('msg.info.editingPKnotSupported'))
return
}
if (event) {
if (event.target instanceof HTMLElement) {
if (event.target.closest('.no-action')) return
}
}
if (compareCols(field, activeField.value) || (field === undefined && activeField.value === undefined)) return
changingField.value = true
nextTick(() => {
activeField.value = field
changingField.value = false
})
}
const addField = (field?: TableExplorerColumn, before = false) => {
if (field) {
setFieldMoveHook(field, before)
}
changeField({})
// Scroll to the bottom of the list for new field add
setTimeout(() => {
if (!field && !before && fieldsListWrapperDomRef.value) {
fieldsListWrapperDomRef.value.scrollTop = fieldsListWrapperDomRef.value.scrollHeight
}
}, 100)
}
const displayColumn = computed(() => {
if (!localMetaColumns.value) return
return localMetaColumns.value.find((col) => col.pv)
})
const duplicateField = async (field: TableExplorerColumn) => {
if (!localMetaColumns.value) return
// generate duplicate column name
const duplicateColumnName = getUniqueColumnName(`${field.title}_copy`, localMetaColumns.value)
let fieldPayload = {}
// construct column create payload
switch (field.uidt) {
case UITypes.LinkToAnotherRecord:
case UITypes.Links:
case UITypes.Lookup:
case UITypes.Rollup:
case UITypes.Formula:
return message.info(t('msg.info.notAvailableAtTheMoment'))
case UITypes.SingleSelect:
case UITypes.MultiSelect:
fieldPayload = {
...field,
title: duplicateColumnName,
column_name: duplicateColumnName,
id: undefined,
order: undefined,
pv: false,
colOptions: {
options:
(field.colOptions as SelectOptionsType)?.options?.map((option: Record<string, any>) => ({
...option,
id: undefined,
})) ?? [],
},
}
break
default:
fieldPayload = {
...field,
title: duplicateColumnName,
column_name: duplicateColumnName,
id: undefined,
colOptions: undefined,
order: undefined,
pv: false,
}
break
}
addField(field)
duplicateFieldHook.value = fieldPayload as TableExplorerColumn
}
Nc feat/links view filter (#8646) * feat(nocodb): add support for limiting selection to specific views * test: fix failing tests * fix: failing playwright tests * feat: allow updating static view filter from both sides * fix: remove console logs * refactor: rename migration name * fix: corrections in ui and update api * fix: apply same behaviour for LTAR column(bt) * refactor: rename view id column in relation to avoid confusion * fix: option to disable view filter(switch) * refactor: some minor ui spacing corrections * fix: avoid setting target view id for bt relation when creating hm relation * feat: links - record selection based on custom filters * fix: corrections * feat: add edit support for conditions * feat: option to switch between dynamic and static value * fix: backend corrections * feat: apis for links filter * feat: filter api integration with ui * feat: filter with save and update * feat: dynamic filter * feat: shared form filter * feat: expanded form * fix: missing imports and corrections * fix: pass correct column list * fix: nested filter bug * fix: corrections in actions and swagger * fix: missing add button menu * fix: expanded form bug * test: playwright test - WIP * test: playwright - link with filters/view * chore: lint * refactor: ui corrections * fix: remove unnecessary filtering from hm/mm list and count * fix: filter ui correction * fix: lable correction * fix: skip view filter for rollup * fix: ui corrections * fix: extract correct column id * fix: duplicate LTAR - missing target view * feat: add duplicate support for link with filters/view * fix: height issue and nested filter creation bug * fix: pass metadata to nested filter component * fix: filter on column creation * fix: filter getting cloned under group * fix: exclude deleted filters when deciding locked state * fix: update state when switching to dynamic filter * fix: unlink view on delete and handle undefined values as null * fix: filter based on unsaved data * fix: handle overflow * fix: multi-field editor - filter UI correction * fix: duplicate link column with dynamic field ref * fix: remove virtual column support * fix: add support to link filter in normal list method * fix: apply filter on count query * fix: pass correct column list * feat: add link filter support in multifield column creation * feat: add link filter support in multifield column creation * Merge branch 'develop' into feat/links-view-filter * fix: dynamic value column export * fix: review comments * test: kludge for groupby tests * fix: extract updated status correctly * test: try waitFor for links * test: kludge * refactor: exclude attachment & rating from dynamic filter and treat float and integer as number * test: label correction * refactor: replace try...catch and use if condition * fix: apply conditions only if enabled * fix: MFE bugs * refactor: show radio button active border only when focused * fix: proper state handling * fix: view delete - unlink from link column * fix: duplicate Link with filter view id * refactor: column filter section padding * fix: exclude system columns * fix: dynamic column filter logic correction * refactor: cleanup * test: kludge with delay for groupby test * refactor: add missing placeholder method * docs: limit link record selection * refactor: add missing placeholder method * chore: lint --------- Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
7 months ago
// Check any filter is changed recursively
const checkForFilterChange = (filters: (FilterType & { status?: string })[]) => {
for (const filter of filters) {
if (filter.status) {
return true
}
if (filter.is_group) {
if (checkForFilterChange(filter.children || [])) {
return true
}
}
}
}
// This method is called whenever there is a change in field properties
Nc feat/links view filter (#8646) * feat(nocodb): add support for limiting selection to specific views * test: fix failing tests * fix: failing playwright tests * feat: allow updating static view filter from both sides * fix: remove console logs * refactor: rename migration name * fix: corrections in ui and update api * fix: apply same behaviour for LTAR column(bt) * refactor: rename view id column in relation to avoid confusion * fix: option to disable view filter(switch) * refactor: some minor ui spacing corrections * fix: avoid setting target view id for bt relation when creating hm relation * feat: links - record selection based on custom filters * fix: corrections * feat: add edit support for conditions * feat: option to switch between dynamic and static value * fix: backend corrections * feat: apis for links filter * feat: filter api integration with ui * feat: filter with save and update * feat: dynamic filter * feat: shared form filter * feat: expanded form * fix: missing imports and corrections * fix: pass correct column list * fix: nested filter bug * fix: corrections in actions and swagger * fix: missing add button menu * fix: expanded form bug * test: playwright test - WIP * test: playwright - link with filters/view * chore: lint * refactor: ui corrections * fix: remove unnecessary filtering from hm/mm list and count * fix: filter ui correction * fix: lable correction * fix: skip view filter for rollup * fix: ui corrections * fix: extract correct column id * fix: duplicate LTAR - missing target view * feat: add duplicate support for link with filters/view * fix: height issue and nested filter creation bug * fix: pass metadata to nested filter component * fix: filter on column creation * fix: filter getting cloned under group * fix: exclude deleted filters when deciding locked state * fix: update state when switching to dynamic filter * fix: unlink view on delete and handle undefined values as null * fix: filter based on unsaved data * fix: handle overflow * fix: multi-field editor - filter UI correction * fix: duplicate link column with dynamic field ref * fix: remove virtual column support * fix: add support to link filter in normal list method * fix: apply filter on count query * fix: pass correct column list * feat: add link filter support in multifield column creation * feat: add link filter support in multifield column creation * Merge branch 'develop' into feat/links-view-filter * fix: dynamic value column export * fix: review comments * test: kludge for groupby tests * fix: extract updated status correctly * test: try waitFor for links * test: kludge * refactor: exclude attachment & rating from dynamic filter and treat float and integer as number * test: label correction * refactor: replace try...catch and use if condition * fix: apply conditions only if enabled * fix: MFE bugs * refactor: show radio button active border only when focused * fix: proper state handling * fix: view delete - unlink from link column * fix: duplicate Link with filter view id * refactor: column filter section padding * fix: exclude system columns * fix: dynamic column filter logic correction * refactor: cleanup * test: kludge with delay for groupby test * refactor: add missing placeholder method * docs: limit link record selection * refactor: add missing placeholder method * chore: lint --------- Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
7 months ago
const onFieldUpdate = (state: TableExplorerColumn, skipLinkChecks = false) => {
const col = fields.value.find((col) => compareCols(col, state))
if (!col) return
if (state.colOptions && [UITypes.SingleSelect, UITypes.MultiSelect].includes(col.uidt)) {
state = {
...state,
colOptions: {
...(state.colOptions || {}),
options: ((state.colOptions as SelectOptionsType)?.options || []).map((option) => {
if (option?.index !== undefined) {
delete option.index
}
return option
}),
},
}
}
const pdiffs: Record<string, any> = diff(col, state)
// remove undefined values
const diffs = Object.fromEntries(
Object.entries(pdiffs).filter(([_, value]) => value !== undefined),
) as Partial<TableExplorerColumn>
if (Object.keys(diffs).length === 0 || (Object.keys(diffs).length === 1 && 'altered' in diffs)) {
ops.value = ops.value.filter((op) => op.op === 'add' || !compareCols(op.column, state))
} else {
const field = ops.value.find((op) => compareCols(op.column, state))
const moveField = moveOps.value.find((op) => compareCols(op.column, state))
const isNewField = newFields.value.find((nField) => compareCols(nField, state))
if (isNewField) {
newFields.value = newFields.value.map((op) => {
if (compareCols(op, state)) {
ops.value = ops.value.filter((op) => op.op === 'add' && !compareCols(op.column, state))
ops.value.push({
op: 'add',
column: state,
})
return state
}
return op
})
return
}
if (field || (field && moveField)) {
field.column = state
Nc feat/links view filter (#8646) * feat(nocodb): add support for limiting selection to specific views * test: fix failing tests * fix: failing playwright tests * feat: allow updating static view filter from both sides * fix: remove console logs * refactor: rename migration name * fix: corrections in ui and update api * fix: apply same behaviour for LTAR column(bt) * refactor: rename view id column in relation to avoid confusion * fix: option to disable view filter(switch) * refactor: some minor ui spacing corrections * fix: avoid setting target view id for bt relation when creating hm relation * feat: links - record selection based on custom filters * fix: corrections * feat: add edit support for conditions * feat: option to switch between dynamic and static value * fix: backend corrections * feat: apis for links filter * feat: filter api integration with ui * feat: filter with save and update * feat: dynamic filter * feat: shared form filter * feat: expanded form * fix: missing imports and corrections * fix: pass correct column list * fix: nested filter bug * fix: corrections in actions and swagger * fix: missing add button menu * fix: expanded form bug * test: playwright test - WIP * test: playwright - link with filters/view * chore: lint * refactor: ui corrections * fix: remove unnecessary filtering from hm/mm list and count * fix: filter ui correction * fix: lable correction * fix: skip view filter for rollup * fix: ui corrections * fix: extract correct column id * fix: duplicate LTAR - missing target view * feat: add duplicate support for link with filters/view * fix: height issue and nested filter creation bug * fix: pass metadata to nested filter component * fix: filter on column creation * fix: filter getting cloned under group * fix: exclude deleted filters when deciding locked state * fix: update state when switching to dynamic filter * fix: unlink view on delete and handle undefined values as null * fix: filter based on unsaved data * fix: handle overflow * fix: multi-field editor - filter UI correction * fix: duplicate link column with dynamic field ref * fix: remove virtual column support * fix: add support to link filter in normal list method * fix: apply filter on count query * fix: pass correct column list * feat: add link filter support in multifield column creation * feat: add link filter support in multifield column creation * Merge branch 'develop' into feat/links-view-filter * fix: dynamic value column export * fix: review comments * test: kludge for groupby tests * fix: extract updated status correctly * test: try waitFor for links * test: kludge * refactor: exclude attachment & rating from dynamic filter and treat float and integer as number * test: label correction * refactor: replace try...catch and use if condition * fix: apply conditions only if enabled * fix: MFE bugs * refactor: show radio button active border only when focused * fix: proper state handling * fix: view delete - unlink from link column * fix: duplicate Link with filter view id * refactor: column filter section padding * fix: exclude system columns * fix: dynamic column filter logic correction * refactor: cleanup * test: kludge with delay for groupby test * refactor: add missing placeholder method * docs: limit link record selection * refactor: add missing placeholder method * chore: lint --------- Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
7 months ago
} else if (isLinksOrLTAR(state) && !skipLinkChecks) {
if (
['title', 'column_name', 'meta'].some((k) => k in diffs) ||
('childViewId' in diffs && diffs.childViewId !== col.colOptions?.fk_target_view_id) ||
checkForFilterChange(diffs.filters || [])
) {
ops.value.push({
op: 'update',
column: state,
})
}
} else {
ops.value.push({
op: 'update',
column: state,
})
}
if (
activeField.value &&
Object.keys(activeField.value).length &&
((state?.id && activeField.value?.id && state?.id === activeField.value?.id) ||
(state?.temp_id && activeField.value?.temp_id && state?.temp_id === activeField.value?.temp_id))
) {
activeField.value = state
}
}
}
const onFieldDelete = (state: TableExplorerColumn) => {
const field = ops.value.find((op) => compareCols(op.column, state))
if (field) {
if (field.op === 'delete') {
ops.value = ops.value.filter((op) => op.column.id !== state.id)
} else if (field.op === 'add') {
if (activeField.value && compareCols(activeField.value, state)) {
changeField()
}
ops.value = ops.value.filter((op) => op.column.temp_id !== state.temp_id)
newFields.value = newFields.value.filter((op) => op.temp_id !== state.temp_id)
} else {
field.op = 'delete'
field.column = state
}
} else {
ops.value.push({
op: 'delete',
column: state,
})
}
}
const onFieldAdd = (state: TableExplorerColumn) => {
if (duplicateFieldHook.value) {
state = duplicateFieldHook.value
duplicateFieldHook.value = undefined
}
state.temp_id = `temp_${++temporaryAddCount.value}`
state.view_id = view.value?.id as string
ops.value.push({
op: 'add',
column: state,
})
newFields.value.push(state)
if (addFieldMoveHook.value) {
moveOps.value.push({
op: 'move',
column: state,
index: addFieldMoveHook.value,
order: calculateOrderForIndex(addFieldMoveHook.value),
})
addFieldMoveHook.value = undefined
} else {
moveOps.value.push({
op: 'move',
column: state,
index: fields.value.length,
order: calculateOrderForIndex(fields.value.length),
})
}
changeField(state)
}
const onMove = (_event: { moved: { newIndex: number; oldIndex: number } }) => {
const field = fields.value[_event.moved.oldIndex]
const order = calculateOrderForIndex(_event.moved.newIndex, _event.moved.newIndex < _event.moved.oldIndex)
const op = ops.value.find((op) => compareCols(op.column, field))
if (op?.op === 'update') {
1 year ago
const diffs = diff(op.column, field)
if (!(Object.keys(diffs).length === 1 && 'column_order' in diffs)) {
message.warning(t('msg.warning.multiField.moveEditedField'))
1 year ago
return
}
}
if (op?.op === 'delete') {
message.warning(t('msg.warning.multiField.moveDeletedField'))
return
}
const mop = moveOps.value.find((op) => compareCols(op.column, fields.value[_event.moved.oldIndex]))
if (mop) {
mop.index = _event.moved.newIndex
mop.order = order
} else {
moveOps.value.push({
op: 'move',
column: fields.value[_event.moved.oldIndex],
index: _event.moved.newIndex,
order,
})
}
if (op) {
Nc feat/links view filter (#8646) * feat(nocodb): add support for limiting selection to specific views * test: fix failing tests * fix: failing playwright tests * feat: allow updating static view filter from both sides * fix: remove console logs * refactor: rename migration name * fix: corrections in ui and update api * fix: apply same behaviour for LTAR column(bt) * refactor: rename view id column in relation to avoid confusion * fix: option to disable view filter(switch) * refactor: some minor ui spacing corrections * fix: avoid setting target view id for bt relation when creating hm relation * feat: links - record selection based on custom filters * fix: corrections * feat: add edit support for conditions * feat: option to switch between dynamic and static value * fix: backend corrections * feat: apis for links filter * feat: filter api integration with ui * feat: filter with save and update * feat: dynamic filter * feat: shared form filter * feat: expanded form * fix: missing imports and corrections * fix: pass correct column list * fix: nested filter bug * fix: corrections in actions and swagger * fix: missing add button menu * fix: expanded form bug * test: playwright test - WIP * test: playwright - link with filters/view * chore: lint * refactor: ui corrections * fix: remove unnecessary filtering from hm/mm list and count * fix: filter ui correction * fix: lable correction * fix: skip view filter for rollup * fix: ui corrections * fix: extract correct column id * fix: duplicate LTAR - missing target view * feat: add duplicate support for link with filters/view * fix: height issue and nested filter creation bug * fix: pass metadata to nested filter component * fix: filter on column creation * fix: filter getting cloned under group * fix: exclude deleted filters when deciding locked state * fix: update state when switching to dynamic filter * fix: unlink view on delete and handle undefined values as null * fix: filter based on unsaved data * fix: handle overflow * fix: multi-field editor - filter UI correction * fix: duplicate link column with dynamic field ref * fix: remove virtual column support * fix: add support to link filter in normal list method * fix: apply filter on count query * fix: pass correct column list * feat: add link filter support in multifield column creation * feat: add link filter support in multifield column creation * Merge branch 'develop' into feat/links-view-filter * fix: dynamic value column export * fix: review comments * test: kludge for groupby tests * fix: extract updated status correctly * test: try waitFor for links * test: kludge * refactor: exclude attachment & rating from dynamic filter and treat float and integer as number * test: label correction * refactor: replace try...catch and use if condition * fix: apply conditions only if enabled * fix: MFE bugs * refactor: show radio button active border only when focused * fix: proper state handling * fix: view delete - unlink from link column * fix: duplicate Link with filter view id * refactor: column filter section padding * fix: exclude system columns * fix: dynamic column filter logic correction * refactor: cleanup * test: kludge with delay for groupby test * refactor: add missing placeholder method * docs: limit link record selection * refactor: add missing placeholder method * chore: lint --------- Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
7 months ago
onFieldUpdate(
{
...op.column,
column_order: {
order,
view_id: view.value?.id as string,
},
},
Nc feat/links view filter (#8646) * feat(nocodb): add support for limiting selection to specific views * test: fix failing tests * fix: failing playwright tests * feat: allow updating static view filter from both sides * fix: remove console logs * refactor: rename migration name * fix: corrections in ui and update api * fix: apply same behaviour for LTAR column(bt) * refactor: rename view id column in relation to avoid confusion * fix: option to disable view filter(switch) * refactor: some minor ui spacing corrections * fix: avoid setting target view id for bt relation when creating hm relation * feat: links - record selection based on custom filters * fix: corrections * feat: add edit support for conditions * feat: option to switch between dynamic and static value * fix: backend corrections * feat: apis for links filter * feat: filter api integration with ui * feat: filter with save and update * feat: dynamic filter * feat: shared form filter * feat: expanded form * fix: missing imports and corrections * fix: pass correct column list * fix: nested filter bug * fix: corrections in actions and swagger * fix: missing add button menu * fix: expanded form bug * test: playwright test - WIP * test: playwright - link with filters/view * chore: lint * refactor: ui corrections * fix: remove unnecessary filtering from hm/mm list and count * fix: filter ui correction * fix: lable correction * fix: skip view filter for rollup * fix: ui corrections * fix: extract correct column id * fix: duplicate LTAR - missing target view * feat: add duplicate support for link with filters/view * fix: height issue and nested filter creation bug * fix: pass metadata to nested filter component * fix: filter on column creation * fix: filter getting cloned under group * fix: exclude deleted filters when deciding locked state * fix: update state when switching to dynamic filter * fix: unlink view on delete and handle undefined values as null * fix: filter based on unsaved data * fix: handle overflow * fix: multi-field editor - filter UI correction * fix: duplicate link column with dynamic field ref * fix: remove virtual column support * fix: add support to link filter in normal list method * fix: apply filter on count query * fix: pass correct column list * feat: add link filter support in multifield column creation * feat: add link filter support in multifield column creation * Merge branch 'develop' into feat/links-view-filter * fix: dynamic value column export * fix: review comments * test: kludge for groupby tests * fix: extract updated status correctly * test: try waitFor for links * test: kludge * refactor: exclude attachment & rating from dynamic filter and treat float and integer as number * test: label correction * refactor: replace try...catch and use if condition * fix: apply conditions only if enabled * fix: MFE bugs * refactor: show radio button active border only when focused * fix: proper state handling * fix: view delete - unlink from link column * fix: duplicate Link with filter view id * refactor: column filter section padding * fix: exclude system columns * fix: dynamic column filter logic correction * refactor: cleanup * test: kludge with delay for groupby test * refactor: add missing placeholder method * docs: limit link record selection * refactor: add missing placeholder method * chore: lint --------- Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
7 months ago
true,
)
} else {
Nc feat/links view filter (#8646) * feat(nocodb): add support for limiting selection to specific views * test: fix failing tests * fix: failing playwright tests * feat: allow updating static view filter from both sides * fix: remove console logs * refactor: rename migration name * fix: corrections in ui and update api * fix: apply same behaviour for LTAR column(bt) * refactor: rename view id column in relation to avoid confusion * fix: option to disable view filter(switch) * refactor: some minor ui spacing corrections * fix: avoid setting target view id for bt relation when creating hm relation * feat: links - record selection based on custom filters * fix: corrections * feat: add edit support for conditions * feat: option to switch between dynamic and static value * fix: backend corrections * feat: apis for links filter * feat: filter api integration with ui * feat: filter with save and update * feat: dynamic filter * feat: shared form filter * feat: expanded form * fix: missing imports and corrections * fix: pass correct column list * fix: nested filter bug * fix: corrections in actions and swagger * fix: missing add button menu * fix: expanded form bug * test: playwright test - WIP * test: playwright - link with filters/view * chore: lint * refactor: ui corrections * fix: remove unnecessary filtering from hm/mm list and count * fix: filter ui correction * fix: lable correction * fix: skip view filter for rollup * fix: ui corrections * fix: extract correct column id * fix: duplicate LTAR - missing target view * feat: add duplicate support for link with filters/view * fix: height issue and nested filter creation bug * fix: pass metadata to nested filter component * fix: filter on column creation * fix: filter getting cloned under group * fix: exclude deleted filters when deciding locked state * fix: update state when switching to dynamic filter * fix: unlink view on delete and handle undefined values as null * fix: filter based on unsaved data * fix: handle overflow * fix: multi-field editor - filter UI correction * fix: duplicate link column with dynamic field ref * fix: remove virtual column support * fix: add support to link filter in normal list method * fix: apply filter on count query * fix: pass correct column list * feat: add link filter support in multifield column creation * feat: add link filter support in multifield column creation * Merge branch 'develop' into feat/links-view-filter * fix: dynamic value column export * fix: review comments * test: kludge for groupby tests * fix: extract updated status correctly * test: try waitFor for links * test: kludge * refactor: exclude attachment & rating from dynamic filter and treat float and integer as number * test: label correction * refactor: replace try...catch and use if condition * fix: apply conditions only if enabled * fix: MFE bugs * refactor: show radio button active border only when focused * fix: proper state handling * fix: view delete - unlink from link column * fix: duplicate Link with filter view id * refactor: column filter section padding * fix: exclude system columns * fix: dynamic column filter logic correction * refactor: cleanup * test: kludge with delay for groupby test * refactor: add missing placeholder method * docs: limit link record selection * refactor: add missing placeholder method * chore: lint --------- Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
7 months ago
onFieldUpdate(
{
...field,
column_order: {
order,
view_id: view.value?.id as string,
},
},
Nc feat/links view filter (#8646) * feat(nocodb): add support for limiting selection to specific views * test: fix failing tests * fix: failing playwright tests * feat: allow updating static view filter from both sides * fix: remove console logs * refactor: rename migration name * fix: corrections in ui and update api * fix: apply same behaviour for LTAR column(bt) * refactor: rename view id column in relation to avoid confusion * fix: option to disable view filter(switch) * refactor: some minor ui spacing corrections * fix: avoid setting target view id for bt relation when creating hm relation * feat: links - record selection based on custom filters * fix: corrections * feat: add edit support for conditions * feat: option to switch between dynamic and static value * fix: backend corrections * feat: apis for links filter * feat: filter api integration with ui * feat: filter with save and update * feat: dynamic filter * feat: shared form filter * feat: expanded form * fix: missing imports and corrections * fix: pass correct column list * fix: nested filter bug * fix: corrections in actions and swagger * fix: missing add button menu * fix: expanded form bug * test: playwright test - WIP * test: playwright - link with filters/view * chore: lint * refactor: ui corrections * fix: remove unnecessary filtering from hm/mm list and count * fix: filter ui correction * fix: lable correction * fix: skip view filter for rollup * fix: ui corrections * fix: extract correct column id * fix: duplicate LTAR - missing target view * feat: add duplicate support for link with filters/view * fix: height issue and nested filter creation bug * fix: pass metadata to nested filter component * fix: filter on column creation * fix: filter getting cloned under group * fix: exclude deleted filters when deciding locked state * fix: update state when switching to dynamic filter * fix: unlink view on delete and handle undefined values as null * fix: filter based on unsaved data * fix: handle overflow * fix: multi-field editor - filter UI correction * fix: duplicate link column with dynamic field ref * fix: remove virtual column support * fix: add support to link filter in normal list method * fix: apply filter on count query * fix: pass correct column list * feat: add link filter support in multifield column creation * feat: add link filter support in multifield column creation * Merge branch 'develop' into feat/links-view-filter * fix: dynamic value column export * fix: review comments * test: kludge for groupby tests * fix: extract updated status correctly * test: try waitFor for links * test: kludge * refactor: exclude attachment & rating from dynamic filter and treat float and integer as number * test: label correction * refactor: replace try...catch and use if condition * fix: apply conditions only if enabled * fix: MFE bugs * refactor: show radio button active border only when focused * fix: proper state handling * fix: view delete - unlink from link column * fix: duplicate Link with filter view id * refactor: column filter section padding * fix: exclude system columns * fix: dynamic column filter logic correction * refactor: cleanup * test: kludge with delay for groupby test * refactor: add missing placeholder method * docs: limit link record selection * refactor: add missing placeholder method * chore: lint --------- Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
7 months ago
true,
)
}
}
const isColumnValid = (column: TableExplorerColumn) => {
const isDeleteOp = ops.value.find((op) => compareCols(column, op.column) && op.op === 'delete')
const isNew = ops.value.find((op) => compareCols(column, op.column) && op.op === 'add')
if (isDeleteOp) return true
if (!column.title && !isNew) {
return false
}
if ((column.uidt === UITypes.Links || column.uidt === UITypes.LinkToAnotherRecord) && isNew) {
if (!column.childColumn || !column.childTable || !column.childId) {
return false
}
}
if (column.uidt === UITypes.Lookup && isNew) {
if (!column.fk_relation_column_id || !column.fk_lookup_column_id) {
return false
}
}
if (column.uidt === UITypes.Rollup && isNew) {
if (!column.fk_relation_column_id || !column.fk_rollup_column_id || !column.rollup_function) {
return false
}
}
if (column.uidt === UITypes.Formula && isNew) {
if (!column.formula_raw) {
return false
}
}
return true
}
12 months ago
function updateDefaultColumnValues(column: TableExplorerColumn) {
if (column.uidt === UITypes.QrCode && column.colOptions?.fk_qr_value_column_id) {
if (!column?.fk_qr_value_column_id) {
column.fk_qr_value_column_id = column.colOptions.fk_qr_value_column_id
}
}
if (column.uidt === UITypes.Barcode && column.colOptions?.fk_barcode_value_column_id) {
if (!column?.fk_barcode_value_column_id) {
column.fk_barcode_value_column_id = column.colOptions.fk_barcode_value_column_id
}
}
if (column.uidt === UITypes.Lookup && column?.colOptions?.fk_lookup_column_id && column?.colOptions?.fk_relation_column_id) {
if (!column?.fk_lookup_column_id) {
column.fk_lookup_column_id = column.colOptions.fk_lookup_column_id
}
if (!column?.fk_relation_column_id) {
column.fk_relation_column_id = column.colOptions.fk_relation_column_id
}
}
if (
column.uidt === UITypes.Rollup &&
column?.colOptions?.fk_relation_column_id &&
column?.colOptions?.fk_rollup_column_id &&
column?.colOptions?.rollup_function
) {
if (!column?.fk_relation_column_id) {
column.fk_relation_column_id = column.colOptions.fk_relation_column_id
}
if (!column?.fk_rollup_column_id) {
column.fk_rollup_column_id = column.colOptions.fk_rollup_column_id
}
if (!column?.rollup_function) {
column.rollup_function = column.colOptions.rollup_function
}
}
if (column.uidt === UITypes.Formula && column.colOptions?.formula_raw && !column?.formula_raw) {
column.formula_raw = column.colOptions?.formula_raw
}
return column
}
const recoverField = (state: TableExplorerColumn) => {
const field = ops.value.find((op) => compareCols(op.column, state))
if (field) {
if (field.op === 'delete') {
ops.value = ops.value.filter((op) => !compareCols(op.column, state))
} else if (field.op === 'update') {
ops.value = ops.value.filter((op) => !compareCols(op.column, state))
moveOps.value = moveOps.value.filter((op) => !compareCols(op.column, state))
}
activeField.value = null
changeField(fields.value.filter((fiel) => fiel.id === state.id)[0])
}
}
const fieldState = (field: TableExplorerColumn) => {
const col = fields.value.find((col) => compareCols(col, field))
if (col) {
const op = ops.value.find((op) => compareCols(op.column, col))
if (op) {
return op.column
}
}
return null
}
const fieldStatuses = computed<Record<string, string>>(() => {
const statuses: Record<string, string> = {}
for (const op of ops.value) {
if (op.op === 'add') {
if (op.column.temp_id) statuses[op.column.temp_id] = 'add'
} else if (op.op === 'update') {
if (op.column.id) statuses[op.column.id] = 'update'
} else if (op.op === 'delete') {
if (op.column.id) statuses[op.column.id] = 'delete'
}
}
return statuses
})
const fieldStatus = (field?: TableExplorerColumn) => {
const id = field?.id || field?.temp_id
return id ? fieldStatuses.value[id] : ''
}
const clearChanges = () => {
ops.value = []
moveOps.value = []
newFields.value = []
visibilityOps.value = []
changeField()
}
const isColumnsValid = computed(() => fields.value.every((f) => isColumnValid(f)))
const metaToLocal = () => {
localMetaColumns.value = meta.value?.columns?.map((c: ColumnType) => {
if (c.uidt && c.uidt in columnDefaultMeta) {
if (!c.meta) c.meta = {}
c.meta = {
...columnDefaultMeta[c.uidt],
...(c.meta || {}),
}
}
return {
...c,
}
})
}
const saveChanges = async () => {
if (!isColumnsValid.value) {
message.error(t('msg.error.multiFieldSaveValidation'))
return
} else if (!loading.value && ops.value.length < 1 && moveOps.value.length < 1 && visibilityOps.value.length < 1) {
return
}
try {
if (!meta.value?.id) return
loading.value = true
const newFieldTitles: string[] = []
for (const mop of moveOps.value) {
const op = ops.value.find((op) => compareCols(op.column, mop.column))
if (op && op.op === 'add') {
if (!op.column?.userHasChangedTitle && !op.column.title) {
const defaultColumnName = generateUniqueColumnName({
formState: op.column,
tableExplorerColumns: fields.value || [],
metaColumns: meta.value?.columns || [],
newFieldTitles,
})
newFieldTitles.push(defaultColumnName)
op.column.title = defaultColumnName
op.column.column_name = defaultColumnName
}
op.column.column_order = {
order: mop.order,
view_id: view.value?.id as string,
}
}
12 months ago
if (op && op.op === 'update') {
op.column.column_order = {
order: mop.order,
view_id: view.value?.id as string,
}
}
}
for (const op of ops.value) {
if (op.op === 'add') {
if (activeField.value && compareCols(activeField.value, op.column)) {
changeField()
}
} else if (op.op === 'delete') {
if (activeField.value && compareCols(activeField.value, op.column)) {
changeField()
}
}
}
for (const op of visibilityOps.value) {
await toggleFieldVisibility(op.visible, {
...op.column,
show: op.visible,
})
}
1 year ago
const res = await $api.dbTableColumn.bulk(meta.value?.id, {
hash: columnsHash.value,
ops: ops.value,
})
await loadViewColumns()
if (res) {
ops.value =
res.failedOps && res.failedOps?.length
12 months ago
? (res.failedOps as (op & { error: unknown })[]).map(({ error: _, ...rest }) => rest)
: []
newFields.value = newFields.value.filter((col) => {
if (res.failedOps) {
const op = res.failedOps.find((fop) => {
return (fop.column as TableExplorerColumn).temp_id === col.temp_id
})
if (op) {
return true
}
}
return false
})
moveOps.value = []
}
await getMeta(meta.value.id, true)
metaToLocal()
columnsHash.value = (await $api.dbTableColumn.hash(meta.value?.id)).hash
visibilityOps.value = []
} catch (e) {
message.error(t('msg.error.somethingWentWrong'))
} finally {
loading.value = false
}
}
const toggleVisibility = async (checked: boolean, field: Field) => {
if (field.fk_column_id && fieldStatuses.value[field.fk_column_id]) {
message.warning(t('msg.warning.multiField.fieldVisibility'))
return
}
if (visibilityOps.value.find((op) => op.column.fk_column_id === field.fk_column_id)) {
visibilityOps.value = visibilityOps.value.filter((op) => op.column.fk_column_id !== field.fk_column_id)
return
}
visibilityOps.value.push({
visible: checked,
column: 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()
return
}
// For Windows and mac
if ((e.altKey && e.key.toLowerCase() === 'c') || (e.altKey && e.code === 'KeyC')) {
if (openedViewsTab.value !== 'field') return
e.preventDefault()
addField()
}
})
const renderCmdOrCtrlKey = () => {
return isMac() ? '⌘' : 'Ctrl'
}
const renderAltOrOptlKey = () => {
return isMac() ? '⌥' : 'ALT'
}
onKeyDown('ArrowDown', () => {
const index = fields.value.findIndex((f) => compareCols(f, activeField.value))
if (index === -1) changeField(fields.value[0])
else if (index === fields.value.length - 1) changeField(fields.value[0])
else changeField(fields.value[index + 1])
})
onKeyDown('ArrowUp', () => {
const index = fields.value.findIndex((f) => compareCols(f, activeField.value))
if (index === -1) changeField(fields.value[0])
else if (index === 0) changeField(fields.value[fields.value.length - 1])
else changeField(fields.value[index - 1])
})
onKeyDown('Delete', () => {
if (isLocked.value) return
if (
document.activeElement?.tagName === 'INPUT' ||
document.activeElement?.tagName === 'TEXTAREA' ||
// A rich text editor is a div with the contenteditable attribute set to true.
document.activeElement?.getAttribute('contenteditable')
) {
return
}
const isDeletedField = fieldStatus(activeField.value) === 'delete'
if (!isDeletedField && activeField.value) {
onFieldDelete(activeField.value)
}
})
onKeyDown('Backspace', () => {
if (isLocked.value) return
if (
document.activeElement?.tagName === 'INPUT' ||
document.activeElement?.tagName === 'TEXTAREA' ||
// A rich text editor is a div with the contenteditable attribute set to true.
document.activeElement?.getAttribute('contenteditable')
) {
return
}
const isDeletedField = fieldStatus(activeField.value) === 'delete'
if (!isDeletedField && activeField.value) {
onFieldDelete(activeField.value)
}
})
onKeyDown('ArrowRight', () => {
if (document.activeElement?.tagName === 'TEXTAREA') return
if (activeField.value) {
const input = document.querySelector('.nc-fields-input') as HTMLInputElement
if (input) {
input.focus()
}
}
})
const onClickCopyFieldUrl = async (field: ColumnType) => {
await copy(field.id!)
isFieldIdCopied.value = true
}
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()
})
watch(
meta,
async (newMeta) => {
if (newMeta?.id) {
columnsHash.value = (await $api.dbTableColumn.hash(newMeta.id)).hash
}
},
{ deep: true },
)
onMounted(async () => {
if (meta.value && meta.value.id) {
columnsHash.value = (await $api.dbTableColumn.hash(meta.value.id)).hash
}
await until(() => meta.value?.columns)
metaToLocal()
})
const onFieldOptionUpdate = () => {
setTimeout(() => {
isFieldIdCopied.value = false
}, 200)
}
watch(
() => activeField.value?.temp_id,
(_newValue, oldValue) => {
if (!oldValue) return
const oldField = fields.value.find((field) => field.temp_id === oldValue)
if (
!oldField ||
(oldField &&
(oldField.title ||
!ops.value.find((op) => op.op === 'add' && op.column.temp_id === oldField.temp_id) ||
oldField?.userHasChangedTitle ||
!isColumnValid(oldField)))
) {
return
}
const newFieldTitles = ops.value
.filter((op) => op.op === 'add' && op.column.title)
.map((op) => op.column.title)
.filter((t) => t) as string[]
const defaultColumnName = generateUniqueColumnName({
formState: oldField,
tableExplorerColumns: fields.value || [],
metaColumns: localMetaColumns.value || [],
newFieldTitles,
})
oldField.title = defaultColumnName
oldField.column_name = defaultColumnName
},
)
</script>
<template>
<div class="nc-fields-wrapper w-full p-4">
<div class="max-w-250 h-full w-full mx-auto">
<div v-if="isViewColumnsLoading" class="flex flex-row justify-between mt-2">
<a-skeleton-input class="!h-8 !w-68 !rounded !overflow-hidden" active size="small" />
<div class="flex flex-row gap-x-4">
<a-skeleton-input class="!h-8 !w-22 !rounded !overflow-hidden" active size="small" />
<a-skeleton-input class="!h-8 !w-22 !rounded !overflow-hidden" active size="small" />
<a-skeleton-input class="!h-8 !w-22 !rounded !overflow-hidden" active size="small" />
</div>
</div>
<template v-else>
<div class="flex w-full justify-between py-2">
<a-input
v-model:value="searchQuery"
data-testid="nc-field-search-input"
class="!h-8 !px-1 !rounded-lg !w-72"
:placeholder="$t('placeholder.searchFields')"
>
<template #prefix>
<GeneralIcon icon="search" class="mx-1 h-3.5 w-3.5 text-gray-500 group-hover:text-black" />
</template>
<template #suffix>
<GeneralIcon
v-if="searchQuery.length > 0"
icon="close"
class="mx-1 h-3.5 w-3.5 text-gray-500 group-hover:text-black"
data-testid="nc-field-clear-search"
@click="searchQuery = ''"
/>
</template>
</a-input>
<div class="flex gap-2">
<NcTooltip :disabled="isLocked">
<template #title> {{ `${renderAltOrOptlKey()} + C` }} </template>
<NcButton
data-testid="nc-field-add-new"
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" />
{{ $t('labels.multiField.newField') }}
</div>
</NcButton>
</NcTooltip>
<NcButton
data-testid="nc-field-reset"
type="secondary"
size="small"
:disabled="(!loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1) || isLocked"
@click="clearChanges()"
>
{{ $t('general.reset') }}
</NcButton>
<NcTooltip :disabled="isLocked">
<template #title> {{ `${renderCmdOrCtrlKey()} + S` }} </template>
<NcButton
data-testid="nc-field-save-changes"
type="primary"
size="small"
:loading="loading"
:disabled="
(isColumnsValid ? !loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1 : true) ||
isLocked
"
@click="saveChanges()"
>
{{ $t('labels.multiField.saveChanges') }}
</NcButton>
</NcTooltip>
</div>
</div>
<div class="flex flex-row rounded-lg border-1 overflow-clip border-gray-200">
<div ref="fieldsListWrapperDomRef" class="nc-scrollbar-md !overflow-auto flex-1 flex-grow-1 nc-fields-height">
<Draggable
:model-value="fields"
:disabled="isLocked"
item-key="id"
data-testid="nc-field-list-wrapper"
@change="onMove($event)"
>
<template #item="{ element: field }">
<div
v-if="field.title.toLowerCase().includes(searchQuery.toLowerCase()) && !field.pv"
class="flex px-2 hover:bg-gray-100 first:rounded-t-lg border-b-1 last:rounded-b-none border-gray-200 pl-5 group"
:class="{ 'selected': compareCols(field, activeField), 'cursor-not-allowed': !isColumnUpdateAllowed(field) }"
:data-testid="`nc-field-item-${fieldState(field)?.title || field.title}`"
@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"
: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
"
data-testid="nc-field-visibility-checkbox"
@change="
(event: any) => {
toggleVisibility(event.target.checked, viewFieldsMap[field.id])
}
"
/>
<NcCheckbox v-else :disabled="true" class="opacity-0" :checked="true" />
<SmartsheetHeaderVirtualCellIcon
v-if="field && isVirtualCol(fieldState(field) || field)"
:column-meta="fieldState(field) || field"
:class="{
'text-brand-500': compareCols(field, activeField),
}"
/>
<SmartsheetHeaderCellIcon
v-else
:column-meta="fieldState(field) || field"
:class="{
'text-brand-500': compareCols(field, activeField),
}"
/>
<NcTooltip
:class="{
'text-brand-500': compareCols(field, activeField),
}"
class="truncate flex-1"
show-on-truncate-only
>
<template #title> {{ fieldState(field)?.title || field.title }} </template>
<span data-testid="nc-field-title">
{{ fieldState(field)?.title || field.title }}
</span>
</NcTooltip>
</div>
<div class="flex items-center justify-end gap-1">
<div class="nc-field-status-wrapper flex items-center">
<NcBadge
v-if="fieldStatus(field) === 'delete'"
color="red"
:border="false"
class="bg-red-50 text-red-700"
data-testid="nc-field-status-deleted-field"
>
{{ $t('labels.multiField.deletedField') }}
</NcBadge>
<NcBadge
Nc feat/links view filter (#8646) * feat(nocodb): add support for limiting selection to specific views * test: fix failing tests * fix: failing playwright tests * feat: allow updating static view filter from both sides * fix: remove console logs * refactor: rename migration name * fix: corrections in ui and update api * fix: apply same behaviour for LTAR column(bt) * refactor: rename view id column in relation to avoid confusion * fix: option to disable view filter(switch) * refactor: some minor ui spacing corrections * fix: avoid setting target view id for bt relation when creating hm relation * feat: links - record selection based on custom filters * fix: corrections * feat: add edit support for conditions * feat: option to switch between dynamic and static value * fix: backend corrections * feat: apis for links filter * feat: filter api integration with ui * feat: filter with save and update * feat: dynamic filter * feat: shared form filter * feat: expanded form * fix: missing imports and corrections * fix: pass correct column list * fix: nested filter bug * fix: corrections in actions and swagger * fix: missing add button menu * fix: expanded form bug * test: playwright test - WIP * test: playwright - link with filters/view * chore: lint * refactor: ui corrections * fix: remove unnecessary filtering from hm/mm list and count * fix: filter ui correction * fix: lable correction * fix: skip view filter for rollup * fix: ui corrections * fix: extract correct column id * fix: duplicate LTAR - missing target view * feat: add duplicate support for link with filters/view * fix: height issue and nested filter creation bug * fix: pass metadata to nested filter component * fix: filter on column creation * fix: filter getting cloned under group * fix: exclude deleted filters when deciding locked state * fix: update state when switching to dynamic filter * fix: unlink view on delete and handle undefined values as null * fix: filter based on unsaved data * fix: handle overflow * fix: multi-field editor - filter UI correction * fix: duplicate link column with dynamic field ref * fix: remove virtual column support * fix: add support to link filter in normal list method * fix: apply filter on count query * fix: pass correct column list * feat: add link filter support in multifield column creation * feat: add link filter support in multifield column creation * Merge branch 'develop' into feat/links-view-filter * fix: dynamic value column export * fix: review comments * test: kludge for groupby tests * fix: extract updated status correctly * test: try waitFor for links * test: kludge * refactor: exclude attachment & rating from dynamic filter and treat float and integer as number * test: label correction * refactor: replace try...catch and use if condition * fix: apply conditions only if enabled * fix: MFE bugs * refactor: show radio button active border only when focused * fix: proper state handling * fix: view delete - unlink from link column * fix: duplicate Link with filter view id * refactor: column filter section padding * fix: exclude system columns * fix: dynamic column filter logic correction * refactor: cleanup * test: kludge with delay for groupby test * refactor: add missing placeholder method * docs: limit link record selection * refactor: add missing placeholder method * chore: lint --------- Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com>
7 months ago
v-else-if="isColumnValid(field) && fieldStatus(field) === 'add'"
color="orange"
:border="false"
class="bg-green-50 text-green-700"
data-testid="nc-field-status-new-field"
>
{{ $t('labels.multiField.newField') }}
</NcBadge>
<NcBadge
v-else-if="fieldStatus(field) === 'update'"
color="orange"
:border="false"
class="bg-orange-50 text-orange-700"
data-testid="nc-field-status-updated-field"
>
{{ $t('labels.multiField.updatedField') }}
</NcBadge>
<NcBadge
v-if="!isColumnValid(field)"
color="yellow"
:border="false"
class="ml-1 bg-yellow-50 text-yellow-700"
data-testid="nc-field-status-incomplete-configuration"
>
{{ $t('labels.multiField.incompleteConfiguration') }}
</NcBadge>
</div>
<NcButton
v-if="fieldStatus(field) === 'delete' || fieldStatus(field) === 'update'"
type="secondary"
size="small"
class="no-action mr-2"
:disabled="loading"
data-testid="nc-field-restore-changes"
@click="recoverField(field)"
>
<div class="flex items-center text-xs gap-1">
<GeneralIcon icon="reload" />
{{ $t('general.restore') }}
</div>
</NcButton>
<NcDropdown
v-else
:trigger="['click']"
overlay-class-name="nc-field-item-action-dropdown nc-dropdown-table-explorer"
@update:visible="onFieldOptionUpdate"
@click.stop
>
<NcButton
size="xsmall"
type="text"
class="!opacity-0 !group-hover:(opacity-100)"
:class="{
'!hover:(text-brand-700 bg-brand-100) !group-hover:(text-brand-500)': compareCols(field, activeField),
'!hover:(text-gray-700 bg-gray-200) !group-hover:(text-gray-500)': !compareCols(field, activeField),
}"
data-testid="nc-field-item-action-button"
>
<GeneralIcon icon="threeDotVertical" class="no-action text-inherit" />
</NcButton>
<template #overlay>
<NcMenu style="padding-top: 0.45rem !important">
<template v-if="fieldStatus(field) !== 'add'">
<NcTooltip placement="top">
<template #title>{{ $t('msg.clickToCopyFieldId') }}</template>
<div
class="flex flex-row px-3 py-2 w-46 justify-between items-center group hover:bg-gray-100 cursor-pointer"
data-testid="nc-field-item-action-copy-id"
@click="onClickCopyFieldUrl(field)"
>
<div class="flex flex-row items-baseline gap-x-1 font-bold text-xs">
<div class="text-gray-600">{{ $t('labels.idColon') }}</div>
<div class="flex flex-row text-gray-600 text-xs" data-testid="nc-field-item-id">
{{ field.id }}
</div>
</div>
<NcButton size="xsmall" type="secondary" class="!group-hover:bg-gray-100">
<GeneralIcon v-if="isFieldIdCopied" icon="check" />
<GeneralIcon v-else icon="copy" />
</NcButton>
</div>
</NcTooltip>
<a-menu-divider v-if="!isLocked" class="my-1.5" />
</template>
<template v-if="!isLocked">
<NcMenuItem
key="table-explorer-duplicate"
data-testid="nc-field-item-action-duplicate"
@click="duplicateField(field)"
>
<GeneralIcon icon="duplicate" class="text-gray-800" />
<span>{{ $t('general.duplicate') }}</span>
</NcMenuItem>
<NcMenuItem
v-if="!field.pv"
key="table-explorer-insert-above"
data-testid="nc-field-item-action-insert-above"
@click="addField(field, true)"
>
<GeneralIcon icon="ncArrowUp" class="text-gray-800" />
<span>{{ $t('general.insertAbove') }}</span>
</NcMenuItem>
<NcMenuItem
key="table-explorer-insert-below"
data-testid="nc-field-item-action-insert-below"
@click="addField(field)"
>
<GeneralIcon icon="ncArrowDown" class="text-gray-800" />
<span>{{ $t('general.insertBelow') }}</span>
</NcMenuItem>
<a-menu-divider class="my-1.5" />
<NcMenuItem
key="table-explorer-delete"
class="!hover:bg-red-50"
data-testid="nc-field-item-action-delete"
@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" />
{{ $t('general.delete') }}
</div>
</NcMenuItem>
</template>
</NcMenu>
</template>
</NcDropdown>
<MdiChevronRight
class="text-brand-500 opacity-0"
:class="{
'opacity-100': compareCols(field, activeField),
}"
/>
</div>
</div>
</template>
<template
v-if="
displayColumn && displayColumn.title && displayColumn.title.toLowerCase().includes(searchQuery.toLowerCase())
"
#header
>
<div
class="flex px-2 bg-white hover:bg-gray-100 border-b-1 border-gray-200 first:rounded-tl-lg last:border-b-1 pl-5 group"
:class="` ${compareCols(displayColumn, activeField) ? 'selected' : ''}`"
:data-testid="`nc-field-item-${fieldState(displayColumn)?.title || displayColumn.title}`"
@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"
:class="{
'opacity-0 !cursor-default': isLocked,
}"
/>
<NcCheckbox :disabled="true" :checked="true" data-testid="nc-field-visibility-checkbox" />
<SmartsheetHeaderCellIcon
v-if="displayColumn"
:column-meta="fieldState(displayColumn) || displayColumn"
:class="{
'text-brand-500': compareCols(displayColumn, activeField),
}"
/>
<NcTooltip
class="truncate flex-1"
:class="{
'text-brand-500': compareCols(displayColumn, activeField),
}"
show-on-truncate-only
>
<template #title> {{ fieldState(displayColumn)?.title || displayColumn.title }} </template>
<span data-testid="nc-field-title">
{{ fieldState(displayColumn)?.title || displayColumn.title }}
</span>
</NcTooltip>
</div>
<div class="flex items-center justify-end gap-1">
<div class="flex items-center">
<NcBadge
v-if="fieldStatus(displayColumn) === 'delete'"
color="red"
:border="false"
class="bg-red-50 text-red-700"
data-testid="nc-field-status-deleted-field"
>
{{ $t('labels.multiField.deletedField') }}
</NcBadge>
<NcBadge
v-else-if="fieldStatus(displayColumn) === 'update'"
color="orange"
:border="false"
class="bg-orange-50 text-orange-700"
data-testid="nc-field-status-updated-field"
>
{{ $t('labels.multiField.updatedField') }}
</NcBadge>
</div>
<NcButton
v-if="fieldStatus(displayColumn) === 'delete' || fieldStatus(displayColumn) === 'update'"
type="secondary"
size="small"
class="no-action mr-2"
:disabled="loading"
data-testid="nc-field-restore-changes"
@click="recoverField(displayColumn)"
>
<div class="flex items-center text-xs gap-1">
<GeneralIcon icon="reload" />
{{ $t('general.restore') }}
</div>
</NcButton>
<NcDropdown
v-else
:trigger="['click']"
overlay-class-name="nc-field-item-action-dropdown-display-column nc-dropdown-table-explorer-display-column"
@update:visible="onFieldOptionUpdate"
@click.stop
>
<NcButton
size="xsmall"
type="text"
class="!opacity-0 !group-hover:(opacity-100)"
:class="{
'!hover:(text-brand-700 bg-brand-100) !group-hover:(text-brand-500)': compareCols(
displayColumn,
activeField,
),
'!hover:(text-gray-700 bg-gray-200) !group-hover:(text-gray-500)': !compareCols(
displayColumn,
activeField,
),
}"
data-testid="nc-field-item-action-button"
>
<GeneralIcon icon="threeDotVertical" class="no-action text-inherit" />
</NcButton>
<template #overlay>
<NcMenu>
<NcTooltip placement="top">
<template #title>{{ $t('msg.clickToCopyFieldId') }}</template>
<div
class="flex flex-row px-3 py-2 w-46 justify-between items-center group hover:bg-gray-100 cursor-pointer"
data-testid="nc-field-item-action-copy-id"
@click="onClickCopyFieldUrl(displayColumn)"
>
<div class="flex flex-row items-baseline gap-x-1 font-bold text-xs">
<div class="text-gray-600">{{ $t('labels.idColon') }}</div>
<div class="flex flex-row text-gray-600 text-xs">
{{ displayColumn.id }}
</div>
</div>
<NcButton size="xsmall" type="secondary" class="!group-hover:bg-gray-100">
<GeneralIcon v-if="isFieldIdCopied" icon="check" />
<GeneralIcon v-else icon="copy" />
</NcButton>
</div>
</NcTooltip>
</NcMenu>
</template>
</NcDropdown>
<MdiChevronRight
class="text-brand-500 opacity-0"
:class="{
'opacity-100': compareCols(displayColumn, activeField),
}"
/>
</div>
</div>
</template>
</Draggable>
</div>
<Transition name="slide-fade">
<div v-if="!changingField" class="border-gray-200 border-l-1 nc-scrollbar-md nc-fields-height !overflow-y-auto">
<SmartsheetColumnEditOrAddProvider
v-if="activeField"
class="p-4 w-[25rem]"
:column="activeField"
:preload="fieldState(activeField)"
:table-explorer-columns="fields"
:is-column-valid="isColumnValid"
embed-mode
:readonly="isLocked"
from-table-explorer
@update="onFieldUpdate"
@add="onFieldAdd"
/>
<div v-else class="w-[25rem] flex flex-col justify-center p-4 items-center">
<img src="~assets/img/placeholder/multi-field-editor.png" class="!w-[18rem]" />
<div class="text-2xl text-gray-600 font-bold text-center pt-6">{{ $t('labels.multiField.selectField') }}</div>
<div class="text-center text-sm px-2 text-gray-500 pt-6">
{{ $t('labels.multiField.selectFieldLabel') }}
</div>
</div>
</div>
</Transition>
</div>
</template>
</div>
</div>
</template>
<style lang="scss">
.nc-dropdown-table-explorer {
@apply !overflow-hidden;
}
.nc-dropdown-table-explorer > div > ul.ant-dropdown-menu.nc-menu {
@apply !pt-0;
}
.nc-dropdown-table-explorer-display-column {
@apply !overflow-hidden;
}
.nc-dropdown-table-explorer-display-column > div > ul.ant-dropdown-menu.nc-menu {
@apply !py-1.5;
}
</style>
<style lang="scss" scoped>
:deep(ul.ant-dropdown-menu.nc-menu) {
@apply !pt-0;
}
.add {
background-color: #e6ffed !important;
border-color: #b7eb8f;
}
.update {
background-color: #fffbe6 !important;
border-color: #ffe58f;
}
.delete {
background-color: #fff1f0 !important;
border-color: #ffa39e;
}
.selected {
@apply bg-brand-50;
}
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.5s cubic-bezier(1, 0.5, 0.8, 1);
}
.skip-animation {
transition: none;
}
.slide-fade-enter-from {
transform: translateX(20px);
opacity: 0;
}
.slide-fade-leave-to {
opacity: 0;
}
1 year ago
.nc-fields-height {
height: calc(100vh - (var(--topbar-height) * 3.6));
}
</style>