Source code for bacommon.bs._clouddialog

# Released under the MIT License. See LICENSE for details.
#
"""Simple cloud-defined UIs for things like notifications."""

from __future__ import annotations

import datetime
from enum import Enum
from dataclasses import dataclass, field
from typing import Annotated, override, assert_never

from efro.dataclassio import ioprepped, IOAttrs, IOMultiType

from bacommon.bs._displayitem import DisplayItemWrapper


[docs] class CloudDialogTypeID(Enum): """Type ID for each of our subclasses.""" UNKNOWN = 'u' BASIC = 'b'
[docs] class CloudDialog(IOMultiType[CloudDialogTypeID]): """Small self-contained ui bit provided by the cloud. These take care of updating and/or dismissing themselves based on user input. Useful for things such as inbox messages. For more complex UI construction, look at :class:`CloudUI`. """
[docs] @override @classmethod def get_type_id(cls) -> CloudDialogTypeID: # Require child classes to supply this themselves. If we did a # full type registry/lookup here it would require us to import # everything and would prevent lazy loading. raise NotImplementedError()
[docs] @override @classmethod def get_type(cls, type_id: CloudDialogTypeID) -> type[CloudDialog]: """Return the subclass for each of our type-ids.""" # pylint: disable=cyclic-import out: type[CloudDialog] t = CloudDialogTypeID if type_id is t.UNKNOWN: out = UnknownCloudDialog elif type_id is t.BASIC: out = BasicCloudDialog else: # Important to make sure we provide all types. assert_never(type_id) return out
[docs] @override @classmethod def get_unknown_type_fallback(cls) -> CloudDialog: # If we encounter some future message type we don't know # anything about, drop in a placeholder. return UnknownCloudDialog()
[docs] @ioprepped @dataclass class UnknownCloudDialog(CloudDialog): """Fallback type for unrecognized entries."""
[docs] @override @classmethod def get_type_id(cls) -> CloudDialogTypeID: return CloudDialogTypeID.UNKNOWN
[docs] class BasicCloudDialogComponentTypeID(Enum): """Type ID for each of our subclasses.""" UNKNOWN = 'u' TEXT = 't' LINK = 'l' BS_CLASSIC_TOURNEY_RESULT = 'ct' DISPLAY_ITEMS = 'di' EXPIRE_TIME = 'd'
[docs] class BasicCloudDialogComponent(IOMultiType[BasicCloudDialogComponentTypeID]): """Top level class for our multitype."""
[docs] @override @classmethod def get_type_id(cls) -> BasicCloudDialogComponentTypeID: # Require child classes to supply this themselves. If we did a # full type registry/lookup here it would require us to import # everything and would prevent lazy loading. raise NotImplementedError()
[docs] @override @classmethod def get_type( cls, type_id: BasicCloudDialogComponentTypeID ) -> type[BasicCloudDialogComponent]: """Return the subclass for each of our type-ids.""" # pylint: disable=cyclic-import t = BasicCloudDialogComponentTypeID if type_id is t.UNKNOWN: return BasicCloudDialogComponentUnknown if type_id is t.TEXT: return BasicCloudDialogComponentText if type_id is t.LINK: return BasicCloudDialogComponentLink if type_id is t.BS_CLASSIC_TOURNEY_RESULT: return BasicCloudDialogBsClassicTourneyResult if type_id is t.DISPLAY_ITEMS: return BasicCloudDialogDisplayItems if type_id is t.EXPIRE_TIME: return BasicCloudDialogExpireTime # Important to make sure we provide all types. assert_never(type_id)
[docs] @override @classmethod def get_unknown_type_fallback(cls) -> BasicCloudDialogComponent: # If we encounter some future message type we don't know # anything about, drop in a placeholder. return BasicCloudDialogComponentUnknown()
[docs] @ioprepped @dataclass class BasicCloudDialogComponentUnknown(BasicCloudDialogComponent): """An unknown basic client component type. In practice these should never show up since the master-server generates these on the fly for the client and so should not send clients one they can't digest. """
[docs] @override @classmethod def get_type_id(cls) -> BasicCloudDialogComponentTypeID: return BasicCloudDialogComponentTypeID.UNKNOWN
[docs] @ioprepped @dataclass class BasicCloudDialogComponentText(BasicCloudDialogComponent): """Show some text in the inbox message.""" text: str subs: list[str] = field( default_factory=list ) scale: float = 1.0 color: tuple[float, float, float, float] = (1.0, 1.0, 1.0, 1.0) spacing_top: float = 0.0 spacing_bottom: float = 0.0
[docs] @override @classmethod def get_type_id(cls) -> BasicCloudDialogComponentTypeID: return BasicCloudDialogComponentTypeID.TEXT
[docs] @ioprepped @dataclass class BasicCloudDialogBsClassicTourneyResult(BasicCloudDialogComponent): """Show info about a classic tourney.""" tournament_id: str game: str players: int rank: int trophy: str | None prizes: list[DisplayItemWrapper]
[docs] @override @classmethod def get_type_id(cls) -> BasicCloudDialogComponentTypeID: return BasicCloudDialogComponentTypeID.BS_CLASSIC_TOURNEY_RESULT
[docs] @ioprepped @dataclass class BasicCloudDialogDisplayItems(BasicCloudDialogComponent): """Show some display-items.""" items: list[DisplayItemWrapper] width: float = 100.0 spacing_top: float = 0.0 spacing_bottom: float = 0.0
[docs] @override @classmethod def get_type_id(cls) -> BasicCloudDialogComponentTypeID: return BasicCloudDialogComponentTypeID.DISPLAY_ITEMS
[docs] @ioprepped @dataclass class BasicCloudDialogExpireTime(BasicCloudDialogComponent): """Show expire-time.""" time: datetime.datetime spacing_top: float = 0.0 spacing_bottom: float = 0.0
[docs] @override @classmethod def get_type_id(cls) -> BasicCloudDialogComponentTypeID: return BasicCloudDialogComponentTypeID.EXPIRE_TIME
[docs] @ioprepped @dataclass class BasicCloudDialog(CloudDialog): """A basic UI for the client."""
[docs] class ButtonLabel(Enum): """Distinct button labels we support.""" UNKNOWN = 'u' OK = 'o' APPLY = 'a' CANCEL = 'c' ACCEPT = 'ac' DECLINE = 'dn' IGNORE = 'ig' CLAIM = 'cl' DISCARD = 'd'
[docs] class InteractionStyle(Enum): """Overall interaction styles we support.""" UNKNOWN = 'u' BUTTON_POSITIVE = 'p' BUTTON_POSITIVE_NEGATIVE = 'pn'
components: list[BasicCloudDialogComponent] interaction_style: InteractionStyle = InteractionStyle.BUTTON_POSITIVE button_label_positive: ButtonLabel = ButtonLabel.OK button_label_negative: ButtonLabel = ButtonLabel.CANCEL
[docs] @override @classmethod def get_type_id(cls) -> CloudDialogTypeID: return CloudDialogTypeID.BASIC
[docs] def contains_unknown_elements(self) -> bool: """Whether something within us is an unknown type or enum.""" return ( self.interaction_style is self.InteractionStyle.UNKNOWN or self.button_label_positive is self.ButtonLabel.UNKNOWN or self.button_label_negative is self.ButtonLabel.UNKNOWN or any( c.get_type_id() is BasicCloudDialogComponentTypeID.UNKNOWN for c in self.components ) )
[docs] @ioprepped @dataclass class CloudDialogWrapper: """Wrapper for a CloudDialog and its common data.""" id: str createtime: datetime.datetime ui: CloudDialog
[docs] class CloudDialogAction(Enum): """Types of actions we can run.""" BUTTON_PRESS_POSITIVE = 'p' BUTTON_PRESS_NEGATIVE = 'n'
# Docs-generation hack; import some stuff that we likely only forward-declared # in our actual source code so that docs tools can find it. from typing import (Coroutine, Any, Literal, Callable, Generator, Awaitable, Sequence, Self) import asyncio from concurrent.futures import Future from pathlib import Path from enum import Enum