Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions integrations/bricks/admin-app/src/components/BundleTab.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
<script>
/**
* Bundle / Settings tab. Shows bundle info and the html_font_size
* plugin setting with its own save button (independent of token saves).
*/
import { meta } from '../lib/stores.svelte.js';
import { saveSettings } from '../lib/api.js';

let bundle = $state(meta.pluginSettings?.css_bundle ?? 'optimal');
let fontSize = $state(meta.pluginSettings?.html_font_size ?? '');
let saving = $state(false);
let saved = $state(false);
Expand All @@ -17,7 +14,7 @@
saved = false;
error = '';
try {
await saveSettings({ html_font_size: fontSize });
await saveSettings({ css_bundle: bundle, html_font_size: fontSize });
saved = true;
if (savedTimer) clearTimeout(savedTimer);
savedTimer = setTimeout(() => { saved = false; savedTimer = null; }, 3000);
Expand Down Expand Up @@ -45,6 +42,18 @@

<h2 class="settings-heading">Plugin Settings</h2>

<div class="setting-row">
<label for="css-bundle">CSS Bundle</label>
<select id="css-bundle" bind:value={bundle}>
<option value="essential">Essential — base variables only</option>
<option value="optimal">Optimal — variables + core utilities (default)</option>
<option value="full">Full — all utilities included</option>
</select>
<p class="description">
Choose which SLASHED CSS bundle to load on the frontend and in the Bricks editor canvas.
</p>
</div>

<div class="setting-row">
<label for="html-font-size">HTML Font Size</label>
<select id="html-font-size" bind:value={fontSize}>
Expand Down Expand Up @@ -91,7 +100,7 @@
.info dd { margin: 0; color: #50575e; }

.setting-row {
margin-bottom: 16px;
margin-bottom: 20px;
}
.setting-row label {
display: block;
Expand Down
2 changes: 1 addition & 1 deletion integrations/bricks/assets/admin-app/app.css

Large diffs are not rendered by default.

22 changes: 13 additions & 9 deletions integrations/bricks/assets/admin-app/app.js

Large diffs are not rendered by default.

106 changes: 40 additions & 66 deletions integrations/bricks/includes/class-admin-page-svelte.php
Original file line number Diff line number Diff line change
@@ -1,58 +1,45 @@
<?php
/**
* Svelte-based admin page (POC).
* Svelte-based admin page.
*
* Lives alongside the legacy jQuery admin page so both can be compared
* side-by-side. PHP still owns capability checks, nonces, sanitization,
* and option storage; this class just registers a second submenu and
* mounts a Svelte SPA into a single div on it.
*
* @package SLASHED_Bricks
*/

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* Class Slashed_Bricks_Admin_Page_Svelte
*
* Adds a "Tokens (v2)" submenu under SLASHED that boots the Svelte app
* built from integrations/bricks/admin-app/.
* Registers the top-level "SLASHED" admin menu and mounts the Svelte SPA
* built from integrations/bricks/admin-app/ into a single div.
*
* Responsibilities split with the SPA:
*
* PHP (this class) Svelte (admin-app/)
* --------------------------------|--------------------------------
* register_submenu | render UI
* register_menu | render UI
* capability check | reactive state + dirty tracking
* enqueue built bundle | live preview
* wp_localize_script hydration | optimistic save / error toasts
* REST endpoint (REST controller) | calls REST endpoint
* sanitize + option write | -
*
* The mount point is just <div id="slashed-admin-app"></div>; everything
* inside it is owned by Svelte. This is the cleanest line to draw - WP
* keeps doing the WordPress-correct things, the SPA owns the inside of
* one div.
* inside it is owned by Svelte.
*
* The class itself is stateless except for the page hook suffix it
* captures during submenu registration; tab metadata, settings, and
* defaults all come from the dedicated helper classes.
* @package SLASHED_Bricks
*/

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* Class Slashed_Bricks_Admin_Page_Svelte
*/
class Slashed_Bricks_Admin_Page_Svelte {

/**
* Submenu slug. Must be unique across the WP admin.
* Top-level menu slug.
*/
const PAGE_SLUG = 'slashed-bricks-svelte';
const PAGE_SLUG = 'slashed-bricks';

/**
* Hook suffix returned by add_submenu_page(). Captured at registration
* Hook suffix returned by add_menu_page(). Captured at registration
* time and compared in enqueue_assets() so we never accidentally load
* the SPA bundle on other admin screens. WordPress mangles the parent
* slug into the hook name in ways that vary across versions; relying
* on the value WP itself returned avoids brittle string assembly.
* the SPA bundle on other admin screens.
*
* @var string
*/
Expand All @@ -62,43 +49,36 @@ class Slashed_Bricks_Admin_Page_Svelte {
* Constructor.
*/
public function __construct() {
add_action( 'admin_menu', array( $this, 'register_submenu' ), 20 );
add_action( 'admin_menu', array( $this, 'register_menu' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}

/**
* Register the submenu under the existing SLASHED top-level page.
*
* Priority 20 so the parent page (registered in
* Slashed_Bricks_Admin_Page::register_menu at default priority) has
* already been added when we attach to it.
* Register the top-level SLASHED admin menu.
*/
public function register_submenu() {
$this->hook_suffix = (string) add_submenu_page(
'slashed-bricks',
__( 'SLASHED Tokens (v2)', 'slashed-bricks' ),
__( 'Tokens (v2)', 'slashed-bricks' ),
public function register_menu() {
$this->hook_suffix = (string) add_menu_page(
__( 'SLASHED Settings', 'slashed-bricks' ),
__( 'SLASHED', 'slashed-bricks' ),
'manage_options',
self::PAGE_SLUG,
array( $this, 'render_page' )
array( $this, 'render_page' ),
'dashicons-art',
59
);
}

/**
* Enqueue the built Svelte bundle on this page only.
*
* The bundle is fully self-contained: no jQuery dependency, no
* wp-color-picker, no other admin libraries. Cache-busting uses
* filemtime() on the built artifact so a fresh `npm run build`
* invalidates browser caches without manual version bumps.
* Compares against the value returned by add_menu_page() at registration
* time rather than assembling the hook name ourselves — WP's hook-name
* conventions have changed across versions, so trusting the return value
* is the only safe approach.
*
* @param string $hook_suffix Current admin page hook suffix.
*/
public function enqueue_assets( $hook_suffix ) {
// Only fire on our submenu. We compare against the value returned
// by add_submenu_page() at registration time rather than building
// the string ourselves - WP's hook-name conventions for submenus
// have changed across versions.
if ( '' === $this->hook_suffix || $hook_suffix !== $this->hook_suffix ) {
return;
}
Expand All @@ -109,8 +89,6 @@ public function enqueue_assets( $hook_suffix ) {
$js_path = $plugin_path . 'assets/admin-app/app.js';
$css_path = $plugin_path . 'assets/admin-app/app.css';

// Defensive: if the build artefact is missing, surface an admin
// notice rather than silently shipping a broken page.
if ( ! file_exists( $js_path ) ) {
add_action( 'admin_notices', array( $this, 'render_missing_bundle_notice' ) );
return;
Expand All @@ -137,9 +115,6 @@ public function enqueue_assets( $hook_suffix ) {
// Modules need type=module on the <script> tag.
add_filter( 'script_loader_tag', array( $this, 'mark_as_module' ), 10, 3 );

// Hydration payload. Mirrors the structure the Svelte app expects
// in window.slashedBricksApp - see admin-app/index.html for the
// dev-time equivalent.
wp_localize_script(
'slashed-bricks-admin-app',
'slashedBricksApp',
Expand All @@ -160,9 +135,11 @@ public function enqueue_assets( $hook_suffix ) {
/**
* Tag the SPA bundle as a JS module so import statements work.
*
* Vite emits ES modules; WordPress' default <script> tag has no
* type attribute, which would prevent the import-graph from loading.
* Filter narrowed by handle so this never affects other scripts.
* Vite emits ES modules; WordPress' default <script> tag has no type
* attribute, which would prevent the import graph from resolving. The
* regex uses limit 1 so only the opening tag is touched, not any inline
* script content that might contain `<script>` as a literal string.
* Filter is narrowed by handle so it never affects other enqueued scripts.
*
* @param string $tag Generated script tag.
* @param string $handle Script handle.
Expand All @@ -173,14 +150,11 @@ public function mark_as_module( $tag, $handle, $src ) {
if ( 'slashed-bricks-admin-app' !== $handle ) {
return $tag;
}
// Insert type="module" without clobbering an existing type attribute.
// preg_replace with limit 1 so only the opening tag is touched.
return preg_replace( '/<script(\b[^>]*)>/', '<script type="module"$1>', $tag, 1 );
}

/**
* Render the page shell. The Svelte app takes over inside
* #slashed-admin-app once main.js runs.
* Render the page shell. The Svelte app takes over inside #slashed-admin-app.
*/
public function render_page() {
if ( ! current_user_can( 'manage_options' ) ) {
Expand All @@ -192,12 +166,12 @@ public function render_page() {
<noscript>
<div class="notice notice-warning">
<p>
<?php esc_html_e( 'This settings page requires JavaScript. The legacy form (still fully functional, no JS required) is available under SLASHED &rarr; SLASHED.', 'slashed-bricks' ); ?>
<?php esc_html_e( 'This settings page requires JavaScript to be enabled in your browser.', 'slashed-bricks' ); ?>
</p>
</div>
</noscript>
<p style="color:#50575e; padding: 24px 0;">
<?php esc_html_e( 'Loading SLASHED admin SPA…', 'slashed-bricks' ); ?>
<?php esc_html_e( 'Loading SLASHED settings…', 'slashed-bricks' ); ?>
</p>
</div>
</div>
Expand All @@ -208,7 +182,7 @@ public function render_page() {
* Admin notice rendered when the built bundle is missing.
*
* Helps developers who clone the repo without running the admin-app
* build step understand why the page is empty.
* build step understand why the page is blank.
*/
public function render_missing_bundle_notice() {
echo '<div class="notice notice-error"><p>';
Expand Down
Loading