From 98b912c2201b6b813d2ab12f4f6e802e376e9b1b Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Sun, 23 Nov 2025 00:17:54 +0100 Subject: [PATCH] Embed icons into js module to avoid xhr requests --- panel/package.json | 3 +- panel/src/scripts/dump-icons.js | 57 ++++ panel/src/ts/components/icons.ts | 311 ++++++++++++++++-- panel/src/ts/components/inputs/date-input.ts | 16 +- .../ts/components/inputs/duration-input.ts | 2 +- .../src/ts/components/inputs/editor-input.ts | 4 +- .../ts/components/inputs/editor/code/menu.ts | 10 +- .../inputs/editor/markdown/linktooltip.ts | 8 +- .../components/inputs/editor/markdown/menu.ts | 18 +- .../src/ts/components/inputs/select-input.ts | 6 +- panel/src/ts/components/inputs/tags-input.ts | 11 +- .../src/ts/components/inputs/upload-input.ts | 8 +- panel/src/ts/components/notification.ts | 20 +- panel/src/ts/components/statistics-chart.ts | 16 +- panel/src/ts/components/views/backups.ts | 8 +- panel/src/ts/components/views/dashboard.ts | 2 +- panel/src/ts/components/views/pages.ts | 2 +- panel/src/ts/components/views/updates.ts | 14 +- panel/src/ts/utils/strings.ts | 11 + 19 files changed, 424 insertions(+), 103 deletions(-) create mode 100644 panel/src/scripts/dump-icons.js create mode 100644 panel/src/ts/utils/strings.ts 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(""); +}