ui: fix spritemap generation for assist sessions

This commit is contained in:
nick-delirium 2025-03-28 16:41:17 +01:00 committed by Delirium
parent 421b3d1dc5
commit e95bdab478
4 changed files with 80 additions and 77 deletions

View file

@ -43,27 +43,6 @@ export default class MessageLoader {
this.session = session;
}
/**
* TODO: has to be moved out of messageLoader logic somehow
* */
spriteMapSvg: SVGElement | null = null;
potentialSpriteMap: Record<string, any> = {};
domParser: DOMParser | null = null;
createSpriteMap = () => {
if (!this.spriteMapSvg) {
this.domParser = new DOMParser();
this.spriteMapSvg = document.createElementNS(
'http://www.w3.org/2000/svg',
'svg',
);
this.spriteMapSvg.setAttribute('style', 'display: none;');
this.spriteMapSvg.setAttribute('id', 'reconstructed-sprite');
}
};
createNewParser(
shouldDecrypt = true,
onMessagesDone: (msgs: PlayerMsg[], file?: string) => void,
@ -101,21 +80,6 @@ export default class MessageLoader {
let startTimeSet = false;
msgs.forEach((msg, i) => {
if (msg.tp === MType.SetNodeAttribute) {
if (msg.value.includes('_$OPENREPLAY_SPRITE$_')) {
this.createSpriteMap();
if (!this.domParser) {
return console.error('DOM parser is not initialized?');
}
handleSprites(
this.potentialSpriteMap,
this.domParser,
msg,
this.spriteMapSvg!,
i,
);
}
}
if (msg.tp === MType.Redux || msg.tp === MType.ReduxDeprecated) {
if ('actionTime' in msg && msg.actionTime) {
msg.time = msg.actionTime - this.session.startedAt;
@ -153,7 +117,7 @@ export default class MessageLoader {
// .sort((m1, m2) => m1.time - m2.time)
.sort(brokenDomSorter)
.sort(sortIframes);
if (brokenMessages > 0) {
console.warn(
'Broken timestamp messages',
@ -333,10 +297,6 @@ export default class MessageLoader {
await Promise.allSettled([restDomFilesPromise, restDevtoolsFilesPromise]);
this.messageManager.onFileReadSuccess();
// no sprites for mobile
if (this.spriteMapSvg && 'injectSpriteMap' in this.messageManager) {
this.messageManager.injectSpriteMap(this.spriteMapSvg);
}
};
loadEFSMobs = async () => {
@ -471,40 +431,6 @@ function findBrokenNodes(nodes: any[]) {
return result;
}
function handleSprites(
potentialSpriteMap: Record<string, any>,
parser: DOMParser,
msg: Record<string, any>,
spriteMapSvg: SVGElement,
i: number,
) {
const [_, svgData] = msg.value.split('_$OPENREPLAY_SPRITE$_');
const potentialSprite = potentialSpriteMap[svgData];
if (potentialSprite) {
msg.value = potentialSprite;
} else {
const svgDoc = parser.parseFromString(svgData, 'image/svg+xml');
const originalSvg = svgDoc.querySelector('svg');
if (originalSvg) {
const symbol = document.createElementNS(
'http://www.w3.org/2000/svg',
'symbol',
);
const symbolId = `symbol-${msg.id || `ind-${i}`}`; // Generate an ID if missing
symbol.setAttribute('id', symbolId);
symbol.setAttribute(
'viewBox',
originalSvg.getAttribute('viewBox') || '0 0 24 24',
);
symbol.innerHTML = originalSvg.innerHTML;
spriteMapSvg.appendChild(symbol);
msg.value = `#${symbolId}`;
potentialSpriteMap[svgData] = `#${symbolId}`;
}
}
}
// @ts-ignore
window.searchOrphans = (msgs) =>
findBrokenNodes(msgs.filter((m) => [8, 9, 10, 70].includes(m.tp)));

View file

@ -201,8 +201,16 @@ export default class MessageManager {
}
Object.values(this.tabs).forEach((tab) => tab.onFileReadSuccess?.());
this.updateSpriteMap();
};
public updateSpriteMap = () => {
if (this.spriteMapSvg) {
this.injectSpriteMap(this.spriteMapSvg);
}
}
public onFileReadFailed = (...e: any[]) => {
logger.error(e);
this.state.update({ error: true });
@ -337,9 +345,38 @@ export default class MessageManager {
this.state.update({ tabChangeEvents: this.tabChangeEvents });
}
spriteMapSvg: SVGElement | null = null;
potentialSpriteMap: Record<string, any> = {};
domParser: DOMParser | null = null;
createSpriteMap = () => {
if (!this.spriteMapSvg) {
this.domParser = new DOMParser();
this.spriteMapSvg = document.createElementNS(
'http://www.w3.org/2000/svg',
'svg',
);
this.spriteMapSvg.setAttribute('style', 'display: none;');
this.spriteMapSvg.setAttribute('id', 'reconstructed-sprite');
}
};
distributeMessage = (msg: Message & { tabId: string }): void => {
// @ts-ignore placeholder msg for timestamps
if (msg.tp === 9999) return;
if (msg.tp === MType.SetNodeAttribute) {
if (msg.value.includes('_$OPENREPLAY_SPRITE$_')) {
this.createSpriteMap();
if (!this.domParser) {
return console.error('DOM parser is not initialized?');
}
handleSprites(
this.potentialSpriteMap,
this.domParser,
msg,
this.spriteMapSvg!,
);
}
}
if (!this.tabs[msg.tabId]) {
this.tabsAmount++;
this.state.update({
@ -454,3 +491,36 @@ function mapTabs(tabs: Record<string, TabSessionManager>) {
return tabMap;
}
function handleSprites(
potentialSpriteMap: Record<string, any>,
parser: DOMParser,
msg: Record<string, any>,
spriteMapSvg: SVGElement,
) {
const [_, svgData] = msg.value.split('_$OPENREPLAY_SPRITE$_');
const potentialSprite = potentialSpriteMap[svgData];
if (potentialSprite) {
msg.value = potentialSprite;
} else {
const svgDoc = parser.parseFromString(svgData, 'image/svg+xml');
const originalSvg = svgDoc.querySelector('svg');
if (originalSvg) {
const symbol = document.createElementNS(
'http://www.w3.org/2000/svg',
'symbol',
);
const symbolId = `symbol-${msg.id || `ind-${msg.time}`}`; // Generate an ID if missing
symbol.setAttribute('id', symbolId);
symbol.setAttribute(
'viewBox',
originalSvg.getAttribute('viewBox') || '0 0 24 24',
);
symbol.innerHTML = originalSvg.innerHTML;
spriteMapSvg.appendChild(symbol);
msg.value = `#${symbolId}`;
potentialSpriteMap[svgData] = `#${symbolId}`;
}
}
}

View file

@ -43,6 +43,7 @@ export default class WebLivePlayer extends WebPlayer {
wpState,
(id) => this.messageManager.getNode(id),
agentId,
this.messageManager.updateSpriteMap,
uiErrorHandler,
);
this.assistManager.connect(session.agentToken!, agentId, projectId);

View file

@ -3,14 +3,14 @@ import type { Socket } from 'socket.io-client';
import type { PlayerMsg, Store } from 'App/player';
import CanvasReceiver from 'Player/web/assist/CanvasReceiver';
import { gunzipSync } from 'fflate';
import { Message } from '../messages';
import { Message, MType } from '../messages';
import type Screen from '../Screen/Screen';
import MStreamReader from '../messages/MStreamReader';
import JSONRawMessageReader from '../messages/JSONRawMessageReader';
import Call, { CallingState } from './Call';
import RemoteControl, { RemoteControlStatus } from './RemoteControl';
import ScreenRecording, { SessionRecordingStatus } from './ScreenRecording';
import { debounceCall } from 'App/utils'
export { RemoteControlStatus, SessionRecordingStatus, CallingState };
export enum ConnectionStatus {
@ -82,6 +82,7 @@ export default class AssistManager {
private store: Store<typeof AssistManager.INITIAL_STATE>,
private getNode: MessageManager['getNode'],
public readonly agentId: number,
private readonly updateSpriteMap: () => void,
public readonly uiErrorHandler?: {
error: (msg: string) => void;
},
@ -243,6 +244,11 @@ export default class AssistManager {
msg !== null;
msg = reader.readNext()
) {
if (msg.tp === MType.SetNodeAttribute) {
if (msg.value.includes('_$OPENREPLAY_SPRITE$_')) {
debounceCall(this.updateSpriteMap, 250)()
}
}
this.handleMessage(msg, msg._index);
}
};