├── .gitignore ├── .prettierignore ├── .prettierrc.cjs ├── .vscode └── launch.json ├── Gruntfile.cjs ├── LICENSE ├── README-DEV.md ├── README.md ├── app ├── course.php ├── event.php ├── html │ ├── aboutlayout.html │ ├── addeventbody.html │ ├── addmapbody.html │ ├── animationlayout.html │ ├── body.html │ ├── deletemapbody.html │ ├── drawbody.html │ ├── editeventbody.html │ ├── head.html │ ├── navbar.html │ ├── script.html │ ├── settingslayout.html │ └── splitsbrowser.html ├── language.php ├── map.php ├── result.php ├── route.php ├── splitsbrowser.php ├── user.php └── utils.php ├── cypress.config.cjs ├── cypress ├── e2e │ ├── 1-core │ │ ├── core.cy.js │ │ ├── extras.cy.js │ │ ├── load-event-no-results.cy.js │ │ ├── load-normal-event.cy.js │ │ ├── load-score-event.cy.js │ │ ├── results.cy.js │ │ └── touch.cy.js │ ├── 2-draw │ │ └── draw.cy.js │ ├── 3-gps │ │ ├── gps-adjustment.cy.js │ │ └── gps.cy.js │ ├── 4-replay │ │ └── replay.cy.js │ ├── 5-manager │ │ ├── create-event-1.cy.js │ │ ├── create-event-2.cy.js │ │ ├── create-event-3.cy.js │ │ ├── edit-event.cy.js │ │ ├── login.cy.js │ │ └── manage-maps.cy.js │ └── run-e2e-tests.cjs ├── fixtures │ ├── config-01 │ │ ├── 1.jpg │ │ ├── 126.jpg │ │ ├── 394.gif │ │ ├── 394.jpg │ │ ├── 402.gif │ │ ├── 402.jpg │ │ ├── exclude_388.txt │ │ ├── hajontakanta_380.txt │ │ ├── hajontakanta_388.txt │ │ ├── kartat.txt │ │ ├── keksi.txt │ │ ├── kilpailijat_1.txt │ │ ├── kilpailijat_129.txt │ │ ├── kilpailijat_380.txt │ │ ├── kilpailijat_388.txt │ │ ├── kisat.txt │ │ ├── kommentit_1.txt │ │ ├── kommentit_129.txt │ │ ├── kommentit_380.txt │ │ ├── kommentit_388.txt │ │ ├── merkinnat_1.txt │ │ ├── merkinnat_129.txt │ │ ├── merkinnat_380.txt │ │ ├── merkinnat_388.txt │ │ ├── radat_1.txt │ │ ├── radat_129.txt │ │ ├── radat_380.txt │ │ ├── radat_388.txt │ │ ├── ratapisteet_1.txt │ │ ├── ratapisteet_129.txt │ │ ├── ratapisteet_380.txt │ │ ├── ratapisteet_388.txt │ │ ├── rg2userinfo.txt │ │ ├── sarjat_1.txt │ │ ├── sarjat_129.txt │ │ ├── sarjat_380.txt │ │ ├── sarjat_388.txt │ │ ├── sarjojenkoodit_1.txt │ │ ├── sarjojenkoodit_380.txt │ │ ├── sarjojenkoodit_388.txt │ │ ├── worldfile_388.txt │ │ ├── worldfile_394.txt │ │ └── worldfile_402.txt │ ├── data │ │ ├── Highfield-2022-06-04-v7.xml │ │ ├── Highfield_Park_Orange.gpx │ │ ├── Highfield_Park_Short_blue.gpx │ │ ├── IOFV2resultsparseerror.xml │ │ ├── IOFV3resultsparseerror.xml │ │ ├── Jersey_Farm_Street_O.gpx │ │ ├── ellenbrook-missing-headers.csv │ │ ├── ellenbrook.csv │ │ ├── ellenbrook.gpx │ │ ├── ellenbrook.jgw │ │ ├── ellenbrook.jpg │ │ ├── ellenbrook.tcx │ │ ├── ellenbrookIOFV1courses.xml │ │ ├── ellenbrookIOFV2Georefcourses.xml │ │ ├── ellenbrookIOFV2courses.xml │ │ ├── ellenbrookIOFV2invalidcourses.xml │ │ ├── ellenbrookIOFV3OCADcourses.xml │ │ ├── ellenbrookIOFV3courses.xml │ │ ├── ellenbrookinvalid.jgw │ │ ├── heartwoodIOFV3Georefcourses.xml │ │ ├── highfield4RG.csv │ │ ├── invalid.gpx │ │ ├── londoncolney.gif │ │ ├── miltonriggIOFV1results.xml │ │ ├── miltonriggIOFV2invalidresults.xml │ │ ├── miltonriggIOFV2results.xml │ │ ├── miltonriggIOFV3courses.xml │ │ ├── miltonriggIOFV3coursesnogeoref.xml │ │ ├── miltonriggIOFV3results.xml │ │ ├── oaklands.gpx │ │ ├── oom_636ca9e43ca63.jgw │ │ ├── oom_636ca9e43ca63.jpg │ │ ├── oom_636ca9e43ca63.kml │ │ ├── oom_636ca9e43ca63.pdf │ │ ├── pwllddu.csv │ │ ├── pwllddu.jgw │ │ ├── pwllddu.jpg │ │ ├── pwlldduIOFV3relay.xml │ │ ├── spklasse.csv │ │ ├── verulamium.gpx │ │ ├── verulamium.jgw │ │ ├── verulamium.jpg │ │ └── verybigmap-poorquality.jpg │ ├── php │ │ ├── config-01.php │ │ ├── config-02.php │ │ └── config-03.php │ └── validmanager.json └── support │ ├── commands.js │ └── e2e.js ├── dist ├── .vite │ └── manifest.json ├── app │ ├── course.php │ ├── event.php │ ├── html │ │ ├── aboutlayout.html │ │ ├── addeventbody.html │ │ ├── addmapbody.html │ │ ├── animationlayout.html │ │ ├── body.html │ │ ├── deletemapbody.html │ │ ├── drawbody.html │ │ ├── editeventbody.html │ │ ├── head.html │ │ ├── navbar.html │ │ ├── script.html │ │ ├── settingslayout.html │ │ └── splitsbrowser.html │ ├── language.php │ ├── map.php │ ├── result.php │ ├── route.php │ ├── splitsbrowser.php │ ├── user.php │ └── utils.php ├── assets │ ├── bootstrap-icons-BOrJxbIo.woff │ ├── bootstrap-icons-BtvjY1KL.woff2 │ ├── grid-CgsJkZsf.js │ ├── grid-CgsJkZsf.js.map │ ├── main-7W2BOCJG.css │ ├── main-D9_pgpFN.js │ ├── main-D9_pgpFN.js.map │ ├── manager-1E3re5s3.js │ ├── manager-1E3re5s3.js.map │ ├── manager-utils-C8g2LUbf.js │ ├── manager-utils-C8g2LUbf.js.map │ ├── manager-utils-HupOsEJb.css │ ├── node-modules-CPWXqQ3f.js │ └── node-modules-CPWXqQ3f.js.map ├── img │ ├── apple-touch-icon.png │ ├── favicon.ico │ ├── rg2-logo-32x32.png │ └── rg2-logo.svg ├── index.php ├── lang │ ├── cz.txt │ ├── de.txt │ ├── fi.txt │ ├── fr.txt │ ├── it.txt │ ├── ja.txt │ ├── no.txt │ ├── pt.txt │ └── ru.txt ├── lock │ └── readme.txt ├── log │ └── readme.txt ├── manifest.json └── rg2api.php ├── eslint.config.js ├── img ├── apple-touch-icon.png ├── favicon.ico ├── rg2-logo-32x32.png └── rg2-logo.svg ├── index.php ├── lang ├── cz.txt ├── de.txt ├── fi.txt ├── fr.txt ├── it.txt ├── ja.txt ├── no.txt ├── pt.txt └── ru.txt ├── lock └── readme.txt ├── log └── readme.txt ├── package-lock.json ├── package.json ├── public ├── img │ ├── apple-touch-icon.png │ ├── favicon.ico │ ├── rg2-logo-32x32.png │ └── rg2-logo.svg ├── lock │ └── readme.txt └── log │ └── readme.txt ├── rg2-config .txt ├── rg2-config.php ├── rg2api.php ├── robots.txt ├── src ├── js │ ├── animation.js │ ├── api.js │ ├── canvas.js │ ├── config.js │ ├── controls.js │ ├── course.js │ ├── courseparser.js │ ├── courses.js │ ├── draw.js │ ├── event.js │ ├── events.js │ ├── georefs.js │ ├── gpstrack.js │ ├── handles.js │ ├── hash.js │ ├── main.js │ ├── manager.js │ ├── mapdata.js │ ├── overlay.js │ ├── result.js │ ├── resultparser.js │ ├── resultparsercsv.js │ ├── resultparseriofv2.js │ ├── resultparseriofv3.js │ ├── results.js │ ├── rg2ui.js │ ├── runner.js │ ├── stats.js │ ├── translate.js │ ├── user.js │ ├── utils.js │ └── worldfile.js └── scss │ └── rg2.scss └── vite.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | rg2log.txt 10 | 11 | node_modules 12 | *.local 13 | coverage 14 | .nyc_output 15 | kartat 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | cypress/videos 20 | cypress/screenshots 21 | cypress/downloads 22 | website 23 | stats.html 24 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Options} */ 2 | module.exports = { 3 | semi: false, 4 | trailingComma: "none", 5 | tabWidth: 2, 6 | bracketSpacing: true, 7 | singleQuote: false, 8 | arrowParens: "always", 9 | printWidth: 120, 10 | overrides: [ 11 | { 12 | files: "*.txt", 13 | options: { 14 | // needed to prevent spurious line breaks in language files 15 | printWidth: 200 16 | } 17 | } 18 | ], 19 | bracketSameLine: true 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "Listen for XDebug", 10 | "type": "php", 11 | "request": "launch", 12 | "port": 9000 13 | }, 14 | { 15 | "name": "Launch currently open script", 16 | "type": "php", 17 | "request": "launch", 18 | "program": "${file}", 19 | "cwd": "${fileDirname}", 20 | "port": 9000 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Simon Errington 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-DEV.md: -------------------------------------------------------------------------------- 1 | # Instructions for running RG2 locally (linux) 2 | 3 | TODO: Not yet adapted to Vite build environment 4 | 5 | ## Install required packages 6 | * web server e.g. apache (`sudo apt install apache2`) 7 | * php (`sudo apt install php libapache2-mod-php`) 8 | * node.js (`sudo apt install nodejs`) 9 | * grunt (`npm install -g grunt-cli`) 10 | 11 | ## Fork and clone repo 12 | `git clone https://github.com//rg2` 13 | 14 | ## Run grunt 15 | ```bash 16 | cd rg2 17 | grunt 18 | ``` 19 | 20 | ## Setup apache web server 21 | In your `apache2.conf` e.g. `sudo vi /etc/apache2/apache2.conf` 22 | ``` 23 | 24 | Options Indexes FollowSymLinks 25 | AllowOverride None 26 | Require all granted 27 | 28 | Alias "/rg2" "/path/to/rg2" 29 | ``` 30 | Then `sudo service apache2 restart`. 31 | 32 | ## Setup rg2 config options 33 | Edit `rg2-config.txt` and rename to `rg2-config.php`, suggested options: 34 | ```php 35 | define('RG_BASE_DIRECTORY', 'http://localhost/'); 36 | define('OVERRIDE_KARTAT_DIRECTORY', 'kartat/'); 37 | ``` 38 | Create some test data (from your `rg2` directory) 39 | ```bash 40 | mkdir kartat 41 | mkdir kartat/cache 42 | wget https://www.happyherts.routegadget.co.uk/kartat/cache/events.json -O kartat/cache/events.json 43 | ``` 44 | 45 | ## Launch browser 46 | Go to `http://localhost/rg2/`, and you should see your local rg2 app. 47 | -------------------------------------------------------------------------------- /app/html/aboutlayout.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
RG2 logo
8 |
9 | Routegadget 2 (RG2) is a web application for drawing and comparing orienteering routes. It is based on the 10 | original Routegadget developed by Jarkko Ryyppö. For further details see the 11 | User Guide 12 | and the 13 | RG2 project page. The latest 14 | version of RG2 is available for download 15 | from GitHub. 16 | You can see details of 17 | all RG2 installations here. 18 | This includes an RSS feed that provides details of new RG2 events when they are added. 19 |
20 |
21 |
22 |
Simon Errington (simon@maprunner.co.uk)
23 |
James Errington (james@maprunner.co.uk)
24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 |
32 |
33 |
34 | -------------------------------------------------------------------------------- /app/html/addeventbody.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Event details 5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 | 28 | 29 |
30 | Results 31 |
32 | 33 |
34 |
35 | 36 | 37 |
38 |
39 | 40 | 41 |
Only select if the results file is not already properly sorted.
42 |
43 | Courses 44 |
45 | 46 |
47 |
48 | 49 | 50 |
51 |
52 | 53 | 54 |
55 | 56 |
57 | 58 | 59 |
60 |
61 |
62 | 63 | 64 |
65 |
66 |
67 |
68 | 69 | 70 |
71 | 72 | 73 |
74 |
-------------------------------------------------------------------------------- /app/html/addmapbody.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | Map details 5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 | Georeferencing 14 |
15 | 16 | 17 |
18 |
19 | 20 | 23 |
24 |
25 |
26 |
27 |
28 | 29 |
30 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /app/html/animationlayout.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | 8 | 14 | 15 |
16 |
17 | 18 | 21 | 22 | 25 | 26 | 29 |
30 |
31 | 34 | 40 | 41 |
42 |
43 | 46 | 52 | 53 |
54 |
55 | 58 | 64 | 75 |
76 | -------------------------------------------------------------------------------- /app/html/deletemapbody.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /app/html/drawbody.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 | 8 | 9 |
10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 | 37 |
38 |
39 | 40 |
Offset
41 |
42 | 43 | 44 | 45 |
46 | 47 |
48 |
49 | 50 |
51 |
52 |
53 | 54 | 55 |
56 |
57 |
58 | Left click to add/lock/unlock a handle. 59 |
    60 |
  • Green: draggable
  • 61 |
  • Red: locked
  • 62 |
63 | Right click to delete a handle. 64 |
65 | Drag a handle to adjust track around locked point(s). 66 |
67 |
68 | -------------------------------------------------------------------------------- /app/html/editeventbody.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Select event 5 |
6 | 7 | 8 |
9 |
10 |
11 | Edit event details 12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 |
30 | 35 | 36 |
37 |
38 | 39 |
Type: 1 Zero splits, 2 Actual splits
40 |
course|type|control,time e.g. 1|1|6,60|15,60
41 |
42 |
43 | 44 | 45 |
46 | 47 |
48 |
49 | Delete route 50 |
51 | 52 | 53 |
54 | 55 |
56 |
57 | Delete event 58 |
59 | 60 |
61 |
62 |
63 |
64 | -------------------------------------------------------------------------------- /app/html/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Routegadget 2 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/html/navbar.html: -------------------------------------------------------------------------------- 1 | 70 | -------------------------------------------------------------------------------- /app/html/script.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 28 | -------------------------------------------------------------------------------- /app/html/settingslayout.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 | 8 | 9 |
10 |
11 |
12 | 20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 |
35 | 36 | 37 |
38 |
39 | 40 | 41 |
42 |
43 | 44 | 45 |
46 |
47 | 48 | 49 |
50 |
51 | 52 | 53 |
54 |
55 | 56 | 57 |
58 |
59 | -------------------------------------------------------------------------------- /app/html/splitsbrowser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <EVENT_NAME> 7 | 8 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/language.php: -------------------------------------------------------------------------------- 1 | 1) { 11 | $output[$data[0]] = $data[1]; 12 | } 13 | } 14 | fclose($handle); 15 | } 16 | return utils::addVersion('lang', $output); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/user.php: -------------------------------------------------------------------------------- 1 | x) && isset($data->y)) { 7 | $userdetails = self::extractString($data->x); 8 | $cookie = $data->y; 9 | } else { 10 | $userdetails = "anon"; 11 | $cookie = "none"; 12 | } 13 | $ok = true; 14 | $keksi = trim(file_get_contents(KARTAT_DIRECTORY . "keksi.txt")); 15 | if (file_exists(KARTAT_DIRECTORY . "rg2userinfo.txt")) { 16 | $saved_user = trim(file_get_contents(KARTAT_DIRECTORY . "rg2userinfo.txt")); 17 | if (!password_verify($userdetails, $saved_user)) { 18 | utils::rg2log("User details incorrect."); 19 | $ok = false; 20 | } 21 | if ($keksi != $cookie) { 22 | utils::rg2log("Cookies don't match. " . $keksi . " : " . $cookie); 23 | $ok = false; 24 | } 25 | } else { 26 | // new account being set up: rely on JS end to force a reasonable name/password 27 | $temp = password_hash($userdetails, PASSWORD_DEFAULT); 28 | utils::rg2log("Creating new account."); 29 | file_put_contents(KARTAT_DIRECTORY . "rg2userinfo.txt", $temp . PHP_EOL); 30 | } 31 | return $ok; 32 | } 33 | 34 | // Mickey Mouse function to extract user name and password 35 | // just avoids plain text transmission for now so a bit better than RG1 36 | private static function extractString($data) 37 | { 38 | $str = ""; 39 | for ($i = 0; $i < strlen($data); $i = $i + 2) { 40 | $str .= substr($data, $i, 1); 41 | } 42 | return $str; 43 | } 44 | 45 | public static function generateNewKeksi() 46 | { 47 | // simple cookie generator! Don't need unique, just need something vaguely random 48 | $keksi = substr(str_shuffle("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"), 0, 20); 49 | $result = file_put_contents(KARTAT_DIRECTORY . "keksi.txt", $keksi . PHP_EOL); 50 | //utils::rg2log(KARTAT_DIRECTORY." ".$keksi." ".$result); 51 | if ($result === false) { 52 | utils::rg2log("Error writing keksi.txt: " . $keksi); 53 | } 54 | return $keksi; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /cypress.config.cjs: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require("cypress") 2 | const fs = require("fs-extra") 3 | const path = require("path") 4 | 5 | module.exports = defineConfig({ 6 | projectId: "vb97jp", 7 | e2e: { 8 | experimentalStudio: true, 9 | setupNodeEvents(on, config) { 10 | require("@cypress/code-coverage/task")(on, config) 11 | on("task", { 12 | setUpKartat: ({ config, php = "config-01" }) => { 13 | const kartatPath = path.join(__dirname, "kartat") 14 | const setupPath = path.join(__dirname, "cypress/fixtures", config) 15 | fs.rmSync(kartatPath, { recursive: true, force: true }) 16 | fs.mkdirSync(kartatPath) 17 | if (fs.existsSync(setupPath)) { 18 | fs.copySync(setupPath, kartatPath) 19 | } 20 | // php config 21 | const sourceConfig = path.join(__dirname, "cypress/fixtures/php", php + ".php") 22 | const destConfig = path.join(__dirname, "rg2-config.php") 23 | fs.copyFileSync(sourceConfig, destConfig) 24 | return null 25 | } 26 | }) 27 | return config 28 | }, 29 | env: { 30 | codeCoverage: { 31 | exclude: "cypress/**/*.*" 32 | } 33 | }, 34 | component: { 35 | devServer: { 36 | bundler: "vite" 37 | }, 38 | setupNodeEvents(on, config) { 39 | require("@cypress/code-coverage/task")(on, config) 40 | 41 | return config 42 | } 43 | } 44 | } 45 | }) 46 | -------------------------------------------------------------------------------- /cypress/e2e/1-core/extras.cy.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable cypress/no-unnecessary-waiting */ 2 | describe("Miscellaneous extras", { testIsolation: false }, () => { 3 | beforeEach(() => { 4 | cy.intercept("rg2api.php?type=event&id=*").as("event") 5 | cy.intercept("rg2api.php?type=events*").as("events") 6 | }) 7 | it("should load a normal event from a URL with routes and courses", () => { 8 | cy.task("setUpKartat", { config: "config-01" }) 9 | cy.visit("http://localhost/rg2/#388&course=2,5&route=50002,50009") 10 | cy.wait("@event") 11 | cy.get("#course-tab").should("not.be.disabled") 12 | cy.get("#result-tab").should("not.be.disabled") 13 | cy.get("#draw-tab").should("not.be.disabled") 14 | cy.get("#rg2-event-title").should("contain", "2022-06-04 Highfield Park Saturday Series") 15 | cy.get("#btn-about").click() 16 | cy.get("#rg2-event-stats").should("not.be.empty").and("contain", "Event statistics") 17 | cy.get("#rg2-right-info-panel").find(".btn-close").click() 18 | }) 19 | it.skip("should handle an invalid URL ID and just display the event list", () => { 20 | cy.task("setUpKartat", { config: "config-01" }) 21 | cy.visit("http://localhost/rg2/#100xyz") 22 | cy.wait("@events") 23 | cy.get("#course-tab").should("be.disabled") 24 | cy.get("#result-tab").should("be.disabled") 25 | cy.get("#draw-tab").should("be.disabled") 26 | }) 27 | 28 | it("reports an invalid kartat directory", () => { 29 | cy.task("setUpKartat", { config: "config-02", php: "config-02" }) 30 | cy.visit("http://localhost/rg2/") 31 | cy.get("body").should("be.visible").and("contain", "Routegadget 2: Kartat directory").and("contain", "not found") 32 | }) 33 | // cypress back/forward broken for # links? https://github.com/cypress-io/cypress/issues/896 34 | it.skip("should load another event and go backwards and forwards", () => { 35 | cy.visit("http://localhost/rg2/#380") 36 | cy.wait("@event") 37 | cy.get("#rg2-event-title").should("contain", "2021-12-26 Trent Park Boxing Day Score") 38 | cy.go("back") 39 | cy.wait("@event") 40 | cy.get("#rg2-event-title").should("contain", "2022-06-04 Highfield Park Saturday Series") 41 | cy.go("forward") 42 | cy.wait("@event") 43 | cy.get("#rg2-event-title").should("contain", "2021-12-26 Trent Park Boxing Day Score") 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /cypress/e2e/1-core/load-event-no-results.cy.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable cypress/no-unnecessary-waiting */ 2 | describe("Load normal event", { testIsolation: false }, () => { 3 | before(() => { 4 | cy.task("setUpKartat", { config: "config-01" }) 5 | }) 6 | beforeEach(() => { 7 | cy.intercept("rg2api.php?type=event&id=*").as("event") 8 | }) 9 | it("should load a non-georeferenced event with no results", () => { 10 | cy.visit("http://localhost/rg2/#129") 11 | cy.wait("@event") 12 | cy.get("#course-tab").should("not.be.disabled") 13 | cy.get("#result-tab").should("not.be.disabled") 14 | cy.get("#draw-tab").should("not.be.disabled") 15 | cy.get("#rg2-event-title").should("contain", "2013-06-04 Herts ARC 2013 Race 5: Jersey Farm") 16 | }) 17 | it("should show a course", () => { 18 | cy.get("#course-tab").click() 19 | cy.get("#rg2-course-table .showcourse[data-courseid='1'").click() 20 | }) 21 | it("should show routes and replay", () => { 22 | cy.get("#rg2-course-table .alltracks").click() 23 | cy.get("#rg2-course-table .allcoursetracksreplay").click() 24 | }) 25 | 26 | it("measures non-georeferenced things", () => { 27 | // non-georeferenced map in pixels 28 | cy.get("#btn-measure").click() 29 | // draw A 30 | cy.get("#rg2-map-canvas").click(600, 200) 31 | cy.get(".rg2-overlay-table").should("contain", "0px") 32 | cy.get("#rg2-map-canvas").click(700, 200) 33 | cy.get(".rg2-overlay-table").should("contain", "141px") 34 | cy.get("#rg2-map-canvas").dblclick(800, 300) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /cypress/e2e/1-core/results.cy.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable cypress/no-unnecessary-waiting */ 2 | describe("looking at results", { testIsolation: false }, () => { 3 | before(() => { 4 | cy.task("setUpKartat", { config: "config-01" }) 5 | }) 6 | it("should load a georeferenced Highfield Park event", () => { 7 | cy.intercept("rg2api.php?type=event&id=*").as("event") 8 | cy.visit("http://localhost/rg2/#388") 9 | cy.wait("@event") 10 | }) 11 | it("displays results", () => { 12 | cy.get("#result-tab").click() 13 | cy.get(".accordion button div[data-courseid='1']").click() 14 | }) 15 | it("filters results by name", () => { 16 | cy.get("[data-courseid='1'][data-runners='41'").should("be.visible") 17 | cy.get("#rg2-result-search input").type("e") 18 | cy.get("[data-courseid='1'][data-runners='33'").should("be.visible") 19 | cy.get("#rg2-result-search input").type("r") 20 | cy.get("[data-courseid='1'][data-runners='10'").should("be.visible") 21 | cy.get("#rg2-result-search input").type("r") 22 | cy.get("[data-courseid='1'][data-runners='1'").should("be.visible") 23 | cy.get("#rg2-result-search input").type("x") 24 | cy.get("[data-courseid='1'][data-runners='0'").should("be.visible") 25 | cy.get("#rg2-result-search input").clear() 26 | cy.get("[data-courseid='1'][data-runners='41'").should("be.visible") 27 | }) 28 | it("displays a GPS route", () => { 29 | cy.get("#rg2-result-table .showcourse[data-courseid='1'").click() 30 | cy.get(".showtrack[data-id='50002']").check() 31 | cy.get("#btn-settings").click() 32 | cy.get("#chk-show-GPS-speed").scrollIntoView().check() 33 | cy.get("#chk-show-GPS-speed").check() 34 | cy.get("#btn-settings").click() 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /cypress/e2e/1-core/touch.cy.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable cypress/no-unnecessary-waiting */ 2 | describe("Load normal event", { testIsolation: false }, () => { 3 | before(() => { 4 | cy.task("setUpKartat", { config: "config-01" }) 5 | }) 6 | it("should load a normal event from the results tab", () => { 7 | cy.intercept("rg2api.php?type=events*").as("events") 8 | cy.visit("http://localhost/rg2/") 9 | cy.wait("@events") 10 | cy.intercept("rg2api.php?type=event&id=*").as("event") 11 | cy.get("#rg2-event-table > tr[data-kartatid='388']").click() 12 | cy.wait("@event") 13 | }) 14 | it("should allow touch drag", () => { 15 | cy.get("#rg2-map-canvas").trigger("touchstart", { 16 | touches: [{ pageX: 200, pageY: 50 }] 17 | }) 18 | cy.get("#rg2-map-canvas").trigger("touchmove", { 19 | touches: [{ pageX: 300, pageY: 150 }] 20 | }) 21 | cy.get("#rg2-map-canvas").trigger("touchend", { 22 | touches: [{ pageX: 300, pageY: 150 }] 23 | }) 24 | }) 25 | it("should allow touch zoom in", () => { 26 | cy.get("#rg2-map-canvas").trigger("touchstart", { 27 | touches: [ 28 | { pageX: 200, pageY: 50 }, 29 | { pageX: 600, pageY: 50 } 30 | ] 31 | }) 32 | cy.get("#rg2-map-canvas").trigger("touchmove", { 33 | touches: [ 34 | { pageX: 200, pageY: 50 }, 35 | { pageX: 500, pageY: 50 } 36 | ] 37 | }) 38 | cy.get("#rg2-map-canvas").trigger("touchmove", { 39 | touches: [ 40 | { pageX: 200, pageY: 50 }, 41 | { pageX: 400, pageY: 50 } 42 | ] 43 | }) 44 | cy.get("#rg2-map-canvas").trigger("touchmove", { 45 | touches: [ 46 | { pageX: 200, pageY: 50 }, 47 | { pageX: 300, pageY: 50 } 48 | ] 49 | }) 50 | cy.get("#rg2-map-canvas").trigger("touchend", { 51 | touches: [ 52 | { pageX: 200, pageY: 50 }, 53 | { pageX: 300, pageY: 50 } 54 | ] 55 | }) 56 | }) 57 | it("should allow touch zoom out", () => { 58 | cy.get("#rg2-map-canvas").trigger("touchstart", { 59 | touches: [ 60 | { pageX: 200, pageY: 450 }, 61 | { pageX: 300, pageY: 50 } 62 | ] 63 | }) 64 | cy.get("#rg2-map-canvas").trigger("touchmove", { 65 | touches: [ 66 | { pageX: 200, pageY: 450 }, 67 | { pageX: 400, pageY: 50 } 68 | ] 69 | }) 70 | cy.get("#rg2-map-canvas").trigger("touchmove", { 71 | touches: [ 72 | { pageX: 200, pageY: 450 }, 73 | { pageX: 500, pageY: 50 } 74 | ] 75 | }) 76 | cy.get("#rg2-map-canvas").trigger("touchmove", { 77 | touches: [ 78 | { pageX: 200, pageY: 450 }, 79 | { pageX: 600, pageY: 50 } 80 | ] 81 | }) 82 | cy.get("#rg2-map-canvas").trigger("touchend", { 83 | touches: [ 84 | { pageX: 200, pageY: 450 }, 85 | { pageX: 600, pageY: 50 } 86 | ] 87 | }) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /cypress/e2e/3-gps/gps-adjustment.cy.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable cypress/no-unnecessary-waiting */ 2 | describe("adjusting GPS routes", { testIsolation: false }, () => { 3 | let mapHeightPixels = 870 4 | // screen is 1000 x 660, header is 66 px of that so canvas is... 5 | const canvasHeightPixels = 594 6 | const leftPanelOffset = 480 7 | const ptX = (x) => (x * canvasHeightPixels) / mapHeightPixels + leftPanelOffset 8 | const ptY = (y) => (y * canvasHeightPixels) / mapHeightPixels 9 | const clickAt = (x, y, options = { button: 0 }) => { 10 | cy.get("#rg2-map-canvas").trigger("mousedown", ptX(x), ptY(y), options) 11 | cy.get("#rg2-map-canvas").trigger("mouseup", ptX(x), ptY(y), options) 12 | } 13 | const drag = (x1, y1, x2, y2, options = { button: 0 }) => { 14 | cy.get("#rg2-map-canvas").trigger("mousedown", ptX(x1), ptY(y1), options) 15 | cy.get("#rg2-map-canvas").trigger("mousemove", ptX(x2), ptY(y2)) 16 | cy.get("#rg2-map-canvas").trigger("mouseup", ptX(x2), ptY(y2), options) 17 | } 18 | before(() => { 19 | cy.task("setUpKartat", { config: "config-01" }) 20 | }) 21 | it("should load an event with no results", () => { 22 | cy.intercept("rg2api.php?type=event&id=*").as("event") 23 | cy.visit("http://localhost/rg2/#129") 24 | cy.wait("@event") 25 | }) 26 | it("allows you to load a GPX file", () => { 27 | cy.get("#draw-tab").click() 28 | cy.get("#rg2-select-course").select("45 minute score") 29 | cy.get("#rg2-name-entry").clear() 30 | cy.get("#rg2-name-entry").type("Peter Errington") 31 | cy.get("#rg2-time-entry").clear() 32 | cy.get("#rg2-time-entry").type("45:43") 33 | cy.get("#rg2-load-gps-file").selectFile("./cypress/fixtures/data/Jersey_Farm_Street_O.gpx") 34 | }) 35 | it("drags the whole track and can undo", () => { 36 | // x, y locations are empirically correct to make what follows do what it should 37 | drag(350, 700, 300, 600) 38 | cy.get("#btn-undo-gps-adjust").click() 39 | // drag with right click drags map and track 40 | drag(350, 700, 300, 600, { button: 2 }) 41 | drag(300, 600, 350, 700, { button: 2 }) 42 | // locks map and track and drags them both 43 | cy.get("#chk-move-all").check() 44 | drag(350, 700, 300, 600) 45 | drag(300, 600, 350, 700) 46 | cy.get("#chk-move-all").uncheck() 47 | }) 48 | it("adds, deletes and locks a handle", () => { 49 | // left click to add a handle 50 | clickAt(345, 739) 51 | // right click to delete a handle 52 | clickAt(345, 739, { button: 2, which: 3 }) 53 | // right click to add a handle 54 | clickAt(345, 739, { button: 2, which: 3 }) 55 | drag(345, 739, 340, 513) 56 | // second click to lock handle 57 | clickAt(340, 513) 58 | // left click to unlock a locked handle 59 | clickAt(340, 513) 60 | // click to lock handle 61 | clickAt(340, 513) 62 | // can also add with a right click 63 | clickAt(-20, 24, { button: 2, which: 3 }) 64 | drag(-20, 24, 168, 278) 65 | clickAt(168, 278) 66 | }) 67 | it("drags an existing handle around a locked point", () => { 68 | drag(350, 316, 400, 320) 69 | cy.get("#btn-undo-gps-adjust").click() 70 | }) 71 | it("adds and drags a handle between two locked points", () => { 72 | clickAt(323, 382) 73 | drag(323, 382, 340, 400) 74 | drag(340, 400, 323, 382) 75 | }) 76 | it("saves the adjusted route", () => { 77 | cy.get("#btn-save-gps-route").click() 78 | cy.closeWarningDialog("Your route has been saved") 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /cypress/e2e/3-gps/gps.cy.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable cypress/no-unnecessary-waiting */ 2 | describe("Loading GPS routes", { testIsolation: false }, () => { 3 | before(() => { 4 | cy.task("setUpKartat", { config: "config-01" }) 5 | }) 6 | beforeEach(() => { 7 | cy.intercept("rg2api.php?type=event&id=*").as("event") 8 | }) 9 | it("loads a georeferenced Highfield Park event", () => { 10 | cy.visit("http://localhost/rg2/#388") 11 | cy.wait("@event") 12 | }) 13 | it("allows you to load a GPX file", () => { 14 | cy.get("#draw-tab").click() 15 | cy.get("#rg2-select-course").select("Short Blue") 16 | cy.get("#rg2-select-name").select("36:22 Simon Errington") 17 | cy.get("#rg2-load-gps-file").selectFile("./cypress/fixtures/data/Highfield_Park_Short_blue.gpx") 18 | cy.get("#btn-save-gps-route").click() 19 | cy.wait("@event") 20 | cy.closeWarningDialog("Your route has been saved") 21 | }) 22 | it("allows you to autofit a GPX file", () => { 23 | cy.get("#draw-tab").click() 24 | cy.get("#rg2-select-course").select("Orange") 25 | cy.get("#rg2-select-name").select("32:38 Peter Errington") 26 | cy.get("#rg2-load-gps-file").selectFile("./cypress/fixtures/data/Highfield_Park_Orange.gpx") 27 | cy.get("#btn-autofit-gps").click() 28 | cy.get("#btn-offset-plus").click() 29 | cy.get("#btn-offset-plus").click() 30 | cy.get("#btn-offset-minus").click() 31 | cy.get("#rg2-offset-value").clear() 32 | cy.get("#rg2-offset-value").type("x") 33 | cy.get("#rg2-offset-value").clear() 34 | cy.get("#rg2-offset-value").type("15") 35 | cy.get("#rg2-offset-value").clear() 36 | cy.get("#rg2-offset-value").type("0") 37 | cy.get("#btn-save-gps-route").click() 38 | cy.wait("@event") 39 | cy.closeWarningDialog("Your route has been saved") 40 | }) 41 | it("displays the new route", () => { 42 | cy.get("#result-tab").click() 43 | cy.get(".accordion button div[data-courseid='1']").click() 44 | }) 45 | it("deletes the new route", () => { 46 | cy.get(".deleteroute[data-resultidx='49']").click() 47 | cy.closeModal("Confirm route delete", ".modal-footer button[data-bs-dismiss='modal'") 48 | cy.get(".deleteroute[data-resultidx='49']").click() 49 | cy.closeModal("Confirm route delete") 50 | cy.wait("@event") 51 | cy.closeWarningDialog("Route deleted") 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /cypress/e2e/4-replay/replay.cy.js: -------------------------------------------------------------------------------- 1 | describe("Replay", { testIsolation: false }, () => { 2 | before(() => { 3 | cy.task("setUpKartat", { config: "config-01" }) 4 | }) 5 | beforeEach(() => { 6 | cy.intercept("rg2api.php?type=event&id=*").as("event") 7 | }) 8 | it("should load a normal Highfield Park event", () => { 9 | cy.visit("http://localhost/rg2/#388") 10 | cy.wait("@event") 11 | }) 12 | it("selects a runner to replay", () => { 13 | cy.get("#result-tab").click() 14 | cy.get(".accordion-button").eq(0).click() 15 | cy.get(".showreplay[data-id='1']").click() 16 | }) 17 | it("changes the track colour", () => { 18 | cy.get("#rg2-track-names").within(() => { 19 | cy.get("input[data-rawid='1']").invoke("val", "#ff0000").trigger("change") 20 | }) 21 | }) 22 | it("select all Orange runners to replay one at a time", () => { 23 | // display Orange results 24 | cy.get(".accordion-button").eq(2).click() 25 | cy.get("#table-3 .showreplay").click({ multiple: true }) 26 | }) 27 | it("selects all runners on White", () => { 28 | // display Orange results 29 | cy.get(".accordion-button").eq(4).click() 30 | cy.get("#table-5 .allcoursereplay").click() 31 | }) 32 | it("should load a Trent Park score event", () => { 33 | cy.visit("http://localhost/rg2/#380") 34 | cy.wait("@event") 35 | }) 36 | it("selects runners to replay", () => { 37 | cy.get("#result-tab").click() 38 | cy.get(".accordion-button").eq(0).click() 39 | // runner with a GPS route 40 | cy.get(".showreplay[data-id='50007']").click() 41 | // runner with a drawn route 42 | cy.get(".showreplay[data-id='38']").click() 43 | // runner with no route 44 | cy.get(".showreplay[data-id='3']").click() 45 | cy.get("#btn-start-stop").click() 46 | cy.wait(1000) 47 | cy.get("#btn-start-stop").click() 48 | }) 49 | it("should load a Jersey Farm event with no split times", () => { 50 | cy.visit("http://localhost/rg2/#129") 51 | cy.wait("@event") 52 | }) 53 | it("selects runners to replay", () => { 54 | cy.get("#result-tab").click() 55 | cy.get(".accordion-button").eq(0).click() 56 | // runner with a GPS route 57 | cy.get(".showreplay[data-id='50001']").click() 58 | // runner with a drawn route 59 | cy.get(".showreplay[data-id='2']").click() 60 | cy.get("#btn-start-stop").click() 61 | cy.wait(1000) 62 | cy.get("#btn-start-stop").click() 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /cypress/e2e/5-manager/create-event-1.cy.js: -------------------------------------------------------------------------------- 1 | import manager from "../../fixtures/validmanager.json" 2 | describe("Event creation 1", { testIsolation: false }, () => { 3 | before(() => { 4 | cy.task("setUpKartat", { config: "config-01" }) 5 | }) 6 | it("should allow login", () => { 7 | cy.intercept("rg2api.php?type=events*").as("events") 8 | cy.intercept("rg2api.php?type=login*").as("login") 9 | cy.visit("http://localhost/rg2/?manage") 10 | cy.wait("@events") 11 | cy.get("#rg2-user-name").clear().type(manager.name) 12 | cy.get("#rg2-password").clear().type(manager.password) 13 | cy.get("#btn-login").click() 14 | cy.wait("@login") 15 | }) 16 | it("should warn about incorrect details", () => { 17 | cy.get("#manage-create-tab").click() 18 | cy.get("#btn-create-event").click() 19 | cy.closeWarningDialog("Event name is not valid") 20 | cy.get("#rg2-event-name").clear().type("Highfield Park Saturday Series") 21 | cy.get("#btn-create-event").click() 22 | cy.closeWarningDialog("No map selected") 23 | cy.get("#rg2-select-map").select("402: Highfield") 24 | cy.get("#btn-create-event").click() 25 | cy.closeWarningDialog("Club name is not valid") 26 | cy.get("#rg2-select-club-name").clear().type("HH") 27 | cy.get("#btn-create-event").click() 28 | cy.closeWarningDialog("Date is not valid") 29 | cy.get("#rg2-select-event-date").clear().type("2022-06-04") 30 | cy.get("#rg2-enter-event-comments").clear().type("Highfield Park normal event") 31 | cy.get("#btn-create-event").click() 32 | cy.closeWarningDialog("Event level is not valid") 33 | cy.get("#rg2-select-event-level").select("L") 34 | cy.get("#btn-create-event").click() 35 | cy.closeWarningDialog("No course information") 36 | cy.selectCourseFile("miltonriggIOFV2results.xml", "File is not a valid XML course file") 37 | cy.selectCourseFile("miltonriggIOFV3courses.xml", "Your course file does not match the map co-ordinates") 38 | cy.closeModal("Course details") 39 | cy.selectCourseFile("Highfield-2022-06-04-v7.xml") 40 | cy.get("#btn-create-event").click() 41 | cy.closeWarningDialog("No results information") 42 | cy.selectResultsFile("ellenbrook.jgw", "Results file type is not recognised") 43 | cy.selectResultsFile("miltonriggIOFV1results.xml", "Invalid IOF file format. Version 1.0 not supported") 44 | cy.selectResultsFile( 45 | "miltonriggIOFV2invalidresults.xml", 46 | "File is not a valid XML results file. ResultList element missing" 47 | ) 48 | cy.selectResultsFile("IOFV2resultsparseerror.xml", "Error processing XML file") 49 | cy.selectResultsFile("IOFV3resultsparseerror.xml", "Error processing XML file") 50 | cy.selectResultsFile("miltonriggIOFV3courses.xml", "File is not a valid XML results file") 51 | cy.selectResultsFile("miltonriggIOFV2results.xml") 52 | cy.selectResultsFile("miltonriggIOFV3results.xml") 53 | cy.selectResultsFile("spklasse.csv") 54 | cy.selectResultsFile("highfield4RG.csv") 55 | }) 56 | it("should allow you to cancel creating an event", () => { 57 | cy.get("#btn-create-event").click() 58 | cy.closeModal("Confirm event creation", ".modal-footer button[data-bs-dismiss='modal'") 59 | }) 60 | it("should create an event", () => { 61 | cy.createEvent() 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /cypress/e2e/5-manager/create-event-3.cy.js: -------------------------------------------------------------------------------- 1 | import manager from "../../fixtures/validmanager.json" 2 | describe("Event creation 3", { testIsolation: false }, () => { 3 | before(() => { 4 | cy.task("setUpKartat", { config: "config-01" }) 5 | }) 6 | beforeEach(() => { 7 | cy.intercept("rg2api.php?type=event&id=*").as("event") 8 | cy.intercept("rg2api.php?type=events*").as("events") 9 | cy.intercept("rg2api.php?type=login*").as("login") 10 | cy.intercept("rg2api.php?type=maps*").as("maps") 11 | }) 12 | it("starts with an empty kartat directory", () => { 13 | cy.task("setUpKartat", { config: "config-03", php: "config-03" }) 14 | cy.visit("http://localhost/rg2/") 15 | cy.wait("@events").its("response.body.data.events").should("have.length", 0) 16 | cy.get("#rg2-event-title").should("be.visible").and("contain", "Routegadget 2") 17 | }) 18 | it("can start with a defined language", () => { 19 | cy.get("#event-tab-label").should("contain", "Compétition") 20 | }) 21 | it("lets you log in from the About dialog", () => { 22 | cy.get("#btn-about").click() 23 | cy.visit("http://localhost/rg2/?manage") 24 | cy.wait("@events") 25 | cy.get("#rg2-user-name").clear().type(manager.name) 26 | cy.get("#rg2-password").clear().type(manager.password) 27 | cy.get("#btn-login").click() 28 | cy.wait("@login") 29 | }) 30 | it("should add a georeferenced OOM map", () => { 31 | cy.get("#manage-map-tab").click() 32 | cy.get("#rg2-map-name").clear().type("Highfield Park OOM") 33 | cy.get("#rg2-load-map-file").selectFile("./cypress/fixtures/data/oom_636ca9e43ca63.jpg") 34 | cy.get("#rg2-load-georef-file").selectFile("./cypress/fixtures/data/oom_636ca9e43ca63.jgw") 35 | cy.get("#rg2-georef-type").select("Google EPSG:900913") 36 | cy.get("#btn-add-map").click() 37 | cy.closeModal("Confirm new map") 38 | cy.wait("@maps") 39 | cy.closeWarningDialog("Map added") 40 | }) 41 | it("should create an OOM event with KML course file", () => { 42 | cy.get("#manage-create-tab").click() 43 | cy.get("#rg2-event-name").clear().type("Highfield Park OOM") 44 | cy.get("#rg2-select-map").select("1: Highfield Park OOM") 45 | cy.get("#rg2-select-club-name").clear().type("HH") 46 | cy.get("#rg2-select-event-date").clear().type("2022-11-10") 47 | cy.get("#rg2-enter-event-comments").clear() 48 | cy.get("#rg2-select-event-level").select("T") 49 | cy.get("#chk-no-results").click() 50 | cy.selectCourseFile("oom_636ca9e43ca63.kml") 51 | cy.createEvent() 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /cypress/e2e/5-manager/edit-event.cy.js: -------------------------------------------------------------------------------- 1 | import manager from "../../fixtures/validmanager.json" 2 | describe("Event editing", { testIsolation: false }, () => { 3 | before(() => { 4 | cy.task("setUpKartat", { config: "config-01" }) 5 | }) 6 | beforeEach(() => { 7 | cy.intercept("rg2api.php?type=event&id=*").as("event") 8 | cy.intercept("rg2api.php?type=events*").as("events") 9 | }) 10 | it("should allow login", () => { 11 | cy.intercept("rg2api.php?type=login*").as("login") 12 | cy.visit("http://localhost/rg2/?manage") 13 | cy.wait("@events") 14 | cy.get("#rg2-user-name").clear().type(manager.name) 15 | cy.get("#rg2-password").clear().type(manager.password) 16 | cy.get("#btn-login").click() 17 | cy.wait("@login") 18 | }) 19 | it("should load an event", () => { 20 | cy.get("#manage-edit-tab").click() 21 | cy.get("#rg2-edit-event-selected").select("129: 2013-06-04: Herts ARC 2013 Race 5: Jersey Farm") 22 | cy.wait("@event") 23 | }) 24 | it("should update an event", () => { 25 | cy.get("#chk-edit-read-only").click() 26 | cy.get("#btn-update-event").click() 27 | cy.closeModal("Confirm event update", ".modal-footer button[data-bs-dismiss='modal'") 28 | cy.get("#btn-update-event").click() 29 | cy.closeModal("Confirm event update") 30 | cy.wait("@events") 31 | cy.closeWarningDialog("Event updated") 32 | }) 33 | it("should delete a route", () => { 34 | cy.get("#manage-edit-tab").click() 35 | cy.get("#rg2-edit-event-selected").select("129: 2013-06-04: Herts ARC 2013 Race 5: Jersey Farm") 36 | cy.wait("@event") 37 | cy.get("#rg2-route-selected").select("50001: GPS Simon Errington on 45 minute score") 38 | cy.get("#btn-delete-route").click() 39 | cy.closeModal("Confirm route delete", ".modal-footer button[data-bs-dismiss='modal'") 40 | cy.get("#btn-delete-route").click() 41 | cy.closeModal("Confirm route delete") 42 | cy.wait("@events") 43 | cy.closeWarningDialog("Route deleted") 44 | }) 45 | it("should delete an event", () => { 46 | cy.get("#manage-edit-tab").click() 47 | cy.get("#rg2-edit-event-selected").select("129: 2013-06-04: Herts ARC 2013 Race 5: Jersey Farm") 48 | cy.wait("@event") 49 | cy.get("#btn-delete-event").click() 50 | cy.closeModal("Confirm event delete", ".modal-footer button[data-bs-dismiss='modal'") 51 | cy.get("#btn-delete-event").click() 52 | cy.closeModal("Confirm event delete") 53 | cy.wait("@events") 54 | cy.closeWarningDialog("Event deleted") 55 | }) 56 | it("should select a score event to edit", () => { 57 | cy.get("#manage-edit-tab").click() 58 | cy.get("#rg2-edit-event-selected").select("380: 2021-12-26: Trent Park Boxing Day Score") 59 | cy.wait("@event") 60 | cy.get("#rg2-edit-event-comments").clear().type("New comments") 61 | cy.get("#btn-update-event").click() 62 | cy.closeModal("Confirm event update") 63 | cy.wait("@events") 64 | cy.closeWarningDialog("Event updated") 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /cypress/e2e/5-manager/login.cy.js: -------------------------------------------------------------------------------- 1 | describe("Manager login", { testIsolation: false }, () => { 2 | before(() => { 3 | cy.task("setUpKartat", { config: "config-01" }) 4 | }) 5 | it("should reject invalid login", () => { 6 | cy.intercept("rg2api.php?type=events*").as("events") 7 | cy.intercept("rg2api.php?type=event&id=*").as("event") 8 | cy.intercept("rg2api.php?type=login*").as("login") 9 | cy.visit("http://localhost/rg2/?manage") 10 | cy.wait("@events") 11 | cy.get("#rg2-event-title").should("be.visible").and("contain", "Routegadget 2") 12 | // incorrect username 13 | cy.get("#rg2-user-name").clear().type("hhhhh") 14 | cy.get("#rg2-password").clear().type("00000") 15 | cy.get("#btn-login").click() 16 | cy.wait("@login") 17 | cy.closeWarningDialog("Login failed") 18 | // user name too short 19 | cy.get("#rg2-user-name").clear().type("hhh") 20 | cy.get("#rg2-password").clear().type("00000") 21 | cy.get("#btn-login").click() 22 | cy.closeWarningDialog("at least five characters") 23 | // password too short 24 | cy.get("#rg2-user-name").clear().type("hhhhh") 25 | cy.get("#rg2-password").clear().type("000") 26 | cy.get("#btn-login").click() 27 | cy.closeWarningDialog("at least five characters") 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /cypress/e2e/5-manager/manage-maps.cy.js: -------------------------------------------------------------------------------- 1 | import manager from "../../fixtures/validmanager.json" 2 | describe("Manage maps", { testIsolation: false }, () => { 3 | before(() => { 4 | cy.task("setUpKartat", { config: "config-01" }) 5 | }) 6 | beforeEach(() => { 7 | cy.intercept("rg2api.php?type=maps*").as("maps") 8 | }) 9 | it("should allow login", () => { 10 | cy.intercept("rg2api.php?type=events*").as("events") 11 | cy.intercept("rg2api.php?type=login*").as("login") 12 | cy.visit("http://localhost/rg2/?manage") 13 | cy.wait("@events") 14 | cy.get("#rg2-user-name").clear().type(manager.name) 15 | cy.get("#rg2-password").clear().type(manager.password) 16 | cy.get("#btn-login").click() 17 | cy.wait("@login") 18 | cy.wait("@maps").its("response.body.data.maps").should("have.length", "11") 19 | }) 20 | it("should allow map upload", () => { 21 | cy.get("#manage-map-tab").click() 22 | cy.get("#rg2-map-name").clear().type("Ellenbrook non-georef") 23 | cy.get("#rg2-load-map-file").selectFile("./cypress/fixtures/data/verybigmap-poorquality.jpg") 24 | cy.closeWarningDialog("Oversized map upload") 25 | cy.get("#rg2-load-map-file").selectFile("./cypress/fixtures/data/ellenbrook.jpg") 26 | // cancel 27 | cy.get("#btn-add-map").click() 28 | cy.closeModal("Confirm new map", ".modal-footer button[data-bs-dismiss='modal'") 29 | // add map 30 | cy.get("#btn-add-map").click() 31 | cy.closeModal("Confirm new map") 32 | cy.wait("@maps").its("response.body.data.maps").should("have.length", "12") 33 | cy.closeWarningDialog("Map added") 34 | }) 35 | it("should allow you to delete one unused map", () => { 36 | cy.get("#manage-delete-map-tab").click() 37 | cy.get("#btn-delete-unused-maps").click() 38 | cy.closeWarningDialog("No maps selected") 39 | cy.get("#rg2-unused-maps [data-map-id='403']").check() 40 | // cancel 41 | cy.get("#btn-delete-unused-maps").click() 42 | cy.closeModal("Confirm map deletion", ".modal-footer button[data-bs-dismiss='modal'") 43 | // delete map 44 | cy.get("#btn-delete-unused-maps").click() 45 | cy.closeModal("Confirm map deletion") 46 | cy.wait("@maps").its("response.body.data.maps").should("have.length", "11") 47 | cy.closeWarningDialog("Maps deleted") 48 | }) 49 | it("should allow you to delete all unused maps", () => { 50 | cy.get("#manage-delete-map-tab").click() 51 | cy.get("#rg2-unused-maps [data-map-id='404']").check() 52 | cy.get("#rg2-unused-maps [data-map-id='300']").check() 53 | cy.get("#btn-delete-unused-maps").click() 54 | cy.closeModal("Confirm map deletion") 55 | cy.intercept("rg2api.php?type=maps*").as("maps") 56 | cy.wait("@maps").its("response.body.data.maps").should("have.length", "9") 57 | cy.closeWarningDialog("Maps deleted") 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /cypress/e2e/run-e2e-tests.cjs: -------------------------------------------------------------------------------- 1 | const cypress = require("cypress") 2 | const specs = [ 3 | "cypress/e2e/1-core/core.cy.js", 4 | "cypress/e2e/1-core/load-normal-event.cy.js", 5 | "cypress/e2e/1-core/load-score-event.cy.js", 6 | "cypress/e2e/1-core/load-event-no-results.cy.js", 7 | "cypress/e2e/1-core/results.cy.js", 8 | "cypress/e2e/1-core/touch.cy.js", 9 | "cypress/e2e/1-core/extras.cy.js", 10 | "cypress/e2e/2-draw/draw.cy.js", 11 | "cypress/e2e/3-gps/gps.cy.js", 12 | "cypress/e2e/3-gps/gps-adjustment.cy.js", 13 | "cypress/e2e/4-replay/replay.cy.js", 14 | "cypress/e2e/5-manager/login.cy.js", 15 | "cypress/e2e/5-manager/manage-maps.cy.js", 16 | "cypress/e2e/5-manager/create-event-1.cy.js", 17 | "cypress/e2e/5-manager/create-event-2.cy.js", 18 | "cypress/e2e/5-manager/create-event-3.cy.js", 19 | "cypress/e2e/5-manager/edit-event.cy.js" 20 | ] 21 | cypress 22 | .run({ 23 | browser: "chrome", 24 | config: {}, 25 | spec: [specs].join(","), 26 | env: {} 27 | }) 28 | .then((results) => { 29 | //console.log(results) 30 | }) 31 | .catch((err) => { 32 | console.error(err) 33 | }) 34 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/cypress/fixtures/config-01/1.jpg -------------------------------------------------------------------------------- /cypress/fixtures/config-01/126.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/cypress/fixtures/config-01/126.jpg -------------------------------------------------------------------------------- /cypress/fixtures/config-01/394.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/cypress/fixtures/config-01/394.gif -------------------------------------------------------------------------------- /cypress/fixtures/config-01/394.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/cypress/fixtures/config-01/394.jpg -------------------------------------------------------------------------------- /cypress/fixtures/config-01/402.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/cypress/fixtures/config-01/402.gif -------------------------------------------------------------------------------- /cypress/fixtures/config-01/402.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/cypress/fixtures/config-01/402.jpg -------------------------------------------------------------------------------- /cypress/fixtures/config-01/exclude_388.txt: -------------------------------------------------------------------------------- 1 | 1|2|7,60 2 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/hajontakanta_388.txt: -------------------------------------------------------------------------------- 1 | 1|Short Blue|100_123_104_126_103_134_102_131_116_122_133_128_124_108_114_125_130 2 | 2|Light Green|125_108_114_131_100_127_118_110_129_113_104_133_128_124_132_130 3 | 3|Orange|113_129_118_104_110_127_121_133_105_131_102_132_115_114_112_119_111_130 4 | 4|Yellow|126_113_104_127_121_107_123_110_129_111_120_105_112_114_115_119_117_102_130 5 | 5|White|101_126_109_113_110_123_118_107_111_120_106_117_130 6 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/kartat.txt: -------------------------------------------------------------------------------- 1 | 1|Whippendell Woods 2 | 3|Ashridge 3 | 126|Jersey Farm 4 | 300| 5 | 317|Jersey Farm|0|-0.31369094889682|0|51.778208785493|2260|-0.29324735785177|1618|51.768675129194|2260|-0.29289897852655|0|51.777908693825 6 | 359|St Albans NE Street-O 2019|0|-0.31869169570657|0|51.776992054769|842|-0.27557415231975|596|51.758104490795|842|-0.27557415231975|0|51.776992054769 7 | 394|TP2021|0|-0.15446741971132|0|51.668384294068|2292|-0.11241877500071|1652|51.649504137272|2292|-0.11238675958519|0|51.668364114754 8 | 397|Ashridge|0|-0.59770167052577|0|51.809038298371|2289|-0.55568009898721|3191|51.772552030612|2289|-0.55554186311158|0|51.808984210042 9 | 401|WGC|0|-0.21579756575005|0|51.817495717811|2740|-0.19051957311708|3333|51.798494275862|2740|-0.19056130346751|0|51.817519646066 10 | 402|Highfield|0|-0.30496890369033|0|51.749261373413|1612|-0.29063746035414|2272|51.73608083977|1612|-0.29014803646268|0|51.74904668645 11 | 403|HABS|0|-0.3189380769621|0|51.660824976179|1283|-0.30696498278616|1835|51.650437187894|1283|-0.30716258931002|0|51.660911501161 12 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/keksi.txt: -------------------------------------------------------------------------------- 1 | YQD4EKvtPRoj71rpeMGL 2 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/kisat.txt: -------------------------------------------------------------------------------- 1 | 1|1|1|Whippendell SENILE|2006-12-09|Happy Herts|T| 2 | 2|3|1|Ashridge Regional Event|2007-01-21|Happy Herts|R|Control 107 failed and was replaced during the event so some split times to and from this control are estimated. The animation feature now works for everybody who was affected by this, rather than just stopping dead at the previous control. 3 | 3|2|1|Northaw Club Champs|2007-01-06|Happy Herts|L| 4 | 129|126|2|Herts ARC 2013 Race 5: Jersey Farm|2013-06-04|Happy Herts|L| 5 | 297|317|1|Jersey Farm Street-O|2018-06-19|HH|L| 6 | 330|353|1|Hatfield South Street-O|2019-06-25|HH|L|_ 7 | 348|298|3|Trent Park Boxing Day Score|2019-12-26|HH|R| 8 | 354|359|2|Marshalswick DIY-O|2020-06-30|HH|X| 9 | 380|394|3|Trent Park Boxing Day Score|2021-12-26|HH|L| 10 | 383|397|1|Ace of Herts: Ashridge South and East|2022-02-27|HH|N|Road crossing times have been taken off. 11 | 387|401|1|Welwyn Garden City Race 2022|2022-05-08|HH|R| 12 | 388|402|1|Highfield Park Saturday Series|2022-06-04|HH|I| 13 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/kommentit_1.txt: -------------------------------------------------------------------------------- 1 | 1|7| Chapman Peter||Oh Dear - Should be able to do better than this! No reflection on the area, however - excellent SENiLe venue. 2 | 2|17| Young Neville||Slow and confused start and got mixed up before 6 at jct just S of it. Not exactly an exciting revelation of a route, but I like RouteGadget! :) 3 | 3|37| Cheetham Charlotte||Type your comment 4 | 3|27| Parkes Laura||Type your comment 5 | 1|3| Barrable Nick||Type your comment 6 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/kommentit_129.txt: -------------------------------------------------------------------------------- 1 | 1|50001| GPS Simon Errington||Type your comment 2 | 1|2|Peter Errington|| 3 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/kommentit_380.txt: -------------------------------------------------------------------------------- 1 | 1|50012| GPS Simon Errington||Type your comment 2 | 1|50011| GPS Anthony Flick||Type your comment 3 | 1|50016| GPS Mark Adams||Type your comment 4 | 1|50036| GPS Sue Carter||Type your comment 5 | 1|50007| GPS David Frampton||Type your comment 6 | 1|50057| GPS Peter Errington||Type your comment 7 | 1|50005| GPS Sebastien Flesch||Type your comment 8 | 1|50009| GPS Jon Chandler||Type your comment 9 | 1|50056| GPS Viv Hodson||Type your comment 10 | 1|50008| GPS David Hodson||Type your comment 11 | 1|50004| GPS Tsz Chun Jason Wong||Type your comment 12 | 1|50027| GPS Nikolay Mikhaylyuk||Type your comment 13 | 1|50058| GPS Ian Byford||Type your comment 14 | 1|50065| GPS Catherine Galvin||Type your comment 15 | 1|50032| GPS Hedley Calderbank||Type your comment 16 | 1|50078| GPS Clive Hillier||Type your comment 17 | 1|50045| GPS Matthew Harden||Type your comment 18 | 1|50053| GPS Tony Harden||Type your comment 19 | 1|13|James Errington||Type your comment 20 | 1|38|Nigel Quinton||Type your comment 21 | 1|50014| GPS Stephen Borrill||Type your comment 22 | 1|44|Tegan Frampton||Type your comment 23 | 1|50020| GPS Jacob Stevens||Type your comment 24 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/kommentit_388.txt: -------------------------------------------------------------------------------- 1 | 1|50002| GPS Simon Errington||Type your comment 2 | 3|50075| GPS Peter Errington||Type your comment 3 | 1|50007| GPS Mark Adams||Type your comment 4 | 2|50058| GPS Helen Errington||Type your comment 5 | 2|50060| GPS John Duffield||Type your comment 6 | 3|50076| GPS Andrew Tang||Type your comment 7 | 1|50008| GPS David Frampton||Type your comment 8 | 1|50012| GPS Mike Bennett||Kindly organiser didn't DSQ me for running OOB after 11. It cost me time getting back into legal territory.#cr##nl#Other than that, tiny errors :#cr##nl#10 optimistic route out of 9#cr##nl#11 wrong side of fence#cr##nl#13 missed N route 9 | 1|50018| GPS David Heale||Type your comment 10 | 1|50009| GPS Ben Bardsley||Type your comment 11 | 1|50014| GPS Rachel Sequeira||Type your comment 12 | 1|50004| GPS Sebastien Flesch||Type your comment 13 | 2|50049| GPS Alice Elder||Type your comment 14 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/radat_1.txt: -------------------------------------------------------------------------------- 1 | 1|1|Navy|2;567;-128;0;0N1;580;-333;0;0N4;576;-182;579;-314N1;496;-340;0;0N4;560;-335;515;-339N3;592;-358;1;0N1;507;-536;0;0N4;497;-360;505;-517N3;459;-332;2;0N1;635;-587;0;0N4;525;-544;616;-580N3;468;-559;3;0N1;653;-308;0;0N4;636;-568;651;-328N3;640;-617;4;0N1;401;-169;0;0N4;635;-299;418;-179N3;669;-304;5;0N1;249;-213;0;0N4;381;-175;268;-208N3;392;-153;6;0N1;149;-309;0;0N4;234;-227;163;-296N3;220;-200;7;0N1;574;-372;0;0N4;168;-312;554;-370N3;105;-325;8;0N1;570;-451;0;0N4;572;-392;571;-432N3;587;-365;9;0N1;323;-457;0;0N4;550;-452;342;-457N3;581;-477;10;0N1;370;-296;0;0N4;328;-438;364;-316N3;284;-481;11;0N1;216;-279;0;0N4;350;-294;235;-282N3;385;-291;12;0N1;216;-390;0;0N4;216;-299;215;-370N3;181;-269;13;0N1;385;-400;0;0N4;235;-392;365;-399N3;179;-416;14;0N1;545;-133;0;0N4;395;-383;534;-151N3;388;-431;15;0N3;548;-164;16;0N4;589;-148;561;-149N4;589;-148;576;-176N4;576;-176;561;-149N 2 | 2|1|Ochre|2;567;-128;0;0N1;591;-223;0;0N4;580;-182;586;-204N1;657;-372;0;0N4;599;-242;648;-354N3;547;-240;1;0N1;562;-580;0;0N4;648;-391;570;-562N3;677;-381;2;0N1;422;-452;0;0N4;547;-567;436;-466N3;556;-613;3;0N1;414;-370;0;0N4;420;-433;415;-390N3;380;-472;4;0N1;521;-302;0;0N4;430;-360;504;-313N3;372;-368;5;0N1;422;-269;0;0N4;502;-296;440;-276N3;541;-307;6;0N1;429;-176;0;0N4;423;-250;427;-196N3;382;-291;7;0N1;545;-133;0;0N4;447;-170;526;-140N3;390;-169;8;0N3;506;-126;9;0N4;586;-146;559;-152N4;586;-146;579;-176N4;579;-176;559;-152N 3 | 3|1|Olive|2;567;-128;0;0N1;653;-308;0;0N4;585;-180;643;-291N1;668;-543;0;0N4;654;-328;666;-524N3;672;-310;1;0N1;562;-580;0;0N4;649;-550;580;-574N3;683;-565;2;0N1;574;-372;0;0N4;563;-561;572;-392N3;530;-608;3;0N1;414;-370;0;0N4;554;-372;433;-371N3;586;-363;4;0N1;323;-457;0;0N4;399;-384;337;-444N3;389;-355;5;0N1;216;-390;0;0N4;306;-447;232;-401N3;314;-490;6;0N1;370;-296;0;0N4;233;-380;352;-307N3;171;-398;7;0N1;249;-213;0;0N4;353;-285;265;-225N3;390;-305;8;0N1;401;-169;0;0N4;268;-208;381;-175N3;204;-218;9;0N1;545;-133;0;0N4;420;-165;525;-138N3;380;-153;10;0N3;524;-117;11;0N4;581;-144;557;-157N4;581;-144;582;-175N4;582;-175;557;-157N 4 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/radat_129.txt: -------------------------------------------------------------------------------- 1 | 1|1|45 minute score|2;370;-332;0;0N4;356;-318;355;-346N4;356;-318;384;-332N4;384;-332;355;-346N 2 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/ratapisteet_1.txt: -------------------------------------------------------------------------------- 1 | 1|576;-162N580;-333N496;-340N507;-536N635;-587N653;-308N401;-169N249;-213N149;-309N574;-372N570;-451N323;-457N370;-296N216;-279N216;-390N385;-400N545;-133N567;-128N 2 | 2|576;-162N591;-223N657;-372N562;-580N422;-452N414;-370N521;-302N422;-269N429;-176N545;-133N567;-128N 3 | 3|576;-162N653;-308N668;-543N562;-580N574;-372N414;-370N323;-457N216;-390N370;-296N249;-213N401;-169N545;-133N567;-128N 4 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/ratapisteet_129.txt: -------------------------------------------------------------------------------- 1 | 1|370;-332N370;-332N 2 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/ratapisteet_388.txt: -------------------------------------------------------------------------------- 1 | 1|581;-890N929;-1004N1250;-1023N1173;-1213N542;-1027N128;-1631N276;-1994N732;-845N904;-752N876;-1619N501;-1550N1479;-670N1033;-330N430;-509N674;-366N1128;-489N863;-931N669;-867N603;-835N 2 | 2|581;-890N863;-931N674;-366N1128;-489N904;-752N929;-1004N1512;-1038N1271;-949N1102;-1073N1054;-942N917;-1053N1173;-1213N1479;-670N1033;-330N430;-509N699;-712N669;-867N603;-835N 3 | 3|581;-890N917;-1053N1054;-942N1271;-949N1173;-1213N1102;-1073N1512;-1038N1433;-818N1479;-670N1170;-701N904;-752N732;-845N699;-712N1038;-394N1128;-489N1157;-626N1035;-608N983;-930N669;-867N603;-835N 4 | 4|581;-890N542;-1027N917;-1053N1173;-1213N1512;-1038N1433;-818N1238;-922N1250;-1023N1102;-1073N1054;-942N983;-930N975;-788N1170;-701N1157;-626N1128;-489N1038;-394N1035;-608N687;-745N732;-845N669;-867N603;-835N 5 | 5|581;-890N499;-957N542;-1027N630;-1152N917;-1053N1102;-1073N1250;-1023N1271;-949N1238;-922N983;-930N975;-788N924;-676N687;-745N669;-867N603;-835N 6 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/rg2userinfo.txt: -------------------------------------------------------------------------------- 1 | $2y$10$/LCq29q6z5wFURZHFRG8aepg71TUp.Ofkcr6KogfslB.cGeovoH92 2 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/sarjat_1.txt: -------------------------------------------------------------------------------- 1 | 1|Navy 2 | 2|Ochre 3 | 3|Olive 4 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/sarjat_129.txt: -------------------------------------------------------------------------------- 1 | 1|45 minute score 2 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/sarjat_380.txt: -------------------------------------------------------------------------------- 1 | 1|1 hr Score 2 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/sarjat_388.txt: -------------------------------------------------------------------------------- 1 | 1|Short Blue 2 | 2|Light Green 3 | 3|Orange 4 | 4|Yellow 5 | 5|White 6 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/sarjojenkoodit_1.txt: -------------------------------------------------------------------------------- 1 | 1|START|205|187|189|201|199|200|198|191|193|182|188|197|194|192|190|183|FINISH 2 | 2|START|179|180|181|184|202|204|185|203|183|FINISH 3 | 3|START|199|186|181|193|202|188|192|197|198|200|183|FINISH 4 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/sarjojenkoodit_380.txt: -------------------------------------------------------------------------------- 1 | 1|STA1|110|111|112|113|114|115|116|117|118|119|120|121|122|123|124|125|126|127|128|129|130|131|132|133|134|135|136|137|138|139|FIN1 2 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/sarjojenkoodit_388.txt: -------------------------------------------------------------------------------- 1 | 1|STA1|100|123|104|126|103|134|102|131|116|122|133|128|124|108|114|125|130|FIN1 2 | 2|STA1|125|108|114|131|100|127|118|110|129|113|104|133|128|124|132|130|FIN1 3 | 3|STA1|113|129|118|104|110|127|121|133|105|131|102|132|115|114|112|119|111|130|FIN1 4 | 4|STA1|126|113|104|127|121|107|123|110|129|111|120|105|112|114|115|119|117|102|130|FIN1 5 | 5|STA1|101|126|109|113|110|123|118|107|111|120|106|117|130|FIN1 6 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/worldfile_388.txt: -------------------------------------------------------------------------------- 1 | 0.951919762736,0.033241770609,522329.0927,0.033241770609,-0.951919762736,212210.8624 2 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/worldfile_394.txt: -------------------------------------------------------------------------------- 1 | 1.269609983555,0.031472045651,527726.2297,0.031472045651,-1.269609983555,198235.3789 2 | -------------------------------------------------------------------------------- /cypress/fixtures/config-01/worldfile_402.txt: -------------------------------------------------------------------------------- 1 | 0.635,0,517110.0175,0,-0.635,206977.3825 2 | -------------------------------------------------------------------------------- /cypress/fixtures/data/IOFV2resultsparseerror.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 991a34eb-7527-4173-b8ff-a4edb2969745 5 | Milton Rigg 6 | 7 | 8 | 2014-01-18 9 | 10 | 11 | 12 | 13 | 14 | Border Liners 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /cypress/fixtures/data/IOFV3resultsparseerror.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Milton Rigg 5 | 6 | 2014-01-18 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /cypress/fixtures/data/ellenbrook.jgw: -------------------------------------------------------------------------------- 1 | 1.268259509000 2 | 0.066466664430 3 | 0.066466664430 4 | -1.268259509000 5 | 519318.6527 6 | 209855.1203 7 | -------------------------------------------------------------------------------- /cypress/fixtures/data/ellenbrook.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/cypress/fixtures/data/ellenbrook.jpg -------------------------------------------------------------------------------- /cypress/fixtures/data/ellenbrookinvalid.jgw: -------------------------------------------------------------------------------- 1 | 1.268259509000 2 | 0.066466664430 3 | 4 | 209855.1203 5 | -------------------------------------------------------------------------------- /cypress/fixtures/data/invalid.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | Garmin Connect 11 | 12 | 13 | 14 | 15 | Verulamium 16 | 17 | 18 | 81.19999694824219 19 | 20 | 21 | 22 | 95 23 | 24 | 25 | 26 | 27 | 81.19999694824219 28 | 29 | 30 | 31 | 95 32 | 33 | 34 | 35 | 36 | 81.19999694824219 37 | 38 | 39 | 40 | 95 41 | 42 | 43 | 44 | 45 | 81.0 46 | 47 | 48 | 49 | 95 50 | 51 | 52 | 53 | 54 | 81.0 55 | 56 | 57 | 58 | 95 59 | 60 | 61 | 62 | 63 | 80.80000305175781 64 | 65 | 66 | 67 | 105 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /cypress/fixtures/data/londoncolney.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/cypress/fixtures/data/londoncolney.gif -------------------------------------------------------------------------------- /cypress/fixtures/data/oom_636ca9e43ca63.jgw: -------------------------------------------------------------------------------- 1 | 5.69747429725 2 | 0 3 | 0 4 | -5.69747429725 5 | -34773.7836885 6 | 6756289.39781 7 | -------------------------------------------------------------------------------- /cypress/fixtures/data/oom_636ca9e43ca63.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/cypress/fixtures/data/oom_636ca9e43ca63.jpg -------------------------------------------------------------------------------- /cypress/fixtures/data/oom_636ca9e43ca63.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | oom_636ca9e43ca63_controls 5 | 1 6 | 7 | 8 | 9 | Controls 10 | 1 11 | S1#startfinish1-0.29864788055419916,51.74660166412059,0 12 | 1#control1-0.29461383819580084,51.74928521792208,0 13 | 2#control1-0.29311180114746094,51.744980826683786,0 14 | 3#control1-0.3023386001586914,51.74375851736767,0 15 | 4#control1-0.298004150390625,51.741101209111235,0 16 | F1#startfinish1-0.29864788055419916,51.74660166412059,0 17 | 18 | 19 | -------------------------------------------------------------------------------- /cypress/fixtures/data/oom_636ca9e43ca63.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/cypress/fixtures/data/oom_636ca9e43ca63.pdf -------------------------------------------------------------------------------- /cypress/fixtures/data/pwllddu.jgw: -------------------------------------------------------------------------------- 1 | 1.689803780000 2 | 0.109274708600 3 | 0.109274708600 4 | -1.689803780000 5 | 322939.7382 6 | 211340.9384 7 | -------------------------------------------------------------------------------- /cypress/fixtures/data/pwllddu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/cypress/fixtures/data/pwllddu.jpg -------------------------------------------------------------------------------- /cypress/fixtures/data/verulamium.jgw: -------------------------------------------------------------------------------- 1 | 0.845506339400 2 | 0.044311109620 3 | 0.044311109620 4 | -0.845506339400 5 | 513046.5740 6 | 207463.7778 7 | -------------------------------------------------------------------------------- /cypress/fixtures/data/verulamium.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/cypress/fixtures/data/verulamium.jpg -------------------------------------------------------------------------------- /cypress/fixtures/data/verybigmap-poorquality.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/cypress/fixtures/data/verybigmap-poorquality.jpg -------------------------------------------------------------------------------- /cypress/fixtures/php/config-01.php: -------------------------------------------------------------------------------- 1 | $el.click() 15 | Cypress.Commands.add("closeModal", (title, selector = ".modal-footer #rg2-do-modal-action") => { 16 | cy.get(".modal-title").should("be.visible").and("contain", title) 17 | cy.get(selector) 18 | .should("be.visible") 19 | .pipe(retryClick) 20 | .should(($el) => { 21 | expect($el).to.not.be.visible 22 | }) 23 | }) 24 | Cypress.Commands.add("closeWarningDialog", (expectedText) => { 25 | cy.get(".toast").should("be.visible").and("contain", expectedText).find(".btn-close").click() 26 | }) 27 | 28 | Cypress.Commands.add("addMap", (name, mapFile, georefFile = "", georefType = "") => { 29 | cy.intercept("rg2api.php?type=maps*").as("maps") 30 | cy.get("#manage-map-tab").click() 31 | cy.get("#rg2-map-name").clear() 32 | cy.get("#rg2-map-name").type(name) 33 | cy.get("#rg2-load-map-file").selectFile("./cypress/fixtures/data/" + mapFile) 34 | if (georefFile !== "") { 35 | cy.get("#rg2-load-georef-file").selectFile("./cypress/fixtures/data/" + georefFile) 36 | } 37 | if (georefType !== "") { 38 | cy.get("#rg2-georef-type").select(georefType) 39 | } 40 | cy.get("#btn-add-map").click() 41 | cy.closeModal("Confirm new map") 42 | cy.closeWarningDialog("Map added") 43 | cy.wait("@maps") 44 | }) 45 | 46 | Cypress.Commands.add("createEvent", () => { 47 | // assumes everything set up and ready to go 48 | // need to trap new event being opened in new window 49 | cy.window().then((win) => { 50 | cy.stub(win, "open", (url) => { 51 | // just log that it would have happened 52 | console.log("Open new window for " + url) 53 | }).as("newEventWindow") 54 | }) 55 | cy.intercept("rg2api.php?type=events*").as("events") 56 | cy.get("#btn-create-event").click() 57 | cy.closeModal("Confirm event creation") 58 | cy.get("@newEventWindow").should("be.called") 59 | cy.wait("@events") 60 | cy.closeWarningDialog("Event created") 61 | }) 62 | 63 | Cypress.Commands.add("selectCourseFile", (courseFile, warningMessage = "") => { 64 | cy.get("#rg2-load-course-file").selectFile("./cypress/fixtures/data/" + courseFile) 65 | if (warningMessage === "") { 66 | // valid file with no warnings 67 | cy.closeModal("Course details") 68 | } else { 69 | // invalid file 70 | cy.closeWarningDialog(warningMessage) 71 | } 72 | }) 73 | 74 | Cypress.Commands.add("selectResultsFile", (resultsFile, warningMessage = "") => { 75 | cy.get("#rg2-load-results-file").selectFile("./cypress/fixtures/data/" + resultsFile) 76 | if (warningMessage === "") { 77 | // valid file with no warnings 78 | cy.closeModal("Result details") 79 | } else { 80 | // invalid file 81 | cy.closeWarningDialog(warningMessage) 82 | } 83 | }) 84 | 85 | Cypress.Commands.add("setLocalStorage", () => { 86 | const defaultLocalStorage = { 87 | perCentMapIntensity: 100, 88 | perCentRouteIntensity: 100, 89 | replayFontSize: 12, 90 | courseWidth: 4, 91 | routeWidth: 5, 92 | circleSize: 20, 93 | snap: true, 94 | showThreeSeconds: false, 95 | showGPSSpeed: true, 96 | alignMap: false, 97 | maxSpeed: 4, 98 | minSpeed: 10, 99 | drawnRoutes: [] 100 | } 101 | localStorage.setItem("rg2-options", JSON.stringify(defaultLocalStorage)) 102 | }) 103 | -------------------------------------------------------------------------------- /cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/e2e.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | import "./commands" 17 | import "@cypress/code-coverage/support" 18 | 19 | // needed to test modals: see https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/ 20 | import "cypress-pipe" 21 | -------------------------------------------------------------------------------- /dist/.vite/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "_grid-CgsJkZsf.js": { 3 | "file": "assets/grid-CgsJkZsf.js", 4 | "name": "grid", 5 | "isDynamicEntry": true 6 | }, 7 | "_manager-utils-!~{005}~.js": { 8 | "file": "assets/manager-utils-HupOsEJb.css", 9 | "src": "_manager-utils-!~{005}~.js" 10 | }, 11 | "_manager-utils-C8g2LUbf.js": { 12 | "file": "assets/manager-utils-C8g2LUbf.js", 13 | "name": "manager-utils", 14 | "imports": [ 15 | "src/js/main.js" 16 | ], 17 | "css": [ 18 | "assets/manager-utils-HupOsEJb.css" 19 | ] 20 | }, 21 | "_node-modules-CPWXqQ3f.js": { 22 | "file": "assets/node-modules-CPWXqQ3f.js", 23 | "name": "node-modules", 24 | "isDynamicEntry": true, 25 | "imports": [ 26 | "src/js/main.js" 27 | ] 28 | }, 29 | "node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff": { 30 | "file": "assets/bootstrap-icons-BOrJxbIo.woff", 31 | "src": "node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff" 32 | }, 33 | "node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2": { 34 | "file": "assets/bootstrap-icons-BtvjY1KL.woff2", 35 | "src": "node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2" 36 | }, 37 | "src/js/main.js": { 38 | "file": "assets/main-D9_pgpFN.js", 39 | "name": "main", 40 | "src": "src/js/main.js", 41 | "isEntry": true, 42 | "imports": [ 43 | "_node-modules-CPWXqQ3f.js", 44 | "src/js/main.js" 45 | ], 46 | "dynamicImports": [ 47 | "_grid-CgsJkZsf.js", 48 | "_grid-CgsJkZsf.js", 49 | "_node-modules-CPWXqQ3f.js", 50 | "src/js/manager.js" 51 | ], 52 | "css": [ 53 | "assets/main-7W2BOCJG.css" 54 | ], 55 | "assets": [ 56 | "assets/bootstrap-icons-BtvjY1KL.woff2", 57 | "assets/bootstrap-icons-BOrJxbIo.woff" 58 | ] 59 | }, 60 | "src/js/manager.js": { 61 | "file": "assets/manager-1E3re5s3.js", 62 | "name": "manager", 63 | "src": "src/js/manager.js", 64 | "isDynamicEntry": true, 65 | "imports": [ 66 | "_node-modules-CPWXqQ3f.js", 67 | "src/js/main.js", 68 | "_manager-utils-C8g2LUbf.js" 69 | ] 70 | } 71 | } -------------------------------------------------------------------------------- /dist/app/html/aboutlayout.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
RG2 logo
8 |
9 | Routegadget 2 (RG2) is a web application for drawing and comparing orienteering routes. It is based on the 10 | original Routegadget developed by Jarkko Ryyppö. For further details see the 11 | User Guide 12 | and the 13 | RG2 project page. The latest 14 | version of RG2 is available for download 15 | from GitHub. 16 | You can see details of 17 | all RG2 installations here. 18 | This includes an RSS feed that provides details of new RG2 events when they are added. 19 |
20 |
21 |
22 |
Simon Errington (simon@maprunner.co.uk)
23 |
James Errington (james@maprunner.co.uk)
24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 |
32 |
33 |
34 | -------------------------------------------------------------------------------- /dist/app/html/addeventbody.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Event details 5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 | 28 | 29 |
30 | Results 31 |
32 | 33 |
34 |
35 | 36 | 37 |
38 |
39 | 40 | 41 |
Only select if the results file is not already properly sorted.
42 |
43 | Courses 44 |
45 | 46 |
47 |
48 | 49 | 50 |
51 |
52 | 53 | 54 |
55 | 56 |
57 | 58 | 59 |
60 |
61 |
62 | 63 | 64 |
65 |
66 |
67 |
68 | 69 | 70 |
71 | 72 | 73 |
74 |
-------------------------------------------------------------------------------- /dist/app/html/addmapbody.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | Map details 5 |
6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 | Georeferencing 14 |
15 | 16 | 17 |
18 |
19 | 20 | 23 |
24 |
25 |
26 |
27 |
28 | 29 |
30 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /dist/app/html/animationlayout.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | 8 | 14 | 15 |
16 |
17 | 18 | 21 | 22 | 25 | 26 | 29 |
30 |
31 | 34 | 40 | 41 |
42 |
43 | 46 | 52 | 53 |
54 |
55 | 58 | 64 | 75 |
76 | -------------------------------------------------------------------------------- /dist/app/html/deletemapbody.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /dist/app/html/drawbody.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 | 8 | 9 |
10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 | 37 |
38 |
39 | 40 |
Offset
41 |
42 | 43 | 44 | 45 |
46 | 47 |
48 |
49 | 50 |
51 |
52 |
53 | 54 | 55 |
56 |
57 |
58 | Left click to add/lock/unlock a handle. 59 |
    60 |
  • Green: draggable
  • 61 |
  • Red: locked
  • 62 |
63 | Right click to delete a handle. 64 |
65 | Drag a handle to adjust track around locked point(s). 66 |
67 |
68 | -------------------------------------------------------------------------------- /dist/app/html/editeventbody.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Select event 5 |
6 | 7 | 8 |
9 |
10 |
11 | Edit event details 12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 |
30 | 35 | 36 |
37 |
38 | 39 |
Type: 1 Zero splits, 2 Actual splits
40 |
course|type|control,time e.g. 1|1|6,60|15,60
41 |
42 |
43 | 44 | 45 |
46 | 47 |
48 |
49 | Delete route 50 |
51 | 52 | 53 |
54 | 55 |
56 |
57 | Delete event 58 |
59 | 60 |
61 |
62 |
63 |
64 | -------------------------------------------------------------------------------- /dist/app/html/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Routegadget 2 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /dist/app/html/navbar.html: -------------------------------------------------------------------------------- 1 | 70 | -------------------------------------------------------------------------------- /dist/app/html/script.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 28 | -------------------------------------------------------------------------------- /dist/app/html/settingslayout.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 |
7 | 8 | 9 |
10 |
11 |
12 | 20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 |
35 | 36 | 37 |
38 |
39 | 40 | 41 |
42 |
43 | 44 | 45 |
46 |
47 | 48 | 49 |
50 |
51 | 52 | 53 |
54 |
55 | 56 | 57 |
58 |
59 | -------------------------------------------------------------------------------- /dist/app/html/splitsbrowser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <EVENT_NAME> 7 | 8 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /dist/app/language.php: -------------------------------------------------------------------------------- 1 | 1) { 11 | $output[$data[0]] = $data[1]; 12 | } 13 | } 14 | fclose($handle); 15 | } 16 | return utils::addVersion('lang', $output); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /dist/app/user.php: -------------------------------------------------------------------------------- 1 | x) && isset($data->y)) { 7 | $userdetails = self::extractString($data->x); 8 | $cookie = $data->y; 9 | } else { 10 | $userdetails = "anon"; 11 | $cookie = "none"; 12 | } 13 | $ok = true; 14 | $keksi = trim(file_get_contents(KARTAT_DIRECTORY . "keksi.txt")); 15 | if (file_exists(KARTAT_DIRECTORY . "rg2userinfo.txt")) { 16 | $saved_user = trim(file_get_contents(KARTAT_DIRECTORY . "rg2userinfo.txt")); 17 | if (!password_verify($userdetails, $saved_user)) { 18 | utils::rg2log("User details incorrect."); 19 | $ok = false; 20 | } 21 | if ($keksi != $cookie) { 22 | utils::rg2log("Cookies don't match. " . $keksi . " : " . $cookie); 23 | $ok = false; 24 | } 25 | } else { 26 | // new account being set up: rely on JS end to force a reasonable name/password 27 | $temp = password_hash($userdetails, PASSWORD_DEFAULT); 28 | utils::rg2log("Creating new account."); 29 | file_put_contents(KARTAT_DIRECTORY . "rg2userinfo.txt", $temp . PHP_EOL); 30 | } 31 | return $ok; 32 | } 33 | 34 | // Mickey Mouse function to extract user name and password 35 | // just avoids plain text transmission for now so a bit better than RG1 36 | private static function extractString($data) 37 | { 38 | $str = ""; 39 | for ($i = 0; $i < strlen($data); $i = $i + 2) { 40 | $str .= substr($data, $i, 1); 41 | } 42 | return $str; 43 | } 44 | 45 | public static function generateNewKeksi() 46 | { 47 | // simple cookie generator! Don't need unique, just need something vaguely random 48 | $keksi = substr(str_shuffle("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"), 0, 20); 49 | $result = file_put_contents(KARTAT_DIRECTORY . "keksi.txt", $keksi . PHP_EOL); 50 | //utils::rg2log(KARTAT_DIRECTORY." ".$keksi." ".$result); 51 | if ($result === false) { 52 | utils::rg2log("Error writing keksi.txt: " . $keksi); 53 | } 54 | return $keksi; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /dist/assets/bootstrap-icons-BOrJxbIo.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/dist/assets/bootstrap-icons-BOrJxbIo.woff -------------------------------------------------------------------------------- /dist/assets/bootstrap-icons-BtvjY1KL.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/dist/assets/bootstrap-icons-BtvjY1KL.woff2 -------------------------------------------------------------------------------- /dist/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/dist/img/apple-touch-icon.png -------------------------------------------------------------------------------- /dist/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/dist/img/favicon.ico -------------------------------------------------------------------------------- /dist/img/rg2-logo-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/dist/img/rg2-logo-32x32.png -------------------------------------------------------------------------------- /dist/img/rg2-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/index.php: -------------------------------------------------------------------------------- 1 | file, -3, 3); 73 | if ($fileExtension === '.js' && isset($file->isEntry) && $file->isEntry === true && (!isset($file->isDynamicEntry) || $file->isDynamicEntry !== true)) { 74 | $jsfiles .= ''; 75 | if (!empty($file->css)) { 76 | foreach ($file->css as $cssFile) { 77 | $cssfiles .= ''; 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | header('Content-type: text/html; charset=utf-8'); 85 | // This forces Siteground to disable its dynamic caching default which does not work for Routegadget 86 | // since response contents may change even if URL is the same (e.g. start-up config info) 87 | header('Cache-Control: no-cache'); 88 | 89 | ?> 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /dist/lang/de.txt: -------------------------------------------------------------------------------- 1 | "language", "Deutsch" 2 | "code", "de" 3 | "translation", "Simon@Harston.de" 4 | "Events", "Wettkämpfe" 5 | "Course", "Strecke" 6 | "Courses", "Strecken" 7 | "Results", "Ergebnisse" 8 | "Draw", "Zeichnen" 9 | "Show", "Anzeigen" 10 | "Runners", "Läufer" 11 | "Routes", "Routen" 12 | "Route", "Route" 13 | "Name", "Name" 14 | "Time", "Zeit" 15 | "Replay", "Animation" 16 | "All", "Alle" 17 | "Help", "Hilfe" 18 | "Options", "Optionen" 19 | "Reset", "Zurücksetzen" 20 | "Zoom out", "Verkleinern" 21 | "Zoom in", "Vergrößern" 22 | "Splits", "Zwischenzeiten" 23 | "Splits table", "Zwischenzeiten-Tabelle" 24 | "Select runners on Results tab", "Läufer auf dem Ergebnis-Reiter auswählen" 25 | "Log in", "Anmelden" 26 | "User name", "Benutzername" 27 | "Password", "Passwort" 28 | "Show controls", "Zeige Posten" 29 | "Hide controls", "Verberge Posten" 30 | "Show initials", "Zeige Initialien" 31 | "Show names", "Zeige Namen" 32 | "Hide names", "Verberge Namen" 33 | "Show info panel", "Zeige Auswahl-Panel" 34 | "Hide info panel", "Verberge Auswahl-Panel" 35 | "Select an event", "Wähle einen Wettkampf aus" 36 | "Slower", "Langsamer" 37 | "Run", "Start" 38 | "Pause", "Pause" 39 | "Faster", "Schneller" 40 | "Real time", "Echtzeit" 41 | "Mass start", "Massenstart" 42 | "Start at", "Beginne bei" 43 | "Length", "Länge" 44 | "Full tails", 45 | "Vollständige Spur", // maybe 'Volle Spur' will suffice if this one is too long? 46 | "No results available", 47 | "Keine Ergebnisse verfügbar" 48 | "Configuration options", "Konfigurations-Optionen" 49 | "Language", "Sprache" 50 | "Map intensity", "Intensität Karte" 51 | "Route intensity", "Intensität Route" 52 | "Route width", "Routen-Breite" 53 | "Replay label font size", "Schriftgröße Animations-Text" 54 | "Course overprint width", "Stiftbreite Bahn-Signaturen" 55 | "Control circle size", "Postenkreis-Größe" 56 | "Snap to control when drawing", "Snap to control when drawing" 57 | "Show +3 time loss for GPS routes", "Zeige +3 Zeitverlust bei GPS-Routen" 58 | "Show GPS speed colours", "Zeige Farben für Geschwindigkeit bei GPS" 59 | "Event statistics", "Wettkampf-Statistik" 60 | "Drawn routes", "Gezeichnete Routen" 61 | "GPS routes", "GPS-Routen" 62 | "Comments", "Kommentare" 63 | "Draw route", "Route zeichnen" 64 | "Select course", "Wähle Strecke" 65 | "Select name", "Wähle Name" 66 | "Type your comment", "Dein Kommentar" 67 | "Load GPS file (GPX or TCX)", "Lade GPS-Datei (GPX oder TCX)" 68 | "Save", "Speichern" 69 | "+3 sec", "+3 Sek" 70 | "Undo", "Rückgängig" 71 | "Save GPS route", "GPS-Route speichern" 72 | "Move track and map together (or right click-drag)", "Verschiebe Route und Karte zusammen (oder ziehe mit Rechts-Klick)" 73 | "Map is georeferenced", "Karte ist geo-referenziert" 74 | "International event", "Internationaler Wettkampf" 75 | "National event", "Nationaler Wettkampf" 76 | "Regional event", "Regionaler Wettkampf" 77 | "Local event", "Kleiner Wettkampf" 78 | "Training event", "Trainings-Wettkampf" 79 | "Unknown", "Unbekannt" 80 | "Warning", "Achtung" 81 | "Your route has been saved", "Deine Route wurde gespeichert" 82 | "Your route was not saved. Please try again", "Deine Route wurde nicht gespeichert! Bitte versuche es erneut" 83 | "Total time", "Gesamtzeit" 84 | "Left click to add/lock/unlock a handle", "Links-Klick zum Hinzufügen/Sperren/Lösen eines Bearbeitungspunkts" 85 | "Green - draggable", "Grün - Verschiebbar" 86 | "Red - locked", "Rot - Gesperrt" 87 | "Right click to delete a handle", "Rechts-Klick zum Löschen eines Bearbeitungspunkts" 88 | "Drag a handle to adjust track around locked point(s)", "Ziehe einen Bearbeitungspunkt, um die Route anzupassen" 89 | "Search", "Suchen" 90 | -------------------------------------------------------------------------------- /dist/lang/it.txt: -------------------------------------------------------------------------------- 1 | "code", "it" 2 | "language", "Italiano" 3 | "translation", "tona.edoardo" 4 | "Events", "Gare" 5 | "Course", "Categoria" 6 | "Courses", "Categorie" 7 | "Controls", "Controlli" 8 | "Results", "Classifiche" 9 | "Draw", "Disegna" 10 | "Show", "Mostra" 11 | "Runners", "Atleti" 12 | "Routes", "Percorsi disegnati" 13 | "Route", "Percorso" 14 | "Name", "Nome" 15 | "Time", "Tempo" 16 | "Replay", "Replay" 17 | "All", "Tutti" 18 | "Help", "Aiuto" 19 | "Options", "Opzioni" 20 | "Reset", "Reset" 21 | "Zoom out", "Zoom indietro" 22 | "Zoom in", "Zoom avanti" 23 | "Splits", "Intertempi" 24 | "Splits table", "Tabella intertempi" 25 | "Select runners on Results tab", "Seleziona un atleta dalle classifiche" 26 | "Log in", "Log in" 27 | "User name", "Nome utente" 28 | "Password", "Password" 29 | "Show controls", "Mostra punti" 30 | "Hide controls", "Nascondi punti" 31 | "Show initials", "Mostra le iniziali" 32 | "Show names", "Mostra i nomi" 33 | "Hide names", "Nascondi i nomi" 34 | "Show info panel", "Mostra il pannello informazioni" 35 | "Hide info panel", "Nascondi il pannello informazioni" 36 | "Select an event", "Seleziona una gara" 37 | "Slower", "Più lento" 38 | "Run", "Corri" 39 | "Pause", "Pausa" 40 | "Faster", "Più veloce" 41 | "Real time", "Orario reale" 42 | "Mass start", "Mass start" 43 | "Start at", "Partenza da" 44 | "Length", "Lungh." 45 | "Full tails", "Traccia" 46 | "No results available", "Nessun risultato disponibile" 47 | "Configuration options", "Configurazione" 48 | "Language", "Lingua" 49 | "Map intensity", "Intensità mappa" 50 | "Route intensity %", "Intensità percorsi" 51 | "Route width", "Larghezza percorsi" 52 | "Replay label font size", "Grandezza scritta Replay" 53 | "Course overprint width", "Larghezza sovrastampa" 54 | "Control circle size", "Grandezza dei cerchi dei punti" 55 | "Snap to control when drawing", "Agganciati al punto mentre disegni" 56 | "Show +3 time loss for GPS routes", "Mostra +3 sec nei percorsi GPS" 57 | "Show GPS speed colours", "Mostra velocità nei percorsi GPS" 58 | "Event statistics", "Statistiche gara" 59 | "Drawn routes", "A mano" 60 | "GPS routes", "Con il GPS" 61 | "Comments", "Commenti" 62 | "Draw route", "Disegna percorso" 63 | "Select course", "Seleziona cat." 64 | "Select name", "Seleziona nome" 65 | "Type your comment", "Scrivi un tuo commento" 66 | "Load GPS file (GPX or TCX)", "Carica un file dal GPS (GPX o TCX)" 67 | "Save", "Salva" 68 | "+3 sec", "+3 sec" 69 | "Undo", "Annulla" 70 | "Save GPS route", "Salva percorso GPS" 71 | "Move track and map together (or right click-drag)", "Muovi percorso e mappa insieme (o sposta con il tasto destro)" 72 | "Map is georeferenced", "La mappa è georeferenziata" 73 | "International event", "Gara internazionale" 74 | "National event", "Gara nazionale" 75 | "Regional event", "Gara regionale" 76 | "Local event", "Gara promozionale" 77 | "Training event", "Allenamento" 78 | "Unknown", "Sconosciuto" 79 | "Warning", "Attenzione" 80 | "Your route has been saved", "Il tuo percorso è stato salvato" 81 | "Your route was not saved. Please try again", "Il tuo percorso non è stato salvato. Riprova" 82 | "Loading courses", "Caricamento tracciati" 83 | "Loading results", "Caricamento classifiche" 84 | "Loading map", "Caricamento mappa" 85 | "Loading routes", "Caricamento percorsi" 86 | "Saving courses", "Salvando i tracciati" 87 | "Saving results", "Salvando le classifiche" 88 | "Saving routes", "Salvando i percorsi" 89 | "Map loaded", "Mappa caricata" 90 | "Total time", "Tempo totale" 91 | "Left click to add/lock/unlock a handle", "Clicca con i sinistro per aggiungere/bloccare/sbloccare un punto" 92 | "Green - draggable", "Verde - spostabile" 93 | "Red - locked", "Rosso - bloccato" 94 | "Right click to delete a handle", "Clicca con il destro per cancellare un punto" 95 | "Drag a handle to adjust track around locked point(s)", "Sposta un punto per aggiustare il percorso attorno a un punto bloccato" 96 | "Autofit", "Adattamento automatico" 97 | "Route already drawn", "Percorso già disegnato" 98 | "If you draw a new route it will overwrite the old route for this runner.", "Se disegni un nuovo percorso, quello vecchio sarà sovrascritto." 99 | "GPS routes are saved separately and will not be overwritten.", "I percorsi GPS sono salvati separatamente e non saranno sovrascritti." 100 | "Search", "Cerca" 101 | -------------------------------------------------------------------------------- /dist/lang/ja.txt: -------------------------------------------------------------------------------- 1 | "code", "ja" 2 | "language", "日本語" 3 | "translation", "tojo masaya" 4 | "Events", "イベント" 5 | "Course", "コース" 6 | "Courses", "コース" 7 | "Controls", "コントロール" 8 | "Results", "リザルト" 9 | "Draw", "記入" 10 | "Show", "表示" 11 | "Runners", "走者" 12 | "Routes", "ルート" 13 | "Route", "ルート" 14 | "Name", "名前" 15 | "Time", "タイム" 16 | "Replay", "再生" 17 | "All", "すべて" 18 | "Help", "ヘルプ" 19 | "Options", "オプション" 20 | "Reset", "リセット" 21 | "Zoom out", "縮小" 22 | "Zoom in", "拡大" 23 | "Rotate right", "右回転" 24 | "Rotate left", "左回転" 25 | "Splits", "スプリット" 26 | "Splits table", "スプリットテーブル" 27 | "Select runners on Results tab", "リザルトタブで走者を選択してください" 28 | "Log in", "ログイン" 29 | "User name", "ユーザ名" 30 | "Password", "パスワード" 31 | "Show controls", "全コントロールを表示" 32 | "Hide controls", "使われていないコントロールを非表示" 33 | "Show initials", "イニシャルを表示" 34 | "Show names", "名前を表示" 35 | "Hide names", "名前を非表示" 36 | "Show info panel", "インフォパネルを表示" 37 | "Hide info panel", "インフォパネルを非表示" 38 | "Select an event", "イベントを選択してください" 39 | "Slower", "遅く" 40 | "Run", "実行" 41 | "Pause", "一時停止" 42 | "Faster", "速く" 43 | "Real time", "リアルタイム" 44 | "Mass start", "マススタート" 45 | "Start at", "開始位置" 46 | "Length", "長さ" 47 | "Full tails", "尻尾を全表示" 48 | "No results available", "使用可能なリザルトがありません" 49 | "Configuration options", "オプション設定" 50 | "Language", "言語" 51 | "Map intensity", "地図の輝度" 52 | "Route intensity %", "ルートの輝度" 53 | "Route width", "ルート線の幅" 54 | "Replay label font size", "名前ラベルのフォントサイズ" 55 | "Course overprint width", "コース線の幅" 56 | "Control circle size", "コントロール円のサイズ" 57 | "Snap to control when drawing", "ルート記入時にコントロールに吸着" 58 | "Show +3 time loss for GPS routes", "GPSルートでタイムロスの +3 を表示" 59 | "Show GPS speed colours", "GPSスピードカラーを表示" 60 | "Event statistics", "イベント統計" 61 | "Drawn routes", "記入済ルート" 62 | "GPS routes", "GPSルート" 63 | "Comments", "コメント" 64 | "Draw route", "ルートを記入" 65 | "Select course", "コースを選択" 66 | "Select name", "名前を選択" 67 | "Type your comment", "コメントを入力してください" 68 | "Load GPS file (GPX or TCX)", "GPSファイルをロード (GPX または TCX)" 69 | "Save", "保存" 70 | "+3 sec", "+3 秒" 71 | "Undo", "元に戻す" 72 | "Save GPS route", "GPSルートを保存" 73 | "Align map to next control", "次のコントロールに地図を合わせる" 74 | "Move track and map together (or right click-drag)", 75 | "GPSトラックと地図を同時に移動 (または右クリックをしながらドラッグ)" 76 | "Map is georeferenced", "ジオレファレンス済の地図" 77 | "International event", "国際イベント" 78 | "National event", "全国イベント" 79 | "Regional event", "地区イベント" 80 | "Local event", "ローカルイベント" 81 | "Training event", "トレーニングイベント" 82 | "Unknown", "不明" 83 | "Warning", "警告" 84 | "Your route has been saved", "ルートが保存されました" 85 | "Your route was not saved. Please try again", "ルートが保存されませんでした。再度保存を行ってください" 86 | "Loading courses", "コースをロード中" 87 | "Loading results", "リザルトをロード中" 88 | "Loading map", "地図をロード中" 89 | "Loading routes", "ルートをロード中" 90 | "Saving courses", "コースを保存中" 91 | "Saving results", "リザルトを保存中" 92 | "Saving routes", "ルートを保存中" 93 | "Map loaded", "地図がロードされました" 94 | "Total time", "合計時間" 95 | "Left click to add/lock/unlock a handle", "左クリックでハンドルを追加/ロック/アンロック" 96 | "Green - draggable", "緑 - ドラッグ可能" 97 | "Red - locked", "赤 - ロック中" 98 | "Right click to delete a handle", "右クリックでハンドルを削除" 99 | "Drag a handle to adjust track around locked point(s)", "ロックされた点の周りでハンドルをドラッグしてトラックを調節" 100 | "Autofit", "オートフィット" 101 | "Route already drawn", "ルートがすでに記入されています" 102 | "If you draw a new route it will overwrite the old route for this runner.", 103 | "新しくルートを記入するとこの走者の古いルートが上書きされます" 104 | "GPS routes are saved separately and will not be overwritten.", "GPSルートは別途保存され、上書きさません。" 105 | "Search", "検索" 106 | -------------------------------------------------------------------------------- /dist/lang/no.txt: -------------------------------------------------------------------------------- 1 | "code", "no" 2 | "language", "Norsk" 3 | "translation", "olav@kvittem.no" 4 | "Events", "Løp" 5 | "Course", "Løype" 6 | "Courses", "Løyper" 7 | "Results", "Resultater" 8 | "Draw", "Tegn" 9 | "Show", "Vis" 10 | "Runners", "Løpere" 11 | "Routes", "Ruter" 12 | "Route", "Rute" 13 | "Name", "Navn" 14 | "Time", "Tid" 15 | "Replay", "Omløp" 16 | "All", "Alle" 17 | "Help", "Hjelp" 18 | "Options", "Valg" 19 | "Reset", "Nullstill" 20 | "Zoom out", "Utvid" 21 | "Zoom in", "Innskrenk" 22 | "Splits", "Strekk" 23 | "Splits table", "Strekk-tabell" 24 | "Select runners on Results tab", "Velg løpere under Resultat-fliken" 25 | "Log in", "Logg inn" 26 | "User name", "Brukernavn" 27 | "Password", "Passord" 28 | "Show controls", "Vis poster" 29 | "Hide controls", "Gjem poster" 30 | "Show initials", "Vis forbokstaver" 31 | "Show names", "Vis navn" 32 | "Hide names", "Gjem navn" 33 | "Show info panel", "Vis info panel" 34 | "Hide info panel", "Gjem info panel" 35 | "Select an event", "Velg et løp" 36 | "Slower", "Saktere" 37 | "Run", "Løp" 38 | "Pause", "Pause" 39 | "Faster", "Fortere" 40 | "Real time", "Sanntid" 41 | "Mass start", "Fellesstart" 42 | "Start at", "Start ved" 43 | "Length", "Lengde" 44 | "Full tails", "Lange hale" 45 | "No results available", "Ingen resultater tilgjengelig" 46 | "Configuration options", "Brukervalg" 47 | "Language", "Språk" 48 | "Map intensity", "Styrke på kart" 49 | "Route intensity %", "Styrke på spor" 50 | "Route width", "Bredde på spor" 51 | "Replay label font size", "Bokstavstørrelse Omatt" 52 | "Course overprint width", "Strekbredde løype" 53 | "Control circle size", "Poststørrelse" 54 | "Snap to control when drawing", "Fang til post ved tegning" 55 | "Show +3 time loss for GPS routes", "Vis +3 tidstap for GPS spor" 56 | "Show GPS speed colours", "Vis farger for fart med GPS" 57 | "Event statistics", "Løps-statistikk" 58 | "Drawn routes", "Tegnede spor" 59 | "GPS routes", "GPS spor" 60 | "Comments", "Kommentarer" 61 | "Draw route", "Tegn spor" 62 | "Select course", "Velg løype" 63 | "Select name", "Velg navn" 64 | "Type your comment", "Kommenter" 65 | "Load GPS file (GPX or TCX)", "Last GPS fil (GPX or TCX)" 66 | "Save", "Lagre" 67 | "+3 sec", "+3 sek" 68 | "Undo", "Angre" 69 | "Save GPS route", "Lagre GPS-spor" 70 | "Move track and map together (or right click-drag)", "Flytt spor og kart sammen(eller dra og høyreklikk)" 71 | "Map is georeferenced", "Kartet er georeferert" 72 | "International event", "Internasjonalt løp" 73 | "National event", "Nasjonalt løp" 74 | "Regional event", "Regionalt løp" 75 | "Local event", "Lokalt løp" 76 | "Training event", "Treningsløp" 77 | "Unknown", "Ukjent" 78 | "Warning", "Advarsel" 79 | "Your route has been saved", "Sporet er lagret" 80 | "Your route was not saved. Please try again", "Sporet ble ikke lagret - prøv igjen" 81 | "Loading courses", "Laster løyper" 82 | "Loading results", "Laster resultater" 83 | "Loading map", "Laster kart" 84 | "Loading routes", "kart spor" 85 | "Saving courses", "Lagrer løyper" 86 | "Saving results", "Lagrer resultater" 87 | "Saving routes", "Lagrer resultater" 88 | "Map loaded", "Kart lastet" 89 | "Total time", "Total tid" 90 | "Left click to add/lock/unlock a handle", "Venstreklikk for å legge til/feste/løsne et håndtak" 91 | "Green - draggable", "Grønn - flyttbar" 92 | "Red - locked", "Rød - festet" 93 | "Right click to delete a handle", "Høyreklikk for å fjerne et håndtak" 94 | "Drag a handle to adjust track around locked point(s)", "Dra et håndtak for å justere et spor mellom festepunkter" 95 | "Search", "Søk" 96 | "Align map to next control", "Tilpass kartet mot neste post" 97 | -------------------------------------------------------------------------------- /dist/lang/ru.txt: -------------------------------------------------------------------------------- 1 | "language", "Русский" 2 | "code", "ru" 3 | "translation", "seikin.alexey@gmail.com" 4 | "Events", "События" 5 | "Course", "Дистанция" 6 | "Courses", "Дистанции" 7 | "Results", "Результаты" 8 | "Draw", "Нарисовать" 9 | "Show", "Показать" 10 | "Runners", "Участники" 11 | "Routes", "Пути движения" 12 | "Route", "Путь движения" 13 | "Name", "Имя" 14 | "Time", "Время" 15 | "Replay", "Анимация трека" 16 | "All", "Все" 17 | "Help", "Помощь" 18 | "Options", "Настройки" 19 | "Reset", "Сбросить" 20 | "Zoom out", "Отдалить карту" 21 | "Zoom in", "Приблизить карту" 22 | "Splits", "Сплиты" 23 | "Splits table", "Таблица сплитов" 24 | "Select runners on Results tab", "Отобразить участников в таблице Результатов" 25 | "Log in", "Вход в панель управления" 26 | "User name", "Имя пользователя" 27 | "Password", "Пароль" 28 | "Show controls", "Показать пункты управления" 29 | "Hide controls", "Скрыть панель управления" 30 | "Show initials", "Отобразить ФИО" 31 | "Show names", "Отобразить имена" 32 | "Hide names", "Скрыть имена" 33 | "Show info panel", "Показать инфо-панель" 34 | "Hide info panel", "Скрыть инфо-панель" 35 | "Select an event", "Выбрать мероприятие" 36 | "Slower", "Медленнее" 37 | "Run", "Старт" 38 | "Pause", "Пауза" 39 | "Faster", "Быстрее" 40 | "Real time", "В режиме реального времени" 41 | "Mass start", "В режиме одновременного старта" 42 | "Start at", "Время старта" 43 | "Length", "Длина" 44 | "Full tails", "Трек полностью" 45 | "No results available", "Данных с результатами нет" 46 | "Configuration options", "Настройки" 47 | "Language", "Язык" 48 | "Map intensity", "Прозрачность карты" 49 | "Route intensity %", "Прозрачность трека" 50 | "Route width", "Ширина линии трека" 51 | "Replay label font size", "Размер шрифта на ярлыке участника" 52 | "Course overprint width", "Ширина дистанции" 53 | "Control circle size", "Размер символа КП" 54 | "Snap to control when drawing", "Щелкнуть для управления при рисовании" 55 | "Show +3 time loss for GPS routes", "Прибавить +3 к времени GPS трека" 56 | "Show GPS speed colours", "Отобразить скорость GPS трека цветами" 57 | "Event statistics", "Статистика событий" 58 | "Drawn routes", "Отрисованные треки" 59 | "GPS routes", "GPS-треки" 60 | "Comments", "Комментарии" 61 | "Draw route", "Нарисовать трек" 62 | "Select course", "Выбрать дистанцию" 63 | "Select name", "Выбрать имя" 64 | "Type your comment", "Написать комментарий" 65 | "Load GPS file (GPX or TCX)", "Загрузить свой трек(GPX oder TCX)" 66 | "Save", "Сохранить" 67 | "+3 sec", "+3 Сек." 68 | "Undo", "Отменить действие" 69 | "Save GPS route", "Сохранить GPS-трек" 70 | "Move track and map together (or right click-drag)", "Перемещать трек и карту одновременно (правый клик мыши - тащить)" 71 | "Map is georeferenced", "Карта привязана (georeferenced)" 72 | "International event", "Международный старт" 73 | "National event", "Национальный старт" 74 | "Regional event", "Региональный старт" 75 | "Local event", "Местное событие" 76 | "Training event", "Тренировочный старт" 77 | "Unknown", "Неизвестно" 78 | "Warning", "Внимание" 79 | "Your route has been saved", "Ваш маршрут был сохранен" 80 | "Your route was not saved. Please try again", "Ваш маршрут не сохранен. Попробовать еще раз" 81 | "Total time", "Итоговое время" 82 | "Left click to add/lock/unlock a handle", "Кликнуть левой кнопкой мыши для добавления/блокировки/разблокировки точки отметки" 83 | "Green - draggable", "Зеленый - можно перемещать" 84 | "Red - locked", "Красный - перемещение заблокировано" 85 | "Right click to delete a handle", "Кликнуть правой кнопкой мыши для удаления линии" 86 | "Drag a handle to adjust track around locked point(s)", "Тянуть точку отметки для выравнивания трека по заблокированной точке" 87 | "Search", "Найти" 88 | -------------------------------------------------------------------------------- /dist/lock/readme.txt: -------------------------------------------------------------------------------- 1 | This /lock directory is used by rg2api.php to implement file locking so that multiple users can update 2 | the data files at the same time. When things are working correctly this directory will normally be empty. 3 | It will contain one or more temporary directories for a very short time (possibly up to around 5 seconds) when 4 | information is being saved. 5 | 6 | If a serious error occurs then a directory may not be deleted correctly, which will prevent RG2 from working. 7 | Any subdirectories in /lock can be deleted if necessary to restore correct operation. 8 | 9 | 10 | -------------------------------------------------------------------------------- /dist/log/readme.txt: -------------------------------------------------------------------------------- 1 | This /log directory may contain a log file containing debug and logging information. The log 2 | file can be deleted at any point and will be automatically recreated as necessary. 3 | -------------------------------------------------------------------------------- /dist/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "_grid-CgsJkZsf.js": { 3 | "file": "assets/grid-CgsJkZsf.js", 4 | "name": "grid", 5 | "isDynamicEntry": true 6 | }, 7 | "_manager-utils-!~{005}~.js": { 8 | "file": "assets/manager-utils-HupOsEJb.css", 9 | "src": "_manager-utils-!~{005}~.js" 10 | }, 11 | "_manager-utils-C8g2LUbf.js": { 12 | "file": "assets/manager-utils-C8g2LUbf.js", 13 | "name": "manager-utils", 14 | "imports": [ 15 | "src/js/main.js" 16 | ], 17 | "css": [ 18 | "assets/manager-utils-HupOsEJb.css" 19 | ] 20 | }, 21 | "_node-modules-CPWXqQ3f.js": { 22 | "file": "assets/node-modules-CPWXqQ3f.js", 23 | "name": "node-modules", 24 | "isDynamicEntry": true, 25 | "imports": [ 26 | "src/js/main.js" 27 | ] 28 | }, 29 | "node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff": { 30 | "file": "assets/bootstrap-icons-BOrJxbIo.woff", 31 | "src": "node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff" 32 | }, 33 | "node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2": { 34 | "file": "assets/bootstrap-icons-BtvjY1KL.woff2", 35 | "src": "node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2" 36 | }, 37 | "src/js/main.js": { 38 | "file": "assets/main-D9_pgpFN.js", 39 | "name": "main", 40 | "src": "src/js/main.js", 41 | "isEntry": true, 42 | "imports": [ 43 | "_node-modules-CPWXqQ3f.js", 44 | "src/js/main.js" 45 | ], 46 | "dynamicImports": [ 47 | "_grid-CgsJkZsf.js", 48 | "_grid-CgsJkZsf.js", 49 | "_node-modules-CPWXqQ3f.js", 50 | "src/js/manager.js" 51 | ], 52 | "css": [ 53 | "assets/main-7W2BOCJG.css" 54 | ], 55 | "assets": [ 56 | "assets/bootstrap-icons-BtvjY1KL.woff2", 57 | "assets/bootstrap-icons-BOrJxbIo.woff" 58 | ] 59 | }, 60 | "src/js/manager.js": { 61 | "file": "assets/manager-1E3re5s3.js", 62 | "name": "manager", 63 | "src": "src/js/manager.js", 64 | "isDynamicEntry": true, 65 | "imports": [ 66 | "_node-modules-CPWXqQ3f.js", 67 | "src/js/main.js", 68 | "_manager-utils-C8g2LUbf.js" 69 | ] 70 | } 71 | } -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | /** @type {import('eslint').Linter.Config} */ 3 | import globals from "globals" 4 | import js from "@eslint/js" 5 | //const eslintPluginPrettierRecommended = require("eslint-plugin-prettier/recommended") 6 | 7 | export default [ 8 | // not yet supported in ESLINT 9.0.0..... 9 | //extends: ["plugin:cypress/recommended"], 10 | js.configs.recommended, 11 | { 12 | ignores: ["src/js/lib/**/*", "dist", "node-modules", "lang", "cypress/**/*", "coverage/**/*", "cypress.config.cjs"] 13 | }, 14 | { 15 | languageOptions: { 16 | ecmaVersion: 2022, 17 | globals: { 18 | ...globals.browser, 19 | rg2Config: "readonly", 20 | Chart: "readonly", 21 | agGrid: "readonly" 22 | } 23 | } 24 | }, 25 | { 26 | rules: { 27 | "no-unused-vars": "warn" 28 | } 29 | } 30 | //eslintPluginPrettierRecommended 31 | ] 32 | -------------------------------------------------------------------------------- /img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/img/apple-touch-icon.png -------------------------------------------------------------------------------- /img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/img/favicon.ico -------------------------------------------------------------------------------- /img/rg2-logo-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/img/rg2-logo-32x32.png -------------------------------------------------------------------------------- /img/rg2-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | file, -3, 3); 73 | if ($fileExtension === '.js' && isset($file->isEntry) && $file->isEntry === true && (!isset($file->isDynamicEntry) || $file->isDynamicEntry !== true)) { 74 | $jsfiles .= ''; 75 | if (!empty($file->css)) { 76 | foreach ($file->css as $cssFile) { 77 | $cssfiles .= ''; 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | header('Content-type: text/html; charset=utf-8'); 85 | // This forces Siteground to disable its dynamic caching default which does not work for Routegadget 86 | // since response contents may change even if URL is the same (e.g. start-up config info) 87 | header('Cache-Control: no-cache'); 88 | 89 | ?> 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /lang/de.txt: -------------------------------------------------------------------------------- 1 | "language", "Deutsch" 2 | "code", "de" 3 | "translation", "Simon@Harston.de" 4 | "Events", "Wettkämpfe" 5 | "Course", "Strecke" 6 | "Courses", "Strecken" 7 | "Results", "Ergebnisse" 8 | "Draw", "Zeichnen" 9 | "Show", "Anzeigen" 10 | "Runners", "Läufer" 11 | "Routes", "Routen" 12 | "Route", "Route" 13 | "Name", "Name" 14 | "Time", "Zeit" 15 | "Replay", "Animation" 16 | "All", "Alle" 17 | "Help", "Hilfe" 18 | "Options", "Optionen" 19 | "Reset", "Zurücksetzen" 20 | "Zoom out", "Verkleinern" 21 | "Zoom in", "Vergrößern" 22 | "Splits", "Zwischenzeiten" 23 | "Splits table", "Zwischenzeiten-Tabelle" 24 | "Select runners on Results tab", "Läufer auf dem Ergebnis-Reiter auswählen" 25 | "Log in", "Anmelden" 26 | "User name", "Benutzername" 27 | "Password", "Passwort" 28 | "Show controls", "Zeige Posten" 29 | "Hide controls", "Verberge Posten" 30 | "Show initials", "Zeige Initialien" 31 | "Show names", "Zeige Namen" 32 | "Hide names", "Verberge Namen" 33 | "Show info panel", "Zeige Auswahl-Panel" 34 | "Hide info panel", "Verberge Auswahl-Panel" 35 | "Select an event", "Wähle einen Wettkampf aus" 36 | "Slower", "Langsamer" 37 | "Run", "Start" 38 | "Pause", "Pause" 39 | "Faster", "Schneller" 40 | "Real time", "Echtzeit" 41 | "Mass start", "Massenstart" 42 | "Start at", "Beginne bei" 43 | "Length", "Länge" 44 | "Full tails", 45 | "Vollständige Spur", // maybe 'Volle Spur' will suffice if this one is too long? 46 | "No results available", 47 | "Keine Ergebnisse verfügbar" 48 | "Configuration options", "Konfigurations-Optionen" 49 | "Language", "Sprache" 50 | "Map intensity", "Intensität Karte" 51 | "Route intensity", "Intensität Route" 52 | "Route width", "Routen-Breite" 53 | "Replay label font size", "Schriftgröße Animations-Text" 54 | "Course overprint width", "Stiftbreite Bahn-Signaturen" 55 | "Control circle size", "Postenkreis-Größe" 56 | "Snap to control when drawing", "Snap to control when drawing" 57 | "Show +3 time loss for GPS routes", "Zeige +3 Zeitverlust bei GPS-Routen" 58 | "Show GPS speed colours", "Zeige Farben für Geschwindigkeit bei GPS" 59 | "Event statistics", "Wettkampf-Statistik" 60 | "Drawn routes", "Gezeichnete Routen" 61 | "GPS routes", "GPS-Routen" 62 | "Comments", "Kommentare" 63 | "Draw route", "Route zeichnen" 64 | "Select course", "Wähle Strecke" 65 | "Select name", "Wähle Name" 66 | "Type your comment", "Dein Kommentar" 67 | "Load GPS file (GPX or TCX)", "Lade GPS-Datei (GPX oder TCX)" 68 | "Save", "Speichern" 69 | "+3 sec", "+3 Sek" 70 | "Undo", "Rückgängig" 71 | "Save GPS route", "GPS-Route speichern" 72 | "Move track and map together (or right click-drag)", "Verschiebe Route und Karte zusammen (oder ziehe mit Rechts-Klick)" 73 | "Map is georeferenced", "Karte ist geo-referenziert" 74 | "International event", "Internationaler Wettkampf" 75 | "National event", "Nationaler Wettkampf" 76 | "Regional event", "Regionaler Wettkampf" 77 | "Local event", "Kleiner Wettkampf" 78 | "Training event", "Trainings-Wettkampf" 79 | "Unknown", "Unbekannt" 80 | "Warning", "Achtung" 81 | "Your route has been saved", "Deine Route wurde gespeichert" 82 | "Your route was not saved. Please try again", "Deine Route wurde nicht gespeichert! Bitte versuche es erneut" 83 | "Total time", "Gesamtzeit" 84 | "Left click to add/lock/unlock a handle", "Links-Klick zum Hinzufügen/Sperren/Lösen eines Bearbeitungspunkts" 85 | "Green - draggable", "Grün - Verschiebbar" 86 | "Red - locked", "Rot - Gesperrt" 87 | "Right click to delete a handle", "Rechts-Klick zum Löschen eines Bearbeitungspunkts" 88 | "Drag a handle to adjust track around locked point(s)", "Ziehe einen Bearbeitungspunkt, um die Route anzupassen" 89 | "Search", "Suchen" 90 | -------------------------------------------------------------------------------- /lang/it.txt: -------------------------------------------------------------------------------- 1 | "code", "it" 2 | "language", "Italiano" 3 | "translation", "tona.edoardo" 4 | "Events", "Gare" 5 | "Course", "Categoria" 6 | "Courses", "Categorie" 7 | "Controls", "Controlli" 8 | "Results", "Classifiche" 9 | "Draw", "Disegna" 10 | "Show", "Mostra" 11 | "Runners", "Atleti" 12 | "Routes", "Percorsi disegnati" 13 | "Route", "Percorso" 14 | "Name", "Nome" 15 | "Time", "Tempo" 16 | "Replay", "Replay" 17 | "All", "Tutti" 18 | "Help", "Aiuto" 19 | "Options", "Opzioni" 20 | "Reset", "Reset" 21 | "Zoom out", "Zoom indietro" 22 | "Zoom in", "Zoom avanti" 23 | "Splits", "Intertempi" 24 | "Splits table", "Tabella intertempi" 25 | "Select runners on Results tab", "Seleziona un atleta dalle classifiche" 26 | "Log in", "Log in" 27 | "User name", "Nome utente" 28 | "Password", "Password" 29 | "Show controls", "Mostra punti" 30 | "Hide controls", "Nascondi punti" 31 | "Show initials", "Mostra le iniziali" 32 | "Show names", "Mostra i nomi" 33 | "Hide names", "Nascondi i nomi" 34 | "Show info panel", "Mostra il pannello informazioni" 35 | "Hide info panel", "Nascondi il pannello informazioni" 36 | "Select an event", "Seleziona una gara" 37 | "Slower", "Più lento" 38 | "Run", "Corri" 39 | "Pause", "Pausa" 40 | "Faster", "Più veloce" 41 | "Real time", "Orario reale" 42 | "Mass start", "Mass start" 43 | "Start at", "Partenza da" 44 | "Length", "Lungh." 45 | "Full tails", "Traccia" 46 | "No results available", "Nessun risultato disponibile" 47 | "Configuration options", "Configurazione" 48 | "Language", "Lingua" 49 | "Map intensity", "Intensità mappa" 50 | "Route intensity %", "Intensità percorsi" 51 | "Route width", "Larghezza percorsi" 52 | "Replay label font size", "Grandezza scritta Replay" 53 | "Course overprint width", "Larghezza sovrastampa" 54 | "Control circle size", "Grandezza dei cerchi dei punti" 55 | "Snap to control when drawing", "Agganciati al punto mentre disegni" 56 | "Show +3 time loss for GPS routes", "Mostra +3 sec nei percorsi GPS" 57 | "Show GPS speed colours", "Mostra velocità nei percorsi GPS" 58 | "Event statistics", "Statistiche gara" 59 | "Drawn routes", "A mano" 60 | "GPS routes", "Con il GPS" 61 | "Comments", "Commenti" 62 | "Draw route", "Disegna percorso" 63 | "Select course", "Seleziona cat." 64 | "Select name", "Seleziona nome" 65 | "Type your comment", "Scrivi un tuo commento" 66 | "Load GPS file (GPX or TCX)", "Carica un file dal GPS (GPX o TCX)" 67 | "Save", "Salva" 68 | "+3 sec", "+3 sec" 69 | "Undo", "Annulla" 70 | "Save GPS route", "Salva percorso GPS" 71 | "Move track and map together (or right click-drag)", "Muovi percorso e mappa insieme (o sposta con il tasto destro)" 72 | "Map is georeferenced", "La mappa è georeferenziata" 73 | "International event", "Gara internazionale" 74 | "National event", "Gara nazionale" 75 | "Regional event", "Gara regionale" 76 | "Local event", "Gara promozionale" 77 | "Training event", "Allenamento" 78 | "Unknown", "Sconosciuto" 79 | "Warning", "Attenzione" 80 | "Your route has been saved", "Il tuo percorso è stato salvato" 81 | "Your route was not saved. Please try again", "Il tuo percorso non è stato salvato. Riprova" 82 | "Loading courses", "Caricamento tracciati" 83 | "Loading results", "Caricamento classifiche" 84 | "Loading map", "Caricamento mappa" 85 | "Loading routes", "Caricamento percorsi" 86 | "Saving courses", "Salvando i tracciati" 87 | "Saving results", "Salvando le classifiche" 88 | "Saving routes", "Salvando i percorsi" 89 | "Map loaded", "Mappa caricata" 90 | "Total time", "Tempo totale" 91 | "Left click to add/lock/unlock a handle", "Clicca con i sinistro per aggiungere/bloccare/sbloccare un punto" 92 | "Green - draggable", "Verde - spostabile" 93 | "Red - locked", "Rosso - bloccato" 94 | "Right click to delete a handle", "Clicca con il destro per cancellare un punto" 95 | "Drag a handle to adjust track around locked point(s)", "Sposta un punto per aggiustare il percorso attorno a un punto bloccato" 96 | "Autofit", "Adattamento automatico" 97 | "Route already drawn", "Percorso già disegnato" 98 | "If you draw a new route it will overwrite the old route for this runner.", "Se disegni un nuovo percorso, quello vecchio sarà sovrascritto." 99 | "GPS routes are saved separately and will not be overwritten.", "I percorsi GPS sono salvati separatamente e non saranno sovrascritti." 100 | "Search", "Cerca" 101 | -------------------------------------------------------------------------------- /lang/ja.txt: -------------------------------------------------------------------------------- 1 | "code", "ja" 2 | "language", "日本語" 3 | "translation", "tojo masaya" 4 | "Events", "イベント" 5 | "Course", "コース" 6 | "Courses", "コース" 7 | "Controls", "コントロール" 8 | "Results", "リザルト" 9 | "Draw", "記入" 10 | "Show", "表示" 11 | "Runners", "走者" 12 | "Routes", "ルート" 13 | "Route", "ルート" 14 | "Name", "名前" 15 | "Time", "タイム" 16 | "Replay", "再生" 17 | "All", "すべて" 18 | "Help", "ヘルプ" 19 | "Options", "オプション" 20 | "Reset", "リセット" 21 | "Zoom out", "縮小" 22 | "Zoom in", "拡大" 23 | "Rotate right", "右回転" 24 | "Rotate left", "左回転" 25 | "Splits", "スプリット" 26 | "Splits table", "スプリットテーブル" 27 | "Select runners on Results tab", "リザルトタブで走者を選択してください" 28 | "Log in", "ログイン" 29 | "User name", "ユーザ名" 30 | "Password", "パスワード" 31 | "Show controls", "全コントロールを表示" 32 | "Hide controls", "使われていないコントロールを非表示" 33 | "Show initials", "イニシャルを表示" 34 | "Show names", "名前を表示" 35 | "Hide names", "名前を非表示" 36 | "Show info panel", "インフォパネルを表示" 37 | "Hide info panel", "インフォパネルを非表示" 38 | "Select an event", "イベントを選択してください" 39 | "Slower", "遅く" 40 | "Run", "実行" 41 | "Pause", "一時停止" 42 | "Faster", "速く" 43 | "Real time", "リアルタイム" 44 | "Mass start", "マススタート" 45 | "Start at", "開始位置" 46 | "Length", "長さ" 47 | "Full tails", "尻尾を全表示" 48 | "No results available", "使用可能なリザルトがありません" 49 | "Configuration options", "オプション設定" 50 | "Language", "言語" 51 | "Map intensity", "地図の輝度" 52 | "Route intensity %", "ルートの輝度" 53 | "Route width", "ルート線の幅" 54 | "Replay label font size", "名前ラベルのフォントサイズ" 55 | "Course overprint width", "コース線の幅" 56 | "Control circle size", "コントロール円のサイズ" 57 | "Snap to control when drawing", "ルート記入時にコントロールに吸着" 58 | "Show +3 time loss for GPS routes", "GPSルートでタイムロスの +3 を表示" 59 | "Show GPS speed colours", "GPSスピードカラーを表示" 60 | "Event statistics", "イベント統計" 61 | "Drawn routes", "記入済ルート" 62 | "GPS routes", "GPSルート" 63 | "Comments", "コメント" 64 | "Draw route", "ルートを記入" 65 | "Select course", "コースを選択" 66 | "Select name", "名前を選択" 67 | "Type your comment", "コメントを入力してください" 68 | "Load GPS file (GPX or TCX)", "GPSファイルをロード (GPX または TCX)" 69 | "Save", "保存" 70 | "+3 sec", "+3 秒" 71 | "Undo", "元に戻す" 72 | "Save GPS route", "GPSルートを保存" 73 | "Align map to next control", "次のコントロールに地図を合わせる" 74 | "Move track and map together (or right click-drag)", 75 | "GPSトラックと地図を同時に移動 (または右クリックをしながらドラッグ)" 76 | "Map is georeferenced", "ジオレファレンス済の地図" 77 | "International event", "国際イベント" 78 | "National event", "全国イベント" 79 | "Regional event", "地区イベント" 80 | "Local event", "ローカルイベント" 81 | "Training event", "トレーニングイベント" 82 | "Unknown", "不明" 83 | "Warning", "警告" 84 | "Your route has been saved", "ルートが保存されました" 85 | "Your route was not saved. Please try again", "ルートが保存されませんでした。再度保存を行ってください" 86 | "Loading courses", "コースをロード中" 87 | "Loading results", "リザルトをロード中" 88 | "Loading map", "地図をロード中" 89 | "Loading routes", "ルートをロード中" 90 | "Saving courses", "コースを保存中" 91 | "Saving results", "リザルトを保存中" 92 | "Saving routes", "ルートを保存中" 93 | "Map loaded", "地図がロードされました" 94 | "Total time", "合計時間" 95 | "Left click to add/lock/unlock a handle", "左クリックでハンドルを追加/ロック/アンロック" 96 | "Green - draggable", "緑 - ドラッグ可能" 97 | "Red - locked", "赤 - ロック中" 98 | "Right click to delete a handle", "右クリックでハンドルを削除" 99 | "Drag a handle to adjust track around locked point(s)", "ロックされた点の周りでハンドルをドラッグしてトラックを調節" 100 | "Autofit", "オートフィット" 101 | "Route already drawn", "ルートがすでに記入されています" 102 | "If you draw a new route it will overwrite the old route for this runner.", 103 | "新しくルートを記入するとこの走者の古いルートが上書きされます" 104 | "GPS routes are saved separately and will not be overwritten.", "GPSルートは別途保存され、上書きさません。" 105 | "Search", "検索" 106 | -------------------------------------------------------------------------------- /lang/no.txt: -------------------------------------------------------------------------------- 1 | "code", "no" 2 | "language", "Norsk" 3 | "translation", "olav@kvittem.no" 4 | "Events", "Løp" 5 | "Course", "Løype" 6 | "Courses", "Løyper" 7 | "Results", "Resultater" 8 | "Draw", "Tegn" 9 | "Show", "Vis" 10 | "Runners", "Løpere" 11 | "Routes", "Ruter" 12 | "Route", "Rute" 13 | "Name", "Navn" 14 | "Time", "Tid" 15 | "Replay", "Omløp" 16 | "All", "Alle" 17 | "Help", "Hjelp" 18 | "Options", "Valg" 19 | "Reset", "Nullstill" 20 | "Zoom out", "Utvid" 21 | "Zoom in", "Innskrenk" 22 | "Splits", "Strekk" 23 | "Splits table", "Strekk-tabell" 24 | "Select runners on Results tab", "Velg løpere under Resultat-fliken" 25 | "Log in", "Logg inn" 26 | "User name", "Brukernavn" 27 | "Password", "Passord" 28 | "Show controls", "Vis poster" 29 | "Hide controls", "Gjem poster" 30 | "Show initials", "Vis forbokstaver" 31 | "Show names", "Vis navn" 32 | "Hide names", "Gjem navn" 33 | "Show info panel", "Vis info panel" 34 | "Hide info panel", "Gjem info panel" 35 | "Select an event", "Velg et løp" 36 | "Slower", "Saktere" 37 | "Run", "Løp" 38 | "Pause", "Pause" 39 | "Faster", "Fortere" 40 | "Real time", "Sanntid" 41 | "Mass start", "Fellesstart" 42 | "Start at", "Start ved" 43 | "Length", "Lengde" 44 | "Full tails", "Lange hale" 45 | "No results available", "Ingen resultater tilgjengelig" 46 | "Configuration options", "Brukervalg" 47 | "Language", "Språk" 48 | "Map intensity", "Styrke på kart" 49 | "Route intensity %", "Styrke på spor" 50 | "Route width", "Bredde på spor" 51 | "Replay label font size", "Bokstavstørrelse Omatt" 52 | "Course overprint width", "Strekbredde løype" 53 | "Control circle size", "Poststørrelse" 54 | "Snap to control when drawing", "Fang til post ved tegning" 55 | "Show +3 time loss for GPS routes", "Vis +3 tidstap for GPS spor" 56 | "Show GPS speed colours", "Vis farger for fart med GPS" 57 | "Event statistics", "Løps-statistikk" 58 | "Drawn routes", "Tegnede spor" 59 | "GPS routes", "GPS spor" 60 | "Comments", "Kommentarer" 61 | "Draw route", "Tegn spor" 62 | "Select course", "Velg løype" 63 | "Select name", "Velg navn" 64 | "Type your comment", "Kommenter" 65 | "Load GPS file (GPX or TCX)", "Last GPS fil (GPX or TCX)" 66 | "Save", "Lagre" 67 | "+3 sec", "+3 sek" 68 | "Undo", "Angre" 69 | "Save GPS route", "Lagre GPS-spor" 70 | "Move track and map together (or right click-drag)", "Flytt spor og kart sammen(eller dra og høyreklikk)" 71 | "Map is georeferenced", "Kartet er georeferert" 72 | "International event", "Internasjonalt løp" 73 | "National event", "Nasjonalt løp" 74 | "Regional event", "Regionalt løp" 75 | "Local event", "Lokalt løp" 76 | "Training event", "Treningsløp" 77 | "Unknown", "Ukjent" 78 | "Warning", "Advarsel" 79 | "Your route has been saved", "Sporet er lagret" 80 | "Your route was not saved. Please try again", "Sporet ble ikke lagret - prøv igjen" 81 | "Loading courses", "Laster løyper" 82 | "Loading results", "Laster resultater" 83 | "Loading map", "Laster kart" 84 | "Loading routes", "kart spor" 85 | "Saving courses", "Lagrer løyper" 86 | "Saving results", "Lagrer resultater" 87 | "Saving routes", "Lagrer resultater" 88 | "Map loaded", "Kart lastet" 89 | "Total time", "Total tid" 90 | "Left click to add/lock/unlock a handle", "Venstreklikk for å legge til/feste/løsne et håndtak" 91 | "Green - draggable", "Grønn - flyttbar" 92 | "Red - locked", "Rød - festet" 93 | "Right click to delete a handle", "Høyreklikk for å fjerne et håndtak" 94 | "Drag a handle to adjust track around locked point(s)", "Dra et håndtak for å justere et spor mellom festepunkter" 95 | "Search", "Søk" 96 | "Align map to next control", "Tilpass kartet mot neste post" 97 | -------------------------------------------------------------------------------- /lang/ru.txt: -------------------------------------------------------------------------------- 1 | "language", "Русский" 2 | "code", "ru" 3 | "translation", "seikin.alexey@gmail.com" 4 | "Events", "События" 5 | "Course", "Дистанция" 6 | "Courses", "Дистанции" 7 | "Results", "Результаты" 8 | "Draw", "Нарисовать" 9 | "Show", "Показать" 10 | "Runners", "Участники" 11 | "Routes", "Пути движения" 12 | "Route", "Путь движения" 13 | "Name", "Имя" 14 | "Time", "Время" 15 | "Replay", "Анимация трека" 16 | "All", "Все" 17 | "Help", "Помощь" 18 | "Options", "Настройки" 19 | "Reset", "Сбросить" 20 | "Zoom out", "Отдалить карту" 21 | "Zoom in", "Приблизить карту" 22 | "Splits", "Сплиты" 23 | "Splits table", "Таблица сплитов" 24 | "Select runners on Results tab", "Отобразить участников в таблице Результатов" 25 | "Log in", "Вход в панель управления" 26 | "User name", "Имя пользователя" 27 | "Password", "Пароль" 28 | "Show controls", "Показать пункты управления" 29 | "Hide controls", "Скрыть панель управления" 30 | "Show initials", "Отобразить ФИО" 31 | "Show names", "Отобразить имена" 32 | "Hide names", "Скрыть имена" 33 | "Show info panel", "Показать инфо-панель" 34 | "Hide info panel", "Скрыть инфо-панель" 35 | "Select an event", "Выбрать мероприятие" 36 | "Slower", "Медленнее" 37 | "Run", "Старт" 38 | "Pause", "Пауза" 39 | "Faster", "Быстрее" 40 | "Real time", "В режиме реального времени" 41 | "Mass start", "В режиме одновременного старта" 42 | "Start at", "Время старта" 43 | "Length", "Длина" 44 | "Full tails", "Трек полностью" 45 | "No results available", "Данных с результатами нет" 46 | "Configuration options", "Настройки" 47 | "Language", "Язык" 48 | "Map intensity", "Прозрачность карты" 49 | "Route intensity %", "Прозрачность трека" 50 | "Route width", "Ширина линии трека" 51 | "Replay label font size", "Размер шрифта на ярлыке участника" 52 | "Course overprint width", "Ширина дистанции" 53 | "Control circle size", "Размер символа КП" 54 | "Snap to control when drawing", "Щелкнуть для управления при рисовании" 55 | "Show +3 time loss for GPS routes", "Прибавить +3 к времени GPS трека" 56 | "Show GPS speed colours", "Отобразить скорость GPS трека цветами" 57 | "Event statistics", "Статистика событий" 58 | "Drawn routes", "Отрисованные треки" 59 | "GPS routes", "GPS-треки" 60 | "Comments", "Комментарии" 61 | "Draw route", "Нарисовать трек" 62 | "Select course", "Выбрать дистанцию" 63 | "Select name", "Выбрать имя" 64 | "Type your comment", "Написать комментарий" 65 | "Load GPS file (GPX or TCX)", "Загрузить свой трек(GPX oder TCX)" 66 | "Save", "Сохранить" 67 | "+3 sec", "+3 Сек." 68 | "Undo", "Отменить действие" 69 | "Save GPS route", "Сохранить GPS-трек" 70 | "Move track and map together (or right click-drag)", "Перемещать трек и карту одновременно (правый клик мыши - тащить)" 71 | "Map is georeferenced", "Карта привязана (georeferenced)" 72 | "International event", "Международный старт" 73 | "National event", "Национальный старт" 74 | "Regional event", "Региональный старт" 75 | "Local event", "Местное событие" 76 | "Training event", "Тренировочный старт" 77 | "Unknown", "Неизвестно" 78 | "Warning", "Внимание" 79 | "Your route has been saved", "Ваш маршрут был сохранен" 80 | "Your route was not saved. Please try again", "Ваш маршрут не сохранен. Попробовать еще раз" 81 | "Total time", "Итоговое время" 82 | "Left click to add/lock/unlock a handle", "Кликнуть левой кнопкой мыши для добавления/блокировки/разблокировки точки отметки" 83 | "Green - draggable", "Зеленый - можно перемещать" 84 | "Red - locked", "Красный - перемещение заблокировано" 85 | "Right click to delete a handle", "Кликнуть правой кнопкой мыши для удаления линии" 86 | "Drag a handle to adjust track around locked point(s)", "Тянуть точку отметки для выравнивания трека по заблокированной точке" 87 | "Search", "Найти" 88 | -------------------------------------------------------------------------------- /lock/readme.txt: -------------------------------------------------------------------------------- 1 | This /lock directory is used by rg2api.php to implement file locking so that multiple users can update 2 | the data files at the same time. When things are working correctly this directory will normally be empty. 3 | It will contain one or more temporary directories for a very short time (possibly up to around 5 seconds) when 4 | information is being saved. 5 | 6 | If a serious error occurs then a directory may not be deleted correctly, which will prevent RG2 from working. 7 | Any subdirectories in /lock can be deleted if necessary to restore correct operation. 8 | 9 | 10 | -------------------------------------------------------------------------------- /log/readme.txt: -------------------------------------------------------------------------------- 1 | This /log directory may contain a log file containing debug and logging information. The log 2 | file can be deleted at any point and will be automatically recreated as necessary. 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rg2", 3 | "private": false, 4 | "version": "2.1", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "test": "cross-env CYPRESS_COVERAGE=true vite", 9 | "test:all": "cross-env CYPRESS_COVERAGE=true node ./cypress/e2e/run-e2e-tests.cjs", 10 | "build": "grunt replace && vite build --debug && grunt copy:distfiles", 11 | "preview": "vite preview", 12 | "lint:js": "eslint .", 13 | "cypress": "cypress open --e2e --browser chrome", 14 | "deploy": "grunt deploy", 15 | "upver": "grunt replace" 16 | }, 17 | "engines": { 18 | "node": ">=18.18.0" 19 | }, 20 | "devDependencies": { 21 | "@cypress/code-coverage": "^3.13.2", 22 | "@eslint/js": "^9.11.1", 23 | "cross-env": "^7.0.3", 24 | "cypress": "^13.15.0", 25 | "cypress-pipe": "^2.0.0", 26 | "eslint": "^9.11.1", 27 | "eslint-config-prettier": "^9.1.0", 28 | "eslint-plugin-cypress": "^2.15.2", 29 | "fs-extra": "^11.1.0", 30 | "globals": "^15.9.0", 31 | "grunt": "^1.6.1", 32 | "grunt-cli": "^1.5.0", 33 | "grunt-contrib-clean": "^2.0.1", 34 | "grunt-contrib-copy": "^1.0.0", 35 | "grunt-text-replace": "^0.4.0", 36 | "load-grunt-tasks": "^5.1.0", 37 | "prettier": "^3.3.3", 38 | "rollup-plugin-visualizer": "^5.12.0", 39 | "sass": "^1.79.3", 40 | "vite": "^5.4.8", 41 | "vite-plugin-istanbul": "^6.0.2" 42 | }, 43 | "dependencies": { 44 | "@ag-grid-community/client-side-row-model": "~32.2.1", 45 | "@ag-grid-community/styles": "~32.2.1", 46 | "@popperjs/core": "^2.11.8", 47 | "axios": "^1.7.7", 48 | "bootstrap": "^5.3.3", 49 | "bootstrap-icons": "^1.11.3", 50 | "chart.js": "^4.4.4", 51 | "html-entities": "^2.5.2", 52 | "interactjs": "^1.10.27", 53 | "leaflet": "^1.9.4", 54 | "proj4": "^2.12.1", 55 | "toolcool-range-slider": "^4.0.28", 56 | "vanillajs-datepicker": "^1.3.4" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /public/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/public/img/apple-touch-icon.png -------------------------------------------------------------------------------- /public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/public/img/favicon.ico -------------------------------------------------------------------------------- /public/img/rg2-logo-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maprunner/rg2/eafae2e2e2c66d4fdc3ff6c51aa10aa276c8dfe9/public/img/rg2-logo-32x32.png -------------------------------------------------------------------------------- /public/img/rg2-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/lock/readme.txt: -------------------------------------------------------------------------------- 1 | This /lock directory is used by rg2api.php to implement file locking so that multiple users can update 2 | the data files at the same time. When things are working correctly this directory will normally be empty. 3 | It will contain one or more temporary directories for a very short time (possibly up to around 5 seconds) when 4 | information is being saved. 5 | 6 | If a serious error occurs then a directory may not be deleted correctly, which will prevent RG2 from working. 7 | Any subdirectories in /lock can be deleted if necessary to restore correct operation. 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/log/readme.txt: -------------------------------------------------------------------------------- 1 | This /log directory may contain a log file containing debug and logging information. The log 2 | file can be deleted at any point and will be automatically recreated as necessary. 3 | -------------------------------------------------------------------------------- /rg2-config .txt: -------------------------------------------------------------------------------- 1 | { 17 | // handlers that need kartatid (unhelpfully called id in the API) send it as a parameter: otherwise it will go as undefined which is OK 18 | onResponse(response.data.data, params.id) 19 | }) 20 | .catch(function (error) { 21 | reportJSONFail(errorMsg + ": " + error) 22 | }) 23 | .finally(function () { 24 | document.getElementById("rg2-container").style.cursor = "auto" 25 | }) 26 | } 27 | 28 | export function postApi(data, params, onResponse, errorMsg, handleKeksi = () => {}) { 29 | let config = { method: "post" } 30 | config.data = data 31 | if (params.headers) { 32 | config.headers = params.headers 33 | delete params.headers 34 | } 35 | params.t = new Date().getTime() 36 | config.params = params 37 | document.getElementById("rg2-container").style.cursor = "wait" 38 | rg2Axios(config) 39 | .then((response) => { 40 | if (response.data.keksi) { 41 | handleKeksi(response.data.keksi) 42 | } 43 | onResponse(response.data) 44 | }) 45 | .catch(function (error) { 46 | reportJSONFail(errorMsg + ": " + error) 47 | }) 48 | .finally(function () { 49 | document.getElementById("rg2-container").style.cursor = "auto" 50 | }) 51 | } 52 | 53 | function reportJSONFail(error) { 54 | document.getElementById("rg2-load-progress").classList.add("d-none") 55 | document.getElementById("rg2-map-load-progress").classList.add("d-none") 56 | showWarningDialog("Configuration error", error) 57 | } 58 | -------------------------------------------------------------------------------- /src/js/event.js: -------------------------------------------------------------------------------- 1 | import { Worldfile } from "./worldfile" 2 | 3 | export class Event { 4 | constructor(data) { 5 | this.kartatid = data.id 6 | this.mapid = data.mapid 7 | this.format = data.format 8 | this.name = data.name 9 | this.date = data.date 10 | this.club = data.club 11 | this.rawtype = data.type 12 | switch (data.type) { 13 | case "I": 14 | this.type = "International event" 15 | break 16 | case "N": 17 | this.type = "National event" 18 | break 19 | case "R": 20 | this.type = "Regional event" 21 | break 22 | case "L": 23 | this.type = "Local event" 24 | break 25 | case "T": 26 | this.type = "Training event" 27 | break 28 | default: 29 | this.type = "Unknown" 30 | break 31 | } 32 | this.comment = data.comment 33 | this.locked = data.locked 34 | this.courses = 0 35 | if (data.suffix === undefined) { 36 | this.mapfilename = this.mapid + "." + "jpg" 37 | } else { 38 | this.mapfilename = this.mapid + "." + data.suffix 39 | } 40 | this.worldfile = new Worldfile(data) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/js/georefs.js: -------------------------------------------------------------------------------- 1 | import { generateSelectOption } from "./utils" 2 | 3 | export class Georefs { 4 | constructor() { 5 | this.georefsystems = [] 6 | this.georefsystems.push({ name: "Not georeferenced", description: "none", params: "", value: 0 }) 7 | this.georefsystems.push({ 8 | name: "GB National Grid", 9 | description: "EPSG:27700", 10 | params: 11 | "+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 +x_0=400000 +y_0=-100000 +ellps=airy +datum=OSGB36 +units=m +no_defs", 12 | value: 1 13 | }) 14 | this.georefsystems.push({ 15 | name: "Google EPSG:900913", 16 | description: "EPSG:900913", 17 | params: 18 | "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs", 19 | value: 2 20 | }) 21 | if (rg2Config.epsg_code !== undefined) { 22 | // if more than one user-defined then they come in as |-separated strings 23 | const codes = rg2Config.epsg_code.split("|") 24 | const params = rg2Config.epsg_params.split("|") 25 | for (let i = 0; i < codes.length; i = i + 1) { 26 | this.georefsystems.push({ 27 | name: codes[i].replace(" ", ""), 28 | description: codes[i].replace(" ", ""), 29 | params: params[i], 30 | value: i + 3 31 | }) 32 | } 33 | // default value for select option is first of the newly added items, afetr None, GBNG and Google 34 | this.defaultGeorefVal = 3 35 | } else { 36 | // default to GB National Grid 37 | this.defaultGeorefVal = 1 38 | } 39 | } 40 | 41 | getDefaultValue() { 42 | return this.georefsystems[this.defaultGeorefVal].value 43 | } 44 | 45 | getGeorefDropdown() { 46 | let html = "" 47 | for (let i = 0; i < this.georefsystems.length; i += 1) { 48 | html += generateSelectOption(i, this.georefsystems[i].name, i === 0) 49 | } 50 | return html 51 | } 52 | 53 | getGeorefSystem(index) { 54 | return this.georefsystems[index] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/js/hash.js: -------------------------------------------------------------------------------- 1 | import { config } from "./config" 2 | let id = 0 3 | let courses = [] 4 | let routes = [] 5 | 6 | // expand an array of routes or courses into a comma-separated string 7 | function extractItems(items, introText) { 8 | if (items.length > 0) { 9 | return introText + items.join(",") 10 | } 11 | return "" 12 | } 13 | 14 | function generateHash(hashID, hashRoutes, hashCourses) { 15 | let hash = 0 16 | if (id !== 0) { 17 | hash = "#" + hashID + extractItems(hashCourses, "&course=") + extractItems(hashRoutes, "&route=") 18 | } 19 | return hash 20 | } 21 | 22 | export function getHashCourses() { 23 | return courses 24 | } 25 | 26 | export function getHashID() { 27 | return id 28 | } 29 | 30 | export function getHashRoutes() { 31 | return routes 32 | } 33 | 34 | export function getHashTab() { 35 | if (routes.length > 0) { 36 | return config.TAB_RESULTS 37 | } 38 | return config.TAB_COURSES 39 | } 40 | 41 | function getIDFromLocationHash(locationHash) { 42 | // id should be numeric and between # and & or end of string whichever comes first 43 | let id = locationHash.replace(/#([0-9]+)($|&).*/, "$1") 44 | //console.log("hash ", locationHash, "id ", id) 45 | if (id.length > 0) { 46 | return parseInt(id, 10) 47 | } 48 | return id 49 | } 50 | 51 | export function parseLocationHash(hash) { 52 | id = 0 53 | courses.length = 0 54 | routes.length = 0 55 | // input looks like #id&course=a,b,c&result=x,y,z 56 | let fields = hash.split("&") 57 | for (let i = 0; i < fields.length; i += 1) { 58 | fields[i] = fields[i].toLowerCase() 59 | if (fields[i].search("#") !== -1) { 60 | // remove # and anything else non-numeric before trying to convert to an integer 61 | id = parseInt(fields[i].replace(/\D/g, ""), 10) 62 | } 63 | if (fields[i].search("course=") !== -1) { 64 | courses = fields[i].replace("course=", "").split(",") 65 | } 66 | if (fields[i].search("route=") !== -1) { 67 | routes = fields[i].replace("route=", "").split(",") 68 | } 69 | } 70 | // convert to integers: NaNs sort themselves out on display so don't check here 71 | courses = courses.map(Number) 72 | routes = routes.map(Number) 73 | 74 | if (isNaN(id)) { 75 | id = 0 76 | courses.length = 0 77 | routes.length = 0 78 | } 79 | setLocationHash() 80 | return id 81 | } 82 | 83 | export function setEventHash(newid) { 84 | id = newid 85 | setLocationHash() 86 | } 87 | 88 | export function setHashCourses(displayedCourses) { 89 | courses = displayedCourses 90 | setLocationHash() 91 | } 92 | 93 | export function setHashRoutes(displayedRoutes) { 94 | routes = displayedRoutes 95 | setLocationHash() 96 | } 97 | 98 | function setLocationHash() { 99 | let hash = "" 100 | if (getIDFromLocationHash(window.location.hash) === id) { 101 | hash = generateHash(id, routes, courses) 102 | // console.log("Replace" + hash) 103 | window.history.replaceState({ hash: hash }, "", hash) 104 | } else { 105 | courses.length = 0 106 | routes.length = 0 107 | hash = generateHash(id, routes, courses) 108 | // console.log("Push " + hash) 109 | window.history.pushState({ hash: hash }, "", hash) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/js/main.js: -------------------------------------------------------------------------------- 1 | import "../scss/rg2.scss" 2 | // eslint-disable-next-line no-unused-vars 3 | import * as bootstrap from "bootstrap" 4 | import { initialiseCanvas } from "./canvas" 5 | import { config, loadConfigOptions } from "./config" 6 | import { initialiseCourses } from "./courses" 7 | import { doGetEvents, getActiveKartatID, isValidKartatID, loadEventByKartatID } from "./events" 8 | import { parseLocationHash } from "./hash" 9 | import { configureUI } from "./rg2ui" 10 | import { initLanguageOptions } from "./translate" 11 | import { showWarningDialog } from "./utils.js" 12 | 13 | window.assetUrl = function (filename) { 14 | return rg2Config.asset_url + filename 15 | } 16 | 17 | if (document.readyState !== "loading") { 18 | rg2init() 19 | } else { 20 | document.addEventListener("DOMContentLoaded", rg2init) 21 | } 22 | 23 | function rg2init() { 24 | const startup = document.querySelector(".rg2-startup") 25 | startup.style.opacity = 0 26 | startup.addEventListener("transitionend", () => { 27 | startup.remove() 28 | // TODO: looks odd but needed to avoid problems with Controls class 29 | initialiseCourses() 30 | loadConfigOptions() 31 | configureUI() 32 | initLanguageOptions() 33 | if (config.managing()) { 34 | import("./manager.js") 35 | .then((module) => { 36 | rg2Config.manager = module 37 | rg2Config.manager.initialiseManager(rg2Config.keksi) 38 | finishInit() 39 | }) 40 | .catch(() => { 41 | console.log("Error loading manager") 42 | showWarningDialog("Error loading manager", "Manager functionality failed to load.") 43 | }) 44 | } else { 45 | finishInit() 46 | } 47 | }) 48 | } 49 | 50 | function finishInit() { 51 | initialiseCanvas() 52 | window.onpopstate = handleNavigation 53 | // check if a specific event has been requested 54 | if (window.location.hash && !config.managing()) { 55 | parseLocationHash(window.location.hash) 56 | } 57 | document.getElementById("rg2-event-title").innerHTML = "Routegadget 2" 58 | doGetEvents() 59 | } 60 | 61 | function handleNavigation() { 62 | // console.log("Pop " + window.location.hash) 63 | // strange null popstates get generated when you toggle the left info panel via the rg2 logo 64 | // so just protect against it for now 65 | if (window.location.hash === "") { 66 | return 67 | } 68 | // don't try to do anything clever in manager 69 | if (!config.managing()) { 70 | // find out where we are trying to go 71 | const requestedKartatID = parseLocationHash(window.location.hash) 72 | if (isValidKartatID(requestedKartatID)) { 73 | // prevent double loading of events for cases where we get popstate for a change 74 | // triggered via RG2 interaction rather than browser navigation 75 | // ... or something like that: at least this seems to work in FF, Chrome and Edge 76 | // which is a start 77 | if (getActiveKartatID() !== requestedKartatID) { 78 | loadEventByKartatID(requestedKartatID) 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/js/mapdata.js: -------------------------------------------------------------------------------- 1 | import { Worldfile } from "./worldfile" 2 | 3 | // can't call this Map since that is a built-in class 4 | export class MapData { 5 | constructor(data) { 6 | if (data !== undefined) { 7 | // existing map from database 8 | this.mapid = data.mapid 9 | this.name = data.name 10 | // worldfile for GPS to map image conversion (for GPS files) 11 | this.worldfile = new Worldfile(data) 12 | // worldfile for local co-ords to map image conversion (for georeferenced courses) 13 | this.localworldfile = new Worldfile({ 14 | A: data.localA, 15 | B: data.localB, 16 | C: data.localC, 17 | D: data.localD, 18 | E: data.localE, 19 | F: data.localF 20 | }) 21 | if (data.mapfilename === undefined) { 22 | this.mapfilename = this.mapid + "." + "jpg" 23 | } else { 24 | this.mapfilename = data.mapfilename 25 | } 26 | } else { 27 | // new map to be added 28 | this.mapid = 0 29 | this.name = "" 30 | this.worldfile = new Worldfile(0) 31 | this.localworldfile = new Worldfile(0) 32 | } 33 | this.xpx = [] 34 | this.ypx = [] 35 | this.lat = [] 36 | this.lon = [] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/js/resultparser.js: -------------------------------------------------------------------------------- 1 | import { config } from "./config" 2 | import { ResultParserCSV } from "./resultparsercsv" 3 | import { ResultParserIOFV2 } from "./resultparseriofv2" 4 | import { ResultParserIOFV3 } from "./resultparseriofv3" 5 | import { extractAttributeZero, showWarningDialog } from "./utils" 6 | 7 | export class ResultParser { 8 | constructor(e, fileFormat) { 9 | this.results = [] 10 | this.resultCourses = [] 11 | this.valid = true 12 | const parsedResults = this.processResults(e, fileFormat) 13 | this.results = parsedResults.results 14 | this.valid = parsedResults.valid 15 | this.getCoursesFromResults() 16 | return { 17 | results: this.results, 18 | resultCourses: this.resultCourses, 19 | valid: this.valid 20 | } 21 | } 22 | 23 | getCoursesFromResults() { 24 | // creates an array of all course names in the results file 25 | for (let i = 0; i < this.results.length; i += 1) { 26 | // have we already found this course? 27 | let found = false 28 | for (let j = 0; j < this.resultCourses.length; j += 1) { 29 | if (this.resultCourses[j].course === this.results[i].course) { 30 | found = true 31 | break 32 | } 33 | } 34 | if (!found) { 35 | // courseid is set later when mapping is known 36 | this.resultCourses.push({ 37 | course: this.results[i].course, 38 | courseid: config.DO_NOT_SAVE_COURSE 39 | }) 40 | } 41 | } 42 | } 43 | 44 | processResults(e, fileFormat) { 45 | switch (fileFormat) { 46 | case "CSV": 47 | return new ResultParserCSV(e.target.result) 48 | case "XML": 49 | return this.processResultsXML(e.target.result) 50 | default: 51 | // shouldn't ever get here but... 52 | showWarningDialog("File type error", "Results file type is not recognised. Please select a valid file.") 53 | return { results: [], valid: false } 54 | } 55 | } 56 | 57 | processResultsXML(rawXML) { 58 | let version = "" 59 | let xml = undefined 60 | try { 61 | xml = new DOMParser().parseFromString(rawXML, "text/xml") 62 | const nodelist = xml.getElementsByTagName("ResultList") 63 | if (nodelist.length === 0) { 64 | showWarningDialog("XML file error", "File is not a valid XML results file. ResultList element missing.") 65 | return { results: [], valid: false } 66 | } 67 | // test for IOF Version 2 68 | version = extractAttributeZero(xml.getElementsByTagName("IOFVersion"), "version", "") 69 | if (version === "") { 70 | // test for IOF Version 3 71 | version = extractAttributeZero(xml.getElementsByTagName("ResultList"), "iofVersion", "") 72 | } 73 | } catch (err) { 74 | showWarningDialog("XML file error", "File is not a valid XML results file.") 75 | return { results: [], valid: false } 76 | } 77 | 78 | switch (version) { 79 | case "2.0.3": 80 | return new ResultParserIOFV2(xml) 81 | case "3.0": 82 | return new ResultParserIOFV3(xml) 83 | default: 84 | showWarningDialog("XML file error", "Invalid IOF file format. Version " + version + " not supported.") 85 | return { results: [], valid: false } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/js/translate.js: -------------------------------------------------------------------------------- 1 | import { getApi } from "./api" 2 | import { generateOption } from "./utils" 3 | 4 | // use English until we load something else 5 | let dictionary = {} 6 | dictionary.code = "en" 7 | let keys = [] 8 | 9 | export function createLanguageDropdown(languages) { 10 | let dropdown = document.getElementById("rg2-select-language") 11 | dropdown.innerHTML = "" 12 | let selected = dictionary.code === "en" 13 | dropdown.options.add(generateOption("en", "en: English", selected)) 14 | for (let i = 0; i < languages.length; i = i + 1) { 15 | selected = dictionary.code === languages[i].code 16 | dropdown.options.add(generateOption(languages[i].code, languages[i].code + ": " + languages[i].language, selected)) 17 | } 18 | dropdown.addEventListener("change", (e) => { 19 | const newlang = e.target.value 20 | if (newlang !== dictionary.code) { 21 | if (newlang === "en") { 22 | setNewLanguage("en") 23 | } else { 24 | doGetNewLanguage(newlang) 25 | } 26 | } 27 | }) 28 | } 29 | 30 | function doGetNewLanguage(lang) { 31 | const params = { type: "lang", lang: lang } 32 | getApi(params, handleLanguageResponse, "Language request failed") 33 | } 34 | 35 | function getEnglishKey(str) { 36 | // builds an array of everything that has been translated 37 | // and allocates a key for use in data-rg2t attribute 38 | const idx = keys.indexOf(str) 39 | if (idx > -1) { 40 | return idx 41 | } else { 42 | keys.push(str) 43 | return keys.length - 1 44 | } 45 | } 46 | 47 | function handleLanguageResponse(response) { 48 | setNewLanguage(response.lang) 49 | } 50 | 51 | export function initLanguageOptions() { 52 | // set available languages and set start language if requested 53 | if (rg2Config.start_language !== "en") { 54 | doGetNewLanguage(rg2Config.start_language) 55 | } 56 | } 57 | 58 | export function setNewLanguage(lang) { 59 | if (lang === "en") { 60 | dictionary = { code: "en" } 61 | } else { 62 | dictionary = lang 63 | } 64 | translateAllText() 65 | } 66 | 67 | export function t(str, element = "span") { 68 | const key = getEnglishKey(str) 69 | // eslint-disable-next-line no-prototype-builtins 70 | if (dictionary.hasOwnProperty(str)) { 71 | str = dictionary[str] 72 | } 73 | // passing an empty element returns an unformatted string 74 | if (element === "") { 75 | return str 76 | } 77 | // otherwise add a wrapper with data attribute to allow translation 78 | return `<${element} data-rg2t='${key}'>${str}` 79 | } 80 | 81 | function translateAllText() { 82 | // translates every element that has a data-rg2t attribute 83 | const elements = document.querySelectorAll("[data-rg2t]") 84 | for (let el of elements) { 85 | const key = parseInt(el.dataset.rg2t, 10) 86 | el.innerText = t(keys[key], "") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/js/user.js: -------------------------------------------------------------------------------- 1 | export class User { 2 | constructor(keksi) { 3 | this.x = "" 4 | this.y = keksi 5 | this.name = null 6 | this.password = null 7 | } 8 | 9 | setDetails(nameSelector, passwordSelector) { 10 | // mickey mouse 5 character requirement 11 | const regex = /...../ 12 | this.name = document.getElementById(nameSelector).value 13 | const nameValid = regex.test(this.name) 14 | this.password = document.getElementById(passwordSelector).value 15 | const passwordValid = regex.test(this.password) 16 | return nameValid && passwordValid 17 | } 18 | 19 | alterString(input, pattern) { 20 | let str = "" 21 | for (let i = 0; i < input.length; i += 1) { 22 | str += input.charAt(i) + pattern.charAt(i) 23 | } 24 | return str 25 | } 26 | 27 | encodeUser() { 28 | return { x: this.alterString(this.name + this.password, this.y), y: this.y } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/js/worldfile.js: -------------------------------------------------------------------------------- 1 | export class Worldfile { 2 | // see http://en.wikipedia.org/wiki/World_file 3 | constructor(wf) { 4 | if (wf.A === undefined) { 5 | this.valid = false 6 | this.A = 0 7 | this.B = 0 8 | this.C = 0 9 | this.D = 0 10 | this.E = 0 11 | this.F = 0 12 | } else { 13 | this.A = parseFloat(wf.A) 14 | this.B = parseFloat(wf.B) 15 | this.C = parseFloat(wf.C) 16 | this.D = parseFloat(wf.D) 17 | this.E = parseFloat(wf.E) 18 | this.F = parseFloat(wf.F) 19 | this.valid = true 20 | // helps make later calculations easier 21 | this.AEDB = wf.A * wf.E - wf.D * wf.B 22 | this.xCorrection = wf.B * wf.F - wf.E * wf.C 23 | this.yCorrection = wf.D * wf.C - wf.A * wf.F 24 | } 25 | } 26 | 27 | // use worldfile to generate latitude 28 | getLat(x, y) { 29 | return this.D * x + this.E * y + this.F 30 | } 31 | 32 | // use worldfile to generate longitude 33 | getLon(x, y) { 34 | return this.A * x + this.B * y + this.C 35 | } 36 | 37 | // use worldfile to generate X value 38 | getX(lng, lat) { 39 | return Math.round((this.E * lng - this.B * lat + this.xCorrection) / this.AEDB) 40 | } 41 | 42 | // use worldfile to generate y value 43 | getY(lng, lat) { 44 | return Math.round((-1 * this.D * lng + this.A * lat + this.yCorrection) / this.AEDB) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import path from "path" 3 | import { defineConfig } from "vite" 4 | import istanbul from "vite-plugin-istanbul" 5 | import { visualizer } from "rollup-plugin-visualizer" 6 | 7 | export default defineConfig(({ command, mode, ssrBuild }) => { 8 | const managerSource = [ 9 | "manager.js", 10 | "courseparser.js", 11 | "resultparseriofv2.js", 12 | "resultparseriofv3.js", 13 | "resultparsercsv.js", 14 | "resultparser.js", 15 | "managerui.js" 16 | ] 17 | return { 18 | base: "/rg2/", 19 | experimental: { 20 | renderBuiltUrl(filename, { hostType }) { 21 | if (hostType === "js") { 22 | return { runtime: `window.assetUrl(${JSON.stringify(filename)})` } 23 | } 24 | return { relative: true } 25 | } 26 | }, 27 | build: { 28 | outDir: "./dist", 29 | emptyOutDir: true, 30 | minify: "esbuild", 31 | manifest: true, 32 | sourcemap: true, 33 | rollupOptions: { 34 | input: path.resolve(__dirname, "src/js/main.js"), 35 | output: { 36 | manualChunks: (id) => { 37 | if (managerSource.some((module) => id.includes(module))) { 38 | return "manager" 39 | } 40 | if ( 41 | id.includes("leaflet") || 42 | id.includes("proj4") || 43 | id.includes("wkt-parser") || 44 | id.includes("mgrs") || 45 | id.includes("vanillajs-datepicker") 46 | ) { 47 | return "manager-utils" 48 | } 49 | if (id.includes("ag-grid-community")) { 50 | return "grid" 51 | } 52 | if (id.includes("node_modules")) { 53 | return "node-modules" 54 | } 55 | return "main" 56 | } 57 | } 58 | } 59 | }, 60 | // suppress Bootstrap warnings until it is updated 61 | css: { 62 | preprocessorOptions: { 63 | scss: { 64 | silenceDeprecations: ["mixed-decls", "color-functions", "legacy-js-api"] 65 | } 66 | } 67 | }, 68 | resolve: { 69 | alias: { 70 | "~ag-grid-community": path.resolve(__dirname, "node_modules/ag-grid-community"), 71 | "~bootstrap": path.resolve(__dirname, "node_modules/bootstrap"), 72 | "~bootstrap-icons": path.resolve(__dirname, "node_modules/bootstrap-icons"), 73 | "~datepicker": path.resolve(__dirname, "node_modules/vanillajs-datepicker") 74 | } 75 | }, 76 | server: { 77 | origin: "http://localhost", 78 | port: 5173, 79 | // needed for vite and Cypress to work together 80 | // see https://stackoverflow.com/questions/72324704/cypress-cant-load-assets-from-vites-devserver 81 | host: "127.0.0.1", 82 | strictPort: true, 83 | hot: true, 84 | hmr: { 85 | port: 5174, 86 | host: "127.0.0.1" 87 | } 88 | }, 89 | preview: { 90 | port: 5173, 91 | strictPort: true 92 | }, 93 | open: true, 94 | plugins: [ 95 | visualizer(), 96 | istanbul({ 97 | cypress: true, 98 | include: "src/*", 99 | exclude: ["node_modules", "test/"], 100 | extension: [".js"], 101 | requireEnv: true 102 | }) 103 | ] 104 | } 105 | }) 106 | --------------------------------------------------------------------------------