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 }) => {
}}
/>
+