Skip to content
This repository was archived by the owner on Mar 26, 2023. It is now read-only.

Commit d04443f

Browse files
authored
Cache (#44)
Fixes #29 Fixes #43
1 parent f4f18bb commit d04443f

File tree

9 files changed

+360
-103
lines changed

9 files changed

+360
-103
lines changed

README.md

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,17 @@
55

66
## Project setup
77
```
8-
npm install
8+
yarn
99
```
1010

1111
### Steps to test your Vue single-spa application:
1212
Compiles and hot-reloads for development
13-
1. Run 'npm run serve'
14-
2. Go to http://single-spa-playground.org/playground/instant-test?name=@profcomff/timetable-webapp&url=%2F%2Flocalhost%3A8080%2Fjs%2Fapp.js&framework=vue to see it working!
13+
1. Run 'yarn serve'
14+
2. Go to http://app.test.profcomff.com/ and use [instrctions here](https://github.com/profcomff/.github/wiki/Frontend-%D1%80%D0%B0%D0%B7%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0) to setup
1515

1616
### Compiles and minifies for production
1717
```
18-
npm run build
19-
```
20-
21-
### Lints and fixes files
22-
```
23-
npm run lint
18+
yarn build
2419
```
2520

2621
### Customize configuration

babel.config.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
const productionPlugns = []
2+
3+
if (process.env.NODE_ENV == 'production')
4+
productionPlugns.push(["transform-remove-console", { "exclude": ["error", "warn", "info"] }]);
5+
16
module.exports = {
27
presets: [
38
'@vue/cli-plugin-babel/preset'
4-
]
9+
],
10+
plugins: productionPlugns
511
}

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212
"click-outside-vue3": "^4.0.1",
1313
"core-js": "^3.6.5",
1414
"single-spa-vue": "^2.1.0",
15-
"vue": "^3.0.0",
16-
"vue-router": "^4.0.0-0"
15+
"vue": "3",
16+
"vue-router": "^4.0.0-0",
17+
"ejs": "^3.1.7",
18+
"glob-parent": "^5.1.2",
19+
"node-forge": "^1.3.0",
20+
"nth-check": "^2.0.1"
1721
},
1822
"devDependencies": {
1923
"@vue/cli-plugin-babel": "~4.5.15",
@@ -23,6 +27,7 @@
2327
"@vue/cli-service": "~4.5.15",
2428
"@vue/compiler-sfc": "^3.0.0",
2529
"babel-eslint": "^10.1.0",
30+
"babel-plugin-transform-remove-console": "^6.9.4",
2631
"eslint": "^6.7.2",
2732
"eslint-plugin-vue": "^7.0.0",
2833
"vue-cli-plugin-single-spa": "~3.3.0"

src/utils/Dates.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export function getMonday(d) {
2+
/**
3+
* Возвращает понедельник текущей недели
4+
*/
5+
d = getMidnight(d);
6+
var day = d.getDay(),
7+
diff = d.getDate() - day + (day == 0 ? -6 : 1); // adjust when day is sunday
8+
return new Date(d.setDate(diff));
9+
}
10+
11+
12+
export function getMidnight(d) {
13+
/**
14+
* Возвращает начало сегодняшнего дня
15+
*/
16+
d = new Date(d);
17+
d.setHours(d.getHours() - d.getTimezoneOffset() / 60);
18+
return d;
19+
}
20+
21+
export function isToday(d, today) {
22+
/**
23+
* Проверяет совпадение дат, но не времени
24+
*/
25+
d = new Date(d);
26+
today = new Date(today);
27+
let isToday = (
28+
today.getFullYear() == d.getFullYear()
29+
&& today.getUTCMonth() == d.getUTCMonth()
30+
&& today.getUTCDate() == d.getUTCDate()
31+
);
32+
return isToday;
33+
}

src/utils/FetchTimetable.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export async function fetchTimetable(time_start, time_end, group_id) {
2+
/**
3+
* Генерит запрос на получение расписания и возвращает промис с спаршенным ответом в словаре
4+
*/
5+
var url = new URL(`${process.env.VUE_APP_API_TIMETABLE}/timetable/event/`),
6+
params = {
7+
start: time_start.toISOString().slice(0, 10),
8+
end: time_end.toISOString().slice(0, 10),
9+
limit: 0,
10+
offset: 0,
11+
group_id: group_id
12+
};
13+
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]))
14+
return fetch(url).then(response => response.json());
15+
}

src/utils/Retrying.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export default function retry(callback, times, interval = 1000) {
2+
/**
3+
* Повторяет запрос, если произошла ошибка
4+
*
5+
*
6+
* Пример:
7+
* function randfail() {
8+
* let r = Math.random();
9+
* console.log(r);
10+
* if (r < 0.9) throw r;
11+
* return r;
12+
* }
13+
*
14+
* let res;
15+
* retry(() => {res = randfail()}, 100, 10)
16+
*/
17+
if (times > 0) {
18+
try {
19+
callback();
20+
} catch (error) {
21+
console.error(`Call failed, retrying... (${times} attempts left)`, error);
22+
setTimeout(() => retry(callback, times - 1, interval), interval);
23+
}
24+
}
25+
}

src/views/Timetable.vue

Lines changed: 118 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
<template>
22
<div class="timetable">
33
<div v-if="!this.loaded" class="lds-dual-ring"></div>
4-
<div v-else class='container'>
5-
<div class='info'>{{ this.groupInfo.number }} группа</div>
6-
<div class="no-events" v-if="!this.timetable.length">пары отсутствуют</div>
4+
<div v-else class="container">
5+
<div class="info">{{ this.groupInfo.number }} группа</div>
6+
<div class="no-events" v-if="!this.timetable.length">
7+
пары отсутствуют
8+
</div>
79
<ul v-else>
8-
<EventRow v-for="lesson of this.timetable" :key='lesson.id' :lesson="lesson" />
10+
<EventRow
11+
v-for="lesson of this.timetable"
12+
:key="lesson.id"
13+
:lesson="lesson"
14+
/>
915
</ul>
10-
1116
</div>
1217
</div>
1318
</template>
1419

1520
<script>
16-
import EventRow from '@/components/EventRow.vue'
21+
import EventRow from "@/components/EventRow.vue";
22+
import retry from "@/utils/Retrying.js";
23+
import { fetchTimetable } from "@/utils/FetchTimetable.js";
24+
import { getMonday, getMidnight, isToday } from "@/utils/Dates.js";
25+
1726
export default {
1827
name: "Timetable",
1928
data() {
@@ -22,78 +31,132 @@ export default {
2231
pageId: 1,
2332
date: new Date(),
2433
groupId: null,
25-
groupInfo: { number: '' },
26-
timetable: []
34+
groupInfo: { number: "" },
35+
timetable: [],
2736
};
2837
},
2938
components: {
3039
EventRow: EventRow,
3140
},
3241
methods: {
3342
loadGroupInfo() {
34-
var url = new URL(`${process.env.VUE_APP_API_TIMETABLE}/timetable/group/${this.groupId}`);
35-
fetch(url).then(response => response.json())
36-
.then(json => {
37-
this.groupInfo = json;
38-
})
43+
// Loading from cache if exists
44+
try {
45+
this.groupInfo = JSON.parse(
46+
localStorage.getItem("timetable-group-info") || '{"number": ""}'
47+
);
48+
} catch (err) {
49+
console.log("Can not take group info from cache", err);
50+
}
51+
52+
// Loading from internet else
53+
try {
54+
fetch(
55+
`${process.env.VUE_APP_API_TIMETABLE}/timetable/group/${this.groupId}`
56+
)
57+
.then((response) => response.json())
58+
.then((json) => {
59+
this.groupInfo = json;
60+
localStorage.setItem("timetable-group-info", JSON.stringify(json));
61+
});
62+
} catch (error) {
63+
console.error("Failed to load group info");
64+
}
3965
},
4066
loadTimetableOnDate(date) {
41-
var time_start = new Date(date);
42-
time_start.setHours(time_start.getHours() - date.getTimezoneOffset() / 60)
67+
var time_start = getMidnight(date);
4368
var time_end = new Date(time_start);
44-
time_end.setDate(time_start.getDate() + 1)
45-
var url = new URL(`${process.env.VUE_APP_API_TIMETABLE}/timetable/event/`),
46-
params = {
47-
start: time_start.toISOString().slice(0, 10),
48-
end: time_end.toISOString().slice(0, 10),
49-
limit: 0,
50-
offset: 0,
51-
group_id: this.groupId
52-
}
53-
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]))
54-
this.loaded = false;
55-
fetch(url).then(response => response.json())
56-
.then(json => {
57-
this.timetable = json.items;
58-
this.loaded = true;
59-
})
69+
time_end.setDate(time_start.getDate() + 1);
70+
71+
// Quering events from internet, trying 5 times with 1sec between
72+
try {
73+
retry(
74+
() =>
75+
fetchTimetable(time_start, time_end, this.groupId).then((json) => {
76+
this.timetable = json.items;
77+
this.loaded = true;
78+
console.log("Loaded from internet");
79+
}),
80+
5,
81+
1000
82+
);
83+
} catch (error) {
84+
console.log("Can not load from internet", error);
85+
}
6086
87+
// Loading from cache if exists
88+
let cached = JSON.parse(localStorage.getItem("timetable-cache") || "[]");
89+
cached = cached.filter((value) =>
90+
isToday(Date.parse(value.start_ts), date)
91+
);
92+
console.log(cached);
93+
if (!this.loaded && cached.length > 0) {
94+
this.timetable = cached;
95+
this.loaded = true;
96+
console.log(`Loaded ${cached.length} events from cache`);
97+
}
98+
},
99+
loadTimetableCache() {
100+
console.log("Caching timetable");
101+
// Загружает текущую неделю + следующую в кэш
102+
var time_start = getMonday(new Date());
103+
var time_end = new Date(time_start);
104+
time_end.setDate(time_start.getDate() + 14);
105+
retry(
106+
// Повтори загрузку недели трижды с интервалом 10 секунд
107+
() =>
108+
fetchTimetable(time_start, time_end, this.groupId).then((json) => {
109+
localStorage.setItem("timetable-cache", JSON.stringify(json.items));
110+
console.log(`Cached ${json.items.length} items`);
111+
}),
112+
3,
113+
10000
114+
);
61115
},
62116
swipeEventHandler(e) {
63-
var nextDate = new Date(this.date)
64-
if (e.detail.dir == 'left')
65-
nextDate.setDate(this.date.getDate() + 1);
66-
if (e.detail.dir == 'right')
67-
nextDate.setDate(this.date.getDate() - 1);
68-
document.dispatchEvent(new CustomEvent('change-main-date', { detail: { date: nextDate } }));
69-
}
70-
117+
var nextDate = new Date(this.date);
118+
if (e.detail.dir == "left") nextDate.setDate(this.date.getDate() + 1);
119+
if (e.detail.dir == "right") nextDate.setDate(this.date.getDate() - 1);
120+
document.dispatchEvent(
121+
new CustomEvent("change-main-date", { detail: { date: nextDate } })
122+
);
123+
},
124+
},
125+
watch: {
126+
date(newDate) {
127+
this.loaded = false;
128+
// 5 раз с интервалом в 1 секунду попробуй скачать расписание
129+
this.loadTimetableOnDate(newDate);
130+
},
71131
},
72132
beforeMount() {
73-
document.dispatchEvent(new CustomEvent("change-page", { detail: this.pageId }));
74-
document.addEventListener('change-date', (e) => {
75-
this.date = e.detail.date;
76-
this.loadTimetableOnDate(this.date);
77-
});
78-
133+
document.dispatchEvent(
134+
new CustomEvent("change-page", { detail: this.pageId })
135+
);
136+
document.addEventListener(
137+
"change-date",
138+
(e) => (this.date = e.detail.date)
139+
);
79140
},
80-
updated(){
81-
document.dispatchEvent(new CustomEvent("change-page", { detail: this.pageId }));
141+
updated() {
142+
document.dispatchEvent(
143+
new CustomEvent("change-page", { detail: this.pageId })
144+
);
82145
},
83146
mounted() {
84-
this.groupId = localStorage.getItem('timetable-group-id');
147+
this.groupId = localStorage.getItem("timetable-group-id");
85148
this.loadGroupInfo();
86-
document.dispatchEvent(new CustomEvent('sync-date'))
149+
document.dispatchEvent(new CustomEvent("sync-date"));
87150
// обработка свайпов
88151
document.addEventListener("swipe", this.swipeEventHandler);
152+
153+
// Загружаем кэш в память
154+
this.loadTimetableCache();
89155
},
90-
beforeUnmount(){
156+
beforeUnmount() {
91157
document.removeEventListener("swipe", this.swipeEventHandler);
92-
console.log('removed');
93-
}
94-
158+
},
95159
};
96-
97160
</script>
98161

99162
<style scoped>
@@ -116,7 +179,6 @@ ul {
116179
font-weight: 700;
117180
color: lightgray;
118181
text-transform: uppercase;
119-
120182
}
121183
122184
.container {
@@ -141,7 +203,7 @@ ul {
141203
142204
.info {
143205
height: 20px;
144-
font-family: 'Roboto';
206+
font-family: "Roboto";
145207
font-style: normal;
146208
font-weight: 700;
147209
font-size: 16px;

vue.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
const TerserPlugin = require('terser-webpack-plugin');
2+
3+
var debug = process.env.NODE_ENV !== "production";
4+
5+
16
module.exports = {
27
runtimeCompiler: true,
38
configureWebpack: {

0 commit comments

Comments
 (0)