Thinking about the user journey, make tags visible at a certain point
This commit is contained in:
parent
47d755e59f
commit
cd37d8db98
14 changed files with 175 additions and 49 deletions
|
@ -15,6 +15,7 @@ import Locale from "../UI/i18n/Locale";
|
||||||
import {State} from "../State";
|
import {State} from "../State";
|
||||||
import {TagRenderingOptions} from "./TagRenderingOptions";
|
import {TagRenderingOptions} from "./TagRenderingOptions";
|
||||||
import Translation from "../UI/i18n/Translation";
|
import Translation from "../UI/i18n/Translation";
|
||||||
|
import {SubtleButton} from "../UI/Base/SubtleButton";
|
||||||
|
|
||||||
|
|
||||||
export class TagRendering extends UIElement implements TagDependantUIElement {
|
export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
|
@ -39,6 +40,8 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
private readonly _questionElement: InputElement<TagsFilter>;
|
private readonly _questionElement: InputElement<TagsFilter>;
|
||||||
|
|
||||||
private readonly _saveButton: UIElement;
|
private readonly _saveButton: UIElement;
|
||||||
|
private readonly _friendlyLogin: UIElement;
|
||||||
|
|
||||||
private readonly _skipButton: UIElement;
|
private readonly _skipButton: UIElement;
|
||||||
private readonly _editButton: UIElement;
|
private readonly _editButton: UIElement;
|
||||||
|
|
||||||
|
@ -142,14 +145,17 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
if (tags === undefined) {
|
if (tags === undefined) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
if ((State.state?.osmConnection?.userDetails?.data?.csCount ?? 0) < 200) {
|
const csCount = State.state.osmConnection.userDetails.data.csCount;
|
||||||
|
if (csCount < State.userJourney.tagsVisibleAt) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return tags.asHumanString()
|
if (csCount < State.userJourney.tagsVisibleAndWikiLinked) {
|
||||||
|
return new FixedUiElement(tags.asHumanString(false)).SetClass("subtle").Render();
|
||||||
|
}
|
||||||
|
return tags.asHumanString(true);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
this._appliedTags.clss = "subtle";
|
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
self._questionSkipped.setData(true);
|
self._questionSkipped.setData(true);
|
||||||
|
@ -161,6 +167,9 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
this._saveButton = new SaveButton(this._questionElement.GetValue())
|
this._saveButton = new SaveButton(this._questionElement.GetValue())
|
||||||
.onClick(save);
|
.onClick(save);
|
||||||
|
|
||||||
|
this._friendlyLogin = Translations.t.general.loginToStart
|
||||||
|
.onClick(() => State.state.osmConnection.AttemptLogin())
|
||||||
|
|
||||||
this._editButton = new FixedUiElement("");
|
this._editButton = new FixedUiElement("");
|
||||||
if (this._question !== undefined) {
|
if (this._question !== undefined) {
|
||||||
this._editButton = new FixedUiElement("<img class='editbutton' src='./assets/pencil.svg' alt='edit'>")
|
this._editButton = new FixedUiElement("<img class='editbutton' src='./assets/pencil.svg' alt='edit'>")
|
||||||
|
@ -381,6 +390,17 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
|
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
|
|
||||||
|
if (this.IsQuestioning() && !State.state.osmConnection.userDetails.data.loggedIn) {
|
||||||
|
const question =
|
||||||
|
this.ApplyTemplate(this._question).Render();
|
||||||
|
return "<div class='question'>" +
|
||||||
|
"<span class='question-text'>" + question + "</span>" +
|
||||||
|
"<br/>" +
|
||||||
|
"<span class='login-button-friendly'>" + this._friendlyLogin.Render() + "</span>" +
|
||||||
|
"</div>"
|
||||||
|
}
|
||||||
|
|
||||||
if (this.IsQuestioning() || this._editMode.data) {
|
if (this.IsQuestioning() || this._editMode.data) {
|
||||||
// Not yet known or questioning, we have to ask a question
|
// Not yet known or questioning, we have to ask a question
|
||||||
|
|
||||||
|
@ -430,14 +450,14 @@ export class TagRendering extends UIElement implements TagDependantUIElement {
|
||||||
}
|
}
|
||||||
const self = this;
|
const self = this;
|
||||||
const tags = this._source.map(tags => self._tagsPreprocessor(self._source.data));
|
const tags = this._source.map(tags => self._tagsPreprocessor(self._source.data));
|
||||||
let transl : Translation;
|
return new VariableUiElement(tags.map(tags => {
|
||||||
if (typeof (template) === "string") {
|
const tr = Translations.WT(template);
|
||||||
transl = new Translation({en: TagUtils.ApplyTemplate(template, tags)});
|
if (tr.Subs === undefined) {
|
||||||
}else{
|
// This is a weird edge case
|
||||||
transl = template;
|
return tr.InnerRender();
|
||||||
}
|
}
|
||||||
|
return tr.Subs(tags).InnerRender()
|
||||||
return new VariableUiElement(tags.map(tags => transl.Subs(tags).InnerRender()));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,9 @@ export class InitUiElements {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (State.state.featureSwitchMoreQuests.data){
|
if (State.state.featureSwitchMoreQuests.data){
|
||||||
tabs.push({header: `<img src='${'./assets/add.svg'}'>`, content: new MoreScreen()});
|
|
||||||
|
tabs.push({header: `<img src='${'./assets/add.svg'}'>`
|
||||||
|
, content: new MoreScreen()});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ export abstract class TagsFilter {
|
||||||
return this.matches(TagUtils.proprtiesToKV(properties));
|
return this.matches(TagUtils.proprtiesToKV(properties));
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract asHumanString();
|
abstract asHumanString(linkToWiki: boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -150,7 +150,12 @@ export class Tag extends TagsFilter {
|
||||||
return new Tag(this.key, TagUtils.ApplyTemplate(this.value as string, tags));
|
return new Tag(this.key, TagUtils.ApplyTemplate(this.value as string, tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
asHumanString() {
|
asHumanString(linkToWiki: boolean) {
|
||||||
|
if (linkToWiki) {
|
||||||
|
return `<a href='https://wiki.openstreetmap.org/wiki/Key:${this.key}' target='_blank'>${this.key}</a>` +
|
||||||
|
`=` +
|
||||||
|
`<a href='https://wiki.openstreetmap.org/wiki/Tag:${this.key}%3D${this.value}' target='_blank'>${this.value}</a>`
|
||||||
|
}
|
||||||
return this.key + "=" + this.value;
|
return this.key + "=" + this.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,8 +206,8 @@ export class Or extends TagsFilter {
|
||||||
return new Or(newChoices);
|
return new Or(newChoices);
|
||||||
}
|
}
|
||||||
|
|
||||||
asHumanString() {
|
asHumanString(linkToWiki: boolean) {
|
||||||
return this.or.map(t => t.asHumanString()).join("|");
|
return this.or.map(t => t.asHumanString(linkToWiki)).join("|");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,8 +267,8 @@ export class And extends TagsFilter {
|
||||||
return new And(newChoices);
|
return new And(newChoices);
|
||||||
}
|
}
|
||||||
|
|
||||||
asHumanString() {
|
asHumanString(linkToWiki: boolean) {
|
||||||
return this.and.map(t => t.asHumanString()).join("&");
|
return this.and.map(t => t.asHumanString(linkToWiki)).join("&");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,8 +293,8 @@ export class Not extends TagsFilter{
|
||||||
return new Not(this.not.substituteValues(tags));
|
return new Not(this.not.substituteValues(tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
asHumanString() {
|
asHumanString(linkToWiki: boolean) {
|
||||||
return "!"+this.not.asHumanString();
|
return "!" + this.not.asHumanString(linkToWiki);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
33
README.md
33
README.md
|
@ -9,7 +9,8 @@ The design goals of MapComplete are to be:
|
||||||
|
|
||||||
- Easy to use, both on web and on mobile
|
- Easy to use, both on web and on mobile
|
||||||
- Easy to deploy (by not having a backand)
|
- Easy to deploy (by not having a backand)
|
||||||
- Easy to modify
|
- Easy to set up a custom theme
|
||||||
|
- Easy to fall down the rabbit hole of OSM
|
||||||
|
|
||||||
The basic functionality is to download some map features from Overpass and then ask certain questions. An answer is sent back to directly to OpenStreetMap.
|
The basic functionality is to download some map features from Overpass and then ask certain questions. An answer is sent back to directly to OpenStreetMap.
|
||||||
|
|
||||||
|
@ -26,6 +27,31 @@ An explicit non-goal of MapComplete is to modify geometries of ways. Although ad
|
||||||
|
|
||||||
Have a theme idea? Drop it in the [issues](https://github.com/pietervdvn/MapComplete/issues)
|
Have a theme idea? Drop it in the [issues](https://github.com/pietervdvn/MapComplete/issues)
|
||||||
|
|
||||||
|
## User journey
|
||||||
|
|
||||||
|
MapComplete is set up to lure people into OpenStreetMap and to teach them while they are on the go, step by step.
|
||||||
|
|
||||||
|
A typical user journey would be:
|
||||||
|
|
||||||
|
0) Oh, this is a cool map of _my specific interest_! There is a lot of data already...
|
||||||
|
0a) The user might discover the explanation about OSM in the dedicated tab page
|
||||||
|
0b) The user might discover the other themes in the other tab
|
||||||
|
0c) The user might share the map and/or embed it
|
||||||
|
|
||||||
|
1) The user clicks that big tempting button 'login' in order to answer questions. The user makes an account - a big step.
|
||||||
|
|
||||||
|
2) The user answers a question! Hooray!
|
||||||
|
When at least one question is answered (aka: having one changeset on OSM), adding a new point is unlocked
|
||||||
|
|
||||||
|
3) The user adds a new POI somewhere
|
||||||
|
3a) Note that _all messages_ must be read before being able to add a point. In other words, sending a message to a misbehaving MapComplete user acts as having a zero-minutes-block. This is added deliberately to avoid new users fucking up too much
|
||||||
|
|
||||||
|
4) At 50 changesets, the custom layout becomes available
|
||||||
|
5) At 200 changesets, the tags become visible when answering questions and when adding a new point from a preset. This is to give more control to power users and to teach new users the tagging scheme
|
||||||
|
5) At 250 changesets, the tags get linked to the wiki
|
||||||
|
6) At 500 changesets, I expect users to be power users and to be comfortable with tagging scheme and such. The custom theme generator is unlocked.
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
GPL + pingback.
|
GPL + pingback.
|
||||||
|
@ -88,6 +114,11 @@ TODO: erase cookies of third party websites and API's
|
||||||
|
|
||||||
Help to translate mapcomplete. Fork this project, open [the file containing all translations](https://github.com/pietervdvn/MapComplete/blob/master/UI/i18n/Translations.ts), add your language and send a pull request.
|
Help to translate mapcomplete. Fork this project, open [the file containing all translations](https://github.com/pietervdvn/MapComplete/blob/master/UI/i18n/Translations.ts), add your language and send a pull request.
|
||||||
|
|
||||||
|
# Creating your own theme
|
||||||
|
|
||||||
|
You can create [your own theme too](https://pietervdvn.github.io/MapComplete/customGenerator.html)
|
||||||
|
|
||||||
|
|
||||||
# Attributions:
|
# Attributions:
|
||||||
|
|
||||||
Data from OpenStreetMap
|
Data from OpenStreetMap
|
||||||
|
|
12
State.ts
12
State.ts
|
@ -24,7 +24,17 @@ export class State {
|
||||||
// The singleton of the global state
|
// The singleton of the global state
|
||||||
public static state: State;
|
public static state: State;
|
||||||
|
|
||||||
public static vNumber = "0.0.5d";
|
public static vNumber = "0.0.5e";
|
||||||
|
|
||||||
|
// The user journey states thresholds when a new feature gets unlocked
|
||||||
|
public static userJourney = {
|
||||||
|
customLayoutUnlock: 50,
|
||||||
|
themeGeneratorUnlock: 500,
|
||||||
|
tagsVisibleAt: 200,
|
||||||
|
tagsVisibleAndWikiLinked: 250
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
public static runningFromConsole: boolean = false;
|
public static runningFromConsole: boolean = false;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {Tag} from "../../Logic/TagsFilter";
|
||||||
import {DropDown} from "../Input/DropDown";
|
import {DropDown} from "../Input/DropDown";
|
||||||
import {TagRendering} from "../../Customizations/TagRendering";
|
import {TagRendering} from "../../Customizations/TagRendering";
|
||||||
import {LayerDefinition} from "../../Customizations/LayerDefinition";
|
import {LayerDefinition} from "../../Customizations/LayerDefinition";
|
||||||
|
import {State} from "../../State";
|
||||||
|
|
||||||
|
|
||||||
TagRendering.injectFunction();
|
TagRendering.injectFunction();
|
||||||
|
@ -620,8 +621,8 @@ export class ThemeGenerator extends UIElement {
|
||||||
if (!this.userDetails.data.loggedIn) {
|
if (!this.userDetails.data.loggedIn) {
|
||||||
return new Combine(["Not logged in. You need to be logged in to create a theme.", this.loginButton]).Render();
|
return new Combine(["Not logged in. You need to be logged in to create a theme.", this.loginButton]).Render();
|
||||||
}
|
}
|
||||||
if (this.userDetails.data.csCount < 500) {
|
if (this.userDetails.data.csCount < State.userJourney.themeGeneratorUnlock ) {
|
||||||
return "You need at least 500 changesets to create your own theme.";
|
return `You need at least ${State.userJourney.themeGeneratorUnlock} changesets to create your own theme.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,19 @@ export class FeatureInfoBox extends UIElement {
|
||||||
|
|
||||||
let questionsHtml = "";
|
let questionsHtml = "";
|
||||||
|
|
||||||
if (State.state.osmConnection.userDetails.data.loggedIn && questions.length > 0) {
|
if (!State.state.osmConnection.userDetails.data.loggedIn) {
|
||||||
|
let mostImportantQuestion;
|
||||||
|
let score = -1000;
|
||||||
|
for (const question of questions) {
|
||||||
|
|
||||||
|
if (mostImportantQuestion === undefined || question.Priority() > score) {
|
||||||
|
mostImportantQuestion = question;
|
||||||
|
score = question.Priority();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
questionsHtml = mostImportantQuestion.Render();
|
||||||
|
} else if (questions.length > 0) {
|
||||||
// We select the most important question and render that one
|
// We select the most important question and render that one
|
||||||
let mostImportantQuestion;
|
let mostImportantQuestion;
|
||||||
let score = -1000;
|
let score = -1000;
|
||||||
|
|
|
@ -2,11 +2,6 @@ import {UIElement} from "./UIElement";
|
||||||
import {VerticalCombine} from "./Base/VerticalCombine";
|
import {VerticalCombine} from "./Base/VerticalCombine";
|
||||||
import Translations from "./i18n/Translations";
|
import Translations from "./i18n/Translations";
|
||||||
import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
|
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";
|
|
||||||
import Combine from "./Base/Combine";
|
import Combine from "./Base/Combine";
|
||||||
import {SubtleButton} from "./Base/SubtleButton";
|
import {SubtleButton} from "./Base/SubtleButton";
|
||||||
import {State} from "../State";
|
import {State} from "../State";
|
||||||
|
@ -21,6 +16,7 @@ export class MoreScreen extends UIElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
|
|
||||||
const tr = Translations.t.general.morescreen;
|
const tr = Translations.t.general.morescreen;
|
||||||
|
|
||||||
const els: UIElement[] = []
|
const els: UIElement[] = []
|
||||||
|
@ -36,7 +32,8 @@ export class MoreScreen extends UIElement {
|
||||||
if (!State.state.osmConnection.userDetails.data.loggedIn) {
|
if (!State.state.osmConnection.userDetails.data.loggedIn) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (State.state.osmConnection.userDetails.data.csCount < 50) {
|
if (State.state.osmConnection.userDetails.data.csCount <
|
||||||
|
State.userJourney.customLayoutUnlock) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {State} from "../State";
|
||||||
|
|
||||||
import {UIEventSource} from "../Logic/UIEventSource";
|
import {UIEventSource} from "../Logic/UIEventSource";
|
||||||
import {UserDetails} from "../Logic/Osm/OsmConnection";
|
import {UserDetails} from "../Logic/Osm/OsmConnection";
|
||||||
|
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asks to add a feature at the last clicked location, at least if zoom is sufficient
|
* Asks to add a feature at the last clicked location, at least if zoom is sufficient
|
||||||
|
@ -62,14 +63,22 @@ export class SimpleAddUI extends UIElement {
|
||||||
console.warn("No icon defined for preset ", preset, "in layer ", layer.layerDef.id)
|
console.warn("No icon defined for preset ", preset, "in layer ", layer.layerDef.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const button =
|
const csCount = State.state.osmConnection.userDetails.data.csCount;
|
||||||
|
let tagInfo = "";
|
||||||
|
if (csCount > State.userJourney.tagsVisibleAt) {
|
||||||
|
tagInfo = preset.tags.map(t => t.asHumanString(false)).join("&");
|
||||||
|
tagInfo = `<br/><span class='subtle'>${tagInfo}</span>`
|
||||||
|
}
|
||||||
|
const button: UIElement =
|
||||||
new SubtleButton(
|
new SubtleButton(
|
||||||
icon,
|
icon,
|
||||||
new Combine([
|
new Combine([
|
||||||
"<b>",
|
"<b>",
|
||||||
preset.title,
|
preset.title,
|
||||||
"</b><br/>",
|
"</b>",
|
||||||
preset.description !== undefined ? preset.description : ""])
|
preset.description !== undefined ? new Combine(["<br/>", preset.description]) : "",
|
||||||
|
tagInfo
|
||||||
|
])
|
||||||
).onClick(
|
).onClick(
|
||||||
() => {
|
() => {
|
||||||
self.confirmButton = new SubtleButton(icon,
|
self.confirmButton = new SubtleButton(icon,
|
||||||
|
@ -90,6 +99,8 @@ export class SimpleAddUI extends UIElement {
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this._addButtons.push(button);
|
this._addButtons.push(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,15 +131,23 @@ export class SimpleAddUI extends UIElement {
|
||||||
if (this._confirmPreset.data !== undefined) {
|
if (this._confirmPreset.data !== undefined) {
|
||||||
|
|
||||||
if(userDetails.data.dryRun){
|
if(userDetails.data.dryRun){
|
||||||
this.CreatePoint(this._confirmPreset.data.tags, this._confirmPreset.data.layerToAddTo)();
|
// this.CreatePoint(this._confirmPreset.data.tags, this._confirmPreset.data.layerToAddTo)();
|
||||||
return "";
|
// return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
let tagInfo = "";
|
||||||
|
const csCount = State.state.osmConnection.userDetails.data.csCount;
|
||||||
|
if (csCount > State.userJourney.tagsVisibleAt) {
|
||||||
|
tagInfo = this._confirmPreset.data .tags.map(t => t.asHumanString(csCount > State.userJourney.tagsVisibleAndWikiLinked)).join("&");
|
||||||
|
tagInfo = `<br/>More information about the preset: ${tagInfo}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Combine([
|
return new Combine([
|
||||||
Translations.t.general.add.confirmIntro.Subs({title: this._confirmPreset.data.name}),
|
Translations.t.general.add.confirmIntro.Subs({title: this._confirmPreset.data.name}),
|
||||||
userDetails.data.dryRun ? "<span class='alert'>TESTING - changes won't be saved</span>":"",
|
userDetails.data.dryRun ? "<span class='alert'>TESTING - changes won't be saved</span>":"",
|
||||||
this.confirmButton,
|
this.confirmButton,
|
||||||
this.cancelButton
|
this.cancelButton,
|
||||||
|
tagInfo
|
||||||
|
|
||||||
]).Render();
|
]).Render();
|
||||||
|
|
||||||
|
|
|
@ -141,6 +141,11 @@ export abstract class UIElement extends UIEventSource<string>{
|
||||||
return this.InnerRender() === "";
|
return this.InnerRender() === "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SetClass(clss: string): UIElement {
|
||||||
|
this.clss = clss;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -673,6 +673,10 @@ export default class Translations {
|
||||||
nl: "Je bent aangemeld. Welkom terug!",
|
nl: "Je bent aangemeld. Welkom terug!",
|
||||||
fr: "Vous êtes connecté, bienvenue"
|
fr: "Vous êtes connecté, bienvenue"
|
||||||
}),
|
}),
|
||||||
|
loginToStart: new T({
|
||||||
|
en: "Login to answer this question",
|
||||||
|
nl: "Meld je aan om deze vraag te beantwoorden"
|
||||||
|
}),
|
||||||
search: {
|
search: {
|
||||||
search: new Translation({
|
search: new Translation({
|
||||||
en: "Search a location",
|
en: "Search a location",
|
||||||
|
|
|
@ -28,6 +28,11 @@
|
||||||
"title": "Toilet",
|
"title": "Toilet",
|
||||||
"tags": "amenity=toilets",
|
"tags": "amenity=toilets",
|
||||||
"description": "Only add public toilets"
|
"description": "Only add public toilets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Toilets with wheelchair accessible toilet",
|
||||||
|
"tags": "amenity=toilets&wheelchair=yes",
|
||||||
|
"description": "A restroom which has at least one wheelchair-accessible toilet"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tagRenderings": [
|
"tagRenderings": [
|
||||||
|
|
19
index.css
19
index.css
|
@ -1111,13 +1111,28 @@ form {
|
||||||
background-color: #3a3aeb;
|
background-color: #3a3aeb;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
padding-left: 0.3em;
|
padding-left: 0.6em;
|
||||||
padding-right: 0.3em;
|
padding-right: 0.6em;
|
||||||
font-size: x-large;
|
font-size: x-large;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-radius: 1.5em;
|
border-radius: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-button-friendly {
|
||||||
|
display: inline-block;
|
||||||
|
border: solid white 2px;
|
||||||
|
background-color: #3a3aeb;
|
||||||
|
color: white;
|
||||||
|
padding: 0.2em;
|
||||||
|
padding-left: 0.6em;
|
||||||
|
padding-right: 0.6em;
|
||||||
|
font-size: large;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 1.5em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.save-non-active {
|
.save-non-active {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: solid lightgrey 2px;
|
border: solid lightgrey 2px;
|
||||||
|
|
Loading…
Reference in a new issue