diff --git a/scripts/generate-theme-css.mjs b/scripts/generate-theme-css.mjs index f7a6f34..280a5d2 100644 --- a/scripts/generate-theme-css.mjs +++ b/scripts/generate-theme-css.mjs @@ -693,13 +693,13 @@ const styleAccents = () => { "rgb(211, 79, 67)", "rgb(205, 73, 69)", ], + secondaryHover = cssVariable({ + name: "accent-secondary_hover", + value: "rgba(235, 87, 87, 0.1)", + }), secondaryContrast = cssVariable({ name: "accent-secondary_contrast", value: "white", - }), - secondaryTransparent = cssVariable({ - name: "accent-secondary_transparent", - value: "rgba(235, 87, 87, 0.1)", }); overrideStyle({ property: "color", @@ -746,7 +746,7 @@ const styleAccents = () => { }); overrideStyle({ property: "background", - variable: secondaryTransparent, + variable: secondaryHover, specificity: ["value"], }); diff --git a/scripts/vendor-dependencies.mjs b/scripts/vendor-dependencies.mjs index f5bae2c..237d672 100644 --- a/scripts/vendor-dependencies.mjs +++ b/scripts/vendor-dependencies.mjs @@ -12,7 +12,7 @@ import { fileURLToPath } from "node:url"; const dependencies = { "htm.min.js": "https://unpkg.com/htm@3.1.1/mini/index.js", "twind.min.js": "https://unpkg.com/@twind/cdn@1.0.7/cdn.global.js", - "lucide.min.js": "https://unpkg.com/lucide@0.104.0/dist/umd/lucide.min.js", + "lucide.min.js": "https://unpkg.com/lucide@0.105.0/dist/umd/lucide.min.js", "coloris.min.js": "https://cdn.jsdelivr.net/gh/mdbassit/Coloris@latest/dist/coloris.min.js", "coloris.min.css": diff --git a/src/api/browser.js b/src/api/browser.js index e856d10..c0a1c34 100644 --- a/src/api/browser.js +++ b/src/api/browser.js @@ -39,38 +39,35 @@ const sendMessage = (channel, message) => { const initDatabase = (namespace, fallbacks = {}) => { if (Array.isArray(namespace)) namespace = namespace.join("__"); namespace = namespace ? namespace + "__" : ""; + const namespaceify = (key) => + key.startsWith(namespace) ? key : namespace + key; return { get: async (key) => { const fallback = fallbacks[key]; - key = key.startsWith(namespace) ? key : namespace + key; - return new Promise((res, _rej) => { - chrome.storage.local.get([key], ({ [key]: value }) => { - return res(value ?? fallback); - }); - }); + key = namespaceify(key); + return (await chrome.storage.local.get([key]))[key] ?? fallback; }, - set: async (key, value) => { - key = key.startsWith(namespace) ? key : namespace + key; - return new Promise((res, _rej) => { - chrome.storage.local.set({ [key]: value }, () => res(true)); - }); + set: (key, value) => { + key = namespaceify(key); + return chrome.storage.local.set({ [key]: value }); + }, + remove: (keys) => { + keys = Array.isArray(keys) ? keys : [keys]; + keys = keys.map(namespaceify); + return chrome.storage.local.remove(keys); }, export: async () => { - const obj = await new Promise((res, _rej) => { - chrome.storage.local.get((value) => res(value)); - }); + const obj = await chrome.storage.local.get(); if (!namespace) return obj; const entries = Object.entries(obj) .filter(([key]) => key.startsWith(namespace)) .map(([key, value]) => [key.slice(namespace.length), value]); return Object.fromEntries(entries); }, - import: async (obj) => { + import: (obj) => { const entries = Object.entries(obj) // .map(([key, value]) => [namespace + key, value]); - return new Promise((res, _rej) => { - chrome.storage.local.set(Object.fromEntries(entries), () => res(true)); - }); + return chrome.storage.local.set(Object.fromEntries(entries)); }, }; }; diff --git a/src/api/electron.cjs b/src/api/electron.cjs index 479e704..a2a12ba 100644 --- a/src/api/electron.cjs +++ b/src/api/electron.cjs @@ -50,6 +50,8 @@ let __db; const initDatabase = (namespace, fallbacks = {}) => { if (Array.isArray(namespace)) namespace = namespace.join("__"); namespace = namespace ? namespace + "__" : ""; + const namespaceify = (key) => + key.startsWith(namespace) ? key : namespace + key; // schema: // - ("profileIds") = $profileId[] @@ -68,11 +70,11 @@ const initDatabase = (namespace, fallbacks = {}) => { init.run(); __db = db; - // prettier-ignore const insert = db.prepare(`INSERT INTO ${table} (key, value) VALUES (?, ?)`), - // prettier-ignore update = db.prepare(`UPDATE ${table} SET value = ? WHERE key = ?`), select = db.prepare(`SELECT * FROM ${table} WHERE key = ? LIMIT 1`), + remove = db.prepare(`DELETE FROM ${table} WHERE key = ?`), + removeMany = db.transaction((arr) => arr.forEach((key) => remove.run(key))), dump = db.prepare(`SELECT * FROM ${table}`), populate = db.transaction((obj) => { for (const key in obj) { @@ -85,7 +87,7 @@ const initDatabase = (namespace, fallbacks = {}) => { // wrap methods in promises for consistency w/ chrome.storage get: (key) => { const fallback = fallbacks[key]; - key = key.startsWith(namespace) ? key : namespace + key; + key = namespaceify(key); try { const value = JSON.parse(select.get(key)?.value); return Promise.resolve(value ?? fallback); @@ -93,13 +95,19 @@ const initDatabase = (namespace, fallbacks = {}) => { return Promise.resolve(fallback); }, set: (key, value) => { - key = key.startsWith(namespace) ? key : namespace + key; + key = namespaceify(key); value = JSON.stringify(value); if (select.get(key) === undefined) { insert.run(key, value); } else update.run(value, key); return Promise.resolve(true); }, + remove: (keys) => { + keys = Array.isArray(keys) ? keys : [keys]; + keys = keys.map(namespaceify); + removeMany(keys); + return Promise.resolve(true); + }, export: () => { const entries = dump .all() diff --git a/src/api/mods.js b/src/api/mods.js index 8434a91..8d7683f 100644 --- a/src/api/mods.js +++ b/src/api/mods.js @@ -51,7 +51,16 @@ const getProfile = async () => { enabledMods = initDatabase([await getProfile(), "enabledMods"]); return Boolean(await enabledMods.get(id)); }, - optionDefaults = async (id) => { + setEnabled = async (id, enabled) => { + const { initDatabase } = globalThis.__enhancerApi; + // prettier-ignore + return await initDatabase([ + await getProfile(), + "enabledMods" + ]).set(id, enabled); + }; + +const optionDefaults = async (id) => { const mod = (await getMods()).find((mod) => mod.id === id), optionEntries = mod.options .map((opt) => { @@ -64,6 +73,10 @@ const getProfile = async () => { }) .filter((opt) => opt); return Object.fromEntries(optionEntries); + }, + modDatabase = async (id) => { + const { initDatabase } = globalThis.__enhancerApi; + return initDatabase([await getProfile(), id], await optionDefaults(id)); }; globalThis.__enhancerApi ??= {}; @@ -75,5 +88,7 @@ Object.assign(globalThis.__enhancerApi, { getIntegrations, getProfile, isEnabled, + setEnabled, optionDefaults, + modDatabase, }); diff --git a/src/core/menu/components.mjs b/src/core/menu/components.mjs index 8134eb5..a62fb61 100644 --- a/src/core/menu/components.mjs +++ b/src/core/menu/components.mjs @@ -9,7 +9,7 @@ import { setState, useState, getState } from "./state.mjs"; // generic function _Button( - { type, size, icon, primary, class: cls = "", ...props }, + { type, size, variant, icon, class: cls = "", ...props }, ...children ) { const { html } = globalThis.__enhancerApi, @@ -20,10 +20,14 @@ function _Button( return html`<${type} class="flex gap-[8px] items-center px-[12px] shrink-0 rounded-[4px] ${size === "sm" ? "h-[28px]" : "h-[32px]"} - transition duration-[20ms] ${primary + transition duration-[20ms] ${variant === "primary" ? `text-[color:var(--theme--accent-primary\\_contrast)] font-medium bg-[color:var(--theme--accent-primary)] hover:bg-[color:var(--theme--accent-primary\\_hover)]` + : variant === "secondary" + ? `text-[color:var(--theme--accent-secondary)] + border-(& [color:var(--theme--accent-secondary)]) + hover:bg-[color:var(--theme--accent-secondary\\_hover)]` : `border-(& [color:var(--theme--fg-border)]) hover:bg-[color:var(--theme--bg-hover)]`} ${cls}" ...${props} @@ -211,6 +215,79 @@ function View({ id }, ...children) { return $el; } +function Popup( + { for: $trigger, onopen, onclose, onbeforeclose, ...props }, + ...children +) { + const { html } = globalThis.__enhancerApi, + $popup = html`
+ Are you sure you want to delete the profile ${$name} permanently? +
+