Skip to content

Commit

Permalink
Merge pull request #771 from 10up/enhancement/react-settings
Browse files Browse the repository at this point in the history
Onboarding and settings refactor to React
  • Loading branch information
dkotter authored Nov 19, 2024
2 parents 54a5647 + 66a87db commit abe2d7f
Show file tree
Hide file tree
Showing 111 changed files with 10,581 additions and 3,853 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ tests/*
vendor/*
bin/*
hookdocs/*
docs/*
6 changes: 5 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
"Headers": "readonly",
"requestAnimationFrame": "readonly",
"React": "readonly",
"Block": "readonly"
"Block": "readonly",
"classifAISettings": "readonly"
},
"rules": {
"react/jsx-no-undef": "off"
},
"extends": ["plugin:@wordpress/eslint-plugin/recommended"],
"ignorePatterns": ["*.json", "webpack.config.js"]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: 'Checkout Repository'
uses: actions/checkout@v4
- name: Dependency Review
uses: actions/dependency-review-action@v4
uses: actions/dependency-review-action@v4.3.3
with:
license-check: true
vulnerability-check: false
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v20.11.1
v20
199 changes: 165 additions & 34 deletions hookdocs/useful-snippets.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,6 @@ class MyProvider extends Provider {
$this->feature_instance = $feature_instance;
}

/**
* This method will be called by the feature to render the fields
* required by the provider, such as API key, endpoint URL, etc.
*
* This should also register settings that are required for the feature
* to work.
*/
public function render_provider_fields() {
$settings = $this->feature_instance->get_settings( static::ID );

$this->add_api_key_field();
}

/**
* Returns the default settings for this provider.
*
Expand Down Expand Up @@ -185,6 +172,87 @@ add_filter(
);
```

5. **Add required provider fields to the settings**
ClassifAI uses a React-based settings panel. To add the necessary provider fields, include the following code in a JavaScript file, and save it within your plugin's directory.

_**Note:** The provided code snippet uses modern JavaScript (ESNext and JSX), which requires a build step to compile it into a browser-compatible format. We recommend using the [@wordpress/scripts](https://www.npmjs.com/package/@wordpress/scripts) package to manage the build process. For a step-by-step guide on how to install and use this package, check out this tutorial: [Get started with wp-scripts](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts/)._

```js
/**
* WordPress dependencies
*/
import { registerPlugin } from '@wordpress/plugins';
import { useSelect, useDispatch } from '@wordpress/data';
import { Fill, TextControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';

const MyProviderSettings = () => {
const providerName = 'my_provider';
const featureSettings = useSelect( ( select ) =>
select( 'classifai-settings' ).getFeatureSettings()
);
const { setFeatureSettings } = useDispatch( 'classifai-settings' );
const providerSettings = featureSettings[ providerName ] || {};
const setProviderSettings = ( data ) =>
setFeatureSettings( {
[ providerName ]: {
...providerSettings,
...data,
},
} );

return (
<Fill name="ClassifAIProviderSettings">
<div className="settings-row">
<div className="settings-label">{ __( 'API Key', 'classifai' ) }</div>
<div className="settings-control">
<TextControl
value={ providerSettings.api_key || '' }
onChange={ ( value ) =>
setProviderSettings( { api_key: value } )
}
/>
<div className="settings-description">
{ __( 'Enter the provider API key.', 'classifai' ) }
</div>
</div>
</div>
</Fill>
);
};

registerPlugin( 'classifai-provider-my-provider', {
scope: 'my-provider', // Provider settings scope. replace "_" with "-" in your provider ID.
render: MyProviderSettings,
} );
```

6. **Enqueue Javascript assets**
To display the provider fields added in the previous step within the settings panel, we need to enqueue the corresponding JavaScript file. For guidance on enqueuing files in the admin area, refer to the "[Enqueue Script](https://developer.wordpress.org/plugins/javascript/enqueuing/#enqueue-script)" guide, but here’s a typical implementation.
```php
/**
* Enqueue the admin scripts.
*
* @param string $hook_suffix The current admin page.
*/
function my_provider_enqueue_admin_assets( $hook_suffix ) {
// Enqueue assets only on the ClassifAI settings page.
if ( 'tools_page_classifai' !== $hook_suffix ) {
return;
}

$asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php');

wp_enqueue_script(
'my-provider-scripts',
plugins_url( 'build/index.js', __FILE__ ),
$asset_file['dependencies'],
$asset_file['version']
);
}
add_action( 'admin_enqueue_scripts', 'my_provider_enqueue_admin_assets' );
```

## Add a new Feature

Starting in ClassifAI 3.0.0, it is easier to add your own Features. Most of the implementation details are left to you but there are a few key steps that need to be followed:
Expand Down Expand Up @@ -296,27 +364,6 @@ class MyFeature extends Feature {
return esc_html__( 'Enable this feature', 'text-domain' );
}

/**
* Add any needed custom fields.
*/
public function add_custom_settings_fields() {
$settings = $this->get_settings();

add_settings_field(
'custom_setting',
esc_html__( 'Custom setting', 'classifai' ),
[ $this, 'render_input' ],
$this->get_option_name(),
$this->get_option_name() . '_section',
[
'label_for' => 'custom_setting',
'placeholder' => esc_html__( 'Custom setting', 'text-domain' ),
'default_value' => $settings['custom_setting'],
'description' => esc_html__( 'Add a custom setting.', 'text-domain' ),
]
);
}

/**
* Returns the default settings for the feature.
*
Expand Down Expand Up @@ -372,3 +419,87 @@ add_filter(
}
);
```

4. **Add additional featured fields to the settings**
ClassifAI uses a React-based settings panel. To add the necessary additional feature fields, include the following code in a JavaScript file, and save it within your plugin's directory.

_**Note:** The provided code snippet uses modern JavaScript (ESNext and JSX), which requires a build step to compile it into a browser-compatible format. We recommend using the [@wordpress/scripts](https://www.npmjs.com/package/@wordpress/scripts) package to manage the build process. For a step-by-step guide on how to install and use this package, check out this tutorial: [Get started with wp-scripts](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts/)._

```js
/**
* WordPress dependencies
*/
import { TextControl, Fill } from '@wordpress/components';
import { registerPlugin } from '@wordpress/plugins';
import { __ } from '@wordpress/i18n';
import { useSelect, useDispatch } from '@wordpress/data';

const MyFeatureSettings = () => {
const featureSettings = useSelect( ( select ) =>
select( 'classifai-settings' ).getFeatureSettings()
);
const { setFeatureSettings } = useDispatch( 'classifai-settings' );
return (
<>
<Fill name="ClassifAIFeatureSettings">
<div className="settings-row">
<div className="settings-label">{ __( 'Custom setting', 'classifai' ) }</div>
<div className="settings-control">
<TextControl
value={ featureSettings.custom_setting || '' }
onChange={ ( value ) =>
setFeatureSettings( { custom_setting: value } )
}
/>
<div className="settings-description">
{ __( 'Choose what type of content to moderate.', 'classifai' ) }
</div>
</div>
</div>
</Fill>
</>
);
};

registerPlugin( 'classifai-feature-custom', {
scope: 'feature-custom', // Feature settings scope. replace "_" with "-" in your feature ID.
render: MyFeatureSettings,
} );
```

5. **Enqueue Javascript assets**
To display the addition feature fields added in the previous step within the settings panel, we need to enqueue the corresponding JavaScript file. For guidance on enqueuing files in the admin area, refer to the "[Enqueue Script](https://developer.wordpress.org/plugins/javascript/enqueuing/#enqueue-script)" guide, but here’s a typical implementation.
```php
/**
* Enqueue the admin scripts.
*
* @param string $hook_suffix The current admin page.
*/
function my_feature_enqueue_admin_assets( $hook_suffix ) {
// Enqueue assets only on the ClassifAI settings page.
if ( 'tools_page_classifai' !== $hook_suffix ) {
return;
}

$asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php');

wp_enqueue_script(
'my-feature-scripts',
plugins_url( 'build/index.js', __FILE__ ),
$asset_file['dependencies'],
$asset_file['version']
);
}
add_action( 'admin_enqueue_scripts', 'my_feature_enqueue_admin_assets' );
```

## Use Legacy settings
ClassifAI 3.2.0 introduces React-based settings and deprecates the PHP-based settings pages. If you have customizations in the legacy settings and would like to continue using the legacy settings panel, you can enable this by using the `classifai_use_legacy_settings_panel` filter hook.

However, please note that legacy settings will be completely removed in future releases. We recommend updating your customizations to use the React-based settings panel. If you encounter any issues, feel free to report them on our [GitHub repository](https://github.com/10up/classifai/).

Add the following snippet to your theme's `functions.php` file or a custom plugin.

```php
add_filter( 'classifai_use_legacy_settings_panel', '__return_true' );
```
65 changes: 63 additions & 2 deletions includes/Classifai/Admin/Notifications.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Classifai\Features\DescriptiveTextGenerator;
use Classifai\Features\Classification;
use function Classifai\should_use_legacy_settings_panel;

class Notifications {

Expand Down Expand Up @@ -44,6 +45,7 @@ public function maybe_render_notices() {
$this->thresholds_update_notice();
$this->v3_migration_completed_notice();
$this->render_embeddings_notice();
$this->render_notices();
}

/**
Expand Down Expand Up @@ -84,6 +86,11 @@ public function render_activation_notice() {
return;
}

$setup_url = admin_url( 'tools.php?page=classifai#/classifai_setup' );
if ( should_use_legacy_settings_panel() ) {
$setup_url = admin_url( 'admin.php?page=classifai_setup' );
}

// Prevent showing the default WordPress "Plugin Activated" notice.
unset( $_GET['activate'] ); // phpcs:ignore WordPress.Security.NonceVerification
?>
Expand All @@ -96,7 +103,7 @@ public function render_activation_notice() {
<h3 class="classifai-activation-message">
<?php esc_html_e( 'Congratulations, the ClassifAI plugin is now activated.', 'classifai' ); ?>
</h3>
<a class="classifai-button" href="<?php echo esc_url( admin_url( 'admin.php?page=classifai_setup' ) ); ?>">
<a class="classifai-button" href="<?php echo esc_url( $setup_url ); ?>">
<?php esc_html_e( 'Start setup', 'classifai' ); ?>
</a>
</div>
Expand Down Expand Up @@ -252,7 +259,7 @@ public function render_embeddings_notice() {
sprintf(
// translators: %1$s: Feature specific message; %2$s: URL to Feature settings.
__( 'ClassifAI has updated to the <code>text-embedding-3-small</code> embeddings model. <br>This requires regenerating any stored embeddings for functionality to work properly. <br><a href="%1$s">Click here to do that</a>, noting this will make multiple API requests to OpenAI.', 'classifai' ),
wp_nonce_url( admin_url( 'tools.php?page=classifai&tab=language_processing&feature=feature_classification' ), 'regen_embeddings', 'embeddings_nonce' )
wp_nonce_url( admin_url( 'admin-post.php?action=classifai_regen_embeddings' ), 'regen_embeddings', 'embeddings_nonce' )
)
);
?>
Expand Down Expand Up @@ -331,4 +338,58 @@ public function ajax_maybe_dismiss_notice() {

update_user_meta( get_current_user_id(), "classifai_dismissed_{$notice_id}", true );
}

/**
* Render any saved notices to display.
*/
public function render_notices() {
$notices = $this->get_notices();
if ( empty( $notices ) ) {
return;
}

foreach ( $notices as $notice ) {
if ( ! empty( $notice['message'] ) ) {
?>
<div class="notice notice-<?php echo esc_attr( $notice['type'] ); ?> is-dismissible">
<p><?php echo esc_html( $notice['message'] ); ?></p>
</div>
<?php
}
}
}

/**
* Get any saved notices to display.
*
* @return mixed
*/
public function get_notices() {
$notices = get_transient( 'classifai_notices' );
delete_transient( 'classifai_notices' );

return $notices;
}

/**
* Set a notice to be displayed.
*
* This will be displayed on the next page load.
* The notice will be stored in a transient.
*
* @param string $message The notice message.
* @param string $type The notice type.
*/
public function set_notice( string $message, string $type = 'info' ) {
$notices = get_transient( 'classifai_notices' );
if ( ! is_array( $notices ) ) {
$notices = [];
}

$notices[] = [
'type' => $type,
'message' => $message,
];
set_transient( 'classifai_notices', $notices );
}
}
Loading

0 comments on commit abe2d7f

Please sign in to comment.