Files
qb-core-qbcore-fivem/html/js/app.js
T
Kakarot 74ac036d4a Refactor core structure and notification UI
Major refactor to centralize QBCore and replace the web UI stack:

- Move QBConfig into a unified QBCore.Config and initialize QBCore.PlayerData; add shared/functions.lua.
- Remove legacy client/main.lua and server/main.lua exports and update fxmanifest to include shared/functions and drop those main scripts.
- Update client event handling: adapt player data events to QBCore:Client:OnPlayerUpdated and trigger job/gang update events; remove deprecated exploitable handlers.
- Web UI rewrite: remove Vue/Quasar dependency and config module; implement a lightweight vanilla JS notification system (html/js/app.js) with updated html and css assets, plus drawtext tweaks.
- CSS overhaul for notification styling and animations; HTML head/meta/font cleanup.
- Server fixes: use QBCore.Shared.Locations in teleport command and strengthen numeric parsing/validation for coords.
- Add .gitattributes for consistent text/eol handling and small code cleanups (thread/comment removal).

These changes consolidate config/shared logic under QBCore, simplify the frontend dependencies, and fix a few parsing/event issues.
2026-05-19 18:13:35 -05:00

121 lines
4.1 KiB
JavaScript

let NOTIFY_CONFIG = null;
const defaultConfig = {
NotificationStyling: {
group: false,
position: "right",
progress: true,
},
VariantDefinitions: {
success: { classes: "success", icon: "check_circle" },
primary: { classes: "primary", icon: "notifications" },
warning: { classes: "warning", icon: "warning" },
error: { classes: "error", icon: "error" },
police: { classes: "police", icon: "local_police" },
ambulance: { classes: "ambulance", icon: "fas fa-ambulance" },
},
};
const fetchNui = async (evName, data) => {
const resourceName = window.GetParentResourceName();
const resp = await fetch(`https://${resourceName}/${evName}`, {
body: JSON.stringify(data),
headers: { "Content-Type": "application/json; charset=UTF8" },
method: "POST",
});
return resp.json();
};
const determineStyleFromVariant = (variant) => {
return NOTIFY_CONFIG.VariantDefinitions[variant] ?? NOTIFY_CONFIG.VariantDefinitions["primary"];
};
const fetchNotifyConfig = async () => {
try {
NOTIFY_CONFIG = await fetchNui("getNotifyConfig", {});
if (!NOTIFY_CONFIG) NOTIFY_CONFIG = defaultConfig;
} catch (error) {
console.error("Failed to fetch notification config, using default", error);
NOTIFY_CONFIG = defaultConfig;
}
};
const POSITION_MAP = {
"top-left": { top: "16px", left: "16px" },
"top-right": { top: "16px", right: "16px" },
top: { top: "16px", left: "50%", transform: "translateX(-50%)" },
"bottom-left": { bottom: "16px", left: "16px" },
"bottom-right": { bottom: "16px", right: "16px" },
bottom: { bottom: "16px", left: "50%", transform: "translateX(-50%)" },
left: { top: "50%", left: "16px", transform: "translateY(-50%)" },
right: { top: "50%", right: "16px", transform: "translateY(-50%)" },
center: { top: "50%", left: "50%", transform: "translate(-50%, -50%)" },
};
let container = null;
const getContainer = () => {
if (container) return container;
const pos = NOTIFY_CONFIG.NotificationStyling.position ?? "right";
const isBottom = pos.startsWith("bottom");
container = document.createElement("div");
container.id = "notify-container";
Object.assign(container.style, {
position: "fixed",
display: "flex",
flexDirection: isBottom ? "column-reverse" : "column",
gap: "8px",
zIndex: "9999",
maxWidth: "400px",
pointerEvents: "none",
...(POSITION_MAP[pos] ?? POSITION_MAP["right"]),
});
document.body.appendChild(container);
return container;
};
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const showNotif = async ({ data }) => {
if (data?.action !== "notify") return;
if (!NOTIFY_CONFIG) {
await fetchNotifyConfig();
}
const { text: message, type, length: duration = 5000, caption } = data;
const style = determineStyleFromVariant(type ?? "primary");
const showProgress = NOTIFY_CONFIG.NotificationStyling.progress && duration > 0;
const isFa = style.icon.startsWith("fa");
const iconHtml = isFa ? `<i class="notify-icon ${style.icon}"></i>` : `<span class="notify-icon material-icons">${style.icon}</span>`;
const item = document.createElement("div");
item.className = `notify-item ${style.classes}`;
item.innerHTML = `
${iconHtml}
<div class="notify-content">
<div class="notify-message${!caption ? " notify-multiline" : ""}">${message}</div>
${caption ? `<div class="notify-caption">${caption}</div>` : ""}
</div>
${showProgress ? `<div class="notify-progress" style="animation-duration:${duration}ms"></div>` : ""}
`;
const c = getContainer();
c.appendChild(item);
await sleep(10);
item.classList.add("notify-show");
if (duration > 0) {
setTimeout(() => {
item.classList.remove("notify-show");
item.classList.add("notify-hide");
setTimeout(() => item.remove(), 350);
}, duration);
}
};
window.addEventListener("message", showNotif);
window.addEventListener("load", fetchNotifyConfig);