From b6802cf6fe752e779b9738891b6fcbb80c9e2af1 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 2 Aug 2024 10:36:41 -0700 Subject: [PATCH 01/14] replaced unicode with svg --- panel/models/player.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panel/models/player.ts b/panel/models/player.ts index 94eb0435c6..28bc66e473 100644 --- a/panel/models/player.ts +++ b/panel/models/player.ts @@ -462,6 +462,6 @@ export class Player extends Widget { show_loop_controls: [Bool, true], })) - this.override({width: 400}) + this.override({ width: 400 }) } } From 5d54c9b2f6d37da71eec847f3a017c96119a6936 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 2 Aug 2024 10:39:49 -0700 Subject: [PATCH 02/14] added display val for player --- Untitled.ipynb | 135 ++++++++++++++++++++++++++++++++++++++++ panel/models/player.ts | 43 ++++++++++++- panel/models/widgets.py | 4 ++ panel/widgets/player.py | 13 +++- 4 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 Untitled.ipynb diff --git a/Untitled.ipynb b/Untitled.ipynb new file mode 100644 index 0000000000..9b4497f35f --- /dev/null +++ b/Untitled.ipynb @@ -0,0 +1,135 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "df6b9324-e7f2-41f8-8e14-d4730bc3fd9c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import panel as pn\n", + "import time\n", + "pn.extension()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e91a2bf8-db74-49ea-8aa2-fb0c5415df49", + "metadata": {}, + "outputs": [], + "source": [ + "slider = pn.widgets.DiscretePlayer(name='Discrete Player', options=[2, 4, 8, 16, 32, 64, 128], value=8, loop_policy='loop',value_location='top_center')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "05661e37-7777-4402-826d-2ff8be03a04f", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "slider.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b91d1c1-2263-4fba-b146-fac33c6808fe", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "slider.name = 'yeet'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3ba2abbf-0866-414a-923e-722a4ae802c0", + "metadata": {}, + "outputs": [], + "source": [ + "slider.value_location = 'top_right'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50428084-2f1e-41ea-a19c-10e5e1320fb8", + "metadata": {}, + "outputs": [], + "source": [ + "slider.value_location" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51873c8d-ed14-46c4-8352-2fb5bfcdcc9b", + "metadata": {}, + "outputs": [], + "source": [ + "slider.value_location = 'top_left'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21a7129f-2b72-4c29-9341-568e430b014c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "slider.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "188c063d-67be-4759-8ead-3bfed3ecf5a8", + "metadata": {}, + "outputs": [], + "source": [ + "slider.value_location = 'top_center'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45d127ad-133b-4a69-ac83-6a2d3caf4212", + "metadata": {}, + "outputs": [], + "source": [ + "slider" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d594a4a3-3dcd-472f-bd16-445c09a982f7", + "metadata": {}, + "outputs": [], + "source": [ + "while True:\n", + " for loc in ('top_left', 'top_center', 'top_right'):\n", + " slider.value_location = loc\n", + " time.sleep(1)" + ] + } + ], + "metadata": { + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/panel/models/player.ts b/panel/models/player.ts index 28bc66e473..24506f5d6d 100644 --- a/panel/models/player.ts +++ b/panel/models/player.ts @@ -68,6 +68,7 @@ export class PlayerView extends WidgetView { declare model: Player protected buttonEl: HTMLDivElement + protected display_value: HTMLLabelElement protected groupEl: HTMLDivElement protected sliderEl: HTMLInputElement protected loop_state: HTMLFormElement @@ -80,7 +81,9 @@ export class PlayerView extends WidgetView { override connect_signals(): void { super.connect_signals() - const {direction, value, loop_policy, disabled, show_loop_controls} = this.model.properties + const {name, value_location, direction, value, loop_policy, disabled, show_loop_controls} = this.model.properties + this.on_change(name, () => this.set_display_value()) + this.on_change(value_location, () => this.set_value_location()) this.on_change(direction, () => this.set_direction()) this.on_change(value, () => this.render()) this.on_change(loop_policy, () => this.set_loop_state(this.model.loop_policy)) @@ -127,7 +130,12 @@ export class PlayerView extends WidgetView { this.groupEl = div() this.groupEl.style.display = "flex" this.groupEl.style.flexDirection = "column" - this.groupEl.style.alignItems = "center" + + // Display Value + this.display_value = document.createElement('label'); + this.display_value.innerHTML = String(this.model.value) + this.display_value.style.cssText = "padding: 0 5px 0 5px; user-select:none;" + this.set_value_location() // Slider this.sliderEl = document.createElement("input") @@ -263,6 +271,7 @@ export class PlayerView extends WidgetView { this.loop_state.appendChild(reflect) this.loop_state.appendChild(reflect_label) + this.groupEl.appendChild(this.display_value) this.groupEl.appendChild(this.sliderEl) this.groupEl.appendChild(button_div) if (this.model.show_loop_controls) { @@ -275,6 +284,7 @@ export class PlayerView extends WidgetView { set_frame(frame: number, throttled: boolean = true): void { this.model.value = frame + this.set_display_value() if (throttled) { this.model.value_throttled = frame } @@ -294,6 +304,30 @@ export class PlayerView extends WidgetView { return "once" } + set_display_value(): void { + console.log('set_display_value'); + let name = String(this.model.name) + let val = String(this.model.value) + if (name != "") + this.display_value.innerHTML = "${name}:${val}" + else + this.display_value.innerHTML = val + } + + set_value_location(): void { + switch (this.model.value_location){ + case 'top_left': + this.display_value.style.textAlign = "left"; + break; + case 'top_center': + this.display_value.style.textAlign = "center"; + break; + case 'top_right': + this.display_value.style.textAlign = "right"; + break; + } + } + set_loop_state(state: string): void { const button_group = this.loop_state.state for (let i = 0; i < button_group.length; i++) { @@ -423,6 +457,7 @@ export const LoopPolicy = Enum("once", "loop", "reflect") export namespace Player { export type Attrs = p.AttrsOf export type Props = Widget.Props & { + //name: p.Property direction: p.Property interval: p.Property start: p.Property @@ -430,6 +465,7 @@ export namespace Player { step: p.Property loop_policy: p.Property value: p.Property + value_location: p.Property value_throttled: p.Property show_loop_controls: p.Property } @@ -450,7 +486,7 @@ export class Player extends Widget { static { this.prototype.default_view = PlayerView - this.define(({Bool, Int}) => ({ + this.define(({Bool, Int, Str}) => ({ direction: [Int, 0], interval: [Int, 500], start: [Int, 0], @@ -458,6 +494,7 @@ export class Player extends Widget { step: [Int, 1], loop_policy: [LoopPolicy, "once"], value: [Int, 0], + value_location: [Str, "top_center"], value_throttled: [Int, 0], show_loop_controls: [Bool, true], })) diff --git a/panel/models/widgets.py b/panel/models/widgets.py index d35d9c298f..b0d4be8370 100644 --- a/panel/models/widgets.py +++ b/panel/models/widgets.py @@ -31,6 +31,7 @@ class Player(Widget): """ The Player widget provides controls to play through a number of frames. """ + #name = String("", help="""Name to display above the widget""") start = Int(0, help="Lower bound of the Player slider") @@ -40,6 +41,9 @@ class Player(Widget): value_throttled = Int(0, help="Current throttled value of the player app") + value_location = String("top_center", help="""Location to display + the value of the slider ("top_left" "top_center", "top_right")""") + step = Int(1, help="Number of steps to advance the player by.") interval = Int(500, help="Interval between updates") diff --git a/panel/widgets/player.py b/panel/widgets/player.py index 817ead241e..35f1a41179 100644 --- a/panel/widgets/player.py +++ b/panel/widgets/player.py @@ -19,6 +19,10 @@ class PlayerBase(Widget): + + #value_location = param.String(default="", allow_None=True, doc=""" + # Name to display above the widget""") + direction = param.Integer(default=0, doc=""" Current play direction of the Player (-1: playing in reverse, 0: paused, 1: playing)""") @@ -39,6 +43,10 @@ class PlayerBase(Widget): height = param.Integer(default=80) + value_location = param.String(default="top_center", allow_None=True, doc=""" + Location to display the value of the slider + ("top_left" "top_center", "top_right")""") + width = param.Integer(default=510, allow_None=True, doc=""" Width of this component. If sizing_mode is set to stretch or scale mode this will merely be used as a suggestion.""") @@ -76,7 +84,7 @@ class Player(PlayerBase): :Example: - >>> Player(name='Player', start=0, end=100, value=32, loop_policy='loop') + >>> Player(name='Player', start=0, end=100, value=32, loop_policy='loop', value_location = 'top_center') """ start = param.Integer(default=0, doc="Lower bound on the slider value") @@ -130,7 +138,8 @@ class DiscretePlayer(PlayerBase, SelectBase): >>> DiscretePlayer( ... name='Discrete Player', ... options=[2, 4, 8, 16, 32, 64, 128], value=32, - ... loop_policy='loop' + ... loop_policy='loop', + ... value_location = 'top_center' ... ) """ From dbbc5337918e64737f4ee6d7dbbb5e31dc4009ac Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 2 Aug 2024 10:41:52 -0700 Subject: [PATCH 03/14] refactored and added bold value --- Untitled.ipynb | 24 +++++++++++++---- panel/dist/css/player.css | 3 +++ panel/models/player.ts | 55 +++++++++++++++++++++++++-------------- panel/models/widgets.py | 7 ++++- panel/widgets/player.py | 14 +++++----- 5 files changed, 72 insertions(+), 31 deletions(-) create mode 100644 panel/dist/css/player.css diff --git a/Untitled.ipynb b/Untitled.ipynb index 9b4497f35f..e9426d2861 100644 --- a/Untitled.ipynb +++ b/Untitled.ipynb @@ -18,10 +18,12 @@ "cell_type": "code", "execution_count": null, "id": "e91a2bf8-db74-49ea-8aa2-fb0c5415df49", - "metadata": {}, + "metadata": { + "tags": [] + }, "outputs": [], "source": [ - "slider = pn.widgets.DiscretePlayer(name='Discrete Player', options=[2, 4, 8, 16, 32, 64, 128], value=8, loop_policy='loop',value_location='top_center')" + "slider = pn.widgets.DiscretePlayer(name='test', options=[2, 4, 8, 16, 32, 64, 128], value=8, loop_policy='loop',value_location='top_center')" ] }, { @@ -33,19 +35,31 @@ }, "outputs": [], "source": [ - "slider.show()" + "slider" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7cf0c913-a5e5-4ae8-b497-79da3a0c2dbe", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "pn.widgets.IntSlider(name='ben')" ] }, { "cell_type": "code", "execution_count": null, - "id": "2b91d1c1-2263-4fba-b146-fac33c6808fe", + "id": "a7ddfacf-decd-47fd-9f18-30b69d72dced", "metadata": { "tags": [] }, "outputs": [], "source": [ - "slider.name = 'yeet'" + "slider.name = \"name\"" ] }, { diff --git a/panel/dist/css/player.css b/panel/dist/css/player.css new file mode 100644 index 0000000000..1b0bb61ad5 --- /dev/null +++ b/panel/dist/css/player.css @@ -0,0 +1,3 @@ +.pn-player-value { + font-weight: bold; +} diff --git a/panel/models/player.ts b/panel/models/player.ts index 24506f5d6d..32420e3994 100644 --- a/panel/models/player.ts +++ b/panel/models/player.ts @@ -2,6 +2,7 @@ import {Enum} from "@bokehjs/core/kinds" import type * as p from "@bokehjs/core/properties" import {div} from "@bokehjs/core/dom" import {Widget, WidgetView} from "@bokehjs/models/widgets/widget" +import {to_string} from "@bokehjs/core/util/pretty" const SVG_STRINGS = { slower: ' this.set_display_value()) + this.on_change(name, () => this.update_title()) this.on_change(value_location, () => this.set_value_location()) this.on_change(direction, () => this.set_direction()) this.on_change(value, () => this.render()) @@ -132,9 +133,9 @@ export class PlayerView extends WidgetView { this.groupEl.style.flexDirection = "column" // Display Value - this.display_value = document.createElement('label'); - this.display_value.innerHTML = String(this.model.value) - this.display_value.style.cssText = "padding: 0 5px 0 5px; user-select:none;" + this.titleEl = document.createElement('label'); + this.update_title() + this.titleEl.style.cssText = "padding: 0 5px 0 5px; user-select:none;" this.set_value_location() // Slider @@ -271,7 +272,7 @@ export class PlayerView extends WidgetView { this.loop_state.appendChild(reflect) this.loop_state.appendChild(reflect_label) - this.groupEl.appendChild(this.display_value) + this.groupEl.appendChild(this.titleEl) this.groupEl.appendChild(this.sliderEl) this.groupEl.appendChild(button_div) if (this.model.show_loop_controls) { @@ -304,26 +305,39 @@ export class PlayerView extends WidgetView { return "once" } - set_display_value(): void { - console.log('set_display_value'); - let name = String(this.model.name) - let val = String(this.model.value) - if (name != "") - this.display_value.innerHTML = "${name}:${val}" - else - this.display_value.innerHTML = val + update_title(): void { + empty(this.titleEl) + + const hide_header = this.model.title == null || (this.model.title.length == 0 && !this.model.show_value) + this.titleEl.style.display = hide_header ? "none" : "" + + if (!hide_header) { + const {title} = this.model + if (title != null && title.length > 0) { + if (this.contains_tex_string(title)) { + this.titleEl.innerHTML = `${this.process_tex(title)}: ` + } else { + this.titleEl.textContent = `${title}: ` + } + } + + if (this.model.show_value) { + + this.titleEl.appendChild(span({class: 'pn-player-value'}, to_string(this.model.value))) + } + } } set_value_location(): void { switch (this.model.value_location){ case 'top_left': - this.display_value.style.textAlign = "left"; + this.titleEl.style.textAlign = "left"; break; case 'top_center': - this.display_value.style.textAlign = "center"; + this.titleEl.style.textAlign = "center"; break; case 'top_right': - this.display_value.style.textAlign = "right"; + this.titleEl.style.textAlign = "right"; break; } } @@ -457,17 +471,18 @@ export const LoopPolicy = Enum("once", "loop", "reflect") export namespace Player { export type Attrs = p.AttrsOf export type Props = Widget.Props & { - //name: p.Property direction: p.Property interval: p.Property start: p.Property end: p.Property step: p.Property loop_policy: p.Property + title: p.Property value: p.Property value_location: p.Property value_throttled: p.Property show_loop_controls: p.Property + show_value: p.Property } } @@ -486,17 +501,19 @@ export class Player extends Widget { static { this.prototype.default_view = PlayerView - this.define(({Bool, Int, Str}) => ({ + this.define(({ Bool, Int, Str }) => ({ direction: [Int, 0], interval: [Int, 500], start: [Int, 0], end: [Int, 10], step: [Int, 1], loop_policy: [LoopPolicy, "once"], + title: [Str,""], value: [Int, 0], value_location: [Str, "top_center"], value_throttled: [Int, 0], show_loop_controls: [Bool, true], + show_value: [Bool, true] })) this.override({ width: 400 }) diff --git a/panel/models/widgets.py b/panel/models/widgets.py index b0d4be8370..1101f29158 100644 --- a/panel/models/widgets.py +++ b/panel/models/widgets.py @@ -31,7 +31,9 @@ class Player(Widget): """ The Player widget provides controls to play through a number of frames. """ - #name = String("", help="""Name to display above the widget""") + title = Nullable(String, default="", help=""" + The slider's label (supports :ref:`math text `). + """) start = Int(0, help="Lower bound of the Player slider") @@ -57,6 +59,9 @@ class Player(Widget): show_loop_controls = Bool(True, help="""Whether the loop controls radio buttons are shown""") + show_value = Bool(True, help=""" + Whether to show the widget value""") + width = Override(default=400) height = Override(default=250) diff --git a/panel/widgets/player.py b/panel/widgets/player.py index 35f1a41179..6a074b439f 100644 --- a/panel/widgets/player.py +++ b/panel/widgets/player.py @@ -8,6 +8,7 @@ import param from ..config import config +from ..io.resources import CDN_DIST from ..models.widgets import Player as _BkPlayer from ..util import indexOf, isIn from .base import Widget @@ -19,10 +20,6 @@ class PlayerBase(Widget): - - #value_location = param.String(default="", allow_None=True, doc=""" - # Name to display above the widget""") - direction = param.Integer(default=0, doc=""" Current play direction of the Player (-1: playing in reverse, 0: paused, 1: playing)""") @@ -38,6 +35,9 @@ class PlayerBase(Widget): show_loop_controls = param.Boolean(default=True, doc=""" Whether the loop controls radio buttons are shown""") + show_value = param.Boolean(default=True, doc=""" + Whether to show the widget value""") + step = param.Integer(default=1, doc=""" Number of frames to step forward and back by on each event.""") @@ -51,10 +51,12 @@ class PlayerBase(Widget): Width of this component. If sizing_mode is set to stretch or scale mode this will merely be used as a suggestion.""") - _rename: ClassVar[Mapping[str, str | None]] = {'name': None} + _rename: ClassVar[Mapping[str, str | None]] = {'name': 'title'} _widget_type: ClassVar[type[Model]] = _BkPlayer + _stylesheets: ClassVar[list[str]] = [f"{CDN_DIST}css/player.css"] + __abstract = True def __init__(self, **params): @@ -149,7 +151,7 @@ class DiscretePlayer(PlayerBase, SelectBase): value_throttled = param.Parameter(constant=True, doc="Current player value") - _rename: ClassVar[Mapping[str, str | None]] = {'name': None, 'options': None} + _rename: ClassVar[Mapping[str, str | None]] = {'name': 'title', 'options': None} _source_transforms: ClassVar[Mapping[str, str | None]] = {'value': None, 'value_throttled': None} From ee36a06fd989637e60d7f9bbe05d8842ec3f2031 Mon Sep 17 00:00:00 2001 From: Benjamin Barnett Date: Mon, 18 Dec 2023 16:14:22 -0700 Subject: [PATCH 04/14] added UI tests --- Untitled.ipynb | 149 -------------------------- panel/models/player.ts | 6 ++ panel/tests/ui/widgets/test_player.py | 54 ++++++++++ panel/widgets/player.py | 7 +- 4 files changed, 64 insertions(+), 152 deletions(-) delete mode 100644 Untitled.ipynb create mode 100644 panel/tests/ui/widgets/test_player.py diff --git a/Untitled.ipynb b/Untitled.ipynb deleted file mode 100644 index e9426d2861..0000000000 --- a/Untitled.ipynb +++ /dev/null @@ -1,149 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "df6b9324-e7f2-41f8-8e14-d4730bc3fd9c", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "import panel as pn\n", - "import time\n", - "pn.extension()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e91a2bf8-db74-49ea-8aa2-fb0c5415df49", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "slider = pn.widgets.DiscretePlayer(name='test', options=[2, 4, 8, 16, 32, 64, 128], value=8, loop_policy='loop',value_location='top_center')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "05661e37-7777-4402-826d-2ff8be03a04f", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "slider" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7cf0c913-a5e5-4ae8-b497-79da3a0c2dbe", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "pn.widgets.IntSlider(name='ben')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a7ddfacf-decd-47fd-9f18-30b69d72dced", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "slider.name = \"name\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3ba2abbf-0866-414a-923e-722a4ae802c0", - "metadata": {}, - "outputs": [], - "source": [ - "slider.value_location = 'top_right'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "50428084-2f1e-41ea-a19c-10e5e1320fb8", - "metadata": {}, - "outputs": [], - "source": [ - "slider.value_location" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "51873c8d-ed14-46c4-8352-2fb5bfcdcc9b", - "metadata": {}, - "outputs": [], - "source": [ - "slider.value_location = 'top_left'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "21a7129f-2b72-4c29-9341-568e430b014c", - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "slider.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "188c063d-67be-4759-8ead-3bfed3ecf5a8", - "metadata": {}, - "outputs": [], - "source": [ - "slider.value_location = 'top_center'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "45d127ad-133b-4a69-ac83-6a2d3caf4212", - "metadata": {}, - "outputs": [], - "source": [ - "slider" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d594a4a3-3dcd-472f-bd16-445c09a982f7", - "metadata": {}, - "outputs": [], - "source": [ - "while True:\n", - " for loc in ('top_left', 'top_center', 'top_right'):\n", - " slider.value_location = loc\n", - " time.sleep(1)" - ] - } - ], - "metadata": { - "language_info": { - "name": "python", - "pygments_lexer": "ipython3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/panel/models/player.ts b/panel/models/player.ts index 32420e3994..d0d9159cde 100644 --- a/panel/models/player.ts +++ b/panel/models/player.ts @@ -96,6 +96,8 @@ export class PlayerView extends WidgetView { this.groupEl.removeChild(this.loop_state) } }) + this.connect(this.model.properties.show_value.change, () => this.update_title()) + } toggle_disable() { @@ -312,6 +314,7 @@ export class PlayerView extends WidgetView { this.titleEl.style.display = hide_header ? "none" : "" if (!hide_header) { + this.titleEl.style.visibility = 'visible'; const {title} = this.model if (title != null && title.length > 0) { if (this.contains_tex_string(title)) { @@ -326,6 +329,9 @@ export class PlayerView extends WidgetView { this.titleEl.appendChild(span({class: 'pn-player-value'}, to_string(this.model.value))) } } + else{ + this.titleEl.style.visibility = 'hidden'; + } } set_value_location(): void { diff --git a/panel/tests/ui/widgets/test_player.py b/panel/tests/ui/widgets/test_player.py new file mode 100644 index 0000000000..9137b1e2d8 --- /dev/null +++ b/panel/tests/ui/widgets/test_player.py @@ -0,0 +1,54 @@ +import pytest + +pytest.importorskip("playwright") + +from playwright.sync_api import expect + +from panel.tests.util import serve_component +from panel.widgets import Player + +pytestmark = pytest.mark.ui + + +def test_init(page): + player = Player() + serve_component(page, player) + + assert not page.is_visible('label') + assert page.query_selector('.pn-player-value') is None + +def test_show_value(page): + player = Player(show_value=True) + serve_component(page, player) + + assert page.is_visible('label') + assert page.query_selector('.pn-player-value') is not None + + +def test_name(page): + player = Player(name='test') + serve_component(page, player) + + assert page.is_visible('label') + assert page.query_selector('.pn-player-value') is None + + name = page.locator('label:has-text("test")') + expect(name).to_have_count(1) + + +def test_value_location(page): + player = Player(name='test', value_location='top_right') + serve_component(page, player) + + name = page.locator('label:has-text("test")') + expect(name).to_have_css("text-align", "right") + +def test_name_and_show_value(page): + player = Player(name='test', show_value=True) + serve_component(page, player) + + assert page.is_visible('label') + assert page.query_selector('.pn-player-value') is not None + + name = page.locator('label:has-text("test")') + expect(name).to_have_count(1) diff --git a/panel/widgets/player.py b/panel/widgets/player.py index 6a074b439f..12a02c9330 100644 --- a/panel/widgets/player.py +++ b/panel/widgets/player.py @@ -35,7 +35,7 @@ class PlayerBase(Widget): show_loop_controls = param.Boolean(default=True, doc=""" Whether the loop controls radio buttons are shown""") - show_value = param.Boolean(default=True, doc=""" + show_value = param.Boolean(default=False, doc=""" Whether to show the widget value""") step = param.Integer(default=1, doc=""" @@ -43,9 +43,10 @@ class PlayerBase(Widget): height = param.Integer(default=80) - value_location = param.String(default="top_center", allow_None=True, doc=""" + value_location = param.ObjectSelector( + objects=["top_left", "top_center", "top_right"], doc=""" Location to display the value of the slider - ("top_left" "top_center", "top_right")""") + ("top_left", "top_center", "top_right")""") width = param.Integer(default=510, allow_None=True, doc=""" Width of this component. If sizing_mode is set to stretch From 1de8d0abec002bcdb0074d4b737ef260d9b643fe Mon Sep 17 00:00:00 2001 From: Benjamin Barnett Date: Tue, 19 Dec 2023 09:34:00 -0700 Subject: [PATCH 05/14] updated examples --- examples/reference/widgets/DiscretePlayer.ipynb | 5 ++++- examples/reference/widgets/Player.ipynb | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/reference/widgets/DiscretePlayer.ipynb b/examples/reference/widgets/DiscretePlayer.ipynb index 9323014579..e2559ab4ff 100644 --- a/examples/reference/widgets/DiscretePlayer.ipynb +++ b/examples/reference/widgets/DiscretePlayer.ipynb @@ -38,6 +38,8 @@ "* **``disabled``** (boolean): Whether the widget is editable\n", "* **``name``** (str): The title of the widget\n", "* **``show_loop_controls``** (boolean): Whether radio buttons allowing to switch between loop policies options are shown\n", + "* **``show_value``** (boolean): Whether to display the value of the player\n", + "* **``value_location``** (str): Where to display the value; must be one of 'top_left', 'top_center', 'top_right'\n", "\n", "___" ] @@ -55,7 +57,8 @@ "metadata": {}, "outputs": [], "source": [ - "discrete_player = pn.widgets.DiscretePlayer(name='Discrete Player', options=[2, 4, 8, 16, 32, 64, 128], value=8, loop_policy='loop')\n", + "discrete_player = pn.widgets.DiscretePlayer(name='Discrete Player', options=[2, 4, 8, 16, 32, 64, 128],\n", + " value=8, loop_policy='loop', show_value=True, value_location='top_left')\n", "\n", "discrete_player" ] diff --git a/examples/reference/widgets/Player.ipynb b/examples/reference/widgets/Player.ipynb index 238b852a96..6245d20a9f 100644 --- a/examples/reference/widgets/Player.ipynb +++ b/examples/reference/widgets/Player.ipynb @@ -40,6 +40,8 @@ "* **``disabled``** (boolean): Whether the widget is editable\n", "* **``name``** (str): The title of the widget\n", "* **``show_loop_controls``** (boolean): Whether radio buttons allowing to switch between loop policies options are shown\n", + "* **``show_value``** (boolean): Whether to display the value of the player\n", + "* **``value_location``** (str): Where to display the value; must be one of 'top_left', 'top_center', 'top_right'\n", "\n", "___" ] @@ -57,7 +59,8 @@ "metadata": {}, "outputs": [], "source": [ - "player = pn.widgets.Player(name='Player', start=0, end=100, value=32, loop_policy='loop')\n", + "player = pn.widgets.Player(name='Player', start=0, end=100, value=32, loop_policy='loop',\n", + " show_value=True, value_location='top_center')\n", "\n", "player" ] From 9702846519e21330905faf8003a9c6d156d6bb06 Mon Sep 17 00:00:00 2001 From: benbarn313 <55669975+benbarn313@users.noreply.github.com> Date: Tue, 19 Dec 2023 07:46:48 -0700 Subject: [PATCH 06/14] Apply suggestions from code review formatting changes Co-authored-by: Andrew <15331990+ahuang11@users.noreply.github.com> --- panel/models/widgets.py | 2 +- panel/widgets/player.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/panel/models/widgets.py b/panel/models/widgets.py index 1101f29158..68c0fc5cac 100644 --- a/panel/models/widgets.py +++ b/panel/models/widgets.py @@ -43,7 +43,7 @@ class Player(Widget): value_throttled = Int(0, help="Current throttled value of the player app") - value_location = String("top_center", help="""Location to display + value_location = String("top_left", help="""Location to display the value of the slider ("top_left" "top_center", "top_right")""") step = Int(1, help="Number of steps to advance the player by.") diff --git a/panel/widgets/player.py b/panel/widgets/player.py index 12a02c9330..52ce028487 100644 --- a/panel/widgets/player.py +++ b/panel/widgets/player.py @@ -87,7 +87,7 @@ class Player(PlayerBase): :Example: - >>> Player(name='Player', start=0, end=100, value=32, loop_policy='loop', value_location = 'top_center') + >>> Player(name='Player', start=0, end=100, value=32, loop_policy='loop', value_location='top_center') """ start = param.Integer(default=0, doc="Lower bound on the slider value") @@ -142,7 +142,7 @@ class DiscretePlayer(PlayerBase, SelectBase): ... name='Discrete Player', ... options=[2, 4, 8, 16, 32, 64, 128], value=32, ... loop_policy='loop', - ... value_location = 'top_center' + ... value_location='top_left' ... ) """ From 26734fc85150a58b074ceca2895d984b93487fb8 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 2 Aug 2024 10:42:37 -0700 Subject: [PATCH 07/14] Apply suggestions from code review --- panel/models/player.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/panel/models/player.ts b/panel/models/player.ts index d0d9159cde..8603c942d8 100644 --- a/panel/models/player.ts +++ b/panel/models/player.ts @@ -82,8 +82,8 @@ export class PlayerView extends WidgetView { override connect_signals(): void { super.connect_signals() - const {name, value_location, direction, value, loop_policy, disabled, show_loop_controls} = this.model.properties - this.on_change(name, () => this.update_title()) + const {title, value_location, direction, value, loop_policy, disabled, show_loop_controls} = this.model.properties + this.on_change(title, () => this.update_title()) this.on_change(value_location, () => this.set_value_location()) this.on_change(direction, () => this.set_direction()) this.on_change(value, () => this.render()) From ac7e7afb92cba30be88b3b56c1a75a38df47d517 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 2 Aug 2024 10:55:25 -0700 Subject: [PATCH 08/14] pre-commit --- panel/models/player.ts | 47 +++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/panel/models/player.ts b/panel/models/player.ts index 8603c942d8..214548918f 100644 --- a/panel/models/player.ts +++ b/panel/models/player.ts @@ -1,8 +1,8 @@ -import {Enum} from "@bokehjs/core/kinds" +import { Enum } from "@bokehjs/core/kinds" import type * as p from "@bokehjs/core/properties" -import {div} from "@bokehjs/core/dom" -import {Widget, WidgetView} from "@bokehjs/models/widgets/widget" -import {to_string} from "@bokehjs/core/util/pretty" +import { div, span } from "@bokehjs/core/dom" +import { Widget, WidgetView } from "@bokehjs/models/widgets/widget" +import { to_string } from "@bokehjs/core/util/pretty" const SVG_STRINGS = { slower: ' this.update_title()) this.on_change(value_location, () => this.set_value_location()) this.on_change(direction, () => this.set_direction()) @@ -135,7 +135,7 @@ export class PlayerView extends WidgetView { this.groupEl.style.flexDirection = "column" // Display Value - this.titleEl = document.createElement('label'); + this.titleEl = document.createElement("label"); this.update_title() this.titleEl.style.cssText = "padding: 0 5px 0 5px; user-select:none;" this.set_value_location() @@ -314,8 +314,8 @@ export class PlayerView extends WidgetView { this.titleEl.style.display = hide_header ? "none" : "" if (!hide_header) { - this.titleEl.style.visibility = 'visible'; - const {title} = this.model + this.titleEl.style.visibility = "visible"; + const { title } = this.model if (title != null && title.length > 0) { if (this.contains_tex_string(title)) { this.titleEl.innerHTML = `${this.process_tex(title)}: ` @@ -325,26 +325,25 @@ export class PlayerView extends WidgetView { } if (this.model.show_value) { - - this.titleEl.appendChild(span({class: 'pn-player-value'}, to_string(this.model.value))) + this.titleEl.appendChild(span({ class: "pn-player-value" }, to_string(this.model.value))) } } - else{ - this.titleEl.style.visibility = 'hidden'; + else { + this.titleEl.style.visibility = "hidden" } } set_value_location(): void { - switch (this.model.value_location){ - case 'top_left': - this.titleEl.style.textAlign = "left"; - break; - case 'top_center': - this.titleEl.style.textAlign = "center"; - break; - case 'top_right': - this.titleEl.style.textAlign = "right"; - break; + switch (this.model.value_location) { + case "top_left": + this.titleEl.style.textAlign = "left" + break + case "top_center": + this.titleEl.style.textAlign = "center" + break + case "top_right": + this.titleEl.style.textAlign = "right" + break } } @@ -485,7 +484,7 @@ export namespace Player { loop_policy: p.Property title: p.Property value: p.Property - value_location: p.Property + value_location: p.Property value_throttled: p.Property show_loop_controls: p.Property show_value: p.Property @@ -514,7 +513,7 @@ export class Player extends Widget { end: [Int, 10], step: [Int, 1], loop_policy: [LoopPolicy, "once"], - title: [Str,""], + title: [Str, ""], value: [Int, 0], value_location: [Str, "top_center"], value_throttled: [Int, 0], From 8d8fac7cbab0093d347ffb5e5c42938e4d0a8397 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 2 Aug 2024 11:17:14 -0700 Subject: [PATCH 09/14] change label to div --- panel/models/player.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/panel/models/player.ts b/panel/models/player.ts index 214548918f..c3570eec4c 100644 --- a/panel/models/player.ts +++ b/panel/models/player.ts @@ -1,6 +1,6 @@ import { Enum } from "@bokehjs/core/kinds" import type * as p from "@bokehjs/core/properties" -import { div, span } from "@bokehjs/core/dom" +import { div, empty, span } from "@bokehjs/core/dom" import { Widget, WidgetView } from "@bokehjs/models/widgets/widget" import { to_string } from "@bokehjs/core/util/pretty" @@ -69,7 +69,7 @@ export class PlayerView extends WidgetView { declare model: Player protected buttonEl: HTMLDivElement - protected titleEl: HTMLLabelElement + protected titleEl: HTMLDivElement protected groupEl: HTMLDivElement protected sliderEl: HTMLInputElement protected loop_state: HTMLFormElement @@ -82,8 +82,8 @@ export class PlayerView extends WidgetView { override connect_signals(): void { super.connect_signals() - const { title, value_location, direction, value, loop_policy, disabled, show_loop_controls } = this.model.properties - this.on_change(title, () => this.update_title()) + const { title, value_location, direction, value, loop_policy, disabled, show_loop_controls, show_value } = this.model.properties + this.on_change(title, () => this.update_title_and_value()) this.on_change(value_location, () => this.set_value_location()) this.on_change(direction, () => this.set_direction()) this.on_change(value, () => this.render()) @@ -96,7 +96,7 @@ export class PlayerView extends WidgetView { this.groupEl.removeChild(this.loop_state) } }) - this.connect(this.model.properties.show_value.change, () => this.update_title()) + this.on_change(show_value, () => this.update_title_and_value()) } @@ -135,10 +135,11 @@ export class PlayerView extends WidgetView { this.groupEl.style.flexDirection = "column" // Display Value - this.titleEl = document.createElement("label"); - this.update_title() - this.titleEl.style.cssText = "padding: 0 5px 0 5px; user-select:none;" + this.titleEl = div() + this.titleEl.setAttribute("class", "pn-player-title") + this.update_title_and_value() this.set_value_location() + this.titleEl.style.cssText = "padding: 0 5px 0 5px; user-select:none;" // Slider this.sliderEl = document.createElement("input") @@ -287,7 +288,7 @@ export class PlayerView extends WidgetView { set_frame(frame: number, throttled: boolean = true): void { this.model.value = frame - this.set_display_value() + this.update_title_and_value() if (throttled) { this.model.value_throttled = frame } @@ -307,7 +308,7 @@ export class PlayerView extends WidgetView { return "once" } - update_title(): void { + update_title_and_value(): void { empty(this.titleEl) const hide_header = this.model.title == null || (this.model.title.length == 0 && !this.model.show_value) @@ -515,7 +516,7 @@ export class Player extends Widget { loop_policy: [LoopPolicy, "once"], title: [Str, ""], value: [Int, 0], - value_location: [Str, "top_center"], + value_location: [Str, "top_left"], value_throttled: [Int, 0], show_loop_controls: [Bool, true], show_value: [Bool, true] From 59472f1185bd6482b76084c960398472c560dfe0 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 2 Aug 2024 12:46:12 -0700 Subject: [PATCH 10/14] change value_location to value_align and implement discrete player --- .../reference/widgets/DiscretePlayer.ipynb | 4 +- examples/reference/widgets/Player.ipynb | 4 +- panel/models/discrete_player.ts | 43 +++++++++++++++++++ panel/models/index.ts | 1 + panel/models/player.ts | 26 ++++++----- panel/models/widgets.py | 10 ++++- panel/widgets/player.py | 18 +++++--- 7 files changed, 82 insertions(+), 24 deletions(-) create mode 100644 panel/models/discrete_player.ts diff --git a/examples/reference/widgets/DiscretePlayer.ipynb b/examples/reference/widgets/DiscretePlayer.ipynb index e2559ab4ff..bcc189b78b 100644 --- a/examples/reference/widgets/DiscretePlayer.ipynb +++ b/examples/reference/widgets/DiscretePlayer.ipynb @@ -39,7 +39,7 @@ "* **``name``** (str): The title of the widget\n", "* **``show_loop_controls``** (boolean): Whether radio buttons allowing to switch between loop policies options are shown\n", "* **``show_value``** (boolean): Whether to display the value of the player\n", - "* **``value_location``** (str): Where to display the value; must be one of 'top_left', 'top_center', 'top_right'\n", + "* **``value_align``** (str): Where to display the value; must be one of 'start', 'center', 'end'\n", "\n", "___" ] @@ -58,7 +58,7 @@ "outputs": [], "source": [ "discrete_player = pn.widgets.DiscretePlayer(name='Discrete Player', options=[2, 4, 8, 16, 32, 64, 128],\n", - " value=8, loop_policy='loop', show_value=True, value_location='top_left')\n", + " value=8, loop_policy='loop', show_value=True, value_align='start')\n", "\n", "discrete_player" ] diff --git a/examples/reference/widgets/Player.ipynb b/examples/reference/widgets/Player.ipynb index 6245d20a9f..d210a01038 100644 --- a/examples/reference/widgets/Player.ipynb +++ b/examples/reference/widgets/Player.ipynb @@ -41,7 +41,7 @@ "* **``name``** (str): The title of the widget\n", "* **``show_loop_controls``** (boolean): Whether radio buttons allowing to switch between loop policies options are shown\n", "* **``show_value``** (boolean): Whether to display the value of the player\n", - "* **``value_location``** (str): Where to display the value; must be one of 'top_left', 'top_center', 'top_right'\n", + "* **``value_align``** (str): Where to display the value; must be one of 'start', 'center', 'end'\n", "\n", "___" ] @@ -60,7 +60,7 @@ "outputs": [], "source": [ "player = pn.widgets.Player(name='Player', start=0, end=100, value=32, loop_policy='loop',\n", - " show_value=True, value_location='top_center')\n", + " show_value=True, value_align='start')\n", "\n", "player" ] diff --git a/panel/models/discrete_player.ts b/panel/models/discrete_player.ts new file mode 100644 index 0000000000..a04b102eb1 --- /dev/null +++ b/panel/models/discrete_player.ts @@ -0,0 +1,43 @@ +import type * as p from "@bokehjs/core/properties" +import { PlayerView, Player } from "./player" +import { span } from "@bokehjs/core/dom" +import { to_string } from "@bokehjs/core/util/pretty" + + +export class DiscretePlayerView extends PlayerView { + declare model: DiscretePlayer + + override append_value_to_title_el(): void { + this.titleEl.appendChild(span({ class: "pn-player-value" }, to_string(this.model.options[this.model.value]))) + } +} + +export namespace DiscretePlayer { + export type Attrs = p.AttrsOf + export type Props = Player.Props & { + options: p.Property + } +} + +export interface DiscretePlayer extends DiscretePlayer.Attrs { } + +export class DiscretePlayer extends Player { + + declare properties: DiscretePlayer.Props + + constructor(attrs?: Partial) { + super(attrs) + } + + static override __module__ = "panel.models.widgets" + + static { + this.prototype.default_view = DiscretePlayerView + + this.define(({ List, Any }) => ({ + options: [List(Any), []], + })) + + this.override({ width: 400 }) + } +} diff --git a/panel/models/index.ts b/panel/models/index.ts index f6e656899c..ba9cf6e1fd 100644 --- a/panel/models/index.ts +++ b/panel/models/index.ts @@ -15,6 +15,7 @@ export {CustomMultiSelect} from "./multiselect" export {DataTabulator} from "./tabulator" export {DatetimePicker} from "./datetime_picker" export {DeckGLPlot} from "./deckgl" +export {DiscretePlayer} from "./discrete_player" export {ECharts} from "./echarts" export {Feed} from "./feed" export {FileDownload} from "./file_download" diff --git a/panel/models/player.ts b/panel/models/player.ts index c3570eec4c..f98a786734 100644 --- a/panel/models/player.ts +++ b/panel/models/player.ts @@ -82,9 +82,9 @@ export class PlayerView extends WidgetView { override connect_signals(): void { super.connect_signals() - const { title, value_location, direction, value, loop_policy, disabled, show_loop_controls, show_value } = this.model.properties + const { title, value_align, direction, value, loop_policy, disabled, show_loop_controls, show_value } = this.model.properties this.on_change(title, () => this.update_title_and_value()) - this.on_change(value_location, () => this.set_value_location()) + this.on_change(value_align, () => this.set_value_align()) this.on_change(direction, () => this.set_direction()) this.on_change(value, () => this.render()) this.on_change(loop_policy, () => this.set_loop_state(this.model.loop_policy)) @@ -138,7 +138,7 @@ export class PlayerView extends WidgetView { this.titleEl = div() this.titleEl.setAttribute("class", "pn-player-title") this.update_title_and_value() - this.set_value_location() + this.set_value_align() this.titleEl.style.cssText = "padding: 0 5px 0 5px; user-select:none;" // Slider @@ -326,7 +326,7 @@ export class PlayerView extends WidgetView { } if (this.model.show_value) { - this.titleEl.appendChild(span({ class: "pn-player-value" }, to_string(this.model.value))) + this.append_value_to_title_el() } } else { @@ -334,15 +334,19 @@ export class PlayerView extends WidgetView { } } - set_value_location(): void { - switch (this.model.value_location) { - case "top_left": + append_value_to_title_el(): void { + this.titleEl.appendChild(span({ class: "pn-player-value" }, to_string(this.model.value))) + } + + set_value_align(): void { + switch (this.model.value_align) { + case "start": this.titleEl.style.textAlign = "left" break - case "top_center": + case "center": this.titleEl.style.textAlign = "center" break - case "top_right": + case "end": this.titleEl.style.textAlign = "right" break } @@ -485,7 +489,7 @@ export namespace Player { loop_policy: p.Property title: p.Property value: p.Property - value_location: p.Property + value_align: p.Property value_throttled: p.Property show_loop_controls: p.Property show_value: p.Property @@ -516,7 +520,7 @@ export class Player extends Widget { loop_policy: [LoopPolicy, "once"], title: [Str, ""], value: [Int, 0], - value_location: [Str, "top_left"], + value_align: [Str, "start"], value_throttled: [Int, 0], show_loop_controls: [Bool, true], show_value: [Bool, true] diff --git a/panel/models/widgets.py b/panel/models/widgets.py index 68c0fc5cac..8504fcddda 100644 --- a/panel/models/widgets.py +++ b/panel/models/widgets.py @@ -43,8 +43,8 @@ class Player(Widget): value_throttled = Int(0, help="Current throttled value of the player app") - value_location = String("top_left", help="""Location to display - the value of the slider ("top_left" "top_center", "top_right")""") + value_align = String("start", help="""Location to display + the value of the slider ("start" "center", "end")""") step = Int(1, help="Number of steps to advance the player by.") @@ -67,6 +67,12 @@ class Player(Widget): height = Override(default=250) +class DiscretePlayer(Player): + + options = List(Any, help=""" + List of discrete options.""") + + class SingleSelect(InputWidget): ''' Single-select widget. diff --git a/panel/widgets/player.py b/panel/widgets/player.py index 52ce028487..565d6489da 100644 --- a/panel/widgets/player.py +++ b/panel/widgets/player.py @@ -9,7 +9,9 @@ from ..config import config from ..io.resources import CDN_DIST -from ..models.widgets import Player as _BkPlayer +from ..models.widgets import ( + DiscretePlayer as _BkDiscretePlayer, Player as _BkPlayer, +) from ..util import indexOf, isIn from .base import Widget from .select import SelectBase @@ -43,10 +45,10 @@ class PlayerBase(Widget): height = param.Integer(default=80) - value_location = param.ObjectSelector( - objects=["top_left", "top_center", "top_right"], doc=""" + value_align = param.ObjectSelector( + objects=["start", "center", "end"], doc=""" Location to display the value of the slider - ("top_left", "top_center", "top_right")""") + ("start", "center", "end")""") width = param.Integer(default=510, allow_None=True, doc=""" Width of this component. If sizing_mode is set to stretch @@ -87,7 +89,7 @@ class Player(PlayerBase): :Example: - >>> Player(name='Player', start=0, end=100, value=32, loop_policy='loop', value_location='top_center') + >>> Player(name='Player', start=0, end=100, value=32, loop_policy='loop', value_align='top_center') """ start = param.Integer(default=0, doc="Lower bound on the slider value") @@ -142,7 +144,7 @@ class DiscretePlayer(PlayerBase, SelectBase): ... name='Discrete Player', ... options=[2, 4, 8, 16, 32, 64, 128], value=32, ... loop_policy='loop', - ... value_location='top_left' + ... value_align='start' ... ) """ @@ -152,10 +154,12 @@ class DiscretePlayer(PlayerBase, SelectBase): value_throttled = param.Parameter(constant=True, doc="Current player value") - _rename: ClassVar[Mapping[str, str | None]] = {'name': 'title', 'options': None} + _rename: ClassVar[Mapping[str, str | None]] = {'name': 'title'} _source_transforms: ClassVar[Mapping[str, str | None]] = {'value': None, 'value_throttled': None} + _widget_type: ClassVar[type[Model]] = _BkDiscretePlayer + def _process_param_change(self, msg): values = self.values if 'options' in msg: From b087367b715bdcf48c2fbf1fb4a905ee62e8a3ce Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 2 Aug 2024 12:52:45 -0700 Subject: [PATCH 11/14] lint --- panel/models/discrete_player.ts | 13 ++++++------- panel/models/player.ts | 25 ++++++++++++------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/panel/models/discrete_player.ts b/panel/models/discrete_player.ts index a04b102eb1..35f55bdadd 100644 --- a/panel/models/discrete_player.ts +++ b/panel/models/discrete_player.ts @@ -1,14 +1,13 @@ import type * as p from "@bokehjs/core/properties" -import { PlayerView, Player } from "./player" -import { span } from "@bokehjs/core/dom" -import { to_string } from "@bokehjs/core/util/pretty" - +import {PlayerView, Player} from "./player" +import {span} from "@bokehjs/core/dom" +import {to_string} from "@bokehjs/core/util/pretty" export class DiscretePlayerView extends PlayerView { declare model: DiscretePlayer override append_value_to_title_el(): void { - this.titleEl.appendChild(span({ class: "pn-player-value" }, to_string(this.model.options[this.model.value]))) + this.titleEl.appendChild(span({class: "pn-player-value"}, to_string(this.model.options[this.model.value]))) } } @@ -34,10 +33,10 @@ export class DiscretePlayer extends Player { static { this.prototype.default_view = DiscretePlayerView - this.define(({ List, Any }) => ({ + this.define(({List, Any}) => ({ options: [List(Any), []], })) - this.override({ width: 400 }) + this.override({width: 400}) } } diff --git a/panel/models/player.ts b/panel/models/player.ts index f98a786734..8c252bbca6 100644 --- a/panel/models/player.ts +++ b/panel/models/player.ts @@ -1,8 +1,8 @@ -import { Enum } from "@bokehjs/core/kinds" +import {Enum} from "@bokehjs/core/kinds" import type * as p from "@bokehjs/core/properties" -import { div, empty, span } from "@bokehjs/core/dom" -import { Widget, WidgetView } from "@bokehjs/models/widgets/widget" -import { to_string } from "@bokehjs/core/util/pretty" +import {div, empty, span} from "@bokehjs/core/dom" +import {Widget, WidgetView} from "@bokehjs/models/widgets/widget" +import {to_string} from "@bokehjs/core/util/pretty" const SVG_STRINGS = { slower: ' this.update_title_and_value()) this.on_change(value_align, () => this.set_value_align()) this.on_change(direction, () => this.set_direction()) @@ -315,8 +315,8 @@ export class PlayerView extends WidgetView { this.titleEl.style.display = hide_header ? "none" : "" if (!hide_header) { - this.titleEl.style.visibility = "visible"; - const { title } = this.model + this.titleEl.style.visibility = "visible" + const {title} = this.model if (title != null && title.length > 0) { if (this.contains_tex_string(title)) { this.titleEl.innerHTML = `${this.process_tex(title)}: ` @@ -328,14 +328,13 @@ export class PlayerView extends WidgetView { if (this.model.show_value) { this.append_value_to_title_el() } - } - else { + } else { this.titleEl.style.visibility = "hidden" } } append_value_to_title_el(): void { - this.titleEl.appendChild(span({ class: "pn-player-value" }, to_string(this.model.value))) + this.titleEl.appendChild(span({class: "pn-player-value"}, to_string(this.model.value))) } set_value_align(): void { @@ -511,7 +510,7 @@ export class Player extends Widget { static { this.prototype.default_view = PlayerView - this.define(({ Bool, Int, Str }) => ({ + this.define(({Bool, Int, Str}) => ({ direction: [Int, 0], interval: [Int, 500], start: [Int, 0], @@ -523,9 +522,9 @@ export class Player extends Widget { value_align: [Str, "start"], value_throttled: [Int, 0], show_loop_controls: [Bool, true], - show_value: [Bool, true] + show_value: [Bool, true], })) - this.override({ width: 400 }) + this.override({width: 400}) } } From d52de6eec0fb7c818335f384ba1fa4ea89d71aad Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 2 Aug 2024 13:00:34 -0700 Subject: [PATCH 12/14] only convert to str if needed --- panel/models/discrete_player.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/panel/models/discrete_player.ts b/panel/models/discrete_player.ts index 35f55bdadd..b6ca880063 100644 --- a/panel/models/discrete_player.ts +++ b/panel/models/discrete_player.ts @@ -7,7 +7,11 @@ export class DiscretePlayerView extends PlayerView { declare model: DiscretePlayer override append_value_to_title_el(): void { - this.titleEl.appendChild(span({class: "pn-player-value"}, to_string(this.model.options[this.model.value]))) + var label = this.model.options[this.model.value] + if (typeof label !== "string") { + label = to_string(label) + } + this.titleEl.appendChild(span({class: "pn-player-value"}, label)) } } From 0cb45e60f5993fe9501f14e7fefa2717f259c63b Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 2 Aug 2024 13:03:41 -0700 Subject: [PATCH 13/14] lint --- panel/models/discrete_player.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panel/models/discrete_player.ts b/panel/models/discrete_player.ts index b6ca880063..97cb31b3d1 100644 --- a/panel/models/discrete_player.ts +++ b/panel/models/discrete_player.ts @@ -7,7 +7,7 @@ export class DiscretePlayerView extends PlayerView { declare model: DiscretePlayer override append_value_to_title_el(): void { - var label = this.model.options[this.model.value] + let label = this.model.options[this.model.value] if (typeof label !== "string") { label = to_string(label) } From e1bd9db7dc1b2a5240447bae93fbd629d4a8a381 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 2 Aug 2024 21:01:58 -0700 Subject: [PATCH 14/14] fix test --- panel/models/player.ts | 15 +++++++++++---- panel/tests/ui/widgets/test_player.py | 17 +++++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/panel/models/player.ts b/panel/models/player.ts index 8c252bbca6..71d876ce3a 100644 --- a/panel/models/player.ts +++ b/panel/models/player.ts @@ -136,10 +136,10 @@ export class PlayerView extends WidgetView { // Display Value this.titleEl = div() - this.titleEl.setAttribute("class", "pn-player-title") + this.titleEl.classList.add("pn-player-title") + this.titleEl.style.padding = "0 5px 0 5px" this.update_title_and_value() this.set_value_align() - this.titleEl.style.cssText = "padding: 0 5px 0 5px; user-select:none;" // Slider this.sliderEl = document.createElement("input") @@ -319,9 +319,15 @@ export class PlayerView extends WidgetView { const {title} = this.model if (title != null && title.length > 0) { if (this.contains_tex_string(title)) { - this.titleEl.innerHTML = `${this.process_tex(title)}: ` + this.titleEl.innerHTML = `${this.process_tex(title)}` + if (this.model.show_value) { + this.titleEl.innerHTML += ": " + } } else { - this.titleEl.textContent = `${title}: ` + this.titleEl.textContent = `${title}` + if (this.model.show_value) { + this.titleEl.textContent += ": " + } } } @@ -347,6 +353,7 @@ export class PlayerView extends WidgetView { break case "end": this.titleEl.style.textAlign = "right" + console.log(this.titleEl) break } } diff --git a/panel/tests/ui/widgets/test_player.py b/panel/tests/ui/widgets/test_player.py index 9137b1e2d8..fde255d6a8 100644 --- a/panel/tests/ui/widgets/test_player.py +++ b/panel/tests/ui/widgets/test_player.py @@ -4,7 +4,7 @@ from playwright.sync_api import expect -from panel.tests.util import serve_component +from panel.tests.util import serve_component, wait_until from panel.widgets import Player pytestmark = pytest.mark.ui @@ -14,14 +14,14 @@ def test_init(page): player = Player() serve_component(page, player) - assert not page.is_visible('label') + assert not page.is_visible('pn-player-value') assert page.query_selector('.pn-player-value') is None def test_show_value(page): player = Player(show_value=True) serve_component(page, player) - assert page.is_visible('label') + wait_until(lambda: page.query_selector('.pn-player-value') is not None) assert page.query_selector('.pn-player-value') is not None @@ -32,17 +32,18 @@ def test_name(page): assert page.is_visible('label') assert page.query_selector('.pn-player-value') is None - name = page.locator('label:has-text("test")') + name = page.locator('.pn-player-title:has-text("test")') expect(name).to_have_count(1) -def test_value_location(page): - player = Player(name='test', value_location='top_right') +def test_value_align(page): + player = Player(name='test', value_align='end') serve_component(page, player) - name = page.locator('label:has-text("test")') + name = page.locator('.pn-player-title:has-text("test")') expect(name).to_have_css("text-align", "right") + def test_name_and_show_value(page): player = Player(name='test', show_value=True) serve_component(page, player) @@ -50,5 +51,5 @@ def test_name_and_show_value(page): assert page.is_visible('label') assert page.query_selector('.pn-player-value') is not None - name = page.locator('label:has-text("test")') + name = page.locator('.pn-player-title:has-text("test")') expect(name).to_have_count(1)