Optimize rendering
This commit is contained in:
parent
8babafaadb
commit
a721d3137a
21 changed files with 361 additions and 362 deletions
|
@ -23,8 +23,6 @@ import * as bike_shops from "../../assets/layers/bike_shop/bike_shop.json"
|
|||
import * as maps from "../../assets/layers/maps/maps.json"
|
||||
import * as information_boards from "../../assets/layers/information_board/information_board.json"
|
||||
import {Utils} from "../../Utils";
|
||||
import ImageCarouselWithUploadConstructor from "../../UI/Image/ImageCarouselWithUpload";
|
||||
import {ImageCarouselConstructor} from "../../UI/Image/ImageCarousel";
|
||||
import State from "../../State";
|
||||
|
||||
export class FromJSON {
|
||||
|
@ -139,23 +137,15 @@ export class FromJSON {
|
|||
|
||||
if (typeof json === "string") {
|
||||
switch (json) {
|
||||
case "picture": {
|
||||
return new ImageCarouselWithUploadConstructor()
|
||||
}
|
||||
case "pictures": {
|
||||
return new ImageCarouselWithUploadConstructor()
|
||||
}
|
||||
case "image": {
|
||||
return new ImageCarouselWithUploadConstructor()
|
||||
json = "{image_carousel()}{image_upload()}";
|
||||
break;
|
||||
}
|
||||
case "images": {
|
||||
return new ImageCarouselWithUploadConstructor()
|
||||
}
|
||||
case "picturesNoUpload": {
|
||||
return new ImageCarouselConstructor()
|
||||
json = "{image_carousel()}{image_upload()}";
|
||||
}
|
||||
}
|
||||
|
||||
console.warn("Possible literal rendering:", json)
|
||||
|
||||
return new TagRenderingOptions({
|
||||
freeform: {
|
||||
|
|
|
@ -16,9 +16,9 @@ export class OnlyShowIfConstructor implements TagDependantUIElementConstructor{
|
|||
this._embedded = embedded;
|
||||
}
|
||||
|
||||
construct(dependencies): TagDependantUIElement {
|
||||
return new OnlyShowIf(dependencies.tags,
|
||||
this._embedded.construct(dependencies),
|
||||
construct(tags: UIEventSource<any>): TagDependantUIElement {
|
||||
return new OnlyShowIf(tags,
|
||||
this._embedded.construct(tags),
|
||||
this._tagsFilter);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Dependencies, TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
|
||||
import {TagDependantUIElement, TagDependantUIElementConstructor} from "./UIElementConstructor";
|
||||
import {TagsFilter, TagUtils} from "../Logic/Tags";
|
||||
import {OnlyShowIfConstructor} from "./OnlyShowIf";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
|
@ -137,8 +137,8 @@ export class TagRenderingOptions implements TagDependantUIElementConstructor {
|
|||
mappings?: { k: TagsFilter; txt: string | Translation; priority?: number; substitute?: boolean, hideInAnswer?: boolean }[]
|
||||
}) => TagDependantUIElement;
|
||||
|
||||
construct(dependencies: Dependencies): TagDependantUIElement {
|
||||
return TagRenderingOptions.tagRendering(dependencies.tags, this.options);
|
||||
construct(tags: UIEventSource<any>): TagDependantUIElement {
|
||||
return TagRenderingOptions.tagRendering(tags, this.options);
|
||||
}
|
||||
|
||||
IsKnown(properties: any): boolean {
|
||||
|
|
|
@ -2,14 +2,9 @@ import {UIElement} from "../UI/UIElement";
|
|||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import Translation from "../UI/i18n/Translation";
|
||||
|
||||
|
||||
export interface Dependencies {
|
||||
tags: UIEventSource<any>
|
||||
}
|
||||
|
||||
export interface TagDependantUIElementConstructor {
|
||||
|
||||
construct(dependencies: Dependencies): TagDependantUIElement;
|
||||
construct(tags: UIEventSource<any>): TagDependantUIElement;
|
||||
IsKnown(properties: any): boolean;
|
||||
IsQuestioning(properties: any): boolean;
|
||||
GetContent(tags: any): Translation;
|
||||
|
|
|
@ -7,7 +7,6 @@ import Combine from "./UI/Base/Combine";
|
|||
import {UIElement} from "./UI/UIElement";
|
||||
import {MoreScreen} from "./UI/MoreScreen";
|
||||
import {FilteredLayer} from "./Logic/FilteredLayer";
|
||||
import {FeatureInfoBox} from "./UI/FeatureInfoBox";
|
||||
import {Basemap} from "./Logic/Leaflet/Basemap";
|
||||
import State from "./State";
|
||||
import {WelcomeMessage} from "./UI/WelcomeMessage";
|
||||
|
@ -37,6 +36,7 @@ import {FromJSON} from "./Customizations/JSON/FromJSON";
|
|||
import {Utils} from "./Utils";
|
||||
import BackgroundSelector from "./UI/BackgroundSelector";
|
||||
import AvailableBaseLayers from "./Logic/AvailableBaseLayers";
|
||||
import {FeatureInfoBox} from "./UI/Popup/FeatureInfoBox";
|
||||
|
||||
export class InitUiElements {
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ export class StrayClickHandler {
|
|||
const map = State.state.bm.map;
|
||||
State.state.filteredLayers.data.forEach((filteredLayer) => {
|
||||
filteredLayer.isDisplayed.addCallback(isEnabled => {
|
||||
if(isEnabled){
|
||||
if(isEnabled && self._lastMarker){
|
||||
// When a layer is activated, we remove the 'last click location' in order to force the user to reclick
|
||||
// This reclick might be at a location where a feature now appeared...
|
||||
map.removeLayer(self._lastMarker);
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
/**
|
||||
* Helps in uplaoding, by generating the rigth title, decription and by adding the tag to the changeset
|
||||
*/
|
||||
import {UIEventSource} from "../UIEventSource";
|
||||
import {ImageUploadFlow} from "../../UI/ImageUploadFlow";
|
||||
import {SlideShow} from "../../UI/SlideShow";
|
||||
import State from "../../State";
|
||||
import {Tag} from "../Tags";
|
||||
|
||||
export class OsmImageUploadHandler {
|
||||
private readonly _tags: UIEventSource<any>;
|
||||
private readonly _slideShow: SlideShow;
|
||||
private readonly _preferedLicense: UIEventSource<string>;
|
||||
|
||||
constructor(tags: UIEventSource<any>,
|
||||
preferedLicense: UIEventSource<string>,
|
||||
slideShow : SlideShow
|
||||
) {
|
||||
this._slideShow = slideShow; // To move the slideshow (if any) to the last, just added element
|
||||
this._tags = tags;
|
||||
this._preferedLicense = preferedLicense;
|
||||
}
|
||||
|
||||
private generateOptions(license: string) {
|
||||
const tags = this._tags.data;
|
||||
const self = this;
|
||||
license = license ?? "CC0"
|
||||
const title = tags.name ?? "Unknown area";
|
||||
const description = [
|
||||
"author:" + State.state.osmConnection.userDetails.data.name,
|
||||
"license:" + license,
|
||||
"wikidata:" + tags.wikidata,
|
||||
"osmid:" + tags.id,
|
||||
"name:" + tags.name
|
||||
].join("\n");
|
||||
|
||||
const changes = State.state.changes;
|
||||
return {
|
||||
title: title,
|
||||
description: description,
|
||||
handleURL: (url) => {
|
||||
|
||||
let key = "image";
|
||||
if (tags["image"] !== undefined) {
|
||||
|
||||
let freeIndex = 0;
|
||||
while (tags["image:" + freeIndex] !== undefined) {
|
||||
freeIndex++;
|
||||
}
|
||||
key = "image:" + freeIndex;
|
||||
}
|
||||
console.log("Adding image:" + key, url);
|
||||
changes.addTag(tags.id, new Tag(key, url));
|
||||
self._slideShow.MoveTo(-1); // set the last (thus newly added) image) to view
|
||||
},
|
||||
allDone: () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getUI(): ImageUploadFlow {
|
||||
const self = this;
|
||||
return new ImageUploadFlow(
|
||||
this._preferedLicense,
|
||||
function (license) {
|
||||
return self.generateOptions(license)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -40,7 +40,7 @@ export default class TagRenderingPreview extends UIElement {
|
|||
new VariableUiElement(es.map(tagRenderingConfig => {
|
||||
try {
|
||||
const tr = FromJSON.TagRendering(tagRenderingConfig, "preview")
|
||||
.construct({tags: self.previewTagValue});
|
||||
.construct(self.previewTagValue);
|
||||
return tr.Render();
|
||||
} catch (e) {
|
||||
return new Combine(["Could not show this tagrendering:", e.message]).Render();
|
||||
|
|
|
@ -1,38 +1,11 @@
|
|||
import {UIElement} from "../UIElement";
|
||||
import {ImageSearcher} from "../../Logic/ImageSearcher";
|
||||
import {SlideShow} from "../SlideShow";
|
||||
import {SlideShow} from "./SlideShow";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {
|
||||
Dependencies,
|
||||
TagDependantUIElement,
|
||||
TagDependantUIElementConstructor
|
||||
} from "../../Customizations/UIElementConstructor";
|
||||
import Translation from "../i18n/Translation";
|
||||
import {TagDependantUIElement} from "../../Customizations/UIElementConstructor";
|
||||
import Combine from "../Base/Combine";
|
||||
import DeleteImage from "./DeleteImage";
|
||||
|
||||
export class ImageCarouselConstructor implements TagDependantUIElementConstructor {
|
||||
IsKnown(properties: any): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
IsQuestioning(properties: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
Priority(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
construct(dependencies: Dependencies): TagDependantUIElement {
|
||||
return new ImageCarousel(dependencies.tags);
|
||||
}
|
||||
|
||||
GetContent(tags: any): Translation {
|
||||
return new Translation({"en":"Images without upload"});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class ImageCarousel extends TagDependantUIElement {
|
||||
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
import {
|
||||
Dependencies,
|
||||
TagDependantUIElement,
|
||||
TagDependantUIElementConstructor
|
||||
} from "../../Customizations/UIElementConstructor";
|
||||
import {TagDependantUIElement, TagDependantUIElementConstructor} from "../../Customizations/UIElementConstructor";
|
||||
import {ImageCarousel} from "./ImageCarousel";
|
||||
import {ImageUploadFlow} from "../ImageUploadFlow";
|
||||
import {OsmImageUploadHandler} from "../../Logic/Osm/OsmImageUploadHandler";
|
||||
import State from "../../State";
|
||||
import {ImageUploadFlow} from "./ImageUploadFlow";
|
||||
import Translation from "../i18n/Translation";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
|
||||
export default class ImageCarouselWithUploadConstructor implements TagDependantUIElementConstructor{
|
||||
|
||||
|
@ -24,20 +19,25 @@ export default class ImageCarouselWithUploadConstructor implements TagDependantU
|
|||
}
|
||||
|
||||
GetContent(tags: any): Translation {
|
||||
return new Translation({"en":"Image carousel with uploader"});
|
||||
return new Translation({"*": "Image carousel with uploader"});
|
||||
}
|
||||
}
|
||||
|
||||
class OsmImageUploadHandler {
|
||||
constructor(tags: UIEventSource<any>) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ImageCarouselWithUpload extends TagDependantUIElement {
|
||||
private _imageElement: ImageCarousel;
|
||||
private _pictureUploader: ImageUploadFlow;
|
||||
|
||||
constructor(dependencies: Dependencies) {
|
||||
super(dependencies.tags);
|
||||
const tags = dependencies.tags;
|
||||
constructor(tags: UIEventSource<any>) {
|
||||
super(tags);
|
||||
this._imageElement = new ImageCarousel(tags);
|
||||
const license = State.state.osmConnection.GetPreference( "pictures-license");
|
||||
this._pictureUploader = new OsmImageUploadHandler(tags, license, this._imageElement.slideshow).getUI();
|
||||
this._pictureUploader = new OsmImageUploadHandler(tags).getUI();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,34 +1,27 @@
|
|||
import {UIElement} from "./UIElement";
|
||||
import $ from "jquery"
|
||||
import {DropDown} from "./Input/DropDown";
|
||||
import Translations from "./i18n/Translations";
|
||||
import Combine from "./Base/Combine";
|
||||
import State from "../State";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import {Imgur} from "../Logic/Web/Imgur";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import State from "../../State";
|
||||
import Combine from "../Base/Combine";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import {Imgur} from "../../Logic/Web/Imgur";
|
||||
import {DropDown} from "../Input/DropDown";
|
||||
import {Tag} from "../../Logic/Tags";
|
||||
import Translations from "../i18n/Translations";
|
||||
|
||||
export class ImageUploadFlow extends UIElement {
|
||||
private _licensePicker: UIElement;
|
||||
private _selectedLicence: UIEventSource<string>;
|
||||
private _isUploading: UIEventSource<number> = new UIEventSource<number>(0)
|
||||
private _didFail: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
private _allDone: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
private _uploadOptions: (license: string) => { title: string; description: string; handleURL: (url: string) => void; allDone: (() => void) };
|
||||
private _connectButton : UIElement;
|
||||
|
||||
constructor(
|
||||
preferedLicense: UIEventSource<string>,
|
||||
uploadOptions: ((license: string) =>
|
||||
{
|
||||
title: string,
|
||||
description: string,
|
||||
handleURL: ((url: string) => void),
|
||||
allDone: (() => void)
|
||||
})
|
||||
) {
|
||||
private readonly _licensePicker: UIElement;
|
||||
private readonly _tags: UIEventSource<any>;
|
||||
private readonly _selectedLicence: UIEventSource<string>;
|
||||
private readonly _isUploading: UIEventSource<number> = new UIEventSource<number>(0)
|
||||
private readonly _didFail: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
private readonly _allDone: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
private readonly _connectButton: UIElement;
|
||||
|
||||
constructor(tags: UIEventSource<any>) {
|
||||
super(State.state.osmConnection.userDetails);
|
||||
this._uploadOptions = uploadOptions;
|
||||
this._tags = tags;
|
||||
|
||||
this.ListenTo(this._isUploading);
|
||||
this.ListenTo(this._didFail);
|
||||
this.ListenTo(this._allDone);
|
||||
|
@ -39,7 +32,7 @@ export class ImageUploadFlow extends UIElement {
|
|||
{value: "CC-BY-SA 4.0", shown: Translations.t.image.ccbs},
|
||||
{value: "CC-BY 4.0", shown: Translations.t.image.ccb}
|
||||
],
|
||||
preferedLicense
|
||||
State.state.osmConnection.GetPreference("pictures-license")
|
||||
);
|
||||
licensePicker.SetStyle("float:left");
|
||||
|
||||
|
@ -54,7 +47,6 @@ export class ImageUploadFlow extends UIElement {
|
|||
|
||||
}
|
||||
|
||||
|
||||
InnerRender(): string {
|
||||
|
||||
const t = Translations.t.image;
|
||||
|
@ -126,9 +118,9 @@ export class ImageUploadFlow extends UIElement {
|
|||
`<label for='fileselector-${this.id}'>` +
|
||||
label.Render() +
|
||||
"</label>" +
|
||||
actualInputElement+
|
||||
actualInputElement +
|
||||
"</form>";
|
||||
|
||||
|
||||
return new Combine([
|
||||
form,
|
||||
extraInfo
|
||||
|
@ -136,6 +128,65 @@ export class ImageUploadFlow extends UIElement {
|
|||
.Render();
|
||||
}
|
||||
|
||||
|
||||
private handleSuccessfulUpload(url) {
|
||||
const tags = this._tags.data;
|
||||
let key = "image";
|
||||
if (tags["image"] !== undefined) {
|
||||
|
||||
let freeIndex = 0;
|
||||
while (tags["image:" + freeIndex] !== undefined) {
|
||||
freeIndex++;
|
||||
}
|
||||
key = "image:" + freeIndex;
|
||||
}
|
||||
console.log("Adding image:" + key, url);
|
||||
State.state.changes.addTag(tags.id, new Tag(key, url));
|
||||
}
|
||||
|
||||
private handleFiles(files) {
|
||||
this._isUploading.setData(files.length);
|
||||
this._allDone.setData(false);
|
||||
|
||||
if (this._selectedLicence.data === undefined) {
|
||||
this._selectedLicence.setData("CC0");
|
||||
}
|
||||
|
||||
|
||||
const tags = this._tags.data;
|
||||
const title = tags.name ?? "Unknown area";
|
||||
const description = [
|
||||
"author:" + State.state.osmConnection.userDetails.data.name,
|
||||
"license:" + (this._selectedLicence.data ?? "CC0"),
|
||||
"wikidata:" + tags.wikidata,
|
||||
"osmid:" + tags.id,
|
||||
"name:" + tags.name
|
||||
].join("\n");
|
||||
|
||||
const self = this;
|
||||
|
||||
Imgur.uploadMultiple(title,
|
||||
description,
|
||||
files,
|
||||
function (url) {
|
||||
console.log("File saved at", url);
|
||||
self._isUploading.setData(self._isUploading.data - 1);
|
||||
self.handleSuccessfulUpload(url);
|
||||
},
|
||||
function () {
|
||||
console.log("All uploads completed");
|
||||
self._allDone.setData(true);
|
||||
},
|
||||
function (failReason) {
|
||||
console.log("Upload failed due to ", failReason)
|
||||
// No need to call something from the options -> we handle this here
|
||||
self._didFail.setData(true);
|
||||
self._isUploading.data--;
|
||||
self._isUploading.ping();
|
||||
}, 0
|
||||
)
|
||||
}
|
||||
|
||||
InnerUpdate(htmlElement: HTMLElement) {
|
||||
super.InnerUpdate(htmlElement);
|
||||
const user = State.state.osmConnection.userDetails.data;
|
||||
|
@ -147,34 +198,7 @@ export class ImageUploadFlow extends UIElement {
|
|||
|
||||
function submitHandler() {
|
||||
const files = $(selector).prop('files');
|
||||
self._isUploading.setData(files.length);
|
||||
self._allDone.setData(false);
|
||||
|
||||
if(self._selectedLicence.data === undefined){
|
||||
self._selectedLicence.setData("CC0");
|
||||
}
|
||||
|
||||
const opts = self._uploadOptions(self._selectedLicence.data);
|
||||
|
||||
Imgur.uploadMultiple(opts.title, opts.description, files,
|
||||
function (url) {
|
||||
console.log("File saved at", url);
|
||||
self._isUploading.setData(self._isUploading.data - 1);
|
||||
opts.handleURL(url);
|
||||
},
|
||||
function () {
|
||||
console.log("All uploads completed");
|
||||
self._allDone.setData(true);
|
||||
opts.allDone();
|
||||
},
|
||||
function(failReason) {
|
||||
console.log("Upload failed due to ", failReason)
|
||||
// No need to call something from the options -> we handle this here
|
||||
self._didFail.setData(true);
|
||||
self._isUploading.data--;
|
||||
self._isUploading.ping();
|
||||
},0
|
||||
)
|
||||
self.handleFiles(files)
|
||||
}
|
||||
|
||||
if (selector != null && form != null) {
|
|
@ -1,6 +1,6 @@
|
|||
import {UIElement} from "./UIElement";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
|
||||
export class SlideShow extends UIElement {
|
||||
|
||||
|
@ -15,6 +15,12 @@ export class SlideShow extends UIElement {
|
|||
super(embeddedElements);
|
||||
this._embeddedElements = embeddedElements;
|
||||
this.ListenTo(this._currentSlide);
|
||||
this._embeddedElements
|
||||
.stabilized(1000)
|
||||
.addCallback(embedded => {
|
||||
// Always move to the last image - but at most once per second
|
||||
this._currentSlide.setData(this._embeddedElements.data.length - 1);
|
||||
});
|
||||
|
||||
const self = this;
|
||||
this._prev = new FixedUiElement("<div class='prev-button'>" +
|
|
@ -1,16 +1,15 @@
|
|||
import {UIElement} from "./UIElement";
|
||||
import {VerticalCombine} from "./Base/VerticalCombine";
|
||||
import {OsmLink} from "../Customizations/Questions/OsmLink";
|
||||
import {WikipediaLink} from "../Customizations/Questions/WikipediaLink";
|
||||
import {And} from "../Logic/Tags";
|
||||
import {TagDependantUIElement, TagDependantUIElementConstructor} from "../Customizations/UIElementConstructor";
|
||||
import Translations from "./i18n/Translations";
|
||||
import {Changes} from "../Logic/Osm/Changes";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
import State from "../State";
|
||||
import {TagRenderingOptions} from "../Customizations/TagRenderingOptions";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import Combine from "./Base/Combine";
|
||||
import {VerticalCombine} from "../Base/VerticalCombine";
|
||||
import {UIElement} from "../UIElement";
|
||||
import Combine from "../Base/Combine";
|
||||
import {WikipediaLink} from "../../Customizations/Questions/WikipediaLink";
|
||||
import {OsmLink} from "../../Customizations/Questions/OsmLink";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {TagRenderingOptions} from "../../Customizations/TagRenderingOptions";
|
||||
import State from "../../State";
|
||||
import {And} from "../../Logic/Tags";
|
||||
import {TagDependantUIElement, TagDependantUIElementConstructor} from "../../Customizations/UIElementConstructor";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import Translations from "../i18n/Translations";
|
||||
|
||||
export class FeatureInfoBox extends UIElement {
|
||||
|
||||
|
@ -22,7 +21,6 @@ export class FeatureInfoBox extends UIElement {
|
|||
* The tags, wrapped in a global event source
|
||||
*/
|
||||
private readonly _tagsES: UIEventSource<any>;
|
||||
private readonly _changes: Changes;
|
||||
private readonly _title: UIElement;
|
||||
private readonly _infoboxes: TagDependantUIElement[];
|
||||
|
||||
|
@ -37,10 +35,13 @@ export class FeatureInfoBox extends UIElement {
|
|||
) {
|
||||
super(tagsES);
|
||||
this._feature = feature;
|
||||
this._tagsES = tagsES;
|
||||
this._tagsES = tagsES
|
||||
if(tagsES === undefined){
|
||||
throw "No Tags event source given"
|
||||
}
|
||||
this.ListenTo(State.state.osmConnection.userDetails);
|
||||
this.SetClass("featureinfobox");
|
||||
const deps = {tags: this._tagsES, changes: this._changes}
|
||||
const tags = this._tagsES;
|
||||
|
||||
this._infoboxes = [];
|
||||
elementsToShow = elementsToShow ?? []
|
||||
|
@ -48,13 +49,13 @@ export class FeatureInfoBox extends UIElement {
|
|||
const self = this;
|
||||
for (const tagRenderingOption of elementsToShow) {
|
||||
self._infoboxes.push(
|
||||
tagRenderingOption.construct(deps));
|
||||
tagRenderingOption.construct(tags));
|
||||
}
|
||||
function initTags() {
|
||||
self._infoboxes.splice(0, self._infoboxes.length);
|
||||
for (const tagRenderingOption of elementsToShow) {
|
||||
self._infoboxes.push(
|
||||
tagRenderingOption.construct(deps));
|
||||
tagRenderingOption.construct(tags));
|
||||
}
|
||||
self.Update();
|
||||
}
|
||||
|
@ -74,7 +75,7 @@ export class FeatureInfoBox extends UIElement {
|
|||
} else if (title instanceof UIElement) {
|
||||
renderedTitle = title;
|
||||
} else {
|
||||
renderedTitle = title.construct(deps);
|
||||
renderedTitle = title.construct(tags);
|
||||
}
|
||||
|
||||
|
||||
|
@ -83,10 +84,10 @@ export class FeatureInfoBox extends UIElement {
|
|||
.SetClass("title-font")
|
||||
|
||||
const osmLink = new OsmLink()
|
||||
.construct(deps)
|
||||
.construct(tags)
|
||||
.SetStyle("width: 24px; display:block;")
|
||||
const wikipedialink = new WikipediaLink()
|
||||
.construct(deps)
|
||||
.construct(tags)
|
||||
.SetStyle("width: 24px; display:block;")
|
||||
|
||||
this._title = new Combine([
|
|
@ -1,6 +1,6 @@
|
|||
import {UIElement} from "./UIElement";
|
||||
import Translations from "./i18n/Translations";
|
||||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import Translations from "../i18n/Translations";
|
||||
|
||||
export class SaveButton extends UIElement {
|
||||
private _value: UIEventSource<any>;
|
|
@ -1,23 +1,23 @@
|
|||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import {And, Tag, TagsFilter, TagUtils} from "../Logic/Tags";
|
||||
import Translations from "../UI/i18n/Translations";
|
||||
import Locale from "../UI/i18n/Locale";
|
||||
import Translation from "../UI/i18n/Translation";
|
||||
import Combine from "../UI/Base/Combine";
|
||||
import {TagDependantUIElement} from "../Customizations/UIElementConstructor";
|
||||
import {UIElement} from "./UIElement";
|
||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||
import InputElementMap from "./Input/InputElementMap";
|
||||
import {InputElement} from "./Input/InputElement";
|
||||
import {UIElement} from "../UIElement";
|
||||
import Translation from "../i18n/Translation";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import InputElementMap from "../Input/InputElementMap";
|
||||
import CheckBoxes from "../Input/Checkboxes";
|
||||
import Combine from "../Base/Combine";
|
||||
import {And, Tag, TagsFilter, TagUtils} from "../../Logic/Tags";
|
||||
import {InputElement} from "../Input/InputElement";
|
||||
import {SaveButton} from "./SaveButton";
|
||||
import {RadioButton} from "./Input/RadioButton";
|
||||
import {FixedInputElement} from "./Input/FixedInputElement";
|
||||
import {TagRenderingOptions} from "../Customizations/TagRenderingOptions";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
import ValidatedTextField from "./Input/ValidatedTextField";
|
||||
import CheckBoxes from "./Input/Checkboxes";
|
||||
import State from "../State";
|
||||
import SpecialVisualizations from "./SpecialVisualizations";
|
||||
import {RadioButton} from "../Input/RadioButton";
|
||||
import {FixedInputElement} from "../Input/FixedInputElement";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import ValidatedTextField from "../Input/ValidatedTextField";
|
||||
import {TagRenderingOptions} from "../../Customizations/TagRenderingOptions";
|
||||
import State from "../../State";
|
||||
import {SubstitutedTranslation} from "../SpecialVisualizations";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import Translations from "../i18n/Translations";
|
||||
import {TagDependantUIElement} from "../../Customizations/UIElementConstructor";
|
||||
import Locale from "../i18n/Locale";
|
||||
|
||||
export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||
|
||||
|
@ -25,7 +25,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
private readonly _question: string | Translation;
|
||||
private readonly _mapping: { k: TagsFilter, txt: string | Translation, priority?: number }[];
|
||||
|
||||
private currentTags: UIEventSource<any>;
|
||||
private readonly currentTags: UIEventSource<any>;
|
||||
|
||||
|
||||
private readonly _freeform: {
|
||||
|
@ -50,7 +50,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
private readonly _questionSkipped: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
|
||||
private readonly _editMode: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
|
||||
|
||||
static injectFunction() {
|
||||
// This is a workaround as not to import tagrendering into TagREnderingOptions
|
||||
TagRenderingOptions.tagRendering = (tags, options) => new TagRendering(tags, options);
|
||||
|
@ -71,15 +71,17 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
mappings?: { k: TagsFilter, txt: string | Translation, priority?: number, substitute?: boolean, hideInAnswer?: boolean }[]
|
||||
}) {
|
||||
super(tags);
|
||||
if (tags === undefined) {
|
||||
throw "No tags given for a tagrendering..."
|
||||
}
|
||||
this.ListenTo(Locale.language);
|
||||
this.ListenTo(this._editMode);
|
||||
this.ListenTo(this._questionSkipped);
|
||||
this.ListenTo(State.state?.osmConnection?.userDetails);
|
||||
|
||||
const self = this;
|
||||
|
||||
this.currentTags = this._source.map(tags =>
|
||||
{
|
||||
|
||||
this.currentTags = this._source.map(tags => {
|
||||
|
||||
if (options.tagsPreprocessor === undefined) {
|
||||
return tags;
|
||||
|
@ -90,7 +92,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
newTags[k] = tags[k];
|
||||
}
|
||||
// ... in order to safely edit them here
|
||||
options.tagsPreprocessor(newTags);
|
||||
options.tagsPreprocessor(newTags);
|
||||
return newTags;
|
||||
}
|
||||
);
|
||||
|
@ -98,7 +100,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
if (options.question !== undefined) {
|
||||
this._question = options.question;
|
||||
}
|
||||
|
||||
|
||||
this._mapping = [];
|
||||
this._freeform = options.freeform;
|
||||
|
||||
|
@ -138,7 +140,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
if (csCount < State.userJourney.tagsVisibleAt) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
if (tags === undefined) {
|
||||
return Translations.t.general.noTagsSelected.SetClass("subtle").Render();
|
||||
}
|
||||
|
@ -186,6 +188,7 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
}, [Locale.language]);
|
||||
// And at last, set up the skip button
|
||||
this._skipButton = new VariableUiElement(cancelContents).onClick(cancel);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -295,14 +298,14 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
(t0, t1) => t0.isEquivalent(t1)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
let txt = this.ApplyTemplate(mapping.txt);
|
||||
if(txt.Render().indexOf("<img") >= 0){
|
||||
if (txt.Render().indexOf("<img") >= 0) {
|
||||
txt.SetClass("question-option-with-border");
|
||||
}
|
||||
const inputEl = new FixedInputElement(txt, mapping.k,
|
||||
(t0, t1) => t1.isEquivalent(t0));
|
||||
|
||||
|
||||
return inputEl;
|
||||
}
|
||||
|
||||
|
@ -324,17 +327,17 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
let type = prepost[1];
|
||||
|
||||
let isTextArea = false;
|
||||
if(type === "text"){
|
||||
if (type === "text") {
|
||||
isTextArea = true;
|
||||
type = "string";
|
||||
}
|
||||
|
||||
if(ValidatedTextField.AllTypes[type] === undefined){
|
||||
console.error("Type:",type, ValidatedTextField.AllTypes)
|
||||
throw "Unkown type: "+type;
|
||||
|
||||
if (ValidatedTextField.AllTypes[type] === undefined) {
|
||||
console.error("Type:", type, ValidatedTextField.AllTypes)
|
||||
throw "Unkown type: " + type;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const pickString =
|
||||
(string: any) => {
|
||||
if (string === "" || string === undefined) {
|
||||
|
@ -444,47 +447,43 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
return new FixedUiElement("");
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
|
||||
if (this.IsQuestioning()
|
||||
private CreateComponent(): UIElement {
|
||||
|
||||
|
||||
if (this.IsQuestioning()
|
||||
&& (State.state !== undefined) // If State.state is undefined, we are testing/custom theme building -> show regular save
|
||||
&& !State.state.osmConnection.userDetails.data.loggedIn) {
|
||||
|
||||
|
||||
const question =
|
||||
this.ApplyTemplate(this._question).SetClass('question-text');
|
||||
return "<div class='question'>" +
|
||||
new Combine([
|
||||
question,
|
||||
"<br/>",
|
||||
this._questionElement,
|
||||
this._friendlyLogin,
|
||||
]).Render() + "</div>";
|
||||
return new Combine(["<div class='question'>",
|
||||
question,
|
||||
"<br/>",
|
||||
this._questionElement,
|
||||
this._friendlyLogin, "</div>"
|
||||
]);
|
||||
}
|
||||
|
||||
if (this.IsQuestioning() || this._editMode.data) {
|
||||
// Not yet known or questioning, we have to ask a question
|
||||
|
||||
return "<div class='question'>" +
|
||||
new Combine([
|
||||
"<span class='question-text'>",
|
||||
this.ApplyTemplate(this._question),
|
||||
"</span>",
|
||||
"<br/>",
|
||||
"<div>", this._questionElement , "</div>",
|
||||
this._skipButton,
|
||||
this._saveButton,
|
||||
"<br/>",
|
||||
this._appliedTags
|
||||
]).Render() +
|
||||
"</div>"
|
||||
return new Combine([
|
||||
this.ApplyTemplate(this._question).SetStyle('question-text'),
|
||||
"<br/>",
|
||||
"<div>", this._questionElement, "</div>",
|
||||
this._skipButton,
|
||||
this._saveButton,
|
||||
"<br/>",
|
||||
this._appliedTags
|
||||
]).SetClass('question');
|
||||
}
|
||||
|
||||
if (this.IsKnown()) {
|
||||
|
||||
const answer = this.RenderAnswer();
|
||||
|
||||
|
||||
if (answer.IsEmpty()) {
|
||||
return "";
|
||||
return new FixedUiElement("");
|
||||
}
|
||||
|
||||
|
||||
|
@ -499,37 +498,35 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
|||
return new Combine([
|
||||
answer,
|
||||
this._editButton])
|
||||
.SetStyle(answerStyle)
|
||||
.Render();
|
||||
.SetStyle(answerStyle);
|
||||
}
|
||||
|
||||
return answer.SetStyle(answerStyle).Render();
|
||||
return answer.SetStyle(answerStyle);
|
||||
}
|
||||
|
||||
return "";
|
||||
return new FixedUiElement("");
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
return this.CreateComponent().Render();
|
||||
}
|
||||
|
||||
|
||||
protected InnerUpdate(htmlElement: HTMLElement) {
|
||||
this._editButton.Update();
|
||||
}
|
||||
|
||||
private answerCache = {}
|
||||
|
||||
private ApplyTemplate(template: string | Translation): UIElement {
|
||||
if (template === undefined || template === null) {
|
||||
return undefined;
|
||||
const tr = Translations.WT(template);
|
||||
if (this.answerCache[tr.id]) {
|
||||
return this.answerCache[tr.id];
|
||||
}
|
||||
|
||||
const knownSpecials : {funcName: string, constr: ((arg: string) => UIElement)}[]= SpecialVisualizations.specialVisualizations.map(
|
||||
special => ({
|
||||
funcName: special.funcName,
|
||||
constr: arg => special.constr(this.currentTags, arg.split(","))
|
||||
})
|
||||
)
|
||||
|
||||
return new VariableUiElement(this.currentTags.map(tags => {
|
||||
return Translations.WT(template)
|
||||
.Subs(tags)
|
||||
.EvaluateSpecialComponents(knownSpecials)
|
||||
.InnerRender()
|
||||
})).ListenTo(Locale.language);
|
||||
// We have to cache these elemnts, otherwise it is to slow
|
||||
const el = new SubstitutedTranslation(tr, this.currentTags);
|
||||
this.answerCache[tr.id] = el;
|
||||
return el;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -3,6 +3,79 @@ import OpeningHoursVisualization from "./OhVisualization";
|
|||
import {UIEventSource} from "../Logic/UIEventSource";
|
||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler";
|
||||
import {ImageCarousel} from "./Image/ImageCarousel";
|
||||
import Translation from "./i18n/Translation";
|
||||
import Combine from "./Base/Combine";
|
||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||
import Locale from "../UI/i18n/Locale";
|
||||
import {ImageUploadFlow} from "./Image/ImageUploadFlow";
|
||||
|
||||
export class SubstitutedTranslation extends UIElement {
|
||||
private readonly tags: UIEventSource<any>;
|
||||
private readonly translation: Translation;
|
||||
private content: UIElement;
|
||||
|
||||
constructor(
|
||||
translation: Translation,
|
||||
tags: UIEventSource<any>) {
|
||||
super(tags);
|
||||
this.translation = translation;
|
||||
this.tags = tags;
|
||||
const self = this;
|
||||
Locale.language.addCallbackAndRun(() => {
|
||||
self.content = self.CreateContent();
|
||||
self.Update();
|
||||
})
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
return this.content.Render();
|
||||
}
|
||||
|
||||
|
||||
CreateContent(): UIElement {
|
||||
let txt = this.translation?.txt;
|
||||
if (txt === undefined) {
|
||||
return new FixedUiElement("")
|
||||
}
|
||||
const tags = this.tags.data;
|
||||
for (const key in tags) {
|
||||
// Poor mans replace all
|
||||
txt = txt.split("{" + key + "}").join(tags[key]);
|
||||
}
|
||||
|
||||
|
||||
return new Combine(this.EvaluateSpecialComponents(txt));
|
||||
}
|
||||
|
||||
public EvaluateSpecialComponents(template: string): UIElement[] {
|
||||
|
||||
for (const knownSpecial of SpecialVisualizations.specialVisualizations) {
|
||||
|
||||
// NOte: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
|
||||
const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)}(.*)`);
|
||||
if (matched != null) {
|
||||
|
||||
// We found a special component that should be brought to live
|
||||
const partBefore = this.EvaluateSpecialComponents(matched[1]);
|
||||
const argument = matched[2];
|
||||
const partAfter = this.EvaluateSpecialComponents(matched[3]);
|
||||
try {
|
||||
const args = argument.trim().split(",").map(str => str.trim());
|
||||
const element = knownSpecial.constr(this.tags, args);
|
||||
return [...partBefore, element, ...partAfter]
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return [...partBefore, ...partAfter]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IF we end up here, no changes have to be made
|
||||
return [new FixedUiElement(template)];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default class SpecialVisualizations {
|
||||
|
||||
|
@ -13,18 +86,55 @@ export default class SpecialVisualizations {
|
|||
args: { name: string, defaultValue?: string, doc: string }[]
|
||||
}[] =
|
||||
|
||||
[{
|
||||
funcName: "opening_hours_table",
|
||||
docs: "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'.",
|
||||
args: [{name: "key", defaultValue: "opening_hours", doc: "The tag from which the table is constructed"}],
|
||||
constr: (tagSource: UIEventSource<any>, args) => {
|
||||
let keyname = args[0];
|
||||
if (keyname === undefined || keyname === "") {
|
||||
keyname = keyname ?? "opening_hours"
|
||||
[
|
||||
{
|
||||
funcName: "image_carousel",
|
||||
docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)",
|
||||
args: [{
|
||||
name: "image tag(s)",
|
||||
defaultValue: "image,image:*,wikidata,wikipedia,wikimedia_commons",
|
||||
doc: "Image tag(s) where images are searched"
|
||||
}],
|
||||
constr: (tags, args) => {
|
||||
if (args.length > 0) {
|
||||
console.error("TODO HANDLE THESE ARGS") // TODO FIXME
|
||||
|
||||
}
|
||||
return new ImageCarousel(tags);
|
||||
}
|
||||
return new OpeningHoursVisualization(tagSource, keyname)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
funcName: "image_upload",
|
||||
docs: "Creates a button where a user can upload an image to IMGUR",
|
||||
args: [{
|
||||
doc: "Image tag to add the URL to (or image-tag:0, image-tag:1 when multiple images are added)",
|
||||
defaultValue: "image", name: "image-key"
|
||||
}],
|
||||
constr: (tags, args) => {
|
||||
if (args.length > 0) {
|
||||
console.error("TODO HANDLE THESE ARGS") // TODO FIXME
|
||||
|
||||
}
|
||||
return new ImageUploadFlow(tags)
|
||||
}
|
||||
},
|
||||
{
|
||||
funcName: "opening_hours_table",
|
||||
docs: "Creates an opening-hours table. Usage: {opening_hours_table(opening_hours)} to create a table of the tag 'opening_hours'.",
|
||||
args: [{
|
||||
name: "key",
|
||||
defaultValue: "opening_hours",
|
||||
doc: "The tag from which the table is constructed"
|
||||
}],
|
||||
constr: (tagSource: UIEventSource<any>, args) => {
|
||||
let keyname = args[0];
|
||||
if (keyname === undefined || keyname === "") {
|
||||
keyname = keyname ?? "opening_hours"
|
||||
}
|
||||
return new OpeningHoursVisualization(tagSource, keyname)
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
funcName: "live",
|
||||
|
|
|
@ -40,39 +40,7 @@ export default class Translation extends UIElement {
|
|||
|
||||
}
|
||||
|
||||
public EvaluateSpecialComponents(knownSpecials: { funcName: string, constr: ((call: string) => UIElement) }[]): UIElement {
|
||||
const newTranslations = {};
|
||||
for (const lang in this.translations) {
|
||||
let template: string = this.translations[lang];
|
||||
|
||||
for (const knownSpecial of knownSpecials) {
|
||||
|
||||
do {
|
||||
const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*)\\)}(.*)`);
|
||||
if (matched === null) {
|
||||
break;
|
||||
}
|
||||
const partBefore = matched[1];
|
||||
const argument = matched[2];
|
||||
const partAfter = matched[3];
|
||||
|
||||
try {
|
||||
|
||||
const element = knownSpecial.constr(argument).Render();
|
||||
template = partBefore + element + partAfter;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
template = partBefore + partAfter;
|
||||
}
|
||||
|
||||
} while (true);
|
||||
|
||||
|
||||
}
|
||||
newTranslations[lang] = template;
|
||||
}
|
||||
return new Translation(newTranslations);
|
||||
}
|
||||
|
||||
|
||||
|
||||
get txt(): string {
|
||||
|
|
|
@ -968,12 +968,18 @@ export default class Translations {
|
|||
return s;
|
||||
}
|
||||
|
||||
private static wtcache = {}
|
||||
public static WT(s: string | Translation): Translation {
|
||||
if(s === undefined){
|
||||
return undefined;
|
||||
}
|
||||
if (typeof (s) === "string") {
|
||||
return new Translation({en: s});
|
||||
if(Translations.wtcache[s]){
|
||||
return Translations.wtcache[s];
|
||||
}
|
||||
const tr = new Translation({en: s});
|
||||
Translations.wtcache[s]= tr;
|
||||
return tr;
|
||||
}
|
||||
if (s instanceof Translation) {
|
||||
return s;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import {UIEventSource} from "./Logic/UIEventSource";
|
||||
import {GenerateEmpty} from "./UI/CustomGenerator/GenerateEmpty";
|
||||
import {TagRendering} from "./UI/TagRendering";
|
||||
import {LayoutConfigJson} from "./Customizations/JSON/LayoutConfigJson";
|
||||
import {OsmConnection} from "./Logic/Osm/OsmConnection";
|
||||
import CustomGeneratorPanel from "./UI/CustomGenerator/CustomGeneratorPanel";
|
||||
import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
|
||||
import {TagRendering} from "./UI/Popup/TagRendering";
|
||||
|
||||
let layout = GenerateEmpty.createEmptyLayout();
|
||||
if (window.location.hash.length > 10) {
|
||||
|
|
|
@ -367,6 +367,7 @@ body {
|
|||
}
|
||||
|
||||
.question {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
background-color: #e5f5ff;
|
||||
padding: 1em;
|
||||
|
|
2
index.ts
2
index.ts
|
@ -6,7 +6,7 @@ import {QueryParameters} from "./Logic/Web/QueryParameters";
|
|||
import {UIEventSource} from "./Logic/UIEventSource";
|
||||
import * as $ from "jquery";
|
||||
import {FromJSON} from "./Customizations/JSON/FromJSON";
|
||||
import {TagRendering} from "./UI/TagRendering";
|
||||
import {TagRendering} from "./UI/Popup/TagRendering";
|
||||
|
||||
TagRendering.injectFunction();
|
||||
|
||||
|
|
Loading…
Reference in a new issue