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

Commit

Permalink
Cache (#44)
Browse files Browse the repository at this point in the history
Fixes #29 
Fixes #43
  • Loading branch information
dyakovri authored Oct 10, 2022
1 parent f4f18bb commit d04443f
Show file tree
Hide file tree
Showing 9 changed files with 360 additions and 103 deletions.
13 changes: 4 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,17 @@

## Project setup
```
npm install
yarn
```

### Steps to test your Vue single-spa application:
Compiles and hot-reloads for development
1. Run 'npm run serve'
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!
1. Run 'yarn serve'
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

### Compiles and minifies for production
```
npm run build
```

### Lints and fixes files
```
npm run lint
yarn build
```

### Customize configuration
Expand Down
8 changes: 7 additions & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
const productionPlugns = []

if (process.env.NODE_ENV == 'production')
productionPlugns.push(["transform-remove-console", { "exclude": ["error", "warn", "info"] }]);

module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
],
plugins: productionPlugns
}
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
"click-outside-vue3": "^4.0.1",
"core-js": "^3.6.5",
"single-spa-vue": "^2.1.0",
"vue": "^3.0.0",
"vue-router": "^4.0.0-0"
"vue": "3",
"vue-router": "^4.0.0-0",
"ejs": "^3.1.7",
"glob-parent": "^5.1.2",
"node-forge": "^1.3.0",
"nth-check": "^2.0.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.15",
Expand All @@ -23,6 +27,7 @@
"@vue/cli-service": "~4.5.15",
"@vue/compiler-sfc": "^3.0.0",
"babel-eslint": "^10.1.0",
"babel-plugin-transform-remove-console": "^6.9.4",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0",
"vue-cli-plugin-single-spa": "~3.3.0"
Expand Down
33 changes: 33 additions & 0 deletions src/utils/Dates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export function getMonday(d) {
/**
* Возвращает понедельник текущей недели
*/
d = getMidnight(d);
var day = d.getDay(),
diff = d.getDate() - day + (day == 0 ? -6 : 1); // adjust when day is sunday
return new Date(d.setDate(diff));
}


export function getMidnight(d) {
/**
* Возвращает начало сегодняшнего дня
*/
d = new Date(d);
d.setHours(d.getHours() - d.getTimezoneOffset() / 60);
return d;
}

export function isToday(d, today) {
/**
* Проверяет совпадение дат, но не времени
*/
d = new Date(d);
today = new Date(today);
let isToday = (
today.getFullYear() == d.getFullYear()
&& today.getUTCMonth() == d.getUTCMonth()
&& today.getUTCDate() == d.getUTCDate()
);
return isToday;
}
15 changes: 15 additions & 0 deletions src/utils/FetchTimetable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export async function fetchTimetable(time_start, time_end, group_id) {
/**
* Генерит запрос на получение расписания и возвращает промис с спаршенным ответом в словаре
*/
var url = new URL(`${process.env.VUE_APP_API_TIMETABLE}/timetable/event/`),
params = {
start: time_start.toISOString().slice(0, 10),
end: time_end.toISOString().slice(0, 10),
limit: 0,
offset: 0,
group_id: group_id
};
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]))
return fetch(url).then(response => response.json());
}
25 changes: 25 additions & 0 deletions src/utils/Retrying.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export default function retry(callback, times, interval = 1000) {
/**
* Повторяет запрос, если произошла ошибка
*
*
* Пример:
* function randfail() {
* let r = Math.random();
* console.log(r);
* if (r < 0.9) throw r;
* return r;
* }
*
* let res;
* retry(() => {res = randfail()}, 100, 10)
*/
if (times > 0) {
try {
callback();
} catch (error) {
console.error(`Call failed, retrying... (${times} attempts left)`, error);
setTimeout(() => retry(callback, times - 1, interval), interval);
}
}
}
174 changes: 118 additions & 56 deletions src/views/Timetable.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
<template>
<div class="timetable">
<div v-if="!this.loaded" class="lds-dual-ring"></div>
<div v-else class='container'>
<div class='info'>{{ this.groupInfo.number }} группа</div>
<div class="no-events" v-if="!this.timetable.length">пары отсутствуют</div>
<div v-else class="container">
<div class="info">{{ this.groupInfo.number }} группа</div>
<div class="no-events" v-if="!this.timetable.length">
пары отсутствуют
</div>
<ul v-else>
<EventRow v-for="lesson of this.timetable" :key='lesson.id' :lesson="lesson" />
<EventRow
v-for="lesson of this.timetable"
:key="lesson.id"
:lesson="lesson"
/>
</ul>

</div>
</div>
</template>

<script>
import EventRow from '@/components/EventRow.vue'
import EventRow from "@/components/EventRow.vue";
import retry from "@/utils/Retrying.js";
import { fetchTimetable } from "@/utils/FetchTimetable.js";
import { getMonday, getMidnight, isToday } from "@/utils/Dates.js";
export default {
name: "Timetable",
data() {
Expand All @@ -22,78 +31,132 @@ export default {
pageId: 1,
date: new Date(),
groupId: null,
groupInfo: { number: '' },
timetable: []
groupInfo: { number: "" },
timetable: [],
};
},
components: {
EventRow: EventRow,
},
methods: {
loadGroupInfo() {
var url = new URL(`${process.env.VUE_APP_API_TIMETABLE}/timetable/group/${this.groupId}`);
fetch(url).then(response => response.json())
.then(json => {
this.groupInfo = json;
})
// Loading from cache if exists
try {
this.groupInfo = JSON.parse(
localStorage.getItem("timetable-group-info") || '{"number": ""}'
);
} catch (err) {
console.log("Can not take group info from cache", err);
}
// Loading from internet else
try {
fetch(
`${process.env.VUE_APP_API_TIMETABLE}/timetable/group/${this.groupId}`
)
.then((response) => response.json())
.then((json) => {
this.groupInfo = json;
localStorage.setItem("timetable-group-info", JSON.stringify(json));
});
} catch (error) {
console.error("Failed to load group info");
}
},
loadTimetableOnDate(date) {
var time_start = new Date(date);
time_start.setHours(time_start.getHours() - date.getTimezoneOffset() / 60)
var time_start = getMidnight(date);
var time_end = new Date(time_start);
time_end.setDate(time_start.getDate() + 1)
var url = new URL(`${process.env.VUE_APP_API_TIMETABLE}/timetable/event/`),
params = {
start: time_start.toISOString().slice(0, 10),
end: time_end.toISOString().slice(0, 10),
limit: 0,
offset: 0,
group_id: this.groupId
}
Object.keys(params).forEach(key => url.searchParams.append(key, params[key]))
this.loaded = false;
fetch(url).then(response => response.json())
.then(json => {
this.timetable = json.items;
this.loaded = true;
})
time_end.setDate(time_start.getDate() + 1);
// Quering events from internet, trying 5 times with 1sec between
try {
retry(
() =>
fetchTimetable(time_start, time_end, this.groupId).then((json) => {
this.timetable = json.items;
this.loaded = true;
console.log("Loaded from internet");
}),
5,
1000
);
} catch (error) {
console.log("Can not load from internet", error);
}
// Loading from cache if exists
let cached = JSON.parse(localStorage.getItem("timetable-cache") || "[]");
cached = cached.filter((value) =>
isToday(Date.parse(value.start_ts), date)
);
console.log(cached);
if (!this.loaded && cached.length > 0) {
this.timetable = cached;
this.loaded = true;
console.log(`Loaded ${cached.length} events from cache`);
}
},
loadTimetableCache() {
console.log("Caching timetable");
// Загружает текущую неделю + следующую в кэш
var time_start = getMonday(new Date());
var time_end = new Date(time_start);
time_end.setDate(time_start.getDate() + 14);
retry(
// Повтори загрузку недели трижды с интервалом 10 секунд
() =>
fetchTimetable(time_start, time_end, this.groupId).then((json) => {
localStorage.setItem("timetable-cache", JSON.stringify(json.items));
console.log(`Cached ${json.items.length} items`);
}),
3,
10000
);
},
swipeEventHandler(e) {
var nextDate = new Date(this.date)
if (e.detail.dir == 'left')
nextDate.setDate(this.date.getDate() + 1);
if (e.detail.dir == 'right')
nextDate.setDate(this.date.getDate() - 1);
document.dispatchEvent(new CustomEvent('change-main-date', { detail: { date: nextDate } }));
}
var nextDate = new Date(this.date);
if (e.detail.dir == "left") nextDate.setDate(this.date.getDate() + 1);
if (e.detail.dir == "right") nextDate.setDate(this.date.getDate() - 1);
document.dispatchEvent(
new CustomEvent("change-main-date", { detail: { date: nextDate } })
);
},
},
watch: {
date(newDate) {
this.loaded = false;
// 5 раз с интервалом в 1 секунду попробуй скачать расписание
this.loadTimetableOnDate(newDate);
},
},
beforeMount() {
document.dispatchEvent(new CustomEvent("change-page", { detail: this.pageId }));
document.addEventListener('change-date', (e) => {
this.date = e.detail.date;
this.loadTimetableOnDate(this.date);
});
document.dispatchEvent(
new CustomEvent("change-page", { detail: this.pageId })
);
document.addEventListener(
"change-date",
(e) => (this.date = e.detail.date)
);
},
updated(){
document.dispatchEvent(new CustomEvent("change-page", { detail: this.pageId }));
updated() {
document.dispatchEvent(
new CustomEvent("change-page", { detail: this.pageId })
);
},
mounted() {
this.groupId = localStorage.getItem('timetable-group-id');
this.groupId = localStorage.getItem("timetable-group-id");
this.loadGroupInfo();
document.dispatchEvent(new CustomEvent('sync-date'))
document.dispatchEvent(new CustomEvent("sync-date"));
// обработка свайпов
document.addEventListener("swipe", this.swipeEventHandler);
// Загружаем кэш в память
this.loadTimetableCache();
},
beforeUnmount(){
beforeUnmount() {
document.removeEventListener("swipe", this.swipeEventHandler);
console.log('removed');
}
},
};
</script>

<style scoped>
Expand All @@ -116,7 +179,6 @@ ul {
font-weight: 700;
color: lightgray;
text-transform: uppercase;
}
.container {
Expand All @@ -141,7 +203,7 @@ ul {
.info {
height: 20px;
font-family: 'Roboto';
font-family: "Roboto";
font-style: normal;
font-weight: 700;
font-size: 16px;
Expand Down
5 changes: 5 additions & 0 deletions vue.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
const TerserPlugin = require('terser-webpack-plugin');

var debug = process.env.NODE_ENV !== "production";


module.exports = {
runtimeCompiler: true,
configureWebpack: {
Expand Down
Loading

0 comments on commit d04443f

Please sign in to comment.