6772 lines
295 KiB
JavaScript
6772 lines
295 KiB
JavaScript
import { S as Services, a as addressToEmoji } from './index-C1Gp83RD.mjs';
|
||
import { g as getCorrectDOM } from './document.utils-SxcZpxzG.mjs';
|
||
|
||
/*!
|
||
* sweetalert2 v11.22.4
|
||
* Released under the MIT License.
|
||
*/
|
||
function _assertClassBrand(e, t, n) {
|
||
if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
|
||
throw new TypeError("Private element is not present on this object");
|
||
}
|
||
function _checkPrivateRedeclaration(e, t) {
|
||
if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
|
||
}
|
||
function _classPrivateFieldGet2(s, a) {
|
||
return s.get(_assertClassBrand(s, a));
|
||
}
|
||
function _classPrivateFieldInitSpec(e, t, a) {
|
||
_checkPrivateRedeclaration(e, t), t.set(e, a);
|
||
}
|
||
function _classPrivateFieldSet2(s, a, r) {
|
||
return s.set(_assertClassBrand(s, a), r), r;
|
||
}
|
||
|
||
const RESTORE_FOCUS_TIMEOUT = 100;
|
||
|
||
/** @type {GlobalState} */
|
||
const globalState = {};
|
||
const focusPreviousActiveElement = () => {
|
||
if (globalState.previousActiveElement instanceof HTMLElement) {
|
||
globalState.previousActiveElement.focus();
|
||
globalState.previousActiveElement = null;
|
||
} else if (document.body) {
|
||
document.body.focus();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Restore previous active (focused) element
|
||
*
|
||
* @param {boolean} returnFocus
|
||
* @returns {Promise<void>}
|
||
*/
|
||
const restoreActiveElement = returnFocus => {
|
||
return new Promise(resolve => {
|
||
if (!returnFocus) {
|
||
return resolve();
|
||
}
|
||
const x = window.scrollX;
|
||
const y = window.scrollY;
|
||
globalState.restoreFocusTimeout = setTimeout(() => {
|
||
focusPreviousActiveElement();
|
||
resolve();
|
||
}, RESTORE_FOCUS_TIMEOUT); // issues/900
|
||
|
||
window.scrollTo(x, y);
|
||
});
|
||
};
|
||
|
||
const swalPrefix = 'swal2-';
|
||
|
||
/**
|
||
* @typedef {Record<SwalClass, string>} SwalClasses
|
||
*/
|
||
|
||
/**
|
||
* @typedef {'success' | 'warning' | 'info' | 'question' | 'error'} SwalIcon
|
||
* @typedef {Record<SwalIcon, string>} SwalIcons
|
||
*/
|
||
|
||
/** @type {SwalClass[]} */
|
||
const classNames = ['container', 'shown', 'height-auto', 'iosfix', 'popup', 'modal', 'no-backdrop', 'no-transition', 'toast', 'toast-shown', 'show', 'hide', 'close', 'title', 'html-container', 'actions', 'confirm', 'deny', 'cancel', 'footer', 'icon', 'icon-content', 'image', 'input', 'file', 'range', 'select', 'radio', 'checkbox', 'label', 'textarea', 'inputerror', 'input-label', 'validation-message', 'progress-steps', 'active-progress-step', 'progress-step', 'progress-step-line', 'loader', 'loading', 'styled', 'top', 'top-start', 'top-end', 'top-left', 'top-right', 'center', 'center-start', 'center-end', 'center-left', 'center-right', 'bottom', 'bottom-start', 'bottom-end', 'bottom-left', 'bottom-right', 'grow-row', 'grow-column', 'grow-fullscreen', 'rtl', 'timer-progress-bar', 'timer-progress-bar-container', 'scrollbar-measure', 'icon-success', 'icon-warning', 'icon-info', 'icon-question', 'icon-error', 'draggable', 'dragging'];
|
||
const swalClasses = classNames.reduce((acc, className) => {
|
||
acc[className] = swalPrefix + className;
|
||
return acc;
|
||
}, /** @type {SwalClasses} */{});
|
||
|
||
/** @type {SwalIcon[]} */
|
||
const icons = ['success', 'warning', 'info', 'question', 'error'];
|
||
const iconTypes = icons.reduce((acc, icon) => {
|
||
acc[icon] = swalPrefix + icon;
|
||
return acc;
|
||
}, /** @type {SwalIcons} */{});
|
||
|
||
const consolePrefix = 'SweetAlert2:';
|
||
|
||
/**
|
||
* Capitalize the first letter of a string
|
||
*
|
||
* @param {string} str
|
||
* @returns {string}
|
||
*/
|
||
const capitalizeFirstLetter = str => str.charAt(0).toUpperCase() + str.slice(1);
|
||
|
||
/**
|
||
* Standardize console warnings
|
||
*
|
||
* @param {string | string[]} message
|
||
*/
|
||
const warn = message => {
|
||
console.warn(`${consolePrefix} ${typeof message === 'object' ? message.join(' ') : message}`);
|
||
};
|
||
|
||
/**
|
||
* Standardize console errors
|
||
*
|
||
* @param {string} message
|
||
*/
|
||
const error = message => {
|
||
console.error(`${consolePrefix} ${message}`);
|
||
};
|
||
|
||
/**
|
||
* Private global state for `warnOnce`
|
||
*
|
||
* @type {string[]}
|
||
* @private
|
||
*/
|
||
const previousWarnOnceMessages = [];
|
||
|
||
/**
|
||
* Show a console warning, but only if it hasn't already been shown
|
||
*
|
||
* @param {string} message
|
||
*/
|
||
const warnOnce = message => {
|
||
if (!previousWarnOnceMessages.includes(message)) {
|
||
previousWarnOnceMessages.push(message);
|
||
warn(message);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Show a one-time console warning about deprecated params/methods
|
||
*
|
||
* @param {string} deprecatedParam
|
||
* @param {string?} useInstead
|
||
*/
|
||
const warnAboutDeprecation = (deprecatedParam, useInstead = null) => {
|
||
warnOnce(`"${deprecatedParam}" is deprecated and will be removed in the next major release.${useInstead ? ` Use "${useInstead}" instead.` : ''}`);
|
||
};
|
||
|
||
/**
|
||
* If `arg` is a function, call it (with no arguments or context) and return the result.
|
||
* Otherwise, just pass the value through
|
||
*
|
||
* @param {Function | any} arg
|
||
* @returns {any}
|
||
*/
|
||
const callIfFunction = arg => typeof arg === 'function' ? arg() : arg;
|
||
|
||
/**
|
||
* @param {any} arg
|
||
* @returns {boolean}
|
||
*/
|
||
const hasToPromiseFn = arg => arg && typeof arg.toPromise === 'function';
|
||
|
||
/**
|
||
* @param {any} arg
|
||
* @returns {Promise<any>}
|
||
*/
|
||
const asPromise = arg => hasToPromiseFn(arg) ? arg.toPromise() : Promise.resolve(arg);
|
||
|
||
/**
|
||
* @param {any} arg
|
||
* @returns {boolean}
|
||
*/
|
||
const isPromise = arg => arg && Promise.resolve(arg) === arg;
|
||
|
||
/**
|
||
* Gets the popup container which contains the backdrop and the popup itself.
|
||
*
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getContainer = () => document.body.querySelector(`.${swalClasses.container}`);
|
||
|
||
/**
|
||
* @param {string} selectorString
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const elementBySelector = selectorString => {
|
||
const container = getContainer();
|
||
return container ? container.querySelector(selectorString) : null;
|
||
};
|
||
|
||
/**
|
||
* @param {string} className
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const elementByClass = className => {
|
||
return elementBySelector(`.${className}`);
|
||
};
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getPopup = () => elementByClass(swalClasses.popup);
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getIcon = () => elementByClass(swalClasses.icon);
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getIconContent = () => elementByClass(swalClasses['icon-content']);
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getTitle = () => elementByClass(swalClasses.title);
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getHtmlContainer = () => elementByClass(swalClasses['html-container']);
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getImage = () => elementByClass(swalClasses.image);
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getProgressSteps = () => elementByClass(swalClasses['progress-steps']);
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getValidationMessage = () => elementByClass(swalClasses['validation-message']);
|
||
|
||
/**
|
||
* @returns {HTMLButtonElement | null}
|
||
*/
|
||
const getConfirmButton = () => (/** @type {HTMLButtonElement} */elementBySelector(`.${swalClasses.actions} .${swalClasses.confirm}`));
|
||
|
||
/**
|
||
* @returns {HTMLButtonElement | null}
|
||
*/
|
||
const getCancelButton = () => (/** @type {HTMLButtonElement} */elementBySelector(`.${swalClasses.actions} .${swalClasses.cancel}`));
|
||
|
||
/**
|
||
* @returns {HTMLButtonElement | null}
|
||
*/
|
||
const getDenyButton = () => (/** @type {HTMLButtonElement} */elementBySelector(`.${swalClasses.actions} .${swalClasses.deny}`));
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getInputLabel = () => elementByClass(swalClasses['input-label']);
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getLoader = () => elementBySelector(`.${swalClasses.loader}`);
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getActions = () => elementByClass(swalClasses.actions);
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getFooter = () => elementByClass(swalClasses.footer);
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getTimerProgressBar = () => elementByClass(swalClasses['timer-progress-bar']);
|
||
|
||
/**
|
||
* @returns {HTMLElement | null}
|
||
*/
|
||
const getCloseButton = () => elementByClass(swalClasses.close);
|
||
|
||
// https://github.com/jkup/focusable/blob/master/index.js
|
||
const focusable = `
|
||
a[href],
|
||
area[href],
|
||
input:not([disabled]),
|
||
select:not([disabled]),
|
||
textarea:not([disabled]),
|
||
button:not([disabled]),
|
||
iframe,
|
||
object,
|
||
embed,
|
||
[tabindex="0"],
|
||
[contenteditable],
|
||
audio[controls],
|
||
video[controls],
|
||
summary
|
||
`;
|
||
/**
|
||
* @returns {HTMLElement[]}
|
||
*/
|
||
const getFocusableElements = () => {
|
||
const popup = getPopup();
|
||
if (!popup) {
|
||
return [];
|
||
}
|
||
/** @type {NodeListOf<HTMLElement>} */
|
||
const focusableElementsWithTabindex = popup.querySelectorAll('[tabindex]:not([tabindex="-1"]):not([tabindex="0"])');
|
||
const focusableElementsWithTabindexSorted = Array.from(focusableElementsWithTabindex)
|
||
// sort according to tabindex
|
||
.sort((a, b) => {
|
||
const tabindexA = parseInt(a.getAttribute('tabindex') || '0');
|
||
const tabindexB = parseInt(b.getAttribute('tabindex') || '0');
|
||
if (tabindexA > tabindexB) {
|
||
return 1;
|
||
} else if (tabindexA < tabindexB) {
|
||
return -1;
|
||
}
|
||
return 0;
|
||
});
|
||
|
||
/** @type {NodeListOf<HTMLElement>} */
|
||
const otherFocusableElements = popup.querySelectorAll(focusable);
|
||
const otherFocusableElementsFiltered = Array.from(otherFocusableElements).filter(el => el.getAttribute('tabindex') !== '-1');
|
||
return [...new Set(focusableElementsWithTabindexSorted.concat(otherFocusableElementsFiltered))].filter(el => isVisible$1(el));
|
||
};
|
||
|
||
/**
|
||
* @returns {boolean}
|
||
*/
|
||
const isModal = () => {
|
||
return hasClass(document.body, swalClasses.shown) && !hasClass(document.body, swalClasses['toast-shown']) && !hasClass(document.body, swalClasses['no-backdrop']);
|
||
};
|
||
|
||
/**
|
||
* @returns {boolean}
|
||
*/
|
||
const isToast = () => {
|
||
const popup = getPopup();
|
||
if (!popup) {
|
||
return false;
|
||
}
|
||
return hasClass(popup, swalClasses.toast);
|
||
};
|
||
|
||
/**
|
||
* @returns {boolean}
|
||
*/
|
||
const isLoading = () => {
|
||
const popup = getPopup();
|
||
if (!popup) {
|
||
return false;
|
||
}
|
||
return popup.hasAttribute('data-loading');
|
||
};
|
||
|
||
/**
|
||
* Securely set innerHTML of an element
|
||
* https://github.com/sweetalert2/sweetalert2/issues/1926
|
||
*
|
||
* @param {HTMLElement} elem
|
||
* @param {string} html
|
||
*/
|
||
const setInnerHtml = (elem, html) => {
|
||
elem.textContent = '';
|
||
if (html) {
|
||
const parser = new DOMParser();
|
||
const parsed = parser.parseFromString(html, `text/html`);
|
||
const head = parsed.querySelector('head');
|
||
if (head) {
|
||
Array.from(head.childNodes).forEach(child => {
|
||
elem.appendChild(child);
|
||
});
|
||
}
|
||
const body = parsed.querySelector('body');
|
||
if (body) {
|
||
Array.from(body.childNodes).forEach(child => {
|
||
if (child instanceof HTMLVideoElement || child instanceof HTMLAudioElement) {
|
||
elem.appendChild(child.cloneNode(true)); // https://github.com/sweetalert2/sweetalert2/issues/2507
|
||
} else {
|
||
elem.appendChild(child);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} elem
|
||
* @param {string} className
|
||
* @returns {boolean}
|
||
*/
|
||
const hasClass = (elem, className) => {
|
||
if (!className) {
|
||
return false;
|
||
}
|
||
const classList = className.split(/\s+/);
|
||
for (let i = 0; i < classList.length; i++) {
|
||
if (!elem.classList.contains(classList[i])) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} elem
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const removeCustomClasses = (elem, params) => {
|
||
Array.from(elem.classList).forEach(className => {
|
||
if (!Object.values(swalClasses).includes(className) && !Object.values(iconTypes).includes(className) && !Object.values(params.showClass || {}).includes(className)) {
|
||
elem.classList.remove(className);
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} elem
|
||
* @param {SweetAlertOptions} params
|
||
* @param {string} className
|
||
*/
|
||
const applyCustomClass = (elem, params, className) => {
|
||
removeCustomClasses(elem, params);
|
||
if (!params.customClass) {
|
||
return;
|
||
}
|
||
const customClass = params.customClass[(/** @type {keyof SweetAlertCustomClass} */className)];
|
||
if (!customClass) {
|
||
return;
|
||
}
|
||
if (typeof customClass !== 'string' && !customClass.forEach) {
|
||
warn(`Invalid type of customClass.${className}! Expected string or iterable object, got "${typeof customClass}"`);
|
||
return;
|
||
}
|
||
addClass(elem, customClass);
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} popup
|
||
* @param {import('./renderers/renderInput').InputClass | SweetAlertInput} inputClass
|
||
* @returns {HTMLInputElement | null}
|
||
*/
|
||
const getInput$1 = (popup, inputClass) => {
|
||
if (!inputClass) {
|
||
return null;
|
||
}
|
||
switch (inputClass) {
|
||
case 'select':
|
||
case 'textarea':
|
||
case 'file':
|
||
return popup.querySelector(`.${swalClasses.popup} > .${swalClasses[inputClass]}`);
|
||
case 'checkbox':
|
||
return popup.querySelector(`.${swalClasses.popup} > .${swalClasses.checkbox} input`);
|
||
case 'radio':
|
||
return popup.querySelector(`.${swalClasses.popup} > .${swalClasses.radio} input:checked`) || popup.querySelector(`.${swalClasses.popup} > .${swalClasses.radio} input:first-child`);
|
||
case 'range':
|
||
return popup.querySelector(`.${swalClasses.popup} > .${swalClasses.range} input`);
|
||
default:
|
||
return popup.querySelector(`.${swalClasses.popup} > .${swalClasses.input}`);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement} input
|
||
*/
|
||
const focusInput = input => {
|
||
input.focus();
|
||
|
||
// place cursor at end of text in text input
|
||
if (input.type !== 'file') {
|
||
// http://stackoverflow.com/a/2345915
|
||
const val = input.value;
|
||
input.value = '';
|
||
input.value = val;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement | HTMLElement[] | null} target
|
||
* @param {string | string[] | readonly string[] | undefined} classList
|
||
* @param {boolean} condition
|
||
*/
|
||
const toggleClass = (target, classList, condition) => {
|
||
if (!target || !classList) {
|
||
return;
|
||
}
|
||
if (typeof classList === 'string') {
|
||
classList = classList.split(/\s+/).filter(Boolean);
|
||
}
|
||
classList.forEach(className => {
|
||
if (Array.isArray(target)) {
|
||
target.forEach(elem => {
|
||
if (condition) {
|
||
elem.classList.add(className);
|
||
} else {
|
||
elem.classList.remove(className);
|
||
}
|
||
});
|
||
} else {
|
||
if (condition) {
|
||
target.classList.add(className);
|
||
} else {
|
||
target.classList.remove(className);
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement | HTMLElement[] | null} target
|
||
* @param {string | string[] | readonly string[] | undefined} classList
|
||
*/
|
||
const addClass = (target, classList) => {
|
||
toggleClass(target, classList, true);
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement | HTMLElement[] | null} target
|
||
* @param {string | string[] | readonly string[] | undefined} classList
|
||
*/
|
||
const removeClass = (target, classList) => {
|
||
toggleClass(target, classList, false);
|
||
};
|
||
|
||
/**
|
||
* Get direct child of an element by class name
|
||
*
|
||
* @param {HTMLElement} elem
|
||
* @param {string} className
|
||
* @returns {HTMLElement | undefined}
|
||
*/
|
||
const getDirectChildByClass = (elem, className) => {
|
||
const children = Array.from(elem.children);
|
||
for (let i = 0; i < children.length; i++) {
|
||
const child = children[i];
|
||
if (child instanceof HTMLElement && hasClass(child, className)) {
|
||
return child;
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} elem
|
||
* @param {string} property
|
||
* @param {*} value
|
||
*/
|
||
const applyNumericalStyle = (elem, property, value) => {
|
||
if (value === `${parseInt(value)}`) {
|
||
value = parseInt(value);
|
||
}
|
||
if (value || parseInt(value) === 0) {
|
||
elem.style.setProperty(property, typeof value === 'number' ? `${value}px` : value);
|
||
} else {
|
||
elem.style.removeProperty(property);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement | null} elem
|
||
* @param {string} display
|
||
*/
|
||
const show = (elem, display = 'flex') => {
|
||
if (!elem) {
|
||
return;
|
||
}
|
||
elem.style.display = display;
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement | null} elem
|
||
*/
|
||
const hide = elem => {
|
||
if (!elem) {
|
||
return;
|
||
}
|
||
elem.style.display = 'none';
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement | null} elem
|
||
* @param {string} display
|
||
*/
|
||
const showWhenInnerHtmlPresent = (elem, display = 'block') => {
|
||
if (!elem) {
|
||
return;
|
||
}
|
||
new MutationObserver(() => {
|
||
toggle(elem, elem.innerHTML, display);
|
||
}).observe(elem, {
|
||
childList: true,
|
||
subtree: true
|
||
});
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} parent
|
||
* @param {string} selector
|
||
* @param {string} property
|
||
* @param {string} value
|
||
*/
|
||
const setStyle = (parent, selector, property, value) => {
|
||
/** @type {HTMLElement | null} */
|
||
const el = parent.querySelector(selector);
|
||
if (el) {
|
||
el.style.setProperty(property, value);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} elem
|
||
* @param {any} condition
|
||
* @param {string} display
|
||
*/
|
||
const toggle = (elem, condition, display = 'flex') => {
|
||
if (condition) {
|
||
show(elem, display);
|
||
} else {
|
||
hide(elem);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* borrowed from jquery $(elem).is(':visible') implementation
|
||
*
|
||
* @param {HTMLElement | null} elem
|
||
* @returns {boolean}
|
||
*/
|
||
const isVisible$1 = elem => !!(elem && (elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length));
|
||
|
||
/**
|
||
* @returns {boolean}
|
||
*/
|
||
const allButtonsAreHidden = () => !isVisible$1(getConfirmButton()) && !isVisible$1(getDenyButton()) && !isVisible$1(getCancelButton());
|
||
|
||
/**
|
||
* @param {HTMLElement} elem
|
||
* @returns {boolean}
|
||
*/
|
||
const isScrollable = elem => !!(elem.scrollHeight > elem.clientHeight);
|
||
|
||
/**
|
||
* @param {HTMLElement} element
|
||
* @param {HTMLElement} stopElement
|
||
* @returns {boolean}
|
||
*/
|
||
const selfOrParentIsScrollable = (element, stopElement) => {
|
||
let parent = element;
|
||
while (parent && parent !== stopElement) {
|
||
if (isScrollable(parent)) {
|
||
return true;
|
||
}
|
||
parent = parent.parentElement;
|
||
}
|
||
return false;
|
||
};
|
||
|
||
/**
|
||
* borrowed from https://stackoverflow.com/a/46352119
|
||
*
|
||
* @param {HTMLElement} elem
|
||
* @returns {boolean}
|
||
*/
|
||
const hasCssAnimation = elem => {
|
||
const style = window.getComputedStyle(elem);
|
||
const animDuration = parseFloat(style.getPropertyValue('animation-duration') || '0');
|
||
const transDuration = parseFloat(style.getPropertyValue('transition-duration') || '0');
|
||
return animDuration > 0 || transDuration > 0;
|
||
};
|
||
|
||
/**
|
||
* @param {number} timer
|
||
* @param {boolean} reset
|
||
*/
|
||
const animateTimerProgressBar = (timer, reset = false) => {
|
||
const timerProgressBar = getTimerProgressBar();
|
||
if (!timerProgressBar) {
|
||
return;
|
||
}
|
||
if (isVisible$1(timerProgressBar)) {
|
||
if (reset) {
|
||
timerProgressBar.style.transition = 'none';
|
||
timerProgressBar.style.width = '100%';
|
||
}
|
||
setTimeout(() => {
|
||
timerProgressBar.style.transition = `width ${timer / 1000}s linear`;
|
||
timerProgressBar.style.width = '0%';
|
||
}, 10);
|
||
}
|
||
};
|
||
const stopTimerProgressBar = () => {
|
||
const timerProgressBar = getTimerProgressBar();
|
||
if (!timerProgressBar) {
|
||
return;
|
||
}
|
||
const timerProgressBarWidth = parseInt(window.getComputedStyle(timerProgressBar).width);
|
||
timerProgressBar.style.removeProperty('transition');
|
||
timerProgressBar.style.width = '100%';
|
||
const timerProgressBarFullWidth = parseInt(window.getComputedStyle(timerProgressBar).width);
|
||
const timerProgressBarPercent = timerProgressBarWidth / timerProgressBarFullWidth * 100;
|
||
timerProgressBar.style.width = `${timerProgressBarPercent}%`;
|
||
};
|
||
|
||
/**
|
||
* Detect Node env
|
||
*
|
||
* @returns {boolean}
|
||
*/
|
||
const isNodeEnv = () => typeof window === 'undefined' || typeof document === 'undefined';
|
||
|
||
const sweetHTML = `
|
||
<div aria-labelledby="${swalClasses.title}" aria-describedby="${swalClasses['html-container']}" class="${swalClasses.popup}" tabindex="-1">
|
||
<button type="button" class="${swalClasses.close}"></button>
|
||
<ul class="${swalClasses['progress-steps']}"></ul>
|
||
<div class="${swalClasses.icon}"></div>
|
||
<img class="${swalClasses.image}" />
|
||
<h2 class="${swalClasses.title}" id="${swalClasses.title}"></h2>
|
||
<div class="${swalClasses['html-container']}" id="${swalClasses['html-container']}"></div>
|
||
<input class="${swalClasses.input}" id="${swalClasses.input}" />
|
||
<input type="file" class="${swalClasses.file}" />
|
||
<div class="${swalClasses.range}">
|
||
<input type="range" />
|
||
<output></output>
|
||
</div>
|
||
<select class="${swalClasses.select}" id="${swalClasses.select}"></select>
|
||
<div class="${swalClasses.radio}"></div>
|
||
<label class="${swalClasses.checkbox}">
|
||
<input type="checkbox" id="${swalClasses.checkbox}" />
|
||
<span class="${swalClasses.label}"></span>
|
||
</label>
|
||
<textarea class="${swalClasses.textarea}" id="${swalClasses.textarea}"></textarea>
|
||
<div class="${swalClasses['validation-message']}" id="${swalClasses['validation-message']}"></div>
|
||
<div class="${swalClasses.actions}">
|
||
<div class="${swalClasses.loader}"></div>
|
||
<button type="button" class="${swalClasses.confirm}"></button>
|
||
<button type="button" class="${swalClasses.deny}"></button>
|
||
<button type="button" class="${swalClasses.cancel}"></button>
|
||
</div>
|
||
<div class="${swalClasses.footer}"></div>
|
||
<div class="${swalClasses['timer-progress-bar-container']}">
|
||
<div class="${swalClasses['timer-progress-bar']}"></div>
|
||
</div>
|
||
</div>
|
||
`.replace(/(^|\n)\s*/g, '');
|
||
|
||
/**
|
||
* @returns {boolean}
|
||
*/
|
||
const resetOldContainer = () => {
|
||
const oldContainer = getContainer();
|
||
if (!oldContainer) {
|
||
return false;
|
||
}
|
||
oldContainer.remove();
|
||
removeClass([document.documentElement, document.body], [swalClasses['no-backdrop'], swalClasses['toast-shown'], swalClasses['has-column']]);
|
||
return true;
|
||
};
|
||
const resetValidationMessage$1 = () => {
|
||
globalState.currentInstance.resetValidationMessage();
|
||
};
|
||
const addInputChangeListeners = () => {
|
||
const popup = getPopup();
|
||
const input = getDirectChildByClass(popup, swalClasses.input);
|
||
const file = getDirectChildByClass(popup, swalClasses.file);
|
||
/** @type {HTMLInputElement} */
|
||
const range = popup.querySelector(`.${swalClasses.range} input`);
|
||
/** @type {HTMLOutputElement} */
|
||
const rangeOutput = popup.querySelector(`.${swalClasses.range} output`);
|
||
const select = getDirectChildByClass(popup, swalClasses.select);
|
||
/** @type {HTMLInputElement} */
|
||
const checkbox = popup.querySelector(`.${swalClasses.checkbox} input`);
|
||
const textarea = getDirectChildByClass(popup, swalClasses.textarea);
|
||
input.oninput = resetValidationMessage$1;
|
||
file.onchange = resetValidationMessage$1;
|
||
select.onchange = resetValidationMessage$1;
|
||
checkbox.onchange = resetValidationMessage$1;
|
||
textarea.oninput = resetValidationMessage$1;
|
||
range.oninput = () => {
|
||
resetValidationMessage$1();
|
||
rangeOutput.value = range.value;
|
||
};
|
||
range.onchange = () => {
|
||
resetValidationMessage$1();
|
||
rangeOutput.value = range.value;
|
||
};
|
||
};
|
||
|
||
/**
|
||
* @param {string | HTMLElement} target
|
||
* @returns {HTMLElement}
|
||
*/
|
||
const getTarget = target => typeof target === 'string' ? document.querySelector(target) : target;
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const setupAccessibility = params => {
|
||
const popup = getPopup();
|
||
popup.setAttribute('role', params.toast ? 'alert' : 'dialog');
|
||
popup.setAttribute('aria-live', params.toast ? 'polite' : 'assertive');
|
||
if (!params.toast) {
|
||
popup.setAttribute('aria-modal', 'true');
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} targetElement
|
||
*/
|
||
const setupRTL = targetElement => {
|
||
if (window.getComputedStyle(targetElement).direction === 'rtl') {
|
||
addClass(getContainer(), swalClasses.rtl);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Add modal + backdrop + no-war message for Russians to DOM
|
||
*
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const init = params => {
|
||
// Clean up the old popup container if it exists
|
||
const oldContainerExisted = resetOldContainer();
|
||
if (isNodeEnv()) {
|
||
error('SweetAlert2 requires document to initialize');
|
||
return;
|
||
}
|
||
const container = document.createElement('div');
|
||
container.className = swalClasses.container;
|
||
if (oldContainerExisted) {
|
||
addClass(container, swalClasses['no-transition']);
|
||
}
|
||
setInnerHtml(container, sweetHTML);
|
||
container.dataset['swal2Theme'] = params.theme;
|
||
const targetElement = getTarget(params.target);
|
||
targetElement.appendChild(container);
|
||
if (params.topLayer) {
|
||
container.setAttribute('popover', '');
|
||
container.showPopover();
|
||
}
|
||
setupAccessibility(params);
|
||
setupRTL(targetElement);
|
||
addInputChangeListeners();
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement | object | string} param
|
||
* @param {HTMLElement} target
|
||
*/
|
||
const parseHtmlToContainer = (param, target) => {
|
||
// DOM element
|
||
if (param instanceof HTMLElement) {
|
||
target.appendChild(param);
|
||
}
|
||
|
||
// Object
|
||
else if (typeof param === 'object') {
|
||
handleObject(param, target);
|
||
}
|
||
|
||
// Plain string
|
||
else if (param) {
|
||
setInnerHtml(target, param);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {any} param
|
||
* @param {HTMLElement} target
|
||
*/
|
||
const handleObject = (param, target) => {
|
||
// JQuery element(s)
|
||
if (param.jquery) {
|
||
handleJqueryElem(target, param);
|
||
}
|
||
|
||
// For other objects use their string representation
|
||
else {
|
||
setInnerHtml(target, param.toString());
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} target
|
||
* @param {any} elem
|
||
*/
|
||
const handleJqueryElem = (target, elem) => {
|
||
target.textContent = '';
|
||
if (0 in elem) {
|
||
for (let i = 0; i in elem; i++) {
|
||
target.appendChild(elem[i].cloneNode(true));
|
||
}
|
||
} else {
|
||
target.appendChild(elem.cloneNode(true));
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const renderActions = (instance, params) => {
|
||
const actions = getActions();
|
||
const loader = getLoader();
|
||
if (!actions || !loader) {
|
||
return;
|
||
}
|
||
|
||
// Actions (buttons) wrapper
|
||
if (!params.showConfirmButton && !params.showDenyButton && !params.showCancelButton) {
|
||
hide(actions);
|
||
} else {
|
||
show(actions);
|
||
}
|
||
|
||
// Custom class
|
||
applyCustomClass(actions, params, 'actions');
|
||
|
||
// Render all the buttons
|
||
renderButtons(actions, loader, params);
|
||
|
||
// Loader
|
||
setInnerHtml(loader, params.loaderHtml || '');
|
||
applyCustomClass(loader, params, 'loader');
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} actions
|
||
* @param {HTMLElement} loader
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
function renderButtons(actions, loader, params) {
|
||
const confirmButton = getConfirmButton();
|
||
const denyButton = getDenyButton();
|
||
const cancelButton = getCancelButton();
|
||
if (!confirmButton || !denyButton || !cancelButton) {
|
||
return;
|
||
}
|
||
|
||
// Render buttons
|
||
renderButton(confirmButton, 'confirm', params);
|
||
renderButton(denyButton, 'deny', params);
|
||
renderButton(cancelButton, 'cancel', params);
|
||
handleButtonsStyling(confirmButton, denyButton, cancelButton, params);
|
||
if (params.reverseButtons) {
|
||
if (params.toast) {
|
||
actions.insertBefore(cancelButton, confirmButton);
|
||
actions.insertBefore(denyButton, confirmButton);
|
||
} else {
|
||
actions.insertBefore(cancelButton, loader);
|
||
actions.insertBefore(denyButton, loader);
|
||
actions.insertBefore(confirmButton, loader);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {HTMLElement} confirmButton
|
||
* @param {HTMLElement} denyButton
|
||
* @param {HTMLElement} cancelButton
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
function handleButtonsStyling(confirmButton, denyButton, cancelButton, params) {
|
||
if (!params.buttonsStyling) {
|
||
removeClass([confirmButton, denyButton, cancelButton], swalClasses.styled);
|
||
return;
|
||
}
|
||
addClass([confirmButton, denyButton, cancelButton], swalClasses.styled);
|
||
|
||
// Apply custom background colors to action buttons
|
||
if (params.confirmButtonColor) {
|
||
confirmButton.style.setProperty('--swal2-confirm-button-background-color', params.confirmButtonColor);
|
||
}
|
||
if (params.denyButtonColor) {
|
||
denyButton.style.setProperty('--swal2-deny-button-background-color', params.denyButtonColor);
|
||
}
|
||
if (params.cancelButtonColor) {
|
||
cancelButton.style.setProperty('--swal2-cancel-button-background-color', params.cancelButtonColor);
|
||
}
|
||
|
||
// Apply the outline color to action buttons
|
||
applyOutlineColor(confirmButton);
|
||
applyOutlineColor(denyButton);
|
||
applyOutlineColor(cancelButton);
|
||
}
|
||
|
||
/**
|
||
* @param {HTMLElement} button
|
||
*/
|
||
function applyOutlineColor(button) {
|
||
const buttonStyle = window.getComputedStyle(button);
|
||
if (buttonStyle.getPropertyValue('--swal2-action-button-focus-box-shadow')) {
|
||
// If the button already has a custom outline color, no need to change it
|
||
return;
|
||
}
|
||
const outlineColor = buttonStyle.backgroundColor.replace(/rgba?\((\d+), (\d+), (\d+).*/, 'rgba($1, $2, $3, 0.5)');
|
||
button.style.setProperty('--swal2-action-button-focus-box-shadow', buttonStyle.getPropertyValue('--swal2-outline').replace(/ rgba\(.*/, ` ${outlineColor}`));
|
||
}
|
||
|
||
/**
|
||
* @param {HTMLElement} button
|
||
* @param {'confirm' | 'deny' | 'cancel'} buttonType
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
function renderButton(button, buttonType, params) {
|
||
const buttonName = /** @type {'Confirm' | 'Deny' | 'Cancel'} */capitalizeFirstLetter(buttonType);
|
||
toggle(button, params[`show${buttonName}Button`], 'inline-block');
|
||
setInnerHtml(button, params[`${buttonType}ButtonText`] || ''); // Set caption text
|
||
button.setAttribute('aria-label', params[`${buttonType}ButtonAriaLabel`] || ''); // ARIA label
|
||
|
||
// Add buttons custom classes
|
||
button.className = swalClasses[buttonType];
|
||
applyCustomClass(button, params, `${buttonType}Button`);
|
||
}
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const renderCloseButton = (instance, params) => {
|
||
const closeButton = getCloseButton();
|
||
if (!closeButton) {
|
||
return;
|
||
}
|
||
setInnerHtml(closeButton, params.closeButtonHtml || '');
|
||
|
||
// Custom class
|
||
applyCustomClass(closeButton, params, 'closeButton');
|
||
toggle(closeButton, params.showCloseButton);
|
||
closeButton.setAttribute('aria-label', params.closeButtonAriaLabel || '');
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const renderContainer = (instance, params) => {
|
||
const container = getContainer();
|
||
if (!container) {
|
||
return;
|
||
}
|
||
handleBackdropParam(container, params.backdrop);
|
||
handlePositionParam(container, params.position);
|
||
handleGrowParam(container, params.grow);
|
||
|
||
// Custom class
|
||
applyCustomClass(container, params, 'container');
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} container
|
||
* @param {SweetAlertOptions['backdrop']} backdrop
|
||
*/
|
||
function handleBackdropParam(container, backdrop) {
|
||
if (typeof backdrop === 'string') {
|
||
container.style.background = backdrop;
|
||
} else if (!backdrop) {
|
||
addClass([document.documentElement, document.body], swalClasses['no-backdrop']);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {HTMLElement} container
|
||
* @param {SweetAlertOptions['position']} position
|
||
*/
|
||
function handlePositionParam(container, position) {
|
||
if (!position) {
|
||
return;
|
||
}
|
||
if (position in swalClasses) {
|
||
addClass(container, swalClasses[position]);
|
||
} else {
|
||
warn('The "position" parameter is not valid, defaulting to "center"');
|
||
addClass(container, swalClasses.center);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {HTMLElement} container
|
||
* @param {SweetAlertOptions['grow']} grow
|
||
*/
|
||
function handleGrowParam(container, grow) {
|
||
if (!grow) {
|
||
return;
|
||
}
|
||
addClass(container, swalClasses[`grow-${grow}`]);
|
||
}
|
||
|
||
/**
|
||
* This module contains `WeakMap`s for each effectively-"private property" that a `Swal` has.
|
||
* For example, to set the private property "foo" of `this` to "bar", you can `privateProps.foo.set(this, 'bar')`
|
||
* This is the approach that Babel will probably take to implement private methods/fields
|
||
* https://github.com/tc39/proposal-private-methods
|
||
* https://github.com/babel/babel/pull/7555
|
||
* Once we have the changes from that PR in Babel, and our core class fits reasonable in *one module*
|
||
* then we can use that language feature.
|
||
*/
|
||
|
||
var privateProps = {
|
||
innerParams: new WeakMap(),
|
||
domCache: new WeakMap()
|
||
};
|
||
|
||
/// <reference path="../../../../sweetalert2.d.ts"/>
|
||
|
||
|
||
/** @type {InputClass[]} */
|
||
const inputClasses = ['input', 'file', 'range', 'select', 'radio', 'checkbox', 'textarea'];
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const renderInput = (instance, params) => {
|
||
const popup = getPopup();
|
||
if (!popup) {
|
||
return;
|
||
}
|
||
const innerParams = privateProps.innerParams.get(instance);
|
||
const rerender = !innerParams || params.input !== innerParams.input;
|
||
inputClasses.forEach(inputClass => {
|
||
const inputContainer = getDirectChildByClass(popup, swalClasses[inputClass]);
|
||
if (!inputContainer) {
|
||
return;
|
||
}
|
||
|
||
// set attributes
|
||
setAttributes(inputClass, params.inputAttributes);
|
||
|
||
// set class
|
||
inputContainer.className = swalClasses[inputClass];
|
||
if (rerender) {
|
||
hide(inputContainer);
|
||
}
|
||
});
|
||
if (params.input) {
|
||
if (rerender) {
|
||
showInput(params);
|
||
}
|
||
// set custom class
|
||
setCustomClass(params);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const showInput = params => {
|
||
if (!params.input) {
|
||
return;
|
||
}
|
||
if (!renderInputType[params.input]) {
|
||
error(`Unexpected type of input! Expected ${Object.keys(renderInputType).join(' | ')}, got "${params.input}"`);
|
||
return;
|
||
}
|
||
const inputContainer = getInputContainer(params.input);
|
||
if (!inputContainer) {
|
||
return;
|
||
}
|
||
const input = renderInputType[params.input](inputContainer, params);
|
||
show(inputContainer);
|
||
|
||
// input autofocus
|
||
if (params.inputAutoFocus) {
|
||
setTimeout(() => {
|
||
focusInput(input);
|
||
});
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLInputElement} input
|
||
*/
|
||
const removeAttributes = input => {
|
||
for (let i = 0; i < input.attributes.length; i++) {
|
||
const attrName = input.attributes[i].name;
|
||
if (!['id', 'type', 'value', 'style'].includes(attrName)) {
|
||
input.removeAttribute(attrName);
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {InputClass} inputClass
|
||
* @param {SweetAlertOptions['inputAttributes']} inputAttributes
|
||
*/
|
||
const setAttributes = (inputClass, inputAttributes) => {
|
||
const popup = getPopup();
|
||
if (!popup) {
|
||
return;
|
||
}
|
||
const input = getInput$1(popup, inputClass);
|
||
if (!input) {
|
||
return;
|
||
}
|
||
removeAttributes(input);
|
||
for (const attr in inputAttributes) {
|
||
input.setAttribute(attr, inputAttributes[attr]);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const setCustomClass = params => {
|
||
if (!params.input) {
|
||
return;
|
||
}
|
||
const inputContainer = getInputContainer(params.input);
|
||
if (inputContainer) {
|
||
applyCustomClass(inputContainer, params, 'input');
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLInputElement | HTMLTextAreaElement} input
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const setInputPlaceholder = (input, params) => {
|
||
if (!input.placeholder && params.inputPlaceholder) {
|
||
input.placeholder = params.inputPlaceholder;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {Input} input
|
||
* @param {Input} prependTo
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const setInputLabel = (input, prependTo, params) => {
|
||
if (params.inputLabel) {
|
||
const label = document.createElement('label');
|
||
const labelClass = swalClasses['input-label'];
|
||
label.setAttribute('for', input.id);
|
||
label.className = labelClass;
|
||
if (typeof params.customClass === 'object') {
|
||
addClass(label, params.customClass.inputLabel);
|
||
}
|
||
label.innerText = params.inputLabel;
|
||
prependTo.insertAdjacentElement('beforebegin', label);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlertInput} inputType
|
||
* @returns {HTMLElement | undefined}
|
||
*/
|
||
const getInputContainer = inputType => {
|
||
const popup = getPopup();
|
||
if (!popup) {
|
||
return;
|
||
}
|
||
return getDirectChildByClass(popup, swalClasses[(/** @type {SwalClass} */inputType)] || swalClasses.input);
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLInputElement | HTMLOutputElement | HTMLTextAreaElement} input
|
||
* @param {SweetAlertOptions['inputValue']} inputValue
|
||
*/
|
||
const checkAndSetInputValue = (input, inputValue) => {
|
||
if (['string', 'number'].includes(typeof inputValue)) {
|
||
input.value = `${inputValue}`;
|
||
} else if (!isPromise(inputValue)) {
|
||
warn(`Unexpected type of inputValue! Expected "string", "number" or "Promise", got "${typeof inputValue}"`);
|
||
}
|
||
};
|
||
|
||
/** @type {Record<SweetAlertInput, (input: Input | HTMLElement, params: SweetAlertOptions) => Input>} */
|
||
const renderInputType = {};
|
||
|
||
/**
|
||
* @param {HTMLInputElement} input
|
||
* @param {SweetAlertOptions} params
|
||
* @returns {HTMLInputElement}
|
||
*/
|
||
renderInputType.text = renderInputType.email = renderInputType.password = renderInputType.number = renderInputType.tel = renderInputType.url = renderInputType.search = renderInputType.date = renderInputType['datetime-local'] = renderInputType.time = renderInputType.week = renderInputType.month = /** @type {(input: Input | HTMLElement, params: SweetAlertOptions) => Input} */
|
||
(input, params) => {
|
||
checkAndSetInputValue(input, params.inputValue);
|
||
setInputLabel(input, input, params);
|
||
setInputPlaceholder(input, params);
|
||
input.type = params.input;
|
||
return input;
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLInputElement} input
|
||
* @param {SweetAlertOptions} params
|
||
* @returns {HTMLInputElement}
|
||
*/
|
||
renderInputType.file = (input, params) => {
|
||
setInputLabel(input, input, params);
|
||
setInputPlaceholder(input, params);
|
||
return input;
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLInputElement} range
|
||
* @param {SweetAlertOptions} params
|
||
* @returns {HTMLInputElement}
|
||
*/
|
||
renderInputType.range = (range, params) => {
|
||
const rangeInput = range.querySelector('input');
|
||
const rangeOutput = range.querySelector('output');
|
||
checkAndSetInputValue(rangeInput, params.inputValue);
|
||
rangeInput.type = params.input;
|
||
checkAndSetInputValue(rangeOutput, params.inputValue);
|
||
setInputLabel(rangeInput, range, params);
|
||
return range;
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLSelectElement} select
|
||
* @param {SweetAlertOptions} params
|
||
* @returns {HTMLSelectElement}
|
||
*/
|
||
renderInputType.select = (select, params) => {
|
||
select.textContent = '';
|
||
if (params.inputPlaceholder) {
|
||
const placeholder = document.createElement('option');
|
||
setInnerHtml(placeholder, params.inputPlaceholder);
|
||
placeholder.value = '';
|
||
placeholder.disabled = true;
|
||
placeholder.selected = true;
|
||
select.appendChild(placeholder);
|
||
}
|
||
setInputLabel(select, select, params);
|
||
return select;
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLInputElement} radio
|
||
* @returns {HTMLInputElement}
|
||
*/
|
||
renderInputType.radio = radio => {
|
||
radio.textContent = '';
|
||
return radio;
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLLabelElement} checkboxContainer
|
||
* @param {SweetAlertOptions} params
|
||
* @returns {HTMLInputElement}
|
||
*/
|
||
renderInputType.checkbox = (checkboxContainer, params) => {
|
||
const checkbox = getInput$1(getPopup(), 'checkbox');
|
||
checkbox.value = '1';
|
||
checkbox.checked = Boolean(params.inputValue);
|
||
const label = checkboxContainer.querySelector('span');
|
||
setInnerHtml(label, params.inputPlaceholder || params.inputLabel);
|
||
return checkbox;
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLTextAreaElement} textarea
|
||
* @param {SweetAlertOptions} params
|
||
* @returns {HTMLTextAreaElement}
|
||
*/
|
||
renderInputType.textarea = (textarea, params) => {
|
||
checkAndSetInputValue(textarea, params.inputValue);
|
||
setInputPlaceholder(textarea, params);
|
||
setInputLabel(textarea, textarea, params);
|
||
|
||
/**
|
||
* @param {HTMLElement} el
|
||
* @returns {number}
|
||
*/
|
||
const getMargin = el => parseInt(window.getComputedStyle(el).marginLeft) + parseInt(window.getComputedStyle(el).marginRight);
|
||
|
||
// https://github.com/sweetalert2/sweetalert2/issues/2291
|
||
setTimeout(() => {
|
||
// https://github.com/sweetalert2/sweetalert2/issues/1699
|
||
if ('MutationObserver' in window) {
|
||
const initialPopupWidth = parseInt(window.getComputedStyle(getPopup()).width);
|
||
const textareaResizeHandler = () => {
|
||
// check if texarea is still in document (i.e. popup wasn't closed in the meantime)
|
||
if (!document.body.contains(textarea)) {
|
||
return;
|
||
}
|
||
const textareaWidth = textarea.offsetWidth + getMargin(textarea);
|
||
if (textareaWidth > initialPopupWidth) {
|
||
getPopup().style.width = `${textareaWidth}px`;
|
||
} else {
|
||
applyNumericalStyle(getPopup(), 'width', params.width);
|
||
}
|
||
};
|
||
new MutationObserver(textareaResizeHandler).observe(textarea, {
|
||
attributes: true,
|
||
attributeFilter: ['style']
|
||
});
|
||
}
|
||
});
|
||
return textarea;
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const renderContent = (instance, params) => {
|
||
const htmlContainer = getHtmlContainer();
|
||
if (!htmlContainer) {
|
||
return;
|
||
}
|
||
showWhenInnerHtmlPresent(htmlContainer);
|
||
applyCustomClass(htmlContainer, params, 'htmlContainer');
|
||
|
||
// Content as HTML
|
||
if (params.html) {
|
||
parseHtmlToContainer(params.html, htmlContainer);
|
||
show(htmlContainer, 'block');
|
||
}
|
||
|
||
// Content as plain text
|
||
else if (params.text) {
|
||
htmlContainer.textContent = params.text;
|
||
show(htmlContainer, 'block');
|
||
}
|
||
|
||
// No content
|
||
else {
|
||
hide(htmlContainer);
|
||
}
|
||
renderInput(instance, params);
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const renderFooter = (instance, params) => {
|
||
const footer = getFooter();
|
||
if (!footer) {
|
||
return;
|
||
}
|
||
showWhenInnerHtmlPresent(footer);
|
||
toggle(footer, params.footer, 'block');
|
||
if (params.footer) {
|
||
parseHtmlToContainer(params.footer, footer);
|
||
}
|
||
|
||
// Custom class
|
||
applyCustomClass(footer, params, 'footer');
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const renderIcon = (instance, params) => {
|
||
const innerParams = privateProps.innerParams.get(instance);
|
||
const icon = getIcon();
|
||
if (!icon) {
|
||
return;
|
||
}
|
||
|
||
// if the given icon already rendered, apply the styling without re-rendering the icon
|
||
if (innerParams && params.icon === innerParams.icon) {
|
||
// Custom or default content
|
||
setContent(icon, params);
|
||
applyStyles(icon, params);
|
||
return;
|
||
}
|
||
if (!params.icon && !params.iconHtml) {
|
||
hide(icon);
|
||
return;
|
||
}
|
||
if (params.icon && Object.keys(iconTypes).indexOf(params.icon) === -1) {
|
||
error(`Unknown icon! Expected "success", "error", "warning", "info" or "question", got "${params.icon}"`);
|
||
hide(icon);
|
||
return;
|
||
}
|
||
show(icon);
|
||
|
||
// Custom or default content
|
||
setContent(icon, params);
|
||
applyStyles(icon, params);
|
||
|
||
// Animate icon
|
||
addClass(icon, params.showClass && params.showClass.icon);
|
||
|
||
// Re-adjust the success icon on system theme change
|
||
const colorSchemeQueryList = window.matchMedia('(prefers-color-scheme: dark)');
|
||
colorSchemeQueryList.addEventListener('change', adjustSuccessIconBackgroundColor);
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} icon
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const applyStyles = (icon, params) => {
|
||
for (const [iconType, iconClassName] of Object.entries(iconTypes)) {
|
||
if (params.icon !== iconType) {
|
||
removeClass(icon, iconClassName);
|
||
}
|
||
}
|
||
addClass(icon, params.icon && iconTypes[params.icon]);
|
||
|
||
// Icon color
|
||
setColor(icon, params);
|
||
|
||
// Success icon background color
|
||
adjustSuccessIconBackgroundColor();
|
||
|
||
// Custom class
|
||
applyCustomClass(icon, params, 'icon');
|
||
};
|
||
|
||
// Adjust success icon background color to match the popup background color
|
||
const adjustSuccessIconBackgroundColor = () => {
|
||
const popup = getPopup();
|
||
if (!popup) {
|
||
return;
|
||
}
|
||
const popupBackgroundColor = window.getComputedStyle(popup).getPropertyValue('background-color');
|
||
/** @type {NodeListOf<HTMLElement>} */
|
||
const successIconParts = popup.querySelectorAll('[class^=swal2-success-circular-line], .swal2-success-fix');
|
||
for (let i = 0; i < successIconParts.length; i++) {
|
||
successIconParts[i].style.backgroundColor = popupBackgroundColor;
|
||
}
|
||
};
|
||
|
||
/**
|
||
*
|
||
* @param {SweetAlertOptions} params
|
||
* @returns {string}
|
||
*/
|
||
const successIconHtml = params => `
|
||
${params.animation ? '<div class="swal2-success-circular-line-left"></div>' : ''}
|
||
<span class="swal2-success-line-tip"></span> <span class="swal2-success-line-long"></span>
|
||
<div class="swal2-success-ring"></div>
|
||
${params.animation ? '<div class="swal2-success-fix"></div>' : ''}
|
||
${params.animation ? '<div class="swal2-success-circular-line-right"></div>' : ''}
|
||
`;
|
||
const errorIconHtml = `
|
||
<span class="swal2-x-mark">
|
||
<span class="swal2-x-mark-line-left"></span>
|
||
<span class="swal2-x-mark-line-right"></span>
|
||
</span>
|
||
`;
|
||
|
||
/**
|
||
* @param {HTMLElement} icon
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const setContent = (icon, params) => {
|
||
if (!params.icon && !params.iconHtml) {
|
||
return;
|
||
}
|
||
let oldContent = icon.innerHTML;
|
||
let newContent = '';
|
||
if (params.iconHtml) {
|
||
newContent = iconContent(params.iconHtml);
|
||
} else if (params.icon === 'success') {
|
||
newContent = successIconHtml(params);
|
||
oldContent = oldContent.replace(/ style=".*?"/g, ''); // undo adjustSuccessIconBackgroundColor()
|
||
} else if (params.icon === 'error') {
|
||
newContent = errorIconHtml;
|
||
} else if (params.icon) {
|
||
const defaultIconHtml = {
|
||
question: '?',
|
||
warning: '!',
|
||
info: 'i'
|
||
};
|
||
newContent = iconContent(defaultIconHtml[params.icon]);
|
||
}
|
||
if (oldContent.trim() !== newContent.trim()) {
|
||
setInnerHtml(icon, newContent);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} icon
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const setColor = (icon, params) => {
|
||
if (!params.iconColor) {
|
||
return;
|
||
}
|
||
icon.style.color = params.iconColor;
|
||
icon.style.borderColor = params.iconColor;
|
||
for (const sel of ['.swal2-success-line-tip', '.swal2-success-line-long', '.swal2-x-mark-line-left', '.swal2-x-mark-line-right']) {
|
||
setStyle(icon, sel, 'background-color', params.iconColor);
|
||
}
|
||
setStyle(icon, '.swal2-success-ring', 'border-color', params.iconColor);
|
||
};
|
||
|
||
/**
|
||
* @param {string} content
|
||
* @returns {string}
|
||
*/
|
||
const iconContent = content => `<div class="${swalClasses['icon-content']}">${content}</div>`;
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const renderImage = (instance, params) => {
|
||
const image = getImage();
|
||
if (!image) {
|
||
return;
|
||
}
|
||
if (!params.imageUrl) {
|
||
hide(image);
|
||
return;
|
||
}
|
||
show(image, '');
|
||
|
||
// Src, alt
|
||
image.setAttribute('src', params.imageUrl);
|
||
image.setAttribute('alt', params.imageAlt || '');
|
||
|
||
// Width, height
|
||
applyNumericalStyle(image, 'width', params.imageWidth);
|
||
applyNumericalStyle(image, 'height', params.imageHeight);
|
||
|
||
// Class
|
||
image.className = swalClasses.image;
|
||
applyCustomClass(image, params, 'image');
|
||
};
|
||
|
||
let dragging = false;
|
||
let mousedownX = 0;
|
||
let mousedownY = 0;
|
||
let initialX = 0;
|
||
let initialY = 0;
|
||
|
||
/**
|
||
* @param {HTMLElement} popup
|
||
*/
|
||
const addDraggableListeners = popup => {
|
||
popup.addEventListener('mousedown', down);
|
||
document.body.addEventListener('mousemove', move);
|
||
popup.addEventListener('mouseup', up);
|
||
popup.addEventListener('touchstart', down);
|
||
document.body.addEventListener('touchmove', move);
|
||
popup.addEventListener('touchend', up);
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} popup
|
||
*/
|
||
const removeDraggableListeners = popup => {
|
||
popup.removeEventListener('mousedown', down);
|
||
document.body.removeEventListener('mousemove', move);
|
||
popup.removeEventListener('mouseup', up);
|
||
popup.removeEventListener('touchstart', down);
|
||
document.body.removeEventListener('touchmove', move);
|
||
popup.removeEventListener('touchend', up);
|
||
};
|
||
|
||
/**
|
||
* @param {MouseEvent | TouchEvent} event
|
||
*/
|
||
const down = event => {
|
||
const popup = getPopup();
|
||
if (event.target === popup || getIcon().contains(/** @type {HTMLElement} */event.target)) {
|
||
dragging = true;
|
||
const clientXY = getClientXY(event);
|
||
mousedownX = clientXY.clientX;
|
||
mousedownY = clientXY.clientY;
|
||
initialX = parseInt(popup.style.insetInlineStart) || 0;
|
||
initialY = parseInt(popup.style.insetBlockStart) || 0;
|
||
addClass(popup, 'swal2-dragging');
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {MouseEvent | TouchEvent} event
|
||
*/
|
||
const move = event => {
|
||
const popup = getPopup();
|
||
if (dragging) {
|
||
let {
|
||
clientX,
|
||
clientY
|
||
} = getClientXY(event);
|
||
popup.style.insetInlineStart = `${initialX + (clientX - mousedownX)}px`;
|
||
popup.style.insetBlockStart = `${initialY + (clientY - mousedownY)}px`;
|
||
}
|
||
};
|
||
const up = () => {
|
||
const popup = getPopup();
|
||
dragging = false;
|
||
removeClass(popup, 'swal2-dragging');
|
||
};
|
||
|
||
/**
|
||
* @param {MouseEvent | TouchEvent} event
|
||
* @returns {{ clientX: number, clientY: number }}
|
||
*/
|
||
const getClientXY = event => {
|
||
let clientX = 0,
|
||
clientY = 0;
|
||
if (event.type.startsWith('mouse')) {
|
||
clientX = /** @type {MouseEvent} */event.clientX;
|
||
clientY = /** @type {MouseEvent} */event.clientY;
|
||
} else if (event.type.startsWith('touch')) {
|
||
clientX = /** @type {TouchEvent} */event.touches[0].clientX;
|
||
clientY = /** @type {TouchEvent} */event.touches[0].clientY;
|
||
}
|
||
return {
|
||
clientX,
|
||
clientY
|
||
};
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const renderPopup = (instance, params) => {
|
||
const container = getContainer();
|
||
const popup = getPopup();
|
||
if (!container || !popup) {
|
||
return;
|
||
}
|
||
|
||
// Width
|
||
// https://github.com/sweetalert2/sweetalert2/issues/2170
|
||
if (params.toast) {
|
||
applyNumericalStyle(container, 'width', params.width);
|
||
popup.style.width = '100%';
|
||
const loader = getLoader();
|
||
if (loader) {
|
||
popup.insertBefore(loader, getIcon());
|
||
}
|
||
} else {
|
||
applyNumericalStyle(popup, 'width', params.width);
|
||
}
|
||
|
||
// Padding
|
||
applyNumericalStyle(popup, 'padding', params.padding);
|
||
|
||
// Color
|
||
if (params.color) {
|
||
popup.style.color = params.color;
|
||
}
|
||
|
||
// Background
|
||
if (params.background) {
|
||
popup.style.background = params.background;
|
||
}
|
||
hide(getValidationMessage());
|
||
|
||
// Classes
|
||
addClasses$1(popup, params);
|
||
if (params.draggable && !params.toast) {
|
||
addClass(popup, swalClasses.draggable);
|
||
addDraggableListeners(popup);
|
||
} else {
|
||
removeClass(popup, swalClasses.draggable);
|
||
removeDraggableListeners(popup);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} popup
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const addClasses$1 = (popup, params) => {
|
||
const showClass = params.showClass || {};
|
||
// Default Class + showClass when updating Swal.update({})
|
||
popup.className = `${swalClasses.popup} ${isVisible$1(popup) ? showClass.popup : ''}`;
|
||
if (params.toast) {
|
||
addClass([document.documentElement, document.body], swalClasses['toast-shown']);
|
||
addClass(popup, swalClasses.toast);
|
||
} else {
|
||
addClass(popup, swalClasses.modal);
|
||
}
|
||
|
||
// Custom class
|
||
applyCustomClass(popup, params, 'popup');
|
||
// TODO: remove in the next major
|
||
if (typeof params.customClass === 'string') {
|
||
addClass(popup, params.customClass);
|
||
}
|
||
|
||
// Icon class (#1842)
|
||
if (params.icon) {
|
||
addClass(popup, swalClasses[`icon-${params.icon}`]);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const renderProgressSteps = (instance, params) => {
|
||
const progressStepsContainer = getProgressSteps();
|
||
if (!progressStepsContainer) {
|
||
return;
|
||
}
|
||
const {
|
||
progressSteps,
|
||
currentProgressStep
|
||
} = params;
|
||
if (!progressSteps || progressSteps.length === 0 || currentProgressStep === undefined) {
|
||
hide(progressStepsContainer);
|
||
return;
|
||
}
|
||
show(progressStepsContainer);
|
||
progressStepsContainer.textContent = '';
|
||
if (currentProgressStep >= progressSteps.length) {
|
||
warn('Invalid currentProgressStep parameter, it should be less than progressSteps.length ' + '(currentProgressStep like JS arrays starts from 0)');
|
||
}
|
||
progressSteps.forEach((step, index) => {
|
||
const stepEl = createStepElement(step);
|
||
progressStepsContainer.appendChild(stepEl);
|
||
if (index === currentProgressStep) {
|
||
addClass(stepEl, swalClasses['active-progress-step']);
|
||
}
|
||
if (index !== progressSteps.length - 1) {
|
||
const lineEl = createLineElement(params);
|
||
progressStepsContainer.appendChild(lineEl);
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* @param {string} step
|
||
* @returns {HTMLLIElement}
|
||
*/
|
||
const createStepElement = step => {
|
||
const stepEl = document.createElement('li');
|
||
addClass(stepEl, swalClasses['progress-step']);
|
||
setInnerHtml(stepEl, step);
|
||
return stepEl;
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} params
|
||
* @returns {HTMLLIElement}
|
||
*/
|
||
const createLineElement = params => {
|
||
const lineEl = document.createElement('li');
|
||
addClass(lineEl, swalClasses['progress-step-line']);
|
||
if (params.progressStepsDistance) {
|
||
applyNumericalStyle(lineEl, 'width', params.progressStepsDistance);
|
||
}
|
||
return lineEl;
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const renderTitle = (instance, params) => {
|
||
const title = getTitle();
|
||
if (!title) {
|
||
return;
|
||
}
|
||
showWhenInnerHtmlPresent(title);
|
||
toggle(title, params.title || params.titleText, 'block');
|
||
if (params.title) {
|
||
parseHtmlToContainer(params.title, title);
|
||
}
|
||
if (params.titleText) {
|
||
title.innerText = params.titleText;
|
||
}
|
||
|
||
// Custom class
|
||
applyCustomClass(title, params, 'title');
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const render = (instance, params) => {
|
||
renderPopup(instance, params);
|
||
renderContainer(instance, params);
|
||
renderProgressSteps(instance, params);
|
||
renderIcon(instance, params);
|
||
renderImage(instance, params);
|
||
renderTitle(instance, params);
|
||
renderCloseButton(instance, params);
|
||
renderContent(instance, params);
|
||
renderActions(instance, params);
|
||
renderFooter(instance, params);
|
||
const popup = getPopup();
|
||
if (typeof params.didRender === 'function' && popup) {
|
||
params.didRender(popup);
|
||
}
|
||
globalState.eventEmitter.emit('didRender', popup);
|
||
};
|
||
|
||
/*
|
||
* Global function to determine if SweetAlert2 popup is shown
|
||
*/
|
||
const isVisible = () => {
|
||
return isVisible$1(getPopup());
|
||
};
|
||
|
||
/*
|
||
* Global function to click 'Confirm' button
|
||
*/
|
||
const clickConfirm = () => {
|
||
var _dom$getConfirmButton;
|
||
return (_dom$getConfirmButton = getConfirmButton()) === null || _dom$getConfirmButton === void 0 ? void 0 : _dom$getConfirmButton.click();
|
||
};
|
||
|
||
/*
|
||
* Global function to click 'Deny' button
|
||
*/
|
||
const clickDeny = () => {
|
||
var _dom$getDenyButton;
|
||
return (_dom$getDenyButton = getDenyButton()) === null || _dom$getDenyButton === void 0 ? void 0 : _dom$getDenyButton.click();
|
||
};
|
||
|
||
/*
|
||
* Global function to click 'Cancel' button
|
||
*/
|
||
const clickCancel = () => {
|
||
var _dom$getCancelButton;
|
||
return (_dom$getCancelButton = getCancelButton()) === null || _dom$getCancelButton === void 0 ? void 0 : _dom$getCancelButton.click();
|
||
};
|
||
|
||
/** @typedef {'cancel' | 'backdrop' | 'close' | 'esc' | 'timer'} DismissReason */
|
||
|
||
/** @type {Record<DismissReason, DismissReason>} */
|
||
const DismissReason = Object.freeze({
|
||
cancel: 'cancel',
|
||
backdrop: 'backdrop',
|
||
close: 'close',
|
||
esc: 'esc',
|
||
timer: 'timer'
|
||
});
|
||
|
||
/**
|
||
* @param {GlobalState} globalState
|
||
*/
|
||
const removeKeydownHandler = globalState => {
|
||
if (globalState.keydownTarget && globalState.keydownHandlerAdded) {
|
||
globalState.keydownTarget.removeEventListener('keydown', globalState.keydownHandler, {
|
||
capture: globalState.keydownListenerCapture
|
||
});
|
||
globalState.keydownHandlerAdded = false;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {GlobalState} globalState
|
||
* @param {SweetAlertOptions} innerParams
|
||
* @param {*} dismissWith
|
||
*/
|
||
const addKeydownHandler = (globalState, innerParams, dismissWith) => {
|
||
removeKeydownHandler(globalState);
|
||
if (!innerParams.toast) {
|
||
globalState.keydownHandler = e => keydownHandler(innerParams, e, dismissWith);
|
||
globalState.keydownTarget = innerParams.keydownListenerCapture ? window : getPopup();
|
||
globalState.keydownListenerCapture = innerParams.keydownListenerCapture;
|
||
globalState.keydownTarget.addEventListener('keydown', globalState.keydownHandler, {
|
||
capture: globalState.keydownListenerCapture
|
||
});
|
||
globalState.keydownHandlerAdded = true;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {number} index
|
||
* @param {number} increment
|
||
*/
|
||
const setFocus = (index, increment) => {
|
||
var _dom$getPopup;
|
||
const focusableElements = getFocusableElements();
|
||
// search for visible elements and select the next possible match
|
||
if (focusableElements.length) {
|
||
index = index + increment;
|
||
|
||
// shift + tab when .swal2-popup is focused
|
||
if (index === -2) {
|
||
index = focusableElements.length - 1;
|
||
}
|
||
|
||
// rollover to first item
|
||
if (index === focusableElements.length) {
|
||
index = 0;
|
||
|
||
// go to last item
|
||
} else if (index === -1) {
|
||
index = focusableElements.length - 1;
|
||
}
|
||
focusableElements[index].focus();
|
||
return;
|
||
}
|
||
// no visible focusable elements, focus the popup
|
||
(_dom$getPopup = getPopup()) === null || _dom$getPopup === void 0 || _dom$getPopup.focus();
|
||
};
|
||
const arrowKeysNextButton = ['ArrowRight', 'ArrowDown'];
|
||
const arrowKeysPreviousButton = ['ArrowLeft', 'ArrowUp'];
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} innerParams
|
||
* @param {KeyboardEvent} event
|
||
* @param {Function} dismissWith
|
||
*/
|
||
const keydownHandler = (innerParams, event, dismissWith) => {
|
||
if (!innerParams) {
|
||
return; // This instance has already been destroyed
|
||
}
|
||
|
||
// Ignore keydown during IME composition
|
||
// https://developer.mozilla.org/en-US/docs/Web/API/Document/keydown_event#ignoring_keydown_during_ime_composition
|
||
// https://github.com/sweetalert2/sweetalert2/issues/720
|
||
// https://github.com/sweetalert2/sweetalert2/issues/2406
|
||
if (event.isComposing || event.keyCode === 229) {
|
||
return;
|
||
}
|
||
if (innerParams.stopKeydownPropagation) {
|
||
event.stopPropagation();
|
||
}
|
||
|
||
// ENTER
|
||
if (event.key === 'Enter') {
|
||
handleEnter(event, innerParams);
|
||
}
|
||
|
||
// TAB
|
||
else if (event.key === 'Tab') {
|
||
handleTab(event);
|
||
}
|
||
|
||
// ARROWS - switch focus between buttons
|
||
else if ([...arrowKeysNextButton, ...arrowKeysPreviousButton].includes(event.key)) {
|
||
handleArrows(event.key);
|
||
}
|
||
|
||
// ESC
|
||
else if (event.key === 'Escape') {
|
||
handleEsc(event, innerParams, dismissWith);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {KeyboardEvent} event
|
||
* @param {SweetAlertOptions} innerParams
|
||
*/
|
||
const handleEnter = (event, innerParams) => {
|
||
// https://github.com/sweetalert2/sweetalert2/issues/2386
|
||
if (!callIfFunction(innerParams.allowEnterKey)) {
|
||
return;
|
||
}
|
||
const input = getInput$1(getPopup(), innerParams.input);
|
||
if (event.target && input && event.target instanceof HTMLElement && event.target.outerHTML === input.outerHTML) {
|
||
if (['textarea', 'file'].includes(innerParams.input)) {
|
||
return; // do not submit
|
||
}
|
||
clickConfirm();
|
||
event.preventDefault();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {KeyboardEvent} event
|
||
*/
|
||
const handleTab = event => {
|
||
const targetElement = event.target;
|
||
const focusableElements = getFocusableElements();
|
||
let btnIndex = -1;
|
||
for (let i = 0; i < focusableElements.length; i++) {
|
||
if (targetElement === focusableElements[i]) {
|
||
btnIndex = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Cycle to the next button
|
||
if (!event.shiftKey) {
|
||
setFocus(btnIndex, 1);
|
||
}
|
||
|
||
// Cycle to the prev button
|
||
else {
|
||
setFocus(btnIndex, -1);
|
||
}
|
||
event.stopPropagation();
|
||
event.preventDefault();
|
||
};
|
||
|
||
/**
|
||
* @param {string} key
|
||
*/
|
||
const handleArrows = key => {
|
||
const actions = getActions();
|
||
const confirmButton = getConfirmButton();
|
||
const denyButton = getDenyButton();
|
||
const cancelButton = getCancelButton();
|
||
if (!actions || !confirmButton || !denyButton || !cancelButton) {
|
||
return;
|
||
}
|
||
/** @type HTMLElement[] */
|
||
const buttons = [confirmButton, denyButton, cancelButton];
|
||
if (document.activeElement instanceof HTMLElement && !buttons.includes(document.activeElement)) {
|
||
return;
|
||
}
|
||
const sibling = arrowKeysNextButton.includes(key) ? 'nextElementSibling' : 'previousElementSibling';
|
||
let buttonToFocus = document.activeElement;
|
||
if (!buttonToFocus) {
|
||
return;
|
||
}
|
||
for (let i = 0; i < actions.children.length; i++) {
|
||
buttonToFocus = buttonToFocus[sibling];
|
||
if (!buttonToFocus) {
|
||
return;
|
||
}
|
||
if (buttonToFocus instanceof HTMLButtonElement && isVisible$1(buttonToFocus)) {
|
||
break;
|
||
}
|
||
}
|
||
if (buttonToFocus instanceof HTMLButtonElement) {
|
||
buttonToFocus.focus();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {KeyboardEvent} event
|
||
* @param {SweetAlertOptions} innerParams
|
||
* @param {Function} dismissWith
|
||
*/
|
||
const handleEsc = (event, innerParams, dismissWith) => {
|
||
event.preventDefault();
|
||
if (callIfFunction(innerParams.allowEscapeKey)) {
|
||
dismissWith(DismissReason.esc);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* This module contains `WeakMap`s for each effectively-"private property" that a `Swal` has.
|
||
* For example, to set the private property "foo" of `this` to "bar", you can `privateProps.foo.set(this, 'bar')`
|
||
* This is the approach that Babel will probably take to implement private methods/fields
|
||
* https://github.com/tc39/proposal-private-methods
|
||
* https://github.com/babel/babel/pull/7555
|
||
* Once we have the changes from that PR in Babel, and our core class fits reasonable in *one module*
|
||
* then we can use that language feature.
|
||
*/
|
||
|
||
var privateMethods = {
|
||
swalPromiseResolve: new WeakMap(),
|
||
swalPromiseReject: new WeakMap()
|
||
};
|
||
|
||
// From https://developer.paciellogroup.com/blog/2018/06/the-current-state-of-modal-dialog-accessibility/
|
||
// Adding aria-hidden="true" to elements outside of the active modal dialog ensures that
|
||
// elements not within the active modal dialog will not be surfaced if a user opens a screen
|
||
// reader’s list of elements (headings, form controls, landmarks, etc.) in the document.
|
||
|
||
const setAriaHidden = () => {
|
||
const container = getContainer();
|
||
const bodyChildren = Array.from(document.body.children);
|
||
bodyChildren.forEach(el => {
|
||
if (el.contains(container)) {
|
||
return;
|
||
}
|
||
if (el.hasAttribute('aria-hidden')) {
|
||
el.setAttribute('data-previous-aria-hidden', el.getAttribute('aria-hidden') || '');
|
||
}
|
||
el.setAttribute('aria-hidden', 'true');
|
||
});
|
||
};
|
||
const unsetAriaHidden = () => {
|
||
const bodyChildren = Array.from(document.body.children);
|
||
bodyChildren.forEach(el => {
|
||
if (el.hasAttribute('data-previous-aria-hidden')) {
|
||
el.setAttribute('aria-hidden', el.getAttribute('data-previous-aria-hidden') || '');
|
||
el.removeAttribute('data-previous-aria-hidden');
|
||
} else {
|
||
el.removeAttribute('aria-hidden');
|
||
}
|
||
});
|
||
};
|
||
|
||
// @ts-ignore
|
||
const isSafariOrIOS = typeof window !== 'undefined' && !!window.GestureEvent; // true for Safari desktop + all iOS browsers https://stackoverflow.com/a/70585394
|
||
|
||
/**
|
||
* Fix iOS scrolling
|
||
* http://stackoverflow.com/q/39626302
|
||
*/
|
||
const iOSfix = () => {
|
||
if (isSafariOrIOS && !hasClass(document.body, swalClasses.iosfix)) {
|
||
const offset = document.body.scrollTop;
|
||
document.body.style.top = `${offset * -1}px`;
|
||
addClass(document.body, swalClasses.iosfix);
|
||
lockBodyScroll();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* https://github.com/sweetalert2/sweetalert2/issues/1246
|
||
*/
|
||
const lockBodyScroll = () => {
|
||
const container = getContainer();
|
||
if (!container) {
|
||
return;
|
||
}
|
||
/** @type {boolean} */
|
||
let preventTouchMove;
|
||
/**
|
||
* @param {TouchEvent} event
|
||
*/
|
||
container.ontouchstart = event => {
|
||
preventTouchMove = shouldPreventTouchMove(event);
|
||
};
|
||
/**
|
||
* @param {TouchEvent} event
|
||
*/
|
||
container.ontouchmove = event => {
|
||
if (preventTouchMove) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
}
|
||
};
|
||
};
|
||
|
||
/**
|
||
* @param {TouchEvent} event
|
||
* @returns {boolean}
|
||
*/
|
||
const shouldPreventTouchMove = event => {
|
||
const target = event.target;
|
||
const container = getContainer();
|
||
const htmlContainer = getHtmlContainer();
|
||
if (!container || !htmlContainer) {
|
||
return false;
|
||
}
|
||
if (isStylus(event) || isZoom(event)) {
|
||
return false;
|
||
}
|
||
if (target === container) {
|
||
return true;
|
||
}
|
||
if (!isScrollable(container) && target instanceof HTMLElement && !selfOrParentIsScrollable(target, htmlContainer) &&
|
||
// #2823
|
||
target.tagName !== 'INPUT' &&
|
||
// #1603
|
||
target.tagName !== 'TEXTAREA' &&
|
||
// #2266
|
||
!(isScrollable(htmlContainer) &&
|
||
// #1944
|
||
htmlContainer.contains(target))) {
|
||
return true;
|
||
}
|
||
return false;
|
||
};
|
||
|
||
/**
|
||
* https://github.com/sweetalert2/sweetalert2/issues/1786
|
||
*
|
||
* @param {*} event
|
||
* @returns {boolean}
|
||
*/
|
||
const isStylus = event => {
|
||
return event.touches && event.touches.length && event.touches[0].touchType === 'stylus';
|
||
};
|
||
|
||
/**
|
||
* https://github.com/sweetalert2/sweetalert2/issues/1891
|
||
*
|
||
* @param {TouchEvent} event
|
||
* @returns {boolean}
|
||
*/
|
||
const isZoom = event => {
|
||
return event.touches && event.touches.length > 1;
|
||
};
|
||
const undoIOSfix = () => {
|
||
if (hasClass(document.body, swalClasses.iosfix)) {
|
||
const offset = parseInt(document.body.style.top, 10);
|
||
removeClass(document.body, swalClasses.iosfix);
|
||
document.body.style.top = '';
|
||
document.body.scrollTop = offset * -1;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Measure scrollbar width for padding body during modal show/hide
|
||
* https://github.com/twbs/bootstrap/blob/master/js/src/modal.js
|
||
*
|
||
* @returns {number}
|
||
*/
|
||
const measureScrollbar = () => {
|
||
const scrollDiv = document.createElement('div');
|
||
scrollDiv.className = swalClasses['scrollbar-measure'];
|
||
document.body.appendChild(scrollDiv);
|
||
const scrollbarWidth = scrollDiv.getBoundingClientRect().width - scrollDiv.clientWidth;
|
||
document.body.removeChild(scrollDiv);
|
||
return scrollbarWidth;
|
||
};
|
||
|
||
/**
|
||
* Remember state in cases where opening and handling a modal will fiddle with it.
|
||
* @type {number | null}
|
||
*/
|
||
let previousBodyPadding = null;
|
||
|
||
/**
|
||
* @param {string} initialBodyOverflow
|
||
*/
|
||
const replaceScrollbarWithPadding = initialBodyOverflow => {
|
||
// for queues, do not do this more than once
|
||
if (previousBodyPadding !== null) {
|
||
return;
|
||
}
|
||
// if the body has overflow
|
||
if (document.body.scrollHeight > window.innerHeight || initialBodyOverflow === 'scroll' // https://github.com/sweetalert2/sweetalert2/issues/2663
|
||
) {
|
||
// add padding so the content doesn't shift after removal of scrollbar
|
||
previousBodyPadding = parseInt(window.getComputedStyle(document.body).getPropertyValue('padding-right'));
|
||
document.body.style.paddingRight = `${previousBodyPadding + measureScrollbar()}px`;
|
||
}
|
||
};
|
||
const undoReplaceScrollbarWithPadding = () => {
|
||
if (previousBodyPadding !== null) {
|
||
document.body.style.paddingRight = `${previousBodyPadding}px`;
|
||
previousBodyPadding = null;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {HTMLElement} container
|
||
* @param {boolean} returnFocus
|
||
* @param {Function} didClose
|
||
*/
|
||
function removePopupAndResetState(instance, container, returnFocus, didClose) {
|
||
if (isToast()) {
|
||
triggerDidCloseAndDispose(instance, didClose);
|
||
} else {
|
||
restoreActiveElement(returnFocus).then(() => triggerDidCloseAndDispose(instance, didClose));
|
||
removeKeydownHandler(globalState);
|
||
}
|
||
|
||
// workaround for https://github.com/sweetalert2/sweetalert2/issues/2088
|
||
// for some reason removing the container in Safari will scroll the document to bottom
|
||
if (isSafariOrIOS) {
|
||
container.setAttribute('style', 'display:none !important');
|
||
container.removeAttribute('class');
|
||
container.innerHTML = '';
|
||
} else {
|
||
container.remove();
|
||
}
|
||
if (isModal()) {
|
||
undoReplaceScrollbarWithPadding();
|
||
undoIOSfix();
|
||
unsetAriaHidden();
|
||
}
|
||
removeBodyClasses();
|
||
}
|
||
|
||
/**
|
||
* Remove SweetAlert2 classes from body
|
||
*/
|
||
function removeBodyClasses() {
|
||
removeClass([document.documentElement, document.body], [swalClasses.shown, swalClasses['height-auto'], swalClasses['no-backdrop'], swalClasses['toast-shown']]);
|
||
}
|
||
|
||
/**
|
||
* Instance method to close sweetAlert
|
||
*
|
||
* @param {any} resolveValue
|
||
*/
|
||
function close(resolveValue) {
|
||
resolveValue = prepareResolveValue(resolveValue);
|
||
const swalPromiseResolve = privateMethods.swalPromiseResolve.get(this);
|
||
const didClose = triggerClosePopup(this);
|
||
if (this.isAwaitingPromise) {
|
||
// A swal awaiting for a promise (after a click on Confirm or Deny) cannot be dismissed anymore #2335
|
||
if (!resolveValue.isDismissed) {
|
||
handleAwaitingPromise(this);
|
||
swalPromiseResolve(resolveValue);
|
||
}
|
||
} else if (didClose) {
|
||
// Resolve Swal promise
|
||
swalPromiseResolve(resolveValue);
|
||
}
|
||
}
|
||
const triggerClosePopup = instance => {
|
||
const popup = getPopup();
|
||
if (!popup) {
|
||
return false;
|
||
}
|
||
const innerParams = privateProps.innerParams.get(instance);
|
||
if (!innerParams || hasClass(popup, innerParams.hideClass.popup)) {
|
||
return false;
|
||
}
|
||
removeClass(popup, innerParams.showClass.popup);
|
||
addClass(popup, innerParams.hideClass.popup);
|
||
const backdrop = getContainer();
|
||
removeClass(backdrop, innerParams.showClass.backdrop);
|
||
addClass(backdrop, innerParams.hideClass.backdrop);
|
||
handlePopupAnimation(instance, popup, innerParams);
|
||
return true;
|
||
};
|
||
|
||
/**
|
||
* @param {any} error
|
||
*/
|
||
function rejectPromise(error) {
|
||
const rejectPromise = privateMethods.swalPromiseReject.get(this);
|
||
handleAwaitingPromise(this);
|
||
if (rejectPromise) {
|
||
// Reject Swal promise
|
||
rejectPromise(error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
*/
|
||
const handleAwaitingPromise = instance => {
|
||
if (instance.isAwaitingPromise) {
|
||
delete instance.isAwaitingPromise;
|
||
// The instance might have been previously partly destroyed, we must resume the destroy process in this case #2335
|
||
if (!privateProps.innerParams.get(instance)) {
|
||
instance._destroy();
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {any} resolveValue
|
||
* @returns {SweetAlertResult}
|
||
*/
|
||
const prepareResolveValue = resolveValue => {
|
||
// When user calls Swal.close()
|
||
if (typeof resolveValue === 'undefined') {
|
||
return {
|
||
isConfirmed: false,
|
||
isDenied: false,
|
||
isDismissed: true
|
||
};
|
||
}
|
||
return Object.assign({
|
||
isConfirmed: false,
|
||
isDenied: false,
|
||
isDismissed: false
|
||
}, resolveValue);
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {HTMLElement} popup
|
||
* @param {SweetAlertOptions} innerParams
|
||
*/
|
||
const handlePopupAnimation = (instance, popup, innerParams) => {
|
||
var _globalState$eventEmi;
|
||
const container = getContainer();
|
||
// If animation is supported, animate
|
||
const animationIsSupported = hasCssAnimation(popup);
|
||
if (typeof innerParams.willClose === 'function') {
|
||
innerParams.willClose(popup);
|
||
}
|
||
(_globalState$eventEmi = globalState.eventEmitter) === null || _globalState$eventEmi === void 0 || _globalState$eventEmi.emit('willClose', popup);
|
||
if (animationIsSupported) {
|
||
animatePopup(instance, popup, container, innerParams.returnFocus, innerParams.didClose);
|
||
} else {
|
||
// Otherwise, remove immediately
|
||
removePopupAndResetState(instance, container, innerParams.returnFocus, innerParams.didClose);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {HTMLElement} popup
|
||
* @param {HTMLElement} container
|
||
* @param {boolean} returnFocus
|
||
* @param {Function} didClose
|
||
*/
|
||
const animatePopup = (instance, popup, container, returnFocus, didClose) => {
|
||
globalState.swalCloseEventFinishedCallback = removePopupAndResetState.bind(null, instance, container, returnFocus, didClose);
|
||
/**
|
||
* @param {AnimationEvent | TransitionEvent} e
|
||
*/
|
||
const swalCloseAnimationFinished = function (e) {
|
||
if (e.target === popup) {
|
||
var _globalState$swalClos;
|
||
(_globalState$swalClos = globalState.swalCloseEventFinishedCallback) === null || _globalState$swalClos === void 0 || _globalState$swalClos.call(globalState);
|
||
delete globalState.swalCloseEventFinishedCallback;
|
||
popup.removeEventListener('animationend', swalCloseAnimationFinished);
|
||
popup.removeEventListener('transitionend', swalCloseAnimationFinished);
|
||
}
|
||
};
|
||
popup.addEventListener('animationend', swalCloseAnimationFinished);
|
||
popup.addEventListener('transitionend', swalCloseAnimationFinished);
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {Function} didClose
|
||
*/
|
||
const triggerDidCloseAndDispose = (instance, didClose) => {
|
||
setTimeout(() => {
|
||
var _globalState$eventEmi2;
|
||
if (typeof didClose === 'function') {
|
||
didClose.bind(instance.params)();
|
||
}
|
||
(_globalState$eventEmi2 = globalState.eventEmitter) === null || _globalState$eventEmi2 === void 0 || _globalState$eventEmi2.emit('didClose');
|
||
// instance might have been destroyed already
|
||
if (instance._destroy) {
|
||
instance._destroy();
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Shows loader (spinner), this is useful with AJAX requests.
|
||
* By default the loader be shown instead of the "Confirm" button.
|
||
*
|
||
* @param {HTMLButtonElement | null} [buttonToReplace]
|
||
*/
|
||
const showLoading = buttonToReplace => {
|
||
let popup = getPopup();
|
||
if (!popup) {
|
||
new Swal();
|
||
}
|
||
popup = getPopup();
|
||
if (!popup) {
|
||
return;
|
||
}
|
||
const loader = getLoader();
|
||
if (isToast()) {
|
||
hide(getIcon());
|
||
} else {
|
||
replaceButton(popup, buttonToReplace);
|
||
}
|
||
show(loader);
|
||
popup.setAttribute('data-loading', 'true');
|
||
popup.setAttribute('aria-busy', 'true');
|
||
popup.focus();
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} popup
|
||
* @param {HTMLButtonElement | null} [buttonToReplace]
|
||
*/
|
||
const replaceButton = (popup, buttonToReplace) => {
|
||
const actions = getActions();
|
||
const loader = getLoader();
|
||
if (!actions || !loader) {
|
||
return;
|
||
}
|
||
if (!buttonToReplace && isVisible$1(getConfirmButton())) {
|
||
buttonToReplace = getConfirmButton();
|
||
}
|
||
show(actions);
|
||
if (buttonToReplace) {
|
||
hide(buttonToReplace);
|
||
loader.setAttribute('data-button-to-replace', buttonToReplace.className);
|
||
actions.insertBefore(loader, buttonToReplace);
|
||
}
|
||
addClass([popup, actions], swalClasses.loading);
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const handleInputOptionsAndValue = (instance, params) => {
|
||
if (params.input === 'select' || params.input === 'radio') {
|
||
handleInputOptions(instance, params);
|
||
} else if (['text', 'email', 'number', 'tel', 'textarea'].some(i => i === params.input) && (hasToPromiseFn(params.inputValue) || isPromise(params.inputValue))) {
|
||
showLoading(getConfirmButton());
|
||
handleInputValue(instance, params);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} innerParams
|
||
* @returns {SweetAlertInputValue}
|
||
*/
|
||
const getInputValue = (instance, innerParams) => {
|
||
const input = instance.getInput();
|
||
if (!input) {
|
||
return null;
|
||
}
|
||
switch (innerParams.input) {
|
||
case 'checkbox':
|
||
return getCheckboxValue(input);
|
||
case 'radio':
|
||
return getRadioValue(input);
|
||
case 'file':
|
||
return getFileValue(input);
|
||
default:
|
||
return innerParams.inputAutoTrim ? input.value.trim() : input.value;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLInputElement} input
|
||
* @returns {number}
|
||
*/
|
||
const getCheckboxValue = input => input.checked ? 1 : 0;
|
||
|
||
/**
|
||
* @param {HTMLInputElement} input
|
||
* @returns {string | null}
|
||
*/
|
||
const getRadioValue = input => input.checked ? input.value : null;
|
||
|
||
/**
|
||
* @param {HTMLInputElement} input
|
||
* @returns {FileList | File | null}
|
||
*/
|
||
const getFileValue = input => input.files && input.files.length ? input.getAttribute('multiple') !== null ? input.files : input.files[0] : null;
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const handleInputOptions = (instance, params) => {
|
||
const popup = getPopup();
|
||
if (!popup) {
|
||
return;
|
||
}
|
||
/**
|
||
* @param {Record<string, any>} inputOptions
|
||
*/
|
||
const processInputOptions = inputOptions => {
|
||
if (params.input === 'select') {
|
||
populateSelectOptions(popup, formatInputOptions(inputOptions), params);
|
||
} else if (params.input === 'radio') {
|
||
populateRadioOptions(popup, formatInputOptions(inputOptions), params);
|
||
}
|
||
};
|
||
if (hasToPromiseFn(params.inputOptions) || isPromise(params.inputOptions)) {
|
||
showLoading(getConfirmButton());
|
||
asPromise(params.inputOptions).then(inputOptions => {
|
||
instance.hideLoading();
|
||
processInputOptions(inputOptions);
|
||
});
|
||
} else if (typeof params.inputOptions === 'object') {
|
||
processInputOptions(params.inputOptions);
|
||
} else {
|
||
error(`Unexpected type of inputOptions! Expected object, Map or Promise, got ${typeof params.inputOptions}`);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const handleInputValue = (instance, params) => {
|
||
const input = instance.getInput();
|
||
if (!input) {
|
||
return;
|
||
}
|
||
hide(input);
|
||
asPromise(params.inputValue).then(inputValue => {
|
||
input.value = params.input === 'number' ? `${parseFloat(inputValue) || 0}` : `${inputValue}`;
|
||
show(input);
|
||
input.focus();
|
||
instance.hideLoading();
|
||
}).catch(err => {
|
||
error(`Error in inputValue promise: ${err}`);
|
||
input.value = '';
|
||
show(input);
|
||
input.focus();
|
||
instance.hideLoading();
|
||
});
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} popup
|
||
* @param {InputOptionFlattened[]} inputOptions
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
function populateSelectOptions(popup, inputOptions, params) {
|
||
const select = getDirectChildByClass(popup, swalClasses.select);
|
||
if (!select) {
|
||
return;
|
||
}
|
||
/**
|
||
* @param {HTMLElement} parent
|
||
* @param {string} optionLabel
|
||
* @param {string} optionValue
|
||
*/
|
||
const renderOption = (parent, optionLabel, optionValue) => {
|
||
const option = document.createElement('option');
|
||
option.value = optionValue;
|
||
setInnerHtml(option, optionLabel);
|
||
option.selected = isSelected(optionValue, params.inputValue);
|
||
parent.appendChild(option);
|
||
};
|
||
inputOptions.forEach(inputOption => {
|
||
const optionValue = inputOption[0];
|
||
const optionLabel = inputOption[1];
|
||
// <optgroup> spec:
|
||
// https://www.w3.org/TR/html401/interact/forms.html#h-17.6
|
||
// "...all OPTGROUP elements must be specified directly within a SELECT element (i.e., groups may not be nested)..."
|
||
// check whether this is a <optgroup>
|
||
if (Array.isArray(optionLabel)) {
|
||
// if it is an array, then it is an <optgroup>
|
||
const optgroup = document.createElement('optgroup');
|
||
optgroup.label = optionValue;
|
||
optgroup.disabled = false; // not configurable for now
|
||
select.appendChild(optgroup);
|
||
optionLabel.forEach(o => renderOption(optgroup, o[1], o[0]));
|
||
} else {
|
||
// case of <option>
|
||
renderOption(select, optionLabel, optionValue);
|
||
}
|
||
});
|
||
select.focus();
|
||
}
|
||
|
||
/**
|
||
* @param {HTMLElement} popup
|
||
* @param {InputOptionFlattened[]} inputOptions
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
function populateRadioOptions(popup, inputOptions, params) {
|
||
const radio = getDirectChildByClass(popup, swalClasses.radio);
|
||
if (!radio) {
|
||
return;
|
||
}
|
||
inputOptions.forEach(inputOption => {
|
||
const radioValue = inputOption[0];
|
||
const radioLabel = inputOption[1];
|
||
const radioInput = document.createElement('input');
|
||
const radioLabelElement = document.createElement('label');
|
||
radioInput.type = 'radio';
|
||
radioInput.name = swalClasses.radio;
|
||
radioInput.value = radioValue;
|
||
if (isSelected(radioValue, params.inputValue)) {
|
||
radioInput.checked = true;
|
||
}
|
||
const label = document.createElement('span');
|
||
setInnerHtml(label, radioLabel);
|
||
label.className = swalClasses.label;
|
||
radioLabelElement.appendChild(radioInput);
|
||
radioLabelElement.appendChild(label);
|
||
radio.appendChild(radioLabelElement);
|
||
});
|
||
const radios = radio.querySelectorAll('input');
|
||
if (radios.length) {
|
||
radios[0].focus();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Converts `inputOptions` into an array of `[value, label]`s
|
||
*
|
||
* @param {Record<string, any>} inputOptions
|
||
* @typedef {string[]} InputOptionFlattened
|
||
* @returns {InputOptionFlattened[]}
|
||
*/
|
||
const formatInputOptions = inputOptions => {
|
||
/** @type {InputOptionFlattened[]} */
|
||
const result = [];
|
||
if (inputOptions instanceof Map) {
|
||
inputOptions.forEach((value, key) => {
|
||
let valueFormatted = value;
|
||
if (typeof valueFormatted === 'object') {
|
||
// case of <optgroup>
|
||
valueFormatted = formatInputOptions(valueFormatted);
|
||
}
|
||
result.push([key, valueFormatted]);
|
||
});
|
||
} else {
|
||
Object.keys(inputOptions).forEach(key => {
|
||
let valueFormatted = inputOptions[key];
|
||
if (typeof valueFormatted === 'object') {
|
||
// case of <optgroup>
|
||
valueFormatted = formatInputOptions(valueFormatted);
|
||
}
|
||
result.push([key, valueFormatted]);
|
||
});
|
||
}
|
||
return result;
|
||
};
|
||
|
||
/**
|
||
* @param {string} optionValue
|
||
* @param {SweetAlertInputValue} inputValue
|
||
* @returns {boolean}
|
||
*/
|
||
const isSelected = (optionValue, inputValue) => {
|
||
return !!inputValue && inputValue.toString() === optionValue.toString();
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
*/
|
||
const handleConfirmButtonClick = instance => {
|
||
const innerParams = privateProps.innerParams.get(instance);
|
||
instance.disableButtons();
|
||
if (innerParams.input) {
|
||
handleConfirmOrDenyWithInput(instance, 'confirm');
|
||
} else {
|
||
confirm(instance, true);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
*/
|
||
const handleDenyButtonClick = instance => {
|
||
const innerParams = privateProps.innerParams.get(instance);
|
||
instance.disableButtons();
|
||
if (innerParams.returnInputValueOnDeny) {
|
||
handleConfirmOrDenyWithInput(instance, 'deny');
|
||
} else {
|
||
deny(instance, false);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {Function} dismissWith
|
||
*/
|
||
const handleCancelButtonClick = (instance, dismissWith) => {
|
||
instance.disableButtons();
|
||
dismissWith(DismissReason.cancel);
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {'confirm' | 'deny'} type
|
||
*/
|
||
const handleConfirmOrDenyWithInput = (instance, type) => {
|
||
const innerParams = privateProps.innerParams.get(instance);
|
||
if (!innerParams.input) {
|
||
error(`The "input" parameter is needed to be set when using returnInputValueOn${capitalizeFirstLetter(type)}`);
|
||
return;
|
||
}
|
||
const input = instance.getInput();
|
||
const inputValue = getInputValue(instance, innerParams);
|
||
if (innerParams.inputValidator) {
|
||
handleInputValidator(instance, inputValue, type);
|
||
} else if (input && !input.checkValidity()) {
|
||
instance.enableButtons();
|
||
instance.showValidationMessage(innerParams.validationMessage || input.validationMessage);
|
||
} else if (type === 'deny') {
|
||
deny(instance, inputValue);
|
||
} else {
|
||
confirm(instance, inputValue);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {SweetAlertInputValue} inputValue
|
||
* @param {'confirm' | 'deny'} type
|
||
*/
|
||
const handleInputValidator = (instance, inputValue, type) => {
|
||
const innerParams = privateProps.innerParams.get(instance);
|
||
instance.disableInput();
|
||
const validationPromise = Promise.resolve().then(() => asPromise(innerParams.inputValidator(inputValue, innerParams.validationMessage)));
|
||
validationPromise.then(validationMessage => {
|
||
instance.enableButtons();
|
||
instance.enableInput();
|
||
if (validationMessage) {
|
||
instance.showValidationMessage(validationMessage);
|
||
} else if (type === 'deny') {
|
||
deny(instance, inputValue);
|
||
} else {
|
||
confirm(instance, inputValue);
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {any} value
|
||
*/
|
||
const deny = (instance, value) => {
|
||
const innerParams = privateProps.innerParams.get(instance || undefined);
|
||
if (innerParams.showLoaderOnDeny) {
|
||
showLoading(getDenyButton());
|
||
}
|
||
if (innerParams.preDeny) {
|
||
instance.isAwaitingPromise = true; // Flagging the instance as awaiting a promise so it's own promise's reject/resolve methods doesn't get destroyed until the result from this preDeny's promise is received
|
||
const preDenyPromise = Promise.resolve().then(() => asPromise(innerParams.preDeny(value, innerParams.validationMessage)));
|
||
preDenyPromise.then(preDenyValue => {
|
||
if (preDenyValue === false) {
|
||
instance.hideLoading();
|
||
handleAwaitingPromise(instance);
|
||
} else {
|
||
instance.close({
|
||
isDenied: true,
|
||
value: typeof preDenyValue === 'undefined' ? value : preDenyValue
|
||
});
|
||
}
|
||
}).catch(error => rejectWith(instance || undefined, error));
|
||
} else {
|
||
instance.close({
|
||
isDenied: true,
|
||
value
|
||
});
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {any} value
|
||
*/
|
||
const succeedWith = (instance, value) => {
|
||
instance.close({
|
||
isConfirmed: true,
|
||
value
|
||
});
|
||
};
|
||
|
||
/**
|
||
*
|
||
* @param {SweetAlert} instance
|
||
* @param {string} error
|
||
*/
|
||
const rejectWith = (instance, error) => {
|
||
instance.rejectPromise(error);
|
||
};
|
||
|
||
/**
|
||
*
|
||
* @param {SweetAlert} instance
|
||
* @param {any} value
|
||
*/
|
||
const confirm = (instance, value) => {
|
||
const innerParams = privateProps.innerParams.get(instance || undefined);
|
||
if (innerParams.showLoaderOnConfirm) {
|
||
showLoading();
|
||
}
|
||
if (innerParams.preConfirm) {
|
||
instance.resetValidationMessage();
|
||
instance.isAwaitingPromise = true; // Flagging the instance as awaiting a promise so it's own promise's reject/resolve methods doesn't get destroyed until the result from this preConfirm's promise is received
|
||
const preConfirmPromise = Promise.resolve().then(() => asPromise(innerParams.preConfirm(value, innerParams.validationMessage)));
|
||
preConfirmPromise.then(preConfirmValue => {
|
||
if (isVisible$1(getValidationMessage()) || preConfirmValue === false) {
|
||
instance.hideLoading();
|
||
handleAwaitingPromise(instance);
|
||
} else {
|
||
succeedWith(instance, typeof preConfirmValue === 'undefined' ? value : preConfirmValue);
|
||
}
|
||
}).catch(error => rejectWith(instance || undefined, error));
|
||
} else {
|
||
succeedWith(instance, value);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Hides loader and shows back the button which was hidden by .showLoading()
|
||
*/
|
||
function hideLoading() {
|
||
// do nothing if popup is closed
|
||
const innerParams = privateProps.innerParams.get(this);
|
||
if (!innerParams) {
|
||
return;
|
||
}
|
||
const domCache = privateProps.domCache.get(this);
|
||
hide(domCache.loader);
|
||
if (isToast()) {
|
||
if (innerParams.icon) {
|
||
show(getIcon());
|
||
}
|
||
} else {
|
||
showRelatedButton(domCache);
|
||
}
|
||
removeClass([domCache.popup, domCache.actions], swalClasses.loading);
|
||
domCache.popup.removeAttribute('aria-busy');
|
||
domCache.popup.removeAttribute('data-loading');
|
||
domCache.confirmButton.disabled = false;
|
||
domCache.denyButton.disabled = false;
|
||
domCache.cancelButton.disabled = false;
|
||
}
|
||
const showRelatedButton = domCache => {
|
||
const buttonToReplace = domCache.popup.getElementsByClassName(domCache.loader.getAttribute('data-button-to-replace'));
|
||
if (buttonToReplace.length) {
|
||
show(buttonToReplace[0], 'inline-block');
|
||
} else if (allButtonsAreHidden()) {
|
||
hide(domCache.actions);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Gets the input DOM node, this method works with input parameter.
|
||
*
|
||
* @returns {HTMLInputElement | null}
|
||
*/
|
||
function getInput() {
|
||
const innerParams = privateProps.innerParams.get(this);
|
||
const domCache = privateProps.domCache.get(this);
|
||
if (!domCache) {
|
||
return null;
|
||
}
|
||
return getInput$1(domCache.popup, innerParams.input);
|
||
}
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {string[]} buttons
|
||
* @param {boolean} disabled
|
||
*/
|
||
function setButtonsDisabled(instance, buttons, disabled) {
|
||
const domCache = privateProps.domCache.get(instance);
|
||
buttons.forEach(button => {
|
||
domCache[button].disabled = disabled;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @param {HTMLInputElement | null} input
|
||
* @param {boolean} disabled
|
||
*/
|
||
function setInputDisabled(input, disabled) {
|
||
const popup = getPopup();
|
||
if (!popup || !input) {
|
||
return;
|
||
}
|
||
if (input.type === 'radio') {
|
||
/** @type {NodeListOf<HTMLInputElement>} */
|
||
const radios = popup.querySelectorAll(`[name="${swalClasses.radio}"]`);
|
||
for (let i = 0; i < radios.length; i++) {
|
||
radios[i].disabled = disabled;
|
||
}
|
||
} else {
|
||
input.disabled = disabled;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Enable all the buttons
|
||
* @this {SweetAlert}
|
||
*/
|
||
function enableButtons() {
|
||
setButtonsDisabled(this, ['confirmButton', 'denyButton', 'cancelButton'], false);
|
||
}
|
||
|
||
/**
|
||
* Disable all the buttons
|
||
* @this {SweetAlert}
|
||
*/
|
||
function disableButtons() {
|
||
setButtonsDisabled(this, ['confirmButton', 'denyButton', 'cancelButton'], true);
|
||
}
|
||
|
||
/**
|
||
* Enable the input field
|
||
* @this {SweetAlert}
|
||
*/
|
||
function enableInput() {
|
||
setInputDisabled(this.getInput(), false);
|
||
}
|
||
|
||
/**
|
||
* Disable the input field
|
||
* @this {SweetAlert}
|
||
*/
|
||
function disableInput() {
|
||
setInputDisabled(this.getInput(), true);
|
||
}
|
||
|
||
/**
|
||
* Show block with validation message
|
||
*
|
||
* @param {string} error
|
||
* @this {SweetAlert}
|
||
*/
|
||
function showValidationMessage(error) {
|
||
const domCache = privateProps.domCache.get(this);
|
||
const params = privateProps.innerParams.get(this);
|
||
setInnerHtml(domCache.validationMessage, error);
|
||
domCache.validationMessage.className = swalClasses['validation-message'];
|
||
if (params.customClass && params.customClass.validationMessage) {
|
||
addClass(domCache.validationMessage, params.customClass.validationMessage);
|
||
}
|
||
show(domCache.validationMessage);
|
||
const input = this.getInput();
|
||
if (input) {
|
||
input.setAttribute('aria-invalid', 'true');
|
||
input.setAttribute('aria-describedby', swalClasses['validation-message']);
|
||
focusInput(input);
|
||
addClass(input, swalClasses.inputerror);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Hide block with validation message
|
||
*
|
||
* @this {SweetAlert}
|
||
*/
|
||
function resetValidationMessage() {
|
||
const domCache = privateProps.domCache.get(this);
|
||
if (domCache.validationMessage) {
|
||
hide(domCache.validationMessage);
|
||
}
|
||
const input = this.getInput();
|
||
if (input) {
|
||
input.removeAttribute('aria-invalid');
|
||
input.removeAttribute('aria-describedby');
|
||
removeClass(input, swalClasses.inputerror);
|
||
}
|
||
}
|
||
|
||
const defaultParams = {
|
||
title: '',
|
||
titleText: '',
|
||
text: '',
|
||
html: '',
|
||
footer: '',
|
||
icon: undefined,
|
||
iconColor: undefined,
|
||
iconHtml: undefined,
|
||
template: undefined,
|
||
toast: false,
|
||
draggable: false,
|
||
animation: true,
|
||
theme: 'light',
|
||
showClass: {
|
||
popup: 'swal2-show',
|
||
backdrop: 'swal2-backdrop-show',
|
||
icon: 'swal2-icon-show'
|
||
},
|
||
hideClass: {
|
||
popup: 'swal2-hide',
|
||
backdrop: 'swal2-backdrop-hide',
|
||
icon: 'swal2-icon-hide'
|
||
},
|
||
customClass: {},
|
||
target: 'body',
|
||
color: undefined,
|
||
backdrop: true,
|
||
heightAuto: true,
|
||
allowOutsideClick: true,
|
||
allowEscapeKey: true,
|
||
allowEnterKey: true,
|
||
stopKeydownPropagation: true,
|
||
keydownListenerCapture: false,
|
||
showConfirmButton: true,
|
||
showDenyButton: false,
|
||
showCancelButton: false,
|
||
preConfirm: undefined,
|
||
preDeny: undefined,
|
||
confirmButtonText: 'OK',
|
||
confirmButtonAriaLabel: '',
|
||
confirmButtonColor: undefined,
|
||
denyButtonText: 'No',
|
||
denyButtonAriaLabel: '',
|
||
denyButtonColor: undefined,
|
||
cancelButtonText: 'Cancel',
|
||
cancelButtonAriaLabel: '',
|
||
cancelButtonColor: undefined,
|
||
buttonsStyling: true,
|
||
reverseButtons: false,
|
||
focusConfirm: true,
|
||
focusDeny: false,
|
||
focusCancel: false,
|
||
returnFocus: true,
|
||
showCloseButton: false,
|
||
closeButtonHtml: '×',
|
||
closeButtonAriaLabel: 'Close this dialog',
|
||
loaderHtml: '',
|
||
showLoaderOnConfirm: false,
|
||
showLoaderOnDeny: false,
|
||
imageUrl: undefined,
|
||
imageWidth: undefined,
|
||
imageHeight: undefined,
|
||
imageAlt: '',
|
||
timer: undefined,
|
||
timerProgressBar: false,
|
||
width: undefined,
|
||
padding: undefined,
|
||
background: undefined,
|
||
input: undefined,
|
||
inputPlaceholder: '',
|
||
inputLabel: '',
|
||
inputValue: '',
|
||
inputOptions: {},
|
||
inputAutoFocus: true,
|
||
inputAutoTrim: true,
|
||
inputAttributes: {},
|
||
inputValidator: undefined,
|
||
returnInputValueOnDeny: false,
|
||
validationMessage: undefined,
|
||
grow: false,
|
||
position: 'center',
|
||
progressSteps: [],
|
||
currentProgressStep: undefined,
|
||
progressStepsDistance: undefined,
|
||
willOpen: undefined,
|
||
didOpen: undefined,
|
||
didRender: undefined,
|
||
willClose: undefined,
|
||
didClose: undefined,
|
||
didDestroy: undefined,
|
||
scrollbarPadding: true,
|
||
topLayer: false
|
||
};
|
||
const updatableParams = ['allowEscapeKey', 'allowOutsideClick', 'background', 'buttonsStyling', 'cancelButtonAriaLabel', 'cancelButtonColor', 'cancelButtonText', 'closeButtonAriaLabel', 'closeButtonHtml', 'color', 'confirmButtonAriaLabel', 'confirmButtonColor', 'confirmButtonText', 'currentProgressStep', 'customClass', 'denyButtonAriaLabel', 'denyButtonColor', 'denyButtonText', 'didClose', 'didDestroy', 'draggable', 'footer', 'hideClass', 'html', 'icon', 'iconColor', 'iconHtml', 'imageAlt', 'imageHeight', 'imageUrl', 'imageWidth', 'preConfirm', 'preDeny', 'progressSteps', 'returnFocus', 'reverseButtons', 'showCancelButton', 'showCloseButton', 'showConfirmButton', 'showDenyButton', 'text', 'title', 'titleText', 'theme', 'willClose'];
|
||
|
||
/** @type {Record<string, string | undefined>} */
|
||
const deprecatedParams = {
|
||
allowEnterKey: undefined
|
||
};
|
||
const toastIncompatibleParams = ['allowOutsideClick', 'allowEnterKey', 'backdrop', 'draggable', 'focusConfirm', 'focusDeny', 'focusCancel', 'returnFocus', 'heightAuto', 'keydownListenerCapture'];
|
||
|
||
/**
|
||
* Is valid parameter
|
||
*
|
||
* @param {string} paramName
|
||
* @returns {boolean}
|
||
*/
|
||
const isValidParameter = paramName => {
|
||
return Object.prototype.hasOwnProperty.call(defaultParams, paramName);
|
||
};
|
||
|
||
/**
|
||
* Is valid parameter for Swal.update() method
|
||
*
|
||
* @param {string} paramName
|
||
* @returns {boolean}
|
||
*/
|
||
const isUpdatableParameter = paramName => {
|
||
return updatableParams.indexOf(paramName) !== -1;
|
||
};
|
||
|
||
/**
|
||
* Is deprecated parameter
|
||
*
|
||
* @param {string} paramName
|
||
* @returns {string | undefined}
|
||
*/
|
||
const isDeprecatedParameter = paramName => {
|
||
return deprecatedParams[paramName];
|
||
};
|
||
|
||
/**
|
||
* @param {string} param
|
||
*/
|
||
const checkIfParamIsValid = param => {
|
||
if (!isValidParameter(param)) {
|
||
warn(`Unknown parameter "${param}"`);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {string} param
|
||
*/
|
||
const checkIfToastParamIsValid = param => {
|
||
if (toastIncompatibleParams.includes(param)) {
|
||
warn(`The parameter "${param}" is incompatible with toasts`);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {string} param
|
||
*/
|
||
const checkIfParamIsDeprecated = param => {
|
||
const isDeprecated = isDeprecatedParameter(param);
|
||
if (isDeprecated) {
|
||
warnAboutDeprecation(param, isDeprecated);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Show relevant warnings for given params
|
||
*
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const showWarningsForParams = params => {
|
||
if (params.backdrop === false && params.allowOutsideClick) {
|
||
warn('"allowOutsideClick" parameter requires `backdrop` parameter to be set to `true`');
|
||
}
|
||
if (params.theme && !['light', 'dark', 'auto', 'minimal', 'borderless', 'embed-iframe', 'bulma', 'bulma-light', 'bulma-dark'].includes(params.theme)) {
|
||
warn(`Invalid theme "${params.theme}"`);
|
||
}
|
||
for (const param in params) {
|
||
checkIfParamIsValid(param);
|
||
if (params.toast) {
|
||
checkIfToastParamIsValid(param);
|
||
}
|
||
checkIfParamIsDeprecated(param);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Updates popup parameters.
|
||
*
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
function update(params) {
|
||
const container = getContainer();
|
||
const popup = getPopup();
|
||
const innerParams = privateProps.innerParams.get(this);
|
||
if (!popup || hasClass(popup, innerParams.hideClass.popup)) {
|
||
warn(`You're trying to update the closed or closing popup, that won't work. Use the update() method in preConfirm parameter or show a new popup.`);
|
||
return;
|
||
}
|
||
const validUpdatableParams = filterValidParams(params);
|
||
const updatedParams = Object.assign({}, innerParams, validUpdatableParams);
|
||
showWarningsForParams(updatedParams);
|
||
container.dataset['swal2Theme'] = updatedParams.theme;
|
||
render(this, updatedParams);
|
||
privateProps.innerParams.set(this, updatedParams);
|
||
Object.defineProperties(this, {
|
||
params: {
|
||
value: Object.assign({}, this.params, params),
|
||
writable: false,
|
||
enumerable: true
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} params
|
||
* @returns {SweetAlertOptions}
|
||
*/
|
||
const filterValidParams = params => {
|
||
const validUpdatableParams = {};
|
||
Object.keys(params).forEach(param => {
|
||
if (isUpdatableParameter(param)) {
|
||
validUpdatableParams[param] = params[param];
|
||
} else {
|
||
warn(`Invalid parameter to update: ${param}`);
|
||
}
|
||
});
|
||
return validUpdatableParams;
|
||
};
|
||
|
||
/**
|
||
* Dispose the current SweetAlert2 instance
|
||
*/
|
||
function _destroy() {
|
||
const domCache = privateProps.domCache.get(this);
|
||
const innerParams = privateProps.innerParams.get(this);
|
||
if (!innerParams) {
|
||
disposeWeakMaps(this); // The WeakMaps might have been partly destroyed, we must recall it to dispose any remaining WeakMaps #2335
|
||
return; // This instance has already been destroyed
|
||
}
|
||
|
||
// Check if there is another Swal closing
|
||
if (domCache.popup && globalState.swalCloseEventFinishedCallback) {
|
||
globalState.swalCloseEventFinishedCallback();
|
||
delete globalState.swalCloseEventFinishedCallback;
|
||
}
|
||
if (typeof innerParams.didDestroy === 'function') {
|
||
innerParams.didDestroy();
|
||
}
|
||
globalState.eventEmitter.emit('didDestroy');
|
||
disposeSwal(this);
|
||
}
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
*/
|
||
const disposeSwal = instance => {
|
||
disposeWeakMaps(instance);
|
||
// Unset this.params so GC will dispose it (#1569)
|
||
delete instance.params;
|
||
// Unset globalState props so GC will dispose globalState (#1569)
|
||
delete globalState.keydownHandler;
|
||
delete globalState.keydownTarget;
|
||
// Unset currentInstance
|
||
delete globalState.currentInstance;
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
*/
|
||
const disposeWeakMaps = instance => {
|
||
// If the current instance is awaiting a promise result, we keep the privateMethods to call them once the promise result is retrieved #2335
|
||
if (instance.isAwaitingPromise) {
|
||
unsetWeakMaps(privateProps, instance);
|
||
instance.isAwaitingPromise = true;
|
||
} else {
|
||
unsetWeakMaps(privateMethods, instance);
|
||
unsetWeakMaps(privateProps, instance);
|
||
delete instance.isAwaitingPromise;
|
||
// Unset instance methods
|
||
delete instance.disableButtons;
|
||
delete instance.enableButtons;
|
||
delete instance.getInput;
|
||
delete instance.disableInput;
|
||
delete instance.enableInput;
|
||
delete instance.hideLoading;
|
||
delete instance.disableLoading;
|
||
delete instance.showValidationMessage;
|
||
delete instance.resetValidationMessage;
|
||
delete instance.close;
|
||
delete instance.closePopup;
|
||
delete instance.closeModal;
|
||
delete instance.closeToast;
|
||
delete instance.rejectPromise;
|
||
delete instance.update;
|
||
delete instance._destroy;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {object} obj
|
||
* @param {SweetAlert} instance
|
||
*/
|
||
const unsetWeakMaps = (obj, instance) => {
|
||
for (const i in obj) {
|
||
obj[i].delete(instance);
|
||
}
|
||
};
|
||
|
||
var instanceMethods = /*#__PURE__*/Object.freeze({
|
||
__proto__: null,
|
||
_destroy: _destroy,
|
||
close: close,
|
||
closeModal: close,
|
||
closePopup: close,
|
||
closeToast: close,
|
||
disableButtons: disableButtons,
|
||
disableInput: disableInput,
|
||
disableLoading: hideLoading,
|
||
enableButtons: enableButtons,
|
||
enableInput: enableInput,
|
||
getInput: getInput,
|
||
handleAwaitingPromise: handleAwaitingPromise,
|
||
hideLoading: hideLoading,
|
||
rejectPromise: rejectPromise,
|
||
resetValidationMessage: resetValidationMessage,
|
||
showValidationMessage: showValidationMessage,
|
||
update: update
|
||
});
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} innerParams
|
||
* @param {DomCache} domCache
|
||
* @param {Function} dismissWith
|
||
*/
|
||
const handlePopupClick = (innerParams, domCache, dismissWith) => {
|
||
if (innerParams.toast) {
|
||
handleToastClick(innerParams, domCache, dismissWith);
|
||
} else {
|
||
// Ignore click events that had mousedown on the popup but mouseup on the container
|
||
// This can happen when the user drags a slider
|
||
handleModalMousedown(domCache);
|
||
|
||
// Ignore click events that had mousedown on the container but mouseup on the popup
|
||
handleContainerMousedown(domCache);
|
||
handleModalClick(innerParams, domCache, dismissWith);
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} innerParams
|
||
* @param {DomCache} domCache
|
||
* @param {Function} dismissWith
|
||
*/
|
||
const handleToastClick = (innerParams, domCache, dismissWith) => {
|
||
// Closing toast by internal click
|
||
domCache.popup.onclick = () => {
|
||
if (innerParams && (isAnyButtonShown(innerParams) || innerParams.timer || innerParams.input)) {
|
||
return;
|
||
}
|
||
dismissWith(DismissReason.close);
|
||
};
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} innerParams
|
||
* @returns {boolean}
|
||
*/
|
||
const isAnyButtonShown = innerParams => {
|
||
return !!(innerParams.showConfirmButton || innerParams.showDenyButton || innerParams.showCancelButton || innerParams.showCloseButton);
|
||
};
|
||
let ignoreOutsideClick = false;
|
||
|
||
/**
|
||
* @param {DomCache} domCache
|
||
*/
|
||
const handleModalMousedown = domCache => {
|
||
domCache.popup.onmousedown = () => {
|
||
domCache.container.onmouseup = function (e) {
|
||
domCache.container.onmouseup = () => {};
|
||
// We only check if the mouseup target is the container because usually it doesn't
|
||
// have any other direct children aside of the popup
|
||
if (e.target === domCache.container) {
|
||
ignoreOutsideClick = true;
|
||
}
|
||
};
|
||
};
|
||
};
|
||
|
||
/**
|
||
* @param {DomCache} domCache
|
||
*/
|
||
const handleContainerMousedown = domCache => {
|
||
domCache.container.onmousedown = e => {
|
||
// prevent the modal text from being selected on double click on the container (allowOutsideClick: false)
|
||
if (e.target === domCache.container) {
|
||
e.preventDefault();
|
||
}
|
||
domCache.popup.onmouseup = function (e) {
|
||
domCache.popup.onmouseup = () => {};
|
||
// We also need to check if the mouseup target is a child of the popup
|
||
if (e.target === domCache.popup || e.target instanceof HTMLElement && domCache.popup.contains(e.target)) {
|
||
ignoreOutsideClick = true;
|
||
}
|
||
};
|
||
};
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} innerParams
|
||
* @param {DomCache} domCache
|
||
* @param {Function} dismissWith
|
||
*/
|
||
const handleModalClick = (innerParams, domCache, dismissWith) => {
|
||
domCache.container.onclick = e => {
|
||
if (ignoreOutsideClick) {
|
||
ignoreOutsideClick = false;
|
||
return;
|
||
}
|
||
if (e.target === domCache.container && callIfFunction(innerParams.allowOutsideClick)) {
|
||
dismissWith(DismissReason.backdrop);
|
||
}
|
||
};
|
||
};
|
||
|
||
const isJqueryElement = elem => typeof elem === 'object' && elem.jquery;
|
||
const isElement = elem => elem instanceof Element || isJqueryElement(elem);
|
||
const argsToParams = args => {
|
||
const params = {};
|
||
if (typeof args[0] === 'object' && !isElement(args[0])) {
|
||
Object.assign(params, args[0]);
|
||
} else {
|
||
['title', 'html', 'icon'].forEach((name, index) => {
|
||
const arg = args[index];
|
||
if (typeof arg === 'string' || isElement(arg)) {
|
||
params[name] = arg;
|
||
} else if (arg !== undefined) {
|
||
error(`Unexpected type of ${name}! Expected "string" or "Element", got ${typeof arg}`);
|
||
}
|
||
});
|
||
}
|
||
return params;
|
||
};
|
||
|
||
/**
|
||
* Main method to create a new SweetAlert2 popup
|
||
*
|
||
* @param {...SweetAlertOptions} args
|
||
* @returns {Promise<SweetAlertResult>}
|
||
*/
|
||
function fire(...args) {
|
||
return new this(...args);
|
||
}
|
||
|
||
/**
|
||
* Returns an extended version of `Swal` containing `params` as defaults.
|
||
* Useful for reusing Swal configuration.
|
||
*
|
||
* For example:
|
||
*
|
||
* Before:
|
||
* const textPromptOptions = { input: 'text', showCancelButton: true }
|
||
* const {value: firstName} = await Swal.fire({ ...textPromptOptions, title: 'What is your first name?' })
|
||
* const {value: lastName} = await Swal.fire({ ...textPromptOptions, title: 'What is your last name?' })
|
||
*
|
||
* After:
|
||
* const TextPrompt = Swal.mixin({ input: 'text', showCancelButton: true })
|
||
* const {value: firstName} = await TextPrompt('What is your first name?')
|
||
* const {value: lastName} = await TextPrompt('What is your last name?')
|
||
*
|
||
* @param {SweetAlertOptions} mixinParams
|
||
* @returns {SweetAlert}
|
||
*/
|
||
function mixin(mixinParams) {
|
||
class MixinSwal extends this {
|
||
_main(params, priorityMixinParams) {
|
||
return super._main(params, Object.assign({}, mixinParams, priorityMixinParams));
|
||
}
|
||
}
|
||
// @ts-ignore
|
||
return MixinSwal;
|
||
}
|
||
|
||
/**
|
||
* If `timer` parameter is set, returns number of milliseconds of timer remained.
|
||
* Otherwise, returns undefined.
|
||
*
|
||
* @returns {number | undefined}
|
||
*/
|
||
const getTimerLeft = () => {
|
||
return globalState.timeout && globalState.timeout.getTimerLeft();
|
||
};
|
||
|
||
/**
|
||
* Stop timer. Returns number of milliseconds of timer remained.
|
||
* If `timer` parameter isn't set, returns undefined.
|
||
*
|
||
* @returns {number | undefined}
|
||
*/
|
||
const stopTimer = () => {
|
||
if (globalState.timeout) {
|
||
stopTimerProgressBar();
|
||
return globalState.timeout.stop();
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Resume timer. Returns number of milliseconds of timer remained.
|
||
* If `timer` parameter isn't set, returns undefined.
|
||
*
|
||
* @returns {number | undefined}
|
||
*/
|
||
const resumeTimer = () => {
|
||
if (globalState.timeout) {
|
||
const remaining = globalState.timeout.start();
|
||
animateTimerProgressBar(remaining);
|
||
return remaining;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Resume timer. Returns number of milliseconds of timer remained.
|
||
* If `timer` parameter isn't set, returns undefined.
|
||
*
|
||
* @returns {number | undefined}
|
||
*/
|
||
const toggleTimer = () => {
|
||
const timer = globalState.timeout;
|
||
return timer && (timer.running ? stopTimer() : resumeTimer());
|
||
};
|
||
|
||
/**
|
||
* Increase timer. Returns number of milliseconds of an updated timer.
|
||
* If `timer` parameter isn't set, returns undefined.
|
||
*
|
||
* @param {number} ms
|
||
* @returns {number | undefined}
|
||
*/
|
||
const increaseTimer = ms => {
|
||
if (globalState.timeout) {
|
||
const remaining = globalState.timeout.increase(ms);
|
||
animateTimerProgressBar(remaining, true);
|
||
return remaining;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Check if timer is running. Returns true if timer is running
|
||
* or false if timer is paused or stopped.
|
||
* If `timer` parameter isn't set, returns undefined
|
||
*
|
||
* @returns {boolean}
|
||
*/
|
||
const isTimerRunning = () => {
|
||
return !!(globalState.timeout && globalState.timeout.isRunning());
|
||
};
|
||
|
||
let bodyClickListenerAdded = false;
|
||
const clickHandlers = {};
|
||
|
||
/**
|
||
* @param {string} attr
|
||
*/
|
||
function bindClickHandler(attr = 'data-swal-template') {
|
||
clickHandlers[attr] = this;
|
||
if (!bodyClickListenerAdded) {
|
||
document.body.addEventListener('click', bodyClickListener);
|
||
bodyClickListenerAdded = true;
|
||
}
|
||
}
|
||
const bodyClickListener = event => {
|
||
for (let el = event.target; el && el !== document; el = el.parentNode) {
|
||
for (const attr in clickHandlers) {
|
||
const template = el.getAttribute(attr);
|
||
if (template) {
|
||
clickHandlers[attr].fire({
|
||
template
|
||
});
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
// Source: https://gist.github.com/mudge/5830382?permalink_comment_id=2691957#gistcomment-2691957
|
||
|
||
class EventEmitter {
|
||
constructor() {
|
||
/** @type {Events} */
|
||
this.events = {};
|
||
}
|
||
|
||
/**
|
||
* @param {string} eventName
|
||
* @returns {EventHandlers}
|
||
*/
|
||
_getHandlersByEventName(eventName) {
|
||
if (typeof this.events[eventName] === 'undefined') {
|
||
// not Set because we need to keep the FIFO order
|
||
// https://github.com/sweetalert2/sweetalert2/pull/2763#discussion_r1748990334
|
||
this.events[eventName] = [];
|
||
}
|
||
return this.events[eventName];
|
||
}
|
||
|
||
/**
|
||
* @param {string} eventName
|
||
* @param {EventHandler} eventHandler
|
||
*/
|
||
on(eventName, eventHandler) {
|
||
const currentHandlers = this._getHandlersByEventName(eventName);
|
||
if (!currentHandlers.includes(eventHandler)) {
|
||
currentHandlers.push(eventHandler);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {string} eventName
|
||
* @param {EventHandler} eventHandler
|
||
*/
|
||
once(eventName, eventHandler) {
|
||
/**
|
||
* @param {Array} args
|
||
*/
|
||
const onceFn = (...args) => {
|
||
this.removeListener(eventName, onceFn);
|
||
eventHandler.apply(this, args);
|
||
};
|
||
this.on(eventName, onceFn);
|
||
}
|
||
|
||
/**
|
||
* @param {string} eventName
|
||
* @param {Array} args
|
||
*/
|
||
emit(eventName, ...args) {
|
||
this._getHandlersByEventName(eventName).forEach(
|
||
/**
|
||
* @param {EventHandler} eventHandler
|
||
*/
|
||
eventHandler => {
|
||
try {
|
||
eventHandler.apply(this, args);
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @param {string} eventName
|
||
* @param {EventHandler} eventHandler
|
||
*/
|
||
removeListener(eventName, eventHandler) {
|
||
const currentHandlers = this._getHandlersByEventName(eventName);
|
||
const index = currentHandlers.indexOf(eventHandler);
|
||
if (index > -1) {
|
||
currentHandlers.splice(index, 1);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {string} eventName
|
||
*/
|
||
removeAllListeners(eventName) {
|
||
if (this.events[eventName] !== undefined) {
|
||
// https://github.com/sweetalert2/sweetalert2/pull/2763#discussion_r1749239222
|
||
this.events[eventName].length = 0;
|
||
}
|
||
}
|
||
reset() {
|
||
this.events = {};
|
||
}
|
||
}
|
||
|
||
globalState.eventEmitter = new EventEmitter();
|
||
|
||
/**
|
||
* @param {string} eventName
|
||
* @param {EventHandler} eventHandler
|
||
*/
|
||
const on = (eventName, eventHandler) => {
|
||
globalState.eventEmitter.on(eventName, eventHandler);
|
||
};
|
||
|
||
/**
|
||
* @param {string} eventName
|
||
* @param {EventHandler} eventHandler
|
||
*/
|
||
const once = (eventName, eventHandler) => {
|
||
globalState.eventEmitter.once(eventName, eventHandler);
|
||
};
|
||
|
||
/**
|
||
* @param {string} [eventName]
|
||
* @param {EventHandler} [eventHandler]
|
||
*/
|
||
const off = (eventName, eventHandler) => {
|
||
// Remove all handlers for all events
|
||
if (!eventName) {
|
||
globalState.eventEmitter.reset();
|
||
return;
|
||
}
|
||
if (eventHandler) {
|
||
// Remove a specific handler
|
||
globalState.eventEmitter.removeListener(eventName, eventHandler);
|
||
} else {
|
||
// Remove all handlers for a specific event
|
||
globalState.eventEmitter.removeAllListeners(eventName);
|
||
}
|
||
};
|
||
|
||
var staticMethods = /*#__PURE__*/Object.freeze({
|
||
__proto__: null,
|
||
argsToParams: argsToParams,
|
||
bindClickHandler: bindClickHandler,
|
||
clickCancel: clickCancel,
|
||
clickConfirm: clickConfirm,
|
||
clickDeny: clickDeny,
|
||
enableLoading: showLoading,
|
||
fire: fire,
|
||
getActions: getActions,
|
||
getCancelButton: getCancelButton,
|
||
getCloseButton: getCloseButton,
|
||
getConfirmButton: getConfirmButton,
|
||
getContainer: getContainer,
|
||
getDenyButton: getDenyButton,
|
||
getFocusableElements: getFocusableElements,
|
||
getFooter: getFooter,
|
||
getHtmlContainer: getHtmlContainer,
|
||
getIcon: getIcon,
|
||
getIconContent: getIconContent,
|
||
getImage: getImage,
|
||
getInputLabel: getInputLabel,
|
||
getLoader: getLoader,
|
||
getPopup: getPopup,
|
||
getProgressSteps: getProgressSteps,
|
||
getTimerLeft: getTimerLeft,
|
||
getTimerProgressBar: getTimerProgressBar,
|
||
getTitle: getTitle,
|
||
getValidationMessage: getValidationMessage,
|
||
increaseTimer: increaseTimer,
|
||
isDeprecatedParameter: isDeprecatedParameter,
|
||
isLoading: isLoading,
|
||
isTimerRunning: isTimerRunning,
|
||
isUpdatableParameter: isUpdatableParameter,
|
||
isValidParameter: isValidParameter,
|
||
isVisible: isVisible,
|
||
mixin: mixin,
|
||
off: off,
|
||
on: on,
|
||
once: once,
|
||
resumeTimer: resumeTimer,
|
||
showLoading: showLoading,
|
||
stopTimer: stopTimer,
|
||
toggleTimer: toggleTimer
|
||
});
|
||
|
||
class Timer {
|
||
/**
|
||
* @param {Function} callback
|
||
* @param {number} delay
|
||
*/
|
||
constructor(callback, delay) {
|
||
this.callback = callback;
|
||
this.remaining = delay;
|
||
this.running = false;
|
||
this.start();
|
||
}
|
||
|
||
/**
|
||
* @returns {number}
|
||
*/
|
||
start() {
|
||
if (!this.running) {
|
||
this.running = true;
|
||
this.started = new Date();
|
||
this.id = setTimeout(this.callback, this.remaining);
|
||
}
|
||
return this.remaining;
|
||
}
|
||
|
||
/**
|
||
* @returns {number}
|
||
*/
|
||
stop() {
|
||
if (this.started && this.running) {
|
||
this.running = false;
|
||
clearTimeout(this.id);
|
||
this.remaining -= new Date().getTime() - this.started.getTime();
|
||
}
|
||
return this.remaining;
|
||
}
|
||
|
||
/**
|
||
* @param {number} n
|
||
* @returns {number}
|
||
*/
|
||
increase(n) {
|
||
const running = this.running;
|
||
if (running) {
|
||
this.stop();
|
||
}
|
||
this.remaining += n;
|
||
if (running) {
|
||
this.start();
|
||
}
|
||
return this.remaining;
|
||
}
|
||
|
||
/**
|
||
* @returns {number}
|
||
*/
|
||
getTimerLeft() {
|
||
if (this.running) {
|
||
this.stop();
|
||
this.start();
|
||
}
|
||
return this.remaining;
|
||
}
|
||
|
||
/**
|
||
* @returns {boolean}
|
||
*/
|
||
isRunning() {
|
||
return this.running;
|
||
}
|
||
}
|
||
|
||
const swalStringParams = ['swal-title', 'swal-html', 'swal-footer'];
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} params
|
||
* @returns {SweetAlertOptions}
|
||
*/
|
||
const getTemplateParams = params => {
|
||
const template = typeof params.template === 'string' ? (/** @type {HTMLTemplateElement} */document.querySelector(params.template)) : params.template;
|
||
if (!template) {
|
||
return {};
|
||
}
|
||
/** @type {DocumentFragment} */
|
||
const templateContent = template.content;
|
||
showWarningsForElements(templateContent);
|
||
const result = Object.assign(getSwalParams(templateContent), getSwalFunctionParams(templateContent), getSwalButtons(templateContent), getSwalImage(templateContent), getSwalIcon(templateContent), getSwalInput(templateContent), getSwalStringParams(templateContent, swalStringParams));
|
||
return result;
|
||
};
|
||
|
||
/**
|
||
* @param {DocumentFragment} templateContent
|
||
* @returns {Record<string, any>}
|
||
*/
|
||
const getSwalParams = templateContent => {
|
||
/** @type {Record<string, any>} */
|
||
const result = {};
|
||
/** @type {HTMLElement[]} */
|
||
const swalParams = Array.from(templateContent.querySelectorAll('swal-param'));
|
||
swalParams.forEach(param => {
|
||
showWarningsForAttributes(param, ['name', 'value']);
|
||
const paramName = /** @type {keyof SweetAlertOptions} */param.getAttribute('name');
|
||
const value = param.getAttribute('value');
|
||
if (!paramName || !value) {
|
||
return;
|
||
}
|
||
if (typeof defaultParams[paramName] === 'boolean') {
|
||
result[paramName] = value !== 'false';
|
||
} else if (typeof defaultParams[paramName] === 'object') {
|
||
result[paramName] = JSON.parse(value);
|
||
} else {
|
||
result[paramName] = value;
|
||
}
|
||
});
|
||
return result;
|
||
};
|
||
|
||
/**
|
||
* @param {DocumentFragment} templateContent
|
||
* @returns {Record<string, any>}
|
||
*/
|
||
const getSwalFunctionParams = templateContent => {
|
||
/** @type {Record<string, any>} */
|
||
const result = {};
|
||
/** @type {HTMLElement[]} */
|
||
const swalFunctions = Array.from(templateContent.querySelectorAll('swal-function-param'));
|
||
swalFunctions.forEach(param => {
|
||
const paramName = /** @type {keyof SweetAlertOptions} */param.getAttribute('name');
|
||
const value = param.getAttribute('value');
|
||
if (!paramName || !value) {
|
||
return;
|
||
}
|
||
result[paramName] = new Function(`return ${value}`)();
|
||
});
|
||
return result;
|
||
};
|
||
|
||
/**
|
||
* @param {DocumentFragment} templateContent
|
||
* @returns {Record<string, any>}
|
||
*/
|
||
const getSwalButtons = templateContent => {
|
||
/** @type {Record<string, any>} */
|
||
const result = {};
|
||
/** @type {HTMLElement[]} */
|
||
const swalButtons = Array.from(templateContent.querySelectorAll('swal-button'));
|
||
swalButtons.forEach(button => {
|
||
showWarningsForAttributes(button, ['type', 'color', 'aria-label']);
|
||
const type = button.getAttribute('type');
|
||
if (!type || !['confirm', 'cancel', 'deny'].includes(type)) {
|
||
return;
|
||
}
|
||
result[`${type}ButtonText`] = button.innerHTML;
|
||
result[`show${capitalizeFirstLetter(type)}Button`] = true;
|
||
if (button.hasAttribute('color')) {
|
||
result[`${type}ButtonColor`] = button.getAttribute('color');
|
||
}
|
||
if (button.hasAttribute('aria-label')) {
|
||
result[`${type}ButtonAriaLabel`] = button.getAttribute('aria-label');
|
||
}
|
||
});
|
||
return result;
|
||
};
|
||
|
||
/**
|
||
* @param {DocumentFragment} templateContent
|
||
* @returns {Pick<SweetAlertOptions, 'imageUrl' | 'imageWidth' | 'imageHeight' | 'imageAlt'>}
|
||
*/
|
||
const getSwalImage = templateContent => {
|
||
const result = {};
|
||
/** @type {HTMLElement | null} */
|
||
const image = templateContent.querySelector('swal-image');
|
||
if (image) {
|
||
showWarningsForAttributes(image, ['src', 'width', 'height', 'alt']);
|
||
if (image.hasAttribute('src')) {
|
||
result.imageUrl = image.getAttribute('src') || undefined;
|
||
}
|
||
if (image.hasAttribute('width')) {
|
||
result.imageWidth = image.getAttribute('width') || undefined;
|
||
}
|
||
if (image.hasAttribute('height')) {
|
||
result.imageHeight = image.getAttribute('height') || undefined;
|
||
}
|
||
if (image.hasAttribute('alt')) {
|
||
result.imageAlt = image.getAttribute('alt') || undefined;
|
||
}
|
||
}
|
||
return result;
|
||
};
|
||
|
||
/**
|
||
* @param {DocumentFragment} templateContent
|
||
* @returns {Record<string, any>}
|
||
*/
|
||
const getSwalIcon = templateContent => {
|
||
const result = {};
|
||
/** @type {HTMLElement | null} */
|
||
const icon = templateContent.querySelector('swal-icon');
|
||
if (icon) {
|
||
showWarningsForAttributes(icon, ['type', 'color']);
|
||
if (icon.hasAttribute('type')) {
|
||
result.icon = icon.getAttribute('type');
|
||
}
|
||
if (icon.hasAttribute('color')) {
|
||
result.iconColor = icon.getAttribute('color');
|
||
}
|
||
result.iconHtml = icon.innerHTML;
|
||
}
|
||
return result;
|
||
};
|
||
|
||
/**
|
||
* @param {DocumentFragment} templateContent
|
||
* @returns {Record<string, any>}
|
||
*/
|
||
const getSwalInput = templateContent => {
|
||
/** @type {Record<string, any>} */
|
||
const result = {};
|
||
/** @type {HTMLElement | null} */
|
||
const input = templateContent.querySelector('swal-input');
|
||
if (input) {
|
||
showWarningsForAttributes(input, ['type', 'label', 'placeholder', 'value']);
|
||
result.input = input.getAttribute('type') || 'text';
|
||
if (input.hasAttribute('label')) {
|
||
result.inputLabel = input.getAttribute('label');
|
||
}
|
||
if (input.hasAttribute('placeholder')) {
|
||
result.inputPlaceholder = input.getAttribute('placeholder');
|
||
}
|
||
if (input.hasAttribute('value')) {
|
||
result.inputValue = input.getAttribute('value');
|
||
}
|
||
}
|
||
/** @type {HTMLElement[]} */
|
||
const inputOptions = Array.from(templateContent.querySelectorAll('swal-input-option'));
|
||
if (inputOptions.length) {
|
||
result.inputOptions = {};
|
||
inputOptions.forEach(option => {
|
||
showWarningsForAttributes(option, ['value']);
|
||
const optionValue = option.getAttribute('value');
|
||
if (!optionValue) {
|
||
return;
|
||
}
|
||
const optionName = option.innerHTML;
|
||
result.inputOptions[optionValue] = optionName;
|
||
});
|
||
}
|
||
return result;
|
||
};
|
||
|
||
/**
|
||
* @param {DocumentFragment} templateContent
|
||
* @param {string[]} paramNames
|
||
* @returns {Record<string, any>}
|
||
*/
|
||
const getSwalStringParams = (templateContent, paramNames) => {
|
||
/** @type {Record<string, any>} */
|
||
const result = {};
|
||
for (const i in paramNames) {
|
||
const paramName = paramNames[i];
|
||
/** @type {HTMLElement | null} */
|
||
const tag = templateContent.querySelector(paramName);
|
||
if (tag) {
|
||
showWarningsForAttributes(tag, []);
|
||
result[paramName.replace(/^swal-/, '')] = tag.innerHTML.trim();
|
||
}
|
||
}
|
||
return result;
|
||
};
|
||
|
||
/**
|
||
* @param {DocumentFragment} templateContent
|
||
*/
|
||
const showWarningsForElements = templateContent => {
|
||
const allowedElements = swalStringParams.concat(['swal-param', 'swal-function-param', 'swal-button', 'swal-image', 'swal-icon', 'swal-input', 'swal-input-option']);
|
||
Array.from(templateContent.children).forEach(el => {
|
||
const tagName = el.tagName.toLowerCase();
|
||
if (!allowedElements.includes(tagName)) {
|
||
warn(`Unrecognized element <${tagName}>`);
|
||
}
|
||
});
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} el
|
||
* @param {string[]} allowedAttributes
|
||
*/
|
||
const showWarningsForAttributes = (el, allowedAttributes) => {
|
||
Array.from(el.attributes).forEach(attribute => {
|
||
if (allowedAttributes.indexOf(attribute.name) === -1) {
|
||
warn([`Unrecognized attribute "${attribute.name}" on <${el.tagName.toLowerCase()}>.`, `${allowedAttributes.length ? `Allowed attributes are: ${allowedAttributes.join(', ')}` : 'To set the value, use HTML within the element.'}`]);
|
||
}
|
||
});
|
||
};
|
||
|
||
const SHOW_CLASS_TIMEOUT = 10;
|
||
|
||
/**
|
||
* Open popup, add necessary classes and styles, fix scrollbar
|
||
*
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const openPopup = params => {
|
||
const container = getContainer();
|
||
const popup = getPopup();
|
||
if (typeof params.willOpen === 'function') {
|
||
params.willOpen(popup);
|
||
}
|
||
globalState.eventEmitter.emit('willOpen', popup);
|
||
const bodyStyles = window.getComputedStyle(document.body);
|
||
const initialBodyOverflow = bodyStyles.overflowY;
|
||
addClasses(container, popup, params);
|
||
|
||
// scrolling is 'hidden' until animation is done, after that 'auto'
|
||
setTimeout(() => {
|
||
setScrollingVisibility(container, popup);
|
||
}, SHOW_CLASS_TIMEOUT);
|
||
if (isModal()) {
|
||
fixScrollContainer(container, params.scrollbarPadding, initialBodyOverflow);
|
||
setAriaHidden();
|
||
}
|
||
if (!isToast() && !globalState.previousActiveElement) {
|
||
globalState.previousActiveElement = document.activeElement;
|
||
}
|
||
if (typeof params.didOpen === 'function') {
|
||
setTimeout(() => params.didOpen(popup));
|
||
}
|
||
globalState.eventEmitter.emit('didOpen', popup);
|
||
removeClass(container, swalClasses['no-transition']);
|
||
};
|
||
|
||
/**
|
||
* @param {AnimationEvent} event
|
||
*/
|
||
const swalOpenAnimationFinished = event => {
|
||
const popup = getPopup();
|
||
if (event.target !== popup) {
|
||
return;
|
||
}
|
||
const container = getContainer();
|
||
popup.removeEventListener('animationend', swalOpenAnimationFinished);
|
||
popup.removeEventListener('transitionend', swalOpenAnimationFinished);
|
||
container.style.overflowY = 'auto';
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} container
|
||
* @param {HTMLElement} popup
|
||
*/
|
||
const setScrollingVisibility = (container, popup) => {
|
||
if (hasCssAnimation(popup)) {
|
||
container.style.overflowY = 'hidden';
|
||
popup.addEventListener('animationend', swalOpenAnimationFinished);
|
||
popup.addEventListener('transitionend', swalOpenAnimationFinished);
|
||
} else {
|
||
container.style.overflowY = 'auto';
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} container
|
||
* @param {boolean} scrollbarPadding
|
||
* @param {string} initialBodyOverflow
|
||
*/
|
||
const fixScrollContainer = (container, scrollbarPadding, initialBodyOverflow) => {
|
||
iOSfix();
|
||
if (scrollbarPadding && initialBodyOverflow !== 'hidden') {
|
||
replaceScrollbarWithPadding(initialBodyOverflow);
|
||
}
|
||
|
||
// sweetalert2/issues/1247
|
||
setTimeout(() => {
|
||
container.scrollTop = 0;
|
||
});
|
||
};
|
||
|
||
/**
|
||
* @param {HTMLElement} container
|
||
* @param {HTMLElement} popup
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
const addClasses = (container, popup, params) => {
|
||
addClass(container, params.showClass.backdrop);
|
||
if (params.animation) {
|
||
// this workaround with opacity is needed for https://github.com/sweetalert2/sweetalert2/issues/2059
|
||
popup.style.setProperty('opacity', '0', 'important');
|
||
show(popup, 'grid');
|
||
setTimeout(() => {
|
||
// Animate popup right after showing it
|
||
addClass(popup, params.showClass.popup);
|
||
// and remove the opacity workaround
|
||
popup.style.removeProperty('opacity');
|
||
}, SHOW_CLASS_TIMEOUT); // 10ms in order to fix #2062
|
||
} else {
|
||
show(popup, 'grid');
|
||
}
|
||
addClass([document.documentElement, document.body], swalClasses.shown);
|
||
if (params.heightAuto && params.backdrop && !params.toast) {
|
||
addClass([document.documentElement, document.body], swalClasses['height-auto']);
|
||
}
|
||
};
|
||
|
||
var defaultInputValidators = {
|
||
/**
|
||
* @param {string} string
|
||
* @param {string} [validationMessage]
|
||
* @returns {Promise<string | void>}
|
||
*/
|
||
email: (string, validationMessage) => {
|
||
return /^[a-zA-Z0-9.+_'-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]+$/.test(string) ? Promise.resolve() : Promise.resolve(validationMessage || 'Invalid email address');
|
||
},
|
||
/**
|
||
* @param {string} string
|
||
* @param {string} [validationMessage]
|
||
* @returns {Promise<string | void>}
|
||
*/
|
||
url: (string, validationMessage) => {
|
||
// taken from https://stackoverflow.com/a/3809435 with a small change from #1306 and #2013
|
||
return /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-z]{2,63}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)$/.test(string) ? Promise.resolve() : Promise.resolve(validationMessage || 'Invalid URL');
|
||
}
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
function setDefaultInputValidators(params) {
|
||
// Use default `inputValidator` for supported input types if not provided
|
||
if (params.inputValidator) {
|
||
return;
|
||
}
|
||
if (params.input === 'email') {
|
||
params.inputValidator = defaultInputValidators['email'];
|
||
}
|
||
if (params.input === 'url') {
|
||
params.inputValidator = defaultInputValidators['url'];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
function validateCustomTargetElement(params) {
|
||
// Determine if the custom target element is valid
|
||
if (!params.target || typeof params.target === 'string' && !document.querySelector(params.target) || typeof params.target !== 'string' && !params.target.appendChild) {
|
||
warn('Target parameter is not valid, defaulting to "body"');
|
||
params.target = 'body';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Set type, text and actions on popup
|
||
*
|
||
* @param {SweetAlertOptions} params
|
||
*/
|
||
function setParameters(params) {
|
||
setDefaultInputValidators(params);
|
||
|
||
// showLoaderOnConfirm && preConfirm
|
||
if (params.showLoaderOnConfirm && !params.preConfirm) {
|
||
warn('showLoaderOnConfirm is set to true, but preConfirm is not defined.\n' + 'showLoaderOnConfirm should be used together with preConfirm, see usage example:\n' + 'https://sweetalert2.github.io/#ajax-request');
|
||
}
|
||
validateCustomTargetElement(params);
|
||
|
||
// Replace newlines with <br> in title
|
||
if (typeof params.title === 'string') {
|
||
params.title = params.title.split('\n').join('<br />');
|
||
}
|
||
init(params);
|
||
}
|
||
|
||
/** @type {SweetAlert} */
|
||
let currentInstance;
|
||
var _promise = /*#__PURE__*/new WeakMap();
|
||
class SweetAlert {
|
||
/**
|
||
* @param {...any} args
|
||
* @this {SweetAlert}
|
||
*/
|
||
constructor(...args) {
|
||
/**
|
||
* @type {Promise<SweetAlertResult>}
|
||
*/
|
||
_classPrivateFieldInitSpec(this, _promise, void 0);
|
||
// Prevent run in Node env
|
||
if (typeof window === 'undefined') {
|
||
return;
|
||
}
|
||
currentInstance = this;
|
||
|
||
// @ts-ignore
|
||
const outerParams = Object.freeze(this.constructor.argsToParams(args));
|
||
|
||
/** @type {Readonly<SweetAlertOptions>} */
|
||
this.params = outerParams;
|
||
|
||
/** @type {boolean} */
|
||
this.isAwaitingPromise = false;
|
||
_classPrivateFieldSet2(_promise, this, this._main(currentInstance.params));
|
||
}
|
||
_main(userParams, mixinParams = {}) {
|
||
showWarningsForParams(Object.assign({}, mixinParams, userParams));
|
||
if (globalState.currentInstance) {
|
||
const swalPromiseResolve = privateMethods.swalPromiseResolve.get(globalState.currentInstance);
|
||
const {
|
||
isAwaitingPromise
|
||
} = globalState.currentInstance;
|
||
globalState.currentInstance._destroy();
|
||
if (!isAwaitingPromise) {
|
||
swalPromiseResolve({
|
||
isDismissed: true
|
||
});
|
||
}
|
||
if (isModal()) {
|
||
unsetAriaHidden();
|
||
}
|
||
}
|
||
globalState.currentInstance = currentInstance;
|
||
const innerParams = prepareParams(userParams, mixinParams);
|
||
setParameters(innerParams);
|
||
Object.freeze(innerParams);
|
||
|
||
// clear the previous timer
|
||
if (globalState.timeout) {
|
||
globalState.timeout.stop();
|
||
delete globalState.timeout;
|
||
}
|
||
|
||
// clear the restore focus timeout
|
||
clearTimeout(globalState.restoreFocusTimeout);
|
||
const domCache = populateDomCache(currentInstance);
|
||
render(currentInstance, innerParams);
|
||
privateProps.innerParams.set(currentInstance, innerParams);
|
||
return swalPromise(currentInstance, domCache, innerParams);
|
||
}
|
||
|
||
// `catch` cannot be the name of a module export, so we define our thenable methods here instead
|
||
then(onFulfilled) {
|
||
return _classPrivateFieldGet2(_promise, this).then(onFulfilled);
|
||
}
|
||
finally(onFinally) {
|
||
return _classPrivateFieldGet2(_promise, this).finally(onFinally);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @param {DomCache} domCache
|
||
* @param {SweetAlertOptions} innerParams
|
||
* @returns {Promise}
|
||
*/
|
||
const swalPromise = (instance, domCache, innerParams) => {
|
||
return new Promise((resolve, reject) => {
|
||
// functions to handle all closings/dismissals
|
||
/**
|
||
* @param {DismissReason} dismiss
|
||
*/
|
||
const dismissWith = dismiss => {
|
||
instance.close({
|
||
isDismissed: true,
|
||
dismiss
|
||
});
|
||
};
|
||
privateMethods.swalPromiseResolve.set(instance, resolve);
|
||
privateMethods.swalPromiseReject.set(instance, reject);
|
||
domCache.confirmButton.onclick = () => {
|
||
handleConfirmButtonClick(instance);
|
||
};
|
||
domCache.denyButton.onclick = () => {
|
||
handleDenyButtonClick(instance);
|
||
};
|
||
domCache.cancelButton.onclick = () => {
|
||
handleCancelButtonClick(instance, dismissWith);
|
||
};
|
||
domCache.closeButton.onclick = () => {
|
||
dismissWith(DismissReason.close);
|
||
};
|
||
handlePopupClick(innerParams, domCache, dismissWith);
|
||
addKeydownHandler(globalState, innerParams, dismissWith);
|
||
handleInputOptionsAndValue(instance, innerParams);
|
||
openPopup(innerParams);
|
||
setupTimer(globalState, innerParams, dismissWith);
|
||
initFocus(domCache, innerParams);
|
||
|
||
// Scroll container to top on open (#1247, #1946)
|
||
setTimeout(() => {
|
||
domCache.container.scrollTop = 0;
|
||
});
|
||
});
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlertOptions} userParams
|
||
* @param {SweetAlertOptions} mixinParams
|
||
* @returns {SweetAlertOptions}
|
||
*/
|
||
const prepareParams = (userParams, mixinParams) => {
|
||
const templateParams = getTemplateParams(userParams);
|
||
const params = Object.assign({}, defaultParams, mixinParams, templateParams, userParams); // precedence is described in #2131
|
||
params.showClass = Object.assign({}, defaultParams.showClass, params.showClass);
|
||
params.hideClass = Object.assign({}, defaultParams.hideClass, params.hideClass);
|
||
if (params.animation === false) {
|
||
params.showClass = {
|
||
backdrop: 'swal2-noanimation'
|
||
};
|
||
params.hideClass = {};
|
||
}
|
||
return params;
|
||
};
|
||
|
||
/**
|
||
* @param {SweetAlert} instance
|
||
* @returns {DomCache}
|
||
*/
|
||
const populateDomCache = instance => {
|
||
const domCache = {
|
||
popup: getPopup(),
|
||
container: getContainer(),
|
||
actions: getActions(),
|
||
confirmButton: getConfirmButton(),
|
||
denyButton: getDenyButton(),
|
||
cancelButton: getCancelButton(),
|
||
loader: getLoader(),
|
||
closeButton: getCloseButton(),
|
||
validationMessage: getValidationMessage(),
|
||
progressSteps: getProgressSteps()
|
||
};
|
||
privateProps.domCache.set(instance, domCache);
|
||
return domCache;
|
||
};
|
||
|
||
/**
|
||
* @param {GlobalState} globalState
|
||
* @param {SweetAlertOptions} innerParams
|
||
* @param {Function} dismissWith
|
||
*/
|
||
const setupTimer = (globalState, innerParams, dismissWith) => {
|
||
const timerProgressBar = getTimerProgressBar();
|
||
hide(timerProgressBar);
|
||
if (innerParams.timer) {
|
||
globalState.timeout = new Timer(() => {
|
||
dismissWith('timer');
|
||
delete globalState.timeout;
|
||
}, innerParams.timer);
|
||
if (innerParams.timerProgressBar) {
|
||
show(timerProgressBar);
|
||
applyCustomClass(timerProgressBar, innerParams, 'timerProgressBar');
|
||
setTimeout(() => {
|
||
if (globalState.timeout && globalState.timeout.running) {
|
||
// timer can be already stopped or unset at this point
|
||
animateTimerProgressBar(innerParams.timer);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Initialize focus in the popup:
|
||
*
|
||
* 1. If `toast` is `true`, don't steal focus from the document.
|
||
* 2. Else if there is an [autofocus] element, focus it.
|
||
* 3. Else if `focusConfirm` is `true` and confirm button is visible, focus it.
|
||
* 4. Else if `focusDeny` is `true` and deny button is visible, focus it.
|
||
* 5. Else if `focusCancel` is `true` and cancel button is visible, focus it.
|
||
* 6. Else focus the first focusable element in a popup (if any).
|
||
*
|
||
* @param {DomCache} domCache
|
||
* @param {SweetAlertOptions} innerParams
|
||
*/
|
||
const initFocus = (domCache, innerParams) => {
|
||
if (innerParams.toast) {
|
||
return;
|
||
}
|
||
// TODO: this is dumb, remove `allowEnterKey` param in the next major version
|
||
if (!callIfFunction(innerParams.allowEnterKey)) {
|
||
warnAboutDeprecation('allowEnterKey');
|
||
blurActiveElement();
|
||
return;
|
||
}
|
||
if (focusAutofocus(domCache)) {
|
||
return;
|
||
}
|
||
if (focusButton(domCache, innerParams)) {
|
||
return;
|
||
}
|
||
setFocus(-1, 1);
|
||
};
|
||
|
||
/**
|
||
* @param {DomCache} domCache
|
||
* @returns {boolean}
|
||
*/
|
||
const focusAutofocus = domCache => {
|
||
const autofocusElements = Array.from(domCache.popup.querySelectorAll('[autofocus]'));
|
||
for (const autofocusElement of autofocusElements) {
|
||
if (autofocusElement instanceof HTMLElement && isVisible$1(autofocusElement)) {
|
||
autofocusElement.focus();
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
|
||
/**
|
||
* @param {DomCache} domCache
|
||
* @param {SweetAlertOptions} innerParams
|
||
* @returns {boolean}
|
||
*/
|
||
const focusButton = (domCache, innerParams) => {
|
||
if (innerParams.focusDeny && isVisible$1(domCache.denyButton)) {
|
||
domCache.denyButton.focus();
|
||
return true;
|
||
}
|
||
if (innerParams.focusCancel && isVisible$1(domCache.cancelButton)) {
|
||
domCache.cancelButton.focus();
|
||
return true;
|
||
}
|
||
if (innerParams.focusConfirm && isVisible$1(domCache.confirmButton)) {
|
||
domCache.confirmButton.focus();
|
||
return true;
|
||
}
|
||
return false;
|
||
};
|
||
const blurActiveElement = () => {
|
||
if (document.activeElement instanceof HTMLElement && typeof document.activeElement.blur === 'function') {
|
||
document.activeElement.blur();
|
||
}
|
||
};
|
||
|
||
// Assign instance methods from src/instanceMethods/*.js to prototype
|
||
SweetAlert.prototype.disableButtons = disableButtons;
|
||
SweetAlert.prototype.enableButtons = enableButtons;
|
||
SweetAlert.prototype.getInput = getInput;
|
||
SweetAlert.prototype.disableInput = disableInput;
|
||
SweetAlert.prototype.enableInput = enableInput;
|
||
SweetAlert.prototype.hideLoading = hideLoading;
|
||
SweetAlert.prototype.disableLoading = hideLoading;
|
||
SweetAlert.prototype.showValidationMessage = showValidationMessage;
|
||
SweetAlert.prototype.resetValidationMessage = resetValidationMessage;
|
||
SweetAlert.prototype.close = close;
|
||
SweetAlert.prototype.closePopup = close;
|
||
SweetAlert.prototype.closeModal = close;
|
||
SweetAlert.prototype.closeToast = close;
|
||
SweetAlert.prototype.rejectPromise = rejectPromise;
|
||
SweetAlert.prototype.update = update;
|
||
SweetAlert.prototype._destroy = _destroy;
|
||
|
||
// Assign static methods from src/staticMethods/*.js to constructor
|
||
Object.assign(SweetAlert, staticMethods);
|
||
|
||
// Proxy to instance methods to constructor, for now, for backwards compatibility
|
||
Object.keys(instanceMethods).forEach(key => {
|
||
/**
|
||
* @param {...any} args
|
||
* @returns {any | undefined}
|
||
*/
|
||
SweetAlert[key] = function (...args) {
|
||
if (currentInstance && currentInstance[key]) {
|
||
return currentInstance[key](...args);
|
||
}
|
||
return null;
|
||
};
|
||
});
|
||
SweetAlert.DismissReason = DismissReason;
|
||
SweetAlert.version = '11.22.4';
|
||
|
||
const Swal = SweetAlert;
|
||
// @ts-ignore
|
||
Swal.default = Swal;
|
||
"undefined"!=typeof document&&function(e,t){var n=e.createElement("style");if(e.getElementsByTagName("head")[0].appendChild(n),n.styleSheet)n.styleSheet.disabled||(n.styleSheet.cssText=t);else try{n.innerHTML=t;}catch(e){n.innerText=t;}}(document,":root{--swal2-outline: 0 0 0 3px rgba(100, 150, 200, 0.5);--swal2-container-padding: 0.625em;--swal2-backdrop: rgba(0, 0, 0, 0.4);--swal2-backdrop-transition: background-color 0.1s;--swal2-width: 32em;--swal2-padding: 0 0 1.25em;--swal2-border: none;--swal2-border-radius: 0.3125rem;--swal2-background: white;--swal2-color: #545454;--swal2-show-animation: swal2-show 0.3s;--swal2-hide-animation: swal2-hide 0.15s forwards;--swal2-icon-zoom: 1;--swal2-icon-animations: true;--swal2-title-padding: 0.8em 1em 0;--swal2-html-container-padding: 1em 1.6em 0.3em;--swal2-input-border: 1px solid #d9d9d9;--swal2-input-border-radius: 0.1875em;--swal2-input-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.06), 0 0 0 3px transparent;--swal2-input-background: transparent;--swal2-input-transition: border-color 0.2s, box-shadow 0.2s;--swal2-input-hover-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.06), 0 0 0 3px transparent;--swal2-input-focus-border: 1px solid #b4dbed;--swal2-input-focus-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.06), 0 0 0 3px $swal2-outline-color;--swal2-progress-step-background: #add8e6;--swal2-validation-message-background: #f0f0f0;--swal2-validation-message-color: #666;--swal2-footer-border-color: #eee;--swal2-footer-background: transparent;--swal2-footer-color: inherit;--swal2-timer-progress-bar-background: rgba(0, 0, 0, 0.3);--swal2-close-button-position: initial;--swal2-close-button-inset: auto;--swal2-close-button-font-size: 2.5em;--swal2-close-button-color: #ccc;--swal2-close-button-transition: color 0.2s, box-shadow 0.2s;--swal2-close-button-outline: initial;--swal2-close-button-box-shadow: inset 0 0 0 3px transparent;--swal2-close-button-focus-box-shadow: inset var(--swal2-outline);--swal2-close-button-hover-transform: none;--swal2-actions-justify-content: center;--swal2-actions-width: auto;--swal2-actions-margin: 1.25em auto 0;--swal2-actions-padding: 0;--swal2-actions-border-radius: 0;--swal2-actions-background: transparent;--swal2-action-button-transition: background-color 0.2s, box-shadow 0.2s;--swal2-action-button-hover: black 10%;--swal2-action-button-active: black 10%;--swal2-confirm-button-box-shadow: none;--swal2-confirm-button-border-radius: 0.25em;--swal2-confirm-button-background-color: #7066e0;--swal2-confirm-button-color: #fff;--swal2-deny-button-box-shadow: none;--swal2-deny-button-border-radius: 0.25em;--swal2-deny-button-background-color: #dc3741;--swal2-deny-button-color: #fff;--swal2-cancel-button-box-shadow: none;--swal2-cancel-button-border-radius: 0.25em;--swal2-cancel-button-background-color: #6e7881;--swal2-cancel-button-color: #fff;--swal2-toast-show-animation: swal2-toast-show 0.5s;--swal2-toast-hide-animation: swal2-toast-hide 0.1s forwards;--swal2-toast-border: none;--swal2-toast-box-shadow: 0 0 1px hsl(0deg 0% 0% / 0.075), 0 1px 2px hsl(0deg 0% 0% / 0.075), 1px 2px 4px hsl(0deg 0% 0% / 0.075), 1px 3px 8px hsl(0deg 0% 0% / 0.075), 2px 4px 16px hsl(0deg 0% 0% / 0.075)}[data-swal2-theme=dark]{--swal2-dark-theme-black: #19191a;--swal2-dark-theme-white: #e1e1e1;--swal2-background: var(--swal2-dark-theme-black);--swal2-color: var(--swal2-dark-theme-white);--swal2-footer-border-color: #555;--swal2-input-background: color-mix(in srgb, var(--swal2-dark-theme-black), var(--swal2-dark-theme-white) 10%);--swal2-validation-message-background: color-mix( in srgb, var(--swal2-dark-theme-black), var(--swal2-dark-theme-white) 10% );--swal2-validation-message-color: var(--swal2-dark-theme-white);--swal2-timer-progress-bar-background: rgba(255, 255, 255, 0.7)}@media(prefers-color-scheme: dark){[data-swal2-theme=auto]{--swal2-dark-theme-black: #19191a;--swal2-dark-theme-white: #e1e1e1;--swal2-background: var(--swal2-dark-theme-black);--swal2-color: var(--swal2-dark-theme-white);--swal2-footer-border-color: #555;--swal2-input-background: color-mix(in srgb, var(--swal2-dark-theme-black), var(--swal2-dark-theme-white) 10%);--swal2-validation-message-background: color-mix( in srgb, var(--swal2-dark-theme-black), var(--swal2-dark-theme-white) 10% );--swal2-validation-message-color: var(--swal2-dark-theme-white);--swal2-timer-progress-bar-background: rgba(255, 255, 255, 0.7)}}body.swal2-shown:not(.swal2-no-backdrop,.swal2-toast-shown){overflow:hidden}body.swal2-height-auto{height:auto !important}body.swal2-no-backdrop .swal2-container{background-color:rgba(0,0,0,0) !important;pointer-events:none}body.swal2-no-backdrop .swal2-container .swal2-popup{pointer-events:all}body.swal2-no-backdrop .swal2-container .swal2-modal{box-shadow:0 0 10px var(--swal2-backdrop)}body.swal2-toast-shown .swal2-container{box-sizing:border-box;width:360px;max-width:100%;background-color:rgba(0,0,0,0);pointer-events:none}body.swal2-toast-shown .swal2-container.swal2-top{inset:0 auto auto 50%;transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-top-end,body.swal2-toast-shown .swal2-container.swal2-top-right{inset:0 0 auto auto}body.swal2-toast-shown .swal2-container.swal2-top-start,body.swal2-toast-shown .swal2-container.swal2-top-left{inset:0 auto auto 0}body.swal2-toast-shown .swal2-container.swal2-center-start,body.swal2-toast-shown .swal2-container.swal2-center-left{inset:50% auto auto 0;transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-center{inset:50% auto auto 50%;transform:translate(-50%, -50%)}body.swal2-toast-shown .swal2-container.swal2-center-end,body.swal2-toast-shown .swal2-container.swal2-center-right{inset:50% 0 auto auto;transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-start,body.swal2-toast-shown .swal2-container.swal2-bottom-left{inset:auto auto 0 0}body.swal2-toast-shown .swal2-container.swal2-bottom{inset:auto auto 0 50%;transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-end,body.swal2-toast-shown .swal2-container.swal2-bottom-right{inset:auto 0 0 auto}@media print{body.swal2-shown:not(.swal2-no-backdrop,.swal2-toast-shown){overflow-y:scroll !important}body.swal2-shown:not(.swal2-no-backdrop,.swal2-toast-shown)>[aria-hidden=true]{display:none}body.swal2-shown:not(.swal2-no-backdrop,.swal2-toast-shown) .swal2-container{position:static !important}}div:where(.swal2-container){display:grid;position:fixed;z-index:1060;inset:0;box-sizing:border-box;grid-template-areas:\"top-start top top-end\" \"center-start center center-end\" \"bottom-start bottom-center bottom-end\";grid-template-rows:minmax(min-content, auto) minmax(min-content, auto) minmax(min-content, auto);height:100%;padding:var(--swal2-container-padding);overflow-x:hidden;transition:var(--swal2-backdrop-transition);-webkit-overflow-scrolling:touch}div:where(.swal2-container).swal2-backdrop-show,div:where(.swal2-container).swal2-noanimation{background:var(--swal2-backdrop)}div:where(.swal2-container).swal2-backdrop-hide{background:rgba(0,0,0,0) !important}div:where(.swal2-container).swal2-top-start,div:where(.swal2-container).swal2-center-start,div:where(.swal2-container).swal2-bottom-start{grid-template-columns:minmax(0, 1fr) auto auto}div:where(.swal2-container).swal2-top,div:where(.swal2-container).swal2-center,div:where(.swal2-container).swal2-bottom{grid-template-columns:auto minmax(0, 1fr) auto}div:where(.swal2-container).swal2-top-end,div:where(.swal2-container).swal2-center-end,div:where(.swal2-container).swal2-bottom-end{grid-template-columns:auto auto minmax(0, 1fr)}div:where(.swal2-container).swal2-top-start>.swal2-popup{align-self:start}div:where(.swal2-container).swal2-top>.swal2-popup{grid-column:2;place-self:start center}div:where(.swal2-container).swal2-top-end>.swal2-popup,div:where(.swal2-container).swal2-top-right>.swal2-popup{grid-column:3;place-self:start end}div:where(.swal2-container).swal2-center-start>.swal2-popup,div:where(.swal2-container).swal2-center-left>.swal2-popup{grid-row:2;align-self:center}div:where(.swal2-container).swal2-center>.swal2-popup{grid-column:2;grid-row:2;place-self:center center}div:where(.swal2-container).swal2-center-end>.swal2-popup,div:where(.swal2-container).swal2-center-right>.swal2-popup{grid-column:3;grid-row:2;place-self:center end}div:where(.swal2-container).swal2-bottom-start>.swal2-popup,div:where(.swal2-container).swal2-bottom-left>.swal2-popup{grid-column:1;grid-row:3;align-self:end}div:where(.swal2-container).swal2-bottom>.swal2-popup{grid-column:2;grid-row:3;place-self:end center}div:where(.swal2-container).swal2-bottom-end>.swal2-popup,div:where(.swal2-container).swal2-bottom-right>.swal2-popup{grid-column:3;grid-row:3;place-self:end end}div:where(.swal2-container).swal2-grow-row>.swal2-popup,div:where(.swal2-container).swal2-grow-fullscreen>.swal2-popup{grid-column:1/4;width:100%}div:where(.swal2-container).swal2-grow-column>.swal2-popup,div:where(.swal2-container).swal2-grow-fullscreen>.swal2-popup{grid-row:1/4;align-self:stretch}div:where(.swal2-container).swal2-no-transition{transition:none !important}div:where(.swal2-container)[popover]{width:auto;border:0}div:where(.swal2-container) div:where(.swal2-popup){display:none;position:relative;box-sizing:border-box;grid-template-columns:minmax(0, 100%);width:var(--swal2-width);max-width:100%;padding:var(--swal2-padding);border:var(--swal2-border);border-radius:var(--swal2-border-radius);background:var(--swal2-background);color:var(--swal2-color);font-family:inherit;font-size:1rem;container-name:swal2-popup}div:where(.swal2-container) div:where(.swal2-popup):focus{outline:none}div:where(.swal2-container) div:where(.swal2-popup).swal2-loading{overflow-y:hidden}div:where(.swal2-container) div:where(.swal2-popup).swal2-draggable{cursor:grab}div:where(.swal2-container) div:where(.swal2-popup).swal2-draggable div:where(.swal2-icon){cursor:grab}div:where(.swal2-container) div:where(.swal2-popup).swal2-dragging{cursor:grabbing}div:where(.swal2-container) div:where(.swal2-popup).swal2-dragging div:where(.swal2-icon){cursor:grabbing}div:where(.swal2-container) h2:where(.swal2-title){position:relative;max-width:100%;margin:0;padding:var(--swal2-title-padding);color:inherit;font-size:1.875em;font-weight:600;text-align:center;text-transform:none;word-wrap:break-word;cursor:initial}div:where(.swal2-container) div:where(.swal2-actions){display:flex;z-index:1;box-sizing:border-box;flex-wrap:wrap;align-items:center;justify-content:var(--swal2-actions-justify-content);width:var(--swal2-actions-width);margin:var(--swal2-actions-margin);padding:var(--swal2-actions-padding);border-radius:var(--swal2-actions-border-radius);background:var(--swal2-actions-background)}div:where(.swal2-container) div:where(.swal2-loader){display:none;align-items:center;justify-content:center;width:2.2em;height:2.2em;margin:0 1.875em;animation:swal2-rotate-loading 1.5s linear 0s infinite normal;border-width:.25em;border-style:solid;border-radius:100%;border-color:#2778c4 rgba(0,0,0,0) #2778c4 rgba(0,0,0,0)}div:where(.swal2-container) button:where(.swal2-styled){margin:.3125em;padding:.625em 1.1em;transition:var(--swal2-action-button-transition);border:none;box-shadow:0 0 0 3px rgba(0,0,0,0);font-weight:500}div:where(.swal2-container) button:where(.swal2-styled):not([disabled]){cursor:pointer}div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-confirm){border-radius:var(--swal2-confirm-button-border-radius);background:initial;background-color:var(--swal2-confirm-button-background-color);box-shadow:var(--swal2-confirm-button-box-shadow);color:var(--swal2-confirm-button-color);font-size:1em}div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-confirm):hover{background-color:color-mix(in srgb, var(--swal2-confirm-button-background-color), var(--swal2-action-button-hover))}div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-confirm):active{background-color:color-mix(in srgb, var(--swal2-confirm-button-background-color), var(--swal2-action-button-active))}div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-deny){border-radius:var(--swal2-deny-button-border-radius);background:initial;background-color:var(--swal2-deny-button-background-color);box-shadow:var(--swal2-deny-button-box-shadow);color:var(--swal2-deny-button-color);font-size:1em}div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-deny):hover{background-color:color-mix(in srgb, var(--swal2-deny-button-background-color), var(--swal2-action-button-hover))}div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-deny):active{background-color:color-mix(in srgb, var(--swal2-deny-button-background-color), var(--swal2-action-button-active))}div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-cancel){border-radius:var(--swal2-cancel-button-border-radius);background:initial;background-color:var(--swal2-cancel-button-background-color);box-shadow:var(--swal2-cancel-button-box-shadow);color:var(--swal2-cancel-button-color);font-size:1em}div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-cancel):hover{background-color:color-mix(in srgb, var(--swal2-cancel-button-background-color), var(--swal2-action-button-hover))}div:where(.swal2-container) button:where(.swal2-styled):where(.swal2-cancel):active{background-color:color-mix(in srgb, var(--swal2-cancel-button-background-color), var(--swal2-action-button-active))}div:where(.swal2-container) button:where(.swal2-styled):focus-visible{outline:none;box-shadow:var(--swal2-action-button-focus-box-shadow)}div:where(.swal2-container) button:where(.swal2-styled)[disabled]:not(.swal2-loading){opacity:.4}div:where(.swal2-container) button:where(.swal2-styled)::-moz-focus-inner{border:0}div:where(.swal2-container) div:where(.swal2-footer){margin:1em 0 0;padding:1em 1em 0;border-top:1px solid var(--swal2-footer-border-color);background:var(--swal2-footer-background);color:var(--swal2-footer-color);font-size:1em;text-align:center;cursor:initial}div:where(.swal2-container) .swal2-timer-progress-bar-container{position:absolute;right:0;bottom:0;left:0;grid-column:auto !important;overflow:hidden;border-bottom-right-radius:var(--swal2-border-radius);border-bottom-left-radius:var(--swal2-border-radius)}div:where(.swal2-container) div:where(.swal2-timer-progress-bar){width:100%;height:.25em;background:var(--swal2-timer-progress-bar-background)}div:where(.swal2-container) img:where(.swal2-image){max-width:100%;margin:2em auto 1em;cursor:initial}div:where(.swal2-container) button:where(.swal2-close){position:var(--swal2-close-button-position);inset:var(--swal2-close-button-inset);z-index:2;align-items:center;justify-content:center;width:1.2em;height:1.2em;margin-top:0;margin-right:0;margin-bottom:-1.2em;padding:0;overflow:hidden;transition:var(--swal2-close-button-transition);border:none;border-radius:var(--swal2-border-radius);outline:var(--swal2-close-button-outline);background:rgba(0,0,0,0);color:var(--swal2-close-button-color);font-family:monospace;font-size:var(--swal2-close-button-font-size);cursor:pointer;justify-self:end}div:where(.swal2-container) button:where(.swal2-close):hover{transform:var(--swal2-close-button-hover-transform);background:rgba(0,0,0,0);color:#f27474}div:where(.swal2-container) button:where(.swal2-close):focus-visible{outline:none;box-shadow:var(--swal2-close-button-focus-box-shadow)}div:where(.swal2-container) button:where(.swal2-close)::-moz-focus-inner{border:0}div:where(.swal2-container) div:where(.swal2-html-container){z-index:1;justify-content:center;margin:0;padding:var(--swal2-html-container-padding);overflow:auto;color:inherit;font-size:1.125em;font-weight:normal;line-height:normal;text-align:center;word-wrap:break-word;word-break:break-word;cursor:initial}div:where(.swal2-container) input:where(.swal2-input),div:where(.swal2-container) input:where(.swal2-file),div:where(.swal2-container) textarea:where(.swal2-textarea),div:where(.swal2-container) select:where(.swal2-select),div:where(.swal2-container) div:where(.swal2-radio),div:where(.swal2-container) label:where(.swal2-checkbox){margin:1em 2em 3px}div:where(.swal2-container) input:where(.swal2-input),div:where(.swal2-container) input:where(.swal2-file),div:where(.swal2-container) textarea:where(.swal2-textarea){box-sizing:border-box;width:auto;transition:var(--swal2-input-transition);border:var(--swal2-input-border);border-radius:var(--swal2-input-border-radius);background:var(--swal2-input-background);box-shadow:var(--swal2-input-box-shadow);color:inherit;font-size:1.125em}div:where(.swal2-container) input:where(.swal2-input).swal2-inputerror,div:where(.swal2-container) input:where(.swal2-file).swal2-inputerror,div:where(.swal2-container) textarea:where(.swal2-textarea).swal2-inputerror{border-color:#f27474 !important;box-shadow:0 0 2px #f27474 !important}div:where(.swal2-container) input:where(.swal2-input):hover,div:where(.swal2-container) input:where(.swal2-file):hover,div:where(.swal2-container) textarea:where(.swal2-textarea):hover{box-shadow:var(--swal2-input-hover-box-shadow)}div:where(.swal2-container) input:where(.swal2-input):focus,div:where(.swal2-container) input:where(.swal2-file):focus,div:where(.swal2-container) textarea:where(.swal2-textarea):focus{border:var(--swal2-input-focus-border);outline:none;box-shadow:var(--swal2-input-focus-box-shadow)}div:where(.swal2-container) input:where(.swal2-input)::placeholder,div:where(.swal2-container) input:where(.swal2-file)::placeholder,div:where(.swal2-container) textarea:where(.swal2-textarea)::placeholder{color:#ccc}div:where(.swal2-container) .swal2-range{margin:1em 2em 3px;background:var(--swal2-background)}div:where(.swal2-container) .swal2-range input{width:80%}div:where(.swal2-container) .swal2-range output{width:20%;color:inherit;font-weight:600;text-align:center}div:where(.swal2-container) .swal2-range input,div:where(.swal2-container) .swal2-range output{height:2.625em;padding:0;font-size:1.125em;line-height:2.625em}div:where(.swal2-container) .swal2-input{height:2.625em;padding:0 .75em}div:where(.swal2-container) .swal2-file{width:75%;margin-right:auto;margin-left:auto;background:var(--swal2-input-background);font-size:1.125em}div:where(.swal2-container) .swal2-textarea{height:6.75em;padding:.75em}div:where(.swal2-container) .swal2-select{min-width:50%;max-width:100%;padding:.375em .625em;background:var(--swal2-input-background);color:inherit;font-size:1.125em}div:where(.swal2-container) .swal2-radio,div:where(.swal2-container) .swal2-checkbox{align-items:center;justify-content:center;background:var(--swal2-background);color:inherit}div:where(.swal2-container) .swal2-radio label,div:where(.swal2-container) .swal2-checkbox label{margin:0 .6em;font-size:1.125em}div:where(.swal2-container) .swal2-radio input,div:where(.swal2-container) .swal2-checkbox input{flex-shrink:0;margin:0 .4em}div:where(.swal2-container) label:where(.swal2-input-label){display:flex;justify-content:center;margin:1em auto 0}div:where(.swal2-container) div:where(.swal2-validation-message){align-items:center;justify-content:center;margin:1em 0 0;padding:.625em;overflow:hidden;background:var(--swal2-validation-message-background);color:var(--swal2-validation-message-color);font-size:1em;font-weight:300}div:where(.swal2-container) div:where(.swal2-validation-message)::before{content:\"!\";display:inline-block;width:1.5em;min-width:1.5em;height:1.5em;margin:0 .625em;border-radius:50%;background-color:#f27474;color:#fff;font-weight:600;line-height:1.5em;text-align:center}div:where(.swal2-container) .swal2-progress-steps{flex-wrap:wrap;align-items:center;max-width:100%;margin:1.25em auto;padding:0;background:rgba(0,0,0,0);font-weight:600}div:where(.swal2-container) .swal2-progress-steps li{display:inline-block;position:relative}div:where(.swal2-container) .swal2-progress-steps .swal2-progress-step{z-index:20;flex-shrink:0;width:2em;height:2em;border-radius:2em;background:#2778c4;color:#fff;line-height:2em;text-align:center}div:where(.swal2-container) .swal2-progress-steps .swal2-progress-step.swal2-active-progress-step{background:#2778c4}div:where(.swal2-container) .swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step{background:var(--swal2-progress-step-background);color:#fff}div:where(.swal2-container) .swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step-line{background:var(--swal2-progress-step-background)}div:where(.swal2-container) .swal2-progress-steps .swal2-progress-step-line{z-index:10;flex-shrink:0;width:2.5em;height:.4em;margin:0 -1px;background:#2778c4}div:where(.swal2-icon){position:relative;box-sizing:content-box;justify-content:center;width:5em;height:5em;margin:2.5em auto .6em;zoom:var(--swal2-icon-zoom);border:.25em solid rgba(0,0,0,0);border-radius:50%;border-color:#000;font-family:inherit;line-height:5em;cursor:default;user-select:none}div:where(.swal2-icon) .swal2-icon-content{display:flex;align-items:center;font-size:3.75em}div:where(.swal2-icon).swal2-error{border-color:#f27474;color:#f27474}div:where(.swal2-icon).swal2-error .swal2-x-mark{position:relative;flex-grow:1}div:where(.swal2-icon).swal2-error [class^=swal2-x-mark-line]{display:block;position:absolute;top:2.3125em;width:2.9375em;height:.3125em;border-radius:.125em;background-color:#f27474}div:where(.swal2-icon).swal2-error [class^=swal2-x-mark-line][class$=left]{left:1.0625em;transform:rotate(45deg)}div:where(.swal2-icon).swal2-error [class^=swal2-x-mark-line][class$=right]{right:1em;transform:rotate(-45deg)}@container swal2-popup style(--swal2-icon-animations:true){div:where(.swal2-icon).swal2-error.swal2-icon-show{animation:swal2-animate-error-icon .5s}div:where(.swal2-icon).swal2-error.swal2-icon-show .swal2-x-mark{animation:swal2-animate-error-x-mark .5s}}div:where(.swal2-icon).swal2-warning{border-color:#f8bb86;color:#f8bb86}@container swal2-popup style(--swal2-icon-animations:true){div:where(.swal2-icon).swal2-warning.swal2-icon-show{animation:swal2-animate-error-icon .5s}div:where(.swal2-icon).swal2-warning.swal2-icon-show .swal2-icon-content{animation:swal2-animate-i-mark .5s}}div:where(.swal2-icon).swal2-info{border-color:#3fc3ee;color:#3fc3ee}@container swal2-popup style(--swal2-icon-animations:true){div:where(.swal2-icon).swal2-info.swal2-icon-show{animation:swal2-animate-error-icon .5s}div:where(.swal2-icon).swal2-info.swal2-icon-show .swal2-icon-content{animation:swal2-animate-i-mark .8s}}div:where(.swal2-icon).swal2-question{border-color:#87adbd;color:#87adbd}@container swal2-popup style(--swal2-icon-animations:true){div:where(.swal2-icon).swal2-question.swal2-icon-show{animation:swal2-animate-error-icon .5s}div:where(.swal2-icon).swal2-question.swal2-icon-show .swal2-icon-content{animation:swal2-animate-question-mark .8s}}div:where(.swal2-icon).swal2-success{border-color:#a5dc86;color:#a5dc86}div:where(.swal2-icon).swal2-success [class^=swal2-success-circular-line]{position:absolute;width:3.75em;height:7.5em;border-radius:50%}div:where(.swal2-icon).swal2-success [class^=swal2-success-circular-line][class$=left]{top:-0.4375em;left:-2.0635em;transform:rotate(-45deg);transform-origin:3.75em 3.75em;border-radius:7.5em 0 0 7.5em}div:where(.swal2-icon).swal2-success [class^=swal2-success-circular-line][class$=right]{top:-0.6875em;left:1.875em;transform:rotate(-45deg);transform-origin:0 3.75em;border-radius:0 7.5em 7.5em 0}div:where(.swal2-icon).swal2-success .swal2-success-ring{position:absolute;z-index:2;top:-0.25em;left:-0.25em;box-sizing:content-box;width:100%;height:100%;border:.25em solid rgba(165,220,134,.3);border-radius:50%}div:where(.swal2-icon).swal2-success .swal2-success-fix{position:absolute;z-index:1;top:.5em;left:1.625em;width:.4375em;height:5.625em;transform:rotate(-45deg)}div:where(.swal2-icon).swal2-success [class^=swal2-success-line]{display:block;position:absolute;z-index:2;height:.3125em;border-radius:.125em;background-color:#a5dc86}div:where(.swal2-icon).swal2-success [class^=swal2-success-line][class$=tip]{top:2.875em;left:.8125em;width:1.5625em;transform:rotate(45deg)}div:where(.swal2-icon).swal2-success [class^=swal2-success-line][class$=long]{top:2.375em;right:.5em;width:2.9375em;transform:rotate(-45deg)}@container swal2-popup style(--swal2-icon-animations:true){div:where(.swal2-icon).swal2-success.swal2-icon-show .swal2-success-line-tip{animation:swal2-animate-success-line-tip .75s}div:where(.swal2-icon).swal2-success.swal2-icon-show .swal2-success-line-long{animation:swal2-animate-success-line-long .75s}div:where(.swal2-icon).swal2-success.swal2-icon-show .swal2-success-circular-line-right{animation:swal2-rotate-success-circular-line 4.25s ease-in}}[class^=swal2]{-webkit-tap-highlight-color:rgba(0,0,0,0)}.swal2-show{animation:var(--swal2-show-animation)}.swal2-hide{animation:var(--swal2-hide-animation)}.swal2-noanimation{transition:none}.swal2-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}.swal2-rtl .swal2-close{margin-right:initial;margin-left:0}.swal2-rtl .swal2-timer-progress-bar{right:0;left:auto}.swal2-toast{box-sizing:border-box;grid-column:1/4 !important;grid-row:1/4 !important;grid-template-columns:min-content auto min-content;padding:1em;overflow-y:hidden;border:var(--swal2-toast-border);background:var(--swal2-background);box-shadow:var(--swal2-toast-box-shadow);pointer-events:all}.swal2-toast>*{grid-column:2}.swal2-toast h2:where(.swal2-title){margin:.5em 1em;padding:0;font-size:1em;text-align:initial}.swal2-toast .swal2-loading{justify-content:center}.swal2-toast input:where(.swal2-input){height:2em;margin:.5em;font-size:1em}.swal2-toast .swal2-validation-message{font-size:1em}.swal2-toast div:where(.swal2-footer){margin:.5em 0 0;padding:.5em 0 0;font-size:.8em}.swal2-toast button:where(.swal2-close){grid-column:3/3;grid-row:1/99;align-self:center;width:.8em;height:.8em;margin:0;font-size:2em}.swal2-toast div:where(.swal2-html-container){margin:.5em 1em;padding:0;overflow:initial;font-size:1em;text-align:initial}.swal2-toast div:where(.swal2-html-container):empty{padding:0}.swal2-toast .swal2-loader{grid-column:1;grid-row:1/99;align-self:center;width:2em;height:2em;margin:.25em}.swal2-toast .swal2-icon{grid-column:1;grid-row:1/99;align-self:center;width:2em;min-width:2em;height:2em;margin:0 .5em 0 0}.swal2-toast .swal2-icon .swal2-icon-content{display:flex;align-items:center;font-size:1.8em;font-weight:bold}.swal2-toast .swal2-icon.swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line]{top:.875em;width:1.375em}.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:.3125em}.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:.3125em}.swal2-toast div:where(.swal2-actions){justify-content:flex-start;height:auto;margin:0;margin-top:.5em;padding:0 .5em}.swal2-toast button:where(.swal2-styled){margin:.25em .5em;padding:.4em .6em;font-size:1em}.swal2-toast .swal2-success{border-color:#a5dc86}.swal2-toast .swal2-success [class^=swal2-success-circular-line]{position:absolute;width:1.6em;height:3em;border-radius:50%}.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=left]{top:-0.8em;left:-0.5em;transform:rotate(-45deg);transform-origin:2em 2em;border-radius:4em 0 0 4em}.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=right]{top:-0.25em;left:.9375em;transform-origin:0 1.5em;border-radius:0 4em 4em 0}.swal2-toast .swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-toast .swal2-success .swal2-success-fix{top:0;left:.4375em;width:.4375em;height:2.6875em}.swal2-toast .swal2-success [class^=swal2-success-line]{height:.3125em}.swal2-toast .swal2-success [class^=swal2-success-line][class$=tip]{top:1.125em;left:.1875em;width:.75em}.swal2-toast .swal2-success [class^=swal2-success-line][class$=long]{top:.9375em;right:.1875em;width:1.375em}@container swal2-popup style(--swal2-icon-animations:true){.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-tip{animation:swal2-toast-animate-success-line-tip .75s}.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-long{animation:swal2-toast-animate-success-line-long .75s}}.swal2-toast.swal2-show{animation:var(--swal2-toast-show-animation)}.swal2-toast.swal2-hide{animation:var(--swal2-toast-hide-animation)}@keyframes swal2-show{0%{transform:scale(0.7)}45%{transform:scale(1.05)}80%{transform:scale(0.95)}100%{transform:scale(1)}}@keyframes swal2-hide{0%{transform:scale(1);opacity:1}100%{transform:scale(0.5);opacity:0}}@keyframes swal2-animate-success-line-tip{0%{top:1.1875em;left:.0625em;width:0}54%{top:1.0625em;left:.125em;width:0}70%{top:2.1875em;left:-0.375em;width:3.125em}84%{top:3em;left:1.3125em;width:1.0625em}100%{top:2.8125em;left:.8125em;width:1.5625em}}@keyframes swal2-animate-success-line-long{0%{top:3.375em;right:2.875em;width:0}65%{top:3.375em;right:2.875em;width:0}84%{top:2.1875em;right:0;width:3.4375em}100%{top:2.375em;right:.5em;width:2.9375em}}@keyframes swal2-rotate-success-circular-line{0%{transform:rotate(-45deg)}5%{transform:rotate(-45deg)}12%{transform:rotate(-405deg)}100%{transform:rotate(-405deg)}}@keyframes swal2-animate-error-x-mark{0%{margin-top:1.625em;transform:scale(0.4);opacity:0}50%{margin-top:1.625em;transform:scale(0.4);opacity:0}80%{margin-top:-0.375em;transform:scale(1.15)}100%{margin-top:0;transform:scale(1);opacity:1}}@keyframes swal2-animate-error-icon{0%{transform:rotateX(100deg);opacity:0}100%{transform:rotateX(0deg);opacity:1}}@keyframes swal2-rotate-loading{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@keyframes swal2-animate-question-mark{0%{transform:rotateY(-360deg)}100%{transform:rotateY(0)}}@keyframes swal2-animate-i-mark{0%{transform:rotateZ(45deg);opacity:0}25%{transform:rotateZ(-25deg);opacity:.4}50%{transform:rotateZ(15deg);opacity:.8}75%{transform:rotateZ(-5deg);opacity:1}100%{transform:rotateX(0);opacity:1}}@keyframes swal2-toast-show{0%{transform:translateY(-0.625em) rotateZ(2deg)}33%{transform:translateY(0) rotateZ(-2deg)}66%{transform:translateY(0.3125em) rotateZ(2deg)}100%{transform:translateY(0) rotateZ(0deg)}}@keyframes swal2-toast-hide{100%{transform:rotateZ(1deg);opacity:0}}@keyframes swal2-toast-animate-success-line-tip{0%{top:.5625em;left:.0625em;width:0}54%{top:.125em;left:.125em;width:0}70%{top:.625em;left:-0.25em;width:1.625em}84%{top:1.0625em;left:.75em;width:.5em}100%{top:1.125em;left:.1875em;width:.75em}}@keyframes swal2-toast-animate-success-line-long{0%{top:1.625em;right:1.375em;width:0}65%{top:1.25em;right:.9375em;width:0}84%{top:.9375em;right:0;width:1.125em}100%{top:.9375em;right:.1875em;width:1.375em}}");
|
||
|
||
const STORAGE_KEYS = {
|
||
pairing: "pairingRows",
|
||
wallet: "walletRows",
|
||
process: "processRows",
|
||
data: "dataRows"
|
||
};
|
||
const defaultRows = [
|
||
{
|
||
column1: "sprt1qqwtvg5q5vcz0reqvmld98u7va3av6gakwe9yxw9yhnpj5djcunn4squ68tuzn8dz78dg4adfv0dekx8hg9sy0t6s9k5em7rffgxmrsfpyy7gtyrz",
|
||
column2: "🎊😑🎄😩",
|
||
column3: "Laptop"
|
||
},
|
||
{
|
||
column1: "sprt1qqwtvg5q5vcz0reqvmld98u7va3av6gakwe9yxw9yhnpj5djcunn4squ68tuzn8dz78dg4adfv0dekx8hg9sy0t6s9k5em7rffgxmrsfpyy7gtyrx",
|
||
column2: "🎏🎕😧🌥",
|
||
column3: "Phone"
|
||
}
|
||
];
|
||
const mockDataRows = [
|
||
{
|
||
column1: "User Project",
|
||
column2: "private",
|
||
column3: "User",
|
||
column4: "6 months",
|
||
column5: "NDA signed",
|
||
column6: "Contract #123",
|
||
processName: "User Process",
|
||
zone: "A"
|
||
},
|
||
{
|
||
column1: "Process Project",
|
||
column2: "private",
|
||
column3: "Process",
|
||
column4: "1 year",
|
||
column5: "Terms accepted",
|
||
column6: "Contract #456",
|
||
processName: "Process Management",
|
||
zone: "B"
|
||
},
|
||
{
|
||
column1: "Member Project",
|
||
column2: "private",
|
||
column3: "Member",
|
||
column4: "3 months",
|
||
column5: "GDPR compliant",
|
||
column6: "Contract #789",
|
||
processName: "Member Process",
|
||
zone: "C"
|
||
},
|
||
{
|
||
column1: "Peer Project",
|
||
column2: "public",
|
||
column3: "Peer",
|
||
column4: "2 years",
|
||
column5: "IP rights",
|
||
column6: "Contract #101",
|
||
processName: "Peer Process",
|
||
zone: "D"
|
||
},
|
||
{
|
||
column1: "Payment Project",
|
||
column2: "confidential",
|
||
column3: "Payment",
|
||
column4: "1 year",
|
||
column5: "NDA signed",
|
||
column6: "Contract #102",
|
||
processName: "Payment Process",
|
||
zone: "E"
|
||
},
|
||
{
|
||
column1: "Deposit Project",
|
||
column2: "private",
|
||
column3: "Deposit",
|
||
column4: "6 months",
|
||
column5: "Terms accepted",
|
||
column6: "Contract #103",
|
||
processName: "Deposit Process",
|
||
zone: "F"
|
||
},
|
||
{
|
||
column1: "Artefact Project",
|
||
column2: "public",
|
||
column3: "Artefact",
|
||
column4: "1 year",
|
||
column5: "GDPR compliant",
|
||
column6: "Contract #104",
|
||
processName: "Artefact Process",
|
||
zone: "G"
|
||
},
|
||
{
|
||
column1: "Resolve Project",
|
||
column2: "private",
|
||
column3: "Resolve",
|
||
column4: "2 years",
|
||
column5: "IP rights",
|
||
column6: "Contract #105",
|
||
processName: "Resolve Process",
|
||
zone: "H"
|
||
},
|
||
{
|
||
column1: "Backup Project",
|
||
column2: "public",
|
||
column3: "Backup",
|
||
column4: "1 year",
|
||
column5: "NDA signed",
|
||
column6: "Contract #106",
|
||
processName: "Backup Process",
|
||
zone: "I"
|
||
}
|
||
];
|
||
const mockProcessRows = [
|
||
{
|
||
process: "User Project",
|
||
role: "User",
|
||
notification: {
|
||
messages: [
|
||
{ id: 1, read: false, date: "2024-03-10", message: "New user joined the project" },
|
||
{ id: 2, read: false, date: "2024-03-09", message: "Project milestone reached" },
|
||
{ id: 3, read: false, date: "2024-03-08", message: "Security update required" },
|
||
{ id: 4, read: true, date: "2024-03-07", message: "Weekly report available" },
|
||
{ id: 5, read: true, date: "2024-03-06", message: "Team meeting scheduled" }
|
||
]
|
||
}
|
||
},
|
||
{
|
||
process: "Member Project",
|
||
role: "Member",
|
||
notification: {
|
||
messages: [
|
||
{ id: 6, read: true, date: "2024-03-10", message: "Member access granted" },
|
||
{ id: 7, read: true, date: "2024-03-09", message: "Documentation updated" },
|
||
{ id: 8, read: true, date: "2024-03-08", message: "Project status: on track" }
|
||
]
|
||
}
|
||
},
|
||
{
|
||
process: "Peer Project",
|
||
role: "Peer",
|
||
notification: {
|
||
unread: 2,
|
||
total: 4,
|
||
messages: [
|
||
{ id: 9, read: false, date: "2024-03-10", message: "New peer project added" },
|
||
{ id: 10, read: false, date: "2024-03-09", message: "Project milestone reached" },
|
||
{ id: 11, read: false, date: "2024-03-08", message: "Security update required" },
|
||
{ id: 12, read: true, date: "2024-03-07", message: "Weekly report available" },
|
||
{ id: 13, read: true, date: "2024-03-06", message: "Team meeting scheduled" }
|
||
]
|
||
}
|
||
},
|
||
{
|
||
process: "Deposit Project",
|
||
role: "Deposit",
|
||
notification: {
|
||
unread: 1,
|
||
total: 10,
|
||
messages: [
|
||
{ id: 14, read: false, date: "2024-03-10", message: "Deposit milestone reached" },
|
||
{ id: 15, read: false, date: "2024-03-09", message: "Security update required" },
|
||
{ id: 16, read: false, date: "2024-03-08", message: "Weekly report available" },
|
||
{ id: 17, read: true, date: "2024-03-07", message: "Team meeting scheduled" },
|
||
{ id: 18, read: true, date: "2024-03-06", message: "Project status: on track" }
|
||
]
|
||
}
|
||
},
|
||
{
|
||
process: "Artefact Project",
|
||
role: "Artefact",
|
||
notification: {
|
||
unread: 0,
|
||
total: 3,
|
||
messages: [
|
||
{ id: 19, read: false, date: "2024-03-10", message: "New artefact added" },
|
||
{ id: 20, read: false, date: "2024-03-09", message: "Security update required" },
|
||
{ id: 21, read: false, date: "2024-03-08", message: "Weekly report available" },
|
||
{ id: 22, read: true, date: "2024-03-07", message: "Team meeting scheduled" },
|
||
{ id: 23, read: true, date: "2024-03-06", message: "Project status: on track" }
|
||
]
|
||
}
|
||
},
|
||
{
|
||
process: "Resolve Project",
|
||
role: "Resolve",
|
||
notification: {
|
||
unread: 5,
|
||
total: 12,
|
||
messages: [
|
||
{ id: 24, read: false, date: "2024-03-10", message: "New issue reported" },
|
||
{ id: 25, read: false, date: "2024-03-09", message: "Security update required" },
|
||
{ id: 26, read: false, date: "2024-03-08", message: "Weekly report available" },
|
||
{ id: 27, read: true, date: "2024-03-07", message: "Team meeting scheduled" },
|
||
{ id: 28, read: true, date: "2024-03-06", message: "Project status: on track" }
|
||
]
|
||
}
|
||
}
|
||
];
|
||
const mockContracts = {
|
||
"Contract #123": {
|
||
title: "User Project Agreement",
|
||
date: "2024-01-15",
|
||
parties: ["Company XYZ", "User Team"],
|
||
terms: ["Data Protection", "User Privacy", "Access Rights", "Service Level Agreement"],
|
||
content: "This agreement establishes the terms and conditions for user project management."
|
||
},
|
||
"Contract #456": {
|
||
title: "Process Management Contract",
|
||
date: "2024-02-01",
|
||
parties: ["Company XYZ", "Process Team"],
|
||
terms: ["Process Workflow", "Quality Standards", "Performance Metrics", "Monitoring Procedures"],
|
||
content: "This contract defines the process management standards and procedures."
|
||
},
|
||
"Contract #789": {
|
||
title: "Member Access Agreement",
|
||
date: "2024-03-15",
|
||
parties: ["Company XYZ", "Member Team"],
|
||
terms: ["Member Rights", "Access Levels", "Security Protocol", "Confidentiality Agreement"],
|
||
content: "This agreement outlines the terms for member access and privileges."
|
||
},
|
||
"Contract #101": {
|
||
title: "Peer Collaboration Agreement",
|
||
date: "2024-04-01",
|
||
parties: ["Company XYZ", "Peer Network"],
|
||
terms: ["Collaboration Rules", "Resource Sharing", "Dispute Resolution", "Network Protocol"],
|
||
content: "This contract establishes peer collaboration and networking guidelines."
|
||
},
|
||
"Contract #102": {
|
||
title: "Payment Processing Agreement",
|
||
date: "2024-05-01",
|
||
parties: ["Company XYZ", "Payment Team"],
|
||
terms: ["Transaction Protocol", "Security Measures", "Fee Structure", "Service Availability"],
|
||
content: "This agreement defines payment processing terms and conditions."
|
||
},
|
||
"Contract #103": {
|
||
title: "Deposit Management Contract",
|
||
date: "2024-06-01",
|
||
parties: ["Company XYZ", "Deposit Team"],
|
||
terms: ["Deposit Rules", "Storage Protocol", "Access Control", "Security Standards"],
|
||
content: "This contract outlines deposit management procedures and security measures."
|
||
},
|
||
"Contract #104": {
|
||
title: "Artefact Handling Agreement",
|
||
date: "2024-07-01",
|
||
parties: ["Company XYZ", "Artefact Team"],
|
||
terms: ["Handling Procedures", "Storage Guidelines", "Access Protocol", "Preservation Standards"],
|
||
content: "This agreement establishes artefact handling and preservation guidelines."
|
||
},
|
||
"Contract #105": {
|
||
title: "Resolution Protocol Agreement",
|
||
date: "2024-08-01",
|
||
parties: ["Company XYZ", "Resolution Team"],
|
||
terms: ["Resolution Process", "Time Constraints", "Escalation Protocol", "Documentation Requirements"],
|
||
content: "This contract defines the resolution process and protocol standards."
|
||
},
|
||
"Contract #106": {
|
||
title: "Backup Service Agreement",
|
||
date: "2024-09-01",
|
||
parties: ["Company XYZ", "Backup Team"],
|
||
terms: ["Backup Schedule", "Data Protection", "Recovery Protocol", "Service Reliability"],
|
||
content: "This agreement outlines backup service terms and recovery procedures."
|
||
}
|
||
};
|
||
|
||
const accountStyle = "/* Styles de base */\n:root {\n --primary-color: #3A506B;\n /* Bleu métallique */\n --secondary-color: #B0BEC5;\n /* Gris acier */\n --accent-color: #D68C45;\n /* Cuivre */\n}\n\nbody {\n font-family: Arial, sans-serif;\n margin: 0;\n padding: 0;\n display: flex;\n height: 100vh;\n background-color: #e9edf1;\n flex-direction: column;\n}\n\n/*-------------------------------------- Avatar--------------------------------------*/\n\n.avatar-section {\n position: relative;\n height: 60px;\n width: 260px;\n margin-left: 10px;\n overflow: hidden;\n border-radius: 10px;\n}\n\n.user-info {\n display: flex;\n flex-direction: column;\n color: white;\n}\n\n.user-name, .user-lastname {\n font-size: 0.9rem;\n font-weight: 700;\n}\n\n\n.user-name:hover, .user-lastname:hover {\n color: var(--accent-color);\n cursor: pointer;\n}\n\n.avatar-container {\n width: 45px;\n height: 45px;\n flex-shrink: 0;\n}\n\n.avatar-container {\n width: 80px; /* Taille réduite */\n height: 80px;\n margin: 0 auto;\n}\n\n.avatar {\n height: 100%;\n border-radius: 50%;\n object-fit: cover;\n border: 2px solid white;\n}\n\n.avatar img {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n}\n\n\n/*-------------------------------------- BANNER--------------------------------------*/\n\n/* Styles pour la bannière avec image */\n.banner-image-container {\n position: relative;\n width: 100%;\n height: 200px;\n overflow: hidden;\n border-radius: 10px;\n margin-bottom: 15px;\n}\n\n.banner-image {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n z-index: 1;\n}\n\n.banner-content {\n position: relative;\n z-index: 2;\n display: flex;\n align-items: center;\n gap: 15px;\n height: 100%;\n padding: 0 15px;\n background: rgba(0, 0, 0, 0.3);\n overflow: visible;\n}\n\n.banner-content .avatar-container {\n width: 45px;\n height: 45px;\n overflow: hidden; \n border-radius: 50%;\n border: 2px solid white;\n transition: transform 0.3s ease; \n}\n\n.banner-content .avatar {\n width: 100%;\n height: 100%;\n object-fit: cover;\n border: none; \n}\n\n.banner-content .avatar-container:hover {\n transform: scale(1.15);\n cursor: pointer;\n}\n\n/* Style pour le bouton de changement de bannière */\n.banner-upload-label {\n display: block;\n width: auto;\n padding: 12px 20px;\n background-color: var(--accent-color);\n color: white;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.3s;\n text-align: center;\n font-size: 16px;\n margin: 20px auto;\n max-width: 250px;\n}\n\n.banner-upload-label:hover {\n background-color: #b06935;\n}\n\n.banner-controls {\n margin-top: 15px;\n display: flex;\n justify-content: center;\n width: 100%;\n}\n\n.banner-preview {\n margin: 10px 0;\n}\n\n.banner-preview h3 {\n margin: 0 0 10px 0;\n font-size: 18px;\n}\n\n.banner-image-container {\n height: 150px;\n margin-bottom: 10px;\n}\n\n\n.nav-wrapper {\n position: fixed;\n background: white;\n display: flex;\n justify-content: space-between;\n align-items: center;\n height: 9vh;\n width: 100vw;\n left: 0;\n top: 0;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n z-index: 1000;\n}\n/* Mise à jour des styles de la navbar pour inclure l'image de bannière */\n.nav-wrapper .avatar-section {\n position: relative;\n background: none;\n}\n\n.nav-wrapper .banner-image {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n z-index: -1;\n filter: brightness(0.7);\n}\n\n\n\n/*-------------------------------------- Popup--------------------------------------*/\n/* Styles pour la popup */\n.popup {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.5);\n z-index: 1000;\n}\n\n.popup-content {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: white;\n padding: 20px;\n border-radius: var(--border-radius);\n box-shadow: var(--box-shadow);\n width: 400px; \n max-height: 80vh; \n overflow-y: auto; \n}\n.popup-content h2 {\n margin: 0 0 15px 0;\n font-size: 24px;\n}\n\n.close-popup {\n position: absolute;\n right: 15px;\n top: 10px;\n font-size: 24px;\n cursor: pointer;\n color: #666;\n}\n\n.close-popup:hover {\n color: #000;\n}\n\n.popup-avatar {\n text-align: center;\n margin: 20px 0;\n position: relative;\n}\n\n.avatar-upload-label {\n position: relative;\n display: inline-block;\n cursor: pointer;\n width: 0%;\n margin-left: -20%;\n}\n\n.avatar-overlay {\n position: absolute;\n top: 0;\n left: 50px;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n border-radius: 50%;\n display: flex;\n justify-content: center;\n align-items: center;\n opacity: 0;\n transition: opacity 0.3s;\n}\n\n.avatar-overlay span {\n color: white;\n font-size: 14px;\n text-align: center;\n}\n\n.avatar-upload-label:hover .avatar-overlay {\n opacity: 1;\n}\n\n.popup-avatar img {\n width: 100px;\n height: 100px;\n border-radius: 50%;\n border: 3px solid var(--accent-color);\n object-fit: cover;\n}\n\n.popup-info {\n margin: 15px 0;\n}\n\n.info-row {\n margin: 8px 0;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.popup-info strong {\n min-width: 100px; /* Largeur fixe pour l'alignement */\n}\n\n/* Editable Name and Lastname */\n.editable {\n cursor: pointer;\n display: inline-block;\n min-width: 100px;\n padding: 2px 5px;\n transition: background-color 0.3s;\n}\n\n.editable:hover {\n background-color: #f0f0f0;\n}\n\n.editable.editing {\n background-color: white;\n border: 1px solid var(--accent-color);\n outline: none;\n}\n\n.edit-input {\n border: 1px solid var(--accent-color);\n border-radius: 3px;\n padding: 2px 5px;\n font-size: inherit;\n font-family: inherit;\n outline: none;\n width: 100%; \n min-width: 100px;\n margin: 0; \n box-sizing: border-box; \n}\n\n/* Boutons */\n\n\n.popup-button-container {\n display: flex\n;\n flex-direction: column;\n margin-top: 20px;\n gap: 15px;\n}\n\n.action-buttons-row {\n display: flex\n;\n justify-content: space-between;\n gap: 15px;\n}\n.banner-upload-label,\n.export-btn,\n.delete-account-btn {\n padding: 8px 15px;\n margin: 10px 0;\n font-size: 14px;\n}\n\n.delete-account-btn {\n background-color: #dc3545;\n}\n\n\n.export-btn,\n.delete-account-btn {\n flex: 1; /* Pour qu'ils prennent la même largeur */\n padding: 12px 20px;\n border: none;\n border-radius: 8px;\n font-size: 16px;\n cursor: pointer;\n transition: background-color 0.3s;\n color: white;\n text-align: center;\n}\n\n\n\n/* Séparateurs */\n.popup-info,\n.export-section,\n.delete-account-section {\n padding-top: 10px;\n margin-top: 10px;\n border-top: 1px solid #eee;\n}\n\n.logout-btn {\n background-color: rgb(108, 117, 125);\n font-size: 16px;\n cursor: pointer;\n color: white;\n text-align: center;\n flex: 1 1 0%;\n padding: 12px 20px;\n border-width: initial;\n border-style: none;\n border-color: initial;\n border-image: initial;\n border-radius: 8px;\n transition: background-color 0.3s;\n}\n\n/*-------------------------------------- Delete Account--------------------------------------*/\n.delete-account-section {\n margin-top: 30px;\n padding-top: 20px;\n border-top: 1px solid #ddd;\n text-align: center;\n}\n\n.delete-account-btn {\n background-color: #dc3545;\n}\n\n.delete-account-btn:hover {\n background-color: #c82333;\n}\n\n/* Style pour la modal de confirmation */\n.confirm-delete-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n justify-content: center;\n align-items: center;\n z-index: 1000;\n}\n\n.confirm-delete-content {\n background-color: white;\n padding: 20px;\n border-radius: 8px;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n max-width: 400px;\n width: 90%;\n text-align: center;\n}\n\n.confirm-delete-content h3 {\n margin-top: 0;\n color: #333;\n}\n\n.confirm-delete-content p {\n margin: 15px 0;\n color: #666;\n}\n\n.confirm-delete-buttons {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 20px;\n}\n\n.confirm-delete-buttons button {\n padding: 8px 20px;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: background-color 0.3s;\n}\n\n.confirm-delete-buttons .confirm-btn {\n background-color: #dc3545;\n color: white;\n}\n\n.confirm-delete-buttons .confirm-btn:hover {\n background-color: #c82333;\n}\n\n.confirm-delete-buttons .cancel-btn {\n background-color: #6c757d;\n color: white;\n}\n\n.confirm-delete-buttons .cancel-btn:hover {\n background-color: #5a6268;\n}\n\n/*-------------------------------------- Export--------------------------------------*/\n.export-section {\n margin: 20px 0;\n text-align: center;\n padding: 15px 0;\n border-top: 1px solid #ddd;\n}\n\n.export-btn {\n background-color: var(--accent-color);\n}\n\n.export-btn:hover {\n background-color: #b06935;\n}\n\n.export-section,\n.delete-account-section {\n width: 100%;\n display: flex;\n justify-content: center;\n margin: 15px 0;\n}\n\n.export-btn,\n.delete-account-btn {\n width: 80%;\n padding: 12px 20px;\n border: none;\n border-radius: 8px;\n font-size: 16px;\n cursor: pointer;\n transition: background-color 0.3s;\n color: white;\n text-align: center;\n}\n\n/*-------------------------------------- NAVBAR--------------------------------------*/\n\n.brand-logo {\n font-size: 1.5rem;\n font-weight: bold;\n}\n\n.nav-wrapper {\n position: fixed;\n background: radial-gradient(circle, white, var(--primary-color));\n display: flex;\n justify-content: space-between;\n align-items: center;\n color: #37474F;\n height: 9vh;\n width: 100vw;\n left: 0;\n top: 0;\n box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, .2), 0px 16px 24px 2px rgba(0, 0, 0, .14), 0px 6px 30px 5px rgba(0, 0, 0, .12);\n}\n\n/* Icônes de la barre de navigation */\n.nav-right-icons {\n margin-right: 20px;\n}\n\n.burger-menu {\n height: 20px;\n width: 20px;\n margin-right: 1rem;\n cursor: pointer;\n}\n\n/* Par défaut, le menu est masqué */\n#menu {\n display: none;\n /* Menu caché par défaut */\n transition: display 0.3s ease-in-out;\n}\n\n\n.burger-menu {\n width: 24px;\n height: 24px;\n cursor: pointer;\n}\n\n.burger-menu-icon {\n width: 100%;\n height: 100%;\n}\n/* Icône burger */\n#burger-icon {\n cursor: pointer;\n}\n\n/* .menu-content {\n display: none;\n position: absolute;\n top: 3.4rem;\n right: 1rem;\n background-color: white;\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\n border-radius: 5px;\n overflow: hidden;\n}\n\n.menu-content a {\n display: block;\n padding: 10px 20px;\n text-decoration: none;\n color: #333;\n border-bottom: 1px solid #e0e0e0;\n\n &:hover {\n background-color: rgba(26, 28, 24, .08);\n }\n}\n\n.menu-content a:last-child {\n border-bottom: none;\n} */\n\n/* Ajustement pour la barre de navigation fixe */\n.container {\n display: flex;\n flex: 1;\n height: 90vh;\n margin-top: 9vh;\n margin-left: -1%;\n text-align: left;\n width: 100vw;\n}\n\n/* Liste des information sur l'account */\n\n.parameter-list {\n width: 24.5%;\n background-color: #1f2c3d;\n color: white;\n padding: 20px;\n box-sizing: border-box;\n overflow-y: auto;\n border-right: 2px solid #2c3e50;\n flex-shrink: 0; \n padding-right: 10px;\n height: 91vh;\n}\n\n.parameter-list ul {\n padding: 15px;\n margin-left: 10px;\n border-radius: 8px;\n background-color: #273646;\n cursor: pointer;\n transition: background-color 0.3s, box-shadow 0.3s;\n}\n\n\n.parameter-list ul:hover {\n background-color: #34495e;\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n}\n\n\n/* Zone des info des parametre */\n\n.parameter-area {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0; \n background-color: #ffffff;\n border-radius: 10px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);\n margin: 0px;\n margin-top: 20px;\n margin-left: 1%;\n margin-bottom: -7px;\n}\n\n/* En-tête du parametre */\n.parameter-header {\n background-color: #34495e;\n color: white;\n padding: 15px;\n font-size: 20px;\n font-weight: bold;\n border-radius: 10px 10px 0 0;\n text-align: center;\n}\n\n/* Style du tableau dans parameter-area */\n.parameter-table {\n width: 100%;\n border-collapse: collapse;\n margin: 15px 0;\n table-layout: fixed; \n}\n\n.parameter-table th, .parameter-table td {\n border: 1px solid #ddd;\n padding: 8px;\n white-space: nowrap;\n overflow: hidden;\n text-align: center;\n}\n\n.parameter-table th {\n background-color: var(--secondary-color);\n color: white;\n font-weight: bold;\n}\n\n.parameter-table tr:nth-child(even) {\n background-color: #f2f2f2;\n}\n\n\n\n.parameter-table tr:hover {\n background-color: #ddd;\n}\n\n/* Conteneur pour les boutons */\n.button-container {\n display: flex;\n justify-content: center;\n gap: 15px;\n margin: 15px 0;\n}\n\n/* Boutons \"Ajouter une ligne\" et \"Confirmer\" */\n.add-row-button, .confirm-all-button, .delete-row-button {\n background-color: var(--accent-color);\n color: white;\n border: none;\n padding: 8px 15px;\n cursor: pointer;\n border-radius: 5px;\n font-size: 0.9em;\n margin-right: 5px;\n}\n\n.add-row-button:hover, .confirm-all-button:hover, .delete-row-button:hover {\n background-color: #b06935;\n}\n\n\n.button-style {\n background-color: var(--accent-color);\n color: white;\n border: none;\n border-radius: 5px;\n padding: 10px 20px;\n cursor: pointer;\n transition: background-color 0.3s;\n}\n\n.button-style:hover {\n background-color: darkorange;\n}\n\n.content-container {\n width: 100%;\n}\n\n#pairing-content,\n#wallet-content {\n width: 100%;\n}\n\n.editable-cell {\n cursor: pointer;\n}\n\n.editable-cell:hover {\n background-color: #f5f5f5;\n}\n\n.edit-input {\n width: 100%;\n padding: 5px;\n box-sizing: border-box;\n border: 1px solid #ddd;\n border-radius: 4px;\n}\n\n/*-------------------------------------- Notification--------------------------------------*/\n.notification-cell {\n position: relative;\n}\n\n.notification-bell {\n position: relative;\n display: inline-block;\n}\n\n.notification-badge {\n position: absolute;\n top: -8px;\n right: -8px;\n background-color: red;\n color: white;\n border-radius: 50%;\n padding: 2px 6px;\n font-size: 12px;\n min-width: 15px;\n text-align: center;\n}\n\n.fa-bell {\n color: #666;\n font-size: 20px;\n}\n\n\n/* Media Queries pour Mobile */\n@media screen and (max-width: 768px) {\n /* Navbar */\n .nav-wrapper {\n height: 9vh;\n padding: 0;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n /* Section avatar (gauche) */\n .avatar-section {\n width: 200px; /* Largeur réduite */\n margin-left: 10px;\n order: 0; /* Garde à gauche */\n }\n\n .avatar-container {\n width: 35px;\n height: 35px;\n }\n\n .user-info span {\n font-size: 0.8rem;\n }\n\n /* Logo (centre) */\n .brand-logo {\n order: 0; \n flex: 0 0 auto;\n padding: 0;\n font-size: 1.2rem;\n }\n\n /* Menu burger (droite) */\n .nav-right-icons {\n order: 0; \n width: auto;\n padding-right: 15px;\n }\n\n .burger-menu {\n width: 24px;\n height: 24px;\n }\n\n /* Ajustements pour la bannière */\n .banner-image-container {\n height: 100%;\n }\n\n .banner-content {\n padding: 0 10px;\n }\n\n /* Style des boutons dans la popup */\n .button-container {\n display: flex;\n gap: 10px;\n margin: 15px 0;\n width: 100%;\n }\n\n .export-btn,\n .delete-account-btn {\n flex: 1;\n padding: 12px 15px;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n cursor: pointer;\n transition: background-color 0.3s;\n color: white;\n }\n\n .export-btn {\n background-color: var(--accent-color);\n }\n\n .delete-account-btn {\n background-color: var(--danger-color);\n }\n}\n\n/* Media Queries pour très petits écrans */\n@media screen and (max-width: 380px) {\n .avatar-section {\n width: 150px; \n }\n\n .user-info span {\n font-size: 0.7rem;\n }\n}\n\n/* Style des boutons */\n.button-container {\n display: flex;\n gap: 15px;\n margin: 15px 0;\n width: 100%;\n}\n\n.export-btn,\n.delete-account-btn {\n flex: 1;\n padding: 12px 20px;\n border: none;\n border-radius: 8px;\n color: white;\n cursor: pointer;\n transition: background-color 0.3s;\n font-size: 14px;\n display: block;\n}\n\n.export-btn {\n background-color: var(--accent-color);\n}\n\n.delete-account-btn {\n background-color: rgb(219, 17, 17);\n display: block;\n visibility: visible;\n}\n\n.export-btn:hover {\n background-color: #b06935;\n}\n\n.delete-account-btn:hover {\n background-color: #b60718;\n}\n\n@media screen and (max-width: 768px) {\n .button-container {\n gap: 10px;\n }\n\n .export-btn,\n .delete-account-btn {\n padding: 12px 15px;\n font-size: 14px;\n }\n}\n\n/* Style pour les boutons de la popup */\n.popup-buttons {\n display: flex;\n gap: 15px;\n margin: 15px 0;\n width: 100%;\n}\n\n/* Style pour les boutons d'action des tableaux */\n.button-container {\n display: flex;\n gap: 15px;\n margin: 15px 0;\n width: 100%;\n}\n\n/* Style pour le header mobile */\n.mobile-nav {\n display: none;\n width: 100%;\n padding: 10px;\n background-color: #34495e;\n overflow-x: auto;\n white-space: nowrap;\n}\n\n.mobile-nav ul {\n display: flex;\n gap: 10px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n/* Media Query pour mobile */\n@media screen and (max-width: 768px) {\n .parameter-list {\n display: flex;\n width: 100%;\n min-width: 100%;\n height: auto;\n overflow-x: auto;\n background-color: rgb(31, 44, 61);\n padding: 10px;\n border-right: none;\n border-bottom: 2px solid rgb(44, 62, 80);\n }\n\n .mobile-nav {\n display: flex; /* Affiche la navigation mobile */\n }\n\n .parameter-header {\n display: flex;\n flex-direction: column;\n }\n\n .parameter-list-ul {\n text-align: center;\n flex: 1 1 0%;\n margin: 0px 5px;\n padding: 10px;\n white-space: nowrap;\n margin-bottom: none;\n }\n\n .parameter-list-ul:hover {\n background-color: #34495e;\n }\n\n .container {\n flex-direction: column;\n }\n\n .parameter-area {\n margin: -5px;\n }\n}\n\n/* Style pour le header et la navigation mobile */\n.parameter-header {\n background-color: #34495e;\n padding: 20px 0;\n margin: 0;\n width: 100%;\n}\n\n.mobile-nav {\n display: none; /* Par défaut caché */\n width: 100%;\n padding: 10px;\n background-color: #34495e;\n overflow-x: auto;\n}\n\n.mobile-nav ul {\n display: flex;\n gap: 10px;\n margin: 0;\n padding: 10px;\n list-style: none;\n}\n\n.mobile-nav li {\n flex: 0 0 auto;\n white-space: nowrap;\n}\n\n/* Ajoutez ces styles pour la bannière dans la popup */\n.banner-container {\n width: 100%;\n margin-bottom: 20px;\n}\n\n.banner-wrapper {\n width: 100%;\n height: 120px;\n overflow: hidden;\n position: relative;\n border-radius: 10px;\n}\n\n.banner-wrapper img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n position: absolute;\n top: 0;\n left: 0;\n}\n\n/* Mise à jour des styles existants */\n.popup-content {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: white;\n padding: 20px;\n border-radius: 10px;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n width: 90%;\n max-width: 500px;\n max-height: 80vh;\n overflow-y: auto;\n}\n\n/* Style pour le conteneur de la bannière */\n.banner-upload-label {\n display: block;\n width: auto;\n padding: 12px 20px;\n background-color: var(--accent-color);\n color: white;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.3s;\n text-align: center;\n font-size: 16px;\n margin: 10px auto;\n max-width: 200px;\n}\n\n/* ---------------------Style pour la popup de contrat--------------------- */\n\n.contract-popup-overlay {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n display: flex\n;\n justify-content: center;\n align-items: center;\n z-index: 1000;\n}\n\n.contract-popup-content {\n background: white;\n padding: 30px;\n border-radius: 8px;\n max-width: 600px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n position: relative;\n}\n\n.close-contract-popup {\n position: absolute;\n top: 15px;\n right: 15px;\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #666;\n}\n\n/* Style pour la popup d'alerte */\n.alert-popup {\n position: fixed;\n top: 20px;\n left: 50%;\n transform: translateX(-50%);\n background-color: #f44336;\n color: white;\n padding: 15px 25px;\n border-radius: 4px;\n box-shadow: 0 2px 5px rgba(0,0,0,0.2);\n z-index: 1000;\n display: none;\n animation: slideDown 0.3s ease-out;\n}\n\n/* ---------------------Style pour la popup notification--------------------- */\n\n\n.notifications-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n justify-content: center;\n align-items: center;\n z-index: 1000;\n}\n\n.notifications-content {\n position: relative;\n background: white;\n border-radius: 8px;\n padding: 24px;\n width: 90%;\n max-width: 500px;\n}\n\n.close-button {\n position: absolute;\n top: 15px;\n right: 20px;\n font-size: 24px;\n color: #666;\n cursor: pointer;\n transition: color 0.2s;\n}\n\n.close-button:hover {\n color: #000;\n}\n\n.notifications-title {\n padding-right: 30px; /* Pour éviter que le titre ne chevauche le bouton de fermeture */\n}\n\n.notifications-title {\n font-size: 24px;\n color: #445B6E;\n margin-bottom: 20px;\n font-weight: 500;\n}\n\n.notifications-list {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.notification-item {\n display: flex;\n align-items: flex-start;\n padding: 12px 0;\n border-bottom: 1px solid #eee;\n cursor: pointer;\n}\n\n.notification-status {\n margin-right: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px; \n height: 24px; \n}\n\n.dot-icon, .check-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n}\n\n.notification-content {\n flex: 1;\n}\n\n.notification-message {\n font-size: 16px;\n color: #333;\n margin-bottom: 4px;\n}\n\n.notification-date {\n font-size: 14px;\n color: #666;\n}\n\n.notification-item:hover {\n background-color: #f8f9fa;\n}\n\n.notification-item.read {\n opacity: 0.7;\n}\n\n.notification-item.unread {\n background-color: #fff;\n}\n\n.close-notifications {\n position: absolute;\n top: 15px;\n right: 15px;\n border: none;\n background: none;\n font-size: 24px;\n color: #666;\n cursor: pointer;\n}\n\n/*-------------------------------------- STYLE ACTION BUTTON ---------------------*/\n\n.action-buttons-wrapper {\n display: flex;\n flex-direction: row;\n gap: 10px;\n justify-content: center;\n margin: 20px 0;\n}\n\n.action-button {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border-radius: 8px;\n border: none;\n font-size: 16px;\n cursor: pointer;\n transition: all 0.3s ease;\n color: white;\n height: 40px;\n width: 40px;\n}\n\n.confirm-button {\n background-color: #4CAF50;\n}\n\n.confirm-button:hover {\n background-color: #3d8b40;\n transform: translateY(-2px);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n}\n\n.cancel-button {\n background-color: rgb(244, 67, 54);\n}\n\n.cancel-button:hover {\n background-color: #d32f2f;\n transform: translateY(-2px);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n}\n\n.banner-image.clickable {\n cursor: pointer;\n transition: opacity 0.3s ease;\n}\n\n.banner-image.clickable:hover {\n opacity: 0.8;\n}\n\n.parameter-list-ul.profile {\n position: relative;\n overflow: hidden; \n max-height: 200px;\n margin-bottom: 20px;\n}\n\n\n.profile-preview {\n position: relative;\n width: 100%;\n height: 100%;\n}\n\n.preview-banner {\n position: relative;\n width: 100%;\n height: 120px; \n overflow: hidden;\n}\n\n.preview-banner-img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.preview-info {\n position: relative;\n display: flex;\n align-items: center;\n padding: 10px;\n gap: 10px;\n background: rgba(0, 0, 0, 0.3);\n}\n\n.preview-avatar {\n width: 45px;\n height: 45px;\n border-radius: 50%;\n border: 2px solid white;\n}\n\n/* ---------------------Style pour le QR code--------------------- */\n\n.qr-code {\n width: 50px;\n height: 50px;\n cursor: pointer;\n transition: transform 0.2s ease;\n}\n\n.qr-code:hover {\n transform: scale(1.5);\n}\n\n.qr-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.7);\n display: flex;\n justify-content: center;\n align-items: center;\n z-index: 1000;\n}\n\n.qr-modal-content {\n background-color: white;\n padding: 20px;\n border-radius: 8px;\n position: relative;\n text-align: center;\n}\n\n.close-qr-modal {\n position: absolute;\n right: 10px;\n top: 5px;\n font-size: 24px;\n cursor: pointer;\n color: #666;\n}\n\n.close-qr-modal:hover {\n color: #000;\n}\n\n.qr-code-large {\n max-width: 300px;\n margin: 10px 0;\n}\n\n.qr-address {\n margin-top: 10px;\n word-break: break-all;\n font-size: 12px;\n color: #666;\n}\n\n.pairing-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n justify-content: center;\n align-items: center;\n z-index: 1000;\n}\n\n.pairing-modal-content {\n background-color: white;\n padding: 2rem;\n border-radius: 8px;\n width: 90%;\n max-width: 500px;\n}\n\n.pairing-form {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n\n.form-group {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.form-group label {\n font-weight: bold;\n}\n\n.button-group {\n display: flex;\n gap: 1rem;\n justify-content: flex-end;\n margin-top: 1rem;\n}\n\n.button-group button {\n padding: 0.5rem 1rem;\n border-radius: 4px;\n cursor: pointer;\n}\n\n.confirm-button {\n background-color: var(--accent-color);\n color: white;\n border: none;\n}\n\n.cancel-button {\n background-color: #ccc;\n border: none;\n}\n";
|
||
|
||
async function loadValidationRuleModal(templatePath = "/src/components/validation-rule-modal/validation-rule-modal.html") {
|
||
if (document.getElementById("validation-rule-modal"))
|
||
return;
|
||
const res = await fetch(templatePath);
|
||
const html = await res.text();
|
||
const tempDiv = document.createElement("div");
|
||
tempDiv.innerHTML = html;
|
||
const modal = tempDiv.querySelector("#validation-rule-modal");
|
||
if (!modal) {
|
||
throw new Error("Modal HTML missing #validation-rule-modal");
|
||
}
|
||
document.body.appendChild(modal);
|
||
}
|
||
function showValidationRuleModal(onSubmit) {
|
||
const modal = document.getElementById("validation-rule-modal");
|
||
const quorumInput = document.getElementById("vr-quorum");
|
||
const minsigInput = document.getElementById("vr-minsig");
|
||
const fieldsInput = document.getElementById("vr-fields");
|
||
const cancelBtn = document.getElementById("vr-cancel");
|
||
const submitBtn = document.getElementById("vr-submit");
|
||
quorumInput.value = "";
|
||
minsigInput.value = "";
|
||
fieldsInput.value = "";
|
||
modal.style.display = "flex";
|
||
cancelBtn.onclick = () => {
|
||
modal.style.display = "none";
|
||
};
|
||
submitBtn.onclick = () => {
|
||
const rule = {
|
||
quorum: parseInt(quorumInput.value),
|
||
min_sig_member: parseInt(minsigInput.value),
|
||
fields: fieldsInput.value.split(",").map((f) => f.trim()).filter(Boolean)
|
||
};
|
||
modal.style.display = "none";
|
||
onSubmit(rule);
|
||
};
|
||
}
|
||
|
||
function createKeyValueSection(title, id, isRoleSection = false) {
|
||
const section = document.createElement("div");
|
||
section.id = id;
|
||
section.style.cssText = "margin-bottom: 2rem; background: #fff; padding: 1rem; border-radius: 0.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1);";
|
||
const titleEl = document.createElement("h2");
|
||
titleEl.textContent = title;
|
||
titleEl.style.cssText = "font-size: 1.25rem; font-weight: bold; margin-bottom: 1rem;";
|
||
section.appendChild(titleEl);
|
||
const rowContainer = document.createElement("div");
|
||
section.appendChild(rowContainer);
|
||
const addBtn = document.createElement("button");
|
||
addBtn.textContent = "+ Add Row";
|
||
addBtn.style.cssText = `
|
||
margin-top: 1rem;
|
||
padding: 0.5rem 1rem;
|
||
border: 1px solid #888;
|
||
border-radius: 0.375rem;
|
||
background-color: #f9f9f9;
|
||
cursor: pointer;
|
||
`;
|
||
section.appendChild(addBtn);
|
||
const roleRowStates = [];
|
||
const nonRoleRowStates = [];
|
||
const inputStyle = "flex: 1; height: 2.5rem; padding: 0.5rem; border: 1px solid #ccc; border-radius: 0.375rem;";
|
||
const createRow = () => {
|
||
const row = document.createElement("div");
|
||
row.style.cssText = "display: flex; gap: 1rem; margin-bottom: 0.5rem; align-items: center;";
|
||
const deleteBtn = document.createElement("button");
|
||
deleteBtn.textContent = "🗑️";
|
||
deleteBtn.style.cssText = "background: none; border: none; font-size: 1.2rem; cursor: pointer;";
|
||
deleteBtn.onclick = () => {
|
||
row.remove();
|
||
updateDeleteButtons();
|
||
};
|
||
if (isRoleSection) {
|
||
const roleName = document.createElement("input");
|
||
const members = document.createElement("input");
|
||
const storages = document.createElement("input");
|
||
roleName.placeholder = "Role name";
|
||
members.placeholder = "members";
|
||
storages.placeholder = "storages";
|
||
[roleName, members, storages].forEach((input) => {
|
||
input.type = "text";
|
||
input.style.cssText = inputStyle;
|
||
});
|
||
const ruleButton = document.createElement("button");
|
||
ruleButton.textContent = "Add Validation Rule";
|
||
ruleButton.style.cssText = "padding: 0.3rem 0.75rem; border: 1px solid #ccc; border-radius: 0.375rem; background: #f0f0f0; cursor: pointer;";
|
||
const rules = [];
|
||
ruleButton.onclick = () => {
|
||
showValidationRuleModal((rule) => {
|
||
rules.push(rule);
|
||
ruleButton.textContent = `Rules (${rules.length})`;
|
||
});
|
||
};
|
||
row.appendChild(roleName);
|
||
row.appendChild(members);
|
||
row.appendChild(storages);
|
||
row.appendChild(ruleButton);
|
||
row.appendChild(deleteBtn);
|
||
roleRowStates.push({ roleNameInput: roleName, membersInput: members, storagesInput: storages, validationRules: rules });
|
||
} else {
|
||
const fileInput = document.createElement("input");
|
||
fileInput.type = "file";
|
||
fileInput.style.display = "none";
|
||
fileInput.onchange = async () => {
|
||
const file = fileInput.files?.[0];
|
||
if (!file)
|
||
return;
|
||
const buffer = await file.arrayBuffer();
|
||
const uint8 = new Uint8Array(buffer);
|
||
rowState.fileBlob = {
|
||
type: file.type,
|
||
data: uint8
|
||
};
|
||
valueInput.value = `📄 ${file.name}`;
|
||
valueInput.disabled = true;
|
||
attachBtn.textContent = `📎 ${file.name}`;
|
||
};
|
||
const attachBtn = document.createElement("button");
|
||
attachBtn.textContent = "📎 Attach";
|
||
attachBtn.style.cssText = "padding: 0.3rem 0.75rem; border: 1px solid #ccc; border-radius: 0.375rem; background: #f0f0f0; cursor: pointer;";
|
||
attachBtn.onclick = () => fileInput.click();
|
||
const keyInput = document.createElement("input");
|
||
const valueInput = document.createElement("input");
|
||
const rowState = {
|
||
keyInput,
|
||
valueInput,
|
||
fileInput,
|
||
fileBlob: null
|
||
};
|
||
nonRoleRowStates.push(rowState);
|
||
keyInput.placeholder = "Key";
|
||
valueInput.placeholder = "Value";
|
||
[keyInput, valueInput].forEach((input) => {
|
||
input.type = "text";
|
||
input.style.cssText = inputStyle;
|
||
});
|
||
row.appendChild(keyInput);
|
||
row.appendChild(valueInput);
|
||
row.appendChild(attachBtn);
|
||
row.appendChild(fileInput);
|
||
row.appendChild(deleteBtn);
|
||
}
|
||
rowContainer.appendChild(row);
|
||
updateDeleteButtons();
|
||
};
|
||
const updateDeleteButtons = () => {
|
||
const rows = Array.from(rowContainer.children);
|
||
rows.forEach((row) => {
|
||
const btn = row.querySelector("button:last-child");
|
||
if (rows.length === 1) {
|
||
btn.disabled = true;
|
||
btn.style.visibility = "hidden";
|
||
} else {
|
||
btn.disabled = false;
|
||
btn.style.visibility = "visible";
|
||
}
|
||
});
|
||
};
|
||
createRow();
|
||
addBtn.addEventListener("click", createRow);
|
||
return {
|
||
element: section,
|
||
getData: () => {
|
||
if (isRoleSection) {
|
||
const data = {};
|
||
for (const row of roleRowStates) {
|
||
const key = row.roleNameInput.value.trim();
|
||
if (!key)
|
||
continue;
|
||
data[key] = {
|
||
members: row.membersInput.value.split(",").map((x) => x.trim()).filter(Boolean),
|
||
storages: row.storagesInput.value.split(",").map((x) => x.trim()).filter(Boolean),
|
||
validation_rules: row.validationRules
|
||
};
|
||
}
|
||
return data;
|
||
} else {
|
||
const data = {};
|
||
for (const row of nonRoleRowStates) {
|
||
const key = row.keyInput.value.trim();
|
||
if (!key)
|
||
continue;
|
||
if (row.fileBlob) {
|
||
data[key] = row.fileBlob;
|
||
} else {
|
||
data[key] = row.valueInput.value.trim();
|
||
}
|
||
}
|
||
return data;
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
async function getProcessCreation(container) {
|
||
await loadValidationRuleModal();
|
||
container.style.display = "block";
|
||
container.innerHTML = `<div class="parameter-header">Process Creation</div>`;
|
||
const privateSec = createKeyValueSection("Private Data", "private-section");
|
||
const publicSec = createKeyValueSection("Public Data", "public-section");
|
||
const rolesSec = createKeyValueSection("Roles", "roles-section", true);
|
||
container.appendChild(privateSec.element);
|
||
container.appendChild(publicSec.element);
|
||
container.appendChild(rolesSec.element);
|
||
const btn = document.createElement("button");
|
||
btn.textContent = "Create Process";
|
||
btn.style.cssText = `
|
||
display: block;
|
||
margin: 2rem auto 0;
|
||
padding: 0.75rem 2rem;
|
||
font-size: 1rem;
|
||
font-weight: bold;
|
||
background-color: #4f46e5;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 0.5rem;
|
||
cursor: pointer;
|
||
`;
|
||
btn.onclick = async () => {
|
||
const privateData = privateSec.getData();
|
||
const publicData = publicSec.getData();
|
||
const roles = rolesSec.getData();
|
||
console.log("Private:", privateData);
|
||
console.log("Public:", publicData);
|
||
console.log("Roles:", roles);
|
||
const service = await Services.getInstance();
|
||
const createProcessResult = await service.createProcess(privateData, publicData, roles);
|
||
const processId = createProcessResult.updated_process.process_id;
|
||
const stateId = createProcessResult.updated_process.current_process.states[0].state_id;
|
||
await service.handleApiReturn(createProcessResult);
|
||
await service.createPrdUpdate(processId, stateId);
|
||
await service.handleApiReturn(createProcessResult);
|
||
const approveChangeResult = await service.approveChange(processId, stateId);
|
||
await service.handleApiReturn(approveChangeResult);
|
||
if (approveChangeResult) {
|
||
const process = await service.getProcess(processId);
|
||
let newState = process ? service.getStateFromId(process, stateId) : null;
|
||
if (!newState)
|
||
return;
|
||
for (const label of Object.keys(newState.keys)) {
|
||
const hash = newState.pcd_commitment[label];
|
||
const encryptedData = await service.getBlobFromDb(hash);
|
||
if (!encryptedData)
|
||
continue;
|
||
const filename = `${label}-${hash.slice(0, 8)}.bin`;
|
||
const blob2 = new Blob([encryptedData], { type: "application/octet-stream" });
|
||
const link = document.createElement("a");
|
||
link.href = URL.createObjectURL(blob2);
|
||
link.download = filename;
|
||
link.click();
|
||
setTimeout(() => URL.revokeObjectURL(link.href), 1e3);
|
||
}
|
||
const blob = new Blob([JSON.stringify(newState, null, 2)], { type: "application/json" });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement("a");
|
||
a.href = url;
|
||
a.download = `process_${processId}_${stateId}.json`;
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
};
|
||
container.appendChild(btn);
|
||
}
|
||
|
||
function getDocumentValidation(container) {
|
||
const state = {
|
||
file: null,
|
||
fileHash: null,
|
||
certificate: null,
|
||
commitmentHashes: []
|
||
};
|
||
container.innerHTML = "";
|
||
container.style.cssText = `
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
min-height: 100vh;
|
||
gap: 2rem;
|
||
`;
|
||
function createDropButton(label, onDrop, accept = "*/*") {
|
||
const wrapper = document.createElement("div");
|
||
wrapper.style.cssText = `
|
||
width: 200px;
|
||
height: 100px;
|
||
border: 2px dashed #888;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
font-weight: bold;
|
||
background: #f8f8f8;
|
||
text-align: center;
|
||
padding: 0.5rem;
|
||
box-sizing: border-box;
|
||
`;
|
||
const title = document.createElement("div");
|
||
title.textContent = label;
|
||
const filename = document.createElement("div");
|
||
filename.style.cssText = `
|
||
font-size: 0.85rem;
|
||
margin-top: 0.5rem;
|
||
color: #444;
|
||
word-break: break-word;
|
||
text-align: center;
|
||
`;
|
||
wrapper.appendChild(title);
|
||
wrapper.appendChild(filename);
|
||
const updateVisuals = (file) => {
|
||
wrapper.style.borderColor = "green";
|
||
wrapper.style.background = "#e6ffed";
|
||
filename.textContent = file.name;
|
||
};
|
||
const fileInput = document.createElement("input");
|
||
fileInput.type = "file";
|
||
fileInput.accept = accept;
|
||
fileInput.style.display = "none";
|
||
document.body.appendChild(fileInput);
|
||
fileInput.onchange = () => {
|
||
const file = fileInput.files?.[0];
|
||
if (file) {
|
||
onDrop(file, updateVisuals);
|
||
fileInput.value = "";
|
||
}
|
||
};
|
||
wrapper.ondragover = (e) => {
|
||
e.preventDefault();
|
||
wrapper.style.background = "#e0e0e0";
|
||
};
|
||
wrapper.ondragleave = () => {
|
||
wrapper.style.background = "#f8f8f8";
|
||
};
|
||
wrapper.ondrop = (e) => {
|
||
e.preventDefault();
|
||
wrapper.style.background = "#f8f8f8";
|
||
const file = e.dataTransfer?.files?.[0];
|
||
if (file) {
|
||
onDrop(file, updateVisuals);
|
||
}
|
||
};
|
||
wrapper.onclick = () => {
|
||
fileInput.click();
|
||
};
|
||
return wrapper;
|
||
}
|
||
const fileDropButton = createDropButton("Drop file", async (file, updateVisuals) => {
|
||
try {
|
||
state.file = file;
|
||
updateVisuals(file);
|
||
console.log("Loaded file:", state.file);
|
||
checkReady();
|
||
} catch (err) {
|
||
alert("Failed to drop the file.");
|
||
console.error(err);
|
||
}
|
||
});
|
||
const certDropButton = createDropButton("Drop certificate", async (file, updateVisuals) => {
|
||
try {
|
||
const text = await file.text();
|
||
const json = JSON.parse(text);
|
||
if (typeof json === "object" && json !== null && typeof json.pcd_commitment === "object" && typeof json.state_id === "string") {
|
||
state.certificate = json;
|
||
state.commitmentHashes = Object.values(json.pcd_commitment).map((h) => h.toLowerCase());
|
||
updateVisuals(file);
|
||
console.log("Loaded certificate, extracted hashes:", state.commitmentHashes);
|
||
checkReady();
|
||
} else {
|
||
alert("Invalid certificate structure.");
|
||
}
|
||
} catch (err) {
|
||
alert("Failed to parse certificate JSON.");
|
||
console.error(err);
|
||
}
|
||
});
|
||
const buttonRow = document.createElement("div");
|
||
buttonRow.style.display = "flex";
|
||
buttonRow.style.gap = "2rem";
|
||
buttonRow.appendChild(fileDropButton);
|
||
buttonRow.appendChild(certDropButton);
|
||
container.appendChild(buttonRow);
|
||
async function checkReady() {
|
||
if (state.file && state.certificate && state.commitmentHashes.length > 0) {
|
||
const fileBlob = {
|
||
type: state.file.type,
|
||
data: new Uint8Array(await state.file.arrayBuffer())
|
||
};
|
||
const service = await Services.getInstance();
|
||
const commitedIn = state.certificate.commited_in;
|
||
if (!commitedIn)
|
||
return;
|
||
const [prevTxid, prevTxVout] = commitedIn.split(":");
|
||
const processId = state.certificate.commited_in;
|
||
const stateId = state.certificate.state_id;
|
||
const process = await service.getProcess(processId);
|
||
if (!process)
|
||
return;
|
||
const nextState = service.getNextStateAfterId(process, stateId);
|
||
if (!nextState) {
|
||
alert(`❌ Validation failed: No next state, is the state you're trying to validate commited?`);
|
||
return;
|
||
}
|
||
const [outspentTxId, _] = nextState.commited_in.split(":");
|
||
console.log(outspentTxId);
|
||
const txInfo = await fetchTransaction(outspentTxId);
|
||
if (!txInfo) {
|
||
console.error(`Validation error: Can't fetch new state commitment transaction`);
|
||
alert(`❌ Validation failed: invalid or non existent commited_in for state ${stateId}.`);
|
||
return;
|
||
}
|
||
let found = false;
|
||
for (const vin of txInfo.vin) {
|
||
if (vin.txid === prevTxid) {
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!found) {
|
||
console.error(`Validation error: new state doesn't spend previous state commitment transaction`);
|
||
alert("❌ Validation failed: Unconsistent commitment transactions history.");
|
||
return;
|
||
}
|
||
found = false;
|
||
for (const vout of txInfo.vout) {
|
||
console.log(vout);
|
||
if (vout.scriptpubkey_type && vout.scriptpubkey_type === "op_return") {
|
||
found = true;
|
||
} else {
|
||
continue;
|
||
}
|
||
if (vout.scriptpubkey_asm) {
|
||
const hash = extractHexFromScriptAsm(vout.scriptpubkey_asm);
|
||
if (hash) {
|
||
if (hash !== stateId) {
|
||
console.error(`Validation error: expected stateId ${stateId}, got ${hash}`);
|
||
alert("❌ Validation failed: Transaction does not commit to that state.");
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!found) {
|
||
alert("❌ Validation failed: Transaction does not contain data.");
|
||
return;
|
||
}
|
||
found = false;
|
||
for (const label of Object.keys(state.certificate.pcd_commitment)) {
|
||
console.log(`Computing hash with label ${label}`);
|
||
const fileHex = service.getHashForFile(commitedIn, label, fileBlob);
|
||
console.log(`Found hash ${fileHex}`);
|
||
found = state.commitmentHashes.includes(fileHex);
|
||
if (found)
|
||
break;
|
||
}
|
||
if (found) {
|
||
alert("✅ Validation successful: file hash found in pcd_commitment.");
|
||
} else {
|
||
alert("❌ Validation failed: file hash NOT found in pcd_commitment.");
|
||
}
|
||
}
|
||
}
|
||
async function fetchTransaction(txid) {
|
||
const url = `https://mempool.4nkweb.com/api/tx/${txid}`;
|
||
const response = await fetch(url);
|
||
if (!response.ok) {
|
||
throw new Error(`Failed to fetch outspend status: ${response.statusText}`);
|
||
}
|
||
const outspend = await response.json();
|
||
return outspend;
|
||
}
|
||
function extractHexFromScriptAsm(scriptAsm) {
|
||
const parts = scriptAsm.trim().split(/\s+/);
|
||
const last = parts[parts.length - 1];
|
||
if (/^[0-9a-fA-F]{64}$/.test(last)) {
|
||
return last.toLowerCase();
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function createProcessTab(container, processes) {
|
||
container.id = "process-tab";
|
||
container.style.display = "block";
|
||
container.style.cssText = "padding: 1.5rem;";
|
||
const title = document.createElement("h2");
|
||
title.textContent = "Processes";
|
||
title.style.cssText = "font-size: 1.5rem; font-weight: bold; margin-bottom: 1rem;";
|
||
container.appendChild(title);
|
||
processes.forEach((proc) => {
|
||
const card = document.createElement("div");
|
||
card.style.cssText = "margin-bottom: 1rem; padding: 1rem; border: 1px solid #ddd; border-radius: 0.5rem; background: #fff;";
|
||
const nameEl = document.createElement("h3");
|
||
nameEl.textContent = proc.name;
|
||
nameEl.style.cssText = "font-size: 1.2rem; font-weight: bold; margin-bottom: 0.5rem;";
|
||
card.appendChild(nameEl);
|
||
const dataList = document.createElement("div");
|
||
for (const [key, value] of Object.entries(proc.publicData)) {
|
||
const item = document.createElement("div");
|
||
item.style.cssText = "margin-bottom: 0.5rem;";
|
||
const label = document.createElement("strong");
|
||
label.textContent = key + ": ";
|
||
item.appendChild(label);
|
||
const trimmed = value.replace(/^'|'$/g, "");
|
||
let parsed;
|
||
try {
|
||
parsed = JSON.parse(trimmed);
|
||
} catch (_) {
|
||
parsed = trimmed;
|
||
}
|
||
if (parsed && typeof parsed === "object") {
|
||
const saveBtn = document.createElement("button");
|
||
saveBtn.textContent = "💾 Save as JSON";
|
||
saveBtn.style.cssText = "margin-left: 0.5rem; padding: 0.25rem 0.5rem; border: 1px solid #ccc; border-radius: 0.375rem; background: #f0f0f0; cursor: pointer;";
|
||
saveBtn.onclick = () => {
|
||
const blob = new Blob([JSON.stringify(parsed, null, 2)], { type: "application/json" });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement("a");
|
||
a.href = url;
|
||
a.download = `${proc.name}_${key}.json`;
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
};
|
||
item.appendChild(saveBtn);
|
||
} else {
|
||
const span = document.createElement("span");
|
||
span.textContent = String(parsed);
|
||
item.appendChild(span);
|
||
}
|
||
dataList.appendChild(item);
|
||
}
|
||
card.appendChild(dataList);
|
||
container.appendChild(card);
|
||
});
|
||
return container;
|
||
}
|
||
|
||
let isAddingRow = false;
|
||
let currentRow = null;
|
||
let currentMode = "pairing";
|
||
class AccountElement extends HTMLElement {
|
||
dom;
|
||
constructor() {
|
||
super();
|
||
this.attachShadow({ mode: "open" });
|
||
this.dom = getCorrectDOM("account-element");
|
||
const fontAwesome = document.createElement("link");
|
||
fontAwesome.rel = "stylesheet";
|
||
fontAwesome.href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css";
|
||
this.shadowRoot.appendChild(fontAwesome);
|
||
const style = document.createElement("style");
|
||
style.textContent = accountStyle;
|
||
this.shadowRoot.appendChild(style);
|
||
this.shadowRoot.innerHTML = `
|
||
|
||
<style>
|
||
${accountStyle}
|
||
</style>
|
||
<!-- Profile Popup -->
|
||
<div id="avatar-popup" class="popup">
|
||
<div class="popup-content">
|
||
<span class="close-popup">×</span>
|
||
<h2>Profile</h2>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main Content -->
|
||
<div class="container">
|
||
<!-- Parameter List -->
|
||
<div class="parameter-list">
|
||
<ul class="parameter-list-ul profile">
|
||
<!-- Profile Preview (visible par défaut) -->
|
||
<div class="profile-preview" onclick="window.openAvatarPopup()">
|
||
<div class="preview-banner">
|
||
<img src="https://via.placeholder.com/800x200" alt="Banner" class="preview-banner-img">
|
||
</div>
|
||
<div class="preview-info">
|
||
<img src="https://via.placeholder.com/150" alt="Avatar" class="preview-avatar">
|
||
<div class="preview-text user-info">
|
||
<span class="preview-name" id="popup-name">Profile</span>
|
||
<span class="preview-lastname" id="popup-lastname"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Profile Content (masqué par défaut) -->
|
||
<div class="profile-content" style="display: none;">
|
||
<!-- Banner Preview Section -->
|
||
<div class="banner-preview">
|
||
<div class="banner-image-container">
|
||
<img src="https://via.placeholder.com/800x200" alt="Banner" class="banner-image" id="popup-banner-img">
|
||
<div class="banner-content">
|
||
<div class="avatar-container">
|
||
<img src="https://via.placeholder.com/150" alt="Avatar" class="avatar" id="popup-avatar-img">
|
||
</div>
|
||
<div class="user-info">
|
||
<span class="editable" id="popup-name"></span>
|
||
<span class="editable" id="popup-lastname"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="banner-controls">
|
||
<label for="banner-upload" class="banner-upload-label button-style">
|
||
Change Banner Image
|
||
<input type="file" id="banner-upload" accept="image/*" style="display: none;">
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Avatar Upload Section -->
|
||
<div class="popup-avatar">
|
||
<label for="avatar-upload" class="avatar-upload-label">
|
||
<img src="https://via.placeholder.com/150" alt="Avatar" class="avatar" id="popup-avatar-img">
|
||
<div class="avatar-overlay">
|
||
<span>Change Avatar</span>
|
||
</div>
|
||
</label>
|
||
<input type="file" id="avatar-upload" accept="image/*" style="display: none;">
|
||
</div>
|
||
|
||
<!-- User Info Section -->
|
||
<div class="popup-info">
|
||
<p><strong>Name:</strong> <span class="editable" id="popup-name"></span></p>
|
||
<!--<p><strong>Last Name:</strong> <span class="editable" id="popup-lastname"></span></p>-->
|
||
<p><strong>Address:</strong> 🏠 🌍 🗽🎊😩-🎊😑🎄😩</p>
|
||
</div>
|
||
|
||
<!-- Buttons Container -->
|
||
<div class="popup-buttons">
|
||
<button class="delete-account-btn" onclick="window.confirmDeleteAccount()">Delete Account</button>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
</ul>
|
||
<ul class="parameter-list-ul" onclick="window.showPairing()">Pairing 🔗</ul>
|
||
<!-- <ul class="parameter-list-ul" onclick="window.showWallet()">Wallet 👛</ul> -->
|
||
<ul class="parameter-list-ul" onclick="window.showProcess()">Process ⚙️</ul>
|
||
<ul class="parameter-list-ul" onclick="window.showProcessCreation()">Process Creation</ul>
|
||
<ul class="parameter-list-ul" onclick="window.showDocumentValidation()">Document Validation</ul>
|
||
<!-- <ul class="parameter-list-ul" onclick="window.showData()">Data 💾</ul> -->
|
||
</div>
|
||
|
||
<!-- Parameter Area -->
|
||
<div class="parameter-area">
|
||
<div class="content-container">
|
||
<div id="pairing-content"></div>
|
||
<!-- <div id="wallet-content"></div> -->
|
||
<div id="process-content"></div>
|
||
<div id="process-creation-content"></div>
|
||
<div id="document-validation-content"></div>
|
||
<!-- <div id="data-content"></div> -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
window.showPairing = () => this.showPairing();
|
||
window.showWallet = () => this.showWallet();
|
||
window.showProcess = () => this.showProcess();
|
||
window.showProcessCreation = () => this.showProcessCreation();
|
||
window.showDocumentValidation = () => this.showDocumentValidation();
|
||
window.showData = () => this.showData();
|
||
window.addWalletRow = () => this.addWalletRow();
|
||
window.confirmWalletRow = () => this.confirmWalletRow();
|
||
window.cancelWalletRow = () => this.cancelWalletRow();
|
||
window.editDeviceName = (cell) => this.editDeviceName(cell);
|
||
window.showProcessNotifications = (processName) => this.showProcessNotifications(processName);
|
||
window.handleLogout = () => this.handleLogout();
|
||
window.confirmDeleteAccount = () => this.confirmDeleteAccount();
|
||
window.showContractPopup = (contractId) => this.showContractPopup(contractId);
|
||
window.addRowPairing = () => this.addRowPairing();
|
||
window.deleteRowPairing = (button) => this.deleteRowPairing(button);
|
||
window.confirmRowPairing = () => this.confirmRowPairing();
|
||
window.cancelRowPairing = () => this.cancelRowPairing();
|
||
window.updateNavbarBanner = (bannerUrl) => this.updateNavbarBanner(bannerUrl);
|
||
window.saveBannerToLocalStorage = (bannerUrl) => this.saveBannerToLocalStorage(bannerUrl);
|
||
window.loadSavedBanner = () => this.loadSavedBanner();
|
||
window.closeNotificationPopup = (event) => this.closeNotificationPopup(event);
|
||
window.markAsRead = (processName, messageId, element) => this.markAsRead(processName, messageId, element);
|
||
window.exportRecovery = () => this.exportRecovery();
|
||
window.generateRecoveryWords = () => this.generateRecoveryWords();
|
||
window.exportUserData = () => this.exportUserData();
|
||
window.updateActionButtons = () => this.updateActionButtons();
|
||
window.openAvatarPopup = () => this.openAvatarPopup();
|
||
window.closeAvatarPopup = () => this.closeAvatarPopup();
|
||
window.showQRCodeModal = (pairingId) => this.showQRCodeModal(pairingId);
|
||
if (!localStorage.getItem("rows")) {
|
||
localStorage.setItem("rows", JSON.stringify(defaultRows));
|
||
}
|
||
}
|
||
connectedCallback() {
|
||
this.initializeEventListeners();
|
||
this.loadSavedBanner();
|
||
this.loadUserInfo();
|
||
const savedAvatar = localStorage.getItem("userAvatar");
|
||
const savedBanner = localStorage.getItem("userBanner");
|
||
const savedName = localStorage.getItem("userName");
|
||
const savedLastName = localStorage.getItem("userLastName");
|
||
if (savedAvatar) {
|
||
const navAvatar = this.shadowRoot?.querySelector(".avatar");
|
||
if (navAvatar)
|
||
navAvatar.src = savedAvatar;
|
||
}
|
||
if (savedBanner) {
|
||
const navBanner = this.shadowRoot?.querySelector(".banner-image");
|
||
if (navBanner)
|
||
navBanner.src = savedBanner;
|
||
}
|
||
if (savedName) {
|
||
this.updateNavbarName(savedName);
|
||
}
|
||
if (savedLastName) {
|
||
this.updateNavbarLastName(savedLastName);
|
||
}
|
||
}
|
||
showAlert(message) {
|
||
let alertPopup = this.shadowRoot?.querySelector(".alert-popup");
|
||
if (!alertPopup) {
|
||
alertPopup = document.createElement("div");
|
||
alertPopup.className = "alert-popup";
|
||
this.shadowRoot?.appendChild(alertPopup);
|
||
}
|
||
alertPopup.textContent = message;
|
||
alertPopup.style.display = "block";
|
||
setTimeout(() => {
|
||
alertPopup.style.display = "none";
|
||
}, 3e3);
|
||
}
|
||
// Fonctions de gestion des comptes et de l'interface utilisateur
|
||
confirmDeleteAccount() {
|
||
const modal = document.createElement("div");
|
||
modal.className = "confirm-delete-modal";
|
||
modal.innerHTML = `
|
||
<h3>Delete Account</h3>
|
||
<p>Are you sure you want to delete your account? This action cannot be undone.</p>
|
||
<div class="confirm-delete-buttons">
|
||
<button class="cancel-btn">Cancel</button>
|
||
<button class="confirm-btn">Delete</button>
|
||
</div>
|
||
`;
|
||
this.shadowRoot?.appendChild(modal);
|
||
modal.style.display = "block";
|
||
const cancelBtn = modal.querySelector(".cancel-btn");
|
||
const confirmBtn = modal.querySelector(".confirm-btn");
|
||
cancelBtn?.addEventListener("click", () => {
|
||
modal.remove();
|
||
});
|
||
confirmBtn?.addEventListener("click", () => {
|
||
this.deleteAccount();
|
||
modal.remove();
|
||
});
|
||
}
|
||
deleteAccount() {
|
||
localStorage.clear();
|
||
window.location.href = "/login.html";
|
||
}
|
||
updateNavbarBanner(imageUrl) {
|
||
const navbarSection = this.shadowRoot?.querySelector(".nav-wrapper .avatar-section");
|
||
if (!navbarSection)
|
||
return;
|
||
let bannerImg = navbarSection.querySelector(".banner-image");
|
||
if (!bannerImg) {
|
||
bannerImg = document.createElement("img");
|
||
bannerImg.className = "banner-image";
|
||
navbarSection.insertBefore(bannerImg, navbarSection.firstChild);
|
||
}
|
||
bannerImg.src = imageUrl;
|
||
}
|
||
saveBannerToLocalStorage(dataUrl) {
|
||
localStorage.setItem("userBanner", dataUrl);
|
||
}
|
||
loadSavedBanner() {
|
||
const savedBanner = localStorage.getItem("userBanner");
|
||
if (savedBanner) {
|
||
const bannerImg = this.shadowRoot?.getElementById("popup-banner-img");
|
||
if (bannerImg) {
|
||
bannerImg.src = savedBanner;
|
||
}
|
||
this.updateNavbarBanner(savedBanner);
|
||
}
|
||
}
|
||
closeNotificationPopup(event) {
|
||
const target = event.target;
|
||
const isOverlay = target.classList.contains("notification-popup-overlay");
|
||
const isCloseButton = target.classList.contains("close-popup");
|
||
if (!isOverlay && !isCloseButton)
|
||
return;
|
||
const popup = this.shadowRoot?.querySelector(".notification-popup-overlay");
|
||
if (popup)
|
||
popup.remove();
|
||
}
|
||
markAsRead(processName, messageId, element) {
|
||
const process = mockProcessRows.find((p) => p.process === processName);
|
||
if (!process)
|
||
return;
|
||
const message = process.notification.messages.find((m) => m.id === messageId);
|
||
if (!message || message.read)
|
||
return;
|
||
message.read = true;
|
||
element.classList.remove("unread");
|
||
element.classList.add("read");
|
||
const statusIcon = element.querySelector(".notification-status");
|
||
if (statusIcon) {
|
||
statusIcon.innerHTML = `
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16" fill="green">
|
||
<path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/>
|
||
</svg>`;
|
||
}
|
||
const notifCount = this.calculateNotifications(process.notification.messages);
|
||
const countElement = this.shadowRoot?.querySelector(`.notification-count[data-process="${processName}"]`);
|
||
if (countElement) {
|
||
countElement.textContent = `${notifCount.unread}/${notifCount.total}`;
|
||
const bellContainer = countElement.closest(".notification-container");
|
||
const bell = bellContainer?.querySelector("svg");
|
||
if (bell && bellContainer && notifCount.unread === 0) {
|
||
bellContainer.classList.remove("has-unread");
|
||
bell.style.fill = "#666";
|
||
}
|
||
}
|
||
}
|
||
// Fonctions de gestion des données et de l'interface
|
||
calculateNotifications(messages) {
|
||
const total = messages.length;
|
||
const unread = messages.filter((msg) => !msg.read).length;
|
||
return { unread, total };
|
||
}
|
||
// Fonctions de récupération
|
||
exportRecovery() {
|
||
Swal.fire({
|
||
title: "Recovery Words Export",
|
||
text: "4 words will be displayed. We strongly recommend writing them down on paper before exporting the account. Do you want to continue?",
|
||
icon: "warning",
|
||
showCancelButton: true,
|
||
confirmButtonText: "Confirm",
|
||
cancelButtonText: "Cancel",
|
||
confirmButtonColor: "#C89666",
|
||
cancelButtonColor: "#6c757d",
|
||
// Ajouter des styles personnalisés
|
||
customClass: {
|
||
container: "recovery-popup-container",
|
||
popup: "recovery-popup"
|
||
}
|
||
}).then((result) => {
|
||
if (result.isConfirmed) {
|
||
const recoveryWords = this.generateRecoveryWords();
|
||
localStorage.setItem("recoveryWords", JSON.stringify(recoveryWords));
|
||
Swal.fire({
|
||
title: "Your Recovery Words",
|
||
html: `
|
||
<div class="recovery-words-container">
|
||
${recoveryWords.map((word, index) => `
|
||
<div class="recovery-word">
|
||
<span class="word-number">${index + 1}.</span>
|
||
<span class="word">${word}</span>
|
||
</div>
|
||
`).join("")}
|
||
</div>
|
||
<div class="recovery-warning">
|
||
Please write these words down carefully. They will be needed to recover your account.
|
||
</div>
|
||
`,
|
||
showCancelButton: false,
|
||
confirmButtonText: "I confirm the export",
|
||
confirmButtonColor: "#C89666",
|
||
allowOutsideClick: false,
|
||
allowEscapeKey: false,
|
||
customClass: {
|
||
container: "recovery-popup-container",
|
||
popup: "recovery-popup"
|
||
}
|
||
}).then((result2) => {
|
||
if (result2.isConfirmed) {
|
||
localStorage.setItem("recoveryExported", "true");
|
||
const exportRecoveryBtn = this.shadowRoot?.querySelector(".recovery-btn");
|
||
if (exportRecoveryBtn) {
|
||
exportRecoveryBtn.disabled = true;
|
||
exportRecoveryBtn.style.opacity = "0.5";
|
||
exportRecoveryBtn.style.cursor = "not-allowed";
|
||
}
|
||
}
|
||
});
|
||
}
|
||
});
|
||
}
|
||
generateRecoveryWords() {
|
||
const wordsList = [
|
||
"apple",
|
||
"banana",
|
||
"orange",
|
||
"grape",
|
||
"kiwi",
|
||
"mango",
|
||
"peach",
|
||
"plum",
|
||
"lemon",
|
||
"lime",
|
||
"cherry",
|
||
"melon",
|
||
"pear",
|
||
"fig",
|
||
"date",
|
||
"berry"
|
||
];
|
||
const recoveryWords = [];
|
||
while (recoveryWords.length < 4) {
|
||
const randomWord = wordsList[Math.floor(Math.random() * wordsList.length)];
|
||
if (!recoveryWords.includes(randomWord)) {
|
||
recoveryWords.push(randomWord);
|
||
}
|
||
}
|
||
return recoveryWords;
|
||
}
|
||
exportUserData() {
|
||
const data = {};
|
||
for (let i = 0; i < localStorage.length; i++) {
|
||
const key = localStorage.key(i);
|
||
if (key) {
|
||
const value = localStorage.getItem(key);
|
||
data[key] = value;
|
||
}
|
||
}
|
||
const jsonData = JSON.stringify(data, null, 2);
|
||
const blob = new Blob([jsonData], { type: "application/json" });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement("a");
|
||
a.href = url;
|
||
a.download = "user_data.json";
|
||
this.shadowRoot?.appendChild(a);
|
||
a.click();
|
||
this.shadowRoot?.removeChild(a);
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
updateActionButtons() {
|
||
const buttonContainer = this.shadowRoot?.querySelector(".button-container");
|
||
if (!buttonContainer)
|
||
return;
|
||
buttonContainer.innerHTML = `
|
||
<div class="action-buttons-wrapper">
|
||
<button onclick="${this.getConfirmFunction()}" class="action-button confirm-button">✓</button>
|
||
<button onclick="${this.getCancelFunction()}" class="action-button cancel-button">✗</button>
|
||
</div>
|
||
`;
|
||
}
|
||
getConfirmFunction() {
|
||
switch (currentMode) {
|
||
case "wallet":
|
||
return "window.confirmWalletRow()";
|
||
case "process":
|
||
return "window.confirmProcessRow()";
|
||
default:
|
||
return "window.confirmRowPairing()";
|
||
}
|
||
}
|
||
getCancelFunction() {
|
||
switch (currentMode) {
|
||
case "wallet":
|
||
return "window.cancelWalletRow()";
|
||
case "process":
|
||
return "window.cancelProcessRow()";
|
||
default:
|
||
return "window.cancelRowPairing()";
|
||
}
|
||
}
|
||
// Fonctions de gestion des tableaux
|
||
async addRowPairing() {
|
||
if (isAddingRow)
|
||
return;
|
||
isAddingRow = true;
|
||
const modal = document.createElement("div");
|
||
modal.className = "pairing-modal";
|
||
modal.innerHTML = `
|
||
<div class="pairing-modal-content">
|
||
<h3>Add New Device</h3>
|
||
<div class="pairing-form">
|
||
<div class="form-group">
|
||
<label for="sp-address">SP Address</label>
|
||
<input type="text" id="sp-address" class="edit-input" placeholder="Enter SP Address">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="device-name">Device Name</label>
|
||
<input type="text" id="device-name" class="edit-input" placeholder="Enter Device Name">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="sp-emojis">SP Emojis</label>
|
||
<input type="text" id="sp-emojis" class="edit-input" readonly>
|
||
</div>
|
||
<div class="button-group">
|
||
<button class="confirm-button">Confirm</button>
|
||
<button class="cancel-button">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
this.shadowRoot?.appendChild(modal);
|
||
const spAddressInput = modal.querySelector("#sp-address");
|
||
const spEmojisInput = modal.querySelector("#sp-emojis");
|
||
const deviceNameInput = modal.querySelector("#device-name");
|
||
const confirmButton = modal.querySelector(".confirm-button");
|
||
const cancelButton = modal.querySelector(".cancel-button");
|
||
spAddressInput?.addEventListener("input", async () => {
|
||
const emojis = await addressToEmoji(spAddressInput.value);
|
||
if (spEmojisInput)
|
||
spEmojisInput.value = emojis;
|
||
});
|
||
confirmButton?.addEventListener("click", () => {
|
||
const spAddress = spAddressInput?.value.trim();
|
||
const deviceName = deviceNameInput?.value.trim();
|
||
const spEmojis = spEmojisInput?.value.trim();
|
||
if (!spAddress || !deviceName) {
|
||
this.showAlert("Please fill in all required fields");
|
||
return;
|
||
}
|
||
const newRow = {
|
||
column1: spAddress,
|
||
column2: deviceName,
|
||
column3: spEmojis || ""
|
||
};
|
||
const storageKey = STORAGE_KEYS[currentMode];
|
||
const rows = JSON.parse(localStorage.getItem(storageKey) || "[]");
|
||
rows.push(newRow);
|
||
localStorage.setItem(storageKey, JSON.stringify(rows));
|
||
this.updateTableContent(rows);
|
||
modal.remove();
|
||
isAddingRow = false;
|
||
});
|
||
cancelButton?.addEventListener("click", () => {
|
||
modal.remove();
|
||
isAddingRow = false;
|
||
});
|
||
}
|
||
// Fonctions de mise à jour de l'interface
|
||
updateTableContent(rows) {
|
||
const tbody = this.shadowRoot?.querySelector("#pairing-table tbody");
|
||
if (!tbody)
|
||
return;
|
||
tbody.innerHTML = rows.map((row) => `
|
||
<tr>
|
||
<td class="device-name" onclick="window.editDeviceName(this)">${row.column2}</td>
|
||
<td>${row.column3}</td>
|
||
<td>
|
||
<img src="https://api.qrserver.com/v1/create-qr-code/?size=50x50&data=${encodeURIComponent(row.column1)}"
|
||
alt="QR Code"
|
||
title="${row.column1}"
|
||
class="qr-code"
|
||
onclick="window.showQRCodeModal('${encodeURIComponent(row.column1)}')">
|
||
</td>
|
||
<td>
|
||
<button class="delete-button" onclick="window.deleteRowPairing(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16" fill="red">
|
||
<path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/>
|
||
</svg>
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
`).join("");
|
||
}
|
||
confirmRowPairing() {
|
||
if (!currentRow)
|
||
return;
|
||
const inputs = currentRow.getElementsByTagName("input");
|
||
const values = Array.from(inputs).map((input) => input.value.trim());
|
||
if (values.some((value) => value === "")) {
|
||
this.showAlert("Please fill in all fields");
|
||
return;
|
||
}
|
||
if (values[0].length !== 118) {
|
||
this.showAlert("SP Address must be exactly 118 characters long");
|
||
return;
|
||
}
|
||
const newRow = {
|
||
column1: values[0],
|
||
column2: values[1],
|
||
column3: values[2]
|
||
};
|
||
const storageKey = STORAGE_KEYS[currentMode];
|
||
const rows = JSON.parse(localStorage.getItem(storageKey) || "[]");
|
||
rows.push(newRow);
|
||
localStorage.setItem(storageKey, JSON.stringify(rows));
|
||
isAddingRow = false;
|
||
currentRow = null;
|
||
this.resetButtonContainer();
|
||
this.updateTableContent(rows);
|
||
}
|
||
cancelRowPairing() {
|
||
if (!currentRow)
|
||
return;
|
||
currentRow.remove();
|
||
isAddingRow = false;
|
||
currentRow = null;
|
||
this.resetButtonContainer();
|
||
}
|
||
resetButtonContainer() {
|
||
const buttonContainer = this.shadowRoot?.querySelector(".button-container");
|
||
if (!buttonContainer)
|
||
return;
|
||
buttonContainer.innerHTML = `
|
||
<button class="add-row-button button-style" onclick="window.addRowPairing()">Add a line</button>
|
||
`;
|
||
}
|
||
deleteRowPairing(button) {
|
||
const row = button.closest("tr");
|
||
if (!row)
|
||
return;
|
||
const table = row.closest("tbody");
|
||
if (!table)
|
||
return;
|
||
const remainingRows = table.getElementsByTagName("tr").length;
|
||
if (remainingRows <= 2) {
|
||
this.showAlert("You must keep at least 2 devices paired");
|
||
return;
|
||
}
|
||
const modal = document.createElement("div");
|
||
modal.className = "confirm-delete-modal";
|
||
modal.innerHTML = `
|
||
<div class="confirm-delete-content">
|
||
<h3>Confirm Deletion</h3>
|
||
<p>Are you sure you want to delete this device?</p>
|
||
<div class="confirm-delete-buttons">
|
||
<button class="cancel-btn">Cancel</button>
|
||
<button class="confirm-btn">Delete</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
this.shadowRoot?.appendChild(modal);
|
||
const confirmBtn = modal.querySelector(".confirm-btn");
|
||
const cancelBtn = modal.querySelector(".cancel-btn");
|
||
confirmBtn?.addEventListener("click", () => {
|
||
const index = Array.from(table.children).indexOf(row);
|
||
const storageKey = STORAGE_KEYS[currentMode];
|
||
const rows = JSON.parse(localStorage.getItem(storageKey) || "[]");
|
||
if (index > -1) {
|
||
rows.splice(index, 1);
|
||
localStorage.setItem(storageKey, JSON.stringify(rows));
|
||
}
|
||
row.style.transition = "opacity 0.3s, transform 0.3s";
|
||
row.style.opacity = "0";
|
||
row.style.transform = "translateX(-100%)";
|
||
setTimeout(() => {
|
||
row.remove();
|
||
}, 300);
|
||
modal.remove();
|
||
});
|
||
cancelBtn?.addEventListener("click", () => {
|
||
modal.remove();
|
||
});
|
||
}
|
||
editDeviceName(cell) {
|
||
if (cell.classList.contains("editing"))
|
||
return;
|
||
const currentValue = cell.textContent || "";
|
||
const input = document.createElement("input");
|
||
input.type = "text";
|
||
input.value = currentValue;
|
||
input.className = "edit-input";
|
||
input.addEventListener("blur", () => this.finishEditing(cell, input));
|
||
input.addEventListener("keypress", (e) => {
|
||
if (e.key === "Enter") {
|
||
this.finishEditing(cell, input);
|
||
}
|
||
});
|
||
cell.textContent = "";
|
||
cell.appendChild(input);
|
||
cell.classList.add("editing");
|
||
input.focus();
|
||
}
|
||
async finishEditing(cell, input) {
|
||
const newValue = input.value.trim();
|
||
if (newValue === "") {
|
||
cell.textContent = cell.getAttribute("data-original-value") || "";
|
||
cell.classList.remove("editing");
|
||
return;
|
||
}
|
||
try {
|
||
const service = await Services.getInstance();
|
||
const pairingProcessId = service.getPairingProcessId();
|
||
const process = await service.getProcess(pairingProcessId);
|
||
if (process) {
|
||
await service.updateMemberPublicName(process, newValue);
|
||
}
|
||
cell.textContent = newValue;
|
||
cell.classList.remove("editing");
|
||
} catch (error) {
|
||
console.error("Failed to update name:", error);
|
||
cell.textContent = cell.getAttribute("data-original-value") || "";
|
||
cell.classList.remove("editing");
|
||
}
|
||
}
|
||
// Fonction pour gérer le téléchargement de l'avatar
|
||
handleAvatarUpload(event) {
|
||
const input = event.target;
|
||
const file = input.files?.[0];
|
||
if (file) {
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => {
|
||
const result = e.target?.result;
|
||
const popupAvatar = this.shadowRoot?.getElementById("popup-avatar-img");
|
||
const navAvatar = this.shadowRoot?.querySelector(".nav-wrapper .avatar");
|
||
if (popupAvatar)
|
||
popupAvatar.src = result;
|
||
if (navAvatar)
|
||
navAvatar.src = result;
|
||
localStorage.setItem("userAvatar", result);
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
}
|
||
async showProcessCreation() {
|
||
this.hideAllContent();
|
||
const container = this.shadowRoot?.getElementById("process-creation-content");
|
||
if (container) {
|
||
getProcessCreation(container);
|
||
}
|
||
}
|
||
async showDocumentValidation() {
|
||
this.hideAllContent();
|
||
const container = this.shadowRoot?.getElementById("document-validation-content");
|
||
if (container) {
|
||
getDocumentValidation(container);
|
||
}
|
||
}
|
||
async showProcess() {
|
||
this.hideAllContent();
|
||
const container = this.shadowRoot?.getElementById("process-content");
|
||
if (container) {
|
||
const service = await Services.getInstance();
|
||
const myProcesses = await service.getMyProcesses();
|
||
if (myProcesses && myProcesses.length != 0) {
|
||
const myProcessesDataUnfiltered = await Promise.all(myProcesses.map(async (processId) => {
|
||
const process = await service.getProcess(processId);
|
||
const lastState = process ? service.getLastCommitedState(process) : null;
|
||
if (!lastState) {
|
||
return {
|
||
name: "",
|
||
publicData: {}
|
||
};
|
||
}
|
||
const description = await service.decryptAttribute(processId, lastState, "description");
|
||
const name = description ? description : "N/A";
|
||
const publicData = process ? await service.getPublicData(process) : null;
|
||
if (!publicData) {
|
||
return {
|
||
name: "",
|
||
publicData: {}
|
||
};
|
||
}
|
||
return {
|
||
name,
|
||
publicData
|
||
};
|
||
}));
|
||
const myProcessesData = myProcessesDataUnfiltered.filter((p) => p.name !== "" && Object.keys(p.publicData).length != 0);
|
||
createProcessTab(container, myProcessesData);
|
||
} else {
|
||
createProcessTab(container, []);
|
||
}
|
||
}
|
||
}
|
||
showProcessNotifications(processName) {
|
||
const process = mockProcessRows.find((p) => p.process === processName);
|
||
if (!process)
|
||
return;
|
||
const modal = document.createElement("div");
|
||
modal.className = "notifications-modal";
|
||
let notificationsList = process.notification.messages.map((msg) => `
|
||
<div class="notification-item ${msg.read ? "read" : "unread"}"
|
||
onclick="window.markAsRead('${processName}', ${msg.id}, this)">
|
||
<div class="notification-status">
|
||
${msg.read ? `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16" fill="green">
|
||
<path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/>
|
||
</svg>` : `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16" fill="black">
|
||
<path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512z"/>
|
||
</svg>`}
|
||
</div>
|
||
<div class="notification-content">
|
||
<span>${msg.message}</span>
|
||
<small>${msg.date}</small>
|
||
</div>
|
||
</div>
|
||
`).join("");
|
||
if (process.notification.messages.length === 0) {
|
||
notificationsList = "<p>No notifications</p>";
|
||
}
|
||
modal.innerHTML = `
|
||
<div class="notifications-content">
|
||
<h3>${processName} Notifications</h3>
|
||
<div class="notifications-list">
|
||
${notificationsList}
|
||
</div>
|
||
<button class="close-notifications">x</button>
|
||
</div>
|
||
`;
|
||
this.shadowRoot?.appendChild(modal);
|
||
const countElement = this.shadowRoot?.querySelector(`.notification-count[data-process="${processName}"]`);
|
||
if (countElement) {
|
||
const notifCount = this.calculateNotifications(process.notification.messages);
|
||
countElement.textContent = `${notifCount.unread}/${notifCount.total}`;
|
||
}
|
||
const closeButton = modal.querySelector(".close-notifications");
|
||
closeButton?.addEventListener("click", () => {
|
||
modal.remove();
|
||
this.showProcess();
|
||
});
|
||
}
|
||
handleLogout() {
|
||
localStorage.clear();
|
||
window.location.href = "../login/login.html";
|
||
}
|
||
// Fonctions de gestion des contrats
|
||
showContractPopup(contractId, event) {
|
||
if (event) {
|
||
event.preventDefault();
|
||
}
|
||
const contract = mockContracts[contractId];
|
||
if (!contract) {
|
||
console.error("Contract not found:", contractId);
|
||
return;
|
||
}
|
||
const popup = document.createElement("div");
|
||
popup.className = "contract-popup-overlay";
|
||
popup.innerHTML = `
|
||
<div class="contract-popup-content">
|
||
<button class="close-contract-popup">×</button>
|
||
<h2>${contract.title}</h2>
|
||
<div>
|
||
<p><strong>Date:</strong> ${contract.date}</p>
|
||
<p><strong>Parties:</strong> ${contract.parties.join(", ")}</p>
|
||
<p><strong>Terms:</strong></p>
|
||
<ul>
|
||
${contract.terms.map((term) => `<li>${term}</li>`).join("")}
|
||
</ul>
|
||
<p><strong>Content:</strong> ${contract.content}</p>
|
||
</div>
|
||
</div>
|
||
`;
|
||
this.shadowRoot?.appendChild(popup);
|
||
const closeBtn = popup.querySelector(".close-contract-popup");
|
||
const closePopup = () => popup.remove();
|
||
closeBtn?.addEventListener("click", closePopup);
|
||
popup.addEventListener("click", (e) => {
|
||
if (e.target === popup)
|
||
closePopup();
|
||
});
|
||
}
|
||
// Fonction utilitaire pour cacher tous les contenus
|
||
hideAllContent() {
|
||
const contents = ["pairing-content", "wallet-content", "process-content", "process-creation-content", "data-content", "document-validation-content"];
|
||
contents.forEach((id) => {
|
||
const element = this.shadowRoot?.getElementById(id);
|
||
if (element) {
|
||
element.style.display = "none";
|
||
}
|
||
});
|
||
}
|
||
// Fonctions d'affichage des sections
|
||
async showPairing() {
|
||
const service = await Services.getInstance();
|
||
const spAddress = await service.getDeviceAddress();
|
||
isAddingRow = false;
|
||
currentRow = null;
|
||
currentMode = "pairing";
|
||
this.hideAllContent();
|
||
const headerElement = this.shadowRoot?.getElementById("parameter-header");
|
||
if (headerElement) {
|
||
headerElement.textContent = "Pairing";
|
||
}
|
||
const pairingContent = this.shadowRoot?.getElementById("pairing-content");
|
||
if (pairingContent) {
|
||
pairingContent.style.display = "block";
|
||
pairingContent.innerHTML = `
|
||
<div class="parameter-header" id="parameter-header">Pairing</div>
|
||
<div class="table-container">
|
||
<table class="parameter-table" id="pairing-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Device Name</th>
|
||
<th>SP Emojis</th>
|
||
<th>QR Code</th>
|
||
<th>Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
<div class="button-container">
|
||
<button class="add-row-button button-style" onclick="window.addRowPairing()">Add a device</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
let rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.pairing) || "[]");
|
||
const deviceExists = rows.some((row) => row.column1 === spAddress);
|
||
if (!deviceExists && spAddress) {
|
||
const emojis = await addressToEmoji(spAddress);
|
||
try {
|
||
const pairingProcessId = await service.getPairingProcessId();
|
||
console.log("Pairing Process ID:", pairingProcessId);
|
||
const pairingProcess = await service.getProcess(pairingProcessId);
|
||
console.log("Pairing Process:", pairingProcess);
|
||
const userName = pairingProcess?.states?.[0]?.public_data?.memberPublicName || localStorage.getItem("userName");
|
||
console.log("Username found:", userName);
|
||
const newRow = {
|
||
column1: spAddress,
|
||
column2: userName,
|
||
column3: emojis
|
||
};
|
||
rows = [newRow, ...rows];
|
||
localStorage.setItem(STORAGE_KEYS.pairing, JSON.stringify(rows));
|
||
} catch (error) {
|
||
console.error("Error getting pairing process:", error);
|
||
const newRow = {
|
||
column1: spAddress,
|
||
column2: "This Device",
|
||
column3: emojis
|
||
};
|
||
rows = [newRow, ...rows];
|
||
localStorage.setItem(STORAGE_KEYS.pairing, JSON.stringify(rows));
|
||
}
|
||
}
|
||
this.updateTableContent(rows);
|
||
}
|
||
}
|
||
showWallet() {
|
||
isAddingRow = false;
|
||
currentRow = null;
|
||
currentMode = "wallet";
|
||
this.hideAllContent();
|
||
const headerTitle = this.shadowRoot?.getElementById("header-title");
|
||
if (headerTitle)
|
||
headerTitle.textContent = "Wallet";
|
||
const walletContent = this.shadowRoot?.getElementById("wallet-content");
|
||
if (!walletContent)
|
||
return;
|
||
walletContent.style.display = "block";
|
||
walletContent.innerHTML = `
|
||
<div class="parameter-header" id="parameter-header">Wallet</div>
|
||
<div class="table-container">
|
||
<table class="parameter-table" id="wallet-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Label</th>
|
||
<th>Wallet</th>
|
||
<th>Type</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
<div class="button-container">
|
||
<button class="add-row-button button-style" onclick="addWalletRow()">Add a line</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.wallet) || "[]");
|
||
this.updateWalletTableContent(rows);
|
||
}
|
||
updateWalletTableContent(rows) {
|
||
const tbody = this.shadowRoot?.querySelector("#wallet-table tbody");
|
||
if (!tbody)
|
||
return;
|
||
tbody.innerHTML = rows.map((row) => `
|
||
<tr>
|
||
<td>${row.column1}</td>
|
||
<td>${row.column2}</td>
|
||
<td class="device-name" onclick="editDeviceName(this)">${row.column3}</td>
|
||
</tr>
|
||
`).join("");
|
||
}
|
||
showData() {
|
||
currentMode = "data";
|
||
this.hideAllContent();
|
||
const headerTitle = this.shadowRoot?.getElementById("header-title");
|
||
if (headerTitle)
|
||
headerTitle.textContent = "Data";
|
||
const dataContent = this.shadowRoot?.getElementById("data-content");
|
||
if (dataContent) {
|
||
dataContent.style.display = "block";
|
||
dataContent.innerHTML = `
|
||
<div class="parameter-header" id="parameter-header">Data</div>
|
||
<div class="table-container">
|
||
<table class="parameter-table" id="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Name</th>
|
||
<th>Visibility</th>
|
||
<th>Role</th>
|
||
<th>Duration</th>
|
||
<th>Legal</th>
|
||
<th>Contract</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
`;
|
||
const rows = mockDataRows || JSON.parse(localStorage.getItem(STORAGE_KEYS.data) || "[]");
|
||
this.updateDataTableContent(rows);
|
||
}
|
||
}
|
||
// Fonctions de gestion du wallet
|
||
addWalletRow() {
|
||
if (isAddingRow)
|
||
return;
|
||
isAddingRow = true;
|
||
const table = this.shadowRoot?.getElementById("wallet-table")?.getElementsByTagName("tbody")[0];
|
||
if (!table)
|
||
return;
|
||
currentRow = table.insertRow();
|
||
const placeholders = ["Label", "Wallet", "Type"];
|
||
placeholders.forEach((placeholder) => {
|
||
const cell = currentRow.insertCell();
|
||
const input = document.createElement("input");
|
||
input.type = "text";
|
||
input.placeholder = placeholder;
|
||
input.className = "edit-input";
|
||
cell.appendChild(input);
|
||
});
|
||
const buttonContainer = this.shadowRoot?.querySelector("#wallet-content .button-container");
|
||
if (!buttonContainer)
|
||
return;
|
||
buttonContainer.innerHTML = `
|
||
<div class="action-buttons-wrapper">
|
||
<button onclick="confirmWalletRow()" class="action-button confirm-button">✓</button>
|
||
<button onclick="cancelWalletRow()" class="action-button cancel-button">✗</button>
|
||
</div>
|
||
`;
|
||
this.updateActionButtons();
|
||
}
|
||
confirmWalletRow() {
|
||
if (!currentRow)
|
||
return;
|
||
const inputs = Array.from(currentRow.getElementsByTagName("input"));
|
||
const allFieldsFilled = inputs.every((input) => input.value.trim() !== "");
|
||
if (allFieldsFilled) {
|
||
const newRow = {
|
||
column1: inputs[0].value.trim(),
|
||
column2: inputs[1].value.trim(),
|
||
column3: inputs[2].value.trim()
|
||
};
|
||
const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.wallet) || "[]");
|
||
rows.push(newRow);
|
||
localStorage.setItem(STORAGE_KEYS.wallet, JSON.stringify(rows));
|
||
isAddingRow = false;
|
||
currentRow = null;
|
||
this.showWallet();
|
||
} else {
|
||
this.showAlert("Please complete all fields before confirming.");
|
||
}
|
||
}
|
||
cancelWalletRow() {
|
||
if (!currentRow)
|
||
return;
|
||
currentRow.remove();
|
||
isAddingRow = false;
|
||
currentRow = null;
|
||
const buttonContainer = this.shadowRoot?.querySelector("#wallet-content .button-container");
|
||
if (!buttonContainer)
|
||
return;
|
||
buttonContainer.innerHTML = `
|
||
<button class="add-row-button button-style" onclick="window.addWalletRow()">Add a line</button>
|
||
`;
|
||
}
|
||
updateDataTableContent(rows) {
|
||
const tbody = this.shadowRoot?.querySelector("#data-table tbody");
|
||
if (!tbody)
|
||
return;
|
||
tbody.innerHTML = rows.map((row) => `
|
||
<tr>
|
||
<td>${row.column1}</td>
|
||
<td>${row.column2}</td>
|
||
<td>${row.column3}</td>
|
||
<td>${row.column4}</td>
|
||
<td>${row.column5}</td>
|
||
<td>
|
||
<a href="javascript:void(0)" onclick="window.showContractPopup('${row.column6}'); return false;">${row.column6}</a>
|
||
</td>
|
||
</tr>
|
||
`).join("");
|
||
}
|
||
// Fonctions de gestion de l'avatar et de la bannière
|
||
openAvatarPopup() {
|
||
const popup = this.shadowRoot?.getElementById("avatar-popup");
|
||
if (!popup)
|
||
return;
|
||
const savedName = localStorage.getItem("userName");
|
||
const savedLastName = localStorage.getItem("userLastName");
|
||
const savedAvatar = localStorage.getItem("userAvatar") || "https://via.placeholder.com/150";
|
||
const savedBanner = localStorage.getItem("userBanner") || "https://via.placeholder.com/800x200";
|
||
const savedAddress = localStorage.getItem("userAddress") || "🏠 🌍 🗽🎊😩-🎊😑😩";
|
||
popup.innerHTML = `
|
||
<div class="popup-content">
|
||
<h1>Profile</h1>
|
||
<span class="close-popup">×</span>
|
||
|
||
<div class="banner-container">
|
||
<div class="banner-wrapper">
|
||
<img src="${savedBanner}" alt="Banner" id="popup-banner-img" class="banner-image clickable">
|
||
<input type="file" id="banner-upload" accept="image/*" style="display: none;">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="popup-avatar">
|
||
<label for="avatar-upload" class="avatar-upload-label">
|
||
<img src="${savedAvatar}" alt="Avatar" id="popup-avatar-img">
|
||
<input type="file" id="avatar-upload" accept="image/*" style="display: none;">
|
||
<div class="avatar-overlay">
|
||
<span>Change Avatar</span>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
|
||
<div class="popup-info">
|
||
<div class="info-row">
|
||
<strong>Name:</strong>
|
||
<input type="text" id="userName" value="${savedName}" class="editable">
|
||
</div>
|
||
<!--<div class="info-row">
|
||
<strong>Last Name:</strong>
|
||
<input type="text" id="userLastName" value="${savedLastName}" class="editable">
|
||
</div>-->
|
||
<div class="info-row">
|
||
<strong>Address:</strong>
|
||
<span>${savedAddress}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="popup-button-container">
|
||
<div class="action-buttons-row">
|
||
<button class="export-btn" onclick="window.exportUserData()">Export User Data</button>
|
||
<button class="export-btn recovery-btn" onclick="window.exportRecovery()">Export Recovery</button>
|
||
<button class="delete-account-btn" onclick="window.confirmDeleteAccount()">Delete Account</button>
|
||
</div>
|
||
<button class="logout-btn" onclick="window.handleLogout()">Log out</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
popup.style.display = "block";
|
||
this.setupEventListeners(popup);
|
||
const bannerImg = popup.querySelector("#popup-banner-img");
|
||
const bannerInput = popup.querySelector("#banner-upload");
|
||
if (bannerImg && bannerInput) {
|
||
bannerImg.addEventListener("click", () => {
|
||
bannerInput.click();
|
||
});
|
||
}
|
||
const recoveryExported = localStorage.getItem("recoveryExported") === "true";
|
||
if (recoveryExported) {
|
||
const exportRecoveryBtn = popup.querySelector(".recovery-btn");
|
||
if (exportRecoveryBtn) {
|
||
exportRecoveryBtn.disabled = true;
|
||
exportRecoveryBtn.style.opacity = "0.5";
|
||
exportRecoveryBtn.style.cursor = "not-allowed";
|
||
}
|
||
}
|
||
}
|
||
setupEventListeners(popup) {
|
||
const closeBtn = popup.querySelector(".close-popup");
|
||
if (closeBtn) {
|
||
closeBtn.addEventListener("click", () => {
|
||
popup.style.display = "none";
|
||
});
|
||
}
|
||
const avatarUpload = popup.querySelector("#avatar-upload");
|
||
if (avatarUpload) {
|
||
avatarUpload.addEventListener("change", (e) => {
|
||
const file = e.target.files?.[0];
|
||
if (file) {
|
||
const reader = new FileReader();
|
||
reader.onload = (e2) => {
|
||
const result = e2.target?.result;
|
||
const popupAvatar = this.shadowRoot?.getElementById("popup-avatar-img");
|
||
const previewAvatar = this.shadowRoot?.querySelector(".preview-avatar");
|
||
if (popupAvatar)
|
||
popupAvatar.src = result;
|
||
if (previewAvatar)
|
||
previewAvatar.src = result;
|
||
localStorage.setItem("userAvatar", result);
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
});
|
||
}
|
||
const bannerUpload = popup.querySelector("#banner-upload");
|
||
if (bannerUpload) {
|
||
bannerUpload.addEventListener("change", (e) => {
|
||
const file = e.target.files?.[0];
|
||
if (file) {
|
||
const reader = new FileReader();
|
||
reader.onload = (e2) => {
|
||
const result = e2.target?.result;
|
||
const popupBanner = this.shadowRoot?.getElementById("popup-banner-img");
|
||
const previewBanner = this.shadowRoot?.querySelector(".preview-banner-img");
|
||
if (popupBanner)
|
||
popupBanner.src = result;
|
||
if (previewBanner)
|
||
previewBanner.src = result;
|
||
localStorage.setItem("userBanner", result);
|
||
};
|
||
reader.readAsDataURL(file);
|
||
}
|
||
});
|
||
}
|
||
const nameInput = popup.querySelector("#userName");
|
||
const lastNameInput = popup.querySelector("#userLastName");
|
||
if (nameInput) {
|
||
nameInput.addEventListener("input", () => {
|
||
const newName = nameInput.value;
|
||
localStorage.setItem("userName", newName);
|
||
const previewName = this.shadowRoot?.querySelector(".preview-name");
|
||
if (previewName)
|
||
previewName.textContent = newName;
|
||
});
|
||
}
|
||
if (lastNameInput) {
|
||
lastNameInput.addEventListener("input", () => {
|
||
const newLastName = lastNameInput.value;
|
||
localStorage.setItem("userLastName", newLastName);
|
||
const previewLastName = this.shadowRoot?.querySelector(".preview-lastname");
|
||
if (previewLastName)
|
||
previewLastName.textContent = newLastName;
|
||
});
|
||
}
|
||
}
|
||
closeAvatarPopup() {
|
||
const popup = this.shadowRoot?.querySelector(".avatar-popup");
|
||
if (popup)
|
||
popup.remove();
|
||
}
|
||
loadAvatar() {
|
||
const savedAvatar = localStorage.getItem("userAvatar");
|
||
if (savedAvatar) {
|
||
const avatarImg = this.shadowRoot?.querySelector(".avatar");
|
||
if (avatarImg) {
|
||
avatarImg.src = savedAvatar;
|
||
}
|
||
}
|
||
}
|
||
loadUserInfo() {
|
||
const savedName = localStorage.getItem("userName");
|
||
const savedLastName = localStorage.getItem("userLastName");
|
||
const savedAvatar = localStorage.getItem("userAvatar");
|
||
const savedBanner = localStorage.getItem("userBanner");
|
||
if (savedName) {
|
||
const previewName = this.shadowRoot?.querySelector(".preview-name");
|
||
if (previewName) {
|
||
previewName.textContent = savedName;
|
||
}
|
||
}
|
||
if (savedLastName) {
|
||
const previewLastName = this.shadowRoot?.querySelector(".preview-lastname");
|
||
if (previewLastName) {
|
||
previewLastName.textContent = savedLastName;
|
||
}
|
||
}
|
||
if (savedAvatar) {
|
||
const previewAvatar = this.shadowRoot?.querySelector(".preview-avatar");
|
||
if (previewAvatar) {
|
||
previewAvatar.src = savedAvatar;
|
||
}
|
||
}
|
||
if (savedBanner) {
|
||
const previewBanner = this.shadowRoot?.querySelector(".preview-banner-img");
|
||
if (previewBanner) {
|
||
previewBanner.src = savedBanner;
|
||
}
|
||
}
|
||
}
|
||
updateNavbarName(name) {
|
||
const nameElement = this.shadowRoot?.querySelector(".nav-wrapper .user-name");
|
||
if (nameElement) {
|
||
nameElement.textContent = name;
|
||
}
|
||
}
|
||
updateNavbarLastName(lastName) {
|
||
const lastNameElement = this.shadowRoot?.querySelector(".nav-wrapper .user-lastname");
|
||
if (lastNameElement) {
|
||
lastNameElement.textContent = lastName;
|
||
}
|
||
}
|
||
updateProfilePreview(data) {
|
||
if (data.avatar) {
|
||
const previewAvatar = this.shadowRoot?.querySelector(".preview-avatar");
|
||
if (previewAvatar)
|
||
previewAvatar.src = data.avatar;
|
||
}
|
||
if (data.banner) {
|
||
const previewBanner = this.shadowRoot?.querySelector(".preview-banner-img");
|
||
if (previewBanner)
|
||
previewBanner.src = data.banner;
|
||
}
|
||
if (data.name) {
|
||
const previewName = this.shadowRoot?.querySelector(".preview-name");
|
||
if (previewName)
|
||
previewName.textContent = data.name;
|
||
}
|
||
if (data.lastName) {
|
||
const previewLastName = this.shadowRoot?.querySelector(".preview-lastname");
|
||
if (previewLastName)
|
||
previewLastName.textContent = data.lastName;
|
||
}
|
||
}
|
||
initializeEventListeners() {
|
||
this.shadowRoot?.addEventListener("DOMContentLoaded", () => {
|
||
this.showPairing();
|
||
});
|
||
const editableFields = this.shadowRoot?.querySelectorAll(".editable");
|
||
if (editableFields) {
|
||
editableFields.forEach((field) => {
|
||
field.addEventListener("click", () => {
|
||
if (!field.classList.contains("editing")) {
|
||
const currentValue = field.textContent || "";
|
||
const input = document.createElement("input");
|
||
input.type = "text";
|
||
input.value = currentValue;
|
||
input.className = "edit-input";
|
||
field.textContent = "";
|
||
field.appendChild(input);
|
||
field.classList.add("editing");
|
||
input.focus();
|
||
}
|
||
});
|
||
});
|
||
}
|
||
const avatarInput = this.shadowRoot?.getElementById("avatar-upload");
|
||
if (avatarInput) {
|
||
avatarInput.addEventListener("change", this.handleAvatarUpload.bind(this));
|
||
}
|
||
}
|
||
showQRCodeModal(pairingId) {
|
||
const modal = document.createElement("div");
|
||
modal.className = "qr-modal";
|
||
modal.innerHTML = `
|
||
<div class="qr-modal-content">
|
||
<span class="close-qr-modal">×</span>
|
||
<img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${pairingId}"
|
||
alt="QR Code Large"
|
||
class="qr-code-large">
|
||
<div class="qr-address">${decodeURIComponent(pairingId)}</div>
|
||
</div>
|
||
`;
|
||
this.shadowRoot?.appendChild(modal);
|
||
const closeBtn = modal.querySelector(".close-qr-modal");
|
||
closeBtn?.addEventListener("click", () => modal.remove());
|
||
modal.addEventListener("click", (e) => {
|
||
if (e.target === modal)
|
||
modal.remove();
|
||
});
|
||
}
|
||
}
|
||
customElements.define("account-element", AccountElement);
|
||
|
||
const accountCss = "/* Styles de base */\n:root {\n --primary-color: #3A506B;\n /* Bleu métallique */\n --secondary-color: #B0BEC5;\n /* Gris acier */\n --accent-color: #D68C45;\n /* Cuivre */\n}\n\nbody {\n font-family: Arial, sans-serif;\n margin: 0;\n padding: 0;\n display: flex;\n height: 100vh;\n background-color: #e9edf1;\n flex-direction: column;\n}\n\n/*-------------------------------------- Avatar--------------------------------------*/\n\n.avatar-section {\n position: relative;\n height: 60px;\n width: 260px;\n margin-left: 10px;\n overflow: hidden;\n border-radius: 10px;\n}\n\n.user-info {\n display: flex;\n flex-direction: column;\n color: white;\n}\n\n.user-name, .user-lastname {\n font-size: 0.9rem;\n font-weight: 700;\n}\n\n\n.user-name:hover, .user-lastname:hover {\n color: var(--accent-color);\n cursor: pointer;\n}\n\n.avatar-container {\n width: 45px;\n height: 45px;\n flex-shrink: 0;\n}\n\n.avatar-container {\n width: 80px; /* Taille réduite */\n height: 80px;\n margin: 0 auto;\n}\n\n.avatar {\n height: 100%;\n border-radius: 50%;\n object-fit: cover;\n border: 2px solid white;\n}\n\n.avatar img {\n width: 100%;\n height: 100%;\n border-radius: 50%;\n}\n\n\n/*-------------------------------------- BANNER--------------------------------------*/\n\n/* Styles pour la bannière avec image */\n.banner-image-container {\n position: relative;\n width: 100%;\n height: 200px;\n overflow: hidden;\n border-radius: 10px;\n margin-bottom: 15px;\n}\n\n.banner-image {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n z-index: 1;\n}\n\n.banner-content {\n position: relative;\n z-index: 2;\n display: flex;\n align-items: center;\n gap: 15px;\n height: 100%;\n padding: 0 15px;\n background: rgba(0, 0, 0, 0.3);\n overflow: visible;\n}\n\n.banner-content .avatar-container {\n width: 45px;\n height: 45px;\n overflow: hidden; \n border-radius: 50%;\n border: 2px solid white;\n transition: transform 0.3s ease; \n}\n\n.banner-content .avatar {\n width: 100%;\n height: 100%;\n object-fit: cover;\n border: none; \n}\n\n.banner-content .avatar-container:hover {\n transform: scale(1.15);\n cursor: pointer;\n}\n\n/* Style pour le bouton de changement de bannière */\n.banner-upload-label {\n display: block;\n width: auto;\n padding: 12px 20px;\n background-color: var(--accent-color);\n color: white;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.3s;\n text-align: center;\n font-size: 16px;\n margin: 20px auto;\n max-width: 250px;\n}\n\n.banner-upload-label:hover {\n background-color: #b06935;\n}\n\n.banner-controls {\n margin-top: 15px;\n display: flex;\n justify-content: center;\n width: 100%;\n}\n\n.banner-preview {\n margin: 10px 0;\n}\n\n.banner-preview h3 {\n margin: 0 0 10px 0;\n font-size: 18px;\n}\n\n.banner-image-container {\n height: 150px;\n margin-bottom: 10px;\n}\n\n\n.nav-wrapper {\n position: fixed;\n background: white;\n display: flex;\n justify-content: space-between;\n align-items: center;\n height: 9vh;\n width: 100vw;\n left: 0;\n top: 0;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n z-index: 1000;\n}\n/* Mise à jour des styles de la navbar pour inclure l'image de bannière */\n.nav-wrapper .avatar-section {\n position: relative;\n background: none;\n}\n\n.nav-wrapper .banner-image {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n object-fit: cover;\n z-index: -1;\n filter: brightness(0.7);\n}\n\n\n\n/*-------------------------------------- Popup--------------------------------------*/\n/* Styles pour la popup */\n.popup {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.5);\n z-index: 1000;\n}\n\n.popup-content {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: white;\n padding: 20px;\n border-radius: var(--border-radius);\n box-shadow: var(--box-shadow);\n width: 400px; \n max-height: 80vh; \n overflow-y: auto; \n}\n.popup-content h2 {\n margin: 0 0 15px 0;\n font-size: 24px;\n}\n\n.close-popup {\n position: absolute;\n right: 15px;\n top: 10px;\n font-size: 24px;\n cursor: pointer;\n color: #666;\n}\n\n.close-popup:hover {\n color: #000;\n}\n\n.popup-avatar {\n text-align: center;\n margin: 20px 0;\n position: relative;\n}\n\n.avatar-upload-label {\n position: relative;\n display: inline-block;\n cursor: pointer;\n width: 0%;\n margin-left: -20%;\n}\n\n.avatar-overlay {\n position: absolute;\n top: 0;\n left: 50px;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n border-radius: 50%;\n display: flex;\n justify-content: center;\n align-items: center;\n opacity: 0;\n transition: opacity 0.3s;\n}\n\n.avatar-overlay span {\n color: white;\n font-size: 14px;\n text-align: center;\n}\n\n.avatar-upload-label:hover .avatar-overlay {\n opacity: 1;\n}\n\n.popup-avatar img {\n width: 100px;\n height: 100px;\n border-radius: 50%;\n border: 3px solid var(--accent-color);\n object-fit: cover;\n}\n\n.popup-info {\n margin: 15px 0;\n}\n\n.info-row {\n margin: 8px 0;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.popup-info strong {\n min-width: 100px; /* Largeur fixe pour l'alignement */\n}\n\n/* Editable Name and Lastname */\n.editable {\n cursor: pointer;\n display: inline-block;\n min-width: 100px;\n padding: 2px 5px;\n transition: background-color 0.3s;\n}\n\n.editable:hover {\n background-color: #f0f0f0;\n}\n\n.editable.editing {\n background-color: white;\n border: 1px solid var(--accent-color);\n outline: none;\n}\n\n.edit-input {\n border: 1px solid var(--accent-color);\n border-radius: 3px;\n padding: 2px 5px;\n font-size: inherit;\n font-family: inherit;\n outline: none;\n width: 100%; \n min-width: 100px;\n margin: 0; \n box-sizing: border-box; \n}\n\n/* Boutons */\n\n\n.popup-button-container {\n display: flex\n;\n flex-direction: column;\n margin-top: 20px;\n gap: 15px;\n}\n\n.action-buttons-row {\n display: flex\n;\n justify-content: space-between;\n gap: 15px;\n}\n.banner-upload-label,\n.export-btn,\n.delete-account-btn {\n padding: 8px 15px;\n margin: 10px 0;\n font-size: 14px;\n}\n\n.delete-account-btn {\n background-color: #dc3545;\n}\n\n\n.export-btn,\n.delete-account-btn {\n flex: 1; /* Pour qu'ils prennent la même largeur */\n padding: 12px 20px;\n border: none;\n border-radius: 8px;\n font-size: 16px;\n cursor: pointer;\n transition: background-color 0.3s;\n color: white;\n text-align: center;\n}\n\n\n\n/* Séparateurs */\n.popup-info,\n.export-section,\n.delete-account-section {\n padding-top: 10px;\n margin-top: 10px;\n border-top: 1px solid #eee;\n}\n\n.logout-btn {\n background-color: rgb(108, 117, 125);\n font-size: 16px;\n cursor: pointer;\n color: white;\n text-align: center;\n flex: 1 1 0%;\n padding: 12px 20px;\n border-width: initial;\n border-style: none;\n border-color: initial;\n border-image: initial;\n border-radius: 8px;\n transition: background-color 0.3s;\n}\n\n/*-------------------------------------- Delete Account--------------------------------------*/\n.delete-account-section {\n margin-top: 30px;\n padding-top: 20px;\n border-top: 1px solid #ddd;\n text-align: center;\n}\n\n.delete-account-btn {\n background-color: #dc3545;\n}\n\n.delete-account-btn:hover {\n background-color: #c82333;\n}\n\n/* Style pour la modal de confirmation */\n.confirm-delete-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n justify-content: center;\n align-items: center;\n z-index: 1000;\n}\n\n.confirm-delete-content {\n background-color: white;\n padding: 20px;\n border-radius: 8px;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n max-width: 400px;\n width: 90%;\n text-align: center;\n}\n\n.confirm-delete-content h3 {\n margin-top: 0;\n color: #333;\n}\n\n.confirm-delete-content p {\n margin: 15px 0;\n color: #666;\n}\n\n.confirm-delete-buttons {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 20px;\n}\n\n.confirm-delete-buttons button {\n padding: 8px 20px;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n transition: background-color 0.3s;\n}\n\n.confirm-delete-buttons .confirm-btn {\n background-color: #dc3545;\n color: white;\n}\n\n.confirm-delete-buttons .confirm-btn:hover {\n background-color: #c82333;\n}\n\n.confirm-delete-buttons .cancel-btn {\n background-color: #6c757d;\n color: white;\n}\n\n.confirm-delete-buttons .cancel-btn:hover {\n background-color: #5a6268;\n}\n\n/*-------------------------------------- Export--------------------------------------*/\n.export-section {\n margin: 20px 0;\n text-align: center;\n padding: 15px 0;\n border-top: 1px solid #ddd;\n}\n\n.export-btn {\n background-color: var(--accent-color);\n}\n\n.export-btn:hover {\n background-color: #b06935;\n}\n\n.export-section,\n.delete-account-section {\n width: 100%;\n display: flex;\n justify-content: center;\n margin: 15px 0;\n}\n\n.export-btn,\n.delete-account-btn {\n width: 80%;\n padding: 12px 20px;\n border: none;\n border-radius: 8px;\n font-size: 16px;\n cursor: pointer;\n transition: background-color 0.3s;\n color: white;\n text-align: center;\n}\n\n/*-------------------------------------- NAVBAR--------------------------------------*/\n\n.brand-logo {\n font-size: 1.5rem;\n font-weight: bold;\n}\n\n.nav-wrapper {\n position: fixed;\n background: radial-gradient(circle, white, var(--primary-color));\n display: flex;\n justify-content: space-between;\n align-items: center;\n color: #37474F;\n height: 9vh;\n width: 100vw;\n left: 0;\n top: 0;\n box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, .2), 0px 16px 24px 2px rgba(0, 0, 0, .14), 0px 6px 30px 5px rgba(0, 0, 0, .12);\n}\n\n/* Icônes de la barre de navigation */\n.nav-right-icons {\n margin-right: 20px;\n}\n\n.burger-menu {\n height: 20px;\n width: 20px;\n margin-right: 1rem;\n cursor: pointer;\n}\n\n/* Par défaut, le menu est masqué */\n#menu {\n display: none;\n /* Menu caché par défaut */\n transition: display 0.3s ease-in-out;\n}\n\n\n.burger-menu {\n width: 24px;\n height: 24px;\n cursor: pointer;\n}\n\n.burger-menu-icon {\n width: 100%;\n height: 100%;\n}\n/* Icône burger */\n#burger-icon {\n cursor: pointer;\n}\n\n/* .menu-content {\n display: none;\n position: absolute;\n top: 3.4rem;\n right: 1rem;\n background-color: white;\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\n border-radius: 5px;\n overflow: hidden;\n}\n\n.menu-content a {\n display: block;\n padding: 10px 20px;\n text-decoration: none;\n color: #333;\n border-bottom: 1px solid #e0e0e0;\n\n &:hover {\n background-color: rgba(26, 28, 24, .08);\n }\n}\n\n.menu-content a:last-child {\n border-bottom: none;\n} */\n\n/* Ajustement pour la barre de navigation fixe */\n.container {\n display: flex;\n flex: 1;\n height: 90vh;\n margin-top: 9vh;\n margin-left: -1%;\n text-align: left;\n width: 100vw;\n}\n\n/* Liste des information sur l'account */\n\n.parameter-list {\n width: 24.5%;\n background-color: #1f2c3d;\n color: white;\n padding: 20px;\n box-sizing: border-box;\n overflow-y: auto;\n border-right: 2px solid #2c3e50;\n flex-shrink: 0; \n padding-right: 10px;\n height: 91vh;\n}\n\n.parameter-list ul {\n padding: 15px;\n margin-left: 10px;\n border-radius: 8px;\n background-color: #273646;\n cursor: pointer;\n transition: background-color 0.3s, box-shadow 0.3s;\n}\n\n\n.parameter-list ul:hover {\n background-color: #34495e;\n box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n}\n\n\n/* Zone des info des parametre */\n\n.parameter-area {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0; \n background-color: #ffffff;\n border-radius: 10px;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);\n margin: 0px;\n margin-top: 20px;\n margin-left: 1%;\n margin-bottom: -7px;\n}\n\n/* En-tête du parametre */\n.parameter-header {\n background-color: #34495e;\n color: white;\n padding: 15px;\n font-size: 20px;\n font-weight: bold;\n border-radius: 10px 10px 0 0;\n text-align: center;\n}\n\n/* Style du tableau dans parameter-area */\n.parameter-table {\n width: 100%;\n border-collapse: collapse;\n margin: 15px 0;\n table-layout: fixed; \n}\n\n.parameter-table th, .parameter-table td {\n border: 1px solid #ddd;\n padding: 8px;\n white-space: nowrap;\n overflow: hidden;\n text-align: center;\n}\n\n.parameter-table th {\n background-color: var(--secondary-color);\n color: white;\n font-weight: bold;\n}\n\n.parameter-table tr:nth-child(even) {\n background-color: #f2f2f2;\n}\n\n\n\n.parameter-table tr:hover {\n background-color: #ddd;\n}\n\n/* Conteneur pour les boutons */\n.button-container {\n display: flex;\n justify-content: center;\n gap: 15px;\n margin: 15px 0;\n}\n\n/* Boutons \"Ajouter une ligne\" et \"Confirmer\" */\n.add-row-button, .confirm-all-button, .delete-row-button {\n background-color: var(--accent-color);\n color: white;\n border: none;\n padding: 8px 15px;\n cursor: pointer;\n border-radius: 5px;\n font-size: 0.9em;\n margin-right: 5px;\n}\n\n.add-row-button:hover, .confirm-all-button:hover, .delete-row-button:hover {\n background-color: #b06935;\n}\n\n\n.button-style {\n background-color: var(--accent-color);\n color: white;\n border: none;\n border-radius: 5px;\n padding: 10px 20px;\n cursor: pointer;\n transition: background-color 0.3s;\n}\n\n.button-style:hover {\n background-color: darkorange;\n}\n\n.content-container {\n width: 100%;\n}\n\n#pairing-content,\n#wallet-content {\n width: 100%;\n}\n\n.editable-cell {\n cursor: pointer;\n}\n\n.editable-cell:hover {\n background-color: #f5f5f5;\n}\n\n.edit-input {\n width: 100%;\n padding: 5px;\n box-sizing: border-box;\n border: 1px solid #ddd;\n border-radius: 4px;\n}\n\n/*-------------------------------------- Notification--------------------------------------*/\n.notification-cell {\n position: relative;\n}\n\n.notification-bell {\n position: relative;\n display: inline-block;\n}\n\n.notification-badge {\n position: absolute;\n top: -8px;\n right: -8px;\n background-color: red;\n color: white;\n border-radius: 50%;\n padding: 2px 6px;\n font-size: 12px;\n min-width: 15px;\n text-align: center;\n}\n\n.fa-bell {\n color: #666;\n font-size: 20px;\n}\n\n\n/* Media Queries pour Mobile */\n@media screen and (max-width: 768px) {\n /* Navbar */\n .nav-wrapper {\n height: 9vh;\n padding: 0;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n\n /* Section avatar (gauche) */\n .avatar-section {\n width: 200px; /* Largeur réduite */\n margin-left: 10px;\n order: 0; /* Garde à gauche */\n }\n\n .avatar-container {\n width: 35px;\n height: 35px;\n }\n\n .user-info span {\n font-size: 0.8rem;\n }\n\n /* Logo (centre) */\n .brand-logo {\n order: 0; \n flex: 0 0 auto;\n padding: 0;\n font-size: 1.2rem;\n }\n\n /* Menu burger (droite) */\n .nav-right-icons {\n order: 0; \n width: auto;\n padding-right: 15px;\n }\n\n .burger-menu {\n width: 24px;\n height: 24px;\n }\n\n /* Ajustements pour la bannière */\n .banner-image-container {\n height: 100%;\n }\n\n .banner-content {\n padding: 0 10px;\n }\n\n /* Style des boutons dans la popup */\n .button-container {\n display: flex;\n gap: 10px;\n margin: 15px 0;\n width: 100%;\n }\n\n .export-btn,\n .delete-account-btn {\n flex: 1;\n padding: 12px 15px;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n cursor: pointer;\n transition: background-color 0.3s;\n color: white;\n }\n\n .export-btn {\n background-color: var(--accent-color);\n }\n\n .delete-account-btn {\n background-color: var(--danger-color);\n }\n}\n\n/* Media Queries pour très petits écrans */\n@media screen and (max-width: 380px) {\n .avatar-section {\n width: 150px; \n }\n\n .user-info span {\n font-size: 0.7rem;\n }\n}\n\n/* Style des boutons */\n.button-container {\n display: flex;\n gap: 15px;\n margin: 15px 0;\n width: 100%;\n}\n\n.export-btn,\n.delete-account-btn {\n flex: 1;\n padding: 12px 20px;\n border: none;\n border-radius: 8px;\n color: white;\n cursor: pointer;\n transition: background-color 0.3s;\n font-size: 14px;\n display: block;\n}\n\n.export-btn {\n background-color: var(--accent-color);\n}\n\n.delete-account-btn {\n background-color: rgb(219, 17, 17);\n display: block;\n visibility: visible;\n}\n\n.export-btn:hover {\n background-color: #b06935;\n}\n\n.delete-account-btn:hover {\n background-color: #b60718;\n}\n\n@media screen and (max-width: 768px) {\n .button-container {\n gap: 10px;\n }\n\n .export-btn,\n .delete-account-btn {\n padding: 12px 15px;\n font-size: 14px;\n }\n}\n\n/* Style pour les boutons de la popup */\n.popup-buttons {\n display: flex;\n gap: 15px;\n margin: 15px 0;\n width: 100%;\n}\n\n/* Style pour les boutons d'action des tableaux */\n.button-container {\n display: flex;\n gap: 15px;\n margin: 15px 0;\n width: 100%;\n}\n\n/* Style pour le header mobile */\n.mobile-nav {\n display: none;\n width: 100%;\n padding: 10px;\n background-color: #34495e;\n overflow-x: auto;\n white-space: nowrap;\n}\n\n.mobile-nav ul {\n display: flex;\n gap: 10px;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n/* Media Query pour mobile */\n@media screen and (max-width: 768px) {\n .parameter-list {\n display: flex;\n width: 100%;\n min-width: 100%;\n height: auto;\n overflow-x: auto;\n background-color: rgb(31, 44, 61);\n padding: 10px;\n border-right: none;\n border-bottom: 2px solid rgb(44, 62, 80);\n }\n\n .mobile-nav {\n display: flex; /* Affiche la navigation mobile */\n }\n\n .parameter-header {\n display: flex;\n flex-direction: column;\n }\n\n .parameter-list-ul {\n text-align: center;\n flex: 1 1 0%;\n margin: 0px 5px;\n padding: 10px;\n white-space: nowrap;\n margin-bottom: none;\n }\n\n .parameter-list-ul:hover {\n background-color: #34495e;\n }\n\n .container {\n flex-direction: column;\n }\n\n .parameter-area {\n margin: -5px;\n }\n}\n\n/* Style pour le header et la navigation mobile */\n.parameter-header {\n background-color: #34495e;\n padding: 20px 0;\n margin: 0;\n width: 100%;\n}\n\n.mobile-nav {\n display: none; /* Par défaut caché */\n width: 100%;\n padding: 10px;\n background-color: #34495e;\n overflow-x: auto;\n}\n\n.mobile-nav ul {\n display: flex;\n gap: 10px;\n margin: 0;\n padding: 10px;\n list-style: none;\n}\n\n.mobile-nav li {\n flex: 0 0 auto;\n white-space: nowrap;\n}\n\n/* Ajoutez ces styles pour la bannière dans la popup */\n.banner-container {\n width: 100%;\n margin-bottom: 20px;\n}\n\n.banner-wrapper {\n width: 100%;\n height: 120px;\n overflow: hidden;\n position: relative;\n border-radius: 10px;\n}\n\n.banner-wrapper img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n position: absolute;\n top: 0;\n left: 0;\n}\n\n/* Mise à jour des styles existants */\n.popup-content {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: white;\n padding: 20px;\n border-radius: 10px;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n width: 90%;\n max-width: 500px;\n max-height: 80vh;\n overflow-y: auto;\n}\n\n/* Style pour le conteneur de la bannière */\n.banner-upload-label {\n display: block;\n width: auto;\n padding: 12px 20px;\n background-color: var(--accent-color);\n color: white;\n border-radius: 8px;\n cursor: pointer;\n transition: background-color 0.3s;\n text-align: center;\n font-size: 16px;\n margin: 10px auto;\n max-width: 200px;\n}\n\n/* ---------------------Style pour la popup de contrat--------------------- */\n\n.contract-popup-overlay {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n display: flex\n;\n justify-content: center;\n align-items: center;\n z-index: 1000;\n}\n\n.contract-popup-content {\n background: white;\n padding: 30px;\n border-radius: 8px;\n max-width: 600px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n position: relative;\n}\n\n.close-contract-popup {\n position: absolute;\n top: 15px;\n right: 15px;\n background: none;\n border: none;\n font-size: 24px;\n cursor: pointer;\n color: #666;\n}\n\n/* Style pour la popup d'alerte */\n.alert-popup {\n position: fixed;\n top: 20px;\n left: 50%;\n transform: translateX(-50%);\n background-color: #f44336;\n color: white;\n padding: 15px 25px;\n border-radius: 4px;\n box-shadow: 0 2px 5px rgba(0,0,0,0.2);\n z-index: 1000;\n display: none;\n animation: slideDown 0.3s ease-out;\n}\n\n/* ---------------------Style pour la popup notification--------------------- */\n\n\n.notifications-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n justify-content: center;\n align-items: center;\n z-index: 1000;\n}\n\n.notifications-content {\n position: relative;\n background: white;\n border-radius: 8px;\n padding: 24px;\n width: 90%;\n max-width: 500px;\n}\n\n.close-button {\n position: absolute;\n top: 15px;\n right: 20px;\n font-size: 24px;\n color: #666;\n cursor: pointer;\n transition: color 0.2s;\n}\n\n.close-button:hover {\n color: #000;\n}\n\n.notifications-title {\n padding-right: 30px; /* Pour éviter que le titre ne chevauche le bouton de fermeture */\n}\n\n.notifications-title {\n font-size: 24px;\n color: #445B6E;\n margin-bottom: 20px;\n font-weight: 500;\n}\n\n.notifications-list {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n\n.notification-item {\n display: flex;\n align-items: flex-start;\n padding: 12px 0;\n border-bottom: 1px solid #eee;\n cursor: pointer;\n}\n\n.notification-status {\n margin-right: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 24px; \n height: 24px; \n}\n\n.dot-icon, .check-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n}\n\n.notification-content {\n flex: 1;\n}\n\n.notification-message {\n font-size: 16px;\n color: #333;\n margin-bottom: 4px;\n}\n\n.notification-date {\n font-size: 14px;\n color: #666;\n}\n\n.notification-item:hover {\n background-color: #f8f9fa;\n}\n\n.notification-item.read {\n opacity: 0.7;\n}\n\n.notification-item.unread {\n background-color: #fff;\n}\n\n.close-notifications {\n position: absolute;\n top: 15px;\n right: 15px;\n border: none;\n background: none;\n font-size: 24px;\n color: #666;\n cursor: pointer;\n}\n\n/*-------------------------------------- STYLE ACTION BUTTON ---------------------*/\n\n.action-buttons-wrapper {\n display: flex;\n flex-direction: row;\n gap: 10px;\n justify-content: center;\n margin: 20px 0;\n}\n\n.action-button {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n border-radius: 8px;\n border: none;\n font-size: 16px;\n cursor: pointer;\n transition: all 0.3s ease;\n color: white;\n height: 40px;\n width: 40px;\n}\n\n.confirm-button {\n background-color: #4CAF50;\n}\n\n.confirm-button:hover {\n background-color: #3d8b40;\n transform: translateY(-2px);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n}\n\n.cancel-button {\n background-color: rgb(244, 67, 54);\n}\n\n.cancel-button:hover {\n background-color: #d32f2f;\n transform: translateY(-2px);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n}\n\n.banner-image.clickable {\n cursor: pointer;\n transition: opacity 0.3s ease;\n}\n\n.banner-image.clickable:hover {\n opacity: 0.8;\n}\n\n.parameter-list-ul.profile {\n position: relative;\n overflow: hidden; \n max-height: 200px;\n margin-bottom: 20px;\n}\n\n\n.profile-preview {\n position: relative;\n width: 100%;\n height: 100%;\n}\n\n.preview-banner {\n position: relative;\n width: 100%;\n height: 120px; \n overflow: hidden;\n}\n\n.preview-banner-img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n\n.preview-info {\n position: relative;\n display: flex;\n align-items: center;\n padding: 10px;\n gap: 10px;\n background: rgba(0, 0, 0, 0.3);\n}\n\n.preview-avatar {\n width: 45px;\n height: 45px;\n border-radius: 50%;\n border: 2px solid white;\n}\n\n/* ---------------------Style pour le QR code--------------------- */\n\n.qr-code {\n width: 50px;\n height: 50px;\n cursor: pointer;\n transition: transform 0.2s ease;\n}\n\n.qr-code:hover {\n transform: scale(1.5);\n}\n\n.qr-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.7);\n display: flex;\n justify-content: center;\n align-items: center;\n z-index: 1000;\n}\n\n.qr-modal-content {\n background-color: white;\n padding: 20px;\n border-radius: 8px;\n position: relative;\n text-align: center;\n}\n\n.close-qr-modal {\n position: absolute;\n right: 10px;\n top: 5px;\n font-size: 24px;\n cursor: pointer;\n color: #666;\n}\n\n.close-qr-modal:hover {\n color: #000;\n}\n\n.qr-code-large {\n max-width: 300px;\n margin: 10px 0;\n}\n\n.qr-address {\n margin-top: 10px;\n word-break: break-all;\n font-size: 12px;\n color: #666;\n}\n\n.pairing-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.5);\n display: flex;\n justify-content: center;\n align-items: center;\n z-index: 1000;\n}\n\n.pairing-modal-content {\n background-color: white;\n padding: 2rem;\n border-radius: 8px;\n width: 90%;\n max-width: 500px;\n}\n\n.pairing-form {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n\n.form-group {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.form-group label {\n font-weight: bold;\n}\n\n.button-group {\n display: flex;\n gap: 1rem;\n justify-content: flex-end;\n margin-top: 1rem;\n}\n\n.button-group button {\n padding: 0.5rem 1rem;\n border-radius: 4px;\n cursor: pointer;\n}\n\n.confirm-button {\n background-color: var(--accent-color);\n color: white;\n border: none;\n}\n\n.cancel-button {\n background-color: #ccc;\n border: none;\n}\n";
|
||
|
||
class AccountComponent extends HTMLElement {
|
||
_callback;
|
||
accountElement = null;
|
||
constructor() {
|
||
super();
|
||
console.log("INIT");
|
||
this.attachShadow({ mode: "open" });
|
||
this.accountElement = this.shadowRoot?.querySelector("account-element") || null;
|
||
}
|
||
connectedCallback() {
|
||
console.log("CALLBACKs");
|
||
this.render();
|
||
this.fetchData();
|
||
if (!customElements.get("account-element")) {
|
||
customElements.define("account-element", AccountElement);
|
||
}
|
||
}
|
||
async fetchData() {
|
||
{
|
||
const service = await Services.getInstance();
|
||
await service.getProcesses();
|
||
}
|
||
}
|
||
set callback(fn) {
|
||
if (typeof fn === "function") {
|
||
this._callback = fn;
|
||
} else {
|
||
console.error("Callback is not a function");
|
||
}
|
||
}
|
||
get callback() {
|
||
return this._callback;
|
||
}
|
||
render() {
|
||
if (this.shadowRoot && !this.shadowRoot.querySelector("account-element")) {
|
||
const style = document.createElement("style");
|
||
style.textContent = accountCss;
|
||
const accountElement = document.createElement("account-element");
|
||
this.shadowRoot.appendChild(style);
|
||
this.shadowRoot.appendChild(accountElement);
|
||
}
|
||
}
|
||
}
|
||
customElements.define("account-component", AccountComponent);
|
||
|
||
export { AccountComponent };
|