Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite panel/models/reactive_html.ts #6527

Merged
merged 1 commit into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 16 additions & 18 deletions panel/models/comm_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {Model} from "@bokehjs/model"
import {Message} from "@bokehjs/protocol/message"
import {Receiver} from "@bokehjs/protocol/receiver"
import type {Patch, DocumentChangedEvent} from "@bokehjs/document"
import {isArray, isPlainObject} from "@bokehjs/core/util/types"
import {values, size} from "@bokehjs/core/util/object"

export const comm_settings: any = {
debounce: true,
Expand Down Expand Up @@ -101,28 +103,24 @@ export class CommManager extends Model {
}
}

protected _extract_buffers(value: any, buffers: ArrayBuffer[]): any {
let extracted: any
if (value instanceof Array) {
extracted = []
protected _extract_buffers(value: unknown, buffers: ArrayBuffer[]): void {
if (isArray(value)) {
for (const val of value) {
extracted.push(this._extract_buffers(val, buffers))
this._extract_buffers(val, buffers)
}
} else if (value instanceof Object) {
extracted = {}
for (const key in value) {
if (key === "buffer" && value[key] instanceof ArrayBuffer) {
const id = Object.keys(buffers).length
extracted = {id}
buffers.push(value[key])
break
} else if (isPlainObject(value)) {
if (size(value) == 1 && value.buffer instanceof ArrayBuffer) {
const {buffer} = value
delete value.buffer
const id = buffers.length
value.id = id
buffers.push(buffer)
} else {
for (const val of values(value)) {
this._extract_buffers(val, buffers)
}
extracted[key] = this._extract_buffers(value[key], buffers)
}
} else {
extracted = value
}
return extracted
}

process_events() {
Expand All @@ -133,7 +131,7 @@ export class CommManager extends Model {
this._event_buffer = []
const message = {...Message.create("PATCH-DOC", {}, patch)}
const buffers: ArrayBuffer[] = []
message.content = this._extract_buffers(message.content, buffers)
this._extract_buffers(message.content, buffers)
this._client_comm.send(message, {}, buffers)
for (const view of this.ns.shared_views.get(this.plot_id)) {
if (view !== this && view.document != null) {
Expand Down
65 changes: 34 additions & 31 deletions panel/models/html.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {ModelEvent} from "@bokehjs/core/bokeh_events"
import type * as p from "@bokehjs/core/properties"
import type {Attrs} from "@bokehjs/core/types"
import type {Attrs, Dict} from "@bokehjs/core/types"
import {entries} from "@bokehjs/core/util/object"
import {Markup} from "@bokehjs/models/widgets/markup"
import {PanelMarkupView} from "./layout"
import {serializeEvent} from "./event-to-object"

export class DOMEvent extends ModelEvent {
constructor(readonly node: string, readonly data: any) {
constructor(readonly node: string, readonly data: unknown) {
super()
}

Expand All @@ -19,27 +20,29 @@ export class DOMEvent extends ModelEvent {
}
}

export function htmlDecode(input: string): string | null {
export function html_decode(input: string): string | null {
const doc = new DOMParser().parseFromString(input, "text/html")
return doc.documentElement.textContent
}

export function runScripts(node: any): void {
Array.from(node.querySelectorAll("script")).forEach((oldScript: any) => {
const newScript = document.createElement("script")
Array.from(oldScript.attributes)
.forEach((attr: any) => newScript.setAttribute(attr.name, attr.value))
newScript.appendChild(document.createTextNode(oldScript.innerHTML))
if (oldScript.parentNode) {
oldScript.parentNode.replaceChild(newScript, oldScript)
export function run_scripts(node: Element): void {
for (const old_script of node.querySelectorAll("script")) {
const new_script = document.createElement("script")
for (const attr of old_script.attributes) {
new_script.setAttribute(attr.name, attr.value)
}
})
new_script.append(document.createTextNode(old_script.innerHTML))
const parent_node = old_script.parentNode
if (parent_node != null) {
parent_node.replaceChild(new_script, old_script)
}
}
}

export class HTMLView extends PanelMarkupView {
declare model: HTML

_event_listeners: any = {}
protected readonly _event_listeners: Map<string, Map<string, (event: Event) => void>> = new Map()

override connect_signals(): void {
super.connect_signals()
Expand Down Expand Up @@ -69,7 +72,7 @@ export class HTMLView extends PanelMarkupView {
if (html !== null) {
this.container.innerHTML = html
if (this.model.run_scripts) {
runScripts(this.container)
run_scripts(this.container)
}
this._setup_event_listeners()
}
Expand All @@ -96,8 +99,8 @@ export class HTMLView extends PanelMarkupView {
}

override process_tex(): string {
const decoded = htmlDecode(this.model.text)
const text = decoded || this.model.text
const decoded = html_decode(this.model.text)
const text = decoded ?? this.model.text
if (this.model.disable_math || !this.contains_tex(text)) {
return text
}
Expand Down Expand Up @@ -129,36 +132,36 @@ export class HTMLView extends PanelMarkupView {
}

private _remove_event_listeners(): void {
for (const node in this._event_listeners) {
const el: any = document.getElementById(node)
for (const [node, callbacks] of this._event_listeners) {
const el = document.getElementById(node)
if (el == null) {
console.warn(`DOM node '${node}' could not be found. Cannot subscribe to DOM events.`)
continue
}
for (const event_name in this._event_listeners[node]) {
const event_callback = this._event_listeners[node][event_name]
for (const [event_name, event_callback] of callbacks) {
el.removeEventListener(event_name, event_callback)
}
}
this._event_listeners = {}
this._event_listeners.clear()
}

private _setup_event_listeners(): void {
for (const node in this.model.events) {
const el: any = document.getElementById(node)
for (const [node, event_names] of entries(this.model.events)) {
const el = document.getElementById(node)
if (el == null) {
console.warn(`DOM node '${node}' could not be found. Cannot subscribe to DOM events.`)
continue
}
for (const event_name of this.model.events[node]) {
const callback = (event: any) => {
for (const event_name of event_names) {
const callback = (event: Event) => {
this.model.trigger_event(new DOMEvent(node, serializeEvent(event)))
}
el.addEventListener(event_name, callback)
if (!(node in this._event_listeners)) {
this._event_listeners[node] = {}
let callbacks = this._event_listeners.get(node)
if (callbacks === undefined) {
this._event_listeners.set(node, callbacks = new Map())
}
this._event_listeners[node][event_name] = callback
callbacks.set(event_name, callback)
}
}
}
Expand All @@ -168,7 +171,7 @@ export namespace HTML {
export type Attrs = p.AttrsOf<Props>

export type Props = Markup.Props & {
events: p.Property<any>
events: p.Property<Dict<string[]>>
run_scripts: p.Property<boolean>
}
}
Expand All @@ -186,8 +189,8 @@ export class HTML extends Markup {

static {
this.prototype.default_view = HTMLView
this.define<HTML.Props>(({Any, Bool}) => ({
events: [ Any, {} ],
this.define<HTML.Props>(({Bool, Str, List, Dict}) => ({
events: [ Dict(List(Str)), {} ],
run_scripts: [ Bool, true ],
}))
}
Expand Down