
* web: fix esbuild issue with style sheets Getting ESBuild, Lit, and Storybook to all agree on how to read and parse stylesheets is a serious pain. This fix better identifies the value types (instances) being passed from various sources in the repo to the three *different* kinds of style processors we're using (the native one, the polyfill one, and whatever the heck Storybook does internally). Falling back to using older CSS instantiating techniques one era at a time seems to do the trick. It's ugly, but in the face of the aggressive styling we use to avoid Flashes of Unstyled Content (FLoUC), it's the logic with which we're left. In standard mode, the following warning appears on the console when running a Flow: ``` Autofocus processing was blocked because a document already has a focused element. ``` In compatibility mode, the following **error** appears on the console when running a Flow: ``` crawler-inject.js:1106 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'. at initDomMutationObservers (crawler-inject.js:1106:18) at crawler-inject.js:1114:24 at Array.forEach (<anonymous>) at initDomMutationObservers (crawler-inject.js:1114:10) at crawler-inject.js:1549:1 initDomMutationObservers @ crawler-inject.js:1106 (anonymous) @ crawler-inject.js:1114 initDomMutationObservers @ crawler-inject.js:1114 (anonymous) @ crawler-inject.js:1549 ``` Despite this error, nothing seems to be broken and flows work as anticipated. * web: move context controllers into reactive controller plugins While I was working on the Patternfly 5 thing, I found myself cleaning up the way our context controllers are plugged into the Interfaces. I realized a couple of things that had bothered me before: 1. It does not matter where the context controller lives so long as the context controller has a references to the LitElement that hosts it. ReactiveControllers provide that reference. 2. ReactiveControllers are a perfect place to hide some of these details, so that they don't have to clutter up our Interface declaration. 3. The ReactiveController `hostConnected()/hostDisconnected()` lifecycle is a much better place to hook up our EVENT_REFRESH events to the contexts and controllers that care about them than some random place in the loader cycle. 4. It's much easier to detect and control when an external change to a context's state object, which is supposed to be a mirror of the context, changes outside the controller, by using the `hostUpdate()` method. When the controller causes a state change, the states will be the same, allowing us to short out the potential infinite loop. This commit also uses the symbol-as-property-name trick to guarantee the privacy of some fields that should truly be private. They're unfindable and inaddressible from the outside world. This is preferable to using the Private Member syntax (the `#` prefix) because Babel, TypeScript, and ESBuild all use an underlying registry of private names that "do not have good performance characteristics if you create many instances of classes with private fields" [ESBuild Caveats](https://esbuild.github.io/content-types/#javascript-caveats).
78 lines
2.3 KiB
TypeScript
78 lines
2.3 KiB
TypeScript
import { UIConfig, uiConfig } from "@goauthentik/common/ui/config";
|
|
import { ensureCSSStyleSheet } from "@goauthentik/elements/utils/ensureCSSStyleSheet";
|
|
|
|
import { state } from "lit/decorators.js";
|
|
|
|
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
|
|
|
import type { Config, CurrentBrand, LicenseSummary } from "@goauthentik/api";
|
|
import { UiThemeEnum } from "@goauthentik/api";
|
|
|
|
import { AKElement } from "../Base";
|
|
import { BrandContextController } from "./BrandContextController";
|
|
import { ConfigContextController } from "./ConfigContextController";
|
|
import { EnterpriseContextController } from "./EnterpriseContextController";
|
|
|
|
export type AkInterface = HTMLElement & {
|
|
getTheme: () => Promise<UiThemeEnum>;
|
|
brand?: CurrentBrand;
|
|
uiConfig?: UIConfig;
|
|
config?: Config;
|
|
};
|
|
|
|
const brandContext = Symbol("brandContext");
|
|
const configContext = Symbol("configContext");
|
|
|
|
export class Interface extends AKElement implements AkInterface {
|
|
@state()
|
|
uiConfig?: UIConfig;
|
|
|
|
[brandContext]!: BrandContextController;
|
|
|
|
[configContext]!: ConfigContextController;
|
|
|
|
@state()
|
|
config?: Config;
|
|
|
|
@state()
|
|
brand?: CurrentBrand;
|
|
|
|
constructor() {
|
|
super();
|
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, ensureCSSStyleSheet(PFBase)];
|
|
this[brandContext] = new BrandContextController(this);
|
|
this[configContext] = new ConfigContextController(this);
|
|
this.dataset.akInterfaceRoot = "true";
|
|
}
|
|
|
|
_activateTheme(root: DocumentOrShadowRoot, theme: UiThemeEnum): void {
|
|
super._activateTheme(root, theme);
|
|
super._activateTheme(document as unknown as DocumentOrShadowRoot, theme);
|
|
}
|
|
|
|
async getTheme(): Promise<UiThemeEnum> {
|
|
if (!this.uiConfig) {
|
|
this.uiConfig = await uiConfig();
|
|
}
|
|
return this.uiConfig.theme?.base || UiThemeEnum.Automatic;
|
|
}
|
|
}
|
|
|
|
export type AkEnterpriseInterface = AkInterface & {
|
|
licenseSummary?: LicenseSummary;
|
|
};
|
|
|
|
const enterpriseContext = Symbol("enterpriseContext");
|
|
|
|
export class EnterpriseAwareInterface extends Interface {
|
|
[enterpriseContext]!: EnterpriseContextController;
|
|
|
|
@state()
|
|
licenseSummary?: LicenseSummary;
|
|
|
|
constructor() {
|
|
super();
|
|
this[enterpriseContext] = new EnterpriseContextController(this);
|
|
}
|
|
}
|