Add dark mode

close #9
This commit is contained in:
CzBiX
2020-04-01 12:50:26 +08:00
parent 707400b7be
commit 2507e126c4
15 changed files with 90 additions and 32 deletions

9
src/assets/styles.scss Normal file
View File

@@ -0,0 +1,9 @@
@import '~vuetify/src/styles/styles.sass';
@mixin dialog-title {
@include theme(v-card) using($material) {
.v-card__title {
background-color: map-get($material, 'app-bar');
}
}
}

View File

@@ -19,9 +19,7 @@
width="40em"
>
<v-card>
<v-card-title
class="headline grey lighten-4"
>
<v-card-title class="headline">
<v-icon class="mr-2">mdi-link-plus</v-icon>
<span>{{ $t('title.add_torrents') }}</span>
</v-card-title>
@@ -337,6 +335,10 @@ export default class AddForm extends Vue {
</script>
<style lang="scss" scoped>
@import '~@/assets/styles.scss';
@include dialog-title;
.btn-add.with-footer {
margin-bottom: 36px;
}

View File

@@ -35,6 +35,12 @@
:value="searchQuery"
/>
<v-spacer v-if="!phoneLayout" />
<v-btn
icon
@click="toggleDarkMode"
>
<v-icon v-text="darkModeIcon" />
</v-btn>
<v-select
v-show="!searchBarExpanded"
class="locales"
@@ -54,7 +60,7 @@ import Vue from 'vue';
import { mapMutations } from 'vuex';
import i18n, { tr, translations, defaultLocale } from '@/locale';
import { DialogType, DialogConfig, SnackBarConfig } from '@/store/types';
import { DialogType, DialogConfig, SnackBarConfig, ConfigPayload } from '@/store/types';
import Component from 'vue-class-component';
import { Prop, Emit, Watch } from 'vue-property-decorator';
@@ -63,6 +69,7 @@ import { Prop, Emit, Watch } from 'vue-property-decorator';
...mapMutations([
'showDialog',
'showSnackBar',
'updateConfig',
]),
},
})
@@ -72,12 +79,17 @@ export default class MainToolbar extends Vue {
showDialog!: (_: DialogConfig) => void
showSnackBar!: (_: SnackBarConfig) => void
updateConfig!: (_: ConfigPayload) => void
locales = this.buildLocales()
currentLocale = i18n.locale()
oldLocale = this.currentLocale
focusedSearch = false
get darkModeIcon() {
return this.$vuetify.theme.dark ? 'mdi-brightness-4' : 'mdi-brightness-6';
}
get searchQuery() {
return this.$store.getters.config.filter.query;
}
@@ -115,7 +127,7 @@ export default class MainToolbar extends Vue {
onSearch = throttle(async (v: string) => {
// avoid hang input
await this.$nextTick();
this.$store.commit('updateConfig', {
this.updateConfig({
key: 'filter',
value: {
query: v,
@@ -156,6 +168,16 @@ export default class MainToolbar extends Vue {
location.reload();
}
toggleDarkMode() {
const { theme } = this.$vuetify;
theme.dark = !theme.dark;
this.updateConfig({
key: 'darkMode',
value: theme.dark,
});
}
@Watch('currentLocale')
onCurrentLocaleChanged(v: keyof typeof translations) {
this.switchLocale(v)

View File

@@ -543,6 +543,8 @@ export default class Torrents extends Vue {
</script>
<style lang="scss" scoped>
@import '~@/assets/styles.scss';
.toolbar {
display: flex;
margin-left: 2px;
@@ -559,7 +561,7 @@ export default class Torrents extends Vue {
flex: 1;
position: relative;
.v-data-table {
@include theme(v-data-table) using ($material) {
position: absolute;
width: 100%;
height: 100%;
@@ -583,7 +585,7 @@ export default class Torrents extends Vue {
}
tr:nth-child(2n) {
background-color: #eee;
background-color: map-deep-get($material, 'table', 'hover');
}
td {

View File

@@ -6,9 +6,7 @@
width="40em"
>
<v-card>
<v-card-title
class="headline grey lighten-4"
>
<v-card-title class="headline">
<v-icon class="mr-2">mdi-delete</v-icon>
<span>{{ $t('title.delete_torrents') }}</span>
</v-card-title>
@@ -121,6 +119,10 @@ export default class ConfirmDeleteDialog extends Vue {
</script>
<style lang="scss" scoped>
@import '~@/assets/styles.scss';
@include dialog-title;
.torrents {
overflow: auto;
}

View File

@@ -6,9 +6,7 @@
width="40em"
>
<v-card>
<v-card-title
class="headline grey lighten-4"
>
<v-card-title class="headline">
<v-icon class="mr-2">mdi-folder</v-icon>
<span>{{ $t('title.set_category') }}</span>
</v-card-title>
@@ -122,6 +120,10 @@ export default class ConfirmSetCategoryDialog extends Vue {
</script>
<style lang="scss" scoped>
@import '~@/assets/styles.scss';
@include dialog-title;
.torrents {
overflow: auto;
}

View File

@@ -7,9 +7,7 @@
width="40em"
>
<v-card>
<v-card-title
class="headline grey lighten-4"
>
<v-card-title class="headline">
<v-icon class="mr-2">mdi-server</v-icon>
<span>Edit tracker</span>
</v-card-title>
@@ -212,6 +210,10 @@ export default class EditTrackerDialog extends Vue {
</script>
<style lang="scss" scoped>
@import '~@/assets/styles.scss';
@include dialog-title;
.torrents {
overflow: auto;
white-space: nowrap;

View File

@@ -6,9 +6,7 @@
:fullscreen="phoneLayout"
>
<v-card>
<v-card-title
class="headline grey lighten-4"
>
<v-card-title class="headline">
<v-icon class="mr-2">
mdi-alert-circle
</v-icon>
@@ -148,6 +146,10 @@ export default class InfoDialog extends Vue {
</script>
<style lang="scss" scoped>
@import '~@/assets/styles.scss';
@include dialog-title;
::v-deep .v-dialog {
max-width: 1100px;

View File

@@ -7,9 +7,7 @@
:width="dialogWidth"
>
<v-card>
<v-card-title
class="headline grey lighten-4"
>
<v-card-title class="headline">
<v-icon class="mr-2">mdi-delta</v-icon>
<span>Logs</span>
</v-card-title>
@@ -63,7 +61,7 @@ import { Prop, Emit } from 'vue-property-decorator';
},
typeColor(type: number) {
const map: any = {
1: 'secondary--text',
1: null,
2: 'info--text',
4: 'warn--text',
8: 'error--text',
@@ -114,6 +112,10 @@ export default class LogsDialog extends HasTask {
</script>
<style lang="scss" scoped>
@import '~@/assets/styles.scss';
@include dialog-title;
.logs {
font-family: monospace;

View File

@@ -6,9 +6,7 @@
persistent
>
<v-card>
<v-card-title
class="headline grey lighten-4"
>
<v-card-title class="headline">
<v-icon class="mr-2">mdi-rss-box</v-icon>
<span>RSS</span>
<v-spacer />
@@ -425,6 +423,10 @@ export default class RssDialog extends HasTask {
</script>
<style lang="scss" scoped>
@import '~@/assets/styles.scss';
@include dialog-title;
.v-card {
display: flex;
flex-direction: column;

View File

@@ -6,9 +6,7 @@
width="50%"
>
<v-card>
<v-card-title
class="headline grey lighten-4"
>
<v-card-title class="headline">
<v-icon class="mr-2">mdi-filter</v-icon>
<span v-text="$t('dialog.rss_rule.title')" />
<v-spacer />
@@ -347,6 +345,10 @@ export default class RssRulesDialog extends Vue {
</script>
<style lang="scss" scoped>
@import '~@/assets/styles.scss';
@include dialog-title;
.v-card {
display: flex;
flex-direction: column;

View File

@@ -28,7 +28,7 @@ function matchLocale() {
export const defaultLocale = matchLocale()
function updateLocale() {
let locale: keyof typeof translations | null = loadConfig()['locale'];
let locale: keyof typeof translations | undefined | null = loadConfig()['locale'];
if (!locale) {
locale = defaultLocale;

View File

@@ -1,6 +1,7 @@
import Vue from 'vue';
import Vuetify from 'vuetify/lib';
import i18n from '@/locale';
import { loadConfig } from '@/store/config';
Vue.use(Vuetify);
@@ -9,6 +10,7 @@ locale = locale === 'zh-CN' ? 'zh-Hans' : locale.split('-', 1)[0];
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { default: translation } = require('vuetify/src/locale/' + locale);
const darkMode = !!loadConfig()['darkMode'];
export default new Vuetify({
lang: {
@@ -18,4 +20,7 @@ export default new Vuetify({
icons: {
iconfont: 'mdi',
},
theme: {
dark: darkMode,
},
});

View File

@@ -17,13 +17,16 @@ const defaultConfig = {
query: null,
},
locale: null,
darkMode: null,
};
export type Config = typeof defaultConfig
function saveConfig(obj: any) {
localStorage.setItem(configKey, JSON.stringify(obj));
}
export function loadConfig() {
export function loadConfig(): Partial<Config> {
const tmp = localStorage.getItem(configKey);
if (!tmp) {
return {};

View File

@@ -1,4 +1,5 @@
import { MainData } from '@/types';
import { Config } from './config';
export interface RootState {
rid: number;
@@ -19,7 +20,7 @@ export interface ConfigState {
}
export interface ConfigPayload {
key: string;
key: keyof Config;
value: any;
}