mirror of
https://github.com/notherealmarco/WASAPhoto.git
synced 2025-03-13 13:35:23 +01:00
Improve code readability
This commit is contained in:
parent
542be61281
commit
83b7eb46a2
9 changed files with 167 additions and 26 deletions
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>-->
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue