M1-Survey-Engine/tools/web_replay/ui/ReplayEngine.py

185 lines
6 KiB
Python

from datetime import datetime, timedelta
from typing import Callable
from PyQt6.QtCore import Qt, QUrl, QPointF, QTimer
from PyQt6.QtGui import QKeyEvent, QMouseEvent
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QApplication, QLabel
from tools.web_replay.ui import ReplayWebEngineView, ReplayNavigation
class ReplayEngine(QWidget):
"""
This widget allow to replay some event that occurred on a web page
"""
def __init__(self, start_time: datetime, replay_data: list):
super().__init__()
self.start_time = start_time - timedelta(days=1) # remove a day to prevent archive rounding
self.replay_data = replay_data
self.replay_index: int = 0
self.replay_time: float = 0
# layout
self._layout = QVBoxLayout()
self.setLayout(self._layout)
# cursor
self.cursor = QLabel(self)
self.cursor.setFixedSize(20, 20)
self.cursor.setStyleSheet("background-color: red; border-radius: 10px;")
# web
self.web = ReplayWebEngineView(self.start_time)
self._layout.addWidget(self.web, 1)
# information
self.navigation = ReplayNavigation()
self._layout.addWidget(self.navigation)
# event timer
self.timer = QTimer()
def run_event(self, event: dict, callback: Callable):
# TODO: check if click are done correctly, check position, if event are correct, ...
match event["type"]:
case "success":
# success event
print(f"success ! ({event['time']}s)")
callback()
case "url":
# changing url event
self.web.setUrl(QUrl(event["url"]))
# callback
self.web.loadFinished.connect(lambda ok: callback()) # NOQA: connect exist
case "resize":
# changing widget size event
w, h = event["size"]
zoom_factor: float = self.web.width() / w
self.web.setZoomFactor(zoom_factor)
# callback
callback()
case "keyboard_press":
# keyboard key pressed event
qevent = QKeyEvent(
QKeyEvent.Type.KeyPress,
event["key"],
Qt.KeyboardModifier.NoModifier
)
qevent.custom = True
QApplication.postEvent(self.web.focusProxy(), qevent)
# callback
callback()
case "keyboard_release":
# keyboard key released event
qevent = QKeyEvent(
QKeyEvent.Type.KeyRelease,
event["key"],
Qt.KeyboardModifier.NoModifier
)
qevent.custom = True
QApplication.postEvent(self.web.focusProxy(), qevent)
# callback
callback()
case "mouse_press":
# mouse pressed event
qevent = QMouseEvent(
QMouseEvent.Type.KeyPress,
QPointF(*event["position"]) / self.web.zoomFactor(),
Qt.MouseButton(event["button"]),
Qt.MouseButton.NoButton,
Qt.KeyboardModifier.NoModifier
)
qevent.custom = True
QApplication.postEvent(self.web.focusProxy(), qevent)
# callback
callback()
case "mouse_release":
# mouse pressed event
qevent = QMouseEvent(
QMouseEvent.Type.KeyRelease,
QPointF(*event["position"]) / self.web.zoomFactor(),
Qt.MouseButton(event["button"]),
Qt.MouseButton.NoButton,
Qt.KeyboardModifier.NoModifier
)
qevent.custom = True
QApplication.postEvent(self.web.focusProxy(), qevent)
# callback
callback()
case "mouse_move":
# mouse moved event
qevent = QMouseEvent(
QMouseEvent.Type.MouseMove,
QPointF(*event["position"]) / self.web.zoomFactor(),
Qt.MouseButton.NoButton,
Qt.MouseButton.NoButton,
Qt.KeyboardModifier.NoModifier
)
qevent.custom = True
QApplication.postEvent(self.web.focusProxy(), qevent)
# move the cursor
self.cursor.move(QPointF(*event["position"]).toPoint())
self.cursor.raise_()
# callback
callback()
case "scroll":
# scroll event
x, y = event["position"]
self.web.page().runJavaScript(
f"window.scrollTo({x}, {y});",
resultCallback=lambda result: callback()
)
def next(self):
# get event information
event = self.replay_data[self.replay_index]
self.replay_time = event["time"]
self.replay_index = self.replay_index + 1
# set text
self.navigation.set_description(f"{event}")
# run the event
self.run_event(
event,
self._next_callback
)
def _next_callback(self):
# prevent the web loading to call this function again
try:
self.web.loadFinished.disconnect(self._next_callback) # NOQA: disconnect exist
except TypeError:
pass
# if there are still events after this one
if self.replay_index < len(self.replay_data):
# next event
next_event: dict = self.replay_data[self.replay_index]
next_time: float = next_event["time"]
# prepare the timer to play the event at the corresponding time
self.timer.singleShot(
round((next_time - self.replay_time) / 1000),
self.next
)