From f45fd363ab596b93d239c0f205517ab643401f43 Mon Sep 17 00:00:00 2001 From: Nikita <62756604+FlamesONE@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:33:45 +0500 Subject: [PATCH] v0.2.2.3-alpha --- .../Http/Controllers/Api/UsersController.php | 7 - .../Admin/Http/Controllers/Views/ApiView.php | 2 +- .../Http/Controllers/Views/CurrenciesView.php | 2 +- .../Http/Controllers/Views/DatabasesView.php | 2 +- .../Views/Footer/FooterSocialsView.php | 5 +- .../Http/Controllers/Views/ModulesView.php | 3 +- .../Controllers/Views/NotificationsView.php | 2 +- .../Http/Controllers/Views/PagesView.php | 2 +- .../Views/Payments/PaymentsPromoView.php | 5 +- .../Views/Payments/PaymentsView.php | 5 +- .../Http/Controllers/Views/RedirectsView.php | 2 +- .../Http/Controllers/Views/ServersView.php | 5 +- .../Http/Controllers/Views/SocialsView.php | 5 +- .../Http/Controllers/Views/ThemesView.php | 3 +- .../Http/Controllers/Views/UsersView.php | 2 +- app/Core/Admin/Http/Views/assets/js/layout.js | 136 ++++++++++++ .../assets/js/pages/footer/social/list.js | 18 +- .../Http/Views/assets/js/pages/modules.js | 15 -- .../Views/assets/js/pages/payments/list.js | 9 - .../assets/js/pages/payments/promo/list.js | 8 +- .../Views/assets/js/pages/servers/list.js | 10 +- .../Views/assets/js/pages/socials/list.js | 11 +- .../Http/Views/assets/js/pages/themes.js | 15 -- .../assets/styles/components/_chips.scss | 1 + .../Views/assets/styles/layouts/_tables.scss | 204 ++++++++---------- app/Core/Admin/Http/Views/layout.blade.php | 7 +- app/Core/Admin/Services/UserService.php | 9 +- app/Core/App.php | 2 +- .../Middlewares/MaintenanceMiddleware.php | 5 +- app/Core/Services/UserService.php | 9 +- app/Core/Table/TableBuilder.php | 42 ++-- config/app.php | 3 + i18n/en/admin.php | 21 +- i18n/en/def.php | 6 +- i18n/ru/admin.php | 1 + 35 files changed, 338 insertions(+), 246 deletions(-) diff --git a/app/Core/Admin/Http/Controllers/Api/UsersController.php b/app/Core/Admin/Http/Controllers/Api/UsersController.php index 0e66b39..2fd0577 100644 --- a/app/Core/Admin/Http/Controllers/Api/UsersController.php +++ b/app/Core/Admin/Http/Controllers/Api/UsersController.php @@ -3,7 +3,6 @@ namespace Flute\Core\Admin\Http\Controllers\Api; use Flute\Core\Admin\Http\Middlewares\HasPermissionMiddleware; -use Flute\Core\DiscordLink\DiscordLinkRoles; use Flute\Core\Http\Middlewares\CSRFMiddleware; use Flute\Core\Support\AbstractController; use Flute\Core\Support\FluteRequest; @@ -37,10 +36,6 @@ public function edit(FluteRequest $request, string $id): Response return $this->error($result['message'], $result['code']); } - $user = user()->get((int) $id, true); - - app()->get(DiscordLinkRoles::class)->linkRoles($user, $user->getRoles()->toArray()); - return $this->success(); } @@ -52,8 +47,6 @@ public function delete(FluteRequest $request, string $id): Response return $this->error($result['message'], $result['code']); } - app()->get(DiscordLinkRoles::class)->clearRoles(user()->get((int) $id, true), ); - return $this->success(); } diff --git a/app/Core/Admin/Http/Controllers/Views/ApiView.php b/app/Core/Admin/Http/Controllers/Views/ApiView.php index 6d966f3..1e883e0 100644 --- a/app/Core/Admin/Http/Controllers/Views/ApiView.php +++ b/app/Core/Admin/Http/Controllers/Views/ApiView.php @@ -21,7 +21,7 @@ public function list(FluteRequest $request) { $apikeys = rep(ApiKey::class)->findAll(); - $table = table(); + $table = table()->setSelectable(true); $table->setPhrases([ 'key' => __('admin.api.key'), diff --git a/app/Core/Admin/Http/Controllers/Views/CurrenciesView.php b/app/Core/Admin/Http/Controllers/Views/CurrenciesView.php index ff9b7dd..be04b70 100644 --- a/app/Core/Admin/Http/Controllers/Views/CurrenciesView.php +++ b/app/Core/Admin/Http/Controllers/Views/CurrenciesView.php @@ -17,7 +17,7 @@ public function __construct() public function list(): Response { - $table = table(); + $table = table()->setSelectable(true); $table->setPhrases([ 'code' => __('admin.currency.currency'), diff --git a/app/Core/Admin/Http/Controllers/Views/DatabasesView.php b/app/Core/Admin/Http/Controllers/Views/DatabasesView.php index 5b0bad3..076c138 100644 --- a/app/Core/Admin/Http/Controllers/Views/DatabasesView.php +++ b/app/Core/Admin/Http/Controllers/Views/DatabasesView.php @@ -18,7 +18,7 @@ public function __construct() public function list(): Response { - $table = table(); + $table = table()->setSelectable(true); $result = rep(DatabaseConnection::class)->select()->load('server'); diff --git a/app/Core/Admin/Http/Controllers/Views/Footer/FooterSocialsView.php b/app/Core/Admin/Http/Controllers/Views/Footer/FooterSocialsView.php index 8ae75b2..013332f 100644 --- a/app/Core/Admin/Http/Controllers/Views/Footer/FooterSocialsView.php +++ b/app/Core/Admin/Http/Controllers/Views/Footer/FooterSocialsView.php @@ -23,7 +23,7 @@ public function add(FluteRequest $request) public function list(FluteRequest $request) { - $table = table(); + $table = table()->setSelectable(true); $socials = rep(FooterSocial::class)->findAll(); $table->addColumns([ @@ -74,7 +74,8 @@ function(data, type, full, meta) { deleteDiv.setAttribute("data-translate", "admin.footer.social_delete"); deleteDiv.setAttribute("data-translate-attribute", "data-tooltip"); deleteDiv.setAttribute("data-tooltip-conf", "left"); - deleteDiv.setAttribute("data-deletesocial", data[0]); + deleteDiv.setAttribute("data-deleteaction", data[0]); + deleteDiv.setAttribute("data-deletepath", "footer/socials"); let deleteIcon = make("i"); deleteIcon.classList.add("ph-bold", "ph-trash"); deleteDiv.appendChild(deleteIcon); diff --git a/app/Core/Admin/Http/Controllers/Views/ModulesView.php b/app/Core/Admin/Http/Controllers/Views/ModulesView.php index 565bfbd..6fde779 100644 --- a/app/Core/Admin/Http/Controllers/Views/ModulesView.php +++ b/app/Core/Admin/Http/Controllers/Views/ModulesView.php @@ -140,7 +140,8 @@ function(data, type, full, meta) { deleteDiv.setAttribute("data-translate", "admin.modules_list.delete_module"); deleteDiv.setAttribute("data-translate-attribute", "data-tooltip"); deleteDiv.setAttribute("data-tooltip-conf", "left"); - deleteDiv.setAttribute("data-deletemodule", data[0]); + deleteDiv.setAttribute("data-deleteaction", data[0]); + deleteDiv.setAttribute("data-deletepath", "modules"); let deleteIcon = make("i"); deleteIcon.classList.add("ph-bold", "ph-trash"); deleteDiv.appendChild(deleteIcon); diff --git a/app/Core/Admin/Http/Controllers/Views/NotificationsView.php b/app/Core/Admin/Http/Controllers/Views/NotificationsView.php index d09aeba..8cdb141 100644 --- a/app/Core/Admin/Http/Controllers/Views/NotificationsView.php +++ b/app/Core/Admin/Http/Controllers/Views/NotificationsView.php @@ -29,7 +29,7 @@ public function __construct() public function list(FluteRequest $request) { - $table = table(); + $table = table()->setSelectable(true); $table->setPhrases([ 'event' => __('admin.notifications.event'), diff --git a/app/Core/Admin/Http/Controllers/Views/PagesView.php b/app/Core/Admin/Http/Controllers/Views/PagesView.php index 52d4655..3146afd 100644 --- a/app/Core/Admin/Http/Controllers/Views/PagesView.php +++ b/app/Core/Admin/Http/Controllers/Views/PagesView.php @@ -20,7 +20,7 @@ public function list(FluteRequest $request) { $pages = rep(Page::class)->select()->orderBy('id', 'desc')->fetchAll(); - $table = table(); + $table = table()->setSelectable(true); $table->setPhrases([ 'route' => __('admin.pages.route_label') diff --git a/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsPromoView.php b/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsPromoView.php index 1794924..f5dd40d 100644 --- a/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsPromoView.php +++ b/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsPromoView.php @@ -19,7 +19,7 @@ public function __construct() public function list(): Response { - $table = table(); + $table = table()->setSelectable(true); $promos = rep(PromoCode::class)->select()->load('usages')->fetchAll(); foreach ($promos as $promo) { @@ -63,7 +63,8 @@ function(data, type, full, meta) { deleteDiv.setAttribute("data-translate", "admin.payments.promo.delete"); deleteDiv.setAttribute("data-translate-attribute", "data-tooltip"); deleteDiv.setAttribute("data-tooltip-conf", "left"); - deleteDiv.setAttribute("data-deletepromo", data[0]); + deleteDiv.setAttribute("data-deleteaction", data[0]); + deleteDiv.setAttribute("data-deletepath", "payments/promo"); let deleteIcon = make("i"); deleteIcon.classList.add("ph-bold", "ph-trash"); deleteDiv.appendChild(deleteIcon); diff --git a/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsView.php b/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsView.php index 9fdccd9..a0453f1 100644 --- a/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsView.php +++ b/app/Core/Admin/Http/Controllers/Views/Payments/PaymentsView.php @@ -65,7 +65,7 @@ public function payments() public function list(): Response { - $table = table(); + $table = table()->setSelectable(true); $payments = rep(PaymentGateway::class)->findAll(); $table->addColumns([ @@ -101,7 +101,8 @@ function(data, type, full, meta) { deleteDiv.classList.add("action-button", "delete"); deleteDiv.setAttribute("data-translate", "admin.payments.delete"); deleteDiv.setAttribute("data-translate-attribute", "data-tooltip"); - deleteDiv.setAttribute("data-deletepayment", data[0]); + deleteDiv.setAttribute("data-deleteaction", data[0]); + deleteDiv.setAttribute("data-deletepath", "payments"); let deleteIcon = make("i"); deleteIcon.classList.add("ph-bold", "ph-trash"); deleteDiv.appendChild(deleteIcon); diff --git a/app/Core/Admin/Http/Controllers/Views/RedirectsView.php b/app/Core/Admin/Http/Controllers/Views/RedirectsView.php index 14cfbef..3685488 100644 --- a/app/Core/Admin/Http/Controllers/Views/RedirectsView.php +++ b/app/Core/Admin/Http/Controllers/Views/RedirectsView.php @@ -18,7 +18,7 @@ public function __construct() public function list(): Response { - $table = table(); + $table = table()->setSelectable(true); $table->setPhrases([ 'fromUrl' => __('admin.redirects.from'), 'toUrl' => __('admin.redirects.to'), diff --git a/app/Core/Admin/Http/Controllers/Views/ServersView.php b/app/Core/Admin/Http/Controllers/Views/ServersView.php index c519ec0..5367814 100644 --- a/app/Core/Admin/Http/Controllers/Views/ServersView.php +++ b/app/Core/Admin/Http/Controllers/Views/ServersView.php @@ -19,7 +19,7 @@ public function __construct() public function list(): Response { - $table = table(); + $table = table()->setSelectable(true); $servers = rep(Server::class)->findAll(); $table->addColumns([ @@ -58,7 +58,8 @@ function(data, type, full, meta) { deleteDiv.classList.add("action-button", "delete"); deleteDiv.setAttribute("data-translate", "admin.servers.delete"); deleteDiv.setAttribute("data-translate-attribute", "data-tooltip"); - deleteDiv.setAttribute("data-deleteserver", data[0]); + deleteDiv.setAttribute("data-deleteaction", data[0]); + deleteDiv.setAttribute("data-deletepath", "servers"); let deleteIcon = make("i"); deleteIcon.classList.add("ph-bold", "ph-trash"); deleteDiv.appendChild(deleteIcon); diff --git a/app/Core/Admin/Http/Controllers/Views/SocialsView.php b/app/Core/Admin/Http/Controllers/Views/SocialsView.php index 2199e79..892ac2e 100644 --- a/app/Core/Admin/Http/Controllers/Views/SocialsView.php +++ b/app/Core/Admin/Http/Controllers/Views/SocialsView.php @@ -20,7 +20,7 @@ public function __construct() public function list(): Response { - $table = table(); + $table = table()->setSelectable(true); $socials = rep(SocialNetwork::class)->findAll(); $table->addColumns([ @@ -68,7 +68,8 @@ function(data, type, full, meta) { deleteDiv.classList.add("action-button", "delete"); deleteDiv.setAttribute("data-translate", "admin.socials.delete"); deleteDiv.setAttribute("data-translate-attribute", "data-tooltip"); - deleteDiv.setAttribute("data-deletesocial", data[0]); + deleteDiv.setAttribute("data-deleteaction", data[0]); + deleteDiv.setAttribute("data-deletepath", "socials"); let deleteIcon = make("i"); deleteIcon.classList.add("ph-bold", "ph-trash"); deleteDiv.appendChild(deleteIcon); diff --git a/app/Core/Admin/Http/Controllers/Views/ThemesView.php b/app/Core/Admin/Http/Controllers/Views/ThemesView.php index 20e0abe..b2672e0 100644 --- a/app/Core/Admin/Http/Controllers/Views/ThemesView.php +++ b/app/Core/Admin/Http/Controllers/Views/ThemesView.php @@ -72,7 +72,8 @@ function(data, type, full, meta) { deleteDiv.classList.add("action-button", "delete"); deleteDiv.setAttribute("data-translate", "admin.themes_list.delete_theme"); deleteDiv.setAttribute("data-translate-attribute", "data-tooltip"); - deleteDiv.setAttribute("data-deletetheme", data[0]); + deleteDiv.setAttribute("data-deleteaction", data[0]); + deleteDiv.setAttribute("data-deletepath", "themes"); deleteDiv.setAttribute("data-tooltip-conf", "left"); let deleteIcon = make("i"); deleteIcon.classList.add("ph-bold", "ph-trash"); diff --git a/app/Core/Admin/Http/Controllers/Views/UsersView.php b/app/Core/Admin/Http/Controllers/Views/UsersView.php index 2f5776d..2d69ab9 100644 --- a/app/Core/Admin/Http/Controllers/Views/UsersView.php +++ b/app/Core/Admin/Http/Controllers/Views/UsersView.php @@ -23,7 +23,7 @@ public function __construct() public function list(): Response { - $table = table(); + $table = table()->setSelectable(true); $users = rep(User::class)->select()->load('roles')->fetchAll(); foreach ($users as $key => $user) { diff --git a/app/Core/Admin/Http/Views/assets/js/layout.js b/app/Core/Admin/Http/Views/assets/js/layout.js index ea83d07..44d4f3a 100644 --- a/app/Core/Admin/Http/Views/assets/js/layout.js +++ b/app/Core/Admin/Http/Views/assets/js/layout.js @@ -477,6 +477,142 @@ $(function () { $iconMenu.slideUp(); }); + let isMouseDown = false; + let isCtrlKey = false; + let isShiftKey = false; + let lastSelectedIndex = null; + let $table, $rows; + + $(document) + .on( + 'mousedown', + 'table.selectable.dataTable > tbody > tr', + function (e) { + isMouseDown = true; + isCtrlKey = e.ctrlKey || e.metaKey; + isShiftKey = e.shiftKey; + $table = $(this).closest('table'); + $rows = $table.find('tbody > tr'); + + const $row = $(this); + const currentIndex = $rows.index($row); + + if (isCtrlKey) { + $row.toggleClass('selected'); + } else if (isShiftKey && lastSelectedIndex !== null) { + const start = Math.min(lastSelectedIndex, currentIndex); + const end = Math.max(lastSelectedIndex, currentIndex); + $rows.slice(start, end + 1).addClass('selected'); + } else { + if ($row.hasClass('selected')) { + $row.removeClass('selected'); + } else { + $rows.removeClass('selected'); + $row.addClass('selected'); + } + } + + lastSelectedIndex = currentIndex; + updateSelectionInfo(); + + return false; // prevent text selection + }, + ) + .on( + 'mousemove', + 'table.selectable.dataTable > tbody > tr', + function (e) { + if (isMouseDown && !isCtrlKey && !isShiftKey) { + const $row = $(this); + const currentIndex = $rows.index($row); + const start = Math.min(lastSelectedIndex, currentIndex); + const end = Math.max(lastSelectedIndex, currentIndex); + $rows.slice(start, end + 1).addClass('selected'); + } + }, + ); + + $(document).on('mouseup', function (e) { + isMouseDown = false; + }); + + // Сброс выделения при клике вне таблицы + $(document).on('click', function (e) { + if (!$(e.target).closest('table.selectable.dataTable').length) { + $('table.selectable.dataTable > tbody > tr').removeClass( + 'selected', + ); + updateSelectionInfo(); + } + }); + + // Сброс выделения при нажатии клавиши Esc + $(document).on('keydown', function (e) { + if (e.key === 'Escape') { + $('table.selectable.dataTable > tbody > tr').removeClass( + 'selected', + ); + updateSelectionInfo(); + } + }); + + // Обновление div с информацией о выделении + function updateSelectionInfo() { + if ($('table.selectable.dataTable > tbody > tr.selected').length > 0) { + $('#selection-info').addClass('opened'); + $('#count-rows > span').text( + $('table.selectable.dataTable > tbody > tr.selected').length, + ); + } else { + $('#selection-info').removeClass('opened'); + } + } + + // Получение HTML содержимого всех выделенных ячеек + $('#delete-rows').on('click', async function () { + const selectedCells = $( + 'table.selectable.dataTable > tbody > tr.selected', + ); + const ids = []; + + let path = null, + count = 0; + + selectedCells.each(function () { + let find = $(this).find('.action-button.delete'); + + if (find.length) { + ids.push(find.attr('data-deleteaction')); + + if (!path) { + path = find.attr('data-deletepath'); + } + } + }); + + const callback = (res) => { + count++; + + if (count === ids.length) { + refreshCurrentPage(); + } + }; + + if (path && ids) { + if (await asyncConfirm(translate('admin.confirm_delete'))) { + for (let id of ids) { + sendRequest( + {}, + 'admin/api/' + path + '/' + id, + 'DELETE', + callback, + false, + ); + } + } + } + }); + fetchIcons(); }); diff --git a/app/Core/Admin/Http/Views/assets/js/pages/footer/social/list.js b/app/Core/Admin/Http/Views/assets/js/pages/footer/social/list.js index 644e570..60f7dd5 100644 --- a/app/Core/Admin/Http/Views/assets/js/pages/footer/social/list.js +++ b/app/Core/Admin/Http/Views/assets/js/pages/footer/social/list.js @@ -1,19 +1,3 @@ $(function () { - $(document).on( - 'click', - '.social-action-buttons .action-button.delete', - async function () { - let socialId = $(this).data('deletesocial'); - if ( - await asyncConfirm( - translate('admin.footer.social_confirm_delete'), - ) - ) - sendRequest( - {}, - 'admin/api/footer/socials/' + socialId, - 'DELETE', - ); - }, - ); + // }); diff --git a/app/Core/Admin/Http/Views/assets/js/pages/modules.js b/app/Core/Admin/Http/Views/assets/js/pages/modules.js index 1ba3985..d198b01 100644 --- a/app/Core/Admin/Http/Views/assets/js/pages/modules.js +++ b/app/Core/Admin/Http/Views/assets/js/pages/modules.js @@ -350,21 +350,6 @@ $(function () { }); }); - // Handle delete module action - $(document).on( - 'click', - '.module-action-buttons .action-button.delete', - async function () { - let moduleId = $(this).data('deletemodule'); - if ( - await asyncConfirm( - translate('admin.modules_list.confirm_delete'), - ) - ) - sendRequest({}, u('admin/api/modules/' + moduleId), 'DELETE'); - }, - ); - // Handle install module action $(document).on( 'click', diff --git a/app/Core/Admin/Http/Views/assets/js/pages/payments/list.js b/app/Core/Admin/Http/Views/assets/js/pages/payments/list.js index 0ec1269..bb1c4a8 100644 --- a/app/Core/Admin/Http/Views/assets/js/pages/payments/list.js +++ b/app/Core/Admin/Http/Views/assets/js/pages/payments/list.js @@ -1,13 +1,4 @@ $(function () { - $(document).on( - 'click', - '.payment-action-buttons .action-button.delete', - async function () { - let paymentId = $(this).data('deletepayment'); - if (await asyncConfirm(translate('admin.payments.confirm_delete'))) - sendRequest({}, u('admin/api/payments/' + paymentId), 'DELETE'); - }, - ); // Handle disable payment action $(document).on( diff --git a/app/Core/Admin/Http/Views/assets/js/pages/payments/promo/list.js b/app/Core/Admin/Http/Views/assets/js/pages/payments/promo/list.js index 92294c2..60f7dd5 100644 --- a/app/Core/Admin/Http/Views/assets/js/pages/payments/promo/list.js +++ b/app/Core/Admin/Http/Views/assets/js/pages/payments/promo/list.js @@ -1,7 +1,3 @@ -$(function() { - $(document).on('click', '.payment-promo-action-buttons .action-button.delete', async function () { - let paymentId = $(this).data('deletepromo'); - if (await asyncConfirm(translate('admin.payments.promo.confirm_delete'))) - sendRequest({}, u('admin/api/payments/promo/' + paymentId), 'DELETE'); - }); +$(function () { + // }); diff --git a/app/Core/Admin/Http/Views/assets/js/pages/servers/list.js b/app/Core/Admin/Http/Views/assets/js/pages/servers/list.js index e9fc224..60f7dd5 100644 --- a/app/Core/Admin/Http/Views/assets/js/pages/servers/list.js +++ b/app/Core/Admin/Http/Views/assets/js/pages/servers/list.js @@ -1,11 +1,3 @@ $(function () { - $(document).on( - 'click', - '.servers-action-buttons .action-button.delete', - async function () { - let serverId = $(this).data('deleteserver'); - if (await asyncConfirm(translate('admin.servers.confirm_delete'))) - sendRequest({}, u('admin/api/servers/' + serverId), 'DELETE'); - }, - ); + // }); diff --git a/app/Core/Admin/Http/Views/assets/js/pages/socials/list.js b/app/Core/Admin/Http/Views/assets/js/pages/socials/list.js index 7d1084f..1525048 100644 --- a/app/Core/Admin/Http/Views/assets/js/pages/socials/list.js +++ b/app/Core/Admin/Http/Views/assets/js/pages/socials/list.js @@ -1,14 +1,5 @@ $(function () { - $(document).on( - 'click', - '.social-action-buttons .action-button.delete', - async function () { - let socialId = $(this).data('deletesocial'); - if (await asyncConfirm(translate('admin.socials.confirm_delete'))) - sendRequest({}, u('admin/api/socials/' + socialId), 'DELETE'); - }, - ); - + // Handle disable social action $(document).on( 'click', diff --git a/app/Core/Admin/Http/Views/assets/js/pages/themes.js b/app/Core/Admin/Http/Views/assets/js/pages/themes.js index 4dda423..c804cef 100644 --- a/app/Core/Admin/Http/Views/assets/js/pages/themes.js +++ b/app/Core/Admin/Http/Views/assets/js/pages/themes.js @@ -280,21 +280,6 @@ $(function () { }); }); - // Handle delete theme action - $(document).on( - 'click', - '.theme-action-buttons .action-button.delete', - async function () { - let themeId = $(this).data('deletetheme'); - if ( - await asyncConfirm( - translate('admin.themes_list.confirm_delete'), - ) - ) - ajaxModuleAction(u('admin/api/themes/' + themeId), 'DELETE'); - }, - ); - // Handle install theme action $(document).on( 'click', diff --git a/app/Core/Admin/Http/Views/assets/styles/components/_chips.scss b/app/Core/Admin/Http/Views/assets/styles/components/_chips.scss index ab45089..bb10fe5 100644 --- a/app/Core/Admin/Http/Views/assets/styles/components/_chips.scss +++ b/app/Core/Admin/Http/Views/assets/styles/components/_chips.scss @@ -13,5 +13,6 @@ font-size: 14px; font-weight: 500; white-space: nowrap; + box-shadow: 2px 2px 9px -6px $color-text-inverse; } } \ No newline at end of file diff --git a/app/Core/Admin/Http/Views/assets/styles/layouts/_tables.scss b/app/Core/Admin/Http/Views/assets/styles/layouts/_tables.scss index 728f0d9..d4d534a 100644 --- a/app/Core/Admin/Http/Views/assets/styles/layouts/_tables.scss +++ b/app/Core/Admin/Http/Views/assets/styles/layouts/_tables.scss @@ -2,7 +2,7 @@ :root { --dt-row-selected: 186, 255, 104; - --dt-row-selected-text: 255, 255, 255; + --dt-row-selected-text: 0, 0, 0, 0; --dt-row-selected-link: 11, 11, 11; --dt-row-stripe: 105, 105, 105; --dt-row-hover: 255, 255, 255; @@ -51,6 +51,19 @@ table.dataTable { } } + &.selectable { + tbody tr { + &:hover { + background-color: $color-white-10; + } + + &.selected { + background-color: rgba($color-primary, .2); + color: $color-text; + } + } + } + thead tr th { &:first-child { border-top-left-radius: 6px; @@ -800,20 +813,6 @@ table.dataTable { border-bottom: none; } - &.selected { - >* { - box-shadow: inset 0 0 0 9999px $color-primary-light; - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.9); - color: $color-text; - color: rgb(var(--dt-row-selected-text)); - } - - a { - color: $color-text-inverse; - color: rgb(var(--dt-row-selected-link)); - } - } - > { th, @@ -835,12 +834,6 @@ table.dataTable { border-top: none; } - &.row-border>tbody>tr.selected+tr.selected>td, - &.display>tbody>tr.selected+tr.selected>td { - border-top-color: rgba(186, 255, 104, 0.65); - border-top-color: rgba(var(--dt-row-selected), 0.65); - } - &.cell-border>tbody>tr { >* { border-top: 1px solid rgba(0, 0, 0, 0.15); @@ -862,24 +855,12 @@ table.dataTable { box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.023); } - &.stripe>tbody>tr:nth-child(odd).selected>*, - &.display>tbody>tr:nth-child(odd).selected>* { - box-shadow: inset 0 0 0 9999px rgba(186, 255, 104, 0.923); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.923); - } - &.hover>tbody>tr:hover>*, &.display>tbody>tr:hover>* { box-shadow: inset 0 0 0 9999px rgba(255, 255, 255, 0.035); box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.035); } - &.hover>tbody>tr.selected:hover>*, - &.display>tbody>tr.selected:hover>* { - box-shadow: inset 0 0 0 9999px $color-primary !important; - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 1) !important; - } - &.order-column>tbody tr> { .sorting_1, @@ -900,26 +881,7 @@ table.dataTable { } } - &.order-column>tbody tr.selected> { - - .sorting_1, - .sorting_2, - .sorting_3 { - box-shadow: inset 0 0 0 9999px rgba(186, 255, 104, 0.919); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919); - } - } - &.display>tbody { - tr.selected> { - - .sorting_1, - .sorting_2, - .sorting_3 { - box-shadow: inset 0 0 0 9999px rgba(186, 255, 104, 0.919); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919); - } - } >tr:nth-child(odd)>.sorting_1 { box-shadow: inset 0 0 0 9999px rgba(105, 105, 105, 0.054); @@ -944,24 +906,6 @@ table.dataTable { box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.039); } - &.display>tbody>tr:nth-child(odd).selected>.sorting_1, - &.order-column.stripe>tbody>tr:nth-child(odd).selected>.sorting_1 { - box-shadow: inset 0 0 0 9999px rgba(186, 255, 104, 0.954); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.954); - } - - &.display>tbody>tr:nth-child(odd).selected>.sorting_2, - &.order-column.stripe>tbody>tr:nth-child(odd).selected>.sorting_2 { - box-shadow: inset 0 0 0 9999px rgba(186, 255, 104, 0.947); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.947); - } - - &.display>tbody>tr:nth-child(odd).selected>.sorting_3, - &.order-column.stripe>tbody>tr:nth-child(odd).selected>.sorting_3 { - box-shadow: inset 0 0 0 9999px rgba(186, 255, 104, 0.939); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.939); - } - &.display>tbody>tr.even>.sorting_1, &.order-column.stripe>tbody>tr.even>.sorting_1 { box-shadow: inset 0 0 0 9999px rgba(105, 105, 105, 0.019); @@ -980,24 +924,6 @@ table.dataTable { box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.003); } - &.display>tbody>tr.even.selected>.sorting_1, - &.order-column.stripe>tbody>tr.even.selected>.sorting_1 { - box-shadow: inset 0 0 0 9999px rgba(186, 255, 104, 0.919); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919); - } - - &.display>tbody>tr.even.selected>.sorting_2, - &.order-column.stripe>tbody>tr.even.selected>.sorting_2 { - box-shadow: inset 0 0 0 9999px rgba(186, 255, 104, 0.911); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.911); - } - - &.display>tbody>tr.even.selected>.sorting_3, - &.order-column.stripe>tbody>tr.even.selected>.sorting_3 { - box-shadow: inset 0 0 0 9999px rgba(186, 255, 104, 0.903); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.903); - } - &.display tbody tr:hover>.sorting_1, &.order-column.hover tbody tr:hover>.sorting_1 { box-shadow: inset 0 0 0 9999px rgba(255, 255, 255, 0.082); @@ -1016,24 +942,6 @@ table.dataTable { box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.062); } - &.display tbody tr:hover.selected>.sorting_1, - &.order-column.hover tbody tr:hover.selected>.sorting_1 { - box-shadow: inset 0 0 0 9999px rgba(186, 255, 104, 0.982); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.982); - } - - &.display tbody tr:hover.selected>.sorting_2, - &.order-column.hover tbody tr:hover.selected>.sorting_2 { - box-shadow: inset 0 0 0 9999px rgba(186, 255, 104, 0.974); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.974); - } - - &.display tbody tr:hover.selected>.sorting_3, - &.order-column.hover tbody tr:hover.selected>.sorting_3 { - box-shadow: inset 0 0 0 9999px rgba(186, 255, 104, 0.962); - box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.962); - } - &.compact { thead { @@ -1341,10 +1249,6 @@ html.dark { --dt-column-ordering: 255, 255, 255; } -table.dataTable>tbody>tr { - opacity: 0.8; -} - html.dark { table.dataTable { > { @@ -1380,12 +1284,6 @@ html.dark { border-top: none; } - &.row-border>tbody>tr.selected+tr.selected>td, - &.display>tbody>tr.selected+tr.selected>td { - border-top-color: rgba(186, 255, 104, 0.65); - border-top-color: rgba(var(--dt-row-selected), 0.65); - } - &.cell-border>tbody>tr> { th, @@ -1495,3 +1393,77 @@ html.dark { } } } + +#selection-info { + position: absolute; + margin-left: auto; + margin-right: auto; + left: 0; + right: 0; + text-align: center; + bottom: 10px; + visibility: hidden; + opacity: 0; + transition: $transition; + animation: .3s closeContextMenu forwards ease-in-out; + background-color: $color-disabled; + display: flex; + flex-direction: row; + align-items: center; + gap: 15px; + border-radius: 50px; + width: max-content; + padding: 10px; + padding-left: 20px; + + >p { + font-size: 16px; + + > span { + margin-left: 5px; + font-weight: bold; + color: $color-primary; + } + } + + &.opened { + z-index: 10; + animation: .3s openContextMenu forwards ease-in-out; + visibility: visible; + } + + >button {} +} + +@keyframes closeContextMenu { + 0% { + bottom: 20px; + opacity: 1; + } + + 50% { + bottom: 25px; + } + + 100% { + bottom: 10px; + opacity: 0; + } +} + +@keyframes openContextMenu { + 0% { + bottom: 10px; + opacity: 0; + } + + 50% { + bottom: 25px; + opacity: 1; + } + + 100% { + bottom: 20px; + opacity: 1; + } +} \ No newline at end of file diff --git a/app/Core/Admin/Http/Views/layout.blade.php b/app/Core/Admin/Http/Views/layout.blade.php index 1c31070..faf0e9a 100644 --- a/app/Core/Admin/Http/Views/layout.blade.php +++ b/app/Core/Admin/Http/Views/layout.blade.php @@ -62,7 +62,12 @@ {{-- @stack('content') --}} - @breadcrumb +
+

@t('admin.selected_rows')0

+ +
diff --git a/app/Core/Admin/Services/UserService.php b/app/Core/Admin/Services/UserService.php index d963d1e..28f44a7 100644 --- a/app/Core/Admin/Services/UserService.php +++ b/app/Core/Admin/Services/UserService.php @@ -5,6 +5,7 @@ use Flute\Core\Database\Entities\User; use Flute\Core\Database\Entities\Role; use Flute\Core\Database\Entities\UserBlock; +use Flute\Core\DiscordLink\DiscordLinkRoles; use Nette\Utils\Validators; use Nette\Utils\AssertionException; @@ -20,7 +21,7 @@ class UserService public function editUser(array $input, string $userId): array { /** @var User */ - $user = rep(User::class)->findByPK((int) $userId); + $user = user()->get((int) $userId, true); if (!$user) { return ['status' => 'error', 'message' => __('admin.users.not_found'), 'code' => 404]; @@ -77,6 +78,8 @@ public function editUser(array $input, string $userId): array transaction($user)->run(); + app(DiscordLinkRoles::class)->linkRoles($user, $user->getRoles()->toArray()); + return ['status' => 'success', 'message' => __('def.success'), 'code' => 200]; } @@ -89,7 +92,7 @@ public function editUser(array $input, string $userId): array */ public function deleteUser(string $userId, User $currentUser): array { - $userToDelete = rep(User::class)->findByPK((int) $userId); + $userToDelete = user()->get((int) $userId, true); if (!$userToDelete) { return ['status' => 'error', 'message' => __('admin.users.not_found'), 'code' => 404]; @@ -109,6 +112,8 @@ public function deleteUser(string $userId, User $currentUser): array user()->log('events.user_deleted', $userToDelete->id); + app(DiscordLinkRoles::class)->clearRoles($userToDelete); + // Фактическое удаление пользователя transaction($userToDelete, 'delete')->run(); diff --git a/app/Core/App.php b/app/Core/App.php index 5d73ead..f54fb33 100644 --- a/app/Core/App.php +++ b/app/Core/App.php @@ -47,7 +47,7 @@ final class App * * @var string */ - public const VERSION = '0.2.2.2-alpha'; + public const VERSION = '0.2.2.3-alpha'; /** * Set the base path of the application diff --git a/app/Core/Http/Middlewares/MaintenanceMiddleware.php b/app/Core/Http/Middlewares/MaintenanceMiddleware.php index 588b1f3..683a88d 100644 --- a/app/Core/Http/Middlewares/MaintenanceMiddleware.php +++ b/app/Core/Http/Middlewares/MaintenanceMiddleware.php @@ -9,6 +9,9 @@ class MaintenanceMiddleware extends AbstractMiddleware { public function __invoke(FluteRequest $request, \Closure $next) { + if (!is_installed()) + return $next($request); + $path = $request->getPathInfo(); if ($path === '/login' || strpos($path, '/social/') === 0) { @@ -19,7 +22,7 @@ public function __invoke(FluteRequest $request, \Closure $next) return $next($request); } - if( config('app.maintenance_mode') && !is_debug() ) { + if (config('app.maintenance_mode') && !is_debug()) { return $this->error(config('app.maintenance_message') ? __(config('app.maintenance_message')) : __('def.maintenance_mode'), 503); } diff --git a/app/Core/Services/UserService.php b/app/Core/Services/UserService.php index 668cb19..7e7502a 100644 --- a/app/Core/Services/UserService.php +++ b/app/Core/Services/UserService.php @@ -54,7 +54,7 @@ public function __construct( */ protected function initializeByToken() { - if (!$this->userToken) + if (!$this->userToken || !is_installed()) return; try { @@ -86,6 +86,9 @@ protected function initializeByToken() */ public function initializeBySession() { + if (!is_installed()) + return; + if ($userId = session()->get('user_id')) { $this->currentUser = $this->get($userId); @@ -96,7 +99,7 @@ public function initializeBySession() } } - public function getByRoute(string $route, bool $force = false) : ?User + public function getByRoute(string $route, bool $force = false): ?User { if (isset($this->usersCache[$route]) && !$force) { return $this->usersCache[$route]; @@ -124,7 +127,7 @@ public function getByRoute(string $route, bool $force = false) : ?User return $this->usersCache[$route]; } - public function get(int $userId, bool $force = false) : ?User + public function get(int $userId, bool $force = false): ?User { if (isset($this->usersCache[$userId]) && !$force) { return $this->usersCache[$userId]; diff --git a/app/Core/Table/TableBuilder.php b/app/Core/Table/TableBuilder.php index 78f4ada..319aeb8 100644 --- a/app/Core/Table/TableBuilder.php +++ b/app/Core/Table/TableBuilder.php @@ -42,6 +42,11 @@ class TableBuilder */ protected array $data = []; + /** + * @var bool Добавляет возможность выбора строк через чекбоксы. + */ + protected bool $selectable = false; + /** * @var array Опции конфигурации DataTables. */ @@ -600,16 +605,6 @@ public function setPhrases(array $phrases): TableBuilder return $this; } - public function __get($name) - { - return isset($this->options[$name]) ? $this->options[$name] : null; - } - - public function __set($name, $value) - { - $this->options[$name] = $value; - } - protected function setJSRenderers(string $encodedHTML): string { foreach ($this->options['columnDefs'] as $column) { @@ -681,14 +676,27 @@ protected function setAjaxErrorHandler(string $html) return $encodedHTML; } + /** + * Устанавливает возможность выбора строк через чекбоксы. + * + * @param bool $selectable Включить или отключить чекбоксы. + * @return TableBuilder + */ + public function setSelectable(bool $selectable): TableBuilder + { + $this->selectable = $selectable; + return $this; + } + protected function generateHtml(): string { $overflowDiv = Html::el('div')->addClass('overflow-table'); - $table = Html::el('table')->id($this->tableId)->addClass("{$this->tableClass} skeleton"); + $table = Html::el('table')->id($this->tableId)->addClass("{$this->tableClass} skeleton" . ($this->selectable ? ' selectable' : '')); if (!empty($this->columns)) { $thead = Html::el('thead'); $tr = Html::el('tr'); + foreach ($this->columns as $column) { $tr->create('th')->addText($column->getTitle()); } @@ -698,7 +706,7 @@ protected function generateHtml(): string if (!empty($this->data)) { $tbody = Html::el('tbody'); - foreach ($this->data as $row) { + foreach ($this->data as $key => $row) { $tr = Html::el('tr'); foreach ($this->columns as $column) { $columnData = (is_string($column->getName()) || is_int($column->getName())) ? $column->getName() : null; @@ -731,6 +739,16 @@ protected function generateHtml(): string return $overflowDiv->render(); } + public function __get($name) + { + return isset($this->options[$name]) ? $this->options[$name] : null; + } + + public function __set($name, $value) + { + $this->options[$name] = $value; + } + protected function generateTableId(): void { $this->tableId = Random::generate(20); diff --git a/config/app.php b/config/app.php index df4e7b9..e2ccf9e 100644 --- a/config/app.php +++ b/config/app.php @@ -21,4 +21,7 @@ 'flute_copyright' => true, 'widget_placeholders' => true, 'footer_name' => '', + 'footer_html' => '', + 'maintenance_message' => '', + 'notifications_new_view' => false, ); \ No newline at end of file diff --git a/i18n/en/admin.php b/i18n/en/admin.php index 4f7e774..d952a21 100644 --- a/i18n/en/admin.php +++ b/i18n/en/admin.php @@ -106,6 +106,7 @@ "db_port_placeholder" => "Port", "settings_bar" => [ "system" => "System", + "additional" => "Additional", "auth" => "Authorization", "database" => "Databases", "language" => "Language", @@ -155,6 +156,8 @@ "timezone_description" => "Specify the site's timezone.", "notifications_label" => "Notifications", "notifications_description" => "Configure notification settings.", + "notifications_new_view_label" => "New view of notifications", + "notifications_new_view_description" => "Show user notifications in a new format (sidebar on the right)", "notifications_all" => "All notifications", "notifications_unread" => "Only unread", "save_button" => "Save", @@ -169,12 +172,17 @@ "performance_warning" => "This feature caches data whose retrieval time exceeds the norm, so some information may be out of date.", "maintenance_mode" => "Maintenance Mode", "maintenance_mode_description" => "This feature will hide the site from all non-admin users", + "maintenance_message" => "Maintenance message", + "maintenance_message_description" => "This message will be shown to users in maintenance mode", "will_close_site" => "Users will not be able to access the site", "maintenance_warning" => "While this feature is enabled, users will not be able to access the site or use its functions", "discord_link_roles" => "Linking Roles to Discord", "discord_link_roles_description" => "Будет связывать роли сайта и Discord сервера. Must have everything configured according to instruction!", "footer_name_label" => "Title in the footer", "footer_name_description" => "The name of your project in the footer of the site", + "token_incorrect" => "This STEAM key is not valid", + "footer_html_label" => "Custom HTML in the footer", + "footer_html_description" => "You can write your own custom HTML in the footer of the site, and it will be automatically added to the site" ], "lk" => [ "currency_view_label" => "Currency (view)", @@ -410,7 +418,14 @@ "server_duplicate" => "A server with these details already exists!", "display_ip" => "Displayed IP:PORT", "enabled" => "Active", - "enabled_description" => "Whether the server will be displayed in modules/widgets" + "enabled_description" => "Whether the server will be displayed in modules/widgets", + "check_online" => "Check connection", + "check_rcon" => "Check RCON", + "check" => "Check connections", + "check_description" => "You can check if the connection to the server via Flute is working.", + "rcon_command" => "RCON command.", + "rcon_command_placeholder" => "Enter RCON command", + "rcon_command_desc" => "This RCON command will be sent to the server" ], "payments" => [ "header" => "Payment gateways", @@ -877,5 +892,7 @@ "refresh_page" => "Refresh Page", "what_it_means" => "What does it mean?", "what_it" => "What is it?", - "update_available" => "Version :version available" + "update_available" => "Version :version available", + "start_tour" => "Guide is available! Click on me to get started!", + "selected_rows" => "Selected cells:" ]; diff --git a/i18n/en/def.php b/i18n/en/def.php index c335bb0..87d858c 100644 --- a/i18n/en/def.php +++ b/i18n/en/def.php @@ -216,5 +216,9 @@ 'hours' => 'hours|hour|hours|hours', 'days' => 'days|day|days|days', "discord_role_id" => "ID of the Role in Flute", - "discord_role_desc" => "This role will be automatically issued in Discord" + "discord_role_desc" => "This role will be automatically issued in Discord", + "check" => "Validate", + "validation" => "validation", + "security" => "security", + "forever" => "forever" ]; diff --git a/i18n/ru/admin.php b/i18n/ru/admin.php index f9f8df0..94b4cc8 100644 --- a/i18n/ru/admin.php +++ b/i18n/ru/admin.php @@ -894,4 +894,5 @@ "what_it" => "Что это?", "update_available" => "Доступна версия :version", "start_tour" => "Доступно обучение! Нажми на меня чтобы начать!", + "selected_rows" => "Выбрано ячеек:" ];