Merge branch 'develop'

This commit is contained in:
Pieter Vander Vennet 2024-06-23 02:55:49 +02:00
commit 5051d2db70
26 changed files with 196 additions and 109 deletions

View file

@ -25,6 +25,8 @@
import Liberapay from "../assets/svg/Liberapay.svelte"
import Bug from "../assets/svg/Bug.svelte"
import Github from "../assets/svg/Github.svelte"
import { Utils } from "../Utils"
import { ArrowTrendingUp } from "@babeard/svelte-heroicons/solid/ArrowTrendingUp"
const featureSwitches = new OsmConnectionFeatureSwitches()
const osmConnection = new OsmConnection({
@ -146,6 +148,15 @@
<UnofficialThemeList search={themeSearchText} {state} />
</LoginToggle>
<a
class="flex button"
href={window.location.protocol + "//" + window.location.host + "/studio.html"}
>
<Pencil class="mr-2 h-6 w-6" />
<Tr t={Translations.t.general.morescreen.createYourOwnTheme} />
</a>
<h3 id="about">
<Tr t={Translations.t.index.about} />
</h3>
@ -161,6 +172,11 @@
<Tr t={Translations.t.general.attribution.openIssueTracker} />
</a>
<a class="flex" href={Utils.OsmChaLinkFor(7)} target="_blank">
<ArrowTrendingUp class="mr-2 h-6 w-6"/>
<Tr t={Translations.t.general.attribution.openOsmchaLastWeek}/>
</a>
<a class="flex" href="https://en.osm.town/@MapComplete" target="_blank">
<Mastodon class="mr-2 h-6 w-6" />
<Tr t={Translations.t.general.attribution.followOnMastodon} />
@ -171,14 +187,6 @@
<Tr t={Translations.t.general.attribution.donate} />
</a>
<a
class="flex"
href={window.location.protocol + "//" + window.location.host + "/studio.html"}
>
<Pencil class="mr-2 h-6 w-6" />
<Tr t={Translations.t.general.morescreen.createYourOwnTheme} />
</a>
<a
class="flex"
href={window.location.protocol + "//" + window.location.host + "/privacy.html"}

View file

@ -40,8 +40,8 @@ export default class CopyrightPanel extends Combine {
const t = Translations.t.general.attribution
const layoutToUse = state.layout
const iconAttributions: BaseUIElement[] = layoutToUse
.getUsedImages()
const iconAttributions: BaseUIElement[] = Utils.Dedup(layoutToUse
.getUsedImages())
.map(CopyrightPanel.IconAttribution)
let maintainer: BaseUIElement = undefined

View file

@ -11,10 +11,9 @@
export let state: SpecialVisualizationState
let theme = state.layout?.id ?? ""
let config: ExtraLinkConfig = state.layout.extraLink
const isIframe = window !== window.top
let basepath = window.location.host
let showWelcomeMessageSwitch = state.featureSwitches.featureSwitchWelcomeMessage
const isIframe = Utils.isIframe
const t = Translations.t.general
const href = state.mapProperties.location.map(
(loc) => {
@ -36,7 +35,7 @@
href={$href}
target={config.newTab ? "_blank" : ""}
rel="noopener"
class="pointer-events-auto flex rounded-full border-black"
class="button pointer-events-auto flex rounded-full border-black"
>
<Icon icon={config.icon} clss="w-6 h-6 m-2" />

View file

@ -15,6 +15,7 @@
import Location_refused from "../../assets/svg/Location_refused.svelte"
import Location from "../../assets/svg/Location.svelte"
import ChevronDoubleLeft from "@babeard/svelte-heroicons/mini/ChevronDoubleLeft"
import Constants from "../../Models/Constants"
/**
* The theme introduction panel
@ -149,13 +150,20 @@
{/if}
</div>
<If condition={state.featureSwitches.featureSwitchBackToThemeOverview}>
<div class="link-underline m-2 mx-4 flex w-full">
<!-- bottom buttons, a bit hidden away: switch layout -->
<a class="flex w-fit items-center justify-end" href={Utils.HomepageLink()}>
<ChevronDoubleLeft class="h-4 w-4" />
<Tr t={Translations.t.general.backToIndex} />
</a>
{#if Utils.isIframe}
<div class="flex justify-end link-underline">
<a href="https://mapcomplete.org" target="_blank">
<Tr t={Translations.t.general.poweredByMapComplete}/>
</a>
</div>
</If>
{:else}
<If condition={state.featureSwitches.featureSwitchBackToThemeOverview}>
<div class="link-underline m-2 mx-4 flex w-full">
<a class="flex w-fit items-center justify-end" href={Utils.HomepageLink()}>
<ChevronDoubleLeft class="h-4 w-4" />
<Tr t={Translations.t.general.backToIndex} />
</a>
</div>
</If>
{/if}
</div>

View file

@ -42,9 +42,10 @@
let knownImages = comparisonState.bindD((ct) => ct.knownImages)
let propertyKeysExternal = comparisonState.mapD((ct) => ct.propertyKeysExternal)
let hasDifferencesAtStart = comparisonState.mapD((ct) => ct.hasDifferencesAtStart)
let enableLogin= state.featureSwitches.featureSwitchEnableLogin
</script>
{#if !$sourceUrl}
{#if !$sourceUrl || !$enableLogin}
<!-- empty block -->
{:else if $externalData === undefined}
<Loading />

View file

@ -54,10 +54,9 @@ export class ImageCarousel extends Toggle {
)
super(
new SlideShow(uiElements).SetClass("w-full"),
new SlideShow(uiElements).SetClass("w-full block w-full my-4"),
undefined,
uiElements.map((els) => els.length > 0)
)
this.SetClass("block w-full")
}
}

View file

@ -25,11 +25,14 @@
const t = Translations.t.image.nearby
let expanded = false
let enableLogin = state.featureSwitches.featureSwitchEnableLogin
</script>
{#if enableLogin.data}
<AccordionSingle>
<span slot="header" class="p-2 text-base">
<Tr t={t.seeNearby} />
</span>
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable} {layer} />
</AccordionSingle>
{/if}

View file

@ -58,7 +58,7 @@
<LoginButton clss="small w-full" osmConnection={state.osmConnection} slot="not-logged-in">
<Tr t={Translations.t.image.pleaseLogin} />
</LoginButton>
<div class="flex flex-col">
<div class="flex flex-col my-4">
<UploadingImageCounter {state} {tags} />
{#each $errors as error}
<Tr t={error} cls="alert" />

View file

@ -932,9 +932,11 @@ export class ToTextualDescription {
public static createTextualDescriptionFor(
oh: opening_hours,
ranges: OpeningRange[][]
): Translation {
): Translation | undefined {
const t = Translations.t.general.opening_hours
if(!ranges){
return undefined
}
if (!ranges?.some((r) => r.length > 0)) {
// <!-- No changes to the opening hours in the next week; probably open 24/7, permanently closed, opening far in the future or unkown -->
if (oh.getNextChange() === undefined) {
@ -1029,9 +1031,9 @@ export class ToTextualDescription {
})
}
private static createRangesFor(ranges: OpeningRange[]): Translation {
private static createRangesFor(ranges: OpeningRange[]): Translation | undefined {
if (ranges.length === 0) {
// return undefined
return undefined
}
let tr = ToTextualDescription.createRangeFor(ranges[0])
for (let i = 1; i < ranges.length; i++) {

View file

@ -26,7 +26,6 @@ export default class OpeningHoursVisualization extends Toggle {
constructor(
tags: UIEventSource<Record<string, string>>,
state: { osmConnection?: OsmConnection },
key: string,
prefix = "",
postfix = ""
@ -56,7 +55,7 @@ export default class OpeningHoursVisualization extends Toggle {
)
Locale.language.mapD((lng) => {
console.debug("Setting OH description to", lng, textual)
vis.ConstructElement().ariaLabel = textual.textFor(lng)
vis.ConstructElement().ariaLabel = textual?.textFor(lng)
})
return vis
})
@ -75,17 +74,13 @@ export default class OpeningHoursVisualization extends Toggle {
ranges: OpeningRange[][],
lastMonday: Date
): BaseUIElement {
/* First, a small sanity check. The business might be permanently closed, 24/7 opened or something other special
* So, we have to handle the case that ranges is completely empty*/
if (ranges.filter((range) => range.length > 0).length === 0) {
return OpeningHoursVisualization.ShowSpecialCase(oh).SetClass(
"p-4 rounded-full block bg-gray-200"
)
// First, a small sanity check. The business might be permanently closed, 24/7 opened or something other special
if (ranges.some((range) => range.length > 0)) {
// The normal case: we have items for the coming days
return OpeningHoursVisualization.ConstructVizTable(oh, ranges, lastMonday)
}
/** With all the edge cases handled, we can actually construct the table! **/
return OpeningHoursVisualization.ConstructVizTable(oh, ranges, lastMonday)
// The special case that range is completely empty
return OpeningHoursVisualization.ShowSpecialCase(oh)
}
private static ConstructVizTable(
@ -308,6 +303,6 @@ export default class OpeningHoursVisualization extends Toggle {
opensAtDate.getHours(),
opensAtDate.getMinutes()
)}`
return Translations.t.general.opening_hours.closed_until.Subs({ date: willOpenAt })
return Translations.t.general.opening_hours.closed_until.Subs({ date: opensAtDate.toLocaleString() })
}
}

View file

@ -47,12 +47,14 @@
async function closeNote() {
await state.osmConnection.closeNote(id, txt.data)
tags.data["closed_at"] = new Date().toISOString()
NoteCommentElement.addCommentTo(txt.data, tags, state)
tags.ping()
}
async function reopenNote() {
await state.osmConnection.reopenNote(id, txt.data)
tags.data["closed_at"] = undefined
NoteCommentElement.addCommentTo(txt.data, tags, state)
tags.ping()
}
</script>

View file

@ -10,6 +10,8 @@ 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"
@ -62,10 +64,7 @@ export class CloseNoteButton implements SpecialVisualization {
zoomButton: string
} = <any>Utils.ParseVisArgs(this.args, args)
let icon: BaseUIElement = new SvelteUIElement(Checkmark)
if (params.icon !== "checkmark.svg" && (args[2] ?? "") !== "") {
icon = new Img(args[1])
}
let icon: BaseUIElement = new SvelteUIElement(Icon, {icon: params.icon ?? "checkmark.svg"})
let textToShow = t.closeNote
if ((params.text ?? "") !== "") {
textToShow = Translations.T(args[0])
@ -75,7 +74,9 @@ export class CloseNoteButton implements SpecialVisualization {
const isClosed = tags.map((tags) => (tags["closed_at"] ?? "") !== "")
closeButton.onClick(() => {
const id = tags.data[args[2] ?? "id"]
state.osmConnection.closeNote(id, args[3])?.then((_) => {
const text = args[3]
state.osmConnection.closeNote(id, text)?.then((_) => {
NoteCommentElement.addCommentTo(text, tags, state)
tags.data["closed_at"] = new Date().toISOString()
tags.ping()
})

View file

@ -19,6 +19,7 @@
import AddSmall from "../../../assets/svg/AddSmall.svelte"
import type { OsmTags } from "../../../Models/OsmFeature"
import Loading from "../../Base/Loading.svelte"
import NextButton from "../../Base/NextButton.svelte"
export let coordinate: UIEventSource<{ lon: number; lat: number }>
export let state: SpecialVisualizationState
@ -109,7 +110,7 @@
</SubtleButton>
</div>
{:else}
<form class="border-grey-500 rounded-sm border" on:submit|preventDefault={uploadNote}>
<form class="low-interaction rounded-sm p-2 flex flex-col" on:submit|preventDefault={uploadNote}>
<label class="neutral-label">
<Tr t={Translations.t.notes.createNoteIntro} />
<div class="w-full p-1">
@ -133,10 +134,10 @@
</LoginToggle>
{#if $comment?.length >= 3}
<SubtleButton on:click={uploadNote}>
<NextButton on:click={uploadNote} clss="self-end primary">
<AddSmall slot="image" class="mr-4 h-8 w-8" />
<Tr slot="message" t={Translations.t.notes.createNote} />
</SubtleButton>
<Tr t={Translations.t.notes.createNote} />
</NextButton>
{:else}
<div class="alert">
<Tr t={Translations.t.notes.textNeeded} />

View file

@ -6,8 +6,7 @@ import Translations from "../../i18n/Translations"
import { Utils } from "../../../Utils"
import Img from "../../Base/Img"
import { SlideShow } from "../../Image/SlideShow"
import { Stores, UIEventSource } from "../../../Logic/UIEventSource"
import { OsmConnection } from "../../../Logic/Osm/OsmConnection"
import { Store, Stores, UIEventSource } from "../../../Logic/UIEventSource"
import { VariableUiElement } from "../../Base/VariableUIElement"
import { SpecialVisualizationState } from "../../SpecialVisualization"
import SvelteUIElement from "../../Base/SvelteUIElement"
@ -50,7 +49,7 @@ export default class NoteCommentElement extends Combine {
}
const userinfo = Stores.FromPromise(
Utils.downloadJsonCached(
Utils.downloadJsonCached<{user: { img: { href: string } }}>(
"https://api.openstreetmap.org/api/0.6/user/" + comment.uid,
24 * 60 * 60 * 1000
)
@ -114,10 +113,16 @@ export default class NoteCommentElement extends Combine {
}
}
/**
* Adds the comment to the _visualisation_ of the given note; doesn't _actually_ upload
* @param txt
* @param tags
* @param state
*/
public static addCommentTo(
txt: string,
tags: UIEventSource<any>,
state: { osmConnection: OsmConnection }
state: { osmConnection: {userDetails: Store<{ name: string, uid: number }>} }
) {
const comments: any[] = JSON.parse(tags.data["comments"])
const username = state.osmConnection.userDetails.data.name

View file

@ -94,6 +94,8 @@
let answered: number = 0
let skipped: number = 0
let loginEnabled = state.featureSwitches.featureSwitchEnableLogin
function skip(question: { id: string }, didAnswer: boolean = false) {
skippedQuestions.data.add(question.id) // Must use ID, the config object might be a copy of the original
skippedQuestions.ping()
@ -108,6 +110,7 @@
}
</script>
{#if $loginEnabled}
<div
bind:this={questionboxElem}
aria-live="polite"
@ -197,3 +200,4 @@
</div>
{/if}
</div>
{/if}

View file

@ -83,7 +83,7 @@
{config.id}
</span>
{/if}
{#if config.question && (!editingEnabled || $editingEnabled)}
{#if config.question}
{#if editMode}
<TagRenderingQuestion
{config}
@ -106,19 +106,19 @@
</TagRenderingQuestion>
{:else}
<div class="low-interaction flex items-center justify-between overflow-hidden rounded pl-2">
<TagRenderingAnswer id={answerId} {config} {tags} {selectedElement} {state} {layer} />
<EditButton
arialabel={config.editButtonAriaLabel}
ariaLabelledBy={answerId}
on:click={() => {
editMode = true
}}
/>
<TagRenderingAnswer id={answerId} {config} {tags} {selectedElement} {state} {layer} extraClasses="my-2"/>
{#if (!editingEnabled || $editingEnabled)}
<EditButton
arialabel={config.editButtonAriaLabel}
ariaLabelledBy={answerId}
on:click={() => {
editMode = true
}}
/>
{/if}
</div>
{/if}
{:else}
<div class="h-full w-full overflow-auto">
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
</div>
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
{/if}
</div>

View file

@ -794,7 +794,7 @@ export default class SpecialVisualizations {
"A normal opening hours table can be invoked with `{opening_hours_table()}`. A table for e.g. conditional access with opening hours can be `{opening_hours_table(access:conditional, no @ &LPARENS, &RPARENS)}`",
constr: (state, tagSource: UIEventSource<any>, args) => {
const [key, prefix, postfix] = args
return new OpeningHoursVisualization(tagSource, state, key, prefix, postfix)
return new OpeningHoursVisualization(tagSource, key, prefix, postfix)
},
},
{

View file

@ -1,4 +1,7 @@
<script lang="ts">
import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization"
</script>
<main />
<main >
<ToSvelte construct={new OpeningHoursVisualization()}
</main>

View file

@ -124,11 +124,11 @@
state.mapProperties.installCustomKeyboardHandler(viewport)
let canZoomIn = mapproperties.maxzoom.map(
(mz) => mapproperties.zoom.data < mz,
[mapproperties.zoom]
[mapproperties.zoom],
)
let canZoomOut = mapproperties.minzoom.map(
(mz) => mapproperties.zoom.data > mz,
[mapproperties.zoom]
[mapproperties.zoom],
)
function updateViewport() {
@ -165,7 +165,7 @@
onDestroy(
rasterLayer.addCallbackAndRunD((l) => {
rasterLayerName = l.properties.name
})
}),
)
let previewedImage = state.previewedImage
@ -196,7 +196,7 @@
let openMapButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
let openMenuButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
let openCurrentViewLayerButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(
undefined
undefined,
)
let _openNewElementButton: HTMLButtonElement
let openNewElementButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
@ -478,7 +478,7 @@
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
<span slot="close-button"><!-- Disable the close button --></span>
<TabbedGroup
condition1={state.featureSwitches.featureSwitchFilter}
condition1={state.featureSwitches.featureSwitchEnableExport}
tab={state.guistate.themeViewTabIndex}
>
<div slot="post-tablist">
@ -572,9 +572,14 @@
<div class="link-underline links-w-full m-2 flex flex-col gap-y-1" slot="content0">
<Tr t={Translations.t.general.aboutMapComplete.intro} />
<a class="flex" href={Utils.HomepageLink()}>
<Add class="h-6 w-6" />
<Tr t={Translations.t.general.backToIndex} />
{#if Utils.isIframe}
<Tr t={Translations.t.general.seeIndex} />
{:else}
<Tr t={Translations.t.general.backToIndex} />
{/if}
</a>
<a class="flex" href="https://github.com/pietervdvn/MapComplete/" target="_blank">