Merge pull request #1635 from auradevelopment5m/main

refactor(esx_notify): redesign, code improvements.
This commit is contained in:
Kenshin13
2025-05-09 23:53:36 +02:00
committed by GitHub
7 changed files with 411 additions and 169 deletions
+3 -2
View File
@@ -152,9 +152,10 @@ end
---@param message string The message to show
---@param notifyType? string The type of notification to show
---@param length? number The length of the notification
---@param title? string The title of the notification
---@return nil
function ESX.ShowNotification(message, notifyType, length)
return IsResourceFound('esx_notify') and exports['esx_notify']:Notify(notifyType, length, message)
function ESX.ShowNotification(message, notifyType, length, title)
return IsResourceFound('esx_notify') and exports['esx_notify']:Notify(notifyType, length, message, title)
end
function ESX.TextUI(...)
+33 -22
View File
@@ -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
+1 -1
View File
@@ -2,7 +2,7 @@ fx_version 'adamant'
lua54 'yes'
game 'gta5'
version '1.12.2'
version '1.13.0'
author 'ESX-Framework'
description 'A beautiful and simple NUI notification system for ESX'
+165 -56
View File
@@ -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);
}
}
+12 -12
View File
@@ -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>
+172 -65
View File
@@ -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(/&lt;br&gt;/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
}
+25 -11
View File
@@ -1,33 +1,48 @@
<h1 align='center'>[ESX] Notify</a></h1><p align='center'><b><a href='https://discord.esx-framework.org/'>Discord</a> - <a href='https://esx-framework.org/'>Website</a> - <a href='https://docs.esx-legacy.com/legacy/installation'>Documentation</a></b></h5>
`<h1 align='center'>`[ESX] Notify`</a>``</h1>``<p align='center'>``<b>``<a href='https://discord.esx-framework.org/'>`Discord`</a>` - `<a href='https://esx-framework.org/'>`Website`</a>` - `<a href='https://docs.esx-legacy.com/legacy/installation'>`Documentation`</a>``</b>``</h5>`
A beautiful and simple NUI notification system for ESX
# Example Code
<h3>Change style and time</h3>
`<h3>`Change style and time`</h3>`
```lua
---usage: message/type/length
ESX.ShowNotification("message here", "error", 3000)
ESX.ShowNotification("message here", "success", 3000)
ESX.ShowNotification("message here", "info", 3000)
ESX.ShowNotification("message here", "warning", 3000)
ESX.ShowNotification("text here") -- Default will time and type will be info/3000
-- With title (optional 4th parameter)
ESX.ShowNotification("message here", "success", 3000, "Achievement")
```
<h3>Export Usage</h3>
`<h3>`Export Usage`</h3>`
```lua
-- Basic usage
exports["esx_notify"]:Notify("info", 3000, "message here")
-- With title
exports["esx_notify"]:Notify("success", 3000, "Item purchased!", "Shop")
-- With line break
exports["esx_notify"]:Notify("warning", 4000, "Inventory full!~br~Some items were dropped.", "Warning")
```
<h3>Event Usage</h3>
`<h3>`Event Usage`</h3>`
```lua
-- Basic usage
TriggerEvent("ESX:Notify", "info", 3000, "message here")
-- With title
TriggerEvent("ESX:Notify", "error", 5000, "You don't have enough money!", "Transaction Failed")
```
<h3>Color Code Usage</h3>
`<h3>`Color Code Usage`</h3>`
```lua
~r~ = Red
@@ -41,15 +56,14 @@ TriggerEvent("ESX:Notify", "info", 3000, "message here")
~o~ = Orange
ESX.ShowNotification("I i ~r~love~s~ donuts", "success", 3000)
-- Line break example
ESX.ShowNotification("You received ~br~1x ~g~ball~s~!", "success", 3000, "Item Received")
```
# Previews
![Preview_1](https://cdn.discordapp.com/attachments/944789399852417096/997890963445927977/unknown.png)
![Preview_2_zoom](https://cdn.discordapp.com/attachments/944789399852417096/997892214053163148/unknown.png)
![Preview_2](https://cdn.discordapp.com/attachments/944789399852417096/997891726326898909/unknown.png)
![Preview 1](https://r2.fivemanage.com/gWoWHGuKZdsK8PFzaVuGC/image_2025-05-05_194204916.png)
## Legal
@@ -61,4 +75,4 @@ This program Is free software: you can redistribute it And/Or modify it under th
This program Is distributed In the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty Of MERCHANTABILITY Or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License For more details.
You should have received a copy Of the GNU General Public License along with this program. If Not, see <http://www.gnu.org/licenses/>.
You should have received a copy Of the GNU General Public License along with this program. If Not, see `<http://www.gnu.org/licenses/>`.