forked from MapComplete/MapComplete
		
	Feature(inspector): keep track of previously visited contributors
This commit is contained in:
		
							parent
							
								
									b86b8dd1ee
								
							
						
					
					
						commit
						4d0d92d250
					
				
					 2 changed files with 148 additions and 18 deletions
				
			
		
							
								
								
									
										102
									
								
								src/UI/History/PreviouslySpiedUsers.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/UI/History/PreviouslySpiedUsers.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,102 @@ | ||||||
|  | <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} </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> | ||||||
|  | @ -23,6 +23,9 @@ | ||||||
|   import AggregateView from "./History/AggregateView.svelte" |   import AggregateView from "./History/AggregateView.svelte" | ||||||
|   import { HistoryUtils } from "./History/HistoryUtils" |   import { HistoryUtils } from "./History/HistoryUtils" | ||||||
|   import AggregateImages from "./History/AggregateImages.svelte" |   import AggregateImages from "./History/AggregateImages.svelte" | ||||||
|  |   import Page from "./Base/Page.svelte" | ||||||
|  |   import PreviouslySpiedUsers from "./History/PreviouslySpiedUsers.svelte" | ||||||
|  |   import { OsmConnection } from "../Logic/Osm/OsmConnection" | ||||||
| 
 | 
 | ||||||
|   let username = QueryParameters.GetQueryParameter("user", undefined, "Inspect this user") |   let username = QueryParameters.GetQueryParameter("user", undefined, "Inspect this user") | ||||||
|   let step = new UIEventSource<"waiting" | "loading" | "done">("waiting") |   let step = new UIEventSource<"waiting" | "loading" | "done">("waiting") | ||||||
|  | @ -32,8 +35,8 @@ | ||||||
|   let lon = UIEventSource.asFloat(QueryParameters.GetQueryParameter("lon", "0")) |   let lon = UIEventSource.asFloat(QueryParameters.GetQueryParameter("lon", "0")) | ||||||
|   let theme = new ThemeConfig(<any>inspector_theme, true) |   let theme = new ThemeConfig(<any>inspector_theme, true) | ||||||
|   let layer = theme.layers.find(l => l.id === "usertouched") |   let layer = theme.layers.find(l => l.id === "usertouched") | ||||||
|  |   // Is this a dirty hack? Yes it is! | ||||||
|   theme.getMatchingLayer = () => { |   theme.getMatchingLayer = () => { | ||||||
|     // Is this a dirty hack? Yes it is! |  | ||||||
|     return layer |     return layer | ||||||
|   } |   } | ||||||
|   let loadingData = false |   let loadingData = false | ||||||
|  | @ -61,23 +64,35 @@ | ||||||
|     } |     } | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   /*  new ShowDataLayer(map, |   let osmConnection = new OsmConnection() | ||||||
|       { |   let inspectedContributors: UIEventSource<{ | ||||||
|         layer, |     name: string, | ||||||
|         zoomToFeatures: true, |     visitedTime: string, | ||||||
|         features, |     label: string | ||||||
|         onClick: (f: Feature) => { |   }[]> = UIEventSource.asObject( | ||||||
|           selectedElement.set(undefined) |     osmConnection.getPreference("spied-upon-users"), []) | ||||||
|           Utils.waitFor(200).then(() => { |  | ||||||
|             selectedElement.set(f) |  | ||||||
|           }) |  | ||||||
|         } |  | ||||||
|       })*/ |  | ||||||
| 
 | 
 | ||||||
|   async function load() { |   async function load() { | ||||||
|  |     const user = username.data | ||||||
|  |     { | ||||||
|  | 
 | ||||||
|  |       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") |     step.setData("loading") | ||||||
|     const overpass = new Overpass(undefined, ["nw(user_touched:\"" + username.data + "\");"], Constants.defaultOverpassUrls[0]) |     featuresStore.set([]) | ||||||
|  |     const overpass = new Overpass(undefined, ["nw(user_touched:\"" + user + "\");"], Constants.defaultOverpassUrls[0]) | ||||||
|     if (!maplibremap.bounds.data) { |     if (!maplibremap.bounds.data) { | ||||||
|       return |       return | ||||||
|     } |     } | ||||||
|  | @ -100,6 +115,9 @@ | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   let mode: "map" | "table" | "aggregate" | "images" = "map" |   let mode: "map" | "table" | "aggregate" | "images" = "map" | ||||||
|  | 
 | ||||||
|  |   let showPreviouslyVisited = new UIEventSource(true) | ||||||
|  | 
 | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div class="flex flex-col w-full h-full"> | <div class="flex flex-col w-full h-full"> | ||||||
|  | @ -112,6 +130,9 @@ | ||||||
|     {:else} |     {:else} | ||||||
|       <button class="primary" on:click={() => load()}>Load</button> |       <button class="primary" on:click={() => load()}>Load</button> | ||||||
|     {/if} |     {/if} | ||||||
|  |     <button on:click={() => showPreviouslyVisited.setData(true)}> | ||||||
|  |       Show earlier inspected contributors | ||||||
|  |     </button> | ||||||
|     <a href="./index.html" class="button">Back to index</a> |     <a href="./index.html" class="button">Back to index</a> | ||||||
|   </div> |   </div> | ||||||
| 
 | 
 | ||||||
|  | @ -171,13 +192,13 @@ | ||||||
|     </div> |     </div> | ||||||
|   {:else if mode === "table"} |   {:else if mode === "table"} | ||||||
|     <div class="m-2 h-full overflow-y-auto"> |     <div class="m-2 h-full overflow-y-auto"> | ||||||
|     {#each $featuresStore as f} |       {#each $featuresStore as f} | ||||||
|       <History onlyShowChangesBy={$username} id={f.properties.id} /> |         <History onlyShowChangesBy={$username} id={f.properties.id} /> | ||||||
|     {/each} |       {/each} | ||||||
|     </div> |     </div> | ||||||
|   {:else if mode === "aggregate"} |   {:else if mode === "aggregate"} | ||||||
|     <div class="m-2 h-full overflow-y-auto"> |     <div class="m-2 h-full overflow-y-auto"> | ||||||
|     <AggregateView features={$featuresStore} onlyShowUsername={$username} /> |       <AggregateView features={$featuresStore} onlyShowUsername={$username} /> | ||||||
|     </div> |     </div> | ||||||
|   {:else if mode === "images"} |   {:else if mode === "images"} | ||||||
|     <div class="m-2 h-full overflow-y-auto"> |     <div class="m-2 h-full overflow-y-auto"> | ||||||
|  | @ -185,3 +206,10 @@ | ||||||
|     </div> |     </div> | ||||||
|   {/if} |   {/if} | ||||||
| </div> | </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> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue