werewolves/werewolves/src/app/class.rs

167 lines
4.5 KiB
Rust

// Copyright (C) 2026 Emilis Bliūdžius
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use werewolves_proto::{
aura::AuraTitle, character::Character, game::Category, role::RoleTitle, team::Team,
};
pub trait PartialClass {
fn partial_class(&self) -> Option<&'static str>;
}
impl PartialClass for AuraTitle {
fn partial_class(&self) -> Option<&'static str> {
match self {
AuraTitle::Damned => Some("damned"),
AuraTitle::Drunk => Some("drunk"),
AuraTitle::Insane
| AuraTitle::Bloodlet
| AuraTitle::Scapegoat
| AuraTitle::RedeemableScapegoat
| AuraTitle::VindictiveScapegoat
| AuraTitle::SpitefulScapegoat
| AuraTitle::InevitableScapegoat => None,
}
}
}
pub trait Class {
fn class(&self) -> &'static str;
}
impl Class for Character {
fn class(&self) -> &'static str {
if let Team::AnyEvil = self.team() {
return "damned";
}
self.role_title().category().class()
}
}
impl Class for RoleTitle {
fn class(&self) -> &'static str {
self.category().class()
}
}
impl Class for Category {
fn class(&self) -> &'static str {
match self {
Category::Wolves => "wolves",
Category::Villager => "village",
Category::Intel => "intel",
Category::Defensive => "defensive",
Category::Offensive => "offensive",
Category::StartsAsVillager => "starts-as-villager",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Classes(Vec<String>);
impl<I> From<I> for Classes
where
I: Into<Vec<String>>,
{
fn from(value: I) -> Self {
Self(value.into())
}
}
impl core::fmt::Display for Classes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0.join(" ").as_str())
}
}
pub trait AsClasses {
fn as_classes(&self) -> Classes;
}
impl AsClasses for [&str] {
fn as_classes(&self) -> Classes {
Classes(self.iter().map(|s| s.to_string()).collect())
}
}
impl leptos::tachys::html::class::IntoClass for Classes {
type AsyncOutput = Self;
type State = (leptos::tachys::renderer::types::Element, Self);
type Cloneable = Self;
type CloneableOwned = Self;
fn html_len(&self) -> usize {
let len = self.0.len();
self.0.iter().map(|c| c.len()).sum::<usize>()
+ if len == 2 {
1
} else if len > 2 {
len - 1
} else {
0
}
}
fn to_html(self, class: &mut String) {
class.push_str(self.0.join(" ").as_str());
}
fn should_overwrite(&self) -> bool {
true
}
fn hydrate<const FROM_SERVER: bool>(
self,
el: &leptos::tachys::renderer::types::Element,
) -> Self::State {
if !FROM_SERVER {
leptos::tachys::renderer::Rndr::set_attribute(el, "class", self.to_string().as_str());
}
(el.clone(), self)
}
fn build(self, el: &leptos::tachys::renderer::types::Element) -> Self::State {
leptos::tachys::renderer::Rndr::set_attribute(el, "class", self.to_string().as_str());
(el.clone(), self)
}
fn rebuild(self, state: &mut Self::State) {
let (el, prev) = state;
if self != *prev {
leptos::tachys::renderer::Rndr::set_attribute(el, "class", self.to_string().as_str());
}
*prev = self;
}
fn into_cloneable(self) -> Self::Cloneable {
self
}
fn into_cloneable_owned(self) -> Self::CloneableOwned {
self.into()
}
fn dry_resolve(&mut self) {}
async fn resolve(self) -> Self::AsyncOutput {
self
}
fn reset(state: &mut Self::State) {
let (el, _prev) = state;
leptos::tachys::renderer::Rndr::remove_attribute(el, "class");
}
}