diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 1dfe9d7911..0339a2f6ca 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -225,8 +225,13 @@ yearsOld: "{age}歳" registeredDate: "登録日" location: "場所" theme: "テーマ" +themeForLightMode: "ライトモードで使うテーマ" +themeForDarkMode: "ダークモードで使うテーマ" +light: "ライト" +dark: "ダーク" lightThemes: "明るいテーマ" darkThemes: "暗いテーマ" +syncDeviceDarkMode: "デバイスのダークモードと同期する" drive: "ドライブ" selectFile: "ファイルを選択" selectFiles: "ファイルを選択" diff --git a/src/client/app.vue b/src/client/app.vue index 4e5dfbd18a..1e9fd6c0a9 100644 --- a/src/client/app.vue +++ b/src/client/app.vue @@ -164,6 +164,7 @@ import { v4 as uuid } from 'uuid'; import i18n from './i18n'; import { host, instanceName } from './config'; import { search } from './scripts/search'; +import { isDeviceDarkmode } from './scripts/is-device-darkmode'; import MkToast from './components/toast.vue'; const DESKTOP_THRESHOLD = 1100; @@ -224,6 +225,10 @@ export default Vue.extend({ }, created() { + if (this.$store.state.device.syncDeviceDarkMode) { + this.$store.commit('device/set', { key: 'darkMode', value: isDeviceDarkmode() }); + } + if (this.$store.getters.isSignedIn) { this.connection = this.$root.stream.useSharedConnection('main'); this.connection.on('notification', this.onNotification); diff --git a/src/client/init.ts b/src/client/init.ts index 29eabfee4e..f1790ac4d9 100644 --- a/src/client/init.ts +++ b/src/client/init.ts @@ -18,7 +18,7 @@ import PostFormDialog from './components/post-form-dialog.vue'; import Dialog from './components/dialog.vue'; import Menu from './components/menu.vue'; import { router } from './router'; -import { applyTheme, lightTheme } from './theme'; +import { applyTheme, lightTheme, builtinThemes } from './theme'; Vue.use(Vuex); Vue.use(VueHotkey); @@ -163,6 +163,12 @@ os.init(async () => { isMobile: isMobile }; }, + watch: { + '$store.state.device.darkMode'() { + const themes = builtinThemes.concat(this.$store.state.device.themes); + applyTheme(themes.find(x => x.id === (this.$store.state.device.darkMode ? this.$store.state.device.darkTheme : this.$store.state.device.lightTheme))); + } + }, methods: { api: os.api, signout: os.signout, diff --git a/src/client/pages/preferences/theme.vue b/src/client/pages/preferences/theme.vue index d406cadb01..5abe65f58d 100644 --- a/src/client/pages/preferences/theme.vue +++ b/src/client/pages/preferences/theme.vue @@ -2,8 +2,30 @@
{{ $t('theme') }}
- - +
+
+ + +
+
+
+
+ + @@ -11,6 +33,18 @@ + + + + + + + + + +
+
+ {{ $t('syncDeviceDarkMode') }}
{{ $t('setWallpaper') }} @@ -25,6 +59,7 @@ import { faPalette } from '@fortawesome/free-solid-svg-icons'; import MkInput from '../../components/ui/input.vue'; import MkButton from '../../components/ui/button.vue'; import MkSelect from '../../components/ui/select.vue'; +import MkSwitch from '../../components/ui/switch.vue'; import i18n from '../../i18n'; import { Theme, builtinThemes, applyTheme } from '../../theme'; import { selectFile } from '../../scripts/select-file'; @@ -36,6 +71,7 @@ export default Vue.extend({ MkInput, MkButton, MkSelect, + MkSwitch, }, data() { @@ -62,15 +98,38 @@ export default Vue.extend({ return this.themes.filter(t => t.base == 'light' || t.kind == 'light'); }, - theme: { - get() { return this.$store.state.device.theme; }, - set(value) { this.$store.commit('device/set', { key: 'theme', value }); } + darkTheme: { + get() { return this.$store.state.device.darkTheme; }, + set(value) { this.$store.commit('device/set', { key: 'darkTheme', value }); } + }, + + lightTheme: { + get() { return this.$store.state.device.lightTheme; }, + set(value) { this.$store.commit('device/set', { key: 'lightTheme', value }); } + }, + + darkMode: { + get() { return this.$store.state.device.darkMode; }, + set(value) { this.$store.commit('device/set', { key: 'darkMode', value }); } + }, + + syncDeviceDarkMode: { + get() { return this.$store.state.device.syncDeviceDarkMode; }, + set(value) { this.$store.commit('device/set', { key: 'syncDeviceDarkMode', value }); } }, }, watch: { - theme() { - applyTheme(this.themes.find(x => x.id === this.theme)); + darkTheme() { + if (this.$store.state.device.darkMode) { + applyTheme(this.themes.find(x => x.id === this.darkTheme)); + } + }, + + lightTheme() { + if (!this.$store.state.device.darkMode) { + applyTheme(this.themes.find(x => x.id === this.lightTheme)); + } }, wallpaper() { @@ -92,3 +151,230 @@ export default Vue.extend({ } }); + + diff --git a/src/client/scripts/is-device-darkmode.ts b/src/client/scripts/is-device-darkmode.ts new file mode 100644 index 0000000000..21c26823c7 --- /dev/null +++ b/src/client/scripts/is-device-darkmode.ts @@ -0,0 +1,3 @@ +export function isDeviceDarkmode() { + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; +} diff --git a/src/client/store.ts b/src/client/store.ts index 7a9d09d2a5..35b932d624 100644 --- a/src/client/store.ts +++ b/src/client/store.ts @@ -35,7 +35,10 @@ const defaultDeviceSettings = { accounts: [], recentEmojis: [], themes: [], - theme: '4eea646f-7afa-4645-83e9-83af0333cd37', + darkTheme: '8c539dc1-0fab-4d47-9194-39c508e9bfe1', + lightTheme: '4eea646f-7afa-4645-83e9-83af0333cd37', + darkMode: false, + syncDeviceDarkMode: true, animation: true, animatedMfm: true, imageNewTab: false, diff --git a/src/client/theme.ts b/src/client/theme.ts index a34f2b5c34..2a6adbffcc 100644 --- a/src/client/theme.ts +++ b/src/client/theme.ts @@ -44,7 +44,7 @@ export function applyTheme(theme: Theme, persist = true) { const _theme = JSON.parse(JSON.stringify(theme)); if (_theme.base) { - const base = [lightTheme, darkTheme].find(x => x.id == _theme.base); + const base = [lightTheme, darkTheme].find(x => x.id === _theme.base); _theme.props = Object.assign({}, base.props, _theme.props); }