640 lines
20 KiB
JavaScript
640 lines
20 KiB
JavaScript
// The Lucia project.
|
||
// Copyright 2023-2026 DSP, inc. All rights reserved.
|
||
// Authors:
|
||
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
|
||
// imacat.yang@dsp.im (imacat), 2023/9/23
|
||
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
|
||
/** @module alertModal SweetAlert2 modal dialogs for user interactions. */
|
||
|
||
import Swal from "sweetalert2";
|
||
import { useAllMapDataStore } from "@/stores/allMapData";
|
||
import { useConformanceStore } from "@/stores/conformance";
|
||
import { useFilesStore } from "@/stores/files";
|
||
import { usePageAdminStore } from "@/stores/pageAdmin";
|
||
import { useModalStore } from "@/stores/modal";
|
||
import { escapeHtml } from "@/utils/escapeHtml.js";
|
||
|
||
const customClass = {
|
||
container: "!z-[99999]",
|
||
popup: "!w-[564px]",
|
||
title: "!text-xl !font-semibold !mb-2",
|
||
htmlContainer: "!text-sm !font-normal !h-full !mb-4 !leading-5",
|
||
inputLabel: "!text-sm !font-normal",
|
||
input:
|
||
"!h-8 !text-sm !font-normal !shadow-inner !shadow-black !border-neutral-200",
|
||
validationMessage: "!bg-neutral-10 !text-danger",
|
||
confirmButton:
|
||
"!inline-block !rounded-full !text-sm !font-medium !text-center !align-middle !transition-colors !duration-300 !px-5 !py-2 !w-[100px] !h-[40px]",
|
||
cancelButton:
|
||
"!inline-block !rounded-full !text-sm !font-medium !text-center !align-middle !transition-colors !duration-300 !px-5 !py-2 !w-[100px] !h-[40px] ",
|
||
};
|
||
/**
|
||
* Shows a modal dialog to save a new filter with a user-provided name.
|
||
*
|
||
* @param {Function} addFilterId - Backend API function to create the filter.
|
||
* @param {Function|null} [next=null] - Vue Router next() guard callback.
|
||
* @returns {Promise<boolean>} True if the filter was saved, false otherwise.
|
||
*/
|
||
export async function saveFilter(addFilterId, next = null) {
|
||
let fileName = "";
|
||
const pageAdminStore = usePageAdminStore();
|
||
|
||
const { value, isConfirmed } = await Swal.fire({
|
||
title: "SAVE NEW FILTER",
|
||
input: "text",
|
||
inputPlaceholder: "Enter Filter Name.",
|
||
inputValue: fileName,
|
||
inputAttributes: {
|
||
maxlength: 200,
|
||
},
|
||
inputValidator: (value) => {
|
||
if (!value) return "You need to write something!";
|
||
fileName = value;
|
||
},
|
||
icon: "info",
|
||
iconHtml:
|
||
'<span class="material-symbols-outlined !text-[58px]">cloud_upload</span>',
|
||
iconColor: "#0099FF",
|
||
reverseButtons: true,
|
||
confirmButtonColor: "#0099FF",
|
||
showCancelButton: true,
|
||
cancelButtonColor: "#94a3b8",
|
||
customClass: customClass,
|
||
});
|
||
// Determine whether to redirect based on the return value
|
||
if (isConfirmed) {
|
||
// Save succeeded
|
||
await addFilterId(fileName);
|
||
// Show save complete notification
|
||
if (value) {
|
||
// Example of value: yes
|
||
savedSuccessfully(value);
|
||
}
|
||
// Clear the input field
|
||
fileName = "";
|
||
return true;
|
||
} else {
|
||
// Clicked cancel or outside the dialog; save failed.
|
||
pageAdminStore.keepPreviousPage();
|
||
|
||
// Not every time we have nontrivial next value
|
||
if (next !== null) {
|
||
next(false);
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* Shows a timed success notification after a file has been saved.
|
||
*
|
||
* @param {string} value - The name of the saved file.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function savedSuccessfully(value = "") {
|
||
await Swal.fire({
|
||
title: "SAVE COMPLETE",
|
||
html: `<span class="text-primary">${escapeHtml(value)}</span> has been saved in Lucia.`,
|
||
timer: 3000, // Auto-close after 3 seconds
|
||
showConfirmButton: false,
|
||
icon: "success",
|
||
iconColor: "#0099FF",
|
||
customClass: customClass,
|
||
});
|
||
}
|
||
/**
|
||
* Prompts the user to save unsaved filter changes before leaving the
|
||
* Map page. Handles confirm (save), cancel (discard), and backdrop
|
||
* (stay) scenarios.
|
||
*
|
||
* @param {Function} next - Vue Router next() guard callback.
|
||
* @param {Function} addFilterId - Backend API function to create the filter.
|
||
* @param {string} toPath - The destination route path.
|
||
* @param {Function} [logOut] - Optional logout function to call instead
|
||
* of navigating.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function leaveFilter(next, addFilterId, toPath, logOut) {
|
||
const allMapDataStore = useAllMapDataStore();
|
||
const pageAdminStore = usePageAdminStore();
|
||
|
||
const result = await Swal.fire({
|
||
title: "SAVE YOUR FILTER?",
|
||
html: "If you want to continue using this filter in any other page, please select [Yes].",
|
||
icon: "warning",
|
||
iconColor: "#FF3366",
|
||
reverseButtons: true,
|
||
confirmButtonText: "Yes",
|
||
confirmButtonColor: "#FF3366",
|
||
showCancelButton: true,
|
||
cancelButtonText: "No",
|
||
cancelButtonColor: "#94a3b8",
|
||
customClass: customClass,
|
||
});
|
||
|
||
if (result.isConfirmed) {
|
||
if (allMapDataStore.createFilterId) {
|
||
await allMapDataStore.updateFilter();
|
||
if (allMapDataStore.isUpdateFilter) {
|
||
await savedSuccessfully(allMapDataStore.filterName);
|
||
}
|
||
} else {
|
||
// Dangerous, here shows a modal
|
||
await saveFilter(addFilterId, next);
|
||
}
|
||
|
||
logOut ? logOut() : next(toPath);
|
||
} else if (result.dismiss === "cancel") {
|
||
// console.log('popup cancel case', );
|
||
// Handle page admin issue
|
||
// console.log("usePageAdminStore.activePage", usePageAdminStore.activePage);
|
||
pageAdminStore.keepPreviousPage();
|
||
|
||
allMapDataStore.tempFilterId = null;
|
||
logOut ? logOut() : next(toPath);
|
||
} else if (result.dismiss === "backdrop") {
|
||
// console.log('popup backdrop case', );
|
||
// Handle page admin issue
|
||
// console.log("usePageAdminStore.activePage", usePageAdminStore.activePage);
|
||
pageAdminStore.keepPreviousPage();
|
||
|
||
if (!logOut) {
|
||
next(false);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Shows a modal dialog to save a new conformance rule with a
|
||
* user-provided name.
|
||
*
|
||
* @param {Function} addConformanceCreateCheckId - Backend API function
|
||
* to create the conformance check.
|
||
* @returns {Promise<boolean>} True if the rule was saved, false otherwise.
|
||
*/
|
||
export async function saveConformance(addConformanceCreateCheckId) {
|
||
let fileName = "";
|
||
const { value, isConfirmed } = await Swal.fire({
|
||
title: "SAVE NEW RULE",
|
||
input: "text",
|
||
inputPlaceholder: "Enter Rule Name.",
|
||
inputValue: fileName,
|
||
inputAttributes: {
|
||
maxlength: 200,
|
||
},
|
||
inputValidator: (value) => {
|
||
if (!value) return "You need to write something!";
|
||
fileName = value;
|
||
},
|
||
icon: "info",
|
||
iconHtml:
|
||
'<span class="material-symbols-outlined !text-[58px]">cloud_upload</span>',
|
||
iconColor: "#0099FF",
|
||
reverseButtons: true,
|
||
confirmButtonColor: "#0099FF",
|
||
showCancelButton: true,
|
||
cancelButtonColor: "#94a3b8",
|
||
customClass: customClass,
|
||
});
|
||
// Determine whether to redirect based on the return value
|
||
if (isConfirmed) {
|
||
// Save succeeded
|
||
await addConformanceCreateCheckId(fileName);
|
||
// Show save complete notification
|
||
if (value) savedSuccessfully(value);
|
||
// Clear the input field
|
||
fileName = "";
|
||
return true;
|
||
} else {
|
||
// Clicked cancel or outside the dialog; save failed.
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* Prompts the user to save unsaved conformance rule changes before
|
||
* leaving the Conformance page. Delegates to helper functions for
|
||
* confirm, cancel, and backdrop scenarios.
|
||
*
|
||
* @param {Function} next - Vue Router next() guard callback.
|
||
* @param {Function} addConformanceCreateCheckId - Backend API function
|
||
* to create the conformance check.
|
||
* @param {string} toPath - The destination route path.
|
||
* @param {Function} [logOut] - Optional logout function.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function leaveConformance(
|
||
next,
|
||
addConformanceCreateCheckId,
|
||
toPath,
|
||
logOut,
|
||
) {
|
||
const conformanceStore = useConformanceStore();
|
||
const result = await showConfirmationDialog();
|
||
|
||
if (result.isConfirmed) {
|
||
await handleConfirmed(conformanceStore, addConformanceCreateCheckId);
|
||
} else {
|
||
await handleDismiss(result.dismiss, conformanceStore, next, toPath, logOut);
|
||
}
|
||
}
|
||
/**
|
||
* Displays the "SAVE YOUR RULE?" confirmation dialog.
|
||
* @returns {Promise<Object>} The SweetAlert2 result object.
|
||
*/
|
||
async function showConfirmationDialog() {
|
||
return Swal.fire({
|
||
title: "SAVE YOUR RULE?",
|
||
icon: "warning",
|
||
iconColor: "#FF3366",
|
||
reverseButtons: true,
|
||
confirmButtonText: "Yes",
|
||
confirmButtonColor: "#FF3366",
|
||
showCancelButton: true,
|
||
cancelButtonText: "No",
|
||
cancelButtonColor: "#94a3b8",
|
||
customClass: customClass,
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Handles the confirmed save action for conformance rules.
|
||
* @param {Object} conformanceStore - The conformance Pinia store.
|
||
* @param {Function} addConformanceCreateCheckId - API function to create check.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
async function handleConfirmed(conformanceStore, addConformanceCreateCheckId) {
|
||
if (
|
||
conformanceStore.conformanceFilterCreateCheckId ||
|
||
conformanceStore.conformanceLogCreateCheckId
|
||
) {
|
||
await conformanceStore.updateConformance();
|
||
if (conformanceStore.isUpdateConformance) {
|
||
await savedSuccessfully(conformanceStore.conformanceFileName);
|
||
}
|
||
} else {
|
||
await saveConformance(addConformanceCreateCheckId);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Handles dismiss actions (cancel or backdrop click) for conformance modals.
|
||
* @param {string} dismissType - The SweetAlert2 dismiss reason.
|
||
* @param {Object} conformanceStore - The conformance Pinia store.
|
||
* @param {Function} next - Vue Router next() guard callback.
|
||
* @param {string} toPath - The destination route path.
|
||
* @param {Function} [logOut] - Optional logout function.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
async function handleDismiss(
|
||
dismissType,
|
||
conformanceStore,
|
||
next,
|
||
toPath,
|
||
logOut,
|
||
) {
|
||
switch (dismissType) {
|
||
case "cancel":
|
||
resetTempCheckId(conformanceStore);
|
||
logOut ? logOut() : next(toPath);
|
||
break;
|
||
case "backdrop":
|
||
if (!logOut) {
|
||
next(false);
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Resets temporary conformance check IDs to null.
|
||
* @param {Object} conformanceStore - The conformance Pinia store.
|
||
*/
|
||
function resetTempCheckId(conformanceStore) {
|
||
conformanceStore.conformanceFilterTempCheckId = null;
|
||
conformanceStore.conformanceLogTempCheckId = null;
|
||
}
|
||
|
||
/**
|
||
* Shows an error modal for the first stage of upload validation failure.
|
||
*
|
||
* @param {string} failureType - The error type from the backend (e.g.
|
||
* "encoding", "insufficient_columns", "empty", "name_suffix", "mime_type").
|
||
* @param {string} failureMsg - The raw error message from the backend.
|
||
* @param {number} [failureLoc] - The row number where the error occurred.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function uploadFailedFirst(failureType, failureMsg, failureLoc) {
|
||
// msg: 'not in UTF-8' | 'insufficient columns' | 'the csv file is empty' | 'the filename does not ends with .csv' | 'not a CSV file'
|
||
// type: 'encoding' | 'insufficient_columns' | 'empty' | 'name_suffix' | mime_type
|
||
let value = "";
|
||
switch (failureType) {
|
||
case "size":
|
||
value = "File size exceeds 90MB.";
|
||
break;
|
||
case "encoding":
|
||
value = `Please use UTF-8 for character encoding: (Row #${failureLoc})`;
|
||
break;
|
||
case "insufficient_columns":
|
||
value = "Need at least five columns of data.";
|
||
break;
|
||
case "empty":
|
||
value = "Need at least one record of data.";
|
||
break;
|
||
case "name_suffix":
|
||
case "mime_type":
|
||
value = "File is not in csv format.";
|
||
break;
|
||
default:
|
||
value = escapeHtml(failureMsg);
|
||
break;
|
||
}
|
||
await Swal.fire({
|
||
title: "IMPORT FAILED",
|
||
html: value,
|
||
timer: 3000, // Auto-close after 3 seconds
|
||
showConfirmButton: false,
|
||
icon: "error",
|
||
iconColor: "#FF3366",
|
||
customClass: customClass,
|
||
});
|
||
}
|
||
/**
|
||
* Shows an error modal for the second stage of upload validation failure,
|
||
* listing individual row-level errors.
|
||
*
|
||
* @param {Array<{type: string, loc: Array, input: string}>} detail -
|
||
* Array of error detail objects from the backend.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function uploadFailedSecond(detail) {
|
||
let srt = "";
|
||
let manySrt = "";
|
||
|
||
detail.forEach((i) => {
|
||
let content = "";
|
||
let key = "";
|
||
switch (i.type) {
|
||
case "too_many":
|
||
manySrt = "There are more errors.";
|
||
break;
|
||
case "unrecognized":
|
||
content = `<li>Data unrecognizable in Status Column: (Row #${i.loc?.[1] ?? "?"}, "${escapeHtml(i.input)}")</li>`;
|
||
break;
|
||
case "malformed":
|
||
content = `<li>Data malformed in Timestamp Column: (Row #${i.loc?.[1] ?? "?"}, "${escapeHtml(i.input)}")</li>`;
|
||
break;
|
||
case "missing":
|
||
switch (i.loc?.[2]) {
|
||
case "case id":
|
||
key = "Case ID";
|
||
break;
|
||
case "timestamp":
|
||
key = "Timestamp";
|
||
break;
|
||
case "name":
|
||
key = "Activity";
|
||
break;
|
||
case "instance":
|
||
key = "Activity Instance ID";
|
||
break;
|
||
case "status":
|
||
key = "Status";
|
||
break;
|
||
default:
|
||
key = escapeHtml(String(i.loc?.[2] ?? ""));
|
||
break;
|
||
}
|
||
content = `<li>Data missing in ${key} Column: (Row #${i.loc?.[1] ?? "?"})</li>`;
|
||
break;
|
||
}
|
||
srt += content;
|
||
});
|
||
await Swal.fire({
|
||
title: "IMPORT FAILED",
|
||
html: `<div class="text-left mx-3 space-y-1"><p>Error(s) detected:</p><ul class="list-disc ml-6">${srt}</ul><p>${manySrt} Please check.</p></div>`,
|
||
timer: 3000, // Auto-close after 3 seconds
|
||
showConfirmButton: false,
|
||
icon: "error",
|
||
iconColor: "#FF3366",
|
||
customClass: customClass,
|
||
});
|
||
srt = "";
|
||
}
|
||
/**
|
||
* Shows a timed success notification after a file upload completes.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function uploadSuccess() {
|
||
await Swal.fire({
|
||
title: "IMPORT COMPLETED",
|
||
timer: 3000, // Auto-close after 3 seconds
|
||
showConfirmButton: false,
|
||
icon: "success",
|
||
iconColor: "#0099FF",
|
||
customClass: customClass,
|
||
});
|
||
}
|
||
/**
|
||
* Shows a confirmation dialog before uploading a file. Proceeds with
|
||
* the upload only if the user confirms.
|
||
*
|
||
* @param {Object} fetchData - The upload request payload for the backend.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function uploadConfirm(fetchData) {
|
||
const filesStore = useFilesStore();
|
||
const result = await Swal.fire({
|
||
title: "ARE YOU SURE?",
|
||
html: "After importing, you won’t be able to modify labels.",
|
||
icon: "warning",
|
||
iconColor: "#FF3366",
|
||
reverseButtons: true,
|
||
confirmButtonText: "Yes",
|
||
confirmButtonColor: "#FF3366",
|
||
showCancelButton: true,
|
||
cancelButtonText: "No",
|
||
cancelButtonColor: "#94a3b8",
|
||
customClass: customClass,
|
||
});
|
||
|
||
if (result.isConfirmed) {
|
||
filesStore.uploadLog(fetchData);
|
||
}
|
||
}
|
||
/**
|
||
* Shows a non-dismissable loading spinner during file upload.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function uploadloader() {
|
||
await Swal.fire({
|
||
html: '<span class="loaderBar mt-7"></span>',
|
||
showConfirmButton: false,
|
||
allowOutsideClick: false,
|
||
customClass: customClass,
|
||
});
|
||
}
|
||
/**
|
||
* Shows a modal dialog for renaming a file.
|
||
*
|
||
* @param {Function} rename - Backend API function to rename the file.
|
||
* @param {string} type - The file type (e.g. "log", "filter").
|
||
* @param {number} id - The file ID.
|
||
* @param {string} baseName - The current file name.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function renameModal(rename, type, id, baseName) {
|
||
const fileName = baseName;
|
||
const { value, isConfirmed } = await Swal.fire({
|
||
title: "RENAME",
|
||
input: "text",
|
||
inputPlaceholder: "Enter File Name.",
|
||
inputValue: fileName,
|
||
inputAttributes: {
|
||
maxlength: 200,
|
||
},
|
||
icon: "info",
|
||
iconHtml:
|
||
'<span class="material-symbols-outlined !text-[58px]">edit_square</span>',
|
||
iconColor: "#0099FF",
|
||
reverseButtons: true,
|
||
confirmButtonColor: "#0099FF",
|
||
showCancelButton: true,
|
||
cancelButtonColor: "#94a3b8",
|
||
customClass: customClass,
|
||
didOpen: () => {
|
||
const confirmButton = Swal.getConfirmButton();
|
||
const inputField = Swal.getInput();
|
||
|
||
inputField.addEventListener("input", function () {
|
||
if (!inputField.value.trim()) {
|
||
confirmButton.classList.add("disable-hover");
|
||
confirmButton.disabled = true;
|
||
} else {
|
||
confirmButton.classList.remove("disable-hover");
|
||
confirmButton.disabled = false;
|
||
}
|
||
});
|
||
},
|
||
});
|
||
|
||
// Rename succeeded
|
||
if (isConfirmed) await rename(type, id, value);
|
||
// Clear the field: fileName = ''
|
||
}
|
||
/**
|
||
* Shows a confirmation dialog for deleting a file and its dependents.
|
||
*
|
||
* @param {string} files - HTML string listing dependent files to be deleted.
|
||
* @param {string} type - The file type (e.g. "log", "filter").
|
||
* @param {number} id - The file ID.
|
||
* @param {string} name - The file name.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function deleteFileModal(files, type, id, name) {
|
||
const filesStore = useFilesStore();
|
||
|
||
const safeName = escapeHtml(name);
|
||
const htmlText =
|
||
files.length === 0
|
||
? `Do you really want to delete <span class="text-primary">${safeName}</span>?`
|
||
: `<div class="text-left mx-4 space-y-1"><p class="mb-2">Do you really want to delete <span class="text-primary">${safeName}</span>?</p><p>The following dependent file(s) will also be deleted:</p><ul class="list-disc ml-6">${files}</ul></div>`;
|
||
|
||
const deleteCustomClass = { ...customClass };
|
||
deleteCustomClass.confirmButton =
|
||
"!inline-block !rounded-full !text-sm !font-medium !text-center !align-middle !transition-colors !duration-300 !px-5 !py-2 !w-[100px] !h-[40px] !text-danger !bg-neutral-10 !border !border-danger";
|
||
|
||
const result = await Swal.fire({
|
||
title: "CONFIRM DELETION",
|
||
html: htmlText,
|
||
icon: "warning",
|
||
iconColor: "#FF3366",
|
||
reverseButtons: true,
|
||
confirmButtonColor: "#ffffff",
|
||
showCancelButton: true,
|
||
cancelButtonColor: "#FF3366",
|
||
customClass: deleteCustomClass,
|
||
didOpen: () => {
|
||
const confirmButton = Swal.getConfirmButton();
|
||
|
||
confirmButton.style.border = "1px solid #FF3366";
|
||
},
|
||
});
|
||
|
||
if (result.isConfirmed) {
|
||
filesStore.deleteFile(type, id);
|
||
}
|
||
}
|
||
/**
|
||
* Shows a timed success notification after file deletion.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function deleteSuccess() {
|
||
await Swal.fire({
|
||
title: "FILE(S) DELETED",
|
||
timer: 3000, // Auto-close after 3 seconds
|
||
showConfirmButton: false,
|
||
icon: "success",
|
||
iconColor: "#0099FF",
|
||
customClass: customClass,
|
||
});
|
||
}
|
||
/**
|
||
* Shows an informational modal about files deleted by other users,
|
||
* then records the deletions and refreshes the file list.
|
||
*
|
||
* @param {string} files - HTML string listing the deleted files.
|
||
* @param {Array<{id: number}>} reallyDeleteData - Array of deleted file
|
||
* objects with their IDs.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function reallyDeleteInformation(files, reallyDeleteData) {
|
||
const filesStore = useFilesStore();
|
||
const deleteCustomClass = { ...customClass };
|
||
const htmlText = `<div class="text-left mx-4 space-y-1"><p>The following file(s) have been deleted by other user(s):</p><ul class="list-disc ml-6">${files}</ul></div>`;
|
||
|
||
deleteCustomClass.confirmButton =
|
||
"!inline-block !rounded-full !text-sm !font-medium !text-center !align-middle !transition-colors !duration-300 !px-5 !py-2 !w-[100px] !h-[40px] !text-primary !bg-neutral-10 !border !border-primary";
|
||
deleteCustomClass.cancelButton = null;
|
||
await Swal.fire({
|
||
title: "FILE(S) DELETED BY OTHER USER(S)",
|
||
html: htmlText,
|
||
icon: "info",
|
||
iconColor: "#0099FF",
|
||
customClass: deleteCustomClass,
|
||
confirmButtonColor: "#ffffff",
|
||
didOpen: () => {
|
||
const confirmButton = Swal.getConfirmButton();
|
||
|
||
confirmButton.style.border = "1px solid #0099FF";
|
||
},
|
||
});
|
||
await Promise.all(
|
||
reallyDeleteData.map((file) => filesStore.deletionRecord(file.id)),
|
||
);
|
||
await filesStore.fetchAllFiles();
|
||
}
|
||
|
||
/**
|
||
* Shows a reminder modal when the user leaves the account management
|
||
* page with unsaved edits.
|
||
* @returns {Promise<void>}
|
||
*/
|
||
export async function leaveAccountManagementToRemind() {
|
||
const modalStore = useModalStore();
|
||
const result = await Swal.fire({
|
||
title: "SAVE YOUR EDIT?",
|
||
icon: "info",
|
||
showCancelButton: true,
|
||
didOpen: () => {
|
||
const confirmButton = Swal.getConfirmButton();
|
||
|
||
confirmButton.style.border = "1px solid #0099FF";
|
||
},
|
||
});
|
||
if (result.isConfirmed) {
|
||
return;
|
||
} else {
|
||
modalStore.openModal();
|
||
}
|
||
}
|