X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/a6633642232efd164d4708967ab59e498fbff896..refs/pull/5096/head:/resources/js/components/image-manager.js diff --git a/resources/js/components/image-manager.js b/resources/js/components/image-manager.js index c974ab1b0..47231477b 100644 --- a/resources/js/components/image-manager.js +++ b/resources/js/components/image-manager.js @@ -1,13 +1,11 @@ -import {onChildEvent, onSelect, removeLoading, showLoading} from "../services/dom"; +import { + onChildEvent, onSelect, removeLoading, showLoading, +} from '../services/dom'; +import {Component} from './component'; -/** - * ImageManager - * @extends {Component} - */ -class ImageManager { +export class ImageManager extends Component { setup() { - // Options this.uploadedTo = this.$opts.uploadedTo; @@ -20,8 +18,12 @@ class ImageManager { this.listContainer = this.$refs.listContainer; this.filterTabs = this.$manyRefs.filterTabs; this.selectButton = this.$refs.selectButton; + this.uploadButton = this.$refs.uploadButton; + this.uploadHint = this.$refs.uploadHint; this.formContainer = this.$refs.formContainer; + this.formContainerPlaceholder = this.$refs.formContainerPlaceholder; this.dropzoneContainer = this.$refs.dropzoneContainer; + this.loadMore = this.$refs.loadMore; // Instance data this.type = 'gallery'; @@ -36,11 +38,10 @@ class ImageManager { this.resetState(); this.setupListeners(); - - window.ImageManager = this; } setupListeners() { + // Filter tab click onSelect(this.filterTabs, e => { this.resetAll(); this.filter = e.target.dataset.filter; @@ -48,32 +49,33 @@ class ImageManager { this.loadGallery(); }); + // Search submit this.searchForm.addEventListener('submit', event => { this.resetListView(); this.loadGallery(); + this.cancelSearch.toggleAttribute('hidden', !this.searchInput.value); event.preventDefault(); }); - onSelect(this.cancelSearch, event => { + // Cancel search button + onSelect(this.cancelSearch, () => { 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(); - }); + // Load more button click + onChildEvent(this.container, '.load-more button', 'click', this.runLoadMore.bind(this)); + // Select image event this.listContainer.addEventListener('event-emit-select-image', this.onImageSelectEvent.bind(this)); + // Image load error handling + this.listContainer.addEventListener('error', event => { + event.target.src = window.baseUrl('loading_error.png'); + }, true); + + // Footer select button click onSelect(this.selectButton, () => { if (this.callback) { this.callback(this.lastSelected); @@ -81,14 +83,48 @@ class ImageManager { this.hide(); }); - onChildEvent(this.formContainer, '#image-manager-delete', 'click', event => { + // Delete button click + onChildEvent(this.formContainer, '#image-manager-delete', 'click', () => { 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)); + // Rebuild thumbs click + onChildEvent(this.formContainer, '#image-manager-rebuild-thumbs', 'click', async (_, button) => { + button.disabled = true; + if (this.lastSelected) { + await this.rebuildThumbnails(this.lastSelected.id); + } + button.disabled = false; + }); + + // Edit form submit + this.formContainer.addEventListener('ajax-form-success', () => { + this.refreshGallery(); + this.resetEditForm(); + }); + + // Image upload success + this.container.addEventListener('dropzone-upload-success', this.refreshGallery.bind(this)); + + // Auto load-more on scroll + const scrollZone = this.listContainer.parentElement; + let scrollEvents = []; + scrollZone.addEventListener('wheel', event => { + const scrollOffset = Math.ceil(scrollZone.scrollHeight - scrollZone.scrollTop); + const bottomedOut = scrollOffset === scrollZone.clientHeight; + if (!bottomedOut || event.deltaY < 1) { + return; + } + + const secondAgo = Date.now() - 1000; + scrollEvents.push(Date.now()); + scrollEvents = scrollEvents.filter(d => d >= secondAgo); + if (scrollEvents.length > 5 && this.canLoadMore()) { + this.runLoadMore(); + } + }); } show(callback, type = 'gallery') { @@ -96,8 +132,16 @@ class ImageManager { this.callback = callback; this.type = type; - this.popupEl.components.popup.show(); - this.dropzoneContainer.classList.toggle('hidden', type !== 'gallery'); + this.getPopup().show(); + + const hideUploads = type !== 'gallery'; + this.dropzoneContainer.classList.toggle('hidden', hideUploads); + this.uploadButton.classList.toggle('hidden', hideUploads); + this.uploadHint.classList.toggle('hidden', hideUploads); + + /** @var {Dropzone} * */ + const dropzone = window.$components.firstOnElement(this.container, 'dropzone'); + dropzone.toggleActive(!hideUploads); if (!this.hasData) { this.loadGallery(); @@ -106,7 +150,14 @@ class ImageManager { } hide() { - this.popupEl.components.popup.hide(); + this.getPopup().hide(); + } + + /** + * @returns {Popup} + */ + getPopup() { + return window.$components.firstOnElement(this.popupEl, 'popup'); } async loadGallery() { @@ -118,6 +169,9 @@ class ImageManager { }; const {data: html} = await window.$http.get(`images/${this.type}`, params); + if (params.page === 1) { + this.listContainer.innerHTML = ''; + } this.addReturnedHtmlElementsToList(html); removeLoading(this.listContainer); } @@ -125,17 +179,24 @@ class ImageManager { addReturnedHtmlElementsToList(html) { const el = document.createElement('div'); el.innerHTML = html; - window.components.init(el); + + const loadMore = el.querySelector('.load-more'); + if (loadMore) { + loadMore.remove(); + this.loadMore.innerHTML = loadMore.innerHTML; + } + this.loadMore.toggleAttribute('hidden', !loadMore); + + 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'); + for (const tab of this.filterTabs) { + const selected = tab.dataset.filter === filterName; + tab.setAttribute('aria-selected', selected ? 'true' : 'false'); } } @@ -150,10 +211,12 @@ class ImageManager { resetSearchView() { this.searchInput.value = ''; + this.cancelSearch.toggleAttribute('hidden', true); } resetEditForm() { this.formContainer.innerHTML = ''; + this.formContainerPlaceholder.removeAttribute('hidden'); } resetListView() { @@ -166,8 +229,8 @@ class ImageManager { this.loadGallery(); } - onImageSelectEvent(event) { - const image = JSON.parse(event.detail.data); + async onImageSelectEvent(event) { + let 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'); @@ -175,12 +238,15 @@ class ImageManager { el.classList.remove('selected'); }); - if (!alreadySelected) { + if (!alreadySelected && !isDblClick) { event.target.classList.add('selected'); - this.loadImageEditForm(image.id); - } else { + image = await this.loadImageEditForm(image.id); + } else if (!isDblClick) { this.resetEditForm(); + } else if (isDblClick) { + image = this.lastSelected; } + this.selectButton.classList.toggle('hidden', alreadySelected); if (isDblClick && this.callback) { @@ -200,9 +266,31 @@ class ImageManager { 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); + this.formContainerPlaceholder.setAttribute('hidden', ''); + window.$components.init(this.formContainer); + + const imageDataEl = this.formContainer.querySelector('#image-manager-form-image-data'); + return JSON.parse(imageDataEl.text); } -} + runLoadMore() { + showLoading(this.loadMore); + this.page += 1; + this.loadGallery(); + } + + canLoadMore() { + return this.loadMore.querySelector('button') && !this.loadMore.hasAttribute('hidden'); + } -export default ImageManager; \ No newline at end of file + async rebuildThumbnails(imageId) { + try { + const response = await window.$http.put(`/images/${imageId}/rebuild-thumbnails`); + window.$events.success(response.data); + this.refreshGallery(); + } catch (err) { + window.$events.showResponseError(err); + } + } + +}