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