diff --git a/Logic/UIEventSource.ts b/Logic/UIEventSource.ts index 23b999e65..4282a28cb 100644 --- a/Logic/UIEventSource.ts +++ b/Logic/UIEventSource.ts @@ -60,7 +60,7 @@ export class UIEventSource{ this.addCallback(update); for (const extraSource of extraSources) { - extraSource.addCallback(update); + extraSource?.addCallback(update); } if(g !== undefined) { diff --git a/UI/CustomGenerator/SharePanel.ts b/UI/CustomGenerator/SharePanel.ts index 98375652b..3b41b04af 100644 --- a/UI/CustomGenerator/SharePanel.ts +++ b/UI/CustomGenerator/SharePanel.ts @@ -13,21 +13,12 @@ export default class SharePanel extends UIElement { super(undefined); this._config = config; - const json = new VariableUiElement(config.map(config => { - return JSON.stringify(config, null, 2) - .replace(/\n/g, "
") - .replace(/ /g, " "); - })); this._panel = new Combine([ "

share

", "Share the following link with friends:
", new VariableUiElement(liveUrl.map(url => `${url}`)), - "

Json

", - "The json configuration is included for debugging purposes", - "
", - json, "
" ]); } diff --git a/UI/Input/AndOrTagInput.ts b/UI/Input/AndOrTagInput.ts new file mode 100644 index 000000000..cd5f68eb1 --- /dev/null +++ b/UI/Input/AndOrTagInput.ts @@ -0,0 +1,90 @@ +import {InputElement} from "./InputElement"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import {UIElement} from "../UIElement"; +import Combine from "../Base/Combine"; +import {SubtleButton} from "../Base/SubtleButton"; +import TagInput from "./TagInput"; +import {FixedUiElement} from "../Base/FixedUiElement"; + +export class AndOrTagInput extends InputElement<(string | AndOrTagInput)[]> { + + + private readonly _value: UIEventSource; + IsSelected: UIEventSource; + private elements: UIElement[] = []; + private inputELements: (InputElement | InputElement)[] = []; + private addTag: UIElement; + + constructor(value: UIEventSource = new UIEventSource([])) { + super(undefined); + this._value = value; + + this.addTag = new SubtleButton("./assets/addSmall.svg", "Add a tag") + .SetClass("small-button") + .onClick(() => { + this.IsSelected.setData(true); + value.data.push(""); + value.ping(); + }); + const self = this; + value.map((tags: string[]) => tags.length).addCallback(() => self.createElements()); + this.createElements(); + + + this._value.addCallback(tags => self.load(tags)); + this.IsSelected = new UIEventSource(false); + } + + private load(tags: string[]) { + if (tags === undefined) { + return; + } + for (let i = 0; i < tags.length; i++) { + console.log("Setting tag ", i) + this.inputELements[i].GetValue().setData(tags[i]); + } + } + + private UpdateIsSelected(){ + this.IsSelected.setData(this.inputELements.map(input => input.IsSelected.data).reduce((a,b) => a && b)) + } + + private createElements() { + this.inputELements = []; + this.elements = []; + for (let i = 0; i < this._value.data.length; i++) { + let tag = this._value.data[i]; + const input = new TagInput(new UIEventSource(tag)); + input.GetValue().addCallback(tag => { + console.log("Writing ", tag) + this._value.data[i] = tag; + this._value.ping(); + } + ); + this.inputELements.push(input); + input.IsSelected.addCallback(() => this.UpdateIsSelected()); + const deleteBtn = new FixedUiElement("") + .onClick(() => { + this._value.data.splice(i, 1); + this._value.ping(); + }); + this.elements.push(new Combine([input, deleteBtn, "
"]).SetClass("tag-input-row")) + } + + this.Update(); + } + + InnerRender(): string { + return new Combine([...this.elements, this.addTag]).SetClass("bordered").Render(); + } + + + IsValid(t: string[]): boolean { + return false; + } + + GetValue(): UIEventSource { + return this._value; + } + +} \ No newline at end of file diff --git a/UI/ShareScreen.ts b/UI/ShareScreen.ts index 5caa4b7f9..f78b7310b 100644 --- a/UI/ShareScreen.ts +++ b/UI/ShareScreen.ts @@ -13,16 +13,20 @@ import {Utils} from "../Utils"; import {UIEventSource} from "../Logic/UIEventSource"; import Translation from "./i18n/Translation"; import {SubtleButton} from "./Base/SubtleButton"; +import {Layout} from "../Customizations/Layout"; export class ShareScreen extends UIElement { - private readonly _options: UIElement; - private readonly _iframeCode: UIElement; - private readonly _link: UIElement; - private readonly _linkStatus: UIEventSource; - private readonly _editLayout: UIElement; + private readonly _options: UIElement; + private readonly _iframeCode: UIElement; + public iframe: UIEventSource; + private readonly _link: UIElement; + private readonly _linkStatus: UIEventSource; + private readonly _editLayout: UIElement; - constructor() { + constructor(layout: Layout = undefined, layoutDefinition: string = undefined) { super(undefined) + layout = layout ?? State.state?.layoutToUse?.data; + layoutDefinition = layoutDefinition ?? State.state?.layoutDefinition; const tr = Translations.t.general.sharescreen; const optionCheckboxes: UIElement[] = [] @@ -34,11 +38,13 @@ export class ShareScreen extends UIElement { true ) optionCheckboxes.push(includeLocation); - - const currentLocation = State.state.locationControl; - const layout = State.state.layoutToUse.data; + + const currentLocation = State.state?.locationControl; optionParts.push(includeLocation.isEnabled.map((includeL) => { + if (currentLocation === undefined) { + return null; + } if (includeL) { return `z=${currentLocation.data.zoom}&lat=${currentLocation.data.lat}&lon=${currentLocation.data.lon}` } else { @@ -47,54 +53,58 @@ export class ShareScreen extends UIElement { }, [currentLocation])); - const currentLayer: UIEventSource<{ id: string, name: string, layer: any }> = (State.state.bm as Basemap).CurrentLayer; - const currentBackground = tr.fsIncludeCurrentBackgroundMap.Subs({name: layout.id}); - const includeCurrentBackground = new CheckBox( - new Combine([Img.checkmark, currentBackground]), - new Combine([Img.no_checkmark, currentBackground]), - true - ) - optionCheckboxes.push(includeCurrentBackground); - optionParts.push(includeCurrentBackground.isEnabled.map((includeBG) => { - if (includeBG) { - return "background=" + currentLayer.data.id - } else { - return null - } - }, [currentLayer])); - - - const includeLayerChoices = new CheckBox( - new Combine([Img.checkmark, tr.fsIncludeCurrentLayers]), - new Combine([Img.no_checkmark, tr.fsIncludeCurrentLayers]), - true - ) - optionCheckboxes.push(includeLayerChoices); - - function fLayerToParam(flayer: FilteredLayer){ - if(flayer.isDisplayed.data){ + function fLayerToParam(flayer: FilteredLayer) { + if (flayer.isDisplayed.data) { return null; // Being displayed is the default } - return "layer-"+flayer.layerDef.id+"="+flayer.isDisplayed.data + return "layer-" + flayer.layerDef.id + "=" + flayer.isDisplayed.data } - optionParts.push(includeLayerChoices.isEnabled.map((includeLayerSelection) => { - if (includeLayerSelection) { - return Utils.NoNull(State.state.filteredLayers.data.map(fLayerToParam)).join("&") - } else { - return null - } - }, State.state.filteredLayers.data.map((flayer) => flayer.isDisplayed))); + if (State.state !== undefined) { + + const currentLayer: UIEventSource<{ id: string, name: string, layer: any }> = (State.state.bm as Basemap).CurrentLayer; + const currentBackground = tr.fsIncludeCurrentBackgroundMap.Subs({name: layout.id}); + const includeCurrentBackground = new CheckBox( + new Combine([Img.checkmark, currentBackground]), + new Combine([Img.no_checkmark, currentBackground]), + true + ) + optionCheckboxes.push(includeCurrentBackground); + optionParts.push(includeCurrentBackground.isEnabled.map((includeBG) => { + if (includeBG) { + return "background=" + currentLayer.data.id + } else { + return null + } + }, [currentLayer])); + + + const includeLayerChoices = new CheckBox( + new Combine([Img.checkmark, tr.fsIncludeCurrentLayers]), + new Combine([Img.no_checkmark, tr.fsIncludeCurrentLayers]), + true + ) + optionCheckboxes.push(includeLayerChoices); + + optionParts.push(includeLayerChoices.isEnabled.map((includeLayerSelection) => { + if (includeLayerSelection) { + return Utils.NoNull(State.state.filteredLayers.data.map(fLayerToParam)).join("&") + } else { + return null + } + }, State.state.filteredLayers.data.map((flayer) => flayer.isDisplayed))); + + } const switches = [ - {urlName: "fs-userbadge", human: tr.fsUserbadge}, - {urlName: "fs-search", human: tr.fsSearch}, - {urlName: "fs-welcome-message", human: tr.fsWelcomeMessage}, - {urlName: "fs-layers", human: tr.fsLayers}, - {urlName: "layer-control-toggle", human: tr.fsLayerControlToggle, reverse: true}, - {urlName: "fs-addXXXnew", human: tr.fsAddNew}, - {urlName: "fs-geolocation", human: tr.fsGeolocation}, + {urlName: "fs-userbadge", human: tr.fsUserbadge}, + {urlName: "fs-search", human: tr.fsSearch}, + {urlName: "fs-welcome-message", human: tr.fsWelcomeMessage}, + {urlName: "fs-layers", human: tr.fsLayers}, + {urlName: "layer-control-toggle", human: tr.fsLayerControlToggle, reverse: true}, + {urlName: "fs-add-new", human: tr.fsAddNew}, + {urlName: "fs-geolocation", human: tr.fsGeolocation}, ] @@ -124,7 +134,7 @@ export class ShareScreen extends UIElement { this._options = new VerticalCombine(optionCheckboxes) - const url = currentLocation.map(() => { + const url = (currentLocation ?? new UIEventSource(undefined)).map(() => { let literalText = "https://pietervdvn.github.io/MapComplete/" + layout.id.toLowerCase() + ".html" @@ -132,12 +142,12 @@ export class ShareScreen extends UIElement { const parts = Utils.NoEmpty(Utils.NoNull(optionParts.map((eventSource) => eventSource.data))); let hash = ""; - if (State.state.layoutDefinition !== undefined) { - hash = ("#" + State.state.layoutDefinition) + if (layoutDefinition !== undefined) { + hash = ("#" + layoutDefinition) literalText = "https://pietervdvn.github.io/MapComplete/index.html" parts.push("userlayout=true"); } - + if (parts.length === 0) { return literalText + hash; @@ -145,10 +155,13 @@ export class ShareScreen extends UIElement { return literalText + "?" + parts.join("&") + hash; }, optionParts); + + + this.iframe = url.map(url => `<iframe src="${url}" width="100%" height="100%" title="${layout.title.InnerRender()} with MapComplete"></iframe>`); + this._iframeCode = new VariableUiElement( url.map((url) => { return ` - <iframe src="${url}" width="100%" height="100%" title="${layout.title.InnerRender()} with MapComplete"></iframe> ` }) ); @@ -157,7 +170,7 @@ export class ShareScreen extends UIElement { this._editLayout = new FixedUiElement(""); - if ((State.state.layoutDefinition !== undefined)) { + if ((layoutDefinition !== undefined && State.state?.osmConnection !== undefined)) { this._editLayout = new VariableUiElement( State.state.osmConnection.userDetails.map( diff --git a/customGenerator.ts b/customGenerator.ts index d2740d709..a6283ee13 100644 --- a/customGenerator.ts +++ b/customGenerator.ts @@ -7,6 +7,8 @@ import GeneralSettings from "./UI/CustomGenerator/GeneralSettings"; import {SubtleButton} from "./UI/Base/SubtleButton"; import {TabbedComponent} from "./UI/Base/TabbedComponent"; import AllLayersPanel from "./UI/CustomGenerator/AllLayersPanel"; +import {ShareScreen} from "./UI/ShareScreen"; +import {FromJSON} from "./Customizations/JSON/FromJSON"; import SharePanel from "./UI/CustomGenerator/SharePanel"; @@ -46,6 +48,7 @@ const es = new UIEventSource(test); const encoded = es.map(config => btoa(JSON.stringify(config))); const testUrl = encoded.map(encoded => `./index.html?userlayout=${es.data.id}&test=true#${encoded}`) const liveUrl = encoded.map(encoded => `./index.html?userlayout=${es.data.id}#${encoded}`) +const iframe = liveUrl.map(url => ``); const currentSetting = new UIEventSource>(undefined) @@ -63,7 +66,12 @@ new TabbedComponent([ }, { header: "", - content: "Save" + content: new VariableUiElement(es.map(config => { + return JSON.stringify(config, null, 2) + .replace(/\n/g, "
") + .replace(/ /g, " "); + })) + }, { header: "", @@ -103,7 +111,7 @@ new Combine([helpText, // The preview new Combine([ - new VariableUiElement(testUrl.map(testUrl => ``)) + new VariableUiElement(iframe) ]).AttachTo("bottomright");