Proyectos de Subversion Moodle

Rev

| 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
 * Element overlay methods.
18
 *
19
 * This module is used to create overlay information on components. For example
20
 * to generate or destroy file drop-zones.
21
 *
22
 * @module     core/local/reactive/overlay
23
 * @copyright  2022 Ferran Recio <ferran@moodle.com>
24
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25
 */
26
 
27
import Templates from 'core/templates';
28
import Prefetch from 'core/prefetch';
29
 
30
// Prefetch the overlay html.
31
const overlayTemplate = 'core/local/reactive/overlay';
32
Prefetch.prefetchTemplate(overlayTemplate);
33
 
34
/**
35
 * @var {boolean} isInitialized if the module is capturing the proper page events.
36
 */
37
let isInitialized = false;
38
 
39
/**
40
 * @var {Object} isInitialized if the module is capturing the proper page events.
41
 */
42
const selectors = {
43
    OVERLAY: "[data-overlay]",
44
    REPOSITION: "[data-overlay-dynamic]",
45
    NAVBAR: "nav.navbar.fixed-top",
46
};
47
 
48
/**
49
 * Adds an overlay to a specific page element.
50
 *
51
 * @param {Object} definition the overlay definition.
52
 * @param {String|Promise} definition.content an optional overlay content.
53
 * @param {String|Promise} definition.icon an optional icon content.
54
 * @param {String} definition.classes an optional CSS classes
55
 * @param {HTMLElement} parent the parent object
56
 * @return {HTMLElement|undefined} the new page element.
57
 */
58
export const addOverlay = async(definition, parent) => {
59
    // Validate non of the passed params is a promise.
60
    if (definition.content && typeof definition.content !== 'string') {
61
        definition.content = await definition.content;
62
    }
63
    if (definition.icon && typeof definition.icon !== 'string') {
64
        definition.icon = await definition.icon;
65
    }
66
    const data = {
67
        content: definition.content,
68
        css: definition.classes ?? 'file-drop-zone',
69
    };
70
    const {html, js} = await Templates.renderForPromise(overlayTemplate, data);
71
    Templates.appendNodeContents(parent, html, js);
72
    const overlay = parent.querySelector(selectors.OVERLAY);
73
    rePositionPreviewInfoElement(overlay);
74
    init();
75
    return overlay;
76
};
77
 
78
/**
79
 * Adds an overlay to a specific page element.
80
 *
81
 * @param {HTMLElement} overlay the parent object
82
 */
83
export const removeOverlay = (overlay) => {
84
    if (!overlay || !overlay.parentNode) {
85
        return;
86
    }
87
    // Remove any forced parentNode position.
88
    if (overlay.dataset?.overlayPosition) {
89
        delete overlay.parentNode.style.position;
90
    }
91
    overlay.parentNode.removeChild(overlay);
92
};
93
 
94
export const removeAllOverlays = () => {
95
    document.querySelectorAll(selectors.OVERLAY).forEach(
96
        (overlay) => {
97
            removeOverlay(overlay);
98
        }
99
    );
100
};
101
 
102
/**
103
 * Re-position the preview information element by calculating the section position.
104
 *
105
 * @param {Object} overlay the overlay element.
106
 */
107
const rePositionPreviewInfoElement = function(overlay) {
108
    if (!overlay) {
109
        throw new Error('Inexistent overlay element');
110
    }
111
    // Add relative position to the parent object.
112
    if (!overlay.parentNode?.style?.position) {
113
        overlay.parentNode.style.position = 'relative';
114
        overlay.dataset.overlayPosition = "true";
115
    }
116
    // Get the element to reposition.
117
    const target = overlay.querySelector(selectors.REPOSITION);
118
    if (!target) {
119
        return;
120
    }
121
    // Get the new bounds.
122
    const rect = overlay.getBoundingClientRect();
123
    const sectionHeight = parseInt(window.getComputedStyle(overlay).height, 10);
124
    const sectionOffset = rect.top;
125
    const previewHeight = parseInt(window.getComputedStyle(target).height, 10) +
126
        (2 * parseInt(window.getComputedStyle(target).padding, 10));
127
    // Calculate the new target position.
128
    let top, bottom;
129
    if (sectionOffset < 0) {
130
        if (sectionHeight + sectionOffset >= previewHeight) {
131
            // We have enough space here, just stick the preview to the top.
132
            let offSetTop = 0 - sectionOffset;
133
            const navBar = document.querySelector(selectors.NAVBAR);
134
            if (navBar) {
135
                offSetTop = offSetTop + navBar.offsetHeight;
136
            }
137
            top = offSetTop + 'px';
138
            bottom = 'unset';
139
        } else {
140
            // We do not have enough space here, just stick the preview to the bottom.
141
            top = 'unset';
142
            bottom = 0;
143
        }
144
    } else {
145
        top = 0;
146
        bottom = 'unset';
147
    }
148
 
149
    target.style.top = top;
150
    target.style.bottom = bottom;
151
};
152
 
153
// Update overlays when the page scrolls.
154
const init = () => {
155
    if (isInitialized) {
156
        return;
157
    }
158
    // Add scroll events.
159
    document.addEventListener('scroll', () => {
160
        document.querySelectorAll(selectors.OVERLAY).forEach(
161
            (overlay) => {
162
                rePositionPreviewInfoElement(overlay);
163
            }
164
        );
165
    }, true);
166
};