@bokeh/bokehjs
Version:
Interactive, novel data visualization
391 lines • 19 kB
JavaScript
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "preact/jsx-runtime";
import { UIElement, UIElementView } from "./ui_element";
import * as p from "../../core/properties";
import { HasProps } from "../../core/has_props";
import { isArray } from "../../core/util/types";
import { keys } from "../../core/util/object";
import { map } from "../../core/util/iterator";
import { receivers_for_sender } from "../../core/signaling";
import { diagnostics } from "../../core/diagnostics";
import { assert } from "../../core/util/assert";
import { ValuePrinter, KindPrinter, OpaqueKindPrinter } from "./printers";
import examiner_css from "../../styles/examiner.css";
import pretty_css from "../../styles/pretty.css";
import icons_css from "../../styles/icons.css";
import { render, Component } from "preact";
import { signal, computed } from "@preact/signals";
import { Serializer } from "../../core/serialization/serializer";
import { Deserializer } from "../../core/serialization/deserializer";
function highlight(el) {
for (const animation of el.getAnimations()) {
animation.cancel();
}
el.animate([
{ backgroundColor: "#def189" },
{ backgroundColor: "initial" },
], { duration: 2000 });
}
function emphasize(text, pattern) {
const i = text.indexOf(pattern);
if (i == -1) {
return _jsx("span", { children: text });
}
else {
const j = i + pattern.length;
const prefix = text.substring(0, i);
const infix = text.substring(i, j);
const suffix = text.substring(j);
return (_jsxs(_Fragment, { children: [_jsx("span", { children: prefix }), _jsx("span", { class: "underline", children: infix }), _jsx("span", { children: suffix })] }));
}
}
function compute_attrs(model) {
const bases = [];
let proto = Object.getPrototypeOf(model);
do {
bases.push([proto.constructor, keys(proto._props)]);
proto = Object.getPrototypeOf(proto);
} while (proto.constructor != HasProps);
bases.reverse();
const cumulative = [];
for (const [, attrs] of bases) {
attrs.splice(0, cumulative.length);
cumulative.push(...attrs);
}
return bases.map(([base, attrs]) => [base, attrs.map((attr) => model.property(attr))]);
}
export class ExaminerView extends UIElementView {
static __name__ = "ExaminerView";
stylesheets() {
return [...super.stylesheets(), pretty_css, examiner_css, icons_css];
}
render() {
super.render();
const models_filter = signal("");
const props_filter = signal("");
const watches_filter = signal("");
const group_props = signal(true);
const show_initial = signal(true);
const show_internal = signal(true);
const opaque_types = signal(false);
const watched_props = signal(new Set());
function cls(...classes) {
const transformed = classes
.flatMap((cls) => isArray(cls) ? cls : [cls])
.filter((cls) => cls != null)
.map((cls) => cls.trim())
.filter((cls) => cls.length != 0);
return [...new Set(transformed)].join(" ");
}
function click(obj) {
if (obj instanceof HasProps) {
current_model.value = obj;
}
}
function to_html(obj) {
const printer = new ValuePrinter(click);
return printer.to_html(obj);
}
class ModelItem extends Component {
static __name__ = "ModelItem";
constructor(props) {
super(props);
this.state = {
value_changed: new Date(),
};
}
_model_el = null;
listener = ((obj) => {
if (!(obj instanceof p.Property && this._model_el != null)) {
return;
}
const { model } = this.props;
for (const prop of model) {
if (prop == obj) {
this.setState({ value_changed: new Date() });
highlight(this._model_el);
}
}
}).bind(this);
componentDidMount() {
diagnostics.connect(this.listener);
}
componentWillUnmount() {
diagnostics.disconnect(this.listener);
}
render() {
const { model } = this.props;
const root = model.is_root ? _jsx("span", { class: "tag", children: "root" }) : null;
const key_down = (event) => {
if (event.key == "Enter") {
click(model);
}
};
const active = current_model.value == model ? "active" : null;
return (_jsxs("span", { class: cls("model-ref", active), tabIndex: 0, onKeyDown: key_down, ref: (el) => { this._model_el = el; }, children: [to_html(model), root] }));
}
}
class ModelsToolbar extends Component {
static __name__ = "ModelsToolbar";
render() {
const key_up = (event) => {
if (event.currentTarget instanceof HTMLInputElement) {
models_filter.value = event.currentTarget.value;
}
};
return (_jsx("div", { class: "toolbar", children: _jsx("input", { class: "filter", type: "text", placeholder: "Filter", onKeyUp: key_up }) }));
}
}
class PropsToolbar extends Component {
static __name__ = "PropsToolbar";
render() {
const key_up = (event) => {
if (event.currentTarget instanceof HTMLInputElement) {
props_filter.value = event.currentTarget.value;
}
};
return (_jsxs("div", { class: "toolbar", children: [_jsx("input", { class: "filter", type: "text", placeholder: "Filter", onKeyUp: key_up }), _jsxs("span", { class: "checkbox", children: [_jsx("input", { type: "checkbox", checked: group_props.value, onChange: (event) => group_props.value = event.currentTarget.checked }), _jsx("span", { children: "Group" })] }), _jsxs("span", { class: "checkbox", children: [_jsx("input", { type: "checkbox", checked: show_initial.value, onChange: (event) => show_initial.value = event.currentTarget.checked }), _jsx("span", { children: "Show initial" })] }), _jsxs("span", { class: "checkbox", children: [_jsx("input", { type: "checkbox", checked: show_internal.value, onChange: (event) => show_internal.value = event.currentTarget.checked }), _jsx("span", { children: "Show internal" })] }), _jsxs("span", { class: "checkbox", children: [_jsx("input", { type: "checkbox", checked: opaque_types.value, onChange: (event) => opaque_types.value = event.currentTarget.checked }), _jsx("span", { children: "Opaque types" })] })] }));
}
}
class ModelsList extends Component {
static __name__ = "ModelsList";
constructor(props) {
super(props);
}
models_list = computed(() => {
const pattern = models_filter.value;
return this.props.models.filter((model) => model.constructor.__qualified__.includes(pattern));
});
render() {
return (_jsxs("div", { class: "models-panel", children: [_jsx(ModelsToolbar, {}), _jsx("div", { class: "models-list", children: this.models_list.value.map((model) => _jsx(ModelItem, { model: model })) })] }));
}
}
class PropEditor extends Component {
static __name__ = "PropEditor";
constructor(props) {
super(props);
}
get serializer() {
const doc = this.props.prop.obj.document;
assert(doc != null);
const all_refs = new Map(map(doc.all_models, (model) => [model, model.ref()]));
return new Serializer({ binary: false, include_defaults: false, references: all_refs });
}
get deserializer() {
const doc = this.props.prop.obj.document;
assert(doc != null);
const all_models = new Map(map(doc.all_models, (model) => [model.id, model]));
return new Deserializer(doc.resolver, all_models, (obj) => obj.attach_document(doc));
}
commit(value) {
const parsed = JSON.parse(value);
const decoded = this.deserializer.decode(parsed);
const { prop } = this.props;
prop.obj.setv({ [prop.attr]: decoded });
}
input_el = null;
render() {
const { prop } = this.props;
const value = this.serializer.encode(prop.get_value());
return (_jsxs(_Fragment, { children: [_jsx("input", { type: "text", ref: (el) => { this.input_el = el; }, onKeyUp: (event) => this.on_key_up(event), value: JSON.stringify(value) }), _jsx("div", { class: "btn btn-accept", onClick: () => this.accept_edit() }), _jsx("div", { class: "btn btn-cancel", onClick: () => this.cancel_edit() }), _jsx("div", { class: "btn btn-delete", onClick: () => this.delete_value() })] }));
}
accept_edit() {
assert(this.input_el != null);
this.commit(this.input_el.value);
this.finalize();
}
cancel_edit() {
this.finalize();
}
delete_value() {
const { prop } = this.props;
const value = prop.default_value(prop.obj);
prop.obj.setv({ [prop.attr]: value });
this.finalize();
}
finalize() {
this.props.close();
}
on_key_up(event) {
switch (event.key) {
case "Enter": {
this.commit(event.currentTarget.value);
break;
}
case "Escape": {
this.cancel_edit();
break;
}
default:
}
}
componentDidMount() {
this.input_el?.focus();
}
}
class PropValue extends Component {
static __name__ = "PropValue";
constructor(props) {
super(props);
this.state = {
value_changed: new Date(),
};
}
_value_el = null;
listener = ((obj) => {
if (obj === this.props.prop && this._value_el != null) {
this.setState({ value_changed: new Date() });
highlight(this._value_el);
}
}).bind(this);
componentDidMount() {
diagnostics.connect(this.listener);
}
componentWillUnmount() {
diagnostics.disconnect(this.listener);
}
render() {
const { prop } = this.props;
return (_jsx("span", { class: "value", ref: (el) => { this._value_el = el; }, children: prop.is_unset ? _jsx("span", { children: "unset" }) : to_html(prop.get_value()) }));
}
}
class PropItem extends Component {
static __name__ = "PropItem";
constructor(props) {
super(props);
this.state = { editing: false };
}
toggle_edit() {
const { editing } = this.state;
this.setState({ editing: !editing });
}
render() {
const { prop } = this.props;
const connections = receivers_for_sender.get(prop.obj) ?? [];
const listeners = connections.filter((connection) => connection.signal == prop.change).length;
const watched = watched_props.value.has(prop);
const watch_el = _jsx("input", { type: "checkbox", checked: watched, onChange: (event) => {
const { checked } = event.currentTarget;
const watched = watched_props.value;
watched[checked ? "add" : "delete"](prop);
watched_props.value = new Set(watched);
} });
const dirty = prop.dirty ? "dirty" : null;
const internal = prop.internal ? "internal" : null;
const hidden = !prop.dirty && !show_initial.value || prop.internal && !show_internal.value ? "hidden" : null;
const pattern = props_filter.value;
const { attr } = prop;
const kind_printer = opaque_types.value ? new OpaqueKindPrinter() : new KindPrinter();
const { editing } = this.state;
return (_jsxs("div", { class: cls("prop", dirty, internal, hidden), children: [_jsxs("div", { class: "prop-attr", tabIndex: 0, children: [watch_el, emphasize(attr, pattern), prop.internal ? _jsx("span", { class: "tag", children: "internal" }) : null] }), _jsx("div", { class: "prop-conns", children: listeners != 0 ? _jsx("span", { class: "tag", children: `${listeners}` }) : null }), _jsx("div", { class: "prop-kind", children: kind_printer.to_html(prop.kind) }), _jsx("div", { class: cls("prop-value", editing ? "editing" : null), children: !editing ?
_jsxs(_Fragment, { children: [_jsx(PropValue, { prop: prop }), _jsx("div", { class: "btn btn-edit", onClick: () => this.toggle_edit() })] })
:
_jsx(PropEditor, { prop: prop, close: () => this.toggle_edit() }) })] }));
}
}
class BaseItem extends Component {
static __name__ = "BaseItem";
collapsed = signal(false);
render() {
const toggle = () => {
this.collapsed.value = !this.collapsed.value;
};
const { base, props } = this.props;
const items = props.map((prop) => _jsx(PropItem, { prop: prop }));
if (group_props.value) {
return (_jsxs("div", { class: cls("branch", this.collapsed.value ? "collapsed" : null), children: [_jsxs("div", { class: "base", onClick: toggle, children: [_jsx("span", { class: "expander" }), "inherited from ", _jsx("span", { class: "monospace", children: base.__qualified__ })] }), items] }));
}
else {
return _jsx(_Fragment, { children: items });
}
}
}
class PropsList extends Component {
static __name__ = "PropsList";
model_props = computed(() => {
const model = current_model.value;
return model != null ? compute_attrs(model) : [];
});
props_list = computed(() => {
const text = props_filter.value;
return this.model_props.value.map(([base, props]) => [base, props.filter((prop) => prop.attr.includes(text))]);
});
render() {
return (_jsx("div", { class: "props-list", children: this.props_list.value.map(([base, props]) => _jsx(BaseItem, { base: base, props: props })) }));
}
}
class PropsPanel extends Component {
static __name__ = "PropsPanel";
render() {
return (_jsxs("div", { class: "props-panel", children: [_jsx(PropsToolbar, {}), _jsx(PropsList, {})] }));
}
}
class WatchesToolbar extends Component {
static __name__ = "WatchesToolbar";
render() {
const key_up = (event) => {
if (event.currentTarget instanceof HTMLInputElement) {
watches_filter.value = event.currentTarget.value;
}
};
return (_jsx("div", { class: "toolbar", children: _jsx("input", { class: "filter", type: "text", placeholder: "Filter", onKeyUp: key_up }) }));
}
}
class WatchesList extends Component {
static __name__ = "WatchesList";
remove_watch(prop) {
const watched = watched_props.value;
watched.delete(prop);
watched_props.value = new Set(watched);
}
render() {
const props = [...watched_props.value];
const entries = (() => {
if (props.length == 0) {
return _jsx("div", { class: "nothing", children: "No watched properties" });
}
else {
return props.map((prop) => (_jsxs("div", { class: cls("prop", prop.dirty ? "dirty" : null), children: [_jsx("div", { children: to_html(prop) }), _jsx("div", { children: _jsx(PropValue, { prop: prop }) }), _jsx("div", { class: "btn btn-delete", onClick: () => this.remove_watch(prop) })] })));
}
})();
return _jsx("div", { class: "watches-list", children: entries });
}
}
class WatchesPanel extends Component {
static __name__ = "WatchesPanel";
render() {
return (_jsxs("div", { class: "watches-panel", children: [_jsx(WatchesToolbar, {}), _jsx(WatchesList, {})] }));
}
}
class ExaminerPanel extends Component {
static __name__ = "ExaminerPanel";
render() {
return (_jsxs("div", { class: "examiner", children: [_jsx(ModelsList, { models: [...models] }), _jsxs("div", { class: "col", style: { width: "100%" }, children: [_jsx(WatchesPanel, {}), _jsx(PropsPanel, {})] })] }));
}
}
const { target } = this.model;
const models = (() => {
if (target != null) {
return target.references();
}
else {
return this.model.document?.all_models ?? new Set();
}
})();
const current_model = signal([...models][0]);
const examiner = _jsx(ExaminerPanel, {});
render(examiner, this.shadow_el);
}
}
export class Examiner extends UIElement {
static __name__ = "Examiner";
constructor(attrs) {
super(attrs);
}
static {
this.prototype.default_view = ExaminerView;
this.define(({ Ref, Nullable }) => ({
target: [Nullable(Ref(HasProps)), null],
}));
}
}
//# sourceMappingURL=examiner.js.map