Skip to content

Commit

Permalink
Update to iTwin 4.7.2 (and newer appui) (#73)
Browse files Browse the repository at this point in the history
* Update to iTwin 4.7.2 (and newer appui)
For some unknown reason, this caused our three remaining class-based React components to fail to compile, so those were converted to functional components.

* Update @types/react and fix lint errors

* Revert to class-based TouchCaptor and TouchDragHandle

* Finish functional to class reversion

* Remove compile error reference that is no longer correct

* Update @types/react-dom to version 17

* Reproduce public ActionSheetButton.onClick.

* Document ActionSheetButton.onClick

* Add comment about usePointerCaptor
  • Loading branch information
tcobbs-bentley authored Jun 25, 2024
1 parent e60252c commit bbe6c55
Show file tree
Hide file tree
Showing 7 changed files with 490 additions and 224 deletions.
488 changes: 321 additions & 167 deletions package-lock.json

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,23 @@
},
"dependencies": {
"@bentley/icons-generic-webfont": "1.0.34",
"@itwin/appui-abstract": "4.5.1",
"@itwin/appui-react": "4.12.0",
"@itwin/components-react": "4.12.0",
"@itwin/core-bentley": "4.5.1",
"@itwin/core-common": "4.5.1",
"@itwin/core-frontend": "4.5.1",
"@itwin/core-geometry": "4.5.1",
"@itwin/core-i18n": "4.5.1",
"@itwin/core-markup": "4.5.1",
"@itwin/core-orbitgt": "4.5.1",
"@itwin/core-quantity": "4.5.1",
"@itwin/core-react": "4.12.0",
"@itwin/imodel-components-react": "4.12.0",
"@itwin/appui-abstract": "4.7.2",
"@itwin/appui-react": "4.14.1",
"@itwin/components-react": "4.14.1",
"@itwin/core-bentley": "4.7.2",
"@itwin/core-common": "4.7.2",
"@itwin/core-frontend": "4.7.2",
"@itwin/core-geometry": "4.7.2",
"@itwin/core-i18n": "4.7.2",
"@itwin/core-markup": "4.7.2",
"@itwin/core-orbitgt": "4.7.2",
"@itwin/core-quantity": "4.7.2",
"@itwin/core-react": "4.14.1",
"@itwin/imodel-components-react": "4.14.1",
"@itwin/mobile-sdk-core": "0.22.4",
"@itwin/presentation-common": "4.5.1",
"@itwin/presentation-frontend": "4.5.1",
"@itwin/webgl-compatibility": "4.5.1",
"@itwin/presentation-common": "4.7.2",
"@itwin/presentation-frontend": "4.7.2",
"@itwin/webgl-compatibility": "4.7.2",
"classnames": "^2.2.6",
"react": "^17.0.0",
"react-dom": "^17.0.0",
Expand All @@ -55,8 +55,8 @@
"@itwin/eslint-plugin": "4.0.0-dev.48",
"@svgr/cli": "^6.3.1",
"@types/node": "10.14.1",
"@types/react": "^16.14.17",
"@types/react-dom": "16.8.4",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/react-redux": "^7.1.19",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
Expand Down
63 changes: 31 additions & 32 deletions src/mobile-ui-react/ActionSheetButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,37 +37,36 @@ export interface ActionSheetButtonProps extends ActionSheetProps, CommonProps {
* in mobile-sdk-core.
* @public
*/
export class ActionSheetButton extends React.Component<ActionSheetButtonProps> {
constructor(props: ActionSheetButtonProps) {
super(props);
}

public static onClick = async (props: ActionSheetButtonProps, source: React.MouseEvent | DOMRect) => {
const result = await presentActionSheet(props, "currentTarget" in source ? source.currentTarget.getBoundingClientRect() : source);
props.onSelected?.(result);
};

public override render() {
const { iconSpec, size, width, height, iconSize } = this.props;
const onClick = async (event: React.MouseEvent) => {
return ActionSheetButton.onClick(this.props, event);
};
let actualIconSize = iconSize;
if (this.props.iconSize === undefined && this.props.iconSpec === undefined) {
actualIconSize = "16px";
}
return (
<NavigationButton
className={this.props.className}
style={this.props.style}
onClick={onClick}
strokeWidth="1px"
size={size}
width={width}
height={height}
iconSpec={iconSpec || "icon-more-vertical-2"}
iconSize={actualIconSize}
/>
);
export function ActionSheetButton(props: ActionSheetButtonProps) {
const { className, style, iconSpec, size, width, height, iconSize } = props;
let actualIconSize = iconSize;
if (iconSize === undefined && iconSpec === undefined) {
actualIconSize = "16px";
}
return (
<NavigationButton
className={className}
style={style}
onClick={async (event) => { await ActionSheetButton.onClick(props, event); }}
strokeWidth="1px"
size={size}
width={width}
height={height}
iconSpec={iconSpec || "icon-more-vertical-2"}
iconSize={actualIconSize}
/>
);
}

/**
* Act as though an {@link ActionSheetButton} with the given props had been clicked. This will show
* an action sheet using the native UI and call `props.onSelected` with the user's selection.
* @param props The {@link ActionSheetButtonProps} to use to show the action sheet.
* @param source The mouse event that triggered the click (whose target's rectangle will be used
* for the action sheet), or the DOM rectangle of the source component that wants to show the action
* sheet.
*/
ActionSheetButton.onClick = async (props: ActionSheetButtonProps, source: React.MouseEvent | DOMRect) => {
const result = await presentActionSheet(props, "currentTarget" in source ? source.currentTarget.getBoundingClientRect() : source);
props.onSelected?.(result);
};
8 changes: 8 additions & 0 deletions src/mobile-ui-react/MobileUi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,17 @@ function stringSetHas(set: Set<string>, values: ReadonlyArray<string>) {
* A custom React hook function for UiSyncEvents.
* @param handler - The callback function.
* @param eventIds - The optional event ids to handle.
*
* __NOTE__: This function should probably be deprecated, but right now there is no obvious way to
* replace it. Consequently, the following is for information only:
* NOT@deprecated in 0.22.5. UiSyncEventArgs were deprecated in appui-react 4.13.x.
*/
// @todo FIX Remove deprecated usage once appui-react provides a reasonable solution.
// eslint-disable-next-line deprecation/deprecation
export function useSyncUiEvent(handler: (args: UiSyncEventArgs) => void, ...eventIds: ReadonlyArray<string>) {
React.useEffect(() => {
// @todo FIX Remove deprecated usage once appui-react provides a reasonable solution.
// eslint-disable-next-line deprecation/deprecation
return SyncUiEventDispatcher.onSyncUiEvent.addListener((args: UiSyncEventArgs) => {
if (eventIds.length === 0 || stringSetHas(args.eventIds, eventIds)) {
handler(args);
Expand Down
2 changes: 2 additions & 0 deletions src/mobile-ui-react/PanViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export class PanTracker {
return this._vpParentDivId;
}

// @todo FIX Remove deprecated usage once appui-react provides a reasonable solution.
// eslint-disable-next-line deprecation/deprecation
private _onSyncUi = (args: UiSyncEventArgs) => {
if (args.eventIds.has(SessionStateActionId.SetIModelConnection) && this._vpParentDivId) {
let panTracker = PanTracker.getWithKey(this._vpParentDivId);
Expand Down
115 changes: 109 additions & 6 deletions src/mobile-ui-react/ResizablePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,11 @@ export function DraggableComponent(props: DraggableComponentProps) {
setLastPosition(undefined);
props.onDragEnd?.();
};
return <TouchDragHandle className={classnames("mui-draggable-component", props.className)} onDragStart={onDragStart} onDrag={onDrag} lastPosition={lastPosition} onDragEnd={onDragEnd} >
return <TouchDragHandle
className={classnames("mui-draggable-component", props.className)}
onDragStart={onDragStart} onDrag={onDrag}
lastPosition={lastPosition} onDragEnd={onDragEnd}
>
{props.children}
</TouchDragHandle>;
}
Expand Down Expand Up @@ -414,6 +418,48 @@ class TouchCaptor extends React.PureComponent<TouchCaptorProps> {
};
}

// @todo Use the functional version of TouchCaptor below after sufficient testing.
// // 2024-06-24: Converted to a function.
// function TouchCaptor(props: TouchCaptorProps) {
// const { className, children, isTouchStarted, onTouchStart, onTouchMove, onTouchEnd } = props;
// const handleTouchStart = React.useCallback((e: React.TouchEvent<HTMLDivElement>) => {
// onTouchStart?.(e.nativeEvent);
// }, [onTouchStart]);
// const handleDocumentTouchEnd = React.useCallback((e: TouchEvent) => {
// if (!isTouchStarted)
// return;
// onTouchEnd?.(e);
// }, [onTouchEnd, isTouchStarted]);
// const handleDocumentTouchMove = React.useCallback((e: TouchEvent) => {
// if (!isTouchStarted)
// return;
// onTouchMove?.(e);
// }, [onTouchMove, isTouchStarted]);
// React.useEffect(() => {
// document.addEventListener("touchend", handleDocumentTouchEnd);
// document.addEventListener("touchmove", handleDocumentTouchMove);
// return () => {
// document.removeEventListener("touchend", handleDocumentTouchEnd);
// document.removeEventListener("touchmove", handleDocumentTouchMove);
// };
// }, [handleDocumentTouchEnd, handleDocumentTouchMove]);
// const fullClassName = classnames(
// "nz-base-pointerCaptor",
// isTouchStarted && "nz-captured",
// className);
// return (
// <div
// className={fullClassName}
// onTouchStart={handleTouchStart}
// style={props.style}
// role="presentation"
// >
// <div className="nz-overlay" />
// {children}
// </div>
// );
// }

interface TouchDragHandleState {
isPointerDown: boolean;
}
Expand Down Expand Up @@ -441,17 +487,19 @@ class TouchDragHandle extends React.PureComponent<TouchDragHandleProps, TouchDra
};

public override render() {
const { style, children, className, lastPosition } = this.props;
return (
<TouchCaptor
children={this.props.children} // eslint-disable-line react/no-children-prop
className={this.props.className}
isTouchStarted={this.props.lastPosition === undefined ? this.state.isPointerDown : true}
className={className}
isTouchStarted={lastPosition === undefined ? this.state.isPointerDown : true}
// onClick={this._handleClick}
onTouchStart={this._handlePointerDown}
onTouchEnd={this._handlePointerUp}
onTouchMove={this._handlePointerMove}
style={this.props.style}
/>
style={style}
>
{children}
</TouchCaptor>
);
}

Expand Down Expand Up @@ -503,3 +551,58 @@ class TouchDragHandle extends React.PureComponent<TouchDragHandleProps, TouchDra
// this.props.onClick?.();
// }
}

// @todo Use the functional version of TouchDragHandle below after sufficient testing.
// NOTE: appui now has usePointerCaptor, which also supports touches. This whole component might
// be redesigned to use that, but I don't understand usePointerCaptor enough to even know if it is
// possible. If usePointerCaptor can be used, then the TouchCaptor class above is not needed.
// // 2024-06-24: Converted to a function.
// function TouchDragHandle(props: TouchDragHandleProps) {
// const { className, style, lastPosition, onDrag, onDragStart, onDragEnd, children } = props;
// const [ isPointerDown, setIsPointerDown ] = React.useState(false);
// const [ initial, setInitial ] = React.useState<XAndY>();
// const handlePointerDown = React.useCallback((e: TouchEvent) => {
// if (e.touches.length !== 1)
// return;
// setIsPointerDown(true);

// e.preventDefault();
// const touch = e.touches[0];

// setInitial({x: touch.clientX, y: touch.clientY});
// }, []);
// const handlePointerMove = React.useCallback((e: TouchEvent) => {
// if (e.touches.length !== 1)
// return;
// const touch = e.touches[0];
// const current = new Point2d(touch.clientX, touch.clientY);
// if (lastPosition) {
// const dragged = current.minus(lastPosition);
// onDrag?.(dragged);
// return;
// }

// if (initial && current.distance(initial) >= 6) {
// onDragStart?.(initial);
// }
// }, [lastPosition, initial, onDragStart, onDrag]);
// const handlePointerUp = React.useCallback(() => {
// setIsPointerDown(false);
// setInitial(undefined);
// if (lastPosition) {
// onDragEnd?.();
// }
// }, [lastPosition, onDragEnd]);
// return (
// <TouchCaptor
// className={className}
// isTouchStarted={lastPosition === undefined ? isPointerDown : true}
// onTouchStart={handlePointerDown}
// onTouchEnd={handlePointerUp}
// onTouchMove={handlePointerMove}
// style={style}
// >
// {children}
// </TouchCaptor>
// );
// }
2 changes: 1 addition & 1 deletion src/mobile-ui-react/TabBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export interface TabsAndStandAlonePanelsAPI {
   * Renders the TabBar and stand-alone panels, this should be called in the parent's rendering section of code.
* @returns A React fragment with the TabBar and the stand-alone panels.
*/
renderTabBarAndPanels: () => JSX.Element;
renderTabBarAndPanels: () => React.JSX.Element;
/** The --mui-bottom-panel-animation-duration CSS variable value in milliseconds. */
openCloseTiming: number;
/** The onAutoClose handler for a resizable panel. */
Expand Down

0 comments on commit bbe6c55

Please sign in to comment.