forked from MapComplete/MapComplete
		
	Feature(inspector): allow to load multiple contributors at once
This commit is contained in:
		
							parent
							
								
									7c6224fd3e
								
							
						
					
					
						commit
						063a912c82
					
				
					 8 changed files with 69 additions and 41 deletions
				
			
		|  | @ -52,8 +52,11 @@ | |||
|         "render": 0, | ||||
|         "mappings": [ | ||||
|           { | ||||
|             "if": {"or": | ||||
|               ["_geometry:type=Polygon","_geometry:type=MultiPolygon"] | ||||
|             "if": { | ||||
|               "or": [ | ||||
|                 "_geometry:type=Polygon", | ||||
|                 "_geometry:type=MultiPolygon" | ||||
|               ] | ||||
|             }, | ||||
|             "then": 20 | ||||
|           } | ||||
|  |  | |||
|  | @ -1 +1,2 @@ | |||
| {} | ||||
| { | ||||
| } | ||||
|  | @ -5,11 +5,9 @@ | |||
|   import OsmObjectDownloader from "../../Logic/Osm/OsmObjectDownloader" | ||||
|   import { OsmObject } from "../../Logic/Osm/OsmObject" | ||||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import AttributedImage from "../Image/AttributedImage.svelte" | ||||
|   import AttributedPanoramaxImage from "./AttributedPanoramaxImage.svelte" | ||||
|   import History from "./History.svelte" | ||||
| 
 | ||||
|   export let onlyShowUsername: string | ||||
|   export let onlyShowUsername: string[] | ||||
|   export let features: Feature[] | ||||
| 
 | ||||
|   const downloader = new OsmObjectDownloader() | ||||
|  | @ -23,11 +21,12 @@ | |||
|     } | ||||
|     return result | ||||
|   })) | ||||
|   let usernamesSet = new Set(onlyShowUsername) | ||||
|   let allDiffs: Store<{ | ||||
|     key: string; | ||||
|     value?: string; | ||||
|     oldValue?: string | ||||
|   }[]> = allHistories.mapD(histories => HistoryUtils.fullHistoryDiff(histories, onlyShowUsername)) | ||||
|   }[]> = allHistories.mapD(histories => HistoryUtils.fullHistoryDiff(histories, usernamesSet)) | ||||
| 
 | ||||
|   let addedImages = allDiffs.mapD(diffs => [].concat(...diffs.filter(({ key }) => imageKeys.has(key)))) | ||||
| 
 | ||||
|  | @ -37,7 +36,11 @@ | |||
| {:else if $addedImages.length === 0} | ||||
|   No images added by this contributor | ||||
| {:else} | ||||
|   {#each $addedImages as imgDiff} | ||||
|     <AttributedPanoramaxImage hash={imgDiff.value} /> | ||||
|   {/each} | ||||
|   <div class="flex"> | ||||
|     {#each $addedImages as imgDiff} | ||||
|       <div class="w-48 h-48"> | ||||
|         <AttributedPanoramaxImage hash={imgDiff.value} /> | ||||
|       </div> | ||||
|     {/each} | ||||
|   </div> | ||||
| {/if} | ||||
|  |  | |||
|  | @ -6,14 +6,16 @@ | |||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import { HistoryUtils } from "./HistoryUtils" | ||||
|   import * as shared_questions from "../../assets/generated/layers/questions.json" | ||||
|   import TagRenderingQuestion from "../Popup/TagRendering/TagRenderingQuestion.svelte" | ||||
|   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 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))) | ||||
|  | @ -22,7 +24,7 @@ | |||
|     key: string; | ||||
|     value?: string; | ||||
|     oldValue?: string | ||||
|   }[]> = allHistories.mapD(histories => HistoryUtils.fullHistoryDiff(histories, onlyShowUsername)) | ||||
|   }[]> = allHistories.mapD(histories => HistoryUtils.fullHistoryDiff(histories, usernames)) | ||||
| 
 | ||||
|   const trs = shared_questions.tagRenderings.map(tr => new TagRenderingConfig(tr)) | ||||
| 
 | ||||
|  | @ -69,6 +71,7 @@ | |||
|     return perKey | ||||
|   }) | ||||
| 
 | ||||
|   const t = Translations.t.inspector | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
|  | @ -85,8 +88,7 @@ | |||
|     </h3> | ||||
|     <AccordionSingle> | ||||
|       <span slot="header"> | ||||
| 
 | ||||
|       Answered {diff.count} times | ||||
| <Tr t={t.answeredCountTimes.Subs(diff)} /> | ||||
|       </span> | ||||
|       <ul> | ||||
|         {#each diff.values as value} | ||||
|  |  | |||
|  | @ -9,10 +9,12 @@ | |||
|   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 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 => ({ | ||||
|  | @ -21,11 +23,11 @@ | |||
|   }))) | ||||
|   let filteredHistory = partOfLayer.mapD(history => | ||||
|     history.filter(({ step }) => { | ||||
|       if (!onlyShowChangesBy) { | ||||
|       if (usernames.size == 0) { | ||||
|         return true | ||||
|       } | ||||
|       console.log("Comparing ", step.tags["_last_edit:contributor"], onlyShowChangesBy, step.tags["_last_edit:contributor"] === onlyShowChangesBy) | ||||
|       return step.tags["_last_edit:contributor"] === onlyShowChangesBy | ||||
|       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) | ||||
|  | @ -38,6 +40,8 @@ | |||
|    * 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)} | ||||
|  | @ -55,7 +59,7 @@ | |||
|   {#if !$filteredHistory} | ||||
|     <Loading>Loading history...</Loading> | ||||
|   {:else if $filteredHistory.length === 0} | ||||
|     Only geometry changes found | ||||
|     <Tr t={t.onlyGeometry} /> | ||||
|   {:else} | ||||
|     <table class="w-full m-1"> | ||||
|       {#each $filteredHistory as { step, layer }} | ||||
|  | @ -64,7 +68,7 @@ | |||
|           <tr> | ||||
|             <td colspan="3"> | ||||
|               <h3> | ||||
|                 Created by {step.tags["_last_edit:contributor"]} | ||||
|                 <Tr t={t.createdBy.Subs({contributor: step.tags["_last_edit:contributor"]})} /> | ||||
|               </h3> | ||||
|             </td> | ||||
|           </tr> | ||||
|  | @ -72,7 +76,7 @@ | |||
|         {#if HistoryUtils.tagHistoryDiff(step, $fullHistory).length === 0} | ||||
|           <tr> | ||||
|             <td class="font-bold justify-center flex w-full" colspan="3"> | ||||
|               Only changes in geometry | ||||
|               <Tr t={t.onlyGeometry} /> | ||||
|             </td> | ||||
|           </tr> | ||||
|         {:else} | ||||
|  |  | |||
|  | @ -33,10 +33,10 @@ export class HistoryUtils { | |||
|             }).filter(ch => ch.oldValue !== ch.value) | ||||
|     } | ||||
| 
 | ||||
|     public static fullHistoryDiff(histories: OsmObject[][], onlyShowUsername?: string){ | ||||
|     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 || step.tags["_last_edit:contributor"] === onlyShowUsername) | ||||
|                 const filtered = history.filter(step => !onlyShowUsername || onlyShowUsername?.has(step.tags["_last_edit:contributor"] )) | ||||
|                 const diffs: { | ||||
|                     key: string; | ||||
|                     value?: string; | ||||
|  |  | |||
|  | @ -88,7 +88,12 @@ | |||
|       No labels | ||||
|     {:else} | ||||
|       {#each $labels as label} | ||||
|         <div class="mx-2">{label} </div> | ||||
|         <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"> | ||||
|  |  | |||
|  | @ -27,6 +27,8 @@ | |||
|   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") | ||||
|  | @ -52,10 +54,12 @@ | |||
|     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, HistoryUtils.personalTheme.layers, { | ||||
|   ShowDataLayer.showMultipleLayers(map, features, [...layersNoFixme, fixme] , { | ||||
|     zoomToFeatures: true, | ||||
|     onClick: (f: Feature) => { | ||||
|       selectedElement.set(undefined) | ||||
|  | @ -75,7 +79,7 @@ | |||
| 
 | ||||
|   async function load() { | ||||
|     const user = username.data | ||||
|     { | ||||
|     if(user.indexOf(";")<0){ | ||||
| 
 | ||||
|       const inspectedData = inspectedContributors.data | ||||
|       const previousEntry = inspectedData.find(e => e.name === user) | ||||
|  | @ -93,7 +97,7 @@ | |||
| 
 | ||||
|     step.setData("loading") | ||||
|     featuresStore.set([]) | ||||
|     const overpass = new Overpass(undefined, ["nw(user_touched:\"" + user + "\");"], Constants.defaultOverpassUrls[0]) | ||||
|     const overpass = new Overpass(undefined, user.split(";").map(user => "nw(user_touched:\"" + user + "\");"), Constants.defaultOverpassUrls[0]) | ||||
|     if (!maplibremap.bounds.data) { | ||||
|       return | ||||
|     } | ||||
|  | @ -118,38 +122,44 @@ | |||
|   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">Inspect contributor</h1> | ||||
|     <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()}>Load</button> | ||||
|       <button class="primary" on:click={() => load()}> | ||||
|         <Tr t={t.load}/> | ||||
|       </button> | ||||
|     {/if} | ||||
|     <button on:click={() => showPreviouslyVisited.setData(true)}> | ||||
|       Show earlier inspected contributors | ||||
|      <Tr t={t.earlierInspected}/> | ||||
|     </button> | ||||
|     <a href="./index.html" class="button">Back to index</a> | ||||
|     <a href="./index.html" class="button"> | ||||
|       <Tr t={t.backToIndex}/> | ||||
|     </a> | ||||
|   </div> | ||||
| 
 | ||||
|   <div class="flex"> | ||||
|     <button class:primary={mode === "map"} on:click={() => mode = "map"}> | ||||
|       Map view | ||||
|       <Tr t={t.mapView}/> | ||||
|     </button> | ||||
|     <button class:primary={mode === "table"} on:click={() => mode = "table"}> | ||||
|       Table view | ||||
|       <Tr t={t.tableView}/> | ||||
|     </button> | ||||
|     <button class:primary={mode === "aggregate"} on:click={() => mode = "aggregate"}> | ||||
|       Aggregate | ||||
|       <Tr t={t.aggregateView}/> | ||||
|     </button> | ||||
|     <button class:primary={mode === "images"} on:click={() => mode = "images"}> | ||||
|       Images | ||||
|       <Tr t={t.images}/> | ||||
|     </button> | ||||
|   </div> | ||||
| 
 | ||||
|  | @ -195,16 +205,16 @@ | |||
|   {:else if mode === "table"} | ||||
|     <div class="m-2 h-full overflow-y-auto"> | ||||
|       {#each $featuresStore as f} | ||||
|         <History onlyShowChangesBy={$username} id={f.properties.id} /> | ||||
|         <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} /> | ||||
|       <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} /> | ||||
|       <AggregateImages features={$featuresStore} onlyShowUsername={$username?.split(";")} /> | ||||
|     </div> | ||||
|   {/if} | ||||
| </div> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue