Huge refactoring: split readonly and writable stores

This commit is contained in:
Pieter Vander Vennet 2022-06-05 02:24:14 +02:00
parent 0946d8ac9c
commit 4283b76f36
95 changed files with 819 additions and 625 deletions

View file

@ -65,7 +65,6 @@ class AutomationPanel extends Combine {
console.warn("Triggered map on nextTileToHandle", tileIndex)
const start = new Date()
return AutomationPanel.TileHandler(layoutToUse, tileIndex, layerId, tagRenderingToAutomate.tagRendering, extraCommentText,
openChangeset,
(result, logMessage) => {
const end = new Date()
const timeNeeded = (end.getTime() - start.getTime()) / 1000;
@ -118,7 +117,6 @@ class AutomationPanel extends Combine {
}
private static TileHandler(layoutToUse: LayoutConfig, tileIndex: number, targetLayer: string, targetAction: TagRenderingConfig, extraCommentText: UIEventSource<string>,
openChangeset: UIEventSource<number>,
whenDone: ((result: string, logMessage?: string) => void)): BaseUIElement {
const state = new MapState(layoutToUse, {attemptLogin: false})
@ -204,7 +202,7 @@ class AutomationPanel extends Combine {
whenDone("no-action", "Inspected " + inspected + " elements: " + log.join("; "))
} else {
state.osmConnection.AttemptLogin()
state.changes.flushChanges("handled tile automatically, time to flush!", openChangeset)
state.changes.flushChanges("handled tile automatically, time to flush!")
whenDone("fixed", "Updated " + handled + " elements, inspected " + inspected + ": " + log.join("; "))
}
return true;

View file

@ -1,6 +1,6 @@
import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "./VariableUIElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Stores, UIEventSource} from "../../Logic/UIEventSource";
import Loading from "./Loading";
export default class AsyncLazy extends BaseUIElement {
@ -15,7 +15,7 @@ export default class AsyncLazy extends BaseUIElement {
// The caching of the BaseUIElement will guarantee that _f will only be called once
return new VariableUiElement(
UIEventSource.FromPromise(this._f()).map(el => {
Stores.FromPromise(this._f()).map(el => {
if (el === undefined) {
return new Loading()
}

View file

@ -1,14 +1,14 @@
import Translations from "../i18n/Translations";
import BaseUIElement from "../BaseUIElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
export default class Link extends BaseUIElement {
private readonly _href: string | UIEventSource<string>;
private readonly _href: string | Store<string>;
private readonly _embeddedShow: BaseUIElement;
private readonly _newTab: boolean;
constructor(embeddedShow: BaseUIElement | string, href: string | UIEventSource<string>, newTab: boolean = false) {
constructor(embeddedShow: BaseUIElement | string, href: string | Store<string>, newTab: boolean = false) {
super();
this._embeddedShow = Translations.W(embeddedShow);
this._href = href;

View file

@ -3,7 +3,7 @@ import Combine from "./Combine";
import BaseUIElement from "../BaseUIElement";
import Link from "./Link";
import Img from "./Img";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import {UIElement} from "../UIElement";
import {VariableUiElement} from "./VariableUIElement";
import Lazy from "./Lazy";
@ -13,11 +13,11 @@ import Loading from "./Loading";
export class SubtleButton extends UIElement {
private readonly imageUrl: string | BaseUIElement;
private readonly message: string | BaseUIElement;
private readonly options: { url?: string | UIEventSource<string>; newTab?: boolean ; imgSize?: string};
private readonly options: { url?: string | Store<string>; newTab?: boolean ; imgSize?: string};
constructor(imageUrl: string | BaseUIElement, message: string | BaseUIElement, options: {
url?: string | UIEventSource<string>,
url?: string | Store<string>,
newTab?: boolean,
imgSize?: "h-11 w-11" | string
} = undefined) {

View file

@ -1,11 +1,11 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import Combine from "./Combine";
export class VariableUiElement extends BaseUIElement {
private readonly _contents: UIEventSource<string | BaseUIElement | BaseUIElement[]>;
private readonly _contents: Store<string | BaseUIElement | BaseUIElement[]>;
constructor(contents: UIEventSource<string | BaseUIElement | BaseUIElement[]>) {
constructor(contents: Store<string | BaseUIElement | BaseUIElement[]>) {
super();
this._contents = contents;
}

View file

@ -2,12 +2,12 @@ import {Utils} from "../../Utils";
import {FixedInputElement} from "../Input/FixedInputElement";
import {RadioButton} from "../Input/RadioButton";
import {VariableUiElement} from "../Base/VariableUIElement";
import Toggle from "../Input/Toggle";
import Toggle, {ClickableToggle} from "../Input/Toggle";
import Combine from "../Base/Combine";
import Translations from "../i18n/Translations";
import {Translation} from "../i18n/Translation";
import Svg from "../../Svg";
import {UIEventSource} from "../../Logic/UIEventSource";
import {ImmutableStore, Store, UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import State from "../../State";
import FilteredLayer, {FilterState} from "../../Models/FilteredLayer";
@ -20,7 +20,6 @@ import {QueryParameters} from "../../Logic/Web/QueryParameters";
import {TagUtils} from "../../Logic/Tags/TagUtils";
import {InputElement} from "../Input/InputElement";
import {DropDown} from "../Input/DropDown";
import {UIElement} from "../UIElement";
export default class FilterView extends VariableUiElement {
constructor(filteredLayer: UIEventSource<FilteredLayer[]>,
@ -180,7 +179,8 @@ export default class FilterView extends VariableUiElement {
const filter = filterConfig.options[0]
const mappings = new Map<string, BaseUIElement>()
let allValid = new UIEventSource(true)
let allValid: Store<boolean> = new ImmutableStore(true)
var allFields: InputElement<string>[] = []
const properties = new UIEventSource<any>({})
for (const {name, type} of filter.fields) {
const value = QueryParameters.GetQueryParameter("filter-" + filterConfig.id + "-" + name, "", "Value for filter " + filterConfig.id)
@ -193,10 +193,11 @@ export default class FilterView extends VariableUiElement {
properties.data[name] = v.toLowerCase();
properties.ping()
})
allFields.push(field)
allValid = allValid.map(previous => previous && field.IsValid(stable.data) && stable.data !== "", [stable])
}
const tr = new SubstitutedTranslation(filter.question, new UIEventSource<any>({id: filterConfig.id}), State.state, mappings)
const trigger: UIEventSource<FilterState> = allValid.map(isValid => {
const trigger: Store<FilterState> = allValid.map(isValid => {
if (!isValid) {
return undefined
}
@ -221,8 +222,16 @@ export default class FilterView extends VariableUiElement {
state: JSON.stringify(props)
}
}, [properties])
const settableFilter = new UIEventSource<FilterState>(undefined)
trigger.addCallbackAndRun(state => settableFilter.setData(state))
settableFilter.addCallback(state => {
if(state.currentFilter === undefined){
allFields.forEach(f => f.GetValue().setData(undefined));
}
})
return [tr, trigger];
return [tr, settableFilter];
}
private static createCheckboxFilter(filterConfig: FilterConfig): [BaseUIElement, UIEventSource<FilterState>] {
@ -231,14 +240,14 @@ export default class FilterView extends VariableUiElement {
const icon = Svg.checkbox_filled_svg().SetClass("block mr-2 w-6");
const iconUnselected = Svg.checkbox_empty_svg().SetClass("block mr-2 w-6");
const toggle = new Toggle(
const toggle = new ClickableToggle(
new Combine([icon, option.question.Clone().SetClass("block")]).SetClass("flex"),
new Combine([iconUnselected, option.question.Clone().SetClass("block")]).SetClass("flex")
)
.ToggleOnClick()
.SetClass("block m-1")
return [toggle, toggle.isEnabled.map(enabled => enabled ? {
return [toggle, toggle.isEnabled.sync(enabled => enabled ? {
currentFilter: option.osmTags,
state: "true"
} : undefined, [],
@ -272,7 +281,7 @@ export default class FilterView extends VariableUiElement {
}
return [filterPicker,
filterPicker.GetValue().map(
filterPicker.GetValue().sync(
i => values[i],
[],
selected => {

View file

@ -1,5 +1,5 @@
import {VariableUiElement} from "../Base/VariableUIElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import Table from "../Base/Table";
import Combine from "../Base/Combine";
import {FixedUiElement} from "../Base/FixedUiElement";
@ -19,7 +19,7 @@ export default class Histogram<T> extends VariableUiElement {
"#fa61fa"
]
constructor(values: UIEventSource<string[]>,
constructor(values: Store<string[]>,
title: string | BaseUIElement,
countTitle: string | BaseUIElement,
options?: {

View file

@ -7,7 +7,7 @@ import * as personal from "../../assets/themes/personal/personal.json"
import Constants from "../../Models/Constants";
import BaseUIElement from "../BaseUIElement";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import {UIEventSource} from "../../Logic/UIEventSource";
import {ImmutableStore, Store, Stores, UIEventSource} from "../../Logic/UIEventSource";
import Loc from "../../Models/Loc";
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
import UserRelatedState from "../../Logic/State/UserRelatedState";
@ -117,7 +117,7 @@ export default class MoreScreen extends Combine {
private static createUrlFor(layout: { id: string, definition?: string },
isCustom: boolean,
state?: { locationControl?: UIEventSource<{ lat, lon, zoom }>, layoutToUse?: { id } }
): UIEventSource<string> {
): Store<string> {
if (layout === undefined) {
return undefined;
}
@ -163,7 +163,7 @@ export default class MoreScreen extends Combine {
.map(part => part[0] + "=" + part[1])
.join("&")
return `${linkPrefix}${params}${hash}`;
}) ?? new UIEventSource<string>(`${linkPrefix}`)
}) ?? new ImmutableStore<string>(`${linkPrefix}`)
}
@ -237,7 +237,7 @@ export default class MoreScreen extends Combine {
private static createUnofficialThemeList(buttonClass: string, state: UserRelatedState, themeListClasses: string, search: UIEventSource<string>): BaseUIElement {
const prefix = "mapcomplete-unofficial-theme-";
var currentIds: UIEventSource<string[]> = state.osmConnection.preferencesHandler.preferences
var currentIds: Store<string[]> = state.osmConnection.preferencesHandler.preferences
.map(allPreferences => {
const ids: string[] = []
@ -250,7 +250,7 @@ export default class MoreScreen extends Combine {
return ids
});
var stableIds = UIEventSource.ListStabilized<string>(currentIds)
var stableIds = Stores.ListStabilized<string>(currentIds)
return new VariableUiElement(
stableIds.map(ids => {
const allThemes: { element: BaseUIElement, predicate?: (s: string) => boolean }[] = []

View file

@ -2,9 +2,8 @@ import {VariableUiElement} from "../Base/VariableUIElement";
import {Translation} from "../i18n/Translation";
import Svg from "../../Svg";
import Combine from "../Base/Combine";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import {Utils} from "../../Utils";
import Toggle from "../Input/Toggle";
import Translations from "../i18n/Translations";
import BaseUIElement from "../BaseUIElement";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
@ -13,7 +12,7 @@ import Loc from "../../Models/Loc";
import BaseLayer from "../../Models/BaseLayer";
import FilteredLayer from "../../Models/FilteredLayer";
import {InputElement} from "../Input/InputElement";
import CheckBoxes, {CheckBox} from "../Input/Checkboxes";
import {CheckBox} from "../Input/Checkboxes";
import {SubtleButton} from "../Base/SubtleButton";
import LZString from "lz-string";
@ -24,7 +23,7 @@ export default class ShareScreen extends Combine {
const tr = Translations.t.general.sharescreen;
const optionCheckboxes: InputElement<boolean>[] = []
const optionParts: (UIEventSource<string>)[] = [];
const optionParts: (Store<string>)[] = [];
const includeLocation = new CheckBox(tr.fsIncludeCurrentLocation, true)
optionCheckboxes.push(includeLocation);

View file

@ -124,7 +124,7 @@ export default class TranslatorsPanel extends Toggle {
const completeness = new Map<string, number>()
const untranslated = new Map<string, string[]>()
Utils.WalkObject(layout, (o, path) => {
Utils.WalkObject(layout, (o) => {
const translation = <Translation><any>o;
if (translation.translations["*"] !== undefined) {
return

View file

@ -2,13 +2,13 @@ import Combine from "../Base/Combine";
import Translations from "../i18n/Translations";
import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import {LicenseInfo} from "../../Logic/ImageProviders/LicenseInfo";
import {FixedUiElement} from "../Base/FixedUiElement";
export default class Attribution extends VariableUiElement {
constructor(license: UIEventSource<LicenseInfo>, icon: BaseUIElement, date?: Date) {
constructor(license: Store<LicenseInfo>, icon: BaseUIElement, date?: Date) {
if (license === undefined) {
throw "No license source given in the attribution element"
}

View file

@ -1,6 +1,6 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store} from "../../Logic/UIEventSource";
import Translations from "../i18n/Translations";
import Toggle from "../Input/Toggle";
import Toggle, {ClickableToggle} from "../Input/Toggle";
import Combine from "../Base/Combine";
import Svg from "../../Svg";
import {Tag} from "../../Logic/Tags/Tag";
@ -11,7 +11,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export default class DeleteImage extends Toggle {
constructor(key: string, tags: UIEventSource<any>, state: { layoutToUse: LayoutConfig, changes?: Changes, osmConnection?: OsmConnection }) {
constructor(key: string, tags: Store<any>, state: { layoutToUse: LayoutConfig, changes?: Changes, osmConnection?: OsmConnection }) {
const oldValue = tags.data[key]
const isDeletedBadge = Translations.t.image.isDeleted.Clone()
.SetClass("rounded-full p-1")
@ -37,7 +37,7 @@ export default class DeleteImage extends Toggle {
const cancelButton = Translations.t.general.cancel.Clone().SetClass("bg-white pl-4 pr-4").SetStyle("border-bottom-left-radius:30rem; border-bottom-right-radius: 30rem;");
const openDelete = Svg.delete_icon_svg().SetStyle("width: 2em; height: 2em; display:block;")
const deleteDialog = new Toggle(
const deleteDialog = new ClickableToggle(
new Combine([
deleteButton,
cancelButton

View file

@ -1,5 +1,5 @@
import {SlideShow} from "./SlideShow";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store} from "../../Logic/UIEventSource";
import Combine from "../Base/Combine";
import DeleteImage from "./DeleteImage";
import {AttributedImage} from "./AttributedImage";
@ -12,8 +12,8 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
export class ImageCarousel extends Toggle {
constructor(images: UIEventSource<{ key: string, url: string, provider: ImageProvider }[]>,
tags: UIEventSource<any>,
constructor(images: Store<{ key: string, url: string, provider: ImageProvider }[]>,
tags: Store<any>,
state: { osmConnection?: OsmConnection, changes?: Changes, layoutToUse: LayoutConfig }) {
const uiElements = images.map((imageURLS: { key: string, url: string, provider: ImageProvider }[]) => {
const uiElements: BaseUIElement[] = [];

View file

@ -1,4 +1,4 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import Combine from "../Base/Combine";
import Translations from "../i18n/Translations";
import Svg from "../../Svg";
@ -22,12 +22,12 @@ export class ImageUploadFlow extends Toggle {
private static readonly uploadCountsPerId = new Map<string, UIEventSource<number>>()
constructor(tagsSource: UIEventSource<any>,
constructor(tagsSource: Store<any>,
state: {
osmConnection: OsmConnection;
layoutToUse: LayoutConfig;
changes: Changes,
featureSwitchUserbadge: UIEventSource<boolean>;
featureSwitchUserbadge: Store<boolean>;
},
imagePrefix: string = "image", text: string = undefined) {
const perId = ImageUploadFlow.uploadCountsPerId

View file

@ -1,4 +1,4 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import {Utils} from "../../Utils";
import Combine from "../Base/Combine";
@ -6,9 +6,9 @@ import Combine from "../Base/Combine";
export class SlideShow extends BaseUIElement {
private readonly embeddedElements: UIEventSource<BaseUIElement[]>;
private readonly embeddedElements: Store<BaseUIElement[]>;
constructor(embeddedElements: UIEventSource<BaseUIElement[]>) {
constructor(embeddedElements: Store<BaseUIElement[]>) {
super()
this.embeddedElements = embeddedElements;
this.SetStyle("scroll-snap-type: x mandatory; overflow-x: auto")

View file

@ -1,12 +1,11 @@
import Combine from "../Base/Combine";
import {FlowStep} from "./FlowStep";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store} from "../../Logic/UIEventSource";
import ValidatedTextField from "../Input/ValidatedTextField";
import {LocalStorageSource} from "../../Logic/Web/LocalStorageSource";
import Title from "../Base/Title";
import {VariableUiElement} from "../Base/VariableUIElement";
import Translations from "../i18n/Translations";
import {FixedUiElement} from "../Base/FixedUiElement";
import {SubtleButton} from "../Base/SubtleButton";
import Svg from "../../Svg";
import {Utils} from "../../Utils";
@ -19,14 +18,14 @@ export class AskMetadata extends Combine implements FlowStep<{
theme: string
}> {
public readonly Value: UIEventSource<{
public readonly Value: Store<{
features: any[],
wikilink: string,
intro: string,
source: string,
theme: string
}>;
public readonly IsValid: UIEventSource<boolean>;
public readonly IsValid: Store<boolean>;
constructor(params: ({ features: any[], theme: string })) {
const t = Translations.t.importHelper.askMetadata

View file

@ -2,7 +2,7 @@ import Combine from "../Base/Combine";
import {FlowStep} from "./FlowStep";
import {BBox} from "../../Logic/BBox";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import CreateNoteImportLayer from "../../Models/ThemeConfig/Conversion/CreateNoteImportLayer";
import FilteredLayer, {FilterState} from "../../Models/FilteredLayer";
import GeoJsonSource from "../../Logic/FeatureSource/Sources/GeoJsonSource";
@ -17,7 +17,6 @@ import * as import_candidate from "../../assets/layers/import_candidate/import_c
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
import Title from "../Base/Title";
import Loading from "../Base/Loading";
import {FixedUiElement} from "../Base/FixedUiElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import * as known_layers from "../../assets/generated/known_layers.json"
import {LayerConfigJson} from "../../Models/ThemeConfig/Json/LayerConfigJson";
@ -28,8 +27,8 @@ import Translations from "../i18n/Translations";
*/
export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, features: any[], theme: string }> {
public IsValid: UIEventSource<boolean>
public Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, features: any[], theme: string }>
public IsValid: Store<boolean>
public Value: Store<{ bbox: BBox, layer: LayerConfig, features: any[], theme: string }>
constructor(state, params: { bbox: BBox, layer: LayerConfig, features: any[], theme: string }) {
@ -94,7 +93,7 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
state,
zoomToFeatures: true,
leafletMap: comparisonMap.leafletMap,
features: new StaticFeatureSource(partitionedImportPoints.map(p => p.hasNearby), false),
features: new StaticFeatureSource(partitionedImportPoints.map(p => p.hasNearby)),
popup: (tags, layer) => new FeatureInfoBox(tags, layer, state)
})
@ -103,7 +102,8 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{
new VariableUiElement(
alreadyOpenImportNotes.features.map(notesWithImport => {
if (allNotesWithinBbox.state.data !== undefined && allNotesWithinBbox.state.data["error"] !== undefined) {
t.loadingFailed.Subs(allNotesWithinBbox.state.data)
const error = allNotesWithinBbox.state.data["error"]
t.loadingFailed.Subs({error})
}
if (allNotesWithinBbox.features.data === undefined || allNotesWithinBbox.features.data.length === 0) {
return new Loading(t.loading)

View file

@ -1,6 +1,6 @@
import Combine from "../Base/Combine";
import {FlowStep} from "./FlowStep";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import Link from "../Base/Link";
import CheckBoxes from "../Input/Checkboxes";
import Title from "../Base/Title";
@ -8,8 +8,8 @@ import Translations from "../i18n/Translations";
export class ConfirmProcess extends Combine implements FlowStep<{ features: any[], theme: string }> {
public IsValid: UIEventSource<boolean>
public Value: UIEventSource<{ features: any[], theme: string }>
public IsValid: Store<boolean>
public Value: Store<{ features: any[], theme: string }>
constructor(v: { features: any[], theme: string }) {
const t = Translations.t.importHelper.confirmProcess;

View file

@ -3,7 +3,7 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import Combine from "../Base/Combine";
import Title from "../Base/Title";
import {Overpass} from "../../Logic/Osm/Overpass";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import Constants from "../../Models/Constants";
import RelationsTracker from "../../Logic/Osm/RelationsTracker";
import {VariableUiElement} from "../Base/VariableUIElement";
@ -35,7 +35,7 @@ import Translations from "../i18n/Translations";
export default class ConflationChecker extends Combine implements FlowStep<{ features: any[], theme: string }> {
public readonly IsValid
public readonly Value: UIEventSource<{ features: any[], theme: string }>
public readonly Value: Store<{ features: any[], theme: string }>
constructor(
state,
@ -89,7 +89,7 @@ export default class ConflationChecker extends Combine implements FlowStep<{ fea
});
const geojson: UIEventSource<any> = fromLocalStorage.map(d => {
const geojson: Store<any> = fromLocalStorage.map(d => {
if (d === undefined) {
return undefined
}
@ -120,7 +120,7 @@ export default class ConflationChecker extends Combine implements FlowStep<{ fea
}
const bounds = osmLiveData.bounds.data
return geojson.features.filter(f => BBox.get(f).overlapsWith(bounds))
}, [osmLiveData.bounds, zoomLevel.GetValue()]), false);
}, [osmLiveData.bounds, zoomLevel.GetValue()]));
new ShowDataLayer({
@ -129,9 +129,9 @@ export default class ConflationChecker extends Combine implements FlowStep<{ fea
leafletMap: osmLiveData.leafletMap,
popup: undefined,
zoomToFeatures: true,
features: new StaticFeatureSource([
features: StaticFeatureSource.fromGeojson([
bbox.asGeoJson({})
], false)
])
})
@ -150,7 +150,7 @@ export default class ConflationChecker extends Combine implements FlowStep<{ fea
leafletMap: osmLiveData.leafletMap,
popup: (tags, layer) => new FeatureInfoBox(tags, layer, state),
zoomToFeatures: false,
features: new StaticFeatureSource(toImport.features, false)
features: StaticFeatureSource.fromGeojson(toImport.features)
})
const nearbyCutoff = ValidatedTextField.ForType("pnat").ConstructInputElement()
@ -172,11 +172,11 @@ export default class ConflationChecker extends Combine implements FlowStep<{ fea
return osmData.features.filter(f =>
toImport.features.some(imp =>
maxDist >= GeoOperations.distanceBetween(imp.geometry.coordinates, GeoOperations.centerpointCoordinates(f))))
}, [nearbyCutoff.GetValue().stabilized(500)]), false);
}, [nearbyCutoff.GetValue().stabilized(500)]));
const paritionedImport = ImportUtils.partitionFeaturesIfNearby(toImport, geojson, nearbyCutoff.GetValue().map(Number));
// Featuresource showing OSM-features which are nearby a toImport-feature
const toImportWithNearby = new StaticFeatureSource(paritionedImport.map(els => els?.hasNearby ?? []), false);
const toImportWithNearby = new StaticFeatureSource(paritionedImport.map(els => els?.hasNearby ?? []));
new ShowDataLayer({
layerToShow: layer,

View file

@ -1,4 +1,4 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import Combine from "../Base/Combine";
import BaseUIElement from "../BaseUIElement";
import {SubtleButton} from "../Base/SubtleButton";
@ -10,8 +10,8 @@ import {UIElement} from "../UIElement";
import {FixedUiElement} from "../Base/FixedUiElement";
export interface FlowStep<T> extends BaseUIElement {
readonly IsValid: UIEventSource<boolean>
readonly Value: UIEventSource<T>
readonly IsValid: Store<boolean>
readonly Value: Store<T>
}
export class FlowPanelFactory<T> {

View file

@ -12,7 +12,6 @@ import ConflationChecker from "./ConflationChecker";
import {AskMetadata} from "./AskMetadata";
import {ConfirmProcess} from "./ConfirmProcess";
import {CreateNotes} from "./CreateNotes";
import {FixedUiElement} from "../Base/FixedUiElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import List from "../Base/List";
import {CompareToAlreadyExistingNotes} from "./CompareToAlreadyExistingNotes";

View file

@ -1,8 +1,8 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store} from "../../Logic/UIEventSource";
import {GeoOperations} from "../../Logic/GeoOperations";
export class ImportUtils {
public static partitionFeaturesIfNearby(toPartitionFeatureCollection: ({ features: any[] }), compareWith: UIEventSource<{ features: any[] }>, cutoffDistanceInMeters: UIEventSource<number>): UIEventSource<{ hasNearby: any[], noNearby: any[] }> {
public static partitionFeaturesIfNearby(toPartitionFeatureCollection: ({ features: any[] }), compareWith: Store<{ features: any[] }>, cutoffDistanceInMeters: Store<number>): Store<{ hasNearby: any[], noNearby: any[] }> {
return compareWith.map(osmData => {
if (osmData?.features === undefined) {
return undefined

View file

@ -13,7 +13,7 @@ import BaseUIElement from "../BaseUIElement";
import ValidatedTextField from "../Input/ValidatedTextField";
import {SubtleButton} from "../Base/SubtleButton";
import Svg from "../../Svg";
import Toggle from "../Input/Toggle";
import Toggle, {ClickableToggle} from "../Input/Toggle";
import Table from "../Base/Table";
import LeftIndex from "../Base/LeftIndex";
import Toggleable, {Accordeon} from "../Base/Toggleable";
@ -271,7 +271,7 @@ class BatchView extends Toggleable {
const selected = new Combine([BatchView.icons[status]().SetClass("h-6 m-1"), count + " " + status])
.SetClass("flex ml-1 mb-1 pl-1 pr-3 items-center rounded-full border-4 border-black animate-pulse")
const toggle = new Toggle(selected, normal, filterOn.map(f => f === status, [], (selected, previous) => {
const toggle = new ClickableToggle(selected, normal, filterOn.sync(f => f === status, [], (selected, previous) => {
if (selected) {
return status;
}

View file

@ -1,7 +1,7 @@
import Combine from "../Base/Combine";
import {FlowStep} from "./FlowStep";
import UserRelatedState from "../../Logic/State/UserRelatedState";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import Translations from "../i18n/Translations";
import Title from "../Base/Title";
import {VariableUiElement} from "../Base/VariableUIElement";
@ -15,8 +15,8 @@ import MoreScreen from "../BigComponents/MoreScreen";
import CheckBoxes from "../Input/Checkboxes";
export default class LoginToImport extends Combine implements FlowStep<UserRelatedState> {
readonly IsValid: UIEventSource<boolean>;
readonly Value: UIEventSource<UserRelatedState>;
readonly IsValid: Store<boolean>;
readonly Value: Store<UserRelatedState>;
private static readonly whitelist = [15015689];

View file

@ -1,5 +1,5 @@
import Combine from "../Base/Combine";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import {BBox} from "../../Logic/BBox";
import UserRelatedState from "../../Logic/State/UserRelatedState";
import Translations from "../i18n/Translations";
@ -27,7 +27,7 @@ import {AllTagsPanel} from "../AllTagsPanel";
class PreviewPanel extends ScrollableFullScreen {
constructor(tags: UIEventSource<any>, layer) {
constructor(tags: UIEventSource<any>) {
super(
_ => new FixedUiElement("Element to import"),
_ => new Combine(["The tags are:",
@ -43,8 +43,8 @@ class PreviewPanel extends ScrollableFullScreen {
* Shows the data to import on a map, asks for the correct layer to be selected
*/
export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, features: any[] }> {
public readonly IsValid: UIEventSource<boolean>;
public readonly Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, features: any[] }>
public readonly IsValid: Store<boolean>;
public readonly Value: Store<{ bbox: BBox, layer: LayerConfig, features: any[] }>
constructor(
state: UserRelatedState,
@ -85,7 +85,7 @@ export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer:
return copy
})
const matching: UIEventSource<{ properties: any, geometry: { coordinates: [number, number] } }[]> = layerPicker.GetValue().map((layer: LayerConfig) => {
const matching: Store<{ properties: any, geometry: { coordinates: [number, number] } }[]> = layerPicker.GetValue().map((layer: LayerConfig) => {
if (layer === undefined) {
return [];
}
@ -120,9 +120,9 @@ export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer:
appliedFilters: new UIEventSource<Map<string, FilterState>>(undefined)
}))),
zoomToFeatures: true,
features: new StaticFeatureSource(matching, false),
features: StaticFeatureSource.fromDateless(matching.map(features => features.map(feature => ({feature})))),
leafletMap: map.leafletMap,
popup: (tag, layer) => new PreviewPanel(tag, layer).SetClass("font-lg")
popup: (tag) => new PreviewPanel(tag).SetClass("font-lg")
})
var bbox = matching.map(feats => BBox.bboxAroundAll(feats.map(f => new BBox([f.geometry.coordinates]))))

View file

@ -1,5 +1,5 @@
import Combine from "../Base/Combine";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import UserRelatedState from "../../Logic/State/UserRelatedState";
import Translations from "../i18n/Translations";
import {Utils} from "../../Utils";
@ -15,8 +15,8 @@ import CheckBoxes from "../Input/Checkboxes";
* Shows the attributes by value, requests to check them of
*/
export class PreviewAttributesPanel extends Combine implements FlowStep<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }> {
public readonly IsValid: UIEventSource<boolean>;
public readonly Value: UIEventSource<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }>
public readonly IsValid: Store<boolean>;
public readonly Value: Store<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }>
constructor(
state: UserRelatedState,

View file

@ -1,5 +1,5 @@
import Combine from "../Base/Combine";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, Stores} from "../../Logic/UIEventSource";
import Translations from "../i18n/Translations";
import {SubtleButton} from "../Base/SubtleButton";
import {VariableUiElement} from "../Base/VariableUIElement";
@ -10,7 +10,6 @@ import FileSelectorButton from "../Input/FileSelectorButton";
import {FlowStep} from "./FlowStep";
import {parse} from "papaparse";
import {FixedUiElement} from "../Base/FixedUiElement";
import {del} from "idb-keyval";
import {TagUtils} from "../../Logic/Tags/TagUtils";
class FileSelector extends InputElementMap<FileList, { name: string, contents: Promise<string> }> {
@ -38,11 +37,11 @@ class FileSelector extends InputElementMap<FileList, { name: string, contents: P
*/
export class RequestFile extends Combine implements FlowStep<{features: any[]}> {
public readonly IsValid: UIEventSource<boolean>
public readonly IsValid: Store<boolean>
/**
* The loaded GeoJSON
*/
public readonly Value: UIEventSource<{features: any[]}>
public readonly Value: Store<{features: any[]}>
constructor() {
const t = Translations.t.importHelper.selectFile;
@ -54,15 +53,15 @@ export class RequestFile extends Combine implements FlowStep<{features: any[]}>
return t.loadedFilesAre.Subs({file: file.name}).SetClass("thanks")
}))
const text = UIEventSource.flatten(
const text = Stores.flatten(
csvSelector.GetValue().map(v => {
if (v === undefined) {
return undefined
}
return UIEventSource.FromPromise(v.contents)
return Stores.FromPromise(v.contents)
}))
const asGeoJson: UIEventSource<any | { error: string | BaseUIElement }> = text.map(src => {
const asGeoJson: Store<any | { error: string | BaseUIElement }> = text.map((src: string) => {
if (src === undefined) {
return undefined
}

View file

@ -1,6 +1,6 @@
import {FlowStep} from "./FlowStep";
import Combine from "../Base/Combine";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store} from "../../Logic/UIEventSource";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import {InputElement} from "../Input/InputElement";
import {AllKnownLayouts} from "../../Customizations/AllKnownLayouts";
@ -24,13 +24,13 @@ export default class SelectTheme extends Combine implements FlowStep<{
bbox: BBox,
}> {
public readonly Value: UIEventSource<{
public readonly Value: Store<{
features: any[],
theme: string,
layer: LayerConfig,
bbox: BBox,
}>;
public readonly IsValid: UIEventSource<boolean>;
public readonly IsValid: Store<boolean>;
constructor(params: ({ features: any[], layer: LayerConfig, bbox: BBox, })) {
const t = Translations.t.importHelper.selectTheme

View file

@ -1,11 +1,12 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
export abstract class InputElement<T> extends BaseUIElement {
abstract GetValue(): UIEventSource<T>;
abstract IsValid(t: T): boolean;
export interface ReadonlyInputElement<T> extends BaseUIElement{
GetValue(): Store<T>;
}
export abstract class InputElement<T> extends BaseUIElement implements ReadonlyInputElement<any>{
abstract GetValue(): UIEventSource<T>;
abstract IsValid(t: T): boolean;
}

View file

@ -21,7 +21,7 @@ export default class InputElementMap<T, X> extends InputElement<X> {
this.toX = toX;
this._inputElement = inputElement;
const self = this;
this._value = inputElement.GetValue().map(
this._value = inputElement.GetValue().sync(
(t => {
const newX = toX(t);
const currentX = self.GetValue()?.data;

View file

@ -1,6 +1,6 @@
import {InputElement} from "./InputElement";
import {ReadonlyInputElement} from "./InputElement";
import Loc from "../../Models/Loc";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import Minimap, {MinimapObj} from "../Base/Minimap";
import BaseLayer from "../../Models/BaseLayer";
import Combine from "../Base/Combine";
@ -17,11 +17,10 @@ import BaseUIElement from "../BaseUIElement";
import Toggle from "./Toggle";
import * as matchpoint from "../../assets/layers/matchpoint/matchpoint.json"
export default class LocationInput extends InputElement<Loc> implements MinimapObj {
export default class LocationInput extends BaseUIElement implements ReadonlyInputElement<Loc>, MinimapObj {
private static readonly matchLayer = new LayerConfig(matchpoint, "LocationInput.matchpoint", true)
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
public readonly snappedOnto: UIEventSource<any> = new UIEventSource<any>(undefined)
public readonly _matching_layer: LayerConfig;
public readonly leafletMap: UIEventSource<any>
@ -33,9 +32,9 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO
* The features to which the input should be snapped
* @private
*/
private readonly _snapTo: UIEventSource<{ feature: any }[]>
private readonly _value: UIEventSource<Loc>
private readonly _snappedPoint: UIEventSource<any>
private readonly _snapTo: Store<{ feature: any }[]>
private readonly _value: Store<Loc>
private readonly _snappedPoint: Store<any>
private readonly _maxSnapDistance: number
private readonly _snappedPointTags: any;
private readonly _bounds: UIEventSource<BBox>;
@ -151,7 +150,7 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO
this.location = this.map.location;
}
GetValue(): UIEventSource<Loc> {
GetValue(): Store<Loc> {
return this._value;
}
@ -188,7 +187,7 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO
// Show the lines to snap to
console.log("Constructing the snap-to layer", this._snapTo)
new ShowDataMultiLayer({
features: new StaticFeatureSource(this._snapTo, true),
features: StaticFeatureSource.fromDateless(this._snapTo),
zoomToFeatures: false,
leafletMap: this.map.leafletMap,
layers: State.state.filteredLayers
@ -201,8 +200,10 @@ export default class LocationInput extends InputElement<Loc> implements MinimapO
}
return [{feature: loc}];
})
console.log("Constructing the match layer", matchPoint)
new ShowDataLayer({
features: new StaticFeatureSource(matchPoint, true),
features: StaticFeatureSource.fromDateless(matchPoint),
zoomToFeatures: false,
leafletMap: this.map.leafletMap,
layerToShow: this._matching_layer,

View file

@ -152,7 +152,7 @@ export class RadioButton<T> extends InputElement<T> {
form.appendChild(block);
}
value.addCallbackAndRun((selected) => {
value.addCallbackAndRun((selected:T) => {
let somethingChecked = false;
for (let i = 0; i < inputs.length; i++) {
let input = inputs[i];

View file

@ -1,4 +1,4 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import Lazy from "../Base/Lazy";
@ -9,16 +9,16 @@ import Lazy from "../Base/Lazy";
*/
export default class Toggle extends VariableUiElement {
public readonly isEnabled: UIEventSource<boolean>;
public readonly isEnabled: Store<boolean>;
constructor(showEnabled: string | BaseUIElement, showDisabled: string | BaseUIElement, isEnabled: UIEventSource<boolean> = new UIEventSource<boolean>(false)) {
constructor(showEnabled: string | BaseUIElement, showDisabled: string | BaseUIElement, isEnabled: Store<boolean> = new UIEventSource<boolean>(false)) {
super(
isEnabled?.map(isEnabled => isEnabled ? showEnabled : showDisabled)
);
this.isEnabled = isEnabled
}
public static If(condition: UIEventSource<boolean>, constructor: () => BaseUIElement): BaseUIElement {
public static If(condition: Store<boolean>, constructor: () => BaseUIElement): BaseUIElement {
if (constructor === undefined) {
return undefined
}
@ -29,8 +29,24 @@ export default class Toggle extends VariableUiElement {
)
}
}
public ToggleOnClick(): Toggle {
/**
* Same as `Toggle`, but will swap on click
*/
export class ClickableToggle extends Toggle {
public readonly isEnabled: UIEventSource<boolean>;
constructor(showEnabled: string | BaseUIElement, showDisabled: string | BaseUIElement, isEnabled: UIEventSource<boolean> = new UIEventSource<boolean>(false)) {
super(
showEnabled, showDisabled, isEnabled
);
this.isEnabled = isEnabled
}
public ToggleOnClick(): ClickableToggle {
const self = this;
this.onClick(() => {
self.isEnabled.setData(!self.isEnabled.data);

View file

@ -1,23 +1,22 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {InputElement, ReadonlyInputElement} from "./InputElement";
import {Store} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
export default class VariableInputElement<T> extends InputElement<T> {
export default class VariableInputElement<T> extends BaseUIElement implements ReadonlyInputElement<T> {
private readonly value: UIEventSource<T>;
private readonly value: Store<T>;
private readonly element: BaseUIElement
private readonly upstream: UIEventSource<InputElement<T>>;
constructor(upstream: UIEventSource<InputElement<T>>) {
private readonly upstream: Store<InputElement<T>>;
constructor(upstream: Store<InputElement<T>>) {
super()
this.upstream = upstream;
this.value = upstream.bind(v => v.GetValue())
this.element = new VariableUiElement(upstream)
}
GetValue(): UIEventSource<T> {
GetValue(): Store<T> {
return this.value;
}

View file

@ -367,6 +367,9 @@ export class OH {
return OH.ToString(OH.MergeTimes(OH.Parse(str)))
}
/**
* Parses a string into Opening Hours
*/
public static Parse(rules: string): OpeningHour[] {
if (rules === undefined || rules === "") {
return []

View file

@ -4,15 +4,14 @@
* Exports everything conventiently as a string, for direct use
*/
import OpeningHoursPicker from "./OpeningHoursPicker";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import {VariableUiElement} from "../Base/VariableUIElement";
import Combine from "../Base/Combine";
import {FixedUiElement} from "../Base/FixedUiElement";
import {OH} from "./OpeningHours";
import {OH, OpeningHour} from "./OpeningHours";
import {InputElement} from "../Input/InputElement";
import PublicHolidayInput from "./PublicHolidayInput";
import Translations from "../i18n/Translations";
import {Utils} from "../../Utils";
import BaseUIElement from "../BaseUIElement";
@ -28,8 +27,7 @@ export default class OpeningHoursInput extends InputElement<string> {
this._value = value;
let valueWithoutPrefix = value
if (prefix !== "" && postfix !== "") {
valueWithoutPrefix = value.map(str => {
valueWithoutPrefix = value.sync(str => {
if (str === undefined) {
return undefined;
}
@ -55,7 +53,7 @@ export default class OpeningHoursInput extends InputElement<string> {
})
}
const leftoverRules = valueWithoutPrefix.map<string[]>(str => {
const leftoverRules: Store<string[]> = valueWithoutPrefix.map(str => {
if (str === undefined) {
return []
}
@ -72,35 +70,40 @@ export default class OpeningHoursInput extends InputElement<string> {
}
return leftOvers;
})
// Note: MUST be bound AFTER the leftover rules!
const rulesFromOhPicker = valueWithoutPrefix.map(OH.Parse);
const ph = valueWithoutPrefix.map<string>(str => {
if (str === undefined) {
return ""
let ph = "";
const rules = valueWithoutPrefix.data?.split(";") ?? [];
for (const rule of rules) {
if (OH.ParsePHRule(rule) !== null) {
ph = rule;
break;
}
const rules = str.split(";");
for (const rule of rules) {
if (OH.ParsePHRule(rule) !== null) {
return rule;
}
}
return "";
})
const phSelector = new PublicHolidayInput(ph);
function update() {
const regular = OH.ToString(rulesFromOhPicker.data);
const rules: string[] = [
regular,
...leftoverRules.data,
ph.data
]
valueWithoutPrefix.setData(Utils.NoEmpty(rules).join(";"));
}
const phSelector = new PublicHolidayInput(new UIEventSource<string>(ph));
// Note: MUST be bound AFTER the leftover rules!
const rulesFromOhPicker: UIEventSource<OpeningHour[]> = valueWithoutPrefix.sync(str => {
console.log(">> Parsing '"+ str+"'")
return OH.Parse(str);
}, [leftoverRules, phSelector.GetValue()], (rules, oldString) => {
let str = OH.ToString(rules);
const ph = phSelector.GetValue().data;
if(ph){
str += "; "+ph
}
str += leftoverRules.data.join("; ")
if(!str.endsWith(";")){
str += ";"
}
if(str === oldString){
return oldString; // We pass a reference to the old string to stabilize the EventSource
}
console.log("Reconstructed '"+ str+"'")
return str;
});
rulesFromOhPicker.addCallback(update);
ph.addCallback(update);
const leftoverWarning = new VariableUiElement(leftoverRules.map((leftovers: string[]) => {

View file

@ -1,7 +1,7 @@
import {SpecialVisualization} from "../SpecialVisualizations";
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState";
import BaseUIElement from "../BaseUIElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Stores, UIEventSource} from "../../Logic/UIEventSource";
import {DefaultGuiState} from "../DefaultGuiState";
import {SubtleButton} from "../Base/SubtleButton";
import Img from "../Base/Img";
@ -97,7 +97,7 @@ class ApplyButton extends UIElement {
new ShowDataLayer({
leafletMap: previewMap.leafletMap,
zoomToFeatures: true,
features: new StaticFeatureSource(features, false),
features: StaticFeatureSource.fromGeojson(features),
state: this.state,
layerToShow: this.layer.layerDef,
})
@ -218,7 +218,7 @@ export default class AutoApplyButton implements SpecialVisualization {
return new Lazy(() => {
const to_parse = new UIEventSource(undefined)
// Very ugly hack: read the value every 500ms
UIEventSource.Chronic(500, () => to_parse.data === undefined).addCallback(() => {
Stores.Chronic(500, () => to_parse.data === undefined).addCallback(() => {
const applicable = tagSource.data[argument[1]]
to_parse.setData(applicable)
})

View file

@ -3,7 +3,7 @@ import Toggle from "../Input/Toggle";
import Translations from "../i18n/Translations";
import Svg from "../../Svg";
import DeleteAction from "../../Logic/Osm/Actions/DeleteAction";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
import Combine from "../Base/Combine";
import {SubtleButton} from "../Base/SubtleButton";
@ -106,7 +106,7 @@ export default class DeleteWizard extends Toggle {
}
)
const isShown: UIEventSource<boolean> = tagsSource.map(tgs => tgs.id.indexOf("-") < 0)
const isShown: Store<boolean> = tagsSource.map(tgs => tgs.id.indexOf("-") < 0)
const deleteOptionPicker = DeleteWizard.constructMultipleChoice(options, tagsSource, state);
const deleteDialog = new Combine([
@ -350,8 +350,10 @@ class DeleteabilityChecker {
if (allByMyself.data === null && useTheInternet) {
// We kickoff the download here as it hasn't yet been downloaded. Note that this is mapped onto 'all by myself' above
OsmObject.DownloadHistory(id).map(versions => versions.map(version => version.tags["_last_edit:contributor:uid"])).syncWith(previousEditors)
const hist = OsmObject.DownloadHistory(id).map(versions => versions.map(version => version.tags["_last_edit:contributor:uid"]))
hist.addCallbackAndRunD(hist => previousEditors.setData(hist))
}
if (allByMyself.data === true) {
// Yay! We can download!
return true;

View file

@ -241,7 +241,7 @@ ${Utils.special_visualizations_importRequirementDocs}
new ShowDataMultiLayer({
leafletMap: confirmationMap.leafletMap,
zoomToFeatures: true,
features: new StaticFeatureSource([feature], false),
features: StaticFeatureSource.fromGeojson([feature]),
state: state,
layers: state.filteredLayers
})

View file

@ -1,4 +1,4 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import Combine from "../Base/Combine";
import {SubtleButton} from "../Base/SubtleButton";
@ -16,12 +16,12 @@ import {OsmConnection} from "../../Logic/Osm/OsmConnection";
export interface MultiApplyParams {
featureIds: UIEventSource<string[]>,
featureIds: Store<string[]>,
keysToApply: string[],
text: string,
autoapply: boolean,
overwrite: boolean,
tagsSource: UIEventSource<any>,
tagsSource: Store<any>,
state: {
changes: Changes,
allElements: ElementStorage,
@ -145,7 +145,7 @@ export default class MultiApply extends Toggle {
}
const isShown: UIEventSource<boolean> = p.state.osmConnection.isLoggedIn.map(loggedIn => {
const isShown: Store<boolean> = p.state.osmConnection.isLoggedIn.map(loggedIn => {
return loggedIn && p.featureIds.data.length > 0
}, [p.featureIds])
super(new Combine(elems), undefined, isShown);

View file

@ -1,7 +1,7 @@
import Combine from "../Base/Combine";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, Stores, UIEventSource} from "../../Logic/UIEventSource";
import {SlideShow} from "../Image/SlideShow";
import Toggle from "../Input/Toggle";
import {ClickableToggle} from "../Input/Toggle";
import Loading from "../Base/Loading";
import {AttributedImage} from "../Image/AttributedImage";
import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders";
@ -15,8 +15,6 @@ import {SubtleButton} from "../Base/SubtleButton";
import {GeoOperations} from "../../Logic/GeoOperations";
import {ElementStorage} from "../../Logic/ElementStorage";
import Lazy from "../Base/Lazy";
import {Utils} from "../../Utils";
import beginningOfLine = Mocha.reporters.Base.cursor.beginningOfLine;
export interface P4CPicture {
pictureUrl: string,
@ -42,7 +40,7 @@ export interface NearbyImageOptions {
// Radius of the upstream search
searchRadius?: 500 | number,
maxDaysOld?: 1095 | number,
blacklist: UIEventSource<{ url: string }[]>,
blacklist: Store<{ url: string }[]>,
shownImagesCount?: UIEventSource<number>,
towardscenter?: UIEventSource<boolean>;
allowSpherical?: UIEventSource<boolean>
@ -173,7 +171,7 @@ export default class NearbyImages extends Lazy {
const nearbyImages = state !== undefined ? new ImagesInLoadedDataFetcher(state).fetchAround(options) : []
return UIEventSource.FromPromise<P4CPicture[]>(
return Stores.FromPromise<P4CPicture[]>(
picManager.startPicsRetrievalAround(new P4C.LatLng(options.lat, options.lon), options.searchRadius ?? 500, {
mindate: new Date().getTime() - (options.maxDaysOld ?? (3 * 365)) * 24 * 60 * 60 * 1000,
towardscenter: false
@ -234,7 +232,7 @@ export default class NearbyImages extends Lazy {
return new AttributedImage({url: info.thumbUrl, provider, date: new Date(info.date)})
}
protected asToggle(info: P4CPicture): Toggle {
protected asToggle(info: P4CPicture): ClickableToggle {
const imgNonSelected = NearbyImages.asAttributedImage(info);
const imageSelected = NearbyImages.asAttributedImage(info);
@ -246,7 +244,7 @@ export default class NearbyImages extends Lazy {
hoveringCheckmark,
]).SetClass("relative block")
return new Toggle(selected, nonSelected).SetClass("").ToggleOnClick();
return new ClickableToggle(selected, nonSelected).SetClass("").ToggleOnClick();
}

View file

@ -7,9 +7,8 @@ import Translations from "../i18n/Translations";
import {Utils} from "../../Utils";
import Img from "../Base/Img";
import {SlideShow} from "../Image/SlideShow";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Stores, UIEventSource} from "../../Logic/UIEventSource";
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
import {UIElement} from "../UIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
export default class NoteCommentElement extends Combine {
@ -25,7 +24,7 @@ export default class NoteCommentElement extends Combine {
}) {
const t = Translations.t.notes;
let actionIcon: BaseUIElement = undefined;
let actionIcon: BaseUIElement;
if (comment.action === "opened" || comment.action === "reopened") {
actionIcon = Svg.note_svg()
} else if (comment.action === "closed") {
@ -41,7 +40,7 @@ export default class NoteCommentElement extends Combine {
user = new Link(comment.user, comment.user_url ?? "", true)
}
let userinfo = UIEventSource.FromPromise( Utils.downloadJsonCached("https://www.openstreetmap.org/api/0.6/user/"+comment.uid, 24*60*60*1000))
let userinfo = Stores.FromPromise( Utils.downloadJsonCached("https://www.openstreetmap.org/api/0.6/user/"+comment.uid, 24*60*60*1000))
let userImg = new VariableUiElement( userinfo.map(userinfo => {
const href = userinfo?.user?.img?.href;
if(href !== undefined){

View file

@ -1,4 +1,4 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import TagRenderingQuestion from "./TagRenderingQuestion";
import Translations from "../i18n/Translations";
import Combine from "../Base/Combine";
@ -14,7 +14,7 @@ import Lazy from "../Base/Lazy";
*/
export default class QuestionBox extends VariableUiElement {
public readonly skippedQuestions: UIEventSource<number[]>;
public readonly restingQuestions: UIEventSource<BaseUIElement[]>;
public readonly restingQuestions: Store<BaseUIElement[]>;
constructor(state, options: {
tagsSource: UIEventSource<any>,
@ -81,7 +81,7 @@ export default class QuestionBox extends VariableUiElement {
return undefined; // The questions are depleted
}, [skippedQuestions]);
const questionsToAsk: UIEventSource<BaseUIElement[]> = tagsSource.map(tags => {
const questionsToAsk: Store<BaseUIElement[]> = tagsSource.map(tags => {
if (tags === undefined) {
return [];
}

View file

@ -1,4 +1,4 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import Translations from "../i18n/Translations";
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
import Toggle from "../Input/Toggle";
@ -6,7 +6,7 @@ import BaseUIElement from "../BaseUIElement";
export class SaveButton extends Toggle {
constructor(value: UIEventSource<any>, osmConnection: OsmConnection, textEnabled ?: BaseUIElement, textDisabled ?: BaseUIElement) {
constructor(value: Store<any>, osmConnection: OsmConnection, textEnabled ?: BaseUIElement, textDisabled ?: BaseUIElement) {
if (value === undefined) {
throw "No event source for savebutton, something is wrong"
}

View file

@ -78,7 +78,7 @@ export default class SplitRoadWizard extends Toggle {
// Datalayer displaying the road and the cut points (if any)
new ShowDataMultiLayer({
features: new StaticFeatureSource([roadElement], false),
features: StaticFeatureSource.fromGeojson([roadElement]),
layers: state.filteredLayers,
leafletMap: miniMap.leafletMap,
zoomToFeatures: true,
@ -86,7 +86,7 @@ export default class SplitRoadWizard extends Toggle {
})
new ShowDataLayer({
features: new StaticFeatureSource(splitPoints, true),
features: new StaticFeatureSource(splitPoints),
leafletMap: miniMap.leafletMap,
zoomToFeatures: false,
layerToShow: SplitRoadWizard.splitLayerStyling,

View file

@ -3,7 +3,7 @@ import Translations from "../i18n/Translations";
import {VariableUiElement} from "../Base/VariableUIElement";
import BaseUIElement from "../BaseUIElement";
import {FixedUiElement} from "../Base/FixedUiElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import {SubtleButton} from "../Base/SubtleButton";
import Combine from "../Base/Combine";
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
@ -40,7 +40,7 @@ export default class TagApplyButton implements AutoAction {
];
public readonly example = "`{tag_apply(survey_date=$_now:date, Surveyed today!)}`, `{tag_apply(addr:street=$addr:street, Apply the address, apply_icon.svg, _closest_osm_id)";
public static generateTagsToApply(spec: string, tagSource: UIEventSource<any>): UIEventSource<Tag[]> {
public static generateTagsToApply(spec: string, tagSource: Store<any>): Store<Tag[]> {
const tgsSpec = spec.split(";").map(spec => {
const kv = spec.split("=").map(s => s.trim());

View file

@ -2,7 +2,6 @@ import {UIEventSource} from "../../Logic/UIEventSource";
import {Utils} from "../../Utils";
import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import List from "../Base/List";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
import Combine from "../Base/Combine";

View file

@ -1,6 +1,6 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, Stores, UIEventSource} from "../../Logic/UIEventSource";
import Combine from "../Base/Combine";
import {InputElement} from "../Input/InputElement";
import {InputElement, ReadonlyInputElement} from "../Input/InputElement";
import ValidatedTextField from "../Input/ValidatedTextField";
import {FixedInputElement} from "../Input/FixedInputElement";
import {RadioButton} from "../Input/RadioButton";
@ -45,14 +45,14 @@ export default class TagRenderingQuestion extends Combine {
units?: Unit[],
afterSave?: () => void,
cancelButton?: BaseUIElement,
saveButtonConstr?: (src: UIEventSource<TagsFilter>) => BaseUIElement,
bottomText?: (src: UIEventSource<TagsFilter>) => BaseUIElement
saveButtonConstr?: (src: Store<TagsFilter>) => BaseUIElement,
bottomText?: (src: Store<TagsFilter>) => BaseUIElement
}
) {
const applicableMappingsSrc =
UIEventSource.ListStabilized(tags.map(tags => {
Stores.ListStabilized(tags.map(tags => {
const applicableMappings: { if: TagsFilter, icon?: string, then: TypedTranslation<object>, ifnot?: TagsFilter, addExtraTags: Tag[] }[] = []
for (const mapping of configuration.mappings ?? []) {
if (mapping.hideInAnswer === true) {
@ -81,7 +81,7 @@ export default class TagRenderingQuestion extends Combine {
const feedback = new UIEventSource<Translation>(undefined)
const inputElement: InputElement<TagsFilter> =
const inputElement: ReadonlyInputElement<TagsFilter> =
new VariableInputElement(applicableMappingsSrc.map(applicableMappings =>
TagRenderingQuestion.GenerateInputElement(state, configuration, applicableMappings, applicableUnit, tags, feedback)
))
@ -452,8 +452,8 @@ export default class TagRenderingQuestion extends Combine {
}
public static CreateTagExplanation(selectedValue: UIEventSource<TagsFilter>,
tags: UIEventSource<object>,
public static CreateTagExplanation(selectedValue: Store<TagsFilter>,
tags: Store<object>,
state?: {osmConnection?: OsmConnection}){
return new VariableUiElement(
selectedValue.map(

View file

@ -37,7 +37,6 @@ export default class ReviewForm extends InputElement<Review> {
const comment = new TextField({
placeholder: Translations.t.reviews.write_a_comment.Clone(),
htmlType: "area",
value: this._value.map(r => r?.comment),
textAreaRows: 5
})
comment.GetValue().addCallback(comment => {
@ -62,10 +61,10 @@ export default class ReviewForm extends InputElement<Review> {
new SaveButton(
this._value.map(r => self.IsValid(r)), osmConnection
).onClick(() => {
reviewIsSaving.setData(true),
onSave(this._value.data, () => {
reviewIsSaved.setData(true)
});
reviewIsSaving.setData(true);
onSave(this._value.data, () => {
reviewIsSaved.setData(true)
});
}),
reviewIsSaving
),

View file

@ -195,7 +195,7 @@ export default class ShowDataLayerImplementation {
const tagsSource = this.allElements?.addOrGetElement(feat) ?? new UIEventSource<any>(feat.properties);
let offsettedLine;
tagsSource
.map(tags => this._layerToShow.lineRendering[feat.lineRenderingIndex].GenerateLeafletStyle(tags), [], undefined, true)
.map(tags => this._layerToShow.lineRendering[feat.lineRenderingIndex].GenerateLeafletStyle(tags))
.withEqualityStabilized((a, b) => {
if (a === b) {
return true

View file

@ -1,5 +1,5 @@
import FeatureSource from "../../Logic/FeatureSource/FeatureSource";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import {ElementStorage} from "../../Logic/ElementStorage";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
@ -10,6 +10,6 @@ export interface ShowDataLayerOptions {
leafletMap: UIEventSource<L.Map>,
popup?: undefined | ((tags: UIEventSource<any>, layer: LayerConfig) => ScrollableFullScreen),
zoomToFeatures?: false | boolean,
doShowLayer?: UIEventSource<boolean>,
doShowLayer?: Store<boolean>,
state?: { allElements?: ElementStorage }
}

View file

@ -1,5 +1,5 @@
import FeatureSource, {Tiled} from "../../Logic/FeatureSource/FeatureSource";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import ShowDataLayer from "./ShowDataLayer";
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource";
@ -18,7 +18,7 @@ export default class ShowTileInfo {
const source = options.source
const metaFeature: UIEventSource<any[]> =
const metaFeature: Store<{feature, freshness: Date}[]> =
source.features.map(features => {
const bbox = source.bbox
const [z, x, y] = Tiles.tile_from_index(source.tileIndex)
@ -47,12 +47,12 @@ export default class ShowTileInfo {
}
}
const center = GeoOperations.centerpoint(box)
return [box, center]
return [box, center].map(feature => ({feature, freshness: new Date()}))
})
new ShowDataLayer({
layerToShow: ShowTileInfo.styling,
features: new StaticFeatureSource(metaFeature, false),
features: new StaticFeatureSource(metaFeature),
leafletMap: options.leafletMap,
doShowLayer: options.doShowLayer,
state: State.state,

View file

@ -141,7 +141,7 @@ export class TileHierarchyAggregator implements FeatureSource {
return empty
}
const features = []
const features: {feature: any, freshness: Date}[] = []
self.visitSubTiles(aggr => {
if (aggr.showCount < cutoff) {
return false
@ -156,7 +156,7 @@ export class TileHierarchyAggregator implements FeatureSource {
return features
}, [this.updateSignal.stabilized(500)])
return new StaticFeatureSource(features, true);
return new StaticFeatureSource(features);
}
private update() {

View file

@ -1,4 +1,4 @@
import {UIEventSource} from "../Logic/UIEventSource";
import {Store, UIEventSource} from "../Logic/UIEventSource";
import {VariableUiElement} from "./Base/VariableUIElement";
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler";
import {ImageCarousel} from "./Image/ImageCarousel";
@ -207,7 +207,7 @@ class NearbyImageVis implements SpecialVisualization {
const nearby = new Lazy(() => {
const towardsCenter = new CheckBox(t.onlyTowards, false)
const radiusValue= state?.osmConnection?.GetPreference("nearby-images-radius","300").map(s => Number(s), [], i => ""+i) ?? new UIEventSource(300);
const radiusValue= state?.osmConnection?.GetPreference("nearby-images-radius","300").sync(s => Number(s), [], i => ""+i) ?? new UIEventSource(300);
const radius = new Slider(25, 500, {value:
radiusValue, step: 25})
@ -453,7 +453,7 @@ export default class SpecialVisualizations {
const keys = [...args]
keys.splice(0, 1)
const featureStore = state.allElements.ContainingFeatures
const featuresToShow: UIEventSource<{ freshness: Date, feature: any }[]> = tagSource.map(properties => {
const featuresToShow: Store<{ freshness: Date, feature: any }[]> = tagSource.map(properties => {
const values: string[] = Utils.NoNull(keys.map(key => properties[key]))
const features: { freshness: Date, feature: any }[] = []
for (const value of values) {
@ -507,7 +507,7 @@ export default class SpecialVisualizations {
leafletMap: minimap["leafletMap"],
zoomToFeatures: true,
layers: state.filteredLayers,
features: new StaticFeatureSource(featuresToShow, true)
features: new StaticFeatureSource(featuresToShow)
}
)
@ -553,7 +553,7 @@ export default class SpecialVisualizations {
leafletMap: minimap["leafletMap"],
zoomToFeatures: true,
layerToShow: new LayerConfig(left_right_style_json, "all_known_layers", true),
features: new StaticFeatureSource([copy], false),
features: StaticFeatureSource.fromGeojson([copy]),
state
}
)
@ -683,7 +683,7 @@ export default class SpecialVisualizations {
}
}
const listSource: UIEventSource<string[]> = tagSource
const listSource: Store<string[]> = tagSource
.map(tags => {
try {
const value = tags[args[0]]
@ -801,7 +801,7 @@ export default class SpecialVisualizations {
const text = args[2]
const autoapply = args[3]?.toLowerCase() === "true"
const overwrite = args[4]?.toLowerCase() === "true"
const featureIds: UIEventSource<string[]> = tagsSource.map(tags => {
const featureIds: Store<string[]> = tagsSource.map(tags => {
const ids = tags[featureIdsKey]
try {
if (ids === undefined) {

View file

@ -1,4 +1,4 @@
import {UIEventSource} from "../Logic/UIEventSource";
import {Store, UIEventSource} from "../Logic/UIEventSource";
import {Translation} from "./i18n/Translation";
import Locale from "./i18n/Locale";
import {FixedUiElement} from "./Base/FixedUiElement";

View file

@ -91,7 +91,7 @@ export default class WikidataPreviewBox extends VariableUiElement {
let link = new Link(
new Combine([
wikidata.id,
options.noImages ? wikidata.id : Svg.wikidata_svg().SetStyle("width: 2.5rem").SetClass("block")
options?.noImages ? wikidata.id : Svg.wikidata_svg().SetStyle("width: 2.5rem").SetClass("block")
]).SetClass("flex"),
Wikidata.IdToArticle(wikidata.id), true)?.SetClass("must-link")

View file

@ -2,7 +2,7 @@ import Combine from "../Base/Combine";
import {InputElement} from "../Input/InputElement";
import {TextField} from "../Input/TextField";
import Translations from "../i18n/Translations";
import {UIEventSource} from "../../Logic/UIEventSource";
import {ImmutableStore, Store, Stores, UIEventSource} from "../../Logic/UIEventSource";
import Wikidata, {WikidataResponse} from "../../Logic/Web/Wikidata";
import Locale from "../i18n/Locale";
import {VariableUiElement} from "../Base/VariableUIElement";
@ -10,6 +10,7 @@ import WikidataPreviewBox from "./WikidataPreviewBox";
import Title from "../Base/Title";
import WikipediaBox from "./WikipediaBox";
import Svg from "../../Svg";
import Loading from "../Base/Loading";
export default class WikidataSearchBox extends InputElement<string> {
@ -51,48 +52,55 @@ export default class WikidataSearchBox extends InputElement<string> {
})
const selectedWikidataId = this.wikidataId
const lastSearchResults = new UIEventSource<WikidataResponse[]>([])
const searchFailMessage = new UIEventSource(undefined)
searchField.GetValue().addCallbackAndRunD(searchText => {
if (searchText.length < 3) {
return;
const tooShort = new ImmutableStore<{success: WikidataResponse[]}>({success: undefined})
const searchResult: Store<{success?: WikidataResponse[], error?: any}> = searchField.GetValue().bind(
searchText => {
if (searchText.length < 3) {
return tooShort;
}
const lang = Locale.language.data
const key = lang + ":" + searchText
let promise = WikidataSearchBox._searchCache.get(key)
if (promise === undefined) {
promise = Wikidata.searchAndFetch(searchText, {
lang,
maxCount: 5,
notInstanceOf: this.notInstanceOf,
instanceOf: this.instanceOf
}
)
WikidataSearchBox._searchCache.set(key, promise)
}
return Stores.FromPromiseWithErr(promise)
}
searchFailMessage.setData(undefined)
)
const lang = Locale.language.data
const key = lang + ":" + searchText
let promise = WikidataSearchBox._searchCache.get(key)
if (promise === undefined) {
promise = Wikidata.searchAndFetch(searchText, {
lang,
maxCount: 5,
notInstanceOf: this.notInstanceOf,
instanceOf: this.instanceOf
}
)
WikidataSearchBox._searchCache.set(key, promise)
}
lastSearchResults.WaitForPromise(promise, err => searchFailMessage.setData(err))
})
const previews = new VariableUiElement(lastSearchResults.map(searchResults => {
if (searchFailMessage.data !== undefined) {
return new Combine([Translations.t.general.wikipedia.failed.Clone().SetClass("alert"), searchFailMessage.data])
}
const previews = new VariableUiElement(searchResult.map(searchResultsOrFail => {
if (searchField.GetValue().data.length === 0) {
return Translations.t.general.wikipedia.doSearch
}
if (searchField.GetValue().data.length < 3) {
return Translations.t.general.wikipedia.searchToShort
}
if( searchResultsOrFail === undefined) {
return new Loading(Translations.t.general.loading)
}
if (searchResultsOrFail.error !== undefined) {
return new Combine([Translations.t.general.wikipedia.failed.Clone().SetClass("alert"), searchResultsOrFail.error])
}
const searchResults = searchResultsOrFail.success;
if (searchResults.length === 0) {
return Translations.t.general.wikipedia.noResults.Subs({search: searchField.GetValue().data ?? ""})
}
return new Combine(searchResults.map(wikidataresponse => {
const el = WikidataPreviewBox.WikidataResponsePreview(wikidataresponse).SetClass("rounded-xl p-1 sm:p-2 md:p-3 m-px border-2 sm:border-4 transition-colors")
el.onClick(() => {
@ -110,7 +118,7 @@ export default class WikidataSearchBox extends InputElement<string> {
})).SetClass("flex flex-col")
}, [searchFailMessage]))
}, [searchField.GetValue()]))
const full = new Combine([
new Title(Translations.t.general.wikipedia.searchWikidata, 3).SetClass("m-2"),

View file

@ -8,7 +8,7 @@ import Title from "../Base/Title";
import Wikipedia from "../../Logic/Web/Wikipedia";
import Wikidata, {WikidataResponse} from "../../Logic/Web/Wikidata";
import {TabbedComponent} from "../Base/TabbedComponent";
import {UIEventSource} from "../../Logic/UIEventSource";
import {Store, UIEventSource} from "../../Logic/UIEventSource";
import Loading from "../Base/Loading";
import {FixedUiElement} from "../Base/FixedUiElement";
import Translations from "../i18n/Translations";
@ -128,7 +128,7 @@ export default class WikipediaBox extends Combine {
const wp = Translations.t.general.wikipedia;
const wikiLink: UIEventSource<[string, string, WikidataResponse] | "loading" | "failed" | ["no page", WikidataResponse]> =
const wikiLink: Store<[string, string, WikidataResponse] | "loading" | "failed" | ["no page", WikidataResponse]> =
Wikidata.LoadWikidataEntry(wikidataId)
.map(maybewikidata => {
if (maybewikidata === undefined) {