import Helper from "@ember/component/helper" ; import Service from "@ember/service" ; import { data as _data } from "./-css-blocks-data" ; import type { AggregateRewriteData , GlobalBlockIndex , OptimizationEntry , StyleExpression } from "./AggregateRewriteData" ; import { StyleEvaluator } from "./StyleEvaluator" ; import { StyleResolver } from "./StyleResolver" ; const data = _data ; type DebugExpression = string | Array < DebugExpression > ; enum Operator { AND = 1 , OR = 2 , NOT = 3 } interface StyleIdToOptimizationsMap { [ styleId ] : Array < number > ; } export default class CSSBlocksService extends Service { possibleOptimizations ! : StyleIdToOptimizationsMap ; styleNames : { [ name ] : string ; } ; enableDebugMode = false ; static enableTestMode = false ; constructor ( ) { super ( ... arguments ) ; this . possibleOptimizations = getOptimizationInverseMap ( data . optimizations ) ; this . styleNames = getStyleNames ( ) ; } /**
     * Returns the set of classNames to be applied to a given element, based on
     * the runtime conditions that are present on the element. This method is
     * invoked as helper to render classNames in the template files rewritten by
     * css blocks
     */ classNamesFor ( argv ) { if ( this . enableDebugMode ) { console . log ( argv ) ; } let stylesApplied = this . getDirectlyAppliedStyles ( argv ) ; let resolverOutput = this . getImpliedStyles ( stylesApplied ) ; stylesApplied = resolverOutput . stylesApplied ; let classNames = resolverOutput . classNames . concat ( this . getOptimizedStyles ( stylesApplied ) ) ; let result = classNames . join ( " " ) ; if ( this . enableDebugMode ) { console . log ( classNames ) ; } return result ; } /**
     * Uses the style evaluator to return the set of directly applied styleIds
     * based on the arguemnts passed to the elements
     */ getDirectlyAppliedStyles ( argv ) { let styleEvaluator = new StyleEvaluator ( data , argv ) ; let stylesApplied = styleEvaluator . evaluate ( ) ; this . debugStyles ( "directly applied" , stylesApplied ) ; return stylesApplied ; } param stylesApplied getImpliedStyles ( stylesApplied ) { let resolver = new StyleResolver ( data ) ; for ( let style of stylesApplied ) { resolver . addStyle ( style ) ; } if ( this . enableDebugMode ) { this . debugStyles ( "all possible implied styles" , resolver . currentStyles ( ) ) ; } stylesApplied = resolver . resolve ( ) ; this . debugStyles ( "after requirements" , stylesApplied ) ; return { stylesApplied , classNames : new Array < string > ( ... resolver . impliedClassNames ( ) ) , } ; } param stylesApplied getOptimizedStyles ( stylesApplied ) { let classNameIndices = new Set < number > ( ) ; let classNames = new Array < string > ( ) ; for ( let [ clsIdx , expr ] of this . getPossibleOptimizations ( stylesApplied ) ) { if ( evaluateExpression ( expr , stylesApplied ) ) { classNameIndices . add ( clsIdx ) ; } } for ( let idx of classNameIndices ) { classNames . push ( data . outputClassnames [ idx ] ) ; } return classNames ; } /**
     * Returns the list of optimization outputs that mention
     * any style applied to the element.
     */ getPossibleOptimizations ( stylesApplied ) { let optimizations = [ ] ; for ( let style of stylesApplied ) { let possibleOpts = this . possibleOptimizations [ style ] ; if ( possibleOpts ) { optimizations . push ( ... possibleOpts ) ; } } return [ ... new Set ( optimizations ) ] . map ( i => data . optimizations [ i ] ) ; } debugStyles ( msg , stylesApplied ) { if ( ! this . enableDebugMode ) return ; console . log ( msg , this . getStyleNames ( stylesApplied ) ) ; } /**
     * Reverse maps style ids to their style names for debugging.
     * We also use this in test mode to generate human readable styleNames
     */ getStyleNames ( stylesApplied ) { let appliedStyleNames = new Array < string > ( ) ; for ( let s of stylesApplied ) { appliedStyleNames . push ( this . styleNames [ s ] ) ; } return appliedStyleNames ; } debugExpression ( expr ) { if ( typeof expr === "number" ) return ( this . styleNames [ expr ] ) ; let debugExpr = [ ] ; if ( expr [ 0 ] === Operator . AND ) { debugExpr . push ( "AND" ) ; } else if ( expr [ 0 ] === Operator . OR ) { debugExpr . push ( "OR" ) ; } else if ( expr [ 0 ] === Operator . NOT ) { debugExpr . push ( "NOT" ) ; } for ( let i = 1 ; i < expr . length ; i ++ ) { debugExpr . push ( this . debugExpression ( expr [ i ] ) ) ; } return debugExpr ; } } function evaluateExpression ( expr , stylesApplied , stylesApplied2 ? ) { if ( typeof expr === "number" ) return ( stylesApplied . has ( expr ) || ( ! ! stylesApplied2 && stylesApplied2 . has ( expr ) ) ) ; if ( expr [ 0 ] === Operator . AND ) { for ( let i = 1 ; i < expr . length ; i ++ ) { if ( ! evaluateExpression ( expr [ i ] , stylesApplied , stylesApplied2 ) ) return false ; } return true ; } else if ( expr [ 0 ] === Operator . OR ) { for ( let i = 1 ; i < expr . length ; i ++ ) { if ( evaluateExpression ( expr [ i ] , stylesApplied , stylesApplied2 ) ) return true ; } return false ; } else if ( expr [ 0 ] === Operator . NOT ) { return ! evaluateExpression ( expr [ 1 ] , stylesApplied , stylesApplied2 ) ; } else { return false ; } } function getOptimizationInverseMap ( optimizations ) { let inverseMap = { } ; for ( let i = 0 ; i < optimizations . length ; i ++ ) { for ( let styleId of new Set ( extractStyleIds ( optimizations [ i ] [ 1 ] ) ) ) { if ( inverseMap [ styleId ] === undefined ) { inverseMap [ styleId ] = [ ] ; } inverseMap [ styleId ] . push ( i ) ; } } return inverseMap ; } function extractStyleIds ( expr ) { if ( typeof expr === "number" ) { return [ expr ] ; } else { let result = new Array < GlobalBlockIndex > ( ) ; for ( let i = 1 ; i < expr . length ; i ++ ) { result . push ( ... extractStyleIds ( expr [ i ] ) ) ; } return result ; } } export function getStyleNames ( ) { let styleNames = { } ; for ( let blockGuid of Object . keys ( data . blockIds ) ) { let blockIndex = data . blockIds [ blockGuid ] ; let blockInfo = data . blocks [ blockIndex ] ; for ( let name of Object . keys ( blockInfo . blockInterfaceStyles ) ) { let styleIndex = blockInfo . blockInterfaceStyles [ name ] ; let styleId = blockInfo . implementations [ blockIndex ] [ styleIndex ] ! ; styleNames [ styleId ] = `${ blockGuid }${ name }` ; } } return styleNames ; }