forked from MapComplete/MapComplete
Fixing too many bugs, cleaning up some old parts of the code
This commit is contained in:
parent
3d05999f85
commit
00a6611e1f
21 changed files with 706 additions and 436 deletions
|
@ -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<string[]>;
|
||||
|
||||
constructor(config: UIEventSource<LayoutConfigJson>,
|
||||
languages: UIEventSource<any>) {
|
||||
languages: UIEventSource<any>, userDetails: UserDetails) {
|
||||
super(undefined);
|
||||
this._config = config;
|
||||
this.languages = languages;
|
||||
|
||||
this.createPanels();
|
||||
this.createPanels(userDetails);
|
||||
const self = this;
|
||||
config.map<number>(config => config.layers.length).addCallback(() => self.createPanels());
|
||||
config.map<number>(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: "<img src='./assets/bug.svg'>",
|
||||
content: new LayerPanelWithPreview(this._config, this.languages, i)});
|
||||
content: new LayerPanelWithPreview(this._config, this.languages, i, userDetails)
|
||||
});
|
||||
}
|
||||
tabs.push({
|
||||
header: "<img src='./assets/layersAdd.svg'>",
|
||||
|
|
|
@ -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 => `<iframe src='${url}' width='100%' height='99%' style="box-sizing: border-box" title='Theme Preview'></iframe>`);
|
||||
const currentSetting = new UIEventSource<SingleSetting<any>>(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: "<img src='./assets/gear.svg'>",
|
||||
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: "<img src='./assets/layers.svg'>",
|
||||
content: new AllLayersPanel(es, languages, userDetails)
|
||||
},
|
||||
{
|
||||
header: "<img src='./assets/floppy.svg'>",
|
||||
content: new SavePanel(this.connection, es, chronic)
|
||||
|
||||
},
|
||||
{
|
||||
header: "<img src='./assets/share.svg'>",
|
||||
content: new SharePanel(es, liveUrl)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
InnerRender(): string {
|
||||
const ud = this.connection.userDetails.data;
|
||||
if (!ud.loggedIn) {
|
||||
return new Combine([
|
||||
"<h3>Not Logged in</h3>",
|
||||
"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([
|
||||
"<h3>Too little experience/h3>",
|
||||
`Creating your own (readonly) themes can only be done if you have more then <b>${State.userJourney.themeGeneratorReadOnlyUnlock}</b> changesets made`,
|
||||
`Making a theme including survey options can be done at <b>${State.userJourney.themeGeneratorFullUnlock}</b> changesets`,
|
||||
this.loginButton
|
||||
]).Render();
|
||||
}
|
||||
return this.mainPanel.Render()
|
||||
}
|
||||
|
||||
}
|
|
@ -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<LayoutConfigJson>,
|
||||
languages: UIEventSource<string[]>,
|
||||
index: number,
|
||||
currentlySelected: UIEventSource<SingleSetting<any>>) {
|
||||
currentlySelected: UIEventSource<SingleSetting<any>>,
|
||||
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<TagRenderingConfigJson>("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<LayoutConfigJson>,
|
||||
languages: UIEventSource<string[]>,
|
||||
index: number,
|
||||
currentlySelected: UIEventSource<SingleSetting<any>>): UIElement {
|
||||
currentlySelected: UIEventSource<SingleSetting<any>>,
|
||||
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",
|
||||
|
|
|
@ -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<any>, languages: UIEventSource<string[]>, index: number) {
|
||||
constructor(config: UIEventSource<any>, languages: UIEventSource<string[]>, index: number, userDetails: UserDetails) {
|
||||
super();
|
||||
|
||||
const currentlySelected = new UIEventSource<(SingleSetting<any>)>(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();
|
||||
|
|
49
UI/CustomGenerator/SavePanel.ts
Normal file
49
UI/CustomGenerator/SavePanel.ts
Normal file
|
@ -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<LayoutConfigJson>,
|
||||
chronic: UIEventSource<Date>) {
|
||||
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, "<br/>")
|
||||
.replace(/ /g, " ");
|
||||
}))
|
||||
.SetClass("literal-code");
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
return new Combine([
|
||||
"<h3>Saving</h3>",
|
||||
this.lastSaveEl,
|
||||
"<h3>JSON configuration</h3>",
|
||||
"The url hash is actually no more then a BASE64-encoding of the below JSON. This json contains the full configuration of the theme.<br/>" +
|
||||
"This configuration is mainly useful for debugging",
|
||||
this.json
|
||||
]).SetClass("scrollable")
|
||||
.Render();
|
||||
}
|
||||
|
||||
}
|
|
@ -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<TagRenderingConfigJson> {
|
||||
|
||||
|
@ -24,6 +26,7 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
|
|||
|
||||
constructor(languages: UIEventSource<string[]>,
|
||||
currentlySelected: UIEventSource<SingleSetting<any>>,
|
||||
userDetails: UserDetails,
|
||||
options?: {
|
||||
title?: string,
|
||||
description?: string,
|
||||
|
@ -36,6 +39,10 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
|
|||
this.SetClass("min-height");
|
||||
|
||||
this.options = options ?? {};
|
||||
const questionsNotUnlocked = userDetails.csCount < State.userJourney.themeGeneratorFullUnlock;
|
||||
this.options.disableQuestions =
|
||||
(this.options.disableQuestions ?? false) &&
|
||||
questionsNotUnlocked;
|
||||
|
||||
this.intro = new Combine(["<h3>", options?.title ?? "TagRendering", "</h3>", options?.description ?? ""])
|
||||
this.IsImage = options?.isImage ?? false;
|
||||
|
@ -47,9 +54,9 @@ export default class TagRenderingPanel extends InputElement<TagRenderingConfigJs
|
|||
return new SingleSetting<any>(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<TagRenderingConfigJs
|
|||
|
||||
const settings: (string | SingleSetting<any>)[] = [
|
||||
setting(new MultiLingualTextFields(languages), "render", "Value to show", " Renders this value. Note that <span class='literal-code'>{key}</span>-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),
|
||||
|
||||
"<h3>Mappings</h3>",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue