diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 0f83ba841..46dea949d 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -285,6 +285,28 @@ common/views/components/media-banner.vue: sensitive: "閲覧注意" click-to-show: "クリックして表示" +common/views/components/theme.vue: + light-theme: "非ダークモード時に使用するテーマ" + dark-theme: "ダークモード時に使用するテーマ" + install-a-theme: "テーマのインストール" + theme-code: "テーマコード" + install: "インストール" + create-a-theme: "テーマの作成" + save-created-theme: "テーマを保存" + primary-color: "プライマリ カラー" + secondary-color: "セカンダリ カラー" + text-color: "文字色" + base-theme: "ベーステーマ" + base-theme-light: "Light" + base-theme-dark: "Dark" + theme-name: "テーマ名" + preview-created-theme: "プレビュー" + invalid-theme: "テーマが正しくありません。" + already-installed: "既にそのテーマはインストールされています。" + saved: "保存しました" + installed-themes: "インストールされたテーマ" + select-theme: "テーマを選択してください" + common/views/components/cw-button.vue: hide: "隠す" show: "もっと見る" @@ -762,6 +784,7 @@ desktop/views/components/settings.vue: 2fa: "二段階認証" other: "その他" license: "ライセンス" + theme: "テーマ" behaviour: "動作" fetch-on-scroll: "スクロールで自動読み込み" @@ -1417,6 +1440,7 @@ mobile/views/pages/settings.vue: notification-position: "通知の表示" notification-position-bottom: "下" notification-position-top: "上" + theme: "テーマ" behavior: "動作" fetch-on-scroll: "スクロールで自動読み込み" note-visibility: "投稿の公開範囲" diff --git a/package.json b/package.json index 347e9d0c2..e19283cf6 100644 --- a/package.json +++ b/package.json @@ -208,6 +208,7 @@ "v-animate-css": "0.0.2", "vue": "2.5.17", "vue-chartjs": "3.4.0", + "vue-color": "2.6.0", "vue-cropperjs": "2.2.2", "vue-js-modal": "1.3.26", "vue-json-tree-view": "2.1.4", diff --git a/src/client/app/app.vue b/src/client/app/app.vue index 9b6af27ec..778e9f29c 100644 --- a/src/client/app/app.vue +++ b/src/client/app/app.vue @@ -14,8 +14,7 @@ export default Vue.extend({ keymap(): any { return { 'h|slash': this.help, - 'd': this.dark, - 'x': this.test + 'd': this.dark }; } }, @@ -26,11 +25,10 @@ export default Vue.extend({ }, dark() { - applyTheme(darkTheme); - }, - - test() { - applyTheme(halloweenTheme); + this.$store.commit('device/set', { + key: 'darkmode', + value: !this.$store.state.device.darkmode + }); } } }); diff --git a/src/client/app/common/views/components/avatar.vue b/src/client/app/common/views/components/avatar.vue index ca09af87d..ac018abcf 100644 --- a/src/client/app/common/views/components/avatar.vue +++ b/src/client/app/common/views/components/avatar.vue @@ -59,7 +59,9 @@ export default Vue.extend({ } }, mounted() { - this.$el.style.color = `rgb(${this.user.avatarColor.slice(0, 3).join(',')})`; + if (this.user.avatarColor) { + this.$el.style.color = `rgb(${this.user.avatarColor.slice(0, 3).join(',')})`; + } }, methods: { onClick(e) { diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts index 4c1c0afa8..0dea38a7a 100644 --- a/src/client/app/common/views/components/index.ts +++ b/src/client/app/common/views/components/index.ts @@ -1,5 +1,6 @@ import Vue from 'vue'; +import theme from './theme.vue'; import instance from './instance.vue'; import cwButton from './cw-button.vue'; import tagCloud from './tag-cloud.vue'; @@ -43,6 +44,7 @@ import uiSelect from './ui/select.vue'; import formButton from './ui/form/button.vue'; import formRadio from './ui/form/radio.vue'; +Vue.component('mk-theme', theme); Vue.component('mk-instance', instance); Vue.component('mk-cw-button', cwButton); Vue.component('mk-tag-cloud', tagCloud); diff --git a/src/client/app/common/views/components/theme.vue b/src/client/app/common/views/components/theme.vue new file mode 100644 index 000000000..27888d1e8 --- /dev/null +++ b/src/client/app/common/views/components/theme.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/src/client/app/common/views/components/ui/button.vue b/src/client/app/common/views/components/ui/button.vue index a165d100a..47644b32b 100644 --- a/src/client/app/common/views/components/ui/button.vue +++ b/src/client/app/common/views/components/ui/button.vue @@ -27,14 +27,6 @@ export default Vue.extend({ return { styl: 'fill' }; - }, - inject: { - isCardChild: { default: false } - }, - created() { - if (this.isCardChild) { - this.styl = 'line'; - } } }); @@ -43,6 +35,7 @@ export default Vue.extend({ .dmtdnykelhudezerjlfpbhgovrgnqqgr display block width 100% + min-height 40px margin 0 padding 0 font-weight normal @@ -52,6 +45,9 @@ export default Vue.extend({ outline none box-shadow none + &:not(.inline) + .dmtdnykelhudezerjlfpbhgovrgnqqgr + margin-top 16px + &.inline display inline-block width auto diff --git a/src/client/app/desktop/views/components/settings.vue b/src/client/app/desktop/views/components/settings.vue index c7d82590e..1cb8d4d4c 100644 --- a/src/client/app/desktop/views/components/settings.vue +++ b/src/client/app/desktop/views/components/settings.vue @@ -19,6 +19,11 @@ +
+

%i18n:@theme%

+ +
+

%i18n:@behaviour%

diff --git a/src/client/app/init.ts b/src/client/app/init.ts index 8d430ad7f..802f7b42e 100644 --- a/src/client/app/init.ts +++ b/src/client/app/init.ts @@ -14,11 +14,11 @@ import App from './app.vue'; import checkForUpdate from './common/scripts/check-for-update'; import MiOS, { API } from './mios'; import { version, codename, lang } from './config'; -import applyTheme from './common/scripts/theme'; -const defaultTheme = require('../theme/light.json'); +import { builtinThemes, applyTheme } from './theme'; +const lightTheme = require('../theme/light.json'); if (localStorage.getItem('theme') == null) { - applyTheme(defaultTheme); + applyTheme(lightTheme); } Vue.use(Vuex); @@ -92,6 +92,35 @@ export default (callback: (launch: (router: VueRouter, api?: (os: MiOS) => API) const launch = (router: VueRouter, api?: (os: MiOS) => API) => { os.apis = api ? api(os) : null; + //#region theme + os.store.watch(s => { + return s.device.darkmode; + }, v => { + const themes = os.store.state.device.themes.concat(builtinThemes); + const dark = themes.find(t => t.meta.id == os.store.state.device.darkTheme); + const light = themes.find(t => t.meta.id == os.store.state.device.lightTheme); + applyTheme(v ? dark : light); + }); + os.store.watch(s => { + return s.device.lightTheme; + }, v => { + const themes = os.store.state.device.themes.concat(builtinThemes); + const theme = themes.find(t => t.meta.id == v); + if (!os.store.state.device.darkmode) { + applyTheme(theme); + } + }); + os.store.watch(s => { + return s.device.darkTheme; + }, v => { + const themes = os.store.state.device.themes.concat(builtinThemes); + const theme = themes.find(t => t.meta.id == v); + if (os.store.state.device.darkmode) { + applyTheme(theme); + } + }); + //#endregion + //#region shadow const shadow = '0 3px 8px rgba(0, 0, 0, 0.2)'; if (os.store.state.settings.useShadow) document.documentElement.style.setProperty('--shadow', shadow); diff --git a/src/client/app/mobile/views/pages/settings.vue b/src/client/app/mobile/views/pages/settings.vue index b83eaf6d3..94fa38cec 100644 --- a/src/client/app/mobile/views/pages/settings.vue +++ b/src/client/app/mobile/views/pages/settings.vue @@ -23,6 +23,13 @@ %i18n:common.use-contrast-reversi-stones%
+
+
%i18n:@theme%
+
+ +
+
+
%i18n:@timeline%
diff --git a/src/client/app/store.ts b/src/client/app/store.ts index fbcc53d7b..545261225 100644 --- a/src/client/app/store.ts +++ b/src/client/app/store.ts @@ -44,6 +44,9 @@ const defaultDeviceSettings = { apiViaStream: true, autoPopout: false, darkmode: false, + darkTheme: 'dark', + lightTheme: 'light', + themes: [], enableSounds: true, soundVolume: 0.5, lang: null, diff --git a/src/client/app/common/scripts/theme.ts b/src/client/app/theme.ts similarity index 74% rename from src/client/app/common/scripts/theme.ts rename to src/client/app/theme.ts index 7a1c6abb7..1147ff300 100644 --- a/src/client/app/common/scripts/theme.ts +++ b/src/client/app/theme.ts @@ -1,22 +1,21 @@ import * as tinycolor from 'tinycolor2'; -const lightTheme = require('../../../theme/light'); -const darkTheme = require('../../../theme/dark'); type Theme = { meta: { id: string; name: string; - inherit: string; + author: string; + base?: string; vars: any; }; } & { [key: string]: string; }; -export default function(theme: Theme) { - if (theme.meta.inherit) { - const inherit = [lightTheme, darkTheme].find(x => x.meta.id == theme.meta.inherit); - theme = Object.assign({}, inherit, theme); +export function applyTheme(theme: Theme, persisted = true) { + if (theme.meta.base) { + const base = [lightTheme, darkTheme].find(x => x.meta.id == theme.meta.base); + theme = Object.assign({}, base, theme); } const props = compile(theme); @@ -26,7 +25,9 @@ export default function(theme: Theme) { document.documentElement.style.setProperty(`--${k}`, v.toString()); }); - localStorage.setItem('theme', JSON.stringify(props)); + if (persisted) { + localStorage.setItem('theme', JSON.stringify(props)); + } } function compile(theme: Theme): { [key: string]: string } { @@ -87,3 +88,13 @@ function compile(theme: Theme): { [key: string]: string } { function genValue(c: tinycolor.Instance): string { return c.toRgbString(); } + +export const lightTheme = require('../theme/light.json'); +export const darkTheme = require('../theme/dark.json'); +export const halloweenTheme = require('../theme/halloween.json'); + +export const builtinThemes = [ + lightTheme, + darkTheme, + halloweenTheme +]; diff --git a/src/client/theme/dark.json b/src/client/theme/dark.json index 015225dda..74447b8f2 100644 --- a/src/client/theme/dark.json +++ b/src/client/theme/dark.json @@ -1,6 +1,6 @@ { "meta": { - "id": "9978f7f9-5616-44fd-a704-cc5985efdd63", + "id": "dark", "name": "Dark", "author": "syuilo", "vars": { diff --git a/src/client/theme/halloween.json b/src/client/theme/halloween.json index 6e92db95f..fb34db57a 100644 --- a/src/client/theme/halloween.json +++ b/src/client/theme/halloween.json @@ -3,10 +3,9 @@ "id": "42e4f09b-67d5-498c-af7d-29faa54745b0", "name": "Halloween", "author": "syuilo", - "inherit": "9978f7f9-5616-44fd-a704-cc5985efdd63", + "base": "dark", "vars": { "primary": "#d67036", - "primaryForeground": "#fff", "secondary": "#1f1d30", "text": "#b1bee3" } diff --git a/src/client/theme/light.json b/src/client/theme/light.json index 3d131f066..1b6604e64 100644 --- a/src/client/theme/light.json +++ b/src/client/theme/light.json @@ -1,6 +1,6 @@ { "meta": { - "id": "406cfea3-a4e7-486c-9057-30ede1353c3f", + "id": "light", "name": "Light", "author": "syuilo", "vars": {