Add theme-and-state-adaptive icons, fixes #29

This commit is contained in:
Jean Viscogliosi-Pate 2025-06-29 13:10:54 -07:00
parent c36d9e9996
commit 5dafbbc59c
7 changed files with 361 additions and 62 deletions

82
src/background.js Normal file
View File

@ -0,0 +1,82 @@
let colorScheme = "light";
let state = "neutral";
/* Update the extension's icon by color scheme and state */
function updateIcon(event) {
browser.action.setIcon({
path: {
16: "icons/" + colorScheme + "-state-" + state + "-16x.png",
32: "icons/" + colorScheme + "-state-" + state + "-32x.png",
48: "icons/" + colorScheme + "-state-" + state + "-48x.png",
64: "icons/" + colorScheme + "-state-" + state + "-64x.png",
96: "icons/" + colorScheme + "-state-" + state + "-96x.png",
128: "icons/" + colorScheme + "-state-" + state + "-128x.png"
},
});
}
/* Listen for color scheme changes */
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateColorScheme);
/* Update the color scheme to light or dark */
function updateColorScheme(event) {
const isDark = event.matches;
if (isDark) {
colorScheme = "dark";
} else {
colorScheme = "light";
}
updateIcon();
}
/* Listen for tab changes */
window.addEventListener("load", updateState());
browser.tabs.onUpdated.addListener(updateStateOnReload, {urls: ["*://*/*"]});
browser.tabs.onActivated.addListener(updateState);
/* Update whether the extension is enabled for a host */
function updateState() {
let tabs = browser.tabs.query({ active: true, currentWindow: true });
tabs.then((tabs) => {
const tab = tabs[0];
const url = new URL(tab.url);
const isAllowedProtocol = url.protocol == "https:" || url.protocol == "http:";
if (!isAllowedProtocol) {
state = "neutral";
updateIcon();
return;
}
let storage = browser.storage.local.get("domains");
storage.then((storage) => {
const hostname = url.host;
const domains = storage.domains;
if (domains === undefined) {
state = "disabled";
}
else if (domains.includes(hostname)) {
state = "enabled";
} else {
state = "disabled";
}
}, logError).then(() => {
updateIcon();
}, logError);
}, logError);
}
/* Only update the state if the page was reloaded */
function updateStateOnReload(tabIs, changeInfo, tab) {
if (changeInfo.url && tab.url.host === changeInfo.url.host) {
return;
} else {
updateState();
}
}
/* Required by then */
function logError(error) {
console.log(`Error: ${error}`);
}

60
src/manifest-chrome.json Normal file
View File

@ -0,0 +1,60 @@
{
"manifest_version": 3,
"name": "MetamorPOV",
"author": "Jean Viscogliosi-Pate",
"version": "1.3.0",
"description": "Enables customization of reader-insert stories by replacing author-provided hooks such as Y/n, pov/s, and vrb/do/present/.",
"action": {
"default_title": "MetamorPOV",
"default_popup": "popup.html",
"default_icon": {
"16": "icons/light-state-neutral-16x.png",
"32": "icons/light-state-neutral-32x.png",
"48": "icons/light-state-neutral-48x.png",
"64": "icons/light-state-neutral-64x.png",
"96": "icons/light-state-neutral-96x.png",
"128": "icons/light-state-neutral-128x.png"
}
},
"permissions": [
"storage",
"tabs",
"offscreen"
],
"host_permissions": [
"http://*/*",
"https://*/*"
],
"icons": {
"16": "icons/light-state-neutral-16x.png",
"32": "icons/light-state-neutral-32x.png",
"48": "icons/light-state-neutral-48x.png",
"64": "icons/light-state-neutral-64x.png",
"96": "icons/light-state-neutral-96x.png",
"128": "icons/light-state-neutral-128x.png"
},
"content_scripts": [
{
"js": [
"content-script.js"
],
"run_at": "document_idle",
"matches": [
"*://*/*"
]
}
],
"background": {
"service_worker": "service-worker.js",
"type": "module"
},
"homepage_url": "https://git.viscogliosi-pate.com/jean/metamorpov"
}

93
src/manifest-firefox.json Normal file
View File

@ -0,0 +1,93 @@
{
"manifest_version": 3,
"name": "MetamorPOV",
"author": "Jean Viscogliosi-Pate",
"version": "1.3.0",
"description": "Enables customization of reader-insert stories by replacing author-provided hooks such as Y/n, pov/s, and vrb/do/present/.",
"browser_specific_settings": {
"gecko": {
"id": "metamorpov@viscogliosi-pate.com",
"strict_min_version": "113.0"
},
"gecko_android": {}
},
"action": {
"default_title": "MetamorPOV",
"default_popup": "popup.html",
"default_icon": {
"16": "icons/light-state-neutral-16x.png",
"32": "icons/light-state-neutral-32x.png",
"48": "icons/light-state-neutral-48x.png",
"64": "icons/light-state-neutral-64x.png",
"96": "icons/light-state-neutral-96x.png",
"128": "icons/light-state-neutral-128x.png"
},
"theme_icons": [{
"light": "icons/light-state-neutral-16x.png",
"dark": "icons/dark-state-neutral-16x.png",
"size": 16
}, {
"light": "icons/light-state-neutral-32x.png",
"dark": "icons/dark-state-neutral-32x.png",
"size": 32
}, {
"light": "icons/light-state-neutral-48x.png",
"dark": "icons/dark-state-neutral-48x.png",
"size": 48
}, {
"light": "icons/light-state-neutral-64x.png",
"dark": "icons/dark-state-neutral-64x.png",
"size": 64
}, {
"light": "icons/light-state-neutral-96x.png",
"dark": "icons/dark-state-neutral-96x.png",
"size": 96
}, {
"light": "icons/light-state-neutral-128x.png",
"dark": "icons/dark-state-neutral-128x.png",
"size": 128
}]
},
"permissions": [
"storage",
"tabs"
],
"host_permissions": [
"http://*/*",
"https://*/*"
],
"icons": {
"16": "icons/light-state-neutral-16x.png",
"32": "icons/light-state-neutral-32x.png",
"48": "icons/light-state-neutral-48x.png",
"64": "icons/light-state-neutral-64x.png",
"96": "icons/light-state-neutral-96x.png",
"128": "icons/light-state-neutral-128x.png"
},
"content_scripts": [
{
"js": [
"content-script.js"
],
"run_at": "document_idle",
"matches": [
"*://*/*"
]
}
],
"background": {
"scripts": [
"background.js"
]
},
"homepage_url": "https://git.viscogliosi-pate.com/jean/metamorpov"
}

View File

@ -1,62 +0,0 @@
{
"manifest_version": 3,
"name": "MetamorPOV",
"author": "Jean Viscogliosi-Pate",
"version": "1.2.0",
"description": "Enables customization of reader-insert stories by replacing author-provided hooks such as Y/n, pov/s, and vrb/do/present/.",
"browser_specific_settings": {
"gecko": {
"id": "metamorpov@viscogliosi-pate.com",
"strict_min_version": "113.0"
},
"gecko_android": {}
},
"action": {
"default_title": "MetamorPOV",
"default_icon": {
"16": "icons/16x.png",
"32": "icons/32x.png",
"48": "icons/48x.png",
"64": "icons/64x.png",
"96": "icons/96x.png",
"128": "icons/128x.png"
},
"default_popup": "popup.html"
},
"permissions": [
"storage",
"tabs"
],
"host_permissions": [
"http://*/*",
"https://*/*"
],
"icons": {
"16": "icons/16x.png",
"32": "icons/32x.png",
"48": "icons/48x.png",
"64": "icons/64x.png",
"96": "icons/96x.png",
"128": "icons/128x.png"
},
"content_scripts": [
{
"js": [
"content-script.js"
],
"run_at": "document_idle",
"matches": [
"*://*/*"
]
}
],
"homepage_url": "https://git.viscogliosi-pate.com/jean/metamorpov"
}

View File

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script src="service-worker-window.js"></script>
</body>
</html>

View File

@ -0,0 +1,19 @@
/* Broadcast color scheme on browser start, because no change event is fired */
updateColorScheme(window.matchMedia('(prefers-color-scheme: dark)'));
function updateColorScheme(event) {
console.log("doing!");
chrome.runtime.sendMessage({
content: "colorScheme",
isDark: event.matches
});
}
/* Broadcast when page is loaded */
window.addEventListener("load", updateState);
function updateState() {
chrome.runtime.sendMessage({
content: "updateState"
});
}

99
src/service-worker.js Normal file
View File

@ -0,0 +1,99 @@
let colorScheme = "light";
let state = "neutral";
/* Update the extension's icon by color scheme and state */
function updateIcon(event) {
chrome.action.setIcon({
path: {
16: "icons/" + colorScheme + "-state-" + state + "-16x.png",
32: "icons/" + colorScheme + "-state-" + state + "-32x.png",
48: "icons/" + colorScheme + "-state-" + state + "-48x.png",
64: "icons/" + colorScheme + "-state-" + state + "-64x.png",
96: "icons/" + colorScheme + "-state-" + state + "-96x.png",
128: "icons/" + colorScheme + "-state-" + state + "-128x.png"
},
});
}
/* Track CSS media feature offscreen */
chrome.offscreen.createDocument({
url: "service-worker-window.html",
reasons: ["MATCH_MEDIA"],
justification: "Update icon by color scheme",
})
/* Listen for color scheme changes */
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.content === "colorScheme") {
updateColorScheme(request.isDark);
}
}
)
/* Update the color scheme to light or dark */
function updateColorScheme(isDark) {
if (isDark) {
colorScheme = "dark";
} else {
colorScheme = "light";
}
updateIcon();
}
/* Listen for tab changes */
chrome.tabs.onUpdated.addListener(updateStateOnReload);
chrome.tabs.onActivated.addListener(updateState);
/* Update whether the extension is enabled for a host */
function updateState() {
let tabs = chrome.tabs.query({ active: true, currentWindow: true });
tabs.then((tabs) => {
const tab = tabs[0];
if (tab.url === "") {
state = "neutral";
updateIcon();
return;
}
const url = new URL(tab.url);
const isAllowedProtocol = url.protocol == "https:" || url.protocol == "http:";
if (!isAllowedProtocol) {
state = "neutral";
updateIcon();
return;
}
let storage = chrome.storage.local.get("domains");
storage.then((storage) => {
const hostname = url.host;
const domains = storage.domains;
if (domains === undefined) {
state = "disabled";
}
else if (domains.includes(hostname)) {
state = "enabled";
} else {
state = "disabled";
}
}, logError).then(() => {
updateIcon();
}, logError);
}, logError);
}
/* Only update the state if the page was reloaded */
function updateStateOnReload(tabIs, changeInfo, tab) {
if (changeInfo.url && tab.url.host === changeInfo.url.host) {
return;
} else {
updateState();
}
}
/* Required by then */
function logError(error) {
console.log(`Error: ${error}`);
}