diff --git a/package.json b/package.json index 27e92cd..9cedec8 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "url": "https://github.com/JulienMaille/dribbblish-dynamic-theme/issues" }, "devDependencies": { + "@material-icons/svg": "^1.0.22", "clean-webpack-plugin": "^4.0.0", "sass": "^1.43.4", "sass-loader": "^12.2.0", diff --git a/src/icons/arrow-down.svg b/src/icons/arrow-down.svg deleted file mode 100644 index 68e8fda..0000000 --- a/src/icons/arrow-down.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/chevron-down.light.svg b/src/icons/chevron-down.light.svg deleted file mode 100644 index a66a34d..0000000 --- a/src/icons/chevron-down.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/chevron-down.svg b/src/icons/chevron-down.svg deleted file mode 100644 index 70ddaf9..0000000 --- a/src/icons/chevron-down.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/code.svg b/src/icons/code.svg deleted file mode 100644 index 1892947..0000000 --- a/src/icons/code.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/cog.svg b/src/icons/cog.svg deleted file mode 100644 index 1d22a59..0000000 --- a/src/icons/cog.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/folder-open.light.svg b/src/icons/folder-open.light.svg deleted file mode 100644 index 9dccaa7..0000000 --- a/src/icons/folder-open.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/folder-open.svg b/src/icons/folder-open.svg deleted file mode 100644 index 58884f9..0000000 --- a/src/icons/folder-open.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/folder.light.svg b/src/icons/folder.light.svg deleted file mode 100644 index 79f5c39..0000000 --- a/src/icons/folder.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/folder.svg b/src/icons/folder.svg deleted file mode 100644 index f918b31..0000000 --- a/src/icons/folder.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/note.svg b/src/icons/note.svg deleted file mode 100644 index 74aaa85..0000000 --- a/src/icons/note.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/palette.svg b/src/icons/palette.svg deleted file mode 100644 index 1dc5586..0000000 --- a/src/icons/palette.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/times.light.svg b/src/icons/times.light.svg deleted file mode 100644 index a2a32db..0000000 --- a/src/icons/times.light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/times.svg b/src/icons/times.svg deleted file mode 100644 index 5c47bc2..0000000 --- a/src/icons/times.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/undo.svg b/src/icons/undo.svg deleted file mode 100644 index 7397f55..0000000 --- a/src/icons/undo.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/wifi-slash.svg b/src/icons/wifi-slash.svg deleted file mode 100644 index e02f87a..0000000 --- a/src/icons/wifi-slash.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/js/ConfigMenu.js b/src/js/ConfigMenu.js index 3c34525..01ddf1d 100644 --- a/src/js/ConfigMenu.js +++ b/src/js/ConfigMenu.js @@ -1,10 +1,7 @@ import $ from "jquery"; import { renderMD } from "./Util"; - -import iconTimesLight from "icon/times.light"; -import iconChevronDown from "icon/chevron-down"; -import iconUndo from "icon/undo"; +import { icons } from "./Icons"; export default class ConfigMenu { /** @@ -79,7 +76,7 @@ export default class ConfigMenu { container.id = "dribbblish-config"; container.innerHTML = /* html */ `
- +

Dribbblish Settings

@@ -122,7 +119,7 @@ export default class ConfigMenu {

${options.name} - ${options.resetButton ? /* html */ `` : ""} + ${options.resetButton ? /* html */ `` : ""}

@@ -411,7 +408,7 @@ export default class ConfigMenu { areaElem.innerHTML = /* html */ `

${area.name} - ${!area.toggleable ? "" : iconChevronDown({ size: 18 })} + ${!area.toggleable ? "" : icons.get("expand-more", { size: 24, scale: 1.2 })}

`; diff --git a/src/js/Dribbblish.js b/src/js/Dribbblish.js index b0aa996..6885227 100644 --- a/src/js/Dribbblish.js +++ b/src/js/Dribbblish.js @@ -1,6 +1,7 @@ import ConfigMenu from "./ConfigMenu"; import Info from "./Info"; import Loader from "./Loader"; +import { icons } from "./Icons"; export default class Dribbblish { /** @@ -22,6 +23,9 @@ export default class Dribbblish { /** @type {Loader} */ loader; + /** @type {Icons} */ + icons; + /** @type {Object.} */ #listeners = {}; @@ -32,6 +36,7 @@ export default class Dribbblish { this.config = new ConfigMenu(); this.info = new Info(); this.loader = new Loader(); + this.icons = icons; const interval = setInterval(() => { if (document.querySelector("#main") == null || Spicetify?.showNotification == undefined || !this.info.isReady()) return; diff --git a/src/js/Folders.js b/src/js/Folders.js index 6769041..6be6a08 100644 --- a/src/js/Folders.js +++ b/src/js/Folders.js @@ -1,8 +1,5 @@ import { waitForElement, htmlToNode } from "./Util"; - -import iconFolder from "icon/folder.light"; -import iconFolderOpen from "icon/folder-open.light"; -import iconNote from "icon/note"; +import { icons } from "./Icons"; waitForElement([`.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"]`, `.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"] li`], ([root, firstItem]) => { const listElem = firstItem.parentElement; @@ -30,7 +27,7 @@ waitForElement([`.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"]`, ` if (!link.querySelector("img")) elem = document.createElement("img"); elem.src = picture; } else { - if (!link.querySelector("svg")) elem = htmlToNode(iconNote().replace(/<\/svg>/, `${title}$1`)); + if (!link.querySelector("svg")) elem = htmlToNode(icons.get("note", { title })); } } else if (app === "folder") { const base64 = localStorage.getItem("dribbblish:folder-image:" + uid); @@ -38,7 +35,7 @@ waitForElement([`.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"]`, ` if (!link.querySelector("img")) elem = document.createElement("img"); elem.src = base64; } else { - if (!link.querySelector("svg")) elem = htmlToNode(iconFolder().replace(/<\/svg>/, `${title}$1`)); + if (!link.querySelector("svg")) elem = htmlToNode(icons.get("folder", { title })); } } else { continue; diff --git a/src/js/Icons.js b/src/js/Icons.js new file mode 100644 index 0000000..f71a237 --- /dev/null +++ b/src/js/Icons.js @@ -0,0 +1,80 @@ +import { parseSync as parseSVG, stringify as stringifySVG } from "svgson"; + +export default class Icons { + /** @typedef {"baseline" | "outline" | "round" | "sharp" | "twotone"} IconStyle */ + + /** + * @typedef {Object} IconOptions + * @property {IconStyle} [style="round"] + * @property {Number} [size=16] + * @property {Number} [scale=1] + * @property {String} [fill="currentColor"] + * @property {Boolean} [base64=false] + */ + + /** @type {Object.>} */ + #icons; + + constructor() { + this.#icons = process.env.DRIBBBLISH_ICONS; + } + + /** + * @param {String} name icon name lowercase with dashes like `ac-unit` + * @param {IconOptions} options + * @see https://fonts.google.com/icons?selected=Material+Icons + * @returns + */ + get(name, options) { + /** @type {IconOptions} */ + const defaultOptions = { + style: "round", + size: 16, + scale: 1, + fill: "currentColor", + base64: false + }; + options = { ...defaultOptions, ...options }; + + if (!this.#icons.hasOwnProperty(name)) throw new Error(`Icon "${name}" does not exist`); + let svg; + if (typeof this.#icons[name] == "string") { + svg = parseSVG(this.#icons[name]); + } else { + if (!this.#icons[name].hasOwnProperty(options.style)) throw new Error(`Icon "${name}" does not have style "${options.style}"`); + svg = parseSVG(this.#icons[name][options.style]); + } + + svg.attributes.type = "dribbblish-icon"; + svg.attributes.fill = options.fill; + svg.attributes.width = options.size; + svg.attributes.height = options.size; + + if (options.scale != 1) { + svg.children = svg.children.map((child) => { + child.attributes.style = `transform: scale(${options.scale}); transform-origin: center;`; + return child; + }); + } + + if (options.title != null) { + console.log(options); + console.log(svg); + svg.children.push({ + name: "title", + type: "element", + value: "", + children: [{ name: "", type: "text", value: options.title, attributes: {}, children: [] }] + }); + console.log(svg); + } + + if (options.base64) { + return `data:image/svg+xml;base64,${Buffer.from(stringifySVG(svg)).toString("base64")}`; + } else { + return stringifySVG(svg); + } + } +} + +export const icons = new Icons(); diff --git a/src/js/Info.js b/src/js/Info.js index 977cc26..65f92bb 100644 --- a/src/js/Info.js +++ b/src/js/Info.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import { icons } from "./Icons"; import { waitForElement } from "./Util"; @@ -7,7 +8,7 @@ export default class Info { * @typedef {Object} DribbblishInfo * @property {String} [text] * @property {String} [tooltip] - * @property {String} [icon] + * @property {String} [icon] svg string or icon name * @property {DribbblishInfoColor} [color] * @property {Number} [order=0] order < 0 = More to the Left | order > 0 = More to the Right * @property {onClick} [onClick] @@ -74,6 +75,7 @@ export default class Info { if (bg != null) elem.style.backgroundColor = bg; } if (info.order != 0) elem.style.order = info.order; + if (!info.icon.startsWith(" { "settings", val ? { - icon: iconCog(), + icon: "settings", color: { fg: "var(--spice-subtext)", bg: "rgba(var(--spice-rgb-subtext), calc(0.1 + var(--is_light) * 0.05))" @@ -288,7 +285,7 @@ Dribbblish.on("ready", () => { progKnob.append(tooltip); function updateProgTime(timeOverride) { - const newText = Spicetify.Player.formatTime(timeOverride || Spicetify.Player.getProgress()) + " / " + Spicetify.Player.formatTime(Spicetify.Player.getDuration()); + const newText = Spicetify.Player.formatTime(timeOverride ?? Spicetify.Player.getProgress()) + " / " + Spicetify.Player.formatTime(Spicetify.Player.getDuration()); // To reduce DOM Updates when the Song is Paused if (tooltip.innerText != newText) tooltip.innerText = newText; } @@ -814,14 +811,14 @@ Dribbblish.on("ready", () => { fetch("https://api.github.com/repos/JulienMaille/dribbblish-dynamic-theme/releases/latest") .then((response) => response.json()) .then((data) => { - Dribbblish.info.set("dribbblish-update", data.tag_name > process.env.DRIBBBLISH_VERSION ? { text: `v${data.tag_name}`, tooltip: "Nev Dribbblish version available", icon: iconPalette(), onClick: () => window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest", "_blank") } : null); + Dribbblish.info.set("dribbblish-update", data.tag_name > process.env.DRIBBBLISH_VERSION ? { text: `v${data.tag_name}`, tooltip: "Nev Dribbblish version available", icon: "palette", onClick: () => window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest", "_blank") } : null); }) .catch(console.error); fetch("https://api.github.com/repos/khanhas/spicetify-cli/releases/latest") .then((response) => response.json()) .then((data) => { - Dribbblish.info.set("spicetify-update", data.tag_name.substring(1) > (Spicetify.version ?? "2.7.2") ? { text: data.tag_name, tooltip: "New Spicetify version available", icon: iconSpotify(), onClick: () => window.open("https://github.com/khanhas/spicetify-cli/releases/latest", "_blank") } : null); + Dribbblish.info.set("spicetify-update", data.tag_name.substring(1) > (Spicetify.version ?? "2.7.2") ? { text: data.tag_name, tooltip: "New Spicetify version available", icon: "spotify", onClick: () => window.open("https://github.com/khanhas/spicetify-cli/releases/latest", "_blank") } : null); }) .catch(console.error); } @@ -836,7 +833,7 @@ Dribbblish.on("ready", () => { show ? { tooltip: "Offline", - icon: iconWifiSlash(), + icon: "cloud-off", order: 998, color: { fg: "#ffffff", @@ -852,7 +849,5 @@ Dribbblish.on("ready", () => { // Show "Dev" info const isDev = process.env.DRIBBBLISH_VERSION == "Dev"; - Dribbblish.info.set("dev", isDev ? { tooltip: "Dev build", icon: iconCode(), order: 997 } : null); + Dribbblish.info.set("dev", isDev ? { tooltip: "Dev build", icon: "code", order: 997 } : null); }); - -$("html").css("--warning_message", " "); diff --git a/src/loaders/icon-loader.js b/src/loaders/icon-loader.js deleted file mode 100644 index 28b1c33..0000000 --- a/src/loaders/icon-loader.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = function (source) { - return ` - module.exports = function(options) { - const { parseSync: parseSVG, stringify: stringifySVG } = require("svgson"); - - const defaultOptions = { - size: 16, - base64: false - }; - options = { ...defaultOptions, ...options }; - const svg = parseSVG(\`${source}\`); - - svg.attributes.type = "icon"; - svg.attributes.width = options.size ?? options.width; - svg.attributes.height = options.size ?? options.height; - - svg.children = svg.children.map((c) => { - if (c.attributes.fill != null && options.fill != null) c.attributes.fill = options.fill; - return c; - }); - - const svgStr = stringifySVG(svg); - if (options.base64) { - return \`data:image/svg+xml;base64,\${Buffer.from(svgStr).toString("base64")}\`; - } else { - return svgStr; - } - } - `; -}; diff --git a/webpack.config.js b/webpack.config.js index 2ee0d25..a20952d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,7 +3,21 @@ const sass = require("sass"); const { CleanWebpackPlugin } = require("clean-webpack-plugin"); const path = require("path"); const fs = require("fs"); -const iconLoader = require("./src/loaders/icon-loader"); + +const icons = {}; +// Add Material Icons +let iconDir = path.resolve(__dirname, "./node_modules/@material-icons/svg/svg"); +for (const dir of fs.readdirSync(iconDir)) { + icons[dir.replace("_", "-")] = {}; + for (const file of fs.readdirSync(path.resolve(iconDir, dir))) { + icons[dir.replace("_", "-")][file.replace(/\..*?$/, "")] = fs.readFileSync(path.resolve(iconDir, dir, file), { encoding: "utf8" }); + } +} +// Add Custom Icons +iconDir = path.resolve(__dirname, "./src/icons"); +for (const icon of fs.readdirSync(iconDir)) { + icons[icon.replace(/\..*?$/, "")] = fs.readFileSync(path.resolve(iconDir, icon), { encoding: "utf8" }); +} /** @type {import('webpack').Configuration} */ module.exports = { @@ -12,12 +26,6 @@ module.exports = { path: path.resolve(__dirname, "dist"), filename: "dribbblish-dynamic.js" }, - resolve: { - extensions: [".js", ".svg"], - alias: { - icon: path.resolve(__dirname, "./src/icons") - } - }, module: { rules: [ { @@ -55,18 +63,15 @@ module.exports = { filename: "color.ini" }, use: [path.resolve(__dirname, "./src/loaders/color-loader.js")] - }, - { - test: /\.svg/, - use: [path.resolve(__dirname, "./src/loaders/icon-loader.js")] } ] }, devtool: "inline-source-map", plugins: [ new webpack.DefinePlugin({ - "process.env.DRIBBBLISH_VERSION": JSON.stringify(process.env.DRIBBBLISH_VERSION || "Dev"), - "process.env.COMMIT_HASH": JSON.stringify(process.env.COMMIT_HASH || "local") + "process.env.DRIBBBLISH_ICONS": JSON.stringify(icons), + "process.env.DRIBBBLISH_VERSION": JSON.stringify(process.env.DRIBBBLISH_VERSION ?? "Dev"), + "process.env.COMMIT_HASH": JSON.stringify(process.env.COMMIT_HASH ?? "local") }), new CleanWebpackPlugin({ protectWebpackAssets: false,