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 { 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" import Toggleable from "../Base/Toggleable" import Lazy from "../Base/Lazy" import LinkToWeblate from "../Base/LinkToWeblate" import Link from "../Base/Link" import { AllLanguagesSelector } from "../Popup/AllLanguagesSelector" class SelectTemplate extends Combine implements FlowStep<{ title: string; pages: string[] }> { readonly IsValid: Store 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, } }, (_) => 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["error"] !== 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 } let path = window.location.pathname path = path.substring(0, path.lastIndexOf("/")) return window.location.protocol + "//" + window.location.host + path + "/" + spec } } class SelectPdfOptions extends Combine implements FlowStep<{ title: string; pages: string[]; options: SvgToPdfOptions }> { readonly IsValid: Store 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: { 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 readonly Value: Store<{ svgToPdf: SvgToPdf; languages: string[] }> constructor(title: string, pages: string[], options: SvgToPdfOptions) { const svgToPdf = new SvgToPdf(title, pages, options) const languageSelector = new AllLanguagesSelector() const isPrepared = UIEventSource.FromPromiseWithErr(svgToPdf.Prepare()) super([ new Title("Select languages..."), languageSelector, 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 = languageSelector.GetValue().data console.log("Languages are", langs) if (langs.length === 0) { return undefined } return { svgToPdf, languages: langs } } return undefined }, [languageSelector.GetValue()] ) this.IsValid = this.Value.map((v) => v !== undefined) } } class InspectStrings extends Toggle implements FlowStep<{ svgToPdf: SvgToPdf; languages: string[] }> { readonly IsValid: Store readonly Value: Store<{ svgToPdf: SvgToPdf; languages: string[] }> constructor(svgToPdf: SvgToPdf, languages: string[]) { const didLoadLanguages = UIEventSource.FromPromiseWithErr( svgToPdf.PrepareLanguages(languages) ).map((l) => l !== undefined && l["success"] !== undefined) super( new Combine([ new Title("Inspect translation strings"), ...languages.map( (l) => new Lazy(() => InspectStrings.createOverviewPanel(svgToPdf, l)) ), ]), new Loading(), didLoadLanguages ) this.Value = new ImmutableStore({ svgToPdf, languages }) this.IsValid = didLoadLanguages } private static createOverviewPanel(svgToPdf: SvgToPdf, language: string): BaseUIElement { const elements: BaseUIElement[] = [] let foundTranslations = 0 const allKeys = Array.from(svgToPdf.translationKeys()) for (const translationKey of allKeys) { let spec = translationKey if (translationKey.startsWith("layer.")) { spec = "layers:" + translationKey.substring(6) } else { spec = "core:" + translationKey } const translated = svgToPdf.getTranslation("$" + translationKey, language, true) if (translated) { foundTranslations++ } const linkToWeblate = new Link( spec, LinkToWeblate.hrefToWeblate(language, spec), true ).SetClass("font-bold link-underline") elements.push( new Combine([ linkToWeblate, " ", translated ?? new FixedUiElement("No translation found!").SetClass("alert"), ]) ) } return new Toggleable( new Title("Translations for " + language), new Combine([ `${foundTranslations}/${allKeys.length} of translations are found (${Math.floor( (100 * foundTranslations) / allKeys.length )}%)`, "The following keys are used:", new List(elements), ]), { closeOnClick: false, height: "15rem" } ) } } 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) ) .then( "Inspect translations", ({ svgToPdf, languages }) => new InspectStrings(svgToPdf, languages) ) .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) } }