diff --git a/src/components/canvas/contextmenu.tsx b/src/components/canvas/contextmenu.tsx index 687312e8..8e56131f 100644 --- a/src/components/canvas/contextmenu.tsx +++ b/src/components/canvas/contextmenu.tsx @@ -56,7 +56,7 @@ export const ContextmenuComponent = (props: ContextmenuProps) => { const _addCol = () => { const sheet = SHEET_SERVICE.getActiveSheet() const { - coodinate: {startCol: start}, + coordinate: {startCol: start}, } = startCell const blocks = _checkBlock() if (blocks.length !== 0) { @@ -83,7 +83,7 @@ export const ContextmenuComponent = (props: ContextmenuProps) => { const _removeCol = () => { const { - coodinate: {startCol: start}, + coordinate: {startCol: start}, } = startCell const sheet = SHEET_SERVICE.getActiveSheet() const blocks = _checkBlock() @@ -111,7 +111,7 @@ export const ContextmenuComponent = (props: ContextmenuProps) => { const _addRow = () => { const { - coodinate: {startRow: start}, + coordinate: {startRow: start}, } = startCell const sheet = SHEET_SERVICE.getActiveSheet() const blocks = _checkBlock() @@ -138,7 +138,7 @@ export const ContextmenuComponent = (props: ContextmenuProps) => { const _removeRow = () => { const { - coodinate: {startRow: start}, + coordinate: {startRow: start}, } = startCell const sheet = SHEET_SERVICE.getActiveSheet() const blocks = _checkBlock() @@ -165,8 +165,8 @@ export const ContextmenuComponent = (props: ContextmenuProps) => { const _addBlock = () => { const endCellTruthy = endCell ?? startCell - const start = startCell.coodinate - const end = endCellTruthy.coodinate + const start = startCell.coordinate + const end = endCellTruthy.coordinate const payload = new CreateBlockBuilder() .blockId(1) .sheetIdx(SHEET_SERVICE.getActiveSheet()) @@ -179,8 +179,8 @@ export const ContextmenuComponent = (props: ContextmenuProps) => { } const _checkBlock = () => { - const {coodinate: start} = startCell - const {coodinate: end} = endCell ?? startCell + const {coordinate: start} = startCell + const {coordinate: end} = endCell ?? startCell const curr = new Range() .setStartRow(start.startRow) .setStartCol(start.startCol) diff --git a/src/components/canvas/defs/cell.ts b/src/components/canvas/defs/cell.ts index 4409a361..a0386065 100644 --- a/src/components/canvas/defs/cell.ts +++ b/src/components/canvas/defs/cell.ts @@ -21,8 +21,8 @@ export class Cell extends RenderCell { export function visibleCells(cell: Cell, end: Cell, sheetSvc: SheetService) { const cells: {readonly row: number; readonly col: number}[] = [] - const {startCol, startRow} = cell.coodinate - const {endCol, endRow} = end.coodinate + const {startCol, startRow} = cell.coordinate + const {endCol, endRow} = end.coordinate for (let row = startRow; row <= endRow; row++) { for (let col = startCol; col < endCol; col++) { if (sheetSvc.getColInfo(col).hidden) continue diff --git a/src/components/canvas/index.tsx b/src/components/canvas/index.tsx index 74328a5e..f12dc635 100644 --- a/src/components/canvas/index.tsx +++ b/src/components/canvas/index.tsx @@ -1,7 +1,15 @@ import {SelectedCell} from './events' import {Subscription, Subject} from 'rxjs' import styles from './canvas.module.scss' -import {MouseEvent, ReactElement, useRef, useState, FC, WheelEvent} from 'react' +import { + MouseEvent, + ReactElement, + useRef, + useState, + FC, + WheelEvent, + KeyboardEvent, +} from 'react' import { useSelector, useStartCell, @@ -17,7 +25,7 @@ import { } from './widgets' import {Cell} from './defs' import {ScrollbarComponent} from '@/components/scrollbar' -import {EventType, on} from '@/core/events' +import {EventType, KeyboardEventCode, on} from '@/core/events' import {ContextmenuComponent} from './contextmenu' import {SelectorComponent} from '@/components/selector' import {ResizerComponent} from '@/components/resize' @@ -28,7 +36,13 @@ import {Buttons} from '@/core' import {CellInputBuilder} from '@/api' import {DialogComponent} from '@/ui/dialog' import {useInjection} from '@/core/ioc/provider' -import {Backend, DataService, SheetService} from '@/core/data' +import { + Backend, + DataService, + MAX_COUNT, + RenderCell, + SheetService, +} from '@/core/data' import {TYPES} from '@/core/ioc/types' export const OFFSET = 100 @@ -66,7 +80,7 @@ export const CanvasComponent: FC = ({selectedCell$}) => { textMng.startCellChange(e) if (e === undefined || e.same) return if (e?.cell?.type !== 'Cell') return - const {startRow: row, startCol: col} = e.cell.coodinate + const {startRow: row, startCol: col} = e.cell.coordinate selectedCell$({row, col}) } const startCellMng = useStartCell({startCellChange}) @@ -106,10 +120,11 @@ export const CanvasComponent: FC = ({selectedCell$}) => { startCellMng.scroll() } - const onMousedown = async (e: MouseEvent) => { + const onMouseDown = async (e: MouseEvent) => { e.stopPropagation() e.preventDefault() const mousedown = async () => { + canvasEl.current?.focus() const isBlur = await textMng.blur() if (!isBlur) { focus$.current.next() @@ -163,6 +178,76 @@ export const CanvasComponent: FC = ({selectedCell$}) => { ) } + const onKeyDown = async (e: KeyboardEvent) => { + e.stopPropagation() + e.preventDefault() + + if (e.metaKey || e.altKey || e.shiftKey || e.ctrlKey) return + + const currSelected = selectorMng.startCell + + if (!currSelected || currSelected.type != 'Cell') return + + const jumpTo = (row: number, col: number, keepHorizontal: boolean) => { + const result = DATA_SERVICE.tryJumpTo(row, col) + + if (result instanceof RenderCell) return result + + const scrollX = result[0] + const scrollY = result[1] + + const scroll = SHEET_SERVICE.getSheet()?.scroll + if (!scroll) throw Error('No active sheet') + + if (keepHorizontal) { + scroll.update('y', scrollY) + } else { + scroll.update('x', scrollX) + } + + renderMng.render() + const newResult = DATA_SERVICE.jumpTo(row, col) + if (!newResult) throw Error('jump to function error') + + return newResult + } + + const startRow = currSelected.coordinate.startRow + const startCol = currSelected.coordinate.startCol + const endRow = currSelected.coordinate.endRow + const endCol = currSelected.coordinate.endCol + + let renderCell: RenderCell | null = null + switch (e.key) { + case KeyboardEventCode.ARROW_UP: { + const newStartRow = Math.max(startRow - 1, 0) + renderCell = jumpTo(newStartRow, startCol, true) + break + } + case KeyboardEventCode.ARROW_DOWN: { + const newRow = Math.min(endRow + 1, MAX_COUNT) + renderCell = jumpTo(newRow, endCol, true) + break + } + case KeyboardEventCode.ARROW_LEFT: { + const newCol = Math.max(startCol - 1, 0) + renderCell = jumpTo(startRow, newCol, false) + break + } + case KeyboardEventCode.ARROW_RIGHT: { + const newCol = Math.min(endCol + 1, MAX_COUNT) + renderCell = jumpTo(startRow, newCol, false) + break + } + default: + return + } + if (renderCell) { + const c = new Cell('Cell').copyByRenderCell(renderCell) + startCellChange(new StartCellEvent(c, 'scroll')) + } + } + const blur = (e: BlurEvent) => { const oldText = textMng.context?.text ?? '' textMng.blur() @@ -171,8 +256,8 @@ export const CanvasComponent: FC = ({selectedCell$}) => { const newText = textMng.currText.current.trim() if (oldText === newText) return const payload = new CellInputBuilder() - .row(e.bindingData.coodinate.startRow) - .col(e.bindingData.coodinate.startCol) + .row(e.bindingData.coordinate.startRow) + .col(e.bindingData.coordinate.startCol) .sheetIdx(SHEET_SERVICE.getActiveSheet()) .input(newText) .build() @@ -209,13 +294,15 @@ export const CanvasComponent: FC = ({selectedCell$}) => { return (
onContextMenu(e)} - onMouseDown={onMousedown} + onMouseDown={onMouseDown} className={styles.host} > 你的浏览器不支持canvas,请升级浏览器 diff --git a/src/components/canvas/widgets/highlight-cell.ts b/src/components/canvas/widgets/highlight-cell.ts index a6ecf94c..d5dfb295 100644 --- a/src/components/canvas/widgets/highlight-cell.ts +++ b/src/components/canvas/widgets/highlight-cell.ts @@ -77,7 +77,7 @@ export const useHighlightCell = () => { .setStartRow(newCell.rowStart) .setEndCol(newCell.colEnd ?? newCell.colStart) .setEndRow(newCell.rowEnd ?? newCell.rowStart) - return c.coodinate.cover(range) + return c.coordinate.cover(range) }) if (!cell) return const oldCell = find(newCell) @@ -117,7 +117,7 @@ export const useHighlightCell = () => { .setStartCol(newCell.colStart) .setEndCol(newCell.colEnd ?? newCell.colStart) .setEndRow(newCell.rowEnd ?? newCell.rowStart) - return c.coodinate.cover(range) + return c.coordinate.cover(range) }) if (!cell) return const currColors = newCells.map((c) => c.style.bgColor) diff --git a/src/components/canvas/widgets/render.ts b/src/components/canvas/widgets/render.ts index 358fb63b..36cc88f6 100644 --- a/src/components/canvas/widgets/render.ts +++ b/src/components/canvas/widgets/render.ts @@ -55,7 +55,7 @@ export const useRender = ({canvas, rendered}: RenderProps) => { rendered() } const _renderCell = (renderCell: RenderCell) => { - const {coodinate: range, position} = renderCell + const {coordinate: range, position} = renderCell const style = sheetSvc.getCell(range.startRow, range.startCol)?.style const box = new Box() box.position = position @@ -90,7 +90,7 @@ export const useRender = ({canvas, rendered}: RenderProps) => { box.position = r.position const attr = new TextAttr() attr.font = SETTINGS.fixedHeader.font - const position = (r.coodinate.startRow + 1).toString() + const position = (r.coordinate.startRow + 1).toString() painterSvc.current.text(position, attr, box) }) painterSvc.current.restore() @@ -106,7 +106,7 @@ export const useRender = ({canvas, rendered}: RenderProps) => { [startCol, endRow], [startCol, startRow], ]) - const a1Notation = toA1notation(c.coodinate.startCol) + const a1Notation = toA1notation(c.coordinate.startCol) const box = new Box() box.position = c.position const attr = new TextAttr() diff --git a/src/components/canvas/widgets/resizer.ts b/src/components/canvas/widgets/resizer.ts index b30aa54c..4e1114f7 100644 --- a/src/components/canvas/widgets/resizer.ts +++ b/src/components/canvas/widgets/resizer.ts @@ -75,7 +75,7 @@ export const useResizers = (canvas: RefObject) => { if (!activeResizer) return false movingStart.current = {x: e.clientX, y: e.clientY} const sheet = SHEET_SERVICE - const {startCol, startRow} = activeResizer.leftCell.coodinate + const {startCol, startRow} = activeResizer.leftCell.coordinate const info = activeResizer.isRow ? sheet.getRowInfo(startCol) : sheet.getColInfo(startRow) @@ -114,7 +114,7 @@ export const useResizers = (canvas: RefObject) => { if (_active.current) { const { isRow, - leftCell: {coodinate, width, height}, + leftCell: {coordinate: coodinate, width, height}, } = _active.current const payload = !isRow ? new SetColWidthBuilder() diff --git a/src/components/canvas/widgets/selector.ts b/src/components/canvas/widgets/selector.ts index 71ac0b39..20889d36 100644 --- a/src/components/canvas/widgets/selector.ts +++ b/src/components/canvas/widgets/selector.ts @@ -119,6 +119,11 @@ export const useSelector = ({canvas, selectorChange}: UseSelectorProps) => { setStartCell(startCell) } + const selectCell = (cell: Cell) => { + setEndCell(undefined) + setStartCell(cell) + } + const startCellChange = (e?: StartCellEvent) => { if (e?.cell === undefined) { _setSelector() @@ -139,5 +144,6 @@ export const useSelector = ({canvas, selectorChange}: UseSelectorProps) => { onContextmenu, onMouseDown, onMouseMove, + selectCell, } } diff --git a/src/components/canvas/widgets/start-cell.ts b/src/components/canvas/widgets/start-cell.ts index 7849dde0..aaad298d 100644 --- a/src/components/canvas/widgets/start-cell.ts +++ b/src/components/canvas/widgets/start-cell.ts @@ -41,15 +41,15 @@ export const useStartCell = ({startCellChange}: StartCellProps) => { const viewRange = DATA_SERVICE.cachedViewRange if (oldStartCell.type === 'FixedLeftHeader') renderCell = viewRange.rows.find((r) => - r.coodinate.cover(oldStartCell.coodinate) + r.coordinate.cover(oldStartCell.coordinate) ) else if (oldStartCell.type === 'FixedTopHeader') renderCell = viewRange.cols.find((c) => - c.coodinate.cover(oldStartCell.coodinate) + c.coordinate.cover(oldStartCell.coordinate) ) else if (oldStartCell.type === 'Cell') renderCell = viewRange.cells.find((c) => - c.coodinate.cover(oldStartCell.coodinate) + c.coordinate.cover(oldStartCell.coordinate) ) else return if (!renderCell) { diff --git a/src/components/canvas/widgets/text.ts b/src/components/canvas/widgets/text.ts index 13df845c..f401d103 100644 --- a/src/components/canvas/widgets/text.ts +++ b/src/components/canvas/widgets/text.ts @@ -70,7 +70,7 @@ export const useText = ({canvas, onEdit}: TextProps) => { const { height, width, - coodinate: {startRow: row, startCol: col}, + coordinate: {startRow: row, startCol: col}, position: {startCol: x, startRow: y}, } = startCell const info = SHEET_SERVICE.getCell(row, col) diff --git a/src/components/canvas/widgets/useMatch.ts b/src/components/canvas/widgets/useMatch.ts index c7cdae3e..440bc0da 100644 --- a/src/components/canvas/widgets/useMatch.ts +++ b/src/components/canvas/widgets/useMatch.ts @@ -2,13 +2,9 @@ import {match} from '../defs' import {ViewRange} from '@/core/data' import {RefObject} from 'react' export const useMatch = (canvas: RefObject) => { - const _match = ( - clientX: number, - clientY: number, - {rows, cols, cells}: ViewRange - ) => { + const _match = (clientX: number, clientY: number, viewRange: ViewRange) => { if (!canvas.current) return - return match(clientX, clientY, canvas.current, {rows, cols, cells}) + return match(clientX, clientY, canvas.current, viewRange) } return { match: _match, diff --git a/src/components/content/index.tsx b/src/components/content/index.tsx index bb9640b7..a277f05b 100644 --- a/src/components/content/index.tsx +++ b/src/components/content/index.tsx @@ -1,5 +1,5 @@ import {SelectedCell, CanvasComponent} from '@/components/canvas' -import {FC} from 'react' +import {FC, useRef} from 'react' import styles from './content.module.scss' import {EditBarComponent} from './edit-bar' import {SheetsTabComponent} from '@/components/sheets-tab' diff --git a/src/core/data/service.ts b/src/core/data/service.ts index c75c2e9f..9dcfd33a 100644 --- a/src/core/data/service.ts +++ b/src/core/data/service.ts @@ -1,6 +1,6 @@ import {MAX_COUNT, SheetService} from './sheet' import {SETTINGS} from '@/core/settings' -import {RenderCell, ViewRange} from './view_range' +import {RenderCell, RenderCellSegment, ViewRange} from './view_range' import {Backend} from './backend' import {DisplayRequest} from '@/bindings' import {Range} from '@/core/standable' @@ -31,6 +31,54 @@ export class DataService { this.backend.send({$case: 'displayRequest', displayRequest: req}) } + /** + * Try to jump to the given cell. If the render cell is already in the view range, return it. + * If not, return the scroll where we can find this cell. + */ + tryJumpTo( + row: number, + col: number + ): RenderCell | readonly [number, number] { + const result = this.jumpTo(row, col) + if (result) return result + + const sheet = this.sheetSvc.getSheet() + + // This case should not happen. + if (!sheet) return this._viewRange.cells[0] + + // compute the new scroll + let scrollY = 0 + for (let i = 0; i < row - 1; i += 1) + scrollY += this.sheetSvc.getRowInfo(i).px ?? 0 + + let scrollX = 0 + for (let j = 0; j < col - 1; j += 1) + scrollX += this.sheetSvc.getColInfo(j).px ?? 0 + + return [scrollX, scrollY] + } + + jumpTo(row: number, col: number): RenderCell | null { + for (let i = 0; i < this._viewRange.cells.length; i += 1) { + const c = this._viewRange.cells[i] + if ( + c.cover( + new RenderCell().setCoordinate( + new Range() + .setStartRow(row) + .setEndRow(row) + .setStartCol(col) + .setEndCol(col) + ) + ) + ) { + return c + } + } + return null + } + /** * filter?, freeze, scroll, hidden */ @@ -38,11 +86,15 @@ export class DataService { const scroll = this._getScrollPosition() const rows = this._initRows(maxHeight, scroll.row) const cols = this._initCols(maxWidth, scroll.col) - const cells = this._initCells(rows, cols) + const cells = this._initCells(rows.cells, cols.cells) const viewRange = new ViewRange() - viewRange.rows = rows - viewRange.cols = cols + viewRange.rows = rows.cells + viewRange.cols = cols.cells viewRange.cells = cells + viewRange.fromRow = rows.from + viewRange.toRow = rows.to + viewRange.fromCol = cols.from + viewRange.toCol = cols.to this._viewRange = viewRange // console.log('init view range', viewRange) return this._viewRange @@ -86,8 +138,9 @@ export class DataService { const rows: RenderCell[] = [] const getHeight = (i: number) => this.sheetSvc.getRowInfo(i).px const staticWidth = SETTINGS.leftTop.width + let i = scrollY for ( - let i = scrollY, y = SETTINGS.leftTop.height; + let y = SETTINGS.leftTop.height; i <= MAX_COUNT && y <= maxHeight; i += 1 ) { @@ -106,15 +159,16 @@ export class DataService { rows.push(cell) y += height } - return rows + return new RenderCellSegment(scrollY, i, rows) } private _initCols(maxWidth: number, scrollX: number) { const cols: RenderCell[] = [] const getWidth = (i: number) => this.sheetSvc.getColInfo(i).px const staticHeight = SETTINGS.leftTop.height + let i = scrollX for ( - let i = scrollX, x = SETTINGS.leftTop.width; + let x = SETTINGS.leftTop.width; i <= MAX_COUNT && x <= maxWidth; i += 1 ) { @@ -133,7 +187,7 @@ export class DataService { cols.push(cell) x += width } - return cols + return new RenderCellSegment(scrollX, i, cols) } private _initCells( @@ -146,13 +200,13 @@ export class DataService { rows.forEach((startRow) => { // tslint:disable-next-line: max-func-body-length cols.forEach((startCol) => { - const key = `${startRow.coodinate.startRow}-${startCol.coodinate.startCol}` + const key = `${startRow.coordinate.startRow}-${startCol.coordinate.startCol}` if (renderedCells.has(key)) return const coordinate = new Range() - .setStartRow(startRow.coodinate.startRow) - .setEndRow(startRow.coodinate.endRow) - .setStartCol(startCol.coodinate.startCol) - .setEndCol(startCol.coodinate.endCol) + .setStartRow(startRow.coordinate.startRow) + .setEndRow(startRow.coordinate.endRow) + .setStartCol(startCol.coordinate.startCol) + .setEndCol(startCol.coordinate.endCol) const mCells = merges.filter((m) => m.cover(coordinate)) // no merge cell if (mCells.length === 0) { @@ -173,23 +227,23 @@ export class DataService { let endRow = startRow let endCol = startCol for ( - let mr = startRow.coodinate.startRow; + let mr = startRow.coordinate.startRow; mr <= cell.endRow; mr += 1 ) { const tmpRow = rows.find( - (r) => r.coodinate.startRow === mr + (r) => r.coordinate.startRow === mr ) if (!tmpRow) continue endRow = tmpRow } for ( - let mc = startCol.coodinate.startCol; + let mc = startCol.coordinate.startCol; mc <= cell.endCol; mc += 1 ) { const tmpCol = cols.find( - (c) => c.coodinate.startCol === mc + (c) => c.coordinate.startCol === mc ) if (!tmpCol) continue endCol = tmpCol @@ -204,20 +258,20 @@ export class DataService { ) .setCoordinate( new Range() - .setStartRow(startRow.coodinate.startRow) - .setStartCol(startCol.coodinate.startCol) - .setEndCol(endCol.coodinate.endCol) - .setEndRow(endRow.coodinate.endRow) + .setStartRow(startRow.coordinate.startRow) + .setStartCol(startCol.coordinate.startCol) + .setEndCol(endCol.coordinate.endCol) + .setEndRow(endRow.coordinate.endRow) ) cells.push(c) for ( - let i = c.coodinate.startRow; - i <= c.coodinate.endRow; + let i = c.coordinate.startRow; + i <= c.coordinate.endRow; i += 1 ) for ( - let j = c.coodinate.startCol; - j <= c.coodinate.endCol; + let j = c.coordinate.startCol; + j <= c.coordinate.endCol; j += 1 ) renderedCells.add(`${i}-${j}`) diff --git a/src/core/data/view_range.ts b/src/core/data/view_range.ts index 297d0423..e0e805cf 100644 --- a/src/core/data/view_range.ts +++ b/src/core/data/view_range.ts @@ -8,7 +8,7 @@ export class RenderCell { return this.position.height } setCoordinate(coordinate: Range) { - this.coodinate = coordinate + this.coordinate = coordinate return this } setPosition(position: Range) { @@ -18,13 +18,13 @@ export class RenderCell { /** * start/end row/col index */ - public coodinate = new Range() + public coordinate = new Range() /** * start/end row/col pixel distance */ public position = new Range() cover(cell: RenderCell) { - return this.coodinate.cover(cell.coodinate) + return this.coordinate.cover(cell.coordinate) } equals(cell: RenderCell) { @@ -32,7 +32,19 @@ export class RenderCell { } } +export class RenderCellSegment { + public constructor( + public readonly from: number, + public readonly to: number, + public cells: readonly RenderCell[] + ) {} +} + export class ViewRange { + public fromRow = 0 + public toRow = 0 + public fromCol = 0 + public toCol = 0 /** * visible rows. */ diff --git a/src/core/painter/box.ts b/src/core/painter/box.ts index 89e2d5d0..e56450a2 100644 --- a/src/core/painter/box.ts +++ b/src/core/painter/box.ts @@ -20,13 +20,13 @@ export class Box { let textAlign: CanvasTextAlign const {startCol: x} = this.position // set default to center - const alignX = align ?? 'Center' + const alignX = align ?? 'center' switch (alignX) { // default // 常规 // 居中 case 'general': - case 'Center': + case 'center': textAlign = 'center' tx = x + this.width / 2 break diff --git a/src/core/painter/painter.service.ts b/src/core/painter/painter.service.ts index 3509c444..aaa6d1eb 100644 --- a/src/core/painter/painter.service.ts +++ b/src/core/painter/painter.service.ts @@ -188,9 +188,9 @@ export class PainterService extends CanvasApi { let yOffset = 0 const lineAttr = new CanvasAttr() lineAttr.strokeStyle = attr.font.standardColor.css() - const horizontal = attr.alignment?.horizontal ?? 'General' - const underline = attr.font.underline?.val ?? 'None' - const vertical = attr.alignment?.vertical ?? 'Center' + const horizontal = attr.alignment?.horizontal ?? 'general' + const underline = attr.font.underline?.val ?? 'none' + const vertical = attr.alignment?.vertical ?? 'center' switch (underline) { case 'double': break