UPDATE: Download to PNG the visible part of the selected area
This downloads only the visible part of the selected area. To download the full selected area we would need to work a bit more
This commit is contained in:
committed by
Nicolás Hatcher Andrés
parent
ce7318840d
commit
eecf6f3c3b
@@ -20,6 +20,7 @@ import {
|
|||||||
Grid2X2,
|
Grid2X2,
|
||||||
Grid2x2Check,
|
Grid2x2Check,
|
||||||
Grid2x2X,
|
Grid2x2X,
|
||||||
|
ImageDown,
|
||||||
Italic,
|
Italic,
|
||||||
PaintBucket,
|
PaintBucket,
|
||||||
PaintRoller,
|
PaintRoller,
|
||||||
@@ -70,6 +71,7 @@ type ToolbarProperties = {
|
|||||||
onBorderChanged: (border: BorderOptions) => void;
|
onBorderChanged: (border: BorderOptions) => void;
|
||||||
onClearFormatting: () => void;
|
onClearFormatting: () => void;
|
||||||
onIncreaseFontSize: (delta: number) => void;
|
onIncreaseFontSize: (delta: number) => void;
|
||||||
|
onDownloadPNG: () => void;
|
||||||
fillColor: string;
|
fillColor: string;
|
||||||
fontColor: string;
|
fontColor: string;
|
||||||
bold: boolean;
|
bold: boolean;
|
||||||
@@ -399,6 +401,17 @@ function Toolbar(properties: ToolbarProperties) {
|
|||||||
>
|
>
|
||||||
<RemoveFormatting />
|
<RemoveFormatting />
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
|
<StyledButton
|
||||||
|
type="button"
|
||||||
|
$pressed={false}
|
||||||
|
disabled={!canEdit}
|
||||||
|
onClick={() => {
|
||||||
|
properties.onDownloadPNG();
|
||||||
|
}}
|
||||||
|
title={t("toolbar.selected_png")}
|
||||||
|
>
|
||||||
|
<ImageDown />
|
||||||
|
</StyledButton>
|
||||||
|
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
color={properties.fontColor}
|
color={properties.fontColor}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import {
|
|||||||
LAST_COLUMN,
|
LAST_COLUMN,
|
||||||
ROW_HEIGH_SCALE,
|
ROW_HEIGH_SCALE,
|
||||||
} from "../WorksheetCanvas/constants";
|
} from "../WorksheetCanvas/constants";
|
||||||
|
import type WorksheetCanvas from "../WorksheetCanvas/worksheetCanvas";
|
||||||
|
import { devicePixelRatio } from "../WorksheetCanvas/worksheetCanvas";
|
||||||
import {
|
import {
|
||||||
CLIPBOARD_ID_SESSION_STORAGE_KEY,
|
CLIPBOARD_ID_SESSION_STORAGE_KEY,
|
||||||
getNewClipboardId,
|
getNewClipboardId,
|
||||||
@@ -30,6 +32,9 @@ import useKeyboardNavigation from "./useKeyboardNavigation";
|
|||||||
const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
|
const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
|
||||||
const { model, workbookState } = props;
|
const { model, workbookState } = props;
|
||||||
const rootRef = useRef<HTMLDivElement | null>(null);
|
const rootRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const worksheetRef = useRef<{
|
||||||
|
getCanvas: () => WorksheetCanvas | null;
|
||||||
|
}>(null);
|
||||||
|
|
||||||
// Calling `setRedrawId((id) => id + 1);` forces a redraw
|
// Calling `setRedrawId((id) => id + 1);` forces a redraw
|
||||||
// This is needed because `model` or `workbookState` can change without React being aware of it
|
// This is needed because `model` or `workbookState` can change without React being aware of it
|
||||||
@@ -548,6 +553,59 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
|
|||||||
onIncreaseFontSize={(delta: number) => {
|
onIncreaseFontSize={(delta: number) => {
|
||||||
onIncreaseFontSize(delta);
|
onIncreaseFontSize(delta);
|
||||||
}}
|
}}
|
||||||
|
onDownloadPNG={() => {
|
||||||
|
// creates a new canvas element in the visible part of the the selected area
|
||||||
|
const worksheetCanvas = worksheetRef.current?.getCanvas();
|
||||||
|
if (!worksheetCanvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {
|
||||||
|
range: [rowStart, columnStart, rowEnd, columnEnd],
|
||||||
|
} = model.getSelectedView();
|
||||||
|
const { topLeftCell, bottomRightCell } =
|
||||||
|
worksheetCanvas.getVisibleCells();
|
||||||
|
const firstRow = Math.max(rowStart, topLeftCell.row);
|
||||||
|
const firstColumn = Math.max(columnStart, topLeftCell.column);
|
||||||
|
const lastRow = Math.min(rowEnd, bottomRightCell.row);
|
||||||
|
const lastColumn = Math.min(columnEnd, bottomRightCell.column);
|
||||||
|
let [x, y] = worksheetCanvas.getCoordinatesByCell(
|
||||||
|
firstRow,
|
||||||
|
firstColumn,
|
||||||
|
);
|
||||||
|
const [x1, y1] = worksheetCanvas.getCoordinatesByCell(
|
||||||
|
lastRow + 1,
|
||||||
|
lastColumn + 1,
|
||||||
|
);
|
||||||
|
const width = (x1 - x) * devicePixelRatio;
|
||||||
|
const height = (y1 - y) * devicePixelRatio;
|
||||||
|
x *= devicePixelRatio;
|
||||||
|
y *= devicePixelRatio;
|
||||||
|
|
||||||
|
const capturedCanvas = document.createElement("canvas");
|
||||||
|
capturedCanvas.width = width;
|
||||||
|
capturedCanvas.height = height;
|
||||||
|
const ctx = capturedCanvas.getContext("2d");
|
||||||
|
if (!ctx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.drawImage(
|
||||||
|
worksheetCanvas.canvas,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
);
|
||||||
|
|
||||||
|
const downloadLink = document.createElement("a");
|
||||||
|
downloadLink.href = capturedCanvas.toDataURL("image/png");
|
||||||
|
downloadLink.download = "ironcalc.png";
|
||||||
|
downloadLink.click();
|
||||||
|
}}
|
||||||
onBorderChanged={(border: BorderOptions): void => {
|
onBorderChanged={(border: BorderOptions): void => {
|
||||||
const {
|
const {
|
||||||
sheet,
|
sheet,
|
||||||
@@ -640,6 +698,7 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => {
|
|||||||
refresh={(): void => {
|
refresh={(): void => {
|
||||||
setRedrawId((id) => id + 1);
|
setRedrawId((id) => id + 1);
|
||||||
}}
|
}}
|
||||||
|
ref={worksheetRef}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SheetTabBar
|
<SheetTabBar
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import { type Model, columnNameFromNumber } from "@ironcalc/wasm";
|
import { type Model, columnNameFromNumber } from "@ironcalc/wasm";
|
||||||
import { styled } from "@mui/material/styles";
|
import { styled } from "@mui/material/styles";
|
||||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
import {
|
||||||
|
forwardRef,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useLayoutEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import Editor from "../Editor/Editor";
|
import Editor from "../Editor/Editor";
|
||||||
import {
|
import {
|
||||||
COLUMN_WIDTH_SCALE,
|
COLUMN_WIDTH_SCALE,
|
||||||
@@ -34,487 +41,497 @@ function useWindowSize() {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Worksheet(props: {
|
const Worksheet = forwardRef(
|
||||||
model: Model;
|
(
|
||||||
workbookState: WorkbookState;
|
props: {
|
||||||
refresh: () => void;
|
model: Model;
|
||||||
}) {
|
workbookState: WorkbookState;
|
||||||
const canvasElement = useRef<HTMLCanvasElement>(null);
|
refresh: () => void;
|
||||||
|
},
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
const canvasElement = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
const worksheetElement = useRef<HTMLDivElement>(null);
|
const worksheetElement = useRef<HTMLDivElement>(null);
|
||||||
const scrollElement = useRef<HTMLDivElement>(null);
|
const scrollElement = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const editorElement = useRef<HTMLDivElement>(null);
|
const editorElement = useRef<HTMLDivElement>(null);
|
||||||
const spacerElement = useRef<HTMLDivElement>(null);
|
const spacerElement = useRef<HTMLDivElement>(null);
|
||||||
const cellOutline = useRef<HTMLDivElement>(null);
|
const cellOutline = useRef<HTMLDivElement>(null);
|
||||||
const areaOutline = useRef<HTMLDivElement>(null);
|
const areaOutline = useRef<HTMLDivElement>(null);
|
||||||
const cellOutlineHandle = useRef<HTMLDivElement>(null);
|
const cellOutlineHandle = useRef<HTMLDivElement>(null);
|
||||||
const extendToOutline = useRef<HTMLDivElement>(null);
|
const extendToOutline = useRef<HTMLDivElement>(null);
|
||||||
const columnResizeGuide = useRef<HTMLDivElement>(null);
|
const columnResizeGuide = useRef<HTMLDivElement>(null);
|
||||||
const rowResizeGuide = useRef<HTMLDivElement>(null);
|
const rowResizeGuide = useRef<HTMLDivElement>(null);
|
||||||
const columnHeaders = useRef<HTMLDivElement>(null);
|
const columnHeaders = useRef<HTMLDivElement>(null);
|
||||||
const worksheetCanvas = useRef<WorksheetCanvas | null>(null);
|
const worksheetCanvas = useRef<WorksheetCanvas | null>(null);
|
||||||
|
|
||||||
const [contextMenuOpen, setContextMenuOpen] = useState(false);
|
const [contextMenuOpen, setContextMenuOpen] = useState(false);
|
||||||
|
|
||||||
const ignoreScrollEventRef = useRef(false);
|
const ignoreScrollEventRef = useRef(false);
|
||||||
|
|
||||||
const { model, workbookState, refresh } = props;
|
const { model, workbookState, refresh } = props;
|
||||||
const [clientWidth, clientHeight] = useWindowSize();
|
const [clientWidth, clientHeight] = useWindowSize();
|
||||||
|
|
||||||
useEffect(() => {
|
useImperativeHandle(ref, () => ({
|
||||||
const canvasRef = canvasElement.current;
|
getCanvas: () => worksheetCanvas.current,
|
||||||
const columnGuideRef = columnResizeGuide.current;
|
}));
|
||||||
const rowGuideRef = rowResizeGuide.current;
|
|
||||||
const columnHeadersRef = columnHeaders.current;
|
|
||||||
const worksheetRef = worksheetElement.current;
|
|
||||||
|
|
||||||
const outline = cellOutline.current;
|
useEffect(() => {
|
||||||
const handle = cellOutlineHandle.current;
|
const canvasRef = canvasElement.current;
|
||||||
const area = areaOutline.current;
|
const columnGuideRef = columnResizeGuide.current;
|
||||||
const extendTo = extendToOutline.current;
|
const rowGuideRef = rowResizeGuide.current;
|
||||||
const editor = editorElement.current;
|
const columnHeadersRef = columnHeaders.current;
|
||||||
|
const worksheetRef = worksheetElement.current;
|
||||||
|
|
||||||
if (
|
const outline = cellOutline.current;
|
||||||
!canvasRef ||
|
const handle = cellOutlineHandle.current;
|
||||||
!columnGuideRef ||
|
const area = areaOutline.current;
|
||||||
!rowGuideRef ||
|
const extendTo = extendToOutline.current;
|
||||||
!columnHeadersRef ||
|
const editor = editorElement.current;
|
||||||
!worksheetRef ||
|
|
||||||
!outline ||
|
if (
|
||||||
!handle ||
|
!canvasRef ||
|
||||||
!area ||
|
!columnGuideRef ||
|
||||||
!extendTo ||
|
!rowGuideRef ||
|
||||||
!scrollElement.current ||
|
!columnHeadersRef ||
|
||||||
!editor
|
!worksheetRef ||
|
||||||
)
|
!outline ||
|
||||||
return;
|
!handle ||
|
||||||
// FIXME: This two need to be computed.
|
!area ||
|
||||||
model.setWindowWidth(clientWidth - 37);
|
!extendTo ||
|
||||||
model.setWindowHeight(clientHeight - 190);
|
!scrollElement.current ||
|
||||||
const canvas = new WorksheetCanvas({
|
!editor
|
||||||
width: worksheetRef.clientWidth,
|
)
|
||||||
height: worksheetRef.clientHeight,
|
return;
|
||||||
model,
|
// FIXME: This two need to be computed.
|
||||||
workbookState,
|
model.setWindowWidth(clientWidth - 37);
|
||||||
elements: {
|
model.setWindowHeight(clientHeight - 190);
|
||||||
canvas: canvasRef,
|
const canvas = new WorksheetCanvas({
|
||||||
columnGuide: columnGuideRef,
|
width: worksheetRef.clientWidth,
|
||||||
rowGuide: rowGuideRef,
|
height: worksheetRef.clientHeight,
|
||||||
columnHeaders: columnHeadersRef,
|
model,
|
||||||
cellOutline: outline,
|
workbookState,
|
||||||
cellOutlineHandle: handle,
|
elements: {
|
||||||
areaOutline: area,
|
canvas: canvasRef,
|
||||||
extendToOutline: extendTo,
|
columnGuide: columnGuideRef,
|
||||||
editor: editor,
|
rowGuide: rowGuideRef,
|
||||||
},
|
columnHeaders: columnHeadersRef,
|
||||||
onColumnWidthChanges(sheet, column, width) {
|
cellOutline: outline,
|
||||||
if (width < 0) {
|
cellOutlineHandle: handle,
|
||||||
return;
|
areaOutline: area,
|
||||||
}
|
extendToOutline: extendTo,
|
||||||
const { range } = model.getSelectedView();
|
editor: editor,
|
||||||
let columnStart = column;
|
},
|
||||||
let columnEnd = column;
|
onColumnWidthChanges(sheet, column, width) {
|
||||||
const fullColumn = range[0] === 1 && range[2] === LAST_ROW;
|
if (width < 0) {
|
||||||
const fullRow = range[1] === 1 && range[3] === LAST_COLUMN;
|
return;
|
||||||
if (
|
}
|
||||||
fullColumn &&
|
const { range } = model.getSelectedView();
|
||||||
column >= range[1] &&
|
let columnStart = column;
|
||||||
column <= range[3] &&
|
let columnEnd = column;
|
||||||
!fullRow
|
const fullColumn = range[0] === 1 && range[2] === LAST_ROW;
|
||||||
) {
|
const fullRow = range[1] === 1 && range[3] === LAST_COLUMN;
|
||||||
columnStart = Math.min(range[1], column, range[3]);
|
if (
|
||||||
columnEnd = Math.max(range[1], column, range[3]);
|
fullColumn &&
|
||||||
}
|
column >= range[1] &&
|
||||||
model.setColumnsWidth(sheet, columnStart, columnEnd, width);
|
column <= range[3] &&
|
||||||
worksheetCanvas.current?.renderSheet();
|
!fullRow
|
||||||
},
|
) {
|
||||||
onRowHeightChanges(sheet, row, height) {
|
columnStart = Math.min(range[1], column, range[3]);
|
||||||
if (height < 0) {
|
columnEnd = Math.max(range[1], column, range[3]);
|
||||||
return;
|
}
|
||||||
}
|
model.setColumnsWidth(sheet, columnStart, columnEnd, width);
|
||||||
const { range } = model.getSelectedView();
|
worksheetCanvas.current?.renderSheet();
|
||||||
let rowStart = row;
|
},
|
||||||
let rowEnd = row;
|
onRowHeightChanges(sheet, row, height) {
|
||||||
const fullColumn = range[0] === 1 && range[2] === LAST_ROW;
|
if (height < 0) {
|
||||||
const fullRow = range[1] === 1 && range[3] === LAST_COLUMN;
|
return;
|
||||||
if (fullRow && row >= range[0] && row <= range[2] && !fullColumn) {
|
}
|
||||||
rowStart = Math.min(range[0], row, range[2]);
|
const { range } = model.getSelectedView();
|
||||||
rowEnd = Math.max(range[0], row, range[2]);
|
let rowStart = row;
|
||||||
}
|
let rowEnd = row;
|
||||||
model.setRowsHeight(sheet, rowStart, rowEnd, height);
|
const fullColumn = range[0] === 1 && range[2] === LAST_ROW;
|
||||||
worksheetCanvas.current?.renderSheet();
|
const fullRow = range[1] === 1 && range[3] === LAST_COLUMN;
|
||||||
},
|
if (fullRow && row >= range[0] && row <= range[2] && !fullColumn) {
|
||||||
refresh,
|
rowStart = Math.min(range[0], row, range[2]);
|
||||||
|
rowEnd = Math.max(range[0], row, range[2]);
|
||||||
|
}
|
||||||
|
model.setRowsHeight(sheet, rowStart, rowEnd, height);
|
||||||
|
worksheetCanvas.current?.renderSheet();
|
||||||
|
},
|
||||||
|
refresh,
|
||||||
|
});
|
||||||
|
const scrollX = model.getScrollX();
|
||||||
|
const scrollY = model.getScrollY();
|
||||||
|
const [sheetWidth, sheetHeight] = [scrollX + 100_000, scrollY + 500_000];
|
||||||
|
if (spacerElement.current) {
|
||||||
|
spacerElement.current.style.height = `${sheetHeight}px`;
|
||||||
|
spacerElement.current.style.width = `${sheetWidth}px`;
|
||||||
|
}
|
||||||
|
const left = scrollElement.current.scrollLeft;
|
||||||
|
const top = scrollElement.current.scrollTop;
|
||||||
|
if (scrollX !== left) {
|
||||||
|
ignoreScrollEventRef.current = true;
|
||||||
|
scrollElement.current.scrollLeft = scrollX;
|
||||||
|
setTimeout(() => {
|
||||||
|
ignoreScrollEventRef.current = false;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scrollY !== top) {
|
||||||
|
ignoreScrollEventRef.current = true;
|
||||||
|
scrollElement.current.scrollTop = scrollY;
|
||||||
|
setTimeout(() => {
|
||||||
|
ignoreScrollEventRef.current = false;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.renderSheet();
|
||||||
|
worksheetCanvas.current = canvas;
|
||||||
});
|
});
|
||||||
const scrollX = model.getScrollX();
|
|
||||||
const scrollY = model.getScrollY();
|
|
||||||
const [sheetWidth, sheetHeight] = [scrollX + 100_000, scrollY + 500_000];
|
|
||||||
if (spacerElement.current) {
|
|
||||||
spacerElement.current.style.height = `${sheetHeight}px`;
|
|
||||||
spacerElement.current.style.width = `${sheetWidth}px`;
|
|
||||||
}
|
|
||||||
const left = scrollElement.current.scrollLeft;
|
|
||||||
const top = scrollElement.current.scrollTop;
|
|
||||||
if (scrollX !== left) {
|
|
||||||
ignoreScrollEventRef.current = true;
|
|
||||||
scrollElement.current.scrollLeft = scrollX;
|
|
||||||
setTimeout(() => {
|
|
||||||
ignoreScrollEventRef.current = false;
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scrollY !== top) {
|
const { onPointerMove, onPointerDown, onPointerHandleDown, onPointerUp } =
|
||||||
ignoreScrollEventRef.current = true;
|
usePointer({
|
||||||
scrollElement.current.scrollTop = scrollY;
|
model,
|
||||||
setTimeout(() => {
|
workbookState,
|
||||||
ignoreScrollEventRef.current = false;
|
refresh,
|
||||||
}, 0);
|
onColumnSelected: (column: number, shift: boolean) => {
|
||||||
}
|
let firstColumn = column;
|
||||||
|
let lastColumn = column;
|
||||||
canvas.renderSheet();
|
if (shift) {
|
||||||
worksheetCanvas.current = canvas;
|
const { range } = model.getSelectedView();
|
||||||
});
|
firstColumn = Math.min(range[1], column, range[3]);
|
||||||
|
lastColumn = Math.max(range[3], column, range[1]);
|
||||||
const { onPointerMove, onPointerDown, onPointerHandleDown, onPointerUp } =
|
}
|
||||||
usePointer({
|
model.setSelectedCell(1, firstColumn);
|
||||||
model,
|
model.setSelectedRange(1, firstColumn, LAST_ROW, lastColumn);
|
||||||
workbookState,
|
refresh();
|
||||||
refresh,
|
},
|
||||||
onColumnSelected: (column: number, shift: boolean) => {
|
onRowSelected: (row: number, shift: boolean) => {
|
||||||
let firstColumn = column;
|
let firstRow = row;
|
||||||
let lastColumn = column;
|
let lastRow = row;
|
||||||
if (shift) {
|
if (shift) {
|
||||||
const { range } = model.getSelectedView();
|
const { range } = model.getSelectedView();
|
||||||
firstColumn = Math.min(range[1], column, range[3]);
|
firstRow = Math.min(range[0], row, range[2]);
|
||||||
lastColumn = Math.max(range[3], column, range[1]);
|
lastRow = Math.max(range[2], row, range[0]);
|
||||||
}
|
}
|
||||||
model.setSelectedCell(1, firstColumn);
|
model.setSelectedCell(firstRow, 1);
|
||||||
model.setSelectedRange(1, firstColumn, LAST_ROW, lastColumn);
|
model.setSelectedRange(firstRow, 1, lastRow, LAST_COLUMN);
|
||||||
refresh();
|
refresh();
|
||||||
},
|
},
|
||||||
onRowSelected: (row: number, shift: boolean) => {
|
onAllSheetSelected: () => {
|
||||||
let firstRow = row;
|
model.setSelectedCell(1, 1);
|
||||||
let lastRow = row;
|
model.setSelectedRange(1, 1, LAST_ROW, LAST_COLUMN);
|
||||||
if (shift) {
|
},
|
||||||
const { range } = model.getSelectedView();
|
onCellSelected: (cell: Cell, event: React.MouseEvent) => {
|
||||||
firstRow = Math.min(range[0], row, range[2]);
|
event.preventDefault();
|
||||||
lastRow = Math.max(range[2], row, range[0]);
|
event.stopPropagation();
|
||||||
}
|
model.setSelectedCell(cell.row, cell.column);
|
||||||
model.setSelectedCell(firstRow, 1);
|
refresh();
|
||||||
model.setSelectedRange(firstRow, 1, lastRow, LAST_COLUMN);
|
},
|
||||||
refresh();
|
onAreaSelecting: (cell: Cell) => {
|
||||||
},
|
|
||||||
onAllSheetSelected: () => {
|
|
||||||
model.setSelectedCell(1, 1);
|
|
||||||
model.setSelectedRange(1, 1, LAST_ROW, LAST_COLUMN);
|
|
||||||
},
|
|
||||||
onCellSelected: (cell: Cell, event: React.MouseEvent) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
model.setSelectedCell(cell.row, cell.column);
|
|
||||||
refresh();
|
|
||||||
},
|
|
||||||
onAreaSelecting: (cell: Cell) => {
|
|
||||||
const canvas = worksheetCanvas.current;
|
|
||||||
if (!canvas) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { row, column } = cell;
|
|
||||||
model.onAreaSelecting(row, column);
|
|
||||||
canvas.renderSheet();
|
|
||||||
refresh();
|
|
||||||
},
|
|
||||||
onAreaSelected: () => {
|
|
||||||
const styles = workbookState.getCopyStyles();
|
|
||||||
if (styles?.length) {
|
|
||||||
model.onPasteStyles(styles);
|
|
||||||
const canvas = worksheetCanvas.current;
|
const canvas = worksheetCanvas.current;
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const { row, column } = cell;
|
||||||
|
model.onAreaSelecting(row, column);
|
||||||
canvas.renderSheet();
|
canvas.renderSheet();
|
||||||
}
|
refresh();
|
||||||
workbookState.setCopyStyles(null);
|
},
|
||||||
if (worksheetElement.current) {
|
onAreaSelected: () => {
|
||||||
worksheetElement.current.style.cursor = "auto";
|
const styles = workbookState.getCopyStyles();
|
||||||
}
|
if (styles?.length) {
|
||||||
refresh();
|
model.onPasteStyles(styles);
|
||||||
},
|
const canvas = worksheetCanvas.current;
|
||||||
onExtendToCell: (cell) => {
|
if (!canvas) {
|
||||||
const canvas = worksheetCanvas.current;
|
return;
|
||||||
if (!canvas) {
|
}
|
||||||
return;
|
canvas.renderSheet();
|
||||||
}
|
|
||||||
const { row, column } = cell;
|
|
||||||
const {
|
|
||||||
range: [rowStart, columnStart, rowEnd, columnEnd],
|
|
||||||
} = model.getSelectedView();
|
|
||||||
// We are either extending by rows or by columns
|
|
||||||
// And we could be doing it in the positive direction (downwards or right)
|
|
||||||
// or the negative direction (upwards or left)
|
|
||||||
|
|
||||||
if (
|
|
||||||
row > rowEnd &&
|
|
||||||
((column <= columnEnd && column >= columnStart) ||
|
|
||||||
(column < columnStart && columnStart - column < row - rowEnd) ||
|
|
||||||
(column > columnEnd && column - columnEnd < row - rowEnd))
|
|
||||||
) {
|
|
||||||
// rows downwards
|
|
||||||
const area = {
|
|
||||||
type: AreaType.rowsDown,
|
|
||||||
rowStart: rowEnd + 1,
|
|
||||||
rowEnd: row,
|
|
||||||
columnStart,
|
|
||||||
columnEnd,
|
|
||||||
};
|
|
||||||
workbookState.setExtendToArea(area);
|
|
||||||
canvas.renderSheet();
|
|
||||||
} else if (
|
|
||||||
row < rowStart &&
|
|
||||||
((column <= columnEnd && column >= columnStart) ||
|
|
||||||
(column < columnStart && columnStart - column < rowStart - row) ||
|
|
||||||
(column > columnEnd && column - columnEnd < rowStart - row))
|
|
||||||
) {
|
|
||||||
// rows upwards
|
|
||||||
const area = {
|
|
||||||
type: AreaType.rowsUp,
|
|
||||||
rowStart: row,
|
|
||||||
rowEnd: rowStart,
|
|
||||||
columnStart,
|
|
||||||
columnEnd,
|
|
||||||
};
|
|
||||||
workbookState.setExtendToArea(area);
|
|
||||||
canvas.renderSheet();
|
|
||||||
} else if (
|
|
||||||
column > columnEnd &&
|
|
||||||
((row <= rowEnd && row >= rowStart) ||
|
|
||||||
(row < rowStart && rowStart - row < column - columnEnd) ||
|
|
||||||
(row > rowEnd && row - rowEnd < column - columnEnd))
|
|
||||||
) {
|
|
||||||
// columns right
|
|
||||||
const area = {
|
|
||||||
type: AreaType.columnsRight,
|
|
||||||
rowStart,
|
|
||||||
rowEnd,
|
|
||||||
columnStart: columnEnd + 1,
|
|
||||||
columnEnd: column,
|
|
||||||
};
|
|
||||||
workbookState.setExtendToArea(area);
|
|
||||||
canvas.renderSheet();
|
|
||||||
} else if (
|
|
||||||
column < columnStart &&
|
|
||||||
((row <= rowEnd && row >= rowStart) ||
|
|
||||||
(row < rowStart && rowStart - row < columnStart - column) ||
|
|
||||||
(row > rowEnd && row - rowEnd < columnStart - column))
|
|
||||||
) {
|
|
||||||
// columns left
|
|
||||||
const area = {
|
|
||||||
type: AreaType.columnsLeft,
|
|
||||||
rowStart,
|
|
||||||
rowEnd,
|
|
||||||
columnStart: column,
|
|
||||||
columnEnd: columnStart,
|
|
||||||
};
|
|
||||||
workbookState.setExtendToArea(area);
|
|
||||||
canvas.renderSheet();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onExtendToEnd: () => {
|
|
||||||
const canvas = worksheetCanvas.current;
|
|
||||||
if (!canvas) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { sheet, range } = model.getSelectedView();
|
|
||||||
const extendedArea = workbookState.getExtendToArea();
|
|
||||||
if (!extendedArea) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rowStart = Math.min(range[0], range[2]);
|
|
||||||
const height = Math.abs(range[2] - range[0]) + 1;
|
|
||||||
const width = Math.abs(range[3] - range[1]) + 1;
|
|
||||||
const columnStart = Math.min(range[1], range[3]);
|
|
||||||
|
|
||||||
const area = {
|
|
||||||
sheet,
|
|
||||||
row: rowStart,
|
|
||||||
column: columnStart,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (extendedArea.type) {
|
|
||||||
case AreaType.rowsDown:
|
|
||||||
model.autoFillRows(area, extendedArea.rowEnd);
|
|
||||||
break;
|
|
||||||
case AreaType.rowsUp: {
|
|
||||||
model.autoFillRows(area, extendedArea.rowStart);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case AreaType.columnsRight: {
|
workbookState.setCopyStyles(null);
|
||||||
model.autoFillColumns(area, extendedArea.columnEnd);
|
if (worksheetElement.current) {
|
||||||
break;
|
worksheetElement.current.style.cursor = "auto";
|
||||||
}
|
}
|
||||||
case AreaType.columnsLeft: {
|
refresh();
|
||||||
model.autoFillColumns(area, extendedArea.columnStart);
|
},
|
||||||
break;
|
onExtendToCell: (cell) => {
|
||||||
|
const canvas = worksheetCanvas.current;
|
||||||
|
if (!canvas) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
const { row, column } = cell;
|
||||||
model.setSelectedRange(
|
const {
|
||||||
Math.min(rowStart, extendedArea.rowStart),
|
range: [rowStart, columnStart, rowEnd, columnEnd],
|
||||||
Math.min(columnStart, extendedArea.columnStart),
|
} = model.getSelectedView();
|
||||||
Math.max(rowStart + height - 1, extendedArea.rowEnd),
|
// We are either extending by rows or by columns
|
||||||
Math.max(columnStart + width - 1, extendedArea.columnEnd),
|
// And we could be doing it in the positive direction (downwards or right)
|
||||||
);
|
// or the negative direction (upwards or left)
|
||||||
workbookState.clearExtendToArea();
|
|
||||||
canvas.renderSheet();
|
|
||||||
},
|
|
||||||
canvasElement,
|
|
||||||
worksheetElement,
|
|
||||||
worksheetCanvas,
|
|
||||||
});
|
|
||||||
|
|
||||||
const onScroll = (): void => {
|
if (
|
||||||
if (!scrollElement.current || !worksheetCanvas.current) {
|
row > rowEnd &&
|
||||||
return;
|
((column <= columnEnd && column >= columnStart) ||
|
||||||
}
|
(column < columnStart && columnStart - column < row - rowEnd) ||
|
||||||
if (ignoreScrollEventRef.current) {
|
(column > columnEnd && column - columnEnd < row - rowEnd))
|
||||||
// Programmatic scroll ignored
|
) {
|
||||||
return;
|
// rows downwards
|
||||||
}
|
const area = {
|
||||||
const left = scrollElement.current.scrollLeft;
|
type: AreaType.rowsDown,
|
||||||
const top = scrollElement.current.scrollTop;
|
rowStart: rowEnd + 1,
|
||||||
|
rowEnd: row,
|
||||||
|
columnStart,
|
||||||
|
columnEnd,
|
||||||
|
};
|
||||||
|
workbookState.setExtendToArea(area);
|
||||||
|
canvas.renderSheet();
|
||||||
|
} else if (
|
||||||
|
row < rowStart &&
|
||||||
|
((column <= columnEnd && column >= columnStart) ||
|
||||||
|
(column < columnStart && columnStart - column < rowStart - row) ||
|
||||||
|
(column > columnEnd && column - columnEnd < rowStart - row))
|
||||||
|
) {
|
||||||
|
// rows upwards
|
||||||
|
const area = {
|
||||||
|
type: AreaType.rowsUp,
|
||||||
|
rowStart: row,
|
||||||
|
rowEnd: rowStart,
|
||||||
|
columnStart,
|
||||||
|
columnEnd,
|
||||||
|
};
|
||||||
|
workbookState.setExtendToArea(area);
|
||||||
|
canvas.renderSheet();
|
||||||
|
} else if (
|
||||||
|
column > columnEnd &&
|
||||||
|
((row <= rowEnd && row >= rowStart) ||
|
||||||
|
(row < rowStart && rowStart - row < column - columnEnd) ||
|
||||||
|
(row > rowEnd && row - rowEnd < column - columnEnd))
|
||||||
|
) {
|
||||||
|
// columns right
|
||||||
|
const area = {
|
||||||
|
type: AreaType.columnsRight,
|
||||||
|
rowStart,
|
||||||
|
rowEnd,
|
||||||
|
columnStart: columnEnd + 1,
|
||||||
|
columnEnd: column,
|
||||||
|
};
|
||||||
|
workbookState.setExtendToArea(area);
|
||||||
|
canvas.renderSheet();
|
||||||
|
} else if (
|
||||||
|
column < columnStart &&
|
||||||
|
((row <= rowEnd && row >= rowStart) ||
|
||||||
|
(row < rowStart && rowStart - row < columnStart - column) ||
|
||||||
|
(row > rowEnd && row - rowEnd < columnStart - column))
|
||||||
|
) {
|
||||||
|
// columns left
|
||||||
|
const area = {
|
||||||
|
type: AreaType.columnsLeft,
|
||||||
|
rowStart,
|
||||||
|
rowEnd,
|
||||||
|
columnStart: column,
|
||||||
|
columnEnd: columnStart,
|
||||||
|
};
|
||||||
|
workbookState.setExtendToArea(area);
|
||||||
|
canvas.renderSheet();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onExtendToEnd: () => {
|
||||||
|
const canvas = worksheetCanvas.current;
|
||||||
|
if (!canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { sheet, range } = model.getSelectedView();
|
||||||
|
const extendedArea = workbookState.getExtendToArea();
|
||||||
|
if (!extendedArea) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rowStart = Math.min(range[0], range[2]);
|
||||||
|
const height = Math.abs(range[2] - range[0]) + 1;
|
||||||
|
const width = Math.abs(range[3] - range[1]) + 1;
|
||||||
|
const columnStart = Math.min(range[1], range[3]);
|
||||||
|
|
||||||
worksheetCanvas.current.setScrollPosition({ left, top });
|
const area = {
|
||||||
worksheetCanvas.current.renderSheet();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Wrapper ref={scrollElement} onScroll={onScroll} className="scroll">
|
|
||||||
<Spacer ref={spacerElement} />
|
|
||||||
<SheetContainer
|
|
||||||
className="sheet-container"
|
|
||||||
ref={worksheetElement}
|
|
||||||
onPointerDown={onPointerDown}
|
|
||||||
onPointerMove={onPointerMove}
|
|
||||||
onPointerUp={onPointerUp}
|
|
||||||
onContextMenu={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
setContextMenuOpen(true);
|
|
||||||
}}
|
|
||||||
onDoubleClick={(event) => {
|
|
||||||
// Starts editing cell
|
|
||||||
const { sheet, row, column } = model.getSelectedView();
|
|
||||||
const text = model.getCellContent(sheet, row, column);
|
|
||||||
const editorWidth =
|
|
||||||
model.getColumnWidth(sheet, column) * COLUMN_WIDTH_SCALE;
|
|
||||||
const editorHeight = model.getRowHeight(sheet, row) * ROW_HEIGH_SCALE;
|
|
||||||
workbookState.setEditingCell({
|
|
||||||
sheet,
|
sheet,
|
||||||
row,
|
row: rowStart,
|
||||||
column,
|
column: columnStart,
|
||||||
text,
|
width,
|
||||||
cursorStart: text.length,
|
height,
|
||||||
cursorEnd: text.length,
|
};
|
||||||
focus: "cell",
|
|
||||||
referencedRange: null,
|
switch (extendedArea.type) {
|
||||||
activeRanges: [],
|
case AreaType.rowsDown:
|
||||||
mode: "accept",
|
model.autoFillRows(area, extendedArea.rowEnd);
|
||||||
editorWidth,
|
break;
|
||||||
editorHeight,
|
case AreaType.rowsUp: {
|
||||||
});
|
model.autoFillRows(area, extendedArea.rowStart);
|
||||||
event.stopPropagation();
|
break;
|
||||||
// event.preventDefault();
|
}
|
||||||
props.refresh();
|
case AreaType.columnsRight: {
|
||||||
}}
|
model.autoFillColumns(area, extendedArea.columnEnd);
|
||||||
>
|
break;
|
||||||
<SheetCanvas ref={canvasElement} />
|
}
|
||||||
<CellOutline ref={cellOutline} />
|
case AreaType.columnsLeft: {
|
||||||
<EditorWrapper ref={editorElement}>
|
model.autoFillColumns(area, extendedArea.columnStart);
|
||||||
<Editor
|
break;
|
||||||
originalText={workbookState.getEditingText()}
|
}
|
||||||
onEditEnd={(): void => {
|
}
|
||||||
props.refresh();
|
model.setSelectedRange(
|
||||||
}}
|
Math.min(rowStart, extendedArea.rowStart),
|
||||||
onTextUpdated={(): void => {
|
Math.min(columnStart, extendedArea.columnStart),
|
||||||
props.refresh();
|
Math.max(rowStart + height - 1, extendedArea.rowEnd),
|
||||||
}}
|
Math.max(columnStart + width - 1, extendedArea.columnEnd),
|
||||||
model={model}
|
);
|
||||||
workbookState={workbookState}
|
workbookState.clearExtendToArea();
|
||||||
type={"cell"}
|
canvas.renderSheet();
|
||||||
|
},
|
||||||
|
canvasElement,
|
||||||
|
worksheetElement,
|
||||||
|
worksheetCanvas,
|
||||||
|
});
|
||||||
|
|
||||||
|
const onScroll = (): void => {
|
||||||
|
if (!scrollElement.current || !worksheetCanvas.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ignoreScrollEventRef.current) {
|
||||||
|
// Programmatic scroll ignored
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const left = scrollElement.current.scrollLeft;
|
||||||
|
const top = scrollElement.current.scrollTop;
|
||||||
|
|
||||||
|
worksheetCanvas.current.setScrollPosition({ left, top });
|
||||||
|
worksheetCanvas.current.renderSheet();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper ref={scrollElement} onScroll={onScroll} className="scroll">
|
||||||
|
<Spacer ref={spacerElement} />
|
||||||
|
<SheetContainer
|
||||||
|
className="sheet-container"
|
||||||
|
ref={worksheetElement}
|
||||||
|
onPointerDown={onPointerDown}
|
||||||
|
onPointerMove={onPointerMove}
|
||||||
|
onPointerUp={onPointerUp}
|
||||||
|
onContextMenu={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
setContextMenuOpen(true);
|
||||||
|
}}
|
||||||
|
onDoubleClick={(event) => {
|
||||||
|
// Starts editing cell
|
||||||
|
const { sheet, row, column } = model.getSelectedView();
|
||||||
|
const text = model.getCellContent(sheet, row, column);
|
||||||
|
const editorWidth =
|
||||||
|
model.getColumnWidth(sheet, column) * COLUMN_WIDTH_SCALE;
|
||||||
|
const editorHeight =
|
||||||
|
model.getRowHeight(sheet, row) * ROW_HEIGH_SCALE;
|
||||||
|
workbookState.setEditingCell({
|
||||||
|
sheet,
|
||||||
|
row,
|
||||||
|
column,
|
||||||
|
text,
|
||||||
|
cursorStart: text.length,
|
||||||
|
cursorEnd: text.length,
|
||||||
|
focus: "cell",
|
||||||
|
referencedRange: null,
|
||||||
|
activeRanges: [],
|
||||||
|
mode: "accept",
|
||||||
|
editorWidth,
|
||||||
|
editorHeight,
|
||||||
|
});
|
||||||
|
event.stopPropagation();
|
||||||
|
// event.preventDefault();
|
||||||
|
props.refresh();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SheetCanvas ref={canvasElement} />
|
||||||
|
<CellOutline ref={cellOutline} />
|
||||||
|
<EditorWrapper ref={editorElement}>
|
||||||
|
<Editor
|
||||||
|
originalText={workbookState.getEditingText()}
|
||||||
|
onEditEnd={(): void => {
|
||||||
|
props.refresh();
|
||||||
|
}}
|
||||||
|
onTextUpdated={(): void => {
|
||||||
|
props.refresh();
|
||||||
|
}}
|
||||||
|
model={model}
|
||||||
|
workbookState={workbookState}
|
||||||
|
type={"cell"}
|
||||||
|
/>
|
||||||
|
</EditorWrapper>
|
||||||
|
<AreaOutline ref={areaOutline} />
|
||||||
|
<ExtendToOutline ref={extendToOutline} />
|
||||||
|
<CellOutlineHandle
|
||||||
|
ref={cellOutlineHandle}
|
||||||
|
onPointerDown={onPointerHandleDown}
|
||||||
/>
|
/>
|
||||||
</EditorWrapper>
|
<ColumnResizeGuide ref={columnResizeGuide} />
|
||||||
<AreaOutline ref={areaOutline} />
|
<RowResizeGuide ref={rowResizeGuide} />
|
||||||
<ExtendToOutline ref={extendToOutline} />
|
<ColumnHeaders ref={columnHeaders} />
|
||||||
<CellOutlineHandle
|
</SheetContainer>
|
||||||
ref={cellOutlineHandle}
|
<CellContextMenu
|
||||||
onPointerDown={onPointerHandleDown}
|
open={contextMenuOpen}
|
||||||
|
onClose={() => setContextMenuOpen(false)}
|
||||||
|
anchorEl={cellOutline.current}
|
||||||
|
onInsertRowAbove={(): void => {
|
||||||
|
const view = model.getSelectedView();
|
||||||
|
model.insertRow(view.sheet, view.row);
|
||||||
|
setContextMenuOpen(false);
|
||||||
|
}}
|
||||||
|
onInsertRowBelow={(): void => {
|
||||||
|
const view = model.getSelectedView();
|
||||||
|
model.insertRow(view.sheet, view.row + 1);
|
||||||
|
setContextMenuOpen(false);
|
||||||
|
}}
|
||||||
|
onInsertColumnLeft={(): void => {
|
||||||
|
const view = model.getSelectedView();
|
||||||
|
model.insertColumn(view.sheet, view.column);
|
||||||
|
setContextMenuOpen(false);
|
||||||
|
}}
|
||||||
|
onInsertColumnRight={(): void => {
|
||||||
|
const view = model.getSelectedView();
|
||||||
|
model.insertColumn(view.sheet, view.column + 1);
|
||||||
|
setContextMenuOpen(false);
|
||||||
|
}}
|
||||||
|
onFreezeColumns={(): void => {
|
||||||
|
const view = model.getSelectedView();
|
||||||
|
model.setFrozenColumnsCount(view.sheet, view.column);
|
||||||
|
setContextMenuOpen(false);
|
||||||
|
}}
|
||||||
|
onFreezeRows={(): void => {
|
||||||
|
const view = model.getSelectedView();
|
||||||
|
model.setFrozenRowsCount(view.sheet, view.row);
|
||||||
|
setContextMenuOpen(false);
|
||||||
|
}}
|
||||||
|
onUnfreezeColumns={(): void => {
|
||||||
|
const sheet = model.getSelectedSheet();
|
||||||
|
model.setFrozenColumnsCount(sheet, 0);
|
||||||
|
setContextMenuOpen(false);
|
||||||
|
}}
|
||||||
|
onUnfreezeRows={(): void => {
|
||||||
|
const sheet = model.getSelectedSheet();
|
||||||
|
model.setFrozenRowsCount(sheet, 0);
|
||||||
|
setContextMenuOpen(false);
|
||||||
|
}}
|
||||||
|
onDeleteRow={(): void => {
|
||||||
|
const view = model.getSelectedView();
|
||||||
|
model.deleteRow(view.sheet, view.row);
|
||||||
|
setContextMenuOpen(false);
|
||||||
|
}}
|
||||||
|
onDeleteColumn={(): void => {
|
||||||
|
const view = model.getSelectedView();
|
||||||
|
model.deleteColumn(view.sheet, view.column);
|
||||||
|
setContextMenuOpen(false);
|
||||||
|
}}
|
||||||
|
row={model.getSelectedView().row}
|
||||||
|
column={columnNameFromNumber(model.getSelectedView().column)}
|
||||||
/>
|
/>
|
||||||
<ColumnResizeGuide ref={columnResizeGuide} />
|
</Wrapper>
|
||||||
<RowResizeGuide ref={rowResizeGuide} />
|
);
|
||||||
<ColumnHeaders ref={columnHeaders} />
|
},
|
||||||
</SheetContainer>
|
);
|
||||||
<CellContextMenu
|
|
||||||
open={contextMenuOpen}
|
|
||||||
onClose={() => setContextMenuOpen(false)}
|
|
||||||
anchorEl={cellOutline.current}
|
|
||||||
onInsertRowAbove={(): void => {
|
|
||||||
const view = model.getSelectedView();
|
|
||||||
model.insertRow(view.sheet, view.row);
|
|
||||||
setContextMenuOpen(false);
|
|
||||||
}}
|
|
||||||
onInsertRowBelow={(): void => {
|
|
||||||
const view = model.getSelectedView();
|
|
||||||
model.insertRow(view.sheet, view.row + 1);
|
|
||||||
setContextMenuOpen(false);
|
|
||||||
}}
|
|
||||||
onInsertColumnLeft={(): void => {
|
|
||||||
const view = model.getSelectedView();
|
|
||||||
model.insertColumn(view.sheet, view.column);
|
|
||||||
setContextMenuOpen(false);
|
|
||||||
}}
|
|
||||||
onInsertColumnRight={(): void => {
|
|
||||||
const view = model.getSelectedView();
|
|
||||||
model.insertColumn(view.sheet, view.column + 1);
|
|
||||||
setContextMenuOpen(false);
|
|
||||||
}}
|
|
||||||
onFreezeColumns={(): void => {
|
|
||||||
const view = model.getSelectedView();
|
|
||||||
model.setFrozenColumnsCount(view.sheet, view.column);
|
|
||||||
setContextMenuOpen(false);
|
|
||||||
}}
|
|
||||||
onFreezeRows={(): void => {
|
|
||||||
const view = model.getSelectedView();
|
|
||||||
model.setFrozenRowsCount(view.sheet, view.row);
|
|
||||||
setContextMenuOpen(false);
|
|
||||||
}}
|
|
||||||
onUnfreezeColumns={(): void => {
|
|
||||||
const sheet = model.getSelectedSheet();
|
|
||||||
model.setFrozenColumnsCount(sheet, 0);
|
|
||||||
setContextMenuOpen(false);
|
|
||||||
}}
|
|
||||||
onUnfreezeRows={(): void => {
|
|
||||||
const sheet = model.getSelectedSheet();
|
|
||||||
model.setFrozenRowsCount(sheet, 0);
|
|
||||||
setContextMenuOpen(false);
|
|
||||||
}}
|
|
||||||
onDeleteRow={(): void => {
|
|
||||||
const view = model.getSelectedView();
|
|
||||||
model.deleteRow(view.sheet, view.row);
|
|
||||||
setContextMenuOpen(false);
|
|
||||||
}}
|
|
||||||
onDeleteColumn={(): void => {
|
|
||||||
const view = model.getSelectedView();
|
|
||||||
model.deleteColumn(view.sheet, view.column);
|
|
||||||
setContextMenuOpen(false);
|
|
||||||
}}
|
|
||||||
row={model.getSelectedView().row}
|
|
||||||
column={columnNameFromNumber(model.getSelectedView().column)}
|
|
||||||
/>
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Spacer = styled("div")`
|
const Spacer = styled("div")`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"vertical_align_bottom": "Align bottom",
|
"vertical_align_bottom": "Align bottom",
|
||||||
"vertical_align_middle": " Align middle",
|
"vertical_align_middle": " Align middle",
|
||||||
"vertical_align_top": "Align top",
|
"vertical_align_top": "Align top",
|
||||||
|
"selected_png": "Export Selected area as PNG",
|
||||||
"format_menu": {
|
"format_menu": {
|
||||||
"auto": "Auto",
|
"auto": "Auto",
|
||||||
"number": "Number",
|
"number": "Number",
|
||||||
|
|||||||
18
webapp/app.ironcalc.com/frontend/package-lock.json
generated
18
webapp/app.ironcalc.com/frontend/package-lock.json
generated
@@ -43,21 +43,21 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@chromatic-com/storybook": "^3.2.4",
|
"@chromatic-com/storybook": "^3.2.4",
|
||||||
"@storybook/addon-essentials": "^8.5.3",
|
"@storybook/addon-essentials": "^8.6.0",
|
||||||
"@storybook/addon-interactions": "^8.5.3",
|
"@storybook/addon-interactions": "^8.6.0",
|
||||||
"@storybook/blocks": "^8.5.3",
|
"@storybook/blocks": "^8.6.0",
|
||||||
"@storybook/react": "^8.5.3",
|
"@storybook/react": "^8.6.0",
|
||||||
"@storybook/react-vite": "^8.5.3",
|
"@storybook/react-vite": "^8.6.0",
|
||||||
"@storybook/test": "^8.5.3",
|
"@storybook/test": "^8.6.0",
|
||||||
"@vitejs/plugin-react": "^4.2.1",
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"storybook": "^8.5.3",
|
"storybook": "^8.6.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"vite": "^6.0.5",
|
"vite": "^6.2.0",
|
||||||
"vite-plugin-svgr": "^4.2.0",
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
"vitest": "^2.0.5"
|
"vitest": "^3.0.7"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^18.0.0 || ^19.0.0",
|
"@types/react": "^18.0.0 || ^19.0.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user