Skip to content

Commit

Permalink
fix: undo/redo of component creation [PT-187949749][PT-187948687] (#1347
Browse files Browse the repository at this point in the history
)

* fix: undo/redo of component creation

* fix: MST warning on undo create case table
  • Loading branch information
kswenson authored Jul 13, 2024
1 parent 128ab00 commit 2530813
Show file tree
Hide file tree
Showing 9 changed files with 42 additions and 55 deletions.
16 changes: 2 additions & 14 deletions v3/src/components/case-table-card-common/case-table-card-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { getSharedDataSetFromDataSetId, getTileCaseMetadata } from "../../models
import { getTileComponentInfo } from "../../models/tiles/tile-component-info"
import { getSharedModelManager, getTileEnvironment } from "../../models/tiles/tile-environment"
import { uiState } from "../../models/ui-state"
import { ComponentRect } from "../../utilities/animation-utils"
import { getPositionOfNewComponent } from "../../utilities/view-utils"
import { kTitleBarHeight } from "../constants"
import { kCaseTableTileType } from "../case-table/case-table-defs"
Expand Down Expand Up @@ -53,20 +52,9 @@ export function createTableOrCardForDataset (
let {x, y} = getPositionOfNewComponent({width, height})
if (options?.x != null) x = options.x
if (options?.y != null) y = options.y
const from: ComponentRect = { x: 0, y: 0, width: 0, height: kTitleBarHeight },
to: ComponentRect = { x, y, width, height: height + kTitleBarHeight}
content?.insertTileInRow(tile, row, from)
const tileOptions = { x, y, width, height: height + kTitleBarHeight, animateCreation: options?.animateCreation }
content?.insertTileInRow(tile, row, tileOptions)
uiState.setFocusedTile(tile.id)
const tileLayout = content.getTileLayoutById(tile.id)
if (!isFreeTileLayout(tileLayout)) return
// use setTimeout to push the change into a subsequent action
setTimeout(() => {
// use applyModelChange to wrap into a single non-undoable action without undo string
content.applyModelChange(() => {
tileLayout.setPosition(to.x, to.y)
tileLayout.setSize(to.width, to.height)
})
})

return tile
}
Expand Down
10 changes: 6 additions & 4 deletions v3/src/components/case-table/case-table-tool-shelf-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Button, Menu, MenuButton, MenuItem, MenuList, ModalBody, ModalFooter,
import { observer } from "mobx-react-lite"
import { appState } from "../../models/app-state"
import { createDefaultTileOfType } from "../../models/codap/add-default-content"
import { INewTileOptions } from "../../models/codap/create-tile"
import { gDataBroker } from "../../models/data/data-broker"
import { DataSet, toCanonical } from "../../models/data/data-set"
import {
Expand Down Expand Up @@ -36,18 +37,19 @@ export const CaseTableToolShelfMenuList = observer(function CaseTableToolShelfMe

if (!content) return null

const handleCreateNewDataSet = () => {
const handleCreateNewCaseTable = () => {
document.applyModelChange(() => {
const newData = [{ AttributeName: "" }]
const ds = DataSet.create({ name: t("DG.AppController.createDataSet.name")})
ds.addAttribute({ name: t("DG.AppController.createDataSet.initialAttribute") })
ds.addCases(toCanonical(ds, newData))
const tile = createDefaultTileOfType(kCaseTableTileType)
const options: INewTileOptions = { animateCreation: true }
const tile = createDefaultTileOfType(kCaseTableTileType, options)
if (!tile) return
const { sharedData, caseMetadata } = gDataBroker.addDataSet(ds, tile.id)
// Add dataset to the formula manager
getFormulaManager(document)?.addDataSet(ds)
createTableOrCardForDataset(sharedData, caseMetadata)
createTableOrCardForDataset(sharedData, caseMetadata, kCaseTableTileType, options)
}, {
notifications: dataContextCountChangedNotification,
undoStringKey: "V3.Undo.caseTable.create",
Expand Down Expand Up @@ -78,7 +80,7 @@ export const CaseTableToolShelfMenuList = observer(function CaseTableToolShelfMe
<MenuItem data-testid="tool-shelf-table-new-clipboard">
{t("DG.AppController.caseTableMenu.clipboardDataset")}
</MenuItem>
<MenuItem onClick={handleCreateNewDataSet} data-testid="tool-shelf-table-new">
<MenuItem onClick={handleCreateNewCaseTable} data-testid="tool-shelf-table-new">
{t("DG.AppController.caseTableMenu.newDataSet")}
</MenuItem>
</MenuList>
Expand Down
23 changes: 16 additions & 7 deletions v3/src/components/container/free-tile-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,31 @@ import { kTitleBarHeight } from "../constants"
import { urlParams } from "../../utilities/url-params"

interface IProps {
row: IFreeTileRow;
tile: ITileModel;
onCloseTile: (tileId: string) => void;
row: IFreeTileRow
tile: ITileModel
onCloseTile: (tileId: string) => void
}

export const FreeTileComponent = observer(function FreeTileComponent({ row, tile, onCloseTile}: IProps) {
const { active } = useDndContext()
const { id: tileId, content: { type: tileType } } = tile
const [useDefaultCreationStyle, setUseDefaultCreationStyle] = useState(row.animateCreationTiles.has(tileId))
const [resizingTileStyle, setResizingTileStyle] =
useState<{left: number, top: number, width?: number, height?: number, zIndex?: number, transition: string}>()
const [resizingTileId, setResizingTileId] = useState("")
const tileId = tile.id
const tileType = tile.content.type
const rowTile = row.tiles.get(tileId)
const { x: left, y: top, width, height, zIndex } = rowTile || {}
const { active } = useDndContext()
const tileStyle: React.CSSProperties = { left, top, width, height, zIndex }
// when animating creation, use the default creation style on the first render
const tileStyle: React.CSSProperties = useDefaultCreationStyle
? { left: 0, top: 0, width: 0, height: kTitleBarHeight, zIndex }
: { left, top, width, height, zIndex }
const draggableOptions: IUseDraggableTile = { prefix: tileType || "tile", tileId }

useEffect(() => {
// after the first render, render the actual style; CSS transitions will handle the animation
setUseDefaultCreationStyle(false)
}, [])

const {setNodeRef, transform} = useDraggableTile(draggableOptions,
activeDrag => {
const dragTileId = getDragTileId(activeDrag)
Expand Down
2 changes: 1 addition & 1 deletion v3/src/components/tool-shelf/tool-shelf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const ToolShelf = observer(function ToolShelf({ document }: IProps) {
}
const [undoStringKey = "", redoStringKey = ""] = undoRedoStringKeysMap[tileType] || []
document?.content?.applyModelChange(() => {
document?.content?.createOrShowTile?.(tileType)
document?.content?.createOrShowTile?.(tileType, { animateCreation: true })
}, { undoStringKey, redoStringKey })
}

Expand Down
1 change: 1 addition & 0 deletions v3/src/models/codap/create-tile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ITileEnvironment } from "../tiles/tile-environment"
import { TileModel } from "../tiles/tile-model"

export interface INewTileOptions {
animateCreation?: boolean
cannotClose?: boolean
content?: ITileContentSnapshotWithType
title?: string
Expand Down
23 changes: 4 additions & 19 deletions v3/src/models/document/document-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { getTileComponentInfo } from "../tiles/tile-component-info"
import { getFormulaManager, getSharedModelManager, getTileEnvironment } from "../tiles/tile-environment"
import { getTileContentInfo } from "../tiles/tile-content-info"
import { ITileModel } from "../tiles/tile-model"
import { ComponentRect } from "../../utilities/animation-utils"
import { getPositionOfNewComponent } from "../../utilities/view-utils"
import { t } from "../../utilities/translation/translate"
import { createTileSnapshotOfType, INewTileOptions } from "../codap/create-tile"
Expand Down Expand Up @@ -112,6 +111,7 @@ export const DocumentContentModel = BaseDocumentContentModel
createTile(tileType: string, options?: INewTileOptions): ITileModel | undefined {
const componentInfo = getTileComponentInfo(tileType)
if (!componentInfo) return
const animateCreation = options?.animateCreation ?? false
const width = options?.width ?? (componentInfo.defaultWidth || 0)
const height = options?.height ?? (componentInfo.defaultHeight || 0)
const row = self.getRowByIndex(0)
Expand All @@ -124,23 +124,8 @@ export const DocumentContentModel = BaseDocumentContentModel
const computedPosition = getPositionOfNewComponent(newTileSize)
const x = options?.x ?? computedPosition.x
const y = options?.y ?? computedPosition.y
const from: ComponentRect = { x: 0, y: 0, width: 0, height: kTitleBarHeight },
to: ComponentRect = { x, y, width, height: height + kTitleBarHeight}
const newTile = self.insertTileSnapshotInRow(newTileSnapshot, row, from)
if (newTile) {
const rowTile = row.tiles.get(newTile.id)
if (width && height && rowTile) {
// use setTimeout to push the change into a subsequent action
setTimeout(() => {
// use applyModelChange to wrap into a single non-undoable action without undo string
self.applyModelChange(() => {
rowTile.setPosition(to.x, to.y)
rowTile.setSize(to.width, to.height)
})
})
}
return newTile
}
const tileOptions = { x, y, width, height: height + kTitleBarHeight, animateCreation }
return self.insertTileSnapshotInRow(newTileSnapshot, row, tileOptions)
}
}
}
Expand Down Expand Up @@ -174,7 +159,7 @@ export const DocumentContentModel = BaseDocumentContentModel
if (options?.title != null) {
tile.setTitle(options.title)
}

// Change visibility of existing tile
const tileLayout = self.getTileLayoutById(tile.id)
if (isFreeTileLayout(tileLayout)) {
Expand Down
11 changes: 9 additions & 2 deletions v3/src/models/document/free-tile-row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export interface IFreeTileInRowOptions extends ITileInRowOptions {
width?: number
height?: number
zIndex?: number
animateCreation?: boolean
}
export const isFreeTileInRowOptions = (options?: ITileInRowOptions): options is IFreeTileInRowOptions =>
!!options && ("x" in options && options.x != null) && ("y" in options && options.y != null)
Expand All @@ -81,6 +82,10 @@ export const FreeTileRow = TileRowModel
tiles: types.map(FreeTileLayout), // tile id => layout
maxZIndex: 0
})
.volatile(self => ({
// tile ids of tiles created by user whose creation should be animated
animateCreationTiles: new Set<string>()
}))
.views(self => ({
get acceptDefaultInsert() {
return true
Expand Down Expand Up @@ -131,9 +136,11 @@ export const FreeTileRow = TileRowModel
self.maxZIndex = zIndex
},
insertTile(tileId: string, options?: ITileInRowOptions) {
const { x = 50, y = 50, width = undefined, height = undefined, zIndex = this.nextZIndex() } =
isFreeTileInRowOptions(options) ? options : {}
const {
x = 50, y = 50, width = undefined, height = undefined, zIndex = this.nextZIndex(), animateCreation = false
} = isFreeTileInRowOptions(options) ? options : {}
self.tiles.set(tileId, { tileId, x, y, width, height, zIndex })
animateCreation && self.animateCreationTiles.add(tileId)
},
removeTile(tileId: string) {
self.tiles.delete(tileId)
Expand Down
4 changes: 3 additions & 1 deletion v3/src/models/formula/formula-manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { comparer, makeObservable, observable, reaction, action } from "mobx"
import { isAlive } from "mobx-state-tree"
import { addDisposer, isAlive } from "mobx-state-tree"
import { ICase } from "../data/data-set-types"
import { CaseList } from "./formula-types"
import { IDataSet } from "../data/data-set"
Expand Down Expand Up @@ -83,6 +83,8 @@ export class FormulaManager {

@action addDataSet(dataSet: IDataSet) {
this.dataSets.set(dataSet.id, dataSet)
// remove the DataSet if it is destroyed
addDisposer(dataSet, () => this.removeDataSet(dataSet.id))
}

@action removeDataSet(dataSetId: string) {
Expand Down
7 changes: 0 additions & 7 deletions v3/src/utilities/animation-utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
export const kDefaultAnimationDuration = 500
export interface ComponentRect {
x: number
y: number
width: number
height: number
[key: string]: number
}

export const interpolateEaseInOut = <T extends Record<string, number>>(duration: number, from: T, to: T) => {
return (time: number) => {
Expand Down

0 comments on commit 2530813

Please sign in to comment.