diff --git a/Customizations/JSON/TagRenderingConfig.ts b/Customizations/JSON/TagRenderingConfig.ts index 80e51c603..52e214c37 100644 --- a/Customizations/JSON/TagRenderingConfig.ts +++ b/Customizations/JSON/TagRenderingConfig.ts @@ -7,8 +7,6 @@ import {Utils} from "../../Utils"; import {TagUtils} from "../../Logic/Tags/TagUtils"; import {And} from "../../Logic/Tags/And"; import {TagsFilter} from "../../Logic/Tags/TagsFilter"; -import {UIElement} from "../../UI/UIElement"; -import {SubstitutedTranslation} from "../../UI/SubstitutedTranslation"; /*** * The parsed version of TagRenderingConfigJSON diff --git a/Models/Constants.ts b/Models/Constants.ts index 71625ac88..06b2a2c4a 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -2,7 +2,7 @@ import { Utils } from "../Utils"; export default class Constants { - public static vNumber = "0.8.0"; + public static vNumber = "0.8.0-rc0"; // The user journey states thresholds when a new feature gets unlocked public static userJourney = { diff --git a/UI/Base/SubtleButton.ts b/UI/Base/SubtleButton.ts index c87e12158..9d87c903d 100644 --- a/UI/Base/SubtleButton.ts +++ b/UI/Base/SubtleButton.ts @@ -20,6 +20,7 @@ export class SubtleButton extends UIElement { private static generateContent(imageUrl: string | BaseUIElement, messageT: string | BaseUIElement, linkTo: { url: string | UIEventSource, newTab?: boolean } = undefined): BaseUIElement { const message = Translations.W(messageT); + message let img; if ((imageUrl ?? "") === "") { img = undefined; @@ -36,7 +37,7 @@ export class SubtleButton extends UIElement { return new Combine([ image, message?.SetClass("blcok ml-4 overflow-ellipsis"), - ]).SetClass("flex group"); + ]).SetClass("flex group w-full"); } @@ -44,7 +45,7 @@ export class SubtleButton extends UIElement { new Combine([ image, message?.SetClass("block ml-4 overflow-ellipsis") - ]).SetClass("flex group"), + ]).SetClass("flex group w-full"), linkTo.url, linkTo.newTab ?? false ) diff --git a/UI/BigComponents/FullWelcomePaneWithTabs.ts b/UI/BigComponents/FullWelcomePaneWithTabs.ts index 918df9cda..b05c26d0d 100644 --- a/UI/BigComponents/FullWelcomePaneWithTabs.ts +++ b/UI/BigComponents/FullWelcomePaneWithTabs.ts @@ -23,14 +23,14 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { const layoutToUse = State.state.layoutToUse.data; super ( () => layoutToUse.title.Clone(), - () => FullWelcomePaneWithTabs.GenerateContents(layoutToUse, State.state.osmConnection.userDetails), + () => FullWelcomePaneWithTabs.GenerateContents(layoutToUse, State.state.osmConnection.userDetails, isShown), "welcome" ,isShown ) } - private static ConstructBaseTabs(layoutToUse: LayoutConfig): { header: string | BaseUIElement; content: BaseUIElement }[]{ + private static ConstructBaseTabs(layoutToUse: LayoutConfig, isShown: UIEventSource): { header: string | BaseUIElement; content: BaseUIElement }[]{ - let welcome: BaseUIElement = new ThemeIntroductionPanel(); + let welcome: BaseUIElement = new ThemeIntroductionPanel(isShown); if (layoutToUse.id === personal.id) { welcome = new PersonalLayersPanel(); } @@ -58,10 +58,10 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { return tabs; } - private static GenerateContents(layoutToUse: LayoutConfig, userDetails: UIEventSource) { + private static GenerateContents(layoutToUse: LayoutConfig, userDetails: UIEventSource, isShown: UIEventSource) { - const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(layoutToUse) - const tabsWithAboutMc = [...FullWelcomePaneWithTabs.ConstructBaseTabs(layoutToUse)] + const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(layoutToUse, isShown) + const tabsWithAboutMc = [...FullWelcomePaneWithTabs.ConstructBaseTabs(layoutToUse, isShown)] tabsWithAboutMc.push({ header: Svg.help, content: new Combine([Translations.t.general.aboutMapcomplete.Clone(), "
Version " + Constants.vNumber]) diff --git a/UI/BigComponents/ThemeIntroductionPanel.ts b/UI/BigComponents/ThemeIntroductionPanel.ts index fc1750f96..7ea3701da 100644 --- a/UI/BigComponents/ThemeIntroductionPanel.ts +++ b/UI/BigComponents/ThemeIntroductionPanel.ts @@ -4,26 +4,45 @@ import LanguagePicker from "../LanguagePicker"; import Translations from "../i18n/Translations"; import {VariableUiElement} from "../Base/VariableUIElement"; import Toggle from "../Input/Toggle"; +import {SubtleButton} from "../Base/SubtleButton"; +import Svg from "../../Svg"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import {FixedUiElement} from "../Base/FixedUiElement"; export default class ThemeIntroductionPanel extends VariableUiElement { - constructor() { + constructor(isShown: UIEventSource) { const languagePicker = new VariableUiElement( State.state.layoutToUse.map(layout => LanguagePicker.CreateLanguagePicker(layout.language, Translations.t.general.pickLanguage.Clone())) ) ; + + const toTheMap = new SubtleButton( + new FixedUiElement(""), + Translations.t.general.openTheMap.Clone().SetClass("text-xl font-bold w-full text-center") + ).onClick(() =>{ + isShown.setData(false) + }).SetClass("only-on-mobile") const plzLogIn = - Translations.t.general.loginWithOpenStreetMap - .Clone() + new SubtleButton( + Svg.osm_logo_ui(), + + new Combine([Translations.t.general.loginWithOpenStreetMap + .Clone().SetClass("text-xl font-bold"), + Translations.t.general.loginOnlyNeededToEdit.Clone().SetClass("font-bold")] + ).SetClass("flex flex-col text-center w-full") + ) .onClick(() => { State.state.osmConnection.AttemptLogin() }); const welcomeBack = Translations.t.general.welcomeBack.Clone(); + + const loginStatus = new Toggle( @@ -40,6 +59,7 @@ export default class ThemeIntroductionPanel extends VariableUiElement { super(State.state.layoutToUse.map (layout => new Combine([ layout.description.Clone(), "

", + toTheMap, loginStatus, layout.descriptionTail.Clone(), "
", diff --git a/UI/Image/SlideShow.ts b/UI/Image/SlideShow.ts index 8c7fb1a2c..6a1ef5dac 100644 --- a/UI/Image/SlideShow.ts +++ b/UI/Image/SlideShow.ts @@ -13,7 +13,6 @@ export class SlideShow extends BaseUIElement { protected InnerConstructElement(): HTMLElement { const el = document.createElement("div") - el.classList.add("slic-carousel") el.style.overflowX = "auto" el.style.width = "min-content" el.style.minWidth = "min-content" @@ -25,8 +24,9 @@ export class SlideShow extends BaseUIElement { } for (const element of elements ?? []) { - element.SetClass("block ml-1") - .SetStyle("width: 300px; max-height: var(--image-carousel-height); height: var(--image-carousel-height)") + element + .SetClass("block ml-1; bg-gray-200") + .SetStyle("min-width: 150; max-height: var(--image-carousel-height); min-height: var(--image-carousel-height)") el.appendChild(element.ConstructElement()) } diff --git a/UI/Input/Checkboxes.ts b/UI/Input/Checkboxes.ts index 025d8e550..e46227ecf 100644 --- a/UI/Input/Checkboxes.ts +++ b/UI/Input/Checkboxes.ts @@ -7,77 +7,83 @@ import BaseUIElement from "../BaseUIElement"; * Supports multi-input */ export default class CheckBoxes extends InputElement { - IsSelected: UIEventSource = new UIEventSource(false); - - -private readonly _element : HTMLElement - private static _nextId = 0; -private readonly value: UIEventSource - constructor(elements: BaseUIElement[], value =new UIEventSource([])) { + IsSelected: UIEventSource = new UIEventSource(false); + private readonly value: UIEventSource + private readonly _elements: BaseUIElement[]; + + constructor(elements: BaseUIElement[], value = new UIEventSource([])) { super(); this.value = value; - elements = Utils.NoNull(elements); - - const el = document.createElement("form") - - for (let i = 0; i < elements.length; i++) { - - let inputI = elements[i]; - const input = document.createElement("input") - const id = CheckBoxes._nextId - CheckBoxes._nextId ++; - input.id = "checkbox"+id + this._elements = Utils.NoNull(elements); + this.SetClass("flex flex-col") - input.type = "checkbox" - const label = document.createElement("label") - label.htmlFor = input.id - label.appendChild(inputI.ConstructElement()) - - value.addCallbackAndRun(selectedValues =>{ - if(selectedValues === undefined){ - return; - } - if(selectedValues.indexOf(i) >= 0){ - input.checked = true; - } - }) - - input.onchange = () => { - const index = value.data.indexOf(i); - if(input.checked && index < 0){ - value.data.push(i); - value.ping(); - }else if(index >= 0){ - value.data.splice(index,1); - value.ping(); - } - } - - - el.appendChild(input) - el.appendChild(document.createElement("br")) - } - - - } - protected InnerConstructElement(): HTMLElement { - return this._element - } - - IsValid(ts: number[]): boolean { return ts !== undefined; - + } GetValue(): UIEventSource { return this.value; } + protected InnerConstructElement(): HTMLElement { + const el = document.createElement("form") + const value = this.value; + const elements = this._elements; + + for (let i = 0; i < elements.length; i++) { + + let inputI = elements[i]; + const input = document.createElement("input") + const id = CheckBoxes._nextId + CheckBoxes._nextId++; + input.id = "checkbox" + id + + input.type = "checkbox" + input.classList.add("p-1","cursor-pointer","ml-3","pl-3") + + const label = document.createElement("label") + label.htmlFor = input.id + label.appendChild(inputI.ConstructElement()) + label.classList.add("block","w-full","p-2","cursor-pointer","bg-red") + + const wrapper = document.createElement("span") + wrapper.classList.add("flex","w-full","border", "border-gray-400") + wrapper.appendChild(input) + wrapper.appendChild(label) + el.appendChild(wrapper) + + value.addCallbackAndRun(selectedValues => { + if (selectedValues === undefined) { + return; + } + if (selectedValues.indexOf(i) >= 0) { + input.checked = true; + } + }) + + input.onchange = () => { + // Index = index in the list of already checked items + const index = value.data.indexOf(i); + if (input.checked && index < 0) { + value.data.push(i); + value.ping(); + } else if (index >= 0) { + value.data.splice(index, 1); + value.ping(); + } + } + + + } + + + return el; + } } \ No newline at end of file diff --git a/UI/Input/DirectionInput.ts b/UI/Input/DirectionInput.ts index 858c9b73c..f5b1b05ef 100644 --- a/UI/Input/DirectionInput.ts +++ b/UI/Input/DirectionInput.ts @@ -18,6 +18,7 @@ export default class DirectionInput extends InputElement { this.value = value ?? new UIEventSource(undefined); } + protected InnerConstructElement(): HTMLElement { diff --git a/UI/Input/RadioButton.ts b/UI/Input/RadioButton.ts index 760c37194..0ed44402c 100644 --- a/UI/Input/RadioButton.ts +++ b/UI/Input/RadioButton.ts @@ -7,12 +7,19 @@ export class RadioButton extends InputElement { IsSelected: UIEventSource = new UIEventSource(false); private readonly value: UIEventSource; private _elements: InputElement[]; - private readonly _element: HTMLElement; + private _selectFirstAsDefault: boolean; constructor(elements: InputElement[], selectFirstAsDefault = true) { super() - elements = Utils.NoNull(elements); + this._selectFirstAsDefault = selectFirstAsDefault; + this._elements = Utils.NoNull(elements); + this.value = new UIEventSource(undefined) + } + protected InnerConstructElement(): HTMLElement { + const elements = this._elements; + const selectFirstAsDefault = this._selectFirstAsDefault; + const selectedElementIndex: UIEventSource = new UIEventSource(null); const value = UIEventSource.flatten(selectedElementIndex.map( @@ -22,6 +29,7 @@ export class RadioButton extends InputElement { } } ), elements.map(e => e?.GetValue())); + value.syncWith(this.value) if(selectFirstAsDefault){ @@ -61,7 +69,20 @@ export class RadioButton extends InputElement { RadioButton._nextId++ const form = document.createElement("form") - this._element = form; + const inputs = [] + + value.addCallbackAndRun( + selected => { + + let somethingChecked = false; + for (let i = 0; i < inputs.length; i++){ + let input = inputs[i]; + input.checked = !somethingChecked && elements[i].IsValid(selected); + somethingChecked = somethingChecked || input.checked + } + } + ) + for (let i1 = 0; i1 < elements.length; i1++) { let element = elements[i1]; const labelHtml = element.ConstructElement(); @@ -73,6 +94,7 @@ export class RadioButton extends InputElement { input.id = "radio" + groupId + "-" + i1; input.name = groupId; input.type = "radio" + input.classList.add("p-1","cursor-pointer","ml-2","pl-2","pr-0","m-0","ml-3") input.onchange = () => { if(input.checked){ @@ -80,30 +102,26 @@ export class RadioButton extends InputElement { } } - value.addCallbackAndRun( - selected => input.checked = element.IsValid(selected) - ) + + inputs.push(input) const label = document.createElement("label") label.appendChild(labelHtml) label.htmlFor = input.id; + label.classList.add("block","w-full","p-2","cursor-pointer","bg-red") + const block = document.createElement("div") block.appendChild(input) block.appendChild(label) + block.classList.add("flex","w-full","border", "rounded-full", "border-gray-400") form.appendChild(block) - form.addEventListener("change", () => { - // TODO FIXME - } - ); } - this.value = value; - this._elements = elements; - this.SetClass("flex flex-col") + return form; } IsValid(t: T): boolean { @@ -120,9 +138,6 @@ export class RadioButton extends InputElement { } - protected InnerConstructElement(): HTMLElement { - return this._element; - } /* public ShowValue(t: T): boolean { diff --git a/UI/Input/TextField.ts b/UI/Input/TextField.ts index ff9101381..4c878ede5 100644 --- a/UI/Input/TextField.ts +++ b/UI/Input/TextField.ts @@ -67,10 +67,10 @@ export class TextField extends InputElement { this.value.addCallbackAndRun(value => { if (!(value !== undefined && value !== null)) { + field["value"] = ""; return; } - // @ts-ignore - field.value = value; + field["value"] = value; if (self.IsValid(value)) { self.RemoveClass("invalid") } else { diff --git a/UI/Popup/EditableTagRendering.ts b/UI/Popup/EditableTagRendering.ts index a420c73b8..85952d9a6 100644 --- a/UI/Popup/EditableTagRendering.ts +++ b/UI/Popup/EditableTagRendering.ts @@ -8,15 +8,13 @@ import State from "../../State"; import Svg from "../../Svg"; import Toggle from "../Input/Toggle"; import BaseUIElement from "../BaseUIElement"; -import {FixedUiElement} from "../Base/FixedUiElement"; export default class EditableTagRendering extends Toggle { constructor(tags: UIEventSource, - configuration: TagRenderingConfig) { - - const editMode = new UIEventSource(false); - + configuration: TagRenderingConfig, + editMode = new UIEventSource(false) + ) { const answer: BaseUIElement = new TagRenderingAnswer(tags, configuration) answer.SetClass("w-full") let rendering = answer; diff --git a/UI/Popup/SaveButton.ts b/UI/Popup/SaveButton.ts index 415b30da5..85c7b3076 100644 --- a/UI/Popup/SaveButton.ts +++ b/UI/Popup/SaveButton.ts @@ -26,8 +26,8 @@ export class SaveButton extends Toggle { isSaveable ) super( - save - , pleaseLogin, + save, + pleaseLogin, osmConnection?.userDetails?.map(userDetails => userDetails.loggedIn) ?? new UIEventSource(false) ) diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index 292b53021..70da15be8 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -71,7 +71,8 @@ export default class TagRenderingQuestion extends UIElement { } - this._saveButton = new SaveButton(this._inputElement.GetValue(), State.state?.osmConnection) + this._saveButton = new SaveButton(this._inputElement.GetValue(), + State.state?.osmConnection) .onClick(save) @@ -92,7 +93,7 @@ export default class TagRenderingQuestion extends UIElement { return tags.asHumanString(true, true, self._tags.data); } ) - ).SetClass("block") + ).SetClass("block break-all") @@ -156,7 +157,9 @@ export default class TagRenderingQuestion extends UIElement { oppositeTags.push(notSelected); } tags.push(TagUtils.FlattenMultiAnswer(oppositeTags)); - return TagUtils.FlattenMultiAnswer(tags); + const actualTags = TagUtils.FlattenMultiAnswer(tags); + console.log("Converted ", indices.join(","), "into", actualTags.asHumanString(false, false, {}), "with elems", elements) + return actualTags; }, (tags: TagsFilter) => { // {key --> values[]} diff --git a/css/mobile.css b/css/mobile.css index a9b321f2d..7165f0cd7 100644 --- a/css/mobile.css +++ b/css/mobile.css @@ -2,19 +2,14 @@ Contains tweaks for small screens */ -.only-on-mobile { - display: none !important; - background-color: var(--background-color); - color: var(--foreground-color); -} - -@media only screen and (max-width: 600px), only screen and (max-height: 600px) { +@media only screen and (min-width: 769px) { .only-on-mobile { - display: unset !important; - background-color: var(--background-color); - color: var(--foreground-color); + display: none !important; } +} +@media only screen and (max-width: 768px), only screen and (max-height: 768px) { + .hidden-on-mobile { display: none !important; diff --git a/css/slideshow.css b/css/slideshow.css deleted file mode 100644 index c5ab215d6..000000000 --- a/css/slideshow.css +++ /dev/null @@ -1,18 +0,0 @@ - - - -.slick-carousel-content { - width: 300px; - max-height: var(--image-carousel-height); - display: block; - margin-left: 10px; -} - -.slick-carousel-content img { - /** -Workaround to patch images within a slick carousel - */ - height: var(--image-carousel-height); - width: auto; -} - diff --git a/index.css b/index.css index afa2d28f9..c825383fd 100644 --- a/index.css +++ b/index.css @@ -226,10 +226,6 @@ li::marker { width: 100%; } -.question form input[type="radio"] { - margin-right: 0.5em; -} - .invalid { box-shadow: 0 0 10px #ff5353; height: min-content; diff --git a/index.html b/index.html index c2b1e8f7e..eb45c805a 100644 --- a/index.html +++ b/index.html @@ -70,7 +70,7 @@
+ class="clutter absolute h-24 left-24 right-24 top-56 text-xl text-center" style="z-index: 4000"> Loading MapComplete, hang on...
diff --git a/langs/en.json b/langs/en.json index b62ea003f..ebc3ec7d5 100644 --- a/langs/en.json +++ b/langs/en.json @@ -29,6 +29,7 @@ }, "general": { "loginWithOpenStreetMap": "Login with OpenStreetMap", + "loginOnlyNeededToEdit": "to make changes to the map", "welcomeBack": "You are logged in, welcome back!", "loginToStart": "Login to answer this question", "testing":"Testing - changes won't be saved", @@ -39,6 +40,7 @@ "error": "Something went wrong…" }, "returnToTheMap": "Return to the map", + "openTheMap": "Open the map", "save": "Save", "cancel": "Cancel", "skip": "Skip this question", diff --git a/scripts/generateDocs.ts b/scripts/generateDocs.ts index f9a9c5a32..1cfe1418a 100644 --- a/scripts/generateDocs.ts +++ b/scripts/generateDocs.ts @@ -1,5 +1,4 @@ import {Utils} from "../Utils"; - Utils.runningFromConsole = true; import SpecialVisualizations from "../UI/SpecialVisualizations"; import SimpleMetaTagger from "../Logic/SimpleMetaTagger"; @@ -12,19 +11,19 @@ import {writeFileSync} from "fs"; import LayoutConfig from "../Customizations/JSON/LayoutConfig"; import State from "../State"; import {QueryParameters} from "../Logic/Web/QueryParameters"; -import Link from "../UI/Base/Link"; -function WriteFile(filename, html: string | BaseUIElement, autogenSource: string): void { + +function WriteFile(filename, html: string | BaseUIElement, autogenSource: string[]): void { writeFileSync(filename, new Combine([Translations.W(html), - new Link("Generated from "+autogenSource, "../../../"+autogenSource) + "Generated from "+autogenSource.join(", ") ]).AsMarkdown()); } -WriteFile("./Docs/SpecialRenderings.md", SpecialVisualizations.HelpMessage, "UI/SpecialVisualisations.ts") +WriteFile("./Docs/SpecialRenderings.md", SpecialVisualizations.HelpMessage, ["UI/SpecialVisualisations.ts"]) WriteFile("./Docs/CalculatedTags.md", new Combine([SimpleMetaTagger.HelpText(), ExtraFunction.HelpText()]).SetClass("flex-col"), - "SimpleMetaTagger and ExtraFunction") -WriteFile("./Docs/SpecialInputElements.md", ValidatedTextField.HelpText(),"ValidatedTextField.ts"); + ["SimpleMetaTagger","ExtraFunction"]) +WriteFile("./Docs/SpecialInputElements.md", ValidatedTextField.HelpText(),["ValidatedTextField.ts"]); new State(new LayoutConfig({ @@ -51,7 +50,7 @@ new State(new LayoutConfig({ })) QueryParameters.GetQueryParameter("layer-", "true", "Wether or not the layer with id is shown") -WriteFile("./Docs/URL_Parameters.md", QueryParameters.GenerateQueryParameterDocs(), "QueryParameters") +WriteFile("./Docs/URL_Parameters.md", QueryParameters.GenerateQueryParameterDocs(), ["QueryParameters"]) console.log("Generated docs") diff --git a/test.html b/test.html index 0318ff703..839114efe 100644 --- a/test.html +++ b/test.html @@ -3,7 +3,6 @@ Small tests - diff --git a/test.ts b/test.ts index 604b2ebb7..92dfa6273 100644 --- a/test.ts +++ b/test.ts @@ -1,10 +1,50 @@ import ValidatedTextField from "./UI/Input/ValidatedTextField"; import Combine from "./UI/Base/Combine"; import {VariableUiElement} from "./UI/Base/VariableUIElement"; +import {UIEventSource} from "./Logic/UIEventSource"; +import TagRenderingConfig from "./Customizations/JSON/TagRenderingConfig"; +import State from "./State"; +import TagRenderingQuestion from "./UI/Popup/TagRenderingQuestion"; -new Combine(ValidatedTextField.tpList.map(tp => { - const tf = ValidatedTextField.InputForType(tp.name); - - return new Combine([tf, new VariableUiElement(tf.GetValue()).SetClass("alert")]); -})).AttachTo("maindiv") \ No newline at end of file +function TestTagRendering(){ + State.state = new State(undefined) + const tagsSource = new UIEventSource({ + id:"node/1" + }) + new TagRenderingQuestion( + tagsSource, + new TagRenderingConfig({ + multiAnswer: false, + freeform: { + key:"valve" + }, + question: "What valves are supported?", + render: "This pump supports {valve}", + mappings: [ + { + if: "valve=dunlop", + then: "This pump supports dunlop" + }, + { + if:"valve=shrader", + then:"shrader is supported", + } + ], + + }, undefined, "test") + ).AttachTo("maindiv") + new VariableUiElement(tagsSource.map(tags => tags["valves"])).SetClass("alert").AttachTo("extradiv") +} + +function TestAllInputMethods(){ + + new Combine(ValidatedTextField.tpList.map(tp => { + const tf = ValidatedTextField.InputForType(tp.name); + + return new Combine([tf, new VariableUiElement(tf.GetValue()).SetClass("alert")]); + })).AttachTo("maindiv") +} + + +TestTagRendering() \ No newline at end of file