#4: Nested albums are now supported in the admin panel
This commit is contained in:
parent
e93e4d2413
commit
7ea1dc5c83
@ -19,7 +19,7 @@ class Album extends Model
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name', 'description', 'url_alias', 'is_private', 'user_id', 'storage_id', 'default_view'
|
||||
'name', 'description', 'url_alias', 'is_private', 'user_id', 'storage_id', 'default_view', 'parent_album_id'
|
||||
];
|
||||
|
||||
/**
|
||||
@ -35,6 +35,11 @@ class Album extends Model
|
||||
return $this->belongsToMany(Permission::class, 'album_anonymous_permissions');
|
||||
}
|
||||
|
||||
public function children()
|
||||
{
|
||||
return $this->hasMany(Album::class, 'parent_album_id');
|
||||
}
|
||||
|
||||
public function doesGroupHavePermission(Group $group, Permission $permission)
|
||||
{
|
||||
return $this->groupPermissions()->where([
|
||||
@ -84,6 +89,31 @@ class Album extends Model
|
||||
return $this->belongsToMany(Permission::class, 'album_group_permissions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this album is a descendant of the given album.
|
||||
* @param Album $album
|
||||
*/
|
||||
public function isChildOf(Album $album)
|
||||
{
|
||||
$currentAlbum = $this;
|
||||
while (!is_null($currentAlbum))
|
||||
{
|
||||
if ($currentAlbum->parent_album_id == $album->id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
$currentAlbum = Album::where('id', $currentAlbum->parent_album_id)->first();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(Album::class, 'parent_album_id');
|
||||
}
|
||||
|
||||
public function photos()
|
||||
{
|
||||
return $this->hasMany(Photo::class);
|
||||
|
@ -9,10 +9,16 @@ use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class DbHelper
|
||||
{
|
||||
public static function getAlbumsForCurrentUser()
|
||||
public static function getAlbumsForCurrentUser($parentID = -1)
|
||||
{
|
||||
return self::getAlbumsForCurrentUser_NonPaged()
|
||||
->paginate(UserConfig::get('items_per_page'));
|
||||
$query = self::getAlbumsForCurrentUser_NonPaged();
|
||||
|
||||
if ($parentID == 0)
|
||||
{
|
||||
$query = $query->where('albums.parent_album_id', null);
|
||||
}
|
||||
|
||||
return $query->paginate(UserConfig::get('items_per_page'));
|
||||
}
|
||||
|
||||
public static function getAlbumsForCurrentUser_NonPaged()
|
||||
|
@ -14,6 +14,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests;
|
||||
use App\Permission;
|
||||
use App\Photo;
|
||||
use App\Services\AlbumService;
|
||||
use App\Services\PhotoService;
|
||||
use App\Storage;
|
||||
use App\Upload;
|
||||
@ -71,11 +72,13 @@ class AlbumController extends Controller
|
||||
return redirect(route('storage.create'));
|
||||
}
|
||||
|
||||
$albumService = new AlbumService();
|
||||
$defaultSource = Storage::where('is_default', true)->limit(1)->first();
|
||||
|
||||
return Theme::render('admin.create_album', [
|
||||
'album_sources' => $albumSources,
|
||||
'default_storage_id' => (!is_null($defaultSource) ? $defaultSource->id : 0)
|
||||
'default_storage_id' => (!is_null($defaultSource) ? $defaultSource->id : 0),
|
||||
'parent_albums' => $albumService->getFlattenedAlbumTree()
|
||||
]);
|
||||
}
|
||||
|
||||
@ -133,7 +136,12 @@ class AlbumController extends Controller
|
||||
$request->session()->flash('_old_input', $album->toArray());
|
||||
}
|
||||
|
||||
return Theme::render('admin.edit_album', ['album' => $album]);
|
||||
$albumService = new AlbumService();
|
||||
|
||||
return Theme::render('admin.edit_album', [
|
||||
'album' => $album,
|
||||
'parent_albums' => $albumService->getFlattenedAlbumTree()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,7 +153,8 @@ class AlbumController extends Controller
|
||||
{
|
||||
$this->authorizeAccessToAdminPanel('admin:manage-albums');
|
||||
|
||||
$albums = DbHelper::getAlbumsForCurrentUser();
|
||||
// Only get top-level albums
|
||||
$albums = DbHelper::getAlbumsForCurrentUser(0);
|
||||
|
||||
return Theme::render('admin.list_albums', [
|
||||
'albums' => $albums,
|
||||
@ -384,7 +393,12 @@ class AlbumController extends Controller
|
||||
$this->authorizeAccessToAdminPanel('admin:manage-albums');
|
||||
|
||||
$album = new Album();
|
||||
$album->fill($request->only(['name', 'description', 'storage_id']));
|
||||
$album->fill($request->only(['name', 'description', 'storage_id', 'parent_album_id']));
|
||||
|
||||
if (strlen($album->parent_album_id) == 0)
|
||||
{
|
||||
$album->parent_album_id = null;
|
||||
}
|
||||
|
||||
$album->default_view = UserConfig::get('default_album_view');
|
||||
$album->is_private = (strtolower($request->get('is_private')) == 'on');
|
||||
@ -408,9 +422,14 @@ class AlbumController extends Controller
|
||||
$this->authorizeAccessToAdminPanel('admin:manage-albums');
|
||||
|
||||
$album = $this->loadAlbum($id);
|
||||
$album->fill($request->only(['name', 'description']));
|
||||
$album->fill($request->only(['name', 'description', 'parent_album_id']));
|
||||
$album->is_private = (strtolower($request->get('is_private')) == 'on');
|
||||
|
||||
if (strlen($album->parent_album_id) == 0)
|
||||
{
|
||||
$album->parent_album_id = null;
|
||||
}
|
||||
|
||||
// These keys are optional and may or may not be in the request, depending on the page requesting it
|
||||
foreach (['storage_id', 'default_view'] as $key)
|
||||
{
|
||||
|
42
app/Services/AlbumService.php
Normal file
42
app/Services/AlbumService.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Album;
|
||||
use App\Helpers\DbHelper;
|
||||
|
||||
class AlbumService
|
||||
{
|
||||
/**
|
||||
* Get a list of all albums in a flattened tree-structure.
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFlattenedAlbumTree()
|
||||
{
|
||||
$allAlbums = DbHelper::getAlbumsForCurrentUser_NonPaged()->get();
|
||||
$result = [];
|
||||
|
||||
$this->buildAlbumTree($result, $allAlbums);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function buildAlbumTree(array &$result, $allAlbums, Album $parent = null, $level = 0)
|
||||
{
|
||||
$parentAlbums = [];
|
||||
foreach ($allAlbums as $album)
|
||||
{
|
||||
if ((is_null($parent) && is_null($album->parent_album_id)) || (!is_null($parent) && $album->parent_album_id == $parent->id))
|
||||
{
|
||||
$parentAlbums[] = $album;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($parentAlbums as $album)
|
||||
{
|
||||
$album->display_name = trim(sprintf('%s %s', str_repeat('-', $level), $album->name));
|
||||
$result[$album->id] = $album;
|
||||
$this->buildAlbumTree($result, $allAlbums, $album, $level + 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -27,13 +27,13 @@ class CreateVisitorHitsTable extends Migration
|
||||
|
||||
$table->foreign('album_id')
|
||||
->references('id')->on('albums')
|
||||
->onDelete('no action');
|
||||
->onDelete('cascade');
|
||||
$table->foreign('photo_id')
|
||||
->references('id')->on('photos')
|
||||
->onDelete('no action');
|
||||
->onDelete('cascade');
|
||||
$table->foreign('user_id')
|
||||
->references('id')->on('users')
|
||||
->onDelete('no action');
|
||||
->onDelete('cascade');
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
|
@ -14,12 +14,12 @@ class AttachHitColumns extends Migration
|
||||
public function up()
|
||||
{
|
||||
Schema::table('albums', function (Blueprint $table) {
|
||||
$table->bigInteger('hits');
|
||||
$table->bigInteger('hits')->default(0);
|
||||
});
|
||||
|
||||
Schema::table('photos', function (Blueprint $table) {
|
||||
$table->bigInteger('hits');
|
||||
$table->bigInteger('hits_download');
|
||||
$table->bigInteger('hits')->default(0);
|
||||
$table->bigInteger('hits_download')->default(0);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddParentAlbumColumn extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('albums', function (Blueprint $table)
|
||||
{
|
||||
$table->unsignedInteger('parent_album_id')->nullable();
|
||||
|
||||
$table->foreign('parent_album_id')
|
||||
->references('id')->on('albums')
|
||||
->onDelete('set null');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('albums', function (Blueprint $table)
|
||||
{
|
||||
$table->dropForeign('albums_parent_album_id_foreign');
|
||||
$table->dropColumn('parent_album_id');
|
||||
});
|
||||
}
|
||||
}
|
5
resources/assets/css/admin.css
vendored
5
resources/assets/css/admin.css
vendored
@ -2,6 +2,11 @@
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.album-expand-handle {
|
||||
cursor: pointer;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.card-header.card-danger {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
|
4
resources/assets/css/global.css
vendored
4
resources/assets/css/global.css
vendored
@ -19,6 +19,10 @@ textarea {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
border: solid 1px rgb(221, 221, 221);
|
||||
border-top: 0;
|
||||
|
@ -19,6 +19,8 @@ return [
|
||||
'email_label' => 'E-mail address:',
|
||||
'login_action' => 'Login',
|
||||
'name_label' => 'Name:',
|
||||
'parent_album_label' => 'Parent album:',
|
||||
'parent_album_placeholder' => 'None (top-level album)',
|
||||
'password_label' => 'Password:',
|
||||
'password_confirm_label' => 'Confirm password:',
|
||||
'private_album_label' => 'Private album (only visible to me)',
|
||||
@ -27,6 +29,7 @@ return [
|
||||
'remember_me_label' => 'Remember me',
|
||||
'remove_action' => 'Remove',
|
||||
'select' => 'Select',
|
||||
'select_current_text' => '(current)',
|
||||
'settings_hotlink_protection' => 'Prevent hot-linking to images',
|
||||
'settings_hotlink_protection_help' => 'With this option enabled, direct linking to images is not allowed. Photos can only be viewed through Blue Twilight.',
|
||||
'settings_restrict_originals_download' => 'Restrict access to original images',
|
||||
|
@ -35,9 +35,19 @@
|
||||
<textarea class="form-control" id="album-description" name="description" rows="5">{{ old('description') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-control-label" for="parent-album">@lang('forms.parent_album_label')</label>
|
||||
<select class="form-control" name="parent_album_id" id="parent-album">
|
||||
<option value="">@lang('forms.parent_album_placeholder')</option>
|
||||
@foreach ($parent_albums as $key => $value)
|
||||
<option value="{{ $key }}"{{ $key == old('parent_album_id') ? ' selected="selected"' : '' }}>{{ $value->display_name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-control-label" for="album-source">@lang('forms.album_source_label')</label>
|
||||
<select class="form-control" name="storage_id">
|
||||
<select class="form-control" name="storage_id" id="album-source">
|
||||
@foreach ($album_sources as $key => $value)
|
||||
<option value="{{ $key }}"{{ $key == old('storage_id') ? ' selected="selected"' : '' }}>{{ $value }}</option>
|
||||
@endforeach
|
||||
|
@ -44,6 +44,16 @@
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-control-label" for="parent-album">@lang('forms.parent_album_label')</label>
|
||||
<select class="form-control" name="parent_album_id" id="parent-album">
|
||||
<option value="">@lang('forms.parent_album_placeholder')</option>
|
||||
@foreach ($parent_albums as $key => $value)
|
||||
<option value="{{ $key }}"{{ $key == $album->id || $value->isChildOf($album) ? ' disabled="disabled"' : '' }}{{ $key == old('parent_album_id') ? ' selected="selected"' : '' }}>{{ $value->display_name }}{{ $key == old('parent_album_id') ? ' ' . trans('forms.select_current_text') : '' }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="text-right">
|
||||
<a href="{{ route('albums.show', ['id' => $album->id]) }}" class="btn btn-link">@lang('forms.cancel_action')</a>
|
||||
<button type="submit" class="btn btn-success"><i class="fa fa-fw fa-check"></i> @lang('forms.save_action')</button>
|
||||
|
@ -28,30 +28,7 @@
|
||||
<table class="table table-hover table-striped">
|
||||
<tbody>
|
||||
@foreach ($albums as $album)
|
||||
<tr>
|
||||
<td>
|
||||
<span style="font-size: 1.3em;">
|
||||
@can('edit', $album)
|
||||
<a href="{{ route('albums.show', ['id' => $album->id]) }}">{{ $album->name }}</a>
|
||||
@endcan
|
||||
@cannot('edit', $album)
|
||||
{{ $album->name }} <i class="fa fa-fw fa-lock"></i>
|
||||
@endcannot
|
||||
</span><br/>
|
||||
<p>{{ $album->description }}</p>
|
||||
<p style="margin-bottom: 0;"><b>{{ $album->photos_count }}</b> {{ trans_choice('admin.stats_widget.photos', $album->photos_count) }}</p>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
@can('edit', $album)
|
||||
<a href="{{ route('albums.edit', ['id' => $album->id]) }}" class="btn btn-secondary">@lang('forms.edit_action')</a>
|
||||
@endcan
|
||||
@can('delete', $album)
|
||||
<a href="{{ route('albums.delete', ['id' => $album->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
|
||||
@endcan
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@include (Theme::viewName('partials.single_album_admin'), ['is_child' => false])
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@ -67,4 +44,39 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('.album-expand-handle').click(function() {
|
||||
var parent = $(this).closest('tr');
|
||||
|
||||
var handle = $('.album-expand-handle', parent);
|
||||
var albumID = parent.data('album-id');
|
||||
$('tr[data-parent-album-id=' + albumID + ']').toggle();
|
||||
|
||||
if (handle.hasClass('fa-plus'))
|
||||
{
|
||||
handle.addClass('fa-minus');
|
||||
handle.removeClass('fa-plus');
|
||||
}
|
||||
else
|
||||
{
|
||||
// Toggle all children
|
||||
$('tr[data-parent-album-id=' + albumID + ']').each(function(index, element)
|
||||
{
|
||||
var childHandle = $('.album-expand-handle', element);
|
||||
if (childHandle.hasClass('fa-minus'))
|
||||
{
|
||||
childHandle.click();
|
||||
}
|
||||
});
|
||||
|
||||
handle.addClass('fa-plus');
|
||||
handle.removeClass('fa-minus');
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@endpush
|
@ -0,0 +1,32 @@
|
||||
<tr data-album-id="{{ $album->id }}" class="{{ $is_child ? 'hidden' : '' }}" @if (!is_null($album->parent_album_id)) data-parent-album-id="{{ $album->parent_album_id }}" @endif>
|
||||
<td style="width: 20px;">
|
||||
@if ($album->children()->count() > 0)
|
||||
<i class="album-expand-handle fa fa-fw fa-plus mt-2"></i>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<span style="font-size: 1.3em;">
|
||||
@can('edit', $album)
|
||||
<a href="{{ route('albums.show', ['id' => $album->id]) }}">{{ $album->name }}</a>
|
||||
@endcan
|
||||
@cannot('edit', $album)
|
||||
{{ $album->name }} <i class="fa fa-fw fa-lock"></i>
|
||||
@endcannot
|
||||
</span><br/>
|
||||
<p>{{ $album->description }}</p>
|
||||
<p style="margin-bottom: 0;"><b>{{ $album->photos_count }}</b> {{ trans_choice('admin.stats_widget.photos', $album->photos_count) }}</p>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="btn-group">
|
||||
@can('edit', $album)
|
||||
<a href="{{ route('albums.edit', ['id' => $album->id]) }}" class="btn btn-secondary">@lang('forms.edit_action')</a>
|
||||
@endcan
|
||||
@can('delete', $album)
|
||||
<a href="{{ route('albums.delete', ['id' => $album->id]) }}" class="btn btn-danger">@lang('forms.delete_action')</a>
|
||||
@endcan
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@foreach ($album->children as $album)
|
||||
@include (Theme::viewName('partials.single_album_admin'), ['is_child' => true])
|
||||
@endforeach
|
Loading…
Reference in New Issue
Block a user