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

Inherit new scroll functionalities from Column in ChatBox log #5310

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1988ac7
Add scroll log
ahuang11 Jul 5, 2023
80b46e8
Attempt to convert to Typescript
ahuang11 Jul 9, 2023
ea2df97
Add to index.ts
ahuang11 Jul 9, 2023
210a37b
Address comments
ahuang11 Jul 10, 2023
ba9411c
Rename
ahuang11 Jul 10, 2023
1676b22
Create workable version
ahuang11 Jul 10, 2023
1effcee
Extend BkColumn
ahuang11 Jul 11, 2023
1ed31ee
Remove scroll log
ahuang11 Jul 11, 2023
b503a8c
Minor fixes
philippjfr Jul 11, 2023
01d2f5d
Address comments
ahuang11 Jul 11, 2023
4083064
Fix scrolling too early
ahuang11 Jul 13, 2023
d275ba0
Add tests + docs
ahuang11 Jul 14, 2023
5c35b8b
Precommit
ahuang11 Jul 14, 2023
ccd50d8
precommit
ahuang11 Jul 14, 2023
f7c3916
Address comments
ahuang11 Jul 14, 2023
ca0a877
Styling
ahuang11 Jul 14, 2023
20d0ccc
Fix inheritance
ahuang11 Jul 14, 2023
216a097
Various fixes
philippjfr Jul 19, 2023
e901c1f
Fix compile issues
ahuang11 Jul 19, 2023
9950822
Fix and revert
philippjfr Jul 19, 2023
cd6c396
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 19, 2023
3954d9a
Fix subtle bug :D
ahuang11 Jul 19, 2023
79c3c64
Negative margin
ahuang11 Jul 19, 2023
32da87a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 19, 2023
07c44e4
Update CSS
ahuang11 Jul 19, 2023
189b4a3
Fix tests
ahuang11 Jul 21, 2023
469aa9c
Maybe fix UI test
ahuang11 Jul 24, 2023
4b6f890
Fix data types and logic issues, enhance auto_scroll with limit, make…
ahuang11 Jul 24, 2023
0aa5ec4
Add view latest
ahuang11 Jul 24, 2023
3de5c2e
Reverse logic in test and fix scroll
ahuang11 Jul 24, 2023
23e3d4e
Inherit new scroll func in chatlog
ahuang11 Jul 24, 2023
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
4 changes: 3 additions & 1 deletion examples/reference/layouts/Column.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
"\n",
"* **``objects``** (list): The list of objects to display in the Column, should not generally be modified directly except when replaced in its entirety.\n",
"* **``scroll``** (boolean): Enable scrollbars if the content overflows the size of the container.\n",
"\n",
"* **``auto_scroll_limit``** (int): Max pixel distance from the latest object in the Column to activate automatic scrolling upon update. Setting to 0 disables auto-scrolling\n",
"* **``scroll_button_threshold``** (int): Min pixel distance from the latest object in the Column to display the scroll button. Setting to 0 disables the scroll button.\"\"\"\n",
"* **``view_latest``** (bool): Whether to scroll to the latest object on init. If not enabled the view will be on the first object.\"\"\"\n",
"___"
]
},
Expand Down
45 changes: 44 additions & 1 deletion panel/dist/css/listpanel.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,46 @@
:host(.scrollable) {
overflow: scroll;
overflow: auto;
}

:host(.scrollable-vertical) {
overflow-y: auto;
}

:host(.scrollable-horizontal) {
overflow-x: auto;
}

.scroll-button {
/* For location */
position: sticky;
top: calc(100% - 38px);
left: calc(100% - 60px);
/* For icon */
cursor: pointer;
visibility: hidden;
font-size: 18px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.25);
color: white;
width: 36px;
min-height: 36px;
margin-bottom: -36px; /* Remove space taken */
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
/* For animation */
opacity: 0;
transition:
visibility 0s,
opacity 0.2s ease-in-out;
}

.visible {
visibility: visible;
opacity: 1;
}

.scroll-button:before {
content: '⬇';
}
1 change: 1 addition & 0 deletions panel/layout/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"Panel",
"Row",
"Spacer",
"Swipe",
"Tabs",
"VSpacer",
"WidgetBox"
Expand Down
52 changes: 41 additions & 11 deletions panel/layout/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@

import param

from bokeh.models import Column as BkColumn, Row as BkRow
from bokeh.models import Row as BkRow

from ..io.model import hold
from ..io.resources import CDN_DIST
from ..io.state import state
from ..models import Column as PnColumn
from ..reactive import Reactive
from ..util import param_name, param_reprs

Expand Down Expand Up @@ -792,11 +793,14 @@ def _linked_properties(self):
)

def _process_param_change(self, params: Dict[str, Any]) -> Dict[str, Any]:
scroll = params.pop('scroll', None)
css_classes = self.css_classes or []
if scroll:
params['css_classes'] = css_classes + ['scrollable']
elif scroll == False:
if 'scroll' in params:
scroll = params['scroll']
css_classes = params.get('css_classes', self.css_classes)
if scroll:
if self._direction is not None:
css_classes += [f'scrollable-{self._direction}']
else:
css_classes += ['scrollable']
params['css_classes'] = css_classes
return super()._process_param_change(params)

Expand Down Expand Up @@ -825,10 +829,13 @@ class NamedListPanel(NamedListLike, Panel):

def _process_param_change(self, params: Dict[str, Any]) -> Dict[str, Any]:
if 'scroll' in params:
scroll = params.pop('scroll')
css_classes = list(self.css_classes or [])
scroll = params['scroll']
css_classes = params.get('css_classes', self.css_classes)
if scroll:
css_classes += ['scrollable']
if self._direction is not None:
css_classes += [f'scrollable-{self._direction}']
else:
css_classes += ['scrollable']
params['css_classes'] = css_classes
return super()._process_param_change(params)

Expand Down Expand Up @@ -879,12 +886,35 @@ class Column(ListPanel):
>>> pn.Column(some_widget, some_pane, some_python_object)
"""

_bokeh_model: ClassVar[Type[Model]] = BkColumn
auto_scroll_limit = param.Integer(bounds=(0, None), doc="""
Max pixel distance from the latest object in the Column to
activate automatic scrolling upon update. Setting to 0
disables auto-scrolling.""")

scroll_button_threshold = param.Integer(bounds=(0, None), doc="""
Min pixel distance from the latest object in the Column to
display the scroll button. Setting to 0
disables the scroll button.""")

view_latest = param.Boolean(default=False, doc="""
Whether to scroll to the latest object on init. If not
enabled the view will be on the first object.""")

_bokeh_model: ClassVar[Type[Model]] = PnColumn

_direction = 'vertical'

_stylesheets: ClassVar[list[str]] = [f'{CDN_DIST}css/listpanel.css']

@param.depends("auto_scroll_limit", "scroll_button_threshold", "view_latest", watch=True, on_init=True)
def _set_scrollable(self):
self.scroll = (
self.scroll or
bool(self.auto_scroll_limit) or
bool(self.scroll_button_threshold) or
self.view_latest
)


class WidgetBox(ListPanel):
"""
Expand Down Expand Up @@ -929,7 +959,7 @@ class WidgetBox(ListPanel):

@property
def _bokeh_model(self) -> Type[Model]: # type: ignore
return BkRow if self.horizontal else BkColumn
return BkRow if self.horizontal else PnColumn

@property
def _direction(self):
Expand Down
11 changes: 1 addition & 10 deletions panel/layout/card.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from ..io.resources import CDN_DIST
from ..models import Card as BkCard
from .base import Column, ListPanel, Row
from .base import Column, Row

if TYPE_CHECKING:
from bokeh.model import Model
Expand Down Expand Up @@ -95,15 +95,6 @@ def _cleanup(self, root: Model | None = None) -> None:
super()._cleanup(root)
self._header_layout._cleanup(root)

def _process_param_change(self, params):
scroll = params.pop('scroll', None)
css_classes = self.css_classes or []
if scroll:
params['css_classes'] = css_classes + ['scrollable']
elif scroll == False:
params['css_classes'] = css_classes
return super(ListPanel, self)._process_param_change(params)

def _update_header(self, *events):
from ..pane import HTML, panel
if self.header is None:
Expand Down
2 changes: 1 addition & 1 deletion panel/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from .datetime_picker import DatetimePicker # noqa
from .ipywidget import IPyWidget # noqa
from .layout import Card # noqa
from .layout import Card, Column # noqa
from .location import Location # noqa
from .markup import HTML, JSON, PDF # noqa
from .reactive_html import ReactiveHTML # noqa
Expand Down
2 changes: 1 addition & 1 deletion panel/models/card.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Column, ColumnView} from "@bokehjs/models/layouts/column"
import {Column, ColumnView} from "./column"
import * as DOM from "@bokehjs/core/dom"
import * as p from "@bokehjs/core/properties"

Expand Down
112 changes: 112 additions & 0 deletions panel/models/column.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Column as BkColumn, ColumnView as BkColumnView } from "@bokehjs/models/layouts/column";
import * as DOM from "@bokehjs/core/dom"
import * as p from "@bokehjs/core/properties";

export class ColumnView extends BkColumnView {
model: Column;
scroll_down_arrow_el: HTMLElement;

connect_signals(): void {
super.connect_signals();

const { children, scroll_button_threshold } = this.model.properties;

this.on_change(children, () => this.trigger_auto_scroll());
this.on_change(scroll_button_threshold, () => this.toggle_scroll_arrow())
}

get distance_from_latest(): number {
return this.el.scrollHeight - this.el.scrollTop - this.el.clientHeight;
}

scroll_to_latest(): void {
// Waits for the child to be rendered before scrolling
requestAnimationFrame(() => {
this.el.scrollTop = this.el.scrollHeight;
});
}

trigger_auto_scroll(): void {
const limit = this.model.auto_scroll_limit
const within_limit = this.distance_from_latest <= limit
if (limit == 0 || !within_limit)
return

this.scroll_to_latest()
}

toggle_scroll_arrow(): void {
const threshold = this.model.scroll_button_threshold
const exceeds_threshold = this.distance_from_latest >= threshold
this.scroll_down_arrow_el.classList.toggle(
"visible", threshold !== 0 && exceeds_threshold
)
}

render(): void {
super.render()
this.empty()
this._update_stylesheets()
this._update_css_classes()
this._apply_styles()
this._apply_visible()

this.class_list.add(...this.css_classes())
this.scroll_down_arrow_el = DOM.createElement('div', { class: 'scroll-button' });
this.shadow_el.appendChild(this.scroll_down_arrow_el);

this.el.addEventListener("scroll", () => {
this.toggle_scroll_arrow();
});
this.scroll_down_arrow_el.addEventListener("click", () => {
this.scroll_to_latest();
});

for (const child_view of this.child_views) {
this.shadow_el.appendChild(child_view.el)
child_view.render()
child_view.after_render()
}
}

after_render(): void {
super.after_render()
requestAnimationFrame(() => {
if (this.model.view_latest) {
this.scroll_to_latest();
}
this.toggle_scroll_arrow();
});
}
}

export namespace Column {
export type Attrs = p.AttrsOf<Props>;
export type Props = BkColumn.Props & {
auto_scroll_limit: p.Property<number>;
scroll_button_threshold: p.Property<number>;
view_latest: p.Property<boolean>;
};
}

export interface Column extends Column.Attrs { }

export class Column extends BkColumn {
properties: Column.Props;

constructor(attrs?: Partial<Column.Attrs>) {
super(attrs);
}

static __module__ = "panel.models.layout";

static {
this.prototype.default_view = ColumnView;

this.define<Column.Props>(({ Int, Boolean }) => ({
auto_scroll_limit: [Int, 0],
scroll_button_threshold: [Int, 0],
view_latest: [Boolean, false],
}));
}
}
1 change: 1 addition & 0 deletions panel/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export {AcePlot} from "./ace"
export {Audio} from "./audio"
export {BrowserInfo} from "./browser"
export {Card} from "./card"
export {Column} from "./column"
export {CommManager} from "./comm_manager"
export {CustomSelect} from "./customselect"
export {DataTabulator} from "./tabulator"
Expand Down
46 changes: 38 additions & 8 deletions panel/models/layout.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,65 @@
from bokeh.core.properties import (
Bool, List, Nullable, String,
Bool, Int, List, Nullable, String,
)
from bokeh.models import Column
from bokeh.models import Column as BkColumn
from bokeh.models.layouts import LayoutDOM

__all__ = (
"Card",
"HTMLBox",
"Column",
)


class HTMLBox(LayoutDOM):
""" """

class Card(Column):

active_header_background = Nullable(String, help="Background color of active Card header.")
class Column(BkColumn):

auto_scroll_limit = Int(
default=0,
help="""
Max pixel distance from the latest object in the Column to
activate automatic scrolling upon update. Setting to 0
disables auto-scrolling.""")

scroll_button_threshold = Int(
default=0,
help="""
Min pixel distance from the latest object in the Column to
display the scroll button. Setting to 0
disables the scroll button.""")

view_latest = Bool(
default=False,
help="""
Whether to scroll to the latest object on init. If not
enabled the view will be on the first object.""")

class Card(Column):
active_header_background = Nullable(
String, help="Background color of active Card header."
)

button_css_classes = List(String, help="CSS classes to add to the Card collapse button.")
button_css_classes = List(
String, help="CSS classes to add to the Card collapse button."
)

collapsed = Bool(True, help="Whether the Card is collapsed.")

collapsible = Bool(True, help="Whether the Card should have a button to collapse it.")
collapsible = Bool(
True, help="Whether the Card should have a button to collapse it."
)

header_background = Nullable(String, help="Background color of the Card header.")

header_color = Nullable(String, help="Color of the header text and button.")

header_css_classes = List(String, help="CSS classes to add to the Card header.")

header_tag = String('div', help="HTML tag to use for the Card header.")
header_tag = String("div", help="HTML tag to use for the Card header.")

hide_header = Bool(False, help="Whether to hide the Card header")

tag = String('tag', help="CSS class to use for the Card as a whole.")
tag = String("tag", help="CSS class to use for the Card as a whole.")
Loading