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

View File

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

View File

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