Files
medusa-store/packages/admin-next/dashboard/src/components/data-grid/models.ts
2024-07-30 18:18:07 +02:00

246 lines
5.2 KiB
TypeScript

import { Command } from "../../hooks/use-command-history"
import { CellCoords } from "./types"
export class Matrix {
private cells: (string | null)[][]
constructor(rows: number, cols: number) {
this.cells = Array.from({ length: rows }, () => Array(cols).fill(null))
}
getFirstNavigableCell(): CellCoords | null {
for (let row = 0; row < this.cells.length; row++) {
for (let col = 0; col < this.cells[0].length; col++) {
if (this.cells[row][col] !== null) {
return { row, col }
}
}
}
return null
}
// Register a navigable cell with a unique key
registerField(row: number, col: number, key: string) {
if (this._isValidPosition(row, col)) {
this.cells[row][col] = key
}
}
getFieldsInSelection(
start: CellCoords | null,
end: CellCoords | null
): string[] {
const keys: string[] = []
if (!start || !end) {
return keys
}
if (start.col !== end.col) {
throw new Error("Selection must be in the same column")
}
const startRow = Math.min(start.row, end.row)
const endRow = Math.max(start.row, end.row)
const col = start.col
for (let row = startRow; row <= endRow; row++) {
if (this._isValidPosition(row, col) && this.cells[row][col] !== null) {
keys.push(this.cells[row][col] as string)
}
}
return keys
}
getCellKey(cell: CellCoords): string | null {
if (this._isValidPosition(cell.row, cell.col)) {
return this.cells[cell.row][cell.col]
}
return null
}
getIsCellSelected(
cell: CellCoords | null,
start: CellCoords | null,
end: CellCoords | null
): boolean {
if (!cell || !start || !end) {
return false
}
if (start.col !== end.col) {
throw new Error("Selection must be in the same column")
}
const startRow = Math.min(start.row, end.row)
const endRow = Math.max(start.row, end.row)
const col = start.col
return cell.col === col && cell.row >= startRow && cell.row <= endRow
}
getValidMovement(
row: number,
col: number,
direction: string,
metaKey: boolean = false
): CellCoords {
const [dRow, dCol] = this._getDirectionDeltas(direction)
if (metaKey) {
return this._getLastValidCellInDirection(row, col, dRow, dCol)
} else {
let newRow = row + dRow
let newCol = col + dCol
if (
newRow < 0 ||
newRow >= this.cells.length ||
newCol < 0 ||
newCol >= this.cells[0].length
) {
return { row, col }
}
while (
this._isValidPosition(newRow, newCol) &&
this.cells[newRow][newCol] === null
) {
newRow += dRow
newCol += dCol
if (
newRow < 0 ||
newRow >= this.cells.length ||
newCol < 0 ||
newCol >= this.cells[0].length
) {
return { row, col }
}
}
return this._isValidPosition(newRow, newCol)
? { row: newRow, col: newCol }
: { row, col }
}
}
private _isValidPosition(row: number, col: number): boolean {
return (
row >= 0 &&
row < this.cells.length &&
col >= 0 &&
col < this.cells[0].length
)
}
private _getDirectionDeltas(direction: string): [number, number] {
switch (direction) {
case "ArrowUp":
return [-1, 0]
case "ArrowDown":
return [1, 0]
case "ArrowLeft":
return [0, -1]
case "ArrowRight":
return [0, 1]
default:
return [0, 0]
}
}
private _getLastValidCellInDirection(
row: number,
col: number,
dRow: number,
dCol: number
): CellCoords {
let newRow = row
let newCol = col
let lastValidRow = row
let lastValidCol = col
while (this._isValidPosition(newRow + dRow, newCol + dCol)) {
newRow += dRow
newCol += dCol
if (this.cells[newRow][newCol] !== null) {
lastValidRow = newRow
lastValidCol = newCol
}
}
return {
row: lastValidRow,
col: lastValidCol,
}
}
}
export type BulkUpdateCommandArgs = {
fields: string[]
next: string[]
prev: string[]
setter: (fields: string[], values: string[]) => void
}
export class BulkUpdateCommand implements Command {
private _fields: string[]
private _prev: string[]
private _next: string[]
private _setter: (string: string[], values: string[]) => void
constructor({ fields, prev, next, setter }: BulkUpdateCommandArgs) {
this._fields = fields
this._prev = prev
this._next = next
this._setter = setter
}
execute(): void {
this._setter(this._fields, this._next)
}
undo(): void {
this._setter(this._fields, this._prev)
}
redo(): void {
this.execute()
}
}
export type UpdateCommandArgs = {
prev: any
next: any
setter: (value: any) => void
}
export class UpdateCommand implements Command {
private _prev: any
private _next: any
private _setter: (value: any) => void
constructor({ prev, next, setter }: UpdateCommandArgs) {
this._prev = prev
this._next = next
this._setter = setter
}
execute(): void {
this._setter(this._next)
}
undo(): void {
this._setter(this._prev)
}
redo(): void {
this.execute()
}
}