├── .dockerignore ├── .env.dist ├── .github └── workflows │ └── docker-image.yml ├── .gitignore ├── Caddyfile ├── Dockerfile ├── LICENSE.md ├── README.md ├── __tests__ ├── lib │ └── conquerUpdater.test.js └── mockupData │ └── TheFingersHex │ ├── 1.json │ ├── 2.json │ ├── 3.json │ ├── 4.json │ └── 5.json ├── app.js ├── bin └── www ├── config.dist.yml ├── docker-compose.yml ├── frontend ├── Components │ ├── Draft.vue │ ├── Stats.vue │ ├── VPCounter.vue │ └── VPCounterStats.vue ├── Interaction │ └── Merge.js ├── Search.js ├── admin.js ├── flags.js ├── index.js ├── main.js ├── mapControls.js ├── mapEditTools.js ├── measure.js ├── staticLayer.js ├── stats.js ├── style.scss ├── tools │ ├── arty.js │ ├── edit.js │ ├── icon.js │ ├── line.js │ ├── merge.js │ ├── polygon.js │ ├── scissor.js │ ├── select.js │ ├── sidebar.js │ └── sidebarArty.js └── webSocket.js ├── jsconfig.json ├── lib ├── ACLS.js ├── ambient.d.ts ├── config.js ├── conquerUpdater.js ├── discord.js ├── draftStatus.js ├── eventLog.js ├── featureLoader.js ├── fileHandler.js ├── session.js ├── updater.js └── warapi.js ├── package-lock.json ├── package.json ├── public ├── images │ ├── artilleryChevron.svg │ ├── artilleryTarget.svg │ ├── artilleryVector.svg │ ├── artilleryWindDir.svg │ ├── artilleryWindPip.svg │ ├── base │ │ ├── EmplacementHouse.svg │ │ ├── base.svg │ │ ├── base_frontline.svg │ │ ├── base_obs.svg │ │ ├── base_obs_t2.svg │ │ ├── base_sleep.svg │ │ ├── flag.png │ │ ├── flag.svg │ │ ├── flag2.png │ │ ├── flag2.svg │ │ ├── friendly_planned_intel_center.svg │ │ ├── friendly_planned_storm_cannon.svg │ │ ├── friendly_planned_weather_station.svg │ │ └── lock.svg │ ├── colonial.webp │ ├── cross.svg │ ├── crossColonial.png │ ├── crossWarden.png │ ├── facility-enemy │ │ ├── enemy_ammo_facility.svg │ │ ├── enemy_ass_mats_1.svg │ │ ├── enemy_ass_mats_2.svg │ │ ├── enemy_ass_mats_3.svg │ │ ├── enemy_ass_mats_4.svg │ │ ├── enemy_ass_mats_5.svg │ │ ├── enemy_base.svg │ │ ├── enemy_base_frontline.svg │ │ ├── enemy_base_obs.svg │ │ ├── enemy_base_obs_t2.svg │ │ ├── enemy_base_sleep.svg │ │ ├── enemy_broken_components.svg │ │ ├── enemy_cmats.svg │ │ ├── enemy_diesel.svg │ │ ├── enemy_drydock.svg │ │ ├── enemy_enriched.svg │ │ ├── enemy_facility_generic.svg │ │ ├── enemy_fire_equipment.svg │ │ ├── enemy_heavy.svg │ │ ├── enemy_pcmats.svg │ │ ├── enemy_petrol.svg │ │ ├── enemy_planned_intel_center.svg │ │ ├── enemy_planned_storm_cannon.svg │ │ ├── enemy_planned_weather_station.svg │ │ ├── enemy_scmats.svg │ │ ├── enemy_supplies.svg │ │ ├── enemy_tank_facility.svg │ │ ├── enemy_train_facility.svg │ │ ├── enemy_vehicle_facility.svg │ │ └── enemy_water.svg │ ├── facility-private │ │ ├── private_ammo_facility.svg │ │ ├── private_ass_mats_1.svg │ │ ├── private_ass_mats_2.svg │ │ ├── private_ass_mats_3.svg │ │ ├── private_ass_mats_4.svg │ │ ├── private_ass_mats_5.svg │ │ ├── private_broken_components.svg │ │ ├── private_cmats.svg │ │ ├── private_diesel.svg │ │ ├── private_drydock.svg │ │ ├── private_enriched.svg │ │ ├── private_facility_generic.svg │ │ ├── private_heavy.svg │ │ ├── private_pcmats.svg │ │ ├── private_petrol.svg │ │ ├── private_scmats.svg │ │ ├── private_supplies.svg │ │ ├── private_tank_facility.svg │ │ ├── private_train_facility.svg │ │ ├── private_vehicle_facility.svg │ │ └── private_water.svg │ ├── facility │ │ ├── Rail_Yard.svg │ │ ├── friendly_fire_equipment.svg │ │ ├── generator.svg │ │ ├── maintenance.svg │ │ ├── port.svg │ │ ├── public_ammo_facility.svg │ │ ├── public_ass_mats_1.svg │ │ ├── public_ass_mats_2.svg │ │ ├── public_ass_mats_3.svg │ │ ├── public_ass_mats_4.svg │ │ ├── public_ass_mats_5.svg │ │ ├── public_broken_components.svg │ │ ├── public_cmats.svg │ │ ├── public_diesel.svg │ │ ├── public_drydock.svg │ │ ├── public_enriched.svg │ │ ├── public_facility_generic.svg │ │ ├── public_heavy.svg │ │ ├── public_pcmats.svg │ │ ├── public_petrol.svg │ │ ├── public_scmats.svg │ │ ├── public_supplies.svg │ │ ├── public_tank_facility.svg │ │ ├── public_train_facility.svg │ │ ├── public_vehicle_facility.svg │ │ └── public_water.svg │ ├── favicon.png │ ├── favicon.svg │ ├── field │ │ ├── MapIconCoalFieldColor.png │ │ ├── MapIconComponentMineColor.png │ │ ├── MapIconComponentsColor.png │ │ ├── MapIconFacilityMineOilRig.png │ │ ├── MapIconOilFieldColor.png │ │ ├── MapIconSalvageColor.png │ │ ├── MapIconSalvageMineColor.png │ │ ├── MapIconSulfurColor.png │ │ ├── MapIconSulfurMineColor.png │ │ ├── coal_field.svg │ │ ├── comp_field.svg │ │ ├── comp_mine.svg │ │ ├── oil_field.svg │ │ ├── scrap_field.svg │ │ ├── scrap_mine.svg │ │ ├── sulfur_field.svg │ │ └── sulfur_mine.svg │ ├── humanQueue.svg │ ├── humanQueueColonial.png │ ├── humanQueueWarden.png │ ├── industry │ │ ├── MapIconAmmoFactory.png │ │ ├── MapIconCoastalGun.png │ │ ├── MapIconConstructionYard.png │ │ ├── MapIconFactory.png │ │ ├── MapIconManufacturing.png │ │ ├── MapIconMassProductionFactory.png │ │ ├── MapIconMedical.png │ │ ├── MapIconSeaport.png │ │ ├── MapIconShipyard.png │ │ ├── MapIconStorageFacility.png │ │ ├── MapIconTechCenter.png │ │ ├── MapIconVehicle.png │ │ └── MapIconWorkshop.png │ ├── information │ │ ├── WeatherEventRain.svg │ │ ├── WeatherEventSnow.svg │ │ ├── caution.svg │ │ ├── danger.svg │ │ ├── enemy_bridge_destroyed.svg │ │ ├── enemy_freighter_blockade.svg │ │ ├── friendly_bridge_destroyed.svg │ │ ├── friendly_freighter_blockade.svg │ │ ├── information.svg │ │ ├── minefield.svg │ │ ├── warning.svg │ │ └── warningIce.svg │ ├── og.png │ ├── og.svg │ ├── sign │ │ ├── Carriageway_Both_Points.svg │ │ ├── Carriageway_Left_Point_Only.svg │ │ ├── Carriageway_Right_Point_Only.svg │ │ ├── Dual_Carriageway.svg │ │ ├── End_Of_Dual_Carriageway.svg │ │ ├── dead_end.svg │ │ ├── dual_carriageway_ends_ahead.svg │ │ ├── keep_left.svg │ │ ├── keep_right.svg │ │ ├── level_crossing.svg │ │ ├── motorway.svg │ │ ├── motorway_end.svg │ │ ├── no_entry_sign.svg │ │ ├── no_stopping.svg │ │ ├── no_waiting.svg │ │ └── parking.svg │ ├── stormCannon │ │ ├── MapIconBorderBase.png │ │ ├── MapIconBunkerBaseTier1.png │ │ ├── MapIconBunkerBaseTier2.png │ │ ├── MapIconBunkerBaseTier3.png │ │ ├── MapIconFacilityVehicleFactory1.png │ │ ├── MapIconForwardBase1.png │ │ ├── MapIconIntelCenter.png │ │ ├── MapIconIntelCenterColonial.png │ │ ├── MapIconIntelCenterWarden.png │ │ ├── MapIconRocketGroundZero.png │ │ ├── MapIconRocketGroundZeroColonial.png │ │ ├── MapIconRocketGroundZeroWarden.png │ │ ├── MapIconRocketSite.png │ │ ├── MapIconRocketSiteColonial.png │ │ ├── MapIconRocketSiteWarden.png │ │ ├── MapIconRocketSiteWithRocket.png │ │ ├── MapIconRocketSiteWithRocketColonial.png │ │ ├── MapIconRocketSiteWithRocketWarden.png │ │ ├── MapIconRocketTarget.png │ │ ├── MapIconRocketTargetColonial.png │ │ ├── MapIconRocketTargetWarden.png │ │ ├── MapIconStormCannon.png │ │ ├── MapIconStormCannonColonial.png │ │ ├── MapIconStormCannonWarden.png │ │ ├── MapIconWeatherStation.png │ │ ├── MapIconWeatherStationColonial.png │ │ └── MapIconWeatherStationWarden.png │ ├── town │ │ ├── MapIconFortKeep.png │ │ ├── MapIconObservationTower.png │ │ ├── MapIconRelicBase.png │ │ ├── MapIconSafehouse.png │ │ ├── MapIconTownBaseTier1.png │ │ ├── MapIconTownBaseTier1Colonial.png │ │ ├── MapIconTownBaseTier1Nuke.png │ │ ├── MapIconTownBaseTier1Warden.png │ │ ├── MapIconTownBaseTier2.png │ │ ├── MapIconTownBaseTier2Colonial.png │ │ ├── MapIconTownBaseTier2Nuke.png │ │ ├── MapIconTownBaseTier2Warden.png │ │ ├── MapIconTownBaseTier3.png │ │ ├── MapIconTownBaseTier3Colonial.png │ │ ├── MapIconTownBaseTier3Nuke.png │ │ └── MapIconTownBaseTier3Warden.png │ └── warden.webp └── static.json ├── routes └── index.js ├── syncBackups.sh ├── tools ├── deadland.json ├── defaultFeatures.js ├── eventlog.js ├── fetchFields.js ├── imageTaint.html ├── moveDefaultFeatures.js ├── regionGenerator.js ├── staticUpdater.js └── voroni.js ├── views ├── access.html ├── admin.config.html ├── admin.eventlog.html ├── base.html ├── clock.svg ├── error.html ├── help.html ├── index.html ├── login.html ├── sidebar-arty.html ├── sidebar-edit.html └── stats.html ├── webpack.config.js ├── webpack.dev.js ├── webpack.prod.js └── websocket.js /.dockerignore: -------------------------------------------------------------------------------- 1 | backups 2 | node_modules 3 | sessions 4 | public/map 5 | data 6 | logs 7 | .git 8 | .dockerignore 9 | .gitignore 10 | .idea 11 | .env 12 | start.bash -------------------------------------------------------------------------------- /.env.dist: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | PORT=3000 3 | SECRET=secret 4 | COMMIT_HASH=dev -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: docker-image 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | - 'dev' 8 | tags: 9 | - 'v*' 10 | 11 | jobs: 12 | docker: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - 16 | name: Checkout 17 | uses: actions/checkout@v3 18 | - 19 | name: Docker meta 20 | id: meta 21 | uses: docker/metadata-action@v4 22 | with: 23 | images: | 24 | attribdd/foxhole-map-annotate 25 | tags: | 26 | type=ref,event=branch 27 | type=ref,event=pr 28 | type=semver,pattern={{version}} 29 | type=semver,pattern={{major}}.{{minor}} 30 | - 31 | name: Login to DockerHub 32 | if: github.event_name != 'pull_request' 33 | uses: docker/login-action@v2 34 | with: 35 | username: ${{ secrets.DOCKERHUB_USERNAME }} 36 | password: ${{ secrets.DOCKERHUB_TOKEN }} 37 | - 38 | name: Build and push 39 | uses: docker/build-push-action@v3 40 | with: 41 | context: . 42 | push: ${{ github.event_name != 'pull_request' }} 43 | tags: ${{ steps.meta.outputs.tags }} 44 | labels: ${{ steps.meta.outputs.labels }} 45 | build-args: | 46 | COMMIT_HASH=${{ github.sha }} 47 | - 48 | name: Build and push 49 | uses: docker/build-push-action@v3 50 | with: 51 | context: . 52 | target: caddy 53 | push: ${{ github.event_name != 'pull_request' }} 54 | tags: ${{ steps.meta.outputs.tags }}-caddy 55 | labels: ${{ steps.meta.outputs.labels }} 56 | build-args: | 57 | COMMIT_HASH=${{ github.sha }} 58 | 59 | deploy: 60 | needs: docker 61 | runs-on: ubuntu-latest 62 | if: github.event_name == 'push' && (github.ref_name == 'master' || github.ref_name == 'dev') 63 | steps: 64 | - 65 | name: Deploy to production 66 | uses: appleboy/ssh-action@v1.0.3 67 | with: 68 | host: ${{ secrets.PROD_HOST }} 69 | username: ${{ secrets.PROD_USER }} 70 | key: ${{ secrets.PROD_KEY }} 71 | passphrase: ${{ secrets.PASSPHRASE }} 72 | script: | 73 | docker pull attribdd/foxhole-map-annotate:${{ github.ref_name }} attribdd/foxhole-map-annotate:${{ github.ref_name }}-caddy 74 | cd /mnt/deployments/foxhole-map-annotate/ 75 | docker compose up -d -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | backups 3 | public/map* 4 | sessions 5 | data 6 | logs 7 | .env 8 | node_modules 9 | public/dist 10 | hidden 11 | 12 | ## VS Code 13 | .history 14 | .vscode -------------------------------------------------------------------------------- /Caddyfile: -------------------------------------------------------------------------------- 1 | :80 { 2 | root * /srv/ 3 | file_server 4 | 5 | @static { 6 | path /dist/* /images/* /map/* 7 | } 8 | 9 | handle @static { 10 | file_server 11 | header { 12 | Cache-Control "public, max-age=7200000, immutable" 13 | } 14 | } 15 | 16 | @notStatic { 17 | not path /dist/* /images/* /map/* 18 | } 19 | 20 | # Proxy non-static requests to the 'map' container 21 | reverse_proxy @notStatic map:3000 { 22 | transport http { 23 | versions h1 h2 24 | } 25 | header_up Host {host} 26 | header_up X-Real-IP {remote_host} 27 | header_up X-Forwarded-For {remote_host} 28 | header_up X-Forwarded-Proto {scheme} 29 | } 30 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:21 AS build 2 | ENV NODE_ENV=development 3 | WORKDIR /app 4 | COPY ["package.json", "package-lock.json*", "./"] 5 | RUN npm install 6 | COPY . . 7 | 8 | RUN npm run build 9 | 10 | FROM caddy:2.10.0-alpine AS caddy 11 | COPY --from=build /app/public /srv/ 12 | COPY Caddyfile /etc/caddy/Caddyfile 13 | 14 | FROM node:21-alpine 15 | ENV NODE_ENV=production 16 | WORKDIR /app 17 | COPY ["package.json", "package-lock.json*", "./"] 18 | RUN npm install --omit=dev 19 | 20 | COPY . . 21 | COPY --from=build /app/public/dist /app/public/dist 22 | 23 | ARG COMMIT_HASH 24 | ENV COMMIT_HASH=${COMMIT_HASH} 25 | 26 | CMD [ "node", "bin/www" ] -------------------------------------------------------------------------------- /__tests__/mockupData/TheFingersHex/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "regionId": 38, 3 | "scorchedVictoryTowns": 0, 4 | "mapItems": [ 5 | { 6 | "teamId": "NONE", 7 | "iconType": 20, 8 | "x": 0.8716014, 9 | "y": 0.58127743, 10 | "flags": 0, 11 | "viewDirection": 0 12 | }, 13 | { 14 | "teamId": "COLONIALS", 15 | "iconType": 45, 16 | "x": 0.4513862, 17 | "y": 0.36646345, 18 | "flags": 8, 19 | "viewDirection": 0 20 | } 21 | ], 22 | "mapItemsC": [], 23 | "mapItemsW": [], 24 | "mapTextItems": [], 25 | "lastUpdated": 1701336872072, 26 | "version": 10 27 | } -------------------------------------------------------------------------------- /__tests__/mockupData/TheFingersHex/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "regionId": 38, 3 | "scorchedVictoryTowns": 0, 4 | "mapItems": [ 5 | { 6 | "teamId": "NONE", 7 | "iconType": 20, 8 | "x": 0.8716014, 9 | "y": 0.58127743, 10 | "flags": 0, 11 | "viewDirection": 0 12 | }, 13 | { 14 | "teamId": "NONE", 15 | "iconType": 45, 16 | "x": 0.4513862, 17 | "y": 0.36646345, 18 | "flags": 8, 19 | "viewDirection": 0 20 | } 21 | ], 22 | "mapItemsC": [], 23 | "mapItemsW": [], 24 | "mapTextItems": [], 25 | "lastUpdated": 1701336872080, 26 | "version": 11 27 | } -------------------------------------------------------------------------------- /__tests__/mockupData/TheFingersHex/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "regionId": 38, 3 | "scorchedVictoryTowns": 0, 4 | "mapItems": [ 5 | { 6 | "teamId": "NONE", 7 | "iconType": 20, 8 | "x": 0.8716014, 9 | "y": 0.58127743, 10 | "flags": 0, 11 | "viewDirection": 0 12 | }, 13 | { 14 | "teamId": "WARDENS", 15 | "iconType": 45, 16 | "x": 0.4513862, 17 | "y": 0.36646345, 18 | "flags": 8, 19 | "viewDirection": 0 20 | }, 21 | { 22 | "teamId": "WARDENS", 23 | "iconType": 59, 24 | "x": 0.5513862, 25 | "y": 0.46646345, 26 | "flags": 0, 27 | "viewDirection": 0 28 | } 29 | ], 30 | "mapItemsC": [], 31 | "mapItemsW": [], 32 | "mapTextItems": [], 33 | "lastUpdated": 1701336872090, 34 | "version": 12 35 | } -------------------------------------------------------------------------------- /__tests__/mockupData/TheFingersHex/4.json: -------------------------------------------------------------------------------- 1 | { 2 | "regionId": 38, 3 | "scorchedVictoryTowns": 0, 4 | "mapItems": [ 5 | { 6 | "teamId": "NONE", 7 | "iconType": 20, 8 | "x": 0.8716014, 9 | "y": 0.58127743, 10 | "flags": 0, 11 | "viewDirection": 0 12 | }, 13 | { 14 | "teamId": "NONE", 15 | "iconType": 45, 16 | "x": 0.4513862, 17 | "y": 0.36646345, 18 | "flags": 8, 19 | "viewDirection": 0 20 | }, 21 | { 22 | "teamId": "WARDENS", 23 | "iconType": 59, 24 | "x": 0.5513862, 25 | "y": 0.46646345, 26 | "flags": 0, 27 | "viewDirection": 0 28 | }, 29 | { 30 | "teamId": "WARDENS", 31 | "iconType": 59, 32 | "x": 0.5523862, 33 | "y": 0.46646345, 34 | "flags": 0, 35 | "viewDirection": 0 36 | } 37 | ], 38 | "mapItemsC": [], 39 | "mapItemsW": [], 40 | "mapTextItems": [], 41 | "lastUpdated": 1701336872100, 42 | "version": 13 43 | } -------------------------------------------------------------------------------- /__tests__/mockupData/TheFingersHex/5.json: -------------------------------------------------------------------------------- 1 | { 2 | "regionId": 38, 3 | "scorchedVictoryTowns": 0, 4 | "mapItems": [ 5 | { 6 | "teamId": "NONE", 7 | "iconType": 20, 8 | "x": 0.8716014, 9 | "y": 0.58127743, 10 | "flags": 0, 11 | "viewDirection": 0 12 | }, 13 | { 14 | "teamId": "COLONIALS", 15 | "iconType": 45, 16 | "x": 0.4513862, 17 | "y": 0.36646345, 18 | "flags": 8, 19 | "viewDirection": 0 20 | }, 21 | { 22 | "teamId": "WARDENS", 23 | "iconType": 59, 24 | "x": 0.5523862, 25 | "y": 0.46646345, 26 | "flags": 0, 27 | "viewDirection": 0 28 | }, 29 | { 30 | "teamId": "COLONIALS", 31 | "iconType": 59, 32 | "x": 0.5513862, 33 | "y": 0.46646345, 34 | "flags": 0, 35 | "viewDirection": 0 36 | } 37 | ], 38 | "mapItemsC": [], 39 | "mapItemsW": [], 40 | "mapTextItems": [], 41 | "lastUpdated": 1701336872110, 42 | "version": 14 43 | } -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | import http from "node:http"; 8 | 9 | import debug from "debug"; 10 | 11 | import app from "../app.js"; 12 | 13 | /** 14 | * Get port from environment and store in Express. 15 | */ 16 | 17 | var port = normalizePort(process.env.PORT || '3000'); 18 | app.set('port', port); 19 | 20 | /** 21 | * Create HTTP server. 22 | */ 23 | 24 | var server = http.createServer(app); 25 | 26 | /** 27 | * Listen on provided port, on all network interfaces. 28 | */ 29 | 30 | server.listen(port); 31 | server.on('error', onError); 32 | server.on('listening', onListening); 33 | 34 | /** 35 | * Websocket 36 | */ 37 | import('../websocket.js') 38 | .then((module) => module.default) 39 | .then((startServer) => startServer(server)) 40 | 41 | /** 42 | * Normalize a port into a number, string, or false. 43 | */ 44 | 45 | function normalizePort(val) { 46 | var port = parseInt(val, 10); 47 | 48 | if (isNaN(port)) { 49 | // named pipe 50 | return val; 51 | } 52 | 53 | if (port >= 0) { 54 | // port number 55 | return port; 56 | } 57 | 58 | return false; 59 | } 60 | 61 | /** 62 | * Event listener for HTTP server "error" event. 63 | */ 64 | 65 | function onError(error) { 66 | if (error.syscall !== 'listen') { 67 | throw error; 68 | } 69 | 70 | var bind = typeof port === 'string' 71 | ? 'Pipe ' + port 72 | : 'Port ' + port; 73 | 74 | // handle specific listen errors with friendly messages 75 | switch (error.code) { 76 | case 'EACCES': 77 | console.error(bind + ' requires elevated privileges'); 78 | process.exit(1); 79 | break; 80 | case 'EADDRINUSE': 81 | console.error(bind + ' is already in use'); 82 | process.exit(1); 83 | break; 84 | default: 85 | throw error; 86 | } 87 | } 88 | 89 | /** 90 | * Event listener for HTTP server "listening" event. 91 | */ 92 | 93 | function onListening() { 94 | var addr = server.address(); 95 | var bind = typeof addr === 'string' 96 | ? 'pipe ' + addr 97 | : 'port ' + addr.port; 98 | var debugInstance = debug('foxhole-map-annotate:server'); 99 | debugInstance('Listening on ' + bind); 100 | } 101 | -------------------------------------------------------------------------------- /config.dist.yml: -------------------------------------------------------------------------------- 1 | access: 2 | users: {} 3 | roles: {} 4 | 5 | shard: 6 | name: Able 7 | url: war-service-live.foxholeservices.com 8 | 9 | discord: 10 | key: '' 11 | secret: '' 12 | 13 | basic: 14 | url: http://localhost:3000 15 | title: Warden Express 16 | color: '#245682' 17 | faction: Warden 18 | links: [] 19 | 20 | images: 21 | favicon: /images/favicon.svg 22 | faviconPng: /images/favicon.png 23 | opengraph: /images/og.png 24 | logo: /images/favicon.svg 25 | 26 | text: 27 | login: | 28 |

You need to be a verified Warden in WUH or 29 | WTG to see the Map.

30 | 31 | accessDenied: | 32 |

You need to be a verified Warden in WUH or WTG.

33 | 34 | feedback: | 35 |

If you have Feedback please contact us via WTAs Discord.

36 |

If you want more access, please also checkout the Discord from WTA.

37 |

Bugs can be reported at #wardenexpress-bugreports on WTAs Discord or at github

38 | 39 | contributors: | 40 |

Hosting: attrib

41 | 42 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | 3 | services: 4 | 5 | map: 6 | build: . 7 | image: "attribdd/foxhole-map-annotate:master" 8 | restart: always 9 | volumes: 10 | - ./public/map:/app/public/map 11 | - ./sessions:/app/sessions 12 | - ./data:/app/data 13 | environment: 14 | SECRET: sessionStorageSecret 15 | ports: 16 | - "3000:3000" 17 | expose: 18 | - 3000 19 | 20 | # optional, better performance for production to handle static files and remove some load from the map server 21 | caddy: 22 | build: 23 | context: . 24 | dockerfile: Dockerfile 25 | target: caddy 26 | image: "attribdd/foxhole-map-annotate:master-caddy" 27 | profiles: 28 | - caddy 29 | restart: always 30 | volumes: 31 | - ./Caddyfile:/etc/caddy/Caddyfile 32 | - ./public/map:/srv/map 33 | - ./data/caddy/data:/data 34 | - ./data/caddy/config:/config 35 | ports: 36 | - "8000:80" 37 | labels: 38 | - "traefik.enable=true" 39 | - "traefik.http.routers.map.rule=Host(`warden.express`)" 40 | - "traefik.http.routers.map.entrypoints=websecure" 41 | - "traefik.http.routers.map.tls.certresolver=le" 42 | -------------------------------------------------------------------------------- /frontend/Components/VPCounter.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 43 | 44 | 49 | 50 | -------------------------------------------------------------------------------- /frontend/Components/VPCounterStats.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 46 | 47 | 52 | 53 | -------------------------------------------------------------------------------- /frontend/Search.js: -------------------------------------------------------------------------------- 1 | import SearchFeature from "ol-ext/control/SearchFeature.js"; 2 | import LayerSwitcher from "ol-layerswitcher"; 3 | import { getCenter } from "ol/extent.js"; 4 | 5 | class Search extends SearchFeature { 6 | 7 | constructor() { 8 | super({ 9 | className: 'feature-search text-bg-light', 10 | property: 'notes', 11 | maxItems: 50, 12 | }); 13 | 14 | this.on('select', function (e) { 15 | const map = this.getMap(); 16 | if (map) { 17 | map.getView().animate({ 18 | center: getCenter(e.search.getGeometry().getExtent()), 19 | resolution: e.search.get('type') === 'Region' ? 1.75 : 0.75, 20 | duration: 2000, 21 | }) 22 | } 23 | }); 24 | } 25 | 26 | getSearchString(f) { 27 | return this.getTitle(f); 28 | } 29 | 30 | autocomplete(search) { 31 | const result = []; 32 | search = search.replace(/^\*/, ''); 33 | const rex = new RegExp(search, 'i'); 34 | let max = this.get('maxItems'); 35 | LayerSwitcher.forEachRecursive(this.getMap(), (layer) => { 36 | if (max <= 0) { 37 | return 38 | } 39 | const searchableLayer = layer.get('searchable') !== undefined ? layer.get('searchable') : true 40 | if (layer.getSource?.().getFeatures && searchableLayer) { 41 | // search by notes 42 | const features = layer.getSource().getFeatures() 43 | for (const feature of features) { 44 | let att = this.getSearchString(feature); 45 | if (att) { 46 | att.replaceAll("\n", ' ') 47 | } 48 | if (att && rex.test(att)) { 49 | result.push(feature); 50 | if ((--max) <= 0) { 51 | break; 52 | } 53 | continue; 54 | } 55 | // search by username 56 | const user = feature.get('user'); 57 | if (user !== undefined && rex.test(user)) { 58 | result.push(feature); 59 | if ((--max) <= 0) { 60 | break; 61 | } 62 | continue; 63 | } 64 | // search by clan 65 | const clan = feature.get('clan'); 66 | if (clan !== undefined && rex.test(clan)) { 67 | result.push(feature); 68 | if ((--max) <= 0) { 69 | break; 70 | } 71 | } 72 | } 73 | } 74 | }); 75 | return result; 76 | } 77 | 78 | } 79 | 80 | export default Search -------------------------------------------------------------------------------- /frontend/main.js: -------------------------------------------------------------------------------- 1 | import "./style.scss"; 2 | import * as bootstrap from 'bootstrap'; 3 | 4 | window.bootstrap = bootstrap -------------------------------------------------------------------------------- /frontend/stats.js: -------------------------------------------------------------------------------- 1 | import { createApp, reactive } from "vue"; 2 | 3 | import Stats from "./Components/Stats.vue"; 4 | import VPCounterStats from "./Components/VPCounterStats.vue"; 5 | import Socket from "./webSocket.js"; 6 | 7 | 8 | const data = reactive({ 9 | version: null, 10 | warStatus: '', 11 | requiredVictoryTowns: 32, 12 | conquerStatus: {}, 13 | warFeatures: {} 14 | }) 15 | 16 | const queue = reactive({queues: {}}) 17 | 18 | const socket = new Socket('/stats') 19 | socket.on('init', (initData) => { 20 | if (data.version === null) { 21 | data.version = initData.version 22 | } else if (data.version !== initData.version) { 23 | window.location.reload() 24 | } 25 | data.requiredVictoryTowns = initData.requiredVictoryTowns 26 | data.warStatus = initData.warStatus 27 | data.conquerStatus = initData.conquerStatus 28 | data.warFeatures = initData.warFeatures 29 | queue.queues = initData.queueStatus.queues 30 | }); 31 | 32 | socket.on('conquer', (conquerData) => { 33 | if (data.conquerStatus.version === conquerData.version) { 34 | return 35 | } 36 | if (!conquerData.full && conquerData.oldVersion !== data.conquerStatus.version) { 37 | socket.send('getConquerStatus', true) 38 | return 39 | } 40 | data.conquerStatus.version = conquerData.version 41 | data.conquerStatus.features = conquerData.full ? conquerData.features : {...data.conquerStatus.features, ...conquerData.features} 42 | data.conquerStatus.warNumber = conquerData.warNumber 43 | }); 44 | 45 | socket.on('queue', (queues) => { 46 | queue.queues = queues.queues 47 | }) 48 | 49 | createApp(Stats, { 50 | data: data, 51 | queueStatus: queue, 52 | }).mount('#map') 53 | 54 | createApp(VPCounterStats, { 55 | data: data, 56 | }).mount('#war-score') -------------------------------------------------------------------------------- /frontend/tools/arty.js: -------------------------------------------------------------------------------- 1 | import { Control } from "ol/control.js"; 2 | 3 | import { createCustomControlElement } from "../mapControls.js"; 4 | 5 | class Arty { 6 | 7 | /** 8 | * @param {EditTools} tools 9 | * @param {import("ol").Map} map 10 | */ 11 | constructor(tools, map) { 12 | this.map = map 13 | this.tools = tools 14 | this.controlElement = createCustomControlElement('triangle', (e, selected) => { 15 | tools.sidebarArty.bsOffcanvas.show() 16 | tools.sidebarArty.artyShow() 17 | this.controlElement.classList.remove('selected') 18 | }, { 19 | elementClass: 'arty-button', 20 | title: 'Toggle Artillery Calculator', 21 | }) 22 | this.control = new Control({ 23 | element: this.controlElement 24 | }) 25 | } 26 | } 27 | 28 | 29 | export default Arty 30 | 31 | -------------------------------------------------------------------------------- /frontend/tools/edit.js: -------------------------------------------------------------------------------- 1 | import { Control } from "ol/control.js"; 2 | import { altKeyOnly, shiftKeyOnly, singleClick } from "ol/events/condition.js"; 3 | import { Modify } from "ol/interaction.js"; 4 | 5 | import { createCustomControlElement } from "../mapControls.js"; 6 | 7 | class Edit { 8 | 9 | /** 10 | * @param {EditTools} tools 11 | * @param {import("ol").Map} map 12 | */ 13 | constructor(tools, map) { 14 | this.map = map 15 | this.tools = tools 16 | this.controlElement = createCustomControlElement('gear', (e, selected) => { 17 | tools.changeMode(selected) 18 | }, { 19 | elementClass: 'edit-button', 20 | title: 'Toggle EditMode (e)', 21 | }) 22 | this.control = new Control({ 23 | element: this.controlElement 24 | }) 25 | document.addEventListener('keydown', (event) => { 26 | if (event.target.nodeName.toLowerCase() === 'input' || event.target.nodeName.toLowerCase() === 'textarea') { 27 | return 28 | } 29 | if (event.key === 'e') { 30 | if (!tools.editMode) { 31 | this.controlElement.classList.add('selected') 32 | tools.changeMode(true) 33 | } 34 | } 35 | if (event.key === 'Escape') { 36 | if (tools.editMode) { 37 | this.controlElement.classList.remove('selected') 38 | tools.changeMode(false) 39 | } 40 | } 41 | }) 42 | tools.on(tools.EVENT_EDIT_MODE_ENABLED, this.editModeEnabled) 43 | tools.on(tools.EVENT_EDIT_MODE_DISABLED, this.editModeDisabled) 44 | 45 | this.modify = new Modify({ 46 | features: this.tools.select.getFeatures(), 47 | deleteCondition: (event) => { 48 | if (altKeyOnly(event) && singleClick(event)) { 49 | event.stopPropagation() 50 | return true 51 | } 52 | if (shiftKeyOnly(event) && singleClick(event)) { 53 | event.stopPropagation() 54 | const feature = this.tools.select.getFeatures().pop() 55 | const type = feature.get('type') 56 | this.tools.emit(type + '-deselected', feature) 57 | if (Object.keys(this.tools.iconTools).includes(type)) { 58 | this.tools.emit(this.tools.EVENT_ICON_DELETED, feature) 59 | } 60 | this.tools.select.changed() 61 | return false 62 | } 63 | return false 64 | } 65 | }); 66 | } 67 | 68 | editModeEnabled = () => { 69 | this.map.addInteraction(this.modify) 70 | this.map.addInteraction(this.tools.line.snap) 71 | } 72 | 73 | 74 | editModeDisabled = () => { 75 | this.map.removeInteraction(this.modify) 76 | this.map.removeInteraction(this.tools.line.snap) 77 | } 78 | } 79 | 80 | 81 | export default Edit -------------------------------------------------------------------------------- /frontend/tools/merge.js: -------------------------------------------------------------------------------- 1 | import MergeInteraction from "../Interaction/Merge.js"; 2 | 3 | class Merge { 4 | 5 | /** 6 | * @param {EditTools} tools 7 | * @param {import("ol").Map} map 8 | */ 9 | constructor(tools, map) { 10 | this.map = map 11 | this.merge = new MergeInteraction({ 12 | features: tools.line.allLinesCollection 13 | }) 14 | 15 | this.merge.on('mergedLine', (event) => { 16 | for (let feature of event.oldFeatures) { 17 | tools.emit(tools.EVENT_ICON_DELETED, feature) 18 | } 19 | tools.emit(tools.EVENT_ICON_ADDED, event.newFeature) 20 | tools.changeTool(false) 21 | }) 22 | 23 | tools.on(tools.EVENT_EDIT_MODE_DISABLED, this.toolDeSelected) 24 | tools.on(tools.EVENT_TOOL_SELECTED, (selectedTool) => { 25 | if (selectedTool === 'merge') { 26 | this.toolSelected() 27 | } else { 28 | this.toolDeSelected() 29 | } 30 | }) 31 | } 32 | 33 | toolSelected = () => { 34 | this.map.addInteraction(this.merge) 35 | } 36 | 37 | toolDeSelected = () => { 38 | this.map.removeInteraction(this.merge) 39 | } 40 | 41 | } 42 | 43 | export default Merge -------------------------------------------------------------------------------- /frontend/tools/scissor.js: -------------------------------------------------------------------------------- 1 | import Split from "ol-ext/interaction/Split.js"; 2 | import { Vector as VectorSource } from "ol/source.js"; 3 | 4 | class Scissor { 5 | 6 | /** 7 | * @param {EditTools} tools 8 | * @param {import("ol").Map} map 9 | */ 10 | constructor(tools, map) { 11 | this.map = map 12 | const fakeSource = new VectorSource({ 13 | features: tools.line.allLinesCollection, 14 | }); 15 | this.split = new Split({ 16 | sources: fakeSource, 17 | }) 18 | 19 | this.split.on('aftersplit', (event) => { 20 | tools.emit(tools.EVENT_ICON_DELETED, event.original) 21 | for (let feature of event.features) { 22 | feature.set('id', null) 23 | // All new features by split will be added by event with a new id 24 | fakeSource.removeFeature(feature) 25 | tools.emit(tools.EVENT_ICON_ADDED, feature) 26 | } 27 | tools.changeTool(false) 28 | }) 29 | 30 | tools.on(tools.EVENT_EDIT_MODE_DISABLED, this.toolDeSelected) 31 | tools.on(tools.EVENT_TOOL_SELECTED, (selectedTool) => { 32 | if (selectedTool === 'scissor') { 33 | this.toolSelected() 34 | } else { 35 | this.toolDeSelected() 36 | } 37 | }) 38 | } 39 | 40 | toolSelected = () => { 41 | this.map.addInteraction(this.split) 42 | } 43 | 44 | toolDeSelected = () => { 45 | this.map.removeInteraction(this.split) 46 | } 47 | 48 | } 49 | 50 | export default Scissor -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "checkJs": true, 4 | "target": "ESNext", 5 | "module": "NodeNext", 6 | "moduleResolution": "NodeNext", 7 | "strict": true, 8 | "noUncheckedIndexedAccess": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "skipLibCheck": true, 11 | "resolveJsonModule": true 12 | }, 13 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /lib/ambient.d.ts: -------------------------------------------------------------------------------- 1 | import type session from "express-session"; 2 | import type { GrantSession, GrantResponse } from "grant"; 3 | 4 | import type { Access } from "./ACLS.js"; 5 | import type { Session, SessionData } from "express-session"; 6 | 7 | declare global { 8 | namespace NodeJS { 9 | interface ProcessEnv { 10 | NODE_ENV: 11 | | "test" 12 | | "development" 13 | | "production" 14 | | (string & NonNullable) /** Wildcard */; 15 | PORT?: string; 16 | SESSION_SECRET: string; 17 | DISCORD_CLIENT_ID: string; 18 | DISCORD_CLIENT_SECRET: string; 19 | DISCORD_CALLBACK_URL: string; 20 | } 21 | } 22 | } 23 | 24 | declare module "express-session" { 25 | export interface SessionData { 26 | user: string; 27 | userId: string; 28 | discordId: string | null; 29 | acl: Access; 30 | lastLoginCheck: number; 31 | grant: GrantSession; 32 | } 33 | } 34 | 35 | declare module "grant" { 36 | export interface GrantResponse { 37 | access_token_end: number | undefined; 38 | } 39 | } 40 | 41 | declare module "http" { 42 | export interface IncomingMessage { 43 | session: Session & Partial; 44 | } 45 | } -------------------------------------------------------------------------------- /lib/featureLoader.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | 4 | import hash from "object-hash"; 5 | 6 | import { featureUpdater } from "./updater.js"; 7 | import { delayedSave } from "./fileHandler.js"; 8 | 9 | const FEATURE_FILE = path.resolve('data/features.json') 10 | 11 | export function loadFeatures() { 12 | if (!fs.existsSync(FEATURE_FILE)) { 13 | return defaultFeatures() 14 | } 15 | const content = fs.readFileSync(FEATURE_FILE, 'utf8') 16 | const parsed = /** @type{UserMapFeatures} */ (JSON.parse(content)) 17 | return featureUpdater(parsed) 18 | } 19 | 20 | /** 21 | * Load the default user map features from the file system 22 | * @returns {UserMapFeatures} 23 | */ 24 | export function defaultFeatures() { 25 | if (!fs.existsSync(path.resolve('data/defaultFeatures.json'))) { 26 | return { 27 | type: "FeatureCollection", 28 | features: [], 29 | hash: "" 30 | } 31 | } 32 | const content = fs.readFileSync(path.resolve('data/defaultFeatures.json'), 'utf8') 33 | const parsed = /** @type{UserMapFeatures} */ (JSON.parse(content)); 34 | parsed.features.forEach((feature) => { 35 | feature.properties.time = (new Date()).toISOString() 36 | }) 37 | return featureUpdater(parsed) 38 | } 39 | 40 | /** 41 | * Saves the features to the file system 42 | * @param {UserMapFeatures} features 43 | * @returns {void} 44 | */ 45 | export function saveFeatures(features) { 46 | features.hash = hash(features) 47 | return delayedSave(FEATURE_FILE, features, 10_000, false) 48 | } 49 | 50 | /** 51 | * User Map Features JSON File 52 | * @typedef {object} UserMapFeatures 53 | * @property {"FeatureCollection"} type 54 | * @property {UserMapFeature[]} features 55 | * @property {string} hash 56 | */ 57 | 58 | /** 59 | * User Map Feature Properties 60 | * @typedef {object} UserMapFeatureProperties 61 | * @property {string} [time] 62 | * @property {string} user 63 | * @property {string} userId 64 | * @property {?string} discordId 65 | * @property {string} notes 66 | * @property {string} [color] 67 | * @property {string} [clan] 68 | * @property {string} [lineType] 69 | * @property {string} id 70 | * @property {string} muser 71 | * @property {string} muserId 72 | * @property {string} type 73 | * @property {string[]} flags 74 | */ 75 | 76 | /** 77 | * User Map Feature Geometry 78 | * @typedef {object} UserMapFeatureGeometry 79 | */ 80 | 81 | /** 82 | * User Map Feature 83 | * @typedef {object} UserMapFeature 84 | * @property {"Feature"} type 85 | * @property {UserMapFeatureProperties} properties 86 | * @property {UserMapFeatureGeometry} geometry 87 | * @property {string} id 88 | * @property {number} [angle] 89 | */ -------------------------------------------------------------------------------- /lib/fileHandler.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | 3 | /** @type{Record} */ 4 | const timers = {}; 5 | 6 | /** 7 | * Synthetic delay to debounce file writes 8 | * @param {string} file 9 | * @param {unknown} data 10 | * @param {number} delay 11 | * @param {boolean} formatted 12 | * @returns {void} 13 | */ 14 | export function delayedSave(file, data, delay = 1000, formatted = true) { 15 | if (file in timers) { 16 | return; 17 | } 18 | timers[file] = setTimeout(() => { 19 | // !! Blocking 20 | fs.writeFile( 21 | file, 22 | // !! Throws 23 | formatted ? JSON.stringify(data, null, 2) : JSON.stringify(data), 24 | (err) => { 25 | delete timers[file]; 26 | if (err) { 27 | console.error(err); 28 | } 29 | } 30 | ); 31 | }, delay); 32 | } 33 | -------------------------------------------------------------------------------- /lib/session.js: -------------------------------------------------------------------------------- 1 | import session from "express-session"; 2 | import fileStore from "session-file-store"; 3 | 4 | import config from "./config.js"; 5 | 6 | const FileStore = fileStore(session); 7 | /** @type {fileStore.Options} */ 8 | const fileStoreOptions = { 9 | ttl: 86400 * 2, // 2 day 10 | retries: 1, 11 | }; 12 | 13 | /** 14 | * Express Session using session-file-store as a backend 15 | */ 16 | export const sessionParser = session({ 17 | secret: process.env.SECRET ?? "grant", 18 | store: new FileStore(fileStoreOptions), 19 | saveUninitialized: true, 20 | resave: false, 21 | cookie: { 22 | maxAge: 86400000 * 2, // 2 day 23 | secure: config.config.basic.url.startsWith("https://"), 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /lib/updater.js: -------------------------------------------------------------------------------- 1 | import sanitizeHtml from "sanitize-html"; 2 | 3 | const sanitizeOptions = { 4 | allowedTags: [ 'b', 'i', 'em', 'strong', 'a', 'p', 'img', 'video', 'source' ], 5 | allowedAttributes: { 6 | 'a': [ 'href', 'title' ], 7 | 'img': [ 'src', 'alt', 'title', 'width', 'height' ], 8 | 'video': [ 'width', 'height' ], 9 | 'source': [ 'src', 'type' ], 10 | }, 11 | }; 12 | const sanitizeOptionsClan = { 13 | allowedTags: [ ], 14 | allowedAttributes: { }, 15 | }; 16 | 17 | /** 18 | * Feature updater injection point to modify active data 19 | * @param {import("./featureLoader.js").UserMapFeatures} data 20 | * @returns 21 | */ 22 | export function featureUpdater(data) { 23 | data.features.forEach((feature) => { 24 | // happens when copy defaultFeatures to features.json manually 25 | // also ui has issues if time is missing, so making sure its always there 26 | if (!('time' in feature.properties)) { 27 | feature.properties.time = (new Date()).toISOString() 28 | } 29 | }) 30 | return data 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "foxhole-map-annotate", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "start": "node --env-file .env ./bin/www", 8 | "dev": "node --watch --env-file .env ./bin/www", 9 | "build": "webpack --config ./webpack.prod.js", 10 | "build.dev": "webpack --config ./webpack.dev.js", 11 | "test": "vitest --dir __tests__" 12 | }, 13 | "dependencies": { 14 | "cookie-parser": "^1.4.4", 15 | "debug": "^2.6.9", 16 | "express": "^4.16.1", 17 | "express-session": "^1.17.3", 18 | "grant": "^5.4.21", 19 | "http-errors": "^1.6.3", 20 | "mongodb": "^6.3", 21 | "nunjucks": "^3.2.3", 22 | "object-hash": "^3.0.0", 23 | "read-last-lines": "^1.8.0", 24 | "sanitize-html": "^2.7.3", 25 | "session-file-store": "^1.5.0", 26 | "ws": "^8.9.0", 27 | "yaml": "^2.1.3" 28 | }, 29 | "devDependencies": { 30 | "@turf/bezier-spline": "^6.5.0", 31 | "@turf/intersect": "^6.5.0", 32 | "@turf/voronoi": "^6.5.0", 33 | "@types/ol-ext": "npm:@siedlerchr/types-ol-ext@^3.0.8", 34 | "@vue/compiler-sfc": "^3.2.47", 35 | "autoprefixer": "^10.4.13", 36 | "bootstrap": "^5.2.2", 37 | "bootstrap-icons": "^1.9.1", 38 | "css-loader": "^6.7.1", 39 | "css-minimizer-webpack-plugin": "^4.2.2", 40 | "mini-css-extract-plugin": "^2.6.1", 41 | "ol": "^7.2.2", 42 | "ol-ext": "^4.0.4", 43 | "ol-layerswitcher": "^4.1.0", 44 | "postcss-loader": "^7.0.1", 45 | "sass": "^1.56.0", 46 | "sass-loader": "^13.1.0", 47 | "style-loader": "^3.3.1", 48 | "tom-select": "^2.2.2", 49 | "vitest": "^1.0.1", 50 | "vue": "^3.2.47", 51 | "vue-loader": "^17.1.0", 52 | "webpack": "^5.74.0", 53 | "webpack-cli": "^4.10.0", 54 | "webpack-dev-middleware": "^5.3.3", 55 | "webpack-merge": "^5.8.0" 56 | }, 57 | "engines": { 58 | "node": ">=20.8.1" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /public/images/artilleryChevron.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/images/artilleryTarget.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/artilleryVector.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/images/artilleryWindDir.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/images/artilleryWindPip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/images/base/EmplacementHouse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/images/base/base.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/base/base_frontline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/base/base_obs.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/base/base_obs_t2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | -------------------------------------------------------------------------------- /public/images/base/base_sleep.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/base/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/base/flag.png -------------------------------------------------------------------------------- /public/images/base/flag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/base/flag2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/base/flag2.png -------------------------------------------------------------------------------- /public/images/base/flag2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/base/friendly_planned_intel_center.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/images/base/friendly_planned_storm_cannon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/images/base/lock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /public/images/colonial.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/colonial.webp -------------------------------------------------------------------------------- /public/images/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /public/images/crossColonial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/crossColonial.png -------------------------------------------------------------------------------- /public/images/crossWarden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/crossWarden.png -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_ammo_facility.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_ass_mats_1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_base.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_base_frontline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_base_obs.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_base_obs_t2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_base_sleep.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_broken_components.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_cmats.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_diesel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_drydock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_enriched.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_facility_generic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_fire_equipment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_heavy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_pcmats.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_petrol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_planned_intel_center.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_planned_storm_cannon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_scmats.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_supplies.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_tank_facility.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_train_facility.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_vehicle_facility.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-enemy/enemy_water.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_ammo_facility.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_ass_mats_1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_broken_components.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/images/facility-private/private_cmats.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_diesel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_drydock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | -------------------------------------------------------------------------------- /public/images/facility-private/private_enriched.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_facility_generic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_heavy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_pcmats.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_petrol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_scmats.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_supplies.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/images/facility-private/private_tank_facility.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_train_facility.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_vehicle_facility.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility-private/private_water.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/friendly_fire_equipment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/images/facility/generator.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/maintenance.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/port.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_ammo_facility.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_ass_mats_1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_broken_components.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/images/facility/public_cmats.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_diesel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_drydock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | -------------------------------------------------------------------------------- /public/images/facility/public_enriched.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_facility_generic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_heavy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_pcmats.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_petrol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_scmats.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_supplies.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/images/facility/public_tank_facility.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_train_facility.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_vehicle_facility.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/facility/public_water.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/favicon.png -------------------------------------------------------------------------------- /public/images/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/field/MapIconCoalFieldColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/field/MapIconCoalFieldColor.png -------------------------------------------------------------------------------- /public/images/field/MapIconComponentMineColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/field/MapIconComponentMineColor.png -------------------------------------------------------------------------------- /public/images/field/MapIconComponentsColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/field/MapIconComponentsColor.png -------------------------------------------------------------------------------- /public/images/field/MapIconFacilityMineOilRig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/field/MapIconFacilityMineOilRig.png -------------------------------------------------------------------------------- /public/images/field/MapIconOilFieldColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/field/MapIconOilFieldColor.png -------------------------------------------------------------------------------- /public/images/field/MapIconSalvageColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/field/MapIconSalvageColor.png -------------------------------------------------------------------------------- /public/images/field/MapIconSalvageMineColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/field/MapIconSalvageMineColor.png -------------------------------------------------------------------------------- /public/images/field/MapIconSulfurColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/field/MapIconSulfurColor.png -------------------------------------------------------------------------------- /public/images/field/MapIconSulfurMineColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/field/MapIconSulfurMineColor.png -------------------------------------------------------------------------------- /public/images/field/coal_field.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/field/comp_field.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/field/comp_mine.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/field/oil_field.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/field/scrap_field.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/field/scrap_mine.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/field/sulfur_field.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/field/sulfur_mine.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/humanQueue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/images/humanQueueColonial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/humanQueueColonial.png -------------------------------------------------------------------------------- /public/images/humanQueueWarden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/humanQueueWarden.png -------------------------------------------------------------------------------- /public/images/industry/MapIconAmmoFactory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/industry/MapIconAmmoFactory.png -------------------------------------------------------------------------------- /public/images/industry/MapIconCoastalGun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/industry/MapIconCoastalGun.png -------------------------------------------------------------------------------- /public/images/industry/MapIconConstructionYard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/industry/MapIconConstructionYard.png -------------------------------------------------------------------------------- /public/images/industry/MapIconFactory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/industry/MapIconFactory.png -------------------------------------------------------------------------------- /public/images/industry/MapIconManufacturing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/industry/MapIconManufacturing.png -------------------------------------------------------------------------------- /public/images/industry/MapIconMassProductionFactory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/industry/MapIconMassProductionFactory.png -------------------------------------------------------------------------------- /public/images/industry/MapIconMedical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/industry/MapIconMedical.png -------------------------------------------------------------------------------- /public/images/industry/MapIconSeaport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/industry/MapIconSeaport.png -------------------------------------------------------------------------------- /public/images/industry/MapIconShipyard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/industry/MapIconShipyard.png -------------------------------------------------------------------------------- /public/images/industry/MapIconStorageFacility.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/industry/MapIconStorageFacility.png -------------------------------------------------------------------------------- /public/images/industry/MapIconTechCenter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/industry/MapIconTechCenter.png -------------------------------------------------------------------------------- /public/images/industry/MapIconVehicle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/industry/MapIconVehicle.png -------------------------------------------------------------------------------- /public/images/industry/MapIconWorkshop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/industry/MapIconWorkshop.png -------------------------------------------------------------------------------- /public/images/information/WeatherEventRain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 14 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /public/images/information/caution.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/information/danger.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/information/enemy_bridge_destroyed.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/information/enemy_freighter_blockade.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/information/friendly_bridge_destroyed.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/information/friendly_freighter_blockade.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/information/information.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/information/warning.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/og.png -------------------------------------------------------------------------------- /public/images/og.svg: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /public/images/sign/Carriageway_Both_Points.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/Carriageway_Left_Point_Only.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/Carriageway_Right_Point_Only.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/Dual_Carriageway.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/End_Of_Dual_Carriageway.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/dead_end.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/dual_carriageway_ends_ahead.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/keep_left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/keep_right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/level_crossing.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/motorway.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/motorway_end.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/no_entry_sign.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/no_stopping.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/no_waiting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/sign/parking.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconBorderBase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconBorderBase.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconBunkerBaseTier1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconBunkerBaseTier1.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconBunkerBaseTier2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconBunkerBaseTier2.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconBunkerBaseTier3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconBunkerBaseTier3.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconFacilityVehicleFactory1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconFacilityVehicleFactory1.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconForwardBase1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconForwardBase1.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconIntelCenter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconIntelCenter.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconIntelCenterColonial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconIntelCenterColonial.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconIntelCenterWarden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconIntelCenterWarden.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconRocketGroundZero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconRocketGroundZero.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconRocketGroundZeroColonial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconRocketGroundZeroColonial.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconRocketGroundZeroWarden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconRocketGroundZeroWarden.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconRocketSite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconRocketSite.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconRocketSiteColonial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconRocketSiteColonial.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconRocketSiteWarden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconRocketSiteWarden.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconRocketSiteWithRocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconRocketSiteWithRocket.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconRocketSiteWithRocketColonial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconRocketSiteWithRocketColonial.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconRocketSiteWithRocketWarden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconRocketSiteWithRocketWarden.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconRocketTarget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconRocketTarget.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconRocketTargetColonial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconRocketTargetColonial.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconRocketTargetWarden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconRocketTargetWarden.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconStormCannon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconStormCannon.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconStormCannonColonial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconStormCannonColonial.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconStormCannonWarden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconStormCannonWarden.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconWeatherStation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconWeatherStation.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconWeatherStationColonial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconWeatherStationColonial.png -------------------------------------------------------------------------------- /public/images/stormCannon/MapIconWeatherStationWarden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/stormCannon/MapIconWeatherStationWarden.png -------------------------------------------------------------------------------- /public/images/town/MapIconFortKeep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconFortKeep.png -------------------------------------------------------------------------------- /public/images/town/MapIconObservationTower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconObservationTower.png -------------------------------------------------------------------------------- /public/images/town/MapIconRelicBase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconRelicBase.png -------------------------------------------------------------------------------- /public/images/town/MapIconSafehouse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconSafehouse.png -------------------------------------------------------------------------------- /public/images/town/MapIconTownBaseTier1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconTownBaseTier1.png -------------------------------------------------------------------------------- /public/images/town/MapIconTownBaseTier1Colonial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconTownBaseTier1Colonial.png -------------------------------------------------------------------------------- /public/images/town/MapIconTownBaseTier1Nuke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconTownBaseTier1Nuke.png -------------------------------------------------------------------------------- /public/images/town/MapIconTownBaseTier1Warden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconTownBaseTier1Warden.png -------------------------------------------------------------------------------- /public/images/town/MapIconTownBaseTier2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconTownBaseTier2.png -------------------------------------------------------------------------------- /public/images/town/MapIconTownBaseTier2Colonial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconTownBaseTier2Colonial.png -------------------------------------------------------------------------------- /public/images/town/MapIconTownBaseTier2Nuke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconTownBaseTier2Nuke.png -------------------------------------------------------------------------------- /public/images/town/MapIconTownBaseTier2Warden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconTownBaseTier2Warden.png -------------------------------------------------------------------------------- /public/images/town/MapIconTownBaseTier3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconTownBaseTier3.png -------------------------------------------------------------------------------- /public/images/town/MapIconTownBaseTier3Colonial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconTownBaseTier3Colonial.png -------------------------------------------------------------------------------- /public/images/town/MapIconTownBaseTier3Nuke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconTownBaseTier3Nuke.png -------------------------------------------------------------------------------- /public/images/town/MapIconTownBaseTier3Warden.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/town/MapIconTownBaseTier3Warden.png -------------------------------------------------------------------------------- /public/images/warden.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/attrib/foxhole-map-annotate/6344580a8731f15115ba1513230989f0c9ba7ab6/public/images/warden.webp -------------------------------------------------------------------------------- /syncBackups.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rsync -avz warden.express:/mnt/deployments/foxhole-map-annotate/backups/ backups/ 4 | -------------------------------------------------------------------------------- /tools/deadland.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "id": "DeadLandsHex", 6 | "type": "Feature", 7 | "geometry": { 8 | "type": "Polygon", 9 | "coordinates": [ 10 | [ 11 | [ 12 | 6661, 13 | -5328 14 | ], 15 | [ 16 | 7689, 17 | -5328 18 | ], 19 | [ 20 | 8198, 21 | -6216 22 | ], 23 | [ 24 | 7689, 25 | -7105 26 | ], 27 | [ 28 | 6661, 29 | -7105 30 | ], 31 | [ 32 | 6149, 33 | -6216 34 | ] 35 | ] 36 | ] 37 | }, 38 | "properties": { 39 | "type": "Region", 40 | "notes": "Deadlands", 41 | "id": "DeadLandsHex" 42 | } 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /tools/defaultFeatures.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | 4 | // const defaultFeature = JSON.parse(fs.readFileSync(`data/features.json`, 'utf8')) 5 | // 6 | // defaultFeature.features.forEach((feature) => { 7 | // feature.properties.notes = "NO FACILITIES\nReserved for city defences!" 8 | // feature.properties.color = '#ff7a7aAA'; 9 | // delete feature.properties.muser; 10 | // delete feature.properties.muserId; 11 | // delete feature.properties.time; 12 | // }); 13 | // 14 | // fs.writeFileSync(`data/defaultFeatures.json`, JSON.stringify(defaultFeature), 'utf8') 15 | 16 | const defaultFeature = { 17 | type: 'FeatureCollection', 18 | features: [], 19 | } 20 | 21 | // const defaultFeature = fs.readFileSync(`data/features.json`, 'utf8') 22 | 23 | for (let i = 104; i <= 104; i++) { 24 | const content = fs.readFileSync(path.resolve(`data/war${i}/features.json`), 'utf8') 25 | console.log(i) 26 | const parsed = JSON.parse(content) 27 | 28 | parsed.features.forEach((feature) => { 29 | if (feature.properties.notes.includes('NO FACILITIES') || feature.properties.notes.includes('KEEP CLEAR!')) { 30 | console.log(i) 31 | feature.properties.notes = "NO FACILITIES\nReserved for city defences!" 32 | feature.properties.color = '#ff7a7aAA'; 33 | delete feature.properties.muser; 34 | delete feature.properties.muserId; 35 | delete feature.properties.time; 36 | 37 | defaultFeature.features.push(feature) 38 | } 39 | }); 40 | } 41 | 42 | console.log(defaultFeature.features.length) 43 | fs.writeFile(path.resolve('data/defaultFeatures.json'), JSON.stringify(defaultFeature), err => { 44 | if (err) { 45 | console.error(err); 46 | } 47 | }); -------------------------------------------------------------------------------- /tools/fetchFields.js: -------------------------------------------------------------------------------- 1 | import crypto from "node:crypto"; 2 | import fs from "node:fs"; 3 | import path from "node:path"; 4 | 5 | import icons from "../data/icons.json"; 6 | import warapi from "../lib/warapi.js"; 7 | import regions from "../public/regions.json"; 8 | 9 | const extent = [-2046, 1777] 10 | const deleteFields = [] 11 | for (const i in icons.features) { 12 | const feature = icons.features[i] 13 | if (feature.properties.type !== 'field') { 14 | continue; 15 | } 16 | deleteFields.push(i) 17 | } 18 | 19 | for (const i of deleteFields.reverse()) { 20 | icons.features.splice(i, 1) 21 | } 22 | 23 | const promises = [] 24 | for (const region of regions.features) { 25 | if (region.properties.type !== 'Region') { 26 | continue; 27 | } 28 | console.log(region.properties) 29 | promises.push(warapi.dynamicMap(region.id).then((data) => { 30 | for (const item of data.mapItems) { 31 | if (item.iconType in warapi.iconTypes && warapi.iconTypes[item.iconType].type === 'field') { 32 | const id = crypto.randomUUID() 33 | icons.features.push({ 34 | "type": "Feature", 35 | "id": id, 36 | "geometry": { 37 | "type": "Point", 38 | "coordinates": [ 39 | region.properties.box[0] - item.x * extent[0], 40 | region.properties.box[1] - item.y * extent[1] 41 | ] 42 | }, 43 | "properties": { 44 | "type": warapi.iconTypes[item.iconType].type, 45 | "icon": warapi.iconTypes[item.iconType].icon, 46 | "notes": warapi.iconTypes[item.iconType].notes, 47 | "id": id, 48 | "user": "World", 49 | "time": "" 50 | } 51 | }) 52 | } 53 | } 54 | })) 55 | } 56 | 57 | Promise.all(promises).then(() => { 58 | fs.writeFile(path.resolve('data/icons.json'), JSON.stringify(icons, null, 2), err => { 59 | if (err) { 60 | console.error(err); 61 | } 62 | }); 63 | }) 64 | 65 | -------------------------------------------------------------------------------- /tools/moveDefaultFeatures.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | 4 | const defaultFeaturePath = path.resolve('data/defaultFeatures.json') 5 | const defaultFeature = JSON.parse(fs.readFileSync(defaultFeaturePath, 'utf8')) 6 | 7 | for (const feature of defaultFeature.features) { 8 | feature.geometry.coordinates[0] = feature.geometry.coordinates[0].map((coords) => { 9 | coords[0] = coords[0] + 1543 10 | return coords 11 | }) 12 | } 13 | 14 | console.log(defaultFeature.features.length) 15 | fs.writeFile(defaultFeaturePath, JSON.stringify(defaultFeature), err => { 16 | if (err) { 17 | console.error(err); 18 | } 19 | }); -------------------------------------------------------------------------------- /views/access.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block javascript %} 4 | {% endblock %} 5 | 6 | {% block content %} 7 |
8 |
9 |

Access Denied

10 | {{ config.text.accessDenied | safe }} 11 |

12 | 15 |

16 |
17 |
18 |

User ID: {{ userId }}

19 | {% for debug in debugInfo %} 20 |

{{ debug }}

21 | {% endfor %} 22 |
23 |
24 |
25 |
26 | {% endblock %} -------------------------------------------------------------------------------- /views/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /views/error.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block javascript %} 4 | {% endblock %} 5 | 6 | {% block content %} 7 |
8 |
9 |

{{ error.status }} {{ message }}

10 |
{{ error.stack }}
11 |
12 |
13 | {% endblock %} -------------------------------------------------------------------------------- /views/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% set showSelect = config.access.discords|length > 1 %} 4 | 5 | {% block javascript %} 6 | {% if showSelect %} 7 | 39 | {% endif %} 40 | {% endblock %} 41 | 42 | {% block content %} 43 |
44 |
45 |

Login required

46 | {{ config.text.login | safe }} 47 |
48 | {% if showSelect %} 49 |
50 | 51 |
52 | 59 |
60 |
61 | {% endif %} 62 |
63 | Login 64 |
65 |
66 |
67 |
68 | {% endblock %} -------------------------------------------------------------------------------- /views/stats.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block css %} 4 | 5 | {% endblock %} 6 | 7 | {% block javascript %} 8 | 9 | {% endblock %} 10 | 11 | {% block content %} 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | {% endblock %} -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | 3 | import miniCssExtractPlugin from "mini-css-extract-plugin"; 4 | import { VueLoaderPlugin } from "vue-loader"; 5 | import webpack from "webpack"; 6 | 7 | export default { 8 | entry: { 9 | index: ['./frontend/index.js'], 10 | stats: ['./frontend/stats.js'], 11 | main: ['./frontend/main.js'], 12 | admin: ['./frontend/admin.js'], 13 | }, 14 | output: { 15 | filename: '[name].js', 16 | path: path.resolve('public', 'dist'), 17 | publicPath: "/dist/", 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.(scss)$/, 23 | use: [ 24 | { 25 | // Extracts CSS for each JS file that includes CSS 26 | loader: miniCssExtractPlugin.loader 27 | }, 28 | { 29 | loader: 'css-loader' 30 | }, 31 | { 32 | loader: 'postcss-loader', 33 | options: { 34 | postcssOptions: { 35 | plugins: () => [ 36 | require('autoprefixer') 37 | ] 38 | } 39 | } 40 | }, 41 | { 42 | loader: 'sass-loader' 43 | } 44 | ] 45 | }, 46 | { 47 | test: /\.(vue)$/, 48 | loader: 'vue-loader' 49 | } 50 | ], 51 | }, 52 | plugins: [ 53 | new miniCssExtractPlugin(), 54 | new VueLoaderPlugin(), 55 | new webpack.DefinePlugin({ 56 | '__VUE_OPTIONS_API__': JSON.stringify(false), 57 | }) 58 | ], 59 | resolve: { 60 | alias: { 61 | vue: 'vue/dist/vue.esm-bundler.js' 62 | } 63 | } 64 | }; -------------------------------------------------------------------------------- /webpack.dev.js: -------------------------------------------------------------------------------- 1 | import webpack from "webpack"; 2 | import { merge } from "webpack-merge"; 3 | 4 | import common from "./webpack.config.js"; 5 | 6 | export default merge(common, { 7 | mode: 'development', 8 | devtool: 'inline-source-map', 9 | plugins: [ 10 | new webpack.DefinePlugin({ 11 | '__VUE_PROD_DEVTOOLS__': JSON.stringify(true), 12 | }) 13 | ] 14 | }); -------------------------------------------------------------------------------- /webpack.prod.js: -------------------------------------------------------------------------------- 1 | import webpack from "webpack"; 2 | import { merge } from "webpack-merge"; 3 | import CssMinimizerPlugin from "css-minimizer-webpack-plugin"; 4 | 5 | import common from "./webpack.config.js"; 6 | 7 | export default merge(common, { 8 | mode: 'production', 9 | devtool: 'source-map', 10 | optimization: { 11 | minimizer: [ 12 | // For webpack@5 you can use the `...` syntax to extend existing minimizers (i.e. `terser-webpack-plugin`), uncomment the next line 13 | `...`, 14 | new CssMinimizerPlugin(), 15 | ], 16 | }, 17 | plugins: [ 18 | new webpack.DefinePlugin({ 19 | 'process.env': { 20 | 'NODE_ENV': JSON.stringify('production'), 21 | '__VUE_PROD_DEVTOOLS__': JSON.stringify(false), 22 | } 23 | }), 24 | ] 25 | }); --------------------------------------------------------------------------------