-
Notifications
You must be signed in to change notification settings - Fork 369
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support multi-line node block in Visual Editor (#2005)
* draft: DynamicBlock * don't setState in effect hook * set default demo to editor * give decorators unique id * implement ElementMeasurer for dynamic size * pass onResize handler to enable boundary propagation * update FormCard css to not truncate text * apply ElementMeasurer to necessary widgets * css: word break in FormCard * support smart layout in all input types * revert exp changes in uischema * add new hook useSmartLayout for layout changes * apply useSmartLayouter to PromptWidgets * apply useSmartLayout hook to IfConditionWidget * include boundary merge logic in useSmartLayout * IfCondition, Prompt adapt to new smart layout hook * apply useSmartLayout to ForeachWidget * apply useSmartLayout to SwitchConditionWidget * add comments to ElementMeasurer * add comments in useSmartLayout * revert ObiEditor changes * update FormCard style * revert unnecessary changes * revert unnecessary transformer changes * fix IfConditionWidget layout * fix a potential undefined func call * improve ts type in Switch * sort Switch case keys * StepGroup apply useSmartLayouter * feat: boundary cache mechanism to fix flickering * remove size limit in uischema (deprecated by smart layouter) * fix FormCard css compatibility Co-authored-by: Andy Brown <asbrown002@gmail.com>
- Loading branch information
1 parent
9eeca92
commit 7b452d3
Showing
15 changed files
with
397 additions
and
190 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
Composer/packages/extensions/visual-designer/src/components/renderers/ElementMeasurer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
import React from 'react'; | ||
import Measure from 'react-measure'; | ||
|
||
import { Boundary } from '../../models/Boundary'; | ||
|
||
export interface ElementMeasurerProps { | ||
children: React.ReactNode; | ||
style?: React.CSSProperties; | ||
onResize: (boundary: Boundary) => void; | ||
} | ||
|
||
/** | ||
* Notify a ReactNode's size once its size has been changed. | ||
* Remember to use it inside the focus border component (ElementWrapper). | ||
*/ | ||
export const ElementMeasurer: React.FC<ElementMeasurerProps> = ({ children, style, onResize }) => { | ||
return ( | ||
<Measure | ||
bounds | ||
onResize={({ bounds: { width, height } }) => { | ||
onResize(new Boundary(width, height)); | ||
}} | ||
> | ||
{({ measureRef }) => ( | ||
<div ref={measureRef} style={style}> | ||
{children} | ||
</div> | ||
)} | ||
</Measure> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
Composer/packages/extensions/visual-designer/src/hooks/useSmartLayout.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
import { useState, useEffect, useMemo } from 'react'; | ||
|
||
import { Boundary, areBoundariesEqual } from '../models/Boundary'; | ||
import { GraphLayout } from '../models/GraphLayout'; | ||
import { GraphNode } from '../models/GraphNode'; | ||
|
||
// 'T extends string' means an Enum. Reference: https://github.com/microsoft/TypeScript/issues/30611#issuecomment-565384924 | ||
type MapWithEnumKey<KeyType extends string, ValueType> = { [key in KeyType]: ValueType }; | ||
|
||
type BoundaryMap<T extends string> = MapWithEnumKey<T, Boundary>; | ||
|
||
export type GraphNodeMap<T extends string> = MapWithEnumKey<T, GraphNode>; | ||
|
||
export function useSmartLayout<T extends string>( | ||
nodeMap: GraphNodeMap<T>, | ||
layouter: (nodeMap: GraphNodeMap<T>) => GraphLayout, | ||
onResize: (boundary: Boundary) => void | ||
): { | ||
layout: GraphLayout; | ||
updateNodeBoundary: (nodeName: T, boundary: Boundary) => void; | ||
} { | ||
const [boundaryMap, setBoundaryMap] = useState<BoundaryMap<T>>({} as BoundaryMap<T>); | ||
/** | ||
* The object `accumulatedPatches` is used to collect all accumulated | ||
* boundary changes happen in a same JS event cyle. After collecting | ||
* them together, they will be submitted to component states to guide | ||
* next redraw. | ||
* | ||
* We shouldn't use `setState()` here because of `patchBoundary` may be | ||
* fired multiple times (especially at the init render cycle), changes | ||
* will be lost by using `setState()`; | ||
* | ||
* We shouldn't use `useRef` here since `accumulatedPatches` as a local | ||
* cache needs to be cleared after taking effect in one redraw. | ||
*/ | ||
const accumulatedPatches = {}; | ||
const patchBoundary = (nodeName: string, boundary: Boundary) => { | ||
if (!boundaryMap[nodeName] || !areBoundariesEqual(boundaryMap[nodeName], boundary)) { | ||
accumulatedPatches[nodeName] = boundary; | ||
setBoundaryMap({ | ||
...boundaryMap, | ||
...accumulatedPatches, | ||
}); | ||
} | ||
}; | ||
|
||
const layout = useMemo(() => { | ||
// write updated boundaries to nodes | ||
Object.keys(nodeMap).map(nodeName => { | ||
const node = nodeMap[nodeName]; | ||
if (node) { | ||
node.boundary = boundaryMap[nodeName] || node.boundary; | ||
} | ||
}); | ||
return layouter(nodeMap); | ||
}, [nodeMap, boundaryMap]); | ||
|
||
useEffect(() => { | ||
onResize && onResize(layout.boundary); | ||
}, [layout]); | ||
|
||
return { | ||
layout, | ||
updateNodeBoundary: patchBoundary, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
Composer/packages/extensions/visual-designer/src/store/DesignerCache.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
import { BaseSchema } from '@bfc/shared'; | ||
import get from 'lodash/get'; | ||
|
||
import { Boundary } from '../models/Boundary'; | ||
|
||
const MAX_CACHE_SIZE = 99999; | ||
|
||
export class DesignerCache { | ||
private boundaryCache = {}; | ||
private cacheSize = 0; | ||
|
||
private getActionDataHash(actionData: BaseSchema): string | null { | ||
const designerId = get(actionData, '$designer.id', ''); | ||
if (!designerId) return null; | ||
|
||
const $type = get(actionData, '$type'); | ||
return `${$type}-${designerId}`; | ||
} | ||
|
||
cacheBoundary(actionData: BaseSchema, boundary: Boundary): boolean { | ||
const key = this.getActionDataHash(actionData); | ||
if (!key) { | ||
return false; | ||
} | ||
|
||
if (this.cacheSize > MAX_CACHE_SIZE) { | ||
delete this.boundaryCache; | ||
this.boundaryCache = {}; | ||
this.cacheSize = 0; | ||
} | ||
this.boundaryCache[key] = boundary; | ||
this.cacheSize += 1; | ||
return true; | ||
} | ||
|
||
loadBounary(actionData: BaseSchema): Boundary | undefined { | ||
const key = this.getActionDataHash(actionData); | ||
if (key) { | ||
return this.boundaryCache[key]; | ||
} | ||
} | ||
} | ||
|
||
export const designerCache = new DesignerCache(); |
Oops, something went wrong.