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.
This commit is contained in:
Kakarot
2026-05-19 18:13:35 -05:00
parent 891b039bf3
commit 74ac036d4a
28 changed files with 1242 additions and 1419 deletions
+104 -49
View File
@@ -1,65 +1,120 @@
import { determineStyleFromVariant, fetchNotifyConfig, NOTIFY_CONFIG } from "./config.js";
let NOTIFY_CONFIG = null;
const { useQuasar } = Quasar;
const { onMounted, onUnmounted } = Vue;
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 rawResp = await fetch(`https://${resourceName}/${evName}`, {
const resp = await fetch(`https://${resourceName}/${evName}`, {
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json; charset=UTF8",
},
headers: { "Content-Type": "application/json; charset=UTF8" },
method: "POST",
});
return await rawResp.json();
return resp.json();
};
window.fetchNui = fetchNui;
const determineStyleFromVariant = (variant) => {
return NOTIFY_CONFIG.VariantDefinitions[variant] ?? NOTIFY_CONFIG.VariantDefinitions["primary"];
};
const app = Vue.createApp({
setup() {
const $q = useQuasar();
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 showNotif = async ({ data }) => {
if (data?.action !== "notify") return;
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%)" },
};
const { text, length, type, caption, icon: dataIcon } = data;
let { classes, icon } = determineStyleFromVariant(type);
let container = null;
if (dataIcon) {
icon = dataIcon;
}
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;
};
if (!NOTIFY_CONFIG) {
console.error("The notification config did not load properly, trying again for next time");
await fetchNotifyConfig();
if (NOTIFY_CONFIG) return showNotif({ data });
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
$q.notify({
message: text,
multiLine: text.length > 100,
group: NOTIFY_CONFIG.NotificationStyling.group ?? false,
progress: NOTIFY_CONFIG.NotificationStyling.progress ?? true,
position: NOTIFY_CONFIG.NotificationStyling.position ?? "right",
timeout: length,
caption,
classes,
icon,
});
};
onMounted(() => {
window.addEventListener("message", showNotif);
});
onUnmounted(() => {
window.removeEventListener("message", showNotif);
});
return {};
},
});
const showNotif = async ({ data }) => {
if (data?.action !== "notify") return;
app.use(Quasar, { config: {} });
app.mount("#q-app");
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);