diff --git a/.editorconfig b/.editorconfig index 76332f27..93ec9757 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,10 +4,11 @@ root = true [*] charset = utf-8 end_of_line = lf -indent_size = 2 -indent_style = space +indent_size = 4 +indent_style = tab insert_final_newline = true trim_trailing_whitespace = true -[*.{php,md,ps1,Dockerfile}] -indent_size = 4 +[*.{json,yaml,yml}] +indent_style = space +indent_size = 2 diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index 3a8c2fa4..00000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,27 +0,0 @@ -root: true -parser: '@typescript-eslint/parser' -parserOptions: - project: tsconfig.eslint.json -plugins: - - '@typescript-eslint/eslint-plugin' -extends: - - eslint:recommended - - plugin:@typescript-eslint/recommended - - plugin:@typescript-eslint/recommended-requiring-type-checking - - plugin:react-hooks/recommended -rules: - prefer-const: error - '@typescript-eslint/no-unsafe-assignment': off - '@typescript-eslint/no-unsafe-member-access': off - '@typescript-eslint/no-unsafe-return': off - '@typescript-eslint/no-unused-vars': off - '@typescript-eslint/explicit-module-boundary-types': off - '@typescript-eslint/no-explicit-any': off - '@typescript-eslint/ban-ts-comment': off - '@typescript-eslint/no-non-null-assertion': off - '@typescript-eslint/no-floating-promises': off - '@typescript-eslint/no-misused-promises': - - off - - checksVoidReturn: false - '@typescript-eslint/unbound-method': off - '@typescript-eslint/restrict-template-expressions': off diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index f3a67960..00000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -yarn pretty-quick --staged diff --git a/app/Http/View/Composers/UserMenuComposer.php b/app/Http/View/Composers/UserMenuComposer.php index cffabb37..d83120b2 100644 --- a/app/Http/View/Composers/UserMenuComposer.php +++ b/app/Http/View/Composers/UserMenuComposer.php @@ -41,7 +41,6 @@ class UserMenuComposer ['label' => trans('general.admin-panel'), 'link' => route('admin.view')], ['label' => trans('general.user-manage'), 'link' => route('admin.users.view')], ['label' => trans('general.report-manage'), 'link' => route('admin.reports.view')], - ['label' => 'Web CLI', 'link' => '#launch-cli'], ); } $menuItems = $this->filter->apply('user_menu', $menuItems, [$user]); diff --git a/composer.lock b/composer.lock index 910f1d78..5aad3ba4 100644 --- a/composer.lock +++ b/composer.lock @@ -31,7 +31,9 @@ "type": "library", "extra": { "laravel": { - "providers": ["Blessing\\FilterServiceProvider"] + "providers": [ + "Blessing\\FilterServiceProvider" + ] } }, "autoload": { @@ -40,7 +42,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Pig Fang", @@ -48,7 +52,11 @@ } ], "description": "Filters API for designing plugin system.", - "keywords": ["filters", "laravel", "wordpress"], + "keywords": [ + "filters", + "laravel", + "wordpress" + ], "support": { "issues": "https://github.com/bs-community/filter/issues", "source": "https://github.com/bs-community/filter/tree/v1.3.0" @@ -82,7 +90,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Pig Fang", @@ -121,7 +131,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Pig Fang", @@ -164,7 +176,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "description": "Arbitrary-precision arithmetic library", "keywords": [ "Arbitrary-precision", @@ -220,7 +234,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "KyleKatarn", @@ -228,7 +244,13 @@ } ], "description": "Types to use Carbon in Doctrine", - "keywords": ["carbon", "date", "datetime", "doctrine", "time"], + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], "support": { "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.1.0" @@ -251,16 +273,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "b66d11b7479109ab547f9405b97205640b17d385" + "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/b66d11b7479109ab547f9405b97205640b17d385", - "reference": "b66d11b7479109ab547f9405b97205640b17d385", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", + "reference": "3ce240142f6d59b808dd65c1f52f7a1c252e6cfd", "shasum": "" }, "require": { @@ -286,7 +308,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Jordi Boggiano", @@ -295,11 +319,17 @@ } ], "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", - "keywords": ["cabundle", "cacert", "certificate", "ssl", "tls"], + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.4.0" + "source": "https://github.com/composer/ca-bundle/tree/1.4.1" }, "funding": [ { @@ -315,7 +345,7 @@ "type": "tidelift" } ], - "time": "2023-12-18T12:05:55+00:00" + "time": "2024-02-23T10:16:52+00:00" }, { "name": "composer/semver", @@ -350,7 +380,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nils Adermann", @@ -369,7 +401,12 @@ } ], "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": ["semantic", "semver", "validation", "versioning"], + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", @@ -414,7 +451,9 @@ "phpunit/phpunit": "^5|^6|^7|^8|^9|^10", "yoast/phpunit-polyfills": "^2.0.0" }, - "bin": ["bin/generate-defuse-key"], + "bin": [ + "bin/generate-defuse-key" + ], "type": "library", "autoload": { "psr-4": { @@ -422,7 +461,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Taylor Hornby", @@ -490,7 +531,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Dragonfly Development Inc.", @@ -515,7 +558,12 @@ ], "description": "Given a deep data structure, access data by dot notation.", "homepage": "https://github.com/dflydev/dflydev-dot-access-data", - "keywords": ["access", "data", "dot", "notation"], + "keywords": [ + "access", + "data", + "dot", + "notation" + ], "support": { "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2" @@ -557,7 +605,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Guilherme Blanco", @@ -615,16 +665,16 @@ }, { "name": "doctrine/dbal", - "version": "3.7.2", + "version": "3.8.2", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "0ac3c270590e54910715e9a1a044cc368df282b2" + "reference": "a19a1d05ca211f41089dffcc387733a6875196cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/0ac3c270590e54910715e9a1a044cc368df282b2", - "reference": "0ac3c270590e54910715e9a1a044cc368df282b2", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/a19a1d05ca211f41089dffcc387733a6875196cb", + "reference": "a19a1d05ca211f41089dffcc387733a6875196cb", "shasum": "" }, "require": { @@ -640,20 +690,22 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.10.42", + "phpstan/phpstan": "1.10.57", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.13", + "phpunit/phpunit": "9.6.16", "psalm/plugin-phpunit": "0.18.4", "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.7.2", - "symfony/cache": "^5.4|^6.0", - "symfony/console": "^4.4|^5.4|^6.0", + "squizlabs/php_codesniffer": "3.8.1", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/console": "^4.4|^5.4|^6.0|^7.0", "vimeo/psalm": "4.30.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." }, - "bin": ["bin/doctrine-dbal"], + "bin": [ + "bin/doctrine-dbal" + ], "type": "library", "autoload": { "psr-4": { @@ -661,7 +713,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Guilherme Blanco", @@ -704,7 +758,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.7.2" + "source": "https://github.com/doctrine/dbal/tree/3.8.2" }, "funding": [ { @@ -720,20 +774,20 @@ "type": "tidelift" } ], - "time": "2023-11-19T08:06:58+00:00" + "time": "2024-02-12T18:36:36+00:00" }, { "name": "doctrine/deprecations", - "version": "1.1.2", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931" + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931", - "reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", "shasum": "" }, "require": { @@ -758,14 +812,16 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.2" + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" }, - "time": "2023-09-27T20:04:15+00:00" + "time": "2024-01-30T19:34:25+00:00" }, { "name": "doctrine/event-manager", @@ -800,7 +856,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Guilherme Blanco", @@ -858,16 +916,16 @@ }, { "name": "doctrine/inflector", - "version": "2.0.8", + "version": "2.0.10", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff" + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/f9301a5b2fb1216b2b08f02ba04dc45423db6bff", - "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", "shasum": "" }, "require": { @@ -888,7 +946,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Guilherme Blanco", @@ -927,7 +987,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.8" + "source": "https://github.com/doctrine/inflector/tree/2.0.10" }, "funding": [ { @@ -943,31 +1003,31 @@ "type": "tidelift" } ], - "time": "2023-06-16T13:40:37+00:00" + "time": "2024-02-18T20:23:39+00:00" }, { "name": "doctrine/lexer", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "84a527db05647743d50373e0ec53a152f2cde568" + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/84a527db05647743d50373e0ec53a152f2cde568", - "reference": "84a527db05647743d50373e0ec53a152f2cde568", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", "shasum": "" }, "require": { "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^9.5", + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^5.0" + "vimeo/psalm": "^5.21" }, "type": "library", "autoload": { @@ -976,7 +1036,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Guilherme Blanco", @@ -993,10 +1055,16 @@ ], "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", "homepage": "https://www.doctrine-project.org/projects/lexer.html", - "keywords": ["annotations", "docblock", "lexer", "parser", "php"], + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/3.0.0" + "source": "https://github.com/doctrine/lexer/tree/3.0.1" }, "funding": [ { @@ -1012,7 +1080,7 @@ "type": "tidelift" } ], - "time": "2022-12-15T16:57:16+00:00" + "time": "2024-02-05T11:56:58+00:00" }, { "name": "dragonmantank/cron-expression", @@ -1048,7 +1116,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Chris Tankersley", @@ -1057,7 +1127,10 @@ } ], "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", - "keywords": ["cron", "schedule"], + "keywords": [ + "cron", + "schedule" + ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" @@ -1108,7 +1181,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Eduardo Gulias Davis" @@ -1174,7 +1249,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Filipe Dobreira", @@ -1240,7 +1317,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Neuman Vong", @@ -1255,7 +1334,10 @@ ], "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", "homepage": "https://github.com/firebase/php-jwt", - "keywords": ["jwt", "php"], + "keywords": [ + "jwt", + "php" + ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", "source": "https://github.com/firebase/php-jwt/tree/v6.10.0" @@ -1297,7 +1379,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fruitcake", @@ -1310,7 +1394,11 @@ ], "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", "homepage": "https://github.com/fruitcake/php-cors", - "keywords": ["cors", "laravel", "symfony"], + "keywords": [ + "cors", + "laravel", + "symfony" + ], "support": { "issues": "https://github.com/fruitcake/php-cors/issues", "source": "https://github.com/fruitcake/php-cors/tree/v1.3.0" @@ -1355,7 +1443,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Graham Campbell", @@ -1417,7 +1507,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Grégoire Passault", @@ -1431,7 +1523,11 @@ ], "description": "Captcha generator", "homepage": "https://github.com/Gregwar/Captcha", - "keywords": ["bot", "captcha", "spam"], + "keywords": [ + "bot", + "captcha", + "spam" + ], "support": { "issues": "https://github.com/Gregwar/Captcha/issues", "source": "https://github.com/Gregwar/Captcha/tree/v1.2.1" @@ -1484,13 +1580,17 @@ } }, "autoload": { - "files": ["src/functions_include.php"], + "files": [ + "src/functions_include.php" + ], "psr-4": { "GuzzleHttp\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Graham Campbell", @@ -1594,7 +1694,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Graham Campbell", @@ -1618,7 +1720,9 @@ } ], "description": "Guzzle promises library", - "keywords": ["promise"], + "keywords": [ + "promise" + ], "support": { "issues": "https://github.com/guzzle/promises/issues", "source": "https://github.com/guzzle/promises/tree/2.0.2" @@ -1684,7 +1788,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Graham Campbell", @@ -1789,7 +1895,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Graham Campbell", @@ -1813,7 +1921,10 @@ } ], "description": "A polyfill class for uri_template of PHP", - "keywords": ["guzzlehttp", "uri-template"], + "keywords": [ + "guzzlehttp", + "uri-template" + ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", "source": "https://github.com/guzzle/uri-template/tree/v1.0.3" @@ -1874,7 +1985,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Ivan Enderlin", @@ -1952,13 +2065,17 @@ } }, "autoload": { - "files": ["Prelude.php"], + "files": [ + "Prelude.php" + ], "psr-4": { "Hoa\\Consistency\\": "." } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Ivan Enderlin", @@ -2024,7 +2141,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Ivan Enderlin", @@ -2037,7 +2156,12 @@ ], "description": "The Hoa\\Event library.", "homepage": "https://hoa-project.net/", - "keywords": ["event", "library", "listener", "observer"], + "keywords": [ + "event", + "library", + "listener", + "observer" + ], "support": { "docs": "https://central.hoa-project.net/Documentation/Library/Event", "email": "support@hoa-project.net", @@ -2082,7 +2206,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Ivan Enderlin", @@ -2095,7 +2221,10 @@ ], "description": "The Hoa\\Exception library.", "homepage": "https://hoa-project.net/", - "keywords": ["exception", "library"], + "keywords": [ + "exception", + "library" + ], "support": { "docs": "https://central.hoa-project.net/Documentation/Library/Exception", "email": "support@hoa-project.net", @@ -2143,7 +2272,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Ivan Enderlin", @@ -2209,7 +2340,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Ivan Enderlin", @@ -2222,7 +2355,10 @@ ], "description": "The Hoa\\Iterator library.", "homepage": "https://hoa-project.net/", - "keywords": ["iterator", "library"], + "keywords": [ + "iterator", + "library" + ], "support": { "docs": "https://central.hoa-project.net/Documentation/Library/Iterator", "email": "support@hoa-project.net", @@ -2271,7 +2407,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Ivan Enderlin", @@ -2340,7 +2478,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Ivan Enderlin", @@ -2353,7 +2493,11 @@ ], "description": "The Hoa\\Regex library.", "homepage": "https://hoa-project.net/", - "keywords": ["compiler", "library", "regex"], + "keywords": [ + "compiler", + "library", + "regex" + ], "support": { "docs": "https://central.hoa-project.net/Documentation/Library/Regex", "email": "support@hoa-project.net", @@ -2400,7 +2544,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Ivan Enderlin", @@ -2473,7 +2619,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Ivan Enderlin", @@ -2486,7 +2634,12 @@ ], "description": "The Hoa\\Ustring library.", "homepage": "https://hoa-project.net/", - "keywords": ["library", "search", "string", "unicode"], + "keywords": [ + "library", + "search", + "string", + "unicode" + ], "support": { "docs": "https://central.hoa-project.net/Documentation/Library/Ustring", "email": "support@hoa-project.net", @@ -2530,7 +2683,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Ivan Enderlin", @@ -2543,7 +2698,12 @@ ], "description": "The Hoa\\Visitor library.", "homepage": "https://hoa-project.net/", - "keywords": ["library", "structure", "visit", "visitor"], + "keywords": [ + "library", + "structure", + "visit", + "visitor" + ], "support": { "docs": "https://central.hoa-project.net/Documentation/Library/Visitor", "email": "support@hoa-project.net", @@ -2585,7 +2745,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Ivan Enderlin", @@ -2598,7 +2760,11 @@ ], "description": "The Hoa\\Zformat library.", "homepage": "https://hoa-project.net/", - "keywords": ["library", "parameter", "zformat"], + "keywords": [ + "library", + "parameter", + "zformat" + ], "support": { "docs": "https://central.hoa-project.net/Documentation/Library/Zformat", "email": "support@hoa-project.net", @@ -2644,7 +2810,9 @@ "dev-master": "2.4-dev" }, "laravel": { - "providers": ["Intervention\\Image\\ImageServiceProvider"], + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], "aliases": { "Image": "Intervention\\Image\\Facades\\Image" } @@ -2656,7 +2824,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Oliver Vogel", @@ -2692,20 +2862,20 @@ }, { "name": "laravel/framework", - "version": "v10.40.0", + "version": "v10.45.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "7a9470071dac9579ebf29ad1b9d73e4b8eb586fc" + "reference": "dcf5d1d722b84ad38a5e053289130b6962f830bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/7a9470071dac9579ebf29ad1b9d73e4b8eb586fc", - "reference": "7a9470071dac9579ebf29ad1b9d73e4b8eb586fc", + "url": "https://api.github.com/repos/laravel/framework/zipball/dcf5d1d722b84ad38a5e053289130b6962f830bd", + "reference": "dcf5d1d722b84ad38a5e053289130b6962f830bd", "shasum": "" }, "require": { - "brick/math": "^0.9.3|^0.10.2|^0.11", + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.3.2", @@ -2749,6 +2919,7 @@ "conflict": { "carbonphp/carbon-doctrine-types": ">=3.0", "doctrine/dbal": ">=4.0", + "phpunit/phpunit": ">=11.0.0", "tightenco/collect": "<5.5.33" }, "provide": { @@ -2874,7 +3045,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Taylor Otwell", @@ -2883,12 +3056,15 @@ ], "description": "The Laravel Framework.", "homepage": "https://laravel.com", - "keywords": ["framework", "laravel"], + "keywords": [ + "framework", + "laravel" + ], "support": { "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-01-09T11:46:47+00:00" + "time": "2024-02-21T14:07:36+00:00" }, { "name": "laravel/passport", @@ -2935,7 +3111,9 @@ "dev-master": "11.x-dev" }, "laravel": { - "providers": ["Laravel\\Passport\\PassportServiceProvider"] + "providers": [ + "Laravel\\Passport\\PassportServiceProvider" + ] } }, "autoload": { @@ -2945,7 +3123,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Taylor Otwell", @@ -2953,7 +3133,11 @@ } ], "description": "Laravel Passport provides OAuth2 server support to Laravel.", - "keywords": ["laravel", "oauth", "passport"], + "keywords": [ + "laravel", + "oauth", + "passport" + ], "support": { "issues": "https://github.com/laravel/passport/issues", "source": "https://github.com/laravel/passport" @@ -3000,13 +3184,17 @@ } }, "autoload": { - "files": ["src/helpers.php"], + "files": [ + "src/helpers.php" + ], "psr-4": { "Laravel\\Prompts\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "support": { "issues": "https://github.com/laravel/prompts/issues", "source": "https://github.com/laravel/prompts/tree/v0.1.15" @@ -3048,7 +3236,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Taylor Otwell", @@ -3060,7 +3250,11 @@ } ], "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", - "keywords": ["closure", "laravel", "serializable"], + "keywords": [ + "closure", + "laravel", + "serializable" + ], "support": { "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" @@ -3102,7 +3296,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Luís Cobucci", @@ -3171,7 +3367,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Luís Cobucci", @@ -3180,7 +3378,10 @@ } ], "description": "A simple library to work with JSON Web Token and JSON Web Signature", - "keywords": ["JWS", "jwt"], + "keywords": [ + "JWS", + "jwt" + ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", "source": "https://github.com/lcobucci/jwt/tree/4.0.4" @@ -3199,16 +3400,16 @@ }, { "name": "league/commonmark", - "version": "2.4.1", + "version": "2.4.2", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5" + "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/3669d6d5f7a47a93c08ddff335e6d945481a1dd5", - "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/91c24291965bd6d7c46c46a12ba7492f83b1cadf", + "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf", "shasum": "" }, "require": { @@ -3221,7 +3422,7 @@ }, "require-dev": { "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.0", + "commonmark/cmark": "0.30.3", "commonmark/commonmark.js": "0.30.0", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", @@ -3231,10 +3432,10 @@ "michelf/php-markdown": "^1.4 || ^2.0", "nyholm/psr7": "^1.5", "phpstan/phpstan": "^1.8.2", - "phpunit/phpunit": "^9.5.21", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "symfony/finder": "^5.3 | ^6.0 || ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 || ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0" }, @@ -3253,7 +3454,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Colin O'Dell", @@ -3299,7 +3502,7 @@ "type": "tidelift" } ], - "time": "2023-08-30T16:55:00+00:00" + "time": "2024-02-02T11:59:32+00:00" }, { "name": "league/config", @@ -3339,7 +3542,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Colin O'Dell", @@ -3414,7 +3619,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Frank de Jonge", @@ -3422,7 +3629,11 @@ } ], "description": "Event package", - "keywords": ["emitter", "event", "listener"], + "keywords": [ + "emitter", + "event", + "listener" + ], "support": { "issues": "https://github.com/thephpleague/event/issues", "source": "https://github.com/thephpleague/event/tree/master" @@ -3431,16 +3642,16 @@ }, { "name": "league/flysystem", - "version": "3.23.0", + "version": "3.24.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "d4ad81e2b67396e33dc9d7e54ec74ccf73151dcc" + "reference": "b25a361508c407563b34fac6f64a8a17a8819675" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/d4ad81e2b67396e33dc9d7e54ec74ccf73151dcc", - "reference": "d4ad81e2b67396e33dc9d7e54ec74ccf73151dcc", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/b25a361508c407563b34fac6f64a8a17a8819675", + "reference": "b25a361508c407563b34fac6f64a8a17a8819675", "shasum": "" }, "require": { @@ -3460,7 +3671,7 @@ "require-dev": { "async-aws/s3": "^1.5 || ^2.0", "async-aws/simple-s3": "^1.1 || ^2.0", - "aws/aws-sdk-php": "^3.220.0", + "aws/aws-sdk-php": "^3.295.10", "composer/semver": "^3.0", "ext-fileinfo": "*", "ext-ftp": "*", @@ -3471,7 +3682,7 @@ "phpseclib/phpseclib": "^3.0.34", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5.11|^10.0", - "sabre/dav": "^4.3.1" + "sabre/dav": "^4.6.0" }, "type": "library", "autoload": { @@ -3480,7 +3691,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Frank de Jonge", @@ -3503,7 +3716,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.23.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.24.0" }, "funding": [ { @@ -3515,20 +3728,20 @@ "type": "github" } ], - "time": "2023-12-04T10:16:17+00:00" + "time": "2024-02-04T12:10:17+00:00" }, { "name": "league/flysystem-local", - "version": "3.23.0", + "version": "3.23.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "5cf046ba5f059460e86a997c504dd781a39a109b" + "reference": "b884d2bf9b53bb4804a56d2df4902bb51e253f00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/5cf046ba5f059460e86a997c504dd781a39a109b", - "reference": "5cf046ba5f059460e86a997c504dd781a39a109b", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/b884d2bf9b53bb4804a56d2df4902bb51e253f00", + "reference": "b884d2bf9b53bb4804a56d2df4902bb51e253f00", "shasum": "" }, "require": { @@ -3544,7 +3757,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Frank de Jonge", @@ -3552,10 +3767,16 @@ } ], "description": "Local filesystem adapter for Flysystem.", - "keywords": ["Flysystem", "file", "files", "filesystem", "local"], + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], "support": { "issues": "https://github.com/thephpleague/flysystem-local/issues", - "source": "https://github.com/thephpleague/flysystem-local/tree/3.23.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.23.1" }, "funding": [ { @@ -3567,20 +3788,20 @@ "type": "github" } ], - "time": "2023-12-04T10:14:46+00:00" + "time": "2024-01-26T18:25:23+00:00" }, { "name": "league/mime-type-detection", - "version": "1.14.0", + "version": "1.15.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "b6a5854368533df0295c5761a0253656a2e52d9e" + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/b6a5854368533df0295c5761a0253656a2e52d9e", - "reference": "b6a5854368533df0295c5761a0253656a2e52d9e", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", "shasum": "" }, "require": { @@ -3599,7 +3820,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Frank de Jonge", @@ -3609,7 +3832,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.14.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" }, "funding": [ { @@ -3621,7 +3844,7 @@ "type": "tidelift" } ], - "time": "2023-10-17T14:13:20+00:00" + "time": "2024-01-28T23:22:08+00:00" }, { "name": "league/oauth2-server", @@ -3665,7 +3888,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Alex Bilbie", @@ -3762,7 +3987,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Ignace Nyamagana Butera", @@ -3848,7 +4075,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Ignace Nyamagana Butera", @@ -3858,7 +4087,12 @@ ], "description": "Common interface for URI representation", "homepage": "http://github.com/thephpleague/uri-interfaces", - "keywords": ["rfc3986", "rfc3987", "uri", "url"], + "keywords": [ + "rfc3986", + "rfc3987", + "uri", + "url" + ], "support": { "issues": "https://github.com/thephpleague/uri-interfaces/issues", "source": "https://github.com/thephpleague/uri-interfaces/tree/2.3.0" @@ -3898,7 +4132,9 @@ "type": "library", "extra": { "laravel": { - "providers": ["Lorisleiva\\LaravelSearchString\\ServiceProvider"] + "providers": [ + "Lorisleiva\\LaravelSearchString\\ServiceProvider" + ] } }, "autoload": { @@ -3907,7 +4143,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Loris Leiva", @@ -3995,7 +4233,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Jordi Boggiano", @@ -4005,7 +4245,11 @@ ], "description": "Sends your logs to files, sockets, inboxes, databases and various web services", "homepage": "https://github.com/Seldaek/monolog", - "keywords": ["log", "logging", "psr-3"], + "keywords": [ + "log", + "logging", + "psr-3" + ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", "source": "https://github.com/Seldaek/monolog/tree/3.5.0" @@ -4024,16 +4268,16 @@ }, { "name": "nesbot/carbon", - "version": "2.72.1", + "version": "2.72.3", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78" + "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/2b3b3db0a2d0556a177392ff1a3bf5608fa09f78", - "reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/0c6fd108360c562f6e4fd1dedb8233b423e91c83", + "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83", "shasum": "" }, "require": { @@ -4061,7 +4305,9 @@ "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", "squizlabs/php_codesniffer": "^3.4" }, - "bin": ["bin/carbon"], + "bin": [ + "bin/carbon" + ], "type": "library", "extra": { "branch-alias": { @@ -4069,10 +4315,14 @@ "dev-master": "2.x-dev" }, "laravel": { - "providers": ["Carbon\\Laravel\\ServiceProvider"] + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] }, "phpstan": { - "includes": ["extension.neon"] + "includes": [ + "extension.neon" + ] } }, "autoload": { @@ -4081,7 +4331,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Brian Nesbitt", @@ -4095,7 +4347,11 @@ ], "description": "An API extension for DateTime that supports 281 different languages.", "homepage": "https://carbon.nesbot.com", - "keywords": ["date", "datetime", "time"], + "keywords": [ + "date", + "datetime", + "time" + ], "support": { "docs": "https://carbon.nesbot.com/docs", "issues": "https://github.com/briannesbitt/Carbon/issues", @@ -4115,42 +4371,48 @@ "type": "tidelift" } ], - "time": "2023-12-08T23:47:49+00:00" + "time": "2024-01-25T10:35:09+00:00" }, { "name": "nette/schema", - "version": "v1.2.5", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a" + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a", - "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a", + "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", "shasum": "" }, "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": "7.1 - 8.3" + "nette/utils": "^4.0", + "php": "8.1 - 8.3" }, "require-dev": { - "nette/tester": "^2.3 || ^2.4", + "nette/tester": "^2.4", "phpstan/phpstan-nette": "^1.0", - "tracy/tracy": "^2.7" + "tracy/tracy": "^2.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], "authors": [ { "name": "David Grudl", @@ -4163,25 +4425,28 @@ ], "description": "📐 Nette Schema: validating data structures against a given Schema.", "homepage": "https://nette.org", - "keywords": ["config", "nette"], + "keywords": [ + "config", + "nette" + ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.5" + "source": "https://github.com/nette/schema/tree/v1.3.0" }, - "time": "2023-10-05T20:37:59+00:00" + "time": "2023-12-11T11:54:22+00:00" }, { "name": "nette/utils", - "version": "v4.0.3", + "version": "v4.0.4", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015" + "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/a9d127dd6a203ce6d255b2e2db49759f7506e015", - "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015", + "url": "https://api.github.com/repos/nette/utils/zipball/d3ad0aa3b9f934602cb3e3902ebccf10be34d218", + "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218", "shasum": "" }, "require": { @@ -4212,10 +4477,16 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], "authors": [ { "name": "David Grudl", @@ -4246,9 +4517,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.3" + "source": "https://github.com/nette/utils/tree/v4.0.4" }, - "time": "2023-10-29T21:02:13+00:00" + "time": "2024-01-17T16:50:36+00:00" }, { "name": "nunomaduro/collision", @@ -4296,13 +4567,17 @@ } }, "autoload": { - "files": ["./src/Adapters/Phpunit/Autoload.php"], + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], "psr-4": { "NunoMaduro\\Collision\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nuno Maduro", @@ -4376,17 +4651,23 @@ "type": "library", "extra": { "laravel": { - "providers": ["Termwind\\Laravel\\TermwindServiceProvider"] + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] } }, "autoload": { - "files": ["src/Functions.php"], + "files": [ + "src/Functions.php" + ], "psr-4": { "Termwind\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nuno Maduro", @@ -4394,7 +4675,14 @@ } ], "description": "Its like Tailwind CSS, but for the console.", - "keywords": ["cli", "console", "css", "package", "php", "style"], + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", "source": "https://github.com/nunomaduro/termwind/tree/v1.15.1" @@ -4458,7 +4746,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Tobias Nyholm", @@ -4471,7 +4761,10 @@ ], "description": "A fast PHP7 implementation of PSR-7", "homepage": "https://tnyholm.se", - "keywords": ["psr-17", "psr-7"], + "keywords": [ + "psr-17", + "psr-7" + ], "support": { "issues": "https://github.com/Nyholm/psr7/issues", "source": "https://github.com/Nyholm/psr7/tree/1.8.1" @@ -4516,7 +4809,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Paragon Initiative Enterprises", @@ -4579,7 +4874,9 @@ }, "type": "library", "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Paragon Initiative Enterprises", @@ -4588,7 +4885,12 @@ } ], "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": ["csprng", "polyfill", "pseudorandom", "random"], + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], "support": { "email": "info@paragonie.com", "issues": "https://github.com/paragonie/random_compat/issues", @@ -4633,7 +4935,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["Apache-2.0"], + "license": [ + "Apache-2.0" + ], "authors": [ { "name": "Johannes M. Schmitt", @@ -4647,7 +4951,12 @@ } ], "description": "Option Type for PHP", - "keywords": ["language", "option", "php", "type"], + "keywords": [ + "language", + "option", + "php", + "type" + ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", "source": "https://github.com/schmittjoh/php-option/tree/1.9.2" @@ -4695,13 +5004,17 @@ }, "type": "library", "autoload": { - "files": ["phpseclib/bootstrap.php"], + "files": [ + "phpseclib/bootstrap.php" + ], "psr-4": { "phpseclib3\\": "phpseclib/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Jim Wigginton", @@ -4799,7 +5112,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "PHP-FIG", @@ -4807,7 +5122,11 @@ } ], "description": "Common interface for caching libraries", - "keywords": ["cache", "psr", "psr-6"], + "keywords": [ + "cache", + "psr", + "psr-6" + ], "support": { "source": "https://github.com/php-fig/cache/tree/3.0.0" }, @@ -4837,7 +5156,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "PHP-FIG", @@ -4846,7 +5167,13 @@ ], "description": "Common interface for reading the clock.", "homepage": "https://github.com/php-fig/clock", - "keywords": ["clock", "now", "psr", "psr-20", "time"], + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], "support": { "issues": "https://github.com/php-fig/clock/issues", "source": "https://github.com/php-fig/clock/tree/1.0.0" @@ -4882,7 +5209,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "PHP-FIG", @@ -4933,7 +5262,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "PHP-FIG", @@ -4941,7 +5272,11 @@ } ], "description": "Standard interfaces for event handling.", - "keywords": ["events", "psr", "psr-14"], + "keywords": [ + "events", + "psr", + "psr-14" + ], "support": { "issues": "https://github.com/php-fig/event-dispatcher/issues", "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" @@ -4978,7 +5313,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "PHP-FIG", @@ -4987,7 +5324,12 @@ ], "description": "Common interface for HTTP clients", "homepage": "https://github.com/php-fig/http-client", - "keywords": ["http", "http-client", "psr", "psr-18"], + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], "support": { "source": "https://github.com/php-fig/http-client" }, @@ -5023,7 +5365,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "PHP-FIG", @@ -5075,7 +5419,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "PHP-FIG", @@ -5126,7 +5472,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "PHP-FIG", @@ -5135,7 +5483,11 @@ ], "description": "Common interface for logging libraries", "homepage": "https://github.com/php-fig/log", - "keywords": ["log", "psr", "psr-3"], + "keywords": [ + "log", + "psr", + "psr-3" + ], "support": { "source": "https://github.com/php-fig/log/tree/3.0.0" }, @@ -5170,7 +5522,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "PHP-FIG", @@ -5178,7 +5532,13 @@ } ], "description": "Common interfaces for simple caching", - "keywords": ["cache", "caching", "psr", "psr-16", "simple-cache"], + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], "support": { "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, @@ -5207,10 +5567,14 @@ }, "type": "library", "autoload": { - "files": ["src/getallheaders.php"] + "files": [ + "src/getallheaders.php" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Ralph Khattar", @@ -5278,7 +5642,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Ben Ramsey", @@ -5287,7 +5653,14 @@ } ], "description": "A PHP library for representing and manipulating collections.", - "keywords": ["array", "collection", "hash", "map", "queue", "set"], + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], "support": { "issues": "https://github.com/ramsey/collection/issues", "source": "https://github.com/ramsey/collection/tree/2.0.0" @@ -5363,15 +5736,23 @@ } }, "autoload": { - "files": ["src/functions.php"], + "files": [ + "src/functions.php" + ], "psr-4": { "Ramsey\\Uuid\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", - "keywords": ["guid", "identifier", "uuid"], + "keywords": [ + "guid", + "identifier", + "uuid" + ], "support": { "issues": "https://github.com/ramsey/uuid/issues", "source": "https://github.com/ramsey/uuid/tree/4.7.5" @@ -5425,7 +5806,9 @@ "dev-master": "0.14-dev" }, "laravel": { - "providers": ["TwigBridge\\ServiceProvider"], + "providers": [ + "TwigBridge\\ServiceProvider" + ], "aliases": { "Twig": "TwigBridge\\Facade\\Twig" } @@ -5438,7 +5821,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Rob Crowe", @@ -5450,7 +5835,10 @@ } ], "description": "Adds the power of Twig to Laravel", - "keywords": ["laravel", "twig"], + "keywords": [ + "laravel", + "twig" + ], "support": { "issues": "https://github.com/rcrowe/TwigBridge/issues", "source": "https://github.com/rcrowe/TwigBridge/tree/v0.14.1" @@ -5491,14 +5879,18 @@ } }, "autoload": { - "files": ["Source/Wrapper.php"], + "files": [ + "Source/Wrapper.php" + ], "psr-4": { "Hoa\\Protocol\\": "Source", "Hoa\\Protocol\\Bin\\": "Bin" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Ivan Enderlin" @@ -5508,7 +5900,13 @@ } ], "description": "The Hoa\\Protocol library.", - "keywords": ["library", "protocol", "resource", "stream", "wrapper"], + "keywords": [ + "library", + "protocol", + "resource", + "stream", + "wrapper" + ], "support": { "issues": "https://github.com/sanmai/Protocol/issues", "source": "https://github.com/sanmai/Protocol/tree/1.21" @@ -5545,7 +5943,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Freek Van de Herten", @@ -5556,7 +5956,10 @@ ], "description": "A better backtrace", "homepage": "https://github.com/spatie/backtrace", - "keywords": ["Backtrace", "spatie"], + "keywords": [ + "Backtrace", + "spatie" + ], "support": { "source": "https://github.com/spatie/backtrace/tree/1.5.3" }, @@ -5574,21 +5977,20 @@ }, { "name": "spatie/flare-client-php", - "version": "1.4.3", + "version": "1.4.4", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec" + "reference": "17082e780752d346c2db12ef5d6bee8e835e399c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec", - "reference": "5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/17082e780752d346c2db12ef5d6bee8e835e399c", + "reference": "17082e780752d346c2db12ef5d6bee8e835e399c", "shasum": "" }, "require": { "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0", - "nesbot/carbon": "^2.62.1", "php": "^8.0", "spatie/backtrace": "^1.5.2", "symfony/http-foundation": "^5.2|^6.0|^7.0", @@ -5611,19 +6013,28 @@ } }, "autoload": { - "files": ["src/helpers.php"], + "files": [ + "src/helpers.php" + ], "psr-4": { "Spatie\\FlareClient\\": "src" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "description": "Send PHP errors to Flare", "homepage": "https://github.com/spatie/flare-client-php", - "keywords": ["exception", "flare", "reporting", "spatie"], + "keywords": [ + "exception", + "flare", + "reporting", + "spatie" + ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.4.3" + "source": "https://github.com/spatie/flare-client-php/tree/1.4.4" }, "funding": [ { @@ -5631,7 +6042,7 @@ "type": "github" } ], - "time": "2023-10-17T15:54:07+00:00" + "time": "2024-01-31T14:18:45+00:00" }, { "name": "spatie/ignition", @@ -5684,7 +6095,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Spatie", @@ -5694,7 +6107,12 @@ ], "description": "A beautiful error page for PHP applications.", "homepage": "https://flareapp.io/ignition", - "keywords": ["error", "flare", "laravel", "page"], + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], "support": { "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", "forum": "https://twitter.com/flareappio", @@ -5711,16 +6129,16 @@ }, { "name": "spatie/laravel-ignition", - "version": "2.4.0", + "version": "2.4.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "b9395ba48d3f30d42092cf6ceff75ed7256cd604" + "reference": "351504f4570e32908839fc5a2dc53bf77d02f85e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/b9395ba48d3f30d42092cf6ceff75ed7256cd604", - "reference": "b9395ba48d3f30d42092cf6ceff75ed7256cd604", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/351504f4570e32908839fc5a2dc53bf77d02f85e", + "reference": "351504f4570e32908839fc5a2dc53bf77d02f85e", "shasum": "" }, "require": { @@ -5752,20 +6170,26 @@ "type": "library", "extra": { "laravel": { - "providers": ["Spatie\\LaravelIgnition\\IgnitionServiceProvider"], + "providers": [ + "Spatie\\LaravelIgnition\\IgnitionServiceProvider" + ], "aliases": { "Flare": "Spatie\\LaravelIgnition\\Facades\\Flare" } } }, "autoload": { - "files": ["src/helpers.php"], + "files": [ + "src/helpers.php" + ], "psr-4": { "Spatie\\LaravelIgnition\\": "src" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Spatie", @@ -5775,7 +6199,12 @@ ], "description": "A beautiful error page for Laravel applications.", "homepage": "https://flareapp.io/ignition", - "keywords": ["error", "flare", "laravel", "page"], + "keywords": [ + "error", + "flare", + "laravel", + "page" + ], "support": { "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction", "forum": "https://twitter.com/flareappio", @@ -5788,7 +6217,7 @@ "type": "github" } ], - "time": "2024-01-04T14:51:24+00:00" + "time": "2024-02-09T16:08:40+00:00" }, { "name": "spatie/laravel-translation-loader", @@ -5815,7 +6244,9 @@ "type": "library", "extra": { "laravel": { - "providers": ["Spatie\\TranslationLoader\\TranslationServiceProvider"] + "providers": [ + "Spatie\\TranslationLoader\\TranslationServiceProvider" + ] } }, "autoload": { @@ -5824,7 +6255,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Freek Van der Herten", @@ -5881,7 +6314,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Andreas Heigl", @@ -5890,7 +6325,12 @@ ], "description": "A pre-release of the proposed PSR-20 Clock-Interface", "homepage": "https://gitlab.com/stella-maris/clock", - "keywords": ["clock", "datetime", "point in time", "psr20"], + "keywords": [ + "clock", + "datetime", + "point in time", + "psr20" + ], "support": { "source": "https://github.com/stella-maris-solutions/clock/tree/0.1.7" }, @@ -5898,16 +6338,16 @@ }, { "name": "symfony/console", - "version": "v6.4.2", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0254811a143e6bc6c8deea08b589a7e68a37f625" + "reference": "2aaf83b4de5b9d43b93e4aec6f2f8b676f7c567e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0254811a143e6bc6c8deea08b589a7e68a37f625", - "reference": "0254811a143e6bc6c8deea08b589a7e68a37f625", + "url": "https://api.github.com/repos/symfony/console/zipball/2aaf83b4de5b9d43b93e4aec6f2f8b676f7c567e", + "reference": "2aaf83b4de5b9d43b93e4aec6f2f8b676f7c567e", "shasum": "" }, "require": { @@ -5945,10 +6385,14 @@ "psr-4": { "Symfony\\Component\\Console\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -5961,9 +6405,14 @@ ], "description": "Eases the creation of beautiful and testable command line interfaces", "homepage": "https://symfony.com", - "keywords": ["cli", "command-line", "console", "terminal"], + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.2" + "source": "https://github.com/symfony/console/tree/v6.4.3" }, "funding": [ { @@ -5979,20 +6428,20 @@ "type": "tidelift" } ], - "time": "2023-12-10T16:15:48+00:00" + "time": "2024-01-23T14:51:35+00:00" }, { "name": "symfony/css-selector", - "version": "v6.4.0", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4" + "reference": "ee0f7ed5cf298cc019431bb3b3977ebc52b86229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/d036c6c0d0b09e24a14a35f8292146a658f986e4", - "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ee0f7ed5cf298cc019431bb3b3977ebc52b86229", + "reference": "ee0f7ed5cf298cc019431bb3b3977ebc52b86229", "shasum": "" }, "require": { @@ -6003,10 +6452,14 @@ "psr-4": { "Symfony\\Component\\CssSelector\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -6024,7 +6477,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.4.0" + "source": "https://github.com/symfony/css-selector/tree/v6.4.3" }, "funding": [ { @@ -6040,7 +6493,7 @@ "type": "tidelift" } ], - "time": "2023-10-31T08:40:20+00:00" + "time": "2024-01-23T14:51:35+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6070,10 +6523,14 @@ } }, "autoload": { - "files": ["function.php"] + "files": [ + "function.php" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -6107,16 +6564,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.4.0", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "c873490a1c97b3a0a4838afc36ff36c112d02788" + "reference": "6dc3c76a278b77f01d864a6005d640822c6f26a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/c873490a1c97b3a0a4838afc36ff36c112d02788", - "reference": "c873490a1c97b3a0a4838afc36ff36c112d02788", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/6dc3c76a278b77f01d864a6005d640822c6f26a6", + "reference": "6dc3c76a278b77f01d864a6005d640822c6f26a6", "shasum": "" }, "require": { @@ -6133,16 +6590,22 @@ "symfony/http-kernel": "^6.4|^7.0", "symfony/serializer": "^5.4|^6.0|^7.0" }, - "bin": ["Resources/bin/patch-type-declarations"], + "bin": [ + "Resources/bin/patch-type-declarations" + ], "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\ErrorHandler\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -6156,7 +6619,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.0" + "source": "https://github.com/symfony/error-handler/tree/v6.4.3" }, "funding": [ { @@ -6172,20 +6635,20 @@ "type": "tidelift" } ], - "time": "2023-10-18T09:43:34+00:00" + "time": "2024-01-29T15:40:36+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.0.2", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "098b62ae81fdd6cbf941f355059f617db28f4f9a" + "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/098b62ae81fdd6cbf941f355059f617db28f4f9a", - "reference": "098b62ae81fdd6cbf941f355059f617db28f4f9a", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/834c28d533dd0636f910909d01b9ff45cc094b5e", + "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e", "shasum": "" }, "require": { @@ -6215,10 +6678,14 @@ "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -6232,7 +6699,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.0.2" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.0.3" }, "funding": [ { @@ -6248,7 +6715,7 @@ "type": "tidelift" } ], - "time": "2023-12-27T22:24:19+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -6284,7 +6751,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -6349,10 +6818,14 @@ "psr-4": { "Symfony\\Component\\Finder\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -6386,16 +6859,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.4.2", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "172d807f9ef3fc3fbed8377cc57c20d389269271" + "reference": "5677bdf7cade4619cb17fc9e1e7b31ec392244a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/172d807f9ef3fc3fbed8377cc57c20d389269271", - "reference": "172d807f9ef3fc3fbed8377cc57c20d389269271", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5677bdf7cade4619cb17fc9e1e7b31ec392244a9", + "reference": "5677bdf7cade4619cb17fc9e1e7b31ec392244a9", "shasum": "" }, "require": { @@ -6422,10 +6895,14 @@ "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -6439,7 +6916,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.2" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.3" }, "funding": [ { @@ -6455,20 +6932,20 @@ "type": "tidelift" } ], - "time": "2023-12-27T22:16:42+00:00" + "time": "2024-01-23T14:51:35+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.2", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "13e8387320b5942d0dc408440c888e2d526efef4" + "reference": "9c6ec4e543044f7568a53a76ab1484ecd30637a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/13e8387320b5942d0dc408440c888e2d526efef4", - "reference": "13e8387320b5942d0dc408440c888e2d526efef4", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/9c6ec4e543044f7568a53a76ab1484ecd30637a2", + "reference": "9c6ec4e543044f7568a53a76ab1484ecd30637a2", "shasum": "" }, "require": { @@ -6531,10 +7008,14 @@ "psr-4": { "Symfony\\Component\\HttpKernel\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -6548,7 +7029,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.2" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.3" }, "funding": [ { @@ -6564,20 +7045,20 @@ "type": "tidelift" } ], - "time": "2023-12-30T15:31:44+00:00" + "time": "2024-01-31T07:21:29+00:00" }, { "name": "symfony/mailer", - "version": "v6.4.2", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "6da89e5c9202f129717a770a03183fb140720168" + "reference": "74412c62f88a85a41b61f0b71ab0afcaad6f03ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/6da89e5c9202f129717a770a03183fb140720168", - "reference": "6da89e5c9202f129717a770a03183fb140720168", + "url": "https://api.github.com/repos/symfony/mailer/zipball/74412c62f88a85a41b61f0b71ab0afcaad6f03ee", + "reference": "74412c62f88a85a41b61f0b71ab0afcaad6f03ee", "shasum": "" }, "require": { @@ -6607,10 +7088,14 @@ "psr-4": { "Symfony\\Component\\Mailer\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -6624,7 +7109,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.4.2" + "source": "https://github.com/symfony/mailer/tree/v6.4.3" }, "funding": [ { @@ -6640,20 +7125,20 @@ "type": "tidelift" } ], - "time": "2023-12-19T09:12:31+00:00" + "time": "2024-01-29T15:01:07+00:00" }, { "name": "symfony/mime", - "version": "v6.4.0", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "ca4f58b2ef4baa8f6cecbeca2573f88cd577d205" + "reference": "5017e0a9398c77090b7694be46f20eb796262a34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/ca4f58b2ef4baa8f6cecbeca2573f88cd577d205", - "reference": "ca4f58b2ef4baa8f6cecbeca2573f88cd577d205", + "url": "https://api.github.com/repos/symfony/mime/zipball/5017e0a9398c77090b7694be46f20eb796262a34", + "reference": "5017e0a9398c77090b7694be46f20eb796262a34", "shasum": "" }, "require": { @@ -6683,10 +7168,14 @@ "psr-4": { "Symfony\\Component\\Mime\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -6699,9 +7188,12 @@ ], "description": "Allows manipulating MIME messages", "homepage": "https://symfony.com", - "keywords": ["mime", "mime-type"], + "keywords": [ + "mime", + "mime-type" + ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.0" + "source": "https://github.com/symfony/mime/tree/v6.4.3" }, "funding": [ { @@ -6717,20 +7209,20 @@ "type": "tidelift" } ], - "time": "2023-10-17T11:49:05+00:00" + "time": "2024-01-30T08:32:12+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", "shasum": "" }, "require": { @@ -6744,22 +7236,23 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Gert de Pagter", @@ -6772,9 +7265,14 @@ ], "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", - "keywords": ["compatibility", "ctype", "polyfill", "portable"], + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" }, "funding": [ { @@ -6790,20 +7288,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "875e90aeea2777b6f135677f618529449334a612" + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", - "reference": "875e90aeea2777b6f135677f618529449334a612", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", "shasum": "" }, "require": { @@ -6814,22 +7312,23 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Intl\\Grapheme\\": "" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -6851,7 +7350,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" }, "funding": [ { @@ -6867,20 +7366,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "ecaafce9f77234a6a449d29e49267ba10499116d" + "reference": "a287ed7475f85bf6f61890146edbc932c0fff919" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d", - "reference": "ecaafce9f77234a6a449d29e49267ba10499116d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a287ed7475f85bf6f61890146edbc932c0fff919", + "reference": "a287ed7475f85bf6f61890146edbc932c0fff919", "shasum": "" }, "require": { @@ -6893,22 +7392,23 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Intl\\Idn\\": "" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Laurent Bassin", @@ -6934,7 +7434,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.29.0" }, "funding": [ { @@ -6950,20 +7450,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:30:37+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", "shasum": "" }, "require": { @@ -6974,23 +7474,26 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, - "classmap": ["Resources/stubs"] + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -7012,7 +7515,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" }, "funding": [ { @@ -7028,20 +7531,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "shasum": "" }, "require": { @@ -7055,22 +7558,23 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -7083,9 +7587,15 @@ ], "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", - "keywords": ["compatibility", "mbstring", "polyfill", "portable", "shim"], + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" }, "funding": [ { @@ -7101,20 +7611,20 @@ "type": "tidelift" } ], - "time": "2023-07-28T09:04:16+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179" + "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179", - "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/861391a8da9a04cbad2d232ddd9e4893220d6e25", + "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25", "shasum": "" }, "require": { @@ -7122,22 +7632,23 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Php72\\": "" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -7150,9 +7661,14 @@ ], "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", "homepage": "https://symfony.com", - "keywords": ["compatibility", "polyfill", "portable", "shim"], + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.29.0" }, "funding": [ { @@ -7168,20 +7684,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { @@ -7189,23 +7705,26 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, - "classmap": ["Resources/stubs"] + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Ion Bazan", @@ -7222,9 +7741,14 @@ ], "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", - "keywords": ["compatibility", "polyfill", "portable", "shim"], + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -7240,20 +7764,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11" + "reference": "86fcae159633351e5fd145d1c47de6c528f8caff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", - "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/86fcae159633351e5fd145d1c47de6c528f8caff", + "reference": "86fcae159633351e5fd145d1c47de6c528f8caff", "shasum": "" }, "require": { @@ -7262,23 +7786,26 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Php83\\": "" }, - "classmap": ["Resources/stubs"] + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -7291,9 +7818,14 @@ ], "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com", - "keywords": ["compatibility", "polyfill", "portable", "shim"], + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.29.0" }, "funding": [ { @@ -7309,20 +7841,20 @@ "type": "tidelift" } ], - "time": "2023-08-16T06:22:46+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e" + "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/9c44518a5aff8da565c8a55dbe85d2769e6f630e", - "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/3abdd21b0ceaa3000ee950097bc3cf9efc137853", + "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853", "shasum": "" }, "require": { @@ -7336,22 +7868,23 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Uuid\\": "" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Grégoire Pineau", @@ -7364,9 +7897,14 @@ ], "description": "Symfony polyfill for uuid functions", "homepage": "https://symfony.com", - "keywords": ["compatibility", "polyfill", "portable", "uuid"], + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.29.0" }, "funding": [ { @@ -7382,20 +7920,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/process", - "version": "v6.4.2", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241" + "reference": "31642b0818bfcff85930344ef93193f8c607e0a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c4b1ef0bc80533d87a2e969806172f1c2a980241", - "reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241", + "url": "https://api.github.com/repos/symfony/process/zipball/31642b0818bfcff85930344ef93193f8c607e0a3", + "reference": "31642b0818bfcff85930344ef93193f8c607e0a3", "shasum": "" }, "require": { @@ -7406,10 +7944,14 @@ "psr-4": { "Symfony\\Component\\Process\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -7423,7 +7965,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.2" + "source": "https://github.com/symfony/process/tree/v6.4.3" }, "funding": [ { @@ -7439,7 +7981,7 @@ "type": "tidelift" } ], - "time": "2023-12-22T16:42:54+00:00" + "time": "2024-01-23T14:51:35+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -7484,10 +8026,14 @@ "psr-4": { "Symfony\\Bridge\\PsrHttpMessage\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -7500,7 +8046,12 @@ ], "description": "PSR HTTP message bridge", "homepage": "http://symfony.com", - "keywords": ["http", "http-message", "psr-17", "psr-7"], + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], "support": { "issues": "https://github.com/symfony/psr-http-message-bridge/issues", "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.3.1" @@ -7523,16 +8074,16 @@ }, { "name": "symfony/routing", - "version": "v6.4.2", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "98eab13a07fddc85766f1756129c69f207ffbc21" + "reference": "3b2957ad54902f0f544df83e3d58b38d7e8e5842" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/98eab13a07fddc85766f1756129c69f207ffbc21", - "reference": "98eab13a07fddc85766f1756129c69f207ffbc21", + "url": "https://api.github.com/repos/symfony/routing/zipball/3b2957ad54902f0f544df83e3d58b38d7e8e5842", + "reference": "3b2957ad54902f0f544df83e3d58b38d7e8e5842", "shasum": "" }, "require": { @@ -7559,10 +8110,14 @@ "psr-4": { "Symfony\\Component\\Routing\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -7575,9 +8130,14 @@ ], "description": "Maps an HTTP request to a set of configuration variables", "homepage": "https://symfony.com", - "keywords": ["router", "routing", "uri", "url"], + "keywords": [ + "router", + "routing", + "uri", + "url" + ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.2" + "source": "https://github.com/symfony/routing/tree/v6.4.3" }, "funding": [ { @@ -7593,7 +8153,7 @@ "type": "tidelift" } ], - "time": "2023-12-29T15:34:34+00:00" + "time": "2024-01-30T13:55:02+00:00" }, { "name": "symfony/service-contracts", @@ -7630,10 +8190,14 @@ "psr-4": { "Symfony\\Contracts\\Service\\": "" }, - "exclude-from-classmap": ["/Test/"] + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -7675,16 +8239,16 @@ }, { "name": "symfony/string", - "version": "v7.0.2", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "cc78f14f91f5e53b42044d0620961c48028ff9f5" + "reference": "524aac4a280b90a4420d8d6a040718d0586505ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/cc78f14f91f5e53b42044d0620961c48028ff9f5", - "reference": "cc78f14f91f5e53b42044d0620961c48028ff9f5", + "url": "https://api.github.com/repos/symfony/string/zipball/524aac4a280b90a4420d8d6a040718d0586505ac", + "reference": "524aac4a280b90a4420d8d6a040718d0586505ac", "shasum": "" }, "require": { @@ -7706,14 +8270,20 @@ }, "type": "library", "autoload": { - "files": ["Resources/functions.php"], + "files": [ + "Resources/functions.php" + ], "psr-4": { "Symfony\\Component\\String\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -7726,9 +8296,16 @@ ], "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", "homepage": "https://symfony.com", - "keywords": ["grapheme", "i18n", "string", "unicode", "utf-8", "utf8"], + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], "support": { - "source": "https://github.com/symfony/string/tree/v7.0.2" + "source": "https://github.com/symfony/string/tree/v7.0.3" }, "funding": [ { @@ -7744,20 +8321,20 @@ "type": "tidelift" } ], - "time": "2023-12-10T16:54:46+00:00" + "time": "2024-01-29T15:41:16+00:00" }, { "name": "symfony/translation", - "version": "v6.4.2", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "a2ab2ec1a462e53016de8e8d5e8912bfd62ea681" + "reference": "637c51191b6b184184bbf98937702bcf554f7d04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/a2ab2ec1a462e53016de8e8d5e8912bfd62ea681", - "reference": "a2ab2ec1a462e53016de8e8d5e8912bfd62ea681", + "url": "https://api.github.com/repos/symfony/translation/zipball/637c51191b6b184184bbf98937702bcf554f7d04", + "reference": "637c51191b6b184184bbf98937702bcf554f7d04", "shasum": "" }, "require": { @@ -7780,7 +8357,7 @@ "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { - "nikic/php-parser": "^4.13", + "nikic/php-parser": "^4.18|^5.0", "psr/log": "^1|^2|^3", "symfony/config": "^5.4|^6.0|^7.0", "symfony/console": "^5.4|^6.0|^7.0", @@ -7796,14 +8373,20 @@ }, "type": "library", "autoload": { - "files": ["Resources/functions.php"], + "files": [ + "Resources/functions.php" + ], "psr-4": { "Symfony\\Component\\Translation\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -7817,7 +8400,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.2" + "source": "https://github.com/symfony/translation/tree/v6.4.3" }, "funding": [ { @@ -7833,7 +8416,7 @@ "type": "tidelift" } ], - "time": "2023-12-18T09:25:29+00:00" + "time": "2024-01-29T13:11:52+00:00" }, { "name": "symfony/translation-contracts", @@ -7866,10 +8449,14 @@ "psr-4": { "Symfony\\Contracts\\Translation\\": "" }, - "exclude-from-classmap": ["/Test/"] + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -7911,16 +8498,16 @@ }, { "name": "symfony/uid", - "version": "v6.4.0", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "8092dd1b1a41372110d06374f99ee62f7f0b9a92" + "reference": "1d31267211cc3a2fff32bcfc7c1818dac41b6fc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/8092dd1b1a41372110d06374f99ee62f7f0b9a92", - "reference": "8092dd1b1a41372110d06374f99ee62f7f0b9a92", + "url": "https://api.github.com/repos/symfony/uid/zipball/1d31267211cc3a2fff32bcfc7c1818dac41b6fc0", + "reference": "1d31267211cc3a2fff32bcfc7c1818dac41b6fc0", "shasum": "" }, "require": { @@ -7935,10 +8522,14 @@ "psr-4": { "Symfony\\Component\\Uid\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Grégoire Pineau", @@ -7955,9 +8546,13 @@ ], "description": "Provides an object-oriented API to generate and represent UIDs", "homepage": "https://symfony.com", - "keywords": ["UID", "ulid", "uuid"], + "keywords": [ + "UID", + "ulid", + "uuid" + ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.4.0" + "source": "https://github.com/symfony/uid/tree/v6.4.3" }, "funding": [ { @@ -7973,20 +8568,20 @@ "type": "tidelift" } ], - "time": "2023-10-31T08:18:17+00:00" + "time": "2024-01-23T14:51:35+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.2", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "68d6573ec98715ddcae5a0a85bee3c1c27a4c33f" + "reference": "0435a08f69125535336177c29d56af3abc1f69da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/68d6573ec98715ddcae5a0a85bee3c1c27a4c33f", - "reference": "68d6573ec98715ddcae5a0a85bee3c1c27a4c33f", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0435a08f69125535336177c29d56af3abc1f69da", + "reference": "0435a08f69125535336177c29d56af3abc1f69da", "shasum": "" }, "require": { @@ -8006,17 +8601,25 @@ "symfony/uid": "^5.4|^6.0|^7.0", "twig/twig": "^2.13|^3.0.4" }, - "bin": ["Resources/bin/var-dump-server"], + "bin": [ + "Resources/bin/var-dump-server" + ], "type": "library", "autoload": { - "files": ["Resources/functions/dump.php"], + "files": [ + "Resources/functions/dump.php" + ], "psr-4": { "Symfony\\Component\\VarDumper\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -8029,9 +8632,12 @@ ], "description": "Provides mechanisms for walking through any arbitrary PHP variable", "homepage": "https://symfony.com", - "keywords": ["debug", "dump"], + "keywords": [ + "debug", + "dump" + ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.2" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.3" }, "funding": [ { @@ -8047,20 +8653,20 @@ "type": "tidelift" } ], - "time": "2023-12-28T19:16:56+00:00" + "time": "2024-01-23T14:53:30+00:00" }, { "name": "symfony/yaml", - "version": "v5.4.31", + "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "f387675d7f5fc4231f7554baa70681f222f73563" + "reference": "e78db7f5c70a21f0417a31f414c4a95fe76c07e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/f387675d7f5fc4231f7554baa70681f222f73563", - "reference": "f387675d7f5fc4231f7554baa70681f222f73563", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e78db7f5c70a21f0417a31f414c4a95fe76c07e4", + "reference": "e78db7f5c70a21f0417a31f414c4a95fe76c07e4", "shasum": "" }, "require": { @@ -8077,16 +8683,22 @@ "suggest": { "symfony/console": "For validating YAML files using the lint command" }, - "bin": ["Resources/bin/yaml-lint"], + "bin": [ + "Resources/bin/yaml-lint" + ], "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Yaml\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -8100,7 +8712,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.31" + "source": "https://github.com/symfony/yaml/tree/v5.4.35" }, "funding": [ { @@ -8116,7 +8728,7 @@ "type": "tidelift" } ], - "time": "2023-11-03T14:41:28+00:00" + "time": "2024-01-23T13:51:25+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -8153,7 +8765,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Tijs Verkoyen", @@ -8200,7 +8814,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Fabien Potencier", @@ -8220,7 +8836,9 @@ ], "description": "Twig, the flexible, fast, and secure template language for PHP", "homepage": "https://twig.symfony.com", - "keywords": ["templating"], + "keywords": [ + "templating" + ], "support": { "issues": "https://github.com/twigphp/Twig/issues", "source": "https://github.com/twigphp/Twig/tree/v3.8.0" @@ -8268,7 +8886,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Daniel Bruce", @@ -8283,7 +8903,11 @@ ], "description": "A PHP class for retrieving accurate IP address information for the client.", "homepage": "https://github.com/Vectorface/whip", - "keywords": ["IP", "cdn", "cloudflare"], + "keywords": [ + "IP", + "cdn", + "cloudflare" + ], "support": { "issues": "https://github.com/Vectorface/whip/issues", "source": "https://github.com/Vectorface/whip" @@ -8337,7 +8961,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Graham Campbell", @@ -8351,7 +8977,11 @@ } ], "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", - "keywords": ["dotenv", "env", "environment"], + "keywords": [ + "dotenv", + "env", + "environment" + ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.0" @@ -8398,7 +9028,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Lars Moelleken", @@ -8407,7 +9039,11 @@ ], "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", "homepage": "https://github.com/voku/portable-ascii", - "keywords": ["ascii", "clean", "php"], + "keywords": [ + "ascii", + "clean", + "php" + ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", "source": "https://github.com/voku/portable-ascii/tree/2.0.1" @@ -8473,7 +9109,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Bernhard Schussek", @@ -8481,7 +9119,11 @@ } ], "description": "Assertions to validate method input/output with nice error messages.", - "keywords": ["assert", "check", "validate"], + "keywords": [ + "assert", + "check", + "validate" + ], "support": { "issues": "https://github.com/webmozarts/assert/issues", "source": "https://github.com/webmozarts/assert/tree/1.11.0" @@ -8492,52 +9134,58 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.9.2", + "version": "v3.10.5", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "bfd0131c146973cab164e50f5cdd8a67cc60cab1" + "reference": "d1a48965f2b25a6cec2eea07d719b568a37c9a88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/bfd0131c146973cab164e50f5cdd8a67cc60cab1", - "reference": "bfd0131c146973cab164e50f5cdd8a67cc60cab1", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/d1a48965f2b25a6cec2eea07d719b568a37c9a88", + "reference": "d1a48965f2b25a6cec2eea07d719b568a37c9a88", "shasum": "" }, "require": { - "illuminate/routing": "^9|^10", - "illuminate/session": "^9|^10", - "illuminate/support": "^9|^10", - "maximebf/debugbar": "^1.18.2", + "illuminate/routing": "^9|^10|^11", + "illuminate/session": "^9|^10|^11", + "illuminate/support": "^9|^10|^11", + "maximebf/debugbar": "~1.20.1", "php": "^8.0", - "symfony/finder": "^6" + "symfony/finder": "^6|^7" }, "require-dev": { "mockery/mockery": "^1.3.3", - "orchestra/testbench-dusk": "^5|^6|^7|^8", + "orchestra/testbench-dusk": "^5|^6|^7|^8|^9", "phpunit/phpunit": "^8.5.30|^9.0", "squizlabs/php_codesniffer": "^3.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.8-dev" + "dev-master": "3.10-dev" }, "laravel": { - "providers": ["Barryvdh\\Debugbar\\ServiceProvider"], + "providers": [ + "Barryvdh\\Debugbar\\ServiceProvider" + ], "aliases": { "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar" } } }, "autoload": { - "files": ["src/helpers.php"], + "files": [ + "src/helpers.php" + ], "psr-4": { "Barryvdh\\Debugbar\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Barry vd. Heuvel", @@ -8545,10 +9193,16 @@ } ], "description": "PHP Debugbar integration for Laravel", - "keywords": ["debug", "debugbar", "laravel", "profiler", "webprofiler"], + "keywords": [ + "debug", + "debugbar", + "laravel", + "profiler", + "webprofiler" + ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.9.2" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.10.5" }, "funding": [ { @@ -8560,44 +9214,44 @@ "type": "github" } ], - "time": "2023-08-25T18:43:57+00:00" + "time": "2024-02-15T10:45:45+00:00" }, { "name": "barryvdh/laravel-ide-helper", - "version": "v2.13.0", + "version": "v2.15.1", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "81d5b223ff067a1f38e14c100997e153b837fe4a" + "reference": "77831852bb7bc54f287246d32eb91274eaf87f8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/81d5b223ff067a1f38e14c100997e153b837fe4a", - "reference": "81d5b223ff067a1f38e14c100997e153b837fe4a", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/77831852bb7bc54f287246d32eb91274eaf87f8b", + "reference": "77831852bb7bc54f287246d32eb91274eaf87f8b", "shasum": "" }, "require": { "barryvdh/reflection-docblock": "^2.0.6", "composer/class-map-generator": "^1.0", - "doctrine/dbal": "^2.6 || ^3", + "doctrine/dbal": "^2.6 || ^3.1.4", "ext-json": "*", - "illuminate/console": "^8 || ^9 || ^10", - "illuminate/filesystem": "^8 || ^9 || ^10", - "illuminate/support": "^8 || ^9 || ^10", - "nikic/php-parser": "^4.7", - "php": "^7.3 || ^8.0", + "illuminate/console": "^9 || ^10", + "illuminate/filesystem": "^9 || ^10", + "illuminate/support": "^9 || ^10", + "nikic/php-parser": "^4.18 || ^5", + "php": "^8.0", "phpdocumentor/type-resolver": "^1.1.0" }, "require-dev": { "ext-pdo_sqlite": "*", - "friendsofphp/php-cs-fixer": "^2", - "illuminate/config": "^8 || ^9 || ^10", - "illuminate/view": "^8 || ^9 || ^10", + "friendsofphp/php-cs-fixer": "^3", + "illuminate/config": "^9 || ^10", + "illuminate/view": "^9 || ^10", "mockery/mockery": "^1.4", - "orchestra/testbench": "^6 || ^7 || ^8", - "phpunit/phpunit": "^8.5 || ^9", - "spatie/phpunit-snapshot-assertions": "^3 || ^4", - "vimeo/psalm": "^3.12" + "orchestra/testbench": "^7 || ^8", + "phpunit/phpunit": "^9", + "spatie/phpunit-snapshot-assertions": "^4", + "vimeo/psalm": "^5.4" }, "suggest": { "illuminate/events": "Required for automatic helper generation (^6|^7|^8|^9|^10)." @@ -8605,10 +9259,12 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.12-dev" + "dev-master": "2.15-dev" }, "laravel": { - "providers": ["Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider"] + "providers": [ + "Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider" + ] } }, "autoload": { @@ -8617,7 +9273,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Barry vd. Heuvel", @@ -8638,7 +9296,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-ide-helper/issues", - "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.13.0" + "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.15.1" }, "funding": [ { @@ -8650,7 +9308,7 @@ "type": "github" } ], - "time": "2023-02-04T13:56:40+00:00" + "time": "2024-02-15T14:23:20+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -8684,11 +9342,15 @@ }, "autoload": { "psr-0": { - "Barryvdh": ["src/"] + "Barryvdh": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Mike van Riel", @@ -8739,7 +9401,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Jordi Boggiano", @@ -8748,7 +9412,9 @@ } ], "description": "Utilities to scan PHP code and generate class maps.", - "keywords": ["classmap"], + "keywords": [ + "classmap" + ], "support": { "issues": "https://github.com/composer/class-map-generator/issues", "source": "https://github.com/composer/class-map-generator/tree/1.1.0" @@ -8803,7 +9469,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Jordi Boggiano", @@ -8812,7 +9480,12 @@ } ], "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": ["PCRE", "preg", "regex", "regular expression"], + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], "support": { "issues": "https://github.com/composer/pcre/issues", "source": "https://github.com/composer/pcre/tree/3.1.1" @@ -8864,7 +9537,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "John Stevenson", @@ -8872,7 +9547,10 @@ } ], "description": "Restarts a process without Xdebug.", - "keywords": ["Xdebug", "performance"], + "keywords": [ + "Xdebug", + "performance" + ], "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/xdebug-handler/issues", @@ -8937,14 +9615,20 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "François Zaninotto" } ], "description": "Faker is a PHP library that generates fake data for you.", - "keywords": ["data", "faker", "fixtures"], + "keywords": [ + "data", + "faker", + "fixtures" + ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" @@ -8953,16 +9637,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.46.0", + "version": "v3.49.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "be6831c9af1740470d2a773119b9273f8ac1c3d2" + "reference": "8742f7aa6f72a399688b65e4f58992c2d4681fc2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/be6831c9af1740470d2a773119b9273f8ac1c3d2", - "reference": "be6831c9af1740470d2a773119b9273f8ac1c3d2", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/8742f7aa6f72a399688b65e4f58992c2d4681fc2", + "reference": "8742f7aa6f72a399688b65e4f58992c2d4681fc2", "shasum": "" }, "require": { @@ -9000,7 +9684,9 @@ "ext-dom": "For handling output formats in XML", "ext-mbstring": "For handling non-UTF8 characters." }, - "bin": ["php-cs-fixer"], + "bin": [ + "php-cs-fixer" + ], "type": "application", "autoload": { "psr-4": { @@ -9008,7 +9694,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -9028,7 +9716,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.46.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.49.0" }, "funding": [ { @@ -9036,7 +9724,7 @@ "type": "github" } ], - "time": "2024-01-03T21:38:46+00:00" + "time": "2024-02-02T00:41:40+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -9071,12 +9759,18 @@ } }, "autoload": { - "classmap": ["hamcrest"] + "classmap": [ + "hamcrest" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "description": "This is the PHP port of Hamcrest Matchers", - "keywords": ["test"], + "keywords": [ + "test" + ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" @@ -9085,36 +9779,32 @@ }, { "name": "laravel/browser-kit-testing", - "version": "v7.0.0", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/laravel/browser-kit-testing.git", - "reference": "5792cc6dbc6dc4c05fc1221378a7099119c91925" + "reference": "a1fd806219de24fe6b220ad22460320f81362dff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/browser-kit-testing/zipball/5792cc6dbc6dc4c05fc1221378a7099119c91925", - "reference": "5792cc6dbc6dc4c05fc1221378a7099119c91925", + "url": "https://api.github.com/repos/laravel/browser-kit-testing/zipball/a1fd806219de24fe6b220ad22460320f81362dff", + "reference": "a1fd806219de24fe6b220ad22460320f81362dff", "shasum": "" }, "require": { "ext-dom": "*", - "illuminate/contracts": "^10.0", - "illuminate/database": "^10.0", - "illuminate/http": "^10.0", - "illuminate/support": "^10.0", - "illuminate/testing": "^10.0", + "laravel/framework": "^10.44|^11.0", "mockery/mockery": "^1.0", - "php": "^8.1", - "phpunit/phpunit": "^10.0.7", - "symfony/console": "^6.2", - "symfony/css-selector": "^6.2", - "symfony/dom-crawler": "^6.2", - "symfony/http-foundation": "^6.2", - "symfony/http-kernel": "^6.2" + "php": "^8.2", + "phpunit/phpunit": "^10.4|^11.0.1", + "symfony/console": "^6.2|^7.0", + "symfony/css-selector": "^6.2|^7.0", + "symfony/dom-crawler": "^6.2|^7.0", + "symfony/http-foundation": "^6.2|^7.0", + "symfony/http-kernel": "^6.2|^7.0" }, "require-dev": { - "laravel/framework": "^10.0" + "orchestra/testbench-core": "^8.21|^9.0" }, "type": "library", "extra": { @@ -9128,7 +9818,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Taylor Otwell", @@ -9136,12 +9828,15 @@ } ], "description": "Provides backwards compatibility for BrowserKit testing in the latest Laravel release.", - "keywords": ["laravel", "testing"], + "keywords": [ + "laravel", + "testing" + ], "support": { "issues": "https://github.com/laravel/browser-kit-testing/issues", - "source": "https://github.com/laravel/browser-kit-testing/tree/v7.0.0" + "source": "https://github.com/laravel/browser-kit-testing/tree/v7.2.0" }, - "time": "2023-02-14T14:53:15+00:00" + "time": "2024-02-09T15:16:10+00:00" }, { "name": "laravel/tinker", @@ -9176,7 +9871,9 @@ "type": "library", "extra": { "laravel": { - "providers": ["Laravel\\Tinker\\TinkerServiceProvider"] + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] } }, "autoload": { @@ -9185,7 +9882,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Taylor Otwell", @@ -9193,7 +9892,12 @@ } ], "description": "Powerful REPL for the Laravel framework.", - "keywords": ["REPL", "Tinker", "laravel", "psysh"], + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], "support": { "issues": "https://github.com/laravel/tinker/issues", "source": "https://github.com/laravel/tinker/tree/v2.9.0" @@ -9233,7 +9937,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Matt Butcher", @@ -9267,22 +9973,22 @@ }, { "name": "maximebf/debugbar", - "version": "v1.19.1", + "version": "v1.20.2", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "03dd40a1826f4d585ef93ef83afa2a9874a00523" + "reference": "484625c23a4fa4f303617f29fcacd42951c9c01d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/03dd40a1826f4d585ef93ef83afa2a9874a00523", - "reference": "03dd40a1826f4d585ef93ef83afa2a9874a00523", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/484625c23a4fa4f303617f29fcacd42951c9c01d", + "reference": "484625c23a4fa4f303617f29fcacd42951c9c01d", "shasum": "" }, "require": { "php": "^7.1|^8", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^4|^5|^6" + "symfony/var-dumper": "^4|^5|^6|^7" }, "require-dev": { "phpunit/phpunit": ">=7.5.20 <10.0", @@ -9296,7 +10002,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-master": "1.20-dev" } }, "autoload": { @@ -9305,7 +10011,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Maxime Bouroumeau-Fuseau", @@ -9319,12 +10027,15 @@ ], "description": "Debug bar in the browser for php application", "homepage": "https://github.com/maximebf/php-debugbar", - "keywords": ["debug", "debugbar"], + "keywords": [ + "debug", + "debugbar" + ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.19.1" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.20.2" }, - "time": "2023-10-12T08:10:52+00:00" + "time": "2024-02-15T10:49:09+00:00" }, { "name": "mockery/mockery", @@ -9354,13 +10065,18 @@ }, "type": "library", "autoload": { - "files": ["library/helpers.php", "library/Mockery.php"], + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], "psr-4": { "Mockery\\": "library/Mockery" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Pádraic Brady", @@ -9432,15 +10148,25 @@ }, "type": "library", "autoload": { - "files": ["src/DeepCopy/deep_copy.php"], + "files": [ + "src/DeepCopy/deep_copy.php" + ], "psr-4": { "DeepCopy\\": "src/DeepCopy/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "description": "Create deep copies (clones) of your objects", - "keywords": ["clone", "copy", "duplicate", "object", "object graph"], + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" @@ -9455,31 +10181,35 @@ }, { "name": "nikic/php-parser", - "version": "v4.18.0", + "version": "v5.0.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/2218c2252c874a4624ab2f613d86ac32d227bc69", + "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, - "bin": ["bin/php-parse"], + "bin": [ + "bin/php-parse" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -9488,19 +10218,24 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Nikita Popov" } ], "description": "A PHP parser written in PHP", - "keywords": ["parser", "php"], + "keywords": [ + "parser", + "php" + ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.1" }, - "time": "2023-12-10T21:03:43+00:00" + "time": "2024-02-21T19:24:10+00:00" }, { "name": "phar-io/manifest", @@ -9530,10 +10265,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Arne Blankerts", @@ -9577,10 +10316,14 @@ }, "type": "library", "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Arne Blankerts", @@ -9634,7 +10377,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Jaap van Otterdijk", @@ -9658,21 +10403,21 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.8.0", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "fad452781b3d774e3337b0c0b245dd8e5a4455fc" + "reference": "153ae662783729388a584b4361f2545e4d841e3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/fad452781b3d774e3337b0c0b245dd8e5a4455fc", - "reference": "fad452781b3d774e3337b0c0b245dd8e5a4455fc", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", - "php": "^7.4 || ^8.0", + "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", "phpstan/phpdoc-parser": "^1.13" }, @@ -9698,7 +10443,9 @@ } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Mike van Riel", @@ -9708,9 +10455,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" }, - "time": "2024-01-11T11:49:22+00:00" + "time": "2024-02-23T11:10:43+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -9743,11 +10490,15 @@ "type": "library", "autoload": { "psr-4": { - "PHPStan\\PhpDocParser\\": ["src/"] + "PHPStan\\PhpDocParser\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", @@ -9798,10 +10549,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -9811,7 +10566,11 @@ ], "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": ["coverage", "testing", "xunit"], + "keywords": [ + "coverage", + "testing", + "xunit" + ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", @@ -9852,10 +10611,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -9865,7 +10628,10 @@ ], "description": "FilterIterator implementation that filters files based on a list of suffixes.", "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": ["filesystem", "iterator"], + "keywords": [ + "filesystem", + "iterator" + ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", @@ -9910,10 +10676,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -9923,7 +10693,9 @@ ], "description": "Invoke callables with a timeout", "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": ["process"], + "keywords": [ + "process" + ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" @@ -9963,10 +10735,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -9976,7 +10752,9 @@ ], "description": "Simple template engine.", "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": ["template"], + "keywords": [ + "template" + ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", @@ -10017,10 +10795,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10030,7 +10812,9 @@ ], "description": "Utility class for timing", "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": ["timer"], + "keywords": [ + "timer" + ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" @@ -10045,16 +10829,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.5", + "version": "10.5.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "ed21115d505b4b4f7dc7b5651464e19a2c7f7856" + "reference": "50b8e314b6d0dd06521dc31d1abffa73f25f850c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ed21115d505b4b4f7dc7b5651464e19a2c7f7856", - "reference": "ed21115d505b4b4f7dc7b5651464e19a2c7f7856", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/50b8e314b6d0dd06521dc31d1abffa73f25f850c", + "reference": "50b8e314b6d0dd06521dc31d1abffa73f25f850c", "shasum": "" }, "require": { @@ -10088,7 +10872,9 @@ "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" }, - "bin": ["phpunit"], + "bin": [ + "phpunit" + ], "type": "library", "extra": { "branch-alias": { @@ -10096,11 +10882,17 @@ } }, "autoload": { - "files": ["src/Framework/Assert/Functions.php"], - "classmap": ["src/"] + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10110,11 +10902,15 @@ ], "description": "The PHP Unit Testing framework.", "homepage": "https://phpunit.de/", - "keywords": ["phpunit", "testing", "xunit"], + "keywords": [ + "phpunit", + "testing", + "xunit" + ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.5" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.10" }, "funding": [ { @@ -10130,7 +10926,7 @@ "type": "tidelift" } ], - "time": "2023-12-27T15:13:52+00:00" + "time": "2024-02-04T09:07:51+00:00" }, { "name": "psy/psysh", @@ -10165,7 +10961,9 @@ "ext-pdo-sqlite": "The doc command requires SQLite to work.", "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." }, - "bin": ["bin/psysh"], + "bin": [ + "bin/psysh" + ], "type": "library", "extra": { "branch-alias": { @@ -10177,13 +10975,17 @@ } }, "autoload": { - "files": ["src/functions.php"], + "files": [ + "src/functions.php" + ], "psr-4": { "Psy\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Justin Hileman", @@ -10193,7 +10995,12 @@ ], "description": "An interactive shell for modern PHP.", "homepage": "http://psysh.org", - "keywords": ["REPL", "console", "interactive", "shell"], + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", "source": "https://github.com/bobthecow/psysh/tree/v0.12.0" @@ -10227,10 +11034,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10279,10 +11090,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10331,10 +11146,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10386,10 +11205,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10410,7 +11233,11 @@ ], "description": "Provides the functionality to compare PHP values for equality", "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": ["comparator", "compare", "equality"], + "keywords": [ + "comparator", + "compare", + "equality" + ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", @@ -10452,10 +11279,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10506,10 +11337,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10522,7 +11357,12 @@ ], "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": ["diff", "udiff", "unidiff", "unified diff"], + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", @@ -10566,10 +11406,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10578,7 +11422,11 @@ ], "description": "Provides functionality to handle HHVM/PHP environments", "homepage": "https://github.com/sebastianbergmann/environment", - "keywords": ["Xdebug", "environment", "hhvm"], + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", @@ -10621,10 +11469,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10649,7 +11501,10 @@ ], "description": "Provides the functionality to export PHP variables for visualization", "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": ["export", "exporter"], + "keywords": [ + "export", + "exporter" + ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", @@ -10693,10 +11548,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10705,7 +11564,9 @@ ], "description": "Snapshotting of global state", "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": ["global state"], + "keywords": [ + "global state" + ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", @@ -10747,10 +11608,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10802,10 +11667,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10853,10 +11722,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10904,10 +11777,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -10963,10 +11840,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -11012,10 +11893,14 @@ } }, "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Sebastian Bergmann", @@ -11039,16 +11924,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v6.4.0", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "14ff4fd2a5c8969d6158dbe7ef5b17d6a9c6ba33" + "reference": "6db31849011fefe091e94d0bb10cba26f7919894" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/14ff4fd2a5c8969d6158dbe7ef5b17d6a9c6ba33", - "reference": "14ff4fd2a5c8969d6158dbe7ef5b17d6a9c6ba33", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/6db31849011fefe091e94d0bb10cba26f7919894", + "reference": "6db31849011fefe091e94d0bb10cba26f7919894", "shasum": "" }, "require": { @@ -11065,10 +11950,14 @@ "psr-4": { "Symfony\\Component\\DomCrawler\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -11082,7 +11971,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v6.4.0" + "source": "https://github.com/symfony/dom-crawler/tree/v6.4.3" }, "funding": [ { @@ -11098,20 +11987,20 @@ "type": "tidelift" } ], - "time": "2023-11-20T16:41:16+00:00" + "time": "2024-01-23T14:51:35+00:00" }, { "name": "symfony/filesystem", - "version": "v7.0.0", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "7da8ea2362a283771478c5f7729cfcb43a76b8b7" + "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/7da8ea2362a283771478c5f7729cfcb43a76b8b7", - "reference": "7da8ea2362a283771478c5f7729cfcb43a76b8b7", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/2890e3a825bc0c0558526c04499c13f83e1b6b12", + "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12", "shasum": "" }, "require": { @@ -11124,10 +12013,14 @@ "psr-4": { "Symfony\\Component\\Filesystem\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -11141,7 +12034,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.0.0" + "source": "https://github.com/symfony/filesystem/tree/v7.0.3" }, "funding": [ { @@ -11157,7 +12050,7 @@ "type": "tidelift" } ], - "time": "2023-07-27T06:33:22+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "symfony/options-resolver", @@ -11182,10 +12075,14 @@ "psr-4": { "Symfony\\Component\\OptionsResolver\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -11198,7 +12095,11 @@ ], "description": "Provides an improved replacement for the array_replace PHP function", "homepage": "https://symfony.com", - "keywords": ["config", "configuration", "options"], + "keywords": [ + "config", + "configuration", + "options" + ], "support": { "source": "https://github.com/symfony/options-resolver/tree/v7.0.0" }, @@ -11220,16 +12121,16 @@ }, { "name": "symfony/polyfill-php81", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b" + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/7581cd600fa9fd681b797d00b02f068e2f13263b", - "reference": "7581cd600fa9fd681b797d00b02f068e2f13263b", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", "shasum": "" }, "require": { @@ -11237,23 +12138,26 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, "autoload": { - "files": ["bootstrap.php"], + "files": [ + "bootstrap.php" + ], "psr-4": { "Symfony\\Polyfill\\Php81\\": "" }, - "classmap": ["Resources/stubs"] + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Nicolas Grekas", @@ -11266,9 +12170,14 @@ ], "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", - "keywords": ["compatibility", "polyfill", "portable", "shim"], + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0" }, "funding": [ { @@ -11284,20 +12193,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/stopwatch", - "version": "v7.0.0", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "7bbfa3dd564a0ce12eb4acaaa46823c740f9cb7a" + "reference": "983900d6fddf2b0cbaacacbbad07610854bd8112" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/7bbfa3dd564a0ce12eb4acaaa46823c740f9cb7a", - "reference": "7bbfa3dd564a0ce12eb4acaaa46823c740f9cb7a", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/983900d6fddf2b0cbaacacbbad07610854bd8112", + "reference": "983900d6fddf2b0cbaacacbbad07610854bd8112", "shasum": "" }, "require": { @@ -11309,10 +12218,14 @@ "psr-4": { "Symfony\\Component\\Stopwatch\\": "" }, - "exclude-from-classmap": ["/Tests/"] + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["MIT"], + "license": [ + "MIT" + ], "authors": [ { "name": "Fabien Potencier", @@ -11326,7 +12239,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.0.0" + "source": "https://github.com/symfony/stopwatch/tree/v7.0.3" }, "funding": [ { @@ -11342,7 +12255,7 @@ "type": "tidelift" } ], - "time": "2023-07-05T13:06:06+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "theseer/tokenizer", @@ -11366,10 +12279,14 @@ }, "type": "library", "autoload": { - "classmap": ["src/"] + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", - "license": ["BSD-3-Clause"], + "license": [ + "BSD-3-Clause" + ], "authors": [ { "name": "Arne Blankerts", diff --git a/config/twigbridge.php b/config/twigbridge.php index 7fdb7b93..c96dbe9d 100644 --- a/config/twigbridge.php +++ b/config/twigbridge.php @@ -123,6 +123,7 @@ return [ // 'TwigBridge\Extension\Laravel\Form', // 'TwigBridge\Extension\Laravel\Html', + 'TwigBridge\Extension\Laravel\Vite', // 'TwigBridge\Extension\Laravel\Legacy\Facades', ], @@ -153,7 +154,8 @@ return [ | in order to be marked as safe. | */ - 'facades' => [], + 'facades' => [ + ], /* |-------------------------------------------------------------------------- diff --git a/package.json b/package.json index a401c2dd..011dafa8 100644 --- a/package.json +++ b/package.json @@ -1,162 +1,134 @@ { "name": "blessing-skin-server", "version": "6.0.2", + "private": true, "description": "A web application brings your custom skins back in offline Minecraft servers.", "repository": { "type": "git", "url": "https://github.com/bs-community/blessing-skin-server" }, - "author": "printempw", "license": "MIT", - "private": true, + "author": "printempw", + "type": "module", "scripts": { - "dev": "webpack serve", - "build": "webpack --env production --progress", - "lint": "eslint --ext=ts -f=beauty .", - "fmt": "prettier --write resources/assets tools webpack.config.ts", - "fmt:check": "prettier --check resources/assets tools webpack.config.ts", - "type:check": "tsc -p . --noEmit && tsc -p ./resources/assets/tests --noEmit", - "test": "jest", + "build": "vite build", "build:urls": "ts-node tools/generateUrls.ts", - "prepare": "husky install" + "dev": "vite", + "lint": "eslint .", + "test": "vitest" }, - "dependencies": { - "@emotion/react": "^11.0.0", - "@emotion/styled": "^11.0.0", - "@fortawesome/fontawesome-free": "^6.3.0", - "@hot-loader/react-dom": "^17.0.0", - "@tweenjs/tween.js": "^18.5.0", - "admin-lte": "^3.2.0", - "blessing-skin-shell": "^0.3.4", - "bootstrap": "^4.6.1", - "cac": "6.6.1", - "cli-spinners": "^2.5.0", - "clsx": "^1.1.1", - "echarts": "^5.1.2", - "events": "^3.2.0", - "immer": "^7.0.4", - "jquery": "^3.6.0", - "lodash.debounce": "^4.0.8", - "nanoid": "^3.1.9", - "prompts": "^2.4.0", - "react": "^17.0.1", - "react-autosuggest": "^10.0.2", - "react-dom": "^17.0.1", - "react-draggable": "^4.4.2", - "react-hot-loader": "^4.12.21", - "react-loading-skeleton": "^2.1.1", - "react-use": "^17.4.0", - "reaptcha": "^1.7.2", - "rxjs": "^6.5.5", - "skinview-utils": "^0.5.5", - "skinview3d": "^3.0.0-alpha.1", - "spectre.css": "^0.5.8", - "use-immer": "^0.4.2", - "xterm": "^4.6.0", - "xterm-addon-fit": "^0.4.0" - }, - "devDependencies": { - "@gplane/tsconfig": "^4.2.0", - "@testing-library/jest-dom": "^5.11.10", - "@testing-library/react": "^11.2.6", - "@types/bootstrap": "^4.3.3", - "@types/css-minimizer-webpack-plugin": "^1.1.0", - "@types/jest": "^26.0.23", - "@types/jquery": "^3.5.13", - "@types/js-yaml": "^3.12.4", - "@types/lodash.debounce": "^4.0.6", - "@types/mini-css-extract-plugin": "^1.2.1", - "@types/prompts": "^2.0.9", - "@types/react": "^16.9.35", - "@types/react-autosuggest": "^9.3.14", - "@types/react-dom": "^16.9.8", - "@types/tween.js": "^18.5.0", - "@types/webpack-dev-server": "^3.11.0", - "@typescript-eslint/eslint-plugin": "^3.6.0", - "@typescript-eslint/parser": "^3.6.0", - "autoprefixer": "^10.2.6", - "css-loader": "^5.2.6", - "css-minimizer-webpack-plugin": "^3.0.1", - "eslint": "^7.4.0", - "eslint-formatter-beauty": "^3.0.0", - "eslint-plugin-react-hooks": "^4.3.0", - "html-webpack-plugin": "^5.3.1", - "husky": "^7.0.4", - "jest": "^27.0.4", - "jest-extended": "^0.11.5", - "js-yaml": "^3.13.1", - "mini-css-extract-plugin": "^1.6.0", - "postcss": "^8.3.0", - "postcss-loader": "^5.3.0", - "prettier": "^2.3.0", - "pretty-quick": "^3.1.3", - "style-loader": "^2.0.0", - "ts-jest": "^27.0.2", - "ts-loader": "^9.2.2", - "ts-node": "^10.0.0", - "typescript": "^4.3.2", - "webpack": "^5.38.1", - "webpack-cli": "^4.7.0", - "webpack-dev-server": "^3.11.2" + "browserslist": [ + "Firefox ESR", + "iOS >= 12.5", + "Chrome >= 87" + ], + "eslintConfig": { + "env": { + "es2024": true + }, + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "extends": [ + "xo", + "xo-react", + "plugin:react/jsx-runtime", + "./node_modules/xo/config/plugins.cjs" + ], + "rules": { + "import/extensions": "off", + "import/no-named-as-default": "off", + "n/file-extension-in-import": "off", + "unicorn/filename-case": "off", + "n/prefer-global/process": "off" + }, + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx" + ], + "extends": [ + "xo-typescript" + ], + "rules": { + "@typescript-eslint/ban-types": "off", + "@typescript-eslint/consistent-type-definitions": "warn", + "@typescript-eslint/naming-convention": "warn" + } + } + ], + "ignorePatterns": [ + "dist", + "public" + ], + "root": true }, "resolutions": { "kleur": "^4.1.3" }, - "browserslist": [ - "> 1%", - "not dead", - "not ie 11", - "Chrome > 52" - ], - "prettier": { - "printWidth": 80, - "semi": false, - "singleQuote": true, - "trailingComma": "all", - "tabWidth": 2 + "dependencies": { + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.0.0", + "@fortawesome/fontawesome-free": "^6.3.0", + "@tweenjs/tween.js": "^23.1.1", + "admin-lte": "next", + "bootstrap": "^5.3.3", + "clsx": "^2.1.0", + "echarts": "^5.5.0", + "immer": "^10.0.3", + "jquery": "^3.6.0", + "lodash-es": "^4.0.8", + "nanoid": "^5.0.6", + "prompts": "^2.4.0", + "react": "^18.2.0", + "react-autosuggest": "^10.0.2", + "react-dom": "^18.2.0", + "react-draggable": "^4.4.2", + "react-loading-skeleton": "^3.4.0", + "react-use": "^17.5.0", + "reaptcha": "^1.7.2", + "rxjs": "^7.8.1", + "skinview-utils": "^0.7.1", + "skinview3d": "^3.0.0-alpha.1", + "spectre.css": "npm:@angular-package/spectre.css", + "use-immer": "^0.9.0" }, - "jest": { - "preset": "ts-jest", - "resetMocks": true, - "testEnvironment": "jsdom", - "moduleFileExtensions": [ - "js", - "ts", - "tsx", - "json", - "node" - ], - "moduleNameMapper": { - "\\.css$": "/resources/assets/tests/__mocks__/style.ts", - "\\.(png|webp)$": "/resources/assets/tests/__mocks__/file.ts", - "^@/(.*)$": "/resources/assets/src/$1" - }, - "setupFilesAfterEnv": [ - "/resources/assets/tests/setup.ts" - ], - "coveragePathIgnorePatterns": [ - "/node_modules/", - "/resources/assets/src/styles", - "/resources/assets/src/scripts/extra.ts", - "/resources/assets/src/scripts/urls.ts", - "/resources/assets/tests/setup", - "/resources/assets/tests/utils", - "/resources/assets/tests/scripts/cli/stdio" - ], - "testMatch": [ - "/resources/assets/tests/**/*.test.ts", - "/resources/assets/tests/**/*.test.tsx" - ], - "testPathIgnorePatterns": [ - "/node_modules/", - "/resources/assets/tests/(views|components)/.*\\.ts$" - ], - "maxWorkers": "50%", - "globals": { - "ts-jest": { - "tsconfig": "/resources/assets/tests/tsconfig.json", - "isolatedModules": true - } + "devDependencies": { + "@testing-library/jest-dom": "^6.4.2", + "@testing-library/react": "^14.2.1", + "@tsconfig/vite-react": "^3.0.0", + "@types/bootstrap": "^5.2.10", + "@types/jquery": "^3.5.13", + "@types/js-yaml": "^4.0.9", + "@types/lodash-es": "^4.0.6", + "@types/prompts": "^2.0.9", + "@types/react": "^18.2.62", + "@types/react-autosuggest": "^10.1.11", + "@types/react-dom": "^18.2.19", + "@types/tween.js": "^18.5.0", + "@vitejs/plugin-react-swc": "^3.6.0", + "autoprefixer": "^10.4.18", + "browserslist": "^4.23.0", + "browserslist-to-esbuild": "^2.1.1", + "eslint-config-xo-react": "^0.27.0", + "eslint-plugin-react": "^7.34.0", + "eslint-plugin-react-hooks": "^4.6.0", + "js-yaml": "^4.1.0", + "laravel-vite-plugin": "^1.0.2", + "postcss": "^8.4.35", + "sass": "^1.71.1", + "typescript": "^5.3.3", + "vite": "^5.1.5", + "vite-plugin-top-level-await": "^1.4.1", + "vite-plugin-wasm": "^3.3.0", + "vitest": "^1.3.1", + "xo": "^0.57.0" + }, + "postcss": { + "plugins": { + "autoprefixer": {} } } } diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index f5815656..00000000 --- a/postcss.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - plugins: [ - require('autoprefixer'), - ], -} diff --git a/public/.gitignore b/public/.gitignore index 908f7bbc..2afdd76e 100644 --- a/public/.gitignore +++ b/public/.gitignore @@ -1 +1,2 @@ app/ +build/ diff --git a/resources/assets/src/app.css b/resources/assets/src/app.css new file mode 100644 index 00000000..243b827d --- /dev/null +++ b/resources/assets/src/app.css @@ -0,0 +1,6 @@ +@import '@/styles/common.css'; +@import 'admin-lte/src/scss/adminlte.scss'; +@import '@fortawesome/fontawesome-free/scss/fontawesome.scss'; +@import '@fortawesome/fontawesome-free/scss/regular.scss'; +@import '@fortawesome/fontawesome-free/scss/solid.scss'; +@import '@fortawesome/fontawesome-free/scss/brands.scss'; diff --git a/resources/assets/src/components/Alert.tsx b/resources/assets/src/components/Alert.tsx index 722b16f2..e05b914b 100644 --- a/resources/assets/src/components/Alert.tsx +++ b/resources/assets/src/components/Alert.tsx @@ -1,28 +1,28 @@ -import React from 'react' -type AlertType = 'success' | 'info' | 'warning' | 'danger' +type AlertType = 'success' | 'info' | 'warning' | 'danger'; const icons = new Map([ - ['success', 'check'], - ['info', 'info'], - ['warning', 'exclamation-triangle'], - ['danger', 'times-circle'], -]) + ['success', 'check'], + ['info', 'info'], + ['warning', 'exclamation-triangle'], + ['danger', 'times-circle'], +]); -interface Props { - type: AlertType -} +type Properties = { + readonly type: AlertType; + readonly children?: React.ReactNode; +}; -const Alert: React.FC = (props) => { - const { type } = props - const icon = icons.get(type) +const Alert: React.FC = properties => { + const {type} = properties; + const icon = icons.get(type); - return props.children ? ( -
- - {props.children} -
- ) : null -} + return properties.children ? ( +
+ + {properties.children} +
+ ) : null; +}; -export default Alert +export default Alert; diff --git a/resources/assets/src/components/ButtonEdit.tsx b/resources/assets/src/components/ButtonEdit.tsx index 83c055aa..076134e7 100644 --- a/resources/assets/src/components/ButtonEdit.tsx +++ b/resources/assets/src/components/ButtonEdit.tsx @@ -1,14 +1,13 @@ -import React from 'react' -interface Props { - title?: string - onClick: React.MouseEventHandler -} +type Properties = { + readonly title?: string; + readonly onClick: React.MouseEventHandler; +}; -const ButtonEdit: React.FC = (props) => ( - - - -) +const ButtonEdit: React.FC = properties => ( + + + +); -export default ButtonEdit +export default ButtonEdit; diff --git a/resources/assets/src/components/Captcha.tsx b/resources/assets/src/components/Captcha.tsx index 33c710e5..d18a1698 100644 --- a/resources/assets/src/components/Captcha.tsx +++ b/resources/assets/src/components/Captcha.tsx @@ -1,103 +1,104 @@ /** @jsxImportSource @emotion/react */ -import * as React from 'react' -import Reaptcha from 'reaptcha' -import { emit, on } from '@/scripts/event' -import { t } from '@/scripts/i18n' -import * as cssUtils from '@/styles/utils' +import React from 'react'; +import Reaptcha from 'reaptcha'; +import {emit, on} from '@/scripts/event'; +import {t} from '@/scripts/i18n'; +import * as cssUtils from '@/styles/utils'; -const eventId = Symbol() +const eventId = Symbol(); type State = { - value: string - time: number - sitekey: string - invisible: boolean -} + value: string; + time: number; + sitekey: string; + invisible: boolean; +}; class Captcha extends React.Component, State> { - state: State - ref: React.MutableRefObject + state: State; + ref: React.MutableRefObject; - constructor(props: Record) { - super(props) - this.state = { - value: '', - time: Date.now(), - sitekey: blessing.extra.recaptcha, - invisible: blessing.extra.invisible, - } - this.ref = React.createRef() - } + constructor(properties: Record) { + super(properties); + this.state = { + value: '', + time: Date.now(), + sitekey: blessing.extra.recaptcha, + invisible: blessing.extra.invisible, + }; + this.ref = React.createRef(); + } - execute = async () => { - const recaptcha = this.ref.current - if (recaptcha && this.state.invisible) { - return new Promise((resolve) => { - const off = on(eventId, (value: string) => { - resolve(value) - off() - }) - recaptcha.execute() - }) - } - return this.state.value - } + execute = async () => { + const recaptcha = this.ref.current; + if (recaptcha && this.state.invisible) { + return new Promise(resolve => { + const off = on(eventId, (value: string) => { + resolve(value); + off(); + }); + recaptcha.execute(); + }); + } - reset = () => { - const recaptcha = this.ref.current - if (recaptcha) { - recaptcha.reset() - } else { - this.setState({ time: Date.now() }) - } - } + return this.state.value; + }; - handleValueChange = (event: React.ChangeEvent) => { - this.setState({ value: event.target.value }) - } + reset = () => { + const recaptcha = this.ref.current; + if (recaptcha) { + recaptcha.reset(); + } else { + this.setState({time: Date.now()}); + } + }; - handleVerify = (value: string) => { - emit(eventId, value) - this.setState({ value }) - } + handleValueChange = (event: React.ChangeEvent) => { + this.setState({value: event.target.value}); + }; - handleRefresh = () => { - this.setState({ time: Date.now() }) - } + handleVerify = (value: string) => { + emit(eventId, value); + this.setState({value}); + }; - render() { - return this.state.sitekey ? ( -
- -
- ) : ( -
-
- -
- {t('auth.captcha')} -
- ) - } + handleRefresh = () => { + this.setState({time: Date.now()}); + }; + + render() { + return this.state.sitekey ? ( +
+ +
+ ) : ( +
+
+ +
+ {t('auth.captcha')} +
+ ); + } } -export default Captcha +export default Captcha; diff --git a/resources/assets/src/components/DarkModeButton.tsx b/resources/assets/src/components/DarkModeButton.tsx index 6ba04d2f..a8313b24 100644 --- a/resources/assets/src/components/DarkModeButton.tsx +++ b/resources/assets/src/components/DarkModeButton.tsx @@ -1,27 +1,27 @@ -import React, { useState } from 'react' -import * as fetch from '@/scripts/net' +import {useState} from 'react'; +import * as fetch from '@/scripts/net'; -interface Props { - initMode: boolean -} +type Properties = { + readonly initMode: boolean; +}; -const DarkModeButton: React.FC = ({ initMode }) => { - const [darkMode, setDarkMode] = useState(initMode) +const DarkModeButton: React.FC = ({initMode}) => { + const [darkMode, setDarkMode] = useState(initMode); - const icon = darkMode ? 'moon' : 'sun' + const icon = darkMode ? 'moon' : 'sun'; - const handleClick = async () => { - setDarkMode((value) => !value) + const handleClick = async () => { + setDarkMode(value => !value); - await fetch.put('/user/dark-mode') - document.body.classList.toggle('dark-mode') - } + await fetch.put('/user/dark-mode'); + document.body.classList.toggle('dark-mode'); + }; - return ( - - - - ) -} + return ( + + + + ); +}; -export default DarkModeButton +export default DarkModeButton; diff --git a/resources/assets/src/components/EmailSuggestion.tsx b/resources/assets/src/components/EmailSuggestion.tsx index 9ecfe080..72f16c89 100644 --- a/resources/assets/src/components/EmailSuggestion.tsx +++ b/resources/assets/src/components/EmailSuggestion.tsx @@ -1,89 +1,90 @@ /** @jsxImportSource @emotion/react */ -import React, { useState, useEffect } from 'react' -import Autosuggest from 'react-autosuggest' -import { css } from '@emotion/react' -import { emit } from '@/scripts/event' -import { pointerCursor } from '@/styles/utils' + +import {useState, useEffect} from 'react'; +import Autosuggest from 'react-autosuggest'; +import {css} from '@emotion/react'; +import {emit} from '@/scripts/event'; +import {pointerCursor} from '@/styles/utils'; const styles = css` .dropdown-menu li { ${pointerCursor} } -` +`; -const domainNames = new Set(['qq.com', '163.com', 'gmail.com', 'hotmail.com']) +const domainNames = new Set(['qq.com', '163.com', 'gmail.com', 'hotmail.com']); -type Props = Omit, 'onChange'> & { - onChange(value: string): void -} +type Properties = Omit, 'onChange'> & { + onChange(value: string): void; +}; -const EmailSuggestion: React.FC = (props) => { - const [suggestions, setSuggestions] = useState([]) +const EmailSuggestion: React.FC = properties => { + const [suggestions, setSuggestions] = useState([]); - useEffect(() => { - emit('emailDomainsSuggestion', domainNames) - }, []) + useEffect(() => { + emit('emailDomainsSuggestion', domainNames); + }, []); - const handleSuggestionsFetchRequested: Autosuggest.SuggestionsFetchRequested = - ({ value }) => { - const segments = value.split('@') - setSuggestions([...domainNames].map((name) => `${segments[0]}@${name}`)) - } + const handleSuggestionsFetchRequested: Autosuggest.SuggestionsFetchRequested + = ({value}) => { + const segments = value.split('@'); + setSuggestions([...domainNames].map(name => `${segments[0]}@${name}`)); + }; - const handleSuggestionsClearRequested = () => { - setSuggestions([]) - } + const handleSuggestionsClearRequested = () => { + setSuggestions([]); + }; - const shouldRenderSuggestions = (value: string) => { - const isSelecting = [...domainNames].some((name) => - value.endsWith(`@${name}`), - ) + const shouldRenderSuggestions = (value: string) => { + const isSelecting = [...domainNames].some(name => + value.endsWith(`@${name}`), + ); - return isSelecting || (value.length > 0 && !value.includes('@')) - } + return isSelecting || (value.length > 0 && !value.includes('@')); + }; - const getSuggestionValue = (value: string) => value + const getSuggestionValue = (value: string) => value; - const renderSuggestion = (suggestion: string) => suggestion + const renderSuggestion = (suggestion: string) => suggestion; - const handleChange = (_: React.FormEvent, event: Autosuggest.ChangeEvent) => { - props.onChange(event.newValue) - } + const handleChange = (_: React.FormEvent, event: Autosuggest.ChangeEvent) => { + properties.onChange(event.newValue); + }; - const renderInputComponent = ( - props: Omit, 'onChange'>, - ) => ( -
- -
-
- -
-
-
- ) + const renderInputComponent = ( + properties_: Omit, 'onChange'>, + ) => ( +
+ +
+
+ +
+
+
+ ); - return ( -
- -
- ) -} + return ( +
+ 0 ? 'show' : ''}`, + suggestionHighlighted: 'active', + }} + onSuggestionsFetchRequested={handleSuggestionsFetchRequested} + onSuggestionsClearRequested={handleSuggestionsClearRequested} + /> +
+ ); +}; -export default EmailSuggestion +export default EmailSuggestion; diff --git a/resources/assets/src/components/FileInput.tsx b/resources/assets/src/components/FileInput.tsx index cf6f2164..29f4f8f6 100644 --- a/resources/assets/src/components/FileInput.tsx +++ b/resources/assets/src/components/FileInput.tsx @@ -1,53 +1,53 @@ /** @jsxImportSource @emotion/react */ -import { useRef } from 'react' -import { css } from '@emotion/react' -import { t } from '@/scripts/i18n' +import {useRef} from 'react'; +import {css} from '@emotion/react'; +import {t} from '@/scripts/i18n'; const hideRawBrowseButton = css` ::after { display: none; } -` +`; -interface Props { - file: File | null - accept?: string - onChange(event: React.ChangeEvent): void -} +type Properties = { + file: File | undefined; + accept?: string; + onChange(event: React.ChangeEvent): void; +}; -const FileInput: React.FC = (props) => { - const ref = useRef(null) +const FileInput: React.FC = properties => { + const reference = useRef(null); - const handleClick = () => { - ref.current!.click() - } + const handleClick = () => { + reference.current!.click(); + }; - return ( -
- -
-
- - -
-
- -
-
-
- ) -} + return ( +
+ +
+
+ + +
+
+ +
+
+
+ ); +}; -export default FileInput +export default FileInput; diff --git a/resources/assets/src/components/Loading.tsx b/resources/assets/src/components/Loading.tsx index 980ba246..ad647978 100644 --- a/resources/assets/src/components/Loading.tsx +++ b/resources/assets/src/components/Loading.tsx @@ -1,9 +1,9 @@ -import React from 'react' +function Loading() { + return ( +
+ +
+ ); +} -const Loading = () => ( -
- -
-) - -export default Loading +export default Loading; diff --git a/resources/assets/src/components/Modal.tsx b/resources/assets/src/components/Modal.tsx index e2c8246c..4ac8427f 100644 --- a/resources/assets/src/components/Modal.tsx +++ b/resources/assets/src/components/Modal.tsx @@ -1,165 +1,163 @@ -import React, { useState, useEffect, useRef } from 'react' -import $ from 'jquery' -import 'bootstrap' -import { t } from '../scripts/i18n' -import ModalHeader from './ModalHeader' -import ModalBody from './ModalBody' -import ModalFooter from './ModalFooter' -import type { Props as HeaderProps } from './ModalHeader' -import type { Props as BodyProps } from './ModalBody' -import type { Props as FooterProps } from './ModalFooter' +import {useState, useEffect, useRef} from 'react'; +import $ from 'jquery'; +import 'bootstrap'; +import {t} from '../scripts/i18n'; +import ModalHeader, {type Props as HeaderProperties} from './ModalHeader'; +import ModalBody, {type Props as BodyProperties} from './ModalBody'; +import ModalFooter, {type Props as FooterProperties} from './ModalFooter'; type BasicOptions = { - mode?: 'alert' | 'confirm' | 'prompt' - show?: boolean - input?: string - validator?(value: any): string | boolean | undefined - type?: string - showHeader?: boolean - center?: boolean - children?: React.ReactNode -} + readonly mode?: 'alert' | 'confirm' | 'prompt'; + readonly show?: boolean; + readonly input?: string; + validator?(value: any): string | boolean | undefined; + readonly type?: string; + readonly showHeader?: boolean; + readonly center?: boolean; + children?: React.ReactNode; +}; -export type ModalOptions = BasicOptions & HeaderProps & BodyProps & FooterProps +export type ModalOptions = BasicOptions & HeaderProperties & BodyProperties & FooterProperties; -type Props = { - id?: string - children?: React.ReactNode - footer?: React.ReactNode - onConfirm?(payload: { value: string }): void - onDismiss?(): void - onClose?(): void -} +type Properties = { + readonly id?: string; + readonly children?: React.ReactNode; + readonly footer?: React.ReactNode; + onConfirm?(payload: {value: string}): void; + onDismiss?(): void; + onClose?(): void; +}; export type ModalResult = { - value: string -} + value: string; +}; -const Modal: React.FC = (props) => { - const { - mode = 'confirm', - title = t('general.tip'), - text = '', - input = '', - placeholder = '', - inputType = 'text', - inputMode, - type = 'default', - showHeader = true, - center = false, - okButtonText = t('general.confirm'), - okButtonType = 'primary', - cancelButtonText = t('general.cancel'), - cancelButtonType = 'secondary', - flexFooter = false, - } = props +const Modal: React.FC = properties => { + const { + mode = 'confirm', + title = t('general.tip'), + text = '', + input = '', + placeholder = '', + inputType = 'text', + inputMode, + type = 'default', + showHeader = true, + center = false, + okButtonText = t('general.confirm'), + okButtonType = 'primary', + cancelButtonText = t('general.cancel'), + cancelButtonType = 'secondary', + flexFooter = false, + } = properties; - const [value, setValue] = useState(input) - const [valid, setValid] = useState(true) - const [validatorMessage, setValidatorMessage] = useState('') - const ref = useRef(null) + const [value, setValue] = useState(input); + const [valid, setValid] = useState(true); + const [validatorMessage, setValidatorMessage] = useState(''); + const reference = useRef(null); - const { show } = props + const {show, onClose} = properties; - useEffect(() => { - if (!show) { - return - } + useEffect(() => { + if (!show) { + return; + } - const onHidden = () => props.onClose?.() + const onHidden = () => { + onClose ? onClose() : void 0; + }; - const el = $(ref.current!) - el.on('hidden.bs.modal', onHidden) + const element = $(reference.current!); + element.on('hidden.bs.modal', onHidden); - return () => { - el.off('hidden.bs.modal', onHidden) - } - }, [show, props.onClose]) + return () => { + element.off('hidden.bs.modal', onHidden); + }; + }, [show, onClose]); - const handleInputChange = (event: React.ChangeEvent) => { - setValue(event.target.value) - } + const handleInputChange = (event: React.ChangeEvent) => { + setValue(event.target.value); + }; - const confirm = () => { - const { validator } = props - if (typeof validator === 'function') { - const result = validator(value) - if (typeof result === 'string') { - setValidatorMessage(result) - setValid(false) - return - } - } + const confirm = () => { + const {validator} = properties; + if (typeof validator === 'function') { + const result = validator(value); + if (typeof result === 'string') { + setValidatorMessage(result); + setValid(false); + return; + } + } - props.onConfirm?.({ value }) - $(ref.current!).modal('hide') + properties.onConfirm?.({value}); + $(reference.current!).modal('hide'); - // The "hidden.bs.modal" event can't be trigged automatically when testing. - /* istanbul ignore next */ - if (process.env.NODE_ENV === 'test') { - $(ref.current!).trigger('hidden.bs.modal') - } - } + // The "hidden.bs.modal" event can't be trigged automatically when testing. - const dismiss = () => { - props.onDismiss?.() - $(ref.current!).modal('hide') + if (process.env.NODE_ENV === 'test') { + $(reference.current!).trigger('hidden.bs.modal'); + } + }; - /* istanbul ignore next */ - if (process.env.NODE_ENV === 'test') { - $(ref.current!).trigger('hidden.bs.modal') - } - } + const dismiss = () => { + properties.onDismiss?.(); + $(reference.current!).modal('hide'); - useEffect(() => { - if (show) { - setTimeout(() => $(ref.current!).modal('show'), 50) - } - }, [show]) + if (process.env.NODE_ENV === 'test') { + $(reference.current!).trigger('hidden.bs.modal'); + } + }; - if (!show) { - return null - } + useEffect(() => { + if (show) { + setTimeout(() => $(reference.current!).modal('show'), 50); + } + }, [show]); - return ( - - ) -} + if (!show) { + return null; + } -export default Modal + return ( + + ); +}; + +export default Modal; diff --git a/resources/assets/src/components/ModalBody.tsx b/resources/assets/src/components/ModalBody.tsx index 9b38b366..18358ded 100644 --- a/resources/assets/src/components/ModalBody.tsx +++ b/resources/assets/src/components/ModalBody.tsx @@ -1,29 +1,24 @@ -import React from 'react' -import ModalContent from './ModalContent' -import ModalInput from './ModalInput' -import type { Props as ContentProps } from './ModalContent' -import type { - Props as InputProps, - InternalProps as InputInteralProps, -} from './ModalInput' -interface InternalProps { - showInput: boolean -} +import ModalContent, {type Props as ContentProperties} from './ModalContent'; +import ModalInput, { + type + Props as InputProperties, type + InternalProps as InputInteralProperties, +} from './ModalInput'; -export type Props = ContentProps & InputProps +type InternalProperties = { + readonly showInput: boolean; +}; -const ModalBody: React.FC = ( - props, -) => { - return ( -
- - {props.children} - - {props.showInput && } -
- ) -} +export type Props = ContentProperties & InputProperties; -export default ModalBody +const ModalBody: React.FC = properties => ( +
+ + {properties.children} + + {properties.showInput && } +
+); + +export default ModalBody; diff --git a/resources/assets/src/components/ModalContent.tsx b/resources/assets/src/components/ModalContent.tsx index 2d03d188..4b0eb2e9 100644 --- a/resources/assets/src/components/ModalContent.tsx +++ b/resources/assets/src/components/ModalContent.tsx @@ -1,26 +1,30 @@ -import React from 'react' -export interface Props { - text?: string - dangerousHTML?: string -} +export type Props = { + readonly text?: string; + readonly dangerousHTML?: string; + readonly children?: React.ReactNode; +}; -const ModalContent: React.FC = (props) => { - if (props.children) { - return <>{props.children} - } else if (props.text) { - return ( - <> - {props.text.split(/\r?\n/).map((line, i) => ( -

{line}

- ))} - - ) - } else if (props.dangerousHTML) { - return
- } +const ModalContent: React.FC = properties => { + if (properties.children) { + return <>{properties.children}; + } - return <> -} + if (properties.text) { + return ( + <> + {properties.text.split(/\r?\n/).map((line, i) => ( +

{line}

+ ))} + + ); + } -export default ModalContent + if (properties.dangerousHTML) { + return
; + } + + return <>; +}; + +export default ModalContent; diff --git a/resources/assets/src/components/ModalFooter.tsx b/resources/assets/src/components/ModalFooter.tsx index 63084f90..a2f4d3d3 100644 --- a/resources/assets/src/components/ModalFooter.tsx +++ b/resources/assets/src/components/ModalFooter.tsx @@ -1,49 +1,50 @@ -import React from 'react' -export interface Props { - flexFooter?: boolean - okButtonText?: string - okButtonType?: string - cancelButtonText?: string - cancelButtonType?: string -} +export type Props = { + readonly flexFooter?: boolean; + readonly okButtonText?: string; + readonly okButtonType?: string; + readonly cancelButtonText?: string; + readonly cancelButtonType?: string; + readonly children?: React.ReactNode; +}; -interface InternalProps { - showCancelButton: boolean - onConfirm?(): void - onDismiss?(): void -} +type InternalProperties = { + readonly showCancelButton: boolean; + onConfirm?(): void; + onDismiss?(): void; +}; -const ModalFooter: React.FC = (props) => { - const classes = ['modal-footer'] - if (props.flexFooter) { - classes.push('d-flex', 'justify-content-between') - } - const footerClass = classes.join(' ') +const ModalFooter: React.FC = properties => { + const classes = ['modal-footer']; + if (properties.flexFooter) { + classes.push('d-flex', 'justify-content-between'); + } - return props.children ? ( -
{props.children}
- ) : ( -
- {props.showCancelButton && ( - - )} - -
- ) -} + const footerClass = classes.join(' '); -export default ModalFooter + return properties.children ? ( +
{properties.children}
+ ) : ( +
+ {properties.showCancelButton && ( + + )} + +
+ ); +}; + +export default ModalFooter; diff --git a/resources/assets/src/components/ModalHeader.tsx b/resources/assets/src/components/ModalHeader.tsx index 761daf07..ac4e5e0c 100644 --- a/resources/assets/src/components/ModalHeader.tsx +++ b/resources/assets/src/components/ModalHeader.tsx @@ -1,28 +1,27 @@ -import React from 'react' -export interface Props { - title?: string -} +export type Props = { + readonly title?: string; +}; -interface InternalProps { - onDismiss?(): void - show?: boolean -} +type InternalProperties = { + onDismiss?(): void; + readonly show?: boolean; +}; -const ModalHeader: React.FC = (props) => - props.show ? ( -
-
{props.title}
- -
- ) : null +const ModalHeader: React.FC = properties => + properties.show ? ( +
+
{properties.title}
+ +
+ ) : null; -export default ModalHeader +export default ModalHeader; diff --git a/resources/assets/src/components/ModalInput.tsx b/resources/assets/src/components/ModalInput.tsx index d155f8c0..b5dba7dc 100644 --- a/resources/assets/src/components/ModalInput.tsx +++ b/resources/assets/src/components/ModalInput.tsx @@ -1,58 +1,57 @@ -import React, { HTMLAttributes } from 'react' -export interface Props { - inputType?: string - inputMode?: HTMLAttributes['inputMode'] - choices?: { text: string; value: string }[] - placeholder?: string -} +export type Props = { + readonly inputType?: string; + readonly inputMode?: React.HTMLAttributes['inputMode']; + readonly choices?: Array<{text: string; value: string}>; + readonly placeholder?: string; +}; -export interface InternalProps { - value?: string - invalid?: boolean - validatorMessage?: string - onChange?: React.ChangeEventHandler -} +export type InternalProps = { + readonly value?: string; + readonly invalid?: boolean; + readonly validatorMessage?: string; + readonly onChange?: React.ChangeEventHandler; +}; -const ModalInput: React.FC = (props) => ( - <> - {props.inputType === 'radios' && props.choices ? ( - <> - {props.choices.map((choice) => ( -
- - -
- ))} - - ) : ( -
- -
- )} - {props.invalid && ( -
- - {props.validatorMessage} -
- )} - -) +const ModalInput: React.FC = properties => ( + <> + {properties.inputType === 'radios' && properties.choices ? ( + <> + {properties.choices.map(choice => ( +
+ + +
+ ))} + + ) : ( +
+ +
+ )} + {properties.invalid && ( +
+ + {properties.validatorMessage} +
+ )} + +); -export default ModalInput +export default ModalInput; diff --git a/resources/assets/src/components/Pagination.tsx b/resources/assets/src/components/Pagination.tsx index 2888177c..eb92dc57 100644 --- a/resources/assets/src/components/Pagination.tsx +++ b/resources/assets/src/components/Pagination.tsx @@ -1,124 +1,124 @@ -import React from 'react' -import { t } from '@/scripts/i18n' -import PaginationItem from './PaginationItem' -interface Props { - page: number - totalPages: number - onChange(page: number): void | Promise -} +import PaginationItem from './PaginationItem'; +import {t} from '@/scripts/i18n'; + +type Properties = { + readonly page: number; + readonly totalPages: number; + onChange(page: number): void | Promise; +}; const labels = { - prev: '‹', - next: '›', -} + prev: '‹', + next: '›', +}; -const Pagination: React.FC = (props) => { - const { page, totalPages, onChange } = props +const Pagination: React.FC = properties => { + const {page, totalPages, onChange} = properties; - if (totalPages < 1) { - return null - } + if (totalPages < 1) { + return null; + } - return ( -
    - onChange(page - 1)} - > - {labels.prev} - - {t('vendor.datatable.prev')} - - - {totalPages < 8 ? ( - Array.from({ length: totalPages }).map((_, i) => ( - onChange(i + 1)} - > - {i + 1} - - )) - ) : ( - <> - {page < 4 ? ( - [1, 2, 3, 4].map((n) => ( - onChange(n)} - > - {n} - - )) - ) : ( - onChange(1)} - > - 1 - - )} - - ... - - {page > 3 && page < totalPages - 2 && ( - <> - {[page - 1, page, page + 1].map((n) => ( - onChange(n)} - > - {n} - - ))} - - ... - - - )} - {totalPages - page < 3 ? ( - [totalPages - 3, totalPages - 2, totalPages - 1, totalPages].map( - (n) => ( - onChange(n)} - > - {n} - - ), - ) - ) : ( - onChange(totalPages)} - > - {totalPages} - - )} - - )} - onChange(page + 1)} - > - - {t('vendor.datatable.next')} - - {labels.next} - -
- ) -} + return ( +
    + onChange(page - 1)} + > + {labels.prev} + + {t('vendor.datatable.prev')} + + + {totalPages < 8 ? ( + Array.from({length: totalPages}).map((_, i) => ( + onChange(i + 1)} + > + {i + 1} + + )) + ) : ( + <> + {page < 4 ? ( + [1, 2, 3, 4].map(n => ( + onChange(n)} + > + {n} + + )) + ) : ( + onChange(1)} + > + 1 + + )} + + ... + + {page > 3 && page < totalPages - 2 && ( + <> + {[page - 1, page, page + 1].map(n => ( + onChange(n)} + > + {n} + + ))} + + ... + + + )} + {totalPages - page < 3 ? ( + [totalPages - 3, totalPages - 2, totalPages - 1, totalPages].map( + n => ( + onChange(n)} + > + {n} + + ), + ) + ) : ( + onChange(totalPages)} + > + {totalPages} + + )} + + )} + onChange(page + 1)} + > + + {t('vendor.datatable.next')} + + {labels.next} + +
+ ); +}; -export default Pagination +export default Pagination; diff --git a/resources/assets/src/components/PaginationItem.tsx b/resources/assets/src/components/PaginationItem.tsx index fe75ec1c..6bf43253 100644 --- a/resources/assets/src/components/PaginationItem.tsx +++ b/resources/assets/src/components/PaginationItem.tsx @@ -1,39 +1,41 @@ -import React from 'react' -interface Props { - disabled?: boolean - active?: boolean - title?: string - className?: string - onClick?(): void -} +type Properties = { + readonly disabled?: boolean; + readonly active?: boolean; + readonly title?: string; + readonly className?: string; + onClick?(): void; + readonly children?: React.ReactNode; +}; -const PaginationItem: React.FC = (props) => { - const classes = ['page-item'] - if (props.active) { - classes.push('active') - } - if (props.disabled) { - classes.push('disabled') - } - if (props.className) { - classes.push(props.className) - } +const PaginationItem: React.FC = properties => { + const classes = ['page-item']; + if (properties.active) { + classes.push('active'); + } - const handleClick = (event: React.MouseEvent) => { - event.preventDefault() - if (!props.disabled && props.onClick) { - props.onClick() - } - } + if (properties.disabled) { + classes.push('disabled'); + } - return ( -
  • - - {props.children} - -
  • - ) -} + if (properties.className) { + classes.push(properties.className); + } -export default PaginationItem + const handleClick = (event: React.MouseEvent) => { + event.preventDefault(); + if (!properties.disabled && properties.onClick) { + properties.onClick(); + } + }; + + return ( +
  • + + {properties.children} + +
  • + ); +}; + +export default PaginationItem; diff --git a/resources/assets/src/components/Toast.tsx b/resources/assets/src/components/Toast.tsx index a20a9649..f70f070f 100644 --- a/resources/assets/src/components/Toast.tsx +++ b/resources/assets/src/components/Toast.tsx @@ -1,21 +1,23 @@ /** @jsxImportSource @emotion/react */ -import React, { useState, useEffect } from 'react' -import { css } from '@emotion/react' -export type ToastType = 'success' | 'info' | 'warning' | 'error' +import {useState, useEffect} from 'react'; +import {css} from '@emotion/react'; -interface Props { - type: ToastType - distance: number - onClose(): void | Promise -} +export type ToastType = 'success' | 'info' | 'warning' | 'error'; + +type Properties = { + readonly type: ToastType; + readonly distance: number; + onClose(): void | Promise; + readonly children: React.ReactNode; +}; const icons = new Map([ - ['success', 'check'], - ['info', 'info'], - ['warning', 'exclamation-triangle'], - ['error', 'times-circle'], -]) + ['success', 'check'], + ['info', 'info'], + ['warning', 'exclamation-triangle'], + ['error', 'times-circle'], +]); const wrapper = css` position: fixed; @@ -24,52 +26,54 @@ const wrapper = css` z-index: 1050; transition-property: top; transition-duration: 0.3s; -` +`; const shadow = css` box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1); -` +`; -const Toast: React.FC = (props) => { - const [show, setShow] = useState(false) +const Toast: React.FC = properties => { + const [show, setShow] = useState(false); - useEffect(() => { - const timer = setTimeout(() => setShow(true), 100) + useEffect(() => { + const timer = setTimeout(() => { + setShow(true); + }, 100); - return () => { - clearTimeout(timer) - } - }, [props.onClose]) + return () => { + clearTimeout(timer); + }; + }, [properties.onClose]); - const type = props.type === 'error' ? 'danger' : props.type + const type = properties.type === 'error' ? 'danger' : properties.type; - const classes = [ - `alert alert-${type}`, - 'd-flex justify-content-between', - 'fade', - ] - if (show) { - classes.push('show') - } + const classes = [ + `alert alert-${type}`, + 'd-flex justify-content-between', + 'fade', + ]; + if (show) { + classes.push('show'); + } - const role = type === 'success' || type === 'info' ? 'status' : 'alert' + const role = type === 'success' || type === 'info' ? 'status' : 'alert'; - return ( -
    -
    - - - - {props.children} - -
    -
    - ) -} + return ( +
    +
    + + + + {properties.children} + +
    +
    + ); +}; -export default Toast +export default Toast; diff --git a/resources/assets/src/components/Viewer.tsx b/resources/assets/src/components/Viewer.tsx index 923fe480..46cf409b 100644 --- a/resources/assets/src/components/Viewer.tsx +++ b/resources/assets/src/components/Viewer.tsx @@ -1,38 +1,40 @@ /** @jsxImportSource @emotion/react */ -import React, { useState, useEffect, useRef } from 'react' -import { useMeasure } from 'react-use' -import { css } from '@emotion/react' -import styled from '@emotion/styled' -import * as skinview3d from 'skinview3d' -import { t } from '@/scripts/i18n' -import * as cssUtils from '@/styles/utils' -import * as breakpoints from '@/styles/breakpoints' -import SkinSteve from '../../../misc/textures/steve.png' -import bg1 from '../../../misc/backgrounds/1.webp' -import bg2 from '../../../misc/backgrounds/2.webp' -import bg3 from '../../../misc/backgrounds/3.webp' -import bg4 from '../../../misc/backgrounds/4.webp' -import bg5 from '../../../misc/backgrounds/5.webp' -import bg6 from '../../../misc/backgrounds/6.webp' -import bg7 from '../../../misc/backgrounds/7.webp' -const backgrounds = [bg1, bg2, bg3, bg4, bg5, bg6, bg7] -export const PICTURES_COUNT = backgrounds.length +import {useState, useEffect, useRef} from 'react'; +import {useMeasure} from 'react-use'; +import {css} from '@emotion/react'; +import styled from '@emotion/styled'; +import * as skinview3d from 'skinview3d'; +import SkinSteve from '../../../misc/textures/steve.png'; +import bg1 from '../../../misc/backgrounds/1.webp'; +import bg2 from '../../../misc/backgrounds/2.webp'; +import bg3 from '../../../misc/backgrounds/3.webp'; +import bg4 from '../../../misc/backgrounds/4.webp'; +import bg5 from '../../../misc/backgrounds/5.webp'; +import bg6 from '../../../misc/backgrounds/6.webp'; +import bg7 from '../../../misc/backgrounds/7.webp'; +import * as breakpoints from '@/styles/breakpoints'; +import * as cssUtils from '@/styles/utils'; +import {t} from '@/scripts/i18n'; -interface Props { - skin?: string - cape?: string - isAlex: boolean - showIndicator?: boolean - initPositionZ?: number -} +const backgrounds = [bg1, bg2, bg3, bg4, bg5, bg6, bg7]; +export const PICTURES_COUNT = backgrounds.length; + +type Properties = { + readonly skin?: string; + readonly cape?: string; + readonly children?: React.ReactNode; + readonly isAlex: boolean; + readonly showIndicator?: boolean; + readonly initPositionZ?: number; +}; const animationFactories = [ - () => new skinview3d.WalkingAnimation(), - () => new skinview3d.RunningAnimation(), - () => new skinview3d.FlyingAnimation(), - () => new skinview3d.IdleAnimation(), -] + () => new skinview3d.WalkingAnimation(), + () => new skinview3d.RunningAnimation(), + () => new skinview3d.FlyingAnimation(), + () => new skinview3d.IdleAnimation(), +]; const ActionButton = styled.i` display: inline; @@ -41,7 +43,7 @@ const ActionButton = styled.i` color: #555; cursor: pointer; } -` +`; const cssViewer = css` flex: 1 1 auto; @@ -56,251 +58,256 @@ const cssViewer = css` display: flex; justify-content: center; } -` +`; -const Viewer: React.FC = (props) => { - const { initPositionZ = 70 } = props +const Viewer: React.FC = properties => { + const {initPositionZ = 70} = properties; - const viewRef: React.MutableRefObject = useRef(null!) - const containerRef = useRef(null) + const viewReference: React.MutableRefObject = useRef(null!); + const containerReference = useRef(null); - const [paused, setPaused] = useState(false) - const [animation, setAnimation] = useState(0) - const [bgPicture, setBgPicture] = useState(-1) + const [paused, setPaused] = useState(false); + const [animation, setAnimation] = useState(0); + const [bgPicture, setBgPicture] = useState(-1); - const indicator = (() => { - const { skin, cape } = props - if (skin && cape) { - return `${t('general.skin')} & ${t('general.cape')}` - } else if (skin) { - return t('general.skin') - } else if (cape) { - return t('general.cape') - } - return '' - })() + const indicator = (() => { + const {skin, cape} = properties; + if (skin && cape) { + return `${t('general.skin')} & ${t('general.cape')}`; + } - useEffect(() => { - const container = containerRef.current! - const viewer = new skinview3d.SkinViewer({ - canvas: container, - width: container.clientWidth, - height: container.clientHeight, - skin: props.skin || SkinSteve, - cape: props.cape || undefined, - model: props.isAlex ? 'slim' : 'default', - zoom: initPositionZ / 100, - }) - viewer.autoRotate = true + if (skin) { + return t('general.skin'); + } - if (document.body.classList.contains('dark-mode')) { - viewer.background = '#6c757d' - } + if (cape) { + return t('general.cape'); + } - viewRef.current = viewer + return ''; + })(); - return () => { - viewer.dispose() - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + useEffect(() => { + const container = containerReference.current!; + const viewer = new skinview3d.SkinViewer({ + canvas: container, + width: container.clientWidth, + height: container.clientHeight, + skin: properties.skin || SkinSteve, + cape: properties.cape || undefined, + model: properties.isAlex ? 'slim' : 'default', + zoom: initPositionZ / 100, + }); + viewer.autoRotate = true; - const [containerWrapperRef, containerMeasure] = useMeasure() - useEffect(() => { - viewRef.current.setSize(containerMeasure.width, containerMeasure.height) - }) + if (document.body.classList.contains('dark-mode')) { + viewer.background = '#6c757d'; + } - useEffect(() => { - const viewer = viewRef.current - viewer.loadSkin(props.skin || SkinSteve, { - model: props.isAlex ? 'slim' : 'default', - }) - }, [props.skin, props.isAlex]) + viewReference.current = viewer; - useEffect(() => { - const viewer = viewRef.current - if (props.cape) { - viewer.loadCape(props.cape) - } else { - viewer.resetCape() - } - }, [props.cape]) + return () => { + viewer.dispose(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - useEffect(() => { - const viewer = viewRef.current - const factory = animationFactories[animation] - if (factory === undefined) { - viewer.animation = null - } else { - const newAnimation = factory() - newAnimation.paused = paused // Perseve `paused` state - viewer.animation = newAnimation - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [animation]) + const [containerWrapperReference, containerMeasure] = useMeasure(); + useEffect(() => { + viewReference.current.setSize(containerMeasure.width, containerMeasure.height); + }); - useEffect(() => { - const currentAnimation = viewRef.current.animation - if (currentAnimation !== null) { - currentAnimation.paused = paused - } - }, [paused]) + useEffect(() => { + const viewer = viewReference.current; + viewer.loadSkin(properties.skin || SkinSteve, { + model: properties.isAlex ? 'slim' : 'default', + }); + }, [properties.skin, properties.isAlex]); - useEffect(() => { - const viewer = viewRef.current - const backgroundUrl = backgrounds[bgPicture] - if (backgroundUrl === undefined) { - viewer.background = null - } else { - viewer.loadBackground(backgroundUrl) - } - }, [bgPicture]) + useEffect(() => { + const viewer = viewReference.current; + if (properties.cape) { + viewer.loadCape(properties.cape); + } else { + viewer.resetCape(); + } + }, [properties.cape]); - const togglePause = () => { - setPaused((paused) => { - if (paused) { - return false - } else { - viewRef.current.autoRotate = false - return true - } - }) - } + useEffect(() => { + const viewer = viewReference.current; + const factory = animationFactories[animation]; + if (factory === undefined) { + viewer.animation = null; + } else { + const newAnimation = factory(); + newAnimation.paused = paused; // Perseve `paused` state + viewer.animation = newAnimation; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [animation]); - const toggleAnimation = () => { - setAnimation((index) => (index + 1) % animationFactories.length) - setPaused(false) - } + useEffect(() => { + const currentAnimation = viewReference.current.animation; + if (currentAnimation !== null) { + currentAnimation.paused = paused; + } + }, [paused]); - const toggleRotate = () => { - const viewer = viewRef.current - viewer.autoRotate = !viewer.autoRotate - } + useEffect(() => { + const viewer = viewReference.current; + const backgroundUrl = backgrounds[bgPicture]; + if (backgroundUrl === undefined) { + viewer.background = null; + } else { + viewer.loadBackground(backgroundUrl); + } + }, [bgPicture]); - const toggleBackEquippment = () => { - const player = viewRef.current.playerObject - if (player.backEquipment === 'cape') { - player.backEquipment = 'elytra' - } else { - player.backEquipment = 'cape' - } - } + const togglePause = () => { + setPaused(paused => { + if (paused) { + return false; + } - const setWhite = () => { - viewRef.current.background = '#fff' - } - const setGray = () => { - viewRef.current.background = '#6c757d' - } - const setBlack = () => { - viewRef.current.background = '#000' - } - const setPrevPicture = () => { - setBgPicture((index) => { - if (bgPicture <= 0) { - return PICTURES_COUNT - 1 - } else { - return index - 1 - } - }) - } - const setNextPicture = () => { - setBgPicture((index) => { - if (bgPicture >= PICTURES_COUNT - 1) { - return 0 - } else { - return index + 1 - } - }) - } + viewReference.current.autoRotate = false; + return true; + }); + }; - return ( -
    -
    -
    -

    - {t('general.texturePreview')} - {props.showIndicator && ( - {indicator} - )} -

    -
    - - - - -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    - {props.children} -
    -
    - ) -} + const toggleAnimation = () => { + setAnimation(index => (index + 1) % animationFactories.length); + setPaused(false); + }; -export default Viewer + const toggleRotate = () => { + const viewer = viewReference.current; + viewer.autoRotate = !viewer.autoRotate; + }; + + const toggleBackEquippment = () => { + const player = viewReference.current.playerObject; + player.backEquipment = player.backEquipment === 'cape' ? 'elytra' : 'cape'; + }; + + const setWhite = () => { + viewReference.current.background = '#fff'; + }; + + const setGray = () => { + viewReference.current.background = '#6c757d'; + }; + + const setBlack = () => { + viewReference.current.background = '#000'; + }; + + const setPreviousPicture = () => { + setBgPicture(index => { + if (bgPicture <= 0) { + return PICTURES_COUNT - 1; + } + + return index - 1; + }); + }; + + const setNextPicture = () => { + setBgPicture(index => { + if (bgPicture >= PICTURES_COUNT - 1) { + return 0; + } + + return index + 1; + }); + }; + + return ( +
    +
    +
    +

    + {t('general.texturePreview')} + {properties.showIndicator && ( + {indicator} + )} +

    +
    + + + + +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    + +
    +
    + +
    +
    + {properties.children} +
    +
    + ); +}; + +export default Viewer; diff --git a/resources/assets/src/components/ViewerSkeleton.tsx b/resources/assets/src/components/ViewerSkeleton.tsx index 3dfd1e67..603f52eb 100644 --- a/resources/assets/src/components/ViewerSkeleton.tsx +++ b/resources/assets/src/components/ViewerSkeleton.tsx @@ -1,17 +1,17 @@ -import React from 'react' -import { t } from '@/scripts/i18n' +import {t} from '@/scripts/i18n'; -const ViewerSkeleton: React.FC = () => ( -
    -
    -
    -

    - {t('general.texturePreview')} -

    -
    -
    -
    -
    -) +export default function ViewerSkeleton() { + return ( +
    +
    +
    +

    + {t('general.texturePreview')} +

    +
    +
    +
    +
    + ); +} -export default ViewerSkeleton diff --git a/resources/assets/src/styles/home.css b/resources/assets/src/home.css similarity index 97% rename from resources/assets/src/styles/home.css rename to resources/assets/src/home.css index df11bb78..e7b3acd1 100644 --- a/resources/assets/src/styles/home.css +++ b/resources/assets/src/home.css @@ -1,5 +1,5 @@ -@import '../fonts/minecraft.css'; -@import './avatar.css'; +@import '@/fonts/minecraft.css'; +@import '@/styles/avatar.css'; body { font-size: 16px; diff --git a/resources/assets/src/index.tsx b/resources/assets/src/index.tsx index 370fc0ba..5e339730 100644 --- a/resources/assets/src/index.tsx +++ b/resources/assets/src/index.tsx @@ -1,37 +1,38 @@ -import * as React from 'react' -import * as ReactDOM from 'react-dom' -import $ from 'jquery' -import './scripts/app' -import routes from './scripts/route' +import React from 'react'; +import ReactDOM from 'react-dom'; +import {createRoot} from 'react-dom/client'; +import $ from 'jquery'; +// eslint-disable-next-line import/no-unassigned-import +import './scripts/app'; +import routes from './scripts/route'; -Object.assign(window, { React, ReactDOM, $ }) +// eslint-disable-next-line @typescript-eslint/naming-convention +Object.assign(window, {React, ReactDOM, $}); -const entry = document.querySelector('[href="#launch-cli"]') -entry?.addEventListener('click', async () => { - const { launch } = await import('./scripts/cli') - launch() -}) - -const route = routes.find((route) => - new RegExp(`^${route.path}$`, 'i').test(blessing.route), -) +const route = routes.find(route => + new RegExp(`^${route.path}$`, 'i').test(blessing.route), +); if (route) { - if (route.module) { - Promise.all(route.module.map((m) => m())) - } - if (route.react) { - const Component = React.lazy( - route.react as () => Promise<{ default: React.ComponentType }>, - ) - const Root = () => ( - - - - - - ) - const c = - typeof route.el === 'string' ? document.querySelector(route.el) : route.el - ReactDOM.render(, c) - } + if (route.module) { + void Promise.all(route.module.map(async m => m())); + } + + if (route.react) { + const Component = React.lazy( + route.react as () => Promise<{default: React.ComponentType}>, + ); + + const container = typeof route.el === 'string' + ? document.querySelector(route.el) + : null; + + const root = createRoot(container!); + root.render( + + + + + , + ); + } } diff --git a/resources/assets/src/scripts/app.ts b/resources/assets/src/scripts/app.ts index 8476510e..a3c44248 100644 --- a/resources/assets/src/scripts/app.ts +++ b/resources/assets/src/scripts/app.ts @@ -1,14 +1,14 @@ -import './init' // must be first -import 'admin-lte' -import './extra' -import './i18n' -import './net' -import './event' -import './notification' -import './emailVerification' -import './logout' -import './darkMode' +/* eslint-disable import/no-unassigned-import */ +import 'admin-lte'; +import './extra'; +import './i18n'; +import './net'; +import './event'; +import './notification'; +import './emailVerification'; +import './logout'; +import './darkMode'; window.addEventListener('load', () => { - $('[data-toggle="tooltip"]').tooltip() -}) + $('[data-toggle="tooltip"]').tooltip(); +}); diff --git a/resources/assets/src/scripts/cli.tsx b/resources/assets/src/scripts/cli.tsx deleted file mode 100644 index 23f06054..00000000 --- a/resources/assets/src/scripts/cli.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import React, { useEffect, useRef } from 'react' -import ReactDOM from 'react-dom' -import styled from '@emotion/styled' -import { Terminal } from 'xterm' -import { FitAddon } from 'xterm-addon-fit' -import { Shell, Stdio } from 'blessing-skin-shell' -import 'xterm/css/xterm.css' -import Draggable from 'react-draggable' -import * as event from './event' -import AptCommand from './cli/AptCommand' -import ClosetCommand from './cli/ClosetCommand' -import DnfCommand from './cli/DnfCommand' -import PacmanCommand from './cli/PacmanCommand' -import RmCommand from './cli/RmCommand' -import * as breakpoints from '@/styles/breakpoints' - -let launched = false - -const TerminalContainer = styled.div` - z-index: 1040; - position: fixed; - bottom: 7vh; - user-select: none; - - .card-body { - background-color: #000; - } - - ${breakpoints.greaterThan(breakpoints.Breakpoint.xl)} { - left: 25vw; - width: 50vw; - height: 50vh; - } - - ${breakpoints.between(breakpoints.Breakpoint.md, breakpoints.Breakpoint.xl)} { - left: 5vw; - width: 90vw; - height: 40vh; - } - - ${breakpoints.lessThan(breakpoints.Breakpoint.md)} { - left: 1vw; - width: 98vw; - height: 35vh; - } -` - -const TerminalWindow: React.FC<{ onClose(): void }> = (props) => { - const mount = useRef(null) - - useEffect(() => { - const el = mount.current - if (!el) { - return - } - - const terminal = new Terminal() - const fitAddon = new FitAddon() - terminal.loadAddon(fitAddon) - terminal.setOption( - 'fontFamily', - 'Monaco, Consolas, "Roboto Mono", "Noto Sans", "Droid Sans Mono"', - ) - terminal.open(el) - fitAddon.fit() - - const programs = new Map void>() - programs.set('apt', AptCommand) - programs.set('closet', ClosetCommand) - programs.set('dnf', DnfCommand) - programs.set('pacman', PacmanCommand) - programs.set('rm', RmCommand) - event.emit('registerCLIPrograms', programs) - - const shell = new Shell(terminal) - programs.forEach((program, name) => { - shell.addExternal(name, program) - }) - - const originalLogger = console.log - console.log = (data: string, ...args: any[]) => { - const stack = new Error().stack - if (stack?.includes('outputHelp')) { - terminal.writeln(data.replace(/\n/g, '\r\n')) - } else { - originalLogger(data, ...args) - } - } - - const unbindData = terminal.onData((e) => shell.input(e)) - const unbindKey = terminal.onKey((e) => - event.emit('terminalKeyPress', e.key), - ) - launched = true - - return () => { - unbindData.dispose() - unbindKey.dispose() - shell.free() - fitAddon.dispose() - terminal.dispose() - console.log = originalLogger - launched = false - } - }, []) - - return ( - - -
    -
    -

    - Blessing Skin Shell -

    - -
    -
    -
    -
    -
    - ) -} - -export function launch() { - if (launched) { - return - } - - const container = document.createElement('div') - document.body.appendChild(container) - - const handleClose = () => { - ReactDOM.unmountComponentAtNode(container) - container.remove() - } - - ReactDOM.render(, container) -} diff --git a/resources/assets/src/scripts/cli/.eslintrc.yml b/resources/assets/src/scripts/cli/.eslintrc.yml deleted file mode 100644 index 653e2937..00000000 --- a/resources/assets/src/scripts/cli/.eslintrc.yml +++ /dev/null @@ -1,2 +0,0 @@ -rules: - '@typescript-eslint/no-empty-function': off diff --git a/resources/assets/src/scripts/cli/AptCommand.ts b/resources/assets/src/scripts/cli/AptCommand.ts deleted file mode 100644 index 360030ca..00000000 --- a/resources/assets/src/scripts/cli/AptCommand.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Stdio } from 'blessing-skin-shell' -import cac from 'cac' -import { install, remove } from './pluginManager' - -export default async function apt(stdio: Stdio, args: string[]) { - const program = cac('apt') - program.help() - - program - .command('install ', 'install a new plugin') - .action((plugin: string) => install(plugin, stdio)) - - program - .command('upgrade ', 'upgrade an existed plugin') - .action((plugin: string) => install(plugin, stdio)) - - program - .command('remove ', 'remove a plugin') - .action((plugin: string) => remove(plugin, stdio)) - - program.parse(['', ''].concat(args), { run: false }) - await program.runMatchedCommand() -} diff --git a/resources/assets/src/scripts/cli/ClosetCommand.ts b/resources/assets/src/scripts/cli/ClosetCommand.ts deleted file mode 100644 index 0d5ebe2d..00000000 --- a/resources/assets/src/scripts/cli/ClosetCommand.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { Stdio } from 'blessing-skin-shell' -import cac from 'cac' -import * as fetch from '../net' -import type { User, Texture } from '../types' - -type Response = fetch.ResponseBody<{ user: User; texture: Texture }> - -export default async function closet(stdio: Stdio, args: string[]) { - const program = cac('closet') - program.help() - - program - .command('add ', "add texture to someone's closet") - .action(async (uid: string, tid: string) => { - const { code, data } = await fetch.post( - `/admin/closet/${uid}`, - { tid }, - ) - if (code === 0) { - const { texture, user } = data - stdio.println( - `Texture "${texture.name}" was added to user ${user.nickname}'s closet.`, - ) - } else { - stdio.println('Error occurred.') - } - }) - program - .command('remove ', "remove texture from someone's closet") - .action(async (uid: string, tid: string) => { - const { code, data } = await fetch.del(`/admin/closet/${uid}`, { - tid, - }) - if (code === 0) { - const { texture, user } = data - stdio.println( - `Texture "${texture.name}" was removed from user ${user.nickname}'s closet.`, - ) - } else { - stdio.println('Error occurred.') - } - }) - - program.parse(['', ''].concat(args), { run: false }) - await program.runMatchedCommand() -} diff --git a/resources/assets/src/scripts/cli/DnfCommand.ts b/resources/assets/src/scripts/cli/DnfCommand.ts deleted file mode 100644 index 8fb7feb7..00000000 --- a/resources/assets/src/scripts/cli/DnfCommand.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Stdio } from 'blessing-skin-shell' -import cac from 'cac' -import { install, remove } from './pluginManager' - -export default async function dnf(stdio: Stdio, args: string[]) { - const program = cac('dnf') - program.help() - - program - .command('install ', 'install a new plugin') - .action((plugin: string) => install(plugin, stdio)) - - program - .command('upgrade ', 'upgrade an existed plugin') - .action((plugin: string) => install(plugin, stdio)) - - program - .command('remove ', 'remove a plugin') - .action((plugin: string) => remove(plugin, stdio)) - - program.parse(['', ''].concat(args), { run: false }) - await program.runMatchedCommand() -} diff --git a/resources/assets/src/scripts/cli/PacmanCommand.ts b/resources/assets/src/scripts/cli/PacmanCommand.ts deleted file mode 100644 index e5b73e9b..00000000 --- a/resources/assets/src/scripts/cli/PacmanCommand.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Stdio } from 'blessing-skin-shell' -import cac from 'cac' -import { install, remove } from './pluginManager' - -type Options = { - sync?: string - remove?: string -} - -export default async function pacman(stdio: Stdio, args: string[]) { - if (args.length === 0) { - stdio.println('error: no operation specified (use -h for help)') - return - } - - const program = cac('pacman') - program.help() - - program.option('-S, --sync ', 'install or upgrade a plugin') - program.option('-R, --remove ', 'remove a plugin') - - const { options } = program.parse(['', ''].concat(args), { run: false }) - - const opts: Options = options - /* istanbul ignore else */ - if (opts.sync) { - await install(opts.sync, stdio) - } else if (opts.remove) { - await remove(opts.remove, stdio) - } -} diff --git a/resources/assets/src/scripts/cli/RmCommand.ts b/resources/assets/src/scripts/cli/RmCommand.ts deleted file mode 100644 index 75176359..00000000 --- a/resources/assets/src/scripts/cli/RmCommand.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { Stdio } from 'blessing-skin-shell' -import cac from 'cac' -import * as fetch from '../net' - -type Options = { - force?: boolean - recursive?: boolean - help?: boolean -} - -export default async function rm(stdio: Stdio, args: string[]) { - const program = cac('rm') - program.help() - - program - .command('') - .option( - '-f, --force', - 'ignore nonexistent files and arguments, never prompt', - ) - .option( - '-r, --recursive', - 'remove directories and their contents recursively', - ) - .option('--no-preserve-root', "do not treat '/' specially") - - const opts: Options = program.parse(['', ''].concat(args), { - run: false, - }).options - const path = program.args[0] - - if (!path && !opts.help) { - stdio.println('rm: missing operand') - stdio.println("Try 'rm --help' for more information.") - } - - if (opts.force && opts.recursive && path?.startsWith('/')) { - await fetch.post('/admin/resource?clear-cache') - } -} diff --git a/resources/assets/src/scripts/cli/Spinner.ts b/resources/assets/src/scripts/cli/Spinner.ts deleted file mode 100644 index 34c8b635..00000000 --- a/resources/assets/src/scripts/cli/Spinner.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { Stdio } from 'blessing-skin-shell' -import spinners from 'cli-spinners/spinners.json' - -const { dots } = spinners - -export class Spinner { - private timerId = 0 - private index = 0 - - constructor(private stdio: Stdio) {} - - start(message = '') { - this.timerId = window.setInterval(() => { - this.index += 1 - this.index %= dots.frames.length - - this.stdio.reset() - this.stdio.print(`${dots.frames[this.index]} ${message}`) - }, dots.interval) - } - - stop(message = '') { - clearInterval(this.timerId) - this.stdio.reset() - this.stdio.println(message) - this.stdio.print('\u001B[?25h') - } -} diff --git a/resources/assets/src/scripts/cli/configureStdio.ts b/resources/assets/src/scripts/cli/configureStdio.ts deleted file mode 100644 index 9f6d6ff1..00000000 --- a/resources/assets/src/scripts/cli/configureStdio.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { Stdio } from 'blessing-skin-shell' -import * as event from '../event' - -/* istanbul ignore next */ -export function hackStdin() { - if (process.env.NODE_ENV === 'test') { - return process.stdin - } - - // @ts-ignore - return { - on(eventName: string, handler: (str: string, key: string) => void) { - if (eventName === 'keypress') { - this._off = event.on('terminalKeyPress', (key: string) => { - handler(key, key) - }) - } - }, - isTTY: true, - setRawMode() {}, - removeListener() { - this._off() - }, - } as NodeJS.ReadStream & { _off(): void } -} - -/* istanbul ignore next */ -export function hackStdout(stdio: Stdio) { - return { - write(msg: string) { - stdio.print(msg.replace(/\n/g, '\r\n')) - return true - }, - } as NodeJS.WriteStream -} diff --git a/resources/assets/src/scripts/cli/pluginManager.ts b/resources/assets/src/scripts/cli/pluginManager.ts deleted file mode 100644 index 68f60706..00000000 --- a/resources/assets/src/scripts/cli/pluginManager.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { Stdio } from 'blessing-skin-shell' -import prompts from 'prompts' -import * as fetch from '../net' -import { hackStdout, hackStdin } from './configureStdio' -import { Spinner } from './Spinner' - -export async function install(plugin: string, stdio: Stdio) { - const spinner = new Spinner(stdio) - spinner.start('Installing plugin...') - - const { message, data } = await fetch.post< - fetch.ResponseBody<{ reason?: string[] } | undefined> - >('/admin/plugins/market/download', { name: plugin }) - - spinner.stop(` ${message}`) - const reasons = data?.reason - if (reasons) { - stdio.println(reasons.map((reason) => `- ${reason}`).join('\r\n')) - } -} - -export async function remove(plugin: string, stdio: Stdio) { - const { confirm }: { confirm: boolean } = await prompts({ - name: 'confirm', - type: 'confirm', - message: `Are you sure to remove plugin "${plugin}"?`, - stdin: hackStdin(), - stdout: hackStdout(stdio), - }) - - if (!confirm) { - return - } - - const spinner = new Spinner(stdio) - spinner.start('Uninstalling plugin...') - - const { message } = await fetch.post( - '/admin/plugins/manage', - { action: 'delete', name: plugin }, - ) - spinner.stop(` ${message}`) -} diff --git a/resources/assets/src/scripts/cli/readline.ts b/resources/assets/src/scripts/cli/readline.ts deleted file mode 100644 index c8dbfa05..00000000 --- a/resources/assets/src/scripts/cli/readline.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function emitKeypressEvents() {} - -export function createInterface() { - return { - pause() {}, - resume() {}, - close() {}, - } -} diff --git a/resources/assets/src/scripts/darkMode.tsx b/resources/assets/src/scripts/darkMode.tsx index adafe9dd..9ba8e23b 100644 --- a/resources/assets/src/scripts/darkMode.tsx +++ b/resources/assets/src/scripts/darkMode.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' -import * as ReactDOM from 'react-dom' -import DarkModeButton from '@/components/DarkModeButton' +import ReactDOM from 'react-dom'; +import DarkModeButton from '@/components/DarkModeButton'; -const el = document.querySelector('#toggle-dark-mode') -if (el) { - const initMode = document.body.classList.contains('dark-mode') - ReactDOM.render(, el) +const element = document.querySelector('#toggle-dark-mode'); +if (element) { + const initMode = document.body.classList.contains('dark-mode'); + ReactDOM.render(, element); } diff --git a/resources/assets/src/scripts/emailVerification.tsx b/resources/assets/src/scripts/emailVerification.tsx index 2389fa59..99796c87 100644 --- a/resources/assets/src/scripts/emailVerification.tsx +++ b/resources/assets/src/scripts/emailVerification.tsx @@ -1,9 +1,8 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import EmailVerification from '@/views/widgets/EmailVerification' +import ReactDOM from 'react-dom'; +import EmailVerification from '@/views/widgets/EmailVerification'; -const container = document.querySelector('#email-verification') +const container = document.querySelector('#email-verification'); if (blessing.extra.unverified && container) { - ReactDOM.render(, container) + ReactDOM.render(, container); } diff --git a/resources/assets/src/scripts/event.ts b/resources/assets/src/scripts/event.ts index afc5b20c..fc66ffce 100644 --- a/resources/assets/src/scripts/event.ts +++ b/resources/assets/src/scripts/event.ts @@ -1,20 +1,21 @@ -/* eslint-disable @typescript-eslint/no-unnecessary-condition */ -const bus = new Map>() + +const bus = new Map>(); export function on(event: string | symbol, listener: CallableFunction) { - if (!bus.has(event)) { - bus.set(event, new Set()) - } - const listeners = bus.get(event)! - listeners.add(listener) + if (!bus.has(event)) { + bus.set(event, new Set()); + } - return () => { - listeners.delete(listener) - } + const listeners = bus.get(event)!; + listeners.add(listener); + + return () => { + listeners.delete(listener); + }; } export function emit(event: string | symbol, payload?: unknown) { - bus.get(event)?.forEach((listener) => listener(payload)) + bus.get(event)?.forEach(listener => listener(payload)); } -blessing.event = { on, emit } +blessing.event = {on, emit}; diff --git a/resources/assets/src/scripts/extra.ts b/resources/assets/src/scripts/extra.ts index 8a185eeb..a375b3f6 100644 --- a/resources/assets/src/scripts/extra.ts +++ b/resources/assets/src/scripts/extra.ts @@ -1,11 +1,11 @@ export function getExtraData(): Record { - const jsonElement = document.querySelector('#blessing-extra') - /* istanbul ignore next */ - if (jsonElement) { - return JSON.parse(jsonElement.textContent ?? '{}') - } else { - return {} - } + const jsonElement = document.querySelector('#blessing-extra'); + + if (jsonElement) { + return JSON.parse(jsonElement.textContent ?? '{}'); + } + + return {}; } -blessing.extra = getExtraData() +blessing.extra = getExtraData(); diff --git a/resources/assets/src/scripts/homePage.ts b/resources/assets/src/scripts/homePage.ts index b8dad290..e2475f9b 100644 --- a/resources/assets/src/scripts/homePage.ts +++ b/resources/assets/src/scripts/homePage.ts @@ -1,25 +1,24 @@ -import { getExtraData } from './extra' +import {getExtraData} from './extra'; export function scrollHander() { - const header = document.querySelector('.navbar') - /* istanbul ignore else */ - if (header) { - window.addEventListener('scroll', () => { - if (window.scrollY >= (window.innerHeight * 2) / 3) { - header.classList.remove('transparent') - } else { - header.classList.add('transparent') - } - }) - } + const header = document.querySelector('.navbar'); + /* istanbul ignore else */ + if (header) { + window.addEventListener('scroll', () => { + if (window.scrollY >= (window.innerHeight * 2) / 3) { + header.classList.remove('transparent'); + } else { + header.classList.add('transparent'); + } + }); + } } -/* istanbul ignore next */ if (process.env.NODE_ENV !== 'test') { - const { transparent_navbar } = getExtraData() as { - transparent_navbar: boolean - } - if (transparent_navbar) { - window.addEventListener('load', scrollHander) - } + const {transparent_navbar} = getExtraData() as { + transparent_navbar: boolean; + }; + if (transparent_navbar) { + window.addEventListener('load', scrollHander); + } } diff --git a/resources/assets/src/scripts/hooks/useBlessingExtra.ts b/resources/assets/src/scripts/hooks/useBlessingExtra.ts index aca79165..6ceedfaa 100644 --- a/resources/assets/src/scripts/hooks/useBlessingExtra.ts +++ b/resources/assets/src/scripts/hooks/useBlessingExtra.ts @@ -1,11 +1,11 @@ -import { useState, useEffect } from 'react' +import {useState, useEffect} from 'react'; export default function useBlessingExtra(key: string, defaultValue?: T): T { - const [value, setValue] = useState(defaultValue!) + const [value, setValue] = useState(defaultValue!); - useEffect(() => { - setValue(blessing.extra[key] as T) - }, [key]) + useEffect(() => { + setValue(blessing.extra[key] as T); + }, [key]); - return value + return value; } diff --git a/resources/assets/src/scripts/hooks/useEmitMounted.ts b/resources/assets/src/scripts/hooks/useEmitMounted.ts index 5c75f9f5..0430d248 100644 --- a/resources/assets/src/scripts/hooks/useEmitMounted.ts +++ b/resources/assets/src/scripts/hooks/useEmitMounted.ts @@ -1,8 +1,8 @@ -import { useEffect } from 'react' -import { emit } from '../event' +import {useEffect} from 'react'; +import {emit} from '../event'; export default function useEmitMounted() { - useEffect(() => { - emit('mounted') - }, []) + useEffect(() => { + emit('mounted'); + }, []); } diff --git a/resources/assets/src/scripts/hooks/useIsLargeScreen.ts b/resources/assets/src/scripts/hooks/useIsLargeScreen.ts index b7019916..b1bb750e 100644 --- a/resources/assets/src/scripts/hooks/useIsLargeScreen.ts +++ b/resources/assets/src/scripts/hooks/useIsLargeScreen.ts @@ -1,13 +1,13 @@ -import { useState, useEffect } from 'react' +import {useState, useEffect} from 'react'; export default function useIsLargeScreen() { - const [isLarge, setIsLarge] = useState(false) + const [isLarge, setIsLarge] = useState(false); - useEffect(() => { - if (window.innerWidth >= 992) { - setIsLarge(true) - } - }, []) + useEffect(() => { + if (window.innerWidth >= 992) { + setIsLarge(true); + } + }, []); - return isLarge + return isLarge; } diff --git a/resources/assets/src/scripts/hooks/useMount.ts b/resources/assets/src/scripts/hooks/useMount.ts index 0577fbd4..89ec3e68 100644 --- a/resources/assets/src/scripts/hooks/useMount.ts +++ b/resources/assets/src/scripts/hooks/useMount.ts @@ -1,20 +1,20 @@ -import { useEffect, useRef } from 'react' +import {useEffect, useRef} from 'react'; -export default function useMount(selector: string): HTMLElement | null { - const container = useRef(null) +export default function useMount(selector: string): HTMLElement | undefined { + const container = useRef(null); - useEffect(() => { - const mount = document.querySelector(selector)! - const div = document.createElement('div') - container.current = div + useEffect(() => { + const mount = document.querySelector(selector)!; + const div = document.createElement('div'); + container.current = div; - mount.appendChild(div) + mount.append(div); - return () => { - mount.removeChild(div) - container.current = null - } - }, [selector]) + return () => { + div.remove(); + container.current = null; + }; + }, [selector]); - return container.current + return container.current; } diff --git a/resources/assets/src/scripts/hooks/useTexture.ts b/resources/assets/src/scripts/hooks/useTexture.ts index 74843997..d85a1c54 100644 --- a/resources/assets/src/scripts/hooks/useTexture.ts +++ b/resources/assets/src/scripts/hooks/useTexture.ts @@ -1,26 +1,27 @@ -import { useState, useEffect } from 'react' -import * as fetch from '../net' -import { Texture, TextureType } from '../types' +import {useState, useEffect} from 'react'; +import * as fetch from '../net'; +import {type Texture, TextureType} from '../types'; export default function useTexture() { - const [tid, setTid] = useState(0) - const [url, setUrl] = useState('') - const [type, setType] = useState(TextureType.Steve) + const [tid, setTid] = useState(0); + const [url, setUrl] = useState(''); + const [type, setType] = useState(TextureType.Steve); - useEffect(() => { - if (tid <= 0) { - setUrl('') - return - } + useEffect(() => { + if (tid <= 0) { + setUrl(''); + return; + } - const getTexture = async () => { - const { hash, type } = await fetch.get(`/skinlib/info/${tid}`) + const getTexture = async () => { + const {hash, type} = await fetch.get(`/skinlib/info/${tid}`); - setUrl(`${blessing.base_url}/textures/${hash}`) - setType(type) - } - getTexture() - }, [tid]) + setUrl(`${blessing.base_url}/textures/${hash}`); + setType(type); + }; - return [{ url, type }, setTid] as const + getTexture(); + }, [tid]); + + return [{url, type}, setTid] as const; } diff --git a/resources/assets/src/scripts/hooks/useTween.ts b/resources/assets/src/scripts/hooks/useTween.ts index 60cab3c6..d6f85c6d 100644 --- a/resources/assets/src/scripts/hooks/useTween.ts +++ b/resources/assets/src/scripts/hooks/useTween.ts @@ -1,22 +1,22 @@ -import { useState, useEffect, useRef } from 'react' -import TWEEN from '@tweenjs/tween.js' +import {useState, useEffect, useRef} from 'react'; +import TWEEN from '@tweenjs/tween.js'; export default function useTween(initialValue: T) { - const [value, setValue] = useState(initialValue) - const ref = useRef(value) - const [dest, setDest] = useState(initialValue) + const [value, setValue] = useState(initialValue); + const reference = useRef(value); + const [destination, setDestination] = useState(initialValue); - useEffect(() => { - function animate() { - requestAnimationFrame(animate) - TWEEN.update() - setValue(ref.current) - } + useEffect(() => { + function animate() { + requestAnimationFrame(animate); + TWEEN.update(); + setValue(reference.current); + } - const tween = new TWEEN.Tween(ref) - tween.to({ current: dest }, 1000).start() - animate() - }, [dest]) + const tween = new TWEEN.Tween(reference); + tween.to({current: destination}, 1000).start(); + animate(); + }, [destination]); - return [value, setDest] as const + return [value, setDestination] as const; } diff --git a/resources/assets/src/scripts/i18n.ts b/resources/assets/src/scripts/i18n.ts index 17b36f40..c73f2068 100644 --- a/resources/assets/src/scripts/i18n.ts +++ b/resources/assets/src/scripts/i18n.ts @@ -1,31 +1,31 @@ -interface I18nTable { - [key: string]: string | I18nTable | undefined +type I18nTable = { + [key: string]: string | I18nTable | undefined; +}; + +export function t(key: string, parameters: Record = Object.create(null)): string { + const segments = key.split('.'); + let temporary = blessing.i18n as I18nTable | undefined; + let result = ''; + + for (const segment of segments) { + const middle = temporary?.[segment]; + if (!middle) { + return key; + } + + if (typeof middle === 'string') { + result = middle; + } else { + temporary = middle; + } + } + + for (const slot of Object.keys(parameters)) { + (result = result.replace(`:${slot}`, parameters[slot] ?? `%{${slot}}`)); + } + + return result; } -export function t(key: string, parameters = Object.create(null)): string { - const segments = key.split('.') - let temp = blessing.i18n as I18nTable | undefined - let result = '' - - for (const segment of segments) { - /* istanbul ignore next */ - const middle = temp?.[segment] - if (!middle) { - return key - } - if (typeof middle === 'string') { - result = middle - } else { - temp = middle - } - } - - Object.keys(parameters).forEach( - (slot) => (result = result.replace(`:${slot}`, parameters[slot])), - ) - - return result -} - -Object.assign(window, { trans: t }) -Object.assign(blessing, { t }) +Object.assign(window, {trans: t}); +Object.assign(blessing, {t}); diff --git a/resources/assets/src/scripts/init.ts b/resources/assets/src/scripts/init.ts index 715a7b7d..4b905714 100644 --- a/resources/assets/src/scripts/init.ts +++ b/resources/assets/src/scripts/init.ts @@ -1,12 +1,12 @@ -declare let __webpack_public_path__: string -declare const __blessing_public_path__: string +declare let __webpack_public_path__: string; +declare const __blessing_public_path__: string; if (process.env.NODE_ENV === 'development') { - __webpack_public_path__ = __blessing_public_path__ + __webpack_public_path__ = __blessing_public_path__; } else { - const link = document.querySelector('link#cdn-host') - const base = link?.href ?? blessing.base_url - __webpack_public_path__ = `${base}/app/` + const link = document.querySelector('link#cdn-host'); + const base = link?.href ?? blessing.base_url; + __webpack_public_path__ = `${base}/app/`; } -export {} +export {}; diff --git a/resources/assets/src/scripts/logout.ts b/resources/assets/src/scripts/logout.ts index 74cd4ca8..028d2bc0 100644 --- a/resources/assets/src/scripts/logout.ts +++ b/resources/assets/src/scripts/logout.ts @@ -1,22 +1,22 @@ -import { post } from './net' -import { t } from './i18n' -import { showModal } from './notify' -import urls from './urls' +import {post} from './net'; +import {t} from './i18n'; +import {showModal} from './notify'; +import urls from './urls'; export async function logout() { - try { - await showModal({ - text: t('general.confirmLogout'), - center: true, - }) - } catch { - return - } + try { + await showModal({ + text: t('general.confirmLogout'), + center: true, + }); + } catch { + return; + } - await post(urls.auth.logout()) - window.location.href = blessing.base_url + await post(urls.auth.logout()); + window.location.href = blessing.base_url; } -const button = document.querySelector('#logout-button') -/* istanbul ignore next */ -button?.addEventListener('click', logout) +const button = document.querySelector('#logout-button'); + +button?.addEventListener('click', logout); diff --git a/resources/assets/src/scripts/modal.tsx b/resources/assets/src/scripts/modal.tsx index 6d0747dc..64a12041 100644 --- a/resources/assets/src/scripts/modal.tsx +++ b/resources/assets/src/scripts/modal.tsx @@ -1,27 +1,26 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import Modal, { ModalOptions, ModalResult } from '../components/Modal' +import ReactDOM from 'react-dom'; +import Modal, {type ModalOptions, type ModalResult} from '../components/Modal'; -export function showModal(options: ModalOptions = {}): Promise { - return new Promise((resolve, reject) => { - const container = document.createElement('div') - document.body.appendChild(container) +export async function showModal(options: ModalOptions = {}): Promise { + return new Promise((resolve, reject) => { + const container = document.createElement('div'); + document.body.append(container); - const handleClose = () => { - ReactDOM.unmountComponentAtNode(container) - document.body.removeChild(container) - } + const handleClose = () => { + ReactDOM.unmountComponentAtNode(container); + container.remove(); + }; - ReactDOM.render( - , - container, - ) - }) + ReactDOM.render( + , + container, + ); + }); } diff --git a/resources/assets/src/scripts/net.ts b/resources/assets/src/scripts/net.ts index ede006d1..10315c64 100644 --- a/resources/assets/src/scripts/net.ts +++ b/resources/assets/src/scripts/net.ts @@ -1,159 +1,164 @@ -import { emit } from './event' -import { showModal } from './notify' -import { t } from './i18n' +import {emit} from './event'; +import {showModal} from './notify'; +import {t} from './i18n'; -export interface ResponseBody { - code: number - message: string - data: T extends null ? never : T -} +export type ResponseBody = { + code: number; + message: string; + data: T extends undefined ? never : T; +}; class HTTPError extends Error { - response: Response + response: Response; - constructor(message: string, response: Response) { - super(message) - this.response = response - } + constructor(message: string, response: Response) { + super(message); + this.response = response; + } } -const empty = Object.create(null) +const empty: Record = Object.create(null); export const init: RequestInit = { - credentials: 'same-origin', - headers: new Headers({ - Accept: 'application/json', - }), -} + credentials: 'same-origin', + headers: new Headers({ + Accept: 'application/json', + }), +}; function retrieveToken() { - const csrfField = document.querySelector( - 'meta[name="csrf-token"]', - ) - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - return csrfField?.content || '' + const csrfField = document.querySelector( + 'meta[name="csrf-token"]', + ); + + return csrfField?.content || ''; } export async function walkFetch(request: Request): Promise { - request.headers.set('X-CSRF-TOKEN', retrieveToken()) + request.headers.set('X-CSRF-TOKEN', retrieveToken()); - try { - const response = await fetch(request) - const cloned = response.clone() - const body = - response.headers.get('Content-Type') === 'application/json' - ? await response.json() - : await response.text() - if (response.ok) { - return body - } - let message: string = body.message + try { + const response = await fetch(request); + const cloned = response.clone(); + const body + = response.headers.get('Content-Type') === 'application/json' + ? await response.json() + : await response.text(); + if (response.ok) { + return body; + } - if (response.status === 422) { - // Process validation errors from Laravel. - const { - errors, - }: { - message: string - errors: { [field: string]: string[] } - } = body - return { - code: 1, - message: Object.keys(errors).map((field) => errors[field]![0])[0], - } - } else if (response.status === 419) { - return showModal({ - mode: 'alert', - text: t('general.csrf'), - }) - } else if (response.status === 403 || response.status === 400) { - return showModal({ - mode: 'alert', - text: message, - type: 'warning', - }) - } + let {message} = body; - if (body.exception && Array.isArray(body.trace)) { - const trace = (body.trace as Array<{ file: string; line: number }>) - .map((t, i) => `[${i + 1}] ${t.file}#L${t.line}`) - .join('
    ') - message = `${message}
    ${trace}
    ` - } + if (response.status === 422) { + // Process validation errors from Laravel. + const { + errors, + }: { + message: string; + errors: Record; + } = body; + return { + code: 1, + message: Object.keys(errors).map(field => errors[field][0])[0], + }; + } - throw new HTTPError(message || body, cloned) - } catch (error: any) { - emit('fetchError', error) - await showModal({ - mode: 'alert', - title: t('general.fatalError'), - dangerousHTML: error.message, - type: 'danger', - okButtonType: 'outline-light', - }) + if (response.status === 419) { + return await showModal({ + mode: 'alert', + text: t('general.csrf'), + }); + } - return { code: -1, message: t('general.fatalError') } - } + if (response.status === 403 || response.status === 400) { + return await showModal({ + mode: 'alert', + text: message, + type: 'warning', + }); + } + + if (body.exception && Array.isArray(body.trace)) { + const trace = (body.trace as Array<{file: string; line: number}>) + .map((t, i) => `[${i + 1}] ${t.file}#L${t.line}`) + .join('
    '); + message = `${message}
    ${trace}
    `; + } + + throw new HTTPError(message || String(body), cloned); + } catch (error: any) { + emit('fetchError', error); + await showModal({ + mode: 'alert', + title: t('general.fatalError'), + dangerousHTML: error.message, + type: 'danger', + okButtonType: 'outline-light', + }); + + return {code: -1, message: t('general.fatalError')}; + } } -export function get(url: string, params = empty): Promise { - emit('beforeFetch', { - method: 'GET', - url, - data: params, - }) +export async function get(url: string, parameters: Record | URLSearchParams = empty): Promise { + emit('beforeFetch', { + method: 'GET', + url, + data: parameters, + }); - const qs = new URLSearchParams(params).toString() + const qs = new URLSearchParams(parameters).toString(); - return walkFetch(new Request(`${blessing.base_url}${url}?${qs}`, init)) + return walkFetch(new Request(`${blessing.base_url}${url}?${qs}`, init)); } -function nonGet( - method: string, - url: string, - data?: FormData | Record, +async function nonGet( + method: string, + url: string, + data?: FormData | Record, ): Promise { - emit('beforeFetch', { - method: method.toUpperCase(), - url, - data, - }) + emit('beforeFetch', { + method: method.toUpperCase(), + url, + data, + }); - const request = new Request(`${blessing.base_url}${url}`, { - body: data instanceof FormData ? data : JSON.stringify(data), - method: method.toUpperCase(), - ...init, - }) - if (!(data instanceof FormData)) { - request.headers.set('Content-Type', 'application/json') - } + const request = new Request(`${blessing.base_url}${url}`, { + body: data instanceof FormData ? data : JSON.stringify(data), + method: method.toUpperCase(), + ...init, + }); + if (!(data instanceof FormData)) { + request.headers.set('Content-Type', 'application/json'); + } - return walkFetch(request) + return walkFetch(request); } -export function post( - url: string, - data?: FormData | Record, +export async function post( + url: string, + data?: FormData | Record, ): Promise { - return nonGet('POST', url, data) + return nonGet('POST', url, data); } -export function put( - url: string, - data?: FormData | Record, +export async function put( + url: string, + data?: FormData | Record, ): Promise { - return nonGet('PUT', url, data) + return nonGet('PUT', url, data); } -export function del( - url: string, - data?: FormData | Record, +export async function del( + url: string, + data?: FormData | Record, ): Promise { - return nonGet('DELETE', url, data) + return nonGet('DELETE', url, data); } blessing.fetch = { - get, - post, - put, - del, -} + get, + post, + put, + del, +}; diff --git a/resources/assets/src/scripts/notification.tsx b/resources/assets/src/scripts/notification.tsx index e807c4e1..13b83c58 100644 --- a/resources/assets/src/scripts/notification.tsx +++ b/resources/assets/src/scripts/notification.tsx @@ -1,8 +1,7 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import NotificationsList from '@/views/widgets/NotificationsList' +import ReactDOM from 'react-dom'; +import NotificationsList from '@/views/widgets/NotificationsList'; -const container = document.querySelector('[data-notifications]') +const container = document.querySelector('[data-notifications]'); if (container) { - ReactDOM.render(, container) + ReactDOM.render(, container); } diff --git a/resources/assets/src/scripts/notify.ts b/resources/assets/src/scripts/notify.ts index d7f90f57..e74f2e0a 100644 --- a/resources/assets/src/scripts/notify.ts +++ b/resources/assets/src/scripts/notify.ts @@ -1,15 +1,14 @@ -import { showModal } from './modal' -import { Toast } from './toast' +import {showModal} from './modal'; +import {Toast} from './toast'; -export const toast = new Toast() +export const toast = new Toast(); -/* istanbul ignore next */ if (process.env.NODE_ENV === 'test') { - afterEach(() => { - toast.clear() - }) + afterEach(() => { + toast.clear(); + }); } -Object.assign(blessing, { notify: { showModal, toast } }) +Object.assign(blessing, {notify: {showModal, toast}}); -export { showModal } from './modal' +export {showModal} from './modal'; diff --git a/resources/assets/src/scripts/route.tsx b/resources/assets/src/scripts/route.tsx index 19d3d958..e40f133c 100644 --- a/resources/assets/src/scripts/route.tsx +++ b/resources/assets/src/scripts/route.tsx @@ -1,118 +1,116 @@ -import React from 'react' - export default [ - { - path: 'user', - react: () => import('../views/user/Dashboard'), - el: '#usage-box', - frame: () => ( -
    -
     
    -
    -
     
    -
    - ), - }, - { - path: 'user/closet', - react: () => import('../views/user/Closet'), - el: '#closet-list', - }, - { - path: 'user/player', - react: () => import('../views/user/Players'), - el: '#players-list', - frame: () => ( -
    -
     
    -
    -
    - ), - }, - { - path: 'user/profile', - module: [() => import('../views/user/profile/index')], - }, - { - path: 'user/oauth/manage', - react: () => import('../views/user/OAuth'), - el: '.content > .container-fluid', - }, - { - path: 'admin', - module: [() => import('../views/admin/Dashboard')], - }, - { - path: 'admin/users', - react: () => import('../views/admin/UsersManagement'), - el: '.content > .container-fluid', - }, - { - path: 'admin/players', - react: () => import('../views/admin/PlayersManagement'), - el: '.content > .container-fluid', - }, - { - path: 'admin/reports', - react: () => import('../views/admin/ReportsManagement'), - el: '.content > .container-fluid', - }, - { - path: 'admin/customize', - module: [() => import('../views/admin/Customization')], - }, - { - path: 'admin/i18n', - react: () => import('../views/admin/Translations'), - el: '#table', - }, - { - path: 'admin/plugins/manage', - react: () => import('../views/admin/PluginsManagement'), - el: '.content > .container-fluid', - }, - { - path: 'admin/plugins/market', - react: () => import('../views/admin/PluginsMarket'), - el: '.content > .container-fluid', - }, - { - path: 'admin/update', - module: [() => import('../views/admin/Update')], - }, - { - path: 'auth/login', - react: () => import('../views/auth/Login'), - el: 'main', - }, - { - path: 'auth/register', - react: () => import('../views/auth/Registration'), - el: 'main', - }, - { - path: 'auth/forgot', - react: () => import('../views/auth/Forgot'), - el: 'main', - }, - { - path: 'auth/reset/(\\d+)', - react: () => import('../views/auth/Reset'), - el: 'main', - }, - { - path: 'skinlib', - react: () => import('../views/skinlib/SkinLibrary'), - el: '.content-wrapper', - }, - { - path: 'skinlib/show/(\\d+)', - react: () => import('../views/skinlib/Show'), - el: '#side', - }, - { - path: 'skinlib/upload', - react: () => import('../views/skinlib/Upload'), - el: '#file-input', - }, -] + { + path: 'user', + react: async () => import('../views/user/Dashboard'), + el: '#usage-box', + frame: () => ( +
    +
     
    +
    +
     
    +
    + ), + }, + { + path: 'user/closet', + react: async () => import('../views/user/Closet'), + el: '#closet-list', + }, + { + path: 'user/player', + react: async () => import('../views/user/Players'), + el: '#players-list', + frame: () => ( +
    +
     
    +
    +
    + ), + }, + { + path: 'user/profile', + module: [async () => import('../views/user/profile/index')], + }, + { + path: 'user/oauth/manage', + react: async () => import('../views/user/OAuth'), + el: '.content > .container-fluid', + }, + { + path: 'admin', + module: [async () => import('../views/admin/Dashboard')], + }, + { + path: 'admin/users', + react: async () => import('../views/admin/UsersManagement'), + el: '.content > .container-fluid', + }, + { + path: 'admin/players', + react: async () => import('../views/admin/PlayersManagement'), + el: '.content > .container-fluid', + }, + { + path: 'admin/reports', + react: async () => import('../views/admin/ReportsManagement'), + el: '.content > .container-fluid', + }, + { + path: 'admin/customize', + module: [async () => import('../views/admin/Customization')], + }, + { + path: 'admin/i18n', + react: async () => import('../views/admin/Translations'), + el: '#table', + }, + { + path: 'admin/plugins/manage', + react: async () => import('../views/admin/PluginsManagement'), + el: '.content > .container-fluid', + }, + { + path: 'admin/plugins/market', + react: async () => import('../views/admin/PluginsMarket'), + el: '.content > .container-fluid', + }, + { + path: 'admin/update', + module: [async () => import('../views/admin/Update')], + }, + { + path: 'auth/login', + react: async () => import('../views/auth/Login'), + el: 'main', + }, + { + path: 'auth/register', + react: async () => import('../views/auth/Registration'), + el: 'main', + }, + { + path: 'auth/forgot', + react: async () => import('../views/auth/Forgot'), + el: 'main', + }, + { + path: 'auth/reset/(\\d+)', + react: async () => import('../views/auth/Reset'), + el: 'main', + }, + { + path: 'skinlib', + react: async () => import('../views/skinlib/SkinLibrary'), + el: '.content-wrapper', + }, + { + path: 'skinlib/show/(\\d+)', + react: async () => import('../views/skinlib/Show'), + el: '#side', + }, + { + path: 'skinlib/upload', + react: async () => import('../views/skinlib/Upload'), + el: '#file-input', + }, +]; diff --git a/resources/assets/src/scripts/textureUtils.ts b/resources/assets/src/scripts/textureUtils.ts index baaf3ea6..d3f95594 100644 --- a/resources/assets/src/scripts/textureUtils.ts +++ b/resources/assets/src/scripts/textureUtils.ts @@ -1,51 +1,49 @@ -import { loadSkinToCanvas } from 'skinview-utils' +import {loadSkinToCanvas} from 'skinview-utils'; -/* istanbul ignore next */ function checkPixel( - context: CanvasRenderingContext2D, - x: number, - y: number, + context: CanvasRenderingContext2D, + x: number, + y: number, ): boolean { - const imageData = context.getImageData(x, y, 1, 1) + const imageData = context.getImageData(x, y, 1, 1); - return ( - imageData.data[0] === 0 && - imageData.data[1] === 0 && - imageData.data[2] === 0 - ) + return ( + imageData.data[0] === 0 + && imageData.data[1] === 0 + && imageData.data[2] === 0 + ); } -/* istanbul ignore next */ -export function isAlex(texture: string): Promise { - return new Promise((resolve) => { - const image = new Image() - image.src = texture - image.onload = () => { - if (image.width !== image.height) { - resolve(false) - return - } +export async function isAlex(texture: string): Promise { + return new Promise(resolve => { + const image = new Image(); + image.src = texture; + image.addEventListener('load', () => { + if (image.width !== image.height) { + resolve(false); + return; + } - const canvas = document.createElement('canvas') - loadSkinToCanvas(canvas, image) + const canvas = document.createElement('canvas'); + loadSkinToCanvas(canvas, image); - const ratio = canvas.width / 64 - const context = canvas.getContext('2d') - if (!context) { - resolve(false) - return - } + const ratio = canvas.width / 64; + const context = canvas.getContext('2d'); + if (!context) { + resolve(false); + return; + } - for (let x = 46 * ratio; x < 48 * ratio; x += 1) { - for (let y = 52 * ratio; y < 64 * ratio; y += 1) { - if (!checkPixel(context, x, y)) { - resolve(false) - return - } - } - } + for (let x = 46 * ratio; x < 48 * ratio; x += 1) { + for (let y = 52 * ratio; y < 64 * ratio; y += 1) { + if (!checkPixel(context, x, y)) { + resolve(false); + return; + } + } + } - resolve(true) - } - }) + resolve(true); + }); + }); } diff --git a/resources/assets/src/scripts/toast.tsx b/resources/assets/src/scripts/toast.tsx index 2826807c..7bfdee35 100644 --- a/resources/assets/src/scripts/toast.tsx +++ b/resources/assets/src/scripts/toast.tsx @@ -1,93 +1,97 @@ -import React, { useState, useEffect } from 'react' -import ReactDOM from 'react-dom' -import { nanoid } from 'nanoid' -import * as emitter from './event' -import ToastBox, { ToastType } from '../components/Toast' +import React, {useState, useEffect} from 'react'; +import ReactDOM from 'react-dom'; +import {nanoid} from 'nanoid'; +import ToastBox, {type ToastType} from '../components/Toast'; +import * as emitter from './event'; -type QueueElement = { id: string; type: ToastType; message: string } -type ToastQueue = QueueElement[] +type QueueElement = {id: string; type: ToastType; message: string}; +type ToastQueue = QueueElement[]; -const TOAST_EVENT = Symbol('toast') -const CLEAR_EVENT = Symbol('clear') +const TOAST_EVENT = Symbol('toast'); +const CLEAR_EVENT = Symbol('clear'); -export const ToastContainer: React.FC = () => { - const [queue, setQueue] = useState([]) +export function ToastContainer() { + const [queue, setQueue] = useState([]); - const handleClose = (id: string) => { - setQueue((queue) => queue.filter((el) => el.id !== id)) - } + const handleClose = (id: string) => { + setQueue(queue => queue.filter(element => element.id !== id)); + }; - useEffect(() => { - const off1 = emitter.on(TOAST_EVENT, (toast: QueueElement) => { - setQueue((queue) => { - queue.push(toast) - return queue.slice() - }) + useEffect(() => { + const off1 = emitter.on(TOAST_EVENT, (toast: QueueElement) => { + setQueue(queue => { + queue.push(toast); + return [...queue]; + }); - setTimeout(() => { - handleClose(toast.id) - }, 3100) - }) - const off2 = emitter.on(CLEAR_EVENT, () => setQueue([])) + setTimeout(() => { + handleClose(toast.id); + }, 3100); + }); + const off2 = emitter.on(CLEAR_EVENT, () => { + setQueue([]); + }); - return () => { - off1() - off2() - } - }, []) + return () => { + off1(); + off2(); + }; + }, []); - return ( - <> - {queue.map((el, i) => ( - handleClose(el.id)} - > - {el.message} - - ))} - - ) + return ( + <> + {queue.map((element, i) => ( + { + handleClose(element.id); + }} + > + {element.message} + + ))} + + ); } export class Toast { - private container: HTMLDivElement + private readonly container: HTMLDivElement; - constructor(render?: (element: JSX.Element) => void) { - this.container = document.createElement('div') - document.body.appendChild(this.container) + constructor(render?: (element: JSX.Element) => void) { + this.container = document.createElement('div'); + document.body.append(this.container); - if (render) { - render() - } else { - ReactDOM.render(, this.container) - } - } + if (render) { + render(); + } else { + ReactDOM.render(, this.container); + } + } - success(message: string) { - emitter.emit(TOAST_EVENT, { id: nanoid(4), type: 'success', message }) - } + success(message: string) { + emitter.emit(TOAST_EVENT, {id: nanoid(4), type: 'success', message}); + } - info(message: string) { - emitter.emit(TOAST_EVENT, { id: nanoid(4), type: 'info', message }) - } + info(message: string) { + emitter.emit(TOAST_EVENT, {id: nanoid(4), type: 'info', message}); + } - warning(message: string) { - emitter.emit(TOAST_EVENT, { id: nanoid(4), type: 'warning', message }) - } + warning(message: string) { + emitter.emit(TOAST_EVENT, {id: nanoid(4), type: 'warning', message}); + } - error(message: string) { - emitter.emit(TOAST_EVENT, { id: nanoid(4), type: 'error', message }) - } + error(message: string) { + emitter.emit(TOAST_EVENT, {id: nanoid(4), type: 'error', message}); + } - clear() { - emitter.emit(CLEAR_EVENT) - } + clear() { + emitter.emit(CLEAR_EVENT); + } - dispose() { - ReactDOM.unmountComponentAtNode(this.container) - this.container.remove() - } + dispose() { + ReactDOM.unmountComponentAtNode(this.container); + this.container.remove(); + } } diff --git a/resources/assets/src/scripts/types.ts b/resources/assets/src/scripts/types.ts index 9ab3706f..5d0d7cef 100644 --- a/resources/assets/src/scripts/types.ts +++ b/resources/assets/src/scripts/types.ts @@ -1,61 +1,61 @@ export type User = { - uid: number - email: string - nickname: string - locale: string | null - score: number - avatar: number - permission: UserPermission - ip: string - is_dark_mode: boolean - last_sign_at: string - register_at: string - verified: boolean -} + uid: number; + email: string; + nickname: string; + locale: string | undefined; + score: number; + avatar: number; + permission: UserPermission; + ip: string; + is_dark_mode: boolean; + last_sign_at: string; + register_at: string; + verified: boolean; +}; export const enum UserPermission { - Banned = -1, - Normal = 0, - Admin = 1, - SuperAdmin = 2, + Banned = -1, + Normal = 0, + Admin = 1, + SuperAdmin = 2, } export type Player = { - pid: number - name: string - uid: number - tid_skin: number - tid_cape: number - last_modified: string -} + pid: number; + name: string; + uid: number; + tid_skin: number; + tid_cape: number; + last_modified: string; +}; export type Texture = { - tid: number - name: string - type: TextureType - hash: string - size: number - uploader: number - public: boolean - upload_at: string - likes: number -} + tid: number; + name: string; + type: TextureType; + hash: string; + size: number; + uploader: number; + public: boolean; + upload_at: string; + likes: number; +}; export const enum TextureType { - Steve = 'steve', - Alex = 'alex', - Cape = 'cape', + Steve = 'steve', + Alex = 'alex', + Cape = 'cape', } export type ClosetItem = Texture & { - pivot: { user_uid: number; texture_tid: number; item_name: string } -} + pivot: {user_uid: number; texture_tid: number; item_name: string}; +}; export type Paginator = { - data: T[] - current_page: number - last_page: number - from: number - to: number - total: number -} + data: T[]; + current_page: number; + last_page: number; + from: number; + to: number; + total: number; +}; diff --git a/resources/assets/src/scripts/urls.ts b/resources/assets/src/scripts/urls.ts index 9bae27d6..1b474997 100644 --- a/resources/assets/src/scripts/urls.ts +++ b/resources/assets/src/scripts/urls.ts @@ -1,68 +1,68 @@ export default { - admin: { - players: { - delete: (player: number) => `/admin/players/${player}`, - list: () => '/admin/players/list' as const, - name: (player: number) => `/admin/players/${player}/name`, - owner: (player: number) => `/admin/players/${player}/owner`, - texture: (player: number) => `/admin/players/${player}/textures`, - }, - users: { - delete: (user: number) => `/admin/users/${user}`, - email: (user: number) => `/admin/users/${user}/email`, - list: () => '/admin/users/list' as const, - nickname: (user: number) => `/admin/users/${user}/nickname`, - password: (user: number) => `/admin/users/${user}/password`, - permission: (user: number) => `/admin/users/${user}/permission`, - score: (user: number) => `/admin/users/${user}/score`, - verification: (user: number) => `/admin/users/${user}/verification`, - }, - }, - auth: { - bind: () => '/auth/bind' as const, - forgot: () => '/auth/forgot' as const, - login: () => '/auth/login' as const, - logout: () => '/auth/logout' as const, - register: () => '/auth/register' as const, - reset: (uid: number) => `/auth/reset/${uid}`, - verify: (uid: number) => `/auth/verify/${uid}`, - }, - skinlib: { - home: () => '/skinlib' as const, - info: (texture: number) => `/skinlib/info/${texture}`, - list: () => '/skinlib/list' as const, - show: (tid: number) => `/skinlib/show/${tid}`, - }, - texture: { - delete: (texture: number) => `/texture/${texture}`, - info: (texture: number) => `/texture/${texture}`, - name: (texture: number) => `/texture/${texture}/name`, - privacy: (texture: number) => `/texture/${texture}/privacy`, - type: (texture: number) => `/texture/${texture}/type`, - upload: () => '/texture' as const, - }, - user: { - closet: { - add: () => '/user/closet' as const, - ids: () => '/user/closet/ids' as const, - list: () => '/user/closet/list' as const, - page: () => '/user/closet' as const, - remove: (tid: number) => `/user/closet/${tid}`, - rename: (tid: number) => `/user/closet/${tid}`, - }, - home: () => '/user' as const, - notification: (id: number) => `/user/notifications/${id}`, - player: { - add: () => '/user/player' as const, - clear: (player: number) => `/user/player/${player}/textures`, - delete: (player: number) => `/user/player/${player}`, - list: () => '/user/player/list' as const, - page: () => '/user/player' as const, - rename: (player: number) => `/user/player/${player}/name`, - set: (player: number) => `/user/player/${player}/textures`, - }, - profile: { avatar: () => '/user/profile/avatar' as const }, - score: () => '/user/score-info' as const, - sign: () => '/user/sign' as const, - }, -} + admin: { + players: { + delete: (player: number) => `/admin/players/${player}`, + list: () => '/admin/players/list' as const, + name: (player: number) => `/admin/players/${player}/name`, + owner: (player: number) => `/admin/players/${player}/owner`, + texture: (player: number) => `/admin/players/${player}/textures`, + }, + users: { + delete: (user: number) => `/admin/users/${user}`, + email: (user: number) => `/admin/users/${user}/email`, + list: () => '/admin/users/list' as const, + nickname: (user: number) => `/admin/users/${user}/nickname`, + password: (user: number) => `/admin/users/${user}/password`, + permission: (user: number) => `/admin/users/${user}/permission`, + score: (user: number) => `/admin/users/${user}/score`, + verification: (user: number) => `/admin/users/${user}/verification`, + }, + }, + auth: { + bind: () => '/auth/bind' as const, + forgot: () => '/auth/forgot' as const, + login: () => '/auth/login' as const, + logout: () => '/auth/logout' as const, + register: () => '/auth/register' as const, + reset: (uid: number) => `/auth/reset/${uid}`, + verify: (uid: number) => `/auth/verify/${uid}`, + }, + skinlib: { + home: () => '/skinlib' as const, + info: (texture: number) => `/skinlib/info/${texture}`, + list: () => '/skinlib/list' as const, + show: (tid: number) => `/skinlib/show/${tid}`, + }, + texture: { + delete: (texture: number) => `/texture/${texture}`, + info: (texture: number) => `/texture/${texture}`, + name: (texture: number) => `/texture/${texture}/name`, + privacy: (texture: number) => `/texture/${texture}/privacy`, + type: (texture: number) => `/texture/${texture}/type`, + upload: () => '/texture' as const, + }, + user: { + closet: { + add: () => '/user/closet' as const, + ids: () => '/user/closet/ids' as const, + list: () => '/user/closet/list' as const, + page: () => '/user/closet' as const, + remove: (tid: number) => `/user/closet/${tid}`, + rename: (tid: number) => `/user/closet/${tid}`, + }, + home: () => '/user' as const, + notification: (id: number) => `/user/notifications/${id}`, + player: { + add: () => '/user/player' as const, + clear: (player: number) => `/user/player/${player}/textures`, + delete: (player: number) => `/user/player/${player}`, + list: () => '/user/player/list' as const, + page: () => '/user/player' as const, + rename: (player: number) => `/user/player/${player}/name`, + set: (player: number) => `/user/player/${player}/textures`, + }, + profile: {avatar: () => '/user/profile/avatar' as const}, + score: () => '/user/score-info' as const, + sign: () => '/user/sign' as const, + }, +}; diff --git a/resources/assets/src/shims.d.ts b/resources/assets/src/shims.d.ts index afc17317..ce41198a 100644 --- a/resources/assets/src/shims.d.ts +++ b/resources/assets/src/shims.d.ts @@ -12,7 +12,7 @@ declare global { site_name: string version: string route: string - extra: any + extra: Record i18n: object fetch: { diff --git a/resources/assets/src/styles/spectre.css b/resources/assets/src/spectre.css similarity index 63% rename from resources/assets/src/styles/spectre.css rename to resources/assets/src/spectre.css index f290c6d6..c0f57c7c 100644 --- a/resources/assets/src/styles/spectre.css +++ b/resources/assets/src/spectre.css @@ -1,3 +1,6 @@ +@import 'spectre.css/src/spectre.scss'; +@import '@/fonts/minecraft.css'; + body { height: 97vh; } diff --git a/resources/assets/src/styles/breakpoints.ts b/resources/assets/src/styles/breakpoints.ts index 8334820c..966be311 100644 --- a/resources/assets/src/styles/breakpoints.ts +++ b/resources/assets/src/styles/breakpoints.ts @@ -1,19 +1,19 @@ export const enum Breakpoint { - xs = 0, - sm = 576, - md = 768, - lg = 992, - xl = 1200, + xs = 0, + sm = 576, + md = 768, + lg = 992, + xl = 1200, } export function lessThan(breakpoint: Breakpoint): string { - return `@media (max-width: ${breakpoint}px)` + return `@media (max-width: ${breakpoint}px)`; } export function between(down: Breakpoint, up: Breakpoint): string { - return `@media (min-width: ${down}px) and (max-width: ${up}px)` + return `@media (min-width: ${down}px) and (max-width: ${up}px)`; } export function greaterThan(breakpoint: Breakpoint): string { - return `@media (min-width: ${breakpoint}px)` + return `@media (min-width: ${breakpoint}px)`; } diff --git a/resources/assets/src/styles/utils.ts b/resources/assets/src/styles/utils.ts index c050957f..8aa92cbc 100644 --- a/resources/assets/src/styles/utils.ts +++ b/resources/assets/src/styles/utils.ts @@ -1,11 +1,11 @@ -import { css } from '@emotion/react' +import {css} from '@emotion/react'; export const pointerCursor = css` cursor: pointer; -` +`; export const center = css` display: flex; justify-content: center; align-items: center; -` +`; diff --git a/resources/assets/src/views/admin/Customization.ts b/resources/assets/src/views/admin/Customization.ts index cf39329e..beb8a937 100644 --- a/resources/assets/src/views/admin/Customization.ts +++ b/resources/assets/src/views/admin/Customization.ts @@ -1,83 +1,83 @@ /* eslint-disable object-curly-newline */ -import { fromEvent, merge, of, partition } from 'rxjs' -import { filter, map, pairwise } from 'rxjs/operators' +import {fromEvent, merge, of, partition} from 'rxjs'; +import {filter, map, pairwise} from 'rxjs/operators'; export function registerNavbarPicker( - navbar: HTMLElement, - picker: HTMLDivElement, - init: string, + navbar: HTMLElement, + picker: HTMLDivElement, + init: string, ): void { - const color$ = fromEvent(picker, 'click').pipe( - map((event) => event.target as HTMLElement), - filter( - (element): element is HTMLInputElement => element.tagName === 'INPUT', - ), - map((element) => element.value), - ) + const color$ = fromEvent(picker, 'click').pipe( + map(event => event.target as HTMLElement), + filter( + (element): element is HTMLInputElement => element.tagName === 'INPUT', + ), + map(element => element.value), + ); - merge(of(init), color$) - .pipe(pairwise()) - .subscribe(([previous, current]) => { - navbar.classList.replace(`navbar-${previous}`, `navbar-${current}`) - }) + merge(of(init), color$) + .pipe(pairwise()) + .subscribe(([previous, current]) => { + navbar.classList.replace(`navbar-${previous}`, `navbar-${current}`); + }); - const [light$, dark$] = partition(color$, (color) => - ['light', 'warning', 'white', 'orange', 'lime'].includes(color), - ) - light$.subscribe(() => { - // DO NOT use `classList.replace`. - navbar.classList.remove('navbar-dark') - navbar.classList.add('navbar-light') - }) - dark$.subscribe(() => { - // DO NOT use `classList.replace`. - navbar.classList.remove('navbar-light') - navbar.classList.add('navbar-dark') - }) + const [light$, dark$] = partition(color$, color => + ['light', 'warning', 'white', 'orange', 'lime'].includes(color), + ); + light$.subscribe(() => { + // DO NOT use `classList.replace`. + navbar.classList.remove('navbar-dark'); + navbar.classList.add('navbar-light'); + }); + dark$.subscribe(() => { + // DO NOT use `classList.replace`. + navbar.classList.remove('navbar-light'); + navbar.classList.add('navbar-dark'); + }); } -const navbar = document.querySelector('.wrapper > nav') -const picker = document.querySelector('#navbar-color-picker') -/* istanbul ignore next */ +const navbar = document.querySelector('.wrapper > nav'); +const picker = document.querySelector('#navbar-color-picker'); + if (navbar && picker) { - registerNavbarPicker(navbar, picker, blessing.extra.navbar || 'white') + registerNavbarPicker(navbar, picker, blessing.extra.navbar as string || 'white'); } export function registerSidebarPicker( - sidebar: HTMLElement, - { dark, light }: { dark: HTMLDivElement; light: HTMLDivElement }, - init: string, + sidebar: HTMLElement, + {dark, light}: {dark: HTMLDivElement; light: HTMLDivElement}, + init: string, ): void { - const color$ = merge( - fromEvent(dark, 'click'), - fromEvent(light, 'click'), - ).pipe( - map((event) => event.target as HTMLElement), - filter( - (element): element is HTMLInputElement => element.tagName === 'INPUT', - ), - map((element) => element.value), - ) + const color$ = merge( + fromEvent(dark, 'click'), + fromEvent(light, 'click'), + ).pipe( + map(event => event.target as HTMLElement), + filter( + (element): element is HTMLInputElement => element.tagName === 'INPUT', + ), + map(element => element.value), + ); - merge(of(init), color$) - .pipe(pairwise()) - .subscribe(([previous, current]) => { - sidebar.classList.replace(`sidebar-${previous}`, `sidebar-${current}`) - }) + merge(of(init), color$) + .pipe(pairwise()) + .subscribe(([previous, current]) => { + sidebar.classList.replace(`sidebar-${previous}`, `sidebar-${current}`); + }); } -const sidebar = document.querySelector('.main-sidebar') +const sidebar = document.querySelector('.main-sidebar'); const darkPicker = document.querySelector( - '#sidebar-dark-picker', -) + '#sidebar-dark-picker', +); const lightPicker = document.querySelector( - '#sidebar-light-picker', -) -/* istanbul ignore next */ + '#sidebar-light-picker', +); + if (sidebar && darkPicker && lightPicker) { - registerSidebarPicker( - sidebar, - { dark: darkPicker, light: lightPicker }, - blessing.extra.sidebar || 'dark-primary', - ) + registerSidebarPicker( + sidebar, + {dark: darkPicker, light: lightPicker}, + blessing.extra.sidebar as string || 'dark-primary', + ); } diff --git a/resources/assets/src/views/admin/Dashboard.ts b/resources/assets/src/views/admin/Dashboard.ts index ae75c110..53d14bf1 100644 --- a/resources/assets/src/views/admin/Dashboard.ts +++ b/resources/assets/src/views/admin/Dashboard.ts @@ -1,120 +1,120 @@ -import * as echarts from 'echarts/core' -import { SVGRenderer } from 'echarts/renderers' -import { LineChart } from 'echarts/charts' +import * as echarts from 'echarts/core'; +import {SVGRenderer} from 'echarts/renderers'; +import {LineChart} from 'echarts/charts'; import { - DataZoomComponent, - GridComponent, - TitleComponent, - TooltipComponent, -} from 'echarts/components' -import { get } from '../../scripts/net' + DataZoomComponent, + GridComponent, + TitleComponent, + TooltipComponent, +} from 'echarts/components'; +import {get} from '../../scripts/net'; -interface ChartData { - labels: string[] - xAxis: string[] - data: number[][] -} +type ChartData = { + labels: string[]; + xAxis: string[]; + data: number[][]; +}; -interface SingleChartData { - label: string - xAxis: string[] - data: number[] -} +type SingleChartData = { + label: string; + xAxis: string[]; + data: number[]; +}; echarts.use([ - SVGRenderer, - LineChart, - DataZoomComponent, - GridComponent, - TitleComponent, - TooltipComponent, -]) + SVGRenderer, + LineChart, + DataZoomComponent, + GridComponent, + TitleComponent, + TooltipComponent, +]); async function main() { - const elUsersRegistration = document.querySelector( - '#chart-users-registration', - ) - const elTexturesUpload = document.querySelector( - '#chart-textures-upload', - ) - if (!elUsersRegistration || !elTexturesUpload) { - return - } + const elementUsersRegistration = document.querySelector( + '#chart-users-registration', + ); + const elementTexturesUpload = document.querySelector( + '#chart-textures-upload', + ); + if (!elementUsersRegistration || !elementTexturesUpload) { + return; + } - const isDarkMode = document.body.classList.contains('dark-mode') - const textColor = isDarkMode ? '#fff' : '#000' + const isDarkMode = document.body.classList.contains('dark-mode'); + const textColor = isDarkMode ? '#fff' : '#000'; - const chartData: ChartData = await get('/admin/chart') - createLineChart( - elUsersRegistration, - isDarkMode ? '#3498db' : '#17a2b8', - textColor, - { - label: chartData.labels[0]!, - xAxis: chartData.xAxis, - data: chartData.data[0]!, - }, - ) - createLineChart(elTexturesUpload, '#6f42c1', textColor, { - label: chartData.labels[1]!, - xAxis: chartData.xAxis, - data: chartData.data[1]!, - }) + const chartData: ChartData = await get('/admin/chart'); + createLineChart( + elementUsersRegistration, + isDarkMode ? '#3498db' : '#17a2b8', + textColor, + { + label: chartData.labels[0], + xAxis: chartData.xAxis, + data: chartData.data[0], + }, + ); + createLineChart(elementTexturesUpload, '#6f42c1', textColor, { + label: chartData.labels[1], + xAxis: chartData.xAxis, + data: chartData.data[1], + }); } function createLineChart( - el: HTMLDivElement, - color: string, - textColor: string, - data: SingleChartData, + element: HTMLDivElement, + color: string, + textColor: string, + data: SingleChartData, ) { - const chart = echarts.init(el) - chart.setOption({ - title: { - text: data.label, - textStyle: { - color: textColor, - }, - }, - textStyle: { - color: textColor, - }, - tooltip: { - trigger: 'axis', - }, - dataZoom: [ - { type: 'inside', start: 75 }, - { type: 'slider', start: 75 }, - ], - xAxis: [ - { - type: 'category', - boundaryGap: false, - data: data.xAxis, - }, - ], - yAxis: [ - { - type: 'value', - minInterval: 1, - boundaryGap: false, - }, - ], - series: [ - { - name: data.label, - type: 'line', - itemStyle: { - color, - }, - areaStyle: { - color, - }, - data: data.data, - smooth: true, - }, - ], - }) + const chart = echarts.init(element); + chart.setOption({ + title: { + text: data.label, + textStyle: { + color: textColor, + }, + }, + textStyle: { + color: textColor, + }, + tooltip: { + trigger: 'axis', + }, + dataZoom: [ + {type: 'inside', start: 75}, + {type: 'slider', start: 75}, + ], + xAxis: [ + { + type: 'category', + boundaryGap: false, + data: data.xAxis, + }, + ], + yAxis: [ + { + type: 'value', + minInterval: 1, + boundaryGap: false, + }, + ], + series: [ + { + name: data.label, + type: 'line', + itemStyle: { + color, + }, + areaStyle: { + color, + }, + data: data.data, + smooth: true, + }, + ], + }); } -main() +void main(); diff --git a/resources/assets/src/views/admin/PlayersManagement/Card.tsx b/resources/assets/src/views/admin/PlayersManagement/Card.tsx index 66079f4d..ea84b05f 100644 --- a/resources/assets/src/views/admin/PlayersManagement/Card.tsx +++ b/resources/assets/src/views/admin/PlayersManagement/Card.tsx @@ -1,163 +1,163 @@ -import React from 'react' -import { t } from '@/scripts/i18n' -import { showModal } from '@/scripts/notify' -import type { Player } from '@/scripts/types' -import { Box } from './styles' -import clsx from 'clsx' -interface Props { - player: Player - onUpdateName(): void - onUpdateOwner(): void - onUpdateTexture(): void - onDelete(): void -} +import clsx from 'clsx'; +import {Box} from './styles'; +import {t} from '@/scripts/i18n'; +import {showModal} from '@/scripts/notify'; +import type {Player} from '@/scripts/types'; -const Card: React.FC = (props) => { - const { player } = props +type Properties = { + readonly player: Player; + onUpdateName(): void; + onUpdateOwner(): void; + onUpdateTexture(): void; + onDelete(): void; +}; - const handlePreviewTextures = () => { - const skinPreview = `${blessing.base_url}/preview/${player.tid_skin}` - const skinPreviewPNG = `${skinPreview}?png` - const capePreview = `${blessing.base_url}/preview/${player.tid_cape}` - const capePreviewPNG = `${capePreview}?png` +const Card: React.FC = properties => { + const {player} = properties; - showModal({ - mode: 'alert', - title: t('general.player.previews'), - children: ( -
    -
    - {player.tid_skin > 0 && ( - - - - {`${player.name} - - - )} -
    -
    - {player.tid_cape > 0 && ( - - - - {`${player.name} - - - )} -
    -
    - ), - }) - } + const handlePreviewTextures = () => { + const skinPreview = `${blessing.base_url}/preview/${player.tid_skin}`; + const skinPreviewPNG = `${skinPreview}?png`; + const capePreview = `${blessing.base_url}/preview/${player.tid_cape}`; + const capePreviewPNG = `${capePreview}?png`; - const isDarkMode = document.body.classList.contains('dark-mode') + showModal({ + mode: 'alert', + title: t('general.player.previews'), + children: ( +
    +
    + {player.tid_skin > 0 && ( + + + + {`${player.name} + + + )} +
    +
    + {player.tid_cape > 0 && ( + + + + {`${player.name} + + + )} +
    +
    + ), + }); + }; - const avatar = `${blessing.base_url}/avatar/player/${player.name}` - const avatarPNG = `${avatar}?png` + const isDarkMode = document.body.classList.contains('dark-mode'); - return ( - -
    - - - - -
    -
    - -
    -
    - PID: {player.pid} - - {t('general.player.owner')}: {player.uid} - -
    -
    - - {`${t('general.player.last-modified')}: `} - {player.last_modified} - -
    -
    -
    -
    - ) -} + const avatar = `${blessing.base_url}/avatar/player/${player.name}`; + const avatarPNG = `${avatar}?png`; -export default Card + return ( + +
    + + + + +
    +
    +
    +
    + {player.name} +
    +
    + +
    +
    + PID: {player.pid} + + {t('general.player.owner')}: {player.uid} + +
    +
    + + {`${t('general.player.last-modified')}: `} + {player.last_modified} + +
    +
    +
    + + ); +}; + +export default Card; diff --git a/resources/assets/src/views/admin/PlayersManagement/LoadingCard.tsx b/resources/assets/src/views/admin/PlayersManagement/LoadingCard.tsx index b83ad5b3..b3bdec14 100644 --- a/resources/assets/src/views/admin/PlayersManagement/LoadingCard.tsx +++ b/resources/assets/src/views/admin/PlayersManagement/LoadingCard.tsx @@ -1,37 +1,36 @@ -import React from 'react' -import styled from '@emotion/styled' -import Skeleton from 'react-loading-skeleton' -import { Box } from './styles' -import clsx from 'clsx' +import styled from '@emotion/styled'; +import Skeleton from 'react-loading-skeleton'; +import clsx from 'clsx'; +import {Box} from './styles'; -const isDarkMode = document.body.classList.contains('dark-mode') +const isDarkMode = document.body.classList.contains('dark-mode'); -const ShrinkedSkeleton = styled(Skeleton)<{ width?: string }>` - width: ${(props) => props.width}; -` +const ShrinkedSkeleton = styled(Skeleton)<{width?: string}>` + width: ${properties => properties.width}; +`; -const LoadingCard: React.FC = () => ( - -
    - -
    -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    -) - -export default LoadingCard +export default function LoadingCard() { + return ( + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + +
    +
    +
    + + ); +} diff --git a/resources/assets/src/views/admin/PlayersManagement/LoadingRow.tsx b/resources/assets/src/views/admin/PlayersManagement/LoadingRow.tsx index b7043a28..97fa8dbe 100644 --- a/resources/assets/src/views/admin/PlayersManagement/LoadingRow.tsx +++ b/resources/assets/src/views/admin/PlayersManagement/LoadingRow.tsx @@ -1,17 +1,16 @@ -import React from 'react' -import styled from '@emotion/styled' -import Skeleton from 'react-loading-skeleton' +import styled from '@emotion/styled'; +import Skeleton from 'react-loading-skeleton'; const ThickSkeleton = styled(Skeleton)` line-height: 2; -` +`; -const LoadingRow: React.FC = () => ( - - - - - -) - -export default LoadingRow +export default function LoadingRow() { + return ( + + + + + + ); +} diff --git a/resources/assets/src/views/admin/PlayersManagement/ModalUpdateTexture.tsx b/resources/assets/src/views/admin/PlayersManagement/ModalUpdateTexture.tsx index e70a4499..47925913 100644 --- a/resources/assets/src/views/admin/PlayersManagement/ModalUpdateTexture.tsx +++ b/resources/assets/src/views/admin/PlayersManagement/ModalUpdateTexture.tsx @@ -1,84 +1,84 @@ -import React, { useState } from 'react' -import { t } from '@/scripts/i18n' -import { TextureType } from '@/scripts/types' -import Modal from '@/components/Modal' +import {useState} from 'react'; +import {t} from '@/scripts/i18n'; +import {TextureType} from '@/scripts/types'; +import Modal from '@/components/Modal'; -interface Props { - open: boolean - onSubmit(type: 'skin' | 'cape', tid: number): void - onClose(): void -} +type Properties = { + readonly open: boolean; + onSubmit(type: 'skin' | 'cape', tid: number): void; + onClose(): void; +}; -const ModalUpdateTexture: React.FC = (props) => { - const [type, setType] = useState<'skin' | 'cape'>('skin') - const [tid, setTid] = useState('') +const ModalUpdateTexture: React.FC = properties => { + const [type, setType] = useState<'skin' | 'cape'>('skin'); + const [tid, setTid] = useState(''); - const handleTypeChange = (event: React.ChangeEvent) => { - setType(event.target.value as 'skin' | 'cape') - } + const handleTypeChange = (event: React.ChangeEvent) => { + setType(event.target.value as 'skin' | 'cape'); + }; - const handleTidChange = (event: React.ChangeEvent) => { - setTid(event.target.value) - } + const handleTidChange = (event: React.ChangeEvent) => { + setTid(event.target.value); + }; - const handleConfirm = () => { - props.onSubmit(type, Number.parseInt(tid)) - setType('skin') - setTid('') - } + const handleConfirm = () => { + properties.onSubmit(type, Number.parseInt(tid)); + setType('skin'); + setTid(''); + }; - const handleClose = () => { - setType('skin') - setTid('') - props.onClose() - } + const handleClose = () => { + setType('skin'); + setTid(''); + properties.onClose(); + }; - return ( - -
    - -
    - - -
    -
    -
    - - -
    -
    - ) -} + return ( + +
    + +
    + + +
    +
    +
    + + +
    +
    + ); +}; -export default ModalUpdateTexture +export default ModalUpdateTexture; diff --git a/resources/assets/src/views/admin/PlayersManagement/Row.tsx b/resources/assets/src/views/admin/PlayersManagement/Row.tsx index d24b65db..6c493ffe 100644 --- a/resources/assets/src/views/admin/PlayersManagement/Row.tsx +++ b/resources/assets/src/views/admin/PlayersManagement/Row.tsx @@ -1,81 +1,81 @@ -import React from 'react' -import { t } from '@/scripts/i18n' -import type { Player } from '@/scripts/types' -import ButtonEdit from '@/components/ButtonEdit' -interface Props { - player: Player - onUpdateName(): void - onUpdateOwner(): void - onUpdateTexture(): void - onDelete(): void -} +import {t} from '@/scripts/i18n'; +import type {Player} from '@/scripts/types'; +import ButtonEdit from '@/components/ButtonEdit'; -const Row: React.FC = (props) => { - const { player } = props +type Properties = { + readonly player: Player; + onUpdateName(): void; + onUpdateOwner(): void; + onUpdateTexture(): void; + onDelete(): void; +}; - return ( - - {player.pid} - - {player.name} - - - - - - {player.uid} - - - - - - {player.tid_skin > 0 && ( - - {`${player.name} - - )} - {player.tid_cape > 0 && ( - - {`${player.name} - - )} - - {player.last_modified} - - - - - - ) -} +const Row: React.FC = properties => { + const {player} = properties; -export default Row + return ( + + {player.pid} + + {player.name} + + + + + + {player.uid} + + + + + + {player.tid_skin > 0 && ( + + {`${player.name} + + )} + {player.tid_cape > 0 && ( + + {`${player.name} + + )} + + {player.last_modified} + + + + + + ); +}; + +export default Row; diff --git a/resources/assets/src/views/admin/PlayersManagement/index.tsx b/resources/assets/src/views/admin/PlayersManagement/index.tsx index 69b21c86..0d183e43 100644 --- a/resources/assets/src/views/admin/PlayersManagement/index.tsx +++ b/resources/assets/src/views/admin/PlayersManagement/index.tsx @@ -1,271 +1,276 @@ -import React, { useState, useEffect, useLayoutEffect } from 'react' -import { hot } from 'react-hot-loader/root' -import { useImmer } from 'use-immer' -import useIsLargeScreen from '@/scripts/hooks/useIsLargeScreen' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import type { Player, Paginator } from '@/scripts/types' -import { toast, showModal } from '@/scripts/notify' -import urls from '@/scripts/urls' -import Pagination from '@/components/Pagination' -import Header from '../UsersManagement/Header' -import Card from './Card' -import LoadingCard from './LoadingCard' -import Row from './Row' -import LoadingRow from './LoadingRow' -import ModalUpdateTexture from './ModalUpdateTexture' +import {useState, useEffect, useLayoutEffect} from 'react'; +import {useImmer} from 'use-immer'; +import Header from '../UsersManagement/Header'; +import Card from './Card'; +import LoadingCard from './LoadingCard'; +import Row from './Row'; +import LoadingRow from './LoadingRow'; +import ModalUpdateTexture from './ModalUpdateTexture'; +import useIsLargeScreen from '@/scripts/hooks/useIsLargeScreen'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import type {Player, Paginator} from '@/scripts/types'; +import {toast, showModal} from '@/scripts/notify'; +import urls from '@/scripts/urls'; +import Pagination from '@/components/Pagination'; -const PlayersManagement: React.FC = () => { - const [players, setPlayers] = useImmer([]) - const [page, setPage] = useState(1) - const [totalPages, setTotalPages] = useState(1) - const [isLoading, setIsLoading] = useState(false) - const isLargeScreen = useIsLargeScreen() - const [isTableMode, setIsTableMode] = useState(false) - const [query, setQuery] = useState('') - const [textureUpdating, setTextureUpdating] = useState(-1) +function PlayersManagement() { + const [players, setPlayers] = useImmer([]); + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [isLoading, setIsLoading] = useState(false); + const isLargeScreen = useIsLargeScreen(); + const [isTableMode, setIsTableMode] = useState(false); + const [query, setQuery] = useState(''); + const [textureUpdating, setTextureUpdating] = useState(-1); - useLayoutEffect(() => { - if (isLargeScreen) { - setIsTableMode(true) - } - }, [isLargeScreen]) + useLayoutEffect(() => { + if (isLargeScreen) { + setIsTableMode(true); + } + }, [isLargeScreen]); - const getPlayers = async () => { - setIsLoading(true) - const { data, last_page }: Paginator = await fetch.get( - urls.admin.players.list(), - { - q: query, - page, - }, - ) - setTotalPages(last_page) - setPlayers(() => data) - setIsLoading(false) - } + const getPlayers = async () => { + setIsLoading(true); + const {data, last_page}: Paginator = await fetch.get( + urls.admin.players.list(), + { + q: query, + page: page.toString(), + }, + ); + setTotalPages(last_page); + setPlayers(() => data); + setIsLoading(false); + }; - useEffect(() => { - getPlayers() - }, [page]) + useEffect(() => { + getPlayers(); + }, [page]); - const handleModeChange = (event: React.ChangeEvent) => { - setIsTableMode(event.target.value === 'table') - } + const handleModeChange = (event: React.ChangeEvent) => { + setIsTableMode(event.target.value === 'table'); + }; - const handleQueryChange = (event: React.ChangeEvent) => { - setQuery(event.target.value) - } + const handleQueryChange = (event: React.ChangeEvent) => { + setQuery(event.target.value); + }; - const handleSubmitQuery = (event: React.FormEvent) => { - event.preventDefault() - getPlayers() - } + const handleSubmitQuery = (event: React.FormEvent) => { + event.preventDefault(); + getPlayers(); + }; - const handleUpdateName = async (player: Player, index: number) => { - let name: string - try { - const { value } = await showModal({ - mode: 'prompt', - text: t('admin.changePlayerNameNotice'), - input: player.name, - validator: (value: string) => { - if (!value) { - return t('admin.emptyPlayerName') - } - }, - }) - name = value - } catch { - return - } + const handleUpdateName = async (player: Player, index: number) => { + let name: string; + try { + const {value} = await showModal({ + mode: 'prompt', + text: t('admin.changePlayerNameNotice'), + input: player.name, + validator(value: string) { + if (!value) { + return t('admin.emptyPlayerName'); + } + }, + }); + name = value; + } catch { + return; + } - const { code, message } = await fetch.put( - urls.admin.players.name(player.pid), - { player_name: name }, - ) - if (code === 0) { - toast.success(message) - setPlayers((players) => { - players[index]!.name = name - }) - } else { - toast.error(message) - } - } + const {code, message} = await fetch.put( + urls.admin.players.name(player.pid), + {player_name: name}, + ); + if (code === 0) { + toast.success(message); + setPlayers(players => { + players[index].name = name; + }); + } else { + toast.error(message); + } + }; - const handleUpdateOwner = async (player: Player, index: number) => { - let uid: number - try { - const { value } = await showModal({ - mode: 'prompt', - text: t('admin.changePlayerOwner'), - input: player.uid.toString(), - inputMode: 'numeric', - }) - uid = Number.parseInt(value) - } catch { - return - } + const handleUpdateOwner = async (player: Player, index: number) => { + let uid: number; + try { + const {value} = await showModal({ + mode: 'prompt', + text: t('admin.changePlayerOwner'), + input: player.uid.toString(), + inputMode: 'numeric', + }); + uid = Number.parseInt(value); + } catch { + return; + } - const { code, message } = await fetch.put( - urls.admin.players.owner(player.pid), - { uid }, - ) - if (code === 0) { - toast.success(message) - setPlayers((players) => { - players[index]!.uid = uid - }) - } else { - toast.error(message) - } - } + const {code, message} = await fetch.put( + urls.admin.players.owner(player.pid), + {uid}, + ); + if (code === 0) { + toast.success(message); + setPlayers(players => { + players[index].uid = uid; + }); + } else { + toast.error(message); + } + }; - const handleCloseModalUpdateTexture = () => setTextureUpdating(-1) + const handleCloseModalUpdateTexture = () => { + setTextureUpdating(-1); + }; - const handleUpdateTexture = async (type: 'skin' | 'cape', tid: number) => { - const { code, message } = await fetch.put( - urls.admin.players.texture(players[textureUpdating]!.pid), - { type, tid }, - ) + const handleUpdateTexture = async (type: 'skin' | 'cape', tid: number) => { + const {code, message} = await fetch.put( + urls.admin.players.texture(players[textureUpdating].pid), + {type, tid}, + ); - if (code === 0) { - toast.success(message) - setPlayers((players) => { - const field = `tid_${type}` as const - players[textureUpdating]![field] = tid - }) - } else { - toast.error(message) - } - } + if (code === 0) { + toast.success(message); + setPlayers(players => { + const field = `tid_${type}` as const; + players[textureUpdating][field] = tid; + }); + } else { + toast.error(message); + } + }; - const handleDelete = async (player: Player) => { - try { - await showModal({ - text: t('admin.deletePlayerNotice'), - okButtonType: 'danger', - }) - } catch { - return - } + const handleDelete = async (player: Player) => { + try { + await showModal({ + text: t('admin.deletePlayerNotice'), + okButtonType: 'danger', + }); + } catch { + return; + } - const { code, message } = await fetch.del( - urls.admin.players.delete(player.pid), - ) - if (code === 0) { - setPlayers((players) => players.filter(({ pid }) => pid !== player.pid)) - toast.success(message) - } else { - toast.error(message) - } - } + const {code, message} = await fetch.del( + urls.admin.players.delete(player.pid), + ); + if (code === 0) { + setPlayers(players => players.filter(({pid}) => pid !== player.pid)); + toast.success(message); + } else { + toast.error(message); + } + }; - return ( -
    -
    -
    - -
    - -
    -
    -
    - - -
    -
    - {players.length === 0 && !isLoading ? ( -
    {t('general.noResult')}
    - ) : isTableMode ? ( -
    - - - - - - - - - - - - - {isLoading - ? new Array(10).fill(null).map((_, i) => ) - : players.map((player, i) => ( - handleUpdateName(player, i)} - onUpdateOwner={() => handleUpdateOwner(player, i)} - onUpdateTexture={() => setTextureUpdating(i)} - onDelete={() => handleDelete(player)} - /> - ))} - -
    PID{t('general.player.player-name')}{t('general.player.owner')}{t('general.player.previews')}{t('general.player.last-modified')}{t('admin.operationsTitle')}
    -
    - ) : ( -
    - {isLoading - ? new Array(10).fill(null).map((_, i) => ) - : players.map((player, i) => ( - handleUpdateName(player, i)} - onUpdateOwner={() => handleUpdateOwner(player, i)} - onUpdateTexture={() => setTextureUpdating(i)} - onDelete={() => handleDelete(player)} - /> - ))} -
    - )} -
    -
    - -
    -
    - -1} - onSubmit={handleUpdateTexture} - onClose={handleCloseModalUpdateTexture} - /> -
    - ) + return ( +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    + {players.length === 0 && !isLoading ? ( +
    {t('general.noResult')}
    + ) : (isTableMode ? ( +
    + + + + + + + + + + + + + {isLoading + ? Array.from({length: 10}).fill(null).map((_, i) => ) + : players.map((player, i) => ( + handleUpdateName(player, i)} + onUpdateOwner={async () => handleUpdateOwner(player, i)} + onUpdateTexture={() => { + setTextureUpdating(i); + }} + onDelete={async () => handleDelete(player)} + /> + ))} + +
    PID{t('general.player.player-name')}{t('general.player.owner')}{t('general.player.previews')}{t('general.player.last-modified')}{t('admin.operationsTitle')}
    +
    + ) : ( +
    + {isLoading + ? Array.from({length: 10}).fill(null).map((_, i) => ) + : players.map((player, i) => ( + handleUpdateName(player, i)} + onUpdateOwner={async () => handleUpdateOwner(player, i)} + onUpdateTexture={() => { + setTextureUpdating(i); + }} + onDelete={async () => handleDelete(player)} + /> + ))} +
    + ))} +
    +
    + +
    +
    + -1} + onSubmit={handleUpdateTexture} + onClose={handleCloseModalUpdateTexture} + /> +
    + ); } -export default hot(PlayersManagement) +export default PlayersManagement; diff --git a/resources/assets/src/views/admin/PlayersManagement/styles.ts b/resources/assets/src/views/admin/PlayersManagement/styles.ts index 20e33a5f..61392d27 100644 --- a/resources/assets/src/views/admin/PlayersManagement/styles.ts +++ b/resources/assets/src/views/admin/PlayersManagement/styles.ts @@ -1,5 +1,5 @@ -import styled from '@emotion/styled' -import * as breakpoints from '@/styles/breakpoints' +import styled from '@emotion/styled'; +import * as breakpoints from '@/styles/breakpoints'; export const Box = styled.div` width: 48%; @@ -8,4 +8,4 @@ export const Box = styled.div` ${breakpoints.lessThan(breakpoints.Breakpoint.lg)} { width: 98%; } -` +`; diff --git a/resources/assets/src/views/admin/PluginsManagement/InfoBox.tsx b/resources/assets/src/views/admin/PluginsManagement/InfoBox.tsx index 99bda687..ebb03d27 100644 --- a/resources/assets/src/views/admin/PluginsManagement/InfoBox.tsx +++ b/resources/assets/src/views/admin/PluginsManagement/InfoBox.tsx @@ -1,8 +1,8 @@ -import React from 'react' -import styled from '@emotion/styled' -import { t } from '@/scripts/i18n' -import type { Plugin } from './types' -import clsx from 'clsx' + +import styled from '@emotion/styled'; +import clsx from 'clsx'; +import type {Plugin} from './types'; +import {t} from '@/scripts/i18n'; const Box = styled.div` cursor: default; @@ -15,7 +15,7 @@ const Box = styled.div` .info-box-content { max-width: calc(100% - 70px); } -` +`; const ActionButton = styled.a` transition-property: color; transition-duration: 0.3s; @@ -29,99 +29,101 @@ const ActionButton = styled.a` &:not(:last-child) { margin-right: 9px; } -` +`; const Header = styled.div` max-width: calc(100% - 40px); display: flex; align-items: center; -` +`; const Description = styled.div` font-size: 14px; -` +`; -interface Props { - plugin: Plugin - onEnable(plugin: Plugin): void - onDisable(plugin: Plugin): void - onDelete(plugin: Plugin): void - baseUrl: string -} +type Properties = { + readonly plugin: Plugin; + onEnable(plugin: Plugin): void; + onDisable(plugin: Plugin): void; + onDelete(plugin: Plugin): void; + readonly baseUrl: string; +}; -const InfoBox: React.FC = (props) => { - const { plugin } = props +const InfoBox: React.FC = properties => { + const {plugin} = properties; - const handleChange = (event: React.ChangeEvent) => { - event.preventDefault() + const handleChange = (event: React.ChangeEvent) => { + event.preventDefault(); - if (event.target.checked) { - props.onEnable(plugin) - } else { - props.onDisable(plugin) - } - } + if (event.target.checked) { + properties.onEnable(plugin); + } else { + properties.onDisable(plugin); + } + }; - const handleDelete = () => props.onDelete(plugin) + const handleDelete = () => { + properties.onDelete(plugin); + }; - const isDarkMode = document.body.classList.contains('dark-mode') + const isDarkMode = document.body.classList.contains('dark-mode'); - return ( - - - - -
    -
    -
    - - - {plugin.title} - - - v{plugin.version} - -
    -
    - {plugin.readme && ( - - - - )} - {plugin.enabled && plugin.config && ( - - - - )} - - - -
    -
    - - {plugin.description} - -
    -
    - ) -} + return ( + + + + +
    +
    +
    + + + {plugin.title} + + + v{plugin.version} + +
    +
    + {plugin.readme && ( + + + + )} + {plugin.enabled && plugin.config && ( + + + + )} + + + +
    +
    + + {plugin.description} + +
    +
    + ); +}; -export default InfoBox +export default InfoBox; diff --git a/resources/assets/src/views/admin/PluginsManagement/index.tsx b/resources/assets/src/views/admin/PluginsManagement/index.tsx index e5dd4d88..b480cd1b 100644 --- a/resources/assets/src/views/admin/PluginsManagement/index.tsx +++ b/resources/assets/src/views/admin/PluginsManagement/index.tsx @@ -1,247 +1,247 @@ -import React, { useState, useEffect } from 'react' -import { hot } from 'react-hot-loader/root' -import { useImmer } from 'use-immer' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { toast, showModal } from '@/scripts/notify' -import FileInput from '@/components/FileInput' -import Loading from '@/components/Loading' -import InfoBox from './InfoBox' -import type { Plugin } from './types' +import {useState, useEffect} from 'react'; +import {useImmer} from 'use-immer'; +import InfoBox from './InfoBox'; +import type {Plugin} from './types'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {toast, showModal} from '@/scripts/notify'; +import FileInput from '@/components/FileInput'; +import Loading from '@/components/Loading'; -const PluginsManagement: React.FC = () => { - const [isLoading, setIsLoading] = useState(true) - const [plugins, setPlugins] = useImmer([]) - const [file, setFile] = useState(null) - const [isUploading, setIsUploading] = useState(false) - const [url, setUrl] = useState('') - const [isDownloading, setIsDownloading] = useState(false) +function PluginsManagement() { + const [isLoading, setIsLoading] = useState(true); + const [plugins, setPlugins] = useImmer([]); + const [file, setFile] = useState(null); + const [isUploading, setIsUploading] = useState(false); + const [url, setUrl] = useState(''); + const [isDownloading, setIsDownloading] = useState(false); - useEffect(() => { - const getPlugins = async () => { - setIsLoading(true) - const plugins = await fetch.get('/admin/plugins/data') - setPlugins(() => plugins) - setIsLoading(false) - } - getPlugins() - }, []) + useEffect(() => { + const getPlugins = async () => { + setIsLoading(true); + const plugins = await fetch.get('/admin/plugins/data'); + setPlugins(() => plugins); + setIsLoading(false); + }; - const handleEnable = async (plugin: Plugin, i: number) => { - const { - code, - message, - data: { reason } = { reason: [] }, - } = await fetch.post< - fetch.ResponseBody<{ - reason: string[] - }> - >('/admin/plugins/manage', { - action: 'enable', - name: plugin.name, - }) - if (code === 0) { - toast.success(message) - setPlugins((plugins) => { - plugins[i]!.enabled = true - }) - } else { - showModal({ - mode: 'alert', - children: ( -
    -

    {message}

    -
      - {reason.map((t, i) => ( -
    • {t}
    • - ))} -
    -
    - ), - }) - } - } + getPlugins(); + }, []); - const handleDisable = async (plugin: Plugin, i: number) => { - const { code, message } = await fetch.post( - '/admin/plugins/manage', - { - action: 'disable', - name: plugin.name, - }, - ) - if (code === 0) { - toast.success(message) - setPlugins((plugins) => { - plugins[i]!.enabled = false - }) - } else { - toast.error(message) - } - } + const handleEnable = async (plugin: Plugin, i: number) => { + const { + code, + message, + data: {reason} = {reason: []}, + } = await fetch.post< + fetch.ResponseBody<{ + reason: string[]; + }> + >('/admin/plugins/manage', { + action: 'enable', + name: plugin.name, + }); + if (code === 0) { + toast.success(message); + setPlugins(plugins => { + plugins[i].enabled = true; + }); + } else { + showModal({ + mode: 'alert', + children: ( +
    +

    {message}

    +
      + {reason.map((t, i) => ( +
    • {t}
    • + ))} +
    +
    + ), + }); + } + }; - const handleDelete = async (plugin: Plugin) => { - try { - await showModal({ - title: plugin.title, - text: t('admin.confirmDeletion'), - okButtonType: 'danger', - }) - } catch { - return - } + const handleDisable = async (plugin: Plugin, i: number) => { + const {code, message} = await fetch.post( + '/admin/plugins/manage', + { + action: 'disable', + name: plugin.name, + }, + ); + if (code === 0) { + toast.success(message); + setPlugins(plugins => { + plugins[i].enabled = false; + }); + } else { + toast.error(message); + } + }; - const { code, message } = await fetch.post( - '/admin/plugins/manage', - { - action: 'delete', - name: plugin.name, - }, - ) - if (code === 0) { - const { name } = plugin - setPlugins((plugins) => plugins.filter((plugin) => plugin.name !== name)) - toast.success(message) - } else { - toast.error(message) - } - } + const handleDelete = async (plugin: Plugin) => { + try { + await showModal({ + title: plugin.title, + text: t('admin.confirmDeletion'), + okButtonType: 'danger', + }); + } catch { + return; + } - const handleFileChange = (event: React.ChangeEvent) => { - setFile(event.target.files![0]!) - } + const {code, message} = await fetch.post( + '/admin/plugins/manage', + { + action: 'delete', + name: plugin.name, + }, + ); + if (code === 0) { + const {name} = plugin; + setPlugins(plugins => plugins.filter(plugin => plugin.name !== name)); + toast.success(message); + } else { + toast.error(message); + } + }; - const handleUrlChange = (event: React.ChangeEvent) => { - setUrl(event.target.value) - } + const handleFileChange = (event: React.ChangeEvent) => { + setFile(event.target.files![0]); + }; - const handleUpload = async () => { - if (!file) { - return - } + const handleUrlChange = (event: React.ChangeEvent) => { + setUrl(event.target.value); + }; - setIsUploading(true) - const formData = new FormData() - formData.append('file', file, file.name) - const { code, message } = await fetch.post( - '/admin/plugins/upload', - formData, - ) + const handleUpload = async () => { + if (!file) { + return; + } - setIsUploading(false) - if (code === 0) { - toast.success(message) - setFile(null) + setIsUploading(true); + const formData = new FormData(); + formData.append('file', file, file.name); + const {code, message} = await fetch.post( + '/admin/plugins/upload', + formData, + ); - const plugins = await fetch.get('/admin/plugins/data') - setPlugins(() => plugins) - } else { - toast.error(message) - } - } + setIsUploading(false); + if (code === 0) { + toast.success(message); + setFile(null); - const handleSubmitUrl = async () => { - setIsDownloading(true) - const { code, message } = await fetch.post( - '/admin/plugins/wget', - { url }, - ) + const plugins = await fetch.get('/admin/plugins/data'); + setPlugins(() => plugins); + } else { + toast.error(message); + } + }; - setIsDownloading(false) - if (code === 0) { - toast.success(message) - setUrl('') + const handleSubmitUrl = async () => { + setIsDownloading(true); + const {code, message} = await fetch.post( + '/admin/plugins/wget', + {url}, + ); - const plugins = await fetch.get('/admin/plugins/data') - setPlugins(() => plugins) - } else { - toast.error(message) - } - } + setIsDownloading(false); + if (code === 0) { + toast.success(message); + setUrl(''); - const chunks = Array(Math.ceil(plugins.length / 2)) - .fill(null) - .map((_, i) => plugins.slice(i * 2, (i + 1) * 2) as [Plugin, Plugin?]) + const plugins = await fetch.get('/admin/plugins/data'); + setPlugins(() => plugins); + } else { + toast.error(message); + } + }; - return ( -
    -
    - {isLoading ? ( - - ) : plugins.length === 0 ? ( - t('general.noResult') - ) : ( - chunks.map((chunk, i) => ( -
    - {(chunk as Plugin[]).map((plugin, j) => ( -
    - handleEnable(plugin, i * 2 + j)} - onDisable={(plugin) => handleDisable(plugin, i * 2 + j)} - onDelete={handleDelete} - baseUrl={blessing.base_url} - /> -
    - ))} -
    - )) - )} -
    -
    -
    -
    -

    {t('admin.uploadArchive')}

    -
    -
    -

    {t('admin.uploadArchiveNotice')}

    - -
    -
    - -
    -
    -
    -
    -

    {t('admin.downloadRemote')}

    -
    -
    -

    {t('admin.downloadRemoteNotice')}

    -
    - - -
    -
    -
    - -
    -
    -
    -
    - ) + const chunks = Array.from({length: Math.ceil(plugins.length / 2)}) + .fill(null) + .map((_, i) => plugins.slice(i * 2, (i + 1) * 2) as [Plugin, Plugin?]); + + return ( +
    +
    + {isLoading ? ( + + ) : (plugins.length === 0 ? ( + t('general.noResult') + ) : ( + chunks.map((chunk, i) => ( +
    + {(chunk as Plugin[]).map((plugin, index) => ( +
    + handleEnable(plugin, i * 2 + index)} + onDisable={async plugin => handleDisable(plugin, i * 2 + index)} + onDelete={handleDelete} + /> +
    + ))} +
    + )) + ))} +
    +
    +
    +
    +

    {t('admin.uploadArchive')}

    +
    +
    +

    {t('admin.uploadArchiveNotice')}

    + +
    +
    + +
    +
    +
    +
    +

    {t('admin.downloadRemote')}

    +
    +
    +

    {t('admin.downloadRemoteNotice')}

    +
    + + +
    +
    +
    + +
    +
    +
    +
    + ); } -export default hot(PluginsManagement) +export default PluginsManagement; diff --git a/resources/assets/src/views/admin/PluginsManagement/types.ts b/resources/assets/src/views/admin/PluginsManagement/types.ts index 3ebf9d04..6e74e23b 100644 --- a/resources/assets/src/views/admin/PluginsManagement/types.ts +++ b/resources/assets/src/views/admin/PluginsManagement/types.ts @@ -1,10 +1,10 @@ export type Plugin = { - name: string - title: string - description: string - version: string - enabled: boolean - config: boolean - readme: boolean - icon: { fa: string; faType: 'fas' | 'fab'; bg: string } -} + name: string; + title: string; + description: string; + version: string; + enabled: boolean; + config: boolean; + readme: boolean; + icon: {fa: string; faType: 'fas' | 'fab'; bg: string}; +}; diff --git a/resources/assets/src/views/admin/PluginsMarket/Row.tsx b/resources/assets/src/views/admin/PluginsMarket/Row.tsx index 3cc1a575..b0417003 100644 --- a/resources/assets/src/views/admin/PluginsMarket/Row.tsx +++ b/resources/assets/src/views/admin/PluginsMarket/Row.tsx @@ -1,92 +1,92 @@ -import React from 'react' -import { t } from '@/scripts/i18n' -import type { Plugin } from './types' -interface Props { - plugin: Plugin - isInstalling: boolean - onInstall(): void - onUpdate(): void -} +import type {Plugin} from './types'; +import {t} from '@/scripts/i18n'; -const Row: React.FC = (props) => { - const { plugin, isInstalling } = props +type Properties = { + readonly plugin: Plugin; + readonly isInstalling: boolean; + onInstall(): void; + onUpdate(): void; +}; - const allDeps = Object.entries(plugin.dependencies.all) - const unsatisfied = Object.keys(plugin.dependencies.unsatisfied) +const Row: React.FC = properties => { + const {plugin, isInstalling} = properties; - return ( - - -
    - {plugin.title} -
    -
    {plugin.name}
    - - {plugin.description} - {plugin.author} - {plugin.version} - - {allDeps.length === 0 ? ( - {t('admin.noDependencies')} - ) : ( -
    - {allDeps.map(([name, constraint]) => { - const classes = [ - 'mb-1', - 'badge', - `bg-${unsatisfied.includes(name) ? 'red' : 'green'}`, - ] - return ( - - {name}: {constraint} - - ) - })} -
    - )} - - - {plugin.can_update ? ( - - ) : ( - - )} - - - ) -} + const allDeps = Object.entries(plugin.dependencies.all); + const unsatisfied = Object.keys(plugin.dependencies.unsatisfied); -export default Row + return ( + + +
    + {plugin.title} +
    +
    {plugin.name}
    + + {plugin.description} + {plugin.author} + {plugin.version} + + {allDeps.length === 0 ? ( + {t('admin.noDependencies')} + ) : ( +
    + {allDeps.map(([name, constraint]) => { + const classes = [ + 'mb-1', + 'badge', + `bg-${unsatisfied.includes(name) ? 'red' : 'green'}`, + ]; + return ( + + {name}: {constraint} + + ); + })} +
    + )} + + + {plugin.can_update ? ( + + ) : ( + + )} + + + ); +}; + +export default Row; diff --git a/resources/assets/src/views/admin/PluginsMarket/index.tsx b/resources/assets/src/views/admin/PluginsMarket/index.tsx index ad7ce04e..ef3e46d5 100644 --- a/resources/assets/src/views/admin/PluginsMarket/index.tsx +++ b/resources/assets/src/views/admin/PluginsMarket/index.tsx @@ -1,167 +1,166 @@ -import React, { useState, useEffect, useMemo } from 'react' -import { hot } from 'react-hot-loader/root' -import { enableMapSet } from 'immer' -import { useImmer } from 'use-immer' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { toast, showModal } from '@/scripts/notify' -import Loading from '@/components/Loading' -import Pagination from '@/components/Pagination' -import type { Plugin } from './types' -import Row from './Row' +import {useState, useEffect, useMemo} from 'react'; +import {enableMapSet} from 'immer'; +import {useImmer} from 'use-immer'; +import type {Plugin} from './types'; +import Row from './Row'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {toast, showModal} from '@/scripts/notify'; +import Loading from '@/components/Loading'; +import Pagination from '@/components/Pagination'; -enableMapSet() +enableMapSet(); -const PluginsMarket: React.FC = () => { - const [plugins, setPlugins] = useImmer([]) - const [isLoading, setIsLoading] = useState(true) - const [search, setSearch] = useState('') - const [page, setPage] = useState(1) - const [totalPages, setTotalPages] = useState(1) - const [installings, setInstallings] = useImmer>(() => new Set()) +export default function PluginsMarket() { + const [plugins, setPlugins] = useImmer([]); + const [isLoading, setIsLoading] = useState(true); + const [search, setSearch] = useState(''); + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [installings, setInstallings] = useImmer>(() => new Set()); - const searchedPlugins = useMemo( - () => - plugins.filter( - (plugin) => - plugin.name.includes(search) || plugin.title.includes(search), - ), - [plugins, search], - ) + const searchedPlugins = useMemo( + () => + plugins.filter( + plugin => + plugin.name.includes(search) || plugin.title.includes(search), + ), + [plugins, search], + ); - useEffect(() => { - const getPlugins = async () => { - setIsLoading(true) - const plugins = await fetch.get('/admin/plugins/market/list') - setPlugins(() => plugins) - setTotalPages(Math.ceil(plugins.length / 10)) - setIsLoading(false) - } - getPlugins() - }, []) + useEffect(() => { + const getPlugins = async () => { + setIsLoading(true); + const plugins = await fetch.get('/admin/plugins/market/list'); + setPlugins(() => plugins); + setTotalPages(Math.ceil(plugins.length / 10)); + setIsLoading(false); + }; - const handleSearchChange = (event: React.ChangeEvent) => { - const search = event.target.value - setSearch(search) - setPage(1) + void getPlugins(); + }, []); - const searchedPlugins = plugins.filter( - (plugin) => plugin.name.includes(search) || plugin.title.includes(search), - ) - setTotalPages(Math.ceil(searchedPlugins.length / 10)) - } + const handleSearchChange = (event: React.ChangeEvent) => { + const search = event.target.value; + setSearch(search); + setPage(1); - const handleInstall = async (plugin: Plugin, index: number) => { - setInstallings((installings) => { - installings.add(plugin.name) - }) + const searchedPlugins = plugins.filter( + plugin => plugin.name.includes(search) || plugin.title.includes(search), + ); + setTotalPages(Math.ceil(searchedPlugins.length / 10)); + }; - const { - code, - message, - data = { reason: [] }, - } = await fetch.post>( - '/admin/plugins/market/download', - { - name: plugin.name, - }, - ) - if (code === 0) { - toast.success(message) - setPlugins((plugins) => { - plugins[index]!.can_update = false - plugins[index]!.installed = plugins[index]!.version - }) - } else { - showModal({ - mode: 'alert', - children: ( -
    -

    {message}

    -
      - {data.reason.map((t, i) => ( -
    • {t}
    • - ))} -
    -
    - ), - }) - } + const handleInstall = async (plugin: Plugin, index: number) => { + setInstallings(installings => { + installings.add(plugin.name); + }); - setInstallings((installings) => { - installings.delete(plugin.name) - }) - } + const { + code, + message, + data = {reason: []}, + } = await fetch.post>( + '/admin/plugins/market/download', + { + name: plugin.name, + }, + ); + if (code === 0) { + toast.success(message); + setPlugins(plugins => { + plugins[index].can_update = false; + plugins[index].installed = plugins[index].version; + }); + } else { + void showModal({ + mode: 'alert', + children: ( +
    +

    {message}

    +
      + {data.reason.map((t, i) => ( +
    • {t}
    • + ))} +
    +
    + ), + }); + } - const handleUpdate = async (plugin: Plugin, index: number) => { - try { - await showModal({ - text: t('admin.confirmUpdate', { - plugin: plugin.title, - old: plugin.installed, - new: plugin.version, - }), - }) - } catch { - return - } + setInstallings(installings => { + installings.delete(plugin.name); + }); + }; - handleInstall(plugin, index) - } + const handleUpdate = async (plugin: Plugin, index: number) => { + try { + await showModal({ + text: t('admin.confirmUpdate', { + plugin: plugin.title, + old: plugin.installed.toString(), + new: plugin.version, + }), + }); + } catch { + return; + } - const pagedPlugins = searchedPlugins.slice((page - 1) * 10, page * 10) + void handleInstall(plugin, index); + }; - return ( -
    -
    - -
    - {isLoading ? ( -
    - -
    - ) : searchedPlugins.length === 0 ? ( -
    {t('general.noResult')}
    - ) : ( -
    - - - - - - - - - - - - - {pagedPlugins.map((plugin, i) => ( - handleInstall(plugin, (page - 1) * 10 + i)} - onUpdate={() => handleUpdate(plugin, (page - 1) * 10 + i)} - /> - ))} - -
    {t('admin.pluginTitle')}{t('admin.pluginDescription')}{t('admin.pluginAuthor')}{t('admin.pluginVersion')}{t('admin.pluginDependencies')}{t('admin.operationsTitle')}
    -
    - )} -
    -
    - -
    -
    -
    - ) + const pagedPlugins = searchedPlugins.slice((page - 1) * 10, page * 10); + + return ( +
    +
    + +
    + {isLoading ? ( +
    + +
    + ) : (searchedPlugins.length === 0 ? ( +
    {t('general.noResult')}
    + ) : ( +
    + + + + + + + + + + + + + {pagedPlugins.map((plugin, i) => ( + handleInstall(plugin, ((page - 1) * 10) + i)} + onUpdate={async () => handleUpdate(plugin, ((page - 1) * 10) + i)} + /> + ))} + +
    {t('admin.pluginTitle')}{t('admin.pluginDescription')}{t('admin.pluginAuthor')}{t('admin.pluginVersion')}{t('admin.pluginDependencies')}{t('admin.operationsTitle')}
    +
    + ))} +
    +
    + +
    +
    +
    + ); } -export default hot(PluginsMarket) diff --git a/resources/assets/src/views/admin/PluginsMarket/types.ts b/resources/assets/src/views/admin/PluginsMarket/types.ts index c97ef8a9..cbd97989 100644 --- a/resources/assets/src/views/admin/PluginsMarket/types.ts +++ b/resources/assets/src/views/admin/PluginsMarket/types.ts @@ -1,13 +1,13 @@ export type Plugin = { - name: string - version: string - title: string - description: string - author: string - installed: string | false - can_update?: boolean - dependencies: { - all: Record - unsatisfied: Record - } -} + name: string; + version: string; + title: string; + description: string; + author: string; + installed: string | false; + can_update?: boolean; + dependencies: { + all: Record; + unsatisfied: Record; + }; +}; diff --git a/resources/assets/src/views/admin/ReportsManagement/ImageBox.tsx b/resources/assets/src/views/admin/ReportsManagement/ImageBox.tsx index 94b73637..ca17a53c 100644 --- a/resources/assets/src/views/admin/ReportsManagement/ImageBox.tsx +++ b/resources/assets/src/views/admin/ReportsManagement/ImageBox.tsx @@ -1,8 +1,8 @@ -import React from 'react' -import styled from '@emotion/styled' -import { t } from '@/scripts/i18n' -import type { Texture } from '@/scripts/types' -import { Report, Status } from './types' + +import styled from '@emotion/styled'; +import {type Report, Status} from './types'; +import {t} from '@/scripts/i18n'; +import type {Texture} from '@/scripts/types'; const Card = styled.div` width: 240px; @@ -27,121 +27,123 @@ const Card = styled.div` margin: 2.5px 0; } } -` +`; -interface Props { - report: Report - onClick(texture: Texture | null): void - onBan(): void - onDelete(): void - onReject(): void -} +type Properties = { + readonly report: Report; + onClick(texture: Texture | undefined): void; + onBan(): void; + onDelete(): void; + onReject(): void; +}; -const ImageBox: React.FC = (props) => { - const { report } = props - const preview = `${blessing.base_url}/preview/${report.tid}?height=150` - const previewPNG = `${preview}&png` +const ImageBox: React.FC = properties => { + const {report} = properties; + const preview = `${blessing.base_url}/preview/${report.tid}?height=150`; + const previewPNG = `${preview}&png`; - const handleImageClick = () => props.onClick(report.texture) + const handleImageClick = () => { + properties.onClick(report.texture); + }; - return ( - -
    - - {t('skinlib.show.uploader')} - {': '} - - {report.texture_uploader?.nickname} - (UID: {report.uploader}) -
    -
    - - - {report.tid.toString()} - -
    -
    -
    -
    - {report.status === Status.Pending ? ( - {t('report.status.0')} - ) : report.status === Status.Resolved ? ( - {t('report.status.1')} - ) : ( - {t('report.status.2')} - )} - TID: {report.tid} -
    - -
    -
    - - {t('report.reporter')} - {': '} - - {report.informer?.nickname} - (UID: {report.reporter}) -
    -
    - - - {t('report.reason')} - {': '} - - {report.reason} - -
    {report.reason}
    -
    - - {t('report.time')} - {': '} - {report.report_at} - -
    -
    -
    -
    - ) -} + return ( + +
    + + {t('skinlib.show.uploader')} + {': '} + + {report.texture_uploader?.nickname} + (UID: {report.uploader}) +
    +
    + + + {report.tid.toString()} + +
    +
    +
    +
    + {report.status === Status.Pending ? ( + {t('report.status.0')} + ) : (report.status === Status.Resolved ? ( + {t('report.status.1')} + ) : ( + {t('report.status.2')} + ))} + TID: {report.tid} +
    + +
    +
    + + {t('report.reporter')} + {': '} + + {report.informer?.nickname} + (UID: {report.reporter}) +
    +
    + + + {t('report.reason')} + {': '} + + {report.reason} + +
    {report.reason}
    +
    + + {t('report.time')} + {': '} + {report.report_at} + +
    +
    +
    +
    + ); +}; -export default ImageBox +export default ImageBox; diff --git a/resources/assets/src/views/admin/ReportsManagement/index.tsx b/resources/assets/src/views/admin/ReportsManagement/index.tsx index 1b9239fa..5798bfcc 100644 --- a/resources/assets/src/views/admin/ReportsManagement/index.tsx +++ b/resources/assets/src/views/admin/ReportsManagement/index.tsx @@ -1,155 +1,154 @@ -import React, { useState, useEffect } from 'react' -import { hot } from 'react-hot-loader/root' -import { useImmer } from 'use-immer' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { Paginator, Texture, TextureType } from '@/scripts/types' -import { toast, showModal } from '@/scripts/notify' -import Loading from '@/components/Loading' -import Pagination from '@/components/Pagination' -import ViewerSkeleton from '@/components/ViewerSkeleton' -import type { Report, Status } from './types' -import ImageBox from './ImageBox' +import React, {useState, useEffect} from 'react'; +import {useImmer} from 'use-immer'; +import type {Report, Status} from './types'; +import ImageBox from './ImageBox'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {type Paginator, type Texture, TextureType} from '@/scripts/types'; +import {toast, showModal} from '@/scripts/notify'; +import Loading from '@/components/Loading'; +import Pagination from '@/components/Pagination'; +import ViewerSkeleton from '@/components/ViewerSkeleton'; -const Previewer = React.lazy(() => import('@/components/Viewer')) +const Previewer = React.lazy(async () => import('@/components/Viewer')); -const ReportsManagement: React.FC = () => { - const [reports, setReports] = useImmer([]) - const [page, setPage] = useState(1) - const [totalPages, setTotalPages] = useState(1) - const [isLoading, setIsLoading] = useState(true) - const [query, setQuery] = useState('status:0 sort:-report_at') - const [viewingTexture, setViewingTexture] = useState(null) +function ReportsManagement() { + const [reports, setReports] = useImmer([]); + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [isLoading, setIsLoading] = useState(true); + const [query, setQuery] = useState('status:0 sort:-report_at'); + const [viewingTexture, setViewingTexture] = useState(null); - const getReports = async () => { - setIsLoading(true) - const { data, last_page }: Paginator = await fetch.get( - '/admin/reports/list', - { - q: query, - page, - }, - ) - setTotalPages(last_page) - setReports(() => data) - setIsLoading(false) - } + const getReports = async () => { + setIsLoading(true); + const {data, last_page}: Paginator = await fetch.get( + '/admin/reports/list', + { + q: query, + page: page.toString(), + }, + ); + setTotalPages(last_page); + setReports(() => data); + setIsLoading(false); + }; - useEffect(() => { - getReports() - }, [page]) + useEffect(() => { + getReports(); + }, [page]); - const handleQueryChange = (event: React.ChangeEvent) => { - setQuery(event.target.value) - } + const handleQueryChange = (event: React.ChangeEvent) => { + setQuery(event.target.value); + }; - const handleSubmitQuery = (event: React.FormEvent) => { - event.preventDefault() - getReports() - } + const handleSubmitQuery = (event: React.FormEvent) => { + event.preventDefault(); + getReports(); + }; - const handleProceedReport = async ( - report: Report, - index: number, - action: 'ban' | 'delete' | 'reject', - ) => { - type Ok = { code: 0; message: string; data: { status: Status } } - type Err = { code: 1; message: string } - const resp = await fetch.put(`/admin/reports/${report.id}`, { - action, - }) + const handleProceedReport = async ( + report: Report, + index: number, + action: 'ban' | 'delete' | 'reject', + ) => { + type Ok = {code: 0; message: string; data: {status: Status}}; + type Error_ = {code: 1; message: string}; + const resp = await fetch.put(`/admin/reports/${report.id}`, { + action, + }); if (resp.code === 0) { - toast.success(resp.message) - setReports((reports) => { - reports[index]!.status = resp.data.status - }) + toast.success(resp.message); + setReports(reports => { + reports[index].status = resp.data.status; + }); } else { - toast.error(resp.message) + toast.error(resp.message); } - } + }; - const handleDelete = async (report: Report, index: number) => { - try { - await showModal({ - text: t('skinlib.deleteNotice'), - okButtonType: 'danger', - }) - } catch { - return - } + const handleDelete = async (report: Report, index: number) => { + try { + await showModal({ + text: t('skinlib.deleteNotice'), + okButtonType: 'danger', + }); + } catch { + return; + } - handleProceedReport(report, index, 'delete') - } + handleProceedReport(report, index, 'delete'); + }; - const textureUrl = - viewingTexture && `${blessing.base_url}/textures/${viewingTexture.hash}` + const textureUrl + = viewingTexture && `${blessing.base_url}/textures/${viewingTexture.hash}`; - return ( -
    -
    -
    -
    -
    - -
    - -
    -
    -
    - {isLoading ? ( -
    - -
    - ) : reports.length === 0 ? ( -
    {t('general.noResult')}
    - ) : ( -
    - {reports.map((report, i) => ( - handleProceedReport(report, i, 'ban')} - onDelete={() => handleDelete(report, i)} - onReject={() => handleProceedReport(report, i, 'reject')} - /> - ))} -
    - )} -
    -
    - -
    -
    -
    -
    -
    - }> - - -
    -
    - ) + return ( +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + {isLoading ? ( +
    + +
    + ) : (reports.length === 0 ? ( +
    {t('general.noResult')}
    + ) : ( +
    + {reports.map((report, i) => ( + handleProceedReport(report, i, 'ban')} + onDelete={async () => handleDelete(report, i)} + onReject={async () => handleProceedReport(report, i, 'reject')} + /> + ))} +
    + ))} +
    +
    + +
    +
    +
    +
    +
    + }> + + +
    +
    + ); } -export default hot(ReportsManagement) +export default ReportsManagement; diff --git a/resources/assets/src/views/admin/ReportsManagement/types.ts b/resources/assets/src/views/admin/ReportsManagement/types.ts index 802ea9fe..35588b81 100644 --- a/resources/assets/src/views/admin/ReportsManagement/types.ts +++ b/resources/assets/src/views/admin/ReportsManagement/types.ts @@ -1,20 +1,20 @@ -import type { Texture, User } from '@/scripts/types' +import type {Texture, User} from '@/scripts/types'; export const enum Status { - Pending = 0, - Resolved = 1, - Rejected = 2, + Pending = 0, + Resolved = 1, + Rejected = 2, } export type Report = { - id: number - tid: number - texture: Texture | null - uploader: number - texture_uploader: User | null - reporter: number - informer: User | null - reason: string - status: Status - report_at: string -} + id: number; + tid: number; + texture: Texture | undefined; + uploader: number; + texture_uploader: User | undefined; + reporter: number; + informer: User | undefined; + reason: string; + status: Status; + report_at: string; +}; diff --git a/resources/assets/src/views/admin/Translations/Row.tsx b/resources/assets/src/views/admin/Translations/Row.tsx index 6b485d50..ab3b02d8 100644 --- a/resources/assets/src/views/admin/Translations/Row.tsx +++ b/resources/assets/src/views/admin/Translations/Row.tsx @@ -1,47 +1,50 @@ -import styled from '@emotion/styled' -import React from 'react' -import { t } from '@/scripts/i18n' -import type { Line } from './types' +import styled from '@emotion/styled'; +import type {Line} from './types'; +import {t} from '@/scripts/i18n'; const Group = styled.td` width: 15%; -` +`; const Key = styled.td` width: 20%; -` +`; const Operations = styled.td` width: 25%; -` +`; -interface Props { - line: Line - onEdit(line: Line): void - onRemove(line: Line): void -} +type Properties = { + readonly line: Line; + onEdit(line: Line): void; + onRemove(line: Line): void; +}; -const Row: React.FC = (props) => { - const { line, onEdit, onRemove } = props - const text = line.text[blessing.locale] +const Row: React.FC = properties => { + const {line, onEdit, onRemove} = properties; + const text = line.text[blessing.locale]; - const handleEditClick = () => onEdit(line) + const handleEditClick = () => { + onEdit(line); + }; - const handleRemoveClick = () => onRemove(line) + const handleRemoveClick = () => { + onRemove(line); + }; - return ( - - {line.group} - {line.key} - {text || t('admin.i18n.empty')} - - - - - - ) -} + return ( + + {line.group} + {line.key} + {text || t('admin.i18n.empty')} + + + + + + ); +}; -export default Row +export default Row; diff --git a/resources/assets/src/views/admin/Translations/index.tsx b/resources/assets/src/views/admin/Translations/index.tsx index 04699989..dabda162 100644 --- a/resources/assets/src/views/admin/Translations/index.tsx +++ b/resources/assets/src/views/admin/Translations/index.tsx @@ -1,120 +1,120 @@ -import React, { useState, useEffect } from 'react' -import { hot } from 'react-hot-loader/root' -import { useImmer } from 'use-immer' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { showModal, toast } from '@/scripts/notify' -import type { Paginator } from '@/scripts/types' -import Loading from '@/components/Loading' -import Pagination from '@/components/Pagination' -import type { Line } from './types' -import Row from './Row' +import React, {useState, useEffect} from 'react'; +import {useImmer} from 'use-immer'; +import type {Line} from './types'; +import Row from './Row'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {showModal, toast} from '@/scripts/notify'; +import type {Paginator} from '@/scripts/types'; +import Loading from '@/components/Loading'; +import Pagination from '@/components/Pagination'; -const Translations: React.FC = () => { - const [lines, setLines] = useImmer([]) - const [isLoading, setIsLoading] = useState(true) - const [page, setPage] = useState(1) - const [totalPages, setTotalPages] = useState(1) +function Translations() { + const [lines, setLines] = useImmer([]); + const [isLoading, setIsLoading] = useState(true); + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); - useEffect(() => { - const getLines = async () => { - setIsLoading(true) - const result = await fetch.get>('/admin/i18n/list', { - page, - }) - setLines(() => result.data) - setTotalPages(result.last_page) - setIsLoading(false) - } - getLines() - }, [page]) + useEffect(() => { + const getLines = async () => { + setIsLoading(true); + const result = await fetch.get>('/admin/i18n/list', { + page: page.toString(), + }); + setLines(() => result.data); + setTotalPages(result.last_page); + setIsLoading(false); + }; - const handleEdit = async (line: Line, index: number) => { - let text: string - try { - const { value } = await showModal({ - mode: 'prompt', - text: t('admin.i18n.updating'), - input: line.text[blessing.locale], - }) - text = value - } catch { - return - } + getLines(); + }, [page]); - const { code, message } = await fetch.put( - `/admin/i18n/${line.id}`, - { text }, - ) - if (code === 0) { - toast.success(message) - setLines((lines) => { - lines[index]!.text[blessing.locale] = text - }) - } else { - toast.error(message) - } - } + const handleEdit = async (line: Line, index: number) => { + let text: string; + try { + const {value} = await showModal({ + mode: 'prompt', + text: t('admin.i18n.updating'), + input: line.text[blessing.locale], + }); + text = value; + } catch { + return; + } - const handleRemove = async (line: Line) => { - try { - await showModal({ - text: t('admin.i18n.confirmDelete'), - okButtonType: 'danger', - }) - } catch { - return - } + const {code, message} = await fetch.put( + `/admin/i18n/${line.id}`, + {text}, + ); + if (code === 0) { + toast.success(message); + setLines(lines => { + lines[index].text[blessing.locale] = text; + }); + } else { + toast.error(message); + } + }; - const { message } = await fetch.del(`/admin/i18n/${line.id}`) - toast.success(message) - const { id } = line - setLines((lines) => lines.filter((line) => line.id !== id)) - } + const handleRemove = async (line: Line) => { + try { + await showModal({ + text: t('admin.i18n.confirmDelete'), + okButtonType: 'danger', + }); + } catch { + return; + } - return ( - <> -
    - - - - - - - - - - - {isLoading ? ( - - - - ) : lines.length === 0 ? ( - - - - ) : ( - lines.map((line, i) => ( - handleEdit(line, i)} - onRemove={handleRemove} - /> - )) - )} - -
    {t('admin.i18n.group')}{t('admin.i18n.key')}{t('admin.i18n.text')}{t('admin.operationsTitle')}
    - -
    - {t('general.noResult')} -
    -
    -
    - -
    - - ) + const {message} = await fetch.del(`/admin/i18n/${line.id}`); + toast.success(String(message)); + const {id} = line; + setLines(lines => lines.filter(line => line.id !== id)); + }; + + return ( + <> +
    + + + + + + + + + + + {isLoading ? ( + + + + ) : (lines.length === 0 ? ( + + + + ) : ( + lines.map((line, i) => ( + handleEdit(line, i)} + onRemove={handleRemove} + /> + )) + ))} + +
    {t('admin.i18n.group')}{t('admin.i18n.key')}{t('admin.i18n.text')}{t('admin.operationsTitle')}
    + +
    + {t('general.noResult')} +
    +
    +
    + +
    + + ); } -export default hot(Translations) +export default Translations; diff --git a/resources/assets/src/views/admin/Translations/types.ts b/resources/assets/src/views/admin/Translations/types.ts index 20a08269..8d5015ee 100644 --- a/resources/assets/src/views/admin/Translations/types.ts +++ b/resources/assets/src/views/admin/Translations/types.ts @@ -1,6 +1,6 @@ export type Line = { - id: number - group: string - key: string - text: Record -} + id: number; + group: string; + key: string; + text: Record; +}; diff --git a/resources/assets/src/views/admin/Update.ts b/resources/assets/src/views/admin/Update.ts index f081e736..c521ae82 100644 --- a/resources/assets/src/views/admin/Update.ts +++ b/resources/assets/src/views/admin/Update.ts @@ -1,24 +1,24 @@ -import { post, ResponseBody } from '../../scripts/net' -import { showModal } from '../../scripts/notify' -import { t } from '../../scripts/i18n' +import {post, type ResponseBody} from '../../scripts/net'; +import {showModal} from '../../scripts/notify'; +import {t} from '../../scripts/i18n'; export default async function handler(event: MouseEvent) { - const button = event.target as HTMLButtonElement - button.disabled = true + const button = event.target as HTMLButtonElement; + button.disabled = true; - const text = button.textContent - button.innerHTML = ` ${t( - 'admin.downloading', - )}` + const text = button.textContent; + button.innerHTML = ` ${t( + 'admin.downloading', + )}`; - const { code, message }: ResponseBody = await post('/admin/update/download') - button.textContent = text - button.disabled = false - await showModal({ mode: 'alert', text: message }) - if (code === 0) { - location.href = '/' - } + const {code, message}: ResponseBody = await post('/admin/update/download'); + button.textContent = text; + button.disabled = false; + await showModal({mode: 'alert', text: message}); + if (code === 0) { + location.href = '/'; + } } -const button = document.querySelector('#update') -button?.addEventListener('click', handler) +const button = document.querySelector('#update'); +button?.addEventListener('click', handler); diff --git a/resources/assets/src/views/admin/UsersManagement/Card.tsx b/resources/assets/src/views/admin/UsersManagement/Card.tsx index de41fbe4..f80bd688 100644 --- a/resources/assets/src/views/admin/UsersManagement/Card.tsx +++ b/resources/assets/src/views/admin/UsersManagement/Card.tsx @@ -1,164 +1,164 @@ -import React from 'react' -import { t } from '@/scripts/i18n' -import type { User } from '@/scripts/types' -import { Box, Icon, InfoTable } from './styles' + +import clsx from 'clsx'; +import {Box, Icon, InfoTable} from './styles'; import { - humanizePermission, - verificationStatusText, - canModifyUser, - canModifyPermission, -} from './utils' -import clsx from 'clsx' + humanizePermission, + verificationStatusText, + canModifyUser, + canModifyPermission, +} from './utils'; +import {t} from '@/scripts/i18n'; +import type {User} from '@/scripts/types'; -interface Props { - user: User - currentUser: User - onEmailChange(): void - onNicknameChange(): void - onScoreChange(): void - onPermissionChange(): void - onVerificationToggle(): void - onPasswordChange(): void - onDelete(): void -} +type Properties = { + readonly user: User; + readonly currentUser: User; + onEmailChange(): void; + onNicknameChange(): void; + onScoreChange(): void; + onPermissionChange(): void; + onVerificationToggle(): void; + onPasswordChange(): void; + onDelete(): void; +}; -const Card: React.FC = (props) => { - const { user, currentUser } = props +const Card: React.FC = properties => { + const {user, currentUser} = properties; - const isDarkMode = document.body.classList.contains('dark-mode') + const isDarkMode = document.body.classList.contains('dark-mode'); - const avatar = `${blessing.base_url}/avatar/user/${user.uid}` - const avatarPNG = `${avatar}?png` - const canModify = canModifyUser(user, currentUser) + const avatar = `${blessing.base_url}/avatar/user/${user.uid}`; + const avatarPNG = `${avatar}?png`; + const canModify = canModifyUser(user, currentUser); - return ( - - - - - - - -
    -
    -
    - {user.nickname} -
    - -
    -
    -
    UID: {user.uid}
    -
    - {t('general.user.email')} - {': '} - {user.email} -
    - -
    - {t('general.user.score')} - {user.score} -
    -
    - {t('admin.permission')} - - {humanizePermission(user.permission)} - -
    -
    - {t('admin.verification')} - - {verificationStatusText(user.verified)} - -
    -
    -
    - - {t('general.user.register-at')} - {': '} - {user.register_at} - -
    -
    -
    -
    - ) -} + return ( + + + + + + + +
    +
    +
    + {user.nickname} +
    +
    + {canModify && ( + +
    +
    UID: {user.uid}
    +
    + {t('general.user.email')} + {': '} + {user.email} +
    + +
    + {t('general.user.score')} + {user.score} +
    +
    + {t('admin.permission')} + + {humanizePermission(user.permission)} + +
    +
    + {t('admin.verification')} + + {verificationStatusText(user.verified)} + +
    +
    +
    + + {t('general.user.register-at')} + {': '} + {user.register_at} + +
    +
    +
    + + ); +}; -export default Card +export default Card; diff --git a/resources/assets/src/views/admin/UsersManagement/Header.tsx b/resources/assets/src/views/admin/UsersManagement/Header.tsx index f3f0c98d..05c73259 100644 --- a/resources/assets/src/views/admin/UsersManagement/Header.tsx +++ b/resources/assets/src/views/admin/UsersManagement/Header.tsx @@ -1,5 +1,5 @@ -import styled from '@emotion/styled' -import { lessThan, Breakpoint } from '@/styles/breakpoints' +import styled from '@emotion/styled'; +import {lessThan, Breakpoint} from '@/styles/breakpoints'; const Header = styled.div` display: flex; @@ -17,6 +17,6 @@ const Header = styled.div` margin: 7px 0 0 0; } } -` +`; -export default Header +export default Header; diff --git a/resources/assets/src/views/admin/UsersManagement/LoadingCard.tsx b/resources/assets/src/views/admin/UsersManagement/LoadingCard.tsx index 10e47590..33efde31 100644 --- a/resources/assets/src/views/admin/UsersManagement/LoadingCard.tsx +++ b/resources/assets/src/views/admin/UsersManagement/LoadingCard.tsx @@ -1,61 +1,60 @@ -import React from 'react' -import styled from '@emotion/styled' -import Skeleton from 'react-loading-skeleton' -import { t } from '@/scripts/i18n' -import { Box, Icon, InfoTable } from './styles' -import clsx from 'clsx' +import styled from '@emotion/styled'; +import Skeleton from 'react-loading-skeleton'; +import clsx from 'clsx'; +import {Box, Icon, InfoTable} from './styles'; +import {t} from '@/scripts/i18n'; -const ShrinkedSkeleton = styled(Skeleton)<{ width?: string }>` - width: ${(props) => props.width}; -` +const ShrinkedSkeleton = styled(Skeleton)<{width?: string}>` + width: ${properties => properties.width}; +`; -const isDarkMode = document.body.classList.contains('dark-mode') +const isDarkMode = document.body.classList.contains('dark-mode'); -const LoadingCard: React.FC = () => ( - - - - -
    -
    -
    - -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    - {t('general.user.score')} - - - -
    -
    - {t('admin.permission')} - - - -
    -
    - {t('admin.verification')} - - - -
    -
    -
    - -
    -
    -
    -
    -) - -export default LoadingCard +export default function LoadingCard() { + return ( + + + + +
    +
    +
    + +
    +
    +
    +
    +
    + +
    +
    + +
    + +
    + {t('general.user.score')} + + + +
    +
    + {t('admin.permission')} + + + +
    +
    + {t('admin.verification')} + + + +
    +
    +
    + +
    +
    +
    + + ); +} diff --git a/resources/assets/src/views/admin/UsersManagement/LoadingRow.tsx b/resources/assets/src/views/admin/UsersManagement/LoadingRow.tsx index 3687744e..8166dd0c 100644 --- a/resources/assets/src/views/admin/UsersManagement/LoadingRow.tsx +++ b/resources/assets/src/views/admin/UsersManagement/LoadingRow.tsx @@ -1,17 +1,16 @@ -import React from 'react' -import styled from '@emotion/styled' -import Skeleton from 'react-loading-skeleton' +import styled from '@emotion/styled'; +import Skeleton from 'react-loading-skeleton'; const ThickSkeleton = styled(Skeleton)` line-height: 2; -` +`; -const LoadingRow: React.FC = () => ( - - - - - -) - -export default LoadingRow +export default function LoadingRow() { + return ( + + + + + + ); +} diff --git a/resources/assets/src/views/admin/UsersManagement/Row.tsx b/resources/assets/src/views/admin/UsersManagement/Row.tsx index 0813f5b4..ea09bd33 100644 --- a/resources/assets/src/views/admin/UsersManagement/Row.tsx +++ b/resources/assets/src/views/admin/UsersManagement/Row.tsx @@ -1,114 +1,114 @@ -import React from 'react' -import { t } from '@/scripts/i18n' -import type { User } from '@/scripts/types' -import ButtonEdit from '@/components/ButtonEdit' + import { - humanizePermission, - verificationStatusText, - canModifyUser, - canModifyPermission, -} from './utils' + humanizePermission, + verificationStatusText, + canModifyUser, + canModifyPermission, +} from './utils'; +import {t} from '@/scripts/i18n'; +import type {User} from '@/scripts/types'; +import ButtonEdit from '@/components/ButtonEdit'; -interface Props { - user: User - currentUser: User - onEmailChange(): void - onNicknameChange(): void - onScoreChange(): void - onPermissionChange(): void - onVerificationToggle(): void - onPasswordChange(): void - onDelete(): void -} +type Properties = { + readonly user: User; + readonly currentUser: User; + onEmailChange(): void; + onNicknameChange(): void; + onScoreChange(): void; + onPermissionChange(): void; + onVerificationToggle(): void; + onPasswordChange(): void; + onDelete(): void; +}; -const Row: React.FC = (props) => { - const { user, currentUser } = props +const Row: React.FC = properties => { + const {user, currentUser} = properties; - const canModify = canModifyUser(user, currentUser) + const canModify = canModifyUser(user, currentUser); - return ( - - {user.uid} - - {user.email} - {canModify && ( - - - - )} - - - {user.nickname} - {canModify && ( - - - - )} - - - {user.score} - {canModify && ( - - - - )} - - - {humanizePermission(user.permission)} - {canModifyPermission(user, currentUser) && ( - - - - )} - - - {verificationStatusText(user.verified)} - {canModify && ( - - {user.verified ? ( - - ) : ( - - )} - - )} - - {user.register_at} - - - - - - ) -} + return ( + + {user.uid} + + {user.email} + {canModify && ( + + + + )} + + + {user.nickname} + {canModify && ( + + + + )} + + + {user.score} + {canModify && ( + + + + )} + + + {humanizePermission(user.permission)} + {canModifyPermission(user, currentUser) && ( + + + + )} + + + {verificationStatusText(user.verified)} + {canModify && ( + + {user.verified ? ( + + ) : ( + + )} + + )} + + {user.register_at} + + + + + + ); +}; -export default Row +export default Row; diff --git a/resources/assets/src/views/admin/UsersManagement/index.tsx b/resources/assets/src/views/admin/UsersManagement/index.tsx index 9eeb32c7..c1a91d1f 100644 --- a/resources/assets/src/views/admin/UsersManagement/index.tsx +++ b/resources/assets/src/views/admin/UsersManagement/index.tsx @@ -1,370 +1,368 @@ -import React, { useState, useEffect, useLayoutEffect } from 'react' -import { hot } from 'react-hot-loader/root' -import { useImmer } from 'use-immer' -import useBlessingExtra from '@/scripts/hooks/useBlessingExtra' -import useIsLargeScreen from '@/scripts/hooks/useIsLargeScreen' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { User, UserPermission, Paginator } from '@/scripts/types' -import { toast, showModal } from '@/scripts/notify' -import urls from '@/scripts/urls' -import type { Props as ModalInputProps } from '@/components/ModalInput' -import Pagination from '@/components/Pagination' -import Header from './Header' -import Card from './Card' -import LoadingCard from './LoadingCard' -import Row from './Row' -import LoadingRow from './LoadingRow' +import {useState, useEffect, useLayoutEffect} from 'react'; +import {useImmer} from 'use-immer'; +import Header from './Header'; +import Card from './Card'; +import LoadingCard from './LoadingCard'; +import Row from './Row'; +import LoadingRow from './LoadingRow'; +import useBlessingExtra from '@/scripts/hooks/useBlessingExtra'; +import useIsLargeScreen from '@/scripts/hooks/useIsLargeScreen'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {type User, UserPermission, type Paginator} from '@/scripts/types'; +import {toast, showModal} from '@/scripts/notify'; +import urls from '@/scripts/urls'; +import type {Props as ModalInputProperties} from '@/components/ModalInput'; +import Pagination from '@/components/Pagination'; -const UsersManagement: React.FC = () => { - const [users, setUsers] = useImmer([]) - const [page, setPage] = useState(1) - const [totalPages, setTotalPages] = useState(1) - const [isLoading, setIsLoading] = useState(false) - const isLargeScreen = useIsLargeScreen() - const [isTableMode, setIsTableMode] = useState(false) - const [query, setQuery] = useState('') - const currentUser = useBlessingExtra('currentUser', { - uid: 0, - permission: UserPermission.Admin, - } as User) +function UsersManagement() { + const [users, setUsers] = useImmer([]); + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [isLoading, setIsLoading] = useState(false); + const isLargeScreen = useIsLargeScreen(); + const [isTableMode, setIsTableMode] = useState(false); + const [query, setQuery] = useState(''); + const currentUser = useBlessingExtra('currentUser', { + uid: 0, + permission: UserPermission.Admin, + } as User); - useLayoutEffect(() => { - if (isLargeScreen) { - setIsTableMode(true) - } - }, [isLargeScreen]) + useLayoutEffect(() => { + if (isLargeScreen) { + setIsTableMode(true); + } + }, [isLargeScreen]); - const getUsers = async () => { - setIsLoading(true) - const { data, last_page }: Paginator = await fetch.get( - urls.admin.users.list(), - { - q: query, - page, - }, - ) - setUsers(() => data) - setTotalPages(last_page) - setIsLoading(false) - } + const getUsers = async () => { + setIsLoading(true); + const {data, last_page}: Paginator = await fetch.get( + urls.admin.users.list(), + { + q: query, + page: page.toString(), + }, + ); + setUsers(() => data); + setTotalPages(last_page); + setIsLoading(false); + }; - useEffect(() => { - getUsers() - }, [page]) + useEffect(() => { + getUsers(); + }, [page]); - const handleModeChange = (event: React.ChangeEvent) => { - setIsTableMode(event.target.value === 'table') - } + const handleModeChange = (event: React.ChangeEvent) => { + setIsTableMode(event.target.value === 'table'); + }; - const handleQueryChange = (event: React.ChangeEvent) => { - setQuery(event.target.value) - } + const handleQueryChange = (event: React.ChangeEvent) => { + setQuery(event.target.value); + }; - const handleSubmitQuery = (event: React.FormEvent) => { - event.preventDefault() - getUsers() - } + const handleSubmitQuery = (event: React.FormEvent) => { + event.preventDefault(); + getUsers(); + }; - const handleEmailChange = async (user: User, index: number) => { - let email: string - try { - const { value } = await showModal({ - mode: 'prompt', - text: t('admin.newUserEmail'), - input: user.email, - inputMode: 'email', - validator: (value: string) => { - if (!value) { - return t('auth.emptyEmail') - } - }, - }) - email = value - } catch { - return - } + const handleEmailChange = async (user: User, index: number) => { + let email: string; + try { + const {value} = await showModal({ + mode: 'prompt', + text: t('admin.newUserEmail'), + input: user.email, + inputMode: 'email', + validator(value: string) { + if (!value) { + return t('auth.emptyEmail'); + } + }, + }); + email = value; + } catch { + return; + } - const { code, message } = await fetch.put( - urls.admin.users.email(user.uid), - { email }, - ) - if (code === 0) { - toast.success(message) - setUsers((users) => { - users[index]!.email = email - }) - } else { - toast.error(message) - } - } + const {code, message} = await fetch.put( + urls.admin.users.email(user.uid), + {email}, + ); + if (code === 0) { + toast.success(message); + setUsers(users => { + users[index].email = email; + }); + } else { + toast.error(message); + } + }; - const handleNicknameChange = async (user: User, index: number) => { - let nickname: string - try { - const { value } = await showModal({ - mode: 'prompt', - text: t('admin.newUserNickname'), - input: user.nickname, - validator: (value: string) => { - if (!value) { - return t('auth.emptyNickname') - } - }, - }) - nickname = value - } catch { - return - } + const handleNicknameChange = async (user: User, index: number) => { + let nickname: string; + try { + const {value} = await showModal({ + mode: 'prompt', + text: t('admin.newUserNickname'), + input: user.nickname, + validator(value: string) { + if (!value) { + return t('auth.emptyNickname'); + } + }, + }); + nickname = value; + } catch { + return; + } - const { code, message } = await fetch.put( - urls.admin.users.nickname(user.uid), - { nickname }, - ) - if (code === 0) { - toast.success(message) - setUsers((users) => { - users[index]!.nickname = nickname - }) - } else { - toast.error(message) - } - } + const {code, message} = await fetch.put( + urls.admin.users.nickname(user.uid), + {nickname}, + ); + if (code === 0) { + toast.success(message); + setUsers(users => { + users[index].nickname = nickname; + }); + } else { + toast.error(message); + } + }; - const handleScoreChange = async (user: User, index: number) => { - let score: number - try { - const { value } = await showModal({ - mode: 'prompt', - text: t('admin.newScore'), - input: user.score.toString(), - inputMode: 'numeric', - }) - score = Number.parseInt(value) - } catch { - return - } + const handleScoreChange = async (user: User, index: number) => { + let score: number; + try { + const {value} = await showModal({ + mode: 'prompt', + text: t('admin.newScore'), + input: user.score.toString(), + inputMode: 'numeric', + }); + score = Number.parseInt(value); + } catch { + return; + } - const { code, message } = await fetch.put( - urls.admin.users.score(user.uid), - { score }, - ) - if (code === 0) { - toast.success(message) - setUsers((users) => { - users[index]!.score = score - }) - } else { - toast.error(message) - } - } + const {code, message} = await fetch.put( + urls.admin.users.score(user.uid), + {score}, + ); + if (code === 0) { + toast.success(message); + setUsers(users => { + users[index].score = score; + }); + } else { + toast.error(message); + } + }; - const handlePermissionChange = async (user: User, index: number) => { - const permissions: ModalInputProps['choices'] = [ - { text: t('admin.banned'), value: '-1' }, - { text: t('admin.normal'), value: '0' }, - ] - if (currentUser.permission > UserPermission.Admin) { - permissions.push({ text: t('admin.admin'), value: '1' }) - } + const handlePermissionChange = async (user: User, index: number) => { + const permissions: ModalInputProperties['choices'] = [ + {text: t('admin.banned'), value: '-1'}, + {text: t('admin.normal'), value: '0'}, + ]; + if (currentUser.permission > UserPermission.Admin) { + permissions.push({text: t('admin.admin'), value: '1'}); + } - let permission: UserPermission - try { - const { value } = await showModal({ - mode: 'prompt', - text: t('admin.newPermission'), - input: user.permission.toString(), - inputType: 'radios', - choices: permissions, - }) - permission = Number.parseInt(value) - } catch { - return - } + let permission: UserPermission; + try { + const {value} = await showModal({ + mode: 'prompt', + text: t('admin.newPermission'), + input: user.permission.toString(), + inputType: 'radios', + choices: permissions, + }); + permission = Number.parseInt(value); + } catch { + return; + } - const { code, message } = await fetch.put( - urls.admin.users.permission(user.uid), - { permission }, - ) - if (code === 0) { - toast.success(message) - setUsers((users) => { - users[index]!.permission = permission - }) - } else { - toast.error(message) - } - } + const {code, message} = await fetch.put( + urls.admin.users.permission(user.uid), + {permission}, + ); + if (code === 0) { + toast.success(message); + setUsers(users => { + users[index].permission = permission; + }); + } else { + toast.error(message); + } + }; - const handleVerificationToggle = async (user: User, index: number) => { - const { code, message } = await fetch.put( - urls.admin.users.verification(user.uid), - ) - if (code === 0) { - toast.success(message) - setUsers((users) => { - users[index]!.verified = !users[index]!.verified - }) - } else { - toast.error(message) - } - } + const handleVerificationToggle = async (user: User, index: number) => { + const {code, message} = await fetch.put( + urls.admin.users.verification(user.uid), + ); + if (code === 0) { + toast.success(message); + setUsers(users => { + users[index].verified = !users[index].verified; + }); + } else { + toast.error(message); + } + }; - const handlePasswordChange = async (user: User) => { - let password: string - try { - const { value } = await showModal({ - mode: 'prompt', - text: t('admin.newUserPassword'), - inputType: 'password', - placeholder: t('admin.changePassword'), - }) - password = value - } catch { - return - } + const handlePasswordChange = async (user: User) => { + let password: string; + try { + const {value} = await showModal({ + mode: 'prompt', + text: t('admin.newUserPassword'), + inputType: 'password', + placeholder: t('admin.changePassword'), + }); + password = value; + } catch { + return; + } - const { code, message } = await fetch.put( - urls.admin.users.password(user.uid), - { password }, - ) - if (code === 0) { - toast.success(message) - } else { - toast.error(message) - } - } + const {code, message} = await fetch.put( + urls.admin.users.password(user.uid), + {password}, + ); + if (code === 0) { + toast.success(message); + } else { + toast.error(message); + } + }; - const handleDelete = async (user: User) => { - try { - await showModal({ - text: t('admin.deleteUserNotice'), - okButtonType: 'danger', - }) - } catch { - return - } + const handleDelete = async (user: User) => { + try { + await showModal({ + text: t('admin.deleteUserNotice'), + okButtonType: 'danger', + }); + } catch { + return; + } - const { code, message } = await fetch.del(urls.admin.users.delete(user.uid)) - if (code === 0) { - toast.success(message) - setUsers((users) => users.filter(({ uid }) => uid !== user.uid)) - } else { - toast.error(message) - } - } + const {code, message} = await fetch.del(urls.admin.users.delete(user.uid)); + if (code === 0) { + toast.success(String(message)); + setUsers(users => users.filter(({uid}) => uid !== user.uid)); + } else { + toast.error(String(message)); + } + }; - return ( -
    -
    -
    - -
    - -
    -
    -
    - - -
    -
    - {users.length === 0 && !isLoading ? ( -
    {t('general.noResult')}
    - ) : isTableMode ? ( -
    - - - - - - - - - - - - - - - {isLoading - ? new Array(10).fill(null).map((_, i) => ) - : users.map((user, i) => ( - handleEmailChange(user, i)} - onNicknameChange={() => handleNicknameChange(user, i)} - onScoreChange={() => handleScoreChange(user, i)} - onPermissionChange={() => handlePermissionChange(user, i)} - onVerificationToggle={() => - handleVerificationToggle(user, i) - } - onPasswordChange={() => handlePasswordChange(user)} - onDelete={() => handleDelete(user)} - /> - ))} - -
    UID{t('general.user.email')}{t('general.user.nickname')}{t('general.user.score')}{t('admin.permission')}{t('admin.verification')}{t('general.user.register-at')}{t('admin.operationsTitle')}
    -
    - ) : ( -
    - {isLoading - ? new Array(10).fill(null).map((_, i) => ) - : users.map((user, i) => ( - handleEmailChange(user, i)} - onNicknameChange={() => handleNicknameChange(user, i)} - onScoreChange={() => handleScoreChange(user, i)} - onPermissionChange={() => handlePermissionChange(user, i)} - onVerificationToggle={() => handleVerificationToggle(user, i)} - onPasswordChange={() => handlePasswordChange(user)} - onDelete={() => handleDelete(user)} - /> - ))} -
    - )} -
    -
    - -
    -
    -
    - ) + return ( +
    +
    +
    + +
    + +
    +
    +
    + + +
    +
    + {users.length === 0 && !isLoading ? ( +
    {t('general.noResult')}
    + ) : (isTableMode ? ( +
    + + + + + + + + + + + + + + + {isLoading + ? Array.from({length: 10}).fill(null).map((_, i) => ) + : users.map((user, i) => ( + handleEmailChange(user, i)} + onNicknameChange={async () => handleNicknameChange(user, i)} + onScoreChange={async () => handleScoreChange(user, i)} + onPermissionChange={async () => handlePermissionChange(user, i)} + onVerificationToggle={async () => + handleVerificationToggle(user, i)} + onPasswordChange={async () => handlePasswordChange(user)} + onDelete={async () => handleDelete(user)} + /> + ))} + +
    UID{t('general.user.email')}{t('general.user.nickname')}{t('general.user.score')}{t('admin.permission')}{t('admin.verification')}{t('general.user.register-at')}{t('admin.operationsTitle')}
    +
    + ) : ( +
    + {isLoading + ? Array.from({length: 10}).fill(null).map((_, i) => ) + : users.map((user, i) => ( + handleEmailChange(user, i)} + onNicknameChange={async () => handleNicknameChange(user, i)} + onScoreChange={async () => handleScoreChange(user, i)} + onPermissionChange={async () => handlePermissionChange(user, i)} + onVerificationToggle={async () => handleVerificationToggle(user, i)} + onPasswordChange={async () => handlePasswordChange(user)} + onDelete={async () => handleDelete(user)} + /> + ))} +
    + ))} +
    +
    + +
    +
    +
    + ); } -export default hot(UsersManagement) +export default UsersManagement; diff --git a/resources/assets/src/views/admin/UsersManagement/styles.ts b/resources/assets/src/views/admin/UsersManagement/styles.ts index 3a1850df..98fe657f 100644 --- a/resources/assets/src/views/admin/UsersManagement/styles.ts +++ b/resources/assets/src/views/admin/UsersManagement/styles.ts @@ -1,5 +1,5 @@ -import styled from '@emotion/styled' -import * as breakpoints from '@/styles/breakpoints' +import styled from '@emotion/styled'; +import * as breakpoints from '@/styles/breakpoints'; export const Box = styled.div` width: 48%; @@ -8,14 +8,14 @@ export const Box = styled.div` ${breakpoints.lessThan(breakpoints.Breakpoint.lg)} { width: 98%; } -` +`; -export const Icon = styled.div<{ py?: boolean }>` +export const Icon = styled.div<{py?: boolean}>` width: 70px; display: flex; justify-content: center; - padding-top: ${(props) => (props.py ? '22px' : '0')}; -` + padding-top: ${properties => (properties.py ? '22px' : '0')}; +`; export const InfoTable = styled.div` > div:not(:last-child) { @@ -26,4 +26,4 @@ export const InfoTable = styled.div` border-right: 1px solid rgba(0, 0, 0, 0.125); } } -` +`; diff --git a/resources/assets/src/views/admin/UsersManagement/utils.ts b/resources/assets/src/views/admin/UsersManagement/utils.ts index 3ef0b83f..14e36308 100644 --- a/resources/assets/src/views/admin/UsersManagement/utils.ts +++ b/resources/assets/src/views/admin/UsersManagement/utils.ts @@ -1,27 +1,34 @@ -import { t } from '@/scripts/i18n' -import { User, UserPermission } from '@/scripts/types' +import {t} from '@/scripts/i18n'; +import {type User, UserPermission} from '@/scripts/types'; export function humanizePermission(permission: UserPermission): string { - switch (permission) { - case UserPermission.Banned: - return t('admin.banned') - case UserPermission.Normal: - return t('admin.normal') - case UserPermission.Admin: - return t('admin.admin') - case UserPermission.SuperAdmin: - return t('admin.superAdmin') - } + switch (permission) { + case UserPermission.Banned: { + return t('admin.banned'); + } + + case UserPermission.Normal: { + return t('admin.normal'); + } + + case UserPermission.Admin: { + return t('admin.admin'); + } + + case UserPermission.SuperAdmin: { + return t('admin.superAdmin'); + } + } } export function verificationStatusText(isVerified: boolean): string { - return isVerified ? t('admin.verified') : t('admin.unverified') + return isVerified ? t('admin.verified') : t('admin.unverified'); } export function canModifyUser(target: User, current: User): boolean { - return target.uid === current.uid || current.permission > target.permission + return target.uid === current.uid || current.permission > target.permission; } export function canModifyPermission(target: User, current: User): boolean { - return current.permission > target.permission + return current.permission > target.permission; } diff --git a/resources/assets/src/views/auth/Forgot.tsx b/resources/assets/src/views/auth/Forgot.tsx index 346b1482..81b8a4be 100644 --- a/resources/assets/src/views/auth/Forgot.tsx +++ b/resources/assets/src/views/auth/Forgot.tsx @@ -1,74 +1,72 @@ -import React, { useState, useRef } from 'react' -import { hot } from 'react-hot-loader/root' -import useEmitMounted from '@/scripts/hooks/useEmitMounted' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import urls from '@/scripts/urls' -import Alert from '@/components/Alert' -import Captcha from '@/components/Captcha' -import EmailSuggestion from '@/components/EmailSuggestion' +import {useState, useRef} from 'react'; +import useEmitMounted from '@/scripts/hooks/useEmitMounted'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import urls from '@/scripts/urls'; +import Alert from '@/components/Alert'; +import Captcha from '@/components/Captcha'; +import EmailSuggestion from '@/components/EmailSuggestion'; -const Forgot: React.FC = () => { - const [email, setEmail] = useState('') - const [isSending, setIsSending] = useState(false) - const [successMessage, setSuccessMessage] = useState('') - const [warningMessage, setWarningMessage] = useState('') - const ref = useRef(null) +export default function Forgot() { + const [email, setEmail] = useState(''); + const [isSending, setIsSending] = useState(false); + const [successMessage, setSuccessMessage] = useState(''); + const [warningMessage, setWarningMessage] = useState(''); + const reference = useRef(null); - useEmitMounted() + useEmitMounted(); - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault() - setWarningMessage('') - setIsSending(true) + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + setWarningMessage(''); + setIsSending(true); - const captcha = await ref.current!.execute() - const { code, message } = await fetch.post( - urls.auth.forgot(), - { email, captcha }, - ) - if (code === 0) { - setSuccessMessage(message) - } else { - setWarningMessage(message) - ref.current!.reset() - } - setIsSending(false) - } + const captcha = await reference.current!.execute(); + const {code, message} = await fetch.post( + urls.auth.forgot(), + {email, captcha}, + ); + if (code === 0) { + setSuccessMessage(message); + } else { + setWarningMessage(message); + reference.current!.reset(); + } - return ( -
    - + setIsSending(false); + }; - + return ( + + - {successMessage} - {warningMessage} + -
    - - {t('auth.forgot.login-link')} - - -
    - - ) + {successMessage} + {warningMessage} + +
    + + {t('auth.forgot.login-link')} + + +
    + + ); } - -export default hot(Forgot) diff --git a/resources/assets/src/views/auth/Login.tsx b/resources/assets/src/views/auth/Login.tsx index bd164025..9a2fe2e7 100644 --- a/resources/assets/src/views/auth/Login.tsx +++ b/resources/assets/src/views/auth/Login.tsx @@ -1,157 +1,154 @@ -import React, { useState, useRef, useEffect } from 'react' -import { hot } from 'react-hot-loader/root' -import useBlessingExtra from '@/scripts/hooks/useBlessingExtra' -import useEmitMounted from '@/scripts/hooks/useEmitMounted' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { showModal } from '@/scripts/notify' -import urls from '@/scripts/urls' -import Alert from '@/components/Alert' -import Captcha from '@/components/Captcha' -import EmailSuggestion from '@/components/EmailSuggestion' +import {useState, useRef, useEffect} from 'react'; +import useBlessingExtra from '@/scripts/hooks/useBlessingExtra'; +import useEmitMounted from '@/scripts/hooks/useEmitMounted'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {showModal} from '@/scripts/notify'; +import urls from '@/scripts/urls'; +import Alert from '@/components/Alert'; +import Captcha from '@/components/Captcha'; +import EmailSuggestion from '@/components/EmailSuggestion'; type SuccessfulResponse = { - code: 0 - message: string - data: { redirectTo: string } -} + code: 0; + message: string; + data: {redirectTo: string}; +}; type FailedResponse = { - code: number - message: string - data: { login_fails: number } -} -type Response = SuccessfulResponse | FailedResponse + code: number; + message: string; + data: {login_fails: number}; +}; +type Response = SuccessfulResponse | FailedResponse; function isSuccessfulResponse( - response: Response, + response: Response, ): response is SuccessfulResponse { - return response.code === 0 + return response.code === 0; } -const Login: React.FC = () => { - const [identification, setIdentification] = useState('') - const [password, setPassword] = useState('') - const [remember, setRemember] = useState(false) - const [hasTooManyFails, setHasTooManyFails] = useState(false) - const [isPending, setIsPending] = useState(false) - const [warningMessage, setWarningMessage] = useState('') - const ref = useRef(null) - const recaptcha = useBlessingExtra('recaptcha') - const invisibleRecaptcha = useBlessingExtra('invisible') +export default function Login() { + const [identification, setIdentification] = useState(''); + const [password, setPassword] = useState(''); + const [remember, setRemember] = useState(false); + const [hasTooManyFails, setHasTooManyFails] = useState(false); + const [isPending, setIsPending] = useState(false); + const [warningMessage, setWarningMessage] = useState(''); + const reference = useRef(null); + const recaptcha = useBlessingExtra('recaptcha'); + const invisibleRecaptcha = useBlessingExtra('invisible'); - useEmitMounted() + useEmitMounted(); - useEffect(() => { - setHasTooManyFails(blessing.extra.tooManyFails as boolean) - }, []) + useEffect(() => { + setHasTooManyFails(blessing.extra.tooManyFails as boolean); + }, []); - const handlePasswordChange = (event: React.ChangeEvent) => { - setPassword(event.target.value) - } + const handlePasswordChange = (event: React.ChangeEvent) => { + setPassword(event.target.value); + }; - const handleRememberChange = (event: React.ChangeEvent) => { - setRemember(event.target.checked) - } + const handleRememberChange = (event: React.ChangeEvent) => { + setRemember(event.target.checked); + }; - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault() - setIsPending(true) + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + setIsPending(true); - const response = await fetch.post(urls.auth.login(), { - identification, - password, - keep: remember, - captcha: hasTooManyFails ? await ref.current!.execute() : undefined, - }) + const response = await fetch.post(urls.auth.login(), { + identification, + password, + keep: remember, + captcha: hasTooManyFails ? await reference.current!.execute() : undefined, + }); - if (isSuccessfulResponse(response)) { - window.location.href = response.data.redirectTo - } else { - setWarningMessage(response.message) - setIsPending(false) - ref.current?.reset() + if (isSuccessfulResponse(response)) { + window.location.href = response.data.redirectTo; + } else { + setWarningMessage(response.message); + setIsPending(false); + reference.current?.reset(); - // only notify user if he/she fails too much at the first time - if (response.data.login_fails > 3 && !hasTooManyFails) { - setHasTooManyFails(true) - if (recaptcha) { - // no need to notify if using invisible recaptcha - if (!invisibleRecaptcha) { - showModal({ - mode: 'alert', - text: t('auth.tooManyFails.recaptcha'), - }) - } - } else { - showModal({ - mode: 'alert', - text: t('auth.tooManyFails.captcha'), - }) - } - } - } - } + // Only notify user if he/she fails too much at the first time + if (response.data.login_fails > 3 && !hasTooManyFails) { + setHasTooManyFails(true); + if (recaptcha) { + // No need to notify if using invisible recaptcha + if (!invisibleRecaptcha) { + void showModal({ + mode: 'alert', + text: t('auth.tooManyFails.recaptcha'), + }); + } + } else { + void showModal({ + mode: 'alert', + text: t('auth.tooManyFails.captcha'), + }); + } + } + } + }; - return ( -
    - -
    - -
    -
    - -
    -
    -
    + return ( + + +
    + +
    +
    + +
    +
    +
    - {hasTooManyFails && } + {hasTooManyFails && } - {warningMessage} + {warningMessage} -
    - - {t('auth.forgot-link')} -
    +
    + + {t('auth.forgot-link')} +
    - - - ) + + + ); } - -export default hot(Login) diff --git a/resources/assets/src/views/auth/Registration.tsx b/resources/assets/src/views/auth/Registration.tsx index f18c577a..d55d199f 100644 --- a/resources/assets/src/views/auth/Registration.tsx +++ b/resources/assets/src/views/auth/Registration.tsx @@ -1,178 +1,176 @@ -import React, { useState, useRef } from 'react' -import { hot } from 'react-hot-loader/root' -import useBlessingExtra from '@/scripts/hooks/useBlessingExtra' -import useEmitMounted from '@/scripts/hooks/useEmitMounted' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { toast } from '@/scripts/notify' -import urls from '@/scripts/urls' -import Alert from '@/components/Alert' -import Captcha from '@/components/Captcha' -import EmailSuggestion from '@/components/EmailSuggestion' +import {useState, useRef} from 'react'; +import useBlessingExtra from '@/scripts/hooks/useBlessingExtra'; +import useEmitMounted from '@/scripts/hooks/useEmitMounted'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {toast} from '@/scripts/notify'; +import urls from '@/scripts/urls'; +import Alert from '@/components/Alert'; +import Captcha from '@/components/Captcha'; +import EmailSuggestion from '@/components/EmailSuggestion'; -const Registration: React.FC = () => { - const [email, setEmail] = useState('') - const [password, setPassword] = useState('') - const [confirmation, setConfirmation] = useState('') - const [nickName, setNickName] = useState('') - const [playerName, setPlayerName] = useState('') - const [isPending, setIsPending] = useState(false) - const [warningMessage, setWarningMessage] = useState('') - const requirePlayer = useBlessingExtra('player') - const confirmationRef = useRef(null) - const captchaRef = useRef(null) +export default function Registration() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [confirmation, setConfirmation] = useState(''); + const [nickName, setNickName] = useState(''); + const [playerName, setPlayerName] = useState(''); + const [isPending, setIsPending] = useState(false); + const [warningMessage, setWarningMessage] = useState(''); + const requirePlayer = useBlessingExtra('player'); + const confirmationReference = useRef(null); + const captchaReference = useRef(null); - useEmitMounted() + useEmitMounted(); - const handlePasswordChange = (event: React.ChangeEvent) => { - setPassword(event.target.value) - } + const handlePasswordChange = (event: React.ChangeEvent) => { + setPassword(event.target.value); + }; - const handleConfirmationChange = ( - event: React.ChangeEvent, - ) => { - setConfirmation(event.target.value) - } + const handleConfirmationChange = ( + event: React.ChangeEvent, + ) => { + setConfirmation(event.target.value); + }; - const handleNickNameChange = (event: React.ChangeEvent) => { - setNickName(event.target.value) - } + const handleNickNameChange = (event: React.ChangeEvent) => { + setNickName(event.target.value); + }; - const handlePlayerNameChange = ( - event: React.ChangeEvent, - ) => { - setPlayerName(event.target.value) - } + const handlePlayerNameChange = ( + event: React.ChangeEvent, + ) => { + setPlayerName(event.target.value); + }; - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault() - setWarningMessage('') + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + setWarningMessage(''); - if (password !== confirmation) { - setWarningMessage(t('auth.invalidConfirmPwd')) - confirmationRef.current!.focus() - return - } + if (password !== confirmation) { + setWarningMessage(t('auth.invalidConfirmPwd')); + confirmationReference.current!.focus(); + return; + } - setIsPending(true) - const { code, message } = await fetch.post( - urls.auth.register(), - Object.assign( - { email, password, captcha: await captchaRef.current!.execute() }, - requirePlayer ? { player_name: playerName } : { nickname: nickName }, - ), - ) - if (code === 0) { - toast.success(message) - setTimeout(() => { - window.location.href = `${blessing.base_url}/user` - }, 3000) - } else { - setWarningMessage(message) - captchaRef.current!.reset() - } - setIsPending(false) - } + setIsPending(true); + const {code, message} = await fetch.post( + urls.auth.register(), + { + email, password, captcha: await captchaReference.current!.execute(), + ...(requirePlayer ? {player_name: playerName} : {nickname: nickName}), + }, + ); + if (code === 0) { + toast.success(message); + setTimeout(() => { + window.location.href = `${blessing.base_url}/user`; + }, 3000); + } else { + setWarningMessage(message); + captchaReference.current!.reset(); + } - return ( -
    - -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    -
    - {requirePlayer ? ( -
    - -
    -
    - -
    -
    -
    - ) : ( -
    - -
    -
    - -
    -
    -
    - )} - + setIsPending(false); + }; - {warningMessage} + return ( + + +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    + {requirePlayer ? ( +
    + +
    +
    + +
    +
    +
    + ) : ( +
    + +
    +
    + +
    +
    +
    + )} + -
    - {t('auth.login-link')} - -
    - - ) + {warningMessage} + +
    + {t('auth.login-link')} + +
    + + ); } - -export default hot(Registration) diff --git a/resources/assets/src/views/auth/Reset.tsx b/resources/assets/src/views/auth/Reset.tsx index 62a1b174..a5a652a8 100644 --- a/resources/assets/src/views/auth/Reset.tsx +++ b/resources/assets/src/views/auth/Reset.tsx @@ -1,111 +1,109 @@ -import React, { useState } from 'react' -import { hot } from 'react-hot-loader/root' -import useEmitMounted from '@/scripts/hooks/useEmitMounted' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { toast } from '@/scripts/notify' -import urls from '@/scripts/urls' -import Alert from '@/components/Alert' +import {useState} from 'react'; +import useEmitMounted from '@/scripts/hooks/useEmitMounted'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {toast} from '@/scripts/notify'; +import urls from '@/scripts/urls'; +import Alert from '@/components/Alert'; -const Reset: React.FC = () => { - const [password, setPassword] = useState('') - const [confirmation, setConfirmation] = useState('') - const [warningMessage, setWarningMessage] = useState('') - const [isPending, setIsPending] = useState(false) +export default function Reset() { + const [password, setPassword] = useState(''); + const [confirmation, setConfirmation] = useState(''); + const [warningMessage, setWarningMessage] = useState(''); + const [isPending, setIsPending] = useState(false); - useEmitMounted() + useEmitMounted(); - const handlePasswordChange = (event: React.ChangeEvent) => { - setPassword(event.target.value) - } + const handlePasswordChange = (event: React.ChangeEvent) => { + setPassword(event.target.value); + }; - const handleConfirmationChange = ( - event: React.ChangeEvent, - ) => { - setConfirmation(event.target.value) - } + const handleConfirmationChange = ( + event: React.ChangeEvent, + ) => { + setConfirmation(event.target.value); + }; - const handleSubmit = async (event: React.FormEvent) => { - event.preventDefault() + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); - if (password !== confirmation) { - setWarningMessage(t('auth.invalidConfirmPwd')) - return - } + if (password !== confirmation) { + setWarningMessage(t('auth.invalidConfirmPwd')); + return; + } - setIsPending(true) - const { code, message } = await fetch.post( - location.href.replace(blessing.base_url, ''), - { password }, - ) - if (code === 0) { - toast.success(message) - setTimeout(() => { - window.location.href = blessing.base_url + urls.auth.login() - }, 2000) - } else { - setWarningMessage(message) - setIsPending(false) - } - } + setIsPending(true); + const {code, message} = await fetch.post( + location.href.replace(blessing.base_url, ''), + {password}, + ); + if (code === 0) { + toast.success(message); + setTimeout(() => { + window.location.href = blessing.base_url + urls.auth.login(); + }, 2000); + } else { + setWarningMessage(message); + setIsPending(false); + } + }; - return ( -
    -
    - -
    -
    - -
    -
    -
    -
    - -
    -
    - -
    -
    -
    + return ( + +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    - {warningMessage} + {warningMessage} - - - ) + + + ); } -export default hot(Reset) diff --git a/resources/assets/src/views/skinlib/Show/addClosetItem.ts b/resources/assets/src/views/skinlib/Show/addClosetItem.ts index dda5a5b3..5124a7fb 100644 --- a/resources/assets/src/views/skinlib/Show/addClosetItem.ts +++ b/resources/assets/src/views/skinlib/Show/addClosetItem.ts @@ -1,39 +1,39 @@ -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { showModal, toast } from '@/scripts/notify' -import type { Texture } from '@/scripts/types' -import urls from '@/scripts/urls' +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {showModal, toast} from '@/scripts/notify'; +import type {Texture} from '@/scripts/types'; +import urls from '@/scripts/urls'; export default async function addClosetItem( - texture: Pick, + texture: Pick, ): Promise { - let name: string - try { - const { value } = await showModal({ - mode: 'prompt', - title: t('skinlib.setItemName'), - text: t('skinlib.applyNotice'), - input: texture.name, - validator: (value: string) => { - if (!value) { - return t('skinlib.emptyItemName') - } - }, - }) - name = value - } catch { - return false - } + let name: string; + try { + const {value} = await showModal({ + mode: 'prompt', + title: t('skinlib.setItemName'), + text: t('skinlib.applyNotice'), + input: texture.name, + validator(value: string) { + if (!value) { + return t('skinlib.emptyItemName'); + } + }, + }); + name = value; + } catch { + return false; + } - const { code, message } = await fetch.post( - urls.user.closet.add(), - { tid: texture.tid, name }, - ) - if (code === 0) { - toast.success(message) - } else { - toast.error(message) - } + const {code, message} = await fetch.post( + urls.user.closet.add(), + {tid: texture.tid, name}, + ); + if (code === 0) { + toast.success(message); + } else { + toast.error(message); + } - return code === 0 + return code === 0; } diff --git a/resources/assets/src/views/skinlib/Show/index.tsx b/resources/assets/src/views/skinlib/Show/index.tsx index e71b5ee7..931f585f 100644 --- a/resources/assets/src/views/skinlib/Show/index.tsx +++ b/resources/assets/src/views/skinlib/Show/index.tsx @@ -1,489 +1,496 @@ -import React, { useState, useEffect } from 'react' -import { createPortal } from 'react-dom' -import { hot } from 'react-hot-loader/root' -import Skeleton from 'react-loading-skeleton' -import useBlessingExtra from '@/scripts/hooks/useBlessingExtra' -import useEmitMounted from '@/scripts/hooks/useEmitMounted' -import useMount from '@/scripts/hooks/useMount' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { showModal, toast } from '@/scripts/notify' -import { Texture, TextureType } from '@/scripts/types' -import urls from '@/scripts/urls' -import ButtonEdit from '@/components/ButtonEdit' -import ViewerSkeleton from '@/components/ViewerSkeleton' -import ModalApply from '@/views/user/Closet/ModalApply' -import removeClosetItem from '@/views/user/Closet/removeClosetItem' -import setAsAvatar from '@/views/user/Closet/setAsAvatar' -import addClosetItem from './addClosetItem' +import React, {useState, useEffect} from 'react'; +import {createPortal} from 'react-dom'; +import Skeleton from 'react-loading-skeleton'; +import addClosetItem from './addClosetItem'; +import useBlessingExtra from '@/scripts/hooks/useBlessingExtra'; +import useEmitMounted from '@/scripts/hooks/useEmitMounted'; +import useMount from '@/scripts/hooks/useMount'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {showModal, toast} from '@/scripts/notify'; +import {type Texture, TextureType} from '@/scripts/types'; +import urls from '@/scripts/urls'; +import ButtonEdit from '@/components/ButtonEdit'; +import ViewerSkeleton from '@/components/ViewerSkeleton'; +import ModalApply from '@/views/user/Closet/ModalApply'; +import removeClosetItem from '@/views/user/Closet/removeClosetItem'; +import setAsAvatar from '@/views/user/Closet/setAsAvatar'; export type Badge = { - color: string - text: string + color: string; + text: string; +}; + +const Previewer = React.lazy(async () => import('@/components/Viewer')); + +export default function Show() { + const [texture, setTexture] = useState({} as Texture); + const [isLoading, setIsLoading] = useState(true); + const [showModalApply, setShowModalApply] = useState(false); + const [liked, setLiked] = useState(false); + const nickname = useBlessingExtra('nickname'); + const isUploaderExists = useBlessingExtra('uploaderExists'); + const currentUid = useBlessingExtra('currentUid', 0); + const isAdmin = useBlessingExtra('admin'); + const badges = useBlessingExtra('badges', []); + const canBeDownloaded = useBlessingExtra('download'); + const reportScore = useBlessingExtra('report'); + const container = useMount('#previewer'); + + useEmitMounted(); + + useEffect(() => { + const fetchInfo = async () => { + const url = location.href + .replace(blessing.base_url, '') + .replace('skinlib/show', 'texture'); + + const texture = await fetch.get(url); + setTexture(texture); + setIsLoading(false); + }; + + void fetchInfo(); + }, []); + + useEffect(() => { + setLiked(blessing.extra.inCloset as boolean); + }, []); + + async function handleEditName() { + let name: string; + try { + const {value} = await showModal({ + mode: 'prompt', + text: t('skinlib.setNewTextureName'), + input: texture.name, + validator(value: string) { + if (!value) { + return t('skinlib.emptyNewTextureName'); + } + }, + }); + name = value; + } catch { + return; + } + + const {code, message} = await fetch.put( + urls.texture.name(texture.tid), + {name}, + ); + if (code === 0) { + toast.success(message); + setTexture(texture => ({...texture, name})); + } else { + toast.error(message); + } + } + + async function handleSwitchType() { + let type: TextureType; + try { + const {value} = await showModal({ + mode: 'prompt', + text: t('skinlib.setNewTextureModel'), + input: texture.type, + inputType: 'radios', + choices: [ + {text: 'Steve', value: TextureType.Steve}, + {text: 'Alex', value: TextureType.Alex}, + {text: t('general.cape'), value: TextureType.Cape}, + ], + }); + type = value as TextureType; + } catch { + return; + } + + const {code, message} = await fetch.put( + urls.texture.type(texture.tid), + {type}, + ); + if (code === 0) { + toast.success(message); + setTexture(texture => ({...texture, type})); + } else { + toast.error(message); + } + } + + async function handleAddItemClick() { + const ok = await addClosetItem(texture); + if (ok) { + setTexture(texture => ({...texture, likes: texture.likes + 1})); + setLiked(true); + } + } + + async function handleRemoveItemClick() { + const ok = await removeClosetItem(texture.tid); + if (ok) { + setTexture(texture => ({...texture, likes: texture.likes - 1})); + setLiked(false); + } + } + + const handleSetAsAvatar = async () => setAsAvatar(texture.tid); + + async function handleReport() { + const prompt = (() => { + if (reportScore > 0) { + return t('skinlib.report.positive', {score: reportScore.toString()}); + } + + if (reportScore < 0) { + return t('skinlib.report.negative', {score: (-reportScore).toString()}); + } + + return ''; + })(); + + let reason: string; + try { + const {value} = await showModal({ + mode: 'prompt', + title: t('skinlib.report.title'), + text: prompt, + placeholder: t('skinlib.report.reason'), + }); + reason = value; + } catch { + return; + } + + const {code, message} = await fetch.post( + '/skinlib/report', + { + tid: texture.tid, + reason, + }, + ); + if (code === 0) { + toast.success(message); + } else { + toast.error(message); + } + } + + async function handlePrivacyClick() { + try { + await showModal({ + text: texture.public + ? t('skinlib.setPrivateNotice') + : t('skinlib.setPublicNotice'), + }); + } catch { + return; + } + + type OkResp = {code: 0; message: string}; + type ErrorResp = {code: 1; message: string}; + type DuplicatedResp = {code: 2; message: string; data: {tid: number}}; + + const resp = await fetch.put( + urls.texture.privacy(texture.tid), + ); + const {code, message} = resp; + if (code === 0) { + toast.success(message); + setTexture(texture => ({...texture, public: !texture.public})); + } else if (resp.code === 2) { + try { + await showModal({ + mode: 'confirm', + text: message, + okButtonText: t('user.viewInSkinlib'), + }); + window.location.href = blessing.base_url + urls.skinlib.show(resp.data.tid); + } catch { + void 0; + } + } else { + toast.error(message); + } + } + + async function handleDeleteTextureClick() { + try { + await showModal({ + text: t('skinlib.deleteNotice'), + okButtonType: 'danger', + }); + } catch { + return; + } + + const {code, message} = await fetch.del( + urls.texture.delete(texture.tid), + ); + if (code === 0) { + toast.success(message); + setTimeout(() => { + window.location.href = `${blessing.base_url}/skinlib`; + }, 2000); + } else { + toast.error(message); + } + } + + const handleOpenModalApply = () => { + setShowModalApply(true); + }; + + const handleCloseModalApply = () => { + setShowModalApply(false); + }; + + const linkToUploader = (() => { + const search = new URLSearchParams(); + search.append( + 'filter', + texture.type === TextureType.Cape ? TextureType.Cape : 'skin', + ); + search.append('uploader', texture.uploader?.toString()); + + return `${blessing.base_url}/skinlib?${search.toString()}`; + })(); + + const canEdit = currentUid === texture.uploader || isAdmin; + const textureUrl = texture.hash + ? `${blessing.base_url}/textures/${texture.hash}` + : ''; + + return ( + <> + {container + && createPortal( + }> + + {currentUid === 0 ? ( + + ) : ( +
    +
    + {liked && ( + + )} + {liked ? ( + + ) : ( + + )} + {texture.type !== TextureType.Cape && ( + + )} + {canBeDownloaded && ( + + {t('skinlib.show.download')} + + )} + +
    +
    + + {texture.likes} +
    +
    + )} +
    +
    , + container, + )} +
    +
    +

    {t('skinlib.show.detail')}

    +
    +
    +
    +
    +
    {t('skinlib.show.name')}
    + {isLoading ? ( +
    + +
    + ) : ( + <> +
    + {texture.name} +
    + {canEdit && ( +
    + +
    + )} + + )} +
    +
    +
    {t('skinlib.show.model')}
    + {isLoading ? ( +
    + +
    + ) : ( + <> +
    + {texture.type === TextureType.Cape + ? t('general.cape') + : texture.type} +
    + {canEdit && ( +
    + +
    + )} + + )} +
    +
    +
    Hash
    +
    + {isLoading ? : texture.hash} +
    +
    +
    +
    {t('skinlib.show.size')}
    +
    + {isLoading ? : {texture.size} KB} +
    +
    +
    +
    {t('skinlib.show.uploader')}
    +
    + {isLoading ? ( + + ) : (isUploaderExists ? ( + <> + +
    + {badges.map(badge => ( + + {badge.text} + + ))} +
    + + ) : ( + nickname + ))} +
    +
    +
    +
    {t('skinlib.show.upload-at')}
    +
    + {isLoading ? : texture.upload_at} +
    +
    +
    +
    +
    + {canEdit && ( +
    +
    +

    {t('admin.operationsTitle')}

    +
    +
    +

    {t('skinlib.show.manage-notice')}

    +
    +
    +
    + + +
    +
    +
    + )} + + + ); } - -const Previewer = React.lazy(() => import('@/components/Viewer')) - -const Show: React.FC = () => { - const [texture, setTexture] = useState({} as Texture) - const [isLoading, setIsLoading] = useState(true) - const [showModalApply, setShowModalApply] = useState(false) - const [liked, setLiked] = useState(false) - const nickname = useBlessingExtra('nickname') - const isUploaderExists = useBlessingExtra('uploaderExists') - const currentUid = useBlessingExtra('currentUid', 0) - const isAdmin = useBlessingExtra('admin') - const badges = useBlessingExtra('badges', []) - const canBeDownloaded = useBlessingExtra('download') - const reportScore = useBlessingExtra('report') - const container = useMount('#previewer') - - useEmitMounted() - - useEffect(() => { - const fetchInfo = async () => { - const url = location.href - .replace(blessing.base_url, '') - .replace('skinlib/show', 'texture') - - const texture = await fetch.get(url) - setTexture(texture) - setIsLoading(false) - } - fetchInfo() - }, []) - - useEffect(() => { - setLiked(blessing.extra.inCloset as boolean) - }, []) - - const handleEditName = async () => { - let name: string - try { - const { value } = await showModal({ - mode: 'prompt', - text: t('skinlib.setNewTextureName'), - input: texture.name, - validator: (value: string) => { - if (!value) { - return t('skinlib.emptyNewTextureName') - } - }, - }) - name = value - } catch { - return - } - - const { code, message } = await fetch.put( - urls.texture.name(texture.tid), - { name }, - ) - if (code === 0) { - toast.success(message) - setTexture((texture) => ({ ...texture, name })) - } else { - toast.error(message) - } - } - - const handleSwitchType = async () => { - let type: TextureType - try { - const { value } = await showModal({ - mode: 'prompt', - text: t('skinlib.setNewTextureModel'), - input: texture.type, - inputType: 'radios', - choices: [ - { text: 'Steve', value: TextureType.Steve }, - { text: 'Alex', value: TextureType.Alex }, - { text: t('general.cape'), value: TextureType.Cape }, - ], - }) - type = value as TextureType - } catch { - return - } - - const { code, message } = await fetch.put( - urls.texture.type(texture.tid), - { type }, - ) - if (code === 0) { - toast.success(message) - setTexture((texture) => ({ ...texture, type })) - } else { - toast.error(message) - } - } - - const handleAddItemClick = async () => { - const ok = await addClosetItem(texture) - if (ok) { - setTexture((texture) => ({ ...texture, likes: texture.likes + 1 })) - setLiked(true) - } - } - - const handleRemoveItemClick = async () => { - const ok = await removeClosetItem(texture.tid) - if (ok) { - setTexture((texture) => ({ ...texture, likes: texture.likes - 1 })) - setLiked(false) - } - } - - const handleSetAsAvatar = () => setAsAvatar(texture.tid) - - const handleDownloadClick = () => { - const a = document.createElement('a') - a.href = `${blessing.base_url}/raw/${texture.tid}` - a.download = `${texture.name}.png` - a.click() - } - - const handleReport = async () => { - const prompt = (() => { - if (reportScore > 0) { - return t('skinlib.report.positive', { score: reportScore }) - } else if (reportScore < 0) { - return t('skinlib.report.negative', { score: -reportScore }) - } - return '' - })() - - let reason: string - try { - const { value } = await showModal({ - mode: 'prompt', - title: t('skinlib.report.title'), - text: prompt, - placeholder: t('skinlib.report.reason'), - }) - reason = value - } catch { - return - } - - const { code, message } = await fetch.post( - '/skinlib/report', - { - tid: texture.tid, - reason, - }, - ) - if (code === 0) { - toast.success(message) - } else { - toast.error(message) - } - } - - const handlePrivacyClick = async () => { - try { - await showModal({ - text: texture.public - ? t('skinlib.setPrivateNotice') - : t('skinlib.setPublicNotice'), - }) - } catch { - return - } - - type Ok = { code: 0; message: string } - type Err = { code: 1; message: string } - type Duplicated = { code: 2; message: string; data: { tid: number } } - - const resp = await fetch.put( - urls.texture.privacy(texture.tid), - ) - const { code, message } = resp - if (code === 0) { - toast.success(message) - setTexture((texture) => ({ ...texture, public: !texture.public })) - } else if (resp.code === 2) { - try { - await showModal({ - mode: 'confirm', - text: message, - okButtonText: t('user.viewInSkinlib'), - }) - window.location.href = - blessing.base_url + urls.skinlib.show(resp.data.tid) - } catch { - // - } - } else { - toast.error(message) - } - } - - const handleDeleteTextureClick = async () => { - try { - await showModal({ - text: t('skinlib.deleteNotice'), - okButtonType: 'danger', - }) - } catch { - return - } - - const { code, message } = await fetch.del( - urls.texture.delete(texture.tid), - ) - if (code === 0) { - toast.success(message) - setTimeout(() => { - window.location.href = `${blessing.base_url}/skinlib` - }, 2000) - } else { - toast.error(message) - } - } - - const handleOpenModalApply = () => setShowModalApply(true) - const handleCloseModalApply = () => setShowModalApply(false) - - const linkToUploader = (() => { - const search = new URLSearchParams() - search.append( - 'filter', - texture.type === TextureType.Cape ? TextureType.Cape : 'skin', - ) - search.append('uploader', texture.uploader?.toString()) - - return `${blessing.base_url}/skinlib?${search.toString()}` - })() - - const canEdit = currentUid === texture.uploader || isAdmin - const textureUrl = texture.hash - ? `${blessing.base_url}/textures/${texture.hash}` - : '' - - return ( - <> - {container && - createPortal( - }> - - {currentUid === 0 ? ( - - ) : ( -
    -
    - {liked && ( - - )} - {liked ? ( - - ) : ( - - )} - {texture.type !== TextureType.Cape && ( - - )} - {canBeDownloaded && ( - - )} - -
    -
    - - {texture.likes} -
    -
    - )} -
    -
    , - container, - )} -
    -
    -

    {t('skinlib.show.detail')}

    -
    -
    -
    -
    -
    {t('skinlib.show.name')}
    - {isLoading ? ( -
    - -
    - ) : ( - <> -
    - {texture.name} -
    - {canEdit && ( -
    - -
    - )} - - )} -
    -
    -
    {t('skinlib.show.model')}
    - {isLoading ? ( -
    - -
    - ) : ( - <> -
    - {texture.type === TextureType.Cape - ? t('general.cape') - : texture.type} -
    - {canEdit && ( -
    - -
    - )} - - )} -
    -
    -
    Hash
    -
    - {isLoading ? : texture.hash} -
    -
    -
    -
    {t('skinlib.show.size')}
    -
    - {isLoading ? : {texture.size} KB} -
    -
    -
    -
    {t('skinlib.show.uploader')}
    -
    - {isLoading ? ( - - ) : isUploaderExists ? ( - <> - -
    - {badges.map((badge) => ( - - {badge.text} - - ))} -
    - - ) : ( - nickname - )} -
    -
    -
    -
    {t('skinlib.show.upload-at')}
    -
    - {isLoading ? : texture.upload_at} -
    -
    -
    -
    -
    - {canEdit && ( -
    -
    -

    {t('admin.operationsTitle')}

    -
    -
    -

    {t('skinlib.show.manage-notice')}

    -
    -
    -
    - - -
    -
    -
    - )} - - - ) -} - -export default hot(Show) diff --git a/resources/assets/src/views/skinlib/SkinLibrary/Button.tsx b/resources/assets/src/views/skinlib/SkinLibrary/Button.tsx index 623c43d6..280b49e4 100644 --- a/resources/assets/src/views/skinlib/SkinLibrary/Button.tsx +++ b/resources/assets/src/views/skinlib/SkinLibrary/Button.tsx @@ -1,31 +1,31 @@ -import React from 'react' -interface Props { - active?: boolean - bg?: string -} +type Properties = { + readonly active?: boolean; + readonly bg?: string; +}; type Attributes = React.DetailedHTMLProps< - React.ButtonHTMLAttributes, - HTMLButtonElement -> +React.ButtonHTMLAttributes, +HTMLButtonElement +>; -const Button: React.FC = (props) => { - const classes = [props.className ?? ''] - if (props.bg) { - classes.push('btn', `bg-${props.bg}`) - } - if (props.active) { - classes.push('active') - } +const Button: React.FC = properties => { + const classes = [properties.className ?? '']; + if (properties.bg) { + classes.push('btn', `bg-${properties.bg}`); + } - const rest = { ...props, active: undefined, bg: undefined } + if (properties.active) { + classes.push('active'); + } - return ( - - ) -} + const rest = {...properties, active: undefined, bg: undefined}; -export default Button + return ( + + ); +}; + +export default Button; diff --git a/resources/assets/src/views/skinlib/SkinLibrary/FilterSelector.tsx b/resources/assets/src/views/skinlib/SkinLibrary/FilterSelector.tsx index c215fa9e..8af50171 100644 --- a/resources/assets/src/views/skinlib/SkinLibrary/FilterSelector.tsx +++ b/resources/assets/src/views/skinlib/SkinLibrary/FilterSelector.tsx @@ -1,64 +1,75 @@ -import React from 'react' -import { t } from '@/scripts/i18n' -import { TextureType } from '@/scripts/types' -import Button from './Button' -import type { Filter } from './types' -import { humanizeType } from './utils' -interface Props { - filter: Filter - onChange(filter: Filter): void -} +import Button from './Button'; +import type {Filter} from './types'; +import {humanizeType} from './utils'; +import {TextureType} from '@/scripts/types'; +import {t} from '@/scripts/i18n'; -const FilterSelector: React.FC = (props) => { - const { filter, onChange } = props +type Properties = { + readonly filter: Filter; + onChange(filter: Filter): void; +}; - const handleSkinClick = () => onChange('skin') - const handleSteveClick = () => onChange(TextureType.Steve) - const handleAlexClick = () => onChange(TextureType.Alex) - const handleCapeClick = () => onChange(TextureType.Cape) +const FilterSelector: React.FC = properties => { + const {filter, onChange} = properties; - return ( - <> - -
    - - - - -
    - - ) -} + const handleSkinClick = () => { + onChange('skin'); + }; -export default FilterSelector + const handleSteveClick = () => { + onChange(TextureType.Steve); + }; + + const handleAlexClick = () => { + onChange(TextureType.Alex); + }; + + const handleCapeClick = () => { + onChange(TextureType.Cape); + }; + + return ( + <> + +
    + + + + +
    + + ); +}; + +export default FilterSelector; diff --git a/resources/assets/src/views/skinlib/SkinLibrary/Item.tsx b/resources/assets/src/views/skinlib/SkinLibrary/Item.tsx index 86f4f8cc..d5e4cecb 100644 --- a/resources/assets/src/views/skinlib/SkinLibrary/Item.tsx +++ b/resources/assets/src/views/skinlib/SkinLibrary/Item.tsx @@ -1,9 +1,9 @@ -import React from 'react' -import styled from '@emotion/styled' -import { t } from '@/scripts/i18n' -import * as cssUtils from '@/styles/utils' -import type { LibraryItem } from './types' -import { humanizeType } from './utils' + +import styled from '@emotion/styled'; +import type {LibraryItem} from './types'; +import {humanizeType} from './utils'; +import {t} from '@/scripts/i18n'; +import * as cssUtils from '@/styles/utils'; const Card = styled.div` width: 245px; @@ -20,109 +20,109 @@ const Card = styled.div` img { height: 210px; } -` +`; const Icon = styled.i` height: 24px; -` +`; const Badge = styled.span` padding-top: 0.4rem; -` +`; const NickNameBadge = styled(Badge)` ${cssUtils.pointerCursor} max-width: 100px; -` +`; -interface ButtonLikeProps { - liked: boolean -} -const ButtonLike = styled.a` +type ButtonLikeProperties = { + liked: boolean; +}; +const ButtonLike = styled.a` ${cssUtils.pointerCursor} i, span { - color: ${(props) => (props.liked ? '#dc3545' : '#6c757d')}; + color: ${properties => (properties.liked ? '#dc3545' : '#6c757d')}; &:hover { - color: ${(props) => (props.liked ? '#dc3545' : '#343a40')}; + color: ${properties => (properties.liked ? '#dc3545' : '#343a40')}; } } -` +`; -interface Props { - item: LibraryItem - liked: boolean - onAdd(texture: LibraryItem): Promise - onRemove(texture: LibraryItem): Promise - onUploaderClick(uploader: number): void -} +type Properties = { + readonly item: LibraryItem; + readonly liked: boolean; + onAdd(texture: LibraryItem): Promise; + onRemove(texture: LibraryItem): Promise; + onUploaderClick(uploader: number): void; +}; -const Item: React.FC = (props) => { - const { item } = props +const Item: React.FC = properties => { + const {item} = properties; - const link = `${blessing.base_url}/skinlib/show/${item.tid}` - const preview = `${blessing.base_url}/preview/${item.tid}?height=150` - const previewPNG = `${preview}&png` + const link = `${blessing.base_url}/skinlib/show/${item.tid}`; + const preview = `${blessing.base_url}/preview/${item.tid}?height=150`; + const previewPNG = `${preview}&png`; - const handleUploaderClick = (event: React.MouseEvent) => { - event.preventDefault() - props.onUploaderClick(item.uploader) - } + const handleUploaderClick = (event: React.MouseEvent) => { + event.preventDefault(); + properties.onUploaderClick(item.uploader); + }; - const handleHeartClick = (event: React.MouseEvent) => { - event.preventDefault() - props.liked ? props.onRemove(item) : props.onAdd(item) - } + const handleHeartClick = (event: React.MouseEvent) => { + event.preventDefault(); + properties.liked ? properties.onRemove(item) : properties.onAdd(item); + }; - return ( - - - -
    -
    - {item.public || ( - - )} - - {item.name} - -
    -
    -
    - - {humanizeType(item.type)} - - - {item.nickname} - -
    - - - {item.likes} - -
    -
    - - - ) -} + return ( + + + +
    +
    + {item.public || ( + + )} + + {item.name} + +
    +
    +
    + + {humanizeType(item.type)} + + + {item.nickname} + +
    + + + {item.likes} + +
    +
    + + + ); +}; -export default Item +export default Item; diff --git a/resources/assets/src/views/skinlib/SkinLibrary/index.tsx b/resources/assets/src/views/skinlib/SkinLibrary/index.tsx index 4963ae97..2dc3114c 100644 --- a/resources/assets/src/views/skinlib/SkinLibrary/index.tsx +++ b/resources/assets/src/views/skinlib/SkinLibrary/index.tsx @@ -1,290 +1,303 @@ -import React, { useState, useEffect } from 'react' -import { hot } from 'react-hot-loader/root' -import useBlessingExtra from '@/scripts/hooks/useBlessingExtra' -import useEmitMounted from '@/scripts/hooks/useEmitMounted' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { toast } from '@/scripts/notify' -import { Paginator, TextureType } from '@/scripts/types' -import urls from '@/scripts/urls' -import Loading from '@/components/Loading' -import Pagination from '@/components/Pagination' -import addClosetItem from '../Show/addClosetItem' -import removeClosetItem from '@/views/user/Closet/removeClosetItem' -import FilterSelector from './FilterSelector' -import Button from './Button' -import Item from './Item' -import type { Filter, LibraryItem } from './types' +import {useState, useEffect} from 'react'; +import addClosetItem from '../Show/addClosetItem'; +import FilterSelector from './FilterSelector'; +import Button from './Button'; +import Item from './Item'; +import type {Filter, LibraryItem} from './types'; +import useBlessingExtra from '@/scripts/hooks/useBlessingExtra'; +import useEmitMounted from '@/scripts/hooks/useEmitMounted'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {toast} from '@/scripts/notify'; +import {type Paginator, TextureType} from '@/scripts/types'; +import urls from '@/scripts/urls'; +import Loading from '@/components/Loading'; +import Pagination from '@/components/Pagination'; +import removeClosetItem from '@/views/user/Closet/removeClosetItem'; -const SkinLibrary: React.FC = () => { - const [isLoading, setIsLoading] = useState(true) - const [items, setItems] = useState([]) - const [closet, setCloset] = useState([]) - const [filter, setFilter] = useState('skin') - const [name, setName] = useState('') - const [keyword, setKeyword] = useState('') - const [uploader, setUploader] = useState(0) - const [sort, setSort] = useState('time') - const [page, setPage] = useState(1) - const [totalPages, setTotalPages] = useState(1) - const currentUid = useBlessingExtra('currentUid', null) +function SkinLibrary() { + const [isLoading, setIsLoading] = useState(true); + const [items, setItems] = useState([]); + const [closet, setCloset] = useState([]); + const [filter, setFilter] = useState('skin'); + const [name, setName] = useState(''); + const [keyword, setKeyword] = useState(''); + const [uploader, setUploader] = useState(0); + const [sort, setSort] = useState('time'); + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const currentUid = useBlessingExtra('currentUid', null); - useEmitMounted() + useEmitMounted(); - useEffect(() => { - const parseSearch = (query: string) => { - const search = new URLSearchParams(query) + useEffect(() => { + const parseSearch = (query: string) => { + const search = new URLSearchParams(query); - const filter = search.get('filter') ?? '' - setFilter( - [ - 'skin', - TextureType.Steve, - TextureType.Alex, - TextureType.Cape, - ].includes(filter) - ? (filter as Filter) - : 'skin', - ) + const filter = search.get('filter') ?? ''; + setFilter( + [ + 'skin', + TextureType.Steve, + TextureType.Alex, + TextureType.Cape, + ].includes(filter) + ? (filter as Filter) + : 'skin', + ); - const keyword = decodeURIComponent(search.get('keyword') ?? '') - setName(keyword) - setKeyword(keyword) + const keyword = decodeURIComponent(search.get('keyword') ?? ''); + setName(keyword); + setKeyword(keyword); - const uploader = search.get('uploader') ?? '0' - setUploader(Number.parseInt(uploader)) + const uploader = search.get('uploader') ?? '0'; + setUploader(Number.parseInt(uploader)); - setSort(search.get('sort') ?? 'time') + setSort(search.get('sort') ?? 'time'); - setPage(Number.parseInt(search.get('page') ?? '1')) - } + setPage(Number.parseInt(search.get('page') ?? '1')); + }; - parseSearch(location.search) + parseSearch(location.search); - const handler = (event: PopStateEvent) => parseSearch(event.state) - window.addEventListener('popstate', handler) + const handler = (event: PopStateEvent) => { + parseSearch(String(event.state)); + }; - return () => { - window.removeEventListener('popstate', handler) - } - }, []) + window.addEventListener('popstate', handler); - useEffect(() => { - const getItems = async () => { - setIsLoading(true) + return () => { + window.removeEventListener('popstate', handler); + }; + }, []); - const search = new URLSearchParams() - search.append('filter', filter) - if (keyword) { - search.append('keyword', keyword) - } - if (uploader) { - search.append('uploader', uploader.toString()) - } - search.append('sort', sort) - search.append('page', page.toString()) - window.history.pushState(search.toString(), '', `?${search}`) + useEffect(() => { + const getItems = async () => { + setIsLoading(true); - const result = await fetch.get>( - urls.skinlib.list(), - search, - ) - setItems(result.data) - setTotalPages(result.last_page) - setIsLoading(false) - } - getItems() - }, [filter, keyword, uploader, sort, page]) + const search = new URLSearchParams(); + search.append('filter', filter); + if (keyword) { + search.append('keyword', keyword); + } - useEffect(() => { - const getCloset = async () => { - const closet = await fetch.get(urls.user.closet.ids()) - setCloset(closet) - } - if (currentUid) { - getCloset() - } - }, [currentUid]) + if (uploader) { + search.append('uploader', uploader.toString()); + } - const handleFilterChange = (filter: Filter) => { - setFilter(filter) - setPage(1) - } + search.append('sort', sort); + search.append('page', page.toString()); + window.history.pushState(search.toString(), '', `?${search}`); - const handleNameChange = (event: React.ChangeEvent) => { - setName(event.target.value) - } + const result = await fetch.get>( + urls.skinlib.list(), + search, + ); + setItems(result.data); + setTotalPages(result.last_page); + setIsLoading(false); + }; - const handleFormSubmit = (event: React.FormEvent) => { - event.preventDefault() - setKeyword(name) - setPage(1) - } + getItems(); + }, [filter, keyword, uploader, sort, page]); - const handleLikesSortClick = () => setSort('likes') - const handleTimeSortClick = () => setSort('time') - const handleSelfUploadClick = () => { - setUploader(currentUid) - setPage(1) - } - const handleResetClick = () => { - setFilter('skin') - setName('') - setKeyword('') - setSort('time') - setUploader(0) - setPage(1) - } + useEffect(() => { + const getCloset = async () => { + const closet = await fetch.get(urls.user.closet.ids()); + setCloset(closet); + }; - const handleUploaderClick = (uploader: number) => { - setUploader(uploader) - setPage(1) - } + if (currentUid) { + getCloset(); + } + }, [currentUid]); - const handleAddToCloset = async (item: LibraryItem, index: number) => { - if (!currentUid) { - toast.warning(t('skinlib.anonymous')) - return - } + const handleFilterChange = (filter: Filter) => { + setFilter(filter); + setPage(1); + }; - const ok = await addClosetItem(item) - if (ok) { - setCloset((closet) => [...closet, item.tid]) - setItems((items) => { - items[index] = { ...item, likes: item.likes + 1 } - return items.slice() - }) - } - } + const handleNameChange = (event: React.ChangeEvent) => { + setName(event.target.value); + }; - const handleRemoveFromCloset = async (item: LibraryItem, index: number) => { - const ok = await removeClosetItem(item.tid) - if (ok) { - setCloset((closet) => closet.filter((id) => id !== item.tid)) - setItems((items) => { - items[index] = { ...item, likes: item.likes - 1 } - return items.slice() - }) - } - } + const handleFormSubmit = (event: React.FormEvent) => { + event.preventDefault(); + setKeyword(name); + setPage(1); + }; - return ( -
    -
    -
    -

    {t('general.skinlib')}

    - - {uploader ? ( - <> - - {t('skinlib.filter.uploader', { uid: uploader })} - - ) : ( - <> - - {t('skinlib.filter.allUsers')} - - )} - -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - -
    - -
    -
    -
    -
    -
    - - - {currentUid !== null && ( - - )} - -
    -
    -
    - {items.length > 0 ? ( -
    - {items.map((item, i) => ( - handleAddToCloset(item, i)} - onRemove={(item) => handleRemoveFromCloset(item, i)} - onUploaderClick={handleUploaderClick} - /> - ))} -
    - ) : ( -

    {t('general.noResult')}

    - )} -
    -
    -
    - -
    -
    - {isLoading && ( -
    - -
    - )} -
    -
    -
    - ) + const handleLikesSortClick = () => { + setSort('likes'); + }; + + const handleTimeSortClick = () => { + setSort('time'); + }; + + const handleSelfUploadClick = () => { + setUploader(currentUid); + setPage(1); + }; + + const handleResetClick = () => { + setFilter('skin'); + setName(''); + setKeyword(''); + setSort('time'); + setUploader(0); + setPage(1); + }; + + const handleUploaderClick = (uploader: number) => { + setUploader(uploader); + setPage(1); + }; + + const handleAddToCloset = async (item: LibraryItem, index: number) => { + if (!currentUid) { + toast.warning(t('skinlib.anonymous')); + return; + } + + const ok = await addClosetItem(item); + if (ok) { + setCloset(closet => [...closet, item.tid]); + setItems(items => { + items[index] = {...item, likes: item.likes + 1}; + return [...items]; + }); + } + }; + + const handleRemoveFromCloset = async (item: LibraryItem, index: number) => { + const ok = await removeClosetItem(item.tid); + if (ok) { + setCloset(closet => closet.filter(id => id !== item.tid)); + setItems(items => { + items[index] = {...item, likes: item.likes - 1}; + return [...items]; + }); + } + }; + + return ( +
    +
    +
    +

    {t('general.skinlib')}

    + + {uploader ? ( + <> + + {t('skinlib.filter.uploader', {uid: uploader.toString()})} + + ) : ( + <> + + {t('skinlib.filter.allUsers')} + + )} + +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    + +
    + +
    +
    + +
    +
    + + + {currentUid !== null && ( + + )} + +
    +
    +
    + {items.length > 0 ? ( +
    + {items.map((item, i) => ( + handleAddToCloset(item, i)} + onRemove={async item => handleRemoveFromCloset(item, i)} + onUploaderClick={handleUploaderClick} + /> + ))} +
    + ) : ( +

    {t('general.noResult')}

    + )} +
    +
    +
    + +
    +
    + {isLoading && ( +
    + +
    + )} +
    +
    +
    + ); } -export default hot(SkinLibrary) +export default SkinLibrary; diff --git a/resources/assets/src/views/skinlib/SkinLibrary/types.ts b/resources/assets/src/views/skinlib/SkinLibrary/types.ts index c37e7d0a..5329c8ec 100644 --- a/resources/assets/src/views/skinlib/SkinLibrary/types.ts +++ b/resources/assets/src/views/skinlib/SkinLibrary/types.ts @@ -1,13 +1,13 @@ -import type { TextureType } from '@/scripts/types' +import type {TextureType} from '@/scripts/types'; -export type Filter = 'skin' | TextureType +export type Filter = 'skin' | TextureType; export type LibraryItem = { - tid: number - name: string - type: TextureType - uploader: number - public: boolean - likes: number - nickname: string -} + tid: number; + name: string; + type: TextureType; + uploader: number; + public: boolean; + likes: number; + nickname: string; +}; diff --git a/resources/assets/src/views/skinlib/SkinLibrary/utils.ts b/resources/assets/src/views/skinlib/SkinLibrary/utils.ts index 90891ea8..4cc520e1 100644 --- a/resources/assets/src/views/skinlib/SkinLibrary/utils.ts +++ b/resources/assets/src/views/skinlib/SkinLibrary/utils.ts @@ -1,14 +1,19 @@ -import { t } from '@/scripts/i18n' -import { TextureType } from '@/scripts/types' -import type { Filter } from './types' +import type {Filter} from './types'; +import {t} from '@/scripts/i18n'; +import {TextureType} from '@/scripts/types'; export function humanizeType(type: Filter): string { - switch (type) { - case TextureType.Steve: - return 'Steve' - case TextureType.Alex: - return 'Alex' - default: - return t(`general.${type}`) - } + switch (type) { + case TextureType.Steve: { + return 'Steve'; + } + + case TextureType.Alex: { + return 'Alex'; + } + + default: { + return t(`general.${type}`); + } + } } diff --git a/resources/assets/src/views/skinlib/Upload.tsx b/resources/assets/src/views/skinlib/Upload.tsx index 0f9e3ed3..0e4bb7fd 100644 --- a/resources/assets/src/views/skinlib/Upload.tsx +++ b/resources/assets/src/views/skinlib/Upload.tsx @@ -1,259 +1,259 @@ -import React, { useState } from 'react' -import ReactDOM from 'react-dom' -import { hot } from 'react-hot-loader/root' -import { t } from '@/scripts/i18n' -import useBlessingExtra from '@/scripts/hooks/useBlessingExtra' -import useEmitMounted from '@/scripts/hooks/useEmitMounted' -import useMount from '@/scripts/hooks/useMount' -import * as fetch from '@/scripts/net' -import { showModal, toast } from '@/scripts/notify' -import { isAlex } from '@/scripts/textureUtils' -import { TextureType } from '@/scripts/types' -import urls from '@/scripts/urls' -import FileInput from '@/components/FileInput' -import ViewerSkeleton from '@/components/ViewerSkeleton' +import React, {useState} from 'react'; +import ReactDOM from 'react-dom'; +import {t} from '@/scripts/i18n'; +import useBlessingExtra from '@/scripts/hooks/useBlessingExtra'; +import useEmitMounted from '@/scripts/hooks/useEmitMounted'; +import useMount from '@/scripts/hooks/useMount'; +import * as fetch from '@/scripts/net'; +import {showModal, toast} from '@/scripts/notify'; +import {isAlex} from '@/scripts/textureUtils'; +import {TextureType} from '@/scripts/types'; +import urls from '@/scripts/urls'; +import FileInput from '@/components/FileInput'; +import ViewerSkeleton from '@/components/ViewerSkeleton'; -const Previewer = React.lazy(() => import('@/components/Viewer')) +const Previewer = React.lazy(async () => import('@/components/Viewer')); -const Upload: React.FC = () => { - const [name, setName] = useState('') - const [type, setType] = useState(TextureType.Steve) - const [isPrivate, setIsPrivate] = useState(false) - const [isUploading, setIsUploading] = useState(false) - const [file, setFile] = useState(null) - const [texture, setTexture] = useState('') - const nameRule = useBlessingExtra('rule') - const contentPolicy = useBlessingExtra('contentPolicy') - const privacyNotice = useBlessingExtra('privacyNotice') - const award = useBlessingExtra('award') - const currentScore = useBlessingExtra('score', 0) - const scorePublic = useBlessingExtra('scorePublic') - const scorePrivate = useBlessingExtra('scorePrivate') - const closetItemCost = useBlessingExtra('closetItemCost') +function Upload() { + const [name, setName] = useState(''); + const [type, setType] = useState(TextureType.Steve); + const [isPrivate, setIsPrivate] = useState(false); + const [isUploading, setIsUploading] = useState(false); + const [file, setFile] = useState(null); + const [texture, setTexture] = useState(''); + const nameRule = useBlessingExtra('rule'); + const contentPolicy = useBlessingExtra('contentPolicy'); + const privacyNotice = useBlessingExtra('privacyNotice'); + const award = useBlessingExtra('award'); + const currentScore = useBlessingExtra('score', 0); + const scorePublic = useBlessingExtra('scorePublic'); + const scorePrivate = useBlessingExtra('scorePrivate'); + const closetItemCost = useBlessingExtra('closetItemCost'); - const container = useMount('#previewer') + const container = useMount('#previewer'); - useEmitMounted() + useEmitMounted(); - const handleNameChange = (event: React.ChangeEvent) => { - setName(event.target.value) - } + const handleNameChange = (event: React.ChangeEvent) => { + setName(event.target.value); + }; - const handleTypeChange = (event: React.ChangeEvent) => { - setType(event.target.value as TextureType) - } + const handleTypeChange = (event: React.ChangeEvent) => { + setType(event.target.value as TextureType); + }; - const handlePrivateChange = (event: React.ChangeEvent) => { - setIsPrivate(event.target.checked) - } + const handlePrivateChange = (event: React.ChangeEvent) => { + setIsPrivate(event.target.checked); + }; - const handleFileChange = async ( - event: React.ChangeEvent, - ) => { - const files = event.target.files! - const [file] = files - if (file) { - setFile(file) - if (!name && file.name.endsWith('.png')) { - setName(file.name.slice(0, file.name.length - 4)) - } - const texture = URL.createObjectURL(file) - setTexture(texture) - if (type !== TextureType.Cape) { - setType((await isAlex(texture)) ? TextureType.Alex : TextureType.Steve) - } - } - } + const handleFileChange = async ( + event: React.ChangeEvent, + ) => { + const files = event.target.files!; + const [file] = files; + if (file) { + setFile(file); + if (!name && file.name.endsWith('.png')) { + setName(file.name.slice(0, -4)); + } - const handleUpload = async () => { - if (!file) { - toast.error(t('skinlib.emptyUploadFile')) - return - } + const texture = URL.createObjectURL(file); + setTexture(texture); + if (type !== TextureType.Cape) { + setType((await isAlex(texture)) ? TextureType.Alex : TextureType.Steve); + } + } + }; - if (!name) { - toast.error(t('skinlib.emptyTextureName')) - return - } + const handleUpload = async () => { + if (!file) { + toast.error(t('skinlib.emptyUploadFile')); + return; + } - if (file.type !== 'image/png' && file.type !== 'image/x-png') { - toast.error(t('skinlib.fileExtError')) - return - } + if (!name) { + toast.error(t('skinlib.emptyTextureName')); + return; + } - const formData = new FormData() - formData.append('name', name) - formData.append('type', type) - formData.append('file', file, file.name) - formData.append('public', isPrivate ? '0' : '1') + if (file.type !== 'image/png' && file.type !== 'image/x-png') { + toast.error(t('skinlib.fileExtError')); + return; + } - setIsUploading(true) - const { - code, - message, - data: { tid } = { tid: 0 }, - } = await fetch.post>( - urls.texture.upload(), - formData, - ) - setIsUploading(false) + const formData = new FormData(); + formData.append('name', name); + formData.append('type', type); + formData.append('file', file, file.name); + formData.append('public', isPrivate ? '0' : '1'); - if (code === 0) { - window.location.href = blessing.base_url + urls.skinlib.show(tid) - } else if (code === 2) { - try { - await showModal({ - mode: 'confirm', - text: message, - okButtonText: t('user.viewInSkinlib'), - }) - window.location.href = blessing.base_url + urls.skinlib.show(tid) - } catch { - // - } - } else { - toast.error(message) - } - } + setIsUploading(true); + const { + code, + message, + data: {tid} = {tid: 0}, + } = await fetch.post>( + urls.texture.upload(), + formData, + ); + setIsUploading(false); - const costRatio = isPrivate ? scorePrivate : scorePublic - const size = file?.size ?? 0 - const scoreCost = Math.ceil(size / 1024) * costRatio + closetItemCost + if (code === 0) { + window.location.href = blessing.base_url + urls.skinlib.show(tid); + } else if (code === 2) { + try { + await showModal({ + mode: 'confirm', + text: message, + okButtonText: t('user.viewInSkinlib'), + }); + window.location.href = blessing.base_url + urls.skinlib.show(tid); + } catch { + // + } + } else { + toast.error(message); + } + }; - return ( - <> -
    -
    -
    - - -
    -
    - -
    - - - -
    - + const costRatio = isPrivate ? scorePrivate : scorePublic; + const size = file?.size ?? 0; + const scoreCost = Math.ceil(size / 1024) * costRatio + closetItemCost; - {contentPolicy && ( -
    - )} -
    -
    -
    - - -
    - {file && ( -
    scoreCost ? 'success' : 'danger' - } mt-3`} - > -
    {t('skinlib.upload.cost', { score: scoreCost })}
    -
    - {t('user.cur-score')} - {currentScore} -
    -
    - )} - {isPrivate && ( -
    {privacyNotice}
    - )} - {!isPrivate && award > 0 && ( -
    - {t('skinlib.upload.award', { score: award })} -
    - )} -
    -
    - {container && - ReactDOM.createPortal( - }> - - , - container, + return ( + <> +
    +
    +
    + + +
    +
    + +
    + + + +
    + + + {contentPolicy && ( +
    + )} +
    +
    +
    + + +
    + {file && ( +
    scoreCost ? 'success' : 'danger' + } mt-3`} + > +
    {t('skinlib.upload.cost', {score: scoreCost.toString()})}
    +
    + {t('user.cur-score')} + {currentScore} +
    +
    + )} + {isPrivate && ( +
    {privacyNotice}
    + )} + {!isPrivate && award > 0 && ( +
    + {t('skinlib.upload.award', {score: award.toString()})} +
    + )} +
    +
    + {container + && ReactDOM.createPortal( + }> + + , + container, )} - - ) + + ); } -export default hot(Upload) +export default Upload; diff --git a/resources/assets/src/views/user/Closet/ClosetItem.tsx b/resources/assets/src/views/user/Closet/ClosetItem.tsx index 831d02c7..1394da6b 100644 --- a/resources/assets/src/views/user/Closet/ClosetItem.tsx +++ b/resources/assets/src/views/user/Closet/ClosetItem.tsx @@ -1,76 +1,76 @@ -import React from 'react' -import { t } from '@/scripts/i18n' -import type { ClosetItem as ClosetItemType } from '@/scripts/types' -import setAsAvatar from './setAsAvatar' -import { Card, DropdownButton } from './styles' -interface Props { - item: ClosetItemType - selected: boolean - onClick(item: ClosetItemType): void - onRename(): void - onRemove(): void -} +import setAsAvatar from './setAsAvatar'; +import {Card, DropdownButton} from './styles'; +import {t} from '@/scripts/i18n'; +import type {ClosetItem as ClosetItemType} from '@/scripts/types'; -const ClosetItem: React.FC = (props) => { - const { item } = props - const preview = `${blessing.base_url}/preview/${item.tid}?height=150` - const previewPNG = `${preview}&png` +type Properties = { + readonly item: ClosetItemType; + readonly selected: boolean; + onClick(item: ClosetItemType): void; + onRename(): void; + onRemove(): void; +}; - const handleItemClick = () => { - props.onClick(item) - } +const ClosetItem: React.FC = properties => { + const {item} = properties; + const preview = `${blessing.base_url}/preview/${item.tid}?height=150`; + const previewPNG = `${preview}&png`; - const handleSetAsAvatar = () => setAsAvatar(item.tid) + const handleItemClick = () => { + properties.onClick(item); + }; - return ( - -
    - - - {item.pivot.item_name} - -
    - -
    - ) -} + const handleSetAsAvatar = async () => setAsAvatar(item.tid); -export default ClosetItem + return ( + +
    + + + {item.pivot.item_name} + +
    + +
    + ); +}; + +export default ClosetItem; diff --git a/resources/assets/src/views/user/Closet/LoadingClosetItem.tsx b/resources/assets/src/views/user/Closet/LoadingClosetItem.tsx index a1a718a6..101bcf60 100644 --- a/resources/assets/src/views/user/Closet/LoadingClosetItem.tsx +++ b/resources/assets/src/views/user/Closet/LoadingClosetItem.tsx @@ -1,26 +1,25 @@ -import React from 'react' -import styled from '@emotion/styled' -import Skeleton from 'react-loading-skeleton' -import { Card, DropdownButton } from './styles' +import styled from '@emotion/styled'; +import Skeleton from 'react-loading-skeleton'; +import {Card, DropdownButton} from './styles'; const ItemNameSkeleton = styled(Skeleton)` width: 150px; -` +`; -const LoadingClosetItem: React.FC = () => ( - -
    -
    -
    - - - - - - -
    -
    -
    -) - -export default LoadingClosetItem +export default function LoadingClosetItem() { + return ( + +
    +
    +
    + + + + + + +
    +
    + + ); +} diff --git a/resources/assets/src/views/user/Closet/ModalApply.tsx b/resources/assets/src/views/user/Closet/ModalApply.tsx index 7cae9885..42900a93 100644 --- a/resources/assets/src/views/user/Closet/ModalApply.tsx +++ b/resources/assets/src/views/user/Closet/ModalApply.tsx @@ -1,113 +1,114 @@ -import React, { useState, useEffect } from 'react' -import $ from 'jquery' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { toast } from '@/scripts/notify' -import type { Player } from '@/scripts/types' -import urls from '@/scripts/urls' -import Loading from '@/components/Loading' -import Modal from '@/components/Modal' +import {useState, useEffect} from 'react'; +import $ from 'jquery'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {toast} from '@/scripts/notify'; +import type {Player} from '@/scripts/types'; +import urls from '@/scripts/urls'; +import Loading from '@/components/Loading'; +import Modal from '@/components/Modal'; -const baseUrl = blessing.base_url +const baseUrl = blessing.base_url; -interface Props { - show: boolean - canAdd: boolean - skin?: number - cape?: number - onClose(): void -} +type Properties = { + readonly show: boolean; + readonly canAdd: boolean; + readonly skin?: number; + readonly cape?: number; + onClose(): void; +}; -const ModalApply: React.FC = (props) => { - const [players, setPlayers] = useState([]) - const [search, setSearch] = useState('') - const [isLoading, setIsLoading] = useState(false) +const ModalApply: React.FC = properties => { + const [players, setPlayers] = useState([]); + const [search, setSearch] = useState(''); + const [isLoading, setIsLoading] = useState(false); - useEffect(() => { - if (!props.show) { - return - } + useEffect(() => { + if (!properties.show) { + return; + } - const getPlayers = async () => { - setIsLoading(true) - const players = await fetch.get(urls.user.player.list()) - setPlayers(players) - setIsLoading(false) - } - getPlayers() - }, [props.show]) + const getPlayers = async () => { + setIsLoading(true); + const players = await fetch.get(urls.user.player.list()); + setPlayers(players); + setIsLoading(false); + }; - const handleSearch = (event: React.ChangeEvent) => { - setSearch(event.target.value) - } + getPlayers(); + }, [properties.show]); - const handleSelect = async (player: Player) => { - const { code, message } = await fetch.put( - urls.user.player.set(player.pid), - { - skin: props.skin, - cape: props.cape, - }, - ) - if (code === 0) { - toast.success(message) - $('#modal-apply').modal('hide') - } else { - toast.error(message) - } - } + const handleSearch = (event: React.ChangeEvent) => { + setSearch(event.target.value); + }; - return ( - } - onClose={props.onClose} - > - {isLoading ? ( - - ) : players.length === 0 ? ( -

    {t('user.closet.use-as.empty')}

    - ) : ( - <> -
    - -
    - {players - .filter((player) => player.name.includes(search)) - .map((player) => ( - - ))} - - )} -
    - ) -} + const handleSelect = async (player: Player) => { + const {code, message} = await fetch.put( + urls.user.player.set(player.pid), + { + skin: properties.skin, + cape: properties.cape, + }, + ); + if (code === 0) { + toast.success(message); + $('#modal-apply').modal('hide'); + } else { + toast.error(message); + } + }; -export default ModalApply + return ( + } + onClose={properties.onClose} + > + {isLoading ? ( + + ) : (players.length === 0 ? ( +

    {t('user.closet.use-as.empty')}

    + ) : ( + <> +
    + +
    + {players + .filter(player => player.name.includes(search)) + .map(player => ( + + ))} + + ))} +
    + ); +}; + +export default ModalApply; diff --git a/resources/assets/src/views/user/Closet/Previewer.tsx b/resources/assets/src/views/user/Closet/Previewer.tsx index 01ff4f4f..08aee849 100644 --- a/resources/assets/src/views/user/Closet/Previewer.tsx +++ b/resources/assets/src/views/user/Closet/Previewer.tsx @@ -1,33 +1,34 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import useMount from '@/scripts/hooks/useMount' -import ViewerSkeleton from '@/components/ViewerSkeleton' +import React from 'react'; +import ReactDOM from 'react-dom'; +import useMount from '@/scripts/hooks/useMount'; +import ViewerSkeleton from '@/components/ViewerSkeleton'; -const Viewer = React.lazy(() => import('@/components/Viewer')) +const Viewer = React.lazy(async () => import('@/components/Viewer')); -interface Props { - skin?: string - cape?: string - isAlex: boolean -} +type Properties = { + skin?: string; + cape?: string; + children: React.ReactNode; + isAlex: boolean; +}; -const Previewer: React.FC = (props) => { - const container = useMount('#previewer') +const Previewer: React.FC = properties => { + const container = useMount('#previewer'); - const skin = props.skin ? `${blessing.base_url}/textures/${props.skin}` : '' - const cape = props.cape ? `${blessing.base_url}/textures/${props.cape}` : '' + const skin = properties.skin ? `${blessing.base_url}/textures/${properties.skin}` : ''; + const cape = properties.cape ? `${blessing.base_url}/textures/${properties.cape}` : ''; - return ( - container && - ReactDOM.createPortal( - }> - - {props.children} - - , - container, + return ( + container + && ReactDOM.createPortal( + }> + + {properties.children} + + , + container, ) - ) -} + ); +}; -export default Previewer +export default Previewer; diff --git a/resources/assets/src/views/user/Closet/index.tsx b/resources/assets/src/views/user/Closet/index.tsx index 45c31551..e97527b2 100644 --- a/resources/assets/src/views/user/Closet/index.tsx +++ b/resources/assets/src/views/user/Closet/index.tsx @@ -1,288 +1,298 @@ -import React, { useState, useEffect, useRef } from 'react' -import { hot } from 'react-hot-loader/root' -import debounce from 'lodash.debounce' -import useEmitMounted from '@/scripts/hooks/useEmitMounted' -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { showModal, toast } from '@/scripts/notify' +import {useState, useEffect, useRef} from 'react'; +import debounce from 'lodash-es/debounce'; +import ClosetItem from './ClosetItem'; +import LoadingClosetItem from './LoadingClosetItem'; +import Previewer from './Previewer'; +import ModalApply from './ModalApply'; +import removeClosetItem from './removeClosetItem'; +import useEmitMounted from '@/scripts/hooks/useEmitMounted'; +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {showModal, toast} from '@/scripts/notify'; import { - ClosetItem as Item, - Texture, - Paginator, - TextureType, -} from '@/scripts/types' -import urls from '@/scripts/urls' -import Pagination from '@/components/Pagination' -import ClosetItem from './ClosetItem' -import LoadingClosetItem from './LoadingClosetItem' -import Previewer from './Previewer' -import ModalApply from './ModalApply' -import removeClosetItem from './removeClosetItem' + type ClosetItem as Item, + type Texture, + type Paginator, + TextureType, +} from '@/scripts/types'; +import urls from '@/scripts/urls'; +import Pagination from '@/components/Pagination'; -type Category = 'skin' | 'cape' +type Category = 'skin' | 'cape'; const updater = debounce( - ( - value: React.SetStateAction, - setter: React.Dispatch>, - ) => setter(value), - 350, -) + // eslint-disable-next-line @typescript-eslint/comma-dangle + ( + value: React.SetStateAction, + setter: React.Dispatch>, + ) => { + setter(value); + }, + 350, +) as (value: React.SetStateAction, setter: React.Dispatch>) => void; -const Closet: React.FC = () => { - const [isLoading, setIsLoading] = useState(true) - const [category, setCategory] = useState('skin') - const [search, setSearch] = useState('') - const [query, setQuery] = useState('') - const [page, setPage] = useState(1) - const [totalPages, setTotalPages] = useState(1) - const [items, setItems] = useState([]) - const [skin, setSkin] = useState(null) - const [cape, setCape] = useState(null) - const [showModalApply, setShowModalApply] = useState(false) - const containerRef = useRef(null) - const perPageRef = useRef(6) +function Closet() { + const [isLoading, setIsLoading] = useState(true); + const [category, setCategory] = useState('skin'); + const [search, setSearch] = useState(''); + const [query, setQuery] = useState(''); + const [page, setPage] = useState(1); + const [totalPages, setTotalPages] = useState(1); + const [items, setItems] = useState([]); + const [skin, setSkin] = useState(null); + const [cape, setCape] = useState(null); + const [showModalApply, setShowModalApply] = useState(false); + const containerReference = useRef(null); + const perPageReference = useRef(6); - useEmitMounted() + useEmitMounted(); - useEffect(() => { - const element = containerRef.current - /* istanbul ignore next */ - if (element) { - const { width } = element.getBoundingClientRect() - if (width >= 500) { - perPageRef.current = Math.floor(width / 235) * 2 - } - } - }, []) + useEffect(() => { + const element = containerReference.current; - useEffect(() => { - const getItems = async () => { - setIsLoading(true) - const { data, last_page } = await fetch.get>( - urls.user.closet.list(), - { category, q: query, page, perPage: perPageRef.current }, - ) + if (element) { + const {width} = element.getBoundingClientRect(); + if (width >= 500) { + perPageReference.current = Math.floor(width / 235) * 2; + } + } + }, []); - setItems(data) - setTotalPages(last_page) - setIsLoading(false) - } - getItems() - }, [category, query, page]) + useEffect(() => { + const getItems = async () => { + setIsLoading(true); + const {data, last_page: lastPage} = await fetch.get>( + urls.user.closet.list(), + { + category, q: query, page: page.toString(), perPage: perPageReference.current.toString(), + }, + ); - const switchCategoryToSkin = () => { - if (category !== 'skin') { - setCategory('skin') - setPage(1) - } - } + setItems(data); + setTotalPages(lastPage); + setIsLoading(false); + }; - const switchCategoryToCape = () => { - if (category !== 'cape') { - setCategory('cape') - setPage(1) - } - } + void getItems(); + }, [category, query, page]); - const handleSearch = (event: React.ChangeEvent) => { - const value = event.target.value - setSearch(value) - updater(value, setQuery) - } + const switchCategoryToSkin = () => { + if (category !== 'skin') { + setCategory('skin'); + setPage(1); + } + }; - const handlePageChange = (page: number) => setPage(page) + const switchCategoryToCape = () => { + if (category !== 'cape') { + setCategory('cape'); + setPage(1); + } + }; - const isSelected = (item: Item): boolean => { - if (category === 'skin') { - return item.tid === skin?.tid - } else { - return item.tid === cape?.tid - } - } + const handleSearch = (event: React.ChangeEvent) => { + const {value} = event.target; + setSearch(value); + updater(value, setQuery); + }; - const handleSelect = (item: Item) => { - if (item.type === TextureType.Cape) { - setCape(item) - } else { - setSkin(item) - } - } + const handlePageChange = (page: number) => { + setPage(page); + }; - const resetSelected = () => { - setSkin(null) - setCape(null) - } + const isSelected = (item: Item): boolean => { + if (category === 'skin') { + return item.tid === skin?.tid; + } - const renameItem = async (item: Item, index: number) => { - let name: string - try { - const { value } = await showModal({ - mode: 'prompt', - text: t('user.renameClosetItem'), - input: item.pivot.item_name, - validator: (value: string) => { - if (!value) { - return t('skinlib.emptyNewTextureName') - } - }, - }) - name = value - } catch { - return - } + return item.tid === cape?.tid; + }; - const { code, message } = await fetch.put( - urls.user.closet.rename(item.tid), - { name }, - ) - if (code === 0) { - toast.success(message) - setItems((items) => { - items[index] = { ...item, pivot: { ...item.pivot, item_name: name } } - return items.slice() - }) - } else { - toast.error(message) - } - } + const handleSelect = (item: Item) => { + if (item.type === TextureType.Cape) { + setCape(item); + } else { + setSkin(item); + } + }; - const removeItem = async (item: Item) => { - const { tid } = item - const ok = await removeClosetItem(tid) - if (ok) { - setItems((items) => items.filter((item) => item.tid !== tid)) - } - } + const resetSelected = () => { + setSkin(null); + setCape(null); + }; - const applyToPlayer = () => { - if (!skin && !cape) { - toast.info(t('user.emptySelectedTexture')) - return - } - setShowModalApply(true) - } + const renameItem = async (item: Item, index: number) => { + let name: string; + try { + const {value} = await showModal({ + mode: 'prompt', + text: t('user.renameClosetItem'), + input: item.pivot.item_name, + validator(value: string) { + if (!value) { + return t('skinlib.emptyNewTextureName'); + } + }, + }); + name = value; + } catch { + return; + } - return ( - <> -
    - -
    - {isLoading ? ( -
    - {new Array(perPageRef.current).fill(null).map((_, i) => ( - - ))} -
    - ) : items.length === 0 ? ( -
    - {search ? ( - t('general.noResult') - ) : ( - - )} -
    - ) : ( -
    - {items.map((item, i) => ( - renameItem(item, i)} - onRemove={() => removeItem(item)} - /> - ))} -
    - )} -
    -
    -
    - -
    -
    -
    - -
    - - -
    -
    - setShowModalApply(false)} - /> - - ) + const {code, message} = await fetch.put( + urls.user.closet.rename(item.tid), + {name}, + ); + if (code === 0) { + toast.success(message); + setItems(items => { + items[index] = {...item, pivot: {...item.pivot, item_name: name}}; + return [...items]; + }); + } else { + toast.error(message); + } + }; + + const removeItem = async (item: Item) => { + const {tid} = item; + const ok = await removeClosetItem(tid); + if (ok) { + setItems(items => items.filter(item => item.tid !== tid)); + } + }; + + const applyToPlayer = () => { + if (!skin && !cape) { + toast.info(t('user.emptySelectedTexture')); + return; + } + + setShowModalApply(true); + }; + + return ( + <> +
    + +
    + {isLoading ? ( +
    + {new Array(perPageReference.current).fill(null).map((_, i) => ( + + ))} +
    + ) : (items.length === 0 ? ( +
    + {search ? ( + t('general.noResult') + ) : ( + + )} +
    + ) : ( +
    + {items.map((item, i) => ( + renameItem(item, i)} + onRemove={async () => removeItem(item)} + /> + ))} +
    + ))} +
    +
    +
    + +
    +
    +
    + +
    + + +
    +
    + { + setShowModalApply(false); + }} + /> + + ); } -export default hot(Closet) +export default Closet; diff --git a/resources/assets/src/views/user/Closet/removeClosetItem.ts b/resources/assets/src/views/user/Closet/removeClosetItem.ts index 9f62c254..035d99ca 100644 --- a/resources/assets/src/views/user/Closet/removeClosetItem.ts +++ b/resources/assets/src/views/user/Closet/removeClosetItem.ts @@ -1,26 +1,26 @@ -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { showModal, toast } from '@/scripts/notify' -import urls from '@/scripts/urls' +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {showModal, toast} from '@/scripts/notify'; +import urls from '@/scripts/urls'; export default async function removeClosetItem(tid: number): Promise { - try { - await showModal({ - text: t('user.removeFromClosetNotice'), - okButtonType: 'danger', - }) - } catch { - return false - } + try { + await showModal({ + text: t('user.removeFromClosetNotice'), + okButtonType: 'danger', + }); + } catch { + return false; + } - const { code, message } = await fetch.del( - urls.user.closet.remove(tid), - ) - if (code === 0) { - toast.success(message) - } else { - toast.error(message) - } + const {code, message} = await fetch.del( + urls.user.closet.remove(tid), + ); + if (code === 0) { + toast.success(message); + } else { + toast.error(message); + } - return code === 0 + return code === 0; } diff --git a/resources/assets/src/views/user/Closet/setAsAvatar.ts b/resources/assets/src/views/user/Closet/setAsAvatar.ts index c75ec617..a5282d87 100644 --- a/resources/assets/src/views/user/Closet/setAsAvatar.ts +++ b/resources/assets/src/views/user/Closet/setAsAvatar.ts @@ -1,30 +1,29 @@ -import { t } from '@/scripts/i18n' -import * as fetch from '@/scripts/net' -import { showModal, toast } from '@/scripts/notify' -import urls from '@/scripts/urls' +import {t} from '@/scripts/i18n'; +import * as fetch from '@/scripts/net'; +import {showModal, toast} from '@/scripts/notify'; +import urls from '@/scripts/urls'; export default async function setAsAvatar(tid: number) { - try { - await showModal({ - title: t('user.setAvatar'), - text: t('user.setAvatarNotice'), - }) - } catch { - return - } + try { + await showModal({ + title: t('user.setAvatar'), + text: t('user.setAvatarNotice'), + }); + } catch { + return; + } - const { code, message } = await fetch.post( - urls.user.profile.avatar(), - { tid }, - ) - if (code === 0) { - toast.success(message) - document - .querySelectorAll('[alt="User Image"]') - .forEach((el) => { - el.src = `${blessing.base_url}/avatar/${tid}` - }) - } else { - toast.error(message) - } + const {code, message} = await fetch.post( + urls.user.profile.avatar(), + {tid}, + ); + if (code === 0) { + toast.success(message); + for (const element of document + .querySelectorAll('[alt="User Image"]')) { + element.src = `${blessing.base_url}/avatar/${tid}`; + } + } else { + toast.error(message); + } } diff --git a/resources/assets/src/views/user/Closet/styles.ts b/resources/assets/src/views/user/Closet/styles.ts index 1deb7913..f381a49c 100644 --- a/resources/assets/src/views/user/Closet/styles.ts +++ b/resources/assets/src/views/user/Closet/styles.ts @@ -1,4 +1,4 @@ -import styled from '@emotion/styled' +import styled from '@emotion/styled'; export const Card = styled.div` width: 235px; @@ -12,11 +12,11 @@ export const Card = styled.div` .card-body { background-color: #eff1f0; } -` +`; export const DropdownButton = styled.span` color: var(--gray); :hover { color: #000; } -` +`; diff --git a/resources/assets/src/views/user/Dashboard/InfoBox.tsx b/resources/assets/src/views/user/Dashboard/InfoBox.tsx index 5f4836f2..e288081c 100644 --- a/resources/assets/src/views/user/Dashboard/InfoBox.tsx +++ b/resources/assets/src/views/user/Dashboard/InfoBox.tsx @@ -1,34 +1,34 @@ -import React from 'react' +import React from 'react'; -interface Props { - name: string - icon: string - color: string - used: number - unused: number - unit: string -} +type Properties = { + readonly name: string; + readonly icon: string; + readonly color: string; + readonly used: number; + readonly unused: number; + readonly unit: string; +}; -const InfoBox: React.FC = (props) => { - const total = ~~(props.used + props.unused) - const percentage = (props.used / total) * 100 +const InfoBox: React.FC = properties => { + const total = Math.trunc(properties.used + properties.unused); + const percentage = (properties.used / total) * 100; - return ( -
    - - - -
    - {props.name} - - {props.used} / {total} {props.unit} - -
    -
    -
    -
    -
    - ) -} + return ( +
    + + + +
    + {properties.name} + + {properties.used} / {total} {properties.unit} + +
    +
    +
    +
    +
    + ); +}; -export default React.memo(InfoBox) +export default React.memo(InfoBox); diff --git a/resources/assets/src/views/user/Dashboard/SignButton.tsx b/resources/assets/src/views/user/Dashboard/SignButton.tsx index 5d32a08b..a279e9b6 100644 --- a/resources/assets/src/views/user/Dashboard/SignButton.tsx +++ b/resources/assets/src/views/user/Dashboard/SignButton.tsx @@ -1,36 +1,36 @@ -import React from 'react' -import { t } from '@/scripts/i18n' -import * as scoreUtils from './scoreUtils' +import React from 'react'; +import * as scoreUtils from './scoreUtils'; +import {t} from '@/scripts/i18n'; -interface Props { - isLoading: boolean - lastSign: Date - canSignAfterZero: boolean - signGap: number - onClick: React.MouseEventHandler -} +type Properties = { + readonly isLoading: boolean; + readonly lastSign: Date; + readonly canSignAfterZero: boolean; + readonly signGap: number; + readonly onClick: React.MouseEventHandler; +}; -const SignButton: React.FC = (props) => { - const { lastSign, signGap, canSignAfterZero } = props - const remainingTime = scoreUtils.remainingTime( - lastSign, - signGap, - canSignAfterZero, - ) - const remainingTimeText = scoreUtils.remainingTimeText(remainingTime) - const canSign = remainingTime <= 0 +const SignButton: React.FC = properties => { + const {lastSign, signGap, canSignAfterZero} = properties; + const remainingTime = scoreUtils.remainingTime( + lastSign, + signGap, + canSignAfterZero, + ); + const remainingTimeText = scoreUtils.remainingTimeText(remainingTime); + const canSign = remainingTime <= 0; - return ( -