mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 01:36:22 +00:00
@@ -16,7 +16,7 @@ import { store } from 'browser/main/store'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { getLocales } from 'browser/lib/Languages'
|
||||
import applyShortcuts from 'browser/main/lib/shortcutManager'
|
||||
import uiThemes from 'browser/lib/ui-themes'
|
||||
import { chooseTheme, applyTheme } from 'browser/main/lib/ThemeManager'
|
||||
import { push } from 'connected-react-router'
|
||||
|
||||
const path = require('path')
|
||||
@@ -148,11 +148,13 @@ class Main extends React.Component {
|
||||
componentDidMount() {
|
||||
const { dispatch, config } = this.props
|
||||
|
||||
if (uiThemes.some(theme => theme.name === config.ui.theme)) {
|
||||
document.body.setAttribute('data-theme', config.ui.theme)
|
||||
} else {
|
||||
document.body.setAttribute('data-theme', 'default')
|
||||
}
|
||||
this.refreshTheme = setInterval(() => {
|
||||
const conf = ConfigManager.get()
|
||||
chooseTheme(conf)
|
||||
}, 5 * 1000)
|
||||
|
||||
chooseTheme(config)
|
||||
applyTheme(config.ui.theme)
|
||||
|
||||
if (getLocales().indexOf(config.ui.language) !== -1) {
|
||||
i18n.setLocale(config.ui.language)
|
||||
@@ -191,6 +193,7 @@ class Main extends React.Component {
|
||||
this.toggleMenuBarVisible.bind(this)
|
||||
)
|
||||
eventEmitter.off('dispatch:push', this.changeRoutePush.bind(this))
|
||||
clearInterval(this.refreshTheme)
|
||||
}
|
||||
|
||||
changeRoutePush(event, destination) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import _ from 'lodash'
|
||||
import RcParser from 'browser/lib/RcParser'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import uiThemes from 'browser/lib/ui-themes'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
const win = global.process.platform === 'win32'
|
||||
@@ -47,6 +46,11 @@ export const DEFAULT_CONFIG = {
|
||||
ui: {
|
||||
language: 'en',
|
||||
theme: 'default',
|
||||
defaultTheme: 'default',
|
||||
enableScheduleTheme: false,
|
||||
scheduledTheme: 'monokai',
|
||||
scheduleStart: 1200,
|
||||
scheduleEnd: 360,
|
||||
showCopyNotification: true,
|
||||
disableDirectWrite: false,
|
||||
showScrollBar: true,
|
||||
@@ -200,12 +204,6 @@ function set(updates) {
|
||||
if (!validate(newConfig)) throw new Error('INVALID CONFIG')
|
||||
_save(newConfig)
|
||||
|
||||
if (uiThemes.some(theme => theme.name === newConfig.ui.theme)) {
|
||||
document.body.setAttribute('data-theme', newConfig.ui.theme)
|
||||
} else {
|
||||
document.body.setAttribute('data-theme', 'default')
|
||||
}
|
||||
|
||||
i18n.setLocale(newConfig.ui.language)
|
||||
|
||||
let editorTheme = document.getElementById('editorTheme')
|
||||
|
||||
65
browser/main/lib/ThemeManager.js
Normal file
65
browser/main/lib/ThemeManager.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
|
||||
const saveChanges = newConfig => {
|
||||
ConfigManager.set(newConfig)
|
||||
}
|
||||
|
||||
const chooseTheme = config => {
|
||||
const { ui } = config
|
||||
if (!ui.enableScheduleTheme) {
|
||||
return
|
||||
}
|
||||
|
||||
const start = parseInt(ui.scheduleStart)
|
||||
const end = parseInt(ui.scheduleEnd)
|
||||
|
||||
const now = new Date()
|
||||
const minutes = now.getHours() * 60 + now.getMinutes()
|
||||
|
||||
const isEndAfterStart = end > start
|
||||
const isBetweenStartAndEnd = minutes >= start && minutes < end
|
||||
const isBetweenEndAndStart = minutes >= start || minutes < end
|
||||
|
||||
if (
|
||||
(isEndAfterStart && isBetweenStartAndEnd) ||
|
||||
(!isEndAfterStart && isBetweenEndAndStart)
|
||||
) {
|
||||
if (ui.theme !== ui.scheduledTheme) {
|
||||
ui.defaultTheme = ui.theme
|
||||
ui.theme = ui.scheduledTheme
|
||||
applyTheme(ui.theme)
|
||||
saveChanges(config)
|
||||
}
|
||||
} else {
|
||||
if (ui.theme !== ui.defaultTheme) {
|
||||
ui.theme = ui.defaultTheme
|
||||
applyTheme(ui.theme)
|
||||
saveChanges(config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const applyTheme = theme => {
|
||||
const supportedThemes = [
|
||||
'dark',
|
||||
'white',
|
||||
'solarized-dark',
|
||||
'monokai',
|
||||
'dracula'
|
||||
]
|
||||
if (supportedThemes.indexOf(theme) !== -1) {
|
||||
document.body.setAttribute('data-theme', theme)
|
||||
if (document.body.querySelector('.MarkdownPreview')) {
|
||||
document.body
|
||||
.querySelector('.MarkdownPreview')
|
||||
.contentDocument.body.setAttribute('data-theme', theme)
|
||||
}
|
||||
} else {
|
||||
document.body.setAttribute('data-theme', 'default')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
chooseTheme,
|
||||
applyTheme
|
||||
}
|
||||
@@ -1,5 +1,109 @@
|
||||
@import('./Tab')
|
||||
|
||||
.container
|
||||
display flex
|
||||
flex-direction column
|
||||
align-items center
|
||||
justify-content center
|
||||
position relative
|
||||
margin-bottom 2em
|
||||
margin-left 2em
|
||||
|
||||
.box-minmax
|
||||
width 608px
|
||||
height 45px
|
||||
display flex
|
||||
justify-content space-between
|
||||
font-size $tab--button-font-size
|
||||
color $ui-text-color
|
||||
span first-child
|
||||
margin-top 18px
|
||||
padding-right 10px
|
||||
padding-left 10px
|
||||
padding-top 8px
|
||||
position relative
|
||||
border $ui-borderColor
|
||||
border-radius 5px
|
||||
background $ui-backgroundColor
|
||||
|
||||
div[id^="secondRow"]
|
||||
position absolute
|
||||
z-index 2
|
||||
left 0
|
||||
top 0
|
||||
margin-bottom -42px
|
||||
.rs-label
|
||||
margin-left -20px
|
||||
|
||||
div[id^="firstRow"]
|
||||
position absolute
|
||||
z-index 2
|
||||
left 0
|
||||
top 0
|
||||
margin-bottom -25px
|
||||
.rs-range
|
||||
&::-webkit-slider-thumb
|
||||
margin-top 0px
|
||||
transform rotate(180deg)
|
||||
.rs-label
|
||||
margin-bottom -85px
|
||||
margin-top 85px
|
||||
|
||||
|
||||
.rs-range
|
||||
margin-top 29px
|
||||
width 600px
|
||||
-webkit-appearance none
|
||||
&:focus
|
||||
outline black
|
||||
&::-webkit-slider-runnable-track
|
||||
width 100%
|
||||
height 0.1px
|
||||
cursor pointer
|
||||
box-shadow none
|
||||
background $ui-backgroundColor
|
||||
border-radius 0px
|
||||
border 0px solid #010101
|
||||
cursor none
|
||||
|
||||
&::-webkit-slider-thumb
|
||||
box-shadow none
|
||||
border 1px solid $ui-borderColor
|
||||
box-shadow 0px 10px 10px rgba(0, 0, 0, 0.25)
|
||||
height 32px
|
||||
width 32px
|
||||
border-radius 22px
|
||||
background white
|
||||
cursor pointer
|
||||
-webkit-appearance none
|
||||
margin-top -20px
|
||||
border-color $ui-default-button-backgroundColor
|
||||
height 32px
|
||||
border-top-left-radius 10%
|
||||
border-top-right-radius 10%
|
||||
|
||||
.rs-label
|
||||
position relative
|
||||
transform-origin center center
|
||||
display block
|
||||
background transparent
|
||||
border-radius none
|
||||
line-height 30px
|
||||
font-weight normal
|
||||
box-sizing border-box
|
||||
border none
|
||||
margin-bottom -5px
|
||||
margin-top -10px
|
||||
clear both
|
||||
float left
|
||||
height 17px
|
||||
margin-left -25px
|
||||
left attr(value)
|
||||
color $ui-text-color
|
||||
font-style normal
|
||||
font-weight normal
|
||||
line-height normal
|
||||
font-size $tab--button-font-size
|
||||
.root
|
||||
padding 15px
|
||||
margin-bottom 30px
|
||||
@@ -175,6 +279,9 @@ body[data-theme="dark"]
|
||||
.group-section-control
|
||||
select, .group-section-control-input
|
||||
colorDarkControl()
|
||||
.rs-label
|
||||
color $ui-dark-text-color
|
||||
|
||||
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
@@ -205,6 +312,8 @@ apply-theme(theme)
|
||||
.group-section-control
|
||||
select, .group-section-control-input
|
||||
colorThemedControl(theme)
|
||||
.rs-label
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
@@ -13,6 +13,7 @@ import i18n from 'browser/lib/i18n'
|
||||
import { getLanguages } from 'browser/lib/Languages'
|
||||
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
||||
import uiThemes from 'browser/lib/ui-themes'
|
||||
import { chooseTheme, applyTheme } from 'browser/main/lib/ThemeManager'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
@@ -84,6 +85,11 @@ class UiTab extends React.Component {
|
||||
const newConfig = {
|
||||
ui: {
|
||||
theme: this.refs.uiTheme.value,
|
||||
defaultTheme: this.refs.uiTheme.value,
|
||||
enableScheduleTheme: this.refs.enableScheduleTheme.checked,
|
||||
scheduledTheme: this.refs.uiScheduledTheme.value,
|
||||
scheduleStart: this.refs.scheduleStart.value,
|
||||
scheduleEnd: this.refs.scheduleEnd.value,
|
||||
language: this.refs.uiLanguage.value,
|
||||
defaultNote: this.refs.defaultNote.value,
|
||||
tagNewNoteWithFilteringTags: this.refs.tagNewNoteWithFilteringTags
|
||||
@@ -190,6 +196,9 @@ class UiTab extends React.Component {
|
||||
preview: this.state.config.preview
|
||||
}
|
||||
|
||||
chooseTheme(newConfig)
|
||||
applyTheme(newConfig.ui.theme)
|
||||
|
||||
ConfigManager.set(newConfig)
|
||||
|
||||
store.dispatch({
|
||||
@@ -208,6 +217,21 @@ class UiTab extends React.Component {
|
||||
}, 2000)()
|
||||
}
|
||||
|
||||
formatTime(time) {
|
||||
let hour = Math.floor(time / 60)
|
||||
let minute = time % 60
|
||||
|
||||
if (hour < 10) {
|
||||
hour = '0' + hour
|
||||
}
|
||||
|
||||
if (minute < 10) {
|
||||
minute = '0' + minute
|
||||
}
|
||||
|
||||
return `${hour}:${minute}`
|
||||
}
|
||||
|
||||
render() {
|
||||
const UiAlert = this.state.UiAlert
|
||||
const UiAlertElement =
|
||||
@@ -232,7 +256,7 @@ class UiTab extends React.Component {
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<select
|
||||
value={config.ui.theme}
|
||||
value={config.ui.defaultTheme}
|
||||
onChange={e => this.handleUIChange(e)}
|
||||
ref='uiTheme'
|
||||
>
|
||||
@@ -263,6 +287,101 @@ class UiTab extends React.Component {
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-header2'>{i18n.__('Theme Schedule')}</div>
|
||||
<div styleName='group-checkBoxSection'>
|
||||
<label>
|
||||
<input
|
||||
onChange={e => this.handleUIChange(e)}
|
||||
checked={config.ui.enableScheduleTheme}
|
||||
ref='enableScheduleTheme'
|
||||
type='checkbox'
|
||||
/>
|
||||
|
||||
{i18n.__('Enable Scheduled Themes')}
|
||||
</label>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Scheduled Theme')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<select
|
||||
disabled={!config.ui.enableScheduleTheme}
|
||||
value={config.ui.scheduledTheme}
|
||||
onChange={e => this.handleUIChange(e)}
|
||||
ref='uiScheduledTheme'
|
||||
>
|
||||
<optgroup label='Light Themes'>
|
||||
{uiThemes
|
||||
.filter(theme => !theme.isDark)
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
.map(theme => {
|
||||
return (
|
||||
<option value={theme.name} key={theme.name}>
|
||||
{theme.label}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</optgroup>
|
||||
<optgroup label='Dark Themes'>
|
||||
{uiThemes
|
||||
.filter(theme => theme.isDark)
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
.map(theme => {
|
||||
return (
|
||||
<option value={theme.name} key={theme.name}>
|
||||
{theme.label}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='container'>
|
||||
<div id='firstRow'>
|
||||
<span
|
||||
id='rs-bullet-1'
|
||||
styleName='rs-label'
|
||||
>{`End: ${this.formatTime(config.ui.scheduleEnd)}`}</span>
|
||||
<input
|
||||
disabled={!config.ui.enableScheduleTheme}
|
||||
id='rs-range-line-1'
|
||||
styleName='rs-range'
|
||||
type='range'
|
||||
value={config.ui.scheduleEnd}
|
||||
min='0'
|
||||
max='1440'
|
||||
step='5'
|
||||
ref='scheduleEnd'
|
||||
onChange={e => this.handleUIChange(e)}
|
||||
/>
|
||||
</div>
|
||||
<div id='secondRow'>
|
||||
<span
|
||||
id='rs-bullet-2'
|
||||
styleName='rs-label'
|
||||
>{`Start: ${this.formatTime(config.ui.scheduleStart)}`}</span>
|
||||
<input
|
||||
disabled={!config.ui.enableScheduleTheme}
|
||||
id='rs-range-line-2'
|
||||
styleName='rs-range'
|
||||
type='range'
|
||||
value={config.ui.scheduleStart}
|
||||
min='0'
|
||||
max='1440'
|
||||
step='5'
|
||||
ref='scheduleStart'
|
||||
onChange={e => this.handleUIChange(e)}
|
||||
/>
|
||||
</div>
|
||||
<div styleName='box-minmax'>
|
||||
<span>00:00</span>
|
||||
<span>24:00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Language')}</div>
|
||||
|
||||
103
tests/lib/themeManager.test.js
Normal file
103
tests/lib/themeManager.test.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* @fileoverview Unit test for browser/main/lib/ThemeManager.js
|
||||
*/
|
||||
const { chooseTheme, applyTheme } = require('browser/main/lib/ThemeManager')
|
||||
jest.mock('../../browser/main/lib/ConfigManager', () => {
|
||||
return {
|
||||
set: () => {}
|
||||
}
|
||||
})
|
||||
|
||||
const originalDate = Date
|
||||
let context = {}
|
||||
|
||||
beforeAll(() => {
|
||||
const constantDate = new Date('2017-11-27T14:33:42Z')
|
||||
global.Date = class extends Date {
|
||||
constructor() {
|
||||
super()
|
||||
return constantDate
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
context = {
|
||||
ui: {
|
||||
theme: 'white',
|
||||
scheduledTheme: 'dark',
|
||||
enableScheduleTheme: true,
|
||||
defaultTheme: 'monokai'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
global.Date = originalDate
|
||||
})
|
||||
|
||||
test("enableScheduleTheme is false, theme shouldn't change", () => {
|
||||
context.ui.enableScheduleTheme = false
|
||||
|
||||
const beforeTheme = context.ui.theme
|
||||
chooseTheme(context)
|
||||
const afterTheme = context.ui.theme
|
||||
|
||||
expect(afterTheme).toBe(beforeTheme)
|
||||
})
|
||||
|
||||
// NOT IN SCHEDULE
|
||||
test("scheduleEnd is bigger than scheduleStart and not in schedule, theme shouldn't change", () => {
|
||||
const beforeTheme = context.ui.defaultTheme
|
||||
context.ui.scheduleStart = 720 // 12:00
|
||||
context.ui.scheduleEnd = 870 // 14:30
|
||||
chooseTheme(context)
|
||||
const afterTheme = context.ui.theme
|
||||
|
||||
expect(afterTheme).toBe(beforeTheme)
|
||||
})
|
||||
|
||||
test("scheduleStart is bigger than scheduleEnd and not in schedule, theme shouldn't change", () => {
|
||||
const beforeTheme = context.ui.defaultTheme
|
||||
context.ui.scheduleStart = 960 // 16:00
|
||||
context.ui.scheduleEnd = 600 // 10:00
|
||||
chooseTheme(context)
|
||||
const afterTheme = context.ui.theme
|
||||
|
||||
expect(afterTheme).toBe(beforeTheme)
|
||||
})
|
||||
|
||||
// IN SCHEDULE
|
||||
test('scheduleEnd is bigger than scheduleStart and in schedule, theme should change', () => {
|
||||
const beforeTheme = context.ui.scheduledTheme
|
||||
context.ui.scheduleStart = 720 // 12:00
|
||||
context.ui.scheduleEnd = 900 // 15:00
|
||||
chooseTheme(context)
|
||||
const afterTheme = context.ui.theme
|
||||
|
||||
expect(afterTheme).toBe(beforeTheme)
|
||||
})
|
||||
|
||||
test('scheduleStart is bigger than scheduleEnd and in schedule, theme should change', () => {
|
||||
const beforeTheme = context.ui.scheduledTheme
|
||||
context.ui.scheduleStart = 1200 // 20:00
|
||||
context.ui.scheduleEnd = 900 // 15:00
|
||||
chooseTheme(context)
|
||||
const afterTheme = context.ui.theme
|
||||
|
||||
expect(afterTheme).toBe(beforeTheme)
|
||||
})
|
||||
|
||||
test("theme to apply is not a supported theme, theme shouldn't change", () => {
|
||||
applyTheme('notATheme')
|
||||
const afterTheme = document.body.dataset.theme
|
||||
|
||||
expect(afterTheme).toBe('default')
|
||||
})
|
||||
|
||||
test('theme to apply is a supported theme, theme should change', () => {
|
||||
applyTheme(context.ui.defaultTheme)
|
||||
const afterTheme = document.body.dataset.theme
|
||||
|
||||
expect(afterTheme).toBe(context.ui.defaultTheme)
|
||||
})
|
||||
Reference in New Issue
Block a user