From 8603439716a1e81bf1c6e9eff20c04ebdd0ea49e Mon Sep 17 00:00:00 2001 From: nick-delirium Date: Thu, 5 Jun 2025 17:18:00 +0200 Subject: [PATCH] ui: adjusting slot placement and classes --- .../app/player/web/managers/DOM/VirtualDOM.ts | 114 +++++++++--------- tracker/tracker/package.json | 2 +- tracker/tracker/src/main/app/guards.ts | 1 + .../tracker/src/main/app/observer/observer.ts | 42 ++++++- 4 files changed, 98 insertions(+), 61 deletions(-) diff --git a/frontend/app/player/web/managers/DOM/VirtualDOM.ts b/frontend/app/player/web/managers/DOM/VirtualDOM.ts index a6dd568dd..0fa71ff6b 100644 --- a/frontend/app/player/web/managers/DOM/VirtualDOM.ts +++ b/frontend/app/player/web/managers/DOM/VirtualDOM.ts @@ -110,7 +110,7 @@ abstract class VParent extends VNode { /* Inserting */ this.mountChildren(); if (this.notMontedChildren.size !== 0) { - console.error('VParent: Something went wrong with children insertion'); + console.error('VParent: Something went wrong with children insertion', this.notMontedChildren); } /* Removing in-between */ const { node } = this; @@ -155,62 +155,6 @@ export class VDocument extends VParent { } } -export class VSlot extends VElement { - assignedNodes: VChild[] = []; - - addAssigned(child: VChild) { - if (this.assignedNodes.indexOf(child) === -1) { - this.assignedNodes.push(child); - this.notMontedChildren.add(child); - } - } - - removeAssigned(child: VChild) { - this.assignedNodes = this.assignedNodes.filter((c) => c !== child); - this.notMontedChildren.delete(child); - } - - private mountAssigned() { - let nextMounted: VChild | null = null; - for (let i = this.assignedNodes.length - 1; i >= 0; i--) { - const child = this.assignedNodes[i]; - if (this.notMontedChildren.has(child)) { - this.node.insertBefore( - child.node, - nextMounted ? nextMounted.node : null, - ); - this.notMontedChildren.delete(child); - } - if (!this.notMontedChildren.has(child)) { - nextMounted = child; - } - } - } - - applyChanges() { - if (this.assignedNodes.length > 0) { - this.assignedNodes.forEach((c) => c.applyChanges()); - this.mountAssigned(); - const { node } = this; - const realChildren = node.childNodes; - if (realChildren.length > 0) { - for (let j = 0; j < this.assignedNodes.length; j++) { - while (realChildren[j] !== this.assignedNodes[j].node) { - if (isNode(realChildren[j])) { - node.removeChild(realChildren[j]); - } - } - } - } - while (realChildren.length > this.assignedNodes.length) { - node.removeChild(node.lastChild as Node); - } - } else { - super.applyChanges(); - } - } -} - export class VShadowRoot extends VParent { constructor(protected readonly createNode: () => ShadowRoot) { super(); @@ -353,6 +297,62 @@ export class VElement extends VParent { } } +export class VSlot extends VElement { + assignedNodes: VChild[] = []; + + addAssigned(child: VChild) { + if (this.assignedNodes.indexOf(child) === -1) { + this.assignedNodes.push(child); + this.notMontedChildren.add(child); + } + } + + removeAssigned(child: VChild) { + this.assignedNodes = this.assignedNodes.filter((c) => c !== child); + this.notMontedChildren.delete(child); + } + + private mountAssigned() { + let nextMounted: VChild | null = null; + for (let i = this.assignedNodes.length - 1; i >= 0; i--) { + const child = this.assignedNodes[i]; + if (this.notMontedChildren.has(child)) { + this.node.insertBefore( + child.node, + nextMounted ? nextMounted.node : null, + ); + this.notMontedChildren.delete(child); + } + if (!this.notMontedChildren.has(child)) { + nextMounted = child; + } + } + } + + applyChanges() { + if (this.assignedNodes.length > 0) { + this.assignedNodes.forEach((c) => c.applyChanges()); + this.mountAssigned(); + const { node } = this; + const realChildren = node.childNodes; + if (realChildren.length > 0) { + for (let j = 0; j < this.assignedNodes.length; j++) { + while (realChildren[j] !== this.assignedNodes[j].node) { + if (isNode(realChildren[j])) { + node.removeChild(realChildren[j]); + } + } + } + } + while (realChildren.length > this.assignedNodes.length) { + node.removeChild(node.lastChild as Node); + } + } else { + super.applyChanges(); + } + } +} + export class VHTMLElement extends VElement { constructor(node: HTMLElement) { super('HTML', false); diff --git a/tracker/tracker/package.json b/tracker/tracker/package.json index 5ed723567..e125fcf9e 100644 --- a/tracker/tracker/package.json +++ b/tracker/tracker/package.json @@ -1,7 +1,7 @@ { "name": "@openreplay/tracker", "description": "The OpenReplay tracker main package", - "version": "17.0.0-beta.0", + "version": "17.1.0", "keywords": [ "logging", "replay" diff --git a/tracker/tracker/src/main/app/guards.ts b/tracker/tracker/src/main/app/guards.ts index f7a7065d3..b3d1c2556 100644 --- a/tracker/tracker/src/main/app/guards.ts +++ b/tracker/tracker/src/main/app/guards.ts @@ -45,6 +45,7 @@ type TagTypeMap = { style: HTMLStyleElement | SVGStyleElement link: HTMLLinkElement canvas: HTMLCanvasElement + slot: HTMLSlotElement } export function hasTag( el: Node, diff --git a/tracker/tracker/src/main/app/observer/observer.ts b/tracker/tracker/src/main/app/observer/observer.ts index 09e02caaf..abd2a0466 100644 --- a/tracker/tracker/src/main/app/observer/observer.ts +++ b/tracker/tracker/src/main/app/observer/observer.ts @@ -11,7 +11,8 @@ import { UnbindNodes, SetNodeAttribute, AdoptedSSInsertRuleURLBased, - AdoptedSSAddOwner + AdoptedSSAddOwner, + SetNodeSlot, } from '../messages.gen.js' import App from '../index.js' import { @@ -199,6 +200,7 @@ export default abstract class Observer { private readonly indexes: Array = [] private readonly attributesMap: Map> = new Map() private readonly textSet: Set = new Set() + private readonly slotMap: Map = new Map() private readonly disableSprites: boolean = false /** * this option means that, instead of using link element with href to load css, @@ -428,6 +430,18 @@ export default abstract class Observer { private bindNode(node: Node): void { const [id, isNew] = this.app.nodes.registerNode(node) + if (isElementNode(node) && hasTag(node, 'slot')) { + this.app.nodes.attachNodeListener(node, 'slotchange', () => { + const sl = node as HTMLSlotElement + sl.assignedNodes({ flatten: true }).forEach((n) => { + const nid = this.app.nodes.getID(n) + if (nid !== undefined) { + this.recents.set(nid, RecentsType.Removed) + this.commitNode(nid) + } + }) + }) + } if (isNew) { this.recents.set(id, RecentsType.New) } else if (this.recents.get(id) !== RecentsType.New) { @@ -463,6 +477,9 @@ export default abstract class Observer { private unbindTree(node: Node) { const id = this.app.nodes.unregisterNode(node) + if (id !== undefined) { + this.slotMap.delete(id) + } if (id !== undefined && this.recents.get(id) === RecentsType.Removed) { // Sending RemoveNode only for parent to maintain this.app.send(RemoveNode(id)) @@ -501,8 +518,8 @@ export default abstract class Observer { if (isRootNode(node)) { return true } - // @ts-ignore SALESFORCE - const parent = node.assignedSlot ? node.assignedSlot : node.parentNode + const slot = (node as any).assignedSlot as HTMLSlotElement | null + const parent = node.parentNode let parentID: number | undefined // Disable parent check for the upper context HTMLHtmlElement, because it is root there... (before) @@ -576,10 +593,29 @@ export default abstract class Observer { this.app.send(CreateTextNode(id, parentID as number, index)) this.throttledSetNodeData(id, parent as Element, node.data) } + if (slot) { + const slotID = this.app.nodes.getID(slot) + console.log('Openreplay: slotID', slotID, 'for node', id, node, 'slot', slot) + if (slotID !== undefined) { + this.slotMap.set(id, slotID) + this.app.send(SetNodeSlot(id, slotID)) + } + } return true } if (recentsType === RecentsType.Removed && parentID !== undefined) { this.app.send(MoveNode(id, parentID, index)) + console.log('RM Openreplay', id, node, 'slot', slot) + if (slot) { + const slotID = this.app.nodes.getID(slot) + if (slotID !== undefined && this.slotMap.get(id) !== slotID) { + this.slotMap.set(id, slotID) + this.app.send(SetNodeSlot(id, slotID)) + } + } else if (this.slotMap.has(id)) { + this.slotMap.delete(id) + this.app.send(SetNodeSlot(id, 0)) + } } const attr = this.attributesMap.get(id) if (attr !== undefined) {