This commit is contained in:
samunohito 2024-01-26 22:16:17 +09:00
parent a2fcc81290
commit 8d1a5734cd
10 changed files with 357 additions and 156 deletions

View File

@ -98,7 +98,6 @@
"@storybook/vue3": "7.6.10",
"@storybook/vue3-vite": "7.6.10",
"@testing-library/vue": "8.0.1",
"@types/blueimp-load-image": "^5.16.6",
"@types/escape-regexp": "0.0.3",
"@types/estree": "1.0.5",
"@types/matter-js": "0.19.6",

View File

@ -54,13 +54,15 @@ import {
equalCellAddress,
getCellAddress,
GridCell,
GridEventEmitter,
GridEventEmitter, Size,
} from '@/components/grid/types.js';
const emit = defineEmits<{
(ev: 'edit:begin', sender: GridCell): void;
(ev: 'edit:end', sender: GridCell): void;
(ev: 'selection:move', sender: GridCell, next: CellAddress): void;
(ev: 'operation:beginEdit', sender: GridCell): void;
(ev: 'operation:endEdit', sender: GridCell): void;
(ev: 'operation:selectionMove', sender: GridCell, next: CellAddress): void;
(ev: 'change:value', sender: GridCell, newValue: CellValue): void;
(ev: 'change:contentSize', sender: GridCell, newSize: Size): void;
}>();
const props = defineProps<{
cell: GridCell,
@ -87,10 +89,9 @@ const needsContentCentering = computed(() => {
}
});
watch(cellWidth, updateContentSize);
watch(() => [cell, cell.value.value], () => {
//
nextTick(updateContentSize);
nextTick(emitContentSizeChanged);
});
watch(() => cell.value.selected, () => {
if (cell.value.selected) {
@ -116,44 +117,13 @@ function onOutsideMouseDown(ev: MouseEvent) {
function onCellKeyDown(ev: KeyboardEvent) {
if (!editing.value) {
ev.preventDefault();
switch (ev.code) {
case 'Enter':
case 'F2': {
beginEditing();
break;
}
case 'ArrowRight': {
const next = {
col: cell.value.address.col + 1,
row: cell.value.address.row,
};
emit('selection:move', cell.value, next);
break;
}
case 'ArrowLeft': {
const next = {
col: cell.value.address.col - 1,
row: cell.value.address.row,
};
emit('selection:move', cell.value, next);
break;
}
case 'ArrowUp': {
const next = {
col: cell.value.address.col,
row: cell.value.address.row - 1,
};
emit('selection:move', cell.value, next);
break;
}
case 'ArrowDown': {
const next = {
col: cell.value.address.col,
row: cell.value.address.row + 1,
};
emit('selection:move', cell.value, next);
break;
}
}
} else {
switch (ev.code) {
@ -193,9 +163,10 @@ function beginEditing() {
editingValue.value = cell.value.value;
editing.value = true;
registerOutsideMouseDown();
emit('edit:begin', cell.value);
emit('operation:beginEdit', cell.value);
nextTick(() => {
// input
if (inputAreaEl.value) {
(inputAreaEl.value.querySelector('*') as HTMLElement).focus();
}
@ -204,7 +175,7 @@ function beginEditing() {
}
case 'boolean': {
// UI
cell.value.value = !cell.value.value;
emitValueChange(!cell.value.value);
break;
}
}
@ -215,22 +186,28 @@ function endEditing(applyValue: boolean) {
return;
}
emit('edit:end', cell.value);
emit('operation:endEdit', cell.value);
unregisterOutsideMouseDown();
if (applyValue) {
cell.value.value = editingValue.value;
emitValueChange(editingValue.value);
}
editingValue.value = undefined;
editing.value = false;
rootEl.value?.focus();
}
function updateContentSize() {
cell.value.contentSize = {
function emitValueChange(newValue: CellValue) {
emit('change:value', cell.value, newValue);
}
function emitContentSizeChanged() {
emit('change:contentSize', cell.value, {
width: contentAreaEl.value?.clientWidth ?? 0,
height: contentAreaEl.value?.clientHeight ?? 0,
};
});
}
</script>

View File

@ -4,31 +4,35 @@
:content="(row.index + 1).toString()"
:selectable="true"
:row="row"
@selection:row="(sender) => emit('selection:row', sender)"
@operation:selectionRow="(sender) => emit('operation:selectionRow', sender)"
/>
<MkDataCell
v-for="cell in cells"
:key="cell.address.col"
:cell="cell"
:bus="bus"
@edit:begin="(sender) => emit('edit:begin', sender)"
@edit:end="(sender) => emit('edit:end', sender)"
@selection:move="(sender, next) => emit('selection:move', sender, next)"
@operation:beginEdit="(sender) => emit('operation:beginEdit', sender)"
@operation:endEdit="(sender) => emit('operation:endEdit', sender)"
@operation:selectionMove="(sender, next) => emit('operation:selectionMove', sender, next)"
@change:value="(sender, newValue) => emit('change:value', sender, newValue)"
@change:contentSize="(sender, newSize) => emit('change:contentSize', sender, newSize)"
/>
</tr>
</template>
<script setup lang="ts">
import { computed, toRefs } from 'vue';
import { CellAddress, GridCell, GridEventEmitter, GridRow } from '@/components/grid/types.js';
import { CellAddress, CellValue, GridCell, GridEventEmitter, GridRow, Size } from '@/components/grid/types.js';
import MkDataCell from '@/components/grid/MkDataCell.vue';
import MkNumberCell from '@/components/grid/MkNumberCell.vue';
const emit = defineEmits<{
(ev: 'edit:begin', sender: GridCell): void;
(ev: 'edit:end', sender: GridCell): void;
(ev: 'selection:move', sender: GridCell, next: CellAddress): void;
(ev: 'selection:row', sender: GridRow): void;
(ev: 'operation:beginEdit', sender: GridCell): void;
(ev: 'operation:endEdit', sender: GridCell): void;
(ev: 'operation:selectionRow', sender: GridRow): void;
(ev: 'operation:selectionMove', sender: GridCell, next: CellAddress): void;
(ev: 'change:value', sender: GridCell, newValue: CellValue): void;
(ev: 'change:contentSize', sender: GridCell, newSize: Size): void;
}>();
const props = defineProps<{
row: GridRow,
@ -37,7 +41,6 @@ const props = defineProps<{
}>();
const { cells } = toRefs(props);
const last = computed(() => cells.value[cells.value.length - 1]);
</script>

View File

@ -2,18 +2,18 @@
<table
:class="$style.grid"
@mousedown="onMouseDown"
@mouseup="onMouseUp"
@mousemove="onMouseMove"
@keydown="onKeyDown"
>
<thead>
<MkHeaderRow
:columns="columns"
:bus="bus"
@width:beginChange="onHeaderCellWidthBeginChange"
@width:endChange="onHeaderCellWidthEndChange"
@width:changing="onHeaderCellWidthChanging"
@width:largest="onHeaderCellWidthLargest"
@selection:column="onSelectionColumn"
@operation:beginWidthChange="onHeaderCellWidthBeginChange"
@operation:endWidthChange="onHeaderCellWidthEndChange"
@operation:widthLargest="onHeaderCellWidthLargest"
@operation:selectionColumn="onSelectionColumn"
@change:width="onHeaderCellChangeWidth"
@change:contentSize="onHeaderCellChangeContentSize"
/>
</thead>
<tbody>
@ -23,10 +23,12 @@
:row="row"
:cells="cells[row.index]"
:bus="bus"
@edit:begin="onCellEditBegin"
@edit:end="onCellEditEnd"
@selection:move="onSelectionMove"
@selection:row="onSelectionRow"
@operation:beginEdit="onCellEditBegin"
@operation:endEdit="onCellEditEnd"
@operation:selectionMove="onSelectionMove"
@operation:selectionRow="onSelectionRow"
@change:value="onChangeCellValue"
@change:contentSize="onChangeCellContentSize"
/>
</tbody>
</table>
@ -38,6 +40,7 @@ import {
calcCellWidth,
CELL_ADDRESS_NONE,
CellAddress,
CellValue,
ColumnSetting,
DataSource,
equalCellAddress,
@ -47,7 +50,7 @@ import {
GridEventEmitter,
GridRow,
GridState,
isCellElement,
Size,
} from '@/components/grid/types.js';
import MkDataRow from '@/components/grid/MkDataRow.vue';
import MkHeaderRow from '@/components/grid/MkHeaderRow.vue';
@ -57,21 +60,24 @@ const props = defineProps<{
data: DataSource[]
}>();
const bus = new GridEventEmitter();
const { columnSettings, data } = toRefs(props);
const columns = ref<GridColumn[]>([]);
const rows = ref<GridRow[]>([]);
const cells = ref<GridCell[][]>([]);
const previousCellAddress = ref<CellAddress>(CELL_ADDRESS_NONE);
const editingCellAddress = ref<CellAddress>(CELL_ADDRESS_NONE);
const firstSelectionColumnIdx = ref<number>(CELL_ADDRESS_NONE.col);
const firstSelectionRowIdx = ref<number>(CELL_ADDRESS_NONE.row);
const state = ref<GridState>('normal');
const selectedCell = computed(() => {
const selected = cells.value.flat().filter(it => it.selected);
return selected.length > 0 ? selected[0] : undefined;
});
const rangedCells = computed(() => cells.value.flat().filter(it => it.ranged));
const previousCellAddress = ref<CellAddress>(CELL_ADDRESS_NONE);
const editingCellAddress = ref<CellAddress>(CELL_ADDRESS_NONE);
const state = ref<GridState>('normal');
const bus = new GridEventEmitter();
watch(columnSettings, refreshColumnsSetting);
watch(data, refreshData);
@ -81,6 +87,43 @@ if (_DEV_) {
});
}
function onKeyDown(ev: KeyboardEvent) {
switch (state.value) {
case 'normal': {
const selectedCellAddress = selectedCell.value?.address;
if (!selectedCellAddress) {
return;
}
let next: CellAddress;
switch (ev.code) {
case 'ArrowRight': {
next = { col: selectedCellAddress.col + 1, row: selectedCellAddress.row };
break;
}
case 'ArrowLeft': {
next = { col: selectedCellAddress.col - 1, row: selectedCellAddress.row };
break;
}
case 'ArrowUp': {
next = { col: selectedCellAddress.col, row: selectedCellAddress.row - 1 };
break;
}
case 'ArrowDown': {
next = { col: selectedCellAddress.col, row: selectedCellAddress.row + 1 };
break;
}
default: {
return;
}
}
selectionCell(next);
break;
}
}
}
function onMouseDown(ev: MouseEvent) {
const cellAddress = getCellAddress(ev.target as HTMLElement);
switch (state.value) {
@ -91,20 +134,34 @@ function onMouseDown(ev: MouseEvent) {
break;
}
case 'normal': {
const cellAddress = getCellAddress(ev.target as HTMLElement);
if (availableCellAddress(cellAddress)) {
selectionCell(cellAddress);
state.value = 'cellSelecting';
}
break;
}
}
}
function onMouseUp() {
switch (state.value) {
case 'cellSelecting': {
state.value = 'normal';
previousCellAddress.value = CELL_ADDRESS_NONE;
registerMouseUp();
registerMouseMove();
state.value = 'cellSelecting';
} else if (isColumnHeaderCellAddress(cellAddress)) {
unSelectionRange();
const colCells = cells.value.map(row => row[cellAddress.col]);
selectionRange(...colCells.map(cell => cell.address));
registerMouseUp();
registerMouseMove();
firstSelectionColumnIdx.value = cellAddress.col;
state.value = 'colSelecting';
} else if (isRowNumberCellAddress(cellAddress)) {
unSelectionRange();
const rowCells = cells.value[cellAddress.row];
selectionRange(...rowCells.map(cell => cell.address));
registerMouseUp();
registerMouseMove();
firstSelectionRowIdx.value = cellAddress.row;
state.value = 'rowSelecting';
}
break;
}
}
@ -129,19 +186,70 @@ function onMouseMove(ev: MouseEvent) {
row: Math.max(targetCellAddress.row, selectedCellAddress.row),
};
for (const cell of rangedCells.value) {
const outOfRangeCol = cell.address.col < leftTop.col || cell.address.col > rightBottom.col;
const outOfRangeRow = cell.address.row < leftTop.row || cell.address.row > rightBottom.row;
if (outOfRangeCol || outOfRangeRow) {
cell.ranged = false;
}
}
unSelectionOutOfRange(leftTop, rightBottom);
expandRange(leftTop, rightBottom);
previousCellAddress.value = targetCellAddress;
break;
}
case 'colSelecting': {
const targetCellAddress = getCellAddress(ev.target as HTMLElement);
if (!isColumnHeaderCellAddress(targetCellAddress) || previousCellAddress.value.col === targetCellAddress.col) {
return;
}
const leftTop = {
col: Math.min(targetCellAddress.col, firstSelectionColumnIdx.value),
row: 0,
};
const rightBottom = {
col: Math.max(targetCellAddress.col, firstSelectionColumnIdx.value),
row: cells.value.length - 1,
};
unSelectionOutOfRange(leftTop, rightBottom);
expandRange(leftTop, rightBottom);
previousCellAddress.value = targetCellAddress;
break;
}
case 'rowSelecting': {
const targetCellAddress = getCellAddress(ev.target as HTMLElement);
if (!isRowNumberCellAddress(targetCellAddress) || previousCellAddress.value.row === targetCellAddress.row) {
return;
}
const leftTop = {
col: 0,
row: Math.min(targetCellAddress.row, firstSelectionRowIdx.value),
};
const rightBottom = {
col: Math.min(...cells.value.map(it => it.length - 1)),
row: Math.max(targetCellAddress.row, firstSelectionRowIdx.value),
};
unSelectionOutOfRange(leftTop, rightBottom);
expandRange(leftTop, rightBottom);
previousCellAddress.value = targetCellAddress;
break;
}
}
}
function onMouseUp(ev: MouseEvent) {
switch (state.value) {
case 'rowSelecting':
case 'colSelecting':
case 'cellSelecting': {
unregisterMouseUp();
unregisterMouseMove();
state.value = 'normal';
previousCellAddress.value = CELL_ADDRESS_NONE;
break;
}
}
}
@ -161,6 +269,14 @@ function onCellEditEnd() {
state.value = 'normal';
}
function onChangeCellValue(sender: GridCell, newValue: CellValue) {
cells.value[sender.address.row][sender.address.col].value = newValue;
}
function onChangeCellContentSize(sender: GridCell, contentSize: Size) {
cells.value[sender.address.row][sender.address.col].contentSize = contentSize;
}
function onSelectionMove(_: GridCell, next: CellAddress) {
if (availableCellAddress(next)) {
selectionCell(next);
@ -185,7 +301,7 @@ function onHeaderCellWidthEndChange(_: GridColumn) {
}
}
function onHeaderCellWidthChanging(sender: GridColumn, width: string) {
function onHeaderCellChangeWidth(sender: GridColumn, width: string) {
switch (state.value) {
case 'colResizing': {
const column = columns.value[sender.index];
@ -195,6 +311,15 @@ function onHeaderCellWidthChanging(sender: GridColumn, width: string) {
}
}
function onHeaderCellChangeContentSize(sender: GridColumn, newSize: Size) {
switch (state.value) {
case 'normal': {
columns.value[sender.index].contentSize = newSize;
break;
}
}
}
function onHeaderCellWidthLargest(sender: GridColumn) {
switch (state.value) {
case 'normal': {
@ -231,6 +356,10 @@ function onSelectionRow(sender: GridRow) {
}
function selectionCell(target: CellAddress) {
if (!availableCellAddress(target)) {
return;
}
unSelectionRange();
const _cells = cells.value;
@ -253,6 +382,17 @@ function unSelectionRange() {
}
}
function unSelectionOutOfRange(leftTop: CellAddress, rightBottom: CellAddress) {
const _cells = rangedCells.value;
for (const cell of _cells) {
const outOfRangeCol = cell.address.col < leftTop.col || cell.address.col > rightBottom.col;
const outOfRangeRow = cell.address.row < leftTop.row || cell.address.row > rightBottom.row;
if (outOfRangeCol || outOfRangeRow) {
cell.ranged = false;
}
}
}
function expandRange(leftTop: CellAddress, rightBottom: CellAddress) {
const targetRows = cells.value.slice(leftTop.row, rightBottom.row + 1);
for (const row of targetRows) {
@ -266,6 +406,14 @@ function availableCellAddress(cellAddress: CellAddress): boolean {
return cellAddress.row >= 0 && cellAddress.col >= 0 && cellAddress.row < rows.value.length && cellAddress.col < columns.value.length;
}
function isColumnHeaderCellAddress(cellAddress: CellAddress): boolean {
return cellAddress.row === -1 && cellAddress.col >= 0;
}
function isRowNumberCellAddress(cellAddress: CellAddress): boolean {
return cellAddress.row >= 0 && cellAddress.col === -1;
}
function refreshColumnsSetting() {
const bindToList = columnSettings.value.map(it => it.bindTo);
if (new Set(bindToList).size !== columnSettings.value.length) {
@ -316,6 +464,24 @@ function refreshData() {
cells.value = _cells;
}
function registerMouseMove() {
unregisterMouseMove();
addEventListener('mousemove', onMouseMove);
}
function unregisterMouseMove() {
removeEventListener('mousemove', onMouseMove);
}
function registerMouseUp() {
unregisterMouseUp();
addEventListener('mouseup', onMouseUp);
}
function unregisterMouseUp() {
removeEventListener('mouseup', onMouseUp);
}
refreshColumnsSetting();
refreshData();

View File

@ -6,7 +6,7 @@
>
<div :class="$style.root">
<div :class="$style.left"/>
<div :class="$style.wrapper" @mouseup="onContentMouseUp">
<div :class="$style.wrapper">
<div ref="contentEl" :class="$style.contentArea">
{{ text }}
</div>
@ -22,15 +22,14 @@
<script setup lang="ts">
import { computed, nextTick, ref, toRefs, watch } from 'vue';
import { GridColumn, GridEventEmitter } from '@/components/grid/types.js';
import { GridColumn, GridEventEmitter, Size } from '@/components/grid/types.js';
const emit = defineEmits<{
//
(ev: 'width:begin-change', sender: GridColumn): void;
(ev: 'width:end-change', sender: GridColumn): void;
(ev: 'width:changing', sender: GridColumn, width: string): void;
(ev: 'width:largest', sender: GridColumn): void;
(ev: 'selection:column', sender: GridColumn): void;
(ev: 'operation:beginWidthChange', sender: GridColumn): void;
(ev: 'operation:endWidthChange', sender: GridColumn): void;
(ev: 'operation:widthLargest', sender: GridColumn): void;
(ev: 'change:width', sender: GridColumn, width: string): void;
(ev: 'change:contentSize', sender: GridColumn, newSize: Size): void;
}>();
const props = defineProps<{
column: GridColumn,
@ -54,19 +53,10 @@ watch(column, () => {
nextTick(updateContentSize);
});
function onContentMouseUp(ev: MouseEvent) {
switch (ev.type) {
case 'mouseup': {
emit('selection:column', column.value);
break;
}
}
}
function onHandleDoubleClick(ev: MouseEvent) {
switch (ev.type) {
case 'dblclick': {
emit('width:largest', column.value);
emit('operation:widthLargest', column.value);
break;
}
}
@ -79,7 +69,7 @@ function onHandleMouseDown(ev: MouseEvent) {
registerHandleMouseUp();
registerHandleMouseMove();
resizing.value = true;
emit('width:begin-change', column.value);
emit('operation:beginWidthChange', column.value);
}
break;
}
@ -99,7 +89,7 @@ function onHandleMouseMove(ev: MouseEvent) {
const clientWidth = rootEl.value.clientWidth;
const clientRight = bounds.left + clientWidth;
const nextWidth = clientWidth + (ev.clientX - clientRight);
emit('width:changing', column.value, `${nextWidth}px`);
emit('change:width', column.value, `${nextWidth}px`);
}
break;
}
@ -113,7 +103,7 @@ function onHandleMouseUp(ev: MouseEvent) {
unregisterHandleMouseUp();
unregisterHandleMouseMove();
resizing.value = false;
emit('width:end-change', column.value);
emit('operation:endWidthChange', column.value);
}
break;
}
@ -141,17 +131,17 @@ function unregisterHandleMouseUp() {
function updateContentSize() {
const clientWidth = contentEl.value?.clientWidth ?? 0;
const clientHeight = contentEl.value?.clientHeight ?? 0;
column.value.contentSize = {
emit('change:contentSize', column.value, {
// +3px
width: clientWidth + 3 + 3,
height: clientHeight,
};
});
}
</script>
<style module lang="scss">
$handleWidth: 3px;
$handleWidth: 5px;
.cell {
border-left: solid 0.5px var(--divider);
@ -180,6 +170,8 @@ $handleWidth: 3px;
}
.left {
// right
margin-left: -$handleWidth;
margin-right: auto;
width: $handleWidth;
min-width: $handleWidth;
@ -187,9 +179,12 @@ $handleWidth: 3px;
.right {
margin-left: auto;
// 使
margin-right: -$handleWidth;
width: $handleWidth;
min-width: $handleWidth;
cursor: w-resize;
z-index: 1;
}
}
</style>

View File

@ -10,26 +10,27 @@
:key="column.index"
:column="column"
:bus="bus"
@width:beginChange="(sender) => emit('width:begin-change', sender)"
@width:endChange="(sender) => emit('width:end-change', sender)"
@width:changing="(sender, width) => emit('width:changing', sender, width)"
@width:largest="(sender) => emit('width:largest', sender)"
@selection:column="(sender) => emit('selection:column', sender)"
@operation:beginWidthChange="(sender) => emit('operation:beginWidthChange', sender)"
@operation:endWidthChange="(sender) => emit('operation:endWidthChange', sender)"
@operation:widthLargest="(sender) => emit('operation:widthLargest', sender)"
@change:width="(sender, width) => emit('change:width', sender, width)"
@change:contentSize="(sender, newSize) => emit('change:contentSize', sender, newSize)"
/>
</tr>
</template>
<script setup lang="ts">
import { GridColumn, GridEventEmitter } from '@/components/grid/types.js';
import { GridColumn, GridEventEmitter, Size } from '@/components/grid/types.js';
import MkHeaderCell from '@/components/grid/MkHeaderCell.vue';
import MkNumberCell from '@/components/grid/MkNumberCell.vue';
const emit = defineEmits<{
(ev: 'width:begin-change', sender: GridColumn): void;
(ev: 'width:end-change', sender: GridColumn): void;
(ev: 'width:changing', sender: GridColumn, width: string): void;
(ev: 'width:largest', sender: GridColumn): void;
(ev: 'selection:column', sender: GridColumn): void;
(ev: 'operation:beginWidthChange', sender: GridColumn): void;
(ev: 'operation:endWidthChange', sender: GridColumn): void;
(ev: 'operation:widthLargest', sender: GridColumn): void;
(ev: 'operation:selectionColumn', sender: GridColumn): void;
(ev: 'change:width', sender: GridColumn, width: string): void;
(ev: 'change:contentSize', sender: GridColumn, newSize: Size): void;
}>();
defineProps<{
columns: GridColumn[],

View File

@ -1,36 +1,21 @@
<template>
<th :class="[$style.num, [top ? {} : $style.border]]" @mouseup="onMouseUp">
<th :class="[$style.num, [top ? {} : $style.border]]">
{{ content }}
</th>
</template>
<script setup lang="ts">
import { toRefs } from 'vue';
import { GridRow } from '@/components/grid/types.js';
const emit = defineEmits<{
(ev: 'selection:row', sender: GridRow): void;
}>();
const emit = defineEmits<{}>();
const props = defineProps<{
defineProps<{
content: string,
row?: GridRow,
selectable: boolean,
top?: boolean,
}>();
const { content, row, selectable } = toRefs(props);
function onMouseUp(ev: MouseEvent) {
switch (ev.type) {
case 'mouseup': {
if (selectable.value && row.value) {
emit('selection:row', row.value);
}
break;
}
}
}
</script>
<style module lang="scss">

View File

@ -4,7 +4,7 @@ export type CellValue = string | boolean | number | undefined | null
export type DataSource = Record<string, CellValue>;
export type GridState = 'normal' | 'cellSelecting' | 'cellEditing' | 'colResizing'
export type GridState = 'normal' | 'cellSelecting' | 'cellEditing' | 'colResizing' | 'colSelecting' | 'rowSelecting'
export type RowState = 'normal' | 'added' | 'deleted'

View File

@ -2,10 +2,39 @@
<div>
<MkStickyContainer>
<template #header>
<MkPageHeader/>
<MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/>
</template>
<div class="_gaps" :class="$style.root">
<MkGrid :data="convertedGridItems" :columnSettings="columnSettings"/>
<MkInput v-model="query" :debounce="true" type="search" autocapitalize="off">
<template #prefix><i class="ti ti-search"></i></template>
<template #label>{{ i18n.ts.search }}</template>
</MkInput>
<div :class="$style.controller">
<MkSelect v-model="limit">
<option value="100">100</option>
</MkSelect>
</div>
<div style="overflow-y: scroll; padding-top: 8px; padding-bottom: 8px;">
<MkGrid :data="convertedGridItems" :columnSettings="columnSettings"/>
</div>
<div :class="$style.pages">
<button>&lt;&lt;</button>
<button>&lt;</button>
<button>1</button>
<button>2</button>
<button>3</button>
<button>4</button>
<button>5</button>
<span>...</span>
<button>10</button>
<button>&gt;</button>
<button>&gt;&gt;</button>
</div>
</div>
</MkStickyContainer>
</div>
@ -18,6 +47,10 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
import { GridItem } from '@/pages/admin/custom-emojis-grid.impl.js';
import MkGrid from '@/components/grid/MkGrid.vue';
import { ColumnSetting } from '@/components/grid/types.js';
import { i18n } from '@/i18n.js';
import MkInput from '@/components/MkInput.vue';
import MkSelect from '@/components/MkSelect.vue';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const columnSettings: ColumnSetting[] = [
{ bindTo: 'url', title: '🎨', type: 'image', editable: false, width: 50 },
@ -32,6 +65,10 @@ const columnSettings: ColumnSetting[] = [
const customEmojis = ref<Misskey.entities.EmojiDetailed[]>([]);
const gridItems = ref<GridItem[]>([]);
const query = ref('');
const limit = ref(100);
const tab = ref('local');
const convertedGridItems = computed(() => gridItems.value.map(it => it.asRecord()));
const refreshCustomEmojis = async () => {
@ -48,6 +85,29 @@ onMounted(async () => {
await refreshCustomEmojis();
refreshGridItems();
});
const headerTabs = computed(() => [{
key: 'local',
title: i18n.ts.local,
}, {
key: 'remote',
title: i18n.ts.remote,
}]);
const headerActions = computed(() => [{
asFullButton: true,
icon: 'ti ti-plus',
text: i18n.ts.addEmoji,
handler: () => {},
}, {
icon: 'ti ti-dots',
handler: () => {},
}]);
definePageMetadata(computed(() => ({
title: i18n.ts.customEmojis,
icon: 'ti ti-icons',
})));
</script>
<style lang="scss">
@ -68,4 +128,26 @@ onMounted(async () => {
padding: 16px;
overflow: scroll;
}
.controller {
display: flex;
justify-content: flex-start;
align-items: center;
margin-top: 16px;
}
.pages {
display: flex;
justify-content: center;
align-items: center;
margin-top: 8px;
button {
background-color: var(--buttonBg);
border-radius: 9999px;
border: none;
margin: 0 4px;
padding: 8px;
}
}
</style>

View File

@ -905,9 +905,6 @@ importers:
'@testing-library/vue':
specifier: 8.0.1
version: 8.0.1(@vue/compiler-sfc@3.4.3)(vue@3.4.15)
'@types/blueimp-load-image':
specifier: ^5.16.6
version: 5.16.6
'@types/escape-regexp':
specifier: 0.0.3
version: 0.0.3
@ -7876,10 +7873,6 @@ packages:
resolution: {integrity: sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==}
dev: true
/@types/blueimp-load-image@5.16.6:
resolution: {integrity: sha512-e7s6CdDCUoBQdCe62Q6OS+DF68M8+ABxCEMh2Isjt4Fl3xuddljCHMN8mak48AMSVGGwUUtNRaZbkzgL5PEWew==}
dev: true
/@types/body-parser@1.19.5:
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
dependencies: