From fc7335707a3205566aca555d2ad797268718e931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Mon, 17 Feb 2025 20:17:57 +0100 Subject: [PATCH] UPDATE: Double click resizes columns/rows automatically --- bindings/wasm/src/lib.rs | 15 +++- .../WorksheetCanvas/worksheetCanvas.ts | 81 +++++++++++++++++-- webapp/IronCalc/src/components/usePointer.ts | 5 ++ webapp/IronCalc/src/components/worksheet.tsx | 14 +++- 4 files changed, 102 insertions(+), 13 deletions(-) diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index e55d950..5767a2e 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -307,7 +307,13 @@ impl Model { // This two are only used when we want to compute the automatic width of a column or height of a row #[wasm_bindgen(js_name = "getRowsWithData")] pub fn get_rows_with_data(&self, sheet: u32, column: i32) -> Result, JsError> { - let sheet_data = &self.model.get_model().workbook.worksheet(sheet).map_err(to_js_error)?.sheet_data; + let sheet_data = &self + .model + .get_model() + .workbook + .worksheet(sheet) + .map_err(to_js_error)? + .sheet_data; Ok(sheet_data .iter() .filter(|(_, data)| data.contains_key(&column)) @@ -317,9 +323,12 @@ impl Model { #[wasm_bindgen(js_name = "getColumnsWithData")] pub fn get_columns_with_data(&self, sheet: u32, row: i32) -> Result, JsError> { - Ok(self.model.get_model() + Ok(self + .model + .get_model() .workbook - .worksheet(sheet).map_err(to_js_error)? + .worksheet(sheet) + .map_err(to_js_error)? .sheet_data .get(&row) .map(|row_data| row_data.keys().copied().collect()) diff --git a/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts b/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts index e276cca..3c705f3 100644 --- a/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts +++ b/webapp/IronCalc/src/components/WorksheetCanvas/worksheetCanvas.ts @@ -37,6 +37,7 @@ export interface CanvasSettings { }; onColumnWidthChanges: (sheet: number, column: number, width: number) => void; onRowHeightChanges: (sheet: number, row: number, height: number) => void; + refresh: () => void; } export const fonts = { @@ -106,6 +107,8 @@ export default class WorksheetCanvas { onRowHeightChanges: (sheet: number, row: number, height: number) => void; + refresh: () => void; + constructor(options: CanvasSettings) { this.model = options.model; this.sheetWidth = 0; @@ -116,6 +119,7 @@ export default class WorksheetCanvas { this.ctx = this.setContext(); this.workbookState = options.workbookState; this.editor = options.elements.editor; + this.refresh = options.refresh; this.cellOutline = options.elements.cellOutline; this.cellOutlineHandle = options.elements.cellOutlineHandle; @@ -580,15 +584,16 @@ export default class WorksheetCanvas { document.removeEventListener("pointermove", resizeHandleMove); document.removeEventListener("pointerup", resizeHandleUp); const newColumnWidth = columnWidth + event.pageX - initPageX; - this.onColumnWidthChanges( - this.model.getSelectedSheet(), - column, - newColumnWidth, - ); + if (newColumnWidth !== columnWidth) { + this.onColumnWidthChanges( + this.model.getSelectedSheet(), + column, + newColumnWidth, + ); + } }; resizeHandleUp = resizeHandleUp.bind(this); div.addEventListener("pointerdown", (event) => { - event.stopPropagation(); div.style.opacity = "1"; this.columnGuide.style.display = "block"; this.columnGuide.style.left = `${headerColumnWidth + x}px`; @@ -596,6 +601,35 @@ export default class WorksheetCanvas { document.addEventListener("pointermove", resizeHandleMove); document.addEventListener("pointerup", resizeHandleUp); }); + + div.addEventListener("dblclick", (event) => { + // This is tough. We should have a call like this.model.setAutofitColumn(sheet, column) + // but we can't do that because the back end knows nothing about the rendering engine. + const sheet = this.model.getSelectedSheet(); + const rows = this.model.getRowsWithData(sheet, column); + let width = 0; + // This is a bit of a HACK. We should use the actual font size and weather is bold or not + const fontSize = 13; + this.ctx.font = `${fontSize}px ${defaultCellFontFamily}`; + for (const row of rows) { + const fullText = this.model.getFormattedCellValue(sheet, row, column); + if (fullText === "") { + continue; + } + const lines = fullText.split("\n"); + for (const line of lines) { + const textWidth = this.ctx.measureText(line).width; + width = Math.max(width, textWidth); + } + } + // If the width is 0, we do nothing + if (width !== 0) { + // The +8 is so that the text is in the same position regardless of the horizontal alignment + this.model.setColumnsWidth(sheet, column, column, width + 8); + this.refresh(); + } + event.stopPropagation(); + }); } private addRowResizeHandle(y: number, row: number, rowHeight: number): void { @@ -618,8 +652,10 @@ export default class WorksheetCanvas { this.rowGuide.style.display = "none"; document.removeEventListener("pointermove", resizeHandleMove); document.removeEventListener("pointerup", resizeHandleUp); - const newRowHeight = rowHeight + event.pageY - initPageY - 1; - this.onRowHeightChanges(sheet, row, newRowHeight); + const newRowHeight = rowHeight + event.pageY - initPageY; + if (newRowHeight !== rowHeight) { + this.onRowHeightChanges(sheet, row, newRowHeight); + } }; resizeHandleUp = resizeHandleUp.bind(this); /* istanbul ignore next */ @@ -632,6 +668,35 @@ export default class WorksheetCanvas { document.addEventListener("pointermove", resizeHandleMove); document.addEventListener("pointerup", resizeHandleUp); }); + + div.addEventListener("dblclick", (event) => { + // This is tough. We should have a call like this.model.setAutofitRow(sheet, row) + // but we can't do that because the back end knows nothing about the rendering engine. + const sheet = this.model.getSelectedSheet(); + const columns = this.model.getColumnsWithData(sheet, row); + let height = 0; + const lineHeight = 22; + // This is a bit of a HACK. We should use the actual font size and weather is bold or not + const fontSize = 13; + this.ctx.font = `${fontSize}px ${defaultCellFontFamily}`; + for (const column of columns) { + const fullText = this.model.getFormattedCellValue(sheet, row, column); + if (fullText === "") { + continue; + } + const lines = fullText.split("\n"); + const lineCount = lines.length; + // This si computed so that the y position of the text is independent of the vertical alignment + const textHeight = (lineCount - 1) * lineHeight + 8 + fontSize; + height = Math.max(height, textHeight); + } + // If the height is 0, we do nothing + if (height !== 0) { + this.model.setRowsHeight(sheet, row, row, height); + this.refresh(); + } + event.stopPropagation(); + }); } private styleColumnHeader( diff --git a/webapp/IronCalc/src/components/usePointer.ts b/webapp/IronCalc/src/components/usePointer.ts index 20ccba8..68cb99b 100644 --- a/webapp/IronCalc/src/components/usePointer.ts +++ b/webapp/IronCalc/src/components/usePointer.ts @@ -119,6 +119,11 @@ const usePointer = (options: PointerSettings): PointerEvents => { const onPointerDown = useCallback( (event: PointerEvent) => { + const target = event.target as HTMLElement; + if (target !== null && target.className === "column-resize-handle") { + // we are resizing a column + return; + } let x = event.clientX; let y = event.clientY; const { diff --git a/webapp/IronCalc/src/components/worksheet.tsx b/webapp/IronCalc/src/components/worksheet.tsx index 20c5f38..a354a58 100644 --- a/webapp/IronCalc/src/components/worksheet.tsx +++ b/webapp/IronCalc/src/components/worksheet.tsx @@ -115,7 +115,14 @@ function Worksheet(props: { const { range } = model.getSelectedView(); let columnStart = column; let columnEnd = column; - if (column >= range[1] && column <= range[3]) { + 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]); } @@ -129,13 +136,16 @@ function Worksheet(props: { const { range } = model.getSelectedView(); let rowStart = row; let rowEnd = row; - if (row >= range[0] && row <= range[2]) { + 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();