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 '
'; $unmet = $this->unmet_requirement(); if ( $unmet !== null ) { $this->render_notice( $unmet['title'], $unmet['message'] ); } elseif ( ! $this->build_exists() ) { $this->render_notice( sprintf( /* translators: %s: screen name, e.g. "Skills". */ __( '%s screen not built', 'albert-ai-butler' ), $this->screen_title() ), sprintf( /* translators: 1: build command, 2: screen name, e.g. "Skills". */ __( 'The compiled assets are missing. Run %1$s in the plugin directory to build the %2$s screen.', 'albert-ai-butler' ), 'npm install && npm run build', $this->screen_title() ) ); } else { printf( '
', esc_attr( $this->root_id() ) ); echo ''; } echo '
'; } /** * Render a page-title heading followed by a standard error notice. * * @param string $title Notice heading, translated but not yet escaped. * @param string $message Notice body, translated but not yet escaped; may * contain safe inline HTML (e.g. ``). * * @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' ); } }