mirror of
https://github.com/h3poteto/whalebird-desktop
synced 2025-01-04 13:05:11 +01:00
refs #4887 Switch dark/light mode
This commit is contained in:
parent
bffa45e331
commit
d058a81be4
@ -167,6 +167,9 @@
|
|||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"font_size": "Font size",
|
"font_size": "Font size",
|
||||||
|
"mode": "Color mode",
|
||||||
|
"dark_mode": "Dark mode",
|
||||||
|
"light_mode": "Light mode",
|
||||||
"theme": "Color theme"
|
"theme": "Color theme"
|
||||||
},
|
},
|
||||||
"thirdparty": {
|
"thirdparty": {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { localeType } from '@/provider/i18n'
|
import { localeType } from '@/provider/i18n'
|
||||||
import { Dialog, DialogBody, DialogHeader, Input, Option, Select, Typography } from '@material-tailwind/react'
|
import { Dialog, DialogBody, DialogHeader, Input, Option, Radio, Select, Typography } from '@material-tailwind/react'
|
||||||
import { ChangeEvent, useEffect, useState } from 'react'
|
import { ChangeEvent, useEffect, useState } from 'react'
|
||||||
import { FormattedMessage } from 'react-intl'
|
import { FormattedMessage } from 'react-intl'
|
||||||
|
|
||||||
@ -55,6 +55,7 @@ export default function Settings(props: Props) {
|
|||||||
const [language, setLanguage] = useState<localeType>('en')
|
const [language, setLanguage] = useState<localeType>('en')
|
||||||
const [fontSize, setFontSize] = useState<number>(16)
|
const [fontSize, setFontSize] = useState<number>(16)
|
||||||
const [theme, setTheme] = useState<string>('theme-blue')
|
const [theme, setTheme] = useState<string>('theme-blue')
|
||||||
|
const [isDark, setIsDark] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof localStorage !== 'undefined') {
|
if (typeof localStorage !== 'undefined') {
|
||||||
@ -64,6 +65,12 @@ export default function Settings(props: Props) {
|
|||||||
} else {
|
} else {
|
||||||
setLanguage('en')
|
setLanguage('en')
|
||||||
}
|
}
|
||||||
|
const dark = localStorage.getItem('color-mode')
|
||||||
|
if (dark === 'dark') {
|
||||||
|
setIsDark(true)
|
||||||
|
} else {
|
||||||
|
setIsDark(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -91,6 +98,18 @@ export default function Settings(props: Props) {
|
|||||||
props.reloadSettings()
|
props.reloadSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const modeChanged = (isDark: boolean) => {
|
||||||
|
setIsDark(isDark)
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
if (isDark) {
|
||||||
|
localStorage.setItem('color-mode', 'dark')
|
||||||
|
} else {
|
||||||
|
localStorage.setItem('color-mode', 'light')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props.reloadSettings()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={props.opened} handler={props.close} size="sm">
|
<Dialog open={props.opened} handler={props.close} size="sm">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
@ -124,6 +143,27 @@ export default function Settings(props: Props) {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="mb-2">
|
||||||
|
<Typography>
|
||||||
|
<FormattedMessage id="settings.mode" />
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Radio
|
||||||
|
name="mode"
|
||||||
|
label={<FormattedMessage id="settings.dark_mode" />}
|
||||||
|
onClick={() => modeChanged(true)}
|
||||||
|
defaultChecked={isDark}
|
||||||
|
/>
|
||||||
|
<Radio
|
||||||
|
name="mode"
|
||||||
|
label={<FormattedMessage id="settings.light_mode" />}
|
||||||
|
onClick={() => modeChanged(false)}
|
||||||
|
defaultChecked={!isDark}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<Typography>
|
<Typography>
|
||||||
|
@ -34,6 +34,7 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
const [style, setStyle] = useState<CSSProperties>({})
|
const [style, setStyle] = useState<CSSProperties>({})
|
||||||
const [openPopover, setOpenPopover] = useState(false)
|
const [openPopover, setOpenPopover] = useState(false)
|
||||||
const [theme, setTheme] = useState('theme-blue')
|
const [theme, setTheme] = useState('theme-blue')
|
||||||
|
const [isDark, setIsDark] = useState(false)
|
||||||
|
|
||||||
const { switchLang } = useContext(Context)
|
const { switchLang } = useContext(Context)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -69,6 +70,11 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('isDark', isDark)
|
||||||
|
document.body.className = isDark ? 'dark' : 'light'
|
||||||
|
}, [isDark])
|
||||||
|
|
||||||
const closeNewModal = async () => {
|
const closeNewModal = async () => {
|
||||||
const acct = await db.accounts.toArray()
|
const acct = await db.accounts.toArray()
|
||||||
setAccounts(acct)
|
setAccounts(acct)
|
||||||
@ -121,12 +127,19 @@ export default function Layout({ children }: LayoutProps) {
|
|||||||
if (t && t.length > 0) {
|
if (t && t.length > 0) {
|
||||||
setTheme(t)
|
setTheme(t)
|
||||||
}
|
}
|
||||||
|
const dark = localStorage.getItem('color-mode')
|
||||||
|
console.log(dark)
|
||||||
|
if (dark && dark === 'dark') {
|
||||||
|
setIsDark(true)
|
||||||
|
} else {
|
||||||
|
setIsDark(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`app flex flex-col min-h-screen ${theme}`} style={style}>
|
<div className={`app flex flex-col min-h-screen ${theme}`} style={style}>
|
||||||
<main className="flex w-full box-border my-0 mx-auto min-h-screen">
|
<main className="flex w-full box-border my-0 mx-auto min-h-screen bg-white dark:bg-gray-900">
|
||||||
<aside className="w-16 theme-account-bg flex flex-col justify-between">
|
<aside className="w-16 theme-account-bg flex flex-col justify-between">
|
||||||
<div>
|
<div>
|
||||||
{accounts.map(account => (
|
{accounts.map(account => (
|
||||||
|
@ -12,7 +12,7 @@ export default function Card(props: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex border-inherit border border-solid rounded-md w-full cursor-pointer overflow-hidden text-ellipsis mb-1"
|
className="flex border-inherit border border-solid border-gray-200 dark:border-gray-800 rounded-md w-full cursor-pointer overflow-hidden text-ellipsis mb-1"
|
||||||
onClick={openCard}
|
onClick={openCard}
|
||||||
>
|
>
|
||||||
<div style={{ height: '60px', width: '60px' }}>
|
<div style={{ height: '60px', width: '60px' }}>
|
||||||
|
@ -79,7 +79,7 @@ export default function Status(props: Props) {
|
|||||||
props.filters.map(f => f.phrase).filter(keyword => props.status.content.toLowerCase().includes(keyword.toLowerCase())).length > 0
|
props.filters.map(f => f.phrase).filter(keyword => props.status.content.toLowerCase().includes(keyword.toLowerCase())).length > 0
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<div className="border-b mr-2 py-2 text-center">
|
<div className="border-b border-gray-200 dark:border-gray-800 mr-2 py-2 text-center">
|
||||||
<FormattedMessage id="timeline.status.filtered" />
|
<FormattedMessage id="timeline.status.filtered" />
|
||||||
<span className="theme-text-subtle cursor-pointer pl-4" onClick={() => setIgnoreFilter(true)}>
|
<span className="theme-text-subtle cursor-pointer pl-4" onClick={() => setIgnoreFilter(true)}>
|
||||||
<FormattedMessage id="timeline.status.show_anyway" />
|
<FormattedMessage id="timeline.status.show_anyway" />
|
||||||
@ -89,7 +89,7 @@ export default function Status(props: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-b mr-2 py-1">
|
<div className="border-b border-gray-200 dark:border-gray-800 mr-2 py-1">
|
||||||
{rebloggedHeader(
|
{rebloggedHeader(
|
||||||
props.status,
|
props.status,
|
||||||
formatMessage(
|
formatMessage(
|
||||||
@ -114,16 +114,16 @@ export default function Status(props: Props) {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-gray-950 break-all overflow-hidden" style={{ width: 'calc(100% - 56px)' }}>
|
<div className="text-gray-950 dark:text-gray-300 break-all overflow-hidden" style={{ width: 'calc(100% - 56px)' }}>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<div className="flex cursor-pointer" onClick={() => openUser(status.account.id)}>
|
<div className="flex cursor-pointer" onClick={() => openUser(status.account.id)}>
|
||||||
<span
|
<span
|
||||||
className="text-gray-950 text-ellipsis break-all overflow-hidden"
|
className="text-gray-950 dark:text-gray-300 text-ellipsis break-all overflow-hidden"
|
||||||
dangerouslySetInnerHTML={{ __html: emojify(status.account.display_name, status.account.emojis) }}
|
dangerouslySetInnerHTML={{ __html: emojify(status.account.display_name, status.account.emojis) }}
|
||||||
></span>
|
></span>
|
||||||
<span className="text-gray-600 text-ellipsis break-all overflow-hidden">@{status.account.acct}</span>
|
<span className="text-gray-600 dark:text-gray-500 text-ellipsis break-all overflow-hidden">@{status.account.acct}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-gray-600 text-right cursor-pointer" onClick={openStatus}>
|
<div className="text-gray-600 dark:text-gray-500 text-right cursor-pointer" onClick={openStatus}>
|
||||||
<time dateTime={status.created_at}>{dayjs(status.created_at).format('YYYY-MM-DD HH:mm:ss')}</time>
|
<time dateTime={status.created_at}>{dayjs(status.created_at).format('YYYY-MM-DD HH:mm:ss')}</time>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -164,7 +164,7 @@ const originalStatus = (status: Entity.Status) => {
|
|||||||
const rebloggedHeader = (status: Entity.Status, alt: string) => {
|
const rebloggedHeader = (status: Entity.Status, alt: string) => {
|
||||||
if (status.reblog && !status.quote) {
|
if (status.reblog && !status.quote) {
|
||||||
return (
|
return (
|
||||||
<div className="flex text-gray-600">
|
<div className="flex text-gray-600 dark:text-gray-500">
|
||||||
<div className="grid justify-items-end pr-2" style={{ width: '56px' }}>
|
<div className="grid justify-items-end pr-2" style={{ width: '56px' }}>
|
||||||
<Avatar src={status.account.avatar} size="xs" variant="rounded" alt={alt} />
|
<Avatar src={status.account.avatar} size="xs" variant="rounded" alt={alt} />
|
||||||
</div>
|
</div>
|
||||||
|
16
renderer/pages/_document.tsx
Normal file
16
renderer/pages/_document.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||||
|
|
||||||
|
export default class MyDocument extends Document {
|
||||||
|
render() {
|
||||||
|
const pageProps = this.props?.__NEXT_DATA__?.props?.pageProps
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Head />
|
||||||
|
<body className={pageProps.isDark ? 'dark' : 'light'}>
|
||||||
|
<Main />
|
||||||
|
<NextScript />
|
||||||
|
</body>
|
||||||
|
</Html>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
const withMT = require('@material-tailwind/react/utils/withMT')
|
const withMT = require('@material-tailwind/react/utils/withMT')
|
||||||
|
|
||||||
module.exports = withMT({
|
module.exports = withMT({
|
||||||
|
darkMode: 'selector',
|
||||||
content: ['./renderer/pages/**/*.{js,ts,jsx,tsx}', './renderer/components/**/*.{js,ts,jsx,tsx}'],
|
content: ['./renderer/pages/**/*.{js,ts,jsx,tsx}', './renderer/components/**/*.{js,ts,jsx,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
Loading…
Reference in New Issue
Block a user