From 80bebea9e624d469690498f1204e704f4ad862ae Mon Sep 17 00:00:00 2001 From: Xeltica <7106976+Xeltica@users.noreply.github.com> Date: Sat, 11 Jul 2020 12:12:35 +0900 Subject: [PATCH] =?UTF-8?q?=E3=83=86=E3=83=BC=E3=83=9E=E3=82=A8=E3=83=87?= =?UTF-8?q?=E3=82=A3=E3=82=BF=E3=83=BC=E3=81=AE=E5=AE=9F=E8=A3=85=20(#6482?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * テーマ機能の実装 * resolve #6478 * 定数を削除できるように * 変更を破棄するか確認ダイアログを表示するように * fix code * Update theme.ts * :v: * fix path * wip * wip * wip Co-authored-by: syuilo --- locales/ja-JP.yml | 68 +++++ src/client/app.vue | 4 + src/client/pages/preferences/theme.vue | 6 +- src/client/pages/room/room.vue | 2 +- src/client/pages/theme-editor.vue | 343 +++++++++++++++++++++++++ src/client/router.ts | 1 + src/client/scripts/theme-editor.ts | 74 ++++++ src/client/scripts/theme.ts | 2 + src/client/themes/_dark.json5 | 2 - src/client/themes/_light.json5 | 2 - src/client/themes/halloween.json5 | 1 - 11 files changed, 495 insertions(+), 10 deletions(-) create mode 100644 src/client/pages/theme-editor.vue create mode 100644 src/client/scripts/theme-editor.ts diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index ab4b10549c..8ae0628471 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -519,6 +519,10 @@ fixedWidgetsPosition: "ウィジェットの位置を固定する" enablePlayer: "プレイヤーを開く" disablePlayer: "プレイヤーを閉じる" expandTweet: "ツイートを展開する" +themeEditor: "テーマエディター" +description: "説明" +author: "作者" +leaveConfirm: "未保存の変更があります。破棄しますか?" deck: "デッキ" undeck: "デッキ解除" @@ -530,6 +534,70 @@ _theme: installed: "{name}をインストールしました" alreadyInstalled: "そのテーマは既にインストールされています" invalid: "テーマの形式が間違っています" + make: "テーマを作る" + base: "ベース" + addConstant: "定数を追加" + constant: "定数" + defaultValue: "デフォルト値" + color: "色" + refProp: "プロパティを参照" + refConst: "定数を参照" + key: "キー" + func: "関数" + funcKind: "関数の種類" + argument: "引数" + basedProp: "元にするプロパティの名前" + alpha: "不透明度" + darken: "暗さ" + lighten: "明るさ" + inputConstantName: "定数名を入力してください" + importInfo: "ここにテーマコードを貼り付けて、エディターにインポートできます" + deleteConstantConfirm: "定数 {const} を削除しても良いですか?" + + keys: + accent: "アクセント" + bg: "背景" + fg: "文字" + focus: "フォーカス" + indicator: "インジケーター" + panel: "パネル" + shadow: "影" + header: "ヘッダー" + navBg: "サイドバーの背景" + navFg: "サイドバーの文字" + navHoverFg: "サイドバー文字(ホバー)" + navActive: "サイドバー文字(アクティブ)" + navIndicator: "サイドバーのインジケーター" + link: "リンク" + hashtag: "ハッシュタグ" + mention: "メンション" + mentionMe: "あなた宛てメンション" + renote: "Renote" + modalBg: "モーダルの背景" + divider: "分割線" + scrollbarHandle: "スクロールバーの取っ手" + scrollbarHandleHover: "スクロールバーの取っ手(ホバー)" + dateLabelFg: "日付ラベルの文字" + infoBg: "情報の背景" + infoFg: "情報の文字" + infoWarnBg: "警告の背景" + infoWarnFg: "警告の文字" + cwBg: "CW ボタンの背景" + cwFg: "CW ボタンの文字" + cwHoverBg: "CW ボタンの背景 (ホバー)" + toastBg: "通知トーストの背景" + toastFg: "通知トーストの文字" + buttonBg: "ボタンの背景" + buttonHoverBg: "ボタンの背景 (ホバー)" + inputBorder: "入力ボックスの縁取り" + listItemHoverBg: "リスト項目の背景 (ホバー)" + driveFolderBg: "ドライブフォルダーの背景" + wallpaperOverlay: "壁紙のオーバーレイ" + badge: "バッジ" + messageBg: "チャットの背景" + accentDarken: "アクセント (暗め)" + accentLighten: "アクセント (明るめ)" + fgHighlighted: "強調された文字" _sfx: note: "ノート" diff --git a/src/client/app.vue b/src/client/app.vue index 4f39183564..a751d5db44 100644 --- a/src/client/app.vue +++ b/src/client/app.vue @@ -131,6 +131,10 @@ export default Vue.extend({ computed: { keymap(): any { return { + 'd': () => { + if (this.$store.state.device.syncDeviceDarkMode) return; + this.$store.commit('device/set', { key: 'darkMode', value: !this.$store.state.device.darkMode }); + }, 'p': this.post, 'n': this.post, 's': this.search, diff --git a/src/client/pages/preferences/theme.vue b/src/client/pages/preferences/theme.vue index 246787fa58..173ccd7091 100644 --- a/src/client/pages/preferences/theme.vue +++ b/src/client/pages/preferences/theme.vue @@ -22,6 +22,7 @@ + {{ $t('syncDeviceDarkMode') }}
@@ -42,10 +43,7 @@ - {{ $t('_theme.explore') }} -
-
- {{ $t('syncDeviceDarkMode') }} + {{ $t('_theme.explore') }}{{ $t('_theme.make') }}
{{ $t('setWallpaper') }} diff --git a/src/client/pages/room/room.vue b/src/client/pages/room/room.vue index cf6850526f..05b93c04e8 100644 --- a/src/client/pages/room/room.vue +++ b/src/client/pages/room/room.vue @@ -143,7 +143,7 @@ export default Vue.extend({ if (this.changed) { this.$root.dialog({ type: 'warning', - text: this.$t('leave-confirm'), + text: this.$t('leaveConfirm'), showCancelButton: true }).then(({ canceled }) => { if (canceled) { diff --git a/src/client/pages/theme-editor.vue b/src/client/pages/theme-editor.vue new file mode 100644 index 0000000000..3a3fbfa2d7 --- /dev/null +++ b/src/client/pages/theme-editor.vue @@ -0,0 +1,343 @@ + + + + + diff --git a/src/client/router.ts b/src/client/router.ts index cf98c57bd7..a741aeb955 100644 --- a/src/client/router.ts +++ b/src/client/router.ts @@ -24,6 +24,7 @@ export const router = new VueRouter({ { path: '/about-misskey', component: page('about-misskey') }, { path: '/featured', component: page('featured') }, { path: '/docs', component: page('docs') }, + { path: '/theme-editor', component: page('theme-editor') }, { path: '/docs/:doc', component: page('doc'), props: true }, { path: '/explore', component: page('explore') }, { path: '/explore/tags/:tag', props: true, component: page('explore') }, diff --git a/src/client/scripts/theme-editor.ts b/src/client/scripts/theme-editor.ts new file mode 100644 index 0000000000..e0c3bc25bc --- /dev/null +++ b/src/client/scripts/theme-editor.ts @@ -0,0 +1,74 @@ +import { v4 as uuid} from 'uuid'; + +import { themeProps, Theme } from './theme'; + +export type Default = null; +export type Color = string; +export type FuncName = 'alpha' | 'darken' | 'lighten'; +export type Func = { type: 'func', name: FuncName, arg: number, value: string }; +export type RefProp = { type: 'refProp', key: string }; +export type RefConst = { type: 'refConst', key: string }; + +export type ThemeValue = Color | Func | RefProp | RefConst | Default; + +export type ThemeViewModel = [ string, ThemeValue ][]; + +export const fromThemeString = (str?: string) : ThemeValue => { + if (!str) return null; + if (str.startsWith(':')) { + const parts = str.slice(1).split('<'); + const name = parts[0] as FuncName; + const arg = parseFloat(parts[1]); + const value = parts[2].startsWith('@') ? parts[2].slice(1) : ''; + return { type: 'func', name, arg, value }; + } else if (str.startsWith('@')) { + return { + type: 'refProp', + key: str.slice(1), + }; + } else if (str.startsWith('$')) { + return { + type: 'refConst', + key: str.slice(1), + }; + } else { + return str; + } +}; + +export const toThemeString = (value: Color | Func | RefProp | RefConst) => { + if (typeof value === 'string') return value; + switch (value.type) { + case 'func': return `:${value.name}<${value.arg}<@${value.value}`; + case 'refProp': return `@${value.key}`; + case 'refConst': return `$${value.key}`; + } +}; + +export const convertToMisskeyTheme = (vm: ThemeViewModel, name: string, desc: string, author: string, base: 'dark' | 'light'): Theme => { + const props = { } as { [key: string]: string }; + for (const [ key, value ] of vm) { + if (value === null) continue; + props[key] = toThemeString(value); + } + + return { + id: uuid(), + name, desc, author, props, base + }; +}; + +export const convertToViewModel = (theme: Theme): ThemeViewModel => { + const vm: ThemeViewModel = []; + // プロパティの登録 + vm.push(...themeProps.map(key => [ key, fromThemeString(theme.props[key])] as [ string, ThemeValue ])); + + // 定数の登録 + const consts = Object + .keys(theme.props) + .filter(k => k.startsWith('$')) + .map(k => [ k, fromThemeString(theme.props[k]) ] as [ string, ThemeValue ]); + + vm.push(...consts); + return vm; +}; diff --git a/src/client/scripts/theme.ts b/src/client/scripts/theme.ts index d458df45f0..30eaf77e01 100644 --- a/src/client/scripts/theme.ts +++ b/src/client/scripts/theme.ts @@ -12,6 +12,8 @@ export type Theme = { export const lightTheme: Theme = require('../themes/_light.json5'); export const darkTheme: Theme = require('../themes/_dark.json5'); +export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X')); + export const builtinThemes = [ require('../themes/white.json5'), require('../themes/black.json5'), diff --git a/src/client/themes/_dark.json5 b/src/client/themes/_dark.json5 index 4e5225db36..8d7a345fd5 100644 --- a/src/client/themes/_dark.json5 +++ b/src/client/themes/_dark.json5 @@ -12,12 +12,10 @@ accent: '#86b300', accentDarken: ':darken<10<@accent', accentLighten: ':lighten<10<@accent', - accentShadow: ':alpha<0.3<@accent', focus: ':alpha<0.3<@accent', bg: '#000', fg: '#c7d1d8', fgHighlighted: ':lighten<3<@fg', - html: '@bg', divider: 'rgba(255, 255, 255, 0.1)', indicator: '@accent', panel: '#000', diff --git a/src/client/themes/_light.json5 b/src/client/themes/_light.json5 index 2317ddef65..4e499e178c 100644 --- a/src/client/themes/_light.json5 +++ b/src/client/themes/_light.json5 @@ -12,12 +12,10 @@ accent: '#86b300', accentDarken: ':darken<10<@accent', accentLighten: ':lighten<10<@accent', - accentShadow: ':alpha<0.4<@accent', focus: ':alpha<0.3<@accent', bg: '#fafafa', fg: '#5c6a73', fgHighlighted: ':darken<3<@fg', - html: '@bg', divider: 'rgba(0, 0, 0, 0.1)', indicator: '@accent', panel: '#fff', diff --git a/src/client/themes/halloween.json5 b/src/client/themes/halloween.json5 index 7cabf01d1e..1394c793ed 100644 --- a/src/client/themes/halloween.json5 +++ b/src/client/themes/halloween.json5 @@ -12,7 +12,6 @@ panel: '#1f1d30', bg: '#0f0e17', fg: '#b1bee3', - html: '@accent', renote: '@accent', }, }