Feat: Decks page.

This commit is contained in:
2026-02-16 19:30:31 +00:00
parent 1cd9b7c976
commit 5661632540
75 changed files with 11850 additions and 536 deletions

View File

@@ -76,6 +76,7 @@ class Base():
FLAG_TEXT_COLOUR: ClassVar[str] = 'text_colour'
FLAG_URL: ClassVar[str] = 'url'
FLAG_USER: ClassVar[str] = 'authorisedUser' # 'user' already used
FLAG_VALUE: ClassVar[str] = 'value'
FLAG_VALUE_LOCAL_VAT_EXCL: ClassVar[str] = 'value_local_vat_excl'
FLAG_VALUE_LOCAL_VAT_INCL: ClassVar[str] = 'value_local_vat_incl'
FLAG_WEBSITE: ClassVar[str] = 'website'

View File

@@ -10,6 +10,7 @@ Feature: MTG Deck Business Object
# internal
from business_objects.base import Base
from business_objects.db_base import SQLAlchemy_ABC, Get_Many_Parameters_Base
from business_objects.tcg.mtg_deck_commander_bracket import MTG_Deck_Commander_Bracket
import lib.argument_validation as av
from extensions import db
from helpers.helper_app import Helper_App
@@ -24,6 +25,7 @@ class MTG_Deck(SQLAlchemy_ABC, Base):
ATTR_COMMANDER_BRACKET_ID: ClassVar[str] = 'commander_bracket_id'
FLAG_DECK: ClassVar[str] = 'deck'
FLAG_IS_COMMANDER: ClassVar[str] = 'is_commander'
FLAG_STATISTICS: ClassVar[str] = 'statistics'
NAME_ATTR_OPTION_VALUE: ClassVar[str] = ATTR_DECK_ID
NAME_ATTR_OPTION_TEXT: ClassVar[str] = Base.FLAG_NAME
@@ -41,9 +43,13 @@ class MTG_Deck(SQLAlchemy_ABC, Base):
updated_last_by_user_id = db.Column(db.Integer)
change_set_id = db.Column(db.Integer)
# commander_bracket: MTG_Deck_Commander_Bracket
def __init__(self):
self.deck_id = 0
self.is_new = False
self.commander_bracket = None
self.statistics = None
super().__init__()
@classmethod
@@ -83,6 +89,7 @@ class MTG_Deck(SQLAlchemy_ABC, Base):
, self.FLAG_ACTIVE: self.active
, self.FLAG_CREATED_ON: self.created_on
, Base.ATTR_USER_ID: self.created_by_user_id
, self.FLAG_STATISTICS: self.statistics
}
return as_json

View File

@@ -104,21 +104,19 @@ class Parameters_MTG_Deck_Commander_Bracket(Get_Many_Parameters_Base):
get_inactive_commander_bracket: bool
commander_bracket_ids: str
commander_bracket_names: str
user_ids: str
require_all_id_filters_met: bool
require_any_id_filters_met: bool
require_all_non_id_filters_met: bool
require_any_non_id_filters_met: bool
@classmethod
def get_default(cls, user_id_session):
def get_default(cls):
return cls(
get_all_commander_bracket = True
, get_inactive_commander_bracket = False
, commander_bracket_ids = ''
, commander_bracket_names = ''
, user_ids = str(user_id_session)
, require_all_id_filters_met = True
, require_all_id_filters_met = False
, require_any_id_filters_met = True
, require_all_non_id_filters_met = False
, require_any_non_id_filters_met = True
@@ -127,12 +125,11 @@ class Parameters_MTG_Deck_Commander_Bracket(Get_Many_Parameters_Base):
@classmethod
def from_json(cls, json):
return cls(
get_all_commander_bracket = json.get('a_get_all_commander_bracket', False)
get_all_commander_bracket = json.get('a_get_all_commander_bracket', True)
, get_inactive_commander_bracket = json.get('a_get_inactive_commander_bracket', False)
, commander_bracket_ids = json.get('a_commander_bracket_ids', '')
, commander_bracket_names = json.get('a_commander_bracket_names', '')
, user_ids = json.get('a_user_ids', '')
, require_all_id_filters_met = json.get('a_require_all_id_filters_met', True)
, require_all_id_filters_met = json.get('a_require_all_id_filters_met', False)
, require_any_id_filters_met = json.get('a_require_any_id_filters_met', True)
, require_all_non_id_filters_met = json.get('a_require_all_non_id_filters_met', False)
, require_any_non_id_filters_met = json.get('a_require_any_non_id_filters_met', True)
@@ -144,7 +141,6 @@ class Parameters_MTG_Deck_Commander_Bracket(Get_Many_Parameters_Base):
, 'a_get_inactive_commander_bracket': self.get_inactive_commander_bracket
, 'a_commander_bracket_ids': self.commander_bracket_ids
, 'a_commander_bracket_names': self.commander_bracket_names
, 'a_user_ids': self.user_ids
, 'a_require_all_id_filters_met': self.require_all_id_filters_met
, 'a_require_any_id_filters_met': self.require_any_id_filters_met
, 'a_require_all_non_id_filters_met': self.require_all_non_id_filters_met

View File

@@ -27,8 +27,9 @@ class MTG_Game_Round_Player_Damage(SQLAlchemy_ABC, Base):
ATTR_RECEIVED_FROM_COMMANDER_PLAYER_ID: ClassVar[str] = 'received_from_commander_player_id'
FLAG_COMMANDER_DEATHS: ClassVar[str] = 'commander_deaths'
FLAG_DAMAGE: ClassVar[str] = 'damage'
FLAG_HEALTH_CHANGE: ClassVar[str] = 'health_change'
FLAG_IS_ELIMINATED: ClassVar[str] = 'is_eliminated'
FLAG_LIFE_GAIN: ClassVar[str] = 'life_gain'
FLAG_LIFE_LOSS: ClassVar[str] = 'life_loss'
NAME_ATTR_OPTION_VALUE: ClassVar[str] = ATTR_DAMAGE_ID
NAME_ATTR_OPTION_TEXT: ClassVar[str] = ATTR_DAMAGE_ID
@@ -39,7 +40,8 @@ class MTG_Game_Round_Player_Damage(SQLAlchemy_ABC, Base):
round_id = db.Column(db.Integer)
player_id = db.Column(db.Integer)
received_from_commander_player_id = db.Column(db.Integer)
health_change = db.Column(db.Integer)
life_gain = db.Column(db.Integer)
life_loss = db.Column(db.Integer)
commander_deaths = db.Column(db.Integer)
is_eliminated = db.Column(db.Boolean)
active = db.Column(db.Boolean)
@@ -62,12 +64,13 @@ class MTG_Game_Round_Player_Damage(SQLAlchemy_ABC, Base):
damage.round_id = query_row[1]
damage.player_id = query_row[2]
damage.received_from_commander_player_id = query_row[3]
damage.health_change = query_row[4]
damage.commander_deaths = query_row[5]
damage.is_eliminated = av.input_bool(query_row[6], cls.FLAG_IS_ELIMINATED, _m)
damage.active = av.input_bool(query_row[7], cls.FLAG_ACTIVE, _m)
damage.created_on = query_row[8]
damage.created_by_user_id = query_row[9]
damage.life_gain = query_row[4]
damage.life_loss = query_row[5]
damage.commander_deaths = query_row[6]
damage.is_eliminated = av.input_bool(query_row[7], cls.FLAG_IS_ELIMINATED, _m)
damage.active = av.input_bool(query_row[8], cls.FLAG_ACTIVE, _m)
damage.created_on = query_row[9]
damage.created_by_user_id = query_row[10]
return damage
@classmethod
@@ -79,7 +82,8 @@ class MTG_Game_Round_Player_Damage(SQLAlchemy_ABC, Base):
damage.round_id = json.get(cls.ATTR_ROUND_ID, None)
damage.player_id = json.get(cls.ATTR_PLAYER_ID, None)
damage.received_from_commander_player_id = json.get(cls.ATTR_RECEIVED_FROM_COMMANDER_PLAYER_ID, None)
damage.health_change = json.get(cls.FLAG_HEALTH_CHANGE, 0)
damage.life_gain = json.get(cls.FLAG_LIFE_GAIN, 0)
damage.life_loss = json.get(cls.FLAG_LIFE_LOSS, 0)
damage.commander_deaths = json.get(cls.FLAG_COMMANDER_DEATHS, 0)
damage.is_eliminated = json.get(cls.FLAG_IS_ELIMINATED, False)
damage.active = av.input_bool(json.get(cls.FLAG_ACTIVE, True), cls.FLAG_ACTIVE, _m)
@@ -94,7 +98,8 @@ class MTG_Game_Round_Player_Damage(SQLAlchemy_ABC, Base):
, self.ATTR_ROUND_ID: self.round_id
, self.ATTR_PLAYER_ID: self.player_id
, self.ATTR_RECEIVED_FROM_COMMANDER_PLAYER_ID: self.received_from_commander_player_id
, self.FLAG_HEALTH_CHANGE: self.health_change
, self.FLAG_LIFE_GAIN: self.life_gain
, self.FLAG_LIFE_LOSS: self.life_loss
, self.FLAG_COMMANDER_DEATHS: self.commander_deaths
, self.FLAG_IS_ELIMINATED: self.is_eliminated
, self.FLAG_ACTIVE: self.active
@@ -110,7 +115,8 @@ class MTG_Game_Round_Player_Damage(SQLAlchemy_ABC, Base):
{self.ATTR_ROUND_ID}: {self.round_id}
{self.ATTR_PLAYER_ID}: {self.player_id}
{self.ATTR_RECEIVED_FROM_COMMANDER_PLAYER_ID}: {self.received_from_commander_player_id}
{self.FLAG_HEALTH_CHANGE}: {self.health_change}
{self.FLAG_LIFE_GAIN}: {self.life_gain}
{self.FLAG_LIFE_LOSS}: {self.life_loss}
{self.FLAG_COMMANDER_DEATHS}: {self.commander_deaths}
{self.FLAG_IS_ELIMINATED}: {self.is_eliminated}
{self.FLAG_ACTIVE}: {self.active}
@@ -127,7 +133,8 @@ class MTG_Game_Round_Player_Damage_Temp(db.Model, Base):
round_id = db.Column(db.Integer)
player_id = db.Column(db.Integer)
received_from_commander_player_id = db.Column(db.Integer)
health_change = db.Column(db.Integer)
life_gain = db.Column(db.Integer)
life_loss = db.Column(db.Integer)
commander_deaths = db.Column(db.Integer)
is_eliminated = db.Column(db.Boolean)
active = db.Column(db.Boolean)
@@ -145,7 +152,8 @@ class MTG_Game_Round_Player_Damage_Temp(db.Model, Base):
temp.round_id = damage.round_id
temp.player_id = damage.player_id
temp.received_from_commander_player_id = damage.received_from_commander_player_id
temp.health_change = damage.health_change
temp.life_gain = damage.life_gain
temp.life_loss = damage.life_loss
temp.commander_deaths = damage.commander_deaths
temp.is_eliminated = damage.is_eliminated
temp.active = damage.active

View File

@@ -0,0 +1,287 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: Business Objects
Feature: Statistic Business Object
"""
# internal
from business_objects.base import Base
from business_objects.db_base import SQLAlchemy_ABC, Get_Many_Parameters_Base
import lib.argument_validation as av
from extensions import db
from helpers.helper_app import Helper_App
# external
from dataclasses import dataclass
from typing import ClassVar
from sqlalchemy import Uuid, Interval
from sqlalchemy.types import Text, Boolean
class Statistic(SQLAlchemy_ABC, Base):
ATTR_ENTITY_RECORD_ID: ClassVar[str] = 'entity_record_id'
ATTR_STATISTIC_ID: ClassVar[str] = 'statistic_id'
FLAG_ENTITY_TYPE_CODE: ClassVar[str] = 'entity_type_code'
FLAG_IS_BOOL: ClassVar[str] = 'is_bool'
FLAG_IS_FLOAT: ClassVar[str] = 'is_float'
FLAG_IS_INTERVAL: ClassVar[str] = 'is_interval'
FLAG_IS_TEXT: ClassVar[str] = 'is_text'
FLAG_IS_TIMESTAMP: ClassVar[str] = 'is_timestamp'
FLAG_VALUE_BOOL: ClassVar[str] = 'value_bool'
FLAG_VALUE_FLOAT: ClassVar[str] = 'value_float'
FLAG_VALUE_INTERVAL: ClassVar[str] = 'value_interval'
FLAG_VALUE_TEXT: ClassVar[str] = 'value_text'
FLAG_VALUE_TIMESTAMP: ClassVar[str] = 'value_timestamp'
NAME_ATTR_OPTION_VALUE: ClassVar[str] = ATTR_STATISTIC_ID
NAME_ATTR_OPTION_TEXT: ClassVar[str] = Base.FLAG_NAME
__tablename__ = 'tcg_statistic'
__table_args__ = { 'extend_existing': True }
statistic_id = db.Column(db.Integer, primary_key=True)
entity_type_code = db.Column(db.Text) # replace with lookup table later
entity_record_id = db.Column(db.Text)
name = db.Column(db.Text)
value_bool = db.Column(db.Boolean)
value_float = db.Column(db.Float)
value_interval = db.Column(Interval)
value_text = db.Column(db.Text)
value_timestamp = db.Column(db.DateTime)
is_bool = db.Column(db.Boolean)
is_float = db.Column(db.Boolean)
is_interval = db.Column(db.Boolean)
is_text = db.Column(db.Boolean)
is_timestamp = db.Column(db.Boolean)
display_order = db.Column(db.Integer)
active = db.Column(db.Boolean)
created_on = db.Column(db.DateTime)
created_by_user_id = db.Column(db.Integer)
updated_last_on = db.Column(db.DateTime)
updated_last_by_user_id = db.Column(db.Integer)
change_set_id = db.Column(db.Integer)
def __init__(self):
self.statistic_id = 0
self.is_new = False
super().__init__()
@classmethod
def from_db_statistic(cls, query_row):
_m = f'{cls.__qualname__}.from_db_statistic'
statistic = cls()
statistic.statistic_id = query_row[0]
statistic.entity_type_code = query_row[1]
statistic.entity_record_id = query_row[2]
statistic.name = query_row[3]
statistic.value_bool = query_row[4]
statistic.value_float = query_row[5]
statistic.value_interval = query_row[6]
statistic.value_text = query_row[7]
statistic.value_timestamp = query_row[8]
statistic.is_bool = query_row[9]
statistic.is_float = query_row[10]
statistic.is_interval = query_row[11]
statistic.is_text = query_row[12]
statistic.is_timestamp = query_row[13]
statistic.display_order = query_row[14]
statistic.active = av.input_bool(query_row[15], cls.FLAG_ACTIVE, _m)
statistic.created_on = query_row[16]
statistic.created_by_user_id = query_row[17]
return statistic
@classmethod
def from_json(cls, json):
_m = f'{cls.__qualname__}.from_json'
statistic = cls()
if json is None: return statistic
statistic.statistic_id = json.get(cls.ATTR_STATISTIC_ID, -1)
statistic.entity_type_code = json.get(cls.FLAG_ENTITY_TYPE_CODE, None)
statistic.entity_record_id = json.get(cls.ATTR_ENTITY_RECORD_ID, None)
statistic.name = json.get(cls.FLAG_NAME, '')
statistic.value_bool = json.get(cls.FLAG_VALUE_BOOL, None)
statistic.value_float = json.get(cls.FLAG_VALUE_FLOAT, None)
statistic.value_interval = json.get(cls.FLAG_VALUE_INTERVAL, None)
statistic.value_text = json.get(cls.FLAG_VALUE_TEXT, None)
statistic.value_timestamp = json.get(cls.FLAG_VALUE_TIMESTAMP, None)
statistic.is_bool = json.get(cls.FLAG_IS_BOOL, False)
statistic.is_float = json.get(cls.FLAG_IS_FLOAT, False)
statistic.is_interval = json.get(cls.FLAG_IS_INTERVAL, False)
statistic.is_text = json.get(cls.FLAG_IS_TEXT, False)
statistic.is_timestamp = json.get(cls.FLAG_IS_TIMESTAMP, False)
statistic.display_order = json.get(cls.FLAG_DISPLAY_ORDER, -1)
statistic.active = av.input_bool(json.get(cls.FLAG_ACTIVE, True), cls.FLAG_ACTIVE, _m)
statistic.created_on = json.get(cls.FLAG_CREATED_ON, None)
statistic.created_by_user_id = json.get(Base.ATTR_USER_ID, None)
return statistic
def to_json(self):
as_json = {
**self.get_shared_json_attributes(self)
, self.ATTR_STATISTIC_ID: self.statistic_id
, self.FLAG_ENTITY_TYPE_CODE: self.entity_type_code
, self.ATTR_ENTITY_RECORD_ID: self.entity_record_id
, self.FLAG_NAME: self.name
, self.FLAG_VALUE_BOOL: self.value_bool
, self.FLAG_VALUE_FLOAT: self.value_float
, self.FLAG_VALUE_INTERVAL: self.value_interval
, self.FLAG_VALUE_TEXT: self.value_text
, self.FLAG_VALUE_TIMESTAMP: self.value_timestamp
, self.FLAG_IS_BOOL: self.is_bool
, self.FLAG_IS_FLOAT: self.is_float
, self.FLAG_IS_INTERVAL: self.is_interval
, self.FLAG_IS_TEXT: self.is_text
, self.FLAG_IS_TIMESTAMP: self.is_timestamp
, self.FLAG_DISPLAY_ORDER: self.display_order
, self.FLAG_ACTIVE: self.active
, self.FLAG_CREATED_ON: self.created_on
, Base.ATTR_USER_ID: self.created_by_user_id
}
return as_json
def __repr__(self):
return f'''
{self.__class__.__name__}(
{self.ATTR_STATISTIC_ID}: {self.statistic_id}
{self.FLAG_ENTITY_TYPE_CODE}: {self.entity_type_code}
{self.ATTR_ENTITY_RECORD_ID}: {self.entity_record_id}
{self.FLAG_NAME}: {self.name}
{self.FLAG_VALUE_BOOL}: {self.value_bool}
{self.FLAG_VALUE_FLOAT}: {self.value_float}
{self.FLAG_VALUE_INTERVAL}: {self.value_interval}
{self.FLAG_VALUE_TEXT}: {self.value_text}
{self.FLAG_VALUE_TIMESTAMP}: {self.value_timestamp}
{self.FLAG_IS_BOOL}: {self.is_bool}
{self.FLAG_IS_FLOAT}: {self.is_float}
{self.FLAG_IS_INTERVAL}: {self.is_interval}
{self.FLAG_IS_TEXT}: {self.is_text}
{self.FLAG_IS_TIMESTAMP}: {self.is_timestamp}
{self.FLAG_DISPLAY_ORDER}: {self.display_order}
{self.FLAG_ACTIVE}: {self.active}
{self.FLAG_CREATED_ON}: {self.created_on}
{Base.ATTR_USER_ID}: {self.created_by_user_id}
)
'''
def get_formatted_value(self):
if self.is_bool:
return self.value_bool
elif self.is_float:
return self.value_float
elif self.is_interval:
return self.value_interval
elif self.is_text:
return self.value_text
elif self.is_timestamp:
return self.value_timestamp
else:
return None
class Statistic_Temp(db.Model, Base):
__tablename__ = 'tcg_statistic_temp'
__table_args__ = { 'extend_existing': True }
temp_id = db.Column(db.Integer, primary_key=True)
statistic_id = db.Column(db.Integer)
entity_type_code = db.Column(db.Text)
entity_record_id = db.Column(db.Integer)
name = db.Column(db.Integer)
value_bool = db.Column(db.Boolean)
value_float = db.Column(db.Float)
value_interval = db.Column(Interval)
value_text = db.Column(db.Text)
value_timestamp = db.Column(db.DateTime)
is_bool = db.Column(db.Boolean)
is_float = db.Column(db.Boolean)
is_interval = db.Column(db.Boolean)
is_text = db.Column(db.Boolean)
is_timestamp = db.Column(db.Boolean)
display_order = db.Column(db.Integer)
active = db.Column(db.Boolean)
created_on = db.Column(db.DateTime)
guid = db.Column(Uuid)
def __init__(self):
self.entity_record_name = None
super().__init__()
@classmethod
def from_statistic(cls, statistic, guid):
_m = 'Statistic_Temp.from_statistic'
temp = cls()
temp.statistic_id = statistic.statistic_id
temp.entity_type_code = statistic.entity_type_code
temp.entity_record_id = statistic.entity_record_id
temp.name = statistic.name
temp.value_bool = statistic.value_bool
temp.value_float = statistic.value_float
temp.value_interval = statistic.value_interval
temp.value_text = statistic.value_text
temp.value_timestamp = statistic.value_timestamp
temp.is_bool = statistic.is_bool
temp.is_float = statistic.is_float
temp.is_interval = statistic.is_interval
temp.is_text = statistic.is_text
temp.is_timestamp = statistic.is_timestamp
temp.display_order = statistic.display_order
temp.active = statistic.active
temp.created_on = statistic.created_on
temp.guid = guid
return temp
class Parameters_Statistic(Get_Many_Parameters_Base):
get_all_statistic: bool
get_inactive_statistic: bool
statistic_ids: str
entity_type_codes: str
entity_record_ids: str
require_all_id_filters_met: bool
require_any_id_filters_met: bool
@classmethod
def get_default(cls):
return cls(
get_all_statistic = True
, get_inactive_statistic = False
, statistic_ids = ''
, entity_type_codes = ''
, entity_record_ids = ''
, require_all_id_filters_met = True
, require_any_id_filters_met = True
)
@classmethod
def from_json(cls, json):
return cls(
get_all_statistic = json.get('a_get_all_statistic', False)
, get_inactive_statistic = json.get('a_get_inactive_statistic', False)
, statistic_ids = json.get('a_statistic_ids', '')
, entity_type_codes = json.get('a_entity_type_codes', '')
, entity_record_ids = json.get('a_entity_record_ids', '')
, require_all_id_filters_met = json.get('a_require_all_id_filters_met', True)
, require_any_id_filters_met = json.get('a_require_any_id_filters_met', True)
)
def to_json(self):
return {
'a_get_all_statistic': self.get_all_statistic
, 'a_get_inactive_statistic': self.get_inactive_statistic
, 'a_statistic_ids': self.statistic_ids
, 'a_entity_type_codes': self.entity_type_codes
, 'a_entity_record_ids': self.entity_record_ids
, 'a_require_all_id_filters_met': self.require_all_id_filters_met
, 'a_require_any_id_filters_met': self.require_any_id_filters_met
}
@staticmethod
def get_type_hints():
return {
'a_get_all_statistic': Boolean
, 'a_get_inactive_statistic': Boolean
, 'a_statistic_ids': Text
, 'a_entity_type_codes': Text
, 'a_entity_record_ids': Text
, 'a_require_all_id_filters_met': Boolean
, 'a_require_any_id_filters_met': Boolean
}

View File

@@ -13,6 +13,7 @@ MTG Game Page Controller.
# IMPORTS
# internal
from business_objects.api import API
from business_objects.tcg.mtg_deck import MTG_Deck
from business_objects.tcg.mtg_game import MTG_Game
from business_objects.tcg.mtg_game_player import MTG_Game_Player, Parameters_MTG_Game_Player
from business_objects.tcg.mtg_game_round import MTG_Game_Round, Parameters_MTG_Game_Round
@@ -21,9 +22,10 @@ from business_objects.tcg.mtg_deck import MTG_Deck, Parameters_MTG_Deck
from business_objects.tcg.user import User, Parameters_User
from datastores.datastore_mtg import DataStore_MTG
from datastores.datastore_user import DataStore_User
from forms.tcg.game import Filters_MTG_Game
from forms.tcg.game import Filters_MTG_Game, Filters_MTG_Deck
from helpers.helper_app import Helper_App
from models.model_view_mtg_base import Model_View_MTG_Base
from models.model_view_mtg_decks import Model_View_MTG_Decks
from models.model_view_mtg_game import Model_View_MTG_Game
from models.model_view_mtg_games import Model_View_MTG_Games
from models.model_view_mtg_home import Model_View_MTG_Home
@@ -373,3 +375,28 @@ def save_game_round_player_damage():
Model_View_MTG_Base.FLAG_STATUS: Model_View_MTG_Base.FLAG_FAILURE
, Model_View_MTG_Base.FLAG_MESSAGE: f'Bad data received by controller.\n{e}'
})
@routes_mtg_game.route(Model_View_MTG_Base.HASH_PAGE_MTG_DECKS, methods=['GET'])
def decks():
Helper_App.console_log('mtg decks')
Helper_App.console_log(f'request_args: {request.args}')
try:
form_filters = Filters_MTG_Deck.from_json(request.args)
except Exception as e:
Helper_App.console_log(f'Error: {e}')
form_filters = Filters_MTG_Deck()
Helper_App.console_log(f'form_filters={form_filters}')
datastore = DataStore_MTG()
user_session = datastore.get_user_session()
if not user_session.get_is_logged_in():
return redirect(url_for('routes_mtg_game.home'))
parameters_deck = form_filters.to_parameters()
model = Model_View_MTG_Decks(parameters_deck = parameters_deck)
model.form_filters = form_filters
Helper_App.console_log(f'form_filters={form_filters}')
return render_template('pages/tcg/mtg/_decks.html', model=model)

View File

@@ -12,11 +12,13 @@ Datastore for MTG game data
# internal
import lib.argument_validation as av
from business_objects.tcg.mtg_deck import MTG_Deck, Parameters_MTG_Deck
from business_objects.tcg.mtg_deck_commander_bracket import MTG_Deck_Commander_Bracket, Parameters_MTG_Deck_Commander_Bracket
from business_objects.tcg.mtg_game import MTG_Game, Parameters_MTG_Game
from business_objects.tcg.mtg_game_player import MTG_Game_Player, Parameters_MTG_Game_Player, MTG_Game_Player_Temp
from business_objects.tcg.mtg_game_round import MTG_Game_Round, Parameters_MTG_Game_Round, MTG_Game_Round_Temp
from business_objects.tcg.mtg_game_round_player_damage import MTG_Game_Round_Player_Damage, Parameters_MTG_Game_Round_Player_Damage, MTG_Game_Round_Player_Damage_Temp
from business_objects.tcg.mtg_deck import MTG_Deck, Parameters_MTG_Deck
from business_objects.tcg.statistic import Statistic, Statistic_Temp, Parameters_Statistic
from business_objects.sql_error import SQL_Error, Parameters_SQL_Error
from datastores.datastore_base import DataStore_Base
from helpers.helper_app import Helper_App
@@ -339,3 +341,55 @@ class DataStore_MTG(DataStore_Base):
cls.clear_error(guid = guid)
return success, errors
@classmethod
def get_many_mtg_deck_commander_bracket(cls, parameters_commander_bracket):
_m = f'{cls.__qualname__}.get_many_mtg_deck_commander_bracket'
argument_dict = parameters_commander_bracket.to_json()
argument_types = Parameters_MTG_Deck.get_type_hints()
Helper_App.console_log(f'{_m}\nargument_dict: {argument_dict}')
commander_brackets = []
errors = []
try:
result = cls.db_function_execute('tcg.public.FN_TCG_MTG_Deck_Commander_Bracket_Get_Many', argument_dict, argument_types)
result_set = result.fetchall()
Helper_App.console_log(f'raw commander brackets: {result_set}')
for row in result_set:
new_commander_bracket = MTG_Deck_Commander_Bracket.from_db_mtg_deck_commander_bracket(row)
commander_brackets.append(new_commander_bracket)
Helper_App.console_log(f'commander bracket {str(type(new_commander_bracket))}: {new_commander_bracket}')
except Exception as e:
Helper_App.console_log(f'Error: {str(e)}')
error = SQL_Error()
error.msg = str(e)
errors.append(error)
return commander_brackets, errors
@classmethod
def get_many_statistic(cls, parameters_statistic):
_m = f'{cls.__qualname__}.get_many_statistic'
argument_dict = parameters_statistic.to_json()
argument_types = Parameters_MTG_Deck.get_type_hints()
Helper_App.console_log(f'{_m}\nargument_dict: {argument_dict}')
statistics = []
errors = []
try:
result = cls.db_function_execute('tcg.public.FN_TCG_Statistic_Get_Many', argument_dict, argument_types)
result_set = result.fetchall()
Helper_App.console_log(f'raw commander brackets: {result_set}')
for row in result_set:
new_statistic = Statistic.from_db_statistic(row)
statistics.append(new_statistic)
Helper_App.console_log(f'statistic {str(type(new_statistic))}: {new_statistic}')
except Exception as e:
Helper_App.console_log(f'Error: {str(e)}')
error = SQL_Error()
error.msg = str(e)
errors.append(error)
return statistics, errors

View File

@@ -170,15 +170,15 @@ class Filters_MTG_Deck(Form_Base):
, MTG_Deck.FLAG_IS_COMMANDER: self.is_commander.data
}
def to_parameters(self, user_id_session):
def to_parameters(self):
return Parameters_MTG_Deck(
get_all_deck = True
, get_inactive_deck = not self.active_only.data
, deck_ids = ''
, deck_names = ''
, deck_names = self.search.data
, include_commander_option = True
, commander_bracket_ids = ''
, user_ids = str(user_id_session) if user_id_session else ''
, require_all_id_filters_met = True
, require_all_id_filters_met = False
, require_any_id_filters_met = True
, require_all_non_id_filters_met = False
, require_any_non_id_filters_met = True

View File

@@ -51,6 +51,7 @@ class Model_View_Base(BaseModel, ABC):
COLOUR_TEXT_LINK_VISITED: ClassVar[str] = '#551A8B'
COMPANY_ADDRESS_SHORT: ClassVar[str] = 'Russet, Sawbridge Road, Grandborough, United Kingdom, CV23 8DN'
COMPANY_NUMBER: ClassVar[str] = '13587499'
DECK_ENTITY_TYPE_CODE: ClassVar[str] = 'deck'
ENDPOINT_GET_ALTCHA_CHALLENGE: ClassVar[str] = 'routes_core_contact.create_altcha_challenge'
ENDPOINT_PAGE_ACCESSIBILITY_REPORT: ClassVar[str] = 'routes_legal.accessibility_report'
ENDPOINT_PAGE_ACCESSIBILITY_STATEMENT: ClassVar[str] = 'routes_legal.accessibility_statement'
@@ -162,6 +163,7 @@ class Model_View_Base(BaseModel, ABC):
FLAG_TEMPORARY_ELEMENT: ClassVar[str] = 'temporary-element'
FLAG_TESTIMONIAL: ClassVar[str] = 'testimonial'
FLAG_USER: ClassVar[str] = User.FLAG_USER
FLAG_VALUE: ClassVar[str] = Base.FLAG_VALUE
# FLAG_VALUE_PROPOSITION: ClassVar[str] = 'value-proposition'
FLAG_WEBSITE: ClassVar[str] = Base.FLAG_WEBSITE
HASH_GET_ALTCHA_CHALLENGE: ClassVar[str] = '/altcha/create-challenge'
@@ -222,6 +224,8 @@ class Model_View_Base(BaseModel, ABC):
URL_REDDIT: ClassVar[str] = f'https://www.reddit.com/u/{USERNAME_REDDIT}/s/gZKEz2ZwHN'
URL_TIKTOK: ClassVar[str] = f'https://www.tiktok.com/@{USERNAME_TIKTOK}'
URL_TWITTER: ClassVar[str] = f'https://x.com/{USERNAME_TWITTER}'
USER_ENTITY_TYPE_CODE: ClassVar[str] = 'user'
USER_DECK_LINK_ENTITY_TYPE_CODE: ClassVar[str] = 'user_deck_link'
_title: str
hash_page_current: str

View File

@@ -12,11 +12,13 @@ Parent data model for MTG views
# internal
from business_objects.tcg.mtg_deck import MTG_Deck, Parameters_MTG_Deck
from business_objects.tcg.mtg_deck_commander_bracket import MTG_Deck_Commander_Bracket
from business_objects.tcg.mtg_game import MTG_Game, Parameters_MTG_Game
from business_objects.tcg.mtg_game_player import MTG_Game_Player, Parameters_MTG_Game_Player
from business_objects.tcg.mtg_game_round import MTG_Game_Round, Parameters_MTG_Game_Round
from business_objects.tcg.mtg_game_round_player_damage import MTG_Game_Round_Player_Damage, Parameters_MTG_Game_Round_Player_Damage
from business_objects.tcg.mtg_deck import MTG_Deck, Parameters_MTG_Deck
from business_objects.tcg.statistic import Statistic
from helpers.helper_app import Helper_App
import lib.argument_validation as av
from models.model_view_base import Model_View_Base
@@ -29,31 +31,45 @@ from abc import abstractmethod
class Model_View_MTG_Base(Model_View_Base):
ATTR_COMMANDER_BRACKET_ID: ClassVar[str] = MTG_Deck_Commander_Bracket.ATTR_COMMANDER_BRACKET_ID
ATTR_DAMAGE_ID: ClassVar[str] = MTG_Game_Round_Player_Damage.ATTR_DAMAGE_ID
ATTR_DECK_ID: ClassVar[str] = MTG_Deck.ATTR_DECK_ID
ATTR_ENTITY_RECORD_ID: ClassVar[str] = Statistic.ATTR_ENTITY_RECORD_ID
ATTR_GAME_ID: ClassVar[str] = MTG_Game.ATTR_GAME_ID
ATTR_PLAYER_ID: ClassVar[str] = MTG_Game_Player.ATTR_PLAYER_ID
ATTR_ROUND_ID: ClassVar[str] = MTG_Game_Round.ATTR_ROUND_ID
ATTR_DAMAGE_ID: ClassVar[str] = MTG_Game_Round_Player_Damage.ATTR_DAMAGE_ID
ATTR_DECK_ID: ClassVar[str] = MTG_Deck.ATTR_DECK_ID
FLAG_COMMANDER_DEATHS: ClassVar[str] = MTG_Game_Round_Player_Damage.FLAG_COMMANDER_DEATHS
FLAG_DAMAGE: ClassVar[str] = MTG_Game_Round_Player_Damage.FLAG_DAMAGE
FLAG_DECK: ClassVar[str] = MTG_Deck.FLAG_DECK
FLAG_ENTITY_TYPE_CODE: ClassVar[str] = Statistic.FLAG_ENTITY_TYPE_CODE
FLAG_GAME: ClassVar[str] = MTG_Game.FLAG_GAME
FLAG_HEALTH_CHANGE: ClassVar[str] = MTG_Game_Round_Player_Damage.FLAG_HEALTH_CHANGE
FLAG_IS_BOOL: ClassVar[str] = Statistic.FLAG_IS_BOOL
FLAG_IS_FLOAT: ClassVar[str] = Statistic.FLAG_IS_FLOAT
FLAG_IS_INTERVAL: ClassVar[str] = Statistic.FLAG_IS_INTERVAL
FLAG_IS_TEXT: ClassVar[str] = Statistic.FLAG_IS_TEXT
FLAG_IS_TIMESTAMP: ClassVar[str] = Statistic.FLAG_IS_TIMESTAMP
FLAG_IS_COMMANDER: ClassVar[str] = MTG_Game.FLAG_IS_COMMANDER
FLAG_IS_DRAFT: ClassVar[str] = MTG_Game.FLAG_IS_DRAFT
FLAG_IS_SEALED: ClassVar[str] = MTG_Game.FLAG_IS_SEALED
FLAG_LIFE_GAIN: ClassVar[str] = MTG_Game_Round_Player_Damage.FLAG_LIFE_GAIN
FLAG_LIFE_LOSS: ClassVar[str] = MTG_Game_Round_Player_Damage.FLAG_LIFE_LOSS
FLAG_LOCATION_NAME: ClassVar[str] = MTG_Game.FLAG_LOCATION_NAME
FLAG_PLAYER: ClassVar[str] = MTG_Game_Player.FLAG_PLAYER
FLAG_ROUND: ClassVar[str] = MTG_Game_Round.FLAG_ROUND
FLAG_STARTING_LIFE: ClassVar[str] = MTG_Game.FLAG_STARTING_LIFE
FLAG_VALUE_BOOL: ClassVar[str] = Statistic.FLAG_VALUE_BOOL
FLAG_VALUE_FLOAT: ClassVar[str] = Statistic.FLAG_VALUE_FLOAT
FLAG_VALUE_INTERVAL: ClassVar[str] = Statistic.FLAG_VALUE_INTERVAL
FLAG_VALUE_TEXT: ClassVar[str] = Statistic.FLAG_VALUE_TEXT
FLAG_VALUE_TIMESTAMP: ClassVar[str] = Statistic.FLAG_VALUE_TIMESTAMP
HASH_GET_MTG_GAME_DAMAGE_RECORDS: ClassVar[str] = '/mtg/api/game/<game_id>/damage-records'
HASH_GET_MTG_GAME_PLAYERS: ClassVar[str] = '/mtg/api/game/<game_id>/players'
HASH_GET_MTG_GAME_ROUNDS: ClassVar[str] = '/mtg/api/game/<game_id>/rounds'
HASH_GET_MTG_GAME_DAMAGE_RECORDS: ClassVar[str] = '/mtg/api/game/<game_id>/damage-records'
HASH_SAVE_MTG_DECK: ClassVar[str] = '/mtg/save-deck'
HASH_SAVE_MTG_GAME: ClassVar[str] = '/mtg/save-game'
HASH_SAVE_MTG_GAME_PLAYER: ClassVar[str] = '/mtg/save-game-player'
HASH_SAVE_MTG_GAME_ROUND: ClassVar[str] = '/mtg/save-game-round'
HASH_SAVE_MTG_GAME_ROUND_PLAYER_DAMAGE: ClassVar[str] = '/mtg/save-game-round-player-damage'
HASH_SAVE_MTG_DECK: ClassVar[str] = '/mtg/save-deck'
is_page_mtg: bool = True

View File

@@ -0,0 +1,85 @@
"""
Project: PARTS Website
Author: Edward Middleton-Smith
Precision And Research Technology Systems Limited
Technology: View Models
Feature: MTG Decks View Model
Description:
Data model for MTG decks view
"""
# internal
from business_objects.tcg.mtg_deck_commander_bracket import MTG_Deck_Commander_Bracket, Parameters_MTG_Deck_Commander_Bracket
from business_objects.tcg.mtg_deck import MTG_Deck, Parameters_MTG_Deck
from business_objects.tcg.statistic import Statistic, Parameters_Statistic
from datastores.datastore_mtg import DataStore_MTG
from models.model_view_mtg_base import Model_View_MTG_Base
from helpers.helper_app import Helper_App
import lib.argument_validation as av
# external
from pydantic import BaseModel
from typing import ClassVar
class Model_View_MTG_Decks(Model_View_MTG_Base):
FLAG_STATISTICS: ClassVar[str] = 'statistics'
commander_brackets: list = None
decks: list = None
form_filters: object = None
parameters_deck: Parameters_MTG_Deck = None
statistics: list = None
def __init__(self, parameters_deck=None, hash_page_current=Model_View_MTG_Base.HASH_PAGE_MTG_DECKS):
_m = 'Model_View_MTG_Decks.__init__'
Helper_App.console_log(f'{_m}\nstarting...')
super().__init__(hash_page_current=hash_page_current)
self._title = 'MTG Decks'
datastore = DataStore_MTG()
# Get all decks
parameters_deck = Parameters_MTG_Deck.get_default()
parameters_deck.get_all_deck = True
parameters_deck.require_all_id_filters_met = False
parameters_deck.require_any_id_filters_met = False
parameters_deck.require_all_non_id_filters_met = False
parameters_deck.require_any_non_id_filters_met = False
self.decks, errors = datastore.get_many_mtg_deck(parameters_deck)
Helper_App.console_log(f'Decks IDs: {self.decks}')
# Get all commander brackets
parameters_commander_bracket = Parameters_MTG_Deck_Commander_Bracket.get_default()
parameters_commander_bracket.get_all_commander_bracket = True
parameters_commander_bracket.require_all_id_filters_met = False
parameters_commander_bracket.require_any_id_filters_met = False
self.commander_brackets, errors = datastore.get_many_mtg_deck_commander_bracket(parameters_commander_bracket = parameters_commander_bracket)
Helper_App.console_log(f'Brackets: {self.commander_brackets}')
commander_bracket_index = {}
for index_commander_bracket in range(len(self.commander_brackets)):
bracket = self.commander_brackets[index_commander_bracket]
commander_bracket_index[bracket.commander_bracket_id] = bracket
Helper_App.console_log(f'Bracket IDs: {commander_bracket_index}')
deck_index = {}
for index_deck in range(len(self.decks)):
bracket = commander_bracket_index[self.decks[index_deck].commander_bracket_id]
self.decks[index_deck].commander_bracket = bracket
deck_index[self.decks[index_deck].deck_id] = self.decks[index_deck]
# Get all statistics
parameters_statistic = Parameters_Statistic.get_default()
parameters_statistic.get_all_statistic = False
parameters_statistic.entity_type_codes = Model_View_MTG_Decks.DECK_ENTITY_TYPE_CODE
parameters_statistic.entity_record_ids = ','.join([str(d.deck_id) for d in self.decks])
parameters_statistic.require_all_id_filters_met = True
parameters_statistic.require_any_id_filters_met = True
self.statistics, errors = datastore.get_many_statistic(parameters_statistic = parameters_statistic)
for index_statistic in range(len(self.statistics)):
deck = deck_index[int(self.statistics[index_statistic].entity_record_id)]
self.statistics[index_statistic].entity_record_name = deck.name

View File

@@ -8,7 +8,7 @@ CREATE TABLE tcg.public.TCG_MTG_Game (
, location_name TEXT
, start_on TIMESTAMP
, end_on TIMESTAMP
, starting_life INT
, starting_life INT NOT NULL
, active BOOLEAN NOT NULL DEFAULT TRUE
, created_on TIMESTAMP NOT NULL
, created_by_user_id INT NOT NULL

View File

@@ -1,3 +1,4 @@
CREATE TABLE tcg.public.TCG_MTG_Game_Round_Player_Damage (
damage_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
, round_id INT NOT NULL
@@ -12,7 +13,8 @@ CREATE TABLE tcg.public.TCG_MTG_Game_Round_Player_Damage (
, CONSTRAINT FK_TCG_MTG_Game_Round_Player_Damage_received_from_commander_player_id
FOREIGN KEY (received_from_commander_player_id)
REFERENCES tcg.public.TCG_MTG_Game_Player(player_id)
, health_change INT NOT NULL
, life_gain INT NOT NULL
, life_loss INT NOT NULL
, commander_deaths INT NOT NULL
, is_eliminated BOOLEAN NOT NULL DEFAULT FALSE
, active BOOLEAN NOT NULL DEFAULT TRUE

View File

@@ -6,8 +6,10 @@ CREATE TABLE tcg.public.TCG_MTG_Game_Round_Player_Damage_Temp (
, round_id INT
, player_id INT
, received_from_commander_player_id INT
, health_change INT
, life_gain INT
, life_loss INT
, commander_deaths INT
, is_eliminated BOOLEAN
, active BOOLEAN
, created_on TIMESTAMP
, created_by_user_id INT

View File

@@ -0,0 +1,32 @@
CREATE TABLE tcg.public.TCG_Statistic (
statistic_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
, entity_type_code TEXT NOT NULL
, entity_record_id TEXT NOT NULL
, name TEXT NOT NULL
, value_bool BOOLEAN
, value_float DOUBLE PRECISION
, value_interval INTERVAL
, value_text TEXT
, value_timestamp TIMESTAMP
, is_bool BOOLEAN
, is_float BOOLEAN
, is_interval BOOLEAN
, is_text BOOLEAN
, is_timestamp BOOLEAN
, display_order INT NOT NULL
, active BOOLEAN NOT NULL DEFAULT TRUE
, created_on TIMESTAMP NOT NULL
, created_by_user_id INT NOT NULL
, CONSTRAINT FK_TCG_Statistic_created_by_user_id
FOREIGN KEY (created_by_user_id)
REFERENCES tcg.public.TCG_User(user_id)
, updated_last_on TIMESTAMP NOT NULL
, updated_last_by_user_id INT NOT NULL
, CONSTRAINT FK_TCG_Statistic_updated_last_by_user_id
FOREIGN KEY (updated_last_by_user_id)
REFERENCES tcg.public.TCG_User(user_id)
, change_set_id INT NOT NULL
, CONSTRAINT FK_TCG_Statistic_change_set_id
FOREIGN KEY (change_set_id)
REFERENCES tcg.public.TCG_Change_Set(change_set_id)
);

View File

@@ -0,0 +1,15 @@
CREATE TABLE tcg.public.TCG_Statistic_Audit (
audit_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
, statistic_id INT NOT NULL
, CONSTRAINT FK_TCG_Statistic_Audit_statistic_id
FOREIGN KEY (statistic_id)
REFERENCES tcg.public.TCG_Statistic(statistic_id)
, name_field TEXT NOT NULL
, value_prev TEXT
, value_new TEXT
, change_set_id INT NOT NULL
, CONSTRAINT FK_TCG_Statistic_Audit_change_set_id
FOREIGN KEY (change_set_id)
REFERENCES tcg.public.TCG_Change_Set(change_set_id)
);

View File

@@ -58,9 +58,13 @@ BEGIN
SELECT NEW.damage_id, 'received_from_commander_player_id', OLD.received_from_commander_player_id::TEXT, NEW.received_from_commander_player_id::TEXT, NEW.change_set_id
WHERE OLD.received_from_commander_player_id IS NOT DISTINCT FROM NEW.received_from_commander_player_id
UNION
-- Changed health_change
SELECT NEW.damage_id, 'health_change', OLD.health_change::TEXT, NEW.health_change::TEXT, NEW.change_set_id
WHERE OLD.health_change IS NOT DISTINCT FROM NEW.health_change
-- Changed life_gain
SELECT NEW.damage_id, 'life_gain', OLD.life_gain::TEXT, NEW.life_gain::TEXT, NEW.change_set_id
WHERE OLD.life_gain IS NOT DISTINCT FROM NEW.life_gain
UNION
-- Changed life_loss
SELECT NEW.damage_id, 'life_loss', OLD.life_loss::TEXT, NEW.life_loss::TEXT, NEW.change_set_id
WHERE OLD.life_loss IS NOT DISTINCT FROM NEW.life_loss
UNION
-- Changed commander_deaths
SELECT NEW.damage_id, 'commander_deaths', OLD.commander_deaths::TEXT, NEW.commander_deaths::TEXT, NEW.change_set_id

View File

@@ -0,0 +1,96 @@
CREATE OR REPLACE FUNCTION tcg.public.FN_before_insert_TCG_Statistic()
RETURNS TRIGGER AS $$
DECLARE
r_change_set RECORD;
BEGIN
NEW.created_on = COALESCE(NEW.created_on, CURRENT_TIMESTAMP);
NEW.updated_last_on = COALESCE(NEW.updated_last_on, CURRENT_TIMESTAMP);
IF NEW.change_set_id IS NULL THEN
RAISE EXCEPTION 'Change Set ID must be provided.';
END IF;
SELECT *
INTO r_change_set
FROM tcg.public.TCG_Change_Set CHANGE_SET
WHERE NEW.change_set_id = CHANGE_SET.change_set_id
;
IF FOUND THEN
NEW.created_by_user_id := COALESCE(NEW.created_by_user_id, r_change_set.updated_last_by_user_id);
NEW.updated_last_by_user_id := COALESCE(NEW.updated_last_by_user_id, r_change_set.updated_last_by_user_id);
NEW.created_on := COALESCE(NEW.created_on, r_change_set.updated_last_on);
ELSE
RAISE EXCEPTION 'Change Set % not found.', NEW.change_set_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql
;
CREATE OR REPLACE FUNCTION tcg.public.FN_before_update_TCG_Statistic()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_last_on = CURRENT_TIMESTAMP;
IF OLD.change_set_id IS NOT DISTINCT FROM NEW.change_set_id THEN
RAISE EXCEPTION 'New Change Set ID must be provided.';
END IF;
INSERT INTO tcg.public.TCG_Statistic_Audit (
statistic_id
, name_field
, value_prev
, value_new
, change_set_id
)
-- Changed entity_type_code
SELECT NEW.statistic_id, 'entity_type_code', OLD.entity_type_code, NEW.entity_type_code, NEW.change_set_id
WHERE OLD.entity_type_code IS NOT DISTINCT FROM NEW.entity_type_code
UNION
-- Changed entity_record_id
SELECT NEW.statistic_id, 'entity_record_id', OLD.entity_record_id::TEXT, NEW.entity_record_id::TEXT, NEW.change_set_id
WHERE OLD.entity_record_id IS NOT DISTINCT FROM NEW.entity_record_id
UNION
-- Changed name
SELECT NEW.statistic_id, 'name', OLD.name, NEW.name, NEW.change_set_id
WHERE OLD.name IS NOT DISTINCT FROM NEW.name
UNION
-- Changed value_bool
SELECT NEW.statistic_id, 'value_bool', OLD.value_bool::TEXT, NEW.value_bool::TEXT, NEW.change_set_id
WHERE OLD.value_bool IS NOT DISTINCT FROM NEW.value_bool
UNION
-- Changed value_float
SELECT NEW.statistic_id, 'value_float', OLD.value_float::TEXT, NEW.value_float::TEXT, NEW.change_set_id
WHERE OLD.value_float IS NOT DISTINCT FROM NEW.value_float
UNION
-- Changed value_text
SELECT NEW.statistic_id, 'value_text', OLD.value_text, NEW.value_text, NEW.change_set_id
WHERE OLD.value_text IS NOT DISTINCT FROM NEW.value_text
UNION
-- Changed value_timestamp
SELECT NEW.statistic_id, 'value_timestamp', OLD.value_timestamp::TEXT, NEW.value_timestamp::TEXT, NEW.change_set_id
WHERE OLD.value_timestamp IS NOT DISTINCT FROM NEW.value_timestamp
UNION
-- Changed active
SELECT NEW.statistic_id, 'active', OLD.active::TEXT, NEW.active::TEXT, NEW.change_set_id
WHERE OLD.active IS NOT DISTINCT FROM NEW.active
;
RETURN NEW;
END;
$$ LANGUAGE plpgsql
;
CREATE TRIGGER TRI_before_insert_TCG_Statistic
BEFORE INSERT ON tcg.public.TCG_Statistic
FOR EACH ROW
EXECUTE FUNCTION tcg.public.FN_before_insert_TCG_Statistic()
;
CREATE TRIGGER TRI_before_update_TCG_Statistic
BEFORE UPDATE ON tcg.public.TCG_Statistic
FOR EACH ROW
EXECUTE FUNCTION tcg.public.FN_before_update_TCG_Statistic()
;

View File

@@ -35,7 +35,7 @@ BEGIN
DROP TABLE IF EXISTS Temp_User_Save_User;
DROP TABLE IF EXISTS Temp_User_Save_Error;
CREATE TABLE Temp_User_Save_User (
CREATE TEMP TABLE Temp_User_Save_User (
user_id INT NOT NULL
, user_auth0_id TEXT
, firstname TEXT

View File

@@ -0,0 +1,128 @@
CREATE OR REPLACE FUNCTION tcg.public.FN_TCG_MTG_Deck_Commander_Bracket_Get_Many (
a_get_all_commander_bracket BOOLEAN
, a_get_inactive_commander_bracket BOOLEAN
, a_commander_bracket_ids TEXT
, a_commander_bracket_names TEXT
, a_require_all_id_filters_met BOOLEAN
, a_require_any_id_filters_met BOOLEAN
, a_require_all_non_id_filters_met BOOLEAN
, a_require_any_non_id_filters_met BOOLEAN
)
RETURNS TABLE (
commander_bracket_id INT
, name TEXT
, description TEXT
, display_order INT
, active BOOLEAN
, created_on TIMESTAMP
, created_by_user_id INT
, updated_last_on TIMESTAMP
, updated_last_by_user_id INT
, change_set_id INT
)
LANGUAGE plpgsql
AS $$
DECLARE
v_get_all_commander_bracket BOOLEAN;
v_get_inactive_commander_bracket BOOLEAN;
v_commander_bracket_ids TEXT;
v_commander_bracket_names TEXT;
v_require_all_id_filters_met BOOLEAN;
v_require_any_id_filters_met BOOLEAN;
v_require_all_non_id_filters_met BOOLEAN;
v_require_any_non_id_filters_met BOOLEAN;
BEGIN
v_get_all_commander_bracket := COALESCE(a_get_all_commander_bracket, TRUE);
v_get_inactive_commander_bracket := COALESCE(a_get_inactive_commander_bracket, FALSE);
v_commander_bracket_ids := TRIM(COALESCE(a_commander_bracket_ids, ''));
v_commander_bracket_names := TRIM(COALESCE(a_commander_bracket_names, ''));
v_require_all_id_filters_met := COALESCE(a_require_all_id_filters_met, FALSE);
v_require_any_id_filters_met := COALESCE(a_require_any_id_filters_met, TRUE);
v_require_all_non_id_filters_met := COALESCE(a_require_all_non_id_filters_met, FALSE);
v_require_any_non_id_filters_met := COALESCE(a_require_any_non_id_filters_met, TRUE);
-- Outputs
RETURN QUERY SELECT
COMMANDER_BRACKET.commander_bracket_id
, COMMANDER_BRACKET.name
, COMMANDER_BRACKET.description
, COMMANDER_BRACKET.display_order
, COMMANDER_BRACKET.active
, COMMANDER_BRACKET.created_on
, COMMANDER_BRACKET.created_by_user_id
, COMMANDER_BRACKET.updated_last_on
, COMMANDER_BRACKET.updated_last_by_user_id
, COMMANDER_BRACKET.change_set_id
FROM tcg.public.TCG_MTG_Deck_Commander_Bracket COMMANDER_BRACKET
WHERE
(
(
NOT v_require_all_id_filters_met
AND NOT v_require_any_id_filters_met
)
OR (
v_require_all_id_filters_met
AND (
v_get_all_commander_bracket
OR COMMANDER_BRACKET.commander_bracket_id = ANY(string_to_array(v_commander_bracket_ids, ',')::INT[])
)
)
OR (
NOT v_require_all_id_filters_met
AND v_require_any_id_filters_met
AND (
v_get_all_commander_bracket
OR COMMANDER_BRACKET.commander_bracket_id = ANY(string_to_array(v_commander_bracket_ids, ',')::INT[])
)
)
)
AND (
(
NOT v_require_all_non_id_filters_met
AND NOT v_require_any_non_id_filters_met
)
OR (
v_require_all_non_id_filters_met
AND (
v_get_all_commander_bracket
OR COMMANDER_BRACKET.name LIKE ANY (SELECT '%' || TRIM(s) || '%' FROM unnest(string_to_array(v_commander_bracket_names, ',')) s)
)
)
OR (
NOT v_require_all_non_id_filters_met
AND v_require_any_non_id_filters_met
AND (
v_get_all_commander_bracket
OR COMMANDER_BRACKET.name LIKE ANY (SELECT '%' || TRIM(s) || '%' FROM unnest(string_to_array(v_commander_bracket_names, ',')) s)
)
)
)
AND (
v_get_inactive_commander_bracket
OR COMMANDER_BRACKET.active
)
ORDER BY COMMANDER_BRACKET.display_order
;
END;
$$;
SELECT *
FROM tcg.public.FN_TCG_MTG_Deck_Commander_Bracket_Get_Many (
a_get_all_commander_bracket := TRUE
, a_get_inactive_commander_bracket := FALSE
, a_commander_bracket_ids := CAST(NULL AS TEXT)
, a_commander_bracket_names := CAST(NULL AS TEXT)
, a_require_all_id_filters_met := FALSE
, a_require_any_id_filters_met := FALSE
, a_require_all_non_id_filters_met := FALSE
, a_require_any_non_id_filters_met := FALSE
)
;
SELECT *
FROM tcg.public.TCG_MTG_Deck_Commander_Bracket
;

View File

@@ -11,7 +11,8 @@ RETURNS TABLE (
, round_id INT
, player_id INT
, received_from_commander_player_id INT
, health_change INT
, life_gain INT
, life_loss INT
, commander_deaths INT
, is_eliminated BOOLEAN
, active BOOLEAN
@@ -42,7 +43,8 @@ BEGIN
, PLAYER_DAMAGE.round_id
, PLAYER_DAMAGE.player_id
, PLAYER_DAMAGE.received_from_commander_player_id
, PLAYER_DAMAGE.health_change
, PLAYER_DAMAGE.life_gain
, PLAYER_DAMAGE.life_loss
, PLAYER_DAMAGE.commander_deaths
, PLAYER_DAMAGE.is_eliminated
, PLAYER_DAMAGE.active
@@ -95,7 +97,7 @@ SELECT *
FROM tcg.public.FN_TCG_MTG_Game_Round_Player_Damage_Get_Many (
a_get_all_game := FALSE
, a_get_inactive_game := FALSE
, a_game_ids := '11'
, a_game_ids := '30'
, a_require_all_id_filters_met := TRUE
, a_require_any_id_filters_met := FALSE
)

View File

@@ -84,7 +84,8 @@ BEGIN
, round_id INT
, player_id INT
, received_from_commander_player_id INT
, health_change INT
, life_gain INT
, life_loss INT
, commander_deaths INT
, is_eliminated BOOLEAN
, active BOOLEAN
@@ -177,7 +178,8 @@ BEGIN
, round_id
, player_id
, received_from_commander_player_id
, health_change
, life_gain
, life_loss
, commander_deaths
, is_eliminated
, active
@@ -199,7 +201,8 @@ BEGIN
, PLAYER_DAMAGE_T.round_id
, PLAYER_DAMAGE_T.player_id
, PLAYER_DAMAGE_T.received_from_commander_player_id
, COALESCE(PLAYER_DAMAGE_T.health_change, 0) -- health_change
, ABS(COALESCE(PLAYER_DAMAGE_T.life_gain, 0)) -- life_gain
, ABS(COALESCE(PLAYER_DAMAGE_T.life_loss, 0)) -- life_loss
, COALESCE(PLAYER_DAMAGE_T.commander_deaths, 0) -- commander_deaths
, COALESCE(PLAYER_DAMAGE_T.is_eliminated, FALSE) -- is_eliminated
, COALESCE(PLAYER_DAMAGE_T.active, TRUE) -- active
@@ -220,8 +223,10 @@ BEGIN
, CAST(PLAYER_DAMAGE_T.player_id AS VARCHAR)
, ', received from commander player id: '
, CAST(PLAYER_DAMAGE_T.received_from_commander_player_id AS VARCHAR)
, ', health change: '
, CAST(PLAYER_DAMAGE_T.health_change AS VARCHAR)
, ', life gain: '
, CAST(PLAYER_DAMAGE_T.life_gain AS VARCHAR)
, ', life loss: '
, CAST(PLAYER_DAMAGE_T.life_loss AS VARCHAR)
, ' }'
) -- error_name
FROM tcg.public.TCG_MTG_Game_Round_Player_Damage_Temp PLAYER_DAMAGE_T
@@ -625,6 +630,7 @@ BEGIN
, 'player_id' AS field
FROM Temp_MTG_Round_Damage_Save_Round_Player_Damage T_PLAYER_DAMAGE
WHERE T_PLAYER_DAMAGE.player_id IS NULL
/*
UNION
SELECT
T_PLAYER_DAMAGE.temp_id
@@ -632,11 +638,24 @@ BEGIN
FROM Temp_MTG_Round_Damage_Save_Round_Player_Damage T_PLAYER_DAMAGE
WHERE T_PLAYER_DAMAGE.health_change IS NULL
UNION
SELECT
T_PLAYER_DAMAGE.temp_id
, 'life_gain' AS field
FROM Temp_MTG_Round_Damage_Save_Round_Player_Damage T_PLAYER_DAMAGE
WHERE T_PLAYER_DAMAGE.life_gain IS NULL
UNION
SELECT
T_PLAYER_DAMAGE.temp_id
, 'life_loss' AS field
FROM Temp_MTG_Round_Damage_Save_Round_Player_Damage T_PLAYER_DAMAGE
WHERE T_PLAYER_DAMAGE.life_loss IS NULL
UNION
SELECT
T_PLAYER_DAMAGE.temp_id
, 'commander_deaths' AS field
FROM Temp_MTG_Round_Damage_Save_Round_Player_Damage T_PLAYER_DAMAGE
WHERE T_PLAYER_DAMAGE.commander_deaths IS NULL
*/
)
INSERT INTO Temp_MTG_Round_Damage_Save_Error (
error_type_id
@@ -910,7 +929,8 @@ BEGIN
round_id
, player_id
, received_from_commander_player_id
, health_change
, life_gain
, life_loss
, commander_deaths
, is_eliminated
, active
@@ -924,7 +944,8 @@ BEGIN
T_ROUND.round_id
, T_PLAYER_DAMAGE.player_id
, T_PLAYER_DAMAGE.received_from_commander_player_id
, T_PLAYER_DAMAGE.health_change
, T_PLAYER_DAMAGE.life_gain
, T_PLAYER_DAMAGE.life_loss
, T_PLAYER_DAMAGE.commander_deaths
, T_PLAYER_DAMAGE.is_eliminated
, T_PLAYER_DAMAGE.active
@@ -951,7 +972,8 @@ BEGIN
round_id = T_ROUND.round_id
, player_id = T_PLAYER_DAMAGE.player_id
, received_from_commander_player_id = T_PLAYER_DAMAGE.received_from_commander_player_id
, health_change = T_PLAYER_DAMAGE.health_change
, life_gain = T_PLAYER_DAMAGE.life_gain
, life_loss = T_PLAYER_DAMAGE.life_loss
, commander_deaths = T_PLAYER_DAMAGE.commander_deaths
, is_eliminated = T_PLAYER_DAMAGE.is_eliminated
, active = T_PLAYER_DAMAGE.active
@@ -1098,7 +1120,9 @@ LEFT JOIN tcg.public.TCG_MTG_Game GAME ON TCG_ROUND.game_id = GAME.game_id
, round_id
, player_id
, received_from_commander_player_id
, health_change
-- , health_change
, life_gain
, life_loss
, commander_deaths
, active
)
@@ -1109,7 +1133,9 @@ LEFT JOIN tcg.public.TCG_MTG_Game GAME ON TCG_ROUND.game_id = GAME.game_id
, -1 -- round_id
, 3 -- player_id
, NULL -- received_from_commander_player_id
, -4 -- health_change
-- , -4 -- health_change
, 0 -- life_gain
, 4 -- life_loss
, 1 -- commander_deaths
, TRUE -- active
)
@@ -1119,7 +1145,9 @@ LEFT JOIN tcg.public.TCG_MTG_Game GAME ON TCG_ROUND.game_id = GAME.game_id
, -1 -- round_id
, 3 -- player_id
, 2 -- received_from_commander_player_id
, -5 -- health_change
-- , -5 -- health_change
, 0 -- life_gain
, 5 -- life_loss
, NULL -- commander_deaths
, TRUE -- active
)
@@ -1129,7 +1157,9 @@ LEFT JOIN tcg.public.TCG_MTG_Game GAME ON TCG_ROUND.game_id = GAME.game_id
, -1 -- round_id
, 3 -- player_id
, 5 -- received_from_commander_player_id
, -6 -- health_change
-- , -6 -- health_change
, 0 -- life_gain
, 6 -- life_loss
, NULL -- commander_deaths
, TRUE -- active
)

View File

@@ -0,0 +1,144 @@
CREATE OR REPLACE FUNCTION tcg.public.FN_TCG_Statistic_Get_Many (
a_get_all_statistic BOOLEAN
, a_get_inactive_statistic BOOLEAN
, a_statistic_ids TEXT
, a_entity_type_codes TEXT
, a_entity_record_ids TEXT
, a_require_all_id_filters_met BOOLEAN
, a_require_any_id_filters_met BOOLEAN
)
RETURNS TABLE (
statistic_id INT
, entity_type_code TEXT
, entity_record_id TEXT
, name TEXT
, value_bool BOOLEAN
, value_float DOUBLE PRECISION
, value_interval INTERVAL
, value_text TEXT
, value_timestamp TIMESTAMP
, is_bool BOOLEAN
, is_float BOOLEAN
, is_interval BOOLEAN
, is_text BOOLEAN
, is_timestamp BOOLEAN
, display_order INT
, active BOOLEAN
, created_on TIMESTAMP
, created_by_user_id INT
, updated_last_on TIMESTAMP
, updated_last_by_user_id INT
, change_set_id INT
)
LANGUAGE plpgsql
AS $$
DECLARE
v_get_all_statistic BOOLEAN;
v_get_inactive_statistic BOOLEAN;
v_statistic_ids TEXT;
v_entity_type_codes TEXT;
v_entity_record_ids TEXT;
v_require_all_id_filters_met BOOLEAN;
v_require_any_id_filters_met BOOLEAN;
BEGIN
v_get_all_statistic := COALESCE(a_get_all_statistic, FALSE);
v_get_inactive_statistic := COALESCE(a_get_inactive_statistic, FALSE);
v_statistic_ids := TRIM(COALESCE(a_statistic_ids, ''));
v_entity_type_codes := TRIM(COALESCE(a_entity_type_codes, ''));
v_entity_record_ids := TRIM(COALESCE(a_entity_record_ids, ''));
v_require_all_id_filters_met := COALESCE(a_require_all_id_filters_met, FALSE);
v_require_any_id_filters_met := COALESCE(a_require_any_id_filters_met, FALSE);
-- Outputs
RETURN QUERY SELECT
STATISTIC.statistic_id
, STATISTIC.entity_type_code
, STATISTIC.entity_record_id
, STATISTIC.name
, STATISTIC.value_bool
, STATISTIC.value_float
, STATISTIC.value_interval
, STATISTIC.value_text
, STATISTIC.value_timestamp
, STATISTIC.is_bool
, STATISTIC.is_float
, STATISTIC.is_interval
, STATISTIC.is_text
, STATISTIC.is_timestamp
, STATISTIC.display_order
, STATISTIC.active
, STATISTIC.created_on
, STATISTIC.created_by_user_id
, STATISTIC.updated_last_on
, STATISTIC.updated_last_by_user_id
, STATISTIC.change_set_id
FROM tcg.public.TCG_Statistic STATISTIC
WHERE
(
(
NOT v_require_all_id_filters_met
AND NOT v_require_any_id_filters_met
)
OR (
v_require_all_id_filters_met
AND (v_get_all_statistic OR v_statistic_ids = '' OR STATISTIC.statistic_id = ANY(string_to_array(v_statistic_ids, ',')::INT[]))
AND (v_get_all_statistic OR v_entity_type_codes = '' OR STATISTIC.entity_type_code = ANY(string_to_array(v_entity_type_codes, ',')))
AND (v_get_all_statistic OR v_entity_record_ids = '' OR STATISTIC.entity_record_id = ANY(string_to_array(v_entity_record_ids, ',')))
)
OR (
NOT v_require_all_id_filters_met
AND v_require_any_id_filters_met
AND (
v_get_all_statistic
OR STATISTIC.statistic_id = ANY(string_to_array(v_statistic_ids, ',')::INT[])
OR STATISTIC.entity_type_code = ANY(string_to_array(v_entity_type_codes, ','))
OR STATISTIC.entity_record_id = ANY(string_to_array(v_entity_record_ids, ','))
)
)
)
AND (
v_get_inactive_statistic
OR STATISTIC.active
)
ORDER BY
STATISTIC.entity_type_code
, STATISTIC.entity_record_id
, STATISTIC.display_order
, STATISTIC.statistic_id
;
END;
$$;
SELECT *
FROM tcg.public.FN_TCG_Statistic_Get_Many (
a_get_all_statistic := FALSE
, a_get_inactive_statistic := FALSE
, a_statistic_ids := ''
, a_entity_type_codes := 'deck'
, a_entity_record_ids := '1,2'
, a_require_all_id_filters_met := TRUE
, a_require_any_id_filters_met := TRUE
)
;
SELECT *
FROM tcg.public.TCG_Statistic
;
SELECT COUNT(*)
FROM tcg.public.TCG_Statistic S
WHERE S.entity_type_code = 'deck'
;
SELECT COUNT(*)
FROM tcg.public.TCG_Statistic S
WHERE S.entity_type_code = 'user'
;
SELECT COUNT(*)
FROM tcg.public.TCG_Statistic S
WHERE S.entity_type_code = 'user_deck_link'
;

File diff suppressed because it is too large Load Diff

View File

@@ -17,8 +17,8 @@
.button-primary {
background: var(--colour-accent);
color: var(--colour-primary);
border: 2px solid var(--colour-primary);
color: var(--primary-color);
border: 2px solid var(--primary-color);
}
.button-primary:hover {
@@ -28,7 +28,7 @@
.button-light {
background: white;
color: var(--colour-primary);
color: var(--primary-color);
}
.button-light:hover {

View File

@@ -2,5 +2,5 @@
select {
border: 1px solid var(--colour-accent);
background-color: var(--colour-page-background-1);
background-color: var(--background-color-1);
}

View File

@@ -8,7 +8,7 @@
position: fixed;
width: 100px;
/* height: 50%; */
background: var(--colour-page-background);
background: var(--background-color);
justify-content: right;
align-items: right;
align-self: right;
@@ -73,11 +73,11 @@
border-bottom-right-radius: 12px;
}
#overlayHamburger > :hover {
color: var(--colour-page-background);
background-color: var(--colour-primary);
color: var(--background-color);
background-color: var(--primary-color);
}
#overlayHamburger .container {
background-color: var(--colour-page-background);
background-color: var(--tcg-accent-purple);
display: flex;
flex-wrap: wrap;
align-items: center;
@@ -93,8 +93,8 @@
height: fit-content;
}
#overlayHamburger .container:hover {
color: var(--colour-page-background);
background-color: var(--colour-primary);
color: var(--background-color);
background-color: var(--primary-color);
}
#overlayHamburger > .container {
/*
@@ -105,13 +105,13 @@
#overlayHamburger .container a {
width: 100%;
padding: 4.5px 0;
color: var(--colour-text);
color: var(--tcg-text-primary);
text-decoration: none;
line-height: initial;
}
#overlayHamburger .container a:hover {
color: var(--colour-page-background);
background-color: var(--colour-primary);
color: var(--background-color);
background-color: var(--primary-color);
}
@media screen and (max-width: 400px) {

View File

@@ -28,7 +28,7 @@ th.is_collapsed, td.is_collapsed {
display: table-cell !important;
}
td.dirty {
background-color: var(--colour-primary);
background-color: var(--primary-color);
}
td:not(.dirty) {
background-color: transparent;
@@ -48,7 +48,7 @@ table button {
}
table button.active {
background-color: var(--colour-page-background);
background-color: var(--background-color);
}
tr.delete, tr.delete > td {

View File

@@ -1 +1,16 @@
/* In sections */
.tcg.footer {
display: flex;
}
.tcg.footer > .container {
/* display: flex; */
}
.footer-content {
display: flex;
margin: 0 auto;
}
.footer-section {
/* display: flex; */
margin: auto auto;
}

View File

@@ -31,7 +31,7 @@
color: var(--colour-text-link-visited);
}
.topnav a:hover {
background-color: var(--colour-page-background);
background-color: var(--background-color);
color: var(--colour-text)
}
.topnav > .container {
@@ -149,10 +149,10 @@
#formFilters button {
padding: 0.5vh 0.75vh;
background-color: var(--colour-accent);
color: var(--colour-primary);
color: var(--primary-color);
font-weight: bold;
border-radius: 0.75vh;
border: 2px solid var(--colour-primary);
border: 2px solid var(--primary-color);
}
#formFilters button.is_collapsed {

View File

@@ -4,38 +4,39 @@
margin-top: 1vh;
}
table.table-main {
.table-main {
overflow-x: auto;
padding: 1vh 1vw;
max-width: 88vw; /* min(calc(1vh * 80), calc(1vw * 90)); */
padding: 0; /*1vh 1vw;*/
max-width: 80vw; /* min(calc(1vh * 80), calc(1vw * 90)); */
width: min-content;
align-items: normal;
justify-content: normal;
margin: 0 auto;
}
table.table-main * {
.table-main * {
padding: 0.25vh 0.5vh;
}
table.table-main thead {
.table-main thead {
max-height: 4vh;
overflow-y: visible;
background-color: var(--colour-text-background);
background-color: var(--background-color);
}
table.table-main tbody {
.table-main tbody {
max-height: 75vh;
overflow-y: auto;
min-width: fit-content;
max-width: fit-content;
overflow-x: visible;
}
table.table-main tbody.is_collapsed {
.table-main tbody.is_collapsed {
display: block;
}
table.table-main:has(tbody > div) tbody {
.table-main:has(tbody > div) tbody {
}
table.table-main tbody > div {
.table-main tbody > div {
margin-left: auto;
margin-right: auto;
text-align: center;
@@ -50,72 +51,72 @@ table.table-main tbody > div {
width: 100%; /* min(calc(90vh), calc(70vw)); */
}
table.table-main select,
table.table-main input:not([type="checkbox"]),
table.table-main textarea,
table.table-main div {
.table-main select,
.table-main input:not([type="checkbox"]),
.table-main textarea,
.table-main div {
box-sizing: border-box;
width: 100%;
max-width: 100%;
height: 100%;
border: 1px solid var(--colour-accent);
border: 1px solid var(--border-color);
border-radius: 0.5vh;
text-align: center;
background-color: var(--colour-text-background);
background-color: var(--background-color);
font-size: 16px;
}
table.table-main thead tr th,
table.table-main tbody tr td {
.table-main thead tr th,
.table-main tbody tr td {
max-width: 20vh;
min-width: 20vh;
padding: 0 0.5vh;
}
table.table-main tbody tr td {
.table-main tbody tr td {
height: 3vh;
/* padding-top: 0.5vh; */
}
table.table-main thead tr th.notes,
table.table-main tbody tr td.notes {
.table-main thead tr th.notes,
.table-main tbody tr td.notes {
max-width: fit-content;
}
table.table-main tbody tr td:has(.dirty) {
background-color: var(--colour-primary);
.table-main tbody tr td:has(.dirty) {
background-color: var(--primary-color);
}
table.table-main tbody tr td:has(.dirty) table tr:not(:has(.dirty)) {
.table-main tbody tr td:has(.dirty) table tr:not(:has(.dirty)) {
background-color: var(--colour-text-background);
}
table.table-main tbody tr:not(:last-of-type) td {
.table-main tbody tr:not(:last-of-type) td {
padding-bottom: 0.25vh;
}
table.table-main tbody tr td.ddl-preview div {
.table-main tbody tr td.ddl-preview div {
cursor: pointer;
}
table.table-main tbody tr td.ddl-preview div,
table.table-main tbody tr td.ddl-preview select {
.table-main tbody tr td.ddl-preview div,
.table-main tbody tr td.ddl-preview select {
padding-left: 2vh;
padding-right: 2vh;
}
table.table-main thead tr th.active,
table.table-main tbody tr td.active {
.table-main thead tr th.active,
.table-main tbody tr td.active {
max-width: 6vh;
min-width: 6vh;
}
table.table-main thead tr th.active svg.active.add {
fill: var(--colour-primary);
background-color: var(--colour-accent);
border: 2px solid var(--colour-accent);
.table-main thead tr th.active svg.active.add {
fill: var(--primary-color);
background-color: var(--background-color);
border: 2px solid var(--border-color);
padding: 0;
border-radius: 1vh;
}
table.table-main tbody tr td.active svg.active.add {
fill: var(--colour-primary);
.table-main tbody tr td.active svg.active.add {
fill: var(--primary-color);
}
table.table-main tbody tr td.active svg.active.delete {
fill: var(--colour-error);
.table-main tbody tr td.active svg.active.delete {
fill: var(--tcg-accent-red);
}
table.table-main tbody tr td.display_order,
table.table-main thead tr th.display_order {
.table-main tbody tr td.display_order,
.table-main thead tr th.display_order {
max-width: 5vh;
min-width: 5vh;
}
@@ -141,13 +142,13 @@ table.table-main thead tr th.display_order {
.company-name {
font-size: 1.2rem;
}
table.table-main {
.table-main {
max-height: 61vh;
}
table.table-main thead {
.table-main thead {
font-size: 0.8rem;
}
table.table-main tbody {
.table-main tbody {
max-height: 53vh;
}
}

View File

@@ -38,14 +38,13 @@ html {
}
body {
background-color: var(--colour-page-background);
color: var(--colour-text);
background-color: var(--background-color);
color: var(--text-color);
font-family: var(--font-family-base);
font-family: Arial;
padding: 0;
margin: 0;
border: 0;
background: linear-gradient(to bottom right, var(--colour-page-background-1), var(--colour-page-background-2)); /* var(--c_purple); */
height: 100vh;
/* max-height: 100vh;
overflow-y: clip; */
@@ -53,6 +52,7 @@ body {
* {
margin: 0;
color: var(--text-color);
}
script, link {
@@ -88,7 +88,7 @@ script, link {
width: 100%;
align-self: center;
font-size: 1rem;
color: var(--colour-text);
color: var(--text-color);
}
#pageBody > * > * {
align-self: center;
@@ -104,7 +104,6 @@ script, link {
/* Add a card effect for articles */
.card {
background-color: var(--colour-text-background);
padding: 1vh 2.5vw;
margin: 1vh 1vw;
display: flex;
@@ -182,7 +181,7 @@ script, link {
.container-input > input,
.container-input > textarea {
border: 2px solid var(--colour-accent);
border: 2px solid var(--border-color);
border-radius: 0.5vh;
padding: 1vh 1vw;
}
@@ -205,10 +204,10 @@ li {
:not(input,textarea,select,button).dirty {
background-color: var(--colour-accent);
background-color: var(--border-color);
}
input.dirty, textarea.dirty, select.dirty {
border-color: var(--colour-primary);
border-color: var(--primary-color);
}
.is-collapsed {

View File

@@ -24,6 +24,9 @@ body.tcg-theme {
}
/* Top Nav TCG Style */
#buttonHamburger {
display: none;
}
.topnav.tcg {
background: var(--tcg-bg-secondary);
border-bottom: 2px solid var(--tcg-border-color);
@@ -88,7 +91,8 @@ body.tcg-theme {
.footer.tcg h3 {
font-family: 'Cinzel', serif;
color: var(--tcg-accent-gold);
margin-bottom: 1rem;
/* margin-bottom: 1vh; */
margin-top: 0;
}
.footer.tcg a {
@@ -174,9 +178,10 @@ body.tcg-theme {
font-family: 'Cinzel', serif;
font-size: 1.5rem;
color: var(--tcg-accent-gold);
margin-bottom: 1.5rem;
letter-spacing: 0.05em;
text-transform: uppercase;
margin: 1.5rem auto 0;
text-align: center;
}
/* TCG Form Elements */
@@ -244,6 +249,9 @@ body.tcg-theme {
.nav-links.tcg {
display: none;
}
#buttonHamburger {
display: block;
}
.topnav.tcg {
padding-right: 30vw;
}

View File

@@ -0,0 +1,38 @@
.tcg-card {
margin-top: 1vh;
margin-bottom: 0;
}
table.table-main tbody {
max-height: 30vh;
overflow-y: auto;
}
table.statistics {
margin-top: 0;
}
table.statistics thead {
background-color: var(--background-color);
}
table.statistics tbody {
max-height: 30vh;
overflow-y: auto;
}
table.statistics thead tr th.deck,
table.statistics tbody tr td.deck_id {
min-width: 20vh;
max-width: 20vh;
}
table.statistics thead tr th.name,
table.statistics tbody tr td.name {
min-width: 40vh;
max-width: 40vh;
}
table.statistics thead tr th.value,
table.statistics tbody tr td.value {
min-width: 10vh;
max-width: 10vh;
}

View File

@@ -147,6 +147,9 @@
}
/* Game Section */
#gameSection {
margin: 0 auto;
}
#gameSection .row.round {
display: flex;
align-items: center;
@@ -347,13 +350,16 @@
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
}
.life-controls {
.life-gain-controls,
.life-loss-controls {
display: flex;
gap: 1rem;
justify-content: center;
margin-bottom: 1vh;
}
.life-btn {
.life-gain-btn,
.life-loss-btn {
background: var(--tcg-bg-card);
border: 2px solid var(--tcg-border-color);
color: var(--tcg-text-primary);
@@ -370,17 +376,31 @@
justify-content: center;
}
.life-btn:hover {
border-color: var(--tcg-accent-purple);
background: var(--tcg-accent-purple);
.life-gain-btn:hover,
.life-loss-btn:hover {
border-color: green; /*var(--tcg-accent-purple);*/
background: green; /*var(--tcg-accent-purple);*/
color: var(--tcg-bg-primary);
transform: scale(1.1);
}
.life-btn:active {
.life-loss-btn:hover {
border-color: red;
background: red;
}
.life-gain-btn:active,
.life-loss-btn:active {
transform: scale(0.95);
}
.life-total label {
color: var(--tcg-text-primary);
width: 100%;
margin-bottom: 1vh;
display: inline-block;
}
/* Commander Damage Section */
.commander-damage-section {
border-top: 1px solid var(--tcg-border-color);
@@ -420,6 +440,7 @@
.damage-source {
font-size: 1rem;
color: var(--tcg-text-secondary);
padding-right: 1vh;
}
.damage-controls {
@@ -478,8 +499,10 @@
.damage-log.container table tbody tr td.received_from_commander_player_id {
width: 20vw;
}
.damage-log.container table thead tr th.health_change,
.damage-log.container table tbody tr td.health_change {
.damage-log.container table thead tr th.life_gain,
.damage-log.container table tbody tr td.life_gain,
.damage-log.container table thead tr th.life_loss,
.damage-log.container table tbody tr td.life_loss {
width: 7vw;
}
.damage-log.container table thead tr th.commander-deaths,
@@ -583,7 +606,8 @@
font-size: 3rem;
}
.life-btn {
.life-gain-btn,
.life-loss-btn {
width: 50px;
height: 50px;
font-size: 1.5rem;

View File

@@ -33,15 +33,18 @@
position: relative;
animation: tcg-fadeIn 0.8s ease-out 0.2s backwards;
margin: 2vh auto;
display: flex;
flex-wrap: wrap;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
margin-bottom: 1rem;
flex-wrap: wrap;
gap: 1rem;
width: 100%;
}
/* Filters Form */
@@ -88,8 +91,7 @@
overflow-x: auto;
}
.games-table {
width: 100%;
#tableMain.games-table {
border-collapse: collapse;
font-size: 1rem;
}
@@ -98,7 +100,6 @@
background: var(--tcg-bg-card);
border-bottom: 2px solid var(--tcg-accent-purple);
}
.games-table th {
font-family: 'Cinzel', serif;
font-size: 0.95rem;
@@ -107,18 +108,22 @@
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 1rem;
text-align: left;
}
.games-table thead tr {
height: 4vh;
}
#tableMain.games-table tbody {
max-height: 59vh;
}
.games-table tbody tr {
border-bottom: 1px solid var(--tcg-border-color);
transition: all 0.3s ease;
height: 4vh;
}
.games-table tbody tr:hover {
background: rgba(139, 92, 246, 0.1);
}
.games-table tbody tr.inactive {
opacity: 0.5;
}
@@ -132,7 +137,38 @@
color: white;
}
.game-id {
.games-table thead tr th.game_id,
.games-table tbody tr td.game_id {
min-width: 10vh;
max-width: 10vh;
}
.games-table thead tr th.is_commander,
.games-table tbody tr td.is_commander {
min-width: 12vh;
max-width: 12vh;
}
.games-table thead tr th.location_name,
.games-table tbody tr td.location_name {
min-width: 20vh;
max-width: 20vh;
}
.games-table thead tr th.start_on,
.games-table tbody tr td.start_on {
min-width: 14vh;
max-width: 14vh;
}
#tableMain.games-table thead tr th.active,
#tableMain.games-table tbody tr td.active {
min-width: 8vh;
max-width: 8vh;
}
.games-table thead tr th.navMtgGame,
.games-table tbody tr td.navMtgGame {
min-width: 13vh;
max-width: 13vh;
}
.game_id {
font-family: 'Cinzel', serif;
font-weight: 600;
color: var(--tcg-text-secondary);
@@ -223,7 +259,7 @@
/* Join Button */
.btn-join {
padding: 0.5rem 1.25rem;
padding: 0.5vh 1vw;
font-size: 0.9rem;
}
@@ -265,7 +301,7 @@
animation: tcg-fadeIn 0.3s ease-out;
}
.modal-overlay.hidden {
.modal-overlay.is_collapsed {
display: none;
}
@@ -338,7 +374,6 @@
}
.section-header {
flex-direction: column;
align-items: stretch;
}

View File

@@ -22,6 +22,6 @@ label {
input.dirty,
textarea.dirty,
select.dirty {
border-color: var(--colour-primary);
background-color: var(--colour-page-background-2);
border-color: var(--primary-color);
background-color: var(--background-color-2);
}

View File

@@ -14,7 +14,7 @@
/* Main Table */
#pageBody {
max-height: 77vh;
max-height: 82vh;
padding: 0 5vw;
margin: 0;
border: 0;

View File

@@ -1,4 +1,4 @@
/*
:root {
--background-color: #121212;
--text-color: #e0e0e0;
@@ -43,5 +43,5 @@
--card-bg: #1f1f1f;
--card-border: #333333;
--card-shadow: 0 0.125rem 0.25rem rgba(255, 255, 255, 0.05);
}
*/
}

View File

@@ -15,7 +15,7 @@
border-radius: 2vh;
}
.theme-switch:hover {
background-color: var(--colour-primary);
background-color: var(--primary-color);
}
.theme-switch img.theme-icon,
.theme-switch svg.theme-icon {
@@ -40,7 +40,7 @@ svg.theme-icon.light-mode-icon .background {
fill: var(--colour-secondary);
}
.theme-switch:hover svg.theme-icon.light-mode-icon .background {
fill: var(--colour-primary);
fill: var(--primary-color);
}
svg.theme-icon.light-mode-icon .sun {
fill: var(--colour-text);

View File

@@ -6,10 +6,10 @@
--colour-error-accent: #fc8181;
--colour-error-highlight: #fff5f5;
--colour-error-title: #c53030;
--colour-page-background: #E0AAFF;
--colour-page-background-1: #F5ECFE;
--colour-page-background-2: #FAE0E2;
--colour-primary: #240046;
--background-color: #E0AAFF;
--background-color-1: #F5ECFE;
--background-color-2: #FAE0E2;
--primary-color: #240046;
--colour-secondary: #3C096C;
--colour-success: #38a169;
--colour-success-highlight: #f0fff4;

View File

@@ -38,14 +38,13 @@ html {
}
body {
background-color: var(--colour-page-background);
color: var(--colour-text);
background-color: var(--background-color);
color: var(--text-color);
font-family: var(--font-family-base);
font-family: Arial;
padding: 0;
margin: 0;
border: 0;
background: linear-gradient(to bottom right, var(--colour-page-background-1), var(--colour-page-background-2)); /* var(--c_purple); */
height: 100vh;
/* max-height: 100vh;
overflow-y: clip; */
@@ -53,6 +52,7 @@ body {
* {
margin: 0;
color: var(--text-color);
}
script, link {
@@ -88,7 +88,7 @@ script, link {
width: 100%;
align-self: center;
font-size: 1rem;
color: var(--colour-text);
color: var(--text-color);
}
#pageBody > * > * {
align-self: center;
@@ -104,7 +104,6 @@ script, link {
/* Add a card effect for articles */
.card {
background-color: var(--colour-text-background);
padding: 1vh 2.5vw;
margin: 1vh 1vw;
display: flex;
@@ -182,7 +181,7 @@ script, link {
.container-input > input,
.container-input > textarea {
border: 2px solid var(--colour-accent);
border: 2px solid var(--border-color);
border-radius: 0.5vh;
padding: 1vh 1vw;
}
@@ -205,10 +204,10 @@ li {
:not(input,textarea,select,button).dirty {
background-color: var(--colour-accent);
background-color: var(--border-color);
}
input.dirty, textarea.dirty, select.dirty {
border-color: var(--colour-primary);
border-color: var(--primary-color);
}
.is-collapsed {
@@ -234,8 +233,8 @@ input.dirty, textarea.dirty, select.dirty {
.button-primary {
background: var(--colour-accent);
color: var(--colour-primary);
border: 2px solid var(--colour-primary);
color: var(--primary-color);
border: 2px solid var(--primary-color);
}
.button-primary:hover {
@@ -245,7 +244,7 @@ input.dirty, textarea.dirty, select.dirty {
.button-light {
background: white;
color: var(--colour-primary);
color: var(--primary-color);
}
.button-light:hover {
@@ -267,7 +266,7 @@ input.dirty, textarea.dirty, select.dirty {
select {
border: 1px solid var(--colour-accent);
background-color: var(--colour-page-background-1);
background-color: var(--background-color-1);
}
img, video {
@@ -321,7 +320,7 @@ h5 {
position: fixed;
width: 100px;
/* height: 50%; */
background: var(--colour-page-background);
background: var(--background-color);
justify-content: right;
align-items: right;
align-self: right;
@@ -386,11 +385,11 @@ h5 {
border-bottom-right-radius: 12px;
}
#overlayHamburger > :hover {
color: var(--colour-page-background);
background-color: var(--colour-primary);
color: var(--background-color);
background-color: var(--primary-color);
}
#overlayHamburger .container {
background-color: var(--colour-page-background);
background-color: var(--tcg-accent-purple);
display: flex;
flex-wrap: wrap;
align-items: center;
@@ -406,8 +405,8 @@ h5 {
height: fit-content;
}
#overlayHamburger .container:hover {
color: var(--colour-page-background);
background-color: var(--colour-primary);
color: var(--background-color);
background-color: var(--primary-color);
}
#overlayHamburger > .container {
/*
@@ -418,13 +417,13 @@ h5 {
#overlayHamburger .container a {
width: 100%;
padding: 4.5px 0;
color: var(--colour-text);
color: var(--tcg-text-primary);
text-decoration: none;
line-height: initial;
}
#overlayHamburger .container a:hover {
color: var(--colour-page-background);
background-color: var(--colour-primary);
color: var(--background-color);
background-color: var(--primary-color);
}
@media screen and (max-width: 400px) {
@@ -497,7 +496,7 @@ th.is_collapsed, td.is_collapsed {
display: table-cell !important;
}
td.dirty {
background-color: var(--colour-primary);
background-color: var(--primary-color);
}
td:not(.dirty) {
background-color: transparent;
@@ -517,7 +516,7 @@ table button {
}
table button.active {
background-color: var(--colour-page-background);
background-color: var(--background-color);
}
tr.delete, tr.delete > td {
@@ -560,7 +559,7 @@ table div {
color: var(--colour-text-link-visited);
}
.topnav a:hover {
background-color: var(--colour-page-background);
background-color: var(--background-color);
color: var(--colour-text)
}
.topnav > .container {
@@ -678,10 +677,10 @@ table div {
#formFilters button {
padding: 0.5vh 0.75vh;
background-color: var(--colour-accent);
color: var(--colour-primary);
color: var(--primary-color);
font-weight: bold;
border-radius: 0.75vh;
border: 2px solid var(--colour-primary);
border: 2px solid var(--primary-color);
}
#formFilters button.is_collapsed {
@@ -725,45 +724,62 @@ form.filter button.save, form.filter button.button-cancel {
padding-right: 30vw;
}
}
/* In sections */
.tcg.footer {
display: flex;
}
.tcg.footer > .container {
/* display: flex; */
}
.footer-content {
display: flex;
margin: 0 auto;
}
.footer-section {
/* display: flex; */
margin: auto auto;
}
#formFilters {
padding: 0.5vh 1vw;
margin-top: 1vh;
}
table.table-main {
.table-main {
overflow-x: auto;
padding: 1vh 1vw;
max-width: 88vw; /* min(calc(1vh * 80), calc(1vw * 90)); */
padding: 0; /*1vh 1vw;*/
max-width: 80vw; /* min(calc(1vh * 80), calc(1vw * 90)); */
width: min-content;
align-items: normal;
justify-content: normal;
margin: 0 auto;
}
table.table-main * {
.table-main * {
padding: 0.25vh 0.5vh;
}
table.table-main thead {
.table-main thead {
max-height: 4vh;
overflow-y: visible;
background-color: var(--colour-text-background);
background-color: var(--background-color);
}
table.table-main tbody {
.table-main tbody {
max-height: 75vh;
overflow-y: auto;
min-width: fit-content;
max-width: fit-content;
overflow-x: visible;
}
table.table-main tbody.is_collapsed {
.table-main tbody.is_collapsed {
display: block;
}
table.table-main:has(tbody > div) tbody {
.table-main:has(tbody > div) tbody {
}
table.table-main tbody > div {
.table-main tbody > div {
margin-left: auto;
margin-right: auto;
text-align: center;
@@ -778,72 +794,72 @@ table.table-main tbody > div {
width: 100%; /* min(calc(90vh), calc(70vw)); */
}
table.table-main select,
table.table-main input:not([type="checkbox"]),
table.table-main textarea,
table.table-main div {
.table-main select,
.table-main input:not([type="checkbox"]),
.table-main textarea,
.table-main div {
box-sizing: border-box;
width: 100%;
max-width: 100%;
height: 100%;
border: 1px solid var(--colour-accent);
border: 1px solid var(--border-color);
border-radius: 0.5vh;
text-align: center;
background-color: var(--colour-text-background);
background-color: var(--background-color);
font-size: 16px;
}
table.table-main thead tr th,
table.table-main tbody tr td {
.table-main thead tr th,
.table-main tbody tr td {
max-width: 20vh;
min-width: 20vh;
padding: 0 0.5vh;
}
table.table-main tbody tr td {
.table-main tbody tr td {
height: 3vh;
/* padding-top: 0.5vh; */
}
table.table-main thead tr th.notes,
table.table-main tbody tr td.notes {
.table-main thead tr th.notes,
.table-main tbody tr td.notes {
max-width: fit-content;
}
table.table-main tbody tr td:has(.dirty) {
background-color: var(--colour-primary);
.table-main tbody tr td:has(.dirty) {
background-color: var(--primary-color);
}
table.table-main tbody tr td:has(.dirty) table tr:not(:has(.dirty)) {
.table-main tbody tr td:has(.dirty) table tr:not(:has(.dirty)) {
background-color: var(--colour-text-background);
}
table.table-main tbody tr:not(:last-of-type) td {
.table-main tbody tr:not(:last-of-type) td {
padding-bottom: 0.25vh;
}
table.table-main tbody tr td.ddl-preview div {
.table-main tbody tr td.ddl-preview div {
cursor: pointer;
}
table.table-main tbody tr td.ddl-preview div,
table.table-main tbody tr td.ddl-preview select {
.table-main tbody tr td.ddl-preview div,
.table-main tbody tr td.ddl-preview select {
padding-left: 2vh;
padding-right: 2vh;
}
table.table-main thead tr th.active,
table.table-main tbody tr td.active {
.table-main thead tr th.active,
.table-main tbody tr td.active {
max-width: 6vh;
min-width: 6vh;
}
table.table-main thead tr th.active svg.active.add {
fill: var(--colour-primary);
background-color: var(--colour-accent);
border: 2px solid var(--colour-accent);
.table-main thead tr th.active svg.active.add {
fill: var(--primary-color);
background-color: var(--background-color);
border: 2px solid var(--border-color);
padding: 0;
border-radius: 1vh;
}
table.table-main tbody tr td.active svg.active.add {
fill: var(--colour-primary);
.table-main tbody tr td.active svg.active.add {
fill: var(--primary-color);
}
table.table-main tbody tr td.active svg.active.delete {
fill: var(--colour-error);
.table-main tbody tr td.active svg.active.delete {
fill: var(--tcg-accent-red);
}
table.table-main tbody tr td.display_order,
table.table-main thead tr th.display_order {
.table-main tbody tr td.display_order,
.table-main thead tr th.display_order {
max-width: 5vh;
min-width: 5vh;
}
@@ -869,13 +885,13 @@ table.table-main thead tr th.display_order {
.company-name {
font-size: 1.2rem;
}
table.table-main {
.table-main {
max-height: 61vh;
}
table.table-main thead {
.table-main thead {
font-size: 0.8rem;
}
table.table-main tbody {
.table-main tbody {
max-height: 53vh;
}
}
@@ -883,44 +899,53 @@ table.table-main thead tr th.display_order {
/* Default */
:root {
/* Claude dark blue / grey theme */
--colour-accent: #C77DFF;
--colour-error: red;
--colour-error-accent: #fc8181;
--colour-error-highlight: #fff5f5;
--colour-error-title: #c53030;
--colour-page-background: #E0AAFF;
--colour-page-background-1: #F5ECFE;
--colour-page-background-2: #FAE0E2;
--colour-primary: #240046;
--colour-secondary: #3C096C;
--colour-success: #38a169;
--colour-success-highlight: #f0fff4;
--colour-success-title: #16a34a;
--colour-text: #10002B;
--colour-text-background: white;
--colour-text-link-unvisited: #0000EE;
--colour-text-link-visited: #551A8B;
}
--background-color: #121212;
--text-color: #e0e0e0;
--primary-color: #bb86fc;
--secondary-color: #03dac6;
--success-color: #00c853;
--danger-color: #cf6679;
--warning-color: #ffab00;
--info-color: #2196f3;
--light-color: #2c2c2c;
--dark-color: #1f1f1f;
--border-color: #333333;
--shadow-color: rgba(255, 255, 255, 0.1);
/*
--c_purple_darker: #310055;
--c_purple_dark: #4A0A77;
--c_purple: #6818A5;
--c_purple_light: #CBAFFE;
--c_purple_lighter: #F5ECFE;
/* Header * /
--header-bg: #1f1f1f;
--header-text: #e0e0e0;
--c_blue: #0044FF;
--c_blue_pastel: #B8E0FF;
--c_blue_light: #73E8FF;
--c_blue_dark: #003ADB;
/* --c_red: * /
--c-red: #FF0000;
--c_red_pastel: #FAE0E2;
--c_red_lighter: #FAE0E2;
}
/* Footer * /
--footer-bg: #1f1f1f;
--footer-text: #a0a0a0;
/* Navigation * /
--nav-bg: #1f1f1f;
--nav-text: #e0e0e0;
--nav-hover-bg: #2c2c2c;
--nav-hover-text: #bb86fc;
/* Buttons * /
--Button-primary-bg: #bb86fc;
--Button-primary-text: #121212;
--Button-secondary-bg: #03dac6;
--Button-secondary-text: #121212;
/* Forms * /
--input-bg: #2c2c2c;
--input-border: #454545;
--input-text: #e0e0e0;
--input-focus-border: #bb86fc;
/* Cards * /
--card-bg: #1f1f1f;
--card-border: #333333;
--card-shadow: 0 0.125rem 0.25rem rgba(255, 255, 255, 0.05);
*/
}
/*# sourceMappingURL=main.bundle.css.map*/

File diff suppressed because one or more lines are too long

106
static/dist/css/tcg_decks.bundle.css vendored Normal file
View File

@@ -0,0 +1,106 @@
.container-input > input {
padding: 0vh 1vh;
border-radius: 0.5vh;
max-width: 7vh;
}
/* Right column */
.rightcolumn {
min-width: fit-content;
}
/* Main Table */
#pageBody {
max-height: 82vh;
padding: 0 5vw;
margin: 0;
border: 0;
align-content: center;
justify-content: flex-start;
display: flex;
flex-direction: column;
align-items: flex-start;
overflow-y: auto;
overflow-x: hidden;
position: absolute;
width: 90vw;
color: var(--colour-text);
}
/* Footer */
.footer {
padding: 1vh 1vw;
text-align: center;
margin: 0;
max-height: 5vh;
overflow-y: auto;
background-color: var(--colour-accent);
position: absolute;
bottom: 0;
width: 98vw;
}
@media screen and (max-width: 400px) {
.footer {
max-height: 8vh;
padding: 0.75vh 2vw;
font-size: 10px;
width: 96vw;
max-width: 96vw;
}
.footer > h4 {
font-size: 10px;
}
.footer > h5 {
font-size: 9px;
}
}
.footer > h4, h5 {
padding: 0;
margin: 0;
}
.tcg-card {
margin-top: 1vh;
margin-bottom: 0;
}
table.table-main tbody {
max-height: 30vh;
overflow-y: auto;
}
table.statistics {
margin-top: 0;
}
table.statistics thead {
background-color: var(--background-color);
}
table.statistics tbody {
max-height: 30vh;
overflow-y: auto;
}
table.statistics thead tr th.deck,
table.statistics tbody tr td.deck_id {
min-width: 20vh;
max-width: 20vh;
}
table.statistics thead tr th.name,
table.statistics tbody tr td.name {
min-width: 40vh;
max-width: 40vh;
}
table.statistics thead tr th.value,
table.statistics tbody tr td.value {
min-width: 10vh;
max-width: 10vh;
}
/*# sourceMappingURL=tcg_decks.bundle.css.map*/

View File

@@ -0,0 +1 @@
{"version":3,"file":"css/tcg_decks.bundle.css","mappings":";;AAEA;IACI,gBAAgB;IAChB,oBAAoB;IACpB,cAAc;AAClB;;;AAGA,iBAAiB;AACjB;IACI,sBAAsB;AAC1B;;AAEA,eAAe;AACf;IACI,gBAAgB;IAChB,cAAc;IACd,SAAS;IACT,SAAS;IACT,qBAAqB;IACrB,2BAA2B;IAC3B,aAAa;IACb,sBAAsB;IACtB,uBAAuB;IACvB,gBAAgB;IAChB,kBAAkB;IAClB,kBAAkB;IAClB,WAAW;IACX,yBAAyB;AAC7B;;;AAGA,WAAW;AACX;IACI,gBAAgB;IAChB,kBAAkB;IAClB,SAAS;IACT,eAAe;IACf,gBAAgB;IAChB,sCAAsC;IACtC,kBAAkB;IAClB,SAAS;IACT,WAAW;AACf;;AAEA;IACI;QACI,eAAe;QACf,mBAAmB;QACnB,eAAe;QACf,WAAW;QACX,eAAe;IACnB;IACA;QACI,eAAe;IACnB;IACA;QACI,cAAc;IAClB;AACJ;;AAEA;IACI,UAAU;IACV,SAAS;AACb,C;;;AC/DA;IACI,eAAe;IACf,gBAAgB;AACpB;;;AAGA;IACI,gBAAgB;IAChB,gBAAgB;AACpB;;AAEA;IACI,aAAa;AACjB;AACA;IACI,yCAAyC;AAC7C;AACA;IACI,gBAAgB;IAChB,gBAAgB;AACpB;AACA;;IAEI,eAAe;IACf,eAAe;AACnB;AACA;;IAEI,eAAe;IACf,eAAe;AACnB;AACA;;IAEI,eAAe;IACf,eAAe;AACnB,C","sources":["webpack://app/./static/css/sections/tcg.css","webpack://app/./static/css/pages/tcg/decks.css"],"sourcesContent":["\n\n.container-input > input {\n padding: 0vh 1vh;\n border-radius: 0.5vh;\n max-width: 7vh;\n}\n\n\n/* Right column */\n.rightcolumn {\n min-width: fit-content;\n}\n\n/* Main Table */\n#pageBody {\n max-height: 82vh;\n padding: 0 5vw;\n margin: 0;\n border: 0;\n align-content: center;\n justify-content: flex-start;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n overflow-y: auto;\n overflow-x: hidden;\n position: absolute;\n width: 90vw;\n color: var(--colour-text);\n}\n\n\n/* Footer */\n.footer {\n padding: 1vh 1vw;\n text-align: center;\n margin: 0;\n max-height: 5vh;\n overflow-y: auto;\n background-color: var(--colour-accent);\n position: absolute;\n bottom: 0;\n width: 98vw;\n}\n\n@media screen and (max-width: 400px) {\n .footer {\n max-height: 8vh;\n padding: 0.75vh 2vw;\n font-size: 10px; \n width: 96vw;\n max-width: 96vw;\n }\n .footer > h4 {\n font-size: 10px;\n }\n .footer > h5 {\n font-size: 9px;\n }\n}\n\n.footer > h4, h5 {\n padding: 0;\n margin: 0;\n}","\n\n.tcg-card {\n margin-top: 1vh;\n margin-bottom: 0;\n}\n\n\ntable.table-main tbody {\n max-height: 30vh;\n overflow-y: auto;\n}\n\ntable.statistics {\n margin-top: 0;\n}\ntable.statistics thead {\n background-color: var(--background-color);\n}\ntable.statistics tbody {\n max-height: 30vh;\n overflow-y: auto;\n}\ntable.statistics thead tr th.deck,\ntable.statistics tbody tr td.deck_id {\n min-width: 20vh;\n max-width: 20vh;\n}\ntable.statistics thead tr th.name,\ntable.statistics tbody tr td.name {\n min-width: 40vh;\n max-width: 40vh;\n}\ntable.statistics thead tr th.value,\ntable.statistics tbody tr td.value {\n min-width: 10vh;\n max-width: 10vh;\n}"],"names":[],"sourceRoot":""}

View File

@@ -14,7 +14,7 @@
/* Main Table */
#pageBody {
max-height: 77vh;
max-height: 82vh;
padding: 0 5vw;
margin: 0;
border: 0;
@@ -213,6 +213,9 @@
}
/* Game Section */
#gameSection {
margin: 0 auto;
}
#gameSection .row.round {
display: flex;
align-items: center;
@@ -413,13 +416,16 @@
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
}
.life-controls {
.life-gain-controls,
.life-loss-controls {
display: flex;
gap: 1rem;
justify-content: center;
margin-bottom: 1vh;
}
.life-btn {
.life-gain-btn,
.life-loss-btn {
background: var(--tcg-bg-card);
border: 2px solid var(--tcg-border-color);
color: var(--tcg-text-primary);
@@ -436,17 +442,31 @@
justify-content: center;
}
.life-btn:hover {
border-color: var(--tcg-accent-purple);
background: var(--tcg-accent-purple);
.life-gain-btn:hover,
.life-loss-btn:hover {
border-color: green; /*var(--tcg-accent-purple);*/
background: green; /*var(--tcg-accent-purple);*/
color: var(--tcg-bg-primary);
transform: scale(1.1);
}
.life-btn:active {
.life-loss-btn:hover {
border-color: red;
background: red;
}
.life-gain-btn:active,
.life-loss-btn:active {
transform: scale(0.95);
}
.life-total label {
color: var(--tcg-text-primary);
width: 100%;
margin-bottom: 1vh;
display: inline-block;
}
/* Commander Damage Section */
.commander-damage-section {
border-top: 1px solid var(--tcg-border-color);
@@ -486,6 +506,7 @@
.damage-source {
font-size: 1rem;
color: var(--tcg-text-secondary);
padding-right: 1vh;
}
.damage-controls {
@@ -544,8 +565,10 @@
.damage-log.container table tbody tr td.received_from_commander_player_id {
width: 20vw;
}
.damage-log.container table thead tr th.health_change,
.damage-log.container table tbody tr td.health_change {
.damage-log.container table thead tr th.life_gain,
.damage-log.container table tbody tr td.life_gain,
.damage-log.container table thead tr th.life_loss,
.damage-log.container table tbody tr td.life_loss {
width: 7vw;
}
.damage-log.container table thead tr th.commander-deaths,
@@ -649,7 +672,8 @@
font-size: 3rem;
}
.life-btn {
.life-gain-btn,
.life-loss-btn {
width: 50px;
height: 50px;
font-size: 1.5rem;

File diff suppressed because one or more lines are too long

View File

@@ -14,7 +14,7 @@
/* Main Table */
#pageBody {
max-height: 77vh;
max-height: 82vh;
padding: 0 5vw;
margin: 0;
border: 0;
@@ -99,15 +99,18 @@
position: relative;
animation: tcg-fadeIn 0.8s ease-out 0.2s backwards;
margin: 2vh auto;
display: flex;
flex-wrap: wrap;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
margin-bottom: 1rem;
flex-wrap: wrap;
gap: 1rem;
width: 100%;
}
/* Filters Form */
@@ -154,8 +157,7 @@
overflow-x: auto;
}
.games-table {
width: 100%;
#tableMain.games-table {
border-collapse: collapse;
font-size: 1rem;
}
@@ -164,7 +166,6 @@
background: var(--tcg-bg-card);
border-bottom: 2px solid var(--tcg-accent-purple);
}
.games-table th {
font-family: 'Cinzel', serif;
font-size: 0.95rem;
@@ -173,18 +174,22 @@
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 1rem;
text-align: left;
}
.games-table thead tr {
height: 4vh;
}
#tableMain.games-table tbody {
max-height: 59vh;
}
.games-table tbody tr {
border-bottom: 1px solid var(--tcg-border-color);
transition: all 0.3s ease;
height: 4vh;
}
.games-table tbody tr:hover {
background: rgba(139, 92, 246, 0.1);
}
.games-table tbody tr.inactive {
opacity: 0.5;
}
@@ -198,7 +203,38 @@
color: white;
}
.game-id {
.games-table thead tr th.game_id,
.games-table tbody tr td.game_id {
min-width: 10vh;
max-width: 10vh;
}
.games-table thead tr th.is_commander,
.games-table tbody tr td.is_commander {
min-width: 12vh;
max-width: 12vh;
}
.games-table thead tr th.location_name,
.games-table tbody tr td.location_name {
min-width: 20vh;
max-width: 20vh;
}
.games-table thead tr th.start_on,
.games-table tbody tr td.start_on {
min-width: 14vh;
max-width: 14vh;
}
#tableMain.games-table thead tr th.active,
#tableMain.games-table tbody tr td.active {
min-width: 8vh;
max-width: 8vh;
}
.games-table thead tr th.navMtgGame,
.games-table tbody tr td.navMtgGame {
min-width: 13vh;
max-width: 13vh;
}
.game_id {
font-family: 'Cinzel', serif;
font-weight: 600;
color: var(--tcg-text-secondary);
@@ -289,7 +325,7 @@
/* Join Button */
.btn-join {
padding: 0.5rem 1.25rem;
padding: 0.5vh 1vw;
font-size: 0.9rem;
}
@@ -331,7 +367,7 @@
animation: tcg-fadeIn 0.3s ease-out;
}
.modal-overlay.hidden {
.modal-overlay.is_collapsed {
display: none;
}
@@ -404,7 +440,6 @@
}
.section-header {
flex-direction: column;
align-items: stretch;
}

File diff suppressed because one or more lines are too long

View File

@@ -14,7 +14,7 @@
/* Main Table */
#pageBody {
max-height: 77vh;
max-height: 82vh;
padding: 0 5vw;
margin: 0;
border: 0;

View File

@@ -1 +1 @@
{"version":3,"file":"css/tcg_home.bundle.css","mappings":";;AAEA;IACI,gBAAgB;IAChB,oBAAoB;IACpB,cAAc;AAClB;;;AAGA,iBAAiB;AACjB;IACI,sBAAsB;AAC1B;;AAEA,eAAe;AACf;IACI,gBAAgB;IAChB,cAAc;IACd,SAAS;IACT,SAAS;IACT,qBAAqB;IACrB,2BAA2B;IAC3B,aAAa;IACb,sBAAsB;IACtB,uBAAuB;IACvB,gBAAgB;IAChB,kBAAkB;IAClB,kBAAkB;IAClB,WAAW;IACX,yBAAyB;AAC7B;;;AAGA,WAAW;AACX;IACI,gBAAgB;IAChB,kBAAkB;IAClB,SAAS;IACT,eAAe;IACf,gBAAgB;IAChB,sCAAsC;IACtC,kBAAkB;IAClB,SAAS;IACT,WAAW;AACf;;AAEA;IACI;QACI,eAAe;QACf,mBAAmB;QACnB,eAAe;QACf,WAAW;QACX,eAAe;IACnB;IACA;QACI,eAAe;IACnB;IACA;QACI,cAAc;IAClB;AACJ;;AAEA;IACI,UAAU;IACV,SAAS;AACb,C;ACjEA;IACI,iBAAiB;AACrB;AACA;IACI,iBAAiB;IACjB,kBAAkB;AACtB,C","sources":["webpack://app/./static/css/sections/tcg.css","webpack://app/./static/css/pages/tcg/home.css"],"sourcesContent":["\n\n.container-input > input {\n padding: 0vh 1vh;\n border-radius: 0.5vh;\n max-width: 7vh;\n}\n\n\n/* Right column */\n.rightcolumn {\n min-width: fit-content;\n}\n\n/* Main Table */\n#pageBody {\n max-height: 77vh;\n padding: 0 5vw;\n margin: 0;\n border: 0;\n align-content: center;\n justify-content: flex-start;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n overflow-y: auto;\n overflow-x: hidden;\n position: absolute;\n width: 90vw;\n color: var(--colour-text);\n}\n\n\n/* Footer */\n.footer {\n padding: 1vh 1vw;\n text-align: center;\n margin: 0;\n max-height: 5vh;\n overflow-y: auto;\n background-color: var(--colour-accent);\n position: absolute;\n bottom: 0;\n width: 98vw;\n}\n\n@media screen and (max-width: 400px) {\n .footer {\n max-height: 8vh;\n padding: 0.75vh 2vw;\n font-size: 10px; \n width: 96vw;\n max-width: 96vw;\n }\n .footer > h4 {\n font-size: 10px;\n }\n .footer > h5 {\n font-size: 9px;\n }\n}\n\n.footer > h4, h5 {\n padding: 0;\n margin: 0;\n}","#pageBody .column .row {\n margin-top: 0.5vh;\n}\n#pageBody .column .row .button {\n margin-left: auto;\n margin-right: auto;\n}"],"names":[],"sourceRoot":""}
{"version":3,"file":"css/tcg_home.bundle.css","mappings":";;AAEA;IACI,gBAAgB;IAChB,oBAAoB;IACpB,cAAc;AAClB;;;AAGA,iBAAiB;AACjB;IACI,sBAAsB;AAC1B;;AAEA,eAAe;AACf;IACI,gBAAgB;IAChB,cAAc;IACd,SAAS;IACT,SAAS;IACT,qBAAqB;IACrB,2BAA2B;IAC3B,aAAa;IACb,sBAAsB;IACtB,uBAAuB;IACvB,gBAAgB;IAChB,kBAAkB;IAClB,kBAAkB;IAClB,WAAW;IACX,yBAAyB;AAC7B;;;AAGA,WAAW;AACX;IACI,gBAAgB;IAChB,kBAAkB;IAClB,SAAS;IACT,eAAe;IACf,gBAAgB;IAChB,sCAAsC;IACtC,kBAAkB;IAClB,SAAS;IACT,WAAW;AACf;;AAEA;IACI;QACI,eAAe;QACf,mBAAmB;QACnB,eAAe;QACf,WAAW;QACX,eAAe;IACnB;IACA;QACI,eAAe;IACnB;IACA;QACI,cAAc;IAClB;AACJ;;AAEA;IACI,UAAU;IACV,SAAS;AACb,C;ACjEA;IACI,iBAAiB;AACrB;AACA;IACI,iBAAiB;IACjB,kBAAkB;AACtB,C","sources":["webpack://app/./static/css/sections/tcg.css","webpack://app/./static/css/pages/tcg/home.css"],"sourcesContent":["\n\n.container-input > input {\n padding: 0vh 1vh;\n border-radius: 0.5vh;\n max-width: 7vh;\n}\n\n\n/* Right column */\n.rightcolumn {\n min-width: fit-content;\n}\n\n/* Main Table */\n#pageBody {\n max-height: 82vh;\n padding: 0 5vw;\n margin: 0;\n border: 0;\n align-content: center;\n justify-content: flex-start;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n overflow-y: auto;\n overflow-x: hidden;\n position: absolute;\n width: 90vw;\n color: var(--colour-text);\n}\n\n\n/* Footer */\n.footer {\n padding: 1vh 1vw;\n text-align: center;\n margin: 0;\n max-height: 5vh;\n overflow-y: auto;\n background-color: var(--colour-accent);\n position: absolute;\n bottom: 0;\n width: 98vw;\n}\n\n@media screen and (max-width: 400px) {\n .footer {\n max-height: 8vh;\n padding: 0.75vh 2vw;\n font-size: 10px; \n width: 96vw;\n max-width: 96vw;\n }\n .footer > h4 {\n font-size: 10px;\n }\n .footer > h5 {\n font-size: 9px;\n }\n}\n\n.footer > h4, h5 {\n padding: 0;\n margin: 0;\n}","#pageBody .column .row {\n margin-top: 0.5vh;\n}\n#pageBody .column .row .button {\n margin-left: auto;\n margin-right: auto;\n}"],"names":[],"sourceRoot":""}

View File

@@ -14,7 +14,7 @@
/* Main Table */
#pageBody {
max-height: 77vh;
max-height: 82vh;
padding: 0 5vw;
margin: 0;
border: 0;
@@ -89,8 +89,8 @@ label {
input.dirty,
textarea.dirty,
select.dirty {
border-color: var(--colour-primary);
background-color: var(--colour-page-background-2);
border-color: var(--primary-color);
background-color: var(--background-color-2);
}
/*# sourceMappingURL=user_account.bundle.css.map*/

View File

@@ -1 +1 @@
{"version":3,"file":"css/user_account.bundle.css","mappings":";;AAEA;IACI,gBAAgB;IAChB,oBAAoB;IACpB,cAAc;AAClB;;;AAGA,iBAAiB;AACjB;IACI,sBAAsB;AAC1B;;AAEA,eAAe;AACf;IACI,gBAAgB;IAChB,cAAc;IACd,SAAS;IACT,SAAS;IACT,qBAAqB;IACrB,2BAA2B;IAC3B,aAAa;IACb,sBAAsB;IACtB,uBAAuB;IACvB,gBAAgB;IAChB,kBAAkB;IAClB,kBAAkB;IAClB,WAAW;IACX,yBAAyB;AAC7B;;;AAGA,WAAW;AACX;IACI,gBAAgB;IAChB,kBAAkB;IAClB,SAAS;IACT,eAAe;IACf,gBAAgB;IAChB,sCAAsC;IACtC,kBAAkB;IAClB,SAAS;IACT,WAAW;AACf;;AAEA;IACI;QACI,eAAe;QACf,mBAAmB;QACnB,eAAe;QACf,WAAW;QACX,eAAe;IACnB;IACA;QACI,eAAe;IACnB;IACA;QACI,cAAc;IAClB;AACJ;;AAEA;IACI,UAAU;IACV,SAAS;AACb,C;;;AChEA;IACI,aAAa;AACjB;;AAEA;IACI,eAAe;IACf,SAAS;IACT,WAAW;AACf;;AAEA;IACI,cAAc;AAClB;AACA;IACI,iBAAiB;AACrB;AACA;IACI,gBAAgB;AACpB;;AAEA;;;IAGI,mCAAmC;IACnC,iDAAiD;AACrD,C","sources":["webpack://app/./static/css/sections/tcg.css","webpack://app/./static/css/pages/user/user.css"],"sourcesContent":["\n\n.container-input > input {\n padding: 0vh 1vh;\n border-radius: 0.5vh;\n max-width: 7vh;\n}\n\n\n/* Right column */\n.rightcolumn {\n min-width: fit-content;\n}\n\n/* Main Table */\n#pageBody {\n max-height: 77vh;\n padding: 0 5vw;\n margin: 0;\n border: 0;\n align-content: center;\n justify-content: flex-start;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n overflow-y: auto;\n overflow-x: hidden;\n position: absolute;\n width: 90vw;\n color: var(--colour-text);\n}\n\n\n/* Footer */\n.footer {\n padding: 1vh 1vw;\n text-align: center;\n margin: 0;\n max-height: 5vh;\n overflow-y: auto;\n background-color: var(--colour-accent);\n position: absolute;\n bottom: 0;\n width: 98vw;\n}\n\n@media screen and (max-width: 400px) {\n .footer {\n max-height: 8vh;\n padding: 0.75vh 2vw;\n font-size: 10px; \n width: 96vw;\n max-width: 96vw;\n }\n .footer > h4 {\n font-size: 10px;\n }\n .footer > h5 {\n font-size: 9px;\n }\n}\n\n.footer > h4, h5 {\n padding: 0;\n margin: 0;\n}","\n#formFilters {\n display: none;\n}\n\n.container.save.button-cancel {\n position: fixed;\n top: 10vh;\n right: 10vh;\n}\n\n.container-input {\n margin: 0 auto;\n}\nlabel {\n font-weight: bold;\n}\n.container-input input {\n max-width: 250px;\n}\n\ninput.dirty, \ntextarea.dirty, \nselect.dirty {\n border-color: var(--colour-primary);\n background-color: var(--colour-page-background-2);\n}"],"names":[],"sourceRoot":""}
{"version":3,"file":"css/user_account.bundle.css","mappings":";;AAEA;IACI,gBAAgB;IAChB,oBAAoB;IACpB,cAAc;AAClB;;;AAGA,iBAAiB;AACjB;IACI,sBAAsB;AAC1B;;AAEA,eAAe;AACf;IACI,gBAAgB;IAChB,cAAc;IACd,SAAS;IACT,SAAS;IACT,qBAAqB;IACrB,2BAA2B;IAC3B,aAAa;IACb,sBAAsB;IACtB,uBAAuB;IACvB,gBAAgB;IAChB,kBAAkB;IAClB,kBAAkB;IAClB,WAAW;IACX,yBAAyB;AAC7B;;;AAGA,WAAW;AACX;IACI,gBAAgB;IAChB,kBAAkB;IAClB,SAAS;IACT,eAAe;IACf,gBAAgB;IAChB,sCAAsC;IACtC,kBAAkB;IAClB,SAAS;IACT,WAAW;AACf;;AAEA;IACI;QACI,eAAe;QACf,mBAAmB;QACnB,eAAe;QACf,WAAW;QACX,eAAe;IACnB;IACA;QACI,eAAe;IACnB;IACA;QACI,cAAc;IAClB;AACJ;;AAEA;IACI,UAAU;IACV,SAAS;AACb,C;;;AChEA;IACI,aAAa;AACjB;;AAEA;IACI,eAAe;IACf,SAAS;IACT,WAAW;AACf;;AAEA;IACI,cAAc;AAClB;AACA;IACI,iBAAiB;AACrB;AACA;IACI,gBAAgB;AACpB;;AAEA;;;IAGI,kCAAkC;IAClC,2CAA2C;AAC/C,C","sources":["webpack://app/./static/css/sections/tcg.css","webpack://app/./static/css/pages/user/user.css"],"sourcesContent":["\n\n.container-input > input {\n padding: 0vh 1vh;\n border-radius: 0.5vh;\n max-width: 7vh;\n}\n\n\n/* Right column */\n.rightcolumn {\n min-width: fit-content;\n}\n\n/* Main Table */\n#pageBody {\n max-height: 82vh;\n padding: 0 5vw;\n margin: 0;\n border: 0;\n align-content: center;\n justify-content: flex-start;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n overflow-y: auto;\n overflow-x: hidden;\n position: absolute;\n width: 90vw;\n color: var(--colour-text);\n}\n\n\n/* Footer */\n.footer {\n padding: 1vh 1vw;\n text-align: center;\n margin: 0;\n max-height: 5vh;\n overflow-y: auto;\n background-color: var(--colour-accent);\n position: absolute;\n bottom: 0;\n width: 98vw;\n}\n\n@media screen and (max-width: 400px) {\n .footer {\n max-height: 8vh;\n padding: 0.75vh 2vw;\n font-size: 10px; \n width: 96vw;\n max-width: 96vw;\n }\n .footer > h4 {\n font-size: 10px;\n }\n .footer > h5 {\n font-size: 9px;\n }\n}\n\n.footer > h4, h5 {\n padding: 0;\n margin: 0;\n}","\n#formFilters {\n display: none;\n}\n\n.container.save.button-cancel {\n position: fixed;\n top: 10vh;\n right: 10vh;\n}\n\n.container-input {\n margin: 0 auto;\n}\nlabel {\n font-weight: bold;\n}\n.container-input input {\n max-width: 250px;\n}\n\ninput.dirty, \ntextarea.dirty, \nselect.dirty {\n border-color: var(--primary-color);\n background-color: var(--background-color-2);\n}"],"names":[],"sourceRoot":""}

View File

@@ -15,7 +15,7 @@
/* Main Table */
#pageBody {
max-height: 77vh;
max-height: 82vh;
padding: 0 5vw;
margin: 0;
border: 0;

View File

@@ -1 +1 @@
{"version":3,"file":"css/user_accounts.bundle.css","mappings":";;;AAEA;IACI,gBAAgB;IAChB,oBAAoB;IACpB,cAAc;AAClB;;;AAGA,iBAAiB;AACjB;IACI,sBAAsB;AAC1B;;AAEA,eAAe;AACf;IACI,gBAAgB;IAChB,cAAc;IACd,SAAS;IACT,SAAS;IACT,qBAAqB;IACrB,2BAA2B;IAC3B,aAAa;IACb,sBAAsB;IACtB,uBAAuB;IACvB,gBAAgB;IAChB,kBAAkB;IAClB,kBAAkB;IAClB,WAAW;IACX,yBAAyB;AAC7B;;;AAGA,WAAW;AACX;IACI,gBAAgB;IAChB,kBAAkB;IAClB,SAAS;IACT,eAAe;IACf,gBAAgB;IAChB,sCAAsC;IACtC,kBAAkB;IAClB,SAAS;IACT,WAAW;AACf;;AAEA;IACI;QACI,eAAe;QACf,mBAAmB;QACnB,eAAe;QACf,WAAW;QACX,eAAe;IACnB;IACA;QACI,eAAe;IACnB;IACA;QACI,cAAc;IAClB;AACJ;;AAEA;IACI,UAAU;IACV,SAAS;AACb,C","sources":["webpack://app/./static/css/sections/tcg.css"],"sourcesContent":["\n\n.container-input > input {\n padding: 0vh 1vh;\n border-radius: 0.5vh;\n max-width: 7vh;\n}\n\n\n/* Right column */\n.rightcolumn {\n min-width: fit-content;\n}\n\n/* Main Table */\n#pageBody {\n max-height: 77vh;\n padding: 0 5vw;\n margin: 0;\n border: 0;\n align-content: center;\n justify-content: flex-start;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n overflow-y: auto;\n overflow-x: hidden;\n position: absolute;\n width: 90vw;\n color: var(--colour-text);\n}\n\n\n/* Footer */\n.footer {\n padding: 1vh 1vw;\n text-align: center;\n margin: 0;\n max-height: 5vh;\n overflow-y: auto;\n background-color: var(--colour-accent);\n position: absolute;\n bottom: 0;\n width: 98vw;\n}\n\n@media screen and (max-width: 400px) {\n .footer {\n max-height: 8vh;\n padding: 0.75vh 2vw;\n font-size: 10px; \n width: 96vw;\n max-width: 96vw;\n }\n .footer > h4 {\n font-size: 10px;\n }\n .footer > h5 {\n font-size: 9px;\n }\n}\n\n.footer > h4, h5 {\n padding: 0;\n margin: 0;\n}"],"names":[],"sourceRoot":""}
{"version":3,"file":"css/user_accounts.bundle.css","mappings":";;;AAEA;IACI,gBAAgB;IAChB,oBAAoB;IACpB,cAAc;AAClB;;;AAGA,iBAAiB;AACjB;IACI,sBAAsB;AAC1B;;AAEA,eAAe;AACf;IACI,gBAAgB;IAChB,cAAc;IACd,SAAS;IACT,SAAS;IACT,qBAAqB;IACrB,2BAA2B;IAC3B,aAAa;IACb,sBAAsB;IACtB,uBAAuB;IACvB,gBAAgB;IAChB,kBAAkB;IAClB,kBAAkB;IAClB,WAAW;IACX,yBAAyB;AAC7B;;;AAGA,WAAW;AACX;IACI,gBAAgB;IAChB,kBAAkB;IAClB,SAAS;IACT,eAAe;IACf,gBAAgB;IAChB,sCAAsC;IACtC,kBAAkB;IAClB,SAAS;IACT,WAAW;AACf;;AAEA;IACI;QACI,eAAe;QACf,mBAAmB;QACnB,eAAe;QACf,WAAW;QACX,eAAe;IACnB;IACA;QACI,eAAe;IACnB;IACA;QACI,cAAc;IAClB;AACJ;;AAEA;IACI,UAAU;IACV,SAAS;AACb,C","sources":["webpack://app/./static/css/sections/tcg.css"],"sourcesContent":["\n\n.container-input > input {\n padding: 0vh 1vh;\n border-radius: 0.5vh;\n max-width: 7vh;\n}\n\n\n/* Right column */\n.rightcolumn {\n min-width: fit-content;\n}\n\n/* Main Table */\n#pageBody {\n max-height: 82vh;\n padding: 0 5vw;\n margin: 0;\n border: 0;\n align-content: center;\n justify-content: flex-start;\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n overflow-y: auto;\n overflow-x: hidden;\n position: absolute;\n width: 90vw;\n color: var(--colour-text);\n}\n\n\n/* Footer */\n.footer {\n padding: 1vh 1vw;\n text-align: center;\n margin: 0;\n max-height: 5vh;\n overflow-y: auto;\n background-color: var(--colour-accent);\n position: absolute;\n bottom: 0;\n width: 98vw;\n}\n\n@media screen and (max-width: 400px) {\n .footer {\n max-height: 8vh;\n padding: 0.75vh 2vw;\n font-size: 10px; \n width: 96vw;\n max-width: 96vw;\n }\n .footer > h4 {\n font-size: 10px;\n }\n .footer > h5 {\n font-size: 9px;\n }\n}\n\n.footer > h4, h5 {\n padding: 0;\n margin: 0;\n}"],"names":[],"sourceRoot":""}

View File

@@ -746,6 +746,7 @@ class BasePage {
// , buttonSave = null, buttonCancel = null
if (Validation.isEmpty(buttonContainerSelector)) buttonContainerSelector = '.' + flagContainer + '.' + flagSave + '.' + flagCancel;
let buttonSave = document.querySelector(buttonContainerSelector + ' ' + idButtonSave);
if (buttonSave == null) return;
let buttonCancel = document.querySelector(buttonContainerSelector + ' ' + idButtonCancel);
utils_Utils.consoleLogIfNotProductionEnvironment({
show,
@@ -1116,6 +1117,7 @@ class TableBasePage extends BasePage {
cacheRowBlank() {
let selectorRowNew = idTableMain + ' tbody tr.' + flagRowNew;
let rowBlankTemp = document.querySelector(selectorRowNew);
if (rowBlankTemp == null) return;
utils_Utils.consoleLogIfNotProductionEnvironment("row blank temp: ", rowBlankTemp);
let countRows = document.querySelectorAll(idTableMain + ' > tbody > tr').length;
_rowBlank = rowBlankTemp.cloneNode(true);
@@ -1524,13 +1526,89 @@ class TableBasePage extends BasePage {
DOM.toggleElementHasClassnameFlag(columnTh, isRequiredFlag, classnameFlag);
}
updateAndToggleShowButtonsSaveCancel() {
let pageBody = document.querySelector(idPageBody);
// let pageBody = document.querySelector(idPageBody);
let isDirty = DOM.hasDirtyChildrenContainer(pageBody);
let buttonContainerSelector = '.' + flagContainer + '.' + flagSave + '.' + flagCancel;
let buttonSave = document.querySelector(buttonContainerSelector + ' ' + idButtonSave);
let areVisibleSaveCancelButtons = !buttonSave.classList.contains(flagIsCollapsed);
console.log({
pageBody,
isDirty
isDirty,
areVisibleSaveCancelButtons
});
this.toggleShowButtonsSaveCancel(isDirty);
this.toggleShowButtonsSaveCancel(isDirty || areVisibleSaveCancelButtons);
}
}
;// ./static/js/pages/tcg/mtg_decks.js
class PageMtgDecks extends TableBasePage {
static hash = hashPageMtgDecks;
static attrIdRowObject = attrDeckId;
callSaveTableContent = API.saveDeck;
constructor(router) {
super(router);
}
initialize() {
this.sharedInitialize();
}
hookupFilters() {
/*
this.sharedHookupFilters();
this.hookupFilterActive();
*/
}
loadRowTable(rowJson) {
if (rowJson == null) return;
if (_verbose) {
utils_Utils.consoleLogIfNotProductionEnvironment("applying data row: ", rowJson);
}
}
getJsonRow(row) {
return;
}
initialiseRowNew(tbody, row) {}
postInitialiseRowNewCallback(tbody) {
let newRows = tbody.querySelectorAll('tr.' + flagRowNew);
let newestRow = newRows[0];
let clickableElementsSelector = ['td.' + attrCommanderBracketId + ' div.' + attrCommanderBracketId].join('');
newestRow.querySelectorAll(clickableElementsSelector).forEach(clickableElement => {
clickableElement.click();
});
}
hookupTableMain() {
super.hookupTableMain();
this.hookupTableMainRows();
this.hookupFieldsNameTable();
this.hookupTableMainIsCommanderCheckboxes();
this.hookupTableMainCommanderBracketPreviews();
this.hookupFieldsActive();
}
hookupTableMainRows() {
return;
// removed by dead control flow
// removed by dead control flow
}
static toggleShowDeckStatisticsSection(showSection) {
let statisticsSectionTableBody = document.querySelector('table.' + flagStatistics + ' tbody');
if (showSection) {
statisticsSectionTableBody.classList.remove(flagIsCollapsed);
} else {
statisticsSectionTableBody.classList.add(flagIsCollapsed);
}
}
hookupTableMainIsCommanderCheckboxes() {
this.hookupChangeHandlerTableCells(idTableMain + ' td.' + flagIsCommander + ' .' + flagIsCommander);
}
hookupTableMainCommanderBracketPreviews() {
this.hookupTableCellDdlPreviews(attrCommanderBracketId, utils_Utils.getListFromDict(commanderBrackets));
}
leave() {
super.leave();
}
}
;// ./static/js/pages/tcg/mtg_game.js
@@ -1698,28 +1776,16 @@ class PageMtgGame extends TableBasePage {
const grid = document.getElementById('playersGrid');
grid.innerHTML = '';
// Build a damage lookup: { playerId: { fromPlayerId: damageAmount } }
/*
const damageLookup = {};
damageRecords.forEach(damage => {
if (!damageLookup[damage.player_id]) {
damageLookup[damage.player_id] = {};
}
if (damage.received_from_commander_player_id) {
damageLookup[damage.player_id][damage.received_from_commander_player_id] = damage.health_change || 0;
}
});
*/
let activeRoundId = PageMtgGame.getActiveRoundId();
// let activeRoundId = PageMtgGame.getActiveRoundId();
const roundDisplayOrderLabel = PageMtgGame.getRoundDisplayOrderLabel();
if (activeRoundId < 0) {
const currentRoundDisplayOrder = Number(DOM.getElementValueCurrent(roundDisplayOrderLabel));
rounds.push(PageMtgGame.makeDefaultGameRound(currentRoundDisplayOrder));
activeRoundId = PageMtgGame.getActiveRoundId();
let activeRound = rounds.filter(round => round[flagDisplayOrder] == currentRoundDisplayOrder)[0];
if (activeRound == null) {
activeRound = PageMtgGame.makeDefaultGameRound(currentRoundDisplayOrder);
rounds.push(activeRound);
}
const latestRound = rounds.filter(round => round[attrRoundId] == activeRoundId)[0];
DOM.setElementValueCurrent(roundDisplayOrderLabel, latestRound[flagDisplayOrder]);
DOM.setElementValueCurrent(roundDisplayOrderLabel, activeRound[flagDisplayOrder]);
const previousRoundIds = rounds.filter(round => round[flagDisplayOrder] <= currentRoundDisplayOrder).map(round => round[attrRoundId]);
players.forEach((player, index) => {
// Build display name: prefer user_name + deck_name, fallback to player name
const playerId = player[attrPlayerId];
@@ -1730,17 +1796,26 @@ class PageMtgGame extends TableBasePage {
let maxCommanderDamageReceived = 0;
damagePlayerPairs.forEach(damagePlayerPair => {
const sourceId = damagePlayerPair[attrPlayerId];
const filteredPlayerDamages = damageRecords.filter(damage => damage[attrRoundId] == activeRoundId && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == sourceId); //[playerId] || {};
const filteredPlayerDamages = damageRecords.filter(damage => damage[attrRoundId] == activeRound[attrRoundId]
// previousRoundIds.includes(damage[attrRoundId])
&& damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == sourceId); //[playerId] || {};
if (filteredPlayerDamages.length == 0) {
damageRecords.push(PageMtgGame.makeDefaultGameRoundPlayerDamage(playerId, sourceId));
}
maxCommanderDamageReceived = Math.max(maxCommanderDamageReceived, damageRecords.filter(damage => damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == sourceId).map(damage => damage[flagHealthChange]).reduce((acc, curr) => acc + curr, 0));
maxCommanderDamageReceived = Math.max(maxCommanderDamageReceived, damageRecords.filter(damage => damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == sourceId && damage[attrReceivedFromCommanderPlayerId] != null && previousRoundIds.includes(damage[attrRoundId])).map(damage => damage[flagLifeLoss]).reduce((a, b) => a + b, 0));
});
const totalDamage = damageRecords.filter(damage => damage[attrPlayerId] == playerId && previousRoundIds.includes(damage[attrRoundId])).map(damage => damage[flagLifeLoss] - damage[flagLifeGain]).reduce((a, b) => a + b, 0);
let life = startingLife - totalDamage;
let isEliminatedByForce = damageRecords.filter(damage => damage[attrPlayerId] == playerId && previousRoundIds.includes(damage[attrRoundId])).map(damage => damage[flagIsEliminated]).some(Boolean);
console.log("renderPlayers");
console.log({
isEliminatedByForce,
player,
life,
maxCommanderDamageReceived
});
const totalDamage = damageRecords.filter(damage => damage[attrPlayerId] == playerId).map(damage => damage[flagHealthChange]).reduce((acc, curr) => acc + curr, 0);
let life = startingLife + totalDamage;
let isEliminatedByForce = damageRecords.filter(damage => damage[attrPlayerId] == playerId).map(damage => damage[flagIsEliminated]).some(Boolean);
const isEliminated = isEliminatedByForce || !player[flagActive] || life < 1 || maxCommanderDamageReceived >= 21;
const playerOwnDamage = damageRecords.filter(damage => damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == null && damage[attrRoundId] == activeRoundId)[0];
const totalCommanderDeaths = damageRecords.filter(damage => damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == null && damage[attrRoundId] == activeRound[attrRoundId]).map(damage => damage[flagCommanderDeaths]).reduce((a, b) => a + b, 0);
const card = document.createElement('div');
card.className = `player-card ${isEliminated ? 'eliminated' : ''}`;
card.style.animationDelay = `${index * 0.1}s`;
@@ -1755,7 +1830,7 @@ class PageMtgGame extends TableBasePage {
<span>Commander Deaths:</span>
<div class="death-counter">
<button class="death-btn death-minus" data-player-id="${playerId}">&minus;</button>
<span class="death-display" data-player-id="${playerId}" ${attrValuePrevious}="${playerOwnDamage[flagCommanderDeaths]}">${playerOwnDamage[flagCommanderDeaths]}</span>
<span class="death-display" data-player-id="${playerId}" ${attrValuePrevious}="${totalCommanderDeaths}">${totalCommanderDeaths}</span>
<button class="death-btn death-plus" data-player-id="${playerId}">+</button>
</div>
</div>
@@ -1768,11 +1843,19 @@ class PageMtgGame extends TableBasePage {
<div class="life-total">
<input type="hidden" class="life-value" data-player-id="${playerId}" value="${life}">
<div class="life-display" data-player-id="${playerId}" ${attrValuePrevious}="${life}">${life}</div>
<div class="life-controls">
<button class="life-btn" data-player-id="${playerId}" data-amount="-5">-5</button>
<button class="life-btn" data-player-id="${playerId}" data-amount="-1">-1</button>
<button class="life-btn" data-player-id="${playerId}" data-amount="1">+1</button>
<button class="life-btn" data-player-id="${playerId}" data-amount="5">+5</button>
<label>Gain</label>
<div class="life-gain-controls">
<button class="life-gain-btn" data-player-id="${playerId}" data-amount="-5">-5</button>
<button class="life-gain-btn" data-player-id="${playerId}" data-amount="-1">-1</button>
<button class="life-gain-btn" data-player-id="${playerId}" data-amount="1">+1</button>
<button class="life-gain-btn" data-player-id="${playerId}" data-amount="5">+5</button>
</div>
<label>Loss</label>
<div class="life-loss-controls">
<button class="life-loss-btn" data-player-id="${playerId}" data-amount="-5">-5</button>
<button class="life-loss-btn" data-player-id="${playerId}" data-amount="-1">-1</button>
<button class="life-loss-btn" data-player-id="${playerId}" data-amount="1">+1</button>
<button class="life-loss-btn" data-player-id="${playerId}" data-amount="5">+5</button>
</div>
</div>
@@ -1795,13 +1878,15 @@ class PageMtgGame extends TableBasePage {
this.hookupPlayerCardEvents();
}
static renderCommanderDamageLog() {
const roundDisplayOrderLabel = PageMtgGame.getRoundDisplayOrderLabel();
const currentRoundDisplayOrder = Number(DOM.getElementValueCurrent(roundDisplayOrderLabel));
const currentRoundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder();
const damageTableBody = document.querySelector('.' + flagDamageLog + '.' + flagContainer + ' table tbody');
damageTableBody.innerHTML = '';
const previousRoundIds = rounds.filter(round => round[flagDisplayOrder] <= currentRoundDisplayOrder).map(round => round[attrRoundId]);
let newTableBodyHtml = '';
damageRecords.forEach(damage => {
if (damage[flagActive] && (damage[flagCommanderDeaths] > 0 || damage[flagHealthChange] != 0 || damage[flagIsEliminated]) && rounds.filter(r => r[attrRoundId] == damage[attrRoundId])[0][flagDisplayOrder] <= currentRoundDisplayOrder) {
if (damage[flagActive] && (damage[flagCommanderDeaths] > 0 || damage[flagLifeGain] != 0 || damage[flagLifeLoss] != 0 || damage[flagIsEliminated])
// && rounds.filter(r => r[attrRoundId] == damage[attrRoundId])[0][flagDisplayOrder] <= currentRoundDisplayOrder
&& previousRoundIds.includes(damage[attrRoundId])) {
let round = rounds.filter(r => r[attrRoundId] == damage[attrRoundId])[0];
let player = players.filter(p => p[attrPlayerId] == damage[attrPlayerId])[0];
let receivedFromPlayer = damage[attrReceivedFromCommanderPlayerId] == null ? {
@@ -1812,7 +1897,8 @@ class PageMtgGame extends TableBasePage {
<td class="${attrRoundId}">${round[flagDisplayOrder]}</td>
<td class="${attrPlayerId}">${player[flagName]}</td>
<td class="${attrReceivedFromCommanderPlayerId}">${receivedFromPlayer[flagName]}</td>
<td class="${flagHealthChange}">${damage[flagHealthChange]}</td>
<td class="${flagLifeGain}">${damage[flagLifeGain]}</td>
<td class="${flagLifeLoss}">${damage[flagLifeLoss]}</td>
<td class="${flagCommanderDeaths}">${damage[flagCommanderDeaths]}</td>
<td class="${flagIsEliminated}">${damage[flagIsEliminated]}</td>
</tr>
@@ -1828,8 +1914,10 @@ class PageMtgGame extends TableBasePage {
[attrRoundId]: roundId,
[attrPlayerId]: playerId,
[attrReceivedFromCommanderPlayerId]: receivedFromCommanderPlayerId,
[flagHealthChange]: 0,
[flagLifeGain]: 0,
[flagLifeLoss]: 0,
[flagCommanderDeaths]: 0,
[flagIsEliminated]: false,
[flagActive]: true
};
}
@@ -1852,10 +1940,13 @@ class PageMtgGame extends TableBasePage {
}
return roundId;
}
static getActiveRoundId() {
static getActiveRoundDisplayOrder() {
const roundDisplayOrderLabel = PageMtgGame.getRoundDisplayOrderLabel();
const currentRoundDisplayOrder = Number(DOM.getElementValueCurrent(roundDisplayOrderLabel));
let roundId = -1;
return Number(DOM.getElementValueCurrent(roundDisplayOrderLabel));
}
static getActiveRoundId() {
const currentRoundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder();
let roundId = 0;
if (rounds.length > 0) {
let filteredRounds = rounds.filter(round => round[flagDisplayOrder] == currentRoundDisplayOrder);
if (filteredRounds.length > 0) roundId = filteredRounds[0][attrRoundId];
@@ -1878,11 +1969,14 @@ class PageMtgGame extends TableBasePage {
return player[flagName] || `${user == null ? 'Error' : user[flagName]} - ${deck == null ? 'Error' : deck[flagName]}`;
}
static renderCommanderDamageRows(playerId) {
// const roundId = PageMtgGame.getLatestRoundId();
const activeRoundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder();
const previousRoundIds = rounds.filter(round => round[flagDisplayOrder] <= activeRoundDisplayOrder).map(round => round[attrRoundId]);
return players.filter(otherPlayer => otherPlayer[attrPlayerId] !== playerId).map(otherPlayer => {
const sourceId = otherPlayer[attrPlayerId];
let otherPlayerDisplayName = PageMtgGame.makePlayerDisplayName(sourceId);
const totalDamage = damageRecords.filter(damage => damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == sourceId).map(damage => -damage[flagHealthChange]).reduce((acc, curr) => acc + curr, 0);
const totalDamage = damageRecords.filter(damage => damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == sourceId
// && damage[attrRoundId] == roundId
&& previousRoundIds.includes(damage[attrRoundId])).map(damage => damage[flagLifeLoss]).reduce((acc, curr) => acc + curr, 0);
const isLethal = totalDamage >= 21;
return `
<div class="damage-row" data-player-id="${playerId}" data-source-id="${sourceId}">
@@ -1922,15 +2016,31 @@ class PageMtgGame extends TableBasePage {
};
}
hookupPlayerCardEvents() {
// Life buttons
let lifeButtonSelector = '.life-btn';
Events.hookupEventHandler("click", lifeButtonSelector, (event, button) => {
// Life gain buttons
let lifeGainButtonSelector = '.life-gain-btn';
Events.hookupEventHandler("click", lifeGainButtonSelector, (event, button) => {
const playerId = button.dataset.playerId;
const amount = parseInt(button.dataset.amount);
const activeRoundId = PageMtgGame.getActiveRoundId();
const damageIndex = damageRecords.findIndex(damage => damage[attrRoundId] == activeRoundId && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == null);
this.changeLife(playerId // playerId
, amount // amount
, true // isLifeGainNotLoss
, true // updateDamage
, damageIndex // damageIndex
);
});
// Life loss buttons
let lifeLossButtonSelector = '.life-loss-btn';
Events.hookupEventHandler("click", lifeLossButtonSelector, (event, button) => {
const playerId = button.dataset.playerId;
const amount = parseInt(button.dataset.amount);
const activeRoundId = PageMtgGame.getActiveRoundId();
const damageIndex = damageRecords.findIndex(damage => damage[attrRoundId] == activeRoundId && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] == null);
this.changeLife(playerId // playerId
, amount // amount
, false // isLifeGainNotLoss
, true // updateDamage
, damageIndex // damageIndex
);
@@ -1962,19 +2072,21 @@ class PageMtgGame extends TableBasePage {
this.toggleEliminate(playerId);
});
}
changeLife(playerId, amount, updateDamage = false, damageIndex = null) {
changeLife(playerId, amount, isLifeGainNotLoss = false, updateDamage = false, damageIndex = null) {
const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`);
if (!card || card.classList.contains('eliminated')) return;
// if (!card || card.classList.contains('eliminated')) return;
const lifeInput = card.querySelector(`.life-value[data-player-id="${playerId}"]`);
const lifeDisplay = card.querySelector(`.life-display[data-player-id="${playerId}"]`);
const currentLife = parseInt(lifeInput.value) || 0;
const newLife = Math.max(0, currentLife + amount);
const newLife = currentLife + amount * (isLifeGainNotLoss ? 1 : -1);
DOM.setElementAttributeValueCurrent(lifeDisplay, newLife);
DOM.isElementDirty(lifeDisplay);
lifeInput.value = newLife;
lifeDisplay.textContent = newLife;
if (updateDamage) {
damageRecords[damageIndex][flagHealthChange] += amount;
let fieldFlag = isLifeGainNotLoss ? flagLifeGain : flagLifeLoss;
damageRecords[damageIndex][fieldFlag] += amount;
}
PageMtgGame.renderCommanderDamageLog();
@@ -1983,7 +2095,8 @@ class PageMtgGame extends TableBasePage {
}
changeCommanderDamage(playerId, sourceId, amount) {
const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`);
if (!card || card.classList.contains('eliminated')) return;
// if (!card || card.classList.contains('eliminated')) return;
const damageInput = card.querySelector(`.damage-value[data-player-id="${playerId}"][data-source-id="${sourceId}"]`);
const damageDisplay = card.querySelector(`.damage-display[data-player-id="${playerId}"][data-source-id="${sourceId}"]`);
const currentDamage = parseInt(damageInput.value) || 0;
@@ -2002,9 +2115,11 @@ class PageMtgGame extends TableBasePage {
}
const activeRoundId = PageMtgGame.getActiveRoundId();
const damageIndex = damageRecords.findIndex(damageRecord => damageRecord[attrRoundId] == activeRoundId && damageRecord[attrPlayerId] == playerId && damageRecord[attrReceivedFromCommanderPlayerId] == sourceId);
damageRecords[damageIndex][flagHealthChange] -= amount;
damageRecords[damageIndex][flagLifeLoss] += amount;
let isLifeGainNotLoss = false;
this.changeLife(playerId // playerId
, -amount // amount
, isLifeGainNotLoss // isLifeGainNotLoss
, false // updateDamage
, damageIndex // damageIndex
);
@@ -2012,7 +2127,8 @@ class PageMtgGame extends TableBasePage {
}
changeCommanderDeaths(playerId, amount) {
const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`);
if (!card || card.classList.contains('eliminated')) return;
// if (!card || card.classList.contains('eliminated')) return;
const deathDisplay = card.querySelector(`.death-display[data-player-id="${playerId}"]`);
const currentDeaths = parseInt(deathDisplay.textContent) || 0;
const newDeaths = Math.max(0, currentDeaths + amount);
@@ -2064,14 +2180,14 @@ class PageMtgGame extends TableBasePage {
[flagDisplayOrder]: index
});
});
let activeRoundId = PageMtgGame.getActiveRoundId();
let activeRoundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder();
let newPlayerGridInnerHTML = '';
let playerIdA, playerIdB, isEliminatedAsIntA, isEliminatedAsIntB, playerA, playerB, indexPlayerCard;
playerCardMetas.sort((a, b) => {
playerIdA = a[attrPlayerId];
playerIdB = b[attrPlayerId];
isEliminatedAsIntA = PageMtgGame.isPlayerEliminated(playerIdA, activeRoundId) ? 1 : 0;
isEliminatedAsIntB = PageMtgGame.isPlayerEliminated(playerIdB, activeRoundId) ? 1 : 0;
isEliminatedAsIntA = PageMtgGame.isPlayerEliminated(playerIdA, activeRoundDisplayOrder) ? 1 : 0;
isEliminatedAsIntB = PageMtgGame.isPlayerEliminated(playerIdB, activeRoundDisplayOrder) ? 1 : 0;
playerA = players.filter(p => p[attrPlayerId] == playerIdA)[0];
playerB = players.filter(p => p[attrPlayerId] == playerIdB)[0];
return players.length * isEliminatedAsIntA + playerA[flagDisplayOrder] - (players.length * isEliminatedAsIntB + playerB[flagDisplayOrder]);
@@ -2085,18 +2201,31 @@ class PageMtgGame extends TableBasePage {
});
this.hookupPlayerCardEvents();
}
static isPlayerEliminated(playerId, roundId = null) {
if (roundId == null) roundId = PageMtgGame.getActiveRoundId();
let hasDamageWithIsEliminated = damageRecords.filter(damage => damage[attrRoundId] <= roundId && damage[attrPlayerId] == playerId && damage[flagIsEliminated]).length > 0;
static isPlayerEliminated(playerId, roundDisplayOrder = null) {
if (roundDisplayOrder == null) roundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder();
const filteredRoundIds = rounds.filter(round => round[flagDisplayOrder] <= roundDisplayOrder).map(round => round[attrRoundId]);
let hasDamageWithIsEliminated = damageRecords.filter(damage =>
// damage[attrRoundId] <= roundDisplayOrder
filteredRoundIds.includes(damage[attrRoundId]) && damage[attrPlayerId] == playerId && damage[flagIsEliminated]).length > 0;
let damageFromOtherPlayers = {};
let otherPlayerId;
damageRecords.filter(damage => damage[attrRoundId] <= roundId && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] != null).forEach(damage => {
damageRecords.filter(damage =>
// damage[attrRoundId] <= roundId
filteredRoundIds.includes(damage[attrRoundId]) && damage[attrPlayerId] == playerId && damage[attrReceivedFromCommanderPlayerId] != null).forEach(damage => {
otherPlayerId = damage[attrReceivedFromCommanderPlayerId];
damageFromOtherPlayers[otherPlayerId] = damage[flagHealthChange] + (damageFromOtherPlayers[otherPlayerId] == null ? 0 : damageFromOtherPlayers[otherPlayerId]);
damageFromOtherPlayers[otherPlayerId] = damage[flagLifeLoss] + (damageFromOtherPlayers[otherPlayerId] == null ? 0 : damageFromOtherPlayers[otherPlayerId]);
});
let maxDamageFromOtherCommander = Object.keys(damageFromOtherPlayers).map(playerId => damageFromOtherPlayers[playerId]).reduce((acc, cur) => Math.max(acc, cur), 0);
let totalDamageTaken = damageRecords.filter(damage =>
// damage[attrRoundId] <= roundId
filteredRoundIds.includes(damage[attrRoundId]) && damage[attrPlayerId] == playerId).map(damage => damage[flagLifeLoss] - damage[flagLifeGain]).reduce((a, b) => a + b, 0);
console.log({
roundDisplayOrder,
filteredRoundIds,
hasDamageWithIsEliminated,
maxDamageFromOtherCommander,
totalDamageTaken
});
let maxDamageFromOtherCommander = Math.max(Object.keys(damageFromOtherPlayers).map(playerId => damageFromOtherPlayers[playerId]));
// .reduce((acc, cur) => acc + cur, 0);
let totalDamageTaken = damageRecords.filter(damage => damage[attrRoundId] <= roundId && damage[attrPlayerId] == playerId).map(damage => damage[flagHealthChange]).reduce((acc, cur) => acc + cur, 0);
return hasDamageWithIsEliminated || maxDamageFromOtherCommander >= 21 || totalDamageTaken >= startingLife;
}
static updatePlayerSetup() {
@@ -2156,7 +2285,8 @@ class PageMtgGame extends TableBasePage {
nameInput = playerSetupWrapper.querySelector('.playerName input');
userId = DOM.getElementValueCurrent(userDdl);
deckId = DOM.getElementValueCurrent(deckDdl);
name = nameInput ? nameInput.value.trim() || `Player ${i + 1}` : `Player ${i + 1}`;
name = nameInput ? nameInput.value.trim() || null : null; // `Player ${i + 1}` : `Player ${i + 1}`;
playerId = playerSetupWrapper.getAttribute(attrPlayerId);
player = players.filter(p => p[attrPlayerId] == playerId)[0];
playersToSave.push({
@@ -2272,12 +2402,32 @@ class PageMtgGames extends TableBasePage {
}
initialize() {
this.sharedInitialize();
}
hookupFilters() {
/*
this.sharedHookupFilters();
this.hookupFilterActive();
*/
}
loadRowTable(rowJson) {
if (rowJson == null) return;
if (_verbose) {
utils_Utils.consoleLogIfNotProductionEnvironment("applying data row: ", rowJson);
}
}
getJsonRow(row) {
return;
}
initialiseRowNew(tbody, row) {}
postInitialiseRowNewCallback(tbody) {}
hookupTableMain() {
super.hookupTableMain();
// this.hookupTableMainRows();
this.hookupTcgGames();
// PageMtgGames.hideNewGameForm();
}
hookupTcgGames() {
this.initGamesPage();
}
initGamesPage() {
console.log("hookupTableMain PageMtgGames");
// Initialize form submission
const newGameForm = document.getElementById('newGameForm');
if (newGameForm) {
@@ -2322,7 +2472,7 @@ class PageMtgGames extends TableBasePage {
static showNewGameForm() {
const modal = document.getElementById('newGameModal');
if (modal) {
modal.classList.remove('hidden');
modal.classList.remove(flagIsCollapsed);
document.body.style.overflow = 'hidden';
// Focus on first input
@@ -2335,7 +2485,7 @@ class PageMtgGames extends TableBasePage {
static hideNewGameForm() {
const modal = document.getElementById('newGameModal');
if (modal) {
modal.classList.add('hidden');
modal.classList.add(flagIsCollapsed);
document.body.style.overflow = '';
// Reset form
@@ -2425,7 +2575,7 @@ class PageMtgGames extends TableBasePage {
if (errorLabel) {
errorLabel.textContent = message;
}
errorOverlay.classList.remove('hidden');
errorOverlay.classList.remove(flagIsCollapsed);
errorOverlay.style.display = 'flex';
} else {
// Fallback to alert
@@ -2754,6 +2904,7 @@ class PageUsers extends TableBasePage {
// Legal
@@ -2774,6 +2925,10 @@ class Router {
this.pages = {};
// Core
// TCG
this.pages[hashPageMtgDecks] = {
name: 'PageMtgDecks',
module: PageMtgDecks
};
this.pages[hashPageMtgGame] = {
name: 'PageMtgGame',
module: PageMtgGame
@@ -2818,6 +2973,7 @@ class Router {
this.routes = {};
// Core
// TCG
this.routes[hashPageMtgDecks] = (isPopState = false) => this.navigateToHash(hashPageMtgDecks, isPopState);
this.routes[hashPageMtgGame] = (isPopState = false) => this.navigateToHash(hashPageMtgGame, isPopState);
this.routes[hashPageMtgGames] = (isPopState = false) => this.navigateToHash(hashPageMtgGames, isPopState);
this.routes[hashPageMtgHome] = (isPopState = false) => this.navigateToHash(hashPageMtgHome, isPopState);

File diff suppressed because one or more lines are too long

17
static/dist/js/tcg_decks.bundle.js vendored Normal file
View File

@@ -0,0 +1,17 @@
/******/ (() => { // webpackBootstrap
/******/ "use strict";
// This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules.
(() => {
// extracted by mini-css-extract-plugin
})();
// This entry needs to be wrapped in an IIFE because it needs to be isolated against other entry modules.
(() => {
// extracted by mini-css-extract-plugin
})();
/******/ })()
;
//# sourceMappingURL=tcg_decks.bundle.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"js/tcg_decks.bundle.js","mappings":";;;;AAAA;;;;;;ACAA","sources":["webpack://app/./static/css/sections/tcg.css?b213","webpack://app/./static/css/pages/tcg/decks.css?c1be"],"sourcesContent":["// extracted by mini-css-extract-plugin\nexport {};","// extracted by mini-css-extract-plugin\nexport {};"],"names":[],"sourceRoot":""}

View File

@@ -150,6 +150,7 @@ export default class BasePage {
toggleShowButtonsSaveCancel(show, buttonContainerSelector = null) { // , buttonSave = null, buttonCancel = null
if (Validation.isEmpty(buttonContainerSelector)) buttonContainerSelector = '.' + flagContainer + '.' + flagSave + '.' + flagCancel;
let buttonSave = document.querySelector(buttonContainerSelector + ' ' + idButtonSave);
if (buttonSave == null) return;
let buttonCancel = document.querySelector(buttonContainerSelector + ' ' + idButtonCancel);
Utils.consoleLogIfNotProductionEnvironment({ show, buttonContainerSelector, buttonCancel, buttonSave });
if (show) {

View File

@@ -335,6 +335,7 @@ export default class TableBasePage extends BasePage {
cacheRowBlank() {
let selectorRowNew = idTableMain + ' tbody tr.' + flagRowNew;
let rowBlankTemp = document.querySelector(selectorRowNew);
if (rowBlankTemp == null) return;
Utils.consoleLogIfNotProductionEnvironment("row blank temp: ", rowBlankTemp);
let countRows = document.querySelectorAll(idTableMain + ' > tbody > tr').length;
_rowBlank = rowBlankTemp.cloneNode(true);
@@ -734,11 +735,15 @@ export default class TableBasePage extends BasePage {
}
updateAndToggleShowButtonsSaveCancel() {
let pageBody = document.querySelector(idPageBody);
// let pageBody = document.querySelector(idPageBody);
let isDirty = DOM.hasDirtyChildrenContainer(pageBody);
console.log({ pageBody, isDirty });
let buttonContainerSelector = '.' + flagContainer + '.' + flagSave + '.' + flagCancel;
let buttonSave = document.querySelector(buttonContainerSelector + ' ' + idButtonSave);
let areVisibleSaveCancelButtons = !buttonSave.classList.contains(flagIsCollapsed);
this.toggleShowButtonsSaveCancel(isDirty);
console.log({ pageBody, isDirty, areVisibleSaveCancelButtons });
this.toggleShowButtonsSaveCancel(isDirty || areVisibleSaveCancelButtons);
}
}

View File

@@ -0,0 +1,110 @@
import API from "../../api.js";
import Events from "../../lib/events.js";
import TableBasePage from "../base_table.js";
import Utils from "../../lib/utils.js";
export default class PageMtgDecks extends TableBasePage {
static hash = hashPageMtgDecks;
static attrIdRowObject = attrDeckId;
callSaveTableContent = API.saveDeck;
constructor(router) {
super(router);
}
initialize() {
this.sharedInitialize();
}
hookupFilters() {
/*
this.sharedHookupFilters();
this.hookupFilterActive();
*/
}
loadRowTable(rowJson) {
if (rowJson == null) return;
if (_verbose) { Utils.consoleLogIfNotProductionEnvironment("applying data row: ", rowJson); }
}
getJsonRow(row) {
return;
}
initialiseRowNew(tbody, row) {
}
postInitialiseRowNewCallback(tbody) {
let newRows = tbody.querySelectorAll('tr.' + flagRowNew);
let newestRow = newRows[0];
let clickableElementsSelector = [
'td.' + attrCommanderBracketId + ' div.' + attrCommanderBracketId
].join('');
newestRow.querySelectorAll(clickableElementsSelector).forEach((clickableElement) => {
clickableElement.click();
});
}
hookupTableMain() {
super.hookupTableMain();
this.hookupTableMainRows();
this.hookupFieldsNameTable();
this.hookupTableMainIsCommanderCheckboxes();
this.hookupTableMainCommanderBracketPreviews();
this.hookupFieldsActive();
}
hookupTableMainRows() {
return;
let rowSelector = 'table.' + flagTableMain + ' tbody tr';
Events.hookupEventHandler("click", rowSelector, (event, row) => {
let isRowExpanded = row.classList.contains(flagActive);
let showSection;
if (isRowExpanded) {
showSection = false;
PageMtgDecks.toggleShowDeckStatisticsSection(showSection);
}
else {
showSection = true;
let deckId = row.getAttribute(attrDeckId);
let statisticsSectionTableBody = document.querySelector('table.' + flagStatistics + ' tbody');
statisticsSectionTableBody.innerHTML = '';
let deck = decks.filter(d => d[attrDeckId] == deckId)[0];
if (deck[flagStatistics].length > 0) {
let newStatisticRowsHtml = '';
deck[flagStatistics]
.sort((a, b) => a[flagDisplayOrder] - b[flagDisplayOrder])
.forEach((statistic) => {
newStatisticRowsHtml += `
<tr ${attrStatisticId}="${statistic[attrStatisticId]}">
<td class="${flagName}">${statistic[flagName]}</td>
<td class="${flagValue}">${statistic[flagValue]}</td>
</tr>
`;
});
statisticsSectionTableBody.innerHTML = newStatisticRowsHtml;
}
PageMtgDecks.toggleShowDeckStatisticsSection(showSection);
}
});
}
static toggleShowDeckStatisticsSection(showSection) {
let statisticsSectionTableBody = document.querySelector('table.' + flagStatistics + ' tbody');
if (showSection) {
statisticsSectionTableBody.classList.remove(flagIsCollapsed);
}
else {
statisticsSectionTableBody.classList.add(flagIsCollapsed);
}
}
hookupTableMainIsCommanderCheckboxes() {
this.hookupChangeHandlerTableCells(idTableMain + ' td.' + flagIsCommander + ' .' + flagIsCommander);
}
hookupTableMainCommanderBracketPreviews() {
this.hookupTableCellDdlPreviews(
attrCommanderBracketId
, Utils.getListFromDict(commanderBrackets)
);
}
leave() {
super.leave();
}
}

View File

@@ -196,29 +196,18 @@ export default class PageMtgGame extends TableBasePage {
const grid = document.getElementById('playersGrid');
grid.innerHTML = '';
// Build a damage lookup: { playerId: { fromPlayerId: damageAmount } }
/*
const damageLookup = {};
damageRecords.forEach(damage => {
if (!damageLookup[damage.player_id]) {
damageLookup[damage.player_id] = {};
}
if (damage.received_from_commander_player_id) {
damageLookup[damage.player_id][damage.received_from_commander_player_id] = damage.health_change || 0;
}
});
*/
let activeRoundId = PageMtgGame.getActiveRoundId();
// let activeRoundId = PageMtgGame.getActiveRoundId();
const roundDisplayOrderLabel = PageMtgGame.getRoundDisplayOrderLabel();
if (activeRoundId < 0) {
const currentRoundDisplayOrder = Number(DOM.getElementValueCurrent(roundDisplayOrderLabel));
rounds.push(PageMtgGame.makeDefaultGameRound(currentRoundDisplayOrder));
activeRoundId = PageMtgGame.getActiveRoundId();
let activeRound = rounds.filter(round => round[flagDisplayOrder] == currentRoundDisplayOrder)[0];
if (activeRound == null) {
activeRound = PageMtgGame.makeDefaultGameRound(currentRoundDisplayOrder);
rounds.push(activeRound);
}
const latestRound = rounds.filter(round => round[attrRoundId] == activeRoundId)[0];
DOM.setElementValueCurrent(roundDisplayOrderLabel, latestRound[flagDisplayOrder]);
DOM.setElementValueCurrent(roundDisplayOrderLabel, activeRound[flagDisplayOrder]);
const previousRoundIds = rounds.filter(round => round[flagDisplayOrder] <= currentRoundDisplayOrder)
.map(round => round[attrRoundId]);
players.forEach((player, index) => {
// Build display name: prefer user_name + deck_name, fallback to player name
const playerId = player[attrPlayerId];
@@ -228,7 +217,8 @@ export default class PageMtgGame extends TableBasePage {
damagePlayerPairs.forEach(damagePlayerPair => {
const sourceId = damagePlayerPair[attrPlayerId];
const filteredPlayerDamages = damageRecords.filter(damage => (
damage[attrRoundId] == activeRoundId
damage[attrRoundId] == activeRound[attrRoundId]
// previousRoundIds.includes(damage[attrRoundId])
&& damage[attrPlayerId] == playerId
&& damage[attrReceivedFromCommanderPlayerId] == sourceId
)); //[playerId] || {};
@@ -240,20 +230,30 @@ export default class PageMtgGame extends TableBasePage {
, damageRecords.filter(damage => (
damage[attrPlayerId] == playerId
&& damage[attrReceivedFromCommanderPlayerId] == sourceId
&& damage[attrReceivedFromCommanderPlayerId] != null
&& previousRoundIds.includes(damage[attrRoundId])
))
.map(damage => damage[flagHealthChange])
.reduce((acc, curr) => acc + curr, 0)
.map(damage => damage[flagLifeLoss])
.reduce((a, b) => a + b, 0)
);
});
const totalDamage = damageRecords.filter(damage => ( damage[attrPlayerId] == playerId ))
.map(damage => damage[flagHealthChange])
.reduce((acc, curr) => acc + curr, 0);
let life = startingLife + totalDamage;
const totalDamage = damageRecords.filter(damage => (
damage[attrPlayerId] == playerId
&& previousRoundIds.includes(damage[attrRoundId])
))
.map(damage => damage[flagLifeLoss] - damage[flagLifeGain])
.reduce((a, b) => a + b, 0);
let life = startingLife - totalDamage;
let isEliminatedByForce = damageRecords.filter(damage => ( damage[attrPlayerId] == playerId ))
let isEliminatedByForce = damageRecords.filter(damage => (
damage[attrPlayerId] == playerId
&& previousRoundIds.includes(damage[attrRoundId])
))
.map(damage => damage[flagIsEliminated])
.some(Boolean);
console.log("renderPlayers");
console.log({isEliminatedByForce, player, life, maxCommanderDamageReceived});
const isEliminated = (
isEliminatedByForce
|| !player[flagActive]
@@ -261,11 +261,13 @@ export default class PageMtgGame extends TableBasePage {
|| maxCommanderDamageReceived >= 21
);
const playerOwnDamage = damageRecords.filter(damage => (
const totalCommanderDeaths = damageRecords.filter(damage => (
damage[attrPlayerId] == playerId
&& damage[attrReceivedFromCommanderPlayerId] == null
&& damage[attrRoundId] == activeRoundId
))[0];
&& damage[attrRoundId] == activeRound[attrRoundId]
))
.map(damage => damage[flagCommanderDeaths])
.reduce((a, b) => a + b, 0);
const card = document.createElement('div');
card.className = `player-card ${isEliminated ? 'eliminated' : ''}`;
card.style.animationDelay = `${index * 0.1}s`;
@@ -281,7 +283,7 @@ export default class PageMtgGame extends TableBasePage {
<span>Commander Deaths:</span>
<div class="death-counter">
<button class="death-btn death-minus" data-player-id="${playerId}">&minus;</button>
<span class="death-display" data-player-id="${playerId}" ${attrValuePrevious}="${playerOwnDamage[flagCommanderDeaths]}">${playerOwnDamage[flagCommanderDeaths]}</span>
<span class="death-display" data-player-id="${playerId}" ${attrValuePrevious}="${totalCommanderDeaths}">${totalCommanderDeaths}</span>
<button class="death-btn death-plus" data-player-id="${playerId}">+</button>
</div>
</div>
@@ -294,11 +296,19 @@ export default class PageMtgGame extends TableBasePage {
<div class="life-total">
<input type="hidden" class="life-value" data-player-id="${playerId}" value="${life}">
<div class="life-display" data-player-id="${playerId}" ${attrValuePrevious}="${life}">${life}</div>
<div class="life-controls">
<button class="life-btn" data-player-id="${playerId}" data-amount="-5">-5</button>
<button class="life-btn" data-player-id="${playerId}" data-amount="-1">-1</button>
<button class="life-btn" data-player-id="${playerId}" data-amount="1">+1</button>
<button class="life-btn" data-player-id="${playerId}" data-amount="5">+5</button>
<label>Gain</label>
<div class="life-gain-controls">
<button class="life-gain-btn" data-player-id="${playerId}" data-amount="-5">-5</button>
<button class="life-gain-btn" data-player-id="${playerId}" data-amount="-1">-1</button>
<button class="life-gain-btn" data-player-id="${playerId}" data-amount="1">+1</button>
<button class="life-gain-btn" data-player-id="${playerId}" data-amount="5">+5</button>
</div>
<label>Loss</label>
<div class="life-loss-controls">
<button class="life-loss-btn" data-player-id="${playerId}" data-amount="-5">-5</button>
<button class="life-loss-btn" data-player-id="${playerId}" data-amount="-1">-1</button>
<button class="life-loss-btn" data-player-id="${playerId}" data-amount="1">+1</button>
<button class="life-loss-btn" data-player-id="${playerId}" data-amount="5">+5</button>
</div>
</div>
@@ -325,22 +335,25 @@ export default class PageMtgGame extends TableBasePage {
this.hookupPlayerCardEvents();
}
static renderCommanderDamageLog() {
const roundDisplayOrderLabel = PageMtgGame.getRoundDisplayOrderLabel();
const currentRoundDisplayOrder = Number(DOM.getElementValueCurrent(roundDisplayOrderLabel));
const currentRoundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder();
const damageTableBody = document.querySelector('.' + flagDamageLog + '.' + flagContainer + ' table tbody');
damageTableBody.innerHTML = '';
const previousRoundIds = rounds.filter(round => round[flagDisplayOrder] <= currentRoundDisplayOrder)
.map(round => round[attrRoundId]);
let newTableBodyHtml = '';
damageRecords.forEach((damage) => {
if (
damage[flagActive]
&& (
damage[flagCommanderDeaths] > 0
|| damage[flagHealthChange] != 0
|| damage[flagLifeGain] != 0
|| damage[flagLifeLoss] != 0
|| damage[flagIsEliminated]
)
&& rounds.filter(r => r[attrRoundId] == damage[attrRoundId])[0][flagDisplayOrder] <= currentRoundDisplayOrder
// && rounds.filter(r => r[attrRoundId] == damage[attrRoundId])[0][flagDisplayOrder] <= currentRoundDisplayOrder
&& previousRoundIds.includes(damage[attrRoundId])
) {
let round = rounds.filter(r => r[attrRoundId] == damage[attrRoundId])[0];
let player = players.filter(p => p[attrPlayerId] == damage[attrPlayerId])[0];
@@ -350,7 +363,8 @@ export default class PageMtgGame extends TableBasePage {
<td class="${attrRoundId}">${round[flagDisplayOrder]}</td>
<td class="${attrPlayerId}">${player[flagName]}</td>
<td class="${attrReceivedFromCommanderPlayerId}">${receivedFromPlayer[flagName]}</td>
<td class="${flagHealthChange}">${damage[flagHealthChange]}</td>
<td class="${flagLifeGain}">${damage[flagLifeGain]}</td>
<td class="${flagLifeLoss}">${damage[flagLifeLoss]}</td>
<td class="${flagCommanderDeaths}">${damage[flagCommanderDeaths]}</td>
<td class="${flagIsEliminated}">${damage[flagIsEliminated]}</td>
</tr>
@@ -366,8 +380,10 @@ export default class PageMtgGame extends TableBasePage {
, [attrRoundId]: roundId
, [attrPlayerId]: playerId
, [attrReceivedFromCommanderPlayerId]: receivedFromCommanderPlayerId
, [flagHealthChange]: 0
, [flagLifeGain]: 0
, [flagLifeLoss]: 0
, [flagCommanderDeaths]: 0
, [flagIsEliminated]: false
, [flagActive]: true
};
}
@@ -384,10 +400,13 @@ export default class PageMtgGame extends TableBasePage {
}
return roundId;
}
static getActiveRoundId() {
static getActiveRoundDisplayOrder() {
const roundDisplayOrderLabel = PageMtgGame.getRoundDisplayOrderLabel();
const currentRoundDisplayOrder = Number(DOM.getElementValueCurrent(roundDisplayOrderLabel));
let roundId = -1;
return Number(DOM.getElementValueCurrent(roundDisplayOrderLabel));
}
static getActiveRoundId() {
const currentRoundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder();
let roundId = 0;
if (rounds.length > 0) {
let filteredRounds = rounds.filter(round => round[flagDisplayOrder] == currentRoundDisplayOrder);
if (filteredRounds.length > 0) roundId = filteredRounds[0][attrRoundId];
@@ -406,7 +425,9 @@ export default class PageMtgGame extends TableBasePage {
return player[flagName] || `${(user == null) ? 'Error' : user[flagName]} - ${(deck == null) ? 'Error' : deck[flagName]}`;
}
static renderCommanderDamageRows(playerId) {
// const roundId = PageMtgGame.getLatestRoundId();
const activeRoundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder();
const previousRoundIds = rounds.filter(round => round[flagDisplayOrder] <= activeRoundDisplayOrder)
.map(round => round[attrRoundId]);
return players
.filter(otherPlayer => otherPlayer[attrPlayerId] !== playerId)
.map(otherPlayer => {
@@ -415,10 +436,12 @@ export default class PageMtgGame extends TableBasePage {
const totalDamage = damageRecords.filter(damage => (
damage[attrPlayerId] == playerId
&& damage[attrReceivedFromCommanderPlayerId] == sourceId
// && damage[attrRoundId] == roundId
&& previousRoundIds.includes(damage[attrRoundId])
))
.map(damage => -damage[flagHealthChange])
.map(damage => damage[flagLifeLoss])
.reduce((acc, curr) => acc + curr, 0);
const isLethal = totalDamage >= 21;
const isLethal = (totalDamage >= 21);
return `
<div class="damage-row" data-player-id="${playerId}" data-source-id="${sourceId}">
@@ -460,9 +483,9 @@ export default class PageMtgGame extends TableBasePage {
};
}
hookupPlayerCardEvents() {
// Life buttons
let lifeButtonSelector = '.life-btn';
Events.hookupEventHandler("click", lifeButtonSelector, (event, button) => {
// Life gain buttons
let lifeGainButtonSelector = '.life-gain-btn';
Events.hookupEventHandler("click", lifeGainButtonSelector, (event, button) => {
const playerId = button.dataset.playerId;
const amount = parseInt(button.dataset.amount);
const activeRoundId = PageMtgGame.getActiveRoundId();
@@ -474,6 +497,27 @@ export default class PageMtgGame extends TableBasePage {
this.changeLife(
playerId // playerId
, amount // amount
, true // isLifeGainNotLoss
, true // updateDamage
, damageIndex // damageIndex
);
});
// Life loss buttons
let lifeLossButtonSelector = '.life-loss-btn';
Events.hookupEventHandler("click", lifeLossButtonSelector, (event, button) => {
const playerId = button.dataset.playerId;
const amount = parseInt(button.dataset.amount);
const activeRoundId = PageMtgGame.getActiveRoundId();
const damageIndex = damageRecords.findIndex(damage => (
damage[attrRoundId] == activeRoundId
&& damage[attrPlayerId] == playerId
&& damage[attrReceivedFromCommanderPlayerId] == null
));
this.changeLife(
playerId // playerId
, amount // amount
, false // isLifeGainNotLoss
, true // updateDamage
, damageIndex // damageIndex
);
@@ -506,22 +550,24 @@ export default class PageMtgGame extends TableBasePage {
});
}
changeLife(playerId, amount, updateDamage = false, damageIndex = null) {
changeLife(playerId, amount, isLifeGainNotLoss = false, updateDamage = false, damageIndex = null) {
const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`);
if (!card || card.classList.contains('eliminated')) return;
// if (!card || card.classList.contains('eliminated')) return;
const lifeInput = card.querySelector(`.life-value[data-player-id="${playerId}"]`);
const lifeDisplay = card.querySelector(`.life-display[data-player-id="${playerId}"]`);
const currentLife = parseInt(lifeInput.value) || 0;
const newLife = Math.max(0, currentLife + amount);
const newLife = currentLife + amount * ((isLifeGainNotLoss) ? 1 : -1);
DOM.setElementAttributeValueCurrent(lifeDisplay, newLife);
DOM.isElementDirty(lifeDisplay);
lifeInput.value = newLife;
lifeDisplay.textContent = newLife;
if (updateDamage) {
damageRecords[damageIndex][flagHealthChange] += amount;
let fieldFlag = (isLifeGainNotLoss) ? flagLifeGain : flagLifeLoss;
damageRecords[damageIndex][fieldFlag] += amount;
}
PageMtgGame.renderCommanderDamageLog();
@@ -532,7 +578,7 @@ export default class PageMtgGame extends TableBasePage {
changeCommanderDamage(playerId, sourceId, amount) {
const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`);
if (!card || card.classList.contains('eliminated')) return;
// if (!card || card.classList.contains('eliminated')) return;
const damageInput = card.querySelector(`.damage-value[data-player-id="${playerId}"][data-source-id="${sourceId}"]`);
const damageDisplay = card.querySelector(`.damage-display[data-player-id="${playerId}"][data-source-id="${sourceId}"]`);
@@ -558,11 +604,13 @@ export default class PageMtgGame extends TableBasePage {
&& damageRecord[attrPlayerId] == playerId
&& damageRecord[attrReceivedFromCommanderPlayerId] == sourceId
));
damageRecords[damageIndex][flagHealthChange] -= amount;
damageRecords[damageIndex][flagLifeLoss] += amount;
let isLifeGainNotLoss = false;
this.changeLife(
playerId // playerId
, -amount // amount
, isLifeGainNotLoss // isLifeGainNotLoss
, false // updateDamage
, damageIndex // damageIndex
);
@@ -571,7 +619,7 @@ export default class PageMtgGame extends TableBasePage {
changeCommanderDeaths(playerId, amount) {
const card = document.querySelector(`.player-card[data-player-id="${playerId}"]`);
if (!card || card.classList.contains('eliminated')) return;
// if (!card || card.classList.contains('eliminated')) return;
const deathDisplay = card.querySelector(`.death-display[data-player-id="${playerId}"]`);
const currentDeaths = parseInt(deathDisplay.textContent) || 0;
@@ -641,14 +689,14 @@ export default class PageMtgGame extends TableBasePage {
, [flagDisplayOrder]: index
});
});
let activeRoundId = PageMtgGame.getActiveRoundId();
let activeRoundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder();
let newPlayerGridInnerHTML = '';
let playerIdA, playerIdB, isEliminatedAsIntA, isEliminatedAsIntB, playerA, playerB, indexPlayerCard;
playerCardMetas.sort((a, b) => {
playerIdA = a[attrPlayerId];
playerIdB = b[attrPlayerId];
isEliminatedAsIntA = PageMtgGame.isPlayerEliminated(playerIdA, activeRoundId) ? 1 : 0;
isEliminatedAsIntB = PageMtgGame.isPlayerEliminated(playerIdB, activeRoundId) ? 1 : 0;
isEliminatedAsIntA = PageMtgGame.isPlayerEliminated(playerIdA, activeRoundDisplayOrder) ? 1 : 0;
isEliminatedAsIntB = PageMtgGame.isPlayerEliminated(playerIdB, activeRoundDisplayOrder) ? 1 : 0;
playerA = players.filter(p => p[attrPlayerId] == playerIdA)[0];
playerB = players.filter(p => p[attrPlayerId] == playerIdB)[0];
return (
@@ -671,35 +719,41 @@ export default class PageMtgGame extends TableBasePage {
this.hookupPlayerCardEvents();
}
static isPlayerEliminated(playerId, roundId = null) {
if (roundId == null) roundId = PageMtgGame.getActiveRoundId();
static isPlayerEliminated(playerId, roundDisplayOrder = null) {
if (roundDisplayOrder == null) roundDisplayOrder = PageMtgGame.getActiveRoundDisplayOrder();
const filteredRoundIds = rounds.filter(round => round[flagDisplayOrder] <= roundDisplayOrder)
.map(round => round[attrRoundId]);
let hasDamageWithIsEliminated = damageRecords.filter(damage => (
damage[attrRoundId] <= roundId
// damage[attrRoundId] <= roundDisplayOrder
filteredRoundIds.includes(damage[attrRoundId])
&& damage[attrPlayerId] == playerId
&& damage[flagIsEliminated]
)).length > 0;
let damageFromOtherPlayers = {};
let otherPlayerId;
damageRecords.filter(damage => (
damage[attrRoundId] <= roundId
// damage[attrRoundId] <= roundId
filteredRoundIds.includes(damage[attrRoundId])
&& damage[attrPlayerId] == playerId
&& damage[attrReceivedFromCommanderPlayerId] != null
))
.forEach((damage) => {
otherPlayerId = damage[attrReceivedFromCommanderPlayerId];
damageFromOtherPlayers[otherPlayerId] =
damage[flagHealthChange]
damage[flagLifeLoss]
+ ((damageFromOtherPlayers[otherPlayerId] == null) ? 0 : damageFromOtherPlayers[otherPlayerId]);
});
let maxDamageFromOtherCommander = Math.max(Object.keys(damageFromOtherPlayers)
.map((playerId) => damageFromOtherPlayers[playerId]));
// .reduce((acc, cur) => acc + cur, 0);
let maxDamageFromOtherCommander = Object.keys(damageFromOtherPlayers)
.map((playerId) => damageFromOtherPlayers[playerId])
.reduce((acc, cur) => Math.max(acc, cur), 0);
let totalDamageTaken = damageRecords.filter(damage => (
damage[attrRoundId] <= roundId
// damage[attrRoundId] <= roundId
filteredRoundIds.includes(damage[attrRoundId])
&& damage[attrPlayerId] == playerId
))
.map((damage) => damage[flagHealthChange])
.reduce((acc, cur) => acc + cur, 0);
.map((damage) => damage[flagLifeLoss] - damage[flagLifeGain])
.reduce((a, b) => a + b, 0);
console.log({ roundDisplayOrder, filteredRoundIds, hasDamageWithIsEliminated, maxDamageFromOtherCommander, totalDamageTaken });
return (
hasDamageWithIsEliminated
|| maxDamageFromOtherCommander >= 21
@@ -771,7 +825,7 @@ export default class PageMtgGame extends TableBasePage {
userId = DOM.getElementValueCurrent(userDdl);
deckId = DOM.getElementValueCurrent(deckDdl);
name = nameInput ? nameInput.value.trim() || `Player ${i + 1}` : `Player ${i + 1}`;
name = nameInput ? nameInput.value.trim() || null : null; // `Player ${i + 1}` : `Player ${i + 1}`;
playerId = playerSetupWrapper.getAttribute(attrPlayerId);
player = players.filter(p => p[attrPlayerId] == playerId)[0];

View File

@@ -14,12 +14,34 @@ export default class PageMtgGames extends TableBasePage {
initialize() {
this.sharedInitialize();
}
hookupFilters() {
/*
this.sharedHookupFilters();
this.hookupFilterActive();
*/
}
loadRowTable(rowJson) {
if (rowJson == null) return;
if (_verbose) { Utils.consoleLogIfNotProductionEnvironment("applying data row: ", rowJson); }
}
getJsonRow(row) {
return;
}
initialiseRowNew(tbody, row) {
}
postInitialiseRowNewCallback(tbody) {
}
hookupTableMain() {
super.hookupTableMain();
// this.hookupTableMainRows();
this.hookupTcgGames();
// PageMtgGames.hideNewGameForm();
}
hookupTcgGames() {
this.initGamesPage();
}
initGamesPage() {
console.log("hookupTableMain PageMtgGames");
// Initialize form submission
const newGameForm = document.getElementById('newGameForm');
if (newGameForm) {
@@ -69,7 +91,7 @@ export default class PageMtgGames extends TableBasePage {
static showNewGameForm() {
const modal = document.getElementById('newGameModal');
if (modal) {
modal.classList.remove('hidden');
modal.classList.remove(flagIsCollapsed);
document.body.style.overflow = 'hidden';
// Focus on first input
@@ -82,7 +104,7 @@ export default class PageMtgGames extends TableBasePage {
static hideNewGameForm() {
const modal = document.getElementById('newGameModal');
if (modal) {
modal.classList.add('hidden');
modal.classList.add(flagIsCollapsed);
document.body.style.overflow = '';
// Reset form
@@ -182,7 +204,7 @@ export default class PageMtgGames extends TableBasePage {
if (errorLabel) {
errorLabel.textContent = message;
}
errorOverlay.classList.remove('hidden');
errorOverlay.classList.remove(flagIsCollapsed);
errorOverlay.style.display = 'flex';
} else {
// Fallback to alert

View File

@@ -2,6 +2,7 @@
// Pages
// Core
// TCG
import PageMtgDecks from './pages/tcg/mtg_decks.js';
import PageMtgGame from './pages/tcg/mtg_game.js';
import PageMtgGames from './pages/tcg/mtg_games.js';
import PageMtgHome from './pages/tcg/mtg_home.js';
@@ -28,6 +29,7 @@ export default class Router {
this.pages = {};
// Core
// TCG
this.pages[hashPageMtgDecks] = { name: 'PageMtgDecks', module: PageMtgDecks };
this.pages[hashPageMtgGame] = { name: 'PageMtgGame', module: PageMtgGame };
this.pages[hashPageMtgGames] = { name: 'PageMtgGames', module: PageMtgGames };
this.pages[hashPageMtgHome] = { name: 'PageMtgGame', module: PageMtgHome };
@@ -45,6 +47,7 @@ export default class Router {
this.routes = {};
// Core
// TCG
this.routes[hashPageMtgDecks] = (isPopState = false) => this.navigateToHash(hashPageMtgDecks, isPopState);
this.routes[hashPageMtgGame] = (isPopState = false) => this.navigateToHash(hashPageMtgGame, isPopState);
this.routes[hashPageMtgGames] = (isPopState = false) => this.navigateToHash(hashPageMtgGames, isPopState);
this.routes[hashPageMtgHome] = (isPopState = false) => this.navigateToHash(hashPageMtgHome, isPopState);

View File

@@ -0,0 +1,5 @@
{% set value_previous = '0' if is_blank_row else commander_bracket_preview.commander_bracket_id %}
{% set text_previous = '' if is_blank_row else commander_bracket_preview.name %}
<div class="{{ model.ATTR_COMMANDER_BRACKET_ID }}" {{ model.ATTR_VALUE_PREVIOUS }}="{{ value_previous }}" {{ model.ATTR_VALUE_CURRENT }}="{{ value_previous }}">{{ text_previous }}</div>

View File

@@ -0,0 +1,44 @@
{% if is_blank_row %}
<tr class="{{ model.FLAG_ROW_NEW }} {{ model.FLAG_DECK }}" {{ model.ATTR_DECK_ID }}>
<td class="{{ model.FLAG_NAME }}">
<input type="text"
class="{{ model.FLAG_NAME }}"
{{ model.ATTR_VALUE_CURRENT }} {{ model.ATTR_VALUE_PREVIOUS }} />
</td>
<td class="{{ model.FLAG_IS_COMMANDER }}">
<input type="checkbox"
class="{{ model.FLAG_IS_COMMANDER }}"
{{ model.ATTR_VALUE_CURRENT }} {{ model.ATTR_VALUE_PREVIOUS }} />
</td>
<td class="{{ model.ATTR_COMMANDER_BRACKET_ID }} {{ model.FLAG_DDL_PREVIEW }}">
{% include 'components/tcg/_preview_ddl_commander_bracket.html' %}
</td>
{% set active = True %}
{% include 'components/common/inputs/_td_active.html' %}
</tr>
{% else %}
<tr class="{{ model.FLAG_DECK }}" {{ model.ATTR_DECK_ID }}="{{ deck.deck_id }}">
<td class="{{ model.FLAG_NAME }}">
<input type="text"
class="{{ model.FLAG_NAME }}"
{{ model.ATTR_VALUE_CURRENT }}="{{ deck.name }}"
{{ model.ATTR_VALUE_PREVIOUS }}="{{ deck.name }}"
value="{{ deck.name }}" />
</td>
<td class="{{ model.FLAG_IS_COMMANDER }}">
<input type="checkbox"
class="{{ model.FLAG_IS_COMMANDER }}"
{{ model.ATTR_VALUE_CURRENT }}="{{ deck.is_commander | lower }}"
{{ model.ATTR_VALUE_PREVIOUS }}="{{ deck.is_commander | lower }}"
{% if deck.is_commander %}checked{% endif %}
value="{{ deck.is_commander | lower }}" />
</td>
{% set commander_bracket_preview = deck.commander_bracket %}
<td class="{{ model.ATTR_COMMANDER_BRACKET_ID }} {{ model.FLAG_DDL_PREVIEW }}">
{% include 'components/tcg/_preview_ddl_commander_bracket.html' %}
</td>
{% set active = deck.active %}
{% include 'components/common/inputs/_td_active.html' %}
</tr>
{% endif %}

View File

@@ -1,23 +1,36 @@
<script>
var attrCommanderBracketId = "{{ model.ATTR_COMMANDER_BRACKET_ID }}";
var attrDamageId = "{{ model.ATTR_DAMAGE_ID }}";
var attrDeckId = "{{ model.ATTR_DECK_ID }}";
var attrGameId = "{{ model.ATTR_GAME_ID }}";
var attrPlayerId = "{{ model.ATTR_PLAYER_ID }}";
var attrRoundId = "{{ model.ATTR_ROUND_ID }}";
var attrDamageId = "{{ model.ATTR_DAMAGE_ID }}";
var attrDeckId = "{{ model.ATTR_DECK_ID }}";
var attrUserId = "{{ model.ATTR_USER_ID }}";
var attrUserAuth0Id = "{{ model.ATTR_USER_AUTH0_ID }}";
var flagGame = "{{ model.FLAG_GAME }}";
var flagPlayer = "{{ model.FLAG_PLAYER }}";
var flagRound = "{{ model.FLAG_ROUND }}";
var flagCommanderDeaths = "{{ model.FLAG_COMMANDER_DEATHS }}";
var flagDamage = "{{ model.FLAG_DAMAGE }}";
var flagDeck = "{{ model.FLAG_DECK }}";
var flagGame = "{{ model.FLAG_GAME }}";
var flagIsBool = "{{ model.FLAG_IS_BOOL }}";
var flagIsCommander = "{{ model.FLAG_IS_COMMANDER }}";
var flagIsDraft = "{{ model.FLAG_IS_DRAFT }}";
var flagIsEliminated = "{{ model.FLAG_IS_ELIMINATED }}";
var flagIsFloat = "{{ model.FLAG_IS_FLOAT }}";
var flagIsInterval = "{{ model.FLAG_IS_INTERVAL }}";
var flagIsSealed = "{{ model.FLAG_IS_SEALED }}";
var flagHealthChange = "{{ model.FLAG_HEALTH_CHANGE }}";
var flagCommanderDeaths = "{{ model.FLAG_COMMANDER_DEATHS }}";
var flagIsText = "{{ model.FLAG_IS_TEXT }}";
var flagIsTimestamp = "{{ model.FLAG_IS_TIMESTAMP }}";
var flagLifeGain = "{{ model.FLAG_LIFE_GAIN }}";
var flagLifeLoss = "{{ model.FLAG_LIFE_LOSS }}";
var flagPlayer = "{{ model.FLAG_PLAYER }}";
var flagRound = "{{ model.FLAG_ROUND }}";
var flagUser = "{{ model.FLAG_USER }}";
var flagValueBool = "{{ model.FLAG_VALUE_BOOL }}";
var flagValueFloat = "{{ model.FLAG_VALUE_FLOAT }}";
var flagValueInterval = "{{ model.FLAG_VALUE_INTERVAL }}";
var flagValueText = "{{ model.FLAG_VALUE_TEXT }}";
var flagValueTimestamp = "{{ model.FLAG_VALUE_TIMESTAMP }}";
var hashSaveMtgGame = "{{ model.HASH_SAVE_MTG_GAME }}";
var hashSaveMtgGamePlayer = "{{ model.HASH_SAVE_MTG_GAME_PLAYER }}";
var hashSaveMtgGameRound = "{{ model.HASH_SAVE_MTG_GAME_ROUND }}";

View File

@@ -102,6 +102,7 @@
var flagTableMain = "{{ model.FLAG_TABLE_MAIN }}";
var flagTemporaryElement = "{{ model.FLAG_TEMPORARY_ELEMENT }}";
var flagUser = "{{ model.FLAG_USER }}";
var flagValue = "{{ model.FLAG_VALUE }}";
var flagWebsite = "{{ model.FLAG_WEBSITE }}";
var hashGetALTCHAChallenge = "{{ model.HASH_GET_ALTCHA_CHALLENGE }}";
var hashPageAccessibilityReport = "{{ model.HASH_PAGE_ACCESSIBILITY_REPORT }}";

View File

@@ -43,24 +43,20 @@
<div class="footer-content">
<div class="footer-section">
<h3>{{ model.NAME_COMPANY }}</h3>
<p>Company Number: {{ model.COMPANY_NUMBER }}</p>
<p>Registered in England and Wales</p>
<p>Registered Office: {{ model.COMPANY_ADDRESS_SHORT }}</p>
<p>Email: {{ model.get_mail_contact_public() }}</p>
</div>
<div class="footer-section">
<h3>Legal</h3>
<ul>
<li><a href="{{ model.HASH_PAGE_PRIVACY_POLICY }}">Privacy Policy</a></li>
<li><a href="{{ model.HASH_PAGE_ACCESSIBILITY_STATEMENT }}">Accessibility Statement</a></li>
</ul>
</div>
</div>
<a href="{{ model.HASH_PAGE_PRIVACY_POLICY }}">Privacy Policy</a>
<a href="{{ model.HASH_PAGE_ACCESSIBILITY_STATEMENT }}">Accessibility Statement</a>
</div>
<div class="footer-bottom">
<div class="footer-section">
<p>&copy; {{ current_year }} {{ model.NAME_COMPANY }}. <a href="{{ model.HASH_PAGE_LICENSE }}" alt="License" aria-label="License">All rights reserved.</a></p>
</div>
</div>
</div>
</footer>
{% include 'layouts/_shared_scripts.html' %}

View File

@@ -0,0 +1,111 @@
{% extends 'layouts/layout_tcg.html' %}
{% block page_head %}
<link rel="stylesheet" href="{{ url_for('static', filename='dist/css/tcg_decks.bundle.css') }}">
{% endblock %}
{% block page_body %}
{#
<div class="section-header">
<h2 class="tcg-section-title">Decks</h2>
</div>
#}
<!-- Filters Form -->
<form id="{{ model.ID_FORM_FILTERS }}" class="tcg-card {{ model.FLAG_FILTER }} {{ model.FLAG_ROW }} {{ model.FLAG_CARD }}">
{{ model.form_filters.hidden_tag() }}
<div class="{{ model.FLAG_CONTAINER }} {{ model.FLAG_COLUMN }}">
<div class="{{ model.FLAG_CONTAINER_INPUT }} {{ model.FLAG_COLUMN }} {{ model.FLAG_FILTER }} {{ model.FLAG_SEARCH }}"
{{ model.ATTR_VALUE_PREVIOUS }}="{{ model.form_filters.search.data }}">
{{ model.form_filters.search.label(class="tcg-label") }}
{{ model.form_filters.search(class="tcg-input", placeholder="Search decks...") }}
</div>
<div class="{{ model.FLAG_CONTAINER_INPUT }} {{ model.FLAG_COLUMN }} {{ model.FLAG_FILTER }} {{ model.FLAG_ACTIVE }}"
{{ model.ATTR_VALUE_PREVIOUS }}="{{ model.form_filters.active_only.data }}">
{{ model.form_filters.active_only() }}
{{ model.form_filters.active_only.label(class="tcg-label") }}
</div>
<div class="{{ model.FLAG_CONTAINER_INPUT }} {{ model.FLAG_COLUMN }} {{ model.FLAG_FILTER }} {{ model.FLAG_IS_COMMANDER }}"
{{ model.ATTR_VALUE_PREVIOUS }}="{{ model.form_filters.is_commander.data }}">
{{ model.form_filters.is_commander() }}
{{ model.form_filters.is_commander.label(class="tcg-label") }}
</div>
</div>
{% set block_id = 'buttons_table_default' %}
{% include 'components/common/buttons/_buttons_save_cancel.html' %}
</form>
{#
{% set block_id = 'container_buttons_save_cancel' %}
{% include 'components/common/buttons/_buttons_save_cancel.html' %}
</form>
#}
<div class="decks-section tcg-card">
<!-- Decks Table -->
<table class="decks-table {{ model.FLAG_TABLE_MAIN }} {{ model.FLAG_ROW }} {{ model.FLAG_CARD }} {{ model.FLAG_DECK }}" id="{{ model.ID_TABLE_MAIN }}">
<thead>
<tr>
<th class="{{ model.FLAG_NAME }}">Name</th>
<th class="{{ model.FLAG_IS_COMMANDER }}">Is Commander</th>
<th class="{{ model.ATTR_COMMANDER_BRACKET_ID }}">Commander Bracket</th>
<th class="{{ model.FLAG_ACTIVE }}">
{% set class_name = model.FLAG_ACTIVE %}
{% set attribute_text = '' %}
{% include 'components/common/buttons/_icon_add.html' %}
</th>
</tr>
</thead>
<tbody>
{% set is_blank_row = False %}
{% for deck in model.decks %}
{% include 'components/tcg/_row_deck.html' %}
{% endfor %}
</tbody>
</table>
<!-- Statistics -->
<div class="section-header">
<h2 class="tcg-section-title">Statistics</h2>
</div>
<table class="{{ model.FLAG_STATISTICS }} {{ model.FLAG_ROW }} {{ model.FLAG_CARD }} {{ model.FLAG_DECK }}">
<thead>
<tr>
<th class="{{ model.FLAG_DECK }}">Deck</th>
<th class="{{ model.FLAG_NAME }}">Name</th>
<th class="{{ model.FLAG_VALUE }}">Value</th>
</tr>
</thead>
<tbody>
{% for statistic in model.statistics %}
<tr class="{{ model.FLAG_ROW_NEW }} {{ model.FLAG_STATISTIC }}" {{ model.ATTR_STATISTIC_ID }}>
<td class="{{ model.ATTR_DECK_ID }} {{ model.ATTR_ENTITY_RECORD_ID }}">{{ statistic.entity_record_name }}</td>
<td class="{{ model.FLAG_NAME }}">{{ statistic.name }}</td>
<td class="{{ model.FLAG_VALUE }}">{{ statistic.get_formatted_value() }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% include 'components/common/temporary/_overlay_confirm.html' %}
{% include 'components/common/temporary/_overlay_error.html' %}
<div id="{{ model.ID_CONTAINER_TEMPLATE_ELEMENTS }}">
<!-- Active column -->
<!-- Delete -->
{% set class_name = '' %}
{% include 'components/common/buttons/_icon_trash.html' %}
<!-- Undelete -->
{% set class_name = model.FLAG_ACTIVE %}
{% set attribute_text = '' %}
{% include 'components/common/buttons/_icon_add.html' %}
</div>
<script>
var attrCommanderBracketId = "{{ model.ATTR_COMMANDER_BRACKET_ID }}";
var commanderBrackets = {{ model.convert_list_objects_to_json(model.commander_brackets) | tojson | safe }};
var decks = {{ model.convert_list_objects_to_json(model.decks) | tojson | safe }};
var flagStatistics = "{{ model.FLAG_STATISTICS }}";
var flagIsCommander = "{{ model.FLAG_IS_COMMANDER }}";
</script>
{% endblock %}

View File

@@ -135,7 +135,8 @@
<th class="{{ model.ATTR_ROUND_ID }}">Round</th>
<th class="{{ model.ATTR_PLAYER_ID }}">Player</th>
<th class="{{ model.ATTR_RECEIVED_FROM_COMMANDER_PLAYER_ID }}">Received From Commander</th>
<th class="{{ model.FLAG_HEALTH_CHANGE }}">Health Change</th>
<th class="{{ model.FLAG_LIFE_GAIN }}">Life Gain</th>
<th class="{{ model.FLAG_LIFE_LOSS }}">Life Loss</th>
<th class="{{ model.FLAG_COMMANDER_DEATHS }}">Commander Deaths</th>
<th class="{{ model.FLAG_IS_ELIMINATED }}">Is Eliminated</th>
</tr>
@@ -181,9 +182,6 @@
var damageRecords = {{ model.convert_list_objects_to_json(model.damage_records) | tojson | safe }};
var decks = {{ model.convert_list_objects_to_json(model.decks) | tojson | safe }};
var flagDamageLog = "{{ model.FLAG_DAMAGE_LOG }}";
var flagDisplayOrder = "{{ model.FLAG_DISPLAY_ORDER }}";
var flagHealthChange = "{{ model.FLAG_HEALTH_CHANGE }}";
var flagIsEliminated = "{{ model.FLAG_IS_ELIMINATED }}";
var flagRoundDisplayOrderButton = "{{ model.FLAG_ROUND_DISPLAY_ORDER_BUTTON }}";
var flagRoundDisplayOrderMinus = "{{ model.FLAG_ROUND_DISPLAY_ORDER_MINUS }}";
var flagRoundDisplayOrderPlus = "{{ model.FLAG_ROUND_DISPLAY_ORDER_PLUS }}";

View File

@@ -40,24 +40,23 @@
</form>
<!-- Games Table -->
<div class="games-table-container">
<table class="games-table" id="gamesTable">
<table class="games-table {{ model.FLAG_TABLE_MAIN }} {{ model.FLAG_ROW }} {{ model.FLAG_CARD }} {{ model.FLAG_DECK }}" id="{{ model.ID_TABLE_MAIN }}">
<thead>
<tr>
<th>Game ID</th>
<th>Type</th>
<th>Location</th>
<th>Started</th>
<th>Status</th>
<th>Actions</th>
<th class="{{ model.ATTR_GAME_ID }}">Game ID</th>
<th class="{{ model.FLAG_IS_COMMANDER }}">Type</th>
<th class="{{ model.FLAG_LOCATION_NAME }}">Location</th>
<th class="{{ model.FLAG_START_ON }}">Started</th>
<th class="{{ model.FLAG_ACTIVE }}">Status</th>
<th class="{{ model.FLAG_NAV_MTG_GAME }}">Actions</th>
</tr>
</thead>
<tbody>
{% if model.games and model.games|length > 0 %}
{% for game in model.games %}
<tr class="game-row {% if not game.active %}inactive{% endif %}" data-game-id="{{ game.game_id }}">
<td class="game-id">#{{ game.game_id }}</td>
<td class="game-type">
<td class="{{ model.ATTR_GAME_ID }}">#{{ game.game_id }}</td>
<td class="{{ model.FLAG_IS_COMMANDER }}">
{% if game.is_commander %}
<span class="badge badge-commander">Commander</span>
{% elif game.is_draft %}
@@ -68,9 +67,9 @@
<span class="badge badge-standard">Standard</span>
{% endif %}
</td>
<td class="game-location">{{ game.location_name or 'Unknown' }}</td>
<td class="game-date">{{ model.format_datetime_text(game.start_on) if game.start_on else 'Not started' }}</td>
<td class="game-status">
<td class="{{ model.FLAG_LOCATION_NAME }}">{{ game.location_name or 'Unknown' }}</td>
<td class="{{ model.FLAG_START_ON }}">{{ model.format_datetime_text(game.start_on) if game.start_on else 'Not started' }}</td>
<td class="{{ model.FLAG_ACTIVE }}">
{% if game.end_on %}
<span class="status status-ended">Ended</span>
{% elif game.active %}
@@ -79,7 +78,7 @@
<span class="status status-inactive">Inactive</span>
{% endif %}
</td>
<td class="game-actions">
<td class="{{ model.FLAG_NAV_MTG_GAME }}">
<a href="{{ model.HASH_PAGE_MTG_GAME }}/{{ game.game_id }}" class="btn-tcg btn-join">Join Game</a>
</td>
</tr>
@@ -97,10 +96,9 @@
</tbody>
</table>
</div>
</div>
<!-- New Game Modal -->
<div id="newGameModal" class="modal-overlay hidden">
<div id="newGameModal" class="modal-overlay {{ model.FLAG_IS_COLLAPSED }}">
<div class="modal-content tcg-card">
<div class="modal-header">
<h2 class="tcg-section-title">Create New Game</h2>
@@ -158,5 +156,4 @@
var flagStartOn = "{{ model.FLAG_START_ON }}";
var flagStartingLife = "{{ model.FLAG_STARTING_LIFE }}";
</script>
{# <script src="{{ url_for('static', filename='js/pages/tcg/mtg_games.js') }}"></script> #}
{% endblock %}

View File

@@ -3,5 +3,9 @@
Winner of game animation - bring to centre with trophy icon and show updates player + deck stats (+ charts?), add end time
Game start + end times left / right of game location
Deck mulligan rate - add to player table and then just get statistics whenever wanted
Ensure no duplicate players by Game ID and User ID and Deck ID
player save with null name not pulling user and deck names
create damage from commander fills is_eliminated with false, but create from player's own damage leaves is_eliminated undefined
Change round - update player cards to show damage at that point for easier editing

View File

@@ -24,7 +24,7 @@ module.exports = {
path.resolve(__dirname, 'static/css/lib/typography.css'),
path.resolve(__dirname, 'static/css/lib/utils.css'),
path.resolve(__dirname, 'static/css/lib/variables.css'),
path.resolve(__dirname, 'static/css/themes/light.css'),
path.resolve(__dirname, 'static/css/themes/dark.css'),
// ...glob.sync(path.resolve(__dirname, 'static/css/main/** /*.css')) // Include CSS files for the main page
],
// Core
@@ -53,6 +53,10 @@ module.exports = {
path.resolve(__dirname, 'static/css/pages/legal/privacy_policy.css')
],
// TCG
tcg_decks: [
path.resolve(__dirname, 'static/css/sections/tcg.css'),
path.resolve(__dirname, 'static/css/pages/tcg/decks.css')
],
tcg_game: [
path.resolve(__dirname, 'static/css/sections/tcg.css'),
path.resolve(__dirname, 'static/css/pages/tcg/game.css')