diff --git a/Customizations/JSON/FromJSON.ts b/Customizations/JSON/FromJSON.ts index 6b9019e..6276902 100644 --- a/Customizations/JSON/FromJSON.ts +++ b/Customizations/JSON/FromJSON.ts @@ -259,8 +259,14 @@ export class FromJSON { const iconSize = FromJSON.TagRenderingWithDefault(json.iconSize, "iconSize", "40,40,center"); const color = FromJSON.TagRenderingWithDefault(json.color, "layercolor", "#0000ff"); const width = FromJSON.TagRenderingWithDefault(json.width, "layerwidth", "10"); - const tagRenderings = json.tagRenderings?.map(FromJSON.TagRendering) ?? []; - + + let tagRenderingDefs = json.tagRenderings ?? []; + if (tagRenderingDefs.indexOf("images") < 0) { + tagRenderingDefs = ["images", ...tagRenderingDefs]; + } + let tagRenderings = tagRenderingDefs.map(FromJSON.TagRendering); + + const renderTags = {"id": "node/-1"} const presets: Preset[] = json?.presets?.map(preset => { return ({ @@ -294,7 +300,7 @@ export class FromJSON { // the anchor is always set from the center of the point // x, y with x going right and y going down if the values are bigger - const popupAnchor = [0, -iconAnchor[1]+3]; + const popupAnchor = [0, 3 - iconAnchor[1]]; return { color: color.GetContent(tags).txt, diff --git a/Logic/UIEventSource.ts b/Logic/UIEventSource.ts index 31cdff8..36e8131 100644 --- a/Logic/UIEventSource.ts +++ b/Logic/UIEventSource.ts @@ -105,7 +105,16 @@ export class UIEventSource{ }); return newSource; - + } + + public static Chronic(millis: number):UIEventSource{ + const source = new UIEventSource(undefined); + function run() { + source.setData(new Date()); + window.setTimeout(run, millis); + } + run(); + return source; } diff --git a/State.ts b/State.ts index 3f6f24f..7cd622e 100644 --- a/State.ts +++ b/State.ts @@ -23,16 +23,15 @@ export class State { // The singleton of the global state public static state: State; - public static vNumber = "0.0.7d Refactored"; + public static vNumber = "0.0.7e Fixing all the bugs"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { - customLayoutUnlock: 50, - themeGeneratorUnlock: 500, - tagsVisibleAt: 200, - tagsVisibleAndWikiLinked: 250 - - + personalLayoutUnlock: 20, + tagsVisibleAt: 100, + tagsVisibleAndWikiLinked: 150, + themeGeneratorReadOnlyUnlock: 200, + themeGeneratorFullUnlock: 500, }; public static runningFromConsole: boolean = false; @@ -68,13 +67,6 @@ export class State { * The message that should be shown at the center of the screen */ public readonly centerMessage = new UIEventSource(""); - - /** - * The countdown: if set to e.g. ten, it'll start counting down. When reaching zero, changes will be saved. NB: this is implemented later, not in the eventSource - */ - public readonly secondsTillChangesAreSaved = new UIEventSource(0); - - /** This message is shown full screen on mobile devices */ @@ -116,10 +108,6 @@ export class State { latlng: number, accuracy: number }> = new UIEventSource<{ latlng: number, accuracy: number }>(undefined); - - /** After this many milliseconds without changes, saves are sent of to OSM - */ - public readonly saveTimeout = new UIEventSource(30 * 1000); public layoutDefinition: string; public installedThemes: UIEventSource<{ layout: Layout; definition: string }[]>; @@ -259,16 +247,5 @@ export class State { return; } - - - } - - public GetFilteredLayerFor(id: string) : FilteredLayer{ - for (const flayer of this.filteredLayers.data) { - if(flayer.layerDef.id === id){ - return flayer; - } - } - return undefined; } } diff --git a/UI/CustomGenerator/AllLayersPanel.ts b/UI/CustomGenerator/AllLayersPanel.ts index bd401c4..829510f 100644 --- a/UI/CustomGenerator/AllLayersPanel.ts +++ b/UI/CustomGenerator/AllLayersPanel.ts @@ -6,6 +6,7 @@ import {LayoutConfigJson} from "../../Customizations/JSON/LayoutConfigJson"; import Combine from "../Base/Combine"; import {GenerateEmpty} from "./GenerateEmpty"; import LayerPanelWithPreview from "./LayerPanelWithPreview"; +import {UserDetails} from "../../Logic/Osm/OsmConnection"; export default class AllLayersPanel extends UIElement { @@ -15,28 +16,29 @@ export default class AllLayersPanel extends UIElement { private readonly languages: UIEventSource; constructor(config: UIEventSource, - languages: UIEventSource) { + languages: UIEventSource, userDetails: UserDetails) { super(undefined); this._config = config; this.languages = languages; - this.createPanels(); + this.createPanels(userDetails); const self = this; - config.map(config => config.layers.length).addCallback(() => self.createPanels()); + config.map(config => config.layers.length).addCallback(() => self.createPanels(userDetails)); } - private createPanels() { + private createPanels(userDetails: UserDetails) { const self = this; const tabs = []; const layers = this._config.data.layers; for (let i = 0; i < layers.length; i++) { - + tabs.push({ header: "", - content: new LayerPanelWithPreview(this._config, this.languages, i)}); + content: new LayerPanelWithPreview(this._config, this.languages, i, userDetails) + }); } tabs.push({ header: "", diff --git a/UI/CustomGenerator/CustomGeneratorPanel.ts b/UI/CustomGenerator/CustomGeneratorPanel.ts index e69de29..5df977f 100644 --- a/UI/CustomGenerator/CustomGeneratorPanel.ts +++ b/UI/CustomGenerator/CustomGeneratorPanel.ts @@ -0,0 +1,115 @@ +import {UIElement} from "../UIElement"; +import {OsmConnection, UserDetails} from "../../Logic/Osm/OsmConnection"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import SingleSetting from "./SingleSetting"; +import GeneralSettings from "./GeneralSettings"; +import Combine from "../Base/Combine"; +import {VariableUiElement} from "../Base/VariableUIElement"; +import {TabbedComponent} from "../Base/TabbedComponent"; +import PageSplit from "../Base/PageSplit"; +import HelpText from "../../Customizations/HelpText"; +import AllLayersPanel from "./AllLayersPanel"; +import SharePanel from "./SharePanel"; +import {LayoutConfigJson} from "../../Customizations/JSON/LayoutConfigJson"; +import {SubtleButton} from "../Base/SubtleButton"; +import {State} from "../../State"; +import {FixedUiElement} from "../Base/FixedUiElement"; +import SavePanel from "./SavePanel"; +import {LocalStorageSource} from "../../Logic/Web/LocalStorageSource"; + + +export default class CustomGeneratorPanel extends UIElement { + private mainPanel: UIElement; + private loginButton: UIElement; + + private connection: OsmConnection; + + constructor(connection: OsmConnection, layout: LayoutConfigJson) { + super(connection.userDetails); + this.connection = connection; + this.SetClass("main-tabs"); + this.loginButton = new SubtleButton("", "Login to create a custom theme").onClick(() => connection.AttemptLogin()) + const self = this; + self.mainPanel = new FixedUiElement("Attempting to log in..."); + connection.OnLoggedIn(userDetails => { + self.InitMainPanel(layout, userDetails, connection); + self.Update(); + }) + } + + private InitMainPanel(layout: LayoutConfigJson, userDetails: UserDetails, connection: OsmConnection) { + const es = new UIEventSource(layout); + const encoded = es.map(config => btoa(JSON.stringify(config))); + encoded.addCallback(encoded => LocalStorageSource.Get("\"last-custom-theme\"")) + const liveUrl = encoded.map(encoded => `./index.html?userlayout=${es.data.id}#${encoded}`) + const iframe = liveUrl.map(url => ``); + const currentSetting = new UIEventSource>(undefined) + const generalSettings = new GeneralSettings(es, currentSetting); + const languages = generalSettings.languages; + + const chronic = UIEventSource.Chronic(120 * 1000) + .map(date => { + if (es.data.id == undefined) { + return undefined + } + if (es.data.id === "") { + return undefined; + } + const pref = connection.GetLongPreference("installed-theme-" + es.data.id); + pref.setData(encoded.data); + return date; + }); + + const preview = new Combine([ + new VariableUiElement(iframe.stabilized(2500)) + ]).SetClass("preview") + this.mainPanel = new TabbedComponent([ + { + header: "", + content: + new PageSplit( + generalSettings.SetStyle("width: 50vw;"), + new Combine([ + new HelpText(currentSetting).SetStyle("height:calc(100% - 65vh); width: 100%; display:block; overflow-y: auto"), + preview.SetStyle("height:65vh; width:100%; display:block") + ]).SetStyle("position:relative; width: 50%;") + ) + }, + { + header: "", + content: new AllLayersPanel(es, languages, userDetails) + }, + { + header: "", + content: new SavePanel(this.connection, es, chronic) + + }, + { + header: "", + content: new SharePanel(es, liveUrl) + } + ]) + } + + + InnerRender(): string { + const ud = this.connection.userDetails.data; + if (!ud.loggedIn) { + return new Combine([ + "

Not Logged in

", + "You need to be logged in in order to create a custom theme", + this.loginButton + ]).Render(); + } + if (ud.csCount <= State.userJourney.themeGeneratorReadOnlyUnlock) { + return new Combine([ + "

Too little experience/h3>", + `Creating your own (readonly) themes can only be done if you have more then ${State.userJourney.themeGeneratorReadOnlyUnlock} changesets made`, + `Making a theme including survey options can be done at ${State.userJourney.themeGeneratorFullUnlock} changesets`, + this.loginButton + ]).Render(); + } + return this.mainPanel.Render() + } + +} \ No newline at end of file diff --git a/UI/CustomGenerator/LayerPanel.ts b/UI/CustomGenerator/LayerPanel.ts index 9d84f2b..5b1ffe9 100644 --- a/UI/CustomGenerator/LayerPanel.ts +++ b/UI/CustomGenerator/LayerPanel.ts @@ -16,6 +16,9 @@ import {TagRenderingConfigJson} from "../../Customizations/JSON/TagRenderingConf import {MultiInput} from "../Input/MultiInput"; import {LayerConfigJson} from "../../Customizations/JSON/LayerConfigJson"; import PresetInputPanel from "./PresetInputPanel"; +import {UserDetails} from "../../Logic/Osm/OsmConnection"; +import {State} from "../../State"; +import {FixedUiElement} from "../Base/FixedUiElement"; /** * Shows the configuration for a single layer @@ -38,10 +41,11 @@ export default class LayerPanel extends UIElement { constructor(config: UIEventSource, languages: UIEventSource, index: number, - currentlySelected: UIEventSource>) { + currentlySelected: UIEventSource>, + userDetails: UserDetails) { super(); this._config = config; - this.mapRendering = this.setupRenderOptions(config, languages, index, currentlySelected); + this.mapRendering = this.setupRenderOptions(config, languages, index, currentlySelected, userDetails); const actualDeleteButton = new SubtleButton( "./assets/delete.svg", @@ -100,7 +104,7 @@ export default class LayerPanel extends UIElement { currentlySelected); const self = this; - const popupTitleRendering = new TagRenderingPanel(languages, currentlySelected, { + const popupTitleRendering = new TagRenderingPanel(languages, currentlySelected, userDetails, { title: "Popup title", description: "This is the rendering shown as title in the popup for this element", disableQuestions: true @@ -113,7 +117,7 @@ export default class LayerPanel extends UIElement { const tagRenderings = new MultiInput("Add a tag rendering/question", () => ({}), () => { - const tagPanel = new TagRenderingPanel(languages, currentlySelected) + const tagPanel = new TagRenderingPanel(languages, currentlySelected, userDetails) self.registerTagRendering(tagPanel); return tagPanel; }); @@ -124,11 +128,16 @@ export default class LayerPanel extends UIElement { } ) - const presetPanel = new MultiInput("Add a preset", - () => ({tags: [], title: {}}), - () => new PresetInputPanel(currentlySelected, languages)); - this.presetsPanel = presetPanel; - new SingleSetting(config, presetPanel, ["layers", index, "presets"], "Presets", "") + if (userDetails.csCount >= State.userJourney.themeGeneratorFullUnlock) { + + const presetPanel = new MultiInput("Add a preset", + () => ({tags: [], title: {}}), + () => new PresetInputPanel(currentlySelected, languages)); + new SingleSetting(config, presetPanel, ["layers", index, "presets"], "Presets", "") + this.presetsPanel = presetPanel; + } else { + this.presetsPanel = new FixedUiElement(`Creating a custom theme which also edits OSM is only unlocked after ${State.userJourney.themeGeneratorFullUnlock} changesets`).SetClass("alert"); + } function loadTagRenderings() { const values = (config.data.layers[index] as LayerConfigJson).tagRenderings; @@ -152,27 +161,29 @@ export default class LayerPanel extends UIElement { private setupRenderOptions(config: UIEventSource, languages: UIEventSource, index: number, - currentlySelected: UIEventSource>): UIElement { + currentlySelected: UIEventSource>, + userDetails: UserDetails + ): UIElement { const iconSelect = new TagRenderingPanel( - languages, currentlySelected, + languages, currentlySelected, userDetails, { title: "Icon", description: "A visual representation for this layer and for the points on the map.", disableQuestions: true }); - const size = new TagRenderingPanel(languages, currentlySelected, + const size = new TagRenderingPanel(languages, currentlySelected, userDetails, { title: "Icon Size", description: "The size of the icons on the map in pixels. Can vary based on the tagging", disableQuestions: true }); - const color = new TagRenderingPanel(languages, currentlySelected, + const color = new TagRenderingPanel(languages, currentlySelected, userDetails, { title: "Way and area color", description: "The color or a shown way or area. Can vary based on the tagging", disableQuestions: true }); - const stroke = new TagRenderingPanel(languages, currentlySelected, + const stroke = new TagRenderingPanel(languages, currentlySelected, userDetails, { title: "Stroke width", description: "The width of lines representing ways and the outline of areas. Can vary based on the tags", diff --git a/UI/CustomGenerator/LayerPanelWithPreview.ts b/UI/CustomGenerator/LayerPanelWithPreview.ts index 583029c..891ad6c 100644 --- a/UI/CustomGenerator/LayerPanelWithPreview.ts +++ b/UI/CustomGenerator/LayerPanelWithPreview.ts @@ -8,16 +8,17 @@ import {FromJSON} from "../../Customizations/JSON/FromJSON"; import Combine from "../Base/Combine"; import PageSplit from "../Base/PageSplit"; import TagRenderingPreview from "./TagRenderingPreview"; +import {UserDetails} from "../../Logic/Osm/OsmConnection"; export default class LayerPanelWithPreview extends UIElement{ private panel: UIElement; - constructor(config: UIEventSource, languages: UIEventSource, index: number) { + constructor(config: UIEventSource, languages: UIEventSource, index: number, userDetails: UserDetails) { super(); const currentlySelected = new UIEventSource<(SingleSetting)>(undefined); - const layer = new LayerPanel(config, languages, index, currentlySelected); + const layer = new LayerPanel(config, languages, index, currentlySelected, userDetails); const helpText = new HelpText(currentlySelected); const previewTagInput = new MultiTagInput(); diff --git a/UI/CustomGenerator/SavePanel.ts b/UI/CustomGenerator/SavePanel.ts new file mode 100644 index 0000000..1defa14 --- /dev/null +++ b/UI/CustomGenerator/SavePanel.ts @@ -0,0 +1,49 @@ +import {UIElement} from "../UIElement"; +import {VariableUiElement} from "../Base/VariableUIElement"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import {LayoutConfigJson} from "../../Customizations/JSON/LayoutConfigJson"; +import Combine from "../Base/Combine"; +import {OsmConnection} from "../../Logic/Osm/OsmConnection"; +import {FixedUiElement} from "../Base/FixedUiElement"; + +export default class SavePanel extends UIElement { + private json: UIElement; + private lastSaveEl: UIElement; + + constructor( + connection: OsmConnection, + config: UIEventSource, + chronic: UIEventSource) { + super(); + + + + this.lastSaveEl = new VariableUiElement(chronic + .map(date => { + if (date === undefined) { + return new FixedUiElement("Your theme will be saved automatically within two minutes... Click here to force saving").SetClass("alert").Render() + } + return "Your theme was last saved at " + date.toISOString() + })).onClick(() => chronic.setData(new Date())); + + this.json = new VariableUiElement(config.map(config => { + return JSON.stringify(config, null, 2) + .replace(/\n/g, "
") + .replace(/ /g, " "); + })) + .SetClass("literal-code"); + } + + InnerRender(): string { + return new Combine([ + "

Saving

", + this.lastSaveEl, + "

JSON configuration

", + "The url hash is actually no more then a BASE64-encoding of the below JSON. This json contains the full configuration of the theme.
" + + "This configuration is mainly useful for debugging", + this.json + ]).SetClass("scrollable") + .Render(); + } + +} \ No newline at end of file diff --git a/UI/CustomGenerator/TagRenderingPanel.ts b/UI/CustomGenerator/TagRenderingPanel.ts index 6937a95..ba14ccb 100644 --- a/UI/CustomGenerator/TagRenderingPanel.ts +++ b/UI/CustomGenerator/TagRenderingPanel.ts @@ -12,6 +12,8 @@ import {MultiInput} from "../Input/MultiInput"; import MappingInput from "./MappingInput"; import {AndOrTagConfigJson} from "../../Customizations/JSON/TagConfigJson"; import {TagRenderingConfigJson} from "../../Customizations/JSON/TagRenderingConfigJson"; +import {UserDetails} from "../../Logic/Osm/OsmConnection"; +import {State} from "../../State"; export default class TagRenderingPanel extends InputElement { @@ -24,6 +26,7 @@ export default class TagRenderingPanel extends InputElement, currentlySelected: UIEventSource>, + userDetails: UserDetails, options?: { title?: string, description?: string, @@ -36,6 +39,10 @@ export default class TagRenderingPanel extends InputElement", options?.title ?? "TagRendering", "

", options?.description ?? ""]) this.IsImage = options?.isImage ?? false; @@ -47,9 +54,9 @@ export default class TagRenderingPanel extends InputElement(value, input, id, name, description); } - const questionSettings = [ + setting(new MultiLingualTextFields(languages), "question", "Question", "If the key or mapping doesn't match, this question is asked"), setting(new AndOrTagInput(), "condition", "Condition", @@ -70,6 +77,8 @@ export default class TagRenderingPanel extends InputElement)[] = [ setting(new MultiLingualTextFields(languages), "render", "Value to show", " Renders this value. Note that {key}-parts are substituted by the corresponding values of the element. If neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value."), + + questionsNotUnlocked ? `You need at least ${State.userJourney.themeGeneratorFullUnlock} changesets to unlock the 'question'-field and to use your theme to edit OSM data`: "", ...(options?.disableQuestions ? [] : questionSettings), "

Mappings

", diff --git a/UI/FeatureInfoBox.ts b/UI/FeatureInfoBox.ts index ab94a6c..cfcd7be 100644 --- a/UI/FeatureInfoBox.ts +++ b/UI/FeatureInfoBox.ts @@ -26,10 +26,10 @@ export class FeatureInfoBox extends UIElement { private readonly _title: UIElement; private readonly _osmLink: UIElement; private readonly _wikipedialink: UIElement; - private _infoboxes: TagDependantUIElement[]; + private readonly _infoboxes: TagDependantUIElement[]; - private _oneSkipped = Translations.t.general.oneSkippedQuestion.Clone(); - private _someSkipped = Translations.t.general.skippedQuestions.Clone(); + private readonly _oneSkipped = Translations.t.general.oneSkippedQuestion.Clone(); + private readonly _someSkipped = Translations.t.general.skippedQuestions.Clone(); constructor( feature: any, @@ -53,7 +53,7 @@ export class FeatureInfoBox extends UIElement { tagRenderingOption.construct(deps)); } function initTags() { - self._infoboxes = [] + self._infoboxes.splice(0, self._infoboxes.length); for (const tagRenderingOption of elementsToShow) { self._infoboxes.push( tagRenderingOption.construct(deps)); @@ -101,10 +101,10 @@ export class FeatureInfoBox extends UIElement { } - let questionsHtml = ""; + let questionElement: UIElement; if (!State.state.osmConnection.userDetails.data.loggedIn) { - let mostImportantQuestion; + let mostImportantQuestion ; let score = -1000; for (const question of questions) { @@ -114,7 +114,7 @@ export class FeatureInfoBox extends UIElement { } } - questionsHtml = mostImportantQuestion?.Render() ?? ""; + questionElement = mostImportantQuestion; } else if (questions.length > 0) { // We select the most important question and render that one let mostImportantQuestion; @@ -127,11 +127,11 @@ export class FeatureInfoBox extends UIElement { } } - questionsHtml = mostImportantQuestion?.Render() ?? ""; + questionElement = mostImportantQuestion; } else if (skippedQuestions == 1) { - questionsHtml = this._oneSkipped.Render(); + questionElement = this._oneSkipped; } else if (skippedQuestions > 0) { - questionsHtml = this._someSkipped.Render(); + questionElement = this._someSkipped; } const title = new Combine([ @@ -140,12 +140,16 @@ export class FeatureInfoBox extends UIElement { this._osmLink]); const infoboxcontents = new Combine( - [ new VerticalCombine(info, "infobox-information "), questionsHtml]); + [ new VerticalCombine(info, "infobox-information "), questionElement ?? ""]); return "
" + new Combine([ - "
" + title.Render() + "
", - "
" + infoboxcontents.Render() + "
"]).Render() + "
"; + "
", + title, + "
", + "
", + infoboxcontents, + "
"]).Render() + ""; } diff --git a/UI/FullScreenMessageBoxHandler.ts b/UI/FullScreenMessageBoxHandler.ts index b7f09f1..d4685a2 100644 --- a/UI/FullScreenMessageBoxHandler.ts +++ b/UI/FullScreenMessageBoxHandler.ts @@ -1,24 +1,36 @@ import {UIElement} from "./UIElement"; -import {VariableUiElement} from "./Base/VariableUIElement"; import Translations from "./i18n/Translations"; import {State} from "../State"; -import {UIEventSource} from "../Logic/UIEventSource"; +import Combine from "./Base/Combine"; /** * Handles the full screen popup on mobile */ -export class FullScreenMessageBoxHandler { +export class FullScreenMessageBox extends UIElement { - private _uielement: UIEventSource; + private _uielement: UIElement; + private returnToTheMap: UIElement; constructor(onClear: (() => void)) { - this._uielement = State.state.fullScreenMessage; + super(undefined); + const self = this; - this._uielement.addCallback(function () { - self.update(); + + State.state.fullScreenMessage.addCallback(uielement => { + return self._uielement = uielement?.SetClass("messagesboxmobile-scroll")?.Activate(); }); - - this.update(); + this._uielement = State.state.fullScreenMessage.data; + this.ListenTo(State.state.fullScreenMessage); + this.HideOnEmpty(true); + + State.state.fullScreenMessage.addCallback(latestData => { + if (latestData === undefined) { + location.hash = ""; + } else { + location.hash = "#element"; + } + this.Update(); + }) if (window !== undefined) { window.onhashchange = function () { @@ -30,36 +42,24 @@ export class FullScreenMessageBoxHandler { } } - Translations.t.general.returnToTheMap + this.returnToTheMap = Translations.t.general.returnToTheMap.Clone() + .SetClass("to-the-map") .onClick(() => { - self._uielement.setData(undefined); + console.log("Returning...") + State.state.fullScreenMessage.setData(undefined); onClear(); - }) - .AttachTo("to-the-map"); - + self.Update(); + }); } - update() { - const wrapper = document.getElementById("messagesboxmobilewrapper"); - const gen = this._uielement.data; - if (gen === undefined) { - wrapper.classList.add("hidden") - if (location.hash !== "") { - location.hash = "" - } - return; + InnerRender(): string { + if (this._uielement === undefined) { + return ""; } - location.hash = "#element" - wrapper.classList.remove("hidden"); - - gen - ?.HideOnEmpty(true) - ?.AttachTo("messagesboxmobile") - ?.Activate(); - - + return new Combine([this._uielement, this.returnToTheMap]).SetStyle("").Render(); } + } \ No newline at end of file diff --git a/UI/MoreScreen.ts b/UI/MoreScreen.ts index 440f385..57eeef2 100644 --- a/UI/MoreScreen.ts +++ b/UI/MoreScreen.ts @@ -72,7 +72,7 @@ export class MoreScreen extends UIElement { els.push(new VariableUiElement( State.state.osmConnection.userDetails.map(userDetails => { - if (userDetails.csCount < State.userJourney.themeGeneratorUnlock) { + if (userDetails.csCount < State.userJourney.themeGeneratorReadOnlyUnlock) { return tr.requestATheme.Render(); } return new SubtleButton("./assets/pencil.svg", tr.createYourOwnTheme, { @@ -86,7 +86,7 @@ export class MoreScreen extends UIElement { for (const k in AllKnownLayouts.allSets) { const layout : Layout = AllKnownLayouts.allSets[k]; if (k === PersonalLayout.NAME) { - if (State.state.osmConnection.userDetails.data.csCount < State.userJourney.customLayoutUnlock) { + if (State.state.osmConnection.userDetails.data.csCount < State.userJourney.personalLayoutUnlock) { continue; } } @@ -99,7 +99,6 @@ export class MoreScreen extends UIElement { const customThemesNames = State.state.installedThemes.data ?? []; if (customThemesNames.length > 0) { - console.log(customThemesNames) els.push(Translations.t.general.customThemeIntro) for (const installed of State.state.installedThemes.data) { diff --git a/UI/UIElement.ts b/UI/UIElement.ts index dea2f53..cae8fb5 100644 --- a/UI/UIElement.ts +++ b/UI/UIElement.ts @@ -68,7 +68,7 @@ export abstract class UIElement extends UIEventSource { if (UIElement.runningFromConsole) { return; } - + let element = document.getElementById(this.id); if (element === undefined || element === null) { // The element is not painted @@ -99,7 +99,7 @@ export abstract class UIElement extends UIEventSource { if (element.innerHTML === "") { element.parentElement.style.display = "none"; } else { - element.parentElement.style.display = undefined; + element.parentElement.style.display = "block"; } } @@ -174,7 +174,7 @@ export abstract class UIElement extends UIEventSource { public abstract InnerRender(): string; - public Activate(): void { + public Activate(): UIElement { for (const i in this) { const child = this[i]; if (child instanceof UIElement) { @@ -187,6 +187,7 @@ export abstract class UIElement extends UIEventSource { } } } + return this; }; public IsEmpty(): boolean { diff --git a/UI/WelcomeMessage.ts b/UI/WelcomeMessage.ts index a60c20a..66cfc5a 100644 --- a/UI/WelcomeMessage.ts +++ b/UI/WelcomeMessage.ts @@ -1,7 +1,6 @@ import {UIElement} from "./UIElement"; import Locale from "../UI/i18n/Locale"; import {State} from "../State"; -import {Layout} from "../Customizations/Layout"; import Translations from "./i18n/Translations"; import Combine from "./Base/Combine"; import {InitUiElements} from "../InitUiElements"; @@ -20,27 +19,18 @@ export class WelcomeMessage extends UIElement { super(State.state.osmConnection.userDetails); this.ListenTo(Locale.language); this.languagePicker = InitUiElements.CreateLanguagePicker(Translations.t.general.pickLanguage); + const layout = State.state.layoutToUse.data; - function fromLayout(f: (layout: Layout) => (string | UIElement)): UIElement { - return Translations.W(f(State.state.layoutToUse.data)); - } - - this.description = fromLayout((layout) => layout.welcomeMessage); - this.plzLogIn = - fromLayout((layout) => layout.gettingStartedPlzLogin - .onClick(() => {State.state.osmConnection.AttemptLogin()}) - ); - this.welcomeBack = fromLayout((layout) => layout.welcomeBackMessage); - this.tail = fromLayout((layout) => layout.welcomeTail); + this.description =Translations.W(layout.welcomeMessage); + this.plzLogIn = + Translations.W(layout.gettingStartedPlzLogin) + .onClick(() => { + State.state.osmConnection.AttemptLogin() + }); + this.welcomeBack = Translations.W(layout.welcomeBackMessage); + this.tail = Translations.W(layout.welcomeTail); } - protected InnerUpdate(htmlElement: HTMLElement) { - super.InnerUpdate(htmlElement); - console.log("Innerupdating welcome message") - this.plzLogIn.Update(); - } - - InnerRender(): string { let loginStatus = undefined; @@ -52,7 +42,8 @@ export class WelcomeMessage extends UIElement { return new Combine([ this.description, "

", - // TODO this button is broken - figure out why loginStatus, + // TODO this button is broken - figure out why loginStatus, + loginStatus, this.tail, "
", this.languagePicker diff --git a/assets/arrow-left-smooth.svg b/assets/arrow-left-smooth.svg new file mode 100644 index 0000000..1139447 --- /dev/null +++ b/assets/arrow-left-smooth.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/assets/arrow-right-smooth.svg b/assets/arrow-right-smooth.svg new file mode 100644 index 0000000..7cc17c6 --- /dev/null +++ b/assets/arrow-right-smooth.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/assets/layers/bike_parking/bike_parking.json b/assets/layers/bike_parking/bike_parking.json index f87738c..fc5129c 100644 --- a/assets/layers/bike_parking/bike_parking.json +++ b/assets/layers/bike_parking/bike_parking.json @@ -12,7 +12,11 @@ "amenity=bicycle_parking" ] }, - "icon": "./assets/layers/bike_parking/parking.svg", + "icon": { + "render": { + "en": "./assets/layers/bike_parking/parking.svg" + } + }, "size": { "render": { "en": "50,50,bottom" diff --git a/customGenerator.ts b/customGenerator.ts index 75dec57..19532fa 100644 --- a/customGenerator.ts +++ b/customGenerator.ts @@ -1,70 +1,25 @@ import {UIEventSource} from "./Logic/UIEventSource"; -import SingleSetting from "./UI/CustomGenerator/SingleSetting"; -import Combine from "./UI/Base/Combine"; -import {VariableUiElement} from "./UI/Base/VariableUIElement"; -import GeneralSettings from "./UI/CustomGenerator/GeneralSettings"; -import {TabbedComponent} from "./UI/Base/TabbedComponent"; -import AllLayersPanel from "./UI/CustomGenerator/AllLayersPanel"; -import SharePanel from "./UI/CustomGenerator/SharePanel"; import {GenerateEmpty} from "./UI/CustomGenerator/GenerateEmpty"; -import PageSplit from "./UI/Base/PageSplit"; -import HelpText from "./Customizations/HelpText"; import {TagRendering} from "./Customizations/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"; let layout = GenerateEmpty.createEmptyLayout(); -if(window.location.hash.length > 10){ +if (window.location.hash.length > 10) { layout = JSON.parse(atob(window.location.hash.substr(1))) as LayoutConfigJson; +} else { + const hash = LocalStorageSource.Get("last-custom-theme").data + if (hash !== undefined) { + layout = JSON.parse(atob(hash)) as LayoutConfigJson; + } } -const es = new UIEventSource(layout); -const encoded = es.map(config => btoa(JSON.stringify(config))); -const liveUrl = encoded.map(encoded => `./index.html?userlayout=${es.data.id}#${encoded}`) -const iframe = liveUrl.map(url => ``); TagRendering.injectFunction(); -const currentSetting = new UIEventSource>(undefined) +const connection = new OsmConnection(false, new UIEventSource(undefined), "customGenerator", false); -const generalSettings = new GeneralSettings(es, currentSetting); -const languages = generalSettings.languages; - - -// The preview -const preview = new Combine([ - new VariableUiElement(iframe.stabilized(2500)) -]).SetClass("preview") - - -new TabbedComponent([ - { - header: "", - content: - new PageSplit( - generalSettings.SetStyle("width: 50vw;"), - new Combine([ - new HelpText(currentSetting).SetStyle("height:calc(100% - 65vh); width: 100%; display:block; overflow-y: auto"), - preview.SetStyle("height:65vh; width:100%; display:block") - ]).SetStyle("position:relative; width: 50%;") - ) - }, - { - header: "", - content: new AllLayersPanel(es, languages) - }, - { - header: "", - content: new VariableUiElement(es.map(config => { - return JSON.stringify(config, null, 2) - .replace(/\n/g, "
") - .replace(/ /g, " "); - })) - - }, - { - header: "", - content: new SharePanel(es, liveUrl) - } -]).SetClass("main-tabs") +new CustomGeneratorPanel(connection, layout) .AttachTo("maindiv"); diff --git a/index.css b/index.css index 2fe3ab9..3e528bb 100644 --- a/index.css +++ b/index.css @@ -284,6 +284,9 @@ padding-top: 0.2em; padding-bottom: 0.2em; } + #hidden-on-mobile { + display: none; /*Only shown on small screens*/ + } @media only screen and (max-height: 600px) and (not (max-width:700px)) { @@ -351,9 +354,6 @@ padding-right: 10px; /* Shadow offset */ } -#messagesboxmobilewrapper { - display: none; /*Only shown on small screens*/ -} .collapse-button { position: absolute; @@ -556,6 +556,10 @@ @media only screen and (max-width: 600px), only screen and (max-height: 600px) { + #hidden-on-mobile { + display: block; + } + #messagesbox-wrapper { display: none; } @@ -590,33 +594,20 @@ } - #messagesboxmobilewrapper { - position: absolute; - padding: 0; - margin: 0; - z-index: 5050; - transition: all 500ms linear; - overflow: hidden; - border-radius: 0; - width: 100%; - height: 100%; - display: block; - background-color: white; - } - - #messagesboxmobile-scroll { + .messagesboxmobile-scroll { display: block; width: 100vw; - overflow-y: auto; - padding: 0; + box-sizing: border-box; + overflow-y: scroll; + padding: 1em; margin: 0; - height: calc(100% - 5em); /*Height of to-the-map is 5em*/ + height: calc(100vh - 5em); /*Height of to-the-map is 5em*/ } #messagesboxmobile { - padding: 1em; - padding-bottom: 2em; - border-radius: 1em; + display: block; + position: absolute; + z-index: 10000; background-color: white; } @@ -634,77 +625,77 @@ box-shadow: unset; overflow-y: unset; } - + } -#to-the-map { - position: relative; -} - -#to-the-map > span{ - - position: absolute; - box-sizing: border-box; - height: 3em; - - padding: 0.5em; - margin: 0; - - padding-top: 0.75em; - text-align: center; - width: 100%; - color: white; - background-color: #7ebc6f; - cursor: pointer; - font-size: xx-large; - font-weight: bold; - -} - - -@media only screen and (max-height: 400px) { - /* Landscape: small 'to the map' */ - #to-the-map { - position: relative; - height: 100%; - width: 100% - } - - #to-the-map span { - width: auto; - border-top-left-radius: 1.5em; - position: absolute; - z-index: 5; - right: 0; - bottom: 0; - height: auto; - margin:0; - padding: 1em; - padding-bottom: 0.75em; - height: 3em; - font-size: x-large; - } - - #messagesboxmobile { - padding-bottom: 5em; - } - - #messagesboxmobile-scroll { - position: absolute; - z-index: 2; - width: 100vw; - height: 100vh; + .to-the-map { + display: block; box-sizing: border-box; - } - - #welcomeMessage{ - box-shadow: unset; - max-height: 100vh; + height: 2.5em; + margin: 0; + padding: 0.5em; + padding-top: 0.75em; + text-align: center; + color: white; + background-color: #7ebc6f; + cursor: pointer; + font-size: xx-large; + font-weight: bold; + border-top-left-radius: 0.5em; + border-top-right-radius: 0.5em; } -} + + @media only screen and (max-height: 400px) { + /* Landscape: small 'to the map' */ + #hidden-on-mobile { + display: unset; + } + + .to-the-map { + position: relative; + height: 100%; + width: 100% + } + + .to-the-map span { + width: auto; + border-top-left-radius: 1.5em; + position: absolute; + z-index: 5; + right: 0; + bottom: 0; + height: auto; + margin: 0; + padding: 1em; + padding-bottom: 0.75em; + height: 3em; + font-size: x-large; + } + + #messagesboxmobile { + position: absolute; + display: block; + z-index: 10000; + padding-bottom: 5em; + } + + .messagesboxmobile-scroll { + display: block; + width: 100vw; + height: calc(100vh - 5em); + box-sizing: border-box; + overflow-y: scroll; + } + + #welcomeMessage { + box-shadow: unset; + max-height: 100vh; + } + + } .logo { float:right; @@ -841,170 +832,173 @@ } -.attribution-author { - display: inline-block; -} + .attribution-author { + display: inline-block; + } -.license { - font-size: small; - font-weight: lighter; -} + .license { + font-size: small; + font-weight: lighter; + } -.attribution a { - color: white; -} + .attribution a { + color: white; + } -/**************** Image upload flow ***************/ + /**************** Image upload flow ***************/ -.imageflow { - margin-top: 1em; - margin-bottom: 2em; - text-align: center; + .imageflow { + margin-top: 1em; + margin-bottom: 2em; + text-align: center; -} + } -.imageflow-file-input-wrapper { - display: flex; - flex-wrap: wrap; - padding: 0.5em; - border-radius: 1em; - border: 3px solid black; -} + .imageflow-file-input-wrapper { + display: flex; + flex-wrap: wrap; + padding: 0.5em; + border-radius: 1em; + border: 3px solid black; + } -.imageflow-add-picture { - font-size: 28px; - font-weight: bold; - float: left; - margin-top: 4px; - padding-top: 4px; - padding-bottom: 4px; - padding-left: 13px; -} + .imageflow-add-picture { + font-size: 28px; + font-weight: bold; + float: left; + margin-top: 4px; + padding-top: 4px; + padding-bottom: 4px; + padding-left: 13px; + } -.imageflow-file-input-wrapper img { - width: 36px; - height: 36px; - padding: 0.1em; - margin-top: 5px; - border-radius: 0; - float: left; -} + .imageflow-file-input-wrapper img { + width: 36px; + height: 36px; + padding: 0.1em; + margin-top: 5px; + border-radius: 0; + float: left; + } -.license-picker { - float: left; -} + .license-picker { + float: left; + } -.imageflow > input { - display: none; -} + .imageflow > input { + display: none; + } -/***************** Info box (box containing features and questions ******************/ + /***************** Info box (box containing features and questions ******************/ -.leaflet-popup-content { - width: 40em !important; -} - -#messagesboxmobile .featureinfobox { - max-height: unset; - overflow-y: unset; -} - -#messagesboxmobile .featureinfobox > div { - width: 100%; - max-width: unset; - padding-left: unset; -} - -.featureinfobox { - max-height: 80vh; - overflow-y: auto; -} - -.featureinfobox > div { - width: calc(100% - 2em); - padding-left: 1em; -} + .leaflet-popup-content { + width: 40em !important; + } -.featureinfoboxtitle { - position: relative; -} + -.question .form-text-field > input{ - width: 100%; - box-sizing: border-box; -} + #messagesboxmobile .featureinfobox { + max-height: unset; + overflow-y: unset; + } -.osmlink { - position: absolute; - right: 0; -} + #messagesboxmobile .featureinfobox > div { + width: 100%; + max-width: unset; + padding-left: unset; + } -.osm-logo path { - fill: #7ebc6f; -} + .featureinfobox { + max-height: 80vh; + overflow-y: auto; + } -.featureinfoboxtitle .answer { - display: inline; - margin-right: 3em; -} - -.featureinfoboxtitle .answer-text { - display: inline; -} - -.featureinfoboxtitle .editbutton { - float: none; - width: 0.8em; - height: 0.8em; - padding: 0.3em; - border-radius: 0.35em; - border: solid black 1px; - margin-left: 0.5em; - top: 0.2em; - vertical-align: middle; - -} + .featureinfobox > div { + width: calc(100% - 2em); + padding-left: 1em; + } -.editbutton { - width: 1.3em; - height: 1.3em; - padding: 0.5em; - border-radius: 0.65em; - border: solid black 1px; + .featureinfoboxtitle { + position: relative; + } - font-size: medium; - float: right; + .question .form-text-field > input { + width: 100%; + box-sizing: border-box; + } + + .osmlink { + position: absolute; + right: 0; + } + + .osm-logo path { + fill: #7ebc6f; + } + + .featureinfoboxtitle .answer { + display: inline; + margin-right: 3em; + } + + .featureinfoboxtitle .answer-text { + display: inline; + } + + .featureinfoboxtitle .editbutton { + float: none; + width: 0.8em; + height: 0.8em; + padding: 0.3em; + border-radius: 0.35em; + border: solid black 1px; + margin-left: 0.5em; + top: 0.2em; + vertical-align: middle; + + } -} + .editbutton { + width: 1.3em; + height: 1.3em; + padding: 0.5em; + border-radius: 0.65em; + border: solid black 1px; -.wikipedialink { - position: absolute; - right: 24px; - width: 24px; - height: 24px; - padding-right: 12px; -} - -.wikipedialink img { - width: 24px; - height: 24px; -} + font-size: medium; + float: right; -.featureinfoboxtitle span { - font-weight: bold; - font-size: x-large; -} + } -.featureinfoboxtitle a { - float: right; - margin-left: 1em; + .wikipedialink { + position: absolute; + right: 24px; + width: 24px; + height: 24px; + padding-right: 12px; + } + + .wikipedialink img { + width: 24px; + height: 24px; + } + + + .featureinfoboxtitle span { + font-weight: bold; + font-size: x-large; + } + + .featureinfoboxtitle a { + float: right; + margin-left: 1em; } @@ -1273,6 +1267,7 @@ padding: 0.5em; word-break: break-all; color: black; + box-sizing: border-box; } .iframe-code-block { diff --git a/index.html b/index.html index c982515..754b501 100644 --- a/index.html +++ b/index.html @@ -14,7 +14,7 @@