array( 'local' => true ) ) ); * * and one rule of the theme's style policy (`rooster/assets/plugin_style_policy`), so the theme's engine keeps the * sheet on those pages and withholds it anywhere else (C7 rule 14): * * 'om-portal' => array( 'handles' => array( 'rooster-admin-front' ), 'when' => 'is_account_page' ), * * The screen prints Ui::app() around its root (`data-rooster-ui="/"`) and the builders inside; the * entry noted the copy and loaded the builders (rooster_admin_builders()), so the screen's classes are there. No * chrome.js, no jQuery, no KTUI, no keenicons: the sheet, the builders' markup, the theme's icons (Icons::svg() prints * rooster_icon()'s mask where the theme has the slug). A screen with a table asks for the engine with `table` * (rooster_table_engine()'s options); the page board's moves and a page's windows are their own opt-in components on * the storefront (`board`, `windows`: their sheets and scripts, loaded only here, never in front-app.css). * * @package RoosterAdmin */ defined( 'ABSPATH' ) || exit; if ( ! function_exists( 'rooster_admin_front' ) ) { /** * Registers (the newest copy wins) and enqueues the front application sheet, notes the copy and loads the builders; * returns the sheet's handle. Called on `wp_enqueue_scripts` by the page that is an application page (the sheet in * the head, so the first paint is the final paint); a later call (while the screen renders) is harmless. * * @param string $dir The copy's folder (holds version.php, rooster-admin.php, assets/front-app.css). * @param string $url The copy's URL (a plugin: plugins_url( 'lib/rooster-admin', __FILE__ ); a * theme: get_template_directory_uri() . '/lib/rooster-admin'). * @param array $opts product (the screen's product slug: a board's arrangement and a layout's windows * are the person's per product, saved through the prefs route, where the product * registers itself in its REST context); table (true, or rooster_table_engine()'s * options: the local table's engine and its parts, for a screen that prints a * table); dialogs (true: the storefront's base script, assets/front.js, which * opens and closes the kit's dialogs, `[data-ra-dialog]`); board (true: the page * board's sheet in the head and its moves' script registered, on the base, * enqueued by the first board with something to move); windows (true: a page's * windows: the sheet in the head, the script registered, on the base, enqueued by * the first layout printed). * @return string The sheet's handle, `rooster-admin-front`. */ function rooster_admin_front( string $dir, string $url, array $opts = array() ): string { $handle = 'rooster-admin-front'; $dir = rtrim( str_replace( '\\', '/', $dir ), '/' ); $url = rtrim( $url, '/' ); $read = is_file( $dir . '/version.php' ) ? include $dir . '/version.php' : ''; $ver = is_string( $read ) && 1 === preg_match( '/^\d+\.\d+\.\d+/', $read ) ? $read : '0.0.0'; $min = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; rooster_admin_front_style( $handle, $url . '/assets/front-app' . $min . '.css', $ver, array() ); wp_enqueue_style( $handle ); // The builders: the copy noted (outside the admin the loader notes it and adds no hook), then the classes; on the // storefront the sprite of the icons the theme lacks goes into the footer (Library::builders()). if ( ! function_exists( 'rooster_admin_builders' ) ) { require_once $dir . '/rooster-admin.php'; } rooster_admin_register( $dir, $ver, $url ); rooster_admin_builders(); \Rooster\Admin\Library::front_sprite(); // the icons the theme lacks, in the footer (the builders may be loaded already) if ( '' !== (string) ( $opts['product'] ?? '' ) ) { \Rooster\Admin\Library::front_product( (string) $opts['product'] ); } if ( ! empty( $opts['table'] ) ) { require_once $dir . '/table-engine.php'; rooster_table_engine( $dir, $url, is_array( $opts['table'] ) ? $opts['table'] : array() ); } // The storefront's own base script (0.75.0, assets/front.js, 1 KB): the kit's dialogs open and close, and the // preference save and the live region the board's and the windows' scripts read from the chrome's script in the // admin. Registered with the first component that needs it; enqueued by `dialogs`, else through a component's // script's dependency, so a page with neither never carries it. $base = ! empty( $opts['dialogs'] ) || ! empty( $opts['board'] ) || ! empty( $opts['windows'] ); if ( $base ) { rooster_admin_front_script( $handle, $url . '/assets/front' . $min . '.js', $ver, array() ); if ( ! wp_scripts()->get_data( $handle, 'before' ) ) { // once a request (false, or the data, never an empty array) $data = array( 'rest' => esc_url_raw( rest_url( 'rooster-admin/v1/prefs' ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'product' => (string) ( $opts['product'] ?? '' ), 'failed' => __( 'Not saved', 'ai-eshop-optimizer' ), ); wp_add_inline_script( $handle, 'window.roosterAdminFront = ' . wp_json_encode( $data, JSON_HEX_TAG | JSON_HEX_AMP ) . ';', 'before' ); } if ( ! empty( $opts['dialogs'] ) ) { wp_enqueue_script( $handle ); } } foreach ( array( 'board', 'windows' ) as $part ) { if ( empty( $opts[ $part ] ) ) { continue; } rooster_admin_front_style( $handle . '-' . $part, $url . '/assets/front-' . $part . $min . '.css', $ver, array( $handle ) ); wp_enqueue_style( $handle . '-' . $part ); // in the head: the layout's first paint is its final paint rooster_admin_front_script( 'rooster-admin-' . $part, $url . '/assets/' . $part . $min . '.js', $ver, array( $handle ) ); if ( 'board' === $part ) { require_once $dir . '/includes/Board.php'; // Board::render(), Board::capture(); the builders alone leave it out if ( ! wp_scripts()->get_data( 'rooster-admin-board', 'before' ) ) { wp_add_inline_script( 'rooster-admin-board', 'window.roosterAdminBoardWords = ' . wp_json_encode( \Rooster\Admin\Board::words(), JSON_HEX_TAG | JSON_HEX_AMP ) . ';', 'before' ); } } } return $handle; } } if ( ! function_exists( 'rooster_admin_front_style' ) ) { /** * Registers a sheet of the copy unless a newer copy's is registered under the handle already. * * @param string $handle The handle. * @param string $src The sheet's URL. * @param string $ver The copy's version. * @param array $deps The handles it follows. */ function rooster_admin_front_style( string $handle, string $src, string $ver, array $deps ): void { $known = wp_styles()->query( $handle, 'registered' ); if ( false === $known || version_compare( $ver, (string) $known->ver, '>' ) ) { wp_deregister_style( $handle ); wp_register_style( $handle, $src, $deps, $ver ); } } } if ( ! function_exists( 'rooster_admin_front_script' ) ) { /** * Registers a script of the copy unless a newer copy's is registered under the handle already (deferred, in the * footer: the storefront's scripts attach behaviour, they never draw the first paint). Registration prints nothing: * the builder that needs the script enqueues it where it prints (Board::html(), Ui::windows()). * * @param string $handle The handle. * @param string $src The script's URL. * @param string $ver The copy's version. * @param array $deps The handles it follows. */ function rooster_admin_front_script( string $handle, string $src, string $ver, array $deps ): void { $known = wp_scripts()->query( $handle, 'registered' ); if ( false === $known || version_compare( $ver, (string) $known->ver, '>' ) ) { wp_deregister_script( $handle ); wp_register_script( $handle, $src, $deps, $ver, array( 'in_footer' => true, 'strategy' => 'defer', ) ); } } }