fix validation and register roles

This commit is contained in:
samunohito 2024-02-26 08:12:37 +09:00
parent cb668b22ad
commit 390af67949
8 changed files with 99 additions and 57 deletions

View File

@ -269,7 +269,7 @@ useTooltip(rootEl, (showing) => {
return;
}
const content = cell.value.violation.violations.map(it => it.result.message).join('\n');
const content = cell.value.violation.violations.filter(it => !it.valid).map(it => it.result.message).join('\n');
os.popup(defineAsyncComponent(() => import('@/components/grid/MkCellTooltip.vue')), {
showing,
content,

View File

@ -76,12 +76,14 @@ const props = defineProps<{
}>();
// non-reactive
// eslint-disable-next-line vue/no-setup-props-destructure
const rowSetting: Required<GridRowSetting> = {
...defaultGridRowSetting,
...props.settings.row,
};
// non-reactive
// eslint-disable-next-line vue/no-setup-props-destructure
const columnSettings = props.settings.cols;
// non-reactive
@ -1117,21 +1119,22 @@ function refreshData() {
//
//
const _cells: RowHolder[] = _rows.map(row => {
const cells = row.using
? _cols.map(col => {
const cell = createCell(col, row, _data[row.index][col.setting.bindTo], cellSettings);
cell.violation = cellValidation(cell, cell.value);
return cell;
})
const newCells = row.using
? _cols.map(col => createCell(col, row, _data[row.index][col.setting.bindTo], cellSettings))
: _cols.map(col => createCell(col, row, undefined, cellSettings));
return { row, cells, origin: _data[row.index] };
return { row, cells: newCells, origin: _data[row.index] };
});
rows.value = _rows;
cells.value = _cells;
applyRowRules(_cells.filter(it => it.row.using).flatMap(it => it.cells));
const allCells = _cells.filter(it => it.row.using).flatMap(it => it.cells);
for (const cell of allCells) {
cell.violation = cellValidation(allCells, cell, cell.value);
}
applyRowRules(allCells);
if (_DEV_) {
console.log('[grid][refresh-data][end]');
@ -1205,7 +1208,6 @@ function patchData(newItems: DataSource[]) {
const oldCell = oldCells[colIdx];
const newValue = newItem[_col.setting.bindTo];
if (oldCell.value !== newValue) {
oldCell.violation = cellValidation(oldCell, newValue);
oldCell.value = _col.setting.valueTransformer
? _col.setting.valueTransformer(holder.row, _col, newValue)
: newValue;
@ -1215,6 +1217,11 @@ function patchData(newItems: DataSource[]) {
}
if (changedCells.length > 0) {
const allCells = cells.value.slice(0, newItems.length).flatMap(it => it.cells);
for (const cell of allCells) {
cell.violation = cellValidation(allCells, cell, cell.value);
}
applyRowRules(changedCells);
//

View File

@ -6,6 +6,7 @@ export type ValidatorParams = {
column: GridColumn;
row: GridRow;
value: CellValue;
allCells: GridCell[];
};
export type ValidatorResult = {
@ -13,7 +14,7 @@ export type ValidatorResult = {
message?: string;
}
export type CellValidator = {
export type GridCellValidator = {
name?: string;
ignoreViolation?: boolean;
validate: (params: ValidatorParams) => ValidatorResult;
@ -27,11 +28,11 @@ export type ValidateViolation = {
export type ValidateViolationItem = {
valid: boolean;
validator: CellValidator;
validator: GridCellValidator;
result: ValidatorResult;
}
export function cellValidation(cell: GridCell, newValue: CellValue): ValidateViolation {
export function cellValidation(allCells: GridCell[], cell: GridCell, newValue: CellValue): ValidateViolation {
const { column, row } = cell;
const validators = column.setting.validators ?? [];
@ -39,6 +40,7 @@ export function cellValidation(cell: GridCell, newValue: CellValue): ValidateVio
column,
row,
value: newValue,
allCells,
};
const violations: ValidateViolationItem[] = validators.map(validator => {
@ -58,11 +60,10 @@ export function cellValidation(cell: GridCell, newValue: CellValue): ValidateVio
}
class ValidatorPreset {
required(): CellValidator {
required(): GridCellValidator {
return {
name: 'required',
validate: (params: ValidatorParams): ValidatorResult => {
const { value } = params;
validate: ({ value }): ValidatorResult => {
return {
valid: value !== null && value !== undefined && value !== '',
message: 'This field is required.',
@ -71,11 +72,10 @@ class ValidatorPreset {
};
}
regex(pattern: RegExp): CellValidator {
regex(pattern: RegExp): GridCellValidator {
return {
name: 'regex',
validate: (params: ValidatorParams): ValidatorResult => {
const { value, column } = params;
validate: ({ value, column }): ValidatorResult => {
if (column.setting.type !== 'text') {
return {
valid: false,
@ -90,6 +90,22 @@ class ValidatorPreset {
},
};
}
unique(): GridCellValidator {
return {
name: 'unique',
validate: ({ column, row, value, allCells }): ValidatorResult => {
const bindTo = column.setting.bindTo;
const isUnique = allCells
.filter(it => it.column.setting.bindTo === bindTo && it.row.index !== row.index)
.every(cell => cell.value !== value);
return {
valid: isUnique,
message: 'This value is already used.',
};
},
};
}
}
export const validators = new ValidatorPreset();

View File

@ -1,4 +1,4 @@
import { CellValidator } from '@/components/grid/cell-validators.js';
import { GridCellValidator } from '@/components/grid/cell-validators.js';
import { Size, SizeStyle } from '@/components/grid/grid.js';
import { calcCellWidth } from '@/components/grid/grid-utils.js';
import { CellValue, GridCell } from '@/components/grid/cell.js';
@ -19,7 +19,7 @@ export type GridColumnSetting = {
type: ColumnType;
width: SizeStyle;
editable?: boolean;
validators?: CellValidator[];
validators?: GridCellValidator[];
customValueEditor?: CustomValueEditor;
valueTransformer?: CellValueTransformer;
contextMenuFactory?: GridColumnContextMenuFactory;

View File

@ -17,3 +17,20 @@ export function emptyStrToEmptyArray(value: string) {
return value === '' ? [] : value.split(',').map(it => it.trim());
}
export function roleIdsParser(text: string): { id: string, name: string }[] {
// idとnameのペア配列をJSONで受け取る。それ以外の形式は許容しない
try {
const obj = JSON.parse(text);
if (!Array.isArray(obj)) {
return [];
}
if (!obj.every(it => typeof it === 'object' && 'id' in it && 'name' in it)) {
return [];
}
return obj.map(it => ({ id: it.id, name: it.name }));
} catch (ex) {
console.warn(ex);
return [];
}
}

View File

@ -189,13 +189,13 @@ import {
emptyStrToEmptyArray,
emptyStrToNull,
emptyStrToUndefined,
RequestLogItem,
RequestLogItem, roleIdsParser,
} from '@/pages/admin/custom-emojis-manager.impl.js';
import MkGrid from '@/components/grid/MkGrid.vue';
import { i18n } from '@/i18n.js';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
import { validators } from '@/components/grid/cell-validators.js';
import { GridCellValidator, validators } from '@/components/grid/cell-validators.js';
import { GridCellValidationEvent, GridCellValueChangeEvent, GridEvent } from '@/components/grid/grid-event.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import MkPagingButtons from '@/components/MkPagingButtons.vue';
@ -247,6 +247,7 @@ type GridSortOrder = {
function setupGrid(): GridSetting {
const required = validators.required();
const regex = validators.regex(/^[a-zA-Z0-9_]+$/);
const unique = validators.unique();
return {
row: {
showNumber: true,
@ -302,7 +303,10 @@ function setupGrid(): GridSetting {
return file.url;
},
},
{ bindTo: 'name', title: 'name', type: 'text', editable: true, width: 140, validators: [required, regex] },
{
bindTo: 'name', title: 'name', type: 'text', editable: true, width: 140,
validators: [required, regex, unique],
},
{ bindTo: 'category', title: 'category', type: 'text', editable: true, width: 140 },
{ bindTo: 'aliases', title: 'aliases', type: 'text', editable: true, width: 140 },
{ bindTo: 'license', title: 'license', type: 'text', editable: true, width: 140 },
@ -335,23 +339,7 @@ function setupGrid(): GridSetting {
return transform;
},
events: {
paste(text) {
// idnameJSON
try {
const obj = JSON.parse(text);
if (!Array.isArray(obj)) {
return [];
}
if (!obj.every(it => typeof it === 'object' && 'id' in it && 'name' in it)) {
return [];
}
return obj.map(it => ({ id: it.id, name: it.name }));
} catch (ex) {
console.warn(ex);
return [];
}
},
paste: roleIdsParser,
delete(cell) {
// undefined
gridItems.value[cell.row.index].roleIdsThatCanBeUsedThisEmojiAsReaction = [];

View File

@ -75,7 +75,12 @@
import * as Misskey from 'misskey-js';
import { onMounted, ref } from 'vue';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { emptyStrToEmptyArray, emptyStrToNull, RequestLogItem } from '@/pages/admin/custom-emojis-manager.impl.js';
import {
emptyStrToEmptyArray,
emptyStrToNull,
RequestLogItem,
roleIdsParser,
} from '@/pages/admin/custom-emojis-manager.impl.js';
import MkGrid from '@/components/grid/MkGrid.vue';
import { i18n } from '@/i18n.js';
import MkSelect from '@/components/MkSelect.vue';
@ -117,6 +122,7 @@ type GridItem = {
function setupGrid(): GridSetting {
const required = validators.required();
const regex = validators.regex(/^[a-zA-Z0-9_]+$/);
const unique = validators.unique();
function removeRows(rows: GridRow[]) {
const idxes = [...new Set(rows.map(it => it.index))];
@ -158,7 +164,10 @@ function setupGrid(): GridSetting {
},
cols: [
{ bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 'auto', validators: [required] },
{ bindTo: 'name', title: 'name', type: 'text', editable: true, width: 140, validators: [required, regex] },
{
bindTo: 'name', title: 'name', type: 'text', editable: true, width: 140,
validators: [required, regex, unique],
},
{ bindTo: 'category', title: 'category', type: 'text', editable: true, width: 140 },
{ bindTo: 'aliases', title: 'aliases', type: 'text', editable: true, width: 140 },
{ bindTo: 'license', title: 'license', type: 'text', editable: true, width: 140 },
@ -190,6 +199,13 @@ function setupGrid(): GridSetting {
return transform;
},
events: {
paste: roleIdsParser,
delete(cell) {
// undefined
gridItems.value[cell.row.index].roleIdsThatCanBeUsedThisEmojiAsReaction = [];
},
},
},
],
cells: {
@ -343,8 +359,7 @@ async function onDrop(ev: DragEvent) {
}
async function onFileSelectClicked() {
const driveFiles = await os.promiseDialog(
chooseFileFromPc(
const driveFiles = await chooseFileFromPc(
true,
{
uploadFolder: selectedFolderId.value,
@ -352,7 +367,6 @@ async function onFileSelectClicked() {
//
nameConverter: (file) => file.name.replace(/\.[a-zA-Z0-9]+$/, ''),
},
),
);
gridItems.value.push(...driveFiles.map(fromDriveFile));

View File

@ -118,18 +118,18 @@ function createRender(params: {
const body = await new Response(bodyStream).json() as entities.AdminEmojiAddRequest;
const fileId = body.fileId;
const file = storedDriveFiles.find(f => f.id === fileId);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const file = storedDriveFiles.find(f => f.id === fileId)!;
const em = emoji({
id: fakeId(),
name: body.name,
url: body.url,
publicUrl: file.url,
originalUrl: file.url,
type: file.type,
aliases: body.aliases,
category: body.category,
license: body.license,
category: body.category ?? undefined,
license: body.license ?? undefined,
localOnly: body.localOnly,
isSensitive: body.isSensitive,
});