Source code for bacommon.docui.routes.classicstore
# Released under the MIT License. See LICENSE for details.
#
"""Routes for the classic store and inventory doc-ui domains.
The store and inventory are two domains served by a single set of
pages (the inventory is essentially the store filtered to owned things
plus player-profiles), so they share one route family. Each has its
own local-actions.
"""
from __future__ import annotations # Docs-generation hack.
from enum import Enum
from dataclasses import dataclass
from typing import Annotated, override
from efro.dataclassio import ioprepped, IOAttrs
import bacommon.docui.v2 as dui2
from bacommon.docui.routes import (
DocUIRoute,
DocUILocalActionBase,
DocUIState,
PressSound,
family_members,
)
[docs]
class PurchaseMethod(Enum):
"""How to purchase something."""
TOKENS = 't'
TICKETS = 'k'
PURPLE_TICKETS = 'p'
GOLD_PASS = 'g'
[docs]
class StoreRoute(DocUIRoute):
"""Family class for classic store/inventory routes."""
[docs]
@override
@classmethod
def get_route_types(cls) -> tuple[type[DocUIRoute], ...]:
return family_members(AnyStoreRoute)
[docs]
@ioprepped
@dataclass
class Root(StoreRoute, path='/'):
"""The main store/inventory listing."""
#: Draw bounds and other debug bits.
debug: bool = False
#: Set on the page's own refresh button. One-time-use; the server
#: does not carry it into further links.
is_refresh: bool = False
#: (Inventory) the client is showing its locally-spliced legacy
#: profiles; omit cloud profile rows.
legacy_profiles: bool = False
#: (Inventory) render only the profiles section (the in-game
#: profile browser).
profiles_only: bool = False
#: Legacy purchase ids required to unlock something; when provided,
#: only items providing those are shown.
unlockreqs: list[str] | None = None
[docs]
@ioprepped
@dataclass
class Purchase(StoreRoute, path='/p'):
"""Purchase options for a single item."""
purchase_id: str
debug: bool = False
[docs]
@ioprepped
@dataclass
class PurchaseConfirm(StoreRoute, path='/pc', method=dui2.RequestMethod.POST):
"""Actually purchase an item."""
purchase_id: str
purchase_method: PurchaseMethod
debug: bool = False
[docs]
@ioprepped
@dataclass
class ProfileDraft(DocUIState, state_id='classic.profile_draft'):
"""The profile editor's working profile.
Page state for the editor and its save: what the user has composed
so far. A request with no draft at all (the first visit) gets one
built from the stored profile, or fresh defaults when creating; an
empty name or character means the same for that field.
"""
color: tuple[float, float, float]
highlight: tuple[float, float, float]
name: str = ''
character: str = ''
[docs]
@ioprepped
@dataclass
class ProfileEdit(StoreRoute, path='/profile'):
"""(Inventory) the cloud player-profile editor.
The draft being composed rides along as :class:`ProfileDraft`
page state; the route itself only says which stored profile (if
any) is being edited.
"""
#: Stored profile being edited, or None when creating one.
profile_name: str | None = (
None
)
[docs]
@override
@classmethod
def get_window_layout(cls) -> dui2.WindowLayout:
# The editor's controls go in the column; the pane beside them
# shows the character being composed.
return dui2.WindowLayout.VIEWER
[docs]
@ioprepped
@dataclass
class ProfileSave(
StoreRoute, path='/profile/save', method=dui2.RequestMethod.POST
):
"""(Inventory) save the editor's draft (its :class:`ProfileDraft`)."""
#: See :attr:`ProfileEdit.profile_name`.
profile_name: str | None = (
None
)
[docs]
@ioprepped
@dataclass
class ProfileDelete(
StoreRoute, path='/profile/delete', method=dui2.RequestMethod.POST
):
"""(Inventory) delete a stored profile."""
profile_name: str
# All routes in the family.
AnyStoreRoute = (
Root
| Purchase
| PurchaseConfirm
| ProfileEdit
| ProfileSave
| ProfileDelete
)
[docs]
class StoreLocalAction(DocUILocalActionBase):
"""Family class for classic store local-actions."""
[docs]
@override
@classmethod
def get_action_types(cls) -> tuple[type[DocUILocalActionBase], ...]:
return family_members(AnyStoreLocalAction)
[docs]
@ioprepped
@dataclass
class GetTokens(StoreLocalAction, name='get_tokens'):
"""Show the get-tokens window."""
[docs]
@ioprepped
@dataclass
class RestorePurchases(StoreLocalAction, name='restore_purchases'):
"""Kick off a platform purchase-restore."""
# All store local-actions.
AnyStoreLocalAction = GetTokens | RestorePurchases
[docs]
class InventoryLocalAction(DocUILocalActionBase):
"""Family class for classic inventory local-actions."""
[docs]
@override
@classmethod
def get_action_types(cls) -> tuple[type[DocUILocalActionBase], ...]:
return family_members(AnyInventoryLocalAction)
[docs]
@ioprepped
@dataclass
class NewProfile(InventoryLocalAction, name='new_profile'):
"""Open the (legacy) profile editor on a new profile."""
[docs]
@override
@classmethod
def get_press_sound(cls) -> PressSound:
return PressSound.SWISH
[docs]
@ioprepped
@dataclass
class EditProfile(InventoryLocalAction, name='edit_profile'):
"""Open the (legacy) profile editor on an existing profile."""
[docs]
@override
@classmethod
def get_press_sound(cls) -> PressSound:
return PressSound.SWISH
profile: str
[docs]
@ioprepped
@dataclass
class SpawnBot(InventoryLocalAction, name='spawn_bot'):
"""Spawn a character in the main-menu background."""
#: Internal appearance name of the character.
name: str
[docs]
@ioprepped
@dataclass
class ShowLegacyProfiles(InventoryLocalAction, name='show_legacy_profiles'):
"""Switch the inventory to the client's legacy profiles."""
[docs]
@ioprepped
@dataclass
class ShowCloudProfiles(InventoryLocalAction, name='show_cloud_profiles'):
"""Switch the inventory to cloud profiles."""
# All inventory local-actions.
AnyInventoryLocalAction = (
NewProfile | EditProfile | SpawnBot | ShowLegacyProfiles | ShowCloudProfiles
)
# 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, TypeIs)
import asyncio
from concurrent.futures import Future
from pathlib import Path
from enum import Enum