Improve frontend readability, move modal to root component, add interceptors, fix usercard on XS displays

This commit is contained in:
Marco Realacci 2022-12-13 01:48:28 +01:00
parent 6840c34d7b
commit 4881fecbca
10 changed files with 220 additions and 287 deletions

View file

@ -1,23 +1,23 @@
{ {
"hash": "80447977", "hash": "0c3e4771",
"browserHash": "b98131b5", "browserHash": "01248067",
"optimized": { "optimized": {
"axios": { "axios": {
"src": "../../axios/index.js", "src": "../../axios/index.js",
"file": "axios.js", "file": "axios.js",
"fileHash": "6b5eae63", "fileHash": "d8d02cf0",
"needsInterop": true "needsInterop": true
}, },
"vue": { "vue": {
"src": "../../vue/dist/vue.runtime.esm-bundler.js", "src": "../../vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js", "file": "vue.js",
"fileHash": "de50261f", "fileHash": "f8034b26",
"needsInterop": false "needsInterop": false
}, },
"vue-router": { "vue-router": {
"src": "../../vue-router/dist/vue-router.mjs", "src": "../../vue-router/dist/vue-router.mjs",
"file": "vue-router.js", "file": "vue-router.js",
"fileHash": "4cad12d6", "fileHash": "b4ca170f",
"needsInterop": false "needsInterop": false
} }
}, },

View file

@ -1,59 +1,62 @@
<script setup> <script>
import { RouterLink, RouterView } from 'vue-router' export default {
import getCurrentSession from './services/authentication'; props: ["user_id", "name", "date", "comments", "likes", "photo_id", "liked"],
import { updateToken } from './services/axios'; data: function () {
return {
modalTitle: "Modal Title",
modalMsg: "Modal Message",
}
},
methods: {
showModal(title, message) {
this.modalTitle = title;
this.modalMsg = message;
// Simulate a click on the hidden modal button to open it
this.$refs.openModal.click();
},
},
mounted() {
// Configure axios interceptors
this.$axios.interceptors.response.use(response => {
// Leave response as is
return response;
}, error => {
if (error.response != undefined) {
// If the response is 401, redirect to /login
if (error.response.status === 401) {
this.$router.push({ path: '/login' })
return
}
// Show the error message from the server in a modal
this.showModal("Error " + error.response.status, error.response.data['status'])
return
}
// Show the error message from axios in a modal
this.showModal("Error", error.toString());
return error;
});
}
}
</script> </script>
<template> <template>
<!-- Invisible button to open the modal -->
<!--<header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow"> <button ref="openModal" type="button" class="btn btn-primary" style="display: none" data-bs-toggle="modal" data-bs-target="#modal" />
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3 fs-6" href="#/">WASAPhoto</a> <!-- Modal to show error messages -->
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation"> <Modal :title="modalTitle" :message="modalMsg" />
<span class="navbar-toggler-icon"></span>
</button>
</header>-->
<div class="container-fluid"> <div class="container-fluid">
<div class="row"> <div class="row">
<!--<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="position-sticky pt-3 sidebar-sticky">
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted text-uppercase">
<span>WASAPhoto</span>
</h6>
<ul class="nav flex-column">
<li class="nav-item">
<RouterLink to="/" class="nav-link">
<svg class="feather"><use href="/feather-sprite-v4.29.0.svg#home"/></svg>
Stream
</RouterLink>
</li>
<li class="nav-item">
<RouterLink to="/link1" class="nav-link">
<svg class="feather"><use href="/feather-sprite-v4.29.0.svg#layout"/></svg>
Search
</RouterLink>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted text-uppercase">
<span>Account</span>
</h6>
<ul class="nav flex-column">
<li class="nav-item">
<RouterLink :to="'/some/' + 'variable_here' + '/path'" class="nav-link">
<svg class="feather"><use href="/feather-sprite-v4.29.0.svg#file-text"/></svg>
Your profile
</RouterLink>
</li>
</ul>
</div>
</nav>-->
<!---<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">-->
<main class="mb-5"> <main class="mb-5">
<!-- The view is rendered here -->
<RouterView /> <RouterView />
</main> </main>
<!-- Bottom navigation buttons -->
<nav id="global-nav" class="navbar fixed-bottom navbar-light bg-light row"> <nav id="global-nav" class="navbar fixed-bottom navbar-light bg-light row">
<div class="collapse navbar-collapse" id="navbarNav"></div> <div class="collapse navbar-collapse" id="navbarNav"></div>
<RouterLink to="/" class="col-4 text-center"> <RouterLink to="/" class="col-4 text-center">
@ -71,6 +74,7 @@ import { updateToken } from './services/axios';
</template> </template>
<style> <style>
/* Make the active navigation button a little bit bigger */
#global-nav a.router-link-active { #global-nav a.router-link-active {
font-size: 1.2em font-size: 1.2em
} }

View file

@ -14,5 +14,3 @@ export default {
</div> </div>
<div v-if="!loading"><slot /></div> <div v-if="!loading"><slot /></div>
</template> </template>
<style></style>

View file

@ -1,12 +1,9 @@
<script> <script>
import getCurrentSession from '../services/authentication';
export default { export default {
props: ["user_id", "name", "date", "comments", "likes", "photo_id", "liked"], props: ["user_id", "name", "date", "comments", "likes", "photo_id", "liked"],
data: function() { data: function() {
return { return {
imageSrc: "", imageSrc: "",
errorMsg: null,
post_liked: this.liked, post_liked: this.liked,
post_like_cnt: this.likes, post_like_cnt: this.likes,
comments_data: [], comments_data: [],
@ -19,17 +16,14 @@ export default {
postComment() { postComment() {
this.$axios.post("/users/" + this.user_id + "/photos/" + this.photo_id + "/comments", { this.$axios.post("/users/" + this.user_id + "/photos/" + this.photo_id + "/comments", {
"comment": this.commentMsg, "comment": this.commentMsg,
"user_id": getCurrentSession(), "user_id": this.$currentSession(),
}).then(response => { }).then(response => {
this.commentMsg = ""; this.commentMsg = "";
this.comments_data = []; this.comments_data = [];
this.comments_start_idx = 0; this.comments_start_idx = 0;
this.getComments(); this.getComments();
}).catch(error => { })
console.log(error);
this.errorMsg = error.toString();
});
}, },
getComments() { getComments() {
this.$axios.get("/users/" + this.user_id + "/photos/" + this.photo_id + this.$axios.get("/users/" + this.user_id + "/photos/" + this.photo_id +
@ -40,29 +34,19 @@ export default {
this.comments_data = this.comments_data.concat(response.data); this.comments_data = this.comments_data.concat(response.data);
this.comments_shown = true; this.comments_shown = true;
//alert(this.comments[0]["comment"]); })
}).catch(error => {
console.log(error);
this.errorMsg = error.toString();
});
}, },
like() { like() {
this.$axios.put("/users/" + this.user_id + "/photos/" + this.photo_id + "/likes/" + getCurrentSession()).then(response => { this.$axios.put("/users/" + this.user_id + "/photos/" + this.photo_id + "/likes/" + this.$currentSession()).then(response => {
this.post_liked = true; this.post_liked = true;
this.post_like_cnt++; this.post_like_cnt++;
}).catch(error => { })
console.log(error);
this.errorMsg = error.toString();
});
}, },
unlike() { unlike() {
this.$axios.delete("/users/" + this.user_id + "/photos/" + this.photo_id + "/likes/" + getCurrentSession()).then(response => { this.$axios.delete("/users/" + this.user_id + "/photos/" + this.photo_id + "/likes/" + this.$currentSession()).then(response => {
this.post_liked = false; this.post_liked = false;
this.post_like_cnt--; this.post_like_cnt--;
}).catch(error => { })
console.log(error);
this.errorMsg = error.toString();
});
}, },
}, },
@ -81,7 +65,6 @@ export default {
<template> <template>
<div class="card mb-5"> <div class="card mb-5">
<!--<img v-auth-img="imageSrc" class="card-img-top" alt="Chicago Skyscrapers"/>-->
<div ref="imageContainer"></div> <div ref="imageContainer"></div>
<div class="container"> <div class="container">
@ -126,14 +109,7 @@ export default {
</div> </div>
</div> </div>
</div> </div>
<!--<ul class="list-group list-group-light list-group-small">
<li class="list-group-item px-4">Cras justo odio</li>
<li class="list-group-item px-4">Dapibus ac facilisis in</li>
<li class="list-group-item px-4">Vestibulum at eros</li>
</ul>-->
</div> </div>
<ErrorMsg v-if="errormsg" :msg="errormsg"></ErrorMsg>
</template> </template>
<style> <style>

View file

@ -1,36 +1,27 @@
<script> <script>
import getCurrentSession from '../services/authentication';
export default { export default {
props: ["user_id", "name", "followed", "banned", "show_new_post"], props: ["user_id", "name", "followed", "banned", "show_new_post"],
watch: { watch: {
banned: function(new_val, old_val) { banned: function (new_val, old_val) {
this.user_banned = new_val; this.user_banned = new_val;
}, },
followed: function(new_val, old_val) { followed: function (new_val, old_val) {
this.user_followed = new_val; this.user_followed = new_val;
}, },
}, },
data: function() { data: function () {
return { return {
errorMsg: "aaa", errorMsg: "aaa",
user_followed: this.followed, user_followed: this.followed,
user_banned: this.banned, user_banned: this.banned,
myself: getCurrentSession() == this.user_id, myself: this.$currentSession() == this.user_id,
show_post_form: false, show_post_form: false,
show_username_form: false, show_username_form: false,
newUsername: "", newUsername: "",
upload_file: null, upload_file: null,
modalTitle: "",
modalMsg: "",
} }
}, },
methods: { methods: {
playModal(title, msg) {
this.modalTitle = title
this.modalMsg = msg
this.$refs.openModal.click()
},
logout() { logout() {
localStorage.removeItem("token"); localStorage.removeItem("token");
sessionStorage.removeItem("token"); sessionStorage.removeItem("token");
@ -40,36 +31,32 @@ export default {
this.$router.push({ path: "/profile/" + this.user_id }); this.$router.push({ path: "/profile/" + this.user_id });
}, },
follow() { follow() {
this.$axios.put("/users/" + this.user_id + "/followers/" + getCurrentSession()) this.$axios.put("/users/" + this.user_id + "/followers/" + this.$currentSession())
.then(response => { .then(response => {
this.user_followed = true this.user_followed = true
this.$emit('updateInfo') this.$emit('updateInfo')
}) })
.catch(error => this.playModal("Error", error.toString()));
}, },
unfollow() { unfollow() {
this.$axios.delete("/users/" + this.user_id + "/followers/" + getCurrentSession()) this.$axios.delete("/users/" + this.user_id + "/followers/" + this.$currentSession())
.then(response => { .then(response => {
this.user_followed = false this.user_followed = false
this.$emit('updateInfo') this.$emit('updateInfo')
}) })
.catch(error => this.playModal("Error", error.toString()));
}, },
ban() { ban() {
this.$axios.put("/users/" + getCurrentSession() + "/bans/" + this.user_id) this.$axios.put("/users/" + this.$currentSession() + "/bans/" + this.user_id)
.then(response => { .then(response => {
this.user_banned = true this.user_banned = true
this.$emit('updateInfo') this.$emit('updateInfo')
}) })
.catch(error => this.playModal("Error", error.toString()));
}, },
unban() { unban() {
this.$axios.delete("/users/" + getCurrentSession() + "/bans/" + this.user_id) this.$axios.delete("/users/" + this.$currentSession() + "/bans/" + this.user_id)
.then(response => { .then(response => {
this.user_banned = false this.user_banned = false
this.$emit('updateInfo') this.$emit('updateInfo')
}) })
.catch(error => this.playModal("Error", error.toString()));
}, },
load_file(e) { load_file(e) {
let files = e.target.files || e.dataTransfer.files; let files = e.target.files || e.dataTransfer.files;
@ -77,38 +64,19 @@ export default {
this.upload_file = files[0]; this.upload_file = files[0];
}, },
submit_file() { submit_file() {
this.$axios.post("/users/" + getCurrentSession() + "/photos", this.upload_file) this.$axios.post("/users/" + this.$currentSession() + "/photos", this.upload_file)
.then(response => { .then(response => {
this.show_post_form = false this.show_post_form = false
this.$emit('updatePosts') this.$emit('updatePosts')
}) })
.catch(error => {
if (error.response.status != null && error.response.data != null) {
this.modalTitle = "Error"
this.modalMsg = error.response.data
this.$refs.openModal.click()
} else {
this.playModal("Error", error.toString())
}
this.playModal("Error", error.toString())
});
}, },
updateUsername() { updateUsername() {
this.$axios.put("/users/" + getCurrentSession() + "/username", {name: this.newUsername}) this.$axios.put("/users/" + this.$currentSession() + "/username", { name: this.newUsername })
.then(response => { .then(response => {
this.show_username_form = false this.show_username_form = false
this.$emit('updateInfo') this.$emit('updateInfo')
this.name = this.newUsername this.name = this.newUsername
}) })
.catch(error => {
if (error.response.status == 409) {
this.modalTitle = "Error"
this.modalMsg = "The chosen username is already taken."
this.$refs.openModal.click()
} else {
this.playModal("Error", error.toString())
}
});
}, },
}, },
created() { created() {
@ -117,37 +85,51 @@ export default {
</script> </script>
<template> <template>
<button ref="openModal" type="button" class="btn btn-primary" style="display: none" data-bs-toggle="modal" data-bs-target="#modal" />
<Modal :title="modalTitle" :message="modalMsg" />
<div class="card mb-3"> <div class="card mb-3">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-10"> <div class="col-5">
<div class="card-body h-100 d-flex align-items-center"> <div class="card-body h-100 d-flex align-items-center">
<a @click="visit"><h5 class="card-title mb-0">{{ name }}</h5></a> <a @click="visit">
<h5 class="card-title mb-0">{{ name }}</h5>
</a>
</div> </div>
</div> </div>
<div class="col-2"> <div class="d-flex flex-column" v-bind:class="{
<div class="card-body d-flex justify-content-end"> 'col-12': (myself && show_new_post),
'col-sm-7': (myself && show_new_post),
'col-7': !(myself && show_new_post),
'align-items-end': !(myself && show_new_post),
'align-items-sm-end': (myself && show_new_post),
}">
<div class="card-body d-flex">
<div v-if="!myself" class="d-flex"> <div v-if="!myself" class="d-flex">
<button v-if="!user_banned" @click="ban" type="button" class="btn btn-outline-danger me-2">Ban</button> <button v-if="!user_banned" @click="ban" type="button"
<button v-if="user_banned" @click="unban" type="button" class="btn btn-danger me-2">Banned</button> class="btn btn-outline-danger me-2">Ban</button>
<button v-if="!user_followed" @click="follow" type="button" class="btn btn-primary">Follow</button> <button v-if="user_banned" @click="unban" type="button"
<button v-if="user_followed" @click="unfollow" type="button" class="btn btn-outline-primary">Following</button> class="btn btn-danger me-2">Banned</button>
<button v-if="!user_followed" @click="follow" type="button"
class="btn btn-primary">Follow</button>
<button v-if="user_followed" @click="unfollow" type="button"
class="btn btn-outline-primary">Following</button>
</div> </div>
<div v-if="(myself && !show_new_post)"> <div v-if="(myself && !show_new_post)">
<button disabled type="button" class="btn btn-secondary">Yourself</button> <button disabled type="button" class="btn btn-secondary">Yourself</button>
</div> </div>
<div v-if="(myself && show_new_post)" class="d-flex"> <div v-if="(myself && show_new_post)" class="col">
<button type="button" class="btn btn-outline-danger me-2" @click="logout">Logout</button> <button type="button" class="btn btn-outline-danger me-2" @click="logout">Logout</button>
</div> </div>
<div v-if="(myself && show_new_post)" class="d-flex"> <div class="d-flex col justify-content-end flex-row">
<button v-if="!show_username_form" type="button" class="btn btn-outline-secondary me-2" @click="show_username_form = true">Username</button> <div v-if="(myself && show_new_post)" class="">
<button v-if="!show_username_form" type="button" class="btn btn-outline-secondary me-2"
@click="show_username_form = true">Username</button>
</div>
<div v-if="(myself && show_new_post)" class="">
<button v-if="!show_post_form" type="button" class="btn btn-primary"
@click="show_post_form = true">Post</button>
</div> </div>
<div v-if="(myself && show_new_post)" class="d-flex">
<button v-if="!show_post_form" type="button" class="btn btn-primary" @click="show_post_form = true">Post</button>
</div> </div>
</div> </div>
</div> </div>
@ -168,7 +150,8 @@ export default {
<div class="row" v-if="show_username_form"> <div class="row" v-if="show_username_form">
<div class="col-10"> <div class="col-10">
<div class="card-body h-100 d-flex align-items-center"> <div class="card-body h-100 d-flex align-items-center">
<input v-model="newUsername" class="form-control form-control-lg" id="formUsername" placeholder="Your new fantastic username! 😜" /> <input v-model="newUsername" class="form-control form-control-lg" id="formUsername"
placeholder="Your new fantastic username! 😜" />
</div> </div>
</div> </div>

View file

@ -2,6 +2,7 @@ import {createApp, reactive} from 'vue'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import { axios, updateToken as axiosUpdate } from './services/axios.js'; import { axios, updateToken as axiosUpdate } from './services/axios.js';
import getCurrentSession from './services/authentication';
import ErrorMsg from './components/ErrorMsg.vue' import ErrorMsg from './components/ErrorMsg.vue'
import LoadingSpinner from './components/LoadingSpinner.vue' import LoadingSpinner from './components/LoadingSpinner.vue'
import PostCard from './components/PostCard.vue' import PostCard from './components/PostCard.vue'
@ -12,13 +13,18 @@ import 'bootstrap-icons/font/bootstrap-icons.css'
import './assets/dashboard.css' import './assets/dashboard.css'
import './assets/main.css' import './assets/main.css'
// Create the Vue SPA
const app = createApp(App) const app = createApp(App)
app.config.globalProperties.$axios = axios; app.config.globalProperties.$axios = axios;
app.config.globalProperties.$axiosUpdate = axiosUpdate; app.config.globalProperties.$axiosUpdate = axiosUpdate;
app.config.globalProperties.$currentSession = getCurrentSession;
// Register the components
app.component("ErrorMsg", ErrorMsg); app.component("ErrorMsg", ErrorMsg);
app.component("LoadingSpinner", LoadingSpinner); app.component("LoadingSpinner", LoadingSpinner);
app.component("PostCard", PostCard); app.component("PostCard", PostCard);
app.component("UserCard", UserCard); app.component("UserCard", UserCard);
app.component("Modal", Modal); app.component("Modal", Modal);
app.use(router) app.use(router)
app.mount('#app') app.mount('#app')

View file

@ -1,5 +1,4 @@
<script> <script>
import getCurrentSession from '../services/authentication';
export default { export default {
data: function() { data: function() {
return { return {
@ -13,6 +12,9 @@ export default {
}, },
methods: { methods: {
async refresh() { async refresh() {
// this way we are sure that we fill the first page
// 450 is a bit more of the max height of a post
// todo: may not work in 4k screens :/
this.limit = Math.round(window.innerHeight / 450); this.limit = Math.round(window.innerHeight / 450);
this.start_idx = 0; this.start_idx = 0;
this.data_ended = false; this.data_ended = false;
@ -28,9 +30,7 @@ export default {
else this.stream_data = this.stream_data.concat(response.data); else this.stream_data = this.stream_data.concat(response.data);
this.loading = false; this.loading = false;
} catch (e) { } catch (e) {
if (e.response.status == 401) { // todo: handle better
this.$router.push({ path: "/login" });
}
this.errormsg = e.toString(); this.errormsg = e.toString();
} }
}, },
@ -45,12 +45,8 @@ export default {
}, },
}, },
mounted() { mounted() {
// this way we are sure that we fill the first page
// 450 is a bit more of the max height of a post
// todo: may not work in 4k screens :/
this.limit = Math.round(window.innerHeight / 450);
this.scroll(); this.scroll();
this.loadContent(); this.refresh();
} }
} }
</script> </script>
@ -87,9 +83,5 @@ export default {
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<style>
</style>

View file

@ -1,6 +1,4 @@
<script> <script>
import LoadingSpinner from '../components/LoadingSpinner.vue';
export default { export default {
data: function () { data: function () {
return { return {
@ -34,7 +32,8 @@ export default {
// Update the header // Update the header
this.$axiosUpdate(); this.$axiosUpdate();
this.$router.push({ path: "/" }); // Go back to the previous page
this.$router.go(-1);
} }
else { else {
this.errormsg = response.data["error"]; this.errormsg = response.data["error"];
@ -45,22 +44,7 @@ export default {
} }
this.loading = false; this.loading = false;
}, },
async refresh() {
//this.loading = true;
//this.errormsg = null;
//try {
// let response = await this.$axios.get("/");
// this.some_data = response.data;
//} catch (e) {
// this.errormsg = e.toString();
//}
this.loading = false;
}, },
},
mounted() {
this.refresh();
},
components: { LoadingSpinner }
} }
</script> </script>

View file

@ -1,5 +1,4 @@
<script> <script>
import getCurrentSession from "../services/authentication";
export default { export default {
data: function() { data: function() {
return { return {
@ -16,6 +15,10 @@ export default {
methods: { methods: {
async refresh() { async refresh() {
this.getMainData(); this.getMainData();
// this way we are sure that we fill the first page todo: check
// 450 is a bit more of the max height of a post
// todo: may not work in 4k screens :/
this.limit = Math.round(window.innerHeight / 450); this.limit = Math.round(window.innerHeight / 450);
this.start_idx = 0; this.start_idx = 0;
this.data_ended = false; this.data_ended = false;
@ -41,9 +44,7 @@ export default {
else this.stream_data = this.stream_data.concat(response.data); else this.stream_data = this.stream_data.concat(response.data);
this.loading = false; this.loading = false;
} catch (e) { } catch (e) {
if (e.response.status == 401) { // todo: move from here // todo: handle better
this.$router.push({ path: "/login" });
}
this.errormsg = e.toString(); this.errormsg = e.toString();
} }
}, },
@ -58,21 +59,15 @@ export default {
}, },
}, },
created() { created() {
// this way we are sure that we fill the first page
// 450 is a bit more of the max height of a post
// todo: may not work in 4k screens :/
if (this.$route.params.user_id == "me") { if (this.$route.params.user_id == "me") {
//this.$router.replace({ path: "/profile/" + }); //this.$router.replace({ path: "/profile/" + }); (It's ok to not redirect, it's just a matter of taste)
this.requestedProfile = getCurrentSession(); this.requestedProfile = this.$currentSession();
}else { } else {
this.requestedProfile = this.$route.params.user_id; this.requestedProfile = this.$route.params.user_id;
} }
this.getMainData();
this.limit = Math.round(window.innerHeight / 450);
this.scroll(); this.scroll();
this.loadContent(); this.refresh();
} }
} }
</script> </script>
@ -90,7 +85,7 @@ export default {
:name = "user_data['name']" :name = "user_data['name']"
:followed = "user_data['followed']" :followed = "user_data['followed']"
:banned = "user_data['banned']" :banned = "user_data['banned']"
:my_id = "getCurrentSession" :my_id = "this.$currentSession"
:show_new_post = "true" :show_new_post = "true"
@updateInfo = "getMainData" @updateInfo = "getMainData"
@updatePosts = "refresh" /> @updatePosts = "refresh" />
@ -128,9 +123,7 @@ export default {
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<style> <style>
</style> </style>

View file

@ -1,50 +1,47 @@
<script> <script>
import getCurrentSession from '../services/authentication'; // import getCurrentSession from '../services/authentication'; todo: can be removed
export default { export default {
data: function() { data: function() {
return { return {
errormsg: null, errormsg: null,
loading: false, loading: false,
stream_data: [], streamData: [],
data_ended: false, dataEnded: false,
start_idx: 0, startIdx: 0,
limit: 1, limit: 1,
field_username: "", fieldUsername: "",
} }
}, },
methods: { methods: {
async refresh() { async refresh() {
this.limit = Math.round(window.innerHeight / 72); this.limit = Math.round(window.innerHeight / 72);
this.start_idx = 0; this.startIdx = 0;
this.data_ended = false; this.dataEnded = false;
this.stream_data = []; this.streamData = [];
this.loadContent(); this.loadContent();
}, },
async loadContent() { async loadContent() {
this.loading = true; this.loading = true;
this.errormsg = null; this.errormsg = null;
if (this.field_username == "") { if (this.fieldUsername == "") {
this.errormsg = "Please enter a username"; this.errormsg = "Please enter a username";
this.loading = false; this.loading = false;
return; return;
} }
try { try {
let response = await this.$axios.get("/users?query=" + this.field_username + "&start_index=" + this.start_idx + "&limit=" + this.limit); let response = await this.$axios.get("/users?query=" + this.fieldUsername + "&start_index=" + this.startIdx + "&limit=" + this.limit);
if (response.data.length == 0) this.data_ended = true; if (response.data.length == 0) this.dataEnded = true;
else this.stream_data = this.stream_data.concat(response.data); else this.streamData = this.streamData.concat(response.data);
this.loading = false; this.loading = false;
} catch (e) { } catch (e) {
this.errormsg = e.toString(); this.errormsg = e.toString(); // todo: handle better
if (e.response.status == 401) {
this.$router.push({ path: "/login" });
}
} }
}, },
scroll () { scroll () {
window.onscroll = () => { window.onscroll = () => {
let bottomOfWindow = Math.max(window.pageYOffset, document.documentElement.scrollTop, document.body.scrollTop) + window.innerHeight === document.documentElement.offsetHeight let bottomOfWindow = Math.max(window.pageYOffset, document.documentElement.scrollTop, document.body.scrollTop) + window.innerHeight === document.documentElement.offsetHeight
if (bottomOfWindow && !this.data_ended) { if (bottomOfWindow && !this.dataEnded) {
this.start_idx += this.limit; this.startIdx += this.limit;
this.loadContent(); this.loadContent();
} }
} }
@ -71,11 +68,11 @@ export default {
<ErrorMsg v-if="errormsg" :msg="errormsg"></ErrorMsg> <ErrorMsg v-if="errormsg" :msg="errormsg"></ErrorMsg>
<div class="form-floating mb-4"> <div class="form-floating mb-4">
<input v-model="field_username" @input="refresh" id="formUsername" class="form-control" placeholder="name@example.com"/> <input v-model="fieldUsername" @input="refresh" id="formUsername" class="form-control" placeholder="name@example.com"/>
<label class="form-label" for="formUsername">Search by username</label> <label class="form-label" for="formUsername">Search by username</label>
</div> </div>
<div id="main-content" v-for="item of stream_data"> <div id="main-content" v-for="item of streamData">
<UserCard <UserCard
:user_id="item.user_id" :user_id="item.user_id"
:name="item.name" :name="item.name"