Refactoring: port CloseNoteButton to svelte
This commit is contained in:
parent
f713d5b6d8
commit
ee1ef81f48
6 changed files with 122 additions and 215 deletions
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "mapcomplete",
|
"name": "mapcomplete",
|
||||||
"version": "0.44.13",
|
"version": "0.44.14",
|
||||||
"repository": "https://github.com/pietervdvn/MapComplete",
|
"repository": "https://github.com/pietervdvn/MapComplete",
|
||||||
"description": "A small website to edit OSM easily",
|
"description": "A small website to edit OSM easily",
|
||||||
"bugs": "https://github.com/pietervdvn/MapComplete/issues",
|
"bugs": "https://github.com/pietervdvn/MapComplete/issues",
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
import { SubtleButton } from "../Base/SubtleButton"
|
|
||||||
import BaseUIElement from "../BaseUIElement"
|
|
||||||
import { OsmConnection, OsmServiceState } from "../../Logic/Osm/OsmConnection"
|
|
||||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
|
||||||
import Loading from "../Base/Loading"
|
|
||||||
import Translations from "../i18n/Translations"
|
|
||||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
|
||||||
import Combine from "../Base/Combine"
|
|
||||||
import { Translation } from "../i18n/Translation"
|
|
||||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
|
||||||
import Login from "../../assets/svg/Login.svelte"
|
|
||||||
import Invalid from "../../assets/svg/Invalid.svelte"
|
|
||||||
|
|
||||||
class LoginButton extends SubtleButton {
|
|
||||||
constructor(
|
|
||||||
text: BaseUIElement | string,
|
|
||||||
state: {
|
|
||||||
osmConnection?: OsmConnection
|
|
||||||
},
|
|
||||||
icon?: BaseUIElement | string
|
|
||||||
) {
|
|
||||||
super(icon ?? new SvelteUIElement(Login), text)
|
|
||||||
this.onClick(() => {
|
|
||||||
state.osmConnection?.AttemptLogin()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LoginToggle extends VariableUiElement {
|
|
||||||
/**
|
|
||||||
* Constructs an element which shows 'el' if the user is logged in
|
|
||||||
* If not logged in, 'text' is shown on the button which invites to login.
|
|
||||||
*
|
|
||||||
* If logging in is not possible for some reason, an appropriate error message is shown
|
|
||||||
*
|
|
||||||
* State contains the 'osmConnection' to work with
|
|
||||||
* @param el: Element to show when logged in
|
|
||||||
* @param text: To show on the login button. Default: nothing
|
|
||||||
* @param state: if no osmConnection is given, assumes test situation and will show 'el' as if logged in
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
el: BaseUIElement,
|
|
||||||
text: BaseUIElement | string,
|
|
||||||
state: {
|
|
||||||
readonly osmConnection?: OsmConnection
|
|
||||||
readonly featureSwitchUserbadge?: Store<boolean>
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const loading = new Loading("Trying to log in...")
|
|
||||||
const login = text === undefined ? undefined : new LoginButton(text, state)
|
|
||||||
const t = Translations.t.general
|
|
||||||
const offlineModes: Partial<Record<OsmServiceState, Translation>> = {
|
|
||||||
offline: t.loginFailedOfflineMode,
|
|
||||||
unreachable: t.loginFailedUnreachableMode,
|
|
||||||
readonly: t.loginFailedReadonlyMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
super(
|
|
||||||
state.osmConnection?.loadingStatus?.map(
|
|
||||||
(osmConnectionState) => {
|
|
||||||
if (state.featureSwitchUserbadge?.data == false) {
|
|
||||||
// All features to login with are disabled
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiState = state.osmConnection?.apiIsOnline?.data ?? "online"
|
|
||||||
const apiTranslation = offlineModes[apiState]
|
|
||||||
if (apiTranslation !== undefined) {
|
|
||||||
return new Combine([
|
|
||||||
new SvelteUIElement(Invalid).SetClass("w-8 h-8 m-2 shrink-0"),
|
|
||||||
apiTranslation,
|
|
||||||
]).SetClass("flex items-center alert max-w-64")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (osmConnectionState === "loading") {
|
|
||||||
return loading
|
|
||||||
}
|
|
||||||
if (osmConnectionState === "not-attempted") {
|
|
||||||
return login
|
|
||||||
}
|
|
||||||
if (osmConnectionState === "logged-in") {
|
|
||||||
return el
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback
|
|
||||||
return new LoginButton(
|
|
||||||
Translations.t.general.loginFailed,
|
|
||||||
state,
|
|
||||||
new SvelteUIElement(Invalid)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
[state.featureSwitchUserbadge, state.osmConnection?.apiIsOnline]
|
|
||||||
) ?? new ImmutableStore(el)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
54
src/UI/Popup/Notes/CloseNoteButton.svelte
Normal file
54
src/UI/Popup/Notes/CloseNoteButton.svelte
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||||
|
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||||
|
import LoginToggle from "../../Base/LoginToggle.svelte"
|
||||||
|
import Translations from "../../i18n/Translations"
|
||||||
|
import Tr from "../../Base/Tr.svelte"
|
||||||
|
import Icon from "../../Map/Icon.svelte"
|
||||||
|
import NoteCommentElement from "./NoteCommentElement"
|
||||||
|
import { Translation } from "../../i18n/Translation"
|
||||||
|
|
||||||
|
const t = Translations.t.notes
|
||||||
|
export let state: SpecialVisualizationState
|
||||||
|
export let tags: UIEventSource<Record<string, string>>
|
||||||
|
export let icon: string = "checkmark"
|
||||||
|
export let idkey: string = "id"
|
||||||
|
export let message: string
|
||||||
|
export let text: Translation = t.closeNote
|
||||||
|
export let minzoom: number
|
||||||
|
export let zoomMoreMessage: string
|
||||||
|
|
||||||
|
|
||||||
|
let curZoom = state.mapProperties.zoom
|
||||||
|
const isClosed = tags.map((tags) => (tags["closed_at"] ?? "") !== "")
|
||||||
|
|
||||||
|
async function closeNote() {
|
||||||
|
const id = tags.data[idkey]
|
||||||
|
await state.osmConnection.closeNote(id, message)
|
||||||
|
NoteCommentElement.addCommentTo(message, tags, state)
|
||||||
|
tags.data["closed_at"] = new Date().toISOString()
|
||||||
|
tags.ping()
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<LoginToggle {state}>
|
||||||
|
<div slot="not-logged-in">
|
||||||
|
<Tr t={t.loginToClose} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{#if $isClosed}
|
||||||
|
<Tr cls="thanks" t={t.isClosed} />
|
||||||
|
{:else if minzoom <= $curZoom}
|
||||||
|
<button on:click={() => closeNote()}>
|
||||||
|
<div class="flex items-center gap-x-2">
|
||||||
|
<Icon {icon} clss="w-10 h-10" />
|
||||||
|
<Tr t={text} />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{:else if zoomMoreMessage}
|
||||||
|
{zoomMoreMessage}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
</LoginToggle>
|
|
@ -1,106 +0,0 @@
|
||||||
import BaseUIElement from "../../BaseUIElement"
|
|
||||||
import Translations from "../../i18n/Translations"
|
|
||||||
import { Utils } from "../../../Utils"
|
|
||||||
import Img from "../../Base/Img"
|
|
||||||
import { SubtleButton } from "../../Base/SubtleButton"
|
|
||||||
import Toggle from "../../Input/Toggle"
|
|
||||||
import { LoginToggle } from ".././LoginButton"
|
|
||||||
import { SpecialVisualization, SpecialVisualizationState } from "../../SpecialVisualization"
|
|
||||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
|
||||||
import Constants from "../../../Models/Constants"
|
|
||||||
import SvelteUIElement from "../../Base/SvelteUIElement"
|
|
||||||
import Checkmark from "../../../assets/svg/Checkmark.svelte"
|
|
||||||
import NoteCommentElement from "./NoteCommentElement"
|
|
||||||
import Icon from "../../Map/Icon.svelte"
|
|
||||||
|
|
||||||
export class CloseNoteButton implements SpecialVisualization {
|
|
||||||
public readonly funcName = "close_note"
|
|
||||||
public readonly needsUrls = [Constants.osmAuthConfig.url]
|
|
||||||
public readonly docs =
|
|
||||||
"Button to close a note. A predefined text can be defined to close the note with. If the note is already closed, will show a small text."
|
|
||||||
public readonly args = [
|
|
||||||
{
|
|
||||||
name: "text",
|
|
||||||
doc: "Text to show on this button",
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "icon",
|
|
||||||
doc: "Icon to show",
|
|
||||||
defaultValue: "checkmark.svg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "idkey",
|
|
||||||
doc: "The property name where the ID of the note to close can be found",
|
|
||||||
defaultValue: "id",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "comment",
|
|
||||||
doc: "Text to add onto the note when closing",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "minZoom",
|
|
||||||
doc: "If set, only show the closenote button if zoomed in enough",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "zoomButton",
|
|
||||||
doc: "Text to show if not zoomed in enough",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
public constr(
|
|
||||||
state: SpecialVisualizationState,
|
|
||||||
tags: UIEventSource<Record<string, string>>,
|
|
||||||
args: string[]
|
|
||||||
): BaseUIElement {
|
|
||||||
const t = Translations.t.notes
|
|
||||||
|
|
||||||
const params: {
|
|
||||||
text: string
|
|
||||||
icon: string
|
|
||||||
idkey: string
|
|
||||||
comment: string
|
|
||||||
minZoom: string
|
|
||||||
zoomButton: string
|
|
||||||
} = <any>Utils.ParseVisArgs(this.args, args)
|
|
||||||
|
|
||||||
let icon: BaseUIElement = new SvelteUIElement(Icon, {
|
|
||||||
icon: params.icon ?? "checkmark.svg",
|
|
||||||
})
|
|
||||||
let textToShow = t.closeNote
|
|
||||||
if ((params.text ?? "") !== "") {
|
|
||||||
textToShow = Translations.T(args[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
let closeButton: BaseUIElement = new SubtleButton(icon, textToShow)
|
|
||||||
const isClosed = tags.map((tags) => (tags["closed_at"] ?? "") !== "")
|
|
||||||
closeButton.onClick(() => {
|
|
||||||
const id = tags.data[args[2] ?? "id"]
|
|
||||||
const text = args[3]
|
|
||||||
state.osmConnection.closeNote(id, text)?.then((_) => {
|
|
||||||
NoteCommentElement.addCommentTo(text, tags, state)
|
|
||||||
tags.data["closed_at"] = new Date().toISOString()
|
|
||||||
tags.ping()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if ((params.minZoom ?? "") !== "" && !isNaN(Number(params.minZoom))) {
|
|
||||||
closeButton = new Toggle(
|
|
||||||
closeButton,
|
|
||||||
params.zoomButton ?? "",
|
|
||||||
state.mapProperties.zoom.map((zoom) => zoom >= Number(params.minZoom))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LoginToggle(
|
|
||||||
new Toggle(
|
|
||||||
t.isClosed.SetClass("thanks"),
|
|
||||||
closeButton,
|
|
||||||
|
|
||||||
isClosed
|
|
||||||
),
|
|
||||||
t.loginToClose,
|
|
||||||
state
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,11 +2,7 @@ import Combine from "./Base/Combine"
|
||||||
import { FixedUiElement } from "./Base/FixedUiElement"
|
import { FixedUiElement } from "./Base/FixedUiElement"
|
||||||
import BaseUIElement from "./BaseUIElement"
|
import BaseUIElement from "./BaseUIElement"
|
||||||
import Title from "./Base/Title"
|
import Title from "./Base/Title"
|
||||||
import {
|
import { RenderingSpecification, SpecialVisualization, SpecialVisualizationState } from "./SpecialVisualization"
|
||||||
RenderingSpecification,
|
|
||||||
SpecialVisualization,
|
|
||||||
SpecialVisualizationState
|
|
||||||
} from "./SpecialVisualization"
|
|
||||||
import { HistogramViz } from "./Popup/HistogramViz"
|
import { HistogramViz } from "./Popup/HistogramViz"
|
||||||
import MinimapViz from "./Popup/MinimapViz.svelte"
|
import MinimapViz from "./Popup/MinimapViz.svelte"
|
||||||
import { ShareLinkViz } from "./Popup/ShareLinkViz"
|
import { ShareLinkViz } from "./Popup/ShareLinkViz"
|
||||||
|
@ -15,7 +11,6 @@ import { MultiApplyViz } from "./Popup/MultiApplyViz"
|
||||||
import { AddNoteCommentViz } from "./Popup/Notes/AddNoteCommentViz"
|
import { AddNoteCommentViz } from "./Popup/Notes/AddNoteCommentViz"
|
||||||
import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"
|
import { PlantNetDetectionViz } from "./Popup/PlantNetDetectionViz"
|
||||||
import TagApplyButton from "./Popup/TagApplyButton"
|
import TagApplyButton from "./Popup/TagApplyButton"
|
||||||
import { CloseNoteButton } from "./Popup/Notes/CloseNoteButton"
|
|
||||||
import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"
|
import { MapillaryLinkVis } from "./Popup/MapillaryLinkVis"
|
||||||
import { ImmutableStore, Store, Stores, UIEventSource } from "../Logic/UIEventSource"
|
import { ImmutableStore, Store, Stores, UIEventSource } from "../Logic/UIEventSource"
|
||||||
import AllTagsPanel from "./Popup/AllTagsPanel.svelte"
|
import AllTagsPanel from "./Popup/AllTagsPanel.svelte"
|
||||||
|
@ -99,6 +94,7 @@ import Trash from "@babeard/svelte-heroicons/mini/Trash"
|
||||||
import NothingKnown from "./Popup/NothingKnown.svelte"
|
import NothingKnown from "./Popup/NothingKnown.svelte"
|
||||||
import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch"
|
import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch"
|
||||||
import { And } from "../Logic/Tags/And"
|
import { And } from "../Logic/Tags/And"
|
||||||
|
import CloseNoteButton from "./Popup/Notes/CloseNoteButton.svelte"
|
||||||
|
|
||||||
class NearbyImageVis implements SpecialVisualization {
|
class NearbyImageVis implements SpecialVisualization {
|
||||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
||||||
|
@ -215,6 +211,66 @@ class StealViz implements SpecialVisualization {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CloseNoteViz implements SpecialVisualization {
|
||||||
|
public readonly funcName = "close_note"
|
||||||
|
public readonly needsUrls = [Constants.osmAuthConfig.url]
|
||||||
|
public readonly docs =
|
||||||
|
"Button to close a note. A predefined text can be defined to close the note with. If the note is already closed, will show a small text."
|
||||||
|
public readonly args = [
|
||||||
|
{
|
||||||
|
name: "text",
|
||||||
|
doc: "Text to show on this button",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "icon",
|
||||||
|
doc: "Icon to show",
|
||||||
|
defaultValue: "checkmark.svg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "idkey",
|
||||||
|
doc: "The property name where the ID of the note to close can be found",
|
||||||
|
defaultValue: "id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "comment",
|
||||||
|
doc: "Text to add onto the note when closing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "minZoom",
|
||||||
|
doc: "If set, only show the closenote button if zoomed in enough",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zoomButton",
|
||||||
|
doc: "Text to show if not zoomed in enough",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
public constr(state: SpecialVisualizationState, tags: UIEventSource<Record<string, string>>, args: string[], feature: Feature, layer: LayerConfig): SvelteUIElement {
|
||||||
|
|
||||||
|
const {
|
||||||
|
text,
|
||||||
|
icon,
|
||||||
|
idkey,
|
||||||
|
comment,
|
||||||
|
minZoom,
|
||||||
|
zoomButton
|
||||||
|
} = Utils.ParseVisArgs(this.args, args)
|
||||||
|
|
||||||
|
|
||||||
|
return new SvelteUIElement(CloseNoteButton, {
|
||||||
|
state,
|
||||||
|
tags,
|
||||||
|
icon,
|
||||||
|
idkey,
|
||||||
|
message: comment,
|
||||||
|
text: Translations.T(text),
|
||||||
|
minzoom: minZoom,
|
||||||
|
zoomMoreMessage: zoomButton
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thin wrapper around QuestionBox.svelte to include it into the special Visualisations
|
* Thin wrapper around QuestionBox.svelte to include it into the special Visualisations
|
||||||
*/
|
*/
|
||||||
|
@ -525,7 +581,7 @@ export default class SpecialVisualizations {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new CloseNoteButton(),
|
new CloseNoteViz(),
|
||||||
new PlantNetDetectionViz(),
|
new PlantNetDetectionViz(),
|
||||||
|
|
||||||
new TagApplyButton(),
|
new TagApplyButton(),
|
||||||
|
@ -533,7 +589,6 @@ export default class SpecialVisualizations {
|
||||||
new PointImportButtonViz(),
|
new PointImportButtonViz(),
|
||||||
new WayImportButtonViz(),
|
new WayImportButtonViz(),
|
||||||
new ConflateImportButtonViz(),
|
new ConflateImportButtonViz(),
|
||||||
|
|
||||||
new NearbyImageVis(),
|
new NearbyImageVis(),
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -1938,7 +1993,7 @@ export default class SpecialVisualizations {
|
||||||
mostShadowed = undefined
|
mostShadowed = undefined
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}else if(!prTags.shadows(mostShadowedTags)) {
|
} else if (!prTags.shadows(mostShadowedTags)) {
|
||||||
// The new contender does not win, but it might defeat the current contender
|
// The new contender does not win, but it might defeat the current contender
|
||||||
mostShadowed = undefined
|
mostShadowed = undefined
|
||||||
break
|
break
|
||||||
|
|
|
@ -170,10 +170,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
/**
|
/**
|
||||||
* Parses the arguments for special visualisations
|
* Parses the arguments for special visualisations
|
||||||
*/
|
*/
|
||||||
public static ParseVisArgs(
|
public static ParseVisArgs<T extends Record<string, string>>(
|
||||||
specs: { name: string; defaultValue?: string }[],
|
specs: { name: string; defaultValue?: string }[],
|
||||||
args: string[]
|
args: string[]
|
||||||
): Record<string, string> {
|
): T {
|
||||||
const parsed: Record<string, string> = {}
|
const parsed: Record<string, string> = {}
|
||||||
if (args.length > specs.length) {
|
if (args.length > specs.length) {
|
||||||
throw (
|
throw (
|
||||||
|
@ -193,7 +193,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
|
||||||
parsed[spec.name] = arg
|
parsed[spec.name] = arg
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsed
|
return <T> parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
static EncodeXmlValue(str) {
|
static EncodeXmlValue(str) {
|
||||||
|
|
Loading…
Reference in a new issue