diff --git a/quadratic-client/src/app/actions.ts b/quadratic-client/src/app/actions.ts index f84072e745..3038648e0a 100644 --- a/quadratic-client/src/app/actions.ts +++ b/quadratic-client/src/app/actions.ts @@ -185,3 +185,8 @@ export const findInSheet = { export const findInSheets = { label: 'Find in all sheets', }; + +export const resizeColumnAction = { + label: 'Resize column to fit data', + isAvailable: hasPermissionToEditFile, +}; diff --git a/quadratic-client/src/app/gridGL/interaction/pointer/PointerHeading.ts b/quadratic-client/src/app/gridGL/interaction/pointer/PointerHeading.ts index a832ed7561..aa14c313af 100644 --- a/quadratic-client/src/app/gridGL/interaction/pointer/PointerHeading.ts +++ b/quadratic-client/src/app/gridGL/interaction/pointer/PointerHeading.ts @@ -1,4 +1,3 @@ -import { events } from '@/app/events/events'; import { multiplayer } from '@/app/web-workers/multiplayerWebWorker/multiplayer'; import { quadraticCore } from '@/app/web-workers/quadraticCore/quadraticCore'; import { renderWebWorker } from '@/app/web-workers/renderWebWorker/renderWebWorker'; @@ -14,6 +13,9 @@ import { DOUBLE_CLICK_TIME } from './pointerUtils'; const MINIMUM_COLUMN_SIZE = 20; +// minimum cell when resizing in 1 character +const MIN_CELL_WIDTH = 10; + export interface ResizeHeadingColumnEvent extends CustomEvent { detail: number; } @@ -70,7 +72,7 @@ export class PointerHeading { if (headingResize) { pixiApp.setViewportDirty(); if (this.clicked && headingResize.column !== undefined) { - this.onDoubleClickColumn(headingResize.column); + this.autoResizeColumn(headingResize.column); event.preventDefault(); return true; } else if (this.clicked && headingResize.row !== undefined) { @@ -255,15 +257,20 @@ export class PointerHeading { return false; } - private async onDoubleClickColumn(column: number) { + async autoResizeColumn(column: number) { const maxWidth = await pixiApp.cellsSheets.getCellsContentMaxWidth(column); - const contentSizePlusMargin = maxWidth + CELL_TEXT_MARGIN_LEFT * 3; - const size = Math.max(contentSizePlusMargin, CELL_WIDTH); + let size: number; + if (maxWidth === 0) { + size = CELL_WIDTH; + } else { + const contentSizePlusMargin = maxWidth + CELL_TEXT_MARGIN_LEFT * 3; + size = Math.max(contentSizePlusMargin, MIN_CELL_WIDTH); + } const sheetId = sheets.sheet.id; const originalSize = sheets.sheet.getCellOffsets(column, 0); if (originalSize.width !== size) { quadraticCore.commitSingleResize(sheetId, column, undefined, size); - events.emit('resizeHeadingColumn', column); + // events.emit('resizeHeadingColumn', column); } } diff --git a/quadratic-client/src/app/ui/menus/CommandPalette/CommandPalette.tsx b/quadratic-client/src/app/ui/menus/CommandPalette/CommandPalette.tsx index 28ae351ead..d7f1db94ef 100644 --- a/quadratic-client/src/app/ui/menus/CommandPalette/CommandPalette.tsx +++ b/quadratic-client/src/app/ui/menus/CommandPalette/CommandPalette.tsx @@ -9,6 +9,7 @@ import { focusGrid } from '../../../helpers/focusGrid'; import { Command } from './CommandPaletteListItem'; import borderCommandGroup from './commands/Borders'; import codeCommandGroup from './commands/Code'; +import { columnRowCommandGroup } from './commands/ColumnRow'; import editCommandGroup from './commands/Edit'; import fileCommandGroup from './commands/File'; import formatCommandGroup from './commands/Format'; @@ -53,6 +54,7 @@ export const CommandPalette = () => { helpCommandGroup, codeCommandGroup, searchCommandGroup, + columnRowCommandGroup, ]; return ( diff --git a/quadratic-client/src/app/ui/menus/CommandPalette/commands/ColumnRow.tsx b/quadratic-client/src/app/ui/menus/CommandPalette/commands/ColumnRow.tsx new file mode 100644 index 0000000000..607e9a9324 --- /dev/null +++ b/quadratic-client/src/app/ui/menus/CommandPalette/commands/ColumnRow.tsx @@ -0,0 +1,22 @@ +import { resizeColumnAction } from '@/app/actions'; +import { sheets } from '@/app/grid/controller/Sheets'; +import { pixiApp } from '@/app/gridGL/pixiApp/PixiApp'; +import { CommandGroup, CommandPaletteListItem } from '@/app/ui/menus/CommandPalette/CommandPaletteListItem'; + +export const columnRowCommandGroup: CommandGroup = { + heading: 'Column', + commands: [ + { + label: resizeColumnAction.label, + isAvailable: resizeColumnAction.isAvailable, + Component: (props) => { + return ( + pixiApp.pointer.pointerHeading.autoResizeColumn(sheets.sheet.cursor.originPosition.x)} + /> + ); + }, + }, + ], +}; diff --git a/quadratic-client/src/app/web-workers/quadraticCore/worker/core.ts b/quadratic-client/src/app/web-workers/quadraticCore/worker/core.ts index 9c7765a280..7eaa491951 100644 --- a/quadratic-client/src/app/web-workers/quadraticCore/worker/core.ts +++ b/quadratic-client/src/app/web-workers/quadraticCore/worker/core.ts @@ -756,12 +756,9 @@ class Core { } commitTransientResize(sheetId: string, transientResize: string, cursor: string) { - return new Promise((resolve) => { - this.clientQueue.push(() => { - if (!this.gridController) throw new Error('Expected gridController to be defined'); - this.gridController.commitOffsetsResize(sheetId, transientResize, cursor); - resolve(undefined); - }); + this.clientQueue.push(() => { + if (!this.gridController) throw new Error('Expected gridController to be defined'); + this.gridController.commitOffsetsResize(sheetId, transientResize, cursor); }); } commitSingleResize( @@ -771,12 +768,9 @@ class Core { size: number, cursor: string ) { - return new Promise((resolve) => { - this.clientQueue.push(() => { - if (!this.gridController) throw new Error('Expected gridController to be defined'); - this.gridController.commitSingleResize(sheetId, column, row, size, cursor); - resolve(undefined); - }); + this.clientQueue.push(() => { + if (!this.gridController) throw new Error('Expected gridController to be defined'); + this.gridController.commitSingleResize(sheetId, column, row, size, cursor); }); } diff --git a/quadratic-client/src/app/web-workers/quadraticCore/worker/coreClient.ts b/quadratic-client/src/app/web-workers/quadraticCore/worker/coreClient.ts index 31429590c3..ab64ca0d27 100644 --- a/quadratic-client/src/app/web-workers/quadraticCore/worker/coreClient.ts +++ b/quadratic-client/src/app/web-workers/quadraticCore/worker/coreClient.ts @@ -462,11 +462,11 @@ class CoreClient { break; case 'clientCoreCommitTransientResize': - await core.commitTransientResize(e.data.sheetId, e.data.transientResize, e.data.cursor); + core.commitTransientResize(e.data.sheetId, e.data.transientResize, e.data.cursor); break; case 'clientCoreCommitSingleResize': - await core.commitSingleResize(e.data.sheetId, e.data.column, e.data.row, e.data.size, e.data.cursor); + core.commitSingleResize(e.data.sheetId, e.data.column, e.data.row, e.data.size, e.data.cursor); break; case 'clientCoreInitPython': diff --git a/quadratic-client/src/app/web-workers/renderWebWorker/renderWebWorker.ts b/quadratic-client/src/app/web-workers/renderWebWorker/renderWebWorker.ts index a37bb6d942..6195d277c6 100644 --- a/quadratic-client/src/app/web-workers/renderWebWorker/renderWebWorker.ts +++ b/quadratic-client/src/app/web-workers/renderWebWorker/renderWebWorker.ts @@ -35,35 +35,45 @@ class RenderWebWorker { private handleMessage = (e: MessageEvent) => { if (debugWebWorkersMessages) console.log(`[RenderWebWorker] message: ${e.data.type}`); - if (!pixiApp.cellsSheets) { this.preloadQueue.push(e); return; } + switch (e.data.type) { case 'renderClientCellsTextHashClear': pixiApp.cellsSheets.cellsTextHashClear(e.data); - break; + return; case 'renderClientLabelMeshEntry': pixiApp.cellsSheets.labelMeshEntry(e.data); - break; + return; case 'renderClientFinalizeCellsTextHash': pixiApp.cellsSheets.finalizeCellsTextHash(e.data); - break; + return; case 'renderClientFirstRenderComplete': pixiApp.firstRenderComplete(); - break; + return; case 'renderClientUnload': pixiApp.cellsSheets.unload(e.data); - break; + return; + } - default: - console.warn('Unhandled message type', e.data); + if (e.data.id !== undefined) { + const callback = this.waitingForResponse[e.data.id]; + if (callback) { + callback(e.data); + delete this.waitingForResponse[e.data.id]; + return; + } else { + console.warn('No callback for id in renderWebWorker', e.data.id); + } } + + console.warn('Unhandled message type', e.data); }; private send(message: ClientRenderMessage) { diff --git a/quadratic-client/src/app/web-workers/renderWebWorker/worker/cellsLabel/CellsLabels.ts b/quadratic-client/src/app/web-workers/renderWebWorker/worker/cellsLabel/CellsLabels.ts index 30e96515d6..e756a9bae2 100644 --- a/quadratic-client/src/app/web-workers/renderWebWorker/worker/cellsLabel/CellsLabels.ts +++ b/quadratic-client/src/app/web-workers/renderWebWorker/worker/cellsLabel/CellsLabels.ts @@ -381,7 +381,7 @@ export class CellsLabels { cellsHash.dirty = renderCells; } - setOffsets(column: number | undefined, row: number | undefined, delta: number) { + setOffsetsDelta(column: number | undefined, row: number | undefined, delta: number) { if (column !== undefined) { const size = this.sheetOffsets.getColumnWidth(column) - delta; this.sheetOffsets.setColumnWidth(column, size); @@ -394,6 +394,21 @@ export class CellsLabels { } } + setOffsetsSize(column: number | undefined, row: number | undefined, size: number) { + let delta = 0; + if (column !== undefined) { + delta = this.sheetOffsets.getColumnWidth(column) - size; + this.sheetOffsets.setColumnWidth(column, size); + } else if (row !== undefined) { + delta = this.sheetOffsets.getRowHeight(row) - size; + this.sheetOffsets.setRowHeight(row, size); + } + + if (delta) { + this.adjustHeadings(delta, column, row); + } + } + showLabel(x: number, y: number, show: boolean) { const hash = this.getCellsHash(x, y); if (hash) { diff --git a/quadratic-client/src/app/web-workers/renderWebWorker/worker/renderClient.ts b/quadratic-client/src/app/web-workers/renderWebWorker/worker/renderClient.ts index 60dff9d753..a48840ff70 100644 --- a/quadratic-client/src/app/web-workers/renderWebWorker/worker/renderClient.ts +++ b/quadratic-client/src/app/web-workers/renderWebWorker/worker/renderClient.ts @@ -32,7 +32,7 @@ class RenderClient { case 'clientRenderInit': renderText.clientInit(e.data.bitmapFonts); renderCore.init(e.ports[0]); - break; + return; case 'clientRenderViewport': const startUpdate = !renderText.viewport; @@ -44,15 +44,19 @@ class RenderClient { ); renderText.sheetId = e.data.sheetId; if (startUpdate) renderText.ready(); - break; + return; case 'clientRenderSheetOffsetsTransient': - renderText.sheetOffsets(e.data.sheetId, e.data.column, e.data.row, e.data.delta); - break; + renderText.sheetOffsetsDelta(e.data.sheetId, e.data.column, e.data.row, e.data.delta); + return; case 'clientRenderShowLabel': renderText.showLabel(e.data.sheetId, e.data.x, e.data.y, e.data.show); - break; + return; + + case 'clientRenderColumnMaxWidth': + this.sendColumnMaxWidth(e.data.id, renderText.columnMaxWidth(e.data.sheetId, e.data.column)); + return; default: console.warn('[renderClient] Unhandled message type', e.data); @@ -96,6 +100,10 @@ class RenderClient { finalizeCellsTextHash(sheetId: string, hashX: number, hashY: number) { this.send({ type: 'renderClientFinalizeCellsTextHash', sheetId, hashX, hashY }); } + + sendColumnMaxWidth(id: number, maxWidth: number) { + this.send({ type: 'renderClientColumnMaxWidth', maxWidth, id }); + } } export const renderClient = new RenderClient(); diff --git a/quadratic-client/src/app/web-workers/renderWebWorker/worker/renderCore.ts b/quadratic-client/src/app/web-workers/renderWebWorker/worker/renderCore.ts index 0aed494b22..e9d4504fc1 100644 --- a/quadratic-client/src/app/web-workers/renderWebWorker/worker/renderCore.ts +++ b/quadratic-client/src/app/web-workers/renderWebWorker/worker/renderCore.ts @@ -50,7 +50,7 @@ class RenderCore { break; case 'coreRenderSheetOffsets': - renderText.sheetOffsets(e.data.sheetId, e.data.column, e.data.row, e.data.size); + renderText.sheetOffsetsSize(e.data.sheetId, e.data.column, e.data.row, e.data.size); break; case 'coreRenderSheetInfoUpdate': diff --git a/quadratic-client/src/app/web-workers/renderWebWorker/worker/renderText.ts b/quadratic-client/src/app/web-workers/renderWebWorker/worker/renderText.ts index 90a9f26ea4..3af074097a 100644 --- a/quadratic-client/src/app/web-workers/renderWebWorker/worker/renderText.ts +++ b/quadratic-client/src/app/web-workers/renderWebWorker/worker/renderText.ts @@ -123,10 +123,16 @@ class RenderText { this.cellsLabels.delete(sheetId); } - sheetOffsets(sheetId: string, column: number | undefined, row: number | undefined, delta: number) { + sheetOffsetsDelta(sheetId: string, column: number | undefined, row: number | undefined, delta: number) { const cellsLabels = this.cellsLabels.get(sheetId); if (!cellsLabels) throw new Error('Expected cellsLabel to be defined in RenderText.sheetOffsets'); - cellsLabels.setOffsets(column, row, delta); + cellsLabels.setOffsetsDelta(column, row, delta); + } + + sheetOffsetsSize(sheetId: string, column: number | undefined, row: number | undefined, size: number) { + const cellsLabels = this.cellsLabels.get(sheetId); + if (!cellsLabels) throw new Error('Expected cellsLabel to be defined in RenderText.sheetOffsetsSize'); + cellsLabels.setOffsetsSize(column, row, size); } sheetInfoUpdate(sheetInfo: SheetInfo) { diff --git a/quadratic-core/src/controller/execution/execute_operation/execute_offsets.rs b/quadratic-core/src/controller/execution/execute_operation/execute_offsets.rs index ef671300cc..f3d5e16ff4 100644 --- a/quadratic-core/src/controller/execution/execute_operation/execute_offsets.rs +++ b/quadratic-core/src/controller/execution/execute_operation/execute_offsets.rs @@ -47,19 +47,16 @@ impl GridController { sheet_id, }); } - if (cfg!(target_family = "wasm") || cfg!(test)) && !client_resized && !transaction.is_server() { - if let Some(sheet) = self.try_sheet(sheet_id) { - crate::wasm_bindings::js::jsOffsetsModified( - sheet.id.to_string(), - Some(column), - None, - new_size, - ); - } + crate::wasm_bindings::js::jsOffsetsModified( + sheet_id.to_string(), + Some(column), + None, + new_size, + ); } } }