988aafce39
Stop ignoring amd/build so generated modules ship with the plugin ZIP. Co-authored-by: Cursor <cursoragent@cursor.com>
419 lines
14 KiB
JavaScript
419 lines
14 KiB
JavaScript
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
/**
|
|
* @module mod_meetletacbfree/settings_access_reset
|
|
* @copyright 2026 MeetLet <info@meetlet.net>
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
define(['core/ajax', 'mod_meetletacbfree/meetlet_modal_scroll'], function(Ajax, MeetletModalScroll) {
|
|
'use strict';
|
|
|
|
var modalState = {
|
|
open: false,
|
|
resolve: null,
|
|
lastFocus: null,
|
|
onKey: null,
|
|
mode: 'confirm',
|
|
pendingSection: null,
|
|
};
|
|
|
|
function modalScroll() {
|
|
return MeetletModalScroll;
|
|
}
|
|
|
|
function byId(id) {
|
|
return document.getElementById(id);
|
|
}
|
|
|
|
function parseStrings(root) {
|
|
if (!root) {
|
|
return {};
|
|
}
|
|
try {
|
|
return JSON.parse(root.getAttribute('data-strings') || '{}') || {};
|
|
} catch (e) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function sectionContainer(section) {
|
|
if (section === 'rolemapping') {
|
|
return document.querySelector('[data-meetlet-rolemapping]');
|
|
}
|
|
if (section === 'featureaccess') {
|
|
return document.querySelector('[data-meetlet-access-matrix]');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function applyRoleMatrix(container, matrix) {
|
|
if (!container || !matrix) {
|
|
return;
|
|
}
|
|
Object.keys(matrix).forEach(function (rowkey) {
|
|
var row = container.querySelector('[data-role-row="' + rowkey + '"]');
|
|
if (!row) {
|
|
return;
|
|
}
|
|
var val = matrix[rowkey];
|
|
var radio = row.querySelector('input[type="radio"][value="' + val + '"]');
|
|
if (radio) {
|
|
radio.checked = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
function applyFeatureMatrix(container, matrix) {
|
|
if (!container || !matrix) {
|
|
return;
|
|
}
|
|
container.querySelectorAll('.meetlet-access-check').forEach(function (cb) {
|
|
var role = cb.getAttribute('data-role');
|
|
var feature = cb.getAttribute('data-feature');
|
|
var on = matrix[role] && matrix[role][feature];
|
|
cb.checked = !!on;
|
|
});
|
|
}
|
|
|
|
function renderModalMessage(bodyEl, mode, message) {
|
|
if (!bodyEl) {
|
|
return;
|
|
}
|
|
bodyEl.textContent = '';
|
|
if (mode === 'busy') {
|
|
var wrap = document.createElement('div');
|
|
wrap.className = 'meetlet-modal__busy';
|
|
var spinner = document.createElement('div');
|
|
spinner.className = 'meetlet-modal__spinner';
|
|
spinner.setAttribute('aria-hidden', 'true');
|
|
var text = document.createElement('p');
|
|
text.className = 'meetlet-modal__lead';
|
|
text.textContent = message || '';
|
|
wrap.appendChild(spinner);
|
|
wrap.appendChild(text);
|
|
bodyEl.appendChild(wrap);
|
|
return;
|
|
}
|
|
var lead = document.createElement('p');
|
|
lead.className = 'meetlet-modal__lead';
|
|
lead.textContent = message || '';
|
|
bodyEl.appendChild(lead);
|
|
}
|
|
|
|
function closeModal(confirmed) {
|
|
var modal = byId('meetlet-access-reset-modal');
|
|
if (!modal || !modalState.open) {
|
|
return;
|
|
}
|
|
modal.setAttribute('hidden', 'hidden');
|
|
modal.style.display = 'none';
|
|
modal.classList.remove('is-busy');
|
|
modal.setAttribute('aria-busy', 'false');
|
|
modalState.open = false;
|
|
modalScroll().unlock();
|
|
if (modalState.onKey) {
|
|
document.removeEventListener('keydown', modalState.onKey, true);
|
|
modalState.onKey = null;
|
|
}
|
|
var resolve = modalState.resolve;
|
|
modalState.resolve = null;
|
|
modalState.pendingSection = null;
|
|
if (resolve) {
|
|
resolve(!!confirmed);
|
|
}
|
|
if (modalState.lastFocus && typeof modalState.lastFocus.focus === 'function') {
|
|
modalScroll().focusNoScroll(modalState.lastFocus);
|
|
}
|
|
modalState.lastFocus = null;
|
|
}
|
|
|
|
function openModal(opts) {
|
|
opts = opts || {};
|
|
var mode = opts.mode === 'alert' ? 'alert' : (opts.mode === 'busy' ? 'busy' : 'confirm');
|
|
var modal = byId('meetlet-access-reset-modal');
|
|
if (!modal) {
|
|
return Promise.resolve(false);
|
|
}
|
|
|
|
if (modalState.open) {
|
|
if (modalState.mode === 'busy' && mode === 'alert') {
|
|
modalState.resolve = null;
|
|
} else {
|
|
closeModal(false);
|
|
}
|
|
}
|
|
|
|
var str = opts.strings || {};
|
|
var titleEl = byId('meetlet-access-reset-modal-title');
|
|
var bodyEl = byId('meetlet-access-reset-modal-body');
|
|
var confirmBtn = byId('meetlet-access-reset-modal-confirm');
|
|
var cancelBtn = modal.querySelector('[data-meetlet-modal-dismiss].btn');
|
|
var closeBtn = modal.querySelector('.meetlet-modal__close');
|
|
|
|
if (titleEl) {
|
|
titleEl.textContent = opts.title || '';
|
|
}
|
|
renderModalMessage(bodyEl, mode, opts.message || '');
|
|
modal.classList.toggle('is-busy', mode === 'busy');
|
|
modal.setAttribute('aria-busy', mode === 'busy' ? 'true' : 'false');
|
|
|
|
if (confirmBtn) {
|
|
confirmBtn.textContent = opts.confirmLabel || (mode === 'alert' ? (str.modal_ok || 'OK') : (str.confirm_reset || 'Reset'));
|
|
confirmBtn.className = opts.confirmClass || (mode === 'alert' ? 'btn btn-primary' : 'btn btn-outline-danger');
|
|
confirmBtn.hidden = mode === 'busy';
|
|
confirmBtn.disabled = mode === 'busy';
|
|
confirmBtn.style.display = mode === 'busy' ? 'none' : '';
|
|
}
|
|
if (cancelBtn) {
|
|
cancelBtn.textContent = str.modal_cancel || 'Cancel';
|
|
cancelBtn.hidden = mode !== 'confirm';
|
|
cancelBtn.style.display = mode !== 'confirm' ? 'none' : '';
|
|
}
|
|
if (closeBtn) {
|
|
if (str.modal_close) {
|
|
closeBtn.setAttribute('aria-label', str.modal_close);
|
|
}
|
|
closeBtn.hidden = mode === 'busy';
|
|
closeBtn.style.display = mode === 'busy' ? 'none' : '';
|
|
}
|
|
|
|
var dir = document.documentElement.getAttribute('dir') || 'ltr';
|
|
modal.setAttribute('dir', dir);
|
|
var dialog = modal.querySelector('.meetlet-modal__dialog');
|
|
if (dialog) {
|
|
dialog.setAttribute('dir', dir);
|
|
}
|
|
|
|
modalState.mode = mode;
|
|
if (!modalState.lastFocus) {
|
|
modalState.lastFocus = document.activeElement;
|
|
}
|
|
modalScroll().lock();
|
|
modal.removeAttribute('hidden');
|
|
modal.style.display = 'flex';
|
|
modalState.open = true;
|
|
|
|
if (modalState.onKey) {
|
|
document.removeEventListener('keydown', modalState.onKey, true);
|
|
}
|
|
modalState.onKey = function (e) {
|
|
if (!modalState.open) {
|
|
return;
|
|
}
|
|
if (e.key === 'Escape') {
|
|
if (modalState.mode === 'busy') {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
closeModal(false);
|
|
return;
|
|
}
|
|
if (e.key !== 'Tab' || modalState.mode === 'busy') {
|
|
return;
|
|
}
|
|
var focusable = modal.querySelectorAll(
|
|
'button:not([disabled]):not([hidden]), [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
);
|
|
var visible = [];
|
|
for (var i = 0; i < focusable.length; i++) {
|
|
if (focusable[i].style.display === 'none') {
|
|
continue;
|
|
}
|
|
visible.push(focusable[i]);
|
|
}
|
|
if (!visible.length) {
|
|
return;
|
|
}
|
|
var first = visible[0];
|
|
var last = visible[visible.length - 1];
|
|
if (e.shiftKey && document.activeElement === first) {
|
|
e.preventDefault();
|
|
modalScroll().focusNoScroll(last);
|
|
} else if (!e.shiftKey && document.activeElement === last) {
|
|
e.preventDefault();
|
|
modalScroll().focusNoScroll(first);
|
|
}
|
|
};
|
|
document.addEventListener('keydown', modalState.onKey, true);
|
|
|
|
if (mode !== 'busy' && confirmBtn && !confirmBtn.hidden) {
|
|
window.setTimeout(function () {
|
|
modalScroll().focusNoScroll(confirmBtn);
|
|
}, 20);
|
|
}
|
|
|
|
if (mode === 'busy') {
|
|
modalState.resolve = null;
|
|
return Promise.resolve(true);
|
|
}
|
|
|
|
return new Promise(function (resolve) {
|
|
modalState.resolve = resolve;
|
|
});
|
|
}
|
|
|
|
function bindModal() {
|
|
var modal = byId('meetlet-access-reset-modal');
|
|
if (!modal || modal.dataset.bound === '1') {
|
|
return;
|
|
}
|
|
modal.dataset.bound = '1';
|
|
modal.addEventListener('click', function (e) {
|
|
if (modalState.mode === 'busy') {
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
if (e.target.closest('[data-meetlet-modal-dismiss]')) {
|
|
e.preventDefault();
|
|
closeModal(false);
|
|
}
|
|
});
|
|
var confirmBtn = byId('meetlet-access-reset-modal-confirm');
|
|
if (confirmBtn) {
|
|
confirmBtn.addEventListener('click', function (e) {
|
|
e.preventDefault();
|
|
if (modalState.mode === 'busy') {
|
|
return;
|
|
}
|
|
closeModal(true);
|
|
});
|
|
}
|
|
}
|
|
|
|
function postReset(root, section, str) {
|
|
return Ajax.call([{
|
|
methodname: 'mod_meetletacbfree_reset_access_settings',
|
|
args: {section: section}
|
|
}])[0].then(function(data) {
|
|
if (data && data.matrixjson) {
|
|
try {
|
|
data.matrix = JSON.parse(data.matrixjson);
|
|
} catch (e) {
|
|
data.matrix = null;
|
|
}
|
|
}
|
|
return data;
|
|
});
|
|
}
|
|
|
|
function confirmCopy(section, str) {
|
|
if (section === 'rolemapping') {
|
|
return {
|
|
title: str.confirm_rolemapping_title || '',
|
|
message: str.confirm_rolemapping || '',
|
|
};
|
|
}
|
|
return {
|
|
title: str.confirm_featureaccess_title || '',
|
|
message: str.confirm_featureaccess || '',
|
|
};
|
|
}
|
|
|
|
function successCopy(section, str, serverMessage) {
|
|
if (serverMessage) {
|
|
return serverMessage;
|
|
}
|
|
if (section === 'rolemapping') {
|
|
return str.success_rolemapping || '';
|
|
}
|
|
return str.success_featureaccess || '';
|
|
}
|
|
|
|
function handleResetClick(btn, root, str) {
|
|
var section = btn.getAttribute('data-meetlet-reset-section') || '';
|
|
if (!section) {
|
|
return;
|
|
}
|
|
var copy = confirmCopy(section, str);
|
|
openModal({
|
|
mode: 'confirm',
|
|
strings: str,
|
|
title: copy.title,
|
|
message: copy.message,
|
|
confirmClass: 'btn btn-outline-danger',
|
|
}).then(function (confirmed) {
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
openModal({
|
|
mode: 'busy',
|
|
strings: str,
|
|
title: str.busy || '',
|
|
message: str.busy || '',
|
|
});
|
|
btn.disabled = true;
|
|
postReset(root, section, str).then(function (data) {
|
|
btn.disabled = false;
|
|
if (!data || !data.success) {
|
|
openModal({
|
|
mode: 'alert',
|
|
strings: str,
|
|
title: str.error || 'Error',
|
|
message: (data && data.error) || str.error || 'Error',
|
|
confirmClass: 'btn btn-primary',
|
|
});
|
|
return;
|
|
}
|
|
var container = sectionContainer(section);
|
|
if (section === 'rolemapping') {
|
|
applyRoleMatrix(container, data.matrix);
|
|
} else {
|
|
applyFeatureMatrix(container, data.matrix);
|
|
}
|
|
openModal({
|
|
mode: 'alert',
|
|
strings: str,
|
|
title: copy.title,
|
|
message: successCopy(section, str, data.message),
|
|
confirmClass: 'btn btn-primary',
|
|
});
|
|
}).catch(function () {
|
|
btn.disabled = false;
|
|
openModal({
|
|
mode: 'alert',
|
|
strings: str,
|
|
title: str.error || 'Error',
|
|
message: str.error || 'Error',
|
|
confirmClass: 'btn btn-primary',
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function init() {
|
|
var root = byId('meetlet-access-reset-root');
|
|
if (!root) {
|
|
return;
|
|
}
|
|
var str = parseStrings(root);
|
|
bindModal();
|
|
document.addEventListener('click', function (e) {
|
|
var btn = e.target.closest('[data-meetlet-reset-section]');
|
|
if (!btn || btn.disabled) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
handleResetClick(btn, root, str);
|
|
});
|
|
}
|
|
|
|
return {init: init};
|
|
});
|