Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | 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';
30
import {getString} from 'core/str';
31
import {
32
    bodyImageInsert,
33
    footerImageInsert,
34
    bodyImageDetails,
35
    footerImageDetails,
36
    showElements,
37
    hideElements,
38
    isPercentageValue,
39
} from 'tiny_media/imagehelpers';
40
 
41
export default class MediaImage {
42
    canShowFilePicker = false;
43
    editor = null;
44
    currentModal = null;
45
    /**
46
     * @type {HTMLElement|null} The root element.
47
     */
48
    root = null;
49
 
50
    constructor(editor) {
51
        const permissions = getImagePermissions(editor);
52
        const options = getFilePicker(editor, 'image');
53
        // Indicates whether the file picker can be shown.
54
        this.canShowFilePicker = permissions.filepicker
55
            && (typeof options !== 'undefined')
56
            && Object.keys(options.repositories).length > 0;
57
        // Indicates whether the drop zone area can be shown.
58
        this.canShowDropZone = Object.values(options.repositories).some(repository => repository.type === 'upload');
59
 
60
        this.editor = editor;
61
    }
62
 
63
    async displayDialogue() {
64
        const currentImageData = await this.getCurrentImageData();
65
        this.currentModal = await ImageModal.create();
66
        this.root = this.currentModal.getRoot()[0];
67
        if (currentImageData && currentImageData.src) {
68
            this.loadPreviewImage(currentImageData.src);
69
        } else {
70
            this.loadInsertImage();
71
        }
72
    }
73
 
74
    /**
75
     * Displays an insert image view asynchronously.
76
     *
77
     * @returns {Promise<void>}
78
     */
79
    loadInsertImage = async function() {
80
        const templateContext = {
81
            elementid: this.editor.id,
82
            showfilepicker: this.canShowFilePicker,
83
            showdropzone: this.canShowDropZone,
84
        };
85
 
86
        Promise.all([bodyImageInsert(templateContext, this.root), footerImageInsert(templateContext, this.root)])
87
            .then(() => {
88
                const imageinsert = new ImageInsert(
89
                    this.root,
90
                    this.editor,
91
                    this.currentModal,
92
                    this.canShowFilePicker,
93
                    this.canShowDropZone,
94
                );
95
                imageinsert.init();
96
                return;
97
            })
98
            .catch(error => {
99
                window.console.log(error);
100
            });
101
    };
102
 
103
    async getTemplateContext(data) {
104
        return {
105
            elementid: this.editor.id,
106
            showfilepicker: this.canShowFilePicker,
107
            ...data,
108
        };
109
    }
110
 
111
    async getCurrentImageData() {
112
        const selectedImageProperties = this.getSelectedImageProperties();
113
        if (!selectedImageProperties) {
114
            return {};
115
        }
116
 
117
        const properties = {...selectedImageProperties};
118
 
119
        if (properties.src) {
120
            properties.haspreview = true;
121
        }
122
 
123
        if (!properties.alt) {
124
            properties.presentation = true;
125
        }
126
 
127
        return properties;
128
    }
129
 
130
    /**
131
     * Asynchronously loads and previews an image from the provided URL.
132
     *
133
     * @param {string} url - The URL of the image to load and preview.
134
     * @returns {Promise<void>}
135
     */
136
    loadPreviewImage = async function(url) {
137
        this.startImageLoading();
138
        const image = new Image();
139
        image.src = url;
140
        image.addEventListener('error', () => {
141
            const urlWarningLabelEle = this.root.querySelector(Selectors.IMAGE.elements.urlWarning);
142
            urlWarningLabelEle.innerHTML = this.langStrings.imageurlrequired;
143
            showElements(Selectors.IMAGE.elements.urlWarning, this.root);
144
            this.stopImageLoading();
145
        });
146
 
147
        image.addEventListener('load', async() => {
148
            const currentImageData = await this.getCurrentImageData();
149
            let templateContext = await this.getTemplateContext(currentImageData);
150
            templateContext.sizecustomhelpicon = {text: await getString('sizecustom_help', 'tiny_media')};
151
 
152
            Promise.all([bodyImageDetails(templateContext, this.root), footerImageDetails(templateContext, this.root)])
153
                .then(() => {
154
                    this.stopImageLoading();
155
                    return;
156
                })
157
                .then(() => {
158
                    const imagedetails = new ImageDetails(
159
                        this.root,
160
                        this.editor,
161
                        this.currentModal,
162
                        this.canShowFilePicker,
163
                        this.canShowDropZone,
164
                        url,
165
                        image,
166
                    );
167
                    imagedetails.init();
168
                    return;
169
                })
170
                .catch(error => {
171
                    window.console.log(error);
172
                });
173
        });
174
    };
175
 
176
    getSelectedImageProperties() {
177
        const image = this.getSelectedImage();
178
        if (!image) {
179
            this.selectedImage = null;
180
            return null;
181
        }
182
 
183
        const properties = {
184
            src: null,
185
            alt: null,
186
            width: null,
187
            height: null,
188
            presentation: false,
189
            customStyle: '', // Custom CSS styles applied to the image.
190
        };
191
 
192
        const getImageHeight = (image) => {
193
            if (!isPercentageValue(String(image.height))) {
194
                return parseInt(image.height, 10);
195
            }
196
 
197
            return image.height;
198
        };
199
 
200
        const getImageWidth = (image) => {
201
            if (!isPercentageValue(String(image.width))) {
202
                return parseInt(image.width, 10);
203
            }
204
 
205
            return image.width;
206
        };
207
 
208
        // Get the current selection.
209
        this.selectedImage = image;
210
 
211
        properties.customStyle = image.style.cssText;
212
 
213
        const width = getImageWidth(image);
214
        if (width !== 0) {
215
            properties.width = width;
216
        }
217
 
218
        const height = getImageHeight(image);
219
        if (height !== 0) {
220
            properties.height = height;
221
        }
222
 
223
        properties.src = image.getAttribute('src');
224
        properties.alt = image.getAttribute('alt') || '';
225
        properties.presentation = (image.getAttribute('role') === 'presentation');
226
 
227
        return properties;
228
    }
229
 
230
    getSelectedImage() {
231
        const imgElm = this.editor.selection.getNode();
232
        const figureElm = this.editor.dom.getParent(imgElm, 'figure.image');
233
        if (figureElm) {
234
            return this.editor.dom.select('img', figureElm)[0];
235
        }
236
 
237
        if (imgElm && (imgElm.nodeName.toUpperCase() !== 'IMG' || this.isPlaceholderImage(imgElm))) {
238
            return null;
239
        }
240
        return imgElm;
241
    }
242
 
243
    isPlaceholderImage(imgElm) {
244
        if (imgElm.nodeName.toUpperCase() !== 'IMG') {
245
            return false;
246
        }
247
 
248
        return (imgElm.hasAttribute('data-mce-object') || imgElm.hasAttribute('data-mce-placeholder'));
249
    }
250
 
251
    /**
252
     * Displays the upload loader and disables UI elements while loading a file.
253
     */
254
    startImageLoading() {
255
        showElements(Selectors.IMAGE.elements.loaderIcon, this.root);
256
        hideElements(Selectors.IMAGE.elements.insertImage, this.root);
257
    }
258
 
259
    /**
260
     * Displays the upload loader and disables UI elements while loading a file.
261
     */
262
    stopImageLoading() {
263
        hideElements(Selectors.IMAGE.elements.loaderIcon, this.root);
264
        showElements(Selectors.IMAGE.elements.insertImage, this.root);
265
    }
266
}