fix performance

This commit is contained in:
samunohito 2024-02-05 08:35:26 +09:00
parent b0b28e0cb7
commit d5db737469
5 changed files with 136 additions and 102 deletions

View File

@ -20,7 +20,6 @@
</template>
<script setup lang="ts">
import { toRefs } from 'vue';
import { GridEventEmitter, GridSetting, Size } from '@/components/grid/grid.js';
import MkDataCell from '@/components/grid/MkDataCell.vue';
import MkNumberCell from '@/components/grid/MkNumberCell.vue';
@ -33,15 +32,13 @@ const emit = defineEmits<{
(ev: 'change:value', sender: GridCell, newValue: CellValue): void;
(ev: 'change:contentSize', sender: GridCell, newSize: Size): void;
}>();
const props = defineProps<{
defineProps<{
row: GridRow,
cells: GridCell[],
gridSetting: GridSetting,
bus: GridEventEmitter,
}>();
const { cells, gridSetting } = toRefs(props);
</script>
<style module lang="scss">

View File

@ -21,11 +21,14 @@
<tbody>
<MkDataRow
v-for="row in rows"
v-show="row.using"
:key="row.index"
:row="row"
:cells="cells[row.index].cells"
:gridSetting="gridSetting"
:bus="bus"
:using="row.using"
:class="[ lastLine === row.index ? $style.lastLine : {} ]"
@operation:beginEdit="onCellEditBegin"
@operation:endEdit="onCellEditEnd"
@change:value="onChangeCellValue"
@ -36,40 +39,53 @@
</template>
<script setup lang="ts">
import { computed, nextTick, onMounted, ref, toRefs, watch } from 'vue';
import { DataSource, GridEventEmitter, GridSetting, GridState, Size } from '@/components/grid/grid.js';
import { computed, onMounted, ref, toRefs, watch } from 'vue';
import {
DataSource,
defaultGridSetting,
GridEventEmitter,
GridSetting,
GridState,
Size,
} from '@/components/grid/grid.js';
import MkDataRow from '@/components/grid/MkDataRow.vue';
import MkHeaderRow from '@/components/grid/MkHeaderRow.vue';
import { cellValidation } from '@/components/grid/cell-validators.js';
import { CELL_ADDRESS_NONE, CellAddress, CellValue, createCell, GridCell } from '@/components/grid/cell.js';
import { CELL_ADDRESS_NONE, CellAddress, CellValue, createCell, GridCell, resetCell } from '@/components/grid/cell.js';
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 { ColumnSetting, createColumn, GridColumn } from '@/components/grid/column.js';
import { createRow, GridRow } from '@/components/grid/row.js';
import { createRow, GridRow, resetRow } from '@/components/grid/row.js';
type RowHolder = {
row: GridRow,
cells: GridCell[],
origin: DataSource,
}
const props = withDefaults(defineProps<{
gridSetting?: GridSetting,
columnSettings: ColumnSetting[],
data: DataSource[]
}>(), {
gridSetting: () => ({
rowNumberVisible: true,
rowSelectable: true,
}),
});
const { gridSetting, columnSettings, data } = toRefs(props);
const emit = defineEmits<{
(ev: 'event', event: GridEvent, current: GridCurrentState): void;
}>();
const props = defineProps<{
gridSetting?: GridSetting,
columnSettings: ColumnSetting[],
data: DataSource[]
}>();
// non-reactive
const gridSetting: Required<GridSetting> = {
...props.gridSetting,
...defaultGridSetting,
};
// non-reactive
const columnSettings = props.columnSettings;
const { data } = toRefs(props);
// #region Event Definitions
// region Event Definitions
@ -95,9 +111,9 @@ const rootEl = ref<InstanceType<typeof HTMLTableElement>>();
*/
const state = ref<GridState>('normal');
/**
* グリッドの列定義propsで受け取った{@link columnSettings}をもとに{@link refreshColumnsSetting}で再計算される
* グリッドの列定義列定義の元の設定値は非リアクティブなので初期値を生成して以降は変更しない
*/
const columns = ref<GridColumn[]>([]);
const columns = ref<GridColumn[]>(columnSettings.map(createColumn));
/**
* グリッドの行定義propsで受け取った{@link data}をもとに{@link refreshData}で再計算される
*/
@ -170,7 +186,7 @@ const availableBounds = computed(() => {
};
const rightBottom = {
col: Math.max(...columns.value.map(it => it.index)),
row: Math.max(...rows.value.map(it => it.index)),
row: Math.max(...rows.value.filter(it => it.using).map(it => it.index)),
};
return { leftTop, rightBottom };
});
@ -179,10 +195,11 @@ const availableBounds = computed(() => {
*/
const rangedRows = computed(() => rows.value.filter(it => it.ranged));
const lastLine = computed(() => rows.value.filter(it => it.using).length - 1);
// endregion
// #endregion
watch(columnSettings, refreshColumnsSetting);
watch(data, patchData, { deep: true });
if (_DEV_) {
@ -424,7 +441,7 @@ function onMouseDown(ev: MouseEvent) {
}
function onLeftMouseDown(ev: MouseEvent) {
const cellAddress = getCellAddress(ev.target as HTMLElement, gridSetting.value);
const cellAddress = getCellAddress(ev.target as HTMLElement, gridSetting);
if (_DEV_) {
console.log(`[grid][mouse-left] state:${state.value}, button: ${ev.button}, cell: ${cellAddress.row}x${cellAddress.col}`);
}
@ -478,7 +495,7 @@ function onLeftMouseDown(ev: MouseEvent) {
}
function onRightMouseDown(ev: MouseEvent) {
const cellAddress = getCellAddress(ev.target as HTMLElement, gridSetting.value);
const cellAddress = getCellAddress(ev.target as HTMLElement, gridSetting);
if (_DEV_) {
console.log(`[grid][mouse-right] button: ${ev.button}, cell: ${cellAddress.row}x${cellAddress.col}`);
}
@ -503,7 +520,7 @@ function onRightMouseDown(ev: MouseEvent) {
function onMouseMove(ev: MouseEvent) {
ev.preventDefault();
const targetCellAddress = getCellAddress(ev.target as HTMLElement, gridSetting.value);
const targetCellAddress = getCellAddress(ev.target as HTMLElement, gridSetting);
if (equalCellAddress(previousCellAddress.value, targetCellAddress)) {
return;
}
@ -606,7 +623,7 @@ function onMouseUp(ev: MouseEvent) {
}
function onContextMenu(ev: MouseEvent) {
const cellAddress = getCellAddress(ev.target as HTMLElement, gridSetting.value);
const cellAddress = getCellAddress(ev.target as HTMLElement, gridSetting);
if (_DEV_) {
console.log(`[grid][context-menu] button: ${ev.button}, cell: ${cellAddress.row}x${cellAddress.col}`);
}
@ -883,7 +900,7 @@ function expandCellRange(leftTop: CellAddress, rightBottom: CellAddress) {
* {@link top}から{@link bottom}までの行を範囲選択状態にする
*/
function expandRowRange(top: number, bottom: number) {
if (!gridSetting.value.rowSelectable) {
if (!gridSetting.rowSelectable) {
return;
}
@ -923,47 +940,32 @@ function unregisterMouseUp() {
removeEventListener('mouseup', onMouseUp);
}
function refreshColumnsSetting() {
const bindToList = columnSettings.value.map(it => it.bindTo);
if (new Set(bindToList).size !== columnSettings.value.length) {
//
throw new Error(`Duplicate bindTo setting : [${bindToList.join(',')}]}]`);
}
refreshData();
}
function refreshData() {
if (_DEV_) {
console.log('[grid][refresh-data][begin]');
}
const _data: DataSource[] = data.value;
const _rows: GridRow[] = _data.map((it, index) => createRow(index));
const _cols: GridColumn[] = columnSettings.value.map(createColumn);
const _rows: GridRow[] = (_data.length > gridSetting.rowMinimumDefinitionCount)
? _data.map((_, index) => createRow(index, true))
: Array.from({ length: gridSetting.rowMinimumDefinitionCount }, (_, index) => createRow(index, index < _data.length));
const _cols: GridColumn[] = columns.value;
//
//
const _cells: RowHolder[] = _rows.map((row, rowIndex) => (
{
cells: _cols.map(col => {
const cell = createCell(
col,
row,
(col.setting.bindTo in _data[rowIndex]) ? _data[rowIndex][col.setting.bindTo] : undefined,
);
//
const _cells: RowHolder[] = _rows.map(row => {
const cells = row.using
? _cols.map(col => {
const cell = createCell(col, row, _data[row.index][col.setting.bindTo]);
cell.violation = cellValidation(cell, cell.value);
return cell;
}),
origin: _data[rowIndex],
}
));
})
: _cols.map(col => createCell(col, row, undefined));
return { row, cells, origin: _data[row.index] };
});
rows.value = _rows;
columns.value = _cols;
cells.value = _cells;
if (_DEV_) {
@ -986,53 +988,44 @@ function patchData(newItems: DataSource[]) {
console.log(`[grid][patch-data][begin] new:${newItems.length} old:${cells.value.length}`);
}
if (cells.value.length != newItems.length) {
const _cells = [...cells.value];
const _rows = [...rows.value];
const _cols = columns.value;
//
unSelectionRangeAll();
const oldOrigins = _cells.map(it => it.origin);
if (rows.value.length < newItems.length) {
const newRows = Array.of<GridRow>();
const newCells = Array.of<RowHolder>();
for (const it of newItems) {
const idx = oldOrigins.indexOf(it);
if (idx >= 0) {
//
newRows.push(_rows[idx]);
newCells.push(_cells[idx]);
} else {
//
const newRow = createRow(newRows.length);
//
for (let rowIdx = rows.value.length; rowIdx < newItems.length; rowIdx++) {
const newRow = createRow(rowIdx, true);
newRows.push(newRow);
newCells.push({
cells: _cols.map(col => {
const cell = createCell(col, newRow, it[col.setting.bindTo]);
cell.violation = cellValidation(cell, cell.value);
return cell;
}),
origin: it,
row: newRow,
cells: _cols.map(col => createCell(col, newRow, newItems[rowIdx][col.setting.bindTo])),
origin: newItems[rowIdx],
});
}
rows.value.push(...newRows);
cells.value.push(...newCells);
}
//
for (let rowIdx = 0; rowIdx < newCells.length; rowIdx++) {
newRows[rowIdx].index = rowIdx;
for (const cell of newCells[rowIdx].cells) {
cell.address.row = rowIdx;
if (rows.value.length > newItems.length) {
//
for (let rowIdx = newItems.length; rowIdx < rows.value.length; rowIdx++) {
resetRow(rows.value[rowIdx]);
for (let colIdx = 0; colIdx < _cols.length; colIdx++) {
const holder = cells.value[rowIdx];
holder.origin = {};
resetCell(holder.cells[colIdx]);
}
}
}
rows.value = newRows;
cells.value = newCells;
}
for (let rowIdx = 0; rowIdx < newItems.length; rowIdx++) {
const holder = cells.value[rowIdx];
holder.row.using = true;
for (let rowIdx = 0; rowIdx < cells.value.length; rowIdx++) {
const oldCells = cells.value[rowIdx].cells;
const oldCells = holder.cells;
const newItem = newItems[rowIdx];
for (let colIdx = 0; colIdx < oldCells.length; colIdx++) {
const _col = columns.value[colIdx];
@ -1049,7 +1042,11 @@ function patchData(newItems: DataSource[]) {
//
emitGridEvent({
type: 'cell-validation',
all: cells.value.flatMap(it => it.cells).map(it => it.violation).filter(it => !it.valid),
all: cells.value
.filter(it => it.row.using)
.flatMap(it => it.cells)
.map(it => it.violation)
.filter(it => !it.valid),
});
if (_DEV_) {
@ -1061,7 +1058,13 @@ function patchData(newItems: DataSource[]) {
// #endregion
onMounted(() => {
refreshColumnsSetting();
const bindToList = columnSettings.map(it => it.bindTo);
if (new Set(bindToList).size !== columnSettings.length) {
//
throw new Error(`Duplicate bindTo setting : [${bindToList.join(',')}]}]`);
}
refreshData();
if (rootEl.value) {
resizeObserver.observe(rootEl.value);
@ -1076,6 +1079,12 @@ onMounted(() => {
});
</script>
<style lang="scss">
.hidden {
background-color: yellow;
}
</style>
<style module lang="scss">
$borderSetting: solid 0.5px var(--divider);
$borderRadius: var(--radius);
@ -1121,7 +1130,7 @@ $borderRadius: var(--radius);
}
}
&:last-child {
&.lastLine {
td, th {
//
border-bottom: $borderSetting;

View File

@ -50,3 +50,17 @@ export function createCell(
},
};
}
export function resetCell(cell: GridCell): void {
cell.selected = false;
cell.ranged = false;
cell.violation = {
valid: true,
params: {
column: cell.column,
row: cell.row,
value: cell.value,
},
violations: [],
};
}

View File

@ -2,10 +2,17 @@ import { EventEmitter } from 'eventemitter3';
import { CellValue } from '@/components/grid/cell.js';
export type GridSetting = {
rowNumberVisible: boolean;
rowSelectable: boolean;
rowNumberVisible?: boolean;
rowSelectable?: boolean;
rowMinimumDefinitionCount?: number;
}
export const defaultGridSetting: Required<GridSetting> = {
rowNumberVisible: true,
rowSelectable: true,
rowMinimumDefinitionCount: 100,
};
export type DataSource = Record<string, CellValue>;
export type GridState =

View File

@ -1,11 +1,18 @@
export type GridRow = {
index: number;
ranged: boolean;
using: boolean;
}
export function createRow(index: number): GridRow {
export function createRow(index: number, using: boolean): GridRow {
return {
index,
ranged: false,
using: using,
};
}
export function resetRow(row: GridRow): void {
row.ranged = false;
row.using = false;
}