From 7a3ef9bc2179f30b6c8b3a83c7aad2f1b0a059ae Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 15 Nov 2022 21:01:40 +0100 Subject: [PATCH 01/64] trfactoring(frontend/player):phase 1 of componnts decomposition; use store per instance --- .../Session_/Player/Controls/Timeline.js | 2 +- .../app/player/MessageDistributor/Lists.ts | 23 - .../StatedScreen/StatedScreen.ts | 152 ------ .../MessageDistributor/StatedScreen/index.ts | 2 - .../app/player/MessageDistributor/index.js | 2 - frontend/app/player/Player.ts | 281 ----------- .../managers => _common}/ListWalker.ts | 27 +- .../app/player/_common/ListWalkerWithMarks.ts | 31 ++ frontend/app/player/_common/SimpleStore.ts | 15 + .../player/{singletone.js => _singletone.ts} | 109 ++--- .../app/player/{store => _store}/connector.js | 0 frontend/app/player/{store => _store}/duck.js | 10 +- .../app/player/{store => _store}/index.js | 0 .../app/player/{store => _store}/selectors.js | 0 .../app/player/{store => _store}/store.js | 0 frontend/app/player/_web/Lists.ts | 71 +++ .../MessageManager.ts} | 149 +++--- .../Screen/BaseScreen.ts | 16 +- .../StatedScreen => _web}/Screen/Cursor.ts | 0 .../StatedScreen => _web}/Screen/Inspector.js | 0 .../Marker.js => _web/Screen/Marker.ts} | 66 +-- .../StatedScreen => _web}/Screen/Screen.ts | 11 +- .../Screen/cursor.module.css | 0 .../StatedScreen => _web}/Screen/index.js | 0 .../Screen/marker.module.css | 0 .../Screen/screen.module.css | 0 .../StatedScreen => _web}/Screen/types.ts | 0 frontend/app/player/_web/WebPlayer.ts | 178 +++++++ .../assist}/AnnotationCanvas.ts | 0 .../managers => _web/assist}/AssistManager.ts | 78 +-- .../managers => _web/assist}/LocalStream.ts | 0 .../managers/ActivityManager.ts | 8 +- .../managers/DOM/DOMManager.ts | 6 +- .../managers/DOM/FocusManager.ts | 2 +- .../managers/DOM/StylesManager.ts | 6 +- .../managers/DOM/VirtualDOM.ts | 0 .../managers/DOM/safeCSSRules.ts | 0 .../managers/MouseMoveManager.ts | 6 +- .../managers/PagesManager.ts | 8 +- .../managers/PerformanceTrackManager.ts | 2 +- .../managers/ReduxStateManager.ts | 2 +- .../managers/WindowNodeCounter.ts | 0 .../messages/JSONRawMessageReader.ts | 0 .../messages/MFileReader.ts | 0 .../messages/MStreamReader.ts | 0 .../messages/PrimitiveReader.ts | 0 .../messages/RawMessageReader.ts | 0 .../messages/index.ts | 0 .../messages/message.ts | 0 .../messages/raw.ts | 0 .../messages/timed.ts | 0 .../messages/tracker-legacy.ts | 0 .../messages/tracker.ts | 0 .../messages/urlResolve.ts | 0 .../network/crypto.ts | 0 .../network/loadFiles.ts | 0 frontend/app/player/create.ts | 25 + frontend/app/player/index.js | 10 +- frontend/app/player/ios/ImagePlayer.js | 454 ------------------ frontend/app/player/ios/Parser.ts | 34 -- frontend/app/player/ios/PerformanceList.js | 73 --- frontend/app/player/ios/ScreenList.ts | 57 --- frontend/app/player/ios/lists.js | 12 - frontend/app/player/ios/state.js | 112 ----- frontend/app/player/lists/ListReader.js | 124 ----- .../app/player/lists/ListReaderWithRed.js | 48 -- frontend/app/player/lists/index.js | 68 --- frontend/app/player/player/Animator.ts | 172 +++++++ frontend/app/player/player/Player.ts | 125 +++++ frontend/app/player/player/_LSCache.ts | 63 +++ frontend/app/player/player/localStorage.ts | 19 + frontend/app/player/player/types.ts | 21 + 72 files changed, 990 insertions(+), 1690 deletions(-) delete mode 100644 frontend/app/player/MessageDistributor/Lists.ts delete mode 100644 frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts delete mode 100644 frontend/app/player/MessageDistributor/StatedScreen/index.ts delete mode 100644 frontend/app/player/MessageDistributor/index.js delete mode 100644 frontend/app/player/Player.ts rename frontend/app/player/{MessageDistributor/managers => _common}/ListWalker.ts (86%) create mode 100644 frontend/app/player/_common/ListWalkerWithMarks.ts create mode 100644 frontend/app/player/_common/SimpleStore.ts rename frontend/app/player/{singletone.js => _singletone.ts} (69%) rename frontend/app/player/{store => _store}/connector.js (100%) rename frontend/app/player/{store => _store}/duck.js (68%) rename frontend/app/player/{store => _store}/index.js (100%) rename frontend/app/player/{store => _store}/selectors.js (100%) rename frontend/app/player/{store => _store}/store.js (100%) create mode 100644 frontend/app/player/_web/Lists.ts rename frontend/app/player/{MessageDistributor/MessageDistributor.ts => _web/MessageManager.ts} (86%) rename frontend/app/player/{MessageDistributor/StatedScreen => _web}/Screen/BaseScreen.ts (94%) rename frontend/app/player/{MessageDistributor/StatedScreen => _web}/Screen/Cursor.ts (100%) rename frontend/app/player/{MessageDistributor/StatedScreen => _web}/Screen/Inspector.js (100%) rename frontend/app/player/{MessageDistributor/StatedScreen/Screen/Marker.js => _web/Screen/Marker.ts} (66%) rename frontend/app/player/{MessageDistributor/StatedScreen => _web}/Screen/Screen.ts (91%) rename frontend/app/player/{MessageDistributor/StatedScreen => _web}/Screen/cursor.module.css (100%) rename frontend/app/player/{MessageDistributor/StatedScreen => _web}/Screen/index.js (100%) rename frontend/app/player/{MessageDistributor/StatedScreen => _web}/Screen/marker.module.css (100%) rename frontend/app/player/{MessageDistributor/StatedScreen => _web}/Screen/screen.module.css (100%) rename frontend/app/player/{MessageDistributor/StatedScreen => _web}/Screen/types.ts (100%) create mode 100644 frontend/app/player/_web/WebPlayer.ts rename frontend/app/player/{MessageDistributor/managers => _web/assist}/AnnotationCanvas.ts (100%) rename frontend/app/player/{MessageDistributor/managers => _web/assist}/AssistManager.ts (87%) rename frontend/app/player/{MessageDistributor/managers => _web/assist}/LocalStream.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/managers/ActivityManager.ts (83%) rename frontend/app/player/{MessageDistributor => _web}/managers/DOM/DOMManager.ts (99%) rename frontend/app/player/{MessageDistributor => _web}/managers/DOM/FocusManager.ts (93%) rename frontend/app/player/{MessageDistributor => _web}/managers/DOM/StylesManager.ts (95%) rename frontend/app/player/{MessageDistributor => _web}/managers/DOM/VirtualDOM.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/managers/DOM/safeCSSRules.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/managers/MouseMoveManager.ts (89%) rename frontend/app/player/{MessageDistributor => _web}/managers/PagesManager.ts (85%) rename frontend/app/player/{MessageDistributor => _web}/managers/PerformanceTrackManager.ts (98%) rename frontend/app/player/{MessageDistributor => _web}/managers/ReduxStateManager.ts (96%) rename frontend/app/player/{MessageDistributor => _web}/managers/WindowNodeCounter.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/messages/JSONRawMessageReader.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/messages/MFileReader.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/messages/MStreamReader.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/messages/PrimitiveReader.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/messages/RawMessageReader.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/messages/index.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/messages/message.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/messages/raw.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/messages/timed.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/messages/tracker-legacy.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/messages/tracker.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/messages/urlResolve.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/network/crypto.ts (100%) rename frontend/app/player/{MessageDistributor => _web}/network/loadFiles.ts (100%) create mode 100644 frontend/app/player/create.ts delete mode 100644 frontend/app/player/ios/ImagePlayer.js delete mode 100644 frontend/app/player/ios/Parser.ts delete mode 100644 frontend/app/player/ios/PerformanceList.js delete mode 100644 frontend/app/player/ios/ScreenList.ts delete mode 100644 frontend/app/player/ios/lists.js delete mode 100644 frontend/app/player/ios/state.js delete mode 100644 frontend/app/player/lists/ListReader.js delete mode 100644 frontend/app/player/lists/ListReaderWithRed.js delete mode 100644 frontend/app/player/lists/index.js create mode 100644 frontend/app/player/player/Animator.ts create mode 100644 frontend/app/player/player/Player.ts create mode 100644 frontend/app/player/player/_LSCache.ts create mode 100644 frontend/app/player/player/localStorage.ts create mode 100644 frontend/app/player/player/types.ts diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.js b/frontend/app/components/Session_/Player/Controls/Timeline.js index 3ff810d57..e3ce9788a 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.js +++ b/frontend/app/components/Session_/Player/Controls/Timeline.js @@ -30,7 +30,7 @@ let debounceTooltipChange = () => null; disabled: state.cssLoading || state.messagesLoading || state.markedTargets, endTime: state.endTime, live: state.live, - notes: state.notes, + notes: state.notes || [], // TODO: implement notes without interaction with Player state })) @connect( (state) => ({ diff --git a/frontend/app/player/MessageDistributor/Lists.ts b/frontend/app/player/MessageDistributor/Lists.ts deleted file mode 100644 index cb7e4d192..000000000 --- a/frontend/app/player/MessageDistributor/Lists.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Message } from './messages' -import ListWalker from './managers/ListWalker'; - -export const LIST_NAMES = ["redux", "mobx", "vuex", "zustand", "ngrx", "graphql", "exceptions", "profiles"] as const; - -export const INITIAL_STATE = {} -LIST_NAMES.forEach(name => { - INITIAL_STATE[`${name}ListNow`] = [] - INITIAL_STATE[`${name}List`] = [] -}) - - -type ListsObject = { - [key in typeof LIST_NAMES[number]]: ListWalker -} - -export function initLists(): ListsObject { - const lists: Partial = {}; - for (var i = 0; i < LIST_NAMES.length; i++) { - lists[LIST_NAMES[i]] = new ListWalker(); - } - return lists as ListsObject; -} diff --git a/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts b/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts deleted file mode 100644 index 45028f88f..000000000 --- a/frontend/app/player/MessageDistributor/StatedScreen/StatedScreen.ts +++ /dev/null @@ -1,152 +0,0 @@ -import Screen, { INITIAL_STATE as SUPER_INITIAL_STATE, State as SuperState } from './Screen/Screen'; -import { update, getState } from '../../store'; - -import type { Point } from './Screen/types'; - -function getOffset(el: Element, innerWindow: Window) { - const rect = el.getBoundingClientRect(); - return { - fixedLeft: rect.left + innerWindow.scrollX, - fixedTop: rect.top + innerWindow.scrollY, - rect, - }; -} - -//export interface targetPosition - -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 extends SuperState { - messagesLoading: boolean, - cssLoading: boolean, - markedTargets: MarkedTarget[] | null, - activeTargetIndex: number, -} - -export const INITIAL_STATE: State = { - ...SUPER_INITIAL_STATE, - messagesLoading: false, - cssLoading: false, - markedTargets: null, - activeTargetIndex: 0 -}; - -export default class StatedScreen extends Screen { - constructor() { super(); } - - setMessagesLoading(messagesLoading: boolean) { - this.display(!messagesLoading); - update({ messagesLoading }); - } - - setCSSLoading(cssLoading: boolean) { - this.displayFrame(!cssLoading); - update({ cssLoading }); - } - - setSize({ height, width }: { height: number, width: number }) { - update({ width, height }); - this.scale(); - this.updateMarketTargets() - } - - updateMarketTargets() { - const { markedTargets } = getState(); - if (markedTargets) { - update({ - markedTargets: markedTargets.map((mt: any) => ({ - ...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, - } - } - - 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 - 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.window?.scrollTo(this.actualScroll.x, this.actualScroll.y) - this.actualScroll = null - } - update({ markedTargets: null }); - } - } -} diff --git a/frontend/app/player/MessageDistributor/StatedScreen/index.ts b/frontend/app/player/MessageDistributor/StatedScreen/index.ts deleted file mode 100644 index 0955acffb..000000000 --- a/frontend/app/player/MessageDistributor/StatedScreen/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './StatedScreen'; -export * from './StatedScreen'; \ No newline at end of file diff --git a/frontend/app/player/MessageDistributor/index.js b/frontend/app/player/MessageDistributor/index.js deleted file mode 100644 index 8502aee50..000000000 --- a/frontend/app/player/MessageDistributor/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './MessageDistributor'; -export * from './MessageDistributor'; diff --git a/frontend/app/player/Player.ts b/frontend/app/player/Player.ts deleted file mode 100644 index 320b141ec..000000000 --- a/frontend/app/player/Player.ts +++ /dev/null @@ -1,281 +0,0 @@ -import { goTo as listsGoTo } from './lists'; -import { update, getState } from './store'; -import MessageDistributor, { INITIAL_STATE as SUPER_INITIAL_STATE } from './MessageDistributor/MessageDistributor'; -import { Note } from 'App/services/NotesService'; - -const fps = 60; -const performance = window.performance || { now: Date.now.bind(Date) }; -const requestAnimationFrame = - window.requestAnimationFrame || - // @ts-ignore - window.webkitRequestAnimationFrame || - // @ts-ignore - window.mozRequestAnimationFrame || - // @ts-ignore - window.oRequestAnimationFrame || - // @ts-ignore - window.msRequestAnimationFrame || - ((callback: (args: any) => void) => window.setTimeout(() => { callback(performance.now()); }, 1000 / fps)); -const cancelAnimationFrame = - window.cancelAnimationFrame || - // @ts-ignore - window.mozCancelAnimationFrame || - window.clearTimeout; - -const HIGHEST_SPEED = 16; - - -const SPEED_STORAGE_KEY = "__$player-speed$__"; -const SKIP_STORAGE_KEY = "__$player-skip$__"; -const SKIP_TO_ISSUE_STORAGE_KEY = "__$session-skipToIssue$__"; -const AUTOPLAY_STORAGE_KEY = "__$player-autoplay$__"; -const SHOW_EVENTS_STORAGE_KEY = "__$player-show-events$__"; -const storedSpeed: number = parseInt(localStorage.getItem(SPEED_STORAGE_KEY) || "") ; -const initialSpeed = [1,2,4,8,16].includes(storedSpeed) ? storedSpeed : 1; -const initialSkip = localStorage.getItem(SKIP_STORAGE_KEY) === 'true'; -const initialSkipToIssue = localStorage.getItem(SKIP_TO_ISSUE_STORAGE_KEY) === 'true'; -const initialAutoplay = localStorage.getItem(AUTOPLAY_STORAGE_KEY) === 'true'; -const initialShowEvents = localStorage.getItem(SHOW_EVENTS_STORAGE_KEY) === 'true'; - -export const INITIAL_STATE = { - ...SUPER_INITIAL_STATE, - time: 0, - playing: false, - completed: false, - endTime: 0, - inspectorMode: false, - live: false, - livePlay: false, - liveTimeTravel: false, - notes: [], -} as const; - - -export const INITIAL_NON_RESETABLE_STATE = { - skip: initialSkip, - skipToIssue: initialSkipToIssue, - autoplay: initialAutoplay, - speed: initialSpeed, - showEvents: initialShowEvents, -} - -export default class Player extends MessageDistributor { - private _animationFrameRequestId: number = 0; - - private _setTime(time: number, index?: number) { - update({ - time, - completed: false, - }); - super.move(time, index); - listsGoTo(time, index); - } - - private _startAnimation() { - let prevTime = getState().time; - let animationPrevTime = performance.now(); - - const nextFrame = (animationCurrentTime: number) => { - const { - speed, - skip, - autoplay, - skipIntervals, - endTime, - live, - livePlay, - disconnected, - messagesLoading, - cssLoading, - } = getState(); - - const diffTime = messagesLoading || cssLoading || disconnected - ? 0 - : Math.max(animationCurrentTime - animationPrevTime, 0) * (live ? 1 : speed); - - let time = prevTime + diffTime; - - const skipInterval = !live && skip && skipIntervals.find((si: Node) => si.contains(time)); // TODO: good skip by messages - if (skipInterval) time = skipInterval.end; - - const fmt = super.getFirstMessageTime(); - if (time < fmt) time = fmt; // ? - - const lmt = super.getLastMessageTime(); - if (livePlay && time < lmt) time = lmt; - if (endTime < lmt) { - update({ - endTime: lmt, - }); - } - - prevTime = time; - animationPrevTime = animationCurrentTime; - - const completed = !live && time >= endTime; - if (completed) { - this._setTime(endTime); - return update({ - playing: false, - completed: true, - }); - } - - // throttle store updates - // TODO: make it possible to change frame rate - if (live && time - endTime > 100) { - update({ - endTime: time, - livePlay: endTime - time < 900 - }); - } - this._setTime(time); - this._animationFrameRequestId = requestAnimationFrame(nextFrame); - }; - this._animationFrameRequestId = requestAnimationFrame(nextFrame); - } - - play() { - cancelAnimationFrame(this._animationFrameRequestId); - update({ playing: true }); - this._startAnimation(); - } - - pause() { - cancelAnimationFrame(this._animationFrameRequestId); - update({ playing: false }) - } - - togglePlay() { - const { playing, completed } = getState(); - if (playing) { - this.pause(); - } else if (completed) { - this._setTime(0); - this.play(); - } else { - this.play(); - } - } - - jump(setTime: number, index: number) { - const { live, liveTimeTravel, endTime } = getState(); - if (live && !liveTimeTravel) return; - const time = setTime ? setTime : getState().time - if (getState().playing) { - cancelAnimationFrame(this._animationFrameRequestId); - // this._animationFrameRequestId = requestAnimationFrame(() => { - this._setTime(time, index); - this._startAnimation(); - // throttilg the redux state update from each frame to nearly half a second - // which is better for performance and component rerenders - update({ livePlay: Math.abs(time - endTime) < 500 }); - //}); - } else { - //this._animationFrameRequestId = requestAnimationFrame(() => { - this._setTime(time, index); - update({ livePlay: Math.abs(time - endTime) < 500 }); - //}); - } - } - - toggleSkip() { - const skip = !getState().skip; - localStorage.setItem(SKIP_STORAGE_KEY, `${skip}`); - update({ skip }); - } - - toggleInspectorMode(flag: boolean, clickCallback?: (args: any) => void) { - if (typeof flag !== 'boolean') { - const { inspectorMode } = getState(); - flag = !inspectorMode; - } - - if (flag) { - this.pause(); - update({ inspectorMode: true }); - return super.enableInspector(clickCallback); - } else { - super.disableInspector(); - update({ inspectorMode: false }); - } - } - - markTargets(targets: { selector: string, count: number }[] | null) { - this.pause(); - this.setMarkedTargets(targets); - } - - activeTarget(index: number) { - this.setActiveTarget(index); - } - - toggleSkipToIssue() { - const skipToIssue = !getState().skipToIssue; - localStorage.setItem(SKIP_TO_ISSUE_STORAGE_KEY, `${skipToIssue}`); - update({ skipToIssue }); - } - - toggleAutoplay() { - const autoplay = !getState().autoplay; - localStorage.setItem(AUTOPLAY_STORAGE_KEY, `${autoplay}`); - update({ autoplay }); - } - - toggleEvents(shouldShow?: boolean) { - const showEvents = shouldShow || !getState().showEvents; - localStorage.setItem(SHOW_EVENTS_STORAGE_KEY, `${showEvents}`); - update({ showEvents }); - } - - _updateSpeed(speed: number) { - localStorage.setItem(SPEED_STORAGE_KEY, `${speed}`); - update({ speed }); - } - - toggleSpeed() { - const { speed } = getState(); - this._updateSpeed(speed < HIGHEST_SPEED ? speed * 2 : 1); - } - - speedUp() { - const { speed } = getState(); - this._updateSpeed(Math.min(HIGHEST_SPEED, speed * 2)); - } - - speedDown() { - const { speed } = getState(); - this._updateSpeed(Math.max(1, speed/2)); - } - - async toggleTimetravel() { - if (!getState().liveTimeTravel) { - return await this.reloadWithUnprocessedFile() - } - } - - jumpToLive() { - cancelAnimationFrame(this._animationFrameRequestId); - this._setTime(getState().endTime); - this._startAnimation(); - update({ livePlay: true }); - } - - toggleUserName(name?: string) { - this.cursor.toggleUserName(name) - } - - injectNotes(notes: Note[]) { - update({ notes }) - } - - filterOutNote(noteId: number) { - const { notes } = getState() - update({ notes: notes.filter((note: Note) => note.noteId !== noteId) }) - } - - clean() { - this.pause(); - super.clean(); - } -} diff --git a/frontend/app/player/MessageDistributor/managers/ListWalker.ts b/frontend/app/player/_common/ListWalker.ts similarity index 86% rename from frontend/app/player/MessageDistributor/managers/ListWalker.ts rename to frontend/app/player/_common/ListWalker.ts index e04c5bb83..92f7585c8 100644 --- a/frontend/app/player/MessageDistributor/managers/ListWalker.ts +++ b/frontend/app/player/_common/ListWalker.ts @@ -1,4 +1,4 @@ -import type { Timed } from '../messages/timed'; +import type { Timed } from './messages/timed'; export default class ListWalker { private p = 0 @@ -79,6 +79,23 @@ export default class ListWalker { return this.p; } + private hasNext() { + return this.p < this.length + } + private hasPrev() { + return this.p > 0 + } + protected moveNext(): T | null { + return this.hasNext() + ? this.list[ this.p++ ] + : null + } + protected movePrev(): T | null { + return this.hasPrev() + ? this.list[ --this.p ] + : null + } + /* Returns last message with the time <= t. Assumed that the current message is already handled so @@ -94,11 +111,11 @@ export default class ListWalker { let changed = false; while (this.p < this.length && this.list[this.p][key] <= val) { - this.p++; + this.moveNext() changed = true; } while (this.p > 0 && this.list[ this.p - 1 ][key] > val) { - this.p--; + this.movePrev() changed = true; } return changed ? this.list[ this.p - 1 ] : null; @@ -112,10 +129,10 @@ export default class ListWalker { const list = this.list while (list[this.p] && list[this.p].time <= t) { - fn(list[ this.p++ ]); + fn(this.moveNext()) } while (fnBack && this.p > 0 && list[ this.p - 1 ].time > t) { - fnBack(list[ --this.p ]); + fnBack(this.movePrev()); } } diff --git a/frontend/app/player/_common/ListWalkerWithMarks.ts b/frontend/app/player/_common/ListWalkerWithMarks.ts new file mode 100644 index 000000000..b2a8f5d3d --- /dev/null +++ b/frontend/app/player/_common/ListWalkerWithMarks.ts @@ -0,0 +1,31 @@ +import type { Timed } from './messages/timed'; +import ListWalker from './ListWalker' + + +type CheckFn = (t: T) => boolean + + +export default class ListWalkerWithMarks extends ListWalker { + private _markCountNow: number = 0 + constructor(private isMarked: CheckFn, initialList?: T[]) { + super(initialList) + } + protected moveNext() { + const val = super.moveNext() + if (val && this.isMarked(val)) { + this._markCountNow++ + } + return val + } + protected movePrev() { + const val = super.movePrev() + if (val && this.isMarked(val)) { + this._markCountNow-- + } + return val + } + get markCountNow(): number { + return this._markCountNow + } + +} \ No newline at end of file diff --git a/frontend/app/player/_common/SimpleStore.ts b/frontend/app/player/_common/SimpleStore.ts new file mode 100644 index 000000000..9e81a79cd --- /dev/null +++ b/frontend/app/player/_common/SimpleStore.ts @@ -0,0 +1,15 @@ + +import { State } from './types' + +// (not a type) +export default class SimpleSore implements State { + constructor(private state: G){} + get(): G { + return this.state + } + update(newState: Partial) { + Object.assign(this.state, newState) + } +} + + diff --git a/frontend/app/player/singletone.js b/frontend/app/player/_singletone.ts similarity index 69% rename from frontend/app/player/singletone.js rename to frontend/app/player/_singletone.ts index feb82ec78..71f29a7ee 100644 --- a/frontend/app/player/singletone.js +++ b/frontend/app/player/_singletone.ts @@ -1,11 +1,40 @@ -import Player from './Player'; -import { update, cleanStore, getState } from './store'; -import { clean as cleanLists } from './lists'; +import WebPlayer from './_web/WebPlayer'; +import reduxStore, {update, cleanStore} from './_store'; -/** @type {Player} */ -let instance = null; +import { State as MMState, INITIAL_STATE as MM_INITIAL_STATE } from './_web/MessageManager' +import { State as PState, INITIAL_STATE as PLAYER_INITIAL_STATE } from './player/Player' +import { Store } from './player/types' -const initCheck = method => (...args) => { + +const INIT_STATE = { + ...MM_INITIAL_STATE, + ...PLAYER_INITIAL_STATE, +} + + +const myStore: Store = { + get() { + return reduxStore.getState() + }, + update(s) { + update(s) + } +} + +let instance: WebPlayer | null = null; + +export function init(session, config, live = false) { + instance = new WebPlayer(myStore, session, config, live); +} + +export function clean() { + if (instance === null) return; + instance.clean(); + cleanStore() + instance = null; +} + +const initCheck = (method) => (...args) => { if (instance === null) { console.error("Player method called before Player have been initialized."); return; @@ -13,47 +42,7 @@ const initCheck = method => (...args) => { return method(...args); } - -let autoPlay = true; -document.addEventListener("visibilitychange", function() { - if (instance === null) return; - if (document.hidden) { - const { playing } = getState(); - autoPlay = playing - if (playing) { - instance.pause(); - } - } else if (autoPlay) { - instance.play(); - } -}); - -export function init(session, config, live = false) { - const endTime = !live && session.duration.valueOf(); - - instance = new Player(session, config, live); - update({ - initialized: true, - live, - livePlay: live, - endTime, // : 0, //TODO: through initialState - session, - }); - - if (!document.hidden) { - instance.play(); - } -} - -export function clean() { - if (instance === null) return; - instance.clean(); - cleanStore(); - cleanLists(); - instance = null; -} export const jump = initCheck((...args) => instance.jump(...args)); - export const togglePlay = initCheck((...args) => instance.togglePlay(...args)); export const pause = initCheck((...args) => instance.pause(...args)); export const toggleSkip = initCheck((...args) => instance.toggleSkip(...args)); @@ -64,9 +53,22 @@ export const toggleEvents = initCheck((...args) => instance.toggleEvents(...args export const speedUp = initCheck((...args) => instance.speedUp(...args)); export const speedDown = initCheck((...args) => instance.speedDown(...args)); export const attach = initCheck((...args) => instance.attach(...args)); -export const markElement = initCheck((...args) => instance.marker && instance.marker.mark(...args)); +export const markElement = initCheck((...args) => instance.mark(...args)); export const scale = initCheck(() => instance.scale()); +/** @type {WebPlayer.toggleTimetravel} */ +export const toggleTimetravel = initCheck((...args) => instance.toggleTimetravel(...args)) export const toggleInspectorMode = initCheck((...args) => instance.toggleInspectorMode(...args)); +export const markTargets = initCheck((...args) => instance.markTargets(...args)) +export const activeTarget =initCheck((...args) => instance.setActiveTarget(...args)) + +export const jumpToLive = initCheck((...args) => instance.jumpToLive(...args)) +export const toggleUserName = initCheck((...args) => instance.toggleUserName(...args)) + +// !not related to player, but rather to the OR platform. +export const injectNotes = () => {} // initCheck((...args) => instance.injectNotes(...args)) +export const filterOutNote = () => {} //initCheck((...args) => instance.filterOutNote(...args)) + + /** @type {Player.assistManager.call} */ export const callPeer = initCheck((...args) => instance.assistManager.call(...args)) /** @type {Player.assistManager.setCallArgs} */ @@ -75,17 +77,10 @@ export const setCallArgs = initCheck((...args) => instance.assistManager.setCall export const initiateCallEnd = initCheck((...args) => instance.assistManager.initiateCallEnd(...args)) export const requestReleaseRemoteControl = initCheck((...args) => instance.assistManager.requestReleaseRemoteControl(...args)) export const releaseRemoteControl = initCheck((...args) => instance.assistManager.releaseRemoteControl(...args)) -export const markTargets = initCheck((...args) => instance.markTargets(...args)) -export const activeTarget = initCheck((...args) => instance.activeTarget(...args)) -export const toggleAnnotation = initCheck((...args) => instance.assistManager.toggleAnnotation(...args)) -/** @type {Player.toggleTimetravel} */ -export const toggleTimetravel = initCheck((...args) => instance.toggleTimetravel(...args)) -export const jumpToLive = initCheck((...args) => instance.jumpToLive(...args)) -export const toggleUserName = initCheck((...args) => instance.toggleUserName(...args)) -export const injectNotes = initCheck((...args) => instance.injectNotes(...args)) -export const filterOutNote = initCheck((...args) => instance.filterOutNote(...args)) /** @type {Player.assistManager.toggleVideoLocalStream} */ export const toggleVideoLocalStream = initCheck((...args) => instance.assistManager.toggleVideoLocalStream(...args)) +export const toggleAnnotation = initCheck((...args) => instance.assistManager.toggleAnnotation(...args)) + export const Controls = { jump, @@ -99,4 +94,4 @@ export const Controls = { speedUp, speedDown, callPeer -} +} \ No newline at end of file diff --git a/frontend/app/player/store/connector.js b/frontend/app/player/_store/connector.js similarity index 100% rename from frontend/app/player/store/connector.js rename to frontend/app/player/_store/connector.js diff --git a/frontend/app/player/store/duck.js b/frontend/app/player/_store/duck.js similarity index 68% rename from frontend/app/player/store/duck.js rename to frontend/app/player/_store/duck.js index faf77041c..bcede3b32 100644 --- a/frontend/app/player/store/duck.js +++ b/frontend/app/player/_store/duck.js @@ -1,20 +1,20 @@ import { applyChange, revertChange } from 'deep-diff'; -import { INITIAL_STATE as listsInitialState } from '../lists'; -import { INITIAL_STATE as playerInitialState, INITIAL_NON_RESETABLE_STATE as playerInitialNonResetableState } from '../Player'; + +import { INITIAL_STATE as MM_INITIAL_STATE } from '../_web/MessageManager' +import { INITIAL_STATE as PLAYER_INITIAL_STATE } from '../player/Player' const UPDATE = 'player/UPDATE'; const CLEAN = 'player/CLEAN'; const REDUX = 'player/REDUX'; const resetState = { - ...listsInitialState, - ...playerInitialState, + ...MM_INITIAL_STATE, + ...PLAYER_INITIAL_STATE, initialized: false, }; const initialState = { ...resetState, - ...playerInitialNonResetableState, } export default (state = initialState, action = {}) => { diff --git a/frontend/app/player/store/index.js b/frontend/app/player/_store/index.js similarity index 100% rename from frontend/app/player/store/index.js rename to frontend/app/player/_store/index.js diff --git a/frontend/app/player/store/selectors.js b/frontend/app/player/_store/selectors.js similarity index 100% rename from frontend/app/player/store/selectors.js rename to frontend/app/player/_store/selectors.js diff --git a/frontend/app/player/store/store.js b/frontend/app/player/_store/store.js similarity index 100% rename from frontend/app/player/store/store.js rename to frontend/app/player/_store/store.js diff --git a/frontend/app/player/_web/Lists.ts b/frontend/app/player/_web/Lists.ts new file mode 100644 index 000000000..4f9c236cd --- /dev/null +++ b/frontend/app/player/_web/Lists.ts @@ -0,0 +1,71 @@ +import ListWalker from '../_common/ListWalker'; +import ListWalkerWithMarks from '../_common/ListWalkerWithMarks'; + +import type { Message } from './messages' + +const SIMPLE_LIST_NAMES = [ "event", "redux", "mobx", "vuex", "zustand", "ngrx", "graphql", "exceptions", "profiles"] as const; +const MARKED_LIST_NAMES = [ "log", "resource", "fetch", "stack" ] as const; +//const entityNamesSimple = [ "event", "profile" ]; + +const LIST_NAMES = [...SIMPLE_LIST_NAMES, ...MARKED_LIST_NAMES ]; + +// TODO: provide correct types + +export const INITIAL_STATE = LIST_NAMES.reduce((state, name) => { + state[`${name}List`] = [] + state[`${name}ListNow`] = [] + if (MARKED_LIST_NAMES.includes(name)) { + state[`${name}MarkedCountNow`] = 0 + } + return state +}, {}) + + +type SimpleListsObject = { + [key in typeof SIMPLE_LIST_NAMES[number]]: ListWalker +} +type MarkedListsObject = { + [key in typeof MARKED_LIST_NAMES[number]]: ListWalkerWithMarks +} +type ListsObject = SimpleListsObject & MarkedListsObject + +type InitialLists = { + [key in typeof LIST_NAMES[number]]: any[] +} + +export default class Lists { + lists: ListsObject + constructor(initialLists: Partial = {}) { + const lists: Partial = {} + for (const name of SIMPLE_LIST_NAMES) { + lists[name] = new ListWalker(initialLists[name]) + } + for (const name of MARKED_LIST_NAMES) { + // TODO: provide types + lists[name] = new ListWalkerWithMarks((el) => el.isRed(), initialLists[name]) + } + this.lists = lists as ListsObject + } + + getFullListsState() { + return LIST_NAMES.reduce((state, name) => { + state[`${name}List`] = this.lists[name].list + return state + }, {}) + } + + moveGetState(t: number) { + return LIST_NAMES.reduce((state, name) => { + const lastMsg = this.lists[name].moveGetLast(t) // index: name === 'exceptions' ? undefined : index); + if (lastMsg != null) { + state[`${name}ListNow`] = this.lists[name].listNow + } + return state + }, MARKED_LIST_NAMES.reduce((state, name) => { + state[`${name}RedCountNow`] = this.lists[name].markCountNow // Red --> Marked + return state + }, {}) + ); + } + +} \ No newline at end of file diff --git a/frontend/app/player/MessageDistributor/MessageDistributor.ts b/frontend/app/player/_web/MessageManager.ts similarity index 86% rename from frontend/app/player/MessageDistributor/MessageDistributor.ts rename to frontend/app/player/_web/MessageManager.ts index 85b01e6a3..e6dc9467c 100644 --- a/frontend/app/player/MessageDistributor/MessageDistributor.ts +++ b/frontend/app/player/_web/MessageManager.ts @@ -2,37 +2,31 @@ import { Decoder } from "syncod"; import logger from 'App/logger'; -import Resource, { TYPES } from 'Types/session/resource'; // MBTODO: player types? +import Resource, { TYPES } from 'Types/session/resource'; import { TYPES as EVENT_TYPES } from 'Types/session/event'; import Log from 'Types/session/log'; -import { update } from '../store'; import { toast } from 'react-toastify'; -import { - init as initListsDepr, - append as listAppend, - setStartTime as setListsStartTime -} from '../lists'; +import type { Store } from '../player/types'; +import ListWalker from '../_common/ListWalker'; -import StatedScreen from './StatedScreen/StatedScreen'; +import Screen from './Screen/Screen'; -import ListWalker from './managers/ListWalker'; import PagesManager from './managers/PagesManager'; import MouseMoveManager from './managers/MouseMoveManager'; import PerformanceTrackManager from './managers/PerformanceTrackManager'; import WindowNodeCounter from './managers/WindowNodeCounter'; import ActivityManager from './managers/ActivityManager'; -import AssistManager from './managers/AssistManager'; 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 './StatedScreen/StatedScreen'; -import { INITIAL_STATE as ASSIST_INITIAL_STATE, State as AssistState } from './managers/AssistManager'; -import { INITIAL_STATE as LISTS_INITIAL_STATE , LIST_NAMES, initLists } from './Lists'; +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 Lists, { INITIAL_STATE as LISTS_INITIAL_STATE } from './Lists'; import type { PerformanceChartPoint } from './managers/PerformanceTrackManager'; import type { SkipInterval } from './managers/ActivityManager'; @@ -50,8 +44,16 @@ export interface State extends SuperState, AssistState { domBuildingTime?: any, loadTime?: any, error: boolean, - devtoolsLoading: boolean + devtoolsLoading: boolean, + + liveTimeTravel: boolean, + messagesLoading: boolean, + cssLoading: boolean, + + ready: boolean, + lastMessageTime: number, } + export const INITIAL_STATE: State = { ...SUPER_INITIAL_STATE, ...LISTS_INITIAL_STATE, @@ -60,6 +62,14 @@ export const INITIAL_STATE: State = { skipIntervals: [], error: false, devtoolsLoading: false, + + liveTimeTravel: false, + messagesLoading: false, + cssLoading: false, + get ready() { + return !this.messagesLoading && !this.cssLoading + }, + lastMessageTime: 0, }; @@ -82,7 +92,7 @@ const visualChanges = [ "set_viewport_scroll", ] -export default class MessageDistributor extends StatedScreen { +export default class MessageManager extends Screen { // TODO: consistent with the other data-lists private locationEventManager: ListWalker/**/ = new ListWalker(); private locationManager: ListWalker = new ListWalker(); @@ -95,12 +105,11 @@ export default class MessageDistributor extends StatedScreen { private resizeManager: ListWalker = new ListWalker([]); private pagesManager: PagesManager; private mouseMoveManager: MouseMoveManager; - private assistManager: AssistManager; private scrollManager: ListWalker = new ListWalker(); private readonly decoder = new Decoder(); - private readonly lists = initLists(); + private readonly lists: Lists; private activityManager: ActivityManager | null = null; @@ -109,37 +118,39 @@ export default class MessageDistributor extends StatedScreen { private lastMessageTime: number = 0; private lastMessageInFileTime: number = 0; - constructor(private readonly session: any /*Session*/, config: any, live: boolean) { + constructor( + private readonly session: any /*Session*/, + private readonly state: Store, + config: any, + live: boolean, + ) { super(); this.pagesManager = new PagesManager(this, this.session.isMobile) this.mouseMoveManager = new MouseMoveManager(this); - this.assistManager = new AssistManager(session, this, config); this.sessionStart = this.session.startedAt; if (live) { - initListsDepr({}) - this.assistManager.connect(this.session.agentToken); + this.lists = new Lists() } else { this.activityManager = new ActivityManager(this.session.duration.milliseconds); /* == REFACTOR_ME == */ - const eventList = this.session.events.toJSON(); - - initListsDepr({ - event: eventList, - stack: this.session.stackEvents.toJSON(), - resource: this.session.resources.toJSON(), - }); - + const eventList = session.events.toJSON(); // TODO: fix types for events, remove immutable js eventList.forEach((e: Record) => { if (e.type === EVENT_TYPES.LOCATION) { //TODO type system this.locationEventManager.append(e); } - }); - this.session.errors.forEach((e: Record) => { - this.lists.exceptions.append(e); - }); + }) + + this.lists = new Lists({ + event: eventList, + stack: session.stackEvents.toJSON(), + resource: session.resources.toJSON(), + exceptions: session.errors, + }) + + /* === */ this.loadMessages(); } @@ -187,23 +198,21 @@ export default class MessageDistributor extends StatedScreen { private waitingForFiles: boolean = false private onFileReadSuccess = () => { - const stateToUpdate: {[key:string]: any} = { + const stateToUpdate = { performanceChartData: this.performanceTrackManager.chartData, performanceAvaliability: this.performanceTrackManager.avaliability, + ...this.lists.getFullListsState() } - LIST_NAMES.forEach(key => { - stateToUpdate[ `${ key }List` ] = this.lists[ key ].list - }) if (this.activityManager) { this.activityManager.end() stateToUpdate.skipIntervals = this.activityManager.list } - update(stateToUpdate) + this.state.update(stateToUpdate) } private onFileReadFailed = (e: any) => { logger.error(e) - update({ error: true }) + this.state.update({ error: true }) toast.error('Error requesting a session file') } private onFileReadFinally = () => { @@ -247,14 +256,14 @@ export default class MessageDistributor extends StatedScreen { // load devtools if (this.session.devtoolsURL.length) { - update({ devtoolsLoading: true }) + this.state.update({ devtoolsLoading: true }) loadFiles(this.session.devtoolsURL, createNewParser()) .catch(() => requestEFSDevtools(this.session.sessionId) .then(createNewParser(false)) ) //.catch() // not able to download the devtools file - .finally(() => update({ devtoolsLoading: false })) + .finally(() => this.state.update({ devtoolsLoading: false })) } } @@ -264,7 +273,7 @@ export default class MessageDistributor extends StatedScreen { this.parseAndDistributeMessages(new MFileReader(byteArray, this.sessionStart), onMessage) } const updateState = () => - update({ + this.state.update({ liveTimeTravel: true, }); @@ -304,7 +313,7 @@ export default class MessageDistributor extends StatedScreen { /* == REFACTOR_ME == */ const lastLoadedLocationMsg = this.loadedLocationManager.moveGetLast(t, index); if (!!lastLoadedLocationMsg) { - setListsStartTime(lastLoadedLocationMsg.time) + // TODO: page-wise resources list // setListsStartTime(lastLoadedLocationMsg.time) this.navigationStartOffset = lastLoadedLocationMsg.navigationStart - this.sessionStart; } const llEvent = this.locationEventManager.moveGetLast(t, index); @@ -340,15 +349,8 @@ export default class MessageDistributor extends StatedScreen { stateToUpdate.performanceChartTime = lastPerformanceTrackMessage.time; } - LIST_NAMES.forEach(key => { - const lastMsg = this.lists[key].moveGetLast(t, key === 'exceptions' ? undefined : index); - if (lastMsg != null) { - // @ts-ignore TODO: fix types - stateToUpdate[`${key}ListNow`] = this.lists[key].listNow; - } - }); - - Object.keys(stateToUpdate).length > 0 && update(stateToUpdate); + this.lists.moveGetState(t) + Object.keys(stateToUpdate).length > 0 && this.state.update(stateToUpdate); /* Sequence of the managers is important here */ // Preparing the size of "screen" @@ -405,6 +407,7 @@ export default class MessageDistributor extends StatedScreen { private distributeMessage(msg: Message, index: number): void { const lastMessageTime = Math.max(msg.time, this.lastMessageTime) this.lastMessageTime = lastMessageTime + this.state.update({ lastMessageTime }) if (visualChanges.includes(msg.tp)) { this.activityManager?.updateAcctivity(msg.time); } @@ -414,15 +417,15 @@ export default class MessageDistributor extends StatedScreen { /* Lists: */ case "console_log": if (msg.level === 'debug') break; - listAppend("log", Log({ + this.lists.lists.log.append(Log({ level: msg.level, value: msg.value, time, index, - })); + })) break; case "fetch": - listAppend("fetch", Resource({ + this.lists.lists.fetch.append(Resource({ method: msg.method, url: msg.url, payload: msg.request, @@ -469,42 +472,42 @@ export default class MessageDistributor extends StatedScreen { decoded = this.decodeStateMessage(msg, ["state", "action"]); logger.log('redux', decoded) if (decoded != null) { - this.lists.redux.append(decoded); + this.lists.lists.redux.append(decoded); } break; case "ng_rx": decoded = this.decodeStateMessage(msg, ["state", "action"]); logger.log('ngrx', decoded) if (decoded != null) { - this.lists.ngrx.append(decoded); + this.lists.lists.ngrx.append(decoded); } break; case "vuex": decoded = this.decodeStateMessage(msg, ["state", "mutation"]); logger.log('vuex', decoded) if (decoded != null) { - this.lists.vuex.append(decoded); + this.lists.lists.vuex.append(decoded); } break; case "zustand": decoded = this.decodeStateMessage(msg, ["state", "mutation"]) logger.log('zustand', decoded) if (decoded != null) { - this.lists.zustand.append(decoded) + this.lists.lists.zustand.append(decoded) } case "mob_x": decoded = this.decodeStateMessage(msg, ["payload"]); logger.log('mobx', decoded) if (decoded != null) { - this.lists.mobx.append(decoded); + this.lists.lists.mobx.append(decoded); } break; case "graph_ql": - this.lists.graphql.append(msg); + this.lists.lists.graphql.append(msg); break; case "profiler": - this.lists.profiles.append(msg); + this.lists.lists.profiles.append(msg); break; default: switch (msg.tp) { @@ -540,11 +543,27 @@ export default class MessageDistributor extends StatedScreen { return this.pagesManager.minTime; } + + setMessagesLoading(messagesLoading: boolean) { + this.display(!messagesLoading); + this.state.update({ messagesLoading }); + } + + setCSSLoading(cssLoading: boolean) { + this.displayFrame(!cssLoading); + this.state.update({ cssLoading }); + } + + private setSize({ height, width }: { height: number, width: number }) { + this.scale({ height, width }); + this.state.update({ width, height }); + + //this.updateMarketTargets() + } + // TODO: clean managers? clean() { - super.clean(); - update(INITIAL_STATE); - this.assistManager.clear(); + this.state.update(INITIAL_STATE); this.incomingMessages.length = 0 } diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts b/frontend/app/player/_web/Screen/BaseScreen.ts similarity index 94% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts rename to frontend/app/player/_web/Screen/BaseScreen.ts index d86284851..67f527e01 100644 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/BaseScreen.ts +++ b/frontend/app/player/_web/Screen/BaseScreen.ts @@ -1,5 +1,4 @@ import styles from './screen.module.css'; -import { getState } from '../../../store'; import type { Point } from './types'; @@ -86,9 +85,6 @@ export default abstract class BaseScreen { parentElement.appendChild(this.screen); this.parentElement = parentElement; - // parentElement.onresize = this.scale; - window.addEventListener('resize', this.scale); - this.scale(); /* == For the Inspecting Document content == */ this.overlay.addEventListener('contextmenu', () => { @@ -197,9 +193,8 @@ export default abstract class BaseScreen { return this.s; } - _scale() { + scale({ height, width }: { height: number, width: number }) { if (!this.parentElement) return; - const { height, width } = getState(); const { offsetWidth, offsetHeight } = this.parentElement; this.s = Math.min(offsetWidth / width, offsetHeight / height); @@ -216,13 +211,4 @@ export default abstract class BaseScreen { this.boundingRect = this.overlay.getBoundingClientRect(); } - - scale = () => { // TODO: solve classes inheritance issues in typescript - this._scale(); - } - - - clean() { - window.removeEventListener('resize', this.scale); - } } diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Cursor.ts b/frontend/app/player/_web/Screen/Cursor.ts similarity index 100% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/Cursor.ts rename to frontend/app/player/_web/Screen/Cursor.ts diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Inspector.js b/frontend/app/player/_web/Screen/Inspector.js similarity index 100% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/Inspector.js rename to frontend/app/player/_web/Screen/Inspector.js diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Marker.js b/frontend/app/player/_web/Screen/Marker.ts similarity index 66% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/Marker.js rename to frontend/app/player/_web/Screen/Marker.ts index ff1476ac3..4d3fab9b1 100644 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Marker.js +++ b/frontend/app/player/_web/Screen/Marker.ts @@ -1,32 +1,32 @@ +import type BaseScreen from './BaseScreen' import styles from './marker.module.css'; -function escapeRegExp(string) { +function escapeRegExp(string: string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } -function escapeHtml(string) { +function escapeHtml(string: string) { return string.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); } -function safeString(string) { +function safeString(string: string) { return (escapeHtml(escapeRegExp(string))) } export default class Marker { - _target = null; - _selector = null; - _tooltip = null; + private _target: Element | null = null; + private selector: string | null = null; + private tooltip: HTMLDivElement + private marker: HTMLDivElement - constructor(overlay, screen) { - this.screen = screen; - - this._tooltip = document.createElement('div'); - this._tooltip.className = styles.tooltip; - this._tooltip.appendChild(document.createElement('div')); + constructor(overlay: HTMLElement, private readonly screen: BaseScreen) { + this.tooltip = document.createElement('div'); + this.tooltip.className = styles.tooltip; + this.tooltip.appendChild(document.createElement('div')); const htmlStr = document.createElement('div'); htmlStr.innerHTML = 'Right-click > Inspect for more details.'; - this._tooltip.appendChild(htmlStr); + this.tooltip.appendChild(htmlStr); const marker = document.createElement('div'); marker.className = styles.marker; @@ -43,34 +43,34 @@ export default class Marker { marker.appendChild(markerT); marker.appendChild(markerB); - marker.appendChild(this._tooltip); + marker.appendChild(this.tooltip); overlay.appendChild(marker); - this._marker = marker; + this.marker = marker; } get target() { return this._target; } - mark(element) { + mark(element: Element | null) { if (this._target === element) { return; } this._target = element; - this._selector = null; + this.selector = null; this.redraw(); } unmark() { - this.mark(null); + this.mark(null) } - _autodefineTarget() { + private autodefineTarget() { // TODO: put to Screen - if (this._selector) { + if (this.selector) { try { - const fitTargets = this.screen.document.querySelectorAll(this._selector); + const fitTargets = this.screen.document.querySelectorAll(this.selector); if (fitTargets.length === 0) { this._target = null; } else { @@ -90,9 +90,9 @@ export default class Marker { } } - markBySelector(selector) { - this._selector = selector; - this._autodefineTarget(); + markBySelector(selector: string) { + this.selector = selector; + this.autodefineTarget(); this.redraw(); } @@ -116,20 +116,20 @@ export default class Marker { } redraw() { - if (this._selector) { - this._autodefineTarget(); + if (this.selector) { + this.autodefineTarget(); } if (!this._target) { - this._marker.style.display = 'none'; + this.marker.style.display = 'none'; return; } const rect = this._target.getBoundingClientRect(); - this._marker.style.display = 'block'; - this._marker.style.left = rect.left + 'px'; - this._marker.style.top = rect.top + 'px'; - this._marker.style.width = rect.width + 'px'; - this._marker.style.height = rect.height + 'px'; + this.marker.style.display = 'block'; + this.marker.style.left = rect.left + 'px'; + this.marker.style.top = rect.top + 'px'; + this.marker.style.width = rect.width + 'px'; + this.marker.style.height = rect.height + 'px'; - this._tooltip.firstChild.innerHTML = this.getTagString(this._target); + this.tooltip.firstChild.innerHTML = this.getTagString(this._target); } } diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Screen.ts b/frontend/app/player/_web/Screen/Screen.ts similarity index 91% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/Screen.ts rename to frontend/app/player/_web/Screen/Screen.ts index a0ae8a800..59986423e 100644 --- a/frontend/app/player/MessageDistributor/StatedScreen/Screen/Screen.ts +++ b/frontend/app/player/_web/Screen/Screen.ts @@ -2,7 +2,6 @@ import Marker from './Marker'; import Cursor from './Cursor'; import Inspector from './Inspector'; // import styles from './screen.module.css'; -// import { getState } from '../../../store'; import BaseScreen from './BaseScreen'; export { INITIAL_STATE } from './BaseScreen'; @@ -12,7 +11,7 @@ export default class Screen extends BaseScreen { public readonly cursor: Cursor; private substitutor: BaseScreen | null = null; private inspector: Inspector | null = null; - private marker: Marker | null = null; + public marker: Marker | null = null; constructor() { super(); this.cursor = new Cursor(this.overlay); @@ -26,14 +25,14 @@ export default class Screen extends BaseScreen { return this.getElementsFromInternalPoint(this.cursor.getPosition()); } - _scale() { - super._scale(); + scale(dims: { height: number, width: number }) { + super.scale(dims) if (this.substitutor) { - this.substitutor._scale(); + this.substitutor.scale(dims) } } - enableInspector(clickCallback: ({ target: Element }) => void): Document | null { + enableInspector(clickCallback: (e: { target: Element }) => void): Document | null { if (!this.parentElement) return null; if (!this.substitutor) { this.substitutor = new Screen(); diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/cursor.module.css b/frontend/app/player/_web/Screen/cursor.module.css similarity index 100% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/cursor.module.css rename to frontend/app/player/_web/Screen/cursor.module.css diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/index.js b/frontend/app/player/_web/Screen/index.js similarity index 100% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/index.js rename to frontend/app/player/_web/Screen/index.js diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/marker.module.css b/frontend/app/player/_web/Screen/marker.module.css similarity index 100% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/marker.module.css rename to frontend/app/player/_web/Screen/marker.module.css diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/screen.module.css b/frontend/app/player/_web/Screen/screen.module.css similarity index 100% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/screen.module.css rename to frontend/app/player/_web/Screen/screen.module.css diff --git a/frontend/app/player/MessageDistributor/StatedScreen/Screen/types.ts b/frontend/app/player/_web/Screen/types.ts similarity index 100% rename from frontend/app/player/MessageDistributor/StatedScreen/Screen/types.ts rename to frontend/app/player/_web/Screen/types.ts diff --git a/frontend/app/player/_web/WebPlayer.ts b/frontend/app/player/_web/WebPlayer.ts new file mode 100644 index 000000000..a48577288 --- /dev/null +++ b/frontend/app/player/_web/WebPlayer.ts @@ -0,0 +1,178 @@ +import type { Store } from '../player/types' +import Player, { State as PlayerState } from '../player/Player' + +import MessageManager from './MessageManager' +import AssistManager from './assist/AssistManager' +import Screen from './Screen/Screen' +import { State as MMState, INITIAL_STATE as MM_INITIAL_STATE } from './MessageManager' + + + +export default class WebPlayer extends Player { + private readonly screen: Screen + private readonly messageManager: MessageManager + + assistManager: AssistManager // public so far + + constructor(private wpState: Store, session, config, live: boolean) { + // TODO: separate screen from manager + const screen = new MessageManager(session, wpState, config, live) + super(wpState, screen) + this.screen = screen + this.messageManager = screen + + // TODO: separate LiveWebPlayer + this.assistManager = new AssistManager(session, this.messageManager, config, wpState) + + + const endTime = !live && session.duration.valueOf() + wpState.update({ + //@ts-ignore + initialized: true, + //@ts-ignore + session, + + live, + livePlay: live, + endTime, // : 0, //TODO: through initialState + }) + + if (live) { + this.assistManager.connect(session.agentToken) + } + } + + attach(parent: HTMLElement) { + this.screen.attach(parent) + window.addEventListener('resize', this.scale) + this.scale() + } + scale = () => { + const { width, height } = this.wpState.get() + this.screen.scale({ width, height }) + } + mark(e: Element) { + this.screen.marker.mark(e) + } + + updateMarketTargets() { + // const { markedTargets } = getState(); + // if (markedTargets) { + // update({ + // markedTargets: markedTargets.map((mt: any) => ({ + // ...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, + // } + // } + + 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 + 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.window?.scrollTo(this.actualScroll.x, this.actualScroll.y) + // this.actualScroll = null + // } + // update({ markedTargets: null }); + // } + } + + markTargets(targets: { selector: string, count: number }[] | null) { + // this.animator.pause(); + // this.setMarkedTargets(targets); + } + + toggleInspectorMode(flag, clickCallback) { + // if (typeof flag !== 'boolean') { + // const { inspectorMode } = getState(); + // flag = !inspectorMode; + // } + + // if (flag) { + // this.pause() + // update({ inspectorMode: true }); + // return super.enableInspector(clickCallback); + // } else { + // super.disableInspector(); + // update({ inspectorMode: false }); + // } + } + + async toggleTimetravel() { + if (!this.wpState.get().liveTimeTravel) { + return await this.messageManager.reloadWithUnprocessedFile() + } + } + + toggleUserName(name?: string) { + this.screen.cursor.toggleUserName(name) + } + clean() { + super.clean() + this.assistManager.clean() + window.removeEventListener('resize', this.scale) + } +} + diff --git a/frontend/app/player/MessageDistributor/managers/AnnotationCanvas.ts b/frontend/app/player/_web/assist/AnnotationCanvas.ts similarity index 100% rename from frontend/app/player/MessageDistributor/managers/AnnotationCanvas.ts rename to frontend/app/player/_web/assist/AnnotationCanvas.ts diff --git a/frontend/app/player/MessageDistributor/managers/AssistManager.ts b/frontend/app/player/_web/assist/AssistManager.ts similarity index 87% rename from frontend/app/player/MessageDistributor/managers/AssistManager.ts rename to frontend/app/player/_web/assist/AssistManager.ts index 897d8dfba..1e1274226 100644 --- a/frontend/app/player/MessageDistributor/managers/AssistManager.ts +++ b/frontend/app/player/_web/assist/AssistManager.ts @@ -1,11 +1,10 @@ import type { Socket } from 'socket.io-client'; import type Peer from 'peerjs'; import type { MediaConnection } from 'peerjs'; -import type MessageDistributor from '../MessageDistributor'; -import store from 'App/store'; +import type MessageManager from '../MessageManager'; +import appStore from 'App/store'; import type { LocalStream } from './LocalStream'; -import { update, getState } from '../../store'; -// import { iceServerConfigFromString } from 'App/utils' +import type { Store } from '../../player/types' import AnnotationCanvas from './AnnotationCanvas'; import MStreamReader from '../messages/MStreamReader'; import JSONRawMessageReader from '../messages/JSONRawMessageReader' @@ -76,10 +75,15 @@ export default class AssistManager { private videoStreams: Record = {} // TODO: Session type - constructor(private session: any, private md: MessageDistributor, private config: any) {} + constructor( + private session: any, + private md: MessageManager, + private config: any, + private store: Store, + ) {} private setStatus(status: ConnectionStatus) { - if (getState().peerConnectionStatus === ConnectionStatus.Disconnected && + if (this.store.get().peerConnectionStatus === ConnectionStatus.Disconnected && status !== ConnectionStatus.Connected) { return } @@ -94,7 +98,7 @@ export default class AssistManager { } else { this.md.display(false); } - update({ peerConnectionStatus: status }); + this.store.update({ peerConnectionStatus: status }); } private get peerID(): string { @@ -106,7 +110,7 @@ export default class AssistManager { this.socketCloseTimeout && clearTimeout(this.socketCloseTimeout) if (document.hidden) { this.socketCloseTimeout = setTimeout(() => { - const state = getState() + const state = this.store.get() if (document.hidden && (state.calling === CallingState.NoCall && state.remoteControl === RemoteControlStatus.Enabled)) { this.socket?.close() @@ -134,7 +138,7 @@ export default class AssistManager { } const now = +new Date() - update({ assistStart: now }) + this.store.update({ assistStart: now }) import('socket.io-client').then(({ default: io }) => { if (this.cleaned) { return } @@ -162,7 +166,7 @@ export default class AssistManager { }) socket.on("disconnect", () => { this.toggleRemoteControl(false) - update({ calling: CallingState.NoCall }) + this.store.update({ calling: CallingState.NoCall }) }) socket.on('messages', messages => { jmr.append(messages) // as RawMessage[] @@ -172,7 +176,7 @@ export default class AssistManager { this.setStatus(ConnectionStatus.Connected) // Call State - if (getState().calling === CallingState.Reconnecting) { + if (this.store.get().calling === CallingState.Reconnecting) { this._callSessionPeer() // reconnecting call (todo improve code separation) } } @@ -222,15 +226,15 @@ export default class AssistManager { this.setStatus(ConnectionStatus.Disconnected) }, 30000) - if (getState().remoteControl === RemoteControlStatus.Requesting) { + if (this.store.get().remoteControl === RemoteControlStatus.Requesting) { this.toggleRemoteControl(false) // else its remaining } // Call State - if (getState().calling === CallingState.OnCall) { - update({ calling: CallingState.Reconnecting }) - } else if (getState().calling === CallingState.Requesting){ - update({ calling: CallingState.NoCall }) + if (this.store.get().calling === CallingState.OnCall) { + this.store.update({ calling: CallingState.Reconnecting }) + } else if (this.store.get().calling === CallingState.Requesting){ + this.store.update({ calling: CallingState.NoCall }) } }) socket.on('error', e => { @@ -263,7 +267,7 @@ export default class AssistManager { private onMouseClick = (e: MouseEvent): void => { if (!this.socket) { return; } - if (getState().annotating) { return; } // ignore clicks while annotating + if (this.store.get().annotating) { return; } // ignore clicks while annotating const data = this.md.getInternalViewportCoordinates(e) // const el = this.md.getElementFromPoint(e); // requires requestiong node_id from domManager @@ -299,31 +303,31 @@ export default class AssistManager { this.md.overlay.addEventListener("click", this.onMouseClick) this.md.overlay.addEventListener("wheel", this.onWheel) this.md.toggleRemoteControlStatus(true) - update({ remoteControl: RemoteControlStatus.Enabled }) + this.store.update({ remoteControl: RemoteControlStatus.Enabled }) } else { this.md.overlay.removeEventListener("mousemove", this.onMouseMove) this.md.overlay.removeEventListener("click", this.onMouseClick) this.md.overlay.removeEventListener("wheel", this.onWheel) this.md.toggleRemoteControlStatus(false) - update({ remoteControl: RemoteControlStatus.Disabled }) + this.store.update({ remoteControl: RemoteControlStatus.Disabled }) this.toggleAnnotation(false) } } requestReleaseRemoteControl = () => { if (!this.socket) { return } - const remoteControl = getState().remoteControl + const remoteControl = this.store.get().remoteControl if (remoteControl === RemoteControlStatus.Requesting) { return } if (remoteControl === RemoteControlStatus.Disabled) { - update({ remoteControl: RemoteControlStatus.Requesting }) + this.store.update({ remoteControl: RemoteControlStatus.Requesting }) this.socket.emit("request_control", JSON.stringify({ ...this.session.agentInfo, query: document.location.search })) // setTimeout(() => { - // if (getState().remoteControl !== RemoteControlStatus.Requesting) { return } + // if (this.store.get().remoteControl !== RemoteControlStatus.Requesting) { return } // this.socket?.emit("release_control") - // update({ remoteControl: RemoteControlStatus.Disabled }) + // this.store.update({ remoteControl: RemoteControlStatus.Disabled }) // }, 8000) } else { this.socket.emit("release_control") @@ -416,15 +420,15 @@ export default class AssistManager { private handleCallEnd() { this.callArgs && this.callArgs.onCallEnd() this.callConnection[0] && this.callConnection[0].close() - update({ calling: CallingState.NoCall }) + this.store.update({ calling: CallingState.NoCall }) this.callArgs = null this.toggleAnnotation(false) } public initiateCallEnd = async () => { - this.socket?.emit("call_end", store.getState().getIn([ 'user', 'account', 'name'])) + this.socket?.emit("call_end", appStore.getState().getIn([ 'user', 'account', 'name'])) this.handleCallEnd() - const remoteControl = getState().remoteControl + const remoteControl = this.store.get().remoteControl if (remoteControl === RemoteControlStatus.Enabled) { this.socket.emit("release_control") this.toggleRemoteControl(false) @@ -432,10 +436,10 @@ export default class AssistManager { } private onRemoteCallEnd = () => { - if (getState().calling === CallingState.Requesting) { + if (this.store.get().calling === CallingState.Requesting) { this.callArgs && this.callArgs.onReject() this.callConnection[0] && this.callConnection[0].close() - update({ calling: CallingState.NoCall }) + this.store.update({ calling: CallingState.NoCall }) this.callArgs = null this.toggleAnnotation(false) } else { @@ -487,10 +491,10 @@ export default class AssistManager { /** Connecting to the app user */ private _callSessionPeer() { - if (![CallingState.NoCall, CallingState.Reconnecting].includes(getState().calling)) { return } - update({ calling: CallingState.Connecting }) + if (![CallingState.NoCall, CallingState.Reconnecting].includes(this.store.get().calling)) { return } + this.store.update({ calling: CallingState.Connecting }) this._peerConnection(this.peerID); - this.socket && this.socket.emit("_agent_name", store.getState().getIn([ 'user', 'account', 'name'])) + this.socket && this.socket.emit("_agent_name", appStore.getState().getIn([ 'user', 'account', 'name'])) } private async _peerConnection(remotePeerId: string) { @@ -509,7 +513,7 @@ export default class AssistManager { }) call.on('stream', stream => { - getState().calling !== CallingState.OnCall && update({ calling: CallingState.OnCall }) + this.store.get().calling !== CallingState.OnCall && this.store.update({ calling: CallingState.OnCall }) this.videoStreams[call.peer] = stream.getVideoTracks()[0] @@ -529,9 +533,9 @@ export default class AssistManager { } toggleAnnotation(enable?: boolean) { - // if (getState().calling !== CallingState.OnCall) { return } + // if (this.store.get().calling !== CallingState.OnCall) { return } if (typeof enable !== "boolean") { - enable = !!getState().annotating + enable = !!this.store.get().annotating } if (enable && !this.annot) { const annot = this.annot = new AnnotationCanvas() @@ -559,11 +563,11 @@ export default class AssistManager { annot.move([ data.x, data.y ]) this.socket.emit("moveAnnotation", [ data.x, data.y ]) }) - update({ annotating: true }) + this.store.update({ annotating: true }) } else if (!enable && !!this.annot) { this.annot.remove() this.annot = null - update({ annotating: false }) + this.store.update({ annotating: false }) } } @@ -577,7 +581,7 @@ export default class AssistManager { /* ==== Cleaning ==== */ private cleaned: boolean = false - clear() { + clean() { this.cleaned = true // sometimes cleaned before modules loaded this.initiateCallEnd(); if (this._peer) { diff --git a/frontend/app/player/MessageDistributor/managers/LocalStream.ts b/frontend/app/player/_web/assist/LocalStream.ts similarity index 100% rename from frontend/app/player/MessageDistributor/managers/LocalStream.ts rename to frontend/app/player/_web/assist/LocalStream.ts diff --git a/frontend/app/player/MessageDistributor/managers/ActivityManager.ts b/frontend/app/player/_web/managers/ActivityManager.ts similarity index 83% rename from frontend/app/player/MessageDistributor/managers/ActivityManager.ts rename to frontend/app/player/_web/managers/ActivityManager.ts index 412fefdce..32265f924 100644 --- a/frontend/app/player/MessageDistributor/managers/ActivityManager.ts +++ b/frontend/app/player/_web/managers/ActivityManager.ts @@ -1,18 +1,18 @@ -import ListWalker from './ListWalker'; +import ListWalker from '../../_common/ListWalker'; class SkipIntervalCls { - constructor(private readonly start = 0, private readonly end = 0) {} + constructor(readonly start = 0, readonly end = 0) {} get time(): number { return this.start; } - contains(ts: number) { + contains(ts) { return ts > this.start && ts < this.end; } } -export type SkipInterval = InstanceType; // exporting only class' type +export type SkipInterval = InstanceType; export default class ActivityManager extends ListWalker { diff --git a/frontend/app/player/MessageDistributor/managers/DOM/DOMManager.ts b/frontend/app/player/_web/managers/DOM/DOMManager.ts similarity index 99% rename from frontend/app/player/MessageDistributor/managers/DOM/DOMManager.ts rename to frontend/app/player/_web/managers/DOM/DOMManager.ts index 2688abb14..370df5c03 100644 --- a/frontend/app/player/MessageDistributor/managers/DOM/DOMManager.ts +++ b/frontend/app/player/_web/managers/DOM/DOMManager.ts @@ -1,9 +1,9 @@ import logger from 'App/logger'; -import type StatedScreen from '../../StatedScreen'; +import type MessageManager from '../../MessageManager'; import type { Message, SetNodeScroll, CreateElementNode } from '../../messages'; -import ListWalker from '../ListWalker'; +import ListWalker from '../../../_common/ListWalker'; import StylesManager, { rewriteNodeStyleSheet } from './StylesManager'; import FocusManager from './FocusManager'; import { @@ -51,7 +51,7 @@ export default class DOMManager extends ListWalker { constructor( - private readonly screen: StatedScreen, + private readonly screen: MessageManager, private readonly isMobile: boolean, public readonly time: number ) { diff --git a/frontend/app/player/MessageDistributor/managers/DOM/FocusManager.ts b/frontend/app/player/_web/managers/DOM/FocusManager.ts similarity index 93% rename from frontend/app/player/MessageDistributor/managers/DOM/FocusManager.ts rename to frontend/app/player/_web/managers/DOM/FocusManager.ts index 6f80ed16c..629f625e3 100644 --- a/frontend/app/player/MessageDistributor/managers/DOM/FocusManager.ts +++ b/frontend/app/player/_web/managers/DOM/FocusManager.ts @@ -1,7 +1,7 @@ import logger from 'App/logger'; import type { SetNodeFocus } from '../../messages'; import type { VElement } from './VirtualDOM'; -import ListWalker from '../ListWalker'; +import ListWalker from '../../../_common/ListWalker'; const FOCUS_CLASS = "-openreplay-focus" diff --git a/frontend/app/player/MessageDistributor/managers/DOM/StylesManager.ts b/frontend/app/player/_web/managers/DOM/StylesManager.ts similarity index 95% rename from frontend/app/player/MessageDistributor/managers/DOM/StylesManager.ts rename to frontend/app/player/_web/managers/DOM/StylesManager.ts index b6dddcdd9..1cb49c5dd 100644 --- a/frontend/app/player/MessageDistributor/managers/DOM/StylesManager.ts +++ b/frontend/app/player/_web/managers/DOM/StylesManager.ts @@ -1,9 +1,9 @@ -import type StatedScreen from '../../StatedScreen'; +import type MessageManager from '../../MessageManager'; import type { CssInsertRule, CssDeleteRule } from '../../messages'; type CSSRuleMessage = CssInsertRule | CssDeleteRule; -import ListWalker from '../ListWalker'; +import ListWalker from '../../../_common/ListWalker'; const HOVER_CN = "-openreplay-hover"; @@ -26,7 +26,7 @@ export default class StylesManager extends ListWalker { private linkLoadPromises: Array> = []; private skipCSSLinks: Array = []; // should be common for all pages - constructor(private readonly screen: StatedScreen) { + constructor(private readonly screen: MessageManager) { super(); } diff --git a/frontend/app/player/MessageDistributor/managers/DOM/VirtualDOM.ts b/frontend/app/player/_web/managers/DOM/VirtualDOM.ts similarity index 100% rename from frontend/app/player/MessageDistributor/managers/DOM/VirtualDOM.ts rename to frontend/app/player/_web/managers/DOM/VirtualDOM.ts diff --git a/frontend/app/player/MessageDistributor/managers/DOM/safeCSSRules.ts b/frontend/app/player/_web/managers/DOM/safeCSSRules.ts similarity index 100% rename from frontend/app/player/MessageDistributor/managers/DOM/safeCSSRules.ts rename to frontend/app/player/_web/managers/DOM/safeCSSRules.ts diff --git a/frontend/app/player/MessageDistributor/managers/MouseMoveManager.ts b/frontend/app/player/_web/managers/MouseMoveManager.ts similarity index 89% rename from frontend/app/player/MessageDistributor/managers/MouseMoveManager.ts rename to frontend/app/player/_web/managers/MouseMoveManager.ts index ca9e3b740..5dd3160a5 100644 --- a/frontend/app/player/MessageDistributor/managers/MouseMoveManager.ts +++ b/frontend/app/player/_web/managers/MouseMoveManager.ts @@ -1,7 +1,7 @@ -import type StatedScreen from '../StatedScreen'; +import type Screen from '../Screen/Screen'; import type { MouseMove } from '../messages'; -import ListWalker from './ListWalker'; +import ListWalker from '../../_common/ListWalker'; const HOVER_CLASS = "-openreplay-hover"; const HOVER_CLASS_DEPR = "-asayer-hover"; @@ -9,7 +9,7 @@ const HOVER_CLASS_DEPR = "-asayer-hover"; export default class MouseMoveManager extends ListWalker { private hoverElements: Array = []; - constructor(private screen: StatedScreen) {super()} + constructor(private screen: Screen) {super()} private updateHover(): void { const curHoverElements = this.screen.getCursorTargets(); diff --git a/frontend/app/player/MessageDistributor/managers/PagesManager.ts b/frontend/app/player/_web/managers/PagesManager.ts similarity index 85% rename from frontend/app/player/MessageDistributor/managers/PagesManager.ts rename to frontend/app/player/_web/managers/PagesManager.ts index 9a4398246..6a3f20f7c 100644 --- a/frontend/app/player/MessageDistributor/managers/PagesManager.ts +++ b/frontend/app/player/_web/managers/PagesManager.ts @@ -1,7 +1,7 @@ -import type StatedScreen from '../StatedScreen'; +import type Screen from '../Screen/Screen'; import type { Message } from '../messages'; -import ListWalker from './ListWalker'; +import ListWalker from '../../_common/ListWalker'; import DOMManager from './DOM/DOMManager'; @@ -9,9 +9,9 @@ export default class PagesManager extends ListWalker { private currentPage: DOMManager | null = null private isMobile: boolean; - private screen: StatedScreen; + private screen: Screen; - constructor(screen: StatedScreen, isMobile: boolean) { + constructor(screen: Screen, isMobile: boolean) { super() this.screen = screen this.isMobile = isMobile diff --git a/frontend/app/player/MessageDistributor/managers/PerformanceTrackManager.ts b/frontend/app/player/_web/managers/PerformanceTrackManager.ts similarity index 98% rename from frontend/app/player/MessageDistributor/managers/PerformanceTrackManager.ts rename to frontend/app/player/_web/managers/PerformanceTrackManager.ts index c4a4a8e63..424466b4a 100644 --- a/frontend/app/player/MessageDistributor/managers/PerformanceTrackManager.ts +++ b/frontend/app/player/_web/managers/PerformanceTrackManager.ts @@ -1,6 +1,6 @@ import type { PerformanceTrack, SetPageVisibility } from '../messages'; -import ListWalker from './ListWalker'; +import ListWalker from '../../_common/ListWalker'; export type PerformanceChartPoint = { time: number, diff --git a/frontend/app/player/MessageDistributor/managers/ReduxStateManager.ts b/frontend/app/player/_web/managers/ReduxStateManager.ts similarity index 96% rename from frontend/app/player/MessageDistributor/managers/ReduxStateManager.ts rename to frontend/app/player/_web/managers/ReduxStateManager.ts index 81638994e..4756b303a 100644 --- a/frontend/app/player/MessageDistributor/managers/ReduxStateManager.ts +++ b/frontend/app/player/_web/managers/ReduxStateManager.ts @@ -1,5 +1,5 @@ // import { applyChange, revertChange } from 'deep-diff'; -// import ListWalker from './ListWalker'; +// import ListWalker from '../../_common/ListWalker'; // import type { Redux } from '../messages'; // export default class ReduxStateManager extends ListWalker { diff --git a/frontend/app/player/MessageDistributor/managers/WindowNodeCounter.ts b/frontend/app/player/_web/managers/WindowNodeCounter.ts similarity index 100% rename from frontend/app/player/MessageDistributor/managers/WindowNodeCounter.ts rename to frontend/app/player/_web/managers/WindowNodeCounter.ts diff --git a/frontend/app/player/MessageDistributor/messages/JSONRawMessageReader.ts b/frontend/app/player/_web/messages/JSONRawMessageReader.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/JSONRawMessageReader.ts rename to frontend/app/player/_web/messages/JSONRawMessageReader.ts diff --git a/frontend/app/player/MessageDistributor/messages/MFileReader.ts b/frontend/app/player/_web/messages/MFileReader.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/MFileReader.ts rename to frontend/app/player/_web/messages/MFileReader.ts diff --git a/frontend/app/player/MessageDistributor/messages/MStreamReader.ts b/frontend/app/player/_web/messages/MStreamReader.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/MStreamReader.ts rename to frontend/app/player/_web/messages/MStreamReader.ts diff --git a/frontend/app/player/MessageDistributor/messages/PrimitiveReader.ts b/frontend/app/player/_web/messages/PrimitiveReader.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/PrimitiveReader.ts rename to frontend/app/player/_web/messages/PrimitiveReader.ts diff --git a/frontend/app/player/MessageDistributor/messages/RawMessageReader.ts b/frontend/app/player/_web/messages/RawMessageReader.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/RawMessageReader.ts rename to frontend/app/player/_web/messages/RawMessageReader.ts diff --git a/frontend/app/player/MessageDistributor/messages/index.ts b/frontend/app/player/_web/messages/index.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/index.ts rename to frontend/app/player/_web/messages/index.ts diff --git a/frontend/app/player/MessageDistributor/messages/message.ts b/frontend/app/player/_web/messages/message.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/message.ts rename to frontend/app/player/_web/messages/message.ts diff --git a/frontend/app/player/MessageDistributor/messages/raw.ts b/frontend/app/player/_web/messages/raw.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/raw.ts rename to frontend/app/player/_web/messages/raw.ts diff --git a/frontend/app/player/MessageDistributor/messages/timed.ts b/frontend/app/player/_web/messages/timed.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/timed.ts rename to frontend/app/player/_web/messages/timed.ts diff --git a/frontend/app/player/MessageDistributor/messages/tracker-legacy.ts b/frontend/app/player/_web/messages/tracker-legacy.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/tracker-legacy.ts rename to frontend/app/player/_web/messages/tracker-legacy.ts diff --git a/frontend/app/player/MessageDistributor/messages/tracker.ts b/frontend/app/player/_web/messages/tracker.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/tracker.ts rename to frontend/app/player/_web/messages/tracker.ts diff --git a/frontend/app/player/MessageDistributor/messages/urlResolve.ts b/frontend/app/player/_web/messages/urlResolve.ts similarity index 100% rename from frontend/app/player/MessageDistributor/messages/urlResolve.ts rename to frontend/app/player/_web/messages/urlResolve.ts diff --git a/frontend/app/player/MessageDistributor/network/crypto.ts b/frontend/app/player/_web/network/crypto.ts similarity index 100% rename from frontend/app/player/MessageDistributor/network/crypto.ts rename to frontend/app/player/_web/network/crypto.ts diff --git a/frontend/app/player/MessageDistributor/network/loadFiles.ts b/frontend/app/player/_web/network/loadFiles.ts similarity index 100% rename from frontend/app/player/MessageDistributor/network/loadFiles.ts rename to frontend/app/player/_web/network/loadFiles.ts diff --git a/frontend/app/player/create.ts b/frontend/app/player/create.ts new file mode 100644 index 000000000..c42815eae --- /dev/null +++ b/frontend/app/player/create.ts @@ -0,0 +1,25 @@ +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 { State as PState, INITIAL_STATE as PLAYER_INITIAL_STATE } from './player/Player' + +import WebPlayer from './_web/WebPlayer' + +export function createWebPlayer(session, config): [WebPlayer, Store] { + const store = new SimpleStore({ + ...PLAYER_INITIAL_STATE, + ...MM_INITIAL_STATE, + }) + const player = new WebPlayer(store, session, config, false) + return [player, store] +} + + +export function createLiveWebPlayer(session, config): [WebPlayer, Store] { + const store = new SimpleStore({ + ...PLAYER_INITIAL_STATE, + ...MM_INITIAL_STATE, + }) + const player = new WebPlayer(store, session, config, true) + return [player, store] +} \ No newline at end of file diff --git a/frontend/app/player/index.js b/frontend/app/player/index.js index c49d75294..c40e2f26c 100644 --- a/frontend/app/player/index.js +++ b/frontend/app/player/index.js @@ -1,4 +1,6 @@ -export * from './store'; -export * from './singletone'; -export * from './MessageDistributor/managers/AssistManager'; -export * from './MessageDistributor/managers/LocalStream'; \ No newline at end of file +export * from './_store'; +export * from './_web/assist/AssistManager'; +export * from './_web/assist/LocalStream'; +export * from './_singletone'; + +export * from './create'; \ No newline at end of file diff --git a/frontend/app/player/ios/ImagePlayer.js b/frontend/app/player/ios/ImagePlayer.js deleted file mode 100644 index 1dcf2b9cc..000000000 --- a/frontend/app/player/ios/ImagePlayer.js +++ /dev/null @@ -1,454 +0,0 @@ -import { io } from 'socket.io-client'; -import { makeAutoObservable, autorun } from 'mobx'; -import logger from 'App/logger'; -import { - createPlayerState, - createToolPanelState, - createToggleState, - PLAYING, - PAUSED, - COMPLETED, - SOCKET_ERROR, - - CRASHES, - LOGS, - NETWORK, - PERFORMANCE, - CUSTOM, - EVENTS, // last evemt +clicks -} from "./state"; -import { - createListState, - createScreenListState, -} from './lists'; -import Parser from './Parser'; -import PerformanceList from './PerformanceList'; - -const HIGHEST_SPEED = 3; - - -export default class ImagePlayer { - _screen = null - _wrapper = null - _socket = null - toolPanel = createToolPanelState() - fullscreen = createToggleState() - lists = { - [LOGS]: createListState(), - [NETWORK]: createListState(), - [CRASHES]: createListState(), - [EVENTS]: createListState(), - [CUSTOM]: createListState(), - [PERFORMANCE]: new PerformanceList(), - } - _clicks = createListState() - _screens = createScreenListState() - - constructor(session) { - this.state = createPlayerState({ - endTime: session.duration.valueOf(), - }); - //const canvas = document.createElement("canvas"); - // this._context = canvas.getContext('2d'); - // this._img = new Image(); - // this._img..onerror = function(e){ - // logger.log('Error during loading image:', e); - // }; - // wrapper.appendChild(this._img); - session.crashes.forEach(c => this.lists[CRASHES].append(c)); - session.events.forEach(e => this.lists[EVENTS].append(e)); - session.stackEvents.forEach(e => this.lists[CUSTOM].append(e)); - window.fetch(session.mobsUrl) - .then(r => r.arrayBuffer()) - .then(b => { - new Parser(new Uint8Array(b)).parseEach(m => { - m.time = m.timestamp - session.startedAt; - try { - if (m.tp === "ios_log") { - this.lists[LOGS].append(m); - } else if (m.tp === "ios_network_call") { - this.lists[NETWORK].append(m); - // } else if (m.tp === "ios_custom_event") { - // this.lists[CUSTOM].append(m); - } else if (m.tp === "ios_click_event") { - m.time -= 600; //for graphic initiation - this._clicks.append(m); - } else if (m.tp === "ios_performance_event") { - this.lists[PERFORMANCE].append(m); - } - } catch (e) { - logger.error(e); - } - }); - Object.values(this.lists).forEach(list => list.moveGetLast(0)); // In case of negative values - }) - - if (session.socket == null || typeof session.socket.jwt !== "string" || typeof session.socket.url !== "string") { - logger.error("No socket info found fpr session", session); - return - } - - const options = { - extraHeaders: {Authorization: `Bearer ${session.socket.jwt}`}, - reconnectionAttempts: 5, - //transports: ['websocket'], - } - - const socket = this._socket = io(session.socket.url, options); - socket.on("connect", () => { - logger.log("Socket Connected"); - }); - - socket.on('disconnect', (reason) => { - if (reason === 'io client disconnect') { - return; - } - logger.error("Disconnected. Reason: ", reason) - // if (reason === 'io server disconnect') { - // socket.connect(); - // } - }); - socket.on('connect_error', (e) => { - this.state.setState(SOCKET_ERROR); - logger.error(e) - }); - - socket.on('screen', (time, width, height, binary) => { - //logger.log("New Screen!", time, width, height, binary); - this._screens.insertScreen(time, width, height, binary); - }); - socket.on('buffered', (playTime) => { - if (playTime === this.state.time) { - this.state.setBufferingState(false); - } - logger.log("Play ack!", playTime); - }); - - let startPingInterval; - socket.on('start', () => { - logger.log("Started!"); - clearInterval(startPingInterval) - this.state.setBufferingState(true); - socket.emit("speed", this.state.speed); - this.play(); - }); - startPingInterval = setInterval(() => socket.emit("start"), 1000); - socket.emit("start"); - - window.addEventListener("resize", this.scale); - autorun(this.scale); - } - - _click - _getClickElement() { - if (this._click != null) { - return this._click; - } - const click = document.createElement('div'); - click.style.position = "absolute"; - click.style.background = "#ddd"; - click.style.border = "solid 4px #bbb"; - click.style.borderRadius = "50%"; - click.style.width = "32px"; - click.style.height = "32px"; - click.style.transformOrigin = "center"; - return this._click = click; - } - // More sufficient ways? - _animateClick({ x, y }) { - if (this._screen == null) { - return; - } - const click = this._getClickElement(); - if (click.parentElement == null) { - this._screen.appendChild(click); - } - click.style.transition = "none"; - click.style.left = `${x-18}px`; - click.style.top = `${y-18}px`; - click.style.transform = "scale(1)"; - click.style.opacity = "1"; - setTimeout(() => { - click.style.transition = "all ease-in .5s"; - click.style.transform = "scale(0)"; - click.style.opacity = "0"; - }, 0) - } - - _updateFrame({ image, width, height }) { - // const img = new Image(); - // img.onload = () => { - // this._context.drawImage(img); - // }; - // img.onerror = function(e){ - // logger.log('Error during loading image:', e); - // }; - // this._screen.style.backgroundImage = `url(${binaryToDataURL(binaryArray)})`; - this._canvas.getContext('2d').drawImage(image, 0, 0, this._canvas.width, this._canvas.height); - } - - _setTime(ts) { - ts = Math.max(Math.min(ts, this.state.endTime), 0); - this.state.setTime(ts); - Object.values(this.lists).forEach(list => list.moveGetLast(ts)); - const screen = this._screens.moveGetLast(ts); - if (screen != null) { - const { dataURL, width, height } = screen; - this.state.setSize(width, height); - //imagePromise.then(() => this._updateFrame({ image, width, height })); - //this._screen.style.backgroundImage = `url(${screen.dataURL})`; - screen.loadImage.then(() => this._screen.style.backgroundImage = `url(${screen.dataURL})`); - } - const lastClick = this._clicks.moveGetLast(ts); - if (lastClick != null && lastClick.time > ts - 600) { - this._animateClick(lastClick); - } - } - - attach({ wrapperId, screenId }) { - const screen = document.getElementById(screenId); - if (!screen) { - throw new Error(`ImagePlayer: No screen element found with ID "${screenId}" `); - } - const wrapper = document.getElementById(wrapperId); - if (!wrapper) { - throw new Error(`ImagePlayer: No wrapper element found with ID "${wrapperId}" `); - } - screen.style.backgroundSize = "contain"; - screen.style.backgroundPosition = "center"; - wrapper.style.position = "absolute"; - wrapper.style.transformOrigin = "left top"; - wrapper.style.top = "50%"; - wrapper.style.left = "50%"; - // const canvas = document.createElement('canvas'); - // canvas.style.width = "300px"; - // canvas.style.height = "600px"; - // screen.appendChild(canvas); - // this._canvas = canvas; - this._screen = screen; - this._wrapper = wrapper; - this.scale(); - } - - - get loading() { - return this.state.initializing; - } - - get buffering() { - return this.state.buffering; - } - - // get timeTravelDisabled() { - // return this.state.initializing; - // } - - get controlsDisabled() { - return this.state.initializing; //|| this.state.buffering; - } - - _animationFrameRequestId = null - _stopAnimation() { - cancelAnimationFrame(this._animationFrameRequestId); - } - _startAnimation() { - let prevTime = this.state.time; - let animationPrevTime = performance.now(); - const nextFrame = (animationCurrentTime) => { - const { - speed, - //skip, - //skipIntervals, - endTime, - playing, - buffering, - //live, - //livePlay, - //disconnected, - //messagesLoading, - //cssLoading, - } = this.state; - - const diffTime = !playing || buffering - ? 0 - : Math.max(animationCurrentTime - animationPrevTime, 0) * speed; - - let time = prevTime + diffTime; - - //const skipInterval = skip && skipIntervals.find(si => si.contains(time)); // TODO: good skip by messages - //if (skipInterval) time = skipInterval.end; - - //const fmt = this.getFirstMessageTime(); - //if (time < fmt) time = fmt; // ? - - //const lmt = this.getLastMessageTime(); - //if (livePlay && time < lmt) time = lmt; - // if (endTime < lmt) { - // update({ - // endTime: lmt, - // }); - // } - - prevTime = time; - animationPrevTime = animationCurrentTime; - - const completed = time >= endTime; - if (completed) { - this._setComplete(); - } else { - - // if (live && time > endTime) { - // update({ - // endTime: time, - // }); - // } - this._setTime(time); - this._animationFrameRequestId = requestAnimationFrame(nextFrame); - } - }; - this._animationFrameRequestId = requestAnimationFrame(nextFrame); - } - - - scale = () => { - const { height, width } = this.state; // should be before any return for mobx observing - if (this._wrapper === null) return; - const parent = this._wrapper.parentElement; - if (parent === null) return; - let s = 1; - const { offsetWidth, offsetHeight } = parent; - - s = Math.min(offsetWidth / width, (offsetHeight - 20) / height); - if (s > 1) { - s = 1; - } else { - s = Math.round(s * 1e3) / 1e3; - } - - this._wrapper.style.transform = `scale(${ s }) translate(-50%, -50%)`; - this._wrapper.style.width = width + 'px'; - this._wrapper.style.height = height + 'px'; - // this._canvas.style.width = width + 'px'; - // this._canvas.style.height = height + 'px'; - } - - _setComplete() { - this.state.setStatus(COMPLETED); - this._setTime(this.state.endTime); - if (this._socket != null) { - this._socket.emit("pause"); - } - } - - - _pause() { - this._stopAnimation(); - this.state.setStatus(PAUSED); - } - - pause = () => { - this._pause(); - if (this._socket != null) { - this._socket.emit("pause"); - } - } - - _play() { - if (!this.state.playing) { - this._startAnimation(); - } - this.state.setStatus(PLAYING); - } - play = () => { - this._play() - if (this._socket != null) { - this._socket.emit("resume"); - } - } - - _jump(ts) { - if (this.state.playing) { - this._stopAnimation(); - this._setTime(ts); - this._startAnimation(); - } else { - this._setTime(ts); - this.state.setStatus(PAUSED); // for the case when completed - } - } - jump = (ts) => { - ts = Math.round(ts); // Should be integer - this._jump(ts); - if (this._socket != null) { - this.state.setBufferingState(true); - console.log("Send play on jump!", ts) - this._socket.emit("jump", ts); - } - } - - togglePlay = () => { - if (this.state.playing) { - this.pause() - } else { - if (this.state.completed) { - //this.state.time = 0; - this.jump(0) - } - this.play() - } - } - backTenSeconds = () => { - this.jump(Math.max(this.state.time - 10000, 0)); - } - forthTenSeconds = () => { - this.jump(Math.min(this.state.time + 10000, this.state.endTime)); - } - - _setSpeed(speed) { - if (this._socket != null) { - this._socket.emit("speed", speed); - } - this.state.setSpeed(speed) - } - - toggleSpeed = () => { - const speed = this.state.speed; - this._setSpeed(speed < HIGHEST_SPEED ? speed + 1 : 1); - } - - speedUp = () => { - const speed = this.state.speed; - this._setSpeed(Math.min(HIGHEST_SPEED, speed + 1)); - } - - speedDown = () => { - const speed = this.state.speed; - this._setSpeed(Math.max(1, speed - 1)); - } - - togglePanel = (key) => { - this.toolPanel.toggle(key); - setTimeout(() => this.scale(), 0); - } - - closePanel = () => { - this.toolPanel.close(); - setTimeout(() => this.scale(), 0); - } - - toggleFullscreen = (flag = true) => { - this.fullscreen.toggle(flag); - setTimeout(() => this.scale(), 0); - } - - - clean() { - this._stopAnimation(); - if (this._socket != null) { - //this._socket.emit("close"); - this._socket.close(); - } - this._screens.clean(); - } -} - diff --git a/frontend/app/player/ios/Parser.ts b/frontend/app/player/ios/Parser.ts deleted file mode 100644 index 15b750df6..000000000 --- a/frontend/app/player/ios/Parser.ts +++ /dev/null @@ -1,34 +0,0 @@ -import RawMessageReader from '../MessageDistributor/messages/RawMessageReader'; - - -export default class Parser { - private reader: RawMessageReader - private error: boolean = false - constructor(byteArray) { - this.reader = new RawMessageReader(byteArray) - } - - parseEach(cb) { - while (this.hasNext()) { - const msg = this.next(); - if (msg !== null) { - cb(msg); - } - } - } - - hasNext() { - return !this.error && this.reader.hasNextByte(); - } - - next() { - try { - return this.reader.readMessage() - } catch(e) { - console.warn(e) - this.error = true - return null - } - } - -} \ No newline at end of file diff --git a/frontend/app/player/ios/PerformanceList.js b/frontend/app/player/ios/PerformanceList.js deleted file mode 100644 index 47d7918f3..000000000 --- a/frontend/app/player/ios/PerformanceList.js +++ /dev/null @@ -1,73 +0,0 @@ -import { - createListState, -} from './lists'; - -const MIN_INTERVAL = 500; - - -const NAME_MAP = { - "mainThreadCPU": "cpu", - "batteryLevel": "battery", - "memoryUsage": "memory", -} - -export default class PerformanceList { - _list = createListState() - availability = { - cpu: false, - memory: false, - battery: false, - } - - get list() { - return this._list.list; - } - - get count() { - return this._list.count; - } - - moveGetLast(t) { - this._list.moveGetLast(t); - } - - append(m) { - if (!["mainThreadCPU", "memoryUsage", "batteryLevel", "thermalState", "activeProcessorCount", "isLowPowerModeEnabled"].includes(m.name)) { - return; - } - - let lastPoint = Object.assign({ time: 0, cpu: null, battery: null, memory: null }, this._list.last); - if (this._list.length === 0) { - this._list.append(lastPoint); - } - - if (NAME_MAP[m.name] != null) { - this.availability[ NAME_MAP[m.name] ] = true; - if (lastPoint[NAME_MAP[m.name]] === null) { - this._list.forEach(p => p[NAME_MAP[m.name]] = m.value); - lastPoint[NAME_MAP[m.name]] = m.value; - } - } - - - const newPoint = Object.assign({}, lastPoint, { - time: m.time, - [ NAME_MAP[m.name] || m.name ]: m.value, - }); - - const dif = m.time - lastPoint.time; - const insertCount = Math.floor(dif/MIN_INTERVAL); - for (let i = 0; i < insertCount; i++){ - const evalValue = (key) => lastPoint[key] + Math.floor((newPoint[key]-lastPoint[key])/insertCount*(i + 1)) - this._list.append({ - ...lastPoint, - time: evalValue("time"), - cpu: evalValue("cpu") + (Math.floor(5*Math.random())-2), - battery: evalValue("battery"), - memory: evalValue("memory")*(1 + (0.1*Math.random() - 0.05)), - }); - } - - this._list.append(newPoint); - } -} \ No newline at end of file diff --git a/frontend/app/player/ios/ScreenList.ts b/frontend/app/player/ios/ScreenList.ts deleted file mode 100644 index ef6d1e73f..000000000 --- a/frontend/app/player/ios/ScreenList.ts +++ /dev/null @@ -1,57 +0,0 @@ -import ListWalker from '../MessageDistributor/managers/ListWalker'; - -//URL.revokeObjectURL() !! -function binaryToDataURL(arrayBuffer){ - var blob = new Blob([new Uint8Array(arrayBuffer)], {'type' : 'image/jpeg'}); - return URL.createObjectURL(blob); -} - -function prepareImage(width, height, arrayBuffer) { - const dataURL = binaryToDataURL(arrayBuffer); - return { - loadImage: new Promise(resolve => { - const img = new Image(); - img.onload = function() { - //URL.revokeObjectURL(this.src); - resolve(img); - }; - img.src = dataURL; - }).then(), - dataURL, - }; -} - -export default class ScreenList { - _walker = new ListWalker(); - _insertUnique(m) { - let p = this._walker._list.length; - while (p > 0 && this._walker._list[ p - 1 ].time > m.time) { - p--; - } - if (p > 0 && this._walker._list[ p - 1 ].time === m.time) { - return; - } - this._walker._list.splice(p, 0, m); - } - - moveGetLast(time) { - return this._walker.moveGetLast(time); - } - - insertScreen(time, width, height, arrayBuffer): void { - this._insertUnique({ - time, - width, - height, - ...prepareImage(width, height, arrayBuffer), - //image: new ImageData(new Uint8ClampedArray(arrayBuffer), width, height), - // dataURL: binaryToDataURL(arrayBuffer) - }); - } - - clean() { - this._walker.forEach(m => { - URL.revokeObjectURL(m.dataURL); - }); - } -} \ No newline at end of file diff --git a/frontend/app/player/ios/lists.js b/frontend/app/player/ios/lists.js deleted file mode 100644 index b0ddb9d0d..000000000 --- a/frontend/app/player/ios/lists.js +++ /dev/null @@ -1,12 +0,0 @@ -import { makeAutoObservable } from "mobx" -import ListWalker from '../MessageDistributor/managers/ListWalker'; - -import ScreenList from './ScreenList'; - -export function createListState(list) { - return makeAutoObservable(new ListWalker(list)); -} - -export function createScreenListState() { - return makeAutoObservable(new ScreenList()); -} \ No newline at end of file diff --git a/frontend/app/player/ios/state.js b/frontend/app/player/ios/state.js deleted file mode 100644 index 766ba08fe..000000000 --- a/frontend/app/player/ios/state.js +++ /dev/null @@ -1,112 +0,0 @@ -import { makeAutoObservable } from "mobx" - -//configure ({empceActions: true}) - -export const - NONE = 0, - CRASHES = 1, - NETWORK = 2, - LOGS = 3, - EVENTS = 4, - CUSTOM = 5, - PERFORMANCE = 6; - -export function createToolPanelState() { - return makeAutoObservable({ - key: NONE, - toggle(key) { // auto-bind?? - this.key = this.key === key ? NONE : key; - }, - close() { - this.key = NONE; - }, - }); -} - - -export function createToggleState() { - return makeAutoObservable({ - enabled: false, - toggle(flag) { - this.enabled = typeof flag === 'boolean' - ? flag - : !this.enabled; - }, - enable() { - this.enabled = true; - }, - disable() { - this.enabled = false; - }, - }); -} - -const SPEED_STORAGE_KEY = "__$player-speed$__"; -//const SKIP_STORAGE_KEY = "__$player-skip$__"; -//const initialSkip = !!localStorage.getItem(SKIP_STORAGE_KEY); - -export const - INITIALIZING = 0, - PLAYING = 1, - PAUSED = 2, - COMPLETED = 3, - SOCKET_ERROR = 5; - -export const - PORTRAIT = 1, - LANDSCAPE = 2; - -export function createPlayerState(state) { - const storedSpeed = +localStorage.getItem(SPEED_STORAGE_KEY); - const initialSpeed = [1,2,3].includes(storedSpeed) ? storedSpeed : 1; - - return makeAutoObservable({ - status: INITIALIZING, - _statusSaved: null, - setTime(t) { - this.time = t - }, - time: 0, - endTime: 0, - setStatus(status) { - this.status = status; - }, - get initializing() { - return this.status === INITIALIZING; - }, - get playing() { - return this.status === PLAYING; - }, - get completed() { - return this.status === COMPLETED; - }, - _buffering: false, - get buffering() { - return this._buffering; - }, - setBufferingState(flag = true) { - this._buffering = flag; - }, - speed: initialSpeed, - setSpeed(speed) { - localStorage.setItem(SPEED_STORAGE_KEY, speed); - this.speed = speed; - }, - width: 360, - height: 780, - orientation: PORTRAIT, - get orientationLandscape() { - return this.orientation === LANDSCAPE; - }, - setSize(width, height) { - if (height < 0 || width < 0) { - console.log("Player: wrong non-positive size") - return; - } - this.width = width; - this.height = height; - this.orientation = width > height ? LANDSCAPE : PORTRAIT; - }, - ...state, - }); -} diff --git a/frontend/app/player/lists/ListReader.js b/frontend/app/player/lists/ListReader.js deleted file mode 100644 index 641d3341a..000000000 --- a/frontend/app/player/lists/ListReader.js +++ /dev/null @@ -1,124 +0,0 @@ -export default class ListReader { - _callback; - _p = -1; - _list = []; - _offset = 0; - - constructor(callback = Function.prototype) { - if (typeof callback !== 'function') { - return console.error("List Reader: wrong constructor argument. `callback` must be a function."); - } - this._callback = callback; - } - - static checkItem(item) { - if(typeof item !== 'object' || item === null) { - console.error("List Reader: expected item to be not null object but got ", item); - return false; - } - if (typeof item.time !== 'number') { - console.error("List Reader: expected item to have number property 'time', ", item); - return false; - } - // if (typeof item.index !== 'number') { - // console.error("List Reader: expected item to have number property 'index', ", item); - // return false; - // } // future: All will have index - return true; - } - /* EXTENDABLE METHODS */ - _onIncrement() {} - _onDecrement() {} - _onStartTimeChange() {} - - inc() { - const item = this._list[ ++this._p ]; - this._onIncrement(item); - return item; - } - - dec() { - const item = this._list[ this._p-- ]; - this._onDecrement(item); - return item - } - - get _goToReturn() { - return { listNow: this.listNow }; - } - - goTo(time) { - const prevPointer = this._p; - while (!!this._list[ this._p + 1 ] && this._list[ this._p + 1 ].time <= time) { - this.inc(); - } - while (this._p >= 0 && this._list[ this._p ].time > time) { - this.dec(); - } - if (prevPointer !== this._p) { - //this._notify([ "listNow" ]); - return this._goToReturn; - } - } - - goToIndex(index) { // thinkaboutit - const prevPointer = this._p; - while (!!this._list[ this._p + 1 ] && - this._list[ this._p + 1 ].index <= index - ) { - this.inc(); - } - while (this._p >= 0 && this._list[ this._p ].index > index) { - this.dec(); - } - if (prevPointer !== this._p) { - //this._notify([ "listNow" ]); - return this._goToReturn; - } - } - - // happens rare MBTODO only in class ResourceListReader extends ListReaderWithRed - set startTime(time) { - const prevOffset = this._offset; - const prevPointer = this._p; - this._offset = this._list.findIndex(({ time, duration = 0 }) => time + duration >= time); // TODO: strict for duration rrrrr - this._p = Math.max(this._p, this._offset - 1); - if (prevOffset !== this._offset || prevPointer !== this._p) { - this._notify([ "listNow" ]); - } - this._onStartTimeChange(); - } - - get list() { - return this._list; - } - get count() { - return this._list.length; - } - get listNow() { - return this._list.slice(this._offset, this._p + 1); - } - - set list(_list) { - if (!Array.isArray(_list)) { - console.error("List Reader: wrong list value.", _list) - } - const valid = _list.every(this.constructor.checkItem); - if (!valid) return; - this._list = _list; // future: time + index sort - this._notify([ "list", "count" ]); - } - - append(item) { - if (!this.constructor.checkItem(item)) return; - this._list.push(item); // future: time + index sort - this._notify([ "count" ]); // list is the same by ref, CAREFULL - } - - _notify(propertyList) { - const changedState = {}; - propertyList.forEach(p => changedState[ p ] = this[ p ]); - this._callback(changedState); - } - -} \ No newline at end of file diff --git a/frontend/app/player/lists/ListReaderWithRed.js b/frontend/app/player/lists/ListReaderWithRed.js deleted file mode 100644 index 84da42138..000000000 --- a/frontend/app/player/lists/ListReaderWithRed.js +++ /dev/null @@ -1,48 +0,0 @@ -import ListReader from './ListReader'; - -export default class ListReaderWithRed extends ListReader { - _redCountNow = 0; - - static checkItem(item) { - const superCheckResult = super.checkItem(item); - if (typeof item.isRed !== 'function') { - console.error("List Reader With Red: expected item to have method 'isRed', ", item); - return false; - } - return superCheckResult; - } - - get _goToReturn() { - return { - listNow: this.listNow, - redCountNow: this.redCountNow, - } - } - - _onIncrement(item) { - if (item.isRed()) { - this._redCountNow++; - //this._notify([ "redCountNow" ]); - } - } - - _onDecrement(item) { - if (item.isRed()) { - this._redCountNow--; - //this._notify([ "redCountNow" ]); - } - } - - _onStartTimeChange() { - this._redCountNow = this._list - .slice(this._offset, this._p + 1) - .filter(item => item.isRed()) - .length; - this._notify([ "redCountNow" ]); - } - - get redCountNow() { - return this._redCountNow; - } - -} \ No newline at end of file diff --git a/frontend/app/player/lists/index.js b/frontend/app/player/lists/index.js deleted file mode 100644 index edae90b0c..000000000 --- a/frontend/app/player/lists/index.js +++ /dev/null @@ -1,68 +0,0 @@ -import ListReader from './ListReader'; -import ListReaderWithRed from './ListReaderWithRed'; -import { update as updateStore } from '../store'; - -const l = n => `${ n }List`; -const c = n => `${ n }Count`; -const ln = n => `${ n }ListNow`; -const rcn = n => `${ n }RedCountNow`; - -const entityNamesWithRed = [ "log", "resource", "fetch", "stack" ]; -const entityNamesSimple = [ "event", "profile" ]; -const entityNames = /*[ "redux" ].*/entityNamesWithRed.concat(entityNamesSimple); - -const is = {}; -entityNames.forEach(n => { - is[ l(n) ] = []; - is[ c(n) ] = 0; - is[ ln(n) ] = []; - if (entityNamesWithRed.includes(n)) { - is[ rcn(n) ] = 0; - } -}); -//is["reduxState"] = {}; -//is["reduxFinalStates"] = []; - - -const createCallback = n => { - const entityfy = s => `${ n }${ s[ 0 ].toUpperCase() }${ s.slice(1) }`; - return state => { - if (!state) return; - const namedState = {}; - Object.keys(state).forEach(key => { - namedState[ entityfy(key) ] = state[ key ]; - }); - return updateStore(namedState); - } -} - -let readers = null; - -export function init(lists) { - readers = {}; - entityNamesSimple.forEach(n => readers[ n ] = new ListReader(createCallback(n))); - entityNamesWithRed.forEach(n => readers[ n ] = new ListReaderWithRed(createCallback(n))); - - entityNames.forEach(n => readers[ n ].list = lists[ n ] || []); -} -export function append(name, item) { - readers[ name ].append(item); -} -export function setStartTime(time) { - readers.resource.startTime = time; -} -const byTimeNames = [ "event", "stack" ]; // TEMP -const byIndexNames = entityNames.filter(n => !byTimeNames.includes(n)); -export function goTo(time, index) { - if (readers === null) return; - if (typeof index === 'number') { - byTimeNames.forEach(n => readers[ n ] && readers[ n ]._callback(readers[ n ].goTo(time))); - byIndexNames.forEach(n => readers[ n ] && readers[ n ]._callback(readers[ n ].goToIndex(index))); - } else { - entityNames.forEach(n => readers[ n ] && readers[ n ]._callback(readers[ n ].goTo(time))); - } -} -export function clean() { - entityNames.forEach(n => delete readers[ n ]); -} -export const INITIAL_STATE = is; diff --git a/frontend/app/player/player/Animator.ts b/frontend/app/player/player/Animator.ts new file mode 100644 index 000000000..e8b8b7279 --- /dev/null +++ b/frontend/app/player/player/Animator.ts @@ -0,0 +1,172 @@ +import type { Store, Mover, Interval } from './types'; +import * as localStorage from './localStorage'; + +const fps = 60 +const performance: { now: () => number } = window.performance || { now: Date.now.bind(Date) } +const requestAnimationFrame: typeof window.requestAnimationFrame = + window.requestAnimationFrame || + // @ts-ignore + window.webkitRequestAnimationFrame || + // @ts-ignore + window.mozRequestAnimationFrame || + // @ts-ignore + window.oRequestAnimationFrame || + // @ts-ignore + window.msRequestAnimationFrame || + (callback => window.setTimeout(() => { callback(performance.now()) }, 1000 / fps)) +const cancelAnimationFrame = + window.cancelAnimationFrame || + // @ts-ignore + window.mozCancelAnimationFrame || + window.clearTimeout + + +export interface SetState { + time: number + playing: boolean + completed: boolean + endTime: number + live: boolean + livePlay: boolean +} + +export interface GetState extends SetState { + skip: boolean + speed: number + skipIntervals: Interval[] + lastMessageTime: number + ready: boolean +} + +export const INITIAL_STATE: SetState = { + time: 0, + playing: false, + completed: false, + endTime: 0, + live: false, + livePlay: false, +} as const + + +export default class Animator { + private animationFrameRequestId: number = 0 + + constructor(private state: Store, private mm: Mover) {} + + private setTime(time: number) { + this.state.update({ + time, + completed: false, + }) + this.mm.move(time) + } + + private startAnimation() { + let prevTime = this.state.get().time + let animationPrevTime = performance.now() + + const frameHandler = (animationCurrentTime: number) => { + const { + speed, + skip, + skipIntervals, + endTime, + live, + livePlay, + ready, // = messagesLoading || cssLoading || disconnected + lastMessageTime, // should be updated + } = this.state.get() + + const diffTime = !ready + ? 0 + : Math.max(animationCurrentTime - animationPrevTime, 0) * (live ? 1 : speed) + + let time = prevTime + diffTime + + const skipInterval = skip && skipIntervals.find(si => si.contains(time)) // TODO: good skip by messages + if (skipInterval) time = skipInterval.end + + if (time < 0) { time = 0 } // ? + //const fmt = getFirstMessageTime(); + //if (time < fmt) time = fmt; // ? + + + if (livePlay && time < lastMessageTime) { time = lastMessageTime } + if (endTime < lastMessageTime) { + this.state.update({ + endTime: lastMessageTime, + }) + } + + prevTime = time + animationPrevTime = animationCurrentTime + + const completed = !live && time >= endTime + if (completed) { + this.setTime(endTime) + return this.state.update({ + playing: false, + completed: true, + }) + } + + if (live && time > endTime) { + this.state.update({ + endTime: time, + }) + } + this.setTime(time) + this.animationFrameRequestId = requestAnimationFrame(frameHandler) + } + this.animationFrameRequestId = requestAnimationFrame(frameHandler) + } + + play() { + cancelAnimationFrame(this.animationFrameRequestId) + this.state.update({ playing: true }) + this.startAnimation() + } + + pause() { + cancelAnimationFrame(this.animationFrameRequestId) + this.state.update({ playing: false }) + } + + togglePlay() { + const { playing, completed } = this.state.get() + if (playing) { + this.pause() + } else if (completed) { + this.setTime(0) + this.play() + } else { + this.play() + } + } + + // jump by index? + jump(time: number) { + const { live } = this.state.get() + if (live) return + + if (this.state.get().playing) { + cancelAnimationFrame(this.animationFrameRequestId) + this.setTime(time) + this.startAnimation() + this.state.update({ livePlay: time === this.state.get().endTime }) + } else { + this.setTime(time) + this.state.update({ livePlay: time === this.state.get().endTime }) + } + } + + // TODO: clearify logic of live time-travel + jumpToLive() { + cancelAnimationFrame(this.animationFrameRequestId) + this.setTime(this.state.get().endTime) + this.startAnimation() + this.state.update({ livePlay: true }) + } + + +} \ No newline at end of file diff --git a/frontend/app/player/player/Player.ts b/frontend/app/player/player/Player.ts new file mode 100644 index 000000000..057d3752f --- /dev/null +++ b/frontend/app/player/player/Player.ts @@ -0,0 +1,125 @@ +import * as typedLocalStorage from './localStorage'; + +import type { Mover, Cleaner, Store } from './types'; +import Animator from './Animator'; +import { INITIAL_STATE as ANIMATOR_INITIAL_STATE } from './Animator'; +import type { GetState as AnimatorGetState, SetState as AnimatorSetState } from './Animator'; + + +/* == separate this == */ +const HIGHEST_SPEED = 16 +const SPEED_STORAGE_KEY = "__$player-speed$__" +const SKIP_STORAGE_KEY = "__$player-skip$__" +const SKIP_TO_ISSUE_STORAGE_KEY = "__$session-skipToIssue$__" +const AUTOPLAY_STORAGE_KEY = "__$player-autoplay$__" +const SHOW_EVENTS_STORAGE_KEY = "__$player-show-events$__" +const storedSpeed: number = typedLocalStorage.number(SPEED_STORAGE_KEY) +const initialSpeed = [1, 2, 4, 8, 16].includes(storedSpeed) ? storedSpeed : 1 +const initialSkip = typedLocalStorage.boolean(SKIP_STORAGE_KEY) +const initialSkipToIssue = typedLocalStorage.boolean(SKIP_TO_ISSUE_STORAGE_KEY) +const initialAutoplay = typedLocalStorage.boolean(AUTOPLAY_STORAGE_KEY) +const initialShowEvents = typedLocalStorage.boolean(SHOW_EVENTS_STORAGE_KEY) +export const INITIAL_STATE = { + ...ANIMATOR_INITIAL_STATE, + + skipToIssue: initialSkipToIssue, + showEvents: initialShowEvents, + + autoplay: initialAutoplay, + skip: initialSkip, + speed: initialSpeed, +} +export type State = typeof INITIAL_STATE & AnimatorGetState +/* == */ + +export default class Player extends Animator { + constructor(private pState: Store, private manager: Mover & Cleaner) { + super(pState, manager) + + // Autoplay + if (pState.get().autoplay) { + let autoPlay = true; + document.addEventListener("visibilitychange", () => { + if (document.hidden) { + const { playing } = pState.get(); + autoPlay = playing + if (playing) { + this.pause(); + } + } else if (autoPlay) { + this.play(); + } + }) + + if (!document.hidden) { + this.play(); + } + } + } + + + + /* === TODO: incapsulate in LSCache === */ + + toggleAutoplay() { + const autoplay = !this.pState.get().autoplay + localStorage.setItem(AUTOPLAY_STORAGE_KEY, `${autoplay}`); + this.pState.update({ autoplay }) + } + + // Shouldn't it be in the react part as a fully (with localStorage-cache react hook)? + toggleEvents() { + const showEvents = !this.pState.get().showEvents + localStorage.setItem(SHOW_EVENTS_STORAGE_KEY, `${showEvents}`); + this.pState.update({ showEvents }) + } + + // move to React part? + toggleSkipToIssue() { + const skipToIssue = !this.pState.get().skipToIssue + localStorage.setItem(SKIP_TO_ISSUE_STORAGE_KEY, `${skipToIssue}`); + this.pState.update({ skipToIssue }) + } + + toggleSkip() { + const skip = !this.pState.get().skip + localStorage.setItem(SKIP_STORAGE_KEY, `${skip}`); + this.pState.update({ skip }) + } + private updateSpeed(speed: number) { + localStorage.setItem(SPEED_STORAGE_KEY, `${speed}`); + this.pState.update({ speed }) + } + + toggleSpeed() { + const { speed } = this.pState.get() + this.updateSpeed(speed < HIGHEST_SPEED ? speed * 2 : 1) + } + + speedUp() { + const { speed } = this.pState.get() + this.updateSpeed(Math.min(HIGHEST_SPEED, speed * 2)) + } + + speedDown() { + const { speed } = this.pState.get() + this.updateSpeed(Math.max(1, speed / 2)) + } + /* === === */ + + // TODO: move theese to React hooks + // injectNotes(notes: Note[]) { + // update({ notes }) + // } + // filterOutNote(noteId: number) { + // const { notes } = getState() + // update({ notes: notes.filter((note: Note) => note.noteId !== noteId) }) + // } + + + clean() { + this.pause() + this.manager.clean() + } + +} \ No newline at end of file diff --git a/frontend/app/player/player/_LSCache.ts b/frontend/app/player/player/_LSCache.ts new file mode 100644 index 000000000..beb48af46 --- /dev/null +++ b/frontend/app/player/player/_LSCache.ts @@ -0,0 +1,63 @@ +import * as lstore from './localStorage' + +import type { SimpleState } from './PlayerState' + +const SPEED_STORAGE_KEY = "__$player-speed$__"; +const SKIP_STORAGE_KEY = "__$player-skip$__"; +const SKIP_TO_ISSUE_STORAGE_KEY = "__$session-skipToIssue$__"; +const AUTOPLAY_STORAGE_KEY = "__$player-autoplay$__"; +const SHOW_EVENTS_STORAGE_KEY = "__$player-show-events$__"; + + +const storedSpeed = lstore.number(SPEED_STORAGE_KEY, 1) +const initialSpeed = [1,2,4,8,16].includes(storedSpeed) ? storedSpeed : 1; +const initialSkip = lstore.boolean(SKIP_STORAGE_KEY) +const initialSkipToIssue = lstore.boolean(SKIP_TO_ISSUE_STORAGE_KEY) +const initialAutoplay = lstore.boolean(AUTOPLAY_STORAGE_KEY) +const initialShowEvents = lstore.boolean(SHOW_EVENTS_STORAGE_KEY) + +export const INITIAL_STATE = { + skipToIssue: initialSkipToIssue, + autoplay: initialAutoplay, + showEvents: initialShowEvents, + skip: initialSkip, + speed: initialSpeed, +} + +const KEY_MAP = { + speed: SPEED_STORAGE_KEY, + skip: SKIP_STORAGE_KEY, + skipToIssue: SKIP_TO_ISSUE_STORAGE_KEY, + autoplay: AUTOPLAY_STORAGE_KEY, + showEvents: SHOW_EVENTS_STORAGE_KEY, +} + +type KeysOfBoolean = keyof T & keyof { [ K in keyof T as T[K] extends boolean ? K : never ] : K }; + +type Entries = { + [K in keyof T]: [K, T[K]]; +}[keyof T][]; + +export default class LSCache { + constructor(private state: SimpleState, private keyMap: Record, string>) { + } + update(newState: Partial) { + for (let [k, v] of Object.entries(newState) as Entries>) { + if (k in this.keyMap) { + // @ts-ignore TODO: nice typing + //lstore[typeof v](this.keyMap[k], v) + localStorage.setItem(this.keyMap[k], String(v)) + } + } + this.state.update(newState) + } + toggle(key: KeysOfBoolean) { + // @ts-ignore TODO: nice typing + this.update({ + [key]: !this.get()[key] + }) + } + get() { + return this.state.get() + } +} \ No newline at end of file diff --git a/frontend/app/player/player/localStorage.ts b/frontend/app/player/player/localStorage.ts new file mode 100644 index 000000000..03bba1d77 --- /dev/null +++ b/frontend/app/player/player/localStorage.ts @@ -0,0 +1,19 @@ + +export function number(key: string, dflt = 0): number { + const stVal = localStorage.getItem(key) + if (stVal === null) { + return dflt + } + const val = parseInt(stVal) + if (isNaN(val)) { + return dflt + } + return val +} + +export function boolean(key: string, dflt = false): boolean { + return localStorage.getItem(key) === "true" +} +export function string(key: string, dflt = ''): string { + return localStorage.getItem(key) || '' +} \ No newline at end of file diff --git a/frontend/app/player/player/types.ts b/frontend/app/player/player/types.ts new file mode 100644 index 000000000..2ad5032ed --- /dev/null +++ b/frontend/app/player/player/types.ts @@ -0,0 +1,21 @@ + +export interface Mover { + move(time: number): void +} + +export interface Cleaner { + clean(): void +} + +export interface Interval { + contains(t: number): boolean + start: number + end: number +} + +export interface Store { + get(): G + update(state: Partial): void +} + + From 291fe0d4094a819afe1db06a0c7cea6b69ed691e Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 15 Nov 2022 22:00:49 +0100 Subject: [PATCH 02/64] fix(frontend/player): export WebPlayer type --- frontend/app/player/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/app/player/index.js b/frontend/app/player/index.js index c40e2f26c..67d56e19c 100644 --- a/frontend/app/player/index.js +++ b/frontend/app/player/index.js @@ -1,6 +1,7 @@ -export * from './_store'; export * from './_web/assist/AssistManager'; export * from './_web/assist/LocalStream'; -export * from './_singletone'; +export * from './_web/WebPlayer'; +export * from './create'; -export * from './create'; \ No newline at end of file +export * from './_store'; +export * from './_singletone'; From 094721684a65f26581404d8f0a480e9ec5f4e9df Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 22 Nov 2022 11:33:27 +0100 Subject: [PATCH 03/64] refactor(frontend/player):ListWithMarks: add markedCount; import types fix --- frontend/app/player/_common/ListWalker.ts | 8 ++++---- .../app/player/_common/ListWalkerWithMarks.ts | 17 ++++++++++++++--- frontend/app/player/_common/SimpleStore.ts | 4 ++-- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend/app/player/_common/ListWalker.ts b/frontend/app/player/_common/ListWalker.ts index 92f7585c8..3600f31da 100644 --- a/frontend/app/player/_common/ListWalker.ts +++ b/frontend/app/player/_common/ListWalker.ts @@ -1,4 +1,4 @@ -import type { Timed } from './messages/timed'; +import type { Timed } from '../_web/messages/timed'; export default class ListWalker { private p = 0 @@ -80,18 +80,18 @@ export default class ListWalker { } private hasNext() { - return this.p < this.length + return this.p < this.length } private hasPrev() { return this.p > 0 } protected moveNext(): T | null { - return this.hasNext() + return this.hasNext() ? this.list[ this.p++ ] : null } protected movePrev(): T | null { - return this.hasPrev() + return this.hasPrev() ? this.list[ --this.p ] : null } diff --git a/frontend/app/player/_common/ListWalkerWithMarks.ts b/frontend/app/player/_common/ListWalkerWithMarks.ts index b2a8f5d3d..10446d7e0 100644 --- a/frontend/app/player/_common/ListWalkerWithMarks.ts +++ b/frontend/app/player/_common/ListWalkerWithMarks.ts @@ -1,4 +1,4 @@ -import type { Timed } from './messages/timed'; +import type { Timed } from '../_web/messages/timed'; import ListWalker from './ListWalker' @@ -7,9 +7,17 @@ type CheckFn = (t: T) => boolean export default class ListWalkerWithMarks extends ListWalker { private _markCountNow: number = 0 - constructor(private isMarked: CheckFn, initialList?: T[]) { + private _markCount: number = 0 + constructor(private isMarked: CheckFn, initialList: T[] = []) { super(initialList) + this._markCount = initialList.reduce((n, item) => isMarked(item) ? n+1 : n, 0) } + + append(item: T) { + if (this.isMarked(item)) { this._markCount++ } + super.append(item) + } + protected moveNext() { const val = super.moveNext() if (val && this.isMarked(val)) { @@ -24,8 +32,11 @@ export default class ListWalkerWithMarks extends ListWalker } return val } - get markCountNow(): number { + get markedCountNow(): number { return this._markCountNow } + get markedCount(): number { + return this._markCount + } } \ No newline at end of file diff --git a/frontend/app/player/_common/SimpleStore.ts b/frontend/app/player/_common/SimpleStore.ts index 9e81a79cd..2b4a77658 100644 --- a/frontend/app/player/_common/SimpleStore.ts +++ b/frontend/app/player/_common/SimpleStore.ts @@ -1,8 +1,8 @@ -import { State } from './types' +import { Store } from '../player/types' // (not a type) -export default class SimpleSore implements State { +export default class SimpleSore implements Store { constructor(private state: G){} get(): G { return this.state From c5209efd879cb5ac3cbc03d4ce59f8d855252c92 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 22 Nov 2022 12:06:31 +0100 Subject: [PATCH 04/64] refactor(frontend/player):inspector responsibility segregation; renamings, types --- frontend/app/player/_singletone.ts | 12 +- frontend/app/player/_store/duck.js | 4 +- .../app/player/_web/InspectorController.ts | 57 ++++ frontend/app/player/_web/Lists.ts | 2 +- frontend/app/player/_web/MessageManager.ts | 14 +- frontend/app/player/_web/Screen/BaseScreen.ts | 214 -------------- frontend/app/player/_web/Screen/Cursor.ts | 29 +- .../Screen/{Inspector.js => Inspector.ts} | 42 ++- frontend/app/player/_web/Screen/Marker.ts | 25 +- frontend/app/player/_web/Screen/Screen.ts | 278 +++++++++++++----- frontend/app/player/_web/Screen/index.js | 2 - frontend/app/player/_web/Screen/types.ts | 9 +- frontend/app/player/_web/WebLivePlayer.ts | 23 ++ frontend/app/player/_web/WebPlayer.ts | 61 ++-- .../app/player/_web/assist/AssistManager.ts | 9 +- .../player/_web/managers/ActivityManager.ts | 2 +- .../player/_web/managers/MouseMoveManager.ts | 39 ++- .../player/_web/managers/ReduxStateManager.ts | 53 ---- frontend/app/player/create.ts | 24 +- frontend/app/player/player/Animator.ts | 69 +++-- frontend/app/player/player/Player.ts | 26 +- frontend/app/player/player/types.ts | 4 +- 22 files changed, 476 insertions(+), 522 deletions(-) create mode 100644 frontend/app/player/_web/InspectorController.ts delete mode 100644 frontend/app/player/_web/Screen/BaseScreen.ts rename frontend/app/player/_web/Screen/{Inspector.js => Inspector.ts} (55%) delete mode 100644 frontend/app/player/_web/Screen/index.js create mode 100644 frontend/app/player/_web/WebLivePlayer.ts delete mode 100644 frontend/app/player/_web/managers/ReduxStateManager.ts diff --git a/frontend/app/player/_singletone.ts b/frontend/app/player/_singletone.ts index 71f29a7ee..c4e0d607b 100644 --- a/frontend/app/player/_singletone.ts +++ b/frontend/app/player/_singletone.ts @@ -1,15 +1,9 @@ import WebPlayer from './_web/WebPlayer'; import reduxStore, {update, cleanStore} from './_store'; -import { State as MMState, INITIAL_STATE as MM_INITIAL_STATE } from './_web/MessageManager' -import { State as PState, INITIAL_STATE as PLAYER_INITIAL_STATE } from './player/Player' -import { Store } from './player/types' - - -const INIT_STATE = { - ...MM_INITIAL_STATE, - ...PLAYER_INITIAL_STATE, -} +import type { State as MMState } from './_web/MessageManager' +import type { State as PState } from './player/Player' +import type { Store } from './player/types' const myStore: Store = { diff --git a/frontend/app/player/_store/duck.js b/frontend/app/player/_store/duck.js index bcede3b32..6cf861ade 100644 --- a/frontend/app/player/_store/duck.js +++ b/frontend/app/player/_store/duck.js @@ -1,7 +1,7 @@ import { applyChange, revertChange } from 'deep-diff'; import { INITIAL_STATE as MM_INITIAL_STATE } from '../_web/MessageManager' -import { INITIAL_STATE as PLAYER_INITIAL_STATE } from '../player/Player' +import Player from '../player/Player' const UPDATE = 'player/UPDATE'; const CLEAN = 'player/CLEAN'; @@ -9,7 +9,7 @@ const REDUX = 'player/REDUX'; const resetState = { ...MM_INITIAL_STATE, - ...PLAYER_INITIAL_STATE, + ...Player.INITIAL_STATE, initialized: false, }; diff --git a/frontend/app/player/_web/InspectorController.ts b/frontend/app/player/_web/InspectorController.ts new file mode 100644 index 000000000..a78bad006 --- /dev/null +++ b/frontend/app/player/_web/InspectorController.ts @@ -0,0 +1,57 @@ +import Marker from './Screen/Marker' +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 + marker: Marker | null = null + constructor(private screen: Screen) {} + + scale(dims: Dimensions) { + if (this.substitutor) { + this.substitutor.scale(dims) + } + } + + enableInspector(clickCallback: (e: { target: Element }) => void): Document | null { + if (!this.screen.parentElement) 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.display(false) + + const docElement = this.screen.document?.documentElement // this.substitutor.document?.importNode( + const doc = this.substitutor.document + if (doc && docElement) { + doc.open() + doc.write(docElement.outerHTML) + doc.close() + + // TODO! : copy stylesheets & cssRules? + } + this.screen.display(false); + this.inspector.enable(clickCallback); + this.substitutor.display(true); + return doc; + } + + disableInspector() { + if (this.substitutor) { + const doc = this.substitutor.document; + if (doc) { + doc.documentElement.innerHTML = ""; + } + this.inspector.clean(); + this.substitutor.display(false); + } + this.screen.display(true); + } + +} \ No newline at end of file diff --git a/frontend/app/player/_web/Lists.ts b/frontend/app/player/_web/Lists.ts index 4f9c236cd..4eb5de6db 100644 --- a/frontend/app/player/_web/Lists.ts +++ b/frontend/app/player/_web/Lists.ts @@ -9,7 +9,7 @@ const MARKED_LIST_NAMES = [ "log", "resource", "fetch", "stack" ] as const; const LIST_NAMES = [...SIMPLE_LIST_NAMES, ...MARKED_LIST_NAMES ]; -// TODO: provide correct types +// TODO: provide correct types; maybe use list object itself inside the store export const INITIAL_STATE = LIST_NAMES.reduce((state, name) => { state[`${name}List`] = [] diff --git a/frontend/app/player/_web/MessageManager.ts b/frontend/app/player/_web/MessageManager.ts index e6dc9467c..14642c3dc 100644 --- a/frontend/app/player/_web/MessageManager.ts +++ b/frontend/app/player/_web/MessageManager.ts @@ -93,7 +93,6 @@ const visualChanges = [ ] export default class MessageManager extends Screen { - // TODO: consistent with the other data-lists private locationEventManager: ListWalker/**/ = new ListWalker(); private locationManager: ListWalker = new ListWalker(); private loadedLocationManager: ListWalker = new ListWalker(); @@ -198,7 +197,7 @@ export default class MessageManager extends Screen { private waitingForFiles: boolean = false private onFileReadSuccess = () => { - const stateToUpdate = { + const stateToUpdate : Partial= { performanceChartData: this.performanceTrackManager.chartData, performanceAvaliability: this.performanceTrackManager.avaliability, ...this.lists.getFullListsState() @@ -349,7 +348,7 @@ export default class MessageManager extends Screen { stateToUpdate.performanceChartTime = lastPerformanceTrackMessage.time; } - this.lists.moveGetState(t) + Object.assign(stateToUpdate, this.lists.moveGetState(t)) Object.keys(stateToUpdate).length > 0 && this.state.update(stateToUpdate); /* Sequence of the managers is important here */ @@ -535,15 +534,6 @@ export default class MessageManager extends Screen { } } - getLastMessageTime(): number { - return this.lastMessageTime; - } - - getFirstMessageTime(): number { - return this.pagesManager.minTime; - } - - setMessagesLoading(messagesLoading: boolean) { this.display(!messagesLoading); this.state.update({ messagesLoading }); diff --git a/frontend/app/player/_web/Screen/BaseScreen.ts b/frontend/app/player/_web/Screen/BaseScreen.ts deleted file mode 100644 index 67f527e01..000000000 --- a/frontend/app/player/_web/Screen/BaseScreen.ts +++ /dev/null @@ -1,214 +0,0 @@ -import styles from './screen.module.css'; - -import type { Point } from './types'; - - -export interface State { - width: number; - height: number; -} - -export const INITIAL_STATE: State = { - width: 0, - height: 0, -} - - -function getElementsFromInternalPoint(doc: Document, { x, y }: Point): Element[] { - // @ts-ignore (IE, Edge) - if (typeof doc.msElementsFromRect === 'function') { - // @ts-ignore - return Array.prototype.slice.call(doc.msElementsFromRect(x,y)) || [] - } - - if (typeof doc.elementsFromPoint === 'function') { - return doc.elementsFromPoint(x, y) - } - const el = doc.elementFromPoint(x, y) - return el ? [ el ] : [] -} - -function getElementsFromInternalPointDeep(doc: Document, point: Point): Element[] { - const elements = getElementsFromInternalPoint(doc, point) - // is it performant though?? - for (let i = 0; i < elements.length; i++) { - const el = elements[i] - if (isIframe(el)){ - const iDoc = el.contentDocument - if (iDoc) { - const iPoint: Point = { - x: point.x - el.clientLeft, - y: point.y - el.clientTop, - } - elements.push(...getElementsFromInternalPointDeep(iDoc, iPoint)) - } - } - } - return elements -} - -function isIframe(el: Element): el is HTMLIFrameElement { - return el.tagName === "IFRAME" -} - -export default abstract class BaseScreen { - public readonly overlay: HTMLDivElement; - - private readonly iframe: HTMLIFrameElement; - protected readonly screen: HTMLDivElement; - protected readonly controlButton: HTMLDivElement; - protected parentElement: HTMLElement | null = null; - - constructor() { - const iframe = document.createElement('iframe'); - iframe.className = styles.iframe; - this.iframe = iframe; - - const overlay = document.createElement('div'); - overlay.className = styles.overlay; - this.overlay = overlay; - - const screen = document.createElement('div'); - - screen.className = styles.screen; - screen.appendChild(iframe); - screen.appendChild(overlay); - this.screen = screen; - } - - attach(parentElement: HTMLElement) { - if (this.parentElement) { - this.parentElement = undefined - console.error("BaseScreen: Trying to attach an attached screen."); - } - - parentElement.appendChild(this.screen); - - this.parentElement = parentElement; - - /* == For the Inspecting Document content == */ - this.overlay.addEventListener('contextmenu', () => { - this.overlay.style.display = 'none' - const doc = this.document - if (!doc) { return } - const returnOverlay = () => { - this.overlay.style.display = 'block' - doc.removeEventListener('mousemove', returnOverlay) - doc.removeEventListener('mouseclick', returnOverlay) // TODO: prevent default in case of input selection - } - doc.addEventListener('mousemove', returnOverlay) - doc.addEventListener('mouseclick', returnOverlay) - }) - } - - toggleRemoteControlStatus(isEnabled: boolean ) { - const styles = isEnabled ? { border: '2px dashed blue' } : { border: 'unset'} - return Object.assign(this.screen.style, styles) - } - - get window(): WindowProxy | null { - return this.iframe.contentWindow; - } - - get document(): Document | null { - return this.iframe.contentDocument; - } - - private boundingRect: DOMRect | null = null; - private getBoundingClientRect(): DOMRect { - if (this.boundingRect === null) { - return this.boundingRect = this.overlay.getBoundingClientRect() // expensive operation? - } - return this.boundingRect - } - - getInternalViewportCoordinates({ x, y }: Point): Point { - const { x: overlayX, y: overlayY, width } = this.getBoundingClientRect(); - - const screenWidth = this.overlay.offsetWidth; - - const scale = screenWidth / width; - const screenX = (x - overlayX) * scale; - const screenY = (y - overlayY) * scale; - - return { x: Math.round(screenX), y: Math.round(screenY) }; - } - - getCurrentScroll(): Point { - const docEl = this.document?.documentElement - const x = docEl ? docEl.scrollLeft : 0 - const y = docEl ? docEl.scrollTop : 0 - return { x, y } - } - - getInternalCoordinates(p: Point): Point { - const { x, y } = this.getInternalViewportCoordinates(p); - - const sc = this.getCurrentScroll() - - return { x: x+sc.x, y: y+sc.y }; - } - - getElementFromInternalPoint({ x, y }: Point): Element | null { - // elementFromPoint && elementFromPoints require viewpoint-related coordinates, - // not document-related - return this.document?.elementFromPoint(x, y) || null; - } - - getElementsFromInternalPoint(point: Point): Element[] { - const doc = this.document - if (!doc) { return [] } - return getElementsFromInternalPointDeep(doc, point) - } - - getElementFromPoint(point: Point): Element | null { - return this.getElementFromInternalPoint(this.getInternalViewportCoordinates(point)); - } - - getElementsFromPoint(point: Point): Element[] { - return this.getElementsFromInternalPoint(this.getInternalViewportCoordinates(point)); - } - - getElementBySelector(selector: string): Element | null { - if (!selector) return null; - try { - const safeSelector = selector.replace(/:/g, '\\\\3A ').replace(/\//g, '\\/'); - return this.document?.querySelector(safeSelector) || null; - } catch (e) { - console.error("Can not select element. ", e) - return null - } - } - - display(flag: boolean = true) { - 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({ height, width }: { height: number, width: number }) { - if (!this.parentElement) return; - const { offsetWidth, offsetHeight } = this.parentElement; - - this.s = Math.min(offsetWidth / width, offsetHeight / height); - if (this.s > 1) { - this.s = 1; - } else { - this.s = Math.round(this.s * 1e3) / 1e3; - } - 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'; - - this.boundingRect = this.overlay.getBoundingClientRect(); - } -} diff --git a/frontend/app/player/_web/Screen/Cursor.ts b/frontend/app/player/_web/Screen/Cursor.ts index 54ea414fd..4d8094b4e 100644 --- a/frontend/app/player/_web/Screen/Cursor.ts +++ b/frontend/app/player/_web/Screen/Cursor.ts @@ -4,8 +4,7 @@ import styles from './cursor.module.css'; export default class Cursor { private readonly cursor: HTMLDivElement; - private nameElement: HTMLDivElement; - private readonly position: Point = { x: -1, y: -1 } + private tagElement: HTMLDivElement; constructor(overlay: HTMLDivElement) { this.cursor = document.createElement('div'); this.cursor.className = styles.cursor; @@ -20,10 +19,10 @@ export default class Cursor { } } - toggleUserName(name?: string) { - if (!this.nameElement) { - this.nameElement = document.createElement('div') - Object.assign(this.nameElement.style, { + showTag(tag?: string) { + if (!this.tagElement) { + this.tagElement = document.createElement('div') + Object.assign(this.tagElement.style, { position: 'absolute', padding: '4px 6px', borderRadius: '8px', @@ -34,21 +33,19 @@ export default class Cursor { fontSize: '12px', whiteSpace: 'nowrap', }) - this.cursor.appendChild(this.nameElement) + this.cursor.appendChild(this.tagElement) } - if (!name) { - this.nameElement.style.display = 'none' + if (!tag) { + this.tagElement.style.display = 'none' } else { - this.nameElement.style.display = 'block' - const nameStr = name ? name.length > 10 ? name.slice(0, 9) + '...' : name : 'User' - this.nameElement.innerHTML = `${nameStr}` + this.tagElement.style.display = 'block' + const nameStr = tag.length > 10 ? tag.slice(0, 9) + '...' : tag + this.tagElement.innerHTML = `${nameStr}` } } move({ x, y }: Point) { - this.position.x = x; - this.position.y = y; this.cursor.style.left = x + 'px'; this.cursor.style.top = y + 'px'; } @@ -64,8 +61,4 @@ export default class Cursor { // transition // setTransitionSpeed() - getPosition(): Point { - return { x: this.position.x, y: this.position.y }; - } - } diff --git a/frontend/app/player/_web/Screen/Inspector.js b/frontend/app/player/_web/Screen/Inspector.ts similarity index 55% rename from frontend/app/player/_web/Screen/Inspector.js rename to frontend/app/player/_web/Screen/Inspector.ts index 98ba5ec0f..b0ea1ca9c 100644 --- a/frontend/app/player/_web/Screen/Inspector.js +++ b/frontend/app/player/_web/Screen/Inspector.ts @@ -1,15 +1,14 @@ +import type Screen from './Screen' +import type Marker from './Marker' + //import { select } from 'optimal-select'; -export default class Inspector { - //private callbacks; - captureCallbacks = []; - bubblingCallbacks = []; - constructor(screen, marker) { - this.screen = screen; - this.marker = marker; - } +export default class Inspector { + // private captureCallbacks = []; + // private bubblingCallbacks = []; + constructor(private screen: Screen, private marker: Marker) {} - _onMouseMove = (e) => { + private onMouseMove = (e: MouseEvent) => { // const { overlay } = this.screen; // if (!overlay.contains(e.target)) { // return; @@ -25,11 +24,11 @@ export default class Inspector { this.marker.mark(target); } - _onOverlayLeave = () => { + private onOverlayLeave = () => { return this.marker.unmark(); } - _onMarkClick = () => { + private onMarkClick = () => { let target = this.marker.target; if (!target) { return @@ -57,16 +56,15 @@ export default class Inspector { // } // } - toggle(flag, clickCallback) { - this.clickCallback = clickCallback; - if (flag) { - this.screen.overlay.addEventListener('mousemove', this._onMouseMove); - this.screen.overlay.addEventListener('mouseleave', this._onOverlayLeave); - this.screen.overlay.addEventListener('click', this._onMarkClick); - } else { - this.screen.overlay.removeEventListener('mousemove', this._onMouseMove); - this.screen.overlay.removeEventListener('mouseleave', this._onOverlayLeave); - this.screen.overlay.removeEventListener('click', this._onMarkClick); - } + private clickCallback: (e: { target: Element }) => void | null = null + enable(clickCallback: Inspector['clickCallback']) { + this.screen.overlay.addEventListener('mousemove', this.onMouseMove) + this.screen.overlay.addEventListener('mouseleave', this.onOverlayLeave) + this.screen.overlay.addEventListener('click', this.onMarkClick) + } + clean() { + this.screen.overlay.removeEventListener('mousemove', this.onMouseMove) + this.screen.overlay.removeEventListener('mouseleave', this.onOverlayLeave) + this.screen.overlay.removeEventListener('click', this.onMarkClick) } } \ No newline at end of file diff --git a/frontend/app/player/_web/Screen/Marker.ts b/frontend/app/player/_web/Screen/Marker.ts index 4d3fab9b1..331995782 100644 --- a/frontend/app/player/_web/Screen/Marker.ts +++ b/frontend/app/player/_web/Screen/Marker.ts @@ -1,4 +1,4 @@ -import type BaseScreen from './BaseScreen' +import type Screen from './Screen' import styles from './marker.module.css'; function escapeRegExp(string: string) { @@ -19,7 +19,7 @@ export default class Marker { private tooltip: HTMLDivElement private marker: HTMLDivElement - constructor(overlay: HTMLElement, private readonly screen: BaseScreen) { + constructor(overlay: HTMLElement, private readonly screen: Screen) { this.tooltip = document.createElement('div'); this.tooltip.className = styles.tooltip; this.tooltip.appendChild(document.createElement('div')); @@ -74,13 +74,14 @@ export default class Marker { if (fitTargets.length === 0) { this._target = null; } else { - this._target = fitTargets[0]; - const cursorTarget = this.screen.getCursorTarget(); - fitTargets.forEach((target) => { - if (target.contains(cursorTarget)) { - this._target = target; - } - }); + // TODO: fix getCursorTarget()? + // this._target = fitTargets[0]; + // const cursorTarget = this.screen.getCursorTarget(); + // fitTargets.forEach((target) => { + // if (target.contains(cursorTarget)) { + // this._target = target; + // } + // }); } } catch (e) { console.info(e); @@ -96,9 +97,9 @@ export default class Marker { this.redraw(); } - getTagString(tag) { - const attrs = tag.attributes; - let str = `${tag.tagName.toLowerCase()}`; + private getTagString(el: Element) { + const attrs = el.attributes; + let str = `${el.tagName.toLowerCase()}`; for (let i = 0; i < attrs.length; i++) { let k = attrs[i]; diff --git a/frontend/app/player/_web/Screen/Screen.ts b/frontend/app/player/_web/Screen/Screen.ts index 59986423e..6d28f06fd 100644 --- a/frontend/app/player/_web/Screen/Screen.ts +++ b/frontend/app/player/_web/Screen/Screen.ts @@ -1,83 +1,215 @@ -import Marker from './Marker'; -import Cursor from './Cursor'; -import Inspector from './Inspector'; -// import styles from './screen.module.css'; -import BaseScreen from './BaseScreen'; +import styles from './screen.module.css' +import Cursor from './Cursor' -export { INITIAL_STATE } from './BaseScreen'; -export type { State } from './BaseScreen'; +import type { Point, Dimensions } from './types'; -export default class Screen extends BaseScreen { - public readonly cursor: Cursor; - private substitutor: BaseScreen | null = null; - private inspector: Inspector | null = null; - public marker: Marker | null = null; - constructor() { - super(); - this.cursor = new Cursor(this.overlay); + +export type State = Dimensions + +export const INITIAL_STATE: State = { + width: 0, + height: 0, +} + + +function getElementsFromInternalPoint(doc: Document, { x, y }: Point): Element[] { + // @ts-ignore (IE, Edge) + if (typeof doc.msElementsFromRect === 'function') { + // @ts-ignore + return Array.prototype.slice.call(doc.msElementsFromRect(x,y)) || [] } - getCursorTarget() { - return this.getElementFromInternalPoint(this.cursor.getPosition()); + if (typeof doc.elementsFromPoint === 'function') { + return doc.elementsFromPoint(x, y) } + const el = doc.elementFromPoint(x, y) + return el ? [ el ] : [] +} - getCursorTargets() { - return this.getElementsFromInternalPoint(this.cursor.getPosition()); - } - - scale(dims: { height: number, width: number }) { - super.scale(dims) - if (this.substitutor) { - this.substitutor.scale(dims) - } - } - - enableInspector(clickCallback: (e: { target: Element }) => void): Document | null { - if (!this.parentElement) 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.parentElement); - } - - this.substitutor.display(false); - - const docElement = this.document?.documentElement; // this.substitutor.document?.importNode( - const doc = this.substitutor.document; - if (doc && docElement) { - // doc.documentElement.innerHTML = ""; - // // Better way? - // for (let i = 1; i < docElement.attributes.length; i++) { - // const att = docElement.attributes[i]; - // doc.documentElement.setAttribute(att.name, att.value); - // } - // for (let i = 1; i < docElement.childNodes.length; i++) { - // doc.documentElement.appendChild(docElement.childNodes[i].cloneNode(true)); - // } - doc.open(); - doc.write(docElement.outerHTML); // Context will be iframe, so instanceof Element won't work - doc.close(); - - // TODO! : copy stylesheets, check with styles - } - this.display(false); - this.inspector.toggle(true, clickCallback); - this.substitutor.display(true); - return doc; - } - - disableInspector() { - if (this.substitutor) { - const doc = this.substitutor.document; - if (doc) { - doc.documentElement.innerHTML = ""; +function getElementsFromInternalPointDeep(doc: Document, point: Point): Element[] { + const elements = getElementsFromInternalPoint(doc, point) + // is it performant though?? + for (let i = 0; i < elements.length; i++) { + const el = elements[i] + if (isIframe(el)){ + const iDoc = el.contentDocument + if (iDoc) { + const iPoint: Point = { + x: point.x - el.clientLeft, + y: point.y - el.clientTop, + } + elements.push(...getElementsFromInternalPointDeep(iDoc, iPoint)) } - this.inspector.toggle(false); - this.substitutor.display(false); } - this.display(true); + } + return elements +} + +function isIframe(el: Element): el is HTMLIFrameElement { + return el.tagName === "IFRAME" +} + +export default class Screen { + readonly overlay: HTMLDivElement + readonly cursor: Cursor + + private readonly iframe: HTMLIFrameElement; + protected readonly screen: HTMLDivElement; + protected readonly controlButton: HTMLDivElement; + protected parentElement: HTMLElement | null = null; + + constructor() { + const iframe = document.createElement('iframe'); + iframe.className = styles.iframe; + this.iframe = iframe; + + const overlay = document.createElement('div'); + overlay.className = styles.overlay; + this.overlay = overlay; + + const screen = document.createElement('div'); + + screen.className = styles.screen; + screen.appendChild(iframe); + screen.appendChild(overlay); + this.screen = screen; + + this.cursor = new Cursor(this.overlay) // TODO: move outside } -} \ No newline at end of file + attach(parentElement: HTMLElement) { + if (this.parentElement) { + this.parentElement = undefined + console.error("BaseScreen: Trying to attach an attached screen."); + } + + parentElement.appendChild(this.screen); + + this.parentElement = parentElement; + + /* == For the Inspecting Document content == */ + this.overlay.addEventListener('contextmenu', () => { + this.overlay.style.display = 'none' + const doc = this.document + if (!doc) { return } + const returnOverlay = () => { + this.overlay.style.display = 'block' + doc.removeEventListener('mousemove', returnOverlay) + doc.removeEventListener('mouseclick', returnOverlay) // TODO: prevent default in case of input selection + } + doc.addEventListener('mousemove', returnOverlay) + doc.addEventListener('mouseclick', returnOverlay) + }) + } + + toggleBorder(isEnabled: boolean ) { + const styles = isEnabled ? { border: '2px dashed blue' } : { border: 'unset'} + return Object.assign(this.screen.style, styles) + } + + get window(): WindowProxy | null { + return this.iframe.contentWindow; + } + + get document(): Document | null { + return this.iframe.contentDocument; + } + + private boundingRect: DOMRect | null = null; + private getBoundingClientRect(): DOMRect { + if (this.boundingRect === null) { + return this.boundingRect = this.overlay.getBoundingClientRect() // expensive operation? + } + return this.boundingRect + } + + getInternalViewportCoordinates({ x, y }: Point): Point { + const { x: overlayX, y: overlayY, width } = this.getBoundingClientRect(); + + const screenWidth = this.overlay.offsetWidth; + + const scale = screenWidth / width; + const screenX = (x - overlayX) * scale; + const screenY = (y - overlayY) * scale; + + return { x: Math.round(screenX), y: Math.round(screenY) }; + } + + getCurrentScroll(): Point { + const docEl = this.document?.documentElement + const x = docEl ? docEl.scrollLeft : 0 + const y = docEl ? docEl.scrollTop : 0 + return { x, y } + } + + getInternalCoordinates(p: Point): Point { + const { x, y } = this.getInternalViewportCoordinates(p); + + const sc = this.getCurrentScroll() + + return { x: x+sc.x, y: y+sc.y }; + } + + getElementFromInternalPoint({ x, y }: Point): Element | null { + // elementFromPoint && elementFromPoints require viewpoint-related coordinates, + // not document-related + return this.document?.elementFromPoint(x, y) || null; + } + + getElementsFromInternalPoint(point: Point): Element[] { + const doc = this.document + if (!doc) { return [] } + return getElementsFromInternalPointDeep(doc, point) + } + + getElementFromPoint(point: Point): Element | null { + return this.getElementFromInternalPoint(this.getInternalViewportCoordinates(point)); + } + + getElementsFromPoint(point: Point): Element[] { + return this.getElementsFromInternalPoint(this.getInternalViewportCoordinates(point)); + } + + getElementBySelector(selector: string): Element | null { + if (!selector) return null; + try { + const safeSelector = selector.replace(/:/g, '\\\\3A ').replace(/\//g, '\\/'); + return this.document?.querySelector(safeSelector) || null; + } catch (e) { + console.error("Can not select element. ", e) + return null + } + } + + display(flag: boolean = true) { + 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({ height, width }: Dimensions) { + if (!this.parentElement) return; + const { offsetWidth, offsetHeight } = this.parentElement; + + this.s = Math.min(offsetWidth / width, offsetHeight / height); + if (this.s > 1) { + this.s = 1; + } else { + this.s = Math.round(this.s * 1e3) / 1e3; + } + 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'; + + this.boundingRect = this.overlay.getBoundingClientRect(); + } +} diff --git a/frontend/app/player/_web/Screen/index.js b/frontend/app/player/_web/Screen/index.js deleted file mode 100644 index 96f315c68..000000000 --- a/frontend/app/player/_web/Screen/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './Screen'; -export * from './Screen'; diff --git a/frontend/app/player/_web/Screen/types.ts b/frontend/app/player/_web/Screen/types.ts index 0fdac4d8b..2c464fdfc 100644 --- a/frontend/app/player/_web/Screen/types.ts +++ b/frontend/app/player/_web/Screen/types.ts @@ -1,4 +1,9 @@ export interface Point { - x: number; - y: number; + x: number + y: number +} + +export interface Dimensions { + width: number + height: number } \ No newline at end of file diff --git a/frontend/app/player/_web/WebLivePlayer.ts b/frontend/app/player/_web/WebLivePlayer.ts new file mode 100644 index 000000000..d7b707b1c --- /dev/null +++ b/frontend/app/player/_web/WebLivePlayer.ts @@ -0,0 +1,23 @@ +// import WebPlayer from './WebPlayer' +// import AssistManager from './assist/AssistManager' + + +// export default class WebLivePlayer extends WebPlayer { +// assistManager: AssistManager // public so far +// constructor(private wpState: Store, session, config: RTCIceServer[]) { +// super(wpState) +// this.assistManager = new AssistManager(session, this.messageManager, config, wpState) +// const endTime = !live && session.duration.valueOf() +// wpState.update({ +// //@ts-ignore +// initialized: true, +// //@ts-ignore +// session, + +// live: true, +// livePlay: true, +// }) + +// this.assistManager.connect(session.agentToken) +// } +// } \ No newline at end of file diff --git a/frontend/app/player/_web/WebPlayer.ts b/frontend/app/player/_web/WebPlayer.ts index a48577288..de3dcfa49 100644 --- a/frontend/app/player/_web/WebPlayer.ts +++ b/frontend/app/player/_web/WebPlayer.ts @@ -2,19 +2,19 @@ import type { Store } from '../player/types' import Player, { State as PlayerState } from '../player/Player' import MessageManager from './MessageManager' +import InspectorController from './InspectorController' import AssistManager from './assist/AssistManager' import Screen from './Screen/Screen' -import { State as MMState, INITIAL_STATE as MM_INITIAL_STATE } from './MessageManager' - - +import type { State as MMState } from './MessageManager' export default class WebPlayer extends Player { private readonly screen: Screen - private readonly messageManager: MessageManager + private readonly inspectorController: InspectorController + protected readonly messageManager: MessageManager assistManager: AssistManager // public so far - constructor(private wpState: Store, session, config, live: boolean) { + constructor(private wpState: Store, session, config: RTCIceServer[], live: boolean) { // TODO: separate screen from manager const screen = new MessageManager(session, wpState, config, live) super(wpState, screen) @@ -24,6 +24,8 @@ export default class WebPlayer extends Player { // TODO: separate LiveWebPlayer this.assistManager = new AssistManager(session, this.messageManager, config, wpState) + this.inspectorController = new InspectorController(screen) + const endTime = !live && session.duration.valueOf() wpState.update({ @@ -50,9 +52,29 @@ export default class WebPlayer extends Player { scale = () => { const { width, height } = this.wpState.get() this.screen.scale({ width, height }) + this.inspectorController.scale({ width, height }) + + // this.updateMarketTargets() ?? } + + // Inspector & marker mark(e: Element) { - this.screen.marker.mark(e) + this.inspectorController.marker?.mark(e) + } + toggleInspectorMode(flag: boolean, clickCallback) { + if (typeof flag !== 'boolean') { + const { inspectorMode } = this.wpState.get() + flag = !inspectorMode; + } + + if (flag) { + this.pause() + this.wpState.update({ inspectorMode: true }) + return this.inspectorController.enableInspector(clickCallback); + } else { + this.inspectorController.disableInspector(); + this.wpState.update({ inspectorMode: false }); + } } updateMarketTargets() { @@ -109,7 +131,7 @@ export default class WebPlayer extends Player { } // private actualScroll: Point | null = null - setMarkedTargets(selections: { selector: string, count: number }[] | null) { + private setMarkedTargets(selections: { selector: string, count: number }[] | null) { // if (selections) { // const totalCount = selections.reduce((a, b) => { // return a + b.count @@ -132,7 +154,7 @@ export default class WebPlayer extends Player { // update({ markedTargets }); // } else { // if (this.actualScroll) { - // this.window?.scrollTo(this.actualScroll.x, this.actualScroll.y) + // this.screen.window?.scrollTo(this.actualScroll.x, this.actualScroll.y) // this.actualScroll = null // } // update({ markedTargets: null }); @@ -140,26 +162,11 @@ export default class WebPlayer extends Player { } markTargets(targets: { selector: string, count: number }[] | null) { - // this.animator.pause(); - // this.setMarkedTargets(targets); - } - - toggleInspectorMode(flag, clickCallback) { - // if (typeof flag !== 'boolean') { - // const { inspectorMode } = getState(); - // flag = !inspectorMode; - // } - - // if (flag) { - // this.pause() - // update({ inspectorMode: true }); - // return super.enableInspector(clickCallback); - // } else { - // super.disableInspector(); - // update({ inspectorMode: false }); - // } + // this.pause(); + // this.setMarkedTargets(targets); } + // TODO async toggleTimetravel() { if (!this.wpState.get().liveTimeTravel) { return await this.messageManager.reloadWithUnprocessedFile() @@ -167,7 +174,7 @@ export default class WebPlayer extends Player { } toggleUserName(name?: string) { - this.screen.cursor.toggleUserName(name) + this.screen.cursor.showTag(name) } clean() { super.clean() diff --git a/frontend/app/player/_web/assist/AssistManager.ts b/frontend/app/player/_web/assist/AssistManager.ts index 1e1274226..1c9984b51 100644 --- a/frontend/app/player/_web/assist/AssistManager.ts +++ b/frontend/app/player/_web/assist/AssistManager.ts @@ -78,7 +78,7 @@ export default class AssistManager { constructor( private session: any, private md: MessageManager, - private config: any, + private config: RTCIceServer[], private store: Store, ) {} @@ -302,13 +302,13 @@ export default class AssistManager { this.md.overlay.addEventListener("mousemove", this.onMouseMove) this.md.overlay.addEventListener("click", this.onMouseClick) this.md.overlay.addEventListener("wheel", this.onWheel) - this.md.toggleRemoteControlStatus(true) + this.md.toggleBorder(true) this.store.update({ remoteControl: RemoteControlStatus.Enabled }) } else { this.md.overlay.removeEventListener("mousemove", this.onMouseMove) this.md.overlay.removeEventListener("click", this.onMouseClick) this.md.overlay.removeEventListener("wheel", this.onWheel) - this.md.toggleRemoteControlStatus(false) + this.md.toggleBorder(false) this.store.update({ remoteControl: RemoteControlStatus.Disabled }) this.toggleAnnotation(false) } @@ -354,7 +354,7 @@ export default class AssistManager { const urlObject = new URL(window.env.API_EDP || window.location.origin) return import('peerjs').then(({ default: Peer }) => { if (this.cleaned) {return Promise.reject("Already cleaned")} - const peerOpts: any = { + const peerOpts: Peer.PeerJSOption = { host: urlObject.hostname, path: '/assist', port: urlObject.port === "" ? (location.protocol === 'https:' ? 443 : 80 ): parseInt(urlObject.port), @@ -362,6 +362,7 @@ export default class AssistManager { if (this.config) { peerOpts['config'] = { iceServers: this.config, + //@ts-ignore sdpSemantics: 'unified-plan', iceTransportPolicy: 'relay', }; diff --git a/frontend/app/player/_web/managers/ActivityManager.ts b/frontend/app/player/_web/managers/ActivityManager.ts index 32265f924..d5e81f62d 100644 --- a/frontend/app/player/_web/managers/ActivityManager.ts +++ b/frontend/app/player/_web/managers/ActivityManager.ts @@ -7,7 +7,7 @@ class SkipIntervalCls { get time(): number { return this.start; } - contains(ts) { + contains(ts: number) { return ts > this.start && ts < this.end; } } diff --git a/frontend/app/player/_web/managers/MouseMoveManager.ts b/frontend/app/player/_web/managers/MouseMoveManager.ts index 5dd3160a5..63b5ca895 100644 --- a/frontend/app/player/_web/managers/MouseMoveManager.ts +++ b/frontend/app/player/_web/managers/MouseMoveManager.ts @@ -1,41 +1,50 @@ -import type Screen from '../Screen/Screen'; -import type { MouseMove } from '../messages'; +import type Screen from '../Screen/Screen' +import type { Point } from '../Screen/types' +import type { MouseMove } from '../messages' -import ListWalker from '../../_common/ListWalker'; +import ListWalker from '../../_common/ListWalker' const HOVER_CLASS = "-openreplay-hover"; const HOVER_CLASS_DEPR = "-asayer-hover"; export default class MouseMoveManager extends ListWalker { - private hoverElements: Array = []; + private hoverElements: Array = [] constructor(private screen: Screen) {super()} + // private getCursorTarget() { + // return this.screen.getElementFromInternalPoint(this.current) + // } + + private getCursorTargets() { + return this.screen.getElementsFromInternalPoint(this.current) + } + private updateHover(): void { - const curHoverElements = this.screen.getCursorTargets(); - const diffAdd = curHoverElements.filter(elem => !this.hoverElements.includes(elem)); - const diffRemove = this.hoverElements.filter(elem => !curHoverElements.includes(elem)); - this.hoverElements = curHoverElements; + const curHoverElements = this.getCursorTargets() + const diffAdd = curHoverElements.filter(elem => !this.hoverElements.includes(elem)) + const diffRemove = this.hoverElements.filter(elem => !curHoverElements.includes(elem)) + this.hoverElements = curHoverElements diffAdd.forEach(elem => { elem.classList.add(HOVER_CLASS) elem.classList.add(HOVER_CLASS_DEPR) - }); + }) diffRemove.forEach(elem => { elem.classList.remove(HOVER_CLASS) elem.classList.remove(HOVER_CLASS_DEPR) - }); + }) } reset(): void { - this.hoverElements = []; + this.hoverElements.length = 0 } move(t: number) { - const lastMouseMove = this.moveGetLast(t); - if (!!lastMouseMove){ - this.screen.cursor.move(lastMouseMove); + const lastMouseMove = this.moveGetLast(t) + if (!!lastMouseMove) { + this.screen.cursor.move(lastMouseMove) //window.getComputedStyle(this.screen.getCursorTarget()).cursor === 'pointer' // might nfluence performance though - this.updateHover(); + this.updateHover() } } } diff --git a/frontend/app/player/_web/managers/ReduxStateManager.ts b/frontend/app/player/_web/managers/ReduxStateManager.ts deleted file mode 100644 index 4756b303a..000000000 --- a/frontend/app/player/_web/managers/ReduxStateManager.ts +++ /dev/null @@ -1,53 +0,0 @@ -// import { applyChange, revertChange } from 'deep-diff'; -// import ListWalker from '../../_common/ListWalker'; -// import type { Redux } from '../messages'; - -// export default class ReduxStateManager extends ListWalker { -// private state: Object = {} -// private finalStates: Object[] = [] - -// moveWasUpdated(time, index) { -// super.moveApply( -// time, -// this.onIncrement, -// this.onDecrement, -// ) -// } - -// onIncrement = (item) => { -// this.processRedux(item, true); -// } - -// onDecrement = (item) => { -// this.processRedux(item, false); -// } - -// private processRedux(action, forward) { -// if (forward) { -// if (!!action.state) { -// this.finalStates.push(this.state); -// this.state = JSON.parse(JSON.stringify(action.state)); // Deep clone :( -// } else { -// action.diff.forEach(d => { -// try { -// applyChange(this.state, d); -// } catch (e) { -// //console.warn("Deepdiff error") -// } -// }); -// } -// } else { -// if (!!action.state) { -// this.state = this.finalStates.pop(); -// } else { -// action.diff.forEach(d => { -// try { -// revertChange(this.state, 1, d); // bad lib :( TODO: write our own diff -// } catch (e) { -// //console.warn("Deepdiff error") -// } -// }); -// } -// } -// } -// } diff --git a/frontend/app/player/create.ts b/frontend/app/player/create.ts index c42815eae..4a928e20f 100644 --- a/frontend/app/player/create.ts +++ b/frontend/app/player/create.ts @@ -1,25 +1,33 @@ 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 { State as PState, INITIAL_STATE as PLAYER_INITIAL_STATE } from './player/Player' +import Player, { State as PState } from './player/Player' import WebPlayer from './_web/WebPlayer' -export function createWebPlayer(session, config): [WebPlayer, Store] { - const store = new SimpleStore({ - ...PLAYER_INITIAL_STATE, +type WebPlayerStore = Store + +export function createWebPlayer(session, wrapStore?: (s:WebPlayerStore) => WebPlayerStore): [WebPlayer, WebPlayerStore] { + let store: WebPlayerStore = new SimpleStore({ + ...Player.INITIAL_STATE, ...MM_INITIAL_STATE, }) - const player = new WebPlayer(store, session, config, false) + if (wrapStore) { + store = wrapStore(store) + } + const player = new WebPlayer(store, session, null, false) return [player, store] } -export function createLiveWebPlayer(session, config): [WebPlayer, Store] { - const store = new SimpleStore({ - ...PLAYER_INITIAL_STATE, +export function createLiveWebPlayer(session, config: RTCIceServer[], wrapStore?: (s:WebPlayerStore) => WebPlayerStore): [WebPlayer, WebPlayerStore] { + let store: WebPlayerStore = new SimpleStore({ + ...Player.INITIAL_STATE, ...MM_INITIAL_STATE, }) + if (wrapStore) { + store = wrapStore(store) + } const player = new WebPlayer(store, session, config, true) return [player, store] } \ No newline at end of file diff --git a/frontend/app/player/player/Animator.ts b/frontend/app/player/player/Animator.ts index e8b8b7279..871da629a 100644 --- a/frontend/app/player/player/Animator.ts +++ b/frontend/app/player/player/Animator.ts @@ -1,4 +1,4 @@ -import type { Store, Mover, Interval } from './types'; +import type { Store, Moveable, Interval } from './types'; import * as localStorage from './localStorage'; const fps = 60 @@ -25,7 +25,6 @@ export interface SetState { time: number playing: boolean completed: boolean - endTime: number live: boolean livePlay: boolean } @@ -34,27 +33,27 @@ export interface GetState extends SetState { skip: boolean speed: number skipIntervals: Interval[] - lastMessageTime: number + endTime: number ready: boolean + + lastMessageTime: number } -export const INITIAL_STATE: SetState = { - time: 0, - playing: false, - completed: false, - endTime: 0, - live: false, - livePlay: false, -} as const - - export default class Animator { + static INITIAL_STATE: SetState = { + time: 0, + playing: false, + completed: false, + live: false, + livePlay: false, + } as const + private animationFrameRequestId: number = 0 - constructor(private state: Store, private mm: Mover) {} + constructor(private store: Store, private mm: Moveable) {} private setTime(time: number) { - this.state.update({ + this.store.update({ time, completed: false, }) @@ -62,7 +61,7 @@ export default class Animator { } private startAnimation() { - let prevTime = this.state.get().time + let prevTime = this.store.get().time let animationPrevTime = performance.now() const frameHandler = (animationCurrentTime: number) => { @@ -74,8 +73,9 @@ export default class Animator { live, livePlay, ready, // = messagesLoading || cssLoading || disconnected - lastMessageTime, // should be updated - } = this.state.get() + + lastMessageTime, + } = this.store.get() const diffTime = !ready ? 0 @@ -83,20 +83,22 @@ export default class Animator { let time = prevTime + diffTime - const skipInterval = skip && skipIntervals.find(si => si.contains(time)) // TODO: good skip by messages + const skipInterval = skip && skipIntervals.find(si => si.contains(time)) if (skipInterval) time = skipInterval.end if (time < 0) { time = 0 } // ? //const fmt = getFirstMessageTime(); //if (time < fmt) time = fmt; // ? - + // if (livePlay && time < endTime) { time = endTime } + // === live only if (livePlay && time < lastMessageTime) { time = lastMessageTime } if (endTime < lastMessageTime) { - this.state.update({ + this.store.update({ endTime: lastMessageTime, }) } + // === prevTime = time animationPrevTime = animationCurrentTime @@ -104,17 +106,20 @@ export default class Animator { const completed = !live && time >= endTime if (completed) { this.setTime(endTime) - return this.state.update({ + return this.store.update({ playing: false, completed: true, }) } + // === live only if (live && time > endTime) { - this.state.update({ + this.store.update({ endTime: time, }) } + // === + this.setTime(time) this.animationFrameRequestId = requestAnimationFrame(frameHandler) } @@ -123,17 +128,17 @@ export default class Animator { play() { cancelAnimationFrame(this.animationFrameRequestId) - this.state.update({ playing: true }) + this.store.update({ playing: true }) this.startAnimation() } pause() { cancelAnimationFrame(this.animationFrameRequestId) - this.state.update({ playing: false }) + this.store.update({ playing: false }) } togglePlay() { - const { playing, completed } = this.state.get() + const { playing, completed } = this.store.get() if (playing) { this.pause() } else if (completed) { @@ -146,26 +151,26 @@ export default class Animator { // jump by index? jump(time: number) { - const { live } = this.state.get() + const { live } = this.store.get() if (live) return - if (this.state.get().playing) { + if (this.store.get().playing) { cancelAnimationFrame(this.animationFrameRequestId) this.setTime(time) this.startAnimation() - this.state.update({ livePlay: time === this.state.get().endTime }) + this.store.update({ livePlay: time === this.store.get().endTime }) } else { this.setTime(time) - this.state.update({ livePlay: time === this.state.get().endTime }) + this.store.update({ livePlay: time === this.store.get().endTime }) } } // TODO: clearify logic of live time-travel jumpToLive() { cancelAnimationFrame(this.animationFrameRequestId) - this.setTime(this.state.get().endTime) + this.setTime(this.store.get().endTime) this.startAnimation() - this.state.update({ livePlay: true }) + this.store.update({ livePlay: true }) } diff --git a/frontend/app/player/player/Player.ts b/frontend/app/player/player/Player.ts index 057d3752f..9b2b98225 100644 --- a/frontend/app/player/player/Player.ts +++ b/frontend/app/player/player/Player.ts @@ -1,8 +1,7 @@ import * as typedLocalStorage from './localStorage'; -import type { Mover, Cleaner, Store } from './types'; +import type { Moveable, Cleanable, Store } from './types'; import Animator from './Animator'; -import { INITIAL_STATE as ANIMATOR_INITIAL_STATE } from './Animator'; import type { GetState as AnimatorGetState, SetState as AnimatorSetState } from './Animator'; @@ -19,21 +18,22 @@ const initialSkip = typedLocalStorage.boolean(SKIP_STORAGE_KEY) const initialSkipToIssue = typedLocalStorage.boolean(SKIP_TO_ISSUE_STORAGE_KEY) const initialAutoplay = typedLocalStorage.boolean(AUTOPLAY_STORAGE_KEY) const initialShowEvents = typedLocalStorage.boolean(SHOW_EVENTS_STORAGE_KEY) -export const INITIAL_STATE = { - ...ANIMATOR_INITIAL_STATE, - skipToIssue: initialSkipToIssue, - showEvents: initialShowEvents, - - autoplay: initialAutoplay, - skip: initialSkip, - speed: initialSpeed, -} -export type State = typeof INITIAL_STATE & AnimatorGetState +export type State = typeof Player.INITIAL_STATE /* == */ export default class Player extends Animator { - constructor(private pState: Store, private manager: Mover & Cleaner) { + static INITIAL_STATE = { + ...Animator.INITIAL_STATE, + skipToIssue: initialSkipToIssue, + showEvents: initialShowEvents, + + autoplay: initialAutoplay, + skip: initialSkip, + speed: initialSpeed, + } as const + + constructor(private pState: Store, private manager: Moveable & Cleanable) { super(pState, manager) // Autoplay diff --git a/frontend/app/player/player/types.ts b/frontend/app/player/player/types.ts index 2ad5032ed..5deb56b06 100644 --- a/frontend/app/player/player/types.ts +++ b/frontend/app/player/player/types.ts @@ -1,9 +1,9 @@ -export interface Mover { +export interface Moveable { move(time: number): void } -export interface Cleaner { +export interface Cleanable { clean(): void } From 1621f73c6959c3697c58687174beb5d647e24567 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 22 Nov 2022 14:23:30 +0100 Subject: [PATCH 05/64] refactor(ui/player): create player context for webplayer components --- .../app/components/Session/PlayerContent.js | 70 +++++++++ frontend/app/components/Session/WebPlayer.js | 139 ------------------ frontend/app/components/Session/WebPlayer.tsx | 127 ++++++++++++++++ .../app/components/Session/playerContext.ts | 12 ++ .../components/Session_/Console/Console.js | 18 --- .../Session_/Console/ConsoleContent.js | 123 ---------------- .../Console/ConsoleRow/ConsoleRow.tsx | 48 ------ .../Session_/Console/ConsoleRow/index.ts | 1 - .../Session_/Console/console.module.css | 38 ----- .../components/Session_/Inspector/index.js | 6 +- .../Session_/LongTasks/LongTasks.js | 8 +- .../app/components/Session_/Player/Player.js | 110 ++++++-------- .../components/Session_/Profiler/Profiler.js | 4 +- frontend/app/player/create.ts | 8 +- 14 files changed, 271 insertions(+), 441 deletions(-) create mode 100644 frontend/app/components/Session/PlayerContent.js delete mode 100644 frontend/app/components/Session/WebPlayer.js create mode 100644 frontend/app/components/Session/WebPlayer.tsx create mode 100644 frontend/app/components/Session/playerContext.ts delete mode 100644 frontend/app/components/Session_/Console/Console.js delete mode 100644 frontend/app/components/Session_/Console/ConsoleContent.js delete mode 100644 frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx delete mode 100644 frontend/app/components/Session_/Console/ConsoleRow/index.ts delete mode 100644 frontend/app/components/Session_/Console/console.module.css diff --git a/frontend/app/components/Session/PlayerContent.js b/frontend/app/components/Session/PlayerContent.js new file mode 100644 index 000000000..8983ca5fc --- /dev/null +++ b/frontend/app/components/Session/PlayerContent.js @@ -0,0 +1,70 @@ +import React from 'react'; +import { + connectPlayer, +} from 'Player'; +import PlayerBlock from '../Session_/PlayerBlock'; +import styles from '../Session_/session.module.css'; +import { countDaysFrom } from 'App/date'; +import cn from 'classnames'; +import RightBlock from './RightBlock'; + +function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, hasError }) { + const sessionDays = countDaysFrom(session.startedAt); + return ( +
+ {hasError ? ( +
+
+
+ {sessionDays > 2 ? 'Session not found.' : 'This session is still being processed.'} +
+
+ {sessionDays > 2 + ? 'Please check your data retention policy.' + : 'Please check it again in a few minutes.'} +
+
+
+ ) : ( +
+
+
+ +
+
+ {activeTab !== '' && ( + + )} +
+ )} +
+ ); +} + +function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) { + return ( + !live && + !fullscreen && + ); +} + +export default connectPlayer((state) => ({ + showEvents: !state.showEvents, + hasError: state.error, +}))(PlayerContent); diff --git a/frontend/app/components/Session/WebPlayer.js b/frontend/app/components/Session/WebPlayer.js deleted file mode 100644 index 6e71d3ea7..000000000 --- a/frontend/app/components/Session/WebPlayer.js +++ /dev/null @@ -1,139 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { connect } from 'react-redux'; -import { Loader, Modal } from 'UI'; -import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; -import { fetchList } from 'Duck/integrations'; -import { PlayerProvider, injectNotes, connectPlayer, init as initPlayer, clean as cleanPlayer, Controls } from 'Player'; -import cn from 'classnames'; -import RightBlock from './RightBlock'; -import withLocationHandlers from 'HOCs/withLocationHandlers'; -import { useStore } from 'App/mstore' -import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; -import PlayerBlock from '../Session_/PlayerBlock'; -import styles from '../Session_/session.module.css'; -import { countDaysFrom } from 'App/date'; -import ReadNote from '../Session_/Player/Controls/components/ReadNote'; -import { fetchList as fetchMembers } from 'Duck/member'; - -const TABS = { - EVENTS: 'User Steps', - HEATMAPS: 'Click Map', -}; - -const InitLoader = connectPlayer((state) => ({ - loading: !state.initialized, -}))(Loader); - -const PlayerContentConnected = connectPlayer((state) => ({ - showEvents: !state.showEvents, - hasError: state.error, -}))(PlayerContent); - -function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, hasError }) { - const sessionDays = countDaysFrom(session.startedAt); - return ( -
- {hasError ? ( -
-
-
{sessionDays > 2 ? 'Session not found.' : 'This session is still being processed.'}
-
{sessionDays > 2 ? 'Please check your data retention policy.' : 'Please check it again in a few minutes.'}
-
-
- ) : ( -
-
-
- -
-
- {activeTab !== '' && } -
- )} -
- ); -} - -function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) { - return !live && !fullscreen && ; -} - -function WebPlayer(props) { - const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, fetchList } = props; - const { notesStore } = useStore() - const [activeTab, setActiveTab] = useState(''); - const [showNoteModal, setShowNote] = useState(false) - const [noteItem, setNoteItem] = useState(null) - - useEffect(() => { - fetchList('issues'); - initPlayer(session, jwt); - props.fetchMembers() - - notesStore.fetchSessionNotes(session.sessionId).then(r => { - injectNotes(r) - const note = props.query.get('note'); - if (note) { - Controls.pause() - setNoteItem(notesStore.getNoteById(parseInt(note, 10), r)) - setShowNote(true) - } - }) - - const jumptTime = props.query.get('jumpto'); - if (jumptTime) { - Controls.jump(parseInt(jumptTime)); - } - - return () => cleanPlayer(); - }, [session.sessionId]); - - // LAYOUT (TODO: local layout state - useContext or something..) - useEffect( - () => () => { - toggleFullscreen(false); - closeBottomBlock(); - }, - [] - ); - - const onNoteClose = () => {setShowNote(false); Controls.togglePlay()} - return ( - - - - - - {showNoteModal ? ( - m.id === noteItem?.userId)?.email || ''} - note={noteItem} - onClose={onNoteClose} - notFound={!noteItem} - /> - ) : null} - - - - ); -} - -export default connect( - (state) => ({ - session: state.getIn(['sessions', 'current']), - jwt: state.get('jwt'), - fullscreen: state.getIn(['components', 'player', 'fullscreen']), - showEvents: state.get('showEvents'), - members: state.getIn(['members', 'list']), - }), - { - toggleFullscreen, - closeBottomBlock, - fetchList, - fetchMembers, - } -)(withLocationHandlers()(WebPlayer)); diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx new file mode 100644 index 000000000..811d6b9ba --- /dev/null +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -0,0 +1,127 @@ +import React, { useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { Modal } from 'UI'; +import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; +import { fetchList } from 'Duck/integrations'; +import { + PlayerProvider, + createWebPlayer, +} from 'Player'; + +import withLocationHandlers from 'HOCs/withLocationHandlers'; +import { useStore } from 'App/mstore'; +import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; +import ReadNote from '../Session_/Player/Controls/components/ReadNote'; +import { fetchList as fetchMembers } from 'Duck/member'; +import PlayerContent from './PlayerContent' +import { + IPlayerContext, + PlayerContext, + defaultContextValue +} from './playerContext' + +const TABS = { + EVENTS: 'User Steps', + HEATMAPS: 'Click Map', +}; + +function WebPlayer(props: any) { + const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, fetchList } = props; + const { notesStore } = useStore(); + const [activeTab, setActiveTab] = useState(''); + const [showNoteModal, setShowNote] = useState(false); + const [noteItem, setNoteItem] = useState(null); + const [contextValue, setContextValue] = useState(defaultContextValue) + + useEffect(() => { + fetchList('issues'); + const [WebPlayerInst, PlayerStore] = createWebPlayer(session, jwt); + setContextValue({ player: WebPlayerInst, store: PlayerStore }) + + // initPlayer(session, jwt); TODOPlayer + props.fetchMembers(); + + notesStore.fetchSessionNotes(session.sessionId).then((r) => { + // WebPlayerInst.injectNotes(r); + // PlayerStore.update({ notes: r }) + const note = props.query.get('note'); + if (note) { + WebPlayerInst.pause(); + setNoteItem(notesStore.getNoteById(parseInt(note, 10), r)); + setShowNote(true); + } + }); + + const jumptTime = props.query.get('jumpto'); + if (jumptTime) { + WebPlayerInst.jump(parseInt(jumptTime)); + } + + return () => WebPlayerInst.clean(); + }, [session.sessionId]); + + // LAYOUT (TODO: local layout state - useContext or something..) + useEffect( + () => () => { + toggleFullscreen(false); + closeBottomBlock(); + }, + [] + ); + + const onNoteClose = () => { + setShowNote(false); + contextValue.player.togglePlay(); + }; + + if (!contextValue.player) return null; + + return ( + + + <> + + {/* @ts-ignore */} + + + {showNoteModal ? ( + ) => m.id === noteItem?.userId)?.email || ''} + note={noteItem} + onClose={onNoteClose} + notFound={!noteItem} + /> + ) : null} + + + + + ); +} + +export default connect( + (state: any) => ({ + session: state.getIn(['sessions', 'current']), + jwt: state.get('jwt'), + fullscreen: state.getIn(['components', 'player', 'fullscreen']), + showEvents: state.get('showEvents'), + members: state.getIn(['members', 'list']), + }), + { + toggleFullscreen, + closeBottomBlock, + fetchList, + fetchMembers, + } +)(withLocationHandlers()(WebPlayer)); diff --git a/frontend/app/components/Session/playerContext.ts b/frontend/app/components/Session/playerContext.ts new file mode 100644 index 000000000..7068b982f --- /dev/null +++ b/frontend/app/components/Session/playerContext.ts @@ -0,0 +1,12 @@ +import { createContext } from 'react'; +import { + IWebPlayer, + IStore +} from 'Player' + +export interface IPlayerContext { + player: IWebPlayer + store: IStore, +} +export const defaultContextValue: IPlayerContext = { player: undefined, store: undefined} +export const PlayerContext = createContext(defaultContextValue); diff --git a/frontend/app/components/Session_/Console/Console.js b/frontend/app/components/Session_/Console/Console.js deleted file mode 100644 index 3c4a3752c..000000000 --- a/frontend/app/components/Session_/Console/Console.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import { connectPlayer, jump } from 'Player'; -import ConsoleContent from './ConsoleContent'; - -@connectPlayer(state => ({ - logs: state.logList, - // time: state.time, - livePlay: state.livePlay, - listNow: state.logListNow, -})) -export default class Console extends React.PureComponent { - render() { - const { logs, time, listNow } = this.props; - return ( - - ); - } -} diff --git a/frontend/app/components/Session_/Console/ConsoleContent.js b/frontend/app/components/Session_/Console/ConsoleContent.js deleted file mode 100644 index a7482b69e..000000000 --- a/frontend/app/components/Session_/Console/ConsoleContent.js +++ /dev/null @@ -1,123 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { getRE } from 'App/utils'; -import { Icon, NoContent, Tabs, Input } from 'UI'; -import { jump } from 'Player'; -import { LEVEL } from 'Types/session/log'; -import Autoscroll from '../Autoscroll'; -import BottomBlock from '../BottomBlock'; -import stl from './console.module.css'; -import ConsoleRow from './ConsoleRow'; -// import { Duration } from 'luxon'; - -const ALL = 'ALL'; -const INFO = 'INFO'; -const WARNINGS = 'WARNINGS'; -const ERRORS = 'ERRORS'; - -const LEVEL_TAB = { - [LEVEL.INFO]: INFO, - [LEVEL.LOG]: INFO, - [LEVEL.WARNING]: WARNINGS, - [LEVEL.ERROR]: ERRORS, - [LEVEL.EXCEPTION]: ERRORS, -}; - -const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({ text: tab, key: tab })); - -// eslint-disable-next-line complexity -const getIconProps = (level) => { - switch (level) { - case LEVEL.INFO: - case LEVEL.LOG: - return { - name: 'console/info', - color: 'blue2', - }; - case LEVEL.WARN: - case LEVEL.WARNING: - return { - name: 'console/warning', - color: 'red2', - }; - case LEVEL.ERROR: - return { - name: 'console/error', - color: 'red', - }; - } - return null; -}; - -function renderWithNL(s = '') { - if (typeof s !== 'string') return ''; - return s.split('\n').map((line, i) =>
{line}
); -} - -export default class ConsoleContent extends React.PureComponent { - state = { - filter: '', - activeTab: ALL, - }; - onTabClick = (activeTab) => this.setState({ activeTab }); - onFilterChange = ({ target: { value } }) => this.setState({ filter: value }); - - render() { - const { logs, isResult, additionalHeight, logsNow } = this.props; - const time = logsNow.length > 0 ? logsNow[logsNow.length - 1].time : undefined; - const { filter, activeTab, currentError } = this.state; - const filterRE = getRE(filter, 'i'); - const filtered = logs.filter(({ level, value }) => - activeTab === ALL - ? filterRE.test(value) - : filterRE.test(value) && LEVEL_TAB[level] === activeTab - ); - - const lastIndex = filtered.filter((item) => item.time <= time).length - 1; - - return ( - <> - - -
- Console - -
- -
- - - - No Data - - } - size="small" - show={filtered.length === 0} - > - - {filtered.map((l) => ( - - ))} - - - -
- - ); - } -} diff --git a/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx deleted file mode 100644 index c87ff3f9c..000000000 --- a/frontend/app/components/Session_/Console/ConsoleRow/ConsoleRow.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { useState } from 'react'; -import cn from 'classnames'; -import stl from '../console.module.css'; -import { Icon } from 'UI'; -import JumpButton from 'Shared/DevTools/JumpButton'; - -interface Props { - log: any; - iconProps: any; - jump?: any; - renderWithNL?: any; -} -function ConsoleRow(props: Props) { - const { log, iconProps, jump, renderWithNL } = props; - const [expanded, setExpanded] = useState(false); - const lines = log.value.split('\n').filter((l: any) => !!l); - const canExpand = lines.length > 1; - return ( -
setExpanded(!expanded)} - > -
- -
- {/*
- {Duration.fromMillis(log.time).toFormat('mm:ss.SSS')} -
*/} -
-
- {canExpand && ( - - )} - {renderWithNL(lines.pop())} -
- {canExpand && expanded && lines.map((l: any) =>
{l}
)} -
- jump(log.time)} /> -
- ); -} - -export default ConsoleRow; diff --git a/frontend/app/components/Session_/Console/ConsoleRow/index.ts b/frontend/app/components/Session_/Console/ConsoleRow/index.ts deleted file mode 100644 index c9140d748..000000000 --- a/frontend/app/components/Session_/Console/ConsoleRow/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ConsoleRow'; diff --git a/frontend/app/components/Session_/Console/console.module.css b/frontend/app/components/Session_/Console/console.module.css deleted file mode 100644 index 2da78f540..000000000 --- a/frontend/app/components/Session_/Console/console.module.css +++ /dev/null @@ -1,38 +0,0 @@ - -.message { - overflow-x: auto; - margin-left: 10px; - font-size: 13px; - overflow-x: auto; - &::-webkit-scrollbar { - height: 2px; - } -} - -.line { - font-family: 'Menlo', 'monaco', 'consolas', monospace; - /* margin-top: -1px; ??? */ - display: flex; - align-items: flex-start; - border-bottom: solid thin $gray-light-shade; - &:hover { - background-coor: $active-blue !important; - } -} - -.timestamp { - -} - -.activeRow { - background-color: $teal-light !important; -} - -.icon { - padding-top: 4px; - margin-right: 7px; -} - -.inactiveRow { - opacity: 0.5; -} \ No newline at end of file diff --git a/frontend/app/components/Session_/Inspector/index.js b/frontend/app/components/Session_/Inspector/index.js index f76834fee..38d44149a 100644 --- a/frontend/app/components/Session_/Inspector/index.js +++ b/frontend/app/components/Session_/Inspector/index.js @@ -53,10 +53,10 @@ export default function Inspector () { if (!doc) return null; return ( - +
markElement(null) } className={stl.wrapper}> - ); -} \ No newline at end of file +} diff --git a/frontend/app/components/Session_/LongTasks/LongTasks.js b/frontend/app/components/Session_/LongTasks/LongTasks.js index fd3b4cc17..2fa80fd01 100644 --- a/frontend/app/components/Session_/LongTasks/LongTasks.js +++ b/frontend/app/components/Session_/LongTasks/LongTasks.js @@ -40,12 +40,12 @@ export default class GraphQL extends React.PureComponent { const { filter, current } = this.state; const filterRE = getRE(filter, 'i'); const filtered = list - .filter(({ containerType, context, containerName = "", containerId = "", containerSrc="" }) => - filterRE.test(containerName) || + .filter(({ containerType, context, containerName = "", containerId = "", containerSrc="" }) => + filterRE.test(containerName) || filterRE.test(containerId) || filterRE.test(containerSrc) || filterRE.test(CONTEXTS[ context ]) || - filterRE.test(CONTAINER_TYPES[ containerType ])); + filterRE.test(CONTAINER_TYPES[ containerType ])); const lastIndex = filtered.filter(item => item.time <= time).length - 1; return ( @@ -64,7 +64,7 @@ export default class GraphQL extends React.PureComponent { - Learn more + Learn more about Long Tasks API } diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index 48d881c29..ea3640847 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -20,21 +20,13 @@ import { OVERVIEW, } from 'Duck/components/player'; import NetworkPanel from 'Shared/DevTools/NetworkPanel'; -import Console from '../Console/Console'; import StackEvents from '../StackEvents/StackEvents'; import Storage from '../Storage'; -import Profiler from '../Profiler'; import { ConnectedPerformance } from '../Performance'; import GraphQL from '../GraphQL'; import Exceptions from '../Exceptions/Exceptions'; import LongTasks from '../LongTasks'; import Inspector from '../Inspector'; -import { - attach as attachPlayer, - Controls as PlayerControls, - scale as scalePlayerScreen, - connectPlayer, -} from 'Player'; import Controls from './Controls'; import Overlay from './Overlay'; import stl from './player.module.css'; @@ -42,70 +34,47 @@ import { updateLastPlayedSession } from 'Duck/sessions'; import OverviewPanel from '../OverviewPanel'; import ConsolePanel from 'Shared/DevTools/ConsolePanel'; import ProfilerPanel from 'Shared/DevTools/ProfilerPanel'; +import { PlayerContext } from 'App/components/Session/playerContext'; -@connectPlayer((state) => ({ - live: state.live, -})) -@connect( - (state) => { - const isAssist = window.location.pathname.includes('/assist/'); - return { - fullscreen: state.getIn(['components', 'player', 'fullscreen']), - nextId: state.getIn(['sessions', 'nextId']), - sessionId: state.getIn(['sessions', 'current', 'sessionId']), - closedLive: - !!state.getIn(['sessions', 'errors']) || - (isAssist && !state.getIn(['sessions', 'current', 'live'])), - }; - }, - { - hideTargetDefiner, +function Player(props) { + const { + className, + bottomBlockIsActive, + fullscreen, fullscreenOff, - updateLastPlayedSession, - } -) -export default class Player extends React.PureComponent { - screenWrapper = React.createRef(); + nextId, + closedLive, + bottomBlock, + activeTab, + } = props; + const playerContext = React.useContext(PlayerContext) + const screenWrapper = React.useRef(); - componentDidUpdate(prevProps) { - if ( - [prevProps.bottomBlock, this.props.bottomBlock].includes(NONE) || - prevProps.fullscreen !== this.props.fullscreen - ) { - scalePlayerScreen(); + React.useEffect(() => { + props.updateLastPlayedSession(props.sessionId); + if (!props.closedLive) { + const parentElement = findDOMNode(screenWrapper.current); //TODO: good architecture + playerContext.player.attach(parentElement); } - } - componentDidMount() { - this.props.updateLastPlayedSession(this.props.sessionId); - if (this.props.closedLive) return; + }, []) - const parentElement = findDOMNode(this.screenWrapper.current); //TODO: good architecture - attachPlayer(parentElement); - } + React.useEffect(() => { + playerContext.player.scale(); + }, [props.bottomBlock, props.fullscreen, playerContext.player]) - render() { - const { - className, - bottomBlockIsActive, - fullscreen, - fullscreenOff, - nextId, - closedLive, - bottomBlock, - activeTab, - } = this.props; + if (!playerContext.player) return null; - const maxWidth = activeTab ? 'calc(100vw - 270px)' : '100vw'; - return ( -
{fullscreen && }
- -
+ +
{!fullscreen && !!bottomBlock && (
@@ -125,8 +94,25 @@ export default class Player extends React.PureComponent { {bottomBlock === INSPECTOR && }
)} - +
- ); - } + ) } + +export default connect((state) => { + const isAssist = window.location.pathname.includes('/assist/'); + return { + fullscreen: state.getIn(['components', 'player', 'fullscreen']), + nextId: state.getIn(['sessions', 'nextId']), + sessionId: state.getIn(['sessions', 'current', 'sessionId']), + closedLive: + !!state.getIn(['sessions', 'errors']) || + (isAssist && !state.getIn(['sessions', 'current', 'live'])), + }; + }, + { + hideTargetDefiner, + fullscreenOff, + updateLastPlayedSession, + } +)(Player) diff --git a/frontend/app/components/Session_/Profiler/Profiler.js b/frontend/app/components/Session_/Profiler/Profiler.js index 398560a3d..9b9e6e42c 100644 --- a/frontend/app/components/Session_/Profiler/Profiler.js +++ b/frontend/app/components/Session_/Profiler/Profiler.js @@ -33,7 +33,7 @@ export default class Profiler extends React.PureComponent { return ( - } @@ -55,7 +55,7 @@ export default class Profiler extends React.PureComponent { /> - +export type IWebPlayer = WebPlayer +export type IWebPlayerStore = Store -export function createWebPlayer(session, wrapStore?: (s:WebPlayerStore) => WebPlayerStore): [WebPlayer, WebPlayerStore] { +export function createWebPlayer(session: Record, wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] { let store: WebPlayerStore = new SimpleStore({ ...Player.INITIAL_STATE, ...MM_INITIAL_STATE, @@ -20,7 +22,7 @@ export function createWebPlayer(session, wrapStore?: (s:WebPlayerStore) => WebPl } -export function createLiveWebPlayer(session, config: RTCIceServer[], wrapStore?: (s:WebPlayerStore) => WebPlayerStore): [WebPlayer, WebPlayerStore] { +export function createLiveWebPlayer(session: Record, config: RTCIceServer[], wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] { let store: WebPlayerStore = new SimpleStore({ ...Player.INITIAL_STATE, ...MM_INITIAL_STATE, @@ -30,4 +32,4 @@ export function createLiveWebPlayer(session, config: RTCIceServer[], wrapStore?: } const player = new WebPlayer(store, session, config, true) return [player, store] -} \ No newline at end of file +} From 93f65211a59b42fb061ac4a1ed3edb30d2651d86 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 22 Nov 2022 14:27:25 +0100 Subject: [PATCH 06/64] refactor(ui/player): fix store type for context --- frontend/app/components/Session/playerContext.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app/components/Session/playerContext.ts b/frontend/app/components/Session/playerContext.ts index 7068b982f..1c3ae9ab5 100644 --- a/frontend/app/components/Session/playerContext.ts +++ b/frontend/app/components/Session/playerContext.ts @@ -1,12 +1,12 @@ import { createContext } from 'react'; import { IWebPlayer, - IStore + IWebPlayerStore } from 'Player' export interface IPlayerContext { player: IWebPlayer - store: IStore, + store: IWebPlayerStore, } export const defaultContextValue: IPlayerContext = { player: undefined, store: undefined} export const PlayerContext = createContext(defaultContextValue); From 2634d6cbd075c2152880aedcfdd1d1208b88729b Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 22 Nov 2022 15:07:22 +0100 Subject: [PATCH 07/64] fix(frontend/player): wrap internal state instead of store --- frontend/app/player/create.ts | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/frontend/app/player/create.ts b/frontend/app/player/create.ts index e954fec03..7b665b35e 100644 --- a/frontend/app/player/create.ts +++ b/frontend/app/player/create.ts @@ -5,31 +5,37 @@ import Player, { State as PState } from './player/Player' import WebPlayer from './_web/WebPlayer' -type WebPlayerStore = Store +type WebState = PState & MMState +type WebPlayerStore = Store +export type IWebState = WebState export type IWebPlayer = WebPlayer -export type IWebPlayerStore = Store +export type IWebPlayerStore = WebPlayerStore -export function createWebPlayer(session: Record, wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] { - let store: WebPlayerStore = new SimpleStore({ +export function createWebPlayer(session: Record, wrapState?: (s:IWebState) => IWebState): [IWebPlayer, IWebPlayerStore] { + let state: WebState = { ...Player.INITIAL_STATE, ...MM_INITIAL_STATE, - }) - if (wrapStore) { - store = wrapStore(store) } + if (wrapState) { + state = wrapState(state) + } + const store: WebPlayerStore = new SimpleStore(state) + const player = new WebPlayer(store, session, null, false) return [player, store] } -export function createLiveWebPlayer(session: Record, config: RTCIceServer[], wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] { - let store: WebPlayerStore = new SimpleStore({ +export function createLiveWebPlayer(session: Record, config: RTCIceServer[], wrapState?: (s:IWebState) => IWebState): [IWebPlayer, IWebPlayerStore] { + let state: WebState = { ...Player.INITIAL_STATE, ...MM_INITIAL_STATE, - }) - if (wrapStore) { - store = wrapStore(store) } + if (wrapState) { + state = wrapState(state) + } + const store: WebPlayerStore = new SimpleStore(state) + const player = new WebPlayer(store, session, config, true) return [player, store] } From 0d57722fecfca5248e4e8ef0c89cfe7626fafa9e Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 22 Nov 2022 15:20:22 +0100 Subject: [PATCH 08/64] refactor(ui/player): refactor timeline and overlay --- frontend/app/components/Session/WebPlayer.tsx | 8 +- .../Session_/Player/Controls/Timeline.js | 199 +++++++++--------- .../components/Session_/Player/Overlay.tsx | 72 +++---- .../app/components/Session_/Player/Player.js | 8 +- 4 files changed, 133 insertions(+), 154 deletions(-) diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index 811d6b9ba..c3565321a 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -7,7 +7,8 @@ import { PlayerProvider, createWebPlayer, } from 'Player'; - +import { makeAutoObservable } from 'mobx' +import { observer } from "mobx-react-lite" import withLocationHandlers from 'HOCs/withLocationHandlers'; import { useStore } from 'App/mstore'; import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; @@ -35,7 +36,10 @@ function WebPlayer(props: any) { useEffect(() => { fetchList('issues'); - const [WebPlayerInst, PlayerStore] = createWebPlayer(session, jwt); + const [WebPlayerInst, PlayerStore] = createWebPlayer( + session, + (state) => makeAutoObservable(state) + ); setContextValue({ player: WebPlayerInst, store: PlayerStore }) // initPlayer(session, jwt); TODOPlayer diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.js b/frontend/app/components/Session_/Player/Controls/Timeline.js index e3ce9788a..9a4d0595b 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.js +++ b/frontend/app/components/Session_/Player/Controls/Timeline.js @@ -1,7 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; import { Icon } from 'UI' -import { connectPlayer, Controls, toggleTimetravel } from 'Player'; import TimeTracker from './TimeTracker'; import stl from './timeline.module.css'; import { setTimelinePointer, setTimelineHoverTime } from 'Duck/sessions'; @@ -9,6 +8,8 @@ import DraggableCircle from './DraggableCircle'; import CustomDragLayer from './CustomDragLayer'; import { debounce } from 'App/utils'; import TooltipContainer from './components/TooltipContainer'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; const BOUNDRY = 0; @@ -20,105 +21,63 @@ function getTimelinePosition(value, scale) { let deboucneJump = () => null; let debounceTooltipChange = () => null; -@connectPlayer((state) => ({ - playing: state.playing, - time: state.time, - skipIntervals: state.skipIntervals, - events: state.eventList, - skip: state.skip, - skipToIssue: state.skipToIssue, - disabled: state.cssLoading || state.messagesLoading || state.markedTargets, - endTime: state.endTime, - live: state.live, - notes: state.notes || [], // TODO: implement notes without interaction with Player state -})) -@connect( - (state) => ({ - issues: state.getIn(['sessions', 'current', 'issues']), - startedAt: state.getIn(['sessions', 'current', 'startedAt']), - tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']), - }), - { setTimelinePointer, setTimelineHoverTime } -) -export default class Timeline extends React.PureComponent { - progressRef = React.createRef(); - timelineRef = React.createRef(); - wasPlaying = false; - seekProgress = (e) => { - const time = this.getTime(e); - this.props.jump(time); - this.hideTimeTooltip(); - }; - loadAndSeek = async (e) => { - e.persist(); - await toggleTimetravel(); +function Timeline(props) { + const { player, store } = React.useContext(PlayerContext) + const [wasPlaying, setWasPlaying] = React.useState(false); + const { + playing, + time, + skipIntervals, + eventList: events, + skip, + skipToIssue, + disabled, + endTime, + live, + notes = [], + } = store.get() - setTimeout(() => { - this.seekProgress(e); - }); - }; + const progressRef = React.useRef(); + const timelineRef = React.useRef(); - jumpToTime = (e) => { - if (this.props.live && !this.props.liveTimeTravel) { - this.loadAndSeek(e); - } else { - this.seekProgress(e); - } - }; - getTime = (e, customEndTime) => { - const { endTime } = this.props; - const p = e.nativeEvent.offsetX / e.target.offsetWidth; - const targetTime = customEndTime || endTime; - const time = Math.max(Math.round(p * targetTime), 0); + const scale = 100 / endTime; - return time; - }; - - createEventClickHandler = (pointer) => (e) => { - e.stopPropagation(); - this.props.jump(pointer.time); - this.props.setTimelinePointer(pointer); - }; - - componentDidMount() { - const { issues, skipToIssue } = this.props; + React.useEffect(() => { + const { issues } = props; const firstIssue = issues.get(0); - deboucneJump = debounce(this.props.jump, 500); - debounceTooltipChange = debounce(this.props.setTimelineHoverTime, 50); + deboucneJump = debounce(player.jump, 500); + debounceTooltipChange = debounce(props.setTimelineHoverTime, 50); if (firstIssue && skipToIssue) { - this.props.jump(firstIssue.time); + player.jump(firstIssue.time); } - } + }, []) - onDragEnd = () => { - const { live, liveTimeTravel } = this.props; + const onDragEnd = () => { if (live && !liveTimeTravel) return; - if (this.wasPlaying) { - this.props.togglePlay(); + if (wasPlaying) { + player.togglePlay(); } }; - onDrag = (offset) => { - const { endTime, live, liveTimeTravel } = this.props; + const onDrag = (offset) => { if (live && !liveTimeTravel) return; - const p = (offset.x - BOUNDRY) / this.progressRef.current.offsetWidth; + const p = (offset.x - BOUNDRY) / progressRef.current.offsetWidth; const time = Math.max(Math.round(p * endTime), 0); deboucneJump(time); - this.hideTimeTooltip(); - if (this.props.playing) { - this.wasPlaying = true; - this.props.pause(); + hideTimeTooltip(); + if (playing) { + setWasPlaying(true) + player.pause(); } }; - getLiveTime = (e) => { - const { startedAt } = this.props; + const getLiveTime = (e) => { const duration = new Date().getTime() - startedAt; const p = e.nativeEvent.offsetX / e.target.offsetWidth; const time = Math.max(Math.round(p * duration), 0); @@ -126,23 +85,23 @@ export default class Timeline extends React.PureComponent { return [time, duration]; }; - showTimeTooltip = (e) => { - if (e.target !== this.progressRef.current && e.target !== this.timelineRef.current) { - return this.props.tooltipVisible && this.hideTimeTooltip(); + const showTimeTooltip = (e) => { + if (e.target !== progressRef.current && e.target !== timelineRef.current) { + return props.tooltipVisible && hideTimeTooltip(); } - const { live } = this.props; + let timeLineTooltip; if (live) { - const [time, duration] = this.getLiveTime(e); + const [time, duration] = getLiveTime(e); timeLineTooltip = { time: duration - time, offset: e.nativeEvent.offsetX, isVisible: true, }; } else { - const time = this.getTime(e); + const time = getTime(e); timeLineTooltip = { time: time, offset: e.nativeEvent.offsetX, @@ -153,18 +112,44 @@ export default class Timeline extends React.PureComponent { debounceTooltipChange(timeLineTooltip); }; - hideTimeTooltip = () => { + const hideTimeTooltip = () => { const timeLineTooltip = { isVisible: false }; debounceTooltipChange(timeLineTooltip); }; - render() { - const { events, skip, skipIntervals, disabled, endTime, live, notes } = this.props; + const seekProgress = (e) => { + const time = getTime(e); + player.jump(time); + hideTimeTooltip(); + }; - const scale = 100 / endTime; + const loadAndSeek = async (e) => { + e.persist(); + await toggleTimetravel(); - return ( -
{ + seekProgress(e); + }); + }; + + const jumpToTime = (e) => { + if (live && !liveTimeTravel) { + loadAndSeek(e); + } else { + seekProgress(e); + } + }; + + const getTime = (e, customEndTime) => { + const p = e.nativeEvent.offsetX / e.target.offsetWidth; + const targetTime = customEndTime || endTime; + const time = Math.max(Math.round(p * targetTime), 0); + + return time; + }; + + return ( +
{/* custo color is live */} - + {!live && skip ? skipIntervals.map((interval) => ( @@ -208,7 +193,7 @@ export default class Timeline extends React.PureComponent { }} /> )) : null} -
+
{events.map((e) => (
- ); - } + ) } + +export default connect( + (state) => ({ + issues: state.getIn(['sessions', 'current', 'issues']), + startedAt: state.getIn(['sessions', 'current', 'startedAt']), + tooltipVisible: state.getIn(['sessions', 'timeLineTooltip', 'isVisible']), + }), + { setTimelinePointer, setTimelineHoverTime } +)(observer(Timeline)) diff --git a/frontend/app/components/Session_/Player/Overlay.tsx b/frontend/app/components/Session_/Player/Overlay.tsx index 7b610ded1..d9a73c56a 100644 --- a/frontend/app/components/Session_/Player/Overlay.tsx +++ b/frontend/app/components/Session_/Player/Overlay.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import { connectPlayer } from 'Player'; import { getStatusText } from 'Player'; -import type { MarkedTarget } from 'Player'; import { CallingState, ConnectionStatus, RemoteControlStatus } from 'Player'; import AutoplayTimer from './Overlay/AutoplayTimer'; @@ -10,45 +8,39 @@ import LiveStatusText from './Overlay/LiveStatusText'; import Loader from './Overlay/Loader'; import ElementsMarker from './Overlay/ElementsMarker'; import RequestingWindow, { WindowType } from 'App/components/Assist/RequestingWindow'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; interface Props { - playing: boolean, - completed: boolean, - inspectorMode: boolean, - loading: boolean, - live: boolean, - liveStatusText: string, - concetionStatus: ConnectionStatus, - autoplay: boolean, - markedTargets: MarkedTarget[] | null, - activeTargetIndex: number, - calling: CallingState, - remoteControl: RemoteControlStatus - nextId: string, - togglePlay: () => void, closedLive?: boolean, - livePlay?: boolean, } function Overlay({ - playing, - completed, - inspectorMode, - loading, - live, - liveStatusText, - concetionStatus, - autoplay, - markedTargets, - activeTargetIndex, nextId, - togglePlay, closedLive, - livePlay, - calling, - remoteControl, }: Props) { + const { player, store } = React.useContext(PlayerContext) + + const { + playing, + messagesLoading, + cssLoading, + completed, + autoplay, + inspectorMode, + live, + peerConnectionStatus, + markedTargets, + activeTargetIndex, + livePlay, + calling, + remoteControl, + } = store.get() + const loading = messagesLoading || cssLoading + const liveStatusText = getStatusText(peerConnectionStatus) + const concetionStatus = peerConnectionStatus + const showAutoplayTimer = !live && completed && autoplay && nextId const showPlayIconLayer = !live && !markedTargets && !inspectorMode && !loading && !showAutoplayTimer; const showLiveStatusText = live && livePlay && liveStatusText && !loading; @@ -65,7 +57,7 @@ function Overlay({ } { loading ? : null } { showPlayIconLayer && - + } { markedTargets && } @@ -74,18 +66,4 @@ function Overlay({ } -export default connectPlayer(state => ({ - playing: state.playing, - loading: state.messagesLoading || state.cssLoading, - completed: state.completed, - autoplay: state.autoplay, - inspectorMode: state.inspectorMode, - live: state.live, - liveStatusText: getStatusText(state.peerConnectionStatus), - concetionStatus: state.peerConnectionStatus, - markedTargets: state.markedTargets, - activeTargetIndex: state.activeTargetIndex, - livePlay: state.livePlay, - calling: state.calling, - remoteControl: state.remoteControl, -}))(Overlay); +export default observer(Overlay); diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index ea3640847..a37fca62b 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -73,7 +73,7 @@ function Player(props) { > {fullscreen && }
- +
{!fullscreen && !!bottomBlock && ( @@ -94,7 +94,11 @@ function Player(props) { {bottomBlock === INSPECTOR && }
)} - +
) } From f2bc1e56e3000fb382409e8a4db1e5caabe26859 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 22 Nov 2022 16:25:27 +0100 Subject: [PATCH 09/64] refactor(player): remove old store and singletone imports --- frontend/app/player/_store/index.js | 8 ++++---- frontend/app/player/index.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/app/player/_store/index.js b/frontend/app/player/_store/index.js index c2a987661..cad6dfaff 100644 --- a/frontend/app/player/_store/index.js +++ b/frontend/app/player/_store/index.js @@ -1,4 +1,4 @@ -export * from './connector'; -export * from './store'; -export * from './selectors'; -export { default } from './store'; \ No newline at end of file +// export * from './connector'; +// export * from './store'; +// export * from './selectors'; +// export { default } from './store'; \ No newline at end of file diff --git a/frontend/app/player/index.js b/frontend/app/player/index.js index 67d56e19c..41f1728c7 100644 --- a/frontend/app/player/index.js +++ b/frontend/app/player/index.js @@ -3,5 +3,5 @@ export * from './_web/assist/LocalStream'; export * from './_web/WebPlayer'; export * from './create'; -export * from './_store'; -export * from './_singletone'; +//export * from './_store'; +//export * from './_singletone'; From 8fd2ce7f8af62c18ec75048cd6536eb5985cc315 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 22 Nov 2022 16:28:38 +0100 Subject: [PATCH 10/64] refactor(player):targetMarker functionality segregation; type fixes --- .../app/player/_web/InspectorController.ts | 6 +- frontend/app/player/_web/MessageManager.ts | 42 +++--- frontend/app/player/_web/Screen/Screen.ts | 10 +- frontend/app/player/_web/TargetMarker.ts | 134 ++++++++++++++++++ frontend/app/player/_web/WebPlayer.ts | 116 ++++----------- frontend/app/player/create.ts | 11 +- frontend/app/player/{index.js => index.ts} | 2 + frontend/app/player/player/Animator.ts | 5 +- 8 files changed, 201 insertions(+), 125 deletions(-) create mode 100644 frontend/app/player/_web/TargetMarker.ts rename frontend/app/player/{index.js => index.ts} (78%) diff --git a/frontend/app/player/_web/InspectorController.ts b/frontend/app/player/_web/InspectorController.ts index a78bad006..d2c4b6624 100644 --- a/frontend/app/player/_web/InspectorController.ts +++ b/frontend/app/player/_web/InspectorController.ts @@ -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) diff --git a/frontend/app/player/_web/MessageManager.ts b/frontend/app/player/_web/MessageManager.ts index 14642c3dc..9af0a4b0a 100644 --- a/frontend/app/player/_web/MessageManager.ts +++ b/frontend/app/player/_web/MessageManager.ts @@ -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/**/ = new ListWalker(); private locationManager: ListWalker = new ListWalker(); private loadedLocationManager: ListWalker = 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 } diff --git a/frontend/app/player/_web/Screen/Screen.ts b/frontend/app/player/_web/Screen/Screen.ts index 6d28f06fd..aa9e9aea2 100644 --- a/frontend/app/player/_web/Screen/Screen.ts +++ b/frontend/app/player/_web/Screen/Screen.ts @@ -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) diff --git a/frontend/app/player/_web/TargetMarker.ts b/frontend/app/player/_web/TargetMarker.ts new file mode 100644 index 000000000..a2ca99cc2 --- /dev/null +++ b/frontend/app/player/_web/TargetMarker.ts @@ -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, + ) {} + + 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 }); + } + } + +} \ No newline at end of file diff --git a/frontend/app/player/_web/WebPlayer.ts b/frontend/app/player/_web/WebPlayer.ts index de3dcfa49..a2da13d4c 100644 --- a/frontend/app/player/_web/WebPlayer.ts +++ b/frontend/app/player/_web/WebPlayer.ts @@ -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, session, config: RTCIceServer[], live: boolean) { + constructor(private wpState: Store, 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) { + this.targetMarker.setActiveTarget(...args) + } + markTargets(args: Parameters) { + 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() diff --git a/frontend/app/player/create.ts b/frontend/app/player/create.ts index 7b665b35e..beacd49e6 100644 --- a/frontend/app/player/create.ts +++ b/frontend/app/player/create.ts @@ -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 export type IWebState = WebState export type IWebPlayer = WebPlayer @@ -13,8 +10,7 @@ export type IWebPlayerStore = WebPlayerStore export function createWebPlayer(session: Record, 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, wrapState?: (s:IWe export function createLiveWebPlayer(session: Record, 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) diff --git a/frontend/app/player/index.js b/frontend/app/player/index.ts similarity index 78% rename from frontend/app/player/index.js rename to frontend/app/player/index.ts index 41f1728c7..55266aa5f 100644 --- a/frontend/app/player/index.js +++ b/frontend/app/player/index.ts @@ -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'; diff --git a/frontend/app/player/player/Animator.ts b/frontend/app/player/player/Animator.ts index 871da629a..a75290929 100644 --- a/frontend/app/player/player/Animator.ts +++ b/frontend/app/player/player/Animator.ts @@ -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 From 56327fa90906008e820a454dea367effabf311d5 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 22 Nov 2022 17:07:14 +0100 Subject: [PATCH 11/64] refactor(player): decouple live state from MessageManager --- frontend/app/player/_web/Lists.ts | 2 +- frontend/app/player/_web/MessageManager.ts | 47 +++++++--------------- frontend/app/player/_web/WebPlayer.ts | 21 ++++++++-- 3 files changed, 32 insertions(+), 38 deletions(-) diff --git a/frontend/app/player/_web/Lists.ts b/frontend/app/player/_web/Lists.ts index 4eb5de6db..fdf560c93 100644 --- a/frontend/app/player/_web/Lists.ts +++ b/frontend/app/player/_web/Lists.ts @@ -29,7 +29,7 @@ type MarkedListsObject = { } type ListsObject = SimpleListsObject & MarkedListsObject -type InitialLists = { +export type InitialLists = { [key in typeof LIST_NAMES[number]]: any[] } diff --git a/frontend/app/player/_web/MessageManager.ts b/frontend/app/player/_web/MessageManager.ts index 9af0a4b0a..8eeb74f19 100644 --- a/frontend/app/player/_web/MessageManager.ts +++ b/frontend/app/player/_web/MessageManager.ts @@ -27,6 +27,7 @@ import { decryptSessionBytes } from './network/crypto'; 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 { InitialLists } from './Lists' import type { PerformanceChartPoint } from './managers/PerformanceTrackManager'; import type { SkipInterval } from './managers/ActivityManager'; @@ -45,7 +46,6 @@ export interface State extends SuperState { error: boolean, devtoolsLoading: boolean, - liveTimeTravel: boolean, messagesLoading: boolean, cssLoading: boolean, @@ -82,7 +82,6 @@ export default class MessageManager extends Screen { error: false, devtoolsLoading: false, - liveTimeTravel: false, messagesLoading: false, cssLoading: false, get ready() { @@ -118,39 +117,25 @@ export default class MessageManager extends Screen { constructor( private readonly session: any /*Session*/, private readonly state: Store, - config: any, - live: boolean, + initialLists?: Partial ) { super(); this.pagesManager = new PagesManager(this, this.session.isMobile) - this.mouseMoveManager = new MouseMoveManager(this); + this.mouseMoveManager = new MouseMoveManager(this) - this.sessionStart = this.session.startedAt; + this.sessionStart = this.session.startedAt - if (live) { - this.lists = new Lists() - } else { - this.activityManager = new ActivityManager(this.session.duration.milliseconds); - /* == REFACTOR_ME == */ - const eventList = session.events.toJSON(); - // TODO: fix types for events, remove immutable js - eventList.forEach((e: Record) => { - if (e.type === EVENT_TYPES.LOCATION) { //TODO type system - this.locationEventManager.append(e); - } - }) + this.lists = new Lists(initialLists) + initialLists && initialLists.event.forEach((e: Record) => { // TODO: to one of "Moveable" module + if (e.type === EVENT_TYPES.LOCATION) { + this.locationEventManager.append(e); + } + }) - this.lists = new Lists({ - event: eventList, - stack: session.stackEvents.toJSON(), - resource: session.resources.toJSON(), - exceptions: session.errors, - }) + this.activityManager = new ActivityManager(this.session.duration.milliseconds) // only if not-live - /* === */ - this.loadMessages(); - } + this.loadMessages() } private parseAndDistributeMessages(fileReader: MFileReader, onMessage?: (msg: Message) => void) { @@ -264,15 +249,11 @@ export default class MessageManager extends Screen { } } - reloadWithUnprocessedFile() { + reloadWithUnprocessedFile(onSuccess: ()=>void) { const onData = (byteArray: Uint8Array) => { const onMessage = (msg: Message) => { this.lastMessageInFileTime = msg.time } this.parseAndDistributeMessages(new MFileReader(byteArray, this.sessionStart), onMessage) } - const updateState = () => - this.state.update({ - liveTimeTravel: true, - }); // assist will pause and skip messages to prevent timestamp related errors this.reloadMessageManagers() @@ -283,7 +264,7 @@ export default class MessageManager extends Screen { return requestEFSDom(this.session.sessionId) .then(onData) - .then(updateState) + .then(onSuccess) .then(this.onFileReadSuccess) .catch(this.onFileReadFailed) .finally(this.onFileReadFinally) diff --git a/frontend/app/player/_web/WebPlayer.ts b/frontend/app/player/_web/WebPlayer.ts index a2da13d4c..572c9e5b4 100644 --- a/frontend/app/player/_web/WebPlayer.ts +++ b/frontend/app/player/_web/WebPlayer.ts @@ -20,6 +20,7 @@ export default class WebPlayer extends Player { ...ASSIST_INITIAL_STATE, inspectorMode: false, + liveTimeTravel: false, } private readonly screen: Screen @@ -29,9 +30,17 @@ export default class WebPlayer extends Player { assistManager: AssistManager // public so far private targetMarker: TargetMarker - constructor(private wpState: Store, session, config: RTCIceServer[], live: boolean) { + constructor(private wpState: Store, session, config: RTCIceServer[], live: boolean) { + + let initialLists = live ? { + event: session.events.toJSON(), + stack: session.stackEvents.toJSON(), + resource: session.resources.toJSON(), + exceptions: session.errors, + } : {} + // TODO: separate screen from manager - const screen = new MessageManager(session, wpState, config, live) + const screen = new MessageManager(session, wpState, initialLists) super(wpState, screen) this.screen = screen this.messageManager = screen @@ -39,7 +48,7 @@ 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.targetMarker = new TargetMarker(this.screen, wpState) this.inspectorController = new InspectorController(screen) @@ -106,7 +115,11 @@ export default class WebPlayer extends Player { // TODO async toggleTimetravel() { if (!this.wpState.get().liveTimeTravel) { - return await this.messageManager.reloadWithUnprocessedFile() + return await this.messageManager.reloadWithUnprocessedFile(() => + this.wpState.update({ + liveTimeTravel: true, + }) + ) } } From c494f0c0bb6786bd7594ba6904a8570c31fc846f Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 22 Nov 2022 17:07:49 +0100 Subject: [PATCH 12/64] refactor(ui/player): refactor connect player for events, autoplay, subheader, player etc --- .../app/components/Session/PlayerContent.js | 20 +++--- .../app/components/Session/RightBlock.tsx | 25 ++++---- frontend/app/components/Session/WebPlayer.tsx | 43 +++++++------ .../components/Session_/Autoplay/Autoplay.js | 22 +++---- .../Session_/EventsBlock/EventsBlock.js | 1 + frontend/app/components/Session_/Subheader.js | 61 ++++++++++--------- frontend/app/player/create.ts | 1 - 7 files changed, 91 insertions(+), 82 deletions(-) diff --git a/frontend/app/components/Session/PlayerContent.js b/frontend/app/components/Session/PlayerContent.js index 8983ca5fc..c0a695173 100644 --- a/frontend/app/components/Session/PlayerContent.js +++ b/frontend/app/components/Session/PlayerContent.js @@ -1,14 +1,21 @@ import React from 'react'; -import { - connectPlayer, -} from 'Player'; import PlayerBlock from '../Session_/PlayerBlock'; import styles from '../Session_/session.module.css'; import { countDaysFrom } from 'App/date'; import cn from 'classnames'; import RightBlock from './RightBlock'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; + +function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab }) { + const { store } = React.useContext(PlayerContext) + + const { + error, + } = store.get() + + const hasError = !!error -function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab, hasError }) { const sessionDays = countDaysFrom(session.startedAt); return (
@@ -64,7 +71,4 @@ function RightMenu({ live, tabs, activeTab, setActiveTab, fullscreen }) { ); } -export default connectPlayer((state) => ({ - showEvents: !state.showEvents, - hasError: state.error, -}))(PlayerContent); +export default observer(PlayerContent); diff --git a/frontend/app/components/Session/RightBlock.tsx b/frontend/app/components/Session/RightBlock.tsx index 8ae42b622..66f92a93c 100644 --- a/frontend/app/components/Session/RightBlock.tsx +++ b/frontend/app/components/Session/RightBlock.tsx @@ -1,23 +1,24 @@ -import React, { useState } from 'react' +import React from 'react' import EventsBlock from '../Session_/EventsBlock'; import PageInsightsPanel from '../Session_/PageInsightsPanel/PageInsightsPanel' -import { Controls as PlayerControls } from 'Player'; -import { connectPlayer } from 'Player'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; + import cn from 'classnames'; import stl from './rightblock.module.css'; -const EventsBlockConnected = connectPlayer(state => ({ - currentTimeEventIndex: state.eventListNow.length > 0 ? state.eventListNow.length - 1 : 0, - playing: state.playing, -}))(EventsBlock) - -function RightBlock(props) { +function RightBlock(props: any) { const { activeTab } = props; + const { player, store } = React.useContext(PlayerContext) - const renderActiveTab = (tab) => { + const { eventListNow, playing } = store.get() + const currentTimeEventIndex = eventListNow.length > 0 ? eventListNow.length - 1 : 0 + + const EventsBlockConnected = () => + const renderActiveTab = (tab: string) => { switch(tab) { case props.tabs.EVENTS: - return + return case props.tabs.HEATMAPS: return } @@ -29,4 +30,4 @@ function RightBlock(props) { ) } -export default React.memo(RightBlock) +export default observer(RightBlock) diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index c3565321a..480ca0530 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -3,23 +3,16 @@ import { connect } from 'react-redux'; import { Modal } from 'UI'; import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; import { fetchList } from 'Duck/integrations'; -import { - PlayerProvider, - createWebPlayer, -} from 'Player'; -import { makeAutoObservable } from 'mobx' -import { observer } from "mobx-react-lite" +import { PlayerProvider, createWebPlayer } from 'Player'; +import { makeAutoObservable } from 'mobx'; +import { observer } from 'mobx-react-lite'; import withLocationHandlers from 'HOCs/withLocationHandlers'; import { useStore } from 'App/mstore'; import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; import ReadNote from '../Session_/Player/Controls/components/ReadNote'; import { fetchList as fetchMembers } from 'Duck/member'; -import PlayerContent from './PlayerContent' -import { - IPlayerContext, - PlayerContext, - defaultContextValue -} from './playerContext' +import PlayerContent from './PlayerContent'; +import { IPlayerContext, PlayerContext, defaultContextValue } from './playerContext'; const TABS = { EVENTS: 'User Steps', @@ -27,20 +20,27 @@ const TABS = { }; function WebPlayer(props: any) { - const { session, toggleFullscreen, closeBottomBlock, live, fullscreen, jwt, fetchList } = props; + const { + session, + toggleFullscreen, + closeBottomBlock, + live, + fullscreen, + jwt, + fetchList + } = props; const { notesStore } = useStore(); const [activeTab, setActiveTab] = useState(''); const [showNoteModal, setShowNote] = useState(false); const [noteItem, setNoteItem] = useState(null); - const [contextValue, setContextValue] = useState(defaultContextValue) + const [contextValue, setContextValue] = useState(defaultContextValue); useEffect(() => { fetchList('issues'); - const [WebPlayerInst, PlayerStore] = createWebPlayer( - session, - (state) => makeAutoObservable(state) + const [WebPlayerInst, PlayerStore] = createWebPlayer(session, (state) => + makeAutoObservable(state) ); - setContextValue({ player: WebPlayerInst, store: PlayerStore }) + setContextValue({ player: WebPlayerInst, store: PlayerStore }); // initPlayer(session, jwt); TODOPlayer props.fetchMembers(); @@ -101,14 +101,17 @@ function WebPlayer(props: any) { {showNoteModal ? ( ) => m.id === noteItem?.userId)?.email || ''} + userEmail={ + props.members.find((m: Record) => m.id === noteItem?.userId) + ?.email || '' + } note={noteItem} onClose={onNoteClose} notFound={!noteItem} /> ) : null} - + ); diff --git a/frontend/app/components/Session_/Autoplay/Autoplay.js b/frontend/app/components/Session_/Autoplay/Autoplay.js index 63fdf6841..5510996ef 100644 --- a/frontend/app/components/Session_/Autoplay/Autoplay.js +++ b/frontend/app/components/Session_/Autoplay/Autoplay.js @@ -3,11 +3,16 @@ import { connect } from 'react-redux'; import { setAutoplayValues } from 'Duck/sessions'; import { session as sessionRoute } from 'App/routes'; import { Link, Icon, Toggler, Tooltip } from 'UI'; -import { Controls as PlayerControls, connectPlayer } from 'Player'; import cn from 'classnames'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; function Autoplay(props) { - const { previousId, nextId, autoplay, disabled } = props; + const { previousId, nextId, disabled } = props; + const { player, store } = React.useContext(PlayerContext) + + const { autoplay } = store.get() + const { toggleAutoplay } = player useEffect(() => { props.setAutoplayValues(); @@ -16,10 +21,10 @@ function Autoplay(props) { return (
- + Auto-Play
@@ -71,12 +76,5 @@ const connectAutoplay = connect( ); export default connectAutoplay( - connectPlayer( - (state) => ({ - autoplay: state.autoplay, - }), - { - toggleAutoplay: PlayerControls.toggleAutoplay, - } - )(Autoplay) + observer(Autoplay) ); diff --git a/frontend/app/components/Session_/EventsBlock/EventsBlock.js b/frontend/app/components/Session_/EventsBlock/EventsBlock.js index 69ba51996..b3d978347 100644 --- a/frontend/app/components/Session_/EventsBlock/EventsBlock.js +++ b/frontend/app/components/Session_/EventsBlock/EventsBlock.js @@ -141,6 +141,7 @@ export default class EventsBlock extends React.Component { eventsIndex, filterOutNote, } = this.props; + const { query } = this.state; const _events = this.eventsList const isLastEvent = index === _events.size - 1; diff --git a/frontend/app/components/Session_/Subheader.js b/frontend/app/components/Session_/Subheader.js index a1ee8e48d..9d462e0f7 100644 --- a/frontend/app/components/Session_/Subheader.js +++ b/frontend/app/components/Session_/Subheader.js @@ -6,31 +6,50 @@ import SharePopup from '../shared/SharePopup/SharePopup'; import copy from 'copy-to-clipboard'; import Issues from './Issues/Issues'; import NotePopup from './components/NotePopup'; -import { connectPlayer, pause } from 'Player'; import ItemMenu from './components/HeaderMenu'; import { useModal } from 'App/components/Modal'; import BugReportModal from './BugReport/BugReportModal'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; function SubHeader(props) { + const { player, store } = React.useContext(PlayerContext) + const { + width, + height, + location: currentLocation, + fetchList, + graphqlList, + resourceList, + exceptionsList, + eventList: eventsList, + endTime, + } = store.get() + + const mappedResourceList = resourceList + .filter((r) => r.isRed() || r.isYellow()) + .concat(fetchList.filter((i) => parseInt(i.status) >= 400)) + .concat(graphqlList.filter((i) => parseInt(i.status) >= 400)) + const [isCopied, setCopied] = React.useState(false); const { showModal, hideModal } = useModal(); const isAssist = window.location.pathname.includes('/assist/'); const location = - props.currentLocation && props.currentLocation.length > 60 - ? `${props.currentLocation.slice(0, 60)}...` - : props.currentLocation; + currentLocation && currentLocation.length > 60 + ? `${currentLocation.slice(0, 60)}...` + : currentLocation; const showReportModal = () => { - pause(); + player.pause(); const xrayProps = { - currentLocation: props.currentLocation, - resourceList: props.resourceList, - exceptionsList: props.exceptionsList, - eventsList: props.eventsList, - endTime: props.endTime, + currentLocation: currentLocation, + resourceList: mappedResourceList, + exceptionsList: exceptionsList, + eventsList: eventsList, + endTime: endTime, } - showModal(, { right: true }); + showModal(, { right: true }); }; return ( @@ -39,7 +58,7 @@ function SubHeader(props) {
{ - copy(props.currentLocation); + copy(currentLocation); setCopied(true); setTimeout(() => setCopied(false), 5000); }} @@ -102,20 +121,4 @@ function SubHeader(props) { ); } -const SubH = connectPlayer( - (state) => ({ - width: state.width, - height: state.height, - currentLocation: state.location, - resourceList: state.resourceList - .filter((r) => r.isRed() || r.isYellow()) - .concat(state.fetchList.filter((i) => parseInt(i.status) >= 400)) - .concat(state.graphqlList.filter((i) => parseInt(i.status) >= 400)), - exceptionsList: state.exceptionsList, - eventsList: state.eventList, - endTime: state.endTime, - }) - - )(SubHeader); - -export default React.memo(SubH); +export default observer(SubHeader); diff --git a/frontend/app/player/create.ts b/frontend/app/player/create.ts index beacd49e6..c212fd769 100644 --- a/frontend/app/player/create.ts +++ b/frontend/app/player/create.ts @@ -21,7 +21,6 @@ export function createWebPlayer(session: Record, wrapState?: (s:IWe return [player, store] } - export function createLiveWebPlayer(session: Record, config: RTCIceServer[], wrapState?: (s:IWebState) => IWebState): [IWebPlayer, IWebPlayerStore] { let state: WebState = { ...WebPlayer.INITIAL_STATE, From b995fa8441e06dc755fd4b940be23c892ec18e20 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 22 Nov 2022 17:27:54 +0100 Subject: [PATCH 13/64] fix(player):actually wrap store instead of state & MessageManager init fix --- frontend/app/player/_web/MessageManager.ts | 2 +- frontend/app/player/create.ts | 24 ++++++++++------------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/frontend/app/player/_web/MessageManager.ts b/frontend/app/player/_web/MessageManager.ts index 8eeb74f19..b092483e2 100644 --- a/frontend/app/player/_web/MessageManager.ts +++ b/frontend/app/player/_web/MessageManager.ts @@ -126,7 +126,7 @@ export default class MessageManager extends Screen { this.sessionStart = this.session.startedAt this.lists = new Lists(initialLists) - initialLists && initialLists.event.forEach((e: Record) => { // TODO: to one of "Moveable" module + initialLists?.event?.forEach((e: Record) => { // TODO: to one of "Moveable" module if (e.type === EVENT_TYPES.LOCATION) { this.locationEventManager.append(e); } diff --git a/frontend/app/player/create.ts b/frontend/app/player/create.ts index c212fd769..2bb60f7a9 100644 --- a/frontend/app/player/create.ts +++ b/frontend/app/player/create.ts @@ -4,31 +4,29 @@ import type { Store } from './player/types' import WebPlayer, { State as WebState} from './_web/WebPlayer' type WebPlayerStore = Store -export type IWebState = WebState export type IWebPlayer = WebPlayer export type IWebPlayerStore = WebPlayerStore -export function createWebPlayer(session: Record, wrapState?: (s:IWebState) => IWebState): [IWebPlayer, IWebPlayerStore] { - let state: WebState = { +export function createWebPlayer(session: Record, wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] { + let store: WebPlayerStore = new SimpleStore({ ...WebPlayer.INITIAL_STATE, + }) + if (wrapStore) { + store = wrapStore(store) } - if (wrapState) { - state = wrapState(state) - } - const store: WebPlayerStore = new SimpleStore(state) const player = new WebPlayer(store, session, null, false) return [player, store] } -export function createLiveWebPlayer(session: Record, config: RTCIceServer[], wrapState?: (s:IWebState) => IWebState): [IWebPlayer, IWebPlayerStore] { - let state: WebState = { + +export function createLiveWebPlayer(session: Record, config: RTCIceServer[], wrapStore?: (s:IWebPlayerStore) => IWebPlayerStore): [IWebPlayer, IWebPlayerStore] { + let store: WebPlayerStore = new SimpleStore({ ...WebPlayer.INITIAL_STATE, + }) + if (wrapStore) { + store = wrapStore(store) } - if (wrapState) { - state = wrapState(state) - } - const store: WebPlayerStore = new SimpleStore(state) const player = new WebPlayer(store, session, config, true) return [player, store] From af6fd085e208f09a5183d3306421d6d45698adad Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 22 Nov 2022 17:31:08 +0100 Subject: [PATCH 14/64] fix(player): state type on creation --- frontend/app/player/create.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/app/player/create.ts b/frontend/app/player/create.ts index 2bb60f7a9..410ad6e2a 100644 --- a/frontend/app/player/create.ts +++ b/frontend/app/player/create.ts @@ -1,8 +1,9 @@ import SimpleStore from './_common/SimpleStore' import type { Store } from './player/types' -import WebPlayer, { State as WebState} from './_web/WebPlayer' +import WebPlayer from './_web/WebPlayer' +type WebState = typeof WebPlayer.INITIAL_STATE //? type WebPlayerStore = Store export type IWebPlayer = WebPlayer export type IWebPlayerStore = WebPlayerStore From daf35a2dfab34291aa02a35cc8f921366926db5c Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 22 Nov 2022 18:59:55 +0100 Subject: [PATCH 15/64] refactoring(player): separating Screen and MessageManager; renamings --- frontend/app/player/_singletone.ts | 6 +-- frontend/app/player/_store/duck.js | 8 +-- frontend/app/player/_store/index.js | 8 +-- .../player/{_common => common}/ListWalker.ts | 2 +- .../ListWalkerWithMarks.ts | 2 +- .../player/{_common => common}/SimpleStore.ts | 3 +- .../app/player/{player => common}/types.ts | 4 +- frontend/app/player/create.ts | 6 +-- frontend/app/player/index.ts | 12 ++--- frontend/app/player/player/Animator.ts | 2 +- frontend/app/player/player/Player.ts | 17 ++----- .../{_web => web}/InspectorController.ts | 0 frontend/app/player/{_web => web}/Lists.ts | 4 +- .../player/{_web => web}/MessageManager.ts | 40 ++++++++------- .../app/player/{_web => web}/Screen/Cursor.ts | 0 .../player/{_web => web}/Screen/Inspector.ts | 0 .../app/player/{_web => web}/Screen/Marker.ts | 0 .../app/player/{_web => web}/Screen/Screen.ts | 0 .../{_web => web}/Screen/cursor.module.css | 0 .../{_web => web}/Screen/marker.module.css | 0 .../{_web => web}/Screen/screen.module.css | 0 .../app/player/{_web => web}/Screen/types.ts | 0 .../app/player/{_web => web}/TargetMarker.ts | 2 +- .../app/player/{_web => web}/WebLivePlayer.ts | 0 .../app/player/{_web => web}/WebPlayer.ts | 25 ++++++---- .../{_web => web}/assist/AnnotationCanvas.ts | 0 .../{_web => web}/assist/AssistManager.ts | 0 .../{_web => web}/assist/LocalStream.ts | 0 .../{_web => web}/managers/ActivityManager.ts | 2 +- .../{_web => web}/managers/DOM/DOMManager.ts | 11 ++-- .../managers/DOM/FocusManager.ts | 2 +- .../managers/DOM/StylesManager.ts | 50 +++---------------- .../{_web => web}/managers/DOM/VirtualDOM.ts | 0 .../managers/DOM/safeCSSRules.ts | 0 .../managers/MouseMoveManager.ts | 2 +- .../{_web => web}/managers/PagesManager.ts | 15 ++---- .../managers/PerformanceTrackManager.ts | 2 +- .../managers/WindowNodeCounter.ts | 0 .../messages/JSONRawMessageReader.ts | 0 .../{_web => web}/messages/MFileReader.ts | 0 .../{_web => web}/messages/MStreamReader.ts | 0 .../{_web => web}/messages/PrimitiveReader.ts | 0 .../messages/RawMessageReader.ts | 0 .../player/{_web => web}/messages/index.ts | 0 .../player/{_web => web}/messages/message.ts | 0 .../app/player/{_web => web}/messages/raw.ts | 0 .../player/{_web => web}/messages/timed.ts | 0 .../{_web => web}/messages/tracker-legacy.ts | 0 .../player/{_web => web}/messages/tracker.ts | 0 .../{_web => web}/messages/urlResolve.ts | 0 .../player/{_web => web}/network/crypto.ts | 0 .../player/{_web => web}/network/loadFiles.ts | 0 52 files changed, 91 insertions(+), 134 deletions(-) rename frontend/app/player/{_common => common}/ListWalker.ts (98%) rename frontend/app/player/{_common => common}/ListWalkerWithMarks.ts (94%) rename frontend/app/player/{_common => common}/SimpleStore.ts (85%) rename frontend/app/player/{player => common}/types.ts (87%) rename frontend/app/player/{_web => web}/InspectorController.ts (100%) rename frontend/app/player/{_web => web}/Lists.ts (94%) rename frontend/app/player/{_web => web}/MessageManager.ts (94%) rename frontend/app/player/{_web => web}/Screen/Cursor.ts (100%) rename frontend/app/player/{_web => web}/Screen/Inspector.ts (100%) rename frontend/app/player/{_web => web}/Screen/Marker.ts (100%) rename frontend/app/player/{_web => web}/Screen/Screen.ts (100%) rename frontend/app/player/{_web => web}/Screen/cursor.module.css (100%) rename frontend/app/player/{_web => web}/Screen/marker.module.css (100%) rename frontend/app/player/{_web => web}/Screen/screen.module.css (100%) rename frontend/app/player/{_web => web}/Screen/types.ts (100%) rename frontend/app/player/{_web => web}/TargetMarker.ts (98%) rename frontend/app/player/{_web => web}/WebLivePlayer.ts (100%) rename frontend/app/player/{_web => web}/WebPlayer.ts (87%) rename frontend/app/player/{_web => web}/assist/AnnotationCanvas.ts (100%) rename frontend/app/player/{_web => web}/assist/AssistManager.ts (100%) rename frontend/app/player/{_web => web}/assist/LocalStream.ts (100%) rename frontend/app/player/{_web => web}/managers/ActivityManager.ts (94%) rename frontend/app/player/{_web => web}/managers/DOM/DOMManager.ts (98%) rename frontend/app/player/{_web => web}/managers/DOM/FocusManager.ts (93%) rename frontend/app/player/{_web => web}/managers/DOM/StylesManager.ts (54%) rename frontend/app/player/{_web => web}/managers/DOM/VirtualDOM.ts (100%) rename frontend/app/player/{_web => web}/managers/DOM/safeCSSRules.ts (100%) rename frontend/app/player/{_web => web}/managers/MouseMoveManager.ts (96%) rename frontend/app/player/{_web => web}/managers/PagesManager.ts (81%) rename frontend/app/player/{_web => web}/managers/PerformanceTrackManager.ts (98%) rename frontend/app/player/{_web => web}/managers/WindowNodeCounter.ts (100%) rename frontend/app/player/{_web => web}/messages/JSONRawMessageReader.ts (100%) rename frontend/app/player/{_web => web}/messages/MFileReader.ts (100%) rename frontend/app/player/{_web => web}/messages/MStreamReader.ts (100%) rename frontend/app/player/{_web => web}/messages/PrimitiveReader.ts (100%) rename frontend/app/player/{_web => web}/messages/RawMessageReader.ts (100%) rename frontend/app/player/{_web => web}/messages/index.ts (100%) rename frontend/app/player/{_web => web}/messages/message.ts (100%) rename frontend/app/player/{_web => web}/messages/raw.ts (100%) rename frontend/app/player/{_web => web}/messages/timed.ts (100%) rename frontend/app/player/{_web => web}/messages/tracker-legacy.ts (100%) rename frontend/app/player/{_web => web}/messages/tracker.ts (100%) rename frontend/app/player/{_web => web}/messages/urlResolve.ts (100%) rename frontend/app/player/{_web => web}/network/crypto.ts (100%) rename frontend/app/player/{_web => web}/network/loadFiles.ts (100%) diff --git a/frontend/app/player/_singletone.ts b/frontend/app/player/_singletone.ts index c4e0d607b..2449542b6 100644 --- a/frontend/app/player/_singletone.ts +++ b/frontend/app/player/_singletone.ts @@ -1,9 +1,9 @@ -import WebPlayer from './_web/WebPlayer'; +import WebPlayer from './web/WebPlayer'; import reduxStore, {update, cleanStore} from './_store'; -import type { State as MMState } from './_web/MessageManager' +import type { State as MMState } from './web/MessageManager' import type { State as PState } from './player/Player' -import type { Store } from './player/types' +import type { Store } from './common/types' const myStore: Store = { diff --git a/frontend/app/player/_store/duck.js b/frontend/app/player/_store/duck.js index 6cf861ade..6996e8ed1 100644 --- a/frontend/app/player/_store/duck.js +++ b/frontend/app/player/_store/duck.js @@ -1,15 +1,11 @@ -import { applyChange, revertChange } from 'deep-diff'; - -import { INITIAL_STATE as MM_INITIAL_STATE } from '../_web/MessageManager' -import Player from '../player/Player' +import WebPlayer from '../web/WebPlayer' const UPDATE = 'player/UPDATE'; const CLEAN = 'player/CLEAN'; const REDUX = 'player/REDUX'; const resetState = { - ...MM_INITIAL_STATE, - ...Player.INITIAL_STATE, + ...WebPlayer.INITIAL_STATE, initialized: false, }; diff --git a/frontend/app/player/_store/index.js b/frontend/app/player/_store/index.js index cad6dfaff..c2a987661 100644 --- a/frontend/app/player/_store/index.js +++ b/frontend/app/player/_store/index.js @@ -1,4 +1,4 @@ -// export * from './connector'; -// export * from './store'; -// export * from './selectors'; -// export { default } from './store'; \ No newline at end of file +export * from './connector'; +export * from './store'; +export * from './selectors'; +export { default } from './store'; \ No newline at end of file diff --git a/frontend/app/player/_common/ListWalker.ts b/frontend/app/player/common/ListWalker.ts similarity index 98% rename from frontend/app/player/_common/ListWalker.ts rename to frontend/app/player/common/ListWalker.ts index 3600f31da..db847150d 100644 --- a/frontend/app/player/_common/ListWalker.ts +++ b/frontend/app/player/common/ListWalker.ts @@ -1,4 +1,4 @@ -import type { Timed } from '../_web/messages/timed'; +import type { Timed } from './types'; export default class ListWalker { private p = 0 diff --git a/frontend/app/player/_common/ListWalkerWithMarks.ts b/frontend/app/player/common/ListWalkerWithMarks.ts similarity index 94% rename from frontend/app/player/_common/ListWalkerWithMarks.ts rename to frontend/app/player/common/ListWalkerWithMarks.ts index 10446d7e0..a97378d03 100644 --- a/frontend/app/player/_common/ListWalkerWithMarks.ts +++ b/frontend/app/player/common/ListWalkerWithMarks.ts @@ -1,4 +1,4 @@ -import type { Timed } from '../_web/messages/timed'; +import type { Timed } from './types'; import ListWalker from './ListWalker' diff --git a/frontend/app/player/_common/SimpleStore.ts b/frontend/app/player/common/SimpleStore.ts similarity index 85% rename from frontend/app/player/_common/SimpleStore.ts rename to frontend/app/player/common/SimpleStore.ts index 2b4a77658..dd7e1ee7b 100644 --- a/frontend/app/player/_common/SimpleStore.ts +++ b/frontend/app/player/common/SimpleStore.ts @@ -1,5 +1,4 @@ - -import { Store } from '../player/types' +import { Store } from './types' // (not a type) export default class SimpleSore implements Store { diff --git a/frontend/app/player/player/types.ts b/frontend/app/player/common/types.ts similarity index 87% rename from frontend/app/player/player/types.ts rename to frontend/app/player/common/types.ts index 5deb56b06..0e5be2841 100644 --- a/frontend/app/player/player/types.ts +++ b/frontend/app/player/common/types.ts @@ -1,3 +1,6 @@ +export interface Timed { + time: number +} export interface Moveable { move(time: number): void @@ -18,4 +21,3 @@ export interface Store { update(state: Partial): void } - diff --git a/frontend/app/player/create.ts b/frontend/app/player/create.ts index 410ad6e2a..a69d9710e 100644 --- a/frontend/app/player/create.ts +++ b/frontend/app/player/create.ts @@ -1,7 +1,7 @@ -import SimpleStore from './_common/SimpleStore' -import type { Store } from './player/types' +import SimpleStore from './common/SimpleStore' +import type { Store } from './common/types' -import WebPlayer from './_web/WebPlayer' +import WebPlayer from './web/WebPlayer' type WebState = typeof WebPlayer.INITIAL_STATE //? type WebPlayerStore = Store diff --git a/frontend/app/player/index.ts b/frontend/app/player/index.ts index 55266aa5f..949088e43 100644 --- a/frontend/app/player/index.ts +++ b/frontend/app/player/index.ts @@ -1,9 +1,9 @@ -export * from './_web/assist/AssistManager'; -export * from './_web/assist/LocalStream'; -export * from './_web/WebPlayer'; +export * from './web/assist/AssistManager'; +export * from './web/assist/LocalStream'; +export * from './web/WebPlayer'; export * from './create'; -export type { MarkedTarget } from './_web/TargetMarker' +export type { MarkedTarget } from './web/TargetMarker' -//export * from './_store'; -//export * from './_singletone'; +export * from './_store'; +export * from './_singletone'; diff --git a/frontend/app/player/player/Animator.ts b/frontend/app/player/player/Animator.ts index a75290929..7f2e4c5bc 100644 --- a/frontend/app/player/player/Animator.ts +++ b/frontend/app/player/player/Animator.ts @@ -1,4 +1,4 @@ -import type { Store, Moveable, Interval } from './types'; +import type { Store, Moveable, Interval } from '../common/types'; import * as localStorage from './localStorage'; const fps = 60 diff --git a/frontend/app/player/player/Player.ts b/frontend/app/player/player/Player.ts index 9b2b98225..95a3c45de 100644 --- a/frontend/app/player/player/Player.ts +++ b/frontend/app/player/player/Player.ts @@ -1,6 +1,6 @@ import * as typedLocalStorage from './localStorage'; -import type { Moveable, Cleanable, Store } from './types'; +import type { Moveable, Cleanable, Store } from '../common/types'; import Animator from './Animator'; import type { GetState as AnimatorGetState, SetState as AnimatorSetState } from './Animator'; @@ -57,8 +57,6 @@ export default class Player extends Animator { } } - - /* === TODO: incapsulate in LSCache === */ toggleAutoplay() { @@ -67,14 +65,14 @@ export default class Player extends Animator { this.pState.update({ autoplay }) } - // Shouldn't it be in the react part as a fully (with localStorage-cache react hook)? + //TODO: move to react part (with localStorage-cache react hook)? toggleEvents() { const showEvents = !this.pState.get().showEvents localStorage.setItem(SHOW_EVENTS_STORAGE_KEY, `${showEvents}`); this.pState.update({ showEvents }) } - // move to React part? + // TODO: move to React part toggleSkipToIssue() { const skipToIssue = !this.pState.get().skipToIssue localStorage.setItem(SKIP_TO_ISSUE_STORAGE_KEY, `${skipToIssue}`); @@ -107,15 +105,6 @@ export default class Player extends Animator { } /* === === */ - // TODO: move theese to React hooks - // injectNotes(notes: Note[]) { - // update({ notes }) - // } - // filterOutNote(noteId: number) { - // const { notes } = getState() - // update({ notes: notes.filter((note: Note) => note.noteId !== noteId) }) - // } - clean() { this.pause() diff --git a/frontend/app/player/_web/InspectorController.ts b/frontend/app/player/web/InspectorController.ts similarity index 100% rename from frontend/app/player/_web/InspectorController.ts rename to frontend/app/player/web/InspectorController.ts diff --git a/frontend/app/player/_web/Lists.ts b/frontend/app/player/web/Lists.ts similarity index 94% rename from frontend/app/player/_web/Lists.ts rename to frontend/app/player/web/Lists.ts index fdf560c93..dc3a088fd 100644 --- a/frontend/app/player/_web/Lists.ts +++ b/frontend/app/player/web/Lists.ts @@ -1,5 +1,5 @@ -import ListWalker from '../_common/ListWalker'; -import ListWalkerWithMarks from '../_common/ListWalkerWithMarks'; +import ListWalker from '../common/ListWalker'; +import ListWalkerWithMarks from '../common/ListWalkerWithMarks'; import type { Message } from './messages' diff --git a/frontend/app/player/_web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts similarity index 94% rename from frontend/app/player/_web/MessageManager.ts rename to frontend/app/player/web/MessageManager.ts index b092483e2..83a8ea95a 100644 --- a/frontend/app/player/_web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -8,10 +8,8 @@ import Log from 'Types/session/log'; import { toast } from 'react-toastify'; -import type { Store } from '../player/types'; -import ListWalker from '../_common/ListWalker'; - -import Screen from './Screen/Screen'; +import type { Store } from '../common/types'; +import ListWalker from '../common/ListWalker'; import PagesManager from './managers/PagesManager'; import MouseMoveManager from './managers/MouseMoveManager'; @@ -24,15 +22,19 @@ import MFileReader from './messages/MFileReader'; import { loadFiles, requestEFSDom, requestEFSDevtools } from './network/loadFiles'; import { decryptSessionBytes } from './network/crypto'; -import { INITIAL_STATE as SCREEN_INITIAL_STATE, State as SuperState } from './Screen/Screen'; import Lists, { INITIAL_STATE as LISTS_INITIAL_STATE } from './Lists'; +import Screen, { + INITIAL_STATE as SCREEN_INITIAL_STATE, + State as ScreenState, +} from './Screen/Screen'; + import type { InitialLists } from './Lists' import type { PerformanceChartPoint } from './managers/PerformanceTrackManager'; import type { SkipInterval } from './managers/ActivityManager'; -export interface State extends SuperState { +export interface State extends ScreenState { performanceChartData: PerformanceChartPoint[], skipIntervals: SkipInterval[], connType?: string, @@ -73,7 +75,7 @@ const visualChanges = [ "set_viewport_scroll", ] -export default class MessageManager extends Screen { +export default class MessageManager { static INITIAL_STATE: State = { ...SCREEN_INITIAL_STATE, ...LISTS_INITIAL_STATE, @@ -116,12 +118,12 @@ export default class MessageManager extends Screen { constructor( private readonly session: any /*Session*/, - private readonly state: Store, + private readonly state: Store, + private readonly screen: Screen, initialLists?: Partial ) { - super(); - this.pagesManager = new PagesManager(this, this.session.isMobile) - this.mouseMoveManager = new MouseMoveManager(this) + this.pagesManager = new PagesManager(screen, this.session.isMobile, this) + this.mouseMoveManager = new MouseMoveManager(screen) this.sessionStart = this.session.startedAt @@ -281,8 +283,8 @@ export default class MessageManager extends Screen { this.performanceTrackManager = new PerformanceTrackManager() this.windowNodeCounter = new WindowNodeCounter(); - this.pagesManager = new PagesManager(this, this.session.isMobile) - this.mouseMoveManager = new MouseMoveManager(this); + this.pagesManager = new PagesManager(this.screen, this.session.isMobile, this) + this.mouseMoveManager = new MouseMoveManager(this.screen); this.activityManager = new ActivityManager(this.session.duration.milliseconds); } @@ -339,14 +341,14 @@ export default class MessageManager extends Screen { this.pagesManager.moveReady(t).then(() => { const lastScroll = this.scrollManager.moveGetLast(t, index); - if (!!lastScroll && this.window) { - this.window.scrollTo(lastScroll.x, lastScroll.y); + if (!!lastScroll && this.screen.window) { + this.screen.window.scrollTo(lastScroll.x, lastScroll.y); } // Moving mouse and setting :hover classes on ready view this.mouseMoveManager.move(t); const lastClick = this.clickManager.moveGetLast(t); if (!!lastClick && t - lastClick.time < 600) { // happend during last 600ms - this.cursor.click(); + this.screen.cursor.click(); } // After all changes - redraw the marker //this.marker.redraw(); @@ -514,17 +516,17 @@ export default class MessageManager extends Screen { } setMessagesLoading(messagesLoading: boolean) { - this.display(!messagesLoading); + this.screen.display(!messagesLoading); this.state.update({ messagesLoading }); } setCSSLoading(cssLoading: boolean) { - this.displayFrame(!cssLoading); + this.screen.displayFrame(!cssLoading); this.state.update({ cssLoading }); } private setSize({ height, width }: { height: number, width: number }) { - this.scale({ height, width }); + this.screen.scale({ height, width }); this.state.update({ width, height }); //this.updateMarketTargets() diff --git a/frontend/app/player/_web/Screen/Cursor.ts b/frontend/app/player/web/Screen/Cursor.ts similarity index 100% rename from frontend/app/player/_web/Screen/Cursor.ts rename to frontend/app/player/web/Screen/Cursor.ts diff --git a/frontend/app/player/_web/Screen/Inspector.ts b/frontend/app/player/web/Screen/Inspector.ts similarity index 100% rename from frontend/app/player/_web/Screen/Inspector.ts rename to frontend/app/player/web/Screen/Inspector.ts diff --git a/frontend/app/player/_web/Screen/Marker.ts b/frontend/app/player/web/Screen/Marker.ts similarity index 100% rename from frontend/app/player/_web/Screen/Marker.ts rename to frontend/app/player/web/Screen/Marker.ts diff --git a/frontend/app/player/_web/Screen/Screen.ts b/frontend/app/player/web/Screen/Screen.ts similarity index 100% rename from frontend/app/player/_web/Screen/Screen.ts rename to frontend/app/player/web/Screen/Screen.ts diff --git a/frontend/app/player/_web/Screen/cursor.module.css b/frontend/app/player/web/Screen/cursor.module.css similarity index 100% rename from frontend/app/player/_web/Screen/cursor.module.css rename to frontend/app/player/web/Screen/cursor.module.css diff --git a/frontend/app/player/_web/Screen/marker.module.css b/frontend/app/player/web/Screen/marker.module.css similarity index 100% rename from frontend/app/player/_web/Screen/marker.module.css rename to frontend/app/player/web/Screen/marker.module.css diff --git a/frontend/app/player/_web/Screen/screen.module.css b/frontend/app/player/web/Screen/screen.module.css similarity index 100% rename from frontend/app/player/_web/Screen/screen.module.css rename to frontend/app/player/web/Screen/screen.module.css diff --git a/frontend/app/player/_web/Screen/types.ts b/frontend/app/player/web/Screen/types.ts similarity index 100% rename from frontend/app/player/_web/Screen/types.ts rename to frontend/app/player/web/Screen/types.ts diff --git a/frontend/app/player/_web/TargetMarker.ts b/frontend/app/player/web/TargetMarker.ts similarity index 98% rename from frontend/app/player/_web/TargetMarker.ts rename to frontend/app/player/web/TargetMarker.ts index a2ca99cc2..f3b4d1d7d 100644 --- a/frontend/app/player/_web/TargetMarker.ts +++ b/frontend/app/player/web/TargetMarker.ts @@ -1,6 +1,6 @@ import type Screen from './Screen/Screen' import type { Point } from './Screen/types' -import type { Store } from '../player/types' +import type { Store } from '../common/types' function getOffset(el: Element, innerWindow: Window) { diff --git a/frontend/app/player/_web/WebLivePlayer.ts b/frontend/app/player/web/WebLivePlayer.ts similarity index 100% rename from frontend/app/player/_web/WebLivePlayer.ts rename to frontend/app/player/web/WebLivePlayer.ts diff --git a/frontend/app/player/_web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts similarity index 87% rename from frontend/app/player/_web/WebPlayer.ts rename to frontend/app/player/web/WebPlayer.ts index 572c9e5b4..ec132d5f8 100644 --- a/frontend/app/player/_web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -1,4 +1,4 @@ -import type { Store } from '../player/types' +import type { Store } from '../common/types' import Player, { State as PlayerState } from '../player/Player' import MessageManager from './MessageManager' @@ -39,17 +39,13 @@ export default class WebPlayer extends Player { exceptions: session.errors, } : {} - // TODO: separate screen from manager - const screen = new MessageManager(session, wpState, initialLists) - super(wpState, screen) + const screen = new Screen() + const messageManager = new MessageManager(session, wpState, screen, initialLists) + super(wpState, messageManager) this.screen = screen - this.messageManager = screen - - // TODO: separate LiveWebPlayer - this.assistManager = new AssistManager(session, this.messageManager, config, wpState) + this.messageManager = messageManager this.targetMarker = new TargetMarker(this.screen, wpState) - this.inspectorController = new InspectorController(screen) @@ -65,6 +61,8 @@ export default class WebPlayer extends Player { endTime, // : 0, //TODO: through initialState }) + // TODO: separate LiveWebPlayer + this.assistManager = new AssistManager(session, this.messageManager, config, wpState) if (live) { this.assistManager.connect(session.agentToken) } @@ -123,6 +121,15 @@ export default class WebPlayer extends Player { } } + // TODO: restore notes functionality + // injectNotes(notes: Note[]) { + // update({ notes }) + // } + // filterOutNote(noteId: number) { + // const { notes } = getState() + // update({ notes: notes.filter((note: Note) => note.noteId !== noteId) }) + // } + toggleUserName(name?: string) { this.screen.cursor.showTag(name) } diff --git a/frontend/app/player/_web/assist/AnnotationCanvas.ts b/frontend/app/player/web/assist/AnnotationCanvas.ts similarity index 100% rename from frontend/app/player/_web/assist/AnnotationCanvas.ts rename to frontend/app/player/web/assist/AnnotationCanvas.ts diff --git a/frontend/app/player/_web/assist/AssistManager.ts b/frontend/app/player/web/assist/AssistManager.ts similarity index 100% rename from frontend/app/player/_web/assist/AssistManager.ts rename to frontend/app/player/web/assist/AssistManager.ts diff --git a/frontend/app/player/_web/assist/LocalStream.ts b/frontend/app/player/web/assist/LocalStream.ts similarity index 100% rename from frontend/app/player/_web/assist/LocalStream.ts rename to frontend/app/player/web/assist/LocalStream.ts diff --git a/frontend/app/player/_web/managers/ActivityManager.ts b/frontend/app/player/web/managers/ActivityManager.ts similarity index 94% rename from frontend/app/player/_web/managers/ActivityManager.ts rename to frontend/app/player/web/managers/ActivityManager.ts index d5e81f62d..383f29c15 100644 --- a/frontend/app/player/_web/managers/ActivityManager.ts +++ b/frontend/app/player/web/managers/ActivityManager.ts @@ -1,4 +1,4 @@ -import ListWalker from '../../_common/ListWalker'; +import ListWalker from '../../common/ListWalker'; class SkipIntervalCls { diff --git a/frontend/app/player/_web/managers/DOM/DOMManager.ts b/frontend/app/player/web/managers/DOM/DOMManager.ts similarity index 98% rename from frontend/app/player/_web/managers/DOM/DOMManager.ts rename to frontend/app/player/web/managers/DOM/DOMManager.ts index 370df5c03..708129add 100644 --- a/frontend/app/player/_web/managers/DOM/DOMManager.ts +++ b/frontend/app/player/web/managers/DOM/DOMManager.ts @@ -1,9 +1,11 @@ import logger from 'App/logger'; +import type Screen from '../../Screen/Screen'; import type MessageManager from '../../MessageManager'; + import type { Message, SetNodeScroll, CreateElementNode } from '../../messages'; -import ListWalker from '../../../_common/ListWalker'; +import ListWalker from '../../../common/ListWalker'; import StylesManager, { rewriteNodeStyleSheet } from './StylesManager'; import FocusManager from './FocusManager'; import { @@ -51,12 +53,13 @@ export default class DOMManager extends ListWalker { constructor( - private readonly screen: MessageManager, + private readonly screen: Screen, private readonly isMobile: boolean, - public readonly time: number + public readonly time: number, + mm: MessageManager, ) { super() - this.stylesManager = new StylesManager(screen) + this.stylesManager = new StylesManager(screen, mm) } append(m: Message): void { diff --git a/frontend/app/player/_web/managers/DOM/FocusManager.ts b/frontend/app/player/web/managers/DOM/FocusManager.ts similarity index 93% rename from frontend/app/player/_web/managers/DOM/FocusManager.ts rename to frontend/app/player/web/managers/DOM/FocusManager.ts index 629f625e3..174335473 100644 --- a/frontend/app/player/_web/managers/DOM/FocusManager.ts +++ b/frontend/app/player/web/managers/DOM/FocusManager.ts @@ -1,7 +1,7 @@ import logger from 'App/logger'; import type { SetNodeFocus } from '../../messages'; import type { VElement } from './VirtualDOM'; -import ListWalker from '../../../_common/ListWalker'; +import ListWalker from '../../../common/ListWalker'; const FOCUS_CLASS = "-openreplay-focus" diff --git a/frontend/app/player/_web/managers/DOM/StylesManager.ts b/frontend/app/player/web/managers/DOM/StylesManager.ts similarity index 54% rename from frontend/app/player/_web/managers/DOM/StylesManager.ts rename to frontend/app/player/web/managers/DOM/StylesManager.ts index 1cb49c5dd..5cffa9be9 100644 --- a/frontend/app/player/_web/managers/DOM/StylesManager.ts +++ b/frontend/app/player/web/managers/DOM/StylesManager.ts @@ -1,11 +1,9 @@ -import type MessageManager from '../../MessageManager'; +import type Screen from '../../Screen/Screen'; +import type MessageManager from '../../MessageManager' import type { CssInsertRule, CssDeleteRule } from '../../messages'; type CSSRuleMessage = CssInsertRule | CssDeleteRule; -import ListWalker from '../../../_common/ListWalker'; - - const HOVER_CN = "-openreplay-hover"; const HOVER_SELECTOR = `.${HOVER_CN}`; @@ -21,17 +19,14 @@ export function rewriteNodeStyleSheet(doc: Document, node: HTMLLinkElement | HTM } } -export default class StylesManager extends ListWalker { +export default class StylesManager { private linkLoadingCount: number = 0; private linkLoadPromises: Array> = []; private skipCSSLinks: Array = []; // should be common for all pages - constructor(private readonly screen: MessageManager) { - super(); - } + constructor(private readonly screen: Screen, private readonly mm: MessageManager) {} reset():void { - super.reset(); this.linkLoadingCount = 0; this.linkLoadPromises = []; @@ -43,7 +38,7 @@ export default class StylesManager extends ListWalker { const promise = new Promise((resolve) => { if (this.skipCSSLinks.includes(value)) resolve(); this.linkLoadingCount++; - this.screen.setCSSLoading(true); + this.mm.setCSSLoading(true); const addSkipAndResolve = () => { this.skipCSSLinks.push(value); // watch out resolve() @@ -62,44 +57,13 @@ export default class StylesManager extends ListWalker { clearTimeout(timeoutId); this.linkLoadingCount--; if (this.linkLoadingCount === 0) { - this.screen.setCSSLoading(false); + this.mm.setCSSLoading(false); } }); this.linkLoadPromises.push(promise); } - private manageRule = (msg: CSSRuleMessage):void => { - // if (msg.tp === "css_insert_rule") { - // let styleSheet = this.#screen.document.styleSheets[ msg.stylesheetID ]; - // if (!styleSheet) { - // logger.log("No stylesheet with corresponding ID found: ", msg) - // styleSheet = this.#screen.document.styleSheets[0]; - // if (!styleSheet) { - // return; - // } - // } - // try { - // styleSheet.insertRule(msg.rule, msg.index); - // } catch (e) { - // logger.log(e, msg) - // //const index = Math.min(msg.index, styleSheet.cssRules.length); - // styleSheet.insertRule(msg.rule, styleSheet.cssRules.length); - // //styleSheet.ownerNode.innerHTML += msg.rule; - // } - // } - // if (msg.tp === "css_delete_rule") { - // // console.warn('Warning: STYLESHEET_DELETE_RULE msg') - // const styleSheet = this.#screen.document.styleSheets[msg.stylesheetID]; - // if (!styleSheet) { - // logger.log("No stylesheet with corresponding ID found: ", msg) - // return; - // } - // styleSheet.deleteRule(msg.index); - // } - } - - moveReady(t: number): Promise { + moveReady(t: number): Promise { return Promise.all(this.linkLoadPromises) - .then(() => this.moveApply(t, this.manageRule)); } } diff --git a/frontend/app/player/_web/managers/DOM/VirtualDOM.ts b/frontend/app/player/web/managers/DOM/VirtualDOM.ts similarity index 100% rename from frontend/app/player/_web/managers/DOM/VirtualDOM.ts rename to frontend/app/player/web/managers/DOM/VirtualDOM.ts diff --git a/frontend/app/player/_web/managers/DOM/safeCSSRules.ts b/frontend/app/player/web/managers/DOM/safeCSSRules.ts similarity index 100% rename from frontend/app/player/_web/managers/DOM/safeCSSRules.ts rename to frontend/app/player/web/managers/DOM/safeCSSRules.ts diff --git a/frontend/app/player/_web/managers/MouseMoveManager.ts b/frontend/app/player/web/managers/MouseMoveManager.ts similarity index 96% rename from frontend/app/player/_web/managers/MouseMoveManager.ts rename to frontend/app/player/web/managers/MouseMoveManager.ts index 63b5ca895..ea08e1bc9 100644 --- a/frontend/app/player/_web/managers/MouseMoveManager.ts +++ b/frontend/app/player/web/managers/MouseMoveManager.ts @@ -2,7 +2,7 @@ import type Screen from '../Screen/Screen' import type { Point } from '../Screen/types' import type { MouseMove } from '../messages' -import ListWalker from '../../_common/ListWalker' +import ListWalker from '../../common/ListWalker' const HOVER_CLASS = "-openreplay-hover"; const HOVER_CLASS_DEPR = "-asayer-hover"; diff --git a/frontend/app/player/_web/managers/PagesManager.ts b/frontend/app/player/web/managers/PagesManager.ts similarity index 81% rename from frontend/app/player/_web/managers/PagesManager.ts rename to frontend/app/player/web/managers/PagesManager.ts index 6a3f20f7c..b4ddbe28b 100644 --- a/frontend/app/player/_web/managers/PagesManager.ts +++ b/frontend/app/player/web/managers/PagesManager.ts @@ -1,28 +1,23 @@ import type Screen from '../Screen/Screen'; import type { Message } from '../messages'; +import type MessageManager from '../MessageManager'; -import ListWalker from '../../_common/ListWalker'; + +import ListWalker from '../../common/ListWalker'; import DOMManager from './DOM/DOMManager'; export default class PagesManager extends ListWalker { private currentPage: DOMManager | null = null - private isMobile: boolean; - private screen: Screen; - - constructor(screen: Screen, isMobile: boolean) { - super() - this.screen = screen - this.isMobile = isMobile - } + constructor(private screen: Screen, private isMobile: boolean, private mm: MessageManager) { super() } /* Assumed that messages added in a correct time sequence. */ appendMessage(m: Message): void { if (m.tp === "create_document") { - super.append(new DOMManager(this.screen, this.isMobile, m.time)) + super.append(new DOMManager(this.screen, this.isMobile, m.time, this.mm)) } if (this.last === null) { // Log wrong diff --git a/frontend/app/player/_web/managers/PerformanceTrackManager.ts b/frontend/app/player/web/managers/PerformanceTrackManager.ts similarity index 98% rename from frontend/app/player/_web/managers/PerformanceTrackManager.ts rename to frontend/app/player/web/managers/PerformanceTrackManager.ts index 424466b4a..98ce2c0b9 100644 --- a/frontend/app/player/_web/managers/PerformanceTrackManager.ts +++ b/frontend/app/player/web/managers/PerformanceTrackManager.ts @@ -1,6 +1,6 @@ import type { PerformanceTrack, SetPageVisibility } from '../messages'; -import ListWalker from '../../_common/ListWalker'; +import ListWalker from '../../common/ListWalker'; export type PerformanceChartPoint = { time: number, diff --git a/frontend/app/player/_web/managers/WindowNodeCounter.ts b/frontend/app/player/web/managers/WindowNodeCounter.ts similarity index 100% rename from frontend/app/player/_web/managers/WindowNodeCounter.ts rename to frontend/app/player/web/managers/WindowNodeCounter.ts diff --git a/frontend/app/player/_web/messages/JSONRawMessageReader.ts b/frontend/app/player/web/messages/JSONRawMessageReader.ts similarity index 100% rename from frontend/app/player/_web/messages/JSONRawMessageReader.ts rename to frontend/app/player/web/messages/JSONRawMessageReader.ts diff --git a/frontend/app/player/_web/messages/MFileReader.ts b/frontend/app/player/web/messages/MFileReader.ts similarity index 100% rename from frontend/app/player/_web/messages/MFileReader.ts rename to frontend/app/player/web/messages/MFileReader.ts diff --git a/frontend/app/player/_web/messages/MStreamReader.ts b/frontend/app/player/web/messages/MStreamReader.ts similarity index 100% rename from frontend/app/player/_web/messages/MStreamReader.ts rename to frontend/app/player/web/messages/MStreamReader.ts diff --git a/frontend/app/player/_web/messages/PrimitiveReader.ts b/frontend/app/player/web/messages/PrimitiveReader.ts similarity index 100% rename from frontend/app/player/_web/messages/PrimitiveReader.ts rename to frontend/app/player/web/messages/PrimitiveReader.ts diff --git a/frontend/app/player/_web/messages/RawMessageReader.ts b/frontend/app/player/web/messages/RawMessageReader.ts similarity index 100% rename from frontend/app/player/_web/messages/RawMessageReader.ts rename to frontend/app/player/web/messages/RawMessageReader.ts diff --git a/frontend/app/player/_web/messages/index.ts b/frontend/app/player/web/messages/index.ts similarity index 100% rename from frontend/app/player/_web/messages/index.ts rename to frontend/app/player/web/messages/index.ts diff --git a/frontend/app/player/_web/messages/message.ts b/frontend/app/player/web/messages/message.ts similarity index 100% rename from frontend/app/player/_web/messages/message.ts rename to frontend/app/player/web/messages/message.ts diff --git a/frontend/app/player/_web/messages/raw.ts b/frontend/app/player/web/messages/raw.ts similarity index 100% rename from frontend/app/player/_web/messages/raw.ts rename to frontend/app/player/web/messages/raw.ts diff --git a/frontend/app/player/_web/messages/timed.ts b/frontend/app/player/web/messages/timed.ts similarity index 100% rename from frontend/app/player/_web/messages/timed.ts rename to frontend/app/player/web/messages/timed.ts diff --git a/frontend/app/player/_web/messages/tracker-legacy.ts b/frontend/app/player/web/messages/tracker-legacy.ts similarity index 100% rename from frontend/app/player/_web/messages/tracker-legacy.ts rename to frontend/app/player/web/messages/tracker-legacy.ts diff --git a/frontend/app/player/_web/messages/tracker.ts b/frontend/app/player/web/messages/tracker.ts similarity index 100% rename from frontend/app/player/_web/messages/tracker.ts rename to frontend/app/player/web/messages/tracker.ts diff --git a/frontend/app/player/_web/messages/urlResolve.ts b/frontend/app/player/web/messages/urlResolve.ts similarity index 100% rename from frontend/app/player/_web/messages/urlResolve.ts rename to frontend/app/player/web/messages/urlResolve.ts diff --git a/frontend/app/player/_web/network/crypto.ts b/frontend/app/player/web/network/crypto.ts similarity index 100% rename from frontend/app/player/_web/network/crypto.ts rename to frontend/app/player/web/network/crypto.ts diff --git a/frontend/app/player/_web/network/loadFiles.ts b/frontend/app/player/web/network/loadFiles.ts similarity index 100% rename from frontend/app/player/_web/network/loadFiles.ts rename to frontend/app/player/web/network/loadFiles.ts From b00bc4fe9ae3c718695d2090bce82127c4e1e5b0 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 22 Nov 2022 17:29:10 +0100 Subject: [PATCH 16/64] refactor(ui/player): refactor notes popup, time comp --- .../components/Session_/Player/Controls/Time.js | 13 ++++++++----- .../Session_/components/NotePopup.tsx | 17 ++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/frontend/app/components/Session_/Player/Controls/Time.js b/frontend/app/components/Session_/Player/Controls/Time.js index e8221b5b8..ab6f5e6b8 100644 --- a/frontend/app/components/Session_/Player/Controls/Time.js +++ b/frontend/app/components/Session_/Player/Controls/Time.js @@ -1,7 +1,8 @@ import React from 'react'; import { Duration } from 'luxon'; -import { connectPlayer } from 'Player'; import styles from './time.module.css'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; const Time = ({ time, isCustom, format = 'm:ss', }) => (
@@ -11,10 +12,12 @@ const Time = ({ time, isCustom, format = 'm:ss', }) => ( Time.displayName = "Time"; -const ReduxTime = connectPlayer((state, { name, format }) => ({ - time: state[ name ], - format, -}))(Time); +const ReduxTime = observer(({ format, name }) => { + const { store } = React.useContext(PlayerContext) + const time = store.get()[name] + + return
); From 5b97a0c3cf1755b40167b00ae703c6da61593356 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 25 Nov 2022 11:02:34 +0100 Subject: [PATCH 21/64] refactor(ui/player): refactor more components --- .../RequestingWindow/RequestingWindow.tsx | 28 +- .../AssistActions/AssistActions.tsx | 45 +- frontend/app/components/Session/LivePlayer.js | 42 +- frontend/app/components/Session/WebPlayer.tsx | 2 +- .../Session_/EventsBlock/NoteEvent.tsx | 2 +- .../Session_/Exceptions/Exceptions.js | 12 - .../Session_/OverviewPanel/OverviewPanel.tsx | 2 +- .../components/SelectorCard/SelectorCard.tsx | 8 +- .../Session_/Player/Controls/ControlButton.js | 2 +- .../Session_/Player/Controls/Controls.js | 434 ------------------ .../Session_/Player/Controls/Controls.tsx | 420 +++++++++++++++++ .../Session_/Player/Controls/Time.js | 20 +- .../Controls/components/PlayerControls.tsx | 8 +- .../components/Session_/PlayerBlockHeader.js | 169 ------- .../components/Session_/PlayerBlockHeader.tsx | 161 +++++++ .../components/Session_/Storage/DiffRow.tsx | 4 +- .../components/Session_/Storage/Storage.js | 321 ------------- .../components/Session_/Storage/Storage.tsx | 315 +++++++++++++ .../Session_/components/NotePopup.tsx | 8 +- .../shared/GuidePopup/GuidePopup.tsx | 4 +- .../app/components/ui/Tooltip/Tooltip.tsx | 6 +- frontend/app/player/player/Animator.ts | 22 +- .../app/player/web/InspectorController.ts | 10 +- frontend/app/player/web/Screen/Inspector.ts | 10 +- frontend/app/player/web/WebPlayer.ts | 14 +- .../app/player/web/assist/AssistManager.ts | 4 +- 26 files changed, 1039 insertions(+), 1034 deletions(-) delete mode 100644 frontend/app/components/Session_/Player/Controls/Controls.js create mode 100644 frontend/app/components/Session_/Player/Controls/Controls.tsx delete mode 100644 frontend/app/components/Session_/PlayerBlockHeader.js create mode 100644 frontend/app/components/Session_/PlayerBlockHeader.tsx delete mode 100644 frontend/app/components/Session_/Storage/Storage.js create mode 100644 frontend/app/components/Session_/Storage/Storage.tsx diff --git a/frontend/app/components/Assist/RequestingWindow/RequestingWindow.tsx b/frontend/app/components/Assist/RequestingWindow/RequestingWindow.tsx index 6d702acdb..3c0fce497 100644 --- a/frontend/app/components/Assist/RequestingWindow/RequestingWindow.tsx +++ b/frontend/app/components/Assist/RequestingWindow/RequestingWindow.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { INDEXES } from 'App/constants/zindex'; import { connect } from 'react-redux'; import { Button, Loader, Icon } from 'UI'; -import { initiateCallEnd, releaseRemoteControl } from 'Player'; +import { PlayerContext } from 'App/components/Session/playerContext'; interface Props { userDisplayName: string; @@ -14,20 +14,38 @@ export enum WindowType { Control, } +enum Actions { + CallEnd, + ControlEnd +} + const WIN_VARIANTS = { [WindowType.Call]: { text: 'to accept the call', icon: 'call' as const, - action: initiateCallEnd, + action: Actions.CallEnd, }, [WindowType.Control]: { text: 'to accept remote control request', icon: 'remote-control' as const, - action: releaseRemoteControl, + action: Actions.ControlEnd, }, }; function RequestingWindow({ userDisplayName, type }: Props) { + const { player } = React.useContext(PlayerContext) + + const { + assistManager: { + initiateCallEnd, + releaseRemoteControl, + } + } = player + + const actions = { + [Actions.CallEnd]: initiateCallEnd, + [Actions.ControlEnd]: releaseRemoteControl + } return (
{WIN_VARIANTS[type].text} -
@@ -48,6 +66,6 @@ function RequestingWindow({ userDisplayName, type }: Props) { ); } -export default connect((state) => ({ +export default connect((state: any) => ({ userDisplayName: state.getIn(['sessions', 'current', 'userDisplayName']), }))(RequestingWindow); diff --git a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx index e377cd3ba..35afb6721 100644 --- a/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx +++ b/frontend/app/components/Assist/components/AssistActions/AssistActions.tsx @@ -3,15 +3,8 @@ import { Button, Tooltip } from 'UI'; import { connect } from 'react-redux'; import cn from 'classnames'; import { toggleChatWindow } from 'Duck/sessions'; -import { connectPlayer } from 'Player'; import ChatWindow from '../../ChatWindow'; -import { - callPeer, - setCallArgs, - requestReleaseRemoteControl, - toggleAnnotation, - toggleUserName, -} from 'Player'; +// state enums import { CallingState, ConnectionStatus, @@ -19,6 +12,8 @@ import { RequestLocalStream, } from 'Player'; import type { LocalStream } from 'Player'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; import { toast } from 'react-toastify'; import { confirm } from 'UI'; import stl from './AassistActions.module.css'; @@ -48,17 +43,31 @@ interface Props { function AssistActions({ userId, - calling, - annotating, - peerConnectionStatus, - remoteControlStatus, hasPermission, isEnterprise, isCallActive, agentIds, - livePlay, userDisplayName, }: Props) { + const { player, store } = React.useContext(PlayerContext) + + const { + assistManager: { + call: callPeer, + setCallArgs, + requestReleaseRemoteControl, + toggleAnnotation, + }, + toggleUserName, + } = player + const { + calling, + annotating, + peerConnectionStatus, + remoteControl: remoteControlStatus, + livePlay, + } = store.get() + const [isPrestart, setPrestart] = useState(false); const [incomeStream, setIncomeStream] = useState([]); const [localStream, setLocalStream] = useState(null); @@ -236,7 +245,7 @@ function AssistActions({ } const con = connect( - (state) => { + (state: any) => { const permissions = state.getIn(['user', 'account', 'permissions']) || []; return { hasPermission: permissions.includes('ASSIST_CALL'), @@ -248,11 +257,5 @@ const con = connect( ); export default con( - connectPlayer((state) => ({ - calling: state.calling, - annotating: state.annotating, - remoteControlStatus: state.remoteControl, - peerConnectionStatus: state.peerConnectionStatus, - livePlay: state.livePlay, - }))(AssistActions) + observer(AssistActions) ); diff --git a/frontend/app/components/Session/LivePlayer.js b/frontend/app/components/Session/LivePlayer.js index 7e0f09145..2168d468f 100644 --- a/frontend/app/components/Session/LivePlayer.js +++ b/frontend/app/components/Session/LivePlayer.js @@ -1,25 +1,20 @@ import React from 'react'; import { useEffect, useState } from 'react'; import { connect } from 'react-redux'; -import { Loader } from 'UI'; import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; import { withRequest } from 'HOCs' import { PlayerProvider, - connectPlayer, - init as initPlayer, - clean as cleanPlayer, } from 'Player'; import withPermissions from 'HOCs/withPermissions'; +import { PlayerContext, defaultContextValue } from './playerContext'; +import { createLiveWebPlayer } from 'Player'; +import { makeAutoObservable } from 'mobx'; import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; import PlayerBlock from '../Session_/PlayerBlock'; import styles from '../Session_/session.module.css'; -const InitLoader = connectPlayer(state => ({ - loading: !state.initialized -}))(Loader); - function LivePlayer ({ session, toggleFullscreen, @@ -32,6 +27,8 @@ function LivePlayer ({ userEmail, userName }) { + const [contextValue, setContextValue] = useState(defaultContextValue); + useEffect(() => { if (!loadingCredentials) { @@ -42,9 +39,16 @@ function LivePlayer ({ name: userName, }, } - initPlayer(sessionWithAgentData, assistCredendials, true); + // initPlayer(sessionWithAgentData, assistCredendials, true); + const [LivePlayer, LivePlayerStore] = createLiveWebPlayer( + sessionWithAgentData, + assistCredendials, + (state) => makeAutoObservable(state) + ) + setContextValue({ player: LivePlayer, store: LivePlayerStore }); + } - return () => cleanPlayer() + return () => LivePlayer.clean() }, [ session.sessionId, loadingCredentials, assistCredendials ]); // LAYOUT (TODO: local layout state - useContext or something..) @@ -64,15 +68,17 @@ function LivePlayer ({ } const [activeTab, setActiveTab] = useState(''); + if (!contextValue.player) return null; + return ( - - - -
- -
-
-
+ + + +
+ +
+
+
); }; diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index 480ca0530..d7e8a115b 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -5,7 +5,6 @@ import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; import { fetchList } from 'Duck/integrations'; import { PlayerProvider, createWebPlayer } from 'Player'; import { makeAutoObservable } from 'mobx'; -import { observer } from 'mobx-react-lite'; import withLocationHandlers from 'HOCs/withLocationHandlers'; import { useStore } from 'App/mstore'; import PlayerBlockHeader from '../Session_/PlayerBlockHeader'; @@ -85,6 +84,7 @@ function WebPlayer(props: any) { <> filterRE.test(e.name) || filterRE.test(e.message)); - // let lastIndex = -1; - // filtered.forEach((item, index) => { - // if ( - // this.props.exceptionsNow.length > 0 && - // item.time <= this.props.exceptionsNow[this.props.exceptionsNow.length - 1].time - // ) { - // lastIndex = index; - // } - // }); - return ( <> jump(e.time)} error={e} key={e.key} - // selected={lastIndex === index} - // inactive={index > lastIndex} onErrorClick={(jsEvent) => { jsEvent.stopPropagation(); jsEvent.preventDefault(); diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index 73bd27cd8..667f37d5b 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -38,7 +38,7 @@ function OverviewPanel({ issuesList }: { issuesList: any[] }) { const resourceList = resourceListUnmap .filter((r: any) => r.isRed() || r.isYellow()) .concat(fetchList.filter((i: any) => parseInt(i.status) >= 400)) - .concat(graphqlList.filter((i: any) => parseInt(i.status) >= 400)), + .concat(graphqlList.filter((i: any) => parseInt(i.status) >= 400)) const resources: any = React.useMemo(() => { return { diff --git a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx index 999dae866..39de770b7 100644 --- a/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx +++ b/frontend/app/components/Session_/PageInsightsPanel/components/SelectorCard/SelectorCard.tsx @@ -1,9 +1,9 @@ -import React, { useState } from 'react'; +import React from 'react'; import stl from './SelectorCard.module.css'; import cn from 'classnames'; import type { MarkedTarget } from 'Player'; -import { activeTarget } from 'Player'; import { Tooltip } from 'react-tippy'; +import { PlayerContext } from 'App/components/Session/playerContext'; interface Props { index?: number; @@ -12,7 +12,11 @@ interface Props { } export default function SelectorCard({ index = 1, target, showContent }: Props) { + const { player } = React.useContext(PlayerContext) + const activeTarget = player.setActiveTarget + return ( + // @ts-ignore TODO for Alex
activeTarget(index)}>
{/* @ts-ignore */} diff --git a/frontend/app/components/Session_/Player/Controls/ControlButton.js b/frontend/app/components/Session_/Player/Controls/ControlButton.js index 3c42895b6..1d6391f1f 100644 --- a/frontend/app/components/Session_/Player/Controls/ControlButton.js +++ b/frontend/app/components/Session_/Player/Controls/ControlButton.js @@ -12,7 +12,7 @@ const ControlButton = ({ hasErrors = false, active = false, size = 20, - noLabel, + noLabel = false, labelClassName, containerClassName, noIcon, diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.js deleted file mode 100644 index e0114926e..000000000 --- a/frontend/app/components/Session_/Player/Controls/Controls.js +++ /dev/null @@ -1,434 +0,0 @@ -import React from 'react'; -import cn from 'classnames'; -import { connect } from 'react-redux'; -import { - connectPlayer, - STORAGE_TYPES, - selectStorageType, - selectStorageListNow, - jumpToLive, - toggleInspectorMode, -} from 'Player'; -import LiveTag from 'Shared/LiveTag'; - -import { Icon, Tooltip } from 'UI'; -import { - fullscreenOn, - fullscreenOff, - toggleBottomBlock, - changeSkipInterval, - OVERVIEW, - CONSOLE, - NETWORK, - STACKEVENTS, - STORAGE, - PROFILER, - PERFORMANCE, - GRAPHQL, - INSPECTOR, -} from 'Duck/components/player'; -import { AssistDuration } from './Time'; -import Timeline from './Timeline'; -import ControlButton from './ControlButton'; -import PlayerControls from './components/PlayerControls'; - -import styles from './controls.module.css'; -import XRayButton from 'Shared/XRayButton'; - -const SKIP_INTERVALS = { - 2: 2e3, - 5: 5e3, - 10: 1e4, - 15: 15e3, - 20: 2e4, - 30: 3e4, - 60: 6e4, -}; - -function getStorageName(type) { - switch (type) { - case STORAGE_TYPES.REDUX: - return 'REDUX'; - case STORAGE_TYPES.MOBX: - return 'MOBX'; - case STORAGE_TYPES.VUEX: - return 'VUEX'; - case STORAGE_TYPES.NGRX: - return 'NGRX'; - case STORAGE_TYPES.ZUSTAND: - return 'ZUSTAND'; - case STORAGE_TYPES.NONE: - return 'STATE'; - } -} - -@connectPlayer((state) => ({ - time: state.time, - endTime: state.endTime, - live: state.live, - livePlay: state.livePlay, - playing: state.playing, - completed: state.completed, - skip: state.skip, - skipToIssue: state.skipToIssue, - speed: state.speed, - disabled: state.cssLoading || state.messagesLoading || state.inspectorMode || state.markedTargets, - inspectorMode: state.inspectorMode, - fullscreenDisabled: state.messagesLoading, - // logCount: state.logList.length, - logRedCount: state.logRedCount, - showExceptions: state.exceptionsList.length > 0, - resourceRedCount: state.resourceRedCount, - fetchRedCount: state.fetchRedCount, - showStack: state.stackList.length > 0, - stackCount: state.stackList.length, - stackRedCount: state.stackRedCount, - profilesCount: state.profilesList.length, - storageCount: selectStorageListNow(state).length, - storageType: selectStorageType(state), - showStorage: selectStorageType(state) !== STORAGE_TYPES.NONE, - showProfiler: state.profilesList.length > 0, - showGraphql: state.graphqlList.length > 0, - showFetch: state.fetchCount > 0, - fetchCount: state.fetchCount, - graphqlCount: state.graphqlList.length, - liveTimeTravel: state.liveTimeTravel, -})) -@connect( - (state, props) => { - const permissions = state.getIn(['user', 'account', 'permissions']) || []; - const isEnterprise = state.getIn(['user', 'account', 'edition']) === 'ee'; - return { - disabled: props.disabled || (isEnterprise && !permissions.includes('DEV_TOOLS')), - fullscreen: state.getIn(['components', 'player', 'fullscreen']), - bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), - showStorage: - props.showStorage || !state.getIn(['components', 'player', 'hiddenHints', 'storage']), - showStack: props.showStack || !state.getIn(['components', 'player', 'hiddenHints', 'stack']), - closedLive: - !!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current', 'live']), - skipInterval: state.getIn(['components', 'player', 'skipInterval']), - }; - }, - { - fullscreenOn, - fullscreenOff, - toggleBottomBlock, - changeSkipInterval, - } -) -export default class Controls extends React.Component { - componentDidMount() { - document.addEventListener('keydown', this.onKeyDown); - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.onKeyDown); - //this.props.toggleInspectorMode(false); - } - - shouldComponentUpdate(nextProps) { - if ( - nextProps.fullscreen !== this.props.fullscreen || - nextProps.bottomBlock !== this.props.bottomBlock || - nextProps.live !== this.props.live || - nextProps.livePlay !== this.props.livePlay || - nextProps.playing !== this.props.playing || - nextProps.completed !== this.props.completed || - nextProps.skip !== this.props.skip || - nextProps.skipToIssue !== this.props.skipToIssue || - nextProps.speed !== this.props.speed || - nextProps.disabled !== this.props.disabled || - nextProps.fullscreenDisabled !== this.props.fullscreenDisabled || - // nextProps.inspectorMode !== this.props.inspectorMode || - // nextProps.logCount !== this.props.logCount || - nextProps.logRedCount !== this.props.logRedCount || - nextProps.showExceptions !== this.props.showExceptions || - nextProps.resourceRedCount !== this.props.resourceRedCount || - nextProps.fetchRedCount !== this.props.fetchRedCount || - nextProps.showStack !== this.props.showStack || - nextProps.stackCount !== this.props.stackCount || - nextProps.stackRedCount !== this.props.stackRedCount || - nextProps.profilesCount !== this.props.profilesCount || - nextProps.storageCount !== this.props.storageCount || - nextProps.storageType !== this.props.storageType || - nextProps.showStorage !== this.props.showStorage || - nextProps.showProfiler !== this.props.showProfiler || - nextProps.showGraphql !== this.props.showGraphql || - nextProps.showFetch !== this.props.showFetch || - nextProps.fetchCount !== this.props.fetchCount || - nextProps.graphqlCount !== this.props.graphqlCount || - nextProps.liveTimeTravel !== this.props.liveTimeTravel || - nextProps.skipInterval !== this.props.skipInterval - ) - return true; - return false; - } - - onKeyDown = (e) => { - if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { - return; - } - if (this.props.inspectorMode) { - if (e.key === 'Esc' || e.key === 'Escape') { - toggleInspectorMode(false); - } - } - // if (e.key === ' ') { - // document.activeElement.blur(); - // this.props.togglePlay(); - // } - if (e.key === 'Esc' || e.key === 'Escape') { - this.props.fullscreenOff(); - } - if (e.key === 'ArrowRight') { - this.forthTenSeconds(); - } - if (e.key === 'ArrowLeft') { - this.backTenSeconds(); - } - if (e.key === 'ArrowDown') { - this.props.speedDown(); - } - if (e.key === 'ArrowUp') { - this.props.speedUp(); - } - }; - - forthTenSeconds = () => { - const { time, endTime, jump, skipInterval } = this.props; - jump(Math.min(endTime, time + SKIP_INTERVALS[skipInterval])); - }; - - backTenSeconds = () => { - //shouldComponentUpdate - const { time, jump, skipInterval } = this.props; - jump(Math.max(0, time - SKIP_INTERVALS[skipInterval])); - }; - - goLive = () => this.props.jump(this.props.endTime); - - renderPlayBtn = () => { - const { completed, playing } = this.props; - let label; - let icon; - if (completed) { - icon = 'arrow-clockwise'; - label = 'Replay this session'; - } else if (playing) { - icon = 'pause-fill'; - label = 'Pause'; - } else { - icon = 'play-fill-new'; - label = 'Pause'; - label = 'Play'; - } - - return ( - -
- -
-
- ); - }; - - controlIcon = (icon, size, action, isBackwards, additionalClasses) => ( -
- -
- ); - - render() { - const { - bottomBlock, - toggleBottomBlock, - live, - livePlay, - skip, - speed, - disabled, - logRedCount, - showExceptions, - resourceRedCount, - showStack, - stackRedCount, - showStorage, - storageType, - showProfiler, - showGraphql, - fullscreen, - inspectorMode, - closedLive, - toggleSpeed, - toggleSkip, - liveTimeTravel, - changeSkipInterval, - skipInterval, - } = this.props; - - const toggleBottomTools = (blockName) => { - if (blockName === INSPECTOR) { - toggleInspectorMode(); - bottomBlock && toggleBottomBlock(); - } else { - toggleInspectorMode(false); - toggleBottomBlock(blockName); - } - }; - - return ( -
- - {!fullscreen && ( -
-
- {!live && ( - <> - -
- toggleBottomTools(OVERVIEW)} - /> - - )} - - {live && !closedLive && ( -
- (livePlay ? null : jumpToLive())} /> -
- -
-
- )} -
- -
- toggleBottomTools(CONSOLE)} - active={bottomBlock === CONSOLE && !inspectorMode} - label="CONSOLE" - noIcon - labelClassName="!text-base font-semibold" - hasErrors={logRedCount > 0 || showExceptions} - containerClassName="mx-2" - /> - {!live && ( - toggleBottomTools(NETWORK)} - active={bottomBlock === NETWORK && !inspectorMode} - label="NETWORK" - hasErrors={resourceRedCount > 0} - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && ( - toggleBottomTools(PERFORMANCE)} - active={bottomBlock === PERFORMANCE && !inspectorMode} - label="PERFORMANCE" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && showGraphql && ( - toggleBottomTools(GRAPHQL)} - active={bottomBlock === GRAPHQL && !inspectorMode} - label="GRAPHQL" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && showStorage && ( - toggleBottomTools(STORAGE)} - active={bottomBlock === STORAGE && !inspectorMode} - label={getStorageName(storageType)} - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && ( - toggleBottomTools(STACKEVENTS)} - active={bottomBlock === STACKEVENTS && !inspectorMode} - label="EVENTS" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - hasErrors={stackRedCount > 0} - /> - )} - {!live && showProfiler && ( - toggleBottomTools(PROFILER)} - active={bottomBlock === PROFILER && !inspectorMode} - label="PROFILER" - noIcon - labelClassName="!text-base font-semibold" - containerClassName="mx-2" - /> - )} - {!live && ( - - {this.controlIcon( - 'arrows-angle-extend', - 16, - this.props.fullscreenOn, - false, - 'rounded hover:bg-gray-light-shade color-gray-medium' - )} - - )} -
-
- )} -
- ); - } -} diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx new file mode 100644 index 000000000..4e39c72c3 --- /dev/null +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -0,0 +1,420 @@ +import React from 'react'; +import cn from 'classnames'; +import { connect } from 'react-redux'; +import { STORAGE_TYPES, selectStorageType, selectStorageListNow } from 'Player'; +import LiveTag from 'Shared/LiveTag'; + +import { Icon, Tooltip } from 'UI'; +import { + fullscreenOn, + fullscreenOff, + toggleBottomBlock, + changeSkipInterval, + OVERVIEW, + CONSOLE, + NETWORK, + STACKEVENTS, + STORAGE, + PROFILER, + PERFORMANCE, + GRAPHQL, + INSPECTOR, +} from 'Duck/components/player'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; + +import { AssistDuration } from './Time'; +import Timeline from './Timeline'; +import ControlButton from './ControlButton'; +import PlayerControls from './components/PlayerControls'; + +import styles from './controls.module.css'; +import XRayButton from 'Shared/XRayButton'; + +const SKIP_INTERVALS = { + 2: 2e3, + 5: 5e3, + 10: 1e4, + 15: 15e3, + 20: 2e4, + 30: 3e4, + 60: 6e4, +}; + +function getStorageName(type: any) { + switch (type) { + case STORAGE_TYPES.REDUX: + return 'REDUX'; + case STORAGE_TYPES.MOBX: + return 'MOBX'; + case STORAGE_TYPES.VUEX: + return 'VUEX'; + case STORAGE_TYPES.NGRX: + return 'NGRX'; + case STORAGE_TYPES.ZUSTAND: + return 'ZUSTAND'; + case STORAGE_TYPES.NONE: + return 'STATE'; + } +} + +function Controls(props: any) { + const { player, store } = React.useContext(PlayerContext); + + const { jumpToLive, toggleInspectorMode } = player; + const { + live, + livePlay, + playing, + completed, + skip, + // skipToIssue, UPDATE + speed, + cssLoading, + messagesLoading, + inspectorMode, + markedTargets, + // messagesLoading: fullscreenDisabled, UPDATE + stackList, + exceptionsList, + profilesList, + graphqlList, + fetchList, + liveTimeTravel, + logMarkedCountNow: logRedCount, + resourceMarkedCountNow: resourceRedCount, + stackMarkedCountNow: stackRedCount, + } = store.get(); + // const storageCount = selectStorageListNow(store.get()).length UPDATE + const { + bottomBlock, + toggleBottomBlock, + fullscreen, + closedLive, + changeSkipInterval, + skipInterval, + disabledRedux, + showStorageRedux, + showStackRedux, + } = props; + + const storageType = selectStorageType(store.get()); + const disabled = disabledRedux || cssLoading || messagesLoading || inspectorMode || markedTargets; + const stackCount = stackList.length; + const profilesCount = profilesList.length; + const graphqlCount = graphqlList.length; + const showGraphql = graphqlCount > 0; + const fetchCount = fetchList.length; + const showProfiler = profilesCount > 0; + const showExceptions = exceptionsList.length > 0; + const showStorage = storageType !== STORAGE_TYPES.NONE || showStorageRedux; + // const showStack = stackCount > 0 || showStackRedux UPDATE + // const showFetch = fetchCount > 0 UPDATE + + const onKeyDown = (e: any) => { + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { + return; + } + if (inspectorMode) { + if (e.key === 'Esc' || e.key === 'Escape') { + player.toggleInspectorMode(false); + } + } + if (e.key === 'Esc' || e.key === 'Escape') { + props.fullscreenOff(); + } + if (e.key === 'ArrowRight') { + forthTenSeconds(); + } + if (e.key === 'ArrowLeft') { + backTenSeconds(); + } + if (e.key === 'ArrowDown') { + props.speedDown(); + } + if (e.key === 'ArrowUp') { + props.speedUp(); + } + }; + + React.useEffect(() => { + document.addEventListener('keydown', onKeyDown); + return () => { + document.removeEventListener('keydown', onKeyDown); + }; + }, []); + + const forthTenSeconds = () => { + // @ts-ignore + player.jumpInterval(SKIP_INTERVALS[skipInterval]) + }; + + const backTenSeconds = () => { + // @ts-ignore + player.jumpInterval(-SKIP_INTERVALS[skipInterval]) + }; + + const renderPlayBtn = () => { + let label; + let icon; + if (completed) { + icon = 'arrow-clockwise' as const; + label = 'Replay this session'; + } else if (playing) { + icon = 'pause-fill' as const; + label = 'Pause'; + } else { + icon = 'play-fill-new' as const; + label = 'Pause'; + label = 'Play'; + } + + return ( + +
player.togglePlay()} + className="hover-main color-main cursor-pointer rounded hover:bg-gray-light-shade" + > + +
+
+ ); + }; + + const controlIcon = ( + icon: string, + size: number, + action: (args: any) => any, + isBackwards: boolean, + additionalClasses: string + ) => ( +
+ +
+ ); + + const toggleBottomTools = (blockName: number) => { + if (blockName === INSPECTOR) { + toggleInspectorMode(false); + bottomBlock && toggleBottomBlock(); + } else { + toggleInspectorMode(false); + toggleBottomBlock(blockName); + } + }; + + return ( +
+ player.jump(t)} + liveTimeTravel={liveTimeTravel} + pause={() => player.pause()} + togglePlay={() => player.togglePlay()} + /> + {!fullscreen && ( +
+
+ {!live && ( + <> + player.toggleSpeed()} + toggleSkip={() => player.toggleSkip()} + playButton={renderPlayBtn()} + controlIcon={controlIcon} + skipIntervals={SKIP_INTERVALS} + setSkipInterval={changeSkipInterval} + currentInterval={skipInterval} + /> +
+ toggleBottomTools(OVERVIEW)} + /> + + )} + + {live && !closedLive && ( +
+ (livePlay ? null : jumpToLive())} /> +
+ +
+
+ )} +
+ +
+ toggleBottomTools(CONSOLE)} + active={bottomBlock === CONSOLE && !inspectorMode} + label="CONSOLE" + noIcon + labelClassName="!text-base font-semibold" + hasErrors={logRedCount > 0 || showExceptions} + containerClassName="mx-2" + /> + {!live && ( + toggleBottomTools(NETWORK)} + active={bottomBlock === NETWORK && !inspectorMode} + label="NETWORK" + hasErrors={resourceRedCount > 0} + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {!live && ( + toggleBottomTools(PERFORMANCE)} + active={bottomBlock === PERFORMANCE && !inspectorMode} + label="PERFORMANCE" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {!live && showGraphql && ( + toggleBottomTools(GRAPHQL)} + active={bottomBlock === GRAPHQL && !inspectorMode} + label="GRAPHQL" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {!live && showStorage && ( + toggleBottomTools(STORAGE)} + active={bottomBlock === STORAGE && !inspectorMode} + label={getStorageName(storageType)} + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {!live && ( + toggleBottomTools(STACKEVENTS)} + active={bottomBlock === STACKEVENTS && !inspectorMode} + label="EVENTS" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + hasErrors={stackRedCount > 0} + /> + )} + {!live && showProfiler && ( + toggleBottomTools(PROFILER)} + active={bottomBlock === PROFILER && !inspectorMode} + label="PROFILER" + noIcon + labelClassName="!text-base font-semibold" + containerClassName="mx-2" + /> + )} + {!live && ( + + {controlIcon( + 'arrows-angle-extend', + 16, + props.fullscreenOn, + false, + 'rounded hover:bg-gray-light-shade color-gray-medium' + )} + + )} +
+
+ )} +
+ ); +} + +const ControlPlayer = observer(Controls); + +export default connect( + (state: any) => { + const permissions = state.getIn(['user', 'account', 'permissions']) || []; + const isEnterprise = state.getIn(['user', 'account', 'edition']) === 'ee'; + return { + disabledRedux: isEnterprise && !permissions.includes('DEV_TOOLS'), + fullscreen: state.getIn(['components', 'player', 'fullscreen']), + bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), + showStorageRedux: !state.getIn(['components', 'player', 'hiddenHints', 'storage']), + showStackRedux: !state.getIn(['components', 'player', 'hiddenHints', 'stack']), + closedLive: + !!state.getIn(['sessions', 'errors']) || !state.getIn(['sessions', 'current', 'live']), + skipInterval: state.getIn(['components', 'player', 'skipInterval']), + }; + }, + { + fullscreenOn, + fullscreenOff, + toggleBottomBlock, + changeSkipInterval, + } +)(ControlPlayer); + +// shouldComponentUpdate(nextProps) { +// if ( +// nextProps.fullscreen !== props.fullscreen || +// nextProps.bottomBlock !== props.bottomBlock || +// nextProps.live !== props.live || +// nextProps.livePlay !== props.livePlay || +// nextProps.playing !== props.playing || +// nextProps.completed !== props.completed || +// nextProps.skip !== props.skip || +// nextProps.skipToIssue !== props.skipToIssue || +// nextProps.speed !== props.speed || +// nextProps.disabled !== props.disabled || +// nextProps.fullscreenDisabled !== props.fullscreenDisabled || +// // nextProps.inspectorMode !== props.inspectorMode || +// // nextProps.logCount !== props.logCount || +// nextProps.logRedCount !== props.logRedCount || +// nextProps.showExceptions !== props.showExceptions || +// nextProps.resourceRedCount !== props.resourceRedCount || +// nextProps.fetchRedCount !== props.fetchRedCount || +// nextProps.showStack !== props.showStack || +// nextProps.stackCount !== props.stackCount || +// nextProps.stackRedCount !== props.stackRedCount || +// nextProps.profilesCount !== props.profilesCount || +// nextProps.storageCount !== props.storageCount || +// nextProps.storageType !== props.storageType || +// nextProps.showStorage !== props.showStorage || +// nextProps.showProfiler !== props.showProfiler || +// nextProps.showGraphql !== props.showGraphql || +// nextProps.showFetch !== props.showFetch || +// nextProps.fetchCount !== props.fetchCount || +// nextProps.graphqlCount !== props.graphqlCount || +// nextProps.liveTimeTravel !== props.liveTimeTravel || +// nextProps.skipInterval !== props.skipInterval +// ) +// return true; +// return false; +// } diff --git a/frontend/app/components/Session_/Player/Controls/Time.js b/frontend/app/components/Session_/Player/Controls/Time.js index ab6f5e6b8..db665fc8e 100644 --- a/frontend/app/components/Session_/Player/Controls/Time.js +++ b/frontend/app/components/Session_/Player/Controls/Time.js @@ -12,21 +12,17 @@ const Time = ({ time, isCustom, format = 'm:ss', }) => ( Time.displayName = "Time"; -const ReduxTime = observer(({ format, name }) => { +const ReduxTime = observer(({ format, name, isCustom }) => { const { store } = React.useContext(PlayerContext) const time = store.get()[name] - return
-
-
- ); - } - - render() { - const { type, listNow, list, hintIsHidden } = this.props; - - const showStore = type !== STORAGE_TYPES.MOBX; - return ( - - - {list.length > 0 && ( -
- {showStore &&

{'STATE'}

} - {this.state.showDiffs ? ( -

- DIFFS -

- ) : null} -

{getActionsName(type)}

-

- - TTE - -

-
- )} -
- - - { - 'Inspect your application state while you’re replaying your users sessions. OpenReplay supports ' - } - - Redux - - {', '} - - VueX - - {', '} - - Pinia - - {', '} - - Zustand - - {', '} - - MobX - - {' and '} - - NgRx - - . -
-
- - - ) : null - } - size="small" - show={listNow.length === 0} - > - {showStore && ( -
- {listNow.length === 0 ? ( -
- {'Empty state.'} -
- ) : ( - this.renderTab() - )} -
- )} -
- - {listNow.map((item, i) => - this.renderItem(item, i, i > 0 ? listNow[i - 1] : undefined) - )} - -
-
-
-
- ); - } -} diff --git a/frontend/app/components/Session_/Storage/Storage.tsx b/frontend/app/components/Session_/Storage/Storage.tsx new file mode 100644 index 000000000..0f5e682c5 --- /dev/null +++ b/frontend/app/components/Session_/Storage/Storage.tsx @@ -0,0 +1,315 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { hideHint } from 'Duck/components/player'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; +import { JSONTree, NoContent, Tooltip } from 'UI'; +import { formatMs } from 'App/date'; +import { diff } from 'deep-diff'; +import { STORAGE_TYPES, selectStorageList, selectStorageListNow, selectStorageType } from 'Player'; +import Autoscroll from '../Autoscroll'; +import BottomBlock from '../BottomBlock/index'; +import DiffRow from './DiffRow'; +import cn from 'classnames'; +import stl from './storage.module.css'; + +function getActionsName(type: string) { + switch (type) { + case STORAGE_TYPES.MOBX: + case STORAGE_TYPES.VUEX: + return 'MUTATIONS'; + default: + return 'ACTIONS'; + } +} + +interface Props { + hideHint: (args: string) => void; + hintIsHidden: boolean; +} +function Storage(props: Props) { + const lastBtnRef = React.useRef(); + const [showDiffs, setShowDiffs] = React.useState(false); + const { player, store } = React.useContext(PlayerContext); + const state = store.get(); + + const listNow = selectStorageListNow(state); + const list = selectStorageList(state); + const type = selectStorageType(state); + + const focusNextButton = () => { + if (lastBtnRef.current) { + lastBtnRef.current.focus(); + } + }; + + React.useEffect(() => { + focusNextButton(); + }, []); + React.useEffect(() => { + focusNextButton(); + }, [listNow]); + + const renderDiff = (item: Record, prevItem: Record) => { + if (!prevItem) { + // we don't have state before first action + return
; + } + + const stateDiff = diff(prevItem.state, item.state); + + if (!stateDiff) { + return ( +
+ No diff +
+ ); + } + + return ( +
+ {stateDiff.map((d: Record, i: number) => renderDiffs(d, i))} +
+ ); + }; + + const renderDiffs = (diff: Record, i: number) => { + const path = createPath(diff); + return ( + + + + ); + }; + + const createPath = (diff: Record) => { + let path: string[] = []; + + if (diff.path) { + path = path.concat(diff.path); + } + if (typeof diff.index !== 'undefined') { + path.push(diff.index); + } + + const pathStr = path.length ? path.join('.') : ''; + return pathStr; + }; + + const ensureString = (actionType: string) => { + if (typeof actionType === 'string') return actionType; + return 'UNKNOWN'; + }; + + const goNext = () => { + // , list[listNow.length]._index + player.jump(list[listNow.length].time); + }; + + const renderTab = () => { + if (listNow.length === 0) { + return 'Not initialized'; //? + } + return ; + }; + + const renderItem = (item: Record, i: number, prevItem: Record) => { + let src; + let name; + + switch (type) { + case STORAGE_TYPES.REDUX: + case STORAGE_TYPES.NGRX: + src = item.action; + name = src && src.type; + break; + case STORAGE_TYPES.VUEX: + src = item.mutation; + name = src && src.type; + break; + case STORAGE_TYPES.MOBX: + src = item.payload; + name = `@${item.type} ${src && src.type}`; + break; + case STORAGE_TYPES.ZUSTAND: + src = null; + name = item.mutation.join(''); + } + + if (src !== null && !showDiffs) { + setShowDiffs(true); + } + + return ( +
+ {src === null ? ( +
+ {name} +
+ ) : ( + <> + {renderDiff(item, prevItem)} +
+ +
+ + )} +
+ {typeof item.duration === 'number' && ( +
{formatMs(item.duration)}
+ )} +
+ {i + 1 < listNow.length && ( + + )} + {i + 1 === listNow.length && i + 1 < list.length && ( + + )} +
+
+
+ ); + }; + + const { hintIsHidden } = props; + + const showStore = type !== STORAGE_TYPES.MOBX; + return ( + + + {list.length > 0 && ( +
+ {showStore && ( +

+ {'STATE'} +

+ )} + {showDiffs ? ( +

+ DIFFS +

+ ) : null} +

+ {getActionsName(type)} +

+

+ TTE +

+
+ )} +
+ + + { + 'Inspect your application state while you’re replaying your users sessions. OpenReplay supports ' + } + + Redux + + {', '} + + VueX + + {', '} + + Pinia + + {', '} + + Zustand + + {', '} + + MobX + + {' and '} + + NgRx + + . +
+
+ + + ) : null + } + size="small" + show={listNow.length === 0} + > + {showStore && ( +
+ {listNow.length === 0 ? ( +
+ {'Empty state.'} +
+ ) : ( + renderTab() + )} +
+ )} +
+ + {listNow.map((item: Record, i: number) => + renderItem(item, i, i > 0 ? listNow[i - 1] : undefined) + )} + +
+
+
+
+ ); +} + +export default connect( + (state: any) => ({ + hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'storage']), + }), + { + hideHint, + } +)(observer(Storage)); diff --git a/frontend/app/components/Session_/components/NotePopup.tsx b/frontend/app/components/Session_/components/NotePopup.tsx index e229d534b..a5b44cfe0 100644 --- a/frontend/app/components/Session_/components/NotePopup.tsx +++ b/frontend/app/components/Session_/components/NotePopup.tsx @@ -4,7 +4,6 @@ import { connect } from 'react-redux'; import { setCreateNoteTooltip } from 'Duck/sessions'; import GuidePopup from 'Shared/GuidePopup'; import { PlayerContext } from 'App/components/Session/playerContext'; -import { observer } from 'mobx-react-lite'; function NotePopup({ setCreateNoteTooltip, @@ -14,12 +13,11 @@ function NotePopup({ tooltipActive: boolean; }) { const { player, store } = React.useContext(PlayerContext) - const { time } = store.get(); const toggleNotePopup = () => { if (tooltipActive) return; player.pause(); - setCreateNoteTooltip({ time: time, isVisible: true }); + setCreateNoteTooltip({ time: store.get().time, isVisible: true }); }; React.useEffect(() => { @@ -42,11 +40,9 @@ function NotePopup({ ); } -const NotePopupPl = observer(NotePopup); - const NotePopupComp = connect( (state: any) => ({ tooltipActive: state.getIn(['sessions', 'createNoteTooltip', 'isVisible']) }), { setCreateNoteTooltip } -)(NotePopupPl); +)(NotePopup); export default React.memo(NotePopupComp); diff --git a/frontend/app/components/shared/GuidePopup/GuidePopup.tsx b/frontend/app/components/shared/GuidePopup/GuidePopup.tsx index c0f0037b2..0b95e7f75 100644 --- a/frontend/app/components/shared/GuidePopup/GuidePopup.tsx +++ b/frontend/app/components/shared/GuidePopup/GuidePopup.tsx @@ -69,6 +69,8 @@ export default function GuidePopup({ children, title, description }: IProps) {
) : ( - children + <> + {children} + ); } diff --git a/frontend/app/components/ui/Tooltip/Tooltip.tsx b/frontend/app/components/ui/Tooltip/Tooltip.tsx index 5cc7a021c..87520b625 100644 --- a/frontend/app/components/ui/Tooltip/Tooltip.tsx +++ b/frontend/app/components/ui/Tooltip/Tooltip.tsx @@ -4,7 +4,7 @@ import type { Placement } from '@floating-ui/react-dom-interactions'; import cn from 'classnames'; interface Props { - title?: any; + title: React.ReactNode; children: any; disabled?: boolean; open?: boolean; @@ -13,6 +13,7 @@ interface Props { delay?: number; style?: any; offset?: number; + anchorClassName?: string; } function Tooltip(props: Props) { const { @@ -21,6 +22,7 @@ function Tooltip(props: Props) { open = false, placement, className = '', + anchorClassName = '', delay = 500, style = {}, offset = 5, @@ -38,7 +40,7 @@ function Tooltip(props: Props) { return (
- {props.children} + {props.children} 0) { + return this.jump( + Math.min( + endTime, + time + interval + ) + ); + } else { + return this.jump( + Math.max( + 0, + time - interval + ) + ); + } + } + // TODO: clearify logic of live time-travel jumpToLive() { cancelAnimationFrame(this.animationFrameRequestId) @@ -177,4 +197,4 @@ export default class Animator { } -} \ No newline at end of file +} diff --git a/frontend/app/player/web/InspectorController.ts b/frontend/app/player/web/InspectorController.ts index d2c4b6624..44f798bea 100644 --- a/frontend/app/player/web/InspectorController.ts +++ b/frontend/app/player/web/InspectorController.ts @@ -16,7 +16,7 @@ export default class InspectorController { } } - enableInspector(clickCallback: (e: { target: Element }) => void): Document | null { + enableInspector(clickCallback?: (e: { target: Element }) => void): Document | null { const parent = this.screen.getParentElement() if (!parent) return null; if (!this.substitutor) { @@ -28,7 +28,7 @@ export default class InspectorController { } this.substitutor.display(false) - + const docElement = this.screen.document?.documentElement // this.substitutor.document?.importNode( const doc = this.substitutor.document if (doc && docElement) { @@ -43,11 +43,11 @@ export default class InspectorController { this.substitutor.display(true); return doc; } - + disableInspector() { if (this.substitutor) { const doc = this.substitutor.document; - if (doc) { + if (doc) { doc.documentElement.innerHTML = ""; } this.inspector.clean(); @@ -56,4 +56,4 @@ export default class InspectorController { this.screen.display(true); } -} \ No newline at end of file +} diff --git a/frontend/app/player/web/Screen/Inspector.ts b/frontend/app/player/web/Screen/Inspector.ts index b0ea1ca9c..028f7f919 100644 --- a/frontend/app/player/web/Screen/Inspector.ts +++ b/frontend/app/player/web/Screen/Inspector.ts @@ -1,14 +1,14 @@ import type Screen from './Screen' import type Marker from './Marker' -//import { select } from 'optimal-select'; +//import { select } from 'optimal-select'; export default class Inspector { // private captureCallbacks = []; // private bubblingCallbacks = []; constructor(private screen: Screen, private marker: Marker) {} - private onMouseMove = (e: MouseEvent) => { + private onMouseMove = (e: MouseEvent) => { // const { overlay } = this.screen; // if (!overlay.contains(e.target)) { // return; @@ -21,7 +21,7 @@ export default class Inspector { return; } - this.marker.mark(target); + this.marker.mark(target); } private onOverlayLeave = () => { @@ -57,7 +57,7 @@ export default class Inspector { // } private clickCallback: (e: { target: Element }) => void | null = null - enable(clickCallback: Inspector['clickCallback']) { + enable(clickCallback?: Inspector['clickCallback']) { this.screen.overlay.addEventListener('mousemove', this.onMouseMove) this.screen.overlay.addEventListener('mouseleave', this.onOverlayLeave) this.screen.overlay.addEventListener('click', this.onMarkClick) @@ -67,4 +67,4 @@ export default class Inspector { this.screen.overlay.removeEventListener('mouseleave', this.onOverlayLeave) this.screen.overlay.removeEventListener('click', this.onMarkClick) } -} \ No newline at end of file +} diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index ec132d5f8..4467bdef5 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -4,7 +4,7 @@ import Player, { State as PlayerState } from '../player/Player' import MessageManager from './MessageManager' import InspectorController from './InspectorController' import TargetMarker from './TargetMarker' -import AssistManager, { +import AssistManager, { INITIAL_STATE as ASSIST_INITIAL_STATE, } from './assist/AssistManager' import Screen from './Screen/Screen' @@ -40,7 +40,7 @@ export default class WebPlayer extends Player { } : {} const screen = new Screen() - const messageManager = new MessageManager(session, wpState, screen, initialLists) + const messageManager = new MessageManager(session, wpState, screen, initialLists) super(wpState, messageManager) this.screen = screen this.messageManager = messageManager @@ -48,14 +48,14 @@ export default class WebPlayer extends Player { this.targetMarker = new TargetMarker(this.screen, wpState) this.inspectorController = new InspectorController(screen) - + const endTime = !live && session.duration.valueOf() wpState.update({ //@ts-ignore initialized: true, //@ts-ignore session, - + live, livePlay: live, endTime, // : 0, //TODO: through initialState @@ -85,7 +85,7 @@ export default class WebPlayer extends Player { mark(e: Element) { this.inspectorController.marker?.mark(e) } - toggleInspectorMode(flag: boolean, clickCallback) { + toggleInspectorMode(flag: boolean, clickCallback?: (args: any) => any) { if (typeof flag !== 'boolean') { const { inspectorMode } = this.wpState.get() flag = !inspectorMode; @@ -104,6 +104,7 @@ export default class WebPlayer extends Player { setActiveTarget(args: Parameters) { this.targetMarker.setActiveTarget(...args) } + markTargets(args: Parameters) { this.pause() this.targetMarker.markTargets(...args) @@ -133,11 +134,10 @@ export default class WebPlayer extends Player { toggleUserName(name?: string) { this.screen.cursor.showTag(name) } - + clean() { super.clean() this.assistManager.clean() window.removeEventListener('resize', this.scale) } } - diff --git a/frontend/app/player/web/assist/AssistManager.ts b/frontend/app/player/web/assist/AssistManager.ts index 1c9984b51..1d2fe08db 100644 --- a/frontend/app/player/web/assist/AssistManager.ts +++ b/frontend/app/player/web/assist/AssistManager.ts @@ -461,7 +461,7 @@ export default class AssistManager { onStream: (s: MediaStream)=>void, onCallEnd: () => void, onReject: () => void, - onError?: ()=> void, + onError?: (e?: any)=> void, ) { this.callArgs = { localStream, @@ -472,7 +472,7 @@ export default class AssistManager { } } - public call(thirdPartyPeers?: string[]): { end: Function } { + public call(thirdPartyPeers?: string[]): { end: () => void } { if (thirdPartyPeers && thirdPartyPeers.length > 0) { this.addPeerCall(thirdPartyPeers) } else { From c4dc4ffded40e3cb8f4cf8df201a2e26d39746af Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 25 Nov 2022 11:52:27 +0100 Subject: [PATCH 22/64] refactor(ui/player): fix errors after merge --- .../Session_/Exceptions/Exceptions.js | 2 - .../Session_/OverviewPanel/OverviewPanel.tsx | 6 +- .../app/components/Session_/Player/Player.js | 1 + frontend/app/player/_web/MessageManager.ts | 570 ++++++++++++++++++ 4 files changed, 573 insertions(+), 6 deletions(-) create mode 100644 frontend/app/player/_web/MessageManager.ts diff --git a/frontend/app/components/Session_/Exceptions/Exceptions.js b/frontend/app/components/Session_/Exceptions/Exceptions.js index 0eabdf314..2b0c206b4 100644 --- a/frontend/app/components/Session_/Exceptions/Exceptions.js +++ b/frontend/app/components/Session_/Exceptions/Exceptions.js @@ -8,10 +8,8 @@ import { ErrorItem, SlideModal, ErrorDetails, - ErrorHeader, Link, QuestionMarkHint, - Tabs, } from 'UI'; import { fetchErrorStackList } from 'Duck/sessions'; import { connectPlayer, jump } from 'Player'; diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index 0b542dce6..81ed0648d 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -14,10 +14,8 @@ import { NoContent, Icon } from 'UI'; import { observer } from 'mobx-react-lite'; import { PlayerContext } from 'App/components/Session/playerContext'; -function OverviewPanel({ issuesList }: { issuesList: any[] }) { - const { store } = React.useContext(PlayerContext) - function OverviewPanel() { + const { store } = React.useContext(PlayerContext) const [dataLoaded, setDataLoaded] = React.useState(false); const [selectedFeatures, setSelectedFeatures] = React.useState([ 'PERFORMANCE', @@ -140,4 +138,4 @@ export default connect( } )( observer(OverviewPanel) -); +) diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index 9312589f8..b283b669a 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -47,6 +47,7 @@ function Player(props) { closedLive, bottomBlock, activeTab, + fullView, } = props; const playerContext = React.useContext(PlayerContext) const screenWrapper = React.useRef(); diff --git a/frontend/app/player/_web/MessageManager.ts b/frontend/app/player/_web/MessageManager.ts new file mode 100644 index 000000000..49105200c --- /dev/null +++ b/frontend/app/player/_web/MessageManager.ts @@ -0,0 +1,570 @@ +// @ts-ignore +import { Decoder } from "syncod"; +import logger from 'App/logger'; + +import Resource, { TYPES } from 'Types/session/resource'; +import { TYPES as EVENT_TYPES } from 'Types/session/event'; +import Log from 'Types/session/log'; + +import { toast } from 'react-toastify'; + +import type { Store } from '../player/types'; +import ListWalker from '../_common/ListWalker'; + +import Screen from './Screen/Screen'; + +import PagesManager from './managers/PagesManager'; +import MouseMoveManager from './managers/MouseMoveManager'; + +import PerformanceTrackManager from './managers/PerformanceTrackManager'; +import WindowNodeCounter from './managers/WindowNodeCounter'; +import ActivityManager from './managers/ActivityManager'; + +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 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 { + performanceChartData: PerformanceChartPoint[], + skipIntervals: SkipInterval[], + connType?: string, + connBandwidth?: number, + location?: string, + performanceChartTime?: number, + + domContentLoadedTime?: any, + domBuildingTime?: any, + loadTime?: any, + error: boolean, + devtoolsLoading: boolean, + + liveTimeTravel: boolean, + messagesLoading: boolean, + cssLoading: boolean, + + ready: boolean, + 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, + SetPageLocation, + ConnectionInformation, + SetViewportSize, + SetViewportScroll, + MouseClick, +} from './messages'; + +const visualChanges = [ + "mouse_move", + "mouse_click", + "create_element_node", + "set_input_value", + "set_input_checked", + "set_viewport_size", + "set_viewport_scroll", +] + +export default class MessageManager extends Screen { + // TODO: consistent with the other data-lists + private locationEventManager: ListWalker/**/ = new ListWalker(); + private locationManager: ListWalker = new ListWalker(); + private loadedLocationManager: ListWalker = new ListWalker(); + private connectionInfoManger: ListWalker = new ListWalker(); + private performanceTrackManager: PerformanceTrackManager = new PerformanceTrackManager(); + private windowNodeCounter: WindowNodeCounter = new WindowNodeCounter(); + private clickManager: ListWalker = new ListWalker(); + + private resizeManager: ListWalker = new ListWalker([]); + private pagesManager: PagesManager; + private mouseMoveManager: MouseMoveManager; + + private scrollManager: ListWalker = new ListWalker(); + + private readonly decoder = new Decoder(); + private readonly lists: Lists; + + private activityManager: ActivityManager | null = null; + + private sessionStart: number; + private navigationStartOffset: number = 0; + private lastMessageTime: number = 0; + private lastMessageInFileTime: number = 0; + + constructor( + private readonly session: any /*Session*/, + private readonly state: Store, + config: any, + live: boolean, + ) { + super(); + this.pagesManager = new PagesManager(this, this.session.isMobile) + this.mouseMoveManager = new MouseMoveManager(this); + + this.sessionStart = this.session.startedAt; + + if (live) { + this.lists = new Lists() + } else { + this.activityManager = new ActivityManager(this.session.duration.milliseconds); + /* == REFACTOR_ME == */ + const eventList = session.events.toJSON(); + // TODO: fix types for events, remove immutable js + eventList.forEach((e: Record) => { + if (e.type === EVENT_TYPES.LOCATION) { //TODO type system + this.locationEventManager.append(e); + } + }) + + this.lists = new Lists({ + event: eventList, + stack: session.stackEvents.toJSON(), + resource: session.resources.toJSON(), + exceptions: session.errors, + }) + + + /* === */ + this.loadMessages(); + } + } + + private parseAndDistributeMessages(fileReader: MFileReader, onMessage?: (msg: Message) => void) { + const msgs: Array = [] + let next: ReturnType + while (next = fileReader.next()) { + const [msg, index] = next + this.distributeMessage(msg, index) + msgs.push(msg) + onMessage?.(msg) + } + + logger.info("Messages count: ", msgs.length, msgs) + + + // @ts-ignore Hack for upet (TODO: fix ordering in one mutation in tracker(removes first)) + const headChildrenIds = msgs.filter(m => m.parentID === 1).map(m => m.id); + this.pagesManager.sortPages((m1, m2) => { + if (m1.time === m2.time) { + if (m1.tp === "remove_node" && m2.tp !== "remove_node") { + if (headChildrenIds.includes(m1.id)) { + return -1; + } + } else if (m2.tp === "remove_node" && m1.tp !== "remove_node") { + if (headChildrenIds.includes(m2.id)) { + return 1; + } + } else if (m2.tp === "remove_node" && m1.tp === "remove_node") { + const m1FromHead = headChildrenIds.includes(m1.id); + const m2FromHead = headChildrenIds.includes(m2.id); + if (m1FromHead && !m2FromHead) { + return -1; + } else if (m2FromHead && !m1FromHead) { + return 1; + } + } + } + return 0; + }) + } + + + private waitingForFiles: boolean = false + private onFileReadSuccess = () => { + const stateToUpdate = { + performanceChartData: this.performanceTrackManager.chartData, + performanceAvaliability: this.performanceTrackManager.avaliability, + ...this.lists.getFullListsState() + } + if (this.activityManager) { + this.activityManager.end() + stateToUpdate.skipIntervals = this.activityManager.list + } + + this.state.update(stateToUpdate) + } + private onFileReadFailed = (e: any) => { + logger.error(e) + this.state.update({ error: true }) + toast.error('Error requesting a session file') + } + private onFileReadFinally = () => { + this.incomingMessages + .filter(msg => msg.time >= this.lastMessageInFileTime) + .forEach(msg => this.distributeMessage(msg, 0)) + + this.waitingForFiles = false + this.setMessagesLoading(false) + } + + private loadMessages() { + // TODO: reuseable decryptor instance + const createNewParser = (shouldDecrypt=true) => { + const decrypt = shouldDecrypt && this.session.fileKey + ? (b: Uint8Array) => decryptSessionBytes(b, this.session.fileKey) + : (b: Uint8Array) => Promise.resolve(b) + // Each time called - new fileReader created + const fileReader = new MFileReader(new Uint8Array(), this.sessionStart) + return (b: Uint8Array) => decrypt(b).then(b => { + fileReader.append(b) + this.parseAndDistributeMessages(fileReader) + this.setMessagesLoading(false) + }) + } + this.setMessagesLoading(true) + this.waitingForFiles = true + + loadFiles(this.session.domURL, createNewParser()) + .catch(() => // do if only the first file missing (404) (?) + requestEFSDom(this.session.sessionId) + .then(createNewParser(false)) + // Fallback to back Compatability with mobsUrl + .catch(e => + loadFiles(this.session.mobsUrl, createNewParser(false)) + ) + ) + .then(this.onFileReadSuccess) + .catch(this.onFileReadFailed) + .finally(this.onFileReadFinally) + + // load devtools + if (this.session.devtoolsURL.length) { + this.state.update({ devtoolsLoading: true }) + loadFiles(this.session.devtoolsURL, createNewParser()) + .catch(() => + requestEFSDevtools(this.session.sessionId) + .then(createNewParser(false)) + ) + //.catch() // not able to download the devtools file + .finally(() => this.state.update({ devtoolsLoading: false })) + } + } + + reloadWithUnprocessedFile() { + const onData = (byteArray: Uint8Array) => { + const onMessage = (msg: Message) => { this.lastMessageInFileTime = msg.time } + this.parseAndDistributeMessages(new MFileReader(byteArray, this.sessionStart), onMessage) + } + const updateState = () => + this.state.update({ + liveTimeTravel: true, + }); + + // assist will pause and skip messages to prevent timestamp related errors + this.reloadMessageManagers() + this.windowNodeCounter.reset() + + this.setMessagesLoading(true) + this.waitingForFiles = true + + return requestEFSDom(this.session.sessionId) + .then(onData) + .then(updateState) + .then(this.onFileReadSuccess) + .catch(this.onFileReadFailed) + .finally(this.onFileReadFinally) + } + + private reloadMessageManagers() { + this.locationEventManager = new ListWalker(); + this.locationManager = new ListWalker(); + this.loadedLocationManager = new ListWalker(); + this.connectionInfoManger = new ListWalker(); + this.clickManager = new ListWalker(); + this.scrollManager = new ListWalker(); + this.resizeManager = new ListWalker([]); + + this.performanceTrackManager = new PerformanceTrackManager() + this.windowNodeCounter = new WindowNodeCounter(); + this.pagesManager = new PagesManager(this, this.session.isMobile) + this.mouseMoveManager = new MouseMoveManager(this); + this.activityManager = new ActivityManager(this.session.duration.milliseconds); + } + + move(t: number, index?: number): void { + const stateToUpdate: Partial = {}; + /* == REFACTOR_ME == */ + const lastLoadedLocationMsg = this.loadedLocationManager.moveGetLast(t, index); + if (!!lastLoadedLocationMsg) { + // TODO: page-wise resources list // setListsStartTime(lastLoadedLocationMsg.time) + this.navigationStartOffset = lastLoadedLocationMsg.navigationStart - this.sessionStart; + } + const llEvent = this.locationEventManager.moveGetLast(t, index); + if (!!llEvent) { + if (llEvent.domContentLoadedTime != null) { + stateToUpdate.domContentLoadedTime = { + time: llEvent.domContentLoadedTime + this.navigationStartOffset, //TODO: predefined list of load event for the network tab (merge events & SetPageLocation: add navigationStart to db) + value: llEvent.domContentLoadedTime, + } + } + if (llEvent.loadTime != null) { + stateToUpdate.loadTime = { + time: llEvent.loadTime + this.navigationStartOffset, + value: llEvent.loadTime, + } + } + if (llEvent.domBuildingTime != null) { + stateToUpdate.domBuildingTime = llEvent.domBuildingTime; + } + } + /* === */ + const lastLocationMsg = this.locationManager.moveGetLast(t, index); + if (!!lastLocationMsg) { + stateToUpdate.location = lastLocationMsg.url; + } + const lastConnectionInfoMsg = this.connectionInfoManger.moveGetLast(t, index); + if (!!lastConnectionInfoMsg) { + stateToUpdate.connType = lastConnectionInfoMsg.type; + stateToUpdate.connBandwidth = lastConnectionInfoMsg.downlink; + } + const lastPerformanceTrackMessage = this.performanceTrackManager.moveGetLast(t, index); + if (!!lastPerformanceTrackMessage) { + stateToUpdate.performanceChartTime = lastPerformanceTrackMessage.time; + } + + this.lists.moveGetState(t) + Object.keys(stateToUpdate).length > 0 && this.state.update(stateToUpdate); + + /* Sequence of the managers is important here */ + // Preparing the size of "screen" + const lastResize = this.resizeManager.moveGetLast(t, index); + if (!!lastResize) { + this.setSize(lastResize) + } + this.pagesManager.moveReady(t).then(() => { + + const lastScroll = this.scrollManager.moveGetLast(t, index); + if (!!lastScroll && this.window) { + this.window.scrollTo(lastScroll.x, lastScroll.y); + } + // Moving mouse and setting :hover classes on ready view + this.mouseMoveManager.move(t); + const lastClick = this.clickManager.moveGetLast(t); + if (!!lastClick && t - lastClick.time < 600) { // happend during last 600ms + this.cursor.click(); + } + // After all changes - redraw the marker + //this.marker.redraw(); + }) + + if (this.waitingForFiles && this.lastMessageTime <= t) { + this.setMessagesLoading(true) + } + } + + private decodeStateMessage(msg: any, keys: Array) { + const decoded = {}; + try { + keys.forEach(key => { + // @ts-ignore TODO: types for decoder + decoded[key] = this.decoder.decode(msg[key]); + }); + } catch (e) { + logger.error("Error on message decoding: ", e, msg); + return null; + } + return { ...msg, ...decoded }; + } + + private readonly incomingMessages: Message[] = [] + appendMessage(msg: Message, index: number) { + // @ts-ignore + // msg.time = this.md.getLastRecordedMessageTime() + msg.time\ + //TODO: put index in message type + this.incomingMessages.push(msg) + if (!this.waitingForFiles) { + this.distributeMessage(msg, index) + } + } + + private distributeMessage(msg: Message, index: number): void { + const lastMessageTime = Math.max(msg.time, this.lastMessageTime) + this.lastMessageTime = lastMessageTime + this.state.update({ lastMessageTime }) + if (visualChanges.includes(msg.tp)) { + this.activityManager?.updateAcctivity(msg.time); + } + let decoded; + const time = msg.time; + switch (msg.tp) { + /* Lists: */ + case "console_log": + if (msg.level === 'debug') break; + this.lists.lists.log.append(Log({ + level: msg.level, + value: msg.value, + time, + index, + })) + break; + case "fetch": + this.lists.lists.fetch.append(Resource({ + method: msg.method, + url: msg.url, + payload: msg.request, + response: msg.response, + status: msg.status, + duration: msg.duration, + type: TYPES.FETCH, + time: msg.timestamp - this.sessionStart, //~ + index, + })); + break; + /* */ + case "set_page_location": + this.locationManager.append(msg); + if (msg.navigationStart > 0) { + this.loadedLocationManager.append(msg); + } + break; + case "set_viewport_size": + this.resizeManager.append(msg); + break; + case "mouse_move": + this.mouseMoveManager.append(msg); + break; + case "mouse_click": + this.clickManager.append(msg); + break; + case "set_viewport_scroll": + this.scrollManager.append(msg); + break; + case "performance_track": + this.performanceTrackManager.append(msg); + break; + case "set_page_visibility": + this.performanceTrackManager.handleVisibility(msg) + break; + case "connection_information": + this.connectionInfoManger.append(msg); + break; + case "o_table": + this.decoder.set(msg.key, msg.value); + break; + case "redux": + decoded = this.decodeStateMessage(msg, ["state", "action"]); + logger.log('redux', decoded) + if (decoded != null) { + this.lists.lists.redux.append(decoded); + } + break; + case "ng_rx": + decoded = this.decodeStateMessage(msg, ["state", "action"]); + logger.log('ngrx', decoded) + if (decoded != null) { + this.lists.lists.ngrx.append(decoded); + } + break; + case "vuex": + decoded = this.decodeStateMessage(msg, ["state", "mutation"]); + logger.log('vuex', decoded) + if (decoded != null) { + this.lists.lists.vuex.append(decoded); + } + break; + case "zustand": + decoded = this.decodeStateMessage(msg, ["state", "mutation"]) + logger.log('zustand', decoded) + if (decoded != null) { + this.lists.lists.zustand.append(decoded) + } + case "mob_x": + decoded = this.decodeStateMessage(msg, ["payload"]); + logger.log('mobx', decoded) + + if (decoded != null) { + this.lists.lists.mobx.append(decoded); + } + break; + case "graph_ql": + this.lists.lists.graphql.append(msg); + break; + case "profiler": + this.lists.lists.profiles.append(msg); + break; + default: + switch (msg.tp) { + case "create_document": + this.windowNodeCounter.reset(); + this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); + break; + case "create_text_node": + case "create_element_node": + this.windowNodeCounter.addNode(msg.id, msg.parentID); + this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); + break; + case "move_node": + this.windowNodeCounter.moveNode(msg.id, msg.parentID); + this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); + break; + case "remove_node": + this.windowNodeCounter.removeNode(msg.id); + this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); + break; + } + this.performanceTrackManager.addNodeCountPointIfNeed(msg.time) + this.pagesManager.appendMessage(msg); + break; + } + } + + getLastMessageTime(): number { + return this.lastMessageTime; + } + + getFirstMessageTime(): number { + return this.pagesManager.minTime; + } + + + setMessagesLoading(messagesLoading: boolean) { + this.display(!messagesLoading); + this.state.update({ messagesLoading }); + } + + setCSSLoading(cssLoading: boolean) { + this.displayFrame(!cssLoading); + this.state.update({ cssLoading }); + } + + private setSize({ height, width }: { height: number, width: number }) { + this.scale({ height, width }); + this.state.update({ width, height }); + + //this.updateMarketTargets() + } + + // TODO: clean managers? + clean() { + this.state.update(INITIAL_STATE); + this.incomingMessages.length = 0 + } + +} From 99be787a07c70b4346f8f7fb5a66b4f292506664 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 25 Nov 2022 11:56:41 +0100 Subject: [PATCH 23/64] refactor(ui/player): fix errors after merge --- .../components/Session_/OverviewPanel/OverviewPanel.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx index 81ed0648d..ef8598ddc 100644 --- a/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx +++ b/frontend/app/components/Session_/OverviewPanel/OverviewPanel.tsx @@ -14,7 +14,7 @@ import { NoContent, Icon } from 'UI'; import { observer } from 'mobx-react-lite'; import { PlayerContext } from 'App/components/Session/playerContext'; -function OverviewPanel() { +function OverviewPanel({ issuesList }: { issuesList: Record[] }) { const { store } = React.useContext(PlayerContext) const [dataLoaded, setDataLoaded] = React.useState(false); const [selectedFeatures, setSelectedFeatures] = React.useState([ @@ -37,9 +37,9 @@ function OverviewPanel() { const fetchPresented = fetchList.length > 0; const resourceList = resourceListUnmap - .filter((r: any) => r.isRed() || r.isYellow()) - .concat(fetchList.filter((i: any) => parseInt(i.status) >= 400)) - .concat(graphqlList.filter((i: any) => parseInt(i.status) >= 400)) + .filter((r: any) => r.isRed() || r.isYellow()) + .concat(fetchList.filter((i: any) => parseInt(i.status) >= 400)) + .concat(graphqlList.filter((i: any) => parseInt(i.status) >= 400)) const resources: any = React.useMemo(() => { return { @@ -68,6 +68,7 @@ function OverviewPanel() { } }, [ resourceList, + issuesList, exceptionsList, eventsList, stackEventList, From c45f5e6d952b0b7c7da5ab7b5fd198a3c7274787 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Fri, 25 Nov 2022 11:58:46 +0100 Subject: [PATCH 24/64] fix(player):type fixes & delete redundant --- frontend/app/player/_web/MessageManager.ts | 570 ------------------ frontend/app/player/web/MessageManager.ts | 7 +- frontend/app/player/web/WebPlayer.ts | 18 +- .../player/web/assist/ListWalkerWithMarks.ts | 42 -- 4 files changed, 12 insertions(+), 625 deletions(-) delete mode 100644 frontend/app/player/_web/MessageManager.ts delete mode 100644 frontend/app/player/web/assist/ListWalkerWithMarks.ts diff --git a/frontend/app/player/_web/MessageManager.ts b/frontend/app/player/_web/MessageManager.ts deleted file mode 100644 index 49105200c..000000000 --- a/frontend/app/player/_web/MessageManager.ts +++ /dev/null @@ -1,570 +0,0 @@ -// @ts-ignore -import { Decoder } from "syncod"; -import logger from 'App/logger'; - -import Resource, { TYPES } from 'Types/session/resource'; -import { TYPES as EVENT_TYPES } from 'Types/session/event'; -import Log from 'Types/session/log'; - -import { toast } from 'react-toastify'; - -import type { Store } from '../player/types'; -import ListWalker from '../_common/ListWalker'; - -import Screen from './Screen/Screen'; - -import PagesManager from './managers/PagesManager'; -import MouseMoveManager from './managers/MouseMoveManager'; - -import PerformanceTrackManager from './managers/PerformanceTrackManager'; -import WindowNodeCounter from './managers/WindowNodeCounter'; -import ActivityManager from './managers/ActivityManager'; - -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 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 { - performanceChartData: PerformanceChartPoint[], - skipIntervals: SkipInterval[], - connType?: string, - connBandwidth?: number, - location?: string, - performanceChartTime?: number, - - domContentLoadedTime?: any, - domBuildingTime?: any, - loadTime?: any, - error: boolean, - devtoolsLoading: boolean, - - liveTimeTravel: boolean, - messagesLoading: boolean, - cssLoading: boolean, - - ready: boolean, - 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, - SetPageLocation, - ConnectionInformation, - SetViewportSize, - SetViewportScroll, - MouseClick, -} from './messages'; - -const visualChanges = [ - "mouse_move", - "mouse_click", - "create_element_node", - "set_input_value", - "set_input_checked", - "set_viewport_size", - "set_viewport_scroll", -] - -export default class MessageManager extends Screen { - // TODO: consistent with the other data-lists - private locationEventManager: ListWalker/**/ = new ListWalker(); - private locationManager: ListWalker = new ListWalker(); - private loadedLocationManager: ListWalker = new ListWalker(); - private connectionInfoManger: ListWalker = new ListWalker(); - private performanceTrackManager: PerformanceTrackManager = new PerformanceTrackManager(); - private windowNodeCounter: WindowNodeCounter = new WindowNodeCounter(); - private clickManager: ListWalker = new ListWalker(); - - private resizeManager: ListWalker = new ListWalker([]); - private pagesManager: PagesManager; - private mouseMoveManager: MouseMoveManager; - - private scrollManager: ListWalker = new ListWalker(); - - private readonly decoder = new Decoder(); - private readonly lists: Lists; - - private activityManager: ActivityManager | null = null; - - private sessionStart: number; - private navigationStartOffset: number = 0; - private lastMessageTime: number = 0; - private lastMessageInFileTime: number = 0; - - constructor( - private readonly session: any /*Session*/, - private readonly state: Store, - config: any, - live: boolean, - ) { - super(); - this.pagesManager = new PagesManager(this, this.session.isMobile) - this.mouseMoveManager = new MouseMoveManager(this); - - this.sessionStart = this.session.startedAt; - - if (live) { - this.lists = new Lists() - } else { - this.activityManager = new ActivityManager(this.session.duration.milliseconds); - /* == REFACTOR_ME == */ - const eventList = session.events.toJSON(); - // TODO: fix types for events, remove immutable js - eventList.forEach((e: Record) => { - if (e.type === EVENT_TYPES.LOCATION) { //TODO type system - this.locationEventManager.append(e); - } - }) - - this.lists = new Lists({ - event: eventList, - stack: session.stackEvents.toJSON(), - resource: session.resources.toJSON(), - exceptions: session.errors, - }) - - - /* === */ - this.loadMessages(); - } - } - - private parseAndDistributeMessages(fileReader: MFileReader, onMessage?: (msg: Message) => void) { - const msgs: Array = [] - let next: ReturnType - while (next = fileReader.next()) { - const [msg, index] = next - this.distributeMessage(msg, index) - msgs.push(msg) - onMessage?.(msg) - } - - logger.info("Messages count: ", msgs.length, msgs) - - - // @ts-ignore Hack for upet (TODO: fix ordering in one mutation in tracker(removes first)) - const headChildrenIds = msgs.filter(m => m.parentID === 1).map(m => m.id); - this.pagesManager.sortPages((m1, m2) => { - if (m1.time === m2.time) { - if (m1.tp === "remove_node" && m2.tp !== "remove_node") { - if (headChildrenIds.includes(m1.id)) { - return -1; - } - } else if (m2.tp === "remove_node" && m1.tp !== "remove_node") { - if (headChildrenIds.includes(m2.id)) { - return 1; - } - } else if (m2.tp === "remove_node" && m1.tp === "remove_node") { - const m1FromHead = headChildrenIds.includes(m1.id); - const m2FromHead = headChildrenIds.includes(m2.id); - if (m1FromHead && !m2FromHead) { - return -1; - } else if (m2FromHead && !m1FromHead) { - return 1; - } - } - } - return 0; - }) - } - - - private waitingForFiles: boolean = false - private onFileReadSuccess = () => { - const stateToUpdate = { - performanceChartData: this.performanceTrackManager.chartData, - performanceAvaliability: this.performanceTrackManager.avaliability, - ...this.lists.getFullListsState() - } - if (this.activityManager) { - this.activityManager.end() - stateToUpdate.skipIntervals = this.activityManager.list - } - - this.state.update(stateToUpdate) - } - private onFileReadFailed = (e: any) => { - logger.error(e) - this.state.update({ error: true }) - toast.error('Error requesting a session file') - } - private onFileReadFinally = () => { - this.incomingMessages - .filter(msg => msg.time >= this.lastMessageInFileTime) - .forEach(msg => this.distributeMessage(msg, 0)) - - this.waitingForFiles = false - this.setMessagesLoading(false) - } - - private loadMessages() { - // TODO: reuseable decryptor instance - const createNewParser = (shouldDecrypt=true) => { - const decrypt = shouldDecrypt && this.session.fileKey - ? (b: Uint8Array) => decryptSessionBytes(b, this.session.fileKey) - : (b: Uint8Array) => Promise.resolve(b) - // Each time called - new fileReader created - const fileReader = new MFileReader(new Uint8Array(), this.sessionStart) - return (b: Uint8Array) => decrypt(b).then(b => { - fileReader.append(b) - this.parseAndDistributeMessages(fileReader) - this.setMessagesLoading(false) - }) - } - this.setMessagesLoading(true) - this.waitingForFiles = true - - loadFiles(this.session.domURL, createNewParser()) - .catch(() => // do if only the first file missing (404) (?) - requestEFSDom(this.session.sessionId) - .then(createNewParser(false)) - // Fallback to back Compatability with mobsUrl - .catch(e => - loadFiles(this.session.mobsUrl, createNewParser(false)) - ) - ) - .then(this.onFileReadSuccess) - .catch(this.onFileReadFailed) - .finally(this.onFileReadFinally) - - // load devtools - if (this.session.devtoolsURL.length) { - this.state.update({ devtoolsLoading: true }) - loadFiles(this.session.devtoolsURL, createNewParser()) - .catch(() => - requestEFSDevtools(this.session.sessionId) - .then(createNewParser(false)) - ) - //.catch() // not able to download the devtools file - .finally(() => this.state.update({ devtoolsLoading: false })) - } - } - - reloadWithUnprocessedFile() { - const onData = (byteArray: Uint8Array) => { - const onMessage = (msg: Message) => { this.lastMessageInFileTime = msg.time } - this.parseAndDistributeMessages(new MFileReader(byteArray, this.sessionStart), onMessage) - } - const updateState = () => - this.state.update({ - liveTimeTravel: true, - }); - - // assist will pause and skip messages to prevent timestamp related errors - this.reloadMessageManagers() - this.windowNodeCounter.reset() - - this.setMessagesLoading(true) - this.waitingForFiles = true - - return requestEFSDom(this.session.sessionId) - .then(onData) - .then(updateState) - .then(this.onFileReadSuccess) - .catch(this.onFileReadFailed) - .finally(this.onFileReadFinally) - } - - private reloadMessageManagers() { - this.locationEventManager = new ListWalker(); - this.locationManager = new ListWalker(); - this.loadedLocationManager = new ListWalker(); - this.connectionInfoManger = new ListWalker(); - this.clickManager = new ListWalker(); - this.scrollManager = new ListWalker(); - this.resizeManager = new ListWalker([]); - - this.performanceTrackManager = new PerformanceTrackManager() - this.windowNodeCounter = new WindowNodeCounter(); - this.pagesManager = new PagesManager(this, this.session.isMobile) - this.mouseMoveManager = new MouseMoveManager(this); - this.activityManager = new ActivityManager(this.session.duration.milliseconds); - } - - move(t: number, index?: number): void { - const stateToUpdate: Partial = {}; - /* == REFACTOR_ME == */ - const lastLoadedLocationMsg = this.loadedLocationManager.moveGetLast(t, index); - if (!!lastLoadedLocationMsg) { - // TODO: page-wise resources list // setListsStartTime(lastLoadedLocationMsg.time) - this.navigationStartOffset = lastLoadedLocationMsg.navigationStart - this.sessionStart; - } - const llEvent = this.locationEventManager.moveGetLast(t, index); - if (!!llEvent) { - if (llEvent.domContentLoadedTime != null) { - stateToUpdate.domContentLoadedTime = { - time: llEvent.domContentLoadedTime + this.navigationStartOffset, //TODO: predefined list of load event for the network tab (merge events & SetPageLocation: add navigationStart to db) - value: llEvent.domContentLoadedTime, - } - } - if (llEvent.loadTime != null) { - stateToUpdate.loadTime = { - time: llEvent.loadTime + this.navigationStartOffset, - value: llEvent.loadTime, - } - } - if (llEvent.domBuildingTime != null) { - stateToUpdate.domBuildingTime = llEvent.domBuildingTime; - } - } - /* === */ - const lastLocationMsg = this.locationManager.moveGetLast(t, index); - if (!!lastLocationMsg) { - stateToUpdate.location = lastLocationMsg.url; - } - const lastConnectionInfoMsg = this.connectionInfoManger.moveGetLast(t, index); - if (!!lastConnectionInfoMsg) { - stateToUpdate.connType = lastConnectionInfoMsg.type; - stateToUpdate.connBandwidth = lastConnectionInfoMsg.downlink; - } - const lastPerformanceTrackMessage = this.performanceTrackManager.moveGetLast(t, index); - if (!!lastPerformanceTrackMessage) { - stateToUpdate.performanceChartTime = lastPerformanceTrackMessage.time; - } - - this.lists.moveGetState(t) - Object.keys(stateToUpdate).length > 0 && this.state.update(stateToUpdate); - - /* Sequence of the managers is important here */ - // Preparing the size of "screen" - const lastResize = this.resizeManager.moveGetLast(t, index); - if (!!lastResize) { - this.setSize(lastResize) - } - this.pagesManager.moveReady(t).then(() => { - - const lastScroll = this.scrollManager.moveGetLast(t, index); - if (!!lastScroll && this.window) { - this.window.scrollTo(lastScroll.x, lastScroll.y); - } - // Moving mouse and setting :hover classes on ready view - this.mouseMoveManager.move(t); - const lastClick = this.clickManager.moveGetLast(t); - if (!!lastClick && t - lastClick.time < 600) { // happend during last 600ms - this.cursor.click(); - } - // After all changes - redraw the marker - //this.marker.redraw(); - }) - - if (this.waitingForFiles && this.lastMessageTime <= t) { - this.setMessagesLoading(true) - } - } - - private decodeStateMessage(msg: any, keys: Array) { - const decoded = {}; - try { - keys.forEach(key => { - // @ts-ignore TODO: types for decoder - decoded[key] = this.decoder.decode(msg[key]); - }); - } catch (e) { - logger.error("Error on message decoding: ", e, msg); - return null; - } - return { ...msg, ...decoded }; - } - - private readonly incomingMessages: Message[] = [] - appendMessage(msg: Message, index: number) { - // @ts-ignore - // msg.time = this.md.getLastRecordedMessageTime() + msg.time\ - //TODO: put index in message type - this.incomingMessages.push(msg) - if (!this.waitingForFiles) { - this.distributeMessage(msg, index) - } - } - - private distributeMessage(msg: Message, index: number): void { - const lastMessageTime = Math.max(msg.time, this.lastMessageTime) - this.lastMessageTime = lastMessageTime - this.state.update({ lastMessageTime }) - if (visualChanges.includes(msg.tp)) { - this.activityManager?.updateAcctivity(msg.time); - } - let decoded; - const time = msg.time; - switch (msg.tp) { - /* Lists: */ - case "console_log": - if (msg.level === 'debug') break; - this.lists.lists.log.append(Log({ - level: msg.level, - value: msg.value, - time, - index, - })) - break; - case "fetch": - this.lists.lists.fetch.append(Resource({ - method: msg.method, - url: msg.url, - payload: msg.request, - response: msg.response, - status: msg.status, - duration: msg.duration, - type: TYPES.FETCH, - time: msg.timestamp - this.sessionStart, //~ - index, - })); - break; - /* */ - case "set_page_location": - this.locationManager.append(msg); - if (msg.navigationStart > 0) { - this.loadedLocationManager.append(msg); - } - break; - case "set_viewport_size": - this.resizeManager.append(msg); - break; - case "mouse_move": - this.mouseMoveManager.append(msg); - break; - case "mouse_click": - this.clickManager.append(msg); - break; - case "set_viewport_scroll": - this.scrollManager.append(msg); - break; - case "performance_track": - this.performanceTrackManager.append(msg); - break; - case "set_page_visibility": - this.performanceTrackManager.handleVisibility(msg) - break; - case "connection_information": - this.connectionInfoManger.append(msg); - break; - case "o_table": - this.decoder.set(msg.key, msg.value); - break; - case "redux": - decoded = this.decodeStateMessage(msg, ["state", "action"]); - logger.log('redux', decoded) - if (decoded != null) { - this.lists.lists.redux.append(decoded); - } - break; - case "ng_rx": - decoded = this.decodeStateMessage(msg, ["state", "action"]); - logger.log('ngrx', decoded) - if (decoded != null) { - this.lists.lists.ngrx.append(decoded); - } - break; - case "vuex": - decoded = this.decodeStateMessage(msg, ["state", "mutation"]); - logger.log('vuex', decoded) - if (decoded != null) { - this.lists.lists.vuex.append(decoded); - } - break; - case "zustand": - decoded = this.decodeStateMessage(msg, ["state", "mutation"]) - logger.log('zustand', decoded) - if (decoded != null) { - this.lists.lists.zustand.append(decoded) - } - case "mob_x": - decoded = this.decodeStateMessage(msg, ["payload"]); - logger.log('mobx', decoded) - - if (decoded != null) { - this.lists.lists.mobx.append(decoded); - } - break; - case "graph_ql": - this.lists.lists.graphql.append(msg); - break; - case "profiler": - this.lists.lists.profiles.append(msg); - break; - default: - switch (msg.tp) { - case "create_document": - this.windowNodeCounter.reset(); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; - case "create_text_node": - case "create_element_node": - this.windowNodeCounter.addNode(msg.id, msg.parentID); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; - case "move_node": - this.windowNodeCounter.moveNode(msg.id, msg.parentID); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; - case "remove_node": - this.windowNodeCounter.removeNode(msg.id); - this.performanceTrackManager.setCurrentNodesCount(this.windowNodeCounter.count); - break; - } - this.performanceTrackManager.addNodeCountPointIfNeed(msg.time) - this.pagesManager.appendMessage(msg); - break; - } - } - - getLastMessageTime(): number { - return this.lastMessageTime; - } - - getFirstMessageTime(): number { - return this.pagesManager.minTime; - } - - - setMessagesLoading(messagesLoading: boolean) { - this.display(!messagesLoading); - this.state.update({ messagesLoading }); - } - - setCSSLoading(cssLoading: boolean) { - this.displayFrame(!cssLoading); - this.state.update({ cssLoading }); - } - - private setSize({ height, width }: { height: number, width: number }) { - this.scale({ height, width }); - this.state.update({ width, height }); - - //this.updateMarketTargets() - } - - // TODO: clean managers? - clean() { - this.state.update(INITIAL_STATE); - this.incomingMessages.length = 0 - } - -} diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index 92c67fd7b..19df79c24 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -41,6 +41,7 @@ export interface State extends ScreenState, ListsState { connBandwidth?: number, location?: string, performanceChartTime?: number, + performanceAvaliability?: PerformanceTrackManager['avaliability'] domContentLoadedTime?: any, domBuildingTime?: any, @@ -279,7 +280,7 @@ export default class MessageManager { this.connectionInfoManger = new ListWalker(); this.clickManager = new ListWalker(); this.scrollManager = new ListWalker(); - this.resizeManager = new ListWalker([]); + this.resizeManager = new ListWalker(); this.performanceTrackManager = new PerformanceTrackManager() this.windowNodeCounter = new WindowNodeCounter(); @@ -350,8 +351,6 @@ export default class MessageManager { if (!!lastClick && t - lastClick.time < 600) { // happend during last 600ms this.screen.cursor.click(); } - // After all changes - redraw the marker - //this.marker.redraw(); }) if (this.waitingForFiles && this.lastMessageTime <= t) { @@ -375,8 +374,6 @@ export default class MessageManager { private readonly incomingMessages: Message[] = [] appendMessage(msg: Message, index: number) { - // @ts-ignore - // msg.time = this.md.getLastRecordedMessageTime() + msg.time\ //TODO: put index in message type this.incomingMessages.push(msg) if (!this.waitingForFiles) { diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index 4467bdef5..6c00b6c65 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -58,7 +58,7 @@ export default class WebPlayer extends Player { live, livePlay: live, - endTime, // : 0, //TODO: through initialState + endTime, // : 0, }) // TODO: separate LiveWebPlayer @@ -85,22 +85,24 @@ export default class WebPlayer extends Player { mark(e: Element) { this.inspectorController.marker?.mark(e) } - toggleInspectorMode(flag: boolean, clickCallback?: (args: any) => any) { + + toggleInspectorMode(flag: boolean, clickCallback?: Parameters[0]) { if (typeof flag !== 'boolean') { const { inspectorMode } = this.wpState.get() - flag = !inspectorMode; + flag = !inspectorMode } if (flag) { this.pause() this.wpState.update({ inspectorMode: true }) - return this.inspectorController.enableInspector(clickCallback); + return this.inspectorController.enableInspector(clickCallback) } else { - this.inspectorController.disableInspector(); - this.wpState.update({ inspectorMode: false }); + this.inspectorController.disableInspector() + this.wpState.update({ inspectorMode: false }) } } + // Target Marker setActiveTarget(args: Parameters) { this.targetMarker.setActiveTarget(...args) } @@ -111,10 +113,10 @@ export default class WebPlayer extends Player { } - // TODO + // TODO separate message receivers async toggleTimetravel() { if (!this.wpState.get().liveTimeTravel) { - return await this.messageManager.reloadWithUnprocessedFile(() => + await this.messageManager.reloadWithUnprocessedFile(() => this.wpState.update({ liveTimeTravel: true, }) diff --git a/frontend/app/player/web/assist/ListWalkerWithMarks.ts b/frontend/app/player/web/assist/ListWalkerWithMarks.ts deleted file mode 100644 index 43c05382b..000000000 --- a/frontend/app/player/web/assist/ListWalkerWithMarks.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { Timed } from './types'; -import ListWalker from './ListWalker' - - -type CheckFn = (t: T) => boolean - - -export default class ListWalkerWithMarks extends ListWalker { - private _markCountNow: number = 0 - private _markCount: number = 0 - constructor(private isMarked: CheckFn, initialList: T[] = []) { - super(initialList) - this._markCount = initialList.reduce((n, item) => isMarked(item) ? n+1 : n, 0) - } - - append(item: T) { - if (this.isMarked(item)) { this._markCount++ } - super.append(item) - } - - protected moveNext() { - const val = super.moveNext() - if (val && this.isMarked(val)) { - this._markCountNow++ - } - return val - } - protected movePrev() { - const val = super.movePrev() - if (val && this.isMarked(val)) { - this._markCountNow-- - } - return val - } - get markedCountNow(): number { - return this._markCountNow - } - get markedCount(): number { - return this._markCount - } - -} From 4af4824059405458b9cf72886900a5a58b99b422 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 25 Nov 2022 12:40:32 +0100 Subject: [PATCH 25/64] refactor(ui/player): fix errors after merge --- .../Session_/Player/Controls/Controls.tsx | 18 +++++----- .../Controls/components/PlayerControls.tsx | 34 ++++++++++++------- frontend/package.json | 1 - frontend/webpack.config.ts | 2 +- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx index 4e39c72c3..c6f8a7519 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.tsx +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -95,19 +95,19 @@ function Controls(props: any) { skipInterval, disabledRedux, showStorageRedux, - showStackRedux, + // showStackRedux, } = props; const storageType = selectStorageType(store.get()); const disabled = disabledRedux || cssLoading || messagesLoading || inspectorMode || markedTargets; - const stackCount = stackList.length; const profilesCount = profilesList.length; const graphqlCount = graphqlList.length; const showGraphql = graphqlCount > 0; - const fetchCount = fetchList.length; const showProfiler = profilesCount > 0; const showExceptions = exceptionsList.length > 0; const showStorage = storageType !== STORAGE_TYPES.NONE || showStorageRedux; + // const fetchCount = fetchList.length; + // const stackCount = stackList.length; // const showStack = stackCount > 0 || showStackRedux UPDATE // const showFetch = fetchCount > 0 UPDATE @@ -130,17 +130,17 @@ function Controls(props: any) { backTenSeconds(); } if (e.key === 'ArrowDown') { - props.speedDown(); + player.speedDown(); } if (e.key === 'ArrowUp') { - props.speedUp(); + player.speedUp(); } }; React.useEffect(() => { - document.addEventListener('keydown', onKeyDown); + document.addEventListener('keydown', onKeyDown.bind(this)); return () => { - document.removeEventListener('keydown', onKeyDown); + document.removeEventListener('keydown', onKeyDown.bind(this)); }; }, []); @@ -204,10 +204,10 @@ function Controls(props: any) { const toggleBottomTools = (blockName: number) => { if (blockName === INSPECTOR) { - toggleInspectorMode(false); + player.toggleInspectorMode(false); bottomBlock && toggleBottomBlock(); } else { - toggleInspectorMode(false); + player.toggleInspectorMode(false); toggleBottomBlock(blockName); } }; diff --git a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx index d152fad0f..918fdb8b6 100644 --- a/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/PlayerControls.tsx @@ -94,11 +94,15 @@ function PlayerControls(props: Props) {
{/* @ts-ignore */} - + +
- {/* @ts-ignore */} + {/* @ts-ignore */} {currentInterval}s
- + +
{!live && ( diff --git a/frontend/package.json b/frontend/package.json index c4f0a68de..d036ac7bb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -98,7 +98,6 @@ "babel-loader": "^8.2.4", "babel-plugin-recharts": "^1.2.1", "babel-plugin-transform-decorators-legacy": "^1.3.5", - "circular-dependency-plugin": "^5.2.0", "compression-webpack-plugin": "^10.0.0", "copy-webpack-plugin": "^11.0.0", "country-data": "0.0.31", diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts index 404531b55..e2655b67e 100644 --- a/frontend/webpack.config.ts +++ b/frontend/webpack.config.ts @@ -123,7 +123,7 @@ const config: Configuration = { { from: "./app/assets", to: "assets" }, ], }), - new MiniCssExtractPlugin(), + new MiniCssExtractPlugin({ ignoreOrder: true }), ], devtool: isDevelopment ? "inline-source-map" : false, performance: { From f9e837c0c879796d29efc20c6e5fd9b5d975a144 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 25 Nov 2022 15:01:46 +0100 Subject: [PATCH 26/64] refactor(ui/player): fix console rerender --- .../components/Session_/Autoplay/Autoplay.js | 5 +- .../Session_/Player/Controls/Controls.tsx | 8 +- .../app/components/Session_/Player/Player.js | 7 +- .../app/components/Session_/PlayerBlock.js | 5 +- .../DevTools/ConsolePanel/ConsolePanel.tsx | 76 ++++++++++--------- frontend/app/utils.ts | 33 ++++++++ 6 files changed, 86 insertions(+), 48 deletions(-) diff --git a/frontend/app/components/Session_/Autoplay/Autoplay.js b/frontend/app/components/Session_/Autoplay/Autoplay.js index 5510996ef..8b094af41 100644 --- a/frontend/app/components/Session_/Autoplay/Autoplay.js +++ b/frontend/app/components/Session_/Autoplay/Autoplay.js @@ -12,7 +12,6 @@ function Autoplay(props) { const { player, store } = React.useContext(PlayerContext) const { autoplay } = store.get() - const { toggleAutoplay } = player useEffect(() => { props.setAutoplayValues(); @@ -21,10 +20,10 @@ function Autoplay(props) { return (
player.toggleAutoplay()} className="cursor-pointer flex items-center mr-2 hover:bg-gray-light-shade rounded-md p-2" > - + player.toggleAutoplay()} checked={autoplay} /> Auto-Play
diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx index c6f8a7519..d88f8eeeb 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.tsx +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -75,11 +75,11 @@ function Controls(props: any) { inspectorMode, markedTargets, // messagesLoading: fullscreenDisabled, UPDATE - stackList, + // stackList, exceptionsList, profilesList, graphqlList, - fetchList, + // fetchList, liveTimeTravel, logMarkedCountNow: logRedCount, resourceMarkedCountNow: resourceRedCount, @@ -204,10 +204,10 @@ function Controls(props: any) { const toggleBottomTools = (blockName: number) => { if (blockName === INSPECTOR) { - player.toggleInspectorMode(false); + // player.toggleInspectorMode(false); bottomBlock && toggleBottomBlock(); } else { - player.toggleInspectorMode(false); + // player.toggleInspectorMode(false); toggleBottomBlock(blockName); } }; diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index b283b669a..df6957b6c 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -20,7 +20,7 @@ import { OVERVIEW, } from 'Duck/components/player'; import NetworkPanel from 'Shared/DevTools/NetworkPanel'; -import StackEvents from '../StackEvents/StackEvents'; +// import StackEvents from '../StackEvents/StackEvents'; import Storage from '../Storage'; import { ConnectedPerformance } from '../Performance'; import GraphQL from '../GraphQL'; @@ -40,7 +40,6 @@ import StackEventPanel from 'Shared/DevTools/StackEventPanel'; function Player(props) { const { className, - bottomBlockIsActive, fullscreen, fullscreenOff, nextId, @@ -51,6 +50,7 @@ function Player(props) { } = props; const playerContext = React.useContext(PlayerContext) const screenWrapper = React.useRef(); + const bottomBlockIsActive = !fullscreen && bottomBlock !== NONE React.useEffect(() => { props.updateLastPlayedSession(props.sessionId); @@ -67,6 +67,8 @@ function Player(props) { if (!playerContext.player) return null; + console.log(bottomBlock) + const maxWidth = activeTab ? 'calc(100vw - 270px)' : '100vw'; return (
{ fullscreen: state.getIn(['components', 'player', 'fullscreen']), nextId: state.getIn(['sessions', 'nextId']), sessionId: state.getIn(['sessions', 'current', 'sessionId']), + bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), closedLive: !!state.getIn(['sessions', 'errors']) || (isAssist && !state.getIn(['sessions', 'current', 'live'])), diff --git a/frontend/app/components/Session_/PlayerBlock.js b/frontend/app/components/Session_/PlayerBlock.js index 54130adf5..d14d8b983 100644 --- a/frontend/app/components/Session_/PlayerBlock.js +++ b/frontend/app/components/Session_/PlayerBlock.js @@ -9,14 +9,13 @@ import styles from './playerBlock.module.css'; @connect((state) => ({ fullscreen: state.getIn(['components', 'player', 'fullscreen']), - bottomBlock: state.getIn(['components', 'player', 'bottomBlock']), sessionId: state.getIn(['sessions', 'current', 'sessionId']), disabled: state.getIn(['components', 'targetDefiner', 'inspectorMode']), jiraConfig: state.getIn(['issues', 'list']).first(), })) export default class PlayerBlock extends React.PureComponent { render() { - const { fullscreen, bottomBlock, sessionId, disabled, activeTab, jiraConfig, fullView = false } = this.props; + const { fullscreen, sessionId, disabled, activeTab, jiraConfig, fullView = false } = this.props; return (
@@ -25,9 +24,7 @@ export default class PlayerBlock extends React.PureComponent { )} ({ text: tab, key: tab })); +let throttledCall = () => 999 function renderWithNL(s = '') { if (typeof s !== 'string') return ''; @@ -64,11 +66,24 @@ const TIMEOUT_DURATION = 5000; function ConsolePanel() { const { player, store } = React.useContext(PlayerContext) + const additionalHeight = 0; + const { + sessionStore: { devTools }, + } = useStore(); + + const filter = devTools[INDEX_KEY].filter; + const activeTab = devTools[INDEX_KEY].activeTab; + const activeIndex = devTools[INDEX_KEY].index; + const [isDetailsModalActive, setIsDetailsModalActive] = useState(false); + const [filteredList, setFilteredList] = useState([]); + const [pauseSync, setPauseSync] = useState(activeIndex > 0); + const synRef: any = useRef({}); + const { showModal } = useModal(); const jump = (t: number) => player.jump(t) const { logList, exceptionsList, time } = store.get() - const logExceptions = exceptionsList.map(({ time, errorId, name, projectId }: any) => + const logExceptions = exceptionsList.map(({ time, errorId, name }: any) => Log({ level: LEVEL.ERROR, value: name, @@ -78,19 +93,6 @@ function ConsolePanel() { ); // @ts-ignore const logs = logList.concat(logExceptions) - const additionalHeight = 0; - const { - sessionStore: { devTools }, - } = useStore(); - - const [isDetailsModalActive, setIsDetailsModalActive] = useState(false); - const [filteredList, setFilteredList] = useState([]); - const filter = useObserver(() => devTools[INDEX_KEY].filter); - const activeTab = useObserver(() => devTools[INDEX_KEY].activeTab); - const activeIndex = useObserver(() => devTools[INDEX_KEY].index); - const [pauseSync, setPauseSync] = useState(activeIndex > 0); - const synRef: any = useRef({}); - const { showModal } = useModal(); const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab }); const onFilterChange = ({ target: { value } }: any) => { @@ -116,30 +118,10 @@ function ConsolePanel() { removePause(); }; - useEffect(() => { - if (pauseSync) { - removePause(); - } - - return () => { - clearTimeout(timeOut); - if (!synRef.current.pauseSync) { - devTools.update(INDEX_KEY, { index: 0 }); - } - }; - }, []); - const getCurrentIndex = () => { return filteredList.filter((item: any) => item.time <= time).length - 1; }; - useEffect(() => { - const currentIndex = getCurrentIndex(); - if (currentIndex !== activeIndex && !pauseSync) { - devTools.update(INDEX_KEY, { index: currentIndex }); - } - }, [time]); - const cache = new CellMeasurerCache({ fixedWidth: true, keyMapper: (index: number) => filteredList[index], @@ -177,6 +159,27 @@ function ConsolePanel() { ); }; + useEffect(() => { + if (pauseSync) { + removePause(); + } + throttledCall = throttle(getCurrentIndex, 500, undefined) + return () => { + clearTimeout(timeOut); + if (!synRef.current.pauseSync) { + devTools.update(INDEX_KEY, { index: 0 }); + } + }; + }, []); + + + useEffect(() => { + const currentIndex = throttledCall() + if (currentIndex !== activeIndex && !pauseSync) { + devTools.update(INDEX_KEY, { index: currentIndex }); + } + }, [time]); + React.useMemo(() => { const filterRE = getRE(filter, 'i'); let list = logs; @@ -186,16 +189,19 @@ function ConsolePanel() { (!!filter ? filterRE.test(value) : true) && (activeTab === ALL || activeTab === LEVEL_TAB[level]) ); + console.log('log filter tab', logs.length, filter, activeTab) setFilteredList(list); - }, [logs, filter, activeTab]); + }, [logs.length, filter, activeTab]); useEffect(() => { if (_list.current) { + console.log('active index') // @ts-ignore _list.current.scrollToRow(activeIndex); } }, [activeIndex]); + console.log('rerender') return ( wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; From 24d9f758645f8babe87389e8e27effbc240f9f78 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Fri, 25 Nov 2022 15:27:19 +0100 Subject: [PATCH 27/64] fix(player): fix lists init & bind web-player api (by being making it arrow function) --- frontend/app/player/web/WebPlayer.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index 6c00b6c65..5ae1347db 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -32,12 +32,12 @@ export default class WebPlayer extends Player { constructor(private wpState: Store, session, config: RTCIceServer[], live: boolean) { - let initialLists = live ? { + let initialLists = live ? {} :{ event: session.events.toJSON(), stack: session.stackEvents.toJSON(), resource: session.resources.toJSON(), exceptions: session.errors, - } : {} + } const screen = new Screen() const messageManager = new MessageManager(session, wpState, screen, initialLists) @@ -68,7 +68,7 @@ export default class WebPlayer extends Player { } } - attach(parent: HTMLElement) { + attach = (parent: HTMLElement) => { this.screen.attach(parent) window.addEventListener('resize', this.scale) this.scale() @@ -86,7 +86,7 @@ export default class WebPlayer extends Player { this.inspectorController.marker?.mark(e) } - toggleInspectorMode(flag: boolean, clickCallback?: Parameters[0]) { + toggleInspectorMode = (flag: boolean, clickCallback?: Parameters[0]) => { if (typeof flag !== 'boolean') { const { inspectorMode } = this.wpState.get() flag = !inspectorMode @@ -103,18 +103,18 @@ export default class WebPlayer extends Player { } // Target Marker - setActiveTarget(args: Parameters) { + setActiveTarget = (args: Parameters) => { this.targetMarker.setActiveTarget(...args) } - markTargets(args: Parameters) { + markTargets = (args: Parameters) => { this.pause() this.targetMarker.markTargets(...args) } // TODO separate message receivers - async toggleTimetravel() { + toggleTimetravel = async () => { if (!this.wpState.get().liveTimeTravel) { await this.messageManager.reloadWithUnprocessedFile(() => this.wpState.update({ @@ -133,11 +133,11 @@ export default class WebPlayer extends Player { // update({ notes: notes.filter((note: Note) => note.noteId !== noteId) }) // } - toggleUserName(name?: string) { + toggleUserName = (name?: string) => { this.screen.cursor.showTag(name) } - clean() { + clean = () => { super.clean() this.assistManager.clean() window.removeEventListener('resize', this.scale) From dd8f666ca57eec9d800a19c34d04790c2e6a87df Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 25 Nov 2022 16:05:08 +0100 Subject: [PATCH 28/64] fix(ui): remove immutable log --- .../shared/DevTools/ConsolePanel/ConsolePanel.tsx | 5 ++--- .../shared/DevTools/ConsoleRow/ConsoleRow.tsx | 3 ++- frontend/app/player/web/MessageManager.ts | 2 +- frontend/app/types/session/{log.js => log.tsx} | 14 +++++++++++++- 4 files changed, 18 insertions(+), 6 deletions(-) rename frontend/app/types/session/{log.js => log.tsx} (66%) diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx index a39c16f28..023b7c5c4 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useRef, useState } from 'react'; -import Log from 'Types/session/log'; +import { Log, LEVEL } from 'Types/session/log'; import BottomBlock from '../BottomBlock'; -import { LEVEL } from 'Types/session/log'; import { Tabs, Input, Icon, NoContent } from 'UI'; import cn from 'classnames'; import ConsoleRow from '../ConsoleRow'; @@ -9,7 +8,6 @@ import { getRE } from 'App/utils'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; import { List, CellMeasurer, CellMeasurerCache, AutoSizer } from 'react-virtualized'; -import { useObserver } from 'mobx-react-lite'; import { useStore } from 'App/mstore'; import ErrorDetailsModal from 'App/components/Dashboard/components/Errors/ErrorDetailsModal'; import { useModal } from 'App/components/Modal'; @@ -138,6 +136,7 @@ function ConsolePanel() { const _rowRenderer = ({ index, key, parent, style }: any) => { const item = filteredList[index]; + console.log({ ...filteredList }, { ...item }) return ( // @ts-ignore diff --git a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx index 83929cbed..9e4abb3fb 100644 --- a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx +++ b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx @@ -15,7 +15,8 @@ interface Props { function ConsoleRow(props: Props) { const { log, iconProps, jump, renderWithNL, style, recalcHeight } = props; const [expanded, setExpanded] = useState(false); - const lines = log.value.split('\n').filter((l: any) => !!l); + console.log(log) + const lines = log.value?.split('\n').filter((l: any) => !!l) || []; const canExpand = lines.length > 1; const clickable = canExpand || !!log.errorId; diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index 19df79c24..bca4dc34a 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -4,7 +4,7 @@ import logger from 'App/logger'; import Resource, { TYPES } from 'Types/session/resource'; import { TYPES as EVENT_TYPES } from 'Types/session/event'; -import Log from 'Types/session/log'; +import { Log } from 'Types/session/log'; import { toast } from 'react-toastify'; diff --git a/frontend/app/types/session/log.js b/frontend/app/types/session/log.tsx similarity index 66% rename from frontend/app/types/session/log.js rename to frontend/app/types/session/log.tsx index c1dbe0ef7..28cabf329 100644 --- a/frontend/app/types/session/log.js +++ b/frontend/app/types/session/log.tsx @@ -16,7 +16,7 @@ export const LEVEL = { } export function isRed(log) { - return log.level === EXCEPTION || log.level === ERROR; + return } export default Record({ @@ -36,4 +36,16 @@ export default Record({ } }); +interface ILog { + level: string + value: string + time: number + index?: number + errorId?: string +} +export const Log = (log: ILog) => ({ + isRed: () => log.level === EXCEPTION || log.level === ERROR, + isYellow: () => log.level === WARNING || log.level === WARN, + ...log +}) From cce70179050776168426a99f7d1f65bbc7175c28 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Fri, 25 Nov 2022 16:09:54 +0100 Subject: [PATCH 29/64] fix(player): non-immutable exception list --- frontend/app/player/web/WebPlayer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index 5ae1347db..78eaa1435 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -32,11 +32,11 @@ export default class WebPlayer extends Player { constructor(private wpState: Store, session, config: RTCIceServer[], live: boolean) { - let initialLists = live ? {} :{ + let initialLists = live ? {} : { event: session.events.toJSON(), stack: session.stackEvents.toJSON(), resource: session.resources.toJSON(), - exceptions: session.errors, + exceptions: session.errors.toJSON(), } const screen = new Screen() From 51a5a99fd33893a9d4ca6b482764395535ef22ed Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 25 Nov 2022 16:18:25 +0100 Subject: [PATCH 30/64] refactor(ui/player): fix performance tab --- .../Session_/Performance/Performance.tsx | 638 ++++++++---------- .../components/Session_/Performance/index.js | 4 +- .../app/components/Session_/Player/Player.js | 2 - .../DevTools/ConsolePanel/ConsolePanel.tsx | 3 - .../shared/DevTools/ConsoleRow/ConsoleRow.tsx | 1 - 5 files changed, 299 insertions(+), 349 deletions(-) diff --git a/frontend/app/components/Session_/Performance/Performance.tsx b/frontend/app/components/Session_/Performance/Performance.tsx index 994401141..45f944b50 100644 --- a/frontend/app/components/Session_/Performance/Performance.tsx +++ b/frontend/app/components/Session_/Performance/Performance.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Controls as PlayerControls, connectPlayer } from 'Player'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; import { AreaChart, Area, @@ -11,10 +12,8 @@ import { Tooltip, ResponsiveContainer, ReferenceLine, - CartesianGrid, Label, } from 'recharts'; -import { Checkbox } from 'UI'; import { durationFromMsFormatted } from 'App/date'; import { formatBytes } from 'App/utils'; @@ -174,362 +173,319 @@ function addFpsMetadata(data) { }); } -@connect((state) => ({ - userDeviceHeapSize: state.getIn(['sessions', 'current', 'userDeviceHeapSize']), - userDeviceMemorySize: state.getIn(['sessions', 'current', 'userDeviceMemorySize']), -})) -export default class Performance extends React.PureComponent { - _timeTicks = generateTicks(this.props.performanceChartData); - _data = addFpsMetadata(this.props.performanceChartData); - // state = { - // totalHeap: false, - // usedHeap: true, - // fps: true, - // } - // onCheckboxClick = (e, { name, checked }) => this.setState({ [ name ]: checked }) +function Performance({ + userDeviceHeapSize, +}: { + userDeviceHeapSize: number; +}) { + const { player, store } = React.useContext(PlayerContext); - onDotClick = ({ index }) => { - const point = this._data[index]; + const { + performanceChartTime, + performanceChartData, + connType, + connBandwidth, + performanceAvaliability: avaliability, + } = store.get(); + + const _timeTicks = generateTicks(performanceChartData); + const _data = addFpsMetadata(performanceChartData); + + const onDotClick = ({ index: pointer }: { index: number }) => { + const point = _data[pointer]; if (!!point) { - PlayerControls.jump(point.time); + player.jump(point.time); } }; - onChartClick = (e) => { + const onChartClick = (e: any) => { if (e === null) return; const { activeTooltipIndex } = e; - const point = this._data[activeTooltipIndex]; + const point = _data[activeTooltipIndex]; if (!!point) { - PlayerControls.jump(point.time); + player.jump(point.time); } }; - render() { - const { - userDeviceHeapSize, - userDeviceMemorySize, - connType, - connBandwidth, - performanceChartTime, - avaliability = {}, - } = this.props; - const { fps, cpu, heap, nodes } = avaliability; - const avaliableCount = [fps, cpu, heap, nodes].reduce((c, av) => (av ? c + 1 : c), 0); - const height = avaliableCount === 0 ? '0' : `${100 / avaliableCount}%`; + const { fps, cpu, heap, nodes } = avaliability; + const avaliableCount = [fps, cpu, heap, nodes].reduce((c, av) => (av ? c + 1 : c), 0); + const height = avaliableCount === 0 ? '0' : `${100 / avaliableCount}%`; - return ( - - -
-
Performance
- - - {/* */} - - = 1000 ? `${connBandwidth / 1000} Mbps` : `${connBandwidth} Kbps` - } - display={connBandwidth != null} - /> - -
-
- - {fps && ( - - + +
+
Performance
+ + + + = 1000 ? `${connBandwidth / 1000} Mbps` : `${connBandwidth} Kbps` + } + display={connBandwidth != null} + /> + +
+
+ + {fps && ( + + + + + + - - - - {/* */} - {/* */} - - - - {/* */} - - - - - {/* */} - - - - - )} - {cpu && ( - - + + + + + + + + + + + )} + {cpu && ( + + + + + + {/* */} + ''} + domain={[0, 'dataMax']} + ticks={_timeTicks} > - - - - {/* */} - ''} - domain={[0, 'dataMax']} - ticks={this._timeTicks} - > - - - - - - - - - )} +
+
+ )} - {heap && ( - - + + + + + ''} // tick={false} + _timeTicks to cartesian array + domain={[0, 'dataMax']} + ticks={_timeTicks} > - - - - {/* */} - ''} // tick={false} + this._timeTicks to cartesian array - domain={[0, 'dataMax']} - ticks={this._timeTicks} - > - - max * 1.2]} - /> - - - - - - - )} - {nodes && ( - - + + max * 1.2]} + /> + + + + + + + )} + {nodes && ( + + + + + + ''} + domain={[0, 'dataMax']} + ticks={_timeTicks} > - - - - {/* */} - ''} - domain={[0, 'dataMax']} - ticks={this._timeTicks} - > - - max * 1.2]} - /> - - - - - - )} -
-
- ); - } +
+ ); } -export const ConnectedPerformance = connectPlayer((state) => ({ - performanceChartTime: state.performanceChartTime, - performanceChartData: state.performanceChartData, - connType: state.connType, - connBandwidth: state.connBandwidth, - avaliability: state.performanceAvaliability, -}))(Performance); +export const ConnectedPerformance = connect((state: any) => ({ + userDeviceHeapSize: state.getIn(['sessions', 'current', 'userDeviceHeapSize']), +}))(observer(Performance)); diff --git a/frontend/app/components/Session_/Performance/index.js b/frontend/app/components/Session_/Performance/index.js index e0e5ed895..967fe1364 100644 --- a/frontend/app/components/Session_/Performance/index.js +++ b/frontend/app/components/Session_/Performance/index.js @@ -1,2 +1,2 @@ -export { default } from './Performance'; -export * from './Performance'; \ No newline at end of file +export { ConnectedPerformance as default } from './Performance'; +export * from './Performance'; diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index df6957b6c..4af1be29e 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -67,8 +67,6 @@ function Player(props) { if (!playerContext.player) return null; - console.log(bottomBlock) - const maxWidth = activeTab ? 'calc(100vw - 270px)' : '100vw'; return (
{ if (_list.current) { - console.log('active index') // @ts-ignore _list.current.scrollToRow(activeIndex); } }, [activeIndex]); - console.log('rerender') return ( !!l) || []; const canExpand = lines.length > 1; From 80ebc26daa34a0e24b305cece66adeab828008c9 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 25 Nov 2022 16:19:16 +0100 Subject: [PATCH 31/64] refactor(ui/player): remove console log --- .../app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx index 664ace432..51322cc14 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx @@ -136,7 +136,6 @@ function ConsolePanel() { const _rowRenderer = ({ index, key, parent, style }: any) => { const item = filteredList[index]; - console.log({ ...filteredList }, { ...item }) return ( // @ts-ignore From 05c035255306866e17583e3156a84fbaf88c9133 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 25 Nov 2022 16:36:24 +0100 Subject: [PATCH 32/64] refactor(ui/player): fix performance crash loop --- frontend/app/components/Session/PlayerContent.js | 6 ++++++ .../app/components/Session_/Performance/Performance.tsx | 9 +++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/frontend/app/components/Session/PlayerContent.js b/frontend/app/components/Session/PlayerContent.js index c0a695173..15187d15d 100644 --- a/frontend/app/components/Session/PlayerContent.js +++ b/frontend/app/components/Session/PlayerContent.js @@ -7,6 +7,12 @@ import RightBlock from './RightBlock'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; + +const TABS = { + EVENTS: 'User Steps', + HEATMAPS: 'Click Map', +}; + function PlayerContent({ session, live, fullscreen, activeTab, setActiveTab }) { const { store } = React.useContext(PlayerContext) diff --git a/frontend/app/components/Session_/Performance/Performance.tsx b/frontend/app/components/Session_/Performance/Performance.tsx index 45f944b50..eca011fcb 100644 --- a/frontend/app/components/Session_/Performance/Performance.tsx +++ b/frontend/app/components/Session_/Performance/Performance.tsx @@ -179,6 +179,8 @@ function Performance({ userDeviceHeapSize: number; }) { const { player, store } = React.useContext(PlayerContext); + const [_timeTicks, setTicks] = React.useState([]) + const [_data, setData] = React.useState([]) const { performanceChartTime, @@ -188,8 +190,11 @@ function Performance({ performanceAvaliability: avaliability, } = store.get(); - const _timeTicks = generateTicks(performanceChartData); - const _data = addFpsMetadata(performanceChartData); + React.useState(() => { + setTicks(generateTicks(performanceChartData)); + setData(addFpsMetadata(performanceChartData)); + }) + const onDotClick = ({ index: pointer }: { index: number }) => { const point = _data[pointer]; From d37f81190d911649e1d3025f7567f770b0c18677 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Fri, 25 Nov 2022 16:42:17 +0100 Subject: [PATCH 33/64] refactor(player): move log type to player --- .../DevTools/ConsolePanel/ConsolePanel.tsx | 32 +++++------- frontend/app/player/index.ts | 1 + frontend/app/player/web/MessageManager.ts | 12 ++--- frontend/app/player/web/WebPlayer.ts | 11 +++- frontend/app/player/web/types.ts | 22 ++++++++ frontend/app/types/run/run.js | 4 -- frontend/app/types/session/log.tsx | 51 ------------------- frontend/app/types/session/session.ts | 4 -- 8 files changed, 50 insertions(+), 87 deletions(-) create mode 100644 frontend/app/player/web/types.ts delete mode 100644 frontend/app/types/session/log.tsx diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx index 51322cc14..44b9f3d00 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; -import { Log, LEVEL } from 'Types/session/log'; +import { LogLevel } from 'Player'; import BottomBlock from '../BottomBlock'; import { Tabs, Input, Icon, NoContent } from 'UI'; import cn from 'classnames'; @@ -19,11 +19,11 @@ const WARNINGS = 'WARNINGS'; const ERRORS = 'ERRORS'; const LEVEL_TAB = { - [LEVEL.INFO]: INFO, - [LEVEL.LOG]: INFO, - [LEVEL.WARNING]: WARNINGS, - [LEVEL.ERROR]: ERRORS, - [LEVEL.EXCEPTION]: ERRORS, + [LogLevel.INFO]: INFO, + [LogLevel.LOG]: INFO, + [LogLevel.WARNING]: WARNINGS, + [LogLevel.ERROR]: ERRORS, + [LogLevel.EXCEPTION]: ERRORS, }; const TABS = [ALL, ERRORS, WARNINGS, INFO].map((tab) => ({ text: tab, key: tab })); @@ -36,19 +36,19 @@ function renderWithNL(s = '') { const getIconProps = (level: any) => { switch (level) { - case LEVEL.INFO: - case LEVEL.LOG: + case LogLevel.INFO: + case LogLevel.LOG: return { name: 'console/info', color: 'blue2', }; - case LEVEL.WARN: - case LEVEL.WARNING: + case LogLevel.WARN: + case LogLevel.WARNING: return { name: 'console/warning', color: 'red2', }; - case LEVEL.ERROR: + case LogLevel.ERROR: return { name: 'console/error', color: 'red', @@ -81,16 +81,8 @@ function ConsolePanel() { const jump = (t: number) => player.jump(t) const { logList, exceptionsList, time } = store.get() - const logExceptions = exceptionsList.map(({ time, errorId, name }: any) => - Log({ - level: LEVEL.ERROR, - value: name, - time, - errorId, - }) - ); // @ts-ignore - const logs = logList.concat(logExceptions) + const logs = logList.concat(exceptionsList) const onTabClick = (activeTab: any) => devTools.update(INDEX_KEY, { activeTab }); const onFilterChange = ({ target: { value } }: any) => { diff --git a/frontend/app/player/index.ts b/frontend/app/player/index.ts index 949088e43..55c67fb0b 100644 --- a/frontend/app/player/index.ts +++ b/frontend/app/player/index.ts @@ -1,6 +1,7 @@ export * from './web/assist/AssistManager'; export * from './web/assist/LocalStream'; export * from './web/WebPlayer'; +export * from './web/types'; export * from './create'; export type { MarkedTarget } from './web/TargetMarker' diff --git a/frontend/app/player/web/MessageManager.ts b/frontend/app/player/web/MessageManager.ts index bca4dc34a..fec4fd9cc 100644 --- a/frontend/app/player/web/MessageManager.ts +++ b/frontend/app/player/web/MessageManager.ts @@ -4,7 +4,7 @@ import logger from 'App/logger'; import Resource, { TYPES } from 'Types/session/resource'; import { TYPES as EVENT_TYPES } from 'Types/session/event'; -import { Log } from 'Types/session/log'; +import { Log } from './types'; import { toast } from 'react-toastify'; @@ -394,12 +394,10 @@ export default class MessageManager { /* Lists: */ case "console_log": if (msg.level === 'debug') break; - this.lists.lists.log.append(Log({ - level: msg.level, - value: msg.value, - time, - index, - })) + this.lists.lists.log.append( + // @ts-ignore : TODO: enums in the message schema + Log(msg) + ) break; case "fetch": this.lists.lists.fetch.append(Resource({ diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index 78eaa1435..20115d590 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -1,3 +1,5 @@ +import { Log, LogLevel } from './types' + import type { Store } from '../common/types' import Player, { State as PlayerState } from '../player/Player' @@ -36,7 +38,14 @@ export default class WebPlayer extends Player { event: session.events.toJSON(), stack: session.stackEvents.toJSON(), resource: session.resources.toJSON(), - exceptions: session.errors.toJSON(), + exceptions: session.errors.toJSON().map(({ time, errorId, name }: any) => + Log({ + level: LogLevel.ERROR, + value: name, + time, + errorId, + }) + ), } const screen = new Screen() diff --git a/frontend/app/player/web/types.ts b/frontend/app/player/web/types.ts new file mode 100644 index 000000000..ee185dfbe --- /dev/null +++ b/frontend/app/player/web/types.ts @@ -0,0 +1,22 @@ +export enum LogLevel { + INFO = 'info', + LOG = 'log', + WARNING = 'warning', + WARN = 'warn', + ERROR = 'error', + EXCEPTION = 'exception', +} + +interface ILog { + level: LogLevel + value: string + time: number + index?: number + errorId?: string +} + +export const Log = (log: ILog) => ({ + isRed: () => log.level === LogLevel.EXCEPTION || log.level === LogLevel.ERROR, + isYellow: () => log.level === LogLevel.WARNING || log.level === LogLevel.WARN, + ...log +}) diff --git a/frontend/app/types/run/run.js b/frontend/app/types/run/run.js index b084a25fe..cfe35488e 100644 --- a/frontend/app/types/run/run.js +++ b/frontend/app/types/run/run.js @@ -4,7 +4,6 @@ import Environment from 'Types/environment'; import stepFromJS from './step'; import seleniumStepFromJS from './seleniumStep'; import Resource from '../session/resource'; -import Log from '../session/log'; export const NOT_FETCHED = undefined; export const QUEUED = 'queued'; @@ -45,7 +44,6 @@ class Run extends Record({ finishedAt: undefined, steps: List(), resources: [], - logs: [], seleniumSteps: List(), url_browser_logs: undefined, url_logs: undefined, @@ -148,7 +146,6 @@ function fromJS(run = {}) { .map(r => r.set("time", r.time - firstResourceTime)) .sort((r1, r2) => r1.time - r2.time).toArray() - const logs = List(run.console).map(Log); const screenshotUrl = run.screenshot_url || seleniumSteps.find(({ screenshotUrl }) => !!screenshotUrl, null, {}).screenshotUrl; @@ -163,7 +160,6 @@ function fromJS(run = {}) { lastExecutedString, steps, resources, - logs, seleniumSteps, tags, environment, diff --git a/frontend/app/types/session/log.tsx b/frontend/app/types/session/log.tsx deleted file mode 100644 index 28cabf329..000000000 --- a/frontend/app/types/session/log.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import Record from 'Types/Record'; - -export const INFO = 'info'; -export const LOG = 'log'; -export const WARNING = 'warning'; -export const WARN = 'warn'; -export const ERROR = 'error'; -export const EXCEPTION = 'exception'; -export const LEVEL = { - INFO, - LOG, - WARNING, - WARN, - ERROR, - EXCEPTION, -} - -export function isRed(log) { - return -} - -export default Record({ - level: '', - value: '', - time: undefined, - index: undefined, - errorId: undefined, -}, { - methods: { - isRed() { - return isRed(this); - }, - isYellow() { - return this.level === WARNING || WARN; - } - } -}); - -interface ILog { - level: string - value: string - time: number - index?: number - errorId?: string -} - -export const Log = (log: ILog) => ({ - isRed: () => log.level === EXCEPTION || log.level === ERROR, - isYellow: () => log.level === WARNING || log.level === WARN, - ...log -}) diff --git a/frontend/app/types/session/session.ts b/frontend/app/types/session/session.ts index 961b34864..5a472b043 100644 --- a/frontend/app/types/session/session.ts +++ b/frontend/app/types/session/session.ts @@ -2,7 +2,6 @@ import Record from 'Types/Record'; import { List, Map } from 'immutable'; import { Duration } from 'luxon'; import SessionEvent, { TYPES } from './event'; -import Log from './log'; import StackEvent from './stackEvent'; import Resource from './resource'; import SessionError from './error'; @@ -32,7 +31,6 @@ export default Record( startedAt: 0, duration: 0, events: List(), - logs: List(), stackEvents: List(), resources: List(), missedResources: List(), @@ -125,7 +123,6 @@ export default Record( .map((r) => r.set('time', r.time - firstResourceTime)) .sort((r1, r2) => r1.time - r2.time); const missedResources = resources.filter(({ success }) => !success); - const logs = List(session.logs).map(Log); const stackEventsList = List(stackEvents) .concat(List(session.userEvents)) @@ -156,7 +153,6 @@ export default Record( errors: exceptions, siteId: projectId, events, - logs, stackEvents: stackEventsList, resources, missedResources, From aa8c3ebbbc399714cdeb261ba8404a9beb8950a1 Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 25 Nov 2022 17:08:52 +0100 Subject: [PATCH 34/64] refactor(ui/player): deprecate unused devtools tabs, refactor exceptions --- .../app/components/Session/PlayerContent.js | 1 - .../Session_/Exceptions/Exceptions.js | 145 ------------------ .../Session_/Exceptions/Exceptions.tsx | 126 +++++++++++++++ .../Fetch/{Fetch.js => Fetch.DEPRECATED.js} | 0 .../{LongTasks.js => LongTasks.DEPRECATED.js} | 0 .../{Controls.js => Controls.DEPRECATED.js} | 0 .../app/components/Session_/Player/Player.js | 2 - .../{Storage.js => Storage.DEPRECATED.js} | 0 frontend/app/player/player/Animator.ts | 2 +- 9 files changed, 127 insertions(+), 149 deletions(-) delete mode 100644 frontend/app/components/Session_/Exceptions/Exceptions.js create mode 100644 frontend/app/components/Session_/Exceptions/Exceptions.tsx rename frontend/app/components/Session_/Fetch/{Fetch.js => Fetch.DEPRECATED.js} (100%) rename frontend/app/components/Session_/LongTasks/{LongTasks.js => LongTasks.DEPRECATED.js} (100%) rename frontend/app/components/Session_/Player/Controls/{Controls.js => Controls.DEPRECATED.js} (100%) rename frontend/app/components/Session_/Storage/{Storage.js => Storage.DEPRECATED.js} (100%) diff --git a/frontend/app/components/Session/PlayerContent.js b/frontend/app/components/Session/PlayerContent.js index 15187d15d..64d896455 100644 --- a/frontend/app/components/Session/PlayerContent.js +++ b/frontend/app/components/Session/PlayerContent.js @@ -7,7 +7,6 @@ import RightBlock from './RightBlock'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; - const TABS = { EVENTS: 'User Steps', HEATMAPS: 'Click Map', diff --git a/frontend/app/components/Session_/Exceptions/Exceptions.js b/frontend/app/components/Session_/Exceptions/Exceptions.js deleted file mode 100644 index 2b0c206b4..000000000 --- a/frontend/app/components/Session_/Exceptions/Exceptions.js +++ /dev/null @@ -1,145 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import { getRE } from 'App/utils'; -import { - NoContent, - Loader, - Input, - ErrorItem, - SlideModal, - ErrorDetails, - Link, - QuestionMarkHint, -} from 'UI'; -import { fetchErrorStackList } from 'Duck/sessions'; -import { connectPlayer, jump } from 'Player'; -import { error as errorRoute } from 'App/routes'; -import Autoscroll from '../Autoscroll'; -import BottomBlock from '../BottomBlock'; - -@connectPlayer((state) => ({ - logs: state.logListNow, - exceptions: state.exceptionsList, - // exceptionsNow: state.exceptionsListNow, -})) -@connect( - (state) => ({ - session: state.getIn(['sessions', 'current']), - errorStack: state.getIn(['sessions', 'errorStack']), - sourcemapUploaded: state.getIn(['sessions', 'sourcemapUploaded']), - loading: state.getIn(['sessions', 'fetchErrorStackList', 'loading']), - }), - { fetchErrorStackList } -) -export default class Exceptions extends React.PureComponent { - state = { - filter: '', - currentError: null, - }; - - onFilterChange = ({ target: { value } }) => this.setState({ filter: value }); - - setCurrentError = (err) => { - const { session } = this.props; - this.props.fetchErrorStackList(session.sessionId, err.errorId); - this.setState({ currentError: err }); - }; - closeModal = () => this.setState({ currentError: null }); - - render() { - const { exceptions, loading, errorStack, sourcemapUploaded } = this.props; - const { filter, currentError } = this.state; - const filterRE = getRE(filter, 'i'); - - const filtered = exceptions.filter((e) => filterRE.test(e.name) || filterRE.test(e.message)); - - return ( - <> - -
- - {currentError.name} - - {currentError.function} -
-
{currentError.message}
-
- ) - } - isDisplayed={currentError != null} - content={ - currentError && ( -
- - - - - -
- ) - } - onClose={this.closeModal} - /> - - -
- Exceptions -
- -
- - - - Upload Source Maps{' '} - - and see source code context obtained from stack traces in their original form. - - } - /> -
-
- - - - {filtered.map((e, index) => ( - jump(e.time)} - error={e} - key={e.key} - onErrorClick={(jsEvent) => { - jsEvent.stopPropagation(); - jsEvent.preventDefault(); - this.setCurrentError(e); - }} - /> - ))} - - - -
- - ); - } -} diff --git a/frontend/app/components/Session_/Exceptions/Exceptions.tsx b/frontend/app/components/Session_/Exceptions/Exceptions.tsx new file mode 100644 index 000000000..e263c9106 --- /dev/null +++ b/frontend/app/components/Session_/Exceptions/Exceptions.tsx @@ -0,0 +1,126 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { getRE } from 'App/utils'; +import { + NoContent, + Loader, + Input, + ErrorItem, + SlideModal, + ErrorDetails, + Link, + QuestionMarkHint, +} from 'UI'; +import { error as errorRoute } from 'App/routes'; +import Autoscroll from '../Autoscroll'; +import BottomBlock from '../BottomBlock'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; + +interface IProps { + loading: boolean; + sourcemapUploaded: boolean; + errorStack: Record; +} + +function Exceptions({ errorStack, sourcemapUploaded, loading }: IProps) { + const { player, store } = React.useContext(PlayerContext); + const { logListNow: logs, exceptionsList: exceptions } = store.get(); + const [filter, setFilter] = React.useState(''); + const [currentError, setCurrentErrorVal] = React.useState(null); + + const onFilterChange = ({ target: { value } }: any) => setFilter(value); + const closeModal = () => setCurrentErrorVal(null); + + const filterRE = getRE(filter, 'i'); + const filtered = exceptions.filter((e: any) => filterRE.test(e.name) || filterRE.test(e.message)); + + return ( + <> + +
+ + {currentError.name} + + {currentError.function} +
+
{currentError.message}
+
+ ) + } + isDisplayed={currentError != null} + content={ + currentError && ( +
+ + + + + +
+ ) + } + onClose={closeModal} + /> + + +
+ Exceptions +
+ +
+ + + + Upload Source Maps{' '} + + and see source code context obtained from stack traces in their original form. + + } + /> +
+
+ + + + {filtered.map((e: any, index) => ( + + player.jump(e.time)} error={e} /> + + ))} + + + +
+ + ); +} + +export default connect((state: any) => ({ + errorStack: state.getIn(['sessions', 'errorStack']), + sourcemapUploaded: state.getIn(['sessions', 'sourcemapUploaded']), + loading: state.getIn(['sessions', 'fetchErrorStackList', 'loading']), +}))(observer(Exceptions)); diff --git a/frontend/app/components/Session_/Fetch/Fetch.js b/frontend/app/components/Session_/Fetch/Fetch.DEPRECATED.js similarity index 100% rename from frontend/app/components/Session_/Fetch/Fetch.js rename to frontend/app/components/Session_/Fetch/Fetch.DEPRECATED.js diff --git a/frontend/app/components/Session_/LongTasks/LongTasks.js b/frontend/app/components/Session_/LongTasks/LongTasks.DEPRECATED.js similarity index 100% rename from frontend/app/components/Session_/LongTasks/LongTasks.js rename to frontend/app/components/Session_/LongTasks/LongTasks.DEPRECATED.js diff --git a/frontend/app/components/Session_/Player/Controls/Controls.js b/frontend/app/components/Session_/Player/Controls/Controls.DEPRECATED.js similarity index 100% rename from frontend/app/components/Session_/Player/Controls/Controls.js rename to frontend/app/components/Session_/Player/Controls/Controls.DEPRECATED.js diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index 4af1be29e..b8d295361 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -15,7 +15,6 @@ import { PERFORMANCE, GRAPHQL, EXCEPTIONS, - LONGTASKS, INSPECTOR, OVERVIEW, } from 'Duck/components/player'; @@ -93,7 +92,6 @@ function Player(props) { {bottomBlock === PERFORMANCE && } {bottomBlock === GRAPHQL && } {bottomBlock === EXCEPTIONS && } - {bottomBlock === LONGTASKS && } {bottomBlock === INSPECTOR && }
)} diff --git a/frontend/app/components/Session_/Storage/Storage.js b/frontend/app/components/Session_/Storage/Storage.DEPRECATED.js similarity index 100% rename from frontend/app/components/Session_/Storage/Storage.js rename to frontend/app/components/Session_/Storage/Storage.DEPRECATED.js diff --git a/frontend/app/player/player/Animator.ts b/frontend/app/player/player/Animator.ts index 83e10f098..787df725b 100644 --- a/frontend/app/player/player/Animator.ts +++ b/frontend/app/player/player/Animator.ts @@ -182,7 +182,7 @@ export default class Animator { return this.jump( Math.max( 0, - time - interval + time + interval ) ); } From 3a6a2dd4a631f768e61bdb0ac8da51f43343849c Mon Sep 17 00:00:00 2001 From: sylenien Date: Fri, 25 Nov 2022 17:59:49 +0100 Subject: [PATCH 35/64] refactor(ui/player): remove key and prop warnings --- frontend/app/components/Errors/List/List.js | 19 +++++---- .../components/Session/IOSPlayer/Crashes.js | 3 +- .../app/components/Session/IOSPlayer/Logs.js | 9 ++--- .../Console/ConsoleRow/ConsoleRow.tsx | 2 +- .../Session_/Exceptions/Exceptions.tsx | 1 - .../components/Session_/GraphQL/GraphQL.js | 1 - .../Session_/Network/NetworkContent.js | 1 - .../DevTools/ConsolePanel/ConsolePanel.tsx | 39 ++++++++++--------- .../shared/DevTools/ConsoleRow/ConsoleRow.tsx | 4 +- .../DevTools/NetworkPanel/NetworkPanel.tsx | 1 - .../StackEventPanel/StackEventPanel.tsx | 1 - .../LiveSessionSearchField.tsx | 3 +- 12 files changed, 38 insertions(+), 46 deletions(-) diff --git a/frontend/app/components/Errors/List/List.js b/frontend/app/components/Errors/List/List.js index 9f379319a..8a22a1f2d 100644 --- a/frontend/app/components/Errors/List/List.js +++ b/frontend/app/components/Errors/List/List.js @@ -25,7 +25,7 @@ const sortOptions = Object.entries(sortOptionsMap) @connect(state => ({ loading: state.getIn([ "errors", "loading" ]), - resolveToggleLoading: state.getIn(["errors", "resolve", "loading"]) || + resolveToggleLoading: state.getIn(["errors", "resolve", "loading"]) || state.getIn(["errors", "unresolve", "loading"]), ignoreLoading: state.getIn([ "errors", "ignore", "loading" ]), mergeLoading: state.getIn([ "errors", "merge", "loading" ]), @@ -54,19 +54,19 @@ export default class List extends React.PureComponent { } this.debounceFetch = debounce(this.props.editOptions, 1000); } - + componentDidMount() { this.props.applyFilter({ }); } check = ({ errorId }) => { const { checkedIds } = this.state; - const newCheckedIds = checkedIds.contains(errorId) - ? checkedIds.remove(errorId) + const newCheckedIds = checkedIds.contains(errorId) + ? checkedIds.remove(errorId) : checkedIds.add(errorId); this.setState({ checkedAll: newCheckedIds.size === this.props.list.size, - checkedIds: newCheckedIds + checkedIds: newCheckedIds }); } @@ -184,7 +184,7 @@ export default class List extends React.PureComponent { onClick={ this.unresolve } disabled={ someLoading || currentCheckedIds.size === 0} /> - } + } { status !== IGNORED && - } + }
- Sort By + Sort By -
- - - - - {[ - { - label: 'Start', - width: 90, - render: renderStart, - }, - { - label: 'Status', - width: 70, - render: renderDefaultStatus, - }, - { - label: 'Type', - dataKey: 'operationKind', - width: 60, - }, - { - label: 'Name', - width: 240, - render: renderName, - }, - ]} - - - - - - ); - } -} diff --git a/frontend/app/components/Session_/GraphQL/GraphQL.tsx b/frontend/app/components/Session_/GraphQL/GraphQL.tsx new file mode 100644 index 000000000..6f89a5ec1 --- /dev/null +++ b/frontend/app/components/Session_/GraphQL/GraphQL.tsx @@ -0,0 +1,174 @@ +import React, { useEffect } from 'react'; +import { NoContent, Input, SlideModal, CloseButton, Button } from 'UI'; +import { getRE } from 'App/utils'; +import BottomBlock from '../BottomBlock'; +import TimeTable from '../TimeTable'; +import GQLDetails from './GQLDetails'; +import { renderStart } from 'Components/Session_/Network/NetworkContent'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; + +function renderDefaultStatus() { + return '2xx-3xx'; +} + +export function renderName(r: Record) { + const { player } = React.useContext(PlayerContext); + + return ( +
+
{r.operationName}
+ +
+ ); +} + +function GraphQL() { + const { player, store } = React.useContext(PlayerContext); + + const { graphqlList: list, graphqlListNow: listNow, time, livePlay } = store.get(); + + const defaultState = { + filter: '', + filteredList: list, + filteredListNow: listNow, + // @ts-ignore + current: null, + currentIndex: 0, + showFetchDetails: false, + hasNextError: false, + hasPreviousError: false, + lastActiveItem: 0, + }; + + const [state, setState] = React.useState(defaultState); + + const filterList = (list: any, value: string) => { + const filterRE = getRE(value, 'i'); + + return value + ? list.filter( + (r: any) => + filterRE.test(r.operationKind) || + filterRE.test(r.operationName) || + filterRE.test(r.variables) + ) + : list; + }; + + const onFilterChange = ({ target: { value } }: React.ChangeEvent) => { + const filtered = filterList(list, value); + setState((prevState) => ({ + ...prevState, + filter: value, + filteredList: filtered, + currentIndex: 0, + })); + }; + + const setCurrent = (item: any, index: number) => { + if (!livePlay) { + player.pause(); + player.jump(item.time); + } + setState((prevState) => ({ ...prevState, current: item, currentIndex: index })); + }; + + const closeModal = () => + setState((prevState) => ({ ...prevState, current: null, showFetchDetails: false })); + + useEffect(() => { + const filtered = filterList(listNow, state.filter); + if (filtered.length !== lastActiveItem) { + setState((prevState) => ({ ...prevState, lastActiveItem: listNow.length })); + } + }, [time]); + + const { current, currentIndex, filteredList, lastActiveItem } = state; + + return ( + + +

GraphQL

+
+ +
+
+ } + isDisplayed={current != null} + content={ + current && ( + + ) + } + onClose={closeModal} + /> + + + GraphQL +
+ +
+
+ + + + {[ + { + label: 'Start', + width: 90, + render: renderStart, + }, + { + label: 'Status', + width: 70, + render: renderDefaultStatus, + }, + { + label: 'Type', + dataKey: 'operationKind', + width: 60, + }, + { + label: 'Name', + width: 240, + render: renderName, + }, + ]} + + + +
+ + ); +} + +export default observer(GraphQL); diff --git a/frontend/app/components/Session_/Network/Network.js b/frontend/app/components/Session_/Network/Network.DEPRECATED.js similarity index 100% rename from frontend/app/components/Session_/Network/Network.js rename to frontend/app/components/Session_/Network/Network.DEPRECATED.js diff --git a/frontend/app/components/Session_/Profiler/Profiler.js b/frontend/app/components/Session_/Profiler/Profiler.DEPRECATED.js similarity index 100% rename from frontend/app/components/Session_/Profiler/Profiler.js rename to frontend/app/components/Session_/Profiler/Profiler.DEPRECATED.js diff --git a/frontend/app/components/Session_/StackEvents/StackEvents.js b/frontend/app/components/Session_/StackEvents/StackEvents.DEPRECATED.js similarity index 100% rename from frontend/app/components/Session_/StackEvents/StackEvents.js rename to frontend/app/components/Session_/StackEvents/StackEvents.DEPRECATED.js diff --git a/frontend/app/components/ui/Button/Button.tsx b/frontend/app/components/ui/Button/Button.tsx index dc73f9e60..35e3588fa 100644 --- a/frontend/app/components/ui/Button/Button.tsx +++ b/frontend/app/components/ui/Button/Button.tsx @@ -5,7 +5,7 @@ import { CircularLoader, Icon, Tooltip } from 'UI'; interface Props { className?: string; children?: React.ReactNode; - onClick?: () => void; + onClick?: (e: React.MouseEvent) => void; disabled?: boolean; type?: 'button' | 'submit' | 'reset'; variant?: 'default' | 'primary' | 'text' | 'text-primary' | 'text-red' | 'outline' | 'green'; From 5e89bbbbb6b624a9ad7e717ce02259f56ae05f7d Mon Sep 17 00:00:00 2001 From: sylenien Date: Mon, 28 Nov 2022 11:29:31 +0100 Subject: [PATCH 44/64] refactor(ui/player): remove unused, deprecated etc --- .../Session/Layout/ToolPanel/StackEvents.js | 19 +++++++++---------- .../components/Session_/BottomBlock/Header.js | 2 +- .../components/Session_/BottomBlock/tabs.js | 9 --------- .../Session_/EventsBlock/NoteEvent.tsx | 3 --- .../Session_/Player/Controls/Controls.tsx | 2 +- .../Player/Overlay/ElementsMarker/Marker.tsx | 7 +++++-- .../app/components/Session_/Player/Player.js | 3 +-- .../app/components/Session_/PlayerBlock.js | 1 - .../shared/DevTools/BottomBlock/Header.js | 2 +- .../shared/DevTools/BottomBlock/tabs.js | 9 --------- 10 files changed, 18 insertions(+), 39 deletions(-) delete mode 100644 frontend/app/components/Session_/BottomBlock/tabs.js delete mode 100644 frontend/app/components/shared/DevTools/BottomBlock/tabs.js diff --git a/frontend/app/components/Session/Layout/ToolPanel/StackEvents.js b/frontend/app/components/Session/Layout/ToolPanel/StackEvents.js index cdfccff31..c66c7d7c2 100644 --- a/frontend/app/components/Session/Layout/ToolPanel/StackEvents.js +++ b/frontend/app/components/Session/Layout/ToolPanel/StackEvents.js @@ -2,9 +2,8 @@ import React from 'react'; import { connect } from 'react-redux'; import { useState } from 'react'; import { NoContent, Tabs } from 'UI'; -import withEnumToggle from 'HOCs/withEnumToggle'; import { hideHint } from 'Duck/components/player'; -import { typeList } from 'Types/session/stackEvent'; +import { typeList } from 'Types/session/stackEvent'; import * as PanelLayout from './PanelLayout'; import UserEvent from 'Components/Session_/StackEvents/UserEvent'; @@ -14,7 +13,7 @@ const ALL = 'ALL'; const TABS = [ ALL, ...typeList ].map(tab =>({ text: tab, key: tab })); -function StackEvents({ +function StackEvents({ stackEvents, hintIsHidden, hideHint, @@ -28,10 +27,10 @@ function StackEvents({ return ( <> - @@ -39,12 +38,12 @@ function StackEvents({ Integrations {' and '} - Events + Events { ' make debugging easier. Sync your backend logs and custom events with session replay.' }

@@ -66,8 +65,8 @@ function StackEvents({ } export default connect(state => ({ - hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'stack']) || + hintIsHidden: state.getIn(['components', 'player', 'hiddenHints', 'stack']) || !state.getIn([ 'site', 'list' ]).some(s => s.stackIntegrations), }), { hideHint -})(StackEvents); \ No newline at end of file +})(StackEvents); diff --git a/frontend/app/components/Session_/BottomBlock/Header.js b/frontend/app/components/Session_/BottomBlock/Header.js index 15cdf3365..4812305b7 100644 --- a/frontend/app/components/Session_/BottomBlock/Header.js +++ b/frontend/app/components/Session_/BottomBlock/Header.js @@ -2,7 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import cn from 'classnames'; import { closeBottomBlock } from 'Duck/components/player'; -import { Input, CloseButton } from 'UI'; +import { CloseButton } from 'UI'; import stl from './header.module.css'; const Header = ({ diff --git a/frontend/app/components/Session_/BottomBlock/tabs.js b/frontend/app/components/Session_/BottomBlock/tabs.js deleted file mode 100644 index 6addd161e..000000000 --- a/frontend/app/components/Session_/BottomBlock/tabs.js +++ /dev/null @@ -1,9 +0,0 @@ -// import { NONE, CONSOLE, NETWORK, STACKEVENTS, REDUX_STATE, PROFILER, PERFORMANCE, GRAPHQL } from 'Duck/components/player'; -// -// -// export default { -// [NONE]: { -// Component: null, -// -// } -// } \ No newline at end of file diff --git a/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx b/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx index 7ad33cece..676b1f901 100644 --- a/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx +++ b/frontend/app/components/Session_/EventsBlock/NoteEvent.tsx @@ -9,7 +9,6 @@ import copy from 'copy-to-clipboard'; import { toast } from 'react-toastify'; import { session } from 'App/routes'; import { confirm } from 'UI'; -import { filterOutNote as filterOutTimelineNote } from 'Player'; import { TeamBadge } from 'Shared/SessionListContainer/components/Notes'; interface Props { @@ -24,7 +23,6 @@ function NoteEvent(props: Props) { const { settingsStore, notesStore } = useStore(); const { timezone } = settingsStore.sessionSettings; - console.log(props.noEdit); const onEdit = () => { props.onEdit({ isVisible: true, @@ -60,7 +58,6 @@ function NoteEvent(props: Props) { ) { notesStore.deleteNote(props.note.noteId).then((r) => { props.filterOutNote(props.note.noteId); - filterOutTimelineNote(props.note.noteId); toast.success('Note deleted'); }); } diff --git a/frontend/app/components/Session_/Player/Controls/Controls.tsx b/frontend/app/components/Session_/Player/Controls/Controls.tsx index d88f8eeeb..3f2fffd01 100644 --- a/frontend/app/components/Session_/Player/Controls/Controls.tsx +++ b/frontend/app/components/Session_/Player/Controls/Controls.tsx @@ -1,7 +1,7 @@ import React from 'react'; import cn from 'classnames'; import { connect } from 'react-redux'; -import { STORAGE_TYPES, selectStorageType, selectStorageListNow } from 'Player'; +import { STORAGE_TYPES, selectStorageType } from 'Player'; import LiveTag from 'Shared/LiveTag'; import { Icon, Tooltip } from 'UI'; diff --git a/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx b/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx index cbb772020..a81dca109 100644 --- a/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx +++ b/frontend/app/components/Session_/Player/Overlay/ElementsMarker/Marker.tsx @@ -2,8 +2,8 @@ import React from 'react'; import type { MarkedTarget } from 'Player'; import cn from 'classnames'; import stl from './Marker.module.css'; -import { activeTarget } from 'Player'; import { Tooltip } from 'UI'; +import { PlayerContext } from 'App/components/Session/playerContext'; interface Props { target: MarkedTarget; @@ -17,11 +17,14 @@ export default function Marker({ target, active }: Props) { width: `${target.boundingRect.width}px`, height: `${target.boundingRect.height}px`, }; + const { player } = React.useContext(PlayerContext) + return (
activeTarget(target.index)} + // @ts-ignore + onClick={() => player.setActiveTarget(target.index)} >
{target.index + 1}
{target.count} Clicks
}> diff --git a/frontend/app/components/Session_/Player/Player.js b/frontend/app/components/Session_/Player/Player.js index bdcb0e925..d72e53b59 100644 --- a/frontend/app/components/Session_/Player/Player.js +++ b/frontend/app/components/Session_/Player/Player.js @@ -4,7 +4,6 @@ import { findDOMNode } from 'react-dom'; import cn from 'classnames'; import { EscapeButton } from 'UI'; import { hide as hideTargetDefiner } from 'Duck/components/targetDefiner'; -import { fullscreenOff } from 'Duck/components/player'; import { NONE, CONSOLE, @@ -17,9 +16,9 @@ import { EXCEPTIONS, INSPECTOR, OVERVIEW, + fullscreenOff, } from 'Duck/components/player'; import NetworkPanel from 'Shared/DevTools/NetworkPanel'; -// import StackEvents from '../StackEvents/StackEvents'; import Storage from '../Storage'; import { ConnectedPerformance } from '../Performance'; import GraphQL from '../GraphQL'; diff --git a/frontend/app/components/Session_/PlayerBlock.js b/frontend/app/components/Session_/PlayerBlock.js index d14d8b983..173b74e3a 100644 --- a/frontend/app/components/Session_/PlayerBlock.js +++ b/frontend/app/components/Session_/PlayerBlock.js @@ -1,7 +1,6 @@ import React from 'react'; import cn from 'classnames'; import { connect } from 'react-redux'; -import { NONE } from 'Duck/components/player'; import Player from './Player'; import SubHeader from './Subheader'; diff --git a/frontend/app/components/shared/DevTools/BottomBlock/Header.js b/frontend/app/components/shared/DevTools/BottomBlock/Header.js index 15dd7a0c9..f743ab3a5 100644 --- a/frontend/app/components/shared/DevTools/BottomBlock/Header.js +++ b/frontend/app/components/shared/DevTools/BottomBlock/Header.js @@ -2,7 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import cn from 'classnames'; import { closeBottomBlock } from 'Duck/components/player'; -import { Input, CloseButton } from 'UI'; +import { CloseButton } from 'UI'; import stl from './header.module.css'; const Header = ({ diff --git a/frontend/app/components/shared/DevTools/BottomBlock/tabs.js b/frontend/app/components/shared/DevTools/BottomBlock/tabs.js deleted file mode 100644 index 6addd161e..000000000 --- a/frontend/app/components/shared/DevTools/BottomBlock/tabs.js +++ /dev/null @@ -1,9 +0,0 @@ -// import { NONE, CONSOLE, NETWORK, STACKEVENTS, REDUX_STATE, PROFILER, PERFORMANCE, GRAPHQL } from 'Duck/components/player'; -// -// -// export default { -// [NONE]: { -// Component: null, -// -// } -// } \ No newline at end of file From 2662b97401a7bf72bdc1297d578b8a72070caac0 Mon Sep 17 00:00:00 2001 From: sylenien Date: Mon, 28 Nov 2022 11:36:23 +0100 Subject: [PATCH 45/64] refactor(ui/player): connect notes --- frontend/app/components/Session/WebPlayer.tsx | 3 --- .../app/components/Session_/Player/Controls/Timeline.js | 4 +++- frontend/app/mstore/notesStore.ts | 8 ++------ 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index d7e8a115b..779fe3a11 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -41,12 +41,9 @@ function WebPlayer(props: any) { ); setContextValue({ player: WebPlayerInst, store: PlayerStore }); - // initPlayer(session, jwt); TODOPlayer props.fetchMembers(); notesStore.fetchSessionNotes(session.sessionId).then((r) => { - // WebPlayerInst.injectNotes(r); - // PlayerStore.update({ notes: r }) const note = props.query.get('note'); if (note) { WebPlayerInst.pause(); diff --git a/frontend/app/components/Session_/Player/Controls/Timeline.js b/frontend/app/components/Session_/Player/Controls/Timeline.js index 9a4d0595b..307076588 100644 --- a/frontend/app/components/Session_/Player/Controls/Timeline.js +++ b/frontend/app/components/Session_/Player/Controls/Timeline.js @@ -10,6 +10,7 @@ import { debounce } from 'App/utils'; import TooltipContainer from './components/TooltipContainer'; import { PlayerContext } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; +import { useStore } from 'App/mstore'; const BOUNDRY = 0; @@ -26,6 +27,7 @@ let debounceTooltipChange = () => null; function Timeline(props) { const { player, store } = React.useContext(PlayerContext) const [wasPlaying, setWasPlaying] = React.useState(false); + const { notesStore } = useStore(); const { playing, time, @@ -36,8 +38,8 @@ function Timeline(props) { disabled, endTime, live, - notes = [], } = store.get() + const notes = notesStore.sessionNotes const progressRef = React.useRef(); const timelineRef = React.useRef(); diff --git a/frontend/app/mstore/notesStore.ts b/frontend/app/mstore/notesStore.ts index 46d7fc652..8e1db4aa7 100644 --- a/frontend/app/mstore/notesStore.ts +++ b/frontend/app/mstore/notesStore.ts @@ -2,13 +2,9 @@ import { makeAutoObservable } from "mobx" import { notesService } from "App/services" import { Note, WriteNote, iTag, NotesFilter } from 'App/services/NotesService' -interface SessionNotes { - [sessionId: string]: Note[] -} - export default class NotesStore { notes: Note[] = [] - sessionNotes: SessionNotes = {} + sessionNotes: Note[] = [] loading: boolean page = 1 pageSize = 10 @@ -48,7 +44,7 @@ export default class NotesStore { this.loading = true try { const notes = await notesService.getNotesBySessionId(sessionId) - this.sessionNotes[sessionId] = notes + this.sessionNotes = notes return notes; } catch (e) { console.error(e) From 1a377b42da967d06b66a38e026c2d8216499a64e Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 29 Nov 2022 10:28:52 +0100 Subject: [PATCH 46/64] fix(player): TargetMarker api types fix --- frontend/app/player/web/WebPlayer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index a07f12867..b9d35d686 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -112,11 +112,11 @@ export default class WebPlayer extends Player { } // Target Marker - setActiveTarget = (args: Parameters) => { + setActiveTarget = (...args: Parameters) => { this.targetMarker.setActiveTarget(...args) } - markTargets = (args: Parameters) => { + markTargets = (...args: Parameters) => { this.pause() this.targetMarker.markTargets(...args) } From c047ff166a77fdcd836cf8cb1afce60f89cd08bc Mon Sep 17 00:00:00 2001 From: sylenien Date: Mon, 28 Nov 2022 16:51:56 +0100 Subject: [PATCH 47/64] fix(ui): remove old func --- .../shared/DevTools/NetworkPanel/NetworkPanel.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index 1193ae86e..17712a295 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -168,13 +168,13 @@ function NetworkPanel() { }, [ showOnlyErrors, list ]) filteredList = useRegExListFilterMemo(filteredList, it => it.status, filter) filteredList = useRegExListFilterMemo(filteredList, it => it.name, filter) - filteredList = useRegExListFilterMemo(filteredList, it => it.type, filter) + filteredList = useRegExListFilterMemo(filteredList, it => it.type, filter) filteredList = useTabListFilterMemo(filteredList, it => TYPE_TO_TAB[it.type], ALL, activeTab) const onTabClick = (activeTab: typeof TAP_KEYS[number]) => devTools.update(INDEX_KEY, { activeTab }) const onFilterChange = ({ target: { value } }: React.ChangeEvent) => devTools.update(INDEX_KEY, { filter: value }) - // AutoScroll + // AutoScroll const autoScrollIndex = fetchListNow.length + resourceListNow.length const { timeoutStartAutoscroll, @@ -190,12 +190,12 @@ function NetworkPanel() { timeoutStartAutoscroll() } - const resourcesSize = useMemo(() => + const resourcesSize = useMemo(() => resourceList.reduce( (sum, { decodedBodySize }) => sum + (decodedBodySize || 0), 0, ), [ resourceList.length ]) - const transferredSize = useMemo(() => + const transferredSize = useMemo(() => resourceList.reduce( (sum, { headerSize, encodedBodySize }) => sum + (headerSize || 0) + (encodedBodySize || 0), @@ -226,12 +226,12 @@ function NetworkPanel() { setIsDetailsModalActive(true) showModal( 0} />, - { + { right: true, onClose: () => { setIsDetailsModalActive(false) timeoutStartAutoscroll() - } + } } ) devTools.update(INDEX_KEY, { index: filteredList.indexOf(item) }) @@ -327,7 +327,6 @@ function NetworkPanel() { sortBy={sortBy} sortAscending={sortAscending} onJump={(row: any) => { - setPauseSync(true); devTools.update(INDEX_KEY, { index: filteredList.indexOf(row) }); player.jump(row.time); }} From efcb317d6b13827cd8eb7967cd1b0113ec738597 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 29 Nov 2022 10:30:02 +0100 Subject: [PATCH 48/64] fix(ui): fix webpack setup --- frontend/webpack.config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/webpack.config.ts b/frontend/webpack.config.ts index e2655b67e..94df4d579 100644 --- a/frontend/webpack.config.ts +++ b/frontend/webpack.config.ts @@ -132,10 +132,12 @@ const config: Configuration = { devServer: { // static: path.join(__dirname, "public"), historyApiFallback: true, - host: 'localhost', + host: '0.0.0.0', open: true, port: 3333, hot: true, + compress: true, + allowedHosts: "all", }, }; From 3699fe5731407eca0276645c0984e47676441a2b Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Tue, 29 Nov 2022 10:49:58 +0100 Subject: [PATCH 49/64] chore(cicd): Update frontend build Signed-off-by: rjshrjndrn --- .github/workflows/frontend-dev.yaml | 87 +++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 .github/workflows/frontend-dev.yaml diff --git a/.github/workflows/frontend-dev.yaml b/.github/workflows/frontend-dev.yaml new file mode 100644 index 000000000..17a64df94 --- /dev/null +++ b/.github/workflows/frontend-dev.yaml @@ -0,0 +1,87 @@ +name: Frontend FOSS Deployment +on: + workflow_dispatch: + push: + branches: + - player-refactoring-phase-1 + paths: + - frontend/** +# Disable previous workflows for this action. +concurrency: + group: ${{ github.workflow }} #-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Cache node modules + uses: actions/cache@v1 + with: + path: node_modules + key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.OS }}-build- + ${{ runner.OS }}- + + - name: Docker login + run: | + docker login ${{ secrets.DEV_REGISTRY_URL }} -u ${{ secrets.DEV_DOCKER_USERNAME }} -p "${{ secrets.DEV_REGISTRY_TOKEN }}" + + - uses: azure/k8s-set-context@v1 + with: + method: kubeconfig + kubeconfig: ${{ secrets.DEV_KUBECONFIG }} # Use content of kubeconfig in secret. + id: setcontext + + - name: Building and Pushing frontend image + id: build-image + env: + DOCKER_REPO: ${{ secrets.DEV_REGISTRY_URL }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} + ENVIRONMENT: staging + run: | + set -x + cd frontend + mv .env.sample .env + docker run --rm -v /etc/passwd:/etc/passwd -u `id -u`:`id -g` -v $(pwd):/home/${USER} -w /home/${USER} --name node_build node:14-stretch-slim /bin/bash -c "yarn && yarn build" + # https://github.com/docker/cli/issues/1134#issuecomment-613516912 + DOCKER_BUILDKIT=1 docker build --target=cicd -t $DOCKER_REPO/frontend:${IMAGE_TAG} . + docker tag $DOCKER_REPO/frontend:${IMAGE_TAG} $DOCKER_REPO/frontend:${IMAGE_TAG}-ee + docker push $DOCKER_REPO/frontend:${IMAGE_TAG} + docker push $DOCKER_REPO/frontend:${IMAGE_TAG}-ee + + - name: Deploy to kubernetes foss + run: | + cd scripts/helmcharts/ + + set -x + cat <>/tmp/image_override.yaml + frontend: + image: + tag: ${IMAGE_TAG} + EOF + + ## Update secerts + sed -i "s#openReplayContainerRegistry.*#openReplayContainerRegistry: \"${{ secrets.OSS_REGISTRY_URL }}\"#g" vars.yaml + sed -i "s/postgresqlPassword: \"changeMePassword\"/postgresqlPassword: \"${{ secrets.DEV_PG_PASSWORD }}\"/g" vars.yaml + sed -i "s/accessKey: \"changeMeMinioAccessKey\"/accessKey: \"${{ secrets.DEV_MINIO_ACCESS_KEY }}\"/g" vars.yaml + sed -i "s/secretKey: \"changeMeMinioPassword\"/secretKey: \"${{ secrets.DEV_MINIO_SECRET_KEY }}\"/g" vars.yaml + sed -i "s/jwt_secret: \"SetARandomStringHere\"/jwt_secret: \"${{ secrets.DEV_JWT_SECRET }}\"/g" vars.yaml + sed -i "s/domainName: \"\"/domainName: \"${{ secrets.DEV_DOMAIN_NAME }}\"/g" vars.yaml + + # Update changed image tag + sed -i "/frontend/{n;n;s/.*/ tag: ${IMAGE_TAG}/}" /tmp/image_override.yaml + + cat /tmp/image_override.yaml + # Deploy command + mv openreplay/charts/{ingress-nginx,frontend,quickwit} /tmp + rm -rf openreplay/charts/* + mv /tmp/{ingress-nginx,frontend,quickwit} openreplay/charts/ + helm template openreplay -n app openreplay -f vars.yaml -f /tmp/image_override.yaml --set ingress-nginx.enabled=false --set skipMigration=true --no-hooks | kubectl apply -n app -f - + env: + DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} + IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} From cfd24dab5aa322074e009f23b8f812a68ea37036 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Tue, 29 Nov 2022 10:59:41 +0100 Subject: [PATCH 50/64] chore(cicd): Rename workflow Signed-off-by: rjshrjndrn --- .github/workflows/frontend.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index 2f2fd3989..8a9ab31a5 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -1,4 +1,4 @@ -name: Frontend FOSS Deployment +name: Frontend Dev Deployment on: workflow_dispatch: push: From 4715b7a5bbb2b9e2822d0ba980915df13cf5fc1c Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 29 Nov 2022 11:05:16 +0100 Subject: [PATCH 51/64] fix(ui): fix store empty state display --- .../app/components/Session_/Storage/Storage.tsx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/frontend/app/components/Session_/Storage/Storage.tsx b/frontend/app/components/Session_/Storage/Storage.tsx index 0f5e682c5..f955912e7 100644 --- a/frontend/app/components/Session_/Storage/Storage.tsx +++ b/frontend/app/components/Session_/Storage/Storage.tsx @@ -53,7 +53,7 @@ function Storage(props: Props) { const renderDiff = (item: Record, prevItem: Record) => { if (!prevItem) { // we don't have state before first action - return
; + return
; } const stateDiff = diff(prevItem.state, item.state); @@ -106,13 +106,6 @@ function Storage(props: Props) { player.jump(list[listNow.length].time); }; - const renderTab = () => { - if (listNow.length === 0) { - return 'Not initialized'; //? - } - return ; - }; - const renderItem = (item: Record, i: number, prevItem: Record) => { let src; let name; @@ -279,16 +272,16 @@ function Storage(props: Props) { ) : null } size="small" - show={listNow.length === 0} + show={list.length === 0} > {showStore && (
- {listNow.length === 0 ? ( + {list.length === 0 ? (
{'Empty state.'}
) : ( - renderTab() + )}
)} From d44af97efda5ec927af4ad6a3e3caa9c17732ea2 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 29 Nov 2022 12:07:57 +0100 Subject: [PATCH 52/64] fix(ui): fix console row expand method --- .../app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx | 4 ++-- .../components/shared/DevTools/NetworkPanel/NetworkPanel.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx index 0aa9b801f..777771de9 100644 --- a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx +++ b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx @@ -49,13 +49,13 @@ function ConsoleRow(props: Props) { )} {renderWithNL(lines.pop())}
- {/* {canExpand && + {canExpand && expanded && lines.map((l: string, i: number) => (
{l}
- ))} */} + ))}
jump(log.time)} />
diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index 17712a295..1eef37f87 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { observer } from 'mobx-react-lite'; import { Duration } from 'luxon'; From f6ee3f2292ec97488b40a934fd247ccf90a440df Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 29 Nov 2022 12:27:50 +0100 Subject: [PATCH 53/64] fix(frontend): autoscroll index: use filtered list --- .../DevTools/ConsolePanel/ConsolePanel.tsx | 11 ++-- .../DevTools/NetworkPanel/NetworkPanel.tsx | 20 +++--- .../StackEventPanel/StackEventPanel.tsx | 7 ++- .../shared/DevTools/useAutoscroll.ts | 61 ++++++++++++------- frontend/app/player/index.ts | 1 + 5 files changed, 64 insertions(+), 36 deletions(-) diff --git a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx index ee4a4b78b..0d59f6648 100644 --- a/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx +++ b/frontend/app/components/shared/DevTools/ConsolePanel/ConsolePanel.tsx @@ -86,13 +86,14 @@ function ConsolePanel() { const onFilterChange = ({ target: { value } }: any) => devTools.update(INDEX_KEY, { filter: value }) // AutoScroll - const autoScrollIndex = logListNow.length + exceptionsListNow.length - const { + const countNow = logListNow.length + exceptionsListNow.length + const [ timeoutStartAutoscroll, stopAutoscroll, - } = useAutoscroll( + ] = useAutoscroll( + filteredList, + list[countNow].time, activeIndex, - autoScrollIndex, index => devTools.update(INDEX_KEY, { index }) ) const onMouseEnter = stopAutoscroll @@ -101,7 +102,7 @@ function ConsolePanel() { timeoutStartAutoscroll() } - const _list = useRef(); + const _list = useRef(); // TODO: fix react-virtualized types & incapsulate scrollToRow logic useEffect(() => { if (_list.current) { // @ts-ignore diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index 1eef37f87..7b0c01230 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -152,16 +152,20 @@ function NetworkPanel() { const activeTab = devTools[INDEX_KEY].activeTab; const activeIndex = devTools[INDEX_KEY].index; - const list = useMemo(() => - resourceList.filter(res => !fetchList.some(ft => { + const { list, intersectedCount } = useMemo(() => { + let intersectedCount = 0 + const list = resourceList.filter(res => !fetchList.some(ft => { if (res.url !== ft.url) { return false } if (Math.abs(res.time - ft.time) > 200) { return false } // TODO: find good epsilons if (Math.abs(res.duration - ft.duration) > 100) { return false } + intersectedCount++ return true })) .concat(fetchList) .sort((a, b) => a.time - b.time) - , [ resourceList.length, fetchList.length ]) + return { list, intersectedCount } + }, [ resourceList.length, fetchList.length ]) + let filteredList = useMemo(() => { if (!showOnlyErrors) { return list } return list.filter(it => parseInt(it.status) >= 400 || !it.success) @@ -174,14 +178,16 @@ function NetworkPanel() { const onTabClick = (activeTab: typeof TAP_KEYS[number]) => devTools.update(INDEX_KEY, { activeTab }) const onFilterChange = ({ target: { value } }: React.ChangeEvent) => devTools.update(INDEX_KEY, { filter: value }) + // AutoScroll - const autoScrollIndex = fetchListNow.length + resourceListNow.length - const { + const countNow = fetchListNow.length + resourceListNow.length - intersectedCount + const [ timeoutStartAutoscroll, stopAutoscroll, - } = useAutoscroll( + ] = useAutoscroll( + filteredList, + list[countNow].time, activeIndex, - autoScrollIndex, index => devTools.update(INDEX_KEY, { index }) ) const onMouseEnter = stopAutoscroll diff --git a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx index 90dcdb218..70916bcb5 100644 --- a/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx +++ b/frontend/app/components/shared/DevTools/StackEventPanel/StackEventPanel.tsx @@ -44,12 +44,13 @@ function StackEventPanel() { [ list.length ], ) - const { + const [ timeoutStartAutoscroll, stopAutoscroll, - } = useAutoscroll( + ] = useAutoscroll( + filteredList, + listNow[listNow.length-1].time, activeIndex, - listNow.length, index => devTools.update(INDEX_KEY, { index }) ) const onMouseEnter = stopAutoscroll diff --git a/frontend/app/components/shared/DevTools/useAutoscroll.ts b/frontend/app/components/shared/DevTools/useAutoscroll.ts index 64883beec..5414edf42 100644 --- a/frontend/app/components/shared/DevTools/useAutoscroll.ts +++ b/frontend/app/components/shared/DevTools/useAutoscroll.ts @@ -1,42 +1,61 @@ -import { useEffect, useState, useRef } from 'react' +import { useEffect, useState, useRef, useMemo } from 'react' +import { Timed } from 'Player' import useLatestRef from 'App/hooks/useLatestRef' import useCancelableTimeout from 'App/hooks/useCancelableTimeout' const TIMEOUT_DURATION = 5000; -export default function useAutoscroll( - savedIndex: number, - autoscrollIndex: number, - updadteIndex: (index: number) => void, + +function useAutoupdate( + savedValue: T, + actualValue: T, + resetValue: T, + updadteValue: (value: T) => void, ) { - const [ autoscroll, setAutoscroll ] = useState(savedIndex === 0) + const [ autoupdate, setAutoupdate ] = useState(savedValue === resetValue) - const [ timeoutStartAutoscroll, stopAutoscroll ] = useCancelableTimeout( - () => setAutoscroll(true), - () => setAutoscroll(false), + const [ timeoutStartAutoupdate, stopAutoupdate ] = useCancelableTimeout( + () => setAutoupdate(true), + () => setAutoupdate(false), TIMEOUT_DURATION, ) useEffect(() => { - if (autoscroll && autoscrollIndex !== savedIndex) { - updadteIndex(autoscrollIndex) + if (autoupdate && actualValue !== savedValue) { + updadteValue(actualValue) } - }, [ autoscroll, autoscrollIndex ]) + }, [ autoupdate, actualValue ]) - const autoScrollRef = useLatestRef(autoscroll) + const autoScrollRef = useLatestRef(autoupdate) useEffect(() => { - if (!autoscroll) { - timeoutStartAutoscroll() + if (!autoupdate) { + timeoutStartAutoupdate() } return () => { if (autoScrollRef.current) { - updadteIndex(0) // index:0 means autoscroll is active + updadteValue(resetValue) } } }, []) - return { - autoscrollIndex, - timeoutStartAutoscroll, - stopAutoscroll, - } + return [ timeoutStartAutoupdate, stopAutoupdate ] +} + +// That might be simplified by removing index from devTools[INDEX_KEY] store... +export default function useAutoscroll( + filteredList: Timed[], + time: number, + savedIndex: number, + updadteIndex: (index: number) => void, +) { + const filteredIndexNow = useMemo(() => { + // Should use findLastIndex here + for (let i=0; i < filteredList.length; i++) { + if (filteredList[i].time > time) { + return i-1 + } + } + return filteredList.length + }, [ time, filteredList ]) + + return useAutoupdate(savedIndex, filteredIndexNow, 0, updadteIndex) } \ No newline at end of file diff --git a/frontend/app/player/index.ts b/frontend/app/player/index.ts index 55c67fb0b..cf7fe3244 100644 --- a/frontend/app/player/index.ts +++ b/frontend/app/player/index.ts @@ -2,6 +2,7 @@ export * from './web/assist/AssistManager'; export * from './web/assist/LocalStream'; export * from './web/WebPlayer'; export * from './web/types'; +export * from './common/types'; export * from './create'; export type { MarkedTarget } from './web/TargetMarker' From 39bf333bc5cc06329a356881f16cdd9d571f8773 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Tue, 29 Nov 2022 13:21:11 +0100 Subject: [PATCH 54/64] fix(cicd): registry token Signed-off-by: rjshrjndrn --- .github/workflows/frontend-dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/frontend-dev.yaml b/.github/workflows/frontend-dev.yaml index 17a64df94..5f8d3c44d 100644 --- a/.github/workflows/frontend-dev.yaml +++ b/.github/workflows/frontend-dev.yaml @@ -29,7 +29,7 @@ jobs: - name: Docker login run: | - docker login ${{ secrets.DEV_REGISTRY_URL }} -u ${{ secrets.DEV_DOCKER_USERNAME }} -p "${{ secrets.DEV_REGISTRY_TOKEN }}" + docker login ${{ secrets.OSS_REGISTRY_URL }} -u ${{ secrets.OSS_DOCKER_USERNAME }} -p "${{ secrets.OSS_REGISTRY_TOKEN }}" - uses: azure/k8s-set-context@v1 with: From 808070c00eb55b7ce252880543da850e01ff5f82 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 29 Nov 2022 13:23:24 +0100 Subject: [PATCH 55/64] fix(ui): fix type --- .../app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx index 777771de9..11039c9f6 100644 --- a/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx +++ b/frontend/app/components/shared/DevTools/ConsoleRow/ConsoleRow.tsx @@ -37,7 +37,7 @@ function ConsoleRow(props: Props) { 'cursor-pointer underline decoration-dotted decoration-gray-200': !!log.errorId, } )} - onClick={clickable ? () => (!!log.errorId ? props.onClick() : toggleExpand()) : () => {}} + onClick={clickable ? () => (!!log.errorId ? props.onClick() : toggleExpand()) : undefined} >
From 08afff365fc4a914820a694a393f91a3ea4cbed2 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 29 Nov 2022 13:28:00 +0100 Subject: [PATCH 56/64] fix(frontend/player): useRegExListFilterMemo many fields filter --- .../DevTools/NetworkPanel/NetworkPanel.tsx | 8 ++++--- .../shared/DevTools/useListFilter.ts | 24 ++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx index 7b0c01230..58831eb48 100644 --- a/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx +++ b/frontend/app/components/shared/DevTools/NetworkPanel/NetworkPanel.tsx @@ -170,9 +170,11 @@ function NetworkPanel() { if (!showOnlyErrors) { return list } return list.filter(it => parseInt(it.status) >= 400 || !it.success) }, [ showOnlyErrors, list ]) - filteredList = useRegExListFilterMemo(filteredList, it => it.status, filter) - filteredList = useRegExListFilterMemo(filteredList, it => it.name, filter) - filteredList = useRegExListFilterMemo(filteredList, it => it.type, filter) + filteredList = useRegExListFilterMemo( + filteredList, + it => [ it.status, it.name, it.type ], + filter, + ) filteredList = useTabListFilterMemo(filteredList, it => TYPE_TO_TAB[it.type], ALL, activeTab) const onTabClick = (activeTab: typeof TAP_KEYS[number]) => devTools.update(INDEX_KEY, { activeTab }) diff --git a/frontend/app/components/shared/DevTools/useListFilter.ts b/frontend/app/components/shared/DevTools/useListFilter.ts index 1f4c30f82..8f526c524 100644 --- a/frontend/app/components/shared/DevTools/useListFilter.ts +++ b/frontend/app/components/shared/DevTools/useListFilter.ts @@ -1,19 +1,31 @@ import { useMemo } from 'react' import { getRE } from 'App/utils' -export function useRegExListFilterMemo(list: T[], filterBy: (item: T) => string, reText: string) { + +// TODO: merge with utils/filterList (use logic of string getter like here instead of using callback) +export function useRegExListFilterMemo( + list: T[], + filterBy: (it: T) => string | string[], + reText: string, +) { return useMemo(() => { if (!reText) { return list } const re = getRE(reText, 'i') - list.filter(it => re.test(filterBy(it))) + return list.filter(it => { + const strs = filterBy(it) + return Array.isArray(strs) + ? strs.some(s => re.test(s)) + : re.test(strs) + }) }, [ list, list.length, reText ]) } -export function useTabListFilterMemo( + +export function useTabListFilterMemo( list: T[], - itemToTab: (item: T) => string, - commonTab: string, - currentTab: string, + itemToTab: (it: T) => Tab, + commonTab: Tab, + currentTab: Tab, ) { return useMemo(() => { if (currentTab === commonTab) { return list } From 0f4744d490225502b09e58c27abf22485f6bf0f6 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Tue, 29 Nov 2022 13:21:11 +0100 Subject: [PATCH 57/64] fix(cicd): updated vars Signed-off-by: rjshrjndrn --- .github/workflows/frontend-dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/frontend-dev.yaml b/.github/workflows/frontend-dev.yaml index 5f8d3c44d..1b7b28788 100644 --- a/.github/workflows/frontend-dev.yaml +++ b/.github/workflows/frontend-dev.yaml @@ -1,4 +1,4 @@ -name: Frontend FOSS Deployment +name: Frontend Dev Deployment on: workflow_dispatch: push: From 126551cddabeedc7e7f42b01f3ba282ba561d633 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 29 Nov 2022 14:54:25 +0100 Subject: [PATCH 58/64] change(player): add more info to walker append error --- frontend/app/player/common/ListWalker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/app/player/common/ListWalker.ts b/frontend/app/player/common/ListWalker.ts index db847150d..638081c12 100644 --- a/frontend/app/player/common/ListWalker.ts +++ b/frontend/app/player/common/ListWalker.ts @@ -6,7 +6,7 @@ export default class ListWalker { append(m: T): void { if (this.length > 0 && this.last && m.time < this.last.time) { - console.error("Trying to append message with the less time then the list tail: ", m) + console.error("Trying to append message with the less time then the list tail: ", m.time, 'vs', this.last.time, m) return } this.list.push(m); @@ -98,7 +98,7 @@ export default class ListWalker { /* Returns last message with the time <= t. - Assumed that the current message is already handled so + Assumed that the current message is already handled so if pointer doesn't cahnge is returned. */ moveGetLast(t: number, index?: number): T | null { From 9bf4f7d99b9997a5728d5abaaeb335d10d25b4b4 Mon Sep 17 00:00:00 2001 From: rjshrjndrn Date: Tue, 29 Nov 2022 15:03:19 +0100 Subject: [PATCH 59/64] chore(cicd): Fixing registry url Signed-off-by: rjshrjndrn --- .github/workflows/frontend-dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/frontend-dev.yaml b/.github/workflows/frontend-dev.yaml index 1b7b28788..ef966c847 100644 --- a/.github/workflows/frontend-dev.yaml +++ b/.github/workflows/frontend-dev.yaml @@ -40,7 +40,7 @@ jobs: - name: Building and Pushing frontend image id: build-image env: - DOCKER_REPO: ${{ secrets.DEV_REGISTRY_URL }} + DOCKER_REPO: ${{ secrets.OSS_REGISTRY_URL }} IMAGE_TAG: ${{ github.ref_name }}_${{ github.sha }} ENVIRONMENT: staging run: | From d15779076e1a9c17b1b33b026779a081e3a24b18 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 29 Nov 2022 15:08:06 +0100 Subject: [PATCH 60/64] refactor(player): remove redux & singletone --- frontend/app/player/_singletone.ts | 91 ------------------- frontend/app/player/_store/connector.js | 24 ----- frontend/app/player/_store/duck.js | 38 -------- frontend/app/player/_store/index.js | 4 - frontend/app/player/_store/selectors.js | 48 ---------- frontend/app/player/_store/store.js | 20 ---- frontend/app/player/index.ts | 3 - .../app/player/web/assist/AssistManager.ts | 2 +- 8 files changed, 1 insertion(+), 229 deletions(-) delete mode 100644 frontend/app/player/_singletone.ts delete mode 100644 frontend/app/player/_store/connector.js delete mode 100644 frontend/app/player/_store/duck.js delete mode 100644 frontend/app/player/_store/index.js delete mode 100644 frontend/app/player/_store/selectors.js delete mode 100644 frontend/app/player/_store/store.js diff --git a/frontend/app/player/_singletone.ts b/frontend/app/player/_singletone.ts deleted file mode 100644 index 63d545edd..000000000 --- a/frontend/app/player/_singletone.ts +++ /dev/null @@ -1,91 +0,0 @@ -import WebPlayer from './web/WebPlayer'; -import reduxStore, {update, cleanStore} from './_store'; - -import type { State as MMState } from './web/MessageManager' -import type { State as PState } from './player/Player' -import type { Store } from './common/types' - - -const myStore: Store = { - get() { - return reduxStore.getState() - }, - update(s) { - update(s) - } -} - -let instance: WebPlayer | null = null; - -export function init(session, config, live = false) { - instance = new WebPlayer(myStore, session, config, live); -} - -export function clean() { - if (instance === null) return; - instance.clean(); - cleanStore(); - instance = null; -} - -const initCheck = (method) => (...args) => { - if (instance === null) { - console.error("Player method called before Player have been initialized."); - return; - } - return method(...args); -} - -export const jump = initCheck((...args) => instance.jump(...args)); -export const togglePlay = initCheck((...args) => instance.togglePlay(...args)); -export const pause = initCheck((...args) => instance.pause(...args)); -export const toggleSkip = initCheck((...args) => instance.toggleSkip(...args)); -export const toggleSkipToIssue = initCheck((...args) => instance.toggleSkipToIssue(...args)); -export const toggleAutoplay = initCheck((...args) => instance.toggleAutoplay(...args)); -export const toggleSpeed = initCheck((...args) => instance.toggleSpeed(...args)); -export const toggleEvents = initCheck((...args) => instance.toggleEvents(...args)); -export const speedUp = initCheck((...args) => instance.speedUp(...args)); -export const speedDown = initCheck((...args) => instance.speedDown(...args)); -export const attach = initCheck((...args) => instance.attach(...args)); -export const markElement = initCheck((...args) => instance.mark(...args)); -export const scale = initCheck(() => instance.scale()); -/** @type {WebPlayer.toggleTimetravel} */ -export const toggleTimetravel = initCheck((...args) => instance.toggleTimetravel(...args)) -export const toggleInspectorMode = initCheck((...args) => instance.toggleInspectorMode(...args)); -export const markTargets = initCheck((...args) => instance.markTargets(...args)) -export const activeTarget =initCheck((...args) => instance.setActiveTarget(...args)) - -export const jumpToLive = initCheck((...args) => instance.jumpToLive(...args)) -export const toggleUserName = initCheck((...args) => instance.toggleUserName(...args)) - -// !not related to player, but rather to the OR platform. -export const injectNotes = () => {} // initCheck((...args) => instance.injectNotes(...args)) -export const filterOutNote = () => {} //initCheck((...args) => instance.filterOutNote(...args)) - - -/** @type {Player.assistManager.call} */ -export const callPeer = initCheck((...args) => instance.assistManager.call(...args)) -/** @type {Player.assistManager.setCallArgs} */ -export const setCallArgs = initCheck((...args) => instance.assistManager.setCallArgs(...args)) -/** @type {Player.assistManager.initiateCallEnd} */ -export const initiateCallEnd = initCheck((...args) => instance.assistManager.initiateCallEnd(...args)) -export const requestReleaseRemoteControl = initCheck((...args) => instance.assistManager.requestReleaseRemoteControl(...args)) -export const releaseRemoteControl = initCheck((...args) => instance.assistManager.releaseRemoteControl(...args)) -/** @type {Player.assistManager.toggleVideoLocalStream} */ -export const toggleVideoLocalStream = initCheck((...args) => instance.assistManager.toggleVideoLocalStream(...args)) -export const toggleAnnotation = initCheck((...args) => instance.assistManager.toggleAnnotation(...args)) - - -export const Controls = { - jump, - togglePlay, - pause, - toggleSkip, - toggleSkipToIssue, - toggleAutoplay, - toggleEvents, - toggleSpeed, - speedUp, - speedDown, - callPeer -} diff --git a/frontend/app/player/_store/connector.js b/frontend/app/player/_store/connector.js deleted file mode 100644 index 9e74f56a6..000000000 --- a/frontend/app/player/_store/connector.js +++ /dev/null @@ -1,24 +0,0 @@ -import { connect, createProvider } from 'react-redux' -import store from './store'; - -const STORE_KEY = 'playerStore'; - -const PlayerProvider = createProvider(STORE_KEY); -PlayerProvider.defaultProps = { store }; - -function connectPlayer( - mapStateToProps, - mapDispatchToProps, - mergeProps, - options = {} -) { - options.storeKey = STORE_KEY - return connect( - mapStateToProps, - mapDispatchToProps, - mergeProps, - options - ) -} - -export { PlayerProvider, connectPlayer }; \ No newline at end of file diff --git a/frontend/app/player/_store/duck.js b/frontend/app/player/_store/duck.js deleted file mode 100644 index 6996e8ed1..000000000 --- a/frontend/app/player/_store/duck.js +++ /dev/null @@ -1,38 +0,0 @@ -import WebPlayer from '../web/WebPlayer' - -const UPDATE = 'player/UPDATE'; -const CLEAN = 'player/CLEAN'; -const REDUX = 'player/REDUX'; - -const resetState = { - ...WebPlayer.INITIAL_STATE, - initialized: false, -}; - -const initialState = { - ...resetState, -} - -export default (state = initialState, action = {}) => { - switch (action.type) { - case UPDATE: - return { ...state, ...action.state }; - case CLEAN: - return { ...state, ...resetState }; - default: - return state; - } -} - -export function update(state = {}) { - return { - type: UPDATE, - state, - }; -} - -export function clean() { - return { - type: CLEAN - }; -} diff --git a/frontend/app/player/_store/index.js b/frontend/app/player/_store/index.js deleted file mode 100644 index c2a987661..000000000 --- a/frontend/app/player/_store/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export * from './connector'; -export * from './store'; -export * from './selectors'; -export { default } from './store'; \ No newline at end of file diff --git a/frontend/app/player/_store/selectors.js b/frontend/app/player/_store/selectors.js deleted file mode 100644 index 36750b56d..000000000 --- a/frontend/app/player/_store/selectors.js +++ /dev/null @@ -1,48 +0,0 @@ -const REDUX = "redux"; -const MOBX = "mobx"; -const VUEX = "vuex"; -const NGRX = "ngrx"; -const ZUSTAND = 'zustand'; -const NONE = 0; - - -export const STORAGE_TYPES = { - REDUX, - MOBX, - VUEX, - NGRX, - ZUSTAND, - NONE, -}; - - -export function selectStorageType(state) { - if (!state.reduxList) return NONE; - if (state.reduxList.length > 0) { - return REDUX; - } else if (state.vuexList.length > 0) { - return VUEX; - } else if (state.mobxList.length > 0) { - return MOBX; - } else if (state.ngrxList.length > 0) { - return NGRX; - } else if (state.zustandList.length > 0) { - return ZUSTAND; - } - return NONE; -} - -export function selectStorageList(state) { - const key = selectStorageType(state); - if (key !== NONE) { - return state[`${key}List`] || []; - } - return []; -} -export function selectStorageListNow(state) { - const key = selectStorageType(state); - if (key !== NONE) { - return state[`${key}ListNow`] || []; - } - return []; -} diff --git a/frontend/app/player/_store/store.js b/frontend/app/player/_store/store.js deleted file mode 100644 index fd4fed5c0..000000000 --- a/frontend/app/player/_store/store.js +++ /dev/null @@ -1,20 +0,0 @@ -import { createStore } from 'redux'; -import reducer, { - update as updateAction, - clean as cleanAction, -} from './duck'; - -const store = createStore(reducer); - -export const getState = store.getState.bind(store); - -export function update(...args) { - const action = updateAction(...args) - return store.dispatch(action); -} - -export function cleanStore() { - return store.dispatch(cleanAction()); -} - -export default store; diff --git a/frontend/app/player/index.ts b/frontend/app/player/index.ts index cf7fe3244..45ac6c40e 100644 --- a/frontend/app/player/index.ts +++ b/frontend/app/player/index.ts @@ -6,6 +6,3 @@ export * from './common/types'; export * from './create'; export type { MarkedTarget } from './web/TargetMarker' - -export * from './_store'; -export * from './_singletone'; diff --git a/frontend/app/player/web/assist/AssistManager.ts b/frontend/app/player/web/assist/AssistManager.ts index 1d2fe08db..bac64ef39 100644 --- a/frontend/app/player/web/assist/AssistManager.ts +++ b/frontend/app/player/web/assist/AssistManager.ts @@ -4,7 +4,7 @@ import type { MediaConnection } from 'peerjs'; import type MessageManager from '../MessageManager'; import appStore from 'App/store'; import type { LocalStream } from './LocalStream'; -import type { Store } from '../../player/types' +import type { Store } from '../../common/types' import AnnotationCanvas from './AnnotationCanvas'; import MStreamReader from '../messages/MStreamReader'; import JSONRawMessageReader from '../messages/JSONRawMessageReader' From 6a82d34d8c3b3e858cb2e8698d7fc50895418b57 Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 29 Nov 2022 15:14:10 +0100 Subject: [PATCH 61/64] change(player): remove unused imports for injectnotes and provider --- frontend/.storybook/{config.js => config.DEPRECATED.js} | 5 +---- frontend/app/components/Session/LivePlayer.js | 2 -- frontend/app/components/Session/WebPlayer.tsx | 4 +--- .../Session_/Player/Controls/components/CreateNote.tsx | 3 --- frontend/app/player/common/ListWalker.ts | 2 +- frontend/app/player/web/WebPlayer.ts | 9 --------- 6 files changed, 3 insertions(+), 22 deletions(-) rename frontend/.storybook/{config.js => config.DEPRECATED.js} (90%) diff --git a/frontend/.storybook/config.js b/frontend/.storybook/config.DEPRECATED.js similarity index 90% rename from frontend/.storybook/config.js rename to frontend/.storybook/config.DEPRECATED.js index 1ff1f28d1..fad172b6f 100644 --- a/frontend/.storybook/config.js +++ b/frontend/.storybook/config.DEPRECATED.js @@ -2,13 +2,10 @@ import { configure, addDecorator } from '@storybook/react'; import { Provider } from 'react-redux'; import store from '../app/store'; import { MemoryRouter } from "react-router" -import { PlayerProvider } from '../app/player/store' const withProvider = (story) => ( - { story() } - ) @@ -33,4 +30,4 @@ configure( require.context('../app', true, /\.stories\.js$/), ], module -); \ No newline at end of file +); diff --git a/frontend/app/components/Session/LivePlayer.js b/frontend/app/components/Session/LivePlayer.js index f06de8972..52de50253 100644 --- a/frontend/app/components/Session/LivePlayer.js +++ b/frontend/app/components/Session/LivePlayer.js @@ -72,12 +72,10 @@ function LivePlayer ({ return ( - {!fullView && ()}
-
); } diff --git a/frontend/app/components/Session/WebPlayer.tsx b/frontend/app/components/Session/WebPlayer.tsx index 779fe3a11..6e169777e 100644 --- a/frontend/app/components/Session/WebPlayer.tsx +++ b/frontend/app/components/Session/WebPlayer.tsx @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import { Modal } from 'UI'; import { toggleFullscreen, closeBottomBlock } from 'Duck/components/player'; import { fetchList } from 'Duck/integrations'; -import { PlayerProvider, createWebPlayer } from 'Player'; +import { createWebPlayer } from 'Player'; import { makeAutoObservable } from 'mobx'; import withLocationHandlers from 'HOCs/withLocationHandlers'; import { useStore } from 'App/mstore'; @@ -78,7 +78,6 @@ function WebPlayer(props: any) { return ( - <> - ); } diff --git a/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx index 2fe7075f1..8ac9d93f4 100644 --- a/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx +++ b/frontend/app/components/Session_/Player/Controls/components/CreateNote.tsx @@ -7,7 +7,6 @@ import { setCreateNoteTooltip, addNote, updateNote } from 'Duck/sessions'; import stl from './styles.module.css'; import { useStore } from 'App/mstore'; import { toast } from 'react-toastify'; -import { injectNotes } from 'Player'; import { fetchList as fetchSlack } from 'Duck/integrations/slack'; import Select from 'Shared/Select'; import { TeamBadge } from 'Shared/SessionListContainer/components/Notes' @@ -86,7 +85,6 @@ function CreateNote({ .then((r) => { toast.success('Note updated'); notesStore.fetchSessionNotes(sessionId).then((notes) => { - injectNotes(notes); onSuccess(editNote.noteId) updateNote(r); }); @@ -108,7 +106,6 @@ function CreateNote({ onSuccess(r.noteId as unknown as string) toast.success('Note added'); notesStore.fetchSessionNotes(sessionId).then((notes) => { - injectNotes(notes); addNote(r); }); }) diff --git a/frontend/app/player/common/ListWalker.ts b/frontend/app/player/common/ListWalker.ts index 638081c12..7d92fa285 100644 --- a/frontend/app/player/common/ListWalker.ts +++ b/frontend/app/player/common/ListWalker.ts @@ -6,7 +6,7 @@ export default class ListWalker { append(m: T): void { if (this.length > 0 && this.last && m.time < this.last.time) { - console.error("Trying to append message with the less time then the list tail: ", m.time, 'vs', this.last.time, m) + console.error("Trying to append message with the less time then the list tail:", m.time, 'vs', this.last.time, m) return } this.list.push(m); diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index b9d35d686..add59b763 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -133,15 +133,6 @@ export default class WebPlayer extends Player { } } - // TODO: restore notes functionality - // injectNotes(notes: Note[]) { - // update({ notes }) - // } - // filterOutNote(noteId: number) { - // const { notes } = getState() - // update({ notes: notes.filter((note: Note) => note.noteId !== noteId) }) - // } - toggleUserName = (name?: string) => { this.screen.cursor.showTag(name) } From bb8a0105a15f215bba8a23e59cdf120a3b9f7afe Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 29 Nov 2022 15:20:16 +0100 Subject: [PATCH 62/64] fix(player):export storage selectors --- frontend/app/player/index.ts | 10 +++-- frontend/app/player/web/storageSelectors.ts | 43 +++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 frontend/app/player/web/storageSelectors.ts diff --git a/frontend/app/player/index.ts b/frontend/app/player/index.ts index 45ac6c40e..0a2df3d82 100644 --- a/frontend/app/player/index.ts +++ b/frontend/app/player/index.ts @@ -1,8 +1,10 @@ -export * from './web/assist/AssistManager'; -export * from './web/assist/LocalStream'; -export * from './web/WebPlayer'; -export * from './web/types'; export * from './common/types'; export * from './create'; + +export * from './web/assist/AssistManager'; +export * from './web/assist/LocalStream'; +export * from './web/WebPlayer'; +export * from './web/storageSelectors'; +export * from './web/types'; export type { MarkedTarget } from './web/TargetMarker' diff --git a/frontend/app/player/web/storageSelectors.ts b/frontend/app/player/web/storageSelectors.ts new file mode 100644 index 000000000..fc6fc7318 --- /dev/null +++ b/frontend/app/player/web/storageSelectors.ts @@ -0,0 +1,43 @@ +import { State } from './Lists' + +enum StorageType { + REDUX = "redux", + MOBX = "mobx", + VUEX = "vuex", + NGRX = "ngrx", + ZUSTAND = "zustand", + NONE = 0, +} + +export const STORAGE_TYPES = StorageType + +export function selectStorageType(state: State): StorageType { + if (!state.reduxList) return STORAGE_TYPES.NONE + if (state.reduxList.length > 0) { + return STORAGE_TYPES.REDUX + } else if (state.vuexList.length > 0) { + return STORAGE_TYPES.VUEX + } else if (state.mobxList.length > 0) { + return STORAGE_TYPES.MOBX + } else if (state.ngrxList.length > 0) { + return STORAGE_TYPES.NGRX + } else if (state.zustandList.length > 0) { + return STORAGE_TYPES.ZUSTAND + } + return STORAGE_TYPES.NONE +} + +export function selectStorageList(state: State) { + const key = selectStorageType(state) + if (key !== StorageType.NONE) { + return state[`${key}List`] + } + return [] +} +export function selectStorageListNow(state: State) { + const key = selectStorageType(state) + if (key !== StorageType.NONE) { + return state[`${key}ListNow`] + } + return [] +} From 78fbc526eb735ff67296cc72fa4951b274decc4c Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Tue, 29 Nov 2022 15:28:14 +0100 Subject: [PATCH 63/64] fix(player):separate screen and messageManager within AssistManager --- frontend/app/player/web/WebPlayer.ts | 2 +- .../app/player/web/assist/AssistManager.ts | 36 ++++++++++--------- frontend/app/player/web/storageSelectors.ts | 18 +++++----- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/frontend/app/player/web/WebPlayer.ts b/frontend/app/player/web/WebPlayer.ts index add59b763..e3862c255 100644 --- a/frontend/app/player/web/WebPlayer.ts +++ b/frontend/app/player/web/WebPlayer.ts @@ -71,7 +71,7 @@ export default class WebPlayer extends Player { }) // TODO: separate LiveWebPlayer - this.assistManager = new AssistManager(session, this.messageManager, config, wpState) + this.assistManager = new AssistManager(session, this.messageManager, screen, config, wpState) if (live) { this.assistManager.connect(session.agentToken) } diff --git a/frontend/app/player/web/assist/AssistManager.ts b/frontend/app/player/web/assist/AssistManager.ts index bac64ef39..b4704f7e3 100644 --- a/frontend/app/player/web/assist/AssistManager.ts +++ b/frontend/app/player/web/assist/AssistManager.ts @@ -2,6 +2,7 @@ import type { Socket } from 'socket.io-client'; import type Peer from 'peerjs'; import type { MediaConnection } from 'peerjs'; import type MessageManager from '../MessageManager'; +import type Screen from '../Screen/Screen'; import appStore from 'App/store'; import type { LocalStream } from './LocalStream'; import type { Store } from '../../common/types' @@ -78,6 +79,7 @@ export default class AssistManager { constructor( private session: any, private md: MessageManager, + private screen: Screen, private config: RTCIceServer[], private store: Store, ) {} @@ -94,9 +96,9 @@ export default class AssistManager { this.md.setMessagesLoading(false); } if (status === ConnectionStatus.Connected) { - this.md.display(true); + this.screen.display(true); } else { - this.md.display(false); + this.screen.display(false); } this.store.update({ peerConnectionStatus: status }); } @@ -253,7 +255,7 @@ export default class AssistManager { private onMouseMove = (e: MouseEvent): void => { if (!this.socket) { return } - const data = this.md.getInternalCoordinates(e) + const data = this.screen.getInternalCoordinates(e) this.socket.emit("move", [ data.x, data.y ]) } @@ -269,9 +271,9 @@ export default class AssistManager { if (!this.socket) { return; } if (this.store.get().annotating) { return; } // ignore clicks while annotating - const data = this.md.getInternalViewportCoordinates(e) - // const el = this.md.getElementFromPoint(e); // requires requestiong node_id from domManager - const el = this.md.getElementFromInternalPoint(data) + const data = this.screen.getInternalViewportCoordinates(e) + // const el = this.screen.getElementFromPoint(e); // requires requestiong node_id from domManager + const el = this.screen.getElementFromInternalPoint(data) if (el instanceof HTMLElement) { el.focus() el.oninput = e => { @@ -299,16 +301,16 @@ export default class AssistManager { private toggleRemoteControl(enable: boolean){ if (enable) { - this.md.overlay.addEventListener("mousemove", this.onMouseMove) - this.md.overlay.addEventListener("click", this.onMouseClick) - this.md.overlay.addEventListener("wheel", this.onWheel) - this.md.toggleBorder(true) + this.screen.overlay.addEventListener("mousemove", this.onMouseMove) + this.screen.overlay.addEventListener("click", this.onMouseClick) + this.screen.overlay.addEventListener("wheel", this.onWheel) + this.screen.toggleBorder(true) this.store.update({ remoteControl: RemoteControlStatus.Enabled }) } else { - this.md.overlay.removeEventListener("mousemove", this.onMouseMove) - this.md.overlay.removeEventListener("click", this.onMouseClick) - this.md.overlay.removeEventListener("wheel", this.onWheel) - this.md.toggleBorder(false) + this.screen.overlay.removeEventListener("mousemove", this.onMouseMove) + this.screen.overlay.removeEventListener("click", this.onMouseClick) + this.screen.overlay.removeEventListener("wheel", this.onWheel) + this.screen.toggleBorder(false) this.store.update({ remoteControl: RemoteControlStatus.Disabled }) this.toggleAnnotation(false) } @@ -540,10 +542,10 @@ export default class AssistManager { } if (enable && !this.annot) { const annot = this.annot = new AnnotationCanvas() - annot.mount(this.md.overlay) + annot.mount(this.screen.overlay) annot.canvas.addEventListener("mousedown", e => { if (!this.socket) { return } - const data = this.md.getInternalViewportCoordinates(e) + const data = this.screen.getInternalViewportCoordinates(e) annot.start([ data.x, data.y ]) this.socket.emit("startAnnotation", [ data.x, data.y ]) }) @@ -560,7 +562,7 @@ export default class AssistManager { annot.canvas.addEventListener("mousemove", e => { if (!this.socket || !annot.isPainting()) { return } - const data = this.md.getInternalViewportCoordinates(e) + const data = this.screen.getInternalViewportCoordinates(e) annot.move([ data.x, data.y ]) this.socket.emit("moveAnnotation", [ data.x, data.y ]) }) diff --git a/frontend/app/player/web/storageSelectors.ts b/frontend/app/player/web/storageSelectors.ts index fc6fc7318..1fa1ea32b 100644 --- a/frontend/app/player/web/storageSelectors.ts +++ b/frontend/app/player/web/storageSelectors.ts @@ -1,6 +1,6 @@ import { State } from './Lists' -enum StorageType { +export enum StorageType { REDUX = "redux", MOBX = "mobx", VUEX = "vuex", @@ -9,22 +9,22 @@ enum StorageType { NONE = 0, } -export const STORAGE_TYPES = StorageType +export const STORAGE_TYPES = StorageType // TODO: update name everywhere export function selectStorageType(state: State): StorageType { - if (!state.reduxList) return STORAGE_TYPES.NONE + if (!state.reduxList) return StorageType.NONE if (state.reduxList.length > 0) { - return STORAGE_TYPES.REDUX + return StorageType.REDUX } else if (state.vuexList.length > 0) { - return STORAGE_TYPES.VUEX + return StorageType.VUEX } else if (state.mobxList.length > 0) { - return STORAGE_TYPES.MOBX + return StorageType.MOBX } else if (state.ngrxList.length > 0) { - return STORAGE_TYPES.NGRX + return StorageType.NGRX } else if (state.zustandList.length > 0) { - return STORAGE_TYPES.ZUSTAND + return StorageType.ZUSTAND } - return STORAGE_TYPES.NONE + return StorageType.NONE } export function selectStorageList(state: State) { From 74afaa8403e045f15dd673d5a5643714db9847ad Mon Sep 17 00:00:00 2001 From: sylenien Date: Tue, 29 Nov 2022 15:56:05 +0100 Subject: [PATCH 64/64] change(player): refactor autoplay comp --- .../app/components/Session_/Autoplay/index.ts | 1 - .../QueueControls.tsx} | 5 ++--- .../components/Session_/QueueControls/index.ts | 1 + frontend/app/components/Session_/Subheader.js | 6 +++--- .../shared/AutoplayToggle/AutoplayToggle.tsx | 18 +++++++----------- 5 files changed, 13 insertions(+), 18 deletions(-) delete mode 100644 frontend/app/components/Session_/Autoplay/index.ts rename frontend/app/components/Session_/{Autoplay/Autoplay.tsx => QueueControls/QueueControls.tsx} (96%) create mode 100644 frontend/app/components/Session_/QueueControls/index.ts diff --git a/frontend/app/components/Session_/Autoplay/index.ts b/frontend/app/components/Session_/Autoplay/index.ts deleted file mode 100644 index acc3d46de..000000000 --- a/frontend/app/components/Session_/Autoplay/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './Autoplay' \ No newline at end of file diff --git a/frontend/app/components/Session_/Autoplay/Autoplay.tsx b/frontend/app/components/Session_/QueueControls/QueueControls.tsx similarity index 96% rename from frontend/app/components/Session_/Autoplay/Autoplay.tsx rename to frontend/app/components/Session_/QueueControls/QueueControls.tsx index 417bd384d..cbb49d02d 100644 --- a/frontend/app/components/Session_/Autoplay/Autoplay.tsx +++ b/frontend/app/components/Session_/QueueControls/QueueControls.tsx @@ -6,7 +6,6 @@ import { Link, Icon, Tooltip } from 'UI';; import { withRouter, RouteComponentProps } from 'react-router-dom'; import cn from 'classnames'; import { fetchAutoplaySessions } from 'Duck/search'; -import { observer } from 'mobx-react-lite'; const PER_PAGE = 10; @@ -21,7 +20,7 @@ interface Props extends RouteComponentProps { sessionIds: any; fetchAutoplaySessions?: (page: number) => Promise; } -function Autoplay(props: Props) { +function QueueControls(props: Props) { const { previousId, nextId, @@ -101,4 +100,4 @@ export default connect( latestRequestTime: state.getIn(['search', 'latestRequestTime']), }), { setAutoplayValues, fetchAutoplaySessions } -)(withRouter(Autoplay)) \ No newline at end of file +)(withRouter(QueueControls)) diff --git a/frontend/app/components/Session_/QueueControls/index.ts b/frontend/app/components/Session_/QueueControls/index.ts new file mode 100644 index 000000000..0d45bd91a --- /dev/null +++ b/frontend/app/components/Session_/QueueControls/index.ts @@ -0,0 +1 @@ +export { default } from './QueueControls' diff --git a/frontend/app/components/Session_/Subheader.js b/frontend/app/components/Session_/Subheader.js index 5baae03a0..be7679fd5 100644 --- a/frontend/app/components/Session_/Subheader.js +++ b/frontend/app/components/Session_/Subheader.js @@ -1,6 +1,6 @@ import React from 'react'; import { Icon, Tooltip, Button } from 'UI'; -import Autoplay from './Autoplay'; +import QueueControls from './QueueControls'; import Bookmark from 'Shared/Bookmark'; import SharePopup from '../shared/SharePopup/SharePopup'; import copy from 'copy-to-clipboard'; @@ -106,7 +106,7 @@ function SubHeader(props) { />
- +
) : null} @@ -114,4 +114,4 @@ function SubHeader(props) { ); } -export default observer(SubHeader); \ No newline at end of file +export default observer(SubHeader); diff --git a/frontend/app/components/shared/AutoplayToggle/AutoplayToggle.tsx b/frontend/app/components/shared/AutoplayToggle/AutoplayToggle.tsx index 3f25fd525..55bef3709 100644 --- a/frontend/app/components/shared/AutoplayToggle/AutoplayToggle.tsx +++ b/frontend/app/components/shared/AutoplayToggle/AutoplayToggle.tsx @@ -1,16 +1,19 @@ import React from 'react'; -import { Controls as PlayerControls, connectPlayer } from 'Player'; import { Toggler } from 'UI'; +import { PlayerContext } from 'App/components/Session/playerContext'; +import { observer } from 'mobx-react-lite'; interface Props { toggleAutoplay: () => void; autoplay: boolean; } function AutoplayToggle(props: Props) { - const { autoplay } = props; + const { player, store } = React.useContext(PlayerContext) + + const { autoplay } = store.get() return (
player.toggleAutoplay()} className="cursor-pointer flex items-center mr-2 hover:bg-gray-light-shade rounded-md p-2" > @@ -19,11 +22,4 @@ function AutoplayToggle(props: Props) { ); } -export default connectPlayer( - (state: any) => ({ - autoplay: state.autoplay, - }), - { - toggleAutoplay: PlayerControls.toggleAutoplay, - } -)(AutoplayToggle); +export default observer(AutoplayToggle);