|
|
@ -88,7 +88,7 @@ const getFieldOrder = (field?: TableExplorerColumn) => { |
|
|
|
|
|
|
|
|
|
|
|
const fields = computed<TableExplorerColumn[]>({ |
|
|
|
const fields = computed<TableExplorerColumn[]>({ |
|
|
|
get: () => { |
|
|
|
get: () => { |
|
|
|
const x = (meta.value?.columns as ColumnType[]) |
|
|
|
const x = ((meta.value?.columns as ColumnType[]) ?? []) |
|
|
|
.filter((field) => !field.fk_column_id && !isSystemColumn(field)) |
|
|
|
.filter((field) => !field.fk_column_id && !isSystemColumn(field)) |
|
|
|
.concat(newFields.value) |
|
|
|
.concat(newFields.value) |
|
|
|
.sort((a, b) => { |
|
|
|
.sort((a, b) => { |
|
|
@ -252,13 +252,30 @@ const duplicateField = async (field: TableExplorerColumn) => { |
|
|
|
const onFieldUpdate = (state: TableExplorerColumn) => { |
|
|
|
const onFieldUpdate = (state: TableExplorerColumn) => { |
|
|
|
const col = fields.value.find((col) => compareCols(col, state)) |
|
|
|
const col = fields.value.find((col) => compareCols(col, state)) |
|
|
|
if (!col) return |
|
|
|
if (!col) return |
|
|
|
|
|
|
|
|
|
|
|
const diffs = diff(col, state) |
|
|
|
const diffs = diff(col, state) |
|
|
|
if (Object.keys(diffs).length === 0 || (Object.keys(diffs).length === 1 && 'altered' in diffs)) { |
|
|
|
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)) |
|
|
|
ops.value = ops.value.filter((op) => op.op === 'add' || !compareCols(op.column, state)) |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
const field = ops.value.find((op) => compareCols(op.column, state)) |
|
|
|
const field = ops.value.find((op) => compareCols(op.column, state)) |
|
|
|
if (field) { |
|
|
|
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 && !moveField) { |
|
|
|
field.column = state |
|
|
|
field.column = state |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
ops.value.push({ |
|
|
|
ops.value.push({ |
|
|
@ -326,11 +343,22 @@ const onFieldAdd = (state: TableExplorerColumn) => { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const onMove = (_event: { moved: { newIndex: number; oldIndex: number } }) => { |
|
|
|
const onMove = (_event: { moved: { newIndex: number; oldIndex: number } }) => { |
|
|
|
const order = calculateOrderForIndex(_event.moved.newIndex, _event.moved.newIndex < _event.moved.oldIndex) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const field = fields.value[_event.moved.oldIndex] |
|
|
|
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)) |
|
|
|
const op = ops.value.find((op) => compareCols(op.column, field)) |
|
|
|
|
|
|
|
if (op?.op === 'update') { |
|
|
|
|
|
|
|
const diffs = diff(op.column, field) |
|
|
|
|
|
|
|
if (!(Object.keys(diffs).length === 1 && 'column_order' in diffs)) { |
|
|
|
|
|
|
|
message.warning('You cannot move field that is being edited. Either save or discard changes first') |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (op?.op === 'delete') { |
|
|
|
|
|
|
|
message.warning('You cannot move field that is deleted. Either save or discard changes first') |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (op) { |
|
|
|
if (op) { |
|
|
|
onFieldUpdate({ |
|
|
|
onFieldUpdate({ |
|
|
@ -364,6 +392,36 @@ const onMove = (_event: { moved: { newIndex: number; oldIndex: number } }) => { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const recoverField = (state: TableExplorerColumn) => { |
|
|
|
const recoverField = (state: TableExplorerColumn) => { |
|
|
|
const field = ops.value.find((op) => compareCols(op.column, state)) |
|
|
|
const field = ops.value.find((op) => compareCols(op.column, state)) |
|
|
|
if (field) { |
|
|
|
if (field) { |
|
|
@ -371,6 +429,7 @@ const recoverField = (state: TableExplorerColumn) => { |
|
|
|
ops.value = ops.value.filter((op) => !compareCols(op.column, state)) |
|
|
|
ops.value = ops.value.filter((op) => !compareCols(op.column, state)) |
|
|
|
} else if (field.op === 'update') { |
|
|
|
} else if (field.op === 'update') { |
|
|
|
ops.value = ops.value.filter((op) => !compareCols(op.column, state)) |
|
|
|
ops.value = ops.value.filter((op) => !compareCols(op.column, state)) |
|
|
|
|
|
|
|
moveOps.value = moveOps.value.filter((op) => !compareCols(op.column, state)) |
|
|
|
} |
|
|
|
} |
|
|
|
activeField.value = null |
|
|
|
activeField.value = null |
|
|
|
changeField(fields.value.filter((fiel) => fiel.id === state.id)[0]) |
|
|
|
changeField(fields.value.filter((fiel) => fiel.id === state.id)[0]) |
|
|
@ -447,17 +506,18 @@ const saveChanges = async () => { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const res = await $api.dbTableColumn.bulk(meta.value?.id, { |
|
|
|
|
|
|
|
hash: columnsHash.value, |
|
|
|
|
|
|
|
ops: ops.value, |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const op of visibilityOps.value) { |
|
|
|
for (const op of visibilityOps.value) { |
|
|
|
await toggleFieldVisibility(op.visible, { |
|
|
|
await toggleFieldVisibility(op.visible, { |
|
|
|
...op.column, |
|
|
|
...op.column, |
|
|
|
show: op.visible, |
|
|
|
show: op.visible, |
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const res = await $api.dbTableColumn.bulk(meta.value?.id, { |
|
|
|
|
|
|
|
hash: columnsHash.value, |
|
|
|
|
|
|
|
ops: ops.value, |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
await loadViewColumns() |
|
|
|
await loadViewColumns() |
|
|
|
|
|
|
|
|
|
|
|
if (res) { |
|
|
|
if (res) { |
|
|
@ -502,6 +562,8 @@ const toggleVisibility = async (checked: boolean, field: Field) => { |
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const isColumnsValid = computed(() => fields.value.every((f) => isColumnValid(f))) |
|
|
|
|
|
|
|
|
|
|
|
onMounted(async () => { |
|
|
|
onMounted(async () => { |
|
|
|
if (!meta.value?.id) return |
|
|
|
if (!meta.value?.id) return |
|
|
|
columnsHash.value = (await $api.dbTableColumn.hash(meta.value?.id)).hash |
|
|
|
columnsHash.value = (await $api.dbTableColumn.hash(meta.value?.id)).hash |
|
|
@ -509,306 +571,257 @@ onMounted(async () => { |
|
|
|
</script> |
|
|
|
</script> |
|
|
|
|
|
|
|
|
|
|
|
<template> |
|
|
|
<template> |
|
|
|
<div class="flex flex-col items-center w-full p-4" style="height: calc(100vh - (var(--topbar-height) * 2))"> |
|
|
|
<div class="w-full p-4"> |
|
|
|
<div class="h-full max-w-250 w-full"> |
|
|
|
<div class="max-w-250 h-full w-full mx-auto"> |
|
|
|
<div class="flex flex-col h-full"> |
|
|
|
<div class="flex w-full justify-between py-2"> |
|
|
|
<div class="flex w-full justify-between py-2"> |
|
|
|
<a-input v-model:value="searchQuery" class="!h-8 !px-1 !rounded-lg !w-72" placeholder="Search field"> |
|
|
|
<div class="flex flex-1 items-center gap-2"> |
|
|
|
<template #prefix> |
|
|
|
<h1 class="font-bold text-base">Fields</h1> |
|
|
|
<GeneralIcon icon="search" class="mx-1 h-3.5 w-3.5 text-gray-500 group-hover:text-black" /> |
|
|
|
<div class="flex bg-gray-100 items-center mb-1.5 rounded-lg px-2"> |
|
|
|
</template> |
|
|
|
<LazyGeneralEmojiPicker :emoji="selectedView?.meta?.icon" readonly size="xsmall"> |
|
|
|
<template #suffix> |
|
|
|
<template #default> |
|
|
|
<GeneralIcon |
|
|
|
<GeneralViewIcon :meta="{ type: selectedView?.type }" class="min-w-4.5 text-lg flex" /> |
|
|
|
v-if="searchQuery.length > 0" |
|
|
|
</template> |
|
|
|
icon="close" |
|
|
|
</LazyGeneralEmojiPicker> |
|
|
|
class="mx-1 h-3.5 w-3.5 text-gray-500 group-hover:text-black" |
|
|
|
|
|
|
|
@click="searchQuery = ''" |
|
|
|
<span class="text-sm pl-1.25 text-gray-700"> |
|
|
|
/> |
|
|
|
{{ selectedView?.title }} |
|
|
|
</template> |
|
|
|
</span> |
|
|
|
</a-input> |
|
|
|
|
|
|
|
<div class="flex gap-2"> |
|
|
|
|
|
|
|
<NcButton type="secondary" size="small" class="mr-1" :disabled="loading" @click="addField()"> |
|
|
|
|
|
|
|
<div class="flex items-center gap-2"> |
|
|
|
|
|
|
|
<GeneralIcon icon="plus" class="h-3.5 mb-1 w-3.5" /> |
|
|
|
|
|
|
|
New field |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</NcButton> |
|
|
|
<div class="flex gap-2"> |
|
|
|
<NcButton |
|
|
|
<NcButton |
|
|
|
type="secondary" |
|
|
|
type="secondary" |
|
|
|
size="small" |
|
|
|
size="small" |
|
|
|
:disabled="!loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1" |
|
|
|
:disabled="!loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1" |
|
|
|
@click="clearChanges()" |
|
|
|
@click="clearChanges()" |
|
|
|
> |
|
|
|
> |
|
|
|
Reset |
|
|
|
Reset |
|
|
|
</NcButton> |
|
|
|
</NcButton> |
|
|
|
<NcButton |
|
|
|
<NcButton |
|
|
|
type="primary" |
|
|
|
type="primary" |
|
|
|
size="small" |
|
|
|
size="small" |
|
|
|
:loading="loading" |
|
|
|
:loading="loading" |
|
|
|
:disabled="isColumnsValid ? !loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1 : true" |
|
|
|
:disabled="!loading && ops.length < 1 && moveOps.length < 1 && visibilityOps.length < 1" |
|
|
|
@click="saveChanges()" |
|
|
|
@click="saveChanges()" |
|
|
|
> |
|
|
|
> |
|
|
|
Save changes |
|
|
|
Save changes |
|
|
|
</NcButton> |
|
|
|
</NcButton> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
<div class="flex gap-x-4 overflow-y-auto"> |
|
|
|
<div class="flex flex-row rounded-lg border-1 border-gray-200"> |
|
|
|
<div class="flex flex-col flex-1 nc-scrollbar-md"> |
|
|
|
<div class="nc-scrollbar-md !overflow-auto w-full flex-grow-1 nc-fields-height"> |
|
|
|
<div class="flex w-full justify-between pb-2 pr-1"> |
|
|
|
<Draggable v-model="fields" item-key="id" @change="onMove($event)"> |
|
|
|
<a-input v-model:value="searchQuery" class="!h-8 !px-1 !rounded-lg !w-3/6" placeholder="Search field"> |
|
|
|
<template #item="{ element: field }"> |
|
|
|
<template #prefix> |
|
|
|
<div |
|
|
|
<GeneralIcon icon="search" class="mx-1 h-3.5 w-3.5 text-gray-500 group-hover:text-black" /> |
|
|
|
v-if="field.title.toLowerCase().includes(searchQuery.toLowerCase()) && !field.pv" |
|
|
|
</template> |
|
|
|
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" |
|
|
|
<template #suffix> |
|
|
|
:class="` ${compareCols(field, activeField) ? 'selected' : ''}`" |
|
|
|
<GeneralIcon |
|
|
|
@click="changeField(field, $event)" |
|
|
|
v-if="searchQuery.length > 0" |
|
|
|
> |
|
|
|
icon="close" |
|
|
|
<div class="flex items-center flex-1 py-2.5 gap-1 w-2/6"> |
|
|
|
class="mx-1 h-3.5 w-3.5 text-gray-500 group-hover:text-black" |
|
|
|
<component :is="iconMap.drag" class="cursor-move !h-3.75 text-gray-600 mr-1" /> |
|
|
|
@click="searchQuery = ''" |
|
|
|
<NcCheckbox |
|
|
|
|
|
|
|
v-if="field.id && viewFieldsMap[field.id]" |
|
|
|
|
|
|
|
:checked=" |
|
|
|
|
|
|
|
visibilityOps.find((op) => op.column.fk_column_id === field.id)?.visible ?? viewFieldsMap[field.id].show |
|
|
|
|
|
|
|
" |
|
|
|
|
|
|
|
@change=" |
|
|
|
|
|
|
|
(event) => { |
|
|
|
|
|
|
|
toggleVisibility(event.target.checked, viewFieldsMap[field.id]) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
" |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
<NcCheckbox v-else :disabled="true" class="opacity-0" :checked="true" /> |
|
|
|
|
|
|
|
<SmartsheetHeaderCellIcon |
|
|
|
|
|
|
|
v-if="field" |
|
|
|
|
|
|
|
:column-meta="fieldState(field) || field" |
|
|
|
|
|
|
|
:class="{ |
|
|
|
|
|
|
|
'text-brand-500': compareCols(field, activeField), |
|
|
|
|
|
|
|
}" |
|
|
|
/> |
|
|
|
/> |
|
|
|
</template> |
|
|
|
<span |
|
|
|
</a-input> |
|
|
|
:class="{ |
|
|
|
<NcButton type="secondary" size="small" :disabled="loading" @click="addField()"> |
|
|
|
'text-brand-500': compareCols(field, activeField), |
|
|
|
<div class="flex items-center gap-2"> |
|
|
|
}" |
|
|
|
<GeneralIcon icon="plus" class="h-3.5 mb-1 w-3.5" /> |
|
|
|
class="truncate max-w-64" |
|
|
|
New field |
|
|
|
> |
|
|
|
|
|
|
|
{{ fieldState(field)?.title || field.title }} |
|
|
|
|
|
|
|
</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</NcButton> |
|
|
|
<div class="flex items-center justify-end gap-1"> |
|
|
|
</div> |
|
|
|
<div class="flex items-center"> |
|
|
|
<Draggable v-model="fields" item-key="id" @change="onMove($event)"> |
|
|
|
<NcBadge v-if="fieldStatus(field) === 'delete'" color="red" :border="false" class="bg-red-50 text-red-700"> |
|
|
|
<template #item="{ element: field }"> |
|
|
|
Deleted field |
|
|
|
<div |
|
|
|
</NcBadge> |
|
|
|
v-if="field.title && field.title.toLowerCase().includes(searchQuery.toLowerCase()) && !field.pv" |
|
|
|
<NcBadge |
|
|
|
class="flex px-2 mr-1 border-x-1 bg-white border-t-1 hover:bg-gray-100 first:rounded-t-lg last:border-b-1 last:rounded-b-lg pl-5 group" |
|
|
|
v-else-if="fieldStatus(field) === 'add'" |
|
|
|
:class="` ${compareCols(field, activeField) ? 'selected' : ''}`" |
|
|
|
color="orange" |
|
|
|
@click="changeField(field, $event)" |
|
|
|
:border="false" |
|
|
|
> |
|
|
|
class="bg-green-50 text-green-700" |
|
|
|
<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" /> |
|
|
|
|
|
|
|
<NcCheckbox |
|
|
|
|
|
|
|
v-if="field.id && viewFieldsMap[field.id]" |
|
|
|
|
|
|
|
:checked=" |
|
|
|
|
|
|
|
visibilityOps.find((op) => op.column.fk_column_id === field.id)?.visible ?? viewFieldsMap[field.id].show |
|
|
|
|
|
|
|
" |
|
|
|
|
|
|
|
@change=" |
|
|
|
|
|
|
|
(event) => { |
|
|
|
|
|
|
|
toggleVisibility(event.target.checked, viewFieldsMap[field.id]) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
" |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
<NcCheckbox v-else :disabled="true" class="opacity-0" :checked="true" /> |
|
|
|
|
|
|
|
<SmartsheetHeaderCellIcon |
|
|
|
|
|
|
|
v-if="field" |
|
|
|
|
|
|
|
:column-meta="fieldState(field) || field" |
|
|
|
|
|
|
|
:class="{ |
|
|
|
|
|
|
|
'text-brand-500': compareCols(field, activeField), |
|
|
|
|
|
|
|
}" |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
<span |
|
|
|
|
|
|
|
:class="{ |
|
|
|
|
|
|
|
'text-brand-500': compareCols(field, activeField), |
|
|
|
|
|
|
|
}" |
|
|
|
|
|
|
|
class="truncate max-w-64" |
|
|
|
|
|
|
|
> |
|
|
|
> |
|
|
|
{{ fieldState(field)?.title || field.title }} |
|
|
|
New field |
|
|
|
</span> |
|
|
|
</NcBadge> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="flex items-center justify-end gap-1"> |
|
|
|
<NcBadge |
|
|
|
<div class="flex items-center"> |
|
|
|
v-else-if="fieldStatus(field) === 'update'" |
|
|
|
<NcBadge v-if="fieldStatus(field) === 'delete'" color="red" :border="false" class="bg-red-50 text-red-700"> |
|
|
|
color="orange" |
|
|
|
Deleted field |
|
|
|
:border="false" |
|
|
|
</NcBadge> |
|
|
|
class="bg-orange-50 text-orange-700" |
|
|
|
<NcBadge |
|
|
|
|
|
|
|
v-else-if="fieldStatus(field) === 'add'" |
|
|
|
|
|
|
|
color="orange" |
|
|
|
|
|
|
|
:border="false" |
|
|
|
|
|
|
|
class="bg-green-50 text-green-700" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
New field |
|
|
|
|
|
|
|
</NcBadge> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<NcBadge |
|
|
|
|
|
|
|
v-else-if="fieldStatus(field) === 'update'" |
|
|
|
|
|
|
|
color="orange" |
|
|
|
|
|
|
|
:border="false" |
|
|
|
|
|
|
|
class="bg-orange-50 text-orange-700" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
Updated field |
|
|
|
|
|
|
|
</NcBadge> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
<NcButton |
|
|
|
|
|
|
|
v-if="fieldStatus(field) === 'delete' || fieldStatus(field) === 'update'" |
|
|
|
|
|
|
|
type="secondary" |
|
|
|
|
|
|
|
size="small" |
|
|
|
|
|
|
|
class="no-action mr-2" |
|
|
|
|
|
|
|
:disabled="loading" |
|
|
|
|
|
|
|
@click="recoverField(field)" |
|
|
|
|
|
|
|
> |
|
|
|
> |
|
|
|
<div class="flex items-center text-xs gap-1"> |
|
|
|
Updated field |
|
|
|
<GeneralIcon icon="reload" /> |
|
|
|
</NcBadge> |
|
|
|
Restore |
|
|
|
<NcBadge |
|
|
|
</div> |
|
|
|
v-if="!isColumnValid(field)" |
|
|
|
</NcButton> |
|
|
|
color="yellow" |
|
|
|
<a-dropdown v-else :trigger="['click']" overlay-class-name="nc-dropdown-table-explorer" @click.stop> |
|
|
|
:border="false" |
|
|
|
<GeneralIcon icon="threeDotVertical" class="no-action opacity-0 group-hover:(opacity-100) text-gray-500" /> |
|
|
|
class="ml-1 bg-yellow-50 text-yellow-700" |
|
|
|
|
|
|
|
|
|
|
|
<template #overlay> |
|
|
|
|
|
|
|
<a-menu> |
|
|
|
|
|
|
|
<a-menu-item key="table-explorer-duplicate" @click="duplicateField(field)"> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
|
|
|
|
<Icon class="iconify text-gray-800" icon="lucide:copy" /><span>Duplicate</span> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
<a-menu-item v-if="!field.pv" key="table-explorer-insert-above" @click="addField(field, true)"> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
|
|
|
|
<Icon class="iconify text-gray-800" icon="lucide:arrow-up" /><span>Insert above</span> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
<a-menu-item key="table-explorer-insert-below" @click="addField(field)"> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
|
|
|
|
<Icon class="iconify text-gray-800" icon="lucide:arrow-down" /><span>Insert below</span> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-menu-divider class="my-0" /> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-menu-item key="table-explorer-delete" @click="onFieldDelete(field)"> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item group text-red-500"> |
|
|
|
|
|
|
|
<GeneralIcon icon="delete" class="group-hover:text-accent" /> |
|
|
|
|
|
|
|
Delete |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
</a-menu> |
|
|
|
|
|
|
|
</template> |
|
|
|
|
|
|
|
</a-dropdown> |
|
|
|
|
|
|
|
<MdiChevronRight |
|
|
|
|
|
|
|
class="text-brand-500 opacity-0" |
|
|
|
|
|
|
|
:class="{ |
|
|
|
|
|
|
|
'opacity-100': compareCols(field, activeField), |
|
|
|
|
|
|
|
}" |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</template> |
|
|
|
|
|
|
|
<template v-if="displayColumn && displayColumn.title.toLowerCase().includes(searchQuery.toLowerCase())" #header> |
|
|
|
|
|
|
|
<div |
|
|
|
|
|
|
|
class="flex px-2 mr-1 border-x-1 bg-white border-t-1 hover:bg-gray-100 first:rounded-t-lg last:border-b-1 last:rounded-b-lg pl-5 group" |
|
|
|
|
|
|
|
:class="` ${compareCols(displayColumn, activeField) ? 'selected' : ''}`" |
|
|
|
|
|
|
|
@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" /> |
|
|
|
|
|
|
|
<NcCheckbox :disabled="true" :checked="true" /> |
|
|
|
|
|
|
|
<SmartsheetHeaderCellIcon |
|
|
|
|
|
|
|
v-if="displayColumn" |
|
|
|
|
|
|
|
:column-meta="fieldState(displayColumn) || displayColumn" |
|
|
|
|
|
|
|
:class="{ |
|
|
|
|
|
|
|
'text-brand-500': compareCols(displayColumn, activeField), |
|
|
|
|
|
|
|
}" |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
<span |
|
|
|
|
|
|
|
:class="{ |
|
|
|
|
|
|
|
'text-brand-500': compareCols(displayColumn, activeField), |
|
|
|
|
|
|
|
}" |
|
|
|
|
|
|
|
> |
|
|
|
> |
|
|
|
{{ fieldState(displayColumn)?.title || displayColumn.title }} |
|
|
|
Incomplete configuration |
|
|
|
</span> |
|
|
|
</NcBadge> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="flex items-center justify-end gap-1"> |
|
|
|
<NcButton |
|
|
|
<div class="flex items-center"> |
|
|
|
v-if="fieldStatus(field) === 'delete' || fieldStatus(field) === 'update'" |
|
|
|
<NcBadge |
|
|
|
type="secondary" |
|
|
|
v-if="fieldStatus(displayColumn) === 'delete'" |
|
|
|
size="small" |
|
|
|
color="red" |
|
|
|
class="no-action mr-2" |
|
|
|
:border="false" |
|
|
|
:disabled="loading" |
|
|
|
class="bg-red-50 text-red-700" |
|
|
|
@click="recoverField(field)" |
|
|
|
> |
|
|
|
> |
|
|
|
Deleted field |
|
|
|
<div class="flex items-center text-xs gap-1"> |
|
|
|
</NcBadge> |
|
|
|
<GeneralIcon icon="reload" /> |
|
|
|
|
|
|
|
Restore |
|
|
|
<NcBadge |
|
|
|
|
|
|
|
v-else-if="fieldStatus(displayColumn) === 'update'" |
|
|
|
|
|
|
|
color="orange" |
|
|
|
|
|
|
|
:border="false" |
|
|
|
|
|
|
|
class="bg-orange-50 text-orange-700" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
Updated field |
|
|
|
|
|
|
|
</NcBadge> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<NcButton |
|
|
|
</NcButton> |
|
|
|
v-if="fieldStatus(displayColumn) === 'delete' || fieldStatus(displayColumn) === 'update'" |
|
|
|
<NcDropdown v-else :trigger="['click']" overlay-class-name="nc-dropdown-table-explorer" @click.stop> |
|
|
|
type="secondary" |
|
|
|
<GeneralIcon icon="threeDotVertical" class="no-action opacity-0 group-hover:(opacity-100) text-gray-500" /> |
|
|
|
size="small" |
|
|
|
|
|
|
|
class="no-action mr-2" |
|
|
|
<template #overlay> |
|
|
|
:disabled="loading" |
|
|
|
<NcMenu> |
|
|
|
@click="recoverField(displayColumn)" |
|
|
|
<NcMenuItem key="table-explorer-duplicate" @click="duplicateField(field)"> |
|
|
|
|
|
|
|
<Icon class="iconify text-gray-800" icon="lucide:copy" /><span>Duplicate</span> |
|
|
|
|
|
|
|
</NcMenuItem> |
|
|
|
|
|
|
|
<NcMenuItem v-if="!field.pv" key="table-explorer-insert-above" @click="addField(field, true)"> |
|
|
|
|
|
|
|
<Icon class="iconify text-gray-800" icon="lucide:arrow-up" /><span>Insert above</span> |
|
|
|
|
|
|
|
</NcMenuItem> |
|
|
|
|
|
|
|
<NcMenuItem key="table-explorer-insert-below" @click="addField(field)"> |
|
|
|
|
|
|
|
<Icon class="iconify text-gray-800" icon="lucide:arrow-down" /><span>Insert below</span> |
|
|
|
|
|
|
|
</NcMenuItem> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-menu-divider class="my-1" /> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<NcMenuItem key="table-explorer-delete" @click="onFieldDelete(field)"> |
|
|
|
|
|
|
|
<div class="text-red-500"> |
|
|
|
|
|
|
|
<GeneralIcon icon="delete" class="group-hover:text-accent" /> |
|
|
|
|
|
|
|
Delete |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</NcMenuItem> |
|
|
|
|
|
|
|
</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.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' : ''}`" |
|
|
|
|
|
|
|
@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" /> |
|
|
|
|
|
|
|
<NcCheckbox :disabled="true" :checked="true" /> |
|
|
|
|
|
|
|
<SmartsheetHeaderCellIcon |
|
|
|
|
|
|
|
v-if="displayColumn" |
|
|
|
|
|
|
|
:column-meta="fieldState(displayColumn) || displayColumn" |
|
|
|
|
|
|
|
:class="{ |
|
|
|
|
|
|
|
'text-brand-500': compareCols(displayColumn, activeField), |
|
|
|
|
|
|
|
}" |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
<span |
|
|
|
|
|
|
|
:class="{ |
|
|
|
|
|
|
|
'text-brand-500': compareCols(displayColumn, activeField), |
|
|
|
|
|
|
|
}" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
{{ fieldState(displayColumn)?.title || displayColumn.title }} |
|
|
|
|
|
|
|
</span> |
|
|
|
|
|
|
|
</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" |
|
|
|
> |
|
|
|
> |
|
|
|
<div class="flex items-center text-xs gap-1"> |
|
|
|
Deleted field |
|
|
|
<GeneralIcon icon="reload" /> |
|
|
|
</NcBadge> |
|
|
|
Restore |
|
|
|
|
|
|
|
</div> |
|
|
|
<NcBadge |
|
|
|
</NcButton> |
|
|
|
v-else-if="fieldStatus(displayColumn) === 'update'" |
|
|
|
<a-dropdown v-else :trigger="['click']" overlay-class-name="nc-dropdown-table-explorer" @click.stop> |
|
|
|
color="orange" |
|
|
|
<GeneralIcon icon="threeDotVertical" class="no-action opacity-0 group-hover:(opacity-100) text-gray-500" /> |
|
|
|
:border="false" |
|
|
|
|
|
|
|
class="bg-orange-50 text-orange-700" |
|
|
|
<template #overlay> |
|
|
|
> |
|
|
|
<a-menu> |
|
|
|
Updated field |
|
|
|
<a-menu-item key="table-explorer-duplicate" @click="duplicateField(displayColumn)"> |
|
|
|
</NcBadge> |
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
|
|
|
|
<Icon class="iconify text-gray-800" icon="lucide:copy" /><span>Duplicate</span> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
<a-menu-item v-if="!field.pv" key="table-explorer-insert-above" @click="addField(displayColumn, true)"> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
|
|
|
|
<Icon class="iconify text-gray-800" icon="lucide:arrow-up" /><span>Insert above</span> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
<a-menu-item key="table-explorer-insert-below" @click="addField(displayColumn)"> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item"> |
|
|
|
|
|
|
|
<Icon class="iconify text-gray-800" icon="lucide:arrow-down" /><span>Insert below</span> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-menu-divider class="my-0" /> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-menu-item key="table-explorer-delete" @click="onFieldDelete(displayColumn)"> |
|
|
|
|
|
|
|
<div class="nc-project-menu-item group text-red-500"> |
|
|
|
|
|
|
|
<GeneralIcon icon="delete" class="group-hover:text-accent" /> |
|
|
|
|
|
|
|
Delete |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</a-menu-item> |
|
|
|
|
|
|
|
</a-menu> |
|
|
|
|
|
|
|
</template> |
|
|
|
|
|
|
|
</a-dropdown> |
|
|
|
|
|
|
|
<MdiChevronRight |
|
|
|
|
|
|
|
class="text-brand-500 opacity-0" |
|
|
|
|
|
|
|
:class="{ |
|
|
|
|
|
|
|
'opacity-100': compareCols(displayColumn, activeField), |
|
|
|
|
|
|
|
}" |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<NcButton |
|
|
|
</template> |
|
|
|
v-if="fieldStatus(displayColumn) === 'delete' || fieldStatus(displayColumn) === 'update'" |
|
|
|
</Draggable> |
|
|
|
type="secondary" |
|
|
|
</div> |
|
|
|
size="small" |
|
|
|
<Transition v-if="!changingField" name="slide-fade"> |
|
|
|
class="no-action mr-2" |
|
|
|
<div class="flex p-4 h-fit w-1/3 border-gray-200 border-1 rounded-xl"> |
|
|
|
:disabled="loading" |
|
|
|
<SmartsheetColumnEditOrAddProvider |
|
|
|
@click="recoverField(displayColumn)" |
|
|
|
v-if="activeField" |
|
|
|
> |
|
|
|
class="w-full" |
|
|
|
<div class="flex items-center text-xs gap-1"> |
|
|
|
:column="activeField" |
|
|
|
<GeneralIcon icon="reload" /> |
|
|
|
:preload="fieldState(activeField)" |
|
|
|
Restore |
|
|
|
:table-explorer-columns="fields" |
|
|
|
</div> |
|
|
|
embed-mode |
|
|
|
</NcButton> |
|
|
|
from-table-explorer |
|
|
|
<MdiChevronRight |
|
|
|
@update="onFieldUpdate" |
|
|
|
class="text-brand-500 opacity-0" |
|
|
|
@add="onFieldAdd" |
|
|
|
:class="{ |
|
|
|
/> |
|
|
|
'opacity-100': compareCols(displayColumn, activeField), |
|
|
|
<div v-else class="flex flex-col gap-6 w-full items-center"> |
|
|
|
}" |
|
|
|
<img src="~assets/img/fieldPlaceholder.svg" class="!w-[18rem]" /> |
|
|
|
/> |
|
|
|
<div class="text-2xl text-gray-600 font-bold text-center">Select a field</div> |
|
|
|
|
|
|
|
<div class="text-center text-sm px-2 text-gray-500"> |
|
|
|
|
|
|
|
Make changes to field properties by selecting a field from the list |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
</Transition> |
|
|
|
</Draggable> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<Transition v-if="!changingField" name="slide-fade"> |
|
|
|
|
|
|
|
<div class="border-gray-200 border-l-1 rounded-r-xl h-[calc(100vh-(var(--topbar-height)*3.85))]"> |
|
|
|
|
|
|
|
<SmartsheetColumnEditOrAddProvider |
|
|
|
|
|
|
|
v-if="activeField" |
|
|
|
|
|
|
|
class="p-4 w-[25rem]" |
|
|
|
|
|
|
|
:column="activeField" |
|
|
|
|
|
|
|
:preload="fieldState(activeField)" |
|
|
|
|
|
|
|
:table-explorer-columns="fields" |
|
|
|
|
|
|
|
embed-mode |
|
|
|
|
|
|
|
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/fieldPlaceholder.svg" class="!w-[18rem]" /> |
|
|
|
|
|
|
|
<div class="text-2xl text-gray-600 font-bold text-center pt-6">Select a field</div> |
|
|
|
|
|
|
|
<div class="text-center text-sm px-2 text-gray-500 pt-6"> |
|
|
|
|
|
|
|
Make changes to field properties by selecting a field from the list |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</Transition> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@ -849,4 +862,8 @@ onMounted(async () => { |
|
|
|
.slide-fade-leave-to { |
|
|
|
.slide-fade-leave-to { |
|
|
|
opacity: 0; |
|
|
|
opacity: 0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.nc-fields-height { |
|
|
|
|
|
|
|
height: calc(100vh - (var(--topbar-height) * 3.6)); |
|
|
|
|
|
|
|
} |
|
|
|
</style> |
|
|
|
</style> |
|
|
|