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,14 +6,15 @@ import Combine from "../Base/Combine";
|
|||
import State from "../../State";
|
||||
import Svg from "../../Svg";
|
||||
import {Tag} from "../../Logic/Tags/Tag";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
|
||||
|
||||
export default class DeleteImage extends UIElement {
|
||||
private readonly key: string;
|
||||
private readonly tags: UIEventSource<any>;
|
||||
|
||||
private readonly isDeletedBadge: UIElement;
|
||||
private readonly deleteDialog: UIElement;
|
||||
private readonly isDeletedBadge: BaseUIElement;
|
||||
private readonly deleteDialog: BaseUIElement;
|
||||
|
||||
constructor(key: string, tags: UIEventSource<any>) {
|
||||
super(tags);
|
||||
|
|
|
@ -6,16 +6,17 @@ import DeleteImage from "./DeleteImage";
|
|||
import {WikimediaImage} from "./WikimediaImage";
|
||||
import {ImgurImage} from "./ImgurImage";
|
||||
import {MapillaryImage} from "./MapillaryImage";
|
||||
import {SimpleImageElement} from "./SimpleImageElement";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import Img from "../Base/Img";
|
||||
|
||||
export class ImageCarousel extends UIElement{
|
||||
|
||||
public readonly slideshow: UIElement;
|
||||
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}[]) => {
|
||||
const uiElements: UIElement[] = [];
|
||||
const uiElements: BaseUIElement[] = [];
|
||||
for (const url of imageURLS) {
|
||||
let image = ImageCarousel.CreateImageElement(url.url)
|
||||
if(url.key !== undefined){
|
||||
|
@ -41,7 +42,7 @@ export class ImageCarousel extends UIElement{
|
|||
* @param url
|
||||
* @constructor
|
||||
*/
|
||||
private static CreateImageElement(url: string): UIElement {
|
||||
private static CreateImageElement(url: string): BaseUIElement {
|
||||
// @ts-ignore
|
||||
if (url.startsWith("File:")) {
|
||||
return new WikimediaImage(url);
|
||||
|
@ -53,11 +54,11 @@ export class ImageCarousel extends UIElement{
|
|||
} else if (url.toLowerCase().startsWith("https://www.mapillary.com/map/im/")) {
|
||||
return new MapillaryImage(url);
|
||||
} else {
|
||||
return new SimpleImageElement(new UIEventSource<string>(url));
|
||||
return new Img(url);
|
||||
}
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
return this.slideshow.Render();
|
||||
InnerRender() {
|
||||
return this.slideshow;
|
||||
}
|
||||
}
|
|
@ -1,207 +1,119 @@
|
|||
import $ from "jquery"
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {UIElement} from "../UIElement";
|
||||
import State from "../../State";
|
||||
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 Svg from "../../Svg";
|
||||
import {Tag} from "../../Logic/Tags/Tag";
|
||||
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 {
|
||||
private readonly _licensePicker: BaseUIElement;
|
||||
|
||||
private readonly _element: BaseUIElement;
|
||||
|
||||
|
||||
private readonly _tags: UIEventSource<any>;
|
||||
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;
|
||||
|
||||
constructor(tags: UIEventSource<any>, imagePrefix: string = "image") {
|
||||
constructor(tagsSource: UIEventSource<any>, imagePrefix: string = "image") {
|
||||
super(State.state.osmConnection.userDetails);
|
||||
this._tags = tags;
|
||||
this._imagePrefix = imagePrefix;
|
||||
|
||||
this.ListenTo(this._isUploading);
|
||||
this.ListenTo(this._didFail);
|
||||
this.ListenTo(this._allDone);
|
||||
|
||||
const licensePicker = new DropDown(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")
|
||||
).SetClass("flex flex-col sm:flex-row");
|
||||
licensePicker.SetStyle("float:left");
|
||||
const uploader = new ImgurUploader(url => {
|
||||
// A file was uploaded - we add it to the tags of the object
|
||||
|
||||
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");
|
||||
const tags = tagsSource.data
|
||||
let key = imagePrefix
|
||||
if (tags[imagePrefix] !== undefined) {
|
||||
let freeIndex = 0;
|
||||
while (tags[imagePrefix + ":" + freeIndex] !== undefined) {
|
||||
freeIndex++;
|
||||
}
|
||||
key = imagePrefix + ":" + freeIndex;
|
||||
}
|
||||
currentStateHtml.SetStyle("display:block ruby")
|
||||
}
|
||||
console.log("Adding image:" + key, url);
|
||||
State.state.changes.addTag(tags.id, new Tag(key, url));
|
||||
})
|
||||
|
||||
const extraInfo = new Combine([
|
||||
Translations.t.image.respectPrivacy.SetStyle("font-size:small;"),
|
||||
"<br/>",
|
||||
this._licensePicker,
|
||||
"<br/>",
|
||||
currentStateHtml,
|
||||
"<br/>"
|
||||
]);
|
||||
|
||||
const licensePicker = new LicensePicker()
|
||||
|
||||
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")
|
||||
|
||||
const actualInputElement =
|
||||
`<input style='display: none' id='fileselector-${this.id}' type='file' accept='image/*' name='picField' multiple='multiple' alt=''/>`;
|
||||
|
||||
const form = "<form id='fileselector-form-" + this.id + "'>" +
|
||||
`<label for='fileselector-${this.id}'>` +
|
||||
label.Render() +
|
||||
"</label>" +
|
||||
actualInputElement +
|
||||
"</form>";
|
||||
const fileSelector = new FileSelectorButton(label)
|
||||
fileSelector.GetValue().addCallback(filelist => {
|
||||
if (filelist === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
return new Combine([
|
||||
form,
|
||||
extraInfo
|
||||
console.log("Received images from the user, starting upload")
|
||||
const license = this._selectedLicence.data ?? "CC0"
|
||||
|
||||
const tags = this._tags.data;
|
||||
|
||||
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 title = matchingLayer?.title?.GetRenderValue(tags)?.ConstructElement().innerText ?? tags.name ?? "Unknown area";
|
||||
const description = [
|
||||
"author:" + State.state.osmConnection.userDetails.data.name,
|
||||
"license:" + license,
|
||||
"osmid:" + tags.id,
|
||||
].join("\n");
|
||||
|
||||
uploader.uploadMany(title, description, filelist)
|
||||
|
||||
})
|
||||
|
||||
|
||||
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;")
|
||||
.Render();
|
||||
}
|
||||
.SetStyle("margin-top: 1em;margin-bottom: 2em;text-align: center;");
|
||||
|
||||
|
||||
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")
|
||||
this._isUploading.setData(files.length);
|
||||
this._allDone.setData(false);
|
||||
|
||||
if (this._selectedLicence.data === undefined) {
|
||||
this._selectedLicence.setData("CC0");
|
||||
}
|
||||
|
||||
|
||||
const tags = this._tags.data;
|
||||
const title = tags.name ?? "Unknown area";
|
||||
const description = [
|
||||
"author:" + State.state.osmConnection.userDetails.data.name,
|
||||
"license:" + (this._selectedLicence.data ?? "CC0"),
|
||||
"wikidata:" + tags.wikidata,
|
||||
"osmid:" + tags.id,
|
||||
"name:" + tags.name
|
||||
].join("\n");
|
||||
|
||||
const self = this;
|
||||
|
||||
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
|
||||
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
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
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()
|
||||
})
|
||||
}
|
||||
protected InnerRender(): string | BaseUIElement {
|
||||
return this._element;
|
||||
}
|
||||
|
||||
}
|
|
@ -4,7 +4,8 @@ import {LicenseInfo} from "../../Logic/Web/Wikimedia";
|
|||
import {Imgur} from "../../Logic/Web/Imgur";
|
||||
import Combine from "../Base/Combine";
|
||||
import Attribution from "./Attribution";
|
||||
import {SimpleImageElement} from "./SimpleImageElement";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import Img from "../Base/Img";
|
||||
|
||||
|
||||
export class ImgurImage extends UIElement {
|
||||
|
@ -35,11 +36,11 @@ export class ImgurImage extends UIElement {
|
|||
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
const image = new SimpleImageElement( new UIEventSource (this._imageLocation));
|
||||
InnerRender(): BaseUIElement {
|
||||
const image = new Img( this._imageLocation);
|
||||
|
||||
if(this._imageMeta.data === null){
|
||||
return image.Render();
|
||||
return image;
|
||||
}
|
||||
|
||||
const meta = this._imageMeta.data;
|
||||
|
@ -48,7 +49,7 @@ export class ImgurImage extends UIElement {
|
|||
new Attribution(meta.artist, meta.license, undefined),
|
||||
|
||||
]).SetClass('block relative')
|
||||
.Render();
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,10 @@ import {UIEventSource} from "../../Logic/UIEventSource";
|
|||
import {LicenseInfo} from "../../Logic/Web/Wikimedia";
|
||||
import {Mapillary} from "../../Logic/Web/Mapillary";
|
||||
import Svg from "../../Svg";
|
||||
import {SimpleImageElement} from "./SimpleImageElement";
|
||||
import Combine from "../Base/Combine";
|
||||
import Attribution from "./Attribution";
|
||||
import Img from "../Base/Img";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
|
||||
|
||||
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 image = new SimpleImageElement(new UIEventSource<string>(url))
|
||||
const image = new Img(url)
|
||||
|
||||
const meta = this._imageMeta?.data;
|
||||
if (!meta) {
|
||||
return image.Render();
|
||||
return image;
|
||||
}
|
||||
|
||||
return new Combine([
|
||||
image,
|
||||
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 {UIElement} from "../UIElement";
|
||||
import Combine from "../Base/Combine";
|
||||
// @ts-ignore
|
||||
import $ from "jquery"
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
|
||||
export class SlideShow extends UIElement {
|
||||
export class SlideShow extends BaseUIElement {
|
||||
|
||||
private readonly _embeddedElements: UIEventSource<UIElement[]>
|
||||
|
||||
private readonly _element: HTMLElement;
|
||||
|
||||
constructor(
|
||||
embeddedElements: UIEventSource<UIElement[]>) {
|
||||
super(embeddedElements);
|
||||
this._embeddedElements = embeddedElements;
|
||||
this._embeddedElements.addCallbackAndRun(elements => {
|
||||
for (const element of elements ?? []) {
|
||||
element.SetClass("slick-carousel-content")
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
InnerRender(): string {
|
||||
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) {
|
||||
embeddedElements: UIEventSource<BaseUIElement[]>) {
|
||||
super()
|
||||
|
||||
const el = document.createElement("div")
|
||||
this._element = el;
|
||||
|
||||
el.classList.add("slick-carousel")
|
||||
require("slick-carousel")
|
||||
if(this._embeddedElements.data.length == 0){
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
$('.slick-carousel').not('.slick-initialized').slick({
|
||||
el.slick({
|
||||
autoplay: true,
|
||||
arrows: true,
|
||||
dots: true,
|
||||
|
@ -48,8 +24,18 @@ export class SlideShow extends UIElement {
|
|||
variableWidth: true,
|
||||
centerMode: true,
|
||||
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 Link from "../Base/Link";
|
||||
import Combine from "../Base/Combine";
|
||||
import {SimpleImageElement} from "./SimpleImageElement";
|
||||
import Attribution from "./Attribution";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import Img from "../Base/Img";
|
||||
|
||||
|
||||
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)
|
||||
.replace(/'/g, '%27');
|
||||
const image = new SimpleImageElement(new UIEventSource<string>(url))
|
||||
const image = new Img(url)
|
||||
const meta = this._imageMeta?.data;
|
||||
|
||||
if (!meta) {
|
||||
return image.Render();
|
||||
return image;
|
||||
}
|
||||
new Link(Svg.wikimedia_commons_white_img,
|
||||
`https://commons.wikimedia.org/wiki/${this._imageLocation}`, true)
|
||||
|
@ -50,7 +51,7 @@ export class WikimediaImage extends UIElement {
|
|||
return new Combine([
|
||||
image,
|
||||
new Attribution(meta.artist, meta.license, Svg.wikimedia_commons_white_svg())
|
||||
]).SetClass("relative block").Render()
|
||||
]).SetClass("relative block")
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue