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

Lottie webcomponent player example using the ReactiveHTML component #1897

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9e8dfdb
Compile against bokeh 2.3
philippjfr Jan 8, 2021
99f21a0
Update package.json
philippjfr Jan 8, 2021
1779184
Update package-lock.json
philippjfr Jan 8, 2021
33f8de5
Use color2css
philippjfr Jan 8, 2021
c73fdfc
Implement CustomHTML model
philippjfr Jan 9, 2021
3cad056
Cleanup
philippjfr Jan 9, 2021
86eaa7d
Add CustomReactive
philippjfr Jan 9, 2021
573164c
Use MutationObserver
philippjfr Jan 9, 2021
cdddea2
Rerender on changes
philippjfr Jan 9, 2021
e2fbf01
Rename to ReactiveHTML
philippjfr Jan 10, 2021
6205d8c
Fixed imports
philippjfr Jan 10, 2021
df04c0f
Add support for children
philippjfr Jan 10, 2021
5cbe7bf
Refactor and cleanup
philippjfr Jan 10, 2021
8e2bdfd
Use preact
philippjfr Jan 10, 2021
453a5c2
Add idom support
philippjfr Jan 11, 2021
0e1709d
Allow * event listeners on ReactiveHTML
philippjfr Jan 11, 2021
51895c8
Clean up TS
philippjfr Jan 11, 2021
02821c2
Cleanup
philippjfr Jan 11, 2021
c0c224b
Fix flakes
philippjfr Jan 11, 2021
1ce0fe8
Test against bokeh dev
philippjfr Jan 11, 2021
ce2cd9b
added first example with questions
Jan 12, 2021
df5c0b0
added id problem
Jan 12, 2021
885e404
Support scripts
philippjfr Jan 12, 2021
f2a6ad5
Support inlined Python callbacks
philippjfr Jan 12, 2021
72406c9
Clean up DOMEvent
philippjfr Jan 12, 2021
8828481
Fix flakes
philippjfr Jan 12, 2021
503d87d
Further fixes
philippjfr Jan 12, 2021
5df5755
Improve warning
philippjfr Jan 12, 2021
6b2c228
Update bokeh
philippjfr Jan 12, 2021
a798b5d
Allow associating script with parameters
philippjfr Jan 12, 2021
77c7b5b
Merge branch 'custom_html_model' of https://github.com/holoviz/panel …
Jan 13, 2021
946d89f
fixed things according to new api
Jan 13, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
DESC: "Python ${{ matrix.python-version }} tests"
HV_REQUIREMENTS: "unit_tests"
PYTHON_VERSION: ${{ matrix.python-version }}
CHANS_DEV: "-c pyviz/label/dev -c bokeh -c conda-forge"
CHANS_DEV: "-c pyviz/label/dev -c bokeh/label/dev -c conda-forge"
CHANS: "-c pyviz -c bokeh -c conda-forge"
DISPLAY: ":99.0"
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
15,489 changes: 15,489 additions & 0 deletions examples/gallery/reactive_html/LottieWebComponentPlayer.ipynb

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions panel/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
files.
"""

from .idom import IDOM # noqa
from .ipywidget import IPyWidget # noqa
from .layout import Card # noqa
from .location import Location # noqa
from .markup import JSON, HTML # noqa
from .reactive_html import ReactiveHTML # noqa
from .state import State # noqa
from .tabulator import DataTabulator # noqa
from .widgets import ( # noqa
Expand Down
2 changes: 1 addition & 1 deletion panel/models/ace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export class AcePlot extends HTMLBox {
print_margin: [ p.Boolean, false ]
})

this.override({
this.override<AcePlot.Props>({
height: 300,
width: 300
})
Expand Down
3 changes: 2 additions & 1 deletion panel/models/card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {Layoutable} from "@bokehjs/core/layout/layoutable"
import {Column as ColumnLayout} from "@bokehjs/core/layout/grid"
import {Size} from "@bokehjs/core/layout/types"
import * as p from "@bokehjs/core/properties"
import {color2css} from "@bokehjs/core/util/color"

export class CollapseableColumnLayout extends ColumnLayout {
collapsed: boolean
Expand Down Expand Up @@ -51,7 +52,7 @@ export class CardView extends ColumnView {

const {background, button_css_classes, header_color, header_tag, header_css_classes} = this.model

this.el.style.backgroundColor = background != null ? background : ""
this.el.style.backgroundColor = background != null ? color2css(background) : ""
classes(this.el).clear().add(...this.css_classes())

let header_background = this.model.header_background
Expand Down
2 changes: 1 addition & 1 deletion panel/models/deckgl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export class DeckGLPlot extends HTMLBox {
viewState: [ p.Any ],
})

this.override({
this.override<DeckGLPlot.Props>({
height: 400,
width: 600
});
Expand Down
135 changes: 135 additions & 0 deletions panel/models/event-to-object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
export function serializeEvent(event: any) {
const data: any = {type: event.type};
if ("value" in event.target) {
data.value = event.target.value;
}
if (event.type in eventTransforms) {
Object.assign(data, eventTransforms[event.type](event));
}
return data;
}

const eventCategoryTransforms: any = {
clipboard: (event: any) => ({
clipboardData: event.clipboardData,
}),
composition: (event: any) => ({
data: event.data,
}),
keyboard: (event: any) => ({
altKey: event.altKey,
charCode: event.charCode,
ctrlKey: event.ctrlKey,
key: event.key,
keyCode: event.keyCode,
locale: event.locale,
location: event.location,
metaKey: event.metaKey,
repeat: event.repeat,
shiftKey: event.shiftKey,
which: event.which,
}),
mouse: (event: any) => ({
altKey: event.altKey,
button: event.button,
buttons: event.buttons,
clientX: event.clientX,
clientY: event.clientY,
ctrlKey: event.ctrlKey,
metaKey: event.metaKey,
pageX: event.pageX,
pageY: event.pageY,
screenX: event.screenX,
screenY: event.screenY,
shiftKey: event.shiftKey,
}),
pointer: (event: any) => ({
pointerId: event.pointerId,
width: event.width,
height: event.height,
pressure: event.pressure,
tiltX: event.tiltX,
tiltY: event.tiltY,
pointerType: event.pointerType,
isPrimary: event.isPrimary,
}),
touch: (event: any) => ({
altKey: event.altKey,
ctrlKey: event.ctrlKey,
metaKey: event.metaKey,
shiftKey: event.shiftKey,
}),
ui: (event: any) => ({
detail: event.detail,
}),
wheel: (event: any) => ({
deltaMode: event.deltaMode,
deltaX: event.deltaX,
deltaY: event.deltaY,
deltaZ: event.deltaZ,
}),
animation: (event: any) => ({
animationName: event.animationName,
pseudoElement: event.pseudoElement,
elapsedTime: event.elapsedTime,
}),
transition: (event: any) => ({
propertyName: event.propertyName,
pseudoElement: event.pseudoElement,
elapsedTime: event.elapsedTime,
}),
};

const eventTypeCategories: any = {
clipboard: ["copy", "cut", "paste"],
composition: ["compositionend", "compositionstart", "compositionupdate"],
keyboard: ["keydown", "keypress", "keyup"],
mouse: [
"click",
"contextmenu",
"doubleclick",
"drag",
"dragend",
"dragenter",
"dragexit",
"dragleave",
"dragover",
"dragstart",
"drop",
"mousedown",
"mouseenter",
"mouseleave",
"mousemove",
"mouseout",
"mouseover",
"mouseup",
],
pointer: [
"pointerdown",
"pointermove",
"pointerup",
"pointercancel",
"gotpointercapture",
"lostpointercapture",
"pointerenter",
"pointerleave",
"pointerover",
"pointerout",
],
selection: ["select"],
touch: ["touchcancel", "touchend", "touchmove", "touchstart"],
ui: ["scroll"],
wheel: ["wheel"],
animation: ["animationstart", "animationend", "animationiteration"],
transition: ["transitionend"],
};

const eventTransforms: any = {};

Object.keys(eventTypeCategories).forEach((category: string) => {
eventTypeCategories[category].forEach((type: string) => {
eventTransforms[type] = eventCategoryTransforms[category];
});
});

export default serializeEvent;
10 changes: 5 additions & 5 deletions panel/models/file_download.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {InputWidget, InputWidgetView} from "@bokehjs/models/widgets/input_widget"

import {bk_btn, bk_btn_type} from "@bokehjs/styles/buttons"
import * as buttons from "@bokehjs/styles/buttons.css"
import {input} from "@bokehjs/core/dom"

import {ButtonType} from "@bokehjs/core/enums"
Expand Down Expand Up @@ -170,12 +170,12 @@ export class FileDownloadView extends InputWidgetView {

_update_button_style(): void{
if ( !this.anchor_el.hasAttribute("class") ){ // When the widget is rendered.
this.anchor_el.classList.add(bk_btn)
this.anchor_el.classList.add(bk_btn_type(this.model.button_type))
this.anchor_el.classList.add(buttons.btn)
this.anchor_el.classList.add(buttons.btn_type(this.model.button_type))
} else { // When the button type is changed.
const prev_button_type = this.anchor_el.classList.item(1)
if ( prev_button_type ) {
this.anchor_el.classList.replace(prev_button_type, bk_btn_type(this.model.button_type))
this.anchor_el.classList.replace(prev_button_type, buttons.btn_type(this.model.button_type))
}
}
}
Expand Down Expand Up @@ -227,7 +227,7 @@ export class FileDownload extends InputWidget {
_transfers: [ p.Number, 0 ],
})

this.override({
this.override<FileDownload.Props>({
title: "",
})
}
Expand Down
57 changes: 48 additions & 9 deletions panel/models/html.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
import * as p from "@bokehjs/core/properties"
import {Markup} from "@bokehjs/models/widgets/markup"
import {ModelEvent, JSON} from "@bokehjs/core/bokeh_events"
import {PanelMarkupView} from "./layout"
import {serializeEvent} from "./event-to-object";

export class DOMEvent extends ModelEvent {
event_name: string = "dom_event"

constructor(readonly node: string, readonly data: any) {
super()
}

protected _to_json(): JSON {
return {model: this.origin, node: this.node, data: this.data}
}
}

export function htmlDecode(input: string): string | null {
var 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 class HTMLView extends PanelMarkupView {
model: HTML

Expand All @@ -19,21 +44,32 @@ export class HTMLView extends PanelMarkupView {
return;
}
this.markup_el.innerHTML = html
Array.from(this.markup_el.querySelectorAll("script")).forEach( oldScript => {
const newScript = document.createElement("script");
Array.from(oldScript.attributes)
.forEach( attr => newScript.setAttribute(attr.name, attr.value) );
newScript.appendChild(document.createTextNode(oldScript.innerHTML));
if (oldScript.parentNode)
oldScript.parentNode.replaceChild(newScript, oldScript);
});
runScripts(this.markup_el)
this._setup_event_listeners()
}

_setup_event_listeners(): void {
for (const name in this.model.events) {
const el: any = document.getElementById(name)
if (el == null) {
console.warn(`DOM node '${name}' could not be found. Cannot subscribe to DOM events.`)
continue
}
for (const event_name of this.model.events[name]) {
el.addEventListener(event_name, (event: any) => {
this.model.trigger_event(new DOMEvent(name, serializeEvent(event)))
})
}
}
}
}

export namespace HTML {
export type Attrs = p.AttrsOf<Props>

export type Props = Markup.Props
export type Props = Markup.Props & {
events: p.Property<any>
}
}

export interface HTML extends HTML.Attrs {}
Expand All @@ -49,5 +85,8 @@ export class HTML extends Markup {

static init_HTML(): void {
this.prototype.default_view = HTMLView
this.define<HTML.Props>({
events: [p.Any, {} ]
})
}
}
11 changes: 11 additions & 0 deletions panel/models/idom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from bokeh.core.properties import Any, Dict, String
from bokeh.models.widgets import Markup


class IDOM(Markup):

importSourceUrl = String()

event = Dict(String, Any)

msg = Dict(String, Any)
Loading