mirror of
https://github.com/esx-framework/esx_core.git
synced 2026-08-29 05:02:15 +00:00
refactor(esx_notify): redesign, code improvements.
This commit is contained in:
@@ -3,11 +3,13 @@ local Debug = ESX.GetConfig().EnableDebug
|
||||
---@param notificatonType string the notification type
|
||||
---@param length number the length of the notification
|
||||
---@param message any the message :D
|
||||
local function Notify(notificatonType, length, message)
|
||||
---@param title string optional title for the notification
|
||||
local function Notify(notificatonType, length, message, title)
|
||||
if Debug then
|
||||
print(("1 %s"):format(tostring(notificatonType)))
|
||||
print(("2 %s"):format(tostring(length)))
|
||||
print(("3 %s"):format(message))
|
||||
print("1 ".. tostring(notificatonType))
|
||||
print("2 "..tostring(length))
|
||||
print("3 "..message)
|
||||
print("4 "..tostring(title))
|
||||
end
|
||||
|
||||
if type(notificatonType) ~= "string" then
|
||||
@@ -19,40 +21,49 @@ local function Notify(notificatonType, length, message)
|
||||
end
|
||||
|
||||
if Debug then
|
||||
print(("4 %s"):format(tostring(notificatonType)))
|
||||
print(("5 %s"):format(tostring(length)))
|
||||
print(("6 %s"):format(message))
|
||||
print("5 ".. tostring(notificatonType))
|
||||
print("6 "..tostring(length))
|
||||
print("7 "..message)
|
||||
print("8 "..tostring(title))
|
||||
end
|
||||
|
||||
if type(message) == "string" then
|
||||
message = message:gsub("~br~", "<br>")
|
||||
end
|
||||
|
||||
SendNuiMessage(json.encode({
|
||||
type = notificatonType,
|
||||
length = length,
|
||||
type = notificatonType or "info",
|
||||
length = length or 5000,
|
||||
message = message or "ESX-Notify",
|
||||
title = title or "New Notification"
|
||||
}))
|
||||
end
|
||||
|
||||
exports("Notify", Notify)
|
||||
ESX.SecureNetEvent("ESX:Notify", Notify)
|
||||
exports('Notify', Notify)
|
||||
RegisterNetEvent("ESX:Notify", Notify)
|
||||
|
||||
if Debug then
|
||||
RegisterCommand("oldnotify", function()
|
||||
---@diagnostic disable-next-line
|
||||
ESX.ShowNotification("No Waypoint Set.", true, false, 140)
|
||||
end, false)
|
||||
ESX.ShowNotification('No Waypoint Set.', true, false, 140)
|
||||
end)
|
||||
|
||||
RegisterCommand("notify", function()
|
||||
ESX.ShowNotification("You Recived ~br~ 1x ball~s~!", "success", 3000)
|
||||
end, false)
|
||||
ESX.ShowNotification("You Received <br>1x ball!", "success", 3000)
|
||||
end)
|
||||
|
||||
RegisterCommand("notify1", function()
|
||||
ESX.ShowNotification("Well ~g~Done~s~!", "success", 3000)
|
||||
end, false)
|
||||
ESX.ShowNotification("Well ~g~Done~s~!", "success", 3000, "Achievement")
|
||||
end)
|
||||
|
||||
RegisterCommand("notify2", function()
|
||||
ESX.ShowNotification("Information Recived", "info", 3000)
|
||||
end, false)
|
||||
ESX.ShowNotification("Information Received", "info", 3000, "System Info")
|
||||
end)
|
||||
|
||||
RegisterCommand("notify3", function()
|
||||
ESX.ShowNotification("You Did something ~r~WRONG~s~!", "error", 3000)
|
||||
end, false)
|
||||
ESX.ShowNotification("You Did something ~r~WRONG~s~!", "error", 3000, "Error")
|
||||
end)
|
||||
|
||||
RegisterCommand("notify4", function()
|
||||
ESX.ShowNotification("You Did something ~r~WRONG~s~!", "warning", 3000, "~y~Warning~s~")
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -2,7 +2,7 @@ fx_version 'adamant'
|
||||
|
||||
lua54 'yes'
|
||||
game 'gta5'
|
||||
version '1.12.4'
|
||||
version '1.13.0'
|
||||
author 'ESX-Framework'
|
||||
description 'A beautiful and simple NUI notification system for ESX'
|
||||
|
||||
|
||||
@@ -1,87 +1,196 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@100&family=Poppins:wght@300;400;500;600;800&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap");
|
||||
|
||||
:root {
|
||||
--color: #919191;
|
||||
--background: rgba(20, 20, 20, 0.9);
|
||||
--text-color: #ffffff;
|
||||
--text-secondary: rgba(255, 255, 255, 0.7);
|
||||
--success-color: #2ecc71;
|
||||
--success-border: #1f9c4d;
|
||||
--error-color: #e74c3c;
|
||||
--error-border: #c0392b;
|
||||
--info-color: #3498db;
|
||||
--info-border: #2980b9;
|
||||
--warning-color: #f39c12;
|
||||
--warning-border: #d35400;
|
||||
}
|
||||
|
||||
* {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
color: var(--color);
|
||||
font-weight: 100;
|
||||
font-family: "Poppins", sans-serif;
|
||||
overflow: hidden;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
font-family: "Poppins", sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#root {
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
right: 2rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 10px;
|
||||
max-height: 80vh;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#root .notify {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex: auto;
|
||||
min-width: 20rem;
|
||||
width: fit-content;
|
||||
height: 3.5rem;
|
||||
background: rgba(15, 15, 15, 0.9);
|
||||
border-radius: 0.4rem;
|
||||
margin-top: 0.5rem;
|
||||
animation: anim 300ms ease-in-out;
|
||||
align-items: center;
|
||||
.notify {
|
||||
display: flex;
|
||||
width: 300px;
|
||||
background: var(--background);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
position: relative;
|
||||
padding: 12px;
|
||||
align-items: center;
|
||||
border-left: 4px solid #555;
|
||||
}
|
||||
|
||||
#root .innerText {
|
||||
padding-left: 0.4rem;
|
||||
padding-right: 0.4rem;
|
||||
width: 100%;
|
||||
.notify-success {
|
||||
border-left-color: var(--success-border);
|
||||
}
|
||||
|
||||
#root .icon {
|
||||
float: left;
|
||||
.notify-error {
|
||||
border-left-color: var(--error-border);
|
||||
}
|
||||
|
||||
#root .text {
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
margin-left: 0.5rem;
|
||||
.notify-info {
|
||||
border-left-color: var(--info-border);
|
||||
}
|
||||
|
||||
#root .error {
|
||||
color: #c0392b;
|
||||
border: 2px solid #c0392b;
|
||||
.notify-warning {
|
||||
border-left-color: var(--warning-border);
|
||||
}
|
||||
|
||||
#root .success {
|
||||
color: #2ecc71;
|
||||
border: 2px solid #2ecc71;
|
||||
.notify-icon-container {
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#root .info {
|
||||
color: #fb9b04;
|
||||
border: 2px solid #fb9b04;
|
||||
.hexagon {
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
clip-path: polygon(50% 0%, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%);
|
||||
background: linear-gradient(145deg, var(--icon-color), rgba(0, 0, 0, 0.7));
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.hexagon::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
right: 3px;
|
||||
bottom: 3px;
|
||||
background: var(--background);
|
||||
clip-path: polygon(50% 0%, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hexagon::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(145deg, var(--icon-color) 10%, transparent 70%);
|
||||
opacity: 0.4;
|
||||
clip-path: polygon(50% 0%, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.hexagon .material-symbols-outlined {
|
||||
color: var(--icon-color);
|
||||
font-size: 20px;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.notify-content {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.notify-title {
|
||||
color: var(--text-color);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.notify-text {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.notify-progress {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 48;
|
||||
font-variation-settings: "FILL" 1, "wght" 400, "GRAD" 0, "opsz" 48;
|
||||
}
|
||||
|
||||
@keyframes anim {
|
||||
0% {
|
||||
transform: scaleY(0);
|
||||
}
|
||||
80% {
|
||||
transform: scaleY(1.1);
|
||||
}
|
||||
100% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
.notify-warning .material-symbols-outlined,
|
||||
.notify-error .material-symbols-outlined {
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.fadeIn {
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.fadeOut {
|
||||
animation: fadeOut 0.5s ease-in-out;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(50px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateX(50px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="nui://game/ui/jquery.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
<script src="js/script.js"></script>
|
||||
<title>ESX Notify</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
<!-- this is just a template! No touchy touchy -->
|
||||
</div>
|
||||
</body>
|
||||
<head>
|
||||
<script src="nui://game/ui/jquery.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
||||
<link rel="stylesheet" href="css/style.css" />
|
||||
<title>ESX Notifications</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
<!-- Notifications will be added here -->
|
||||
</div>
|
||||
<script src="js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,83 +1,190 @@
|
||||
const w = window;
|
||||
const w = window
|
||||
|
||||
// Gets the current icon it needs to use.
|
||||
const types = {
|
||||
["success"]: {
|
||||
["icon"]: "check_circle",
|
||||
},
|
||||
["error"]: {
|
||||
["icon"]: "error",
|
||||
},
|
||||
["info"]: {
|
||||
["icon"]: "info",
|
||||
},
|
||||
};
|
||||
["success"]: {
|
||||
["icon"]: "check_circle",
|
||||
["frequency"]: 800,
|
||||
["duration"]: 100,
|
||||
["color"]: "#2ecc71",
|
||||
["borderColor"]: "#1f9c4d",
|
||||
},
|
||||
["error"]: {
|
||||
["icon"]: "error",
|
||||
["frequency"]: 300,
|
||||
["duration"]: 150,
|
||||
["color"]: "#e74c3c",
|
||||
["borderColor"]: "#c0392b",
|
||||
},
|
||||
["info"]: {
|
||||
["icon"]: "info",
|
||||
["frequency"]: 600,
|
||||
["duration"]: 100,
|
||||
["color"]: "#3498db",
|
||||
["borderColor"]: "#2980b9",
|
||||
},
|
||||
["warning"]: {
|
||||
["icon"]: "warning_amber",
|
||||
["frequency"]: 450,
|
||||
["duration"]: 125,
|
||||
["color"]: "#f39c12",
|
||||
["borderColor"]: "#d35400",
|
||||
},
|
||||
}
|
||||
|
||||
// the color codes example `i ~r~love~s~ donuts`
|
||||
const codes = {
|
||||
"~r~": "#c0392b",
|
||||
"~b~": "#378cbf",
|
||||
"~g~": "#2ecc71",
|
||||
"~y~": "yellow",
|
||||
"~p~": "purple",
|
||||
"~c~": "grey",
|
||||
"~m~": "#212121",
|
||||
"~u~": "black",
|
||||
"~o~": "#fb9b04",
|
||||
};
|
||||
"~r~": "#c0392b",
|
||||
"~b~": "#378cbf",
|
||||
"~g~": "#2ecc71",
|
||||
"~y~": "yellow",
|
||||
"~p~": "purple",
|
||||
"~c~": "grey",
|
||||
"~m~": "#212121",
|
||||
"~u~": "black",
|
||||
"~o~": "#fb9b04",
|
||||
}
|
||||
|
||||
// Audio context for sound effects
|
||||
let audioContext
|
||||
|
||||
// Initialize audio context on user interaction
|
||||
document.addEventListener("click", initAudioContext, { once: true })
|
||||
document.addEventListener("keydown", initAudioContext, { once: true })
|
||||
|
||||
function initAudioContext() {
|
||||
if (!audioContext) {
|
||||
audioContext = new (window.AudioContext || window.webkitAudioContext)()
|
||||
}
|
||||
}
|
||||
|
||||
function playNotificationSound(type) {
|
||||
if (!audioContext) initAudioContext()
|
||||
|
||||
const typeConfig = types[type] || types["info"]
|
||||
const oscillator = audioContext.createOscillator()
|
||||
const gainNode = audioContext.createGain()
|
||||
|
||||
oscillator.type = "sine"
|
||||
oscillator.frequency.setValueAtTime(typeConfig.frequency, audioContext.currentTime)
|
||||
|
||||
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime)
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + typeConfig.duration / 1000)
|
||||
|
||||
oscillator.connect(gainNode)
|
||||
gainNode.connect(audioContext.destination)
|
||||
|
||||
oscillator.start()
|
||||
oscillator.stop(audioContext.currentTime + typeConfig.duration / 1000)
|
||||
}
|
||||
|
||||
w.addEventListener("message", (event) => {
|
||||
notification({
|
||||
type: event.data.type,
|
||||
message: event.data.message,
|
||||
length: event.data.length,
|
||||
});
|
||||
});
|
||||
notification({
|
||||
type: event.data.type,
|
||||
title: event.data.title || "New Notification",
|
||||
message: event.data.message,
|
||||
length: event.data.length,
|
||||
})
|
||||
})
|
||||
|
||||
const replaceColors = (str, obj) => {
|
||||
let strToReplace = str;
|
||||
let strToReplace = str
|
||||
|
||||
for (let id in obj) {
|
||||
strToReplace = strToReplace.replace(new RegExp(id, "g"), obj[id]);
|
||||
for (const id in obj) {
|
||||
strToReplace = strToReplace.replace(new RegExp(id, "g"), obj[id])
|
||||
}
|
||||
|
||||
return strToReplace
|
||||
}
|
||||
|
||||
const sanitizeHTML = (str) => {
|
||||
const temp = document.createElement("div")
|
||||
temp.textContent = str
|
||||
return temp.innerHTML
|
||||
}
|
||||
|
||||
const processLineBreaks = (str) => {
|
||||
// Replace <br> tags with actual line breaks
|
||||
return str.replace(/<br>/g, "<br>")
|
||||
}
|
||||
|
||||
const notification = (data) => {
|
||||
if (typeof $ === "undefined") {
|
||||
console.error("jQuery is not loaded. Please ensure jQuery is included in your project.")
|
||||
return
|
||||
}
|
||||
|
||||
if (data.message) {
|
||||
// Remove any standalone ~s~ tags (not preceded by a color code)
|
||||
data.message = data.message.replace(/~s~/g, "")
|
||||
}
|
||||
|
||||
if (data.title) {
|
||||
// Remove any standalone ~s~ tags (not preceded by a color code)
|
||||
data.title = data.title.replace(/~s~/g, "")
|
||||
}
|
||||
|
||||
let sanitizedTitle = data.title ? sanitizeHTML(data.title) : ""
|
||||
let sanitizedMessage = data.message ? sanitizeHTML(data.message) : ""
|
||||
|
||||
if (data.title) {
|
||||
for (const color in codes) {
|
||||
if (sanitizedTitle.includes(color)) {
|
||||
const objArr = {}
|
||||
objArr[color] = `<span style="color: ${codes[color]}">`
|
||||
objArr["~s~"] = "</span>"
|
||||
sanitizedTitle = replaceColors(sanitizedTitle, objArr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strToReplace;
|
||||
};
|
||||
|
||||
notification = (data) => {
|
||||
for (color in codes) {
|
||||
if (data["message"].includes(color)) {
|
||||
let objArr = {};
|
||||
objArr[color] = `<span style="color: ${codes[color]}">`;
|
||||
objArr["~s~"] = "</span>";
|
||||
|
||||
let newStr = replaceColors(data["message"], objArr);
|
||||
|
||||
data["message"] = newStr;
|
||||
}
|
||||
for (const color in codes) {
|
||||
if (sanitizedMessage.includes(color)) {
|
||||
const objArr = {}
|
||||
objArr[color] = `<span style="color: ${codes[color]}">`
|
||||
objArr["~s~"] = "</span>"
|
||||
sanitizedMessage = replaceColors(sanitizedMessage, objArr)
|
||||
}
|
||||
}
|
||||
|
||||
const id = Math.floor(Math.random() * Math.random());
|
||||
const notification = $(`
|
||||
<div id="${id}" class="notify ${data.type} fadeIn">
|
||||
<div class="innerText">
|
||||
<span class="material-symbols-outlined icon">${types[data.type] ? types[data.type]["icon"] : types["info"]["icon"]}</span>
|
||||
<p class="text">${data["message"]}</p>
|
||||
</div>
|
||||
sanitizedMessage = processLineBreaks(sanitizedMessage)
|
||||
sanitizedTitle = processLineBreaks(sanitizedTitle)
|
||||
|
||||
const id = Math.floor(Math.random() * 100000)
|
||||
const typeInfo = types[data.type] || types["info"]
|
||||
const duration = data.length || 3000
|
||||
|
||||
const notificationElement = $(`
|
||||
<div id="${id}" class="notify notify-${data.type} fadeIn">
|
||||
<div class="notify-icon-container">
|
||||
<div class="hexagon" style="--icon-color: ${typeInfo.color}; --border-color: ${typeInfo.borderColor}">
|
||||
<span class="material-symbols-outlined">${typeInfo.icon}</span>
|
||||
</div>
|
||||
`).appendTo(`#root`);
|
||||
</div>
|
||||
<div class="notify-content">
|
||||
<h3 class="notify-title">${sanitizedTitle}</h3>
|
||||
<p class="notify-text">${sanitizedMessage}</p>
|
||||
<div class="notify-progress" style="background-color: ${typeInfo.color}"></div>
|
||||
</div>
|
||||
</div>
|
||||
`).appendTo(`#root`)
|
||||
|
||||
$(`#${id} .notify-progress`).css({
|
||||
transition: `width ${duration}ms linear`,
|
||||
width: "0%",
|
||||
})
|
||||
|
||||
playNotificationSound(data.type)
|
||||
|
||||
setTimeout(() => {
|
||||
$(`#${id} .notify-progress`).css("width", "100%")
|
||||
}, 10)
|
||||
|
||||
setTimeout(() => {
|
||||
$(`#${id}`).removeClass("fadeIn").addClass("fadeOut")
|
||||
setTimeout(() => {
|
||||
document.getElementById(id).classList.remove("fadeIn");
|
||||
}, 300);
|
||||
$(`#${id}`).remove()
|
||||
}, 500)
|
||||
}, duration)
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById(id).classList.add("fadeOut");
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById(id).remove();
|
||||
}, 400);
|
||||
},data.length);
|
||||
|
||||
return notification;
|
||||
};
|
||||
return notificationElement
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user