From 049f839b30e546c313e152f97ad5845e0e4056bf Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 17 Aug 2023 09:36:59 -0700 Subject: [PATCH] chore: use glass pane for dragging (#26513) --- packages/trace-viewer/src/ui/timeline.css | 8 +-- packages/trace-viewer/src/ui/timeline.tsx | 77 +++++++++++++---------- packages/web/src/components/glassPane.tsx | 60 ++++++++++++++++++ 3 files changed, 109 insertions(+), 36 deletions(-) create mode 100644 packages/web/src/components/glassPane.tsx diff --git a/packages/trace-viewer/src/ui/timeline.css b/packages/trace-viewer/src/ui/timeline.css index e8922fb20f26f..6e98cde0854d7 100644 --- a/packages/trace-viewer/src/ui/timeline.css +++ b/packages/trace-viewer/src/ui/timeline.css @@ -25,10 +25,6 @@ margin-left: 10px; } -.timeline-view.dragging { - cursor: ew-resize; -} - .timeline-divider { position: absolute; width: 1px; @@ -137,6 +133,10 @@ background-color: #3879d91a; } +body.dark-mode .timeline-window-curtain { + background-color: #161718bf; +} + .timeline-window-curtain.left { border-right: 1px solid var(--vscode-panel-border); } diff --git a/packages/trace-viewer/src/ui/timeline.tsx b/packages/trace-viewer/src/ui/timeline.tsx index 828095aca1eb5..41f9698b5e837 100644 --- a/packages/trace-viewer/src/ui/timeline.tsx +++ b/packages/trace-viewer/src/ui/timeline.tsx @@ -1,21 +1,21 @@ -/* - * Copyright 2017 Google Inc. All rights reserved. - * Modifications copyright (c) Microsoft Corporation. - - Licensed 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. -*/ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed 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. + */ import { msToString, useMeasure } from '@web/uiUtils'; +import { GlassPane } from '@web/components/glassPane'; import * as React from 'react'; import type { Boundaries } from '../geometry'; import { FilmStrip } from './filmStrip'; @@ -111,16 +111,13 @@ export const Timeline: React.FunctionComponent<{ } }, [boundaries, measure, ref, selectedTime]); - const onMouseMove = React.useCallback((event: React.MouseEvent) => { + const onGlassPaneMouseMove = React.useCallback((event: MouseEvent) => { if (!ref.current) return; const x = event.clientX - ref.current.getBoundingClientRect().left; const time = positionToTime(measure.width, boundaries, x); const action = model?.actions.findLast(action => action.startTime <= time); - if (!dragWindow) { - setPreviewPoint({ x, clientY: event.clientY, action, sdkLanguage }); - return; - } + if (!event.buttons) { setDragWindow(undefined); return; @@ -130,6 +127,10 @@ export const Timeline: React.FunctionComponent<{ if (action) onSelected(action); + // Should not happen, but for type safety. + if (!dragWindow) + return; + let newDragWindow = dragWindow; if (dragWindow.type === 'resize') { newDragWindow = { ...dragWindow, endX: x }; @@ -153,9 +154,9 @@ export const Timeline: React.FunctionComponent<{ const time2 = positionToTime(measure.width, boundaries, newDragWindow.endX); if (time1 !== time2) setSelectedTime({ minimum: Math.min(time1, time2), maximum: Math.max(time1, time2) }); - }, [boundaries, dragWindow, measure, model, onSelected, ref, sdkLanguage, setSelectedTime]); + }, [boundaries, dragWindow, measure, model, onSelected, ref, setSelectedTime]); - const onMouseUp = React.useCallback(() => { + const onGlassPaneMouseUp = React.useCallback(() => { setPreviewPoint(undefined); if (!dragWindow) return; @@ -178,23 +179,35 @@ export const Timeline: React.FunctionComponent<{ setDragWindow(undefined); }, [boundaries, dragWindow, measure, model, selectedTime, setSelectedTime, onSelected]); + const onMouseMove = React.useCallback((event: React.MouseEvent) => { + if (!ref.current) + return; + const x = event.clientX - ref.current.getBoundingClientRect().left; + const time = positionToTime(measure.width, boundaries, x); + const action = model?.actions.findLast(action => action.startTime <= time); + setPreviewPoint({ x, clientY: event.clientY, action, sdkLanguage }); + }, [boundaries, measure, model, ref, sdkLanguage]); + const onMouseLeave = React.useCallback(() => { setPreviewPoint(undefined); }, []); - const onDoubleClick = React.useCallback(() => { + const onPaneDoubleClick = React.useCallback(() => { setSelectedTime(undefined); }, [setSelectedTime]); return
-
+
+ onMouseLeave={onMouseLeave}>
{ offsets.map((offset, index) => { return
@@ -219,7 +232,7 @@ export const Timeline: React.FunctionComponent<{ display: (previewPoint !== undefined) ? 'block' : 'none', left: (previewPoint?.x || 0) + 'px', }} /> -
+ {selectedTime &&
@@ -227,7 +240,7 @@ export const Timeline: React.FunctionComponent<{
-
+
}
; }; diff --git a/packages/web/src/components/glassPane.tsx b/packages/web/src/components/glassPane.tsx new file mode 100644 index 0000000000000..6919cb30d33a7 --- /dev/null +++ b/packages/web/src/components/glassPane.tsx @@ -0,0 +1,60 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed 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. + */ + +import React from 'react'; + +export const GlassPane: React.FC<{ + enabled: boolean; + cursor: string; + onPaneMouseMove?: (e: MouseEvent) => void; + onPaneMouseUp?: (e: MouseEvent) => void; + onPaneDoubleClick?: (e: MouseEvent) => void; +}> = ({ enabled, cursor, onPaneMouseMove, onPaneMouseUp, onPaneDoubleClick }) => { + React.useEffect(() => { + if (!enabled) + return; + + const glassPaneDiv = document.createElement('div'); + glassPaneDiv.style.position = 'absolute'; + glassPaneDiv.style.top = '0'; + glassPaneDiv.style.right = '0'; + glassPaneDiv.style.bottom = '0'; + glassPaneDiv.style.left = '0'; + glassPaneDiv.style.zIndex = '9999'; + glassPaneDiv.style.cursor = cursor; + + document.body.appendChild(glassPaneDiv); + + if (onPaneMouseMove) + glassPaneDiv.addEventListener('mousemove', onPaneMouseMove); + if (onPaneMouseUp) + glassPaneDiv.addEventListener('mouseup', onPaneMouseUp); + if (onPaneDoubleClick) + document.body.addEventListener('dblclick', onPaneDoubleClick); + + return () => { + if (onPaneMouseMove) + glassPaneDiv.removeEventListener('mousemove', onPaneMouseMove); + if (onPaneMouseUp) + glassPaneDiv.removeEventListener('mouseup', onPaneMouseUp); + if (onPaneDoubleClick) + document.body.removeEventListener('dblclick', onPaneDoubleClick); + document.body.removeChild(glassPaneDiv); + }; + }, [enabled, cursor, onPaneMouseMove, onPaneMouseUp, onPaneDoubleClick]); + + return <>; +};