diff --git a/global.d.ts b/global.d.ts
index f5552e47c..fd3305f68 100644
--- a/global.d.ts
+++ b/global.d.ts
@@ -33,6 +33,13 @@ declare namespace Timer {
displaySiteName: boolean
}
+ type AppearanceOptionDarkMode =
+ // Always on
+ | "on"
+ // Always off
+ | "off"
+ // Timed on
+ | "timed"
type AppearanceOption = {
/**
* Whether to display the whitelist button in the context menu
@@ -58,6 +65,20 @@ declare namespace Timer {
* @since 0.8.6
*/
printInConsole: boolean
+ /**
+ * The state of dark mode
+ *
+ * @since 1.1.0
+ */
+ darkMode: AppearanceOptionDarkMode
+
+ /**
+ * The range of seconds to turn on dark mode. Required if {@param darkMode} is 'timed'
+ *
+ * @since 1.1.0
+ */
+ darkModeTimeStart?: number
+ darkModeTimeEnd?: number
}
type StatisticsOption = {
diff --git a/public/popup.html b/public/popup.html
index f6967c29c..0af46d1b6 100644
--- a/public/popup.html
+++ b/public/popup.html
@@ -15,14 +15,18 @@
diff --git a/src/app/components/dashboard/components/calendar-heat-map.ts b/src/app/components/dashboard/components/calendar-heat-map.ts
index 6f480a1b1..64de1e821 100644
--- a/src/app/components/dashboard/components/calendar-heat-map.ts
+++ b/src/app/components/dashboard/components/calendar-heat-map.ts
@@ -36,6 +36,7 @@ import { ElLoading } from "element-plus"
import { defineComponent, h, onMounted, ref, Ref } from "vue"
import { groupBy, rotate } from "@util/array"
import { BASE_TITLE_OPTION } from "../common"
+import { getPrimaryTextColor } from "@util/style"
const WEEK_NUM = 53
@@ -110,6 +111,7 @@ function optionOf(data: _Value[], days: string[]): EcOption {
const totalMinutes = data.map(d => d[2] || 0).reduce((a, b) => a + b, 0)
const totalHours = Math.floor(totalMinutes / 60)
const xAxisLabelMap = getXAxisLabelMap(data)
+ const textColor = getPrimaryTextColor()
return {
title: {
...BASE_TITLE_OPTION,
@@ -117,7 +119,10 @@ function optionOf(data: _Value[], days: string[]): EcOption {
? msg.dashboard.heatMap.title0
: msg.dashboard.heatMap.title1,
{ hour: totalHours }
- )
+ ),
+ textStyle: {
+ color: textColor
+ }
},
tooltip: {
position: 'top',
@@ -137,12 +142,16 @@ function optionOf(data: _Value[], days: string[]): EcOption {
formatter: (x: string) => xAxisLabelMap[x] || '',
interval: 0,
margin: 14,
+ color: textColor
},
},
yAxis: {
type: 'category',
data: days,
- axisLabel: { padding: /* T R B L */[0, 12, 0, 0] },
+ axisLabel: {
+ padding: /* T R B L */[0, 12, 0, 0],
+ color: textColor
+ },
axisLine: { show: false },
axisTick: { show: false, alignWithLabel: true }
},
@@ -155,7 +164,10 @@ function optionOf(data: _Value[], days: string[]): EcOption {
orient: 'vertical',
right: '2%',
top: 'center',
- dimension: 2
+ dimension: 2,
+ textStyle: {
+ color: textColor
+ }
}],
series: [{
name: 'Daily Focus',
@@ -163,11 +175,10 @@ function optionOf(data: _Value[], days: string[]): EcOption {
data: data.map(d => {
let item = { value: d, itemStyle: undefined, label: undefined, emphasis: undefined, tooltip: undefined, silent: false }
const minutes = d[2]
- const date = d[3]
if (minutes) {
} else {
item.itemStyle = {
- color: '#fff',
+ color: 'transparent',
}
item.emphasis = {
disabled: true
diff --git a/src/app/components/dashboard/components/top-k-visit.ts b/src/app/components/dashboard/components/top-k-visit.ts
index e693852ab..96dfe2591 100644
--- a/src/app/components/dashboard/components/top-k-visit.ts
+++ b/src/app/components/dashboard/components/top-k-visit.ts
@@ -8,6 +8,8 @@
import type { ECharts, ComposeOption } from "echarts/core"
import type { PieSeriesOption } from "echarts/charts"
import type { TitleComponentOption, TooltipComponentOption } from "echarts/components"
+import type { Ref } from "vue"
+import type { TimerQueryParam } from "@service/timer-service"
import { init, use } from "@echarts/core"
import PieChart from "@echarts/chart/pie"
@@ -16,13 +18,14 @@ import TooltipComponent from "@echarts/component/tooltip"
use([PieChart, TitleComponent, TooltipComponent])
-import timerService, { SortDirect, TimerQueryParam } from "@service/timer-service"
+import timerService, { SortDirect } from "@service/timer-service"
import { MILL_PER_DAY } from "@util/time"
import { ElLoading } from "element-plus"
-import { defineComponent, h, onMounted, ref, Ref } from "vue"
+import { defineComponent, h, onMounted, ref } from "vue"
import DataItem from "@entity/dto/data-item"
import { BASE_TITLE_OPTION } from "../common"
import { t } from "@app/locale"
+import { getPrimaryTextColor } from "@util/style"
const CONTAINER_ID = '__timer_dashboard_top_k_visit'
const TOP_NUM = 6
@@ -40,10 +43,12 @@ type _Value = {
}
function optionOf(data: _Value[]): EcOption {
+ const textColor = getPrimaryTextColor()
return {
title: {
...BASE_TITLE_OPTION,
- text: t(msg => msg.dashboard.topK.title, { k: TOP_NUM, day: DAY_NUM })
+ text: t(msg => msg.dashboard.topK.title, { k: TOP_NUM, day: DAY_NUM }),
+ textStyle: { color: textColor }
},
tooltip: {
show: true,
@@ -64,6 +69,7 @@ function optionOf(data: _Value[]): EcOption {
itemStyle: {
borderRadius: 7
},
+ label: { color: textColor },
data: data
}
}
diff --git a/src/app/components/dashboard/components/week-on-week.ts b/src/app/components/dashboard/components/week-on-week.ts
index 1c011f30a..8d0aec858 100644
--- a/src/app/components/dashboard/components/week-on-week.ts
+++ b/src/app/components/dashboard/components/week-on-week.ts
@@ -26,6 +26,7 @@ import DataItem from "@entity/dto/data-item"
import { groupBy, sum } from "@util/array"
import { BASE_TITLE_OPTION } from "../common"
import { t } from "@app/locale"
+import { getPrimaryTextColor } from "@util/style"
type EcOption = ComposeOption<
| CandlestickSeriesOption
@@ -48,6 +49,7 @@ type _Value = {
}
function optionOf(lastPeriodItems: DataItem[], thisPeriodItems: DataItem[]): EcOption {
+ const textColor = getPrimaryTextColor()
const lastPeriodMap: { [host: string]: number } = groupBy(lastPeriodItems,
item => item.host,
grouped => Math.floor(sum(grouped.map(item => item.focus)) / 1000)
@@ -113,15 +115,18 @@ function optionOf(lastPeriodItems: DataItem[], thisPeriodItems: DataItem[]): EcO
},
xAxis: {
type: 'category',
- name: 'Seconds',
splitLine: { show: false },
data: topK.map(a => a.host),
axisLabel: {
- interval: 0
+ interval: 0,
+ color: textColor,
},
},
yAxis: {
type: 'value',
+ axisLabel: {
+ color: textColor,
+ }
},
series: [{
type: 'candlestick',
diff --git a/src/app/components/dashboard/feedback.ts b/src/app/components/dashboard/feedback.ts
index d1d78b50d..84b8038d9 100644
--- a/src/app/components/dashboard/feedback.ts
+++ b/src/app/components/dashboard/feedback.ts
@@ -25,7 +25,7 @@ const _default = defineComponent({
}, h(ElTooltip, {
placement: 'top',
content: t(msg => msg.dashboard.feedback.tooltip),
- effect: Effect.LIGHT,
+ effect: Effect.DARK,
}, () => h(ElButton, {
type: "info",
size: 'small',
diff --git a/src/app/components/habit/component/chart/wrapper.ts b/src/app/components/habit/component/chart/wrapper.ts
index d59a97354..f6e633ebd 100644
--- a/src/app/components/habit/component/chart/wrapper.ts
+++ b/src/app/components/habit/component/chart/wrapper.ts
@@ -24,6 +24,7 @@ import { PeriodKey, PERIODS_PER_DATE } from "@entity/dto/period-info"
import PeriodResult from "@entity/dto/period-result"
import { formatPeriodCommon, formatTime, MILL_PER_DAY } from "@util/time"
import { t } from "@app/locale"
+import { getPrimaryTextColor, getSecondaryTextColor } from "@util/style"
type EcOption = ComposeOption<
| BarSeriesOption
@@ -89,9 +90,12 @@ function generateOptions(data: PeriodResult[], averageByDate: boolean, periodSiz
const xAxisMin = periodData[0].startTime.getTime()
const xAxisMax = periodData[periodData.length - 1].endTime.getTime()
const xAxisAxisLabelFormatter = averageByDate ? '{HH}:{mm}' : formatXAxis
+ const textColor = getPrimaryTextColor()
+ const secondaryTextColor = getSecondaryTextColor()
return {
title: {
text: TITLE,
+ textStyle: { color: textColor },
left: 'center'
},
tooltip: {
@@ -105,18 +109,26 @@ function generateOptions(data: PeriodResult[], averageByDate: boolean, periodSiz
title: t(msg => msg.habit.chart.saveAsImageTitle),
name: TITLE, // file name
excludeComponents: ['toolbox'],
- pixelRatio: 1
+ pixelRatio: 1,
+ iconStyle: {
+ borderColor: secondaryTextColor
+ }
}
}
},
xAxis: {
- axisLabel: { formatter: xAxisAxisLabelFormatter },
+ axisLabel: { formatter: xAxisAxisLabelFormatter, color: textColor },
type: 'time',
axisLine: { show: false },
min: xAxisMin,
max: xAxisMax
},
- yAxis: { name: Y_AXIAS_NAME, type: 'value' },
+ yAxis: {
+ name: Y_AXIAS_NAME,
+ nameTextStyle: { color: textColor },
+ type: 'value',
+ axisLabel: { color: textColor },
+ },
series: [{
type: "bar",
large: true,
diff --git a/src/app/components/option/common.ts b/src/app/components/option/common.ts
index 53059d5a5..57c6faeef 100644
--- a/src/app/components/option/common.ts
+++ b/src/app/components/option/common.ts
@@ -32,7 +32,7 @@ export function renderOptionItem(input: VNode | { [key: string]: VNode }, label:
* @param text text
*/
export function tagText(text: I18nKey): VNode {
- return h('a', { style: { color: '#F56C6C' } }, t(text))
+ return h('a', { class: 'option-tag' }, t(text))
}
/**
diff --git a/src/app/components/option/components/appearance/dark-mode-input.ts b/src/app/components/option/components/appearance/dark-mode-input.ts
new file mode 100644
index 000000000..5c45e5be0
--- /dev/null
+++ b/src/app/components/option/components/appearance/dark-mode-input.ts
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2022 Hengyang Zhang
+ *
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+import { Ref, PropType, ComputedRef, watch } from "vue"
+
+import { ElOption, ElSelect, ElTimePicker } from "element-plus"
+import { defineComponent, ref, h, computed } from "vue"
+import { t } from "@app/locale"
+
+function computeSecondToDate(secondOfDate: number): Date {
+ const now = new Date()
+ const hour = Math.floor(secondOfDate / 3600)
+ const minute = Math.floor((secondOfDate - hour * 3600) / 60)
+ const second = Math.floor(secondOfDate % 60)
+ now.setHours(hour)
+ now.setMinutes(minute)
+ now.setSeconds(second)
+ now.setMilliseconds(0)
+ return now
+}
+
+function computeDateToSecond(date: Date) {
+ const hour = date.getHours()
+ const minute = date.getMinutes()
+ const second = date.getSeconds()
+ return hour * 3600 + minute * 60 + second
+}
+
+const _default = defineComponent({
+ name: "DarkModeInput",
+ props: {
+ modelValue: String as PropType,
+ startSecond: Number,
+ endSecond: Number
+ },
+ emits: ["change"],
+ setup(props, ctx) {
+ const darkMode: Ref = ref(props.modelValue)
+ // @ts-ignore
+ const start: Ref = ref(computeSecondToDate(props.startSecond))
+ // @ts-ignore
+ const end: Ref = ref(computeSecondToDate(props.endSecond))
+ watch(() => props.modelValue, newVal => darkMode.value = newVal)
+ watch(() => props.startSecond, newVal => start.value = computeSecondToDate(newVal))
+ watch(() => props.endSecond, newVal => end.value = computeSecondToDate(newVal))
+ const startSecond: ComputedRef = computed(() => computeDateToSecond(start.value))
+ const endSecond: ComputedRef = computed(() => computeDateToSecond(end.value))
+
+ const handleChange = () => ctx.emit("change", darkMode.value, [startSecond.value, endSecond.value])
+
+ return () => {
+ const result = [h(ElSelect, {
+ modelValue: darkMode.value,
+ size: 'small',
+ style: { width: '120px', marginLeft: '10px' },
+ onChange: async (newVal: string) => {
+ const before = darkMode.value
+ darkMode.value = newVal as Timer.AppearanceOptionDarkMode
+ handleChange()
+ }
+ }, {
+ default: () => ["on", "off", "timed"].map(
+ value => h(ElOption, { value, label: t(msg => msg.option.appearance.darkMode.options[value]) })
+ )
+ })]
+ if (darkMode.value === "timed") {
+ result.push(
+ h(ElTimePicker, {
+ modelValue: start.value,
+ size: "small",
+ style: { marginLeft: '10px' },
+ "onUpdate:modelValue": (newVal) => {
+ start.value = newVal
+ handleChange()
+ },
+ clearable: false
+ }),
+ h('a', '-'),
+ h(ElTimePicker, {
+ modelValue: end.value,
+ size: "small",
+ "onUpdate:modelValue": (newVal) => {
+ end.value = newVal
+ handleChange()
+ },
+ clearable: false
+ })
+ )
+ }
+ return result
+ }
+ }
+})
+
+export default _default
\ No newline at end of file
diff --git a/src/app/components/option/components/appearance.ts b/src/app/components/option/components/appearance/index.ts
similarity index 79%
rename from src/app/components/option/components/appearance.ts
rename to src/app/components/option/components/appearance/index.ts
index a30bca466..f80b1173e 100644
--- a/src/app/components/option/components/appearance.ts
+++ b/src/app/components/option/components/appearance/index.ts
@@ -5,15 +5,19 @@
* https://opensource.org/licenses/MIT
*/
+import type { Ref } from "vue"
+
import { ElDivider, ElIcon, ElMessageBox, ElOption, ElSelect, ElSwitch, ElTooltip } from "element-plus"
-import { defineComponent, h, Ref, ref } from "vue"
+import { defineComponent, h, ref } from "vue"
import optionService from "@service/option-service"
import { defaultAppearance } from "@util/constant/option"
+import DarkModeInput from "./dark-mode-input"
import { t, tWith } from "@app/locale"
-import { renderOptionItem, tagText } from "../common"
+import { renderOptionItem, tagText } from "../../common"
import localeMessages from "@util/i18n/components/locale"
import { InfoFilled } from "@element-plus/icons-vue"
import { localeSameAsBrowser } from "@util/i18n"
+import { toggle } from "@util/dark-mode"
const displayWhitelist = (option: Ref) => h(ElSwitch, {
modelValue: option.value.displayWhitelistMenu,
@@ -81,14 +85,35 @@ const _default = defineComponent({
name: "AppearanceOptionContainer",
setup(_props, ctx) {
const option: Ref = ref(defaultAppearance())
- optionService.getAllOption().then(currentVal => option.value = currentVal)
+ optionService.getAllOption().then(currentVal => {
+ option.value = currentVal
+ console.log(option.value)
+ })
ctx.expose({
async reset() {
option.value = defaultAppearance()
await optionService.setAppearanceOption(option.value)
+ toggle(await optionService.isDarkMode(option.value))
}
})
return () => h('div', [
+ renderOptionItem({
+ input: h(DarkModeInput, {
+ modelValue: option.value.darkMode,
+ startSecond: option.value.darkModeTimeStart,
+ endSecond: option.value.darkModeTimeEnd,
+ onChange: async (darkMode, range) => {
+ option.value.darkMode = darkMode
+ option.value.darkModeTimeStart = range?.[0]
+ option.value.darkModeTimeEnd = range?.[1]
+ await optionService.setAppearanceOption(option.value)
+ toggle(await optionService.isDarkMode())
+ }
+ })
+ },
+ msg => msg.appearance.darkMode.label,
+ t(msg => msg.option.appearance.darkMode.options["off"])),
+ h(ElDivider),
renderOptionItem({
input: locale(option),
info: h(ElTooltip, {}, {
diff --git a/src/app/components/option/style/index.sass b/src/app/components/option/style/index.sass
index ed5965ae2..350216e8b 100644
--- a/src/app/components/option/style/index.sass
+++ b/src/app/components/option/style/index.sass
@@ -23,6 +23,9 @@
margin: 22px 10px 10px 10px
font-size: 14px
.option-line
+ display: flex
+ justify-content: space-between
+ align-items: center
height: 30px
line-height: 30px
.el-input--small
@@ -30,9 +33,29 @@
.el-input__wrapper
height: 26px
.option-label
- float: left
+ display: inline-flex
+ align-items: center
+ color: var(--el-text-color-primary)
+ i
+ margin: 0 2px
+ .el-input-number,.el-select,.el-date-editor--time
+ margin: 0 3px
+ .el-select
+ display: inline-flex
+ .el-date-editor--time
+ width: 100px
+ .el-input__prefix
+ width: 16px
+ margin-left: 5px
+ .el-switch
+ margin-right: 6px
+ .option-tag
+ color: #F56C6C
+ margin: 0 3px
.option-default
- float: right
+ display: inline-flex
+ align-items: center
+ color: var(--el-text-color-primary)
.el-tag
height: 20px
.option-container>span
diff --git a/src/app/components/report/styles/element.sass b/src/app/components/report/styles/element.sass
index 5cd419b38..5d3469f68 100644
--- a/src/app/components/report/styles/element.sass
+++ b/src/app/components/report/styles/element.sass
@@ -10,8 +10,8 @@
.batch-delete-button
margin-right: 20px
- height: 41px !important
- margin-top: 2px
+ padding-top: 8px !important
+ display: inline-flex
.export-dropdown
@@ -32,7 +32,11 @@ body .el-table th.gutter
body .el-table colgroup.gutter
display: table-cell!important
+$inputHeight: 28px
+
.el-table__cell .cell
+ .el-input
+ height: $inputHeight
.edit-btn
cursor: pointer
line-height: 17px
@@ -48,6 +52,9 @@ body .el-table colgroup.gutter
margin-left: 0px
padding-left: 4px
padding-right: 2px
- .el-input-group--append .el-input__inner
- padding-left: 5px
- padding-right: 5px
+ .el-input-group--append
+ .el-input__wrapper
+ height: $inputHeight
+ .el-input__inner
+ padding-left: 5px
+ padding-right: 5px
diff --git a/src/app/components/report/table/columns/operation.ts b/src/app/components/report/table/columns/operation.ts
index 869c87876..762318696 100644
--- a/src/app/components/report/table/columns/operation.ts
+++ b/src/app/components/report/table/columns/operation.ts
@@ -60,8 +60,7 @@ const _default = defineComponent({
return () => h(ElTableColumn, {
width: width.value,
label: columnLabel,
- align: "center",
- fixed: "right"
+ align: "center"
}, {
default: ({ row }: { row: DataItem }) => [
// Trend
diff --git a/src/app/components/site-manage/table/column/operation.ts b/src/app/components/site-manage/table/column/operation.ts
index 58fef18f4..7c17704da 100644
--- a/src/app/components/site-manage/table/column/operation.ts
+++ b/src/app/components/site-manage/table/column/operation.ts
@@ -40,7 +40,6 @@ const _default = defineComponent({
minWidth: 100,
label,
align: 'center',
- fixed: 'right'
}, {
default: ({ row }: { row: HostAliasInfo }) => [
modifyButton(ctx, row),
diff --git a/src/app/components/trend/components/chart/wrapper.ts b/src/app/components/trend/components/chart/wrapper.ts
index a8054b96b..09ef12cae 100644
--- a/src/app/components/trend/components/chart/wrapper.ts
+++ b/src/app/components/trend/components/chart/wrapper.ts
@@ -9,6 +9,7 @@ import type { ECharts, ComposeOption } from "echarts/core"
import type { LineSeriesOption } from "echarts/charts"
import type {
GraphicComponentOption,
+ GridComponentOption,
LegendComponentOption,
TitleComponentOption,
TooltipComponentOption,
@@ -30,6 +31,7 @@ import HostOptionInfo from "../../host-option-info"
import DataItem from "@entity/dto/data-item"
import hostAliasService from "@service/host-alias-service"
import HostAlias from "@entity/dao/host-alias"
+import { getPrimaryTextColor, getSecondaryTextColor } from "@util/style"
use([
LineChart,
@@ -44,6 +46,7 @@ use([
type EcOption = ComposeOption<
| LineSeriesOption
| GraphicComponentOption
+ | GridComponentOption
| LegendComponentOption
| TitleComponentOption
| ToolboxComponentOption
@@ -70,10 +73,18 @@ function optionOf(
subtext: string,
[focusData, totalData, timeData]: number[][]
) {
+ const textColor = getPrimaryTextColor()
+ const secondaryTextColor = getSecondaryTextColor()
const option: EcOption = {
backgroundColor: 'rgba(0,0,0,0)',
grid: { top: '100' },
- title: { text: TITLE, subtext, left: 'center' },
+ title: {
+ text: TITLE,
+ textStyle: { color: textColor },
+ subtext,
+ subtextStyle: { color: secondaryTextColor },
+ left: 'center',
+ },
tooltip: { trigger: 'item' },
toolbox: {
feature: {
@@ -82,18 +93,36 @@ function optionOf(
title: SAVE_AS_IMAGE,
excludeComponents: ['toolbox'],
pixelRatio: 1,
- backgroundColor: '#fff'
+ backgroundColor: '#fff',
+ iconStyle: {
+ borderColor: secondaryTextColor
+ }
}
}
},
- xAxis: { type: 'category', data: xAxisData },
+ xAxis: {
+ type: 'category',
+ data: xAxisData,
+ axisLabel: { color: textColor },
+ },
yAxis: [
- { name: TIME_UNIT, type: 'value' },
- { name: NUMBER_UNIT, type: 'value' }
+ {
+ name: TIME_UNIT,
+ nameTextStyle: { color: textColor },
+ type: 'value',
+ axisLabel: { color: textColor },
+ },
+ {
+ name: NUMBER_UNIT,
+ nameTextStyle: { color: textColor },
+ type: 'value',
+ axisLabel: { color: textColor },
+ }
],
legend: {
left: 'left',
- data: [t(msg => msg.item.total), t(msg => msg.item.focus), t(msg => msg.item.time)]
+ data: [t(msg => msg.item.total), t(msg => msg.item.focus), t(msg => msg.item.time)],
+ textStyle: { color: textColor },
},
series: [{
// run time
diff --git a/src/app/index.ts b/src/app/index.ts
index 48996b15e..3c8583411 100644
--- a/src/app/index.ts
+++ b/src/app/index.ts
@@ -5,16 +5,19 @@
* https://opensource.org/licenses/MIT
*/
-import { App, createApp } from "vue"
+import type { App } from "vue"
+import type { Language } from "element-plus/lib/locale"
+
+import { createApp } from "vue"
import Main from "./layout"
import 'element-plus/theme-chalk/index.css'
import './styles' // global css
-import './styles/compatible'
import installRouter from "./router"
import '../common/timer'
import ElementPlus from 'element-plus'
import { initLocale, locale as appLocale } from "@util/i18n"
-import { Language } from "element-plus/lib/locale"
+import { toggle, init as initTheme } from "@util/dark-mode"
+import optionService from "@service/option-service"
const locales: { [locale in Timer.Locale]: () => Promise<{ default: Language }> } = {
zh_CN: () => import('element-plus/lib/locale/lang/zh-cn'),
@@ -24,6 +27,10 @@ const locales: { [locale in Timer.Locale]: () => Promise<{ default: Language }>
}
async function main() {
+ // Init theme with cache first
+ initTheme()
+ // Calculate the latest mode
+ optionService.isDarkMode().then(toggle)
await initLocale()
const app: App = createApp(Main)
installRouter(app)
diff --git a/src/app/locale/components/faq.ts b/src/app/locale/components/faq.ts
new file mode 100644
index 000000000..e6d1e2310
--- /dev/null
+++ b/src/app/locale/components/faq.ts
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2022 Hengyang Zhang
+ *
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+import { Messages } from "@util/i18n"
+
+
+export type FaqMessage = {
+ usage: {
+ q: string
+ a: string
+ }
+}
+
+const _default: Messages = {
+ zh_CN: {
+ usage: {
+ q: 'Test question',
+ a: ''
+ }
+ },
+ zh_TW: {
+ usage: {
+ q: '',
+ a: ''
+ }
+ },
+ en: {
+ usage: {
+ q: '',
+ a: ''
+ }
+ },
+ ja: {
+ usage: {
+ q: '',
+ a: ''
+ }
+ }
+}
+
+export default _default
\ No newline at end of file
diff --git a/src/app/locale/components/menu.ts b/src/app/locale/components/menu.ts
index 27cd768de..b90b9066e 100644
--- a/src/app/locale/components/menu.ts
+++ b/src/app/locale/components/menu.ts
@@ -22,6 +22,7 @@ export type MenuMessage = {
mergeRule: string
option: string
other: string
+ faq: string
feedback: string
rate: string
meat: string
@@ -43,6 +44,7 @@ const _default: Messages = {
habit: '上网习惯',
limit: '每日时限设置',
other: '其他',
+ faq: 'F & Q',
feedback: '有什么反馈吗?',
rate: '打个分吧!',
meat: '请作者吃饭~',
@@ -63,6 +65,7 @@ const _default: Messages = {
habit: '上網習慣',
limit: '每日時限設置',
other: '其他',
+ faq: 'F & Q',
feedback: '有什麼反饋嗎?',
rate: '打個分吧!',
meat: '請作者吃飯~',
@@ -82,6 +85,7 @@ const _default: Messages = {
whitelist: 'Whitelist',
mergeRule: 'Merge-site Rules',
other: 'Other Features',
+ faq: 'F & Q',
option: 'Options',
feedback: 'Feedback Questionnaire',
rate: 'Rate It',
@@ -102,6 +106,7 @@ const _default: Messages = {
whitelist: 'Webホワイトリスト',
mergeRule: 'ドメイン合併',
other: 'その他の機能',
+ faq: 'F & Q',
option: '拡張設定',
feedback: 'フィードバックアンケート',
rate: 'それを評価',
diff --git a/src/app/locale/components/option.ts b/src/app/locale/components/option.ts
index 176896363..543176fbf 100644
--- a/src/app/locale/components/option.ts
+++ b/src/app/locale/components/option.ts
@@ -41,6 +41,14 @@ export type OptionMessage = {
label: string
console: string
info: string
+ },
+ darkMode: {
+ label: string
+ options: {
+ on: string
+ off: string
+ timed: string
+ }
}
}
statistics: {
@@ -92,6 +100,14 @@ const _default: Messages = {
label: '{input} 是否在 {console} 里打印当前网站的 {info}',
console: '浏览器的控制台',
info: '今日访问信息'
+ },
+ darkMode: {
+ label: "夜间模式 {input}",
+ options: {
+ on: "始终开启",
+ off: "始终关闭",
+ timed: "定时开启"
+ }
}
},
statistics: {
@@ -141,6 +157,14 @@ const _default: Messages = {
label: '{input} 是否在 {console} 裡打印當前網站的 {info}',
console: '瀏覽器的控製颱',
info: '今日訪問信息'
+ },
+ darkMode: {
+ label: "深色主題 {input}",
+ options: {
+ on: "始終開啟",
+ off: "始終關閉",
+ timed: "定時開啟"
+ }
}
},
statistics: {
@@ -190,6 +214,14 @@ const _default: Messages = {
label: '{input} Whether to print {info} in the {console}',
console: 'console',
info: 'the visit count of the current website today'
+ },
+ darkMode: {
+ label: "Dark mode {info} {input}",
+ options: {
+ on: "Always on",
+ off: "Always off",
+ timed: "Timed on"
+ }
}
},
statistics: {
@@ -240,7 +272,15 @@ const _default: Messages = {
label: '{input} 現在のウェブサイトの {info} を {console} に印刷するかどうか',
console: 'コンソール',
info: '今日の情報をご覧ください'
- }
+ },
+ darkMode: {
+ label: "ダークモード {info} {input}",
+ options: {
+ on: "常にオン",
+ off: "常にオフ",
+ timed: "時限スタート"
+ }
+ },
},
statistics: {
title: '統計',
diff --git a/src/app/locale/messages.ts b/src/app/locale/messages.ts
index 694c46706..5c418ed5b 100644
--- a/src/app/locale/messages.ts
+++ b/src/app/locale/messages.ts
@@ -23,92 +23,92 @@ import dashboardMessages, { DashboardMessage } from "./components/dashboard"
import calendarMessages, { CalendarMessage } from "@util/i18n/components/calendar"
export type AppMessage = {
- dataManage: DataManageMessage
- item: ItemMessage
- report: ReportMessage
- whitelist: WhitelistMessage
- mergeRule: MergeRuleMessage
- option: OptionMessage
- trend: TrendMessage
- menu: MenuMessage
- habit: HabitMessage
- limit: LimitMessage
- siteManage: SiteManageMessage
- operation: OperationMessage
- confirm: ConfirmMessage
- dashboard: DashboardMessage
- calendar: CalendarMessage
+ dataManage: DataManageMessage
+ item: ItemMessage
+ report: ReportMessage
+ whitelist: WhitelistMessage
+ mergeRule: MergeRuleMessage
+ option: OptionMessage
+ trend: TrendMessage
+ menu: MenuMessage
+ habit: HabitMessage
+ limit: LimitMessage
+ siteManage: SiteManageMessage
+ operation: OperationMessage
+ confirm: ConfirmMessage
+ dashboard: DashboardMessage
+ calendar: CalendarMessage
}
const _default: Messages = {
- zh_CN: {
- dataManage: dataManageMessages.zh_CN,
- item: itemMessages.zh_CN,
- report: reportMessages.zh_CN,
- whitelist: whitelistMessages.zh_CN,
- mergeRule: mergeRuleMessages.zh_CN,
- option: optionMessages.zh_CN,
- trend: trendMessages.zh_CN,
- menu: menuMessages.zh_CN,
- habit: habitMessages.zh_CN,
- limit: limitMessages.zh_CN,
- siteManage: siteManageManages.zh_CN,
- operation: operationMessages.zh_CN,
- confirm: confirmMessages.zh_CN,
- dashboard: dashboardMessages.zh_CN,
- calendar: calendarMessages.zh_CN,
- },
- zh_TW: {
- dataManage: dataManageMessages.zh_TW,
- item: itemMessages.zh_TW,
- report: reportMessages.zh_TW,
- whitelist: whitelistMessages.zh_TW,
- mergeRule: mergeRuleMessages.zh_TW,
- option: optionMessages.zh_TW,
- trend: trendMessages.zh_TW,
- menu: menuMessages.zh_TW,
- habit: habitMessages.zh_TW,
- limit: limitMessages.zh_TW,
- siteManage: siteManageManages.zh_TW,
- operation: operationMessages.zh_TW,
- confirm: confirmMessages.zh_TW,
- dashboard: dashboardMessages.zh_TW,
- calendar: calendarMessages.zh_TW,
- },
- en: {
- dataManage: dataManageMessages.en,
- item: itemMessages.en,
- report: reportMessages.en,
- whitelist: whitelistMessages.en,
- mergeRule: mergeRuleMessages.en,
- option: optionMessages.en,
- trend: trendMessages.en,
- menu: menuMessages.en,
- habit: habitMessages.en,
- limit: limitMessages.en,
- siteManage: siteManageManages.en,
- operation: operationMessages.en,
- confirm: confirmMessages.en,
- dashboard: dashboardMessages.en,
- calendar: calendarMessages.en,
- },
- ja: {
- dataManage: dataManageMessages.ja,
- item: itemMessages.ja,
- report: reportMessages.ja,
- whitelist: whitelistMessages.ja,
- mergeRule: mergeRuleMessages.ja,
- option: optionMessages.ja,
- trend: trendMessages.ja,
- menu: menuMessages.ja,
- habit: habitMessages.ja,
- limit: limitMessages.ja,
- siteManage: siteManageManages.ja,
- operation: operationMessages.ja,
- confirm: confirmMessages.ja,
- dashboard: dashboardMessages.ja,
- calendar: calendarMessages.ja,
- }
+ zh_CN: {
+ dataManage: dataManageMessages.zh_CN,
+ item: itemMessages.zh_CN,
+ report: reportMessages.zh_CN,
+ whitelist: whitelistMessages.zh_CN,
+ mergeRule: mergeRuleMessages.zh_CN,
+ option: optionMessages.zh_CN,
+ trend: trendMessages.zh_CN,
+ menu: menuMessages.zh_CN,
+ habit: habitMessages.zh_CN,
+ limit: limitMessages.zh_CN,
+ siteManage: siteManageManages.zh_CN,
+ operation: operationMessages.zh_CN,
+ confirm: confirmMessages.zh_CN,
+ dashboard: dashboardMessages.zh_CN,
+ calendar: calendarMessages.zh_CN,
+ },
+ zh_TW: {
+ dataManage: dataManageMessages.zh_TW,
+ item: itemMessages.zh_TW,
+ report: reportMessages.zh_TW,
+ whitelist: whitelistMessages.zh_TW,
+ mergeRule: mergeRuleMessages.zh_TW,
+ option: optionMessages.zh_TW,
+ trend: trendMessages.zh_TW,
+ menu: menuMessages.zh_TW,
+ habit: habitMessages.zh_TW,
+ limit: limitMessages.zh_TW,
+ siteManage: siteManageManages.zh_TW,
+ operation: operationMessages.zh_TW,
+ confirm: confirmMessages.zh_TW,
+ dashboard: dashboardMessages.zh_TW,
+ calendar: calendarMessages.zh_TW,
+ },
+ en: {
+ dataManage: dataManageMessages.en,
+ item: itemMessages.en,
+ report: reportMessages.en,
+ whitelist: whitelistMessages.en,
+ mergeRule: mergeRuleMessages.en,
+ option: optionMessages.en,
+ trend: trendMessages.en,
+ menu: menuMessages.en,
+ habit: habitMessages.en,
+ limit: limitMessages.en,
+ siteManage: siteManageManages.en,
+ operation: operationMessages.en,
+ confirm: confirmMessages.en,
+ dashboard: dashboardMessages.en,
+ calendar: calendarMessages.en,
+ },
+ ja: {
+ dataManage: dataManageMessages.ja,
+ item: itemMessages.ja,
+ report: reportMessages.ja,
+ whitelist: whitelistMessages.ja,
+ mergeRule: mergeRuleMessages.ja,
+ option: optionMessages.ja,
+ trend: trendMessages.ja,
+ menu: menuMessages.ja,
+ habit: habitMessages.ja,
+ limit: limitMessages.ja,
+ siteManage: siteManageManages.ja,
+ operation: operationMessages.ja,
+ confirm: confirmMessages.ja,
+ dashboard: dashboardMessages.ja,
+ calendar: calendarMessages.ja,
+ }
}
export default _default
\ No newline at end of file
diff --git a/src/app/router/index.ts b/src/app/router/index.ts
index 9ebb79213..b28636ca7 100644
--- a/src/app/router/index.ts
+++ b/src/app/router/index.ts
@@ -5,8 +5,10 @@
* https://opensource.org/licenses/MIT
*/
-import { App } from "vue"
-import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router"
+import type { App } from "vue"
+import type { RouteRecordRaw } from "vue-router"
+
+import { createRouter, createWebHashHistory } from "vue-router"
import { OPTION_ROUTE, TREND_ROUTE, LIMIT_ROUTE, REPORT_ROUTE } from "./constants"
import metaService from "@service/meta-service"
@@ -64,11 +66,14 @@ const additionalRoutes: RouteRecordRaw[] = [
}
]
+const otherRoutes: RouteRecordRaw[] = []
+
const routes: RouteRecordRaw[] = [
{ path: '/', redirect: '/data' },
...dataRoutes,
...behaviorRoutes,
- ...additionalRoutes
+ ...additionalRoutes,
+ ...otherRoutes,
]
const router = createRouter({
diff --git a/src/app/styles/compatible.sass b/src/app/styles/compatible.sass
index 3ce165107..03df3b636 100644
--- a/src/app/styles/compatible.sass
+++ b/src/app/styles/compatible.sass
@@ -5,12 +5,11 @@
* https://opensource.org/licenses/MIT
*/
-// Compatible for element-ui
+// Compatible element-plus 2.x.x for element-ui
+
.el-button--small
min-height: 28px
padding: 7px 15px
-.el-input
- line-height: var(--el-input-height)
.el-input__wrapper
height: var(--el-input-inner-height)
diff --git a/src/app/styles/dark-theme.sass b/src/app/styles/dark-theme.sass
new file mode 100644
index 000000000..8420bc3fb
--- /dev/null
+++ b/src/app/styles/dark-theme.sass
@@ -0,0 +1,78 @@
+html[data-theme='dark']:root
+ --el-color-primary: #409eff
+ --el-color-primary-light-3: #3375b9
+ --el-color-primary-light-5: #2a598a
+ --el-color-primary-light-7: #213d5b
+ --el-color-primary-light-8: #1d3043
+ --el-color-primary-light-9: #18222c
+ --el-color-primary-dark-2: #66b1ff
+ --el-color-success: #67c23a
+ --el-color-success-light-3: #4e8e2f
+ --el-color-success-light-5: #3e6b27
+ --el-color-success-light-7: #2d481f
+ --el-color-success-light-8: #25371c
+ --el-color-success-light-9: #1c2518
+ --el-color-success-dark-2: #85ce61
+ --el-color-warning: #e6a23c
+ --el-color-warning-light-3: #a77730
+ --el-color-warning-light-5: #7d5b28
+ --el-color-warning-light-7: #533f20
+ --el-color-warning-light-8: #3e301c
+ --el-color-warning-light-9: #292218
+ --el-color-warning-dark-2: #ebb563
+ --el-color-danger: #f56c6c
+ --el-color-danger-light-3: #b25252
+ --el-color-danger-light-5: #854040
+ --el-color-danger-light-7: #582e2e
+ --el-color-danger-light-8: #412626
+ --el-color-danger-light-9: #2b1d1d
+ --el-color-danger-dark-2: #f78989
+ --el-color-error: #f56c6c
+ --el-color-error-light-3: #b25252
+ --el-color-error-light-5: #854040
+ --el-color-error-light-7: #582e2e
+ --el-color-error-light-8: #412626
+ --el-color-error-light-9: #2b1d1d
+ --el-color-error-dark-2: #f78989
+ --el-color-info: #909399
+ --el-color-info-light-3: #6b6d71
+ --el-color-info-light-5: #525457
+ --el-color-info-light-7: #393a3c
+ --el-color-info-light-8: #2d2d2f
+ --el-color-info-light-9: #202121
+ --el-color-info-dark-2: #a6a9ad
+ --el-box-shadow: 0px 12px 32px 4px rgba(0 0 0 .36) 0px 8px 20px rgba(0 0 0 .72)
+ --el-box-shadow-light: 0px 0px 12px rgba(0 0 0 .72)
+ --el-box-shadow-lighter: 0px 0px 6px rgba(0 0 0 .72)
+ --el-box-shadow-dark: 0px 16px 48px 16px rgba(0 0 0 .72) 0px 12px 32px #000000 0px 8px 16px -8px #000000
+ --el-bg-color-page: #0a0a0a
+ --el-bg-color: #141414
+ --el-bg-color-overlay: #1d1e1f
+ --el-text-color-primary: #E5EAF3
+ --el-text-color-regular: #CFD3DC
+ --el-text-color-secondary: #A3A6AD
+ --el-text-color-placeholder: #8D9095
+ --el-text-color-disabled: #6C6E72
+ --el-border-color-darker: #636466
+ --el-border-color-dark: #58585B
+ --el-border-color: #4C4D4F
+ --el-border-color-light: #414243
+ --el-border-color-lighter: #363637
+ --el-border-color-extra-light: #2B2B2C
+ --el-fill-color-darker: #424243
+ --el-fill-color-dark: #39393A
+ --el-fill-color: #303030
+ --el-fill-color-light: #262727
+ --el-fill-color-lighter: #1D1D1D
+ --el-fill-color-extra-light: #191919
+ --el-fill-color-blank: var(--el-fill-color-darker)
+ --el-mask-color: rgba(0 0 0 .8)
+ --el-mask-color-extra-light: rgba(0 0 0 .3)
+ // timer
+ --timer-app-container-bg-color: var(--el-fill-color-dark)
+ // element-plus
+ .el-switch__core .el-switch__action
+ background-color: var(--el-fill-color-darker)
+ // menu
+ --el-menu-bg-color: var(--el-fill-color-light)
+ --el-menu-item-active-bg-color: var(--el-color-primary-light-5)
diff --git a/src/app/styles/index.sass b/src/app/styles/index.sass
index f958d6f42..35cd16851 100644
--- a/src/app/styles/index.sass
+++ b/src/app/styles/index.sass
@@ -5,6 +5,8 @@
* https://opensource.org/licenses/MIT
*/
+@import "./dark-theme"
+@import "./light-theme"
@import "./compatible"
body
@@ -34,6 +36,7 @@ a
.app-container
width: 100%
+ background: var(--timer-app-container-bg-color)
margin: auto
.content-container
@@ -51,6 +54,8 @@ a
overflow-y: auto
.filter-container
+ display: flex
+ align-items: center
user-select: none
.el-card__body
padding-bottom: 10px
@@ -62,6 +67,7 @@ a
margin-right: 4px
.filter-item
+ display: inline-flex
padding-right: 20px
.el-input__suffix
@@ -84,7 +90,7 @@ a
align-items: center
.filter-name
display: inline-flex
- color: #909399
+ color: var(--el-text-color-secondary)
font-weight: bold
font-size: 14px
pointer-events: none
@@ -117,8 +123,6 @@ a
.el-menu
height: 100%
-
-.el-menu
border: none
padding-top: 10px
@@ -128,13 +132,9 @@ a
font-size: 18px !important
.el-menu-item.is-active
- background: #0a6cfa
+ background: var(--el-menu-item-active-bg-color)
\:root
- // el-menu
- --el-menu-bg-color: rgb(29, 34, 45)
// el-menu-item
--el-menu-item-height: 48px
- --el-menu-text-color: #c1c6c8
--el-menu-active-color: var(--el-menu-text-color)
- --el-menu-hover-bg-color: #262f3e
diff --git a/src/app/styles/light-theme.sass b/src/app/styles/light-theme.sass
new file mode 100644
index 000000000..f17f4a37e
--- /dev/null
+++ b/src/app/styles/light-theme.sass
@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2021 Hengyang Zhang
+ *
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+\:root
+ --el-menu-bg-color: #1d222d
+ --timer-app-container-bg-color: var(--el-fill-color-blank)
+ --el-menu-text-color: #c1c6c8
+ --el-menu-item-active-bg-color: #0a6cfa
+ --el-menu-hover-bg-color: #262f3e
diff --git a/src/popup/components/chart/option.ts b/src/popup/components/chart/option.ts
index 22ffbacb0..00dbee8b0 100644
--- a/src/popup/components/chart/option.ts
+++ b/src/popup/components/chart/option.ts
@@ -18,6 +18,7 @@ import type QueryResult from "@popup/common/query-result"
import DataItem from "@entity/dto/data-item"
import { formatPeriodCommon, formatTime } from "@util/time"
import { t } from "@popup/locale"
+import { getPrimaryTextColor, getSecondaryTextColor } from "@util/style"
type EcOption = ComposeOption<
| PieSeriesOption
@@ -62,13 +63,6 @@ const staticOptions: EcOption = {
tooltip: {
trigger: 'item'
},
- legend: {
- type: 'scroll',
- orient: 'vertical',
- left: 15,
- top: 20,
- bottom: 20,
- },
series: [{
name: "Wasted Time",
type: "pie",
@@ -151,22 +145,34 @@ export function pieOptions(props: PipProps, container: HTMLDivElement): EcOption
const { type, mergeHost, data, displaySiteName, chartTitle, date } = props
const titleText = chartTitle
const subTitleText = `${calculateSubTitleText(date)} @ ${app}`
+ const textColor = getPrimaryTextColor()
+ const secondaryColor = getSecondaryTextColor()
const options: EcOption = {
title: {
text: titleText,
subtext: subTitleText,
- left: 'center'
+ left: 'center',
+ textStyle: { color: textColor },
+ subtextStyle: { color: secondaryColor },
},
tooltip: {
...staticOptions.tooltip,
formatter: params => toolTipFormatter(props, params),
position: (point: (number | string)[]) => calcPositionOfTooltip(container, point)
},
- legend: staticOptions.legend,
+ legend: {
+ type: 'scroll',
+ orient: 'vertical',
+ left: 15,
+ top: 20,
+ bottom: 20,
+ textStyle: { color: secondaryColor }
+ },
series: [{
...staticOptions.series[0],
label: {
- formatter: ({ name }) => mergeHost || name === t(msg => msg.otherLabel) ? name : `{${legend2LabelStyle(name)}|} {a|${name}}`
+ formatter: ({ name }) => mergeHost || name === t(msg => msg.otherLabel) ? name : `{${legend2LabelStyle(name)}|} {a|${name}}`,
+ color: textColor
}
}],
toolbox: staticOptions.toolbox
diff --git a/src/popup/index.ts b/src/popup/index.ts
index 0388c3902..f4e533dee 100644
--- a/src/popup/index.ts
+++ b/src/popup/index.ts
@@ -10,6 +10,12 @@ import renderChart, { handleRestore } from "./components/chart"
import initFooter, { queryInfo } from "./components/footer"
import metaService from "@service/meta-service"
import "../common/timer"
+import { toggle, init as initTheme } from "@util/dark-mode"
+import optionService from "@service/option-service"
+
+// Calculate the latest mode
+initTheme()
+optionService.isDarkMode().then(toggle)
handleRestore(queryInfo)
initFooter(renderChart)
diff --git a/src/popup/style/dark-theme.sass b/src/popup/style/dark-theme.sass
new file mode 100644
index 000000000..694a65447
--- /dev/null
+++ b/src/popup/style/dark-theme.sass
@@ -0,0 +1,22 @@
+html[data-theme='dark']:root
+ --el-bg-color: #141414
+ --el-bg-color-overlay: #1d1e1f
+ --el-fill-color-darker: #424243
+ --el-fill-color-dark: #39393A
+ --el-fill-color-light: #262727
+ --el-fill-color-lighter: #1D1D1D
+ --el-text-color-primary: #E5EAF3
+ --el-text-color-regular: #CFD3DC
+ --el-text-color-secondary: #A3A6AD
+ --el-text-color-placeholder: #8D9095
+ --el-border-color-darker: #636466
+ --el-border-color-dark: #58585B
+ --el-border-color: #4C4D4F
+ --el-border-color-light: #414243
+ --el-border-color-lighter: #363637
+ --el-border-color-extra-light: #2B2B2C
+
+ --el-bg-color: var(--el-fill-color-dark)
+ --el-fill-color-blank: var(--el-fill-color-darker)
+ .el-switch__core .el-switch__action
+ background-color: var(--el-fill-color-darker)
diff --git a/src/popup/style/index.sass b/src/popup/style/index.sass
index d86795fac..ced23dc43 100644
--- a/src/popup/style/index.sass
+++ b/src/popup/style/index.sass
@@ -12,12 +12,18 @@
@import url("element-plus/theme-chalk/el-popper.css")
@import url("element-plus/theme-chalk/el-option.css")
@import url("element-plus/theme-chalk/el-icon.css")
+@import ./dark-theme
$width: 750px
$height: 500px
$footerHeight: 40px
+body
+ margin: 0
+ background-color: var(--el-bg-color)
+
#app
+ padding: 8px
width: $width
height: calc($height + $footerHeight)
@@ -32,7 +38,7 @@ $optionPadding: 10px
margin: auto
height: calc($footerHeight - $optionPadding)
#total-info
- color: rgb(96, 98, 102)
+ color: var(--el-text-color-secondary)
font-size: 12px
.option-right
diff --git a/src/service/option-service.ts b/src/service/option-service.ts
index 156d290aa..8e9bdf3b9 100644
--- a/src/service/option-service.ts
+++ b/src/service/option-service.ts
@@ -41,12 +41,43 @@ async function setOption(option: Partial): Promise {
await db.setOption(toSet)
}
+async function isDarkMode(targetVal?: Timer.AppearanceOption): Promise {
+ const option = targetVal || await getAllOption()
+ const darkMode = option.darkMode
+ if (darkMode === "on") {
+ return true
+ } else if (darkMode === "off") {
+ return false
+ } else if (darkMode === "timed") {
+ const start = option.darkModeTimeStart
+ const end = option.darkModeTimeEnd
+ if (start === undefined || end === undefined) {
+ return false
+ }
+ const now = new Date()
+ const currentSecs = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds()
+ if (start > end) {
+ // Mostly
+ return start <= currentSecs || currentSecs <= end
+ } else if (start < end) {
+ return start <= currentSecs && currentSecs <= end
+ } else {
+ return currentSecs === start
+ }
+ }
+ return false
+}
+
class OptionService {
getAllOption = getAllOption
setPopupOption = setPopupOption
setAppearanceOption = setAppearanceOption
setStatisticsOption = setStatisticsOption
addOptionChangeListener = db.addOptionChangeListener
+ /**
+ * @since 1.1.0
+ */
+ isDarkMode = isDarkMode
}
export default new OptionService()
\ No newline at end of file
diff --git a/src/util/constant/option.ts b/src/util/constant/option.ts
index 5d687f93e..69de4d0c0 100644
--- a/src/util/constant/option.ts
+++ b/src/util/constant/option.ts
@@ -24,7 +24,13 @@ export function defaultAppearance(): Timer.AppearanceOption {
// Change false to true @since 0.8.4
displayBadgeText: true,
locale: "default",
- printInConsole: true
+ printInConsole: true,
+ darkMode: "off",
+ // 6 PM - 6 AM
+ // 18*60*60
+ darkModeTimeStart: 64800,
+ // 6*60*60
+ darkModeTimeEnd: 21600,
}
}
diff --git a/src/util/dark-mode.ts b/src/util/dark-mode.ts
new file mode 100644
index 000000000..b15f86f87
--- /dev/null
+++ b/src/util/dark-mode.ts
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2022 Hengyang Zhang
+ *
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+/**
+ * Dark mode
+ *
+ * @since 1.1.0
+ */
+
+const THEME_ATTR = "data-theme"
+const DARK_VAL = "dark"
+const STORAGE_KEY = "isDark"
+const STORAGE_FLAG = "1"
+
+function toggle0(isDarkMode: boolean) {
+ const htmlEl = document.getElementsByTagName("html")?.[0]
+ htmlEl.setAttribute(THEME_ATTR, isDarkMode ? DARK_VAL : "")
+}
+
+/**
+ * Init from local storage
+ */
+export function init() {
+ toggle0(isDarkMode())
+}
+
+export function toggle(isDarkMode: boolean) {
+ toggle0(isDarkMode)
+ localStorage.setItem(STORAGE_KEY, isDarkMode ? STORAGE_FLAG : undefined)
+}
+
+export function isDarkMode() {
+ return localStorage.getItem(STORAGE_KEY) === STORAGE_FLAG
+}
\ No newline at end of file
diff --git a/src/util/style.ts b/src/util/style.ts
new file mode 100644
index 000000000..439a88485
--- /dev/null
+++ b/src/util/style.ts
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2022 Hengyang Zhang
+ *
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+export function getCssVariable(varName: string, ele?: HTMLElement): string {
+ const realEle = ele || document.documentElement
+ if (!realEle) {
+ return undefined
+ }
+ return getComputedStyle(ele || document.documentElement).getPropertyValue(varName)
+}
+
+export function getPrimaryTextColor(): string {
+ return getCssVariable("--el-text-color-primary")
+}
+
+export function getSecondaryTextColor(): string {
+ return getCssVariable("--el-text-color-secondary")
+}
\ No newline at end of file