├── .all-contributorsrc
├── .editorconfig
├── .eslintrc.json
├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── support_question.md
├── .gitignore
├── .jsdoc2md.json
├── .versionrc
├── .vscode
├── extensions.json
├── launch.json
└── settings.json
├── config.example.json
├── dist
├── 2b3e1faf89f94a483539.png
├── 416d91365b44e4b4f477.png
├── 8f2c4d11474275fbc161.png
├── app.ebf5ee37024ab61be51d.js
├── app.ebf5ee37024ab61be51d.js.map
├── index.html
├── runtime.ebf5ee37024ab61be51d.js
├── runtime.ebf5ee37024ab61be51d.js.map
├── style.ebf5ee37024ab61be51d.js
├── style.ebf5ee37024ab61be51d.js.map
├── vendors.ebf5ee37024ab61be51d.js
├── vendors.ebf5ee37024ab61be51d.js.LICENSE.txt
└── vendors.ebf5ee37024ab61be51d.js.map
├── docs
├── CHANGELOG.md
├── FAQ.md
├── LICENSE.md
├── README.md
├── config.md
├── contributing.md
├── custom_maps.md
├── developers.md
├── images
│ ├── 0c19843d5026e94c83623b294406d2cf6de64f072cd15f6f9afe65b2ffcd5db8.gif
│ ├── 0e2595e1746eddc1e694a32653fa0ddd1d0eadde33cdc3e22be5e708f059f898.png
│ ├── 1b74c6a78fda6509d07d328ce58d890f1b955122b68b6f05080b6532e97ffe7f.png
│ ├── 4f9f772cf2c04f1ada6be1d56dccb56155d6607cb0dc7bf2e526a6f43bf7128b.png
│ ├── 583c0e9ed7c51aad2d394775cdee98ed83b3637eb92bca89af8d14422ec84db4.png
│ ├── 5a53cff3a68dd004f524716274d12f7791a7de7cdcd40e49de60b7e50ccd4569.png
│ ├── 697acd53e4f8099573cdaf9ca7f142e4cff57f35c3cba6b67fc4876420172e06.png
│ ├── 7ae7ff5a3642b4fc38b60e8696cc6bd96592726e608780d763afce9043a0aa8e.png
│ ├── LiveMap logo.svg
│ ├── a7369dd2c6a1d9e31db91cf8822373ad89d310aea6953f85cf952d02c2bcff9f.png
│ ├── b47eab9039fe5df729ee8caba868f4de61df991c55fc06c83cc083897d97b140.png
│ ├── b4e0ced36d3ffcdd61f08fa3eb442c5a01d23699a330fd58207a95c3842a20de.png
│ ├── be9ec7dfc81bd265a3a44d20a98133b05df9a14a8d644409e40528a7debce5b8.png
│ ├── cbc7f7b8ac5ca1b0e7690d642a6229830fa36ee16683cd0f63f32e1bffd230d4.png
│ └── dfd01895ddccb7f7dd9f5cd0e1d07b73c46ecb1823f1ae9f05750ceb08a2af3d.png
└── reverse_proxy.md
├── images
├── icons
│ ├── blips.png
│ ├── blips_texturesheet.png
│ ├── debug.png
│ └── normal.png
├── layers-2x.png
├── layers.png
├── marker-icon-2x.png
├── marker-icon.png
├── marker-shadow.png
├── profile.png
└── tiles
│ ├── extract_png.py
│ ├── normal
│ ├── minimap_sea_0_0.png
│ ├── minimap_sea_0_1.png
│ ├── minimap_sea_1_0.png
│ ├── minimap_sea_1_1.png
│ ├── minimap_sea_2_0.png
│ ├── minimap_sea_2_1.png
│ └── stitched.png
│ ├── postal
│ ├── minimap_sea_0_0.png
│ ├── minimap_sea_0_1.png
│ ├── minimap_sea_1_0.png
│ ├── minimap_sea_1_1.png
│ ├── minimap_sea_2_0.png
│ └── minimap_sea_2_1.png
│ └── read_ytd.py
├── index.html
├── mkdocs.yml
├── package.json
├── public
├── dev
│ ├── blips.json
│ ├── config.json
│ └── version.json
└── index.html
├── src
├── js
│ ├── _app.js
│ ├── alerter.js
│ ├── config.js
│ ├── controls.js
│ ├── init.js
│ ├── map.js
│ ├── markers.js
│ ├── objects.js
│ ├── socket.js
│ ├── translator.js
│ ├── utils.js
│ └── version-check.js
└── sass
│ ├── _alerts.scss
│ ├── _leaflet.scss
│ ├── _markerCluster.scss
│ ├── _ui.scss
│ ├── _variables.scss
│ └── main.scss
├── tools
├── api-doc-template.hbs
├── partials
│ ├── header.hbs
│ └── link.hbs
└── standard-version-bump.js
├── translations
├── en.json
├── es.json
├── fr.json
├── manifest.json
└── ru.json
├── version.json
├── webpack.config.js
└── yarn.lock
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "live_map-interface",
3 | "projectOwner": "TGRHavoc",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "docs/README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": true,
11 | "commitConvention": "angular",
12 | "contributors": [
13 | {
14 | "login": "TGRHavoc",
15 | "name": "Jordan Dalton",
16 | "avatar_url": "https://avatars.githubusercontent.com/u/1770893?v=4",
17 | "profile": "https://tgrhavoc.co.uk/",
18 | "contributions": [
19 | "code",
20 | "doc",
21 | "design",
22 | "ideas"
23 | ]
24 | },
25 | {
26 | "login": "TomGrobbe",
27 | "name": "Tom",
28 | "avatar_url": "https://avatars.githubusercontent.com/u/31419184?v=4",
29 | "profile": "https://tomgrobbe.nl/",
30 | "contributions": [
31 | "bug",
32 | "code"
33 | ]
34 | },
35 | {
36 | "login": "xlxAciDxlx",
37 | "name": "AciD",
38 | "avatar_url": "https://avatars.githubusercontent.com/u/7502881?v=4",
39 | "profile": "https://xlxacidxlx.com/",
40 | "contributions": [
41 | "bug",
42 | "code"
43 | ]
44 | },
45 | {
46 | "login": "jiynn",
47 | "name": "jiynn",
48 | "avatar_url": "https://avatars.githubusercontent.com/u/33206565?v=4",
49 | "profile": "https://github.com/jiynn",
50 | "contributions": [
51 | "bug"
52 | ]
53 | },
54 | {
55 | "login": "Local9",
56 | "name": "127.0.0.1",
57 | "avatar_url": "https://avatars.githubusercontent.com/u/6077794?v=4",
58 | "profile": "https://github.com/Local9",
59 | "contributions": [
60 | "bug",
61 | "code"
62 | ]
63 | },
64 | {
65 | "login": "davwheat",
66 | "name": "David Wheatley",
67 | "avatar_url": "https://avatars.githubusercontent.com/u/7406822?v=4",
68 | "profile": "https://github.com/davwheat",
69 | "contributions": [
70 | "code",
71 | "bug"
72 | ]
73 | },
74 | {
75 | "login": "DaveOkpare",
76 | "name": "David Okpare",
77 | "avatar_url": "https://avatars.githubusercontent.com/u/19241431?v=4",
78 | "profile": "https://github.com/DaveOkpare",
79 | "contributions": [
80 | "doc"
81 | ]
82 | },
83 | {
84 | "login": "matsn0w",
85 | "name": "matsn0w",
86 | "avatar_url": "https://avatars.githubusercontent.com/u/15019582?v=4",
87 | "profile": "https://github.com/matsn0w",
88 | "contributions": [
89 | "code",
90 | "bug"
91 | ]
92 | },
93 | {
94 | "login": "JasonO99",
95 | "name": "Jason Olsen",
96 | "avatar_url": "https://avatars.githubusercontent.com/u/2074263?v=4",
97 | "profile": "https://github.com/JasonO99",
98 | "contributions": [
99 | "bug"
100 | ]
101 | },
102 | {
103 | "login": "Aaron-Downham",
104 | "name": "Aaron-Downham",
105 | "avatar_url": "https://avatars.githubusercontent.com/u/32743520?v=4",
106 | "profile": "https://github.com/Aaron-Downham",
107 | "contributions": [
108 | "bug"
109 | ]
110 | },
111 | {
112 | "login": "Annihilator4423",
113 | "name": "Annihilator4423",
114 | "avatar_url": "https://avatars.githubusercontent.com/u/61148120?v=4",
115 | "profile": "https://github.com/Annihilator4423",
116 | "contributions": [
117 | "doc"
118 | ]
119 | },
120 | {
121 | "login": "Cu-chi",
122 | "name": "Cuchi'",
123 | "avatar_url": "https://avatars.githubusercontent.com/u/42467470?v=4",
124 | "profile": "https://github.com/Cu-chi",
125 | "contributions": [
126 | "translation"
127 | ]
128 | }
129 | ],
130 | "contributorsPerLine": 5,
131 | "skipCi": true
132 | }
133 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true
5 | },
6 | "extends": [
7 | "eslint:recommended",
8 | "plugin:prettier/recommended"
9 | ],
10 | "plugins": [
11 | "prettier"
12 | ],
13 |
14 | "parserOptions": {
15 | "ecmaVersion": 2021,
16 | "sourceType": "module"
17 | },
18 | "rules": {
19 | "eqeqeq": "error",
20 | "no-console": 0,
21 | "prettier/prettier": "error",
22 | "indent": [
23 | "error",
24 | 4
25 | ],
26 | "linebreak-style": [
27 | "error",
28 | "unix"
29 | ],
30 | "quotes": [
31 | "error",
32 | "double"
33 | ],
34 | "semi": [
35 | "error",
36 | "always"
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: ["https://www.paypal.com/donate?hosted_button_id=4PPJGZNAGWJHL"]
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41B Bug Report"
3 | about: "If something isn't working as expected \U0001F914."
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Possible Solution**
38 |
39 |
40 | **Additional context/Screenshots**
41 | Add any other context about the problem here. If applicable, add screenshots to help explain.
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F680 Feature Request"
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/support_question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F917 Support Question"
3 | about: "If you have a question \U0001F4AC, please check out the discussions"
4 | title: ''
5 | labels: question
6 | assignees: ''
7 |
8 | ---
9 |
10 | --------------^ Click "Preview" for a nicer view!
11 |
12 | For usage and support questions, please check out the discussions page. Thanks! 😁.
13 |
14 | https://github.com/TGRHavoc/live_map-interface/discussions
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | images/map/
3 | images/__map/
4 | images/tiles/
5 |
6 | generate-changes.bat
7 | .changelog.rc
8 |
9 | node_modules/
10 | /config.json
11 |
12 | _site/
13 | obj/
14 |
15 | ## Browserified/Babel'd js file. This should only exist on a developer's machine so, might as well ignore it.
16 | dist/js/
17 | yarn-error.log
18 | .env
19 | /blips.json
20 |
21 | coverage/
22 | .cache/
23 | /blips2.json
24 |
--------------------------------------------------------------------------------
/.jsdoc2md.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "js/src/**"
4 | ],
5 | "template": "tools/api-doc-template.hbs",
6 | "partial": "tools/partials/**",
7 | "no-gfm": false
8 | }
9 |
--------------------------------------------------------------------------------
/.versionrc:
--------------------------------------------------------------------------------
1 | {
2 | "header": "# Changelog\n",
3 | "infile": "docs/CHANGELOG.md",
4 | "releaseCommitMessageFormat": "chore(release): v{{currentTag}}",
5 | "scripts": {
6 | "prerelease": "yarn build",
7 | "precommit": "git add -A dist/ index.html"
8 | },
9 | "bumpFiles": [
10 | {
11 | "filename": "version.json",
12 | "updater": "tools/standard-version-bump.js"
13 | },
14 | {
15 | "filename": "package.json",
16 | "type": "json"
17 | }
18 | ],
19 | "commitAll": true,
20 | "types": [
21 | {
22 | "type": "translation",
23 | "section": "Translations",
24 | "hidden": false
25 | },
26 | {
27 | "type": "chore",
28 | "hidden": false
29 | },
30 | {
31 | "type": "docs",
32 | "section": "Documentation",
33 | "hidden": false
34 | },
35 | {
36 | "type": "style",
37 | "hidden": false
38 | },
39 | {
40 | "type": "refactor",
41 | "hidden": false
42 | },
43 | {
44 | "type": "perf",
45 | "section": "Performance",
46 | "hidden": false
47 | },
48 | {
49 | "type": "test",
50 | "section": "Tests",
51 | "hidden": false
52 | }
53 | ]
54 | }
55 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "syler.sass-indented",
4 | "dbaeumer.vscode-eslint",
5 | "sibiraj-s.vscode-scss-formatter",
6 | "wayou.vscode-todo-highlight"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Launch debug.html",
6 | "type": "firefox",
7 | "request": "launch",
8 | "reAttach": true,
9 | "url": "http://localhost/gtav/debug.html",
10 | "pathMappings": [
11 | {
12 | "url": "http://localhost/gtav",
13 | "path": "${workspaceFolder}"
14 | }
15 | ]
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.ignorePaths": [
3 | "**/package-lock.json",
4 | "**/node_modules/**",
5 | "**/vscode-extension/**",
6 | "**/.git/objects/**",
7 | ".vscode",
8 | ".vscode-insiders",
9 | "dist/**"
10 | ],
11 | "cSpell.words": [
12 | "autoclose",
13 | "autotimeout",
14 | "esversion",
15 | "spiderfy",
16 | "sprintf",
17 | "vsprintf"
18 | ],
19 | "eslint.validate": [
20 | "javascript",
21 | "javascriptreact",
22 | "json"
23 | ],
24 | "editor.codeActionsOnSave": {
25 | "source.fixAll.eslint": true,
26 | },
27 | }
28 |
--------------------------------------------------------------------------------
/config.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug": false,
3 | "tileDirectory": "images/tiles",
4 | "iconDirectory": "images/icons",
5 | "showIdentifiers": false,
6 | "groupPlayers": true, // Wether the map should group players together into "clusters" or not.
7 | "defaults": { // If a server doesn't have the key-value set, it will fallback to these values
8 | "ip": "127.0.0.1", // Make sure this is the PUBLIC IP of the FIVEM server
9 | "socketPort": "30121" // Set to the port that you set in the "socket_port" convar (if different to the one in the config)
10 | },
11 | "servers": {
12 | "Default server 1": {}, // The config values are above, no need to write them again
13 | "Only IP changed": { // Only the IP is different for this server. So, change it
14 | "ip": "192.168.1.2"
15 | },
16 | "Everything is different": {
17 | "ip": "10.10.0.1",
18 | "socketPort": "60121"
19 | },
20 | "Reverse Proxy Example": {
21 | "reverseProxy": { // If you have knowledge on how to set up a "reverse proxy" for your webserver and want to keep the map on a secure connection, set the values below.
22 | "socket": "wss://example.com", // The secure, proxied url for the socket connection
23 | "blips": "https://example.com/server1/blips.json" // The secure, proxied url for the blip file
24 | }
25 | }
26 | },
27 | "maps": [
28 | {
29 | "name": "Normal",
30 | "url": "{tileDirectory}/normal/minimap_sea_{y}_{x}.png",
31 | "minZoom": -2 // Set lower to zoom out more
32 | },
33 | {
34 | "name": "Postal",
35 | "url": "{tileDirectory}/postal/minimap_sea_{y}_{x}.png",
36 | "minZoom": -5 // Since this is 3 times bigger, just remove 3 from the default minZoom
37 | }
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/dist/2b3e1faf89f94a483539.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/dist/2b3e1faf89f94a483539.png
--------------------------------------------------------------------------------
/dist/416d91365b44e4b4f477.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/dist/416d91365b44e4b4f477.png
--------------------------------------------------------------------------------
/dist/8f2c4d11474275fbc161.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/dist/8f2c4d11474275fbc161.png
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
Havoc's LiveMap
--------------------------------------------------------------------------------
/dist/runtime.ebf5ee37024ab61be51d.js:
--------------------------------------------------------------------------------
1 | (()=>{"use strict";var e,r={},t={};function o(e){var n=t[e];if(void 0!==n)return n.exports;var i=t[e]={id:e,exports:{}};return r[e].call(i.exports,i,i.exports,o),i.exports}o.m=r,e=[],o.O=(r,t,n,i)=>{if(!t){var a=1/0;for(s=0;s=i)&&Object.keys(o.O).every((e=>o.O[e](t[c])))?t.splice(c--,1):(l=!1,i0&&e[s-1][2]>i;s--)e[s]=e[s-1];e[s]=[t,n,i]},o.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return o.d(r,{a:r}),r},o.d=(e,r)=>{for(var t in r)o.o(r,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var e;o.g.importScripts&&(e=o.g.location+"");var r=o.g.document;if(!e&&r&&(r.currentScript&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");t.length&&(e=t[t.length-1].src)}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),o.p=e})(),(()=>{o.b=document.baseURI||self.location.href;var e={666:0};o.O.j=r=>0===e[r];var r=(r,t)=>{var n,i,[a,l,c]=t,u=0;if(a.some((r=>0!==e[r]))){for(n in l)o.o(l,n)&&(o.m[n]=l[n]);if(c)var s=c(o)}for(r&&r(t);u {\n\tif(chunkIds) {\n\t\tpriority = priority || 0;\n\t\tfor(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1];\n\t\tdeferred[i] = [chunkIds, fn, priority];\n\t\treturn;\n\t}\n\tvar notFulfilled = Infinity;\n\tfor (var i = 0; i < deferred.length; i++) {\n\t\tvar [chunkIds, fn, priority] = deferred[i];\n\t\tvar fulfilled = true;\n\t\tfor (var j = 0; j < chunkIds.length; j++) {\n\t\t\tif ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) {\n\t\t\t\tchunkIds.splice(j--, 1);\n\t\t\t} else {\n\t\t\t\tfulfilled = false;\n\t\t\t\tif(priority < notFulfilled) notFulfilled = priority;\n\t\t\t}\n\t\t}\n\t\tif(fulfilled) {\n\t\t\tdeferred.splice(i--, 1)\n\t\t\tvar r = fn();\n\t\t\tif (r !== undefined) result = r;\n\t\t}\n\t}\n\treturn result;\n};","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\tid: moduleId,\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n// expose the modules object (__webpack_modules__)\n__webpack_require__.m = __webpack_modules__;\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","var scriptUrl;\nif (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + \"\";\nvar document = __webpack_require__.g.document;\nif (!scriptUrl && document) {\n\tif (document.currentScript)\n\t\tscriptUrl = document.currentScript.src\n\tif (!scriptUrl) {\n\t\tvar scripts = document.getElementsByTagName(\"script\");\n\t\tif(scripts.length) scriptUrl = scripts[scripts.length - 1].src\n\t}\n}\n// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration\n// or pass an empty string (\"\") and set the __webpack_public_path__ variable from your code to use your own logic.\nif (!scriptUrl) throw new Error(\"Automatic publicPath is not supported in this browser\");\nscriptUrl = scriptUrl.replace(/#.*$/, \"\").replace(/\\?.*$/, \"\").replace(/\\/[^\\/]+$/, \"/\");\n__webpack_require__.p = scriptUrl;","__webpack_require__.b = document.baseURI || self.location.href;\n\n// object to store loaded and loading chunks\n// undefined = chunk not loaded, null = chunk preloaded/prefetched\n// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded\nvar installedChunks = {\n\t666: 0\n};\n\n// no chunk on demand loading\n\n// no prefetching\n\n// no preloaded\n\n// no HMR\n\n// no HMR manifest\n\n__webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0);\n\n// install a JSONP callback for chunk loading\nvar webpackJsonpCallback = (parentChunkLoadingFunction, data) => {\n\tvar [chunkIds, moreModules, runtime] = data;\n\t// add \"moreModules\" to the modules object,\n\t// then flag all \"chunkIds\" as loaded and fire callback\n\tvar moduleId, chunkId, i = 0;\n\tif(chunkIds.some((id) => (installedChunks[id] !== 0))) {\n\t\tfor(moduleId in moreModules) {\n\t\t\tif(__webpack_require__.o(moreModules, moduleId)) {\n\t\t\t\t__webpack_require__.m[moduleId] = moreModules[moduleId];\n\t\t\t}\n\t\t}\n\t\tif(runtime) var result = runtime(__webpack_require__);\n\t}\n\tif(parentChunkLoadingFunction) parentChunkLoadingFunction(data);\n\tfor(;i < chunkIds.length; i++) {\n\t\tchunkId = chunkIds[i];\n\t\tif(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {\n\t\t\tinstalledChunks[chunkId][0]();\n\t\t}\n\t\tinstalledChunks[chunkIds[i]] = 0;\n\t}\n\treturn __webpack_require__.O(result);\n}\n\nvar chunkLoadingGlobal = self[\"webpackChunk\"] = self[\"webpackChunk\"] || [];\nchunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));\nchunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));"],"names":["deferred","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","id","__webpack_modules__","call","m","O","result","chunkIds","fn","priority","notFulfilled","Infinity","i","length","fulfilled","j","Object","keys","every","key","splice","r","n","getter","__esModule","d","a","definition","o","defineProperty","enumerable","get","g","globalThis","this","Function","e","window","obj","prop","prototype","hasOwnProperty","Symbol","toStringTag","value","scriptUrl","importScripts","location","document","currentScript","src","scripts","getElementsByTagName","Error","replace","p","b","baseURI","self","href","installedChunks","chunkId","webpackJsonpCallback","parentChunkLoadingFunction","data","moreModules","runtime","some","chunkLoadingGlobal","forEach","bind","push"],"sourceRoot":""}
--------------------------------------------------------------------------------
/dist/vendors.ebf5ee37024ab61be51d.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /* @preserve
2 | * Leaflet 1.7.1, a JS library for interactive maps. http://leafletjs.com
3 | * (c) 2010-2019 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4 | */
5 |
6 | /*!
7 | * Bootstrap v5.1.3 (https://getbootstrap.com/)
8 | * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
9 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
10 | */
11 |
--------------------------------------------------------------------------------
/docs/FAQ.md:
--------------------------------------------------------------------------------
1 | # Frequently Asked Questions
2 |
3 | - [Server Not Connecting/Showing Up](#server-not-connectingshowing-up)
4 | - [Not Able to Communicate with the FiveM server](#not-able-to-communicate-with-the-fivem-server)
5 | - [Adding blips using a web request](#adding-blips-using-a-web-request)
6 | - [Error Getting Config, Cannot load map! Multiple Choices](#error-getting-config-cannot-load-map-multiple-choices)
7 | - [Localhost Not Working](#localhost-not-working)
8 | - [Unable to Display Livemap when using HTTPS](#unable-to-display-livemap-when-using-https)
9 | - [Error with Socket Connection](#error-with-socket-connection)
10 | - [Interface Not Connecting to Server](#interface-not-connecting-to-server)
11 | - [Blips Not Appearing](#blips-not-appearing)
12 |
13 |
14 | ## Server Not Connecting/Showing Up
15 |
16 | - If your website is using HTTPS then, you need to set up a reverse proxy for the blip URL and the socket port in order for it to work.
17 | If you don't know how to do this, just make the website use HTTP.
18 | - If the issue persists, make sure you have a "map" array in the config. See [config.example.json](https://github.com/TGRHavoc/live_map-interface/blob/master/config.example.json).
19 |
20 | Additionally, if you wanted to use your own images, you would need the YTD files then, you can get a developer to run the extract_png.py file located [here](https://github.com/TGRHavoc/live_map-interface/tree/master/images/tiles).
21 |
22 | ## Not Able to Communicate with the FiveM server
23 |
24 | - Make sure the resource is actually starting on the server. Usually, setting the debug to debug or something should give you enough information to see if it's starting.
25 | - If it is starting and listening, make sure it's allowed through the firewall.
26 |
27 | ## Adding blips using a web request
28 |
29 | No, but you can add then manually. You can either go to each place and and do the command in game or, edit the json file manually.
30 |
31 | ## Error Getting Config, Cannot load map! Multiple Choices
32 |
33 | Probably means that the user's cannot access /config.json on your webserver.
34 | Make sure you can access it by going to http://{{WEB_SERVER}}/config.json and making sure it shows your config file.
35 |
36 | ## Localhost Not Working
37 |
38 | You need to use the server's public IP in the config.json file for it to work for other users.
39 |
40 | ## Unable to Display Livemap when using HTTPS
41 |
42 | 1. It's not possible for the resource to make a secure connection to FiveM. Simply, uninstall the resource.
43 | 2. If you do, then lookup on Google for "set up reverse proxy in {{webserver}}".
44 | 3. Substitute with the webservers name. And to get the websocket running securely as well, just change the query to "set up websocket revserse proxy in {{webserver}}".
45 |
46 | ## Error with Socket Connection
47 |
48 | Most likely a network issue. Make sure you have correctly opened your ports for the websocket connection.
49 |
50 | ## Interface Not Connecting to Server
51 |
52 | Port forward (if needed), allow though firewall and set the config to use the FiveM server's public IP address.
53 |
54 | ## Blips Not Appearing
55 |
56 | This is most likely because you haven't done the /blips generate command in game (you need to be an admin).
57 |
58 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # LiveMap interface
2 |
3 | [](#contributors-)
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | This is the Web Interface for the FiveM addon [live_map](https://github.com/TGRHavoc/live_map).
12 |
13 | - [Prerequisites](#prerequisites)
14 | - [How to install](#how-to-install)
15 | - [Screenshots 🖼️](#screenshots-️)
16 | - [Frequently Asked Questions ❓](#frequently-asked-questions-)
17 | - [Adding custom maps 🗺️](#adding-custom-maps-️)
18 | - [Developers Information 🧑💻](#developers-information-)
19 | - [Contributors ✨](#contributors-)
20 |
21 | ## Prerequisites
22 |
23 | In order to have this working, it is advised that you already have a webserver running and correctly configured.
24 | You will also need to install [live_map](https://github.com/TGRHavoc/live_map) on your FiveM server and have it configured.
25 |
26 | ## How to install
27 |
28 | Download the [latest version](https://github.com/TGRHavoc/live_map-interface/archive/master.zip).
29 |
30 | This should be enough to get the interface up and running on your website. If you want to change stuff like the images, look in the [configuration](config.md) section.
31 |
32 | ## Screenshots 🖼️
33 |
34 | |  |  |
35 | | :----------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------: |
36 | | Main Interface (Desktop) | Control Modal (Desktop) |
37 |
38 | |  |  |
39 | | :--------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------: |
40 | | Blip control modal (Desktop) | Language Select (Desktop) |
41 |
42 |
43 | |  |  |
44 | | :-------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------: |
45 | | Main Interface (Mobile) | Control Modal (Mobile) |
46 |
47 | |  |  |
48 | | :-----------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------: |
49 | | Blip control modal (Mobile) | Language Select (Mobile) |
50 |
51 | |  |
52 | | :--------------------------------------------------------------------------------------------------: |
53 | | Player information (example) |
54 |
55 | |  |
56 | | :------------------------------------------------------------------------------------------------: |
57 | | GIF of the interface |
58 |
59 | ## Frequently Asked Questions ❓
60 |
61 | If you run into any issues, consult the [F.A.Q.](FAQ.md) first. Amongst other things, this document contains information about common issues involving:
62 | - server not connecting/showing up
63 | - unable to communicate with FiveM server
64 | - errors getting blips
65 | - issues with localhost
66 |
67 | ## Adding custom maps 🗺️
68 | Information on how to add custom maps from GTA or alternative into livemap. See [Custom Maps](custom_maps.md).
69 |
70 | ## Developers Information 🧑💻
71 |
72 | Useful information for developers on dependencies and files locations for development. See [Developers Information](developers.md).
73 |
74 | ## Contributors ✨
75 |
76 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
77 |
78 |
79 |
80 |
81 |
101 |
102 |
103 |
104 |
105 |
106 |
107 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification.
108 | Contributions of any kind welcome!
109 |
110 | If you would like to contribute, then check out the [contributing guide](contributing.md).
111 |
--------------------------------------------------------------------------------
/docs/config.md:
--------------------------------------------------------------------------------
1 | # Configuration
2 |
3 | - [config.json](#configjson)
4 | - [defaults object](#defaults-object)
5 | - [reverse proxy object](#reverse-proxy-object)
6 | - [server object](#server-object)
7 | - [map object](#map-object)
8 | - [config.html](#confightml)
9 |
10 | ## config.json
11 | The only file you will need to change to configure the map is the `config.json` file.
12 | This file can have comments in it without breaking the interface.
13 | Below is a table with the different things you can put into your `config.json` file.
14 |
15 | | Name | Type | Example | What it does |
16 | | :-------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------: |
17 | | debug | boolean | false | This just enables/disables debug mode. |
18 | | tileDirectory | string | "images/tiles" | This is the directory that the interface should look in to find the tiled images. Note: This is used in the `maps` array |
19 | | iconDirectory | string | "images/icons" | This is where the interface can find the icon images |
20 | | showIdentifiers | boolean | false | This determines whether the interface shows a player's identifier or not. Note: This may be an IP (if server has it enabled). |
21 | | defaults | [default object](#defaults-object) | `"defaults": { "ip": "tgrhavoc.me", "socketPort": "30121"}` | This is the default the interface should fall back to use if a server doesn't have the value set. |
22 | | servers | [server object](#server-object) | `"A server": {"ip": "example.com"}` | This is the object that contains the server data for the interface. |
23 | | maps | array of [map objects](#map-object) | `"maps": [{"name": "Normal", "url": "{tileDirectory}/normal/minimap_sea_{y}_{x}.png"}]` | An array containing the different map tiles available to use on the interface. |
24 |
25 | #### defaults object
26 |
27 | | Name | Type | Example | What it does |
28 | | :----------- | --------------------------------------------- | --------------------------------------------- | :------------------------------------------------------------------------------------: |
29 | | ip | string | "example.com" | The **public** IP for your FiveM server. |
30 | | socketPort | number | 30121 | The port your LiveMap resource is listening on. |
31 | | reverseProxy | [reserse proxy object](#reverse-proxy-object) | `{"blips": "https://example.com/blips.json"}` | If you have a reverse proxy set up for the blips and socket connection, then use this. |
32 |
33 |
34 | ### reverse proxy object
35 |
36 | | Name | Type | Example | What it does |
37 | | :----- | ------ | ---------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------: |
38 | | socket | string | "wss://echo.example.com" | The URL to the reverse proxy for the websocket connection. If you're using NGINX, the [following should be useful](https://www.nginx.com/blog/websocket-nginx/). |
39 | | blips | string | "https://example.com/server1/blips.json" | The URL to the reverse proxy for the blips file (can even just be a static JSON file on a webserver). |
40 |
41 | ### server object
42 |
43 | The server objects must have a key whoes value is the server's name.
44 | For example, if you have a server called `This is my cool server` then, the server entry would look like.
45 | ```json
46 | "servers" : {
47 | "This is my cool server" : {
48 | "ip": "example.com"
49 | }
50 | }
51 | ```
52 |
53 | **If you don't set something in this object, the interface will look at the "defaults" object and use it's values instead.**
54 | It's therefore best practice to only use the `revserProxy` property in the server's object itself.
55 |
56 | | Name | Type | Example | What it does |
57 | | :----------- | --------------------------------------------- | --------------------------------------------- | :------------------------------------------------------------------------------------: |
58 | | ip | string | "example.com" | The **public** IP for your FiveM server. |
59 | | socketPort | number | 30121 | The port your LiveMap resource is listening on. |
60 | | reverseProxy | [reserse proxy object](#reverse-proxy-object) | `{"blips": "https://example.com/blips.json"}` | If you have a reverse proxy set up for the blips and socket connection, then use this. |
61 |
62 |
63 | ### map object
64 |
65 | | Name | Type | Example | What it does |
66 | | :------ | ------ | ------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------: |
67 | | name | string | "Postal" | The name of this tileset. This is used when the user wants to change the map |
68 | | url | string | "{tileDirectory}/postal/minimap_sea_{y}_{x}.png" | Where the images are located for this map. GTA's minimap files have the Y coordinate before the X coordinate hence the `{y}_{x}` in the string. |
69 | | minZoom | number | -3 | How many times can the user zoom out for this tile set. The lower the number, the more they can zoom out. |
70 |
71 | ## config.html
72 |
73 |
74 |
75 | If you're more of a visual person and, don't want to write a bunch of JSON to set up your server, you can use the utility page `config.html`.
76 | To get to this, just navigate to `utils/config.html` in your browser.
77 | This will give you a basic interface which, you can use to quickly configure the interface.
78 |
79 | !!! note
80 |
81 | You cannot add maps via this page. You will need to do this manually.
--------------------------------------------------------------------------------
/docs/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing to LiveMap Interface
2 |
3 | First off, thanks for taking the time to contribute! ❤️
4 |
5 | All types of contributions are encouraged and valued. See the Table of Contents for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
6 |
7 | !!! note ""
8 | And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
9 |
10 | - Star the project
11 | - Tweet about it
12 |
13 | - [I Have a Question](#i-have-a-question)
14 | - [I Want To Contribute](#i-want-to-contribute)
15 | - [Reporting Bugs](#reporting-bugs)
16 | - [Suggesting Enhancements](#suggesting-enhancements)
17 | - [Your First Code Contribution](#your-first-code-contribution)
18 | - [Commit Messages](#commit-messages)
19 | - [Commit Message Header](#commit-message-header)
20 | - [Type](#type)
21 | - [Scope](#scope)
22 | - [Summary](#summary)
23 | - [Commit Message Body](#commit-message-body)
24 | - [Commit Message Footer](#commit-message-footer)
25 | - [Revert commits](#revert-commits)
26 |
27 |
28 | ## I Have a Question
29 |
30 | > If you want to ask a question, we assume that you have read the available [Documentation](https://docs.tgrhavoc.co.uk/livemap-interface/).
31 |
32 | Before you ask a question, it is best to search for existing [Issues](https://github.com/TGRHavoc/live_map-interface/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
33 |
34 | If you then still feel the need to ask a question and need clarification, we recommend the following:
35 |
36 | - Start a [Issue](https://github.com/TGRHavoc/live_map-interface/issues/new?labels=question).
37 | - Provide as much context as you can about what you're running into.
38 | - Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant.
39 |
40 | We will then take care of the issue as soon as possible.
41 |
42 | ## I Want To Contribute
43 |
44 | > ### Legal Notice
45 | > When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
46 |
47 | ### Reporting Bugs
48 |
49 | #### Before Submitting a Bug Report
50 |
51 | A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
52 |
53 | - Make sure that you are using the latest version.
54 | - Determine if your bug is really a bug and not an error on your side e.g. incorrectly configured (Make sure that you have read the [documentation](https://docs.tgrhavoc.co.uk/livemap-interface/). If you are looking for support, you might want to check [this section](#i-have-a-question)).
55 | - To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/TGRHavoc/live_map-interface/issues?q=label%3Abug).
56 | - Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
57 | - Collect information about the bug:
58 | - Console Errors (Shift + CTRL + J)
59 | - Browser (including version) and OS (Windows, Linux, macOS, x86, ARM)
60 | - Possibly your input and the output or what you did to get the issue
61 | - Can you reliably reproduce the issue? And can you also reproduce it with older versions?
62 |
63 | #### How Do I Submit a Good Bug Report?
64 |
65 | > You must never report security related issues, vulnerabilities or bugs to the issue tracker, or elsewhere in public. Instead please use the [Security Advisories](https://github.com/TGRHavoc/live_map-interface/security/advisories) feature of Github.
66 |
67 |
68 |
69 | We use GitHub issues to track bugs and errors. If you run into an issue with the project:
70 |
71 | - Open an [Issue](https://github.com/TGRHavoc/live_map-interface/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
72 | - Explain the behavior you would expect and the actual behavior.
73 | - Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
74 | - Provide the information you collected in the previous section.
75 |
76 | Once it's filed:
77 |
78 | - The project team will label the issue accordingly.
79 | - A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
80 | - If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution).
81 |
82 | There will be templates you can follow whilst filling out issues.
83 | Please fill these out to the best of your ability when submitting an issue as it will allow the issue to be dealt with smoother and quicker than without.
84 |
85 | ### Suggesting Enhancements
86 |
87 | This section guides you through submitting an enhancement suggestion for LiveMap Interface, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
88 |
89 | #### Before Submitting an Enhancement
90 |
91 | - Make sure that you are using the latest version.
92 | - Read the [documentation](https://docs.tgrhavoc.co.uk/livemap-interface/) carefully and find out if the functionality is already covered, maybe by an individual configuration.
93 | - Perform a [search](https://github.com/TGRHavoc/live_map-interface/issues) to see if the [enhancement](https://github.com/TGRHavoc/live_map-interface/labels/enhancement) or [feature request](https://github.com/TGRHavoc/live_map-interface/labels/feature-request) has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
94 | - Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
95 |
96 | #### How Do I Submit a Good Enhancement Suggestion?
97 |
98 | Enhancement suggestions are tracked as [GitHub issues](https://github.com/TGRHavoc/live_map-interface/labels/enhancement).
99 |
100 | - Use a **clear and descriptive title** for the issue to identify the suggestion.
101 | - Provide a **step-by-step description of the suggested enhancement** in as much detail as possible.
102 | - **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
103 | - You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
104 | - **Explain why this enhancement would be useful** to most LiveMap Interface users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
105 |
106 | ### Your First Code Contribution
107 | In order to get started with contributing code to LiveMap Interface, you will need to have a text editor or an Integrated Development Environment (IDE).
108 | The most popular text editors that you might want to use include (but is not limited to) [VisualStudio Code](https://code.visualstudio.com/), [Atom](https://atom.io/), [Sublime Text](https://www.sublimetext.com/) or [Notepad++](https://notepad-plus-plus.org/downloads/).
109 |
110 | After you have your editor/IDE set up to your liking and you're ready to get coding, head over to the [developers](developers.md) file for more information.
111 |
112 | ## Commit Messages
113 |
114 | This project utilises the [Conventional Commits Guidelines](https://www.conventionalcommits.org/en/v1.0.0/).
115 | All commits should follow this convention as it leads to **more readable messages** that are easy to follow when looking through the **project history**.
116 |
117 | Each commit message consists of a **header**, a **body**, and a **footer**.
118 |
119 | ```
120 |
121 |
122 |
123 |
124 |
125 | ```
126 |
127 | The `header` is mandatory and must conform to the [Commit Message Header](#commit-message-header) format.
128 |
129 | The `body` is mandatory for all commits except for those of type "docs".
130 | When the body is present it must be at least 20 characters long and must conform to the [Commit Message Body](#commit-message-body) format.
131 |
132 | The `footer` is optional. The [Commit Message Footer](#commit-message-footer) format describes what the footer is used for and the structure it must have.
133 |
134 | Any line of the commit message cannot be longer than 100 characters.
135 |
136 |
137 | ### Commit Message Header
138 |
139 | ```
140 | ():
141 | │ │ │
142 | │ │ └─⫸ Summary in present tense. Not capitalized. No period at the end.
143 | │ │
144 | │ └─⫸ Commit Scope
145 | │
146 | └─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test
147 | ```
148 |
149 | The `` and `` fields are mandatory, the `()` field is optional.
150 |
151 |
152 | #### Type
153 |
154 | Must be one of the following:
155 |
156 | * **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
157 | * **ci**: Changes to our CI configuration files and scripts (example scopes: Circle, BrowserStack, SauceLabs)
158 | * **docs**: Documentation only changes
159 | * **feat**: A new feature
160 | * **fix**: A bug fix
161 | * **perf**: A code change that improves performance
162 | * **refactor**: A code change that neither fixes a bug nor adds a feature
163 | * **test**: Adding missing tests or correcting existing tests
164 | * **translation**: Adding or updating translations
165 |
166 |
167 | #### Scope
168 | The scope should be the name of the file being modified.
169 | If creating a new file or general changes in a directory then, the directory name (or one of the parents if more descriptive) will suffice.
170 |
171 | If you're unsure of the scope then, just leave it blank.
172 |
173 | #### Summary
174 |
175 | Use the summary field to provide a succinct description of the change:
176 |
177 | * use the imperative, present tense: "change" not "changed" nor "changes"
178 | * don't capitalize the first letter
179 | * no dot (.) at the end
180 |
181 |
182 | ### Commit Message Body
183 |
184 | Just as in the summary, use the imperative, present tense: "fix" not "fixed" nor "fixes".
185 |
186 | Explain the motivation for the change in the commit message body. This commit message should explain _why_ you are making the change.
187 | You can include a comparison of the previous behavior with the new behavior in order to illustrate the impact of the change.
188 |
189 |
190 | ### Commit Message Footer
191 |
192 | The footer can contain information about breaking changes and is also the place to reference GitHub issues, Jira tickets, and other PRs that this commit closes or is related to.
193 |
194 | ```
195 | BREAKING CHANGE:
196 |
197 |
198 |
199 |
200 | Fixes #
201 | ```
202 |
203 | Breaking Change section should start with the phrase "BREAKING CHANGE: " followed by a summary of the breaking change, a blank line, and a detailed description of the breaking change that also includes migration instructions if required.
204 |
205 |
206 | ### Revert commits
207 |
208 | If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit.
209 |
210 | The content of the commit message body should contain:
211 |
212 | - information about the SHA of the commit being reverted in the following format: `This reverts commit `,
213 | - a clear description of the reason for reverting the commit message.
214 |
--------------------------------------------------------------------------------
/docs/custom_maps.md:
--------------------------------------------------------------------------------
1 | # Custom Images
2 |
3 | It's possible to have custom map images in LiveMap.
4 | You can do this if you want LiveMap to show the same map that you have in game, cool right?
5 |
6 | It's a bit of an involved process but, if you're willing to put in the time, the result is worth it.
7 |
8 | Note: You must have [Python 3](https://www.python.org/downloads/) installed.
9 |
10 | - [Step 1: Extract YTD](#step-1-extract-ytd)
11 | - [Step 2: Installing Pillow](#step-2-installing-pillow)
12 | - [Step 3: Getting the Images](#step-3-getting-the-images)
13 | - [Step 4: Configure the map](#step-4-configure-the-map)
14 |
15 | ## Step 1: Extract YTD
16 | If you have a custom map in FiveM then, you should already have the YTD files.
17 |
18 | If not, then you can extract them from GTA using [OpenIV](https://openiv.com/).
19 | The default path for the minimap files is `GTA V\x64b.rpf\data\cdimages\scaleform_generic.rpf\`.
20 | I recommend that you extract the `minimap_sea` files if they are available as they're generally higher quality.
21 |
22 | 
23 |
24 | I recommend you extract the files into the [images/tiles](https://github.com/TGRHavoc/live_map-interface/tree/master/images/tiles) directory.
25 | If not, make sure to copy the Python scripts to the same directory as your YTD files.
26 |
27 | ## Step 2: Installing Pillow
28 |
29 | Time to install [Pillow](https://pypi.org/project/Pillow/).
30 |
31 | ```shell
32 | ❯ python -m pip install Pillow
33 | ```
34 |
35 | ## Step 3: Getting the Images
36 |
37 | This set is as easy as `python extract_png.py`.
38 |
39 | This should output something like the following:
40 | ```shell
41 | live_map-interface\images\tiles @4.0.1 develop ±
42 | ❯ python extract_png.py
43 | minimap_sea_0_0.ytd
44 | Magic: b'RSC7', 0x37435352
45 | Version in header: 13
46 | System flag = 131072, Graphics flag = 3489660947
47 | Calculated version '13'. Valid RSC7 header
48 | Shift (system) : 0
49 | Shift (gfx) : 3
50 | Size calculated: 1056784, Size of system: 8192, Size of gfx: 1048576. SIZE: 190009
51 | --- SNIP ---
52 | Read name: minimap_sea_2_1
53 | Reading from GFX
54 | Done
55 | ```
56 |
57 | This should give you some PNG files!
58 |
59 | 
60 |
61 | Put these into their own folder.
62 |
63 | For example: `new-map`
64 | ```shell
65 | new-map/
66 | --- minimap_sea_0_0.png
67 | --- minimap_sea_0_1.png
68 | --- minimap_sea_1_0.png
69 | --- minimap_sea_1_1.png
70 | --- minimap_sea_2_0.png
71 | --- minimap_sea_2_1.png
72 | ```
73 |
74 | ## Step 4: Configure the map
75 |
76 | Make sure you add the new map images to `config.json`.
77 | Using the example above, you would add the following to the [maps section of the config](config.md#config.json).
78 |
79 | ```json
80 | {
81 | "name": "New Map",
82 | "url": "{tileDirectory}/new-map/minimap_sea_{y}_{x}.png",
83 | "minZoom": -2 // Set lower to zoom out more
84 | }
85 | ```
86 |
87 | So, it would look like
88 | ```json
89 | "maps" : [
90 | {
91 | "name": "Normal",
92 | "url": "{tileDirectory}/normal/minimap_sea_{y}_{x}.png",
93 | "minZoom": -2 // Set lower to zoom out more
94 | },
95 | {
96 | "name": "New Map",
97 | "url": "{tileDirectory}/new-map/minimap_sea_{y}_{x}.png",
98 | "minZoom": -2 // Set lower to zoom out more
99 | },
100 | {
101 | "name": "Postal",
102 | "url": "{tileDirectory}/postal/minimap_sea_{y}_{x}.png",
103 | "minZoom": -3 // Since this is 3 times bigger, just remove 3 from the default minZoom
104 | }
105 | ]
106 | ```
107 |
108 | If you _just_ wanted the new map, that would be the only entry:
109 | ```json
110 | "maps" : [
111 | {
112 | "name": "New Map",
113 | "url": "{tileDirectory}/new-map/minimap_sea_{y}_{x}.png",
114 | "minZoom": -2 // Set lower to zoom out more
115 | }
116 | ]
117 | ```
118 |
--------------------------------------------------------------------------------
/docs/developers.md:
--------------------------------------------------------------------------------
1 | # Developer Information
2 |
3 | If you're looking to customize the interface for your community or, want to improve it for all then this should help you get started.
4 |
5 | - [Step 1: Fork & Clone](#step-1-fork--clone)
6 | - [Step 2: Installing Dependencies](#step-2-installing-dependencies)
7 | - [Step 3: Edit code](#step-3-edit-code)
8 | - [Editing JS files](#editing-js-files)
9 | - [Editing SCSS files](#editing-scss-files)
10 | - [Step 4: Build!](#step-4-build)
11 |
12 | ## Step 1: Fork & Clone
13 |
14 | So. The first thing you're going to need to do is fork & clone this repository.
15 | ```shell
16 | ❯ git clone https://github.com/TGRHavoc/live_map-interface.git
17 | # or if you have ssh keys set up
18 | ❯ git clone git@github.com:TGRHavoc/live_map-interface.git
19 |
20 | > Cloning into 'live_map-interface'...
21 | > remote: Enumerating objects: 8288, done.
22 | > remote: Counting objects: 100% (511/511), done.
23 | > remote: Compressing objects: 100% (350/350), done.
24 | > remote: Total 8288 (delta 199), reused 430 (delta 150), pack-reused 7777
25 | > Receiving objects: 100% (8288/8288), 257.39 MiB | 7.10 MiB/s, done.
26 | > Resolving deltas: 100% (2991/2991), done.```
27 | ```
28 |
29 | If you're working on a new feature that you want to push to the main repository then, best practice is to create a new branch.
30 | ```shell
31 | ❯ git checkout -b feature/new-feature
32 | ```
33 |
34 | ## Step 2: Installing Dependencies
35 |
36 | In order to work with this repository, you will need a few programs installed.
37 | The main one is Node Package Manager (NPM).
38 | You can find information on their website on how to install NPM: [https://www.npmjs.com/get-npm](https://www.npmjs.com/get-npm).
39 |
40 | It is recommended that you also install [yarn](https://classic.yarnpkg.com/en/docs/install/) but, not it's not required.
41 |
42 | Once npm/yarn is installed you can install the dependencies needed to "build" your code.
43 |
44 | ```
45 | ❯ npm install
46 | # or if using yarn
47 | ❯ yarn install
48 |
49 | > yarn install v1.13.0
50 | > [1/4] Resolving packages...
51 | > [2/4] Fetching packages...
52 | > info fsevents@1.2.13: The platform "win32" is incompatible with this module.
53 | > info "fsevents@1.2.13" is an optional dependency and failed compatibility check. Excluding it from installation.
54 | > [3/4] Linking dependencies...
55 | > [4/4] Building fresh packages...
56 | > Done in 108.30s.
57 | ```
58 |
59 | ## Step 3: Edit code
60 |
61 | Hopefully the structure of the project isn't too alien.
62 | I've tried to create a directory structure that "makes sense".
63 | For example, the JavaScript files all go in [js/src](https://github.com/TGRHavoc/live_map-interface/tree/master/js) and all the styling files go into [style](https://github.com/TGRHavoc/live_map-interface/tree/master/style).
64 |
65 | Some directory names you may come across include:
66 | - `vendor` - Other projects that this project depends on. For example, bootstrap's files.
67 | - `{first,last}_bundle` - This determines where in the HTML page these JS files are loaded.
68 | - `first_bundle` - Loaded in the "head".
69 | - `last_bundle` - Loaded after all the other HTML tags.
70 | - `scss` - The SCSS files for styling the interface
71 | - `webfonts` - Fonts used by the interface
72 |
73 | ### Editing JS files
74 |
75 | Since moving over to Webpack this process has gotten a whole lot easier.
76 | All you have to do now, is create a JS file for your code, make sure it's included somewhere (`import {YourComponent} from "./yourfile"`) and voila!
77 |
78 | If you want to see your changes in real-time you can run `yarn dev` (or `npm run dev`) to start the webpack development server.
79 |
80 | Building this into the bundled script file is as easy as `yarn build` (or `npm run build`)!
81 | This will do some magic and place the files needed to run LiveMap in the `dist/` folder.
82 |
83 |
84 | ### Editing SCSS files
85 |
86 | This should be fairly straight forward with webpack as well.
87 | Create your SCSS file (e.g. `src/sass/myStyle.scss`).
88 | Make sure it's included in the main scss file (`src/sass/main.scss`) by importing it `@import "./myStyle";`.
89 |
90 | You should now be done.
91 |
92 | Webpack will transform this into CSS and pop it into the JavaScript bundle, loading the style when the JS is loaded.
93 | Again, to see your changes in real-time make sure you have the webpack development server running (`yarn dev` or `npm run dev`).
94 |
95 |
96 |
97 | ## Step 4: Build!
98 |
99 | Since you've already installed all the dependencies and what not, this step should be easy.
100 | Just run the build script!
101 |
102 | ```
103 | ❯ npm run build
104 | # or if using yarn
105 | ❯ yarn build
106 | yarn run v1.22.17
107 | $ webpack build
108 | assets by status 3.34 KiB [cached] 3 assets
109 | assets by status 739 KiB [emitted]
110 | asset livemap.e39bf3ab164133c7ac48.js 722 KiB [emitted] [immutable] [minimized] [big] (name: index) 2 related assets
111 | asset ../index.html 8.46 KiB [emitted] [compared for emit]
112 | asset index.html 8.41 KiB [emitted]
113 | orphan modules 86.4 KiB [orphan] 15 modules
114 | runtime modules 2.39 KiB 7 modules
115 | cacheable modules 1.1 MiB (javascript) 3.34 KiB (asset)
116 | modules by path ./ 1.1 MiB (javascript) 3.34 KiB (asset)
117 | javascript modules 1.1 MiB
118 | modules by path ./node_modules/ 559 KiB 14 modules
119 | modules by path ./src/ 566 KiB
120 | ./src/js/_app.js + 14 modules 87.4 KiB [built] [code generated]
121 | ./node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./src/sass/main.scss 479 KiB [built] [code generated]
122 | asset modules 126 bytes (javascript) 3.34 KiB (asset)
123 | ./node_modules/leaflet/dist/images/layers.png 42 bytes (javascript) 696 bytes (asset) [built] [code generated]
124 | ./node_modules/leaflet/dist/images/layers-2x.png 42 bytes (javascript) 1.23 KiB (asset) [built] [code generated]
125 | ./node_modules/leaflet/dist/images/marker-icon.png 42 bytes (javascript) 1.43 KiB (asset) [built] [code generated]
126 | modules by path data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/ 4.39 KiB 16 modules
127 |
128 | WARNING in webpack performance recommendations:
129 | You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
130 | For more info visit https://webpack.js.org/guides/code-splitting/
131 |
132 | webpack 5.64.1 compiled with 14 warnings in 7914 ms
133 | Done in 8.90s.
134 |
135 | ```
136 |
--------------------------------------------------------------------------------
/docs/images/0c19843d5026e94c83623b294406d2cf6de64f072cd15f6f9afe65b2ffcd5db8.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/0c19843d5026e94c83623b294406d2cf6de64f072cd15f6f9afe65b2ffcd5db8.gif
--------------------------------------------------------------------------------
/docs/images/0e2595e1746eddc1e694a32653fa0ddd1d0eadde33cdc3e22be5e708f059f898.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/0e2595e1746eddc1e694a32653fa0ddd1d0eadde33cdc3e22be5e708f059f898.png
--------------------------------------------------------------------------------
/docs/images/1b74c6a78fda6509d07d328ce58d890f1b955122b68b6f05080b6532e97ffe7f.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/1b74c6a78fda6509d07d328ce58d890f1b955122b68b6f05080b6532e97ffe7f.png
--------------------------------------------------------------------------------
/docs/images/4f9f772cf2c04f1ada6be1d56dccb56155d6607cb0dc7bf2e526a6f43bf7128b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/4f9f772cf2c04f1ada6be1d56dccb56155d6607cb0dc7bf2e526a6f43bf7128b.png
--------------------------------------------------------------------------------
/docs/images/583c0e9ed7c51aad2d394775cdee98ed83b3637eb92bca89af8d14422ec84db4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/583c0e9ed7c51aad2d394775cdee98ed83b3637eb92bca89af8d14422ec84db4.png
--------------------------------------------------------------------------------
/docs/images/5a53cff3a68dd004f524716274d12f7791a7de7cdcd40e49de60b7e50ccd4569.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/5a53cff3a68dd004f524716274d12f7791a7de7cdcd40e49de60b7e50ccd4569.png
--------------------------------------------------------------------------------
/docs/images/697acd53e4f8099573cdaf9ca7f142e4cff57f35c3cba6b67fc4876420172e06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/697acd53e4f8099573cdaf9ca7f142e4cff57f35c3cba6b67fc4876420172e06.png
--------------------------------------------------------------------------------
/docs/images/7ae7ff5a3642b4fc38b60e8696cc6bd96592726e608780d763afce9043a0aa8e.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/7ae7ff5a3642b4fc38b60e8696cc6bd96592726e608780d763afce9043a0aa8e.png
--------------------------------------------------------------------------------
/docs/images/LiveMap logo.svg:
--------------------------------------------------------------------------------
1 | LiveMap logo Liv e Map
--------------------------------------------------------------------------------
/docs/images/a7369dd2c6a1d9e31db91cf8822373ad89d310aea6953f85cf952d02c2bcff9f.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/a7369dd2c6a1d9e31db91cf8822373ad89d310aea6953f85cf952d02c2bcff9f.png
--------------------------------------------------------------------------------
/docs/images/b47eab9039fe5df729ee8caba868f4de61df991c55fc06c83cc083897d97b140.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/b47eab9039fe5df729ee8caba868f4de61df991c55fc06c83cc083897d97b140.png
--------------------------------------------------------------------------------
/docs/images/b4e0ced36d3ffcdd61f08fa3eb442c5a01d23699a330fd58207a95c3842a20de.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/b4e0ced36d3ffcdd61f08fa3eb442c5a01d23699a330fd58207a95c3842a20de.png
--------------------------------------------------------------------------------
/docs/images/be9ec7dfc81bd265a3a44d20a98133b05df9a14a8d644409e40528a7debce5b8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/be9ec7dfc81bd265a3a44d20a98133b05df9a14a8d644409e40528a7debce5b8.png
--------------------------------------------------------------------------------
/docs/images/cbc7f7b8ac5ca1b0e7690d642a6229830fa36ee16683cd0f63f32e1bffd230d4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/cbc7f7b8ac5ca1b0e7690d642a6229830fa36ee16683cd0f63f32e1bffd230d4.png
--------------------------------------------------------------------------------
/docs/images/dfd01895ddccb7f7dd9f5cd0e1d07b73c46ecb1823f1ae9f05750ceb08a2af3d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/docs/images/dfd01895ddccb7f7dd9f5cd0e1d07b73c46ecb1823f1ae9f05750ceb08a2af3d.png
--------------------------------------------------------------------------------
/docs/reverse_proxy.md:
--------------------------------------------------------------------------------
1 | # Reverse Proxy
2 |
3 | If you want to set up **secure** website for your LiveMap interface, you will need to use a reverse proxy.
4 | Below are some examples of how you can set up the reverse proxy for some of the more popular webservers.
5 |
6 | The examples below will allow you to add the following [reverse proxy object](config.md#reverse-proxy-object) to your config.
7 |
8 | ```json
9 | "reverseProxy": {
10 | "blips": "https://{{YOUR_WEBSITE}}/blips",
11 | "socket": "wss://{{YOUR_WEBSITE}}/ws"
12 | }
13 | ```
14 |
15 | Where `{{YOUR_WEBSITE}}` is the URL for your LiveMap interface.
16 |
17 | In the examples, please make sure you replace the variables with their _real_ values.
18 |
19 | - `{{FIVEM_IP}}` = The public IP for your FiveM server.
20 | - `{{SOCKET_PORT}}` = The configured [socket port for LiveMap](https://docs.tgrhavoc.co.uk/livemap-resource/config/#socket_port)
21 |
22 | ## Examples:
23 |
24 | - [Apache](#apache)
25 | - [NGINX](#nginx)
26 | - [IIS](#iis)
27 | - [Others](#others)
28 |
29 |
30 | ## Apache
31 |
32 | In your `VirtualHost` block for your website, you will need to add the following:
33 |
34 | ```conf
35 | ProxyRequests on
36 | RequestHeader set X-Forwarded-Proto "http"
37 | ProxyPass /blips http://{{FIVEM_IP}}:{{SOCKET_PORT}}/blips
38 | ProxyPassReverse /blips http://{{FIVEM_IP}}:{{SOCKET_PORT}}/blips
39 |
40 | RewriteEngine on
41 | RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
42 | RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
43 | RewriteRule /ws ws://{{FIVEM_IP}}:{{SOCKET_PORT}}/%{REQUEST_URI} [P]
44 | ```
45 |
46 | So the full block would look like:
47 | ```conf
48 |
49 | ServerName myserver.local
50 |
51 | ProxyRequests on
52 | RequestHeader set X-Forwarded-Proto "http"
53 | ProxyPass /blips http://{{FIVEM_IP}}:{{SOCKET_PORT}}/blips
54 | ProxyPassReverse /blips http://{{FIVEM_IP}}:{{SOCKET_PORT}}/blips
55 |
56 | RewriteEngine on
57 | RewriteCond %{HTTP:UPGRADE} ^WebSocket$ [NC]
58 | RewriteCond %{HTTP:CONNECTION} ^Upgrade$ [NC]
59 | RewriteRule /ws ws://{{FIVEM_IP}}:{{SOCKET_PORT}}/%{REQUEST_URI} [P]
60 |
61 | ```
62 |
63 | ## NGINX
64 |
65 | Inside your `server` block for your website you will need to add
66 | ```conf
67 | location /blips {
68 | proxy_pass http://{{FIVEM_IP}}:{{SOCKET_PORT}}/blips;
69 | }
70 |
71 | location /ws {
72 | proxy_pass http://{{FIVEM_IP}}:{{SOCKET_PORT}}/;
73 |
74 | proxy_set_header X-Real-IP $remote_addr;
75 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
76 | proxy_set_header X-Forwarded-Proto $scheme;
77 | proxy_set_header Upgrade $http_upgrade;
78 | proxy_set_header Connection "upgrade";
79 | }
80 | ```
81 |
82 | The full block would look like:
83 | ```conf
84 | server {
85 | listen 443 ssl;
86 | listen [::]:443 ssl;
87 |
88 | server_name myserver.local;
89 | root /some/location/for/livemap-interface;
90 | index index.html;
91 |
92 | location /blips {
93 | proxy_pass http://{{FIVEM_IP}}:{{SOCKET_PORT}}/blips;
94 | }
95 |
96 | location /ws {
97 | proxy_pass http://{{FIVEM_IP}}:{{SOCKET_PORT}}/;
98 |
99 | proxy_set_header X-Real-IP $remote_addr;
100 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
101 | proxy_set_header X-Forwarded-Proto $scheme;
102 | proxy_set_header Upgrade $http_upgrade;
103 | proxy_set_header Connection "upgrade";
104 | }
105 | }
106 | ```
107 |
108 | ## IIS
109 |
110 | Inside your `web.config` file you will need to add:
111 |
112 | ```conf
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | ```
126 |
127 | The full file would look like:
128 | ```conf
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | ```
147 |
148 | Note: You may need to install [URL Rewrite](http://www.iis.net/downloads/microsoft/url-rewrite) for IIS first.
149 |
150 |
151 | ## Others
152 |
153 | If your webserver isn't listed here them, unfortunately your best bet is to research how to set up a reverse proxy on your webserver.
154 | After you have got yours working and, want to help other's with the same webserver, why don't you [edit this page on Github](https://github.com/TGRHavoc/live_map-interface/edit/develop/docs/reverse_proxy.md) with the information needed?
155 |
--------------------------------------------------------------------------------
/images/icons/blips.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/icons/blips.png
--------------------------------------------------------------------------------
/images/icons/blips_texturesheet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/icons/blips_texturesheet.png
--------------------------------------------------------------------------------
/images/icons/debug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/icons/debug.png
--------------------------------------------------------------------------------
/images/icons/normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/icons/normal.png
--------------------------------------------------------------------------------
/images/layers-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/layers-2x.png
--------------------------------------------------------------------------------
/images/layers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/layers.png
--------------------------------------------------------------------------------
/images/marker-icon-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/marker-icon-2x.png
--------------------------------------------------------------------------------
/images/marker-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/marker-icon.png
--------------------------------------------------------------------------------
/images/marker-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/marker-shadow.png
--------------------------------------------------------------------------------
/images/profile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/profile.png
--------------------------------------------------------------------------------
/images/tiles/extract_png.py:
--------------------------------------------------------------------------------
1 | import os
2 | import zlib
3 | import sys
4 | from read_ytd import YTD
5 | import glob
6 |
7 | #filename = input("Please enter the file name > ")
8 |
9 | for ytd_file in list(glob.glob("minimap_*.ytd")):
10 | print(ytd_file)
11 | ytd = YTD(ytd_file)
12 | ytd.finished()
13 |
--------------------------------------------------------------------------------
/images/tiles/normal/minimap_sea_0_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/tiles/normal/minimap_sea_0_0.png
--------------------------------------------------------------------------------
/images/tiles/normal/minimap_sea_0_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/tiles/normal/minimap_sea_0_1.png
--------------------------------------------------------------------------------
/images/tiles/normal/minimap_sea_1_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/tiles/normal/minimap_sea_1_0.png
--------------------------------------------------------------------------------
/images/tiles/normal/minimap_sea_1_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/tiles/normal/minimap_sea_1_1.png
--------------------------------------------------------------------------------
/images/tiles/normal/minimap_sea_2_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/tiles/normal/minimap_sea_2_0.png
--------------------------------------------------------------------------------
/images/tiles/normal/minimap_sea_2_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/tiles/normal/minimap_sea_2_1.png
--------------------------------------------------------------------------------
/images/tiles/normal/stitched.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/tiles/normal/stitched.png
--------------------------------------------------------------------------------
/images/tiles/postal/minimap_sea_0_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/tiles/postal/minimap_sea_0_0.png
--------------------------------------------------------------------------------
/images/tiles/postal/minimap_sea_0_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/tiles/postal/minimap_sea_0_1.png
--------------------------------------------------------------------------------
/images/tiles/postal/minimap_sea_1_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/tiles/postal/minimap_sea_1_0.png
--------------------------------------------------------------------------------
/images/tiles/postal/minimap_sea_1_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/tiles/postal/minimap_sea_1_1.png
--------------------------------------------------------------------------------
/images/tiles/postal/minimap_sea_2_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/tiles/postal/minimap_sea_2_0.png
--------------------------------------------------------------------------------
/images/tiles/postal/minimap_sea_2_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TGRHavoc/live_map-interface/86678087abe4916bfe245bfd4f5b07e2245fe9d2/images/tiles/postal/minimap_sea_2_1.png
--------------------------------------------------------------------------------
/images/tiles/read_ytd.py:
--------------------------------------------------------------------------------
1 | import os
2 | import zlib
3 | from PIL import Image
4 | import io
5 | import math
6 |
7 |
8 | def extract_data_from_array(arr, count):
9 | return arr[:count], arr[count:]
10 |
11 |
12 | class TextureData:
13 | LENGTH = 64
14 |
15 | def __init__(self, raw_data, reader): # raw_data = 64 bytes from system data
16 | self.VTF, raw_data = extract_data_from_array(raw_data, 4)
17 | self.unk_4h, raw_data = extract_data_from_array(raw_data, 4)
18 | self.unk_8h, raw_data = extract_data_from_array(raw_data, 4)
19 | self.unk_Ch, raw_data = extract_data_from_array(raw_data, 4)
20 | self.unk_10h, raw_data = extract_data_from_array(raw_data, 4)
21 | self.unk_14h, raw_data = extract_data_from_array(raw_data, 4)
22 | self.unk_18h, raw_data = extract_data_from_array(raw_data, 4)
23 | self.unk_1Ch, raw_data = extract_data_from_array(raw_data, 4)
24 | self.unk_20h, raw_data = extract_data_from_array(raw_data, 4)
25 | self.unk_24h, raw_data = extract_data_from_array(raw_data, 4)
26 | self.name_pointer, raw_data = extract_data_from_array(raw_data, 8)
27 | self.Unknown_30h, raw_data = extract_data_from_array(raw_data, 4)
28 | self.Unknown_34h, raw_data = extract_data_from_array(raw_data, 4)
29 | self.Unknown_38h, raw_data = extract_data_from_array(raw_data, 4)
30 | self.Unknown_3Ch, raw_data = extract_data_from_array(raw_data, 4)
31 |
32 | self.name_pointer = int.from_bytes(self.name_pointer, 'little')
33 |
34 | temp = reader.read(self.name_pointer, 1)
35 | how_many_read = 0
36 | name = ""
37 | while temp != b'\x00': # While not null terminator
38 | name += temp.decode("utf-8")
39 | how_many_read += 1
40 | temp = reader.read(self.name_pointer+how_many_read, 1)
41 |
42 | print("Read name: %s" % name)
43 | self.name = name
44 |
45 |
46 | class TextureDX11(TextureData):
47 | LENGTH = 0x90
48 |
49 | def __init__(self, raw_data, reader):
50 | cpy = raw_data
51 | super().__init__(raw_data, reader)
52 |
53 | _, raw_data = extract_data_from_array(raw_data, TextureData.LENGTH)
54 |
55 | self.Unknown_40h, raw_data = extract_data_from_array(raw_data, 4)
56 | self.Unknown_44h, raw_data = extract_data_from_array(raw_data, 4)
57 | self.Unknown_48h, raw_data = extract_data_from_array(raw_data, 4)
58 | self.Unknown_4Ch, raw_data = extract_data_from_array(raw_data, 4)
59 | self.Width, raw_data = extract_data_from_array(raw_data, 2)
60 | self.Height, raw_data = extract_data_from_array(raw_data, 2)
61 | self.Unknown_54h, raw_data = extract_data_from_array(raw_data, 2)
62 | self.Stride, raw_data = extract_data_from_array(raw_data, 2)
63 | self.Format, raw_data = extract_data_from_array(raw_data, 4)
64 | self.Unknown_5Ch, raw_data = extract_data_from_array(raw_data, 1)
65 | self.Levels, raw_data = extract_data_from_array(raw_data, 1)
66 | self.Unknown_5Eh, raw_data = extract_data_from_array(raw_data, 2)
67 | self.Unknown_60h, raw_data = extract_data_from_array(raw_data, 4)
68 | self.Unknown_64h, raw_data = extract_data_from_array(raw_data, 4)
69 | self.Unknown_68h, raw_data = extract_data_from_array(raw_data, 4)
70 | self.Unknown_6Ch, raw_data = extract_data_from_array(raw_data, 4)
71 | self.DataPointer, raw_data = extract_data_from_array(raw_data, 8)
72 | self.Unknown_78h, raw_data = extract_data_from_array(raw_data, 4)
73 | self.Unknown_7Ch, raw_data = extract_data_from_array(raw_data, 4)
74 | self.Unknown_80h, raw_data = extract_data_from_array(raw_data, 4)
75 | self.Unknown_84h, raw_data = extract_data_from_array(raw_data, 4)
76 | self.Unknown_88h, raw_data = extract_data_from_array(raw_data, 4)
77 | self.Unknown_8Ch, raw_data = extract_data_from_array(raw_data, 4)
78 |
79 | self.Format = int.from_bytes(self.Format, "little")
80 | self.Width = int.from_bytes(self.Width, "little")
81 | self.Height = int.from_bytes(self.Height, "little")
82 | self.Levels = int.from_bytes(self.Levels, "little")
83 | self.Stride = int.from_bytes(self.Stride, "little")
84 |
85 | textureLength = 0
86 | length = self.Stride * self.Height
87 | for i in range(0, self.Levels):
88 | textureLength += length
89 | length /= 4
90 | textureLength = math.floor(textureLength)
91 | n = 1
92 | if self.Format == 894720068:
93 | n = 3
94 |
95 | textureDataRaw = reader.read(int.from_bytes(self.DataPointer, "little"), textureLength)
96 |
97 | Image.frombytes('RGBA', (self.Width, self.Height), textureDataRaw, "bcn", n).save("%s.png" % self.name)
98 | print("Done")
99 |
100 |
101 | class SystemData:
102 | SYSTEM_CONST = ~0x50000000
103 | SYSTEM_BASE = 0x50000000
104 |
105 | class GraphicsData:
106 | GFX_CONST = ~0x60000000
107 | GFX_BASE = 0x60000000
108 |
109 | class Resources:
110 |
111 | def read_system(self, pos, count):
112 | start = pos & SystemData.SYSTEM_CONST
113 | return self.ytd.systemData[start:start+count]
114 |
115 | def read_gfx(self, pos, count):
116 | start = pos & GraphicsData.GFX_CONST
117 | return self.ytd.gfxData[start:start+count]
118 |
119 | def read(self, position, bytes=16):
120 | sp = position & SystemData.SYSTEM_BASE
121 | if sp == SystemData.SYSTEM_BASE:
122 | print("Reading from system")
123 | return self.read_system(position, bytes)
124 | elif (position & GraphicsData.GFX_BASE) == GraphicsData.GFX_BASE:
125 | print("Reading from GFX")
126 | return self.read_gfx(position, bytes)
127 | else:
128 | raise Exception("Invalid position")
129 |
130 | def __init__(self, ytdfile):
131 | self.ytd = ytdfile
132 | # Skip the first 48 bytes... They're not useful.. I hope
133 | resourcePointerArrayStart = 48
134 |
135 | resourceData = ytdfile.systemData[resourcePointerArrayStart:resourcePointerArrayStart+16] # Read the resource data line
136 |
137 | arrayPointerStart = resourceData[:8] # Where does the resource array info start?
138 | resourceData = resourceData[8:]
139 |
140 | resourceCount = resourceData[:2]
141 | resourceData = resourceData[2:]
142 |
143 | resourceCapacity = resourceData[:2]
144 | resourceData = resourceData[2:]
145 |
146 | print("Pointer: 0x%x, Entry count: %i, Entry capacity: %i" %
147 | (int.from_bytes(arrayPointerStart, 'little'), int.from_bytes(resourceCount, 'little'),
148 | int.from_bytes(resourceCapacity, 'little')))
149 |
150 | self.read(int.from_bytes(arrayPointerStart, "little"))
151 |
152 | textures = []
153 | for i in range(0, int.from_bytes(resourceCount, 'little')):
154 | offset = i * 8
155 | #pointerLocation = int.from_bytes(resourcePointer, 'little') & ~0x50000000
156 | pointerDataLocation = self.read(int.from_bytes(arrayPointerStart, 'little')+offset, 8) # The real location of the textures
157 |
158 | # pointersRealLocation = int.from_bytes(pointerData, 'little') & ~0x50000000
159 | textureData = TextureDX11(self.read(int.from_bytes(pointerDataLocation, 'little'), TextureDX11.LENGTH), self)
160 | textures.append(textureData)
161 |
162 |
163 | class YTD:
164 | DEFAULT_SIZE = 0x2000
165 |
166 | def G(self, systemFlag, graphicsFlag):
167 | return int(((graphicsFlag >> 28) & 0xF) | (((systemFlag >> 28) & 0xF) << 4))
168 |
169 | def sizeFromFlag(self, flag, baseSize):
170 | baseSize <<= int(flag & 0xf)
171 | size = int((((flag >> 17) & 0x7f) + (((flag >> 11) & 0x3f) << 1) + (((flag >> 7) & 0xf) << 2) + (
172 | ((flag >> 5) & 0x3) << 3) + (((flag >> 4) & 0x1) << 4)) * baseSize)
173 | print("Adding size %i, basesize: %i" % (size, baseSize))
174 | i = 0
175 | while i < 4:
176 | # size += (((flag >> (24 + i)) & 1) == 1) ? (baseSize >> (1 + i)) : 0;
177 | if (flag >> (24 + i)) & 1 == 1:
178 | size += (baseSize >> (1 + i))
179 | i += 1
180 | return size
181 |
182 | def inflate(self, data):
183 | decompress = zlib.decompressobj(
184 | -zlib.MAX_WBITS # see above
185 | )
186 | inflated = decompress.decompress(data)
187 | inflated += decompress.flush()
188 | return inflated
189 |
190 | def calcSytemSize(self, sysFlag):
191 | s16 = int(sysFlag >> 27) & 0x1
192 | s8 = int(sysFlag >> 26) & 0x1
193 | s4 = int(sysFlag >> 25) & 0x1
194 | s2 = int(sysFlag >> 24) & 0x1
195 | m1 = int(sysFlag >> 17) & 0x7f
196 | m2 = int(sysFlag >> 11) & 0x3f
197 | m4 = int(sysFlag >> 7) & 0xf
198 | m8 = int(sysFlag >> 5) & 0x3
199 | m16 = int(sysFlag >> 4) & 0x1
200 | shiftSize = int(sysFlag >> 0) & 0xf
201 | baseSize = self.DEFAULT_SIZE << shiftSize
202 |
203 | print("Shift (system) : %i" % shiftSize)
204 |
205 | self.SYSTEM_SIZE = int(baseSize * s16 / 16 + \
206 | baseSize * s8 / 8 + \
207 | baseSize * s4 / 4 + \
208 | baseSize * s2 / 2 + \
209 | baseSize * m1 * 1 + \
210 | baseSize * m2 * 2 + \
211 | baseSize * m4 * 4 + \
212 | baseSize * m8 * 8 + \
213 | baseSize * m16 * 16)
214 |
215 | def calculateGfxSize(self, gfxFlag):
216 | s16 = int(gfxFlag >> 27) & 0x1
217 | s8 = int(gfxFlag >> 26) & 0x1
218 | s4 = int(gfxFlag >> 25) & 0x1
219 | s2 = int(gfxFlag >> 24) & 0x1
220 | m1 = int(gfxFlag >> 17) & 0x7f
221 | m2 = int(gfxFlag >> 11) & 0x3f
222 | m4 = int(gfxFlag >> 7) & 0xf
223 | m8 = int(gfxFlag >> 5) & 0x3
224 | m16 = int(gfxFlag >> 4) & 0x1
225 | shiftSize = int(gfxFlag >> 0) & 0xf
226 | baseSize = self.DEFAULT_SIZE << shiftSize
227 | print("Shift (gfx) : %i" % shiftSize)
228 |
229 | self.GFX_SIZE = int(baseSize * s16 / 16 + \
230 | baseSize * s8 / 8 + \
231 | baseSize * s4 / 4 + \
232 | baseSize * s2 / 2 + \
233 | baseSize * m1 * 1 + \
234 | baseSize * m2 * 2 + \
235 | baseSize * m4 * 4 + \
236 | baseSize * m8 * 8 + \
237 | baseSize * m16 * 16)
238 |
239 | def __init__(self, filename):
240 | self.fs = f = open(filename, "rb")
241 | magic = f.read(4)
242 | version = int.from_bytes(f.read(4), "little")
243 | sysFlag = int.from_bytes(f.read(4), "little")
244 | gfxFlag = int.from_bytes(f.read(4), "little")
245 |
246 | calcVersion = self.G(sysFlag, gfxFlag)
247 |
248 | if version != 13:
249 | print("Not a valid YTD file")
250 | exit()
251 |
252 | print("Magic: %s, 0x%x" % (str(magic), int.from_bytes(magic, "little")))
253 | print("Version in header: %i" % version)
254 | print("System flag = %i, Graphics flag = %i" % (sysFlag, gfxFlag))
255 |
256 | valid = "Not valid RSC7 format"
257 | if calcVersion == version:
258 | valid = "Valid RSC7 header"
259 |
260 | print("Calculated version '%i'. %s" % (calcVersion, valid))
261 | self.calcSytemSize(sysFlag)
262 | self.calculateGfxSize(gfxFlag)
263 |
264 | streamLength = os.fstat(f.fileno()).st_size
265 |
266 | sizeFromFlags = self.SYSTEM_SIZE + self.GFX_SIZE + 0x10
267 | print("Size calculated: %i, Size of system: %i, Size of gfx: %i. SIZE: %i"
268 | % (sizeFromFlags, self.SYSTEM_SIZE, self.GFX_SIZE, streamLength))
269 | self.decompress(sizeFromFlags)
270 |
271 | self.resources = Resources(self)
272 |
273 | def decompress(self, sizeFromFlags):
274 | uncompressed = self.inflate(self.fs.read(int(sizeFromFlags)))
275 | # print(uncompressed)
276 | self.systemData = uncompressed[0:int(self.SYSTEM_SIZE)]
277 | self.gfxData = uncompressed[int(self.SYSTEM_SIZE):int(self.SYSTEM_SIZE + self.GFX_SIZE)]
278 | def finished(self):
279 | self.fs.close()
280 | def dump(self):
281 | sf = open("system.dat", "wb")
282 | sf.write(self.systemData)
283 | sf.close()
284 |
285 | gd = open("gfx.dat", "wb")
286 | gd.write(self.gfxData)
287 | gd.close()
288 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 | Havoc's LiveMap
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: livemap_interface
2 |
3 | nav:
4 | - "README.md"
5 | - Configuration:
6 | - "config.md"
7 | - Reverse Proxy: "reverse_proxy.md"
8 | - Custom Maps: "custom_maps.md"
9 | - Contributing:
10 | - "contributing.md"
11 | - Developers: "developers.md"
12 | - FAQ: "FAQ.md"
13 | - Changelog: "CHANGELOG.md"
14 | - License: "LICENSE.md"
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "4.1.8",
3 | "scripts": {
4 | "build": "webpack build",
5 | "dev": "webpack serve",
6 | "doc": "jsdoc2md > docs/api.md",
7 | "release": "standard-version && git push --follow-tags origin develop && conventional-github-releaser",
8 | "test": "jest"
9 | },
10 | "license": "GPL-3.0",
11 | "jshintConfig": {
12 | "esversion": "2015",
13 | "browser": true,
14 | "latedef": true,
15 | "newcap": true,
16 | "undef": true
17 | },
18 | "dependencies": {
19 | "@popperjs/core": "^2.11.0",
20 | "bootstrap": "^5.1.3",
21 | "css-loader": "^6.5.1",
22 | "html-webpack-plugin": "^5.5.0",
23 | "leaflet": "^1.7.1",
24 | "leaflet.markercluster": "^1.5.3",
25 | "sass": "^1.45.1",
26 | "sass-loader": "^12.4.0",
27 | "simple-notify": "^0.5.4",
28 | "sprintf-js": "^1.1.2",
29 | "standard-version": "^9.3.2",
30 | "style-loader": "^3.3.1",
31 | "webpack": "^5.65.0"
32 | },
33 | "devDependencies": {
34 | "@types/bootstrap": "^5.1.6",
35 | "@types/leaflet": "^1.7.6",
36 | "all-contributors-cli": "^6.20.0",
37 | "conventional-github-releaser": "^3.1.5",
38 | "eslint": "^8.5.0",
39 | "eslint-config-prettier": "^8.3.0",
40 | "eslint-plugin-prettier": "^4.0.0",
41 | "jest": "^27.4.5",
42 | "jsdoc-to-markdown": "^7.1.0",
43 | "prettier": "^2.5.1",
44 | "webpack-cli": "^4.9.1",
45 | "webpack-dev-server": "^4.7.1"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/public/dev/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug": true,
3 | "tileDirectory": "images/tiles",
4 | "iconDirectory": "images/icons",
5 | "showIdentifiers": false,
6 | "groupPlayers": true, // Wether the map should group players together into "clusters" or not.
7 | "defaults": { // If a server doesn't have the key-value set, it will fallback to these values
8 | "ip": "127.0.0.1", // Make sure this is the PUBLIC IP of the FIVEM server
9 | "socketPort": "30121", // Set to the port that you set in the "socket_port" convar (if different to the one in the config)
10 | "reverseProxy": {
11 | "blips": "blips.json"
12 | }
13 | },
14 | "servers": {
15 | "Default server 1": {}
16 | },
17 | "maps": [
18 | {
19 | "name": "Normal",
20 | "url": "{tileDirectory}/normal/minimap_sea_{y}_{x}.png"
21 | },
22 | {
23 | "name": "Postal",
24 | "url": "{tileDirectory}/postal/minimap_sea_{y}_{x}.png",
25 | "minZoom": -5 // Since this is 3 times bigger, just remove 3 from the default minZoom
26 | }
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/public/dev/version.json:
--------------------------------------------------------------------------------
1 | {
2 | "interface": "4.1.8"
3 | }
4 |
--------------------------------------------------------------------------------
/src/js/_app.js:
--------------------------------------------------------------------------------
1 | import { Config } from "./config.js";
2 | import { Translator } from "./translator.js";
3 | import { SocketHandler } from "./socket.js";
4 | import { MapWrapper } from "./map.js";
5 | import { Alerter } from "./alerter.js";
6 | import { VersionCheck } from "./version-check.js";
7 | import { Initializer } from "./init.js";
8 |
9 | (async () => {
10 | window.Alerter = Alerter;
11 |
12 | // let translator = new Translator();
13 |
14 | let config = null;
15 |
16 | try {
17 | await Translator.getLanguageFromFile();
18 |
19 | config = await Config.getConfigFileFromRemote();
20 | } catch (ex) {
21 | console.error("Couldn't load LiveMap");
22 | console.error(ex);
23 | return;
24 | }
25 |
26 | if (!config.debug) {
27 | console.log("Disabling console.log... Goodbye console!");
28 | console.log = function () {}; // If we don't have debugging enabled. Just route all console.log's to an empty function
29 | }
30 |
31 | window.VersionCheck = new VersionCheck();
32 |
33 | for (const serverName in config.servers) {
34 | // Make sure all servers inherit defaults if they need
35 | let o = Object.assign({}, config.defaults, config.servers[serverName]);
36 | Config.staticConfig.servers[serverName] = o;
37 | }
38 |
39 | const socketHandler = (window.socketHandler = new SocketHandler());
40 | const mapWrapper = (window.mapWrapper = new MapWrapper(socketHandler));
41 |
42 | Initializer.page(config);
43 | mapWrapper.changeServer(Object.keys(Config.staticConfig.servers)[0]); // Show the stuff for the first server in the config.
44 | })();
45 |
--------------------------------------------------------------------------------
/src/js/alerter.js:
--------------------------------------------------------------------------------
1 | import Notify from "simple-notify";
2 |
3 | /**
4 | * Option object for creating alerts
5 | * @typedef {Object} AlerterOptions
6 | * @property {string} status=warning - The status of the alert. One of "warning", "success", "error"
7 | * @property {string} title=Warning! - The title of the alert
8 | * @property {string} text - The text of the alert
9 | * @property {string | null} effect=null - The effect for the alert. Can be "fade" or "slide"
10 | * @property {number} speed=500 - The speed of the effect
11 | * @property {string | null} customClass=null - A custom class to add to the alert.
12 | * @property {string | null} customIcon=null - HTML For the icon that shows with the alert. Mostly, this is a SVG icon
13 | * @property {boolean} showIcon=true - Should the alert show the icon
14 | * @property {boolean} showCloseButton=true - Should the alert show the close button
15 | * @property {boolean} autoclose=true - Should the alert close itself after an amount of time?
16 | * @property {number} autotimeout=20000 - How long after the alert has been shown should it automatically close itself (only if autoclose is true)
17 | * @property {number} gap=10 - The margin between notifications
18 | * @property {number} distance=20 - The distance to edges
19 | * @property {number} type=2 - The type of the alert
20 | * @property {string} position=bottom-right - The position of the alert. Combine x and y position. 'left', 'right', 'top', 'bottom', 'x-center', 'y-center' or use only 'center' to center both x and y.
21 | */
22 |
23 | /**
24 | * The main class for Alerts.
25 | */
26 | class Alerter {
27 | /**
28 | * @param {AlerterOptions | string} data The options to use to create Alerts or the text of an alert.
29 | * @constructor
30 | */
31 | constructor(data) {
32 | this.DEFAULT_OPTIONS = {
33 | status: "warning",
34 | title: "Warning!",
35 | text: "This is a warning",
36 | effect: "fade",
37 | speed: 500,
38 | customClass: null,
39 | customIcon: null,
40 | showIcon: true,
41 | showCloseButton: true,
42 | autoclose: true,
43 | autotimeout: 20000,
44 | gap: 10,
45 | distance: 20,
46 | type: 2,
47 | position: "bottom right",
48 | };
49 |
50 | if (data == undefined || data == null) {
51 | console.error("Data needs to be set");
52 | return;
53 | }
54 | var myOptionTemp;
55 | if (typeof data === "string") {
56 | myOptionTemp = { text: data };
57 | } else {
58 | myOptionTemp = data;
59 | }
60 |
61 | this.options = Object.assign(this.DEFAULT_OPTIONS, myOptionTemp);
62 |
63 | this.notify = new Notify(this.options);
64 | }
65 | }
66 |
67 | /* For testing the styles
68 | new Alerter({title: "success test", text: "Link! Some more text!", type: 1, status: "success"});
69 | new Alerter({title: "success test", text: "Link! Some more text!", type: 2, status: "success"});
70 | new Alerter({title: "success test", text: "Link! Some more text!", type: 3, status: "success"});
71 | new Alerter({title: "warning test", text: "Link! Some more text!", type: 1, status: "warning"});
72 | new Alerter({title: "warning test", text: "Link! Some more text!", type: 2, status: "warning"});
73 | new Alerter({title: "warning test", text: "Link! Some more text!", type: 3, status: "warning"});
74 | new Alerter({title: "error test", text: "Link! Some more text!", type: 1, status: "error"});
75 | new Alerter({title: "error test", text: "Link! Some more text!", type: 2, status: "error"});
76 | new Alerter({title: "error test", text: "Link! Some more text!", type: 3, status: "error"});
77 | */
78 |
79 | export { Alerter };
80 |
--------------------------------------------------------------------------------
/src/js/config.js:
--------------------------------------------------------------------------------
1 | import { JsonStrip } from "./utils.js";
2 | import { Alerter } from "./alerter.js";
3 |
4 | import { Translator } from "./translator";
5 |
6 | class Config {
7 | constructor() {}
8 |
9 | static getConfig() {
10 | if (JSON.stringify(this.staticConfig) === "{}") {
11 | this.getConfigFileFromRemote();
12 | console.warn("config didn't exist... try getting it again");
13 | }
14 |
15 | return this.staticConfig;
16 | }
17 |
18 | static async getConfigFileFromRemote() {
19 | const lang = Translator;
20 |
21 | try {
22 | let config = await fetch("config.json");
23 |
24 | let configData = await config.text();
25 | // console.log("Parsing: ", configData);
26 |
27 | let str = JsonStrip.stripJsonOfComments(configData);
28 | let configParsed = JSON.parse(str);
29 | Config.staticConfig = Object.assign(
30 | Config.defaultConfig,
31 | configParsed
32 | );
33 |
34 | return Promise.resolve(configParsed);
35 | } catch (ex) {
36 | console.error(ex);
37 | new Alerter({
38 | status: "error",
39 | title: lang.t("errors.getting-config.title"),
40 | text: lang.t("errors.getting-config.message", { error: ex }),
41 | });
42 | return Promise.reject(ex);
43 | }
44 | }
45 | }
46 |
47 | Config.defaultConfig = {
48 | debug: false,
49 | tileDirectory: "images/tiles",
50 | iconDirectory: "images/icons",
51 | showIdentifiers: false,
52 | groupPlayers: true,
53 | defaults: {
54 | ip: "127.0.0.1",
55 | socketPort: "30121",
56 | },
57 | servers: [],
58 | };
59 | Config.staticConfig = {};
60 |
61 | export { Config };
62 |
--------------------------------------------------------------------------------
/src/js/controls.js:
--------------------------------------------------------------------------------
1 | import { MapWrapper } from "./map.js";
2 | import { types } from "./markers.js";
3 | import { Initializer } from "./init.js";
4 |
5 | import { Translator } from "./translator";
6 |
7 | class Controls {
8 | /**
9 | * Creates an instance of Controls.
10 | * @param {MapWrapper} mapWrapper
11 | * @memberof Controls
12 | */
13 | constructor(mapWrapper) {
14 | this.mapWrapper = mapWrapper;
15 |
16 | this.blipControlToggleAll = true;
17 |
18 | this.initControls();
19 | }
20 |
21 | initControls() {
22 | document.getElementById("showBlips").onclick =
23 | this.showBlips_onClick.bind(this);
24 | document.getElementById("toggleAllBlips").onclick =
25 | this.toggleAllBlips_onClick.bind(this);
26 | document.getElementById("reconnect").onclick =
27 | this.reconnect_onClick.bind(this);
28 | document.getElementById("serverMenu").onclick =
29 | this.serverMenu_onClick.bind(this);
30 |
31 | this.mapWrapper.PlayerMarkers.on(
32 | "cluckerclick",
33 | this.playerMarker_clusterClick.bind(this)
34 | );
35 |
36 | document.getElementById("playerSelect").onchange =
37 | this.playerSelect_onChange.bind(this);
38 | document.getElementById("filterOn").onchange =
39 | this.filterOn_onChange.bind(this);
40 | document.getElementById("refreshBlips").onclick =
41 | this.refreshBlips_onClick.bind(this);
42 | }
43 |
44 | /**
45 | *
46 | * @param {Markers} markers
47 | * @memberof Controls
48 | */
49 | generateBlipControls(markers) {
50 | let container = document.getElementById("blipControlContainer");
51 |
52 | for (var blipName in types) {
53 | let a = document.createElement("a");
54 | a.setAttribute("data-blip-number", markers.nameToId[blipName]);
55 | a.id = `blip_${blipName}_link`;
56 | a.classList.add("blip-button-a", "d-inline-block", "blip-enabled");
57 | let span = document.createElement("span");
58 | span.classList.add("blip", `blip-${blipName}`);
59 |
60 | a.appendChild(span);
61 |
62 | container.appendChild(a);
63 |
64 | console.log("Added ahref for " + blipName);
65 | }
66 |
67 | var allButtons = document.getElementsByClassName("blip-button-a");
68 |
69 | for (let ele of allButtons) {
70 | ele.onclick = this.blipButtonClicked.bind(this, ele);
71 | }
72 |
73 | this.mapWrapper.clearAllMarkers();
74 | this.mapWrapper.toggleBlips();
75 | }
76 |
77 | blipButtonClicked(ele) {
78 | let blipId = ele.getAttribute("data-blip-number");
79 | // Toggle blip
80 | if (this.mapWrapper.disabledBlips.includes(blipId)) {
81 | // Already disabled, enable it
82 | this.mapWrapper.disabledBlips.splice(
83 | this.mapWrapper.disabledBlips.indexOf(blipId),
84 | 1
85 | );
86 | ele.classList.remove("blip-disabled");
87 | ele.classList.add("blip-enabled");
88 | } else {
89 | // Enabled, disable it
90 | this.mapWrapper.disabledBlips.push(blipId);
91 | ele.classList.remove("blip-enabled");
92 | ele.classList.add("blip-disabled");
93 | }
94 |
95 | this.mapWrapper.toggleBlips();
96 | }
97 |
98 | showBlips_onClick(event) {
99 | const lang = Translator;
100 |
101 | event.preventDefault();
102 |
103 | this.mapWrapper.showBlips = !this.mapWrapper.showBlips;
104 |
105 | //webSocket.send("getBlips");
106 | this.mapWrapper.toggleBlips();
107 |
108 | let ele = document.getElementById("blipsEnabled");
109 | ele.classList.remove("bg-success", "bg-danger");
110 |
111 | ele.classList.add(
112 | this.mapWrapper.showBlips ? "bg-success" : "bg-danger"
113 | );
114 |
115 | let onOff = this.mapWrapper.showBlips ? "on" : "off";
116 | ele.innerText = lang.t(`generic.${onOff}`);
117 | }
118 |
119 | toggleAllBlips_onClick(event) {
120 | this.blipControlToggleAll = !this.blipControlToggleAll;
121 |
122 | let allButtons = document.getElementsByClassName("blip-button-a");
123 |
124 | for (let ele of allButtons) {
125 | let blipId = ele.getAttribute("data-blip-number");
126 |
127 | if (this.blipControlToggleAll) {
128 | // Showing them
129 | this.mapWrapper.disabledBlips.splice(
130 | this.mapWrapper.disabledBlips.indexOf(blipId),
131 | 1
132 | );
133 | ele.classList.remove("blip-disabled");
134 | ele.classList.add("blip-enabled");
135 | } else {
136 | //Hiding them
137 | this.mapWrapper.disabledBlips.push(blipId);
138 | ele.classList.remove("blip-enabled");
139 | ele.classList.add("blip-disabled");
140 | }
141 | }
142 |
143 | // Now we can refresh the markers
144 | this.mapWrapper.toggleBlips();
145 | }
146 |
147 | playerMarker_clusterClick(a) {
148 | const Map = this.mapWrapper.Map;
149 |
150 | var html = L.DomUtil.create("ul");
151 | var markers = a.layer.getAllChildMarkers();
152 | for (var i = 0; i < markers.length; i++) {
153 | var marker = markers[i].options;
154 |
155 | var name = marker.title;
156 | var child = L.DomUtil.create("li", "clusteredPlayerMarker");
157 | child.setAttribute("data-identifier", marker.player.identifier);
158 | child.appendChild(document.createTextNode(name));
159 |
160 | html.appendChild(child);
161 | }
162 |
163 | // If they click on a username
164 | L.DomEvent.on(
165 | html,
166 | "click",
167 | this.playerInsideCluster_onClick.bind(this)
168 | );
169 |
170 | Map.openPopup(html, a.layer.getLatLng());
171 | }
172 |
173 | playerInsideCluster_onClick(e) {
174 | var t = e.target;
175 | var attribute = t.getAttribute("data-identifier");
176 | var m =
177 | this.mapWrapper.PopupStore[
178 | this.mapWrapper.localCache[attribute].marker
179 | ]; // Get the marker using the localcache.
180 |
181 | Map.closePopup(Map._popup); //Close the currently open popup
182 | Map.openPopup(m); // Open the user's popup
183 | }
184 |
185 | reconnect_onClick(e) {
186 | e.preventDefault();
187 |
188 | const lang = Translator;
189 | let connectionEle = document.getElementById("connection");
190 |
191 | connectionEle.classList.remove("bg-success", "bg-danger");
192 |
193 | connectionEle.classList.add("bg-warning");
194 | connectionEle.innerText = lang.t("generic.reconnecting");
195 |
196 | if (
197 | this.mapWrapper.socketHandler.webSocket != undefined ||
198 | this.mapWrapper.socketHandler.webSocket != null
199 | ) {
200 | this.mapWrapper.socketHandler.webSocket.close();
201 | }
202 |
203 | this.mapWrapper.socketHandler.connect(
204 | this.mapWrapper.connectedTo.getSocketUrl()
205 | );
206 | }
207 |
208 | serverMenu_onClick(e) {
209 | let target = e.target;
210 | this.mapWrapper.changeServer(target.innerText);
211 | }
212 |
213 | playerMarker_onClick(mw, e) {
214 | mw.Map.closePopup(mw.Map._popup);
215 | mw.PopupStore[e.target.options.id].setLatLng(e.latlng);
216 | mw.Map.openPopup(mw.PopupStore[e.target.options.id]);
217 | }
218 |
219 | playerSelect_onChange(e) {
220 | let value = e.target.value;
221 |
222 | if (value == "") {
223 | // No longer want to track
224 | this.mapWrapper.trackPlayer = null;
225 | e.target.classList.add("text-danger");
226 | return;
227 | }
228 |
229 | e.target.classList.remove("text-danger");
230 | this.mapWrapper.Map.setZoom(3);
231 | this.mapWrapper.trackPlayer = value;
232 | }
233 |
234 | filterOn_onChange(e) {
235 | const value = e.target.value;
236 | if (value == "") {
237 | this.mapWrapper.Filter = undefined;
238 | e.target.classList.add("text-danger");
239 | //document.getElementById("filterOn").classList.remove("text-danger");
240 | return;
241 | }
242 |
243 | e.target.classList.remove("text-danger");
244 | this.mapWrapper.Filter = {
245 | on: value,
246 | };
247 | }
248 |
249 | refreshBlips_onClick(e) {
250 | e.preventDefault();
251 |
252 | this.mapWrapper.clearAllMarkers();
253 | Initializer.blips(
254 | this.mapWrapper.connectedTo.getBlipUrl(),
255 | this.mapWrapper.markers,
256 | this.mapWrapper
257 | );
258 | }
259 | }
260 |
261 | export { Controls };
262 |
--------------------------------------------------------------------------------
/src/js/init.js:
--------------------------------------------------------------------------------
1 | import { Alerter } from "./alerter.js";
2 |
3 | import { Translator } from "./translator";
4 |
5 | // This file should initialize the map and set everything up for it to work.
6 |
7 | class Initializer {
8 | static page(config) {
9 | let serverMenu = document.getElementById("serverMenu");
10 | for (const serverName in config.servers) {
11 | let li = document.createElement("li");
12 | let link = document.createElement("a");
13 | link.classList.add("dropdown-item");
14 | link.href = "#";
15 | link.innerText = serverName;
16 |
17 | li.appendChild(link);
18 | serverMenu.appendChild(li);
19 | //$("#serverMenu").append("");
20 | }
21 | }
22 |
23 | /**
24 | * @typedef {import("./map").MapWrapper} MapWrapper
25 | *
26 | * @param {String} url
27 | * @param {Markers} markers
28 | * @param {MapWrapper} mapWrapper
29 | */
30 | static async blips(url, markers, mapWrapper) {
31 | console.log("Sending request to", url);
32 | const lang = Translator;
33 | let data = null;
34 |
35 | try {
36 | let response = await fetch(url);
37 | data = await response.json();
38 | } catch (error) {
39 | console.error("Getting blips: ", error);
40 |
41 | new Alerter({
42 | status: "error",
43 | title: lang.t("errors.getting-blips.title"),
44 | text: lang.t("errors.getting-blips.message", { error: error }),
45 | });
46 | return false;
47 | }
48 |
49 | for (var spriteId in data) {
50 | if (data.hasOwnProperty(spriteId)) {
51 | // data[spriteId] == array of blips for that type
52 | var blipArray = data[spriteId];
53 |
54 | for (var i in blipArray) {
55 | var blip = blipArray[i];
56 | var fallbackName =
57 | markers.MarkerTypes[spriteId] != undefined &&
58 | markers.MarkerTypes[spriteId].hasOwnProperty("name")
59 | ? markers.MarkerTypes[spriteId].name
60 | : "Unknown Name... Please make sure the sprite exists.";
61 |
62 | blip.name =
63 | blip.hasOwnProperty("name") || blip.name != null
64 | ? blip.name
65 | : fallbackName;
66 | blip.description =
67 | blip.hasOwnProperty("description") ||
68 | blip.description != null
69 | ? blip.description
70 | : "";
71 |
72 | blip.type = spriteId;
73 | //TODO: Implement
74 | //createBlip(blip);
75 |
76 | mapWrapper.createBlip(blip, markers.MarkerTypes);
77 | }
78 | }
79 | }
80 |
81 | document.getElementById("blipCount").innerText = mapWrapper.blipCount;
82 | mapWrapper.toggleBlips();
83 | }
84 | }
85 |
86 | export { Initializer };
87 |
--------------------------------------------------------------------------------
/src/js/markers.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prettier/prettier */
2 | // divide by 2 since we want to make icons 32x32 image
3 |
4 | const textureSheetWidth = 1024; // px
5 | const textureSheetHeight = 2000; // px
6 |
7 | // Icons will always be 32x32 pixels in size?
8 | const customImageWidth = 32;
9 | const customImageHeight = 32;
10 |
11 | // FUCK ME, GTA HAS A LOT OF FUCKING BLIPS
12 | const types = {
13 | Standard: { id: 1, x: 0, y: 0 },
14 | Jet: { id: 16 },
15 | Lift: { id: 36 },
16 | RaceFinish: { id: 38 },
17 | Safehouse: { id: 40 },
18 | PoliceHelicopter: { id: 43 },
19 | ChatBubble: { id: 47 },
20 | Garage2: { id: 50 },
21 | Drugs: {},
22 | Store: {},
23 | PoliceCar: { id: 56 },
24 | PoliceStation: { id: 60, x: 12, y: 0 },
25 | Hospital: {},
26 | Helicopter: { id: 64 },
27 | StrangersAndFreaks: {},
28 |
29 | ArmoredTruck: { x: 0, y: 1 },
30 | TowTruck: { id: 68 },
31 | Barber: { id: 71 },
32 | LosSantosCustoms: {},
33 | Clothes: {},
34 | TattooParlor: { id: 75 },
35 | Simeon: {},
36 | Lester: {},
37 | Michael: {},
38 | Trevor: {},
39 | Rampage: { id: 84, x: 11 },
40 | VinewoodTours: {},
41 | Lamar: {},
42 | Franklin: { id: 88 },
43 | Chinese: {},
44 |
45 | Airport: { x: 0, y: 2 },
46 | Bar: { id: 93 },
47 | BaseJump: {},
48 | CarWash: { id: 100, x: 4 },
49 | ComedyClub: { id: 102 },
50 | Dart: {},
51 | Fib: { id: 106, x: 9 },
52 | Bank: { id: 108, x: 11 }, // These are generally "banks". Original: DollarSign
53 | Golf: {},
54 | AmmuNation: {},
55 | Exile: { id: 112 },
56 |
57 | ShootingRange: { id: 119, x: 1, y: 3 },
58 | Solomon: {},
59 | StripClub: {},
60 | Tennis: {},
61 | Triathlon: { id: 126, x: 7 },
62 | OffRoadRaceFinish: {},
63 | Key: { id: 134, x: 10 },
64 | MovieTheater: {},
65 | Music: {},
66 | FireStation: { id: 137, x: 13 },
67 | Marijuana: { id: 140 },
68 | Hunting: {},
69 |
70 | ArmsTraffickingGround: { id: 147, y: 4, x: 0 },
71 | Nigel: { id: 149 },
72 | AssaultRifle: {},
73 | Bat: {},
74 | Grenade: {},
75 | Health: {},
76 | Knife: {},
77 | Molotov: {},
78 | Pistol: {},
79 | Rpg: {},
80 | Shotgun: {},
81 | Smg: {},
82 | Sniper: {},
83 | PointOfInterest: { id: 162 },
84 | GtaOPassive: {},
85 | GtaOUsingMenu: {},
86 |
87 | Minigun: { id: 173, x: 0, y: 5 },
88 | GrenadeLauncher: {},
89 | Armor: {},
90 | Castle: {},
91 | Camera: { id: 184, x: 7 },
92 | Handcuffs: { id: 188, x: 11 },
93 | Yoga: { id: 197 },
94 | Cab: {},
95 | Shrink: { id: 205 },
96 | Epsilon: {},
97 |
98 | PersonalVehicleCar: { id: 225, x: 5, y: 6 },
99 | PersonalVehicleBike: {},
100 | Custody: { id: 237, x: 10 },
101 | ArmsTraffickingAir: { id: 251 },
102 | Fairground: { id: 266, x: 15 },
103 |
104 | PropertyManagement: { x: 0, y: 7 },
105 | Altruist: { id: 269 },
106 | Chop: { id: 273, x: 3 },
107 | Hooker: { id: 279, x: 7 },
108 | Friend: {},
109 | GtaOMission: { id: 304, x: 14 },
110 | GtaOSurvival: {},
111 |
112 | CrateDrop: { x: 0, y: 8 },
113 | PlaneDrop: {},
114 | Sub: {},
115 | Race: {},
116 | Deathmatch: {},
117 | ArmWrestling: {},
118 | AmmuNationShootingRange: { id: 313 },
119 | RaceAir: {},
120 | RaceCar: {},
121 | RaceSea: {},
122 | GarbageTruck: { id: 318, x: 11 },
123 | SafehouseForSale: { id: 350, x: 14 },
124 | Package: {},
125 |
126 | MartinMadrazo: { x: 0, y: 9 },
127 | Boost: { id: 354 },
128 | Devin: {},
129 | Marina: {},
130 | Garage: {},
131 | GolfFlag: {},
132 | Hangar: {},
133 | Helipad: {},
134 | JerryCan: {},
135 | Masks: {},
136 | HeistSetup: {},
137 | PickupSpawn: { id: 365 },
138 | BoilerSuit: {},
139 | Completed: {},
140 | Rockets: {},
141 | GarageForSale: {},
142 |
143 | HelipadForSale: { x: 0, y: 10 },
144 | MarinaForSale: {},
145 | HangarForSale: {},
146 | Business: { id: 374 },
147 | BusinessForSale: {},
148 | RaceBike: {},
149 | Parachute: {},
150 | TeamDeathmatch: {},
151 | RaceFoot: {},
152 | VehicleDeathmatch: {},
153 | Barry: {},
154 | Dom: {},
155 | MaryAnn: {},
156 | Cletus: {},
157 | Josh: {},
158 | Minute: {},
159 |
160 | Omega: { x: 0, y: 11 },
161 | Tonya: {},
162 | Paparazzo: {},
163 | Abigail: { id: 400 },
164 | Blimp: {},
165 | Repair: {},
166 | Testosterone: {},
167 | Dinghy: {},
168 | Fanatic: {},
169 | CaptureBriefcase: { id: 408 },
170 | LastTeamStanding: {},
171 | Boat: {},
172 | CaptureHouse: {},
173 | JerryCan2: { id: 415, x: 14 },
174 | CaptureAmericanFlag: { id: 419 },
175 |
176 | CaptureFlag: { x: 0, y: 12 },
177 | Tank: {},
178 | GunCar: { id: 426, x: 3 },
179 | Speedboat: {},
180 | Heist: {},
181 | Stopwatch: { id: 430 },
182 | DollarSignCircled: {},
183 | Crosshair2: {},
184 | DollarSignSquared: { id: 434 },
185 | FlameIcon: { id: 436, x: 15 },
186 |
187 | DiamondCasino: { id: 679, x: 3, y: 25 },
188 | LSCarMeet: { id: 777, x: 4, y: 29 },
189 | LSCarMeetGarage: { id: 779, x: 2, y: 29 },
190 | };
191 |
192 | class Markers {
193 | /**
194 | * Creates an instance of Markers.
195 | * @param {Config} config
196 | * @param {Controls} controls
197 | * @memberof Markers
198 | */
199 | constructor(config, controls) {
200 | this.nameToId = {};
201 | this.config = config;
202 |
203 | this.MarkerTypes = {
204 | 0: {
205 | name: "Blank Icon",
206 | iconUrl:
207 | "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAFElEQVR4XgXAAQ0AAABAMP1L30IDCPwC/o5WcS4AAAAASUVORK5CYII=",
208 | iconSize: [0, 0],
209 | popupAnchor: [0, 0],
210 | iconAnchor: [0, 0],
211 | },
212 | 999: {
213 | name: "Debug Icon",
214 | iconUrl: config.iconDirectory + "/debug.png",
215 | iconSize: [23, 32],
216 | popupAnchor: [0, 0],
217 | iconAnchor: [11.5, 0], // Bottom middle
218 | },
219 | // Apparently players have an icon of "6" so, might as well make normal that
220 | 6: {
221 | name: "Player",
222 | iconUrl: config.iconDirectory + "/normal.png",
223 | iconSize: [22, 32],
224 | popupAnchor: [0, 0],
225 | iconAnchor: [11, 0],
226 | },
227 | };
228 |
229 | this.blipCss = `.blip {
230 | background: url("${config.iconDirectory}/blips_texturesheet.png");
231 | background-size: ${textureSheetWidth / 2}px ${textureSheetHeight / 2}px;
232 | display: inline-block;
233 | width: ${customImageWidth}px;
234 | height: ${customImageHeight}px;
235 | }`;
236 |
237 | this.generateBlipShit(controls);
238 | }
239 |
240 | /**
241 | *
242 | *
243 | * @param {Controls} controls
244 | * @memberof Markers
245 | */
246 | generateBlipShit(controls) {
247 | var currentX = 0,
248 | currentY = 0,
249 | currentId = 0;
250 | const linePadding = 0;
251 |
252 | for (var blipName in types) {
253 | var blip = types[blipName];
254 |
255 | if (typeof blip.id !== "undefined") {
256 | currentId = blip.id;
257 | } else {
258 | currentId++;
259 | }
260 |
261 | if (typeof blip.x !== "undefined") {
262 | currentX = blip.x;
263 | } else {
264 | currentX++;
265 | }
266 |
267 | if (typeof blip.y !== "undefined") {
268 | currentY = blip.y;
269 | }
270 |
271 | this.MarkerTypes[currentId] = {
272 | name: blipName.replace(/([A-Z0-9])/g, " $1").trim(),
273 | className: `blip blip-${blipName}`,
274 | iconUrl:
275 | "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAFElEQVR4XgXAAQ0AAABAMP1L30IDCPwC/o5WcS4AAAAASUVORK5CYII=",
276 | iconSize: [customImageWidth, customImageHeight],
277 | iconAnchor: [customImageWidth / 2, 0],
278 | popupAnchor: [0, 0],
279 |
280 | //scaledSize: [ 1024/2,1024/2 ],
281 | //origin: [ customImageWidth * currentX , customImageHeight * currentY ],
282 | };
283 |
284 | this.nameToId[blipName] = currentId;
285 |
286 | // CSS GENERATOR FOR BLIP ICONS IN HTML
287 | // Just add the class "blip blip-" to the element for blip icons
288 | // e.g. for a Standard blip
289 | var left = currentX * customImageWidth + linePadding; // 0 = padding between images
290 | var top = currentY * customImageHeight + linePadding; // 0 = padding
291 |
292 | // For styling spans and shit
293 | this.blipCss += `.blip-${blipName} { background-position: -${left}px -${top}px }\n`;
294 | }
295 |
296 | let style = document.createElement("style");
297 | style.innerHTML = this.blipCss;
298 | document.getElementsByTagName("head")[0].appendChild(style);
299 |
300 | const _ = this;
301 | setTimeout(() => controls.generateBlipControls(_), 50);
302 | }
303 | }
304 | ///setTimeout(generateBlipShit, 50);
305 |
306 | export { types, Markers };
307 |
--------------------------------------------------------------------------------
/src/js/objects.js:
--------------------------------------------------------------------------------
1 | class Coordinates {
2 | constructor(x, y, z) {
3 | this.x = x;
4 | this.y = y;
5 | this.z = z;
6 | }
7 | }
8 |
9 | class MarkerObject {
10 | constructor(reference, position, type, description, data) {
11 | this.reference = reference;
12 | this.position = position;
13 | this.type = type;
14 | this.description = description;
15 | this.data = data;
16 | }
17 | }
18 |
19 | export { Coordinates, MarkerObject };
20 |
--------------------------------------------------------------------------------
/src/js/socket.js:
--------------------------------------------------------------------------------
1 | import { Config } from "./config.js";
2 | import { Alerter } from "./alerter.js";
3 | import { MapWrapper } from "./map.js";
4 | import { Markers } from "./markers.js";
5 | import { Utils } from "./utils.js";
6 |
7 | import { Translator } from "./translator";
8 |
9 | //TODO: Document the player and blip objects
10 | // TODO: Document the array of player objects
11 |
12 | class SocketHandler {
13 | /**
14 | * Creates an instance of SocketHandler.
15 | * @memberof SocketHandler
16 | */
17 | constructor() {
18 | this.config = Config.getConfig();
19 | this.webSocket = null;
20 | this.localPlayerCache = {};
21 | }
22 |
23 | //FIXME: We should pass a reference to mapWrapper here and call it's functions
24 | connect(connectionString, mapWrapper) {
25 | if (this.webSocket != null) {
26 | // Clean up the current websocket connection
27 | this.webSocket.close();
28 | this.webSocket = null;
29 | }
30 |
31 | this.webSocket = new WebSocket(connectionString);
32 |
33 | this.webSocket.onopen = this.onOpen.bind(this);
34 |
35 | this.webSocket.onmessage = this.onMessage.bind(this, mapWrapper);
36 |
37 | this.webSocket.onerror = this.onError.bind(this);
38 |
39 | this.webSocket.onclose = this.onClose.bind(this);
40 | }
41 |
42 | onOpen(e) {
43 | console.log(
44 | "_isConnected: " + this.webSocket.readyState == WebSocket.OPEN
45 | );
46 | let conn = document.getElementById("connection");
47 |
48 | conn.classList.remove("bg-danger", "bg-warning");
49 | conn.classList.add("bg-success");
50 | conn.textContent = Translator.t("generic.connected");
51 |
52 | //document.getElementById("socket_error").textContent = "";
53 | }
54 |
55 | /**
56 | *
57 | *
58 | * @param {MapWrapper} mapWrapper
59 | * @param {*} e
60 | * @memberof SocketHandler
61 | */
62 | onMessage(mapWrapper, e) {
63 | let m = encodeURIComponent(e.data).match(/%[89ABab]/g);
64 | let byteSize = e.data.length + (m ? m.length : 0);
65 |
66 | console.log("recieved message (" + byteSize / 1024 + " kB)");
67 | console.log("data: " + e.data);
68 |
69 | let data = JSON.parse(e.data);
70 |
71 | if (
72 | data.type == "addBlip" ||
73 | data.type == "updateBlip" ||
74 | data.type == "removeBlip"
75 | ) {
76 | // BACKWARDS COMPATABILITY!!
77 | if (!data.payload.hasOwnProperty("pos")) {
78 | data.payload.pos = {
79 | x: data.payload.x,
80 | y: data.payload.y,
81 | z: data.payload.z,
82 | };
83 |
84 | delete data.payload.x;
85 | delete data.payload.y;
86 | delete data.payload.z;
87 | }
88 | }
89 |
90 | if (data.type == "addBlip") {
91 | mapWrapper.addBlip(data.payload);
92 | // this.addBlip(data.payload);
93 | } else if (data.type == "removeBlip") {
94 | mapWrapper.removeBlip(data.payload);
95 | // this.removeBlip(data.payload);
96 | } else if (data.type == "updateBlip") {
97 | mapWrapper.updateBlip(data.payload);
98 | // this.updateBlip(data.payload);
99 | } else if (data.type == "playerData") {
100 | //console.log("updating players(" + typeof(data.payload) + "): " + JSON.stringify(data.payload));
101 | let sortedPlayers = data.payload.sort(Utils.playerNameSorter);
102 | //this.doPlayerUpdate(sortedPlayers);
103 | mapWrapper.doPlayerUpdate(sortedPlayers);
104 | } else if (data.type == "playerLeft") {
105 | //console.log("player left:" + data.payload);
106 | //this.playerLeft(data.payload);
107 | mapWrapper.playerLeft(data.payload);
108 | }
109 | }
110 |
111 | onError(event) {
112 | // TODO: Alert the user?
113 |
114 | new Alerter({
115 | title: Translator.t("errors.socket-error"),
116 | status: "error",
117 | autoclose: true,
118 | text: Translator.t("errors.getting-config.message", {
119 | error: {
120 | message:
121 | "There was an error with your websocket connection.",
122 | },
123 | }),
124 | });
125 | }
126 |
127 | onClose(event) {
128 | let conn = document.getElementById("connection");
129 |
130 | conn.classList.remove("bg-success", "bg-warning");
131 | conn.classList.add("bg-danger");
132 | conn.textContent = Translator.t("generic.disconnected");
133 | }
134 | }
135 |
136 | //TODO: Reimplment
137 | // Every minute, just clear what we can "filter". In case we get one player with a unique property that is never seen again.
138 | // setInterval(()=> {
139 | // window.CanFilterOn = [];
140 | // $("#filterOn").innerHtml = " ";
141 | // }, 60000);
142 |
143 | export { SocketHandler };
144 |
--------------------------------------------------------------------------------
/src/js/translator.js:
--------------------------------------------------------------------------------
1 | import { Alerter } from "./alerter.js";
2 | import { vsprintf } from "sprintf-js";
3 |
4 | class Translator {
5 | constructor() {
6 | this._lang = this.getLanguage();
7 | this.translations = {};
8 | this.manifest = {};
9 |
10 | this._elements = document.querySelectorAll("[data-i18n]");
11 |
12 | // Get the current manifest
13 | const _ = this;
14 |
15 | fetch("translations/manifest.json")
16 | .then(async (resp) => {
17 | let j = await resp.json();
18 | _.manifest = j;
19 |
20 | _.putLanguagesIntoNavbar(j);
21 | })
22 | .catch((err) => {
23 | new Alerter({
24 | status: "error",
25 | title: "Error getting translation manifest",
26 | text: err.message,
27 | });
28 | });
29 |
30 | if (!this.manifest[this._lang]) {
31 | // The user's language isn't in the manifest... Just fall back to English
32 | this._lang = "en";
33 | }
34 |
35 | //this.setLanguage(this._lang);
36 | localStorage.setItem("lang", this._lang);
37 | this.toggleLangTag();
38 | }
39 |
40 | putLanguagesIntoNavbar(json) {
41 | let languageList = document.getElementById("availableLanguages");
42 |
43 | languageList.innerHTML = ""; // Clear any children already here
44 |
45 | for (let key in json) {
46 | const li = document.createElement("li");
47 | const link = document.createElement("a");
48 |
49 | link.classList.add("dropdown-item");
50 | link.innerText = `${key} - ${json[key]}`;
51 | link.href = "#";
52 | link.setAttribute("data-translation-language", key);
53 |
54 | li.onclick = function (e) {
55 | this.setLanguage(
56 | e.target.getAttribute("data-translation-language")
57 | );
58 | }.bind(this);
59 |
60 | li.appendChild(link);
61 | languageList.appendChild(li);
62 | }
63 | }
64 |
65 | setLanguage(lang) {
66 | console.log("Switching language to " + lang);
67 |
68 | if (lang == this._lang) {
69 | return;
70 | }
71 |
72 | this._lang = lang;
73 | localStorage.setItem("lang", this._lang);
74 | this.toggleLangTag();
75 |
76 | this.getLanguageFromFile();
77 | }
78 |
79 | toggleLangTag() {
80 | if (document.documentElement.lang !== this._lang) {
81 | document.documentElement.lang = this._lang;
82 | }
83 |
84 | document.getElementById("currentLang").innerText = this._lang;
85 | }
86 |
87 | async getLanguageFromFile() {
88 | try {
89 | let translations = await fetch(`translations/${this._lang}.json`);
90 | this.translations = await translations.json();
91 | this.translatePage();
92 |
93 | return Promise.resolve();
94 | } catch (ex) {
95 | new Alerter({
96 | status: "error",
97 | title: "Error getting translation file",
98 | text: ex.message,
99 | });
100 |
101 | return Promise.reject(ex);
102 | }
103 | }
104 |
105 | getValueFromJSON(key) {
106 | return key
107 | .split(".")
108 | .reduce((obj, i) => (obj ? obj[i] : null), this.translations);
109 | }
110 |
111 | t(key, ...params) {
112 | console.log(`Translating ${this.getValueFromJSON(key)}:`, params);
113 | return vsprintf(this.getValueFromJSON(key), params);
114 | }
115 |
116 | /**
117 | *
118 | * @param {Element} element
119 | */
120 | replace(element) {
121 | if (!element.getAttribute("data-i18n")) {
122 | console.error(`Element needs a data-i18n attribute to be translated.
123 | ${element}`);
124 | return;
125 | }
126 |
127 | const keys = element.getAttribute("data-i18n").split(/\s/g);
128 | let attributes = element.getAttribute("data-i18n-attr");
129 |
130 | if (attributes && keys.length != attributes.split(/\s/g).length) {
131 | console.warn(`The attributes "data-i18n" and "data-i18n-attr" must contain the same number of keys.
132 | Values in \`data-i18n\`: (${keys.length}) \`${keys.join(" ")}\`
133 | Values in \`data-i18n-attr\`: (${attributes.length}) \`${attributes.join(" ")}\`
134 | The HTML element is:
135 | ${element.outerHTML}`);
136 | }
137 |
138 | if (attributes) {
139 | attributes = attributes.split(/\s/g);
140 | }
141 |
142 | keys.forEach((key, index) => {
143 | const text = this.getValueFromJSON(key);
144 | const attr = attributes ? attributes[index] : "innerText";
145 |
146 | if (text) {
147 | if (attr == "innerHTML" || attr == "innerText") {
148 | element[attr] = text;
149 | } else {
150 | element.setAttribute(attr, text);
151 | }
152 | } else {
153 | console.warn(
154 | `No translation found for key "${key}" in language "${this._lang}". Is there a key/value in your translation file?`
155 | );
156 | }
157 | });
158 | }
159 |
160 | translatePage() {
161 | this._elements.forEach((element) => {
162 | this.replace(element);
163 | });
164 | }
165 |
166 | getLanguage() {
167 | let inStorage = localStorage.getItem("lang");
168 |
169 | if (inStorage) {
170 | return inStorage;
171 | } else {
172 | var lang = navigator.languages
173 | ? navigator.languages[0]
174 | : navigator.language;
175 |
176 | return lang.substr(0, 2);
177 | }
178 | }
179 | }
180 |
181 | const translator = new Translator();
182 |
183 | export default translator;
184 | export { translator as Translator };
185 |
--------------------------------------------------------------------------------
/src/js/utils.js:
--------------------------------------------------------------------------------
1 | import { Translator } from "./translator";
2 |
3 | import * as L from "leaflet";
4 |
5 | // This file needs to be loaded first
6 | class Utils {
7 | constructor() {}
8 |
9 | static isNumeric(n) {
10 | return !isNaN(parseFloat(n)) && isFinite(n);
11 | }
12 | static normalize(value, min, max) {
13 | return Math.abs((value - min) / (max - min));
14 | }
15 |
16 | /**
17 | *
18 | * @param {L.Map} MapL
19 | * @param {L.Layer} CurrentLayer
20 | * @param {*} x
21 | * @param {*} y
22 | * @returns
23 | */
24 | static convertToMap(MapL, CurrentLayer, x, y) {
25 | const h = CurrentLayer.options.tileSize * 3,
26 | w = CurrentLayer.options.tileSize * 2;
27 |
28 | const latLng1 = MapL.unproject([0, 0], 0);
29 | const latLng2 = MapL.unproject(
30 | [w / 2, h - CurrentLayer.options.tileSize],
31 | 0
32 | );
33 |
34 | let rLng =
35 | latLng1.lng +
36 | ((x - Utils.game_1_x) * (latLng1.lng - latLng2.lng)) /
37 | (Utils.game_1_x - Utils.game_2_x);
38 | let rLat =
39 | latLng1.lat +
40 | ((y - Utils.game_1_y) * (latLng1.lat - latLng2.lat)) /
41 | (Utils.game_1_y - Utils.game_2_y);
42 | return {
43 | lat: rLat,
44 | lng: rLng,
45 | };
46 | }
47 |
48 | /**
49 | *
50 | * @param {L.Map} MapL
51 | * @param {} layer
52 | * @returns
53 | */
54 | static getMapBounds(MapL, layer) {
55 | const h = layer.options.tileSize * 3,
56 | w = layer.options.tileSize * 2,
57 | southWest = MapL.unproject([0, h], 0),
58 | northEast = MapL.unproject([w, 0], 0);
59 |
60 | return new L.LatLngBounds(southWest, northEast);
61 | }
62 |
63 | static convertToMapLeaflet(mapL, currentLayer, x, y) {
64 | let t = this.convertToMap(mapL, currentLayer, x, y);
65 | return t;
66 | }
67 |
68 | static stringCoordToFloat(coord) {
69 | return {
70 | x: parseFloat(coord.x),
71 | y: parseFloat(coord.y),
72 | z: parseFloat(coord.z),
73 | };
74 | }
75 |
76 | /**
77 | *
78 | *
79 | * @static
80 | * @param {Object} coords
81 | * @param {number} coords.x
82 | * @param {number} coords.y
83 | * @param {number} coords.z
84 | * @memberof Utils
85 | */
86 | static getPositionHtml(coords) {
87 | let lang = Translator;
88 | return `${lang.t(
89 | "map.position"
90 | )}: X ${coords.x.toFixed(2)} Y ${coords.y.toFixed(
91 | 2
92 | )} Z ${coords.z.toFixed(2)}
`;
93 | }
94 |
95 | /**
96 | *
97 | *
98 | * @static
99 | * @param {number} x
100 | * @param {number} y
101 | * @param {number} z
102 | * @memberof Utils
103 | */
104 | static getPositionHtmlWith(x, y, z) {
105 | return Utils.getPositionHtml({ x, y, z });
106 | }
107 |
108 | /**
109 | *
110 | *
111 | * @static
112 | * @param {string} key
113 | * @param {any} value
114 | * @return {string}
115 | * @memberof Utils
116 | */
117 | static getHtmlForInformation(key, value) {
118 | return `${key}: ${value}
`;
119 | }
120 |
121 | static getInfoHtmlForMarkers(name, extraHtml) {
122 | return ``;
123 | }
124 |
125 | static getPlayerInfoHtml(plr) {
126 | //let html = 'Position: X {' + plr.pos.x.toFixed(0) + "} Y {" + plr.pos.y.toFixed(0) + "} Z {" + plr.pos.z.toFixed(0) + "}
";
127 | let html = Utils.getPositionHtml(plr.pos);
128 |
129 | for (let key in plr) {
130 | //console.log("found key: "+ key);
131 | if (key == "name" || key == "pos" || key == "icon") {
132 | // I should probably turn this into a array or something
133 | continue; // We're already displaying this info
134 | }
135 |
136 | if (key !== "identifier") {
137 | html += Utils.getHtmlForInformation(key, plr[key]);
138 | ///html += '' + key + ': ' + plr[key] + '
';
139 | } else if (
140 | Config.getConfig().showIdentifiers &&
141 | key == "identifier"
142 | ) {
143 | html += Utils.getHtmlForInformation(key, plr[key]);
144 | } else {
145 | continue;
146 | }
147 | }
148 | return html;
149 | }
150 |
151 | static playerNameSorter(plr1, plr2) {
152 | let str1 = plr1.name;
153 | let str2 = plr2.name;
154 |
155 | return str1 < str2 ? -1 : str1 > str2 ? 1 : 0;
156 | }
157 |
158 | static getFilterProps(plr) {
159 | let props = [];
160 | for (let key in plr) {
161 | //console.log("found key: "+ key);
162 | if (key == "name" || key == "pos" || key == "icon") {
163 | // I should probably turn this into a array or something
164 | continue; // We're already displaying this info
165 | }
166 |
167 | if (key !== "identifier") {
168 | props.push(key);
169 | } else if (
170 | Config.getConfig().showIdentifiers &&
171 | key == "identifier"
172 | ) {
173 | props.push(key);
174 | } else {
175 | continue;
176 | }
177 | }
178 |
179 | return props;
180 | }
181 |
182 | /**
183 | * Return if a particular option exists in a object
184 | * @param {String} needle A string representing the option you are looking for
185 | * @param {Object} haystack A Select object
186 | */
187 | static optionExists(needle, haystack) {
188 | var optionExists = false,
189 | optionsLength = haystack.length;
190 |
191 | while (optionsLength--) {
192 | if (haystack.options[optionsLength].value === needle) {
193 | optionExists = true;
194 | break;
195 | }
196 | }
197 | return optionExists;
198 | }
199 | }
200 |
201 | // :thinking: This seems to improve the accuracy. I think what the problem is, if that the images I'm using doesn't correlate 1:1 to the map I'm using as a reference
202 | // Reference image (for those who care): https://drive.google.com/file/d/0B-zvE86DVcv2MXhVSHZnc01QWm8/view
203 | // I'm pretty sure that the + and -'s account for the differences. So, maybe fine-tuning them will increase accuracy of the map.
204 |
205 | // Top left corner of the GTA Map
206 | Utils.game_1_x = -4000.0 - 230;
207 | Utils.game_1_y = 8000.0 + 420;
208 |
209 | // Around this location: https://tgrhavoc.me/fuck_you/retard/MGoWO6nCymLmFC3M98bXbXL7C.png.
210 |
211 | // It's the middle of the map and one tile up (in leaflet). You can find it's location on leaflet by running the line below in console
212 | // let m = new L.Marker(Map.unproject([1024,1024*2],0)); m.addTo(Map);
213 | Utils.game_2_x = 400.0 - 30;
214 | Utils.game_2_y = -300.0 - 340.0;
215 |
216 | // Some information. I've spent too long looking at this to clearly see a patten
217 | // 0 0 for leaflet = -4000 8000 for GTA
218 | // mapWidth mapHeight for leaflet = 7000 -4000 for GTA
219 | // 2048 3072 = 7000 -4000
220 | // 4096 6144 = 7000 -4000
221 |
222 | // Leaflet assumes tileSize = 1024
223 | //
224 | // tile 1 in game = tile 1 in leaflet: gta delta leaflet delta:
225 | // p1: -4000, 8000 (top left) 0,0 y: 4200 y: 1024 (tilesize)
226 | // p2: -4000, 3800 (bottom left) 0,1024 x: 4400 x: 1024 (tilesize)
227 | // p3: 400, 8000 (top right) 1024,0
228 | // p4: 400, 3800 (bottom rigt) 1024,1024
229 |
230 | class JsonStrip {
231 | constructor() {
232 | this.singleComment = 1;
233 | this.multiComment = 2;
234 | }
235 |
236 | static stripWithoutWhitespace() {
237 | return "";
238 | }
239 | static stripWithWhitespace(str, start, end) {
240 | return str.slice(start, end).replace(/\S/g, " ");
241 | }
242 |
243 | static stripJsonOfComments(str, opts) {
244 | opts = opts || {};
245 |
246 | const strip =
247 | opts.whitespace === false
248 | ? JsonStrip.stripWithoutWhitespace
249 | : JsonStrip.stripWithWhitespace;
250 |
251 | let insideString = false;
252 | let insideComment = false;
253 | let offset = 0;
254 | let ret = "";
255 |
256 | for (let i = 0; i < str.length; i++) {
257 | const currentChar = str[i];
258 | const nextChar = str[i + 1];
259 |
260 | if (!insideComment && currentChar === "\"") {
261 | const escaped = str[i - 1] === "\\" && str[i - 2] !== "\\";
262 | if (!escaped) {
263 | insideString = !insideString;
264 | }
265 | }
266 |
267 | if (insideString) {
268 | continue;
269 | }
270 |
271 | if (!insideComment && currentChar + nextChar === "//") {
272 | ret += str.slice(offset, i);
273 | offset = i;
274 | insideComment = this.singleComment;
275 | i++;
276 | } else if (
277 | insideComment === this.singleComment &&
278 | currentChar + nextChar === "\r\n"
279 | ) {
280 | i++;
281 | insideComment = false;
282 | ret += strip(str, offset, i);
283 | offset = i;
284 | continue;
285 | } else if (
286 | insideComment === this.singleComment &&
287 | currentChar === "\n"
288 | ) {
289 | insideComment = false;
290 | ret += strip(str, offset, i);
291 | offset = i;
292 | } else if (!insideComment && currentChar + nextChar === "/*") {
293 | ret += str.slice(offset, i);
294 | offset = i;
295 | insideComment = this.multiComment;
296 | i++;
297 | continue;
298 | } else if (
299 | insideComment === this.multiComment &&
300 | currentChar + nextChar === "*/"
301 | ) {
302 | i++;
303 | insideComment = false;
304 | ret += strip(str, offset, i + 1);
305 | offset = i + 1;
306 | continue;
307 | }
308 | }
309 |
310 | return (
311 | ret +
312 | (insideComment ? strip(str.substr(offset)) : str.substr(offset))
313 | );
314 | }
315 | }
316 |
317 | export { Utils, JsonStrip };
318 |
--------------------------------------------------------------------------------
/src/js/version-check.js:
--------------------------------------------------------------------------------
1 | import { Alerter } from "./alerter.js";
2 | import { Translator } from "./translator";
3 |
4 | class VersionCheck {
5 | constructor() {
6 | this.versionFile = "version.json";
7 | this.currentVersion = "0.0.0";
8 | this.remoteVersion = "4.0.0";
9 | this.remoteVersionUrl =
10 | "https://raw.githubusercontent.com/TGRHavoc/live_map-interface/master/version.json";
11 |
12 | this.semver =
13 | // eslint-disable-next-line no-useless-escape
14 | /^v?(?:\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+)(\.(?:[x*]|\d+))?(?:-[\da-z\-]+(?:\.[\da-z\-]+)*)?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
15 |
16 | this.doUpdate();
17 | }
18 |
19 | updateInterface() {
20 | document.getElementById("livemapVersion").textContent =
21 | this.currentVersion;
22 | }
23 |
24 | indexOrEnd(str, q) {
25 | return str.indexOf(q) === -1 ? str.length : str.indexOf(q);
26 | }
27 |
28 | split(v) {
29 | var c = v.replace(/^v/, "").replace(/\+.*$/, "");
30 | var patchIndex = this.indexOrEnd(c, "-");
31 | var arr = c.substring(0, patchIndex).split(".");
32 | arr.push(c.substring(patchIndex + 1));
33 | return arr;
34 | }
35 |
36 | tryParse(v) {
37 | return isNaN(Number(v)) ? v : Number(v);
38 | }
39 |
40 | validate(version) {
41 | if (typeof version !== "string") {
42 | throw new TypeError("Invalid argument expected string");
43 | }
44 | if (!this.semver.test(version)) {
45 | throw new Error(
46 | "Invalid argument not valid semver ('" + version + "' received)"
47 | );
48 | }
49 | }
50 |
51 | compareVersions(v1, v2) {
52 | const _ = this;
53 | [v1, v2].forEach((e) => _.validate(e));
54 |
55 | var s1 = this.split(v1);
56 | var s2 = this.split(v2);
57 |
58 | for (var i = 0; i < Math.max(s1.length - 1, s2.length - 1); i++) {
59 | var n1 = parseInt(s1[i] || 0, 10);
60 | var n2 = parseInt(s2[i] || 0, 10);
61 |
62 | if (n1 > n2) return 1;
63 | if (n2 > n1) return -1;
64 | }
65 |
66 | var sp1 = s1[s1.length - 1];
67 | var sp2 = s2[s2.length - 1];
68 |
69 | if (sp1 && sp2) {
70 | var p1 = sp1.split(".").map(this.tryParse);
71 | var p2 = sp2.split(".").map(this.tryParse);
72 |
73 | for (i = 0; i < Math.max(p1.length, p2.length); i++) {
74 | if (
75 | p1[i] === undefined ||
76 | (typeof p2[i] === "string" && typeof p1[i] === "number")
77 | )
78 | return -1;
79 | if (
80 | p2[i] === undefined ||
81 | (typeof p1[i] === "string" && typeof p2[i] === "number")
82 | )
83 | return 1;
84 |
85 | if (p1[i] > p2[i]) return 1;
86 | if (p2[i] > p1[i]) return -1;
87 | }
88 | } else if (sp1 || sp2) {
89 | return sp1 ? -1 : 1;
90 | }
91 |
92 | return 0;
93 | }
94 |
95 | async getCurrentVersion() {
96 | const lang = Translator;
97 | try {
98 | let response = await fetch(this.versionFile);
99 |
100 | let data = await response.json();
101 | this.currentVersion = data.interface;
102 |
103 | return Promise.resolve(data.interface);
104 | } catch (error) {
105 | new Alerter({
106 | status: "error",
107 | text: lang.t("errors.version-check.current.message", {
108 | error: error,
109 | }),
110 | title: lang.t("errors.version-check.current.title"),
111 | });
112 |
113 | return Promise.reject(error);
114 | }
115 | }
116 |
117 | async getRemoteVersion() {
118 | const lang = Translator;
119 | try {
120 | let response = await fetch(this.remoteVersionUrl);
121 | let data = await response.json();
122 |
123 | this.remoteVersion = data.interface;
124 | return Promise.resolve(data.interface);
125 | } catch (err) {
126 | new Alerter({
127 | status: "error",
128 | text: lang.t("errors.version-check.remote.message", {
129 | error: err,
130 | }),
131 | title: lang.t("errors.version-check.remote.title"),
132 | });
133 |
134 | return Promise.reject(err);
135 | }
136 | }
137 |
138 | async doUpdate() {
139 | const lang = Translator;
140 |
141 | try {
142 | await this.getCurrentVersion();
143 | this.updateInterface();
144 |
145 | await this.getRemoteVersion();
146 | } catch (err) {
147 | console.error(err);
148 | }
149 |
150 | if (
151 | this.compareVersions(this.currentVersion, this.remoteVersion) <= 0
152 | ) {
153 | new Alerter({
154 | title: lang.t("updates.available.title"),
155 | text: lang.t("updates.available.message", this),
156 | });
157 | } else {
158 | //console.log("Up to date or, a higher version");
159 | new Alerter({
160 | status: "success",
161 | title: lang.t("updates.latest.title"),
162 | text: lang.t("updates.latest.message", this),
163 | autoclose: true,
164 | autotimeout: 10000,
165 | });
166 | }
167 | }
168 | }
169 |
170 | export { VersionCheck };
171 |
172 | // window.VersionCheck = new VersionCheck();
173 |
174 | /*
175 | $jsArrayString = sprintf("An update is available (%s -> %s). Please download it HERE. ", self::$version, self::$latestVer, self::$downloadUrl);
176 |
177 | return "";
178 | */
179 |
--------------------------------------------------------------------------------
/src/sass/_alerts.scss:
--------------------------------------------------------------------------------
1 | $notify-error : #eb5757;
2 | $notify-success : #11b957;
3 | $notify-warning : #f38d49;
4 | $notify-gray : #333333;
5 | $notify-gray-2 : #4d4d4d;
6 | $notify-gray-3 : #828282;
7 | $notify-white : #fff;
8 | $notify-white-2 : rgba(255, 255, 255, 0.8);
9 | $notify-padding : 0.75rem;
10 | $notify-icon-size : 32px;
11 | $notify-close-icon-size: 16px;
12 |
13 | .notifications-container {
14 | max-width : 320px;
15 | width : 100%;
16 | position : fixed;
17 | max-height : 100vh;
18 | z-index : 9999;
19 | pointer-events: none;
20 |
21 | &.notify-is-x-center {
22 | left : 50%;
23 | transform: translateX(-50%);
24 | }
25 |
26 | &.notify-is-y-center {
27 | top : 50%;
28 | transform: translateY(-50%);
29 | }
30 |
31 | &.notify-is-center {
32 | left : 50%;
33 | top : 50%;
34 | transform: translate(-50%, -50%);
35 | }
36 |
37 | &.notify-is-left {
38 | left: 0;
39 | }
40 |
41 | &.notify-is-right {
42 | right: 0;
43 | }
44 |
45 | &.notify-is-top {
46 | top: 0;
47 | }
48 |
49 | &.notify-is-bottom {
50 | bottom: 0;
51 | }
52 |
53 | &.notify-is-x-center.notify-is-top {
54 | top: var(--distance);
55 | }
56 |
57 | &.notify-is-x-center.notify-is-bottom {
58 | bottom: var(--distance);
59 | }
60 |
61 | &>* {
62 | pointer-events: auto;
63 | }
64 | }
65 |
66 | .notify {
67 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
68 | "Helvetica Neue", sans-serif;
69 | padding : $notify-padding;
70 | border-radius : 6px;
71 | display : flex;
72 | align-items : center;
73 | width : 100%;
74 | position : relative;
75 | user-select : none;
76 | position : relative;
77 | transition-timing-function: ease;
78 | box-sizing : border-box;
79 |
80 | @at-root .notify-is-left & {
81 | left: var(--distance);
82 | }
83 |
84 | @at-root .notify-is-right & {
85 | right: var(--distance);
86 | }
87 |
88 | @at-root .notify-is-top &,
89 | .notify-is-center &,
90 | .notify-is-y-center &,
91 | .notify-is-x-center.notify-is-top & {
92 | margin-top: var(--gap);
93 | }
94 |
95 | @at-root .notify-is-bottom &,
96 | .notify-is-x-center:not(.notify-is-top) & {
97 | margin-bottom: var(--gap);
98 | }
99 |
100 | $this: &;
101 |
102 | &__icon {
103 | height : $notify-icon-size;
104 | width : $notify-icon-size;
105 | flex-shrink : 0;
106 | display : flex;
107 | align-items : center;
108 | justify-content: center;
109 | margin-right : 12px;
110 | }
111 |
112 | &__close {
113 | position : absolute;
114 | right : 12px;
115 | top : 12px;
116 | cursor : pointer;
117 | height : $notify-close-icon-size;
118 | width : $notify-close-icon-size;
119 | display : flex;
120 | align-items : center;
121 | justify-content: center;
122 |
123 | * {
124 | pointer-events: none;
125 | }
126 | }
127 |
128 | &__title {
129 | font-weight : 600;
130 | font-size : 1rem;
131 | padding-right: calc($notify-padding + $notify-close-icon-size);
132 | }
133 |
134 | &__text {
135 | font-size : 0.875rem;
136 | margin-top: 0.25rem;
137 | }
138 |
139 | &--type-1 {
140 | background-color: #333439;
141 | border : 1px solid currentColor;
142 |
143 | #{$this}__close {
144 | color: $notify-white-2;
145 | }
146 |
147 | #{$this}__title {
148 | color: $notify-white;
149 | }
150 |
151 | #{$this}__text {
152 | color: $notify-white-2;
153 | }
154 | }
155 |
156 | &--type-2 {
157 | color: $notify-gray;
158 |
159 | a,
160 | a:hover {
161 | color: $notify-white;
162 | }
163 | }
164 |
165 | &--type-3 {
166 | color: $notify-white;
167 |
168 | #{$this}__text {
169 | color: $notify-white-2;
170 | }
171 |
172 | a,
173 | a:hover {
174 | color: $notify-gray-2;
175 | }
176 | }
177 |
178 | &--error {
179 | {$this}--type-1 {
180 | box-shadow: 0 2px 26px rgba(#d70000, 0.1);
181 | color : $notify-error;
182 | }
183 |
184 | {$this}--type-2,
185 | {$this}--type-3 {
186 | background-color: $notify-error;
187 | }
188 | }
189 |
190 | &--warning {
191 | {$this}--type-1 {
192 | box-shadow: 0 2px 26px rgba(#f2c94c, 0.1);
193 | color : $notify-warning;
194 | }
195 |
196 | {$this}--type-2,
197 | {$this}--type-3 {
198 | background-color: $notify-warning;
199 | }
200 | }
201 |
202 | &--success {
203 | {$this}--type-1 {
204 | box-shadow: 0 2px 26px rgba(#52d700, 0.1);
205 | color : $notify-success;
206 | }
207 |
208 | {$this}--type-2,
209 | {$this}--type-3 {
210 | background-color: $notify-success;
211 | }
212 | }
213 |
214 | // effects
215 | &--fade {
216 | will-change: opacity;
217 | opacity : 0;
218 | }
219 |
220 | &--fadeIn {
221 | opacity: 1;
222 | }
223 |
224 | &--slide {
225 | will-change: opacity, transform;
226 | opacity : 0;
227 |
228 | @at-root .notify-is-center &,
229 | .notify-is-y-center &,
230 | .notify-is-x-center:not(.notify-is-bottom) & {
231 | transform: translateY(-20px);
232 | }
233 |
234 | @at-root .notify-is-x-center.notify-is-bottom & {
235 | transform: translateY(20px);
236 | }
237 |
238 | @at-root .notify-is-right & {
239 | transform: translateX(calc(var(--distance) + 110%));
240 | }
241 |
242 | @at-root .notify-is-left & {
243 | transform: translateX(calc((var(--distance) * -1) - 110%));
244 | }
245 | }
246 |
247 | &--slideIn {
248 |
249 | @at-root .notify-is-x-center:not(.notify-is-bottom) &,
250 | .notify-is-center &,
251 | .notify-is-y-center &,
252 | .notify-is-x-center.notify-is-bottom & {
253 | opacity : 1;
254 | transform: translateY(0);
255 | }
256 |
257 | @at-root .notify-is-right &,
258 | .notify-is-left & {
259 | opacity : 1;
260 | transform: translateX(0);
261 | }
262 | }
263 |
264 | .notify-content {
265 | padding-top: 0.4em;
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/src/sass/_leaflet.scss:
--------------------------------------------------------------------------------
1 | // Leaflet's css for pop-ups. Needs to be overridden to be consistent with the theme
2 | .leaflet-popup-content-wrapper,
3 | .leaflet-popup-tip {
4 | background-color: $body-bg !important;
5 | color: $body-color !important;
6 | }
7 |
8 | .leaflet-control-layers {
9 | background-color: $body-bg !important;
10 | color: $body-color !important;
11 | }
12 |
13 | .leaflet-bar {
14 | a.leaflet-disabled {
15 | color: $nav-link-disabled-color !important;
16 | }
17 |
18 | a {
19 | background-color: $body-bg !important;
20 | color: $link-color !important;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/sass/_markerCluster.scss:
--------------------------------------------------------------------------------
1 | .leaflet-cluster-anim .leaflet-marker-icon,
2 | .leaflet-cluster-anim .leaflet-marker-shadow {
3 | -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
4 | -moz-transition : -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
5 | -o-transition : -o-transform 0.3s ease-out, opacity 0.3s ease-in;
6 | transition : transform 0.3s ease-out, opacity 0.3s ease-in;
7 | }
8 |
9 | .leaflet-cluster-spider-leg {
10 | /* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */
11 | -webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in;
12 | -moz-transition : -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in;
13 | -o-transition : -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in;
14 | transition : stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in;
15 | }
16 |
--------------------------------------------------------------------------------
/src/sass/_ui.scss:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | #mapCanvas {
4 | height : calc(100vh - 74px); // 74px = Navbar height
5 | background-color: rgba(#0fa8d2, 1);
6 | }
7 |
8 | .control-header {
9 | color: rgba($cyan, 1);
10 | }
11 |
12 | div#blipControlContainer {
13 | a.blip-button-a {
14 | position : relative;
15 | padding : 0.5rem 1rem;
16 | color : #fff;
17 | text-decoration : none;
18 | background-color: #303030;
19 | border : 1px solid #444;
20 |
21 | &.blip-enabled {
22 | -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.6);
23 | -moz-box-shadow : inset 0 1px 4px rgba(0, 0, 0, 0.6);
24 | box-shadow : inset 0 1px 4px rgba(0, 0, 0, 0.6);
25 | background-color : rgba(lighten($orange, 15%), 0.67);
26 | padding : 0.3rem 1.1em;
27 | }
28 |
29 | &.blip-disabled {
30 | background-color: rgba(darken($red, 10%), 0.67);
31 | padding : 0.3rem 1.1em;
32 | }
33 | }
34 | }
35 |
36 | .clear {
37 | clear: both;
38 | }
39 |
40 | .info-window {
41 | padding : 10px;
42 | background-color: $body-bg;
43 |
44 | .info-header-box {
45 | margin-bottom: 2px;
46 | }
47 |
48 | .info-header {
49 | float : left;
50 | font-size : 1.8em !important;
51 | font-weight: bold !important;
52 | padding-top: 3px;
53 | }
54 |
55 | .info-body {
56 | height : 33px;
57 | border-top: 1px solid $light;
58 | }
59 |
60 | .info-body-row {
61 | strong {
62 | width: auto;
63 | }
64 | }
65 |
66 | .info-body-row {
67 | padding: 2px 0;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/sass/_variables.scss:
--------------------------------------------------------------------------------
1 | // Darkly 5.0.1
2 | // Bootswatch
3 |
4 | $theme: "darkly" !default;
5 |
6 | //
7 | // Color system
8 | //
9 |
10 | $white : #fff !default;
11 | $gray-100: #f8f9fa !default;
12 | $gray-200: #ebebeb !default;
13 | $gray-300: #dee2e6 !default;
14 | $gray-400: #ced4da !default;
15 | $gray-500: #adb5bd !default;
16 | $gray-600: #888 !default;
17 | $gray-700: #444 !default;
18 | $gray-800: #303030 !default;
19 | $gray-900: #222 !default;
20 | $black : #000 !default;
21 |
22 | $blue : #375a7f !default;
23 | $indigo: #6610f2 !default;
24 | $purple: #6f42c1 !default;
25 | $pink : #e83e8c !default;
26 | $red : #e74c3c !default;
27 | $orange: #fd7e14 !default;
28 | $yellow: #f39c12 !default;
29 | $green : #00bc8c !default;
30 | $teal : #20c997 !default;
31 | $cyan : #3498db !default;
32 |
33 | $primary : $blue !default;
34 | $secondary: $gray-700 !default;
35 | $success : $green !default;
36 | $info : $cyan !default;
37 | $warning : $yellow !default;
38 | $danger : $red !default;
39 | $light : $gray-500 !default;
40 | $dark : $gray-800 !default;
41 |
42 | $min-contrast-ratio: 2.15 !default;
43 |
44 | // Body
45 |
46 | $body-bg : $gray-900 !default;
47 | $body-color: $white !default;
48 |
49 | // Links
50 |
51 | $link-color: $orange !default;
52 |
53 | // Fonts
54 |
55 | // stylelint-disable-next-line value-keyword-case
56 | $font-family-sans-serif: Lato,
57 | -apple-system,
58 | BlinkMacSystemFont,
59 | "Segoe UI",
60 | Roboto,
61 | "Helvetica Neue",
62 | Arial,
63 | sans-serif,
64 | "Apple Color Emoji",
65 | "Segoe UI Emoji",
66 | "Segoe UI Symbol" !default;
67 | $h1-font-size: 3rem !default;
68 | $h2-font-size: 2.5rem !default;
69 | $h3-font-size: 2rem !default;
70 | $text-muted : $gray-600 !default;
71 |
72 | // Tables
73 |
74 | $table-accent-bg : $gray-800 !default;
75 | $table-border-color: $gray-700 !default;
76 |
77 | $table-bg-scale: 0 !default;
78 |
79 | // Forms
80 |
81 | $input-color : $gray-800 !default;
82 | $input-border-color : $body-bg !default;
83 | $input-group-addon-color: $gray-500 !default;
84 | $input-group-addon-bg : $gray-700 !default;
85 |
86 | $form-check-input-bg : $white !default;
87 | $form-check-input-border: none !default;
88 |
89 | $form-file-button-color: $white !default;
90 |
91 | // Dropdowns
92 |
93 | $dropdown-bg : $gray-900 !default;
94 | $dropdown-border-color : $gray-700 !default;
95 | $dropdown-divider-bg : $gray-700 !default;
96 | $dropdown-link-color : $white !default;
97 | $dropdown-link-hover-color: $white !default;
98 | $dropdown-link-hover-bg : $primary !default;
99 |
100 | // Navs
101 |
102 | $nav-link-padding-x : 2rem !default;
103 | $nav-link-disabled-color : $gray-500 !default;
104 | $nav-tabs-border-color : $gray-700 !default;
105 | $nav-tabs-link-hover-border-color : $nav-tabs-border-color $nav-tabs-border-color transparent !default;
106 | $nav-tabs-link-active-color : $white !default;
107 | $nav-tabs-link-active-border-color: $nav-tabs-border-color $nav-tabs-border-color transparent !default;
108 |
109 | // Navbar
110 |
111 | $navbar-padding-y : 1rem !default;
112 | $navbar-dark-color : rgba(lighten($orange, 10%), 0.6) !default;
113 | $navbar-dark-hover-color : $orange !default;
114 | $navbar-light-color : rgba($gray-900, 0.7) !default;
115 | $navbar-light-hover-color : $gray-900 !default;
116 | $navbar-light-active-color : $gray-900 !default;
117 | $navbar-light-toggler-border-color: rgba($gray-900, 0.1) !default;
118 |
119 | // Pagination
120 |
121 | $pagination-color : $white !default;
122 | $pagination-bg : $success !default;
123 | $pagination-border-width : 0 !default;
124 | $pagination-border-color : transparent !default;
125 | $pagination-hover-color : $white !default;
126 | $pagination-hover-bg : lighten($success, 10%) !default;
127 | $pagination-hover-border-color : transparent !default;
128 | $pagination-active-bg : $pagination-hover-bg !default;
129 | $pagination-active-border-color : transparent !default;
130 | $pagination-disabled-color : $white !default;
131 | $pagination-disabled-bg : darken($success, 15%) !default;
132 | $pagination-disabled-border-color: transparent !default;
133 |
134 | // Cards
135 |
136 | $card-cap-bg: $gray-700 !default;
137 | $card-bg : $gray-800 !default;
138 |
139 | // Popovers
140 |
141 | $popover-bg : $gray-800 !default;
142 | $popover-header-bg: $gray-700 !default;
143 |
144 | // Toasts
145 |
146 | $toast-background-color : $gray-700 !default;
147 | $toast-header-background-color: $gray-800 !default;
148 |
149 | // Modals
150 |
151 | $modal-content-bg : $gray-800 !default;
152 | $modal-content-border-color: $gray-700 !default;
153 | $modal-header-border-color : $gray-700 !default;
154 |
155 | // Progress bars
156 |
157 | $progress-bg: $gray-700 !default;
158 |
159 | // List group
160 |
161 | $list-group-color : $body-color !default;
162 | $list-group-bg : $gray-800 !default;
163 | $list-group-border-color : $gray-700 !default;
164 | $list-group-hover-bg : $gray-700 !default;
165 | $list-group-action-hover-color: $list-group-color !default;
166 | $list-group-action-active-bg : $gray-900 !default;
167 |
168 | // Breadcrumbs
169 |
170 | $breadcrumb-padding-y : 0.375rem !default;
171 | $breadcrumb-padding-x : 0.75rem !default;
172 | $breadcrumb-bg : $gray-700 !default;
173 | $breadcrumb-border-radius: 0.25rem !default;
174 |
175 | // Close
176 |
177 | $btn-close-color : $black !default;
178 | $btn-close-opacity : 0.4 !default;
179 | $btn-close-hover-opacity: 1 !default;
180 |
181 | // Code
182 |
183 | $pre-color: inherit !default;
184 |
--------------------------------------------------------------------------------
/src/sass/main.scss:
--------------------------------------------------------------------------------
1 | @import "./variables";
2 |
3 | @import "../../node_modules/bootstrap/scss/bootstrap.scss";
4 | @import "../../node_modules/leaflet/dist/leaflet.css";
5 |
6 | @import "../../node_modules/leaflet.markercluster/dist/MarkerCluster.css";
7 |
8 | @import "../../node_modules/simple-notify/dist/simple-notify.min.css";
9 |
10 | @import "./alerts";
11 | @import "./ui";
12 | @import "./leaflet";
13 | @import "./markerCluster";
14 |
--------------------------------------------------------------------------------
/tools/api-doc-template.hbs:
--------------------------------------------------------------------------------
1 | ---
2 | layout: default
3 | title: API Documentation
4 | nav_order: 1
5 | parent: Development
6 | grand_parent: LiveMap Interface
7 | ---
8 |
9 | {{>main}}
10 |
--------------------------------------------------------------------------------
/tools/partials/header.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{>heading-indent}}{{>sig-name}}
3 |
--------------------------------------------------------------------------------
/tools/partials/link.hbs:
--------------------------------------------------------------------------------
1 | {{! usage: link to="namepath" html=true/false caption="optional caption"~}}
2 |
3 | {{~#if html~}}
4 |
5 |
6 | {{~#link to~}}
7 | {{#if url~}}
8 | {{#if ../../caption}}{{../../../caption}}{{else}}{{name}}{{/if}}
9 | {{~else~}}
10 | {{#if ../../caption}}{{../../../caption}}{{else}}{{name}}{{/if~}}
11 | {{/if~}}
12 | {{/link~}}
13 |
14 |
15 | {{~else~}}
16 |
17 | {{#link to~}}
18 | {{#if url~}}
19 | [`{{#if ../../caption}}{{escape ../../../caption}}{{else}}{{escape name}}{{/if}}`]({{{url}}})
20 | {{~else~}}
21 | `{{#if ../../caption}}{{escape ../../../caption}}{{else}}{{escape name}}{{/if~}}`
22 | {{~/if~}}
23 | {{/link~}}
24 |
25 | {{/if~}}
--------------------------------------------------------------------------------
/tools/standard-version-bump.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports.readVersion = function (contents) {
3 | return JSON.parse(contents).interface;
4 | }
5 |
6 | module.exports.writeVersion = function (contents, version) {
7 | const json = JSON.parse(contents);
8 | json.interface = version
9 | return JSON.stringify(json, null, 4)
10 | }
11 |
--------------------------------------------------------------------------------
/translations/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Havoc's LiveMap",
3 | "credit": "This was originally created by ",
4 | "navbar": {
5 | "select-server": "Select a server",
6 | "controls": "Show Controls",
7 | "blips": "Blip controls",
8 | "language": "Language"
9 | },
10 | "generic": {
11 | "on": "On",
12 | "off": "Off",
13 | "connected": "Connected",
14 | "disconnected": "Disconnected",
15 | "reconnecting": "Reconnecting...",
16 | "close": "Close"
17 | },
18 | "controls": {
19 | "title": "Controls",
20 | "blips": {
21 | "title": "Blips",
22 | "refresh": "Refresh Blips",
23 | "show": "Show Blips"
24 | },
25 | "connection": {
26 | "title": "Connections",
27 | "live-update": "Live Update",
28 | "reconnect": "Reconnect"
29 | },
30 | "information": {
31 | "title": "Information",
32 | "viewing": "Currently viewing",
33 | "blips-loaded": "Blips loaded",
34 | "players": "Player Count"
35 | },
36 | "track-player": {
37 | "title": "Track a player",
38 | "disable": "Stop tracking"
39 | },
40 | "filter": {
41 | "title": "Filter",
42 | "disable-filter": "Disable filter",
43 | "filter-value": "With a value of",
44 | "filter-placeholder": "Some value"
45 | }
46 | },
47 | "blip-controls": {
48 | "title": "Blip Controls",
49 | "toggle": "Toggle all"
50 | },
51 | "errors": {
52 | "version-check": {
53 | "current": {
54 | "title": "Error getting current version",
55 | "message": "%(error.message)s"
56 | },
57 | "remote": {
58 | "title": "Error getting remote version",
59 | "message": "%(error.message)s"
60 | }
61 | },
62 | "getting-blips": {
63 | "title": "Error getting blips.json",
64 | "message": "%(error.message)s"
65 | },
66 | "getting-config": {
67 | "title": "Error getting config",
68 | "message": "%(error.message)s"
69 | },
70 | "socket-error": "Error with WebSocket connection",
71 | "server-config": {
72 | "title": "Couldn't load server config!",
73 | "message": "The server \"%(nameOfServer)s\" doesn't exist in the config file."
74 | },
75 | "socket": {
76 | "1000": "Normal closure, meaning that the purpose for which the connection was established has been fulfilled.",
77 | "1001": "Server is going down or a browser having navigated away from a page.",
78 | "1002": "An endpoint is terminating the connection due to a protocol error",
79 | "1003": "Wrong data type received by the server",
80 | "1004": "Reserved. The specific meaning might be defined in the future.",
81 | "1005": "No status code was actually present.",
82 | "1006": "The connection was closed abnormally, e.g., without sending or receiving a Close control frame.",
83 | "1007": "Server has received data within a message that was not consistent with the type of the message.",
84 | "1008": "Server has received a message that 'violates its policy'.",
85 | "1009": "Server received a message that is too big for it to process.",
86 | "1010": "Client expected the server to negotiate one or more extension, but the server didn't return them in the response message of the WebSocket handshake.\n Specifically, the extensions that are needed are: %(reason)s",
87 | "1011": "Server encountered an unexpected condition that prevented it from fulfilling the request.",
88 | "1015": "The connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate can't be verified).",
89 | "other": "Unknown reason: %(code)s %(reason)s"
90 | }
91 | },
92 | "updates": {
93 | "available": {
94 | "title": "Update available",
95 | "message": "An update is available (%(currentVersion)s -> %(remoteVersion)s). Please download it HERE. "
96 | },
97 | "latest": {
98 | "title": "Up to date!",
99 | "message": "Good news, you're using the latest version of the LiveMap interface. Please enjoy %(currentVersion)s!"
100 | }
101 | },
102 | "map": {
103 | "position": "Position",
104 | "description": "Description"
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/translations/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "credit": "Esto fue creado originalmente por",
3 | "navbar": {
4 | "select-server": "Seleccionar un servidor",
5 | "controls": "Mostrar controles",
6 | "blips": "Controles blip",
7 | "language": "Idioma"
8 | },
9 | "generic": {
10 | "on": "Activado",
11 | "off": "Desactivado",
12 | "connected": "Conectado",
13 | "disconnected": "Desconectado",
14 | "reconnecting": "Reconectando ...",
15 | "close": "Cerrar"
16 | },
17 | "controls": {
18 | "title": "Controls",
19 | "blips": {
20 | "title": "Marcadores",
21 | "refresh": "Actualizar",
22 | "show": "Show"
23 | },
24 | "connection": {
25 | "title": "Conexiones",
26 | "live-update": "Actualización en vivo",
27 | "reconnect": "Vuelva a conectar"
28 | },
29 | "information": {
30 | "viewing": "Viendo actualmente",
31 | "blips-loaded": "Marcadores cargados",
32 | "players": "Recuento de jugadores"
33 | },
34 | "track-player": {
35 | "title": "Rastrear a un jugador",
36 | "disable": "Detener el seguimiento"
37 | },
38 | "filter": {
39 | "title": "Filtrar",
40 | "filter-value": "Con el valor",
41 | "filter-placeholder": "Alguna cosa",
42 | "disable-filter": "Deshabilitar filtro"
43 | }
44 | },
45 | "blip-controls": {
46 | "title": "Mostrar controles",
47 | "toggle": "Alternar todo"
48 | },
49 | "errors": {
50 | "version-check": {
51 | "current": {
52 | "title": "Error al obtener la versión local.",
53 | "message": "%(error.message)s"
54 | },
55 | "remote": {
56 | "title": "Error al obtener la versión remota.",
57 | "message": "%(error.message)s"
58 | }
59 | },
60 | "getting-blips": {
61 | "title": "Error al obtener blips.json",
62 | "message": "%(error.message)s"
63 | },
64 | "getting-config": {
65 | "title": "Error al obtener la configuración",
66 | "message": "%(error.message)s"
67 | },
68 | "socket-error": "Error con la conexión de WebSocket",
69 | "server-config": {
70 | "title": "¡No se pudo cargar la configuración del servidor!",
71 | "message": "El servidor \"%(nameOfServer)s\" no existe en el archivo de configuración."
72 | },
73 | "socket": {
74 | "1000": "Cierre normal, lo que significa que se ha cumplido el propósito para el que se estableció la conexión.",
75 | "1001": "El servidor está cayendo o un navegador ha salido de una página.",
76 | "1002": "Un punto final está terminando la conexión debido a un error de protocolo",
77 | "1003": "Incorrecto tipo de datos recibido por el servidor",
78 | "1004": "Reservado. El significado específico podría definirse en el futuro.",
79 | "1005": "No había ningún código de estado realmente presente.",
80 | "1006": "La conexión se cerró de forma anormal, por ejemplo, sin enviar o recibir un marco de control Cerrar",
81 | "1007": "El servidor ha recibido datos dentro de un mensaje que no coherente con el tipo de mensaje.",
82 | "1008": "El servidor ha recibido un mensaje que 'viola su política'.",
83 | "1009": "El servidor recibió un mensaje que es demasiado grande para procesarlo.",
84 | "1010": "El cliente esperaba que el servidor negociara una o más extensiones, pero el servidor no las devolvió en el mensaje de respuesta del protocolo de enlace de WebSocket.\nEspecíficamente, las extensiones que se necesitan son: %(reason)s",
85 | "1011": "El servidor encontró una condición inesperada que le impidió cumplir con la solicitud.",
86 | "1015": "La conexión se cerró debido a una falla al realizar un protocolo de enlace TLS (por ejemplo, el certificado del servidor no se puede verificar).",
87 | "other": "Razón desconocida: %(code)s %(reason)s"
88 | }
89 | },
90 | "updates": {
91 | "available": {
92 | "title": "Actualización disponible",
93 | "message": "Hay una actualización disponible (%(currentVersion)s -> %(remoteVersion)s). Descárguelo AQUÍ. "
94 | },
95 | "latest": {
96 | "title": "¡A hoy!",
97 | "message": "Buenas noticias, está utilizando la última versión de la interfaz de LiveMap. Por favor, disfruta %(currentVersion)s!"
98 | }
99 | },
100 | "map": {
101 | "position": "Posición",
102 | "description": "Descripción"
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/translations/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "credit": "Cette carte a été créée à l'origine par ",
3 | "navbar": {
4 | "select-server": "Sélectionnez un serveur",
5 | "controls": "Afficher les contrôles",
6 | "blips": "Contrôles du blip",
7 | "language": "Langue"
8 | },
9 | "generic": {
10 | "on": "Activé",
11 | "off": "Désactivé",
12 | "connected": "Connecté",
13 | "disconnected": "Déconnecté",
14 | "reconnecting": "Reconnexion...",
15 | "close": "Fermer"
16 | },
17 | "controls": {
18 | "title": "Contrôles",
19 | "blips": {
20 | "title": "Blips",
21 | "refresh": "Rafraîchir les Blips",
22 | "show": "Afficher les blips"
23 | },
24 | "connection": {
25 | "title": "Connexion",
26 | "live-update": "Mise à jour en direct",
27 | "reconnect": "Reconnecter"
28 | },
29 | "information": {
30 | "title": "Informations",
31 | "viewing": "Affichage actuel",
32 | "blips-loaded": "Blips chargés",
33 | "players": "Nombre de joueurs"
34 | },
35 | "track-player": {
36 | "title": "Suivre un joueur",
37 | "disable": "Arrêter le suivi"
38 | },
39 | "filter": {
40 | "title": "Filtre",
41 | "disable-filter": "Désactiver le filtre",
42 | "filter-value": "Avec une valeur de",
43 | "filter-placeholder": "La valeur"
44 | }
45 | },
46 | "blip-controls": {
47 | "title": "Contrôles du blip",
48 | "toggle": "Tout faire basculer"
49 | },
50 | "errors": {
51 | "getting-blips": {
52 | "title": "Erreur dans l'obtention de blips.json",
53 | "message": "%(error.message)s"
54 | },
55 | "getting-config": {
56 | "title": "Erreur dans l'obtention de la configuration",
57 | "message": "%(error.message)s"
58 | },
59 | "socket-error": "Erreur avec la connexion WebSocket",
60 | "server-config": {
61 | "title": "Impossible de charger la configuration du serveur !",
62 | "message": "Le serveur \"%(nameOfServer)s\" n'existe pas dans le fichier de configuration."
63 | },
64 | "socket": {
65 | "1000": "Fermeture normale, ce qui signifie que l'objectif pour lequel la connexion a été établie a été atteint.",
66 | "1001": "Le serveur s'arrête ou le navigateur a quitté une page.",
67 | "1002": "Un point de terminaison met fin à la connexion en raison d'une erreur de protocole.",
68 | "1003": "Mauvais type de données reçu par le serveur",
69 | "1004": "Réservé. La signification spécifique pourrait être définie à l'avenir.",
70 | "1005": "Aucun code d'état n'était réellement présent.",
71 | "1006": "La connexion a été fermée de manière anormale, par exemple, sans envoyer ou recevoir une trame de contrôle de fermeture.",
72 | "1007": "Le serveur a reçu des données dans un message qui n'étaient pas cohérentes avec le type du message.",
73 | "1008": "Le serveur a reçu un message qui \"viole sa politique\".",
74 | "1009": "Le serveur a reçu un message trop volumineux pour qu'il puisse le traiter.",
75 | "1010": "Le client s'attendait à ce que le serveur négocie une ou plusieurs extensions, mais le serveur ne les a pas renvoyées dans le message de réponse du handshake WebSocket.\n Plus précisément, les extensions qui sont nécessaires sont : %(reason)s",
76 | "1011": "Le serveur a rencontré une condition inattendue qui l'a empêché de répondre à la demande.",
77 | "1015": "La connexion a été fermée en raison d'un échec de la poignée de main TLS (par exemple, le certificat du serveur ne peut pas être vérifié).",
78 | "other": "Raison inconnue : %(code)s %(reason)s"
79 | },
80 | "version-check": {
81 | "current": {
82 | "title": "Erreur lors de l'obtention de la version locale.",
83 | "message": "%(error.message)s"
84 | },
85 | "remote": {
86 | "title": "Erreur lors de l'obtention de la version distante.",
87 | "message": "%(error.message)s"
88 | }
89 | }
90 | },
91 | "updates": {
92 | "available": {
93 | "title": "Mise à jour disponible",
94 | "message": "Une mise à jour est disponible (%(currentVersion)s -> %(remoteVersion)s). Veuillez la télécharger ICI. "
95 | },
96 | "latest": {
97 | "title": "À jour!",
98 | "message": "Bonne nouvelle, vous utilisez la dernière version de l'interface LiveMap. Amusez-vous %(currentVersion)s!"
99 | }
100 | },
101 | "map": {
102 | "position": "Positionner",
103 | "description": "La description "
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/translations/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "en": "English",
3 | "fr": "Français",
4 | "es": "Español",
5 | "ru": "Pусский"
6 | }
--------------------------------------------------------------------------------
/translations/ru.json:
--------------------------------------------------------------------------------
1 | {
2 | "credit": "Первоначально она была создана ",
3 | "navbar": {
4 | "select-server": "Выберите сервер",
5 | "controls": "Показать элементы управления",
6 | "blips": "Элементы управления Blip",
7 | "language": "Язык"
8 | },
9 | "generic": {
10 | "on": "Вкл.",
11 | "off": "Выключено",
12 | "connected": "Подключено",
13 | "disconnected": "Отключено",
14 | "reconnecting": "Повторное подключение ...",
15 | "close": "Закрыть"
16 | },
17 | "controls": {
18 | "title": "Управление",
19 | "blips": {
20 | "title": "Всплески",
21 | "refresh": "Обновить всплески",
22 | "show": "Показать всплывающие подсказки"
23 | },
24 | "connection": {
25 | "title": "Соединения",
26 | "live-update": "Обновление в реальном времени",
27 | "reconnect": "Переподключиться"
28 | },
29 | "information": {
30 | "title": "Информация",
31 | "viewing": "Текущий просмотр",
32 | "blips-loaded": "Загруженные всплески",
33 | "players": "Подсчет игроков"
34 | },
35 | "track-player": {
36 | "title": "Отслеживание игрока",
37 | "disable": "Прекратить отслеживание"
38 | },
39 | "filter": {
40 | "title": "Фильтр",
41 | "disable-filter": "Отключить фильтр",
42 | "filter-value": "Со значением",
43 | "filter-placeholder": "Некоторое значение"
44 | }
45 | },
46 | "blip-controls": {
47 | "title": "Управление всплесками",
48 | "toggle": "Переключить все"
49 | },
50 | "errors": {
51 | "getting-blips": {
52 | "title": "Ошибка при получении файла blips.json",
53 | "message": "%(error.message)s"
54 | },
55 | "getting-config": {
56 | "title": "Ошибка при получении конфигурации",
57 | "message": "%(error.message)s"
58 | },
59 | "socket-error": "Ошибка при подключении WebSocket",
60 | "server-config": {
61 | "title": "Не удалось загрузить конфигурацию сервера!",
62 | "message": "Сервер%(nameOfServer)s\" не существует в конфигурационном файле."
63 | },
64 | "socket": {
65 | "1000": "Нормальное закрытие, означающее, что цель, ради которой было установлено соединение, выполнена.",
66 | "1001": "Сервер завершает работу или браузер перешел со страницы.",
67 | "1002": "Конечная точка завершает соединение из-за ошибки протокола.",
68 | "1003": "Неправильный тип данных получен сервером",
69 | "1004": "Зарезервировано. Конкретное значение может быть определено в будущем.",
70 | "1005": "Код состояния отсутствует.",
71 | "1006": "Соединение было закрыто ненормально, например, без отправки или получения управляющего кадра Close.",
72 | "1007": "Сервер получил данные в сообщении, которые не соответствовали типу сообщения.",
73 | "1008": "Сервер получил сообщение, которое \"нарушает его политику\".",
74 | "1009": "Сервер получил сообщение, которое слишком велико для его обработки.",
75 | "1010": "Клиент ожидал от сервера согласования одного или нескольких расширений, но сервер не вернул их в ответном сообщении WebSocket handshake.\n В частности, требуются следующие расширения: %(reason)s",
76 | "1011": "Сервер столкнулся с непредвиденным условием, которое не позволило ему выполнить запрос.",
77 | "1015": "Соединение было закрыто из-за невозможности выполнить рукопожатие TLS (например, сертификат сервера не может быть проверен).",
78 | "other": "Неизвестная причина: %(code)s %(reason)s"
79 | },
80 | "version-check": {
81 | "current": {
82 | "title": "Ошибка при получении локальной версии.",
83 | "message": "%(error.message)s"
84 | },
85 | "remote": {
86 | "title": "Ошибка при получении удаленной версии.",
87 | "message": "%(error.message)s"
88 | }
89 | }
90 | },
91 | "updates": {
92 | "available": {
93 | "title": "Обновление доступно",
94 | "message": "Доступно обновление (%(currentVersion)s -> %(remoteVersion)s). Пожалуйста, скачайте это ЗДЕСЬ. "
95 | },
96 | "latest": {
97 | "title": "До настоящего времени!",
98 | "message": "Хорошие новости, вы используете последнюю версию интерфейса LiveMap. Пожалуйста, наслаждайтесь %(currentVersion)s!"
99 | }
100 | },
101 | "map": {
102 | "position": "Должность",
103 | "description": "Описание"
104 | }
105 | }
--------------------------------------------------------------------------------
/version.json:
--------------------------------------------------------------------------------
1 | {
2 | "interface": "4.1.8"
3 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | const path = require("path");
4 | const HtmlWebpackPlugin = require("html-webpack-plugin");
5 | const webpack = require("webpack");
6 |
7 | var config = {
8 | mode: "production",
9 | devtool: "source-map",
10 | module: {
11 | rules: [
12 | {
13 | test: /\.s[ac]ss$/i,
14 | use: [
15 | // Creates `style` nodes from JS strings
16 | "style-loader",
17 | // Translates CSS into CommonJS
18 | "css-loader",
19 | // Compiles Sass to CSS
20 | "sass-loader",
21 | ],
22 | },
23 | ],
24 | },
25 | entry: {
26 | vendors: {
27 | import: ["leaflet", "leaflet.markercluster", "@popperjs/core", "bootstrap"],
28 | },
29 |
30 | style: "./src/sass/main.scss",
31 | app: {
32 | dependOn: "vendors",
33 | import: "./src/js/_app.js",
34 | }
35 | },
36 | optimization: {
37 | runtimeChunk: "single"
38 | },
39 | plugins: [
40 | new webpack.ProvidePlugin({
41 | L: "leaflet",
42 | }),
43 | // Dev build, for dev server
44 | new HtmlWebpackPlugin({
45 | template: "./public/index.html",
46 | filename: "index.html",
47 | }),
48 | // Production build
49 | new HtmlWebpackPlugin({
50 | title: "Havoc's LiveMap",
51 | template: "./public/index.html",
52 | hash: true,
53 | inject: true,
54 | filename: "../index.html",
55 | minify: {
56 | collapseWhitespace: true,
57 | removeComments: true,
58 | },
59 | }),
60 | ],
61 | output: {
62 | filename: "[name].[fullhash].js",
63 | path: path.resolve(__dirname, "dist"),
64 | // publicPath: "dist/",
65 | clean: true,
66 | },
67 | performance: {
68 | maxEntrypointSize: 5242880,
69 | maxAssetSize: 5242880
70 | },
71 | devServer: {
72 | static: [
73 | {
74 | directory: path.join(__dirname, "images"),
75 | publicPath: "/images",
76 | },
77 | {
78 | directory: path.join(__dirname, "translations"),
79 | publicPath: "/translations",
80 | },
81 | {
82 | directory: path.join(__dirname, "public", "dev"),
83 | publicPath: "/",
84 | }
85 | ],
86 | compress: true,
87 | port: 9000,
88 | },
89 | };
90 |
91 | module.exports = (env, argv) => {
92 | return config;
93 | };
94 |
--------------------------------------------------------------------------------