mirror of
https://github.com/goauthentik/authentik
synced 2026-04-25 17:15:26 +02:00
Flesh out.
This commit is contained in:
@@ -62,7 +62,7 @@ async function fetchAboutDetails(): Promise<AboutEntry[]> {
|
||||
export class AboutModal extends WithLicenseSummary(WithBrandConfig(AKModal)) {
|
||||
static hostStyles = [
|
||||
css`
|
||||
.ak-c-modal:has(ak-about-modal) {
|
||||
dialog.ak-c-modal:has(ak-about-modal) {
|
||||
--ak-c-modal--BackgroundColor: var(--pf-global--palette--black-900);
|
||||
--ak-c-modal--BorderColor: var(--pf-global--palette--black-600);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import "#elements/banner/EnterpriseStatusBanner";
|
||||
import "#elements/banner/VersionBanner";
|
||||
import "#elements/commands/ak-command-palette";
|
||||
import "#elements/messages/MessageContainer";
|
||||
import "#elements/router/RouterOutlet";
|
||||
import "#elements/sidebar/Sidebar";
|
||||
@@ -19,7 +18,6 @@ import { isGuest } from "#common/users";
|
||||
import { WebsocketClient } from "#common/ws/WebSocketClient";
|
||||
|
||||
import { AuthenticatedInterface } from "#elements/AuthenticatedInterface";
|
||||
import { AKCommandPalette } from "#elements/commands/ak-command-palette";
|
||||
import { listen } from "#elements/decorators/listen";
|
||||
import { WithCapabilitiesConfig } from "#elements/mixins/capabilities";
|
||||
import { WithNotifications } from "#elements/mixins/notifications";
|
||||
@@ -117,8 +115,6 @@ export class AdminInterface extends WithCapabilitiesConfig(
|
||||
|
||||
//#region Lifecycle
|
||||
|
||||
protected commandPalette: AKCommandPalette;
|
||||
|
||||
constructor() {
|
||||
configureSentry();
|
||||
|
||||
@@ -126,11 +122,45 @@ export class AdminInterface extends WithCapabilitiesConfig(
|
||||
|
||||
WebsocketClient.connect();
|
||||
|
||||
this.commandPalette = this.ownerDocument.createElement("ak-command-palette");
|
||||
this.#sidebarMatcher = window.matchMedia("(width >= 1200px)");
|
||||
this.sidebarOpen = this.#sidebarMatcher.matches;
|
||||
}
|
||||
|
||||
#refreshCommandsFrameID = -1;
|
||||
|
||||
#refreshCommands = () => {
|
||||
const commands = [
|
||||
{
|
||||
label: msg("Create a new application..."),
|
||||
action: () => navigate("/core/applications", { createWizard: true }),
|
||||
prefix: msg("Jump to", { id: "command-palette.prefix.jump-to" }),
|
||||
group: msg("Applications"),
|
||||
},
|
||||
{
|
||||
label: msg("Check the logs"),
|
||||
action: () => navigate("/events/log"),
|
||||
group: msg("Events"),
|
||||
},
|
||||
{
|
||||
label: msg("Manage users"),
|
||||
action: () => navigate("/identity/users"),
|
||||
group: msg("Users"),
|
||||
},
|
||||
...this.entries.flatMap(([, label, , children]) => [
|
||||
...(children ?? []).map(([path, childLabel]) => ({
|
||||
label: childLabel,
|
||||
prefix: msg("Jump to", { id: "command-palette.prefix.jump-to" }),
|
||||
group: label,
|
||||
action: () => {
|
||||
navigate(path!);
|
||||
},
|
||||
})),
|
||||
]),
|
||||
];
|
||||
|
||||
this.commandPalette.modal.setCommands(commands);
|
||||
};
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
@@ -142,6 +172,8 @@ export class AdminInterface extends WithCapabilitiesConfig(
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
|
||||
cancelAnimationFrame(this.#refreshCommandsFrameID);
|
||||
|
||||
this.#sidebarMatcher.removeEventListener("change", this.#sidebarMediaQueryListener);
|
||||
|
||||
WebsocketClient.close();
|
||||
@@ -150,18 +182,7 @@ export class AdminInterface extends WithCapabilitiesConfig(
|
||||
public firstUpdated(changedProperties: PropertyValues<this>): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
|
||||
this.commandPalette.modal.addCommands(
|
||||
this.entries.flatMap(([, label, , children]) => [
|
||||
...(children ?? []).map(([path, childLabel]) => ({
|
||||
label: childLabel,
|
||||
suffix: msg("Jump to", { id: "command-palette.prefix.jump-to" }),
|
||||
group: label,
|
||||
action: () => {
|
||||
navigate(path!);
|
||||
},
|
||||
})),
|
||||
]),
|
||||
);
|
||||
this.#refreshCommandsFrameID = requestAnimationFrame(this.#refreshCommands);
|
||||
}
|
||||
|
||||
public override updated(changedProperties: PropertyValues<this>): void {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import "#elements/commands/ak-command-palette";
|
||||
|
||||
import { globalAK } from "#common/global";
|
||||
import { applyDocumentTheme, createUIThemeEffect } from "#common/theme";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { AKCommandPalette } from "#elements/commands/ak-command-palette";
|
||||
import { BrandingContextController } from "#elements/controllers/BrandContextController";
|
||||
import { ConfigContextController } from "#elements/controllers/ConfigContextController";
|
||||
import { ContextControllerRegistry } from "#elements/controllers/ContextControllerRegistry";
|
||||
@@ -30,6 +33,12 @@ export abstract class Interface extends AKElement {
|
||||
*/
|
||||
#registryKeys = new WeakMap<ReactiveController, ContextType<Context<unknown, unknown>>>();
|
||||
|
||||
/**
|
||||
* The command palette instance. This must be inserted by the extending class,
|
||||
* as the palette may depend on a context that is not available at the time of this class's construction.
|
||||
*/
|
||||
public readonly commandPalette: AKCommandPalette;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -45,6 +54,7 @@ export abstract class Interface extends AKElement {
|
||||
this.addController(new ModalOrchestrationController());
|
||||
|
||||
this.id = "interface-root";
|
||||
this.commandPalette = this.ownerDocument.createElement("ak-command-palette");
|
||||
}
|
||||
|
||||
public override addController(
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { CURRENT_CLASS, EVENT_REFRESH } from "#common/constants";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
import {
|
||||
CommandPaletteState,
|
||||
PaletteCommandAction,
|
||||
PaletteCommandDefinition,
|
||||
} from "#elements/commands/shared";
|
||||
import { intersectionObserver } from "#elements/decorators/intersection-observer";
|
||||
import { getURLParams, updateURLParams } from "#elements/router/RouteMatch";
|
||||
import Styles from "#elements/Tabs.css" with { type: "bundled-text" };
|
||||
import { ifPresent } from "#elements/utils/attributes";
|
||||
import { isFocusable } from "#elements/utils/focus";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, html, LitElement, TemplateResult } from "lit";
|
||||
import { capitalCase } from "change-case";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { createRef, ref } from "lit/directives/ref.js";
|
||||
|
||||
@@ -30,16 +38,59 @@ export class Tabs extends AKElement {
|
||||
|
||||
@state()
|
||||
protected tabs: ReadonlyMap<string, Element> = new Map();
|
||||
/**
|
||||
* Whether the tab is visible in the viewport.
|
||||
*/
|
||||
@intersectionObserver()
|
||||
public visible = false;
|
||||
|
||||
#focusTargetRef = createRef<HTMLSlotElement>();
|
||||
#observer: MutationObserver | null = null;
|
||||
|
||||
#commands = new CommandPaletteState<string>();
|
||||
|
||||
#updateTabs = (): void => {
|
||||
this.tabs = new Map(
|
||||
Array.from(this.querySelectorAll(":scope > [slot^='page-']"), (element) => {
|
||||
return [element.getAttribute("slot") || "", element];
|
||||
}),
|
||||
);
|
||||
|
||||
requestAnimationFrame(this.#updateCommands);
|
||||
};
|
||||
|
||||
#updateCommands = (): void => {
|
||||
const commands: PaletteCommandDefinition<string>[] = [];
|
||||
|
||||
if (!this.visible) {
|
||||
this.#commands.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const group = msg(str`Landmark: ${capitalCase(this.pageIdentifier)}`);
|
||||
const prefix = msg("Switch to tab", { id: "command-palette.switch-to-tab" });
|
||||
|
||||
const action: PaletteCommandAction<string> = (slotName) => {
|
||||
this.activateTab(slotName);
|
||||
};
|
||||
|
||||
for (const [slotName, tabPanel] of this.tabs) {
|
||||
if (this.activeTabName === slotName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const label = tabPanel.getAttribute("aria-label") || slotName;
|
||||
|
||||
commands.push({
|
||||
label,
|
||||
action,
|
||||
group,
|
||||
prefix,
|
||||
details: slotName,
|
||||
});
|
||||
}
|
||||
|
||||
this.#commands.set(commands);
|
||||
};
|
||||
|
||||
public override connectedCallback(): void {
|
||||
@@ -78,9 +129,18 @@ export class Tabs extends AKElement {
|
||||
|
||||
public override disconnectedCallback(): void {
|
||||
this.#observer?.disconnect();
|
||||
this.#commands.clear();
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
public override updated(changedProperties: PropertyValues<this>): void {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has("visible")) {
|
||||
this.#updateCommands();
|
||||
}
|
||||
}
|
||||
|
||||
public findActiveTabPanel(): Element | null {
|
||||
return this.querySelector(`[slot='${this.activeTabName}']`);
|
||||
}
|
||||
|
||||
@@ -10,29 +10,25 @@
|
||||
--pf-global--BackgroundColor--dark-transparent-200
|
||||
);
|
||||
|
||||
--ak-fieldset--BorderColor: var(--pf-global--palette--purple-500);
|
||||
|
||||
--ak-c-command-palette__item--BackgroundColor: transparent;
|
||||
--ak-c-command-palette__item--Color: var(--pf-global--palette--purple-700);
|
||||
|
||||
--ak-c-command-palette__item--Color: var(--pf-global--palette--blue-50);
|
||||
--ak-c-command-palette__item--selected--BackgroundColor: var(--pf-global--palette--blue-400);
|
||||
--ak-c-command-palette__item--hover--BackgroundColor: var(--pf-global--palette--blue-600);
|
||||
--ak-c-command-palette__item--hover--BackgroundColor: var(--pf-global--palette--purple-600);
|
||||
--ak-c-command-palette__item--hover-selected--BackgroundColor: var(
|
||||
--pf-global--palette--blue-300
|
||||
);
|
||||
|
||||
--pf-global--Color--100: var(--pf-global--Color--light-100);
|
||||
--pf-global--Color--100: var(--pf-global--palette--purple-50);
|
||||
|
||||
@media (prefers-reduced-transparency: reduce) {
|
||||
--ak-c-command-palette--Translucency: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
fieldset {
|
||||
--ak-fieldset-border-color: var(--ak-c-command-palette__group--BorderColor) !important;
|
||||
|
||||
&:has(.more-contrast-only) {
|
||||
--ak-c-command-palette__group--BorderColor: transparent;
|
||||
}
|
||||
will-change: opacity, text-decoration-color, background-color, color;
|
||||
transform: translate3d(0, 0, 0); /* Fixes rendering artifacts. */
|
||||
}
|
||||
|
||||
:host {
|
||||
@@ -45,16 +41,13 @@ fieldset {
|
||||
|
||||
[part="command-field"] {
|
||||
border-bottom: 0.5px solid var(--pf-global--palette--black-600);
|
||||
margin-block-end: var(--pf-global--spacer--sm);
|
||||
padding-block-end: var(--pf-global--spacer--sm);
|
||||
margin-block-end: var(--pf-global--spacer--xs);
|
||||
padding-block-end: var(--pf-global--spacer--xs);
|
||||
margin-inline: var(--pf-global--spacer--sm);
|
||||
|
||||
legend {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
#command-input {
|
||||
background: transparent;
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-size: var(--pf-global--FontSize--2xl);
|
||||
@@ -64,6 +57,26 @@ fieldset {
|
||||
border: none;
|
||||
|
||||
outline: none;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--pf-global--palette--purple-100);
|
||||
font-weight: 100;
|
||||
font-family: var(--pf-global--FontFamily--heading--sans-serif);
|
||||
}
|
||||
}
|
||||
|
||||
[part="results-group"] {
|
||||
border-width: 0.5px;
|
||||
padding-inline: 0 !important;
|
||||
padding-block-end: 0 !important;
|
||||
|
||||
legend {
|
||||
padding-block: var(--pf-global--spacer--xs) !important;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-block-start: var(--pf-global--spacer--md);
|
||||
}
|
||||
}
|
||||
|
||||
[part="results"] {
|
||||
@@ -71,15 +84,34 @@ fieldset {
|
||||
max-height: calc(100dvh - (1.75 * var(--ak-c-modal--MarginBlockStart)));
|
||||
}
|
||||
|
||||
[part="results-list"] {
|
||||
margin-block-start: calc(var(--pf-global--spacer--sm) * -1);
|
||||
}
|
||||
|
||||
[part="command-item-label"] {
|
||||
flex: 1 1 auto;
|
||||
grid-row: label;
|
||||
font-family: var(--pf-global--FontFamily--heading--sans-serif);
|
||||
}
|
||||
|
||||
[part="command-item-suffix"] {
|
||||
[part="command-item-prefix"] {
|
||||
grid-area: prefix;
|
||||
font-variant: all-small-caps;
|
||||
font-weight: bold;
|
||||
color: var(--pf-global--palette--blue-300);
|
||||
color: var(--pf-global--palette--gold-200);
|
||||
}
|
||||
|
||||
[part="command-item-suffix"] {
|
||||
grid-area: suffix;
|
||||
font-variant: all-small-caps;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
color: var(--pf-global--palette--gold-200);
|
||||
justify-content: end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
[part="command-item-description"] {
|
||||
grid-area: description;
|
||||
}
|
||||
|
||||
[part="group-heading"] {
|
||||
@@ -95,34 +127,40 @@ fieldset {
|
||||
--ak-c-command-palette__item--hover--BackgroundColor: var(
|
||||
--ak-c-command-palette__item--hover-selected--BackgroundColor
|
||||
);
|
||||
--ak-c-command-palette__item--AccentColor: var(--pf-global--primary-color--100);
|
||||
}
|
||||
|
||||
.pf-c-button {
|
||||
display: flex;
|
||||
gap: var(--pf-global--spacer--sm);
|
||||
--pf-c-button--PaddingTop: var(--pf-global--spacer--md);
|
||||
--pf-c-button--PaddingBottom: var(--pf-global--spacer--md);
|
||||
border-inline-start: 3px solid var(--ak-c-command-palette__item--AccentColor, transparent);
|
||||
background-color: color-mix(
|
||||
var(--ak-c-command-palette__item--BackgroundColor),
|
||||
transparent var(--ak-c-command-palette--Translucency)
|
||||
);
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
text-align: start;
|
||||
color: var(--ak-c-command-palette__item--Color);
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(
|
||||
var(--ak-c-command-palette__item--hover--BackgroundColor),
|
||||
transparent var(--ak-c-command-palette--Translucency)
|
||||
);
|
||||
}
|
||||
--ak-c-command-palette__item--AccentColor: var(--ak-accent);
|
||||
}
|
||||
}
|
||||
|
||||
[part="label"] {
|
||||
[part="command-button"] {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"prefix prefix prefix suffix"
|
||||
"icon label label suffix"
|
||||
"icon description description suffix";
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
padding: var(--pf-global--spacer--md);
|
||||
border: none;
|
||||
column-gap: var(--pf-global--spacer--sm);
|
||||
border-inline-start: 3px solid var(--ak-c-command-palette__item--AccentColor, transparent);
|
||||
background-color: color-mix(
|
||||
var(--ak-c-command-palette__item--BackgroundColor),
|
||||
transparent var(--ak-c-command-palette--Translucency)
|
||||
);
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
text-align: start;
|
||||
color: var(--ak-c-command-palette__item--Color);
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(
|
||||
var(--ak-c-command-palette__item--hover--BackgroundColor),
|
||||
transparent var(--ak-c-command-palette--Translucency)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[part="input-label"] {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
inset-block-start: var(--ak-c-command-palette--PaddingBlock);
|
||||
|
||||
@@ -4,12 +4,11 @@ import { torusIndex } from "#common/collections";
|
||||
import { PFSize } from "#common/enums";
|
||||
|
||||
import Styles from "#elements/commands/ak-command-palette-modal.css";
|
||||
import { AKRegisterCommandsEvent } from "#elements/commands/events";
|
||||
import { CommandPaletteCommand } from "#elements/commands/shared";
|
||||
import { AKCommandChangeEvent } from "#elements/commands/events";
|
||||
import { PaletteCommandDefinition } from "#elements/commands/shared";
|
||||
import { listen } from "#elements/decorators/listen";
|
||||
import { AKModal } from "#elements/modals/ak-modal";
|
||||
import { asInvoker } from "#elements/modals/utils";
|
||||
import { navigate } from "#elements/router/RouterOutlet";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
import { FocusTarget } from "#elements/utils/focus";
|
||||
|
||||
@@ -21,9 +20,42 @@ import { msg, str } from "@lit/localize";
|
||||
import { html, PropertyValues } from "lit";
|
||||
import { guard } from "lit-html/directives/guard.js";
|
||||
import { createRef, ref } from "lit-html/directives/ref.js";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import { repeat } from "lit/directives/repeat.js";
|
||||
|
||||
function openDocsSearch(query: string) {
|
||||
const url = new URL("/search", import.meta.env.AK_DOCS_URL);
|
||||
url.searchParams.set("q", query);
|
||||
|
||||
window.open(url, "_ak_docs", "noopener,noreferrer");
|
||||
}
|
||||
|
||||
function createCommonCommands(): PaletteCommandDefinition<unknown>[] {
|
||||
return [
|
||||
{
|
||||
label: msg("Integrations"),
|
||||
prefix: msg("View", { id: "command-palette.prefix.view" }),
|
||||
action: () => window.open("https://integrations.goauthentik.io/", "_blank"),
|
||||
group: msg("Documentation"),
|
||||
},
|
||||
{
|
||||
label: msg("Release notes"),
|
||||
action: () => window.open(import.meta.env.AK_DOCS_RELEASE_NOTES_URL, "_blank"),
|
||||
prefix: msg("View", { id: "command-palette.prefix.view" }),
|
||||
suffix: msg(str`New in ${import.meta.env.AK_VERSION}`, {
|
||||
id: "command-palette.suffix.new-in",
|
||||
}),
|
||||
group: msg("authentik"),
|
||||
},
|
||||
{
|
||||
label: msg("About authentik"),
|
||||
action: AboutModal.open,
|
||||
prefix: msg("View", { id: "command-palette.prefix.view" }),
|
||||
group: msg("authentik"),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@customElement("ak-command-palette-modal")
|
||||
export class AKCommandPaletteModal extends AKModal {
|
||||
static openOnConnect = false;
|
||||
@@ -35,6 +67,9 @@ export class AKCommandPaletteModal extends AKModal {
|
||||
protected autofocusTarget = new FocusTarget<HTMLInputElement>();
|
||||
protected formRef = createRef<HTMLFormElement>();
|
||||
|
||||
#scrollCommandFrameID = -1;
|
||||
#autoFocusFrameID = -1;
|
||||
|
||||
// TODO: Fix form references.
|
||||
declare form: null;
|
||||
|
||||
@@ -42,12 +77,17 @@ export class AKCommandPaletteModal extends AKModal {
|
||||
return this.autofocusTarget.target?.value.trim() || "";
|
||||
}
|
||||
|
||||
protected fuse = new Fuse<CommandPaletteCommand>([], {
|
||||
protected fuse = new Fuse<PaletteCommandDefinition>([], {
|
||||
keys: [
|
||||
// ---
|
||||
{ name: "label", weight: 3 },
|
||||
"description",
|
||||
"group",
|
||||
{
|
||||
name: "keywords",
|
||||
getFn: (command) => command.keywords?.join(" ") || "",
|
||||
weight: 2,
|
||||
},
|
||||
],
|
||||
findAllMatches: true,
|
||||
includeScore: true,
|
||||
@@ -62,56 +102,32 @@ export class AKCommandPaletteModal extends AKModal {
|
||||
@property({ type: Number, attribute: false, useDefault: true })
|
||||
public selectionIndex = 1;
|
||||
|
||||
public get selectedCommand(): PaletteCommandDefinition | null {
|
||||
if (this.selectionIndex === -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.filteredCommands[this.selectionIndex] || null;
|
||||
}
|
||||
|
||||
@property({ type: Number, attribute: false, useDefault: true })
|
||||
public maxCount = 20;
|
||||
|
||||
@property({ type: Array, attribute: false, useDefault: true })
|
||||
public filteredCommands: readonly CommandPaletteCommand[] = [];
|
||||
public filteredCommands: readonly PaletteCommandDefinition<unknown>[] = [];
|
||||
|
||||
@property({ attribute: false, type: Array })
|
||||
public commands: CommandPaletteCommand[] = [
|
||||
{
|
||||
label: msg("Create a new application..."),
|
||||
action: () => navigate("/core/applications", { createWizard: true }),
|
||||
suffix: msg("Jump to", { id: "command-palette.prefix.jump-to" }),
|
||||
group: msg("Applications"),
|
||||
},
|
||||
{
|
||||
label: msg("Check the logs"),
|
||||
action: () => navigate("/events/log"),
|
||||
group: msg("Events"),
|
||||
},
|
||||
{
|
||||
label: msg("Manage users"),
|
||||
action: () => navigate("/identity/users"),
|
||||
group: msg("Users"),
|
||||
},
|
||||
{
|
||||
label: msg("Explore integrations"),
|
||||
action: () => window.open("https://integrations.goauthentik.io/", "_blank"),
|
||||
group: msg("authentik"),
|
||||
},
|
||||
{
|
||||
label: msg("Check the release notes"),
|
||||
action: () => window.open(import.meta.env.AK_DOCS_RELEASE_NOTES_URL, "_blank"),
|
||||
/**
|
||||
* A map of the currently filtered commands to their index in the flattened commands array,
|
||||
* used to normalize the selection index while rendering groups.
|
||||
*/
|
||||
#filteredCommandsIndex = new Map<PaletteCommandDefinition<unknown>, number>();
|
||||
/**
|
||||
* A flattened array of all commands in the command palette, used for filtering and selection.
|
||||
*/
|
||||
#flattenedCommands: PaletteCommandDefinition<unknown>[] = [];
|
||||
|
||||
suffix: msg(str`New in ${import.meta.env.AK_VERSION}`, {
|
||||
id: "command-palette.suffix.new-in",
|
||||
}),
|
||||
group: msg("authentik"),
|
||||
},
|
||||
{
|
||||
label: msg("View documentation"),
|
||||
action: () => this.#openDocs(),
|
||||
suffix: msg("New Tab", { id: "command-palette.suffix.view-docs" }),
|
||||
group: msg("authentik"),
|
||||
},
|
||||
{
|
||||
label: msg("About authentik"),
|
||||
action: AboutModal.open,
|
||||
group: msg("authentik"),
|
||||
},
|
||||
];
|
||||
@state()
|
||||
public commands = new Set<readonly PaletteCommandDefinition<unknown>[]>();
|
||||
|
||||
public override size = PFSize.Medium;
|
||||
|
||||
@@ -119,16 +135,47 @@ export class AKCommandPaletteModal extends AKModal {
|
||||
|
||||
//#region Public Methods
|
||||
|
||||
public addCommands = (commands: CommandPaletteCommand[]) => {
|
||||
this.commands = [...this.commands, ...commands];
|
||||
public setCommands = (
|
||||
commands?: readonly PaletteCommandDefinition<unknown>[] | null,
|
||||
previousCommands?: readonly PaletteCommandDefinition<unknown>[] | null,
|
||||
) => {
|
||||
if (previousCommands) {
|
||||
this.commands.delete(previousCommands);
|
||||
}
|
||||
|
||||
if (commands) {
|
||||
this.commands.add(commands);
|
||||
this.#flattenedCommands = Array.from(this.commands).reverse().flat();
|
||||
}
|
||||
|
||||
const { target } = this.autofocusTarget;
|
||||
|
||||
if (target) {
|
||||
target.value = "";
|
||||
}
|
||||
|
||||
if (this.open && (commands || previousCommands)) {
|
||||
this.requestUpdate("commands");
|
||||
}
|
||||
};
|
||||
|
||||
public scrollCommandIntoView = (commandIndex = this.selectionIndex) => {
|
||||
const id = `command-${commandIndex}`;
|
||||
public scrollCommandIntoView = () => {
|
||||
const id = `command-${this.selectionIndex}`;
|
||||
|
||||
const element = this.renderRoot.querySelector(`#${id}`);
|
||||
|
||||
element?.scrollIntoView({
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const legend = element.closest("fieldset")?.querySelector("legend");
|
||||
|
||||
legend?.scrollIntoView({
|
||||
behavior: "auto",
|
||||
block: "nearest",
|
||||
});
|
||||
|
||||
element.scrollIntoView({
|
||||
behavior: "auto",
|
||||
block: "nearest",
|
||||
});
|
||||
@@ -139,59 +186,80 @@ export class AKCommandPaletteModal extends AKModal {
|
||||
public override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.addEventListener("focus", this.autofocusTarget.toEventListener());
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.setCommands([
|
||||
{
|
||||
label: msg("Documentation"),
|
||||
action: () => openDocsSearch(this.value),
|
||||
keywords: [msg("Docs"), msg("Readme"), msg("Help")],
|
||||
prefix: msg("View", { id: "command-palette.prefix.view" }),
|
||||
suffix: msg("New Tab", { id: "command-palette.suffix.view-docs" }),
|
||||
group: msg("Documentation"),
|
||||
},
|
||||
...createCommonCommands(),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
public override updated(changedProperties: PropertyValues<this>): void {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has("commands")) {
|
||||
this.fuse.setCollection(this.commands);
|
||||
this.fuse.setCollection(this.#flattenedCommands);
|
||||
this.selectionIndex = 0;
|
||||
this.synchronizeFilteredCommands();
|
||||
}
|
||||
|
||||
if (changedProperties.has("open") && this.open) {
|
||||
requestAnimationFrame(() => {
|
||||
cancelAnimationFrame(this.#autoFocusFrameID);
|
||||
|
||||
this.#autoFocusFrameID = requestAnimationFrame(() => {
|
||||
this.autofocusTarget.focus();
|
||||
this.autofocusTarget.target?.select();
|
||||
});
|
||||
}
|
||||
|
||||
if (changedProperties.has("selectionIndex")) {
|
||||
this.scrollCommandIntoView();
|
||||
cancelAnimationFrame(this.#scrollCommandFrameID);
|
||||
this.#scrollCommandFrameID = requestAnimationFrame(this.scrollCommandIntoView);
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
#scrollCommandFrameID = -1;
|
||||
|
||||
public synchronizeFilteredCommands = () => {
|
||||
cancelAnimationFrame(this.#scrollCommandFrameID);
|
||||
|
||||
const { value } = this;
|
||||
|
||||
if (!value) {
|
||||
this.filteredCommands = this.commands.slice(0, this.maxCount);
|
||||
return;
|
||||
}
|
||||
|
||||
const filteredCommands = this.fuse
|
||||
.search(value, {
|
||||
limit: this.maxCount,
|
||||
})
|
||||
.map((result) => result.item);
|
||||
|
||||
filteredCommands.push({
|
||||
label: msg(str`Search the docs for "${value}"`),
|
||||
suffix: msg("New Tab", { id: "command-palette.suffix.search-docs" }),
|
||||
action: this.#openDocs,
|
||||
});
|
||||
|
||||
this.filteredCommands = filteredCommands;
|
||||
this.selectionIndex = 0;
|
||||
|
||||
this.#scrollCommandFrameID = requestAnimationFrame(() => this.scrollCommandIntoView());
|
||||
const { value } = this;
|
||||
|
||||
if (value) {
|
||||
const filteredCommands = this.fuse
|
||||
.search(value, {
|
||||
limit: this.maxCount,
|
||||
})
|
||||
.map((result) => result.item);
|
||||
|
||||
filteredCommands.push({
|
||||
group: msg("Documentation"),
|
||||
label: msg(str`Search the docs for "${value}"`),
|
||||
prefix: msg("Open", { id: "command-palette.prefix.open" }),
|
||||
suffix: msg("New Tab", { id: "command-palette.suffix.view-docs" }),
|
||||
action: () => openDocsSearch(value),
|
||||
});
|
||||
|
||||
this.filteredCommands = filteredCommands;
|
||||
} else {
|
||||
this.filteredCommands = this.#flattenedCommands.slice(0, this.maxCount);
|
||||
}
|
||||
|
||||
this.#filteredCommandsIndex = new Map(
|
||||
this.filteredCommands.map((command, index) => [command, index]),
|
||||
);
|
||||
|
||||
this.#scrollCommandFrameID = requestAnimationFrame(this.scrollCommandIntoView);
|
||||
};
|
||||
|
||||
public submit() {
|
||||
@@ -222,7 +290,7 @@ export class AKCommandPaletteModal extends AKModal {
|
||||
if (!command) return;
|
||||
|
||||
this.open = false;
|
||||
command.action();
|
||||
command.action(command.details || null);
|
||||
};
|
||||
|
||||
#commandClickListener = (event: MouseEvent) => {
|
||||
@@ -236,12 +304,12 @@ export class AKCommandPaletteModal extends AKModal {
|
||||
|
||||
//#region Event Listeners
|
||||
|
||||
@listen(AKRegisterCommandsEvent, {
|
||||
target: window,
|
||||
@listen(AKCommandChangeEvent, {
|
||||
target: this,
|
||||
})
|
||||
protected registerCommandsListener(event: AKRegisterCommandsEvent) {
|
||||
this.commands = [...this.commands, ...event.commands];
|
||||
}
|
||||
protected commandChangeListener = (event: AKCommandChangeEvent) => {
|
||||
this.setCommands(event.commands, event.previousCommands);
|
||||
};
|
||||
|
||||
#keydownListener = (event: KeyboardEvent) => {
|
||||
const visibleCommandsCount = this.filteredCommands.length;
|
||||
@@ -293,24 +361,21 @@ export class AKCommandPaletteModal extends AKModal {
|
||||
return null;
|
||||
}
|
||||
|
||||
#openDocs = (): void => {
|
||||
const url = new URL("/search", import.meta.env.AK_DOCS_URL);
|
||||
url.searchParams.set("q", this.value);
|
||||
|
||||
window.open(url, "_ak_docs", "noopener,noreferrer");
|
||||
};
|
||||
|
||||
protected renderCommands() {
|
||||
const { selectionIndex, value, filteredCommands } = this;
|
||||
|
||||
return guard([filteredCommands, selectionIndex, value], () => {
|
||||
const grouped = Object.groupBy(filteredCommands, (command) => command.group || "");
|
||||
let commandCount = 0;
|
||||
|
||||
return html`<div part="results">
|
||||
return html`<div
|
||||
part="results"
|
||||
role="listbox"
|
||||
id="command-suggestions"
|
||||
aria-label=${msg("Query suggestions")}
|
||||
>
|
||||
${repeat(
|
||||
Object.entries(grouped),
|
||||
([groupLabel]) => groupLabel,
|
||||
(_, groupIdx) => `group-${groupIdx}`,
|
||||
([groupLabel, commands], groupIdx) => html`
|
||||
<fieldset part="results-group">
|
||||
<legend
|
||||
@@ -324,43 +389,61 @@ export class AKCommandPaletteModal extends AKModal {
|
||||
<ul
|
||||
part="results-list"
|
||||
data-group-index=${groupIdx}
|
||||
role="listbox"
|
||||
id="command-suggestions"
|
||||
aria-label=${msg("Query suggestions")}
|
||||
role="presentation"
|
||||
>
|
||||
${repeat(
|
||||
commands!,
|
||||
(command) => command,
|
||||
({ label, prefix, suffix }) => {
|
||||
const relativeIdx = commandCount;
|
||||
commandCount++;
|
||||
(_, commandIdx) => `group-${groupIdx}-command-${commandIdx}`,
|
||||
(command) => {
|
||||
const absoluteIdx =
|
||||
this.#filteredCommandsIndex.get(command) ?? -1;
|
||||
const { label, prefix, suffix, description } = command;
|
||||
|
||||
const selected = selectionIndex === relativeIdx;
|
||||
const selected = selectionIndex === absoluteIdx;
|
||||
return html`<li
|
||||
role="option"
|
||||
id="command-${relativeIdx}"
|
||||
role="presentation"
|
||||
id="command-${absoluteIdx}"
|
||||
aria-selected=${selected ? "true" : "false"}
|
||||
class="command-item ${selected ? "selected" : ""}"
|
||||
part="command-item"
|
||||
>
|
||||
<button
|
||||
class="pf-c-button"
|
||||
part="command-button"
|
||||
type="submit"
|
||||
formmethod="dialog"
|
||||
data-index=${relativeIdx}
|
||||
data-index=${absoluteIdx}
|
||||
@click=${this.#commandClickListener}
|
||||
aria-labelledby="command-${absoluteIdx}-label"
|
||||
aria-describedby="command-${absoluteIdx}-description"
|
||||
>
|
||||
${prefix
|
||||
? html`<span part="command-item-prefix"
|
||||
>${prefix}</span
|
||||
>`
|
||||
? html`<div
|
||||
part="command-item-prefix"
|
||||
id="command-${absoluteIdx}-prefix"
|
||||
>
|
||||
${prefix}
|
||||
</div>`
|
||||
: null}
|
||||
<span part="command-item-label">${label}</span>
|
||||
<div
|
||||
part="command-item-label"
|
||||
id="command-${absoluteIdx}-label"
|
||||
>
|
||||
${label}
|
||||
</div>
|
||||
${suffix
|
||||
? html`<span part="command-item-suffix"
|
||||
>${suffix}</span
|
||||
>`
|
||||
? html`<div
|
||||
part="command-item-suffix"
|
||||
id="command-${absoluteIdx}-suffix"
|
||||
>
|
||||
${suffix}
|
||||
</div>`
|
||||
: null}
|
||||
<div
|
||||
part="command-item-description"
|
||||
id="command-${absoluteIdx}-description"
|
||||
>
|
||||
${description || ""}
|
||||
</div>
|
||||
</button>
|
||||
</li>`;
|
||||
},
|
||||
@@ -395,8 +478,8 @@ export class AKCommandPaletteModal extends AKModal {
|
||||
>
|
||||
<div part="command-field">
|
||||
<label
|
||||
part="label"
|
||||
for="locale-selector"
|
||||
part="input-label"
|
||||
for="command-input"
|
||||
@click=${this.show}
|
||||
aria-label=${msg("Type a command...", {
|
||||
id: "command-palette-placeholder",
|
||||
@@ -423,7 +506,10 @@ export class AKCommandPaletteModal extends AKModal {
|
||||
name="command"
|
||||
aria-controls="command-suggestions"
|
||||
type="search"
|
||||
placeholder=${msg("Type a command...")}
|
||||
placeholder=${msg("What are you looking for?", {
|
||||
id: "command-palette-placeholder-extended",
|
||||
desc: "Placeholder for the command palette input",
|
||||
})}
|
||||
class="pf-c-control command-input"
|
||||
autocomplete="off"
|
||||
autocapitalize="off"
|
||||
|
||||
@@ -5,7 +5,7 @@ ak-command-palette {
|
||||
.ak-c-modal:has(ak-command-palette-modal) {
|
||||
overflow: visible;
|
||||
--ak-c-modal--MarginBlockStart: 25dvh;
|
||||
--ak-c-modal--BackgroundColor: var(--pf-global--palette--cyan-700);
|
||||
--ak-c-modal--BackgroundColor: var(--pf-global--palette--purple-700);
|
||||
--ak-c-modal--BorderColor: var(--pf-global--BackgroundColor--dark-300);
|
||||
--ak-c-modal__backdrop--active--BackdropFilter: blur(1px);
|
||||
--ak-c-modal__backdrop--active--BackgroundColor: hsla(0, 0%, 0%, 0.25);
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
import { CommandPaletteCommand } from "#elements/commands/shared";
|
||||
import type { PaletteCommandDefinition } from "#elements/commands/shared";
|
||||
|
||||
/**
|
||||
* Event dispatched when the state of the interface drawers changes.
|
||||
* Event dispatched when the available commands in the command palette change.
|
||||
* This is used by the command palette to update the list of available commands.
|
||||
*/
|
||||
export class AKRegisterCommandsEvent extends Event {
|
||||
public static readonly eventName = "ak-register-commands";
|
||||
export class AKCommandChangeEvent<D = unknown> extends Event {
|
||||
public static readonly eventName = "ak-command-change";
|
||||
|
||||
public readonly commands: CommandPaletteCommand[];
|
||||
constructor(commands: CommandPaletteCommand[]) {
|
||||
super(AKRegisterCommandsEvent.eventName, { bubbles: true, composed: true });
|
||||
public readonly commands: readonly PaletteCommandDefinition<D>[];
|
||||
public readonly previousCommands: readonly PaletteCommandDefinition<D>[] | null;
|
||||
|
||||
constructor(
|
||||
commands: PaletteCommandDefinition<D>[],
|
||||
previousCommands?: PaletteCommandDefinition<D>[] | null,
|
||||
) {
|
||||
super(AKCommandChangeEvent.eventName, { bubbles: true, composed: true });
|
||||
|
||||
this.commands = commands;
|
||||
this.previousCommands = previousCommands ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface WindowEventMap {
|
||||
[AKRegisterCommandsEvent.eventName]: AKRegisterCommandsEvent;
|
||||
[AKCommandChangeEvent.eventName]: AKCommandChangeEvent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,41 @@
|
||||
import { AKCommandChangeEvent } from "#elements/commands/events";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
export interface CommandPaletteCommand {
|
||||
export type PaletteCommandAction<D = unknown> = (data: D) => unknown | Promise<unknown>;
|
||||
|
||||
export interface PaletteCommandDefinition<D = unknown> {
|
||||
label: SlottedTemplateResult;
|
||||
keywords?: string[];
|
||||
prefix?: SlottedTemplateResult;
|
||||
suffix?: SlottedTemplateResult;
|
||||
description?: SlottedTemplateResult;
|
||||
group?: string;
|
||||
action: () => unknown | Promise<unknown>;
|
||||
details?: D;
|
||||
action: PaletteCommandAction<D>;
|
||||
}
|
||||
|
||||
export interface CommandPaletteStateInit<D = unknown> {
|
||||
commands?: PaletteCommandDefinition<D>[] | null;
|
||||
target?: EventTarget;
|
||||
}
|
||||
|
||||
export class CommandPaletteState<D = unknown> {
|
||||
#commands: PaletteCommandDefinition<D>[] | null = null;
|
||||
#target: EventTarget;
|
||||
|
||||
constructor({ commands = null, target = window }: CommandPaletteStateInit<D> = {}) {
|
||||
this.#commands = commands;
|
||||
this.#target = target ?? window;
|
||||
}
|
||||
|
||||
public set(nextCommands: PaletteCommandDefinition<D>[] | null): void {
|
||||
const previousCommands = this.#commands;
|
||||
this.#commands = nextCommands;
|
||||
|
||||
this.#target.dispatchEvent(new AKCommandChangeEvent(nextCommands ?? [], previousCommands));
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { type APIResult, isAPIResultReady } from "#common/api/responses";
|
||||
import { globalAK } from "#common/global";
|
||||
import { applyThemeChoice, formatColorScheme } from "#common/theme";
|
||||
import { createUIConfig, DefaultUIConfig } from "#common/ui/config";
|
||||
import { autoDetectLanguage } from "#common/ui/locale/utils";
|
||||
import { me } from "#common/users";
|
||||
|
||||
import { CommandPaletteState, PaletteCommandDefinition } from "#elements/commands/shared";
|
||||
import { ReactiveContextController } from "#elements/controllers/ReactiveContextController";
|
||||
import { AKConfigMixin, kAKConfig } from "#elements/mixins/config";
|
||||
import { kAKLocale, type LocaleMixin } from "#elements/mixins/locale";
|
||||
import { SessionContext, SessionMixin, UIConfigContext } from "#elements/mixins/session";
|
||||
import {
|
||||
canAccessAdmin,
|
||||
SessionContext,
|
||||
SessionMixin,
|
||||
UIConfigContext,
|
||||
} from "#elements/mixins/session";
|
||||
import { AKDrawerChangeEvent } from "#elements/notifications/events";
|
||||
import type { ReactiveElementHost } from "#elements/types";
|
||||
|
||||
import { SessionUser } from "@goauthentik/api";
|
||||
import { CoreApi, SessionUser } from "@goauthentik/api";
|
||||
|
||||
import { setUser } from "@sentry/browser";
|
||||
|
||||
import { ContextProvider } from "@lit/context";
|
||||
import { msg } from "@lit/localize";
|
||||
|
||||
/**
|
||||
* A controller that provides the session information to the element.
|
||||
@@ -52,38 +62,128 @@ export class SessionContextController extends ReactiveContextController<APIResul
|
||||
return me(requestInit);
|
||||
}
|
||||
|
||||
#refreshCommandsFrameID = -1;
|
||||
|
||||
#commands = new CommandPaletteState({
|
||||
target: this.host,
|
||||
});
|
||||
|
||||
protected doRefresh(session: APIResult<SessionUser>): void {
|
||||
this.context.setValue(session);
|
||||
this.host.session = session;
|
||||
|
||||
if (isAPIResultReady(session)) {
|
||||
const localeHint: string | undefined = session.user.settings.locale;
|
||||
if (!isAPIResultReady(session)) return;
|
||||
|
||||
if (localeHint) {
|
||||
const locale = autoDetectLanguage(localeHint);
|
||||
this.logger.info(`Activating user's configured locale '${locale}'`);
|
||||
this.host[kAKLocale]?.setLocale(locale);
|
||||
}
|
||||
const localeHint: string | undefined = session.user.settings.locale;
|
||||
|
||||
const { settings = {} } = session.user || {};
|
||||
|
||||
const nextUIConfig = createUIConfig(settings);
|
||||
this.uiConfigContext.setValue(nextUIConfig);
|
||||
this.host.uiConfig = nextUIConfig;
|
||||
const colorScheme = formatColorScheme(nextUIConfig.theme.base);
|
||||
|
||||
applyThemeChoice(colorScheme, this.host.ownerDocument);
|
||||
|
||||
const config = this.host[kAKConfig];
|
||||
|
||||
if (config?.errorReporting.sendPii) {
|
||||
this.logger.info("Sentry with PII enabled.");
|
||||
|
||||
setUser({ email: session.user.email });
|
||||
}
|
||||
if (localeHint) {
|
||||
const locale = autoDetectLanguage(localeHint);
|
||||
this.logger.info(`Activating user's configured locale '${locale}'`);
|
||||
this.host[kAKLocale]?.setLocale(locale);
|
||||
}
|
||||
|
||||
const { settings = {} } = session.user || {};
|
||||
|
||||
const nextUIConfig = createUIConfig(settings);
|
||||
this.uiConfigContext.setValue(nextUIConfig);
|
||||
this.host.uiConfig = nextUIConfig;
|
||||
const colorScheme = formatColorScheme(nextUIConfig.theme.base);
|
||||
|
||||
applyThemeChoice(colorScheme, this.host.ownerDocument);
|
||||
|
||||
const config = this.host[kAKConfig];
|
||||
|
||||
if (config?.errorReporting.sendPii) {
|
||||
this.logger.info("Sentry with PII enabled.");
|
||||
|
||||
setUser({ email: session.user.email });
|
||||
}
|
||||
|
||||
this.#refreshCommandsFrameID = requestAnimationFrame(this.#refreshCommands);
|
||||
}
|
||||
|
||||
#refreshCommands = (): void => {
|
||||
const session = this.context.value;
|
||||
|
||||
if (!isAPIResultReady(session)) {
|
||||
this.#commands.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const base = globalAK().api.base;
|
||||
const group = msg("Session");
|
||||
|
||||
const commands: PaletteCommandDefinition[] = [
|
||||
{
|
||||
label: msg("Sign out"),
|
||||
suffix: msg("Reloads page", { id: "command-palette.prefix.reloads-page" }),
|
||||
keywords: [msg("Logout"), msg("Log off"), msg("Sign off")],
|
||||
group,
|
||||
action: () => {
|
||||
window.location.assign(`${base}flows/-/default/invalidation/`);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: msg("User settings"),
|
||||
prefix: msg("Navigate to", { id: "command-palette.prefix.navigate" }),
|
||||
group,
|
||||
action: () => {
|
||||
window.location.assign(`${base}if/user/#/settings`);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const { notificationDrawer, apiDrawer } = this.host.uiConfig?.enabledFeatures ?? {};
|
||||
const drawerGroup = msg("Interface");
|
||||
|
||||
if (apiDrawer) {
|
||||
commands.push({
|
||||
label: msg("API requests drawer", {
|
||||
id: "command-palette.label.api-requests-drawer",
|
||||
}),
|
||||
prefix: msg("Toggle", { id: "command-palette.prefix.toggle" }),
|
||||
group: drawerGroup,
|
||||
action: AKDrawerChangeEvent.dispatchAPIToggle,
|
||||
});
|
||||
}
|
||||
|
||||
if (notificationDrawer) {
|
||||
commands.push({
|
||||
label: msg("Notifications drawer", {
|
||||
id: "command-palette.label.notifications-drawer",
|
||||
}),
|
||||
prefix: msg("Toggle", { id: "command-palette.prefix.toggle" }),
|
||||
group: drawerGroup,
|
||||
action: AKDrawerChangeEvent.dispatchNotificationsToggle,
|
||||
});
|
||||
}
|
||||
|
||||
if (canAccessAdmin(session.user)) {
|
||||
commands.push({
|
||||
label: msg("Admin interface"),
|
||||
prefix: msg("Navigate to", { id: "command-palette.prefix.navigate" }),
|
||||
group,
|
||||
action: () => {
|
||||
window.location.assign(`${base}if/admin/`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (session.original) {
|
||||
commands.push({
|
||||
label: msg("Stop impersonation"),
|
||||
suffix: msg("Reloads page", { id: "command-palette.prefix.reloads-page" }),
|
||||
group,
|
||||
action: async () => {
|
||||
await new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve();
|
||||
window.location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.#commands.set(commands);
|
||||
};
|
||||
|
||||
public override hostConnected() {
|
||||
this.logger.debug("Host connected, refreshing session");
|
||||
this.refresh();
|
||||
@@ -91,6 +191,7 @@ export class SessionContextController extends ReactiveContextController<APIResul
|
||||
|
||||
public override hostDisconnected() {
|
||||
this.context.clearCallbacks();
|
||||
cancelAnimationFrame(this.#refreshCommandsFrameID);
|
||||
|
||||
super.hostDisconnected();
|
||||
}
|
||||
|
||||
@@ -127,8 +127,8 @@
|
||||
/* #region Footer */
|
||||
|
||||
fieldset.ak-c-modal__footer {
|
||||
--ak-legend-padding-inline-base: var(--pf-global--spacer--md);
|
||||
padding-block: calc(var(--ak-legend-padding-inline-base) / 2);
|
||||
--ak-fieldset__legend--PaddingInlineBase: var(--pf-global--spacer--md);
|
||||
padding-block: calc(var(--ak-fieldset__legend--PaddingInlineBase) / 2);
|
||||
border-inline: none;
|
||||
border-block-end: none;
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
margin-block-end: var(--pf-global--spacer--md);
|
||||
|
||||
@media not (prefers-contrast: more) {
|
||||
--ak-legend-margin-inline-start: calc(
|
||||
var(--pf-c-card--child--PaddingLeft) - var(--ak-legend-padding-inline-base)
|
||||
--ak-fieldset__legend--MarginInlineStart: calc(
|
||||
var(--pf-c-card--child--PaddingLeft) - var(--ak-fieldset__legend--PaddingInlineBase)
|
||||
);
|
||||
--ak-legend-margin-inline-end: calc(
|
||||
var(--pf-c-card--child--PaddingRight) - var(--ak-legend-padding-inline-base)
|
||||
var(--pf-c-card--child--PaddingRight) - var(--ak-fieldset__legend--PaddingInlineBase)
|
||||
);
|
||||
|
||||
border-width: 0;
|
||||
|
||||
@@ -76,34 +76,34 @@
|
||||
/* #region Fields */
|
||||
|
||||
fieldset {
|
||||
--ak-fieldset-border-width: thin;
|
||||
--ak-fieldset-border-color: var(--pf-global--BackgroundColor--light-100);
|
||||
--ak-legend-margin-inline-base: var(--pf-global--spacer--sm);
|
||||
--ak-legend-padding-inline-base: var(--pf-global--spacer--sm);
|
||||
--ak-fieldset--BorderWidth: thin;
|
||||
--ak-fieldset__legend--MarginInlineBase: var(--pf-global--spacer--sm);
|
||||
--ak-fieldset__legend--PaddingInlineBase: var(--pf-global--spacer--sm);
|
||||
|
||||
border-color: var(--ak-fieldset-border-color);
|
||||
border-width: var(--ak-fieldset-border-width);
|
||||
|
||||
padding: var(--ak-legend-padding-inline-base) !important;
|
||||
border-color: var(--ak-fieldset--BorderColor, var(--pf-global--BackgroundColor--light-100));
|
||||
|
||||
@media (prefers-contrast: more) {
|
||||
--ak-fieldset-border-color: var(--pf-global--BorderColor--200);
|
||||
border-color: var(--ak-fieldset--BorderColor, var(--pf-global--BorderColor--200));
|
||||
}
|
||||
|
||||
@media (prefers-contrast: less) {
|
||||
--ak-fieldset-border-color: transparent;
|
||||
border-color: var(--ak-fieldset--BorderColor, transparent);
|
||||
}
|
||||
|
||||
border-width: var(--ak-fieldset--BorderWidth);
|
||||
|
||||
padding: var(--ak-fieldset__legend--PaddingInlineBase) !important;
|
||||
|
||||
& > legend {
|
||||
line-height: 1;
|
||||
padding: var(--ak-legend-padding-inline-base) !important;
|
||||
padding: var(--ak-fieldset__legend--PaddingInlineBase) !important;
|
||||
margin-inline-start: var(
|
||||
--ak-legend-margin-inline-start,
|
||||
var(--ak-legend-margin-inline-base)
|
||||
--ak-fieldset__legend--MarginInlineStart,
|
||||
var(--ak-fieldset__legend--MarginInlineBase)
|
||||
) !important;
|
||||
margin-inline-end: var(
|
||||
--ak-legend-margin-inline-end,
|
||||
var(--ak-legend-margin-inline-base)
|
||||
var(--ak-fieldset__legend--MarginInlineBase)
|
||||
) !important;
|
||||
}
|
||||
|
||||
@@ -111,8 +111,8 @@ fieldset {
|
||||
border-width: 0;
|
||||
|
||||
&:not(.pf-c-modal-box__footer) {
|
||||
--ak-legend-padding-inline-base: 0;
|
||||
--ak-legend-margin-inline-base: 0;
|
||||
--ak-fieldset__legend--PaddingInlineBase: 0;
|
||||
--ak-fieldset__legend--MarginInlineBase: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +137,9 @@ fieldset {
|
||||
}
|
||||
|
||||
&.pf-c-modal-box__footer {
|
||||
--ak-legend-padding-inline-base: var(--pf-global--spacer--md);
|
||||
padding-block: calc(var(--ak-legend-padding-inline-base) / 2);
|
||||
--ak-fieldset__legend--PaddingInlineBase: var(--pf-global--spacer--md);
|
||||
|
||||
padding-block: calc(var(--ak-fieldset__legend--PaddingInlineBase) / 2);
|
||||
border-inline: none;
|
||||
border-block-end: none;
|
||||
|
||||
@@ -209,14 +210,17 @@ fieldset {
|
||||
}
|
||||
|
||||
fieldset {
|
||||
--ak-fieldset-border-color: var(--pf-global--BackgroundColor--dark-transparent-200);
|
||||
border-color: var(
|
||||
--ak-fieldset--BorderColor,
|
||||
var(--pf-global--BackgroundColor--dark-transparent-200)
|
||||
);
|
||||
|
||||
@media (prefers-contrast: more) {
|
||||
--ak-fieldset-border-color: var(--pf-global--BorderColor--300);
|
||||
border-color: var(--ak-fieldset--BorderColor, var(--pf-global--BorderColor--300));
|
||||
}
|
||||
|
||||
@media (prefers-contrast: less) {
|
||||
--ak-fieldset-border-color: transparent;
|
||||
border-color: var(--ak-fieldset--BorderColor, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ ak-app-icon {
|
||||
|
||||
[part="app-group-header"] {
|
||||
@media not (prefers-contrast: more) {
|
||||
--ak-legend-padding-inline-base: 1rem;
|
||||
--ak-fieldset__legend--PaddingInlineBase: 1rem;
|
||||
padding-block-start: 0 !important;
|
||||
padding-inline: 0 !important;
|
||||
margin-inline: 0 !important;
|
||||
|
||||
@@ -182,7 +182,8 @@ class UserInterface extends WithBrandConfig(WithSession(AuthenticatedInterface))
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
</div>
|
||||
${this.commandPalette}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user