mirror of
https://github.com/qbcore-fivem/qb-core.git
synced 2026-09-04 09:13:29 +00:00
Merge pull request #265 from TasoOneAsia/refactor/notify-nui
refactor(ui/notify): Cleanup internal code & new config options
This commit is contained in:
@@ -58,6 +58,10 @@ function QBCore.Functions.DrawText3D(x, y, z, text) -- Use local function instea
|
|||||||
ClearDrawOrigin()
|
ClearDrawOrigin()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
RegisterNUICallback('getNotifyConfig', function(_, cb)
|
||||||
|
cb(QBCore.Config.Notify)
|
||||||
|
end)
|
||||||
|
|
||||||
function QBCore.Functions.Notify(text, textype, length)
|
function QBCore.Functions.Notify(text, textype, length)
|
||||||
if type(text) == "table" then
|
if type(text) == "table" then
|
||||||
local ttext = text.text or 'Placeholder'
|
local ttext = text.text or 'Placeholder'
|
||||||
@@ -65,6 +69,7 @@ function QBCore.Functions.Notify(text, textype, length)
|
|||||||
local ttype = textype or 'primary'
|
local ttype = textype or 'primary'
|
||||||
local length = length or 5000
|
local length = length or 5000
|
||||||
SendNUIMessage({
|
SendNUIMessage({
|
||||||
|
action = 'notify',
|
||||||
type = ttype,
|
type = ttype,
|
||||||
length = length,
|
length = length,
|
||||||
text = ttext,
|
text = ttext,
|
||||||
@@ -73,7 +78,8 @@ function QBCore.Functions.Notify(text, textype, length)
|
|||||||
else
|
else
|
||||||
local ttype = textype or 'primary'
|
local ttype = textype or 'primary'
|
||||||
local length = length or 5000
|
local length = length or 5000
|
||||||
SendNUIMessage({
|
SendNUIMessage({
|
||||||
|
action = 'notify',
|
||||||
type = ttype,
|
type = ttype,
|
||||||
length = length,
|
length = length,
|
||||||
text = text
|
text = text
|
||||||
|
|||||||
+34
@@ -33,3 +33,37 @@ QBConfig.Server.uptime = 0 -- Time the server has been up.
|
|||||||
QBConfig.Server.whitelist = false -- Enable or disable whitelist on the server
|
QBConfig.Server.whitelist = false -- Enable or disable whitelist on the server
|
||||||
QBConfig.Server.discord = "" -- Discord invite link
|
QBConfig.Server.discord = "" -- Discord invite link
|
||||||
QBConfig.Server.PermissionList = {} -- permission list
|
QBConfig.Server.PermissionList = {} -- permission list
|
||||||
|
|
||||||
|
QBConfig.Notify = {}
|
||||||
|
|
||||||
|
QBConfig.Notify.NotificationStyling = {
|
||||||
|
group = false, -- Allow notifications to stack with a badge instead of repeating
|
||||||
|
position = "right", -- top-left | top-right | bottom-left | bottom-right | top | bottom | left | right | center
|
||||||
|
progress = true -- Display Progress Bar
|
||||||
|
}
|
||||||
|
|
||||||
|
-- These are how you define different notification variants
|
||||||
|
-- The "color" key is background of the notification
|
||||||
|
-- The "icon" key is the css-icon code, this project uses `Material Icons` & `Font Awesome`
|
||||||
|
QBconfig.Notify.VariantDefinitions = {
|
||||||
|
success = {
|
||||||
|
color = 'green',
|
||||||
|
icon = 'done'
|
||||||
|
},
|
||||||
|
primary = {
|
||||||
|
color = 'blue',
|
||||||
|
icon = 'info'
|
||||||
|
},
|
||||||
|
error = {
|
||||||
|
color = 'red',
|
||||||
|
icon = 'dangerous'
|
||||||
|
},
|
||||||
|
police = {
|
||||||
|
color = 'blue',
|
||||||
|
icon = 'local_police'
|
||||||
|
},
|
||||||
|
ambulance = {
|
||||||
|
color = 'red',
|
||||||
|
icon = 'fas fa-ambulance'
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
-41
@@ -1,63 +1,66 @@
|
|||||||
|
import { registerWindowMethods } from "./testing.js";
|
||||||
|
import { isEnvBrowser } from "./utils.js";
|
||||||
|
import {
|
||||||
|
determineStyleFromVariant,
|
||||||
|
DEV_MODE,
|
||||||
|
fetchNotifyConfig,
|
||||||
|
NOTIFY_CONFIG,
|
||||||
|
} from "./config.js";
|
||||||
|
|
||||||
const { useQuasar } = Quasar;
|
const { useQuasar } = Quasar;
|
||||||
|
|
||||||
const { onMounted, onUnmounted } = Vue;
|
const { onMounted, onUnmounted } = Vue;
|
||||||
|
|
||||||
const app = Vue.createApp({
|
const app = Vue.createApp({
|
||||||
setup() {
|
setup() {
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
const showNotif = (e) => {
|
|
||||||
const text = e.data.text;
|
|
||||||
const length = e.data.length;
|
|
||||||
const type = e.data.type;
|
|
||||||
const caption = e.data.caption;
|
|
||||||
|
|
||||||
switch (type) {
|
const showNotif = async ({ data }) => {
|
||||||
case 'success':
|
// Otherwise we process any old MessageEvent with a data property
|
||||||
color = 'green';
|
if (data?.action !== "notify") return;
|
||||||
icon = 'done';
|
|
||||||
break;
|
|
||||||
case 'primary':
|
|
||||||
color = 'blue';
|
|
||||||
icon = 'info';
|
|
||||||
break;
|
|
||||||
case 'error':
|
|
||||||
color = 'red';
|
|
||||||
icon = 'dangerous';
|
|
||||||
break;
|
|
||||||
case 'police':
|
|
||||||
color = 'blue';
|
|
||||||
icon = 'local_police';
|
|
||||||
break;
|
|
||||||
case 'ambulance':
|
|
||||||
color = 'red';
|
|
||||||
icon = 'fas fa-ambulance';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text.length > 100) {
|
const { text, length, type, caption } = data;
|
||||||
multiline = true;
|
const { color, icon } = determineStyleFromVariant(type);
|
||||||
} else {
|
|
||||||
multiline = false;
|
// Make sure we have sucessfully fetched out config properly
|
||||||
|
if (!NOTIFY_CONFIG) {
|
||||||
|
console.error(
|
||||||
|
"The notification config did not load properly, trying again for next time"
|
||||||
|
);
|
||||||
|
// Lets check again to see if it exists
|
||||||
|
await fetchNotifyConfig();
|
||||||
|
// If we have a config lets re-run notification with same data, this
|
||||||
|
// isn't recursive though.
|
||||||
|
if (NOTIFY_CONFIG) return showNotif({ data });
|
||||||
}
|
}
|
||||||
|
|
||||||
$q.notify({
|
$q.notify({
|
||||||
message: text,
|
message: text,
|
||||||
caption: caption,
|
multiLine: text.length > 100,
|
||||||
multiLine: multiline,
|
// If our text is larger than a 100 characters,
|
||||||
color: color,
|
// we should use multiline notifications
|
||||||
group: false,
|
group: NOTIFY_CONFIG.NotificationStyling.group ?? false,
|
||||||
progress: true,
|
progress: NOTIFY_CONFIG.NotificationStyling.progress ?? true,
|
||||||
position: 'right',
|
position: NOTIFY_CONFIG.NotificationStyling.position ?? "right",
|
||||||
timeout: length,
|
timeout: length,
|
||||||
icon: icon,
|
caption,
|
||||||
|
color,
|
||||||
|
icon,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('message', showNotif);
|
window.addEventListener("message", showNotif);
|
||||||
});
|
});
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('message', showNotif);
|
window.removeEventListener("message", showNotif);
|
||||||
});
|
});
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(Quasar, { config: {} });
|
app.use(Quasar, { config: {} });
|
||||||
app.mount('#q-app');
|
app.mount("#q-app");
|
||||||
|
|
||||||
|
if (DEV_MODE || isEnvBrowser()) {
|
||||||
|
registerWindowMethods();
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { fetchNui, isEnvBrowser } from "./utils.js";
|
||||||
|
import { BrowserMockConfigData } from "./testing.js";
|
||||||
|
|
||||||
|
export const DEV_MODE = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef NotiVariantData
|
||||||
|
* @property {string} icon
|
||||||
|
* @property {string} color
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will hold config statically outside of Vue state
|
||||||
|
* @property {Record<string, NotiVariantData>} VariantDefinitions
|
||||||
|
* @property {Record<string, any>} NotificationStyling
|
||||||
|
* */
|
||||||
|
export let NOTIFY_CONFIG = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pure function taking a notification type and returning an object
|
||||||
|
* with style details
|
||||||
|
* @param variant {string}
|
||||||
|
* @returns NotiVariantData
|
||||||
|
**/
|
||||||
|
export const determineStyleFromVariant = (variant) => {
|
||||||
|
const variantData = NOTIFY_CONFIG.VariantDefinitions[variant];
|
||||||
|
if (!variantData)
|
||||||
|
throw new Error(`Style of type: ${variant}, does not exist in the config`);
|
||||||
|
return variantData;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fetch and set NOTIFY_CONFIG from client script callback
|
||||||
|
export const fetchNotifyConfig = async () => {
|
||||||
|
NOTIFY_CONFIG = await fetchNui("getNotifyConfig", {}, BrowserMockConfigData);
|
||||||
|
if (isEnvBrowser() || DEV_MODE) {
|
||||||
|
console.log("Fetched Config:");
|
||||||
|
console.dir(NOTIFY_CONFIG);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// We specifically wait for all other files to load
|
||||||
|
// just in case of a race condition between client handlers
|
||||||
|
// and NUI fetch call
|
||||||
|
window.addEventListener("load", async () => {
|
||||||
|
await fetchNotifyConfig();
|
||||||
|
});
|
||||||
+1
-1
@@ -26,7 +26,7 @@
|
|||||||
src="https://cdn.jsdelivr.net/npm/quasar@2.1.0/dist/quasar.umd.prod.js"
|
src="https://cdn.jsdelivr.net/npm/quasar@2.1.0/dist/quasar.umd.prod.js"
|
||||||
defer
|
defer
|
||||||
></script>
|
></script>
|
||||||
<script src="app.js" defer></script>
|
<script type="module" src="app.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body style="font-family: 'Poppins', sans-serif">
|
<body style="font-family: 'Poppins', sans-serif">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// Will register dev utilities on window
|
||||||
|
export const registerWindowMethods = () => {
|
||||||
|
window.SendNotification = (data) => {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new MessageEvent("message", {
|
||||||
|
data: {
|
||||||
|
action: "notify",
|
||||||
|
...data,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Used for browser env handling
|
||||||
|
export const BrowserMockConfigData = {
|
||||||
|
NotificationStyling: {
|
||||||
|
group: true,
|
||||||
|
position: "top-right",
|
||||||
|
progress: true,
|
||||||
|
},
|
||||||
|
VariantDefinitions: {
|
||||||
|
success: {
|
||||||
|
color: "green",
|
||||||
|
icon: "done",
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
color: "blue",
|
||||||
|
icon: "info",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
color: "red",
|
||||||
|
icon: "dangerous",
|
||||||
|
},
|
||||||
|
police: {
|
||||||
|
color: "blue",
|
||||||
|
icon: "local_police",
|
||||||
|
},
|
||||||
|
ambulance: {
|
||||||
|
color: "red",
|
||||||
|
icon: "fas fa-ambulance",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Returns whether we are in browser or not
|
||||||
|
export const isEnvBrowser = () => !window.invokeNative;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Real simple wrapper around fetch api for NUI focus
|
||||||
|
* it will return the mockData param if we are in browser. So we don't
|
||||||
|
* make a useless request to a hostname that doesn't exist.
|
||||||
|
* @param evName {string} - The callback event name/type
|
||||||
|
* @param data {any} - Optional `body` data JSON stringified & passed with the request
|
||||||
|
* @param mockData {any} - Mock data to return if this is running in browser
|
||||||
|
* @return Promise
|
||||||
|
**/
|
||||||
|
export const fetchNui = async (evName, data, mockData = null) => {
|
||||||
|
if (isEnvBrowser()) return mockData;
|
||||||
|
|
||||||
|
const resourceName = window.GetParentResourceName();
|
||||||
|
|
||||||
|
const rawResp = await fetch(`https://cfx-nui-${resourceName}/${evName}`, {
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json; charset=UTF8",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return await rawResp.json();
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user