mirror of
https://github.com/misskey-dev/misskey.git
synced 2025-01-01 04:56:19 +01:00
refine context menu setting
This commit is contained in:
parent
369d5971d4
commit
c0f941689b
11 changed files with 243 additions and 255 deletions
|
@ -48,7 +48,7 @@ import { CELL_ADDRESS_NONE, CellAddress, CellValue, createCell, GridCell, resetC
|
|||
import { equalCellAddress, getCellAddress, getCellElement } from '@/components/grid/grid-utils.js';
|
||||
import { MenuItem } from '@/types/menu.js';
|
||||
import * as os from '@/os.js';
|
||||
import { GridCurrentState, GridEvent } from '@/components/grid/grid-event.js';
|
||||
import { GridContext, GridEvent } from '@/components/grid/grid-event.js';
|
||||
import { createColumn, GridColumn } from '@/components/grid/column.js';
|
||||
import { createRow, defaultGridRowSetting, GridRow, GridRowSetting, resetRow } from '@/components/grid/row.js';
|
||||
|
||||
|
@ -59,7 +59,7 @@ type RowHolder = {
|
|||
}
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'event', event: GridEvent, current: GridCurrentState): void;
|
||||
(ev: 'event', event: GridEvent, current: GridContext): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -76,6 +76,9 @@ const rowSetting: Required<GridRowSetting> = {
|
|||
// non-reactive
|
||||
const columnSettings = props.settings.cols;
|
||||
|
||||
// non-reactive
|
||||
const cellSettings = props.settings.cells ?? {};
|
||||
|
||||
const { data } = toRefs(props);
|
||||
|
||||
// #region Event Definitions
|
||||
|
@ -700,15 +703,30 @@ function onContextMenu(ev: MouseEvent) {
|
|||
console.log(`[grid][context-menu] button: ${ev.button}, cell: ${cellAddress.row}x${cellAddress.col}`);
|
||||
}
|
||||
|
||||
const context = createContext();
|
||||
const menuItems = Array.of<MenuItem>();
|
||||
|
||||
// 外でメニュー項目を挿してもらう
|
||||
if (availableCellAddress(cellAddress)) {
|
||||
emitGridEvent({ type: 'cell-context-menu', event: ev, menuItems });
|
||||
} else if (isRowNumberCellAddress(cellAddress)) {
|
||||
emitGridEvent({ type: 'row-context-menu', event: ev, menuItems });
|
||||
} else if (isColumnHeaderCellAddress(cellAddress)) {
|
||||
emitGridEvent({ type: 'column-context-menu', event: ev, menuItems });
|
||||
switch (true) {
|
||||
case availableCellAddress(cellAddress): {
|
||||
const cell = cells.value[cellAddress.row].cells[cellAddress.col];
|
||||
if (cell.setting.contextMenuFactory) {
|
||||
menuItems.push(...cell.setting.contextMenuFactory(cell.column, cell.row, cell.value, context));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case isColumnHeaderCellAddress(cellAddress): {
|
||||
const col = columns.value[cellAddress.col];
|
||||
if (col.setting.contextMenuFactory) {
|
||||
menuItems.push(...col.setting.contextMenuFactory(col, context));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case isRowNumberCellAddress(cellAddress): {
|
||||
const row = rows.value[cellAddress.row];
|
||||
if (row.setting.contextMenuFactory) {
|
||||
menuItems.push(...row.setting.contextMenuFactory(row, context));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (menuItems.length > 0) {
|
||||
|
@ -835,7 +853,7 @@ function calcLargestCellWidth(column: GridColumn) {
|
|||
* {@link emit}を使用してイベントを発行する。
|
||||
*/
|
||||
function emitGridEvent(ev: GridEvent) {
|
||||
const currentState: GridCurrentState = {
|
||||
const currentState: GridContext = {
|
||||
selectedCell: selectedCell.value,
|
||||
rangedCells: rangedCells.value,
|
||||
rangedRows: rangedRows.value,
|
||||
|
@ -1037,6 +1055,19 @@ function unregisterMouseUp() {
|
|||
removeEventListener('mouseup', onMouseUp);
|
||||
}
|
||||
|
||||
function createContext(): GridContext {
|
||||
return {
|
||||
selectedCell: selectedCell.value,
|
||||
rangedCells: rangedCells.value,
|
||||
rangedRows: rangedRows.value,
|
||||
randedBounds: rangedBounds.value,
|
||||
availableBounds: availableBounds.value,
|
||||
state: state.value,
|
||||
rows: rows.value,
|
||||
columns: columns.value,
|
||||
};
|
||||
}
|
||||
|
||||
function refreshData() {
|
||||
if (_DEV_) {
|
||||
console.log('[grid][refresh-data][begin]');
|
||||
|
@ -1044,8 +1075,8 @@ function refreshData() {
|
|||
|
||||
const _data: DataSource[] = data.value;
|
||||
const _rows: GridRow[] = (_data.length > rowSetting.minimumDefinitionCount)
|
||||
? _data.map((_, index) => createRow(index, true))
|
||||
: Array.from({ length: rowSetting.minimumDefinitionCount }, (_, index) => createRow(index, index < _data.length));
|
||||
? _data.map((_, index) => createRow(index, true, rowSetting))
|
||||
: Array.from({ length: rowSetting.minimumDefinitionCount }, (_, index) => createRow(index, index < _data.length, rowSetting));
|
||||
const _cols: GridColumn[] = columns.value;
|
||||
|
||||
// 行・列の定義から、元データの配列より値を取得してセルを作成する。
|
||||
|
@ -1053,11 +1084,11 @@ 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]);
|
||||
const cell = createCell(col, row, _data[row.index][col.setting.bindTo], cellSettings);
|
||||
cell.violation = cellValidation(cell, cell.value);
|
||||
return cell;
|
||||
})
|
||||
: _cols.map(col => createCell(col, row, undefined));
|
||||
: _cols.map(col => createCell(col, row, undefined, cellSettings));
|
||||
|
||||
return { row, cells, origin: _data[row.index] };
|
||||
});
|
||||
|
@ -1095,11 +1126,11 @@ function patchData(newItems: DataSource[]) {
|
|||
|
||||
// 行数が増えているので新しい行を追加する
|
||||
for (let rowIdx = rows.value.length; rowIdx < newItems.length; rowIdx++) {
|
||||
const newRow = createRow(rowIdx, true);
|
||||
const newRow = createRow(rowIdx, true, rowSetting);
|
||||
newRows.push(newRow);
|
||||
newCells.push({
|
||||
row: newRow,
|
||||
cells: _cols.map(col => createCell(col, newRow, newItems[rowIdx][col.setting.bindTo], col.setting.cellSetting ?? {})),
|
||||
cells: _cols.map(col => createCell(col, newRow, newItems[rowIdx][col.setting.bindTo], cellSettings)),
|
||||
origin: newItems[rowIdx],
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ import { ValidateViolation } from '@/components/grid/cell-validators.js';
|
|||
import { Size } from '@/components/grid/grid.js';
|
||||
import { GridColumn } from '@/components/grid/column.js';
|
||||
import { GridRow } from '@/components/grid/row.js';
|
||||
import { MenuItem } from '@/types/menu.js';
|
||||
import { GridContext } from '@/components/grid/grid-event.js';
|
||||
|
||||
export type CellValue = string | boolean | number | undefined | null
|
||||
|
||||
|
@ -23,13 +25,21 @@ export type GridCell = {
|
|||
selected: boolean;
|
||||
ranged: boolean;
|
||||
contentSize: Size;
|
||||
setting: GridCellSetting;
|
||||
violation: ValidateViolation;
|
||||
}
|
||||
|
||||
export type GridCellContextMenuFactory = (col: GridColumn, row: GridRow, value: CellValue, context: GridContext) => MenuItem[];
|
||||
|
||||
export type GridCellSetting = {
|
||||
contextMenuFactory?: GridCellContextMenuFactory;
|
||||
}
|
||||
|
||||
export function createCell(
|
||||
column: GridColumn,
|
||||
row: GridRow,
|
||||
value: CellValue,
|
||||
setting: GridCellSetting,
|
||||
): GridCell {
|
||||
return {
|
||||
address: { row: row.index, col: column.index },
|
||||
|
@ -48,6 +58,7 @@ export function createCell(
|
|||
},
|
||||
violations: [],
|
||||
},
|
||||
setting,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import { CellValidator } from '@/components/grid/cell-validators.js';
|
||||
import { Size, SizeStyle } from '@/components/grid/grid.js';
|
||||
import { calcCellWidth } from '@/components/grid/grid-utils.js';
|
||||
import { CellValue } from '@/components/grid/cell.js';
|
||||
import { GridRow } from '@/components/grid/row.js';
|
||||
import { MenuItem } from '@/types/menu.js';
|
||||
import { GridContext } from '@/components/grid/grid-event.js';
|
||||
|
||||
export type ColumnType = 'text' | 'number' | 'date' | 'boolean' | 'image' | 'hidden';
|
||||
|
||||
export type CellValueConverter = (row: GridRow, col: GridColumn, value: CellValue) => CellValue;
|
||||
export type GridColumnContextMenuFactory = (col: GridColumn, context: GridContext) => MenuItem[];
|
||||
|
||||
export type GridColumnSetting = {
|
||||
bindTo: string;
|
||||
title?: string;
|
||||
|
@ -12,6 +19,8 @@ export type GridColumnSetting = {
|
|||
width: SizeStyle;
|
||||
editable?: boolean;
|
||||
validators?: CellValidator[];
|
||||
valueConverter?: CellValueConverter;
|
||||
contextMenuFactory?: GridColumnContextMenuFactory;
|
||||
};
|
||||
|
||||
export type GridColumn = {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
|
||||
import { GridState } from '@/components/grid/grid.js';
|
||||
import { ValidateViolation } from '@/components/grid/cell-validators.js';
|
||||
import { MenuItem } from '@/types/menu.js';
|
||||
import { GridColumn } from '@/components/grid/column.js';
|
||||
import { GridRow } from '@/components/grid/row.js';
|
||||
|
||||
export type GridCurrentState = {
|
||||
export type GridContext = {
|
||||
selectedCell?: GridCell;
|
||||
rangedCells: GridCell[];
|
||||
rangedRows: GridRow[];
|
||||
|
@ -26,10 +25,7 @@ export type GridEvent =
|
|||
GridCellValueChangeEvent |
|
||||
GridKeyDownEvent |
|
||||
GridMouseDownEvent |
|
||||
GridCellValidationEvent |
|
||||
GridCellContextMenuEvent |
|
||||
GridRowContextMenuEvent |
|
||||
GridColumnContextMenuEvent
|
||||
GridCellValidationEvent
|
||||
;
|
||||
|
||||
export type GridCellValueChangeEvent = {
|
||||
|
@ -57,21 +53,3 @@ export type GridMouseDownEvent = {
|
|||
event: MouseEvent;
|
||||
clickedCellAddress: CellAddress;
|
||||
};
|
||||
|
||||
export type GridCellContextMenuEvent = {
|
||||
type: 'cell-context-menu';
|
||||
event: MouseEvent;
|
||||
menuItems: MenuItem[];
|
||||
};
|
||||
|
||||
export type GridRowContextMenuEvent = {
|
||||
type: 'row-context-menu';
|
||||
event: MouseEvent;
|
||||
menuItems: MenuItem[];
|
||||
};
|
||||
|
||||
export type GridColumnContextMenuEvent = {
|
||||
type: 'column-context-menu';
|
||||
event: MouseEvent;
|
||||
menuItems: MenuItem[];
|
||||
};
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { EventEmitter } from 'eventemitter3';
|
||||
import { CellValue } from '@/components/grid/cell.js';
|
||||
import { CellValue, GridCellSetting } from '@/components/grid/cell.js';
|
||||
import { GridColumnSetting } from '@/components/grid/column.js';
|
||||
import { GridRowSetting } from '@/components/grid/row.js';
|
||||
|
||||
export type GridSetting = {
|
||||
row?: GridRowSetting;
|
||||
cols: GridColumnSetting[];
|
||||
cells?: GridCellSetting;
|
||||
};
|
||||
|
||||
export type DataSource = Record<string, CellValue>;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Ref } from 'vue';
|
||||
import { GridCurrentState, GridKeyDownEvent } from '@/components/grid/grid-event.js';
|
||||
import { GridContext, GridKeyDownEvent } from '@/components/grid/grid-event.js';
|
||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||
import { GridColumnSetting } from '@/components/grid/column.js';
|
||||
import { CellValue } from '@/components/grid/cell.js';
|
||||
import { DataSource } from '@/components/grid/grid.js';
|
||||
|
||||
class OptInGridUtils {
|
||||
async defaultKeyDownHandler(gridItems: Ref<DataSource[]>, event: GridKeyDownEvent, currentState: GridCurrentState) {
|
||||
async defaultKeyDownHandler(gridItems: Ref<DataSource[]>, event: GridKeyDownEvent, context: GridContext) {
|
||||
const { ctrlKey, shiftKey, code } = event.event;
|
||||
|
||||
switch (true) {
|
||||
|
@ -16,11 +16,11 @@ class OptInGridUtils {
|
|||
case ctrlKey: {
|
||||
switch (code) {
|
||||
case 'KeyC': {
|
||||
this.copyToClipboard(gridItems, currentState);
|
||||
this.copyToClipboard(gridItems, context);
|
||||
break;
|
||||
}
|
||||
case 'KeyV': {
|
||||
await this.pasteFromClipboard(gridItems, currentState);
|
||||
await this.pasteFromClipboard(gridItems, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ class OptInGridUtils {
|
|||
default: {
|
||||
switch (code) {
|
||||
case 'Delete': {
|
||||
this.deleteSelectionRange(gridItems, currentState);
|
||||
this.deleteSelectionRange(gridItems, context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -41,14 +41,14 @@ class OptInGridUtils {
|
|||
}
|
||||
}
|
||||
|
||||
copyToClipboard(gridItems: Ref<DataSource[]>, currentState: GridCurrentState) {
|
||||
copyToClipboard(gridItems: Ref<DataSource[]>, context: GridContext) {
|
||||
const lines = Array.of<string>();
|
||||
const bounds = currentState.randedBounds;
|
||||
const bounds = context.randedBounds;
|
||||
|
||||
for (let row = bounds.leftTop.row; row <= bounds.rightBottom.row; row++) {
|
||||
const items = Array.of<string>();
|
||||
for (let col = bounds.leftTop.col; col <= bounds.rightBottom.col; col++) {
|
||||
const bindTo = currentState.columns[col].setting.bindTo;
|
||||
const bindTo = context.columns[col].setting.bindTo;
|
||||
const cell = gridItems.value[row][bindTo];
|
||||
items.push(cell?.toString() ?? '');
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ class OptInGridUtils {
|
|||
|
||||
async pasteFromClipboard(
|
||||
gridItems: Ref<DataSource[]>,
|
||||
currentState: GridCurrentState,
|
||||
context: GridContext,
|
||||
) {
|
||||
function parseValue(value: string, type: GridColumnSetting['type']): CellValue {
|
||||
switch (type) {
|
||||
|
@ -86,14 +86,14 @@ class OptInGridUtils {
|
|||
console.log(`Paste from clipboard: ${clipBoardText}`);
|
||||
}
|
||||
|
||||
const bounds = currentState.randedBounds;
|
||||
const bounds = context.randedBounds;
|
||||
const lines = clipBoardText.replace(/\r/g, '')
|
||||
.split('\n')
|
||||
.map(it => it.split('\t'));
|
||||
|
||||
if (lines.length === 1 && lines[0].length === 1) {
|
||||
// 単独文字列の場合は選択範囲全体に同じテキストを貼り付ける
|
||||
const ranges = currentState.rangedCells;
|
||||
const ranges = context.rangedCells;
|
||||
for (const cell of ranges) {
|
||||
gridItems.value[cell.row.index][cell.column.setting.bindTo] = parseValue(lines[0][0], cell.column.setting.type);
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ class OptInGridUtils {
|
|||
// 表形式文字列の場合は表形式にパースし、選択範囲に合うように貼り付ける
|
||||
const offsetRow = bounds.leftTop.row;
|
||||
const offsetCol = bounds.leftTop.col;
|
||||
const columns = currentState.columns;
|
||||
const columns = context.columns;
|
||||
for (let row = bounds.leftTop.row; row <= bounds.rightBottom.row; row++) {
|
||||
const rowIdx = row - offsetRow;
|
||||
if (lines.length <= rowIdx) {
|
||||
|
@ -123,12 +123,12 @@ class OptInGridUtils {
|
|||
}
|
||||
}
|
||||
|
||||
deleteSelectionRange(gridItems: Ref<DataSource[]>, currentState: GridCurrentState) {
|
||||
if (currentState.rangedRows.length > 0) {
|
||||
const deletedIndexes = currentState.rangedRows.map(it => it.index);
|
||||
deleteSelectionRange(gridItems: Ref<DataSource[]>, context: GridContext) {
|
||||
if (context.rangedRows.length > 0) {
|
||||
const deletedIndexes = context.rangedRows.map(it => it.index);
|
||||
gridItems.value = gridItems.value.filter((_, index) => !deletedIndexes.includes(index));
|
||||
} else {
|
||||
const ranges = currentState.rangedCells;
|
||||
const ranges = context.rangedCells;
|
||||
for (const cell of ranges) {
|
||||
if (cell.column.setting.editable) {
|
||||
gridItems.value[cell.row.index][cell.column.setting.bindTo] = undefined;
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import { AdditionalStyle } from '@/components/grid/grid.js';
|
||||
import { GridCell } from '@/components/grid/cell.js';
|
||||
import { GridColumn } from '@/components/grid/column.js';
|
||||
import { MenuItem } from '@/types/menu.js';
|
||||
import { GridContext } from '@/components/grid/grid-event.js';
|
||||
|
||||
export const defaultGridRowSetting: Required<GridRowSetting> = {
|
||||
showNumber: true,
|
||||
selectable: true,
|
||||
minimumDefinitionCount: 100,
|
||||
styleRules: [],
|
||||
contextMenuFactory: () => [],
|
||||
};
|
||||
|
||||
export type GridRowStyleRuleConditionParams = {
|
||||
|
@ -20,25 +23,30 @@ export type GridRowStyleRule = {
|
|||
applyStyle: AdditionalStyle;
|
||||
}
|
||||
|
||||
export type GridRowContextMenuFactory = (row: GridRow, context: GridContext) => MenuItem[];
|
||||
|
||||
export type GridRowSetting = {
|
||||
showNumber?: boolean;
|
||||
selectable?: boolean;
|
||||
minimumDefinitionCount?: number;
|
||||
styleRules?: GridRowStyleRule[];
|
||||
contextMenuFactory?: GridRowContextMenuFactory;
|
||||
}
|
||||
|
||||
export type GridRow = {
|
||||
index: number;
|
||||
ranged: boolean;
|
||||
using: boolean;
|
||||
setting: GridRowSetting;
|
||||
additionalStyles: AdditionalStyle[];
|
||||
}
|
||||
|
||||
export function createRow(index: number, using: boolean): GridRow {
|
||||
export function createRow(index: number, using: boolean, setting: GridRowSetting): GridRow {
|
||||
return {
|
||||
index,
|
||||
ranged: false,
|
||||
using: using,
|
||||
setting,
|
||||
additionalStyles: [],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -137,13 +137,11 @@ import MkInput from '@/components/MkInput.vue';
|
|||
import MkButton from '@/components/MkButton.vue';
|
||||
import { validators } from '@/components/grid/cell-validators.js';
|
||||
import {
|
||||
GridCellContextMenuEvent,
|
||||
GridCellValidationEvent,
|
||||
GridCellValueChangeEvent,
|
||||
GridCurrentState,
|
||||
GridContext,
|
||||
GridEvent,
|
||||
GridKeyDownEvent,
|
||||
GridRowContextMenuEvent,
|
||||
} from '@/components/grid/grid-event.js';
|
||||
import { optInGridUtils } from '@/components/grid/optin-utils.js';
|
||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||
|
@ -209,6 +207,26 @@ function setupGrid(): GridSetting {
|
|||
applyStyle: { className: 'violationRow' },
|
||||
},
|
||||
],
|
||||
contextMenuFactory: (row, context) => {
|
||||
return [
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行をコピー',
|
||||
icon: 'ti ti-copy',
|
||||
action: () => optInGridUtils.copyToClipboard(gridItems, context),
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行を削除対象とする',
|
||||
icon: 'ti ti-trash',
|
||||
action: () => {
|
||||
for (const row of context.rangedRows) {
|
||||
gridItems.value[row.index].checked = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
cols: [
|
||||
{ bindTo: 'checked', icon: 'ti-trash', type: 'boolean', editable: true, width: 34 },
|
||||
|
@ -224,6 +242,34 @@ function setupGrid(): GridSetting {
|
|||
{ bindTo: 'publicUrl', type: 'text', editable: false, width: 180 },
|
||||
{ bindTo: 'originalUrl', type: 'text', editable: false, width: 180 },
|
||||
],
|
||||
cells: {
|
||||
contextMenuFactory: (col, row, value, context) => {
|
||||
return [
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択範囲をコピー',
|
||||
icon: 'ti ti-copy',
|
||||
action: () => optInGridUtils.copyToClipboard(gridItems, context),
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択範囲を削除',
|
||||
icon: 'ti ti-trash',
|
||||
action: () => optInGridUtils.deleteSelectionRange(gridItems, context),
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行を削除対象とする',
|
||||
icon: 'ti ti-trash',
|
||||
action: () => {
|
||||
for (const rowIdx of [...new Set(context.rangedCells.map(it => it.row.index)).values()]) {
|
||||
gridItems.value[rowIdx].checked = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -407,17 +453,11 @@ async function onPageChanged(pageNumber: number) {
|
|||
await refreshCustomEmojis();
|
||||
}
|
||||
|
||||
function onGridEvent(event: GridEvent, currentState: GridCurrentState) {
|
||||
function onGridEvent(event: GridEvent, currentState: GridContext) {
|
||||
switch (event.type) {
|
||||
case 'cell-validation':
|
||||
onGridCellValidation(event);
|
||||
break;
|
||||
case 'row-context-menu':
|
||||
onGridRowContextMenu(event, currentState);
|
||||
break;
|
||||
case 'cell-context-menu':
|
||||
onGridCellContextMenu(event, currentState);
|
||||
break;
|
||||
case 'cell-value-change':
|
||||
onGridCellValueChange(event);
|
||||
break;
|
||||
|
@ -431,54 +471,6 @@ function onGridCellValidation(event: GridCellValidationEvent) {
|
|||
updateButtonDisabled.value = event.all.filter(it => !it.valid).length > 0;
|
||||
}
|
||||
|
||||
function onGridRowContextMenu(event: GridRowContextMenuEvent, currentState: GridCurrentState) {
|
||||
event.menuItems.push(
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行をコピー',
|
||||
icon: 'ti ti-copy',
|
||||
action: () => optInGridUtils.copyToClipboard(gridItems, currentState),
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行を削除対象とする',
|
||||
icon: 'ti ti-trash',
|
||||
action: () => {
|
||||
for (const row of currentState.rangedRows) {
|
||||
gridItems.value[row.index].checked = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function onGridCellContextMenu(event: GridCellContextMenuEvent, currentState: GridCurrentState) {
|
||||
event.menuItems.push(
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択範囲をコピー',
|
||||
icon: 'ti ti-copy',
|
||||
action: () => optInGridUtils.copyToClipboard(gridItems, currentState),
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択範囲を削除',
|
||||
icon: 'ti ti-trash',
|
||||
action: () => optInGridUtils.deleteSelectionRange(gridItems, currentState),
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行を削除対象とする',
|
||||
icon: 'ti ti-trash',
|
||||
action: () => {
|
||||
for (const rowIdx of [...new Set(currentState.rangedCells.map(it => it.row.index)).values()]) {
|
||||
gridItems.value[rowIdx].checked = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function onGridCellValueChange(event: GridCellValueChangeEvent) {
|
||||
const { row, column, newValue } = event;
|
||||
if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) {
|
||||
|
@ -492,7 +484,7 @@ function onGridCellValueChange(event: GridCellValueChangeEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
async function onGridKeyDown(event: GridKeyDownEvent, currentState: GridCurrentState) {
|
||||
async function onGridKeyDown(event: GridKeyDownEvent, currentState: GridContext) {
|
||||
const { ctrlKey, shiftKey, code } = event.event;
|
||||
|
||||
switch (true) {
|
||||
|
|
|
@ -26,14 +26,11 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
import { computed, ref, toRefs } from 'vue';
|
||||
import { GridColumnSetting } from '@/components/grid/column.js';
|
||||
import { RequestLogItem } from '@/pages/admin/custom-emojis-grid.impl.js';
|
||||
import {
|
||||
GridCellContextMenuEvent,
|
||||
GridCurrentState,
|
||||
GridContext,
|
||||
GridEvent,
|
||||
GridKeyDownEvent,
|
||||
GridRowContextMenuEvent,
|
||||
} from '@/components/grid/grid-event.js';
|
||||
import { optInGridUtils } from '@/components/grid/optin-utils.js';
|
||||
import MkGrid from '@/components/grid/MkGrid.vue';
|
||||
|
@ -45,6 +42,16 @@ function setupGrid(): GridSetting {
|
|||
row: {
|
||||
showNumber: false,
|
||||
selectable: false,
|
||||
contextMenuFactory: (row, context) => {
|
||||
return [
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行をコピー',
|
||||
icon: 'ti ti-copy',
|
||||
action: () => optInGridUtils.copyToClipboard(logs, context),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
cols: [
|
||||
{ bindTo: 'failed', title: 'failed', type: 'boolean', editable: false, width: 50 },
|
||||
|
@ -52,6 +59,18 @@ function setupGrid(): GridSetting {
|
|||
{ bindTo: 'name', title: 'name', type: 'text', editable: false, width: 140 },
|
||||
{ bindTo: 'error', title: 'log', type: 'text', editable: false, width: 'auto' },
|
||||
],
|
||||
cells: {
|
||||
contextMenuFactory: (col, row, value, context) => {
|
||||
return [
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択範囲をコピー',
|
||||
icon: 'ti ti-copy',
|
||||
action: () => optInGridUtils.copyToClipboard(logs, context),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -67,55 +86,15 @@ const filteredLogs = computed(() => {
|
|||
return logs.value.filter((log) => forceShowing || log.failed);
|
||||
});
|
||||
|
||||
function onGridEvent(event: GridEvent, currentState: GridCurrentState) {
|
||||
function onGridEvent(event: GridEvent, currentState: GridContext) {
|
||||
switch (event.type) {
|
||||
case 'row-context-menu':
|
||||
onGridRowContextMenu(event, currentState);
|
||||
break;
|
||||
case 'cell-context-menu':
|
||||
onGridCellContextMenu(event, currentState);
|
||||
break;
|
||||
case 'keydown':
|
||||
onGridKeyDown(event, currentState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function onGridRowContextMenu(event: GridRowContextMenuEvent, currentState: GridCurrentState) {
|
||||
event.menuItems.push(
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行をコピー',
|
||||
icon: 'ti ti-copy',
|
||||
action: () => optInGridUtils.copyToClipboard(logs, currentState),
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行を削除',
|
||||
icon: 'ti ti-trash',
|
||||
action: () => optInGridUtils.deleteSelectionRange(logs, currentState),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function onGridCellContextMenu(event: GridCellContextMenuEvent, currentState: GridCurrentState) {
|
||||
event.menuItems.push(
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択範囲をコピー',
|
||||
icon: 'ti ti-copy',
|
||||
action: () => optInGridUtils.copyToClipboard(logs, currentState),
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行を削除',
|
||||
icon: 'ti ti-trash',
|
||||
action: () => optInGridUtils.deleteSelectionRange(logs, currentState),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function onGridKeyDown(event: GridKeyDownEvent, currentState: GridCurrentState) {
|
||||
function onGridKeyDown(event: GridKeyDownEvent, currentState: GridContext) {
|
||||
optInGridUtils.defaultKeyDownHandler(logs, event, currentState);
|
||||
}
|
||||
|
||||
|
|
|
@ -88,13 +88,11 @@ import { validators } from '@/components/grid/cell-validators.js';
|
|||
import { chooseFileFromDrive, chooseFileFromPc } from '@/scripts/select-file.js';
|
||||
import { uploadFile } from '@/scripts/upload.js';
|
||||
import {
|
||||
GridCellContextMenuEvent,
|
||||
GridCellValidationEvent,
|
||||
GridCellValueChangeEvent,
|
||||
GridCurrentState,
|
||||
GridContext,
|
||||
GridEvent,
|
||||
GridKeyDownEvent,
|
||||
GridRowContextMenuEvent,
|
||||
} from '@/components/grid/grid-event.js';
|
||||
import { DroppedFile, extractDroppedItems, flattenDroppedFiles } from '@/scripts/file-drop.js';
|
||||
import { optInGridUtils } from '@/components/grid/optin-utils.js';
|
||||
|
@ -136,6 +134,22 @@ function setupGrid(): GridSetting {
|
|||
applyStyle: { className: 'violationRow' },
|
||||
},
|
||||
],
|
||||
contextMenuFactory: (row, context) => {
|
||||
return [
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行をコピー',
|
||||
icon: 'ti ti-copy',
|
||||
action: () => optInGridUtils.copyToClipboard(gridItems, context),
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行を削除',
|
||||
icon: 'ti ti-trash',
|
||||
action: () => optInGridUtils.deleteSelectionRange(gridItems, context),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
cols: [
|
||||
{ bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 'auto', validators: [required] },
|
||||
|
@ -147,6 +161,24 @@ function setupGrid(): GridSetting {
|
|||
{ bindTo: 'localOnly', title: 'localOnly', type: 'boolean', editable: true, width: 90 },
|
||||
{ bindTo: 'roleIdsThatCanBeUsedThisEmojiAsReaction', title: 'role', type: 'text', editable: true, width: 100 },
|
||||
],
|
||||
cells: {
|
||||
contextMenuFactory: (col, row, value, context) => {
|
||||
return [
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択範囲をコピー',
|
||||
icon: 'ti ti-copy',
|
||||
action: () => optInGridUtils.copyToClipboard(gridItems, context),
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行を削除',
|
||||
icon: 'ti ti-trash',
|
||||
action: () => optInGridUtils.deleteSelectionRange(gridItems, context),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -300,17 +332,11 @@ async function onDriveSelectClicked() {
|
|||
gridItems.value.push(...driveFiles.map(fromDriveFile));
|
||||
}
|
||||
|
||||
function onGridEvent(event: GridEvent, currentState: GridCurrentState) {
|
||||
function onGridEvent(event: GridEvent, currentState: GridContext) {
|
||||
switch (event.type) {
|
||||
case 'cell-validation':
|
||||
onGridCellValidation(event);
|
||||
break;
|
||||
case 'row-context-menu':
|
||||
onGridRowContextMenu(event, currentState);
|
||||
break;
|
||||
case 'cell-context-menu':
|
||||
onGridCellContextMenu(event, currentState);
|
||||
break;
|
||||
case 'cell-value-change':
|
||||
onGridCellValueChange(event);
|
||||
break;
|
||||
|
@ -324,40 +350,6 @@ function onGridCellValidation(event: GridCellValidationEvent) {
|
|||
registerButtonDisabled.value = event.all.filter(it => !it.valid).length > 0;
|
||||
}
|
||||
|
||||
function onGridRowContextMenu(event: GridRowContextMenuEvent, currentState: GridCurrentState) {
|
||||
event.menuItems.push(
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行をコピー',
|
||||
icon: 'ti ti-copy',
|
||||
action: () => optInGridUtils.copyToClipboard(gridItems, currentState),
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行を削除',
|
||||
icon: 'ti ti-trash',
|
||||
action: () => optInGridUtils.deleteSelectionRange(gridItems, currentState),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function onGridCellContextMenu(event: GridCellContextMenuEvent, currentState: GridCurrentState) {
|
||||
event.menuItems.push(
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択範囲をコピー',
|
||||
icon: 'ti ti-copy',
|
||||
action: () => optInGridUtils.copyToClipboard(gridItems, currentState),
|
||||
},
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行を削除',
|
||||
icon: 'ti ti-trash',
|
||||
action: () => optInGridUtils.deleteSelectionRange(gridItems, currentState),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function onGridCellValueChange(event: GridCellValueChangeEvent) {
|
||||
const { row, column, newValue } = event;
|
||||
if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) {
|
||||
|
@ -365,7 +357,7 @@ function onGridCellValueChange(event: GridCellValueChangeEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
function onGridKeyDown(event: GridKeyDownEvent, currentState: GridCurrentState) {
|
||||
function onGridKeyDown(event: GridKeyDownEvent, currentState: GridContext) {
|
||||
optInGridUtils.defaultKeyDownHandler(gridItems, event, currentState);
|
||||
}
|
||||
|
||||
|
|
|
@ -51,16 +51,8 @@ import { i18n } from '@/i18n.js';
|
|||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkGrid from '@/components/grid/MkGrid.vue';
|
||||
import { GridColumnSetting } from '@/components/grid/column.js';
|
||||
import { RequestLogItem } from '@/pages/admin/custom-emojis-grid.impl.js';
|
||||
import {
|
||||
GridCellContextMenuEvent,
|
||||
GridCellValueChangeEvent,
|
||||
GridCurrentState,
|
||||
GridEvent,
|
||||
GridKeyDownEvent,
|
||||
GridRowContextMenuEvent,
|
||||
} from '@/components/grid/grid-event.js';
|
||||
import { GridCellValueChangeEvent, GridContext, GridEvent, GridKeyDownEvent } from '@/components/grid/grid-event.js';
|
||||
import { optInGridUtils } from '@/components/grid/optin-utils.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import XRegisterLogs from '@/pages/admin/custom-emojis-grid.local.logs.vue';
|
||||
|
@ -77,12 +69,42 @@ type GridItem = {
|
|||
|
||||
function setupGrid(): GridSetting {
|
||||
return {
|
||||
row: {
|
||||
contextMenuFactory: (row, context) => {
|
||||
return [
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行をインポート',
|
||||
icon: 'ti ti-download',
|
||||
action: async () => {
|
||||
const targets = context.rangedRows.map(it => gridItems.value[it.index]);
|
||||
await importEmojis(targets);
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
cols: [
|
||||
{ bindTo: 'checked', icon: 'ti-download', type: 'boolean', editable: true, width: 34 },
|
||||
{ bindTo: 'url', icon: 'ti-icons', type: 'image', editable: false, width: 'auto' },
|
||||
{ bindTo: 'name', title: 'name', type: 'text', editable: false, width: 'auto' },
|
||||
{ bindTo: 'host', title: 'host', type: 'text', editable: false, width: 'auto' },
|
||||
],
|
||||
cells: {
|
||||
contextMenuFactory: (col, row, value, context) => {
|
||||
return [
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択範囲の行をインポート',
|
||||
icon: 'ti ti-download',
|
||||
action: async () => {
|
||||
const targets = context.rangedCells.map(it => gridItems.value[it.row.index]);
|
||||
await importEmojis(targets);
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -113,14 +135,8 @@ async function onImportClicked() {
|
|||
await importEmojis(targets);
|
||||
}
|
||||
|
||||
function onGridEvent(event: GridEvent, currentState: GridCurrentState) {
|
||||
function onGridEvent(event: GridEvent, currentState: GridContext) {
|
||||
switch (event.type) {
|
||||
case 'row-context-menu':
|
||||
onGridRowContextMenu(event, currentState);
|
||||
break;
|
||||
case 'cell-context-menu':
|
||||
onGridCellContextMenu(event, currentState);
|
||||
break;
|
||||
case 'cell-value-change':
|
||||
onGridCellValueChange(event);
|
||||
break;
|
||||
|
@ -130,35 +146,6 @@ function onGridEvent(event: GridEvent, currentState: GridCurrentState) {
|
|||
}
|
||||
}
|
||||
|
||||
function onGridRowContextMenu(event: GridRowContextMenuEvent, currentState: GridCurrentState) {
|
||||
event.menuItems.push(
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択行をインポート',
|
||||
icon: 'ti ti-download',
|
||||
action: async () => {
|
||||
const targets = currentState.rangedRows.map(it => gridItems.value[it.index]);
|
||||
console.log(targets);
|
||||
await importEmojis(targets);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function onGridCellContextMenu(event: GridCellContextMenuEvent, currentState: GridCurrentState) {
|
||||
event.menuItems.push(
|
||||
{
|
||||
type: 'button',
|
||||
text: '選択された絵文字をインポート',
|
||||
icon: 'ti ti-download',
|
||||
action: async () => {
|
||||
const targets = [...new Set(currentState.rangedCells.map(it => it.row)).values()].map(it => gridItems.value[it.index]);
|
||||
await importEmojis(targets);
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function onGridCellValueChange(event: GridCellValueChangeEvent) {
|
||||
const { row, column, newValue } = event;
|
||||
if (gridItems.value.length > row.index && column.setting.bindTo in gridItems.value[row.index]) {
|
||||
|
@ -166,7 +153,7 @@ function onGridCellValueChange(event: GridCellValueChangeEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
function onGridKeyDown(event: GridKeyDownEvent, currentState: GridCurrentState) {
|
||||
function onGridKeyDown(event: GridKeyDownEvent, currentState: GridContext) {
|
||||
optInGridUtils.defaultKeyDownHandler(gridItems, event, currentState);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue