summaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2020-03-22 10:39:12 +0900
committersyuilo <syuilotan@yahoo.co.jp>2020-03-22 10:39:12 +0900
commite2c0ee66e4f2e2258ac6558ad0f9b6cd9c953390 (patch)
tree7c4063187177633c6d828b3a9c18465790322f09 /src/client
parent:art: (diff)
downloadsharkey-e2c0ee66e4f2e2258ac6558ad0f9b6cd9c953390.tar.gz
sharkey-e2c0ee66e4f2e2258ac6558ad0f9b6cd9c953390.tar.bz2
sharkey-e2c0ee66e4f2e2258ac6558ad0f9b6cd9c953390.zip
Resolve #6170
Diffstat (limited to 'src/client')
-rw-r--r--src/client/app.vue5
-rw-r--r--src/client/init.ts8
-rw-r--r--src/client/pages/preferences/theme.vue300
-rw-r--r--src/client/scripts/is-device-darkmode.ts3
-rw-r--r--src/client/store.ts5
-rw-r--r--src/client/theme.ts2
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);
}