mirror of
https://github.com/esx-framework/esx_core.git
synced 2026-08-29 01:01:22 +00:00
Merge pull request #1694 from alexistb2904/esx_notify-position
feat(esx_notify): Add a configurable position
This commit is contained in:
@@ -154,9 +154,10 @@ end
|
|||||||
---@param notifyType? string The type of notification to show
|
---@param notifyType? string The type of notification to show
|
||||||
---@param length? number The length of the notification
|
---@param length? number The length of the notification
|
||||||
---@param title? string The title of the notification
|
---@param title? string The title of the notification
|
||||||
|
---@param position? string The position of the notification
|
||||||
---@return nil
|
---@return nil
|
||||||
function ESX.ShowNotification(message, notifyType, length, title)
|
function ESX.ShowNotification(message, notifyType, length, title, position)
|
||||||
return IsResourceFound('esx_notify') and exports['esx_notify']:Notify(notifyType, length, message, title)
|
return IsResourceFound('esx_notify') and exports['esx_notify']:Notify(notifyType, length, message, title, position)
|
||||||
end
|
end
|
||||||
|
|
||||||
function ESX.TextUI(...)
|
function ESX.TextUI(...)
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
---@field setMeta fun(index: string, value: any, subValue?: any) # Set metadata value(s).
|
---@field setMeta fun(index: string, value: any, subValue?: any) # Set metadata value(s).
|
||||||
---@field clearMeta fun(index: string, subValues?: string|table) # Clear metadata value(s).
|
---@field clearMeta fun(index: string, subValues?: string|table) # Clear metadata value(s).
|
||||||
--- Notification Functions
|
--- Notification Functions
|
||||||
---@field showNotification fun(msg: string, notifyType?: string, length?: number) # Show a simple notification.
|
---@field showNotification fun(msg: string, notifyType?: string, length?: number, title?: string, position?: string) # Show a simple notification.
|
||||||
---@field showAdvancedNotification fun(sender: string, subject: string, msg: string, textureDict: string, iconType: string, flash: boolean, saveToBrief: boolean, hudColorIndex: number) # Show advanced notification.
|
---@field showAdvancedNotification fun(sender: string, subject: string, msg: string, textureDict: string, iconType: string, flash: boolean, saveToBrief: boolean, hudColorIndex: number) # Show advanced notification.
|
||||||
---@field showHelpNotification fun(msg: string, thisFrame?: boolean, beep?: boolean, duration?: number) # Show help notification.
|
---@field showHelpNotification fun(msg: string, thisFrame?: boolean, beep?: boolean, duration?: number) # Show help notification.
|
||||||
--- Misc Functions
|
--- Misc Functions
|
||||||
@@ -793,11 +793,10 @@ function CreateExtendedPlayer(playerId, identifier, ssn, group, accounts, invent
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function self.showNotification(msg, notifyType, length)
|
function self.showNotification(msg, notifyType, length, title, position)
|
||||||
self.triggerEvent("esx:showNotification", msg, notifyType, length)
|
self.triggerEvent("esx:showNotification", msg, notifyType, length, title, position)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function self.showAdvancedNotification(sender, subject, msg, textureDict, iconType, flash, saveToBrief, hudColorIndex)
|
function self.showAdvancedNotification(sender, subject, msg, textureDict, iconType, flash, saveToBrief, hudColorIndex)
|
||||||
self.triggerEvent("esx:showAdvancedNotification", sender, subject, msg, textureDict, iconType, flash, saveToBrief, hudColorIndex)
|
self.triggerEvent("esx:showAdvancedNotification", sender, subject, msg, textureDict, iconType, flash, saveToBrief, hudColorIndex)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
Config = {}
|
Config = {}
|
||||||
|
|
||||||
Config.notificationSoundEnabled = true
|
Config.notificationSoundEnabled = true
|
||||||
|
Config.position = "middle-right" -- top-right, top-left, top-middle, bottom-right, bottom-left, bottom-middle, middle-left, middle-right
|
||||||
@@ -1,16 +1,28 @@
|
|||||||
local Debug = ESX.GetConfig().EnableDebug
|
local Debug = ESX.GetConfig().EnableDebug
|
||||||
|
|
||||||
|
local possiblePosition = {
|
||||||
|
["top-right"] = true,
|
||||||
|
["top-left"] = true,
|
||||||
|
["top-middle"] = true,
|
||||||
|
["bottom-right"] = true,
|
||||||
|
["bottom-left"] = true,
|
||||||
|
["bottom-middle"] = true,
|
||||||
|
["middle-left"] = true,
|
||||||
|
["middle-right"] = true
|
||||||
|
}
|
||||||
|
|
||||||
---@param notificatonType string the notification type
|
---@param notificatonType string the notification type
|
||||||
---@param length number the length of the notification
|
---@param length number the length of the notification
|
||||||
---@param message any the message :D
|
---@param message any the message :D
|
||||||
---@param title string optional title for the notification
|
---@param title string optional title for the notification
|
||||||
local function Notify(notificatonType, length, message, title)
|
---@param position string optional position for the notification
|
||||||
|
local function Notify(notificatonType, length, message, title, position)
|
||||||
if Debug then
|
if Debug then
|
||||||
print("1 ".. tostring(notificatonType))
|
print("1 ".. tostring(notificatonType))
|
||||||
print("2 "..tostring(length))
|
print("2 "..tostring(length))
|
||||||
print("3 "..message)
|
print("3 "..message)
|
||||||
print("4 "..tostring(title))
|
print("4 "..tostring(title))
|
||||||
|
print("5 "..tostring(position))
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(notificatonType) ~= "string" then
|
if type(notificatonType) ~= "string" then
|
||||||
@@ -21,11 +33,16 @@ local function Notify(notificatonType, length, message, title)
|
|||||||
length = 3000
|
length = 3000
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not possiblePosition[position] then
|
||||||
|
position = Config.position or "middle-right"
|
||||||
|
end
|
||||||
|
|
||||||
if Debug then
|
if Debug then
|
||||||
print("5 ".. tostring(notificatonType))
|
print("6 ".. tostring(notificatonType))
|
||||||
print("6 "..tostring(length))
|
print("7 "..tostring(length))
|
||||||
print("7 "..message)
|
print("8 "..message)
|
||||||
print("8 "..tostring(title))
|
print("9 "..tostring(title))
|
||||||
|
print("10 "..tostring(position))
|
||||||
end
|
end
|
||||||
|
|
||||||
if type(message) == "string" then
|
if type(message) == "string" then
|
||||||
@@ -37,7 +54,8 @@ local function Notify(notificatonType, length, message, title)
|
|||||||
length = length or 5000,
|
length = length or 5000,
|
||||||
message = message or "ESX-Notify",
|
message = message or "ESX-Notify",
|
||||||
title = title or "New Notification",
|
title = title or "New Notification",
|
||||||
notificationSoundEnabled = Config.notificationSoundEnabled
|
position = position,
|
||||||
|
notificationSoundEnabled = Config.notificationSoundEnabled or false
|
||||||
}))
|
}))
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -67,5 +85,5 @@ if Debug then
|
|||||||
|
|
||||||
RegisterCommand("notify4", function()
|
RegisterCommand("notify4", function()
|
||||||
ESX.ShowNotification("You Did something ~r~WRONG~s~!", "warning", 3000, "~y~Warning~s~")
|
ESX.ShowNotification("You Did something ~r~WRONG~s~!", "warning", 3000, "~y~Warning~s~")
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
+267
-126
@@ -1,196 +1,337 @@
|
|||||||
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap");
|
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap");
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background: rgba(20, 20, 20, 0.9);
|
--background: rgba(20, 20, 20, 0.9);
|
||||||
--text-color: #ffffff;
|
--text-color: #ffffff;
|
||||||
--text-secondary: rgba(255, 255, 255, 0.7);
|
--text-secondary: rgba(255, 255, 255, 0.7);
|
||||||
--success-color: #2ecc71;
|
--success-color: #2ecc71;
|
||||||
--success-border: #1f9c4d;
|
--success-border: #1f9c4d;
|
||||||
--error-color: #e74c3c;
|
--error-color: #e74c3c;
|
||||||
--error-border: #c0392b;
|
--error-border: #c0392b;
|
||||||
--info-color: #3498db;
|
--info-color: #3498db;
|
||||||
--info-border: #2980b9;
|
--info-border: #2980b9;
|
||||||
--warning-color: #f39c12;
|
--warning-color: #f39c12;
|
||||||
--warning-border: #d35400;
|
--warning-border: #d35400;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
font-family: "Poppins", sans-serif;
|
font-family: "Poppins", sans-serif;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#root {
|
.notification-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 2rem;
|
display: flex;
|
||||||
top: 50%;
|
flex-direction: column;
|
||||||
transform: translateY(-50%);
|
align-items: flex-end;
|
||||||
display: flex;
|
gap: 10px;
|
||||||
flex-direction: column;
|
max-height: 80vh;
|
||||||
align-items: flex-end;
|
z-index: 1000;
|
||||||
gap: 10px;
|
}
|
||||||
max-height: 80vh;
|
|
||||||
z-index: 1000;
|
#middle-right {
|
||||||
|
right: 2rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#middle-left {
|
||||||
|
left: 2rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-right {
|
||||||
|
right: 2rem;
|
||||||
|
top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-left {
|
||||||
|
left: 2rem;
|
||||||
|
top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-middle {
|
||||||
|
left: 50%;
|
||||||
|
top: 2rem;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#bottom-right {
|
||||||
|
right: 2rem;
|
||||||
|
bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bottom-left {
|
||||||
|
left: 2rem;
|
||||||
|
bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bottom-middle {
|
||||||
|
left: 50%;
|
||||||
|
bottom: 2rem;
|
||||||
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify {
|
.notify {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-left: 4px solid #555;
|
border-left: 4px solid #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-success {
|
.notify-success {
|
||||||
border-left-color: var(--success-border);
|
border-left-color: var(--success-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-error {
|
.notify-error {
|
||||||
border-left-color: var(--error-border);
|
border-left-color: var(--error-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-info {
|
.notify-info {
|
||||||
border-left-color: var(--info-border);
|
border-left-color: var(--info-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-warning {
|
.notify-warning {
|
||||||
border-left-color: var(--warning-border);
|
border-left-color: var(--warning-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-icon-container {
|
.notify-icon-container {
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hexagon {
|
.hexagon {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
clip-path: polygon(50% 0%, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%);
|
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));
|
background: linear-gradient(145deg, var(--icon-color), rgba(0, 0, 0, 0.7));
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hexagon::before {
|
.hexagon::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 3px;
|
top: 3px;
|
||||||
left: 3px;
|
left: 3px;
|
||||||
right: 3px;
|
right: 3px;
|
||||||
bottom: 3px;
|
bottom: 3px;
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
clip-path: polygon(50% 0%, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%);
|
clip-path: polygon(50% 0%, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hexagon::after {
|
.hexagon::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(145deg, var(--icon-color) 10%, transparent 70%);
|
background: linear-gradient(145deg, var(--icon-color) 10%, transparent 70%);
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
clip-path: polygon(50% 0%, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%);
|
clip-path: polygon(50% 0%, 95% 25%, 95% 75%, 50% 100%, 5% 75%, 5% 25%);
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hexagon .material-symbols-outlined {
|
.hexagon .material-symbols-outlined {
|
||||||
color: var(--icon-color);
|
color: var(--icon-color);
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
|
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-content {
|
.notify-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-title {
|
.notify-title {
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-text {
|
.notify-text {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-progress {
|
.notify-progress {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-symbols-outlined {
|
.material-symbols-outlined {
|
||||||
font-variation-settings: "FILL" 1, "wght" 400, "GRAD" 0, "opsz" 48;
|
font-variation-settings: "FILL" 1, "wght" 400, "GRAD" 0, "opsz" 48;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notify-warning .material-symbols-outlined,
|
.notify-warning .material-symbols-outlined,
|
||||||
.notify-error .material-symbols-outlined {
|
.notify-error .material-symbols-outlined {
|
||||||
margin-top: -2px;
|
margin-top: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fadeIn {
|
#top-right .fadeIn,
|
||||||
animation: fadeIn 0.3s ease-in-out;
|
#bottom-right .fadeIn,
|
||||||
|
#middle-right .fadeIn {
|
||||||
|
animation: fadeIn-right 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-left .fadeIn,
|
||||||
|
#bottom-left .fadeIn,
|
||||||
|
#middle-left .fadeIn {
|
||||||
|
animation: fadeIn-left 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-middle .fadeIn {
|
||||||
|
animation: fadeIn-top 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bottom-middle .fadeIn {
|
||||||
|
animation: fadeIn-bottom 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fadeOut {
|
.fadeOut {
|
||||||
animation: fadeOut 0.5s ease-in-out;
|
opacity: 0;
|
||||||
opacity: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
#top-right .fadeOut,
|
||||||
from {
|
#bottom-right .fadeOut,
|
||||||
opacity: 0;
|
#middle-right .fadeOut {
|
||||||
transform: translateX(50px);
|
animation: fadeOut-right 0.5s ease-in-out;
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeOut {
|
#top-left .fadeOut,
|
||||||
from {
|
#bottom-left .fadeOut,
|
||||||
opacity: 1;
|
#middle-left .fadeOut {
|
||||||
transform: translateX(0);
|
animation: fadeOut-left 0.5s ease-in-out;
|
||||||
}
|
}
|
||||||
to {
|
|
||||||
opacity: 0;
|
#top-middle .fadeOut {
|
||||||
transform: translateX(50px);
|
animation: fadeOut-top 0.5s ease-in-out;
|
||||||
}
|
}
|
||||||
|
#bottom-middle .fadeOut {
|
||||||
|
animation: fadeOut-bottom 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn-right {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(50px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn-left {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn-top {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-50px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn-bottom {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(50px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOut-right {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(50px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOut-left {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOut-top {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-50px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOut-bottom {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(50px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,39 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
<script src="nui://game/ui/jquery.js" type="text/javascript"></script>
|
<head>
|
||||||
<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" />
|
<script src="nui://game/ui/jquery.js" type="text/javascript"></script>
|
||||||
<link rel="stylesheet" href="css/style.css" />
|
<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" />
|
||||||
<title>ESX Notifications</title>
|
<link rel="stylesheet" href="css/style.css" />
|
||||||
</head>
|
<title>ESX Notifications</title>
|
||||||
<body>
|
</head>
|
||||||
<div id="root">
|
|
||||||
<!-- Notifications will be added here -->
|
<body>
|
||||||
</div>
|
<div id="top-right" class="notification-container">
|
||||||
<script src="js/script.js"></script>
|
<!-- Top Right notifications will be added here -->
|
||||||
</body>
|
</div>
|
||||||
</html>
|
<div id="top-left" class="notification-container">
|
||||||
|
<!-- Top Left notifications will be added here -->
|
||||||
|
</div>
|
||||||
|
<div id="top-middle" class="notification-container">
|
||||||
|
<!-- Top Middle notifications will be added here -->
|
||||||
|
</div>
|
||||||
|
<div id="middle-left" class="notification-container">
|
||||||
|
<!-- Middle Left notifications will be added here -->
|
||||||
|
</div>
|
||||||
|
<div id="middle-right" class="notification-container">
|
||||||
|
<!-- Middle Right notifications will be added here -->
|
||||||
|
</div>
|
||||||
|
<div id="bottom-left" class="notification-container">
|
||||||
|
<!-- Bottom Left notifications will be added here -->
|
||||||
|
</div>
|
||||||
|
<div id="bottom-middle" class="notification-container">
|
||||||
|
<!-- Bottom Middle notifications will be added here -->
|
||||||
|
</div>
|
||||||
|
<div id="bottom-right" class="notification-container">
|
||||||
|
<!-- Bottom Right notifications will be added here -->
|
||||||
|
</div>
|
||||||
|
<script src="js/script.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
+143
-137
@@ -1,160 +1,167 @@
|
|||||||
const w = window
|
const w = window;
|
||||||
|
|
||||||
const types = {
|
const types = {
|
||||||
["success"]: {
|
["success"]: {
|
||||||
["icon"]: "check_circle",
|
["icon"]: "check_circle",
|
||||||
["frequency"]: 800,
|
["frequency"]: 800,
|
||||||
["duration"]: 100,
|
["duration"]: 100,
|
||||||
["color"]: "#2ecc71",
|
["color"]: "#2ecc71",
|
||||||
["borderColor"]: "#1f9c4d",
|
["borderColor"]: "#1f9c4d",
|
||||||
},
|
},
|
||||||
["error"]: {
|
["error"]: {
|
||||||
["icon"]: "error",
|
["icon"]: "error",
|
||||||
["frequency"]: 300,
|
["frequency"]: 300,
|
||||||
["duration"]: 150,
|
["duration"]: 150,
|
||||||
["color"]: "#e74c3c",
|
["color"]: "#e74c3c",
|
||||||
["borderColor"]: "#c0392b",
|
["borderColor"]: "#c0392b",
|
||||||
},
|
},
|
||||||
["info"]: {
|
["info"]: {
|
||||||
["icon"]: "info",
|
["icon"]: "info",
|
||||||
["frequency"]: 600,
|
["frequency"]: 600,
|
||||||
["duration"]: 100,
|
["duration"]: 100,
|
||||||
["color"]: "#3498db",
|
["color"]: "#3498db",
|
||||||
["borderColor"]: "#2980b9",
|
["borderColor"]: "#2980b9",
|
||||||
},
|
},
|
||||||
["warning"]: {
|
["warning"]: {
|
||||||
["icon"]: "warning_amber",
|
["icon"]: "warning_amber",
|
||||||
["frequency"]: 450,
|
["frequency"]: 450,
|
||||||
["duration"]: 125,
|
["duration"]: 125,
|
||||||
["color"]: "#f39c12",
|
["color"]: "#f39c12",
|
||||||
["borderColor"]: "#d35400",
|
["borderColor"]: "#d35400",
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
// the color codes example `i ~r~love~s~ donuts`
|
// the color codes example `i ~r~love~s~ donuts`
|
||||||
const codes = {
|
const codes = {
|
||||||
"~r~": "#c0392b",
|
"~r~": "#c0392b",
|
||||||
"~b~": "#378cbf",
|
"~b~": "#378cbf",
|
||||||
"~g~": "#2ecc71",
|
"~g~": "#2ecc71",
|
||||||
"~y~": "yellow",
|
"~y~": "yellow",
|
||||||
"~p~": "purple",
|
"~p~": "purple",
|
||||||
"~c~": "grey",
|
"~c~": "grey",
|
||||||
"~m~": "#212121",
|
"~m~": "#212121",
|
||||||
"~u~": "black",
|
"~u~": "black",
|
||||||
"~o~": "#fb9b04",
|
"~o~": "#fb9b04",
|
||||||
}
|
};
|
||||||
|
|
||||||
// Audio context for sound effects
|
// Audio context for sound effects
|
||||||
let audioContext
|
let audioContext;
|
||||||
|
|
||||||
// Initialize audio context on user interaction
|
// Initialize audio context on user interaction
|
||||||
document.addEventListener("click", initAudioContext, { once: true })
|
document.addEventListener("click", initAudioContext, { once: true });
|
||||||
document.addEventListener("keydown", initAudioContext, { once: true })
|
document.addEventListener("keydown", initAudioContext, { once: true });
|
||||||
|
|
||||||
function initAudioContext() {
|
function initAudioContext() {
|
||||||
if (!audioContext) {
|
if (!audioContext) {
|
||||||
audioContext = new (window.AudioContext || window.webkitAudioContext)()
|
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function playNotificationSound(type) {
|
function playNotificationSound(type) {
|
||||||
if (!audioContext) initAudioContext()
|
if (!audioContext) initAudioContext();
|
||||||
|
|
||||||
const typeConfig = types[type] || types["info"]
|
const typeConfig = types[type] || types["info"];
|
||||||
const oscillator = audioContext.createOscillator()
|
const oscillator = audioContext.createOscillator();
|
||||||
const gainNode = audioContext.createGain()
|
const gainNode = audioContext.createGain();
|
||||||
|
|
||||||
oscillator.type = "sine"
|
oscillator.type = "sine";
|
||||||
oscillator.frequency.setValueAtTime(typeConfig.frequency, audioContext.currentTime)
|
oscillator.frequency.setValueAtTime(typeConfig.frequency, audioContext.currentTime);
|
||||||
|
|
||||||
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime)
|
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
|
||||||
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + typeConfig.duration / 1000)
|
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + typeConfig.duration / 1000);
|
||||||
|
|
||||||
oscillator.connect(gainNode)
|
oscillator.connect(gainNode);
|
||||||
gainNode.connect(audioContext.destination)
|
gainNode.connect(audioContext.destination);
|
||||||
|
|
||||||
oscillator.start()
|
oscillator.start();
|
||||||
oscillator.stop(audioContext.currentTime + typeConfig.duration / 1000)
|
oscillator.stop(audioContext.currentTime + typeConfig.duration / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
w.addEventListener("message", (event) => {
|
w.addEventListener("message", (event) => {
|
||||||
notification({
|
notification({
|
||||||
type: event.data.type,
|
type: event.data.type,
|
||||||
title: event.data.title || "New Notification",
|
title: event.data.title || "New Notification",
|
||||||
message: event.data.message,
|
message: event.data.message,
|
||||||
length: event.data.length,
|
length: event.data.length,
|
||||||
notificationSoundEnabled: event.data.notificationSoundEnabled,
|
position: event.data.position,
|
||||||
})
|
notificationSoundEnabled: event.data.notificationSoundEnabled,
|
||||||
})
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const replaceColors = (str, obj) => {
|
const replaceColors = (str, obj) => {
|
||||||
let strToReplace = str
|
let strToReplace = str;
|
||||||
|
|
||||||
for (const id in obj) {
|
for (const id in obj) {
|
||||||
strToReplace = strToReplace.replace(new RegExp(id, "g"), obj[id])
|
strToReplace = strToReplace.replace(new RegExp(id, "g"), obj[id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return strToReplace
|
return strToReplace;
|
||||||
}
|
};
|
||||||
|
|
||||||
const sanitizeHTML = (str) => {
|
const sanitizeHTML = (str) => {
|
||||||
const temp = document.createElement("div")
|
const temp = document.createElement("div");
|
||||||
temp.textContent = str
|
temp.textContent = str;
|
||||||
return temp.innerHTML
|
return temp.innerHTML;
|
||||||
}
|
};
|
||||||
|
|
||||||
const processLineBreaks = (str) => {
|
const processLineBreaks = (str) => {
|
||||||
// Replace <br> tags with actual line breaks
|
// Replace <br> tags with actual line breaks
|
||||||
return str.replace(/<br>/g, "<br>")
|
return str.replace(/<br>/g, "<br>");
|
||||||
}
|
};
|
||||||
|
|
||||||
const notification = (data) => {
|
const notification = (data) => {
|
||||||
if (typeof $ === "undefined") {
|
if (typeof $ === "undefined") {
|
||||||
console.error("jQuery is not loaded. Please ensure jQuery is included in your project.")
|
console.error("jQuery is not loaded. Please ensure jQuery is included in your project.");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.message) {
|
if (data.message) {
|
||||||
// Remove any standalone ~s~ tags (not preceded by a color code)
|
// Remove any standalone ~s~ tags (not preceded by a color code)
|
||||||
data.message = data.message.replace(/~s~/g, "")
|
data.message = data.message.replace(/~s~/g, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.title) {
|
if (data.title) {
|
||||||
// Remove any standalone ~s~ tags (not preceded by a color code)
|
// Remove any standalone ~s~ tags (not preceded by a color code)
|
||||||
data.title = data.title.replace(/~s~/g, "")
|
data.title = data.title.replace(/~s~/g, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
let sanitizedTitle = data.title ? sanitizeHTML(data.title) : ""
|
let sanitizedTitle = data.title ? sanitizeHTML(data.title) : "";
|
||||||
let sanitizedMessage = data.message ? sanitizeHTML(data.message) : ""
|
let sanitizedMessage = data.message ? sanitizeHTML(data.message) : "";
|
||||||
|
|
||||||
if (data.title) {
|
if (data.title) {
|
||||||
for (const color in codes) {
|
for (const color in codes) {
|
||||||
if (sanitizedTitle.includes(color)) {
|
if (sanitizedTitle.includes(color)) {
|
||||||
const objArr = {}
|
const objArr = {};
|
||||||
objArr[color] = `<span style="color: ${codes[color]}">`
|
objArr[color] = `<span style="color: ${codes[color]}">`;
|
||||||
objArr["~s~"] = "</span>"
|
objArr["~s~"] = "</span>";
|
||||||
sanitizedTitle = replaceColors(sanitizedTitle, objArr)
|
sanitizedTitle = replaceColors(sanitizedTitle, objArr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const color in codes) {
|
for (const color in codes) {
|
||||||
if (sanitizedMessage.includes(color)) {
|
if (sanitizedMessage.includes(color)) {
|
||||||
const objArr = {}
|
const objArr = {};
|
||||||
objArr[color] = `<span style="color: ${codes[color]}">`
|
objArr[color] = `<span style="color: ${codes[color]}">`;
|
||||||
objArr["~s~"] = "</span>"
|
objArr["~s~"] = "</span>";
|
||||||
sanitizedMessage = replaceColors(sanitizedMessage, objArr)
|
sanitizedMessage = replaceColors(sanitizedMessage, objArr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitizedMessage = processLineBreaks(sanitizedMessage)
|
sanitizedMessage = processLineBreaks(sanitizedMessage);
|
||||||
sanitizedTitle = processLineBreaks(sanitizedTitle)
|
sanitizedTitle = processLineBreaks(sanitizedTitle);
|
||||||
|
|
||||||
const id = Math.floor(Math.random() * 100000)
|
const id = Math.floor(Math.random() * 100000);
|
||||||
const typeInfo = types[data.type] || types["info"]
|
const typeInfo = types[data.type] || types["info"];
|
||||||
const duration = data.length || 3000
|
const duration = data.length || 3000;
|
||||||
|
const containerToAppend = data.position ? `#${data.position}` : "#middle-right";
|
||||||
|
|
||||||
const notificationElement = $(`
|
if (!$(containerToAppend).length) {
|
||||||
|
console.error(`Container ${containerToAppend} to add the notification not found.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notificationElement = $(`
|
||||||
<div id="${id}" class="notify notify-${data.type} fadeIn">
|
<div id="${id}" class="notify notify-${data.type} fadeIn">
|
||||||
<div class="notify-icon-container">
|
<div class="notify-icon-container">
|
||||||
<div class="hexagon" style="--icon-color: ${typeInfo.color}; --border-color: ${typeInfo.borderColor}">
|
<div class="hexagon" style="--icon-color: ${typeInfo.color}; --border-color: ${typeInfo.borderColor}">
|
||||||
@@ -167,28 +174,27 @@ const notification = (data) => {
|
|||||||
<div class="notify-progress" style="background-color: ${typeInfo.color}"></div>
|
<div class="notify-progress" style="background-color: ${typeInfo.color}"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).appendTo(`#root`)
|
`).appendTo(containerToAppend);
|
||||||
|
|
||||||
$(`#${id} .notify-progress`).css({
|
$(`#${id} .notify-progress`).css({
|
||||||
transition: `width ${duration}ms linear`,
|
transition: `width ${duration}ms linear`,
|
||||||
width: "0%",
|
width: "0%",
|
||||||
})
|
});
|
||||||
|
|
||||||
if (data.notificationSoundEnabled) {
|
if (data.notificationSoundEnabled) {
|
||||||
playNotificationSound(data.type)
|
playNotificationSound(data.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
$(`#${id} .notify-progress`).css("width", "100%")
|
$(`#${id} .notify-progress`).css("width", "100%");
|
||||||
}, 10)
|
}, 10);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
$(`#${id}`).removeClass("fadeIn").addClass("fadeOut")
|
$(`#${id}`).removeClass("fadeIn").addClass("fadeOut");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
$(`#${id}`).remove()
|
$(`#${id}`).remove();
|
||||||
}, 500)
|
}, 500);
|
||||||
}, duration)
|
}, duration);
|
||||||
|
|
||||||
return notificationElement
|
return notificationElement;
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -17,6 +17,20 @@ ESX.ShowNotification("text here") -- Default will time and type will be info/300
|
|||||||
|
|
||||||
-- With title (optional 4th parameter)
|
-- With title (optional 4th parameter)
|
||||||
ESX.ShowNotification("message here", "success", 3000, "Achievement")
|
ESX.ShowNotification("message here", "success", 3000, "Achievement")
|
||||||
|
|
||||||
|
--[[
|
||||||
|
With position (optional 5th parameter)
|
||||||
|
Possible positions:
|
||||||
|
"top-right"
|
||||||
|
"top-left"
|
||||||
|
"top-middle"
|
||||||
|
"bottom-right"
|
||||||
|
"bottom-left"
|
||||||
|
"bottom-middle"
|
||||||
|
"middle-left"
|
||||||
|
"middle-right"
|
||||||
|
]]
|
||||||
|
ESX.ShowNotification("Your message here", "info", 3000, "Info Title", "top-right")
|
||||||
```
|
```
|
||||||
|
|
||||||
`<h3>`Export Usage`</h3>`
|
`<h3>`Export Usage`</h3>`
|
||||||
@@ -28,6 +42,9 @@ exports["esx_notify"]:Notify("info", 3000, "message here")
|
|||||||
-- With title
|
-- With title
|
||||||
exports["esx_notify"]:Notify("success", 3000, "Item purchased!", "Shop")
|
exports["esx_notify"]:Notify("success", 3000, "Item purchased!", "Shop")
|
||||||
|
|
||||||
|
-- With position
|
||||||
|
exports["esx_notify"]:Notify("warning", 4000, "Inventory full!", "Warning", "bottom-left")
|
||||||
|
|
||||||
-- With line break
|
-- With line break
|
||||||
exports["esx_notify"]:Notify("warning", 4000, "Inventory full!~br~Some items were dropped.", "Warning")
|
exports["esx_notify"]:Notify("warning", 4000, "Inventory full!~br~Some items were dropped.", "Warning")
|
||||||
```
|
```
|
||||||
@@ -40,6 +57,9 @@ TriggerEvent("ESX:Notify", "info", 3000, "message here")
|
|||||||
|
|
||||||
-- With title
|
-- With title
|
||||||
TriggerEvent("ESX:Notify", "error", 5000, "You don't have enough money!", "Transaction Failed")
|
TriggerEvent("ESX:Notify", "error", 5000, "You don't have enough money!", "Transaction Failed")
|
||||||
|
|
||||||
|
-- With position
|
||||||
|
TriggerEvent("ESX:Notify", "success", 4000, "Welcome!", "Greetings", "top-middle")
|
||||||
```
|
```
|
||||||
|
|
||||||
`<h3>`Color Code Usage`</h3>`
|
`<h3>`Color Code Usage`</h3>`
|
||||||
@@ -75,4 +95,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.
|
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/>`.
|
||||||
|
|||||||
Reference in New Issue
Block a user