forked from MapComplete/MapComplete
Port user settings
This commit is contained in:
parent
97aaa8e941
commit
8085079eff
30 changed files with 566 additions and 497 deletions
|
@ -2,10 +2,17 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||||
import { OsmConnection } from "../Osm/OsmConnection"
|
import { OsmConnection } from "../Osm/OsmConnection"
|
||||||
import { MangroveIdentity } from "../Web/MangroveReviews"
|
import { MangroveIdentity } from "../Web/MangroveReviews"
|
||||||
import { Store, Stores, UIEventSource } from "../UIEventSource"
|
import { Store, Stores, UIEventSource } from "../UIEventSource"
|
||||||
import Locale from "../../UI/i18n/Locale"
|
|
||||||
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"
|
import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"
|
||||||
import { FeatureSource } from "../FeatureSource/FeatureSource"
|
import { FeatureSource } from "../FeatureSource/FeatureSource"
|
||||||
import { Feature } from "geojson"
|
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,
|
* 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.
|
* The number of seconds that the GPS-locations are stored in memory.
|
||||||
* Time in seconds
|
* Time in seconds
|
||||||
*/
|
*/
|
||||||
public gpsLocationHistoryRetentionTime = new UIEventSource(
|
public readonly gpsLocationHistoryRetentionTime = new UIEventSource(
|
||||||
7 * 24 * 60 * 60,
|
7 * 24 * 60 * 60,
|
||||||
"gps_location_retention"
|
"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
|
this.osmConnection = osmConnection
|
||||||
{
|
{
|
||||||
const translationMode: UIEventSource<undefined | "true" | "false" | "mobile" | string> =
|
const translationMode: UIEventSource<undefined | "true" | "false" | "mobile" | string> =
|
||||||
this.osmConnection.GetPreference("translation-mode")
|
this.osmConnection.GetPreference("translation-mode", "false")
|
||||||
translationMode.addCallbackAndRunD((mode) => {
|
translationMode.addCallbackAndRunD((mode) => {
|
||||||
mode = mode.toLowerCase()
|
mode = mode.toLowerCase()
|
||||||
if (mode === "true" || mode === "yes") {
|
if (mode === "true" || mode === "yes") {
|
||||||
|
@ -73,6 +94,8 @@ export default class UserRelatedState {
|
||||||
this.installedUserThemes = this.InitInstalledUserThemes()
|
this.installedUserThemes = this.InitInstalledUserThemes()
|
||||||
|
|
||||||
this.homeLocation = this.initHomeLocation()
|
this.homeLocation = this.initHomeLocation()
|
||||||
|
|
||||||
|
this.preferencesAsTags = this.initAmendedPrefs(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
public GetUnofficialTheme(id: string):
|
public GetUnofficialTheme(id: string):
|
||||||
|
@ -211,4 +234,127 @@ export default class UserRelatedState {
|
||||||
})
|
})
|
||||||
return new StaticFeatureSource(feature)
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,6 @@ export default class FilteredLayer {
|
||||||
} else {
|
} else {
|
||||||
properties = fieldstate
|
properties = fieldstate
|
||||||
}
|
}
|
||||||
console.log("Building tagsspec with properties", properties)
|
|
||||||
const missingKeys = option.fields
|
const missingKeys = option.fields
|
||||||
.map((f) => f.name)
|
.map((f) => f.name)
|
||||||
.filter((key) => properties[key] === undefined)
|
.filter((key) => properties[key] === undefined)
|
||||||
|
@ -182,7 +181,6 @@ export default class FilteredLayer {
|
||||||
// We calculate the fields
|
// We calculate the fields
|
||||||
const fieldProperties = FilteredLayer.stringToFieldProperties(<string>state.data)
|
const fieldProperties = FilteredLayer.stringToFieldProperties(<string>state.data)
|
||||||
const asTags = FilteredLayer.fieldsToTags(filter.options[0], fieldProperties)
|
const asTags = FilteredLayer.fieldsToTags(filter.options[0], fieldProperties)
|
||||||
console.log("Current field properties:", state.data, fieldProperties, asTags)
|
|
||||||
if (asTags) {
|
if (asTags) {
|
||||||
needed.push(asTags)
|
needed.push(asTags)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ export class MenuState {
|
||||||
public readonly highlightedLayerInFilters: UIEventSource<string> = new UIEventSource<string>(
|
public readonly highlightedLayerInFilters: UIEventSource<string> = new UIEventSource<string>(
|
||||||
undefined
|
undefined
|
||||||
)
|
)
|
||||||
|
public highlightedUserSetting: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||||
constructor() {
|
constructor() {
|
||||||
this.themeViewTabIndex = new UIEventSource(0)
|
this.themeViewTabIndex = new UIEventSource(0)
|
||||||
this.themeViewTab = this.themeViewTabIndex.sync(
|
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() {
|
public closeAll() {
|
||||||
this.menuIsOpened.setData(false)
|
this.menuIsOpened.setData(false)
|
||||||
this.themeIsOpened.setData(false)
|
this.themeIsOpened.setData(false)
|
||||||
|
|
|
@ -805,7 +805,13 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
|
||||||
const param = special[arg.name]
|
const param = special[arg.name]
|
||||||
if (param === undefined) {
|
if (param === undefined) {
|
||||||
errors.push(
|
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}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
||||||
),
|
),
|
||||||
osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data,
|
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.selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element")
|
||||||
this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer")
|
this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer")
|
||||||
this.geolocation = new GeoLocationHandler(
|
this.geolocation = new GeoLocationHandler(
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
const offlineModes: Partial<Record<OsmServiceState, Translation>> = {
|
const offlineModes: Partial<Record<OsmServiceState, Translation>> = {
|
||||||
offline: t.loginFailedOfflineMode,
|
offline: t.loginFailedOfflineMode,
|
||||||
unreachable: t.loginFailedUnreachableMode,
|
unreachable: t.loginFailedUnreachableMode,
|
||||||
|
unknown: t.loginFailedUnreachableMode,
|
||||||
readonly: t.loginFailedReadonlyMode
|
readonly: t.loginFailedReadonlyMode
|
||||||
};
|
};
|
||||||
const apiState = state.osmConnection.apiIsOnline;
|
const apiState = state.osmConnection.apiIsOnline;
|
||||||
|
|
|
@ -7,15 +7,17 @@
|
||||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||||
import { onDestroy } from "svelte";
|
import { onDestroy } from "svelte";
|
||||||
|
|
||||||
export let selectedElement: Feature;
|
export let state: SpecialVisualizationState;
|
||||||
export let layer: LayerConfig;
|
export let layer: LayerConfig;
|
||||||
|
export let selectedElement: Feature;
|
||||||
export let tags: UIEventSource<Record<string, string>>;
|
export let tags: UIEventSource<Record<string, string>>;
|
||||||
|
export let highlightedRendering: UIEventSource<string> = undefined;
|
||||||
|
|
||||||
|
|
||||||
let _tags: Record<string, string>;
|
let _tags: Record<string, string>;
|
||||||
onDestroy(tags.addCallbackAndRun(tags => {
|
onDestroy(tags.addCallbackAndRun(tags => {
|
||||||
_tags = tags;
|
_tags = tags;
|
||||||
}));
|
}));
|
||||||
export let state: SpecialVisualizationState;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -40,7 +42,7 @@
|
||||||
{#each layer.tagRenderings as config (config.id)}
|
{#each layer.tagRenderings as config (config.id)}
|
||||||
{#if config.condition === undefined || config.condition.matchesProperties(_tags)}
|
{#if config.condition === undefined || config.condition.matchesProperties(_tags)}
|
||||||
{#if config.IsKnown(_tags)}
|
{#if config.IsKnown(_tags)}
|
||||||
<TagRenderingEditable {tags} {config} {state} {selectedElement} {layer}></TagRenderingEditable>
|
<TagRenderingEditable {tags} {config} {state} {selectedElement} {layer} {highlightedRendering}></TagRenderingEditable>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
|
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||||
import Combine from "../Base/Combine"
|
import Combine from "../Base/Combine"
|
||||||
|
@ -8,25 +7,13 @@ import { VariableUiElement } from "../Base/VariableUIElement"
|
||||||
import Img from "../Base/Img"
|
import Img from "../Base/Img"
|
||||||
import { FixedUiElement } from "../Base/FixedUiElement"
|
import { FixedUiElement } from "../Base/FixedUiElement"
|
||||||
import Link from "../Base/Link"
|
import Link from "../Base/Link"
|
||||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import Loc from "../../Models/Loc"
|
import Loc from "../../Models/Loc"
|
||||||
import BaseUIElement from "../BaseUIElement"
|
import BaseUIElement from "../BaseUIElement"
|
||||||
import Showdown from "showdown"
|
import Showdown from "showdown"
|
||||||
import LanguagePicker from "../LanguagePicker"
|
import LanguagePicker from "../LanguagePicker"
|
||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
|
||||||
import Constants from "../../Models/Constants"
|
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 {
|
export class ImportViewerLinks extends VariableUiElement {
|
||||||
constructor(osmConnection: OsmConnection) {
|
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 {
|
class UserInformationMainPanel extends VariableUiElement {
|
||||||
private readonly settings: UIEventSource<Record<string, BaseUIElement>>
|
private readonly settings: UIEventSource<Record<string, BaseUIElement>>
|
||||||
private readonly userInfoFocusedQuestion?: UIEventSource<string>
|
private readonly userInfoFocusedQuestion?: UIEventSource<string>
|
||||||
|
@ -104,210 +46,9 @@ class UserInformationMainPanel extends VariableUiElement {
|
||||||
isOpened: UIEventSource<boolean>,
|
isOpened: UIEventSource<boolean>,
|
||||||
userInfoFocusedQuestion?: UIEventSource<string>
|
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 settings = new UIEventSource<Record<string, BaseUIElement>>({})
|
||||||
const usersettingsConfig = new LayerConfig(usersettings, "userinformationpanel")
|
|
||||||
|
|
||||||
const amendedPrefs = new UIEventSource<any>({ _theme: layout?.id })
|
super()
|
||||||
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(/>/g, ">")
|
|
||||||
.replace(/</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")
|
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.userInfoFocusedQuestion = userInfoFocusedQuestion
|
this.userInfoFocusedQuestion = userInfoFocusedQuestion
|
||||||
const self = this
|
const self = this
|
||||||
|
@ -325,50 +66,3 @@ class UserInformationMainPanel extends VariableUiElement {
|
||||||
this.settings.data[focusedId]?.ScrollIntoView()
|
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
48
UI/BigComponents/UserProfile.svelte
Normal file
48
UI/BigComponents/UserProfile.svelte
Normal 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(/>/g, ">")
|
||||||
|
?.replace(/</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>
|
|
@ -158,30 +158,6 @@ export default class DefaultGUI {
|
||||||
|
|
||||||
const self = this
|
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(
|
const extraLink = Toggle.If(
|
||||||
state.featureSwitchExtraLinkEnabled,
|
state.featureSwitchExtraLinkEnabled,
|
||||||
() => new ExtraLinkButton(state, state.layoutToUse.extraLink)
|
() => new ExtraLinkButton(state, state.layoutToUse.extraLink)
|
||||||
|
@ -200,7 +176,7 @@ export default class DefaultGUI {
|
||||||
const copyright = new MapControlButton(Svg.copyright_svg()).onClick(() =>
|
const copyright = new MapControlButton(Svg.copyright_svg()).onClick(() =>
|
||||||
guiState.copyrightViewIsOpened.setData(true)
|
guiState.copyrightViewIsOpened.setData(true)
|
||||||
)
|
)
|
||||||
new Combine([welcomeMessageMapControl, userInfoMapControl, copyright, extraLink])
|
new Combine([welcomeMessageMapControl, copyright, extraLink])
|
||||||
.SetClass("flex flex-col")
|
.SetClass("flex flex-col")
|
||||||
.AttachTo("top-left")
|
.AttachTo("top-left")
|
||||||
|
|
||||||
|
|
|
@ -15,32 +15,51 @@
|
||||||
export let tags: UIEventSource<Record<string, string>>;
|
export let tags: UIEventSource<Record<string, string>>;
|
||||||
export let selectedElement: Feature;
|
export let selectedElement: Feature;
|
||||||
export let state: SpecialVisualizationState;
|
export let state: SpecialVisualizationState;
|
||||||
export let layer: LayerConfig
|
export let layer: LayerConfig;
|
||||||
|
|
||||||
export let showQuestionIfUnknown : boolean= false
|
export let highlightedRendering: UIEventSource<string> = undefined;
|
||||||
let editMode = false
|
export let showQuestionIfUnknown: boolean = false;
|
||||||
|
let editMode = false;
|
||||||
onDestroy(tags.addCallbackAndRunD(tags => {
|
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>
|
</script>
|
||||||
|
|
||||||
{#if config.question}
|
<div bind:this={htmlElem}>
|
||||||
{#if editMode}
|
{#if config.question}
|
||||||
<TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer} >
|
{#if editMode}
|
||||||
<button slot="cancel" on:click={() => {editMode = false}}>
|
<TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer}>
|
||||||
<Tr t={Translations.t.general.cancel}/>
|
<button slot="cancel" on:click={() => {editMode = false}}>
|
||||||
</button>
|
<Tr t={Translations.t.general.cancel} />
|
||||||
</TagRenderingQuestion>
|
</button>
|
||||||
{:else}
|
</TagRenderingQuestion>
|
||||||
<div class="flex justify-between">
|
{:else}
|
||||||
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
|
<div class="flex justify-between">
|
||||||
<button on:click={() => {editMode = true}} class="w-6 h-6 rounded-full subtle-background p-1">
|
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
|
||||||
<PencilAltIcon></PencilAltIcon>
|
<button on:click={() => {editMode = true}} class="shrink-0 w-6 h-6 rounded-full subtle-background p-1">
|
||||||
</button>
|
<PencilAltIcon></PencilAltIcon>
|
||||||
</div>
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{:else }
|
||||||
|
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
|
||||||
{/if}
|
{/if}
|
||||||
{:else }
|
</div>
|
||||||
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer} />
|
|
||||||
{/if}
|
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
import { TagsFilter } from "../../../Logic/Tags/TagsFilter";
|
import { TagsFilter } from "../../../Logic/Tags/TagsFilter";
|
||||||
import FreeformInput from "./FreeformInput.svelte";
|
import FreeformInput from "./FreeformInput.svelte";
|
||||||
import Translations from "../../i18n/Translations.js";
|
import Translations from "../../i18n/Translations.js";
|
||||||
import FromHtml from "../../Base/FromHtml.svelte";
|
|
||||||
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction";
|
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction";
|
||||||
import { createEventDispatcher } from "svelte";
|
import { createEventDispatcher } from "svelte";
|
||||||
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
|
||||||
|
@ -63,6 +62,24 @@
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
function onSave() {
|
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 });
|
dispatch("saved", { config, applied: selectedTags });
|
||||||
const change = new ChangeTagAction(
|
const change = new ChangeTagAction(
|
||||||
tags.data.id,
|
tags.data.id,
|
||||||
|
@ -76,6 +93,7 @@
|
||||||
freeformInput.setData(undefined);
|
freeformInput.setData(undefined);
|
||||||
selectedMapping = 0;
|
selectedMapping = 0;
|
||||||
selectedTags = undefined;
|
selectedTags = undefined;
|
||||||
|
|
||||||
change.CreateChangeDescriptions().then(changes =>
|
change.CreateChangeDescriptions().then(changes =>
|
||||||
state.changes.applyChanges(changes)
|
state.changes.applyChanges(changes)
|
||||||
).catch(console.error);
|
).catch(console.error);
|
||||||
|
@ -93,12 +111,14 @@
|
||||||
</span>
|
</span>
|
||||||
<span class="alert">{config.id}</span>
|
<span class="alert">{config.id}</span>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
{#if config.questionhint}
|
{#if config.questionhint}
|
||||||
<div class="subtle">
|
<div class="subtle">
|
||||||
<SpecialTranslation t={config.questionhint} {tags} {state} {layer} feature={selectedElement}></SpecialTranslation>
|
<SpecialTranslation t={config.questionhint} {tags} {state} {layer}
|
||||||
|
feature={selectedElement}></SpecialTranslation>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -152,7 +172,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<TagHint osmConnection={state.osmConnection} tags={selectedTags}></TagHint>
|
<TagHint osmConnection={state.osmConnection} tags={selectedTags}></TagHint>
|
||||||
<div>
|
<div>
|
||||||
<!-- TagRenderingQuestion-buttons -->
|
<!-- TagRenderingQuestion-buttons -->
|
||||||
<slot name="cancel"></slot>
|
<slot name="cancel"></slot>
|
||||||
|
@ -163,7 +183,7 @@
|
||||||
{:else }
|
{:else }
|
||||||
<div class="w-6 h-6">
|
<div class="w-6 h-6">
|
||||||
<!-- Invalid value; show an inactive button or something like that-->
|
<!-- Invalid value; show an inactive button or something like that-->
|
||||||
<ExclamationIcon></ExclamationIcon>
|
<ExclamationIcon></ExclamationIcon>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -56,16 +56,27 @@ import Maproulette from "../Logic/Maproulette"
|
||||||
import SvelteUIElement from "./Base/SvelteUIElement"
|
import SvelteUIElement from "./Base/SvelteUIElement"
|
||||||
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
|
import { BBoxFeatureSourceForLayer } from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
|
||||||
import QuestionViz from "./Popup/QuestionViz"
|
import QuestionViz from "./Popup/QuestionViz"
|
||||||
import SimpleAddUI from "./BigComponents/SimpleAddUI"
|
|
||||||
import { Feature } from "geojson"
|
import { Feature } from "geojson"
|
||||||
import { GeoOperations } from "../Logic/GeoOperations"
|
import { GeoOperations } from "../Logic/GeoOperations"
|
||||||
import CreateNewNote from "./Popup/CreateNewNote.svelte"
|
import CreateNewNote from "./Popup/CreateNewNote.svelte"
|
||||||
import { svelte } from "@sveltejs/vite-plugin-svelte"
|
|
||||||
import AddNewPoint from "./Popup/AddNewPoint/AddNewPoint.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 {
|
export default class SpecialVisualizations {
|
||||||
public static specialVisualizations: SpecialVisualization[] = SpecialVisualizations.initList()
|
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.
|
* 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 ?? "")
|
const args = knownSpecial.args.map((arg) => arg.defaultValue ?? "")
|
||||||
if (argument.length > 0) {
|
if (argument.length > 0) {
|
||||||
const realArgs = argument.split(",").map((str) =>
|
const realArgs = argument.split(",").map((str) => this.undoEncoding(str))
|
||||||
str
|
|
||||||
.trim()
|
|
||||||
.replace(/&LPARENS/g, "(")
|
|
||||||
.replace(/&RPARENS/g, ")")
|
|
||||||
.replace(/&LBRACE/g, "{")
|
|
||||||
.replace(/&RBRACE/g, "}")
|
|
||||||
.replace(/&COMMA/g, ",")
|
|
||||||
)
|
|
||||||
for (let i = 0; i < realArgs.length; i++) {
|
for (let i = 0; i < realArgs.length; i++) {
|
||||||
if (args.length <= i) {
|
if (args.length <= i) {
|
||||||
args.push(realArgs[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 HistogramViz(),
|
||||||
new StealViz(),
|
new StealViz(),
|
||||||
new MinimapViz(),
|
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()}?'",
|
docs: "Shows the title of the popup. Useful for some cases, e.g. 'What is phone number of {title()}?'",
|
||||||
example:
|
example:
|
||||||
"`What is the phone number of {title()}`, which might automatically become `What is the phone number of XYZ`.",
|
"`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(
|
new VariableUiElement(
|
||||||
tagsSource.map((tags) => {
|
tagsSource.map((tags) => {
|
||||||
const layer = state.layout.getMatchingLayer(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",
|
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",
|
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",
|
||||||
|
|
|
@ -87,7 +87,7 @@ export class SubstitutedTranslation extends VariableUiElement {
|
||||||
tagsSource.data.id
|
tagsSource.data.id
|
||||||
)
|
)
|
||||||
return viz.func
|
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)
|
?.SetStyle(proto.style)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e)
|
console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e)
|
||||||
|
|
|
@ -2,13 +2,12 @@
|
||||||
import { Store, UIEventSource } from "../Logic/UIEventSource";
|
import { Store, UIEventSource } from "../Logic/UIEventSource";
|
||||||
import { Map as MlMap } from "maplibre-gl";
|
import { Map as MlMap } from "maplibre-gl";
|
||||||
import MaplibreMap from "./Map/MaplibreMap.svelte";
|
import MaplibreMap from "./Map/MaplibreMap.svelte";
|
||||||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
|
|
||||||
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
|
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
|
||||||
import MapControlButton from "./Base/MapControlButton.svelte";
|
import MapControlButton from "./Base/MapControlButton.svelte";
|
||||||
import ToSvelte from "./Base/ToSvelte.svelte";
|
import ToSvelte from "./Base/ToSvelte.svelte";
|
||||||
import Svg from "../Svg";
|
import Svg from "../Svg";
|
||||||
import If from "./Base/If.svelte";
|
import If from "./Base/If.svelte";
|
||||||
import { GeolocationControl } from "./BigComponents/GeolocationControl.js";
|
import { GeolocationControl } from "./BigComponents/GeolocationControl";
|
||||||
import type { Feature } from "geojson";
|
import type { Feature } from "geojson";
|
||||||
import SelectedElementView from "./BigComponents/SelectedElementView.svelte";
|
import SelectedElementView from "./BigComponents/SelectedElementView.svelte";
|
||||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
|
||||||
|
@ -17,19 +16,21 @@
|
||||||
import ThemeViewState from "../Models/ThemeViewState";
|
import ThemeViewState from "../Models/ThemeViewState";
|
||||||
import type { MapProperties } from "../Models/MapProperties";
|
import type { MapProperties } from "../Models/MapProperties";
|
||||||
import Geosearch from "./BigComponents/Geosearch.svelte";
|
import Geosearch from "./BigComponents/Geosearch.svelte";
|
||||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui";
|
|
||||||
import Translations from "./i18n/Translations";
|
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 Tr from "./Base/Tr.svelte";
|
||||||
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
|
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
|
||||||
import FloatOver from "./Base/FloatOver.svelte";
|
import FloatOver from "./Base/FloatOver.svelte";
|
||||||
import PrivacyPolicy from "./BigComponents/PrivacyPolicy.js";
|
import PrivacyPolicy from "./BigComponents/PrivacyPolicy";
|
||||||
import { Utils } from "../Utils.js";
|
import { Utils } from "../Utils";
|
||||||
import Constants from "../Models/Constants";
|
import Constants from "../Models/Constants";
|
||||||
import TabbedGroup from "./Base/TabbedGroup.svelte";
|
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;
|
export let state: ThemeViewState;
|
||||||
const state = new ThemeViewState(layout);
|
let layout = state.layout;
|
||||||
|
|
||||||
let selectedElementTags: Store<UIEventSource<Record<string, string>>> =
|
let selectedElementTags: Store<UIEventSource<Record<string, string>>> =
|
||||||
state.selectedElement.mapD((f) => {
|
state.selectedElement.mapD((f) => {
|
||||||
|
@ -43,6 +44,7 @@
|
||||||
let mapproperties: MapProperties = state.mapProperties;
|
let mapproperties: MapProperties = state.mapProperties;
|
||||||
let featureSwitches: FeatureSwitchState = state.featureSwitches;
|
let featureSwitches: FeatureSwitchState = state.featureSwitches;
|
||||||
let availableLayers = state.availableLayers;
|
let availableLayers = state.availableLayers;
|
||||||
|
let userdetails = state.osmConnection.userDetails;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,7 +99,7 @@
|
||||||
{#if $selectedElement !== undefined && $selectedLayer !== undefined}
|
{#if $selectedElement !== undefined && $selectedLayer !== undefined}
|
||||||
<FloatOver on:close={() => {selectedElement.setData(undefined)}}>
|
<FloatOver on:close={() => {selectedElement.setData(undefined)}}>
|
||||||
<SelectedElementView layer={$selectedLayer} selectedElement={$selectedElement}
|
<SelectedElementView layer={$selectedLayer} selectedElement={$selectedElement}
|
||||||
tags={$selectedElementTags} state={state}></SelectedElementView>
|
tags={$selectedElementTags} state={state} />
|
||||||
</FloatOver>
|
</FloatOver>
|
||||||
|
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -106,7 +108,7 @@
|
||||||
<!-- Theme page -->
|
<!-- Theme page -->
|
||||||
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
|
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
|
||||||
<TabbedGroup tab={state.guistate.themeViewTabIndex}>
|
<TabbedGroup tab={state.guistate.themeViewTabIndex}>
|
||||||
<Tr slot="title0" t={layout.title} />
|
<Tr slot="title0" t={layout.title} />
|
||||||
|
|
||||||
<div slot="content0">
|
<div slot="content0">
|
||||||
|
|
||||||
|
@ -130,17 +132,18 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div slot="title1" class="flex">
|
<div class="flex" slot="title1">
|
||||||
<If condition={state.featureSwitches.featureSwitchFilter}>
|
<If condition={state.featureSwitches.featureSwitchFilter}>
|
||||||
<img class="w-4 h-4" src="./assets/svg/filter.svg">
|
<img class="w-4 h-4" src="./assets/svg/filter.svg">
|
||||||
<Tr t={Translations.t.general.menu.filter} />
|
<Tr t={Translations.t.general.menu.filter} />
|
||||||
</If>
|
</If>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div slot="content1" class="flex flex-col">
|
<div class="flex flex-col" slot="content1">
|
||||||
{#each layout.layers as layer}
|
{#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}
|
{/each}
|
||||||
<If condition={state.featureSwitches.featureSwitchBackgroundSelection}>
|
<If condition={state.featureSwitches.featureSwitchBackgroundSelection}>
|
||||||
<RasterLayerPicker {availableLayers} value={mapproperties.rasterLayer}></RasterLayerPicker>
|
<RasterLayerPicker {availableLayers} value={mapproperties.rasterLayer}></RasterLayerPicker>
|
||||||
|
@ -154,50 +157,56 @@
|
||||||
<If condition={state.guistate.menuIsOpened}>
|
<If condition={state.guistate.menuIsOpened}>
|
||||||
<!-- Menu page -->
|
<!-- Menu page -->
|
||||||
<FloatOver on:close={() => state.guistate.menuIsOpened.setData(false)}>
|
<FloatOver on:close={() => state.guistate.menuIsOpened.setData(false)}>
|
||||||
<TabGroup on:change={(e) => {state.guistate.menuViewTabIndex.setData(e.detail)} }>
|
<TabbedGroup tab={state.guistate.menuViewTabIndex}>
|
||||||
<TabList>
|
<div class="flex" slot="title0">
|
||||||
<Tab class={({selected}) => selected ? "tab-selected" : "tab-unselected"}>
|
<Tr t={Translations.t.general.aboutMapcompleteTitle}></Tr>
|
||||||
<div class="flex">
|
</div>
|
||||||
<Tr t={Translations.t.general.aboutMapcompleteTitle}></Tr>
|
|
||||||
</div>
|
<div class="flex flex-col" slot="content0">
|
||||||
</Tab>
|
<Tr t={Translations.t.general.aboutMapcomplete.Subs({
|
||||||
<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({
|
|
||||||
osmcha_link: Utils.OsmChaLinkFor(7),
|
osmcha_link: Utils.OsmChaLinkFor(7),
|
||||||
})}></Tr>
|
})}></Tr>
|
||||||
|
|
||||||
{Constants.vNumber}
|
{Constants.vNumber}
|
||||||
</TabPanel>
|
</div>
|
||||||
<TabPanel>User settings</TabPanel>
|
|
||||||
<TabPanel>
|
|
||||||
<CommunityIndexView location={state.mapProperties.location}></CommunityIndexView>
|
|
||||||
|
|
||||||
</TabPanel>
|
<If condition={state.osmConnection.isLoggedIn} slot="title1">
|
||||||
<TabPanel>
|
<div class="flex">
|
||||||
<ToSvelte construct={() => new PrivacyPolicy()}></ToSvelte>
|
<CogIcon class="w-6 h-6" />
|
||||||
</TabPanel>
|
<Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})} />
|
||||||
</TabPanels>
|
</div>
|
||||||
</TabGroup>
|
</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>
|
</FloatOver>
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
|
|
|
@ -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",
|
"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"
|
"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",
|
"source": "special",
|
||||||
"calculatedTags": [
|
"calculatedTags": [
|
||||||
"_mastodon_candidate_md=feat.properties._description.match(/\\[[^\\]]*\\]\\((.*(mastodon|en.osm.town).*)\\).*/)?.at(1)",
|
"_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"
|
"_mastodon_candidate=feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a"
|
||||||
],
|
],
|
||||||
"tagRenderings": [
|
"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",
|
"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",
|
"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
|
"mapRendering": null
|
||||||
}
|
}
|
||||||
|
|
|
@ -859,10 +859,6 @@ video {
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-1 {
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mr-2 {
|
.mr-2 {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
@ -931,6 +927,10 @@ video {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mt-1 {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.ml-3 {
|
.ml-3 {
|
||||||
margin-left: 0.75rem;
|
margin-left: 0.75rem;
|
||||||
}
|
}
|
||||||
|
@ -1063,14 +1063,14 @@ video {
|
||||||
height: 2.75rem;
|
height: 2.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-96 {
|
|
||||||
height: 24rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-64 {
|
.h-64 {
|
||||||
height: 16rem;
|
height: 16rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-96 {
|
||||||
|
height: 24rem;
|
||||||
|
}
|
||||||
|
|
||||||
.h-0 {
|
.h-0 {
|
||||||
height: 0px;
|
height: 0px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -594,9 +594,7 @@
|
||||||
},
|
},
|
||||||
"userinfo": {
|
"userinfo": {
|
||||||
"gotoInbox": "Obre la teva safata d'entrada",
|
"gotoInbox": "Obre la teva safata d'entrada",
|
||||||
"gotoSettings": "Aneu a la vostra configuració a OpenStreetMap.org",
|
"gotoSettings": "Aneu a la vostra configuració a OpenStreetMap.org"
|
||||||
"moveToHome": "Mou el mapa a la vostra ubicació de casa",
|
|
||||||
"welcome": "Benvingut/da {name}"
|
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"color": {
|
"color": {
|
||||||
|
|
|
@ -265,10 +265,7 @@
|
||||||
"userinfo": {
|
"userinfo": {
|
||||||
"gotoInbox": "Otevřít poštu",
|
"gotoInbox": "Otevřít poštu",
|
||||||
"gotoSettings": "Přejít do vašich nastavení na OpenStreetMap.org",
|
"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",
|
"noDescription": "Na svém profilu zatím nemáte popis",
|
||||||
"noDescriptionCallToAction": "Přidat popis profilu",
|
"noDescriptionCallToAction": "Přidat popis profilu"
|
||||||
"titleNotLoggedIn": "Vítejte",
|
|
||||||
"welcome": "Vítejte, {name}"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -934,12 +934,8 @@
|
||||||
"userinfo": {
|
"userinfo": {
|
||||||
"gotoInbox": "Posteingang öffnen",
|
"gotoInbox": "Posteingang öffnen",
|
||||||
"gotoSettings": "Einstellungen auf OpenStreetMap.org ö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",
|
"noDescription": "Sie haben noch keine Profilbeschreibung",
|
||||||
"noDescriptionCallToAction": "Profilbeschreibung hinzufügen",
|
"noDescriptionCallToAction": "Profilbeschreibung hinzufügen"
|
||||||
"titleNotLoggedIn": "Willkommen",
|
|
||||||
"welcome": "Willkommen {name}"
|
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"color": {
|
"color": {
|
||||||
|
|
|
@ -958,12 +958,9 @@
|
||||||
"userinfo": {
|
"userinfo": {
|
||||||
"gotoInbox": "Open your inbox",
|
"gotoInbox": "Open your inbox",
|
||||||
"gotoSettings": "Go to your settings on OpenStreetMap.org",
|
"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",
|
"noDescription": "You don't have a description on your profile yet",
|
||||||
"noDescriptionCallToAction": "Add a profile description",
|
"noDescriptionCallToAction": "Add a profile description",
|
||||||
"titleNotLoggedIn": "Welcome",
|
"notLoggedIn": "You have logged out"
|
||||||
"welcome": "Welcome {name}"
|
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"color": {
|
"color": {
|
||||||
|
|
|
@ -713,9 +713,7 @@
|
||||||
"missing": "{count} cadenas sin traducir",
|
"missing": "{count} cadenas sin traducir",
|
||||||
"notImmediate": "Las traducciones no se actualizan directamente. Habitualmente esto lleva unos días"
|
"notImmediate": "Las traducciones no se actualizan directamente. Habitualmente esto lleva unos días"
|
||||||
},
|
},
|
||||||
"userinfo": {
|
"userinfo": {},
|
||||||
"welcome": "Bienvenido {name}"
|
|
||||||
},
|
|
||||||
"validation": {
|
"validation": {
|
||||||
"color": {
|
"color": {
|
||||||
"description": "Un color o código hexadecimal"
|
"description": "Un color o código hexadecimal"
|
||||||
|
|
|
@ -488,9 +488,7 @@
|
||||||
},
|
},
|
||||||
"userinfo": {
|
"userinfo": {
|
||||||
"gotoInbox": "Ouvrir sa boite de réception",
|
"gotoInbox": "Ouvrir sa boite de réception",
|
||||||
"gotoSettings": "Paramètres sur OpenStreetMap.org",
|
"gotoSettings": "Paramètres sur OpenStreetMap.org"
|
||||||
"moveToHome": "Déplacez la carte vers votre emplacement",
|
|
||||||
"welcome": "Bienvenue {name}"
|
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"email": {
|
"email": {
|
||||||
|
|
|
@ -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": {
|
"picture-license": {
|
||||||
"mappings": {
|
"mappings": {
|
||||||
"0": {
|
"0": {
|
||||||
|
@ -8617,6 +8635,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"render": "Settings"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"veterinary": {
|
"veterinary": {
|
||||||
|
|
|
@ -8265,6 +8265,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"inbox": {
|
||||||
|
"mappings": {
|
||||||
|
"0": {
|
||||||
|
"then": {
|
||||||
|
"special": {
|
||||||
|
"text": "Ga naar je inbox"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"picture-license": {
|
"picture-license": {
|
||||||
"mappings": {
|
"mappings": {
|
||||||
"0": {
|
"0": {
|
||||||
|
@ -8314,6 +8325,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"render": "Instellingen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"veterinary": {
|
"veterinary": {
|
||||||
|
|
|
@ -692,11 +692,8 @@
|
||||||
"userinfo": {
|
"userinfo": {
|
||||||
"gotoInbox": "Åpne innboksen din",
|
"gotoInbox": "Åpne innboksen din",
|
||||||
"gotoSettings": "Gå til innstillingene dine på OpenStreetMap.org",
|
"gotoSettings": "Gå til innstillingene dine på OpenStreetMap.org",
|
||||||
"newMessages": "du har nye meldinger",
|
|
||||||
"noDescription": "Du har ikke noen profilbeskrivelse enda",
|
"noDescription": "Du har ikke noen profilbeskrivelse enda",
|
||||||
"noDescriptionCallToAction": "Legg til profilbeskrivelse",
|
"noDescriptionCallToAction": "Legg til profilbeskrivelse"
|
||||||
"titleNotLoggedIn": "Velkommen",
|
|
||||||
"welcome": "Velkommen {name}"
|
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"color": {
|
"color": {
|
||||||
|
|
|
@ -938,12 +938,8 @@
|
||||||
"userinfo": {
|
"userinfo": {
|
||||||
"gotoInbox": "Open je inbox",
|
"gotoInbox": "Open je inbox",
|
||||||
"gotoSettings": "Ga naar je instellingen op OpenStreetMap.org",
|
"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",
|
"noDescription": "Je hebt nog geen beschrijving op je profiel",
|
||||||
"noDescriptionCallToAction": "Voeg een profielbeschrijving toe",
|
"noDescriptionCallToAction": "Voeg een profielbeschrijving toe"
|
||||||
"titleNotLoggedIn": "Welkom",
|
|
||||||
"welcome": "Welkom {name}"
|
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"color": {
|
"color": {
|
||||||
|
|
|
@ -192,11 +192,8 @@
|
||||||
"userinfo": {
|
"userinfo": {
|
||||||
"gotoInbox": "Otwórz swoją skrzynkę odbiorczą",
|
"gotoInbox": "Otwórz swoją skrzynkę odbiorczą",
|
||||||
"gotoSettings": "Przejdź do swoich ustawień na OpenStreetMap.org",
|
"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",
|
"noDescription": "Nie masz jeszcze opisu w swoim profilu",
|
||||||
"noDescriptionCallToAction": "Dodaj opis profilu",
|
"noDescriptionCallToAction": "Dodaj opis profilu"
|
||||||
"welcome": "Witaj {name}"
|
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"color": {
|
"color": {
|
||||||
|
|
|
@ -216,10 +216,7 @@
|
||||||
"translations": {
|
"translations": {
|
||||||
"activateButton": "Помогите перевести MapComplete"
|
"activateButton": "Помогите перевести MapComplete"
|
||||||
},
|
},
|
||||||
"userinfo": {
|
"userinfo": {},
|
||||||
"titleNotLoggedIn": "Добро пожаловать",
|
|
||||||
"welcome": "Добро пожаловать, {name}"
|
|
||||||
},
|
|
||||||
"validation": {
|
"validation": {
|
||||||
"nat": {
|
"nat": {
|
||||||
"notANumber": "Введите число"
|
"notANumber": "Введите число"
|
||||||
|
|
16
test.ts
16
test.ts
|
@ -7,17 +7,24 @@ import ThemeViewState from "./Models/ThemeViewState"
|
||||||
import Combine from "./UI/Base/Combine"
|
import Combine from "./UI/Base/Combine"
|
||||||
import SpecialVisualizations from "./UI/SpecialVisualizations"
|
import SpecialVisualizations from "./UI/SpecialVisualizations"
|
||||||
import AddNewPoint from "./UI/Popup/AddNewPoint/AddNewPoint.svelte"
|
import AddNewPoint from "./UI/Popup/AddNewPoint/AddNewPoint.svelte"
|
||||||
|
import UserProfile from "./UI/BigComponents/UserProfile.svelte"
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
new FixedUiElement("").AttachTo("extradiv")
|
new FixedUiElement("").AttachTo("extradiv")
|
||||||
const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)
|
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")
|
main.AttachTo("maindiv")
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testspecial() {
|
async function testspecial() {
|
||||||
const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)
|
const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)
|
||||||
const state = new ThemeViewState(layout)
|
const state = new ThemeViewState(layout)
|
||||||
|
|
||||||
|
state.guistate.openUsersettings("picture-license")
|
||||||
const all = SpecialVisualizations.specialVisualizations.map((s) =>
|
const all = SpecialVisualizations.specialVisualizations.map((s) =>
|
||||||
SpecialVisualizations.renderExampleOfSpecial(state, s)
|
SpecialVisualizations.renderExampleOfSpecial(state, s)
|
||||||
)
|
)
|
||||||
|
@ -27,12 +34,7 @@ async function testspecial() {
|
||||||
async function test() {
|
async function test() {
|
||||||
const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)
|
const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ? : new AllKnownLayoutsLazy().get(qp.data)
|
||||||
const state = new ThemeViewState(layout)
|
const state = new ThemeViewState(layout)
|
||||||
state.featureSwitches.featureSwitchIsTesting.setData(true)
|
new SvelteUIElement(UserProfile, { osmConnection: state.osmConnection }).AttachTo("maindiv")
|
||||||
new SvelteUIElement(AddNewPoint, {
|
|
||||||
state,
|
|
||||||
coordinate: { lon: 3.22001, lat: 51.21576 },
|
|
||||||
}).AttachTo("maindiv")
|
|
||||||
//*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue