add: user homepage block ordering

- closes #4791
- uses alpinejs drag and drop function
- renames visible columns for better consistency
- eager loads user settings for block performance
This commit is contained in:
HDVinnie
2025-07-15 18:04:18 -04:00
parent fc763252d0
commit 170f704d0c
8 changed files with 481 additions and 247 deletions
+18 -2
View File
@@ -44,10 +44,26 @@ class HomeController extends Controller
$expiresAt = now()->addMinutes(5);
// Authorized User
$user = $request->user();
$user = $request->user()->load('settings');
return view('home.index', [
'user' => $user,
'user' => $user,
'blocks' => collect(
[
'news', 'chat', 'featured', 'random_media', 'poll',
'top_torrents', 'top_users', 'latest_topics', 'latest_posts',
'latest_comments', 'online'
]
)
->map(fn ($block) => [
'key' => $block,
'visible' => $user->settings->{"{$block}_block_visible"},
'position' => $user->settings->{"{$block}_block_position"},
])
->sortBy('position')
->filter(fn ($block) => $block['visible'])
->pluck('key')
->toArray(),
'users' => cache()->remember(
'online_users:by-group:'.auth()->user()->group_id,
$expiresAt,
@@ -22,50 +22,94 @@ class UpdateGeneralSettingRequest extends FormRequest
'required',
'boolean',
],
'news_visible' => [
'news_block_visible' => [
'required',
'boolean',
],
'chat_visible' => [
'news_block_position' => [
'required',
'numeric',
],
'chat_block_visible' => [
'required',
'boolean',
],
'featured_visible' => [
'chat_block_position' => [
'required',
'numeric',
],
'featured_block_visible' => [
'required',
'boolean',
],
'random_media_visible' => [
'featured_block_position' => [
'required',
'numeric',
],
'random_media_block_visible' => [
'required',
'boolean',
],
'poll_visible' => [
'random_media_block_position' => [
'required',
'numeric',
],
'poll_block_visible' => [
'required',
'boolean',
],
'top_torrents_visible' => [
'poll_block_position' => [
'required',
'numeric',
],
'top_torrents_block_visible' => [
'required',
'boolean',
],
'top_users_visible' => [
'top_torrents_block_position' => [
'required',
'numeric',
],
'top_users_block_visible' => [
'required',
'boolean',
],
'latest_topics_visible' => [
'top_users_block_position' => [
'required',
'numeric',
],
'latest_topics_block_visible' => [
'required',
'boolean',
],
'latest_posts_visible' => [
'latest_topics_block_position' => [
'required',
'numeric',
],
'latest_posts_block_visible' => [
'required',
'boolean',
],
'latest_comments_visible' => [
'latest_posts_block_position' => [
'required',
'numeric',
],
'latest_comments_block_visible' => [
'required',
'boolean',
],
'online_visible' => [
'latest_comments_block_position' => [
'required',
'numeric',
],
'online_block_visible' => [
'required',
'boolean',
],
'online_block_position' => [
'required',
'numeric',
],
'locale' => [
'required',
Rule::in(array_keys(Language::allowed())),
+22 -11
View File
@@ -297,17 +297,28 @@ class User extends Authenticatable implements MustVerifyEmail
{
return $this->hasOne(UserSetting::class)->withDefault([
'censor' => false,
'news_visible' => true,
'chat_visible' => true,
'featured_visible' => true,
'random_media_visible' => true,
'poll_visible' => true,
'top_torrents_visible' => true,
'top_users_visible' => true,
'latest_topics_visible' => true,
'latest_posts_visible' => true,
'latest_comments_visible' => true,
'online_visible' => true,
'news_block_visible' => true,
'news_block_position' => 0,
'chat_block_visible' => true,
'chat_block_position' => 1,
'featured_block_visible' => true,
'featured_block_position' => 2,
'random_media_block_visible' => true,
'random_media_block_position' => 3,
'poll_block_visible' => true,
'poll_block_position' => 4,
'top_torrents_block_visible' => true,
'top_torrents_block_position' => 5,
'top_users_block_visible' => true,
'top_users_block_position' => 6,
'latest_topics_block_visible' => true,
'latest_topics_block_position' => 7,
'latest_posts_block_visible' => true,
'latest_posts_block_position' => 8,
'latest_comments_block_visible' => true,
'latest_comments_block_position' => 9,
'online_block_visible' => true,
'online_block_position' => 10,
'locale' => config('app.locale'),
'style' => config('other.default_style', 0),
'torrent_layout' => 0,
+67 -33
View File
@@ -25,17 +25,29 @@ use Illuminate\Database\Eloquent\Model;
* @property int $id
* @property int $user_id
* @property bool $censor
* @property bool $news_visible
* @property bool $chat_visible
* @property bool $featured_visible
* @property bool $random_media_visible
* @property bool $poll_visible
* @property bool $top_torrents_visible
* @property bool $top_users_visible
* @property bool $latest_topics_visible
* @property bool $latest_posts_visible
* @property bool $latest_comments_visible
* @property bool $online_visible
* @property bool $news_block_visible
* @property int $news_block_position
* @property bool $chat_block_visible
* @property int $chat_block_position
* @property bool $featured_block_visible
* @property int $featured_block_position
* @property bool $random_media_block_visible
* @property int $random_media_block_position
* @property bool $poll_block_visible
* @property int $poll_block_position
* @property bool $top_torrents_block_visible
* @property int $top_torrents_block_position
* @property bool $top_users_block_visible
* @property int $top_users_block_position
* @property bool $latest_topics_block_visible
* @property int $latest_topics_block_position
* @property bool $latest_posts_block_visible
* @property int $latest_posts_block_position
* @property bool $latest_comments_block_visible
* @property int $latest_comments_block_position
* @property bool $online_block_visible
* @property int $online_block_position
* @property array $homepage_blocks_order
* @property string $locale
* @property int $style
* @property int $torrent_layout
@@ -57,17 +69,28 @@ class UserSetting extends Model
*
* @return array{
* censor: 'bool',
* news_visible: 'bool',
* chat_visible: 'bool',
* featured_visible: 'bool',
* random_media_visible: 'bool',
* poll_visible: 'bool',
* top_torrents_visible: 'bool',
* top_users_visible: 'bool',
* latest_topics_visible: 'bool',
* latest_posts_visible: 'bool',
* latest_comments_visible: 'bool',
* online_visible: 'bool',
* news_block_visible: 'bool',
* news_block_position: 'int',
* chat_block_visible: 'bool',
* chat_block_position: 'int',
* featured_block_visible: 'bool',
* featured_block_position: 'int',
* random_media_block_visible: 'bool',
* random_media_block_position: 'int',
* poll_block_visible: 'bool',
* poll_block_position: 'int',
* top_torrents_block_visible: 'bool',
* top_torrents_block_position: 'int',
* top_users_block_visible: 'bool',
* top_users_block_position: 'int',
* latest_topics_block_visible: 'bool',
* latest_topics_block_position: 'int',
* latest_posts_block_visible: 'bool',
* latest_posts_block_position: 'int',
* latest_comments_block_visible: 'bool',
* latest_comments_block_position: 'int',
* online_block_visible: 'bool',
* online_block_position: 'int',
* torrent_filters: 'bool',
* show_poster: 'bool',
* unbookmark_torrents_on_completion: 'bool',
@@ -77,17 +100,28 @@ class UserSetting extends Model
{
return [
'censor' => 'bool',
'news_visible' => 'bool',
'chat_visible' => 'bool',
'featured_visible' => 'bool',
'random_media_visible' => 'bool',
'poll_visible' => 'bool',
'top_torrents_visible' => 'bool',
'top_users_visible' => 'bool',
'latest_topics_visible' => 'bool',
'latest_posts_visible' => 'bool',
'latest_comments_visible' => 'bool',
'online_visible' => 'bool',
'news_block_visible' => 'bool',
'news_block_position' => 'int',
'chat_block_visible' => 'bool',
'chat_block_position' => 'int',
'featured_block_visible' => 'bool',
'featured_block_position' => 'int',
'random_media_block_visible' => 'bool',
'random_media_block_position' => 'int',
'poll_block_visible' => 'bool',
'poll_block_position' => 'int',
'top_torrents_block_visible' => 'bool',
'top_torrents_block_position' => 'int',
'top_users_block_visible' => 'bool',
'top_users_block_position' => 'int',
'latest_topics_block_visible' => 'bool',
'latest_topics_block_position' => 'int',
'latest_posts_block_visible' => 'bool',
'latest_posts_block_position' => 'int',
'latest_comments_block_visible' => 'bool',
'latest_comments_block_position' => 'int',
'online_block_visible' => 'bool',
'online_block_position' => 'int',
'torrent_filters' => 'bool',
'show_poster' => 'bool',
'unbookmark_torrents_on_completion' => 'bool',
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
/**
* NOTICE OF LICENSE.
*
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
* The details is bundled with this project in the file LICENSE.txt.
*
* @project UNIT3D Community Edition
*
* @author HDVinnie <hdinnovations@protonmail.com>
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
*/
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('user_settings', function (Blueprint $table): void {
$table->renameColumn('news_visible', 'news_block_visible');
$table->renameColumn('chat_visible', 'chat_block_visible');
$table->renameColumn('featured_visible', 'featured_block_visible');
$table->renameColumn('random_media_visible', 'random_media_block_visible');
$table->renameColumn('poll_visible', 'poll_block_visible');
$table->renameColumn('top_torrents_visible', 'top_torrents_block_visible');
$table->renameColumn('top_users_visible', 'top_users_block_visible');
$table->renameColumn('latest_topics_visible', 'latest_topics_block_visible');
$table->renameColumn('latest_posts_visible', 'latest_posts_block_visible');
$table->renameColumn('latest_comments_visible', 'latest_comments_block_visible');
$table->renameColumn('online_visible', 'online_block_visible');
$table->unsignedTinyInteger('news_block_position')->default(0)->after('news_block_visible');
$table->unsignedTinyInteger('chat_block_position')->default(1)->after('chat_block_visible');
$table->unsignedTinyInteger('featured_block_position')->default(2)->after('featured_block_visible');
$table->unsignedTinyInteger('random_media_block_position')->default(3)->after('random_media_block_visible');
$table->unsignedTinyInteger('poll_block_position')->default(4)->after('poll_block_visible');
$table->unsignedTinyInteger('top_torrents_block_position')->default(5)->after('top_torrents_block_visible');
$table->unsignedTinyInteger('top_users_block_position')->default(6)->after('top_users_block_visible');
$table->unsignedTinyInteger('latest_topics_block_position')->default(7)->after('latest_topics_block_visible');
$table->unsignedTinyInteger('latest_posts_block_position')->default(8)->after('latest_posts_block_visible');
$table->unsignedTinyInteger('latest_comments_block_position')->default(9)->after('latest_comments_block_visible');
$table->unsignedTinyInteger('online_block_position')->default(10)->after('online_block_visible');
});
}
};
+11 -11
View File
@@ -144,17 +144,17 @@ return [
'hit-n-runs-count' => 'Hit and Run Count (All Time)',
'hit-n-runs-history' => 'Torrent Hit and Runs History',
'homepage-blocks' => 'Homepage Blocks',
'homepage-block-chat-visible' => 'Show Chatbox Block',
'homepage-block-featured-visible' => 'Show Featured Torrents Block',
'homepage-block-latest-comments-visible' => 'Show Latest Comments Block',
'homepage-block-latest-posts-visible' => 'Show Latest Posts Block',
'homepage-block-latest-topics-visible' => 'Show Latest Topics Block',
'homepage-block-news-visible' => 'Show News Block',
'homepage-block-online-visible' => 'Show Online Users Block',
'homepage-block-poll-visible' => 'Show Poll Block',
'homepage-block-random-media-visible' => 'Show Random Media Block',
'homepage-block-top-torrents-visible' => 'Show Top Torrents Block',
'homepage-block-top-users-visible' => 'Show Top Users Block',
'homepage-block-chat-visible' => 'Show Chatbox',
'homepage-block-featured-visible' => 'Show Featured Torrents',
'homepage-block-latest-comments-visible' => 'Show Latest Comments',
'homepage-block-latest-posts-visible' => 'Show Latest Posts',
'homepage-block-latest-topics-visible' => 'Show Latest Topics',
'homepage-block-news-visible' => 'Show Latest News',
'homepage-block-online-visible' => 'Show Online Users',
'homepage-block-poll-visible' => 'Show Latest Poll',
'homepage-block-random-media-visible' => 'Show Random Media',
'homepage-block-top-torrents-visible' => 'Show Top Torrents',
'homepage-block-top-users-visible' => 'Show Top Users',
'id-permissions' => 'ID & Permissions',
'image' => 'Image',
'important' => 'Important',
+39 -36
View File
@@ -3,50 +3,53 @@
@section('page', 'page__home')
@section('main')
@if ($user->settings->news_visible)
@include('blocks.news')
@endif
@foreach ($blocks as $block)
@switch($block)
@case('news')
@include('blocks.news')
@if ($user->settings->chat_visible)
<div id="vue">
@include('blocks.chat')
</div>
@vite('resources/js/unit3d/chat.js')
@endif
@break
@case('chat')
@include('blocks.chat')
@vite('resources/js/unit3d/chat.js')
@if ($user->settings->featured_visible)
@include('blocks.featured')
@endif
@break
@case('featured')
@include('blocks.featured')
@if ($user->settings->random_media_visible)
@livewire('random-media')
@endif
@break
@case('random_media')
@livewire('random-media')
@if ($user->settings->poll_visible)
@include('blocks.poll')
@endif
@break
@case('poll')
@include('blocks.poll')
@if ($user->settings->top_torrents_visible)
@livewire('top-torrents')
@endif
@break
@case('top_torrents')
@livewire('top-torrents')
@if ($user->settings->top_users_visible)
@livewire('top-users')
@endif
@break
@case('top_users')
@livewire('top-users')
@if ($user->settings->latest_topics_visible)
@include('blocks.latest-topics')
@endif
@break
@case('latest_topics')
@include('blocks.latest-topics')
@if ($user->settings->latest_posts_visible)
@include('blocks.latest-posts')
@endif
@break
@case('latest_posts')
@include('blocks.latest-posts')
@if ($user->settings->latest_comments_visible)
@include('blocks.latest-comments')
@endif
@break
@case('latest_comments')
@include('blocks.latest-comments')
@if ($user->settings->online_visible)
@include('blocks.online')
@endif
@break
@case('online')
@include('blocks.online')
@break
@endswitch
@endforeach
@endsection
@@ -215,149 +215,222 @@
</fieldset>
<fieldset class="form form__fieldset">
<legend class="form__legend">{{ __('user.homepage-blocks') }}</legend>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="news_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="news_visible"
value="1"
@checked($user->settings->news_visible)
/>
{{ __('user.homepage-block-news-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="chat_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="chat_visible"
value="1"
@checked($user->settings->chat_visible)
/>
{{ __('user.homepage-block-chat-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="featured_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="featured_visible"
value="1"
@checked($user->settings->featured_visible)
/>
{{ __('user.homepage-block-featured-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="random_media_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="random_media_visible"
value="1"
@checked($user->settings->random_media_visible)
/>
{{ __('user.homepage-block-random-media-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="poll_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="poll_visible"
value="1"
@checked($user->settings->poll_visible)
/>
{{ __('user.homepage-block-poll-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="top_torrents_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="top_torrents_visible"
value="1"
@checked($user->settings->top_torrents_visible)
/>
{{ __('user.homepage-block-top-torrents-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="top_users_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="top_users_visible"
value="1"
@checked($user->settings->top_users_visible)
/>
{{ __('user.homepage-block-top-users-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="latest_topics_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="latest_topics_visible"
value="1"
@checked($user->settings->latest_topics_visible)
/>
{{ __('user.homepage-block-latest-topics-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="latest_posts_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="latest_posts_visible"
value="1"
@checked($user->settings->latest_posts_visible)
/>
{{ __('user.homepage-block-latest-posts-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="latest_comments_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="latest_comments_visible"
value="1"
@checked($user->settings->latest_comments_visible)
/>
{{ __('user.homepage-block-latest-comments-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="online_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="online_visible"
value="1"
@checked($user->settings->online_visible)
/>
{{ __('user.homepage-block-online-visible') }}
</label>
</p>
<fieldset class="form__fieldset">
<legend class="form__legend">Block Visability</legend>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="news_block_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="news_block_visible"
value="1"
@checked($user->settings->news_block_visible)
/>
{{ __('user.homepage-block-news-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="chat_block_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="chat_block_visible"
value="1"
@checked($user->settings->chat_block_visible)
/>
{{ __('user.homepage-block-chat-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="featured_block_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="featured_block_visible"
value="1"
@checked($user->settings->featured_block_visible)
/>
{{ __('user.homepage-block-featured-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="random_media_block_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="random_media_block_visible"
value="1"
@checked($user->settings->random_media_block_visible)
/>
{{ __('user.homepage-block-random-media-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="poll_block_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="poll_block_visible"
value="1"
@checked($user->settings->poll_block_visible)
/>
{{ __('user.homepage-block-poll-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="top_torrents_block_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="top_torrents_block_visible"
value="1"
@checked($user->settings->top_torrents_block_visible)
/>
{{ __('user.homepage-block-top-torrents-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="top_users_block_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="top_users_block_visible"
value="1"
@checked($user->settings->top_users_block_visible)
/>
{{ __('user.homepage-block-top-users-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="latest_topics_block_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="latest_topics_block_visible"
value="1"
@checked($user->settings->latest_topics_block_visible)
/>
{{ __('user.homepage-block-latest-topics-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="latest_posts_block_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="latest_posts_block_visible"
value="1"
@checked($user->settings->latest_posts_block_visible)
/>
{{ __('user.homepage-block-latest-posts-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input
type="hidden"
name="latest_comments_block_visible"
value="0"
/>
<input
class="form__checkbox"
type="checkbox"
name="latest_comments_block_visible"
value="1"
@checked($user->settings->latest_comments_block_visible)
/>
{{ __('user.homepage-block-latest-comments-visible') }}
</label>
</p>
<p class="form__group">
<label class="form__label">
<input type="hidden" name="online_block_visible" value="0" />
<input
class="form__checkbox"
type="checkbox"
name="online_block_visible"
value="1"
@checked($user->settings->online_block_visible)
/>
{{ __('user.homepage-block-online-visible') }}
</label>
</p>
</fieldset>
<fieldset class="form__fieldset">
<legend class="form__legend">Block Order</legend>
<ul
x-data="{
blocks: [
@foreach ([
'news' => __('blocks.check-news'),
'chat' => __('blocks.chatbox'),
'featured' => __('blocks.featured-torrents'),
'random_media' => 'Random Media',
'poll' => 'Polls',
'top_torrents' => __('blocks.top-torrents'),
'top_users' => 'Top Users',
'latest_topics' => __('blocks.latest-topics'),
'latest_posts' => __('blocks.latest-posts'),
'latest_comments' => __('blocks.latest-comments'),
'online' => 'Online Users'
] as $block => $label)
{
key: '{{ $block }}',
label: '{{ $label }}',
position: {{ (int) $user->settings->{$block . '_block_position'} }},
},
@endforeach
].sort((a, b) => a.position - b.position),
dragging: null,
dragOver: null,
move(from, to) {
if (from === to) return;
const moved = this.blocks.splice(from, 1)[0];
this.blocks.splice(to, 0, moved);
this.blocks.forEach((block, index) => block.position = index);
}
}"
class="order__list"
style="padding-inline-start: 0; margin-block-start: 0; margin-block-end: 0;"
>
<template x-for="(block, index) in blocks" :key="block.key">
<li
class="order__item"
:data-block="block.key"
draggable="true"
@dragstart="dragging = index"
@dragover.prevent="dragOver = index"
@dragleave="dragOver = null"
@drop="move(dragging, index); dragging = null; dragOver = null"
:class="{'drag-over': dragOver === index}"
style="
cursor: move;
user-select: none;
padding: 4px 0;
list-style: none;
"
>
<i class="{{ config('other.font-awesome') }} fa-arrows-alt"></i>
<span x-text="block.label"></span>
<input
type="hidden"
:name="block.key + '_block_position'"
:value="block.position"
/>
</li>
</template>
</ul>
<small class="text-info">Drag and drop to reorder blocks.</small>
</fieldset>
</fieldset>
<fieldset class="form form__fieldset">
<legend class="form__legend">Torrent</legend>