2022-02-09 22:37:21 +01:00
import { Conversion , DesugaringStep } from "./Conversion"
import { LayoutConfigJson } from "../Json/LayoutConfigJson"
import { Utils } from "../../../Utils"
2023-02-08 01:14:21 +01:00
import metapaths from "../../../assets/layoutconfigmeta.json"
import tagrenderingmetapaths from "../../../assets/questionabletagrenderingconfigmeta.json"
2022-02-28 17:17:38 +01:00
import Translations from "../../../UI/i18n/Translations"
2022-02-09 22:37:21 +01:00
2023-02-03 03:57:30 +01:00
export class ExtractImages extends Conversion <
LayoutConfigJson ,
{ path : string ; context : string } [ ]
> {
2022-02-14 22:21:01 +01:00
private _isOfficial : boolean
2023-02-03 03:57:30 +01:00
private _sharedTagRenderings : Set < string >
2022-02-19 02:45:15 +01:00
2023-02-08 01:14:21 +01:00
private static readonly layoutMetaPaths = metapaths . filter (
2022-09-08 21:40:48 +02:00
( mp ) = >
2023-02-08 01:14:21 +01:00
ExtractImages . mightBeTagRendering ( < any > mp ) ||
2023-02-09 00:10:59 +01:00
( mp . typeHint !== undefined &&
( mp . typeHint === "image" ||
mp . typeHint === "icon" ||
mp . typeHint === "image[]" ||
mp . typeHint === "icon[]" ) )
2022-09-08 21:40:48 +02:00
)
2023-02-08 01:14:21 +01:00
private static readonly tagRenderingMetaPaths = tagrenderingmetapaths
2022-02-19 02:45:15 +01:00
2023-02-03 03:57:30 +01:00
constructor ( isOfficial : boolean , sharedTagRenderings : Set < string > ) {
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-09-08 21:40:48 +02:00
2023-02-08 01:14:21 +01:00
public static mightBeTagRendering ( metapath : { type ? : string | string [ ] } ) : boolean {
2022-02-28 17:17:38 +01:00
if ( ! Array . isArray ( metapath . type ) ) {
return false
}
2023-02-08 01:14:21 +01:00
return (
metapath . type ? . some (
( t ) = >
t [ "$ref" ] == "#/definitions/TagRenderingConfigJson" ||
t [ "$ref" ] == "#/definitions/QuestionableTagRenderingConfigJson"
) ? ? false
2022-02-28 17:17:38 +01:00
)
}
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" ,
* }
* ]
* }
* ]
* }
* ]
2023-02-10 14:28:00 +01:00
* } , "test" ) . result . map ( i = > i . path ) ;
2022-03-24 03:11:52 +01:00
* images . length // => 2
2023-02-10 14:28:00 +01:00
* images . findIndex ( img = > img == "./assets/layers/bike_parking/staple.svg" ) >= 0 // => true
* images . findIndex ( img = > img == "./assets/layers/bike_parking/bollard.svg" ) >= 0 // => true
2022-03-24 03:11:52 +01:00
*
* // should not pickup rotation, should drop color
2023-02-10 14:28:00 +01:00
* const images = new ExtractImages ( true , new Set < string > ( ) ) . convert ( < any > { "layers" : [ { mapRendering : [ { "location" : [ "point" , "centroid" ] , "icon" : "pin:black" , rotation : 180 , iconSize : "40,40,center" } ] } ]
2022-03-24 03:11:52 +01:00
* } , "test" ) . result
* images . length // => 1
2023-02-10 14:28:00 +01:00
* images [ 0 ] . path // => "pin"
2022-03-23 19:48:06 +01:00
*
* /
2022-02-14 22:21:01 +01:00
convert (
json : LayoutConfigJson ,
context : string
2023-02-03 03:57:30 +01:00
) : { result : { path : string ; context : string } [ ] ; errors : string [ ] ; warnings : string [ ] } {
const allFoundImages : { path : string ; context : 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 ) {
2023-02-08 01:14:21 +01:00
const mightBeTr = ExtractImages . mightBeTagRendering ( < any > metapath )
2022-02-28 17:17:38 +01:00
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" ) {
if ( ! allRenderedValuesAreImages ) {
continue
}
2022-09-08 21:40:48 +02:00
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
}
2022-09-08 21:40:48 +02:00
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-09-08 21:40:48 +02:00
2023-02-03 03:57:30 +01:00
allFoundImages . push ( { path : foundImage , context : context + "." + path } )
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 "
2022-09-08 21:40:48 +02:00
)
2022-02-28 17:17:38 +01:00
} 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 )
2022-09-08 21:40:48 +02:00
)
2022-02-28 17:17:38 +01:00
} else {
2023-02-03 03:57:30 +01:00
allFoundImages . push ( {
path : img.leaf ,
context : context + "." + path ,
} )
2022-02-28 17:17:38 +01:00
}
}
if ( ! allRenderedValuesAreImages && isImage ) {
// Extract images from the translations
allFoundImages . push (
. . . Translations . T (
img . leaf ,
"extract_images from " + img . path . join ( "." )
2023-02-03 03:57:30 +01:00
)
. ExtractImages ( false )
. map ( ( path ) = > ( {
path ,
context : context + "." + path ,
} ) )
2022-09-08 21:40:48 +02:00
)
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"
2022-09-08 21:40:48 +02:00
)
2022-02-19 17:39:16 +01:00
continue
}
2023-02-09 00:10:59 +01:00
if ( typeof foundElement . leaf !== "string" ) {
continue
}
allFoundImages . push ( {
context : context + "." + foundElement . path . join ( "." ) ,
path : foundElement.leaf ,
} )
2022-02-19 17:39:16 +01:00
}
2022-02-09 22:37:21 +01:00
}
}
2023-02-03 03:57:30 +01:00
const cleanedImages : { path : string ; context : string } [ ] = [ ]
for ( const foundImage of allFoundImages ) {
// Split "circle:white;./assets/layers/.../something.svg" into ["circle", "./assets/layers/.../something.svg"]
const allPaths = Utils . NoNull (
Utils . NoEmpty ( foundImage . path ? . split ( ";" ) ? . map ( ( part ) = > part . split ( ":" ) [ 0 ] ) )
2022-09-08 21:40:48 +02:00
)
2023-02-03 03:57:30 +01:00
for ( const path of allPaths ) {
cleanedImages . push ( { path , context : foundImage.context } )
}
}
return { result : cleanedImages , 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-09-08 21:40:48 +02:00
)
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-09-08 21:40:48 +02:00
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-09-08 21:40:48 +02:00
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 )
2022-09-08 21:40:48 +02:00
)
2022-02-28 20:49:48 +01:00
return leaf
}
2022-09-08 21:40:48 +02:00
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 )
2023-02-08 01:14:21 +01:00
for ( const metapath of metapaths ) {
2022-02-09 22:37:21 +01:00
if ( metapath . typeHint !== "image" && metapath . typeHint !== "icon" ) {
continue
}
2023-02-08 01:14:21 +01:00
const mightBeTr = ExtractImages . mightBeTagRendering ( < any > 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
2023-02-08 01:14:21 +01:00
for ( const trpath of tagrenderingmetapaths ) {
2022-02-09 22:37:21 +01:00
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 ,
}
}
}