refactor(player):targetMarker functionality segregation; type fixes
This commit is contained in:
parent
f2bc1e56e3
commit
8fd2ce7f8a
8 changed files with 201 additions and 125 deletions
|
|
@ -3,6 +3,7 @@ import Inspector from './Screen/Inspector'
|
|||
import Screen from './Screen/Screen'
|
||||
import type { Dimensions } from './Screen/types'
|
||||
|
||||
|
||||
export default class InspectorController {
|
||||
private substitutor: Screen | null = null
|
||||
private inspector: Inspector | null = null
|
||||
|
|
@ -16,13 +17,14 @@ export default class InspectorController {
|
|||
}
|
||||
|
||||
enableInspector(clickCallback: (e: { target: Element }) => void): Document | null {
|
||||
if (!this.screen.parentElement) return null;
|
||||
const parent = this.screen.getParentElement()
|
||||
if (!parent) return null;
|
||||
if (!this.substitutor) {
|
||||
this.substitutor = new Screen()
|
||||
this.marker = new Marker(this.substitutor.overlay, this.substitutor)
|
||||
this.inspector = new Inspector(this.substitutor, this.marker)
|
||||
//this.inspector.addClickListener(clickCallback, true)
|
||||
this.substitutor.attach(this.screen.parentElement)
|
||||
this.substitutor.attach(parent)
|
||||
}
|
||||
|
||||
this.substitutor.display(false)
|
||||
|
|
|
|||
|
|
@ -24,15 +24,14 @@ import MFileReader from './messages/MFileReader';
|
|||
import { loadFiles, requestEFSDom, requestEFSDevtools } from './network/loadFiles';
|
||||
import { decryptSessionBytes } from './network/crypto';
|
||||
|
||||
import { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './Screen/Screen';
|
||||
import { INITIAL_STATE as ASSIST_INITIAL_STATE, State as AssistState } from './assist/AssistManager';
|
||||
import { INITIAL_STATE as SCREEN_INITIAL_STATE, State as SuperState } from './Screen/Screen';
|
||||
import Lists, { INITIAL_STATE as LISTS_INITIAL_STATE } from './Lists';
|
||||
|
||||
import type { PerformanceChartPoint } from './managers/PerformanceTrackManager';
|
||||
import type { SkipInterval } from './managers/ActivityManager';
|
||||
|
||||
|
||||
export interface State extends SuperState, AssistState {
|
||||
export interface State extends SuperState {
|
||||
performanceChartData: PerformanceChartPoint[],
|
||||
skipIntervals: SkipInterval[],
|
||||
connType?: string,
|
||||
|
|
@ -54,24 +53,6 @@ export interface State extends SuperState, AssistState {
|
|||
lastMessageTime: number,
|
||||
}
|
||||
|
||||
export const INITIAL_STATE: State = {
|
||||
...SUPER_INITIAL_STATE,
|
||||
...LISTS_INITIAL_STATE,
|
||||
...ASSIST_INITIAL_STATE,
|
||||
performanceChartData: [],
|
||||
skipIntervals: [],
|
||||
error: false,
|
||||
devtoolsLoading: false,
|
||||
|
||||
liveTimeTravel: false,
|
||||
messagesLoading: false,
|
||||
cssLoading: false,
|
||||
get ready() {
|
||||
return !this.messagesLoading && !this.cssLoading
|
||||
},
|
||||
lastMessageTime: 0,
|
||||
};
|
||||
|
||||
|
||||
import type {
|
||||
Message,
|
||||
|
|
@ -93,6 +74,23 @@ const visualChanges = [
|
|||
]
|
||||
|
||||
export default class MessageManager extends Screen {
|
||||
static INITIAL_STATE: State = {
|
||||
...SCREEN_INITIAL_STATE,
|
||||
...LISTS_INITIAL_STATE,
|
||||
performanceChartData: [],
|
||||
skipIntervals: [],
|
||||
error: false,
|
||||
devtoolsLoading: false,
|
||||
|
||||
liveTimeTravel: false,
|
||||
messagesLoading: false,
|
||||
cssLoading: false,
|
||||
get ready() {
|
||||
return !this.messagesLoading && !this.cssLoading
|
||||
},
|
||||
lastMessageTime: 0,
|
||||
}
|
||||
|
||||
private locationEventManager: ListWalker<any>/*<LocationEvent>*/ = new ListWalker();
|
||||
private locationManager: ListWalker<SetPageLocation> = new ListWalker();
|
||||
private loadedLocationManager: ListWalker<SetPageLocation> = new ListWalker();
|
||||
|
|
@ -553,7 +551,7 @@ export default class MessageManager extends Screen {
|
|||
|
||||
// TODO: clean managers?
|
||||
clean() {
|
||||
this.state.update(INITIAL_STATE);
|
||||
this.state.update(MessageManager.INITIAL_STATE);
|
||||
this.incomingMessages.length = 0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,9 +54,9 @@ export default class Screen {
|
|||
readonly cursor: Cursor
|
||||
|
||||
private readonly iframe: HTMLIFrameElement;
|
||||
protected readonly screen: HTMLDivElement;
|
||||
protected readonly controlButton: HTMLDivElement;
|
||||
protected parentElement: HTMLElement | null = null;
|
||||
private readonly screen: HTMLDivElement;
|
||||
private readonly controlButton: HTMLDivElement;
|
||||
private parentElement: HTMLElement | null = null;
|
||||
|
||||
constructor() {
|
||||
const iframe = document.createElement('iframe');
|
||||
|
|
@ -102,6 +102,10 @@ export default class Screen {
|
|||
})
|
||||
}
|
||||
|
||||
getParentElement(): HTMLElement | null {
|
||||
return this.parentElement
|
||||
}
|
||||
|
||||
toggleBorder(isEnabled: boolean ) {
|
||||
const styles = isEnabled ? { border: '2px dashed blue' } : { border: 'unset'}
|
||||
return Object.assign(this.screen.style, styles)
|
||||
|
|
|
|||
134
frontend/app/player/_web/TargetMarker.ts
Normal file
134
frontend/app/player/_web/TargetMarker.ts
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
import type Screen from './Screen/Screen'
|
||||
import type { Point } from './Screen/types'
|
||||
import type { Store } from '../player/types'
|
||||
|
||||
|
||||
function getOffset(el: Element, innerWindow: Window) {
|
||||
const rect = el.getBoundingClientRect();
|
||||
return {
|
||||
fixedLeft: rect.left + innerWindow.scrollX,
|
||||
fixedTop: rect.top + innerWindow.scrollY,
|
||||
rect,
|
||||
};
|
||||
}
|
||||
|
||||
interface BoundingRect {
|
||||
top: number,
|
||||
left: number,
|
||||
width: number,
|
||||
height: number,
|
||||
}
|
||||
|
||||
export interface MarkedTarget {
|
||||
boundingRect: BoundingRect,
|
||||
el: Element,
|
||||
selector: string,
|
||||
count: number,
|
||||
index: number,
|
||||
active?: boolean,
|
||||
percent: number
|
||||
}
|
||||
|
||||
export interface State {
|
||||
markedTargets: MarkedTarget[] | null,
|
||||
activeTargetIndex: number,
|
||||
}
|
||||
|
||||
|
||||
export default class TargetMarker {
|
||||
static INITIAL_STATE: State = {
|
||||
markedTargets: null,
|
||||
activeTargetIndex: 0
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly screen: Screen,
|
||||
private readonly store: Store<State>,
|
||||
) {}
|
||||
|
||||
updateMarketTargets() {
|
||||
const { markedTargets } = this.store.get()
|
||||
if (markedTargets) {
|
||||
this.store.update({
|
||||
markedTargets: markedTargets.map((mt: any) => ({
|
||||
...mt,
|
||||
boundingRect: this.calculateRelativeBoundingRect(mt.el),
|
||||
})),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private calculateRelativeBoundingRect(el: Element): BoundingRect {
|
||||
const parentEl = this.screen.getParentElement()
|
||||
if (!parentEl) return {top:0, left:0, width:0,height:0} //TODO: can be initialized(?) on mounted screen only
|
||||
const { top, left, width, height } = el.getBoundingClientRect()
|
||||
const s = this.screen.getScale()
|
||||
const scrinRect = this.screen.overlay.getBoundingClientRect() //this.screen.getBoundingClientRect() (now private)
|
||||
const parentRect = parentEl.getBoundingClientRect()
|
||||
|
||||
return {
|
||||
top: top*s + scrinRect.top - parentRect.top,
|
||||
left: left*s + scrinRect.left - parentRect.left,
|
||||
width: width*s,
|
||||
height: height*s,
|
||||
}
|
||||
}
|
||||
|
||||
setActiveTarget(index: number) {
|
||||
const window = this.screen.window
|
||||
const markedTargets: MarkedTarget[] | null = this.store.get().markedTargets
|
||||
const target = markedTargets && markedTargets[index]
|
||||
if (target && window) {
|
||||
const { fixedTop, rect } = getOffset(target.el, window)
|
||||
const scrollToY = fixedTop - window.innerHeight / 1.5
|
||||
if (rect.top < 0 || rect.top > window.innerHeight) {
|
||||
// behavior hack TODO: fix it somehow when they will decide to remove it from browser api
|
||||
// @ts-ignore
|
||||
window.scrollTo({ top: scrollToY, behavior: 'instant' })
|
||||
setTimeout(() => {
|
||||
if (!markedTargets) { return }
|
||||
this.store.update({
|
||||
markedTargets: markedTargets.map(t => t === target ? {
|
||||
...target,
|
||||
boundingRect: this.calculateRelativeBoundingRect(target.el),
|
||||
} : t)
|
||||
})
|
||||
}, 0)
|
||||
}
|
||||
|
||||
}
|
||||
this.store.update({ activeTargetIndex: index });
|
||||
}
|
||||
|
||||
private actualScroll: Point | null = null
|
||||
markTargets(selections: { selector: string, count: number }[] | null) {
|
||||
if (selections) {
|
||||
const totalCount = selections.reduce((a, b) => {
|
||||
return a + b.count
|
||||
}, 0);
|
||||
const markedTargets: MarkedTarget[] = [];
|
||||
let index = 0;
|
||||
selections.forEach((s) => {
|
||||
const el = this.screen.getElementBySelector(s.selector);
|
||||
if (!el) return;
|
||||
markedTargets.push({
|
||||
...s,
|
||||
el,
|
||||
index: index++,
|
||||
percent: Math.round((s.count * 100) / totalCount),
|
||||
boundingRect: this.calculateRelativeBoundingRect(el),
|
||||
count: s.count,
|
||||
})
|
||||
});
|
||||
this.actualScroll = this.screen.getCurrentScroll()
|
||||
this.store.update({ markedTargets });
|
||||
} else {
|
||||
if (this.actualScroll) {
|
||||
this.screen.window?.scrollTo(this.actualScroll.x, this.actualScroll.y)
|
||||
this.actualScroll = null
|
||||
}
|
||||
this.store.update({ markedTargets: null });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,18 +3,33 @@ import Player, { State as PlayerState } from '../player/Player'
|
|||
|
||||
import MessageManager from './MessageManager'
|
||||
import InspectorController from './InspectorController'
|
||||
import AssistManager from './assist/AssistManager'
|
||||
import TargetMarker from './TargetMarker'
|
||||
import AssistManager, {
|
||||
INITIAL_STATE as ASSIST_INITIAL_STATE,
|
||||
} from './assist/AssistManager'
|
||||
import Screen from './Screen/Screen'
|
||||
import type { State as MMState } from './MessageManager'
|
||||
|
||||
// export type State = typeof WebPlayer.INITIAL_STATE
|
||||
|
||||
export default class WebPlayer extends Player {
|
||||
static INITIAL_STATE = {
|
||||
...Player.INITIAL_STATE,
|
||||
...TargetMarker.INITIAL_STATE,
|
||||
|
||||
...MessageManager.INITIAL_STATE,
|
||||
...ASSIST_INITIAL_STATE,
|
||||
|
||||
inspectorMode: false,
|
||||
}
|
||||
|
||||
private readonly screen: Screen
|
||||
private readonly inspectorController: InspectorController
|
||||
protected readonly messageManager: MessageManager
|
||||
|
||||
assistManager: AssistManager // public so far
|
||||
private targetMarker: TargetMarker
|
||||
|
||||
constructor(private wpState: Store<MMState & PlayerState>, session, config: RTCIceServer[], live: boolean) {
|
||||
constructor(private wpState: Store<State>, session, config: RTCIceServer[], live: boolean) {
|
||||
// TODO: separate screen from manager
|
||||
const screen = new MessageManager(session, wpState, config, live)
|
||||
super(wpState, screen)
|
||||
|
|
@ -24,6 +39,8 @@ export default class WebPlayer extends Player {
|
|||
// TODO: separate LiveWebPlayer
|
||||
this.assistManager = new AssistManager(session, this.messageManager, config, wpState)
|
||||
|
||||
this.targetMarker = new TargetMarker(this.screen)
|
||||
|
||||
this.inspectorController = new InspectorController(screen)
|
||||
|
||||
|
||||
|
|
@ -77,94 +94,14 @@ export default class WebPlayer extends Player {
|
|||
}
|
||||
}
|
||||
|
||||
updateMarketTargets() {
|
||||
// const { markedTargets } = getState();
|
||||
// if (markedTargets) {
|
||||
// update({
|
||||
// markedTargets: markedTargets.map((mt: any) => ({
|
||||
// ...mt,
|
||||
// boundingRect: this.calculateRelativeBoundingRect(mt.el),
|
||||
// })),
|
||||
// });
|
||||
// }
|
||||
setActiveTarget(args: Parameters<TargetMarker['setActiveTarget']>) {
|
||||
this.targetMarker.setActiveTarget(...args)
|
||||
}
|
||||
markTargets(args: Parameters<TargetMarker['markTargets']>) {
|
||||
this.pause()
|
||||
this.targetMarker.markTargets(...args)
|
||||
}
|
||||
|
||||
// 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,
|
||||
// }
|
||||
// }
|
||||
|
||||
setActiveTarget(index: number) {
|
||||
// const window = this.window
|
||||
// const markedTargets: MarkedTarget[] | null = getState().markedTargets
|
||||
// const target = markedTargets && markedTargets[index]
|
||||
// if (target && window) {
|
||||
// const { fixedTop, rect } = getOffset(target.el, window)
|
||||
// const scrollToY = fixedTop - window.innerHeight / 1.5
|
||||
// if (rect.top < 0 || rect.top > window.innerHeight) {
|
||||
// // behavior hack TODO: fix it somehow when they will decide to remove it from browser api
|
||||
// // @ts-ignore
|
||||
// window.scrollTo({ top: scrollToY, behavior: 'instant' })
|
||||
// setTimeout(() => {
|
||||
// if (!markedTargets) { return }
|
||||
// update({
|
||||
// markedTargets: markedTargets.map(t => t === target ? {
|
||||
// ...target,
|
||||
// boundingRect: this.calculateRelativeBoundingRect(target.el),
|
||||
// } : t)
|
||||
// })
|
||||
// }, 0)
|
||||
// }
|
||||
|
||||
// }
|
||||
// update({ activeTargetIndex: index });
|
||||
}
|
||||
|
||||
// private actualScroll: Point | null = null
|
||||
private setMarkedTargets(selections: { selector: string, count: number }[] | null) {
|
||||
// if (selections) {
|
||||
// const totalCount = selections.reduce((a, b) => {
|
||||
// return a + b.count
|
||||
// }, 0);
|
||||
// const markedTargets: MarkedTarget[] = [];
|
||||
// let index = 0;
|
||||
// selections.forEach((s) => {
|
||||
// const el = this.getElementBySelector(s.selector);
|
||||
// if (!el) return;
|
||||
// markedTargets.push({
|
||||
// ...s,
|
||||
// el,
|
||||
// index: index++,
|
||||
// percent: Math.round((s.count * 100) / totalCount),
|
||||
// boundingRect: this.calculateRelativeBoundingRect(el),
|
||||
// count: s.count,
|
||||
// })
|
||||
// });
|
||||
// this.actualScroll = this.getCurrentScroll()
|
||||
// update({ markedTargets });
|
||||
// } else {
|
||||
// if (this.actualScroll) {
|
||||
// this.screen.window?.scrollTo(this.actualScroll.x, this.actualScroll.y)
|
||||
// this.actualScroll = null
|
||||
// }
|
||||
// update({ markedTargets: null });
|
||||
// }
|
||||
}
|
||||
|
||||
markTargets(targets: { selector: string, count: number }[] | null) {
|
||||
// this.pause();
|
||||
// this.setMarkedTargets(targets);
|
||||
}
|
||||
|
||||
// TODO
|
||||
async toggleTimetravel() {
|
||||
|
|
@ -176,6 +113,7 @@ export default class WebPlayer extends Player {
|
|||
toggleUserName(name?: string) {
|
||||
this.screen.cursor.showTag(name)
|
||||
}
|
||||
|
||||
clean() {
|
||||
super.clean()
|
||||
this.assistManager.clean()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import SimpleStore from './_common/SimpleStore'
|
||||
import type { Store } from './player/types'
|
||||
import { State as MMState, INITIAL_STATE as MM_INITIAL_STATE } from './_web/MessageManager'
|
||||
import Player, { State as PState } from './player/Player'
|
||||
|
||||
import WebPlayer from './_web/WebPlayer'
|
||||
import WebPlayer, { State as WebState} from './_web/WebPlayer'
|
||||
|
||||
type WebState = PState & MMState
|
||||
type WebPlayerStore = Store<WebState>
|
||||
export type IWebState = WebState
|
||||
export type IWebPlayer = WebPlayer
|
||||
|
|
@ -13,8 +10,7 @@ export type IWebPlayerStore = WebPlayerStore
|
|||
|
||||
export function createWebPlayer(session: Record<string, any>, wrapState?: (s:IWebState) => IWebState): [IWebPlayer, IWebPlayerStore] {
|
||||
let state: WebState = {
|
||||
...Player.INITIAL_STATE,
|
||||
...MM_INITIAL_STATE,
|
||||
...WebPlayer.INITIAL_STATE,
|
||||
}
|
||||
if (wrapState) {
|
||||
state = wrapState(state)
|
||||
|
|
@ -28,8 +24,7 @@ export function createWebPlayer(session: Record<string, any>, wrapState?: (s:IWe
|
|||
|
||||
export function createLiveWebPlayer(session: Record<string, any>, config: RTCIceServer[], wrapState?: (s:IWebState) => IWebState): [IWebPlayer, IWebPlayerStore] {
|
||||
let state: WebState = {
|
||||
...Player.INITIAL_STATE,
|
||||
...MM_INITIAL_STATE,
|
||||
...WebPlayer.INITIAL_STATE,
|
||||
}
|
||||
if (wrapState) {
|
||||
state = wrapState(state)
|
||||
|
|
|
|||
|
|
@ -3,5 +3,7 @@ export * from './_web/assist/LocalStream';
|
|||
export * from './_web/WebPlayer';
|
||||
export * from './create';
|
||||
|
||||
export type { MarkedTarget } from './_web/TargetMarker'
|
||||
|
||||
//export * from './_store';
|
||||
//export * from './_singletone';
|
||||
|
|
@ -27,13 +27,14 @@ export interface SetState {
|
|||
completed: boolean
|
||||
live: boolean
|
||||
livePlay: boolean
|
||||
|
||||
endTime: number
|
||||
}
|
||||
|
||||
export interface GetState extends SetState {
|
||||
skip: boolean
|
||||
speed: number
|
||||
skipIntervals: Interval[]
|
||||
endTime: number
|
||||
ready: boolean
|
||||
|
||||
lastMessageTime: number
|
||||
|
|
@ -46,6 +47,8 @@ export default class Animator {
|
|||
completed: false,
|
||||
live: false,
|
||||
livePlay: false,
|
||||
|
||||
endTime: 0,
|
||||
} as const
|
||||
|
||||
private animationFrameRequestId: number = 0
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue