This commit is contained in:
syuilo 2018-09-29 00:01:11 +09:00
parent fc2848664f
commit 65f3fc4af2
15 changed files with 287 additions and 31 deletions

View file

@ -285,6 +285,28 @@ common/views/components/media-banner.vue:
sensitive: "閲覧注意" sensitive: "閲覧注意"
click-to-show: "クリックして表示" 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: common/views/components/cw-button.vue:
hide: "隠す" hide: "隠す"
show: "もっと見る" show: "もっと見る"
@ -762,6 +784,7 @@ desktop/views/components/settings.vue:
2fa: "二段階認証" 2fa: "二段階認証"
other: "その他" other: "その他"
license: "ライセンス" license: "ライセンス"
theme: "テーマ"
behaviour: "動作" behaviour: "動作"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
@ -1417,6 +1440,7 @@ mobile/views/pages/settings.vue:
notification-position: "通知の表示" notification-position: "通知の表示"
notification-position-bottom: "下" notification-position-bottom: "下"
notification-position-top: "上" notification-position-top: "上"
theme: "テーマ"
behavior: "動作" behavior: "動作"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "スクロールで自動読み込み"
note-visibility: "投稿の公開範囲" note-visibility: "投稿の公開範囲"

View file

@ -208,6 +208,7 @@
"v-animate-css": "0.0.2", "v-animate-css": "0.0.2",
"vue": "2.5.17", "vue": "2.5.17",
"vue-chartjs": "3.4.0", "vue-chartjs": "3.4.0",
"vue-color": "2.6.0",
"vue-cropperjs": "2.2.2", "vue-cropperjs": "2.2.2",
"vue-js-modal": "1.3.26", "vue-js-modal": "1.3.26",
"vue-json-tree-view": "2.1.4", "vue-json-tree-view": "2.1.4",

View file

@ -14,8 +14,7 @@ export default Vue.extend({
keymap(): any { keymap(): any {
return { return {
'h|slash': this.help, 'h|slash': this.help,
'd': this.dark, 'd': this.dark
'x': this.test
}; };
} }
}, },
@ -26,11 +25,10 @@ export default Vue.extend({
}, },
dark() { dark() {
applyTheme(darkTheme); this.$store.commit('device/set', {
}, key: 'darkmode',
value: !this.$store.state.device.darkmode
test() { });
applyTheme(halloweenTheme);
} }
} }
}); });

View file

@ -59,7 +59,9 @@ export default Vue.extend({
} }
}, },
mounted() { 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: { methods: {
onClick(e) { onClick(e) {

View file

@ -1,5 +1,6 @@
import Vue from 'vue'; import Vue from 'vue';
import theme from './theme.vue';
import instance from './instance.vue'; import instance from './instance.vue';
import cwButton from './cw-button.vue'; import cwButton from './cw-button.vue';
import tagCloud from './tag-cloud.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 formButton from './ui/form/button.vue';
import formRadio from './ui/form/radio.vue'; import formRadio from './ui/form/radio.vue';
Vue.component('mk-theme', theme);
Vue.component('mk-instance', instance); Vue.component('mk-instance', instance);
Vue.component('mk-cw-button', cwButton); Vue.component('mk-cw-button', cwButton);
Vue.component('mk-tag-cloud', tagCloud); Vue.component('mk-tag-cloud', tagCloud);

View file

@ -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>

View file

@ -27,14 +27,6 @@ export default Vue.extend({
return { return {
styl: 'fill' styl: 'fill'
}; };
},
inject: {
isCardChild: { default: false }
},
created() {
if (this.isCardChild) {
this.styl = 'line';
}
} }
}); });
</script> </script>
@ -43,6 +35,7 @@ export default Vue.extend({
.dmtdnykelhudezerjlfpbhgovrgnqqgr .dmtdnykelhudezerjlfpbhgovrgnqqgr
display block display block
width 100% width 100%
min-height 40px
margin 0 margin 0
padding 0 padding 0
font-weight normal font-weight normal
@ -52,6 +45,9 @@ export default Vue.extend({
outline none outline none
box-shadow none box-shadow none
&:not(.inline) + .dmtdnykelhudezerjlfpbhgovrgnqqgr
margin-top 16px
&.inline &.inline
display inline-block display inline-block
width auto width auto

View file

@ -19,6 +19,11 @@
<x-profile/> <x-profile/>
</section> </section>
<section class="web" v-show="page == 'web'">
<h1>%i18n:@theme%</h1>
<mk-theme/>
</section>
<section class="web" v-show="page == 'web'"> <section class="web" v-show="page == 'web'">
<h1>%i18n:@behaviour%</h1> <h1>%i18n:@behaviour%</h1>
<ui-switch v-model="fetchOnScroll"> <ui-switch v-model="fetchOnScroll">

View file

@ -14,11 +14,11 @@ import App from './app.vue';
import checkForUpdate from './common/scripts/check-for-update'; import checkForUpdate from './common/scripts/check-for-update';
import MiOS, { API } from './mios'; import MiOS, { API } from './mios';
import { version, codename, lang } from './config'; import { version, codename, lang } from './config';
import applyTheme from './common/scripts/theme'; import { builtinThemes, applyTheme } from './theme';
const defaultTheme = require('../theme/light.json'); const lightTheme = require('../theme/light.json');
if (localStorage.getItem('theme') == null) { if (localStorage.getItem('theme') == null) {
applyTheme(defaultTheme); applyTheme(lightTheme);
} }
Vue.use(Vuex); 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) => { const launch = (router: VueRouter, api?: (os: MiOS) => API) => {
os.apis = api ? api(os) : null; 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 //#region shadow
const shadow = '0 3px 8px rgba(0, 0, 0, 0.2)'; const shadow = '0 3px 8px rgba(0, 0, 0, 0.2)';
if (os.store.state.settings.useShadow) document.documentElement.style.setProperty('--shadow', shadow); if (os.store.state.settings.useShadow) document.documentElement.style.setProperty('--shadow', shadow);

View file

@ -23,6 +23,13 @@
<ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch> <ui-switch v-model="games_reversi_useContrastStones">%i18n:common.use-contrast-reversi-stones%</ui-switch>
</section> </section>
<section>
<header>%i18n:@theme%</header>
<div>
<mk-theme/>
</div>
</section>
<section> <section>
<header>%i18n:@timeline%</header> <header>%i18n:@timeline%</header>
<div> <div>

View file

@ -44,6 +44,9 @@ const defaultDeviceSettings = {
apiViaStream: true, apiViaStream: true,
autoPopout: false, autoPopout: false,
darkmode: false, darkmode: false,
darkTheme: 'dark',
lightTheme: 'light',
themes: [],
enableSounds: true, enableSounds: true,
soundVolume: 0.5, soundVolume: 0.5,
lang: null, lang: null,

View file

@ -1,22 +1,21 @@
import * as tinycolor from 'tinycolor2'; import * as tinycolor from 'tinycolor2';
const lightTheme = require('../../../theme/light');
const darkTheme = require('../../../theme/dark');
type Theme = { type Theme = {
meta: { meta: {
id: string; id: string;
name: string; name: string;
inherit: string; author: string;
base?: string;
vars: any; vars: any;
}; };
} & { } & {
[key: string]: string; [key: string]: string;
}; };
export default function(theme: Theme) { export function applyTheme(theme: Theme, persisted = true) {
if (theme.meta.inherit) { if (theme.meta.base) {
const inherit = [lightTheme, darkTheme].find(x => x.meta.id == theme.meta.inherit); const base = [lightTheme, darkTheme].find(x => x.meta.id == theme.meta.base);
theme = Object.assign({}, inherit, theme); theme = Object.assign({}, base, theme);
} }
const props = compile(theme); const props = compile(theme);
@ -26,7 +25,9 @@ export default function(theme: Theme) {
document.documentElement.style.setProperty(`--${k}`, v.toString()); 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 } { function compile(theme: Theme): { [key: string]: string } {
@ -87,3 +88,13 @@ function compile(theme: Theme): { [key: string]: string } {
function genValue(c: tinycolor.Instance): string { function genValue(c: tinycolor.Instance): string {
return c.toRgbString(); 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
];

View file

@ -1,6 +1,6 @@
{ {
"meta": { "meta": {
"id": "9978f7f9-5616-44fd-a704-cc5985efdd63", "id": "dark",
"name": "Dark", "name": "Dark",
"author": "syuilo", "author": "syuilo",
"vars": { "vars": {

View file

@ -3,10 +3,9 @@
"id": "42e4f09b-67d5-498c-af7d-29faa54745b0", "id": "42e4f09b-67d5-498c-af7d-29faa54745b0",
"name": "Halloween", "name": "Halloween",
"author": "syuilo", "author": "syuilo",
"inherit": "9978f7f9-5616-44fd-a704-cc5985efdd63", "base": "dark",
"vars": { "vars": {
"primary": "#d67036", "primary": "#d67036",
"primaryForeground": "#fff",
"secondary": "#1f1d30", "secondary": "#1f1d30",
"text": "#b1bee3" "text": "#b1bee3"
} }

View file

@ -1,6 +1,6 @@
{ {
"meta": { "meta": {
"id": "406cfea3-a4e7-486c-9057-30ede1353c3f", "id": "light",
"name": "Light", "name": "Light",
"author": "syuilo", "author": "syuilo",
"vars": { "vars": {