Refactoring: move all code files into a src directory

This commit is contained in:
Pieter Vander Vennet 2023-07-09 13:09:05 +02:00
parent de99f56ca8
commit e75d2789d2
389 changed files with 0 additions and 12 deletions

View file

@ -0,0 +1,29 @@
import Combine from "../Base/Combine"
import Attribution from "./Attribution"
import Img from "../Base/Img"
import ImageProvider from "../../Logic/ImageProviders/ImageProvider"
import BaseUIElement from "../BaseUIElement"
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
import { UIEventSource } from "../../Logic/UIEventSource"
export class AttributedImage extends Combine {
constructor(imageInfo: { url: string; provider?: ImageProvider; date?: Date }) {
let img: BaseUIElement
img = new Img(imageInfo.url, false, {
fallbackImage:
imageInfo.provider === Mapillary.singleton ? "./assets/svg/blocked.svg" : undefined,
})
let attr: BaseUIElement = undefined
if (imageInfo.provider !== undefined) {
attr = new Attribution(
UIEventSource.FromPromise(imageInfo.provider?.DownloadAttribution(imageInfo.url)),
imageInfo.provider?.SourceIcon(),
imageInfo.date
)
}
super([img, attr])
this.SetClass("block relative h-full")
}
}

View file

@ -0,0 +1,50 @@
import Combine from "../Base/Combine"
import Translations from "../i18n/Translations"
import BaseUIElement from "../BaseUIElement"
import { VariableUiElement } from "../Base/VariableUIElement"
import { Store } from "../../Logic/UIEventSource"
import { LicenseInfo } from "../../Logic/ImageProviders/LicenseInfo"
import { FixedUiElement } from "../Base/FixedUiElement"
import Link from "../Base/Link"
/**
* Small box in the bottom left of an image, e.g. the image in a popup
*/
export default class Attribution extends VariableUiElement {
constructor(license: Store<LicenseInfo>, icon: BaseUIElement, date?: Date) {
if (license === undefined) {
throw "No license source given in the attribution element"
}
super(
license.map((license: LicenseInfo) => {
if (license === undefined) {
return undefined
}
let title = undefined
if (license?.title) {
title = Translations.W(license?.title).SetClass("block")
if (license.informationLocation) {
title = new Link(title, license.informationLocation.href, true)
}
}
return new Combine([
icon
?.SetClass("block left")
.SetStyle("height: 2em; width: 2em; padding-right: 0.5em;"),
new Combine([
title,
Translations.W(license?.artist ?? "").SetClass("block font-bold"),
Translations.W(license?.license ?? license?.licenseShortName),
date === undefined
? undefined
: new FixedUiElement(date.toLocaleDateString()),
]).SetClass("flex flex-col"),
]).SetClass(
"flex flex-row bg-black text-white text-sm absolute bottom-0 left-0 p-0.5 pl-5 pr-3 rounded-lg no-images"
)
})
)
}
}

View file

@ -0,0 +1,71 @@
import { Store } from "../../Logic/UIEventSource"
import Translations from "../i18n/Translations"
import Toggle, { ClickableToggle } from "../Input/Toggle"
import Combine from "../Base/Combine"
import Svg from "../../Svg"
import { Tag } from "../../Logic/Tags/Tag"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { Changes } from "../../Logic/Osm/Changes"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
export default class DeleteImage extends Toggle {
constructor(
key: string,
tags: Store<any>,
state: { layout: LayoutConfig; changes?: Changes; osmConnection?: OsmConnection }
) {
const oldValue = tags.data[key]
const isDeletedBadge = Translations.t.image.isDeleted
.Clone()
.SetClass("rounded-full p-1")
.SetStyle("color:white;background:#ff8c8c")
.onClick(async () => {
await state?.changes?.applyAction(
new ChangeTagAction(tags.data.id, new Tag(key, oldValue), tags.data, {
changeType: "delete-image",
theme: state.layout.id,
})
)
})
const deleteButton = Translations.t.image.doDelete
.Clone()
.SetClass("block w-full pl-4 pr-4")
.SetStyle(
"color:white;background:#ff8c8c; border-top-left-radius:30rem; border-top-right-radius: 30rem;"
)
.onClick(async () => {
await state?.changes?.applyAction(
new ChangeTagAction(tags.data.id, new Tag(key, ""), tags.data, {
changeType: "answer",
theme: state.layout.id,
})
)
})
const cancelButton = Translations.t.general.cancel
.Clone()
.SetClass("bg-white pl-4 pr-4")
.SetStyle("border-bottom-left-radius:30rem; border-bottom-right-radius: 30rem;")
const openDelete = Svg.delete_icon_svg().SetStyle("width: 2em; height: 2em; display:block;")
const deleteDialog = new ClickableToggle(
new Combine([deleteButton, cancelButton]).SetClass("flex flex-col background-black"),
openDelete
)
cancelButton.onClick(() => deleteDialog.isEnabled.setData(false))
openDelete.onClick(() => deleteDialog.isEnabled.setData(true))
super(
new Toggle(
deleteDialog,
isDeletedBadge,
tags.map((tags) => (tags[key] ?? "") !== "")
),
undefined /*Login (and thus editing) is disabled*/,
state?.osmConnection?.isLoggedIn
)
this.SetClass("cursor-pointer")
}
}

View file

@ -0,0 +1,53 @@
import { SlideShow } from "./SlideShow"
import { Store } from "../../Logic/UIEventSource"
import Combine from "../Base/Combine"
import DeleteImage from "./DeleteImage"
import { AttributedImage } from "./AttributedImage"
import BaseUIElement from "../BaseUIElement"
import Toggle from "../Input/Toggle"
import ImageProvider from "../../Logic/ImageProviders/ImageProvider"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { Changes } from "../../Logic/Osm/Changes"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
export class ImageCarousel extends Toggle {
constructor(
images: Store<{ key: string; url: string; provider: ImageProvider }[]>,
tags: Store<any>,
state: { osmConnection?: OsmConnection; changes?: Changes; layout: LayoutConfig }
) {
const uiElements = images.map(
(imageURLS: { key: string; url: string; provider: ImageProvider }[]) => {
const uiElements: BaseUIElement[] = []
for (const url of imageURLS) {
try {
let image = new AttributedImage(url)
if (url.key !== undefined) {
image = new Combine([
image,
new DeleteImage(url.key, tags, state).SetClass(
"delete-image-marker absolute top-0 left-0 pl-3"
),
]).SetClass("relative")
}
image
.SetClass("w-full block")
.SetStyle("min-width: 50px; background: grey;")
uiElements.push(image)
} catch (e) {
console.error("Could not generate image element for", url.url, "due to", e)
}
}
return uiElements
}
)
super(
new SlideShow(uiElements).SetClass("w-full"),
undefined,
uiElements.map((els) => els.length > 0)
)
this.SetClass("block w-full")
}
}

View file

@ -0,0 +1,202 @@
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Combine from "../Base/Combine"
import Translations from "../i18n/Translations"
import Svg from "../../Svg"
import { Tag } from "../../Logic/Tags/Tag"
import BaseUIElement from "../BaseUIElement"
import Toggle from "../Input/Toggle"
import FileSelectorButton from "../Input/FileSelectorButton"
import ImgurUploader from "../../Logic/ImageProviders/ImgurUploader"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { FixedUiElement } from "../Base/FixedUiElement"
import { VariableUiElement } from "../Base/VariableUIElement"
import Loading from "../Base/Loading"
import { LoginToggle } from "../Popup/LoginButton"
import Constants from "../../Models/Constants"
import { SpecialVisualizationState } from "../SpecialVisualization"
export class ImageUploadFlow extends Toggle {
private static readonly uploadCountsPerId = new Map<string, UIEventSource<number>>()
constructor(
tagsSource: Store<any>,
state: SpecialVisualizationState,
imagePrefix: string = "image",
text: string = undefined
) {
const perId = ImageUploadFlow.uploadCountsPerId
const id = tagsSource.data.id
if (!perId.has(id)) {
perId.set(id, new UIEventSource<number>(0))
}
const uploadedCount = perId.get(id)
const uploader = new ImgurUploader(async (url) => {
// A file was uploaded - we add it to the tags of the object
const tags = tagsSource.data
let key = imagePrefix
if (tags[imagePrefix] !== undefined) {
let freeIndex = 0
while (tags[imagePrefix + ":" + freeIndex] !== undefined) {
freeIndex++
}
key = imagePrefix + ":" + freeIndex
}
await state.changes.applyAction(
new ChangeTagAction(tags.id, new Tag(key, url), tagsSource.data, {
changeType: "add-image",
theme: state.layout.id,
})
)
console.log("Adding image:" + key, url)
uploadedCount.data++
uploadedCount.ping()
})
const t = Translations.t.image
let labelContent: BaseUIElement
if (text === undefined) {
labelContent = Translations.t.image.addPicture
.Clone()
.SetClass("block align-middle mt-1 ml-3 text-4xl ")
} else {
labelContent = new FixedUiElement(text).SetClass(
"block align-middle mt-1 ml-3 text-2xl "
)
}
const label = new Combine([
Svg.camera_plus_svg().SetClass("block w-12 h-12 p-1 text-4xl "),
labelContent,
]).SetClass("w-full flex justify-center items-center")
const licenseStore = state?.osmConnection?.GetPreference(
Constants.OsmPreferenceKeyPicturesLicense,
"CC0"
)
const fileSelector = new FileSelectorButton(label, {
acceptType: "image/*",
allowMultiple: true,
labelClasses: "rounded-full border-2 border-black font-bold",
})
/* fileSelector.SetClass(
"p-2 border-4 border-detail rounded-full font-bold h-full align-middle w-full flex justify-center"
)
.SetStyle(" border-color: var(--foreground-color);")*/
fileSelector.GetValue().addCallback((filelist) => {
if (filelist === undefined || filelist.length === 0) {
return
}
for (var i = 0; i < filelist.length; i++) {
const sizeInBytes = filelist[i].size
console.log(filelist[i].name + " has a size of " + sizeInBytes + " Bytes")
if (sizeInBytes > uploader.maxFileSizeInMegabytes * 1000000) {
alert(
Translations.t.image.toBig.Subs({
actual_size: Math.floor(sizeInBytes / 1000000) + "MB",
max_size: uploader.maxFileSizeInMegabytes + "MB",
}).txt
)
return
}
}
const license = licenseStore?.data ?? "CC0"
const tags = tagsSource.data
const layout = state?.layout
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)?.Subs(tags)?.ConstructElement()
?.textContent ??
tags.name ??
"https//osm.org/" + tags.id
const description = [
"author:" + state.osmConnection.userDetails.data.name,
"license:" + license,
"osmid:" + tags.id,
].join("\n")
uploader.uploadMany(title, description, filelist)
})
const uploadFlow: BaseUIElement = new Combine([
new VariableUiElement(
uploader.queue
.map((q) => q.length)
.map((l) => {
if (l == 0) {
return undefined
}
if (l == 1) {
return new Loading(t.uploadingPicture).SetClass("alert")
} else {
return new Loading(
t.uploadingMultiple.Subs({ count: "" + l })
).SetClass("alert")
}
})
),
new VariableUiElement(
uploader.failed
.map((q) => q.length)
.map((l) => {
if (l == 0) {
return undefined
}
console.log(l)
return t.uploadFailed.SetClass("block alert")
})
),
new VariableUiElement(
uploadedCount.map((l) => {
if (l == 0) {
return undefined
}
if (l == 1) {
return t.uploadDone.Clone().SetClass("thanks block")
}
return t.uploadMultipleDone.Subs({ count: l }).SetClass("thanks block")
})
),
fileSelector,
new Combine([
Translations.t.image.respectPrivacy,
new VariableUiElement(
licenseStore.map((license) =>
Translations.t.image.currentLicense.Subs({ license })
)
)
.onClick(() => {
console.log("Opening the license settings... ")
state.guistate.openUsersettings("picture-license")
})
.SetClass("underline"),
]).SetStyle("font-size:small;"),
]).SetClass("flex flex-col image-upload-flow mt-4 mb-8 text-center leading-none")
super(
new LoginToggle(
/*We can show the actual upload button!*/
uploadFlow,
/* User not logged in*/ t.pleaseLogin.Clone(),
state
),
undefined /* Nothing as the user badge is disabled*/,
state?.featureSwitchUserbadge
)
}
}

48
src/UI/Image/SlideShow.ts Normal file
View file

@ -0,0 +1,48 @@
import { Store } from "../../Logic/UIEventSource"
import BaseUIElement from "../BaseUIElement"
import { Utils } from "../../Utils"
import Combine from "../Base/Combine"
export class SlideShow extends BaseUIElement {
private readonly embeddedElements: Store<BaseUIElement[]>
constructor(embeddedElements: Store<BaseUIElement[]>) {
super()
this.embeddedElements = embeddedElements
this.SetStyle("scroll-snap-type: x mandatory; overflow-x: auto")
}
protected InnerConstructElement(): HTMLElement {
const el = document.createElement("div")
el.style.minWidth = "min-content"
el.style.display = "flex"
el.style.justifyContent = "center"
this.embeddedElements.addCallbackAndRun((elements) => {
if (elements.length > 1) {
el.style.justifyContent = "unset"
}
while (el.firstChild) {
el.removeChild(el.lastChild)
}
elements = Utils.NoNull(elements).map((el) =>
new Combine([el])
.SetClass("block relative ml-1 bg-gray-200 m-1 rounded slideshow-item")
.SetStyle(
"min-width: 150px; width: max-content; height: var(--image-carousel-height);max-height: var(--image-carousel-height);scroll-snap-align: start;"
)
)
for (const element of elements ?? []) {
el.appendChild(element.ConstructElement())
}
})
const wrapper = document.createElement("div")
wrapper.style.maxWidth = "100%"
wrapper.style.overflowX = "auto"
wrapper.appendChild(el)
return wrapper
}
}