From 1f87728782859d8deff7f92de698cc92dc01a216 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Sat, 17 Sep 2022 21:35:56 +0200 Subject: [PATCH] More work on the flyer --- Utils/svgToPdf.ts | 27 +- assets/templates/MapComplete-flyer.back.svg | 176 +++++++------ assets/templates/MapComplete-flyer.svg | 272 ++++++++++---------- langs/en.json | 2 +- langs/nl.json | 12 +- test.ts | 75 +++--- 6 files changed, 291 insertions(+), 273 deletions(-) diff --git a/Utils/svgToPdf.ts b/Utils/svgToPdf.ts index 6d989096a..0ab5e1a36 100644 --- a/Utils/svgToPdf.ts +++ b/Utils/svgToPdf.ts @@ -12,7 +12,6 @@ import Translations from "../UI/i18n/Translations"; import {Utils} from "../Utils"; import Locale from "../UI/i18n/Locale"; import Constants from "../Models/Constants"; -import {QueryParameters} from "../Logic/Web/QueryParameters"; import Hash from "../Logic/Web/Hash"; class SvgToPdfInternals { @@ -213,13 +212,13 @@ class SvgToPdfInternals { } private extractTranslation(text: string) { + if(text === "$version"){ + return new Date().toISOString().substring("2022-01-02THH:MM".length )+" - v"+Constants.vNumber + } const pathPart = text.match(/\$(([_a-zA-Z0-9?]+\.)+[_a-zA-Z0-9?]+)(.*)/) if (pathPart === null) { return text } - if(text === "$version"){ - return new Date().toISOString()+" "+Constants.vNumber - } let t: any = Translations.t const path = pathPart[1].split(".") if (this._importedTranslations[path[0]]) { @@ -673,7 +672,6 @@ export class SvgToPdfPage { history.replaceState(null, "", "") const state = new FeaturePipelineState(layout) - state.locationControl.addCallbackAndRunD(l => console.trace("Location is",l)) state.locationControl.setData({ zoom, lat: this.options?.overrideLocation?.lat ?? Number(params["lat"] ?? 51.05016), @@ -784,12 +782,15 @@ export class SvgToPdfPage { export class SvgToPdf { public static readonly templates : Record= { - flyer_a4:{pages: ["/assets/templates/MapComplete-flyer.svg","/assets/templates/MapComplete-flyer.back.svg"], description: Translations.t.flyer.description} + flyer_a4:{pages: ["/assets/templates/MapComplete-flyer.svg","/assets/templates/MapComplete-flyer.back.svg"], description: Translations.t.flyer.description}, + poster_a2: {pages: ["/assets/templates/MapComplete-poster-a2.svg"], description: "A basic A2 poster (similar to the flyer)"} } + private readonly _title: string; private readonly _pages: SvgToPdfPage[] - constructor(pages: string[], options?: SvgToPdfOptions) { + constructor(title: string, pages: string[], options?: SvgToPdfOptions) { + this._title = title; options = options ?? {} options.textSubstitutions = options.textSubstitutions ?? {} const mapCount = "" + Array.from(AllKnownLayouts.allKnownLayouts.values()).filter(th => !th.hideFromOverview).length; @@ -799,7 +800,7 @@ export class SvgToPdf { } - public async ConvertSvg(saveAs: string, language: string): Promise { + public async ConvertSvg(language: string): Promise { const firstPage = this._pages[0]._svgRoot const width = SvgToPdfInternals.attrNumber(firstPage, "width") const height = SvgToPdfInternals.attrNumber(firstPage, "height") @@ -812,11 +813,15 @@ export class SvgToPdf { } Locale.language.setData(language) - const doc = new jsPDF(mode) + const doc = new jsPDF(mode, undefined, [width, height]) doc.advancedAPI(advancedApi => { for (let i = 0; i < this._pages.length; i++) { if (i > 0) { - advancedApi.addPage() + const page = this._pages[i]._svgRoot + const width = SvgToPdfInternals.attrNumber(page, "width") + const height = SvgToPdfInternals.attrNumber(page, "height") + + advancedApi.addPage([width, height]) const mediabox: { bottomLeftX: number, bottomLeftY: number, topRightX: number, topRightY: number } = advancedApi.getCurrentPageInfo().pageContext.mediaBox const targetWidth = 297 const targetHeight = 210 @@ -827,7 +832,7 @@ export class SvgToPdf { this._pages[i].drawPage(advancedApi, i, language) } }) - await doc.save(saveAs); + await doc.save(this._title+"."+language+".pdf"); } diff --git a/assets/templates/MapComplete-flyer.back.svg b/assets/templates/MapComplete-flyer.back.svg index 9f2976afd..5aa27cfe0 100644 --- a/assets/templates/MapComplete-flyer.back.svg +++ b/assets/templates/MapComplete-flyer.back.svg @@ -26,9 +26,9 @@ showgrid="false" showguides="true" inkscape:guide-bbox="true" - inkscape:zoom="1.9704628" - inkscape:cx="1058.3808" - inkscape:cy="146.15856" + inkscape:zoom="2.0156476" + inkscape:cx="420.46041" + inkscape:cy="393.42195" inkscape:window-width="1920" inkscape:window-height="1007" inkscape:window-x="0" @@ -59,6 +59,12 @@ + $map(theme:aed,z:14,lat:$map(theme:aed,z:14,lat:51.2098,lon:3.2284) + id="tspan8301">51.2098,lon:3.2284) $flyer.toerisme_vlaanderen + id="tspan8305">$flyer.toerisme_vlaanderen $map(theme:toerisme_vlaandere$map(theme:toerisme_vlaanderen,layers:nonen,layers:none,layer-,layer-charging_station_ebikes:force,lat:charging_station_ebikes:force,lat:50.855250.8552,lon:4.3156, z:10) + id="tspan8325">,lon:4.3156, z:10) $map(theme:cyclofix,z:14,lat:51.05016,lon:$map(theme:cyclofix,z:14,lat:51.05016,lon:3.717842,layers:none,layer-3.717842,layers:none,layer-bike_repair_station:true,layer-bike_repair_station:true,layer-drinking_water:true,layer-drinking_water:true,layer-bike_cafe:true,layer-bike_cafe:true,layer-bicycle_tube_vending_machine: true) + id="tspan8349">bicycle_tube_vending_machine: true) $map(theme:artwork,z:15,lat:51.2098,lon:$map(theme:artwork,z:15,lat:51.2098,lon:3.2284,background:AGIV) + id="tspan8357">3.2284,background:AGIV) $map(theme:cyclestreets,$map(theme:cyclestreets,z:15,lat:51.02802,lon:z:15,lat:51.02702,lon:4.48029, scaling:3) + id="tspan8369">4.48029, scaling:3) + transform="matrix(-1,0,-0.20502864,-1,112.68819,218.91305)"> $map(theme:benches,z:14,lat:51.2098,lon:$map(theme:benches,z:14,lat:51.2098,lon:3.2284, layers:none, layer-bench:force) + id="tspan8377">3.2284, layers:none, layer-bench:force) $flyer.aerial + id="tspan8381">$flyer.aerial $flyer.examples + id="tspan8385">$flyer.examples @@ -726,9 +732,9 @@ style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect5917);fill:#000000;fill-opacity:1;stroke:none">$flyer.lines_too + id="tspan8389">$flyer.lines_too $map(theme:onwheels,z:18,lat:50.86622,lon:$map(theme:onwheels,z:19,lat:50.86622,lon:4.350124.35012,layer-governments:false) + id="tspan8399">,layer-governments:false,layer-parking:false,layer-toilet:false,layer-cafe_pub:false,layer-food:false,scaling:1.5) $flyer.onwheels + id="tspan8411">$flyer.onwheels @@ -790,35 +806,45 @@ $flyer.cyclofix + id="tspan8415">$flyer.cyclofix + $version + id="g7940" + transform="matrix(0.97797993,0.20869898,-0.00818509,1.0207692,100.41144,13.334379)"> + id="g7936"> + id="path7932" /> @@ -857,9 +883,9 @@ style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;white-space:pre;shape-inside:url(#rect135032);fill:#000000;fill-opacity:1;stroke:none">$flyer.title + id="tspan8423">$flyer.title + y="110.0814" /> + y="6.3389206" /> $flyer.frontParagraph + id="tspan9086">$flyer.frontParagraph $flyer.tagline + id="tspan9090">$flyer.tagline $flyer.title + id="tspan9094">$flyer.title $flyer.whatIsOsm + id="tspan9096">$flyer.whatIsOsm $flyer.mapcomplete.title + id="tspan9100">$flyer.mapcomplete.title $flyer.mapcomplete.intro + id="tspan9104">$flyer.mapcomplete.intro + id="tspan9108"> $list(flyer.mapcomplete.li) + id="tspan9112">$list(flyer.mapcomplete.li) + id="tspan9116"> + id="tspan9120"> + id="tspan9124"> + id="tspan9128"> + id="tspan9132"> + id="tspan9136"> + id="tspan9140"> $flyer.mapcomplete.customize + id="tspan9146">$flyer.mapcomplete.customize $flyer.callToAction$flyer.callToAction + id="tspan9150"> + id="tspan9154"> $flyer.osm + id="tspan9158">$flyer.osm $flyer.editing.title + id="tspan9162">$flyer.editing.title $flyer.editing.intro + id="tspan9166">$flyer.editing.intro $import layer.nature_reserve as nr + id="tspan9170">$import layer.nature_reserve as nr $import layer.nature_reserve.tagRenderings.Dogs? as nrd + id="tspan9174">$import layer.nature_reserve.tagRenderings.Dogs? as nrd $import layer.nature_reserve.tagRenderings.Access tag as nra + id="tspan9178">$import layer.nature_reserve.tagRenderings.Access tag as nra $import layer.nature_reserve.tagRenderings.Surface area as nrsa + id="tspan9182">$import layer.nature_reserve.tagRenderings.Surface area as nrsa $import flyer.fakeui as fui + id="tspan9186">$import flyer.fakeui as fui $import general as g + id="tspan9190">$import general as g $set(_surface:ha, 13.2) + id="tspan9194">$set(_surface:ha, 13.2) $image.addPicture + id="tspan9198">$image.addPicture Pieter Vander Vennet $fui.add_images + id="tspan9202">$fui.add_images $fui.see_images + id="tspan9206">$fui.see_images $fui.wikipedia + id="tspan9210">$fui.wikipedia $nra.mappings.0.then + id="tspan9214">$nra.mappings.0.then $nrsa.render + id="tspan9218">$nrsa.render $fui.attributes - - - - - - - - - - + id="tspan9222">$fui.attributes $fui.edit + id="tspan9226">$fui.edit www.example.org/nature + id="tspan9230">www.example.org/nature + transform="matrix(0.7575687,0.65275544,0.65275544,-0.7575687,-208.90773,48.52653)"> $g.wikipedia.fromWikipedia + id="tspan9234">$g.wikipedia.fromWikipedia $fui.question + id="tspan9238">$fui.question + + + + + + + + + $general.save + id="tspan9242">$general.save $nrd.question + id="tspan9246">$nrd.question $nrd.mappings.0.then + id="tspan9250">$nrd.mappings.0.then $nrd.mappings.1.then + id="tspan9254">$nrd.mappings.1.then $nrd.mappings.2.then + id="tspan9258">$nrd.mappings.2.then Skip + id="tspan9262">Skip l.id === "charging_station_ebikes") - bikechargingStationLayer.source.osmTags = new And([new Tag("amenity", "charging_station"), new Tag("bicycle", "yes")]) - Constants.defaultOverpassUrls.splice(0, 1) // remove overpass-api.de for this run - } - - - const svg = await Utils.download(window.location.protocol + "//" + window.location.host + "/assets/templates/MapComplete-flyer.svg") - const svgBack = await Utils.download(window.location.protocol + "//" + window.location.host + "/assets/templates/MapComplete-flyer.back.svg") - - const options = { - getFreeDiv: createElement, - disableMaps: false - } - const front = await new SvgToPdf([svg, svgBack], options) - await front.ConvertSvg("Flyer-nl.pdf", "nl") - await front.ConvertSvg("Flyer-en.pdf", "en") - -} - -class SelectTemplate extends Combine implements FlowStep { +class SelectTemplate extends Combine implements FlowStep<{ title: string, pages: string[] }> { readonly IsValid: Store; - readonly Value: Store; + readonly Value: Store<{ title: string, pages: string[] }>; constructor() { - const elements: InputElement<{ pages: string[] }>[] = [] + const elements: InputElement<{ templateName: string, pages: string[] }>[] = [] for (const templateName in SvgToPdf.templates) { const template = SvgToPdf.templates[templateName] - elements.push(new FixedInputElement(template.description, new UIEventSource(template))) + elements.push(new FixedInputElement( + new Combine([new FixedUiElement(templateName).SetClass("font-bold pr-2"), + template.description + ]) + , new UIEventSource({templateName, pages: template.pages}))) } const radio = new RadioButton(elements, {selectFirstAsDefault: true}) - const loaded: Store<{ success: string[] } | { error: any }> = radio.GetValue().bind(template => { + const loaded: Store<{ success: { title: string, pages: string[] } } | { error: any }> = radio.GetValue().bind(template => { if (template === undefined) { return undefined } const urls = template.pages.map(p => SelectTemplate.ToUrl(p)) - const dloadAll: Promise = Promise.all(urls.map(url => Utils.download(url))) + 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) }) @@ -89,7 +69,7 @@ class SelectTemplate extends Combine implements FlowStep { return new FixedUiElement("Loading preview failed: " + pages["err"]).SetClass("alert") } const els: BaseUIElement[] = [] - for (const pageSrc of pages["success"]) { + for (const pageSrc of pages["success"].pages) { const el = new Img(pageSrc, true) .SetClass("w-96 m-2 border-black border-2") els.push(el) @@ -117,15 +97,15 @@ class SelectTemplate extends Combine implements FlowStep { } -class SelectPdfOptions extends Combine implements FlowStep<{ pages: string[], options: SvgToPdfOptions }> { +class SelectPdfOptions extends Combine implements FlowStep<{ title: string, pages: string[], options: SvgToPdfOptions }> { readonly IsValid: Store; - readonly Value: Store<{ pages: string[], options: SvgToPdfOptions }>; + readonly Value: Store<{ title: string, pages: string[], options: SvgToPdfOptions }>; - constructor(pages: string[], getFreeDiv: () => string) { + 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 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()), @@ -139,6 +119,7 @@ class SelectPdfOptions extends Combine implements FlowStep<{ pages: string[], op this.Value = dummy.GetValue().map((disableMaps) => { return { pages, + title, options: { disableMaps, getFreeDiv, @@ -155,8 +136,8 @@ class PreparePdf extends Combine implements FlowStep<{ svgToPdf: SvgToPdf, langu readonly IsValid: Store; readonly Value: Store<{ svgToPdf: SvgToPdf, languages: string[] }>; - constructor(pages: string[], options: SvgToPdfOptions) { - const svgToPdf = new SvgToPdf(pages, options) + constructor(title: string, pages: string[], options: SvgToPdfOptions) { + const svgToPdf = new SvgToPdf(title, pages, options) const languageOptions = [ new FixedInputElement("Nederlands", "nl"), new FixedInputElement("English", "en") @@ -181,10 +162,13 @@ class PreparePdf extends Combine implements FlowStep<{ svgToPdf: SvgToPdf, langu 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()]) + }, [languages.GetValue()]) this.IsValid = this.Value.map(v => v !== undefined) } @@ -200,7 +184,7 @@ class SavePdf extends Combine { new List(languages.map(lng => new Toggle( lng + " is done!", new Loading("Creating pdf for " + lng), - UIEventSource.FromPromiseWithErr(svgToPdf.ConvertSvg("Template" + "_" + lng + ".pdf", lng).then(() => true)) + UIEventSource.FromPromiseWithErr(svgToPdf.ConvertSvg(lng).then(() => true)) .map(x => x !== undefined && x["success"] === true) ))) ]); @@ -209,10 +193,11 @@ class SavePdf extends Combine { const {flow, furthestStep, titles} = FlowPanelFactory.start( new Title("Select template"), new SelectTemplate() -).then(new Title("Select options"), (pages) => new SelectPdfOptions(pages, createElement)) - .then("Generate maps...", ({pages, options}) => new PreparePdf(pages, options)) +).then(new Title("Select options"), ({title, pages}) => new SelectPdfOptions(title, pages, createElement)) + .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) =>