]> BookStack Code Mirror - bookstack/blob - resources/js/components/image-manager.js
Dropzone: Developed ux further
[bookstack] / resources / js / components / image-manager.js
1 import {
2     onChildEvent, onSelect, removeLoading, showLoading,
3 } from '../services/dom';
4 import {Component} from './component';
5
6 export class ImageManager extends Component {
7
8     setup() {
9         // Options
10         this.uploadedTo = this.$opts.uploadedTo;
11
12         // Element References
13         this.container = this.$el;
14         this.popupEl = this.$refs.popup;
15         this.searchForm = this.$refs.searchForm;
16         this.searchInput = this.$refs.searchInput;
17         this.cancelSearch = this.$refs.cancelSearch;
18         this.listContainer = this.$refs.listContainer;
19         this.filterTabs = this.$manyRefs.filterTabs;
20         this.selectButton = this.$refs.selectButton;
21         this.formContainer = this.$refs.formContainer;
22         this.formContainerPlaceholder = this.$refs.formContainerPlaceholder;
23         this.dropzoneContainer = this.$refs.dropzoneContainer;
24
25         // Instance data
26         this.type = 'gallery';
27         this.lastSelected = {};
28         this.lastSelectedTime = 0;
29         this.callback = null;
30         this.resetState = () => {
31             this.hasData = false;
32             this.page = 1;
33             this.filter = 'all';
34         };
35         this.resetState();
36
37         this.setupListeners();
38
39         window.setTimeout(() => {
40             this.show(() => {
41             }, 'gallery');
42         }, 500);
43     }
44
45     setupListeners() {
46         onSelect(this.filterTabs, e => {
47             this.resetAll();
48             this.filter = e.target.dataset.filter;
49             this.setActiveFilterTab(this.filter);
50             this.loadGallery();
51         });
52
53         this.searchForm.addEventListener('submit', event => {
54             this.resetListView();
55             this.loadGallery();
56             event.preventDefault();
57         });
58
59         onSelect(this.cancelSearch, () => {
60             this.resetListView();
61             this.resetSearchView();
62             this.loadGallery();
63             this.cancelSearch.classList.remove('active');
64         });
65
66         this.searchInput.addEventListener('input', () => {
67             this.cancelSearch.classList.toggle('active', this.searchInput.value.trim());
68         });
69
70         onChildEvent(this.listContainer, '.load-more', 'click', async event => {
71             showLoading(event.target);
72             this.page += 1;
73             await this.loadGallery();
74             event.target.remove();
75         });
76
77         this.listContainer.addEventListener('event-emit-select-image', this.onImageSelectEvent.bind(this));
78
79         this.listContainer.addEventListener('error', event => {
80             event.target.src = window.baseUrl('loading_error.png');
81         }, true);
82
83         onSelect(this.selectButton, () => {
84             if (this.callback) {
85                 this.callback(this.lastSelected);
86             }
87             this.hide();
88         });
89
90         onChildEvent(this.formContainer, '#image-manager-delete', 'click', () => {
91             if (this.lastSelected) {
92                 this.loadImageEditForm(this.lastSelected.id, true);
93             }
94         });
95
96         this.formContainer.addEventListener('ajax-form-success', () => {
97             this.refreshGallery();
98             this.resetEditForm();
99         });
100         this.container.addEventListener('dropzone-upload-success', this.refreshGallery.bind(this));
101     }
102
103     show(callback, type = 'gallery') {
104         this.resetAll();
105
106         this.callback = callback;
107         this.type = type;
108         this.getPopup().show();
109         this.dropzoneContainer.classList.toggle('hidden', type !== 'gallery');
110
111         if (!this.hasData) {
112             this.loadGallery();
113             this.hasData = true;
114         }
115     }
116
117     hide() {
118         this.getPopup().hide();
119     }
120
121     /**
122      * @returns {Popup}
123      */
124     getPopup() {
125         return window.$components.firstOnElement(this.popupEl, 'popup');
126     }
127
128     async loadGallery() {
129         const params = {
130             page: this.page,
131             search: this.searchInput.value || null,
132             uploaded_to: this.uploadedTo,
133             filter_type: this.filter === 'all' ? null : this.filter,
134         };
135
136         const {data: html} = await window.$http.get(`images/${this.type}`, params);
137         if (params.page === 1) {
138             this.listContainer.innerHTML = '';
139         }
140         this.addReturnedHtmlElementsToList(html);
141         removeLoading(this.listContainer);
142     }
143
144     addReturnedHtmlElementsToList(html) {
145         const el = document.createElement('div');
146         el.innerHTML = html;
147         window.$components.init(el);
148         for (const child of [...el.children]) {
149             this.listContainer.appendChild(child);
150         }
151     }
152
153     setActiveFilterTab(filterName) {
154         for (const tab of this.filterTabs) {
155             const selected = tab.dataset.filter === filterName;
156             tab.setAttribute('aria-selected', selected ? 'true' : 'false');
157         }
158     }
159
160     resetAll() {
161         this.resetState();
162         this.resetListView();
163         this.resetSearchView();
164         this.resetEditForm();
165         this.setActiveFilterTab('all');
166         this.selectButton.classList.add('hidden');
167     }
168
169     resetSearchView() {
170         this.searchInput.value = '';
171     }
172
173     resetEditForm() {
174         this.formContainer.innerHTML = '';
175         this.formContainerPlaceholder.removeAttribute('hidden');
176     }
177
178     resetListView() {
179         showLoading(this.listContainer);
180         this.page = 1;
181     }
182
183     refreshGallery() {
184         this.resetListView();
185         this.loadGallery();
186     }
187
188     onImageSelectEvent(event) {
189         const image = JSON.parse(event.detail.data);
190         const isDblClick = ((image && image.id === this.lastSelected.id)
191             && Date.now() - this.lastSelectedTime < 400);
192         const alreadySelected = event.target.classList.contains('selected');
193         [...this.listContainer.querySelectorAll('.selected')].forEach(el => {
194             el.classList.remove('selected');
195         });
196
197         if (!alreadySelected) {
198             event.target.classList.add('selected');
199             this.loadImageEditForm(image.id);
200         } else {
201             this.resetEditForm();
202         }
203         this.selectButton.classList.toggle('hidden', alreadySelected);
204
205         if (isDblClick && this.callback) {
206             this.callback(image);
207             this.hide();
208         }
209
210         this.lastSelected = image;
211         this.lastSelectedTime = Date.now();
212     }
213
214     async loadImageEditForm(imageId, requestDelete = false) {
215         if (!requestDelete) {
216             this.formContainer.innerHTML = '';
217         }
218
219         const params = requestDelete ? {delete: true} : {};
220         const {data: formHtml} = await window.$http.get(`/images/edit/${imageId}`, params);
221         this.formContainer.innerHTML = formHtml;
222         this.formContainerPlaceholder.setAttribute('hidden', '');
223         window.$components.init(this.formContainer);
224     }
225
226 }