2022-02-09 22:37:21 +01:00
import { Conversion , DesugaringStep } from "./Conversion" ;
import { LayoutConfigJson } from "../Json/LayoutConfigJson" ;
import { Utils } from "../../../Utils" ;
import * as metapaths from "../../../assets/layoutconfigmeta.json" ;
2022-02-28 17:17:38 +01:00
import * as tagrenderingmetapaths from "../../../assets/questionabletagrenderingconfigmeta.json" ;
import Translations from "../../../UI/i18n/Translations" ;
2022-02-09 22:37:21 +01:00
export class ExtractImages extends Conversion < LayoutConfigJson , string [ ] > {
2022-02-14 22:21:01 +01:00
private _isOfficial : boolean ;
2022-02-18 23:10:27 +01:00
private _sharedTagRenderings : Map < string , any > ;
2022-02-19 02:45:15 +01:00
2022-02-28 17:17:38 +01:00
private static readonly layoutMetaPaths = ( metapaths [ "default" ] ? ? metapaths )
. filter ( mp = > ( ExtractImages . mightBeTagRendering ( mp ) ) || mp . typeHint !== undefined && ( mp . typeHint === "image" || mp . typeHint === "icon" ) )
private static readonly tagRenderingMetaPaths = ( tagrenderingmetapaths [ "default" ] ? ? tagrenderingmetapaths )
2022-02-19 02:45:15 +01:00
2022-02-18 23:10:27 +01:00
constructor ( isOfficial : boolean , sharedTagRenderings : Map < string , any > ) {
2022-02-28 17:17:38 +01:00
super ( "Extract all images from a layoutConfig using the meta paths." , [ ] , "ExctractImages" ) ;
2022-02-14 22:21:01 +01:00
this . _isOfficial = isOfficial ;
2022-02-18 23:10:27 +01:00
this . _sharedTagRenderings = sharedTagRenderings ;
2022-02-09 22:37:21 +01:00
}
2022-02-28 17:17:38 +01:00
public static mightBeTagRendering ( metapath : { type : string | string [ ] } ) : boolean {
if ( ! Array . isArray ( metapath . type ) ) {
return false
}
return metapath . type . some ( t = >
t [ "$ref" ] == "#/definitions/TagRenderingConfigJson" || t [ "$ref" ] == "#/definitions/QuestionableTagRenderingConfigJson" )
}
2022-02-09 22:37:21 +01:00
2022-03-23 19:48:06 +01:00
/ * *
2022-03-24 03:11:52 +01:00
* const images = new ExtractImages ( true , new Map < string , any > ( ) ) . convert ( < any > {
* "layers" : [
* {
* tagRenderings : [
* {
* "mappings" : [
* {
* "if" : "bicycle_parking=stands" ,
* "then" : {
* "en" : "Staple racks" ,
* } ,
* "icon" : {
* path : "./assets/layers/bike_parking/staple.svg" ,
* class : "small"
* }
* } ,
* {
* "if" : "bicycle_parking=stands" ,
* "then" : {
* "en" : "Bollard" ,
* } ,
* "icon" : "./assets/layers/bike_parking/bollard.svg" ,
* }
* ]
* }
* ]
* }
* ]
* } , "test" ) . result ;
* images . length // => 2
* images . findIndex ( img = > img == "./assets/layers/bike_parking/staple.svg" ) // => 0
* images . findIndex ( img = > img == "./assets/layers/bike_parking/bollard.svg" ) // => 1
*
* // should not pickup rotation, should drop color
* const images = new ExtractImages ( true , new Map < string , any > ( ) ) . convert ( < any > { "layers" : [ { mapRendering : [ { "location" : [ "point" , "centroid" ] , "icon" : "pin:black" , rotation : 180 , iconSize : "40,40,center" } ] } ]
* } , "test" ) . result
* images . length // => 1
* images [ 0 ] // => "pin"
2022-03-23 19:48:06 +01:00
*
* /
2022-02-14 22:21:01 +01:00
convert ( json : LayoutConfigJson , context : string ) : { result : string [ ] , errors : string [ ] , warnings : string [ ] } {
2022-02-19 17:39:16 +01:00
const allFoundImages : string [ ] = [ ]
2022-02-11 19:56:31 +01:00
const errors = [ ]
2022-02-14 22:21:01 +01:00
const warnings = [ ]
2022-02-19 02:45:15 +01:00
for ( const metapath of ExtractImages . layoutMetaPaths ) {
2022-02-28 17:17:38 +01:00
const mightBeTr = ExtractImages . mightBeTagRendering ( metapath )
const allRenderedValuesAreImages = metapath . typeHint === "icon" || metapath . typeHint === "image"
2022-02-09 22:37:21 +01:00
const found = Utils . CollectPath ( metapath . path , json )
if ( mightBeTr ) {
// We might have tagRenderingConfigs containing icons here
2022-02-19 02:45:15 +01:00
for ( const el of found ) {
const path = el . path
const foundImage = el . leaf ;
2022-02-28 17:17:38 +01:00
if ( typeof foundImage === "string" ) {
2022-02-18 23:10:27 +01:00
2022-02-28 17:17:38 +01:00
if ( ! allRenderedValuesAreImages ) {
continue
}
2022-02-18 23:10:27 +01:00
if ( foundImage == "" ) {
2022-02-19 17:39:16 +01:00
warnings . push ( context + "." + path . join ( "." ) + " Found an empty image" )
2022-02-18 23:10:27 +01:00
}
if ( this . _sharedTagRenderings ? . has ( foundImage ) ) {
// This is not an image, but a shared tag rendering
2022-02-28 17:17:38 +01:00
// At key positions for checking, they'll be expanded already, so we can safely ignore them here
2022-02-18 23:10:27 +01:00
continue
}
2022-02-09 22:37:21 +01:00
allFoundImages . push ( foundImage )
2022-02-17 23:54:14 +01:00
} else {
2022-02-28 17:17:38 +01:00
// This is a tagRendering.
// Either every rendered value might be an icon
// or -in the case of a normal tagrendering- only the 'icons' in the mappings have an icon (or exceptionally an '<img>' tag in the translation
2022-02-19 02:45:15 +01:00
for ( const trpath of ExtractImages . tagRenderingMetaPaths ) {
2022-02-28 17:17:38 +01:00
// Inspect all the rendered values
2022-02-11 19:56:31 +01:00
const fromPath = Utils . CollectPath ( trpath . path , foundImage )
2022-02-28 17:17:38 +01:00
const isRendered = trpath . typeHint === "rendered"
const isImage = trpath . typeHint === "icon" || trpath . typeHint === "image"
2022-02-11 19:56:31 +01:00
for ( const img of fromPath ) {
2022-02-28 17:17:38 +01:00
if ( allRenderedValuesAreImages && isRendered ) {
// What we found is an image
if ( img . leaf === "" || img . leaf [ "path" ] == "" ) {
warnings . push ( context + [ . . . path , . . . img . path ] . join ( "." ) + ": Found an empty image at " )
} else if ( typeof img . leaf !== "string" ) {
( this . _isOfficial ? errors : warnings ) . push ( context + "." + img . path . join ( "." ) + ": found an image path that is not a string: " + JSON . stringify ( img . leaf ) )
} else {
allFoundImages . push ( img . leaf )
}
}
if ( ! allRenderedValuesAreImages && isImage ) {
// Extract images from the translations
allFoundImages . push ( . . . ( Translations . T ( img . leaf , "extract_images from " + img . path . join ( "." ) ) . ExtractImages ( false ) ) )
2022-02-18 23:10:27 +01:00
}
}
2022-02-09 22:37:21 +01:00
}
2022-02-28 17:17:38 +01:00
}
2022-02-09 22:37:21 +01:00
}
} else {
2022-02-19 17:39:16 +01:00
for ( const foundElement of found ) {
if ( foundElement . leaf === "" ) {
warnings . push ( context + "." + foundElement . path . join ( "." ) + " Found an empty image" )
continue
}
allFoundImages . push ( foundElement . leaf )
}
2022-02-09 22:37:21 +01:00
}
}
2022-02-17 23:54:14 +01:00
const splitParts = [ ] . concat ( . . . Utils . NoNull ( allFoundImages )
. map ( img = > img [ "path" ] ? ? img )
. map ( img = > img . split ( ";" ) ) )
2022-02-09 22:37:21 +01:00
. map ( img = > img . split ( ":" ) [ 0 ] )
2022-02-19 17:39:16 +01:00
. filter ( img = > img !== "" )
2022-02-14 22:21:01 +01:00
return { result : Utils.Dedup ( splitParts ) , errors , warnings } ;
2022-02-09 22:37:21 +01:00
}
}
export class FixImages extends DesugaringStep < LayoutConfigJson > {
private readonly _knownImages : Set < string > ;
constructor ( knownImages : Set < string > ) {
2022-02-14 02:26:03 +01:00
super ( "Walks over the entire theme and replaces images to the relative URL. Only works if the ID of the theme is an URL" , [ ] , "fixImages" ) ;
2022-02-09 22:37:21 +01:00
this . _knownImages = knownImages ;
}
2022-03-23 19:48:06 +01:00
/ * *
* If the id is an URL to a json file , replaces "./" in images with the path to the json file
*
* const theme = {
* "id" : "https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/verkeerdeborden.json"
* "layers" : [
* {
* "mapRendering" : [
* {
* "icon" : "./TS_bolt.svg" ,
* iconBadges : [ {
* if : "id=yes" ,
* then : {
* mappings : [
* {
* if : "id=yes" ,
* then : "./Something.svg"
* }
* ]
* }
* } ] ,
* "location" : [
* "point" ,
* "centroid"
* ]
* }
* ]
* }
* ] ,
* }
* const fixed = new FixImages ( new Set < string > ( ) ) . convert ( < any > theme , "test" ) . result
* fixed . layers [ 0 ] [ "mapRendering" ] [ 0 ] . icon // => "https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/TS_bolt.svg"
* fixed . layers [ 0 ] [ "mapRendering" ] [ 0 ] . iconBadges [ 0 ] . then . mappings [ 0 ] . then // => "https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/Something.svg"
* /
2022-02-28 20:58:12 +01:00
convert ( json : LayoutConfigJson , context : string ) : { result : LayoutConfigJson , warnings? : string [ ] } {
2022-02-09 22:37:21 +01:00
let url : URL ;
try {
url = new URL ( json . id )
} catch ( e ) {
// Not a URL, we don't rewrite
return { result : json }
}
2022-02-28 20:58:12 +01:00
const warnings : string [ ] = [ ]
2022-02-09 22:37:21 +01:00
const absolute = url . protocol + "//" + url . host
let relative = url . protocol + "//" + url . host + url . pathname
relative = relative . substring ( 0 , relative . lastIndexOf ( "/" ) )
const self = this ;
2022-06-21 16:47:54 +02:00
if ( relative . endsWith ( "assets/generated/themes" ) ) {
warnings . push ( "Detected 'assets/generated/themes' as relative URL. I'm assuming that you are loading your file for the MC-repository, so I'm rewriting all image links as if they were absolute instead of relative" )
relative = absolute
}
2022-02-09 22:37:21 +01:00
function replaceString ( leaf : string ) {
if ( self . _knownImages . has ( leaf ) ) {
return leaf ;
}
2022-02-28 20:49:48 +01:00
if ( typeof leaf !== "string" ) {
warnings . push ( "Found a non-string object while replacing images: " + JSON . stringify ( leaf ) )
return leaf ;
}
2022-02-09 22:37:21 +01:00
if ( leaf . startsWith ( "./" ) ) {
return relative + leaf . substring ( 1 )
}
if ( leaf . startsWith ( "/" ) ) {
return absolute + leaf
}
return leaf ;
}
json = Utils . Clone ( json )
let paths = metapaths [ "default" ] ? ? metapaths
let trpaths = tagrenderingmetapaths [ "default" ] ? ? tagrenderingmetapaths
for ( const metapath of paths ) {
if ( metapath . typeHint !== "image" && metapath . typeHint !== "icon" ) {
continue
}
2022-02-28 17:17:38 +01:00
const mightBeTr = ExtractImages . mightBeTagRendering ( metapath )
2022-02-18 23:10:27 +01:00
Utils . WalkPath ( metapath . path , json , ( leaf , path ) = > {
2022-02-09 22:37:21 +01:00
if ( typeof leaf === "string" ) {
return replaceString ( leaf )
}
if ( mightBeTr ) {
// We might have reached a tagRenderingConfig containing icons
// lets walk every rendered value and fix the images in there
for ( const trpath of trpaths ) {
if ( trpath . typeHint !== "rendered" ) {
continue
}
Utils . WalkPath ( trpath . path , leaf , ( rendered = > {
return replaceString ( rendered )
} ) )
}
}
return leaf ;
} )
}
return {
2022-02-28 20:49:48 +01:00
warnings ,
2022-02-09 22:37:21 +01:00
result : json
} ;
}
}