A lot of things.

This commit is contained in:
2026-06-08 16:25:52 +02:00
parent 6f6d6b9b84
commit f529f74823
94 changed files with 9178 additions and 107 deletions

View File

@@ -63,10 +63,10 @@
</div>
<div class="hovercard-actions">
<a href="#" class="btn" title="View profile">
<a :href="`{{ xfRoute('members') }}/${$store.hovercard.data.user_id}/`" class="btn" title="View profile">
<i data-lucide="user" size="14"></i>
</a>
<a href="#" class="btn" title="Send message">
<a :href="`{{ xfRoute('direct-messages/add') }}?to=${$store.hovercard.data.username.replace(' ', '+')}`" class="btn" title="Send message">
<i data-lucide="mail" size="14"></i>
</a>
</div>

View File

@@ -15,10 +15,12 @@
<div class="menu-group">
<div class="menu-group-title">{{ $menu['name'] }}</div>
@foreach( $menu['items'] as $item )
<a href="{{ isset($item['xf_route']) ? xfRoute($item['xf_route']) : route($item['route']) }}"
@class(['menu-item', 'active' => request()->routeIs( $item['route'] ?? '' )]) >
<i data-lucide="{{ $item['icon'] }}"></i><span>{{ $item['name'] }}</span>
</a>
@if( !isset( $item['condition'] ) || $item['condition']() )
<a href="{{ isset($item['xf_route']) ? xfRoute($item['xf_route']) : route($item['route']) }}"
@class(['menu-item', 'active' => request()->routeIs( $item['route'] ?? '' )]) >
<i data-lucide="{{ $item['icon'] }}"></i><span>{{ $item['name'] }}</span>
</a>
@endif
@endforeach
</div>
@endforeach

View File

@@ -0,0 +1,6 @@
<form class="search-bar" style="margin-bottom: 15px;">
<input type="text" name="{{ $param }}" placeholder="{{ $placeholder }}" value="{{ request($param) }}" autocomplete="off" />
<button type="submit" class="search-button">
<i data-lucide="search" size="18" color="var(--text2)"></i>
</button>
</form>

View File

@@ -1,6 +1,7 @@
<div class="submit-level" x-data="{
nsfw: null,
state: '{{ old('submit-state', $defaultState) }}',
deleteOpen: false,
init(){
this.$watch('nsfw', (val) => {
if( val && this.state === 'published' ) {
@@ -9,6 +10,13 @@
});
}
}" x-init="init()">
@if($isEdit)
<div>
<button type="button" class="btn danger" @click="deleteOpen = true; $dispatch('modal:opened')">
<i data-lucide="trash-2" size="13"></i> Delete
</button>
</div>
@endif
<div>
@if( section_must_be( [ 'romhacks', 'homebrew' ], $section ) && !$isEdit )
<label class="nsfw-label"><input id="nsfw-checkbox" type="checkbox" name="nsfw-entry" x-model="nsfw" style="transform: scale(1.5)"> NSFW</label>
@@ -25,4 +33,49 @@
@endif
@endforeach
</select>
@if($isEdit)
<template x-teleport="body">
<div
class="modal-overlay"
x-cloak
x-show="deleteOpen"
x-transition.opacity
@click.self="deleteOpen = false"
@keydown.escape.window="deleteOpen = false"
@modal:opened.window="refreshIcons($el)"
>
<div class="modal-window" x-show="deleteOpen" x-transition>
<div class="modal-header">
<span class="modal-title">Delete entry</span>
<button type="button" class="modal-close" @click="deleteOpen = false">
<i data-lucide="x" size="20"></i>
</button>
</div>
<div class="modal-body">
<p style="margin-bottom: 1.5rem; color: var(--text, #333);">
Are you sure you want to delete this entry? This action cannot be undone.
</p>
<form action="{{ route('submit.destroy', ['section' => $section, 'entry' => $entry ]) }}" method="POST">
@csrf
@method('DELETE')
<div class="queue-mod-actions">
<button type="button" class="btn" @click="deleteOpen = false">
Cancel
</button>
<button type="submit" class="btn danger">
<i data-lucide="trash-2" size="14"></i>
Confirm deletion
</button>
</div>
</form>
</div>
</div>
</div>
</template>
@endif
</div>

View File

@@ -4,14 +4,16 @@
<i data-lucide="menu"></i>
</button>
<div class="search-bar">
<i data-lucide="search" size="18" color="var(--text2)"></i>
<input type="text">Search</input>
</div>
<form class="search-bar" action="{{ route('entries.index') }}">
<input type="text" name="s" placeholder="Search" />
<button type="submit" class="search-button">
<i data-lucide="search" size="18" color="var(--text2)"></i>
</button>
</form>
<div class="topbar-actions">
@if( !\Auth::guest() && \Auth::user()->is_admin === 1 )
@can('is-admin')
@php $topbarAdminSeparator = true; @endphp
<a href="{{ config('app.forum_url') . '/admin.php' }}" class="btn">
<i data-lucide="landmark" size="18"></i>
@@ -19,15 +21,15 @@
<a href="{{ config('app.url') . '/manage' }}" class="btn">
<i data-lucide="shield-cog" size="18"></i>
</a>
@endif
@endcan
@if( $topbarAdminSeparator )
<div class="vertical-separator"></div>
@endif
@if( !\Auth::guest() && \Auth::user()->is_moderator === 1 )
@can('is-mod')
@php $topbarModSeparator = true; @endphp
<a href="#" class="btn">
<a href="{{ route('modcp.index') }}" class="btn">
<i data-lucide="siren" size="18"></i>
</a>
<a href="{{ xfRoute('approval-queue') }}" class="btn">
@@ -36,7 +38,7 @@
<a href="{{ xfRoute('reports') }}" class="btn">
<i data-lucide="triangle-alert" size="18"></i>
</a>
@endif
@endcan
@if( $topbarModSeparator )
<div class="vertical-separator"></div>

View File

@@ -0,0 +1,54 @@
<div class="drafts-item">
<div class="drafts-cover">
@if($entry->main_image)
<img src="{{ Storage::url($entry->main_image) }}">
@else
<div class="drafts-cover-placeholder">
<i data-lucide="image" size="24"></i>
</div>
@endif
</div>
<div class="drafts-info">
<div class="drafts-top">
<div>
<h3 class="drafts-title">
{{ $entry->complete_title }}
</h3>
<div class="drafts-meta">
<span class="badge {{ $entry->type }}">
{{ \App\Livewire\Database::ENTRY_TYPES[$entry->type] }}
</span>
@if( $entry->getRealPlatform() )
<span class="badge">{{ $entry->getRealPlatform()->name }}</span>
@endif
@if( $entry->version )
<span class="badge">{{ $entry->version }}</span>
@endif
</div>
</div>
<div class="drafts-dates">
<span>
<i data-lucide="pencil" size="12"></i>
Last edited {{ $draft->updated_at->diffForHumans() }}
</span>
<span>
<i data-lucide="calendar" size="12"></i>
Created {{ $draft->created_at->format('d M Y') }}
</span>
</div>
</div>
<div class="drafts-actions">
<a href="{{ route('submit.edit', ['section' =>$entry->type, 'entry' => $entry]) }}" class="btn primary">
<i data-lucide="pen" size="13"></i>
Continue editing
</a>
<a href="{{ route('entries.show', ['section' => $entry->type, 'entry' => $entry ] ) }}" class="btn" target="_blank">
<i data-lucide="eye" size="13"></i>
Preview
</a>
</div>
</div>
</div>

View File

@@ -0,0 +1,25 @@
@extends('layouts.app')
@section('page-title', "My Drafts - " . config('app.name'))
@section('content')
<div class="page-title">
My Drafts
</div>
@if($drafts->isEmpty())
<div class="drafts-empty">
<i data-lucide="pen" size="48"></i>
<p>No drafts here</p>
</div>
@else
<div class="drafts-count">
<span>{{ $drafts->total() }} draft{{ $drafts->total() > 1 ? 's' : '' }}</span>
</div>
<div class="drafts-list">
@foreach($drafts as $draft)
@include('entries.draft_item', ['entry' => $draft])
@endforeach
</div>
{{ $drafts->links() }}
@endif
@endsection

View File

@@ -83,28 +83,28 @@
</div>
<div class="entry-meta-grid">
@if( $entry->game )
<x-entry-meta-item label="Game Name" value="{{ $entry->game->name }}" />
<x-entry-meta-item label="Game Name" value="{{ $entry->game->name }}" route="{!! databaseRoute( [ 'games' => [ $entry->game->id ], 'platforms' => [ $entry->getRealPlatform()?->id ] ] ) !!}" />
@endif
@if( $entry->getRealPlatform() )
<x-entry-meta-item label="Platform" value="{{ ($entry->getRealPlatform())->name }}" />
<x-entry-meta-item label="Platform" value="{{ ($entry->getRealPlatform())->name }}" route="{!! databaseRoute( ['platforms' => [ $entry->getRealPlatform()->id ] ] ) !!}" />
@endif
@if( $entry->game && $entry->game->genre )
<x-entry-meta-item label="Genre" value="{{ $entry->game->genre->name }}" />
<x-entry-meta-item label="Genre" value="{{ $entry->game->genre->name }}" route="{!! databaseRoute( ['genres' => [ $entry->game->genre->id ] ]) !!}" />
@endif
@if( $entry->languages->isNotEmpty() )
<x-entry-meta-item label="Language" value="{{ $entry->languages->pluck('name')->implode(', ') }}" route="none" />
<x-entry-meta-item label="Language" value="{{ $entry->languages->pluck('name')->implode(', ') }}" route="{!! databaseRoute( [ 'languages' => $entry->languages->pluck('id')->toArray() ]) !!}" />
@endif
@if( $entry->status_id )
<x-entry-meta-item label="Status" value="{{ $entry->status->name }}" />
<x-entry-meta-item label="Status" value="{{ $entry->status->name }}" route="{!! databaseRoute( ['statuses' => [ $entry->status->id ] ]) !!}" />
@endif
@if( $entry->modifications->isNotEmpty() )
<x-entry-meta-item label="Type of hack" value="{{ $entry->modifications->pluck('name')->implode(', ') }}" route="{!! databaseRoute( [ 'modifications' => $entry->modifications->pluck('id')->toArray() ] ) !!}" />
@endif
@if( $entry->version )
<x-entry-meta-item label="Version" value="{{ $entry->version }}" route="none" />
@endif
@if( $entry->release_date )
<x-entry-meta-item label="Release Date" value="{{ $entry->release_date }}" />
@endif
@if( $entry->modifications->isNotEmpty() )
<x-entry-meta-item label="Type of hack" value="{{ $entry->modifications->pluck('name')->implode(', ') }}" route="none" />
<x-entry-meta-item label="Release Date" value="{{ $entry->release_date->format('Y-m-d') }}" route="none" />
@endif
</div>
<div class="hack-actions" style="display:flex;gap:10px;">
@@ -206,7 +206,7 @@
@endforeach
</div>
@endif
@if( $entry->staff_credits )
@if( $entry->parseStaffCredits() )
<x-entry-section-title label="Staff Credits" icon="users-round" />
<div class="entry-description">
<ul>

View File

@@ -0,0 +1,95 @@
@extends('layouts.app')
@section('content')
<div class="modcp-wrapper">
<aside class="modcp-sidebar">
<div class="modcp-sidebar-header">
<i data-lucide="shield" size="16"></i>
Mod CP
</div>
<nav class="modcp-nav">
<div class="modcp-nav-group">
<span class="modcp-nav-label">Overview</span>
<a href="{{ route('modcp.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.index') ? 'active' : '' }}>
<i data-lucide="layout-dashboard" size="15"></i>
Dashboard
</a>
<a href="{{ route('queue.index') }}" class="modcp-nav-item" {{ request()->routeIs('queue.index') ? 'active' : '' }}>
<i data-lucide="gavel" size="15"></i>
Submissions Queue
@if(( $pending = \App\Models\Entry::where('state','pending')->count() ) > 0)
<span class="modcp-nav-badge">{{ $pending }}</span>
@endif
</a>
</div>
<div class="modcp-nav-group">
<span class="modcp-nav-label">Content</span>
<a href="{{ route('modcp.locked') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.locked') ? 'active' : '' }}>
<i data-lucide="lock" size="15"></i>
Locked entries
</a>
@can('is-admin')
<a href="{{ route('modcp.draft') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.draft') ? 'active' : '' }}>
<i data-lucide="scissors" size="15"></i>
Draft entries
</a>
<a href="{{ route('modcp.hidden') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.hidden') ? 'active' : '' }}>
<i data-lucide="eye-off" size="15"></i>
Hidden entries
</a>
<a href="{{ route('modcp.deleted') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.deleted') ? 'active' : '' }}>
<i data-lucide="trash-2" size="15"></i>
Deleted entries
</a>
@endcan
</div>
<div class="modcp-nav-group">
<span class="modcp-nav-label">Resources</span>
<a href="{{ route('modcp.games.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.games.*') ? 'active' : '' }}">
<i data-lucide="gamepad-2" size="15"></i>
Games
</a>
<a href="{{ route('modcp.languages.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.languages.*') ? 'active' : '' }}">
<i data-lucide="languages" size="15"></i>
Languages
</a>
<a href="{{ route('modcp.authors.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.authors.*') ? 'active' : '' }}">
<i data-lucide="users" size="15"></i>
Authors
</a>
@can('is-admin')
<a href="{{ route('modcp.platforms.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.platforms.*') ? 'active' : '' }}">
<i data-lucide="gamepad-directional" size="15"></i>
Platforms
</a>
<a href="{{ route('modcp.genres.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.genres.*') ? 'active' : '' }}">
<i data-lucide="box" size="15"></i>
Genres
</a>
@endcan
</div>
<div class="modcp-nav-group">
<span class="modcp-nav-label">Community</span>
<a href="{{ xfRoute('reports') }}" class="modcp-nav-item">
<i data-lucide="triangle-alert" size="15"></i>
Reports
</a>
<a href="{{ xfRoute('approval-queue') }}" class="modcp-nav-item">
<i data-lucide="message-circle-check" size="15"></i>
Approval Queue
</a>
</div>
</nav>
</aside>
<div class="modcp-content">
@yield('modcp-content')
</div>
</div>
@endsection

View File

@@ -37,6 +37,9 @@
{{-- Platforms --}}
<x-database-filter-without-mode title="Platform" :items="$allPlatforms" model="platforms"/>
{{-- Genres --}}
<x-database-filter-without-mode title="Genre" :items="$allGenres" model="genres"/>
{{-- Statuses --}}
<x-database-filter-without-mode title="Status" :items="$allStatuses" model="statuses"/>

View File

@@ -0,0 +1,89 @@
@extends('layouts.modcp')
@section('modcp-content')
<div class="modcp-page-title">
Authors
<span class="modcp-count">{{ $items->total() }}</span>
</div>
<x-mod-c-p-search placeholder="Search an author..."/>
<div class="modcp-add-form">
<form action="{{ route('modcp.authors.store') }}" method="POST">
@csrf
<div class="modcp-add-form-inner">
<input type="text" name="name" class="form-input"
placeholder="Author name..." required>
<div style="width:20%">
<livewire:xf-user-selector />
</div>
<input type="text" name="website" class="form-input"
placeholder="Website">
<button type="submit" class="btn primary">
<i data-lucide="plus" size="14"></i> Add
</button>
</div>
</form>
</div>
<div class="modcp-list">
@forelse($items as $author)
<div class="modcp-list-item" x-data="{ editing: false }">
<div class="modcp-list-item-info" x-show="!editing">
<span class="modcp-list-item-title">{{ $author->name }}</span>
<span class="modcp-list-item-meta">
<span class="badge">{{ $author->website ?? '—' }}</span>
@if(($xfUser = $author->user()) !== null )
<span class="badge">
<x-xf-username-link :user="$xfUser" />
</span>
@endif
· {{ $author->entries_count }} {{ Str::plural('entry', $author->entries_count) }}
</span>
</div>
<form action="{{ route('modcp.authors.update', $author) }}" method="POST"
class="modcp-list-item-edit modcp-list-item-edit--game"
x-show="editing" x-cloak>
@csrf @method('PATCH')
<input type="text" name="name" class="form-input"
placeholder="Author name..." value="{{ $author->name }}" required>
<div style="width:20%">
<livewire:xf-user-selector :initial-user-id="$author->user_id" />
</div>
<input type="text" name="website" class="form-input"
placeholder="Website" value="{{ $author->website }}">
<button type="submit" class="btn primary">
<i data-lucide="check" size="13"></i>
</button>
<button type="button" class="btn" @click="editing = false">
<i data-lucide="x" size="13"></i>
</button>
</form>
<div class="modcp-list-item-actions" x-show="!editing">
<button type="button" class="btn" @click="editing = true">
<i data-lucide="pen" size="13"></i>
</button>
<form action="{{ route('modcp.authors.destroy', $author) }}" method="POST"
style="display:inline"
onsubmit="return confirm('Delete {{ addslashes($author->name) }}?')">
@csrf @method('DELETE')
<button type="submit" class="btn danger"
{{ $author->entries_count > 0 ? 'disabled title=Has entries' : '' }}>
<i data-lucide="trash-2" size="13"></i>
</button>
</form>
</div>
</div>
@empty
<div class="modcp-empty"><p>No authors yet.</p></div>
@endforelse
</div>
{{ $items->links() }}
@endsection

View File

@@ -0,0 +1,61 @@
@extends('layouts.modcp')
@section('page-title', 'Deleted entries - ' . config('app.name') )
@section('modcp-content')
<div class="modcp-page-title">
<i data-lucide="trash-2" size="20"></i>
Deleted entries
<span class="modcp-count">{{ $entries->total() }}</span>
</div>
@if($entries->isEmpty())
<div class="modcp-empty">
<i data-lucide="check-circle" size="36"></i>
<p>No deleted entries.</p>
</div>
@else
<div class="modcp-list">
@foreach($entries as $entry)
<div class="modcp-list-item modcp-list-item--deleted">
<div class="modcp-list-item-cover">
@if($entry->main_image)
<img src="{{ Storage::url($entry->main_image) }}" alt="">
@else
<i data-lucide="image" size="20"></i>
@endif
</div>
<div class="modcp-list-item-info">
<span class="modcp-list-item-title">{{ $entry->complete_title ?? $entry->title }}</span>
<span class="modcp-list-item-meta">
<span class="badge {{ $entry->type }}">{{ $entry->type }}</span>
@php $daysLeft = max(0, 7 - (int) now()->diffInDays($entry->deleted_at)) @endphp
<span style="color: var(--error)">
Deleted {{ $entry->deleted_at->diffForHumans() }}
@if($daysLeft > 0) · purged in {{ $daysLeft }}d @endif
</span>
</span>
</div>
<div class="modcp-list-item-actions">
<form action="{{ route('modcp.restore', $entry->id) }}" method="POST" style="display:inline">
@csrf @method('PATCH')
<button type="submit" class="btn success">
<i data-lucide="rotate-ccw" size="13"></i> Restore
</button>
</form>
<form action="{{ route('modcp.destroy', $entry->id) }}" method="POST" style="display:inline"
@submit="if (!confirm('Permanently delete this entry?')) $event.preventDefault()">
@csrf @method('DELETE')
<button type="submit" class="btn danger">
<i data-lucide="trash-2" size="13"></i> Purge
</button>
</form>
</div>
</div>
@endforeach
</div>
{{ $entries->links() }}
@endif
@endsection

View File

@@ -0,0 +1,52 @@
@extends('layouts.modcp')
@section('page-title', $pageTitle . ' - ' . config('app.name') )
@section('modcp-content')
<div class="modcp-page-title">
{{ $pageTitle }}
<span class="modcp-count">{{ $entries->count() }}</span>
</div>
@if($entries->isEmpty())
<div class="modcp-empty">
<i data-lucide="check-circle" size="36"></i>
<p>No {{ $state }} entries.</p>
</div>
@else
<div class="modcp-list">
@foreach($entries as $entry)
<div class="modcp-list-item">
<div class="modcp-list-item-cover">
@if($entry->main_image)
<img src="{{ Storage::url($entry->main_image) }}" alt="">
@else
<i data-lucide="image" size="20"></i>
@endif
</div>
<div class="modcp-list-item-info">
<span class="modcp-list-item-title">{{ $entry->complete_title }}</span>
<span class="modcp-list-item-meta">
<span class="badge {{ $entry->type }}">{{ \App\Livewire\Database::ENTRY_TYPES[$entry->type] }}</span>
@if($entry->getRealPlatform())
<span class="badge">{{ $entry->getRealPlatform()->name }}</span>
@endif
Added {{ $entry->created_at->format('d M Y') }} by<x-xf-username-link :user-id="$entry->user_id" />
</span>
</div>
<div class="modcp-list-item-actions">
<a href="{{ route('entries.show', ['section' => $entry->type, 'entry' => $entry]) }}"
class="btn" target="_blank">
<i data-lucide="eye" size="13"></i> View
</a>
<a href="{{ route('submit.edit', [$entry->type, $entry->id]) }}"
class="btn">
<i data-lucide="pen" size="13"></i> Edit
</a>
</div>
</div>
@endforeach
</div>
{{ $entries->links() }}
@endif
@endsection

View File

@@ -0,0 +1,103 @@
@extends('layouts.modcp')
@section('modcp-content')
<div class="modcp-page-title">
Games
<span class="modcp-count">{{ $items->total() }}</span>
</div>
<x-mod-c-p-search placeholder="Search a game..." />
<div class="modcp-add-form">
<form action="{{ route('modcp.games.store') }}" method="POST">
@csrf
<div class="modcp-add-form-inner">
<input type="text" name="name" class="form-input"
placeholder="Game name..." required>
<select name="platform_id" class="form-select" required style="width: 20%">
<option value="" disabled selected>Platform...</option>
@foreach($platforms as $platform)
<option value="{{ $platform->id }}">{{ $platform->name }}</option>
@endforeach
</select>
<select name="genre_id" class="form-select" required style="width: 20%">
<option value="" disabled selected>Genre...</option>
@foreach($genres as $genre)
<option value="{{ $genre->id }}">{{ $genre->name }}</option>
@endforeach
</select>
<button type="submit" class="btn primary">
<i data-lucide="plus" size="14"></i> Add
</button>
</div>
</form>
</div>
<div class="modcp-list">
@forelse($items as $game)
<div class="modcp-list-item" x-data="{ editing: false }">
<div class="modcp-list-item-info" x-show="!editing">
<span class="modcp-list-item-title">{{ $game->name }}</span>
<span class="modcp-list-item-meta">
<span class="badge">{{ $game->platform->name ?? '—' }}</span>
<span class="badge">{{ $game->genre->name ?? '—' }}</span>
· {{ $game->entries_count }} {{ Str::plural('entry', $game->entries_count) }}
</span>
</div>
<form action="{{ route('modcp.games.update', $game) }}" method="POST"
class="modcp-list-item-edit modcp-list-item-edit--game"
x-show="editing" x-cloak>
@csrf @method('PATCH')
<input type="text" name="name" class="form-input"
value="{{ $game->name }}" required>
<select name="platform_id" class="form-select form-select--small" required>
@foreach($platforms as $platform)
<option value="{{ $platform->id }}"
{{ $game->platform_id == $platform->id ? 'selected' : '' }}>
{{ $platform->name }}
</option>
@endforeach
</select>
<select name="genre_id" class="form-select form-select--small" required>
@foreach($genres as $genre)
<option value="{{ $genre->id }}"
{{ $game->genre_id == $genre->id ? 'selected' : '' }}>
{{ $genre->name }}
</option>
@endforeach
</select>
<button type="submit" class="btn primary">
<i data-lucide="check" size="13"></i>
</button>
<button type="button" class="btn" @click="editing = false">
<i data-lucide="x" size="13"></i>
</button>
</form>
<div class="modcp-list-item-actions" x-show="!editing">
<button type="button" class="btn" @click="editing = true">
<i data-lucide="pen" size="13"></i>
</button>
<form action="{{ route('modcp.games.destroy', $game) }}" method="POST"
style="display:inline"
onsubmit="return confirm('Delete {{ addslashes($game->name) }}?')">
@csrf @method('DELETE')
<button type="submit" class="btn danger"
{{ $game->entries_count > 0 ? 'disabled title=Has entries' : '' }}>
<i data-lucide="trash-2" size="13"></i>
</button>
</form>
</div>
</div>
@empty
<div class="modcp-empty"><p>No games yet.</p></div>
@endforelse
</div>
{{ $items->links() }}
@endsection

View File

@@ -0,0 +1,91 @@
@extends('layouts.modcp')
@section('page-title', "Dashboard - " . config('app.name') )
@section('modcp-content')
<div class="modcp-page-title">
Dashboard
</div>
<div class="modcp-stats">
<a href="{{ route('queue.index') }}" class="modcp-stat-card modcp-stat-card--orange">
<div class="modcp-stat-icon"><i data-lucide="clipboard-list" size="22"></i></div>
<div class="modcp-stat-info">
<span class="modcp-stat-value">{{ $stats['pending'] }}</span>
<span class="modcp-stat-label">In queue</span>
</div>
</a>
<a href="{{ route('modcp.locked') }}" class="modcp-stat-card">
<div class="modcp-stat-icon"><i data-lucide="lock" size="22"></i></div>
<div class="modcp-stat-info">
<span class="modcp-stat-value">{{ $stats['locked'] }}</span>
<span class="modcp-stat-label">Locked</span>
</div>
</a>
@can('is-admin')
<a href="{{ route('modcp.draft') }}" class="modcp-stat-card">
<div class="modcp-stat-icon"><i data-lucide="scissors" size="22"></i></div>
<div class="modcp-stat-info">
<span class="modcp-stat-value">{{ $stats['draft'] }}</span>
<span class="modcp-stat-label">Draft</span>
</div>
</a>
<a href="{{ route('modcp.hidden') }}" class="modcp-stat-card">
<div class="modcp-stat-icon"><i data-lucide="eye-off" size="22"></i></div>
<div class="modcp-stat-info">
<span class="modcp-stat-value">{{ $stats['hidden'] }}</span>
<span class="modcp-stat-label">Hidden</span>
</div>
</a>
<a href="{{ route('modcp.deleted') }}" class="modcp-stat-card modcp-stat-card--danger">
<div class="modcp-stat-icon"><i data-lucide="trash-2" size="22"></i></div>
<div class="modcp-stat-info">
<span class="modcp-stat-value">{{ $stats['deleted'] }}</span>
<span class="modcp-stat-label">Deleted</span>
</div>
</a>
@endcan
<div class="modcp-stat-card modcp-stat-card--muted">
<div class="modcp-stat-icon"><i data-lucide="database" size="22"></i></div>
<div class="modcp-stat-info">
<span class="modcp-stat-value">{{ $stats['total'] }}</span>
<span class="modcp-stat-label">Total entries</span>
</div>
</div>
</div>
@if($recentDeleted->isNotEmpty())
<div class="modcp-section-title" style="margin-top: 25px;">Recently deleted</div>
<div class="modcp-list">
@foreach($recentDeleted as $entry)
<div class="modcp-list-item">
<div class="modcp-list-item-info">
<span class="modcp-list-item-title">{{ $entry->complete_title ?? $entry->title }}</span>
<span class="modcp-list-item-meta">
<span class="badge {{ $entry->type }}">{{ $entry->type }}</span>
Deleted {{ $entry->deleted_at->diffForHumans() }}
</span>
</div>
<div class="modcp-list-item-actions">
<form action="{{ route('modcp.restore', $entry->id) }}" method="POST" style="display:inline">
@csrf @method('PATCH')
<button type="submit" class="btn success">
<i data-lucide="rotate-ccw" size="13"></i> Restore
</button>
</form>
<form action="{{ route('modcp.destroy', $entry->id) }}" method="POST" style="display:inline"
onsubmit="return confirm('Permanently delete?')">
@csrf @method('DELETE')
<button type="submit" class="btn danger">
<i data-lucide="trash-2" size="13"></i> Purge
</button>
</form>
</div>
</div>
@endforeach
<a href="{{ route('modcp.deleted') }}" class="modcp-list-see-all">
See all deleted entries
</a>
</div>
@endif
@endsection

View File

@@ -0,0 +1,76 @@
@extends('layouts.modcp')
@section('page-title', $title . ' - ' . config('app.name'))
@section('modcp-content')
<div class="modcp-page-title">
{{ $title }}
<span class="modcp-count">{{ $items->total() }}</span>
</div>
<x-mod-c-p-search placeholder="Search a {{ $singular }}..." />
<div class="modcp-add-form">
<form action="{{ route($storeRoute) }}" method="POST" class="modcp-add-form-inner">
@csrf
<input type="text" name="name" class="form-input" placeholder="Add new {{ strtolower($singular) }}..." required>
@if(isset($extraFields))
@foreach($extraFields as $field)
<input type="text" name="{{ $field['name'] }}" class="form-input" placeholder="{{ $field['placeholder'] }}">
@endforeach
@endif
<button type="submit" class="btn primary">
<i data-lucide="plus" size="14"></i> Add
</button>
</form>
</div>
<div class="modcp-list">
@forelse($items as $item)
<div class="modcp-list-item" x-data="{ editing: false }">
<div class="modcp-list-item-info" x-show="!editing">
<span class="modcp-list-item-title">{{ $item->name }}</span>
<span class="modcp-list-item-meta">
slug: {{ $item->slug }}
@isset($item->entries_count)
· {{ $item->entries_count }} {{ Str::plural('entry', $item->entries_count) }}
@endisset
</span>
</div>
<form action="{{ route($updateRoute, $item) }}" method="POST"
class="modcp-list-item-edit" x-show="editing" x-cloak>
@csrf @method('PATCH')
<input type="text" name="name" class="form-input" value="{{ $item->name }}">
<button type="submit" class="btn primary">
<i data-lucide="check" size="13"></i>
</button>
<button type="button" class="btn" @click="editing = false">
<i data-lucide="x" size="13"></i>
</button>
</form>
<div class="modcp-list-item-actions" x-show="!editing">
<button type="button" class="btn" @click="editing = true">
<i data-lucide="pen" size="13"></i>
</button>
<form action="{{ route($destroyRoute, $item) }}" method="POST" style="display:inline"
onsubmit="return confirm('Delete {{ $item->name }}?')">
@csrf @method('DELETE')
<button type="submit" class="btn danger">
<i data-lucide="trash-2" size="13"></i>
</button>
</form>
</div>
</div>
@empty
<div class="modcp-empty">
<p>No {{ strtolower($title) }} yet.</p>
</div>
@endforelse
</div>
{{ $items->links() }}
@endsection

View File

@@ -137,7 +137,7 @@
@endif
<x-form-group-title label="{{ $words['attachments'] }}" icon="paperclip" />
<x-main-image-field :old-path="old('main-image', $entry->main_image ?? '')" />
<x-main-image-field :old-path="old('main-image', $entry->main_image ?? '') ?? ''" />
<x-gallery-field :old-paths="old('gallery', $entry->gallery->pluck('image')->toArray() ?? [] )"/>
@error('gallery')
<x-form-error-text message="{{ $message }}" />
@@ -181,6 +181,18 @@
<livewire:xf-user-selector :initial-user-id="$entry->user_id" />
</div>
</div>
<div class="form-group grid-c2">
<div>
<x-form-field-title name="XenForo Comments Thread ID" />
<input type="text" name="comments_thread_id" class="form-input" value="{{ old('comments_thread_id', $entry->comments_thread_id) }}">
</div>
</div>
<div class="form-group">
<x-form-field-title name="Metadata" required="true" />
<div class="form-type-of-checkboxes form-group level" id="entry-metadata">
<label><input class="form-checkbox" type="checkbox" name="featured" value="1" {{ old('featured', $entry->featured ) ? 'checked' : '' }}>Featured entry</label>
</div>
</div>
@endcan
@cannot('moderate', $entry)

View File

@@ -36,21 +36,18 @@
'upload-item-done': file.done,
'upload-item-error': file.error
}">
<template x-if="!file.done && !file.error">
<i data-lucide="loader-2" class="spin"></i>
</template>
<template x-if="file.error">
<i data-lucide="alert-circle"></i>
</template>
<template x-if="file.done && file.state === 'public'">
<i data-lucide="eye" class="file-state-icon file-state-icon--public"></i>
</template>
<template x-if="file.done && file.state === 'private'">
<i data-lucide="eye-off" class="file-state-icon file-state-icon--private"></i>
</template>
<template x-if="file.done && file.state === 'archived'">
<i data-lucide="archive" class="file-state-icon file-state-icon--archived"></i>
</template>
<div class="file-status-icons">
<i x-show="!file.done && !file.error" data-lucide="loader-2" class="spin"></i>
<i x-show="file.error" data-lucide="alert-circle"></i>
<i x-show="file.done && file.state === 'public'" data-lucide="eye" class="file-state-icon file-state-icon--public"></i>
<i x-show="file.done && file.state === 'private'" data-lucide="eye-off" class="file-state-icon file-state-icon--private"></i>
<i x-show="file.done && file.state === 'archived'" data-lucide="archive" class="file-state-icon file-state-icon--archived"></i>
</div>
<div class="upload-item-info">
<span class="upload-item-name" x-text="file.name"></span>
@@ -103,13 +100,46 @@
</div>
</div>
@endif
<div class="upload-item-actions">
<div class="upload-item-actions" x-data="{ showMetadata: false }">
<button type="button" class="btn" x-show="file.error" @click="handleRetryFile(i)">
<i data-lucide="refresh-cw"></i>
</button>
@if($isEdit)
<button type="button" class="btn" x-show="file.done" @click="showMetadata = true">
<i data-lucide="settings"></i>
</button>
@endif
<button type="button" class="btn" x-show="file.done || file.error" @click="handleRemoveFile(i)">
<i data-lucide="x"></i>
</button>
<template x-teleport="body">
<div class="modal-overlay"
x-cloak
x-show="showMetadata"
x-transition.opacity.duration.300ms
@click.self="showMetadata = false"
@keydown.escape.window="showMetadata = false">
<div class="modal-window" x-show="showMetadata" x-transition>
<div class="modal-header">
<span class="modal-title" style="display: flex; align-items: center; gap: 8px;">
File Settings: <span x-text="file.name" style="color: var(--rhpz-orange);"></span>
</span>
<button type="button" class="modal-close" @click="showMetadata = false">
<i data-lucide="x" size="20"></i>
</button>
</div>
<div class="modal-content">
<div class="form-group">
<x-form-group-title label="Online patcher" />
<label><input type="checkbox" class="form-checkbox" :name="'files_metadata[' + file.uuid + '][online_patcher]'" x-model="file.meta_online_patcher"> Enable it</label>
<label x-show="file.meta_online_patcher"><input type="checkbox" class="form-checkbox" :name="'files_metadata[' + file.meta_secondary_online_patcher + '][secondary_online_patcher]'" x-model="file.meta_secondary_online_patcher"> Mark as a secondary patch</label>
</div>
</div>
</div>
</div>
</template>
</div>
<input type="hidden" name="files_uuid[]" :value="file.uuid" x-show="file.done">
@if($isEdit)

View File

@@ -0,0 +1,92 @@
@extends('layouts.app')
@section('page-title', "ROM Patcher - " . config('app.name'))
@push('scripts')
<script type="text/javascript" src="{{ asset('rom-patcher-js/RomPatcher.webapp.js') }}"></script>
@endpush
@section('content')
<div class="page-title">
<span>ROM Patcher</span>
</div>
<div id="rom-patcher-container" class="patcher-container" x-data="RomPatcher({{ Js::from($patches ?? []) }})">
<div class="patcher-grid">
<div class="form-group">
<label class="form-label">1. Original ROM</label>
<div class="patcher-dropzone" id="rom-dropzone"
:class="{ 'dragover': isRomDragOver, 'has-file': romFileName !== '' }"
@click="triggerFileInput('rom-patcher-input-file-rom')"
@dragover.prevent="isRomDragOver = true"
@dragleave.prevent="isRomDragOver = false"
@drop.prevent="isRomDragOver = false; handleDrop($event, 'rom')">
<input type="file" id="rom-patcher-input-file-rom" style="display: none;" @change="handleInputChange($event, 'rom')" disabled />
<i data-lucide="gamepad-2" size="40" :style="romFileName ? 'color: var(--rhpz-orange)' : 'color: var(--text2)'"></i>
<span style="color: var(--text); font-weight: 500;" x-text="romFileName ? romFileName : 'Drag n\'drop your file here or click'"></span>
</div>
</div>
<div class="form-group">
<label class="form-label">2. Patch file</label>
<div class="patcher-dropzone" id="patch-dropzone"
x-show="!hasEmbedded"
:class="{ 'dragover': isPatchDragOver, 'has-file': patchFileName !== '' }"
@click="triggerFileInput('rom-patcher-input-file-patch')"
@dragover.prevent="isPatchDragOver = true"
@dragleave.prevent="isPatchDragOver = false"
@drop.prevent="isPatchDragOver = false; handleDrop($event, 'patch')">
<input type="file" id="rom-patcher-input-file-patch" style="display: none;" @change="handleInputChange($event, 'patch')" disabled />
<i data-lucide="file-archive" size="40" :style="patchFileName ? 'color: var(--rhpz-orange)' : 'color: var(--text2)'"></i>
<span style="color: var(--text); font-weight: 500;" x-text="patchFileName ? patchFileName : 'Drag n\'drop your file here or click'"></span>
</div>
<div x-show="hasEmbedded" class="embed-patch-box">
<div class="embed-patch-box-icon">
<div class="embed-patch-box-icon-block">
<i data-lucide="package" size="24" color="var(--rhpz-orange)"></i>
</div>
<div>
<div style="font-weight: 600; color: var(--text); font-size: 1.1rem;">Patch file</div>
<div style="font-size: 0.85rem; color: var(--text2);">Select if there is multiple patch files</div>
</div>
</div>
<div class="rom-patcher-container-input" style="margin-top: 10px;">
<select id="rom-patcher-select-patch" class="form-select" style="width: 100%; cursor: pointer;"></select>
</div>
</div>
</div>
</div>
<div class="patcher-status-box" id="patcher-status" x-show="showStatusBox" x-transition x-cloak style="margin-top: 20px;">
<div class="rom-patcher-row" style="color: var(--text2);">
<div style="color: var(--rhpz-orange); font-weight: bold;">Checksums:</div>
<ul style="margin-bottom: 0">
<li>CRC32: <span id="rom-patcher-span-crc32"></span></li>
<li>MD5: <span id="rom-patcher-span-md5"></span></li>
<li>SHA-1: <span id="rom-patcher-span-sha1"></span></li>
</ul>
</div>
<span id="rom-patcher-span-rom-info"></span>
<div class="rom-patcher-row margin-bottom" id="rom-patcher-row-patch-description">
<div style="color: var(--rhpz-orange); font-weight: bold;">Description:</div>
<div id="rom-patcher-patch-description"></div>
</div>
<div class="rom-patcher-row margin-bottom" id="rom-patcher-row-patch-requirements">
<div id="rom-patcher-patch-requirements-type" style="color: var(--rhpz-orange); font-weight: bold;">ROM requirements:</div>
<div id="rom-patcher-patch-requirements-value"></div>
</div>
</div>
<div style="margin-top: 25px; border-top: 1px solid var(--border); padding-top: 20px; text-align: right;">
<button type="button" class="btn primary" id="rom-patcher-button-apply" disabled>
<i data-lucide="wrench" size="16"></i> Apply patch
</button>
</div>
</div>
@endsection