Refactor routing in App component to enhance navigation and improve error handling by integrating dynamic routes and updating the NotFound route.

This commit is contained in:
becarta
2025-05-23 12:43:00 +02:00
parent f40db0f5c9
commit a544759a3b
11127 changed files with 1647032 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
declare const sizes: readonly ["small", "large"];
declare const styles: readonly ["purple", "gray", "red", "green", "yellow", "blue"];
type BadgeSize = (typeof sizes)[number];
type BadgeStyle = (typeof styles)[number];
export declare class DevToolbarBadge extends HTMLElement {
_size: BadgeSize;
_badgeStyle: BadgeStyle;
get size(): "small" | "large";
set size(value: "small" | "large");
get badgeStyle(): "red" | "purple" | "gray" | "green" | "yellow" | "blue";
set badgeStyle(value: "red" | "purple" | "gray" | "green" | "yellow" | "blue");
shadowRoot: ShadowRoot;
static observedAttributes: string[];
constructor();
connectedCallback(): void;
attributeChangedCallback(): void;
updateStyle(): void;
}
export {};

View File

@@ -0,0 +1,105 @@
import { settings } from "../settings.js";
const sizes = ["small", "large"];
const styles = ["purple", "gray", "red", "green", "yellow", "blue"];
class DevToolbarBadge extends HTMLElement {
_size = "small";
_badgeStyle = "purple";
get size() {
return this._size;
}
set size(value) {
if (!sizes.includes(value)) {
settings.logger.error(
`Invalid size: ${value}, expected one of ${sizes.join(", ")}, got ${value}.`
);
return;
}
this._size = value;
this.updateStyle();
}
get badgeStyle() {
return this._badgeStyle;
}
set badgeStyle(value) {
if (!styles.includes(value)) {
settings.logger.error(
`Invalid style: ${value}, expected one of ${styles.join(", ")}, got ${value}.`
);
return;
}
this._badgeStyle = value;
this.updateStyle();
}
shadowRoot;
static observedAttributes = ["badge-style", "size"];
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
.badge {
box-sizing: border-box;
border-radius: 4px;
border: 1px solid transparent;
padding: 8px;
font-size: 12px;
color: var(--text-color);
height: var(--size);
border: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: center;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--purple-text: rgba(224, 204, 250, 1);
--purple-border: rgba(113, 24, 226, 1);
--gray-text: rgba(191, 193, 201, 1);
--gray-border:rgba(191, 193, 201, 1);
--red-text: rgba(249, 196, 215, 1);
--red-border: rgba(179, 62, 102, 1);
--green-text: rgba(213, 249, 196, 1);
--green-border: rgba(61, 125, 31, 1);
--yellow-text: rgba(249, 233, 196, 1);
--yellow-border: rgba(181, 138, 45, 1);
--blue-text: rgba(189, 195, 255, 1);
--blue-border: rgba(54, 69, 217, 1);
--large: 24px;
--small: 20px;
}
</style>
<style id="selected-style"></style>
<div class="badge">
<slot></slot>
</div>
`;
}
connectedCallback() {
this.updateStyle();
}
attributeChangedCallback() {
if (this.hasAttribute("badge-style"))
this.badgeStyle = this.getAttribute("badge-style");
if (this.hasAttribute("size")) this.size = this.getAttribute("size");
}
updateStyle() {
const style = this.shadowRoot.querySelector("#selected-style");
if (style) {
style.innerHTML = `
.badge {
--text-color: var(--${this.badgeStyle}-text);
--border-color: var(--${this.badgeStyle}-border);
--size: var(--${this.size});
}`;
}
}
}
export {
DevToolbarBadge
};

View File

@@ -0,0 +1,24 @@
declare const sizes: readonly ["small", "medium", "large"];
declare const styles: readonly ["ghost", "outline", "purple", "gray", "red", "green", "yellow", "blue"];
declare const borderRadii: readonly ["normal", "rounded"];
type ButtonSize = (typeof sizes)[number];
type ButtonStyle = (typeof styles)[number];
type ButtonBorderRadius = (typeof borderRadii)[number];
export declare class DevToolbarButton extends HTMLElement {
_size: ButtonSize;
_buttonStyle: ButtonStyle;
_buttonBorderRadius: ButtonBorderRadius;
get size(): "small" | "large" | "medium";
set size(value: "small" | "large" | "medium");
get buttonStyle(): "red" | "purple" | "gray" | "green" | "yellow" | "blue" | "ghost" | "outline";
set buttonStyle(value: "red" | "purple" | "gray" | "green" | "yellow" | "blue" | "ghost" | "outline");
get buttonBorderRadius(): "normal" | "rounded";
set buttonBorderRadius(value: "normal" | "rounded");
static observedAttributes: string[];
shadowRoot: ShadowRoot;
constructor();
connectedCallback(): void;
updateStyle(): void;
attributeChangedCallback(): void;
}
export {};

View File

@@ -0,0 +1,159 @@
import { settings } from "../settings.js";
const sizes = ["small", "medium", "large"];
const styles = ["ghost", "outline", "purple", "gray", "red", "green", "yellow", "blue"];
const borderRadii = ["normal", "rounded"];
class DevToolbarButton extends HTMLElement {
_size = "small";
_buttonStyle = "purple";
_buttonBorderRadius = "normal";
get size() {
return this._size;
}
set size(value) {
if (!sizes.includes(value)) {
settings.logger.error(
`Invalid size: ${value}, expected one of ${sizes.join(", ")}, got ${value}.`
);
return;
}
this._size = value;
this.updateStyle();
}
get buttonStyle() {
return this._buttonStyle;
}
set buttonStyle(value) {
if (!styles.includes(value)) {
settings.logger.error(
`Invalid style: ${value}, expected one of ${styles.join(", ")}, got ${value}.`
);
return;
}
this._buttonStyle = value;
this.updateStyle();
}
get buttonBorderRadius() {
return this._buttonBorderRadius;
}
set buttonBorderRadius(value) {
if (!borderRadii.includes(value)) {
settings.logger.error(
`Invalid border-radius: ${value}, expected one of ${borderRadii.join(", ")}, got ${value}.`
);
return;
}
this._buttonBorderRadius = value;
this.updateStyle();
}
static observedAttributes = ["button-style", "size", "button-border-radius"];
shadowRoot;
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
button {
--purple-background: rgba(113, 24, 226, 1);
--purple-border: rgba(224, 204, 250, 0.33);
--purple-text: #fff;
--gray-background: rgba(52, 56, 65, 1);
--gray-border: rgba(71, 78, 94, 1);
--gray-text: #fff;
--red-background: rgba(179, 62, 102, 1);
--red-border: rgba(249, 196, 215, 0.33);
--red-text: #fff;
--green-background: rgba(213, 249, 196, 1);
--green-border: rgba(61, 125, 31, 1);
--green-text: #000;
--yellow-background: rgba(255, 236, 179, 1);
--yellow-border: rgba(255, 191, 0, 1);
--yellow-text: #000;
--blue-background: rgba(54, 69, 217, 1);
--blue-border: rgba(189, 195, 255, 1);
--blue-text: #fff;
--outline-background: transparent;
--outline-border: #fff;
--outline-text: #fff;
--ghost-background: transparent;
--ghost-border: transparent;
--ghost-text: #fff;
--large-font-size: 16px;
--medium-font-size: 14px;
--small-font-size: 12px;
--large-padding: 12px 16px;
--large-rounded-padding: 12px 12px;
--medium-padding: 8px 12px;
--medium-rounded-padding: 8px 8px;
--small-padding: 4px 8px;
--small-rounded-padding: 4px 4px;
--normal-border-radius: 4px;
--rounded-border-radius: 9999px;
border: 1px solid var(--border);
padding: var(--padding);
font-size: var(--font-size);
background: var(--background);
color: var(--text-color);
border-radius: var(--border-radius);
display: flex;
align-items: center;
justify-content: center;
}
button:hover {
cursor: pointer;
}
/* TODO: Remove "astro-dev-overlay-icon" in Astro 5.0 */
::slotted(astro-dev-overlay-icon),
::slotted(astro-dev-toolbar-icon) {
display: inline-block;
height: 1em;
width: 1em;
margin-left: 0.5em;
}
</style>
<style id="selected-style"></style>
<button>
<slot></slot>
</button>
`;
}
connectedCallback() {
this.updateStyle();
}
updateStyle() {
const style = this.shadowRoot.querySelector("#selected-style");
if (style) {
style.innerHTML = `
button {
--background: var(--${this.buttonStyle}-background);
--border: var(--${this.buttonStyle}-border);
--font-size: var(--${this.size}-font-size);
--text-color: var(--${this.buttonStyle}-text);
${this.buttonBorderRadius === "normal" ? "--padding: var(--" + this.size + "-padding);" : "--padding: var(--" + this.size + "-rounded-padding);"}
--border-radius: var(--${this.buttonBorderRadius}-border-radius);
}`;
}
}
attributeChangedCallback() {
if (this.hasAttribute("size")) this.size = this.getAttribute("size");
if (this.hasAttribute("button-style"))
this.buttonStyle = this.getAttribute("button-style");
}
}
export {
DevToolbarButton
};

View File

@@ -0,0 +1,16 @@
declare const styles: readonly ["purple", "gray", "red", "green", "yellow", "blue"];
type CardStyle = (typeof styles)[number];
export declare class DevToolbarCard extends HTMLElement {
link?: string | undefined | null;
clickAction?: () => void | (() => Promise<void>);
shadowRoot: ShadowRoot;
_cardStyle: CardStyle;
get cardStyle(): "red" | "purple" | "gray" | "green" | "yellow" | "blue";
set cardStyle(value: "red" | "purple" | "gray" | "green" | "yellow" | "blue");
static observedAttributes: string[];
constructor();
attributeChangedCallback(): void;
updateStyle(): void;
connectedCallback(): void;
}
export {};

View File

@@ -0,0 +1,116 @@
import { settings } from "../settings.js";
const styles = ["purple", "gray", "red", "green", "yellow", "blue"];
class DevToolbarCard extends HTMLElement {
link;
clickAction;
shadowRoot;
_cardStyle = "purple";
get cardStyle() {
return this._cardStyle;
}
set cardStyle(value) {
if (!styles.includes(value)) {
settings.logger.error(
`Invalid style: ${value}, expected one of ${styles.join(", ")}, got ${value}.`
);
return;
}
this._cardStyle = value;
this.updateStyle();
}
static observedAttributes = ["card-style"];
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: "open" });
this.link = this.getAttribute("link");
}
attributeChangedCallback() {
if (this.hasAttribute("card-style"))
this.cardStyle = this.getAttribute("card-style");
this.updateStyle();
}
updateStyle() {
const style = this.shadowRoot.querySelector("#selected-style");
if (style) {
style.innerHTML = `
:host {
--hover-background: var(--${this.cardStyle}-hover-background);
--hover-border: var(--${this.cardStyle}-hover-border);
}
`;
}
}
connectedCallback() {
const element = this.link ? "a" : this.clickAction ? "button" : "div";
this.shadowRoot.innerHTML += `
<style>
:host {
--purple-hover-background: rgba(136, 58, 234, 0.33);
--purple-hover-border: 1px solid rgba(113, 24, 226, 1);
--gray-hover-background: rgba(191, 193, 201, 0.33);
--gray-hover-border: 1px solid rgba(191, 193, 201, 1);
--red-hover-background: rgba(249, 196, 215, 0.33);
--red-hover-border: 1px solid rgba(179, 62, 102, 1);
--green-hover-background: rgba(213, 249, 196, 0.33);
--green-hover-border: 1px solid rgba(61, 125, 31, 1);
--yellow-hover-background: rgba(255, 236, 179, 0.33);
--yellow-hover-border: 1px solid rgba(255, 191, 0, 1);
--blue-hover-background: rgba(189, 195, 255, 0.33);
--blue-hover-border: 1px solid rgba(54, 69, 217, 1);
}
:host>a, :host>button, :host>div {
box-sizing: border-box;
padding: 16px;
display: block;
border-radius: 8px;
border: 1px solid rgba(35, 38, 45, 1);
color: rgba(191, 193, 201, 1);
text-decoration: none;
background-color: #13151A;
box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0.10), 0px 1px 2px 0px rgba(0, 0, 0, 0.10), 0px 4px 4px 0px rgba(0, 0, 0, 0.09), 0px 10px 6px 0px rgba(0, 0, 0, 0.05), 0px 17px 7px 0px rgba(0, 0, 0, 0.01), 0px 26px 7px 0px rgba(0, 0, 0, 0.00);
width: 100%;
height: 100%;
}
h1, h2, h3, h4, h5, h6 {
color: #fff;
font-weight: 600;
}
a:hover, button:hover {
background: var(--hover-background);
border: var(--hover-border);
}
svg {
display: block;
margin: 0 auto;
}
span {
margin-top: 8px;
display: block;
text-align: center;
}
</style>
<style id="selected-style"></style>
<${element}${this.link ? ` href="${this.link}" target="_blank"` : ``} id="astro-overlay-card">
<slot />
</${element}>
`;
this.updateStyle();
if (this.clickAction) {
this.shadowRoot.getElementById("astro-overlay-card")?.addEventListener("click", this.clickAction);
}
}
}
export {
DevToolbarCard
};

View File

@@ -0,0 +1,16 @@
import { type Icon } from './icons.js';
declare const styles: readonly ["purple", "gray", "red", "green", "yellow", "blue"];
type HighlightStyle = (typeof styles)[number];
export declare class DevToolbarHighlight extends HTMLElement {
icon?: Icon | undefined | null;
_highlightStyle: HighlightStyle;
get highlightStyle(): "red" | "purple" | "gray" | "green" | "yellow" | "blue";
set highlightStyle(value: "red" | "purple" | "gray" | "green" | "yellow" | "blue");
static observedAttributes: string[];
shadowRoot: ShadowRoot;
constructor();
updateStyle(): void;
attributeChangedCallback(): void;
connectedCallback(): void;
}
export {};

View File

@@ -0,0 +1,114 @@
import { settings } from "../settings.js";
import { getIconElement, isDefinedIcon } from "./icons.js";
const styles = ["purple", "gray", "red", "green", "yellow", "blue"];
class DevToolbarHighlight extends HTMLElement {
icon;
_highlightStyle = "purple";
get highlightStyle() {
return this._highlightStyle;
}
set highlightStyle(value) {
if (!styles.includes(value)) {
settings.logger.error(
`Invalid style: ${value}, expected one of ${styles.join(", ")}, got ${value}.`
);
return;
}
this._highlightStyle = value;
this.updateStyle();
}
static observedAttributes = ["highlight-style"];
shadowRoot;
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: "open" });
this.icon = this.hasAttribute("icon") ? this.getAttribute("icon") : void 0;
this.shadowRoot.innerHTML = `
<style>
:host {
--purple-background: linear-gradient(180deg, rgba(224, 204, 250, 0.33) 0%, rgba(224, 204, 250, 0.0825) 100%);
--purple-border: 1px solid rgba(113, 24, 226, 1);
--gray-background: linear-gradient(180deg, rgba(191, 193, 201, 0.33) 0%, rgba(191, 193, 201, 0.0825) 100%);
--gray-border: 1px solid rgba(191, 193, 201, 1);
--red-background: linear-gradient(180deg, rgba(249, 196, 215, 0.33) 0%, rgba(249, 196, 215, 0.0825) 100%);
--red-border: 1px solid rgba(179, 62, 102, 1);
--green-background: linear-gradient(180deg, rgba(213, 249, 196, 0.33) 0%, rgba(213, 249, 196, 0.0825) 100%);
--green-border: 1px solid rgba(61, 125, 31, 1);
--yellow-background: linear-gradient(180deg, rgba(255, 236, 179, 0.33) 0%, rgba(255, 236, 179, 0.0825) 100%);
--yellow-border: 1px solid rgba(181, 138, 45, 1);
--blue-background: linear-gradient(180deg, rgba(189, 195, 255, 0.33) 0%, rgba(189, 195, 255, 0.0825) 100%);
--blue-border: 1px solid rgba(54, 69, 217, 1);
border-radius: 4px;
display: block;
width: 100%;
height: 100%;
position: absolute;
z-index: 2000000000;
background: var(--background);
border: var(--border);
}
.icon {
width: 24px;
height: 24px;
color: white;
background: linear-gradient(0deg, #B33E66, #B33E66), linear-gradient(0deg, #351722, #351722);
border: 1px solid rgba(53, 23, 34, 1);
border-radius: 9999px;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: -15px;
right: -15px;
}
</style>
<style id="selected-style"></style>
`;
}
updateStyle() {
const style = this.shadowRoot.querySelector("#selected-style");
if (style) {
style.innerHTML = `
:host {
--background: var(--${this.highlightStyle}-background);
--border: var(--${this.highlightStyle}-border);
}`;
}
}
attributeChangedCallback() {
if (this.hasAttribute("highlight-style"))
this.highlightStyle = this.getAttribute("highlight-style");
}
connectedCallback() {
this.updateStyle();
if (this.icon) {
let iconContainer = document.createElement("div");
iconContainer.classList.add("icon");
let iconElement;
if (isDefinedIcon(this.icon)) {
iconElement = getIconElement(this.icon);
} else {
iconElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
iconElement.setAttribute("viewBox", "0 0 16 16");
iconElement.innerHTML = this.icon;
}
if (iconElement) {
iconElement?.style.setProperty("width", "16px");
iconElement?.style.setProperty("height", "16px");
iconContainer.append(iconElement);
this.shadowRoot.append(iconContainer);
}
}
}
}
export {
DevToolbarHighlight
};

View File

@@ -0,0 +1,10 @@
import { type Icon } from './icons.js';
export declare class DevToolbarIcon extends HTMLElement {
_icon: Icon | undefined;
shadowRoot: ShadowRoot;
get icon(): Icon | undefined;
set icon(name: Icon | undefined);
constructor();
getIconHTML(icon: Icon | undefined): string;
buildTemplate(): void;
}

View File

@@ -0,0 +1,46 @@
import { getIconElement, isDefinedIcon } from "./icons.js";
class DevToolbarIcon extends HTMLElement {
_icon = void 0;
shadowRoot;
get icon() {
return this._icon;
}
set icon(name) {
this._icon = name;
this.buildTemplate();
}
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: "open" });
if (this.hasAttribute("icon")) {
this.icon = this.getAttribute("icon");
} else {
this.buildTemplate();
}
}
getIconHTML(icon) {
if (icon && isDefinedIcon(icon)) {
return getIconElement(icon)?.outerHTML ?? "";
}
return "<slot />";
}
buildTemplate() {
this.shadowRoot.innerHTML = `
<style>
svg {
width: 100%;
height: 100%;
}
@media (forced-colors: active) {
svg path[fill="#fff"] {
fill: black;
}
}
</style>
${this.getIconHTML(this._icon)}`;
}
}
export {
DevToolbarIcon
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
export { DevToolbarBadge } from './badge.js';
export { DevToolbarButton } from './button.js';
export { DevToolbarCard } from './card.js';
export { DevToolbarHighlight } from './highlight.js';
export { DevToolbarIcon } from './icon.js';
export { DevToolbarSelect } from './select.js';
export { DevToolbarToggle } from './toggle.js';
export { DevToolbarTooltip } from './tooltip.js';
export { DevToolbarWindow } from './window.js';
export { DevToolbarRadioCheckbox } from './radio-checkbox.js';

View File

@@ -0,0 +1,22 @@
import { DevToolbarBadge } from "./badge.js";
import { DevToolbarButton } from "./button.js";
import { DevToolbarCard } from "./card.js";
import { DevToolbarHighlight } from "./highlight.js";
import { DevToolbarIcon } from "./icon.js";
import { DevToolbarSelect } from "./select.js";
import { DevToolbarToggle } from "./toggle.js";
import { DevToolbarTooltip } from "./tooltip.js";
import { DevToolbarWindow } from "./window.js";
import { DevToolbarRadioCheckbox } from "./radio-checkbox.js";
export {
DevToolbarBadge,
DevToolbarButton,
DevToolbarCard,
DevToolbarHighlight,
DevToolbarIcon,
DevToolbarRadioCheckbox,
DevToolbarSelect,
DevToolbarToggle,
DevToolbarTooltip,
DevToolbarWindow
};

View File

@@ -0,0 +1,13 @@
export declare class DevToolbarRadioCheckbox extends HTMLElement {
private _radioStyle;
input: HTMLInputElement;
shadowRoot: ShadowRoot;
get radioStyle(): "red" | "purple" | "gray" | "green" | "yellow" | "blue";
set radioStyle(value: "red" | "purple" | "gray" | "green" | "yellow" | "blue");
static observedAttributes: string[];
constructor();
connectedCallback(): void;
updateStyle(): void;
updateInputState(): void;
attributeChangedCallback(): void;
}

View File

@@ -0,0 +1,109 @@
const styles = ["purple", "gray", "red", "green", "yellow", "blue"];
class DevToolbarRadioCheckbox extends HTMLElement {
_radioStyle = "purple";
input;
shadowRoot;
get radioStyle() {
return this._radioStyle;
}
set radioStyle(value) {
if (!styles.includes(value)) {
console.error(`Invalid style: ${value}, expected one of ${styles.join(", ")}.`);
return;
}
this._radioStyle = value;
this.updateStyle();
}
static observedAttributes = ["radio-style", "checked", "disabled", "name", "value"];
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
:host {
--purple-unchecked: rgba(224, 204, 250, 0.33);
--purple-checked: rgba(224, 204, 250, 1);
--gray-unchecked: rgba(191, 193, 201, 0.33);
--gray-checked: rgba(191, 193, 201, 1);
--red-unchecked: rgba(249, 196, 215, 0.33);
--red-checked: rgba(179, 62, 102, 1);
--green-unchecked: rgba(213, 249, 196, 0.33);
--green-checked: rgba(61, 125, 31, 1);
--yellow-unchecked: rgba(255, 236, 179, 0.33);
--yellow-checked: rgba(181, 138, 45, 1);
--blue-unchecked: rgba(189, 195, 255, 0.33);
--blue-checked: rgba(54, 69, 217, 1);
}
input[type="radio"] {
appearance: none;
-webkit-appearance: none;
display: flex;
align-content: center;
justify-content: center;
border: 2px solid var(--unchecked-color);
border-radius: 9999px;
width: 16px;
height: 16px;
}
input[type="radio"]::before {
content: "";
background-color: var(--checked-color);
width: 8px;
height: 8px;
border-radius: 9999px;
visibility: hidden;
margin: 2px;
}
input[type="radio"]:checked {
border-color: var(--checked-color);
}
input[type="radio"]:checked::before {
visibility: visible;
}
</style>
<style id="selected-style"></style>
`;
this.input = document.createElement("input");
this.input.type = "radio";
this.shadowRoot.append(this.input);
}
connectedCallback() {
this.updateInputState();
this.updateStyle();
}
updateStyle() {
const styleElement = this.shadowRoot.querySelector("#selected-style");
if (styleElement) {
styleElement.innerHTML = `
:host {
--unchecked-color: var(--${this._radioStyle}-unchecked);
--checked-color: var(--${this._radioStyle}-checked);
}
`;
}
}
updateInputState() {
this.input.checked = this.hasAttribute("checked");
this.input.disabled = this.hasAttribute("disabled");
this.input.name = this.getAttribute("name") || "";
this.input.value = this.getAttribute("value") || "";
}
attributeChangedCallback() {
if (this.hasAttribute("radio-style")) {
this.radioStyle = this.getAttribute("radio-style");
}
this.updateInputState();
}
}
export {
DevToolbarRadioCheckbox
};

View File

@@ -0,0 +1,15 @@
declare const styles: readonly ["purple", "gray", "red", "green", "yellow", "blue"];
type SelectStyle = (typeof styles)[number];
export declare class DevToolbarSelect extends HTMLElement {
shadowRoot: ShadowRoot;
element: HTMLSelectElement;
_selectStyle: SelectStyle;
get selectStyle(): "red" | "purple" | "gray" | "green" | "yellow" | "blue";
set selectStyle(value: "red" | "purple" | "gray" | "green" | "yellow" | "blue");
static observedAttributes: string[];
constructor();
connectedCallback(): void;
attributeChangedCallback(): void;
updateStyle(): void;
}
export {};

View File

@@ -0,0 +1,100 @@
import { settings } from "../settings.js";
const styles = ["purple", "gray", "red", "green", "yellow", "blue"];
class DevToolbarSelect extends HTMLElement {
shadowRoot;
element;
_selectStyle = "gray";
get selectStyle() {
return this._selectStyle;
}
set selectStyle(value) {
if (!styles.includes(value)) {
settings.logger.error(`Invalid style: ${value}, expected one of ${styles.join(", ")}.`);
return;
}
this._selectStyle = value;
this.updateStyle();
}
static observedAttributes = ["select-style"];
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
:host {
--purple-text: rgba(224, 204, 250, 1);
--purple-border: rgba(113, 24, 226, 1);
--gray-text: rgba(191, 193, 201, 1);
--gray-border:rgba(191, 193, 201, 1);
--red-text: rgba(249, 196, 215, 1);
--red-border: rgba(179, 62, 102, 1);
--green-text: rgba(213, 249, 196, 1);
--green-border: rgba(61, 125, 31, 1);
--yellow-text: rgba(249, 233, 196, 1);
--yellow-border: rgba(181, 138, 45, 1);
--blue-text: rgba(189, 195, 255, 1);
--blue-border: rgba(54, 69, 217, 1);
--text-color: var(--gray-text);
--border-color: var(--gray-border);
}
select {
appearance: none;
text-align-last: center;
display: inline-block;
font-family: system-ui, sans-serif;
font-size: 14px;
padding: 4px 24px 4px 8px;
border: 1px solid var(--border-color);
border-radius: 4px;
color: var(--text-color);
background-color: transparent;
background-image:
linear-gradient(45deg, transparent 50%, var(--text-color) 50%),
linear-gradient(135deg, var(--text-color) 50%, transparent 50%);
background-position:
calc(100% - 12px) calc(1em - 2px),
calc(100% - 8px) calc(1em - 2px);
background-size: 4px 4px;
background-repeat: no-repeat;
}
</style>
<style id="selected-style"></style>
<slot></slot>
`;
this.element = document.createElement("select");
this.shadowRoot.addEventListener("slotchange", (event) => {
if (event.target instanceof HTMLSlotElement) {
this.element.append(...event.target.assignedNodes());
}
});
}
connectedCallback() {
this.shadowRoot.append(this.element);
this.updateStyle();
}
attributeChangedCallback() {
if (this.hasAttribute("select-style")) {
this.selectStyle = this.getAttribute("select-style");
}
}
updateStyle() {
const style = this.shadowRoot.querySelector("#selected-style");
if (style) {
style.innerHTML = `
:host {
--text-color: var(--${this.selectStyle}-text);
--border-color: var(--${this.selectStyle}-border);
}
`;
}
}
}
export {
DevToolbarSelect
};

View File

@@ -0,0 +1,17 @@
declare const styles: readonly ["purple", "gray", "red", "green", "yellow", "blue"];
type ToggleStyle = (typeof styles)[number];
export declare class DevToolbarToggle extends HTMLElement {
shadowRoot: ShadowRoot;
input: HTMLInputElement;
_toggleStyle: ToggleStyle;
get toggleStyle(): "red" | "purple" | "gray" | "green" | "yellow" | "blue";
set toggleStyle(value: "red" | "purple" | "gray" | "green" | "yellow" | "blue");
static observedAttributes: string[];
constructor();
attributeChangedCallback(): void;
updateStyle(): void;
connectedCallback(): void;
get value(): string;
set value(val: string);
}
export {};

View File

@@ -0,0 +1,124 @@
import { settings } from "../settings.js";
const styles = ["purple", "gray", "red", "green", "yellow", "blue"];
class DevToolbarToggle extends HTMLElement {
shadowRoot;
input;
_toggleStyle = "gray";
get toggleStyle() {
return this._toggleStyle;
}
set toggleStyle(value) {
if (!styles.includes(value)) {
settings.logger.error(`Invalid style: ${value}, expected one of ${styles.join(", ")}.`);
return;
}
this._toggleStyle = value;
this.updateStyle();
}
static observedAttributes = ["toggle-style"];
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = `
<style>
:host {
--purple-bg-on: rgba(113, 24, 226, 1);
--purple-border-off: rgba(113, 24, 226, 1);
--purple-border-on: rgba(224, 204, 250, 1);
--gray-bg-on: rgba(61, 125, 31, 1);
--gray-border-off: rgba(145, 152, 173, 1);
--gray-border-on: rgba(213, 249, 196, 1);
--red-bg-on: rgba(179, 62, 102, 1);
--red-border-off: rgba(179, 62, 102, 1);
--red-border-on: rgba(249, 196, 215, 1);
--green-bg-on: rgba(61, 125, 31, 1);
--green-border-off: rgba(61, 125, 31, 1);
--green-border-on: rgba(213, 249, 196, 1);
--yellow-bg-on: rgba(181, 138, 45, 1);
--yellow-border-off: rgba(181, 138, 45, 1);
--yellow-border-on: rgba(255, 236, 179, 1);
--blue-bg-on: rgba(54, 69, 217, 1);
--blue-border-off: rgba(54, 69, 217, 1);
--blue-border-on: rgba(189, 195, 255, 1);
}
input {
appearance: none;
width: 32px;
height: 20px;
border: 1px solid var(--border-off);
transition: background-color 0.2s ease, border-color 0.2s ease;
border-radius: 9999px;
}
input::after {
content: '';
width: 16px;
display: inline-block;
height: 16px;
background-color: var(--border-off);
border-radius: 9999px;
transition: transform 0.2s ease, background-color 0.2s ease;
top: 1px;
left: 1px;
position: relative;
}
@media (forced-colors: active) {
input::after {
border: 1px solid black;
top: 0px;
left: 0px;
}
}
input:checked {
border: 1px solid var(--border-on);
background-color: var(--bg-on);
}
input:checked::after {
transform: translateX(12px);
background: var(--border-on);
}
</style>
<style id="selected-style"></style>
`;
this.input = document.createElement("input");
}
attributeChangedCallback() {
if (this.hasAttribute("toggle-style"))
this.toggleStyle = this.getAttribute("toggle-style");
}
updateStyle() {
const style = this.shadowRoot.querySelector("#selected-style");
if (style) {
style.innerHTML = `
:host {
--bg-on: var(--${this.toggleStyle}-bg-on);
--border-off: var(--${this.toggleStyle}-border-off);
--border-on: var(--${this.toggleStyle}-border-on);
}
`;
}
}
connectedCallback() {
this.input.type = "checkbox";
this.shadowRoot.append(this.input);
this.updateStyle();
}
get value() {
return this.input.value;
}
set value(val) {
this.input.value = val;
}
}
export {
DevToolbarToggle
};

View File

@@ -0,0 +1,16 @@
import { type Icon } from './icons.js';
export interface DevToolbarTooltipSection {
title?: string;
inlineTitle?: string;
icon?: Icon;
content?: string;
clickAction?: () => void | Promise<void>;
clickDescription?: string;
}
export declare class DevToolbarTooltip extends HTMLElement {
sections: DevToolbarTooltipSection[];
shadowRoot: ShadowRoot;
constructor();
connectedCallback(): void;
getElementForIcon(icon: Icon | (string & NonNullable<unknown>)): string;
}

View File

@@ -0,0 +1,143 @@
import { getIconElement, isDefinedIcon } from "./icons.js";
class DevToolbarTooltip extends HTMLElement {
sections = [];
shadowRoot;
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: "open" });
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
:host {
position: absolute;
display: none;
color: white;
background: linear-gradient(0deg, #310A65, #310A65), linear-gradient(0deg, #7118E2, #7118E2);
border: 1px solid rgba(113, 24, 226, 1);
border-radius: 4px;
padding: 0;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-size: 14px;
margin: 0;
z-index: 2000000001;
max-width: 45ch;
width: fit-content;
min-width: 30ch;
box-shadow: 0px 0px 0px 0px rgba(0, 0, 0, 0.30), 0px 1px 2px 0px rgba(0, 0, 0, 0.29), 0px 4px 4px 0px rgba(0, 0, 0, 0.26), 0px 10px 6px 0px rgba(0, 0, 0, 0.15), 0px 17px 7px 0px rgba(0, 0, 0, 0.04), 0px 26px 7px 0px rgba(0, 0, 0, 0.01);
}
:host([data-show="true"]) {
display: block;
}
svg {
vertical-align: bottom;
margin-inline-end: 4px;
}
hr {
border: 1px solid rgba(136, 58, 234, 0.33);
padding: 0;
margin: 0;
}
section {
padding: 8px;
}
.section-content {
max-height: 250px;
overflow-y: auto;
}
.modal-title {
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-main-title {
font-weight: bold;
}
.modal-title + div {
margin-top: 8px;
}
.modal-cta {
display: block;
font-weight: bold;
font-size: 0.9em;
}
.clickable-section {
background: rgba(113, 24, 226, 1);
padding: 8px;
border: 0;
color: white;
font-family: system-ui, sans-serif;
text-align: left;
line-height: 1.2;
white-space: nowrap;
text-decoration: none;
margin: 0;
width: 100%;
}
.clickable-section:hover {
cursor: pointer;
}
pre, code {
background: rgb(78, 27, 145);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
border-radius: 2px;
font-size: 14px;
padding: 2px;
}
pre {
padding: 1em;
margin: 0 0;
overflow: auto;
}
`;
const fragment = new DocumentFragment();
this.sections.forEach((section, index) => {
const sectionElement = section.clickAction ? document.createElement("button") : document.createElement("section");
if (section.clickAction) {
sectionElement.classList.add("clickable-section");
sectionElement.addEventListener("click", async () => {
await section.clickAction();
});
}
sectionElement.innerHTML = `
${section.title ? `<div class="modal-title"><span class="modal-main-title">
${section.icon ? this.getElementForIcon(section.icon) : ""}${section.title}</span>${section.inlineTitle ?? ""}</div>` : ""}
${section.content ? `<div class="section-content">${section.content}</div>` : ""}
${section.clickDescription ? `<span class="modal-cta">${section.clickDescription}</span>` : ""}
`;
fragment.append(sectionElement);
if (index < this.sections.length - 1) {
fragment.append(document.createElement("hr"));
}
});
this.shadowRoot.append(fragment);
}
getElementForIcon(icon) {
let iconElement;
if (isDefinedIcon(icon)) {
iconElement = getIconElement(icon);
} else {
iconElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
iconElement.setAttribute("viewBox", "0 0 16 16");
iconElement.innerHTML = icon;
}
iconElement?.style.setProperty("width", "16px");
iconElement?.style.setProperty("height", "16px");
return iconElement?.outerHTML ?? "";
}
}
export {
DevToolbarTooltip
};

View File

@@ -0,0 +1,14 @@
export declare const placements: readonly ["bottom-left", "bottom-center", "bottom-right"];
export type Placement = (typeof placements)[number];
export declare function isValidPlacement(value: string): value is Placement;
export declare class DevToolbarWindow extends HTMLElement {
shadowRoot: ShadowRoot;
_placement: Placement;
get placement(): "bottom-left" | "bottom-center" | "bottom-right";
set placement(value: "bottom-left" | "bottom-center" | "bottom-right");
static observedAttributes: string[];
constructor();
connectedCallback(): Promise<void>;
attributeChangedCallback(): void;
updateStyle(): void;
}

View File

@@ -0,0 +1,133 @@
import { defaultSettings, settings } from "../settings.js";
const placements = ["bottom-left", "bottom-center", "bottom-right"];
function isValidPlacement(value) {
return placements.map(String).includes(value);
}
class DevToolbarWindow extends HTMLElement {
shadowRoot;
_placement = defaultSettings.placement;
get placement() {
return this._placement;
}
set placement(value) {
if (!isValidPlacement(value)) {
settings.logger.error(
`Invalid placement: ${value}, expected one of ${placements.join(", ")}, got ${value}.`
);
return;
}
this._placement = value;
this.updateStyle();
}
static observedAttributes = ["placement"];
constructor() {
super();
this.shadowRoot = this.attachShadow({ mode: "open" });
}
async connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
:host {
box-sizing: border-box;
display: flex;
flex-direction: column;
background: linear-gradient(0deg, #13151A, #13151A), linear-gradient(0deg, #343841, #343841);
border: 1px solid rgba(52, 56, 65, 1);
width: min(640px, 100%);
max-height: 480px;
border-radius: 12px;
padding: 24px;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
color: rgba(191, 193, 201, 1);
position: fixed;
z-index: 999999999;
bottom: 72px;
box-shadow: 0px 0px 0px 0px rgba(19, 21, 26, 0.30), 0px 1px 2px 0px rgba(19, 21, 26, 0.29), 0px 4px 4px 0px rgba(19, 21, 26, 0.26), 0px 10px 6px 0px rgba(19, 21, 26, 0.15), 0px 17px 7px 0px rgba(19, 21, 26, 0.04), 0px 26px 7px 0px rgba(19, 21, 26, 0.01);
}
@media (forced-colors: active) {
:host {
background: white;
}
}
@media (max-width: 640px) {
:host {
border-radius: 0;
}
}
::slotted(h1), ::slotted(h2), ::slotted(h3), ::slotted(h4), ::slotted(h5) {
font-weight: 600;
color: #fff;
}
::slotted(h1) {
font-size: 22px;
}
::slotted(h2) {
font-size: 20px;
}
::slotted(h3) {
font-size: 18px;
}
::slotted(h4) {
font-size: 16px;
}
::slotted(h5) {
font-size: 14px;
}
hr, ::slotted(hr) {
border: 1px solid rgba(27, 30, 36, 1);
margin: 1em 0;
}
p, ::slotted(p) {
line-height: 1.5em;
}
</style>
<style id="selected-style"></style>
<slot />
`;
this.updateStyle();
}
attributeChangedCallback() {
if (this.hasAttribute("placement"))
this.placement = this.getAttribute("placement");
}
updateStyle() {
const style = this.shadowRoot.querySelector("#selected-style");
if (style) {
const styleMap = {
"bottom-left": `
:host {
left: 16px;
}
`,
"bottom-center": `
:host {
left: 50%;
transform: translateX(-50%);
}
`,
"bottom-right": `
:host {
right: 16px;
}
`
};
style.innerHTML = styleMap[this.placement];
}
}
}
export {
DevToolbarWindow,
isValidPlacement,
placements
};