From a7db2f0dff9b895c084cdd4696b6676045049aa0 Mon Sep 17 00:00:00 2001 From: heswell Date: Sat, 7 Sep 2024 15:13:42 +0100 Subject: [PATCH] griditem drag drop --- .../vuu-layout/src/grid-layout/GridLayout.css | 1 + .../src/grid-layout/GridLayoutItem.tsx | 20 +- .../src/grid-layout/GridLayoutModel.ts | 164 +++++++------- .../src/grid-layout/GridLayoutProvider.tsx | 14 +- .../src/grid-layout/GridLayoutStackedtem.tsx | 23 +- .../src/grid-layout/grid-layout-utils.ts | 35 ++- .../src/grid-layout/react-element-utils.ts | 45 ++-- .../src/grid-layout/useAsDropTarget.ts | 19 +- .../src/grid-layout/useDraggable.ts | 49 ++-- .../grid-layout/useGridSplitterResizing.tsx | 213 ++++++++++-------- .../html/components/DebugGridItem.tsx | 25 +- 11 files changed, 320 insertions(+), 288 deletions(-) diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayout.css b/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayout.css index da689b209..c786d6c10 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayout.css +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayout.css @@ -36,6 +36,7 @@ .vuuGridLayoutItemHeader { align-items: center; background-color: #ccc; + cursor: grab; display: flex; flex: 0 0 var(--vuu-header-height); inset: var(--vuu-grid-gap) var(--vuu-grid-gap) auto var(--vuu-grid-gap); diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutItem.tsx b/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutItem.tsx index 00c830f55..1802be4d7 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutItem.tsx +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutItem.tsx @@ -2,7 +2,7 @@ import { useDraggable, useGridLayoutDragStartHandler, useGridLayoutProps, - useGridLayoutProviderDispatch + useGridLayoutProviderDispatch, } from "@finos/vuu-layout"; import { IconButton } from "@finos/vuu-ui-controls"; import { useComponentCssInjection } from "@salt-ds/styles"; @@ -13,7 +13,7 @@ import { DragEvent, HTMLAttributes, MouseEventHandler, - useCallback + useCallback, } from "react"; import { GridResizeable } from "./GridLayout"; import { useAsDropTarget } from "./useAsDropTarget"; @@ -58,12 +58,12 @@ export const GridLayoutItem = ({ useComponentCssInjection({ testId: "vuu-grid-layout", css: gridLayoutCss, - window: targetWindow + window: targetWindow, }); useComponentCssInjection({ testId: "vuu-grid-splitter", css: gridSplitterCss, - window: targetWindow + window: targetWindow, }); const dispatch = useGridLayoutProviderDispatch(); @@ -75,7 +75,7 @@ export const GridLayoutItem = ({ evt.stopPropagation(); dispatch({ type: "close", id }); }, - [dispatch, id] + [dispatch, id], ); const getPayload = useCallback( @@ -86,27 +86,29 @@ export const GridLayoutItem = ({ } throw Error("GridLayoutItem no found"); }, - [] + [], ); + const getDragImg = useCallback(() => {}, []); + const useDropTargetHook = isDropTarget ? useAsDropTarget : useNotDropTarget; const { dropTargetClassName, ...droppableProps } = useDropTargetHook(); const draggableProps = useDraggable({ draggableClassName: classBaseItem, getPayload, - onDragStart + onDragStart, }); const className = cx(classBaseItem, { [`${classBaseItem}-resizeable-h`]: resizeable === "h", [`${classBaseItem}-resizeable-v`]: resizeable === "v", - [`${classBaseItem}-resizeable-vh`]: resizeable === "hv" + [`${classBaseItem}-resizeable-vh`]: resizeable === "hv", }); const style = { ...styleProp, ...layoutProps, - "--header-height": header ? "25px" : "0px" + "--header-height": header ? "25px" : "0px", }; return ( diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutModel.ts b/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutModel.ts index bd6137e30..09b9b3d8e 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutModel.ts +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutModel.ts @@ -12,7 +12,7 @@ import { itemsFillColumn, itemsFillRow, splitTrack, - splitTracks + splitTracks, } from "./grid-layout-utils"; import { getEmptyExtents, getGridMatrix } from "./grid-matrix"; @@ -94,17 +94,17 @@ type ColumnAndRowUpdates = [GridItemUpdate[], GridItemUpdate[]]; function findUpdate( updates: GridItemUpdate[], gridItemId: string, - throwIfNotFound?: false + throwIfNotFound?: false, ): GridLayoutModelPosition | undefined; function findUpdate( updates: GridItemUpdate[], gridItemId: string, - throwIfNotFound: true + throwIfNotFound: true, ): GridLayoutModelPosition; function findUpdate( updates: GridItemUpdate[], gridItemId: string, - throwIfNotFound = false + throwIfNotFound = false, ) { const update = updates.find(([id]) => id === gridItemId); if (update) { @@ -118,7 +118,7 @@ function findUpdate( const storeMapValue = ( map: GridItemMap, key: number, - value: IGridLayoutModelItem + value: IGridLayoutModelItem, ) => { const values = map.get(key); if (values) { @@ -131,7 +131,7 @@ const storeMapValue = ( const clearMapValue = ( map: GridItemMap, key: number, - value: IGridLayoutModelItem + value: IGridLayoutModelItem, ) => { const values = map.get(key); if (values && values.includes(value)) { @@ -140,7 +140,7 @@ const clearMapValue = ( } else { map.set( key, - values.filter((v) => v !== value) + values.filter((v) => v !== value), ); } values.push(value); @@ -152,7 +152,7 @@ const clearMapValue = ( const removeMapValue = ( map: GridItemMap, key: number, - value: IGridLayoutModelItem + value: IGridLayoutModelItem, ) => { const values = map.get(key); if (values?.includes(value)) { @@ -161,7 +161,7 @@ const removeMapValue = ( } else { map.set( key, - values.filter((i) => i !== value) + values.filter((i) => i !== value), ); } } @@ -177,23 +177,22 @@ export class GridLayoutModel { private columnMaps: GridItemMaps = { start: new Map(), - end: new Map() + end: new Map(), }; private rowMaps: GridItemMaps = { start: new Map(), - end: new Map() + end: new Map(), }; constructor(colCount: number, rowCount: number) { this.colCount = colCount; - console.log(`constructor colCount ${colCount}`); this.rowCount = rowCount; } private storeItem( maps: GridItemMaps, { end, start }: GridLayoutModelPosition, - item: IGridLayoutModelItem + item: IGridLayoutModelItem, ) { storeMapValue(maps.start, start, item); storeMapValue(maps.end, end, item); @@ -202,7 +201,7 @@ export class GridLayoutModel { private clearItemFromStore( maps: GridItemMaps, { end, start }: GridLayoutModelPosition, - item: IGridLayoutModelItem + item: IGridLayoutModelItem, ) { clearMapValue(maps.start, start, item); clearMapValue(maps.end, end, item); @@ -211,7 +210,7 @@ export class GridLayoutModel { private updateContrasToOccupySpace = ({ column, id, - row + row, }: IGridLayoutModelItem): ColumnAndRowUpdates => { // Do we have one or more GridItems that can be extended horizontally // to fill the space described by column, row @@ -221,7 +220,7 @@ export class GridLayoutModel { .get(row.start) ?.filter( ({ column: { start, end }, fixed }) => - (!fixed && end === column.start) || start === column.end + (!fixed && end === column.start) || start === column.end, ); if (adjacentItemsWithSameRowStart) { // 2) do any of the items that start on the same row as our gridcell @@ -230,12 +229,12 @@ export class GridLayoutModel { // extended to cover our cell(s) of interest. // TODO sort by column, so we get the left item first const itemsInSameRow = adjacentItemsWithSameRowStart.filter( - (item) => item.row.end === row.end && item.id !== id + (item) => item.row.end === row.end && item.id !== id, ); if (itemsInSameRow.length === 1) { const { id: contraId, - column: { start, end } + column: { start, end }, } = itemsInSameRow[0]; if (end === column.start) { return [[[contraId, { start, end: column.end }]], []]; @@ -246,22 +245,22 @@ export class GridLayoutModel { // assuming no overlapping gridcells, the most we can have here // is 2 items in same row, one left and one right const adjacentBefore = itemsInSameRow.filter( - (item) => item.column.end === column.start + (item) => item.column.end === column.start, ); if (adjacentBefore.length === 1) { const { id: contraId, - column: { start } + column: { start }, } = adjacentBefore[0]; return [[[contraId, { start, end: column.end }]], []]; } const adjacentAfter = itemsInSameRow.filter( - (item) => item.column.start === column.end + (item) => item.column.start === column.end, ); if (adjacentAfter.length === 1) { const { id: contraId, - column: { end } + column: { end }, } = adjacentAfter[0]; return [[[contraId, { start: column.start, end }]], []]; } @@ -275,7 +274,7 @@ export class GridLayoutModel { const itemsEndingWhereTargetStarts = this.columnMaps.end .get(column.start) ?.filter( - (item) => item.row.start >= row.start && item.row.end <= row.end + (item) => item.row.start >= row.start && item.row.end <= row.end, ); if ( itemsEndingWhereTargetStarts && @@ -283,14 +282,14 @@ export class GridLayoutModel { ) { const columnUpdates = itemsEndingWhereTargetStarts.map( ({ id: contraId, column: { start } }) => - [contraId, { start, end: column.end }] as GridItemUpdate + [contraId, { start, end: column.end }] as GridItemUpdate, ); return [columnUpdates, []]; } const itemsStartingWhereTargetEnds = this.columnMaps.start .get(column.end) ?.filter( - (item) => item.row.start >= row.start && item.row.end <= row.end + (item) => item.row.start >= row.start && item.row.end <= row.end, ); if ( itemsStartingWhereTargetEnds && @@ -298,7 +297,7 @@ export class GridLayoutModel { ) { const columnUpdates = itemsStartingWhereTargetEnds.map( ({ id: contraId, column: { end } }) => - [contraId, { start: column.start, end }] as GridItemUpdate + [contraId, { start: column.start, end }] as GridItemUpdate, ); return [columnUpdates, []]; } @@ -309,17 +308,17 @@ export class GridLayoutModel { .get(column.start) ?.filter( ({ row: { start, end }, fixed }) => - (!fixed && end === row.start) || start === row.end + (!fixed && end === row.start) || start === row.end, ); if (adjacentItemsWithSameColumnStart) { const itemsInSameColumn = adjacentItemsWithSameColumnStart.filter( - (item) => item.column.end === column.end && item.id !== id + (item) => item.column.end === column.end && item.id !== id, ); if (itemsInSameColumn.length === 1) { const { id: contraId, - row: { start, end } + row: { start, end }, } = itemsInSameColumn[0]; if (end === row.start) { return [[], [[contraId, { start, end: row.end }]]]; @@ -328,22 +327,22 @@ export class GridLayoutModel { } } else if (itemsInSameColumn.length === 2) { const adjacentBefore = itemsInSameColumn.filter( - (item) => item.row.end === row.start + (item) => item.row.end === row.start, ); if (adjacentBefore.length === 1) { const { id: contraId, - row: { start } + row: { start }, } = adjacentBefore[0]; return [[[contraId, { start, end: row.end }]], []]; } const adjacentAfter = itemsInSameColumn.filter( - (item) => item.row.start === row.end + (item) => item.row.start === row.end, ); if (adjacentAfter.length === 1) { const { id: contraId, - row: { end } + row: { end }, } = adjacentAfter[0]; return [[[contraId, { start: row.start, end }]], []]; } @@ -354,7 +353,7 @@ export class GridLayoutModel { ?.filter( (item) => item.column.start >= column.start && - item.column.end <= column.end + item.column.end <= column.end, ); if ( itemsEndingWhereTargetStarts && @@ -362,7 +361,7 @@ export class GridLayoutModel { ) { const rowUpdates = itemsEndingWhereTargetStarts.map( ({ id: contraId, row: { start } }) => - [contraId, { start, end: row.end }] as GridItemUpdate + [contraId, { start, end: row.end }] as GridItemUpdate, ); return [[], rowUpdates]; } @@ -371,7 +370,7 @@ export class GridLayoutModel { ?.filter( (item) => item.column.start >= column.start && - item.column.end <= column.end + item.column.end <= column.end, ); if ( itemsStartingWhereTargetEnds && @@ -379,7 +378,7 @@ export class GridLayoutModel { ) { const rowUpdates = itemsStartingWhereTargetEnds.map( ({ id: contraId, row: { end } }) => - [contraId, { start: row.start, end }] as GridItemUpdate + [contraId, { start: row.start, end }] as GridItemUpdate, ); return [[], rowUpdates]; } @@ -393,7 +392,7 @@ export class GridLayoutModel { const allContrasAbove = this.rowMaps.end.get(row.start); if (allContrasAbove) { const indexOfAlignedContra = allContrasAbove.findIndex( - (item) => item.column.start === column.start + (item) => item.column.start === column.start, ); if (indexOfAlignedContra !== -1) { return allContrasAbove.sort(byColumnStart).slice(indexOfAlignedContra); @@ -406,7 +405,7 @@ export class GridLayoutModel { const allContrasLeft = this.columnMaps.end.get(column.start); if (allContrasLeft) { const indexOfAlignedContra = allContrasLeft.findIndex( - (item) => item.row.start === row.start + (item) => item.row.start === row.start, ); if (indexOfAlignedContra !== -1) { // placeholders may not be in correct sort position @@ -435,7 +434,7 @@ export class GridLayoutModel { private setGridRow = ( gridItemId: string, - { start, end }: GridLayoutModelPosition + { start, end }: GridLayoutModelPosition, ) => { const gridItem = this.index.get(gridItemId); if (gridItem) { @@ -457,7 +456,7 @@ export class GridLayoutModel { private setGridColumn = ( gridItemId: string, - { start, end }: GridLayoutModelPosition + { start, end }: GridLayoutModelPosition, ) => { const gridItem = this.index.get(gridItemId); if (gridItem) { @@ -480,33 +479,33 @@ export class GridLayoutModel { private setColExpanded = ({ id, - column: { start, end } + column: { start, end }, }: IGridLayoutModelItem): GridItemUpdate => [id, { start, end: end + 1 }]; private setRowExpanded = ({ id, - row: { start, end } + row: { start, end }, }: IGridLayoutModelItem): GridItemUpdate => [id, { start, end: end + 1 }]; private setShiftColForward = ({ id, - column: { start, end } + column: { start, end }, }: IGridLayoutModelItem): GridItemUpdate => [ id, - { start: start + 1, end: end + 1 } + { start: start + 1, end: end + 1 }, ]; private setShiftRowForward = ({ id, - row: { start, end } + row: { start, end }, }: IGridLayoutModelItem): GridItemUpdate => [ id, - { start: start + 1, end: end + 1 } + { start: start + 1, end: end + 1 }, ]; private findContrasAndSiblings( gridItem: IGridLayoutModelItem, - resizeDirection: GridLayoutResizeDirection = "horizontal" + resizeDirection: GridLayoutResizeDirection = "horizontal", ) { if (resizeDirection === "vertical") { const contrasAbove = this.getContrasAbove(gridItem); @@ -564,7 +563,7 @@ export class GridLayoutModel { console.warn( `multiple unused lines ${unusedColLines.join(",")} (colCount = ${ this.colCount - })` + })`, ); } if (unusedColLines.length === 1) { @@ -573,7 +572,7 @@ export class GridLayoutModel { colUpdates.forEach(([id, u]) => { const existingUpdate = colItemUpdates.find( - ([itemId]) => id === itemId + ([itemId]) => id === itemId, ); if (existingUpdate) { existingUpdate[1] = u; @@ -587,7 +586,7 @@ export class GridLayoutModel { const rowUpdates = this.removeTrack(trackIndex, "vertical"); rowUpdates.forEach(([id, u]) => { const existingUpdate = rowItemUpdates.find( - ([itemId]) => id === itemId + ([itemId]) => id === itemId, ); if (existingUpdate) { existingUpdate[1] = u; @@ -599,7 +598,7 @@ export class GridLayoutModel { return { updates: [colItemUpdates, rowItemUpdates], - removedTrackLines: [unusedColLines, unusedRowLines] + removedTrackLines: [unusedColLines, unusedRowLines], }; } @@ -631,7 +630,7 @@ export class GridLayoutModel { getGridItem(gridItemId: string, throwIfNotFound: true): IGridLayoutModelItem; getGridItem( gridItemId: string, - throwIfNotFound?: false + throwIfNotFound?: false, ): IGridLayoutModelItem | undefined; getGridItem(gridItemId: string, throwIfNotFound = false) { const gridItem = this.index.get(gridItemId); @@ -645,7 +644,7 @@ export class GridLayoutModel { clearPlaceholders() { const placeHolders = this.getPlaceholders(); placeHolders.forEach((placeholder) => - this.removeGridItem(placeholder.id, false) + this.removeGridItem(placeholder.id, false), ); } /* @@ -682,7 +681,7 @@ export class GridLayoutModel { id: `${id}-splitter-h`, orientation: "horizontal", row: rowSpan.position, - type: "splitter" + type: "splitter", }); } @@ -698,7 +697,7 @@ export class GridLayoutModel { id: `${id}-splitter-v`, orientation: "vertical", row, - type: "splitter" + type: "splitter", }); } } @@ -709,18 +708,18 @@ export class GridLayoutModel { getSplitter( gridLayoutItem: IGridLayoutModelItem, - resizeDirection: GridLayoutResizeDirection + resizeDirection: GridLayoutResizeDirection, ) { const splitter = this.splitters?.find( ({ controls, orientation }) => - controls === gridLayoutItem.id && orientation === resizeDirection + controls === gridLayoutItem.id && orientation === resizeDirection, ); if (splitter) { return splitter; } throw Error( - `no splitter for gridItem ${gridLayoutItem.id} (${resizeDirection})` + `no splitter for gridItem ${gridLayoutItem.id} (${resizeDirection})`, ); } @@ -729,7 +728,7 @@ export class GridLayoutModel { newTrackIndex: number, newTrackSize: number, resizeOperation: GridLayoutResizeOperation, - state: ResizeState + state: ResizeState, ) { const { contras, resizeDirection, resizeItem, siblings } = state; @@ -745,7 +744,7 @@ export class GridLayoutModel { tracks, newTrackSize, resizeOperation, - state + state, ); const updates = this.addTrack(newTrackIndex, resizeDirection); const indexAdjustment = expandingResizeItem ? -1 : +1; @@ -777,10 +776,14 @@ export class GridLayoutModel { return { newTracks, updates }; } + // We may be splitting a GridItem either to make space for a new item (initialised below) + // or because we are dropping an existing component (droppedGridItemId) that is being + // repositioned. The GridItem for the dropped component will have been removed splitGridItem( gridItemId: string, dropPosition: GridLayoutSplitDirection, - tracks: number[] + tracks: number[], + newGridItemId?: string, ): { newGridItem: IGridLayoutModelItem; tracks: number[]; @@ -794,16 +797,17 @@ export class GridLayoutModel { const isVertical = resizeDirection === "vertical"; const track = isVertical ? "row" : "column"; const { - [track]: { start, end } + [track]: { start, end }, } = gridItem; - const newItemId = uuid(); + const newItemId = newGridItemId ?? uuid(); + const newGridItem: IGridLayoutModelItem = { column: { ...gridItem.column }, id: newItemId, resizeable: "vh", row: { ...gridItem.row }, - type: "content" + type: "content", }; this.addGridItem(newGridItem); @@ -830,12 +834,12 @@ export class GridLayoutModel { if (dropPosition === "west" || dropPosition === "north") { updates = [ [newItemId, { start, end: bisectingGridLine }], - [gridItemId, { start: bisectingGridLine, end }] + [gridItemId, { start: bisectingGridLine, end }], ]; } else { updates = [ [gridItemId, { start, end: bisectingGridLine }], - [newItemId, { start: bisectingGridLine, end }] + [newItemId, { start: bisectingGridLine, end }], ]; } } else { @@ -867,11 +871,11 @@ export class GridLayoutModel { return { updates: updates ?? [], tracks: newTracks, - newGridItem + newGridItem, }; } else { throw Error( - `GridLayoutModel splitGridItem: no gridItem with id ${gridItemId}` + `GridLayoutModel splitGridItem: no gridItem with id ${gridItemId}`, ); } } @@ -889,20 +893,20 @@ export class GridLayoutModel { { end: endMap, start: startMap }, setExpanded, setShiftForward, - setTrack + setTrack, ] = resizeDirection === "vertical" ? [ this.rowMaps, this.setRowExpanded, this.setShiftRowForward, - this.setGridRow + this.setGridRow, ] : [ this.columnMaps, this.setColExpanded, this.setShiftColForward, - this.setGridColumn + this.setGridColumn, ]; for (const [position, gridItems] of startMap) { @@ -954,7 +958,7 @@ export class GridLayoutModel { const updateMap = ( map: GridItemMap, track: GridLayoutTrack, - pos: "start" | "end" + pos: "start" | "end", ) => { for (const [position, gridItems] of map) { if (position > gridPosition) { @@ -995,7 +999,7 @@ export class GridLayoutModel { tracks: number[], newTrackSize: number, resizeOperation: GridLayoutResizeOperation, - { resizeTrackIndex }: ResizeState + { resizeTrackIndex }: ResizeState, ) { const newTracks = tracks.slice(); newTracks.splice(resizeTrackIndex, 0, 0); @@ -1012,7 +1016,7 @@ export class GridLayoutModel { flipResizeTracks( trackIndex: number, - resizeDirection: GridLayoutResizeDirection + resizeDirection: GridLayoutResizeDirection, ) { const trackStart = trackIndex + 1; const trackEnd = trackIndex + 2; @@ -1043,7 +1047,7 @@ export class GridLayoutModel { gridItems.forEach((item) => { updates.push([ item.id, - { start: item[track].start, end: trackStart } + { start: item[track].start, end: trackStart }, ]); }); } else if (position === trackStart) { @@ -1086,7 +1090,7 @@ export class GridLayoutModel { }): ResizeState | undefined { const contrasAndSiblings = this.findContrasAndSiblings( resizeItem, - resizeDirection + resizeDirection, ); if (contrasAndSiblings && this.splitters) { @@ -1120,7 +1124,7 @@ export class GridLayoutModel { resizeTrackIsShared, contrasAndSiblings, contraTrackIndex, - resizeTrackIndex + resizeTrackIndex, }); return { @@ -1136,7 +1140,7 @@ export class GridLayoutModel { rows, siblings, resizeTrackIndex, - resizeTrackIsShared + resizeTrackIsShared, }; } } @@ -1146,7 +1150,7 @@ export class GridLayoutModel { ${this.gridItems .map( ({ id, column, resizeable = "", row }) => - `\n${id}\t\tcol ${column.start}/${column.end}\t row ${row.start}/${row.end}\t${resizeable}` + `\n${id}\t\tcol ${column.start}/${column.end}\t row ${row.start}/${row.end}\t${resizeable}`, ) .join("")} `; diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutProvider.tsx b/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutProvider.tsx index cdf2999bf..ec5814d4a 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutProvider.tsx +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutProvider.tsx @@ -8,6 +8,7 @@ import { useContext, } from "react"; import { GridLayoutDropHandler } from "./GridPlaceholder"; +import { GridLayoutDragStartHandler } from "./useDraggable"; export type GridStyle = Pick< CSSProperties, @@ -27,19 +28,14 @@ export type GridLayoutAction = GridLayoutCloseAction; export type GridLayoutProviderDispatch = Dispatch; const unconfiguredGridLayoutProviderDispatch: GridLayoutProviderDispatch = ( - action + action, ) => console.log( - `dispatch ${action.type}, have you forgotten to provide a GridLayoutProvider ?` + `dispatch ${action.type}, have you forgotten to provide a GridLayoutProvider ?`, ); export type GridLayoutDragEndHandler = (evt: DragEvent) => void; -export type GridLayoutDragStartHandler = ( - evt: DragEvent, - id: string -) => void; - export interface GridLayoutProviderContextProps { dispatchGridLayoutAction: GridLayoutProviderDispatch; layoutMap: GridLayoutMap; @@ -56,7 +52,7 @@ const GridLayoutProviderContext = createContext( onDragStart: () => console.log("no GridLayoutProvider"), onDrop: () => console.log("no GridLayoutProvider"), version: -1, - } + }, ); export interface GridLayoutProviderProps @@ -73,7 +69,7 @@ export interface GridLayoutProviderProps } export const GridLayoutProvider = ( - props: GridLayoutProviderProps + props: GridLayoutProviderProps, ): ReactElement => { const { children, diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutStackedtem.tsx b/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutStackedtem.tsx index a6861a9cc..64bceb4bf 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutStackedtem.tsx +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/GridLayoutStackedtem.tsx @@ -4,19 +4,19 @@ import { renderTabsForStack, useDraggable, useGridLayoutDragStartHandler, - useGridLayoutProps + useGridLayoutProps, } from "@finos/vuu-layout"; import { useComponentCssInjection } from "@salt-ds/styles"; import { useWindow } from "@salt-ds/window"; import cx from "clsx"; -import { DragEvent, useCallback, useState } from "react"; +import { DragEvent, useCallback, useLayoutEffect, useState } from "react"; import { useAsDropTarget } from "./useAsDropTarget"; import { useNotDropTarget } from "./useNotDropTarget"; import { queryClosest } from "@finos/vuu-utils"; import gridLayoutCss from "./GridLayout.css"; import gridSplitterCss from "./GridSplitter.css"; -import { Tabstrip } from "@finos/vuu-ui-controls"; +import { Tabstrip, useEditableText } from "@finos/vuu-ui-controls"; const classBaseItem = "vuuGridLayoutItem"; @@ -38,18 +38,23 @@ export const GridLayoutStackedItem = ({ useComponentCssInjection({ testId: "vuu-grid-layout", css: gridLayoutCss, - window: targetWindow + window: targetWindow, }); useComponentCssInjection({ testId: "vuu-grid-splitter", css: gridSplitterCss, - window: targetWindow + window: targetWindow, }); const layoutProps = useGridLayoutProps(id); const onDragStart = useGridLayoutDragStartHandler(); const [active, setActive] = useState(activeProp); + useLayoutEffect(() => { + console.log(`activeProp changed ${activeProp}`); + setActive(activeProp); + }, [activeProp]); + const getPayload = useCallback( (evt: DragEvent): [string, string] => { const draggedItem = queryClosest(evt.target, ".vuuGridLayoutItem"); @@ -58,7 +63,7 @@ export const GridLayoutStackedItem = ({ } throw Error("GridLayoutItem no found"); }, - [] + [], ); const useDropTargetHook = isDropTarget ? useAsDropTarget : useNotDropTarget; @@ -66,7 +71,7 @@ export const GridLayoutStackedItem = ({ const draggableProps = useDraggable({ draggableClassName: classBaseItem, getPayload, - onDragStart + onDragStart, }); // const TabstripProps = useMemo(() => ({}), []); @@ -74,12 +79,12 @@ export const GridLayoutStackedItem = ({ const className = cx(classBaseItem, { [`${classBaseItem}-resizeable-h`]: resizeable === "h", [`${classBaseItem}-resizeable-v`]: resizeable === "v", - [`${classBaseItem}-resizeable-vh`]: resizeable === "hv" + [`${classBaseItem}-resizeable-vh`]: resizeable === "hv", }); const style = { ...styleProp, - ...layoutProps + ...layoutProps, }; const stackId = `stack-${id}`; diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/grid-layout-utils.ts b/vuu-ui/packages/vuu-layout/src/grid-layout/grid-layout-utils.ts index d55cbf48d..8dab2fa38 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/grid-layout-utils.ts +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/grid-layout-utils.ts @@ -1,7 +1,6 @@ import { GridLayoutSplitDirection } from "@finos/vuu-utils"; import { GridItemMaps, - GridLayoutModelItem, GridLayoutModelPosition, GridLayoutResizeDirection, IGridLayoutModelItem, @@ -20,7 +19,7 @@ import { const insertTrack = (tracks: number[], trackIndex: number, size = 0) => { if (tracks[trackIndex] < size) { throw Error( - `insertTrack target track ${tracks[trackIndex]} is not large enough to accommodate new track ${size}` + `insertTrack target track ${tracks[trackIndex]} is not large enough to accommodate new track ${size}`, ); } return tracks.reduce((list, track, i) => { @@ -41,7 +40,7 @@ export const splitTrack = (tracks: number[], trackIndex: number) => { export const removeTrackFromTracks = ( tracks: number[], trackIndex: number, - assignDirection: "bwd" | "fwd" = "fwd" + assignDirection: "bwd" | "fwd" = "fwd", ) => { if (trackIndex === tracks.length - 1) { const lastValue = tracks.at(-1) as number; @@ -119,7 +118,7 @@ export const splitTracks = (tracks: number[], start: number, end: number) => { export const getBisectingGridLine = ( tracks: number[], startLine: number, - endLine: number + endLine: number, ) => { if (endLine - startLine > 1) { // Total the sizes between start and end @@ -163,7 +162,7 @@ export type ContrasAndSiblings = { export const getMatchingColspan = ( targetGridItem: IGridLayoutModelItem, siblings: IGridLayoutModelItem[], - contras: IGridLayoutModelItem[] + contras: IGridLayoutModelItem[], ): ContrasAndSiblings | undefined => { const { column } = targetGridItem; const startCol = column.start; @@ -217,7 +216,7 @@ export const getMatchingColspan = ( export const getMatchingRowspan = ( gridItem: IGridLayoutModelItem, siblings: IGridLayoutModelItem[], - contras: IGridLayoutModelItem[] + contras: IGridLayoutModelItem[], ): ContrasAndSiblings | undefined => { const { row } = gridItem; const startRow = row.start; @@ -266,11 +265,11 @@ export const getMatchingRowspan = ( */ export const doesResizeRequireNewTrack = ( splitters: ISplitter[], - splitter: ISplitter + splitter: ISplitter, ) => { const potentialCandidates = splitters.filter( ({ id, orientation }) => - orientation === splitter.orientation && id !== splitter.id + orientation === splitter.orientation && id !== splitter.id, ); if (potentialCandidates.length > 0) { @@ -279,7 +278,7 @@ export const doesResizeRequireNewTrack = ( [track]: { start: splitterStart }, } = splitter; return potentialCandidates.some( - ({ [track]: { start } }) => start === splitterStart + ({ [track]: { start } }) => start === splitterStart, ); } return false; @@ -301,20 +300,20 @@ export const adjustDistance = (moveBy: number, adjustmentAmount: number) => { export const byColumnStart = ( item1: GridLayoutModelItem, - item2: GridLayoutModelItem + item2: GridLayoutModelItem, ) => { return item1.column.start - item2.column.start; }; export const byRowStart = ( item1: GridLayoutModelItem, - item2: GridLayoutModelItem + item2: GridLayoutModelItem, ) => { return item1.row.start - item2.row.start; }; export const gridResizeDirectionFromDropPosition = ( - dropPosition: GridLayoutSplitDirection + dropPosition: GridLayoutSplitDirection, ): GridLayoutResizeDirection => dropPosition === "north" || dropPosition === "south" ? "vertical" @@ -322,7 +321,7 @@ export const gridResizeDirectionFromDropPosition = ( export const getUnusedGridTrackLines = ( gridItemMaps: GridItemMaps, - trackCount: number + trackCount: number, ): number[] => { const unusedStartPositions: number[] = []; const unusedTrackLines: number[] = []; @@ -345,7 +344,7 @@ export const getUnusedGridTrackLines = ( const gridLayoutPositionComparator = ( p1: GridLayoutModelPosition, - p2: GridLayoutModelPosition + p2: GridLayoutModelPosition, ) => { if (p1.start < p2.start) { return -1; @@ -360,17 +359,17 @@ const gridLayoutPositionComparator = ( }; export const byColumnPosition = ( { column: pos1 }: GridLayoutModelItem, - { column: pos2 }: GridLayoutModelItem + { column: pos2 }: GridLayoutModelItem, ) => gridLayoutPositionComparator(pos1, pos2); export const byRowPosition = ( { row: pos1 }: GridLayoutModelItem, - { row: pos2 }: GridLayoutModelItem + { row: pos2 }: GridLayoutModelItem, ) => gridLayoutPositionComparator(pos1, pos2); export const itemsFillColumn = ( items: GridLayoutModelItem[], - pos: GridLayoutModelPosition + pos: GridLayoutModelPosition, ) => { const sortedItems = items.sort(byColumnPosition); const firstItem = sortedItems.at(0); @@ -397,7 +396,7 @@ export const itemsFillColumn = ( }; export const itemsFillRow = ( items: GridLayoutModelItem[], - row: GridLayoutModelPosition + row: GridLayoutModelPosition, ) => { const sortedItems = items.sort(byRowPosition); const firstItem = sortedItems.at(0); diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/react-element-utils.ts b/vuu-ui/packages/vuu-layout/src/grid-layout/react-element-utils.ts index 745cb6cdc..10b4bfd85 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/react-element-utils.ts +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/react-element-utils.ts @@ -1,29 +1,38 @@ import React, { ReactElement } from "react"; +import { StackProps } from "../stack"; -export const getChildComponent = ( - children: ReactElement[], - gridItemId: string +export const getGridItemChild = ( + gridItems: ReactElement[], + gridItemId: string, ): ReactElement => { - const targetGridItem = children.find( - (child) => child.props.id === gridItemId + const targetGridItem = gridItems.find( + (gridItem) => gridItem.props.id === gridItemId, ); if (targetGridItem) { - const childComponent = targetGridItem.props.children; - if (React.isValidElement(childComponent)) { - return childComponent; - } else if (Array.isArray(childComponent)) { - return childComponent.at(0) as ReactElement; - } else { - throw Error(`invalid child component`); - } + return targetGridItem; } else { - throw Error(`getChildComponent #${gridItemId} not found`); + throw Error(`getGridItemChild #${gridItemId} not found`); } }; -export const addChildComponentToStack = ( +export const getGridItemComponent = ( + gridItems: ReactElement[], + gridItemId: string, +): ReactElement => { + const targetGridItem = getGridItemChild(gridItems, gridItemId); + const childComponent = targetGridItem.props.children; + if (React.isValidElement(childComponent)) { + return childComponent; + } else if (Array.isArray(childComponent)) { + return childComponent.at(0) as ReactElement; + } else { + throw Error(`invalid child component`); + } +}; + +export const addChildToStackedGridItem = ( stackElement: ReactElement, - childElement: ReactElement + childElement: ReactElement, ) => { if (Array.isArray(stackElement.props.children)) { console.log(`children is an array`); @@ -33,7 +42,7 @@ export const addChildComponentToStack = ( // can we add an imperative API method to Stack ? return React.cloneElement( stackElement, - {}, - stackChildren.concat(childElement) + { active: stackChildren.length } as StackProps, + stackChildren.concat(childElement), ); }; diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/useAsDropTarget.ts b/vuu-ui/packages/vuu-layout/src/grid-layout/useAsDropTarget.ts index ee111c7a0..ca2d141fb 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/useAsDropTarget.ts +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/useAsDropTarget.ts @@ -41,16 +41,10 @@ export const useAsDropTarget = () => { const onDragEnter = useCallback((evt) => { const target = queryClosest(evt.target, `.${dropTargetClassName}`); - if (target) { - console.log(`enter dropTarget ${evt.target.className} - current dropTarget ${dropTargetRef.current?.className} - `); - } if (target !== dropTargetRef.current) { if (target) { dropTargetRef.current = target; const { bottom, left, right, top } = target.getBoundingClientRect(); - console.log(`set dropTarget to new target ${top} ${bottom}`); targetRectRef.current.bottom = bottom; targetRectRef.current.left = left; targetRectRef.current.right = right; @@ -67,10 +61,7 @@ export const useAsDropTarget = () => { const onDragLeave = useCallback((evt) => { const target = queryClosest(evt.target, `.${dropTargetClassName}`); if (target === evt.target) { - console.log(`drag leave ${target.className}`); - if (target === dropTargetRef.current) { - console.log("set dropTarget ref to undefined"); dropTargetRef.current = undefined; positionRef.current = undefined; } @@ -108,18 +99,15 @@ export const useAsDropTarget = () => { clientY, rect, pctX, - pctY + pctY, ); const { current: lastPosition } = positionRef; - console.log( - `dragOver ${target.className} last postion ${lastPosition} position ${position}` - ); if (position !== lastPosition) { if (dropTargetRef.current) { removedropTargetPositionClassName(target); dropTargetRef.current.classList.add( - `${dropTargetClassName}-${position}` + `${dropTargetClassName}-${position}`, ); } positionRef.current = position; @@ -151,7 +139,6 @@ export const useAsDropTarget = () => { const dropId = evt.dataTransfer.getData("text/plain"); if (dropId) { drop(id, dropId, positionRef.current); - console.log(`useAsDropTarget, reposition ${dropId}`); } else { throw Error("onDrop no payload to drop"); } @@ -161,7 +148,7 @@ export const useAsDropTarget = () => { positionRef.current = undefined; } }, - [drop] + [drop], ); return { diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/useDraggable.ts b/vuu-ui/packages/vuu-layout/src/grid-layout/useDraggable.ts index f76c42b15..678a751f2 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/useDraggable.ts +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/useDraggable.ts @@ -4,20 +4,33 @@ import { MouseEventHandler, useCallback, } from "react"; -import { - GridLayoutDragEndHandler, - GridLayoutDragStartHandler, -} from "./GridLayoutProvider"; +import { GridLayoutDragEndHandler } from "./GridLayoutProvider"; +import { LayoutJSON } from "@finos/vuu-utils"; + +export type DragStartIdOptions = { + id: string; + type: "text/plain"; +}; +export type DragStartJsonOptions = { + payload: LayoutJSON; + type: "text/json"; +}; + +export type GridLayoutDragStartHandler = ( + evt: DragEvent, + dragStartOptions: DragStartIdOptions | DragStartJsonOptions, +) => void; export interface DraggableHookProps { draggableClassName?: string; + getDragImg?: (evt: DragEvent) => HTMLElement; getPayload: (evt: DragEvent) => [string, string]; onDragEnd?: GridLayoutDragEndHandler; onDragStart?: GridLayoutDragStartHandler; } export const useDraggable = ({ - draggableClassName, + getDragImg, getPayload, onDragEnd, onDragStart, @@ -25,31 +38,31 @@ export const useDraggable = ({ const handleDragStart = useCallback>( (e) => { const [type, payload] = getPayload(e); + console.log(`useDraggable drag start payload = ${payload} ${type}`); e.dataTransfer.setData(type, payload); e.dataTransfer.effectAllowed = "move"; - + const dragImg = getDragImg?.(e); + if (dragImg) { + e.dataTransfer.setDragImage(dragImg, 0, 0); + } e.stopPropagation(); - // if (type === "text/plain") { - onDragStart?.(e, payload); - // } + if (type === "text/plain") { + onDragStart?.(e, { id: payload, type }); + } else if (type === "text/json") { + onDragStart?.(e, { payload: JSON.parse(payload), type }); + } }, - [getPayload, onDragStart] + [getDragImg, getPayload, onDragStart], ); const handleDragEnd = useCallback>( (e) => { + console.log("drag end"); (e.target as HTMLElement).classList.remove("dragging"); - console.log( - `%cuseDraggable onDragEnd dropEffect ${e.dataTransfer.dropEffect}`, - "color: brown", - { - e, - } - ); onDragEnd?.(e); }, - [onDragEnd] + [onDragEnd], ); const onMouseDown = useCallback((e) => { diff --git a/vuu-ui/packages/vuu-layout/src/grid-layout/useGridSplitterResizing.tsx b/vuu-ui/packages/vuu-layout/src/grid-layout/useGridSplitterResizing.tsx index 7e7c9d1bd..a1d03e3c1 100644 --- a/vuu-ui/packages/vuu-layout/src/grid-layout/useGridSplitterResizing.tsx +++ b/vuu-ui/packages/vuu-layout/src/grid-layout/useGridSplitterResizing.tsx @@ -18,13 +18,13 @@ import { layoutFromJson, IGridLayoutModelItem, gridResizeDirectionFromDropPosition, - GridLayoutDragStartHandler + GridLayoutDragStartHandler, } from "@finos/vuu-layout"; import { LayoutJSON, asReactElements, queryClosest, - uuid + uuid, } from "@finos/vuu-utils"; import React, { MouseEventHandler, @@ -33,7 +33,7 @@ import React, { useLayoutEffect, useMemo, useRef, - useState + useState, } from "react"; import { classNameLayoutItem, @@ -43,14 +43,15 @@ import { isSplitter, setGridColumn, setGridRow, - setGridTrackTemplate + setGridTrackTemplate, } from "@finos/vuu-layout"; import { GridLayoutProps } from "./GridLayout"; import { GridLayoutItem, GridLayoutItemProps } from "./GridLayoutItem"; import { GridLayoutStackedItem } from "./GridLayoutStackedtem"; import { - addChildComponentToStack, - getChildComponent + addChildToStackedGridItem, + getGridItemChild, + getGridItemComponent, } from "./react-element-utils"; export type SplitterResizingHookProps = Pick< @@ -77,7 +78,7 @@ type NonContentGridItems = { const buildLayoutMap = ( children: | ReactElement[] - | ReactElement + | ReactElement, ): GridLayoutMap => { const layoutMap: GridLayoutMap = {}; React.Children.forEach( @@ -85,16 +86,16 @@ const buildLayoutMap = ( ({ props: { id, - style: { gridColumnEnd, gridColumnStart, gridRowEnd, gridRowStart } - } + style: { gridColumnEnd, gridColumnStart, gridRowEnd, gridRowStart }, + }, }) => { layoutMap[id] = { gridColumnEnd, gridColumnStart, gridRowEnd, - gridRowStart + gridRowStart, }; - } + }, ); return layoutMap; }; @@ -105,21 +106,21 @@ export const useGridSplitterResizing = ({ cols = Array(colCount).fill("1fr"), onClick: onClickProp, rowCount, - rows = Array(rowCount).fill("1fr") + rows = Array(rowCount).fill("1fr"), }: SplitterResizingHookProps) => { // TODO memoize this call const [children, setChildren] = useState[]>( - asReactElements(childrenProp) + asReactElements(childrenProp), ); const [nonContentGridItems, setNonContentGridItems] = useState({ splitters: [], - placeholders: [] + placeholders: [], }); const layoutModel = useMemo( // TODO must cater for colCount/rowCount changing () => new GridLayoutModel(cols.length, rows.length), - [cols.length, rows.length] + [cols.length, rows.length], ); const layoutMapRef = useRef({}); useMemo(() => { @@ -130,7 +131,7 @@ export const useGridSplitterResizing = ({ ( id: string, { start, end }: GridLayoutModelPosition, - resizeDirection: GridLayoutResizeDirection + resizeDirection: GridLayoutResizeDirection, ) => { const gridItemStyle = layoutMapRef.current[id]; if (gridItemStyle) { @@ -144,7 +145,7 @@ export const useGridSplitterResizing = ({ } } }, - [] + [], ); const containerRef = useRef(null); const resizingState = useRef(); @@ -153,7 +154,7 @@ export const useGridSplitterResizing = ({ ( resizeDirection: GridLayoutResizeDirection, updates: [string, GridLayoutModelPosition][], - resetSplitters = false + resetSplitters = false, ) => { const setTrack = resizeDirection === "vertical" ? setGridRow : setGridColumn; @@ -168,7 +169,7 @@ export const useGridSplitterResizing = ({ setNonContentGridItems((items) => ({ ...items, splitters })); } }, - [layoutModel, setGridLayoutMap] + [layoutModel, setGridLayoutMap], ); const initiateResize = useCallback( @@ -187,7 +188,7 @@ export const useGridSplitterResizing = ({ newTrackIndex, Math.abs(moveBy), resizeOperation, - state + state, ); if (resizeOperation === "contract") { @@ -200,14 +201,14 @@ export const useGridSplitterResizing = ({ state.resizeTrackIsShared = false; } }, - [applyUpdates, layoutModel] + [applyUpdates, layoutModel], ); const removeTrack = useCallback( ( moveBy: number, resizeOperation: GridLayoutResizeOperation, - nextResizeOperation: GridLayoutResizeOperation | null + nextResizeOperation: GridLayoutResizeOperation | null, ) => { const { current: state } = resizingState; let restoredDistance = 0; @@ -216,7 +217,7 @@ export const useGridSplitterResizing = ({ resizeDirection, resizeTrackIndex, contraTrackIndex, - resizeTrackIsShared: resizeRequiresNewTrack + resizeTrackIsShared: resizeRequiresNewTrack, } = state; const targetTrack = @@ -239,7 +240,7 @@ export const useGridSplitterResizing = ({ const newTracks = removeTrackFromTracks( currentTracks, targetTrack, - assignDirection + assignDirection, ); const updates = layoutModel.removeTrack(targetTrack, resizeDirection); @@ -260,7 +261,7 @@ export const useGridSplitterResizing = ({ } } }, - [applyUpdates, initiateResize, layoutModel] + [applyUpdates, initiateResize, layoutModel], ); const resize = useCallback( @@ -290,7 +291,7 @@ export const useGridSplitterResizing = ({ } } }, - [removeTrack] + [removeTrack], ); const mouseMove = useCallback( @@ -311,7 +312,7 @@ export const useGridSplitterResizing = ({ } } }, - [initiateResize, resize] + [initiateResize, resize], ); // TODO need to identify the expanding track and the contracting track @@ -325,7 +326,7 @@ export const useGridSplitterResizing = ({ } const resizeDirection: GridLayoutResizeDirection = isHorizontalSplitter( - splitterElement + splitterElement, ) ? "horizontal" : "vertical"; @@ -336,14 +337,14 @@ export const useGridSplitterResizing = ({ if (!grid || !resizeElement) { throw Error( - `cannot find either grid or element associated with Splitter` + `cannot find either grid or element associated with Splitter`, ); } const mousePos = resizeDirection === "vertical" ? e.clientY : e.clientX; const resizeItem = layoutModel.gridItems.find( - (item) => item.id === resizeElement.id + (item) => item.id === resizeElement.id, ); if (!resizeItem) { @@ -356,7 +357,7 @@ export const useGridSplitterResizing = ({ resizeDirection, resizeItem, splitterElement, - mousePos + mousePos, }); if (resizeDirection === "vertical") { @@ -368,7 +369,7 @@ export const useGridSplitterResizing = ({ document.addEventListener("mousemove", mouseMove); } }, - [layoutModel, mouseMove] + [layoutModel, mouseMove], ); const onMouseUp = useCallback( @@ -379,7 +380,7 @@ export const useGridSplitterResizing = ({ // console.log(layoutModel.toDebugString()); }, - [mouseMove] + [mouseMove], ); const selectedRef = useRef(); @@ -398,7 +399,7 @@ export const useGridSplitterResizing = ({ if (selectedRef.current) { const el = document.getElementById( - selectedRef.current + selectedRef.current, ) as HTMLElement; el.classList.remove(`${classNameLayoutItem}-active`); } @@ -409,7 +410,7 @@ export const useGridSplitterResizing = ({ } onClickProp?.(e); }, - [onClickProp] + [onClickProp], ); const splitGridCol = useCallback( @@ -422,7 +423,7 @@ export const useGridSplitterResizing = ({ const { tracks, updates } = layoutModel.splitGridItem( gridItemId, "west", - columns + columns, ); if (updates.length > 0) { setGridTrackTemplate({ grid, resizeDirection: "horizontal" }, tracks); @@ -439,7 +440,7 @@ export const useGridSplitterResizing = ({ throw Error(`splitGridCol no gridItem with id ${gridItemId}`); } }, - [applyUpdates, layoutModel] + [applyUpdates, layoutModel], ); const splitGridRow = useCallback( @@ -452,7 +453,7 @@ export const useGridSplitterResizing = ({ const { tracks, updates } = layoutModel.splitGridItem( gridItemId, "north", - rows + rows, ); if (updates.length > 0) { @@ -469,7 +470,7 @@ export const useGridSplitterResizing = ({ throw Error(`splitGridRow no gridItem with id ${gridItemId}`); } }, - [applyUpdates, layoutModel] + [applyUpdates, layoutModel], ); const addGridColumn = useCallback( @@ -479,13 +480,13 @@ export const useGridSplitterResizing = ({ const resizeDirection = "horizontal"; if (grid && gridItem) { const { - column: { start } + column: { start }, } = gridItem; const trackIndex = start - 1; const columns = splitTrack(getColumns(grid), trackIndex); setGridTrackTemplate( { grid, resizeDirection: resizeDirection }, - columns + columns, ); const updates = layoutModel.addTrack(trackIndex, resizeDirection); if (updates.length > 0) { @@ -495,7 +496,7 @@ export const useGridSplitterResizing = ({ throw Error(`addGridTrack no gridItem with id ${gridItemId}`); } }, - [applyUpdates, layoutModel] + [applyUpdates, layoutModel], ); const addGridRow = useCallback((gridItemId: string) => { @@ -511,17 +512,17 @@ export const useGridSplitterResizing = ({ const columns = removeTrackFromTracks(getColumns(grid), trackIndex); setGridTrackTemplate( { grid, resizeDirection: resizeDirection }, - columns + columns, ); console.log(`removeGridColumn at ${trackIndex}`, { - columns + columns, }); const updates = layoutModel.removeTrack(trackIndex, resizeDirection); applyUpdates(resizeDirection, updates, true); } }, - [applyUpdates, layoutModel] + [applyUpdates, layoutModel], ); useLayoutEffect(() => { @@ -540,7 +541,7 @@ export const useGridSplitterResizing = ({ fixed, resizeable, row, - type + type, }); } }); @@ -564,11 +565,11 @@ export const useGridSplitterResizing = ({ const newTracks = removeTrackFromTracks( columnTracks, indexOfRemovedColumnTrack - 1, - "bwd" + "bwd", ); setGridTrackTemplate( { grid, resizeDirection: "horizontal" }, - newTracks + newTracks, ); } @@ -578,16 +579,16 @@ export const useGridSplitterResizing = ({ const newTracks = removeTrackFromTracks( rowTracks, indexOfRemovedRowTrack - 1, - "bwd" + "bwd", ); setGridTrackTemplate( { grid, resizeDirection: "vertical" }, - newTracks + newTracks, ); } } }, - [] + [], ); const removeGridItem = useCallback( @@ -601,7 +602,7 @@ export const useGridSplitterResizing = ({ } const { removedTrackLines, - updates: [horizontalUpdates, verticalUpdates] + updates: [horizontalUpdates, verticalUpdates], } = layoutModel.removeGridItem(id); applyUpdates("horizontal", horizontalUpdates); @@ -615,7 +616,7 @@ export const useGridSplitterResizing = ({ const splitters = layoutModel.getSplitterPositions(); setNonContentGridItems({ placeholders, splitters }); }, - [applyUpdates, layoutModel, removeTracks] + [applyUpdates, layoutModel, removeTracks], ); const dispatchGridLayoutAction = useCallback( @@ -624,19 +625,19 @@ export const useGridSplitterResizing = ({ removeGridItem(action.id); } }, - [removeGridItem] + [removeGridItem], ); const addChildComponent = useCallback( ( component: JSX.Element, - { column, id, row, type }: IGridLayoutModelItem + { column, id, row, type }: IGridLayoutModelItem, ) => { if (type === "stacked-content") { - const stack = getChildComponent(children, id); - const newChild = addChildComponentToStack(stack, component); + const stackedGridItem = getGridItemChild(children, id); + const newChild = addChildToStackedGridItem(stackedGridItem, component); setChildren((c) => - c.map((child) => (child.props.id === id ? newChild : child)) + c.map((child) => (child.props.id === id ? newChild : child)), ); } else { const newChild = ( @@ -649,7 +650,7 @@ export const useGridSplitterResizing = ({ gridColumnStart: column.start, gridColumnEnd: column.end, gridRowStart: row.start, - gridRowEnd: row.end + gridRowEnd: row.end, }} title="New One" > @@ -659,13 +660,13 @@ export const useGridSplitterResizing = ({ setChildren((c) => c.concat(newChild)); } }, - [children] + [children], ); const replaceChildComponent = useCallback( ( component: JSX.Element, - { column, id, row, type }: IGridLayoutModelItem + { column, id, row, type }: IGridLayoutModelItem, ) => { const props: Pick & { key: string; @@ -677,14 +678,14 @@ export const useGridSplitterResizing = ({ gridColumnStart: column.start, gridColumnEnd: column.end, gridRowStart: row.start, - gridRowEnd: row.end - } + gridRowEnd: row.end, + }, }; const newChild = type === "stacked-content" ? ( - {[getChildComponent(children, id), component]} + {[getGridItemComponent(children, id), component]} ) : ( @@ -692,10 +693,10 @@ export const useGridSplitterResizing = ({ ); setChildren((c) => - c.map((child) => (child.props.id === id ? newChild : child)) + c.map((child) => (child.props.id === id ? newChild : child)), ); }, - [children] + [children], ); const handleDragEnd = useCallback(() => { @@ -706,17 +707,18 @@ export const useGridSplitterResizing = ({ }, []); const handleDragStart = useCallback( - (evt, id) => { + (evt, options) => { const { current: grid } = containerRef; if (grid) { - grid.classList.add("vuuDragging"); requestAnimationFrame(() => { - // beginDragGridItem - release from all positioning structures but no need to delete griditem - removeGridItem(id, false); + grid.classList.add("vuuDragging"); + if (options.type === "text/plain") { + removeGridItem(options.id, false); + } }); } }, - [removeGridItem] + [removeGridItem], ); /** @@ -730,37 +732,72 @@ export const useGridSplitterResizing = ({ if (grid) { if (typeof payload === "string") { - const gridItem = layoutModel.getGridItem(payload); + // repositioning existing layout item const gridItemElement = document.getElementById(payload); - // TODO ... - console.log({ - gridItem, - gridItemElement - }); + if (gridItemElement) { + gridItemElement.classList.remove("vuuGridLayoutItem-dragging"); + + console.log(`drop at position ${position}`); + if (position === "tabs") { + console.log(`what are we going to do now ?`); + } else if (position === "centre") { + const newGridItem = layoutModel.replaceGridItem( + targetId, + "content", + ); + + setGridColumn(newGridItem.id, newGridItem.column); + setGridRow(newGridItem.id, newGridItem.row); + + // if target is not a placeholder, remove the child gridItem + } else { + const resizeDirection = + gridResizeDirectionFromDropPosition(position); + const currentTracks = + resizeDirection === "vertical" + ? getRows(grid) + : getColumns(grid); + + const { tracks, updates } = layoutModel.splitGridItem( + targetId, + position, + currentTracks, + payload, + ); + + if (updates.length > 0) { + setGridTrackTemplate({ grid, resizeDirection }, tracks); + applyUpdates(resizeDirection, updates); + // addChildComponent(component, newGridItem); + } + + const placeholders = layoutModel.getPlaceholders(); + const splitters = layoutModel.getSplitterPositions(); + setNonContentGridItems({ placeholders, splitters }); + } + } } else { + // dragging from palette or similar const { type } = targetGridItem; // TODO look at how we manage component id values const id = uuid(); const component = layoutFromJson( { ...payload, id } as LayoutJSON, - "" + "", ); if (position === "centre") { const newGridItem = layoutModel.replaceGridItem( targetId, - "content" + "content", ); - if (type === "placeholder") { - addChildComponent(component, newGridItem); - } else { - replaceChildComponent(component, newGridItem); - } + console.log(`drop ${position} ${type}`, { newGridItem }); + addChildComponent(component, newGridItem); } else if (position === "tabs") { if (type === "content") { // all this does is change the type const newGridItem = layoutModel.replaceGridItem( targetId, - "stacked-content" + "stacked-content", ); replaceChildComponent(component, newGridItem); } else if (type === "stacked-content") { @@ -777,7 +814,7 @@ export const useGridSplitterResizing = ({ const { tracks, updates, newGridItem } = layoutModel.splitGridItem( targetId, position, - currentTracks + currentTracks, ); if (updates.length > 0) { @@ -795,7 +832,7 @@ export const useGridSplitterResizing = ({ throw Error(`splitGridRow no gridItem with id ${targetId}`); } }, - [addChildComponent, applyUpdates, layoutModel, replaceChildComponent] + [addChildComponent, applyUpdates, layoutModel, replaceChildComponent], ); return { @@ -816,6 +853,6 @@ export const useGridSplitterResizing = ({ removeGridColumn, splitGridCol, splitGridRow, - nonContentGridItems + nonContentGridItems, }; }; diff --git a/vuu-ui/showcase/src/examples/html/components/DebugGridItem.tsx b/vuu-ui/showcase/src/examples/html/components/DebugGridItem.tsx index 57948c7e5..fa6a768ec 100644 --- a/vuu-ui/showcase/src/examples/html/components/DebugGridItem.tsx +++ b/vuu-ui/showcase/src/examples/html/components/DebugGridItem.tsx @@ -1,10 +1,4 @@ -import { - HTMLAttributes, - RefCallback, - useCallback, - useLayoutEffect, - useRef, -} from "react"; +import { HTMLAttributes, RefCallback, useCallback, useRef } from "react"; export interface DebugGridItemProps extends HTMLAttributes { debugLabel?: string; @@ -19,20 +13,5 @@ export const DebugGridItem = ({ debugLabel = "", ...htmlAttributes }) => { ref.current = el; }, []); - const displayStats = useCallback(() => { - const style = getComputedStyle(ref.current as HTMLDivElement); - const width = style.getPropertyValue("width"); - console.log(`${pad(debugLabel, 8)}width = ${width}`); - }, [debugLabel]); - - useLayoutEffect(() => displayStats(), [debugLabel, displayStats]); - - return ( -
- ); + return
; };