forked from MapComplete/MapComplete
More refactoring, still very broken
This commit is contained in:
parent
d5d90afc74
commit
62f471df1e
23 changed files with 428 additions and 356 deletions
|
@ -6,8 +6,11 @@ import {UIElement} from "../../UI/UIElement";
|
||||||
import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer";
|
import TagRenderingAnswer from "../../UI/Popup/TagRenderingAnswer";
|
||||||
import {ElementStorage} from "../ElementStorage";
|
import {ElementStorage} from "../ElementStorage";
|
||||||
import Combine from "../../UI/Base/Combine";
|
import Combine from "../../UI/Base/Combine";
|
||||||
|
import BaseUIElement from "../../UI/BaseUIElement";
|
||||||
|
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
|
||||||
|
|
||||||
|
class TitleElement extends UIEventSource<string> {
|
||||||
|
|
||||||
class TitleElement extends UIElement {
|
|
||||||
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
||||||
private readonly _selectedFeature: UIEventSource<any>;
|
private readonly _selectedFeature: UIEventSource<any>;
|
||||||
private readonly _allElementsStorage: ElementStorage;
|
private readonly _allElementsStorage: ElementStorage;
|
||||||
|
@ -15,41 +18,43 @@ class TitleElement extends UIElement {
|
||||||
constructor(layoutToUse: UIEventSource<LayoutConfig>,
|
constructor(layoutToUse: UIEventSource<LayoutConfig>,
|
||||||
selectedFeature: UIEventSource<any>,
|
selectedFeature: UIEventSource<any>,
|
||||||
allElementsStorage: ElementStorage) {
|
allElementsStorage: ElementStorage) {
|
||||||
super(layoutToUse);
|
super("MapComplete");
|
||||||
|
|
||||||
this._layoutToUse = layoutToUse;
|
this._layoutToUse = layoutToUse;
|
||||||
this._selectedFeature = selectedFeature;
|
this._selectedFeature = selectedFeature;
|
||||||
this._allElementsStorage = allElementsStorage;
|
this._allElementsStorage = allElementsStorage;
|
||||||
this.ListenTo(Locale.language);
|
|
||||||
this.ListenTo(this._selectedFeature)
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
|
|
||||||
|
this.syncWith(
|
||||||
|
this._selectedFeature.map(
|
||||||
|
selected => {
|
||||||
const defaultTitle = Translations.WT(this._layoutToUse.data?.title)?.txt ??"MapComplete"
|
const defaultTitle = Translations.WT(this._layoutToUse.data?.title)?.txt ??"MapComplete"
|
||||||
const feature = this._selectedFeature.data;
|
|
||||||
|
|
||||||
if (feature === undefined) {
|
if(selected === undefined){
|
||||||
return defaultTitle;
|
return defaultTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const layout = layoutToUse.data;
|
||||||
|
const tags = selected.properties;
|
||||||
|
|
||||||
const layout = this._layoutToUse.data;
|
|
||||||
const properties = this._selectedFeature.data.properties;
|
|
||||||
|
|
||||||
for (const layer of layout.layers) {
|
for (const layer of layout.layers) {
|
||||||
if (layer.title === undefined) {
|
if (layer.title === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (layer.source.osmTags.matchesProperties(properties)) {
|
if (layer.source.osmTags.matchesProperties(tags)) {
|
||||||
const tags = this._allElementsStorage.getEventSourceById(feature.properties.id);
|
|
||||||
if (tags == undefined) {
|
|
||||||
return defaultTitle;
|
|
||||||
}
|
|
||||||
const title = new TagRenderingAnswer(tags, layer.title)
|
const title = new TagRenderingAnswer(tags, layer.title)
|
||||||
return new Combine([defaultTitle, " | ", title]).Render();
|
return new Combine([defaultTitle, " | ", title]).ConstructElement().innerText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return defaultTitle;
|
|
||||||
|
return defaultTitle
|
||||||
|
}
|
||||||
|
, [Locale.language, layoutToUse]
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -58,14 +63,8 @@ export default class TitleHandler {
|
||||||
constructor(layoutToUse: UIEventSource<LayoutConfig>,
|
constructor(layoutToUse: UIEventSource<LayoutConfig>,
|
||||||
selectedFeature: UIEventSource<any>,
|
selectedFeature: UIEventSource<any>,
|
||||||
allElementsStorage: ElementStorage) {
|
allElementsStorage: ElementStorage) {
|
||||||
|
new TitleElement(layoutToUse, selectedFeature, allElementsStorage).addCallbackAndRun(title => {
|
||||||
selectedFeature.addCallbackAndRun(_ => {
|
document.title = title
|
||||||
const title = new TitleElement(layoutToUse, selectedFeature, allElementsStorage)
|
|
||||||
const d = document.createElement('div');
|
|
||||||
d.innerHTML = title.InnerRenderAsString();
|
|
||||||
// We pass everything into a div to strip out images etc...
|
|
||||||
document.title = (d.textContent || d.innerText);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,11 +10,8 @@ export class Imgur {
|
||||||
handleSuccessfullUpload: ((imageURL: string) => void),
|
handleSuccessfullUpload: ((imageURL: string) => void),
|
||||||
allDone: (() => void),
|
allDone: (() => void),
|
||||||
onFail: ((reason: string) => void),
|
onFail: ((reason: string) => void),
|
||||||
offset:number) {
|
offset:number = 0) {
|
||||||
|
|
||||||
if(offset === undefined){
|
|
||||||
throw "Offset undefined - not uploading to prevent to much uploads!"
|
|
||||||
}
|
|
||||||
if (blobs.length == offset) {
|
if (blobs.length == offset) {
|
||||||
allDone();
|
allDone();
|
||||||
return;
|
return;
|
||||||
|
@ -36,6 +33,7 @@ export class Imgur {
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDescriptionOfImage(url: string,
|
static getDescriptionOfImage(url: string,
|
||||||
handleDescription: ((license: LicenseInfo) => void)) {
|
handleDescription: ((license: LicenseInfo) => void)) {
|
||||||
|
|
||||||
|
|
41
Logic/Web/ImgurUploader.ts
Normal file
41
Logic/Web/ImgurUploader.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import {UIEventSource} from "../UIEventSource";
|
||||||
|
import {Imgur} from "./Imgur";
|
||||||
|
|
||||||
|
export default class ImgurUploader {
|
||||||
|
|
||||||
|
public queue: UIEventSource<string[]>;
|
||||||
|
public failed: UIEventSource<string[]>;
|
||||||
|
public success: UIEventSource<string[]>
|
||||||
|
private readonly _handleSuccessUrl: (string) => void;
|
||||||
|
|
||||||
|
constructor(handleSuccessUrl: (string) => void) {
|
||||||
|
this._handleSuccessUrl = handleSuccessUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uploadMany(title: string, description: string, files: FileList) {
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
this.queue.data.push(files.item(i).name)
|
||||||
|
}
|
||||||
|
this.queue.ping()
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
this.queue.setData([...self.queue.data])
|
||||||
|
Imgur.uploadMultiple(title,
|
||||||
|
description,
|
||||||
|
files,
|
||||||
|
function (url) {
|
||||||
|
console.log("File saved at", url);
|
||||||
|
self.success.setData([...self.success.data, url]);
|
||||||
|
this. handleSuccessUrl(url);
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
console.log("All uploads completed");
|
||||||
|
},
|
||||||
|
|
||||||
|
function (failReason) {
|
||||||
|
console.log("Upload failed due to ", failReason)
|
||||||
|
self.failed.setData([...self.failed.data, failReason])
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
62
UI/Base/FileSelectorButton.ts
Normal file
62
UI/Base/FileSelectorButton.ts
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import {InputElement} from "../Input/InputElement";
|
||||||
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
|
||||||
|
export default class FileSelectorButton extends InputElement<FileList> {
|
||||||
|
|
||||||
|
IsSelected: UIEventSource<boolean>;
|
||||||
|
private readonly _value = new UIEventSource(undefined);
|
||||||
|
private readonly _label: BaseUIElement;
|
||||||
|
private readonly _acceptType: string;
|
||||||
|
|
||||||
|
constructor(label: BaseUIElement, acceptType: string = "image/*") {
|
||||||
|
super();
|
||||||
|
this._label = label;
|
||||||
|
this._acceptType = acceptType;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetValue(): UIEventSource<FileList> {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsValid(t: FileList): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ export class VariableUiElement extends BaseUIElement {
|
||||||
|
|
||||||
private _element : HTMLElement;
|
private _element : HTMLElement;
|
||||||
|
|
||||||
constructor(contents: UIEventSource<string | BaseUIElement>) {
|
constructor(contents: UIEventSource<string | BaseUIElement | BaseUIElement[]>) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._element = document.createElement("span")
|
this._element = document.createElement("span")
|
||||||
|
@ -22,6 +22,10 @@ export class VariableUiElement extends BaseUIElement {
|
||||||
}
|
}
|
||||||
if (typeof contents === "string") {
|
if (typeof contents === "string") {
|
||||||
el.innerHTML = contents
|
el.innerHTML = contents
|
||||||
|
} else if (contents instanceof Array) {
|
||||||
|
for (const content of contents) {
|
||||||
|
el.appendChild(content.ConstructElement())
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
el.appendChild(contents.ConstructElement())
|
el.appendChild(contents.ConstructElement())
|
||||||
}
|
}
|
||||||
|
|
19
UI/BigComponents/LicensePicker.ts
Normal file
19
UI/BigComponents/LicensePicker.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import {DropDown} from "../Input/DropDown";
|
||||||
|
import Translations from "../i18n/Translations";
|
||||||
|
import State from "../../State";
|
||||||
|
|
||||||
|
export default class LicensePicker extends DropDown<string>{
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(Translations.t.image.willBePublished,
|
||||||
|
[
|
||||||
|
{value: "CC0", shown: Translations.t.image.cco},
|
||||||
|
{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")
|
||||||
|
)
|
||||||
|
this.SetClass("flex flex-col sm:flex-row").SetStyle("float:left");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
54
UI/BigComponents/UploadFlowStateUI.ts
Normal file
54
UI/BigComponents/UploadFlowStateUI.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import {UIElement} from "../UIElement";
|
||||||
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
|
import Translations from "../i18n/Translations";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows that 'images are uploading', 'all images are uploaded' as relevant...
|
||||||
|
*/
|
||||||
|
export default class UploadFlowStateUI extends UIElement{
|
||||||
|
|
||||||
|
private readonly _element: BaseUIElement
|
||||||
|
|
||||||
|
constructor(queue: UIEventSource<string[]>, failed: UIEventSource<string[]>, success: UIEventSource<string[]>) {
|
||||||
|
super();
|
||||||
|
const t = Translations.t.image;
|
||||||
|
|
||||||
|
this._element = new VariableUiElement(
|
||||||
|
|
||||||
|
queue.map(queue => {
|
||||||
|
const failedReasons = failed.data
|
||||||
|
const successCount = success.data.length
|
||||||
|
const pendingCount = queue.length - successCount - failedReasons.length;
|
||||||
|
|
||||||
|
let stateMessages : BaseUIElement[] = []
|
||||||
|
|
||||||
|
if(pendingCount == 1){
|
||||||
|
stateMessages.push(t.uploadingPicture.Clone().SetClass("alert"))
|
||||||
|
}
|
||||||
|
if(pendingCount > 1){
|
||||||
|
stateMessages.push(t.uploadingMultiple.Subs({count: ""+pendingCount}).SetClass("alert"))
|
||||||
|
}
|
||||||
|
if(failedReasons.length > 0){
|
||||||
|
stateMessages.push(t.uploadFailed.Clone().SetClass("alert"))
|
||||||
|
}
|
||||||
|
if(successCount > 0 && pendingCount == 0){
|
||||||
|
stateMessages.push(t.uploadDone.SetClass("thanks"))
|
||||||
|
}
|
||||||
|
|
||||||
|
stateMessages.forEach(msg => msg.SetStyle("display: block ruby"))
|
||||||
|
|
||||||
|
return stateMessages
|
||||||
|
}, [failed, success])
|
||||||
|
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected InnerRender(): string | BaseUIElement {
|
||||||
|
return this._element
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,14 +6,15 @@ import Combine from "../Base/Combine";
|
||||||
import State from "../../State";
|
import State from "../../State";
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
import {Tag} from "../../Logic/Tags/Tag";
|
import {Tag} from "../../Logic/Tags/Tag";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
|
|
||||||
export default class DeleteImage extends UIElement {
|
export default class DeleteImage extends UIElement {
|
||||||
private readonly key: string;
|
private readonly key: string;
|
||||||
private readonly tags: UIEventSource<any>;
|
private readonly tags: UIEventSource<any>;
|
||||||
|
|
||||||
private readonly isDeletedBadge: UIElement;
|
private readonly isDeletedBadge: BaseUIElement;
|
||||||
private readonly deleteDialog: UIElement;
|
private readonly deleteDialog: BaseUIElement;
|
||||||
|
|
||||||
constructor(key: string, tags: UIEventSource<any>) {
|
constructor(key: string, tags: UIEventSource<any>) {
|
||||||
super(tags);
|
super(tags);
|
||||||
|
|
|
@ -6,16 +6,17 @@ import DeleteImage from "./DeleteImage";
|
||||||
import {WikimediaImage} from "./WikimediaImage";
|
import {WikimediaImage} from "./WikimediaImage";
|
||||||
import {ImgurImage} from "./ImgurImage";
|
import {ImgurImage} from "./ImgurImage";
|
||||||
import {MapillaryImage} from "./MapillaryImage";
|
import {MapillaryImage} from "./MapillaryImage";
|
||||||
import {SimpleImageElement} from "./SimpleImageElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import Img from "../Base/Img";
|
||||||
|
|
||||||
export class ImageCarousel extends UIElement{
|
export class ImageCarousel extends UIElement{
|
||||||
|
|
||||||
public readonly slideshow: UIElement;
|
public readonly slideshow: BaseUIElement;
|
||||||
|
|
||||||
constructor(images: UIEventSource<{key: string, url:string}[]>, tags: UIEventSource<any>) {
|
constructor(images: UIEventSource<{key: string, url:string}[]>, tags: UIEventSource<any>) {
|
||||||
super(images);
|
super(images);
|
||||||
const uiElements = images.map((imageURLS: {key: string, url:string}[]) => {
|
const uiElements = images.map((imageURLS: {key: string, url:string}[]) => {
|
||||||
const uiElements: UIElement[] = [];
|
const uiElements: BaseUIElement[] = [];
|
||||||
for (const url of imageURLS) {
|
for (const url of imageURLS) {
|
||||||
let image = ImageCarousel.CreateImageElement(url.url)
|
let image = ImageCarousel.CreateImageElement(url.url)
|
||||||
if(url.key !== undefined){
|
if(url.key !== undefined){
|
||||||
|
@ -41,7 +42,7 @@ export class ImageCarousel extends UIElement{
|
||||||
* @param url
|
* @param url
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
private static CreateImageElement(url: string): UIElement {
|
private static CreateImageElement(url: string): BaseUIElement {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (url.startsWith("File:")) {
|
if (url.startsWith("File:")) {
|
||||||
return new WikimediaImage(url);
|
return new WikimediaImage(url);
|
||||||
|
@ -53,11 +54,11 @@ export class ImageCarousel extends UIElement{
|
||||||
} else if (url.toLowerCase().startsWith("https://www.mapillary.com/map/im/")) {
|
} else if (url.toLowerCase().startsWith("https://www.mapillary.com/map/im/")) {
|
||||||
return new MapillaryImage(url);
|
return new MapillaryImage(url);
|
||||||
} else {
|
} else {
|
||||||
return new SimpleImageElement(new UIEventSource<string>(url));
|
return new Img(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender() {
|
||||||
return this.slideshow.Render();
|
return this.slideshow;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,207 +1,119 @@
|
||||||
import $ from "jquery"
|
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import {UIElement} from "../UIElement";
|
import {UIElement} from "../UIElement";
|
||||||
import State from "../../State";
|
import State from "../../State";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
|
||||||
import {Imgur} from "../../Logic/Web/Imgur";
|
|
||||||
import {DropDown} from "../Input/DropDown";
|
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
import {Tag} from "../../Logic/Tags/Tag";
|
import {Tag} from "../../Logic/Tags/Tag";
|
||||||
import BaseUIElement from "../BaseUIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import LicensePicker from "../BigComponents/LicensePicker";
|
||||||
|
import Toggle from "../Input/Toggle";
|
||||||
|
import FileSelectorButton from "../Base/FileSelectorButton";
|
||||||
|
import ImgurUploader from "../../Logic/Web/ImgurUploader";
|
||||||
|
import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI";
|
||||||
|
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
||||||
|
|
||||||
export class ImageUploadFlow extends UIElement {
|
export class ImageUploadFlow extends UIElement {
|
||||||
private readonly _licensePicker: BaseUIElement;
|
|
||||||
|
private readonly _element: BaseUIElement;
|
||||||
|
|
||||||
|
|
||||||
private readonly _tags: UIEventSource<any>;
|
private readonly _tags: UIEventSource<any>;
|
||||||
private readonly _selectedLicence: UIEventSource<string>;
|
private readonly _selectedLicence: UIEventSource<string>;
|
||||||
private readonly _isUploading: UIEventSource<number> = new UIEventSource<number>(0)
|
|
||||||
private readonly _didFail: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
|
||||||
private readonly _allDone: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
|
||||||
private readonly _connectButton: UIElement;
|
|
||||||
private readonly _imagePrefix: string;
|
private readonly _imagePrefix: string;
|
||||||
|
|
||||||
constructor(tags: UIEventSource<any>, imagePrefix: string = "image") {
|
constructor(tagsSource: UIEventSource<any>, imagePrefix: string = "image") {
|
||||||
super(State.state.osmConnection.userDetails);
|
super(State.state.osmConnection.userDetails);
|
||||||
this._tags = tags;
|
|
||||||
this._imagePrefix = imagePrefix;
|
this._imagePrefix = imagePrefix;
|
||||||
|
|
||||||
this.ListenTo(this._isUploading);
|
|
||||||
this.ListenTo(this._didFail);
|
|
||||||
this.ListenTo(this._allDone);
|
|
||||||
|
|
||||||
const licensePicker = new DropDown(Translations.t.image.willBePublished,
|
const uploader = new ImgurUploader(url => {
|
||||||
[
|
// A file was uploaded - we add it to the tags of the object
|
||||||
{value: "CC0", shown: Translations.t.image.cco},
|
|
||||||
{value: "CC-BY-SA 4.0", shown: Translations.t.image.ccbs},
|
const tags = tagsSource.data
|
||||||
{value: "CC-BY 4.0", shown: Translations.t.image.ccb}
|
let key = imagePrefix
|
||||||
],
|
if (tags[imagePrefix] !== undefined) {
|
||||||
State.state.osmConnection.GetPreference("pictures-license")
|
let freeIndex = 0;
|
||||||
).SetClass("flex flex-col sm:flex-row");
|
while (tags[imagePrefix + ":" + freeIndex] !== undefined) {
|
||||||
licensePicker.SetStyle("float:left");
|
freeIndex++;
|
||||||
|
}
|
||||||
|
key = imagePrefix + ":" + freeIndex;
|
||||||
|
}
|
||||||
|
console.log("Adding image:" + key, url);
|
||||||
|
State.state.changes.addTag(tags.id, new Tag(key, url));
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const licensePicker = new LicensePicker()
|
||||||
|
|
||||||
const t = Translations.t.image;
|
const t = Translations.t.image;
|
||||||
|
|
||||||
this._licensePicker = licensePicker;
|
|
||||||
this._selectedLicence = licensePicker.GetValue();
|
|
||||||
|
|
||||||
this._connectButton = t.pleaseLogin.Clone()
|
|
||||||
.onClick(() => State.state.osmConnection.AttemptLogin())
|
|
||||||
.SetClass("login-button-friendly");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
|
|
||||||
if(!State.state.featureSwitchUserbadge.data){
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const t = Translations.t.image;
|
|
||||||
if (State.state.osmConnection.userDetails === undefined) {
|
|
||||||
return ""; // No user details -> logging in is probably disabled or smthing
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!State.state.osmConnection.userDetails.data.loggedIn) {
|
|
||||||
return this._connectButton.Render();
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentState: UIElement[] = [];
|
|
||||||
if (this._isUploading.data == 1) {
|
|
||||||
currentState.push(t.uploadingPicture);
|
|
||||||
} else if (this._isUploading.data > 0) {
|
|
||||||
currentState.push(t.uploadingMultiple.Subs({count: ""+this._isUploading.data}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._didFail.data) {
|
|
||||||
currentState.push(t.uploadFailed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._allDone.data) {
|
|
||||||
currentState.push(t.uploadDone)
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentStateHtml : UIElement = new FixedUiElement("");
|
|
||||||
if (currentState.length > 0) {
|
|
||||||
currentStateHtml = new Combine(currentState);
|
|
||||||
if (!this._allDone.data) {
|
|
||||||
currentStateHtml.SetClass("alert");
|
|
||||||
}else{
|
|
||||||
currentStateHtml.SetClass("thanks");
|
|
||||||
}
|
|
||||||
currentStateHtml.SetStyle("display:block ruby")
|
|
||||||
}
|
|
||||||
|
|
||||||
const extraInfo = new Combine([
|
|
||||||
Translations.t.image.respectPrivacy.SetStyle("font-size:small;"),
|
|
||||||
"<br/>",
|
|
||||||
this._licensePicker,
|
|
||||||
"<br/>",
|
|
||||||
currentStateHtml,
|
|
||||||
"<br/>"
|
|
||||||
]);
|
|
||||||
|
|
||||||
const label = new Combine([
|
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"),
|
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
|
Translations.t.image.addPicture
|
||||||
]).SetClass("image-upload-flow-button")
|
]).SetClass("image-upload-flow-button")
|
||||||
|
const fileSelector = new FileSelectorButton(label)
|
||||||
const actualInputElement =
|
fileSelector.GetValue().addCallback(filelist => {
|
||||||
`<input style='display: none' id='fileselector-${this.id}' type='file' accept='image/*' name='picField' multiple='multiple' alt=''/>`;
|
if (filelist === undefined) {
|
||||||
|
return;
|
||||||
const form = "<form id='fileselector-form-" + this.id + "'>" +
|
|
||||||
`<label for='fileselector-${this.id}'>` +
|
|
||||||
label.Render() +
|
|
||||||
"</label>" +
|
|
||||||
actualInputElement +
|
|
||||||
"</form>";
|
|
||||||
|
|
||||||
return new Combine([
|
|
||||||
form,
|
|
||||||
extraInfo
|
|
||||||
]).SetClass("image-upload-flow")
|
|
||||||
.SetStyle("margin-top: 1em;margin-bottom: 2em;text-align: center;")
|
|
||||||
.Render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private handleSuccessfulUpload(url) {
|
|
||||||
const tags = this._tags.data;
|
|
||||||
let key = this._imagePrefix;
|
|
||||||
if (tags[this._imagePrefix] !== undefined) {
|
|
||||||
|
|
||||||
let freeIndex = 0;
|
|
||||||
while (tags[this._imagePrefix + ":" + freeIndex] !== undefined) {
|
|
||||||
freeIndex++;
|
|
||||||
}
|
|
||||||
key = this._imagePrefix + ":" + freeIndex;
|
|
||||||
}
|
|
||||||
console.log("Adding image:" + key, url);
|
|
||||||
State.state.changes.addTag(tags.id, new Tag(key, url));
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleFiles(files) {
|
|
||||||
console.log("Received images from the user, starting upload")
|
console.log("Received images from the user, starting upload")
|
||||||
this._isUploading.setData(files.length);
|
const license = this._selectedLicence.data ?? "CC0"
|
||||||
this._allDone.setData(false);
|
|
||||||
|
|
||||||
if (this._selectedLicence.data === undefined) {
|
const tags = this._tags.data;
|
||||||
this._selectedLicence.setData("CC0");
|
|
||||||
|
const layout = State.state.layoutToUse.data
|
||||||
|
let matchingLayer: LayerConfig = undefined
|
||||||
|
for (const layer of layout.layers) {
|
||||||
|
if (layer.source.osmTags.matchesProperties(tags)) {
|
||||||
|
matchingLayer = layer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const tags = this._tags.data;
|
const title = matchingLayer?.title?.GetRenderValue(tags)?.ConstructElement().innerText ?? tags.name ?? "Unknown area";
|
||||||
const title = tags.name ?? "Unknown area";
|
|
||||||
const description = [
|
const description = [
|
||||||
"author:" + State.state.osmConnection.userDetails.data.name,
|
"author:" + State.state.osmConnection.userDetails.data.name,
|
||||||
"license:" + (this._selectedLicence.data ?? "CC0"),
|
"license:" + license,
|
||||||
"wikidata:" + tags.wikidata,
|
|
||||||
"osmid:" + tags.id,
|
"osmid:" + tags.id,
|
||||||
"name:" + tags.name
|
|
||||||
].join("\n");
|
].join("\n");
|
||||||
|
|
||||||
const self = this;
|
uploader.uploadMany(title, description, filelist)
|
||||||
|
|
||||||
Imgur.uploadMultiple(title,
|
|
||||||
description,
|
|
||||||
files,
|
|
||||||
function (url) {
|
|
||||||
console.log("File saved at", url);
|
|
||||||
self._isUploading.setData(self._isUploading.data - 1);
|
|
||||||
self.handleSuccessfulUpload(url);
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
console.log("All uploads completed");
|
|
||||||
self._allDone.setData(true);
|
|
||||||
},
|
|
||||||
function (failReason) {
|
|
||||||
console.log("Upload failed due to ", failReason)
|
|
||||||
// No need to call something from the options -> we handle this here
|
|
||||||
self._didFail.setData(true);
|
|
||||||
self._isUploading.data--;
|
|
||||||
self._isUploading.ping();
|
|
||||||
}, 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerUpdate(htmlElement: HTMLElement) {
|
|
||||||
this._licensePicker.Update()
|
|
||||||
const form = document.getElementById('fileselector-form-' + this.id) as HTMLFormElement
|
|
||||||
const selector = document.getElementById('fileselector-' + this.id)
|
|
||||||
const self = this
|
|
||||||
|
|
||||||
function submitHandler() {
|
|
||||||
self.handleFiles($(selector).prop('files'))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selector != null && form != null) {
|
|
||||||
selector.onchange = function () {
|
|
||||||
submitHandler()
|
|
||||||
}
|
|
||||||
form.addEventListener('submit', e => {
|
|
||||||
e.preventDefault()
|
|
||||||
submitHandler()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const uploadStateUi = new UploadFlowStateUI(uploader.queue, uploader.failed, uploader.success)
|
||||||
|
|
||||||
|
const uploadFlow: BaseUIElement = new Combine([
|
||||||
|
fileSelector,
|
||||||
|
Translations.t.image.respectPrivacy.SetStyle("font-size:small;"),
|
||||||
|
licensePicker,
|
||||||
|
uploadStateUi
|
||||||
|
]).SetClass("image-upload-flow")
|
||||||
|
.SetStyle("margin-top: 1em;margin-bottom: 2em;text-align: center;");
|
||||||
|
|
||||||
|
|
||||||
|
const pleaseLoginButton = t.pleaseLogin.Clone()
|
||||||
|
.onClick(() => State.state.osmConnection.AttemptLogin())
|
||||||
|
.SetClass("login-button-friendly");
|
||||||
|
this._element = new Toggle(
|
||||||
|
new Toggle(
|
||||||
|
/*We can show the actual upload button!*/
|
||||||
|
uploadFlow,
|
||||||
|
/* User not logged in*/ pleaseLoginButton,
|
||||||
|
State.state.osmConnection.userDetails.map(userinfo => userinfo.loggedIn)
|
||||||
|
),
|
||||||
|
undefined /* Nothing as the user badge is disabled*/, State.state.featureSwitchUserbadge
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected InnerRender(): string | BaseUIElement {
|
||||||
|
return this._element;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -4,7 +4,8 @@ import {LicenseInfo} from "../../Logic/Web/Wikimedia";
|
||||||
import {Imgur} from "../../Logic/Web/Imgur";
|
import {Imgur} from "../../Logic/Web/Imgur";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import Attribution from "./Attribution";
|
import Attribution from "./Attribution";
|
||||||
import {SimpleImageElement} from "./SimpleImageElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import Img from "../Base/Img";
|
||||||
|
|
||||||
|
|
||||||
export class ImgurImage extends UIElement {
|
export class ImgurImage extends UIElement {
|
||||||
|
@ -35,11 +36,11 @@ export class ImgurImage extends UIElement {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): BaseUIElement {
|
||||||
const image = new SimpleImageElement( new UIEventSource (this._imageLocation));
|
const image = new Img( this._imageLocation);
|
||||||
|
|
||||||
if(this._imageMeta.data === null){
|
if(this._imageMeta.data === null){
|
||||||
return image.Render();
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = this._imageMeta.data;
|
const meta = this._imageMeta.data;
|
||||||
|
@ -48,7 +49,7 @@ export class ImgurImage extends UIElement {
|
||||||
new Attribution(meta.artist, meta.license, undefined),
|
new Attribution(meta.artist, meta.license, undefined),
|
||||||
|
|
||||||
]).SetClass('block relative')
|
]).SetClass('block relative')
|
||||||
.Render();
|
;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,10 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import {LicenseInfo} from "../../Logic/Web/Wikimedia";
|
import {LicenseInfo} from "../../Logic/Web/Wikimedia";
|
||||||
import {Mapillary} from "../../Logic/Web/Mapillary";
|
import {Mapillary} from "../../Logic/Web/Mapillary";
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
import {SimpleImageElement} from "./SimpleImageElement";
|
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import Attribution from "./Attribution";
|
import Attribution from "./Attribution";
|
||||||
|
import Img from "../Base/Img";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
|
|
||||||
export class MapillaryImage extends UIElement {
|
export class MapillaryImage extends UIElement {
|
||||||
|
@ -40,19 +41,19 @@ export class MapillaryImage extends UIElement {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): BaseUIElement {
|
||||||
const url = `https://images.mapillary.com/${this._imageLocation}/thumb-640.jpg?client_id=TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2`;
|
const url = `https://images.mapillary.com/${this._imageLocation}/thumb-640.jpg?client_id=TXhLaWthQ1d4RUg0czVxaTVoRjFJZzowNDczNjUzNmIyNTQyYzI2`;
|
||||||
const image = new SimpleImageElement(new UIEventSource<string>(url))
|
const image = new Img(url)
|
||||||
|
|
||||||
const meta = this._imageMeta?.data;
|
const meta = this._imageMeta?.data;
|
||||||
if (!meta) {
|
if (!meta) {
|
||||||
return image.Render();
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Combine([
|
return new Combine([
|
||||||
image,
|
image,
|
||||||
new Attribution(meta.artist, meta.license, Svg.mapillary_svg())
|
new Attribution(meta.artist, meta.license, Svg.mapillary_svg())
|
||||||
]).SetClass("relative block").Render();
|
]).SetClass("relative block");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
|
||||||
|
|
||||||
|
|
||||||
export class SimpleImageElement extends UIElement {
|
|
||||||
|
|
||||||
constructor(source: UIEventSource<string>) {
|
|
||||||
super(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
InnerRender(): string {
|
|
||||||
return "<img src='" + this._source.data + "' alt='img'>";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,46 +1,22 @@
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import {UIElement} from "../UIElement";
|
import BaseUIElement from "../BaseUIElement";
|
||||||
import Combine from "../Base/Combine";
|
|
||||||
// @ts-ignore
|
|
||||||
import $ from "jquery"
|
|
||||||
|
|
||||||
export class SlideShow extends UIElement {
|
export class SlideShow extends BaseUIElement {
|
||||||
|
|
||||||
private readonly _embeddedElements: UIEventSource<UIElement[]>
|
|
||||||
|
private readonly _element: HTMLElement;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
embeddedElements: UIEventSource<UIElement[]>) {
|
embeddedElements: UIEventSource<BaseUIElement[]>) {
|
||||||
super(embeddedElements);
|
super()
|
||||||
this._embeddedElements = embeddedElements;
|
|
||||||
this._embeddedElements.addCallbackAndRun(elements => {
|
|
||||||
for (const element of elements ?? []) {
|
|
||||||
element.SetClass("slick-carousel-content")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
const el = document.createElement("div")
|
||||||
|
this._element = el;
|
||||||
|
|
||||||
InnerRender(): string {
|
el.classList.add("slick-carousel")
|
||||||
return new Combine(
|
|
||||||
this._embeddedElements.data,
|
|
||||||
).SetClass("block slick-carousel")
|
|
||||||
.Render();
|
|
||||||
}
|
|
||||||
|
|
||||||
Update() {
|
|
||||||
super.Update();
|
|
||||||
for (const uiElement of this._embeddedElements.data) {
|
|
||||||
uiElement.Update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InnerUpdate(htmlElement: HTMLElement) {
|
|
||||||
require("slick-carousel")
|
require("slick-carousel")
|
||||||
if(this._embeddedElements.data.length == 0){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
$('.slick-carousel').not('.slick-initialized').slick({
|
el.slick({
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
arrows: true,
|
arrows: true,
|
||||||
dots: true,
|
dots: true,
|
||||||
|
@ -50,6 +26,16 @@ export class SlideShow extends UIElement {
|
||||||
centerPadding: "60px",
|
centerPadding: "60px",
|
||||||
adaptive: true
|
adaptive: true
|
||||||
});
|
});
|
||||||
|
embeddedElements.addCallbackAndRun(elements => {
|
||||||
|
for (const element of elements ?? []) {
|
||||||
|
element.SetClass("slick-carousel-content")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected InnerConstructElement(): HTMLElement {
|
||||||
|
return this._element;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -4,8 +4,9 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
import Link from "../Base/Link";
|
import Link from "../Base/Link";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import {SimpleImageElement} from "./SimpleImageElement";
|
|
||||||
import Attribution from "./Attribution";
|
import Attribution from "./Attribution";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import Img from "../Base/Img";
|
||||||
|
|
||||||
|
|
||||||
export class WikimediaImage extends UIElement {
|
export class WikimediaImage extends UIElement {
|
||||||
|
@ -34,14 +35,14 @@ export class WikimediaImage extends UIElement {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): BaseUIElement {
|
||||||
const url = Wikimedia.ImageNameToUrl(this._imageLocation, 500, 400)
|
const url = Wikimedia.ImageNameToUrl(this._imageLocation, 500, 400)
|
||||||
.replace(/'/g, '%27');
|
.replace(/'/g, '%27');
|
||||||
const image = new SimpleImageElement(new UIEventSource<string>(url))
|
const image = new Img(url)
|
||||||
const meta = this._imageMeta?.data;
|
const meta = this._imageMeta?.data;
|
||||||
|
|
||||||
if (!meta) {
|
if (!meta) {
|
||||||
return image.Render();
|
return image;
|
||||||
}
|
}
|
||||||
new Link(Svg.wikimedia_commons_white_img,
|
new Link(Svg.wikimedia_commons_white_img,
|
||||||
`https://commons.wikimedia.org/wiki/${this._imageLocation}`, true)
|
`https://commons.wikimedia.org/wiki/${this._imageLocation}`, true)
|
||||||
|
@ -50,7 +51,7 @@ export class WikimediaImage extends UIElement {
|
||||||
return new Combine([
|
return new Combine([
|
||||||
image,
|
image,
|
||||||
new Attribution(meta.artist, meta.license, Svg.wikimedia_commons_white_svg())
|
new Attribution(meta.artist, meta.license, Svg.wikimedia_commons_white_svg())
|
||||||
]).SetClass("relative block").Render()
|
]).SetClass("relative block")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {InputElement} from "./InputElement";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import {UIElement} from "../UIElement";
|
import {UIElement} from "../UIElement";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supports multi-input
|
* Supports multi-input
|
||||||
|
@ -10,15 +11,24 @@ export default class CheckBoxes extends InputElement<number[]> {
|
||||||
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||||
|
|
||||||
private readonly value: UIEventSource<number[]>;
|
private readonly value: UIEventSource<number[]>;
|
||||||
private readonly _elements: UIElement[]
|
private readonly _elements: BaseUIElement[]
|
||||||
|
|
||||||
|
|
||||||
constructor(elements: UIElement[]) {
|
private readonly _element : HTMLElement
|
||||||
super(undefined);
|
|
||||||
|
constructor(elements: BaseUIElement[]) {
|
||||||
|
super();
|
||||||
this._elements = Utils.NoNull(elements);
|
this._elements = Utils.NoNull(elements);
|
||||||
|
|
||||||
this.value = new UIEventSource<number[]>([])
|
this.value = new UIEventSource<number[]>([])
|
||||||
this.ListenTo(this.value);
|
|
||||||
|
|
||||||
|
const el = document.createElement()
|
||||||
|
this._element = el;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected InnerConstructElement(): HTMLElement {
|
||||||
|
return this._element
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,46 +4,33 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
export default class ColorPicker extends InputElement<string> {
|
export default class ColorPicker extends InputElement<string> {
|
||||||
|
|
||||||
private readonly value: UIEventSource<string>
|
private readonly value: UIEventSource<string>
|
||||||
|
private readonly _element : HTMLElement
|
||||||
constructor(
|
constructor(
|
||||||
value?: UIEventSource<string>
|
value: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.value = value ?? new UIEventSource<string>(undefined);
|
this.value = value ;
|
||||||
const self = this;
|
|
||||||
|
const el = document.createElement("input")
|
||||||
|
this._element = el;
|
||||||
|
|
||||||
|
el.type = "color"
|
||||||
|
|
||||||
this.value.addCallbackAndRun(v => {
|
this.value.addCallbackAndRun(v => {
|
||||||
if(v === undefined){
|
if(v === undefined){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.SetValue(v);
|
el.value =v
|
||||||
});
|
});
|
||||||
|
|
||||||
|
el.oninput = () => {
|
||||||
|
const hex = el.value;
|
||||||
|
value.setData(hex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected InnerConstructElement(): HTMLElement {
|
||||||
InnerRender(): string {
|
return this._element;
|
||||||
return `<span id="${this.id}"><input type='color' id='color-${this.id}'></span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SetValue(color: string){
|
|
||||||
const field = document.getElementById("color-" + this.id);
|
|
||||||
if (field === undefined || field === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
field.value = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected InnerUpdate() {
|
|
||||||
const field = document.getElementById("color-" + this.id);
|
|
||||||
if (field === undefined || field === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const self = this;
|
|
||||||
field.oninput = () => {
|
|
||||||
const hex = field["value"];
|
|
||||||
self.value.setData(hex);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GetValue(): UIEventSource<string> {
|
GetValue(): UIEventSource<string> {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Combine from "../Base/Combine";
|
||||||
import {SubstitutedTranslation} from "../SubstitutedTranslation";
|
import {SubstitutedTranslation} from "../SubstitutedTranslation";
|
||||||
import {Translation} from "../i18n/Translation";
|
import {Translation} from "../i18n/Translation";
|
||||||
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Displays the correct value for a known tagrendering
|
* Displays the correct value for a known tagrendering
|
||||||
|
@ -13,7 +14,7 @@ import {TagUtils} from "../../Logic/Tags/TagUtils";
|
||||||
export default class TagRenderingAnswer extends UIElement {
|
export default class TagRenderingAnswer extends UIElement {
|
||||||
private readonly _tags: UIEventSource<any>;
|
private readonly _tags: UIEventSource<any>;
|
||||||
private _configuration: TagRenderingConfig;
|
private _configuration: TagRenderingConfig;
|
||||||
private _content: UIElement;
|
private _content: BaseUIElement;
|
||||||
private readonly _contentClass: string;
|
private readonly _contentClass: string;
|
||||||
private _contentStyle: string;
|
private _contentStyle: string;
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ export default class TagRenderingAnswer extends UIElement {
|
||||||
this.SetStyle("word-wrap: anywhere;");
|
this.SetStyle("word-wrap: anywhere;");
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string | UIElement{
|
InnerRender(): string | BaseUIElement{
|
||||||
if (this._configuration.condition !== undefined) {
|
if (this._configuration.condition !== undefined) {
|
||||||
if (!this._configuration.condition.matchesProperties(this._tags.data)) {
|
if (!this._configuration.condition.matchesProperties(this._tags.data)) {
|
||||||
return "";
|
return "";
|
||||||
|
@ -74,8 +75,7 @@ export default class TagRenderingAnswer extends UIElement {
|
||||||
this._content = valuesToRender[0];
|
this._content = valuesToRender[0];
|
||||||
} else {
|
} else {
|
||||||
this._content = new Combine(["<ul>",
|
this._content = new Combine(["<ul>",
|
||||||
...valuesToRender.map(tr => new Combine(["<li>", tr, "</li>"]))
|
...valuesToRender.map(tr => new Combine(["<li>", tr, "</li>"])) ,
|
||||||
,
|
|
||||||
"</ul>"
|
"</ul>"
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,14 @@ import {UIElement} from "../UIElement";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import SingleReview from "./SingleReview";
|
import SingleReview from "./SingleReview";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export default class ReviewElement extends UIElement {
|
export default class ReviewElement extends UIElement {
|
||||||
private readonly _reviews: UIEventSource<Review[]>;
|
private readonly _reviews: UIEventSource<Review[]>;
|
||||||
private readonly _subject: string;
|
private readonly _subject: string;
|
||||||
private readonly _middleElement: UIElement;
|
private readonly _middleElement: BaseUIElement;
|
||||||
|
|
||||||
constructor(subject: string, reviews: UIEventSource<Review[]>, middleElement: UIElement) {
|
constructor(subject: string, reviews: UIEventSource<Review[]>, middleElement: BaseUIElement) {
|
||||||
super(reviews);
|
super(reviews);
|
||||||
this._middleElement = middleElement;
|
this._middleElement = middleElement;
|
||||||
if (reviews === undefined) {
|
if (reviews === undefined) {
|
||||||
|
@ -26,7 +27,7 @@ export default class ReviewElement extends UIElement {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
InnerRender(): UIElement {
|
InnerRender(): BaseUIElement {
|
||||||
|
|
||||||
const elements = [];
|
const elements = [];
|
||||||
const revs = this._reviews.data;
|
const revs = this._reviews.data;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import {UIElement} from "../UIElement";
|
|
||||||
import {InputElement} from "../Input/InputElement";
|
import {InputElement} from "../Input/InputElement";
|
||||||
import {Review} from "../../Logic/Web/Review";
|
import {Review} from "../../Logic/Web/Review";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
|
@ -10,16 +9,18 @@ import {VariableUiElement} from "../Base/VariableUIElement";
|
||||||
import {SaveButton} from "../Popup/SaveButton";
|
import {SaveButton} from "../Popup/SaveButton";
|
||||||
import CheckBoxes from "../Input/Checkboxes";
|
import CheckBoxes from "../Input/Checkboxes";
|
||||||
import UserDetails from "../../Logic/Osm/OsmConnection";
|
import UserDetails from "../../Logic/Osm/OsmConnection";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
import Toggle from "../Input/Toggle";
|
||||||
|
|
||||||
export default class ReviewForm extends InputElement<Review> {
|
export default class ReviewForm extends InputElement<Review> {
|
||||||
|
|
||||||
private readonly _value: UIEventSource<Review>;
|
private readonly _value: UIEventSource<Review>;
|
||||||
private readonly _comment: UIElement;
|
private readonly _comment: BaseUIElement;
|
||||||
private readonly _stars: UIElement;
|
private readonly _stars: BaseUIElement;
|
||||||
private _saveButton: UIElement;
|
private _saveButton: BaseUIElement;
|
||||||
private readonly _isAffiliated: UIElement;
|
private readonly _isAffiliated: BaseUIElement;
|
||||||
private userDetails: UIEventSource<UserDetails>;
|
private userDetails: UIEventSource<UserDetails>;
|
||||||
private readonly _postingAs: UIElement;
|
private readonly _postingAs: BaseUIElement;
|
||||||
|
|
||||||
|
|
||||||
constructor(onSave: ((r: Review, doneSaving: (() => void)) => void), userDetails: UIEventSource<UserDetails>) {
|
constructor(onSave: ((r: Review, doneSaving: (() => void)) => void), userDetails: UIEventSource<UserDetails>) {
|
||||||
|
@ -86,13 +87,9 @@ export default class ReviewForm extends InputElement<Review> {
|
||||||
return this._value;
|
return this._value;
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): UIElement {
|
InnerConstructElement(): HTMLElement {
|
||||||
|
|
||||||
if(!this.userDetails.data.loggedIn){
|
const form = new Combine([
|
||||||
return Translations.t.reviews.plz_login;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Combine([
|
|
||||||
new Combine([this._stars, this._postingAs]).SetClass("review-form-top"),
|
new Combine([this._stars, this._postingAs]).SetClass("review-form-top"),
|
||||||
this._comment,
|
this._comment,
|
||||||
new Combine([
|
new Combine([
|
||||||
|
@ -103,6 +100,11 @@ export default class ReviewForm extends InputElement<Review> {
|
||||||
Translations.t.reviews.tos.SetClass("subtle")
|
Translations.t.reviews.tos.SetClass("subtle")
|
||||||
])
|
])
|
||||||
.SetClass("review-form")
|
.SetClass("review-form")
|
||||||
|
|
||||||
|
|
||||||
|
return new Toggle(form, Translations.t.reviews.plz_login,
|
||||||
|
this.userDetails.map(userdetails => userdetails.loggedIn))
|
||||||
|
.ConstructElement()
|
||||||
}
|
}
|
||||||
|
|
||||||
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {FixedUiElement} from "../Base/FixedUiElement";
|
||||||
import Translations from "../i18n/Translations";
|
import Translations from "../i18n/Translations";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import ReviewElement from "./ReviewElement";
|
import ReviewElement from "./ReviewElement";
|
||||||
|
import BaseUIElement from "../BaseUIElement";
|
||||||
|
|
||||||
export default class SingleReview extends UIElement{
|
export default class SingleReview extends UIElement{
|
||||||
private _review: Review;
|
private _review: Review;
|
||||||
|
@ -13,7 +14,7 @@ export default class SingleReview extends UIElement{
|
||||||
this._review = review;
|
this._review = review;
|
||||||
|
|
||||||
}
|
}
|
||||||
public static GenStars(rating: number): UIElement {
|
public static GenStars(rating: number): BaseUIElement {
|
||||||
if (rating === undefined) {
|
if (rating === undefined) {
|
||||||
return Translations.t.reviews.no_rating;
|
return Translations.t.reviews.no_rating;
|
||||||
}
|
}
|
||||||
|
@ -26,7 +27,7 @@ export default class SingleReview extends UIElement{
|
||||||
scoreTen % 2 == 1 ? "<img src='./assets/svg/star_half.svg' class='h-8 md:h-12'/>" : ""
|
scoreTen % 2 == 1 ? "<img src='./assets/svg/star_half.svg' class='h-8 md:h-12'/>" : ""
|
||||||
]).SetClass("flex w-max")
|
]).SetClass("flex w-max")
|
||||||
}
|
}
|
||||||
InnerRender(): UIElement {
|
InnerRender(): BaseUIElement {
|
||||||
const d = this._review.date;
|
const d = this._review.date;
|
||||||
let review = this._review;
|
let review = this._review;
|
||||||
const el= new Combine(
|
const el= new Combine(
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import {UIElement} from "./UIElement";
|
|
||||||
import {UIEventSource} from "../Logic/UIEventSource";
|
import {UIEventSource} from "../Logic/UIEventSource";
|
||||||
import {VariableUiElement} from "./Base/VariableUIElement";
|
import {VariableUiElement} from "./Base/VariableUIElement";
|
||||||
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler";
|
import LiveQueryHandler from "../Logic/Web/LiveQueryHandler";
|
||||||
|
@ -17,12 +16,15 @@ import OpeningHoursVisualization from "./OpeningHours/OhVisualization";
|
||||||
|
|
||||||
import State from "../State";
|
import State from "../State";
|
||||||
import {ImageSearcher} from "../Logic/Actors/ImageSearcher";
|
import {ImageSearcher} from "../Logic/Actors/ImageSearcher";
|
||||||
|
import BaseUIElement from "./BaseUIElement";
|
||||||
|
|
||||||
export default class SpecialVisualizations {
|
export default class SpecialVisualizations {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static specialVisualizations: {
|
public static specialVisualizations: {
|
||||||
funcName: string,
|
funcName: string,
|
||||||
constr: ((state: State, tagSource: UIEventSource<any>, argument: string[]) => UIElement),
|
constr: ((state: State, tagSource: UIEventSource<any>, argument: string[]) => BaseUIElement),
|
||||||
docs: string,
|
docs: string,
|
||||||
example?: string,
|
example?: string,
|
||||||
args: { name: string, defaultValue?: string, doc: string }[]
|
args: { name: string, defaultValue?: string, doc: string }[]
|
||||||
|
@ -36,6 +38,9 @@ export default class SpecialVisualizations {
|
||||||
return new VariableUiElement(tags.map(tags => {
|
return new VariableUiElement(tags.map(tags => {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
for (const key in tags) {
|
for (const key in tags) {
|
||||||
|
if(!tags.hasOwnProperty(key)){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
parts.push(key + "=" + tags[key]);
|
parts.push(key + "=" + tags[key]);
|
||||||
}
|
}
|
||||||
return parts.join("<br/>")
|
return parts.join("<br/>")
|
||||||
|
@ -179,7 +184,7 @@ export default class SpecialVisualizations {
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
static HelpMessage: UIElement = SpecialVisualizations.GenHelpMessage();
|
static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage();
|
||||||
|
|
||||||
private static GenHelpMessage() {
|
private static GenHelpMessage() {
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,12 @@ import Combine from "./Base/Combine";
|
||||||
import State from "../State";
|
import State from "../State";
|
||||||
import {FixedUiElement} from "./Base/FixedUiElement";
|
import {FixedUiElement} from "./Base/FixedUiElement";
|
||||||
import SpecialVisualizations from "./SpecialVisualizations";
|
import SpecialVisualizations from "./SpecialVisualizations";
|
||||||
|
import BaseUIElement from "./BaseUIElement";
|
||||||
|
|
||||||
export class SubstitutedTranslation extends UIElement {
|
export class SubstitutedTranslation extends UIElement {
|
||||||
private readonly tags: UIEventSource<any>;
|
private readonly tags: UIEventSource<any>;
|
||||||
private readonly translation: Translation;
|
private readonly translation: Translation;
|
||||||
private content: UIElement[];
|
private content: BaseUIElement[];
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
translation: Translation,
|
translation: Translation,
|
||||||
|
@ -54,7 +55,7 @@ export class SubstitutedTranslation extends UIElement {
|
||||||
return new Combine(this.content);
|
return new Combine(this.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CreateContent(): UIElement[] {
|
private CreateContent(): BaseUIElement[] {
|
||||||
let txt = this.translation?.txt;
|
let txt = this.translation?.txt;
|
||||||
if (txt === undefined) {
|
if (txt === undefined) {
|
||||||
return []
|
return []
|
||||||
|
@ -64,7 +65,7 @@ export class SubstitutedTranslation extends UIElement {
|
||||||
return this.EvaluateSpecialComponents(txt);
|
return this.EvaluateSpecialComponents(txt);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EvaluateSpecialComponents(template: string): UIElement[] {
|
private EvaluateSpecialComponents(template: string): BaseUIElement[] {
|
||||||
|
|
||||||
for (const knownSpecial of SpecialVisualizations.specialVisualizations) {
|
for (const knownSpecial of SpecialVisualizations.specialVisualizations) {
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue