diff options
| author | syuilo <syuilotan@yahoo.co.jp> | 2018-09-29 00:01:11 +0900 |
|---|---|---|
| committer | syuilo <syuilotan@yahoo.co.jp> | 2018-09-29 00:01:11 +0900 |
| commit | 6a82e94c5489d4879cbbf86091cd15c7d144f284 (patch) | |
| tree | 148f1b4bba454a71075d4b7b21d983b58215e8af /src/client | |
| parent | wip (diff) | |
| download | misskey-6a82e94c5489d4879cbbf86091cd15c7d144f284.tar.gz misskey-6a82e94c5489d4879cbbf86091cd15c7d144f284.tar.bz2 misskey-6a82e94c5489d4879cbbf86091cd15c7d144f284.zip | |
wip
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/app/app.vue | 12 | ||||
| -rw-r--r-- | src/client/app/common/views/components/avatar.vue | 4 | ||||
| -rw-r--r-- | src/client/app/common/views/components/index.ts | 2 | ||||
| -rw-r--r-- | src/client/app/common/views/components/theme.vue | 179 | ||||
| -rw-r--r-- | src/client/app/common/views/components/ui/button.vue | 12 | ||||
| -rw-r--r-- | src/client/app/desktop/views/components/settings.vue | 5 | ||||
| -rw-r--r-- | src/client/app/init.ts | 35 | ||||
| -rw-r--r-- | src/client/app/mobile/views/pages/settings.vue | 7 | ||||
| -rw-r--r-- | src/client/app/store.ts | 3 | ||||
| -rw-r--r-- | src/client/app/theme.ts (renamed from src/client/app/common/scripts/theme.ts) | 27 | ||||
| -rw-r--r-- | src/client/theme/dark.json | 2 | ||||
| -rw-r--r-- | src/client/theme/halloween.json | 3 | ||||
| -rw-r--r-- | src/client/theme/light.json | 2 |
13 files changed, 262 insertions, 31 deletions
diff --git a/src/client/app/app.vue b/src/client/app/app.vue index 9b6af27ece..778e9f29cf 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 ca09af87de..ac018abcfc 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 4c1c0afa80..0dea38a7a1 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 0000000000..27888d1e85 --- /dev/null +++ b/src/client/app/common/views/components/theme.vue @@ -0,0 +1,179 @@ +<template> +<div class="nicnklzforebnpfgasiypmpdaaglujqm"> + <label> + <span>%i18n:@light-theme%</span> + <ui-select v-model="light" placeholder="%i18n:@light-theme%"> + <option v-for="x in themes" :value="x.meta.id" :key="x.meta.id">{{ x.meta.name }}</option> + </ui-select> + </label> + + <label> + <span>%i18n:@dark-theme%</span> + <ui-select v-model="dark" placeholder="%i18n:@dark-theme%"> + <option v-for="x in themes" :value="x.meta.id" :key="x.meta.id">{{ x.meta.name }}</option> + </ui-select> + </label> + + <details class="creator"> + <summary>%i18n:@create-a-theme%</summary> + <div> + <span>%i18n:@base-theme%:</span> + <ui-radio v-model="myThemeBase" value="light">%i18n:@base-theme-light%</ui-radio> + <ui-radio v-model="myThemeBase" value="dark">%i18n:@base-theme-dark%</ui-radio> + </div> + <div> + <ui-input v-model="myThemeName"> + <span>%i18n:@theme-name%</span> + </ui-input> + </div> + <div> + <div style="padding-bottom:8px;">%i18n:@primary-color%:</div> + <color-picker v-model="myThemePrimary"/> + </div> + <div> + <div style="padding-bottom:8px;">%i18n:@secondary-color%:</div> + <color-picker v-model="myThemeSecondary"/> + </div> + <div> + <div style="padding-bottom:8px;">%i18n:@text-color%:</div> + <color-picker v-model="myThemeText"/> + </div> + <ui-button @click="preview()">%i18n:@preview-created-theme%</ui-button> + <ui-button primary @click="gen()">%i18n:@save-created-theme%</ui-button> + </details> + + <details> + <summary>%i18n:@install-a-theme%</summary> + <ui-textarea v-model="installThemeCode"> + <span>%i18n:@theme-code%</span> + </ui-textarea> + <ui-button @click="install()">%i18n:@install%</ui-button> + </details> + + <details> + <summary>%i18n:@installed-themes%</summary> + <ui-select v-model="selectedInstalledTheme" placeholder="%i18n:@select-theme%"> + <option v-for="x in installedThemes" :value="x.meta.id" :key="x.meta.id">{{ x.meta.name }}</option> + </ui-select> + <ui-textarea readonly :value="selectedInstalledThemeCode"> + <span>%i18n:@theme-code%</span> + </ui-textarea> + </details> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import { apiUrl, docsUrl } from '../../../config'; +import { lightTheme, darkTheme, builtinThemes, applyTheme } from '../../../theme'; +import { Chrome } from 'vue-color'; +import * as uuid from 'uuid'; +import * as tinycolor from 'tinycolor2'; + +export default Vue.extend({ + components: { + ColorPicker: Chrome + }, + + data() { + return { + installThemeCode: null, + selectedInstalledTheme: null, + myThemeBase: 'light', + myThemeName: '', + myThemePrimary: lightTheme.meta.vars.primary, + myThemeSecondary: lightTheme.meta.vars.secondary, + myThemeText: lightTheme.meta.vars.text + }; + }, + + computed: { + themes(): any { + return this.$store.state.device.themes.concat(builtinThemes); + }, + + installedThemes(): any { + return this.$store.state.device.themes; + }, + + light: { + get() { return this.$store.state.device.lightTheme; }, + set(value) { this.$store.commit('device/set', { key: 'lightTheme', value }); } + }, + + dark: { + get() { return this.$store.state.device.darkTheme; }, + set(value) { this.$store.commit('device/set', { key: 'darkTheme', value }); } + }, + + selectedInstalledThemeCode() { + if (this.selectedInstalledTheme == null) return null; + return JSON.stringify(this.installedThemes.find(x => x.meta.id == this.selectedInstalledTheme)); + }, + + myTheme(): any { + return { + meta: { + name: this.myThemeName, + author: this.$store.state.i.name, + base: this.myThemeBase, + vars: { + primary: tinycolor(typeof this.myThemePrimary == 'string' ? this.myThemePrimary : this.myThemePrimary.rgba).toRgbString(), + secondary: tinycolor(typeof this.myThemeSecondary == 'string' ? this.myThemeSecondary : this.myThemeSecondary.rgba).toRgbString(), + text: tinycolor(typeof this.myThemeText == 'string' ? this.myThemeText : this.myThemeText.rgba).toRgbString() + } + } + }; + } + }, + + watch: { + myThemeBase(v) { + const theme = v == 'light' ? lightTheme : darkTheme; + this.myThemePrimary = theme.meta.vars.primary; + this.myThemeSecondary = theme.meta.vars.secondary; + this.myThemeText = theme.meta.vars.text; + } + }, + + methods: { + install() { + const theme = JSON.parse(this.installThemeCode); + if (theme.meta == null || theme.meta.id == null) { + alert('%i18n:@invalid-theme%'); + return; + } + if (this.$store.state.device.themes.some(t => t.meta.id == theme.meta.id)) { + alert('%i18n:@already-installed%'); + return; + } + const themes = this.$store.state.device.themes.concat(theme); + this.$store.commit('device/set', { + key: 'themes', value: themes + }); + }, + + preview() { + applyTheme(this.myTheme, false); + }, + + gen() { + const theme = this.myTheme; + theme.meta.id = uuid(); + const themes = this.$store.state.device.themes.concat(theme); + this.$store.commit('device/set', { + key: 'themes', value: themes + }); + alert('%i18n:@saved%'); + } + } +}); +</script> + +<style lang="stylus" scoped> +.nicnklzforebnpfgasiypmpdaaglujqm + > .creator + > div + padding 16px 0 + border-bottom solid 1px var(--faceDivider) +</style> diff --git a/src/client/app/common/views/components/ui/button.vue b/src/client/app/common/views/components/ui/button.vue index a165d100a4..47644b32b5 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'; - } } }); </script> @@ -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 c7d82590ea..1cb8d4d4c8 100644 --- a/src/client/app/desktop/views/components/settings.vue +++ b/src/client/app/desktop/views/components/settings.vue @@ -20,6 +20,11 @@ </section> <section class="web" v-show="page == 'web'"> + <h1>%i18n:@theme%</h1> + <mk-theme/> + </section> + + <section class="web" v-show="page == 'web'"> <h1>%i18n:@behaviour%</h1> <ui-switch v-model="fetchOnScroll"> %i18n:@fetch-on-scroll% diff --git a/src/client/app/init.ts b/src/client/app/init.ts index 8d430ad7ff..802f7b42eb 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 b83eaf6d33..94fa38cec9 100644 --- a/src/client/app/mobile/views/pages/settings.vue +++ b/src/client/app/mobile/views/pages/settings.vue @@ -24,6 +24,13 @@ </section> <section> + <header>%i18n:@theme%</header> + <div> + <mk-theme/> + </div> + </section> + + <section> <header>%i18n:@timeline%</header> <div> <ui-switch v-model="showReplyTarget">%i18n:@show-reply-target%</ui-switch> diff --git a/src/client/app/store.ts b/src/client/app/store.ts index fbcc53d7be..545261225a 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 index 7a1c6abb76..1147ff300d 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 015225ddab..74447b8f2f 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 6e92db95ff..fb34db57a8 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 3d131f066a..1b6604e642 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": { |