diff --git a/package.json b/package.json index a2837d1..c29279b 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "zip:firefox": "export BROWSER=firefox && yarn build && node src/zip.js" }, "dependencies": { + "colorthief": "^2.6.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/public/img/weathers/01d.png b/public/img/weathers/01d.png new file mode 100644 index 0000000..ed42ad9 Binary files /dev/null and b/public/img/weathers/01d.png differ diff --git a/public/img/weathers/01n.png b/public/img/weathers/01n.png new file mode 100644 index 0000000..85efa16 Binary files /dev/null and b/public/img/weathers/01n.png differ diff --git a/public/img/weathers/02d.png b/public/img/weathers/02d.png new file mode 100644 index 0000000..fabd9c3 Binary files /dev/null and b/public/img/weathers/02d.png differ diff --git a/public/img/weathers/02n.png b/public/img/weathers/02n.png new file mode 100644 index 0000000..288a40e Binary files /dev/null and b/public/img/weathers/02n.png differ diff --git a/public/img/weathers/03d.png b/public/img/weathers/03d.png new file mode 100644 index 0000000..ef2e9f7 Binary files /dev/null and b/public/img/weathers/03d.png differ diff --git a/public/img/weathers/03n.png b/public/img/weathers/03n.png new file mode 100644 index 0000000..ef2e9f7 Binary files /dev/null and b/public/img/weathers/03n.png differ diff --git a/public/img/weathers/04d.png b/public/img/weathers/04d.png new file mode 100644 index 0000000..9c64ea8 Binary files /dev/null and b/public/img/weathers/04d.png differ diff --git a/public/img/weathers/04n.png b/public/img/weathers/04n.png new file mode 100644 index 0000000..9c64ea8 Binary files /dev/null and b/public/img/weathers/04n.png differ diff --git a/public/img/weathers/09d.png b/public/img/weathers/09d.png new file mode 100644 index 0000000..0f14cb6 Binary files /dev/null and b/public/img/weathers/09d.png differ diff --git a/public/img/weathers/09n.png b/public/img/weathers/09n.png new file mode 100644 index 0000000..0f14cb6 Binary files /dev/null and b/public/img/weathers/09n.png differ diff --git a/public/img/weathers/10d.png b/public/img/weathers/10d.png new file mode 100644 index 0000000..62304fd Binary files /dev/null and b/public/img/weathers/10d.png differ diff --git a/public/img/weathers/10n.png b/public/img/weathers/10n.png new file mode 100644 index 0000000..b5e5d10 Binary files /dev/null and b/public/img/weathers/10n.png differ diff --git a/public/img/weathers/11d.png b/public/img/weathers/11d.png new file mode 100644 index 0000000..4a885cf Binary files /dev/null and b/public/img/weathers/11d.png differ diff --git a/public/img/weathers/11n.png b/public/img/weathers/11n.png new file mode 100644 index 0000000..4a885cf Binary files /dev/null and b/public/img/weathers/11n.png differ diff --git a/public/img/weathers/13d.png b/public/img/weathers/13d.png new file mode 100644 index 0000000..7867322 Binary files /dev/null and b/public/img/weathers/13d.png differ diff --git a/public/img/weathers/13n.png b/public/img/weathers/13n.png new file mode 100644 index 0000000..7867322 Binary files /dev/null and b/public/img/weathers/13n.png differ diff --git a/public/img/weathers/50d.png b/public/img/weathers/50d.png new file mode 100644 index 0000000..f04122b Binary files /dev/null and b/public/img/weathers/50d.png differ diff --git a/public/img/weathers/50n.png b/public/img/weathers/50n.png new file mode 100644 index 0000000..f04122b Binary files /dev/null and b/public/img/weathers/50n.png differ diff --git a/src/background/index.js b/src/background/index.js index c081fb1..2bc2f7d 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -20,6 +20,10 @@ chrome.runtime.onInstalled.addListener(async () => { showsTime: true, initialized: true, maxBlobSize: 52_428_800, // 50MB in bytes + showsWeather: false, + weatherApiKey: null, + coords: null, + weatherUnit: 'C', }) } catch (error) { console.error('Failed to init data', error) diff --git a/src/newtab/Background.jsx b/src/newtab/Background.jsx index 93fa151..9ce0fad 100644 --- a/src/newtab/Background.jsx +++ b/src/newtab/Background.jsx @@ -1,18 +1,36 @@ -import { memo } from 'react' +import ColorThief from 'colorthief' +import { createRef, memo, useEffect } from 'react' +import { rgbToHex } from './utils' -const Background = ({ media, blur = 0, width, height, ...props }) => { +const colorThief = new ColorThief() + +const Background = ({ media, blur = 0, width, height, onColorThief = null, ...props }) => { + const imgRef = createRef() let style = { filter: `blur(${(blur * 20) / 100}px)`, // max 20px width, height, } + // reset color thief on video change + useEffect(() => { + if (media.blob && media.blob.type.startsWith('video')) { + onColorThief?.(null) + } + }, [media]) + if (media.blob) { const objectUrl = URL.createObjectURL(media.blob) if (media.blob.type.startsWith('image')) { return ( { + const img = imgRef.current + const result = colorThief.getColor(img, 25) + onColorThief?.(rgbToHex(result)) + }} className="pointer-events-none object-cover w-full h-full blur-[10px]" src={objectUrl} style={style} diff --git a/src/newtab/NewTab.jsx b/src/newtab/NewTab.jsx index 6f29663..82e2a14 100644 --- a/src/newtab/NewTab.jsx +++ b/src/newtab/NewTab.jsx @@ -4,10 +4,12 @@ import { db, settingsStorage } from '../helper' import '../index.css' import Background from './Background' import Time from './Time' +import Weather from './Weather' export const NewTab = () => { const [media, setMedia] = useState(null) const [config, setConfig] = useState({}) + const [dominantColor, setDominantColor] = useState(null) function initialize() { settingsStorage.get('mediaId').then((mediaId) => { @@ -48,13 +50,24 @@ export const NewTab = () => { return (
- {media && } + {media && }
{config.showsTime && ( -
+
)} + {config.showsWeather && ( +
+ +
+ )}
) } diff --git a/src/newtab/Time.jsx b/src/newtab/Time.jsx index 5f56874..daaa1f3 100644 --- a/src/newtab/Time.jsx +++ b/src/newtab/Time.jsx @@ -22,7 +22,7 @@ const Time = () => { return (
-

{time}

+

{time}

) } diff --git a/src/newtab/Weather.jsx b/src/newtab/Weather.jsx new file mode 100644 index 0000000..19e0d8f --- /dev/null +++ b/src/newtab/Weather.jsx @@ -0,0 +1,89 @@ +import { useEffect, useState } from 'react' +import { showToast } from '../components/Toast' +import { settingsStorage } from '../helper' + +function Weather() { + const [weather, setWeather] = useState(null) + const [coords, setCoords] = useState(null) + const [unit, setUnit] = useState('C') + + useEffect(() => { + async function getCoords() { + const savedCoords = await settingsStorage.get('coords') + setCoords(savedCoords) + + if (!savedCoords) { + navigator.geolocation.getCurrentPosition( + (position) => { + const newCoords = { + latitude: position.coords.latitude, + longitude: position.coords.longitude, + } + settingsStorage.set('coords', newCoords) + setCoords(newCoords) + }, + (error) => { + showToast(error.message) + console.error(error) + }, + ) + } + } + getCoords() + }, []) + + useEffect(() => { + async function getWeather() { + const appid = await settingsStorage.get('weatherApiKey') + const unit = await settingsStorage.get('weatherUnit') + setUnit(unit) + + if (!appid) { + return console.warn('Please provide weather API key in settings') + } + if (coords) { + fetch( + `https://api.openweathermap.org/data/2.5/weather?${new URLSearchParams({ + lat: coords.latitude, + lon: coords.longitude, + appid, + units: unit === 'C' ? 'metric' : 'imperial', + })}`, + ) + .then(async (res) => { + const json = await res.json() + if (res.ok) { + setWeather(json) + } else + throw new Error(json.message ?? 'Unknown error occurred while fetching weather data.') + }) + .catch((error) => { + console.error(error) + showToast(error.message) + }) + } + } + + getWeather() + }, [coords]) + + if (!weather) { + return null + } + + return ( +
+

+ Temperature: {weather.main.temp}°{unit} +

+

Humidity: {weather.main.humidity}%

+

Pressure: {weather.main.pressure}hPa

+

+ + {weather.weather[0].description} +

+
+ ) +} + +export default Weather diff --git a/src/newtab/index.jsx b/src/newtab/index.jsx index db9a764..b01aad3 100644 --- a/src/newtab/index.jsx +++ b/src/newtab/index.jsx @@ -1,10 +1,12 @@ import React from 'react' import ReactDOM from 'react-dom/client' +import { Toast, toastRef } from '../components/Toast' import { NewTab } from './NewTab' import './index.css' ReactDOM.createRoot(document.getElementById('app')).render( + , ) diff --git a/src/newtab/utils.js b/src/newtab/utils.js new file mode 100644 index 0000000..0a604a9 --- /dev/null +++ b/src/newtab/utils.js @@ -0,0 +1,10 @@ +const rgbToHex = ([r, g, b]) => + '#' + + [r, g, b] + .map((x) => { + const hex = x.toString(16) + return hex.length === 1 ? '0' + hex : hex + }) + .join('') + +export { rgbToHex } diff --git a/src/options/ConfigSection.jsx b/src/options/ConfigSection.jsx index 4d5ae87..4f64796 100644 --- a/src/options/ConfigSection.jsx +++ b/src/options/ConfigSection.jsx @@ -3,15 +3,29 @@ import { settingsStorage } from '../helper' const ConfigSection = ({ onConfigChanged }) => { const [showsTime, setshowsTime] = useState(false) + const [showsWeather, setShowsWeather] = useState(false) + const [unit, setUnit] = useState('C') + const [apiKey, setApiKey] = useState(null) const [blurValue, setBlurValue] = useState(0) useEffect(() => { settingsStorage.get().then((config) => { setshowsTime(config.showsTime ?? false) setBlurValue(config.blur ?? 0) + setApiKey(config.weatherApiKey) + setShowsWeather(config.showsWeather ?? false) }) }, []) + // debounce saving apiKey to storage + useEffect(() => { + const timeout = setTimeout(() => { + if (apiKey) settingsStorage.set('weatherApiKey', apiKey) + }, 500) + + return () => clearTimeout(timeout) + }, [apiKey]) + return (

Config

@@ -29,6 +43,46 @@ const ConfigSection = ({ onConfigChanged }) => { }} /> +