feat: add dark theme and light theme

This commit is contained in:
Jocs 2018-03-04 21:04:15 +08:00
parent 590f49cf24
commit 12c22ce588
17 changed files with 255 additions and 85 deletions

View File

@ -6,6 +6,8 @@
- Add Focus Mode, the current paragraph's will be focused. - Add Focus Mode, the current paragraph's will be focused.
- Add Dark theme, Light theme.
**Optimization** **Optimization**
- Optimize the display of path name and file name in title bar. - Optimize the display of path name and file name in title bar.

View File

@ -47,6 +47,7 @@ export const LOWERCASE_TAGS = generateKeyHash([
export const CLASS_OR_ID = genUpper2LowerKeyHash([ export const CLASS_OR_ID = genUpper2LowerKeyHash([
'mousetrap', 'mousetrap',
'AG_THEME_ID',
'AG_GRAY', 'AG_GRAY',
'AG_HIDE', 'AG_HIDE',
'AG_WARN', 'AG_WARN',

View File

@ -1,7 +1,7 @@
import ContentState from './contentState' import ContentState from './contentState'
import selection from './selection' import selection from './selection'
import EventCenter from './event' import EventCenter from './event'
import { LOWERCASE_TAGS, EVENT_KEYS, CLASS_OR_ID } from './config' import { LOWERCASE_TAGS, EVENT_KEYS, CLASS_OR_ID, codeMirrorConfig } from './config'
import { throttle, debounce } from './utils' import { throttle, debounce } from './utils'
import { search } from './codeMirror' import { search } from './codeMirror'
import { checkEditLanguage } from './codeMirror/language' import { checkEditLanguage } from './codeMirror/language'
@ -429,6 +429,23 @@ class Aganippe {
this.focusMode = bool this.focusMode = bool
} }
setTheme (name, css) {
if (name === 'dark') {
codeMirrorConfig.theme = 'railscasts'
} else {
delete codeMirrorConfig.theme
}
const themeStyleId = CLASS_OR_ID['AG_THEME_ID']
let styleEle = document.querySelector(`#${themeStyleId}`)
if (!styleEle) {
styleEle = document.createElement('style')
styleEle.id = themeStyleId
document.querySelector('head').appendChild(styleEle)
}
styleEle.innerHTML = css
this.contentState.render()
}
updateParagraph (type) { updateParagraph (type) {
this.contentState.updateParagraph(type) this.contentState.updateParagraph(type)
} }

View File

@ -84,7 +84,6 @@
.ag-table-picker .footer button { .ag-table-picker .footer button {
outline: none; outline: none;
cursor: pointer; cursor: pointer;
vertical-align: text-bottom;
border-radius: 2px; border-radius: 2px;
line-height: 12px; line-height: 12px;
border: none; border: none;

View File

@ -5,37 +5,8 @@
@include-when-export url(http://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,700,400&subset=latin,latin-ext); @include-when-export url(http://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,700,400&subset=latin,latin-ext);
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: normal;
src: local('Open Sans Regular'), url('./github/400.woff') format('woff')
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: normal;
src: local('Open Sans Italic'), url('./github/400i.woff') format('woff')
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: bold;
src: local('Open Sans Bold'), url('./github/700.woff') format('woff')
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: bold;
src: local('Open Sans Bold Italic'), url('./github/700i.woff') format('woff')
}
html, body { html, body {
font-size: 16px; font-size: 16px;
background: rgb(43, 43, 43);
} }
body { body {
@ -62,11 +33,44 @@ body {
margin-top: 30px; margin-top: 30px;
} }
.ag-float-box {
background: #303133;
border: 1px solid #303133;
}
.ag-float-item {
color: #909399;
}
.ag-float-item-active {
background: #606266;
color: #C0C4CC;
}
.ag-table-picker {
background: #606266;
border: 1px solid #606266;
}
.ag-table-picker::before {
background: #606266;
border: 1px solid #606266;
border-right: none;
border-bottom: none;
}
.ag-gray { .ag-gray {
color: #909399; color: #909399;
text-decoration: none; text-decoration: none;
} }
.el-dialog {
background: rgb(43, 43, 43);
}
.v-modal {
background: rgba(0, 0, 0, .9);
}
body>*:first-child { body>*:first-child {
margin-top: 0 !important; margin-top: 0 !important;
} }

View File

@ -5,34 +5,6 @@
@include-when-export url(http://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,700,400&subset=latin,latin-ext); @include-when-export url(http://fonts.googleapis.com/css?family=Open+Sans:400italic,700italic,700,400&subset=latin,latin-ext);
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: normal;
src: local('Open Sans Regular'), url('./github/400.woff') format('woff')
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: normal;
src: local('Open Sans Italic'), url('./github/400i.woff') format('woff')
}
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: bold;
src: local('Open Sans Bold'), url('./github/700.woff') format('woff')
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: bold;
src: local('Open Sans Bold Italic'), url('./github/700i.woff') format('woff')
}
html, body { html, body {
font-size: 16px; font-size: 16px;
background: rgb(252, 252, 252); background: rgb(252, 252, 252);
@ -40,7 +12,7 @@ html, body {
body { body {
font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #606266; color: #303133;
line-height: 1.6; line-height: 1.6;
} }
@ -67,6 +39,11 @@ body {
text-decoration: none; text-decoration: none;
} }
.v-modal {
background: #fff;
opacity: .8;
}
body>*:first-child { body>*:first-child {
margin-top: 0 !important; margin-top: 0 !important;
} }

45
src/main/actions/theme.js Normal file
View File

@ -0,0 +1,45 @@
import fs from 'fs'
import path from 'path'
import { ipcMain, BrowserWindow } from 'electron'
import { getMenuItem } from '../utils'
const THEME_PATH = path.resolve(__dirname, '../../editor/themes')
const themeCSS = {}
export const selectTheme = (win, theme, themeCSS) => {
win.webContents.send('AGANI::theme', { theme, themeCSS })
}
const getSelectTheme = () => {
const themeMenu = getMenuItem('Theme')
return themeMenu.submenu.items.find(item => item.checked)
}
ipcMain.on('AGANI::ask-for-theme', e => {
const win = BrowserWindow.fromWebContents(e.sender)
if (!Object.keys(themeCSS).length) {
const promises = ['dark', 'light'].map(theme => {
return new Promise((resolve, reject) => {
fs.readFile(`${THEME_PATH}/${theme}.css`, 'utf-8', (err, data) => {
if (err) reject(err)
resolve({ theme, data })
})
})
})
Promise.all(promises)
.then(themes => {
themes.forEach(t => {
console.log(t)
const { theme, data } = t
themeCSS[theme] = data
})
const selectedTheme = getSelectTheme().label.toLowerCase()
console.log(selectedTheme)
console.log(themeCSS)
selectTheme(win, selectedTheme, themeCSS)
})
} else {
const selectedTheme = getSelectTheme().label.toLowerCase()
selectTheme(win, selectedTheme, themeCSS)
}
})

View File

@ -17,6 +17,8 @@ export const EXTENSION_HASN = {
pdf: '.pdf' pdf: '.pdf'
} }
export const DEFAULT_THEME = 'dark'
export const VIEW_MENU_ITEM = { export const VIEW_MENU_ITEM = {
'Source Code Mode': false, 'Source Code Mode': false,
'Typewriter Mode': false, 'Typewriter Mode': false,

View File

@ -6,6 +6,7 @@ import view from './view'
import windowMenu from './windowMenu' import windowMenu from './windowMenu'
import paragraph from './paragraph' import paragraph from './paragraph'
import format from './format' import format from './format'
import theme from './theme'
export dockMenu from './dock' export dockMenu from './dock'
@ -19,6 +20,7 @@ export default function configureMenu ({ app }) {
paragraph, paragraph,
format, format,
windowMenu, windowMenu,
theme,
view, view,
help help
] ]

20
src/main/menus/theme.js Normal file
View File

@ -0,0 +1,20 @@
import * as actions from '../actions/theme'
export default {
label: 'Theme',
submenu: [{
label: 'Dark',
type: 'radio',
checked: true,
click (menuItem, browserWindow) {
actions.selectTheme(browserWindow, 'dark')
}
}, {
label: 'Light',
type: 'radio',
checked: false,
click (menuItem, browserWindow) {
actions.selectTheme(browserWindow, 'light')
}
}]
}

View File

@ -2,5 +2,6 @@ import { Menu } from 'electron'
export const getMenuItem = menuName => { export const getMenuItem = menuName => {
const menus = Menu.getApplicationMenu() const menus = Menu.getApplicationMenu()
console.log(typeof menus.append)
return menus.items.find(menu => menu.label === menuName) return menus.items.find(menu => menu.label === menuName)
} }

View File

@ -5,6 +5,7 @@
:filename="filename" :filename="filename"
:active="windowActive" :active="windowActive"
:word-count="wordCount" :word-count="wordCount"
:theme="theme"
></title-bar> ></title-bar>
<editor <editor
:typewriter="typewriter" :typewriter="typewriter"
@ -13,14 +14,18 @@
:markdown="markdown" :markdown="markdown"
:cursor="cursor" :cursor="cursor"
v-if="!sourceCode" v-if="!sourceCode"
:theme="theme"
:theme-css="themeCSS"
></editor> ></editor>
<source-code <source-code
v-else v-else
:markdown="markdown" :markdown="markdown"
:cursor="cursor" :cursor="cursor"
:theme="theme"
></source-code> ></source-code>
<search <search
v-if="!sourceCode" v-if="!sourceCode"
:theme="theme"
></search> ></search>
</div> </div>
</template> </template>
@ -46,12 +51,14 @@
computed: { computed: {
...mapState([ ...mapState([
'pathname', 'filename', 'windowActive', 'wordCount', 'pathname', 'filename', 'windowActive', 'wordCount',
'typewriter', 'focus', 'sourceCode', 'markdown', 'cursor' 'typewriter', 'focus', 'sourceCode', 'markdown', 'cursor',
'theme', 'themeCSS'
]) ])
}, },
created () { created () {
const { dispatch } = this.$store const { dispatch } = this.$store
dispatch('ASK_FOR_THEME')
dispatch('ASK_FOR_MODE') dispatch('ASK_FOR_MODE')
dispatch('LISTEN_FOR_CLOSE') dispatch('LISTEN_FOR_CLOSE')
dispatch('LISTEN_FOR_SAVE_AS') dispatch('LISTEN_FOR_SAVE_AS')

View File

@ -1,7 +1,7 @@
<template> <template>
<div <div
class="editor-wrapper" class="editor-wrapper"
:class="{ 'typewriter': typewriter, 'focus': focus, 'source': sourceCode }" :class="[{ 'typewriter': typewriter, 'focus': focus, 'source': sourceCode }, theme]"
> >
<div <div
ref="editor" ref="editor"
@ -83,7 +83,9 @@
required: true required: true
}, },
markdown: String, markdown: String,
cursor: Object cursor: Object,
theme: String,
themeCss: Object
}, },
data () { data () {
return { return {
@ -106,6 +108,12 @@
}, },
focus: function (value) { focus: function (value) {
this.editor.setFocusMode(value) this.editor.setFocusMode(value)
},
theme: function (value, oldValue) {
const { editor, themeCss } = this
if (value !== oldValue && editor) {
editor.setTheme(value, themeCss[value])
}
} }
}, },
created () { created () {
@ -113,7 +121,7 @@
const ele = this.$refs.editor const ele = this.$refs.editor
this.editor = new Aganippe(ele) this.editor = new Aganippe(ele)
const { container } = this.editor const { container } = this.editor
const { markdown } = this const { markdown, theme, themeCss } = this
// init set markdown and edit mode(typewriter mode and focus mode) // init set markdown and edit mode(typewriter mode and focus mode)
if (markdown.trim()) { if (markdown.trim()) {
this.setMarkdownToEditor(markdown) this.setMarkdownToEditor(markdown)
@ -123,6 +131,10 @@
this.editor.setFocusMode(this.focus) this.editor.setFocusMode(this.focus)
} }
if (theme) {
this.editor.setTheme(theme, themeCss[theme])
}
bus.$on('file-loaded', this.setMarkdownToEditor) bus.$on('file-loaded', this.setMarkdownToEditor)
bus.$on('undo', () => this.editor.undo()) bus.$on('undo', () => this.editor.undo())
bus.$on('redo', () => this.editor.redo()) bus.$on('redo', () => this.editor.redo())
@ -247,7 +259,7 @@
</script> </script>
<style> <style>
@import '../../editor/themes/light.css'; /* @import '../../editor/themes/dark.css';*/
@import '../../editor/index.css'; @import '../../editor/index.css';
.editor-wrapper { .editor-wrapper {
height: calc(100vh - 22px); height: calc(100vh - 22px);
@ -267,15 +279,17 @@
padding-top: calc(50vh - 136px); padding-top: calc(50vh - 136px);
padding-bottom: calc(50vh - 54px); padding-bottom: calc(50vh - 54px);
} }
.v-modal {
background: #fff;
opacity: .8;
}
.ag-dialog-table { .ag-dialog-table {
box-shadow: none; box-shadow: none;
} }
.ag-dialog-table .dialog-title svg { .ag-dialog-table .dialog-title svg {
width: 1.5em; width: 1.5em;
height: 1.5em; height: 1.5em;
} }
/* for dark theme */
.dark.editor-wrapper {
background: rgb(43, 43, 43);
}
</style> </style>

View File

@ -1,5 +1,6 @@
<template> <template>
<div class="search-bar" <div class="search-bar"
:class="theme"
@click.stop="noop" @click.stop="noop"
v-show="showSearch" v-show="showSearch"
> >
@ -100,6 +101,9 @@
import { mapState } from 'vuex' import { mapState } from 'vuex'
export default { export default {
props: {
theme: String
},
data () { data () {
return { return {
showSearch: false, showSearch: false,
@ -258,6 +262,7 @@
display: flex; display: flex;
flex: 1; flex: 1;
position: relative; position: relative;
margin-right: 5px;
} }
.input-wrapper .search-result { .input-wrapper .search-result {
position: absolute; position: absolute;
@ -276,4 +281,21 @@
color: #606266; color: #606266;
padding: 0 8px; padding: 0 8px;
} }
/* css for dark theme*/
.dark {
caret-color: #efefef;
background: rgb(43, 43, 43);
color: #606266;
}
.dark input {
background: rgb(54, 55, 49);
color: #C0C4CC;
}
.dark .button:hover {
background: rgb(71, 72, 66);
color: #C0C4CC;
}
.dark .button:active {
background: #303133;
}
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="source-code" ref="sourceCode"> <div class="source-code" ref="sourceCode" :class="[theme]">
</div> </div>
</template> </template>
@ -11,7 +11,8 @@
export default { export default {
props: { props: {
markdown: String, markdown: String,
cursor: Object cursor: Object,
theme: String
}, },
data () { data () {
return { return {
@ -19,14 +20,28 @@
editor: null editor: null
} }
}, },
watch: {
theme: function (value, oldValue) {
const cm = this.$refs.sourceCode.querySelector('.CodeMirror')
if (value !== oldValue) {
if (value === 'dark') {
cm.classList.remove('cm-s-default')
cm.classList.add('cm-s-railscasts')
} else {
cm.classList.add('cm-s-default')
cm.classList.remove('cm-s-railscasts')
}
}
}
},
created () { created () {
this.$nextTick(() => { this.$nextTick(() => {
const { markdown = '' } = this const { markdown = '', theme } = this
this.contentState = new ContentState() this.contentState = new ContentState()
const container = this.$refs.sourceCode const container = this.$refs.sourceCode
const editor = this.editor = codeMirror(container, { const codeMirrorConfig = {
// theme: 'railscasts', // theme: 'railscasts',
value: markdown, value: '',
lineNumbers: true, lineNumbers: true,
autofocus: true, autofocus: true,
lineWrapping: true, lineWrapping: true,
@ -38,9 +53,22 @@
return '' return ''
} }
} }
}) }
if (theme === 'dark') codeMirrorConfig.theme = 'railscasts'
this.editor = codeMirror(container, codeMirrorConfig)
bus.$on('file-loaded', this.setMarkdown) bus.$on('file-loaded', this.setMarkdown)
this.listenChange()
this.setMarkdown(markdown)
})
},
beforeDestory () {
bus.$off('file-loaded', this.setMarkdown)
},
methods: {
listenChange () {
const { editor } = this
editor.on('cursorActivity', (cm, event) => { editor.on('cursorActivity', (cm, event) => {
const cursor = cm.getCursor() const cursor = cm.getCursor()
const markdown = cm.getValue() const markdown = cm.getValue()
@ -51,13 +79,7 @@
}) })
setMode(editor, 'markdown') setMode(editor, 'markdown')
this.setMarkdown(markdown) },
})
},
beforeDestory () {
bus.$off('file-loaded', this.setMarkdown)
},
methods: {
setMarkdown (markdown) { setMarkdown (markdown) {
const { editor, cursor } = this const { editor, cursor } = this
this.editor.setValue(markdown) this.editor.setValue(markdown)
@ -89,4 +111,11 @@
.source-code .CodeMirror-activeline-gutter { .source-code .CodeMirror-activeline-gutter {
background: #F2F6FC; background: #F2F6FC;
} }
.dark {
background: rgb(43, 43, 43);
}
.dark.source-code .CodeMirror-activeline-background,
.dark.source-code .CodeMirror-activeline-gutter {
background: #333;
}
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="title-bar" <div class="title-bar"
:class="{ 'active': active }" :class="[{ 'active': active }, theme]"
> >
<div class="title"> <div class="title">
<span v-for="(path, index) of paths" :key="index"> <span v-for="(path, index) of paths" :key="index">
@ -37,7 +37,8 @@
filename: String, filename: String,
pathname: String, pathname: String,
active: Boolean, active: Boolean,
wordCount: Object wordCount: Object,
theme: String
}, },
computed: { computed: {
paths () { paths () {
@ -122,4 +123,16 @@
background: #F2F6FC; background: #F2F6FC;
color: #606266; color: #606266;
} }
/* css for dark theme */
.dark {
background: rgb(43, 43, 43);
color: #909399;
}
.dark .title:hover {
color: #F2F6FC;
}
.dark .word-count:hover {
background: rgb(71, 72, 66);
color: #C0C4CC;
}
</style> </style>

View File

@ -9,6 +9,8 @@ const state = {
matches: [], matches: [],
value: '' value: ''
}, },
theme: '',
themeCSS: null,
typewriter: false, // typewriter mode typewriter: false, // typewriter mode
focus: false, // focus mode focus: false, // focus mode
sourceCode: false, // source code mode sourceCode: false, // source code mode
@ -26,6 +28,12 @@ const state = {
} }
const mutations = { const mutations = {
SET_THEME (state, { theme, themeCSS }) {
state.theme = theme
if (themeCSS) {
state.themeCSS = themeCSS
}
},
SET_MODE (state, { type, checked }) { SET_MODE (state, { type, checked }) {
state[type] = checked state[type] = checked
}, },
@ -57,6 +65,13 @@ const mutations = {
} }
const actions = { const actions = {
ASK_FOR_THEME ({ commit }) {
ipcRenderer.send('AGANI::ask-for-theme')
ipcRenderer.on('AGANI::theme', (e, themes) => {
console.log(themes)
commit('SET_THEME', themes)
})
},
SEARCH ({ commit }, value) { SEARCH ({ commit }, value) {
commit('SET_SEARCH', value) commit('SET_SEARCH', value)
}, },