273 lines
11 KiB
TypeScript
273 lines
11 KiB
TypeScript
|
import Combine from "../Base/Combine";
|
||
|
import {FlowPanelFactory, FlowStep} from "../ImportFlow/FlowStep";
|
||
|
import {ImmutableStore, Store, UIEventSource} from "../../Logic/UIEventSource";
|
||
|
import {InputElement} from "../Input/InputElement";
|
||
|
import {SvgToPdf, SvgToPdfOptions} from "../../Utils/svgToPdf";
|
||
|
import {FixedInputElement} from "../Input/FixedInputElement";
|
||
|
import {FixedUiElement} from "../Base/FixedUiElement";
|
||
|
import FileSelectorButton from "../Input/FileSelectorButton";
|
||
|
import InputElementMap from "../Input/InputElementMap";
|
||
|
import {RadioButton} from "../Input/RadioButton";
|
||
|
import {Utils} from "../../Utils";
|
||
|
import {VariableUiElement} from "../Base/VariableUIElement";
|
||
|
import Loading from "../Base/Loading";
|
||
|
import BaseUIElement from "../BaseUIElement";
|
||
|
import Img from "../Base/Img";
|
||
|
import Title from "../Base/Title";
|
||
|
import CheckBoxes, {CheckBox} from "../Input/Checkboxes";
|
||
|
import Minimap from "../Base/Minimap";
|
||
|
import SearchAndGo from "./SearchAndGo";
|
||
|
import Toggle from "../Input/Toggle";
|
||
|
import List from "../Base/List";
|
||
|
import LeftIndex from "../Base/LeftIndex";
|
||
|
import Constants from "../../Models/Constants";
|
||
|
|
||
|
class SelectTemplate extends Combine implements FlowStep<{ title: string, pages: string[] }> {
|
||
|
readonly IsValid: Store<boolean>;
|
||
|
readonly Value: Store<{ title: string, pages: string[] }>;
|
||
|
|
||
|
constructor() {
|
||
|
const elements: InputElement<{ templateName: string, pages: string[] }>[] = []
|
||
|
for (const templateName in SvgToPdf.templates) {
|
||
|
const template = SvgToPdf.templates[templateName]
|
||
|
elements.push(new FixedInputElement(
|
||
|
new Combine([new FixedUiElement(templateName).SetClass("font-bold pr-2"),
|
||
|
template.description
|
||
|
])
|
||
|
, new UIEventSource({templateName, pages: template.pages})))
|
||
|
}
|
||
|
|
||
|
const file = new FileSelectorButton(new FixedUiElement("Select an svg image which acts as template"), {
|
||
|
acceptType: "image/svg+xml",
|
||
|
allowMultiple: true
|
||
|
})
|
||
|
const fileMapped = new InputElementMap<FileList, { templateName: string, pages: string[], fromFile: true }>(file, (x0, x1) => x0 === x1,
|
||
|
(filelist) => {
|
||
|
if (filelist === undefined) {
|
||
|
return undefined;
|
||
|
}
|
||
|
const pages = []
|
||
|
let templateName: string = undefined;
|
||
|
for (const file of Array.from(filelist)) {
|
||
|
|
||
|
if (templateName == undefined) {
|
||
|
templateName = file.name.substring(file.name.lastIndexOf("/") + 1)
|
||
|
templateName = templateName.substring(0, templateName.lastIndexOf("."))
|
||
|
}
|
||
|
pages.push(file.text())
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
templateName,
|
||
|
pages,
|
||
|
fromFile: true
|
||
|
}
|
||
|
|
||
|
},
|
||
|
fromX => undefined
|
||
|
)
|
||
|
elements.push(fileMapped)
|
||
|
const radio = new RadioButton(elements, {selectFirstAsDefault: true})
|
||
|
|
||
|
const loaded: Store<{ success: { title: string, pages: string[] } } | { error: any }> = radio.GetValue().bind(template => {
|
||
|
if (template === undefined) {
|
||
|
return undefined
|
||
|
}
|
||
|
if (template["fromFile"]) {
|
||
|
return UIEventSource.FromPromiseWithErr(Promise.all(template.pages).then(pages => ({
|
||
|
title: template.templateName,
|
||
|
pages
|
||
|
})))
|
||
|
}
|
||
|
const urls = template.pages.map(p => SelectTemplate.ToUrl(p))
|
||
|
const dloadAll: Promise<{ title: string, pages: string[] }> = Promise.all(urls.map(url => Utils.download(url))).then(pages => ({
|
||
|
pages,
|
||
|
title: template.templateName
|
||
|
}))
|
||
|
|
||
|
return UIEventSource.FromPromiseWithErr(dloadAll)
|
||
|
})
|
||
|
const preview = new VariableUiElement(
|
||
|
loaded.map(pages => {
|
||
|
if (pages === undefined) {
|
||
|
return new Loading()
|
||
|
}
|
||
|
if (pages["err"] !== undefined) {
|
||
|
return new FixedUiElement("Loading preview failed: " + pages["err"]).SetClass("alert")
|
||
|
}
|
||
|
const svgs = pages["success"].pages
|
||
|
if (svgs.length === 0) {
|
||
|
return new FixedUiElement("No pages loaded...").SetClass("alert")
|
||
|
}
|
||
|
const els: BaseUIElement[] = []
|
||
|
for (const pageSrc of svgs) {
|
||
|
const el = new Img(pageSrc, true)
|
||
|
.SetClass("w-96 m-2 border-black border-2")
|
||
|
els.push(el)
|
||
|
}
|
||
|
return new Combine(els).SetClass("flex border border-subtle rounded-xl");
|
||
|
})
|
||
|
)
|
||
|
|
||
|
super([
|
||
|
new Title("Select template"),
|
||
|
radio,
|
||
|
new Title("Preview"),
|
||
|
preview
|
||
|
]);
|
||
|
this.Value = loaded.map(l => l === undefined ? undefined : l["success"])
|
||
|
this.IsValid = this.Value.map(v => v !== undefined)
|
||
|
}
|
||
|
|
||
|
public static ToUrl(spec: string) {
|
||
|
if (spec.startsWith("http")) {
|
||
|
return spec
|
||
|
}
|
||
|
return window.location.protocol + "//" + window.location.host + "/" + spec
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
class SelectPdfOptions extends Combine implements FlowStep<{ title: string, pages: string[], options: SvgToPdfOptions }> {
|
||
|
readonly IsValid: Store<boolean>;
|
||
|
readonly Value: Store<{ title: string, pages: string[], options: SvgToPdfOptions }>;
|
||
|
|
||
|
constructor(title: string, pages: string[], getFreeDiv: () => string) {
|
||
|
const dummy = new CheckBox("Don't add data to the map (to quickly preview the PDF)", false)
|
||
|
const overrideMapLocation = new CheckBox("Override map location: use a selected location instead of the location set in the template", false)
|
||
|
const locationInput = Minimap.createMiniMap().SetClass("block w-full")
|
||
|
const searchField = new SearchAndGo({leafletMap: locationInput.leafletMap})
|
||
|
const selectLocation =
|
||
|
new Combine([
|
||
|
new Toggle(new Combine([new Title("Select override location"), searchField]).SetClass("flex"), undefined, overrideMapLocation.GetValue()),
|
||
|
new Toggle(locationInput.SetStyle("height: 20rem"), undefined, overrideMapLocation.GetValue()).SetStyle("height: 20rem")
|
||
|
]).SetClass("block").SetStyle("height: 25rem")
|
||
|
super([new Title("Select options"),
|
||
|
dummy,
|
||
|
overrideMapLocation,
|
||
|
selectLocation
|
||
|
]);
|
||
|
this.Value = dummy.GetValue().map((disableMaps) => {
|
||
|
return {
|
||
|
pages,
|
||
|
title,
|
||
|
options: <SvgToPdfOptions>{
|
||
|
disableMaps,
|
||
|
getFreeDiv,
|
||
|
overrideLocation: overrideMapLocation.GetValue().data ? locationInput.location.data : undefined
|
||
|
}
|
||
|
}
|
||
|
}, [overrideMapLocation.GetValue(), locationInput.location])
|
||
|
this.IsValid = new ImmutableStore(true)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
class PreparePdf extends Combine implements FlowStep<{ svgToPdf: SvgToPdf, languages: string[] }> {
|
||
|
readonly IsValid: Store<boolean>;
|
||
|
readonly Value: Store<{ svgToPdf: SvgToPdf, languages: string[] }>;
|
||
|
|
||
|
constructor(title: string, pages: string[], options: SvgToPdfOptions) {
|
||
|
const svgToPdf = new SvgToPdf(title, pages, options)
|
||
|
const languageOptions = [
|
||
|
new FixedInputElement("Nederlands", "nl"),
|
||
|
new FixedInputElement("English", "en")
|
||
|
]
|
||
|
const languages = new CheckBoxes(languageOptions)
|
||
|
|
||
|
const isPrepared = UIEventSource.FromPromiseWithErr(svgToPdf.Prepare())
|
||
|
|
||
|
super([
|
||
|
new Title("Select languages..."),
|
||
|
languages,
|
||
|
new Toggle(
|
||
|
new Loading("Preparing maps..."),
|
||
|
undefined,
|
||
|
isPrepared.map(p => p === undefined)
|
||
|
)
|
||
|
]);
|
||
|
this.Value = isPrepared.map(isPrepped => {
|
||
|
if (isPrepped === undefined) {
|
||
|
return undefined
|
||
|
}
|
||
|
if (isPrepped["success"] !== undefined) {
|
||
|
const svgToPdf = isPrepped["success"]
|
||
|
const langs = languages.GetValue().data.map(i => languageOptions[i].GetValue().data)
|
||
|
if (langs.length === 0) {
|
||
|
return undefined
|
||
|
}
|
||
|
return {svgToPdf, languages: langs}
|
||
|
}
|
||
|
return undefined;
|
||
|
}, [languages.GetValue()])
|
||
|
this.IsValid = this.Value.map(v => v !== undefined)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
class SavePdf extends Combine {
|
||
|
|
||
|
constructor(svgToPdf: SvgToPdf, languages: string[]) {
|
||
|
|
||
|
super([
|
||
|
new Title("Generating your pdfs..."),
|
||
|
new List(languages.map(lng => new Toggle(
|
||
|
lng + " is done!",
|
||
|
new Loading("Creating pdf for " + lng),
|
||
|
UIEventSource.FromPromiseWithErr(svgToPdf.ConvertSvg(lng).then(() => true))
|
||
|
.map(x => x !== undefined && x["success"] === true)
|
||
|
)))
|
||
|
]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export class PdfExportGui extends LeftIndex {
|
||
|
|
||
|
|
||
|
constructor(freeDivId: string) {
|
||
|
|
||
|
let i = 0
|
||
|
const createDiv = (): string => {
|
||
|
const div = document.createElement("div")
|
||
|
div.id = "freediv-" + (i++)
|
||
|
document.getElementById(freeDivId).append(div)
|
||
|
return div.id
|
||
|
}
|
||
|
|
||
|
Constants.defaultOverpassUrls.splice(0, 1)
|
||
|
const {flow, furthestStep, titles} = FlowPanelFactory.start(
|
||
|
new Title("Select template"), new SelectTemplate()
|
||
|
).then(new Title("Select options"), ({title, pages}) => new SelectPdfOptions(title, pages, createDiv))
|
||
|
.then("Generate maps...", ({title, pages, options}) => new PreparePdf(title, pages, options))
|
||
|
.finish("Generating...", ({svgToPdf, languages}) => new SavePdf(svgToPdf, languages))
|
||
|
|
||
|
|
||
|
const toc = new List(
|
||
|
titles.map(
|
||
|
(title, i) =>
|
||
|
new VariableUiElement(
|
||
|
furthestStep.map((currentStep) => {
|
||
|
if (i > currentStep) {
|
||
|
return new Combine([title]).SetClass("subtle")
|
||
|
}
|
||
|
if (i == currentStep) {
|
||
|
return new Combine([title]).SetClass("font-bold")
|
||
|
}
|
||
|
if (i < currentStep) {
|
||
|
return title
|
||
|
}
|
||
|
})
|
||
|
)
|
||
|
),
|
||
|
true
|
||
|
)
|
||
|
|
||
|
const leftContents: BaseUIElement[] = [
|
||
|
toc
|
||
|
].map((el) => el?.SetClass("pl-4"))
|
||
|
|
||
|
super(leftContents, flow)
|
||
|
}
|
||
|
}
|