From b0adb74d628875a0f1a88deb71362290d568dce6 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 24 Jul 2022 12:23:25 +0100 Subject: [PATCH] Improved shelf book management interface - Added ability to search books list (Local simple text match). - Added handles, hover-states and cursor states for better user interaction and clearer use of drag & drop. - Improved styles for dark mode. - Converted shelf sort component to newer component format. - Modernized shelf controller code a little. Related to #3266 --- app/Http/Controllers/BookshelfController.php | 53 +++++++++----------- resources/js/components/shelf-sort.js | 38 +++++++++++--- resources/lang/en/entities.php | 2 +- resources/sass/_forms.scss | 3 +- resources/sass/styles.scss | 42 +++++++++++++--- resources/views/shelves/parts/form.blade.php | 12 +++-- 6 files changed, 102 insertions(+), 48 deletions(-) diff --git a/app/Http/Controllers/BookshelfController.php b/app/Http/Controllers/BookshelfController.php index feb581c78..ccbeb6484 100644 --- a/app/Http/Controllers/BookshelfController.php +++ b/app/Http/Controllers/BookshelfController.php @@ -10,22 +10,19 @@ use BookStack\Entities\Tools\PermissionsUpdater; use BookStack\Entities\Tools\ShelfContext; use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\NotFoundException; -use BookStack\Uploads\ImageRepo; use Exception; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; class BookshelfController extends Controller { - protected $bookshelfRepo; - protected $entityContextManager; - protected $imageRepo; + protected BookshelfRepo $shelfRepo; + protected ShelfContext $shelfContext; - public function __construct(BookshelfRepo $bookshelfRepo, ShelfContext $entityContextManager, ImageRepo $imageRepo) + public function __construct(BookshelfRepo $shelfRepo, ShelfContext $shelfContext) { - $this->bookshelfRepo = $bookshelfRepo; - $this->entityContextManager = $entityContextManager; - $this->imageRepo = $imageRepo; + $this->shelfRepo = $shelfRepo; + $this->shelfContext = $shelfContext; } /** @@ -42,12 +39,12 @@ class BookshelfController extends Controller 'updated_at' => trans('common.sort_updated_at'), ]; - $shelves = $this->bookshelfRepo->getAllPaginated(18, $sort, $order); - $recents = $this->isSignedIn() ? $this->bookshelfRepo->getRecentlyViewed(4) : false; - $popular = $this->bookshelfRepo->getPopular(4); - $new = $this->bookshelfRepo->getRecentlyCreated(4); + $shelves = $this->shelfRepo->getAllPaginated(18, $sort, $order); + $recents = $this->isSignedIn() ? $this->shelfRepo->getRecentlyViewed(4) : false; + $popular = $this->shelfRepo->getPopular(4); + $new = $this->shelfRepo->getRecentlyCreated(4); - $this->entityContextManager->clearShelfContext(); + $this->shelfContext->clearShelfContext(); $this->setPageTitle(trans('entities.shelves')); return view('shelves.index', [ @@ -68,7 +65,7 @@ class BookshelfController extends Controller public function create() { $this->checkPermission('bookshelf-create-all'); - $books = Book::visible()->get(); + $books = Book::visible()->orderBy('name')->get(['name', 'id', 'slug']); $this->setPageTitle(trans('entities.shelves_create')); return view('shelves.create', ['books' => $books]); @@ -91,7 +88,7 @@ class BookshelfController extends Controller ]); $bookIds = explode(',', $request->get('books', '')); - $shelf = $this->bookshelfRepo->create($validated, $bookIds); + $shelf = $this->shelfRepo->create($validated, $bookIds); return redirect($shelf->getUrl()); } @@ -103,7 +100,7 @@ class BookshelfController extends Controller */ public function show(ActivityQueries $activities, string $slug) { - $shelf = $this->bookshelfRepo->getBySlug($slug); + $shelf = $this->shelfRepo->getBySlug($slug); $this->checkOwnablePermission('bookshelf-view', $shelf); $sort = setting()->getForCurrentUser('shelf_books_sort', 'default'); @@ -115,7 +112,7 @@ class BookshelfController extends Controller ->all(); View::incrementFor($shelf); - $this->entityContextManager->setShelfContext($shelf->id); + $this->shelfContext->setShelfContext($shelf->id); $view = setting()->getForCurrentUser('bookshelf_view_type'); $this->setPageTitle($shelf->getShortName()); @@ -135,11 +132,11 @@ class BookshelfController extends Controller */ public function edit(string $slug) { - $shelf = $this->bookshelfRepo->getBySlug($slug); + $shelf = $this->shelfRepo->getBySlug($slug); $this->checkOwnablePermission('bookshelf-update', $shelf); $shelfBookIds = $shelf->books()->get(['id'])->pluck('id'); - $books = Book::visible()->whereNotIn('id', $shelfBookIds)->get(); + $books = Book::visible()->whereNotIn('id', $shelfBookIds)->orderBy('name')->get(['name', 'id', 'slug']); $this->setPageTitle(trans('entities.shelves_edit_named', ['name' => $shelf->getShortName()])); @@ -158,7 +155,7 @@ class BookshelfController extends Controller */ public function update(Request $request, string $slug) { - $shelf = $this->bookshelfRepo->getBySlug($slug); + $shelf = $this->shelfRepo->getBySlug($slug); $this->checkOwnablePermission('bookshelf-update', $shelf); $validated = $this->validate($request, [ 'name' => ['required', 'string', 'max:255'], @@ -174,7 +171,7 @@ class BookshelfController extends Controller } $bookIds = explode(',', $request->get('books', '')); - $shelf = $this->bookshelfRepo->update($shelf, $validated, $bookIds); + $shelf = $this->shelfRepo->update($shelf, $validated, $bookIds); return redirect($shelf->getUrl()); } @@ -184,7 +181,7 @@ class BookshelfController extends Controller */ public function showDelete(string $slug) { - $shelf = $this->bookshelfRepo->getBySlug($slug); + $shelf = $this->shelfRepo->getBySlug($slug); $this->checkOwnablePermission('bookshelf-delete', $shelf); $this->setPageTitle(trans('entities.shelves_delete_named', ['name' => $shelf->getShortName()])); @@ -199,10 +196,10 @@ class BookshelfController extends Controller */ public function destroy(string $slug) { - $shelf = $this->bookshelfRepo->getBySlug($slug); + $shelf = $this->shelfRepo->getBySlug($slug); $this->checkOwnablePermission('bookshelf-delete', $shelf); - $this->bookshelfRepo->destroy($shelf); + $this->shelfRepo->destroy($shelf); return redirect('/shelves'); } @@ -212,7 +209,7 @@ class BookshelfController extends Controller */ public function showPermissions(string $slug) { - $shelf = $this->bookshelfRepo->getBySlug($slug); + $shelf = $this->shelfRepo->getBySlug($slug); $this->checkOwnablePermission('restrictions-manage', $shelf); return view('shelves.permissions', [ @@ -225,7 +222,7 @@ class BookshelfController extends Controller */ public function permissions(Request $request, PermissionsUpdater $permissionsUpdater, string $slug) { - $shelf = $this->bookshelfRepo->getBySlug($slug); + $shelf = $this->shelfRepo->getBySlug($slug); $this->checkOwnablePermission('restrictions-manage', $shelf); $permissionsUpdater->updateFromPermissionsForm($shelf, $request); @@ -240,10 +237,10 @@ class BookshelfController extends Controller */ public function copyPermissions(string $slug) { - $shelf = $this->bookshelfRepo->getBySlug($slug); + $shelf = $this->shelfRepo->getBySlug($slug); $this->checkOwnablePermission('restrictions-manage', $shelf); - $updateCount = $this->bookshelfRepo->copyDownPermissions($shelf); + $updateCount = $this->shelfRepo->copyDownPermissions($shelf); $this->showSuccessNotification(trans('entities.shelves_copy_permission_success', ['count' => $updateCount])); return redirect($shelf->getUrl()); diff --git a/resources/js/components/shelf-sort.js b/resources/js/components/shelf-sort.js index 38e8ae8d3..07526716a 100644 --- a/resources/js/components/shelf-sort.js +++ b/resources/js/components/shelf-sort.js @@ -2,10 +2,12 @@ import Sortable from "sortablejs"; class ShelfSort { - constructor(elem) { - this.elem = elem; - this.input = document.getElementById('books-input'); - this.shelfBooksList = elem.querySelector('[shelf-sort-assigned-books]'); + setup() { + this.elem = this.$el; + this.input = this.$refs.input; + this.shelfBookList = this.$refs.shelfBookList; + this.allBookList = this.$refs.allBookList; + this.bookSearchInput = this.$refs.bookSearch; this.initSortable(); this.setupListeners(); @@ -25,12 +27,36 @@ class ShelfSort { setupListeners() { this.elem.addEventListener('click', event => { - const sortItem = event.target.closest('.scroll-box-item:not(.instruction)'); + const sortItem = event.target.closest('.scroll-box-item'); if (sortItem) { event.preventDefault(); this.sortItemClick(sortItem); } }); + + this.bookSearchInput.addEventListener('input', event => { + this.filterBooksByName(this.bookSearchInput.value); + }); + } + + /** + * @param {String} filterVal + */ + filterBooksByName(filterVal) { + + // Set height on first search, if not already set, to prevent the distraction + // of the list height jumping around + if (!this.allBookList.style.height) { + this.allBookList.style.height = this.allBookList.getBoundingClientRect().height + 'px'; + } + + const books = this.allBookList.children; + const lowerFilter = filterVal.trim().toLowerCase(); + + for (const bookEl of books) { + const show = !filterVal || bookEl.textContent.toLowerCase().includes(lowerFilter); + bookEl.style.display = show ? null : 'none'; + } } /** @@ -47,7 +73,7 @@ class ShelfSort { } onChange() { - const shelfBookElems = Array.from(this.shelfBooksList.querySelectorAll('[data-id]')); + const shelfBookElems = Array.from(this.shelfBookList.querySelectorAll('[data-id]')); this.input.value = shelfBookElems.map(elem => elem.getAttribute('data-id')).join(','); } diff --git a/resources/lang/en/entities.php b/resources/lang/en/entities.php index aa353bdac..83e3c12d9 100644 --- a/resources/lang/en/entities.php +++ b/resources/lang/en/entities.php @@ -88,7 +88,7 @@ return [ 'shelves_save' => 'Save Shelf', 'shelves_books' => 'Books on this shelf', 'shelves_add_books' => 'Add books to this shelf', - 'shelves_drag_books' => 'Drag books here to add them to this shelf', + 'shelves_drag_books' => 'Drag books below to add them to this shelf', 'shelves_empty_contents' => 'This shelf has no books assigned to it', 'shelves_edit_and_assign' => 'Edit shelf to assign books', 'shelves_edit_named' => 'Edit Bookshelf :name', diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss index 73799f0a0..e39f5414f 100644 --- a/resources/sass/_forms.scss +++ b/resources/sass/_forms.scss @@ -317,7 +317,8 @@ input[type=color] { .form-group[collapsible] { padding: 0 $-m; - border: 1px solid #DDD; + border: 1px solid; + @include lightDark(border-color, #DDD, #000); border-radius: 4px; .collapse-title { margin-inline-start: -$-m; diff --git a/resources/sass/styles.scss b/resources/sass/styles.scss index ee99d7668..65eee866d 100644 --- a/resources/sass/styles.scss +++ b/resources/sass/styles.scss @@ -241,28 +241,54 @@ $btt-size: 40px; .scroll-box { max-height: 250px; overflow-y: scroll; - border: 1px solid #DDD; + border: 1px solid; + @include lightDark(border-color, #DDD, #000); border-radius: 3px; + min-height: 20px; + @include lightDark(background-color, #EEE, #000); .scroll-box-item { padding: $-xs $-m; - border-bottom: 1px solid #DDD; - border-top: 1px solid #DDD; + border-bottom: 1px solid; + border-top: 1px solid; + @include lightDark(border-color, #DDD, #000); margin-top: -1px; + @include lightDark(background-color, #FFF, #222); + display: flex; + gap: $-xs; &:last-child { border-bottom: 0; } + &:hover { + cursor: pointer; + @include lightDark(background-color, #f8f8f8, #333); + } + .handle { + color: #AAA; + cursor: grab; + } } } -.scroll-box[data-instruction]:before { - content: attr(data-instruction); +input.scroll-box-search, .scroll-box-header-item { + font-size: 0.8rem; padding: $-xs $-m; - border-bottom: 1px solid #DDD; - display: block; - font-size: 0.75rem; + border: 1px solid; + @include lightDark(border-color, #DDD, #000); + @include lightDark(background-color, #FFF, #222); + margin-bottom: -1px; + border-radius: 3px 3px 0 0; + width: 100%; + max-width: 100%; + height: auto; + line-height: 1.4; color: #666; } +.scroll-box-search + .scroll-box, +.scroll-box-header-item + .scroll-box { + border-radius: 0 0 3px 3px; +} + .fullscreen { border:0; position:fixed; diff --git a/resources/views/shelves/parts/form.blade.php b/resources/views/shelves/parts/form.blade.php index f29c28c81..1dcc4192f 100644 --- a/resources/views/shelves/parts/form.blade.php +++ b/resources/views/shelves/parts/form.blade.php @@ -10,15 +10,17 @@ @include('form.textarea', ['name' => 'description']) -
+
- -
+
{{ trans('entities.shelves_drag_books') }}
+
@if (count($shelf->visibleBooks ?? []) > 0) @foreach ($shelf->visibleBooks as $book) @endforeach @@ -27,9 +29,11 @@
-
+ +
@foreach ($books as $book) @endforeach