forked from Simnation/Main
862 lines
No EOL
28 KiB
JavaScript
862 lines
No EOL
28 KiB
JavaScript
let Utils = {};
|
|
|
|
let locale;
|
|
let format;
|
|
Utils.translate = function (key) {
|
|
if (!Lang.hasOwnProperty(locale)) {
|
|
console.warn(`Language '${locale}' is not available. Using default 'en'.`);
|
|
locale = "en";
|
|
}
|
|
|
|
let langObj = Lang[locale];
|
|
const keys = key.split(".");
|
|
|
|
for (const k of keys) {
|
|
if (!langObj.hasOwnProperty(k)) {
|
|
console.warn(`Translation key '${key}' not found for language '${locale}'.`);
|
|
return "missing_translation";
|
|
}
|
|
langObj = langObj[k];
|
|
}
|
|
|
|
return langObj;
|
|
};
|
|
|
|
Utils.setLocale = function (current_locale) {
|
|
locale = current_locale;
|
|
};
|
|
|
|
Utils.setFormat = function (current_format) {
|
|
format = current_format;
|
|
};
|
|
|
|
Utils.loadLanguageFile = async function () {
|
|
try {
|
|
await new Promise((resolve, reject) => {
|
|
let fileUrl = `lang/${locale}.js`;
|
|
const script = document.createElement("script");
|
|
script.src = fileUrl;
|
|
|
|
script.onload = () => {
|
|
resolve();
|
|
};
|
|
|
|
script.onerror = (event) => {
|
|
reject(new Error("Failed to load language file: " + fileUrl));
|
|
};
|
|
|
|
document.body.appendChild(script);
|
|
|
|
const timeoutDuration = 10000; // 10 seconds
|
|
setTimeout(() => {
|
|
reject(new Error("Timeout: The script took too long to load the language file: " + fileUrl));
|
|
}, timeoutDuration);
|
|
});
|
|
} catch (error) {
|
|
if (locale !== "en") {
|
|
console.warn(`Language '${locale}' is not available. Using default 'en'.`);
|
|
Utils.setLocale("en");
|
|
await Utils.loadLanguageFile();
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
};
|
|
|
|
Utils.loadLanguageModules = async function (utils_module) {
|
|
Utils.setLocale(utils_module.config.locale);
|
|
Utils.setFormat(utils_module.config.format);
|
|
await Utils.loadLanguageFile();
|
|
Utils.deepMerge(Lang,utils_module.lang);
|
|
};
|
|
|
|
Utils.timeConverter = function (UNIX_timestamp, options = {}) {
|
|
const timestampMillis = UNIX_timestamp * 1000;
|
|
const formattedTime = new Date(timestampMillis).toLocaleString(locale, options);
|
|
|
|
return formattedTime;
|
|
};
|
|
|
|
Utils.currencyFormat = function (number, decimalPlaces = null) {
|
|
const options = {
|
|
style: "currency",
|
|
currency: format.currency,
|
|
};
|
|
|
|
if (decimalPlaces != null) {
|
|
options.minimumFractionDigits = decimalPlaces;
|
|
options.maximumFractionDigits = decimalPlaces;
|
|
}
|
|
|
|
return new Intl.NumberFormat(format.location, options).format(number);
|
|
};
|
|
|
|
Utils.numberFormat = function (number, decimalPlaces = null) {
|
|
const options = {};
|
|
|
|
if (decimalPlaces != null) {
|
|
options.minimumFractionDigits = decimalPlaces;
|
|
options.maximumFractionDigits = decimalPlaces;
|
|
}
|
|
|
|
return new Intl.NumberFormat(format.location, options).format(number);
|
|
};
|
|
|
|
Utils.getCurrencySymbol = function () {
|
|
const options = {
|
|
style: "currency",
|
|
currency: format.currency,
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: 0,
|
|
};
|
|
|
|
return new Intl.NumberFormat(locale, options).format(0).replace(/\d/g, "").trim();
|
|
};
|
|
|
|
const requestQueue = [];
|
|
let isProcessing = false;
|
|
|
|
const processQueue = async () => {
|
|
if (!isProcessing && requestQueue.length > 0) {
|
|
isProcessing = true;
|
|
const { event, data, route, cb } = requestQueue.shift();
|
|
try {
|
|
const response = await fetch(Utils.getRoute(route), {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({ event, data }),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Request failed with status: ${response.status}`);
|
|
}
|
|
|
|
const responseData = await response.json();
|
|
|
|
if (cb) {
|
|
cb(responseData);
|
|
} else {
|
|
if (responseData !== 200) {
|
|
console.log(responseData);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error occurred while making a POST request with event: "${event}", data: "${JSON.stringify(data)}", and route: "${Utils.getRoute(route)}": ${error.message}`);
|
|
} finally {
|
|
isProcessing = false;
|
|
setTimeout(function() {
|
|
processQueue();
|
|
}, 200);
|
|
}
|
|
}
|
|
};
|
|
|
|
Utils.post = function (event, data, route = "post", cb) {
|
|
requestQueue.push({ event, data, route, cb });
|
|
processQueue();
|
|
};
|
|
|
|
let resource_name;
|
|
Utils.getRoute = function (name) {
|
|
return `https://${resource_name}/${name}`;
|
|
};
|
|
|
|
Utils.setResourceName = function (current_resource_name) {
|
|
resource_name = current_resource_name;
|
|
};
|
|
|
|
const modalTemplate = `
|
|
<div id="confirmation-modal" class="modal fade">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title"></h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<form id="form-confirmation-modal" style="margin: 0;">
|
|
<div class="modal-body">
|
|
|
|
</div>
|
|
<div class="modal-footer">
|
|
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
Utils.showDefaultModal = function (action, body = Utils.translate("confirmation_modal_body")) {
|
|
Utils.showCustomModal({
|
|
title: Utils.translate("confirmation_modal_title"),
|
|
body,
|
|
buttons: [
|
|
{ text: Utils.translate("confirmation_modal_cancel_button"), class: "btn btn-outline-primary", dismiss: true },
|
|
{ text: Utils.translate("confirmation_modal_confirm_button"), class: "btn btn-primary", dismiss: true, action },
|
|
],
|
|
});
|
|
};
|
|
|
|
Utils.showDefaultDangerModal = function (action, body = Utils.translate("confirmation_modal_body")) {
|
|
Utils.showCustomModal({
|
|
title: Utils.translate("confirmation_modal_title"),
|
|
body,
|
|
buttons: [
|
|
{ text: Utils.translate("confirmation_modal_cancel_button"), class: "btn btn-outline-danger", dismiss: true },
|
|
{ text: Utils.translate("confirmation_modal_confirm_button"), class: "btn btn-danger", dismiss: true, action },
|
|
],
|
|
});
|
|
};
|
|
/*
|
|
const exampleConfig = {
|
|
title: 'Custom Modal Title',
|
|
body: 'Custom Modal Body Text',
|
|
bodyHtml: '<p>Custom Modal Body Text that accept HTML</p>',
|
|
bodyImage: "https://shuffle.dev/randomizer/saas/bootstrap-pstls/1.0.0/static_elements/footer/10_awz.jpg",
|
|
footerText: "Custom Footer Text",
|
|
buttons: [
|
|
{ text: Utils.translate('confirmation_modal_cancel_button'), class: 'btn btn-outline-primary', dismiss: true },
|
|
{ text: Utils.translate('confirmation_modal_confirm_button'), class: 'btn btn-primary', dismiss: false, action: () => console.log('Confirmed') }
|
|
{ text: 'Submit', class: 'btn btn-primary', dismiss: false, type: 'submit' }
|
|
],
|
|
inputs: [
|
|
{
|
|
type: 'text', // Input type: text
|
|
label: 'Text Input:',
|
|
small: 'Small text',
|
|
id: 'text-input-id',
|
|
name: 'text-input-name',
|
|
value: 'xxxx',
|
|
required: true,
|
|
placeholder: 'Enter text here'
|
|
},
|
|
{
|
|
type: 'number', // Input type: number
|
|
label: 'Number Input:',
|
|
small: 'Small text',
|
|
id: 'number-input-id',
|
|
name: 'number-input-name',
|
|
value: 10,
|
|
min: 0,
|
|
max: 10,
|
|
required: true,
|
|
placeholder: 'Enter a number'
|
|
},
|
|
{
|
|
type: 'custom',
|
|
html: `
|
|
<div class="d-flex>
|
|
// custom html
|
|
</div>
|
|
`
|
|
},
|
|
{
|
|
type: 'select', // Input type: select
|
|
label: 'Select Input:',
|
|
id: 'select-input-id',
|
|
name: 'select-input-name',
|
|
required: true,
|
|
options: [
|
|
{ value: 'option1', text: 'Option 1' },
|
|
{ value: 'option2', text: 'Option 2' },
|
|
{ value: 'option3', text: 'Option 3' }
|
|
]
|
|
}
|
|
],
|
|
onSubmit: function(formData) {
|
|
console.log("Form submitted with input values:", [...formData]);
|
|
let amount = formData.get("number-input-name"); // Get by element name
|
|
// You can perform further actions with the input values here
|
|
}
|
|
};
|
|
*/
|
|
Utils.showCustomModal = function (config) {
|
|
// Check if the modal already exists
|
|
const $existingModal = $("#confirmation-modal");
|
|
if ($existingModal.length > 0) {
|
|
return;
|
|
}
|
|
|
|
const modalConfig = {
|
|
title: Utils.translate("confirmation_modal_title"),
|
|
buttons: [],
|
|
inputs: [],
|
|
};
|
|
|
|
// Merge the provided config with the default modalConfig
|
|
const mergedConfig = { ...modalConfig };
|
|
Utils.deepMerge(mergedConfig, config);
|
|
|
|
// Append the modal HTML to the body
|
|
$("body").append(modalTemplate);
|
|
|
|
// Cache the modal element
|
|
const $modal = $("#confirmation-modal");
|
|
const $modalBody = $modal.find(".modal-body");
|
|
|
|
// Set modal content
|
|
$modal.find(".modal-title").text(mergedConfig.title);
|
|
|
|
if (mergedConfig.bodyImage) {
|
|
const $imageContainer = $("<div>", { class: "d-flex justify-content-center m-2" });
|
|
const $image = $("<img>", { src: mergedConfig.bodyImage, class: "w-50" });
|
|
$imageContainer.append($image);
|
|
$modalBody.append($imageContainer);
|
|
}
|
|
|
|
if (mergedConfig.body) {
|
|
const $p = $("<p>", { id: "modal-body-text", text: mergedConfig.body });
|
|
$modalBody.append($p);
|
|
}
|
|
|
|
if (mergedConfig.bodyHtml) {
|
|
$modalBody.append(mergedConfig.bodyHtml);
|
|
}
|
|
|
|
// Set modal inputs
|
|
const $form = $modal.find("#form-confirmation-modal");
|
|
mergedConfig.inputs.forEach(inputConfig => {
|
|
const $inputContainer = $("<div>", { class: "form-group mx-2" });
|
|
|
|
if (inputConfig.type === "select") {
|
|
const $label = $("<label>", { text: inputConfig.label, for: inputConfig.id });
|
|
const $select = $("<select>", { class: "form-control", id: inputConfig.id, name: inputConfig.name, required: inputConfig.required });
|
|
if (!Array.isArray(inputConfig.options)) {
|
|
inputConfig.options = Object.values(inputConfig.options);
|
|
}
|
|
inputConfig.options.forEach(option => {
|
|
const $option = $("<option>", { value: option.value, text: option.text });
|
|
$select.append($option);
|
|
});
|
|
$inputContainer.append($label, $select);
|
|
} else if (inputConfig.type === "checkbox") {
|
|
const $checkboxContainer = $("<div>", { class: "form-check" });
|
|
const $label = $("<label>", { for: inputConfig.id, class: "form-check-label" });
|
|
const $input = $("<input>", { type: "checkbox", class: "form-check-input", id: inputConfig.id, name: inputConfig.name, required: inputConfig.required });
|
|
$label.text(inputConfig.label);
|
|
$inputContainer.append($checkboxContainer.append($input, $label));
|
|
} else if (inputConfig.type === "slider" || inputConfig.type === "range") {
|
|
const $label = $("<label>", { text: inputConfig.label, for: inputConfig.id });
|
|
if (!inputConfig.default) {
|
|
inputConfig.default = inputConfig.max ?? 100;
|
|
}
|
|
let range_slider = `
|
|
<div class="range-slider mt-2" style='--min:${inputConfig.min || 0}; --max:${inputConfig.max || 100}; --step:${inputConfig.step || 1}; --value:${inputConfig.default}; --text-value:"${inputConfig.default}"; --prefix:"${inputConfig.isCurrency ? Utils.getCurrencySymbol() : ""} ";'>
|
|
<input id="${inputConfig.id}" name="${inputConfig.name}" type="range" min="${inputConfig.min || 0}" max="${inputConfig.max || 100}" step="${inputConfig.step || 1}" value="${inputConfig.default}" oninput="this.parentNode.style.setProperty('--value',this.value); this.parentNode.style.setProperty('--text-value', JSON.stringify(this.value))">
|
|
<output></output>
|
|
<div class='range-slider__progress'></div>
|
|
</div>`;
|
|
$inputContainer.append($label, range_slider);
|
|
} else if (inputConfig.type === "custom") {
|
|
const $customInput = $(inputConfig.html);
|
|
$inputContainer.append($customInput);
|
|
} else {
|
|
const $label = $("<label>", { text: inputConfig.label, for: inputConfig.id });
|
|
const $input = $("<input>", { type: inputConfig.type, class: "form-control", id: inputConfig.id, name: inputConfig.name, required: inputConfig.required, placeholder: inputConfig.placeholder, min: inputConfig.min, max: inputConfig.max, value: inputConfig.value });
|
|
$inputContainer.append($label, $input);
|
|
}
|
|
if (inputConfig.small) {
|
|
const $small = $("<small>", { text: inputConfig.small, class: "text-muted", style: "font-size: 12px;" });
|
|
$inputContainer.append($small);
|
|
}
|
|
$modalBody.append($inputContainer);
|
|
});
|
|
|
|
if (mergedConfig.footerText) {
|
|
const $p = $("<p>", { id: "modal-footer-text", text: mergedConfig.footerText });
|
|
$modalBody.append($p);
|
|
}
|
|
|
|
// Set modal buttons
|
|
const $footer = $modal.find(".modal-footer");
|
|
$footer.empty();
|
|
mergedConfig.buttons.forEach(button => {
|
|
const $button = $("<button>", { class: button.class, text: button.text, type: button.type ?? "button" });
|
|
if (button.dismiss) {
|
|
$button.attr("data-dismiss", "modal");
|
|
}
|
|
if (button.action) {
|
|
$button.on("click", button.action);
|
|
}
|
|
$footer.append($button);
|
|
});
|
|
|
|
// Set modal form submit
|
|
$form.on("submit", function (e) {
|
|
e.preventDefault();
|
|
|
|
if (config.onSubmit) {
|
|
config.onSubmit(new FormData(e.target));
|
|
}
|
|
$modal.modal("hide");
|
|
});
|
|
|
|
// Show the modal
|
|
$modal.modal({ show: true });
|
|
|
|
// Remove the modal from the DOM when hidden
|
|
$modal.on("hidden.bs.modal", function () {
|
|
setTimeout(() => {
|
|
$(this).remove();
|
|
}, 50);
|
|
});
|
|
};
|
|
|
|
Utils.deepMerge = function (target, source) {
|
|
for (const key in source) {
|
|
if (source.hasOwnProperty(key)) {
|
|
if (typeof source[key] === "function") {
|
|
target[key] = source[key];
|
|
} else if (source[key] instanceof Object && source[key] !== null) {
|
|
if (!target.hasOwnProperty(key)) {
|
|
target[key] = {};
|
|
}
|
|
Utils.deepMerge(target[key], source[key]);
|
|
} else {
|
|
target[key] = source[key];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
if (!String.prototype.format) {
|
|
String.prototype.format = function() {
|
|
let args = arguments;
|
|
return this.replace(/{(\d+)}/g, function(match, number) {
|
|
return typeof args[number] != "undefined"
|
|
? args[number]
|
|
: match
|
|
;
|
|
});
|
|
};
|
|
}
|
|
|
|
/**
|
|
* DEPRECATED: Use Utils.onInvalidInput(this) on input events instead.
|
|
*/
|
|
Utils.invalidMsg = function (textbox, min = null, max = null) {
|
|
textbox.setCustomValidity("");
|
|
if (textbox.value === "") {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.fill_field"));
|
|
}
|
|
else if (textbox.validity.typeMismatch) {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.invalid_value"));
|
|
}
|
|
else if (textbox.validity.rangeUnderflow && min !== null) {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.more_than").format(min));
|
|
}
|
|
else if (textbox.validity.rangeOverflow && max !== null) {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.less_than").format(max));
|
|
}
|
|
else if (textbox.validity.stepMismatch) {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.invalid_value"));
|
|
}
|
|
else if (textbox.validity.patternMismatch) {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.pattern_mismatch"));
|
|
}
|
|
else if (textbox.validity.tooLong) {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.too_long"));
|
|
}
|
|
else if (textbox.validity.tooShort) {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.too_short"));
|
|
}
|
|
textbox.reportValidity();
|
|
return true;
|
|
};
|
|
|
|
Utils.onInvalidInput = function (textbox) { // oninvalid="Utils.onInvalidInput(this)"
|
|
textbox.setCustomValidity("");
|
|
|
|
const elementType = textbox.tagName.toLowerCase(); // 'input', 'select', 'textarea'
|
|
const inputType = elementType === "input" ? textbox.type.toLowerCase() : elementType; // 'text', 'email', 'select', etc.
|
|
|
|
if (textbox.validity.valueMissing || textbox.value === "") {
|
|
if (inputType == "select") {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.select_fill_field"));
|
|
} else {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.fill_field"));
|
|
}
|
|
}
|
|
else if (textbox.validity.typeMismatch || textbox.validity.badInput) {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.invalid_value"));
|
|
}
|
|
else if (textbox.validity.rangeUnderflow) {
|
|
const min = $(textbox).attr("min");
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.more_than").format(Utils.numberFormat(min)));
|
|
}
|
|
else if (textbox.validity.rangeOverflow) {
|
|
const max = $(textbox).attr("max");
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.less_than").format(Utils.numberFormat(max)));
|
|
}
|
|
else if (textbox.validity.stepMismatch) {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.invalid_value"));
|
|
}
|
|
else if (textbox.validity.patternMismatch) {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.pattern_mismatch"));
|
|
}
|
|
else if (textbox.validity.tooLong) {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.too_long"));
|
|
}
|
|
else if (textbox.validity.tooShort) {
|
|
textbox.setCustomValidity(Utils.translate("custom_validity.too_short"));
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Sorts an object or an array of objects based on specified property paths.
|
|
* If the input is an object, it converts it to an array of objects, including the original object's key as an `.id` property.
|
|
* @param {Object|Array} input - The object or array to sort.
|
|
* @param {Array|String} propertyPath - The path(s) to the property for sorting, can be a single path or an array of paths for multiple criteria.
|
|
* @param {Boolean} ascending - Whether the sorting should be in ascending order. Defaults to true.
|
|
* @returns {Array} - The sorted array of objects.
|
|
*/
|
|
Utils.sortElement = function(input, propertyPath, ascending = true) {
|
|
let arrayToSort;
|
|
|
|
// Convert input to an array of objects if it's not already one, including `.id`
|
|
if (!Array.isArray(input)) {
|
|
arrayToSort = Object.entries(input).map(([index, item]) => {
|
|
if (item !== null && (typeof item === "object" || Array.isArray(item))) {
|
|
return { ...item, id: item.id || index };
|
|
} else {
|
|
return { value: item, id: index };
|
|
}
|
|
});
|
|
} else {
|
|
arrayToSort = input.map((item, index) => ({
|
|
...item,
|
|
id: item.id || index,
|
|
}));
|
|
}
|
|
|
|
// Convert propertyPath to an array if it's not already one
|
|
if (!Array.isArray(propertyPath)) {
|
|
propertyPath = [propertyPath];
|
|
}
|
|
|
|
// A helper function to safely access nested properties
|
|
const resolvePath = (object, path) => {
|
|
return path.split(".").reduce((accumulator, currentValue) => {
|
|
return accumulator ? accumulator[currentValue] : undefined;
|
|
}, object);
|
|
};
|
|
|
|
// The sorting function
|
|
return arrayToSort.sort((a, b) => {
|
|
for (let i = 0; i < propertyPath.length; i++) {
|
|
const aValue = resolvePath(a, propertyPath[i]);
|
|
const bValue = resolvePath(b, propertyPath[i]);
|
|
|
|
if (typeof aValue === "string" && typeof bValue === "string") {
|
|
const comparison = aValue.localeCompare(bValue);
|
|
if (comparison !== 0) return ascending ? comparison : -comparison;
|
|
} else {
|
|
if (aValue < bValue) return ascending ? -1 : 1;
|
|
if (aValue > bValue) return ascending ? 1 : -1;
|
|
}
|
|
}
|
|
return 0; // if all criteria are equal
|
|
});
|
|
};
|
|
|
|
Utils.convertFileToBase64 = function (file, callback) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
callback(e.target.result);
|
|
};
|
|
reader.readAsDataURL(file);
|
|
};
|
|
|
|
$(function () {
|
|
window.addEventListener("message", function (event) {
|
|
let item = event.data;
|
|
if (item.notification) {
|
|
vt.showNotification(item.notification, {
|
|
position: item.position,
|
|
duration: item.duration,
|
|
|
|
title: item.title,
|
|
closable: true,
|
|
focusable: false,
|
|
callback: undefined,
|
|
}, item.notification_type);
|
|
}
|
|
if (item.dark_theme != undefined) {
|
|
if(item.dark_theme == 0){
|
|
// Light theme
|
|
$("#utils-css-light").prop("disabled", false);
|
|
$("#utils-css-dark").prop("disabled", true);
|
|
} else if(item.dark_theme == 1){
|
|
// Dark theme
|
|
$("#utils-css-dark").prop("disabled", false);
|
|
$("#utils-css-light").prop("disabled", true);
|
|
}
|
|
}
|
|
});
|
|
|
|
document.onkeyup = function(data){
|
|
if (data.key == "Escape"){
|
|
if ($("#confirmation-modal").is(":visible")){
|
|
$("#confirmation-modal").modal("hide");
|
|
} else if ($(".main").is(":visible")){
|
|
$(".modal").modal("hide");
|
|
Utils.post("close","");
|
|
}
|
|
}
|
|
};
|
|
|
|
// Notification
|
|
(() => {
|
|
const toastPosition = {
|
|
TopLeft: "top-left",
|
|
TopCenter: "top-center",
|
|
TopRight: "top-right",
|
|
MiddleLeft: "middle-left",
|
|
MiddleRight: "middle-right",
|
|
BottomLeft: "bottom-left",
|
|
BottomCenter: "bottom-center",
|
|
BottomRight: "bottom-right",
|
|
};
|
|
|
|
const toastPositionIndex = [
|
|
[toastPosition.TopLeft, toastPosition.TopCenter, toastPosition.TopRight],
|
|
[toastPosition.MiddleLeft, "", toastPosition.MiddleRight],
|
|
[toastPosition.BottomLeft, toastPosition.BottomCenter, toastPosition.BottomRight],
|
|
];
|
|
|
|
const svgs = {
|
|
success: "<svg viewBox=\"0 0 426.667 426.667\" width=\"18\" height=\"18\"><path d=\"M213.333 0C95.518 0 0 95.514 0 213.333s95.518 213.333 213.333 213.333c117.828 0 213.333-95.514 213.333-213.333S331.157 0 213.333 0zm-39.134 322.918l-93.935-93.931 31.309-31.309 62.626 62.622 140.894-140.898 31.309 31.309-172.203 172.207z\" fill=\"#6ac259\"></path></svg>",
|
|
warning: "<svg viewBox=\"0 0 310.285 310.285\" width=18 height=18> <path d=\"M264.845 45.441C235.542 16.139 196.583 0 155.142 0 113.702 0 74.743 16.139 45.44 45.441 16.138 74.743 0 113.703 0 155.144c0 41.439 16.138 80.399 45.44 109.701 29.303 29.303 68.262 45.44 109.702 45.44s80.399-16.138 109.702-45.44c29.303-29.302 45.44-68.262 45.44-109.701.001-41.441-16.137-80.401-45.439-109.703zm-132.673 3.895a12.587 12.587 0 0 1 9.119-3.873h28.04c3.482 0 6.72 1.403 9.114 3.888 2.395 2.485 3.643 5.804 3.514 9.284l-4.634 104.895c-.263 7.102-6.26 12.933-13.368 12.933H146.33c-7.112 0-13.099-5.839-13.345-12.945L128.64 58.594c-.121-3.48 1.133-6.773 3.532-9.258zm23.306 219.444c-16.266 0-28.532-12.844-28.532-29.876 0-17.223 12.122-30.211 28.196-30.211 16.602 0 28.196 12.423 28.196 30.211.001 17.591-11.456 29.876-27.86 29.876z\" fill=\"#FFDA44\" /> </svg>",
|
|
info: "<svg viewBox=\"0 0 23.625 23.625\" width=18 height=18> <path d=\"M11.812 0C5.289 0 0 5.289 0 11.812s5.289 11.813 11.812 11.813 11.813-5.29 11.813-11.813S18.335 0 11.812 0zm2.459 18.307c-.608.24-1.092.422-1.455.548a3.838 3.838 0 0 1-1.262.189c-.736 0-1.309-.18-1.717-.539s-.611-.814-.611-1.367c0-.215.015-.435.045-.659a8.23 8.23 0 0 1 .147-.759l.761-2.688c.067-.258.125-.503.171-.731.046-.23.068-.441.068-.633 0-.342-.071-.582-.212-.717-.143-.135-.412-.201-.813-.201-.196 0-.398.029-.605.09-.205.063-.383.12-.529.176l.201-.828c.498-.203.975-.377 1.43-.521a4.225 4.225 0 0 1 1.29-.218c.731 0 1.295.178 1.692.53.395.353.594.812.594 1.376 0 .117-.014.323-.041.617a4.129 4.129 0 0 1-.152.811l-.757 2.68a7.582 7.582 0 0 0-.167.736 3.892 3.892 0 0 0-.073.626c0 .356.079.599.239.728.158.129.435.194.827.194.185 0 .392-.033.626-.097.232-.064.4-.121.506-.17l-.203.827zm-.134-10.878a1.807 1.807 0 0 1-1.275.492c-.496 0-.924-.164-1.28-.492a1.57 1.57 0 0 1-.533-1.193c0-.465.18-.865.533-1.196a1.812 1.812 0 0 1 1.28-.497c.497 0 .923.165 1.275.497.353.331.53.731.53 1.196 0 .467-.177.865-.53 1.193z\" fill=\"#006DF0\" /> </svg>",
|
|
error: "<svg viewBox=\"0 0 51.976 51.976\" width=18 height=18> <path d=\"M44.373 7.603c-10.137-10.137-26.632-10.138-36.77 0-10.138 10.138-10.137 26.632 0 36.77s26.632 10.138 36.77 0c10.137-10.138 10.137-26.633 0-36.77zm-8.132 28.638a2 2 0 0 1-2.828 0l-7.425-7.425-7.778 7.778a2 2 0 1 1-2.828-2.828l7.778-7.778-7.425-7.425a2 2 0 1 1 2.828-2.828l7.425 7.425 7.071-7.071a2 2 0 1 1 2.828 2.828l-7.071 7.071 7.425 7.425a2 2 0 0 1 0 2.828z\" fill=\"#D80027\" /> </svg>",
|
|
};
|
|
|
|
const styles = `
|
|
.vt-container {
|
|
position: fixed;
|
|
width: 100%;
|
|
height: 100vh;
|
|
top: 0;
|
|
left: 0;
|
|
z-index: 9999;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.vt-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.vt-col {
|
|
flex: 1;
|
|
margin: 10px 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.vt-col.top-left,
|
|
.vt-col.bottom-left {
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.vt-col.top-right,
|
|
.vt-col.bottom-right {
|
|
align-items: flex-end;
|
|
}
|
|
|
|
.vt-col.middle-left {
|
|
justify-content: center;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.vt-col.middle-right {
|
|
justify-content: center;
|
|
align-items: flex-end;
|
|
}
|
|
|
|
|
|
.vt-card {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 12px 20px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
border-radius: 4px;
|
|
margin: 0px;
|
|
transition: 0.3s all ease-in-out;
|
|
pointer-events: all;
|
|
border-left: 3px solid #8b8b8b;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.vt-card.success {
|
|
border-left: 3px solid #6ec05f;
|
|
}
|
|
|
|
.vt-card.warning {
|
|
border-left: 3px solid #fed953;
|
|
}
|
|
|
|
.vt-card.info {
|
|
border-left: 3px solid #1271ec;
|
|
}
|
|
|
|
.vt-card.error {
|
|
border-left: 3px solid #d60a2e;
|
|
}
|
|
|
|
.vt-card .text-group {
|
|
margin-left: 15px;
|
|
}
|
|
|
|
.vt-card h4 {
|
|
margin: 0;
|
|
margin-bottom: 2px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.vt-card p {
|
|
margin: 0;
|
|
font-size: 14px;
|
|
}
|
|
`;
|
|
|
|
const styleSheet = document.createElement("style");
|
|
styleSheet.innerText = styles.replace((/ |\r\n|\n|\r/gm), "");
|
|
document.head.appendChild(styleSheet);
|
|
|
|
const vtContainer = document.createElement("div");
|
|
vtContainer.className = "vt-container";
|
|
|
|
for (const ri of [0, 1, 2]) {
|
|
const row = document.createElement("div");
|
|
row.className = "vt-row";
|
|
|
|
for (const ci of [0, 1, 2]) {
|
|
const col = document.createElement("div");
|
|
col.className = `vt-col ${toastPositionIndex[ri][ci]}`;
|
|
|
|
row.appendChild(col);
|
|
}
|
|
|
|
vtContainer.appendChild(row);
|
|
}
|
|
|
|
document.body.appendChild(vtContainer);
|
|
|
|
window.vt = {
|
|
options: {
|
|
title: undefined,
|
|
position: toastPosition.TopCenter,
|
|
duration: 10000,
|
|
closable: true,
|
|
focusable: true,
|
|
callback: undefined,
|
|
},
|
|
showNotification(message, options, type) {
|
|
show(message, options, type);
|
|
},
|
|
};
|
|
|
|
function show(message = "", options, type) {
|
|
options = { ...window.vt.options, ...options };
|
|
|
|
const col = document.getElementsByClassName(options.position)[0];
|
|
|
|
const vtCard = document.createElement("div");
|
|
vtCard.className = `vt-card ${type}`;
|
|
vtCard.innerHTML += svgs[type];
|
|
vtCard.options = {
|
|
...options, ...{
|
|
message,
|
|
type: type,
|
|
yPos: options.position.indexOf("top") > -1 ? "top" : "bottom",
|
|
isFocus: false,
|
|
},
|
|
};
|
|
|
|
setVTCardContent(vtCard);
|
|
setVTCardIntroAnim(vtCard);
|
|
setVTCardBindEvents(vtCard);
|
|
autoDestroy(vtCard);
|
|
|
|
col.appendChild(vtCard);
|
|
}
|
|
|
|
function setVTCardContent(vtCard) {
|
|
const textGroupDiv = document.createElement("div");
|
|
|
|
textGroupDiv.className = "text-group";
|
|
|
|
if (vtCard.options.title) {
|
|
textGroupDiv.innerHTML = `<h4>${vtCard.options.title}</h4>`;
|
|
}
|
|
|
|
textGroupDiv.innerHTML += `<p>${vtCard.options.message}</p>`;
|
|
|
|
vtCard.appendChild(textGroupDiv);
|
|
}
|
|
|
|
function setVTCardIntroAnim(vtCard) {
|
|
vtCard.style.setProperty(`margin-${vtCard.options.yPos}`, "-15px");
|
|
vtCard.style.setProperty("opacity", "0");
|
|
|
|
setTimeout(() => {
|
|
vtCard.style.setProperty(`margin-${vtCard.options.yPos}`, "15px");
|
|
vtCard.style.setProperty("opacity", "1");
|
|
}, 50);
|
|
}
|
|
|
|
function setVTCardBindEvents(vtCard) {
|
|
vtCard.addEventListener("click", () => {
|
|
if (vtCard.options.closable) {
|
|
destroy(vtCard);
|
|
}
|
|
});
|
|
|
|
vtCard.addEventListener("mouseover", () => {
|
|
vtCard.options.isFocus = vtCard.options.focusable;
|
|
});
|
|
|
|
vtCard.addEventListener("mouseout", () => {
|
|
vtCard.options.isFocus = false;
|
|
autoDestroy(vtCard, vtCard.options.duration);
|
|
});
|
|
}
|
|
|
|
function destroy(vtCard) {
|
|
vtCard.style.setProperty(`margin-${vtCard.options.yPos}`, `-${vtCard.offsetHeight}px`);
|
|
vtCard.style.setProperty("opacity", "0");
|
|
|
|
setTimeout(() => {
|
|
if(typeof x !== "undefined"){
|
|
vtCard.parentNode.removeChild(v);
|
|
|
|
if (typeof vtCard.options.callback === "function") {
|
|
vtCard.options.callback();
|
|
}
|
|
}
|
|
}, 500);
|
|
}
|
|
|
|
function autoDestroy(vtCard) {
|
|
if (vtCard.options.duration !== 0) {
|
|
setTimeout(() => {
|
|
if (!vtCard.options.isFocus) {
|
|
destroy(vtCard);
|
|
}
|
|
}, vtCard.options.duration);
|
|
}
|
|
}
|
|
})();
|
|
}); |