Skip to content

Commit

Permalink
Tweak: Adjusting the widgets inline-CSS experiment to support custom-…
Browse files Browse the repository at this point in the history
…breakpoints [ED-5536] (elementor#16922)
  • Loading branch information
rotemee authored Nov 18, 2021
1 parent 42419ec commit 55dc260
Show file tree
Hide file tree
Showing 8 changed files with 308 additions and 26 deletions.
103 changes: 88 additions & 15 deletions .grunt-config/widgets-css.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,40 @@ class WidgetsCss {
constructor( env ) {
this.env = env;

// List of widgets that uses media queries.
this.responsiveWidgets;

// List of the widgets .scss files.
this.widgetsScssFilesList;

// Can't be empty.
this.tempFilePrefix = 'widget-';
this.cssFilePrefix = 'widget-';

// The widgets .scss files source folder.
this.sourceScssFolder = path.resolve( __dirname, '../assets/dev/scss/frontend/widgets' );

// Temporary SCSS files are created and eventually transpiled into production CSS files.
this.tempScssFolder = path.resolve( __dirname, '../assets/dev/scss/direction' );

// The templates folder of files that are used when using custom-breakpoints.
this.templatesCssFolder = path.resolve( __dirname, '../assets/css/templates' );

// The destination folder of the generated widgets .css files.
this.cssDestinationFolder = path.resolve( __dirname, '../assets/css' );
}

createWidgetsTempScssFiles() {
if ( fs.existsSync( this.sourceScssFolder ) ) {
fs.readdirSync( this.sourceScssFolder ).forEach( ( fileName ) => {
const widgetName = fileName.replace( '.scss', '' ),
widgetScssFileDest = path.join( this.tempScssFolder, this.tempFilePrefix + fileName ),
widgetScssRtlFileDest = path.join( this.tempScssFolder, this.tempFilePrefix + fileName.replace( '.scss', '-rtl.scss' ) );

write.sync( widgetScssFileDest, this.getWidgetScssContent( widgetName, 'ltr' ) );
write.sync( widgetScssRtlFileDest, this.getWidgetScssContent( widgetName, 'rtl' ) );
} );
}
const widgetsCssFilesList = this.getWidgetsCssFilesList();

widgetsCssFilesList.forEach( ( filename ) => {
const widgetName = filename.replace( '.scss', '' ),
{ defaultFilename, rtlFilename } = this.getCssFileNames( filename ),
widgetScssFileDest = path.join( this.tempScssFolder, defaultFilename ),
widgetScssRtlFileDest = path.join( this.tempScssFolder, rtlFilename );

write.sync( widgetScssFileDest, this.getWidgetScssContent( widgetName, 'ltr' ) );
write.sync( widgetScssRtlFileDest, this.getWidgetScssContent( widgetName, 'rtl' ) );
} );
}

getWidgetScssContent( widgetName, direction ) {
Expand All @@ -50,15 +62,15 @@ class WidgetsCss {
}

removeWidgetsUnusedStyleFiles() {
const tempFilesFolders = [ this.tempScssFolder, this.cssDestinationFolder ];
const tempFilesFolders = [ this.tempScssFolder, this.cssDestinationFolder, this.templatesCssFolder ];

tempFilesFolders.forEach( ( folder ) => {
if ( fs.existsSync( folder ) ) {
fs.readdirSync( folder ).forEach( ( fileName ) => {
const filePath = path.join( folder, fileName );
fs.readdirSync( folder ).forEach( ( filename ) => {
const filePath = path.join( folder, filename );

// In the assets/css folder we should only keep the .min.css files because the widgets CSS conditional loading is active only in production mode.
if ( 0 === fileName.indexOf( this.tempFilePrefix ) && fs.existsSync( filePath ) && -1 === fileName.indexOf( '.min.css' ) ) {
if ( 0 === filename.indexOf( this.cssFilePrefix ) && fs.existsSync( filePath ) && -1 === filename.indexOf( '.min.css' ) ) {
fs.unlink( filePath, err => {
if ( err ) throw err;
} );
Expand All @@ -67,6 +79,67 @@ class WidgetsCss {
}
} );
}

getCssFileNames( filename ) {
return {
defaultFilename: this.cssFilePrefix + filename,
rtlFilename: this.cssFilePrefix + filename.replace( '.scss', '-rtl.scss' ),
};
}

getWidgetsCssFilesList() {
if ( Array.isArray( this.widgetsScssFilesList ) ) {
return this.widgetsScssFilesList;
}

this.widgetsScssFilesList = fs.existsSync( this.sourceScssFolder ) ? fs.readdirSync( this.sourceScssFolder ) : [];

return this.widgetsScssFilesList;
}

getResponsiveWidgetsList() {
if ( Array.isArray( this.responsiveWidgets ) ) {
return this.responsiveWidgets;
}

this.responsiveWidgets = [];

const widgetsCssFilesList = this.getWidgetsCssFilesList();

widgetsCssFilesList.forEach( ( filename ) => {
const widgetSourceFilePath = path.join( this.sourceScssFolder, filename ),
{ defaultFilename, rtlFilename } = this.getCssFileNames( filename ),
fileContent = fs.readFileSync( widgetSourceFilePath ).toString();

// Collecting all widgets .scss files that has @media queries in order to create templates files for custom breakpoints.
if ( fileContent.indexOf( '@media' ) > -1 ) {
this.responsiveWidgets.push( defaultFilename, rtlFilename );
}
} );

this.createResponsiveWidgetsJson( this.responsiveWidgets );

return this.responsiveWidgets;
}

createResponsiveWidgetsJson( responsiveWidgets ) {
const responsiveWidgetsJsonFolder = path.resolve( __dirname, '../assets/data' ),
responsiveWidgetsJsonPath = path.join( responsiveWidgetsJsonFolder, 'responsive-widgets.json' ),
responsiveWidgetsObject = responsiveWidgets.reduce( ( obj, val ) => {
// No need to save also the -rtl key.
if ( val.indexOf( '-rtl' ) > -1 ) {
return obj;
}

val = val.replace( this.cssFilePrefix, '' ).replace( '.scss', '' );

return { ...obj, [ val ]: true };
}, {} );

// Breaking the line for the linter that throws a warning.
write.sync( responsiveWidgetsJsonPath, JSON.stringify( responsiveWidgetsObject ) + `
` );
}
}

module.exports = WidgetsCss;
3 changes: 3 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ module.exports = function( grunt ) {
grunt.registerTask( 'css_templates', () => {
grunt.task.run( 'css_templates_proxy:templates' );

const responsiveWidgetsList = widgetsCss.getResponsiveWidgetsList();

grunt.config( 'sass.dist', {
files: [ {
expand: true,
Expand All @@ -99,6 +101,7 @@ module.exports = function( grunt ) {
'frontend-lite-rtl.scss',
'frontend-legacy.scss',
'frontend-legacy-rtl.scss',
...responsiveWidgetsList,
],
dest: 'assets/css/templates',
ext: '.css',
Expand Down
1 change: 1 addition & 0 deletions assets/data/responsive-widgets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"accordion":true,"alert":true,"icon-box":true,"icon-list":true,"image-box":true,"image-gallery":true,"progress":true,"star-rating":true,"tabs":true,"toggle":true}
24 changes: 24 additions & 0 deletions core/breakpoints/manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ class Manager extends Module {

private $icons_map;

/**
* Has Custom Breakpoints
*
* A flag that holds a cached value that indicates if there are active custom-breakpoints.
*
* @since 3.5.0
* @access private
*
* @var boolean
*/
private $has_custom_breakpoints;

public function get_name() {
return 'breakpoints';
}
Expand Down Expand Up @@ -157,6 +169,10 @@ public function get_active_devices_list( $args = [] ) {
* @return boolean
*/
public function has_custom_breakpoints() {
if ( isset( $this->has_custom_breakpoints ) ) {
return $this->has_custom_breakpoints;
}

$breakpoints = $this->get_active_breakpoints();

$additional_breakpoints = [
Expand All @@ -168,15 +184,21 @@ public function has_custom_breakpoints() {

foreach ( $breakpoints as $breakpoint_name => $breakpoint ) {
if ( in_array( $breakpoint_name, $additional_breakpoints, true ) ) {
$this->has_custom_breakpoints = true;

return true;
}

/** @var Breakpoint $breakpoint */
if ( $breakpoint->is_custom() ) {
$this->has_custom_breakpoints = true;

return true;
}
}

$this->has_custom_breakpoints = false;

return false;
}

Expand Down Expand Up @@ -241,6 +263,8 @@ public function get_desktop_min_point() {
}

public function refresh() {
unset( $this->has_custom_breakpoints );

$this->init_breakpoints();
$this->init_active_breakpoints();
}
Expand Down
14 changes: 14 additions & 0 deletions core/files/base.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,20 @@ public function get_url() {
return add_query_arg( [ 'ver' => $this->get_meta( 'time' ) ], $url );
}

/**
* Get Path
*
* Returns the local path of the generated file.
*
* @since 3.5.0
* @access public
*
* @return string
*/
public function get_path() {
return set_url_scheme( self::get_base_uploads_dir() . $this->files_dir . $this->file_name );
}

/**
* @since 2.1.0
* @access public
Expand Down
31 changes: 31 additions & 0 deletions core/page-assets/data-managers/responsive-widgets.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
namespace Elementor\Core\Page_Assets\Data_Managers;

if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}

/**
* Elementor Responsive Widgets Data.
*
* @since 3.5.0
*/
class Responsive_Widgets extends Base {
const RESPONSIVE_WIDGETS_DATABASE_KEY = 'responsive-widgets';

const RESPONSIVE_WIDGETS_FILE_PATH = 'data/responsive-widgets.json';

protected $content_type = 'json';

protected $assets_category = 'widgets';

protected function get_asset_content() {
$data = $this->get_file_data( 'content' );

if ( $data ) {
$data = json_decode( $data, true );
}

return $data;
}
}
81 changes: 78 additions & 3 deletions includes/base/widget-base.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace Elementor;

use Elementor\Core\Page_Assets\Data_Managers\Responsive_Widgets as Responsive_Widgets_Data_Manager;
use Elementor\Core\Page_Assets\Data_Managers\Widgets_Css as Widgets_Css_Data_Manager;

if ( ! defined( 'ABSPATH' ) ) {
Expand Down Expand Up @@ -50,6 +51,8 @@ abstract class Widget_Base extends Element_Base {

private static $widgets_css_data_manager;

private static $responsive_widgets_data_manager;

/**
* Get element type.
*
Expand Down Expand Up @@ -1005,14 +1008,22 @@ public function register_runtime_widget( $widget_name ) {
public function get_widget_css_config( $widget_name ) {
$direction = is_rtl() ? '-rtl' : '';

$css_file_path = 'css/widget-' . $widget_name . $direction . '.min.css';
$has_custom_breakpoints = $this->is_custom_breakpoints_widget();

$file_name = 'widget-' . $widget_name . $direction . '.min.css';

// The URL of the widget's external CSS file that is loaded in case that the CSS content is too large to be printed inline.
$file_url = Plugin::$instance->frontend->get_frontend_file_url( $file_name, $has_custom_breakpoints );

// The local path of the widget's CSS file that is being read and saved in the DB when the CSS content should be printed inline.
$file_path = Plugin::$instance->frontend->get_frontend_file_path( $file_name, $has_custom_breakpoints );

return [
'key' => $widget_name,
'version' => ELEMENTOR_VERSION,
'file_path' => ELEMENTOR_ASSETS_PATH . $css_file_path,
'file_path' => $file_path,
'data' => [
'file_url' => ELEMENTOR_ASSETS_URL . $css_file_path,
'file_url' => $file_url,
],
];
}
Expand All @@ -1021,6 +1032,70 @@ public function get_css_config() {
return $this->get_widget_css_config( $this->get_group_name() );
}

public function get_responsive_widgets_config() {
$responsive_widgets_data_manager = $this->get_responsive_widgets_data_manager();

return [
'key' => $responsive_widgets_data_manager::RESPONSIVE_WIDGETS_DATABASE_KEY,
'version' => ELEMENTOR_VERSION,
'file_path' => ELEMENTOR_ASSETS_PATH . $responsive_widgets_data_manager::RESPONSIVE_WIDGETS_FILE_PATH,
];
}

public function get_responsive_widgets() {
$responsive_widgets_data_manager = $this->get_responsive_widgets_data_manager();

$config = $this->get_responsive_widgets_config();

return $responsive_widgets_data_manager->get_asset_data_from_config( $config );
}

/**
* Get Responsive Widgets Data Manager.
*
* Retrieve the data manager that handles widgets that are using media queries for custom-breakpoints values.
*
* @since 3.5.0
* @access protected
*
* @return Responsive_Widgets_Data_Manager
*/
protected function get_responsive_widgets_data_manager() {
if ( ! self::$responsive_widgets_data_manager ) {
self::$responsive_widgets_data_manager = new Responsive_Widgets_Data_Manager();
}

return self::$responsive_widgets_data_manager;
}

/**
* Is Custom Breakpoints Widget.
*
* Checking if there are active custom-breakpoints and if the widget use them.
*
* @since 3.5.0
* @access protected
*
* @return boolean
*/
protected function is_custom_breakpoints_widget() {
$has_custom_breakpoints = Plugin::$instance->breakpoints->has_custom_breakpoints();

if ( $has_custom_breakpoints ) {
$responsive_widgets = $this->get_responsive_widgets();

// The $widget_name can also represents a widgets group name, therefore we need to use the current widget name to check if it's responsive widget.
$current_widget_name = $this->get_name();

// If the widget is not implementing custom-breakpoints media queries then it has no custom- css file.
if ( ! isset( $responsive_widgets[ $current_widget_name ] ) ) {
$has_custom_breakpoints = false;
}
}

return $has_custom_breakpoints;
}

private function get_widget_css() {
$widgets_css_data_manager = $this->get_widgets_css_data_manager();

Expand Down
Loading

0 comments on commit 55dc260

Please sign in to comment.