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,