diff --git a/Mimir/config/routes.php b/Mimir/config/routes.php index a85021b6a..03ea8f0bb 100644 --- a/Mimir/config/routes.php +++ b/Mimir/config/routes.php @@ -90,6 +90,8 @@ 'getNextPrescriptedSeating' => ['SeatingController', 'getNextSeatingForPrescriptedEvent'], 'getPrescriptedEventConfig' => ['EventsController', 'getPrescriptedEventConfig'], 'updatePrescriptedEventConfig' => ['EventsController', 'updatePrescriptedEventConfig'], + 'initStartingTimer' => ['EventsController', 'initStartingTimer'], + 'getStartingTimer' => ['EventsController', 'getStartingTimer'], 'addErrorLog' => ['MiscController', 'addErrorLog'], ]; diff --git a/Mimir/data/migrations/20220406011544_add_start_timer.php b/Mimir/data/migrations/20220406011544_add_start_timer.php new file mode 100644 index 000000000..fa52798d3 --- /dev/null +++ b/Mimir/data/migrations/20220406011544_add_start_timer.php @@ -0,0 +1,14 @@ +table('event') + ->addColumn('next_game_start_time', 'integer', ['default' => 0]) + ->addColumn('time_to_start', 'integer', ['default' => 600]) + ->save(); + } +} diff --git a/Mimir/src/controllers/Events.php b/Mimir/src/controllers/Events.php index 297705915..eb6971d1b 100644 --- a/Mimir/src/controllers/Events.php +++ b/Mimir/src/controllers/Events.php @@ -40,6 +40,7 @@ class EventsController extends Controller * @param int $lobbyId Tenhou lobby id for online tournaments * @param bool $isTeam If event is team tournament * @param bool $isPrescripted If tournament should have predefined seating + * @param int $autostartTimer Interval before games autostart * @param string $rulesetChangesJson Json-encoded changes for base ruleset * @throws BadActionException * @throws InvalidParametersException @@ -58,6 +59,7 @@ public function createEvent( $lobbyId, $isTeam, $isPrescripted, + $autostartTimer, $rulesetChangesJson ) { $this->_log->addInfo('Creating new event with [' . $ruleset . '] rules'); @@ -113,6 +115,7 @@ public function createEvent( ->setUseTimer(1) ->setUsePenalty(1) ->setIsTeam($isTeam ? 1 : 0) + ->setTimeToStart($autostartTimer) ->setIsPrescripted($isPrescripted ? 1 : 0) ; break; @@ -160,6 +163,7 @@ public function createEvent( * @param int $lobbyId Tenhou lobby id for online tournaments * @param bool $isTeam If event is team tournament * @param bool $isPrescripted If tournament should have predefined seating + * @param int $autostartTimer Interval before games are started automatically * @param string $rulesetChangesJson Json-encoded changes for base ruleset * @throws BadActionException * @throws InvalidParametersException @@ -178,6 +182,7 @@ public function updateEvent( $lobbyId, $isTeam, $isPrescripted, + $autostartTimer, $rulesetChangesJson ) { $this->_log->addInfo('Updating event with [' . $ruleset . '] rules'); @@ -213,6 +218,7 @@ public function updateEvent( $event ->setAutoSeating($isPrescripted ? 0 : 1) ->setIsTeam($isTeam ? 1 : 0) + ->setTimeToStart($autostartTimer) ->setIsPrescripted($isPrescripted ? 1 : 0) ; } elseif ($event->getIsOnline()) { // Should be online tournament @@ -271,6 +277,7 @@ public function getEventForEdit($id) 'minGames' => $event->getMinGamesCount(), 'isTeam' => $event->getIsTeam(), 'isPrescripted' => $event->getIsPrescripted(), + 'autostart' => $event->getTimeToStart(), 'rulesetChanges' => json_encode($event->getRulesetChanges() ?: []) ]; @@ -825,6 +832,8 @@ public function getTimerState($eventId) } $response['waiting_for_timer'] = ($event[0]->getGamesStatus() == EventPrimitive::GS_SEATING_READY); + $response['have_autostart'] = ($event[0]->getNextGameStartTime() > 0 && $event[0]->getTimeToStart() > 0); + $response['autostart_timer'] = $event[0]->getNextGameStartTime() - time(); $this->_log->addInfo('Successfully got timer data for event id#' . $eventId); @@ -1079,6 +1088,7 @@ public function getCountries($addr = '') */ public function rebuildEventScoring($eventId) { + $this->_log->addInfo('Rebuilding ratings for event #' . $eventId); if (!$this->_meta->isEventAdminById($eventId)) { throw new BadActionException("You don't have enough privileges to rebuild ratings for this event"); } @@ -1098,6 +1108,53 @@ public function rebuildEventScoring($eventId) $session->recreateHistory(); } + $this->_log->addInfo('Rebuild ratings successful for event #' . $eventId); return true; } + + /** + * @param int $eventId + * @return bool + * @throws BadActionException + * @throws InvalidParametersException + */ + public function initStartingTimer($eventId) + { + $this->_log->addInfo('Setting starting timer for event #' . $eventId); + if (!$this->_meta->isEventAdminById($eventId)) { + throw new BadActionException("You don't have enough privileges to init starting timer for this event"); + } + + $event = EventPrimitive::findById($this->_ds, [$eventId]); + if (empty($event)) { + throw new InvalidParametersException('Event id#' . $eventId . ' not found in DB'); + } + + $success = $event[0]->setNextGameStartTime(time() + $event[0]->getTimeToStart())->save(); + if ($success) { + $this->_log->addInfo('Successfully set starting timer for event #' . $eventId); + } else { + $this->_log->addInfo('Failed to set starting timer for event #' . $eventId); + } + + return $success; + } + + /** + * @param int $eventId + * @return int seconds to start + * @throws InvalidParametersException + */ + public function getStartingTimer($eventId) + { + $this->_log->addInfo('Getting starting timer for event #' . $eventId); + + $event = EventPrimitive::findById($this->_ds, [$eventId]); + if (empty($event)) { + throw new InvalidParametersException('Event id#' . $eventId . ' not found in DB'); + } + + $this->_log->addInfo('Successfully got starting timer for event #' . $eventId); + return $event[0]->getNextGameStartTime() - time(); + } } diff --git a/Mimir/src/models/InteractiveSession.php b/Mimir/src/models/InteractiveSession.php index a07c5a8eb..7b7e44d07 100644 --- a/Mimir/src/models/InteractiveSession.php +++ b/Mimir/src/models/InteractiveSession.php @@ -79,6 +79,8 @@ public function startGame($eventId, $playerIds, $tableIndex = null, $replayHash throw new InvalidUserException('Some players do not exist in DB, check ids'); } + $event[0]->setNextGameStartTime(0)->save(); + $newSession = new SessionPrimitive($this->_ds); $success = $newSession ->setEvent($event[0]) diff --git a/Mimir/src/primitives/Event.php b/Mimir/src/primitives/Event.php index f22a9d165..3334fddad 100644 --- a/Mimir/src/primitives/Event.php +++ b/Mimir/src/primitives/Event.php @@ -61,6 +61,8 @@ class EventPrimitive extends Primitive 'is_prescripted' => '_isPrescripted', 'min_games_count' => '_minGamesCount', 'finished' => '_finished', + 'next_game_start_time' => '_nextGameStartTime', + 'time_to_start' => '_timeToStart' ]; protected function _getFieldsTransforms() @@ -89,6 +91,8 @@ protected function _getFieldsTransforms() '_hideResults' => $this->_integerTransform(), '_minGamesCount' => $this->_integerTransform(), '_finished' => $this->_integerTransform(), + '_nextGameStartTime' => $this->_integerTransform(), + '_timeToStart' => $this->_integerTransform(), '_ruleset' => [ 'serialize' => function (\Common\Ruleset $rules) { return $rules->title(); @@ -249,6 +253,16 @@ protected function _getFieldsTransforms() * @var integer */ protected $_finished; + /** + * What interval next game should start in + * @var integer + */ + protected $_timeToStart; + /** + * Next game start time + * @var integer + */ + protected $_nextGameStartTime; /** * Status of games in event: one of * - seating_ready @@ -844,6 +858,42 @@ public function setIsFinished($isFinished) return $this; } + /** + * @return int + */ + public function getTimeToStart() + { + return $this->_timeToStart; + } + + /** + * @param int $time + * @return EventPrimitive + */ + public function setTimeToStart($time) + { + $this->_timeToStart = $time; + return $this; + } + + /** + * @return int + */ + public function getNextGameStartTime() + { + return $this->_nextGameStartTime; + } + + /** + * @param int $startTime + * @return EventPrimitive + */ + public function setNextGameStartTime($startTime) + { + $this->_nextGameStartTime = $startTime; + return $this; + } + /** * Check if events are compatible (can be used in aggregated event). * diff --git a/Mimir/tests/RealApiTest.php b/Mimir/tests/RealApiTest.php index 94cede8a5..a050efe26 100644 --- a/Mimir/tests/RealApiTest.php +++ b/Mimir/tests/RealApiTest.php @@ -62,12 +62,12 @@ public function testGameConfig() public function testTimer() { $response = $this->_client->execute('getTimerState', [1]); - $this->assertEquals([ - 'started' => false, - 'finished' => false, - 'time_remaining' => null, - 'waiting_for_timer' => false - ], $response); + + $this->assertFalse($response['started']); + $this->assertFalse($response['finished']); + $this->assertNull($response['time_remaining']); + $this->assertFalse($response['waiting_for_timer']); + $this->assertFalse($response['have_autostart']); $this->assertTrue($this->_client->execute('startTimer', [1])); $response = $this->_client->execute('getTimerState', [1]); diff --git a/Rheda/config/routes.php b/Rheda/config/routes.php index 91b23eb86..4ae5b29b3 100644 --- a/Rheda/config/routes.php +++ b/Rheda/config/routes.php @@ -42,6 +42,7 @@ '/tourn/(?toggleHideResults)' => 'TournamentControlPanel', '/tourn/(?finalizeSessions)' => 'TournamentControlPanel', '/tourn/(?sendNotification)' => 'TournamentControlPanel', + '/tourn/(?resetStartingTimer)' => 'TournamentControlPanel', '/prescript' => 'PrescriptControls', diff --git a/Rheda/i18n/de_DE.UTF-8/LC_MESSAGES/messages.mo b/Rheda/i18n/de_DE.UTF-8/LC_MESSAGES/messages.mo index f94ab1f8b..ec8822217 100644 Binary files a/Rheda/i18n/de_DE.UTF-8/LC_MESSAGES/messages.mo and b/Rheda/i18n/de_DE.UTF-8/LC_MESSAGES/messages.mo differ diff --git a/Rheda/i18n/de_DE.UTF-8/LC_MESSAGES/messages.po b/Rheda/i18n/de_DE.UTF-8/LC_MESSAGES/messages.po index aa9f82f80..7bff2c850 100644 --- a/Rheda/i18n/de_DE.UTF-8/LC_MESSAGES/messages.po +++ b/Rheda/i18n/de_DE.UTF-8/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Pantheon - Rheda\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-02-02 22:18+0300\n" +"POT-Creation-Date: 2022-06-06 01:10+0300\n" "PO-Revision-Date: \n" "Last-Translator: Jesterboxboy \n" "Language-Team: Jesterboxboy\n" @@ -15,8 +15,8 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 2.3\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.0.1\n" msgid "%d - %d (%d total)" msgstr "%d - %d (%d gesamt)" @@ -211,6 +211,9 @@ msgstr "Der Riichi Einsatz, Bitte" msgid "Apply penalty" msgstr "Strafe anwenden" +msgid "Autostart interval should be positive integer or zero" +msgstr "" + msgid "Average" msgstr "Durchschnitt" @@ -427,6 +430,11 @@ msgstr "" msgid "Drawn rounds: %d" msgstr "Unentschiedene Runden: %d" +msgid "" +"Duration in seconds before games are started automatically after seating is " +"ready. Set to 0 to disable autostart." +msgstr "" + msgid "E-mail address" msgstr "Email Adresse" @@ -581,12 +589,18 @@ msgstr "Spieldetails" msgid "Game duration in minutes" msgstr "Spieldauer in Minuten" +msgid "Games autostart interval" +msgstr "" + msgid "Games control panel" msgstr "Spieleinstellungen" msgid "Games played" msgstr "Spiele gespielt" +msgid "Games will be automatically started in: " +msgstr "" + msgid "Given for collecting a yakuman during tournament." msgstr "Für das Sammeln eines Yakuman während des Turniers." @@ -877,9 +891,6 @@ msgstr "Genau nach Plan" msgid "Kokushi musou" msgstr "Kokushi musou" -msgid "Language:" -msgstr "" - msgid "Last played round:" msgstr "Letzte gespielte Runde:" @@ -1328,6 +1339,12 @@ msgstr "Renhou" msgid "Replacement player" msgstr "Ersatzspieler" +msgid "Reset autostart timer" +msgstr "" + +msgid "Reset autostart timer?" +msgstr "" + msgid "Reset the timer" msgstr "Timer zurücksetzen" @@ -1404,6 +1421,9 @@ msgstr "Ryan peikou" msgid "Ryuu iisou" msgstr "Ryuu iisou" +msgid "STARTING IN:" +msgstr "" + msgid "San ankou" msgstr "San ankou" @@ -1425,6 +1445,11 @@ msgstr "Lokale IDs sichern" msgid "Save teams list" msgstr "Team Liste sichern" +msgid "" +"Seconds before tournament games are started automatically (after seating is " +"ready)" +msgstr "" + msgid "Select basic ruleset for event." msgstr "Regelsatz für Event auswählen." @@ -1437,13 +1462,6 @@ msgstr "Ihr Land auswählen" msgid "Send confirmation e-mail" msgstr "Bestätigungsmail senden" -#, fuzzy -msgid "Send notification" -msgstr "Bestätigungsmail senden" - -msgid "Send notification to players" -msgstr "" - msgid "Series" msgstr "Serie" @@ -1510,6 +1528,9 @@ msgid "" "add new city" msgstr "Wähle Stadt aus der Liste oder gib den Name manuell ein" +msgid "Starting now!" +msgstr "" + msgid "Statistics" msgstr "Statistik" @@ -1806,6 +1827,11 @@ msgid "" "You are required to log in with your email and password before proceeding" msgstr "Bitte mit Email und Passwort einloggen" +msgid "" +"You can press next button to reset the timer. Also you can start games " +"manually in any moment." +msgstr "" + msgid "Your e-mail address" msgstr "Deine Email Adresse" @@ -2043,18 +2069,16 @@ msgstr "ja" #~ "Zahlungsprüfungen durchgeführt: Im Falle einer Inkonsistenz wird der " #~ "Alarm angezeigt." -#~ msgid "SEATING" -#~ msgstr "SITZPLÄTZE" - #~ msgid "SOUTH" #~ msgstr "SÜD" -#~ msgid "Seating controls" -#~ msgstr "Sitzsteuerungen" - #~ msgid "Select player" #~ msgstr "Spieler auswählen" +#, fuzzy +#~ msgid "Send notification" +#~ msgstr "Bestätigungsmail senden" + #~ msgid "Sorry, this page is not available for mobile devices" #~ msgstr "Entschuldigung, diese Seite ist nicht für Mobilgeräte verfügbar" diff --git a/Rheda/i18n/messages.pot b/Rheda/i18n/messages.pot index 3a2a552b9..24ce86f66 100644 --- a/Rheda/i18n/messages.pot +++ b/Rheda/i18n/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-02-02 22:18+0300\n" +"POT-Creation-Date: 2022-06-06 01:10+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,12 +18,6 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -msgid "Game details" -msgstr "" - -msgid "Achievements" -msgstr "" - msgid "Achievement" msgstr "" @@ -84,6 +78,12 @@ msgid_plural "%d riichi bets" msgstr[0] "" msgstr[1] "" +msgid "Achievements" +msgstr "" + +msgid "Game details" +msgstr "" + msgid "The game has been successfully added!" msgstr "" @@ -93,6 +93,21 @@ msgstr "" msgid "Add game" msgstr "" +msgid "Recent games" +msgstr "" + +msgid "No games has been played yet" +msgstr "" + +msgid "%d - %d (%d total)" +msgstr "" + +msgid "Later" +msgstr "" + +msgid "Earlier" +msgstr "" + msgid "Open game control app" msgstr "" @@ -102,9 +117,6 @@ msgstr "" msgid "To event list" msgstr "" -msgid "Recent games" -msgstr "" - msgid "Rating table" msgstr "" @@ -183,13 +195,27 @@ msgstr "" msgid "Last series" msgstr "" -msgid "Rheda: mahjong statistic system" +msgid "Additional penalties" msgstr "" -msgid "Later" +msgid "" +"A penalty may be applied only to player currently in game. Otherwise error " +"will occur." msgstr "" -msgid "Earlier" +msgid "Penalty amount" +msgstr "" + +msgid "Penalty reason" +msgstr "" + +msgid "Check twice penalty data. You won't be able to cancel the penalty" +msgstr "" + +msgid "Apply penalty" +msgstr "" + +msgid "Rheda: mahjong statistic system" msgstr "" msgid "Manage profile: %s" @@ -243,12 +269,6 @@ msgstr "" msgid "Save" msgstr "" -msgid "No games has been played yet" -msgstr "" - -msgid "%d - %d (%d total)" -msgstr "" - msgid "Recover your password" msgstr "" @@ -278,26 +298,6 @@ msgstr "" msgid "Change password" msgstr "" -msgid "Additional penalties" -msgstr "" - -msgid "" -"A penalty may be applied only to player currently in game. Otherwise error " -"will occur." -msgstr "" - -msgid "Penalty amount" -msgstr "" - -msgid "Penalty reason" -msgstr "" - -msgid "Check twice penalty data. You won't be able to cancel the penalty" -msgstr "" - -msgid "Apply penalty" -msgstr "" - msgid "" "Please enter your e-mail address and password. We will send authentication " "link to your email, so you will be able to sign in and fill your personal " @@ -440,34 +440,6 @@ msgstr "" msgid "Average" msgstr "" -msgid "Predefined seating configuration" -msgstr "" - -msgid "Next session index (starting from 1):" -msgstr "" - -msgid "Update predefined seating config" -msgstr "" - -msgid "Found errors:" -msgstr "" - -msgid "No errors found." -msgstr "" - -msgid "" -"Predefined seating is entered as list of tables, one table per row. New game " -"session should be indicated as an empty line." -msgstr "" - -msgid "Example:" -msgstr "" - -msgid "" -"In this example, two tables play two games. Numbers in seating persist " -"between games and are defined at Admin actions / Manage players page." -msgstr "" - msgid "Player added successfully. Reload page to see results" msgstr "" @@ -537,75 +509,74 @@ msgstr "" msgid "Save teams list" msgstr "" -msgid "Rights and privileges: %s" -msgstr "" - -msgid "Customize" +msgid "Predefined seating configuration" msgstr "" -msgid "Title" +msgid "Next session index (starting from 1):" msgstr "" -msgid "Type" +msgid "Update predefined seating config" msgstr "" -msgid "Rating table has been hidden by tournament administrator" +msgid "Found errors:" msgstr "" -msgid "These results are visible only for tournament administrator" +msgid "No errors found." msgstr "" -msgid "Order by player rating" +msgid "" +"Predefined seating is entered as list of tables, one table per row. New game " +"session should be indicated as an empty line." msgstr "" -msgid "Order by player name" +msgid "Example:" msgstr "" -msgid "Order by average place" +msgid "" +"In this example, two tables play two games. Numbers in seating persist " +"between games and are defined at Admin actions / Manage players page." msgstr "" -msgid "Order by average score" +msgid "Rights and privileges: %s" msgstr "" -msgid "Team:" +msgid "Customize" msgstr "" -msgid "Table #" +msgid "Title" msgstr "" -msgid "Manage owned events" +msgid "Type" msgstr "" -msgid "" -"Really finish event #id (#title)? You won't be able to add games after event " -"is finished." +msgid "Rating table has been hidden by tournament administrator" msgstr "" -msgid "New club rating" +msgid "These results are visible only for tournament administrator" msgstr "" -msgid "New tournament" +msgid "Order by player rating" msgstr "" -msgid "New online tournament" +msgid "Order by player name" msgstr "" -msgid "Event" +msgid "Order by average place" msgstr "" -msgid "Controls" +msgid "Order by average score" msgstr "" -msgid "Edit" +msgid "Team:" msgstr "" -msgid "Privileges" +msgid "Starting now!" msgstr "" -msgid "Rebuild scoring" +msgid "STARTING IN:" msgstr "" -msgid "Finish" +msgid "Table #" msgstr "" msgid "Something went wrong: %s" @@ -629,21 +600,6 @@ msgstr "" msgid "Please wait a while and reload the page." msgstr "" -msgid "Manage events privileges: %s" -msgstr "" - -msgid "Revoke privilege #title?" -msgstr "" - -msgid "Privilege" -msgstr "" - -msgid "global" -msgstr "" - -msgid "Revoke" -msgstr "" - msgid "Update event" msgstr "" @@ -712,52 +668,68 @@ msgid "" "hand" msgstr "" +msgid "Games autostart interval" +msgstr "" + +msgid "" +"Duration in seconds before games are started automatically after seating is " +"ready. Set to 0 to disable autostart." +msgstr "" + msgid "Ruleset tuning" msgstr "" msgid "Create event" msgstr "" -msgid "Please enter your e-mail address and password." +msgid "Manage events privileges: %s" msgstr "" -msgid "Forgot your password?" +msgid "Revoke privilege #title?" msgstr "" -msgid "Send notification to players" +msgid "Privilege" msgstr "" -msgid "Language:" +msgid "Controls" msgstr "" -msgid "Send notification" +msgid "global" msgstr "" -msgid "View replay online" +msgid "Revoke" msgstr "" -msgid "Best hand: %s - %s" +msgid "Manage owned events" msgstr "" -msgid "Ron hands: %d" +msgid "" +"Really finish event #id (#title)? You won't be able to add games after event " +"is finished." msgstr "" -msgid "Tsumo hands: %d" +msgid "New club rating" msgstr "" -msgid "Drawn rounds: %d" +msgid "New tournament" msgstr "" -msgid "Chombo penalties: %s" +msgid "New online tournament" msgstr "" -msgid "Additional penalties:" +msgid "Event" msgstr "" -msgid "%s - %d points (%s)" +msgid "Edit" msgstr "" -msgid "Full game log:" +msgid "Privileges" +msgstr "" + +msgid "Rebuild scoring" +msgstr "" + +msgid "Finish" msgstr "" msgid "%s - %s (%s, pao: %s), yakuman!" @@ -823,18 +795,31 @@ msgstr "" msgid "Tenpai: %s." msgstr "" -msgid "Finish all the games" +msgid "View replay online" msgstr "" -msgid "" -"After this button is pressed, you won't be able to cancel any rounds, and " -"all the results will become visible to all players in rating table." +msgid "Best hand: %s - %s" msgstr "" -msgid "Finish the games? You will not be able to cancel rounds anymore!" +msgid "Ron hands: %d" msgstr "" -msgid "Confirm the results and finish all games" +msgid "Tsumo hands: %d" +msgstr "" + +msgid "Drawn rounds: %d" +msgstr "" + +msgid "Chombo penalties: %s" +msgstr "" + +msgid "Additional penalties:" +msgstr "" + +msgid "%s - %d points (%s)" +msgstr "" + +msgid "Full game log:" msgstr "" msgid "Rating" @@ -852,6 +837,32 @@ msgstr "" msgid "Yaku collected over all time" msgstr "" +msgid "Please enter your e-mail address and password." +msgstr "" + +msgid "Forgot your password?" +msgstr "" + +msgid "Finish all the games" +msgstr "" + +msgid "" +"After this button is pressed, you won't be able to cancel any rounds, and " +"all the results will become visible to all players in rating table." +msgstr "" + +msgid "Finish the games? You will not be able to cancel rounds anymore!" +msgstr "" + +msgid "Confirm the results and finish all games" +msgstr "" + +msgid "Ready to generate seating" +msgstr "" + +msgid "Select seating mode and press the appropriate button." +msgstr "" + msgid "Random seating" msgstr "" @@ -944,82 +955,90 @@ msgstr "" msgid "Use this seating" msgstr "" -msgid "Confirm round cancellation: " +msgid "Start timer and games" msgstr "" -msgid "" -"Confirm game definalization. Note that you will need to cancel last round by " -"hand after that." +msgid "Press next button to start the timer and begin the games." msgstr "" -msgid "Confirm game cancellation: " +msgid "Start games and timer?" msgstr "" -msgid "Tables state:" +msgid "Start games!" msgstr "" -msgid "Last played round:" +msgid "Games will be automatically started in: " msgstr "" -msgid "(no rounds are played yet)" +msgid "" +"You can press next button to reset the timer. Also you can start games " +"manually in any moment." msgstr "" -msgid "Penalties:" +msgid "Reset autostart timer?" msgstr "" -msgid "%s - %s points of penalty. Reason: %s" +msgid "Reset autostart timer" msgstr "" -msgid "Cancel last played round" +msgid "Reset the timer" msgstr "" -msgid "Cancel game" +msgid "" +"Pressing this button will reset the timer to its initial value. Be careful: " +"you should not need this usually." msgstr "" -msgid "Definalize game" +msgid "Really reset the timer?" msgstr "" -msgid "Ready to generate seating" +msgid "Hide/show rating table" msgstr "" -msgid "Select seating mode and press the appropriate button." +msgid "" +"Hiding rating table for all players may be useful to keep everybody " +"intrigued at the end of tournament" msgstr "" -msgid "Start timer and games" +msgid "Rating table is hidden. Show" msgstr "" -msgid "Press next button to start the timer and begin the games." +msgid "Rating table is visible. Hide" msgstr "" -msgid "Start games and timer?" +msgid "Confirm round cancellation: " msgstr "" -msgid "Start games!" +msgid "" +"Confirm game definalization. Note that you will need to cancel last round by " +"hand after that." msgstr "" -msgid "Reset the timer" +msgid "Confirm game cancellation: " msgstr "" -msgid "" -"Pressing this button will reset the timer to its initial value. Be careful: " -"you should not need this usually." +msgid "Tables state:" msgstr "" -msgid "Really reset the timer?" +msgid "Last played round:" msgstr "" -msgid "Hide/show rating table" +msgid "(no rounds are played yet)" msgstr "" -msgid "" -"Hiding rating table for all players may be useful to keep everybody " -"intrigued at the end of tournament" +msgid "Penalties:" msgstr "" -msgid "Rating table is hidden. Show" +msgid "%s - %s points of penalty. Reason: %s" msgstr "" -msgid "Rating table is visible. Hide" +msgid "Cancel last played round" +msgstr "" + +msgid "Cancel game" +msgstr "" + +msgid "Definalize game" msgstr "" msgid "

Oops.

Failed to get event configuration!" @@ -1342,6 +1361,9 @@ msgstr "" msgid "There must be non-zero duration for tournaments" msgstr "" +msgid "Autostart interval should be positive integer or zero" +msgstr "" + msgid "Lobby id must be in format: C####, where # is a digit" msgstr "" @@ -1405,6 +1427,11 @@ msgstr "" msgid "If timer should be used" msgstr "" +msgid "" +"Seconds before tournament games are started automatically (after seating is " +"ready)" +msgstr "" + msgid "If arbitrary penalties are enabled" msgstr "" diff --git a/Rheda/i18n/ru_RU.UTF-8/LC_MESSAGES/messages.mo b/Rheda/i18n/ru_RU.UTF-8/LC_MESSAGES/messages.mo index 825613793..2787f948b 100644 Binary files a/Rheda/i18n/ru_RU.UTF-8/LC_MESSAGES/messages.mo and b/Rheda/i18n/ru_RU.UTF-8/LC_MESSAGES/messages.mo differ diff --git a/Rheda/i18n/ru_RU.UTF-8/LC_MESSAGES/messages.po b/Rheda/i18n/ru_RU.UTF-8/LC_MESSAGES/messages.po index 4aca04964..9b732a8aa 100644 --- a/Rheda/i18n/ru_RU.UTF-8/LC_MESSAGES/messages.po +++ b/Rheda/i18n/ru_RU.UTF-8/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-02-02 22:18+0300\n" +"POT-Creation-Date: 2022-06-06 01:10+0300\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: \n" @@ -15,9 +15,9 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.0\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Poedit 3.0.1\n" msgid "%d - %d (%d total)" msgstr "%d - %d (всего %d)" @@ -220,6 +220,9 @@ msgstr "И Ваша риичи палочка" msgid "Apply penalty" msgstr "Применить штраф" +msgid "Autostart interval should be positive integer or zero" +msgstr "Интервал автостарта должен быть положительным целым числом либо нулем" + msgid "Average" msgstr "В среднем" @@ -439,6 +442,14 @@ msgstr "" msgid "Drawn rounds: %d" msgstr "Ничьих: %d" +msgid "" +"Duration in seconds before games are started automatically after seating is " +"ready. Set to 0 to disable autostart." +msgstr "" +"Время в секундах, по истечении которого игры начинаются автоматически после " +"того, как рассадка была опубликована. Установите в 0 чтобы выключить " +"автостарт." + msgid "E-mail address" msgstr "Адрес e-mail" @@ -589,12 +600,18 @@ msgstr "Подробнее об игре" msgid "Game duration in minutes" msgstr "Длительность игры в минутах" +msgid "Games autostart interval" +msgstr "Интервал автостарта игр" + msgid "Games control panel" msgstr "Управление играми" msgid "Games played" msgstr "Сыграно игр" +msgid "Games will be automatically started in: " +msgstr "Игры будут автоматически запущены через:" + msgid "Given for collecting a yakuman during tournament." msgstr "Дается за сбор якумана на турнире." @@ -892,9 +909,6 @@ msgstr "Как и планировалось" msgid "Kokushi musou" msgstr "Кокуши мусо" -msgid "Language:" -msgstr "Язык:" - msgid "Last played round:" msgstr "Последняя сыгранная раздача:" @@ -1345,6 +1359,12 @@ msgstr "Ренхо" msgid "Replacement player" msgstr "Игрок замены" +msgid "Reset autostart timer" +msgstr "Сбросить таймер автостарта" + +msgid "Reset autostart timer?" +msgstr "Сбросить таймер автостарта?" + msgid "Reset the timer" msgstr "Сбросить таймер" @@ -1421,6 +1441,9 @@ msgstr "Рянпейко" msgid "Ryuu iisou" msgstr "Рюисо" +msgid "STARTING IN:" +msgstr "НАЧАЛО ЧЕРЕЗ:" + msgid "San ankou" msgstr "Сананко" @@ -1442,6 +1465,13 @@ msgstr "Сохранить локальные ID" msgid "Save teams list" msgstr "Сохранить список команд" +msgid "" +"Seconds before tournament games are started automatically (after seating is " +"ready)" +msgstr "" +"Число секунд перед тем как турнирные игры будут запущены автоматически " +"(после генерации рассадки)" + msgid "Select basic ruleset for event." msgstr "Выберите базовый набор правил для события." @@ -1454,12 +1484,6 @@ msgstr "Выберите страну, которую вы представля msgid "Send confirmation e-mail" msgstr "Выслать письмо с подтверждением" -msgid "Send notification" -msgstr "Послать уведомление" - -msgid "Send notification to players" -msgstr "Послать уведомление игрокам" - msgid "Series" msgstr "Серии игр" @@ -1532,6 +1556,9 @@ msgstr "" "Начните ввод и выберите город из списка, либо просто напишите полное " "название города" +msgid "Starting now!" +msgstr "Начинаем!" + msgid "Statistics" msgstr "Статистика" @@ -1836,6 +1863,13 @@ msgstr "" "Перед тем как продолжить, вам необходимо войти, используя адрес e-mail и " "пароль, указанные при регистрации" +msgid "" +"You can press next button to reset the timer. Also you can start games " +"manually in any moment." +msgstr "" +"Вы можете нажать следующую кнопку для сброса таймера. Также вы можете в " +"любой момент начать игры, не дожидаясь таймера." + msgid "Your e-mail address" msgstr "Ваш адрес e-mail" @@ -2037,6 +2071,9 @@ msgstr "да" #~ msgid "If tanyao counts on open hand" #~ msgstr "Засчитывается ли таняо на открытой руке" +#~ msgid "Language:" +#~ msgstr "Язык:" + #~ msgid "Login" #~ msgstr "Вход" @@ -2120,15 +2157,9 @@ msgstr "да" #~ "Ренчаны/хонба просчитываются автоматически. Очки и выплаты контролируются " #~ "системой, в случае несоответствия будет выдано предупреждение." -#~ msgid "SEATING" -#~ msgstr "РАССАДКА" - #~ msgid "SOUTH" #~ msgstr "ЮГ" -#~ msgid "Seating controls" -#~ msgstr "Управление рассадкой" - #~ msgid "" #~ "Secondary divider (applied after primary) to make rating points from uma/" #~ "oka compliant points" @@ -2137,6 +2168,12 @@ msgstr "да" #~ msgid "Select player" #~ msgstr "Выберите игрока" +#~ msgid "Send notification" +#~ msgstr "Послать уведомление" + +#~ msgid "Send notification to players" +#~ msgstr "Послать уведомление игрокам" + #~ msgid "Sorry, this page is not available for mobile devices" #~ msgstr "Извините, эта страница недоступна для мобильных устройств" diff --git a/Rheda/src/MimirClient.php b/Rheda/src/MimirClient.php index 168243db7..62ba9e418 100644 --- a/Rheda/src/MimirClient.php +++ b/Rheda/src/MimirClient.php @@ -356,13 +356,14 @@ public function rebuildScoring(int $eventId): bool * @param int $lobbyId * @param bool $isTeam * @param bool $isPrescripted + * @param int $autostartTimer * @param string $rulesetChangesJson * @return int */ - public function createEvent(string $type, string $title, string $description, string $ruleset, int $gameDuration, string $timezone, int $series, int $minGamesCount, int $lobbyId, bool $isTeam, bool $isPrescripted, string $rulesetChangesJson): int + public function createEvent(string $type, string $title, string $description, string $ruleset, int $gameDuration, string $timezone, int $series, int $minGamesCount, int $lobbyId, bool $isTeam, bool $isPrescripted, int $autostartTimer, string $rulesetChangesJson): int { /** @phpstan-ignore-next-line */ - return (int)$this->_client->execute('createEvent', [$type, $title, $description, $ruleset, $gameDuration, $timezone, $series, $minGamesCount, $lobbyId, $isTeam, $isPrescripted, $rulesetChangesJson]); + return (int)$this->_client->execute('createEvent', [$type, $title, $description, $ruleset, $gameDuration, $timezone, $series, $minGamesCount, $lobbyId, $isTeam, $isPrescripted, $autostartTimer, $rulesetChangesJson]); } /** @@ -379,13 +380,14 @@ public function createEvent(string $type, string $title, string $description, st * @param int $lobbyId * @param bool $isTeam * @param bool $isPrescripted + * @param int $autostartTimer * @param string $rulesetChangesJson * @return bool */ - public function updateEvent(int $id, string $title, string $description, string $ruleset, int $gameDuration, string $timezone, int $series, int $minGamesCount, int $lobbyId, bool $isTeam, bool $isPrescripted, string $rulesetChangesJson): bool + public function updateEvent(int $id, string $title, string $description, string $ruleset, int $gameDuration, string $timezone, int $series, int $minGamesCount, int $lobbyId, bool $isTeam, bool $isPrescripted, int $autostartTimer, string $rulesetChangesJson): bool { /** @phpstan-ignore-next-line */ - return (bool)$this->_client->execute('updateEvent', [$id, $title, $description, $ruleset, $gameDuration, $timezone, $series, $minGamesCount, $lobbyId, $isTeam, $isPrescripted, $rulesetChangesJson]); + return (bool)$this->_client->execute('updateEvent', [$id, $title, $description, $ruleset, $gameDuration, $timezone, $series, $minGamesCount, $lobbyId, $isTeam, $isPrescripted, $autostartTimer, $rulesetChangesJson]); } /** @@ -424,17 +426,6 @@ public function startTimer(int $eventId): bool return (bool)$this->_client->execute('startTimer', [$eventId]); } - /** - * @param int $eventId - * @param array $notification - * @return void - */ - public function sendNotification(int $eventId, array $notification): void - { - /** @phpstan-ignore-next-line */ - $this->_client->execute('sendNotification', [$eventId, $notification]); - } - /** * Register for participation in event (from admin control panel) * @@ -781,6 +772,26 @@ public function updatePrescriptedEventConfig(int $eventId, int $nextSessionIndex return $this->_client->execute('updatePrescriptedEventConfig', [$eventId, $nextSessionIndex, $prescript]); } + /** + * @param int $eventId + * @return bool + */ + public function initStartingTimer(int $eventId): bool + { + /** @phpstan-ignore-next-line */ + return (bool)$this->_client->execute('initStartingTimer', [$eventId]); + } + + /** + * @param int $eventId + * @return int + */ + public function getStartingTimer(int $eventId): int + { + /** @phpstan-ignore-next-line */ + return (int)$this->_client->execute('getStartingTimer', [$eventId]); + } + /** * @param string $facility * @param string $sessionHash diff --git a/Rheda/src/controllers/Timer.php b/Rheda/src/controllers/Timer.php index b12933c2c..ed06b3867 100644 --- a/Rheda/src/controllers/Timer.php +++ b/Rheda/src/controllers/Timer.php @@ -53,6 +53,7 @@ protected function _run(): array $this->_mainEventRules->timerPolicy() === 'yellowZone' ? $this->_mainEventRules->yellowZone() : 0 ); $timerState = $this->_mimir->getTimerState($this->_mainEventId); + $autostartTimer = $this->_mimir->getStartingTimer($this->_mainEventId); $remaining = $timerState['time_remaining'] - $zonesDuration; $currentSeating = $this->_formatSeating($this->_mimir->getCurrentSeating($this->_mainEventId)); $durationWithoutSeating = $this->_mainEventRules->gameDuration() - 5 - ($zonesDuration / 60); @@ -72,6 +73,8 @@ protected function _run(): array } return [ + 'startingTimer' => $autostartTimer, + 'haveStartingTimer' => $autostartTimer > 0, 'waiting' => $this->_mainEventRules->gamesWaitingForTimer(), 'redZoneLength' => $this->_mainEventRules->redZone() / 60, 'yellowZoneLength' => $this->_mainEventRules->yellowZone() / 60, diff --git a/Rheda/src/controllers/TournamentControlPanel.php b/Rheda/src/controllers/TournamentControlPanel.php index de9066ad8..554d9ec8f 100644 --- a/Rheda/src/controllers/TournamentControlPanel.php +++ b/Rheda/src/controllers/TournamentControlPanel.php @@ -59,15 +59,22 @@ protected function _beforeRun() switch ($this->_path['action']) { case 'shuffledSeating': $this->_mimir->makeShuffledSeating($this->_mainEventId, 1, mt_rand(100000, 999999)); + $this->_mimir->initStartingTimer($this->_mainEventId); break; case 'intervalSeating': $this->_mimir->makeIntervalSeating($this->_mainEventId, intval($_POST['step'])); + $this->_mimir->initStartingTimer($this->_mainEventId); break; case 'predefinedSeating': $this->_mimir->makePrescriptedSeating($this->_mainEventId, isset($_POST['rndseat']) && $_POST['rndseat'] == 1); + $this->_mimir->initStartingTimer($this->_mainEventId); break; case 'swissSeating': $this->_mimir->makeSwissSeating($this->_mainEventId); + $this->_mimir->initStartingTimer($this->_mainEventId); + break; + case 'resetStartingTimer': + $this->_mimir->initStartingTimer($this->_mainEventId); break; case 'startTimer': $this->_mimir->startTimer($this->_mainEventId); @@ -163,8 +170,11 @@ protected function _run(): array } } + $startingTimer = $this->_mimir->getStartingTimer($this->_mainEventId); return [ 'error' => null, + 'startingTimer' => $startingTimer, + 'haveStartingTimer' => $startingTimer > 0, 'isTournament' => true, // false in games control panel controller 'tablesList' => empty($_POST['description']) ? '' : $_POST['description'], 'tables' => $tablesFormatted, diff --git a/Rheda/src/controllers/UserActionEventEdit.php b/Rheda/src/controllers/UserActionEventEdit.php index ad402129e..3319ae95a 100644 --- a/Rheda/src/controllers/UserActionEventEdit.php +++ b/Rheda/src/controllers/UserActionEventEdit.php @@ -47,6 +47,7 @@ class UserActionEventEdit extends Controller * @var array */ protected $_defaultSettings = [ + 'startingTimer' => 600, 'duration' => 90, 'seriesLength' => 0, 'minGames' => 0, @@ -159,6 +160,7 @@ protected function _newTournamentEvent($prevData) 'all_yaku' => $rulesets['all_yaku'], 'pao_yaku' => $rulesets['pao_yaku'], 'yaku_translations' => $rulesets['yaku_translations'], + 'autostart' => $prevData['autostart'], 'available_timezones' => $this->_getTimezones(empty($prevData['timezone']) ? '' : $prevData['timezone']), ]); } @@ -206,6 +208,7 @@ protected function _editEvent($eventId) 'ruleset_fields_names' => $rulesets['fields_names'], 'all_yaku' => $rulesets['all_yaku'], 'pao_yaku' => $rulesets['pao_yaku'], + 'autostart' => $prevData['autostart'], 'yaku_translations' => $rulesets['yaku_translations'], 'available_timezones' => $this->_getTimezones(empty($prevData['timezone']) ? '' : $prevData['timezone']), ]); @@ -279,6 +282,10 @@ protected function _checkData($data) $checkedData['error_duration'] = _t('There must be non-zero duration for tournaments'); } + if (!empty($data['autostart']) && is_numeric($data['autostart']) && !empty($data['isTournament']) && intval($data['autostart']) < 0) { + $checkedData['error_autostart'] = _t('Autostart interval should be positive integer or zero'); + } + if (!empty($data['isOnline']) && ( empty($data['lobbyId']) || !preg_match('#^C\d+$#is', $data['lobbyId'])) ) { @@ -350,6 +357,7 @@ protected function _saveNewEvent($checkData) empty($checkData['lobbyId']) ? 0 : intval('1' . str_replace('C', '', $checkData['lobbyId'])), empty($checkData['isTeam']) ? false : true, empty($checkData['isPrescripted']) ? false : true, + intval($checkData['autostart']), empty($checkData['rulesetChanges']) ? '{}' : (json_encode($checkData['rulesetChanges']) ?: '{}') ); $ruleId = $this->_frey->addRuleForPerson( @@ -388,6 +396,7 @@ protected function _saveExistingEvent($checkData) empty($checkData['lobbyId']) ? 0 : intval('1' . str_replace('C', '', $checkData['lobbyId'])), empty($checkData['isTeam']) ? false : true, empty($checkData['isPrescripted']) ? false : true, + intval($checkData['autostart']), empty($checkData['rulesetChanges']) ? '{}' : (json_encode($checkData['rulesetChanges']) ?: '{}') ); if (!$success) { diff --git a/Rheda/src/helpers/Config.php b/Rheda/src/helpers/Config.php index d848ccd4a..2a768164d 100644 --- a/Rheda/src/helpers/Config.php +++ b/Rheda/src/helpers/Config.php @@ -195,6 +195,10 @@ class Config * @var int */ protected $_chipsValue = 0; + /** + * @var int + */ + protected $_startingTimer = 0; /** * @var bool */ @@ -249,6 +253,7 @@ class Config 'isPrescripted' => false, 'minGamesCount' => 0, 'isFinished' => false, + 'startingTimer' => 0, ]; /** @@ -267,6 +272,7 @@ public static function getRuleDescriptions(): array 'sortByGames' => _t('If rating table should first be sorted by number of games played'), 'allowPlayerAppend' => _t('If new players are allowed to join in the middle of event'), 'useTimer' => _t('If timer should be used'), + 'startingTimer' => _t('Seconds before tournament games are started automatically (after seating is ready)'), 'usePenalty' => _t('If arbitrary penalties are enabled'), 'seriesLength' => _t('If game series are enabled'), 'isPrescripted' => _t('If seating for all games is entered in advance'), @@ -593,6 +599,13 @@ public function useTimer() { return $this->_useTimer; } + /** + * @return int + */ + public function startingTimer() + { + return $this->_startingTimer; + } /** * @return bool */ diff --git a/Rheda/src/interfaces/IMimirClient.php b/Rheda/src/interfaces/IMimirClient.php index f5b32150a..213a42635 100644 --- a/Rheda/src/interfaces/IMimirClient.php +++ b/Rheda/src/interfaces/IMimirClient.php @@ -246,10 +246,11 @@ public function rebuildScoring(int $eventId): bool; * @param int $lobbyId * @param bool $isTeam * @param bool $isPrescripted + * @param int $autostartTimer * @param string $rulesetChangesJson * @return int */ - public function createEvent(string $type, string $title, string $description, string $ruleset, int $gameDuration, string $timezone, int $series, int $minGamesCount, int $lobbyId, bool $isTeam, bool $isPrescripted, string $rulesetChangesJson): int; + public function createEvent(string $type, string $title, string $description, string $ruleset, int $gameDuration, string $timezone, int $series, int $minGamesCount, int $lobbyId, bool $isTeam, bool $isPrescripted, int $autostartTimer, string $rulesetChangesJson): int; /** * Update settings of existing event @@ -265,10 +266,11 @@ public function createEvent(string $type, string $title, string $description, st * @param int $lobbyId * @param bool $isTeam * @param bool $isPrescripted + * @param int $autostartTimer * @param string $rulesetChangesJson * @return bool */ - public function updateEvent(int $id, string $title, string $description, string $ruleset, int $gameDuration, string $timezone, int $series, int $minGamesCount, int $lobbyId, bool $isTeam, bool $isPrescripted, string $rulesetChangesJson): bool; + public function updateEvent(int $id, string $title, string $description, string $ruleset, int $gameDuration, string $timezone, int $series, int $minGamesCount, int $lobbyId, bool $isTeam, bool $isPrescripted, int $autostartTimer, string $rulesetChangesJson): bool; /** * Finish event @@ -294,13 +296,6 @@ public function getTablesState(int $eventId): array; */ public function startTimer(int $eventId): bool; - /** - * @param int $eventId - * @param array $notification - * @return void - */ - public function sendNotification(int $eventId, array $notification): void; - /** * Register for participation in event (from admin control panel) * @@ -539,6 +534,18 @@ public function getPrescriptedEventConfig(int $eventId); */ public function updatePrescriptedEventConfig(int $eventId, int $nextSessionIndex, string $prescript); + /** + * @param int $eventId + * @return bool + */ + public function initStartingTimer(int $eventId): bool; + + /** + * @param int $eventId + * @return int + */ + public function getStartingTimer(int $eventId): int; + /** * @param string $facility * @param string $sessionHash diff --git a/Rheda/src/templates/Timer.handlebars b/Rheda/src/templates/Timer.handlebars index 06b48d2b8..f5be8da5a 100755 --- a/Rheda/src/templates/Timer.handlebars +++ b/Rheda/src/templates/Timer.handlebars @@ -46,12 +46,14 @@ !function () { var soundBeforeEnd = new Audio("/assets/5min.wav"); // buffers automatically when created var initialTime = '{{initialTime}}'.split(':'); + var autostartTimer = {{startingTimer}}; var minutes = parseInt(initialTime[0]); var seconds = parseInt(initialTime[1]) / 10; var redZone = '{{redZone}}' === '1'; var yellowZone = '{{yellowZone}}' === '1'; var waiting = '{{waiting}}'; var timer; + var autostart; $(function () { runTimer(); @@ -60,6 +62,20 @@ $('.seating').show(); } timer = window.setInterval(runTimer, 10000); + if (autostartTimer && autostartTimer > 0) { + autostart = window.setInterval(function () { + $('.timer').addClass('mini'); + autostartTimer--; + if (autostartTimer <= 0) { + window.clearInterval(autostart); + $('#autostart').html('{{_t 'Starting now!'}}'); + window.setTimeout(function () { + window.location.reload(); + }, 4000); + } + $('#autostart').html(Math.floor(autostartTimer / 60) + ':' + (autostartTimer % 60 < 10 ? '0' : '') + (autostartTimer % 60)) + }, 1000); + } }); function formatTime(minutes, seconds) { @@ -131,6 +147,16 @@ {{/waiting}} + {{#haveStartingTimer}} +
+
+ {{_t 'STARTING IN:'}} +
+
+ {{initialTime}} +
+
+ {{/haveStartingTimer}} {{#seating}}
diff --git a/Rheda/src/templates/UserActionEventEdit.handlebars b/Rheda/src/templates/UserActionEventEdit.handlebars index a46ad88e1..9d409598e 100644 --- a/Rheda/src/templates/UserActionEventEdit.handlebars +++ b/Rheda/src/templates/UserActionEventEdit.handlebars @@ -227,6 +227,21 @@ {{_t "Choose this if you want to define your own custom seating for tournament by hand"}}
+
+ + + {{#error_autostart}} +
{{error_autostart}}
+ {{/error_autostart}} + {{^error_autostart}} + + {{_t "Duration in seconds before games are started automatically after seating is ready. Set to 0 to disable autostart."}} + + {{/error_autostart}} +
{{/isTournament}}
diff --git a/Rheda/src/templates/partials/_TournamentControlsStageSeatingReady.handlebars b/Rheda/src/templates/partials/_TournamentControlsStageSeatingReady.handlebars index 363065398..92f48301b 100644 --- a/Rheda/src/templates/partials/_TournamentControlsStageSeatingReady.handlebars +++ b/Rheda/src/templates/partials/_TournamentControlsStageSeatingReady.handlebars @@ -1,13 +1,51 @@ +

{{_t 'Start timer and games'}}

-
{{_t 'Press next button to start the timer and begin the games.'}}
- {{#form action='/tourn/startTimer/' method='post'}} - - {{/form}} +
+
+ {{_t 'Press next button to start the timer and begin the games.'}} +
+
+ {{#form action='/tourn/startTimer/' method='post'}} + + {{/form}} +
+
+ {{#haveStartingTimer}} +
+
+
+ {{_t 'Games will be automatically started in: '}} +
+ {{_t 'You can press next button to reset the timer. Also you can start games manually in any moment.'}} +
+
+ {{#form action='/tourn/resetStartingTimer/' method='post'}} + + {{/form}} +
+
+ {{/haveStartingTimer}}

diff --git a/Tyr/app/components/screens/table/TableHelper.ts b/Tyr/app/components/screens/table/TableHelper.ts index eb3c0afa9..3a75b18ff 100644 --- a/Tyr/app/components/screens/table/TableHelper.ts +++ b/Tyr/app/components/screens/table/TableHelper.ts @@ -55,7 +55,7 @@ import {TableInfoProps} from '#/components/screens/table/base/TableInfo'; import {roundToString} from '#/components/helpers/Utils'; import {AppOutcome} from '#/interfaces/app'; import {getNextWinnerWithPao} from '#/store/selectors/paoSelectors'; -import {formatTime, getTimeRemaining} from '#/store/selectors/overviewSelectors'; +import {formatTime, getAutostartTimeRemaining, getTimeRemaining} from '#/store/selectors/overviewSelectors'; import {I18nService} from "#/services/i18n"; // todo move to selectors most of code from here @@ -516,6 +516,7 @@ export function getTableInfo(state: IAppState, dispatch: Dispatch): TableInfoPro let tableNumber = state.currentOtherTable?.tableIndex ?? state.tableIndex; let showRoundInfo = true; let showTimer = false; + let isAutostartTimer = false; let currentTime: string | undefined = undefined; let gamesLeft: number | undefined = undefined; @@ -530,6 +531,13 @@ export function getTableInfo(state: IAppState, dispatch: Dispatch): TableInfoPro showTableNumber = true; showRoundInfo = false; } + + const timeRemaining = getAutostartTimeRemaining(state); + if (timeRemaining !== undefined) { + showTimer = true; + isAutostartTimer = true; + currentTime = formatTime(timeRemaining.minutes, timeRemaining.seconds); + } } else if (state.currentScreen === 'currentGame' || state.currentScreen === 'outcomeSelect') { const timeRemaining = getTimeRemaining(state); if (timeRemaining !== undefined) { @@ -548,6 +556,7 @@ export function getTableInfo(state: IAppState, dispatch: Dispatch): TableInfoPro showRoundInfo, showTableNumber, showTimer, + isAutostartTimer, gamesLeft, round: roundToString(state.currentOtherTable?.currentRound ?? state.currentRound), honbaCount: state.currentOtherTable?.honba ?? state.honba, diff --git a/Tyr/app/components/screens/table/base/TableInfo.tsx b/Tyr/app/components/screens/table/base/TableInfo.tsx index 058cdd9f4..e6739dbe6 100644 --- a/Tyr/app/components/screens/table/base/TableInfo.tsx +++ b/Tyr/app/components/screens/table/base/TableInfo.tsx @@ -10,6 +10,7 @@ export type TableInfoProps = { showRoundInfo?: boolean; showTableNumber?: boolean; showTimer?: boolean; + isAutostartTimer?: boolean; gamesLeft?: number; round?: string; honbaCount?: number; @@ -29,6 +30,7 @@ export const TableInfo = React.memo(function (props: TableInfoProps) { showTableNumber, showRotators, showTimer, + isAutostartTimer, gamesLeft, round, honbaCount, @@ -82,6 +84,12 @@ export const TableInfo = React.memo(function (props: TableInfoProps) {
{loc._t('Table #%1', [tableNumber])}
+ {showTimer && isAutostartTimer && (<>
{loc._t('Time before game start:')}
)} + {showTimer && ( +
+ {currentTime} +
+ )} )}
diff --git a/Tyr/app/components/screens/table/base/page-table.css b/Tyr/app/components/screens/table/base/page-table.css index da2d1c336..e15cf91bb 100644 --- a/Tyr/app/components/screens/table/base/page-table.css +++ b/Tyr/app/components/screens/table/base/page-table.css @@ -61,3 +61,14 @@ .table-info__games-left-caption { font-size: 14px; } + +.table-info__autostart-hint { + text-align: center; +} + +.table-info__autostart-separator { + color: #555; + background-color: #555; + height: 1px; + width: 100%; +} diff --git a/Tyr/app/i18n/messages.pot b/Tyr/app/i18n/messages.pot index 77edd63b8..ec5c4c744 100644 --- a/Tyr/app/i18n/messages.pot +++ b/Tyr/app/i18n/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-02-02 22:18+0300\n" +"POT-Creation-Date: 2022-06-06 01:10+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -229,56 +229,40 @@ msgstr "" msgid "Yakuman" msgstr "" -#: app/components/screens/settings/SettingsScreen.tsx:65:58 +#: app/components/screens/settings/SettingsScreen.tsx:56:58 msgid "Player name" msgstr "" -#: app/components/screens/settings/SettingsScreenView.tsx:54:57 +#: app/components/screens/settings/SettingsScreenView.tsx:48:57 msgid "Language" msgstr "" -#: app/components/screens/settings/SettingsScreenView.tsx:66:57 +#: app/components/screens/settings/SettingsScreenView.tsx:60:57 msgid "Theme" msgstr "" -#: app/components/screens/settings/SettingsScreenView.tsx:87:54 +#: app/components/screens/settings/SettingsScreenView.tsx:81:54 msgid "Select another event" msgstr "" -#: app/components/screens/settings/SettingsScreenView.tsx:95:51 -msgid "Enable experimental updates" -msgstr "" - -#: app/components/screens/settings/SettingsScreenView.tsx:104:52 -msgid "Disable experimental updates" -msgstr "" - -#: app/components/screens/settings/SettingsScreenView.tsx:113:73 -msgid "Enable Pantheon notifications" -msgstr "" - -#: app/components/screens/settings/SettingsScreenView.tsx:120:16 -msgid "Pantheon notifications are enabled" -msgstr "" - -#: app/components/screens/settings/SettingsScreenView.tsx:127:14 -msgid "Pantheon notifications are disabled. See your browser settings." -msgstr "" - -#: app/components/screens/settings/SettingsScreenView.tsx:142:32 +#: app/components/screens/settings/SettingsScreenView.tsx:95:32 msgid "Log out" msgstr "" -#: app/components/screens/table/base/TableInfo.tsx:74:18 +#: app/components/screens/table/base/TableInfo.tsx:76:18 msgid "%1 deal left" msgid_plural "%1 deals left" msgstr[0] "" msgstr[1] "" -#: app/components/screens/table/base/TableInfo.tsx:83:14 +#: app/components/screens/table/base/TableInfo.tsx:85:14 msgid "Table #%1" msgstr "" +#: app/components/screens/table/base/TableInfo.tsx:87:143 +msgid "Time before game start:" +msgstr "" + #: app/components/screens/table/TableHelper.ts:107:13 #: app/components/screens/table/TableHelper.ts:423:15 #: app/store/selectors/commonSelectors.ts:9:24 diff --git a/Tyr/app/i18n/ru.json b/Tyr/app/i18n/ru.json index d32cec551..5a1af6f6a 100644 --- a/Tyr/app/i18n/ru.json +++ b/Tyr/app/i18n/ru.json @@ -1 +1 @@ -{"meta":{"projectIdVersion":"PROJECT VERSION","reportMsgidBugsTo":"","potCreationDate":"2022-02-02 22:18+0300","poRevisionDate":"2022-02-02 22:21+0300","languageTeam":"","language":"ru_RU","mimeVersion":"1.0","contentType":"text/plain; charset=UTF-8","contentTransferEncoding":"8bit","generatedBy":"i18n-json2po","pluralForms":"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"},"items":[{"type":"plural","entry":["%1 deal left","%1 deals left"],"translations":["Осталась %1 раздача","Осталось %1 раздачи","Осталось %1 раздач"]},{"type":"single","entry":"%1 han","translation":"%1 хан"},{"type":"single","entry":"%1 han %2 fu","translation":"%1 хан, %2 фу"},{"type":"single","entry":"%1 ← %2","translation":"%1 ← %2"},{"type":"single","entry":"Abort","translation":"Абортивная ничья"},{"type":"single","entry":"Abortive draw","translation":"Абортивная ничья"},{"type":"single","entry":"Chankan","translation":"Чанкан"},{"type":"single","entry":"Chanta","translation":"Чанта"},{"type":"single","entry":"Chiihou","translation":"Чихо"},{"type":"single","entry":"Chiitoitsu","translation":"Чиитойцу"},{"type":"single","entry":"Chinitsu","translation":"Чиницу"},{"type":"single","entry":"Chinroutou","translation":"Чинрото"},{"type":"single","entry":"Chombo","translation":"Чомбо"},{"type":"single","entry":"Chuuren poutou","translation":"Чууренпото"},{"type":"single","entry":"Current game","translation":"Текущая игра"},{"type":"single","entry":"Daburu","translation":"Дабури"},{"type":"single","entry":"Daburu riichi","translation":"Дабл-риичи"},{"type":"single","entry":"Daisangen","translation":"Дайсанген"},{"type":"single","entry":"Daisuushii","translation":"Дайсуши"},{"type":"single","entry":"Disable experimental updates","translation":"Выключить экспериментальные обновления"},{"type":"single","entry":"Dora","translation":"Дора"},{"type":"single","entry":"Draw","translation":"Ничья"},{"type":"single","entry":"E-mail is not registered in Pantheon database (%1)","translation":"E-mail не зарегистрирован в базе данных Pantheon (%1)"},{"type":"single","entry":"Email","translation":"Email"},{"type":"single","entry":"Enable Pantheon notifications","translation":"Включить уведомления от Pantheon"},{"type":"single","entry":"Enable experimental updates","translation":"Включить экспериментальные обновления"},{"type":"single","entry":"Event title","translation":"Название события"},{"type":"single","entry":"Exhaustive draw","translation":"Ничья"},{"type":"single","entry":"Fu","translation":"Фу"},{"type":"single","entry":"Game log","translation":"Лог игры"},{"type":"single","entry":"Games overview","translation":"Обзор игр"},{"type":"single","entry":"Haitei","translation":"Хайтей"},{"type":"single","entry":"Han from yaku","translation":"Хан из яку"},{"type":"single","entry":"Honba: %1","translation":"Хонба: %1"},{"type":"single","entry":"Honitsu","translation":"Хоницу"},{"type":"single","entry":"Honroutou","translation":"Хонрото"},{"type":"single","entry":"Houtei","translation":"Хотей"},{"type":"single","entry":"Houtei raoyui","translation":"Хотей раоюй"},{"type":"single","entry":"If in doubt, contact the tournament administrator for further instructions.","translation":"Обратитесь к администратору турнира для дальнейших инструкций."},{"type":"single","entry":"If you want to select a riichi, return back and press riichi button for the winner","translation":"Если вы хотите выбрать яку риичи, вернитесь назад и отметьте ставку риичи для выигравшего"},{"type":"single","entry":"Iipeikou","translation":"Иипейко"},{"type":"single","entry":"Ippatsu","translation":"Иппацу"},{"type":"single","entry":"Ittsu","translation":"Иццу"},{"type":"single","entry":"Junchan","translation":"Джунчан"},{"type":"single","entry":"Kokushi musou","translation":"Кокушимусо"},{"type":"single","entry":"Language","translation":"Язык"},{"type":"single","entry":"Loading...","translation":"Загрузка..."},{"type":"single","entry":"Log in","translation":"Вход"},{"type":"single","entry":"Log out","translation":"Выйти из системы"},{"type":"single","entry":"Login attempt has failed. Possible reasons are:","translation":"Попытка входа в систему не удалась. Возможные причины:"},{"type":"single","entry":"Loser","translation":"Проигравший"},{"type":"single","entry":"Menzentsumo","translation":"Мензенцумо"},{"type":"single","entry":"Multiron, from %1","translation":"Мультирон с %1"},{"type":"single","entry":"Nagashi","translation":"Нагаши манган"},{"type":"single","entry":"Nagashi mangan","translation":"Нагаши манган"},{"type":"single","entry":"New game","translation":"Новая игра"},{"type":"single","entry":"No games found","translation":"Игры не найдены"},{"type":"single","entry":"No results found","translation":"Результаты отсутствуют"},{"type":"single","entry":"OK","translation":"ОК"},{"type":"single","entry":"Open riichi","translation":"Опен риичи"},{"type":"single","entry":"Opened hand","translation":"Открытая рука"},{"type":"single","entry":"Other playing tables","translation":"Другие столы"},{"type":"single","entry":"Pantheon","translation":"Pantheon"},{"type":"single","entry":"Pantheon notifications are disabled. See your browser settings.","translation":"Уведомления отключены. Проверьте настройки браузера."},{"type":"single","entry":"Pantheon notifications are enabled","translation":"Уведомления Pantheon включены"},{"type":"single","entry":"Pantheon: log in","translation":"Pantheon: вход в систему"},{"type":"single","entry":"Pao from: %1","translation":"Пао с: %1"},{"type":"single","entry":"Password","translation":"Пароль"},{"type":"single","entry":"Password check has failed","translation":"Пароль введен некорректно"},{"type":"single","entry":"Penalty","translation":"Штраф"},{"type":"single","entry":"Pinfu","translation":"Пин-фу"},{"type":"single","entry":"Player name","translation":"Имя игрока"},{"type":"single","entry":"Previous game","translation":"Предыдущая раздача"},{"type":"single","entry":"Red fives","translation":"Акадоры"},{"type":"single","entry":"Renhou","translation":"Ренхо"},{"type":"single","entry":"Riichi","translation":"Риичи"},{"type":"single","entry":"Riichi bets: %1","translation":"Ставок риичи: %1"},{"type":"single","entry":"Rinshan","translation":"Риншан"},{"type":"single","entry":"Rinshan kaihou","translation":"Риншан кайхо"},{"type":"single","entry":"Ron","translation":"Рон"},{"type":"single","entry":"Ron, %1","translation":"Рон, %1"},{"type":"single","entry":"Ryanpeikou","translation":"Рянпейко"},{"type":"single","entry":"Ryuuiisou","translation":"Рюисо"},{"type":"single","entry":"Sanankou","translation":"Сананко"},{"type":"single","entry":"Sankantsu","translation":"Санканцу"},{"type":"single","entry":"Sanshoku","translation":"Саншоку"},{"type":"single","entry":"Sanshoku doukou","translation":"Саншоку доко"},{"type":"single","entry":"Select another event","translation":"Выбрать другое событие"},{"type":"single","entry":"Select event","translation":"Выбрать событие"},{"type":"single","entry":"Select pao","translation":"Выбрать пао"},{"type":"single","entry":"Select tempai","translation":"Выбрать темпай"},{"type":"single","entry":"Shousangen","translation":"Шосанген"},{"type":"single","entry":"Shousuushii","translation":"Шосуши"},{"type":"single","entry":"Sign up","translation":"Регистрация"},{"type":"single","entry":"Statistics","translation":"Статистика"},{"type":"single","entry":"Suuankou","translation":"Сууанко"},{"type":"single","entry":"Suukantsu","translation":"Сууканцу"},{"type":"single","entry":"Table #%1","translation":"Стол №%1"},{"type":"single","entry":"Tanyao","translation":"Тан-яо"},{"type":"single","entry":"Tempai: %1","translation":"Темпай: %1"},{"type":"single","entry":"Tenhou","translation":"Тенхо"},{"type":"single","entry":"Theme","translation":"Оформление"},{"type":"single","entry":"There is no events you participate in right now","translation":"На данный момент нет активных событий, в которых вы участвуете"},{"type":"single","entry":"Toitoi","translation":"Тойтой"},{"type":"single","entry":"Tsumo","translation":"Цумо"},{"type":"single","entry":"Tsumo, %1","translation":"Цумо, %1"},{"type":"single","entry":"Tsuuiisou","translation":"Цуисо"},{"type":"single","entry":"Unexpected server error","translation":"Неожиданная ошибка сервера"},{"type":"single","entry":"Ura dora","translation":"Урадора"},{"type":"single","entry":"Winner","translation":"Победитель"},{"type":"single","entry":"Yakuhai","translation":"Якухай"},{"type":"single","entry":"Yakuhai x1","translation":"Якухай 1"},{"type":"single","entry":"Yakuhai x2","translation":"Якухай 2"},{"type":"single","entry":"Yakuhai x3","translation":"Якухай 3"},{"type":"single","entry":"Yakuhai x4","translation":"Якухай 4"},{"type":"single","entry":"Yakuman","translation":"Якуман"},{"type":"single","entry":"dora %1","translation":"дора %1"},{"type":"single","entry":"empty outcome","translation":"пустой исход"},{"type":"single","entry":"name","translation":"имя"},{"type":"single","entry":"register","context":"Name of registration link","translation":"регистрация"},{"type":"single","entry":"select player","translation":"выбрать игрока"},{"type":"single","entry":"something goes wrong, please contact your administrator","translation":"что-то пошло не так, свяжитесь с администратором системы"},{"type":"single","entry":"type to find someone","translation":"начните вводить чтобы найти игроков"},{"type":"single","entry":"wrong outcome for paoSelect","translation":"неверный исход для выбора пао"},{"type":"single","entry":"x2","translation":"х2"},{"type":"single","entry":"x3","translation":"х3"},{"type":"single","entry":"x4","translation":"х4"},{"type":"single","entry":"yakuman","translation":"якуман"}]} \ No newline at end of file +{"meta":{"projectIdVersion":"PROJECT VERSION","reportMsgidBugsTo":"","potCreationDate":"2022-06-06 01:10+0300","poRevisionDate":"2022-06-06 01:11+0300","languageTeam":"","language":"ru_RU","mimeVersion":"1.0","contentType":"text/plain; charset=UTF-8","contentTransferEncoding":"8bit","pluralForms":"nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);","generatedBy":"i18n-json2po"},"items":[{"type":"plural","entry":["%1 deal left","%1 deals left"],"translations":["Осталась %1 раздача","Осталось %1 раздачи","Осталось %1 раздач"]},{"type":"single","entry":"%1 han","translation":"%1 хан"},{"type":"single","entry":"%1 han %2 fu","translation":"%1 хан, %2 фу"},{"type":"single","entry":"%1 ← %2","translation":"%1 ← %2"},{"type":"single","entry":"Abort","translation":"Абортивная ничья"},{"type":"single","entry":"Abortive draw","translation":"Абортивная ничья"},{"type":"single","entry":"Chankan","translation":"Чанкан"},{"type":"single","entry":"Chanta","translation":"Чанта"},{"type":"single","entry":"Chiihou","translation":"Чихо"},{"type":"single","entry":"Chiitoitsu","translation":"Чиитойцу"},{"type":"single","entry":"Chinitsu","translation":"Чиницу"},{"type":"single","entry":"Chinroutou","translation":"Чинрото"},{"type":"single","entry":"Chombo","translation":"Чомбо"},{"type":"single","entry":"Chuuren poutou","translation":"Чууренпото"},{"type":"single","entry":"Current game","translation":"Текущая игра"},{"type":"single","entry":"Daburu","translation":"Дабури"},{"type":"single","entry":"Daburu riichi","translation":"Дабл-риичи"},{"type":"single","entry":"Daisangen","translation":"Дайсанген"},{"type":"single","entry":"Daisuushii","translation":"Дайсуши"},{"type":"single","entry":"Dora","translation":"Дора"},{"type":"single","entry":"Draw","translation":"Ничья"},{"type":"single","entry":"E-mail is not registered in Pantheon database (%1)","translation":"E-mail не зарегистрирован в базе данных Pantheon (%1)"},{"type":"single","entry":"Email","translation":"Email"},{"type":"single","entry":"Event title","translation":"Название события"},{"type":"single","entry":"Exhaustive draw","translation":"Ничья"},{"type":"single","entry":"Fu","translation":"Фу"},{"type":"single","entry":"Game log","translation":"Лог игры"},{"type":"single","entry":"Games overview","translation":"Обзор игр"},{"type":"single","entry":"Haitei","translation":"Хайтей"},{"type":"single","entry":"Han from yaku","translation":"Хан из яку"},{"type":"single","entry":"Honba: %1","translation":"Хонба: %1"},{"type":"single","entry":"Honitsu","translation":"Хоницу"},{"type":"single","entry":"Honroutou","translation":"Хонрото"},{"type":"single","entry":"Houtei","translation":"Хотей"},{"type":"single","entry":"Houtei raoyui","translation":"Хотей раоюй"},{"type":"single","entry":"If in doubt, contact the tournament administrator for further instructions.","translation":"Обратитесь к администратору турнира для дальнейших инструкций."},{"type":"single","entry":"If you want to select a riichi, return back and press riichi button for the winner","translation":"Если вы хотите выбрать яку риичи, вернитесь назад и отметьте ставку риичи для выигравшего"},{"type":"single","entry":"Iipeikou","translation":"Иипейко"},{"type":"single","entry":"Ippatsu","translation":"Иппацу"},{"type":"single","entry":"Ittsu","translation":"Иццу"},{"type":"single","entry":"Junchan","translation":"Джунчан"},{"type":"single","entry":"Kokushi musou","translation":"Кокушимусо"},{"type":"single","entry":"Language","translation":"Язык"},{"type":"single","entry":"Loading...","translation":"Загрузка..."},{"type":"single","entry":"Log in","translation":"Вход"},{"type":"single","entry":"Log out","translation":"Выйти из системы"},{"type":"single","entry":"Login attempt has failed. Possible reasons are:","translation":"Попытка входа в систему не удалась. Возможные причины:"},{"type":"single","entry":"Loser","translation":"Проигравший"},{"type":"single","entry":"Menzentsumo","translation":"Мензенцумо"},{"type":"single","entry":"Multiron, from %1","translation":"Мультирон с %1"},{"type":"single","entry":"Nagashi","translation":"Нагаши манган"},{"type":"single","entry":"Nagashi mangan","translation":"Нагаши манган"},{"type":"single","entry":"New game","translation":"Новая игра"},{"type":"single","entry":"No games found","translation":"Игры не найдены"},{"type":"single","entry":"No results found","translation":"Результаты отсутствуют"},{"type":"single","entry":"OK","translation":"ОК"},{"type":"single","entry":"Open riichi","translation":"Опен риичи"},{"type":"single","entry":"Opened hand","translation":"Открытая рука"},{"type":"single","entry":"Other playing tables","translation":"Другие столы"},{"type":"single","entry":"Pantheon","translation":"Pantheon"},{"type":"single","entry":"Pantheon: log in","translation":"Pantheon: вход в систему"},{"type":"single","entry":"Pao from: %1","translation":"Пао с: %1"},{"type":"single","entry":"Password","translation":"Пароль"},{"type":"single","entry":"Password check has failed","translation":"Пароль введен некорректно"},{"type":"single","entry":"Penalty","translation":"Штраф"},{"type":"single","entry":"Pinfu","translation":"Пин-фу"},{"type":"single","entry":"Player name","translation":"Имя игрока"},{"type":"single","entry":"Previous game","translation":"Предыдущая раздача"},{"type":"single","entry":"Red fives","translation":"Акадоры"},{"type":"single","entry":"Renhou","translation":"Ренхо"},{"type":"single","entry":"Riichi","translation":"Риичи"},{"type":"single","entry":"Riichi bets: %1","translation":"Ставок риичи: %1"},{"type":"single","entry":"Rinshan","translation":"Риншан"},{"type":"single","entry":"Rinshan kaihou","translation":"Риншан кайхо"},{"type":"single","entry":"Ron","translation":"Рон"},{"type":"single","entry":"Ron, %1","translation":"Рон, %1"},{"type":"single","entry":"Ryanpeikou","translation":"Рянпейко"},{"type":"single","entry":"Ryuuiisou","translation":"Рюисо"},{"type":"single","entry":"Sanankou","translation":"Сананко"},{"type":"single","entry":"Sankantsu","translation":"Санканцу"},{"type":"single","entry":"Sanshoku","translation":"Саншоку"},{"type":"single","entry":"Sanshoku doukou","translation":"Саншоку доко"},{"type":"single","entry":"Select another event","translation":"Выбрать другое событие"},{"type":"single","entry":"Select event","translation":"Выбрать событие"},{"type":"single","entry":"Select pao","translation":"Выбрать пао"},{"type":"single","entry":"Select tempai","translation":"Выбрать темпай"},{"type":"single","entry":"Shousangen","translation":"Шосанген"},{"type":"single","entry":"Shousuushii","translation":"Шосуши"},{"type":"single","entry":"Sign up","translation":"Регистрация"},{"type":"single","entry":"Statistics","translation":"Статистика"},{"type":"single","entry":"Suuankou","translation":"Сууанко"},{"type":"single","entry":"Suukantsu","translation":"Сууканцу"},{"type":"single","entry":"Table #%1","translation":"Стол №%1"},{"type":"single","entry":"Tanyao","translation":"Тан-яо"},{"type":"single","entry":"Tempai: %1","translation":"Темпай: %1"},{"type":"single","entry":"Tenhou","translation":"Тенхо"},{"type":"single","entry":"Theme","translation":"Оформление"},{"type":"single","entry":"There is no events you participate in right now","translation":"На данный момент нет активных событий, в которых вы участвуете"},{"type":"single","entry":"Time before game start:","translation":"До начала игры:"},{"type":"single","entry":"Toitoi","translation":"Тойтой"},{"type":"single","entry":"Tsumo","translation":"Цумо"},{"type":"single","entry":"Tsumo, %1","translation":"Цумо, %1"},{"type":"single","entry":"Tsuuiisou","translation":"Цуисо"},{"type":"single","entry":"Unexpected server error","translation":"Неожиданная ошибка сервера"},{"type":"single","entry":"Ura dora","translation":"Урадора"},{"type":"single","entry":"Winner","translation":"Победитель"},{"type":"single","entry":"Yakuhai","translation":"Якухай"},{"type":"single","entry":"Yakuhai x1","translation":"Якухай 1"},{"type":"single","entry":"Yakuhai x2","translation":"Якухай 2"},{"type":"single","entry":"Yakuhai x3","translation":"Якухай 3"},{"type":"single","entry":"Yakuhai x4","translation":"Якухай 4"},{"type":"single","entry":"Yakuman","translation":"Якуман"},{"type":"single","entry":"dora %1","translation":"дора %1"},{"type":"single","entry":"empty outcome","translation":"пустой исход"},{"type":"single","entry":"name","translation":"имя"},{"type":"single","entry":"register","context":"Name of registration link","translation":"регистрация"},{"type":"single","entry":"select player","translation":"выбрать игрока"},{"type":"single","entry":"something goes wrong, please contact your administrator","translation":"что-то пошло не так, свяжитесь с администратором системы"},{"type":"single","entry":"type to find someone","translation":"начните вводить чтобы найти игроков"},{"type":"single","entry":"wrong outcome for paoSelect","translation":"неверный исход для выбора пао"},{"type":"single","entry":"x2","translation":"х2"},{"type":"single","entry":"x3","translation":"х3"},{"type":"single","entry":"x4","translation":"х4"},{"type":"single","entry":"yakuman","translation":"якуман"}]} \ No newline at end of file diff --git a/Tyr/app/i18n/ru.po b/Tyr/app/i18n/ru.po index ee9fb2d61..68048fe60 100644 --- a/Tyr/app/i18n/ru.po +++ b/Tyr/app/i18n/ru.po @@ -7,20 +7,20 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-02-02 22:18+0300\n" -"PO-Revision-Date: 2022-02-02 22:21+0300\n" +"POT-Creation-Date: 2022-06-06 01:10+0300\n" +"PO-Revision-Date: 2022-06-06 01:11+0300\n" "Last-Translator: \n" "Language-Team: \n" "Language: ru_RU\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: i18n-json2po\n" -"X-Generator: Poedit 3.0\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Generated-By: i18n-json2po\n" +"X-Generator: Poedit 3.0.1\n" -#: app/components/screens/table/base/TableInfo.tsx:74:18 +#: app/components/screens/table/base/TableInfo.tsx:76:18 msgid "%1 deal left" msgid_plural "%1 deals left" msgstr[0] "Осталась %1 раздача" @@ -106,10 +106,6 @@ msgstr "Дайсанген" msgid "Daisuushii" msgstr "Дайсуши" -#: app/components/screens/settings/SettingsScreenView.tsx:104:52 -msgid "Disable experimental updates" -msgstr "Выключить экспериментальные обновления" - #: app/components/screens/select-hand/view/SelectTotalPanel.tsx:73:16 msgid "Dora" msgstr "Дора" @@ -126,14 +122,6 @@ msgstr "E-mail не зарегистрирован в базе данных Pant msgid "Email" msgstr "Email" -#: app/components/screens/settings/SettingsScreenView.tsx:113:73 -msgid "Enable Pantheon notifications" -msgstr "Включить уведомления от Pantheon" - -#: app/components/screens/settings/SettingsScreenView.tsx:95:51 -msgid "Enable experimental updates" -msgstr "Включить экспериментальные обновления" - #: app/components/screens/home/HomeScreen.tsx:68:55 msgid "Event title" msgstr "Название события" @@ -217,7 +205,7 @@ msgstr "Джунчан" msgid "Kokushi musou" msgstr "Кокушимусо" -#: app/components/screens/settings/SettingsScreenView.tsx:54:57 +#: app/components/screens/settings/SettingsScreenView.tsx:48:57 msgid "Language" msgstr "Язык" @@ -229,7 +217,7 @@ msgstr "Загрузка..." msgid "Log in" msgstr "Вход" -#: app/components/screens/settings/SettingsScreenView.tsx:142:32 +#: app/components/screens/settings/SettingsScreenView.tsx:95:32 msgid "Log out" msgstr "Выйти из системы" @@ -291,14 +279,6 @@ msgstr "Другие столы" msgid "Pantheon" msgstr "Pantheon" -#: app/components/screens/settings/SettingsScreenView.tsx:127:14 -msgid "Pantheon notifications are disabled. See your browser settings." -msgstr "Уведомления отключены. Проверьте настройки браузера." - -#: app/components/screens/settings/SettingsScreenView.tsx:120:16 -msgid "Pantheon notifications are enabled" -msgstr "Уведомления Pantheon включены" - #: app/components/screens/login/EnterCredentialsView.tsx:18:10 msgid "Pantheon: log in" msgstr "Pantheon: вход в систему" @@ -323,7 +303,7 @@ msgstr "Штраф" msgid "Pinfu" msgstr "Пин-фу" -#: app/components/screens/settings/SettingsScreen.tsx:65:58 +#: app/components/screens/settings/SettingsScreen.tsx:56:58 msgid "Player name" msgstr "Имя игрока" @@ -389,7 +369,7 @@ msgstr "Саншоку" msgid "Sanshoku doukou" msgstr "Саншоку доко" -#: app/components/screens/settings/SettingsScreenView.tsx:87:54 +#: app/components/screens/settings/SettingsScreenView.tsx:81:54 msgid "Select another event" msgstr "Выбрать другое событие" @@ -429,7 +409,7 @@ msgstr "Сууанко" msgid "Suukantsu" msgstr "Сууканцу" -#: app/components/screens/table/base/TableInfo.tsx:83:14 +#: app/components/screens/table/base/TableInfo.tsx:85:14 msgid "Table #%1" msgstr "Стол №%1" @@ -445,7 +425,7 @@ msgstr "Темпай: %1" msgid "Tenhou" msgstr "Тенхо" -#: app/components/screens/settings/SettingsScreenView.tsx:66:57 +#: app/components/screens/settings/SettingsScreenView.tsx:60:57 msgid "Theme" msgstr "Оформление" @@ -453,6 +433,10 @@ msgstr "Оформление" msgid "There is no events you participate in right now" msgstr "На данный момент нет активных событий, в которых вы участвуете" +#: app/components/screens/table/base/TableInfo.tsx:87:143 +msgid "Time before game start:" +msgstr "До начала игры:" + #: app/primitives/yaku.ts:131:34 app/primitives/yaku.ts:132:39 msgid "Toitoi" msgstr "Тойтой" @@ -556,3 +540,18 @@ msgstr "х4" #: app/components/screens/log/view/RoundSelectors.ts:13:12 msgid "yakuman" msgstr "якуман" + +#~ msgid "Disable experimental updates" +#~ msgstr "Выключить экспериментальные обновления" + +#~ msgid "Enable Pantheon notifications" +#~ msgstr "Включить уведомления от Pantheon" + +#~ msgid "Enable experimental updates" +#~ msgstr "Включить экспериментальные обновления" + +#~ msgid "Pantheon notifications are disabled. See your browser settings." +#~ msgstr "Уведомления отключены. Проверьте настройки браузера." + +#~ msgid "Pantheon notifications are enabled" +#~ msgstr "Уведомления Pantheon включены" diff --git a/Tyr/app/interfaces/local.ts b/Tyr/app/interfaces/local.ts index 52b929a29..e58045a3f 100644 --- a/Tyr/app/interfaces/local.ts +++ b/Tyr/app/interfaces/local.ts @@ -50,6 +50,8 @@ export interface LTimerState { finished: boolean; timeRemaining: number; waitingForTimer: boolean; + haveAutostart: boolean; + autostartTimer: number; } export interface LWinItem { diff --git a/Tyr/app/interfaces/remote.ts b/Tyr/app/interfaces/remote.ts index f040f9b38..f39974d26 100644 --- a/Tyr/app/interfaces/remote.ts +++ b/Tyr/app/interfaces/remote.ts @@ -138,6 +138,8 @@ export interface RTimerState { finished: boolean; time_remaining: string; waiting_for_timer: boolean; + have_autostart: boolean; + autostart_timer: string; } export interface RSessionOverview { diff --git a/Tyr/app/services/formatters.ts b/Tyr/app/services/formatters.ts index 0b0466b0b..ea6881adf 100644 --- a/Tyr/app/services/formatters.ts +++ b/Tyr/app/services/formatters.ts @@ -69,7 +69,9 @@ export function timerFormatter(timer: RTimerState): LTimerState { started: !!timer.started, finished: !!timer.finished, timeRemaining: timer.time_remaining ? parseInt(timer.time_remaining.toString(), 10) : 0, - waitingForTimer: !!timer.waiting_for_timer + waitingForTimer: !!timer.waiting_for_timer, + haveAutostart: !!timer.have_autostart, + autostartTimer: timer.autostart_timer ? parseInt(timer.autostart_timer.toString(), 10) : 0, }; } diff --git a/Tyr/app/store/actions/interfaces.ts b/Tyr/app/store/actions/interfaces.ts index bc51e0183..5da5f9253 100644 --- a/Tyr/app/store/actions/interfaces.ts +++ b/Tyr/app/store/actions/interfaces.ts @@ -373,14 +373,18 @@ interface SetTimerAction { payload: { waiting: boolean; secondsRemaining: number; + autostartSecondsRemaining: number; + haveAutostart: boolean; } } interface UpdateTimerDataAction { type: typeof UPDATE_TIMER_DATA; payload: { waiting: boolean; - secondsRemaining: number; + secondsRemaining?: number; lastUpdateTimestamp?: number; + autostartSecondsRemaining?: number; + autostartLastUpdateTimestamp?: number; } } diff --git a/Tyr/app/store/index.ts b/Tyr/app/store/index.ts index a4d1dc17a..31960707f 100644 --- a/Tyr/app/store/index.ts +++ b/Tyr/app/store/index.ts @@ -29,6 +29,7 @@ export class Store { constructor(i18n: I18nService) { this.timerSt = { timer: undefined, + autostartTimer: undefined, setInterval: (callback: () => any, milliseconds: number) => window.setInterval(callback, milliseconds), clearInterval: (handle: number) => window.clearInterval(handle) }; diff --git a/Tyr/app/store/interfaces.ts b/Tyr/app/store/interfaces.ts index b3eb46dc8..8a938708b 100644 --- a/Tyr/app/store/interfaces.ts +++ b/Tyr/app/store/interfaces.ts @@ -29,6 +29,9 @@ export type TimerData = { lastUpdateSecondsRemaining: number; lastUpdateTimestamp: number; waiting: boolean; + autostartSecondsRemaining: number; + autostartLastUpdateSecondsRemaining: number; + autostartLastUpdateTimestamp: number; }; export type ErrorState = { @@ -120,6 +123,7 @@ export interface IAppState { export type TimerStorage = { timer?: number; + autostartTimer?: number; setInterval: (callback: () => any, milliseconds: number) => number; clearInterval: (handle: number) => void; } diff --git a/Tyr/app/store/middlewares/apiClient.ts b/Tyr/app/store/middlewares/apiClient.ts index fee04866d..1f57cecec 100644 --- a/Tyr/app/store/middlewares/apiClient.ts +++ b/Tyr/app/store/middlewares/apiClient.ts @@ -214,7 +214,9 @@ function updateCurrentGames(api: RiichiApiService, dispatchNext: Dispatch, dispa } dispatchToStore({ type: SET_TIMER, payload: { waiting: timerState.waitingForTimer, - secondsRemaining: timerState.timeRemaining + secondsRemaining: timerState.timeRemaining, + autostartSecondsRemaining: timerState.autostartTimer, + haveAutostart: timerState.haveAutostart, }}); }).catch((e) => { if (e.code === 401) { // token has rotten diff --git a/Tyr/app/store/middlewares/timer.ts b/Tyr/app/store/middlewares/timer.ts index 9d3552ecf..006640f31 100644 --- a/Tyr/app/store/middlewares/timer.ts +++ b/Tyr/app/store/middlewares/timer.ts @@ -12,11 +12,49 @@ export const timerMw = (timerStorage: TimerStorage) => (mw: MiddlewareAPI { + const timerNotRequired = !mw.getState().gameConfig?.useTimer; + if (timerNotRequired) { + if (timerStorage.autostartTimer) { + timerStorage.clearInterval(timerStorage.autostartTimer); + } + return; + } + // Calc delta to support mobile suspending with js timers stopping + let delta = (now() - (mw.getState().timer?.autostartLastUpdateTimestamp || 0)); + let timeRemaining = (mw.getState().timer?.autostartLastUpdateSecondsRemaining || 0) - delta; + if (timeRemaining <= 0) { + // timer is finished + next({ type: UPDATE_TIMER_DATA, payload: { + ...action.payload, + autostartLastUpdateTimestamp: now(), + autostartSecondsRemaining: 0 + }}); + if (timerStorage.autostartTimer) { + timerStorage.clearInterval(timerStorage.autostartTimer); + } + timerStorage.autostartTimer = undefined; + } else { + next({ type: UPDATE_TIMER_DATA, payload: { + ...action.payload, + autostartLastUpdateTimestamp: now(), + autostartSecondsRemaining: timeRemaining + }}); + } + }, 1000); + next({ type: UPDATE_TIMER_DATA, payload: { ...action.payload, - lastUpdateTimestamp: undefined + autostartLastUpdateTimestamp: now() }}); } else { + if (timerStorage.autostartTimer) { + timerStorage.clearInterval(timerStorage.autostartTimer); + } if (timerStorage.timer) { timerStorage.clearInterval(timerStorage.timer); } diff --git a/Tyr/app/store/reducers/mimirReducer.ts b/Tyr/app/store/reducers/mimirReducer.ts index 4b27bc74f..7a20f15ec 100644 --- a/Tyr/app/store/reducers/mimirReducer.ts +++ b/Tyr/app/store/reducers/mimirReducer.ts @@ -176,7 +176,10 @@ export function mimirReducer( secondsRemaining: action.payload.timerState.timeRemaining || 0, lastUpdateSecondsRemaining: action.payload.timerState.timeRemaining || 0, lastUpdateTimestamp: Math.round((new Date()).getTime() / 1000), - waiting: action.payload.timerState.waitingForTimer + waiting: action.payload.timerState.waitingForTimer, + autostartSecondsRemaining: action.payload.timerState.autostartTimer || 0, + autostartLastUpdateSecondsRemaining: action.payload.timerState.autostartTimer || 0, + autostartLastUpdateTimestamp: Math.round((new Date()).getTime() / 1000), }, loading: { ...state.loading, diff --git a/Tyr/app/store/reducers/timerReducer.ts b/Tyr/app/store/reducers/timerReducer.ts index 92677bba2..d7e135ffd 100644 --- a/Tyr/app/store/reducers/timerReducer.ts +++ b/Tyr/app/store/reducers/timerReducer.ts @@ -12,10 +12,15 @@ export function timerReducer( timer: { ...state.timer, waiting: action.payload.waiting, - secondsRemaining: action.payload.secondsRemaining, + secondsRemaining: action.payload.secondsRemaining || 0, + autostartSecondsRemaining: action.payload.autostartSecondsRemaining || 0, + autostartLastUpdateSecondsRemaining: action.payload.autostartLastUpdateTimestamp + ? action.payload.autostartSecondsRemaining || 0 + : state.timer?.autostartLastUpdateSecondsRemaining || 0, + autostartLastUpdateTimestamp: action.payload.autostartLastUpdateTimestamp || state.timer?.autostartLastUpdateTimestamp || 0, lastUpdateTimestamp: action.payload.lastUpdateTimestamp || state.timer?.lastUpdateTimestamp || 0, lastUpdateSecondsRemaining: action.payload.lastUpdateTimestamp - ? action.payload.secondsRemaining + ? action.payload.secondsRemaining || 0 : state.timer?.lastUpdateSecondsRemaining || 0 } }; diff --git a/Tyr/app/store/selectors/overviewSelectors.ts b/Tyr/app/store/selectors/overviewSelectors.ts index dc179d4e0..82052f116 100644 --- a/Tyr/app/store/selectors/overviewSelectors.ts +++ b/Tyr/app/store/selectors/overviewSelectors.ts @@ -25,7 +25,27 @@ function _getTimeRemaining(state: IAppState): {minutes: number, seconds: number} } } +function _getAutostartTimeRemaining(state: IAppState): {minutes: number, seconds: number} | undefined { + if (!state.gameConfig?.useTimer || !state.timer?.waiting) { + return undefined; + } + + let min = Math.floor((state.timer?.autostartSecondsRemaining || 0) / 60); + + if (min < 0) { + return { + minutes: 0, + seconds: 0, + } + } + return { + minutes: min, + seconds: (state.timer?.autostartSecondsRemaining || 0) % 60 + } +} + export const getTimeRemaining = memoize(_getTimeRemaining); +export const getAutostartTimeRemaining = memoize(_getAutostartTimeRemaining); export function formatTime(minutes: number, seconds: number) { return minutes.toString() + ':' + (