diff --git a/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx b/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx index e82877d..f572eae 100644 --- a/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx +++ b/webapp/IronCalc/src/components/Toolbar/Toolbar.tsx @@ -20,6 +20,7 @@ import { Grid2X2, Grid2x2Check, Grid2x2X, + ImageDown, Italic, PaintBucket, PaintRoller, @@ -70,6 +71,7 @@ type ToolbarProperties = { onBorderChanged: (border: BorderOptions) => void; onClearFormatting: () => void; onIncreaseFontSize: (delta: number) => void; + onDownloadPNG: () => void; fillColor: string; fontColor: string; bold: boolean; @@ -399,6 +401,17 @@ function Toolbar(properties: ToolbarProperties) { > + { + properties.onDownloadPNG(); + }} + title={t("toolbar.selected_png")} + > + + { const { model, workbookState } = props; const rootRef = useRef(null); + const worksheetRef = useRef<{ + getCanvas: () => WorksheetCanvas | null; + }>(null); // Calling `setRedrawId((id) => id + 1);` forces a redraw // 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); }} + 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 => { const { sheet, @@ -640,6 +698,7 @@ const Workbook = (props: { model: Model; workbookState: WorkbookState }) => { refresh={(): void => { setRedrawId((id) => id + 1); }} + ref={worksheetRef} /> void; -}) { - const canvasElement = useRef(null); +const Worksheet = forwardRef( + ( + props: { + model: Model; + workbookState: WorkbookState; + refresh: () => void; + }, + ref, + ) => { + const canvasElement = useRef(null); - const worksheetElement = useRef(null); - const scrollElement = useRef(null); + const worksheetElement = useRef(null); + const scrollElement = useRef(null); - const editorElement = useRef(null); - const spacerElement = useRef(null); - const cellOutline = useRef(null); - const areaOutline = useRef(null); - const cellOutlineHandle = useRef(null); - const extendToOutline = useRef(null); - const columnResizeGuide = useRef(null); - const rowResizeGuide = useRef(null); - const columnHeaders = useRef(null); - const worksheetCanvas = useRef(null); + const editorElement = useRef(null); + const spacerElement = useRef(null); + const cellOutline = useRef(null); + const areaOutline = useRef(null); + const cellOutlineHandle = useRef(null); + const extendToOutline = useRef(null); + const columnResizeGuide = useRef(null); + const rowResizeGuide = useRef(null); + const columnHeaders = useRef(null); + const worksheetCanvas = useRef(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 [clientWidth, clientHeight] = useWindowSize(); + const { model, workbookState, refresh } = props; + const [clientWidth, clientHeight] = useWindowSize(); - useEffect(() => { - const canvasRef = canvasElement.current; - const columnGuideRef = columnResizeGuide.current; - const rowGuideRef = rowResizeGuide.current; - const columnHeadersRef = columnHeaders.current; - const worksheetRef = worksheetElement.current; + useImperativeHandle(ref, () => ({ + getCanvas: () => worksheetCanvas.current, + })); - const outline = cellOutline.current; - const handle = cellOutlineHandle.current; - const area = areaOutline.current; - const extendTo = extendToOutline.current; - const editor = editorElement.current; + useEffect(() => { + const canvasRef = canvasElement.current; + const columnGuideRef = columnResizeGuide.current; + const rowGuideRef = rowResizeGuide.current; + const columnHeadersRef = columnHeaders.current; + const worksheetRef = worksheetElement.current; - if ( - !canvasRef || - !columnGuideRef || - !rowGuideRef || - !columnHeadersRef || - !worksheetRef || - !outline || - !handle || - !area || - !extendTo || - !scrollElement.current || - !editor - ) - return; - // FIXME: This two need to be computed. - model.setWindowWidth(clientWidth - 37); - model.setWindowHeight(clientHeight - 190); - const canvas = new WorksheetCanvas({ - width: worksheetRef.clientWidth, - height: worksheetRef.clientHeight, - model, - workbookState, - elements: { - canvas: canvasRef, - columnGuide: columnGuideRef, - rowGuide: rowGuideRef, - columnHeaders: columnHeadersRef, - cellOutline: outline, - cellOutlineHandle: handle, - areaOutline: area, - extendToOutline: extendTo, - editor: editor, - }, - onColumnWidthChanges(sheet, column, width) { - if (width < 0) { - return; - } - const { range } = model.getSelectedView(); - let columnStart = column; - let columnEnd = column; - const fullColumn = range[0] === 1 && range[2] === LAST_ROW; - const fullRow = range[1] === 1 && range[3] === LAST_COLUMN; - if ( - fullColumn && - column >= range[1] && - column <= range[3] && - !fullRow - ) { - columnStart = Math.min(range[1], column, range[3]); - columnEnd = Math.max(range[1], column, range[3]); - } - model.setColumnsWidth(sheet, columnStart, columnEnd, width); - worksheetCanvas.current?.renderSheet(); - }, - onRowHeightChanges(sheet, row, height) { - if (height < 0) { - return; - } - const { range } = model.getSelectedView(); - let rowStart = row; - let rowEnd = row; - const fullColumn = range[0] === 1 && range[2] === LAST_ROW; - const fullRow = range[1] === 1 && range[3] === LAST_COLUMN; - if (fullRow && row >= range[0] && row <= range[2] && !fullColumn) { - 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 outline = cellOutline.current; + const handle = cellOutlineHandle.current; + const area = areaOutline.current; + const extendTo = extendToOutline.current; + const editor = editorElement.current; + + if ( + !canvasRef || + !columnGuideRef || + !rowGuideRef || + !columnHeadersRef || + !worksheetRef || + !outline || + !handle || + !area || + !extendTo || + !scrollElement.current || + !editor + ) + return; + // FIXME: This two need to be computed. + model.setWindowWidth(clientWidth - 37); + model.setWindowHeight(clientHeight - 190); + const canvas = new WorksheetCanvas({ + width: worksheetRef.clientWidth, + height: worksheetRef.clientHeight, + model, + workbookState, + elements: { + canvas: canvasRef, + columnGuide: columnGuideRef, + rowGuide: rowGuideRef, + columnHeaders: columnHeadersRef, + cellOutline: outline, + cellOutlineHandle: handle, + areaOutline: area, + extendToOutline: extendTo, + editor: editor, + }, + onColumnWidthChanges(sheet, column, width) { + if (width < 0) { + return; + } + const { range } = model.getSelectedView(); + let columnStart = column; + let columnEnd = column; + const fullColumn = range[0] === 1 && range[2] === LAST_ROW; + const fullRow = range[1] === 1 && range[3] === LAST_COLUMN; + if ( + fullColumn && + column >= range[1] && + column <= range[3] && + !fullRow + ) { + columnStart = Math.min(range[1], column, range[3]); + columnEnd = Math.max(range[1], column, range[3]); + } + model.setColumnsWidth(sheet, columnStart, columnEnd, width); + worksheetCanvas.current?.renderSheet(); + }, + onRowHeightChanges(sheet, row, height) { + if (height < 0) { + return; + } + const { range } = model.getSelectedView(); + let rowStart = row; + let rowEnd = row; + const fullColumn = range[0] === 1 && range[2] === LAST_ROW; + const fullRow = range[1] === 1 && range[3] === LAST_COLUMN; + if (fullRow && row >= range[0] && row <= range[2] && !fullColumn) { + 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) { - ignoreScrollEventRef.current = true; - scrollElement.current.scrollTop = scrollY; - setTimeout(() => { - ignoreScrollEventRef.current = false; - }, 0); - } - - canvas.renderSheet(); - worksheetCanvas.current = canvas; - }); - - const { onPointerMove, onPointerDown, onPointerHandleDown, onPointerUp } = - usePointer({ - model, - workbookState, - refresh, - onColumnSelected: (column: number, shift: boolean) => { - let firstColumn = column; - let lastColumn = column; - if (shift) { - const { range } = model.getSelectedView(); - firstColumn = Math.min(range[1], column, range[3]); - lastColumn = Math.max(range[3], column, range[1]); - } - model.setSelectedCell(1, firstColumn); - model.setSelectedRange(1, firstColumn, LAST_ROW, lastColumn); - refresh(); - }, - onRowSelected: (row: number, shift: boolean) => { - let firstRow = row; - let lastRow = row; - if (shift) { - const { range } = model.getSelectedView(); - firstRow = Math.min(range[0], row, range[2]); - lastRow = Math.max(range[2], row, range[0]); - } - model.setSelectedCell(firstRow, 1); - model.setSelectedRange(firstRow, 1, lastRow, LAST_COLUMN); - refresh(); - }, - 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 { onPointerMove, onPointerDown, onPointerHandleDown, onPointerUp } = + usePointer({ + model, + workbookState, + refresh, + onColumnSelected: (column: number, shift: boolean) => { + let firstColumn = column; + let lastColumn = column; + if (shift) { + const { range } = model.getSelectedView(); + firstColumn = Math.min(range[1], column, range[3]); + lastColumn = Math.max(range[3], column, range[1]); + } + model.setSelectedCell(1, firstColumn); + model.setSelectedRange(1, firstColumn, LAST_ROW, lastColumn); + refresh(); + }, + onRowSelected: (row: number, shift: boolean) => { + let firstRow = row; + let lastRow = row; + if (shift) { + const { range } = model.getSelectedView(); + firstRow = Math.min(range[0], row, range[2]); + lastRow = Math.max(range[2], row, range[0]); + } + model.setSelectedCell(firstRow, 1); + model.setSelectedRange(firstRow, 1, lastRow, LAST_COLUMN); + refresh(); + }, + 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(); - } - workbookState.setCopyStyles(null); - if (worksheetElement.current) { - worksheetElement.current.style.cursor = "auto"; - } - refresh(); - }, - onExtendToCell: (cell) => { - const canvas = worksheetCanvas.current; - if (!canvas) { - return; - } - 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; + refresh(); + }, + onAreaSelected: () => { + const styles = workbookState.getCopyStyles(); + if (styles?.length) { + model.onPasteStyles(styles); + const canvas = worksheetCanvas.current; + if (!canvas) { + return; + } + canvas.renderSheet(); } - case AreaType.columnsRight: { - model.autoFillColumns(area, extendedArea.columnEnd); - break; + workbookState.setCopyStyles(null); + if (worksheetElement.current) { + worksheetElement.current.style.cursor = "auto"; } - case AreaType.columnsLeft: { - model.autoFillColumns(area, extendedArea.columnStart); - break; + refresh(); + }, + onExtendToCell: (cell) => { + const canvas = worksheetCanvas.current; + if (!canvas) { + return; } - } - model.setSelectedRange( - Math.min(rowStart, extendedArea.rowStart), - Math.min(columnStart, extendedArea.columnStart), - Math.max(rowStart + height - 1, extendedArea.rowEnd), - Math.max(columnStart + width - 1, extendedArea.columnEnd), - ); - workbookState.clearExtendToArea(); - canvas.renderSheet(); - }, - canvasElement, - worksheetElement, - worksheetCanvas, - }); + 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) - 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; + 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]); - worksheetCanvas.current.setScrollPosition({ left, top }); - worksheetCanvas.current.renderSheet(); - }; - - return ( - - - { - 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({ + const area = { 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(); - }} - > - - - - { - props.refresh(); - }} - onTextUpdated={(): void => { - props.refresh(); - }} - model={model} - workbookState={workbookState} - type={"cell"} + 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: { + model.autoFillColumns(area, extendedArea.columnEnd); + break; + } + case AreaType.columnsLeft: { + model.autoFillColumns(area, extendedArea.columnStart); + break; + } + } + model.setSelectedRange( + Math.min(rowStart, extendedArea.rowStart), + Math.min(columnStart, extendedArea.columnStart), + Math.max(rowStart + height - 1, extendedArea.rowEnd), + Math.max(columnStart + width - 1, extendedArea.columnEnd), + ); + workbookState.clearExtendToArea(); + 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 ( + + + { + 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(); + }} + > + + + + { + props.refresh(); + }} + onTextUpdated={(): void => { + props.refresh(); + }} + model={model} + workbookState={workbookState} + type={"cell"} + /> + + + + - - - - + + + + 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)} /> - - - - - 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)} - /> - - ); -} + + ); + }, +); const Spacer = styled("div")` position: absolute; diff --git a/webapp/IronCalc/src/locale/en_us.json b/webapp/IronCalc/src/locale/en_us.json index dc35972..60ae6b9 100644 --- a/webapp/IronCalc/src/locale/en_us.json +++ b/webapp/IronCalc/src/locale/en_us.json @@ -25,6 +25,7 @@ "vertical_align_bottom": "Align bottom", "vertical_align_middle": " Align middle", "vertical_align_top": "Align top", + "selected_png": "Export Selected area as PNG", "format_menu": { "auto": "Auto", "number": "Number", diff --git a/webapp/app.ironcalc.com/frontend/package-lock.json b/webapp/app.ironcalc.com/frontend/package-lock.json index 4b12df0..ebeb880 100644 --- a/webapp/app.ironcalc.com/frontend/package-lock.json +++ b/webapp/app.ironcalc.com/frontend/package-lock.json @@ -43,21 +43,21 @@ "devDependencies": { "@biomejs/biome": "1.9.4", "@chromatic-com/storybook": "^3.2.4", - "@storybook/addon-essentials": "^8.5.3", - "@storybook/addon-interactions": "^8.5.3", - "@storybook/blocks": "^8.5.3", - "@storybook/react": "^8.5.3", - "@storybook/react-vite": "^8.5.3", - "@storybook/test": "^8.5.3", + "@storybook/addon-essentials": "^8.6.0", + "@storybook/addon-interactions": "^8.6.0", + "@storybook/blocks": "^8.6.0", + "@storybook/react": "^8.6.0", + "@storybook/react-vite": "^8.6.0", + "@storybook/test": "^8.6.0", "@vitejs/plugin-react": "^4.2.1", "react": "^19.0.0", "react-dom": "^19.0.0", - "storybook": "^8.5.3", + "storybook": "^8.6.0", "ts-node": "^10.9.2", "typescript": "~5.6.2", - "vite": "^6.0.5", + "vite": "^6.2.0", "vite-plugin-svgr": "^4.2.0", - "vitest": "^2.0.5" + "vitest": "^3.0.7" }, "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0",