From 02dc3154e34775f44b4f38b525224a2354aa698c Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 25 Jul 2020 00:20:58 +0100 Subject: [PATCH] Converted image-manager to be component/HTML based Instead of vue based. --- .../Images/DrawioImageController.php | 6 +- .../Images/GalleryImageController.php | 13 +- .../Controllers/Images/ImageController.php | 50 +++-- app/Uploads/ImageRepo.php | 18 +- resources/js/components/ajax-form.js | 50 +++-- resources/js/components/dropzone.js | 1 - resources/js/components/image-manager.js | 201 +++++++++++++++++ resources/js/services/dom.js | 11 + resources/js/vues/image-manager.js | 204 ------------------ resources/js/vues/vues.js | 3 - resources/lang/en/common.php | 1 + resources/lang/en/components.php | 2 +- resources/sass/_colors.scss | 5 + resources/sass/_components.scss | 27 ++- resources/sass/_layout.scss | 5 + resources/sass/styles.scss | 5 + .../components/image-manager-form.blade.php | 60 ++++++ .../components/image-manager-list.blade.php | 23 ++ .../views/components/image-manager.blade.php | 123 ++++------- resources/views/pages/edit.blade.php | 3 +- resources/views/settings/index.blade.php | 1 - routes/web.php | 24 +-- tests/Uploads/ImageTest.php | 39 +--- 23 files changed, 483 insertions(+), 392 deletions(-) create mode 100644 resources/js/components/image-manager.js delete mode 100644 resources/js/vues/image-manager.js create mode 100644 resources/views/components/image-manager-form.blade.php create mode 100644 resources/views/components/image-manager-list.blade.php diff --git a/app/Http/Controllers/Images/DrawioImageController.php b/app/Http/Controllers/Images/DrawioImageController.php index 106dfd630..29b1e9027 100644 --- a/app/Http/Controllers/Images/DrawioImageController.php +++ b/app/Http/Controllers/Images/DrawioImageController.php @@ -30,7 +30,10 @@ class DrawioImageController extends Controller $parentTypeFilter = $request->get('filter_type', null); $imgData = $this->imageRepo->getEntityFiltered('drawio', $parentTypeFilter, $page, 24, $uploadedToFilter, $searchTerm); - return response()->json($imgData); + return view('components.image-manager-list', [ + 'images' => $imgData['images'], + 'hasMore' => $imgData['has_more'], + ]); } /** @@ -72,6 +75,7 @@ class DrawioImageController extends Controller if ($imageData === null) { return $this->jsonError("Image data could not be found"); } + return response()->json([ 'content' => base64_encode($imageData) ]); diff --git a/app/Http/Controllers/Images/GalleryImageController.php b/app/Http/Controllers/Images/GalleryImageController.php index e506215ca..61907c003 100644 --- a/app/Http/Controllers/Images/GalleryImageController.php +++ b/app/Http/Controllers/Images/GalleryImageController.php @@ -6,6 +6,7 @@ use BookStack\Exceptions\ImageUploadException; use BookStack\Uploads\ImageRepo; use Illuminate\Http\Request; use BookStack\Http\Controllers\Controller; +use Illuminate\Validation\ValidationException; class GalleryImageController extends Controller { @@ -13,7 +14,6 @@ class GalleryImageController extends Controller /** * GalleryImageController constructor. - * @param ImageRepo $imageRepo */ public function __construct(ImageRepo $imageRepo) { @@ -24,8 +24,6 @@ class GalleryImageController extends Controller /** * Get a list of gallery images, in a list. * Can be paged and filtered by entity. - * @param Request $request - * @return \Illuminate\Http\JsonResponse */ public function list(Request $request) { @@ -35,14 +33,15 @@ class GalleryImageController extends Controller $parentTypeFilter = $request->get('filter_type', null); $imgData = $this->imageRepo->getEntityFiltered('gallery', $parentTypeFilter, $page, 24, $uploadedToFilter, $searchTerm); - return response()->json($imgData); + return view('components.image-manager-list', [ + 'images' => $imgData['images'], + 'hasMore' => $imgData['has_more'], + ]); } /** * Store a new gallery image in the system. - * @param Request $request - * @return Illuminate\Http\JsonResponse - * @throws \Exception + * @throws ValidationException */ public function create(Request $request) { diff --git a/app/Http/Controllers/Images/ImageController.php b/app/Http/Controllers/Images/ImageController.php index 9c67704dd..7d06facff 100644 --- a/app/Http/Controllers/Images/ImageController.php +++ b/app/Http/Controllers/Images/ImageController.php @@ -6,8 +6,11 @@ use BookStack\Http\Controllers\Controller; use BookStack\Repos\PageRepo; use BookStack\Uploads\Image; use BookStack\Uploads\ImageRepo; +use Exception; use Illuminate\Filesystem\Filesystem as File; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Validation\ValidationException; class ImageController extends Controller { @@ -17,9 +20,6 @@ class ImageController extends Controller /** * ImageController constructor. - * @param Image $image - * @param File $file - * @param ImageRepo $imageRepo */ public function __construct(Image $image, File $file, ImageRepo $imageRepo) { @@ -31,8 +31,6 @@ class ImageController extends Controller /** * Provide an image file from storage. - * @param string $path - * @return mixed */ public function showImage(string $path) { @@ -47,13 +45,10 @@ class ImageController extends Controller /** * Update image details - * @param Request $request - * @param integer $id - * @return \Illuminate\Http\JsonResponse * @throws ImageUploadException - * @throws \Exception + * @throws ValidationException */ - public function update(Request $request, $id) + public function update(Request $request, string $id) { $this->validate($request, [ 'name' => 'required|min:2|string' @@ -64,47 +59,50 @@ class ImageController extends Controller $this->checkOwnablePermission('image-update', $image); $image = $this->imageRepo->updateImageDetails($image, $request->all()); - return response()->json($image); + + $this->imageRepo->loadThumbs($image); + return view('components.image-manager-form', [ + 'image' => $image, + 'dependantPages' => null, + ]); } /** - * Show the usage of an image on pages. + * Get the form for editing the given image. + * @throws Exception */ - public function usage(int $id) + public function edit(Request $request, string $id) { $image = $this->imageRepo->getById($id); $this->checkImagePermission($image); - $pages = Page::visible()->where('html', 'like', '%' . $image->url . '%')->get(['id', 'name', 'slug', 'book_id']); - foreach ($pages as $page) { - $page->url = $page->getUrl(); - $page->html = ''; - $page->text = ''; + if ($request->has('delete')) { + $dependantPages = $this->imageRepo->getPagesUsingImage($image); } - $result = count($pages) > 0 ? $pages : false; - return response()->json($result); + $this->imageRepo->loadThumbs($image); + return view('components.image-manager-form', [ + 'image' => $image, + 'dependantPages' => $dependantPages ?? null, + ]); } /** * Deletes an image and all thumbnail/image files - * @param int $id - * @return \Illuminate\Http\JsonResponse - * @throws \Exception + * @throws Exception */ - public function destroy($id) + public function destroy(string $id) { $image = $this->imageRepo->getById($id); $this->checkOwnablePermission('image-delete', $image); $this->checkImagePermission($image); $this->imageRepo->destroyImage($image); - return response()->json(trans('components.images_deleted')); + return response(''); } /** * Check related page permission and ensure type is drawio or gallery. - * @param Image $image */ protected function checkImagePermission(Image $image) { diff --git a/app/Uploads/ImageRepo.php b/app/Uploads/ImageRepo.php index b7a21809f..a08555085 100644 --- a/app/Uploads/ImageRepo.php +++ b/app/Uploads/ImageRepo.php @@ -185,7 +185,7 @@ class ImageRepo * Load thumbnails onto an image object. * @throws Exception */ - protected function loadThumbs(Image $image) + public function loadThumbs(Image $image) { $image->thumbs = [ 'gallery' => $this->getThumbnail($image, 150, 150, false), @@ -219,4 +219,20 @@ class ImageRepo return null; } } + + /** + * Get the user visible pages using the given image. + */ + public function getPagesUsingImage(Image $image): array + { + $pages = Page::visible() + ->where('html', 'like', '%' . $image->url . '%') + ->get(['id', 'name', 'slug', 'book_id']); + + foreach ($pages as $page) { + $page->url = $page->getUrl(); + } + + return $pages->all(); + } } diff --git a/resources/js/components/ajax-form.js b/resources/js/components/ajax-form.js index 92b19dcff..91029d042 100644 --- a/resources/js/components/ajax-form.js +++ b/resources/js/components/ajax-form.js @@ -5,52 +5,76 @@ import {onEnterPress, onSelect} from "../services/dom"; * Will handle button clicks or input enter press events and submit * the data over ajax. Will always expect a partial HTML view to be returned. * Fires an 'ajax-form-success' event when submitted successfully. + * + * Will handle a real form if that's what the component is added to + * otherwise will act as a fake form element. + * * @extends {Component} */ class AjaxForm { setup() { this.container = this.$el; + this.responseContainer = this.container; this.url = this.$opts.url; this.method = this.$opts.method || 'post'; this.successMessage = this.$opts.successMessage; this.submitButtons = this.$manyRefs.submit || []; + if (this.$opts.responseContainer) { + this.responseContainer = this.container.closest(this.$opts.responseContainer); + } + this.setupListeners(); } setupListeners() { + + if (this.container.tagName === 'FORM') { + this.container.addEventListener('submit', this.submitRealForm.bind(this)); + return; + } + onEnterPress(this.container, event => { - this.submit(); + this.submitFakeForm(); event.preventDefault(); }); - this.submitButtons.forEach(button => onSelect(button, this.submit.bind(this))); + this.submitButtons.forEach(button => onSelect(button, this.submitFakeForm.bind(this))); } - async submit() { + submitFakeForm() { const fd = new FormData(); const inputs = this.container.querySelectorAll(`[name]`); - console.log(inputs); for (const input of inputs) { fd.append(input.getAttribute('name'), input.value); } + this.submit(fd); + } + + submitRealForm(event) { + event.preventDefault(); + const fd = new FormData(this.container); + this.submit(fd); + } + + async submit(formData) { + this.responseContainer.style.opacity = '0.7'; + this.responseContainer.style.pointerEvents = 'none'; - this.container.style.opacity = '0.7'; - this.container.style.pointerEvents = 'none'; try { - const resp = await window.$http[this.method.toLowerCase()](this.url, fd); - this.container.innerHTML = resp.data; - this.$emit('success', {formData: fd}); + const resp = await window.$http[this.method.toLowerCase()](this.url, formData); + this.$emit('success', {formData}); + this.responseContainer.innerHTML = resp.data; if (this.successMessage) { window.$events.emit('success', this.successMessage); } } catch (err) { - this.container.innerHTML = err.data; + this.responseContainer.innerHTML = err.data; } - window.components.init(this.container); - this.container.style.opacity = null; - this.container.style.pointerEvents = null; + window.components.init(this.responseContainer); + this.responseContainer.style.opacity = null; + this.responseContainer.style.pointerEvents = null; } } diff --git a/resources/js/components/dropzone.js b/resources/js/components/dropzone.js index 5a7e29de5..e7273df62 100644 --- a/resources/js/components/dropzone.js +++ b/resources/js/components/dropzone.js @@ -43,7 +43,6 @@ class Dropzone { } onSuccess(file, data) { - this.container.dispatchEvent(new Event('dropzone')) this.$emit('success', {file, data}); if (this.successMessage) { diff --git a/resources/js/components/image-manager.js b/resources/js/components/image-manager.js new file mode 100644 index 000000000..71bc55f2e --- /dev/null +++ b/resources/js/components/image-manager.js @@ -0,0 +1,201 @@ +import {onChildEvent, onSelect, removeLoading, showLoading} from "../services/dom"; + +/** + * ImageManager + * @extends {Component} + */ +class ImageManager { + + setup() { + + // Options + this.uploadedTo = this.$opts.uploadedTo; + + // Element References + this.container = this.$el; + this.popupEl = this.$refs.popup; + this.searchForm = this.$refs.searchForm; + this.searchInput = this.$refs.searchInput; + this.cancelSearch = this.$refs.cancelSearch; + this.listContainer = this.$refs.listContainer; + this.filterTabs = this.$manyRefs.filterTabs; + this.selectButton = this.$refs.selectButton; + this.formContainer = this.$refs.formContainer; + this.dropzoneContainer = this.$refs.dropzoneContainer; + + // Instance data + this.type = 'gallery'; + this.lastSelected = {}; + this.lastSelectedTime = 0; + this.resetState = () => { + this.callback = null; + this.hasData = false; + this.page = 1; + this.filter = 'all'; + }; + this.resetState(); + + this.setupListeners(); + + window.ImageManager = this; + } + + setupListeners() { + onSelect(this.filterTabs, e => { + this.resetAll(); + this.filter = e.target.dataset.filter; + this.setActiveFilterTab(this.filter); + this.loadGallery(); + }); + + this.searchForm.addEventListener('submit', event => { + this.resetListView(); + this.loadGallery(); + event.preventDefault(); + }); + + onSelect(this.cancelSearch, event => { + this.resetListView(); + this.resetSearchView(); + this.loadGallery(); + this.cancelSearch.classList.remove('active'); + }); + + this.searchInput.addEventListener('input', event => { + this.cancelSearch.classList.toggle('active', this.searchInput.value.trim()); + }); + + onChildEvent(this.listContainer, '.load-more', 'click', async event => { + showLoading(event.target); + this.page++; + await this.loadGallery(); + event.target.remove(); + }); + + this.listContainer.addEventListener('event-emit-select-image', this.onImageSelectEvent.bind(this)); + + onSelect(this.selectButton, () => { + if (this.callback) { + this.callback(this.lastSelected); + } + this.hide(); + }); + + onChildEvent(this.formContainer, '#image-manager-delete', 'click', event => { + if (this.lastSelected) { + this.loadImageEditForm(this.lastSelected.id, true); + } + }); + + this.formContainer.addEventListener('ajax-form-success', this.refreshGallery.bind(this)); + this.container.addEventListener('dropzone-success', this.refreshGallery.bind(this)); + } + + show(callback, type = 'gallery') { + this.resetAll(); + + this.callback = callback; + this.type = type; + this.popupEl.components.popup.show(); + this.dropzoneContainer.classList.toggle('hidden', type !== 'gallery'); + + if (!this.hasData) { + this.loadGallery(); + this.hasData = true; + } + } + + hide() { + this.popupEl.components.popup.hide(); + } + + async loadGallery() { + const params = { + page: this.page, + search: this.searchInput.value || null, + uploaded_to: this.uploadedTo, + filter_type: this.filter === 'all' ? null : this.filter, + }; + + const {data: html} = await window.$http.get(`images/${this.type}`, params); + this.addReturnedHtmlElementsToList(html); + removeLoading(this.listContainer); + } + + addReturnedHtmlElementsToList(html) { + const el = document.createElement('div'); + el.innerHTML = html; + window.components.init(el); + for (const child of [...el.children]) { + this.listContainer.appendChild(child); + } + } + + setActiveFilterTab(filterName) { + this.filterTabs.forEach(t => t.classList.remove('selected')); + const activeTab = this.filterTabs.find(t => t.dataset.filter === filterName); + if (activeTab) { + activeTab.classList.add('selected'); + } + } + + resetAll() { + this.resetState(); + this.resetListView(); + this.resetSearchView(); + this.formContainer.innerHTML = ''; + this.setActiveFilterTab('all'); + } + + resetSearchView() { + this.searchInput.value = ''; + } + + resetListView() { + showLoading(this.listContainer); + this.page = 1; + } + + refreshGallery() { + this.resetListView(); + this.loadGallery(); + } + + onImageSelectEvent(event) { + const image = JSON.parse(event.detail.data); + const isDblClick = ((image && image.id === this.lastSelected.id) + && Date.now() - this.lastSelectedTime < 400); + const alreadySelected = event.target.classList.contains('selected'); + [...this.listContainer.querySelectorAll('.selected')].forEach(el => { + el.classList.remove('selected'); + }); + + if (!alreadySelected) { + event.target.classList.add('selected'); + this.loadImageEditForm(image.id); + } + this.selectButton.classList.toggle('hidden', alreadySelected); + + if (isDblClick && this.callback) { + this.callback(image); + this.hide(); + } + + this.lastSelected = image; + this.lastSelectedTime = Date.now(); + } + + async loadImageEditForm(imageId, requestDelete = false) { + if (!requestDelete) { + this.formContainer.innerHTML = ''; + } + + const params = requestDelete ? {delete: true} : {}; + const {data: formHtml} = await window.$http.get(`/images/edit/${imageId}`, params); + this.formContainer.innerHTML = formHtml; + window.components.init(this.formContainer); + } + +} + +export default ImageManager; \ No newline at end of file diff --git a/resources/js/services/dom.js b/resources/js/services/dom.js index 00b34bf34..7a7b2c9bc 100644 --- a/resources/js/services/dom.js +++ b/resources/js/services/dom.js @@ -106,4 +106,15 @@ export function findText(selector, text) { */ export function showLoading(element) { element.innerHTML = `
`; +} + +/** + * Remove any loading indicators within the given element. + * @param {Element} element + */ +export function removeLoading(element) { + const loadingEls = element.querySelectorAll('.loading-container'); + for (const el of loadingEls) { + el.remove(); + } } \ No newline at end of file diff --git a/resources/js/vues/image-manager.js b/resources/js/vues/image-manager.js deleted file mode 100644 index b87734556..000000000 --- a/resources/js/vues/image-manager.js +++ /dev/null @@ -1,204 +0,0 @@ -import * as Dates from "../services/dates"; -import dropzone from "./components/dropzone"; - -let page = 1; -let previousClickTime = 0; -let previousClickImage = 0; -let dataLoaded = false; -let callback = false; -let baseUrl = ''; - -let preSearchImages = []; -let preSearchHasMore = false; - -const data = { - images: [], - - imageType: false, - uploadedTo: false, - - selectedImage: false, - dependantPages: false, - showing: false, - filter: null, - hasMore: false, - searching: false, - searchTerm: '', - - imageUpdateSuccess: false, - imageDeleteSuccess: false, - deleteConfirm: false, -}; - -const methods = { - - show(providedCallback, imageType = null) { - callback = providedCallback; - this.showing = true; - this.$el.children[0].components.popup.show(); - - // Get initial images if they have not yet been loaded in. - if (dataLoaded && imageType === this.imageType) return; - if (imageType) { - this.imageType = imageType; - this.resetState(); - } - this.fetchData(); - dataLoaded = true; - }, - - hide() { - if (this.$refs.dropzone) { - this.$refs.dropzone.onClose(); - } - this.showing = false; - this.selectedImage = false; - this.$el.children[0].components.popup.hide(); - }, - - async fetchData() { - const params = { - page, - search: this.searching ? this.searchTerm : null, - uploaded_to: this.uploadedTo || null, - filter_type: this.filter, - }; - - const {data} = await this.$http.get(baseUrl, params); - this.images = this.images.concat(data.images); - this.hasMore = data.has_more; - page++; - }, - - setFilterType(filterType) { - this.filter = filterType; - this.resetState(); - this.fetchData(); - }, - - resetState() { - this.cancelSearch(); - this.resetListView(); - this.deleteConfirm = false; - baseUrl = window.baseUrl(`/images/${this.imageType}`); - }, - - resetListView() { - this.images = []; - this.hasMore = false; - page = 1; - }, - - searchImages() { - if (this.searchTerm === '') return this.cancelSearch(); - - // Cache current settings for later - if (!this.searching) { - preSearchImages = this.images; - preSearchHasMore = this.hasMore; - } - - this.searching = true; - this.resetListView(); - this.fetchData(); - }, - - cancelSearch() { - if (!this.searching) return; - this.searching = false; - this.searchTerm = ''; - this.images = preSearchImages; - this.hasMore = preSearchHasMore; - }, - - imageSelect(image) { - const dblClickTime = 300; - const currentTime = Date.now(); - const timeDiff = currentTime - previousClickTime; - const isDblClick = timeDiff < dblClickTime && image.id === previousClickImage; - - if (isDblClick) { - this.callbackAndHide(image); - } else { - this.selectedImage = image; - this.deleteConfirm = false; - this.dependantPages = false; - } - - previousClickTime = currentTime; - previousClickImage = image.id; - }, - - callbackAndHide(imageResult) { - if (callback) callback(imageResult); - this.hide(); - }, - - async saveImageDetails() { - let url = window.baseUrl(`/images/${this.selectedImage.id}`); - try { - await this.$http.put(url, this.selectedImage) - } catch (error) { - if (error.response.status === 422) { - let errors = error.response.data; - let message = ''; - Object.keys(errors).forEach((key) => { - message += errors[key].join('\n'); - }); - this.$events.emit('error', message); - } - } - }, - - async deleteImage() { - - if (!this.deleteConfirm) { - const url = window.baseUrl(`/images/usage/${this.selectedImage.id}`); - try { - const {data} = await this.$http.get(url); - this.dependantPages = data; - } catch (error) { - console.error(error); - } - this.deleteConfirm = true; - return; - } - - const url = window.baseUrl(`/images/${this.selectedImage.id}`); - await this.$http.delete(url); - this.images.splice(this.images.indexOf(this.selectedImage), 1); - this.selectedImage = false; - this.$events.emit('success', trans('components.image_delete_success')); - this.deleteConfirm = false; - }, - - getDate(stringDate) { - return Dates.formatDateTime(new Date(stringDate)); - }, - - uploadSuccess(event) { - this.images.unshift(event.data); - this.$events.emit('success', trans('components.image_upload_success')); - }, -}; - -const computed = { - uploadUrl() { - return window.baseUrl(`/images/${this.imageType}`); - } -}; - -function mounted() { - window.ImageManager = this; - this.imageType = this.$el.getAttribute('image-type'); - this.uploadedTo = this.$el.getAttribute('uploaded-to'); - baseUrl = window.baseUrl('/images/' + this.imageType) -} - -export default { - mounted, - methods, - data, - computed, - components: {dropzone}, -}; diff --git a/resources/js/vues/vues.js b/resources/js/vues/vues.js index faa191b95..d4bd88a52 100644 --- a/resources/js/vues/vues.js +++ b/resources/js/vues/vues.js @@ -4,10 +4,7 @@ function exists(id) { return document.getElementById(id) !== null; } -import imageManager from "./image-manager"; - let vueMapping = { - 'image-manager': imageManager, }; window.vues = {}; diff --git a/resources/lang/en/common.php b/resources/lang/en/common.php index 68c58b92b..e87bd11a5 100644 --- a/resources/lang/en/common.php +++ b/resources/lang/en/common.php @@ -33,6 +33,7 @@ return [ 'copy' => 'Copy', 'reply' => 'Reply', 'delete' => 'Delete', + 'delete_confirm' => 'Confirm Deletion', 'search' => 'Search', 'search_clear' => 'Clear Search', 'reset' => 'Reset', diff --git a/resources/lang/en/components.php b/resources/lang/en/components.php index 32667eb4e..48a0a32fa 100644 --- a/resources/lang/en/components.php +++ b/resources/lang/en/components.php @@ -15,7 +15,7 @@ return [ 'image_load_more' => 'Load More', 'image_image_name' => 'Image Name', 'image_delete_used' => 'This image is used in the pages below.', - 'image_delete_confirm' => 'Click delete again to confirm you want to delete this image.', + 'image_delete_confirm_text' => 'Are you sure you want to delete this image?', 'image_select_image' => 'Select Image', 'image_dropzone' => 'Drop images or click here to upload', 'images_deleted' => 'Images Deleted', diff --git a/resources/sass/_colors.scss b/resources/sass/_colors.scss index 683694d96..a76d166e9 100644 --- a/resources/sass/_colors.scss +++ b/resources/sass/_colors.scss @@ -51,6 +51,11 @@ fill: currentColor !important; } +.text-white { + color: #fff; + fill: currentColor !important; +} + /* * Entity text colors */ diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss index c73c503b4..eb40741d1 100644 --- a/resources/sass/_components.scss +++ b/resources/sass/_components.scss @@ -197,11 +197,8 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { transition: all cubic-bezier(.4, 0, 1, 1) 160ms; overflow: hidden; &.selected { - //transform: scale3d(0.92, 0.92, 0.92); - border: 4px solid #FFF; - overflow: hidden; - border-radius: 8px; - box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2); + transform: scale3d(0.92, 0.92, 0.92); + outline: currentColor 2px solid; } img { width: 100%; @@ -231,7 +228,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { } } -#image-manager .load-more { +.image-manager .load-more { display: block; text-align: center; @include lightDark(background-color, #EEE, #444); @@ -243,6 +240,10 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { font-style: italic; } +.image-manager .loading-container { + text-align: center; +} + .image-manager-sidebar { width: 300px; overflow-y: auto; @@ -250,6 +251,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { border-inline-start: 1px solid #DDD; @include lightDark(border-color, #ddd, #000); .inner { + min-height: auto; padding: $-m; } img { @@ -291,6 +293,12 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { } } +.image-manager .corner-button { + margin: 0; + border-radius: 0; + padding: $-m; +} + // Dropzone /* * The MIT License @@ -298,7 +306,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { */ .dz-message { font-size: 1em; - line-height: 2.35; + line-height: 2.85; font-style: italic; color: #888; text-align: center; @@ -601,9 +609,14 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group { display: inline-block; @include lightDark(color, #666, #999); cursor: pointer; + border-right: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 2px solid transparent; &.selected { border-bottom: 2px solid var(--color-primary); } + &:last-child { + border-right: 0; + } } } diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss index b6968afc6..439bf8512 100644 --- a/resources/sass/_layout.scss +++ b/resources/sass/_layout.scss @@ -121,6 +121,11 @@ body.flexbox { position: relative; } +.flex-container-column { + display: flex; + flex-direction: column; +} + .flex { min-height: 0; flex: 1; diff --git a/resources/sass/styles.scss b/resources/sass/styles.scss index 8af363469..330af51e8 100644 --- a/resources/sass/styles.scss +++ b/resources/sass/styles.scss @@ -140,8 +140,10 @@ $btt-size: 40px; .contained-search-box { display: flex; + height: 38px; input, button { border-radius: 0; + border: 1px solid #ddd; @include lightDark(border-color, #ddd, #000); margin-inline-start: -1px; } @@ -162,6 +164,9 @@ $btt-size: 40px; background-color: $negative; color: #EEE; } + svg { + margin: 0; + } } .entity-selector { diff --git a/resources/views/components/image-manager-form.blade.php b/resources/views/components/image-manager-form.blade.php new file mode 100644 index 000000000..e49a5fca7 --- /dev/null +++ b/resources/views/components/image-manager-form.blade.php @@ -0,0 +1,60 @@ +
+ +
+ +
+ + {{ $image->name }} + +
+
+ + +
+
+
+ +
+
+ +
+
+
+ + @if(!is_null($dependantPages)) + @if(count($dependantPages) > 0) +

{{ trans('components.image_delete_used') }}

+ + @endif +

{{ trans('components.image_delete_confirm_text') }}

+
+ +
+ @endif + +
\ No newline at end of file diff --git a/resources/views/components/image-manager-list.blade.php b/resources/views/components/image-manager-list.blade.php new file mode 100644 index 000000000..e5562e10f --- /dev/null +++ b/resources/views/components/image-manager-list.blade.php @@ -0,0 +1,23 @@ +@foreach($images as $index => $image) +
+
+ {{ $image->name }} +
+ {{ $image->name }} + {{ trans('components.image_uploaded', ['uploadedDate' => $image->created_at->format('Y-m-d H:i:s')]) }} +
+
+
+@endforeach +@if($hasMore) +
{{ trans('components.image_load_more') }}
+@endif \ No newline at end of file diff --git a/resources/views/components/image-manager.blade.php b/resources/views/components/image-manager.blade.php index 5e2de7bb8..4f03eeaec 100644 --- a/resources/views/components/image-manager.blade.php +++ b/resources/views/components/image-manager.blade.php @@ -1,101 +1,62 @@ -
+
- @exposeTranslations([ - 'components.image_delete_success', - 'components.image_upload_success', - 'errors.server_upload_limit', - 'components.image_upload_remove', - 'components.file_upload_timeout', - ]) - -