Favourites: include _all_ tagRenderings

This commit is contained in:
Pieter Vander Vennet 2023-11-30 00:39:55 +01:00
parent eb444ab849
commit 473931891c
20 changed files with 436 additions and 154 deletions

View file

@ -1,45 +1,54 @@
import known_themes from "../assets/generated/known_themes.json"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
import favourite from "../assets/generated/layers/favourite.json"
import { LayoutConfigJson } from "../Models/ThemeConfig/Json/LayoutConfigJson"
import { AllSharedLayers } from "./AllSharedLayers"
import Constants from "../Models/Constants"
/**
* Somewhat of a dictionary, which lazily parses needed themes
*/
export class AllKnownLayoutsLazy {
private readonly dict: Map<string, { data: LayoutConfig } | { func: () => LayoutConfig }> =
new Map()
constructor() {
private readonly raw: Map<string, LayoutConfigJson> = new Map()
private readonly dict: Map<string, LayoutConfig> = new Map()
constructor(includeFavouriteLayer = true) {
for (const layoutConfigJson of known_themes["themes"]) {
this.dict.set(layoutConfigJson.id, {
func: () => {
const layout = new LayoutConfig(<LayoutConfigJson>layoutConfigJson, true)
for (let i = 0; i < layout.layers.length; i++) {
let layer = layout.layers[i]
if (typeof layer === "string") {
throw "Layer " + layer + " was not expanded in " + layout.id
}
for (const layerId of Constants.added_by_default) {
if (layerId === "favourite") {
if (includeFavouriteLayer) {
layoutConfigJson.layers.push(favourite)
}
return layout
},
})
continue
}
const defaultLayer = AllSharedLayers.getSharedLayersConfigs().get(layerId)
if (defaultLayer === undefined) {
console.error("Could not find builtin layer", layerId)
continue
}
layoutConfigJson.layers.push(defaultLayer)
}
this.raw.set(layoutConfigJson.id, layoutConfigJson)
}
}
public getConfig(key: string): LayoutConfigJson {
return this.raw.get(key)
}
public get(key: string): LayoutConfig {
const thunk = this.dict.get(key)
if (thunk === undefined) {
return undefined
const cached = this.dict.get(key)
if (cached !== undefined) {
return cached
}
if (thunk["data"]) {
return thunk["data"]
}
const layout = thunk["func"]()
this.dict.set(key, { data: layout })
const layout = new LayoutConfig(this.getConfig(key))
this.dict.set(key, layout)
return layout
}
public keys() {
return this.dict.keys()
return this.raw.keys()
}
public values() {

View file

@ -173,7 +173,6 @@ export default class GeoLocationHandler {
properties[k] = location[k]
}
}
console.debug("Current location object:", location)
properties["_all"] = JSON.stringify(location)
const feature = <Feature>{

View file

@ -16,6 +16,11 @@ export default class FavouritesFeatureSource extends StaticFeatureSource {
private readonly _osmConnection: OsmConnection
private readonly _detectedIds: Store<string[]>
/**
* All favourites, including the ones which are filtered away because they are already displayed
*/
public readonly allFavourites: Store<Feature[]>
constructor(
connection: OsmConnection,
indexedSource: FeaturePropertiesStore,
@ -53,6 +58,7 @@ export default class FavouritesFeatureSource extends StaticFeatureSource {
)
super(featuresWithoutAlreadyPresent)
this.allFavourites = features
this._osmConnection = connection
this._detectedIds = Stores.ListStabilized(
@ -76,6 +82,7 @@ export default class FavouritesFeatureSource extends StaticFeatureSource {
const geometry = <[number, number]>JSON.parse(prefs[key])
const properties = FavouritesFeatureSource.getPropertiesFor(prefs, id)
properties._orig_layer = prefs[FavouritesFeatureSource.prefix + id + "-layer"]
properties._orig_theme = prefs[FavouritesFeatureSource.prefix + id + "-theme"]
properties.id = osmId
properties._favourite = "yes"

View file

@ -2,6 +2,7 @@ import { LayerConfigJson } from "../Json/LayerConfigJson"
import { Utils } from "../../../Utils"
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
import { ConversionContext } from "./ConversionContext"
import { T } from "vitest/dist/types-aac763a5"
export interface DesugaringContext {
tagRenderings: Map<string, QuestionableTagRenderingConfigJson>
@ -81,18 +82,36 @@ export class Pure<TIn, TOut> extends Conversion<TIn, TOut> {
}
}
export class Bypass<T> extends DesugaringStep<T> {
private readonly _applyIf: (t: T) => boolean
private readonly _step: DesugaringStep<T>
constructor(applyIf: (t: T) => boolean, step: DesugaringStep<T>) {
super("Applies the step on the object, if the object satisfies the predicate", [], "Bypass")
this._applyIf = applyIf
this._step = step
}
convert(json: T, context: ConversionContext): T {
if (!this._applyIf(json)) {
return json
}
return this._step.convert(json, context)
}
}
export class Each<X, Y> extends Conversion<X[], Y[]> {
private readonly _step: Conversion<X, Y>
private readonly _msg: string
private readonly _filter: (x: X) => boolean
constructor(step: Conversion<X, Y>, msg?: string) {
constructor(step: Conversion<X, Y>, options?: { msg?: string }) {
super(
"Applies the given step on every element of the list",
[],
"OnEach(" + step.name + ")"
)
this._step = step
this._msg = msg
this._msg = options?.msg
}
convert(values: X[], context: ConversionContext): Y[] {

View file

@ -85,7 +85,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
description: trs(t.description, { title: layer.title.render }),
source: {
osmTags: {
and: ["id~*"],
and: ["id~[0-9]+", "comment_url~.*notes/[0-9]*g.json"],
},
geoJson:
"https://api.openstreetmap.org/api/0.6/notes.json?limit=10000&closed=" +

View file

@ -566,6 +566,16 @@ export class AddQuestionBox extends DesugaringStep<LayerConfigJson> {
}
export class AddEditingElements extends DesugaringStep<LayerConfigJson> {
static addedElements: string[] = [
"minimap",
"just_created",
"split_button",
"move_button",
"delete_button",
"last_edit",
"favourite_state",
"all_tags",
]
private readonly _desugaring: DesugaringContext
constructor(desugaring: DesugaringContext) {
@ -1210,7 +1220,7 @@ class AddFavouriteBadges extends DesugaringStep<LayerConfigJson> {
}
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
if (json.id === "favourite") {
if (json.source === "special" || json.source === "special:library") {
return json
}
const pr = json.pointRendering?.[0]

View file

@ -1,4 +1,4 @@
import { Conversion, DesugaringStep, Each, Fuse, On, Pipe, Pure } from "./Conversion"
import { Bypass, Conversion, DesugaringStep, Each, Fuse, On } from "./Conversion"
import { LayerConfigJson } from "../Json/LayerConfigJson"
import LayerConfig from "../LayerConfig"
import { Utils } from "../../../Utils"
@ -11,7 +11,6 @@ import { TagUtils } from "../../../Logic/Tags/TagUtils"
import { ExtractImages } from "./FixImages"
import { And } from "../../../Logic/Tags/And"
import Translations from "../../../UI/i18n/Translations"
import Svg from "../../../Svg"
import FilterConfigJson from "../Json/FilterConfigJson"
import DeleteConfig from "../DeleteConfig"
import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
@ -276,9 +275,9 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> {
new On(
"layers",
new Each(
new Pipe(
new ValidateLayer(undefined, isBuiltin, doesImageExist, false, true),
new Pure((x) => x?.raw)
new Bypass(
(layer) => Constants.added_by_default.indexOf(<any>layer.id) < 0,
new ValidateLayerConfig(undefined, isBuiltin, doesImageExist, false, true)
)
)
)
@ -968,7 +967,7 @@ export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
"Various validation on tagRenderingConfigs",
new DetectShadowedMappings(layerConfig),
new DetectConflictingAddExtraTags(),
// new DetectNonErasedKeysInMappings(),
// TODO enable new DetectNonErasedKeysInMappings(),
new DetectMappingsWithImages(doesImageExist),
new On("render", new ValidatePossibleLinks()),
new On("question", new ValidatePossibleLinks()),
@ -1350,6 +1349,29 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
}
}
export class ValidateLayerConfig extends DesugaringStep<LayerConfigJson> {
private readonly validator: ValidateLayer
constructor(
path: string,
isBuiltin: boolean,
doesImageExist: DoesImageExist,
studioValidations: boolean = false,
skipDefaultLayers: boolean = false
) {
super("Thin wrapper around 'ValidateLayer", [], "ValidateLayerConfig")
this.validator = new ValidateLayer(
path,
isBuiltin,
doesImageExist,
studioValidations,
skipDefaultLayers
)
}
convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson {
return this.validator.convert(json, context).raw
}
}
export class ValidateLayer extends Conversion<
LayerConfigJson,
{ parsed: LayerConfig; raw: LayerConfigJson }

View file

@ -462,6 +462,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
* @private
*/
private selectClosestAtCenter(i: number = 0) {
this.mapProperties.lastKeyNavigation.setData(Date.now() / 1000)
const toSelect = this.closestFeatures.features.data[i]
if (!toSelect) {
return
@ -567,46 +568,6 @@ export default class ThemeViewState implements SpecialVisualizationState {
})
}
private addLastClick(last_click: LastClickFeatureSource) {
// The last_click gets a _very_ special treatment as it interacts with various parts
this.featureProperties.trackFeatureSource(last_click)
this.indexedFeatures.addSource(last_click)
last_click.features.addCallbackAndRunD((features) => {
if (this.selectedLayer.data?.id === "last_click") {
// The last-click location moved, but we have selected the last click of the previous location
// So, we update _after_ clearing the selection to make sure no stray data is sticking around
this.selectedElement.setData(undefined)
this.selectedElement.setData(features[0])
}
})
new ShowDataLayer(this.map, {
features: new FilteringFeatureSource(this.newPointDialog, last_click),
doShowLayer: this.featureSwitches.featureSwitchEnableLogin,
layer: this.newPointDialog.layerDef,
selectedElement: this.selectedElement,
selectedLayer: this.selectedLayer,
metaTags: this.userRelatedState.preferencesAsTags,
onClick: (feature: Feature) => {
if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) {
this.map.data.flyTo({
zoom: Constants.minZoomLevelToAddNewPoint,
center: this.mapProperties.lastClickLocation.data,
})
return
}
// We first clear the selection to make sure no weird state is around
this.selectedLayer.setData(undefined)
this.selectedElement.setData(undefined)
this.selectedElement.setData(feature)
this.selectedLayer.setData(this.newPointDialog.layerDef)
},
})
}
/**
* Add the special layers to the map
*/
@ -663,9 +624,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
}
const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range")
const rangeIsDisplayed = rangeFLayer?.isDisplayed
if (
!QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef))
) {

View file

@ -121,9 +121,9 @@ export default class UploadTraceToOsmUI extends LoginToggle {
]).SetClass("flex p-2 rounded-xl border-2 subtle-border items-center"),
new Toggle(
confirmPanel,
new SubtleButton(new SvelteUIElement(Upload), t.title).onClick(() =>
clicked.setData(true)
),
new SubtleButton(new SvelteUIElement(Upload), t.title)
.onClick(() => clicked.setData(true))
.SetClass("w-full"),
clicked
),
uploadFinished

View file

@ -0,0 +1,10 @@
<script lang="ts">
export let properties: Record<string, string>
</script>
<div>
{JSON.stringify(properties)}
{properties?.id ?? "undefined"}
<a href={properties._backend +"/"+ properties?.id}>OSM</a>
</div>

View file

@ -1,8 +1,19 @@
<script lang="ts">
import { SpecialVisualization } from "../SpecialVisualization";
import type { SpecialVisualizationState } from "../SpecialVisualization";
import FavouriteSummary from "./FavouriteSummary.svelte";
/**
* A panel showing all your favourites
*/
export let state: SpecialVisualizationState
export let state: SpecialVisualizationState;
let favourites = state.favourites.allFavourites;
</script>
<div class="flex flex-col">
You marked {$favourites.length} locations as a favourite location.
This list is only visible to you
{#each $favourites as f}
<FavouriteSummary properties={f.properties} />
{/each}
</div>

View file

@ -31,14 +31,16 @@ export class ExportAsGpxViz implements SpecialVisualization {
t.downloadFeatureAsGpx.SetClass("font-bold text-lg"),
t.downloadGpxHelper.SetClass("subtle"),
]).SetClass("flex flex-col")
).onClick(() => {
console.log("Exporting as GPX!")
const tags = tagSource.data
const title = layer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track"
const gpx = GeoOperations.toGpx(<Feature<LineString>>feature, title)
Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", {
mimetype: "{gpx=application/gpx+xml}",
)
.SetClass("w-full")
.onClick(() => {
console.log("Exporting as GPX!")
const tags = tagSource.data
const title = layer.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "gpx_track"
const gpx = GeoOperations.toGpx(<Feature<LineString>>feature, title)
Utils.offerContentsAsDownloadableFile(gpx, title + "_mapcomplete_export.gpx", {
mimetype: "{gpx=application/gpx+xml}",
})
})
})
}
}

View file

@ -534,6 +534,9 @@ export default class SpecialVisualizations {
feature: Feature,
layer: LayerConfig
): BaseUIElement {
if (!layer.deletion) {
return undefined
}
return new SvelteUIElement(DeleteWizard, {
tags: tagSource,
deleteConfig: layer.deletion,
@ -873,20 +876,22 @@ export default class SpecialVisualizations {
t.downloadFeatureAsGeojson.SetClass("font-bold text-lg"),
t.downloadGeoJsonHelper.SetClass("subtle"),
]).SetClass("flex flex-col")
).onClick(() => {
console.log("Exporting as Geojson")
const tags = tagSource.data
const title =
layer?.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson"
const data = JSON.stringify(feature, null, " ")
Utils.offerContentsAsDownloadableFile(
data,
title + "_mapcomplete_export.geojson",
{
mimetype: "application/vnd.geo+json",
}
)
})
)
.onClick(() => {
console.log("Exporting as Geojson")
const tags = tagSource.data
const title =
layer?.title?.GetRenderValue(tags)?.Subs(tags)?.txt ?? "geojson"
const data = JSON.stringify(feature, null, " ")
Utils.offerContentsAsDownloadableFile(
data,
title + "_mapcomplete_export.geojson",
{
mimetype: "application/vnd.geo+json",
}
)
})
.SetClass("w-full")
},
},
{

View file

@ -15,7 +15,7 @@
import type { MapProperties } from "../Models/MapProperties";
import Geosearch from "./BigComponents/Geosearch.svelte";
import Translations from "./i18n/Translations";
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import { CogIcon, EyeIcon, HeartIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import Tr from "./Base/Tr.svelte";
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
import FloatOver from "./Base/FloatOver.svelte";
@ -64,6 +64,8 @@
import Community from "../assets/svg/Community.svelte";
import Download from "../assets/svg/Download.svelte";
import Share from "../assets/svg/Share.svelte";
import FavouriteSummary from "./Favourites/FavouriteSummary.svelte";
import Favourites from "./Favourites/Favourites.svelte";
export let state: ThemeViewState;
let layout = state.layout;
@ -493,22 +495,31 @@
</div>
<div class="flex" slot="title2">
<HeartIcon class="h-6 w-6" />
Your favourites
</div>
<div class="flex flex-col" slot="content2">
<h3>Your favourite locations</h3>
<Favourites {state}/>
</div>
<div class="flex" slot="title3">
<Community class="w-6 h-6"/>
<Tr t={Translations.t.communityIndex.title} />
</div>
<div class="m-2" slot="content2">
<div class="m-2" slot="content3">
<CommunityIndexView location={state.mapProperties.location} />
</div>
<div class="flex" slot="title3">
<div class="flex" slot="title4">
<EyeIcon class="w-6" />
<Tr t={Translations.t.privacy.title} />
</div>
<div class="m-2" slot="content3">
<div class="m-2" slot="content4">
<ToSvelte construct={() => new PrivacyPolicy()} />
</div>
<Tr slot="title4" t={Translations.t.advanced.title} />
<div class="m-2 flex flex-col" slot="content4">
<Tr slot="title5" t={Translations.t.advanced.title} />
<div class="m-2 flex flex-col" slot="content5">
<If condition={featureSwitches.featureSwitchEnableLogin}>
<OpenIdEditor mapProperties={state.mapProperties} />
<OpenJosm {state}/>