mirror of
https://github.com/rio-labs/rio.git
synced 2026-05-01 00:20:53 -05:00
fix inability to remove SwitcherBar icons
This commit is contained in:
@@ -2,7 +2,6 @@ import { applySwitcheroo } from '../designApplication';
|
||||
import { ColorSet, ComponentId } from '../dataModels';
|
||||
import { ComponentBase, ComponentState } from './componentBase';
|
||||
import { RippleEffect } from '../rippleEffect';
|
||||
import { firstDefined } from '../utils';
|
||||
import { markEventAsHandled } from '../eventHandling';
|
||||
|
||||
type AbstractButtonState = ComponentState & {
|
||||
@@ -109,13 +108,11 @@ abstract class AbstractButtonComponent extends ComponentBase {
|
||||
) {
|
||||
// It looks ugly if every new button is initially greyed out, so for
|
||||
// the styling ignore `self.isStillInitiallyDisabled`.
|
||||
let is_sensitive: boolean = firstDefined(
|
||||
deltaState.is_sensitive,
|
||||
this.state['is_sensitive']
|
||||
);
|
||||
let is_sensitive =
|
||||
deltaState.is_sensitive ?? this.state.is_sensitive;
|
||||
|
||||
let colorSet = is_sensitive
|
||||
? firstDefined(deltaState.color, this.state['color'])
|
||||
? deltaState.color ?? this.state.color
|
||||
: 'disabled';
|
||||
|
||||
// If no new colorset is specified, bump to the next palette. This
|
||||
|
||||
@@ -69,29 +69,10 @@ export class CalendarComponent extends ComponentBase {
|
||||
] = Array.from(headerElement.children) as HTMLElement[];
|
||||
|
||||
// Initialize icons
|
||||
applyIcon(
|
||||
this.prevYearButton,
|
||||
'material/keyboard-double-arrow-left',
|
||||
'currentColor'
|
||||
);
|
||||
|
||||
applyIcon(
|
||||
this.prevMonthButton,
|
||||
'material/keyboard-arrow-left',
|
||||
'currentColor'
|
||||
);
|
||||
|
||||
applyIcon(
|
||||
this.nextMonthButton,
|
||||
'material/keyboard-arrow-right',
|
||||
'currentColor'
|
||||
);
|
||||
|
||||
applyIcon(
|
||||
this.nextYearButton,
|
||||
'material/keyboard-double-arrow-right',
|
||||
'currentColor'
|
||||
);
|
||||
applyIcon(this.prevYearButton, 'material/keyboard_double_arrow_left');
|
||||
applyIcon(this.prevMonthButton, 'material/keyboard_arrow_left');
|
||||
applyIcon(this.nextMonthButton, 'material/keyboard_arrow_right');
|
||||
applyIcon(this.nextYearButton, 'material/keyboard_double_arrow_right');
|
||||
|
||||
// Initialize the state
|
||||
this.displayedYear = this.state.selectedYear;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ComponentBase, ComponentState } from './componentBase';
|
||||
import hljs from 'highlight.js/lib/common';
|
||||
import { Language } from 'highlight.js';
|
||||
|
||||
import { setClipboard, firstDefined } from '../utils';
|
||||
import { setClipboard } from '../utils';
|
||||
import { applyIcon } from '../designApplication';
|
||||
import { markEventAsHandled } from '../eventHandling';
|
||||
|
||||
@@ -104,11 +104,7 @@ export function convertDivToCodeBlock(
|
||||
labelElement.textContent = languageNiceName;
|
||||
|
||||
// Initialize the copy button
|
||||
applyIcon(
|
||||
copyButton,
|
||||
'material/content-copy',
|
||||
'var(--rio-local-text-color)'
|
||||
);
|
||||
applyIcon(copyButton, 'material/content_copy');
|
||||
|
||||
copyButton.addEventListener('click', (event) => {
|
||||
const codeToCopy = (preElement as HTMLPreElement).textContent ?? '';
|
||||
@@ -116,19 +112,11 @@ export function convertDivToCodeBlock(
|
||||
setClipboard(codeToCopy);
|
||||
|
||||
copyButton.title = 'Copied!';
|
||||
applyIcon(
|
||||
copyButton,
|
||||
'material/done',
|
||||
'var(--rio-local-text-color)'
|
||||
);
|
||||
applyIcon(copyButton, 'material/done');
|
||||
|
||||
setTimeout(() => {
|
||||
copyButton.title = 'Copy code';
|
||||
applyIcon(
|
||||
copyButton,
|
||||
'material/content-copy',
|
||||
'var(--rio-local-text-color)'
|
||||
);
|
||||
applyIcon(copyButton, 'material/content_copy');
|
||||
}, 5000);
|
||||
|
||||
markEventAsHandled(event);
|
||||
@@ -152,22 +140,12 @@ export class CodeBlockComponent extends ComponentBase {
|
||||
): void {
|
||||
super.updateElement(deltaState, latentComponents);
|
||||
|
||||
// Find the value sto use
|
||||
let code = firstDefined(deltaState.code, this.state.code);
|
||||
|
||||
let language = firstDefined(deltaState.language, this.state.language);
|
||||
|
||||
let displayControls = firstDefined(
|
||||
deltaState.show_controls,
|
||||
this.state.show_controls
|
||||
);
|
||||
|
||||
// Re-create the code block
|
||||
convertDivToCodeBlock(
|
||||
this.element as HTMLDivElement,
|
||||
code,
|
||||
language,
|
||||
displayControls
|
||||
deltaState.code ?? this.state.code,
|
||||
deltaState.language ?? this.state.language,
|
||||
deltaState.show_controls ?? this.state.show_controls
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,14 +109,14 @@ export class CodeExplorerComponent extends ComponentBase {
|
||||
this.element.style.flexDirection = 'row';
|
||||
applyIcon(
|
||||
this.arrowElement,
|
||||
'material/arrow-right-alt:fill',
|
||||
'material/arrow_right_alt:fill',
|
||||
'var(--rio-global-secondary-bg)'
|
||||
);
|
||||
} else {
|
||||
this.element.style.flexDirection = 'column';
|
||||
applyIcon(
|
||||
this.arrowElement,
|
||||
'material/arrow-downward:fill',
|
||||
'material/arrow_downward:fill',
|
||||
'var(--rio-global-secondary-bg)'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,11 +11,7 @@ export class ComponentPickerComponent extends ComponentBase {
|
||||
let element = document.createElement('div');
|
||||
element.classList.add('rio-component-picker');
|
||||
|
||||
applyIcon(
|
||||
element,
|
||||
'material/arrow-selector-tool:fill',
|
||||
'var(--rio-local-text-color)'
|
||||
);
|
||||
applyIcon(element, 'material/arrow-selector-tool:fill');
|
||||
|
||||
element.addEventListener('click', this.pickComponent.bind(this));
|
||||
|
||||
|
||||
@@ -167,11 +167,7 @@ export class ComponentTreeComponent extends ComponentBase {
|
||||
header.insertBefore(iconElement, header.firstChild);
|
||||
|
||||
if (children.length > 0) {
|
||||
applyIcon(
|
||||
iconElement,
|
||||
'material/keyboard-arrow-right',
|
||||
'currentColor'
|
||||
);
|
||||
applyIcon(iconElement, 'material/keyboard-arrow-right');
|
||||
}
|
||||
|
||||
node.appendChild(header);
|
||||
@@ -227,7 +223,7 @@ export class ComponentTreeComponent extends ComponentBase {
|
||||
header.appendChild(iconElement);
|
||||
|
||||
iconElement.title = tooltip;
|
||||
applyIcon(iconElement, icon, 'currentColor');
|
||||
applyIcon(iconElement, icon);
|
||||
}
|
||||
|
||||
// Click...
|
||||
|
||||
@@ -34,11 +34,7 @@ export class DropdownComponent extends ComponentBase {
|
||||
// Add an arrow icon
|
||||
let arrowElement = document.createElement('div');
|
||||
arrowElement.classList.add('rio-dropdown-arrow');
|
||||
applyIcon(
|
||||
arrowElement,
|
||||
'material/expand-more',
|
||||
'var(--rio-local-text-color)'
|
||||
);
|
||||
applyIcon(arrowElement, 'material/expand_more');
|
||||
this.inputBox.suffixElement = arrowElement;
|
||||
|
||||
// Create the popup
|
||||
@@ -408,11 +404,7 @@ export class DropdownComponent extends ComponentBase {
|
||||
|
||||
// Was anything found?
|
||||
if (this.optionsElement.children.length === 0) {
|
||||
applyIcon(
|
||||
this.optionsElement,
|
||||
'material/error',
|
||||
'var(--rio-local-text-color)'
|
||||
);
|
||||
applyIcon(this.optionsElement, 'material/error');
|
||||
|
||||
// The icon is loaded asynchronously, so make sure to give the
|
||||
// element some space
|
||||
|
||||
@@ -73,11 +73,7 @@ export class ImageComponent extends ComponentBase {
|
||||
private _onError(event: string | Event): void {
|
||||
this.imageElement.classList.remove('rio-content-loading');
|
||||
|
||||
applyIcon(
|
||||
this.element,
|
||||
'material/broken-image',
|
||||
'var(--rio-local-text-color)'
|
||||
);
|
||||
applyIcon(this.element, 'material/broken-image');
|
||||
|
||||
this.sendMessageToBackend({
|
||||
type: 'onError',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ComponentBase, ComponentState } from './componentBase';
|
||||
import { ComponentId } from '../dataModels';
|
||||
import { firstDefined } from '../utils';
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values
|
||||
const HARDWARE_KEY_MAP = {
|
||||
@@ -710,18 +709,11 @@ export class KeyEventListenerComponent extends ComponentBase {
|
||||
|
||||
let element = this.element;
|
||||
|
||||
let reportKeyDown = firstDefined(
|
||||
deltaState.reportKeyDown,
|
||||
this.state.reportKeyDown
|
||||
);
|
||||
let reportKeyUp = firstDefined(
|
||||
deltaState.reportKeyUp,
|
||||
this.state.reportKeyUp
|
||||
);
|
||||
let reportKeyPress = firstDefined(
|
||||
deltaState.reportKeyPress,
|
||||
this.state.reportKeyPress
|
||||
);
|
||||
let reportKeyDown =
|
||||
deltaState.reportKeyDown ?? this.state.reportKeyDown;
|
||||
let reportKeyUp = deltaState.reportKeyUp ?? this.state.reportKeyUp;
|
||||
let reportKeyPress =
|
||||
deltaState.reportKeyPress ?? this.state.reportKeyPress;
|
||||
|
||||
if (reportKeyDown || reportKeyPress) {
|
||||
element.onkeydown = (e: KeyboardEvent) => {
|
||||
|
||||
@@ -112,12 +112,6 @@ function hijackLocalLinks(div: HTMLElement): void {
|
||||
export class MarkdownComponent extends ComponentBase {
|
||||
state: Required<MarkdownState>;
|
||||
|
||||
// Since laying out markdown is time intensive, this component does its best
|
||||
// not to re-layout unless needed. This is done by setting the height
|
||||
// request lazily, and only if the width has changed. This value here is the
|
||||
// component's allocated width when the height request was last set.
|
||||
private heightRequestAssumesWidth: number;
|
||||
|
||||
createElement(): HTMLElement {
|
||||
const element = document.createElement('div');
|
||||
element.classList.add('rio-markdown');
|
||||
|
||||
@@ -63,7 +63,7 @@ export class RevealerComponent extends ComponentBase {
|
||||
) as HTMLElement;
|
||||
|
||||
// Initialize them
|
||||
applyIcon(this.arrowElement, 'material/expand_more', 'currentColor');
|
||||
applyIcon(this.arrowElement, 'material/expand_more');
|
||||
|
||||
this.rippleInstance = new RippleEffect(element, {
|
||||
triggerOnPress: false,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { applySwitcheroo } from '../designApplication';
|
||||
import { markEventAsHandled } from '../eventHandling';
|
||||
import { firstDefined } from '../utils';
|
||||
import { ComponentBase, ComponentState } from './componentBase';
|
||||
|
||||
export type SliderState = ComponentState & {
|
||||
@@ -152,10 +151,10 @@ export class SliderComponent extends ComponentBase {
|
||||
) {
|
||||
// The server can send invalid values due to reconciliation. Fix
|
||||
// them.
|
||||
let value = firstDefined(deltaState.value, this.state.value);
|
||||
let step = firstDefined(deltaState.step, this.state.step);
|
||||
let minimum = firstDefined(deltaState.minimum, this.state.minimum);
|
||||
let maximum = firstDefined(deltaState.maximum, this.state.maximum);
|
||||
let value = deltaState.value ?? this.state.value;
|
||||
let step = deltaState.step ?? this.state.step;
|
||||
let minimum = deltaState.minimum ?? this.state.minimum;
|
||||
let maximum = deltaState.maximum ?? this.state.maximum;
|
||||
|
||||
// Bring the value into a valid range
|
||||
value = Math.max(minimum, Math.min(maximum, value));
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { ComponentBase, ComponentState } from './componentBase';
|
||||
import { ColorSet } from '../dataModels';
|
||||
import { applySwitcheroo } from '../designApplication';
|
||||
import { firstDefined } from '../utils';
|
||||
import { applyIcon, applySwitcheroo } from '../designApplication';
|
||||
import { MappingTween } from '../tweens/mappingTweens';
|
||||
import { BaseTween } from '../tweens/baseTween';
|
||||
import { KineticTween } from '../tweens/kineticTween';
|
||||
import { pixelsPerRem } from '../app';
|
||||
import { firstDefined } from '../utils';
|
||||
|
||||
export type SwitcherBarState = ComponentState & {
|
||||
_type_: 'SwitcherBar-builtin';
|
||||
names?: string[];
|
||||
icon_svg_sources?: (string | null)[];
|
||||
icons?: (string | null)[] | null;
|
||||
color?: ColorSet;
|
||||
orientation?: 'horizontal' | 'vertical';
|
||||
spacing?: number;
|
||||
@@ -83,7 +83,7 @@ export class SwitcherBarComponent extends ComponentBase {
|
||||
this.resizeObserver.disconnect();
|
||||
}
|
||||
|
||||
onResize(entries: ResizeObserverEntry[]): void {
|
||||
onResize(): void {
|
||||
// Update the marker position
|
||||
if (this.state.selectedName !== null) {
|
||||
this.markerAtAnimationEnd = this.getMarkerTarget()!;
|
||||
@@ -253,24 +253,22 @@ export class SwitcherBarComponent extends ComponentBase {
|
||||
result.classList.add('rio-switcher-bar-options');
|
||||
result.style.gap = `${this.state.spacing}rem`;
|
||||
|
||||
let names = firstDefined(deltaState.names, this.state.names);
|
||||
let iconSvgSources = firstDefined(
|
||||
deltaState.icon_svg_sources,
|
||||
this.state.icon_svg_sources
|
||||
);
|
||||
let names = deltaState.names ?? this.state.names;
|
||||
let icons = firstDefined(deltaState.icons, this.state.icons);
|
||||
|
||||
// Iterate over both
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
let name = names[i];
|
||||
let iconSvg = iconSvgSources[i];
|
||||
|
||||
let optionElement = document.createElement('div');
|
||||
optionElement.classList.add('rio-switcher-bar-option');
|
||||
result.appendChild(optionElement);
|
||||
|
||||
// Icon
|
||||
if (iconSvg !== null) {
|
||||
optionElement.innerHTML = iconSvg;
|
||||
if (icons !== null && icons[i] !== null) {
|
||||
let iconContainer = document.createElement('div');
|
||||
optionElement.appendChild(iconContainer);
|
||||
applyIcon(iconContainer, icons[i]!);
|
||||
}
|
||||
|
||||
// Text
|
||||
@@ -294,10 +292,7 @@ export class SwitcherBarComponent extends ComponentBase {
|
||||
super.updateElement(deltaState, latentComponents);
|
||||
|
||||
// Have the options changed?
|
||||
if (
|
||||
deltaState.names !== undefined ||
|
||||
deltaState.icon_svg_sources !== undefined
|
||||
) {
|
||||
if (deltaState.names !== undefined || deltaState.icons !== undefined) {
|
||||
this.innerElement.innerHTML = '';
|
||||
this.markerElement.innerHTML = '';
|
||||
|
||||
|
||||
@@ -270,7 +270,7 @@ export async function applyIcon(
|
||||
| Color
|
||||
| ColorSet
|
||||
| 'dim'
|
||||
| string // A CSS color value
|
||||
| string = 'currentColor' // A CSS color value
|
||||
): Promise<void> {
|
||||
// Avoid races: When calling this function multiple times on the same
|
||||
// element it can sometimes assign the first icon AFTER the second one, thus
|
||||
|
||||
@@ -131,16 +131,16 @@ export function zip<T1, T2>(
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Returns the first argument that isn't `undefined`. Returns `undefined` if
|
||||
/// none of the arguments are defined.
|
||||
export function firstDefined(...args: any[]): any {
|
||||
/// Returns the first argument that isn't `undefined`. Throws an error if all
|
||||
/// arguments are `undefined`.
|
||||
export function firstDefined<T>(...args: (T | undefined)[]): T {
|
||||
for (let arg of args) {
|
||||
if (arg !== undefined) {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
throw new Error('All arguments were `undefined`');
|
||||
}
|
||||
|
||||
/// Removes `oldElement` from the DOM and inserts `newElement` at its position
|
||||
|
||||
@@ -2347,7 +2347,7 @@ $rio-input-box-text-distance-from-bottom: 0.4rem; // To be aligned with the <inp
|
||||
color 0.1s ease-out;
|
||||
}
|
||||
|
||||
.rio-switcher-bar-option > svg {
|
||||
.rio-switcher-bar-option svg {
|
||||
width: 1.8rem;
|
||||
height: 1.8rem;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
@@ -120,9 +120,9 @@ class SwitcherBar(FundamentalComponent, Generic[T]):
|
||||
`experimental`: True
|
||||
"""
|
||||
|
||||
names: list[str]
|
||||
values: list[T]
|
||||
icon_svg_sources: list[str | None]
|
||||
names: Sequence[str]
|
||||
values: Sequence[T]
|
||||
icons: Sequence[str | None] | None
|
||||
color: rio.ColorSet
|
||||
orientation: Literal["horizontal", "vertical"]
|
||||
spacing: float
|
||||
@@ -132,9 +132,9 @@ class SwitcherBar(FundamentalComponent, Generic[T]):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
values: list[T],
|
||||
values: Sequence[T],
|
||||
*,
|
||||
names: list[str] | None = None,
|
||||
names: Sequence[str] | None = None,
|
||||
icons: Sequence[str | None] | None = None,
|
||||
color: rio.ColorSet = "keep",
|
||||
orientation: Literal["horizontal", "vertical"] = "horizontal",
|
||||
@@ -179,44 +179,44 @@ class SwitcherBar(FundamentalComponent, Generic[T]):
|
||||
# SCROLLING-REWORK scroll_y=scroll_y,
|
||||
)
|
||||
|
||||
# Names default to the string representation of the values
|
||||
if names is None:
|
||||
names = [str(value) for value in values]
|
||||
|
||||
self.names = names
|
||||
self.values = values
|
||||
self.icons = icons
|
||||
self.selected_value = selected_value
|
||||
self.color = color
|
||||
self.orientation = orientation
|
||||
self.spacing = spacing
|
||||
self.allow_none = allow_none
|
||||
self.on_change = on_change
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
values = self.values
|
||||
|
||||
if not values:
|
||||
raise ValueError("`SwitcherBar` must have at least one option.")
|
||||
|
||||
# Names default to the string representation of the values
|
||||
if names is None:
|
||||
self.names = [str(value) for value in values]
|
||||
else:
|
||||
if len(names) != len(values):
|
||||
raise ValueError("`names` must be the same length as `values`.")
|
||||
if len(self.names) != len(values):
|
||||
raise ValueError("`names` must be the same length as `values`.")
|
||||
|
||||
self.names = names
|
||||
# Icons
|
||||
icons = self.icons
|
||||
|
||||
# Icons default to `None`. Also, fetch their SVG sources so any errors
|
||||
# are raised now, rather than later.
|
||||
if icons is None:
|
||||
self.icon_svg_sources = [None] * len(values)
|
||||
else:
|
||||
if icons is not None:
|
||||
if len(icons) != len(values):
|
||||
raise ValueError("`icons` must be the same length as `values`.")
|
||||
|
||||
self.icon_svg_sources = [
|
||||
None if icon is None else icon_registry.get_icon_svg(icon)
|
||||
for icon in icons
|
||||
]
|
||||
# Make sure the icon names are valid
|
||||
for icon in icons:
|
||||
if icon is not None:
|
||||
icon_registry.get_icon_svg(icon)
|
||||
|
||||
self.selected_value = selected_value
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
# Make sure a value is selected, if needed
|
||||
if self.selected_value is None and not self.allow_none:
|
||||
self.selected_value = self.values[0]
|
||||
self.selected_value = values[0]
|
||||
|
||||
def _fetch_selected_name(self) -> str | None:
|
||||
# None is fine
|
||||
@@ -238,15 +238,10 @@ class SwitcherBar(FundamentalComponent, Generic[T]):
|
||||
return self.names[0]
|
||||
|
||||
def _custom_serialize(self) -> JsonDoc:
|
||||
result = {
|
||||
"optionNames": self.names,
|
||||
"optionIcons": self.icon_svg_sources,
|
||||
return {
|
||||
"selectedName": self._fetch_selected_name(),
|
||||
"color": self.session.theme._serialize_colorset(self.color),
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
async def _on_message(self, msg: Any) -> None:
|
||||
# Parse the message
|
||||
assert isinstance(msg, dict), msg
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ def get_child_component_containing_attribute_names(
|
||||
attr_names.append(attr_name)
|
||||
# : list[Component]
|
||||
elif (
|
||||
serializer.func is serialization._serialize_list
|
||||
serializer.func is serialization._serialize_sequence
|
||||
and serializer.keywords["item_serializer"]
|
||||
is serialization._serialize_child_component
|
||||
):
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import collections.abc
|
||||
import enum
|
||||
import functools
|
||||
import inspect
|
||||
import json
|
||||
import types
|
||||
import typing
|
||||
from typing import * # type: ignore
|
||||
|
||||
import introspection.types
|
||||
@@ -195,10 +197,10 @@ def _serialize_child_component(
|
||||
return component._id
|
||||
|
||||
|
||||
def _serialize_list(
|
||||
sess: session.Session, list_: list[T], item_serializer: Serializer[T]
|
||||
def _serialize_sequence(
|
||||
sess: session.Session, sequence: Sequence[T], item_serializer: Serializer[T]
|
||||
) -> Jsonable:
|
||||
return [item_serializer(sess, item) for item in list_]
|
||||
return [item_serializer(sess, item) for item in sequence]
|
||||
|
||||
|
||||
def _serialize_enum(
|
||||
@@ -262,12 +264,13 @@ def _get_serializer_for_annotation(
|
||||
return functools.partial(_serialize_enum, as_type=annotation)
|
||||
|
||||
# Sequences of serializable values
|
||||
if origin is list:
|
||||
if origin in (list, typing.Sequence, collections.abc.Sequence):
|
||||
item_serializer = _get_serializer_for_annotation(args[0])
|
||||
if item_serializer is None:
|
||||
return None
|
||||
|
||||
return functools.partial(
|
||||
_serialize_list, item_serializer=item_serializer
|
||||
_serialize_sequence, item_serializer=item_serializer
|
||||
)
|
||||
|
||||
# Literal
|
||||
|
||||
Reference in New Issue
Block a user