diff --git a/Customizations/Layout.ts b/Customizations/Layout.ts index 5d11f29e9..2062787e0 100644 --- a/Customizations/Layout.ts +++ b/Customizations/Layout.ts @@ -16,7 +16,7 @@ export class Layout { public name: string; public icon: string = "./assets/logo.svg"; public title: UIElement; - public description: string | UIElement = Translations.t.general.about; + public description: string | UIElement; public socialImage: string = "" public layers: LayerDefinition[]; @@ -103,22 +103,13 @@ export class WelcomeMessage extends UIElement { } InnerRender(): string { - return "" + + return "" + this.description.Render() + (this.userDetails.data.loggedIn ? this.welcomeBack : this.plzLogIn).Render() + this.tail.Render() + "
" + this.languagePicker.Render() + - "
" - - ; - /* - return new VariableUiElement( - this.userDetails.map((userdetails) => { - }), - function () { - - }).ListenTo(Locale.language);*/ + "
"; } protected InnerUpdate(htmlElement: HTMLElement) { diff --git a/InitUiElements.ts b/InitUiElements.ts new file mode 100644 index 000000000..e878540e0 --- /dev/null +++ b/InitUiElements.ts @@ -0,0 +1,75 @@ +import {Layout, WelcomeMessage} from "./Customizations/Layout"; +import Locale from "./UI/i18n/Locale"; +import Translations from "./UI/i18n/Translations"; +import {TabbedComponent} from "./UI/Base/TabbedComponent"; +import {ShareScreen} from "./UI/ShareScreen"; +import {FixedUiElement} from "./UI/Base/FixedUiElement"; +import {CheckBox} from "./UI/Input/CheckBox"; +import Combine from "./UI/Base/Combine"; +import {OsmConnection} from "./Logic/OsmConnection"; +import {Basemap} from "./Logic/Basemap"; +import {UIEventSource} from "./UI/UIEventSource"; +import {UIElement} from "./UI/UIElement"; +import {MoreScreen} from "./UI/MoreScreen"; + +export class InitUiElements { + + + static OnlyIf(featureSwitch: UIEventSource, callback: () => void) { + featureSwitch.addCallback(() => { + + if (featureSwitch.data === "false") { + return; + } + callback(); + }); + + if (featureSwitch.data !== "false") { + callback(); + } + + } + + + private static CreateWelcomePane(layoutToUse: Layout, osmConnection: OsmConnection, bm: Basemap) { + + const welcome = new WelcomeMessage(layoutToUse, Locale.CreateLanguagePicker(layoutToUse, Translations.t.general.pickLanguage), osmConnection) + + const fullOptions = new TabbedComponent([ + {header: ``, content: welcome}, + {header: ``, content: Translations.t.general.openStreetMapIntro}, + {header: ``, content: new ShareScreen(layoutToUse, bm.Location)}, + {header: ``, content: new MoreScreen(bm.Location)} + ]) + + return fullOptions; + + } + + + static InitWelcomeMessage(layoutToUse: Layout, osmConnection: OsmConnection, bm: Basemap, + fullScreenMessage: UIEventSource) { + + const fullOptions = this.CreateWelcomePane(layoutToUse, osmConnection, bm); + + const help = new FixedUiElement(`
help
`); + const close = new FixedUiElement(`
close
`); + new CheckBox( + new Combine([ + "", close, "", + "", fullOptions.onClick(() => { + }), ""]), + new Combine(["", help, ""]) + , true + ).AttachTo("messagesbox"); + + + const fullOptions2 = this.CreateWelcomePane(layoutToUse, osmConnection, bm); + fullScreenMessage.setData(fullOptions2) + new FixedUiElement(`
help
`).onClick(() => { + fullScreenMessage.setData(fullOptions2) + }).AttachTo("help-button-mobile"); + + } + +} \ No newline at end of file diff --git a/Logic/QueryParameters.ts b/Logic/QueryParameters.ts index 4ba8e0431..cc244dab1 100644 --- a/Logic/QueryParameters.ts +++ b/Logic/QueryParameters.ts @@ -7,10 +7,10 @@ export class QueryParameters { private static order: string [] = ["layout","test","z","lat","lon"]; private static knownSources = QueryParameters.init(); + private static defaults = {} private static addOrder(key){ if(this.order.indexOf(key) < 0){ - console.log("Adding order", key) this.order.push(key) } } @@ -41,18 +41,22 @@ export class QueryParameters { if (QueryParameters.knownSources[key] === undefined || QueryParameters.knownSources[key].data === undefined) { continue; } + if (QueryParameters.knownSources[key].data == QueryParameters.defaults[key]) { + continue; + } parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(QueryParameters.knownSources[key].data)) } history.replaceState(null, "", "?" + parts.join("&")); } - public static GetQueryParameter(key: string): UIEventSource { + public static GetQueryParameter(key: string, deflt: string): UIEventSource { + QueryParameters.defaults[key] = deflt; if (QueryParameters.knownSources[key] !== undefined) { return QueryParameters.knownSources[key]; } QueryParameters.addOrder(key); - const source = new UIEventSource(undefined); + const source = new UIEventSource(deflt); QueryParameters.knownSources[key] = source; source.addCallback(() => QueryParameters.Serialize()) return source; diff --git a/UI/Base/TabbedComponent.ts b/UI/Base/TabbedComponent.ts new file mode 100644 index 000000000..5e74e814f --- /dev/null +++ b/UI/Base/TabbedComponent.ts @@ -0,0 +1,42 @@ +import {UIElement} from "../UIElement"; +import {UIEventSource} from "../UIEventSource"; +import Translations from "../i18n/Translations"; + + +export class TabbedComponent extends UIElement { + + private headers: UIElement[] = []; + private content: UIElement[] = []; + + constructor(elements: { header: UIElement | string, content: UIElement | string }[]) { + super(new UIEventSource(0)); + const self = this; + for (let i = 0; i < elements.length; i++) { + let element = elements[i]; + this.headers.push(Translations.W(element.header).onClick(() => self._source.setData(i))); + this.content.push(Translations.W(element.content)); + } + + + } + + InnerRender(): string { + let html = ""; + + let headerBar = ""; + for (let i = 0; i < this.headers.length; i++) { + let header = this.headers[i]; + + headerBar += `
` + + header.Render() + "
" + } + + + headerBar = "
" + headerBar + "
" + + const content = this.content[this._source.data].Render(); + + return headerBar + "
" + content + "
"; + } + +} \ No newline at end of file diff --git a/UI/Base/VerticalCombine.ts b/UI/Base/VerticalCombine.ts index 82d2f3d53..03e7abfe5 100644 --- a/UI/Base/VerticalCombine.ts +++ b/UI/Base/VerticalCombine.ts @@ -10,7 +10,7 @@ export class VerticalCombine extends UIElement { this._className = className; } - protected InnerRender(): string { + InnerRender(): string { let html = ""; for (const element of this._elements) { if (!element.IsEmpty()) { diff --git a/UI/Img.ts b/UI/Img.ts index 72ca2ba40..7a89820ce 100644 --- a/UI/Img.ts +++ b/UI/Img.ts @@ -1,4 +1,6 @@ export class Img { + static readonly checkmark = ``; + static readonly no_checkmark = ``; static osmAbstractLogo: string = " ` + static openFilterButton: string = ` ` - + } diff --git a/UI/Input/CheckBox.ts b/UI/Input/CheckBox.ts index 7f03c9735..f2da2e7bb 100644 --- a/UI/Input/CheckBox.ts +++ b/UI/Input/CheckBox.ts @@ -6,26 +6,26 @@ import instantiate = WebAssembly.instantiate; export class CheckBox extends UIElement{ - private readonly _data: UIEventSource; + public readonly isEnabled: UIEventSource; private readonly _showEnabled: string|UIElement; private readonly _showDisabled: string|UIElement; constructor(showEnabled: string | UIElement, showDisabled: string | UIElement, data: UIEventSource | boolean = false) { super(undefined); - this._data = + this.isEnabled = data instanceof UIEventSource ? data : new UIEventSource(data ?? false); - this.ListenTo(this._data); + this.ListenTo(this.isEnabled); this._showEnabled = showEnabled; this._showDisabled = showDisabled; const self = this; this.onClick(() => { - self._data.setData(!self._data.data); + self.isEnabled.setData(!self.isEnabled.data); }) } InnerRender(): string { - if (this._data.data) { + if (this.isEnabled.data) { return Translations.W(this._showEnabled).Render(); } else { return Translations.W(this._showDisabled).Render(); diff --git a/UI/LayerSelection.ts b/UI/LayerSelection.ts index 27be49a41..28432b91c 100644 --- a/UI/LayerSelection.ts +++ b/UI/LayerSelection.ts @@ -3,6 +3,7 @@ import { FilteredLayer } from "../Logic/FilteredLayer"; import { CheckBox } from "./Input/CheckBox"; import Combine from "./Base/Combine"; import {Utils} from "../Utils"; +import {Img} from "./Img"; export class LayerSelection extends UIElement{ @@ -25,9 +26,7 @@ export class LayerSelection extends UIElement{ this._checkboxes.push(new CheckBox( new Combine([checkbox, icon, name]), new Combine([ - ` - - `, + Img.checkmark, icon, layer.layerDef.name]), layer.isDisplayed)); diff --git a/UI/MoreScreen.ts b/UI/MoreScreen.ts new file mode 100644 index 000000000..fab85f868 --- /dev/null +++ b/UI/MoreScreen.ts @@ -0,0 +1,58 @@ +import {UIElement} from "./UIElement"; +import {VerticalCombine} from "./Base/VerticalCombine"; +import Translations from "./i18n/Translations"; +import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; +import {FixedUiElement} from "./Base/FixedUiElement"; +import {Utils} from "../Utils"; +import {link} from "fs"; +import {UIEventSource} from "./UIEventSource"; +import {VariableUiElement} from "./Base/VariableUIElement"; + + +export class MoreScreen extends UIElement { + private currentLocation: UIEventSource<{ zoom: number, lat: number, lon: number }>; + + constructor(currentLocation: UIEventSource<{ zoom: number, lat: number, lon: number }>) { + super(currentLocation); + this.currentLocation = currentLocation; + } + + InnerRender(): string { + const tr = Translations.t.general.morescreen; + + const els: UIElement[] = [] + for (const k in AllKnownLayouts.allSets) { + if (k === "all") { + continue; + } + const layout = AllKnownLayouts.allSets[k] + + const linkText = + `https://pietervdvn.github.io/MapComplete/${layout.name}.html?z=${this.currentLocation.data.zoom}&lat=${this.currentLocation.data.lat}&lon=${this.currentLocation.data.lon} + ` + const link = new FixedUiElement( + ` + + + +
${Utils.Upper(layout.name)}
+ ${Translations.W(layout.description).Render()}
+
+
+` + ); + + els.push(link) + + + } + + + return new VerticalCombine([ + tr.intro, + new VerticalCombine(els), + tr.streetcomplete + ]).Render(); + } + +} \ No newline at end of file diff --git a/UI/ShareScreen.ts b/UI/ShareScreen.ts new file mode 100644 index 000000000..591a3f3fe --- /dev/null +++ b/UI/ShareScreen.ts @@ -0,0 +1,160 @@ +import {UIElement} from "./UIElement"; +import {Layout} from "../Customizations/Layout"; +import Translations from "./i18n/Translations"; +import {FixedUiElement} from "./Base/FixedUiElement"; +import Combine from "./Base/Combine"; +import {VariableUiElement} from "./Base/VariableUIElement"; +import {UIEventSource} from "./UIEventSource"; +import {CheckBox} from "./Input/CheckBox"; +import {VerticalCombine} from "./Base/VerticalCombine"; +import {QueryParameters} from "../Logic/QueryParameters"; +import {Img} from "./Img"; + +export class ShareScreen extends UIElement { + + private _shareButton: UIElement; + + private _options: UIElement; + private _iframeCode: UIElement; + private _link: UIElement; + private _linkStatus: UIElement; + + constructor(layout: Layout, currentLocation: UIEventSource<{ zoom: number, lat: number, lon: number }>) { + super(undefined) + const tr = Translations.t.general.sharescreen; + + const optionCheckboxes: UIElement[] = [] + const optionParts: (UIEventSource)[] = []; + + const includeLocation = new CheckBox( + new Combine([Img.checkmark, "Include current location"]), + new Combine([Img.no_checkmark, "Include current location"]), + true + ) + optionCheckboxes.push(includeLocation); + optionParts.push(includeLocation.isEnabled.map((includeL) => { + if (includeL) { + return `z=${currentLocation.data.zoom}&lat=${currentLocation.data.lat}&lon=${currentLocation.data.lon}` + } else { + return null; + } + }, [currentLocation])); + + + const switches = [{urlName: "fs-userbadge", human: "Enable the login-button"}, + {urlName: "fs-search", human: "Enable search bar"}, + {urlName: "fs-welcome-message", human: "Enable the welcome message"}, + {urlName: "fs-layers", human: "Enable layer control"}, + {urlName: "fs-add-new", human: "Enable the 'add new POI' button"} + ] + + + for (const swtch of switches) { + + const checkbox = new CheckBox( + new Combine([Img.checkmark, swtch.human]), + new Combine([Img.no_checkmark, swtch.human]), + true + ); + optionCheckboxes.push(checkbox); + optionParts.push(checkbox.isEnabled.map((isEn) => { + if (isEn) { + return null; + } else { + return `${swtch.urlName}=false` + } + })) + + + } + + + this._options = new VerticalCombine(optionCheckboxes) + const url = currentLocation.map(() => { + + let literalText = "https://pietervdvn.github.io/MapComplete/" + layout.name + ".html" + + const parts = []; + for (const part of optionParts) { + if (part.data === null) { + continue; + } + parts.push(part.data); + } + + if (parts.length === 0) { + return literalText; + } + + return literalText + "?" + parts.join("&"); + }, optionParts); + this._iframeCode = new VariableUiElement( + url.map((url) => { + return ` + <iframe src="${url}" title="${layout.name} with MapComplete"></iframe> + ` + }) + ); + + + this._link = new VariableUiElement( + url.map((url) => { + return `` + }) + ); + + + const status = new UIEventSource(" "); + this._linkStatus = new VariableUiElement(status); + const self = this; + this._link.onClick(async () => { + + const shareData = { + title: Translations.W(layout.name).InnerRender(), + text: Translations.W(layout.description).InnerRender(), + url: self._link.data, + } + + function rejected() { + status.setData("Copying to clipboard...") + var copyText = document.getElementById("code-link--copyable"); + + // @ts-ignore + copyText.select(); + // @ts-ignore + copyText.setSelectionRange(0, 99999); /*For mobile devices*/ + + document.execCommand("copy"); + status.setData("Copied to clipboard") + } + + try { + navigator.share(shareData) + .then(() => { + status.setData("Thanks for sharing!") + }, rejected) + .catch(rejected) + } catch (err) { + rejected(); + } + + }); + + } + + InnerRender(): string { + + const tr = Translations.t.general.sharescreen; + + return new VerticalCombine([ + tr.intro, + this._link, + this._linkStatus, + tr.addToHomeScreen, + tr.embedIntro, + this._options, + this._iframeCode + ]).Render() + } + +} \ No newline at end of file diff --git a/UI/UIElement.ts b/UI/UIElement.ts index 48b42687e..6d6d7cfd0 100644 --- a/UI/UIElement.ts +++ b/UI/UIElement.ts @@ -136,6 +136,8 @@ export abstract class UIElement extends UIEventSource{ public IsEmpty(): boolean { return this.InnerRender() === ""; } + } + diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts index 0389b7631..2d34a7f15 100644 --- a/UI/i18n/Translations.ts +++ b/UI/i18n/Translations.ts @@ -160,7 +160,11 @@ export default class Translations { // title: new T({en: 'Bike station', nl: 'Fietsstation', fr: 'Station vélo'}), Old, non-dynamic title titlePump: new T({en: 'Bike pump', nl: 'Fietspomp', fr: 'TODO: fr'}), titleRepair: new T({en: 'Bike repair station', nl: 'Herstelpunt', fr: 'TODO: fr'}), - titlePumpAndRepair: new T({en: 'Bike station (pump & repair)', nl: 'Herstelpunt met pomp', fr: 'TODO: fr'}), + titlePumpAndRepair: new T({ + en: 'Bike station (pump & repair)', + nl: 'Herstelpunt met pomp', + fr: 'TODO: fr' + }), manometer: { question: new T({ en: 'Does the pump have a pressure indicator or manometer?', @@ -168,7 +172,11 @@ export default class Translations { fr: 'Est-ce que la pompe à un manomètre integré?' }), yes: new T({en: 'There is a manometer', nl: 'Er is een luchtdrukmeter', fr: 'Il y a un manomètre'}), - no: new T({en: 'There is no manometer', nl: 'Er is geen luchtdrukmeter', fr: 'Il n\'y a pas de manomètre'}), + no: new T({ + en: 'There is no manometer', + nl: 'Er is geen luchtdrukmeter', + fr: 'Il n\'y a pas de manomètre' + }), broken: new T({ en: 'There is manometer but it is broken', nl: 'Er is een luchtdrukmeter maar die is momenteel defect', @@ -300,11 +308,22 @@ export default class Translations { title: new T({en: "Bike repair/shop", nl: "Fietszaak", fr: "Magasin et réparateur de vélo"}), titleRepair: new T({en: "Bike repair", nl: "Fietsenmaker", fr: "Réparateur de vélo"}), titleShop: new T({en: "Bike shop", nl: "Fietswinkel", fr: "Magasin de vélo"}), - - titleNamed: new T({en: "Bike repair/shop {name}", nl: "Fietszaak {name}", fr: "Magasin et réparateur de vélo {name}"}), - titleRepairNamed: new T({en: "Bike repair {name}", nl: "Fietsenmaker {name}", fr: "Réparateur de vélo {name}"}), - titleShopNamed: new T({en: "Bike shop {name}", nl: "Fietswinkel {name}", fr: "Magasin de vélo {name}"}), + titleNamed: new T({ + en: "Bike repair/shop {name}", + nl: "Fietszaak {name}", + fr: "Magasin et réparateur de vélo {name}" + }), + titleRepairNamed: new T({ + en: "Bike repair {name}", + nl: "Fietsenmaker {name}", + fr: "Réparateur de vélo {name}" + }), + titleShopNamed: new T({ + en: "Bike shop {name}", + nl: "Fietswinkel {name}", + fr: "Magasin de vélo {name}" + }), retail: { @@ -313,7 +332,11 @@ export default class Translations { nl: "Verkoopt deze winkel fietsen?", fr: "Est-ce que ce magasin vend des vélos?" }), - yes: new T({en: "This shop sells bikes", nl: "Deze winkel verkoopt fietsen", fr: "Ce magasin vend des vélos"}), + yes: new T({ + en: "This shop sells bikes", + nl: "Deze winkel verkoopt fietsen", + fr: "Ce magasin vend des vélos" + }), no: new T({ en: "This shop doesn't sell bikes", nl: "Deze winkel verkoopt geen fietsen", @@ -451,15 +474,43 @@ export default class Translations { } }, nonBikeShop: { - name: new T({en: "shop that sells/repairs bikes", nl: "winkel die fietsen verkoopt/herstelt", fr: "TODO: fr"}), + name: new T({ + en: "shop that sells/repairs bikes", + nl: "winkel die fietsen verkoopt/herstelt", + fr: "TODO: fr" + }), - title: new T({en: "Shop that sells/repairs bikes", nl: "Winkel die fietsen verkoopt/herstelt", fr: "TODO: fr"}), - titleRepair: new T({en: "Shop that repairs bikes", nl: "Winkel die fietsen herstelt", fr: "TODO: fr"}), - titleShop: new T({en: "Shop that sells bikes", nl: "Winkel die fietsen verkoopt", fr: "TODO: fr"}), + title: new T({ + en: "Shop that sells/repairs bikes", + nl: "Winkel die fietsen verkoopt/herstelt", + fr: "TODO: fr" + }), + titleRepair: new T({ + en: "Shop that repairs bikes", + nl: "Winkel die fietsen herstelt", + fr: "TODO: fr" + }), + titleShop: new T({ + en: "Shop that sells bikes", + nl: "Winkel die fietsen verkoopt", + fr: "TODO: fr" + }), - titleNamed: new T({en: "{name} (sells/repairs bikes)", nl: "{name} (verkoopt/herstelt fietsen)", fr: "TODO: fr"}), - titleRepairNamed: new T({en: "{name} (repairs bikes)", nl: "{name} (herstelt fietsen)", fr: "TODO: fr"}), - titleShopNamed: new T({en: "{name} (sells bikes)", nl: "{name} (verkoopt fietsen)", fr: "TODO: fr"}), + titleNamed: new T({ + en: "{name} (sells/repairs bikes)", + nl: "{name} (verkoopt/herstelt fietsen)", + fr: "TODO: fr" + }), + titleRepairNamed: new T({ + en: "{name} (repairs bikes)", + nl: "{name} (herstelt fietsen)", + fr: "TODO: fr" + }), + titleShopNamed: new T({ + en: "{name} (sells bikes)", + nl: "{name} (verkoopt fietsen)", + fr: "TODO: fr" + }), }, drinking_water: { title: new T({ @@ -686,6 +737,43 @@ export default class Translations { en: "Website: {website}", nl: "Website: {website}" }) + + }, + openStreetMapIntro: new T({ + en: "

An Open Map

" + + "

Wouldn't it be cool if there was a single map, which everyone could freely use and edit?" + + "A single place to store all geo-information? Then, all those websites with different, small and incompatible maps (which are always outdated) wouldn't be needed anymore.

" + + "

OpenStreetMap is this map. The map data can be used for free (with attribution and publication of changes to that data)." + + " On top of that, everyone can freely add new data and fix errors. This website uses OpenStreetMap as well. All the data is from there, and your answers and corrections are added there as well.

" + + "

A ton of people and application already use OpenStreetMap: Maps.me, OsmAnd, but also the maps at Facebook, Intsagram, Apple-maps and Bing-maps are (partly) powered by OpenStreetMap." + + "If you change something here, it'll be reflected in those applications too - after their next update!

", + nl: "

Een open kaart

" + + "

Zou het niet fantastisch zijn als er een open kaart zou zijn, die door iedereen aangepast én gebruikt kon worden? Waar iedereen zijn interesses aan zou kunnen toevoegen?" + + "Dan zouden er geen duizend-en-één verschillende kleine kaartjes, websites, ... meer nodig zijn

" + + "

OpenStreetMap is deze open kaart. Je mag de kaartdata gratis gebruiken (mits bronvermelding en herpublicatie van aanpassingen). Daarenboven mag je de kaart ook gratis aanpassen als je een account maakt." + + "Ook deze website is gebaseerd op OpenStreetMap. Als je hier een vraag beantwoord, gaat het antwoord daar ook naartoe

" + + "

Tenslotte zijn er reeds vele gebruikers van OpenStreetMap. Denk maar Maps.me, OsmAnd, verschillende gespecialiseerde routeplanners, de achtergrondkaarten op Facebook, Instagram,...
Zelfs Apple Maps en Bing-Maps gebruiken OpenStreetMap in hun kaarten!

" + + "

Kortom, als je hier een antwoord geeft of een fout aanpast, zal dat na een tijdje ook in al dié applicaties te zien zijn.

" + }), + + sharescreen: { + intro: new T({ + en: "

Share this map

Share this map by copying the link below and sending it to friends and family:" + }), + addToHomeScreen: new T({ + en: "

Add to your home screen

You can easily add this website to your home screen for a native feel. Click the 'add to home screen button' in the URL bar to do this." + }), + embedIntro: new T({ + en: "

Embed on your website

Please, embed this map into your website.
We encourage you to do it - you don't even have to ask permission.
It is free, and always will be. The more people using this, the more valuable it becomes." + }) + }, + morescreen: { + intro:new T({ + en:"

More quests

Do you enjoy collecting geodata?
There are more layers available.", + }), + streetcomplete: new T({ + en: "Another, similar application is StreetComplete" + }) } } } diff --git a/assets/share.svg b/assets/share.svg new file mode 100644 index 000000000..d70d17511 --- /dev/null +++ b/assets/share.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/index.css b/index.css index d61fef0f9..dfdc93d8e 100644 --- a/index.css +++ b/index.css @@ -21,8 +21,6 @@ form { } - - #leafletDiv { height: 100%; } @@ -343,18 +341,14 @@ form { #welcomeMessage { display: inline-block; - background-color: white; - padding: 1em; margin-left: 3.5em; - padding-left: 1em; - padding-bottom: 2em; border-radius: 2em; border-top-left-radius: 0; border-bottom-left-radius: 0; - max-width: calc(max(35vw, 30em)); + max-width: 40em; + width: 45vw; max-height: calc(100vh - 15em); overflow-y: auto; - box-shadow: 0 0 10px #00000066; } #messagesbox { @@ -647,32 +641,15 @@ form { height: auto; } -#bottomRight { +#top-right { - display: block ruby; position: absolute; - - margin: auto; - right: 1%; - bottom: 1.5em; - height: auto; - min-height: 1em; - width: auto; - - font-size: large; - - padding: 2em; - border-radius: 2em; + display: block; + right: 0.5em; + top: 0.5em; z-index: 5000; - opacity: 1; - background-color: white; - transition: all 500ms linear; - - text-align: center; - horiz-align: center; - font-weight: bold; } @@ -844,7 +821,6 @@ form { .license-picker { - background-color: orange; float: left; } @@ -1133,4 +1109,101 @@ form { padding-left: 0.3em; padding-right: 0.3em; border-radius: 1.5em; -} \ No newline at end of file +} + + +/******** TabbedElement ****/ + +.tabs-header-bar { + padding-left: 1em; + padding-top: 10px; /* For the shadow */ + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: flex-start; + align-items: start; + background-color: white; +} + + +.tab-single-header img { + height: 3em; + width: 3em; + padding: 0.5em; +} + +.tab-content { + padding: 1em; + z-index: 5002; + background-color: white; + position: relative; + +} + +.tab-single-header { + border-top-left-radius: 1em; + border-top-right-radius: 1em; + box-shadow: 0 0 10px black; + z-index: 5000; + border-bottom: 1px solid white; + padding-bottom: 1px; +} + +.tab-active { + background-color: white; + z-index: 5001; +} + +.tab-non-active { + background-color: #e5f5ff; + opacity: 0.5; + border-bottom: 1px solid lightgray; +} + + +/****** ShareScreen *****/ + +.literal-code { + display: inline-block; + background-color: lightgray; + padding: 0.5em; +} + +.iframe-code-block { + +} + +.iframe-escape { + background-color: white; + border-radius: 2em; + display: block; +} + +.iframe-escape img{ + padding: 1em; + width: 2em; + height: 2em; +} + +/** Switch layout **/ + +.switch-layout a{ + display: flex; + flex-wrap: nowrap; + flex-direction: row; + font-size: large; + margin: 0.5em; + background-color: #e5f5ff; + border-radius: 1em; + align-items: center; + text-decoration: none; + color: black; + +} + +.switch-layout a img{ + width: 3em; + max-height: 3em; + margin-right: 0.5em; + padding: 0.5em; +} diff --git a/index.html b/index.html index 168e78778..6ef768359 100644 --- a/index.html +++ b/index.html @@ -28,8 +28,6 @@
@@ -42,8 +40,8 @@
-
Loading...
- +
Loading MapComplete, hang on...
+
diff --git a/index.ts b/index.ts index 6ca5c8247..fdd57fce8 100644 --- a/index.ts +++ b/index.ts @@ -32,6 +32,9 @@ import {QueryParameters} from "./Logic/QueryParameters"; import {Utils} from "./Utils"; import {LocalStorageSource} from "./Logic/LocalStorageSource"; import {Button} from "./UI/Base/Button"; +import {TabbedComponent} from "./UI/Base/TabbedComponent"; +import {ShareScreen} from "./UI/ShareScreen"; +import {InitUiElements} from "./InitUiElements"; // --------------------- Special actions based on the parameters ----------------- @@ -44,7 +47,7 @@ if (location.href.startsWith("http://buurtnatuur.be")) { if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { // Set to true if testing and changes should NOT be saved - const testing = QueryParameters.GetQueryParameter("test"); + const testing = QueryParameters.GetQueryParameter("test", "true"); testing.setData(testing.data ?? "true") // If you have a testfile somewhere, enable this to spoof overpass // This should be hosted independantly, e.g. with `cd assets; webfsd -p 8080` + a CORS plugin to disable cors rules @@ -54,7 +57,7 @@ if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { // ----------------- SELECT THE RIGHT QUESTSET ----------------- -let defaultLayout = "all" +let defaultLayout = "bookcases" const path = window.location.pathname.split("/").slice(-1)[0]; if (path !== "index.html") { @@ -76,7 +79,7 @@ for (const k in AllKnownLayouts.allSets) { } } -defaultLayout = QueryParameters.GetQueryParameter("layout").data ?? defaultLayout; +defaultLayout = QueryParameters.GetQueryParameter("layout", defaultLayout).data; const layoutToUse: Layout = AllKnownLayouts.allSets[defaultLayout] ?? AllKnownLayouts["all"]; console.log("Using layout: ", layoutToUse.name); @@ -102,13 +105,21 @@ const fullScreenMessage = new UIEventSource(undefined); // The latest element that was selected - used to generate the right UI at the right place const selectedElement = new UIEventSource<{ feature: any }>(undefined); -const zoom = QueryParameters.GetQueryParameter("z") +const zoom = QueryParameters.GetQueryParameter("z", "" + layoutToUse.startzoom) .syncWith(LocalStorageSource.Get("zoom")); -const lat = QueryParameters.GetQueryParameter("lat") +const lat = QueryParameters.GetQueryParameter("lat", "" + layoutToUse.startLat) .syncWith(LocalStorageSource.Get("lat")); -const lon = QueryParameters.GetQueryParameter("lon") +const lon = QueryParameters.GetQueryParameter("lon", "" + layoutToUse.startLon) .syncWith(LocalStorageSource.Get("lon")); +const featureSwitchUserbadge = QueryParameters.GetQueryParameter("fs-userbadge", "true"); +const featureSwitchSearch = QueryParameters.GetQueryParameter("fs-search", "true"); +const featureSwitchWelcomeMessage = QueryParameters.GetQueryParameter("fs-welcome-message", "true"); +const featureSwitchLayers = QueryParameters.GetQueryParameter("fs-layers", "true"); +const featureSwitchEmbedded = QueryParameters.GetQueryParameter("fs-embedded", "true"); +const featureSwitchAddNew = QueryParameters.GetQueryParameter("fs-add-new", "true"); +const featureSwitchIframe = QueryParameters.GetQueryParameter("fs-iframe", "false"); + const locationControl = new UIEventSource<{ lat: number, lon: number, zoom: number }>({ zoom: Utils.asFloat(zoom.data) ?? layoutToUse.startzoom, @@ -126,7 +137,7 @@ locationControl.addCallback((latlonz) => { // ----------------- Prepare the important objects ----------------- const osmConnection = new OsmConnection( - QueryParameters.GetQueryParameter("test").data === "true" + QueryParameters.GetQueryParameter("test", "false").data === "true" ); @@ -169,6 +180,7 @@ const bm = new Basemap("leafletDiv", locationControl, new VariableUiElement( )); + // ------------- Setup the layers ------------------------------- const addButtons: { name: UIElement, @@ -228,7 +240,9 @@ if (flayers.length > 1) { layerControl = new Combine([layerSelection, backgroundMapPicker]); } -new CheckBox(layerControl, closedFilterButton).AttachTo("filter__selection"); +InitUiElements.OnlyIf(featureSwitchLayers, () => { + new CheckBox(layerControl, closedFilterButton).AttachTo("filter__selection"); +}); // ------------------ Setup various other UI elements ------------ @@ -240,16 +254,19 @@ Locale.language.addCallback(e => { }) -new StrayClickHandler(bm, selectedElement, fullScreenMessage, () => { - return new SimpleAddUI(bm.Location, - bm.LastClickLocation, - changes, - selectedElement, - layerUpdater.runningQuery, - osmConnection.userDetails, - addButtons); - } -); +InitUiElements.OnlyIf(featureSwitchAddNew, () => { + new StrayClickHandler(bm, selectedElement, fullScreenMessage, () => { + return new SimpleAddUI(bm.Location, + bm.LastClickLocation, + changes, + selectedElement, + layerUpdater.runningQuery, + osmConnection.userDetails, + addButtons); + } + ); +}); + /** * Show the questions and information for the selected element @@ -283,40 +300,31 @@ selectedElement.addCallback((feature) => { const pendingChanges = new PendingChanges(changes, secondsTillChangesAreSaved,); -new UserBadge(osmConnection.userDetails, - pendingChanges, - Locale.CreateLanguagePicker(layoutToUse), - bm) - .AttachTo('userbadge'); +InitUiElements.OnlyIf(featureSwitchUserbadge, () => { -new SearchAndGo(bm).AttachTo("searchbox"); - -const welcome = new WelcomeMessage(layoutToUse, - Locale.CreateLanguagePicker(layoutToUse, Translations.t.general.pickLanguage), - osmConnection).onClick(() => { + new UserBadge(osmConnection.userDetails, + pendingChanges, + Locale.CreateLanguagePicker(layoutToUse), + bm) + .AttachTo('userbadge'); }); -const help = new FixedUiElement(`
help
`); -const close = new FixedUiElement(`
close
`); -new CheckBox( - new Combine([ - new Combine(["", close, ""]), - welcome]), - new Combine(["", help, ""]) - , true -).AttachTo("messagesbox"); - +InitUiElements.OnlyIf((featureSwitchSearch), () => { + new SearchAndGo(bm).AttachTo("searchbox"); +}); new FullScreenMessageBoxHandler(fullScreenMessage, () => { selectedElement.setData(undefined) }).update(); -const welcome2 = new WelcomeMessage(layoutToUse, Locale.CreateLanguagePicker(layoutToUse, Translations.t.general.pickLanguage), osmConnection) -fullScreenMessage.setData(welcome2) -new FixedUiElement(`
help
`).onClick(() => { - fullScreenMessage.setData(welcome2) -}) - .AttachTo("help-button-mobile") +InitUiElements.OnlyIf(featureSwitchWelcomeMessage, () => { + InitUiElements.InitWelcomeMessage(layoutToUse, osmConnection, bm, fullScreenMessage) +}); + +if (window != window.top || featureSwitchIframe.data !== "false") { + new FixedUiElement(``).AttachTo("top-right") +} + new CenterMessageBox( minZoom, diff --git a/test.html b/test.html index ad22b5de6..b5f19fbc3 100644 --- a/test.html +++ b/test.html @@ -5,9 +5,7 @@ -
'maindiv' not attached
-
'extradiv' not attached
diff --git a/test.ts b/test.ts index a2530e447..0d6802345 100644 --- a/test.ts +++ b/test.ts @@ -1,36 +1,4 @@ -import { And, Tag, Or } from "./Logic/TagsFilter"; -import { Overpass } from "./Logic/Overpass"; +import {MoreScreen} from "./UI/MoreScreen"; +import {UIEventSource} from "./UI/UIEventSource"; - -function anyValueExcept(key: string, exceptValue: string) { - return new And([ - new Tag(key, "*"), - new Tag(key, exceptValue, true) - ]) -} - -const sellsBikes = new Tag("service:bicycle:retail", "yes") -const repairsBikes = anyValueExcept("service:bicycle:repair", "no") -const rentsBikes = new Tag("service:bicycle:rental", "yes") -const hasPump = new Tag("service:bicycle:pump", "yes") -const hasDiy = new Tag("service:bicycle:diy", "yes") -const sellsSecondHand = anyValueExcept("service:bicycle:repair", "no") -const hasBikeServices = new Or([ - sellsBikes, - repairsBikes, - rentsBikes, - hasPump, - hasDiy, - sellsSecondHand -]) - -const overpassFilter = new And([ - new Tag("shop", "bicycle", true), - hasBikeServices -]) - -const overpass = new Overpass(overpassFilter) - -// console.log(overpass.buildQuery('bbox:51.12246976163816,3.1045767593383795,51.289518504257174,3.2848313522338866')) - -console.log(overpassFilter.asOverpass()) +new MoreScreen(new UIEventSource<{zoom: number, lat: number, lon: number}>({zoom: 16, lat: 51.3, lon: 3.2})).AttachTo("maindiv") \ No newline at end of file