From ebcf5ad655c3fc2b41410b122a7836c77f025b6c Mon Sep 17 00:00:00 2001 From: ShiKhu Date: Fri, 23 Jul 2021 19:41:54 +0800 Subject: [PATCH] feat (frontend-player): heatmaps player markup + overlay refactor --- .../Session_/AutoplayTimer/index.js | 1 - .../Session_/Player/Controls/Controls.js | 2 +- .../components/Session_/Player/Overlay.tsx | 77 ++++++++++++ .../Session_/Player/Overlay/AutoplayTimer.css | 3 + .../Overlay/AutoplayTimer.tsx} | 8 +- .../Player/Overlay/ElementsMarker.tsx | 9 ++ .../Player/Overlay/ElementsMarker/Marker.css | 5 + .../Player/Overlay/ElementsMarker/Marker.tsx | 21 ++++ .../Player/Overlay/LiveStatusText.css | 4 + .../Player/Overlay/LiveStatusText.tsx | 11 ++ .../Session_/Player/Overlay/Loader.tsx | 7 ++ .../Session_/Player/Overlay/PlayIconLayer.css | 17 +++ .../Session_/Player/Overlay/PlayIconLayer.tsx | 38 ++++++ .../Overlay/overlay.css} | 8 +- .../app/components/Session_/Player/Player.js | 113 +----------------- .../app/components/Session_/Player/player.css | 55 --------- frontend/app/components/ui/Icon/Icon.js | 1 + .../MessageDistributor/MessageDistributor.ts | 3 - .../StatedScreen/Screen/BaseScreen.ts | 32 +++-- .../StatedScreen/StatedScreen.ts | 71 +++++++++-- frontend/app/player/Player.ts | 9 +- frontend/app/player/singletone.js | 1 + 22 files changed, 297 insertions(+), 199 deletions(-) delete mode 100644 frontend/app/components/Session_/AutoplayTimer/index.js create mode 100644 frontend/app/components/Session_/Player/Overlay.tsx create mode 100644 frontend/app/components/Session_/Player/Overlay/AutoplayTimer.css rename frontend/app/components/Session_/{AutoplayTimer/AutoplayTimer.js => Player/Overlay/AutoplayTimer.tsx} (89%) create mode 100644 frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx create mode 100644 frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.css create mode 100644 frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx create mode 100644 frontend/app/components/Session_/Player/Overlay/LiveStatusText.css create mode 100644 frontend/app/components/Session_/Player/Overlay/LiveStatusText.tsx create mode 100644 frontend/app/components/Session_/Player/Overlay/Loader.tsx create mode 100644 frontend/app/components/Session_/Player/Overlay/PlayIconLayer.css create mode 100644 frontend/app/components/Session_/Player/Overlay/PlayIconLayer.tsx rename frontend/app/components/Session_/{AutoplayTimer/AutoplayTimer.css => Player/Overlay/overlay.css} (64%) diff --git a/frontend/app/components/Session_/AutoplayTimer/index.js b/frontend/app/components/Session_/AutoplayTimer/index.js deleted file mode 100644 index 4014ce449..000000000 --- a/frontend/app/components/Session_/AutoplayTimer/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './AutoplayTimer' \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js index 0c88f729e..d9eb985f2 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.js +++ b/frontend/app/components/Session_/Player/Controls/Controls.js @@ -73,7 +73,7 @@ function getStorageName(type) { skip: state.skip, skipToIssue: state.skipToIssue, speed: state.speed, - disabled: state.cssLoading || state.messagesLoading || state.inspectorMode, + disabled: state.cssLoading || state.messagesLoading || state.inspectorMode || state.markedTargets, inspectorMode: state.inspectorMode, fullscreenDisabled: state.messagesLoading, logCount: state.logListNow.length, diff --git a/frontend/app/components/Session_/Player/Overlay.tsx b/frontend/app/components/Session_/Player/Overlay.tsx new file mode 100644 index 000000000..29560bb2b --- /dev/null +++ b/frontend/app/components/Session_/Player/Overlay.tsx @@ -0,0 +1,77 @@ +import React, {useEffect} from 'react'; +import { connectPlayer, markTargets } from 'Player'; +import { getStatusText } from 'Player/MessageDistributor/managers/AssistManager'; +import type { MarkedTarget } from 'Player/MessageDistributor/StatedScreen/StatedScreen'; + +import AutoplayTimer from './Overlay/AutoplayTimer'; +import PlayIconLayer from './Overlay/PlayIconLayer'; +import LiveStatusText from './Overlay/LiveStatusText'; +import Loader from './Overlay/Loader'; +import ElementsMarker from './Overlay/ElementsMarker'; + +interface Props { + playing: boolean, + completed: boolean, + inspectorMode: boolean, + messagesLoading: boolean, + loading: boolean, + live: boolean, + liveStatusText: string, + autoplay: boolean, + markedTargets: MarkedTarget[] | null, + + nextId: string, + togglePlay: () => void, +} + +function Overlay({ + playing, + completed, + inspectorMode, + messagesLoading, + loading, + live, + liveStatusText, + autoplay, + markedTargets, + + nextId, + togglePlay, +}: Props) { + + useEffect(() =>{ + setTimeout(() => markTargets([{ selector: 'div', count:6}]), 5000) + setTimeout(() => markTargets(null), 8000) + },[]) + + const showAutoplayTimer = !live && completed && autoplay && nextId + const showPlayIconLayer = !live && !markedTargets && !inspectorMode && !loading && !showAutoplayTimer; + const showLiveStatusText = live && liveStatusText && !loading; + + return ( + <> + { showAutoplayTimer && } + { showLiveStatusText && + + } + { messagesLoading && } + { showPlayIconLayer && + + } + { markedTargets && + } + + ); +} + + +export default connectPlayer(state => ({ + playing: state.playing, + messagesLoading: state.messagesLoading, + loading: state.messagesLoading || state.cssLoading, + completed: state.completed, + autoplay: state.autoplay, + live: state.live, + liveStatusText: getStatusText(state.peerConnectionStatus), + markedTargets: state.markedTargets +}))(Overlay); \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Overlay/AutoplayTimer.css b/frontend/app/components/Session_/Player/Overlay/AutoplayTimer.css new file mode 100644 index 000000000..9fdf9bade --- /dev/null +++ b/frontend/app/components/Session_/Player/Overlay/AutoplayTimer.css @@ -0,0 +1,3 @@ +.overlayBg { + background-color: rgba(255, 255, 255, 0.8); +} \ No newline at end of file diff --git a/frontend/app/components/Session_/AutoplayTimer/AutoplayTimer.js b/frontend/app/components/Session_/Player/Overlay/AutoplayTimer.tsx similarity index 89% rename from frontend/app/components/Session_/AutoplayTimer/AutoplayTimer.js rename to frontend/app/components/Session_/Player/Overlay/AutoplayTimer.tsx index 72bde71ce..91ce53722 100644 --- a/frontend/app/components/Session_/AutoplayTimer/AutoplayTimer.js +++ b/frontend/app/components/Session_/Player/Overlay/AutoplayTimer.tsx @@ -1,9 +1,11 @@ import React, { useEffect, useState } from 'react' +import cn from 'classnames'; import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom'; import { Button, Link } from 'UI' import { session as sessionRoute, withSiteId } from 'App/routes' -import stl from './AutoplayTimer.css' -import { withRouter } from 'react-router-dom'; +import stl from './AutoplayTimer.css'; +import clsOv from './overlay.css'; function AutoplayTimer({ nextId, siteId, history }) { let timer @@ -33,7 +35,7 @@ function AutoplayTimer({ nextId, siteId, history }) { return '' return ( -
+
Next recording will be played in {counter}s
diff --git a/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx b/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx new file mode 100644 index 000000000..47a695a0f --- /dev/null +++ b/frontend/app/components/Session_/Player/Overlay/ElementsMarker.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import Marker from './ElementsMarker/Marker'; + +export default function ElementsMarker({ targets }) { + return targets.map(t => ) +} + + + diff --git a/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.css b/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.css new file mode 100644 index 000000000..c008214f1 --- /dev/null +++ b/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.css @@ -0,0 +1,5 @@ +.marker { + position: absolute; + border: 2px dashed; + z-index: 9999; +} \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx b/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx new file mode 100644 index 000000000..5b168ceb2 --- /dev/null +++ b/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx @@ -0,0 +1,21 @@ +import React, { useState, useEffect } from 'react'; + +import { Popup } from 'UI'; +import type { MarkedTarget } from 'Player/MessageDistributor/StatedScreen/StatedScreen'; + +import stl from './Marker.css'; + +interface Props { + target: MarkedTarget; +} + +export default function Marker({ target }: Props) { + const style = { + top: `${ target.boundingRect.top }px`, + left: `${ target.boundingRect.left }px`, + width: `${ target.boundingRect.width }px`, + height: `${ target.boundingRect.height }px`, + } + + return
+} \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Overlay/LiveStatusText.css b/frontend/app/components/Session_/Player/Overlay/LiveStatusText.css new file mode 100644 index 000000000..b7a17464b --- /dev/null +++ b/frontend/app/components/Session_/Player/Overlay/LiveStatusText.css @@ -0,0 +1,4 @@ +.text { + color: $gray-light; + font-size: 40px; +} \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Overlay/LiveStatusText.tsx b/frontend/app/components/Session_/Player/Overlay/LiveStatusText.tsx new file mode 100644 index 000000000..7ed13e562 --- /dev/null +++ b/frontend/app/components/Session_/Player/Overlay/LiveStatusText.tsx @@ -0,0 +1,11 @@ +import React from 'react'; +import stl from './LiveStatusText.css'; +import ovStl from './overlay.css'; + +interface Props { + text: string; +} + +export default function LiveStatusText({ text }: Props) { + return
{text}
+} \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Overlay/Loader.tsx b/frontend/app/components/Session_/Player/Overlay/Loader.tsx new file mode 100644 index 000000000..4fe414ef0 --- /dev/null +++ b/frontend/app/components/Session_/Player/Overlay/Loader.tsx @@ -0,0 +1,7 @@ +import React from 'react'; +import { Loader } from 'UI'; +import ovStl from './overlay.css'; + +export default function OverlayLoader() { + return
+} \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Overlay/PlayIconLayer.css b/frontend/app/components/Session_/Player/Overlay/PlayIconLayer.css new file mode 100644 index 000000000..36aad6296 --- /dev/null +++ b/frontend/app/components/Session_/Player/Overlay/PlayIconLayer.css @@ -0,0 +1,17 @@ +.iconWrapper { + background-color: rgba(0, 0, 0, 0.1); + width: 50px; + height: 50px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + opacity: 0; + transition: all .2s; /* Animation */ +} + +.zoomIcon { + opacity: 1; + transform: scale(1.8); + transition: all .8s; +} \ No newline at end of file diff --git a/frontend/app/components/Session_/Player/Overlay/PlayIconLayer.tsx b/frontend/app/components/Session_/Player/Overlay/PlayIconLayer.tsx new file mode 100644 index 000000000..ff23870df --- /dev/null +++ b/frontend/app/components/Session_/Player/Overlay/PlayIconLayer.tsx @@ -0,0 +1,38 @@ +import React, { useState, useCallback } from 'react'; +import cn from 'classnames'; +import { Icon } from 'UI'; + +import cls from './PlayIconLayer.css'; +import clsOv from './overlay.css'; + +interface Props { + togglePlay: () => void, + playing: boolean, +} + +export default function PlayIconLayer({ playing, togglePlay }: Props) { + const [ showPlayOverlayIcon, setShowPlayOverlayIcon ] = useState(false); + const togglePlayAnimated = useCallback(() => { + setShowPlayOverlayIcon(true); + togglePlay(); + setTimeout( + () => setShowPlayOverlayIcon(false), + 800, + ); + }, []); + return ( +
+
+ +
+
+ ) +} \ No newline at end of file diff --git a/frontend/app/components/Session_/AutoplayTimer/AutoplayTimer.css b/frontend/app/components/Session_/Player/Overlay/overlay.css similarity index 64% rename from frontend/app/components/Session_/AutoplayTimer/AutoplayTimer.css rename to frontend/app/components/Session_/Player/Overlay/overlay.css index 26b5e97d4..2c5cab1bd 100644 --- a/frontend/app/components/Session_/AutoplayTimer/AutoplayTimer.css +++ b/frontend/app/components/Session_/Player/Overlay/overlay.css @@ -1,8 +1,3 @@ -.wrapper { - width: 30%; - height: 30%; -} - .overlay { position: absolute; top: 0; @@ -13,5 +8,4 @@ display: flex; align-items: center; justify-content: center; - background-color: rgba(255, 255, 255, 0.8); -} \ No newline at end of file +} diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index 51a250efe..baae88ea3 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -2,29 +2,17 @@ import { connect } from 'react-redux'; import { findDOMNode } from 'react-dom'; import cn from 'classnames'; import { Loader, IconButton, EscapeButton } from 'UI'; -import { hide as hideTargetDefiner, toggleInspectorMode } from 'Duck/components/targetDefiner'; +import { hide as hideTargetDefiner } from 'Duck/components/targetDefiner'; import { fullscreenOff } from 'Duck/components/player'; -import withOverlay from 'Components/hocs/withOverlay'; import { attach as attachPlayer, Controls as PlayerControls, connectPlayer } from 'Player'; import Controls from './Controls'; +import Overlay from './Overlay'; import stl from './player.css'; -import AutoplayTimer from '../AutoplayTimer'; import EventsToggleButton from '../../Session/EventsToggleButton'; -import { getStatusText } from 'Player/MessageDistributor/managers/AssistManager'; -const ScreenWrapper = withOverlay()(React.memo(() =>
)); - @connectPlayer(state => ({ - playing: state.playing, - loading: state.messagesLoading, - disconnected: state.disconnected, - disabled: state.cssLoading || state.messagesLoading || state.inspectorMode, - removeOverlay: !state.messagesLoading && state.inspectorMode || state.live, - completed: state.completed, - autoplay: state.autoplay, live: state.live, - liveStatusText: getStatusText(state.peerConnectionStatus), })) @connect(state => ({ //session: state.getIn([ 'sessions', 'current' ]), @@ -32,15 +20,9 @@ const ScreenWrapper = withOverlay()(React.memo(() =>
toggleInspectorMode(false), fullscreenOff, }) export default class Player extends React.PureComponent { - state = { - showPlayOverlayIcon: false, - - startedToPlayAt: Date.now(), - }; screenWrapper = React.createRef(); componentDidMount() { @@ -48,69 +30,14 @@ export default class Player extends React.PureComponent { attachPlayer(parentElement); } - componentDidUpdate(prevProps) { - if (prevProps.targetSelector !== this.props.targetSelector) { - PlayerControls.mark(this.props.targetSelector); - } - if (prevProps.playing !== this.props.playing) { - if (this.props.playing) { - this.setState({ startedToPlayAt: Date.now() }); - } else { - this.updateWatchingTime(); - } - } - } - - componentWillUnmount() { - if (this.props.playing) { - this.updateWatchingTime(); - } - } - - updateWatchingTime() { - const diff = Date.now() - this.state.startedToPlayAt; - } - - - // onTargetClick = (targetPath) => { - // const { targetCustomList, location } = this.props; - // const targetCustomFromList = targetCustomList !== this.props.targetSelector - // .find(({ path }) => path === targetPath); - // const target = targetCustomFromList - // ? targetCustomFromList.set('location', location) - // : { path: targetPath, isCustom: true, location }; - // this.props.showTargetDefiner(target); - // } - - togglePlay = () => { - this.setState({ showPlayOverlayIcon: true }); - PlayerControls.togglePlay(); - - setTimeout( - () => this.setState({ showPlayOverlayIcon: false }), - 800, - ); - } - render() { - const { - showPlayOverlayIcon, - } = this.state; const { className, - playing, - disabled, - removeOverlay, bottomBlockIsActive, - loading, - disconnected, fullscreen, fullscreenOff, - completed, - autoplay, nextId, live, - liveStatusText, } = this.props; return ( @@ -120,40 +47,12 @@ export default class Player extends React.PureComponent { > { fullscreen && - // } {!live && !fullscreen && } -
- { (!removeOverlay || live && liveStatusText) && -
- { live && liveStatusText - ? {liveStatusText} - : - } - { !live && -
-
-
- } -
- } - { completed && autoplay && nextId && } - + +
diff --git a/frontend/app/components/Session_/Player/player.css b/frontend/app/components/Session_/Player/player.css index 08879d7c6..8e1c3d1e0 100644 --- a/frontend/app/components/Session_/Player/player.css +++ b/frontend/app/components/Session_/Player/player.css @@ -1,5 +1,3 @@ -@import 'icons.css'; - .playerBody { background: $white; /* border-radius: 3px; */ @@ -25,61 +23,8 @@ font-weight: 200; color: $gray-medium; } -.overlay { - position: absolute; - top: 0; - bottom: 0; - right: 0; - left: 0; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; - - -/* &[data-protect] { - pointer-events: none; - background: $white; - opacity: 0.3; -} - */ - & .iconWrapper { - background-color: rgba(0, 0, 0, 0.1); - width: 50px; - height: 50px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - opacity: 0; - transition: all .2s; /* Animation */ - } - - & .zoomIcon { - opacity: 1; - transform: scale(1.8); - transition: all .8s; - } - - & .playIcon { - @mixin icon play, $gray-medium, 30px; - } - - & .pauseIcon { - @mixin icon pause, $gray-medium, 30px; - } -} .playerView { position: relative; flex: 1; } - -.inspectorMode { - z-index: 99991 !important; -} - -.liveStatusText { - color: $gray-light; - font-size: 40px; -} \ No newline at end of file diff --git a/frontend/app/components/ui/Icon/Icon.js b/frontend/app/components/ui/Icon/Icon.js index c6c41efa0..431e2dbc7 100644 --- a/frontend/app/components/ui/Icon/Icon.js +++ b/frontend/app/components/ui/Icon/Icon.js @@ -1,3 +1,4 @@ +import React from 'react'; import cn from 'classnames'; import SVG from 'UI/SVG'; import styles from './icon.css'; diff --git a/frontend/app/player/MessageDistributor/MessageDistributor.ts b/frontend/app/player/MessageDistributor/MessageDistributor.ts index b65e4aa40..cc131446f 100644 --- a/frontend/app/player/MessageDistributor/MessageDistributor.ts +++ b/frontend/app/player/MessageDistributor/MessageDistributor.ts @@ -306,9 +306,7 @@ export default class MessageDistributor extends StatedScreen { this.pagesManager.moveReady(t).then(() => { const lastScroll = this.scrollManager.moveToLast(t, index); - // @ts-ignore ??can't see double inheritance if (!!lastScroll && this.window) { - // @ts-ignore this.window.scrollTo(lastScroll.x, lastScroll.y); } // Moving mouse and setting :hover classes on ready view @@ -479,7 +477,6 @@ export default class MessageDistributor extends StatedScreen { // TODO: clean managers? clean() { - // @ts-ignore super.clean(); //if (this._socket) this._socket.close(); update(INITIAL_STATE); diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts b/frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts index e48416cf2..80356e2d4 100644 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts +++ b/frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts @@ -18,7 +18,7 @@ export const INITIAL_STATE: State = { export default abstract class BaseScreen { public readonly overlay: HTMLDivElement; private readonly iframe: HTMLIFrameElement; - private readonly _screen: HTMLDivElement; + protected readonly screen: HTMLDivElement; protected parentElement: HTMLElement | null = null; constructor() { const iframe = document.createElement('iframe'); @@ -44,7 +44,7 @@ export default abstract class BaseScreen { screen.className = styles.screen; screen.appendChild(iframe); screen.appendChild(overlay); - this._screen = screen; + this.screen = screen; } attach(parentElement: HTMLElement) { @@ -52,7 +52,7 @@ export default abstract class BaseScreen { throw new Error("BaseScreen: Trying to attach an attached screen."); } - parentElement.appendChild(this._screen); + parentElement.appendChild(this.screen); this.parentElement = parentElement; // parentElement.onresize = this.scale; @@ -115,29 +115,37 @@ export default abstract class BaseScreen { return this.getElementsFromInternalPoint(this.getInternalCoordinates(point)); } + getElementBySelector(selector: string): Element | null { + return this.document?.querySelector(selector) || null; + } + display(flag: boolean = true) { - this._screen.style.display = flag ? '' : 'none'; + this.screen.style.display = flag ? '' : 'none'; } displayFrame(flag: boolean = true) { this.iframe.style.display = flag ? '' : 'none'; } + private s: number = 1; + getScale() { + return this.s; + } + _scale() { if (!this.parentElement) return; - let s = 1; const { height, width } = getState(); const { offsetWidth, offsetHeight } = this.parentElement; - s = Math.min(offsetWidth / width, offsetHeight / height); - if (s > 1) { - s = 1; + this.s = Math.min(offsetWidth / width, offsetHeight / height); + if (this.s > 1) { + this.s = 1; } else { - s = Math.round(s * 1e3) / 1e3; + this.s = Math.round(this.s * 1e3) / 1e3; } - this._screen.style.transform = `scale(${ s }) translate(-50%, -50%)`; - this._screen.style.width = width + 'px'; - this._screen.style.height = height + 'px'; + this.screen.style.transform = `scale(${ this.s }) translate(-50%, -50%)`; + this.screen.style.width = width + 'px'; + this.screen.style.height = height + 'px'; this.iframe.style.width = width + 'px'; this.iframe.style.height = height + 'px'; diff --git a/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts b/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts index 50147f90a..21a34e7e6 100644 --- a/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts +++ b/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts @@ -1,12 +1,29 @@ -import Screen, { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './Screen'; +import Screen, { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './Screen/Screen'; import { update, getState } from '../../store'; +//export interface targetPosition + +interface BoundingRect { + top: number, + left: number, + width: number, + height: number, +} + +export interface MarkedTarget { + boundingRect: BoundingRect, + el: Element, + selector: string, + count: number, +} + export interface State extends SuperState { messagesLoading: boolean, cssLoading: boolean, disconnected: boolean, userPageLoading: boolean, + markedTargets: MarkedTarget[] | null } export const INITIAL_STATE: State = { @@ -15,40 +32,78 @@ export const INITIAL_STATE: State = { cssLoading: false, disconnected: false, userPageLoading: false, -} + markedTargets: [], +}; export default class StatedScreen extends Screen { constructor() { super(); } setMessagesLoading(messagesLoading: boolean) { - // @ts-ignore this.display(!messagesLoading); update({ messagesLoading }); } setCSSLoading(cssLoading: boolean) { - // @ts-ignore - this.displayFrame(!cssLoading); update({ cssLoading }); } setDisconnected(disconnected: boolean) { if (!getState().live) return; //? - // @ts-ignore this.display(!disconnected); update({ disconnected }); } setUserPageLoading(userPageLoading: boolean) { - // @ts-ignore this.display(!userPageLoading); update({ userPageLoading }); } setSize({ height, width }: { height: number, width: number }) { update({ width, height }); - // @ts-ignore this.scale(); + + const { markedTargets } = getState(); + if (markedTargets) { + update({ + markedTargets: markedTargets.map(mt => ({ + ...mt, + boundingRect: this.calculateRelativeBoundingRect(mt.el), + })), + }); + } + } + + private calculateRelativeBoundingRect(el: Element): BoundingRect { + if (!this.parentElement) return {top:0, left:0, width:0,height:0} //TODO + const { top, left, width, height } = el.getBoundingClientRect(); + const s = this.getScale(); + const scrinRect = this.screen.getBoundingClientRect(); + const parentRect = this.parentElement.getBoundingClientRect(); + + return { + top: top*s + scrinRect.top - parentRect.top, + left: left*s + scrinRect.left - parentRect.left, + width: width*s, + height: height*s, + } + } + + setMarkedTargets(selections: { selector: string, count: number }[] | null) { + if (selections) { + const targets: MarkedTarget[] = []; + selections.forEach(s => { + const el = this.getElementBySelector(s.selector); + if (!el) return; + targets.push({ + ...s, + el, + boundingRect: this.calculateRelativeBoundingRect(el), + }) + }); + update({ markedTargets: targets }); + } else { + update({ markedTargets: null }); + } } } \ No newline at end of file diff --git a/frontend/app/player/Player.ts b/frontend/app/player/Player.ts index ffc36c022..4e454fa11 100644 --- a/frontend/app/player/Player.ts +++ b/frontend/app/player/Player.ts @@ -1,6 +1,6 @@ import { goTo as listsGoTo } from './lists'; import { update, getState } from './store'; -import MessageDistributor, { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './MessageDistributor'; +import MessageDistributor, { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './MessageDistributor/MessageDistributor'; const fps = 60; const performance = window.performance || { now: Date.now.bind(Date) }; @@ -35,7 +35,7 @@ const initialSkipToIssue = !!localStorage.getItem(SKIP_TO_ISSUE_STORAGE_KEY); const initialAutoplay = !!localStorage.getItem(AUTOPLAY_STORAGE_KEY); const initialShowEvents = !!localStorage.getItem(SHOW_EVENTS_STORAGE_KEY); -export const INITIAL_STATE: SuperState = { +export const INITIAL_STATE = { ...SUPER_INITIAL_STATE, time: 0, playing: false, @@ -191,6 +191,11 @@ export default class Player extends MessageDistributor { update({ inspectorMode: false }); } } + + markTargets(targets: { selector: string, count: number }[] | null) { + this.pause(); + this.setMarkedTargets(targets); + } toggleSkipToIssue() { const skipToIssue = !getState().skipToIssue; diff --git a/frontend/app/player/singletone.js b/frontend/app/player/singletone.js index 63865de91..38d56815f 100644 --- a/frontend/app/player/singletone.js +++ b/frontend/app/player/singletone.js @@ -69,6 +69,7 @@ export const markElement = initCheck((...args) => instance.marker && instance.ma export const scale = initCheck(() => instance.scale()); export const toggleInspectorMode = initCheck((...args) => instance.toggleInspectorMode(...args)); export const callPeer = initCheck((...args) => instance.assistManager.call(...args)) +export const markTargets = initCheck((...args) => instance.markTargets(...args)) export const Controls = { jump,