Fixed part of the special renderings

This commit is contained in:
pietervdvn 2021-06-14 19:21:33 +02:00
parent eec762b71f
commit e480c97676
11 changed files with 156 additions and 147 deletions

View file

@ -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) {

View file

@ -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)

View file

@ -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");
}

View file

@ -1,4 +1,3 @@
import {UIElement} from "../UIElement";
import {SlideShow} from "./SlideShow";
import {UIEventSource} from "../../Logic/UIEventSource";
import Combine from "../Base/Combine";
@ -8,33 +7,35 @@ 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")
]).SetClass("relative");
}
image
.SetClass("w-full block")
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;
}
}

View file

@ -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;
}
}

View file

@ -1,41 +1,59 @@
import {UIEventSource} from "../../Logic/UIEventSource";
import BaseUIElement from "../BaseUIElement";
import $ from "jquery"
export class SlideShow extends BaseUIElement {
private readonly _element: HTMLElement;
constructor(
embeddedElements: UIEventSource<BaseUIElement[]>) {
super()
const el = document.createElement("div")
this._element = el;
el.classList.add("slick-carousel")
require("slick-carousel")
// @ts-ignore
el.slick({
autoplay: true,
arrows: true,
dots: true,
lazyLoad: 'progressive',
variableWidth: true,
centerMode: true,
centerPadding: "60px",
adaptive: true
});
embeddedElements.addCallbackAndRun(elements => {
for (const element of elements ?? []) {
element.SetClass("slick-carousel-content")
}
});
private readonly embeddedElements: UIEventSource<BaseUIElement[]>;
constructor(embeddedElements: UIEventSource<BaseUIElement[]>) {
super()
this.embeddedElements = embeddedElements;
}
protected InnerConstructElement(): HTMLElement {
return this._element;
const el = document.createElement("div")
el.classList.add("slic-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({
autoplay: true,
arrows: true,
dots: true,
lazyLoad: 'progressive',
variableWidth: true,
centerMode: true,
centerPadding: "60px",
adaptive: true
});
})
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())
}
});
return el;
}
}

View file

@ -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,36 +29,37 @@ 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;
const label = document.createElement("label")
label.appendChild(this._label.ConstructElement())
el.appendChild(label)
actualInputElement.onchange = () => {
if (actualInputElement.files !== null) {
self._value.setData(actualInputElement.files)
}
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) {
self._value.setData(actualInputElement.files)
}
el.addEventListener('submit', e => {
if (actualInputElement.files !== null) {
self._value.setData(actualInputElement.files)
}
e.preventDefault()
})
el.appendChild(actualInputElement)
}
return undefined;
el.addEventListener('submit', e => {
if (actualInputElement.files !== null) {
self._value.setData(actualInputElement.files)
}
e.preventDefault()
})
el.appendChild(actualInputElement)
return el;
}

View file

@ -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)
@ -100,10 +101,12 @@ export default class ReviewForm extends InputElement<Review> {
Translations.t.reviews.tos.SetClass("subtle")
])
.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()
}

View file

@ -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("")

View file

@ -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
View file

@ -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")