Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 13 additions & 15 deletions client/main.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---@diagnostic disable: missing-parameter, param-type-mismatch
local QBCore = exports['qb-core']:GetCoreObject()
RegisterNetEvent('QBCore:Client:UpdateObject', function() QBCore = exports['qb-core']:GetCoreObject() end)

Expand Down Expand Up @@ -79,19 +80,17 @@ RegisterNUICallback('clickedButton', function(option, cb)
cb('ok')
return
end
if data then
if data.params.event then
if data.params.isServer then
TriggerServerEvent(data.params.event, data.params.args)
elseif data.params.isCommand then
ExecuteCommand(data.params.event)
elseif data.params.isQBCommand then
TriggerServerEvent('QBCore:CallCommand', data.params.event, data.params.args)
elseif data.params.isAction then
data.params.event(data.params.args)
else
TriggerEvent(data.params.event, data.params.args)
end
if data and data.params and data.params.event then
if data.params.isServer then
TriggerServerEvent(data.params.event, data.params.args)
elseif data.params.isCommand then
ExecuteCommand(data.params.event)
elseif data.params.isQBCommand then
TriggerServerEvent('QBCore:CallCommand', data.params.event, data.params.args)
elseif data.params.isAction then
data.params.event(data.params.args)
else
TriggerEvent(data.params.event, data.params.args)
end
end
end
Expand All @@ -117,8 +116,7 @@ end)

RegisterKeyMapping('playerFocus', 'Give Menu Focus', 'keyboard', 'LMENU')

-- Exports

exports('openMenu', openMenu)
exports('closeMenu', closeMenu)
exports('showHeader', showHeader)
exports('showHeader', showHeader)
3 changes: 2 additions & 1 deletion fxmanifest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ ui_page 'html/index.html'
files {
'html/index.html',
'html/script.js',
'html/style.css'
'html/style.css',
'html/audio/press.wav'
}
Binary file added html/audio/press.wav
Binary file not shown.
1 change: 1 addition & 0 deletions html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
</head>
<body>
<div id="container">
<div id="menuTitle"></div>
<div id="buttons"></div>
<div id="imageHover">
<img id="image" />
Expand Down
182 changes: 105 additions & 77 deletions html/script.js
Original file line number Diff line number Diff line change
@@ -1,95 +1,123 @@
let buttonParams = [];
let images = [];
let images = []

const openMenu = (data = null) => {
let html = "";
data.forEach((item, index) => {
if(!item.hidden) {
let header = item.header;
let message = item.txt || item.text;
let isMenuHeader = item.isMenuHeader;
let isDisabled = item.disabled;
let icon = item.icon;
images[index] = item;
html += getButtonRender(header, message, index, isMenuHeader, isDisabled, icon);
if (item.params) buttonParams[index] = item.params;
}
});
let html = ""
let titleHtml = ""
let startIndex = 0

$("#buttons").html(html);
if (data[0] && data[0].isMenuTitle) {
titleHtml = `<div class=\"menu-title\">${data[0].header}</div>`
startIndex = 1
$("#menuTitle").show()
} else {
$("#menuTitle").hide()
}

$('.button').click(function() {
const target = $(this)
if (!target.hasClass('title') && !target.hasClass('disabled')) {
postData(target.attr('id'));
}
});
};
$("#menuTitle").html(titleHtml)

for (let i = startIndex; i < data.length; i++) {
const item = data[i]
if (!item.hidden) {
const header = item.header
const message = item.txt || item.text
const isMenuHeader = item.isMenuHeader
const isDisabled = item.disabled
const icon = item.icon
images[i] = item
html += getButtonRender(header, message, i, isMenuHeader, isDisabled, icon)
}
}

$("#buttons").html(html)

$(".button").click(function () {
const target = $(this)
if (!target.hasClass("title") && !target.hasClass("disabled")) {
const audio = new Audio("./audio/press.wav");
audio.volume = 0.7;
audio.play();
postData(target.attr("id"))
}
})
}

// so nobody can inject weird stuff into the menu.
// I know it's basic, but it does the job
function escapeHtml(text) {
// If it's not a string, just return it as-is.
if (typeof text !== 'string') return text;
// Replace all the usual suspects with their HTML entities.
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}

const getButtonRender = (header, message = null, id, isMenuHeader, isDisabled, icon) => {
return `
<div class="${isMenuHeader ? "title" : "button"} ${isDisabled ? "disabled" : ""}" id="${id}">
<div class="icon"> <img src=${icon} width=30px onerror="this.onerror=null; this.remove();"> <i class="${icon}" onerror="this.onerror=null; this.remove();"></i> </div>
<div class="column">
<div class="header"> ${header}</div>
${message ? `<div class="text">${message}</div>` : ""}
</div>
</div>
`;
};
const safeHeader = escapeHtml(header);
const safeMessage = message ? escapeHtml(message) : null;
return `
<div class="${isMenuHeader ? "title" : "button"} ${isDisabled ? "disabled" : ""}" id="${id}">
<div class="icon"> <img src=${icon} width=30px onerror="this.onerror=null; this.remove();"> <i class="${icon}" onerror="this.onerror=null; this.remove();"></i> </div>
<div class="column">
<div class="header"> ${safeHeader}</div>
${safeMessage ? `<div class="text">${safeMessage}</div>` : ""}
</div>
</div>
`
}

const closeMenu = () => {
$("#buttons").html(" ");
$('#imageHover').css('display' , 'none');
buttonParams = [];
images = [];
};
$("#menuTitle").html("").hide()
$("#buttons").html(" ")
$("#imageHover").css("display", "none")
images = []
}

const postData = (id) => {
$.post(`https://${GetParentResourceName()}/clickedButton`, JSON.stringify(parseInt(id) + 1));
return closeMenu();
};
$.post(`https://${GetParentResourceName()}/clickedButton`, JSON.stringify(Number.parseInt(id) + 1))
return closeMenu()
}

const cancelMenu = () => {
$.post(`https://${GetParentResourceName()}/closeMenu`);
return closeMenu();
};


$.post(`https://${GetParentResourceName()}/closeMenu`)
return closeMenu()
}

window.addEventListener("message", (event) => {
const data = event.data;
const buttons = data.data;
const action = data.action;
switch (action) {
case "OPEN_MENU":
case "SHOW_HEADER":
return openMenu(buttons);
case "CLOSE_MENU":
return closeMenu();
default:
return;
}
});
const data = event.data
const buttons = data.data
const action = data.action
switch (action) {
case "OPEN_MENU":
case "SHOW_HEADER":
return openMenu(buttons)
case "CLOSE_MENU":
return closeMenu()
default:
return
}
})

window.addEventListener('mousemove', (event) => {
let $target = $(event.target);
if ($target.closest('.button:hover').length && $('.button').is(":visible")) {
let id = event.target.id;
if (!images[id]) return
if (images[id].image) {
$('#image').attr('src', images[id].image);
$('#imageHover').css('display' , 'block');
}
}
else {
$('#imageHover').css('display' , 'none');
window.addEventListener("mousemove", (event) => {
const $target = $(event.target)
if ($target.closest(".button:hover").length && $(".button").is(":visible")) {
const id = event.target.id
if (!images[id]) return
if (images[id].image) {
$("#image").attr("src", images[id].image)
$("#imageHover").css("display", "block")
}
} else {
$("#imageHover").css("display", "none")
}
})

document.onkeyup = function (event) {
const charCode = event.key;
if (charCode == "Escape") {
cancelMenu();
}
};
document.onkeyup = (event) => {
const charCode = event.key
if (charCode == "Escape") {
cancelMenu()
}
}
Loading