Fixed part of the special renderings
This commit is contained in:
parent
eec762b71f
commit
e480c97676
11 changed files with 156 additions and 147 deletions
|
@ -3,9 +3,9 @@ import {Imgur} from "./Imgur";
|
|||
|
||||
export default class ImgurUploader {
|
||||
|
||||
public queue: UIEventSource<string[]>;
|
||||
public failed: UIEventSource<string[]>;
|
||||
public success: UIEventSource<string[]>
|
||||
public readonly queue: UIEventSource<string[]> = new UIEventSource<string[]>([]);
|
||||
public readonly failed: UIEventSource<string[]> = new UIEventSource<string[]>([]);
|
||||
public readonly success: UIEventSource<string[]> = new UIEventSource<string[]>([]);
|
||||
private readonly _handleSuccessUrl: (string) => void;
|
||||
|
||||
constructor(handleSuccessUrl: (string) => void) {
|
||||
|
|
|
@ -15,8 +15,17 @@ export class TabbedComponent extends Combine {
|
|||
for (let i = 0; i < elements.length; i++) {
|
||||
let element = elements[i];
|
||||
const header = Translations.W(element.header).onClick(() => openedTabSrc.setData(i))
|
||||
openedTabSrc.addCallbackAndRun(selected => {
|
||||
if(selected === i){
|
||||
header.SetClass("tab-active")
|
||||
header.RemoveClass("tab-non-active")
|
||||
}else{
|
||||
header.SetClass("tab-non-active")
|
||||
header.RemoveClass("tab-active")
|
||||
}
|
||||
})
|
||||
const content = Translations.W(element.content)
|
||||
content.SetClass("tab-content")
|
||||
content.SetClass("relative p-4 w-full inline-block")
|
||||
contentElements.push(content);
|
||||
const tab = header.SetClass("block tab-single-header")
|
||||
tabs.push(tab)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {DropDown} from "../Input/DropDown";
|
||||
import Translations from "../i18n/Translations";
|
||||
import State from "../../State";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
|
||||
export default class LicensePicker extends DropDown<string>{
|
||||
|
||||
|
@ -11,7 +12,7 @@ export default class LicensePicker extends DropDown<string>{
|
|||
{value: "CC-BY-SA 4.0", shown: Translations.t.image.ccbs},
|
||||
{value: "CC-BY 4.0", shown: Translations.t.image.ccb}
|
||||
],
|
||||
State.state.osmConnection.GetPreference("pictures-license")
|
||||
State.state?.osmConnection?.GetPreference("pictures-license") ?? new UIEventSource<string>("CC0")
|
||||
)
|
||||
this.SetClass("flex flex-col sm:flex-row").SetStyle("float:left");
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import {UIElement} from "../UIElement";
|
||||
import {SlideShow} from "./SlideShow";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import Combine from "../Base/Combine";
|
||||
|
@ -8,18 +7,16 @@ import {ImgurImage} from "./ImgurImage";
|
|||
import {MapillaryImage} from "./MapillaryImage";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import Img from "../Base/Img";
|
||||
import Toggle from "../Input/Toggle";
|
||||
|
||||
export class ImageCarousel extends UIElement{
|
||||
export class ImageCarousel extends Toggle {
|
||||
|
||||
public readonly slideshow: BaseUIElement;
|
||||
|
||||
constructor(images: UIEventSource<{key: string, url:string}[]>, tags: UIEventSource<any>) {
|
||||
super(images);
|
||||
const uiElements = images.map((imageURLS: {key: string, url:string}[]) => {
|
||||
constructor(images: UIEventSource<{ key: string, url: string }[]>, tags: UIEventSource<any>) {
|
||||
const uiElements = images.map((imageURLS: { key: string, url: string }[]) => {
|
||||
const uiElements: BaseUIElement[] = [];
|
||||
for (const url of imageURLS) {
|
||||
let image = ImageCarousel.CreateImageElement(url.url)
|
||||
if(url.key !== undefined){
|
||||
if (url.key !== undefined) {
|
||||
image = new Combine([
|
||||
image,
|
||||
new DeleteImage(url.key, tags).SetClass("delete-image-marker absolute top-0 left-0 pl-3")
|
||||
|
@ -27,14 +24,18 @@ export class ImageCarousel extends UIElement{
|
|||
}
|
||||
image
|
||||
.SetClass("w-full block")
|
||||
.SetStyle("min-width: 50px; background: grey;")
|
||||
uiElements.push(image);
|
||||
}
|
||||
return uiElements;
|
||||
});
|
||||
|
||||
this.slideshow = new SlideShow(uiElements);
|
||||
super(
|
||||
new SlideShow(uiElements).SetClass("w-full"),
|
||||
undefined,
|
||||
uiElements.map(els => els.length > 0)
|
||||
)
|
||||
this.SetClass("block w-full");
|
||||
this.slideshow.SetClass("w-full");
|
||||
}
|
||||
|
||||
/***
|
||||
|
@ -57,8 +58,4 @@ export class ImageCarousel extends UIElement{
|
|||
return new Img(url);
|
||||
}
|
||||
}
|
||||
|
||||
InnerRender() {
|
||||
return this.slideshow;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import State from "../../State";
|
||||
import Combine from "../Base/Combine";
|
||||
import Translations from "../i18n/Translations";
|
||||
|
@ -13,22 +12,9 @@ import ImgurUploader from "../../Logic/Web/ImgurUploader";
|
|||
import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI";
|
||||
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
||||
|
||||
export class ImageUploadFlow extends UIElement {
|
||||
|
||||
private readonly _element: BaseUIElement;
|
||||
|
||||
|
||||
private readonly _tags: UIEventSource<any>;
|
||||
private readonly _selectedLicence: UIEventSource<string>;
|
||||
|
||||
|
||||
private readonly _imagePrefix: string;
|
||||
export class ImageUploadFlow extends Toggle {
|
||||
|
||||
constructor(tagsSource: UIEventSource<any>, imagePrefix: string = "image") {
|
||||
super(State.state.osmConnection.userDetails);
|
||||
this._imagePrefix = imagePrefix;
|
||||
|
||||
|
||||
const uploader = new ImgurUploader(url => {
|
||||
// A file was uploaded - we add it to the tags of the object
|
||||
|
||||
|
@ -50,9 +36,10 @@ export class ImageUploadFlow extends UIElement {
|
|||
|
||||
const t = Translations.t.image;
|
||||
const label = new Combine([
|
||||
Svg.camera_plus_svg().SetStyle("width: 36px;height: 36px;padding: 0.1em;margin-top: 5px;border-radius: 0;float: left;display:block"),
|
||||
Translations.t.image.addPicture
|
||||
]).SetClass("image-upload-flow-button")
|
||||
Svg.camera_plus_ui().SetClass("block w-12 h-12 p-1"),
|
||||
Translations.t.image.addPicture.Clone().SetClass("block align-middle mt-1 ml-3")
|
||||
]).SetClass("p-2 border-4 border-black rounded-full text-4xl font-bold h-full align-middle w-full flex justify-center")
|
||||
|
||||
const fileSelector = new FileSelectorButton(label)
|
||||
fileSelector.GetValue().addCallback(filelist => {
|
||||
if (filelist === undefined) {
|
||||
|
@ -60,13 +47,13 @@ export class ImageUploadFlow extends UIElement {
|
|||
}
|
||||
|
||||
console.log("Received images from the user, starting upload")
|
||||
const license = this._selectedLicence.data ?? "CC0"
|
||||
const license = licensePicker.GetValue().data ?? "CC0"
|
||||
|
||||
const tags = this._tags.data;
|
||||
const tags = tagsSource.data;
|
||||
|
||||
const layout = State.state.layoutToUse.data
|
||||
const layout = State.state?.layoutToUse?.data
|
||||
let matchingLayer: LayerConfig = undefined
|
||||
for (const layer of layout.layers) {
|
||||
for (const layer of layout?.layers ?? []) {
|
||||
if (layer.source.osmTags.matchesProperties(tags)) {
|
||||
matchingLayer = layer;
|
||||
break;
|
||||
|
@ -90,30 +77,27 @@ export class ImageUploadFlow extends UIElement {
|
|||
|
||||
const uploadFlow: BaseUIElement = new Combine([
|
||||
fileSelector,
|
||||
Translations.t.image.respectPrivacy.SetStyle("font-size:small;"),
|
||||
Translations.t.image.respectPrivacy.Clone().SetStyle("font-size:small;"),
|
||||
licensePicker,
|
||||
uploadStateUi
|
||||
]).SetClass("image-upload-flow")
|
||||
.SetStyle("margin-top: 1em;margin-bottom: 2em;text-align: center;");
|
||||
]).SetClass("flex flex-col image-upload-flow mt-4 mb-8 text-center")
|
||||
|
||||
|
||||
const pleaseLoginButton = t.pleaseLogin.Clone()
|
||||
.onClick(() => State.state.osmConnection.AttemptLogin())
|
||||
.SetClass("login-button-friendly");
|
||||
this._element = new Toggle(
|
||||
super(
|
||||
new Toggle(
|
||||
/*We can show the actual upload button!*/
|
||||
uploadFlow,
|
||||
/* User not logged in*/ pleaseLoginButton,
|
||||
State.state.osmConnection.userDetails.map(userinfo => userinfo.loggedIn)
|
||||
State.state?.osmConnection?.isLoggedIn
|
||||
),
|
||||
undefined /* Nothing as the user badge is disabled*/, State.state.featureSwitchUserbadge
|
||||
undefined /* Nothing as the user badge is disabled*/,
|
||||
State.state.featureSwitchUserbadge
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
protected InnerRender(): string | BaseUIElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +1,30 @@
|
|||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import $ from "jquery"
|
||||
|
||||
export class SlideShow extends BaseUIElement {
|
||||
|
||||
|
||||
private readonly _element: HTMLElement;
|
||||
private readonly embeddedElements: UIEventSource<BaseUIElement[]>;
|
||||
|
||||
constructor(
|
||||
embeddedElements: UIEventSource<BaseUIElement[]>) {
|
||||
constructor(embeddedElements: UIEventSource<BaseUIElement[]>) {
|
||||
super()
|
||||
this.embeddedElements = embeddedElements;
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
const el = document.createElement("div")
|
||||
this._element = el;
|
||||
el.classList.add("slic-carousel")
|
||||
|
||||
el.classList.add("slick-carousel")
|
||||
el.onchange = () => {
|
||||
console.log("Parent is now ", el.parentElement)
|
||||
}
|
||||
|
||||
const mutationObserver = new MutationObserver(mutations => {
|
||||
console.log("Mutations are: ", mutations)
|
||||
|
||||
|
||||
mutationObserver.disconnect()
|
||||
require("slick-carousel")
|
||||
// @ts-ignore
|
||||
el.slick({
|
||||
|
@ -26,16 +37,23 @@ export class SlideShow extends BaseUIElement {
|
|||
centerPadding: "60px",
|
||||
adaptive: true
|
||||
});
|
||||
embeddedElements.addCallbackAndRun(elements => {
|
||||
})
|
||||
|
||||
mutationObserver.observe(el, {
|
||||
childList: true,
|
||||
characterData: true,
|
||||
subtree: true
|
||||
})
|
||||
|
||||
|
||||
this.embeddedElements.addCallbackAndRun(elements => {
|
||||
for (const element of elements ?? []) {
|
||||
element.SetClass("slick-carousel-content")
|
||||
el.appendChild(element.ConstructElement())
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
protected InnerConstructElement(): HTMLElement {
|
||||
return this._element;
|
||||
return el;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
import BaseUIElement from "../BaseUIElement";
|
||||
import {InputElement} from "../Input/InputElement";
|
||||
import {InputElement} from "./InputElement";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
|
||||
export default class FileSelectorButton extends InputElement<FileList> {
|
||||
|
||||
private static _nextid;
|
||||
IsSelected: UIEventSource<boolean>;
|
||||
private readonly _value = new UIEventSource(undefined);
|
||||
private readonly _label: BaseUIElement;
|
||||
|
@ -13,6 +14,8 @@ export default class FileSelectorButton extends InputElement<FileList> {
|
|||
super();
|
||||
this._label = label;
|
||||
this._acceptType = acceptType;
|
||||
this.SetClass("block cursor-pointer")
|
||||
label.SetClass("cursor-pointer")
|
||||
}
|
||||
|
||||
GetValue(): UIEventSource<FileList> {
|
||||
|
@ -26,18 +29,20 @@ export default class FileSelectorButton extends InputElement<FileList> {
|
|||
protected InnerConstructElement(): HTMLElement {
|
||||
const self = this;
|
||||
const el = document.createElement("form")
|
||||
{
|
||||
const label = document.createElement("label")
|
||||
label.appendChild(this._label.ConstructElement())
|
||||
el.appendChild(label)
|
||||
}
|
||||
{
|
||||
|
||||
const actualInputElement = document.createElement("input");
|
||||
actualInputElement.style.cssText = "display:none";
|
||||
actualInputElement.type = "file";
|
||||
actualInputElement.accept = this._acceptType;
|
||||
actualInputElement.name = "picField";
|
||||
actualInputElement.multiple = true;
|
||||
actualInputElement.id = "fileselector" + FileSelectorButton._nextid;
|
||||
FileSelectorButton._nextid++;
|
||||
|
||||
label.htmlFor = actualInputElement.id;
|
||||
|
||||
actualInputElement.onchange = () => {
|
||||
if (actualInputElement.files !== null) {
|
||||
|
@ -53,9 +58,8 @@ export default class FileSelectorButton extends InputElement<FileList> {
|
|||
})
|
||||
|
||||
el.appendChild(actualInputElement)
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return el;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -8,9 +8,10 @@ import Svg from "../../Svg";
|
|||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import {SaveButton} from "../Popup/SaveButton";
|
||||
import CheckBoxes from "../Input/Checkboxes";
|
||||
import UserDetails from "../../Logic/Osm/OsmConnection";
|
||||
import UserDetails, {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import State from "../../State";
|
||||
|
||||
export default class ReviewForm extends InputElement<Review> {
|
||||
|
||||
|
@ -19,19 +20,19 @@ export default class ReviewForm extends InputElement<Review> {
|
|||
private readonly _stars: BaseUIElement;
|
||||
private _saveButton: BaseUIElement;
|
||||
private readonly _isAffiliated: BaseUIElement;
|
||||
private userDetails: UIEventSource<UserDetails>;
|
||||
private readonly _postingAs: BaseUIElement;
|
||||
private readonly _osmConnection: OsmConnection;
|
||||
|
||||
|
||||
constructor(onSave: ((r: Review, doneSaving: (() => void)) => void), userDetails: UIEventSource<UserDetails>) {
|
||||
constructor(onSave: ((r: Review, doneSaving: (() => void)) => void), osmConnection: OsmConnection) {
|
||||
super();
|
||||
this.userDetails = userDetails;
|
||||
this._osmConnection = osmConnection;
|
||||
const t = Translations.t.reviews;
|
||||
this._value = new UIEventSource({
|
||||
made_by_user: new UIEventSource<boolean>(true),
|
||||
rating: undefined,
|
||||
comment: undefined,
|
||||
author: userDetails.data.name,
|
||||
author: osmConnection.userDetails.data.name,
|
||||
affiliated: false,
|
||||
date: new Date()
|
||||
});
|
||||
|
@ -48,7 +49,7 @@ export default class ReviewForm extends InputElement<Review> {
|
|||
const self = this;
|
||||
|
||||
this._postingAs =
|
||||
new Combine([t.posting_as, new VariableUiElement(userDetails.map((ud: UserDetails) => ud.name)).SetClass("review-author")])
|
||||
new Combine([t.posting_as, new VariableUiElement(osmConnection.userDetails.map((ud: UserDetails) => ud.name)).SetClass("review-author")])
|
||||
.SetStyle("display:flex;flex-direction: column;align-items: flex-end;margin-left: auto;")
|
||||
this._saveButton =
|
||||
new SaveButton(this._value.map(r => self.IsValid(r)), undefined)
|
||||
|
@ -101,9 +102,11 @@ export default class ReviewForm extends InputElement<Review> {
|
|||
])
|
||||
.SetClass("review-form")
|
||||
|
||||
const connection = this._osmConnection;
|
||||
const login = Translations.t.reviews.plz_login.Clone().onClick(() => connection.AttemptLogin())
|
||||
|
||||
return new Toggle(form, Translations.t.reviews.plz_login.Clone(),
|
||||
this.userDetails.map(userdetails => userdetails.loggedIn)).ToggleOnClick()
|
||||
return new Toggle(form,login ,
|
||||
connection.isLoggedIn)
|
||||
.ConstructElement()
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ export default class SpecialVisualizations {
|
|||
state.mangroveIdentity,
|
||||
state.osmConnection._dryRun
|
||||
);
|
||||
const form = new ReviewForm((r, whenDone) => mangrove.AddReview(r, whenDone), state.osmConnection.userDetails);
|
||||
const form = new ReviewForm((r, whenDone) => mangrove.AddReview(r, whenDone), state.osmConnection);
|
||||
return new ReviewElement(mangrove.GetSubjectUri(), mangrove.GetReviews(), form);
|
||||
}
|
||||
},
|
||||
|
@ -160,7 +160,7 @@ export default class SpecialVisualizations {
|
|||
],
|
||||
constr: (state: State, tagSource: UIEventSource<any>, args) => {
|
||||
if (window.navigator.share) {
|
||||
const title = state.layoutToUse.data.title.txt;
|
||||
const title = state?.layoutToUse?.data?.title?.txt ?? "MapComplete";
|
||||
let name = tagSource.data.name;
|
||||
if (name) {
|
||||
name = `${name} (${title})`
|
||||
|
@ -174,7 +174,7 @@ export default class SpecialVisualizations {
|
|||
return new ShareButton(Svg.share_ui(), {
|
||||
title: name,
|
||||
url: url,
|
||||
text: state.layoutToUse.data.shortDescription.txt
|
||||
text: state?.layoutToUse?.data?.shortDescription?.txt ?? "MapComplete"
|
||||
})
|
||||
} else {
|
||||
return new FixedUiElement("")
|
||||
|
|
|
@ -30,17 +30,6 @@
|
|||
}
|
||||
|
||||
|
||||
.tab-content {
|
||||
z-index: 5002;
|
||||
background-color: var(--background-color);
|
||||
color: var(--foreground-color);
|
||||
position: relative;
|
||||
padding: 1em;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tab-single-header {
|
||||
border-top-left-radius: 1em;
|
||||
border-top-right-radius: 1em;
|
||||
|
|
38
test.ts
38
test.ts
|
@ -1,27 +1,31 @@
|
|||
import {RadioButton} from "./UI/Input/RadioButton";
|
||||
import {FixedInputElement} from "./UI/Input/FixedInputElement";
|
||||
import {SubstitutedTranslation} from "./UI/SubstitutedTranslation";
|
||||
import {UIEventSource} from "./Logic/UIEventSource";
|
||||
import {Translation} from "./UI/i18n/Translation";
|
||||
import TagRenderingAnswer from "./UI/Popup/TagRenderingAnswer";
|
||||
import TagRenderingConfig from "./Customizations/JSON/TagRenderingConfig";
|
||||
import EditableTagRendering from "./UI/Popup/EditableTagRendering";
|
||||
import SpecialVisualizations from "./UI/SpecialVisualizations";
|
||||
import State from "./State";
|
||||
import Combine from "./UI/Base/Combine";
|
||||
import {FixedUiElement} from "./UI/Base/FixedUiElement";
|
||||
|
||||
|
||||
const tagsSource = new UIEventSource({
|
||||
id:'id',
|
||||
name:'name',
|
||||
surface:'asphalt'
|
||||
surface:'asphalt',
|
||||
image: "https://i.imgur.com/kX3rl3v.jpg",
|
||||
"image:1": "https://i.imgur.com/kX3rl3v.jpg",
|
||||
_country:"be",
|
||||
// "opening_hours":"mo-fr 09:00-18:00"
|
||||
})
|
||||
|
||||
const config = new TagRenderingConfig({
|
||||
render: "Rendering {name} {id} {surface}"
|
||||
}, null, "test")
|
||||
const state = new State(undefined)
|
||||
State.state = state
|
||||
|
||||
new EditableTagRendering(
|
||||
tagsSource,
|
||||
config
|
||||
).AttachTo("extradiv")
|
||||
const allSpecials = SpecialVisualizations.specialVisualizations.map(spec => {
|
||||
try{
|
||||
|
||||
|
||||
window.v = tagsSource
|
||||
return new Combine([spec.funcName, spec.constr(state, tagsSource, spec.args.map(a => a.defaultValue ?? "")).SetClass("block")])
|
||||
.SetClass("flex flex-col border border-black p-2 m-2");
|
||||
}catch(e){
|
||||
console.error(e)
|
||||
return new FixedUiElement("Could not construct "+spec.funcName+" due to "+e).SetClass("alert")
|
||||
}
|
||||
})
|
||||
new Combine(allSpecials).AttachTo("maindiv")
|
Loading…
Reference in a new issue