wip
This commit is contained in:
parent
a2fcc81290
commit
8d1a5734cd
|
@ -98,7 +98,6 @@
|
||||||
"@storybook/vue3": "7.6.10",
|
"@storybook/vue3": "7.6.10",
|
||||||
"@storybook/vue3-vite": "7.6.10",
|
"@storybook/vue3-vite": "7.6.10",
|
||||||
"@testing-library/vue": "8.0.1",
|
"@testing-library/vue": "8.0.1",
|
||||||
"@types/blueimp-load-image": "^5.16.6",
|
|
||||||
"@types/escape-regexp": "0.0.3",
|
"@types/escape-regexp": "0.0.3",
|
||||||
"@types/estree": "1.0.5",
|
"@types/estree": "1.0.5",
|
||||||
"@types/matter-js": "0.19.6",
|
"@types/matter-js": "0.19.6",
|
||||||
|
|
|
@ -54,13 +54,15 @@ import {
|
||||||
equalCellAddress,
|
equalCellAddress,
|
||||||
getCellAddress,
|
getCellAddress,
|
||||||
GridCell,
|
GridCell,
|
||||||
GridEventEmitter,
|
GridEventEmitter, Size,
|
||||||
} from '@/components/grid/types.js';
|
} from '@/components/grid/types.js';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'edit:begin', sender: GridCell): void;
|
(ev: 'operation:beginEdit', sender: GridCell): void;
|
||||||
(ev: 'edit:end', sender: GridCell): void;
|
(ev: 'operation:endEdit', sender: GridCell): void;
|
||||||
(ev: 'selection:move', sender: GridCell, next: CellAddress): 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<{
|
const props = defineProps<{
|
||||||
cell: GridCell,
|
cell: GridCell,
|
||||||
|
@ -87,10 +89,9 @@ const needsContentCentering = computed(() => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(cellWidth, updateContentSize);
|
|
||||||
watch(() => [cell, cell.value.value], () => {
|
watch(() => [cell, cell.value.value], () => {
|
||||||
// 中身がセットされた直後はサイズが分からないので、次のタイミングで更新する
|
// 中身がセットされた直後はサイズが分からないので、次のタイミングで更新する
|
||||||
nextTick(updateContentSize);
|
nextTick(emitContentSizeChanged);
|
||||||
});
|
});
|
||||||
watch(() => cell.value.selected, () => {
|
watch(() => cell.value.selected, () => {
|
||||||
if (cell.value.selected) {
|
if (cell.value.selected) {
|
||||||
|
@ -116,44 +117,13 @@ function onOutsideMouseDown(ev: MouseEvent) {
|
||||||
|
|
||||||
function onCellKeyDown(ev: KeyboardEvent) {
|
function onCellKeyDown(ev: KeyboardEvent) {
|
||||||
if (!editing.value) {
|
if (!editing.value) {
|
||||||
|
ev.preventDefault();
|
||||||
switch (ev.code) {
|
switch (ev.code) {
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
case 'F2': {
|
case 'F2': {
|
||||||
beginEditing();
|
beginEditing();
|
||||||
break;
|
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 {
|
} else {
|
||||||
switch (ev.code) {
|
switch (ev.code) {
|
||||||
|
@ -193,9 +163,10 @@ function beginEditing() {
|
||||||
editingValue.value = cell.value.value;
|
editingValue.value = cell.value.value;
|
||||||
editing.value = true;
|
editing.value = true;
|
||||||
registerOutsideMouseDown();
|
registerOutsideMouseDown();
|
||||||
emit('edit:begin', cell.value);
|
emit('operation:beginEdit', cell.value);
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
// inputの展開後にフォーカスを当てたい
|
||||||
if (inputAreaEl.value) {
|
if (inputAreaEl.value) {
|
||||||
(inputAreaEl.value.querySelector('*') as HTMLElement).focus();
|
(inputAreaEl.value.querySelector('*') as HTMLElement).focus();
|
||||||
}
|
}
|
||||||
|
@ -204,7 +175,7 @@ function beginEditing() {
|
||||||
}
|
}
|
||||||
case 'boolean': {
|
case 'boolean': {
|
||||||
// とくに特殊なUIは設けず、トグルするだけ
|
// とくに特殊なUIは設けず、トグルするだけ
|
||||||
cell.value.value = !cell.value.value;
|
emitValueChange(!cell.value.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,22 +186,28 @@ function endEditing(applyValue: boolean) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('edit:end', cell.value);
|
emit('operation:endEdit', cell.value);
|
||||||
unregisterOutsideMouseDown();
|
unregisterOutsideMouseDown();
|
||||||
|
|
||||||
if (applyValue) {
|
if (applyValue) {
|
||||||
cell.value.value = editingValue.value;
|
emitValueChange(editingValue.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
editingValue.value = undefined;
|
||||||
editing.value = false;
|
editing.value = false;
|
||||||
|
|
||||||
rootEl.value?.focus();
|
rootEl.value?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateContentSize() {
|
function emitValueChange(newValue: CellValue) {
|
||||||
cell.value.contentSize = {
|
emit('change:value', cell.value, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitContentSizeChanged() {
|
||||||
|
emit('change:contentSize', cell.value, {
|
||||||
width: contentAreaEl.value?.clientWidth ?? 0,
|
width: contentAreaEl.value?.clientWidth ?? 0,
|
||||||
height: contentAreaEl.value?.clientHeight ?? 0,
|
height: contentAreaEl.value?.clientHeight ?? 0,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,31 +4,35 @@
|
||||||
:content="(row.index + 1).toString()"
|
:content="(row.index + 1).toString()"
|
||||||
:selectable="true"
|
:selectable="true"
|
||||||
:row="row"
|
:row="row"
|
||||||
@selection:row="(sender) => emit('selection:row', sender)"
|
@operation:selectionRow="(sender) => emit('operation:selectionRow', sender)"
|
||||||
/>
|
/>
|
||||||
<MkDataCell
|
<MkDataCell
|
||||||
v-for="cell in cells"
|
v-for="cell in cells"
|
||||||
:key="cell.address.col"
|
:key="cell.address.col"
|
||||||
:cell="cell"
|
:cell="cell"
|
||||||
:bus="bus"
|
:bus="bus"
|
||||||
@edit:begin="(sender) => emit('edit:begin', sender)"
|
@operation:beginEdit="(sender) => emit('operation:beginEdit', sender)"
|
||||||
@edit:end="(sender) => emit('edit:end', sender)"
|
@operation:endEdit="(sender) => emit('operation:endEdit', sender)"
|
||||||
@selection:move="(sender, next) => emit('selection:move', sender, next)"
|
@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>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, toRefs } from 'vue';
|
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 MkDataCell from '@/components/grid/MkDataCell.vue';
|
||||||
import MkNumberCell from '@/components/grid/MkNumberCell.vue';
|
import MkNumberCell from '@/components/grid/MkNumberCell.vue';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'edit:begin', sender: GridCell): void;
|
(ev: 'operation:beginEdit', sender: GridCell): void;
|
||||||
(ev: 'edit:end', sender: GridCell): void;
|
(ev: 'operation:endEdit', sender: GridCell): void;
|
||||||
(ev: 'selection:move', sender: GridCell, next: CellAddress): void;
|
(ev: 'operation:selectionRow', sender: GridRow): void;
|
||||||
(ev: 'selection:row', 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<{
|
const props = defineProps<{
|
||||||
row: GridRow,
|
row: GridRow,
|
||||||
|
@ -37,7 +41,6 @@ const props = defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { cells } = toRefs(props);
|
const { cells } = toRefs(props);
|
||||||
const last = computed(() => cells.value[cells.value.length - 1]);
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,18 @@
|
||||||
<table
|
<table
|
||||||
:class="$style.grid"
|
:class="$style.grid"
|
||||||
@mousedown="onMouseDown"
|
@mousedown="onMouseDown"
|
||||||
@mouseup="onMouseUp"
|
@keydown="onKeyDown"
|
||||||
@mousemove="onMouseMove"
|
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<MkHeaderRow
|
<MkHeaderRow
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:bus="bus"
|
:bus="bus"
|
||||||
@width:beginChange="onHeaderCellWidthBeginChange"
|
@operation:beginWidthChange="onHeaderCellWidthBeginChange"
|
||||||
@width:endChange="onHeaderCellWidthEndChange"
|
@operation:endWidthChange="onHeaderCellWidthEndChange"
|
||||||
@width:changing="onHeaderCellWidthChanging"
|
@operation:widthLargest="onHeaderCellWidthLargest"
|
||||||
@width:largest="onHeaderCellWidthLargest"
|
@operation:selectionColumn="onSelectionColumn"
|
||||||
@selection:column="onSelectionColumn"
|
@change:width="onHeaderCellChangeWidth"
|
||||||
|
@change:contentSize="onHeaderCellChangeContentSize"
|
||||||
/>
|
/>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -23,10 +23,12 @@
|
||||||
:row="row"
|
:row="row"
|
||||||
:cells="cells[row.index]"
|
:cells="cells[row.index]"
|
||||||
:bus="bus"
|
:bus="bus"
|
||||||
@edit:begin="onCellEditBegin"
|
@operation:beginEdit="onCellEditBegin"
|
||||||
@edit:end="onCellEditEnd"
|
@operation:endEdit="onCellEditEnd"
|
||||||
@selection:move="onSelectionMove"
|
@operation:selectionMove="onSelectionMove"
|
||||||
@selection:row="onSelectionRow"
|
@operation:selectionRow="onSelectionRow"
|
||||||
|
@change:value="onChangeCellValue"
|
||||||
|
@change:contentSize="onChangeCellContentSize"
|
||||||
/>
|
/>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -38,6 +40,7 @@ import {
|
||||||
calcCellWidth,
|
calcCellWidth,
|
||||||
CELL_ADDRESS_NONE,
|
CELL_ADDRESS_NONE,
|
||||||
CellAddress,
|
CellAddress,
|
||||||
|
CellValue,
|
||||||
ColumnSetting,
|
ColumnSetting,
|
||||||
DataSource,
|
DataSource,
|
||||||
equalCellAddress,
|
equalCellAddress,
|
||||||
|
@ -47,7 +50,7 @@ import {
|
||||||
GridEventEmitter,
|
GridEventEmitter,
|
||||||
GridRow,
|
GridRow,
|
||||||
GridState,
|
GridState,
|
||||||
isCellElement,
|
Size,
|
||||||
} from '@/components/grid/types.js';
|
} from '@/components/grid/types.js';
|
||||||
import MkDataRow from '@/components/grid/MkDataRow.vue';
|
import MkDataRow from '@/components/grid/MkDataRow.vue';
|
||||||
import MkHeaderRow from '@/components/grid/MkHeaderRow.vue';
|
import MkHeaderRow from '@/components/grid/MkHeaderRow.vue';
|
||||||
|
@ -57,21 +60,24 @@ const props = defineProps<{
|
||||||
data: DataSource[]
|
data: DataSource[]
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const bus = new GridEventEmitter();
|
||||||
|
|
||||||
const { columnSettings, data } = toRefs(props);
|
const { columnSettings, data } = toRefs(props);
|
||||||
|
|
||||||
const columns = ref<GridColumn[]>([]);
|
const columns = ref<GridColumn[]>([]);
|
||||||
const rows = ref<GridRow[]>([]);
|
const rows = ref<GridRow[]>([]);
|
||||||
const cells = ref<GridCell[][]>([]);
|
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 selectedCell = computed(() => {
|
||||||
const selected = cells.value.flat().filter(it => it.selected);
|
const selected = cells.value.flat().filter(it => it.selected);
|
||||||
return selected.length > 0 ? selected[0] : undefined;
|
return selected.length > 0 ? selected[0] : undefined;
|
||||||
});
|
});
|
||||||
const rangedCells = computed(() => cells.value.flat().filter(it => it.ranged));
|
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(columnSettings, refreshColumnsSetting);
|
||||||
watch(data, refreshData);
|
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) {
|
function onMouseDown(ev: MouseEvent) {
|
||||||
const cellAddress = getCellAddress(ev.target as HTMLElement);
|
const cellAddress = getCellAddress(ev.target as HTMLElement);
|
||||||
switch (state.value) {
|
switch (state.value) {
|
||||||
|
@ -91,20 +134,34 @@ function onMouseDown(ev: MouseEvent) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'normal': {
|
case 'normal': {
|
||||||
|
const cellAddress = getCellAddress(ev.target as HTMLElement);
|
||||||
if (availableCellAddress(cellAddress)) {
|
if (availableCellAddress(cellAddress)) {
|
||||||
selectionCell(cellAddress);
|
selectionCell(cellAddress);
|
||||||
state.value = 'cellSelecting';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMouseUp() {
|
registerMouseUp();
|
||||||
switch (state.value) {
|
registerMouseMove();
|
||||||
case 'cellSelecting': {
|
state.value = 'cellSelecting';
|
||||||
state.value = 'normal';
|
} else if (isColumnHeaderCellAddress(cellAddress)) {
|
||||||
previousCellAddress.value = CELL_ADDRESS_NONE;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,19 +186,70 @@ function onMouseMove(ev: MouseEvent) {
|
||||||
row: Math.max(targetCellAddress.row, selectedCellAddress.row),
|
row: Math.max(targetCellAddress.row, selectedCellAddress.row),
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const cell of rangedCells.value) {
|
unSelectionOutOfRange(leftTop, rightBottom);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expandRange(leftTop, rightBottom);
|
expandRange(leftTop, rightBottom);
|
||||||
previousCellAddress.value = targetCellAddress;
|
previousCellAddress.value = targetCellAddress;
|
||||||
|
|
||||||
break;
|
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';
|
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) {
|
function onSelectionMove(_: GridCell, next: CellAddress) {
|
||||||
if (availableCellAddress(next)) {
|
if (availableCellAddress(next)) {
|
||||||
selectionCell(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) {
|
switch (state.value) {
|
||||||
case 'colResizing': {
|
case 'colResizing': {
|
||||||
const column = columns.value[sender.index];
|
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) {
|
function onHeaderCellWidthLargest(sender: GridColumn) {
|
||||||
switch (state.value) {
|
switch (state.value) {
|
||||||
case 'normal': {
|
case 'normal': {
|
||||||
|
@ -231,6 +356,10 @@ function onSelectionRow(sender: GridRow) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectionCell(target: CellAddress) {
|
function selectionCell(target: CellAddress) {
|
||||||
|
if (!availableCellAddress(target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
unSelectionRange();
|
unSelectionRange();
|
||||||
|
|
||||||
const _cells = cells.value;
|
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) {
|
function expandRange(leftTop: CellAddress, rightBottom: CellAddress) {
|
||||||
const targetRows = cells.value.slice(leftTop.row, rightBottom.row + 1);
|
const targetRows = cells.value.slice(leftTop.row, rightBottom.row + 1);
|
||||||
for (const row of targetRows) {
|
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;
|
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() {
|
function refreshColumnsSetting() {
|
||||||
const bindToList = columnSettings.value.map(it => it.bindTo);
|
const bindToList = columnSettings.value.map(it => it.bindTo);
|
||||||
if (new Set(bindToList).size !== columnSettings.value.length) {
|
if (new Set(bindToList).size !== columnSettings.value.length) {
|
||||||
|
@ -316,6 +464,24 @@ function refreshData() {
|
||||||
cells.value = _cells;
|
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();
|
refreshColumnsSetting();
|
||||||
refreshData();
|
refreshData();
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
>
|
>
|
||||||
<div :class="$style.root">
|
<div :class="$style.root">
|
||||||
<div :class="$style.left"/>
|
<div :class="$style.left"/>
|
||||||
<div :class="$style.wrapper" @mouseup="onContentMouseUp">
|
<div :class="$style.wrapper">
|
||||||
<div ref="contentEl" :class="$style.contentArea">
|
<div ref="contentEl" :class="$style.contentArea">
|
||||||
{{ text }}
|
{{ text }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,15 +22,14 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, ref, toRefs, watch } from 'vue';
|
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<{
|
const emit = defineEmits<{
|
||||||
// ヘッダのサイズ変更系イベントはセル全体の横幅設定に影響するので上位コンポーネントにリレーする必要あり
|
(ev: 'operation:beginWidthChange', sender: GridColumn): void;
|
||||||
(ev: 'width:begin-change', sender: GridColumn): void;
|
(ev: 'operation:endWidthChange', sender: GridColumn): void;
|
||||||
(ev: 'width:end-change', sender: GridColumn): void;
|
(ev: 'operation:widthLargest', sender: GridColumn): void;
|
||||||
(ev: 'width:changing', sender: GridColumn, width: string): void;
|
(ev: 'change:width', sender: GridColumn, width: string): void;
|
||||||
(ev: 'width:largest', sender: GridColumn): void;
|
(ev: 'change:contentSize', sender: GridColumn, newSize: Size): void;
|
||||||
(ev: 'selection:column', sender: GridColumn): void;
|
|
||||||
}>();
|
}>();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
column: GridColumn,
|
column: GridColumn,
|
||||||
|
@ -54,19 +53,10 @@ watch(column, () => {
|
||||||
nextTick(updateContentSize);
|
nextTick(updateContentSize);
|
||||||
});
|
});
|
||||||
|
|
||||||
function onContentMouseUp(ev: MouseEvent) {
|
|
||||||
switch (ev.type) {
|
|
||||||
case 'mouseup': {
|
|
||||||
emit('selection:column', column.value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onHandleDoubleClick(ev: MouseEvent) {
|
function onHandleDoubleClick(ev: MouseEvent) {
|
||||||
switch (ev.type) {
|
switch (ev.type) {
|
||||||
case 'dblclick': {
|
case 'dblclick': {
|
||||||
emit('width:largest', column.value);
|
emit('operation:widthLargest', column.value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,7 +69,7 @@ function onHandleMouseDown(ev: MouseEvent) {
|
||||||
registerHandleMouseUp();
|
registerHandleMouseUp();
|
||||||
registerHandleMouseMove();
|
registerHandleMouseMove();
|
||||||
resizing.value = true;
|
resizing.value = true;
|
||||||
emit('width:begin-change', column.value);
|
emit('operation:beginWidthChange', column.value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -99,7 +89,7 @@ function onHandleMouseMove(ev: MouseEvent) {
|
||||||
const clientWidth = rootEl.value.clientWidth;
|
const clientWidth = rootEl.value.clientWidth;
|
||||||
const clientRight = bounds.left + clientWidth;
|
const clientRight = bounds.left + clientWidth;
|
||||||
const nextWidth = clientWidth + (ev.clientX - clientRight);
|
const nextWidth = clientWidth + (ev.clientX - clientRight);
|
||||||
emit('width:changing', column.value, `${nextWidth}px`);
|
emit('change:width', column.value, `${nextWidth}px`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -113,7 +103,7 @@ function onHandleMouseUp(ev: MouseEvent) {
|
||||||
unregisterHandleMouseUp();
|
unregisterHandleMouseUp();
|
||||||
unregisterHandleMouseMove();
|
unregisterHandleMouseMove();
|
||||||
resizing.value = false;
|
resizing.value = false;
|
||||||
emit('width:end-change', column.value);
|
emit('operation:endWidthChange', column.value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -141,17 +131,17 @@ function unregisterHandleMouseUp() {
|
||||||
function updateContentSize() {
|
function updateContentSize() {
|
||||||
const clientWidth = contentEl.value?.clientWidth ?? 0;
|
const clientWidth = contentEl.value?.clientWidth ?? 0;
|
||||||
const clientHeight = contentEl.value?.clientHeight ?? 0;
|
const clientHeight = contentEl.value?.clientHeight ?? 0;
|
||||||
column.value.contentSize = {
|
emit('change:contentSize', column.value, {
|
||||||
// バーの横幅も考慮したいので、+3px
|
// バーの横幅も考慮したいので、+3px
|
||||||
width: clientWidth + 3 + 3,
|
width: clientWidth + 3 + 3,
|
||||||
height: clientHeight,
|
height: clientHeight,
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style module lang="scss">
|
<style module lang="scss">
|
||||||
$handleWidth: 3px;
|
$handleWidth: 5px;
|
||||||
|
|
||||||
.cell {
|
.cell {
|
||||||
border-left: solid 0.5px var(--divider);
|
border-left: solid 0.5px var(--divider);
|
||||||
|
@ -180,6 +170,8 @@ $handleWidth: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
|
// rightのぶんだけズレるのでそれを相殺するためのネガティブマージン
|
||||||
|
margin-left: -$handleWidth;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
width: $handleWidth;
|
width: $handleWidth;
|
||||||
min-width: $handleWidth;
|
min-width: $handleWidth;
|
||||||
|
@ -187,9 +179,12 @@ $handleWidth: 3px;
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
// 判定を罫線の上に重ねたいのでネガティブマージンを使う
|
||||||
|
margin-right: -$handleWidth;
|
||||||
width: $handleWidth;
|
width: $handleWidth;
|
||||||
min-width: $handleWidth;
|
min-width: $handleWidth;
|
||||||
cursor: w-resize;
|
cursor: w-resize;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -10,26 +10,27 @@
|
||||||
:key="column.index"
|
:key="column.index"
|
||||||
:column="column"
|
:column="column"
|
||||||
:bus="bus"
|
:bus="bus"
|
||||||
@width:beginChange="(sender) => emit('width:begin-change', sender)"
|
@operation:beginWidthChange="(sender) => emit('operation:beginWidthChange', sender)"
|
||||||
@width:endChange="(sender) => emit('width:end-change', sender)"
|
@operation:endWidthChange="(sender) => emit('operation:endWidthChange', sender)"
|
||||||
@width:changing="(sender, width) => emit('width:changing', sender, width)"
|
@operation:widthLargest="(sender) => emit('operation:widthLargest', sender)"
|
||||||
@width:largest="(sender) => emit('width:largest', sender)"
|
@change:width="(sender, width) => emit('change:width', sender, width)"
|
||||||
@selection:column="(sender) => emit('selection:column', sender)"
|
@change:contentSize="(sender, newSize) => emit('change:contentSize', sender, newSize)"
|
||||||
/>
|
/>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 MkHeaderCell from '@/components/grid/MkHeaderCell.vue';
|
||||||
import MkNumberCell from '@/components/grid/MkNumberCell.vue';
|
import MkNumberCell from '@/components/grid/MkNumberCell.vue';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'width:begin-change', sender: GridColumn): void;
|
(ev: 'operation:beginWidthChange', sender: GridColumn): void;
|
||||||
(ev: 'width:end-change', sender: GridColumn): void;
|
(ev: 'operation:endWidthChange', sender: GridColumn): void;
|
||||||
(ev: 'width:changing', sender: GridColumn, width: string): void;
|
(ev: 'operation:widthLargest', sender: GridColumn): void;
|
||||||
(ev: 'width:largest', sender: GridColumn): void;
|
(ev: 'operation:selectionColumn', sender: GridColumn): void;
|
||||||
(ev: 'selection:column', sender: GridColumn): void;
|
(ev: 'change:width', sender: GridColumn, width: string): void;
|
||||||
|
(ev: 'change:contentSize', sender: GridColumn, newSize: Size): void;
|
||||||
}>();
|
}>();
|
||||||
defineProps<{
|
defineProps<{
|
||||||
columns: GridColumn[],
|
columns: GridColumn[],
|
||||||
|
|
|
@ -1,36 +1,21 @@
|
||||||
<template>
|
<template>
|
||||||
<th :class="[$style.num, [top ? {} : $style.border]]" @mouseup="onMouseUp">
|
<th :class="[$style.num, [top ? {} : $style.border]]">
|
||||||
{{ content }}
|
{{ content }}
|
||||||
</th>
|
</th>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { toRefs } from 'vue';
|
|
||||||
import { GridRow } from '@/components/grid/types.js';
|
import { GridRow } from '@/components/grid/types.js';
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{}>();
|
||||||
(ev: 'selection:row', sender: GridRow): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
content: string,
|
content: string,
|
||||||
row?: GridRow,
|
row?: GridRow,
|
||||||
selectable: boolean,
|
selectable: boolean,
|
||||||
top?: 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>
|
</script>
|
||||||
|
|
||||||
<style module lang="scss">
|
<style module lang="scss">
|
||||||
|
|
|
@ -4,7 +4,7 @@ export type CellValue = string | boolean | number | undefined | null
|
||||||
|
|
||||||
export type DataSource = Record<string, CellValue>;
|
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'
|
export type RowState = 'normal' | 'added' | 'deleted'
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,40 @@
|
||||||
<div>
|
<div>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header>
|
<template #header>
|
||||||
<MkPageHeader/>
|
<MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/>
|
||||||
</template>
|
</template>
|
||||||
<div class="_gaps" :class="$style.root">
|
<div class="_gaps" :class="$style.root">
|
||||||
|
<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"/>
|
<MkGrid :data="convertedGridItems" :columnSettings="columnSettings"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div :class="$style.pages">
|
||||||
|
<button><<</button>
|
||||||
|
<button><</button>
|
||||||
|
|
||||||
|
<button>1</button>
|
||||||
|
<button>2</button>
|
||||||
|
<button>3</button>
|
||||||
|
<button>4</button>
|
||||||
|
<button>5</button>
|
||||||
|
<span>...</span>
|
||||||
|
<button>10</button>
|
||||||
|
|
||||||
|
<button>></button>
|
||||||
|
<button>>></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -18,6 +47,10 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { GridItem } from '@/pages/admin/custom-emojis-grid.impl.js';
|
import { GridItem } from '@/pages/admin/custom-emojis-grid.impl.js';
|
||||||
import MkGrid from '@/components/grid/MkGrid.vue';
|
import MkGrid from '@/components/grid/MkGrid.vue';
|
||||||
import { ColumnSetting } from '@/components/grid/types.js';
|
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[] = [
|
const columnSettings: ColumnSetting[] = [
|
||||||
{ bindTo: 'url', title: '🎨', type: 'image', editable: false, width: 50 },
|
{ bindTo: 'url', title: '🎨', type: 'image', editable: false, width: 50 },
|
||||||
|
@ -32,6 +65,10 @@ const columnSettings: ColumnSetting[] = [
|
||||||
|
|
||||||
const customEmojis = ref<Misskey.entities.EmojiDetailed[]>([]);
|
const customEmojis = ref<Misskey.entities.EmojiDetailed[]>([]);
|
||||||
const gridItems = ref<GridItem[]>([]);
|
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 convertedGridItems = computed(() => gridItems.value.map(it => it.asRecord()));
|
||||||
|
|
||||||
const refreshCustomEmojis = async () => {
|
const refreshCustomEmojis = async () => {
|
||||||
|
@ -48,6 +85,29 @@ onMounted(async () => {
|
||||||
await refreshCustomEmojis();
|
await refreshCustomEmojis();
|
||||||
refreshGridItems();
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -68,4 +128,26 @@ onMounted(async () => {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
overflow: scroll;
|
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>
|
</style>
|
||||||
|
|
|
@ -905,9 +905,6 @@ importers:
|
||||||
'@testing-library/vue':
|
'@testing-library/vue':
|
||||||
specifier: 8.0.1
|
specifier: 8.0.1
|
||||||
version: 8.0.1(@vue/compiler-sfc@3.4.3)(vue@3.4.15)
|
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':
|
'@types/escape-regexp':
|
||||||
specifier: 0.0.3
|
specifier: 0.0.3
|
||||||
version: 0.0.3
|
version: 0.0.3
|
||||||
|
@ -7876,10 +7873,6 @@ packages:
|
||||||
resolution: {integrity: sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==}
|
resolution: {integrity: sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/blueimp-load-image@5.16.6:
|
|
||||||
resolution: {integrity: sha512-e7s6CdDCUoBQdCe62Q6OS+DF68M8+ABxCEMh2Isjt4Fl3xuddljCHMN8mak48AMSVGGwUUtNRaZbkzgL5PEWew==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/body-parser@1.19.5:
|
/@types/body-parser@1.19.5:
|
||||||
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
|
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
Loading…
Reference in New Issue