alexandrusoare commented on code in PR #45090: URL: https://github.com/apache/superset/pull/45090#discussion_r4348005439
########## superset-frontend/src/features/canvas/resolveGrid.ts: ########## @@ -0,0 +1,139 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * @fileoverview The grid placement rules, for previewing a drop. + * + * A deliberate second implementation of `superset/canvas/definition/grid.py`: + * the server stays the authority and its answer is what gets stored, but a + * drag needs to show where the neighbours will end up *before* the write, so + * the same rules have to run locally while the pointer moves. + * + * The two are kept honest by `grid_cases.json`, which both test suites read — + * generated from the server, so a change there that this file doesn't follow + * fails on both sides. + * + * Children resolve in reading order: + * + * - An explicitly placed child (`col`/`row` set) keeps its column, and is + * pushed straight down until it clears anything resolved before it. + * - An auto-placed child (`col`/`row` omitted) takes the first free slot that + * fits its span, scanning from a cursor that only moves forward. + */ + +import type { GridPlacement } from './types'; + +/** A child's stored layout: any of these may be absent. */ +export interface GridLayout { + col?: number; + row?: number; + colSpan?: number; + rowSpan?: number; +} + +const cells = (rect: GridPlacement): string[] => { + const out: string[] = []; + for (let { row } = rect; row < rect.row + rect.rowSpan; row += 1) { + for (let { col } = rect; col < rect.col + rect.colSpan; col += 1) { + out.push(`${row}:${col}`); + } + } + return out; +}; + +const clear = (rect: GridPlacement, occupied: Set<string>): boolean => + cells(rect).every(cell => !occupied.has(cell)); + +/** + * Where each child lands on a grid of `columns` columns, in reading order. + * + * Mirrors the server's `resolve_grid`. Spans default to the full width and a + * single row, and a span wider than the grid is clamped to it. + */ +export function resolveGrid( + layouts: GridLayout[], + columns: number, +): GridPlacement[] { + const occupied = new Set<string>(); + const resolved: GridPlacement[] = []; + let cursorRow = 1; + let cursorCol = 1; + + layouts.forEach(layout => { + const colSpan = Math.min(layout.colSpan || columns, columns); + const rowSpan = layout.rowSpan || 1; + let rect: GridPlacement; + + if (layout.col !== undefined && layout.col !== null) { + rect = { + col: layout.col, + row: layout.row as number, + colSpan, + rowSpan, + }; + while (!clear(rect, occupied)) { + rect = { ...rect, row: rect.row + 1 }; + } + } else { + let row = cursorRow; + let col = cursorCol; + for (;;) { + if (col + colSpan - 1 > columns) { + row += 1; + col = 1; + // eslint-disable-next-line no-continue + continue; + } + rect = { col, row, colSpan, rowSpan }; + if (clear(rect, occupied)) break; + col += 1; + } + cursorRow = rect.row; + cursorCol = rect.col + colSpan; + } + + cells(rect).forEach(cell => occupied.add(cell)); + resolved.push(rect); + }); + + return resolved; +} + +/** + * The placements a grid would settle on if `moving` were dropped at `target`. + * + * Children keep their reading order, so the dragged widget pushes the ones + * after it down exactly as the server will once the drop is written. + */ +export function previewGrid( + childIds: string[], + placements: Record<string, GridPlacement>, + moving: string, + target: GridPlacement, + columns: number, +): Record<string, GridPlacement> { + const layouts = childIds.map(nodeId => Review Comment: Done. It now takes the nodes' stored layouts, threaded from definition.nodes[*].layout. Your exact scenario is a test: two full-width auto-placed widgets, drag the first to row 3, and the second previews moving up to row 1. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
