refactor(player):targetMarker functionality segregation; type fixes

This commit is contained in:
Alex Kaminskii 2022-11-22 16:28:38 +01:00
parent f2bc1e56e3
commit 8fd2ce7f8a
8 changed files with 201 additions and 125 deletions

View file

@ -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)

View file

@ -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
}

View file

@ -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)

View 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 });
}
}
}

View file

@ -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()

View file

@ -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)

View file

@ -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';

View file

@ -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