夜间模式

This commit is contained in:
Peng-YM 2020-12-11 11:59:02 +08:00
parent 1f998f70b5
commit 3f002e0c52
9 changed files with 268 additions and 80 deletions

View File

@ -76,6 +76,13 @@ function service() {
// rules API
$app.get("/download/rule/:name", downloadRule);
$app.route("/api/rules")
.post(createRule)
.get(getAllRules);
$app.route("/api/rule/:name")
.patch(updateRule)
.delete(deleteRule)
.get(getRule);
// Storage management
$app.route("/api/storage")
@ -435,6 +442,22 @@ function service() {
}
}
function createRule(req, res) {
}
function deleteRule(req, res) {
}
function updateRule(req, res) {
}
function getAllRules(req, res) {
}
function getRule(req, res) {
}
// settings API
function getSettings(req, res) {
const settings = $.read(SETTINGS_KEY);

View File

@ -5,8 +5,11 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta charset="utf-8"/>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta name="apple-mobile-web-app-capable" content="yes"/>
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
<!-- <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>-->
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"/>
<link rel="Bookmark" href="https://raw.githubusercontent.com/58xinian/icon/master/Sub-Store1.png"/>

View File

@ -34,20 +34,20 @@ import BottomNav from "@/components/BottomNav";
import {showError} from "@/utils";
function initStore(store) {
store.dispatch('FETCH_SUBSCRIPTIONS').catch(() => {
async function initStore(store) {
await store.dispatch('FETCH_SUBSCRIPTIONS').catch(() => {
showError(`无法拉取订阅列表!`);
});
store.dispatch("FETCH_COLLECTIONS").catch(() => {
await store.dispatch("FETCH_COLLECTIONS").catch(() => {
showError(`无法拉取组合订阅列表!`);
});
store.dispatch("FETCH_ARTIFACTS").catch(() => {
await store.dispatch("FETCH_ARTIFACTS").catch(() => {
showError(`无法拉取配置列表!`);
});
store.dispatch("FETCH_SETTINGS").catch(() => {
await store.dispatch("FETCH_SETTINGS").catch(() => {
showError(`无法拉取配置列表!`);
});
store.dispatch("FETCH_ENV").catch(() => {
await store.dispatch("FETCH_ENV").catch(() => {
showError(`无法获取当前运行环境!`);
});
}
@ -60,7 +60,12 @@ export default {
created() {
initStore(this.$store);
// this.$vuetify.theme.dark = !this.$vuetify.theme.dark;
this.$store.watch(
(state => state.settings.theme.darkMode),
(value => {
this.$vuetify.theme.dark = value
})
)
},
computed: {
@ -74,15 +79,23 @@ export default {
watch: {
successMessage() {
setTimeout(() => {
if (this.$store.state.snackbarTimer) {
clearTimeout(this.$store.state.snackbarTimer);
}
const timer = setTimeout(() => {
this.$store.commit("SET_SUCCESS_MESSAGE", "");
}, 1000);
}, 3000);
this.$store.commit("SET_SNACK_BAR_TIMER", timer);
},
errorMessage() {
setTimeout(() => {
if (this.$store.state.snackbarTimer) {
clearTimeout(this.$store.state.snackbarTimer);
}
const timer = setTimeout(() => {
this.$store.commit("SET_ERROR_MESSAGE", "");
}, 1000);
},
}, 3000);
this.$store.commit("SET_SNACK_BAR_TIMER", timer);
}
}
}
</script>

View File

@ -17,25 +17,25 @@ const router = new Router({
path: "/",
name: "subscriptions",
component: Subscription,
meta: {title: "订阅"}
meta: {title: "订阅", keepAlive: true}
},
{
path: "/dashboard",
name: "dashboard",
component: Dashboard,
meta: {title: "首页"}
meta: {title: "首页", keepAlive: true}
},
{
path: "/cloud",
name: "artifact",
component: Cloud,
meta: {title: "同步"}
meta: {title: "同步", keepAlive: true}
},
{
path: "/user",
name: "user",
component: User,
meta: {title: "我的"}
meta: {title: "我的", keepAlive: true}
},
{
path: "/sub-edit/:name",

View File

@ -7,17 +7,21 @@ Vue.use(Vuex);
const store = new Vuex.Store({
state: {
title: "Sub-Store",
isDarkMode: false,
clipboard: "",
successMessage: "",
errorMessage: "",
snackbarTimer: "",
subscriptions: {},
collections: {},
artifacts: {},
env: {},
settings: {}
settings: {
theme: {
darkMode: false
}
}
},
mutations: {
@ -28,8 +32,9 @@ const store = new Vuex.Store({
SET_NAV_TITLE(state, title) {
state.title = title;
},
SET_DARK_MODE(state, isDarkMode) {
state.isDarkMode = isDarkMode
SET_SNACK_BAR_TIMER(state, timer) {
state.snackbarTimer = timer;
},
SET_SUCCESS_MESSAGE(state, msg) {
@ -40,6 +45,9 @@ const store = new Vuex.Store({
state.errorMessage = msg;
},
SET_DARK_MODE(state, on) {
state.settings.theme.darkMode = on;
}
},
actions: {
@ -71,7 +79,12 @@ const store = new Vuex.Store({
},
async FETCH_SETTINGS({state}) {
return axios.get("/settings").then(resp => {
state.settings = resp.data;
state.settings = {
theme: {
darkMode: false
},
...resp.data
}
});
},
// update subscriptions

View File

@ -2,14 +2,12 @@
<v-container fluid>
<v-card>
<v-card-title>
<v-icon left>mdi-cloud</v-icon>
同步配置
<v-spacer></v-spacer>
<!-- <v-btn icon>-->
<!-- <v-icon>mdi-cloud-circle</v-icon>-->
<!-- </v-btn>-->
<!-- <v-btn icon>-->
<!-- <v-icon>mdi-refresh-circle</v-icon>-->
<!-- </v-btn>-->
<v-btn icon @click="openGist()">
<v-icon>visibility</v-icon>
</v-btn>
<v-dialog max-width="400px" v-model="addArtifactDialog">
<template #activator="{on}">
<v-btn icon v-on="on">
@ -195,6 +193,9 @@ export default {
artifacts() {
const items = this.$store.state.artifacts;
return Object.keys(items).map(k => items[k]);
},
settings() {
return this.$store.state.settings;
}
},
methods: {
@ -287,6 +288,10 @@ export default {
data = this.$store.state.collections;
}
return Object.keys(data);
},
openGist() {
window.open(`https://gist.github.com${ '/' + this.settings.githubUser || ''}`)
}
}
}

View File

@ -36,7 +36,7 @@
<v-list-item v-for="sub in availableSubs" :key="sub.name">
<v-list-item-avatar>
<v-icon v-if="!sub.icon" color="teal darken-1">mdi-cloud</v-icon>
<v-img :src="sub.icon" v-else color="blue"/>
<v-img :src="sub.icon" v-else :class="sub.icon.indexOf('#invert') !== -1 ? 'invert' : ''"/>
</v-list-item-avatar>
<v-list-item-content>
{{ sub.name }}
@ -571,7 +571,7 @@ function uuidv4() {
</script>
<style>
.v-label {
font-size: small;
.invert {
filter: invert(100%);
}
</style>

View File

@ -2,6 +2,7 @@
<v-container fluid>
<v-card>
<v-card-title>
<v-icon left>local_airport</v-icon>
单个订阅
<v-spacer></v-spacer>
<v-btn icon @click="createSub">
@ -17,7 +18,7 @@
>
<v-list-item-avatar>
<v-icon v-if="!sub.icon" color="teal darken-1">mdi-cloud</v-icon>
<v-img :src="sub.icon" v-else color="blue"/>
<v-img :src="sub.icon" v-else :class="sub.icon.indexOf('#invert') !== -1 ? 'invert' : ''"/>
</v-list-item-avatar>
<v-list-item-content>
@ -54,6 +55,7 @@
<v-card>
<v-card-title>
<v-icon left>work_outline</v-icon>
组合订阅
<v-spacer></v-spacer>
<v-btn icon @click="createCol">
@ -70,7 +72,7 @@
>
<v-list-item-avatar>
<v-icon v-if="!collection.icon" color="teal darken-1">mdi-cloud</v-icon>
<v-img :src="collection.icon" v-else color="blue"/>
<v-img :src="collection.icon" v-else :class="collection.icon.indexOf('#invert') !== -1 ? 'invert' : ''"/>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="collection.name" class="font-weight-medium"></v-list-item-title>
@ -166,6 +168,14 @@ export default {
title: "删除",
action: "DELETE"
},
// {
// title: "",
// action: "MOVE_UP"
// },
// {
// title: "",
// action: "MOVE_DOWN"
// }
]
}
},
@ -174,9 +184,14 @@ export default {
subscriptionBaseURL() {
return BACKEND_BASE;
},
subscriptions() {
const subs = this.$store.state.subscriptions;
return Object.keys(subs).map(k => subs[k]);
subscriptions: {
get(){
const subs = this.$store.state.subscriptions;
return Object.keys(subs).map(k => subs[k]);
},
set(){
}
},
collections() {
const cols = this.$store.state.collections;
@ -197,6 +212,12 @@ export default {
break
case 'DELETE':
this.$store.dispatch("DELETE_SUBSCRIPTION", sub.name);
break
case 'MOVE_UP':
this.moveUpSubscription(sub.name);
break
case 'MOVE_DOWN':
break
}
},
@ -232,15 +253,29 @@ export default {
},
refreshProxyList() {
this.$refs.proxyList.refresh();
}
},
moveUpSubscription(name) {
let index = 0;
for (; index < this.subscriptions.length; index++) {
if (this.subscriptions[index].name === name) {
break;
}
}
if (index === 0) return;
// otherwise swap with previous one
const prev = this.subscriptions[index - 1];
const cur = this.subscriptions[index];
this.subscriptions.splice(index - 1, 2, cur, prev);
},
// moveDownSubscription(name) {
//
// }
}
}
</script>
<style scoped>
.top-toolbar {
position: sticky;
top: 0;
z-index: 999;
.invert {
filter: invert(100%);
}
</style>

View File

@ -2,25 +2,86 @@
<v-container fluid>
<v-card>
<v-card-title>
GitHub 配置
<v-icon left>mdi-cloud</v-icon>
数据同步
<v-spacer></v-spacer>
<v-icon small>settings</v-icon>
<v-btn icon @click="openGist()">
<v-icon small color="primary">visibility</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<v-text-field
label="GitHub 用户名"
hint="填入GitHub用户名"
v-model="settings.githubUser"
clearable clear-icon="clear"
>
最近同步于{{ syncTime }}
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn small
:loading="status.uploading"
color="blue-grey"
class="ma-2 white--text"
@click="sync('upload')">
上传
<v-icon right>
mdi-cloud-upload
</v-icon>
</v-btn>
<v-btn small
:loading="status.downloading"
color="blue-grey"
class="ma-2 white--text"
@click="sync('download')">
恢复
<v-icon right>
mdi-cloud-download
</v-icon>
</v-btn>
</v-card-actions>
</v-card>
</v-text-field>
<v-text-field
label="GitHub Token"
hint="填入GitHub Token"
v-model="settings.gistToken"
clearable clear-icon="clear"
/>
<v-card>
<v-card-title>
<v-icon left>settings</v-icon>
设置
<v-spacer></v-spacer>
</v-card-title>
<v-card-text>
<v-list dense>
<v-subheader>GitHub配置</v-subheader>
<v-list-item>
<v-col>
<v-row>
<v-text-field
label="GitHub 用户名"
hint="填入GitHub用户名"
v-model="settings.githubUser"
clearable clear-icon="clear"
/>
</v-row>
<v-row>
<v-text-field
label="GitHub Token"
hint="填入GitHub Token"
v-model="settings.gistToken"
clearable clear-icon="clear"
/>
</v-row>
</v-col>
</v-list-item>
<v-divider></v-divider>
<v-subheader>外观</v-subheader>
<v-list-item>
<v-list-item-content>
夜间模式 (实验性支持)
</v-list-item-content>
<v-list-item-action>
<v-switch
label=""
hide-details
v-model="settings.theme.darkMode"
/>
</v-list-item-action>
</v-list-item>
</v-list>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
@ -30,21 +91,21 @@
<v-divider/>
</v-card>
<v-card>
<v-card-title>
Gist 数据同步
<v-spacer></v-spacer>
<v-icon small>mdi-cloud</v-icon>
</v-card-title>
<v-card-text>
最近同步于{{syncTime}}
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn small text @click="sync('upload')">上传</v-btn>
<v-btn small text @click="sync('download')">恢复</v-btn>
</v-card-actions>
</v-card>
<!-- <v-card>-->
<!-- <v-card-title>-->
<!-- <v-icon left>-->
<!-- mdi-star-->
<!-- </v-icon>-->
<!-- 关于-->
<!-- </v-card-title>-->
<!-- <v-card-text>-->
<!-- <v-list>-->
<!-- <v-list-item>-->
<!-- <v-list-item-title>GitHub</v-list-item-title>-->
<!-- </v-list-item>-->
<!-- </v-list>-->
<!-- </v-card-text>-->
<!-- </v-card>-->
</v-container>
</template>
@ -53,11 +114,24 @@ import {axios, showError} from "@/utils";
import {format} from "timeago.js";
export default {
data() {
return {
status: {
uploading: false,
downloading: false
}
}
},
computed: {
settings() {
return this.$store.state.settings;
settings: {
get() {
return this.$store.state.settings;
},
set(value) {
this.$store.state.settings = value;
}
},
syncTime(){
syncTime() {
if (this.settings.syncTime) {
return format(this.settings.syncTime, "zh_CN");
} else {
@ -66,26 +140,41 @@ export default {
}
},
methods: {
save() {
axios.patch(`/settings`, this.settings);
this.$store.dispatch("FETCH_SETTINGS");
async save() {
await axios.patch(`/settings`, this.settings);
await this.$store.dispatch("FETCH_SETTINGS");
this.$store.commit("SET_SUCCESS_MESSAGE", `保存成功!`);
},
// eslint-disable-next-line no-unused-vars
sync(action) {
const setLoading = (status) => {
if (action === 'upload') {
this.status.uploading = status;
} else if (action === 'download'){
this.status.downloading = status;
}
}
if (!this.settings.gistToken) {
this.$store.commit("SET_ERROR_MESSAGE", "未设置GitHub Token");
return;
}
setLoading(true);
axios.get(`/utils/backup?action=${action}`).then(resp => {
if (resp.data.status === 'success') {
this.$store.commit("SET_SUCCESS_MESSAGE", `${action === 'upload' ? "备份" : "还原"}成功!`);
this.settings.syncTime = new Date().getTime();
if (action === 'upload') {
this.settings.syncTime = new Date().getTime();
}
axios.patch(`/settings`, this.settings);
this.updateStore(this.$store);
} else
this.$store.commit("SET_ERROR_MESSAGE", `备份失败!${resp.data.message}`);
}
}).catch(err => {
this.$store.commit("SET_ERROR_MESSAGE", `备份失败!${err}`);
}).finally(() => {
setLoading(false);
});
},
@ -96,9 +185,16 @@ export default {
store.dispatch("FETCH_COLLECTIONS").catch(() => {
showError(`无法拉取组合订阅列表!`);
});
store.dispatch("FETCH_SETTINGS").catch(() => {
showError(`无法拉取设置!`);
});
store.dispatch("FETCH_ARTIFACTS").catch(() => {
showError(`无法拉取同步配置!`);
});
},
openGist() {
window.open(`https://gist.github.com${'/' + this.settings.githubUser || ''}`)
}
}
}