Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// This file is part of Moodle - http://moodle.org/
2
//
3
// Moodle is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// Moodle is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
 
16
/**
17
 * Tiny Media plugin Image class for Moodle.
18
 *
19
 * @module      tiny_media/image
20
 * @copyright   2022 Huong Nguyen <huongnv13@gmail.com>
21
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 */
23
 
24
import Selectors from './selectors';
25
import ImageModal from './imagemodal';
26
import {getImagePermissions} from './options';
27
import {getFilePicker} from 'editor_tiny/options';
28
import {ImageInsert} from 'tiny_media/imageinsert';
29
import {ImageDetails} from 'tiny_media/imagedetails';
1441 ariadna 30
import {prefetchStrings} from 'core/prefetch';
1 efrain 31
import {getString} from 'core/str';
32
import {
1441 ariadna 33
    body,
34
    footer,
35
    hideElements,
1 efrain 36
    showElements,
37
    isPercentageValue,
1441 ariadna 38
} from './helpers';
39
import {MAX_LENGTH_ALT} from './imagehelpers';
1 efrain 40
 
1441 ariadna 41
prefetchStrings('tiny_media', [
42
    'imageurlrequired',
43
    'sizecustom_help',
44
]);
45
 
1 efrain 46
export default class MediaImage {
47
    canShowFilePicker = false;
48
    editor = null;
49
    currentModal = null;
50
    /**
51
     * @type {HTMLElement|null} The root element.
52
     */
53
    root = null;
54
 
55
    constructor(editor) {
56
        const permissions = getImagePermissions(editor);
57
        const options = getFilePicker(editor, 'image');
58
        // Indicates whether the file picker can be shown.
59
        this.canShowFilePicker = permissions.filepicker
60
            && (typeof options !== 'undefined')
61
            && Object.keys(options.repositories).length > 0;
62
        // Indicates whether the drop zone area can be shown.
11 efrain 63
        this.canShowDropZone = (typeof options !== 'undefined') &&
64
            Object.values(options.repositories).some(repository => repository.type === 'upload');
1 efrain 65
 
66
        this.editor = editor;
67
    }
68
 
69
    async displayDialogue() {
70
        const currentImageData = await this.getCurrentImageData();
71
        this.currentModal = await ImageModal.create();
72
        this.root = this.currentModal.getRoot()[0];
73
        if (currentImageData && currentImageData.src) {
74
            this.loadPreviewImage(currentImageData.src);
75
        } else {
76
            this.loadInsertImage();
77
        }
78
    }
79
 
80
    /**
81
     * Displays an insert image view asynchronously.
82
     *
83
     * @returns {Promise<void>}
84
     */
85
    loadInsertImage = async function() {
86
        const templateContext = {
87
            elementid: this.editor.id,
88
            showfilepicker: this.canShowFilePicker,
89
            showdropzone: this.canShowDropZone,
1441 ariadna 90
            bodyTemplate: Selectors.IMAGE.template.body.insertImageBody,
91
            footerTemplate: Selectors.IMAGE.template.footer.insertImageFooter,
92
            selector: Selectors.IMAGE.type,
1 efrain 93
        };
94
 
1441 ariadna 95
        Promise.all([body(templateContext, this.root), footer(templateContext, this.root)])
1 efrain 96
            .then(() => {
97
                const imageinsert = new ImageInsert(
98
                    this.root,
99
                    this.editor,
100
                    this.currentModal,
101
                    this.canShowFilePicker,
102
                    this.canShowDropZone,
103
                );
104
                imageinsert.init();
105
                return;
106
            })
107
            .catch(error => {
108
                window.console.log(error);
109
            });
110
    };
111
 
112
    async getTemplateContext(data) {
113
        return {
114
            elementid: this.editor.id,
115
            showfilepicker: this.canShowFilePicker,
116
            ...data,
117
        };
118
    }
119
 
120
    async getCurrentImageData() {
121
        const selectedImageProperties = this.getSelectedImageProperties();
122
        if (!selectedImageProperties) {
123
            return {};
124
        }
125
 
126
        const properties = {...selectedImageProperties};
127
 
128
        if (properties.src) {
129
            properties.haspreview = true;
130
        }
131
 
132
        if (!properties.alt) {
133
            properties.presentation = true;
134
        }
135
 
136
        return properties;
137
    }
138
 
139
    /**
140
     * Asynchronously loads and previews an image from the provided URL.
141
     *
142
     * @param {string} url - The URL of the image to load and preview.
143
     * @returns {Promise<void>}
144
     */
145
    loadPreviewImage = async function(url) {
146
        this.startImageLoading();
147
        const image = new Image();
148
        image.src = url;
1441 ariadna 149
        image.addEventListener('error', async() => {
1 efrain 150
            const urlWarningLabelEle = this.root.querySelector(Selectors.IMAGE.elements.urlWarning);
1441 ariadna 151
            urlWarningLabelEle.innerHTML = await getString('imageurlrequired', 'tiny_media');
1 efrain 152
            showElements(Selectors.IMAGE.elements.urlWarning, this.root);
153
            this.stopImageLoading();
154
        });
155
 
156
        image.addEventListener('load', async() => {
157
            const currentImageData = await this.getCurrentImageData();
158
            let templateContext = await this.getTemplateContext(currentImageData);
159
            templateContext.sizecustomhelpicon = {text: await getString('sizecustom_help', 'tiny_media')};
1441 ariadna 160
            templateContext.bodyTemplate = Selectors.IMAGE.template.body.insertImageDetailsBody;
161
            templateContext.footerTemplate = Selectors.IMAGE.template.footer.insertImageDetailsFooter;
162
            templateContext.selector = Selectors.IMAGE.type;
163
            templateContext.maxlengthalt = MAX_LENGTH_ALT;
1 efrain 164
 
1441 ariadna 165
            Promise.all([body(templateContext, this.root), footer(templateContext, this.root)])
1 efrain 166
                .then(() => {
167
                    this.stopImageLoading();
168
                    return;
169
                })
170
                .then(() => {
171
                    const imagedetails = new ImageDetails(
172
                        this.root,
173
                        this.editor,
174
                        this.currentModal,
175
                        this.canShowFilePicker,
176
                        this.canShowDropZone,
177
                        url,
178
                        image,
179
                    );
180
                    imagedetails.init();
181
                    return;
182
                })
183
                .catch(error => {
184
                    window.console.log(error);
185
                });
186
        });
187
    };
188
 
189
    getSelectedImageProperties() {
190
        const image = this.getSelectedImage();
191
        if (!image) {
192
            this.selectedImage = null;
193
            return null;
194
        }
195
 
196
        const properties = {
197
            src: null,
198
            alt: null,
199
            width: null,
200
            height: null,
201
            presentation: false,
202
            customStyle: '', // Custom CSS styles applied to the image.
203
        };
204
 
205
        const getImageHeight = (image) => {
206
            if (!isPercentageValue(String(image.height))) {
207
                return parseInt(image.height, 10);
208
            }
209
 
210
            return image.height;
211
        };
212
 
213
        const getImageWidth = (image) => {
214
            if (!isPercentageValue(String(image.width))) {
215
                return parseInt(image.width, 10);
216
            }
217
 
218
            return image.width;
219
        };
220
 
221
        // Get the current selection.
222
        this.selectedImage = image;
223
 
224
        properties.customStyle = image.style.cssText;
225
 
226
        const width = getImageWidth(image);
227
        if (width !== 0) {
228
            properties.width = width;
229
        }
230
 
231
        const height = getImageHeight(image);
232
        if (height !== 0) {
233
            properties.height = height;
234
        }
235
 
236
        properties.src = image.getAttribute('src');
237
        properties.alt = image.getAttribute('alt') || '';
238
        properties.presentation = (image.getAttribute('role') === 'presentation');
239
 
240
        return properties;
241
    }
242
 
243
    getSelectedImage() {
244
        const imgElm = this.editor.selection.getNode();
245
        const figureElm = this.editor.dom.getParent(imgElm, 'figure.image');
246
        if (figureElm) {
247
            return this.editor.dom.select('img', figureElm)[0];
248
        }
249
 
250
        if (imgElm && (imgElm.nodeName.toUpperCase() !== 'IMG' || this.isPlaceholderImage(imgElm))) {
251
            return null;
252
        }
253
        return imgElm;
254
    }
255
 
256
    isPlaceholderImage(imgElm) {
257
        if (imgElm.nodeName.toUpperCase() !== 'IMG') {
258
            return false;
259
        }
260
 
261
        return (imgElm.hasAttribute('data-mce-object') || imgElm.hasAttribute('data-mce-placeholder'));
262
    }
263
 
264
    /**
265
     * Displays the upload loader and disables UI elements while loading a file.
266
     */
267
    startImageLoading() {
268
        showElements(Selectors.IMAGE.elements.loaderIcon, this.root);
269
        hideElements(Selectors.IMAGE.elements.insertImage, this.root);
270
    }
271
 
272
    /**
273
     * Displays the upload loader and disables UI elements while loading a file.
274
     */
275
    stopImageLoading() {
276
        hideElements(Selectors.IMAGE.elements.loaderIcon, this.root);
277
        showElements(Selectors.IMAGE.elements.insertImage, this.root);
278
    }
279
}