diff --git a/.github/actions/setup-and-validate/action.yml b/.github/actions/setup-and-validate/action.yml deleted file mode 100644 index 651ff087b..000000000 --- a/.github/actions/setup-and-validate/action.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: "Theme Validation" -description: "Validate the themes" -runs: - using: "composite" - steps: - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: "20" - cache: "npm" - cache-dependency-path: package-lock.json - - - name: print actor - shell: bash - run: echo ${{ github.actor }} - - - name: install deps - run: npm ci - shell: bash - - - name: REUSE compliance check - uses: fsfe/reuse-action@952281636420dd0b691786c93e9d3af06032f138 - - - name: create generated dir - run: mkdir ./assets/generated - shell: bash - - - name: create dependencies - run: npm run generate:licenses; npm run generate:images; npm run generate:charging-stations; npm run generate:service-worker - shell: bash - - - name: sync translations - run: npm run generate:translations - shell: bash - - - name: generate layeroverview - run: npm run reset:layeroverview - shell: bash - - - name: run tests - run: npm run test - shell: bash - - - name: Prepare deploy - run: npm run prepare-deploy - shell: bash diff --git a/.github/workflows/deploy_dev.yml b/.github/workflows/deploy_dev.yml deleted file mode 100644 index 21ee96e20..000000000 --- a/.github/workflows/deploy_dev.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: Deploy develop on dev.mapcomplete.org -on: - push: - branches: - - develop - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: "20" - cache: "npm" - cache-dependency-path: package-lock.json - - - name: install deps - run: npm ci - shell: bash - - - name: create generated dir - run: mkdir ./assets/generated - shell: bash - - - name: create dependencies - run: npm run generate:licenses; npm run generate:images; npm run generate:charging-stations; npm run generate:service-worker; npm run download:editor-layer-index - shell: bash - - - name: sync translations - run: npm run generate:translations - shell: bash - - - name: Prepare deploy - run: npm run prepare-deploy - shell: bash - - - name: run tests - run: npm run test - shell: bash - - - name: Clone deployment repo - env: - DEPLOY_KEY_PIETERVDVN: ${{ secrets.DEPLOY_KEY_PIETERVDVN }} - run: | - echo "Cloning destination repo" - git config --global user.email "pietervdvn@posteo.net" - git config --global user.name "pietervdvn" - git clone --depth 1 --single-branch --branch main "https://x-access-token:$DEPLOY_KEY_PIETERVDVN@github.com/MapComplete/mapcomplete-dev.git" - echo "Destination repo is cloned" - - - name: Sync repo - env: - DEPLOY_KEY_PIETERVDVN: ${{ secrets.DEPLOY_KEY_PIETERVDVN }} - run: | - cd mapcomplete-dev - git pull - - - name: "Copying files" - run: | - echo "Deploying" - rm -rf mapcomplete-dev/* - cp -r dist/* mapcomplete-dev/ - cd mapcomplete-dev/ - echo "dev.mapcomplete.org" > CNAME - touch .nojekyll - git add * - if git status | grep -q "Changes to be committed" - then - git commit -am "Deploying a new version" - git push - else - echo "No changes to commit" - fi - diff --git a/.github/workflows/deploy_hosted.yml b/.github/workflows/deploy_hosted.yml new file mode 100644 index 000000000..f07108415 --- /dev/null +++ b/.github/workflows/deploy_hosted.yml @@ -0,0 +1,71 @@ +name: Deploy develop on dev.mapcomplete.org +on: + - push + - pull_request + +jobs: + deploy_on_hosted: + runs-on: [ubuntu-latest, hetzner-access] + steps: + - uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: package-lock.json + + - name: install deps + run: npm ci + shell: bash + + - name: create generated dir + run: mkdir ./assets/generated + shell: bash + + - name: create dependencies + run: npm run generate:licenses; npm run generate:images; npm run generate:charging-stations; npm run generate:service-worker; npm run download:editor-layer-index + shell: bash + + - name: sync translations + run: npm run generate:translations + shell: bash + + - name: Prepare build + run: npm run generate:service-worker && ./scripts/prepare-build.sh + shell: bash + + - name: Run tests + run: | + # This is the same as `npm run test`, but `vitest` doesn't want to run within npm :shrug: + export NODE_OPTIONS="--max-old-space-size=8192" + npm run clean:tests + npm run generate:doctests 2>&1 | grep -v "No doctests found in" + if which vitest + then + vitest --run test + else + npm run test + fi + + npm run clean:tests + shell: bash + + + - name: Build files + run: npm run build + + - name: Zipping dist file + run: | + mv dist ${{ github.ref_name }} + zip ${{ github.ref_name }}.zip -r ${{ github.ref_name }}/* + + - name: uploading file + run: scp ${{ github.ref_name }}.zip hetzner:/root/staging/ + + - name: unzipping remote file + run: ssh hetzner "cd /root/staging && unzip ${{ github.ref_name }}.zip && rm -rf /root/public/${{ github.ref_name }} && mv /root/staging/${{ github.ref_name }}/ /root/public/ && rm ${{ github.ref_name }}.zip" + + + diff --git a/.github/workflows/deploy_pietervdvn.yml b/.github/workflows/deploy_pietervdvn.yml deleted file mode 100644 index 284f94890..000000000 --- a/.github/workflows/deploy_pietervdvn.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Deployment on pietervdvn -on: - push: - branches: - - feature/* - - theme/* - - refactoring/* -jobs: - build: - runs-on: ubuntu-latest - if: ${{ github.actor != 'weblate' }} - steps: - - uses: actions/checkout@v2 - - - name: print actor - shell: bash - run: echo ${{ github.actor }} - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: "20" - cache: "npm" - cache-dependency-path: package-lock.json - - - name: install deps - run: npm ci - shell: bash - - - name: create generated dir - run: mkdir ./assets/generated - shell: bash - - - name: create dependencies - run: npm run generate:licenses; npm run generate:images; npm run generate:charging-stations; npm run generate:service-worker; npm run download:editor-layer-index - shell: bash - - - name: sync translations - run: npm run generate:translations - shell: bash - - - name: generate layeroverview - run: npm run reset:layeroverview - shell: bash - - - name: run tests - run: npm run test - shell: bash - - - name: Prepare deploy - run: npm run prepare-deploy - shell: bash - - - name: Clone deployment repo - env: - DEPLOY_KEY_PIETERVDVN: ${{ secrets.DEPLOY_KEY_PIETERVDVN }} - run: | - echo "Cloning destination repo" - git config --global user.email "pietervdvn@posteo.net" - git config --global user.name "pietervdvn" - git clone --depth 1 --single-branch --branch master "https://x-access-token:$DEPLOY_KEY_PIETERVDVN@github.com/pietervdvn/pietervdvn.github.io.git" - echo "Destination repo is cloned" - - - name: Sync repo - env: - DEPLOY_KEY_PIETERVDVN: ${{ secrets.DEPLOY_KEY_PIETERVDVN }} - run: | - cd pietervdvn.github.io - git pull - - - name: get branch name - run: echo TARGET_BRANCH=${GITHUB_REF:11} >> $GITHUB_ENV - - - name: "Copying files" - run: | - echo "Deploying" - rm -rf pietervdvn.github.io/mc/${{ env.TARGET_BRANCH }}/* - mkdir -p pietervdvn.github.io/mc/${{ env.TARGET_BRANCH }}/ - cp -r dist/* pietervdvn.github.io/mc/${{ env.TARGET_BRANCH }}/ - cd pietervdvn.github.io/ - git add * - if git status | grep -q "Changes to be committed" - then - git commit -am "Deploying a new version of mapcomplete" - git push - else - echo "No changes to commit" - fi - env: - TARGET_BRANCH: ${{ env.TARGET_BRANCH }} - - - uses: mshick/add-pr-comment@a96c578acba98b60f16c6866d5f20478dc4ef68b - name: Comment the PR with the review URL - if: ${{ success() && github.ref != 'refs/heads/develop' && github.ref != 'refs/heads/master' }} - with: - message: | - [🚀 Preview Branch](https://pietervdvn.github.io/mc/${{ env.TARGET_BRANCH }}) - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/validate-pr.yml b/.github/workflows/validate-pr.yml deleted file mode 100644 index c1724ca9b..000000000 --- a/.github/workflows/validate-pr.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Build and validate PR (but don't deploy) -on: - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: "20" - cache: "npm" - cache-dependency-path: package-lock.json - - - name: install deps - run: npm ci - shell: bash - - - name: create generated dir - run: mkdir ./assets/generated - shell: bash - - - name: create dependencies - run: npm run generate:licenses; npm run generate:images; npm run generate:charging-stations; npm run generate:service-worker - shell: bash - - - name: sync translations - run: npm run generate:translations - shell: bash - - - name: generate layeroverview - run: npm run reset:layeroverview - shell: bash - - - name: run tests - run: npm run test - shell: bash diff --git a/.vscode/settings.json b/.vscode/settings.json index 0eedd1323..aa73d8edf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -53,8 +53,5 @@ "[svelte]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "editor.formatOnSave": true, - "files.associations": { - "*.protojson": "json" - } + "editor.formatOnSave": true } diff --git a/Docs/ServerConfig/hetzner/Caddyfile b/Docs/ServerConfig/hetzner/Caddyfile index 631efadd4..8641124fc 100644 --- a/Docs/ServerConfig/hetzner/Caddyfile +++ b/Docs/ServerConfig/hetzner/Caddyfile @@ -6,6 +6,14 @@ hosted.mapcomplete.org { } } +dev.mapcomplete.org { + root * public/develop/ + file_server + header { + +Permissions-Policy "interest-cohort=()" + } +} + countrycoder.mapcomplete.org { root * tiles/ file_server @@ -28,5 +36,5 @@ lod.mapcomplete.org { } ipinfo.mapcomplete.org { - reverse_proxy 127.0.0.1:2347 + reverse_proxy 127.0.0.1:2347 } diff --git a/assets/layers/beehive/beehive.json b/assets/layers/beehive/beehive.json new file mode 100644 index 000000000..4bedb02a2 --- /dev/null +++ b/assets/layers/beehive/beehive.json @@ -0,0 +1,75 @@ +{ + "id": "beehive", + "name": { + "en": "Beehives" + }, + "description": { + "en": "Layer showing beehives" + }, + "source": { + "osmTags": "man_made=beehive" + }, + "minzoom": 11, + "title": { + "en": "Beehive" + }, + "presets": [ + { + "title": { + "en": "a beehive" + }, + "tags": [ + "man_made=beehive" + ] + } + ], + "tagRenderings": [ + "images", + { + "id": "capacity", + "question": { + "en": "How many beehives are there?" + }, + "freeform": { + "key": "capacity", + "type": "pnat", + "placeholder": { + "en": "Number of beehives" + } + }, + "render": { + "en": "There are {capacity} beehives" + }, + "mappings": [ + { + "if": "capacity=1", + "then": { + "en": "There is 1 beehive" + } + } + ] + } + ], + "pointRendering": [ + { + "location": [ + "point", + "centroid" + ], + "marker": [ + { + "icon": "circle", + "color": "white" + }, + { + "icon": "./assets/layers/beehive/beehive.svg" + } + ] + } + ], + "allowMove": { + "enableImproveAccuracy": true, + "enableRelocation": true + }, + "deletion": true +} \ No newline at end of file diff --git a/assets/layers/beehive/beehive.svg b/assets/layers/beehive/beehive.svg new file mode 100644 index 000000000..df543ea1d --- /dev/null +++ b/assets/layers/beehive/beehive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/layers/beehive/beehive.svg.license b/assets/layers/beehive/beehive.svg.license new file mode 100644 index 000000000..c028557e3 --- /dev/null +++ b/assets/layers/beehive/beehive.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Delapouite +SPDX-License-Identifier: CC BY 3.0 \ No newline at end of file diff --git a/assets/layers/beehive/license_info.json b/assets/layers/beehive/license_info.json new file mode 100644 index 000000000..83c3b974a --- /dev/null +++ b/assets/layers/beehive/license_info.json @@ -0,0 +1,12 @@ +[ + { + "path": "beehive.svg", + "license": "CC BY 3.0", + "authors": [ + "Delapouite" + ], + "sources": [ + "https://game-icons.net/1x1/delapouite/beehive.html" + ] + } +] \ No newline at end of file diff --git a/assets/layers/cafe_pub/cafe_pub.json b/assets/layers/cafe_pub/cafe_pub.json index 2fb2a6423..866cb56b9 100644 --- a/assets/layers/cafe_pub/cafe_pub.json +++ b/assets/layers/cafe_pub/cafe_pub.json @@ -404,6 +404,7 @@ "wheelchair-access", "smoking", "service:electricity", + "seating", "dog-access", "internet", "internet-fee", @@ -415,7 +416,9 @@ "accepts_cash", "accepts_cards", "has_internet", - "has_electricity" + "has_electricity", + "outdoor_seating", + "indoor_seating" ], "deletion": { "softDeletionTags": { @@ -442,4 +445,4 @@ ] }, "allowMove": true -} +} \ No newline at end of file diff --git a/assets/layers/charging_station/charging_station.json b/assets/layers/charging_station/charging_station.json index 3e308b91c..c9a4fa501 100644 --- a/assets/layers/charging_station/charging_station.json +++ b/assets/layers/charging_station/charging_station.json @@ -1833,9 +1833,9 @@ { "en": "Tesla Supercharger", "nl": "Tesla Supercharger", + "cs": "Tesla Supercharger", "de": "Tesla Supercharger", - "es": "Tesla Supercharger", - "cs": "Tesla Supercharger" + "es": "Tesla Supercharger" }, "Tesla-hpwc-model-s.svg", [ @@ -2584,43 +2584,15 @@ }, { "id": "maxstay", - "question": { - "en": "What is the maximum amount of time one is allowed to stay here?", - "nl": "Hoelang mag een voertuig hier blijven staan?", - "ca": "Quina és la quantitat màxima de temps que es permet permaneixer aquí?", - "de": "Wie lange darf man hier maximal parken?", - "es": "¿Cuál es el tiempo máximo que se permite permanecer aquí?" - }, - "freeform": { - "key": "maxstay", - "type": "pfloat" - }, - "render": { - "en": "One can stay at most {canonical(maxstay)}", - "nl": "De maximale parkeertijd hier is {canonical(maxstay)}", - "ca": "Un pot quedar-se com a màxim {canonical(maxstay)}", - "de": "Die maximale Parkdauer beträgt {canonical(maxstay)}", - "es": "Se puede permanecer como máximo {canonical(maxstay)}" - }, - "mappings": [ - { - "if": "maxstay=unlimited", - "then": { - "en": "No timelimit on leaving your vehicle here", - "nl": "Geen maximum parkeertijd", - "ca": "No hi ha límit de temps per a deixar el teu vehicle aquí", - "de": "Keine Höchstparkdauer", - "es": "Sin límite de tiempo para dejar tu vehículo aquí" - } + "builtin": "maxstay", + "override": { + "condition": { + "or": [ + "motorcar=yes", + "hgv=yes", + "bus=yes" + ] } - ], - "condition": { - "or": [ - "maxstay~*", - "motorcar=yes", - "hgv=yes", - "bus=yes" - ] } }, { @@ -2639,11 +2611,11 @@ "en": "Is this charging station part of a network?", "nl": "Is dit oplaadpunt deel van een groter netwerk?", "ca": "Aquesta estació de càrrega forma part d'una xarxa?", + "cs": "Je tato nabíjecí stanice součástí sítě?", "de": "Ist diese Ladestation Teil eines Netzwerks?", "es": "¿Este punto de carga forma parte de una red?", "pl": "Czy ta stacja ładowania jest częścią sieci?", - "uk": "Чи є ця зарядна станція частиною мережі?", - "cs": "Je tato nabíjecí stanice součástí sítě?" + "uk": "Чи є ця зарядна станція частиною мережі?" }, "freeform": { "key": "network" @@ -2665,11 +2637,11 @@ "then": { "en": "Not part of a bigger network", "nl": "Maakt geen deel uit van een groter netwerk", + "cs": "Není součástí větší sítě", "de": "Nicht Teil eines größeren Netzwerks", "es": "No forma parte de una red mayor", "pl": "Nie jest częścią większej sieci", - "uk": "Не є частиною великої мережі", - "cs": "Není součástí větší sítě" + "uk": "Не є частиною великої мережі" }, "hideInAnswer": true }, @@ -3132,11 +3104,11 @@ "en": "All vehicle types", "nl": "Alle voertuigen", "ca": "Tots els tipus de vehicles", + "cs": "Všechny typy vozidel", "de": "Ladestationen für alle Fahrzeugtypen", "es": "Todos los tipos de vehículos", "fr": "Tous les types de véhicules", - "it": "Tutti i tipi di veicoli", - "cs": "Všechny typy vozidel" + "it": "Tutti i tipi di veicoli" } }, { @@ -3144,11 +3116,11 @@ "en": "Charging station for bicycles", "nl": "Oplaadpunten voor fietsen", "ca": "Punt de recàrrega per a bicicletes", + "cs": "Nabíjecí stanice pro kola", "de": "Ladestationen für Fahrräder", "es": "Punto de carga para bicicletas", "fr": "Station de recharge pour vélos", - "it": "Stazione di ricarica per biciclette", - "cs": "Nabíjecí stanice pro kola" + "it": "Stazione di ricarica per biciclette" }, "osmTags": "bicycle=yes" }, @@ -3157,11 +3129,11 @@ "en": "Charging station for cars", "nl": "Oplaadpunten voor auto's", "ca": "Estació de càrrega per a cotxes", + "cs": "Nabíjecí stanice pro auta", "de": "Ladestationen für Autos", "es": "Punto de carga para coches", "fr": "Station de recharge pour voitures", - "it": "Stazione di ricarica per auto", - "cs": "Nabíjecí stanice pro auta" + "it": "Stazione di ricarica per auto" }, "osmTags": { "or": [ @@ -3180,11 +3152,11 @@ "en": "Only working charging stations", "nl": "Enkel werkende oplaadpunten", "ca": "Només estacions de recàrrega en funcionament", + "cs": "Pouze funkční nabíjecí stanice", "de": "Nur Ladestationen in Betrieb", "es": "Solo puntos de carga en funcionamiento", "fr": "Seulement les stations de recharge qui fonctionnent", - "it": "Solo stazioni di ricarica funzionanti", - "cs": "Pouze funkční nabíjecí stanice" + "it": "Solo stazioni di ricarica funzionanti" }, "osmTags": { "and": [ @@ -3203,10 +3175,10 @@ "en": "All connectors", "nl": "Alle types", "ca": "Tots els connectors", + "cs": "Všechny konektory", "de": "Alle Anschlüsse", "es": "Todos los conectores", - "it": "Tutti i connettori", - "cs": "Všechny konektory" + "it": "Tutti i connettori" } }, { diff --git a/assets/layers/charging_station/charging_station.proto.json b/assets/layers/charging_station/charging_station.proto.json index 12cbcadf3..8329088bb 100644 --- a/assets/layers/charging_station/charging_station.proto.json +++ b/assets/layers/charging_station/charging_station.proto.json @@ -559,34 +559,15 @@ }, { "id": "maxstay", - "question": { - "en": "What is the maximum amount of time one is allowed to stay here?", - "nl": "Hoelang mag een voertuig hier blijven staan?" - }, - "freeform": { - "key": "maxstay", - "type": "pfloat" - }, - "render": { - "en": "One can stay at most {canonical(maxstay)}", - "nl": "De maximale parkeertijd hier is {canonical(maxstay)}" - }, - "mappings": [ - { - "if": "maxstay=unlimited", - "then": { - "en": "No timelimit on leaving your vehicle here", - "nl": "Geen maximum parkeertijd" - } + "builtin": "maxstay", + "override": { + "condition": { + "or": [ + "motorcar=yes", + "hgv=yes", + "bus=yes" + ] } - ], - "condition": { - "or": [ - "maxstay~*", - "motorcar=yes", - "hgv=yes", - "bus=yes" - ] } }, { @@ -1050,4 +1031,4 @@ }, "neededChangesets": 10 } -} +} \ No newline at end of file diff --git a/assets/layers/climbing_gym/climbing_gym.json b/assets/layers/climbing_gym/climbing_gym.json index c5093d811..492ecedf3 100644 --- a/assets/layers/climbing_gym/climbing_gym.json +++ b/assets/layers/climbing_gym/climbing_gym.json @@ -28,7 +28,12 @@ "osmTags": { "and": [ "sport=climbing", - "leisure=sports_centre" + { + "or": [ + "leisure=sports_centre", + "leisure=sports_hall" + ] + } ] } }, @@ -147,8 +152,8 @@ { "id": "shoe_rental", "question": { - "en": "Can one rent climbing shoes here?", - "nl": "Kunnen hier klimschoenen gehuurd worden?", + "en": "Can one rent climbing shoes here to use in the gym?", + "nl": "Kunnen hier klimschoenen gehuurd worden voor gebruik in de zaal?", "fr": "Peut-on louer des chaussures d'escalade ici ?", "de": "Kann man hier Kletterschuhe ausleihen?", "pl": "Czy można tutaj wypożyczyć buty do wspinaczki?", @@ -233,8 +238,8 @@ { "id": "harness_rental", "question": { - "en": "Can one rent a climbing harness here?", - "nl": "Kan een klimgordel hier gehuurd worden?", + "en": "Can one rent a climbing harness here to use in the gym?", + "nl": "Kan hier een klimgordel gehuurd worden voor gebruik in de zaal?", "fr": "Peut-on louer un baudrier d'escalade ici ?", "de": "Kann man hier einen Klettergurt ausleihen?", "cs": "Lze si zde zapůjčit horolezecký postroj?", @@ -242,8 +247,24 @@ }, "condition": { "or": [ - "climbing:sport!=no", - "climbing:toprope!=no" + { + "and": [ + "climbing:sport!=", + "climbing:sport!=no" + ] + }, + { + "and": [ + "climbing:toprope!=", + "climbing:toprope!=no" + ] + }, + { + "and": [ + "climbing:speed!=", + "climbing:speed!=no" + ] + } ] }, "mappings": [ @@ -315,11 +336,110 @@ } ] }, + { + "id": "auto_belay_toprope", + "question": { + "en": "Are there auto belays for top roping here?", + "nl": "Zijn hier auto belays voor toprope?" + }, + "questionHint": { + "en": "Excluding auto belays that are only for speed climbing", + "nl": "Autobelays die enkel voor speed zijn, tellen niet mee" + }, + "condition": { + "and": [ + "climbing:toprope!=", + "climbing:toprope!=no" + ] + }, + "freeform": { + "key": "climbing:autobelay:toprope", + "type": "nat" + }, + "mappings": [ + { + "if": "climbing:autobelay:toprope=no", + "then": { + "en": "There are no auto belays for top roping", + "nl": "Er zijn geen autobelays voor toprope" + } + }, + { + "if": "climbing:autobelay:toprope=yes", + "then": { + "en": "There are a number of auto belays for top roping", + "nl": "Er zijn enkele autobelays voor toprope" + } + }, + { + "if": "climbing:autobelay:toprope=all", + "then": { + "en": "There is an auto belay for every top rope route but manual belaying is also possible", + "nl": "Elke toproperoute kan op autobelay geklommen worden maar handmatig zekeren is ook mogelijk" + } + }, + { + "if": "climbing:autobelay:toprope=only", + "then": { + "en": "Top rope routes can only be climbed on auto belay", + "nl": "Toproperoutes kunnen enkel op autobelay geklommen worden" + } + } + ], + "render": { + "en": "There are {climbing:autobelay:toprope} auto belay devices for top roping", + "nl": "Er zijn {climbing:autobelay:toprope} autobelaytoestellen voor toprope" + } + }, + { + "id": "auto_belay_lead", + "question": { + "en": "Are there auto belays for lead climbing here?", + "nl": "Zijn hier autobelays voor voorklimmen?" + }, + "condition": { + "and": [ + "climbing:sport!=", + "climbing:sport!=no" + ] + }, + "freeform": { + "key": "climbing:autobelay:sport", + "type": "nat" + }, + "mappings": [ + { + "if": "climbing:autobelay:sport=no", + "then": { + "en": "There are no auto belays for lead climbing", + "nl": "Er zijn geen autobelays voor voorklimmen" + } + }, + { + "if": "climbing:autobelay:sport=yes", + "then": { + "en": "There is a number of auto belays for lead climbing", + "nl": "Er zijn enkele autobelays voor voorklimmen" + } + }, + { + "if": "climbing:autobelay:sport=all", + "then": { + "en": "There is an auto belay for every lead climbing route", + "nl": "Elke voorklimroute kan op autobelay geklommen worden" + } + } + ], + "render": { + "en": "There are {climbing:autobelay:sport} auto belays for lead climbing", + "nl": "Er zijn {climbing:autobelay:sport} autobelays voor voorklimmen" + } + }, { "id": "belay_device_rental", "question": { - "en": "Can one rent a belay device here?", - "nl": "Kan een zekeringsapparaat hier gehuurd worden?", + "en": "Can one rent a belay device here to use in the gym?", + "nl": "Kan hier een zekeringsapparaat gehuurd worden voor gebruik in de zaal?", "fr": "Peut-on louer un dispositif d'assurage ici ?", "de": "Kann man hier ein Sicherungsgerät ausleihen?", "cs": "Lze si zde půjčit jištění?", @@ -327,8 +447,18 @@ }, "condition": { "or": [ - "climbing:sport!=no", - "climbing:toprope!=no" + { + "and": [ + "climbing:sport!=", + "climbing:sport!=no" + ] + }, + { + "and": [ + "climbing:toprope!=", + "climbing:toprope!=no" + ] + } ] }, "mappings": [ @@ -422,8 +552,8 @@ { "id": "rope_rental", "question": { - "en": "Can one rent a climbing rope here?", - "nl": "Kan een klimtouw hier gehuurd worden?", + "en": "Can one rent a climbing rope here to use in the gym?", + "nl": "Kan hier een klimtouw gehuurd worden voor gebruik in de zaal?", "fr": "Peut-on louer une corde d'escalade ici ?", "de": "Kann man hier ein Kletterseil ausleihen?", "cs": "Lze si zde půjčit lezecké lano?", diff --git a/assets/layers/filters/filters.json b/assets/layers/filters/filters.json index c2ef1a5c6..ffdddd3b1 100644 --- a/assets/layers/filters/filters.json +++ b/assets/layers/filters/filters.json @@ -418,7 +418,30 @@ } } ] + }, + { + "id": "outdoor_seating", + "options": [ + { + "question": { + "en": "Has outdoor seating" + }, + "icon": "./assets/layers/outdoor_seating.svg", + "osmTags": "outdoor_seating=yes" + } + ] + }, + { + "id": "indoor_seating", + "options": [ + { + "question": { + "en": "Has indoor seating" + }, + "osmTags": "indoor_seating=yes" + } + ] } ], "allowMove": false -} +} \ No newline at end of file diff --git a/assets/layers/food/food.json b/assets/layers/food/food.json index 77d776907..bc81032fa 100644 --- a/assets/layers/food/food.json +++ b/assets/layers/food/food.json @@ -804,6 +804,55 @@ } ] }, + { + "id": "drive-through", + "condition": "amenity=fast_food", + "question": { + "en": "Does this fast-food restaurant have a drive-through?", + "nl": "Heeft dit fastfoodrestaurant een drive-through?" + }, + "mappings": [ + { + "if": "drive_through=yes", + "then": { + "en": "This fast-food restaurant has a drive-through", + "nl": "Dit fastfoodrestaurant heeft een drive-through" + } + }, + { + "if": "drive_through=no", + "then": { + "en": "This fast-food restaurant does not have a drive-through", + "nl": "Dit fastfoodrestaurant heeft geen drive-through" + } + } + ] + }, + { + "id": "drive-through-opening_hours", + "condition": "drive_through=yes", + "question": { + "en": "What are the opening hours of the drive-through?", + "nl": "Wat zijn de openingsuren van de drive-through?" + }, + "freeform": { + "key": "opening_hours:drive_through", + "type": "opening_hours" + }, + "mappings": [ + { + "if": "opening_hours:drive_through=", + "then": { + "en": "The opening hours of the drive-through are the same as the restaurant", + "nl": "De openingsuren van de drive-through zijn dezelfde als die van het restaurant" + } + } + ], + "render": { + "en": "

Drive-through opening hours

{opening_hours_table(opening_hours:drive_through)}", + "nl": "

Openingsuren van de drive-through

{opening_hours_table(opening_hours:drive_through)}" + } + }, { "question": { "nl": "Heeft deze eetgelegenheid een vegetarische optie?", @@ -1341,6 +1390,7 @@ "lactose_free", "smoking", "service:electricity", + "seating", "dog-access", "internet", "internet-fee", @@ -1488,6 +1538,8 @@ "filters.sugar_free", "filters.gluten_free", "filters.lactose_free", + "outdoor_seating", + "indoor_seating", "accepts_cash", "accepts_cards", "dogs" @@ -1545,4 +1597,4 @@ ] }, "allowMove": true -} +} \ No newline at end of file diff --git a/assets/layers/insect_hotel/insect_hotel.json b/assets/layers/insect_hotel/insect_hotel.json new file mode 100644 index 000000000..acb872b34 --- /dev/null +++ b/assets/layers/insect_hotel/insect_hotel.json @@ -0,0 +1,51 @@ +{ + "id": "insect_hotel", + "name": { + "en": "Insect Hotels", + "nl": "Insectenhotels" + }, + "description": { + "en": "Layer showing insect hotels", + "nl": "Laag met insectenhotels" + }, + "source": { + "osmTags": "man_made=insect_hotel" + }, + "minzoom": 11, + "title": { + "en": "Insect Hotel", + "nl": "Insectenhotel" + }, + "presets": [ + { + "title": { + "en": "an insect hotel", + "nl": "een insectenhotel" + }, + "tags": [ + "man_made=insect_hotel" + ] + } + ], + "tagRenderings": [ + "images" + ], + "pointRendering": [ + { + "location": [ + "point", + "centroid" + ], + "marker": [ + { + "icon": "./assets/layers/insect_hotel/insect_hotel.svg" + } + ] + } + ], + "allowMove": { + "enableImproveAccuracy": true, + "enableRelocation": true + }, + "deletion": true +} \ No newline at end of file diff --git a/assets/layers/insect_hotel/insect_hotel.svg b/assets/layers/insect_hotel/insect_hotel.svg new file mode 100644 index 000000000..a612d917a --- /dev/null +++ b/assets/layers/insect_hotel/insect_hotel.svg @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/layers/insect_hotel/insect_hotel.svg.license b/assets/layers/insect_hotel/insect_hotel.svg.license new file mode 100644 index 000000000..75299f884 --- /dev/null +++ b/assets/layers/insect_hotel/insect_hotel.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Robin van der Linde +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/assets/layers/insect_hotel/license_info.json b/assets/layers/insect_hotel/license_info.json new file mode 100644 index 000000000..1b8152aef --- /dev/null +++ b/assets/layers/insect_hotel/license_info.json @@ -0,0 +1,10 @@ +[ + { + "path": "insect_hotel.svg", + "license": "CC0-1.0", + "authors": [ + "Robin van der Linde" + ], + "sources": [] + } +] \ No newline at end of file diff --git a/assets/layers/note/note.json b/assets/layers/note/note.json index 6cc81a558..918292038 100644 --- a/assets/layers/note/note.json +++ b/assets/layers/note/note.json @@ -28,6 +28,7 @@ "_is_import_note:=(() => {const lines = feat.properties['_first_comment'].split('\\n'); const matchesMapCompleteURL = lines.map(l => l.match(\".*https://mapcomplete.\\(osm.be|org\\)/\\([a-zA-Z_-]+\\)\\(.html\\).*#import\")); const matchedIndexes = matchesMapCompleteURL.map((doesMatch, i) => [doesMatch !== null, i]).filter(v => v[0]).map(v => v[1]); return matchedIndexes[0] })()" ], "minzoom": 7, + "isShown": "_total_comments>0", "title": { "render": { "en": "Note", diff --git a/assets/layers/parking/parking.json b/assets/layers/parking/parking.json index d00150365..2cbaeb2f0 100644 --- a/assets/layers/parking/parking.json +++ b/assets/layers/parking/parking.json @@ -350,7 +350,8 @@ "cs": "Počet parkovacích míst {capacity}", "es": "Hay {capacity} plazas de aparcamiento" } - } + }, + "maxstay" ], "deletion": { "softDeletionTags": { @@ -365,4 +366,4 @@ "enableRelocation": false, "enableImproveAccuracy": true } -} +} \ No newline at end of file diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index 1cc5f3ffb..8d976ffc3 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -3186,7 +3186,82 @@ } } ] + }, + { + "id": "seating", + "question": { + "en": "What kind of seating does {title()} have?", + "nl": "Wat voor zitplaatsen heeft {title()}?" + }, + "mappings": [ + { + "if": "outdoor_seating=yes", + "ifnot": "outdoor_seating=no", + "then": { + "en": "This place has outdoor seating", + "nl": "Deze plaats heeft zitplaatsen buiten" + }, + "icon": "./assets/layers/outdoor_seating/outdoor_seating.svg" + }, + { + "if": "indoor_seating=yes", + "ifnot": "indoor_seating=no", + "then": { + "en": "This place has indoor seating", + "nl": "Deze plaats heeft zitplaatsen binnen" + } + } + ], + "multiAnswer": true + }, + { + "id": "maxstay", + "question": { + "en": "What is the maximum amount of time one is allowed to stay here?", + "nl": "Wat is de maximale tijd die je hier mag blijven?" + }, + "freeform": { + "key": "maxstay", + "type": "pfloat" + }, + "render": { + "en": "One can stay at most {canonical(maxstay)}", + "nl": "Je mag hier maximaal {canonical(maxstay)} blijven" + }, + "mappings": [ + { + "if": "maxstay=unlimited", + "then": { + "en": "There is no limit to the amount of time one can stay here", + "nl": "Er is geen limiet aan de tijd die je hier mag blijven" + } + } + ] + }, + { + "id": "name", + "question":{ + "en": "What is the name of this place?" + }, + "render": { + "*": "{name}" + }, + "freeform": { + "key": "name" + } } ], - "allowMove": false + "allowMove": false, + "units": [ + { + "maxstay": { + "quantity": "duration", + "denominations": [ + "minutes", + "hours", + "days" + ] + } + } + ] } diff --git a/assets/layers/recycling/recycling.json b/assets/layers/recycling/recycling.json index 17d336cec..a7dbb58fb 100644 --- a/assets/layers/recycling/recycling.json +++ b/assets/layers/recycling/recycling.json @@ -1292,8 +1292,7 @@ "icon": { "path": "./assets/layers/waste_disposal/waste_disposal.svg", "class": "medium" - }, - "hideInAnswer": "recycling_type=container" + } }, { "if": "recycling:bicycles=yes", @@ -1795,4 +1794,4 @@ "enableRelocation": true, "enableImproveAccuracy": true } -} +} \ No newline at end of file diff --git a/assets/layers/stairs/escalator.svg b/assets/layers/stairs/escalator.svg new file mode 100644 index 000000000..454a4656a --- /dev/null +++ b/assets/layers/stairs/escalator.svg @@ -0,0 +1,6 @@ + + + + + + + + + + + diff --git a/src/UI/BigComponents/SelectedElementTitle.svelte b/src/UI/BigComponents/SelectedElementTitle.svelte index 898f0437b..4a759362f 100644 --- a/src/UI/BigComponents/SelectedElementTitle.svelte +++ b/src/UI/BigComponents/SelectedElementTitle.svelte @@ -1,23 +1,21 @@ +{#if $allDiffs === undefined} + +{:else if $addedImages.length === 0} + No images added by this contributor +{:else} +
+ {#each $addedImages as imgDiff} +
+ +
+ {/each} +
+{/if} diff --git a/src/UI/History/AggregateView.svelte b/src/UI/History/AggregateView.svelte new file mode 100644 index 000000000..22bd1d822 --- /dev/null +++ b/src/UI/History/AggregateView.svelte @@ -0,0 +1,105 @@ + + +{#if allHistories === undefined} + +{:else if $allDiffs !== undefined} + {#each $mergedCount as diff} +

+ {#if diff.tr} + + {:else} + {diff.key} + {/if} +

+ + + + +
    + {#each diff.values as value} +
  • + {value.value} + {#if value.count > 1} + - {value.count} + {/if} +
  • + {/each} +
+
+ {/each} +{/if} diff --git a/src/UI/History/AttributedPanoramaxImage.svelte b/src/UI/History/AttributedPanoramaxImage.svelte new file mode 100644 index 000000000..83463ada6 --- /dev/null +++ b/src/UI/History/AttributedPanoramaxImage.svelte @@ -0,0 +1,13 @@ + + +{#if $image !== undefined} + +{/if} diff --git a/src/UI/History/History.svelte b/src/UI/History/History.svelte new file mode 100644 index 000000000..5cfa6b908 --- /dev/null +++ b/src/UI/History/History.svelte @@ -0,0 +1,106 @@ + + +{#if !$allGeometry || !ignoreLayersIfNoChanges.has($lastStep?.layer?.id)} + {#if $lastStep?.layer} +
+

+
+ +
+ +

+
+ {/if} + + {#if !$filteredHistory} + Loading history... + {:else if $filteredHistory.length === 0} + + {:else} + + {#each $filteredHistory as { step, layer }} + + {#if step.version === 1} + + + + + + {/if} + {#if HistoryUtils.tagHistoryDiff(step, $fullHistory).length === 0} + + + + + {:else} + {#each HistoryUtils.tagHistoryDiff(step, $fullHistory) as diff} + + + + {#if diff.oldValue === undefined} + + + {:else if diff.value === undefined } + + + {:else} + + + {/if} + + + + {/each} + {/if} + {/each} +
+

+

+
{step.version}{layer?.id ?? "Unknown layer"}{diff.key}{diff.value}{diff.key} {diff.value}{diff.key} {diff.oldValue} → {diff.value}
+ {/if} +{/if} diff --git a/src/UI/History/HistoryUtils.ts b/src/UI/History/HistoryUtils.ts new file mode 100644 index 000000000..c85aaeff2 --- /dev/null +++ b/src/UI/History/HistoryUtils.ts @@ -0,0 +1,51 @@ +import * as all_layers from "../../assets/generated/themes/personal.json" +import ThemeConfig from "../../Models/ThemeConfig/ThemeConfig" +import { OsmObject } from "../../Logic/Osm/OsmObject" + +export class HistoryUtils { + + public static readonly personalTheme = new ThemeConfig( all_layers, true) + private static ignoredLayers = new Set(["fixme"]) + public static determineLayer(properties: Record){ + return this.personalTheme.getMatchingLayer(properties, this.ignoredLayers) + } + + public static tagHistoryDiff(step: OsmObject, history: OsmObject[]): { + key: string, + value?: string, + oldValue?: string, + step: OsmObject + }[] { + const previous = history[step.version - 2] + if (!previous) { + return Object.keys(step.tags).filter(key => !key.startsWith("_") && key !== "id").map(key => ({ + key, value: step.tags[key], step + })) + } + const previousTags = previous.tags + return Object.keys(step.tags).filter(key => !key.startsWith("_") ) + .map(key => { + const value = step.tags[key] + const oldValue = previousTags[key] + return { + key, value, oldValue, step + } + }).filter(ch => ch.oldValue !== ch.value) + } + + public static fullHistoryDiff(histories: OsmObject[][], onlyShowUsername?: Set){ + const allDiffs: {key: string, oldValue?: string, value?: string}[] = [].concat(...histories.map( + history => { + const filtered = history.filter(step => !onlyShowUsername || onlyShowUsername?.has(step.tags["_last_edit:contributor"] )) + const diffs: { + key: string; + value?: string; + oldValue?: string + }[][] = filtered.map(step => HistoryUtils.tagHistoryDiff(step, history)) + return [].concat(...diffs) + } + )) + return allDiffs + } + +} diff --git a/src/UI/History/PreviouslySpiedUsers.svelte b/src/UI/History/PreviouslySpiedUsers.svelte new file mode 100644 index 000000000..4bb757b43 --- /dev/null +++ b/src/UI/History/PreviouslySpiedUsers.svelte @@ -0,0 +1,107 @@ + + + + + + + + + + + {#each $inspectedContributors as c} + + + + + + + {/each} +
+ + + + + + + Remove
+ + + {c.visitedTime} + + + + remove(c.name)} /> +
+ + + +
Labels
+ {#if $labels.length === 0} + No labels + {:else} + {#each $labels as label} +
{label} + +
+ {/each} + {/if} +
+
Create a new label
+ + +
+
+
diff --git a/src/UI/Image/AttributedImage.svelte b/src/UI/Image/AttributedImage.svelte index 02a871249..546de016d 100644 --- a/src/UI/Image/AttributedImage.svelte +++ b/src/UI/Image/AttributedImage.svelte @@ -28,22 +28,24 @@ export let imgClass: string = undefined export let state: SpecialVisualizationState = undefined export let attributionFormat: "minimal" | "medium" | "large" = "medium" - export let previewedImage: UIEventSource + export let previewedImage: UIEventSource = undefined export let canZoom = previewedImage !== undefined let loaded = false let showBigPreview = new UIEventSource(false) onDestroy( showBigPreview.addCallbackAndRun((shown) => { if (!shown) { - previewedImage.set(undefined) + previewedImage?.set(undefined) } }) ) + if(previewedImage){ onDestroy( previewedImage.addCallbackAndRun((previewedImage) => { showBigPreview.set(previewedImage?.id === image.id) }) ) + } function highlight(entered: boolean = true) { if (!entered) { @@ -82,7 +84,7 @@ class="normal-background" on:click={() => { console.log("Closing") - previewedImage.set(undefined) + previewedImage?.set(undefined) }} /> @@ -124,7 +126,7 @@ {#if canZoom && loaded}
previewedImage.set(image)} + on:click={() => previewedImage?.set(image)} >
diff --git a/src/UI/InspectorGUI.svelte b/src/UI/InspectorGUI.svelte new file mode 100644 index 000000000..8d2c99afc --- /dev/null +++ b/src/UI/InspectorGUI.svelte @@ -0,0 +1,219 @@ + + +
+ +
+ +

+ +

+ load()} /> + {#if loadingData} + + {:else} + + {/if} + + + + +
+ +
+ + + + +
+ + {#if mode === "map"} + {#if $selectedElement !== undefined} + + + {/if} + +
+ +
+ {:else if mode === "table"} +
+ {#each $featuresStore as f} + + {/each} +
+ {:else if mode === "aggregate"} +
+ +
+ {:else if mode === "images"} +
+ +
+ {/if} +
+ + +
Earlier inspected constributors
+ { + username.set(e.detail); load();showPreviouslyVisited.set(false) + }} /> +
diff --git a/src/UI/InspectorGUI.ts b/src/UI/InspectorGUI.ts new file mode 100644 index 000000000..d26f12987 --- /dev/null +++ b/src/UI/InspectorGUI.ts @@ -0,0 +1,5 @@ +import InspectorGUI from "./InspectorGUI.svelte" + +new InspectorGUI({ + target: document.getElementById("main"), +}) diff --git a/src/UI/Popup/DeleteFlow/DeleteFlowState.ts b/src/UI/Popup/DeleteFlow/DeleteFlowState.ts index 9bab13c12..97bd82876 100644 --- a/src/UI/Popup/DeleteFlow/DeleteFlowState.ts +++ b/src/UI/Popup/DeleteFlow/DeleteFlowState.ts @@ -97,7 +97,7 @@ export class DeleteFlowState { if (allByMyself.data === null && useTheInternet) { // We kickoff the download here as it hasn't yet been downloaded. Note that this is mapped onto 'all by myself' above const hist = this.objectDownloader - .DownloadHistory(id) + .downloadHistory(id) .map((versions) => versions.map((version) => Number(version.tags["_last_edit:contributor:uid"]) diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index 58c256c32..e021daad3 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -80,7 +80,7 @@ export interface SpecialVisualizationState { readonly geocodedImages: UIEventSource readonly searchState: SearchState - getMatchingLayer(properties: Record) + getMatchingLayer(properties: Record): LayerConfig | undefined showCurrentLocationOn(map: Store): ShowDataLayer reportError(message: string | Error | XMLHttpRequest, extramessage?: string): Promise diff --git a/src/UI/Test.svelte b/src/UI/Test.svelte index 118013989..586400478 100644 --- a/src/UI/Test.svelte +++ b/src/UI/Test.svelte @@ -34,6 +34,9 @@ >tags?.GPSLongitude?.value const exifLat = latD + latM / 60 + latS / (3600 * latSDenom) const exifLon = lonD + lonM / 60 + lonS / (3600 * lonSDenom) + const directValueLat = tags?.GPSLatitude?.description + const directValueLon = tags?.GPSLongitude?.description + if ( typeof exifLat === "number" && !isNaN(exifLat) && @@ -43,11 +46,22 @@ ) { lat = exifLat lon = exifLon + if(tags?.GPSLatitudeRef?.value?.[0] === "S"){ + lat *= -1 + } + if(tags?.GPSLongitudeRef?.value?.[0] === "W"){ + lon *= -1 + } l("Using EXIFLAT + EXIFLON") } else { l("NOT using exifLat and exifLon: invalid value detected") } l("Lat and lon are", lat, lon) + l("ref lat is", tags?.GPSLatitudeRef?.description, JSON.stringify(tags?.GPSLatitudeRef?.value)) + l("ref lon is", tags?.GPSLongitudeRef?.description, JSON.stringify(tags?.GPSLongitudeRef?.value)) + + + l("Direct values are", directValueLat,directValueLon,"corrected:",lat,lon) l("Datetime value is", JSON.stringify(tags.DateTime)) const [date, time] = tags.DateTime.value[0].split(" ") datetime = new Date(date.replaceAll(":", "-") + "T" + time).toISOString() diff --git a/statistics.html b/statistics.html index 35b3cf327..6b62b9d56 100644 --- a/statistics.html +++ b/statistics.html @@ -5,9 +5,7 @@ MapComplete statistics - -