forked from MapComplete/MapComplete
		
	Merge feature branch
This commit is contained in:
		
						commit
						a6c752037b
					
				
					 28 changed files with 931 additions and 149 deletions
				
			
		
							
								
								
									
										55
									
								
								.github/workflows/deploy_hosted.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								.github/workflows/deploy_hosted.yml
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					name: Deploy develop on dev.mapcomplete.org
 | 
				
			||||||
 | 
					on:
 | 
				
			||||||
 | 
					  push
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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: run tests
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          pwd
 | 
				
			||||||
 | 
					          ls
 | 
				
			||||||
 | 
					          npm run test
 | 
				
			||||||
 | 
					        shell: bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Prepare deploy
 | 
				
			||||||
 | 
					        run: npm run prepare-deploy
 | 
				
			||||||
 | 
					        shell: bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Upload artefact
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          SSH_KEY: ${{ secrets.HETZNER_KEY }}
 | 
				
			||||||
 | 
					        run: |
 | 
				
			||||||
 | 
					          mkdir .ssh
 | 
				
			||||||
 | 
					          echo $SSH_KEY > .ssh/id_ed25519
 | 
				
			||||||
 | 
					          scp dist/* pietervdvn@hosted.mapcomplete.org:/root/public/${{ github.ref_name }}
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3232,6 +3232,18 @@
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "name",
 | 
				
			||||||
 | 
					      "question":{
 | 
				
			||||||
 | 
					        "en": "What is the name of this place?"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "*": "<b>{name}</b>"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "freeform": {
 | 
				
			||||||
 | 
					        "key": "name"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  "allowMove": false,
 | 
					  "allowMove": false,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										70
									
								
								assets/layers/usertouched/usertouched.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								assets/layers/usertouched/usertouched.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,70 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "id": "usertouched",
 | 
				
			||||||
 | 
					  "description": {
 | 
				
			||||||
 | 
					    "en": "Special layer showing all items which were changed by a certain user"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "name": {
 | 
				
			||||||
 | 
					    "en": "Changed by user"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "title": {
 | 
				
			||||||
 | 
					    "render": {
 | 
				
			||||||
 | 
					      "en": "Changed by user"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "source": "special",
 | 
				
			||||||
 | 
					  "tagRenderings": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "id": "test",
 | 
				
			||||||
 | 
					      "render": {
 | 
				
			||||||
 | 
					        "en": "Changed by user"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "all_tags"
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "pointRendering": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "location": [
 | 
				
			||||||
 | 
					        "point",
 | 
				
			||||||
 | 
					        "centroid"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "iconSize": "15,15",
 | 
				
			||||||
 | 
					      "marker": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "icon": "circle",
 | 
				
			||||||
 | 
					          "color": "#aaa"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "icon": "ring",
 | 
				
			||||||
 | 
					          "color": "#000"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "lineRendering": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "color": "black",
 | 
				
			||||||
 | 
					      "width": 3,
 | 
				
			||||||
 | 
					      "fillColor": "#00000000"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "color": "#cccccccc",
 | 
				
			||||||
 | 
					      "width": {
 | 
				
			||||||
 | 
					        "render": 0,
 | 
				
			||||||
 | 
					        "mappings": [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            "if": {
 | 
				
			||||||
 | 
					              "or": [
 | 
				
			||||||
 | 
					                "_geometry:type=Polygon",
 | 
				
			||||||
 | 
					                "_geometry:type=MultiPolygon"
 | 
				
			||||||
 | 
					              ]
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "then": 20
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "offset": 15,
 | 
				
			||||||
 | 
					      "fillColor": "#00000000"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "allowMove": false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								assets/themes/inspector/inspector.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								assets/themes/inspector/inspector.json
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "id": "inspector",
 | 
				
			||||||
 | 
					  "title": {
 | 
				
			||||||
 | 
					    "en": "Inspect changes from a single user"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "description": {
 | 
				
			||||||
 | 
					    "en": "A theme to inspect what a single user did in the past"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "icon": "./assets/svg/add.svg",
 | 
				
			||||||
 | 
					  "layers": [
 | 
				
			||||||
 | 
					    "usertouched"
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -4,26 +4,20 @@
 | 
				
			||||||
    "en": "Changes made with MapComplete",
 | 
					    "en": "Changes made with MapComplete",
 | 
				
			||||||
    "de": "Änderungen mit MapComplete",
 | 
					    "de": "Änderungen mit MapComplete",
 | 
				
			||||||
    "cs": "Změny provedené pomocí MapComplete",
 | 
					    "cs": "Změny provedené pomocí MapComplete",
 | 
				
			||||||
    "es": "Cambios realizados con MapComplete",
 | 
					    "es": "Cambios realizados con MapComplete"
 | 
				
			||||||
    "fr": "Modifications faites avec MapComplete",
 | 
					 | 
				
			||||||
    "nl": "Wijzigingen gemaakt met MapComplete"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "shortDescription": {
 | 
					  "shortDescription": {
 | 
				
			||||||
    "en": "Shows changes made by MapComplete",
 | 
					    "en": "Shows changes made by MapComplete",
 | 
				
			||||||
    "de": "Zeigt die von MapComplete vorgenommenen Änderungen an",
 | 
					    "de": "Zeigt die von MapComplete vorgenommenen Änderungen an",
 | 
				
			||||||
    "cs": "Zobrazuje změny provedené nástrojem MapComplete",
 | 
					    "cs": "Zobrazuje změny provedené nástrojem MapComplete",
 | 
				
			||||||
    "es": "Muestra los cambios realizados por MapComplete",
 | 
					    "es": "Muestra los cambios realizados por MapComplete"
 | 
				
			||||||
    "fr": "Afficher les modifications faites avec MapComplete",
 | 
					 | 
				
			||||||
    "nl": "Toont wijzigingen gemaakt met MapComplete"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "description": {
 | 
					  "description": {
 | 
				
			||||||
    "en": "This maps shows all the changes made with MapComplete",
 | 
					    "en": "This maps shows all the changes made with MapComplete",
 | 
				
			||||||
    "de": "Diese Karte zeigt alle mit MapComplete vorgenommenen Änderungen",
 | 
					    "de": "Diese Karte zeigt alle mit MapComplete vorgenommenen Änderungen",
 | 
				
			||||||
    "es": "Este mapa muestra todos los cambios realizados con MapComplete",
 | 
					    "es": "Este mapa muestra todos los cambios realizados con MapComplete",
 | 
				
			||||||
    "pl": "Ta mapa pokazuje wszystkie zmiany wprowadzone za pomocą MapComplete",
 | 
					    "pl": "Ta mapa pokazuje wszystkie zmiany wprowadzone za pomocą MapComplete",
 | 
				
			||||||
    "cs": "Tyto mapy zobrazují všechny změny provedené pomocí MapComplete",
 | 
					    "cs": "Tyto mapy zobrazují všechny změny provedené pomocí MapComplete"
 | 
				
			||||||
    "fr": "Cette carte montre tous les changements effectués avec MapComplete",
 | 
					 | 
				
			||||||
    "nl": "Deze kaarten tonen alle wijzigingen die zijn gemaakt met MapComplete"
 | 
					 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "icon": "./assets/svg/logo.svg",
 | 
					  "icon": "./assets/svg/logo.svg",
 | 
				
			||||||
  "hideFromOverview": true,
 | 
					  "hideFromOverview": true,
 | 
				
			||||||
| 
						 | 
					@ -36,10 +30,7 @@
 | 
				
			||||||
      "name": {
 | 
					      "name": {
 | 
				
			||||||
        "en": "Changeset centers",
 | 
					        "en": "Changeset centers",
 | 
				
			||||||
        "de": "Changeset-Zentren",
 | 
					        "de": "Changeset-Zentren",
 | 
				
			||||||
        "es": "Centros de conjuntos de cambios",
 | 
					        "es": "Centros de conjuntos de cambios"
 | 
				
			||||||
        "fr": "Centre du groupe de modifications",
 | 
					 | 
				
			||||||
        "nl": "Changeset centra",
 | 
					 | 
				
			||||||
        "cs": "Changeset centra"
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "minzoom": 0,
 | 
					      "minzoom": 0,
 | 
				
			||||||
      "source": {
 | 
					      "source": {
 | 
				
			||||||
| 
						 | 
					@ -52,16 +43,14 @@
 | 
				
			||||||
          "en": "Changeset for {theme}",
 | 
					          "en": "Changeset for {theme}",
 | 
				
			||||||
          "de": "Änderungssatz für {theme}",
 | 
					          "de": "Änderungssatz für {theme}",
 | 
				
			||||||
          "cs": "Sada změn pro {theme}",
 | 
					          "cs": "Sada změn pro {theme}",
 | 
				
			||||||
          "es": "Conjunto de cambios para {theme}",
 | 
					          "es": "Conjunto de cambios para {theme}"
 | 
				
			||||||
          "nl": "Changeset voor {theme}"
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "description": {
 | 
					      "description": {
 | 
				
			||||||
        "en": "Shows all MapComplete changes",
 | 
					        "en": "Shows all MapComplete changes",
 | 
				
			||||||
        "de": "Zeigt alle MapComplete-Änderungen",
 | 
					        "de": "Zeigt alle MapComplete-Änderungen",
 | 
				
			||||||
        "es": "Muestra todos los cambios de MapComplete",
 | 
					        "es": "Muestra todos los cambios de MapComplete",
 | 
				
			||||||
        "cs": "Zobrazí všechny změny MapComplete",
 | 
					        "cs": "Zobrazí všechny změny MapComplete"
 | 
				
			||||||
        "nl": "Toon alle MapComplete-wijzigingen"
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "tagRenderings": [
 | 
					      "tagRenderings": [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
| 
						 | 
					@ -70,8 +59,7 @@
 | 
				
			||||||
            "en": "Changeset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
 | 
					            "en": "Changeset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
 | 
				
			||||||
            "de": "Änderungssatz <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
 | 
					            "de": "Änderungssatz <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
 | 
				
			||||||
            "cs": "Sada změn <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
 | 
					            "cs": "Sada změn <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
 | 
				
			||||||
            "es": "Conjunto de cambios <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
 | 
					            "es": "Conjunto de cambios <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>"
 | 
				
			||||||
            "nl": "Changeset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>"
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
| 
						 | 
					@ -80,8 +68,7 @@
 | 
				
			||||||
            "en": "What contributor did make this change?",
 | 
					            "en": "What contributor did make this change?",
 | 
				
			||||||
            "de": "Wer hat zu dieser Änderung beigetragen?",
 | 
					            "de": "Wer hat zu dieser Änderung beigetragen?",
 | 
				
			||||||
            "cs": "Který přispěvatel provedl tuto změnu?",
 | 
					            "cs": "Který přispěvatel provedl tuto změnu?",
 | 
				
			||||||
            "es": "¿Qué colaborador realizó este cambio?",
 | 
					            "es": "¿Qué colaborador realizó este cambio?"
 | 
				
			||||||
            "nl": "Welke bijdrager maakte deze verandering?"
 | 
					 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          "freeform": {
 | 
					          "freeform": {
 | 
				
			||||||
            "key": "user"
 | 
					            "key": "user"
 | 
				
			||||||
| 
						 | 
					@ -90,9 +77,7 @@
 | 
				
			||||||
            "en": "Change made by <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
 | 
					            "en": "Change made by <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
 | 
				
			||||||
            "de": "Änderung vorgenommen von <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
 | 
					            "de": "Änderung vorgenommen von <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
 | 
				
			||||||
            "cs": "Změna provedena uživatelem <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
 | 
					            "cs": "Změna provedena uživatelem <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
 | 
				
			||||||
            "es": "Cambio realizado por <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
 | 
					            "es": "Cambio realizado por <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>"
 | 
				
			||||||
            "fr": "Modification faite par <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>",
 | 
					 | 
				
			||||||
            "nl": "Wijziging aangebracht door <a href='https://openstreetmap.org/user/{user}' target='_blank'>{user}</a>"
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
| 
						 | 
					@ -101,8 +86,7 @@
 | 
				
			||||||
            "en": "What theme was used to make this change?",
 | 
					            "en": "What theme was used to make this change?",
 | 
				
			||||||
            "de": "Welches Thema wurde für diese Änderung verwendet?",
 | 
					            "de": "Welches Thema wurde für diese Änderung verwendet?",
 | 
				
			||||||
            "cs": "Jaký motiv byl použit k provedení této změny?",
 | 
					            "cs": "Jaký motiv byl použit k provedení této změny?",
 | 
				
			||||||
            "es": "¿Qué tema se utilizó para realizar este cambio?",
 | 
					            "es": "¿Qué tema se utilizó para realizar este cambio?"
 | 
				
			||||||
            "nl": "Welk thema werd gebruikt voor deze wijziging?"
 | 
					 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          "freeform": {
 | 
					          "freeform": {
 | 
				
			||||||
            "key": "theme"
 | 
					            "key": "theme"
 | 
				
			||||||
| 
						 | 
					@ -110,9 +94,7 @@
 | 
				
			||||||
          "render": {
 | 
					          "render": {
 | 
				
			||||||
            "en": "Change with theme <a href='https://mapcomplete.org/{theme}'>{theme}</a>",
 | 
					            "en": "Change with theme <a href='https://mapcomplete.org/{theme}'>{theme}</a>",
 | 
				
			||||||
            "de": "Änderung mit Thema <a href='https://mapcomplete.org/{theme}'>{theme}</a>",
 | 
					            "de": "Änderung mit Thema <a href='https://mapcomplete.org/{theme}'>{theme}</a>",
 | 
				
			||||||
            "es": "Cambio con el tema <a href='https://mapcomplete.org/{theme}'>{theme}</a>",
 | 
					            "es": "Cambio con el tema <a href='https://mapcomplete.org/{theme}'>{theme}</a>"
 | 
				
			||||||
            "nl": "Verander met thema <a href='https://mapcomplete.org/{theme}'>{theme}</a>",
 | 
					 | 
				
			||||||
            "cs": "Změna pomocí tématu <a href='https://mapcomplete.org/{theme}'>{theme}</a>"
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
| 
						 | 
					@ -124,15 +106,13 @@
 | 
				
			||||||
            "en": "What locale (language) was this change made in?",
 | 
					            "en": "What locale (language) was this change made in?",
 | 
				
			||||||
            "de": "In welcher Sprache (Locale) wurde diese Änderung vorgenommen?",
 | 
					            "de": "In welcher Sprache (Locale) wurde diese Änderung vorgenommen?",
 | 
				
			||||||
            "cs": "V jakém prostředí (jazyce) byla tato změna provedena?",
 | 
					            "cs": "V jakém prostředí (jazyce) byla tato změna provedena?",
 | 
				
			||||||
            "es": "¿En qué configuración regional (idioma) se realizó este cambio?",
 | 
					            "es": "¿En qué configuración regional (idioma) se realizó este cambio?"
 | 
				
			||||||
            "nl": "In welke 'locale' (taal) is deze wijziging gemaakt?"
 | 
					 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          "render": {
 | 
					          "render": {
 | 
				
			||||||
            "en": "User locale is {locale}",
 | 
					            "en": "User locale is {locale}",
 | 
				
			||||||
            "de": "Die Benutzersprache ist {locale}",
 | 
					            "de": "Die Benutzersprache ist {locale}",
 | 
				
			||||||
            "cs": "Uživatelské prostředí je {locale}",
 | 
					            "cs": "Uživatelské prostředí je {locale}",
 | 
				
			||||||
            "es": "Configuración regional del usuario es {locale}",
 | 
					            "es": "Configuración regional del usuario es {locale}"
 | 
				
			||||||
            "nl": "De gebruikerstaal (locale) is {locale}"
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
| 
						 | 
					@ -141,15 +121,13 @@
 | 
				
			||||||
            "en": "Change with with <a href='{host}'>{host}</a>",
 | 
					            "en": "Change with with <a href='{host}'>{host}</a>",
 | 
				
			||||||
            "de": "Änderung mit <a href='{host}'>{host}</a>",
 | 
					            "de": "Änderung mit <a href='{host}'>{host}</a>",
 | 
				
			||||||
            "cs": "Změnit pomocí <a href='{host}'>{host}</a>",
 | 
					            "cs": "Změnit pomocí <a href='{host}'>{host}</a>",
 | 
				
			||||||
            "es": "Cambio realizado con <a href='{host}'>{host}</a>",
 | 
					            "es": "Cambio realizado con <a href='{host}'>{host}</a>"
 | 
				
			||||||
            "nl": "Gewijzigd met <a href='{host}'>{host}</a>"
 | 
					 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          "question": {
 | 
					          "question": {
 | 
				
			||||||
            "en": "What host (website) was this change made with?",
 | 
					            "en": "What host (website) was this change made with?",
 | 
				
			||||||
            "de": "Bei welchem Host (Website) wurde diese Änderung vorgenommen?",
 | 
					            "de": "Bei welchem Host (Website) wurde diese Änderung vorgenommen?",
 | 
				
			||||||
            "cs": "U jakého hostitele (webové stránky) byla tato změna provedena?",
 | 
					            "cs": "U jakého hostitele (webové stránky) byla tato změna provedena?",
 | 
				
			||||||
            "es": "¿Con qué anfitrión (sitio web) se realizó este cambio?",
 | 
					            "es": "¿Con qué anfitrión (sitio web) se realizó este cambio?"
 | 
				
			||||||
            "nl": "Met welke host (website) is deze wijziging gemaakt?"
 | 
					 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          "freeform": {
 | 
					          "freeform": {
 | 
				
			||||||
            "key": "host"
 | 
					            "key": "host"
 | 
				
			||||||
| 
						 | 
					@ -173,17 +151,13 @@
 | 
				
			||||||
            "en": "What version of MapComplete was used to make this change?",
 | 
					            "en": "What version of MapComplete was used to make this change?",
 | 
				
			||||||
            "de": "Welche Version von MapComplete wurde verwendet, um diese Änderung vorzunehmen?",
 | 
					            "de": "Welche Version von MapComplete wurde verwendet, um diese Änderung vorzunehmen?",
 | 
				
			||||||
            "cs": "Jaká verze aplikace MapComplete byla použita k provedení této změny?",
 | 
					            "cs": "Jaká verze aplikace MapComplete byla použita k provedení této změny?",
 | 
				
			||||||
            "es": "¿Qué versión de MapComplete se utilizó para realizar este cambio?",
 | 
					            "es": "¿Qué versión de MapComplete se utilizó para realizar este cambio?"
 | 
				
			||||||
            "fr": "Quelle version de MapCompletee a été utilisée pour faire cette modification ?",
 | 
					 | 
				
			||||||
            "nl": "Welke versie van MapComplete is gebruikt voor deze wijziging?"
 | 
					 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          "render": {
 | 
					          "render": {
 | 
				
			||||||
            "en": "Made with {editor}",
 | 
					            "en": "Made with {editor}",
 | 
				
			||||||
            "de": "Erstellt mit {editor}",
 | 
					            "de": "Erstellt mit {editor}",
 | 
				
			||||||
            "cs": "Vytvořeno pomocí {editor}",
 | 
					            "cs": "Vytvořeno pomocí {editor}",
 | 
				
			||||||
            "es": "Hecho con {editor}",
 | 
					            "es": "Hecho con {editor}"
 | 
				
			||||||
            "fr": "Fait avec {editor}",
 | 
					 | 
				
			||||||
            "nl": "Gemaakt met {editor}"
 | 
					 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          "freeform": {
 | 
					          "freeform": {
 | 
				
			||||||
            "key": "editor"
 | 
					            "key": "editor"
 | 
				
			||||||
| 
						 | 
					@ -378,8 +352,8 @@
 | 
				
			||||||
                    "then": "./assets/layers/entrance/entrance.svg"
 | 
					                    "then": "./assets/layers/entrance/entrance.svg"
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                  {
 | 
					                  {
 | 
				
			||||||
                    "if": "theme=insects",
 | 
					                    "if": "theme=inspector",
 | 
				
			||||||
                    "then": "./assets/layers/insect_hotel/insect_hotel.svg"
 | 
					                    "then": "./assets/svg/add.svg"
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                  {
 | 
					                  {
 | 
				
			||||||
                    "if": "theme=items_with_image",
 | 
					                    "if": "theme=items_with_image",
 | 
				
			||||||
| 
						 | 
					@ -589,9 +563,7 @@
 | 
				
			||||||
                "de": "Themenname enthält {search}",
 | 
					                "de": "Themenname enthält {search}",
 | 
				
			||||||
                "es": "El nombre del tema contiene {search}",
 | 
					                "es": "El nombre del tema contiene {search}",
 | 
				
			||||||
                "pl": "Nazwa tematu zawiera {search}",
 | 
					                "pl": "Nazwa tematu zawiera {search}",
 | 
				
			||||||
                "cs": "Název obsahuje {search}",
 | 
					                "cs": "Název obsahuje {search}"
 | 
				
			||||||
                "fr": "Le nom du thème contient {search}",
 | 
					 | 
				
			||||||
                "nl": "Themanaam bevat {search}"
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
| 
						 | 
					@ -610,9 +582,7 @@
 | 
				
			||||||
                "en": "Themename does <b>not</b> contain {search}",
 | 
					                "en": "Themename does <b>not</b> contain {search}",
 | 
				
			||||||
                "de": "Themename enthält <b>nicht</b> {search}",
 | 
					                "de": "Themename enthält <b>nicht</b> {search}",
 | 
				
			||||||
                "es": "El nombre del tema <b>no</b> contiene {search}",
 | 
					                "es": "El nombre del tema <b>no</b> contiene {search}",
 | 
				
			||||||
                "cs": "Název motivu <b>neobsahuje</b> {search}",
 | 
					                "cs": "Název motivu <b>neobsahuje</b> {search}"
 | 
				
			||||||
                "fr": "Le nom du thème <b>ne contient pas</b> {search}",
 | 
					 | 
				
			||||||
                "nl": "Themanaam bevat <b>geen</b> {search}"
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
| 
						 | 
					@ -631,9 +601,7 @@
 | 
				
			||||||
                "en": "Made by contributor {search}",
 | 
					                "en": "Made by contributor {search}",
 | 
				
			||||||
                "de": "Erstellt von Mitwirkendem {search}",
 | 
					                "de": "Erstellt von Mitwirkendem {search}",
 | 
				
			||||||
                "es": "Hecho por el colaborador {search}",
 | 
					                "es": "Hecho por el colaborador {search}",
 | 
				
			||||||
                "cs": "Vytvořeno přispěvatelem {search}",
 | 
					                "cs": "Vytvořeno přispěvatelem {search}"
 | 
				
			||||||
                "fr": "Fait par le·a contributeur·trice {search}",
 | 
					 | 
				
			||||||
                "nl": "Toegevoegd door {search}"
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
| 
						 | 
					@ -652,9 +620,7 @@
 | 
				
			||||||
                "en": "<b>Not</b> made by contributor {search}",
 | 
					                "en": "<b>Not</b> made by contributor {search}",
 | 
				
			||||||
                "de": "<b>Nicht</b> erstellt von Mitwirkendem {search}",
 | 
					                "de": "<b>Nicht</b> erstellt von Mitwirkendem {search}",
 | 
				
			||||||
                "es": "<b>No</b> hecho por el colaborador {search}",
 | 
					                "es": "<b>No</b> hecho por el colaborador {search}",
 | 
				
			||||||
                "cs": "<b>Nevytvořeno</b> přispěvatelem {search}",
 | 
					                "cs": "<b>Nevytvořeno</b> přispěvatelem {search}"
 | 
				
			||||||
                "fr": "<b>Pas</b> fait par le·a contributeur·trice {search}",
 | 
					 | 
				
			||||||
                "nl": "<b>Niet</b> toegevoegd door {search}"
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
| 
						 | 
					@ -674,9 +640,7 @@
 | 
				
			||||||
                "en": "Made before {search}",
 | 
					                "en": "Made before {search}",
 | 
				
			||||||
                "de": "Erstellt vor {search}",
 | 
					                "de": "Erstellt vor {search}",
 | 
				
			||||||
                "es": "Hecho antes de {search}",
 | 
					                "es": "Hecho antes de {search}",
 | 
				
			||||||
                "cs": "Vytvořeno před {search}",
 | 
					                "cs": "Vytvořeno před {search}"
 | 
				
			||||||
                "fr": "Fait avant {search}",
 | 
					 | 
				
			||||||
                "nl": "Toegevoegd vóór {search}"
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
| 
						 | 
					@ -696,9 +660,7 @@
 | 
				
			||||||
                "en": "Made after {search}",
 | 
					                "en": "Made after {search}",
 | 
				
			||||||
                "de": "Erstellt nach {search}",
 | 
					                "de": "Erstellt nach {search}",
 | 
				
			||||||
                "es": "Hecho después de {search}",
 | 
					                "es": "Hecho después de {search}",
 | 
				
			||||||
                "cs": "Vytvořeno po {search}",
 | 
					                "cs": "Vytvořeno po {search}"
 | 
				
			||||||
                "fr": "Fait après {search}",
 | 
					 | 
				
			||||||
                "nl": "Toegevoegd na {search}"
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
| 
						 | 
					@ -717,9 +679,7 @@
 | 
				
			||||||
                "en": "User language (iso-code) {search}",
 | 
					                "en": "User language (iso-code) {search}",
 | 
				
			||||||
                "de": "Benutzersprache (ISO-Code) {search}",
 | 
					                "de": "Benutzersprache (ISO-Code) {search}",
 | 
				
			||||||
                "es": "Idioma del usuario (código ISO) {search}",
 | 
					                "es": "Idioma del usuario (código ISO) {search}",
 | 
				
			||||||
                "cs": "Jazyk uživatele (iso-kód) {search}",
 | 
					                "cs": "Jazyk uživatele (iso-kód) {search}"
 | 
				
			||||||
                "fr": "Langage utilisateur (code iso) {search}",
 | 
					 | 
				
			||||||
                "nl": "Gebruikerstaal (iso-code) {search}"
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
| 
						 | 
					@ -738,8 +698,7 @@
 | 
				
			||||||
                "en": "Made with host {search}",
 | 
					                "en": "Made with host {search}",
 | 
				
			||||||
                "de": "Erstellt mit Host {search}",
 | 
					                "de": "Erstellt mit Host {search}",
 | 
				
			||||||
                "cs": "Vytvořeno pomocí hostitele {search}",
 | 
					                "cs": "Vytvořeno pomocí hostitele {search}",
 | 
				
			||||||
                "es": "Hecho con el anfitrión {search}",
 | 
					                "es": "Hecho con el anfitrión {search}"
 | 
				
			||||||
                "nl": "Gemaakt met {search}"
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
| 
						 | 
					@ -753,8 +712,7 @@
 | 
				
			||||||
                "en": "Changeset added at least one image",
 | 
					                "en": "Changeset added at least one image",
 | 
				
			||||||
                "de": "Changeset hat mindestens ein Bild hinzugefügt",
 | 
					                "de": "Changeset hat mindestens ein Bild hinzugefügt",
 | 
				
			||||||
                "cs": "Sada změn přidala alespoň jeden obrázek",
 | 
					                "cs": "Sada změn přidala alespoň jeden obrázek",
 | 
				
			||||||
                "es": "El conjunto de cambios agregó al menos una imagen",
 | 
					                "es": "El conjunto de cambios agregó al menos una imagen"
 | 
				
			||||||
                "nl": "Changeset voegde minstens één afbeelding toe"
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
| 
						 | 
					@ -768,8 +726,7 @@
 | 
				
			||||||
                "en": "Exclude GRB theme",
 | 
					                "en": "Exclude GRB theme",
 | 
				
			||||||
                "de": "GRB-Thema ausschließen",
 | 
					                "de": "GRB-Thema ausschließen",
 | 
				
			||||||
                "cs": "Vyloučit motiv GRB",
 | 
					                "cs": "Vyloučit motiv GRB",
 | 
				
			||||||
                "es": "Excluir el tema GRB",
 | 
					                "es": "Excluir el tema GRB"
 | 
				
			||||||
                "nl": "GRB-thema uitsluiten"
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
| 
						 | 
					@ -783,8 +740,7 @@
 | 
				
			||||||
                "en": "Exclude etymology theme",
 | 
					                "en": "Exclude etymology theme",
 | 
				
			||||||
                "de": "Etymologie-Thema ausschließen",
 | 
					                "de": "Etymologie-Thema ausschließen",
 | 
				
			||||||
                "es": "Excluir el tema de etimología",
 | 
					                "es": "Excluir el tema de etimología",
 | 
				
			||||||
                "cs": "Vyloučit etymologii tématu",
 | 
					                "cs": "Vyloučit etymologii tématu"
 | 
				
			||||||
                "nl": "Thema etymologie uitsluiten"
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          ]
 | 
					          ]
 | 
				
			||||||
| 
						 | 
					@ -802,9 +758,7 @@
 | 
				
			||||||
              "en": "More statistics can be found <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>here</a>",
 | 
					              "en": "More statistics can be found <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>here</a>",
 | 
				
			||||||
              "de": "Weitere Statistiken findest du <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>hier</a>",
 | 
					              "de": "Weitere Statistiken findest du <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>hier</a>",
 | 
				
			||||||
              "cs": "Další statistiky najdete <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>zde</a>",
 | 
					              "cs": "Další statistiky najdete <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>zde</a>",
 | 
				
			||||||
              "es": "Puedes encontrar más estadísticas <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>aquí</a>",
 | 
					              "es": "Puedes encontrar más estadísticas <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>aquí</a>"
 | 
				
			||||||
              "fr": "Plus de statistiques peuvent être trouvées <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>ici</a>",
 | 
					 | 
				
			||||||
              "nl": "Meer statistieken vind je <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>hier</a>"
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -675,6 +675,9 @@
 | 
				
			||||||
        "pickTheme": "Pick a theme below to get started.",
 | 
					        "pickTheme": "Pick a theme below to get started.",
 | 
				
			||||||
        "title": "MapComplete"
 | 
					        "title": "MapComplete"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "inspector": {
 | 
				
			||||||
 | 
					        "menu": "Inspect a contributor"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "move": {
 | 
					    "move": {
 | 
				
			||||||
        "cancel": "Select a different reason",
 | 
					        "cancel": "Select a different reason",
 | 
				
			||||||
        "cannotBeMoved": "This feature cannot be moved.",
 | 
					        "cannotBeMoved": "This feature cannot be moved.",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -643,11 +643,15 @@ class LayerOverviewUtils extends Script {
 | 
				
			||||||
                    LayerOverviewUtils.layerPath +
 | 
					                    LayerOverviewUtils.layerPath +
 | 
				
			||||||
                    sharedLayerPath.substring(sharedLayerPath.lastIndexOf("/"))
 | 
					                    sharedLayerPath.substring(sharedLayerPath.lastIndexOf("/"))
 | 
				
			||||||
                if (!forceReload && !this.shouldBeUpdated(sharedLayerPath, targetPath)) {
 | 
					                if (!forceReload && !this.shouldBeUpdated(sharedLayerPath, targetPath)) {
 | 
				
			||||||
 | 
					                    try{
 | 
				
			||||||
                        const sharedLayer = JSON.parse(readFileSync(targetPath, "utf8"))
 | 
					                        const sharedLayer = JSON.parse(readFileSync(targetPath, "utf8"))
 | 
				
			||||||
                        sharedLayers.set(sharedLayer.id, sharedLayer)
 | 
					                        sharedLayers.set(sharedLayer.id, sharedLayer)
 | 
				
			||||||
                        skippedLayers.push(sharedLayer.id)
 | 
					                        skippedLayers.push(sharedLayer.id)
 | 
				
			||||||
                        ScriptUtils.erasableLog("Loaded " + sharedLayer.id)
 | 
					                        ScriptUtils.erasableLog("Loaded " + sharedLayer.id)
 | 
				
			||||||
                        continue
 | 
					                        continue
 | 
				
			||||||
 | 
					                    }catch (e) {
 | 
				
			||||||
 | 
					                        throw "Could not parse "+targetPath+" : "+e
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@ import Panoramax_bw from "../../assets/svg/Panoramax_bw.svelte"
 | 
				
			||||||
import Link from "../../UI/Base/Link"
 | 
					import Link from "../../UI/Base/Link"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class PanoramaxImageProvider extends ImageProvider {
 | 
					export default class PanoramaxImageProvider extends ImageProvider {
 | 
				
			||||||
    public static readonly singleton = new PanoramaxImageProvider()
 | 
					    public static readonly singleton: PanoramaxImageProvider = new PanoramaxImageProvider()
 | 
				
			||||||
    private static readonly xyz = new PanoramaxXYZ()
 | 
					    private static readonly xyz = new PanoramaxXYZ()
 | 
				
			||||||
    private static defaultPanoramax = new AuthorizedPanoramax(
 | 
					    private static defaultPanoramax = new AuthorizedPanoramax(
 | 
				
			||||||
        Constants.panoramax.url,
 | 
					        Constants.panoramax.url,
 | 
				
			||||||
| 
						 | 
					@ -126,7 +126,11 @@ export default class PanoramaxImageProvider extends ImageProvider {
 | 
				
			||||||
        if (!Panoramax.isId(value)) {
 | 
					        if (!Panoramax.isId(value)) {
 | 
				
			||||||
            return undefined
 | 
					            return undefined
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return [await this.getInfoFor(value).then((r) => this.featureToImage(<any>r))]
 | 
					        return [await this.getInfo(value)]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async getInfo(hash: string): Promise<ProvidedImage> {
 | 
				
			||||||
 | 
					      return  await this.getInfoFor(hash).then((r) => this.featureToImage(<any>r))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getRelevantUrls(tags: Record<string, string>, prefixes: string[]): Store<ProvidedImage[]> {
 | 
					    getRelevantUrls(tags: Record<string, string>, prefixes: string[]): Store<ProvidedImage[]> {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ export default class OsmObjectDownloader {
 | 
				
			||||||
        readonly isUploading: Store<boolean>
 | 
					        readonly isUploading: Store<boolean>
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    private readonly backend: string
 | 
					    private readonly backend: string
 | 
				
			||||||
    private historyCache = new Map<string, UIEventSource<OsmObject[]>>()
 | 
					    private historyCache = new Map<string, Promise<OsmObject[]>>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
        backend: string = "https://api.openstreetmap.org",
 | 
					        backend: string = "https://api.openstreetmap.org",
 | 
				
			||||||
| 
						 | 
					@ -75,32 +75,19 @@ export default class OsmObjectDownloader {
 | 
				
			||||||
        return await this.applyPendingChanges(obj)
 | 
					        return await this.applyPendingChanges(obj)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public DownloadHistory(id: NodeId): UIEventSource<OsmNode[]>
 | 
					    private async _downloadHistoryUncached(id: string): Promise<OsmObject[]> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public DownloadHistory(id: WayId): UIEventSource<OsmWay[]>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public DownloadHistory(id: RelationId): UIEventSource<OsmRelation[]>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public DownloadHistory(id: OsmId): UIEventSource<OsmObject[]>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public DownloadHistory(id: string): UIEventSource<OsmObject[]> {
 | 
					 | 
				
			||||||
        if (this.historyCache.has(id)) {
 | 
					 | 
				
			||||||
            return this.historyCache.get(id)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        const splitted = id.split("/")
 | 
					        const splitted = id.split("/")
 | 
				
			||||||
        const type = splitted[0]
 | 
					        const type = splitted[0]
 | 
				
			||||||
        const idN = Number(splitted[1])
 | 
					        const idN = Number(splitted[1])
 | 
				
			||||||
        const src = new UIEventSource<OsmObject[]>([])
 | 
					        const data = await Utils.downloadJsonCached(
 | 
				
			||||||
        this.historyCache.set(id, src)
 | 
					 | 
				
			||||||
        Utils.downloadJsonCached(
 | 
					 | 
				
			||||||
            `${this.backend}api/0.6/${type}/${idN}/history`,
 | 
					            `${this.backend}api/0.6/${type}/${idN}/history`,
 | 
				
			||||||
            10 * 60 * 1000
 | 
					            10 * 60 * 1000
 | 
				
			||||||
        ).then((data) => {
 | 
					        )
 | 
				
			||||||
            const elements: any[] = data.elements
 | 
					        const elements: [] = data["elements"]
 | 
				
			||||||
        const osmObjects: OsmObject[] = []
 | 
					        const osmObjects: OsmObject[] = []
 | 
				
			||||||
        for (const element of elements) {
 | 
					        for (const element of elements) {
 | 
				
			||||||
            let osmObject: OsmObject = null
 | 
					            let osmObject: OsmObject = null
 | 
				
			||||||
                element.nodes = []
 | 
					            element["nodes"] = []
 | 
				
			||||||
            switch (type) {
 | 
					            switch (type) {
 | 
				
			||||||
                case "node":
 | 
					                case "node":
 | 
				
			||||||
                    osmObject = new OsmNode(idN, element)
 | 
					                    osmObject = new OsmNode(idN, element)
 | 
				
			||||||
| 
						 | 
					@ -115,9 +102,24 @@ export default class OsmObjectDownloader {
 | 
				
			||||||
            osmObject?.SaveExtraData(element, [])
 | 
					            osmObject?.SaveExtraData(element, [])
 | 
				
			||||||
            osmObjects.push(osmObject)
 | 
					            osmObjects.push(osmObject)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
            src.setData(osmObjects)
 | 
					        return osmObjects
 | 
				
			||||||
        })
 | 
					    }
 | 
				
			||||||
        return src
 | 
					
 | 
				
			||||||
 | 
					    public downloadHistory(id: NodeId): Promise<OsmNode[]>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public downloadHistory(id: WayId): Promise<OsmWay[]>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public downloadHistory(id: RelationId): Promise<OsmRelation[]>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public downloadHistory(id: OsmId): Promise<OsmObject[]>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async downloadHistory(id: string): Promise<OsmObject[]> {
 | 
				
			||||||
 | 
					        if (this.historyCache.has(id)) {
 | 
				
			||||||
 | 
					            return this.historyCache.get(id)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const promise = this._downloadHistoryUncached(id)
 | 
				
			||||||
 | 
					        this.historyCache.set(id, promise)
 | 
				
			||||||
 | 
					        return promise
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,10 @@ export class Overpass {
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        this._timeout = timeout ?? new ImmutableStore<number>(90)
 | 
					        this._timeout = timeout ?? new ImmutableStore<number>(90)
 | 
				
			||||||
        this._interpreterUrl = interpreterUrl
 | 
					        this._interpreterUrl = interpreterUrl
 | 
				
			||||||
        const optimized = filter.optimize()
 | 
					        if (filter === undefined && !extraScripts) {
 | 
				
			||||||
 | 
					            throw "Filter is undefined. This is probably a bug. Alternatively, pass an 'extraScript'"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const optimized = filter?.optimize()
 | 
				
			||||||
        if (optimized === true || optimized === false) {
 | 
					        if (optimized === true || optimized === false) {
 | 
				
			||||||
            throw "Invalid filter: optimizes to true of false"
 | 
					            throw "Invalid filter: optimizes to true of false"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -85,7 +88,7 @@ export class Overpass {
 | 
				
			||||||
     * new Overpass(new Tag("key","value"), [], "").buildScript("{{bbox}}") // => `[out:json][timeout:90]{{bbox}};(nwr["key"="value"];);out body;out meta;>;out skel qt;`
 | 
					     * new Overpass(new Tag("key","value"), [], "").buildScript("{{bbox}}") // => `[out:json][timeout:90]{{bbox}};(nwr["key"="value"];);out body;out meta;>;out skel qt;`
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public buildScript(bbox: string, postCall: string = "", pretty = false): string {
 | 
					    public buildScript(bbox: string, postCall: string = "", pretty = false): string {
 | 
				
			||||||
        const filters = this._filter.asOverpass()
 | 
					        const filters = this._filter?.asOverpass() ?? []
 | 
				
			||||||
        let filter = ""
 | 
					        let filter = ""
 | 
				
			||||||
        for (const filterOr of filters) {
 | 
					        for (const filterOr of filters) {
 | 
				
			||||||
            if (pretty) {
 | 
					            if (pretty) {
 | 
				
			||||||
| 
						 | 
					@ -97,12 +100,13 @@ export class Overpass {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        for (const extraScript of this._extraScripts) {
 | 
					        for (const extraScript of this._extraScripts) {
 | 
				
			||||||
            filter += "(" + extraScript + ");"
 | 
					            filter += extraScript
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return `[out:json][timeout:${this._timeout.data}]${bbox};(${filter});out body;${
 | 
					        return `[out:json][timeout:${this._timeout.data}]${bbox};(${filter});out body;${
 | 
				
			||||||
            this._includeMeta ? "out meta;" : ""
 | 
					            this._includeMeta ? "out meta;" : ""
 | 
				
			||||||
        }>;out skel qt;`
 | 
					        }>;out skel qt;`
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Constructs the actual script to execute on Overpass with geocoding
 | 
					     * Constructs the actual script to execute on Overpass with geocoding
 | 
				
			||||||
     * 'PostCall' can be used to set an extra range, see 'AsOverpassTurboLink'
 | 
					     * 'PostCall' can be used to set an extra range, see 'AsOverpassTurboLink'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -727,6 +727,7 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
					     * Parse the number and round to the nearest int
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param source
 | 
					     * @param source
 | 
				
			||||||
     * UIEventSource.asInt(new UIEventSource("123")).data // => 123
 | 
					     * UIEventSource.asInt(new UIEventSource("123")).data // => 123
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,6 +41,7 @@ export default class Constants {
 | 
				
			||||||
        "usersettings",
 | 
					        "usersettings",
 | 
				
			||||||
        "icons",
 | 
					        "icons",
 | 
				
			||||||
        "filters",
 | 
					        "filters",
 | 
				
			||||||
 | 
					        "usertouched"
 | 
				
			||||||
    ] as const
 | 
					    ] as const
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Layer IDs of layers which have special properties through built-in hooks
 | 
					     * Layer IDs of layers which have special properties through built-in hooks
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -306,7 +306,7 @@ export default class ThemeConfig implements ThemeInformation {
 | 
				
			||||||
        return { untranslated, total }
 | 
					        return { untranslated, total }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public getMatchingLayer(tags: Record<string, string>): LayerConfig | undefined {
 | 
					    public getMatchingLayer(tags: Record<string, string>, blacklistLayers?: Set<string>): LayerConfig | undefined {
 | 
				
			||||||
        if (tags === undefined) {
 | 
					        if (tags === undefined) {
 | 
				
			||||||
            return undefined
 | 
					            return undefined
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -314,6 +314,9 @@ export default class ThemeConfig implements ThemeInformation {
 | 
				
			||||||
            return this.getLayer("current_view")
 | 
					            return this.getLayer("current_view")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        for (const layer of this.layers) {
 | 
					        for (const layer of this.layers) {
 | 
				
			||||||
 | 
					            if(blacklistLayers?.has(layer.id)){
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            if (!layer.source) {
 | 
					            if (!layer.source) {
 | 
				
			||||||
                if (layer.isShown?.matchesProperties(tags)) {
 | 
					                if (layer.isShown?.matchesProperties(tags)) {
 | 
				
			||||||
                    return layer
 | 
					                    return layer
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1061,7 +1061,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Searches the appropriate layer - will first try if a special layer matches; if not, a normal layer will be used by delegating to the theme
 | 
					     * Searches the appropriate layer - will first try if a special layer matches; if not, a normal layer will be used by delegating to the theme
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public getMatchingLayer(properties: Record<string, string>) {
 | 
					    public getMatchingLayer(properties: Record<string, string>): LayerConfig | undefined {
 | 
				
			||||||
        const id = properties.id
 | 
					        const id = properties.id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (id.startsWith("summary_")) {
 | 
					        if (id.startsWith("summary_")) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,24 +3,15 @@
 | 
				
			||||||
  import type { Feature } from "geojson"
 | 
					  import type { Feature } from "geojson"
 | 
				
			||||||
  import SelectedElementView from "../BigComponents/SelectedElementView.svelte"
 | 
					  import SelectedElementView from "../BigComponents/SelectedElementView.svelte"
 | 
				
			||||||
  import SelectedElementTitle from "../BigComponents/SelectedElementTitle.svelte"
 | 
					  import SelectedElementTitle from "../BigComponents/SelectedElementTitle.svelte"
 | 
				
			||||||
  import UserRelatedState from "../../Logic/State/UserRelatedState"
 | 
					 | 
				
			||||||
  import { LastClickFeatureSource } from "../../Logic/FeatureSource/Sources/LastClickFeatureSource"
 | 
					 | 
				
			||||||
  import Loading from "./Loading.svelte"
 | 
					  import Loading from "./Loading.svelte"
 | 
				
			||||||
  import { onDestroy } from "svelte"
 | 
					  import { onDestroy } from "svelte"
 | 
				
			||||||
  import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | 
					 | 
				
			||||||
  import { GeocodingUtils } from "../../Logic/Search/GeocodingProvider"
 | 
					 | 
				
			||||||
  import ThemeViewState from "../../Models/ThemeViewState"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let state: SpecialVisualizationState
 | 
					  export let state: SpecialVisualizationState
 | 
				
			||||||
  export let selected: Feature
 | 
					  export let selected: Feature
 | 
				
			||||||
  let tags = state.featureProperties.getStore(selected.properties.id)
 | 
					  let tags = state.featureProperties.getStore(selected.properties.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let absolute = true
 | 
					  export let absolute = true
 | 
				
			||||||
  function getLayer(properties: Record<string, string>): LayerConfig {
 | 
					  let layer = state.getMatchingLayer(selected.properties)
 | 
				
			||||||
    return state.getMatchingLayer(properties)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let layer = getLayer(selected.properties)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let stillMatches = tags.map(
 | 
					  let stillMatches = tags.map(
 | 
				
			||||||
    (tags) => !layer?.source?.osmTags || layer?.source?.osmTags?.matchesProperties(tags)
 | 
					    (tags) => !layer?.source?.osmTags || layer?.source?.osmTags?.matchesProperties(tags)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -279,6 +279,11 @@
 | 
				
			||||||
      </Page>
 | 
					      </Page>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <a class="flex" href={window.location.protocol + "//" + window.location.host + "/inspector.html"}>
 | 
				
			||||||
 | 
					      <MagnifyingGlassCircle class="mr-2 h-6 w-6" />
 | 
				
			||||||
 | 
					      <Tr t={Translations.t.inspector.menu} />
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <a class="flex" href="https://github.com/pietervdvn/MapComplete/" target="_blank">
 | 
					    <a class="flex" href="https://github.com/pietervdvn/MapComplete/" target="_blank">
 | 
				
			||||||
      <Github class="h-6 w-6" />
 | 
					      <Github class="h-6 w-6" />
 | 
				
			||||||
      <Tr t={Translations.t.general.attribution.gotoSourceCode} />
 | 
					      <Tr t={Translations.t.general.attribution.gotoSourceCode} />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,23 +1,21 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import type { Feature } from "geojson"
 | 
					  import type { Feature } from "geojson"
 | 
				
			||||||
  import { Store, UIEventSource } from "../../Logic/UIEventSource"
 | 
					  import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
 | 
				
			||||||
  import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | 
					  import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | 
				
			||||||
  import type { SpecialVisualizationState } from "../SpecialVisualization"
 | 
					  import type { SpecialVisualizationState } from "../SpecialVisualization"
 | 
				
			||||||
  import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte"
 | 
					  import TagRenderingAnswer from "../Popup/TagRendering/TagRenderingAnswer.svelte"
 | 
				
			||||||
  import Translations from "../i18n/Translations"
 | 
					  import Translations from "../i18n/Translations"
 | 
				
			||||||
  import Tr from "../Base/Tr.svelte"
 | 
					  import Tr from "../Base/Tr.svelte"
 | 
				
			||||||
  import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
 | 
					 | 
				
			||||||
  import { ariaLabel } from "../../Utils/ariaLabel"
 | 
					 | 
				
			||||||
  import { CloseButton } from "flowbite-svelte"
 | 
					  import { CloseButton } from "flowbite-svelte"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let state: SpecialVisualizationState
 | 
					  export let state: SpecialVisualizationState
 | 
				
			||||||
  export let layer: LayerConfig
 | 
					  export let layer: LayerConfig
 | 
				
			||||||
  export let selectedElement: Feature
 | 
					  export let selectedElement: Feature
 | 
				
			||||||
  let tags: UIEventSource<Record<string, string>> = state.featureProperties.getStore(
 | 
					  let tags: UIEventSource<Record<string, string>> = state?.featureProperties?.getStore(
 | 
				
			||||||
    selectedElement.properties.id
 | 
					    selectedElement.properties.id
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
  $: {
 | 
					  $: {
 | 
				
			||||||
    tags = state.featureProperties.getStore(selectedElement.properties.id)
 | 
					    tags = state?.featureProperties?.getStore(selectedElement.properties.id)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let isTesting = state.featureSwitchIsTesting
 | 
					  let isTesting = state.featureSwitchIsTesting
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										46
									
								
								src/UI/History/AggregateImages.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/UI/History/AggregateImages.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					  import { Store, UIEventSource } from "../../Logic/UIEventSource"
 | 
				
			||||||
 | 
					  import { HistoryUtils } from "./HistoryUtils"
 | 
				
			||||||
 | 
					  import type { Feature } from "geojson"
 | 
				
			||||||
 | 
					  import OsmObjectDownloader from "../../Logic/Osm/OsmObjectDownloader"
 | 
				
			||||||
 | 
					  import { OsmObject } from "../../Logic/Osm/OsmObject"
 | 
				
			||||||
 | 
					  import Loading from "../Base/Loading.svelte"
 | 
				
			||||||
 | 
					  import AttributedPanoramaxImage from "./AttributedPanoramaxImage.svelte"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export let onlyShowUsername: string[]
 | 
				
			||||||
 | 
					  export let features: Feature[]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const downloader = new OsmObjectDownloader()
 | 
				
			||||||
 | 
					  let allHistories: UIEventSource<OsmObject[][]> = UIEventSource.FromPromise(
 | 
				
			||||||
 | 
					    Promise.all(features.map(f => downloader.downloadHistory(f.properties.id)))
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					  let imageKeys = new Set(...["panoramax", "image:streetsign", "image:menu"].map(k => {
 | 
				
			||||||
 | 
					    const result: string[] = [k]
 | 
				
			||||||
 | 
					    for (let i = 0; i < 10; i++) {
 | 
				
			||||||
 | 
					      result.push(k + ":" + i)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return result
 | 
				
			||||||
 | 
					  }))
 | 
				
			||||||
 | 
					  let usernamesSet = new Set(onlyShowUsername)
 | 
				
			||||||
 | 
					  let allDiffs: Store<{
 | 
				
			||||||
 | 
					    key: string;
 | 
				
			||||||
 | 
					    value?: string;
 | 
				
			||||||
 | 
					    oldValue?: string
 | 
				
			||||||
 | 
					  }[]> = allHistories.mapD(histories => HistoryUtils.fullHistoryDiff(histories, usernamesSet))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let addedImages = allDiffs.mapD(diffs => [].concat(...diffs.filter(({ key }) => imageKeys.has(key))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					{#if $allDiffs === undefined}
 | 
				
			||||||
 | 
					  <Loading />
 | 
				
			||||||
 | 
					{:else if $addedImages.length === 0}
 | 
				
			||||||
 | 
					  No images added by this contributor
 | 
				
			||||||
 | 
					{:else}
 | 
				
			||||||
 | 
					  <div class="flex">
 | 
				
			||||||
 | 
					    {#each $addedImages as imgDiff}
 | 
				
			||||||
 | 
					      <div class="w-48 h-48">
 | 
				
			||||||
 | 
					        <AttributedPanoramaxImage hash={imgDiff.value} />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    {/each}
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
							
								
								
									
										105
									
								
								src/UI/History/AggregateView.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/UI/History/AggregateView.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,105 @@
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					  import type { Feature } from "geojson"
 | 
				
			||||||
 | 
					  import { Store, UIEventSource } from "../../Logic/UIEventSource"
 | 
				
			||||||
 | 
					  import OsmObjectDownloader from "../../Logic/Osm/OsmObjectDownloader"
 | 
				
			||||||
 | 
					  import { OsmObject } from "../../Logic/Osm/OsmObject"
 | 
				
			||||||
 | 
					  import Loading from "../Base/Loading.svelte"
 | 
				
			||||||
 | 
					  import { HistoryUtils } from "./HistoryUtils"
 | 
				
			||||||
 | 
					  import * as shared_questions from "../../assets/generated/layers/questions.json"
 | 
				
			||||||
 | 
					  import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
 | 
				
			||||||
 | 
					  import Tr from "../Base/Tr.svelte"
 | 
				
			||||||
 | 
					  import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
 | 
				
			||||||
 | 
					  import Translations from "../i18n/Translations"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export let onlyShowUsername: string[]
 | 
				
			||||||
 | 
					  export let features: Feature[]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let usernames = new Set(onlyShowUsername)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const downloader = new OsmObjectDownloader()
 | 
				
			||||||
 | 
					  let allHistories: UIEventSource<OsmObject[][]> = UIEventSource.FromPromise(
 | 
				
			||||||
 | 
					    Promise.all(features.map(f => downloader.downloadHistory(f.properties.id)))
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					  let allDiffs: Store<{
 | 
				
			||||||
 | 
					    key: string;
 | 
				
			||||||
 | 
					    value?: string;
 | 
				
			||||||
 | 
					    oldValue?: string
 | 
				
			||||||
 | 
					  }[]> = allHistories.mapD(histories => HistoryUtils.fullHistoryDiff(histories, usernames))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const trs = shared_questions.tagRenderings.map(tr => new TagRenderingConfig(tr))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function detectQuestion(key: string): TagRenderingConfig {
 | 
				
			||||||
 | 
					    return trs.find(tr => tr.freeform?.key === key)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const mergedCount: Store<{
 | 
				
			||||||
 | 
					    key: string;
 | 
				
			||||||
 | 
					    tr: TagRenderingConfig;
 | 
				
			||||||
 | 
					    count: number;
 | 
				
			||||||
 | 
					    values: { value: string; count: number }[]
 | 
				
			||||||
 | 
					  }[]> = allDiffs.mapD(allDiffs => {
 | 
				
			||||||
 | 
					    const keyCounts = new Map<string, Map<string, number>>()
 | 
				
			||||||
 | 
					    for (const diff of allDiffs) {
 | 
				
			||||||
 | 
					      const k = diff.key
 | 
				
			||||||
 | 
					      if (!keyCounts.has(k)) {
 | 
				
			||||||
 | 
					        keyCounts.set(k, new Map<string, number>())
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const valueCounts = keyCounts.get(k)
 | 
				
			||||||
 | 
					      const v = diff.value ?? ""
 | 
				
			||||||
 | 
					      valueCounts.set(v, 1 + (valueCounts.get(v) ?? 0))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const perKey: {
 | 
				
			||||||
 | 
					      key: string, tr: TagRenderingConfig, count: number, values:
 | 
				
			||||||
 | 
					        { value: string, count: number }[]
 | 
				
			||||||
 | 
					    }[] = []
 | 
				
			||||||
 | 
					    keyCounts.forEach((values, key) => {
 | 
				
			||||||
 | 
					      const keyTotal: { value: string, count: number }[] = []
 | 
				
			||||||
 | 
					      values.forEach((count, value) => {
 | 
				
			||||||
 | 
					        keyTotal.push({ value, count })
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      let countForKey = 0
 | 
				
			||||||
 | 
					      for (const { count } of keyTotal) {
 | 
				
			||||||
 | 
					        countForKey += count
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      keyTotal.sort((a, b) => b.count - a.count)
 | 
				
			||||||
 | 
					      const tr = detectQuestion(key)
 | 
				
			||||||
 | 
					      perKey.push({ count: countForKey, tr, key, values: keyTotal })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    perKey.sort((a, b) => b.count - a.count)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return perKey
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const t = Translations.t.inspector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{#if allHistories === undefined}
 | 
				
			||||||
 | 
					  <Loading />
 | 
				
			||||||
 | 
					{:else if $allDiffs !== undefined}
 | 
				
			||||||
 | 
					  {#each $mergedCount as diff}
 | 
				
			||||||
 | 
					    <h3>
 | 
				
			||||||
 | 
					      {#if diff.tr}
 | 
				
			||||||
 | 
					        <Tr t={diff.tr.question} />
 | 
				
			||||||
 | 
					      {:else}
 | 
				
			||||||
 | 
					        {diff.key}
 | 
				
			||||||
 | 
					      {/if}
 | 
				
			||||||
 | 
					    </h3>
 | 
				
			||||||
 | 
					    <AccordionSingle>
 | 
				
			||||||
 | 
					      <span slot="header">
 | 
				
			||||||
 | 
					<Tr t={t.answeredCountTimes.Subs(diff)} />
 | 
				
			||||||
 | 
					      </span>
 | 
				
			||||||
 | 
					      <ul>
 | 
				
			||||||
 | 
					        {#each diff.values as value}
 | 
				
			||||||
 | 
					          <li>
 | 
				
			||||||
 | 
					            <b>{value.value}</b>
 | 
				
			||||||
 | 
					            {#if value.count > 1}
 | 
				
			||||||
 | 
					              - {value.count}
 | 
				
			||||||
 | 
					            {/if}
 | 
				
			||||||
 | 
					          </li>
 | 
				
			||||||
 | 
					        {/each}
 | 
				
			||||||
 | 
					      </ul>
 | 
				
			||||||
 | 
					    </AccordionSingle>
 | 
				
			||||||
 | 
					  {/each}
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
							
								
								
									
										13
									
								
								src/UI/History/AttributedPanoramaxImage.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/UI/History/AttributedPanoramaxImage.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,13 @@
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					  import AttributedImage from "../Image/AttributedImage.svelte"
 | 
				
			||||||
 | 
					  import PanoramaxImageProvider from "../../Logic/ImageProviders/Panoramax"
 | 
				
			||||||
 | 
					  import { UIEventSource } from "../../Logic/UIEventSource"
 | 
				
			||||||
 | 
					  import type { ProvidedImage } from "../../Logic/ImageProviders/ImageProvider"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export let hash: string
 | 
				
			||||||
 | 
					  let image: UIEventSource<ProvidedImage> = UIEventSource.FromPromise(PanoramaxImageProvider.singleton.getInfo(hash))
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{#if $image !== undefined}
 | 
				
			||||||
 | 
					  <AttributedImage image={$image}></AttributedImage>
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
							
								
								
									
										106
									
								
								src/UI/History/History.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/UI/History/History.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,106 @@
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Shows a history of the object which focuses on changes made by a certain username
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  import type { OsmId } from "../../Models/OsmFeature"
 | 
				
			||||||
 | 
					  import OsmObjectDownloader from "../../Logic/Osm/OsmObjectDownloader"
 | 
				
			||||||
 | 
					  import { UIEventSource } from "../../Logic/UIEventSource"
 | 
				
			||||||
 | 
					  import Loading from "../Base/Loading.svelte"
 | 
				
			||||||
 | 
					  import { HistoryUtils } from "./HistoryUtils"
 | 
				
			||||||
 | 
					  import ToSvelte from "../Base/ToSvelte.svelte"
 | 
				
			||||||
 | 
					  import Tr from "../Base/Tr.svelte"
 | 
				
			||||||
 | 
					  import Translations from "../i18n/Translations"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export let onlyShowChangesBy: string[]
 | 
				
			||||||
 | 
					  export let id: OsmId
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let usernames = new Set(onlyShowChangesBy)
 | 
				
			||||||
 | 
					  let fullHistory = UIEventSource.FromPromise(new OsmObjectDownloader().downloadHistory(id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let partOfLayer = fullHistory.mapD(history => history.map(step => ({
 | 
				
			||||||
 | 
					    step,
 | 
				
			||||||
 | 
					    layer: HistoryUtils.determineLayer(step.tags)
 | 
				
			||||||
 | 
					  })))
 | 
				
			||||||
 | 
					  let filteredHistory = partOfLayer.mapD(history =>
 | 
				
			||||||
 | 
					    history.filter(({ step }) => {
 | 
				
			||||||
 | 
					      if (usernames.size == 0) {
 | 
				
			||||||
 | 
					        return true
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      console.log("Checking if ", step.tags["_last_edit:contributor"],"is contained in", onlyShowChangesBy)
 | 
				
			||||||
 | 
					      return usernames.has(step.tags["_last_edit:contributor"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }).map(({ step, layer }) => {
 | 
				
			||||||
 | 
					      const diff = HistoryUtils.tagHistoryDiff(step, fullHistory.data)
 | 
				
			||||||
 | 
					      return { step, layer, diff }
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let lastStep = filteredHistory.mapD(history => history.at(-1))
 | 
				
			||||||
 | 
					  let allGeometry = filteredHistory.mapD(all => !all.some(x => x.diff.length > 0))
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * These layers are only shown if there are tag changes as well
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  const ignoreLayersIfNoChanges: ReadonlySet<string> = new Set(["walls_and_buildings"])
 | 
				
			||||||
 | 
					  const t = Translations.t.inspector.previousContributors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{#if !$allGeometry || !ignoreLayersIfNoChanges.has($lastStep?.layer?.id)}
 | 
				
			||||||
 | 
					  {#if $lastStep?.layer}
 | 
				
			||||||
 | 
					    <a href={"https://openstreetmap.org/" + $lastStep.step.tags.id} target="_blank">
 | 
				
			||||||
 | 
					      <h3 class="flex items-center gap-x-2">
 | 
				
			||||||
 | 
					        <div class="w-8 h-8 shrink-0 inline-block">
 | 
				
			||||||
 | 
					          <ToSvelte construct={$lastStep.layer?.defaultIcon($lastStep.step.tags)} />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <Tr t={$lastStep.layer?.title?.GetRenderValue($lastStep.step.tags)?.Subs($lastStep.step.tags)} />
 | 
				
			||||||
 | 
					      </h3>
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					  {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  {#if !$filteredHistory}
 | 
				
			||||||
 | 
					    <Loading>Loading history...</Loading>
 | 
				
			||||||
 | 
					  {:else if $filteredHistory.length === 0}
 | 
				
			||||||
 | 
					    <Tr t={t.onlyGeometry} />
 | 
				
			||||||
 | 
					  {:else}
 | 
				
			||||||
 | 
					    <table class="w-full m-1">
 | 
				
			||||||
 | 
					      {#each $filteredHistory as { step, layer }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        {#if step.version === 1}
 | 
				
			||||||
 | 
					          <tr>
 | 
				
			||||||
 | 
					            <td colspan="3">
 | 
				
			||||||
 | 
					              <h3>
 | 
				
			||||||
 | 
					                <Tr t={t.createdBy.Subs({contributor: step.tags["_last_edit:contributor"]})} />
 | 
				
			||||||
 | 
					              </h3>
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					          </tr>
 | 
				
			||||||
 | 
					        {/if}
 | 
				
			||||||
 | 
					        {#if HistoryUtils.tagHistoryDiff(step, $fullHistory).length === 0}
 | 
				
			||||||
 | 
					          <tr>
 | 
				
			||||||
 | 
					            <td class="font-bold justify-center flex w-full" colspan="3">
 | 
				
			||||||
 | 
					              <Tr t={t.onlyGeometry} />
 | 
				
			||||||
 | 
					            </td>
 | 
				
			||||||
 | 
					          </tr>
 | 
				
			||||||
 | 
					        {:else}
 | 
				
			||||||
 | 
					          {#each HistoryUtils.tagHistoryDiff(step, $fullHistory) as diff}
 | 
				
			||||||
 | 
					            <tr>
 | 
				
			||||||
 | 
					              <td><a href={"https://osm.org/changeset/"+step.tags["_last_edit:changeset"]}
 | 
				
			||||||
 | 
					                     target="_blank">{step.version}</a></td>
 | 
				
			||||||
 | 
					              <td>{layer?.id ?? "Unknown layer"}</td>
 | 
				
			||||||
 | 
					              {#if diff.oldValue === undefined}
 | 
				
			||||||
 | 
					                <td>{diff.key}</td>
 | 
				
			||||||
 | 
					                <td>{diff.value}</td>
 | 
				
			||||||
 | 
					              {:else if diff.value === undefined }
 | 
				
			||||||
 | 
					                <td>{diff.key}</td>
 | 
				
			||||||
 | 
					                <td class="line-through"> {diff.value}</td>
 | 
				
			||||||
 | 
					              {:else}
 | 
				
			||||||
 | 
					                <td>{diff.key}</td>
 | 
				
			||||||
 | 
					                <td><span class="line-through"> {diff.oldValue}</span> → {diff.value}</td>
 | 
				
			||||||
 | 
					              {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            </tr>
 | 
				
			||||||
 | 
					          {/each}
 | 
				
			||||||
 | 
					        {/if}
 | 
				
			||||||
 | 
					      {/each}
 | 
				
			||||||
 | 
					    </table>
 | 
				
			||||||
 | 
					  {/if}
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/UI/History/HistoryUtils.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/UI/History/HistoryUtils.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -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(<any> all_layers, true)
 | 
				
			||||||
 | 
					    private static ignoredLayers = new Set<string>(["fixme"])
 | 
				
			||||||
 | 
					    public static determineLayer(properties: Record<string, string>){
 | 
				
			||||||
 | 
					        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<string>){
 | 
				
			||||||
 | 
					        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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										107
									
								
								src/UI/History/PreviouslySpiedUsers.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/UI/History/PreviouslySpiedUsers.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,107 @@
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  import { UIEventSource } from "../../Logic/UIEventSource"
 | 
				
			||||||
 | 
					  import { OsmConnection } from "../../Logic/Osm/OsmConnection"
 | 
				
			||||||
 | 
					  import LoginToggle from "../Base/LoginToggle.svelte"
 | 
				
			||||||
 | 
					  import { createEventDispatcher } from "svelte"
 | 
				
			||||||
 | 
					  import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
 | 
				
			||||||
 | 
					  import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
 | 
				
			||||||
 | 
					  import Dropdown from "../Base/Dropdown.svelte"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export let osmConnection: OsmConnection
 | 
				
			||||||
 | 
					  export let inspectedContributors: UIEventSource<{
 | 
				
			||||||
 | 
					    name: string,
 | 
				
			||||||
 | 
					    visitedTime: string,
 | 
				
			||||||
 | 
					    label: string
 | 
				
			||||||
 | 
					  }[]>
 | 
				
			||||||
 | 
					  let dispatch = createEventDispatcher<{ selectUser: string }>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let labels = UIEventSource.asObject<string[]>(osmConnection.getPreference("previously-spied-labels"), [])
 | 
				
			||||||
 | 
					  let labelField = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function remove(user: string) {
 | 
				
			||||||
 | 
					    inspectedContributors.set(inspectedContributors.data.filter(entry => entry.name !== user))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function addLabel() {
 | 
				
			||||||
 | 
					    if (labels.data.indexOf(labelField) >= 0) {
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    labels.data.push(labelField)
 | 
				
			||||||
 | 
					    labels.ping()
 | 
				
			||||||
 | 
					    labelField = ""
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function sort(key: string) {
 | 
				
			||||||
 | 
					    console.log("Sorting on", key)
 | 
				
			||||||
 | 
					    inspectedContributors.data.sort((a, b) => (a[key] ?? "").localeCompare(b[key] ?? ""))
 | 
				
			||||||
 | 
					    inspectedContributors.ping()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<LoginToggle ignoreLoading state={{osmConnection}}>
 | 
				
			||||||
 | 
					  <table class="w-full">
 | 
				
			||||||
 | 
					    <tr>
 | 
				
			||||||
 | 
					      <td>
 | 
				
			||||||
 | 
					        <button class="as-link cursor-pointer" on:click={() => sort("name")}>
 | 
				
			||||||
 | 
					          Contributor
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      </td>
 | 
				
			||||||
 | 
					      <td>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <button class="as-link cursor-pointer" on:click={() => sort("visitedTime")}>
 | 
				
			||||||
 | 
					          Visited time
 | 
				
			||||||
 | 
					        </button>
 | 
				
			||||||
 | 
					      </td>
 | 
				
			||||||
 | 
					      <td>
 | 
				
			||||||
 | 
					        <button class="as-link cursor-pointer" on:click={() => sort("label")}>Label</button>
 | 
				
			||||||
 | 
					      </td>
 | 
				
			||||||
 | 
					      <td>Remove</td>
 | 
				
			||||||
 | 
					    </tr>
 | 
				
			||||||
 | 
					    {#each $inspectedContributors as c}
 | 
				
			||||||
 | 
					      <tr>
 | 
				
			||||||
 | 
					        <td>
 | 
				
			||||||
 | 
					          <button class="as-link" on:click={() => dispatch("selectUser", c.name)}>{c.name}</button>
 | 
				
			||||||
 | 
					        </td>
 | 
				
			||||||
 | 
					        <td>
 | 
				
			||||||
 | 
					          {c.visitedTime}
 | 
				
			||||||
 | 
					        </td>
 | 
				
			||||||
 | 
					        <td>
 | 
				
			||||||
 | 
					          <select bind:value={c.label} on:change={() => inspectedContributors.ping()}>
 | 
				
			||||||
 | 
					            <option value={undefined}><i>No label</i></option>
 | 
				
			||||||
 | 
					            {#each $labels as l}
 | 
				
			||||||
 | 
					              <option value={l}>{l}</option>
 | 
				
			||||||
 | 
					            {/each}
 | 
				
			||||||
 | 
					          </select>
 | 
				
			||||||
 | 
					        </td>
 | 
				
			||||||
 | 
					        <td>
 | 
				
			||||||
 | 
					          <XCircleIcon class="w-6 h-6" on:click={() => remove(c.name)} />
 | 
				
			||||||
 | 
					        </td>
 | 
				
			||||||
 | 
					      </tr>
 | 
				
			||||||
 | 
					    {/each}
 | 
				
			||||||
 | 
					  </table>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <AccordionSingle>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div slot="header">Labels</div>
 | 
				
			||||||
 | 
					    {#if $labels.length === 0}
 | 
				
			||||||
 | 
					      No labels
 | 
				
			||||||
 | 
					    {:else}
 | 
				
			||||||
 | 
					      {#each $labels as label}
 | 
				
			||||||
 | 
					        <div class="mx-2">{label}
 | 
				
			||||||
 | 
					          <button class:disabled={!$inspectedContributors.some(c => c.label === label)} on:click={() => {dispatch("selectUser",
 | 
				
			||||||
 | 
					        inspectedContributors.data.filter(c =>c.label === label).map(c => c .name).join(";")
 | 
				
			||||||
 | 
					        )}}>See all changes for these users
 | 
				
			||||||
 | 
					          </button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      {/each}
 | 
				
			||||||
 | 
					    {/if}
 | 
				
			||||||
 | 
					    <div class="interactive flex m-2 items-center gap-x-2 rounded-lg p-2">
 | 
				
			||||||
 | 
					      <div class="shrink-0">Create a new label</div>
 | 
				
			||||||
 | 
					      <input bind:value={labelField} type="text" />
 | 
				
			||||||
 | 
					      <button on:click={() => addLabel()} class:disabled={!(labelField?.length > 0) } class="disabled shrink-0">Add
 | 
				
			||||||
 | 
					        label
 | 
				
			||||||
 | 
					      </button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </AccordionSingle>
 | 
				
			||||||
 | 
					</LoginToggle>
 | 
				
			||||||
| 
						 | 
					@ -28,22 +28,24 @@
 | 
				
			||||||
  export let imgClass: string = undefined
 | 
					  export let imgClass: string = undefined
 | 
				
			||||||
  export let state: SpecialVisualizationState = undefined
 | 
					  export let state: SpecialVisualizationState = undefined
 | 
				
			||||||
  export let attributionFormat: "minimal" | "medium" | "large" = "medium"
 | 
					  export let attributionFormat: "minimal" | "medium" | "large" = "medium"
 | 
				
			||||||
  export let previewedImage: UIEventSource<ProvidedImage>
 | 
					  export let previewedImage: UIEventSource<ProvidedImage> = undefined
 | 
				
			||||||
  export let canZoom = previewedImage !== undefined
 | 
					  export let canZoom = previewedImage !== undefined
 | 
				
			||||||
  let loaded = false
 | 
					  let loaded = false
 | 
				
			||||||
  let showBigPreview = new UIEventSource(false)
 | 
					  let showBigPreview = new UIEventSource(false)
 | 
				
			||||||
  onDestroy(
 | 
					  onDestroy(
 | 
				
			||||||
    showBigPreview.addCallbackAndRun((shown) => {
 | 
					    showBigPreview.addCallbackAndRun((shown) => {
 | 
				
			||||||
      if (!shown) {
 | 
					      if (!shown) {
 | 
				
			||||||
        previewedImage.set(undefined)
 | 
					        previewedImage?.set(undefined)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					  if(previewedImage){
 | 
				
			||||||
  onDestroy(
 | 
					  onDestroy(
 | 
				
			||||||
    previewedImage.addCallbackAndRun((previewedImage) => {
 | 
					    previewedImage.addCallbackAndRun((previewedImage) => {
 | 
				
			||||||
      showBigPreview.set(previewedImage?.id === image.id)
 | 
					      showBigPreview.set(previewedImage?.id === image.id)
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function highlight(entered: boolean = true) {
 | 
					  function highlight(entered: boolean = true) {
 | 
				
			||||||
    if (!entered) {
 | 
					    if (!entered) {
 | 
				
			||||||
| 
						 | 
					@ -82,7 +84,7 @@
 | 
				
			||||||
      class="normal-background"
 | 
					      class="normal-background"
 | 
				
			||||||
      on:click={() => {
 | 
					      on:click={() => {
 | 
				
			||||||
        console.log("Closing")
 | 
					        console.log("Closing")
 | 
				
			||||||
        previewedImage.set(undefined)
 | 
					        previewedImage?.set(undefined)
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
| 
						 | 
					@ -124,7 +126,7 @@
 | 
				
			||||||
      {#if canZoom && loaded}
 | 
					      {#if canZoom && loaded}
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          class="bg-black-transparent absolute right-0 top-0 rounded-bl-full"
 | 
					          class="bg-black-transparent absolute right-0 top-0 rounded-bl-full"
 | 
				
			||||||
          on:click={() => previewedImage.set(image)}
 | 
					          on:click={() => previewedImage?.set(image)}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <MagnifyingGlassPlusIcon class="h-8 w-8 cursor-zoom-in pl-3 pb-3" color="white" />
 | 
					          <MagnifyingGlassPlusIcon class="h-8 w-8 cursor-zoom-in pl-3 pb-3" color="white" />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										227
									
								
								src/UI/InspectorGUI.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								src/UI/InspectorGUI.svelte
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,227 @@
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					  import { UIEventSource } from "../Logic/UIEventSource"
 | 
				
			||||||
 | 
					  import { QueryParameters } from "../Logic/Web/QueryParameters"
 | 
				
			||||||
 | 
					  import ValidatedInput from "./InputElement/ValidatedInput.svelte"
 | 
				
			||||||
 | 
					  import { Overpass } from "../Logic/Osm/Overpass"
 | 
				
			||||||
 | 
					  import Constants from "../Models/Constants"
 | 
				
			||||||
 | 
					  import MaplibreMap from "./Map/MaplibreMap.svelte"
 | 
				
			||||||
 | 
					  import { MapLibreAdaptor } from "./Map/MapLibreAdaptor"
 | 
				
			||||||
 | 
					  import { Map as MlMap } from "maplibre-gl"
 | 
				
			||||||
 | 
					  import ShowDataLayer from "./Map/ShowDataLayer"
 | 
				
			||||||
 | 
					  import * as inspector_theme from "../assets/generated/themes/inspector.json"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"
 | 
				
			||||||
 | 
					  import type { Feature } from "geojson"
 | 
				
			||||||
 | 
					  import Loading from "./Base/Loading.svelte"
 | 
				
			||||||
 | 
					  import { linear } from "svelte/easing"
 | 
				
			||||||
 | 
					  import { Drawer } from "flowbite-svelte"
 | 
				
			||||||
 | 
					  import ThemeConfig from "../Models/ThemeConfig/ThemeConfig"
 | 
				
			||||||
 | 
					  import History from "./History/History.svelte"
 | 
				
			||||||
 | 
					  import TitledPanel from "./Base/TitledPanel.svelte"
 | 
				
			||||||
 | 
					  import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
 | 
				
			||||||
 | 
					  import { Utils } from "../Utils"
 | 
				
			||||||
 | 
					  import AggregateView from "./History/AggregateView.svelte"
 | 
				
			||||||
 | 
					  import { HistoryUtils } from "./History/HistoryUtils"
 | 
				
			||||||
 | 
					  import AggregateImages from "./History/AggregateImages.svelte"
 | 
				
			||||||
 | 
					  import Page from "./Base/Page.svelte"
 | 
				
			||||||
 | 
					  import PreviouslySpiedUsers from "./History/PreviouslySpiedUsers.svelte"
 | 
				
			||||||
 | 
					  import { OsmConnection } from "../Logic/Osm/OsmConnection"
 | 
				
			||||||
 | 
					  import MagnifyingGlassCircle from "@babeard/svelte-heroicons/outline/MagnifyingGlassCircle"
 | 
				
			||||||
 | 
					  import Translations from "./i18n/Translations"
 | 
				
			||||||
 | 
					  import Tr from "./Base/Tr.svelte"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let username = QueryParameters.GetQueryParameter("user", undefined, "Inspect this user")
 | 
				
			||||||
 | 
					  let step = new UIEventSource<"waiting" | "loading" | "done">("waiting")
 | 
				
			||||||
 | 
					  let map = new UIEventSource<MlMap>(undefined)
 | 
				
			||||||
 | 
					  let zoom = UIEventSource.asFloat(QueryParameters.GetQueryParameter("z", "0"))
 | 
				
			||||||
 | 
					  let lat = UIEventSource.asFloat(QueryParameters.GetQueryParameter("lat", "0"))
 | 
				
			||||||
 | 
					  let lon = UIEventSource.asFloat(QueryParameters.GetQueryParameter("lon", "0"))
 | 
				
			||||||
 | 
					  let theme = new ThemeConfig(<any>inspector_theme, true)
 | 
				
			||||||
 | 
					  let layer = theme.layers.find(l => l.id === "usertouched")
 | 
				
			||||||
 | 
					  // Is this a dirty hack? Yes it is!
 | 
				
			||||||
 | 
					  theme.getMatchingLayer = () => {
 | 
				
			||||||
 | 
					    return layer
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  let loadingData = false
 | 
				
			||||||
 | 
					  let selectedElement = new UIEventSource<Feature>(undefined)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let maplibremap: MapLibreAdaptor = new MapLibreAdaptor(map, {
 | 
				
			||||||
 | 
					    zoom,
 | 
				
			||||||
 | 
					    location: new UIEventSource<{ lon: number; lat: number }>({ lat: lat.data, lon: lon.data })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  maplibremap.location.stabilized(500).addCallbackAndRunD(l => {
 | 
				
			||||||
 | 
					    lat.set(l.lat)
 | 
				
			||||||
 | 
					    lon.set(l.lon)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let allLayers = HistoryUtils.personalTheme.layers
 | 
				
			||||||
 | 
					    let layersNoFixme = allLayers.filter(l => l.id !== "fixme")
 | 
				
			||||||
 | 
					let fixme = allLayers.find(l => l.id === "fixme")
 | 
				
			||||||
 | 
					  let featuresStore = new UIEventSource<Feature[]>([])
 | 
				
			||||||
 | 
					  let features = new StaticFeatureSource(featuresStore)
 | 
				
			||||||
 | 
					  ShowDataLayer.showMultipleLayers(map, features, [...layersNoFixme, fixme] , {
 | 
				
			||||||
 | 
					    zoomToFeatures: true,
 | 
				
			||||||
 | 
					    onClick: (f: Feature) => {
 | 
				
			||||||
 | 
					      selectedElement.set(undefined)
 | 
				
			||||||
 | 
					      Utils.waitFor(200).then(() => {
 | 
				
			||||||
 | 
					        selectedElement.set(f)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let osmConnection = new OsmConnection()
 | 
				
			||||||
 | 
					  let inspectedContributors: UIEventSource<{
 | 
				
			||||||
 | 
					    name: string,
 | 
				
			||||||
 | 
					    visitedTime: string,
 | 
				
			||||||
 | 
					    label: string
 | 
				
			||||||
 | 
					  }[]> = UIEventSource.asObject(
 | 
				
			||||||
 | 
					    osmConnection.getPreference("spied-upon-users"), [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function load() {
 | 
				
			||||||
 | 
					    const user = username.data
 | 
				
			||||||
 | 
					    if(user.indexOf(";")<0){
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const inspectedData = inspectedContributors.data
 | 
				
			||||||
 | 
					      const previousEntry = inspectedData.find(e => e.name === user)
 | 
				
			||||||
 | 
					      if (previousEntry) {
 | 
				
			||||||
 | 
					        previousEntry.visitedTime = new Date().toISOString()
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        inspectedData.push({
 | 
				
			||||||
 | 
					          label: undefined,
 | 
				
			||||||
 | 
					          visitedTime: new Date().toISOString(),
 | 
				
			||||||
 | 
					          name: user
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      inspectedContributors.ping()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    step.setData("loading")
 | 
				
			||||||
 | 
					    featuresStore.set([])
 | 
				
			||||||
 | 
					    const overpass = new Overpass(undefined, user.split(";").map(user => "nw(user_touched:\"" + user + "\");"), Constants.defaultOverpassUrls[0])
 | 
				
			||||||
 | 
					    if (!maplibremap.bounds.data) {
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    loadingData = true
 | 
				
			||||||
 | 
					    const [data, date] = await overpass.queryGeoJson(maplibremap.bounds.data)
 | 
				
			||||||
 | 
					    console.log("Overpass result:", data)
 | 
				
			||||||
 | 
					    loadingData = false
 | 
				
			||||||
 | 
					    console.log(data, date)
 | 
				
			||||||
 | 
					    featuresStore.set(data.features)
 | 
				
			||||||
 | 
					    console.log("Loaded", data.features.length)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  map.addCallbackAndRunD(() => {
 | 
				
			||||||
 | 
					    // when the map is loaded: attempt to load the user given via Queryparams
 | 
				
			||||||
 | 
					    if (username.data) {
 | 
				
			||||||
 | 
					      console.log("Current username is", username.data)
 | 
				
			||||||
 | 
					      load()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let mode: "map" | "table" | "aggregate" | "images" = "map"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let showPreviouslyVisited = new UIEventSource(true)
 | 
				
			||||||
 | 
					const t = Translations.t.inspector
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="flex flex-col w-full h-full">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <div class="flex gap-x-2 items-center low-interaction p-2">
 | 
				
			||||||
 | 
					    <MagnifyingGlassCircle class="w-12 h-12"/>
 | 
				
			||||||
 | 
					    <h1 class="flex-shrink-0 m-0 mx-2">
 | 
				
			||||||
 | 
					      <Tr t={t.title}/>
 | 
				
			||||||
 | 
					    </h1>
 | 
				
			||||||
 | 
					    <ValidatedInput type="string" value={username} on:submit={() => load()} />
 | 
				
			||||||
 | 
					    {#if loadingData}
 | 
				
			||||||
 | 
					      <Loading />
 | 
				
			||||||
 | 
					    {:else}
 | 
				
			||||||
 | 
					      <button class="primary" on:click={() => load()}>
 | 
				
			||||||
 | 
					        <Tr t={t.load}/>
 | 
				
			||||||
 | 
					      </button>
 | 
				
			||||||
 | 
					    {/if}
 | 
				
			||||||
 | 
					    <button on:click={() => showPreviouslyVisited.setData(true)}>
 | 
				
			||||||
 | 
					     <Tr t={t.earlierInspected}/>
 | 
				
			||||||
 | 
					    </button>
 | 
				
			||||||
 | 
					    <a href="./index.html" class="button">
 | 
				
			||||||
 | 
					      <Tr t={t.backToIndex}/>
 | 
				
			||||||
 | 
					    </a>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <div class="flex">
 | 
				
			||||||
 | 
					    <button class:primary={mode === "map"} on:click={() => mode = "map"}>
 | 
				
			||||||
 | 
					      <Tr t={t.mapView}/>
 | 
				
			||||||
 | 
					    </button>
 | 
				
			||||||
 | 
					    <button class:primary={mode === "table"} on:click={() => mode = "table"}>
 | 
				
			||||||
 | 
					      <Tr t={t.tableView}/>
 | 
				
			||||||
 | 
					    </button>
 | 
				
			||||||
 | 
					    <button class:primary={mode === "aggregate"} on:click={() => mode = "aggregate"}>
 | 
				
			||||||
 | 
					      <Tr t={t.aggregateView}/>
 | 
				
			||||||
 | 
					    </button>
 | 
				
			||||||
 | 
					    <button class:primary={mode === "images"} on:click={() => mode = "images"}>
 | 
				
			||||||
 | 
					      <Tr t={t.images}/>
 | 
				
			||||||
 | 
					    </button>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  {#if mode === "map"}
 | 
				
			||||||
 | 
					    {#if $selectedElement !== undefined}
 | 
				
			||||||
 | 
					      <!-- right modal with the selected element view -->
 | 
				
			||||||
 | 
					      <Drawer
 | 
				
			||||||
 | 
					        placement="right"
 | 
				
			||||||
 | 
					        transitionType="fly"
 | 
				
			||||||
 | 
					        activateClickOutside={false}
 | 
				
			||||||
 | 
					        backdrop={false}
 | 
				
			||||||
 | 
					        id="drawer-right"
 | 
				
			||||||
 | 
					        width="w-full md:w-6/12 lg:w-5/12 xl:w-4/12"
 | 
				
			||||||
 | 
					        rightOffset="inset-y-0 right-0"
 | 
				
			||||||
 | 
					        transitionParams={{
 | 
				
			||||||
 | 
					        x: 640,
 | 
				
			||||||
 | 
					        duration: 0,
 | 
				
			||||||
 | 
					        easing: linear,
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					        divClass="overflow-y-auto z-50 bg-white"
 | 
				
			||||||
 | 
					        hidden={$selectedElement === undefined}
 | 
				
			||||||
 | 
					        on:close={() => {
 | 
				
			||||||
 | 
					        selectedElement.setData(undefined)
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <TitledPanel>
 | 
				
			||||||
 | 
					          <div slot="title" class="flex justify-between">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <a target="_blank" rel="noopener"
 | 
				
			||||||
 | 
					               href={"https://osm.org/"+$selectedElement.properties.id}>{$selectedElement.properties.id}</a>
 | 
				
			||||||
 | 
					            <XCircleIcon class="w-6 h-6" on:click={() => selectedElement.set(undefined)} />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <History onlyShowChangesBy={$username} id={$selectedElement.properties.id}></History>
 | 
				
			||||||
 | 
					        </TitledPanel>
 | 
				
			||||||
 | 
					      </Drawer>
 | 
				
			||||||
 | 
					    {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="flex-grow overflow-hidden m-1 rounded-xl">
 | 
				
			||||||
 | 
					      <MaplibreMap map={map} mapProperties={maplibremap} autorecovery={true} />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  {:else if mode === "table"}
 | 
				
			||||||
 | 
					    <div class="m-2 h-full overflow-y-auto">
 | 
				
			||||||
 | 
					      {#each $featuresStore as f}
 | 
				
			||||||
 | 
					        <History onlyShowChangesBy={$username?.split(";")} id={f.properties.id} />
 | 
				
			||||||
 | 
					      {/each}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  {:else if mode === "aggregate"}
 | 
				
			||||||
 | 
					    <div class="m-2 h-full overflow-y-auto">
 | 
				
			||||||
 | 
					      <AggregateView features={$featuresStore} onlyShowUsername={$username?.split(";")} />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  {:else if mode === "images"}
 | 
				
			||||||
 | 
					    <div class="m-2 h-full overflow-y-auto">
 | 
				
			||||||
 | 
					      <AggregateImages features={$featuresStore} onlyShowUsername={$username?.split(";")} />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  {/if}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<Page shown={showPreviouslyVisited}>
 | 
				
			||||||
 | 
					  <div slot="header">Earlier inspected constributors</div>
 | 
				
			||||||
 | 
					  <PreviouslySpiedUsers {osmConnection} {inspectedContributors} on:selectUser={(e) => {
 | 
				
			||||||
 | 
					    username.set(e.detail); load();showPreviouslyVisited.set(false)
 | 
				
			||||||
 | 
					  }}  />
 | 
				
			||||||
 | 
					</Page>
 | 
				
			||||||
							
								
								
									
										5
									
								
								src/UI/InspectorGUI.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/UI/InspectorGUI.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					import InspectorGUI from "./InspectorGUI.svelte"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					new InspectorGUI({
 | 
				
			||||||
 | 
					    target: document.getElementById("main"),
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
| 
						 | 
					@ -97,7 +97,7 @@ export class DeleteFlowState {
 | 
				
			||||||
                if (allByMyself.data === null && useTheInternet) {
 | 
					                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
 | 
					                    // 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
 | 
					                    const hist = this.objectDownloader
 | 
				
			||||||
                        .DownloadHistory(id)
 | 
					                        .downloadHistory(id)
 | 
				
			||||||
                        .map((versions) =>
 | 
					                        .map((versions) =>
 | 
				
			||||||
                            versions.map((version) =>
 | 
					                            versions.map((version) =>
 | 
				
			||||||
                                Number(version.tags["_last_edit:contributor:uid"])
 | 
					                                Number(version.tags["_last_edit:contributor:uid"])
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -87,7 +87,7 @@ export interface SpecialVisualizationState {
 | 
				
			||||||
    readonly geocodedImages: UIEventSource<Feature[]>
 | 
					    readonly geocodedImages: UIEventSource<Feature[]>
 | 
				
			||||||
    readonly searchState: SearchState
 | 
					    readonly searchState: SearchState
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getMatchingLayer(properties: Record<string, string>)
 | 
					    getMatchingLayer(properties: Record<string, string>): LayerConfig | undefined
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    showCurrentLocationOn(map: Store<MlMap>): ShowDataLayer
 | 
					    showCurrentLocationOn(map: Store<MlMap>): ShowDataLayer
 | 
				
			||||||
    reportError(message: string | Error | XMLHttpRequest, extramessage?: string): Promise<void>
 | 
					    reportError(message: string | Error | XMLHttpRequest, extramessage?: string): Promise<void>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue