fix
26
resources/[standalone]/ps-multijob/.gitignore
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
|
||||
gs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
./svelte-source/node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
84
resources/[standalone]/ps-multijob/README.md
Normal file
|
@ -0,0 +1,84 @@
|
|||
# ps-multijob
|
||||
|
||||

|
||||
|
||||
A script designed with a sleek and modern design for being able to display your current jobs as well as switching between them.
|
||||
|
||||
## Features
|
||||
|
||||
* Configurable ignore certain jobs.
|
||||
* Configurable keybind to open the job menu - J by default.
|
||||
* Configurable max jobs per citizen ID. Unlimited jobs for players with the 'admin' permission.
|
||||
* Configurable white list jobs.
|
||||
* Configurable descriptions per job.
|
||||
* Configurable side (left or right) of the screen you want the ui to show on. Right side by default. (see Config)
|
||||
* Configurable job icon via font awesome icons. Change these icons in the config
|
||||
* Remove someone's job by doing /removejob - Admin only.
|
||||
* Coming later: Admin Tab for job handling.
|
||||
|
||||
## Preview
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
* Rename to ps-multijob. Do not change the name or it will not work.
|
||||
* Import [SQL](https://github.com/Project-Sloth/ps-multijob/blob/main/database.sql) into your database
|
||||
* Ensure to server.cfg
|
||||
|
||||
### Linking to qb-management | Auto Firing
|
||||
|
||||
1. Find the following event
|
||||
|
||||
```txt
|
||||
qb-bossmenu:server:FireEmployee
|
||||
```
|
||||
|
||||
2. Insert the TriggerEvent right under the notification for 'Employee Fired!'. The TriggerEvent should be added twice, once near line 174 and once near line 199.
|
||||
|
||||
```lua
|
||||
TriggerClientEvent('QBCore:Notify', src, "Employee fired!", "success")
|
||||
TriggerEvent('ps-multijob:server:removeJob', target)
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Serversided Exports
|
||||
|
||||
* GetJobs(citizenid)
|
||||
|
||||
Example usage:
|
||||
|
||||
```lua
|
||||
local jobs = exports["ps-multijob"]:GetJobs("citizenid here")
|
||||
```
|
||||
|
||||
* AddJob(citizenid, job, grade)
|
||||
|
||||
Example usage:
|
||||
|
||||
```lua
|
||||
exports["ps-multijob"]:AddJob("citizenid here", "police", 0)
|
||||
```
|
||||
|
||||
* UpdateJobRank(citizenid, job, grade)
|
||||
Example usage:
|
||||
|
||||
```lua
|
||||
exports["ps-multijob"]:UpdateJobRank("citizenid here", "police", 3)
|
||||
```
|
||||
|
||||
* RemoveJob(citizenid, job)
|
||||
|
||||
Example usage:
|
||||
|
||||
```lua
|
||||
exports["ps-multijob"]:RemoveJob("citizenid here", "police")
|
||||
```
|
||||
|
||||
## Credits
|
||||
|
||||
* [xFutte](https://github.com/xFutte)
|
||||
* [Silent](https://github.com/S1lentcodes)
|
||||
* [Jay](https://github.com/jay-fivem)
|
||||
* [Snipe](https://github.com/pushkart2)
|
74
resources/[standalone]/ps-multijob/client/cl_main.lua
Normal file
|
@ -0,0 +1,74 @@
|
|||
local QBCore = exports['qb-core']:GetCoreObject()
|
||||
|
||||
local function GetJobs()
|
||||
local p = promise.new()
|
||||
QBCore.Functions.TriggerCallback('ps-multijob:getJobs', function(result)
|
||||
p:resolve(result)
|
||||
end)
|
||||
return Citizen.Await(p)
|
||||
end
|
||||
|
||||
local function OpenUI()
|
||||
local job = QBCore.Functions.GetPlayerData().job
|
||||
SetNuiFocus(true,true)
|
||||
SendNUIMessage({
|
||||
action = 'sendjobs',
|
||||
activeJob = job["name"],
|
||||
onDuty = job["onduty"],
|
||||
jobs = GetJobs(),
|
||||
side = Config.Side,
|
||||
})
|
||||
end
|
||||
|
||||
RegisterNUICallback('selectjob', function(data, cb)
|
||||
TriggerServerEvent("ps-multijob:changeJob", data["name"], data["grade"])
|
||||
local onDuty = false
|
||||
if data["name"] ~= "police" then onDuty = QBCore.Shared.Jobs[data["name"]].defaultDuty end
|
||||
cb({onDuty = onDuty})
|
||||
end)
|
||||
|
||||
RegisterNUICallback('closemenu', function(data, cb)
|
||||
cb({})
|
||||
SetNuiFocus(false,false)
|
||||
end)
|
||||
|
||||
RegisterNUICallback('removejob', function(data, cb)
|
||||
TriggerServerEvent("ps-multijob:removeJob", data["name"], data["grade"])
|
||||
local jobs = GetJobs()
|
||||
jobs[data["name"]] = nil
|
||||
cb(jobs)
|
||||
end)
|
||||
|
||||
RegisterNUICallback('toggleduty', function(data, cb)
|
||||
cb({})
|
||||
|
||||
local job = QBCore.Functions.GetPlayerData().job.name
|
||||
|
||||
if Config.DenyDuty[job] then
|
||||
TriggerEvent("QBCore:Notify", 'Not allowed to use this station for clock-in.', 'error')
|
||||
return
|
||||
end
|
||||
|
||||
TriggerServerEvent("QBCore:ToggleDuty")
|
||||
end)
|
||||
|
||||
RegisterNetEvent('QBCore:Client:OnJobUpdate', function(JobInfo)
|
||||
SendNUIMessage({
|
||||
action = 'updatejob',
|
||||
name = JobInfo["name"],
|
||||
label = JobInfo["label"],
|
||||
onDuty = JobInfo["onduty"],
|
||||
gradeLabel = JobInfo["grade"].name,
|
||||
grade = JobInfo["grade"].level,
|
||||
salary = JobInfo["payment"],
|
||||
isWhitelist = Config.WhitelistJobs[JobInfo["name"]] or false,
|
||||
description = Config.Descriptions[JobInfo["name"]] or "",
|
||||
icon = Config.FontAwesomeIcons[JobInfo["name"]] or "",
|
||||
})
|
||||
end)
|
||||
|
||||
RegisterCommand("jobmenu", OpenUI, false)
|
||||
|
||||
RegisterKeyMapping('jobmenu', "Show Job Management", "keyboard", "J")
|
||||
|
||||
TriggerEvent('chat:removeSuggestion', '/jobmenu')
|
60
resources/[standalone]/ps-multijob/config.lua
Normal file
|
@ -0,0 +1,60 @@
|
|||
Config = Config or {}
|
||||
|
||||
-- Side of the screen where you want the ui to be on. Can either be "left" or "right"
|
||||
Config.Side = "right"
|
||||
|
||||
Config.MaxJobs = 3
|
||||
Config.IgnoredJobs = {
|
||||
["unemployed"] = true,
|
||||
}
|
||||
|
||||
Config.DenyDuty = {
|
||||
["ambulance"] = true,
|
||||
["police"] = true,
|
||||
}
|
||||
|
||||
Config.WhitelistJobs = {
|
||||
["police"] = true,
|
||||
["ambulance"] = true,
|
||||
["mechanic"] = true,
|
||||
["judge"] = true,
|
||||
["lawyer"] = true,
|
||||
}
|
||||
|
||||
Config.Descriptions = {
|
||||
["police"] = "Shoot some criminals or maybe be a good cop and arrest them",
|
||||
["ambulance"] = "Fix the bullet holes",
|
||||
["mechanic"] = "Fix the bullet holes",
|
||||
["tow"] = "Pickup the tow truck and steal some vehicles",
|
||||
["taxi"] = "Pickup people around the city and drive them to their destination",
|
||||
["bus"] = "Pickup multiple people around the city and drive them to their destination",
|
||||
["realestate"] = "Sell houses or something",
|
||||
["cardealer"] = "Sell cars or something",
|
||||
["judge"] = "Decide if people are guilty",
|
||||
["lawyer"] = "Help the good or the bad",
|
||||
["reporter"] = "Lowkey useless",
|
||||
["trucker"] = "Drive a truck",
|
||||
["garbage"] = "Drive a garbage truck",
|
||||
["vineyard"] = "Get them vines",
|
||||
["admin"] = "Sell them glizzys",
|
||||
}
|
||||
|
||||
-- Change the icons to any free font awesome icon, also add other jobs your server might have to the list
|
||||
-- List: https://fontawesome.com/search?o=r&s=solid
|
||||
Config.FontAwesomeIcons = {
|
||||
["police"] = "fa-solid fa-handcuffs",
|
||||
["ambulance"] = "fa-solid fa-user-doctor",
|
||||
["mechanic"] = "fa-solid fa-wrench",
|
||||
["tow"] = "fa-solid fa-truck-tow",
|
||||
["taxi"] = "fa-solid fa-taxi",
|
||||
["bus"] = "fa-solid fa-bus",
|
||||
["realestate"] = "fa-solid fa-sign-hanging",
|
||||
["cardealer"] = "fa-solid fa-cards",
|
||||
["judge"] = "fa-solid fa-gave",
|
||||
["lawyer"] = "fa-solid fa-gavel",
|
||||
["reporter"] = "fa-solid fa-microphone",
|
||||
["trucker"] = "fa-solid fa-truck-front",
|
||||
["garbage"] = "fa-solid fa-trash-can",
|
||||
["vineyard"] = "fa-solid fa-wine-bottle",
|
||||
["hotdog"] = "fa-solid fa-hotdog",
|
||||
}
|
5
resources/[standalone]/ps-multijob/database.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE IF NOT EXISTS `multijobs` (
|
||||
`citizenid` varchar(100) NOT NULL,
|
||||
`jobdata` text DEFAULT NULL,
|
||||
PRIMARY KEY (`citizenid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
19
resources/[standalone]/ps-multijob/fxmanifest.lua
Normal file
|
@ -0,0 +1,19 @@
|
|||
|
||||
fx_version 'cerulean'
|
||||
|
||||
game 'gta5'
|
||||
|
||||
version '1.1.4'
|
||||
|
||||
shared_script 'config.lua'
|
||||
client_script 'client/cl_*.lua'
|
||||
server_scripts{
|
||||
'@oxmysql/lib/MySQL.lua',
|
||||
'server/sv_*.lua',
|
||||
}
|
||||
|
||||
ui_page 'html/index.html'
|
||||
|
||||
files {
|
||||
'html/*',
|
||||
}
|
1
resources/[standalone]/ps-multijob/html/index.css
Normal file
1
resources/[standalone]/ps-multijob/html/index.html
Normal file
|
@ -0,0 +1 @@
|
|||
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>PS-MultiJob</title><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer"><script type="module" crossorigin src="./index.js"></script><link rel="stylesheet" href="./index.css"></head><body><div id="app"></div></body></html>
|
44
resources/[standalone]/ps-multijob/html/index.js
Normal file
280
resources/[standalone]/ps-multijob/server/sv_main.lua
Normal file
|
@ -0,0 +1,280 @@
|
|||
local QBCore = exports['qb-core']:GetCoreObject()
|
||||
|
||||
local function GetJobs(citizenid)
|
||||
local p = promise.new()
|
||||
MySQL.Async.fetchAll("SELECT jobdata FROM multijobs WHERE citizenid = @citizenid",{
|
||||
["@citizenid"] = citizenid
|
||||
}, function(jobs)
|
||||
if jobs[1] and jobs ~= "[]" then
|
||||
jobs = json.decode(jobs[1].jobdata)
|
||||
else
|
||||
local Player = QBCore.Functions.GetOfflinePlayerByCitizenId(citizenid)
|
||||
local temp = {}
|
||||
if not Config.IgnoredJobs[Player.PlayerData.job.name] then
|
||||
temp[Player.PlayerData.job.name] = Player.PlayerData.job.grade.level
|
||||
MySQL.insert('INSERT INTO multijobs (citizenid, jobdata) VALUES (:citizenid, :jobdata) ON DUPLICATE KEY UPDATE jobdata = :jobdata', {
|
||||
citizenid = citizenid,
|
||||
jobdata = json.encode(temp),
|
||||
})
|
||||
end
|
||||
jobs = temp
|
||||
end
|
||||
p:resolve(jobs)
|
||||
end)
|
||||
return Citizen.Await(p)
|
||||
end
|
||||
exports("GetJobs", GetJobs)
|
||||
|
||||
local function AddJob(citizenid, job, grade)
|
||||
local jobs = GetJobs(citizenid)
|
||||
for ignored in pairs(Config.IgnoredJobs) do
|
||||
if jobs[ignored] then
|
||||
jobs[ignored] = nil
|
||||
end
|
||||
end
|
||||
|
||||
jobs[job] = grade
|
||||
MySQL.insert('INSERT INTO multijobs (citizenid, jobdata) VALUES (:citizenid, :jobdata) ON DUPLICATE KEY UPDATE jobdata = :jobdata', {
|
||||
citizenid = citizenid,
|
||||
jobdata = json.encode(jobs),
|
||||
})
|
||||
end
|
||||
exports("AddJob", AddJob)
|
||||
|
||||
local function UpdatePlayerJob(Player, job, grade)
|
||||
if Player.PlayerData.source ~= nil then
|
||||
Player.Functions.SetJob(job,grade)
|
||||
else -- player is offline
|
||||
local sharedJobData = QBCore.Shared.Jobs[job]
|
||||
if sharedJobData == nil then return end
|
||||
|
||||
local sharedGradeData = sharedJobData.grades[grade]
|
||||
if sharedGradeData == nil then return end
|
||||
|
||||
local isBoss = false
|
||||
if sharedGradeData.isboss then isBoss = true end
|
||||
|
||||
MySQL.update.await("update players set job = @jobData where citizenid = @citizenid", {
|
||||
jobData = json.encode({
|
||||
label = sharedJobData.label,
|
||||
name = job,
|
||||
isboss = isBoss,
|
||||
onduty = sharedJobData.defaultDuty,
|
||||
payment = sharedGradeData.payment,
|
||||
grade = {
|
||||
name = sharedGradeData.name,
|
||||
level = grade,
|
||||
},
|
||||
}),
|
||||
citizenid = Player.PlayerData.citizenid
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local function UpdateJobRank(citizenid, job, grade)
|
||||
local Player = QBCore.Functions.GetOfflinePlayerByCitizenId(citizenid)
|
||||
if Player == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local jobs = GetJobs(citizenid)
|
||||
if jobs[job] == nil then
|
||||
return
|
||||
end
|
||||
|
||||
jobs[job] = grade
|
||||
|
||||
MySQL.update.await("update multijobs set jobdata = :jobdata where citizenid = :citizenid", {
|
||||
citizenid = citizenid,
|
||||
jobdata = json.encode(jobs),
|
||||
})
|
||||
|
||||
-- if the current job matches, then update
|
||||
if Player.PlayerData.job.name == job then
|
||||
UpdatePlayerJob(Player, job, grade)
|
||||
end
|
||||
end
|
||||
exports("UpdateJobRank", UpdateJobRank)
|
||||
|
||||
local function RemoveJob(citizenid, job)
|
||||
local Player = QBCore.Functions.GetPlayerByCitizenId(citizenid)
|
||||
|
||||
if Player == nil then
|
||||
Player = QBCore.Functions.GetOfflinePlayerByCitizenId(citizenid)
|
||||
end
|
||||
|
||||
if Player == nil then return end
|
||||
|
||||
local jobs = GetJobs(citizenid)
|
||||
jobs[job] = nil
|
||||
|
||||
-- Since we removed a job, put player in a new job
|
||||
local foundNewJob = false
|
||||
if Player.PlayerData.job.name == job then
|
||||
for k,v in pairs(jobs) do
|
||||
UpdatePlayerJob(Player, k,v)
|
||||
foundNewJob = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not foundNewJob then
|
||||
UpdatePlayerJob(Player, "unemployed", 0)
|
||||
end
|
||||
|
||||
MySQL.insert('INSERT INTO multijobs (citizenid, jobdata) VALUES (:citizenid, :jobdata) ON DUPLICATE KEY UPDATE jobdata = :jobdata', {
|
||||
citizenid = citizenid,
|
||||
jobdata = json.encode(jobs),
|
||||
})
|
||||
end
|
||||
exports("RemoveJob", RemoveJob)
|
||||
|
||||
QBCore.Commands.Add('removejob', 'Remove Multi Job (Admin Only)', { { name = 'id', help = 'ID of player' }, { name = 'job', help = 'Job Name' } }, false, function(source, args)
|
||||
local source = source
|
||||
if source ~= 0 then
|
||||
if args[1] then
|
||||
local Player = QBCore.Functions.GetPlayer(tonumber(args[1]))
|
||||
if Player then
|
||||
if args[2] then
|
||||
RemoveJob(Player.PlayerData.citizenid, args[2])
|
||||
else
|
||||
TriggerClientEvent("QBCore:Notify", source, "Wrong usage!")
|
||||
end
|
||||
else
|
||||
TriggerClientEvent("QBCore:Notify", source, "Wrong usage!")
|
||||
end
|
||||
else
|
||||
TriggerClientEvent("QBCore:Notify", source, "Wrong usage!")
|
||||
end
|
||||
else
|
||||
TriggerClientEvent("QBCore:Notify", source, "Wrong usage!")
|
||||
end
|
||||
end, 'admin')
|
||||
|
||||
QBCore.Commands.Add('addjob', 'Add Multi Job (Admin Only)', { { name = 'id', help = 'ID of player' }, { name = 'job', help = 'Job Name' }, { name = 'grade', help = 'Job Grade' } }, false, function(source, args)
|
||||
local source = source
|
||||
if source ~= 0 then
|
||||
if args[1] then
|
||||
local Player = QBCore.Functions.GetPlayer(tonumber(args[1]))
|
||||
if Player then
|
||||
if args[2]and args[3] then
|
||||
AddJob(Player.PlayerData.citizenid, args[2], args[3])
|
||||
else
|
||||
TriggerClientEvent("QBCore:Notify", source, "Wrong usage!")
|
||||
end
|
||||
else
|
||||
TriggerClientEvent("QBCore:Notify", source, "Wrong usage!")
|
||||
end
|
||||
else
|
||||
TriggerClientEvent("QBCore:Notify", source, "Wrong usage!")
|
||||
end
|
||||
else
|
||||
TriggerClientEvent("QBCore:Notify", source, "Wrong usage!")
|
||||
end
|
||||
end, 'admin')
|
||||
|
||||
QBCore.Functions.CreateCallback("ps-multijob:getJobs", function(source, cb)
|
||||
local Player = QBCore.Functions.GetPlayer(source)
|
||||
local jobs = GetJobs(Player.PlayerData.citizenid)
|
||||
local multijobs = {}
|
||||
local whitelistedjobs = {}
|
||||
local civjobs = {}
|
||||
local active = {}
|
||||
local getjobs = {}
|
||||
local Players = QBCore.Functions.GetPlayers()
|
||||
|
||||
for i = 1, #Players, 1 do
|
||||
local xPlayer = QBCore.Functions.GetPlayer(Players[i])
|
||||
active[xPlayer.PlayerData.job.name] = 0
|
||||
if active[xPlayer.PlayerData.job.name] and xPlayer.PlayerData.job.onduty then
|
||||
active[xPlayer.PlayerData.job.name] = active[xPlayer.PlayerData.job.name] + 1
|
||||
end
|
||||
end
|
||||
|
||||
for job, grade in pairs(jobs) do
|
||||
if QBCore.Shared.Jobs[job] == nil then
|
||||
print("The job '" .. job .. "' has been removed and is not present in your QBCore jobs. Remove it from the multijob SQL or add it back to your qbcore jobs.lua.")
|
||||
else
|
||||
local online = active[job] or 0
|
||||
getjobs = {
|
||||
name = job,
|
||||
grade = grade,
|
||||
description = Config.Descriptions[job],
|
||||
icon = Config.FontAwesomeIcons[job],
|
||||
label = QBCore.Shared.Jobs[job].label,
|
||||
gradeLabel = QBCore.Shared.Jobs[job].grades[tostring(grade)].name,
|
||||
salary = QBCore.Shared.Jobs[job].grades[tostring(grade)].payment,
|
||||
active = online,
|
||||
}
|
||||
if Config.WhitelistJobs[job] then
|
||||
whitelistedjobs[#whitelistedjobs+1] = getjobs
|
||||
else
|
||||
civjobs[#civjobs+1] = getjobs
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
multijobs = {
|
||||
whitelist = whitelistedjobs,
|
||||
civilian = civjobs,
|
||||
}
|
||||
cb(multijobs)
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-multijob:changeJob",function(cjob, cgrade)
|
||||
local source = source
|
||||
local Player = QBCore.Functions.GetPlayer(source)
|
||||
|
||||
if cjob == "unemployed" and cgrade == 0 then
|
||||
Player.Functions.SetJob(cjob, cgrade)
|
||||
return
|
||||
end
|
||||
|
||||
local jobs = GetJobs(Player.PlayerData.citizenid)
|
||||
for job, grade in pairs(jobs) do
|
||||
if cjob == job and cgrade == grade then
|
||||
Player.Functions.SetJob(job, grade)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent("ps-multijob:removeJob",function(job, grade)
|
||||
local source = source
|
||||
local Player = QBCore.Functions.GetPlayer(source)
|
||||
RemoveJob(Player.PlayerData.citizenid, job)
|
||||
end)
|
||||
|
||||
-- QBCORE EVENTS
|
||||
|
||||
RegisterNetEvent('ps-multijob:server:removeJob', function(targetCitizenId)
|
||||
MySQL.Async.execute('DELETE FROM multijobs WHERE citizenid = ?', { targetCitizenId }, function(affectedRows)
|
||||
if affectedRows > 0 then
|
||||
print('Removed job: ' .. targetCitizenId)
|
||||
else
|
||||
print('Cannot remove job: ' .. targetCitizenId)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('QBCore:Server:OnJobUpdate', function(source, newJob)
|
||||
local source = source
|
||||
local Player = QBCore.Functions.GetPlayer(source)
|
||||
local jobs = GetJobs(Player.PlayerData.citizenid)
|
||||
local amount = 0
|
||||
local setjob = newJob
|
||||
for k,v in pairs(jobs) do
|
||||
amount = amount + 1
|
||||
end
|
||||
|
||||
local maxJobs = Config.MaxJobs
|
||||
if QBCore.Functions.HasPermission(source, "admin") then
|
||||
maxJobs = math.huge
|
||||
end
|
||||
|
||||
if amount < maxJobs and not Config.IgnoredJobs[setjob.name] then
|
||||
local foundOldJob = jobs[setjob.name]
|
||||
if not foundOldJob or foundOldJob ~= setjob.grade.level then
|
||||
AddJob(Player.PlayerData.citizenid, setjob.name, setjob.grade.level)
|
||||
end
|
||||
end
|
||||
end)
|
24
resources/[standalone]/ps-multijob/svelte-source/.gitignore
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
3
resources/[standalone]/ps-multijob/svelte-source/.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"recommendations": ["svelte.svelte-vscode"]
|
||||
}
|
34
resources/[standalone]/ps-multijob/svelte-source/global.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:root {
|
||||
--color-green: #02f1b5;
|
||||
--color-orange: #ff4545;
|
||||
--color-darkestblue: #131121;
|
||||
--color-darkerblue: #222033;
|
||||
--color-darkblue: #424057;
|
||||
--color-white: #ffffff;
|
||||
--color-black: #000000;
|
||||
--color-lightestgrey: #dadada;
|
||||
--color-lightgrey: #cacaca;
|
||||
--color-grey: #797979;
|
||||
--font-color: rgba(var(--theme-white), 0.87);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 2px;
|
||||
background-color: rgba(60, 60, 60, 1);
|
||||
}
|
26
resources/[standalone]/ps-multijob/svelte-source/index.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PS-MultiJob</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
|
||||
integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<link rel="stylesheet" href="./global.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "svelte-source",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"build": "pnpm check && vite build",
|
||||
"preview": "vite preview --host",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"coverage": "vitest run --coverage"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||
"@testing-library/svelte": "^3.1.3",
|
||||
"@tsconfig/svelte": "^2.0.1",
|
||||
"@unocss/preset-uno": "^0.44.5",
|
||||
"@unocss/reset": "^0.44.5",
|
||||
"html-minifier": "^4.0.0",
|
||||
"jsdom": "^20.0.0",
|
||||
"sass": "^1.54.9",
|
||||
"svelte": "^3.49.0",
|
||||
"svelte-check": "^2.8.0",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.4",
|
||||
"unocss": "^0.44.5",
|
||||
"vite": ">=3.2.7",
|
||||
"vite-plugin-windicss": "^1.8.7",
|
||||
"vitest": "^0.18.1"
|
||||
}
|
||||
}
|
2180
resources/[standalone]/ps-multijob/svelte-source/pnpm-lock.yaml
generated
Normal file
|
@ -0,0 +1,44 @@
|
|||
<script lang="ts">
|
||||
import { fly } from 'svelte/transition';
|
||||
import CategoryMenu from './components/CategoryMenu.svelte';
|
||||
import NavBar from './components/NavBar.svelte';
|
||||
import { EventHandler } from './utils/eventHandler';
|
||||
import DebugMode from './stores/debugStore';
|
||||
import PanelStore from './stores/PanelStore';
|
||||
import JobStore from './stores/JobStore';
|
||||
import { mockJobMenuOpen } from './utils/mockEvent';
|
||||
|
||||
const { panelActive, show, side } = PanelStore;
|
||||
const { jobManifest } = JobStore;
|
||||
|
||||
EventHandler();
|
||||
document.onkeyup = PanelStore.handleKeyUp;
|
||||
|
||||
if (DebugMode) {
|
||||
mockJobMenuOpen();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if $show}
|
||||
<main class={"min-h-screen flex"+($side == "right" ? " justify-end ":" ")+(DebugMode ? "bg-dark-200": "bg-transparent")}>
|
||||
{#if $side == "right"}
|
||||
{#if $panelActive != ""}
|
||||
<div in:fly|local="{{x: 500, duration: 500}}" out:fly|local="{{x: 500, duration: 500}}">
|
||||
<CategoryMenu jobArray={$jobManifest[$panelActive] || []} panelName={$panelActive}/>
|
||||
</div>
|
||||
{/if}
|
||||
<NavBar side={$side}/>
|
||||
{:else}
|
||||
<NavBar side={$side}/>
|
||||
{#if $panelActive != ""}
|
||||
<div in:fly|local="{{x: -500, duration: 500}}" out:fly|local="{{x: -500, duration: 500}}">
|
||||
<CategoryMenu jobArray={$jobManifest[$panelActive] || []} panelName={$panelActive}/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</main>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -0,0 +1,41 @@
|
|||
<script lang="ts">
|
||||
import JobCard from './JobCard.svelte';
|
||||
import type { Job } from '../types/types';
|
||||
|
||||
export let jobArray: Array<Job> = [];
|
||||
export let panelName: string = "";
|
||||
|
||||
</script>
|
||||
|
||||
<main class="w-[380px] min-h-screen block pt-[20px] select-none">
|
||||
<div class="text-white px-[28px] pb-4">
|
||||
<p class="category">CATEGORY</p>
|
||||
<p class="category-name text-white block mt-[-5px] font-medium capitalize">
|
||||
{panelName} Jobs
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="max-h-screen overflow-y-auto px-[28px] pb-20">
|
||||
{#each jobArray as job (job.name)}
|
||||
<JobCard name={job.label} nuiName={job.name} nuiRank={job.grade} icon={job.icon} description={job.description}
|
||||
salary={job.salary} rank={job.gradeLabel} active={job.active} category={panelName}/>
|
||||
{/each}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style lang="scss">
|
||||
main {
|
||||
background: var(--color-darkestblue);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.category {
|
||||
font-size: 10pt;
|
||||
color: var(--color-lightestgrey);
|
||||
}
|
||||
|
||||
.category-name {
|
||||
font-size: 15pt;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,163 @@
|
|||
<script lang="ts">
|
||||
import JobDetail from './atoms/JobDetail.svelte';
|
||||
import SalarySVG from './atoms/svgs/SalarySVG.svelte';
|
||||
import RankSVG from './atoms/svgs/RankSVG.svelte';
|
||||
import ActiveSVG from './atoms/svgs/ActiveSVG.svelte';
|
||||
import SelectSVG from './atoms/svgs/SelectSVG.svelte';
|
||||
import CrossMarkSVG from './atoms/svgs/CrossMarkSVG.svelte';
|
||||
import DeleteSVG from './atoms/svgs/DeleteSVG.svelte';
|
||||
import ClockSVG from './atoms/svgs/ClockSVG.svelte';
|
||||
import TaxiSVG from './atoms/svgs/TaxiSVG.svelte';
|
||||
import JobStore from '../stores/JobStore';
|
||||
|
||||
export let name: string;
|
||||
export let nuiName: string;
|
||||
export let icon: string = "";
|
||||
export let description: string = "";
|
||||
export let salary: number;
|
||||
export let rank: string;
|
||||
export let nuiRank: number;
|
||||
export let active: number;
|
||||
export let category: string;
|
||||
|
||||
function getDutyText(onDuty: boolean) {
|
||||
return onDuty ? "On Duty" : "Off Duty";
|
||||
}
|
||||
|
||||
function getSelectText(select: boolean) {
|
||||
return select ? "Selected" : "Unselect";
|
||||
}
|
||||
|
||||
const { activeJob, onDuty, setActiveJob, toggleDuty, unSetActiveJob, deleteJob } = JobStore;
|
||||
|
||||
let isActive: boolean = false;
|
||||
$: isActive = $activeJob == nuiName;
|
||||
$: dutyText = getDutyText($onDuty);
|
||||
|
||||
let onDutyHover: boolean = false;
|
||||
let transitionOnDuty: boolean = false;
|
||||
let transitionOffDuty: boolean = false;
|
||||
|
||||
function handleOnDutyMouseEnter() {
|
||||
dutyText = getDutyText(!$onDuty);
|
||||
onDutyHover = true;
|
||||
}
|
||||
|
||||
function handleOnDutyMouseLeave() {
|
||||
dutyText = getDutyText($onDuty);
|
||||
onDutyHover = false;
|
||||
transitionOnDuty = false;
|
||||
transitionOffDuty = false;
|
||||
}
|
||||
|
||||
function handleDutyChange() {
|
||||
if ($onDuty) {
|
||||
transitionOffDuty = true;
|
||||
transitionOnDuty = false;
|
||||
} else {
|
||||
transitionOnDuty = true;
|
||||
transitionOffDuty = false;
|
||||
}
|
||||
toggleDuty();
|
||||
}
|
||||
|
||||
let selectText: string = "selected";
|
||||
let selectHover: boolean = false;
|
||||
|
||||
function handleOnSelectMouseEnter() {
|
||||
selectText = getSelectText(false);
|
||||
selectHover = true;
|
||||
}
|
||||
|
||||
function handleOnSelectMouseLeave() {
|
||||
selectText = getSelectText(true);
|
||||
selectHover = false;
|
||||
}
|
||||
|
||||
function handleUnSelectJob() {
|
||||
unSetActiveJob();
|
||||
selectHover = false;
|
||||
selectText = "selected";
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="job w-full flex flex-col gap-4 mb-[30px] b-rd-[10px] px-[22px] py-5
|
||||
relative select-none bg-[var(--color-darkerblue)] border border-[var(--color-darkblue)]">
|
||||
<div class="flex flex-row items-center gap-2 text-center">
|
||||
<div class="w-6 text-[var(--color-green)]">
|
||||
{#if icon}
|
||||
<i
|
||||
class="{icon} fa-lg"
|
||||
/>
|
||||
{:else}
|
||||
<svelte:component this={TaxiSVG} />
|
||||
{/if}
|
||||
</div>
|
||||
<p class="text-xl tracking-wide capitalize">
|
||||
{name}
|
||||
</p>
|
||||
<div class="w-7 text-[var(--color-darkblue)] cursor-pointer ml-auto hover:text-[var(--color-orange)]"
|
||||
on:click={() => deleteJob(nuiName, nuiRank, category)}>
|
||||
<svelte:component this={DeleteSVG} />
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-[var(--color-lightestgrey)]">
|
||||
{description}
|
||||
</p>
|
||||
<div class="job-details flex gap-[12px] justify-stretch">
|
||||
<JobDetail icon={SalarySVG} detail="Salary" value={salary} svgSize="w-[0.8rem]"/>
|
||||
<JobDetail icon={RankSVG} detail="Rank" value={rank} svgSize="w-[1.4rem]"/>
|
||||
<JobDetail icon={ActiveSVG} detail="Active" value={active} svgSize="w-[1.1rem]"/>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
{#if !isActive}
|
||||
<button class="bg-[var(--color-green)] flex flex-row h-11 items-center justify-center gap-1 b-rd-[5px] py-[10px] font-medium text-black flex-1 w-full"
|
||||
on:click={() => setActiveJob(nuiName, nuiName, nuiRank)}
|
||||
>
|
||||
<div class="w-4">
|
||||
<svelte:component this={SelectSVG} />
|
||||
</div>
|
||||
<p class="ml-[5px] uppercase tracking-wide">select</p>
|
||||
</button>
|
||||
{/if}
|
||||
{#if isActive}
|
||||
<div class="flex flex-row justify-between gap-2">
|
||||
<button class={"flex flex-1 flex-row gap-2 border-1 b-rd-[5px] justify-center items-center h-11"+
|
||||
(selectHover ? "border-[var(--color-orange)] text-[var(--color-orange)]":"")}
|
||||
on:click={handleUnSelectJob} on:mouseenter={handleOnSelectMouseEnter} on:mouseleave={handleOnSelectMouseLeave}>
|
||||
{#if !selectHover}
|
||||
<div class="w-5">
|
||||
<svelte:component this={SelectSVG}/>
|
||||
</div>
|
||||
{/if}
|
||||
<p class="uppercase tracking-wide">
|
||||
{selectText}
|
||||
</p>
|
||||
</button>
|
||||
<div class="flex-1">
|
||||
<button class={`flex flex-row justify-center items-center gap-1 h-11 border-1 b-rd-[5px] py-[10px] font-medium flex-1 w-full ` +
|
||||
($onDuty ?
|
||||
"border-[var(--color-green)] text-[var(--color-green)] "
|
||||
: "border-[var(--color-orange)] text-[var(--color-orange)] ")+
|
||||
($onDuty && !transitionOnDuty ? "hover:border-[var(--color-orange)] hover:text-[var(--color-orange)]":"")+
|
||||
(!$onDuty && !transitionOffDuty ? "hover:border-[var(--color-green)] hover:text-[var(--color-green)]":"")
|
||||
}
|
||||
on:click={handleDutyChange} on:mouseenter={handleOnDutyMouseEnter} on:mouseleave={handleOnDutyMouseLeave}
|
||||
>
|
||||
{#if ($onDuty && !onDutyHover) || transitionOnDuty}
|
||||
<div class="w-5">
|
||||
<svelte:component this={ClockSVG} />
|
||||
</div>
|
||||
{/if}
|
||||
{#if (!$onDuty && !onDutyHover) || transitionOffDuty}
|
||||
<div class="w-[0.9rem]">
|
||||
<svelte:component this={CrossMarkSVG} />
|
||||
</div>
|
||||
{/if}
|
||||
<p class="ml-[5px] uppercase tracking-wide">{dutyText}</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
|
@ -0,0 +1,23 @@
|
|||
<script lang="ts">
|
||||
import NavItem from './atoms/NavItem.svelte';
|
||||
import PanelStore from '../stores/PanelStore';
|
||||
import type { side } from '../types/types';
|
||||
|
||||
export let side: side;
|
||||
|
||||
const { panelActive, panels } = PanelStore;
|
||||
</script>
|
||||
|
||||
<nav class="w-[80px] min-h-screen nav flex flex-col z-10">
|
||||
<div class="ps-logo w-full h-[80px]"/>
|
||||
{#each $panels as item}
|
||||
<NavItem name={item.name} isActive={item.name == $panelActive} icon={item.icon} {side}/>
|
||||
{/each}
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.nav {
|
||||
background: var(--color-darkerblue);
|
||||
border-left: 1px solid var(--color-darkblue);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
export let icon: any = null;
|
||||
export let detail: string;
|
||||
export let value: string | number;
|
||||
export let svgSize: string;
|
||||
</script>
|
||||
|
||||
<div class="flex flex-1 flex-col items-center gap-2 b-2 b-rd-2 border-[var(--color-darkblue)] pt-[14px] pb-[14px]">
|
||||
<div class="w-full flex justify-center text-white">
|
||||
<div class={svgSize}>
|
||||
<svelte:component this={icon}/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="text-xs">
|
||||
{detail}:
|
||||
<span class="text-[var(--color-green)]">
|
||||
{value}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,38 @@
|
|||
<script lang="ts">
|
||||
import PanelStore from "../../stores/PanelStore";
|
||||
import type { side } from '../../types/types';
|
||||
|
||||
export let icon: any;
|
||||
export let isActive: boolean;
|
||||
export let name: string;
|
||||
export let side: side;
|
||||
|
||||
function navItemClicked(item: string): void {
|
||||
if (isActive) {
|
||||
PanelStore.setActive("");
|
||||
} else {
|
||||
PanelStore.setActive(item);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class={"navitem w-full h-[60px] flex justify-center items-center cursor-pointer duration-200 "+
|
||||
(side == "left" ? "border-l-4 " : "border-r-4 ")+
|
||||
(isActive ? side == "left" ? "border-l-[var(--color-green)] bg-[var(--color-darkestblue)] ": "border-r-[var(--color-green)] bg-[var(--color-darkestblue)] "
|
||||
: side == "left" ? "border-l-transparent ": "border-r-transparent ")}
|
||||
on:click={() => navItemClicked(name)}
|
||||
>
|
||||
<div class="icon">
|
||||
<svelte:component this={icon} color={isActive ? "var(--color-green)" : "var(--color-grey)"}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.icon {
|
||||
width: 40%;
|
||||
color: var(--color-lightestgrey);
|
||||
}
|
||||
.navitem:hover {
|
||||
background-color: var(--color-darkestblue);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,5 @@
|
|||
<svg fill="currentColor" viewBox="0 0 448 512">
|
||||
<path d="M224 256c70.7 0 128-57.31 128-128s-57.3-128-128-128C153.3 0 96 57.31 96 128S153.3 256 224 256zM274.7 304H173.3C77.61
|
||||
304 0 381.6 0 477.3c0 19.14 15.52 34.67 34.66 34.67h378.7C432.5 512 448 496.5 448 477.3C448 381.6 370.4 304 274.7 304z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 311 B |
|
@ -0,0 +1,7 @@
|
|||
<script lang="ts">
|
||||
export let color: string = "black";
|
||||
</script>
|
||||
|
||||
<svg fill={color} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||
<path d="M0 32v448h448V32H0zm316.5 325.2L224 445.9l-92.5-88.7 64.5-184-64.5-86.6h184.9L252 173.2l64.5 184z"/>
|
||||
</svg>
|
|
@ -0,0 +1,6 @@
|
|||
<svg fill="currentColor" viewBox="0 0 512 512">
|
||||
<path d="M256 512C114.6 512 0 397.4 0 256S114.6 0 256 0S512 114.6 512 256s-114.6 256-256 256zM232 120V256c0
|
||||
8 4 15.5 10.7 20l96 64c11 7.4 25.9 4.4 33.3-6.7s4.4-25.9-6.7-33.3L280 243.2V120c0-13.3-10.7-24-24-24s-24
|
||||
10.7-24 24z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 295 B |
|
@ -0,0 +1,5 @@
|
|||
<svg fill="currentColor" viewBox="0 0 320 512">
|
||||
<path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3
|
||||
0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5
|
||||
12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 355 B |
|
@ -0,0 +1,4 @@
|
|||
<svg fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 184 B |
|
@ -0,0 +1,14 @@
|
|||
<svg fill="currentColor" viewBox="0 0 576 512">
|
||||
<path d="M0 48C0 21.49 21.49 0 48 0H336C362.5 0 384 21.49 384 48V207L341.6 224H272C263.2 224 256 231.2 256
|
||||
240V304C256 304.9 256.1 305.7 256.2 306.6C258.5 364.7 280.3 451.4 354.9 508.1C349.1 510.6 342.7 512 336 512H240V432C240
|
||||
405.5 218.5 384 192 384C165.5 384 144 405.5 144 432V512H48C21.49 512 0 490.5 0 464V48zM80 224C71.16 224 64 231.2 64 240V272C64
|
||||
280.8 71.16 288 80 288H112C120.8 288 128 280.8 128 272V240C128 231.2 120.8 224 112 224H80zM160 272C160 280.8 167.2 288 176
|
||||
288H208C216.8 288 224 280.8 224 272V240C224 231.2 216.8 224 208 224H176C167.2 224 160 231.2 160 240V272zM64 144C64 152.8 71.16
|
||||
160 80 160H112C120.8 160 128 152.8 128 144V112C128 103.2 120.8 96 112 96H80C71.16 96 64 103.2 64 112V144zM176 96C167.2 96 160
|
||||
103.2 160 112V144C160 152.8 167.2 160 176 160H208C216.8 160 224 152.8 224 144V112C224 103.2 216.8 96 208 96H176zM256 144C256
|
||||
152.8 263.2 160 272 160H304C312.8 160 320 152.8 320 144V112C320 103.2 312.8 96 304 96H272C263.2 96 256 103.2 256 112V144zM423.1
|
||||
225.7C428.8 223.4 435.2 223.4 440.9 225.7L560.9 273.7C570 277.4 576 286.2 576 296C576 359.3 550.1 464.8 441.2 510.2C435.3 512.6
|
||||
428.7 512.6 422.8 510.2C313.9 464.8 288 359.3 288 296C288 286.2 293.1 277.4 303.1 273.7L423.1 225.7zM432 273.8V461.7C500.2 428.7
|
||||
523.5 362.7 527.4 311.1L432 273.8z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,10 @@
|
|||
<svg fill="currentColor" viewBox="0 0 576 512">
|
||||
<path d="M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4
|
||||
551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9
|
||||
435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9
|
||||
22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9
|
||||
78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6
|
||||
387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7
|
||||
199.3 343.9 194.3 340.5 187.2L287.9 78.95z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 885 B |
|
@ -0,0 +1,13 @@
|
|||
<svg fill="currentColor" viewBox="0 0 320 512">
|
||||
<path d="M160 0C177.7 0 192 14.33 192 32V67.68C193.6 67.89 195.1 68.12 196.7 68.35C207.3 69.93 238.9 75.02 251.9 78.31C268.1
|
||||
82.65 279.4 100.1 275 117.2C270.7 134.3 253.3 144.7 236.1 140.4C226.8 137.1 198.5 133.3 187.3 131.7C155.2 126.9 127.7 129.3
|
||||
108.8 136.5C90.52 143.5 82.93 153.4 80.92 164.5C78.98 175.2 80.45 181.3 82.21 185.1C84.1 189.1 87.79 193.6 95.14 198.5C111.4
|
||||
209.2 136.2 216.4 168.4 225.1L171.2 225.9C199.6 233.6 234.4 243.1 260.2 260.2C274.3 269.6 287.6 282.3 295.8 299.9C304.1 317.7
|
||||
305.9 337.7 302.1 358.1C295.1 397 268.1 422.4 236.4 435.6C222.8 441.2 207.8 444.8 192 446.6V480C192 497.7 177.7 512 160 512C142.3
|
||||
512 128 497.7 128 480V445.1C127.6 445.1 127.1 444.1 126.7 444.9L126.5 444.9C102.2 441.1 62.07 430.6 35 418.6C18.85 411.4 11.58
|
||||
392.5 18.76 376.3C25.94 360.2 44.85 352.9 60.1 360.1C81.9 369.4 116.3 378.5 136.2 381.6C168.2 386.4 194.5 383.6 212.3 376.4C229.2
|
||||
369.5 236.9 359.5 239.1 347.5C241 336.8 239.6 330.7 237.8 326.9C235.9 322.9 232.2 318.4 224.9 313.5C208.6 302.8 183.8 295.6 151.6
|
||||
286.9L148.8 286.1C120.4 278.4 85.58 268.9 59.76 251.8C45.65 242.4 32.43 229.7 24.22 212.1C15.89 194.3 14.08 174.3 17.95 153C25.03
|
||||
114.1 53.05 89.29 85.96 76.73C98.98 71.76 113.1 68.49 128 66.73V32C128 14.33 142.3 0 160 0V0z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,7 @@
|
|||
<svg fill="currentColor" x="0px" y="0px" viewBox="0 0 562.7 502.7" xml:space="preserve">
|
||||
<path d="M547.3,112.1c-98,98.1-196,196.2-294,294.2c-11.9,11.9-23.7,23.8-35.6,35.6c-6,6-9.3,6-15.3,0
|
||||
C141.5,381.1,80.7,320.2,19.8,259.3c-6-6-6-9.2,0-15.2c13.6-13.6,27.2-27.2,40.8-40.8c6-6,9.2-6,15.2,0
|
||||
c43.2,43.2,86.5,86.4,129.7,129.7c1.3,1.3,2.4,3,3.8,4.8c2-1.9,3.4-3.1,4.7-4.4c90.5-90.5,181-181,271.5-271.5
|
||||
c7.7-7.7,10-7.7,17.8,0.1c12.7,12.7,25.5,25.5,38.2,38.2c2.1,2.1,3.9,4.4,5.8,6.6C547.3,108.6,547.3,110.4,547.3,112.1z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 539 B |
|
@ -0,0 +1,10 @@
|
|||
<svg fill="currentColor" viewBox="0 0 576 512">
|
||||
<path d="M352 0C369.7 0 384 14.33 384 32V64L384 64.15C422.6 66.31 456.3 91.49 469.2 128.3L504.4 228.8C527.6 238.4 544
|
||||
261.3 544 288V480C544 497.7 529.7 512 512 512H480C462.3 512 448 497.7 448 480V432H128V480C128 497.7 113.7 512 96 512H64C46.33
|
||||
512 32 497.7 32 480V288C32 261.3 48.36 238.4 71.61 228.8L106.8 128.3C119.7 91.49 153.4 66.31 192 64.15L192 64V32C192 14.33
|
||||
206.3 0 224 0L352 0zM197.4 128C183.8 128 171.7 136.6 167.2 149.4L141.1 224H434.9L408.8 149.4C404.3 136.6 392.2 128 378.6
|
||||
128H197.4zM128 352C145.7 352 160 337.7 160 320C160 302.3 145.7 288 128 288C110.3 288 96 302.3 96 320C96 337.7 110.3 352
|
||||
128 352zM448 288C430.3 288 416 302.3 416 320C416 337.7 430.3 352 448 352C465.7 352 480 337.7 480 320C480 302.3 465.7 288
|
||||
448 288z"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 824 B |
|
@ -0,0 +1,16 @@
|
|||
<script lang="ts">
|
||||
export let color: string = "black";
|
||||
</script>
|
||||
|
||||
<svg fill={color} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<path d="M152.1 38.16C161.9 47.03 162.7 62.2 153.8 72.06L81.84 152.1C77.43 156.9 71.21 159.8 64.63 159.1C58.05
|
||||
160.2 51.69 157.6 47.03 152.1L7.029 112.1C-2.343 103.6-2.343 88.4 7.029 79.03C16.4 69.66 31.6 69.66 40.97 79.03L63.08
|
||||
101.1L118.2 39.94C127 30.09 142.2 29.29 152.1 38.16V38.16zM152.1 198.2C161.9 207 162.7 222.2 153.8 232.1L81.84 312.1C77.43
|
||||
316.9 71.21 319.8 64.63 319.1C58.05 320.2 51.69 317.6 47.03 312.1L7.029 272.1C-2.343 263.6-2.343 248.4 7.029 239C16.4
|
||||
229.7 31.6 229.7 40.97 239L63.08 261.1L118.2 199.9C127 190.1 142.2 189.3 152.1 198.2V198.2zM224 96C224 78.33 238.3 64
|
||||
256 64H480C497.7 64 512 78.33 512 96C512 113.7 497.7 128 480 128H256C238.3 128 224 113.7 224 96V96zM224 256C224 238.3
|
||||
238.3 224 256 224H480C497.7 224 512 238.3 512 256C512 273.7 497.7 288 480 288H256C238.3 288 224 273.7 224 256zM160 416C160
|
||||
398.3 174.3 384 192 384H480C497.7 384 512 398.3 512 416C512 433.7 497.7 448 480 448H192C174.3 448 160 433.7 160 416zM0
|
||||
416C0 389.5 21.49 368 48 368C74.51 368 96 389.5 96 416C96 442.5 74.51 464 48 464C21.49 464 0 442.5 0 416z"
|
||||
/>
|
||||
</svg>
|
|
@ -0,0 +1,9 @@
|
|||
import App from './App.svelte'
|
||||
import 'uno.css'
|
||||
import '@unocss/reset/tailwind.css'
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById('app')
|
||||
})
|
||||
|
||||
export default app
|
|
@ -0,0 +1,120 @@
|
|||
import { writable, Writable, get } from "svelte/store";
|
||||
import fetchNUI from '../utils/fetch';
|
||||
import type { Job, JobManifest, side, nuiUpdateJobMessage } from '../types/types';
|
||||
import PanelStore from "./PanelStore";
|
||||
|
||||
export interface nuiOpenMessage {
|
||||
activeJob: string;
|
||||
onDuty: boolean;
|
||||
jobs: JobManifest;
|
||||
side: side;
|
||||
}
|
||||
|
||||
interface JobState {
|
||||
jobManifest: Writable<JobManifest>;
|
||||
activeJob: Writable<string>;
|
||||
onDuty: Writable<boolean>;
|
||||
}
|
||||
|
||||
const store = () => {
|
||||
const JobStore: JobState = {
|
||||
jobManifest: writable({
|
||||
"civilian": [],
|
||||
"whitelist": [],
|
||||
}),
|
||||
activeJob: writable("police person"),
|
||||
onDuty: writable(false),
|
||||
}
|
||||
|
||||
const methods = {
|
||||
deleteJob(nuiName: string, nuiRank: number, category: string) {
|
||||
fetchNUI("removejob", {
|
||||
name: nuiName,
|
||||
grade: nuiRank,
|
||||
});
|
||||
// Remove job from list
|
||||
JobStore.jobManifest.update((state) => {
|
||||
state[category] = state[category].filter((element: Job) => element.name != nuiName);
|
||||
return state;
|
||||
});
|
||||
},
|
||||
receiveOpenMessage(data: nuiOpenMessage) {
|
||||
JobStore.jobManifest.set(data.jobs);
|
||||
JobStore.activeJob.set(data.activeJob);
|
||||
JobStore.onDuty.set(data.onDuty);
|
||||
PanelStore.side.set(data.side || "right");
|
||||
},
|
||||
recieveUpdateJob(data: nuiUpdateJobMessage) {
|
||||
const activeJob: string = get(JobStore.activeJob);
|
||||
if (activeJob == data.name) {
|
||||
JobStore.onDuty.set(data.onDuty);
|
||||
}
|
||||
|
||||
JobStore.jobManifest.update((state) => {
|
||||
function updateJob(kind: "whitelist" | "civilian", index: number) {
|
||||
let changeJob = state[kind][index];
|
||||
changeJob.grade = data.grade;
|
||||
changeJob.gradeLabel = data.gradeLabel;
|
||||
changeJob.salary = data.salary;
|
||||
}
|
||||
|
||||
function newJob(): Job {
|
||||
return {
|
||||
name: data.name,
|
||||
label: data.label,
|
||||
description: data.description,
|
||||
salary: data.salary,
|
||||
gradeLabel: data.gradeLabel,
|
||||
grade: data.grade,
|
||||
active: 0,
|
||||
icon: data.icon,
|
||||
}
|
||||
}
|
||||
|
||||
let findSameName = (job: Job) => {
|
||||
return job.name == data.name
|
||||
}
|
||||
|
||||
const accessString: "civilian" | "whitelist" = data.isWhitelist ? "whitelist" : "civilian";
|
||||
let index = state[accessString]?.findIndex(findSameName);
|
||||
|
||||
if (index != -1) {
|
||||
updateJob(accessString, index);
|
||||
} else {
|
||||
state[accessString] = [...state[accessString], newJob()]
|
||||
}
|
||||
|
||||
return state;
|
||||
})
|
||||
},
|
||||
async setActiveJob(jobName: string, nuiName: string, nuiRank: number) {
|
||||
JobStore.activeJob.set(jobName);
|
||||
// Needs to give back onDuty
|
||||
let data = await fetchNUI("selectjob", {
|
||||
name: nuiName,
|
||||
grade: nuiRank,
|
||||
});
|
||||
JobStore.onDuty.set(data?.onDuty);
|
||||
},
|
||||
unSetActiveJob() {
|
||||
JobStore.activeJob.set("");
|
||||
JobStore.onDuty.set(false);
|
||||
// Unselect current job by setting player to unemployed
|
||||
fetchNUI("selectjob", {
|
||||
name: 'unemployed',
|
||||
grade: 0,
|
||||
});
|
||||
},
|
||||
toggleDuty() {
|
||||
JobStore.onDuty.update(state => !state);
|
||||
fetchNUI('toggleduty', null);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...JobStore,
|
||||
...methods
|
||||
}
|
||||
}
|
||||
|
||||
export default store();
|
|
@ -0,0 +1,62 @@
|
|||
import { writable, Writable } from "svelte/store";
|
||||
import type { side } from '../types/types';
|
||||
import fetchNUI from '../utils/fetch';
|
||||
import CivilianSVG from '../components/atoms/svgs/CivilianSVG.svelte';
|
||||
import WhiteListSVG from '../components/atoms/svgs/WhitelistSVG.svelte';
|
||||
|
||||
interface panel {
|
||||
name: string;
|
||||
icon: any;
|
||||
}
|
||||
|
||||
interface PanelState {
|
||||
show: Writable<boolean>;
|
||||
panelActive: Writable<string>;
|
||||
panels: Writable<Array<panel>>;
|
||||
side: Writable<side>;
|
||||
}
|
||||
|
||||
const panels: Array<panel> = [
|
||||
{
|
||||
name: "whitelist",
|
||||
icon: WhiteListSVG,
|
||||
},
|
||||
{
|
||||
name: "civilian",
|
||||
icon: CivilianSVG,
|
||||
}
|
||||
]
|
||||
|
||||
const store = () => {
|
||||
const PanelStore: PanelState = {
|
||||
panelActive: writable(""),
|
||||
panels: writable(panels),
|
||||
show: writable(false),
|
||||
side: writable("right"),
|
||||
}
|
||||
|
||||
const methods = {
|
||||
handleKeyUp(event: KeyboardEvent) {
|
||||
if (event.key == "Escape") {
|
||||
methods.setShow(false);
|
||||
fetchNUI("closemenu", null);
|
||||
}
|
||||
},
|
||||
setActive(name: string) {
|
||||
PanelStore.panelActive.set(name);
|
||||
},
|
||||
setShow(show: boolean) {
|
||||
PanelStore.show.set(show);
|
||||
},
|
||||
setSide(side: side) {
|
||||
PanelStore.side.set(side);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...PanelStore,
|
||||
...methods
|
||||
}
|
||||
}
|
||||
|
||||
export default store();
|
|
@ -0,0 +1,2 @@
|
|||
const debugMode: boolean = import.meta.env.DEV;
|
||||
export default debugMode;
|
|
@ -0,0 +1,7 @@
|
|||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
describe('function()', async () => {
|
||||
test('behavior', () => {
|
||||
expect(1).toBe(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
export interface Job {
|
||||
name: string;
|
||||
label: string;
|
||||
description: string;
|
||||
salary: number;
|
||||
gradeLabel: string;
|
||||
grade: number;
|
||||
active: number;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface nuiUpdateJobMessage extends Omit<Job, "active"> {
|
||||
isWhitelist: boolean;
|
||||
onDuty: boolean;
|
||||
}
|
||||
|
||||
export interface JobManifest {
|
||||
"whitelist": Array<Job>;
|
||||
"civilian": Array<Job>;
|
||||
}
|
||||
|
||||
export type side = "left" | "right";
|
|
@ -0,0 +1,33 @@
|
|||
import { onMount, onDestroy } from "svelte";
|
||||
import JobStore from '../stores/JobStore';
|
||||
import PanelStore from '../stores/PanelStore';
|
||||
|
||||
interface nuiMessage {
|
||||
data: {
|
||||
action: string,
|
||||
[key: string]: any,
|
||||
},
|
||||
}
|
||||
|
||||
export function EventHandler() {
|
||||
function mainEvent(event: nuiMessage) {
|
||||
switch (event.data.action) {
|
||||
case "sendjobs":
|
||||
JobStore.receiveOpenMessage(event.data as any);
|
||||
PanelStore.setShow(true);
|
||||
break;
|
||||
case "updatejob":
|
||||
JobStore.recieveUpdateJob(event.data as any);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => window.addEventListener("message", mainEvent));
|
||||
onDestroy(() => window.removeEventListener("message", mainEvent));
|
||||
}
|
||||
|
||||
export function handleKeyUp(event: KeyboardEvent) {
|
||||
const charCode = event.key;
|
||||
if (charCode == "Escape") {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
export default async function fetchNui(eventName: string, data: unknown = {}) {
|
||||
const options = {
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=UTF-8",
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
};
|
||||
|
||||
const getResourceName = () => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
return window.GetParentResourceName();
|
||||
} catch(err) {
|
||||
return "ps-multijob";
|
||||
}
|
||||
}
|
||||
|
||||
const resourceName = getResourceName();
|
||||
|
||||
try {
|
||||
const resp = await fetch(`https://${resourceName}/${eventName}`, options);
|
||||
return await resp.json();
|
||||
} catch(err) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
import type { JobManifest } from '../types/types';
|
||||
import type { nuiOpenMessage } from '../stores/JobStore';
|
||||
|
||||
export default function mockEventCall(data: unknown = {}) {
|
||||
window.dispatchEvent(
|
||||
new MessageEvent("message", {data})
|
||||
);
|
||||
};
|
||||
|
||||
export function exampleCall() {
|
||||
setTimeout(() => {
|
||||
mockEventCall({
|
||||
action: 'show',
|
||||
data: {
|
||||
header: "Some Header!",
|
||||
},
|
||||
});
|
||||
}, 100);
|
||||
};
|
||||
|
||||
export function mockJobMenuOpen() {
|
||||
const mockJobManifest: JobManifest = {
|
||||
"whitelist": [
|
||||
{
|
||||
name: "police person",
|
||||
label: "police person",
|
||||
description: `Generate Lorem lpsum placeholder text.
|
||||
Select the number of characters, words, sentences or paragraphs, and hit generate!`,
|
||||
salary: 250,
|
||||
gradeLabel: "Regular",
|
||||
grade: 0,
|
||||
active: 0,
|
||||
icon: "fa-solid fa-trash-can",
|
||||
},
|
||||
{
|
||||
name: "police chief",
|
||||
label: "police chief",
|
||||
description: "Blah blah blah",
|
||||
salary: 500,
|
||||
gradeLabel: "Boss",
|
||||
grade: 0,
|
||||
active: 1,
|
||||
icon: "",
|
||||
},
|
||||
{
|
||||
name: "police chief2",
|
||||
label: "police chief2",
|
||||
description: "Blah blah blah",
|
||||
salary: 500,
|
||||
gradeLabel: "Boss",
|
||||
grade: 0,
|
||||
active: 1,
|
||||
icon: "",
|
||||
},
|
||||
{
|
||||
name: "police chief3",
|
||||
label: "police chief3",
|
||||
description: "Blah blah blah",
|
||||
salary: 500,
|
||||
gradeLabel: "Boss",
|
||||
grade: 0,
|
||||
active: 1,
|
||||
icon: "",
|
||||
},
|
||||
{
|
||||
name: "police chief4",
|
||||
label: "police chief4",
|
||||
description: "Blah blah blah",
|
||||
salary: 500,
|
||||
gradeLabel: "Boss",
|
||||
grade: 0,
|
||||
active: 1,
|
||||
icon: "",
|
||||
},
|
||||
],
|
||||
"civilian": [
|
||||
{
|
||||
name: "taxi driver",
|
||||
label: "taxi driver",
|
||||
description: `Generate Lorem lpsum placeholder text.
|
||||
Select the number of characters, words, sentences or paragraphs, and hit generate!`,
|
||||
salary: 150,
|
||||
gradeLabel: "Regular",
|
||||
grade: 0,
|
||||
active: 0,
|
||||
icon: "",
|
||||
},
|
||||
{
|
||||
name: "murdershot1",
|
||||
label: "murdershot1",
|
||||
description: "Take people's order and serve them food",
|
||||
salary: 100,
|
||||
gradeLabel: "Cashier",
|
||||
grade: 0,
|
||||
active: 0,
|
||||
icon: "",
|
||||
},
|
||||
{
|
||||
name: "murdershot2",
|
||||
label: "murdershot2",
|
||||
description: "Take people's order and serve them food",
|
||||
salary: 100,
|
||||
gradeLabel: "Cashier",
|
||||
grade: 0,
|
||||
active: 0,
|
||||
icon: "",
|
||||
},
|
||||
{
|
||||
name: "murdershot3",
|
||||
label: "murdershot3",
|
||||
description: "Take people's order and serve them food",
|
||||
salary: 100,
|
||||
gradeLabel: "Cashier",
|
||||
grade: 0,
|
||||
active: 0,
|
||||
icon: "",
|
||||
}
|
||||
],
|
||||
}
|
||||
setTimeout(() => {
|
||||
let sendData: nuiOpenMessage = {
|
||||
activeJob: "murdershot1",
|
||||
jobs: mockJobManifest,
|
||||
onDuty: true,
|
||||
side: "right",
|
||||
}
|
||||
mockEventCall({
|
||||
action: 'sendjobs',
|
||||
...sendData,
|
||||
});
|
||||
}, 1000);
|
||||
}
|
2
resources/[standalone]/ps-multijob/svelte-source/src/vite-env.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
|
@ -0,0 +1,7 @@
|
|||
import sveltePreprocess from 'svelte-preprocess'
|
||||
|
||||
export default {
|
||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: sveltePreprocess()
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
* Note that setting allowJs false does not prevent the use
|
||||
* of JS in `.svelte` files.
|
||||
*/
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
import { minify } from "html-minifier";
|
||||
import Unocss from 'unocss/vite'
|
||||
import presetUno from '@unocss/preset-uno'
|
||||
|
||||
const minifyHtml = () => {
|
||||
return {
|
||||
name: 'html-transform',
|
||||
transformIndexHtml(html) {
|
||||
return minify(html, {
|
||||
collapseWhitespace: true,
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const isProduction = mode === 'production';
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
Unocss({
|
||||
presets: [ presetUno() ],
|
||||
}),
|
||||
svelte(),
|
||||
isProduction && minifyHtml(),
|
||||
],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
},
|
||||
base: './', // fivem nui needs to have local dir reference
|
||||
build: {
|
||||
minify: isProduction,
|
||||
emptyOutDir: true,
|
||||
outDir: '../html',
|
||||
assetsDir: './',
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// By not having hashes in the name, you don't have to update the manifest, yay!
|
||||
entryFileNames: `[name].js`,
|
||||
chunkFileNames: `[name].js`,
|
||||
assetFileNames: `[name].[ext]`
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|