Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 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 embed upload class.
18
 *
19
 * This handles the embed upload using url, drag-drop and repositories.
20
 *
21
 * @module      tiny_media/embed/embedinsert
22
 * @copyright   2024 Stevani Andolo <stevani@hotmail.com.au>
23
 * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
import {prefetchStrings} from 'core/prefetch';
27
import {getStrings} from 'core/str';
28
import {component} from "../common";
29
import {
30
    setPropertiesFromData,
31
    startMediaLoading,
32
    stopMediaLoading,
33
    showElements,
34
} from '../helpers';
35
import Selectors from "../selectors";
36
import Dropzone from 'core/dropzone';
37
import uploadFile from 'editor_tiny/uploader';
38
import {EmbedHandler} from './embedhandler';
39
import {
40
    getMediaTitle,
41
    mediaDetailsTemplateContext,
42
    checkMediaType,
43
    fetchPreview,
44
} from './embedhelpers';
45
import {EmbedPreview} from './embedpreview';
46
 
47
prefetchStrings(component, [
48
    'insertmedia',
49
    'addmediafilesdrop',
50
    'uploading',
51
    'loadingmedia',
52
]);
53
 
54
export class EmbedInsert {
55
 
56
    constructor(data) {
57
        setPropertiesFromData(this, data); // Creates dynamic properties based on "data" param.
58
    }
59
 
60
    /**
61
     * Init the dropzone and lang strings.
62
     */
63
    init = async() => {
64
        const langStringKeys = [
65
            'insertmedia',
66
            'addmediafilesdrop',
67
            'uploading',
68
            'loadingmedia',
69
        ];
70
        const langStringValues = await getStrings([...langStringKeys].map((key) => ({key, component})));
71
        this.langStrings = Object.fromEntries(langStringKeys.map((key, index) => [key, langStringValues[index]]));
72
        this.currentModal.setTitle(this.langStrings.insertmedia);
73
 
74
        // Let's init the dropzone if canShowDropZone is true and mediaType is null.
75
        if (this.canShowDropZone && !this.mediaType) {
76
            const dropZoneEle = document.querySelector(Selectors.EMBED.elements.dropzoneContainer);
77
            const dropZone = new Dropzone(
78
                dropZoneEle,
79
                this.acceptedMediaTypes,
80
                files => {
81
                    this.handleUploadedFile(files);
82
                }
83
            );
84
 
85
            dropZone.setLabel(this.langStrings.addmediafilesdrop);
86
            dropZone.init();
87
        }
88
    };
89
 
90
    /**
91
     * Loads and displays a preview media based on the provided URL, and handles media loading events.
92
     *
93
     * @param {string} url - The URL of the media to load and display.
94
     */
95
    loadMediaPreview = async(url) => {
96
        this.originalUrl = url;
97
        this.fetchedMediaLinkTitle = await getMediaTitle(url, this);
98
 
99
        if (this.newMediaLink) { // Media added using url input.
100
            this.filteredContent = await fetchPreview(this.originalUrl, this.contextId);
101
 
102
            if (!this.mediaType) {
103
                // It means the url points to a physical media file.
104
                if (this.fetchedMediaLinkTitle) {
105
                    // Case-insensitive regex for video tag.
106
                    const videoRegex = /<video[^>]*>.*<\/video>/i;
107
                    // Case-insensitive regex for audio tag.
108
                    const audioRegex = /<audio[^>]*>.*<\/audio>/i;
109
 
110
                    if (videoRegex.test(this.filteredContent)) {
111
                        this.mediaType = 'video';
112
                    } else if (audioRegex.test(this.filteredContent)) {
113
                        this.mediaType = 'audio';
114
                    }
115
                } else {
116
                    this.mediaType = 'link';
117
                }
118
            }
119
 
120
            // Process the media preview.
121
            this.processMediaPreview();
122
        } else { // Media added using dropzone or repositories.
123
            this.mediaType ??= await checkMediaType(url);
124
 
125
            // Process the media preview.
126
            this.processMediaPreview();
127
        }
128
    };
129
 
130
    /**
131
     * Process the media preview.
132
     */
133
    processMediaPreview = async() => {
134
        // Let's combine the props.
135
        setPropertiesFromData(
136
            this,
137
            await (new EmbedHandler(this)).getMediaTemplateContext()
138
        );
139
 
140
        // Construct templateContext for embed preview.
141
        const templateContext = await mediaDetailsTemplateContext(this);
142
 
143
        if (this.isUpdating && !this.newMediaLink) {
144
            // Will be used to set the media title if it's in update state.
145
            this.mediaTitle = templateContext.media.title;
146
        }
147
 
148
        // Load the media details and preview of the selected media.
149
        (new EmbedHandler(this)).loadMediaDetails(new EmbedPreview(this), templateContext);
150
    };
151
 
152
    /**
153
     * Updates the content of the loader icon.
154
     *
155
     * @param {HTMLElement} root - The root element containing the loader icon.
156
     * @param {object} langStrings - An object containing language strings.
157
     * @param {number|null} progress - The progress percentage (optional).
158
     * @returns {void}
159
     */
160
    updateLoaderIcon = (root, langStrings, progress = null) => {
161
        const loaderIconState = root.querySelector(Selectors.EMBED.elements.loaderIconContainer + ' div');
162
        loaderIconState.innerHTML = (progress !== null) ?
163
                               `${langStrings.uploading} ${Math.round(progress)}%` :
164
                               langStrings.loadingmedia;
165
    };
166
 
167
    /**
168
     * Handles media preview on file picker callback.
169
     *
170
     * @param {object} params Object of uploaded file
171
     */
172
    filePickerCallback = (params) => {
173
        if (params.url) {
174
            if (this.mediaType) {
175
                // Set mediaType to "null" if it started with viewing embedded link, otherwise it will not be consistent.
176
                this.mediaType = null;
177
            }
178
 
179
            // Flag as new file upload.
180
            this.newFileUpload = true;
181
 
182
            // Load the media preview.
183
            this.loadMediaPreview(params.url);
184
        }
185
    };
186
 
187
    /**
188
     * Handles the uploaded file, initiates the upload process, and updates the UI during the upload.
189
     *
190
     * @param {FileList} files - The list of files to upload (usually from a file input field).
191
     * @returns {Promise<void>} A promise that resolves when the file is uploaded and processed.
192
     */
193
    handleUploadedFile = async(files) => {
194
        try {
195
            startMediaLoading(this.root, Selectors.EMBED.type);
196
            const fileURL = await uploadFile(this.editor, 'media', files[0], files[0].name, (progress) => {
197
                this.updateLoaderIcon(this.root, this.langStrings, progress);
198
            });
199
 
200
            // Set the loader icon content to "loading" after the file upload completes.
201
            this.updateLoaderIcon(this.root, this.langStrings);
202
            this.filePickerCallback({url: fileURL});
203
        } catch (error) {
204
            // Handle the error.
205
            const urlWarningLabelEle = this.root.querySelector(Selectors.EMBED.elements.urlWarning);
206
            urlWarningLabelEle.innerHTML = error.error !== undefined ? error.error : error;
207
            showElements(Selectors.EMBED.elements.urlWarning, this.root);
208
            stopMediaLoading(this.root, Selectors.EMBED.type);
209
        }
210
    };
211
}