diff options
| author | syuilo <syuilotan@yahoo.co.jp> | 2020-03-22 10:39:12 +0900 |
|---|---|---|
| committer | syuilo <syuilotan@yahoo.co.jp> | 2020-03-22 10:39:12 +0900 |
| commit | e2c0ee66e4f2e2258ac6558ad0f9b6cd9c953390 (patch) | |
| tree | 7c4063187177633c6d828b3a9c18465790322f09 /src/client | |
| parent | :art: (diff) | |
| download | sharkey-e2c0ee66e4f2e2258ac6558ad0f9b6cd9c953390.tar.gz sharkey-e2c0ee66e4f2e2258ac6558ad0f9b6cd9c953390.tar.bz2 sharkey-e2c0ee66e4f2e2258ac6558ad0f9b6cd9c953390.zip | |
Resolve #6170
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/app.vue | 5 | ||||
| -rw-r--r-- | src/client/init.ts | 8 | ||||
| -rw-r--r-- | src/client/pages/preferences/theme.vue | 300 | ||||
| -rw-r--r-- | src/client/scripts/is-device-darkmode.ts | 3 | ||||
| -rw-r--r-- | src/client/store.ts | 5 | ||||
| -rw-r--r-- | src/client/theme.ts | 2 |
6 files changed, 313 insertions, 10 deletions
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 @@ <section class="rfqxtzch _card"> <div class="_title"><fa :icon="faPalette"/> {{ $t('theme') }}</div> <div class="_content"> - <mk-select v-model="theme" :placeholder="$t('theme')"> - <template #label>{{ $t('theme') }}</template> + <div class="darkMode" :class="{ disabled: syncDeviceDarkMode }"> + <div class="toggleWrapper"> + <input type="checkbox" class="dn" id="dn" v-model="darkMode" :disabled="syncDeviceDarkMode"/> + <label for="dn" class="toggle"> + <span class="before">{{ $t('light') }}</span> + <span class="after">{{ $t('dark') }}</span> + <span class="toggle__handler"> + <span class="crater crater--1"></span> + <span class="crater crater--2"></span> + <span class="crater crater--3"></span> + </span> + <span class="star star--1"></span> + <span class="star star--2"></span> + <span class="star star--3"></span> + <span class="star star--4"></span> + <span class="star star--5"></span> + <span class="star star--6"></span> + </label> + </div> + </div> + </div> + <div class="_content"> + <mk-select v-model="lightTheme"> + <template #label>{{ $t('themeForLightMode') }}</template> <optgroup :label="$t('lightThemes')"> <option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option> </optgroup> @@ -11,6 +33,18 @@ <option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option> </optgroup> </mk-select> + <mk-select v-model="darkTheme"> + <template #label>{{ $t('themeForDarkMode') }}</template> + <optgroup :label="$t('darkThemes')"> + <option v-for="x in darkThemes" :value="x.id" :key="x.id">{{ x.name }}</option> + </optgroup> + <optgroup :label="$t('lightThemes')"> + <option v-for="x in lightThemes" :value="x.id" :key="x.id">{{ x.name }}</option> + </optgroup> + </mk-select> + </div> + <div class="_content"> + <mk-switch v-model="syncDeviceDarkMode">{{ $t('syncDeviceDarkMode') }}</mk-switch> </div> <div class="_content"> <mk-button primary v-if="wallpaper == null" @click="setWallpaper">{{ $t('setWallpaper') }}</mk-button> @@ -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({ } }); </script> + +<style lang="scss" scoped> +.rfqxtzch { + > ._content { + > .darkMode { + position: relative; + padding: 32px 0; + + &.disabled { + opacity: 0.7; + + &, * { + cursor: not-allowed !important; + } + } + + .toggleWrapper { + position: absolute; + top: 50%; + left: 50%; + overflow: hidden; + padding: 0 200px; + transform: translate3d(-50%, -50%, 0); + + input { + position: absolute; + left: -99em; + } + } + + .toggle { + cursor: pointer; + display: inline-block; + position: relative; + width: 90px; + height: 50px; + background-color: #83D8FF; + border-radius: 90px - 6; + transition: background-color 200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important; + + > .before, > .after { + position: absolute; + top: 15px; + font-size: 18px; + transition: color 1s ease; + } + + > .before { + left: -60px; + color: var(--accent); + } + + > .after { + right: -58px; + color: var(--fg); + } + } + + .toggle__handler { + display: inline-block; + position: relative; + z-index: 1; + top: 3px; + left: 3px; + width: 50px - 6; + height: 50px - 6; + background-color: #FFCF96; + border-radius: 50px; + box-shadow: 0 2px 6px rgba(0,0,0,.3); + transition: all 400ms cubic-bezier(0.68, -0.55, 0.265, 1.55) !important; + transform: rotate(-45deg); + + .crater { + position: absolute; + background-color: #E8CDA5; + opacity: 0; + transition: opacity 200ms ease-in-out !important; + border-radius: 100%; + } + + .crater--1 { + top: 18px; + left: 10px; + width: 4px; + height: 4px; + } + + .crater--2 { + top: 28px; + left: 22px; + width: 6px; + height: 6px; + } + + .crater--3 { + top: 10px; + left: 25px; + width: 8px; + height: 8px; + } + } + + .star { + position: absolute; + background-color: #ffffff; + transition: all 300ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important; + border-radius: 50%; + } + + .star--1 { + top: 10px; + left: 35px; + z-index: 0; + width: 30px; + height: 3px; + } + + .star--2 { + top: 18px; + left: 28px; + z-index: 1; + width: 30px; + height: 3px; + } + + .star--3 { + top: 27px; + left: 40px; + z-index: 0; + width: 30px; + height: 3px; + } + + .star--4, + .star--5, + .star--6 { + opacity: 0; + transition: all 300ms 0 cubic-bezier(0.445, 0.05, 0.55, 0.95) !important; + } + + .star--4 { + top: 16px; + left: 11px; + z-index: 0; + width: 2px; + height: 2px; + transform: translate3d(3px,0,0); + } + + .star--5 { + top: 32px; + left: 17px; + z-index: 0; + width: 3px; + height: 3px; + transform: translate3d(3px,0,0); + } + + .star--6 { + top: 36px; + left: 28px; + z-index: 0; + width: 2px; + height: 2px; + transform: translate3d(3px,0,0); + } + + input:checked { + + .toggle { + background-color: #749DD6; + + > .before { + color: var(--fg); + } + + > .after { + color: var(--accent); + } + + .toggle__handler { + background-color: #FFE5B5; + transform: translate3d(40px, 0, 0) rotate(0); + + .crater { opacity: 1; } + } + + .star--1 { + width: 2px; + height: 2px; + } + + .star--2 { + width: 4px; + height: 4px; + transform: translate3d(-5px, 0, 0); + } + + .star--3 { + width: 2px; + height: 2px; + transform: translate3d(-7px, 0, 0); + } + + .star--4, + .star--5, + .star--6 { + opacity: 1; + transform: translate3d(0,0,0); + } + + .star--4 { + transition: all 300ms 200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important; + } + + .star--5 { + transition: all 300ms 300ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important; + } + + .star--6 { + transition: all 300ms 400ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important; + } + } + } + } + } +} +</style> 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); } |