menu_position() ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); } /** * Register the submenu page under Albert. * * @return void * @since 1.4.0 */ public function add_menu_page(): void { add_submenu_page( Menu::PARENT_SLUG, $this->screen_title(), $this->screen_title(), 'manage_options', static::PAGE_SLUG, [ $this, 'render_page' ] ); } /** * Render the admin page. * * Renders the React mount point, or an actionable notice when an * environment requirement is unmet or the compiled bundle is missing (a * dev checkout that hasn't run the build). * * @return void * @since 1.4.0 */ public function render_page(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'albert-ai-butler' ) ); } echo '
npm install && npm run build',
$this->screen_title()
)
);
} else {
printf( '', esc_attr( $this->root_id() ) );
echo '';
}
echo '`).
*
* @return void
* @since 1.4.0
*/
private function render_notice( string $title, string $message ): void {
printf( '%s
', esc_html( get_admin_page_title() ) );
echo '';
printf( '%s ', esc_html( $title ) );
echo wp_kses_post( $message );
echo '
';
}
/**
* Whether the compiled bundle is present.
*
* @return bool True when the compiled bundle is on disk.
* @since 1.4.0
*/
protected function build_exists(): bool {
return file_exists( ALBERT_PLUGIN_DIR . 'assets/build/js/' . $this->asset_key() . '.asset.php' );
}
/**
* Enqueue this page's assets.
*
* Reads the wp-scripts asset manifest (dependencies + version) emitted to
* assets/build/js/{asset_key}.asset.php. @wordpress/* deps load from
* core's registered handles; @wordpress/dataviews (where used) is bundled
* into the script and its stylesheet is shipped as
* assets/build/css/dataviews.css. Bails quietly if the build is absent
* (render_page shows the build notice in that case).
*
* @param string $hook Current admin page hook.
*
* @return void
* @since 1.4.0
*/
public function enqueue_assets( string $hook ): void {
if ( Menu::PARENT_SLUG . '_page_' . static::PAGE_SLUG !== $hook ) {
return;
}
$asset_file = ALBERT_PLUGIN_DIR . 'assets/build/js/' . $this->asset_key() . '.asset.php';
if ( ! file_exists( $asset_file ) ) {
return;
}
$asset = require $asset_file;
$version = $asset['version'] ?? ALBERT_VERSION;
// Component chrome (Button, ToggleControl, …).
wp_enqueue_style( 'wp-components' );
// DataViews ships its own stylesheet and core registers no handle for
// it, so we ship the vendor copy emitted by the build
// (CopyWebpackPlugin), where this screen uses DataViews at all.
$style_dependency = 'wp-components';
if ( $this->needs_dataviews_css() ) {
$dataviews_css = ALBERT_PLUGIN_DIR . 'assets/build/css/dataviews.css';
if ( file_exists( $dataviews_css ) ) {
wp_enqueue_style(
'albert-dataviews',
ALBERT_PLUGIN_URL . 'assets/build/css/dataviews.css',
[ 'wp-components' ],
$version
);
$style_dependency = 'albert-dataviews';
}
}
// Our authored screen styles, loaded last so they can override the above.
$stylesheet = $this->stylesheet();
wp_enqueue_style(
$stylesheet['handle'],
ALBERT_PLUGIN_URL . $stylesheet['path'],
[ Assets::PRIMITIVES_HANDLE, $style_dependency ],
Assets::version( $stylesheet['path'] )
);
$script_handle = 'albert-' . $this->asset_key() . '-app';
wp_enqueue_script(
$script_handle,
ALBERT_PLUGIN_URL . 'assets/build/js/' . $this->asset_key() . '.js',
$asset['dependencies'] ?? [],
$version,
true
);
wp_add_inline_script(
$script_handle,
'window.' . $this->js_global() . ' = ' . wp_json_encode(
[
// REST root for this controller; the app builds its paths
// from here rather than hardcoding the namespace in JS.
'restBase' => Plugin::rest_namespace() . '/' . $this->rest_suffix(),
]
) . ';',
'before'
);
wp_set_script_translations( $script_handle, 'albert-ai-butler' );
}
}