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'] MdiArrowDownDropCircleOutline: typeof import('~icons/mdi/arrow-down-drop-circle-outline')['default']
MdiArrowExpand: typeof import('~icons/mdi/arrow-expand')['default'] MdiArrowExpand: typeof import('~icons/mdi/arrow-expand')['default']
MdiArrowLeftBold: typeof import('~icons/mdi/arrow-left-bold')['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'] MdiAt: typeof import('~icons/mdi/at')['default']
MdiBackburger: typeof import('~icons/mdi/backburger')['default'] MdiBackburger: typeof import('~icons/mdi/backburger')['default']
MdiBookOpenOutline: typeof import('~icons/mdi/book-open-outline')['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 { UITypes } from 'nocodb-sdk'
import { IsKanbanInj, enumColor, onMounted, useColumnCreateStoreOrThrow, useVModel, watch } from '#imports' 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<{ const props = defineProps<{
value: any value: any
}>() }>()
@ -13,7 +21,10 @@ const vModel = useVModel(props, 'value', emit)
const { setAdditionalValidations, validateInfos, isPg, isMysql } = useColumnCreateStoreOrThrow() 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>({}) const colorMenus = $ref<any>({})
@ -58,6 +69,9 @@ onMounted(() => {
} }
} }
options = vModel.value.colOptions.options options = vModel.value.colOptions.options
renderedOptions = [...options]
// Support for older options // Support for older options
for (const op of options.filter((el) => el.order === null)) { for (const op of options.filter((el) => el.order === null)) {
op.title = op.title.replace(/^'/, '').replace(/'$/, '') 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 = () => { const getNextColor = () => {
let tempColor = colors[0] let tempColor = colors[0]
if (options.length && options[options.length - 1].color) { if (options.length && options[options.length - 1].color) {
@ -108,13 +115,40 @@ const addNewOption = () => {
title: '', title: '',
color: getNextColor(), color: getNextColor(),
} }
renderedOptions.push(tempOption)
options.push(tempOption) options.push(tempOption)
} }
const removeOption = (index: number) => { const syncOptions = () => {
const optionId = options[index]?.id vModel.value.colOptions.options = renderedOptions.filter((op) => op.status !== 'remove')
options.splice(index, 1) }
optionDropped(optionId)
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 // focus last created input
@ -128,47 +162,62 @@ watch(inputs, () => {
<template> <template>
<div class="w-full"> <div class="w-full">
<div class="max-h-[250px] overflow-x-auto scrollbar-thin-dull pr-3"> <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 }"> <template #item="{ element, index }">
<div class="flex py-1 items-center nc-select-option"> <div class="flex p-1 items-center nc-select-option">
<MdiDragVertical <div
v-if="!isKanban" class="flex items-center w-full"
small :data-testid="`select-column-option-${index}`"
class="nc-child-draggable-icon handle" :class="{ removed: element.status === 'remove' }"
: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> <MdiDragVertical
<LazyGeneralColorPicker v-if="!isKanban"
v-model="element.color" small
:pick-button="true" class="nc-child-draggable-icon handle"
@update:model-value="colorMenus[index] = false" :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> </a-dropdown>
<MdiArrowDownDropCircle
class="mr-2 text-[1.5em] outline-0 hover:!text-[1.75em]" <a-input
:class="{ 'text-[1.75em]': colorMenus[index] }" ref="inputs"
:style="{ color: element.color }" 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> </div>
<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)"
/>
<MdiClose <MdiClose
v-if="element.status !== 'remove'"
class="ml-2 hover:!text-black-500 text-gray-500 cursor-pointer" class="ml-2 hover:!text-black-500 text-gray-500 cursor-pointer"
:data-testid="`select-column-option-remove-${index}`" :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> </div>
</template> </template>
@ -186,3 +235,19 @@ watch(inputs, () => {
</a-button> </a-button>
</div> </div>
</template> </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 { ColumnPageObject } from '.';
import BasePage from '../../../Base'; import BasePage from '../../../Base';
import { expect } from '@playwright/test';
export class SelectOptionColumnPageObject extends BasePage { export class SelectOptionColumnPageObject extends BasePage {
readonly column: ColumnPageObject; 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 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 }); 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.select({ index: 0, columnHeader: 'SingleSelect', option: 'Option 3' });
await grid.cell.selectOption.verify({ 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.column.selectOption.deleteOption({ index: 2, columnTitle: 'SingleSelect' });
await grid.cell.selectOption.verifyNoOptionsSelected({ index: 0, columnHeader: 'SingleSelect' }); await grid.cell.selectOption.verifyNoOptionsSelected({ index: 0, columnHeader: 'SingleSelect' });

Loading…
Cancel
Save