diff --git a/public/manifest.json b/public/manifest.json index dc4097e..07742e9 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -23,5 +23,11 @@ "32": "images/icon32.png", "48": "images/icon48.png", "128": "images/icon128.png" - } + }, + "content_scripts": [ + { + "matches": ["https://www.netflix.com/watch/*"], + "js": ["assets/content-scripts/netflix.js"] + } + ] } diff --git a/src/App.tsx b/src/App.tsx index 0a31518..e32f996 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,15 @@ import { Button, ColorPicker, InputLabel, Stack, Switch } from '@mantine/core'; -import { useCallback, useEffect } from 'react'; +import { useCallback } from 'react'; import { IoRefresh } from 'react-icons/io5'; import { useChromeSyncStorageState } from './hooks'; import { Style, defaultStyle, - applyStyle, styleStorageKey, isOnStorageKey, defaultIsOn, IsOn, - removeStyle, + sendChangeStyleMessage, } from './modules'; const mantinePaddingMd: number = 16; @@ -24,36 +23,42 @@ export function App() { const handleChangeOnOff = useCallback( (e: React.ChangeEvent) => { - setIsOn(e.currentTarget.checked); + setIsOn(() => { + const checked = e.target.checked; + sendChangeStyleMessage(checked, style); + return checked; + }); }, - [setIsOn] + [setIsOn, style] ); - const handleChangeTextColor = useCallback( - (value: string) => { - setStyle((prev) => ({ ...prev, textColor: value })); + const handleChangeColor = useCallback( + (property: keyof Style, value: string) => { + setStyle((prev) => { + const style = { ...prev, [property]: value }; + sendChangeStyleMessage(isOn, style); + return style; + }); }, - [setStyle] + [isOn, setStyle] + ); + + const handleChangeTextColor = useCallback( + (value: string) => handleChangeColor('textColor', value), + [handleChangeColor] ); const handleChangeBgColor = useCallback( - (value: string) => { - setStyle((prev) => ({ ...prev, backgroundColor: value })); - }, - [setStyle] + (value: string) => handleChangeColor('backgroundColor', value), + [handleChangeColor] ); const handleClickReset = useCallback(() => { - setStyle(defaultStyle); - }, [setStyle]); - - useEffect(() => { - if (isOn) { - applyStyle(style); - } else { - removeStyle(style); - } - }, [isOn, style]); + setStyle(() => { + sendChangeStyleMessage(isOn, defaultStyle); + return defaultStyle; + }); + }, [isOn, setStyle]); return ( diff --git a/src/content-scripts/netflix.ts b/src/content-scripts/netflix.ts new file mode 100644 index 0000000..905e293 --- /dev/null +++ b/src/content-scripts/netflix.ts @@ -0,0 +1,69 @@ +const isOnStorageKey: string = 'isOn'; + +interface Style { + textColor: string; + backgroundColor: string; +} + +const styleStorageKey: string = 'style'; + +const defaultStyle: Style = { + textColor: 'rgba(255, 255, 255, 1)', + backgroundColor: 'rgba(0, 0, 0, 0.5)', +}; + +function applyStyle(style: Style) { + const css = ` + .player-timedtext > .player-timedtext-text-container > span > span { + color: ${style.textColor} !important; + background-color: ${style.backgroundColor} !important; + } + `; + + const currentStyleTag = document.querySelector('style#sub-stylist-style') as HTMLStyleElement | null; + if (currentStyleTag) { + currentStyleTag.textContent = css; + return; + } + + const styleTag = document.createElement('style'); + styleTag.id = 'sub-stylist-style'; + styleTag.textContent = css; + document.head.appendChild(styleTag); +} + +function removeStyle() { + const currentStyleTag = document.querySelector('style#sub-stylist-style') as HTMLStyleElement | null; + if (currentStyleTag) { + currentStyleTag.remove(); + } +} + +async function loadAndApplyStyle() { + const states = await chrome.storage.sync.get([isOnStorageKey, styleStorageKey]); + const isOn = states[isOnStorageKey] as boolean | undefined; + if (!isOn) { + return; + } + + const style = states[styleStorageKey] as Style | undefined; + if (style !== undefined) { + applyStyle(style); + } else { + applyStyle(defaultStyle); + } +} + +chrome.runtime.onMessage.addListener((message) => { + if (message.action === 'changeStyle') { + const isOn = message.payload.isOn as boolean; + if (isOn) { + const style = message.payload.style as Style; + applyStyle(style); + } else { + removeStyle(); + } + } +}); + +loadAndApplyStyle(); diff --git a/src/modules/style.ts b/src/modules/style.ts index 4509952..bd7eaf0 100644 --- a/src/modules/style.ts +++ b/src/modules/style.ts @@ -15,25 +15,8 @@ export const defaultStyle: Style = { backgroundColor: 'rgba(0, 0, 0, 0.5)', }; -export function getPlatformOverrideCss(url: string, style: Style) { - // - // Netflix - // - if (url.startsWith('https://www.netflix.com/watch/')) { - return ` - .player-timedtext > .player-timedtext-text-container > span > span { - color: ${style.textColor} !important; - background-color: ${style.backgroundColor} !important; - } - `; - } - - // - // Other will append below - // -} - -export async function applyStyle(style: Style) { +export async function sendChangeStyleMessage(isOn: boolean, style: Style) { + console.log('send "changeStyle" message'); const activeTab = await getActiveTab(); if (!activeTab.id) { @@ -41,44 +24,5 @@ export async function applyStyle(style: Style) { return; } - if (!activeTab.url) { - console.error("don't have active tab url"); - return; - } - - const css = getPlatformOverrideCss(activeTab.url, style); - if (!css) { - console.error('this platform not support'); - return; - } - - await chrome.scripting.insertCSS({ - target: { tabId: activeTab.id }, - css: css, - }); -} - -export async function removeStyle(style: Style) { - const activeTab = await getActiveTab(); - - if (!activeTab.id) { - console.error("don't have active tab id"); - return; - } - - if (!activeTab.url) { - console.error("don't have active tab url"); - return; - } - - const css = getPlatformOverrideCss(activeTab.url, style); - if (!css) { - console.error('this platform not support'); - return; - } - - await chrome.scripting.removeCSS({ - target: { tabId: activeTab.id }, - css: css, - }); + await chrome.tabs.sendMessage(activeTab.id, { action: 'changeStyle', payload: { isOn, style } }); } diff --git a/src/service-worker.ts b/src/service-worker.ts index 3486962..e69de29 100644 --- a/src/service-worker.ts +++ b/src/service-worker.ts @@ -1,21 +0,0 @@ -import { applyStyle, defaultStyle, isOnSchema, isOnStorageKey, styleSchema, styleStorageKey } from './modules'; - -async function loadAndApplyStyle() { - const states = await chrome.storage.sync.get([isOnStorageKey, styleStorageKey]); - const isOn = isOnSchema.optional().parse(states[isOnStorageKey]); - if (!isOn) { - console.warn('now off'); - return; - } - - const style = styleSchema.optional().parse(states[styleStorageKey]); - if (style !== undefined) { - await applyStyle(style); - } else { - await applyStyle(defaultStyle); - } -} - -chrome.tabs.onUpdated.addListener(() => { - loadAndApplyStyle(); -}); diff --git a/vite.config.ts b/vite.config.ts index 7e54ba9..646f380 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -9,6 +9,7 @@ export default defineConfig({ input: { popup: 'popup.html', 'service-worker': './src/service-worker.ts', + 'content-scripts/netflix': './src/content-scripts/netflix.ts', }, // build without hash