Class ConsoleChromeMixin

java.lang.Object
org.apache.juneau.console.ConsoleChromeMixin

public class ConsoleChromeMixin extends Object
Mixin that serves the admin-console chrome stylesheet at /juneau-console/chrome.css.

Compose into a host resource via @Rest(mixins=ConsoleChromeMixin.class) (see DataTablesMixin in the juneau-console-datatables module for the identical asset-serving pattern this mirrors — not linked here since that module is not a dependency of this one). The served response is the static structural chrome.css (shipped in this module's classpath) with Theme.OPEN's block, then the selected stock theme's override block if one other than open was selected, appended as html:root{} blocks.

Mount styles:

Both of the following arrangements serve the stylesheets at the CHROME_CSS_PATH / THEME_CSS_DIR URLs with no path juggling by the host resource:

  • Composed — the host resource is mounted wherever the application already mounts it (e.g. /rest/*) and the stylesheets hang off that mount, at <host-mount>/juneau-console/chrome.css.
  • Standalone — the host resource is registered with the servlet container at url-pattern /juneau-console/* so the stylesheets sit at a fixed site-root URL, independent of which page or tab rendered the referencing <link>.

The two arrangements need different operation paths, because a container mount at /juneau-console/* reports servletPath="/juneau-console" and Juneau resolves an operation's path against the request URI with contextPath + servletPath already removed — leaving only /chrome.css to match. Each operation below therefore declares both its prefixed path and the same path minus the /juneau-console prefix, so whichever one the arrangement leaves to be matched resolves.

Theme precedence

ConsoleChromeMixin.Builder.theme(String) if set, else Theme.OPEN. Theme.OPEN's block is always appended first; a selected non-open stock theme adds exactly one more block.

Stylesheet load-order band:

At equal cascade specificity, whichever of juneau-views.css and this class's chrome.css is declared later in a page's <head> wins — the exact ambiguity the overrideBlock theme block below is raised to html:root to make irrelevant. Juneau's own in-tree page emitters (the examples module) don't rely on that fix alone: they declare a five-position stylesheet/script load-order band, enforced by a build-time test, so their own asset order is never left to chance in the first place:

  1. vendor stylesheets
  2. juneau-views.css — the views base layer
  3. first-party widget stylesheets that build on the views layer, e.g. juneau-calendar.css
  4. the consumer theme, e.g. this class's chrome.css
  5. page-local <style>

That guard reaches only the pages Juneau itself emits. It says nothing about, and claims no control over, an arbitrary host application's own document — a consumer is free to link chrome.css and juneau-views.css in either order, which is precisely why the html:root fix exists: so the active theme wins regardless of which order the consumer's links happen to appear in.

Example:

// Stock chrome.css (Theme.OPEN tokens), mounted standalone at /juneau-console/*. @Rest(mixins=ConsoleChromeMixin.class) public class ConsoleAssetsRest extends BasicRestServlet { @Bean public ConsoleChromeMixin consoleChrome() { return ConsoleChromeMixin.create().build(); } } // Bake a different stock palette into the served chrome.css. ConsoleChromeMixin.create().theme("gray").build();

Custom palettes aren't configured here. Author them in the FreeMarker chrome with <@theme>/<@token> (see the Theme example), which emits the stock-theme <link> plus an inline override block.

Since:
10.0.0
  • Field Details

  • Constructor Details

    • ConsoleChromeMixin

      No-arg constructor, mirroring FreemarkerMixin()'s equivalent so a bean-store lookup miss can fall back to a default-configured instance without relying on BeanInstantiator's builder-pattern detection.
    • ConsoleChromeMixin

      Constructor.
      Parameters:
      builder - The builder.
  • Method Details

    • roleAliasNames

      public static Set<String> roleAliasNames()
      Returns the names of the role aliases the chrome declares, such as --jc-surface, --jc-danger-text and --jc-header-bg.

      These are the custom properties the chrome derives from the Theme.OPEN tokens. They are not tokens of any Theme, but a page resolves them at run time, so the FreeMarker <@token scheme="dark"> alias check accepts them as targets.

      Returns:
      An unmodifiable set of custom-property names; never null.
    • create

      Creates a new builder.
      Returns:
      A new builder.
    • getChromeCss

      [GET /juneau-console/chrome.css] — serve the admin-console chrome stylesheet.
      Parameters:
      req - The current request, used to key the per-mount body cache.
      Returns:
      The chrome stylesheet as a CSS HttpResource.
      Throws:
      IOException - If the shipped chrome.css resource could not be read (effectively unreachable — the resource is shipped in the same jar as this class).
    • getThemeCss

      public HttpResource getThemeCss(@Path("file") String file)
      [GET /juneau-console/themes/{file}] — serve one of the shipped stock-theme stylesheets.

      The {file} segment is fail-closed to the known BUILTIN_THEME_NAMES stylesheet filenames, so this endpoint can never read an arbitrary classpath resource. Each stylesheet is a structure-free html:root{} token block (one per Theme constant); the FTL <@theme name="…"/> directive links the winning one after chrome.css so its tokens override chrome.css's baked-in Theme.OPEN block.

      Parameters:
      file - The requested stylesheet filename (e.g. juneau-theme-light-red.css).
      Returns:
      The theme stylesheet as a CSS HttpResource.
      Throws:
      NotFound - If file is not one of the shipped stylesheet filenames.
    • getConsoleJs

      [GET /juneau-console/juneau-console.js] — serve the console shell.
      Returns:
      The script.
    • getBadgesJs

      [GET /juneau-console/juneau-badges.js] — serve the count-badge script.
      Returns:
      The script.
    • getChartsJs

      [GET /juneau-console/juneau-charts.js] — serve the chart-card runtime.
      Returns:
      The script.
    • getJobLogJs

      [GET /juneau-console/juneau-joblog.js] — serve the job-log card runtime.
      Returns:
      The script.
    • getDashboardJs

      [GET /juneau-console/juneau-dashboard.js] — serve the dashboard chrome runtime.
      Returns:
      The script.
    • getPageSchema

      [GET /juneau-console/juneau-page.schema.json] — serve the page-contract schema.
      Returns:
      The schema.
    • themeAssetUrl

      public static String themeAssetUrl(RestRequest req, String name)
      Returns a real, browser-fetchable URL for a shipped theme stylesheet, resolved against the request's context root (not the page resource) and carrying the same ?v=<buildVersion>-<hash8> content-sensitive cache-buster chromeCssUrl(RestRequest) uses.

      The console document shell (<@console> / <@theme>) emits this as a <link href> to the standalone /juneau-console/* mount. Resolving with context: rather than servlet: is load-bearing: a page resource at /rest/setup reports that path as its servlet path, so servlet:/juneau-console/themes/… would become /rest/setup/juneau-console/themes/… and 404. A template-rendering consumer sits downstream of Juneau's servlet:-rewriting serializer, so it needs the URL already resolved here.

      Parameters:
      req - The current request, supplying the context path to resolve against.
      name - One of the BUILTIN_THEME_NAMES.
      Returns:
      The context-root-absolute theme-stylesheet URL with the version+content-hash cache-buster appended.
    • chromeCssUrl

      public static String chromeCssUrl(RestRequest req)
      Returns a real, browser-fetchable URL for the shipped chrome.css, resolved against the request's context root (not the page resource) and carrying the same content-sensitive ?v=… cache-buster the theme stylesheets use.

      The console document shell (<@console>) emits this as the first stylesheet in its head cascade. See themeAssetUrl(RestRequest, String) for why the resolution is context: rather than servlet:.

      Parameters:
      req - The current request, supplying the context path to resolve against.
      Returns:
      The context-root-absolute chrome.css URL with the version+content-hash cache-buster appended.
    • consoleJsUrl

      public static String consoleJsUrl(RestRequest req)
      Context-root-absolute, cache-busted URL of the console shell script.
      Parameters:
      req - The current request.
      Returns:
      The URL.
    • badgesJsUrl

      public static String badgesJsUrl(RestRequest req)
      Context-root-absolute, cache-busted URL of the count-badge script.
      Parameters:
      req - The current request.
      Returns:
      The URL.
    • consoleAssetUrl

      public static String consoleAssetUrl(RestRequest req, String path)
      Context-root-absolute, cache-busted URL of a console card runtime; the resolver of the charts and joblog toolkit packs.
      Parameters:
      req - The current request.
      path - CHARTS_JS_PATH, JOBLOG_JS_PATH or DASHBOARD_JS_PATH.
      Returns:
      The URL.
      Throws:
      IllegalArgumentException - If path is neither.
    • stockTheme

      public static Theme stockTheme(String name)
      Maps one of the BUILTIN_THEME_NAMES stock names to its Theme constant.

      The single place the kebab-case stock name (as written on FTL <@theme name="…"> / the theme(String) builder) is resolved to its shipped Theme, without a second copy of the name→theme table.

      Parameters:
      name - One of the BUILTIN_THEME_NAMES.
      Returns:
      The stock Theme for name.
      Throws:
      IllegalArgumentException - If name is not a built-in stock-theme name.
    • overrideBlock

      public static String overrideBlock(Theme theme)
      Renders a theme's override block: one html:root&#123;...&#125; carrying the theme's leaf tokens, then its alias tokens, each in declaration order.

      The two halves are escaped differently. A leaf is a validated literal and goes through CssValueEscaper: a grammar-accepted value can still carry a character such as ; that would end the declaration early. An alias is a single anchored var(--jc-name) reference (Theme.Builder.alias(String, String)), in which nothing dangerous is representable, so it is emitted verbatim. That asymmetry is the reason Theme.getAliases() stays a map.

      The block uses the html type prefix. Two :root blocks in separately linked stylesheets tie at specificity (0,0,1,0) and are resolved by <link> order, which this framework neither sets nor observes. html:root scores (0,0,1,1), so a theme override wins in either order.

      A theme with no leaves and no aliases renders as the empty string. Each declared name is re-checked against the reserved --jc-chrome-* namespace (defense in depth; a Theme built by its builder can't carry one). This method is public so the FreeMarker <@theme> directive renders its inline block with the same code that serves chrome.css.

      Example:

      Theme theme = Theme.create("corporate") .token("--jc-accent", "#b45309") .alias("--jc-tab-bar-bg", "var(--jc-card-bg)") .build(); String css = ConsoleChromeMixin.overrideBlock(theme); // html:root{--jc-accent:#b45309;--jc-tab-bar-bg:var(--jc-card-bg);}

      Parameters:
      theme - The theme. Must not be null.
      Returns:
      The override block, or an empty string if the theme declares nothing.
    • schemeHead

      public static ConsoleChromeMixin.SchemeHead schemeHead(RestRequest req, String pin, boolean toggle)
      Returns the colour-scheme head pieces for a page that does not render through the FreeMarker console directive.

      A pinned page (light or dark) gets data-jc-scheme and data-jc-scheme-pin set to the pin, so it paints in that scheme even with scripts disabled. A toggleable page gets data-jc-scheme-toggle, which lets the pre-paint script honour the visitor's stored choice. The head piece is SCHEME_SCRIPT (stamped with the request's CSP nonce when one is set) followed by a link to the shipped dark layer.

      Example:

      SchemeHead h = ConsoleChromeMixin.schemeHead(req, "auto", true); var attrs = h.htmlAttributes().isEmpty() ? "" : " " + h.htmlAttributes(); out.write("<html lang=\"en\"" + attrs + "><head>" + h.head());

      The head piece carries only the scheme script and the dark layer. It does not replace the page's own links to the chrome stylesheet and a theme palette, which the caller still emits.

      Parameters:
      req - The current request, supplying the context path and any CSP nonce.
      pin - One of the SCHEMES.
      toggle - Whether the visitor may override the scheme with a stored choice.
      Returns:
      The head pieces.
      Throws:
      IllegalArgumentException - If pin is null or not one of the SCHEMES.
    • schemeHtmlAttributes

      public static String schemeHtmlAttributes(String pin, boolean toggle)
      Returns the <html> start-tag attributes for a scheme pin and toggle flag.

      The FreeMarker console directive and schemeHead(RestRequest, String, boolean) share this so the two shells stamp identical attributes.

      Parameters:
      pin - One of the SCHEMES.
      toggle - Whether the visitor may override the scheme with a stored choice.
      Returns:
      The attributes, space-separated with no leading space, or an empty string.
      Throws:
      IllegalArgumentException - If pin is null or not one of the SCHEMES.
    • schemeScriptElement

      public static String schemeScriptElement(String nonce)
      Returns the SCHEME_SCRIPT element, carrying nonce as an attribute when it is not null.
      Parameters:
      nonce - The request's CSP nonce, or null.
      Returns:
      The <script> element, with no trailing newline.
    • nonceAttr

      public static String nonceAttr(String nonce)
      Returns nonce="…" (with a leading space, attribute-escaped) or an empty string for null.
      Parameters:
      nonce - The request's CSP nonce, or null.
      Returns:
      The attribute text.
    • schemeOverrideBlock

      public static String schemeOverrideBlock(Map<String,String> tokens, Map<String,String> aliases)
      Renders the dark-scheme override rule for app-declared dark tokens.

      The rule is @media not print{html:root[data-jc-scheme="dark"]{…}}, so it wins over the light override block only while the page is dark and never reaches print. Leaves come first, then aliases, each in declaration order. A leaf must be a literal: a dark leaf that is itself a var() reference is rejected, because it would resolve against the light palette. A live link belongs in an alias. Names and values get the same checks as Theme.Builder: the name shape, the reserved --jc-chrome-* namespace, the value grammar, and one channel per name.

      Example:

      String css = ConsoleChromeMixin.schemeOverrideBlock( Map.of("--jc-link", "#6cb4ff"), Map.of("--jc-focus", "var(--jc-accent)")); // @media not print{html:root[data-jc-scheme="dark"]{--jc-link:#6cb4ff;--jc-focus:var(--jc-accent);}}

      Parameters:
      tokens - The dark leaf tokens by name. Must not be null.
      aliases - The dark aliases by name, each a var(--jc-name) reference. Must not be null.
      Returns:
      The rule, or an empty string if both maps are empty.
      Throws:
      IllegalArgumentException - If a name or value fails validation.