Improve code readability

This commit is contained in:
Marco Realacci 2023-01-12 01:45:14 +01:00
parent 542be61281
commit 83b7eb46a2
9 changed files with 167 additions and 26 deletions

View file

@ -1,10 +1,12 @@
<script>
export default {
// The error message to display
props: ['msg']
}
</script>
<template>
<!-- This component renders an error message -->
<div class="alert alert-danger" role="alert">
{{ msg }}
</div>

View file

@ -1,4 +1,9 @@
<script>
// This component emits an event when the sentinal element is intersecting the viewport
// (used to load more content when the user scrolls down)
// This component uses JavaScript's IntersectionObserver API
export default {
name: 'IntersectionObserver',
props: {
@ -9,10 +14,12 @@ export default {
},
data() {
return {
// Whether the sentinal element is intersecting the viewport
isIntersectingElement: false,
}
},
watch: {
// Emit an event when the sentinal element is intersecting the viewport
isIntersectingElement: function (value) {
if (!value) return
this.$emit('on-intersection-element')
@ -20,6 +27,8 @@ export default {
},
mounted() {
const sentinal = this.$refs[this.sentinalName]
// Create an observer to check if the sentinal element is intersecting the viewport
const handler = (entries) => {
if (entries[0].isIntersecting) {
this.isIntersectingElement = true
@ -35,5 +44,6 @@ export default {
</script>
<template>
<!-- The sentinal element -->
<div :ref="sentinalName" class="w-full h-px relative" />
</template>

View file

@ -11,12 +11,18 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<!-- Modal title -->
<h5 class="modal-title" id="modalLabel">{{ title }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- Modal body -->
<div class="modal-body">
{{ message }}
</div>
<!-- Footer with close button -->
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>

View file

@ -116,6 +116,8 @@ export default {
<template>
<div class="card mb-5">
<!-- Image container div -->
<div ref="imageContainer">
<div v-if="!imageReady" class="mt-3 mb-3">
<LoadingSpinner :loading="!imageReady" />
@ -124,6 +126,8 @@ export default {
<div class="container">
<div class="row">
<!-- Username and date -->
<div class="col-10">
<div class="card-body">
<h5 @click="visitUser" class="card-title d-inline-block" style="cursor: pointer">{{ name }}</h5>
@ -131,9 +135,9 @@ export default {
</div>
</div>
<!-- Comment and like buttons -->
<div class="col-2">
<div class="card-body d-flex justify-content-end" style="display: inline-flex">
<!-- not quite sure flex is the right property, but it works -->
<a @click="showHideComments">
<h5><i class="card-title bi bi-chat-right pe-1"></i></h5>
</a>
@ -149,6 +153,8 @@ export default {
</div>
</div>
</div>
<!-- Comments section -->
<div v-if="comments_shown">
<div v-for="item of comments_data" class="row" v-bind:key="item.comment_id">
<div class="col-7 card-body border-top">
@ -158,13 +164,21 @@ export default {
{{ new Date(Date.parse(item.date)).toDateString() }}
</div>
</div>
<!-- Show more comments label -->
<div v-if="!data_ended" class="col-12 card-body text-end pt-0 pb-1 px-0">
<a @click="getComments" class="text-primary">Mostra altro...</a>
</div>
<!-- New comment form -->
<div class="row">
<!-- Comment input -->
<div class="col-10 card-body border-top text-end">
<input v-model="commentMsg" type="text" class="form-control" placeholder="Commenta...">
</div>
<!-- Comment publish button -->
<div class="col-1 card-body border-top text-end ps-0 d-flex">
<button style="width: 100%" type="button" class="btn btn-primary"
@click="postComment">Go</button>

View file

@ -127,6 +127,7 @@ export default {
</div>
</div>
<!-- Whether to show one or two rows -->
<div class="d-flex flex-column" v-bind:class="{
'col-12': (myself && show_new_post),
'col-sm-7': (myself && show_new_post),
@ -135,6 +136,7 @@ export default {
'align-items-sm-end': (myself && show_new_post),
}">
<!-- Buttons -->
<div class="card-body d-flex">
<div v-if="!myself" class="d-flex">
<button v-if="!user_banned" @click="ban" type="button"
@ -146,17 +148,26 @@ export default {
<button v-if="user_followed" @click="unfollow" type="button"
class="btn btn-outline-primary">Following</button>
</div>
<!-- Users cannot follow or ban themselves -->
<div v-if="(myself && !show_new_post)">
<button disabled type="button" class="btn btn-secondary">Yourself</button>
</div>
<!-- Logout button -->
<div v-if="(myself && show_new_post)" class="col">
<button type="button" class="btn btn-outline-danger me-2" @click="logout">Logout</button>
</div>
<div class="d-flex col justify-content-end flex-row">
<!-- Update 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>
<!-- Post a new photo button -->
<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>
@ -165,6 +176,8 @@ export default {
</div>
</div>
</div>
<!-- File input -->
<div class="row" v-if="show_post_form">
<div class="col-9">
<div class="card-body h-100 d-flex align-items-center">
@ -172,13 +185,18 @@ export default {
</div>
</div>
<!-- Publish button -->
<div class="col-3">
<div class="card-body d-flex justify-content-end">
<button type="button" class="btn btn-primary btn-lg" @click="submit_file">Publish</button>
</div>
</div>
</div>
<!-- New username form -->
<div class="row" v-if="show_username_form">
<!-- Username input -->
<div class="col-10">
<div class="card-body h-100 d-flex align-items-center">
<input v-model="newUsername" class="form-control form-control-lg" id="formUsername"
@ -186,6 +204,7 @@ export default {
</div>
</div>
<!-- Username update button -->
<div class="col-2">
<div class="card-body d-flex justify-content-end">
<button type="button" class="btn btn-primary btn-lg" @click="updateUsername">Set</button>

View file

@ -34,6 +34,8 @@ export default {
this.start_idx = 0;
this.data_ended = false;
this.stream_data = [];
// Fetch the first batch of posts
this.loadContent();
},
@ -86,27 +88,36 @@ export default {
<div class="col-xl-6 col-lg-9">
<h3 class="card-title border-bottom mb-4 pb-2 text-center">Your daily WASAStream!</h3>
<!-- Show a message if there's no content to show -->
<div v-if="(stream_data.length == 0)" class="alert alert-secondary text-center" role="alert">
There's nothing here 😢
<br />Why don't you start following somebody? 👻
</div>
<!-- The stream -->
<div id="main-content" v-for="item of stream_data" v-bind:key="item.photo_id">
<!-- PostCard for each photo -->
<PostCard :user_id="item.user_id" :photo_id="item.photo_id" :name="item.name" :date="item.date"
:comments="item.comments" :likes="item.likes" :liked="item.liked" />
</div>
<!-- Show a message if there's no more content to show -->
<div v-if="(data_ended && !(stream_data.length == 0))" class="alert alert-secondary text-center" role="alert">
This is the end of your stream. Hooray! 👻
</div>
<!-- The loading spinner -->
<LoadingSpinner :loading="loading" /><br />
<div class="d-flex align-items-center flex-column">
<!-- Retry button -->
<button v-if="loadingError" @click="refresh" class="btn btn-secondary w-100 py-3">Retry</button>
<!-- Load more button -->
<button v-if="(!data_ended && !loading)" @click="loadMore" class="btn btn-secondary py-1 mb-5"
style="border-radius: 15px">Load more</button>
<!-- The IntersectionObserver for dynamic loading -->
<IntersectionObserver sentinal-name="load-more-home" @on-intersection-element="loadMore" />
</div>
</div>

View file

@ -2,18 +2,26 @@
export default {
data: function () {
return {
// The error message to display
errormsg: null,
// Loading spinner state
loading: false,
some_data: null,
// Form inputs
field_username: "",
rememberLogin: false,
};
},
methods: {
// Send the login request to the server
// if the login is successful, the token is saved
// and the user is redirected to the previous page
async login() {
this.loading = true;
this.errormsg = null;
// Send the login request
let response = await this.$axios.post("/session", {
name: this.field_username,
});
@ -24,6 +32,7 @@ export default {
return
}
// If the login is successful, save the token and redirect to the previous page
if (response.status == 201 || response.status == 200) {
// Save the token in the local storage if the user wants to be remembered
if (this.rememberLogin) {
@ -44,9 +53,10 @@ export default {
this.$router.go(-1);
}
else {
// Login failed, show the error message
this.errormsg = response.data["error"];
}
// Disable the loading spinner
this.loading = false;
},
},
@ -54,6 +64,7 @@ export default {
</script>
<template>
<!-- Login form centered in the page -->
<div class="vh-100 container py-5 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<!--<div class="col-sm"><h2>* immagina un logo carino *</h2></div>-->

View file

@ -4,64 +4,95 @@ import IntersectionObserver from '../components/IntersectionObserver.vue';
export default {
data: function () {
return {
// The profile to show
requestedProfile: this.$route.params.user_id,
// Loading flags
loading: true,
loadingError: false,
// Profile data from the server
user_data: [],
// Protos data from the server
stream_data: [],
// Dynamic loading parameters
data_ended: false,
start_idx: 0,
limit: 1,
user_data: [],
};
},
methods: {
async refresh() {
// Fetch profile info from the server
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 :/
// Limits the number of posts to load based on the window height
// to avoid loading too many posts at once
// 450px is (a bit more) of the height of a single post
this.limit = Math.max(Math.round(window.innerHeight / 450), 1);
// Reset the parameters and the data
this.start_idx = 0;
this.data_ended = false;
this.stream_data = [];
// Fetch the first batch of posts
this.loadContent();
},
// Fetch profile info from the server
async getMainData() {
let response = await this.$axios.get("/users/" + this.requestedProfile);
if (response == null) {
// An error occurred, set the error flag
this.loading = false;
this.loadingError = true;
return;
}
this.user_data = response.data;
},
// Fetch photos from the server
async loadContent() {
this.loading = true;
let response = await this.$axios.get("/users/" + this.requestedProfile + "/photos" + "?start_index=" + this.start_idx + "&limit=" + this.limit);
if (response == null) {
// do something
return;
}
if (response == null) return // An error occurred. The interceptor will show a modal
// If the server returned less elements than requested,
// it means that there are no more photos to load
if (response.data.length == 0 || response.data.length < this.limit)
this.data_ended = true;
this.stream_data = this.stream_data.concat(response.data);
this.loading = false;
this.data_ended = true
// Append the new photos to the array
this.stream_data = this.stream_data.concat(response.data)
// Disable the loading spinner
this.loading = false
},
// Load more photos when the user scrolls to the bottom of the page
loadMore() {
// Avoid sending a request if there are no more photos
if (this.loading || this.data_ended) return
// Increase the start index and load more photos
this.start_idx += this.limit
this.loadContent()
},
},
created() {
if (this.$route.params.user_id == "me") {
//this.$router.replace({ path: "/profile/" + }); (It's ok to not redirect, it's just a matter of taste)
// If the id is "me", show the current user's profile
this.requestedProfile = this.$currentSession();
}
else {
// Otherwise, show "id"'s profile
this.requestedProfile = this.$route.params.user_id;
}
//this.scroll();
// Fetch the profile info and the first batch of photos
this.refresh();
},
components: { IntersectionObserver }
@ -75,10 +106,12 @@ export default {
<div class="row justify-content-md-center">
<div class="col-xl-6 col-lg-9">
<!-- User card for profile info -->
<UserCard :user_id="requestedProfile" :name="user_data['name']" :followed="user_data['followed']"
:banned="user_data['banned']" :my_id="this.$currentSession" :show_new_post="true"
@updateInfo="getMainData" @updatePosts="refresh" />
<!-- Photos, followers and following counters -->
<div class="row text-center mt-2 mb-3">
<div class="col-4" style="border-right: 1px">
<h3>{{ user_data["photos"] }}</h3>
@ -94,22 +127,30 @@ export default {
</div>
</div>
<!-- Photos -->
<div id="main-content" v-for="item of stream_data" v-bind:key="item.photo_id">
<!-- PostCard for the photo -->
<PostCard :user_id="requestedProfile" :photo_id="item.photo_id" :name="user_data['name']"
:date="item.date" :comments="item.comments" :likes="item.likes" :liked="item.liked" />
</div>
<!-- Message when the end is reached -->
<div v-if="data_ended" class="alert alert-secondary text-center" role="alert">
You reached the end. Hooray! 👻
</div>
<!-- The loading spinner -->
<LoadingSpinner :loading="loading" />
<div class="d-flex align-items-center flex-column">
<!-- Refresh button -->
<button v-if="loadingError" @click="refresh" class="btn btn-secondary w-100 py-3">Retry</button>
<!-- Load more button -->
<button v-if="(!data_ended && !loading)" @click="loadMore" class="btn btn-secondary py-1 mb-5"
style="border-radius: 15px">Load more</button>
<!-- The IntersectionObserver for dynamic loading -->
<IntersectionObserver sentinal-name="load-more-profile" @on-intersection-element="loadMore" />
</div>
</div>

View file

@ -3,32 +3,52 @@
export default {
data: function () {
return {
// The error message to display
errormsg: null,
loading: false,
// Search results
streamData: [],
// Dynamic loading
dataEnded: false,
startIdx: 0,
limit: 1,
// Search input
fieldUsername: "",
}
},
methods: {
async refresh() {
// Reset the results and fetch the new requested ones
async query() {
// Set the limit to the number of cards that can fit in the window
this.limit = Math.round(window.innerHeight / 72);
// Reset the parameters and the data
this.startIdx = 0;
this.dataEnded = false;
this.streamData = [];
// Fetch the first batch of results
this.loadContent();
},
// Fetch the search results from the server
async loadContent() {
this.loading = true;
this.errormsg = null;
// Check if the username is empty
// and show an error message
if (this.fieldUsername == "") {
this.errormsg = "Please enter a username";
this.loading = false;
return;
}
// Fetch the results from the server
let response = await this.$axios.get("/users?query=" + this.fieldUsername + "&start_index=" + this.startIdx + "&limit=" + this.limit);
// Errors are handled by the interceptor, which shows a modal dialog to the user and returns a null response.
@ -37,23 +57,23 @@ export default {
return
}
// If there are no more results, set the dataEnded flag
if (response.data.length == 0) this.dataEnded = true;
else this.streamData = this.streamData.concat(response.data);
this.loading = false;
// Otherwise, append the new results to the array
else this.streamData = this.streamData.concat(response.data);
// Hide the loading spinner
this.loading = false;
},
// Load a new batch of results when the user scrolls to the bottom of the page
loadMore() {
if (this.loading || this.dataEnded) return
this.startIdx += this.limit
this.loadContent()
},
},
mounted() {
// this way we are sure that we fill the first page
// 72 is a bit more of the max height of a card
// todo: may not work in 4k screens :/
this.limit = Math.round(window.innerHeight / 72)
}
}
</script>
@ -65,20 +85,27 @@ export default {
<h3 class="card-title border-bottom mb-4 pb-2 text-center">WASASearch</h3>
<!-- Error message -->
<ErrorMsg v-if="errormsg" :msg="errormsg"></ErrorMsg>
<!-- Search form -->
<div class="form-floating mb-4">
<input v-model="fieldUsername" @input="refresh" id="formUsername" class="form-control"
<input v-model="fieldUsername" @input="query" id="formUsername" class="form-control"
placeholder="name@example.com" />
<label class="form-label" for="formUsername">Search by username</label>
</div>
<!-- Search results -->
<div id="main-content" v-for="item of streamData" v-bind:key="item.user_id">
<!-- User card (search result entry) -->
<UserCard :user_id="item.user_id" :name="item.name" :followed="item.followed"
:banned="item.banned" />
</div>
<!-- Loading spinner -->
<LoadingSpinner :loading="loading" /><br />
<!-- The IntersectionObserver for dynamic loading -->
<IntersectionObserver sentinal-name="load-more-search" @on-intersection-element="loadMore" />
</div>
</div>