Browse Source

Merge pull request #4781 from ketewan/confirm-select-option-removal

Undo select option removal
pull/4870/head
navi 2 years ago committed by GitHub
parent
commit
f881346164
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      packages/nc-gui/components.d.ts
  2. 155
      packages/nc-gui/components/smartsheet/column/SelectOptions.vue
  3. 17
      tests/playwright/pages/Dashboard/Grid/Column/SelectOptionColumn.ts
  4. 7
      tests/playwright/tests/columnSingleSelect.spec.ts

1
packages/nc-gui/components.d.ts vendored

@ -124,6 +124,7 @@ declare module '@vue/runtime-core' {
MdiArrowDownDropCircleOutline: typeof import('~icons/mdi/arrow-down-drop-circle-outline')['default']
MdiArrowExpand: typeof import('~icons/mdi/arrow-expand')['default']
MdiArrowLeftBold: typeof import('~icons/mdi/arrow-left-bold')['default']
MdiArrowULeftBottom: typeof import('~icons/mdi/arrow-u-left-bottom')['default']
MdiAt: typeof import('~icons/mdi/at')['default']
MdiBackburger: typeof import('~icons/mdi/backburger')['default']
MdiBookOpenOutline: typeof import('~icons/mdi/book-open-outline')['default']

155
packages/nc-gui/components/smartsheet/column/SelectOptions.vue

@ -3,6 +3,14 @@ import Draggable from 'vuedraggable'
import { UITypes } from 'nocodb-sdk'
import { IsKanbanInj, enumColor, onMounted, useColumnCreateStoreOrThrow, useVModel, watch } from '#imports'
interface Option {
color: string
title: string
id?: string
fk_colum_id?: string
order?: number
}
const props = defineProps<{
value: any
}>()
@ -13,7 +21,10 @@ const vModel = useVModel(props, 'value', emit)
const { setAdditionalValidations, validateInfos, isPg, isMysql } = useColumnCreateStoreOrThrow()
let options = $ref<any[]>([])
let options = $ref<Option[]>([])
let renderedOptions = $ref<(Option & { status?: 'remove' })[]>([])
let savedDefaultOption = $ref<Option | null>(null)
let savedCdf = $ref<string | null>(null)
const colorMenus = $ref<any>({})
@ -58,6 +69,9 @@ onMounted(() => {
}
}
options = vModel.value.colOptions.options
renderedOptions = [...options]
// Support for older options
for (const op of options.filter((el) => el.order === null)) {
op.title = op.title.replace(/^'/, '').replace(/'$/, '')
@ -87,13 +101,6 @@ const optionChanged = (changedId: string) => {
}
}
const optionDropped = (changedId: string) => {
if (changedId && changedId === defaultOption.value?.id) {
vModel.value.cdf = null
defaultOption.value = null
}
}
const getNextColor = () => {
let tempColor = colors[0]
if (options.length && options[options.length - 1].color) {
@ -108,13 +115,40 @@ const addNewOption = () => {
title: '',
color: getNextColor(),
}
renderedOptions.push(tempOption)
options.push(tempOption)
}
const removeOption = (index: number) => {
const optionId = options[index]?.id
options.splice(index, 1)
optionDropped(optionId)
const syncOptions = () => {
vModel.value.colOptions.options = renderedOptions.filter((op) => op.status !== 'remove')
}
const removeRenderedOption = (index: number) => {
renderedOptions[index].status = 'remove'
syncOptions()
const optionId = renderedOptions[index]?.id
if (optionId === defaultOption.value?.id) {
savedDefaultOption = { ...defaultOption.value }
savedCdf = vModel.value.cdf
defaultOption.value = null
vModel.value.cdf = null
}
}
const undoRemoveRenderedOption = (index: number) => {
renderedOptions[index].status = undefined
syncOptions()
const optionId = renderedOptions[index]?.id
if (optionId === savedDefaultOption?.id) {
defaultOption.value = { ...savedDefaultOption }
vModel.value.cdf = savedCdf
savedDefaultOption = null
savedCdf = null
}
}
// focus last created input
@ -128,47 +162,62 @@ watch(inputs, () => {
<template>
<div class="w-full">
<div class="max-h-[250px] overflow-x-auto scrollbar-thin-dull pr-3">
<Draggable :list="options" item-key="id" handle=".nc-child-draggable-icon">
<Draggable :list="renderedOptions" item-key="id" handle=".nc-child-draggable-icon" @change="syncOptions">
<template #item="{ element, index }">
<div class="flex py-1 items-center nc-select-option">
<MdiDragVertical
v-if="!isKanban"
small
class="nc-child-draggable-icon handle"
:data-testid="`select-option-column-handle-icon-${element.title}`"
/>
<a-dropdown
v-model:visible="colorMenus[index]"
:trigger="['click']"
overlay-class-name="nc-dropdown-select-color-options"
<div class="flex p-1 items-center nc-select-option">
<div
class="flex items-center w-full"
:data-testid="`select-column-option-${index}`"
:class="{ removed: element.status === 'remove' }"
>
<template #overlay>
<LazyGeneralColorPicker
v-model="element.color"
:pick-button="true"
@update:model-value="colorMenus[index] = false"
<MdiDragVertical
v-if="!isKanban"
small
class="nc-child-draggable-icon handle"
:data-testid="`select-option-column-handle-icon-${element.title}`"
/>
<a-dropdown
v-model:visible="colorMenus[index]"
:trigger="['click']"
overlay-class-name="nc-dropdown-select-color-options"
>
<template #overlay>
<LazyGeneralColorPicker
v-model="element.color"
:pick-button="true"
@update:model-value="colorMenus[index] = false"
/>
</template>
<MdiArrowDownDropCircle
class="mr-2 text-[1.5em] outline-0 hover:!text-[1.75em]"
:class="{ 'text-[1.75em]': colorMenus[index] }"
:style="{ color: element.color }"
/>
</template>
<MdiArrowDownDropCircle
class="mr-2 text-[1.5em] outline-0 hover:!text-[1.75em]"
:class="{ 'text-[1.75em]': colorMenus[index] }"
:style="{ color: element.color }"
</a-dropdown>
<a-input
ref="inputs"
v-model:value="element.title"
class="caption"
:data-testid="`select-column-option-input-${index}`"
:disabled="element.status === 'remove'"
@keydown.enter.prevent="element.title?.trim() && addNewOption()"
@change="optionChanged(element.id)"
/>
</a-dropdown>
<a-input
ref="inputs"
v-model:value="element.title"
class="caption"
:data-testid="`select-column-option-input-${index}`"
@keydown.enter.prevent="element.title?.trim() && addNewOption()"
@change="optionChanged(element.id)"
/>
</div>
<MdiClose
v-if="element.status !== 'remove'"
class="ml-2 hover:!text-black-500 text-gray-500 cursor-pointer"
:data-testid="`select-column-option-remove-${index}`"
@click="removeOption(index)"
@click="removeRenderedOption(index)"
/>
<MdiArrowULeftBottom
v-else
class="ml-2 hover:!text-black-500 text-gray-500 cursor-pointer"
:data-testid="`select-column-option-remove-undo-${index}`"
@click="undoRemoveRenderedOption(index)"
/>
</div>
</template>
@ -186,3 +235,19 @@ watch(inputs, () => {
</a-button>
</div>
</template>
<style scoped>
.removed {
position: relative;
}
.removed:after {
position: absolute;
left: 0;
top: 50%;
height: 1px;
background: #ccc;
content: '';
width: calc(100% + 5px);
display: block;
}
</style>

17
tests/playwright/pages/Dashboard/Grid/Column/SelectOptionColumn.ts

@ -1,5 +1,6 @@
import { ColumnPageObject } from '.';
import BasePage from '../../../Base';
import { expect } from '@playwright/test';
export class SelectOptionColumnPageObject extends BasePage {
readonly column: ColumnPageObject;
@ -49,6 +50,22 @@ export class SelectOptionColumnPageObject extends BasePage {
await this.column.get().locator(`svg[data-testid="select-column-option-remove-${index}"]`).click();
await expect(this.column.get().getByTestId(`select-column-option-${index}`)).toHaveClass(/removed/);
await this.column.save({ isUpdated: true });
}
async deleteOptionWithUndo({ columnTitle, index }: { index: number; columnTitle: string }) {
await this.column.openEdit({ title: columnTitle });
await this.column.get().locator(`svg[data-testid="select-column-option-remove-${index}"]`).click();
await expect(this.column.get().getByTestId(`select-column-option-${index}`)).toHaveClass(/removed/);
await this.column.get().locator(`svg[data-testid="select-column-option-remove-undo-${index}"]`).click();
await expect(this.column.get().getByTestId(`select-column-option-${index}`)).not.toHaveClass(/removed/);
await this.column.save({ isUpdated: true });
}

7
tests/playwright/tests/columnSingleSelect.spec.ts

@ -53,6 +53,13 @@ test.describe('Single select', () => {
await grid.cell.selectOption.select({ index: 0, columnHeader: 'SingleSelect', option: 'Option 3' });
await grid.cell.selectOption.verify({ index: 0, columnHeader: 'SingleSelect', option: 'Option 3' });
await grid.column.selectOption.deleteOptionWithUndo({ index: 0, columnTitle: 'SingleSelect' });
await grid.cell.selectOption.verifyOptions({
index: 0,
columnHeader: 'SingleSelect',
options: ['Option 1', 'Option 2', 'Option 3'],
});
await grid.column.selectOption.deleteOption({ index: 2, columnTitle: 'SingleSelect' });
await grid.cell.selectOption.verifyNoOptionsSelected({ index: 0, columnHeader: 'SingleSelect' });

Loading…
Cancel
Save