Skip to content

Commit 32d6abf

Browse files
authored
Merge pull request #114 from sheepzh/darkmode
Supports darkmode
2 parents 87d14ba + 4e0a0bc commit 32d6abf

File tree

33 files changed

+717
-149
lines changed

33 files changed

+717
-149
lines changed

global.d.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ declare namespace Timer {
3333
displaySiteName: boolean
3434
}
3535

36+
type AppearanceOptionDarkMode =
37+
// Always on
38+
| "on"
39+
// Always off
40+
| "off"
41+
// Timed on
42+
| "timed"
3643
type AppearanceOption = {
3744
/**
3845
* Whether to display the whitelist button in the context menu
@@ -58,6 +65,20 @@ declare namespace Timer {
5865
* @since 0.8.6
5966
*/
6067
printInConsole: boolean
68+
/**
69+
* The state of dark mode
70+
*
71+
* @since 1.1.0
72+
*/
73+
darkMode: AppearanceOptionDarkMode
74+
75+
/**
76+
* The range of seconds to turn on dark mode. Required if {@param darkMode} is 'timed'
77+
*
78+
* @since 1.1.0
79+
*/
80+
darkModeTimeStart?: number
81+
darkModeTimeEnd?: number
6182
}
6283

6384
type StatisticsOption = {

public/popup.html

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@
1515
<div id="type-select-container" class="el-select el-select--mini option-right">
1616
<div class="select-trigger">
1717
<div class="el-input el-input--mini el-input--suffix">
18-
<input id="type-select-input" class="el-input__inner" type="text" readonly="" autocomplete="off" />
18+
<div class="el-input__wrapper">
19+
<input id="type-select-input" class="el-input__inner" type="text" readonly="" autocomplete="off" />
20+
</div>
1921
</div>
2022
</div>
2123
</div>
2224
<div id="time-select-container" class="el-select el-select--mini option-right">
2325
<div class="select-trigger">
2426
<div class="el-input el-input--mini el-input--suffix">
25-
<input id="time-select-input" class="el-input__inner" type="text" readonly="" autocomplete="off" />
27+
<div class="el-input__wrapper">
28+
<input id="time-select-input" class="el-input__inner" type="text" readonly="" autocomplete="off" />
29+
</div>
2630
</div>
2731
</div>
2832
</div>

src/app/components/dashboard/components/calendar-heat-map.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { ElLoading } from "element-plus"
3636
import { defineComponent, h, onMounted, ref, Ref } from "vue"
3737
import { groupBy, rotate } from "@util/array"
3838
import { BASE_TITLE_OPTION } from "../common"
39+
import { getPrimaryTextColor } from "@util/style"
3940

4041
const WEEK_NUM = 53
4142

@@ -110,14 +111,18 @@ function optionOf(data: _Value[], days: string[]): EcOption {
110111
const totalMinutes = data.map(d => d[2] || 0).reduce((a, b) => a + b, 0)
111112
const totalHours = Math.floor(totalMinutes / 60)
112113
const xAxisLabelMap = getXAxisLabelMap(data)
114+
const textColor = getPrimaryTextColor()
113115
return {
114116
title: {
115117
...BASE_TITLE_OPTION,
116118
text: t(msg => totalHours
117119
? msg.dashboard.heatMap.title0
118120
: msg.dashboard.heatMap.title1,
119121
{ hour: totalHours }
120-
)
122+
),
123+
textStyle: {
124+
color: textColor
125+
}
121126
},
122127
tooltip: {
123128
position: 'top',
@@ -137,12 +142,16 @@ function optionOf(data: _Value[], days: string[]): EcOption {
137142
formatter: (x: string) => xAxisLabelMap[x] || '',
138143
interval: 0,
139144
margin: 14,
145+
color: textColor
140146
},
141147
},
142148
yAxis: {
143149
type: 'category',
144150
data: days,
145-
axisLabel: { padding: /* T R B L */[0, 12, 0, 0] },
151+
axisLabel: {
152+
padding: /* T R B L */[0, 12, 0, 0],
153+
color: textColor
154+
},
146155
axisLine: { show: false },
147156
axisTick: { show: false, alignWithLabel: true }
148157
},
@@ -155,19 +164,21 @@ function optionOf(data: _Value[], days: string[]): EcOption {
155164
orient: 'vertical',
156165
right: '2%',
157166
top: 'center',
158-
dimension: 2
167+
dimension: 2,
168+
textStyle: {
169+
color: textColor
170+
}
159171
}],
160172
series: [{
161173
name: 'Daily Focus',
162174
type: 'heatmap',
163175
data: data.map(d => {
164176
let item = { value: d, itemStyle: undefined, label: undefined, emphasis: undefined, tooltip: undefined, silent: false }
165177
const minutes = d[2]
166-
const date = d[3]
167178
if (minutes) {
168179
} else {
169180
item.itemStyle = {
170-
color: '#fff',
181+
color: 'transparent',
171182
}
172183
item.emphasis = {
173184
disabled: true

src/app/components/dashboard/components/top-k-visit.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import type { ECharts, ComposeOption } from "echarts/core"
99
import type { PieSeriesOption } from "echarts/charts"
1010
import type { TitleComponentOption, TooltipComponentOption } from "echarts/components"
11+
import type { Ref } from "vue"
12+
import type { TimerQueryParam } from "@service/timer-service"
1113

1214
import { init, use } from "@echarts/core"
1315
import PieChart from "@echarts/chart/pie"
@@ -16,13 +18,14 @@ import TooltipComponent from "@echarts/component/tooltip"
1618

1719
use([PieChart, TitleComponent, TooltipComponent])
1820

19-
import timerService, { SortDirect, TimerQueryParam } from "@service/timer-service"
21+
import timerService, { SortDirect } from "@service/timer-service"
2022
import { MILL_PER_DAY } from "@util/time"
2123
import { ElLoading } from "element-plus"
22-
import { defineComponent, h, onMounted, ref, Ref } from "vue"
24+
import { defineComponent, h, onMounted, ref } from "vue"
2325
import DataItem from "@entity/dto/data-item"
2426
import { BASE_TITLE_OPTION } from "../common"
2527
import { t } from "@app/locale"
28+
import { getPrimaryTextColor } from "@util/style"
2629

2730
const CONTAINER_ID = '__timer_dashboard_top_k_visit'
2831
const TOP_NUM = 6
@@ -40,10 +43,12 @@ type _Value = {
4043
}
4144

4245
function optionOf(data: _Value[]): EcOption {
46+
const textColor = getPrimaryTextColor()
4347
return {
4448
title: {
4549
...BASE_TITLE_OPTION,
46-
text: t(msg => msg.dashboard.topK.title, { k: TOP_NUM, day: DAY_NUM })
50+
text: t(msg => msg.dashboard.topK.title, { k: TOP_NUM, day: DAY_NUM }),
51+
textStyle: { color: textColor }
4752
},
4853
tooltip: {
4954
show: true,
@@ -64,6 +69,7 @@ function optionOf(data: _Value[]): EcOption {
6469
itemStyle: {
6570
borderRadius: 7
6671
},
72+
label: { color: textColor },
6773
data: data
6874
}
6975
}

src/app/components/dashboard/components/week-on-week.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import DataItem from "@entity/dto/data-item"
2626
import { groupBy, sum } from "@util/array"
2727
import { BASE_TITLE_OPTION } from "../common"
2828
import { t } from "@app/locale"
29+
import { getPrimaryTextColor } from "@util/style"
2930

3031
type EcOption = ComposeOption<
3132
| CandlestickSeriesOption
@@ -48,6 +49,7 @@ type _Value = {
4849
}
4950

5051
function optionOf(lastPeriodItems: DataItem[], thisPeriodItems: DataItem[]): EcOption {
52+
const textColor = getPrimaryTextColor()
5153
const lastPeriodMap: { [host: string]: number } = groupBy(lastPeriodItems,
5254
item => item.host,
5355
grouped => Math.floor(sum(grouped.map(item => item.focus)) / 1000)
@@ -113,15 +115,18 @@ function optionOf(lastPeriodItems: DataItem[], thisPeriodItems: DataItem[]): EcO
113115
},
114116
xAxis: {
115117
type: 'category',
116-
name: 'Seconds',
117118
splitLine: { show: false },
118119
data: topK.map(a => a.host),
119120
axisLabel: {
120-
interval: 0
121+
interval: 0,
122+
color: textColor,
121123
},
122124
},
123125
yAxis: {
124126
type: 'value',
127+
axisLabel: {
128+
color: textColor,
129+
}
125130
},
126131
series: [{
127132
type: 'candlestick',

src/app/components/dashboard/feedback.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const _default = defineComponent({
2525
}, h(ElTooltip, {
2626
placement: 'top',
2727
content: t(msg => msg.dashboard.feedback.tooltip),
28-
effect: Effect.LIGHT,
28+
effect: Effect.DARK,
2929
}, () => h(ElButton, {
3030
type: "info",
3131
size: 'small',

src/app/components/habit/component/chart/wrapper.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { PeriodKey, PERIODS_PER_DATE } from "@entity/dto/period-info"
2424
import PeriodResult from "@entity/dto/period-result"
2525
import { formatPeriodCommon, formatTime, MILL_PER_DAY } from "@util/time"
2626
import { t } from "@app/locale"
27+
import { getPrimaryTextColor, getSecondaryTextColor } from "@util/style"
2728

2829
type EcOption = ComposeOption<
2930
| BarSeriesOption
@@ -89,9 +90,12 @@ function generateOptions(data: PeriodResult[], averageByDate: boolean, periodSiz
8990
const xAxisMin = periodData[0].startTime.getTime()
9091
const xAxisMax = periodData[periodData.length - 1].endTime.getTime()
9192
const xAxisAxisLabelFormatter = averageByDate ? '{HH}:{mm}' : formatXAxis
93+
const textColor = getPrimaryTextColor()
94+
const secondaryTextColor = getSecondaryTextColor()
9295
return {
9396
title: {
9497
text: TITLE,
98+
textStyle: { color: textColor },
9599
left: 'center'
96100
},
97101
tooltip: {
@@ -105,18 +109,26 @@ function generateOptions(data: PeriodResult[], averageByDate: boolean, periodSiz
105109
title: t(msg => msg.habit.chart.saveAsImageTitle),
106110
name: TITLE, // file name
107111
excludeComponents: ['toolbox'],
108-
pixelRatio: 1
112+
pixelRatio: 1,
113+
iconStyle: {
114+
borderColor: secondaryTextColor
115+
}
109116
}
110117
}
111118
},
112119
xAxis: {
113-
axisLabel: { formatter: xAxisAxisLabelFormatter },
120+
axisLabel: { formatter: xAxisAxisLabelFormatter, color: textColor },
114121
type: 'time',
115122
axisLine: { show: false },
116123
min: xAxisMin,
117124
max: xAxisMax
118125
},
119-
yAxis: { name: Y_AXIAS_NAME, type: 'value' },
126+
yAxis: {
127+
name: Y_AXIAS_NAME,
128+
nameTextStyle: { color: textColor },
129+
type: 'value',
130+
axisLabel: { color: textColor },
131+
},
120132
series: [{
121133
type: "bar",
122134
large: true,

src/app/components/option/common.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function renderOptionItem(input: VNode | { [key: string]: VNode }, label:
3232
* @param text text
3333
*/
3434
export function tagText(text: I18nKey): VNode {
35-
return h('a', { style: { color: '#F56C6C' } }, t(text))
35+
return h('a', { class: 'option-tag' }, t(text))
3636
}
3737

3838
/**
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Copyright (c) 2022 Hengyang Zhang
3+
*
4+
* This software is released under the MIT License.
5+
* https://opensource.org/licenses/MIT
6+
*/
7+
import { Ref, PropType, ComputedRef, watch } from "vue"
8+
9+
import { ElOption, ElSelect, ElTimePicker } from "element-plus"
10+
import { defineComponent, ref, h, computed } from "vue"
11+
import { t } from "@app/locale"
12+
13+
function computeSecondToDate(secondOfDate: number): Date {
14+
const now = new Date()
15+
const hour = Math.floor(secondOfDate / 3600)
16+
const minute = Math.floor((secondOfDate - hour * 3600) / 60)
17+
const second = Math.floor(secondOfDate % 60)
18+
now.setHours(hour)
19+
now.setMinutes(minute)
20+
now.setSeconds(second)
21+
now.setMilliseconds(0)
22+
return now
23+
}
24+
25+
function computeDateToSecond(date: Date) {
26+
const hour = date.getHours()
27+
const minute = date.getMinutes()
28+
const second = date.getSeconds()
29+
return hour * 3600 + minute * 60 + second
30+
}
31+
32+
const _default = defineComponent({
33+
name: "DarkModeInput",
34+
props: {
35+
modelValue: String as PropType<Timer.AppearanceOptionDarkMode>,
36+
startSecond: Number,
37+
endSecond: Number
38+
},
39+
emits: ["change"],
40+
setup(props, ctx) {
41+
const darkMode: Ref<Timer.AppearanceOptionDarkMode> = ref(props.modelValue)
42+
// @ts-ignore
43+
const start: Ref<Date> = ref(computeSecondToDate(props.startSecond))
44+
// @ts-ignore
45+
const end: Ref<Date> = ref(computeSecondToDate(props.endSecond))
46+
watch(() => props.modelValue, newVal => darkMode.value = newVal)
47+
watch(() => props.startSecond, newVal => start.value = computeSecondToDate(newVal))
48+
watch(() => props.endSecond, newVal => end.value = computeSecondToDate(newVal))
49+
const startSecond: ComputedRef<number> = computed(() => computeDateToSecond(start.value))
50+
const endSecond: ComputedRef<number> = computed(() => computeDateToSecond(end.value))
51+
52+
const handleChange = () => ctx.emit("change", darkMode.value, [startSecond.value, endSecond.value])
53+
54+
return () => {
55+
const result = [h(ElSelect, {
56+
modelValue: darkMode.value,
57+
size: 'small',
58+
style: { width: '120px', marginLeft: '10px' },
59+
onChange: async (newVal: string) => {
60+
const before = darkMode.value
61+
darkMode.value = newVal as Timer.AppearanceOptionDarkMode
62+
handleChange()
63+
}
64+
}, {
65+
default: () => ["on", "off", "timed"].map(
66+
value => h(ElOption, { value, label: t(msg => msg.option.appearance.darkMode.options[value]) })
67+
)
68+
})]
69+
if (darkMode.value === "timed") {
70+
result.push(
71+
h(ElTimePicker, {
72+
modelValue: start.value,
73+
size: "small",
74+
style: { marginLeft: '10px' },
75+
"onUpdate:modelValue": (newVal) => {
76+
start.value = newVal
77+
handleChange()
78+
},
79+
clearable: false
80+
}),
81+
h('a', '-'),
82+
h(ElTimePicker, {
83+
modelValue: end.value,
84+
size: "small",
85+
"onUpdate:modelValue": (newVal) => {
86+
end.value = newVal
87+
handleChange()
88+
},
89+
clearable: false
90+
})
91+
)
92+
}
93+
return result
94+
}
95+
}
96+
})
97+
98+
export default _default

0 commit comments

Comments
 (0)