From ce302157ff581f761473c8388b9248db521f5126 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Mon, 21 Aug 2023 00:51:06 +0200 Subject: [PATCH 01/43] feat: support miscs alongside extras --- resource/client/commands.lua | 39 ++++++++++++++ resource/client/lights.lua | 96 ++++++++++++++++++++++++++++----- resource/client/menu.lua | 23 ++++++-- resource/config.example.lua | 1 + resource/server/parseVCF.lua | 40 ++++++++++++-- resource/shared/funcs.lua | 101 +++++++++++++++++++++++++++++++++++ 6 files changed, 282 insertions(+), 18 deletions(-) diff --git a/resource/client/commands.lua b/resource/client/commands.lua index c142123..53880c4 100644 --- a/resource/client/commands.lua +++ b/resource/client/commands.lua @@ -6,6 +6,7 @@ AddEventHandler('kjELS:toggleExtra', function(vehicle, extra) end extra = tonumber(extra) or -1 + local model = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)) if not DoesExtraExist(vehicle, extra) then @@ -33,3 +34,41 @@ RegisterCommand('extra', function(source, args) TriggerEvent('kjELS:toggleExtra', vehicle, extra) end) + +RegisterNetEvent('kjELS:toggleMisc') +AddEventHandler('kjELS:toggleMisc', function(vehicle, misc) + if not vehicle or not misc then + CancelEvent() + return + end + + local model = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)) + + if not DoesMiscExist(vehicle, misc) then + print('Misc ' .. ConvertMiscIdToName(misc) .. ' does not exist on your ' .. model .. '!') + CancelEvent() + return + end + + local index = IsVehicleMiscTurnedOn(vehicle, misc) and 0 or -1 + + -- toggle the misc + SetVehicleModKit(vehicle, 0) + -- TODO: respect custom wheel setting + SetVehicleMod(vehicle, misc, index, false) +end) + +RegisterCommand('misc', function(source, args) + local ped = PlayerPedId() + if not IsPedInAnyVehicle(ped) then return end + + local vehicle = GetVehiclePedIsIn(ped) + local misc = args[1] or -1 + + if not string.match(misc, '%a') then + print('Misc must be a single letter!') + return + end + + TriggerEvent('kjELS:toggleMisc', vehicle, ConvertMiscNameToId(misc)) +end) diff --git a/resource/client/lights.lua b/resource/client/lights.lua index 5a1d45d..e42f3e0 100644 --- a/resource/client/lights.lua +++ b/resource/client/lights.lua @@ -1,3 +1,16 @@ +local function ToggleExtra(vehicle, extra, toggle) + local value = toggle and 0 or 1 + + SetVehicleAutoRepairDisabled(vehicle, true) + SetVehicleExtra(vehicle, extra, value) +end + +local function ToggleMisc(vehicle, misc, toggle) + SetVehicleModKit(vehicle, 0) + -- TODO: respect custom wheel setting + SetVehicleMod(vehicle, misc, toggle, false) +end + local function SetLightStage(vehicle, stage, toggle) local ELSvehicle = kjEnabledVehicles[vehicle] local VCFdata = kjxmlData[GetCarHash(vehicle)] @@ -5,8 +18,9 @@ local function SetLightStage(vehicle, stage, toggle) -- get the pattern data local patternData = VCFdata.patterns[ConvertStageToPattern(stage)] - -- reset all extras + -- reset all extras and miscs TriggerEvent('kjELS:resetExtras', vehicle) + TriggerEvent('kjELS:resetMiscs', vehicle) -- set the light state ELSvehicle[stage] = toggle @@ -66,7 +80,10 @@ local function SetLightStage(vehicle, stage, toggle) -- keep the engine on whilst the lights are activated SetVehicleEngineOn(vehicle, true, true, false) - local lastFlash = {} + local lastFlash = { + extras = {}, + miscs = {}, + } for _, flash in ipairs(patternData) do if ELSvehicle[stage] then @@ -75,24 +92,37 @@ local function SetLightStage(vehicle, stage, toggle) SetVehicleAutoRepairDisabled(vehicle, true) -- turn the extra on - SetVehicleExtra(vehicle, extra, 0) + ToggleExtra(vehicle, extra, true) -- save the extra as last flashed - table.insert(lastFlash, extra) + table.insert(lastFlash.extras, extra) + end + + for _, misc in ipairs(flash['miscs']) do + -- turn the misc on + ToggleMisc(vehicle, misc, true) + + -- save the misc as last flashed + table.insert(lastFlash.miscs, misc) end Citizen.Wait(flash.duration) end - -- turn off the last flashed extras - for _, v in ipairs(lastFlash) do + -- turn off the last flashed lights + for _, v in ipairs(lastFlash.extras) do -- disable auto repairs SetVehicleAutoRepairDisabled(vehicle, true) - SetVehicleExtra(vehicle, v, 1) + ToggleExtra(vehicle, v, false) end - lastFlash = {} + for _, v in ipairs(lastFlash.miscs) do + ToggleMisc(vehicle, v, false) + end + + lastFlash.extras = {} + lastFlash.miscs = {} end Citizen.Wait(0) @@ -124,7 +154,31 @@ AddEventHandler('kjELS:resetExtras', function(vehicle) SetVehicleAutoRepairDisabled(vehicle, true) -- disable the extra - SetVehicleExtra(vehicle, extra, true) + ToggleExtra(vehicle, extra, false) + end + end +end) + +RegisterNetEvent('kjELS:resetMiscs') +AddEventHandler('kjELS:resetMiscs', function(vehicle) + if not vehicle then + CancelEvent() + return + end + + local model = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)) + + if not SetContains(kjxmlData, model) then + CancelEvent() + return + end + + -- loop through all miscs + for misc, info in pairs(kjxmlData[model].miscs) do + -- check if we can control this misc + if info.enabled == true then + -- disable the misc + ToggleMisc(vehicle, misc, false) end end end) @@ -249,8 +303,9 @@ AddEventHandler('kjELS:updateIndicators', function(dir, toggle) end end) -local function CreateEnviromentLight(vehicle, extra, offset, color) - local boneIndex = GetEntityBoneIndexByName(vehicle, 'extra_' .. extra) +local function CreateEnviromentLight(vehicle, light, offset, color) + -- local boneIndex = GetEntityBoneIndexByName(vehicle, 'extra_' .. extra) + local boneIndex = GetEntityBoneIndexByName(vehicle, light.type .. '_' .. tostring(light.name)) local coords = GetWorldPositionOfEntityBone(vehicle, boneIndex) local position = coords + offset @@ -286,9 +341,26 @@ Citizen.CreateThread(function() for extra, info in pairs(data.extras) do if IsVehicleExtraTurnedOn(vehicle, extra) and info.env_light then local offset = vector3(info.env_pos.x, info.env_pos.y, info.env_pos.z) + local light = { + type = 'extra', + name = extra + } + + -- flash on walls + CreateEnviromentLight(vehicle, light, offset, info.env_color) + end + end + + for misc, info in pairs(data.miscs) do + if IsVehicleMiscTurnedOn(vehicle, misc) and info.env_light then + local offset = vector3(info.env_pos.x, info.env_pos.y, info.env_pos.z) + local light = { + type = 'misc', + name = ConvertMiscIdToName(misc) + } -- flash on walls - CreateEnviromentLight(vehicle, extra, offset, info.env_color) + CreateEnviromentLight(vehicle, light, offset, info.env_color) end end end diff --git a/resource/client/menu.lua b/resource/client/menu.lua index d6ad771..1e9c932 100644 --- a/resource/client/menu.lua +++ b/resource/client/menu.lua @@ -6,10 +6,10 @@ local function AddHighBeamMenuEntry(vehicle) end end -local function AddStaticExtraEntries(vehicle) +local function AddStaticsEntries(vehicle) local statics = kjxmlData[GetCarHash(vehicle)].statics - for extra, info in spairs(statics) do + for extra, info in spairs(statics.extras) do local name = info.name or ('Extra ' .. extra) local checked = IsVehicleExtraTurnedOn(vehicle, extra) and true or false local extraExists = DoesExtraExist(vehicle, extra) @@ -28,6 +28,23 @@ local function AddStaticExtraEntries(vehicle) end end end + + for misc, info in spairs(statics.miscs) do + local name = info.name or ('Misc ' .. ConvertMiscIdToName(misc)) + local checked = IsVehicleMiscTurnedOn(vehicle, misc) + local miscExists = DoesMiscExist(vehicle, misc) + + if WarMenu.CheckBox(name, checked) and miscExists then + -- toggle the misc + TriggerEvent('kjELS:toggleMisc', vehicle, misc) + end + + if not miscExists then + if WarMenu.IsItemHovered() then + WarMenu.ToolTip(Config.Translations.VehicleControlMenu.MiscDoesNotExist) + end + end + end end local function ShowMainMenu() @@ -40,7 +57,7 @@ local function ShowMainMenu() if WarMenu.Begin('main') then AddHighBeamMenuEntry(vehicle) - AddStaticExtraEntries(vehicle) + AddStaticsEntries(vehicle) WarMenu.End() else return end diff --git a/resource/config.example.lua b/resource/config.example.lua index 7518012..8380979 100644 --- a/resource/config.example.lua +++ b/resource/config.example.lua @@ -46,6 +46,7 @@ Config.Translations = { VehicleControlMenu = { MenuTitle = 'Vehicle Control Menu', ExtraDoesNotExist = 'This extra does not exist on your vehicle!', + MiscDoesNotExist = 'This misc does not exist on your vehicle!', FlashingHighBeam = 'Flashing high beam', } } diff --git a/resource/server/parseVCF.lua b/resource/server/parseVCF.lua index 4edec7f..b2798e2 100644 --- a/resource/server/parseVCF.lua +++ b/resource/server/parseVCF.lua @@ -8,7 +8,11 @@ function ParseVCF(xml, fileName) vcf.patterns.secondary = {} vcf.patterns.rearreds = {} vcf.extras = {} - vcf.statics = {} + vcf.miscs = {} + vcf.statics = { + extras = {}, + miscs = {}, + } vcf.sounds = {} for i = 1, #xml.root.el do @@ -46,6 +50,25 @@ function ParseVCF(xml, fileName) vcf.extras[extra].env_pos['z'] = tonumber(elem.attr['OffsetZ'] or 0.0) vcf.extras[extra].env_color = string.upper(elem.attr['Color'] or 'RED') end + elseif string.find(elem.name, 'Misc', 1) then + local misc = ConvertMiscNameToId(string.sub(elem.name, -1)) + + vcf.miscs[misc] = {} + vcf.miscs[misc].enabled = elem.attr['IsElsControlled'] == 'true' + vcf.miscs[misc].env_light = false + vcf.miscs[misc].env_pos = {} + vcf.miscs[misc].env_pos['x'] = 0 + vcf.miscs[misc].env_pos['y'] = 0 + vcf.miscs[misc].env_pos['z'] = 0 + vcf.miscs[misc].env_color = 'RED' + + if elem.attr['AllowEnvLight'] == 'true' then + vcf.miscs[misc].env_light = true + vcf.miscs[misc].env_pos['x'] = tonumber(elem.attr['OffsetX'] or 0.0) + vcf.miscs[misc].env_pos['y'] = tonumber(elem.attr['OffsetY'] or 0.0) + vcf.miscs[misc].env_pos['z'] = tonumber(elem.attr['OffsetZ'] or 0.0) + vcf.miscs[misc].env_color = string.upper(elem.attr['Color'] or 'RED') + end end end end @@ -58,9 +81,14 @@ function ParseVCF(xml, fileName) local extra = tonumber(string.sub(elem.name, -2)) if extra then - vcf.statics[extra] = {} - vcf.statics[extra].name = elem.attr['Name'] + vcf.statics.extras[extra] = {} + vcf.statics.extras[extra].name = elem.attr['Name'] end + elseif string.upper(string.sub(elem.name, 1, -2)) == 'MISC' then + local misc = ConvertMiscNameToId(string.sub(elem.name, -1)) + + vcf.statics.miscs[misc] = {} + vcf.statics.miscs[misc].name = elem.attr['Name'] end end end @@ -129,6 +157,7 @@ function ParseVCF(xml, fileName) if tag == 'FLASH' then vcf.patterns[type][id] = {} vcf.patterns[type][id].extras = {} + vcf.patterns[type][id].miscs = {} vcf.patterns[type][id].duration = tonumber(flash.attr['Duration'] or '100') for extra in string.gmatch(flash.attr['Extras'] or '', '([0-9]+)') do @@ -139,6 +168,11 @@ function ParseVCF(xml, fileName) table.insert(vcf.patterns[type][id].extras, tonumber(extra)) end + for misc in string.gmatch(flash.attr['Miscs'] or '', '([a-z]+)') do + -- insert misc in the table + table.insert(vcf.patterns[type][id].miscs, ConvertMiscNameToId(misc)) + end + id = id + 1 end end diff --git a/resource/shared/funcs.lua b/resource/shared/funcs.lua index 6300024..e3b22f3 100644 --- a/resource/shared/funcs.lua +++ b/resource/shared/funcs.lua @@ -102,3 +102,104 @@ function CanControlELS() return true end + +-- source: https://docs.fivem.net/natives/?_0x6AF0636DDEDCB6DD +local VMT = { + SPOILER = 0, + BUMPER_F = 1, + BUMPER_R = 2, + SKIRT = 3, + EXHAUST = 4, + CHASSIS = 5, + GRILL = 6, + BONNET = 7, + WING_L = 8, + WING_R = 9, + ROOF = 10, + ENGINE = 11, + BRAKES = 12, + GEARBOX = 13, + HORN = 14, + SUSPENSION = 15, + ARMOUR = 16, + NITROUS = 17, + TURBO = 18, + SUBWOOFER = 19, + TYRE_SMOKE = 20, + HYDRAULICS = 21, + XENON_LIGHTS = 22, + WHEELS = 23, + WHEELS_REAR_OR_HYDRAULICS = 24, + PLTHOLDER = 25, + PLTVANITY = 26, + INTERIOR1 = 27, + INTERIOR2 = 28, + INTERIOR3 = 29, + INTERIOR4 = 30, + INTERIOR5 = 31, + SEATS = 32, + STEERING = 33, + KNOB = 34, + PLAQUE = 35, + ICE = 36, + TRUNK = 37, + HYDRO = 38, + ENGINEBAY1 = 39, + ENGINEBAY2 = 40, + ENGINEBAY3 = 41, + CHASSIS2 = 42, + CHASSIS3 = 43, + CHASSIS4 = 44, + CHASSIS5 = 45, + DOOR_L = 46, + DOOR_R = 47, + LIVERY_MOD = 48, + LIGHTBAR = 49, +} + +local miscs = { + A = VMT.SPOILER, + B = VMT.BUMPER_F, + C = VMT.BUMPER_R, + D = VMT.SKIRT, + E = VMT.EXHAUST, + F = VMT.CHASSIS, + G = VMT.GRILL, + H = VMT.BONNET, + I = VMT.WING_L, + J = VMT.WING_R, + K = VMT.ROOF, + L = VMT.PLTHOLDER, + M = VMT.PLTVANITY, + N = VMT.INTERIOR1, + O = VMT.INTERIOR2, + P = VMT.INTERIOR3, + Q = VMT.INTERIOR4, + R = VMT.INTERIOR5, + S = VMT.SEATS, + T = VMT.STEERING, + U = VMT.KNOB, + V = VMT.PLAQUE, + W = VMT.ICE, + X = VMT.TRUNK, + Y = VMT.HYDRO, + Z = VMT.ENGINEBAY1, +} + +function ConvertMiscNameToId(misc) + return miscs[string.upper(misc)] +end + +function ConvertMiscIdToName(misc) + for k, v in pairs(miscs) do + if v == misc then return string.lower(k) end + end +end + +function IsVehicleMiscTurnedOn(vehicle, misc) + return GetVehicleMod(vehicle, misc) == -1 +end + +function DoesMiscExist(vehicle, misc) + return GetNumVehicleMods(vehicle, misc) > 0 +end From 9bf329c9e77e159a6553e48c2a027b1326f62388 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Mon, 21 Aug 2023 01:17:25 +0200 Subject: [PATCH 02/43] docs: add references --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fe8ac9a..4b1f2f9 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,12 @@ Oh, and you might ask yourself where all the 'Els' or 'Miss Els' jokes come from ## Key Features * Sirens and lights synced across the entire server -* Customizable light patterns per vehicle +* Fully customizable light patterns per vehicle * Use native game sirens or use your own with [WMServerSirens](https://github.com/Walsheyy/WMServerSirens) * A simple yet comprehensive configuration * Support for 3 different light stages * Support for up to 4 different sirens per vehicle +* Support for up to 36 independent light sources (12 extras + 26 miscs) * Optional light reflections around the vehicle * Indicator controls * Customizable keybinds (client side!) @@ -36,7 +37,7 @@ See the instructions in the [documentation](docs/README.md)! ## Vehicle Configuration Files -If you have any existing VCF files from the original author of this resource, then you'll probably be fine. The XML should be fully backwards compatible. If you have a VCF from any other resource, that'll most likely not work. +If you have any existing VCF files from the [original author](https://github.com/InfImpSolutions) of this resource, then you'll probably be fine. The XML should be fully backwards compatible. If you have a VCF from any other resource, that'll most likely not work. In both cases, it's probably best to re-create them by either writing them yourself OR... use my convient configuration GUI! Using this tool, you can easily generate configuration files for your vehicles. You can find it here: @@ -46,7 +47,7 @@ In both cases, it's probably best to re-create them by either writing them yours Please read the [documentation](docs/README.md) first and check the [Q&A section](https://github.com/matsn0w/IIS-EmergencyLS-ELS-FiveM/discussions/categories/q-a) on Github if your question has already been asked. It probably is! -Didn't find and answer? Then feel free to [start a new topic](https://github.com/matsn0w/IIS-EmergencyLS-ELS-FiveM/discussions/new?category=q-a). The community is here to help you! +Didn't find and answer? Then feel free to [start a new topic](https://github.com/matsn0w/IIS-EmergencyLS-ELS-FiveM/discussions/new?category=q-a). The community is here to help you! You can also join the official [MISS ELS Discord server](https://matsn0w.dev/discord). Found a bug? Please [make an issue](https://github.com/matsn0w/MISS-ELS/issues/new)! From fd17de02f7511284ed5b19b6e3180ebee3bed180 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Mon, 21 Aug 2023 16:13:30 +0200 Subject: [PATCH 03/43] feat: upgrade configurator to Nuxt 3 --- configurator/.editorconfig | 13 - configurator/.eslintrc.js | 19 - configurator/.gitignore | 100 +- configurator/.npmrc | 2 + configurator/README.md | 80 +- configurator/app.vue | 5 + configurator/assets/css/main.scss | 82 +- configurator/components/extras.vue | 28 +- configurator/components/importButton.vue | 23 +- configurator/components/patterns.vue | 131 +- configurator/components/sounds.vue | 78 +- configurator/components/statics.vue | 53 +- configurator/composables/vcfConfiguration.js | 146 + configurator/helpers/transformImportedVCF.js | 94 - configurator/helpers/xmlGenerator.js | 108 - configurator/jsconfig.json | 12 - configurator/layouts/default.vue | 5 + configurator/nuxt.config.js | 56 - configurator/nuxt.config.ts | 16 + configurator/package-lock.json | 39818 +++++------------ configurator/package.json | 38 +- configurator/pages/index.vue | 135 +- configurator/public/favicon.ico | Bin 0 -> 4286 bytes configurator/server/tsconfig.json | 3 + configurator/static/favicon.ico | Bin 8636 -> 0 bytes configurator/store/README.md | 10 - configurator/store/index.js | 94 - configurator/tailwind.config.js | 18 +- configurator/tsconfig.json | 4 + configurator/utils/transformImportedVCF.js | 96 + configurator/utils/xmlGenerator.js | 110 + 31 files changed, 10905 insertions(+), 30472 deletions(-) delete mode 100644 configurator/.editorconfig delete mode 100644 configurator/.eslintrc.js create mode 100644 configurator/.npmrc create mode 100644 configurator/app.vue create mode 100644 configurator/composables/vcfConfiguration.js delete mode 100644 configurator/helpers/transformImportedVCF.js delete mode 100644 configurator/helpers/xmlGenerator.js delete mode 100644 configurator/jsconfig.json create mode 100644 configurator/layouts/default.vue delete mode 100644 configurator/nuxt.config.js create mode 100644 configurator/nuxt.config.ts create mode 100644 configurator/public/favicon.ico create mode 100644 configurator/server/tsconfig.json delete mode 100644 configurator/static/favicon.ico delete mode 100644 configurator/store/README.md delete mode 100644 configurator/store/index.js create mode 100644 configurator/tsconfig.json create mode 100644 configurator/utils/transformImportedVCF.js create mode 100644 configurator/utils/xmlGenerator.js diff --git a/configurator/.editorconfig b/configurator/.editorconfig deleted file mode 100644 index 5d12634..0000000 --- a/configurator/.editorconfig +++ /dev/null @@ -1,13 +0,0 @@ -# editorconfig.org -root = true - -[*] -indent_style = space -indent_size = 2 -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/configurator/.eslintrc.js b/configurator/.eslintrc.js deleted file mode 100644 index 9e383e1..0000000 --- a/configurator/.eslintrc.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - root: true, - env: { - browser: true, - node: true - }, - parserOptions: { - parser: '@babel/eslint-parser', - requireConfigFile: false - }, - extends: [ - '@nuxtjs', - 'plugin:nuxt/recommended' - ], - plugins: [ - ], - // add your custom rules here - rules: {} -} diff --git a/configurator/.gitignore b/configurator/.gitignore index e8f682b..4a7f73a 100644 --- a/configurator/.gitignore +++ b/configurator/.gitignore @@ -1,90 +1,24 @@ -# Created by .ignore support plugin (hsz.mobi) -### Node template -# Logs -/logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# next.js build output -.next - -# nuxt.js build output +# Nuxt dev/build outputs +.output +.data .nuxt - -# Nuxt generate +.nitro +.cache dist -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless - -# IDE / Editor -.idea +# Node dependencies +node_modules -# Service worker -sw.* +# Logs +logs +*.log -# macOS +# Misc .DS_Store +.fleet +.idea -# Vim swap files -*.swp +# Local env files +.env +.env.* +!.env.example diff --git a/configurator/.npmrc b/configurator/.npmrc new file mode 100644 index 0000000..cf04042 --- /dev/null +++ b/configurator/.npmrc @@ -0,0 +1,2 @@ +shamefully-hoist=true +strict-peer-dependencies=false diff --git a/configurator/README.md b/configurator/README.md index 1334515..0496ac8 100644 --- a/configurator/README.md +++ b/configurator/README.md @@ -1,69 +1,39 @@ -# miss-els-vcf-configurator +# MISS ELS VCF Configurator -## Build Setup +Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. -```bash -# install dependencies -$ npm install - -# serve with hot reload at localhost:3000 -$ npm run dev +## Setup -# build for production and launch server -$ npm run build -$ npm run start +Make sure to install the dependencies: -# generate static project -$ npm run generate +```bash +# npm +npm install ``` -For detailed explanation on how things work, check out the [documentation](https://nuxtjs.org). - -## Special Directories - -You can create the following extra directories, some of which have special behaviors. Only `pages` is required; you can delete them if you don't want to use their functionality. - -### `assets` - -The assets directory contains your uncompiled assets such as Stylus or Sass files, images, or fonts. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/assets). - -### `components` - -The components directory contains your Vue.js components. Components make up the different parts of your page and can be reused and imported into your pages, layouts and even other components. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/components). - -### `layouts` - -Layouts are a great help when you want to change the look and feel of your Nuxt app, whether you want to include a sidebar or have distinct layouts for mobile and desktop. +## Development Server -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/layouts). +Start the development server on `http://localhost:3000`: +```bash +# npm +npm run dev +``` -### `pages` - -This directory contains your application views and routes. Nuxt will read all the `*.vue` files inside this directory and setup Vue Router automatically. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/get-started/routing). - -### `plugins` - -The plugins directory contains JavaScript plugins that you want to run before instantiating the root Vue.js Application. This is the place to add Vue plugins and to inject functions or constants. Every time you need to use `Vue.use()`, you should create a file in `plugins/` and add its path to plugins in `nuxt.config.js`. - -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/plugins). - -### `static` - -This directory contains your static files. Each file inside this directory is mapped to `/`. +## Production -Example: `/static/robots.txt` is mapped as `/robots.txt`. +Build the application for production: -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/static). +```bash +# npm +npm run build +``` -### `store` +Locally preview production build: -This directory contains your Vuex store files. Creating a file in this directory automatically activates Vuex. +```bash +# npm +npm run preview +``` -More information about the usage of this directory in [the documentation](https://nuxtjs.org/docs/2.x/directory-structure/store). +Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. diff --git a/configurator/app.vue b/configurator/app.vue new file mode 100644 index 0000000..f8eacfa --- /dev/null +++ b/configurator/app.vue @@ -0,0 +1,5 @@ + diff --git a/configurator/assets/css/main.scss b/configurator/assets/css/main.scss index 84ab727..eccfed8 100644 --- a/configurator/assets/css/main.scss +++ b/configurator/assets/css/main.scss @@ -7,67 +7,56 @@ body { } .card { - @apply px-3 py-1 my-6 bg-white rounded-md shadow-lg; -} + @apply p-2 my-6 bg-white rounded-md shadow-lg; -.card h2 { - @apply font-semibold text-gray-800; + h2 { + @apply font-semibold text-gray-800; + } } button { - @apply px-2 py-1 bg-blue-500 text-white rounded-md shadow; + @apply px-2 py-1 rounded-md shadow; } -thead { - @apply font-semibold uppercase text-gray-400 bg-gray-50; -} +table { + thead { + @apply font-semibold uppercase text-gray-400 bg-gray-50; + } -tbody { - @apply divide-y divide-gray-100; -} + tbody { + @apply divide-y divide-gray-100; + } -th, td { - @apply p-2 align-middle; -} + th, + td { + @apply p-2 align-middle; + } -th { - @apply font-semibold text-left; + th { + @apply font-semibold text-left; + } } label { @apply block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4; } -input[type=text], input[type=number] { - @apply bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full p-2 text-gray-700 leading-tight align-middle; -} - -input[type=text]:focus, input[type=number]:focus { - @apply focus:outline-none focus:bg-white focus:border-blue-500; +input[type="text"], +input[type="number"], +select { + @apply form-input block w-full px-2 py-1 rounded bg-gray-200 text-gray-700 border-2 border-gray-200 focus:bg-white focus:border-blue-500 focus:outline-none; } select { - @apply - form-select appearance-none - block w-full - px-3 py-1.5 - text-base font-normal text-gray-700 - bg-white bg-clip-padding bg-no-repeat - border border-solid border-gray-300 rounded - transition ease-in-out - m-0 - focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none; -} - -.form-select { - -moz-padding-start: calc(.75rem - 3px); + @extend select; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"); - background-position: right .75rem center; + background-position: right 0.75rem center; background-size: 16px 12px; + background-repeat: no-repeat; } -input[type='checkbox'] { - @apply form-checkbox h-5 w-5 mr-2 text-blue-500 rounded; +input[type="checkbox"] { + @apply form-checkbox h-5 w-5 text-blue-500 rounded; } .cb-label { @@ -108,11 +97,12 @@ input[type='checkbox'] { border-radius: 5px; padding: 8px 12px; cursor: pointer; -} -.import-button input { - position: absolute; - top: 0; - left: 0; - z-index: -1; - opacity: 0; + + input { + position: absolute; + top: 0; + left: 0; + z-index: -1; + opacity: 0; + } } diff --git a/configurator/components/extras.vue b/configurator/components/extras.vue index 9f56fcb..6a268a9 100644 --- a/configurator/components/extras.vue +++ b/configurator/components/extras.vue @@ -16,26 +16,22 @@ - - - Extra {{ extra.id }} - + + Extra {{ extra.id }}
+ Is emergency
- + @@ -41,21 +58,30 @@ - + @@ -67,50 +93,43 @@ - diff --git a/configurator/components/lights.vue b/configurator/components/lights.vue new file mode 100644 index 0000000..bcdd3a3 --- /dev/null +++ b/configurator/components/lights.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/configurator/components/patterns.vue b/configurator/components/patterns.vue index d770fe6..aca50ea 100644 --- a/configurator/components/patterns.vue +++ b/configurator/components/patterns.vue @@ -53,6 +53,7 @@ + @@ -66,16 +67,26 @@ {{ extra.id }} + + + + + + + + + + @@ -105,14 +147,16 @@ diff --git a/configurator/components/statics.vue b/configurator/components/statics.vue index 05ab3d9..17b9225 100644 --- a/configurator/components/statics.vue +++ b/configurator/components/statics.vue @@ -10,7 +10,8 @@
Duration Extras
- + {{ extra.id }} + >{{ extra.id }} -
Duration ExtrasMiscs
+ {{ misc.id }} +
+ Preview + + {{ extra?.id }} + + {{ misc.id }} + + + +
+ +
@@ -93,7 +135,7 @@ class="red" @click="removeFlash(pattern, flash)" > - × +
- + + @@ -19,16 +20,21 @@ - diff --git a/configurator/composables/vcfConfiguration.js b/configurator/composables/vcfConfiguration.ts similarity index 53% rename from configurator/composables/vcfConfiguration.js rename to configurator/composables/vcfConfiguration.ts index ad34222..607d498 100644 --- a/configurator/composables/vcfConfiguration.js +++ b/configurator/composables/vcfConfiguration.ts @@ -1,4 +1,11 @@ -export const useVcfConfiguration = () => { +import {letterLightableId, Lightable, numericalLightableId} from "~/types/lights"; +import {vcfConfig} from "~/types/vcfConfig"; +import {flashType} from "~/types/flash"; +import {staticType} from "~/types/static"; +import {patternType} from "~/types/patterns"; +import {Ref} from "vue"; + +export const useVcfConfiguration = (): Ref => { return useState("vcfConfiguration", () => ({ flashID: 1, configuration: { @@ -14,7 +21,10 @@ export const useVcfConfiguration = () => { audioString: "SIRENS_AIRHORN", soundSet: null, }, - { name: "NineMode", allowUse: true }, + { + name: "NineMode", + allowUse: false, + }, { name: "SrnTone1", allowUse: true, @@ -46,7 +56,7 @@ export const useVcfConfiguration = () => { isEmergency: true, flashHighBeam: false, enableWarningBeep: false, - loopPreview: true, + loopPreview: false, }, { name: "SECONDARY", @@ -65,37 +75,44 @@ export const useVcfConfiguration = () => { ], flashes: [], }, - })); + } as vcfConfig)); }; -const getFlashIndex = (flash) => { +const getFlashIndex = (flash: flashType) => { const VCF = useVcfConfiguration(); return VCF.value.configuration.flashes.map((f) => f.id).indexOf(flash.id); }; -export const useAddStatic = (value) => { +export const miscIds: letterLightableId[] = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]; +export const extraIds: numericalLightableId[] = [1,2,3,4,5,6,7,8,9,10,11,12] + +export const isLightableIdInUse = (id: letterLightableId|numericalLightableId) => useVcfConfiguration().value.configuration.lightables.map((lightable: Lightable) => lightable.id).includes(id) +export const availableMiscIds = computed(() => miscIds.filter(miscId => !isLightableIdInUse(miscId))) +export const availableExtraIds = computed(() => extraIds.filter(extraId => !isLightableIdInUse(extraId))) + +export const useAddStatic = (value: staticType) => { const VCF = useVcfConfiguration(); VCF.value.configuration.statics.push(value); }; -export const useRemoveStatic = (value) => { +export const useRemoveStatic = (value: staticType) => { const VCF = useVcfConfiguration(); const index = VCF.value.configuration.statics - .map((item) => item.extra) - .indexOf(value.extra); + .map((item) => item.id) + .indexOf(value.id); VCF.value.configuration.statics.splice(index, 1); }; -export const useAddFlash = (value) => { +export const useAddFlash = (pattern: patternType) => { const VCF = useVcfConfiguration(); - const flash = { + const flash: flashType = { id: VCF.value.flashID++, - pattern: value.pattern.name, + pattern: pattern.name, duration: 100, extras: [], miscs: [], @@ -104,43 +121,42 @@ export const useAddFlash = (value) => { VCF.value.configuration.flashes.push(flash); }; -export const useRemoveFlash = (value) => { +export const useRemoveFlash = (pattern: patternType, flash: flashType) => { const VCF = useVcfConfiguration(); - const flashIndex = getFlashIndex(value.flash); + const flashIndex = getFlashIndex(flash); if (flashIndex !== -1) { VCF.value.configuration.flashes.splice(flashIndex, 1); } }; -export const useToggleLight = (value) => { +export const useToggleLight = (pattern: patternType, flash: flashType, lightable: Lightable) => { const VCF = useVcfConfiguration(); - const flashIndex = getFlashIndex(value.flash); + const flashIndex: number = getFlashIndex(flash); const extras = VCF.value.configuration.flashes[flashIndex].extras; const miscs = VCF.value.configuration.flashes[flashIndex].miscs; - if (!isNaN(value.light.id)) { - if (extras.includes(value.light.id)) { - extras.splice(extras.indexOf(value.light.id), 1); + if (typeof lightable.id === "number") { + if (extras.includes(lightable.id)) { + extras.splice(extras.indexOf(lightable.id), 1); } else { - extras.push(value.light.id); + extras.push(lightable.id); } } else { - if (miscs.includes(value.light.id)) { - miscs.splice(miscs.indexOf(value.light.id), 1); - } else { - miscs.push(value.light.id); + if (miscs.includes(lightable.id)) { + miscs.splice(miscs.indexOf(lightable.id), 1); + } else if (typeof lightable.id === "string") { + miscs.push(lightable.id); } } }; -export const useImportExistingConfiguration = (value) => { +export const useImportExistingConfiguration = (value: vcfConfig) => { const VCF = useVcfConfiguration(); + console.log(value) VCF.value.flashID = value.flashID; - delete value.flashID; - - VCF.value.configuration = value; + VCF.value.configuration = value.configuration; }; diff --git a/configurator/types/flash.ts b/configurator/types/flash.ts new file mode 100644 index 0000000..cce3bf6 --- /dev/null +++ b/configurator/types/flash.ts @@ -0,0 +1,10 @@ +import {patternType} from "~/types/patterns"; +import {letterLightableId, numericalLightableId} from "~/types/lights"; + +export type flashType = { + id: number + pattern: patternType["name"] + duration: number + extras: numericalLightableId[] + miscs: letterLightableId[] +} \ No newline at end of file diff --git a/configurator/types/patterns.ts b/configurator/types/patterns.ts new file mode 100644 index 0000000..6fa8957 --- /dev/null +++ b/configurator/types/patterns.ts @@ -0,0 +1,7 @@ +export type patternType = { + name: string + isEmergency: boolean + flashHighBeam: boolean, + enableWarningBeep: boolean + loopPreview: boolean +} \ No newline at end of file diff --git a/configurator/types/sounds.ts b/configurator/types/sounds.ts new file mode 100644 index 0000000..dc6ae5f --- /dev/null +++ b/configurator/types/sounds.ts @@ -0,0 +1,6 @@ +export type soundType = { + name: string + allowUse: boolean + audioString?: string|null + soundSet?: string|null +} \ No newline at end of file diff --git a/configurator/types/static.ts b/configurator/types/static.ts new file mode 100644 index 0000000..8658454 --- /dev/null +++ b/configurator/types/static.ts @@ -0,0 +1,7 @@ +import {letterLightableId, lightableType, numericalLightableId} from "~/types/lights"; + +export type staticType = { + id: numericalLightableId|letterLightableId|null, + type: lightableType|null + name: string|null +} \ No newline at end of file diff --git a/configurator/types/vcfConfig.ts b/configurator/types/vcfConfig.ts new file mode 100644 index 0000000..031544a --- /dev/null +++ b/configurator/types/vcfConfig.ts @@ -0,0 +1,19 @@ +import {Lightable, lightableType} from "~/types/lights"; +import {staticType} from "~/types/static"; +import {soundType} from "~/types/sounds"; +import {patternType} from "~/types/patterns"; +import {flashType} from "~/types/flash"; + +export type vcfConfig = { + flashID: number + configuration: { + author: string|null + description: string|null + lightables: Lightable[] + statics: staticType[] + useServerSirens: boolean + sounds: soundType[] + patterns: patternType[] + flashes: flashType[] + } +} \ No newline at end of file diff --git a/configurator/utils/transformImportedVCF.js b/configurator/utils/transformImportedVCF.ts similarity index 70% rename from configurator/utils/transformImportedVCF.js rename to configurator/utils/transformImportedVCF.ts index 548d796..1f6c942 100644 --- a/configurator/utils/transformImportedVCF.js +++ b/configurator/utils/transformImportedVCF.ts @@ -1,32 +1,34 @@ +import {vcfConfig} from "~/types/vcfConfig"; +import {letterLightableId, numericalLightableId} from "~/types/lights"; + /** * This method takes in an existing VCF file (as XML string), parses the XML * and maps it to an expected format for the VueX store. * */ export const generateStoreAttributesFromExistingVCF = (data) => { - const vcf = { + const vcf: vcfConfig = { flashID: 1, - author: null, - description: null, - lightables: [], - statics: { - extras: [], - miscs: [], - }, - useServerSirens: false, - sounds: [], - patterns: [], - flashes: [], + configuration: { + author: null, + description: null, + lightables: [], + statics: [], + useServerSirens: false, + sounds: [], + patterns: [], + flashes: [], + } }; // parse the XML string const parsedVCF = new DOMParser().parseFromString(data, "text/xml"); // get basic document info - vcf.description = parsedVCF + vcf.configuration.description = parsedVCF .querySelector("vcfroot") - .getAttribute("Description"); - vcf.author = parsedVCF.querySelector("vcfroot").getAttribute("Author"); + .getAttribute("Description") ?? null; + vcf.configuration.author = parsedVCF.querySelector("vcfroot").getAttribute("Author"); // EOVERRIDE section const eoverride = parsedVCF.querySelector("EOVERRIDE"); @@ -41,7 +43,7 @@ export const generateStoreAttributesFromExistingVCF = (data) => { }); extras.forEach((extra) => { - vcf.lightables.push({ + vcf.configuration.lightables.push({ id: parseInt(extra.nodeName.match(/([0-9]|[1-9][0-9])$/g)[0]), type: 'extra', enabled: extra.getAttribute("IsElsControlled") === "true", @@ -51,7 +53,7 @@ export const generateStoreAttributesFromExistingVCF = (data) => { }); miscs.forEach((misc) => { - vcf.lightables.push({ + vcf.configuration.lightables.push({ id: misc.nodeName.match(/([A-Z])$/g)[0], type: 'misc', enabled: misc.getAttribute("IsElsControlled") === "true", @@ -73,15 +75,17 @@ export const generateStoreAttributesFromExistingVCF = (data) => { }); staticExtras.forEach((staticExtra) => { - vcf.statics.extras.push({ - extra: parseInt(staticExtra.nodeName.match(/([0-9]|[1-9][0-9])$/g)[0]), + vcf.configuration.statics.push({ + id: parseInt(staticExtra.nodeName.match(/([0-9]|[1-9][0-9])$/g)[0]) as numericalLightableId, + type: 'extra', name: staticExtra.getAttribute("Name") ?? staticExtra.nodeName, }); }); staticMiscs.forEach((staticMisc) => { - vcf.statics.miscs.push({ - misc: staticMisc.nodeName.match(/([A-Z])$/g)[0], + vcf.configuration.statics.push({ + id: staticMisc.nodeName.match(/([A-Z])$/g)[0].toLowerCase() as letterLightableId, + type: 'misc', name: staticMisc.getAttribute("Name") ?? staticMisc.nodeName, }); }); @@ -91,7 +95,7 @@ export const generateStoreAttributesFromExistingVCF = (data) => { const sounds = soundsObject.querySelectorAll("*"); sounds.forEach((sound) => { - vcf.sounds.push({ + vcf.configuration.sounds.push({ name: sound.nodeName, allowUse: sound.getAttribute("AllowUse") === "true", audioString: sound.getAttribute("AudioString") ?? null, @@ -102,7 +106,7 @@ export const generateStoreAttributesFromExistingVCF = (data) => { // determine whether a SoundSet is present sounds.forEach((sound) => { if (sound.getAttribute("SoundSet") !== null) { - vcf.useServerSirens = true; + vcf.configuration.useServerSirens = true; } }); @@ -110,21 +114,22 @@ export const generateStoreAttributesFromExistingVCF = (data) => { const patternsObject = parsedVCF.querySelector("PATTERN"); for (const pattern of patternsObject.children) { - vcf.patterns.push({ + vcf.configuration.patterns.push({ name: pattern.nodeName, isEmergency: pattern.getAttribute("IsEmergency") === "true", flashHighBeam: pattern.getAttribute("FlashHighBeam") === "true", enableWarningBeep: pattern.getAttribute("EnableWarningBeep") === "true", + loopPreview: false }); for (const flash of pattern.children) { const enabledExtras = flash.getAttribute("Extras")?.split(",") ?? []; - const enabledMiscs = flash.getAttribute("Miscs")?.split(",") ?? []; + const enabledMiscs: letterLightableId[] = flash.getAttribute("Miscs")?.split(",") as letterLightableId[] ?? []; - vcf.flashes.push({ + vcf.configuration.flashes.push({ id: vcf.flashID++, duration: parseInt(flash.getAttribute("Duration")) ?? 100, - extras: enabledExtras.map((extra) => parseInt(extra)) ?? [], + extras: enabledExtras.map((extra) => parseInt(extra) as numericalLightableId) ?? [], miscs: enabledMiscs ?? [], pattern: pattern.nodeName, }); diff --git a/configurator/utils/xmlGenerator.js b/configurator/utils/xmlGenerator.js index 3f9281e..0dbdc30 100644 --- a/configurator/utils/xmlGenerator.js +++ b/configurator/utils/xmlGenerator.js @@ -62,8 +62,15 @@ export const generateVcfDocument = (data) => { const statics = doc.createElement("STATIC"); data.statics.forEach((stat) => { - const s = doc.createElement(`Extra${stat.extra}`); - s.setAttribute("Name", stat.name ?? `Extra ${stat.extra}`); + let nodeName = '' + if (s.type === 'extra') { + nodeName = `EXTRA${stat.id}` + } else if (stat.type === 'misc') { + nodeName = `MISC${String(stat.id).toUpperCase()}` + } + + const s = doc.createElement(nodeName); + s.setAttribute("Name", stat.name ?? nodeName); statics.appendChild(s); }); From 7a162390be2dd155a0512477b5166563426f4c64 Mon Sep 17 00:00:00 2001 From: matsn0w Date: Fri, 8 Sep 2023 22:47:27 +0200 Subject: [PATCH 16/43] make button bigger --- configurator/pages/index.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configurator/pages/index.vue b/configurator/pages/index.vue index 6213cf1..d6fa4f4 100644 --- a/configurator/pages/index.vue +++ b/configurator/pages/index.vue @@ -37,7 +37,7 @@
- +
From d05242af74624a76fb0db36333d1847d290672b3 Mon Sep 17 00:00:00 2001 From: Justin Rijsdijk Date: Mon, 11 Sep 2023 19:14:50 +0200 Subject: [PATCH 17/43] small fixes for import/export. Leave the border around the loopPreview button --- configurator/components/patterns.vue | 15 +++++++++++---- configurator/composables/vcfConfiguration.ts | 2 ++ configurator/utils/transformImportedVCF.ts | 2 +- configurator/utils/xmlGenerator.js | 4 ++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/configurator/components/patterns.vue b/configurator/components/patterns.vue index 08ffe30..f2b77a8 100644 --- a/configurator/components/patterns.vue +++ b/configurator/components/patterns.vue @@ -93,7 +93,7 @@ + +
- @@ -36,11 +35,6 @@ -
ExtraLightable typeLightable id Name
Lightable type Lightable IDIs ELS controlled Allow env light Color - - @@ -62,13 +62,16 @@ + + \ No newline at end of file diff --git a/configurator/components/sounds.vue b/configurator/components/sounds.vue index 26f2f44..11ec6b7 100644 --- a/configurator/components/sounds.vue +++ b/configurator/components/sounds.vue @@ -12,10 +12,10 @@ /> Use  WMServerSirensWMServerSirens @@ -67,5 +67,58 @@ diff --git a/configurator/composables/vcfConfiguration.ts b/configurator/composables/vcfConfiguration.ts index 221c17f..cd0350f 100644 --- a/configurator/composables/vcfConfiguration.ts +++ b/configurator/composables/vcfConfiguration.ts @@ -6,7 +6,7 @@ import {patternType} from "~/types/patterns"; import {Ref} from "vue"; import {DateTime} from "luxon"; -const defaultVcfConfig: vcfConfig = { +export const defaultVcfConfig: vcfConfig = { flashID: 1, configuration: { author: null, diff --git a/configurator/pages/index.vue b/configurator/pages/index.vue index de7cbf4..72ce318 100644 --- a/configurator/pages/index.vue +++ b/configurator/pages/index.vue @@ -11,22 +11,10 @@ - + /> @@ -46,23 +34,11 @@
- + :isForNotice="true" + />