From 872eb168c07c81d9b77532990bdf96ef6d416c9a Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Tue, 19 Nov 2024 12:44:53 -0800 Subject: [PATCH 01/16] Switch to river-style SQL in Deck constructor --- gatherling/Models/Deck.php | 57 ++++++++++++-------------------------- 1 file changed, 18 insertions(+), 39 deletions(-) diff --git a/gatherling/Models/Deck.php b/gatherling/Models/Deck.php index efd3daf6e..6176d0932 100644 --- a/gatherling/Models/Deck.php +++ b/gatherling/Models/Deck.php @@ -53,13 +53,10 @@ public function __construct(int $id) return; } $sql = ' - SELECT - id, name, playername, archetype, format, tribe, notes, deck_hash, - sideboard_hash, whole_hash, created_date, deck_colors AS deck_color_str - FROM - decks d - WHERE - id = :id'; + SELECT id, name, playername, archetype, format, tribe, notes, deck_hash, + sideboard_hash, whole_hash, created_date, deck_colors AS deck_color_str + FROM decks d + WHERE id = :id'; $deck = db()->selectOnlyOrNull($sql, DeckDto::class, ['id' => $id]); if (!$deck) { $this->id = 0; @@ -73,12 +70,9 @@ public function __construct(int $id) if (empty($this->playername)) { $sql = ' - SELECT - p.name - FROM - players p, entries e, decks d - WHERE - p.name = e.player AND d.id = e.deck AND d.id = :id'; + SELECT p.name + FROM players p, entries e, decks d + WHERE p.name = e.player AND d.id = e.deck AND d.id = :id'; $this->playername = db()->optionalString($sql, ['id' => $id]); } @@ -90,16 +84,10 @@ public function __construct(int $id) // Retrieve cards. $sql = ' - SELECT - c.name, dc.qty, dc.issideboard - FROM - cards c, deckcontents dc, decks d - WHERE - d.id = dc.deck - AND c.id = dc.card - AND d.id = :id - ORDER BY - c.name'; + SELECT c.name, dc.qty, dc.issideboard + FROM cards c, deckcontents dc, decks d + WHERE d.id = dc.deck AND c.id = dc.card AND d.id = :id + ORDER BY c.name'; $cards = db()->select($sql, DeckCardDto::class, ['id' => $id]); $this->maindeck_cardcount = 0; @@ -116,14 +104,9 @@ public function __construct(int $id) // Retrieve event $sql = ' - SELECT - e.name, e.id - FROM - events e, entries n, decks d - WHERE - d.id = :id - AND d.id = n.deck - AND n.event_id = e.id'; + SELECT e.name, e.id + FROM events e, entries n, decks d + WHERE d.id = :id AND d.id = n.deck AND n.event_id = e.id'; $event = db()->selectOnlyOrNull($sql, DeckEventDto::class, ['id' => $id]); if ($event) { $this->eventname = $event->name; @@ -137,14 +120,10 @@ public function __construct(int $id) // Find subevent id - ignores sub-subevents like finals, which have the same name but different subevent id if (!is_null($this->eventname)) { $sql = ' - SELECT - events.format - FROM - entries - INNER JOIN - events ON entries.event_id = events.id - WHERE - entries.deck = :id'; + SELECT events.format + FROM entries + INNER JOIN events ON entries.event_id = events.id + WHERE entries.deck = :id'; $this->format = db()->optionalString($sql, ['id' => $id]); $sql = 'SELECT MIN(id) FROM subevents WHERE parent = :eventname'; $this->subeventid = db()->optionalInt($sql, ['eventname' => $this->eventname]); From c25a78fe283ecce011cf1ba9764d05dadd50a089 Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Tue, 19 Nov 2024 14:23:37 -0800 Subject: [PATCH 02/16] Tighten up nullability on Deck --- gatherling/Models/Deck.php | 39 ++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/gatherling/Models/Deck.php b/gatherling/Models/Deck.php index 6176d0932..91931a338 100644 --- a/gatherling/Models/Deck.php +++ b/gatherling/Models/Deck.php @@ -12,7 +12,7 @@ class Deck { - public ?int $id; + public int $id; public ?string $name = null; public ?string $archetype = null; public ?string $notes = null; @@ -25,8 +25,8 @@ class Deck /** @var array */ public array $errors = []; public ?string $playername = null; // Belongs to player through entries, now held in decks table - public ?string $eventname; // Belongs to event through entries - public ?int $event_id; // Belongs to event through entries + public ?string $eventname = null; // Belongs to event through entries + public ?int $event_id = null; // Belongs to event through entries public ?int $subeventid; // Belongs to event public ?string $format = null; // Belongs to event.. now held in decks table public ?string $tribe = null; // used only for tribal events @@ -36,12 +36,12 @@ class Deck public ?string $sideboard_hash; public ?string $whole_hash; /** @var array */ - public array $unparsed_cards; + public array $unparsed_cards = []; /** @var array */ - public array $unparsed_side; - public ?string $deck_contents_cache; + public array $unparsed_side = []; + public ?string $deck_contents_cache = null; /** @var ?list */ - public ?array $identical_decks; + public ?array $identical_decks = null; public ?string $medal = null; // has a medal public bool $new; // is new @@ -58,14 +58,23 @@ public function __construct(int $id) FROM decks d WHERE id = :id'; $deck = db()->selectOnlyOrNull($sql, DeckDto::class, ['id' => $id]); - if (!$deck) { + if ($deck === null) { $this->id = 0; $this->new = true; return; } - foreach (get_object_vars($deck) as $key => $value) { - $this->{$key} = $value; - } + $this->id = $deck->id; + $this->name = $deck->name; + $this->playername = $deck->playername; + $this->archetype = $deck->archetype; + $this->format = $deck->format; + $this->tribe = $deck->tribe; + $this->notes = $deck->notes; + $this->deck_hash = $deck->deck_hash; + $this->sideboard_hash = $deck->sideboard_hash; + $this->whole_hash = $deck->whole_hash; + $this->created_date = $deck->created_date; + $this->deck_color_str = $deck->deck_color_str; $this->new = false; if (empty($this->playername)) { @@ -108,7 +117,7 @@ public function __construct(int $id) FROM events e, entries n, decks d WHERE d.id = :id AND d.id = n.deck AND n.event_id = e.id'; $event = db()->selectOnlyOrNull($sql, DeckEventDto::class, ['id' => $id]); - if ($event) { + if ($event !== null) { $this->eventname = $event->name; $this->event_id = $event->id; } @@ -155,6 +164,9 @@ public static function getArchetypes(): array public function getEntry(): Entry { + if ($this->event_id === null || $this->playername === null) { + throw new InvalidArgumentException('Cannot get entry for deck without event ID and playername'); + } return new Entry($this->event_id, $this->playername); } @@ -230,6 +242,9 @@ public function getCastingCosts(): array public function getEvent(): Event { + if ($this->event_id === null) { + throw new InvalidArgumentException('Cannot get event for deck without event ID'); + } return new Event($this->event_id); } From b95b79f03b5e361e760b88b93a81d1276f241801 Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Tue, 19 Nov 2024 14:24:53 -0800 Subject: [PATCH 03/16] Remove some error checking we have typesafed out of existence --- gatherling/deck.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/gatherling/deck.php b/gatherling/deck.php index f47b1e1db..ece6f1fd9 100644 --- a/gatherling/deck.php +++ b/gatherling/deck.php @@ -95,9 +95,6 @@ function main(): never } } elseif ($postMode === 'Update Deck') { $deck = updateDeck($deck, post()->string('archetype'), post()->string('name'), post()->string('notes'), post()->string('contents', ''), post()->string('sideboard', '')); - if ($deck->id === null) { - throw new InvalidArgumentException('Trying to update a deck with null id, which is not possible'); - } $deck = new Deck($deck->id); // had to do this to get the constructor to run, otherwise errors weren't loading if ($deck->isValid()) { $viewComponent = deckProfile($deck); From 317b61bb53a01ff876d09814bb90308defe2b84b Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Tue, 19 Nov 2024 14:25:51 -0800 Subject: [PATCH 04/16] Remove a test that has been typesafed away --- tests/Models/DeckTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Models/DeckTest.php b/tests/Models/DeckTest.php index 01a0f6e2d..83b997300 100644 --- a/tests/Models/DeckTest.php +++ b/tests/Models/DeckTest.php @@ -81,8 +81,6 @@ public function testSave(): void $deck->event_id = $this->event->id; $deck->save(); - $this->assertNotNull($deck->id); - $deck = new Deck($deck->id); $this->assertFalse($deck->new); $this->assertEquals($deck->playername, $this->player->name); From b8245133a771a98ba4a89ea0848cbfd38db16f60 Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Tue, 19 Nov 2024 14:26:33 -0800 Subject: [PATCH 05/16] Update phpstan baseline after recent improvements --- phpstan-baseline.neon | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 2b62a3d8a..3960301e4 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -70,31 +70,11 @@ parameters: count: 1 path: gatherling/Models/Deck.php - - - message: "#^Parameter \\#1 \\$deckID of method Gatherling\\\\Models\\\\Format\\:\\:isDeckCommanderLegal\\(\\) expects int, int\\|null given\\.$#" - count: 1 - path: gatherling/Models/Deck.php - - - - message: "#^Parameter \\#1 \\$deckID of method Gatherling\\\\Models\\\\Format\\:\\:isDeckTribalLegal\\(\\) expects int, int\\|null given\\.$#" - count: 1 - path: gatherling/Models/Deck.php - - - - message: "#^Parameter \\#1 \\$event_id of class Gatherling\\\\Models\\\\Entry constructor expects int, int\\|null given\\.$#" - count: 1 - path: gatherling/Models/Deck.php - - message: "#^Parameter \\#1 \\$event_id of static method Gatherling\\\\Models\\\\Entry\\:\\:findByEventAndPlayer\\(\\) expects int, int\\|null given\\.$#" count: 1 path: gatherling/Models/Deck.php - - - message: "#^Parameter \\#1 \\$name of class Gatherling\\\\Models\\\\Event constructor expects int\\|string, int\\|null given\\.$#" - count: 1 - path: gatherling/Models/Deck.php - - message: "#^Parameter \\#1 \\$name of class Gatherling\\\\Models\\\\Player constructor expects string, string\\|null given\\.$#" count: 1 @@ -105,11 +85,6 @@ parameters: count: 2 path: gatherling/Models/Deck.php - - - message: "#^Parameter \\#2 \\$playername of class Gatherling\\\\Models\\\\Entry constructor expects string, string\\|null given\\.$#" - count: 1 - path: gatherling/Models/Deck.php - - message: "#^Parameter \\#2 \\$playername of static method Gatherling\\\\Models\\\\Entry\\:\\:findByEventAndPlayer\\(\\) expects string, string\\|null given\\.$#" count: 1 From 6411e93afd21b1bd9dae6068f26422a88a061578 Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Tue, 19 Nov 2024 14:27:23 -0800 Subject: [PATCH 06/16] Update psalm baseline after fixing some props in Deck constructor --- psalm-baseline.xml | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 0dc55c635..c9905dce0 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -258,9 +258,6 @@ $colors->{$c}]]> - - - ]]> @@ -272,11 +269,6 @@ deck_color_str]]> deck_color_str]]> event_id]]> - event_id]]> - event_id]]> - id]]> - id]]> - playername]]> playername]]> playername]]> playername]]> @@ -285,24 +277,9 @@ archetype]]> eventname]]> - id]]> - id]]> playername]]> tribe]]> - - - - - - - - - - - - - @@ -311,13 +288,7 @@ format]]> name]]> deck_color_str)]]> - playername)]]> - - created_date]]> - eventname]]> - eventname]]> - @@ -1670,7 +1641,6 @@ - id]]> name]]> @@ -1696,9 +1666,6 @@ - - id]]> - @@ -1854,7 +1821,6 @@ - id]]> name]]> From 4c3a9c8502d366ee8aadcd38525be3c10236d5e1 Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Tue, 19 Nov 2024 16:11:38 -0800 Subject: [PATCH 07/16] Componentize MonthDropMenu --- gatherling/Views/Components/MonthDropMenu.php | 26 +++++++++++++++ gatherling/Views/Pages/EventForm.php | 32 ++----------------- 2 files changed, 29 insertions(+), 29 deletions(-) create mode 100644 gatherling/Views/Components/MonthDropMenu.php diff --git a/gatherling/Views/Components/MonthDropMenu.php b/gatherling/Views/Components/MonthDropMenu.php new file mode 100644 index 000000000..d5271fa3c --- /dev/null +++ b/gatherling/Views/Components/MonthDropMenu.php @@ -0,0 +1,26 @@ + $month === $m, + 'value' => (string) $m, + 'text' => $names[$m - 1], + ]; + } + + parent::__construct('month', $options, '- Month -'); + } +} diff --git a/gatherling/Views/Pages/EventForm.php b/gatherling/Views/Pages/EventForm.php index b439ac919..537cf2544 100644 --- a/gatherling/Views/Pages/EventForm.php +++ b/gatherling/Views/Pages/EventForm.php @@ -8,6 +8,7 @@ use Gatherling\Models\Player; use Gatherling\Views\Components\ClientDropMenu; use Gatherling\Views\Components\TextInput; +use Gatherling\Views\Components\MonthDropMenu; use Gatherling\Views\Components\NumDropMenu; use Gatherling\Views\Components\SelectInput; use Gatherling\Views\Components\StringField; @@ -27,8 +28,7 @@ class EventForm extends EventFrame /** @var list */ public array $navLinks; public NumDropMenu $yearDropMenu; - /** @var array */ - public array $monthDropMenu; + public MonthDropMenu $monthDropMenu; public NumDropMenu $dayDropMenu; public TimeDropMenu $timeDropMenu; public SeriesDropMenu $seriesDropMenu; @@ -93,7 +93,7 @@ public function __construct(Event $event, bool $edit) $navLinks[] = $nextEvent->makeLinkArgs('Next'); } $yearDropMenu = new NumDropMenu('year', '- Year -', (int) date('Y') + 1, $year, 2011); - $monthDropMenu = monthDropMenuArgs($month); + $monthDropMenu = new MonthDropMenu($month); $dayDropMenu = new NumDropMenu('day', '- Day- ', 31, $day, 1); $timeDropMenu = new TimeDropMenu('hour', $hour, $minutes); @@ -193,32 +193,6 @@ function kValueSelectInput(int $kvalue): SelectInput return new SelectInput('K-Value', 'kvalue', $names, $kvalue); } -/** @return array{name: string, default: string, options: array} */ -function monthDropMenuArgs(string|int $month): array -{ - if ($month === '') { - $month = -1; - } - $names = [ - 'January', 'February', 'March', 'April', 'May', 'June', - 'July', 'August', 'September', 'October', 'November', 'December', - ]; - $options = []; - for ($m = 1; $m <= 12; $m++) { - $options[] = [ - 'isSelected' => $month == $m, - 'value' => $m, - 'text' => $names[$m - 1], - ]; - } - - return [ - 'name' => 'month', - 'default' => '- Month -', - 'options' => $options, - ]; -} - /** @return array{name: string, default: string, options: list} */ function structDropMenuArgs(string $field, string $def): array { From 0273dc48ab9b1274b4e5c99ab2a51e80992e2b18 Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Tue, 19 Nov 2024 16:44:22 -0800 Subject: [PATCH 08/16] Componentize EntryListItem Used in the list of registered players on event.php. This was one of the first things converted to the Page system and I later decided on a more componentized and less "pass horking great big objects into mustache templates" style. --- gatherling/Views/Components/EntryListItem.php | 79 +++++++++++++++++++ gatherling/Views/Pages/PlayerList.php | 57 +------------ .../templates/partials/entryListItem.mustache | 61 ++++++++++++++ gatherling/templates/playerList.mustache | 62 +-------------- 4 files changed, 144 insertions(+), 115 deletions(-) create mode 100644 gatherling/Views/Components/EntryListItem.php create mode 100644 gatherling/templates/partials/entryListItem.mustache diff --git a/gatherling/Views/Components/EntryListItem.php b/gatherling/Views/Components/EntryListItem.php new file mode 100644 index 000000000..ac1bed8da --- /dev/null +++ b/gatherling/Views/Components/EntryListItem.php @@ -0,0 +1,79 @@ +dropRound = $entry->drop_round; + $this->eventName = $entry->event->name; + $this->emailAddress = $entry->player->emailAddress; + if ($entry->event->active == 1) { + $playerActive = Standings::playerActive($entry->event->name, $entry->player->name); + $this->canDrop = $playerActive; + $this->canUndrop = !$playerActive; + $undropParams = [ + 'view' => 'reg', + 'player' => $entry->player->name, + 'event' => $entry->event->id, + 'action' => 'undrop', + 'event_id' => $entry->event->id, + ]; + $this->undropLink = 'event.php?' . http_build_query($undropParams, '', '&', PHP_QUERY_RFC3986); + } + if ($entry->event->isFinished() && $entry->medal !== '') { + $this->medalSrc = "styles/images/{$entry->medal}.png"; + } + $this->gameName = new GameName($entry->player, $entry->event->client); + if ($entry->deck) { + $this->deckLink = new DeckLink($entry->deck); + } else { + $this->createDeckLink = new CreateDeckLink($entry); + } + $this->invalidRegistration = $entry->deck != null && !$entry->deck->isValid(); + $this->tribe = $isTribal && $entry->deck != null ? $entry->deck->tribe : ''; + $this->isSwiss = $entry->event->isSwiss(); + $this->hasStarted = $entry->event->hasStarted(); + $this->initialByes = $entry->initial_byes; + $this->isSingleElim = $entry->event->isSingleElim(); + $this->initialSeed = $entry->initial_seed; + if ($this->isSwiss && !$this->hasStarted) { + $this->initialByeDropMenu = new InitialByesDropMenu('initial_byes[]', $entry->player->name, $entry->initial_byes); + } elseif ($entry->event->isSingleElim() && !$entry->event->hasStarted()) { + $this->initialSeedDropMenu = new InitialSeedDropMenu('initial_seed[]', $entry->player->name, $entry->initial_seed, $numEntries); + } + if ($entry->canDelete()) { + $this->canDelete = true; + } else { + $this->notAllowed = new NotAllowed("Can't delete player, they have matches recorded."); + } + } +} diff --git a/gatherling/Views/Pages/PlayerList.php b/gatherling/Views/Pages/PlayerList.php index b3d04131d..380a18336 100644 --- a/gatherling/Views/Pages/PlayerList.php +++ b/gatherling/Views/Pages/PlayerList.php @@ -4,19 +4,10 @@ namespace Gatherling\Views\Pages; -use Gatherling\Models\Entry; use Gatherling\Models\Event; use Gatherling\Models\Format; -use Gatherling\Models\Standings; -use Gatherling\Views\Components\CreateDeckLink; -use Gatherling\Views\Components\DeckLink; -use Gatherling\Views\Components\GameName; -use Gatherling\Views\Components\NotAllowed; +use Gatherling\Views\Components\EntryListItem; use Gatherling\Views\Components\StringField; -use Gatherling\Views\Components\InitialByesDropMenu; -use Gatherling\Views\Components\InitialSeedDropMenu; - -use function Gatherling\Helpers\getObjectVarsCamelCase; class PlayerList extends EventFrame { @@ -27,7 +18,7 @@ class PlayerList extends EventFrame public bool $hasStarted; public bool $hasEntries; public int $numEntries; - /** @var list> */ + /** @var list */ public array $entries; public bool $isSwiss; public bool $isSingleElim; @@ -50,7 +41,7 @@ public function __construct(Event $event) $deckless = $entryInfoList = []; foreach ($entries as $entry) { - $entryInfoList[] = entryListArgs($entry, $numEntries, (bool) $format->tribal); + $entryInfoList[] = new EntryListItem($entry, $numEntries, (bool) $format->tribal); if (!$entry->deck) { $deckless[] = $entry->player->name; } @@ -87,45 +78,3 @@ public function __construct(Event $event) $this->deckless = implode(', ', $deckless); } } - -/** @return array */ -function entryListArgs(Entry $entry, int $numEntries, bool $isTribal): array -{ - $entryInfo = getObjectVarsCamelCase($entry); - if ($entry->event->active == 1) { - $playerActive = Standings::playerActive($entry->event->name, $entry->player->name); - $entryInfo['canDrop'] = $playerActive; - $entryInfo['canUndrop'] = !$playerActive; - $undropParams = [ - 'view' => 'reg', - 'player' => $entry->player->name, - 'event' => $entry->event->id, - 'action' => 'undrop', - 'event_id' => $entry->event->id, - ]; - $entryInfo['undropLink'] = 'event.php?' . http_build_query($undropParams, '', '&', PHP_QUERY_RFC3986); - } - if ($entry->event->isFinished() && $entry->medal !== '') { - $entryInfo['medalSrc'] = "styles/images/{$entry->medal}.png"; - } - $entryInfo['gameName'] = new GameName($entry->player, $entry->event->client); - if ($entry->deck) { - $entryInfo['deckLink'] = new DeckLink($entry->deck); - } else { - $entryInfo['createDeckLink'] = new CreateDeckLink($entry); - } - $entryInfo['invalidRegistration'] = $entry->deck != null && !$entry->deck->isValid(); - $entryInfo['tribe'] = $isTribal && $entry->deck != null ? $entry->deck->tribe : ''; - if ($entry->event->isSwiss() && !$entry->event->hasStarted()) { - $entryInfo['initialByeDropMenu'] = new InitialByesDropMenu('initial_byes[]', $entry->player->name, $entry->initial_byes); - } elseif ($entry->event->isSingleElim() && !$entry->event->hasStarted()) { - $entryInfo['initialSeedDropMenu'] = new InitialSeedDropMenu('initial_seed[]', $entry->player->name, $entry->initial_seed, $numEntries); - } - if ($entry->canDelete()) { - $entryInfo['canDelete'] = $entry->canDelete(); - } else { - $entryInfo['notAllowed'] = new NotAllowed("Can't delete player, they have matches recorded."); - } - - return $entryInfo; -} diff --git a/gatherling/templates/partials/entryListItem.mustache b/gatherling/templates/partials/entryListItem.mustache new file mode 100644 index 000000000..70b64b87e --- /dev/null +++ b/gatherling/templates/partials/entryListItem.mustache @@ -0,0 +1,61 @@ + + {{#canDrop}} + + + + {{/canDrop}} + {{#canUndrop}} + + ⚐ {{dropRound}} + (undrop) + + {{/canUndrop}} + {{#medalSrc}} + + Medal + + {{/medalSrc}} + + {{^emailAddress}} + {{#gameName}}{{> gameName}}{{/gameName}} + {{/emailAddress}} + {{#emailAddress}} + {{#gameName}}{{> gameName}}{{/gameName}} + {{/emailAddress}} + + + {{#deckLink}}{{> deckLink}}{{/deckLink}} + {{#createDeckLink}}{{> createDeckLink}}{{/createDeckLink}} + + {{#isTribal}} + + {{tribe}} + + {{/isTribal}} + + {{#isSwiss}} + {{#hasStarted}} + {{initialByes}} + {{/hasStarted}} + {{#initialByeDropMenu}} + {{> dropMenu}} + {{/initialByeDropMenu}} + {{/isSwiss}} + {{#isSingleElim}} + {{#hasStarted}} + {{initialSeed}} + {{/hasStarted}} + {{#initialSeedDropMenu}} + {{> dropMenu}} + {{/initialSeedDropMenu}} + {{/isSingleElim}} + + + {{#canDelete}} + + {{/canDelete}} + {{#notAllowed}} + {{> notAllowed}} + {{/notAllowed}} + + diff --git a/gatherling/templates/playerList.mustache b/gatherling/templates/playerList.mustache index 8d5335d15..4e98514dd 100644 --- a/gatherling/templates/playerList.mustache +++ b/gatherling/templates/playerList.mustache @@ -49,67 +49,7 @@ {{/hasEntries}} {{#entries}} - - {{#canDrop}} - - - - {{/canDrop}} - {{#canUndrop}} - - ⚐ {{dropRound}} - (undrop) - - {{/canUndrop}} - {{#medalSrc}} - - Medal - - {{/medalSrc}} - - {{^player.emailAddress}} - {{#gameName}}{{> gameName}}{{/gameName}} - {{/player.emailAddress}} - {{#player.emailAddress}} - {{#gameName}}{{> gameName}}{{/gameName}} - {{/player.emailAddress}} - - - {{#deckLink}}{{> deckLink}}{{/deckLink}} - {{#createDeckLink}}{{> createDeckLink}}{{/createDeckLink}} - - {{#format.tribal}} - - {{tribe}} - - {{/format.tribal}} - - {{#isSwiss}} - {{#hasStarted}} - {{initialByes}} - {{/hasStarted}} - {{#initialByeDropMenu}} - {{> dropMenu}} - {{/initialByeDropMenu}} - {{/isSwiss}} - {{#isSingleElim}} - {{#hasStarted}} - {{initialSeed}} - {{/hasStarted}} - {{#initialSeedDropMenu}} - {{> dropMenu}} - {{/initialSeedDropMenu}} - {{/isSingleElim}} - - - {{#canDelete}} - - {{/canDelete}} - {{#notAllowed}} - {{> notAllowed}} - {{/notAllowed}} - - + {{> entryListItem}} {{/entries}} {{#newEntry}} From b1b1b7c6d6e93993c25f167bef071c68de3e1255 Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Wed, 20 Nov 2024 07:48:30 -0800 Subject: [PATCH 09/16] Componentize UnverifiedPlayerCell --- gatherling/Helpers/ErrorHandler.php | 1 - gatherling/Views/Components/EntryListItem.php | 4 +- .../Views/Components/UnverifiedPlayerCell.php | 37 +++++++++++++++++++ gatherling/Views/Pages/MatchList.php | 28 ++------------ psalm-baseline.xml | 10 ----- 5 files changed, 42 insertions(+), 38 deletions(-) create mode 100644 gatherling/Views/Components/UnverifiedPlayerCell.php diff --git a/gatherling/Helpers/ErrorHandler.php b/gatherling/Helpers/ErrorHandler.php index f7afe659d..a3a71b0c5 100644 --- a/gatherling/Helpers/ErrorHandler.php +++ b/gatherling/Helpers/ErrorHandler.php @@ -18,7 +18,6 @@ public function handle(Throwable $e): never $requestId = Request::getRequestId(); if ($e instanceof NotFoundInDatabaseException) { - // BAKERT this is weak but it's a start $e = new NotFoundException($e->getMessage(), $e->getCode(), $e, $e->type, $e->ids); } diff --git a/gatherling/Views/Components/EntryListItem.php b/gatherling/Views/Components/EntryListItem.php index ac1bed8da..1062be142 100644 --- a/gatherling/Views/Components/EntryListItem.php +++ b/gatherling/Views/Components/EntryListItem.php @@ -33,7 +33,7 @@ class EntryListItem public function __construct(Entry $entry, int $numEntries, public bool $isTribal) { - // BAKERT !!! $this->dropRound = $entry->drop_round; + $this->dropRound = $entry->drop_round; $this->eventName = $entry->event->name; $this->emailAddress = $entry->player->emailAddress; if ($entry->event->active == 1) { @@ -59,7 +59,7 @@ public function __construct(Entry $entry, int $numEntries, public bool $isTribal $this->createDeckLink = new CreateDeckLink($entry); } $this->invalidRegistration = $entry->deck != null && !$entry->deck->isValid(); - $this->tribe = $isTribal && $entry->deck != null ? $entry->deck->tribe : ''; + $this->tribe = $isTribal && $entry->deck !== null && $entry->deck->tribe !== null ? $entry->deck->tribe : ''; $this->isSwiss = $entry->event->isSwiss(); $this->hasStarted = $entry->event->hasStarted(); $this->initialByes = $entry->initial_byes; diff --git a/gatherling/Views/Components/UnverifiedPlayerCell.php b/gatherling/Views/Components/UnverifiedPlayerCell.php new file mode 100644 index 000000000..cc99af921 --- /dev/null +++ b/gatherling/Views/Components/UnverifiedPlayerCell.php @@ -0,0 +1,37 @@ +playerName = $player->name; + $this->wins = $match->getPlayerWins($this->playerName); + $this->losses = $match->getPlayerLosses($this->playerName); + $this->hasGames = is_int($this->wins) && is_int($this->losses) && $this->wins + $this->losses > 0; + $this->matchResult = $this->hasGames ? ($this->wins > $this->losses ? 'W' : 'L') : null; + $this->displayName = new GameName($player, $event->client); + $this->displayNameText = new GameName($player, $event->client, false); + $this->hasDropped = $match->playerDropped($this->playerName); + $this->isDraw = ($this->wins == 1 && $this->losses == 1); + $this->verification = $match->verification; + } +} diff --git a/gatherling/Views/Pages/MatchList.php b/gatherling/Views/Pages/MatchList.php index bee73f166..c87f578e3 100644 --- a/gatherling/Views/Pages/MatchList.php +++ b/gatherling/Views/Pages/MatchList.php @@ -6,10 +6,10 @@ use Gatherling\Models\Event; use Gatherling\Models\Player; -use Gatherling\Models\Matchup; use Gatherling\Views\Components\GameName; use Gatherling\Views\Components\RoundDropMenu; use Gatherling\Views\Components\PlayerDropMenu; +use Gatherling\Views\Components\UnverifiedPlayerCell; use function Gatherling\Helpers\getObjectVarsCamelCase; @@ -84,9 +84,9 @@ public function __construct(Event $event, string|int|null $newMatchRound) $isActiveUnverified = strcasecmp($match->verification, 'verified') != 0 && $event->finalized == 0; if ($isActiveUnverified) { - $matchInfo['unverifiedPlayerCellA'] = unverifiedPlayerCellArgs($event, $match, $playerA); + $matchInfo['unverifiedPlayerCellA'] = new UnverifiedPlayerCell($event, $match, $playerA); $matchInfo['resultDropMenu'] = resultDropMenuArgs('matchresult[]'); - $matchInfo['unverifiedPlayerCellB'] = unverifiedPlayerCellArgs($event, $match, $playerB); + $matchInfo['unverifiedPlayerCellB'] = new UnverifiedPlayerCell($event, $match, $playerB); } else { $playerAWins = $match->getPlayerWins($match->playera); $playerBWins = $match->getPlayerWins($match->playerb); @@ -179,25 +179,3 @@ function resultDropMenuArgs(string $name, array $extraOptions = []): array 'options' => $options, ]; } - -/** @return array */ -function unverifiedPlayerCellArgs(Event $event, Matchup $match, Player $player): array -{ - $playerName = $player->name; - $wins = $match->getPlayerWins($playerName); - $losses = $match->getPlayerLosses($playerName); - $matchResult = ($wins + $losses > 0) ? ($wins > $losses ? 'W' : 'L') : null; - - return [ - 'playerName' => $playerName, - 'displayName' => new GameName($player, $event->client), - 'displayNameText' => new GameName($player, $event->client, false), - 'hasDropped' => $match->playerDropped($playerName), - 'hasGames' => ($wins + $losses > 0), - 'matchResult' => $matchResult, - 'isDraw' => ($wins == 1 && $losses == 1), - 'verification' => $match->verification, - 'wins' => $wins, - 'losses' => $losses, - ]; -} diff --git a/psalm-baseline.xml b/psalm-baseline.xml index c9905dce0..8fac892bd 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -2007,12 +2007,6 @@ - - - - - - playera]]> playera]]> @@ -2027,12 +2021,8 @@ - - round]]> - - From 7a103cb22b23d9698840a396bdb1cf158a205c0e Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Wed, 20 Nov 2024 08:29:49 -0800 Subject: [PATCH 10/16] Stop confusing the typechecker with too-specific types --- gatherling/Models/Event.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gatherling/Models/Event.php b/gatherling/Models/Event.php index acc3456ba..d0d01823c 100644 --- a/gatherling/Models/Event.php +++ b/gatherling/Models/Event.php @@ -467,8 +467,8 @@ public function getFinalists(): array } /** - * @param array{0?: ?string, 1?: ?string} $t4 - * @param array{0?: ?string, 1?: ?string, 2?: ?string, 3?: ?string} $t8 + * @param list $t4 + * @param list $t8 */ public function setFinalists(string $win, ?string $sec, array $t4, array $t8): void { From d12cf9ea39f623e2da648440b0804a4409d46aff Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Wed, 20 Nov 2024 08:42:44 -0800 Subject: [PATCH 11/16] Accomodate events that don't have any final rounds Set the medals correctly and don't error upon event conclusion if, say, you only have 4 rounds Single Elimination as mainrounds with no finalrounds. This used to work in the old code but I didn't quite translate it right when refactoring: ``` // Assigns trophies based on the finals matches which are entered. public function assignTropiesFromMatches() { $t8 = []; $t4 = []; $sec = ''; $win = ''; if ($this->finalrounds > 0) { $quarter_finals = $this->finalrounds >= 3; if ($quarter_finals) { $quart_round = $this->mainrounds + $this->finalrounds - 2; $matches = $this->getRoundMatches($quart_round); foreach ($matches as $match) { $t8[] = $match->getLoser(); } } $semi_finals = $this->finalrounds >= 2; if ($semi_finals) { $semi_round = $this->mainrounds + $this->finalrounds - 1; $matches = $this->getRoundMatches($semi_round); foreach ($matches as $match) { $t4[] = $match->getLoser(); } } $finalmatches = $this->getRoundMatches($this->mainrounds + $this->finalrounds); $finalmatch = $finalmatches[0]; $sec = $finalmatch->getLoser(); $win = $finalmatch->getWinner(); } else { $quarter_finals = $this->mainrounds >= 3; if ($quarter_finals) { $quart_round = $this->mainrounds - 2; $matches = $this->getRoundMatches($quart_round); foreach ($matches as $match) { $t8[] = $match->getLoser(); } } $semi_finals = $this->mainrounds >= 2; if ($semi_finals) { $semi_round = $this->mainrounds - 1; $matches = $this->getRoundMatches($semi_round); foreach ($matches as $match) { $t4[] = $match->getLoser(); } } $finalmatches = $this->getRoundMatches($this->mainrounds); $finalmatch = $finalmatches[0]; $sec = $finalmatch->getLoser(); $win = $finalmatch->getWinner(); } $this->setFinalists($win, $sec, $t4, $t8); } ``` --- gatherling/Models/Event.php | 44 +++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/gatherling/Models/Event.php b/gatherling/Models/Event.php index d0d01823c..19239cd6b 100644 --- a/gatherling/Models/Event.php +++ b/gatherling/Models/Event.php @@ -890,32 +890,34 @@ public function assignTrophiesFromMatches(): void $finalRounds = $this->finalrounds; $totalRounds = $this->mainrounds + $finalRounds; + // If this event only has main rounds then those are effectively the "final rounds". + if ($finalRounds === 0) { + $finalRounds = $this->mainrounds; + } - if ($finalRounds > 0) { - $finalMatches = $this->getRoundMatches($totalRounds); - if (!empty($finalMatches)) { - $finalMatch = $finalMatches[0]; - $win = $finalMatch->getWinner(); - $sec = $finalMatch->getLoser(); - } + $finalMatches = $this->getRoundMatches($totalRounds); + if (!empty($finalMatches)) { + $finalMatch = $finalMatches[0]; + $win = $finalMatch->getWinner(); + $sec = $finalMatch->getLoser(); + } - if ($finalRounds >= 2) { - $semiMatches = $this->getRoundMatches($totalRounds - 1); - foreach ($semiMatches as $match) { - $loser = $match->getLoser(); - if ($loser !== null) { - $t4[] = $loser; - } + if ($finalRounds >= 2) { + $semiMatches = $this->getRoundMatches($totalRounds - 1); + foreach ($semiMatches as $match) { + $loser = $match->getLoser(); + if ($loser !== null) { + $t4[] = $loser; } } + } - if ($finalRounds >= 3) { - $quarterMatches = $this->getRoundMatches($totalRounds - 2); - foreach ($quarterMatches as $match) { - $loser = $match->getLoser(); - if ($loser !== null) { - $t8[] = $loser; - } + if ($finalRounds >= 3) { + $quarterMatches = $this->getRoundMatches($totalRounds - 2); + foreach ($quarterMatches as $match) { + $loser = $match->getLoser(); + if ($loser !== null) { + $t8[] = $loser; } } } From 630365d666be4848729ef1231d634d2fb55331cc Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Wed, 20 Nov 2024 11:51:38 -0800 Subject: [PATCH 12/16] Remove all remaining *Args funcs that return arrays in favor of Components --- gatherling/Models/Event.php | 9 --- gatherling/Views/Components/PlayerByeMenu.php | 23 +++++++ .../Views/Components/ResultDropMenu.php | 28 +++++++++ .../Views/Components/StructDropMenu.php | 26 ++++++++ gatherling/Views/Components/TrophyField.php | 19 ++++++ gatherling/Views/Pages/EventForm.php | 60 +++++-------------- gatherling/Views/Pages/MatchList.php | 58 +++--------------- 7 files changed, 119 insertions(+), 104 deletions(-) create mode 100644 gatherling/Views/Components/PlayerByeMenu.php create mode 100644 gatherling/Views/Components/ResultDropMenu.php create mode 100644 gatherling/Views/Components/StructDropMenu.php create mode 100644 gatherling/Views/Components/TrophyField.php diff --git a/gatherling/Models/Event.php b/gatherling/Models/Event.php index 19239cd6b..5a4bb473a 100644 --- a/gatherling/Models/Event.php +++ b/gatherling/Models/Event.php @@ -999,15 +999,6 @@ public function findNext(): ?self return null; } - /** @return array{link: string, text: string} */ - public function makeLinkArgs(string $text): array - { - return [ - 'link' => 'event.php?name=' . rawurlencode($this->name), - 'text' => $text, - ]; - } - public static function count(): int { return db()->int('SELECT COUNT(name) FROM events'); diff --git a/gatherling/Views/Components/PlayerByeMenu.php b/gatherling/Views/Components/PlayerByeMenu.php new file mode 100644 index 000000000..0f9a94669 --- /dev/null +++ b/gatherling/Views/Components/PlayerByeMenu.php @@ -0,0 +1,23 @@ +getRegisteredPlayers(true); + $options = []; + foreach ($playerNames as $player) { + $options[] = [ + 'value' => $player, + 'text' => $player, + ]; + } + parent::__construct('newbyeplayer', $options, '- Bye Player -'); + } +} diff --git a/gatherling/Views/Components/ResultDropMenu.php b/gatherling/Views/Components/ResultDropMenu.php new file mode 100644 index 000000000..226f70b97 --- /dev/null +++ b/gatherling/Views/Components/ResultDropMenu.php @@ -0,0 +1,28 @@ + $extraOptions + * @return array{name: string, default: string, options: array} + */ + public function __construct(string $name, array $extraOptions = []) + { + $options = [ + ['value' => '2-0', 'text' => '2-0'], + ['value' => '2-1', 'text' => '2-1'], + ['value' => '1-2', 'text' => '1-2'], + ['value' => '0-2', 'text' => '0-2'], + ['value' => 'D', 'text' => 'Draw'], + + ]; + foreach ($extraOptions as $value => $text) { + $options[] = ['value' => $value, 'text' => $text]; + } + parent::__construct($name, $options, '- Result -'); + } +} diff --git a/gatherling/Views/Components/StructDropMenu.php b/gatherling/Views/Components/StructDropMenu.php new file mode 100644 index 000000000..5e5017cb0 --- /dev/null +++ b/gatherling/Views/Components/StructDropMenu.php @@ -0,0 +1,26 @@ + $name, + 'text' => $name, + 'isSelected' => $def === $name, + ]; + } + + parent::__construct($field, $options, '- Structure -'); + } +} diff --git a/gatherling/Views/Components/TrophyField.php b/gatherling/Views/Components/TrophyField.php new file mode 100644 index 000000000..2631c3d53 --- /dev/null +++ b/gatherling/Views/Components/TrophyField.php @@ -0,0 +1,19 @@ +hasTrophy = (bool) $event->hastrophy; + $this->trophySrc = 'displayTrophy.php?event=' . rawurlencode($event->name); + } +} diff --git a/gatherling/Views/Pages/EventForm.php b/gatherling/Views/Pages/EventForm.php index 537cf2544..ca6aeb234 100644 --- a/gatherling/Views/Pages/EventForm.php +++ b/gatherling/Views/Pages/EventForm.php @@ -17,8 +17,9 @@ use Gatherling\Views\Components\FormatDropMenu; use Gatherling\Views\Components\SeasonDropMenu; use Gatherling\Views\Components\SeriesDropMenu; +use Gatherling\Views\Components\StructDropMenu; use Gatherling\Views\Components\TimeDropMenu; - +use Gatherling\Views\Components\TrophyField; use function Gatherling\Helpers\getObjectVarsCamelCase; use function Safe\preg_match; @@ -42,11 +43,9 @@ class EventForm extends EventFrame public TextInput $metagameUrlField; public TextInput $reportUrlField; public NumDropMenu $mainRoundsNumDropMenu; - /** @var array */ - public array $mainRoundsStructDropMenu; + public StructDropMenu $mainRoundsStructDropMenu; public NumDropMenu $finalRoundsNumDropMenu; - /** @var array */ - public array $finalRoundsStructDropMenu; + public StructDropMenu $finalRoundsStructDropMenu; public CheckboxInput $preregistrationAllowedCheckbox; public TextInput $lateEntryLimitField; public CheckboxInput $playerReportedResultsCheckbox; @@ -59,8 +58,7 @@ class EventForm extends EventFrame public ?CheckboxInput $finalizeEventCheckbox; public ?CheckboxInput $eventActiveCheckbox; public ?RoundDropMenu $currentRoundDropMenu; - /** @var ?array */ - public ?array $trophyField; + public ?TrophyField $trophyField; public bool $showCreateNextEvent; public bool $showCreateNextSeason; @@ -86,11 +84,17 @@ public function __construct(Event $event, bool $edit) $navLinks = []; $prevEvent = $event->findPrev(); if ($prevEvent) { - $navLinks[] = $prevEvent->makeLinkArgs('Previous'); + $navLinks[] = [ + 'link' => 'event.php?name=' . rawurlencode($prevEvent->name), + 'text' => 'Previous', + ]; } $nextEvent = $event->findNext(); if ($nextEvent) { - $navLinks[] = $nextEvent->makeLinkArgs('Next'); + $navLinks[] = [ + 'link' => 'event.php?name=' . rawurlencode($nextEvent->name), + 'text' => 'Next', + ]; } $yearDropMenu = new NumDropMenu('year', '- Year -', (int) date('Y') + 1, $year, 2011); $monthDropMenu = new MonthDropMenu($month); @@ -115,9 +119,9 @@ public function __construct(Event $event, bool $edit) $metagameUrlField = new TextInput('Metagame URL', 'metaurl', $event->metaurl, 60); $reportUrlField = new TextInput('Report URL', 'reporturl', $event->reporturl, 60); $mainRoundsNumDropMenu = new NumDropMenu('mainrounds', '- No. of Rounds -', 10, $event->mainrounds, 1); - $mainRoundsStructDropMenu = structDropMenuArgs('mainstruct', $event->mainstruct); + $mainRoundsStructDropMenu = new StructDropMenu('mainstruct', $event->mainstruct); $finalRoundsNumDropMenu = new NumDropMenu('finalrounds', '- No. of Rounds -', 10, $event->finalrounds, 0); - $finalRoundsStructDropMenu = structDropMenuArgs('finalstruct', $event->finalstruct); + $finalRoundsStructDropMenu = new StructDropMenu('finalstruct', $event->finalstruct); $preregistrationAllowedCheckbox = new CheckboxInput('Allow Pre-Registration', 'prereg_allowed', (bool) $event->prereg_allowed, null); $lateEntryLimitField = new TextInput('Late Entry Limit', 'late_entry_limit', $event->late_entry_limit, 4, 'The event host may still add players after this round.'); $playerReportedResultsCheckbox = new CheckboxInput('Allow Players to Report Results', 'player_reportable', (bool) $event->player_reportable); @@ -134,7 +138,7 @@ public function __construct(Event $event, bool $edit) $finalizeEventCheckbox = new CheckboxInput('Finalize Event', 'finalized', (bool) $event->finalized); $eventActiveCheckbox = new CheckboxInput('Event Active', 'active', (bool) $event->active); $currentRoundDropMenu = new RoundDropMenu($event, $event->current_round, 0); - $trophyField = trophyFieldArgs($event); + $trophyField = new TrophyField($event); $nextEventName = sprintf('%s %d.%02d', $event->series, $event->season, $event->number + 1); $nextSeasonName = sprintf('%s %d.%02d', $event->series, $event->season + 1, 1); $showCreateNextEvent = !Event::exists($nextEventName); @@ -192,35 +196,3 @@ function kValueSelectInput(int $kvalue): SelectInput ]; return new SelectInput('K-Value', 'kvalue', $names, $kvalue); } - -/** @return array{name: string, default: string, options: list} */ -function structDropMenuArgs(string $field, string $def): array -{ - $names = ['Swiss', 'Single Elimination', 'League', 'League Match']; - if ($def == 'Swiss (Blossom)') { - $def = 'Swiss'; - } - $options = []; - foreach ($names as $name) { - $options[] = [ - 'value' => $name, - 'text' => $name, - 'isSelected' => $def === $name, - ]; - } - - return [ - 'name' => $field, - 'default' => '- Structure -', - 'options' => $options, - ]; -} - -/** @return array{hasTrophy: bool, trophySrc: string} */ -function trophyFieldArgs(Event $event): array -{ - return [ - 'hasTrophy' => (bool) $event->hastrophy, - 'trophySrc' => 'displayTrophy.php?event=' . rawurlencode($event->name), - ]; -} diff --git a/gatherling/Views/Pages/MatchList.php b/gatherling/Views/Pages/MatchList.php index c87f578e3..cc06112c2 100644 --- a/gatherling/Views/Pages/MatchList.php +++ b/gatherling/Views/Pages/MatchList.php @@ -8,7 +8,9 @@ use Gatherling\Models\Player; use Gatherling\Views\Components\GameName; use Gatherling\Views\Components\RoundDropMenu; +use Gatherling\Views\Components\PlayerByeMenu; use Gatherling\Views\Components\PlayerDropMenu; +use Gatherling\Views\Components\ResultDropMenu; use Gatherling\Views\Components\UnverifiedPlayerCell; use function Gatherling\Helpers\getObjectVarsCamelCase; @@ -24,11 +26,9 @@ class MatchList extends EventFrame public array $lastRound; public PlayerDropMenu $playerADropMenu; public PlayerDropMenu $playerBDropMenu; - /** @var array{name: string, default: string, options: array} */ - public ?array $playerByeMenu; + public ?PlayerByeMenu $playerByeMenu; public ?RoundDropMenu $roundDropMenu; - /** @var array{name: string, default: string, options: array} */ - public ?array $resultDropMenu; + public ?ResultDropMenu $resultDropMenu; public bool $isBeforeRoundTwo; public string $structureSummary; public bool $isLeague; @@ -85,7 +85,7 @@ public function __construct(Event $event, string|int|null $newMatchRound) $isActiveUnverified = strcasecmp($match->verification, 'verified') != 0 && $event->finalized == 0; if ($isActiveUnverified) { $matchInfo['unverifiedPlayerCellA'] = new UnverifiedPlayerCell($event, $match, $playerA); - $matchInfo['resultDropMenu'] = resultDropMenuArgs('matchresult[]'); + $matchInfo['resultDropMenu'] = new ResultDropMenu('matchresult[]'); $matchInfo['unverifiedPlayerCellB'] = new UnverifiedPlayerCell($event, $match, $playerB); } else { $playerAWins = $match->getPlayerWins($match->playera); @@ -112,10 +112,10 @@ public function __construct(Event $event, string|int|null $newMatchRound) $playerBDropMenu = new PlayerDropMenu($event, 'B'); $playerByeMenu = $roundDropMenu = $resultDropMenu = null; if ($event->active) { - $playerByeMenu = playerByeMenuArgs($event); + $playerByeMenu = new PlayerByeMenu($event); } else { $roundDropMenu = new RoundDropMenu($event, $newMatchRound); - $resultDropMenu = resultDropMenuArgs('newmatchresult'); + $resultDropMenu = new ResultDropMenu('newmatchresult'); } $structure = $event->current_round > $event->mainrounds ? $event->finalstruct : $event->mainstruct; @@ -135,47 +135,3 @@ public function __construct(Event $event, string|int|null $newMatchRound) $this->isLeague = $isLeague; } } - -/** @return array{name: string, default: string, options: array} */ -function playerByeMenuArgs(Event $event): array -{ - $playerNames = $event->getRegisteredPlayers(true); - $options = []; - foreach ($playerNames as $player) { - $options[] = [ - 'value' => $player, - 'text' => $player, - ]; - } - - return [ - 'name' => 'newbyeplayer', - 'default' => '- Bye Player -', - 'options' => $options, - ]; -} - -/** - * @param array $extraOptions - * @return array{name: string, default: string, options: array} - */ -function resultDropMenuArgs(string $name, array $extraOptions = []): array -{ - $options = [ - ['value' => '2-0', 'text' => '2-0'], - ['value' => '2-1', 'text' => '2-1'], - ['value' => '1-2', 'text' => '1-2'], - ['value' => '0-2', 'text' => '0-2'], - ['value' => 'D', 'text' => 'Draw'], - - ]; - foreach ($extraOptions as $value => $text) { - $options[] = ['value' => $value, 'text' => $text]; - } - - return [ - 'name' => $name, - 'default' => '- Result -', - 'options' => $options, - ]; -} From abd7e719b34271b1123aecf2710baff54aa3194e Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Wed, 20 Nov 2024 12:07:17 -0800 Subject: [PATCH 13/16] Return assoc array when returning more than one var That's what we do elsewhere so let's stay consistent. This is more self- describing if a bit more verbose. --- gatherling/Data/Db.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gatherling/Data/Db.php b/gatherling/Data/Db.php index 00e16b6ad..ce896f29d 100644 --- a/gatherling/Data/Db.php +++ b/gatherling/Data/Db.php @@ -497,7 +497,7 @@ private function bindParams(PDOStatement $stmt, array $params): void /** * @param array $params - * @return array{0: string, 1: array} + * @return array{sql: string, params: array} */ private function expandArrayParams(string $sql, array $params): array { @@ -520,7 +520,7 @@ private function expandArrayParams(string $sql, array $params): array $expandedParams[$key] = $value; } } - return [$sql, $expandedParams]; + return ['sql' => $sql, 'params' => $expandedParams]; } /** @param array $params */ @@ -539,7 +539,7 @@ private function executeInternal(string $sql, array $params, callable $operation logger()->warning('[DB] DDL statement issued within transaction, this may cause issues.'); } - [$sql, $params] = $this->expandArrayParams($sql, $params); + ['sql' => $sql, 'params' => $params] = $this->expandArrayParams($sql, $params); try { $startTime = microtime(true); From c527b890b1df7aeb37cac703104a1d085bdf2a4a Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Wed, 20 Nov 2024 12:08:18 -0800 Subject: [PATCH 14/16] Remove a couple rogue colors in favor of vars --- gatherling/styles/css/stylesheet.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gatherling/styles/css/stylesheet.css b/gatherling/styles/css/stylesheet.css index 84072e73d..e67b7c165 100644 --- a/gatherling/styles/css/stylesheet.css +++ b/gatherling/styles/css/stylesheet.css @@ -517,7 +517,7 @@ a.create_deck_link { } .btn-primary:hover { - background: rgb(255 107 26 / 10%); + background: var(--highlight-background-color); border-color: var(--link-border-color); color: var(--link-emphasis-text-color); } @@ -1146,8 +1146,7 @@ table.center { background-color: var(--card-background-color); border-radius: var(--card-border-radius); padding: var(--spacing-20-static); - box-shadow: 0 var(--spacing-5-static) var(--spacing-5-static) -1px rgb(0 0 0 / - 10%); + box-shadow: 0 var(--spacing-5-static) var(--spacing-5-static) -1px var(--control-border-color); } .event-card a, From c851224e62eade465a434ff3ad00f73c91ef7679 Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Wed, 20 Nov 2024 21:03:53 -0800 Subject: [PATCH 15/16] Types for medallists --- gatherling/Models/Event.php | 4 ++-- .../Views/Components/{HostActiveEvents.php => HostEvents.php} | 0 gatherling/event.php | 4 ++-- .../{hostActiveEvents.mustache => hostEvents.mustache} | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename gatherling/Views/Components/{HostActiveEvents.php => HostEvents.php} (100%) rename gatherling/templates/partials/{hostActiveEvents.mustache => hostEvents.mustache} (100%) diff --git a/gatherling/Models/Event.php b/gatherling/Models/Event.php index 5a4bb473a..c0d31140a 100644 --- a/gatherling/Models/Event.php +++ b/gatherling/Models/Event.php @@ -1638,10 +1638,10 @@ public function assignMedalsByStandings(): void $t8 = $t4 = []; if ($medalCount >= 8) { - $t8 = array_map(fn($i) => $players[$i]->player, [4, 5, 6, 7]); + $t8 = array_values(array_filter(array_map(fn($i) => $players[$i]->player ?? null, [4, 5, 6, 7]))); } if ($medalCount >= 4) { - $t4 = array_map(fn($i) => $players[$i]->player, [2, 3]); + $t4 = array_values(array_filter(array_map(fn($i) => $players[$i]->player ?? null, [2, 3]))); } $sec = $players[1]->player ?? null; $win = $players[0]->player; diff --git a/gatherling/Views/Components/HostActiveEvents.php b/gatherling/Views/Components/HostEvents.php similarity index 100% rename from gatherling/Views/Components/HostActiveEvents.php rename to gatherling/Views/Components/HostEvents.php diff --git a/gatherling/event.php b/gatherling/event.php index b09938b23..e7b7abdcd 100644 --- a/gatherling/event.php +++ b/gatherling/event.php @@ -528,8 +528,8 @@ function updateMedals(): void $winner = post()->string('newmatchplayer1'); $second = post()->optionalString('newmatchplayer2'); - $t4 = [post()->optionalString('newmatchplayer3'), post()->optionalString('newmatchplayer4')]; - $t8 = [post()->optionalString('newmatchplayer5'), post()->optionalString('newmatchplayer6'), post()->optionalString('newmatchplayer7'), post()->optionalString('newmatchplayer8')]; + $t4 = array_values(array_filter([post()->optionalString('newmatchplayer3'), post()->optionalString('newmatchplayer4')])); + $t8 = array_values(array_filter([post()->optionalString('newmatchplayer5'), post()->optionalString('newmatchplayer6'), post()->optionalString('newmatchplayer7'), post()->optionalString('newmatchplayer8')])); $event->setFinalists($winner, $second, $t4, $t8); } diff --git a/gatherling/templates/partials/hostActiveEvents.mustache b/gatherling/templates/partials/hostEvents.mustache similarity index 100% rename from gatherling/templates/partials/hostActiveEvents.mustache rename to gatherling/templates/partials/hostEvents.mustache From c8849815a3efb3f603f5df1382ca6aee76b1b711 Mon Sep 17 00:00:00 2001 From: Thomas David Baker Date: Wed, 20 Nov 2024 21:04:17 -0800 Subject: [PATCH 16/16] Split event display into Pending, Active, Upcoming and Past Events Events that need to be started are most important, event that are running are next most important, events in the future are next, and old events are least; now the hierarchy of the page reflects that. Remove a bunch of not-super-useful columns from event display. This'll work better on mobile and "k value" and "host/cohost" were not super useful columns. "Finalized" is now implied by location on the page and can go too. --- gatherling/Views/Components/HostEvents.php | 30 +++++++--- .../Views/Components/ResultDropMenu.php | 5 +- gatherling/Views/Pages/EventList.php | 26 +++++--- gatherling/templates/eventList.mustache | 58 +++++++++--------- .../templates/partials/eventCard.mustache | 38 ++++++++++++ .../templates/partials/hostEvents.mustache | 59 +++++-------------- psalm-baseline.xml | 4 -- 7 files changed, 127 insertions(+), 93 deletions(-) create mode 100644 gatherling/templates/partials/eventCard.mustache diff --git a/gatherling/Views/Components/HostEvents.php b/gatherling/Views/Components/HostEvents.php index b94797e17..758696c89 100644 --- a/gatherling/Views/Components/HostEvents.php +++ b/gatherling/Views/Components/HostEvents.php @@ -6,25 +6,41 @@ use function Safe\strtotime; -class HostActiveEvents extends Component +class HostEvents extends Component { - /** @var array */ - public array $events = []; + /** @var list */ + public array $pendingEvents = []; + /** @var list */ + public array $activeEvents = []; + + public bool $hasPendingEvents; + public bool $hasActiveEvents; + public Icon $playersIcon; public Icon $structureIcon; public Icon $standingsIcon; - /** @param array $events */ - public function __construct(array $events) + /** + * @param list $pendingEvents + * @param list $activeEvents + */ + public function __construct(array $pendingEvents, array $activeEvents) { $this->playersIcon = new Icon('lucide:users'); $this->structureIcon = new Icon('lucide:trophy'); $this->standingsIcon = new Icon('lucide:chevron-right'); + $this->hasPendingEvents = count($pendingEvents) > 0; + $this->hasActiveEvents = count($activeEvents) > 0; + $now = time(); - foreach ($events as $event) { + foreach ($pendingEvents as $event) { + $event['startTime'] = new Time(strtotime($event['start']), $now); + $this->pendingEvents[] = $event; + } + foreach ($activeEvents as $event) { $event['startTime'] = new Time(strtotime($event['start']), $now); - $this->events[] = $event; + $this->activeEvents[] = $event; } } } diff --git a/gatherling/Views/Components/ResultDropMenu.php b/gatherling/Views/Components/ResultDropMenu.php index 226f70b97..bbb9dec5e 100644 --- a/gatherling/Views/Components/ResultDropMenu.php +++ b/gatherling/Views/Components/ResultDropMenu.php @@ -6,10 +6,7 @@ class ResultDropMenu extends DropMenu { - /** - * @param array $extraOptions - * @return array{name: string, default: string, options: array} - */ + /** @param array $extraOptions */ public function __construct(string $name, array $extraOptions = []) { $options = [ diff --git a/gatherling/Views/Pages/EventList.php b/gatherling/Views/Pages/EventList.php index 533f6fb47..d35774d24 100644 --- a/gatherling/Views/Pages/EventList.php +++ b/gatherling/Views/Pages/EventList.php @@ -8,9 +8,10 @@ use Gatherling\Models\HostedEventDto; use Gatherling\Models\Player; use Gatherling\Views\Components\FormatDropMenu; -use Gatherling\Views\Components\HostActiveEvents; +use Gatherling\Views\Components\HostEvents; use Gatherling\Views\Components\SeasonDropMenu; use Gatherling\Views\Components\SeriesDropMenu; +use Gatherling\Views\Components\Time; use function Gatherling\Helpers\db; use function Gatherling\Helpers\get; @@ -18,13 +19,15 @@ class EventList extends Page { - public ?HostActiveEvents $hostActiveEvents; + public ?HostEvents $hostEvents; public FormatDropMenu $formatDropMenu; public SeriesDropMenu $seriesDropMenu; public SeasonDropMenu $seasonDropMenu; public bool $hasPlayerSeries; /** @var list */ - public array $events = []; + public array $upcomingEvents; + /** @var list */ + public array $pastEvents; public bool $hasMore; public function __construct(string $seriesName, string $format, ?int $season) @@ -44,7 +47,7 @@ public function __construct(string $seriesName, string $format, ?int $season) 32 => 'Championship', ]; - $activeEvents = $seriesShown = []; + $pendingEvents = $activeEvents = $upcomingEvents = $pastEvents = $seriesShown = []; foreach ($events as $event) { $seriesShown[] = $event->series; $baseLink = 'event.php?name=' . rawurlencode($event->name) . '&view='; @@ -68,11 +71,18 @@ public function __construct(string $seriesName, string $format, ?int $season) 'standingsLink' => "{$baseLink}standings", 'structureSummary' => (new Event($event->name))->structureSummary(), ]; - $this->events[] = $eventInfo; - if ($event->active == 1 || (!$event->finalized && strtotime($event->start) <= strtotime('+1 day'))) { + if (!$event->active && !$event->finalized && strtotime($event->start) <= strtotime('+1 hour')) { + $pendingEvents[] = $eventInfo; + } elseif ($event->active == 1) { $activeEvents[] = $eventInfo; + } elseif (strtotime($event->start) > strtotime('+1 hour')) { + $upcomingEvents[] = $eventInfo; + } else { + $pastEvents[] = $eventInfo; } } + $this->upcomingEvents = array_reverse($upcomingEvents); + $this->pastEvents = $pastEvents; if ($seriesName) { $seriesShown = $playerSeries; @@ -80,7 +90,7 @@ public function __construct(string $seriesName, string $format, ?int $season) $seriesShown = array_values(array_unique($seriesShown)); } - $this->hostActiveEvents = count($activeEvents) > 0 ? new HostActiveEvents($activeEvents) : null; + $this->hostEvents = new HostEvents($pendingEvents, $activeEvents); $this->formatDropMenu = new FormatDropMenu(get()->optionalString('format'), true); $this->seriesDropMenu = new SeriesDropMenu($seriesName, 'All', $seriesShown); $this->seasonDropMenu = new SeasonDropMenu($season, 'All'); @@ -114,7 +124,7 @@ function queryEvents(Player $player, array $playerSeries, string $seriesName, st $sql .= ' AND e.season = :season'; $params['season'] = $season; } - $sql .= ' GROUP BY e.name ORDER BY e.finalized, e.start DESC LIMIT 100'; + $sql .= ' GROUP BY e.name ORDER BY e.start DESC LIMIT 100'; return db()->select($sql, HostedEventDto::class, $params); } diff --git a/gatherling/templates/eventList.mustache b/gatherling/templates/eventList.mustache index 08554c4bd..0c969628f 100644 --- a/gatherling/templates/eventList.mustache +++ b/gatherling/templates/eventList.mustache @@ -5,9 +5,9 @@ {{/hasPlayerSeries}} -{{#hostActiveEvents}} - {{> hostActiveEvents}} -{{/hostActiveEvents}} +{{#hostEvents}} + {{> hostEvents}} +{{/hostEvents}}

Filter Events

@@ -45,39 +45,43 @@
-

All Events

- +

Upcoming Events

+
- - - - - {{#events}} + {{#upcomingEvents}} - - - + - - - - {{/events}} - {{#hasMore}} - - + {{/upcomingEvents}} + + + +
EventFormatK-Value PlayersHost(s)Finalized
{{#isOngoing}}* {{/isOngoing}}{{name}}{{format}}{{kvalueDisplay}}{{name}} {{players}}{{host}}{{#cohost}}/{{cohost}}{{/cohost}}{{#finalized}}✔{{/finalized}}
 
 
+ +

Past Events

+ + + + + + {{#pastEvents}} - + + + - {{/hasMore}} + {{/pastEvents}} - +
EventPlayers
- This list only shows the 100 most recent results. - Please use the filters at the top of this page to find older - results. - {{name}}{{players}}
  
+ +{{#hasMore}} +

This list only shows the 100 most recent results. + Please use the filters at the top of this page to find older + results.

+{{/hasMore}} diff --git a/gatherling/templates/partials/eventCard.mustache b/gatherling/templates/partials/eventCard.mustache new file mode 100644 index 000000000..b2057ee12 --- /dev/null +++ b/gatherling/templates/partials/eventCard.mustache @@ -0,0 +1,38 @@ + diff --git a/gatherling/templates/partials/hostEvents.mustache b/gatherling/templates/partials/hostEvents.mustache index c488aaa44..f4d71cead 100644 --- a/gatherling/templates/partials/hostEvents.mustache +++ b/gatherling/templates/partials/hostEvents.mustache @@ -1,44 +1,17 @@ -

Current Events

-
- {{#events}} - - {{/events}} -
+{{#hasActiveEvents}} +

Active Events

+
+ {{#activeEvents}} + {{> eventCard}} + {{/activeEvents}} +
+{{/hasActiveEvents}} diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 8fac892bd..6bd6f100e 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -374,10 +374,6 @@ - - - -