diff --git a/panel/package.json b/panel/package.json
index d3bb6bcd0..c4b0de76a 100644
--- a/panel/package.json
+++ b/panel/package.json
@@ -31,7 +31,8 @@
"lint:ts": "eslint './src/ts/**/*.ts' --fix",
"check": "pnpm check:css && pnpm check:ts",
"check:css": "stylelint './src/scss/**/*.scss'",
- "check:ts": "eslint './src/ts/**/*.ts'"
+ "check:ts": "eslint './src/ts/**/*.ts'",
+ "dump:icons": "node ./src/scripts/dump-icons.js"
},
"dependencies": {
"@codemirror/commands": "^6.10.0",
diff --git a/panel/src/scripts/dump-icons.js b/panel/src/scripts/dump-icons.js
new file mode 100644
index 000000000..0d07c2653
--- /dev/null
+++ b/panel/src/scripts/dump-icons.js
@@ -0,0 +1,57 @@
+import fs from "fs";
+import path from "path";
+
+const ICONS_DIR = path.resolve(import.meta.dirname, "../../assets/icons/svg/");
+const OUTPUT_FILE = path.resolve(import.meta.dirname, "../ts/components/icons.ts");
+
+function sanitizeName(filename) {
+ return filename
+ .replace(/\.svg$/i, "")
+ .split(/[-_ ]+/)
+ .map((part, index) => {
+ if (index === 0) {
+ return part.toLowerCase();
+ }
+ return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
+ })
+ .join("");
+}
+
+function readSVGFiles(dir) {
+ return fs.readdirSync(dir).filter((file) => file.endsWith(".svg"));
+}
+
+function generateModule(svgs) {
+ let content = "// This file is auto-generated. Do not edit directly.\n\n";
+
+ svgs.forEach(({ name, svg }) => {
+ const cleanedSVG = svg
+ .replace(/\r?\n|\r/g, " ")
+ .replace(/\s+/g, " ")
+ .trim();
+ content += `export const ${name} = \`${cleanedSVG}\\n\`;\n\n`;
+ });
+
+ return content;
+}
+
+function buildIcons() {
+ const files = readSVGFiles(ICONS_DIR);
+ if (!files.length) {
+ console.error("No SVG files found in", ICONS_DIR);
+ return;
+ }
+
+ const svgs = files.map((file) => {
+ const filepath = path.join(ICONS_DIR, file);
+ const svg = fs.readFileSync(filepath, "utf8");
+ const name = sanitizeName(file);
+ return { name, svg };
+ });
+
+ const moduleContent = generateModule(svgs);
+ fs.writeFileSync(OUTPUT_FILE, moduleContent, "utf8");
+ console.log(`Generated ${OUTPUT_FILE} with ${svgs.length} icons`);
+}
+
+buildIcons();
diff --git a/panel/src/ts/components/icons.ts b/panel/src/ts/components/icons.ts
index 819bbed4a..cba5b17b1 100644
--- a/panel/src/ts/components/icons.ts
+++ b/panel/src/ts/components/icons.ts
@@ -1,40 +1,291 @@
-import { app } from "../app";
+// This file is auto-generated. Do not edit directly.
-const cache = new Map();
-const pending = new Map();
+export const arrowArrowLeftRightUpDown = `\n`;
-export function passIcon(icon: string, callback: (iconData: string) => void) {
- if (cache.has(icon)) {
- callback(cache.get(icon));
- return;
- }
+export const arrowDown = `\n`;
- if (pending.has(icon)) {
- pending.get(icon).push(callback);
- return;
- }
+export const arrowLeftCircle = `\n`;
- pending.set(icon, [callback]);
+export const arrowLeftDownRightUp = `\n`;
- const request = new XMLHttpRequest();
+export const arrowLeftDown = `\n`;
- request.onload = function () {
- const data = this.status === 200 ? this.response : "";
- if (data !== "") {
- cache.set(icon, data);
- }
+export const arrowLeftRight = `\n`;
- for (const callback of pending.get(icon)) {
- callback(data);
- }
+export const arrowLeftUpRightDown = `\n`;
- pending.delete(icon);
- };
+export const arrowLeftUp = `\n`;
- request.open("GET", `${app.config.baseUri}assets/icons/svg/${icon}.svg`);
- request.send();
-}
+export const arrowLeft = `\n`;
-export function insertIcon(icon: string, element: HTMLElement, position: InsertPosition = "afterbegin") {
- passIcon(icon, (data) => element.insertAdjacentHTML(position, data));
-}
+export const arrowRightCircle = `\n`;
+
+export const arrowRightDown = `\n`;
+
+export const arrowRightUpBox = `\n`;
+
+export const arrowRightUp = `\n`;
+
+export const arrowRight = `\n`;
+
+export const arrowUpDownLeftRight = `\n`;
+
+export const arrowUpDown = `\n`;
+
+export const arrowUp = `\n`;
+
+export const arrowsRotateClockwise = `\n`;
+
+export const arrowsRotateCounterclockwise = `\n`;
+
+export const bars = `\n`;
+
+export const blockquote = `\n`;
+
+export const bold = `\n`;
+
+export const bolt = `\n`;
+
+export const cacheClear = `\n`;
+
+export const cache = `\n`;
+
+export const calendarClock = `\n`;
+
+export const calendar = `\n`;
+
+export const cameraFlash = `\n`;
+
+export const cameraMeteringAverage = `\n`;
+
+export const cameraMeteringEvaluative = `\n`;
+
+export const cameraMeteringPartial = `\n`;
+
+export const cameraMeteringSpot = `\n`;
+
+export const cameraNoFlash = `\n`;
+
+export const chartLine = `\n`;
+
+export const checkCircle = `\n`;
+
+export const checkSquare = `\n`;
+
+export const check = `\n`;
+
+export const chevronDown = `\n`;
+
+export const chevronLeft = `\n`;
+
+export const chevronRight = `\n`;
+
+export const chevronUp = `\n`;
+
+export const circleSmallFill = `\n`;
+
+export const circleSmall = `\n`;
+
+export const circle = `\n`;
+
+export const clockRotateLeft = `\n`;
+
+export const clockRotateRight = `\n`;
+
+export const clock = `\n`;
+
+export const cloudDownload = `\n`;
+
+export const cloudUpload = `\n`;
+
+export const cloud = `\n`;
+
+export const code = `\n`;
+
+export const desktop = `\n`;
+
+export const duplicate = `\n`;
+
+export const ellipsisH = `\n`;
+
+export const ellipsisV = `\n`;
+
+export const envelope = `\n`;
+
+export const exclamationCircle = `\n`;
+
+export const exclamationOctagon = `\n`;
+
+export const exclamationSquare = `\n`;
+
+export const exclamationTriangle = `\n`;
+
+export const exclamation = `\n`;
+
+export const eyeSlash = `\n`;
+
+export const eye = `\n`;
+
+export const fileArchive = `\n`;
+
+export const fileBackup = `\n`;
+
+export const fileBinary = `\n`;
+
+export const fileDocument = `\n`;
+
+export const fileExclamation = `\n`;
+
+export const fileIcons = `\n`;
+
+export const fileImage = `\n`;
+
+export const fileList = `\n`;
+
+export const filePage = `\n`;
+
+export const filePdf = `\n`;
+
+export const filePresentation = `\n`;
+
+export const fileSpreadsheet = `\n`;
+
+export const fileText = `\n`;
+
+export const fileVideo = `\n`;
+
+export const file = `\n`;
+
+export const filesImages = `\n`;
+
+export const files = `\n`;
+
+export const gear = `\n`;
+
+export const globe = `\n`;
+
+export const grabber = `\n`;
+
+export const home = `\n`;
+
+export const hourglass = `\n`;
+
+export const image = `\n`;
+
+export const images = `\n`;
+
+export const indentDecrease = `\n`;
+
+export const indentIncrease = `\n`;
+
+export const infoCircle = `\n`;
+
+export const infoSquare = `\n`;
+
+export const info = `\n`;
+
+export const italic = `\n`;
+
+export const key = `\n`;
+
+export const link = `\n`;
+
+export const listOrdered = `\n`;
+
+export const listUnordered = `\n`;
+
+export const list = `\n`;
+
+export const markdown = `\n`;
+
+export const minusCircle = `\n`;
+
+export const minusSquare = `\n`;
+
+export const minus = `\n`;
+
+export const mobile = `\n`;
+
+export const octagon = `\n`;
+
+export const pageBlank = `\n`;
+
+export const pageCompile = `\n`;
+
+export const pageDown = `\n`;
+
+export const pageError = `\n`;
+
+export const pageExclamation = `\n`;
+
+export const pageHome = `\n`;
+
+export const pageListing = `\n`;
+
+export const pageUp = `\n`;
+
+export const page = `\n`;
+
+export const pagesLevelDown = `\n`;
+
+export const pages = `\n`;
+
+export const pencil = `\n`;
+
+export const plusCircle = `\n`;
+
+export const plusSquare = `\n`;
+
+export const plus = `\n`;
+
+export const quotes = `\n`;
+
+export const readmore = `\n`;
+
+export const reorderV = `\n`;
+
+export const rotateLeft = `\n`;
+
+export const rotateRight = `\n`;
+
+export const search = `\n`;
+
+export const section = `\n`;
+
+export const sitemap = `\n`;
+
+export const sparks = `\n`;
+
+export const square = `\n`;
+
+export const tablet = `\n`;
+
+export const tag = `\n`;
+
+export const tags = `\n`;
+
+export const template = `\n`;
+
+export const timesCircle = `\n`;
+
+export const timesOctagon = `\n`;
+
+export const timesSquare = `\n`;
+
+export const times = `\n`;
+
+export const toolbox = `\n`;
+
+export const translate = `\n`;
+
+export const trash = `\n`;
+
+export const triangle = `\n`;
+
+export const userImageSlash = `\n`;
+
+export const userImage = `\n`;
+
+export const user = `\n`;
+
+export const users = `\n`;
diff --git a/panel/src/ts/components/inputs/date-input.ts b/panel/src/ts/components/inputs/date-input.ts
index af906bfac..6f448169f 100644
--- a/panel/src/ts/components/inputs/date-input.ts
+++ b/panel/src/ts/components/inputs/date-input.ts
@@ -1,6 +1,6 @@
import { $, $$ } from "../../utils/selectors";
+import { calendarClock, chevronDown, chevronLeft, chevronRight, chevronUp } from "../icons";
import { getOuterHeight, getOuterWidth } from "../../utils/dimensions";
-import { insertIcon } from "../icons";
import { longClick } from "../../utils/events";
import { mod } from "../../utils/math";
import { throttle } from "../../utils/events";
@@ -420,17 +420,17 @@ class Calendar {
if (this.input.options.time) {
element.innerHTML += `
`;
- insertIcon("chevron-down", $(".prevHour", element) as HTMLElement);
- insertIcon("chevron-up", $(".nextHour", element) as HTMLElement);
+ ($(".prevHour", element) as HTMLElement).innerHTML = chevronDown;
+ ($(".nextHour", element) as HTMLElement).innerHTML = chevronUp;
- insertIcon("chevron-down", $(".prevMinute", element) as HTMLElement);
- insertIcon("chevron-up", $(".nextMinute", element) as HTMLElement);
+ ($(".prevMinute", element) as HTMLElement).innerHTML = chevronDown;
+ ($(".nextMinute", element) as HTMLElement).innerHTML = chevronUp;
}
- insertIcon("calendar-clock", $(".currentMonth", element) as HTMLElement);
+ ($(".currentMonth", element) as HTMLElement).insertAdjacentHTML("afterbegin", calendarClock);
- insertIcon("chevron-left", $(".prevMonth", element) as HTMLElement);
- insertIcon("chevron-right", $(".nextMonth", element) as HTMLElement);
+ ($(".prevMonth", element) as HTMLElement).innerHTML = chevronLeft;
+ ($(".nextMonth", element) as HTMLElement).innerHTML = chevronRight;
($(".currentMonth", element) as HTMLElement).addEventListener("mousedown", (event) => {
this.now();
diff --git a/panel/src/ts/components/inputs/duration-input.ts b/panel/src/ts/components/inputs/duration-input.ts
index b98868e5e..5e6935aaa 100644
--- a/panel/src/ts/components/inputs/duration-input.ts
+++ b/panel/src/ts/components/inputs/duration-input.ts
@@ -77,7 +77,7 @@ export class DurationInput {
Object.keys(TIME_INTERVALS).forEach((t: TimeInterval) => {
if (intervalNames.includes(t)) {
intervals[t] = Math.floor(seconds / TIME_INTERVALS[t]);
- seconds -= (intervals[t]) * TIME_INTERVALS[t];
+ seconds -= intervals[t] * TIME_INTERVALS[t];
}
});
return intervals;
diff --git a/panel/src/ts/components/inputs/editor-input.ts b/panel/src/ts/components/inputs/editor-input.ts
index 2d49a2d05..d5e97bf81 100644
--- a/panel/src/ts/components/inputs/editor-input.ts
+++ b/panel/src/ts/components/inputs/editor-input.ts
@@ -3,7 +3,7 @@ import { app } from "../../app";
import { type CodeView } from "./editor/code/view";
import { debounce } from "../../utils/events";
import { escapeRegExp } from "../../utils/validation";
-import { insertIcon } from "../icons";
+import { markdown } from "../icons";
import { type MarkdownView } from "./editor/markdown/view";
function addBaseUri(markdown: string, baseUri: string) {
@@ -83,7 +83,7 @@ export class EditorInput {
toggleButton.title = app.config.EditorInput.labels.toggleMarkdown;
toggleButton.ariaLabel = app.config.EditorInput.labels.toggleMarkdown;
toggleButton.disabled = this.element.disabled;
- insertIcon("markdown", toggleButton);
+ toggleButton.innerHTML = markdown;
toolbar.appendChild(toggleButton);
this.container.appendChild(toolbar);
diff --git a/panel/src/ts/components/inputs/editor/code/menu.ts b/panel/src/ts/components/inputs/editor/code/menu.ts
index 7e0e64bed..776b4c708 100644
--- a/panel/src/ts/components/inputs/editor/code/menu.ts
+++ b/panel/src/ts/components/inputs/editor/code/menu.ts
@@ -1,16 +1,16 @@
+import * as icons from "../../../icons";
import type { EditorView, ViewUpdate } from "@codemirror/view";
import { redo, redoDepth, undo, undoDepth } from "@codemirror/commands";
import { app } from "../../../../app";
-import { passIcon } from "../../../icons";
import { ViewPlugin } from "@codemirror/view";
-function createButton(icon: string, title: string) {
+function createButton(icon: keyof typeof icons, title: string) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = `button toolbar-button`;
btn.title = title;
btn.ariaLabel = title;
- passIcon(icon, (data) => (btn.innerHTML = data));
+ btn.innerHTML = icons[icon] || "";
return btn;
}
@@ -70,8 +70,8 @@ export function MenuPlugin() {
(view) =>
new Menu(
[
- { dom: createButton("rotate-left", app.config.EditorInput.labels.undo), command: (view) => undo(view), enabler: (view) => undoDepth(view.state) > 0 },
- { dom: createButton("rotate-right", app.config.EditorInput.labels.redo), command: (view) => redo(view), enabler: (view) => redoDepth(view.state) > 0 },
+ { dom: createButton("rotateLeft", app.config.EditorInput.labels.undo), command: (view) => undo(view), enabler: (view) => undoDepth(view.state) > 0 },
+ { dom: createButton("rotateRight", app.config.EditorInput.labels.redo), command: (view) => redo(view), enabler: (view) => redoDepth(view.state) > 0 },
],
view,
),
diff --git a/panel/src/ts/components/inputs/editor/markdown/linktooltip.ts b/panel/src/ts/components/inputs/editor/markdown/linktooltip.ts
index d53a395f3..f45691124 100644
--- a/panel/src/ts/components/inputs/editor/markdown/linktooltip.ts
+++ b/panel/src/ts/components/inputs/editor/markdown/linktooltip.ts
@@ -1,9 +1,9 @@
import { debounce, throttle } from "../../../../utils/events";
import { getMarkRange, insertLink, removeLink } from "./commands";
+import { link as linkIcon, pencil, trash } from "../../../icons";
import { $ } from "../../../../utils/selectors";
import { app } from "../../../../app";
import type { EditorView } from "prosemirror-view";
-import { insertIcon } from "../../../icons";
import type { Mark } from "prosemirror-model";
import { Plugin } from "prosemirror-state";
import { schema } from "prosemirror-markdown";
@@ -82,9 +82,9 @@ class LinkTooltipView {
const tooltipEditLink = $('[data-command="edit-link"]', this.tooltip.element) as HTMLButtonElement;
const tooltipDeleteLink = $('[data-command="delete-link"]', this.tooltip.element) as HTMLButtonElement;
- insertIcon("link", tooltipLink);
- insertIcon("pencil", tooltipEditLink);
- insertIcon("trash", tooltipDeleteLink);
+ tooltipLink.insertAdjacentHTML("afterbegin", linkIcon);
+ tooltipEditLink.insertAdjacentHTML("afterbegin", pencil);
+ tooltipDeleteLink.insertAdjacentHTML("afterbegin", trash);
const { state, dispatch } = this.editorView;
diff --git a/panel/src/ts/components/inputs/editor/markdown/menu.ts b/panel/src/ts/components/inputs/editor/markdown/menu.ts
index 5fcdb59ac..5a8d05ef4 100644
--- a/panel/src/ts/components/inputs/editor/markdown/menu.ts
+++ b/panel/src/ts/components/inputs/editor/markdown/menu.ts
@@ -1,3 +1,4 @@
+import * as icons from "../../../icons";
import { insertImage, insertLink, isMarkActive, lift, redo, setBlockType, sinkListItem, toggleMark, undo, wrapIn, wrapInList } from "./commands";
import type { MarkType, NodeType } from "prosemirror-model";
import { NodeSelection, Plugin } from "prosemirror-state";
@@ -5,7 +6,6 @@ import { $$ } from "../../../../utils/selectors";
import { app } from "../../../../app";
import type { Command } from "prosemirror-state";
import type { EditorView } from "prosemirror-view";
-import { passIcon } from "../../../icons";
import { schema } from "prosemirror-markdown";
interface MenuItem {
@@ -192,12 +192,12 @@ export function menuPlugin(id: string) {
},
{
command: wrapInList(schema.nodes.bullet_list, schema.nodes.list_item),
- dom: createButton("list-unordered", app.config.EditorInput.labels.bulletList),
+ dom: createButton("listUnordered", app.config.EditorInput.labels.bulletList),
group: "blocks",
},
{
command: wrapInList(schema.nodes.ordered_list, schema.nodes.list_item),
- dom: createButton("list-ordered", app.config.EditorInput.labels.numberedList),
+ dom: createButton("listOrdered", app.config.EditorInput.labels.numberedList),
group: "blocks",
},
{
@@ -208,12 +208,12 @@ export function menuPlugin(id: string) {
},
{
command: sinkListItem(schema.nodes.list_item),
- dom: createButton("indent-increase", app.config.EditorInput.labels.increaseIndent),
+ dom: createButton("indentIncrease", app.config.EditorInput.labels.increaseIndent),
group: "blocks",
},
{
command: lift,
- dom: createButton("indent-decrease", app.config.EditorInput.labels.decreaseIndent),
+ dom: createButton("indentDecrease", app.config.EditorInput.labels.decreaseIndent),
group: "blocks",
},
{
@@ -230,11 +230,11 @@ export function menuPlugin(id: string) {
},
{
command: undo,
- dom: createButton("rotate-left", app.config.EditorInput.labels.undo),
+ dom: createButton("rotateLeft", app.config.EditorInput.labels.undo),
},
{
command: redo,
- dom: createButton("rotate-right", app.config.EditorInput.labels.redo),
+ dom: createButton("rotateRight", app.config.EditorInput.labels.redo),
},
];
@@ -250,13 +250,13 @@ export function menuPlugin(id: string) {
});
}
-function createButton(icon: string, title: string) {
+function createButton(icon: keyof typeof icons, title: string) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = `button toolbar-button`;
btn.title = title;
btn.ariaLabel = title;
- passIcon(icon, (data) => (btn.innerHTML = data));
+ btn.innerHTML = icons[icon] || "";
return btn;
}
diff --git a/panel/src/ts/components/inputs/select-input.ts b/panel/src/ts/components/inputs/select-input.ts
index 109a50e8e..bcd6813fc 100644
--- a/panel/src/ts/components/inputs/select-input.ts
+++ b/panel/src/ts/components/inputs/select-input.ts
@@ -1,6 +1,7 @@
+import * as icons from "../icons";
import { $, $$ } from "../../utils/selectors";
import { escapeRegExp, makeDiacriticsRegExp } from "../../utils/validation";
-import { insertIcon } from "../icons";
+import { toCamelCase } from "../../utils/strings";
type SelectInputListItem = {
label: string;
@@ -203,7 +204,8 @@ export class SelectInput {
img.className = "dropdown-thumb";
item.insertAdjacentElement("afterbegin", img);
} else if (option.dataset.icon) {
- insertIcon(option.dataset.icon, item);
+ const icon = toCamelCase(option.dataset.icon) as keyof typeof icons;
+ item.insertAdjacentHTML("afterbegin", icons[icon]);
}
for (const key in option.dataset) {
diff --git a/panel/src/ts/components/inputs/tags-input.ts b/panel/src/ts/components/inputs/tags-input.ts
index 7c600f9fe..29b063814 100644
--- a/panel/src/ts/components/inputs/tags-input.ts
+++ b/panel/src/ts/components/inputs/tags-input.ts
@@ -1,8 +1,9 @@
+import * as icons from "../icons";
import { $, $$ } from "../../utils/selectors";
import { escapeRegExp, makeDiacriticsRegExp } from "../../utils/validation";
import { debounce } from "../../utils/events";
-import { insertIcon } from "../icons";
import type { SortableEvent } from "sortablejs";
+import { toCamelCase } from "../../utils/strings";
interface TagsInputOptions {
labels: { [key: string]: string };
@@ -15,7 +16,7 @@ interface TagsInputOptions {
interface TagsInputDropdownItem {
label: string;
value: string;
- icon?: string;
+ icon?: keyof typeof icons;
thumb?: string;
}
@@ -181,7 +182,7 @@ export class TagsInput {
img.className = "dropdown-thumb";
item.insertAdjacentElement("afterbegin", img);
} else if (option.icon) {
- insertIcon(option.icon, item);
+ item.insertAdjacentHTML("afterbegin", icons[option.icon]);
}
item.addEventListener("click", () => {
@@ -232,8 +233,8 @@ export class TagsInput {
this.addDropdownItem({
label: value,
- value: isAssociative ? key : (value),
- icon,
+ value: isAssociative ? key : value,
+ icon: icon ? (toCamelCase(icon) as keyof typeof icons) : undefined,
thumb,
});
}
diff --git a/panel/src/ts/components/inputs/upload-input.ts b/panel/src/ts/components/inputs/upload-input.ts
index 2b6c1e1f2..22b44d5da 100644
--- a/panel/src/ts/components/inputs/upload-input.ts
+++ b/panel/src/ts/components/inputs/upload-input.ts
@@ -1,13 +1,14 @@
+import * as icons from "../icons";
import { $ } from "../../utils/selectors";
import { app } from "../../app";
import { escapeHtml } from "../../utils/validation";
import { FilesList } from "../fileslist";
import type { Form } from "../form";
-import { insertIcon } from "../icons";
import { Notification } from "../notification";
import { Request } from "../../utils/request";
import { SelectInput } from "./select-input";
import { TagsInput } from "./tags-input";
+import { toCamelCase } from "../../utils/strings";
export class UploadInput {
readonly element: HTMLInputElement;
@@ -158,7 +159,7 @@ export class UploadInput {
label: data.name,
value: data.name,
thumb: data.thumbnail,
- icon: `file-${data.type}`,
+ icon: toCamelCase(`file-${data.type}`) as keyof typeof icons,
});
input.sortDropdownItems();
}
@@ -223,7 +224,8 @@ export class UploadInput {
$(".file-thumbnail", filesItem)?.remove();
}
- insertIcon(info.type ? `file-${info.type}` : "file", $(".file-icon", filesItem) as HTMLElement);
+ const icon = icons[toCamelCase(`file-${info.type}`) as keyof typeof icons] || icons["file"];
+ ($(".file-icon", filesItem) as HTMLElement).innerHTML = icon;
const anchor = $(".file-name a", filesItem) as HTMLAnchorElement;
anchor.href = info.actions.info;
diff --git a/panel/src/ts/components/notification.ts b/panel/src/ts/components/notification.ts
index caebb89bc..63c004ede 100644
--- a/panel/src/ts/components/notification.ts
+++ b/panel/src/ts/components/notification.ts
@@ -1,16 +1,16 @@
+import * as icons from "./icons";
import { $ } from "../utils/selectors";
-import { passIcon } from "./icons";
type NotificationType = "info" | "success" | "warning" | "error";
type NotificationOptions = {
interval: number;
- icon?: string;
+ icon?: keyof typeof icons;
newestOnTop: boolean;
fadeOutDelay: number;
mouseleaveDelay: number;
typeClass: Record;
- defaultIcons: Record;
+ defaultIcons: Record;
};
export class Notification {
@@ -34,10 +34,10 @@ export class Notification {
error: "danger",
},
defaultIcons: {
- info: "info-circle",
- success: "check-circle",
- warning: "exclamation-triangle",
- error: "exclamation-octagon",
+ info: "infoCircle",
+ success: "checkCircle",
+ warning: "exclamationTriangle",
+ error: "exclamationOctagon",
},
};
@@ -83,10 +83,8 @@ export class Notification {
}
if (this.options.icon) {
- passIcon(this.options.icon, (icon) => {
- this.notificationElement = create(this.text, this.type, this.options.interval);
- this.notificationElement.insertAdjacentHTML("afterbegin", icon);
- });
+ this.notificationElement = create(this.text, this.type, this.options.interval);
+ this.notificationElement.insertAdjacentHTML("afterbegin", icons[this.options.icon] || "");
} else {
this.notificationElement = create(this.text, this.type, this.options.interval);
}
diff --git a/panel/src/ts/components/statistics-chart.ts b/panel/src/ts/components/statistics-chart.ts
index 6beb42a8e..358b07e00 100644
--- a/panel/src/ts/components/statistics-chart.ts
+++ b/panel/src/ts/components/statistics-chart.ts
@@ -1,5 +1,5 @@
+import { circleSmallFill } from "./icons";
import type { LineChartData } from "chartist";
-import { passIcon } from "./icons";
import { Tooltip } from "./tooltip";
export class StatisticsChart {
@@ -55,15 +55,13 @@ export class StatisticsChart {
const strokeWidth = parseFloat(getComputedStyle(target).strokeWidth);
const index = target.getAttribute("ct:index");
if (index) {
- passIcon("circle-small-fill", (icon) => {
- // @ts-expect-error TODO
- const text = `${data.labels[index]}
${icon} ${data.series[0][index]} ${icon}${data.series[1][index]}
`;
- const tooltip = new Tooltip(text, {
- referenceElement: event.target as HTMLElement,
- offset: { x: 0, y: -strokeWidth },
- });
- tooltip.show();
+ // @ts-expect-error TODO
+ const text = `${data.labels[index]}
${circleSmallFill} ${data.series[0][index]} ${circleSmallFill}${data.series[1][index]}
`;
+ const tooltip = new Tooltip(text, {
+ referenceElement: event.target as HTMLElement,
+ offset: { x: 0, y: -strokeWidth },
});
+ tooltip.show();
}
}
});
diff --git a/panel/src/ts/components/views/backups.ts b/panel/src/ts/components/views/backups.ts
index cda96ef0d..e09cf744a 100644
--- a/panel/src/ts/components/views/backups.ts
+++ b/panel/src/ts/components/views/backups.ts
@@ -1,6 +1,6 @@
import { $, $$ } from "../../utils/selectors";
+import { check, exclamation } from "../icons";
import { app } from "../../app";
-import { insertIcon } from "../icons";
import { Notification } from "../notification";
import { Request } from "../../utils/request";
import { triggerDownload } from "../../utils/forms";
@@ -42,7 +42,7 @@ export class Backups {
button.disabled = false;
spinner.classList.add("spinner-success");
- insertIcon("check", spinner);
+ spinner.innerHTML = check;
const template = $("#backups-row") as HTMLTemplateElement;
if (template) {
@@ -75,11 +75,11 @@ export class Backups {
if (response.status === "error") {
spinner.classList.add("spinner-danger");
- insertIcon("exclamation", spinner);
+ spinner.innerHTML = exclamation;
button.disabled = false;
}
- const notification = new Notification(response.message, response.status, { icon: "check-circle" });
+ const notification = new Notification(response.message, response.status, { icon: "checkCircle" });
notification.show();
if (response.status === "success") {
diff --git a/panel/src/ts/components/views/dashboard.ts b/panel/src/ts/components/views/dashboard.ts
index 0f83dfd07..7c4181fcb 100644
--- a/panel/src/ts/components/views/dashboard.ts
+++ b/panel/src/ts/components/views/dashboard.ts
@@ -19,7 +19,7 @@ export class Dashboard {
data: { "csrf-token": app.config.csrfToken as string },
},
(response) => {
- const icon = response.status === "error" ? "exclamation-octagon" : "check-circle";
+ const icon = response.status === "error" ? "exclamationOctagon" : "checkCircle";
const notification = new Notification(response.message, response.status, { icon });
notification.show();
},
diff --git a/panel/src/ts/components/views/pages.ts b/panel/src/ts/components/views/pages.ts
index 09367756b..193d97572 100644
--- a/panel/src/ts/components/views/pages.ts
+++ b/panel/src/ts/components/views/pages.ts
@@ -353,7 +353,7 @@ export class Pages {
},
(response) => {
if (response.status) {
- const notification = new Notification(response.message, response.status, { icon: "check-circle" });
+ const notification = new Notification(response.message, response.status, { icon: "checkCircle" });
notification.show();
}
if (!response.status || response.status === "error") {
diff --git a/panel/src/ts/components/views/updates.ts b/panel/src/ts/components/views/updates.ts
index 6da5b555a..dc1b0da18 100644
--- a/panel/src/ts/components/views/updates.ts
+++ b/panel/src/ts/components/views/updates.ts
@@ -1,6 +1,6 @@
+import { check, exclamation, info } from "../icons";
import { $ } from "../../utils/selectors";
import { app } from "../../app";
-import { insertIcon } from "../icons";
import { Notification } from "../notification";
import { Request } from "../../utils/request";
@@ -19,20 +19,20 @@ export class Updates {
const showNewVersion = (name: string) => {
spinner.classList.add("spinner-info");
- insertIcon("info", spinner);
+ spinner.innerHTML = info;
newVersionName.innerText = name;
newVersion.style.display = "block";
};
const showCurrentVersion = () => {
spinner.classList.add("spinner-success");
- insertIcon("check", spinner);
+ spinner.innerHTML = check;
currentVersion.style.display = "block";
};
const showInstalledVersion = () => {
spinner.classList.add("spinner-success");
- insertIcon("check", spinner);
+ spinner.innerHTML = check;
currentVersionName.innerText = newVersionName.innerText;
currentVersion.style.display = "block";
};
@@ -57,7 +57,7 @@ export class Updates {
}
} else {
spinner.classList.add("spinner-danger");
- insertIcon("exclamation", spinner);
+ spinner.innerHTML = exclamation;
}
},
);
@@ -76,7 +76,7 @@ export class Updates {
data: { "csrf-token": app.config.csrfToken as string },
},
(response) => {
- const notification = new Notification(response.message, response.status, { icon: "check-circle" });
+ const notification = new Notification(response.message, response.status, { icon: "checkCircle" });
notification.show();
updateStatus.innerText = response.data.status;
@@ -85,7 +85,7 @@ export class Updates {
showInstalledVersion();
} else {
spinner.classList.add("spinner-danger");
- insertIcon("exclamation", spinner);
+ spinner.innerHTML = exclamation;
}
},
);
diff --git a/panel/src/ts/utils/strings.ts b/panel/src/ts/utils/strings.ts
new file mode 100644
index 000000000..56b328369
--- /dev/null
+++ b/panel/src/ts/utils/strings.ts
@@ -0,0 +1,11 @@
+export function toCamelCase(string: string) {
+ return string
+ .split(/[-_ ]+/)
+ .map((part, index) => {
+ if (index === 0) {
+ return part.toLowerCase();
+ }
+ return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
+ })
+ .join("");
+}