夜间模式

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 // rules API
$app.get("/download/rule/:name", downloadRule); $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 // Storage management
$app.route("/api/storage") $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 // settings API
function getSettings(req, res) { function getSettings(req, res) {
const settings = $.read(SETTINGS_KEY); const settings = $.read(SETTINGS_KEY);

View File

@ -5,8 +5,11 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta charset="utf-8"/> <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-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" <meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"/> 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"/> <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"; import {showError} from "@/utils";
function initStore(store) { async function initStore(store) {
store.dispatch('FETCH_SUBSCRIPTIONS').catch(() => { await store.dispatch('FETCH_SUBSCRIPTIONS').catch(() => {
showError(`无法拉取订阅列表!`); showError(`无法拉取订阅列表!`);
}); });
store.dispatch("FETCH_COLLECTIONS").catch(() => { await store.dispatch("FETCH_COLLECTIONS").catch(() => {
showError(`无法拉取组合订阅列表!`); showError(`无法拉取组合订阅列表!`);
}); });
store.dispatch("FETCH_ARTIFACTS").catch(() => { await store.dispatch("FETCH_ARTIFACTS").catch(() => {
showError(`无法拉取配置列表!`); showError(`无法拉取配置列表!`);
}); });
store.dispatch("FETCH_SETTINGS").catch(() => { await store.dispatch("FETCH_SETTINGS").catch(() => {
showError(`无法拉取配置列表!`); showError(`无法拉取配置列表!`);
}); });
store.dispatch("FETCH_ENV").catch(() => { await store.dispatch("FETCH_ENV").catch(() => {
showError(`无法获取当前运行环境!`); showError(`无法获取当前运行环境!`);
}); });
} }
@ -60,7 +60,12 @@ export default {
created() { created() {
initStore(this.$store); 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: { computed: {
@ -74,15 +79,23 @@ export default {
watch: { watch: {
successMessage() { successMessage() {
setTimeout(() => { if (this.$store.state.snackbarTimer) {
clearTimeout(this.$store.state.snackbarTimer);
}
const timer = setTimeout(() => {
this.$store.commit("SET_SUCCESS_MESSAGE", ""); this.$store.commit("SET_SUCCESS_MESSAGE", "");
}, 1000); }, 3000);
this.$store.commit("SET_SNACK_BAR_TIMER", timer);
}, },
errorMessage() { errorMessage() {
setTimeout(() => { if (this.$store.state.snackbarTimer) {
clearTimeout(this.$store.state.snackbarTimer);
}
const timer = setTimeout(() => {
this.$store.commit("SET_ERROR_MESSAGE", ""); this.$store.commit("SET_ERROR_MESSAGE", "");
}, 1000); }, 3000);
}, this.$store.commit("SET_SNACK_BAR_TIMER", timer);
}
} }
} }
</script> </script>

View File

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

View File

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

View File

@ -2,14 +2,12 @@
<v-container fluid> <v-container fluid>
<v-card> <v-card>
<v-card-title> <v-card-title>
<v-icon left>mdi-cloud</v-icon>
同步配置 同步配置
<v-spacer></v-spacer> <v-spacer></v-spacer>
<!-- <v-btn icon>--> <v-btn icon @click="openGist()">
<!-- <v-icon>mdi-cloud-circle</v-icon>--> <v-icon>visibility</v-icon>
<!-- </v-btn>--> </v-btn>
<!-- <v-btn icon>-->
<!-- <v-icon>mdi-refresh-circle</v-icon>-->
<!-- </v-btn>-->
<v-dialog max-width="400px" v-model="addArtifactDialog"> <v-dialog max-width="400px" v-model="addArtifactDialog">
<template #activator="{on}"> <template #activator="{on}">
<v-btn icon v-on="on"> <v-btn icon v-on="on">
@ -195,6 +193,9 @@ export default {
artifacts() { artifacts() {
const items = this.$store.state.artifacts; const items = this.$store.state.artifacts;
return Object.keys(items).map(k => items[k]); return Object.keys(items).map(k => items[k]);
},
settings() {
return this.$store.state.settings;
} }
}, },
methods: { methods: {
@ -287,6 +288,10 @@ export default {
data = this.$store.state.collections; data = this.$store.state.collections;
} }
return Object.keys(data); 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 v-for="sub in availableSubs" :key="sub.name">
<v-list-item-avatar> <v-list-item-avatar>
<v-icon v-if="!sub.icon" color="teal darken-1">mdi-cloud</v-icon> <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-avatar>
<v-list-item-content> <v-list-item-content>
{{ sub.name }} {{ sub.name }}
@ -571,7 +571,7 @@ function uuidv4() {
</script> </script>
<style> <style>
.v-label { .invert {
font-size: small; filter: invert(100%);
} }
</style> </style>

View File

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

View File

@ -2,25 +2,86 @@
<v-container fluid> <v-container fluid>
<v-card> <v-card>
<v-card-title> <v-card-title>
GitHub 配置 <v-icon left>mdi-cloud</v-icon>
数据同步
<v-spacer></v-spacer> <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-title>
<v-card-text> <v-card-text>
最近同步于{{ 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-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 <v-text-field
label="GitHub 用户名" label="GitHub 用户名"
hint="填入GitHub用户名" hint="填入GitHub用户名"
v-model="settings.githubUser" v-model="settings.githubUser"
clearable clear-icon="clear" clearable clear-icon="clear"
> />
</v-row>
</v-text-field> <v-row>
<v-text-field <v-text-field
label="GitHub Token" label="GitHub Token"
hint="填入GitHub Token" hint="填入GitHub Token"
v-model="settings.gistToken" v-model="settings.gistToken"
clearable clear-icon="clear" 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-card-text>
<v-divider></v-divider> <v-divider></v-divider>
<v-card-actions> <v-card-actions>
@ -30,21 +91,21 @@
<v-divider/> <v-divider/>
</v-card> </v-card>
<v-card> <!-- <v-card>-->
<v-card-title> <!-- <v-card-title>-->
Gist 数据同步 <!-- <v-icon left>-->
<v-spacer></v-spacer> <!-- mdi-star-->
<v-icon small>mdi-cloud</v-icon> <!-- </v-icon>-->
</v-card-title> <!-- 关于-->
<v-card-text> <!-- </v-card-title>-->
最近同步于{{syncTime}} <!-- <v-card-text>-->
</v-card-text> <!-- <v-list>-->
<v-card-actions> <!-- <v-list-item>-->
<v-spacer></v-spacer> <!-- <v-list-item-title>GitHub</v-list-item-title>-->
<v-btn small text @click="sync('upload')">上传</v-btn> <!-- </v-list-item>-->
<v-btn small text @click="sync('download')">恢复</v-btn> <!-- </v-list>-->
</v-card-actions> <!-- </v-card-text>-->
</v-card> <!-- </v-card>-->
</v-container> </v-container>
</template> </template>
@ -53,10 +114,23 @@ import {axios, showError} from "@/utils";
import {format} from "timeago.js"; import {format} from "timeago.js";
export default { export default {
data() {
return {
status: {
uploading: false,
downloading: false
}
}
},
computed: { computed: {
settings() { settings: {
get() {
return this.$store.state.settings; return this.$store.state.settings;
}, },
set(value) {
this.$store.state.settings = value;
}
},
syncTime() { syncTime() {
if (this.settings.syncTime) { if (this.settings.syncTime) {
return format(this.settings.syncTime, "zh_CN"); return format(this.settings.syncTime, "zh_CN");
@ -66,26 +140,41 @@ export default {
} }
}, },
methods: { methods: {
save() { async save() {
axios.patch(`/settings`, this.settings); await axios.patch(`/settings`, this.settings);
this.$store.dispatch("FETCH_SETTINGS"); await this.$store.dispatch("FETCH_SETTINGS");
this.$store.commit("SET_SUCCESS_MESSAGE", `保存成功!`); this.$store.commit("SET_SUCCESS_MESSAGE", `保存成功!`);
}, },
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
sync(action) { sync(action) {
const setLoading = (status) => {
if (action === 'upload') {
this.status.uploading = status;
} else if (action === 'download'){
this.status.downloading = status;
}
}
if (!this.settings.gistToken) { if (!this.settings.gistToken) {
this.$store.commit("SET_ERROR_MESSAGE", "未设置GitHub Token"); this.$store.commit("SET_ERROR_MESSAGE", "未设置GitHub Token");
return; return;
} }
setLoading(true);
axios.get(`/utils/backup?action=${action}`).then(resp => { axios.get(`/utils/backup?action=${action}`).then(resp => {
if (resp.data.status === 'success') { if (resp.data.status === 'success') {
this.$store.commit("SET_SUCCESS_MESSAGE", `${action === 'upload' ? "备份" : "还原"}成功!`); this.$store.commit("SET_SUCCESS_MESSAGE", `${action === 'upload' ? "备份" : "还原"}成功!`);
if (action === 'upload') {
this.settings.syncTime = new Date().getTime(); this.settings.syncTime = new Date().getTime();
}
axios.patch(`/settings`, this.settings); axios.patch(`/settings`, this.settings);
this.updateStore(this.$store); 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(() => { store.dispatch("FETCH_COLLECTIONS").catch(() => {
showError(`无法拉取组合订阅列表!`); showError(`无法拉取组合订阅列表!`);
}); });
store.dispatch("FETCH_SETTINGS").catch(() => {
showError(`无法拉取设置!`);
});
store.dispatch("FETCH_ARTIFACTS").catch(() => { store.dispatch("FETCH_ARTIFACTS").catch(() => {
showError(`无法拉取同步配置!`); showError(`无法拉取同步配置!`);
}); });
},
openGist() {
window.open(`https://gist.github.com${'/' + this.settings.githubUser || ''}`)
} }
} }
} }