Port user settings

This commit is contained in:
Pieter Vander Vennet 2023-04-07 02:13:57 +02:00
parent 97aaa8e941
commit 8085079eff
30 changed files with 566 additions and 497 deletions

View file

@ -2,10 +2,17 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { OsmConnection } from "../Osm/OsmConnection"
import { MangroveIdentity } from "../Web/MangroveReviews"
import { Store, Stores, UIEventSource } from "../UIEventSource"
import Locale from "../../UI/i18n/Locale"
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"
import { FeatureSource } from "../FeatureSource/FeatureSource"
import { Feature } from "geojson"
import { Utils } from "../../Utils"
import translators from "../../assets/translators.json"
import codeContributors from "../../assets/contributors.json"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
import usersettings from "../../assets/generated/layers/usersettings.json"
import Locale from "../../UI/i18n/Locale"
import LinkToWeblate from "../../UI/Base/LinkToWeblate"
/**
* The part of the state which keeps track of user-related stuff, e.g. the OSM-connection,
@ -30,16 +37,30 @@ export default class UserRelatedState {
* The number of seconds that the GPS-locations are stored in memory.
* Time in seconds
*/
public gpsLocationHistoryRetentionTime = new UIEventSource(
public readonly gpsLocationHistoryRetentionTime = new UIEventSource(
7 * 24 * 60 * 60,
"gps_location_retention"
)
constructor(osmConnection: OsmConnection, availableLanguages?: string[]) {
/**
* Preferences as tags exposes many preferences and state properties as record.
* This is used to bridge the internal state with the usersettings.json layerconfig file
*/
public readonly preferencesAsTags: UIEventSource<Record<string, string>>
public static readonly usersettingsConfig = new LayerConfig(
<LayerConfigJson>usersettings,
"userinformationpanel"
)
constructor(
osmConnection: OsmConnection,
availableLanguages?: string[],
layout?: LayoutConfig
) {
this.osmConnection = osmConnection
{
const translationMode: UIEventSource<undefined | "true" | "false" | "mobile" | string> =
this.osmConnection.GetPreference("translation-mode")
this.osmConnection.GetPreference("translation-mode", "false")
translationMode.addCallbackAndRunD((mode) => {
mode = mode.toLowerCase()
if (mode === "true" || mode === "yes") {
@ -73,6 +94,8 @@ export default class UserRelatedState {
this.installedUserThemes = this.InitInstalledUserThemes()
this.homeLocation = this.initHomeLocation()
this.preferencesAsTags = this.initAmendedPrefs(layout)
}
public GetUnofficialTheme(id: string):
@ -211,4 +234,127 @@ export default class UserRelatedState {
})
return new StaticFeatureSource(feature)
}
/**
* Initialize the 'amended preferences'.
* This is inherently a dirty and chaotic method, as it shoves many properties into this EventSourcd
* */
private initAmendedPrefs(layout?: LayoutConfig): UIEventSource<Record<string, string>> {
const amendedPrefs = new UIEventSource<Record<string, string>>({
_theme: layout?.id,
_backend: this.osmConnection.Backend(),
})
const osmConnection = this.osmConnection
osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => {
for (const k in newPrefs) {
amendedPrefs.data[k] = newPrefs[k]
}
amendedPrefs.ping()
})
const usersettingsConfig = UserRelatedState.usersettingsConfig
const translationMode = osmConnection.GetPreference("translation-mode")
Locale.language.mapD(
(language) => {
amendedPrefs.data["_language"] = language
const trmode = translationMode.data
if ((trmode === "true" || trmode === "mobile") && layout !== undefined) {
const missing = layout.missingTranslations()
const total = missing.total
const untranslated = missing.untranslated.get(language) ?? []
const hasMissingTheme = untranslated.some((k) => k.startsWith("themes:"))
const missingLayers = Utils.Dedup(
untranslated
.filter((k) => k.startsWith("layers:"))
.map((k) => k.slice("layers:".length).split(".")[0])
)
const zenLinks: { link: string; id: string }[] = Utils.NoNull([
hasMissingTheme
? {
id: "theme:" + layout.id,
link: LinkToWeblate.hrefToWeblateZen(
language,
"themes",
layout.id
),
}
: undefined,
...missingLayers.map((id) => ({
id: "layer:" + id,
link: LinkToWeblate.hrefToWeblateZen(language, "layers", id),
})),
])
const untranslated_count = untranslated.length
amendedPrefs.data["_translation_total"] = "" + total
amendedPrefs.data["_translation_translated_count"] =
"" + (total - untranslated_count)
amendedPrefs.data["_translation_percentage"] =
"" + Math.floor((100 * (total - untranslated_count)) / total)
console.log("Setting zenLinks", zenLinks)
amendedPrefs.data["_translation_links"] = JSON.stringify(zenLinks)
}
amendedPrefs.ping()
},
[translationMode]
)
osmConnection.userDetails.addCallback((userDetails) => {
for (const k in userDetails) {
amendedPrefs.data["_" + k] = "" + userDetails[k]
}
for (const [name, code, _] of usersettingsConfig.calculatedTags) {
try {
let result = new Function("feat", "return " + code + ";")({
properties: amendedPrefs.data,
})
if (result !== undefined && result !== "" && result !== null) {
if (typeof result !== "string") {
result = JSON.stringify(result)
}
amendedPrefs.data[name] = result
}
} catch (e) {
console.error(
"Calculating a tag for userprofile-settings failed for variable",
name,
e
)
}
}
const simplifiedName = userDetails.name.toLowerCase().replace(/\s+/g, "")
const isTranslator = translators.contributors.find(
(c: { contributor: string; commits: number }) => {
const replaced = c.contributor.toLowerCase().replace(/\s+/g, "")
return replaced === simplifiedName
}
)
if (isTranslator) {
amendedPrefs.data["_translation_contributions"] = "" + isTranslator.commits
}
const isCodeContributor = codeContributors.contributors.find(
(c: { contributor: string; commits: number }) => {
const replaced = c.contributor.toLowerCase().replace(/\s+/g, "")
return replaced === simplifiedName
}
)
if (isCodeContributor) {
amendedPrefs.data["_code_contributions"] = "" + isCodeContributor.commits
}
amendedPrefs.ping()
})
amendedPrefs.addCallbackD((tags) => {
for (const key in tags) {
if (key.startsWith("_")) {
continue
}
this.osmConnection.GetPreference(key, undefined, { prefix: "" }).setData(tags[key])
}
})
return amendedPrefs
}
}

View file

@ -123,7 +123,6 @@ export default class FilteredLayer {
} else {
properties = fieldstate
}
console.log("Building tagsspec with properties", properties)
const missingKeys = option.fields
.map((f) => f.name)
.filter((key) => properties[key] === undefined)
@ -182,7 +181,6 @@ export default class FilteredLayer {
// We calculate the fields
const fieldProperties = FilteredLayer.stringToFieldProperties(<string>state.data)
const asTags = FilteredLayer.fieldsToTags(filter.options[0], fieldProperties)
console.log("Current field properties:", state.data, fieldProperties, asTags)
if (asTags) {
needed.push(asTags)
}

View file

@ -21,6 +21,7 @@ export class MenuState {
public readonly highlightedLayerInFilters: UIEventSource<string> = new UIEventSource<string>(
undefined
)
public highlightedUserSetting: UIEventSource<string> = new UIEventSource<string>(undefined)
constructor() {
this.themeViewTabIndex = new UIEventSource(0)
this.themeViewTab = this.themeViewTabIndex.sync(
@ -57,6 +58,12 @@ export class MenuState {
}
}
public openUsersettings(highlightTagRendering?: string) {
this.menuIsOpened.setData(true)
this.menuViewTab.setData("settings")
this.highlightedUserSetting.setData(highlightTagRendering)
}
public closeAll() {
this.menuIsOpened.setData(false)
this.themeIsOpened.setData(false)

View file

@ -805,7 +805,13 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
const param = special[arg.name]
if (param === undefined) {
errors.push(
`At ${context}: Obligated parameter '${arg.name}' in special rendering of type ${vis.funcName} not found.\n${arg.doc}`
`At ${context}: Obligated parameter '${
arg.name
}' in special rendering of type ${
vis.funcName
} not found.\n The full special rendering specification is: '${JSON.stringify(
input
)}'\n ${arg.name}: ${arg.doc}`
)
}
}

View file

@ -101,7 +101,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
),
osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data,
})
this.userRelatedState = new UserRelatedState(this.osmConnection, layout?.language)
this.userRelatedState = new UserRelatedState(this.osmConnection, layout?.language, layout)
this.selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element")
this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer")
this.geolocation = new GeoLocationHandler(

View file

@ -17,6 +17,7 @@
const offlineModes: Partial<Record<OsmServiceState, Translation>> = {
offline: t.loginFailedOfflineMode,
unreachable: t.loginFailedUnreachableMode,
unknown: t.loginFailedUnreachableMode,
readonly: t.loginFailedReadonlyMode
};
const apiState = state.osmConnection.apiIsOnline;

View file

@ -7,15 +7,17 @@
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
import { onDestroy } from "svelte";
export let selectedElement: Feature;
export let state: SpecialVisualizationState;
export let layer: LayerConfig;
export let selectedElement: Feature;
export let tags: UIEventSource<Record<string, string>>;
export let highlightedRendering: UIEventSource<string> = undefined;
let _tags: Record<string, string>;
onDestroy(tags.addCallbackAndRun(tags => {
_tags = tags;
}));
export let state: SpecialVisualizationState;
</script>
<div>
@ -40,7 +42,7 @@
{#each layer.tagRenderings as config (config.id)}
{#if config.condition === undefined || config.condition.matchesProperties(_tags)}
{#if config.IsKnown(_tags)}
<TagRenderingEditable {tags} {config} {state} {selectedElement} {layer}></TagRenderingEditable>
<TagRenderingEditable {tags} {config} {state} {selectedElement} {layer} {highlightedRendering}></TagRenderingEditable>
{/if}
{/if}
{/each}

View file

@ -1,4 +1,3 @@
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
import Translations from "../i18n/Translations"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Combine from "../Base/Combine"
@ -8,25 +7,13 @@ import { VariableUiElement } from "../Base/VariableUIElement"
import Img from "../Base/Img"
import { FixedUiElement } from "../Base/FixedUiElement"
import Link from "../Base/Link"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { UIEventSource } from "../../Logic/UIEventSource"
import Loc from "../../Models/Loc"
import BaseUIElement from "../BaseUIElement"
import Showdown from "showdown"
import LanguagePicker from "../LanguagePicker"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import Constants from "../../Models/Constants"
import EditableTagRendering from "../Popup/EditableTagRendering"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import { SaveButton } from "../Popup/SaveButton"
import { TagUtils } from "../../Logic/Tags/TagUtils"
import usersettings from "../../assets/generated/layers/usersettings.json"
import { LoginToggle } from "../Popup/LoginButton"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import translators from "../../assets/translators.json"
import codeContributors from "../../assets/contributors.json"
import Locale from "../i18n/Locale"
import { Utils } from "../../Utils"
import LinkToWeblate from "../Base/LinkToWeblate"
export class ImportViewerLinks extends VariableUiElement {
constructor(osmConnection: OsmConnection) {
@ -48,51 +35,6 @@ export class ImportViewerLinks extends VariableUiElement {
}
}
class SingleUserSettingsPanel extends EditableTagRendering {
constructor(
config: TagRenderingConfig,
osmConnection: OsmConnection,
amendedPrefs: UIEventSource<any>,
userInfoFocusedQuestion?: UIEventSource<string>
) {
const editMode = new UIEventSource(false)
// Isolate the preferences. They'll be updated explicitely later on anyway
super(
amendedPrefs,
config,
[],
{ osmConnection },
{
answerElementClasses: "p-2",
editMode,
createSaveButton: (store) =>
new SaveButton(amendedPrefs, osmConnection).onClick(() => {
const selection = TagUtils.FlattenMultiAnswer(
TagUtils.FlattenAnd(store.data, amendedPrefs.data)
).asChange(amendedPrefs.data)
for (const kv of selection) {
if (kv.k.startsWith("_")) {
continue
}
osmConnection.GetPreference(kv.k, "", { prefix: "" }).setData(kv.v)
}
editMode.setData(false)
}),
}
)
const self = this
this.SetClass("rounded-xl")
userInfoFocusedQuestion.addCallbackAndRun((selected) => {
if (config.id !== selected) {
self.RemoveClass("glowing-shadow")
} else {
self.SetClass("glowing-shadow")
}
})
}
}
class UserInformationMainPanel extends VariableUiElement {
private readonly settings: UIEventSource<Record<string, BaseUIElement>>
private readonly userInfoFocusedQuestion?: UIEventSource<string>
@ -104,210 +46,9 @@ class UserInformationMainPanel extends VariableUiElement {
isOpened: UIEventSource<boolean>,
userInfoFocusedQuestion?: UIEventSource<string>
) {
const t = Translations.t.userinfo
const imgSize = "h-6 w-6"
const ud = osmConnection.userDetails
const settings = new UIEventSource<Record<string, BaseUIElement>>({})
const usersettingsConfig = new LayerConfig(usersettings, "userinformationpanel")
const amendedPrefs = new UIEventSource<any>({ _theme: layout?.id })
osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => {
for (const k in newPrefs) {
amendedPrefs.data[k] = newPrefs[k]
}
amendedPrefs.ping()
})
const translationMode = osmConnection.GetPreference("translation-mode")
Locale.language.mapD(
(language) => {
amendedPrefs.data["_language"] = language
const trmode = translationMode.data
if (trmode === "true" || trmode === "mobile") {
const missing = layout.missingTranslations()
const total = missing.total
const untranslated = missing.untranslated.get(language) ?? []
const hasMissingTheme = untranslated.some((k) => k.startsWith("themes:"))
const missingLayers = Utils.Dedup(
untranslated
.filter((k) => k.startsWith("layers:"))
.map((k) => k.slice("layers:".length).split(".")[0])
)
const zenLinks: { link: string; id: string }[] = Utils.NoNull([
hasMissingTheme
? {
id: "theme:" + layout.id,
link: LinkToWeblate.hrefToWeblateZen(
language,
"themes",
layout.id
),
}
: undefined,
...missingLayers.map((id) => ({
id: "layer:" + id,
link: LinkToWeblate.hrefToWeblateZen(language, "layers", id),
})),
])
const untranslated_count = untranslated.length
amendedPrefs.data["_translation_total"] = "" + total
amendedPrefs.data["_translation_translated_count"] =
"" + (total - untranslated_count)
amendedPrefs.data["_translation_percentage"] =
"" + Math.floor((100 * (total - untranslated_count)) / total)
console.log("Setting zenLinks", zenLinks)
amendedPrefs.data["_translation_links"] = JSON.stringify(zenLinks)
}
amendedPrefs.ping()
},
[translationMode]
)
osmConnection.userDetails.addCallback((userDetails) => {
for (const k in userDetails) {
amendedPrefs.data["_" + k] = "" + userDetails[k]
}
for (const [name, code, _] of usersettingsConfig.calculatedTags) {
try {
let result = new Function("feat", "return " + code + ";")({
properties: amendedPrefs.data,
})
if (result !== undefined && result !== "" && result !== null) {
if (typeof result !== "string") {
result = JSON.stringify(result)
}
amendedPrefs.data[name] = result
}
} catch (e) {
console.error(
"Calculating a tag for userprofile-settings failed for variable",
name,
e
)
}
}
const simplifiedName = userDetails.name.toLowerCase().replace(/\s+/g, "")
const isTranslator = translators.contributors.find(
(c: { contributor: string; commits: number }) => {
const replaced = c.contributor.toLowerCase().replace(/\s+/g, "")
return replaced === simplifiedName
}
)
if (isTranslator) {
amendedPrefs.data["_translation_contributions"] = "" + isTranslator.commits
}
const isCodeContributor = codeContributors.contributors.find(
(c: { contributor: string; commits: number }) => {
const replaced = c.contributor.toLowerCase().replace(/\s+/g, "")
return replaced === simplifiedName
}
)
if (isCodeContributor) {
amendedPrefs.data["_code_contributions"] = "" + isCodeContributor.commits
}
amendedPrefs.ping()
})
super(
ud.map((ud) => {
let img: BaseUIElement = Svg.person_ui().SetClass("block")
if (ud.img !== undefined) {
img = new Img(ud.img)
}
img.SetClass("rounded-full h-12 w-12 m-4")
let description: BaseUIElement = undefined
const editLink = osmConnection.Backend() + "/profile/edit"
if (ud.description) {
const editButton = new Link(
Svg.pencil_svg().SetClass("h-4 w-4"),
editLink,
true
).SetClass(
"absolute block bg-subtle rounded-full p-2 bottom-2 right-2 w-min self-end"
)
const htmlString = new Showdown.Converter()
.makeHtml(ud.description)
.replace(/&gt;/g, ">")
.replace(/&lt;/g, "<")
description = new Combine([
new FixedUiElement(htmlString).SetClass("link-underline"),
editButton,
]).SetClass("relative w-full m-2")
} else {
description = new Combine([
t.noDescription,
new SubtleButton(Svg.pencil_svg(), t.noDescriptionCallToAction, {
imgSize,
url: editLink,
newTab: true,
}),
]).SetClass("w-full m-2")
}
let panToHome: BaseUIElement
if (ud.home) {
panToHome = new SubtleButton(Svg.home_svg(), t.moveToHome, {
imgSize,
}).onClick(() => {
const home = ud?.home
if (home === undefined) {
return
}
locationControl.setData({ ...home, zoom: 16 })
isOpened.setData(false)
})
}
const settingElements = []
for (const c of usersettingsConfig.tagRenderings) {
const settingsPanel = new SingleUserSettingsPanel(
c,
osmConnection,
amendedPrefs,
userInfoFocusedQuestion
).SetClass("block my-4")
settings.data[c.id] = settingsPanel
settingElements.push(settingsPanel)
}
settings.ping()
return new Combine([
new Combine([img, description]).SetClass("flex border border-black rounded-md"),
new LanguagePicker(
layout.language,
Translations.t.general.pickLanguage.Clone()
),
...settingElements,
new SubtleButton(
Svg.envelope_svg(),
new Combine([
t.gotoInbox,
ud.unreadMessages == 0
? undefined
: t.newMessages.SetClass("alert block"),
]),
{ imgSize, url: `${ud.backend}/messages/inbox`, newTab: true }
),
new SubtleButton(Svg.gear_svg(), t.gotoSettings, {
imgSize,
url: `${ud.backend}/user/${encodeURIComponent(ud.name)}/account`,
newTab: true,
}),
panToHome,
new ImportViewerLinks(osmConnection),
new SubtleButton(Svg.logout_svg(), Translations.t.general.logout, {
imgSize,
}).onClick(() => {
osmConnection.LogOut()
}),
])
})
)
this.SetClass("flex flex-col")
super()
this.settings = settings
this.userInfoFocusedQuestion = userInfoFocusedQuestion
const self = this
@ -325,50 +66,3 @@ class UserInformationMainPanel extends VariableUiElement {
this.settings.data[focusedId]?.ScrollIntoView()
}
}
export default class UserInformationPanel extends ScrollableFullScreen {
private readonly userPanel: UserInformationMainPanel
constructor(
state: {
readonly layoutToUse: LayoutConfig
readonly osmConnection: OsmConnection
readonly locationControl: UIEventSource<Loc>
readonly featureSwitchUserbadge: Store<boolean>
},
options?: {
isOpened?: UIEventSource<boolean>
userInfoFocusedQuestion?: UIEventSource<string>
}
) {
const isOpened = options?.isOpened ?? new UIEventSource<boolean>(false)
const userPanel = new UserInformationMainPanel(
state.osmConnection,
state.locationControl,
state.layoutToUse,
isOpened,
options?.userInfoFocusedQuestion
)
super(
() => {
return new VariableUiElement(
state.osmConnection.userDetails.map((ud) => {
if (ud.loggedIn === false) {
return Translations.t.userinfo.titleNotLoggedIn
}
return Translations.t.userinfo.welcome.Subs(ud)
})
)
},
() => new LoginToggle(userPanel, Translations.t.general.getStartedLogin, state),
"userinfo",
isOpened
)
this.userPanel = userPanel
}
Activate() {
super.Activate()
this.userPanel?.focusOnSelectedQuestion()
}
}

View file

@ -0,0 +1,48 @@
<script lang="ts">
import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection";
import { UIEventSource } from "../../Logic/UIEventSource";
import { PencilAltIcon, UserCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import { onDestroy } from "svelte";
import Showdown from "showdown";
import FromHtml from "../Base/FromHtml.svelte";
import Tr from "../Base/Tr.svelte";
import Translations from "../i18n/Translations.js";
/**
* This panel shows information about the logged-in user, showing account name, profile pick, description and an edit-button
*/
export let osmConnection: OsmConnection;
let userdetails: UIEventSource<UserDetails> = osmConnection.userDetails;
let description: string;
onDestroy(userdetails.addCallbackAndRunD(userdetails => {
description = new Showdown.Converter()
.makeHtml(userdetails.description)
?.replace(/&gt;/g, ">")
?.replace(/&lt;/g, "<");
}));
</script>
<div class="flex border border-gray-300 border-dashed m-1 p-1 rounded-md link-underline">
{#if $userdetails.img}
<img src={$userdetails.img} class="rounded-full w-12 h-12 m-4">
{:else}
<UserCircleIcon class="w-12 h-12" />
{/if}
<div class="flex flex-col relative">
<h3>{$userdetails.name}</h3>
{#if description}
<FromHtml src={description} />
<a href={osmConnection.Backend() + "/profile/edit"} target="_blank">
<PencilAltIcon class="p-2 w-6 h-6 subtle-background rounded-full absolute right-1 bottom-1" />
</a>
{:else}
<Tr t={Translations.t. userinfo.noDescription} />
<a href={osmConnection.Backend() + "/profile/edit"} target="_blank" class="flex subtle-background items-center">
<PencilAltIcon slot="image" class="p-2 w-8 h-8" />
<Tr slot="message" t={Translations.t.userinfo.noDescriptionCallToAction} />
</a>
{/if}
</div>
</div>

View file

@ -158,30 +158,6 @@ export default class DefaultGUI {
const self = this
const userInfoMapControl = Toggle.If(state.featureSwitchUserbadge, () => {
new UserInformationPanel(state, {
isOpened: guiState.userInfoIsOpened,
userInfoFocusedQuestion: guiState.userInfoFocusedQuestion,
})
const mapControl = new MapControlButton(
new VariableUiElement(
state.osmConnection.userDetails.map((ud) => {
if (ud?.img === undefined) {
return Svg.person_ui().SetClass("mt-1 block")
}
return new Img(ud?.img)
})
).SetClass("block rounded-full overflow-hidden"),
{
dontStyle: true,
}
).onClick(() => {
self.guiState.userInfoIsOpened.setData(true)
})
return new LoginToggle(mapControl, Translations.t.general.loginWithOpenStreetMap, state)
})
const extraLink = Toggle.If(
state.featureSwitchExtraLinkEnabled,
() => new ExtraLinkButton(state, state.layoutToUse.extraLink)
@ -200,7 +176,7 @@ export default class DefaultGUI {
const copyright = new MapControlButton(Svg.copyright_svg()).onClick(() =>
guiState.copyrightViewIsOpened.setData(true)
)
new Combine([welcomeMessageMapControl, userInfoMapControl, copyright, extraLink])
new Combine([welcomeMessageMapControl, copyright, extraLink])
.SetClass("flex flex-col")
.AttachTo("top-left")

View file

@ -15,32 +15,51 @@
export let tags: UIEventSource<Record<string, string>>;
export let selectedElement: Feature;
export let state: SpecialVisualizationState;
export let layer: LayerConfig
export let layer: LayerConfig;
export let showQuestionIfUnknown : boolean= false
let editMode = false
export let highlightedRendering: UIEventSource<string> = undefined;
export let showQuestionIfUnknown: boolean = false;
let editMode = false;
onDestroy(tags.addCallbackAndRunD(tags => {
editMode = showQuestionIfUnknown && !config.IsKnown(tags)
}))
editMode = showQuestionIfUnknown && !config.IsKnown(tags);
}));
let htmlElem: HTMLElement;
if (highlightedRendering) {
onDestroy(highlightedRendering.addCallbackAndRun(highlighted => {
console.log("Highlighted rendering is", highlighted)
if(htmlElem === undefined){
return
}
if (config.id === highlighted) {
htmlElem.classList.add("glowing-shadow");
} else {
htmlElem.classList.remove("glowing-shadow");
}
}));
}
</script>
{#if config.question}
{#if editMode}
<TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer} >
<button slot="cancel" on:click={() => {editMode = false}}>
<Tr t={Translations.t.general.cancel}/>
</button>
</TagRenderingQuestion>
{:else}
<div class="flex justify-between">
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
<button on:click={() => {editMode = true}} class="w-6 h-6 rounded-full subtle-background p-1">
<PencilAltIcon></PencilAltIcon>
</button>
</div>
<div bind:this={htmlElem}>
{#if config.question}
{#if editMode}
<TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer}>
<button slot="cancel" on:click={() => {editMode = false}}>
<Tr t={Translations.t.general.cancel} />
</button>
</TagRenderingQuestion>
{:else}
<div class="flex justify-between">
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
<button on:click={() => {editMode = true}} class="shrink-0 w-6 h-6 rounded-full subtle-background p-1">
<PencilAltIcon></PencilAltIcon>
</button>
</div>
{/if}
{:else }
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
{/if}
{:else }
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
{/if}
</div>

View file

@ -10,7 +10,6 @@
import { TagsFilter } from "../../../Logic/Tags/TagsFilter";
import FreeformInput from "./FreeformInput.svelte";
import Translations from "../../i18n/Translations.js";
import FromHtml from "../../Base/FromHtml.svelte";
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction";
import { createEventDispatcher } from "svelte";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
@ -63,6 +62,24 @@
}>();
function onSave() {
if (layer.source === null) {
/**
* This is a special, priviliged layer.
* We simply apply the tags onto the records
*/
const kv = selectedTags.asChange(tags.data);
for (const { k, v } of kv) {
if (v === undefined) {
delete tags.data[k];
} else {
tags.data[k] = v;
}
}
tags.ping();
return;
}
dispatch("saved", { config, applied: selectedTags });
const change = new ChangeTagAction(
tags.data.id,
@ -76,6 +93,7 @@
freeformInput.setData(undefined);
selectedMapping = 0;
selectedTags = undefined;
change.CreateChangeDescriptions().then(changes =>
state.changes.applyChanges(changes)
).catch(console.error);
@ -93,12 +111,14 @@
</span>
<span class="alert">{config.id}</span>
</div>
<SpecialTranslation slot="else" t={config.question} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation>
<SpecialTranslation slot="else" t={config.question} {tags} {state} {layer}
feature={selectedElement}></SpecialTranslation>
</If>
{#if config.questionhint}
<div class="subtle">
<SpecialTranslation t={config.questionhint} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation>
<SpecialTranslation t={config.questionhint} {tags} {state} {layer}
feature={selectedElement}></SpecialTranslation>
</div>
{/if}
@ -152,7 +172,7 @@
</div>
{/if}
<TagHint osmConnection={state.osmConnection} tags={selectedTags}></TagHint>
<TagHint osmConnection={state.osmConnection} tags={selectedTags}></TagHint>
<div>
<!-- TagRenderingQuestion-buttons -->
<slot name="cancel"></slot>
@ -163,7 +183,7 @@
{:else }
<div class="w-6 h-6">
<!-- Invalid value; show an inactive button or something like that-->
<ExclamationIcon></ExclamationIcon>
<ExclamationIcon></ExclamationIcon>
</div>
{/if}
</div>

View file

@ -56,16 +56,27 @@ import Maproulette from "../Logic/Maproulette"
import SvelteUIElement from "./Base/SvelteUIElement"
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import QuestionViz from "./Popup/QuestionViz"
import SimpleAddUI from "./BigComponents/SimpleAddUI"
import { Feature } from "geojson"
import { GeoOperations } from "../Logic/GeoOperations"
import CreateNewNote from "./Popup/CreateNewNote.svelte"
import { svelte } from "@sveltejs/vite-plugin-svelte"
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.svelte"
import UserProfile from "./BigComponents/UserProfile.svelte"
import LanguagePicker from "./LanguagePicker"
import Link from "./Base/Link"
export default class SpecialVisualizations {
public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList()
static undoEncoding(str: string) {
return str
.trim()
.replace(/&LPARENS/g, "(")
.replace(/&RPARENS/g, ")")
.replace(/&LBRACE/g, "{")
.replace(/&RBRACE/g, "}")
.replace(/&COMMA/g, ",")
}
/**
*
* For a given string, returns a specification what parts are fixed and what parts are special renderings.
@ -115,15 +126,7 @@ export default class SpecialVisualizations {
)
const args = knownSpecial.args.map((arg) => arg.defaultValue ?? "")
if (argument.length > 0) {
const realArgs = argument.split(",").map((str) =>
str
.trim()
.replace(/&LPARENS/g, "(")
.replace(/&RPARENS/g, ")")
.replace(/&LBRACE/g, "{")
.replace(/&RBRACE/g, "}")
.replace(/&COMMA/g, ",")
)
const realArgs = argument.split(",").map((str) => this.undoEncoding(str))
for (let i = 0; i < realArgs.length; i++) {
if (args.length <= i) {
args.push(realArgs[i])
@ -273,6 +276,39 @@ export default class SpecialVisualizations {
})
},
},
{
funcName: "user_profile",
args: [],
docs: "A component showing information about the currently logged in user (username, profile description, profile picture + link to edit them). Mostly meant to be used in the 'user-settings'",
constr(state: SpecialVisualizationState): BaseUIElement {
return new SvelteUIElement(UserProfile, {
osmConnection: state.osmConnection,
})
},
},
{
funcName: "language_picker",
args: [],
docs: "A component to set the language of the user interface",
constr(state: SpecialVisualizationState): BaseUIElement {
return new LanguagePicker(
state.layout.language,
Translations.t.general.pickLanguage.Clone()
)
},
},
{
funcName: "logout",
args: [],
docs: "Shows a button where the user can log out",
constr(state: SpecialVisualizationState): BaseUIElement {
return new SubtleButton(Svg.logout_ui(), Translations.t.general.logout, {
imgSize: "w-6 h-6",
}).onClick(() => {
state.osmConnection.LogOut()
})
},
},
new HistogramViz(),
new StealViz(),
new MinimapViz(),
@ -717,7 +753,7 @@ export default class SpecialVisualizations {
docs: "Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'",
example:
"`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`.",
constr: (state, tagsSource, args, feature) =>
constr: (state, tagsSource) =>
new VariableUiElement(
tagsSource.map((tags) => {
const layer = state.layout.getMatchingLayer(tags)
@ -933,6 +969,40 @@ export default class SpecialVisualizations {
)
},
},
{
funcName: "link",
docs: "Construct a link. By using the 'special' visualisation notation, translation should be easier",
args: [
{
name: "text",
doc: "Text to be shown",
required: true,
},
{
name: "href",
doc: "The URL to link to",
required: true,
},
],
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature
): BaseUIElement {
const [text, href] = args
return new VariableUiElement(
tagSource.map(
(tags) =>
new Link(
Utils.SubstituteKeys(text, tags),
Utils.SubstituteKeys(href, tags),
true
)
)
)
},
},
{
funcName: "multi",
docs: "Given an embedded tagRendering (read only) and a key, will read the keyname as a JSON-list. Every element of this list will be considered as tags and rendered with the tagRendering",

View file

@ -87,7 +87,7 @@ export class SubstitutedTranslation extends VariableUiElement {
tagsSource.data.id
)
return viz.func
.constr(state, tagsSource, proto.args, feature, undefined)
.constr(state, tagsSource, proto.args.map(t => SpecialVisualizations.undoEncoding(t)), feature, undefined)
?.SetStyle(proto.style)
} catch (e) {
console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e)

View file

@ -2,13 +2,12 @@
import { Store, UIEventSource } from "../Logic/UIEventSource";
import { Map as MlMap } from "maplibre-gl";
import MaplibreMap from "./Map/MaplibreMap.svelte";
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
import MapControlButton from "./Base/MapControlButton.svelte";
import ToSvelte from "./Base/ToSvelte.svelte";
import Svg from "../Svg";
import If from "./Base/If.svelte";
import { GeolocationControl } from "./BigComponents/GeolocationControl.js";
import { GeolocationControl } from "./BigComponents/GeolocationControl";
import type { Feature } from "geojson";
import SelectedElementView from "./BigComponents/SelectedElementView.svelte";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
@ -17,19 +16,21 @@
import ThemeViewState from "../Models/ThemeViewState";
import type { MapProperties } from "../Models/MapProperties";
import Geosearch from "./BigComponents/Geosearch.svelte";
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui";
import Translations from "./i18n/Translations";
import { CogIcon, MenuIcon, EyeIcon } from "@rgossiaux/svelte-heroicons/solid";
import { CogIcon, EyeIcon, MenuIcon } from "@rgossiaux/svelte-heroicons/solid";
import Tr from "./Base/Tr.svelte";
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
import FloatOver from "./Base/FloatOver.svelte";
import PrivacyPolicy from "./BigComponents/PrivacyPolicy.js";
import { Utils } from "../Utils.js";
import PrivacyPolicy from "./BigComponents/PrivacyPolicy";
import { Utils } from "../Utils";
import Constants from "../Models/Constants";
import TabbedGroup from "./Base/TabbedGroup.svelte";
import UserRelatedState from "../Logic/State/UserRelatedState";
import LoginToggle from "./Base/LoginToggle.svelte";
import LoginButton from "./Base/LoginButton.svelte";
export let layout: LayoutConfig;
const state = new ThemeViewState(layout);
export let state: ThemeViewState;
let layout = state.layout;
let selectedElementTags: Store<UIEventSource<Record<string, string>>> =
state.selectedElement.mapD((f) => {
@ -43,6 +44,7 @@
let mapproperties: MapProperties = state.mapProperties;
let featureSwitches: FeatureSwitchState = state.featureSwitches;
let availableLayers = state.availableLayers;
let userdetails = state.osmConnection.userDetails;
</script>
@ -97,7 +99,7 @@
{#if $selectedElement !== undefined && $selectedLayer !== undefined}
<FloatOver on:close={() => {selectedElement.setData(undefined)}}>
<SelectedElementView layer={$selectedLayer} selectedElement={$selectedElement}
tags={$selectedElementTags} state={state}></SelectedElementView>
tags={$selectedElementTags} state={state} />
</FloatOver>
{/if}
@ -106,7 +108,7 @@
<!-- Theme page -->
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
<TabbedGroup tab={state.guistate.themeViewTabIndex}>
<Tr slot="title0" t={layout.title} />
<Tr slot="title0" t={layout.title} />
<div slot="content0">
@ -130,17 +132,18 @@
</div>
</div>
<div slot="title1" class="flex">
<div class="flex" slot="title1">
<If condition={state.featureSwitches.featureSwitchFilter}>
<img class="w-4 h-4" src="./assets/svg/filter.svg">
<Tr t={Translations.t.general.menu.filter} />
<img class="w-4 h-4" src="./assets/svg/filter.svg">
<Tr t={Translations.t.general.menu.filter} />
</If>
</div>
<div slot="content1" class="flex flex-col">
<div class="flex flex-col" slot="content1">
{#each layout.layers as layer}
<Filterview zoomlevel={state.mapProperties.zoom} filteredLayer={state.layerState.filteredLayers.get(layer.id)} highlightedLayer={state.guistate.highlightedLayerInFilters}></Filterview>
<Filterview zoomlevel={state.mapProperties.zoom} filteredLayer={state.layerState.filteredLayers.get(layer.id)}
highlightedLayer={state.guistate.highlightedLayerInFilters}></Filterview>
{/each}
<If condition={state.featureSwitches.featureSwitchBackgroundSelection}>
<RasterLayerPicker {availableLayers} value={mapproperties.rasterLayer}></RasterLayerPicker>
@ -154,50 +157,56 @@
<If condition={state.guistate.menuIsOpened}>
<!-- Menu page -->
<FloatOver on:close={() => state.guistate.menuIsOpened.setData(false)}>
<TabGroup on:change={(e) => {state.guistate.menuViewTabIndex.setData(e.detail)} }>
<TabList>
<Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}>
<div class="flex">
<Tr t={Translations.t.general.aboutMapcompleteTitle}></Tr>
</div>
</Tab>
<Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}>
<div class="flex">
<CogIcon class="w-6 h-6"/>
Settings
</div>
</Tab>
<Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}>
<div class="flex">
<img class="w-6" src="./assets/svg/community.svg">
Get in touch with others
</div>
</Tab>
<Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}>
<div class="flex">
<EyeIcon class="w-6"/>
<Tr t={Translations.t.privacy.title}></Tr>
</div>
</Tab>
</TabList>
<TabPanels >
<TabPanel class="flex flex-col">
<Tr t={Translations.t.general.aboutMapcomplete.Subs({
<TabbedGroup tab={state.guistate.menuViewTabIndex}>
<div class="flex" slot="title0">
<Tr t={Translations.t.general.aboutMapcompleteTitle}></Tr>
</div>
<div class="flex flex-col" slot="content0">
<Tr t={Translations.t.general.aboutMapcomplete.Subs({
osmcha_link: Utils.OsmChaLinkFor(7),
})}></Tr>
{Constants.vNumber}
</TabPanel>
<TabPanel>User settings</TabPanel>
<TabPanel>
<CommunityIndexView location={state.mapProperties.location}></CommunityIndexView>
{Constants.vNumber}
</div>
</TabPanel>
<TabPanel>
<ToSvelte construct={() => new PrivacyPolicy()}></ToSvelte>
</TabPanel>
</TabPanels>
</TabGroup>
<If condition={state.osmConnection.isLoggedIn} slot="title1">
<div class="flex">
<CogIcon class="w-6 h-6" />
<Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})} />
</div>
</If>
<div slot="content1">
<!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
<LoginToggle {state}>
<div slot="not-logged-in">
<Tr class="alert" t={Translations.t.userinfo.notLoggedIn}/>
<LoginButton osmConnection={state.osmConnection}></LoginButton>
</div>
<SelectedElementView
layer={UserRelatedState.usersettingsConfig}
selectedElement={({
type:"Feature",properties: {}, geometry: {type:"Point", coordinates: [0,0]}
})} {state}
tags={state.userRelatedState.preferencesAsTags}
highlightedRendering={state.guistate.highlightedUserSetting}
/>
</LoginToggle>
</div>
<div class="flex" slot="title2">
<img class="w-6" src="./assets/svg/community.svg">
Get in touch with others
</div>
<CommunityIndexView location={state.mapProperties.location} slot="content2"></CommunityIndexView>
<div class="flex" slot="title3">
<EyeIcon class="w-6" />
<Tr t={Translations.t.privacy.title}></Tr>
</div>
<ToSvelte construct={() => new PrivacyPolicy()} slot="content3"></ToSvelte>
</TabbedGroup>
</FloatOver>
</If>

View file

@ -5,7 +5,12 @@
"de": "Eine spezielle Ebene, die nicht für die Darstellung auf einer Karte gedacht ist, sondern für die Festlegung von Benutzereinstellungen verwendet wird",
"nl": "Een speciale lag die niet getoond wordt op de kaart, maar die de instellingen van de gebruiker weergeeft"
},
"title": null,
"title": {
"render": {
"en": "Settings",
"nl": "Instellingen"
}
},
"source": "special",
"calculatedTags": [
"_mastodon_candidate_md=feat.properties._description.match(/\\[[^\\]]*\\]\\((.*(mastodon|en.osm.town).*)\\).*/)?.at(1)",
@ -15,6 +20,66 @@
"_mastodon_candidate=feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a"
],
"tagRenderings": [
{
"id": "profile",
"render": {
"*": "{user_profile()}"
}
},
{
"id": "language_picker",
"render": {
"*": "{language_picker()}"
}
},
{
"id": "inbox",
"mappings": [
{
"if": "_unreadMessages=0",
"then": {
"special": {
"type": "link",
"href": "{_backend}/messages/inbox",
"text": {
"en": "Open your inbox",
"nl": "Ga naar je inbox"
}
}
}
},
{
"if": "_unreadMessages>0",
"then": {
"special": {
"type": "link",
"text": {
"en": "<b class='alert'>You have {_unreadMessages}</b><br/>Open your inbox"
},
"href": "{_backend}/messages/inbox"
}
}
}
]
},
{
"id": "settings-link",
"render": {
"special": {
"type": "link",
"text": {
"en": "Open your settings on OpenStreetMap.org"
},
"href": "{_backend}/account/edit"
}
}
},
{
"id": "logout",
"render": {
"*": "{logout()}"
}
},
{
"id": "picture-license",
"description": "This question is not meant to be placed on an OpenStreetMap-element; however it is used in the user information panel to ask which license the user wants",
@ -328,4 +393,4 @@
}
],
"mapRendering": null
}
}

View file

@ -859,10 +859,6 @@ video {
margin-bottom: 0.75rem;
}
.mt-1 {
margin-top: 0.25rem;
}
.mr-2 {
margin-right: 0.5rem;
}
@ -931,6 +927,10 @@ video {
margin-top: 2rem;
}
.mt-1 {
margin-top: 0.25rem;
}
.ml-3 {
margin-left: 0.75rem;
}
@ -1063,14 +1063,14 @@ video {
height: 2.75rem;
}
.h-96 {
height: 24rem;
}
.h-64 {
height: 16rem;
}
.h-96 {
height: 24rem;
}
.h-0 {
height: 0px;
}

View file

@ -594,9 +594,7 @@
},
"userinfo": {
"gotoInbox": "Obre la teva safata d'entrada",
"gotoSettings": "Aneu a la vostra configuració a OpenStreetMap.org",
"moveToHome": "Mou el mapa a la vostra ubicació de casa",
"welcome": "Benvingut/da {name}"
"gotoSettings": "Aneu a la vostra configuració a OpenStreetMap.org"
},
"validation": {
"color": {

View file

@ -265,10 +265,7 @@
"userinfo": {
"gotoInbox": "Otevřít poštu",
"gotoSettings": "Přejít do vašich nastavení na OpenStreetMap.org",
"moveToHome": "Přesunout mapu na vaší domovskou polohu",
"noDescription": "Na svém profilu zatím nemáte popis",
"noDescriptionCallToAction": "Přidat popis profilu",
"titleNotLoggedIn": "Vítejte",
"welcome": "Vítejte, {name}"
"noDescriptionCallToAction": "Přidat popis profilu"
}
}

View file

@ -934,12 +934,8 @@
"userinfo": {
"gotoInbox": "Posteingang öffnen",
"gotoSettings": "Einstellungen auf OpenStreetMap.org öffnen",
"moveToHome": "Verschieben Sie die Karte an Ihren Heimatstandort",
"newMessages": "Sie haben neue Nachrichten",
"noDescription": "Sie haben noch keine Profilbeschreibung",
"noDescriptionCallToAction": "Profilbeschreibung hinzufügen",
"titleNotLoggedIn": "Willkommen",
"welcome": "Willkommen {name}"
"noDescriptionCallToAction": "Profilbeschreibung hinzufügen"
},
"validation": {
"color": {

View file

@ -958,12 +958,9 @@
"userinfo": {
"gotoInbox": "Open your inbox",
"gotoSettings": "Go to your settings on OpenStreetMap.org",
"moveToHome": "Move the map to your home location",
"newMessages": "you have new messages",
"noDescription": "You don't have a description on your profile yet",
"noDescriptionCallToAction": "Add a profile description",
"titleNotLoggedIn": "Welcome",
"welcome": "Welcome {name}"
"notLoggedIn": "You have logged out"
},
"validation": {
"color": {

View file

@ -713,9 +713,7 @@
"missing": "{count} cadenas sin traducir",
"notImmediate": "Las traducciones no se actualizan directamente. Habitualmente esto lleva unos días"
},
"userinfo": {
"welcome": "Bienvenido {name}"
},
"userinfo": {},
"validation": {
"color": {
"description": "Un color o código hexadecimal"

View file

@ -488,9 +488,7 @@
},
"userinfo": {
"gotoInbox": "Ouvrir sa boite de réception",
"gotoSettings": "Paramètres sur OpenStreetMap.org",
"moveToHome": "Déplacez la carte vers votre emplacement",
"welcome": "Bienvenue {name}"
"gotoSettings": "Paramètres sur OpenStreetMap.org"
},
"validation": {
"email": {

View file

@ -8540,6 +8540,24 @@
}
}
},
"inbox": {
"mappings": {
"0": {
"then": {
"special": {
"text": "Open your inbox"
}
}
},
"1": {
"then": {
"special": {
"text": "<b class='alert'>You have {_unreadMessages}</b><br/>Open your inbox"
}
}
}
}
},
"picture-license": {
"mappings": {
"0": {
@ -8617,6 +8635,9 @@
}
}
}
},
"title": {
"render": "Settings"
}
},
"veterinary": {

View file

@ -8265,6 +8265,17 @@
}
}
},
"inbox": {
"mappings": {
"0": {
"then": {
"special": {
"text": "Ga naar je inbox"
}
}
}
}
},
"picture-license": {
"mappings": {
"0": {
@ -8314,6 +8325,9 @@
}
}
}
},
"title": {
"render": "Instellingen"
}
},
"veterinary": {

View file

@ -692,11 +692,8 @@
"userinfo": {
"gotoInbox": "Åpne innboksen din",
"gotoSettings": "Gå til innstillingene dine på OpenStreetMap.org",
"newMessages": "du har nye meldinger",
"noDescription": "Du har ikke noen profilbeskrivelse enda",
"noDescriptionCallToAction": "Legg til profilbeskrivelse",
"titleNotLoggedIn": "Velkommen",
"welcome": "Velkommen {name}"
"noDescriptionCallToAction": "Legg til profilbeskrivelse"
},
"validation": {
"color": {

View file

@ -938,12 +938,8 @@
"userinfo": {
"gotoInbox": "Open je inbox",
"gotoSettings": "Ga naar je instellingen op OpenStreetMap.org",
"moveToHome": "Beweeg de kaart naar je thuislocatie",
"newMessages": "je hebt nieuwe berichten",
"noDescription": "Je hebt nog geen beschrijving op je profiel",
"noDescriptionCallToAction": "Voeg een profielbeschrijving toe",
"titleNotLoggedIn": "Welkom",
"welcome": "Welkom {name}"
"noDescriptionCallToAction": "Voeg een profielbeschrijving toe"
},
"validation": {
"color": {

View file

@ -192,11 +192,8 @@
"userinfo": {
"gotoInbox": "Otwórz swoją skrzynkę odbiorczą",
"gotoSettings": "Przejdź do swoich ustawień na OpenStreetMap.org",
"moveToHome": "Przesuń mapę do Twojej lokalizacji domowej",
"newMessages": "masz nowe wiadomości",
"noDescription": "Nie masz jeszcze opisu w swoim profilu",
"noDescriptionCallToAction": "Dodaj opis profilu",
"welcome": "Witaj {name}"
"noDescriptionCallToAction": "Dodaj opis profilu"
},
"validation": {
"color": {

View file

@ -216,10 +216,7 @@
"translations": {
"activateButton": "Помогите перевести MapComplete"
},
"userinfo": {
"titleNotLoggedIn": "Добро пожаловать",
"welcome": "Добро пожаловать, {name}"
},
"userinfo": {},
"validation": {
"nat": {
"notANumber": "Введите число"

16
test.ts
View file

@ -7,17 +7,24 @@ import ThemeViewState from "./Models/ThemeViewState"
import Combine from "./UI/Base/Combine"
import SpecialVisualizations from "./UI/SpecialVisualizations"
import AddNewPoint from "./UI/Popup/AddNewPoint/AddNewPoint.svelte"
import UserProfile from "./UI/BigComponents/UserProfile.svelte"
async function main() {
new FixedUiElement("").AttachTo("extradiv")
const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)
const main = new SvelteUIElement(ThemeViewGUI, { layout })
const state = new ThemeViewState(layout)
const main = new SvelteUIElement(ThemeViewGUI, { state })
state.guistate.menuIsOpened.setData(true)
state.guistate.menuViewTab.setData("settings")
main.AttachTo("maindiv")
}
async function testspecial() {
const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)
const state = new ThemeViewState(layout)
state.guistate.openUsersettings("picture-license")
const all = SpecialVisualizations.specialVisualizations.map((s) =>
SpecialVisualizations.renderExampleOfSpecial(state, s)
)
@ -27,12 +34,7 @@ async function testspecial() {
async function test() {
const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)
const state = new ThemeViewState(layout)
state.featureSwitches.featureSwitchIsTesting.setData(true)
new SvelteUIElement(AddNewPoint, {
state,
coordinate: { lon: 3.22001, lat: 51.21576 },
}).AttachTo("maindiv")
//*/
new SvelteUIElement(UserProfile, { osmConnection: state.osmConnection }).AttachTo("maindiv")
}
/*