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 Dark theme, Light theme.
**Optimization**
- 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([
'mousetrap',
'AG_THEME_ID',
'AG_GRAY',
'AG_HIDE',
'AG_WARN',

View File

@ -1,7 +1,7 @@
import ContentState from './contentState'
import selection from './selection'
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 { search } from './codeMirror'
import { checkEditLanguage } from './codeMirror/language'
@ -429,6 +429,23 @@ class Aganippe {
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) {
this.contentState.updateParagraph(type)
}

View File

@ -84,7 +84,6 @@
.ag-table-picker .footer button {
outline: none;
cursor: pointer;
vertical-align: text-bottom;
border-radius: 2px;
line-height: 12px;
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);
@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 {
font-size: 16px;
background: rgb(43, 43, 43);
}
body {
@ -62,11 +33,44 @@ body {
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 {
color: #909399;
text-decoration: none;
}
.el-dialog {
background: rgb(43, 43, 43);
}
.v-modal {
background: rgba(0, 0, 0, .9);
}
body>*:first-child {
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);
@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 {
font-size: 16px;
background: rgb(252, 252, 252);
@ -40,7 +12,7 @@ html, body {
body {
font-family: "Open Sans", "Clear Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #606266;
color: #303133;
line-height: 1.6;
}
@ -67,6 +39,11 @@ body {
text-decoration: none;
}
.v-modal {
background: #fff;
opacity: .8;
}
body>*:first-child {
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'
}
export const DEFAULT_THEME = 'dark'
export const VIEW_MENU_ITEM = {
'Source Code Mode': false,
'Typewriter Mode': false,

View File

@ -6,6 +6,7 @@ import view from './view'
import windowMenu from './windowMenu'
import paragraph from './paragraph'
import format from './format'
import theme from './theme'
export dockMenu from './dock'
@ -19,6 +20,7 @@ export default function configureMenu ({ app }) {
paragraph,
format,
windowMenu,
theme,
view,
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 => {
const menus = Menu.getApplicationMenu()
console.log(typeof menus.append)
return menus.items.find(menu => menu.label === menuName)
}

View File

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

View File

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

View File

@ -1,5 +1,6 @@
<template>
<div class="search-bar"
:class="theme"
@click.stop="noop"
v-show="showSearch"
>
@ -100,6 +101,9 @@
import { mapState } from 'vuex'
export default {
props: {
theme: String
},
data () {
return {
showSearch: false,
@ -258,6 +262,7 @@
display: flex;
flex: 1;
position: relative;
margin-right: 5px;
}
.input-wrapper .search-result {
position: absolute;
@ -276,4 +281,21 @@
color: #606266;
padding: 0 8px;
}
</style>
/* 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>

View File

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

View File

@ -1,6 +1,6 @@
<template>
<div class="title-bar"
:class="{ 'active': active }"
:class="[{ 'active': active }, theme]"
>
<div class="title">
<span v-for="(path, index) of paths" :key="index">
@ -37,7 +37,8 @@
filename: String,
pathname: String,
active: Boolean,
wordCount: Object
wordCount: Object,
theme: String
},
computed: {
paths () {
@ -122,4 +123,16 @@
background: #F2F6FC;
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>

View File

@ -9,6 +9,8 @@ const state = {
matches: [],
value: ''
},
theme: '',
themeCSS: null,
typewriter: false, // typewriter mode
focus: false, // focus mode
sourceCode: false, // source code mode
@ -26,6 +28,12 @@ const state = {
}
const mutations = {
SET_THEME (state, { theme, themeCSS }) {
state.theme = theme
if (themeCSS) {
state.themeCSS = themeCSS
}
},
SET_MODE (state, { type, checked }) {
state[type] = checked
},
@ -57,6 +65,13 @@ const mutations = {
}
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) {
commit('SET_SEARCH', value)
},