"""
Provide configuration for the Cotton Candy extension.
"""
from __future__ import annotations
import re
from typing import Self, Sequence, TYPE_CHECKING
from betty.assertion import (
assert_str,
assert_record,
OptionalField,
assert_path,
assert_setattr,
)
from betty.assertion.error import AssertionFailed
from betty.config import Configuration
from betty.locale.localizable import _
from betty.project import EntityReference, EntityReferenceSequence
from betty.serde.dump import Dump, VoidableDump, minimize
from betty.typing import Void
from typing_extensions import override
if TYPE_CHECKING:
from betty.model import UserFacingEntity, Entity
from pathlib import Path
[docs]
class ColorConfiguration(Configuration):
"""
Configure a color.
"""
_HEX_PATTERN = re.compile(r"^#[a-zA-Z0-9]{6}$")
[docs]
def __init__(self, hex_value: str):
super().__init__()
self._hex: str
self.hex = hex_value
def _assert_hex(self, hex_value: str) -> str:
if not self._HEX_PATTERN.match(hex_value):
raise AssertionFailed(
_(
'"{hex_value}" is not a valid hexadecimal color, such as #ffc0cb.'
).format(
hex_value=hex_value,
)
)
return hex_value
@property
def hex(self) -> str:
"""
The color's hexadecimal value.
"""
return self._hex
@hex.setter
def hex(self, hex_value: str) -> None:
self._assert_hex(hex_value)
self._hex = hex_value
[docs]
@override
def update(self, other: Self) -> None:
self.hex = other.hex
[docs]
@override
def load(self, dump: Dump) -> None:
self._hex = (assert_str() | self._assert_hex)(dump)
[docs]
@override
def dump(self) -> VoidableDump:
return self._hex
[docs]
class CottonCandyConfiguration(Configuration):
"""
Provide configuration for the :py:class:`betty.extension.cotton_candy.CottonCandy` extension.
"""
DEFAULT_PRIMARY_INACTIVE_COLOR = "#ffc0cb"
DEFAULT_PRIMARY_ACTIVE_COLOR = "#ff69b4"
DEFAULT_LINK_INACTIVE_COLOR = "#149988"
DEFAULT_LINK_ACTIVE_COLOR = "#2a615a"
[docs]
def __init__(
self,
*,
featured_entities: (
Sequence[EntityReference[UserFacingEntity & Entity]] | None
) = None,
primary_inactive_color: str = DEFAULT_PRIMARY_INACTIVE_COLOR,
primary_active_color: str = DEFAULT_PRIMARY_ACTIVE_COLOR,
link_inactive_color: str = DEFAULT_LINK_INACTIVE_COLOR,
link_active_color: str = DEFAULT_LINK_ACTIVE_COLOR,
logo: Path | None = None,
):
super().__init__()
self._featured_entities = EntityReferenceSequence["UserFacingEntity & Entity"](
featured_entities or ()
)
self._primary_inactive_color = ColorConfiguration(primary_inactive_color)
self._primary_active_color = ColorConfiguration(primary_active_color)
self._link_inactive_color = ColorConfiguration(link_inactive_color)
self._link_active_color = ColorConfiguration(link_active_color)
self._logo = logo
@property
def featured_entities(self) -> EntityReferenceSequence[UserFacingEntity & Entity]:
"""
The entities featured on the front page.
"""
return self._featured_entities
@property
def primary_inactive_color(self) -> ColorConfiguration:
"""
The color for inactive primary/CTA elements.
"""
return self._primary_inactive_color
@property
def primary_active_color(self) -> ColorConfiguration:
"""
The color for active primary/CTA elements.
"""
return self._primary_active_color
@property
def link_inactive_color(self) -> ColorConfiguration:
"""
The color for inactive hyperlinks.
"""
return self._link_inactive_color
@property
def link_active_color(self) -> ColorConfiguration:
"""
The color for active hyperlinks.
"""
return self._link_active_color
@property
def logo(self) -> Path | None:
"""
The path to the logo.
"""
return self._logo
@logo.setter
def logo(self, logo: Path | None) -> None:
self._logo = logo
[docs]
@override
def update(self, other: Self) -> None:
self.featured_entities.update(other.featured_entities)
self.primary_inactive_color.update(other.primary_inactive_color)
self.primary_active_color.update(other.primary_active_color)
self.link_inactive_color.update(other.link_inactive_color)
self.link_active_color.update(other.link_active_color)
self.logo = other.logo
[docs]
@override
def load(self, dump: Dump) -> None:
assert_record(
OptionalField("featured_entities", self.featured_entities.load),
OptionalField("primary_inactive_color", self.primary_inactive_color.load),
OptionalField("primary_active_color", self.primary_active_color.load),
OptionalField("link_inactive_color", self.link_inactive_color.load),
OptionalField("link_active_color", self.link_active_color.load),
OptionalField("logo", assert_path() | assert_setattr(self, "logo")),
)(dump)
[docs]
@override
def dump(self) -> VoidableDump:
return minimize(
{
"featured_entities": self.featured_entities.dump(),
"primary_inactive_color": self._primary_inactive_color.dump(),
"primary_active_color": self._primary_active_color.dump(),
"link_inactive_color": self._link_inactive_color.dump(),
"link_active_color": self._link_active_color.dump(),
"logo": str(self._logo) if self._logo else Void,
}
)