mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-09 19:40:19 +00:00
completely ajaxify the picture page
This commit is contained in:
@ -72,44 +72,30 @@
|
||||
aspect-ratio: 16/9;
|
||||
background: #333333;
|
||||
|
||||
> a {
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
opacity: 70%;
|
||||
}
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
> div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 30px;
|
||||
color: white;
|
||||
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 0, 0, .3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> #prev > a > div::before {
|
||||
content: '←';
|
||||
}
|
||||
> #next > a > div::before {
|
||||
content: '→';
|
||||
}
|
||||
}
|
||||
|
||||
> .tags {
|
||||
@ -304,20 +290,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.moderation {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
border: 2px solid coral;
|
||||
border-radius: 2px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
> div:last-child {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
|
167
sas/static/sas/js/picture.js
Normal file
167
sas/static/sas/js/picture.js
Normal file
@ -0,0 +1,167 @@
|
||||
/**
|
||||
* @typedef PictureIdentification
|
||||
* @property {number} id The actual id of the identification
|
||||
* @property {UserProfile} user The identified user
|
||||
*/
|
||||
|
||||
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("picture_viewer", () => ({
|
||||
/**
|
||||
* All the pictures that can be displayed on this picture viewer
|
||||
* @type Picture[]
|
||||
* */
|
||||
pictures: [],
|
||||
/**
|
||||
* The users identified on the currently displayed picture
|
||||
* @type PictureIdentification[]
|
||||
*/
|
||||
identifications: [],
|
||||
/**
|
||||
* The currently displayed picture
|
||||
* @type Picture
|
||||
* */
|
||||
current_picture: undefined,
|
||||
/**
|
||||
* The picture which will be displayed next if the user press the "next" button
|
||||
* @type ?Picture
|
||||
* */
|
||||
next_picture: null,
|
||||
/**
|
||||
* The picture which will be dispalyed next if the user press the "previous" button
|
||||
* @type ?Picture
|
||||
* */
|
||||
previous_picture: null,
|
||||
/**
|
||||
* The select2 component used to identify users
|
||||
*/
|
||||
selector: undefined,
|
||||
/**
|
||||
* true if the page is in a loading state, else false
|
||||
*/
|
||||
loading: true,
|
||||
/**
|
||||
* Error message when a moderation operation fails
|
||||
* @type string
|
||||
*/
|
||||
moderation_error: "",
|
||||
|
||||
async init() {
|
||||
this.pictures = await fetch_paginated(picture_endpoint);
|
||||
this.selector = sithSelect2({
|
||||
element: $(this.$refs.search),
|
||||
data_source: remote_data_source("/api/user/search", {
|
||||
excluded: () => [...this.identifications.map((i) => i.user.id)],
|
||||
result_converter: (obj) => Object({ ...obj, text: obj.display_name }),
|
||||
}),
|
||||
picture_getter: (user) => user.profile_pict,
|
||||
});
|
||||
this.current_picture = this.pictures.find(
|
||||
(i) => i.id === first_picture_id,
|
||||
);
|
||||
this.$watch("current_picture", () => this.update_picture());
|
||||
await this.update_picture();
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the page.
|
||||
* Called when the `current_picture` property changes.
|
||||
*
|
||||
* The url is modified without reloading the page,
|
||||
* and the previous picture, the next picture and
|
||||
* the list of identified users are updated.
|
||||
*/
|
||||
async update_picture() {
|
||||
this.loading = true;
|
||||
window.history.pushState(
|
||||
{},
|
||||
"",
|
||||
`/sas/picture/${this.current_picture.id}/`,
|
||||
);
|
||||
this.moderation_error = "";
|
||||
const index = this.pictures.indexOf(this.current_picture);
|
||||
this.previous_picture = this.pictures[index - 1] || null;
|
||||
this.next_picture = this.pictures[index + 1] || null;
|
||||
this.identifications = await (
|
||||
await fetch(`/api/sas/picture/${this.current_picture.id}/identified`)
|
||||
).json();
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
async moderate_picture() {
|
||||
const res = await fetch(
|
||||
`/api/sas/picture/${this.current_picture.id}/moderate`,
|
||||
{
|
||||
method: "PATCH",
|
||||
},
|
||||
);
|
||||
if (!res.ok) {
|
||||
this.moderation_error =
|
||||
gettext("Couldn't moderate picture") + " : " + res.statusText;
|
||||
return;
|
||||
}
|
||||
this.current_picture.is_moderated = true;
|
||||
this.current_picture.asked_for_removal = false;
|
||||
},
|
||||
|
||||
async delete_picture() {
|
||||
const res = await fetch(`/api/sas/picture/${this.current_picture}/`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (!res.ok) {
|
||||
this.moderation_error =
|
||||
gettext("Couldn't delete picture") + " : " + res.statusText;
|
||||
return;
|
||||
}
|
||||
this.pictures.splice(this.pictures.indexOf(this.current_picture), 1);
|
||||
if (this.pictures.length === 0) {
|
||||
// The deleted picture was the only one in the list.
|
||||
// As the album is now empty, go back to the parent page
|
||||
document.location.href = album_url;
|
||||
}
|
||||
this.current_picture = this.next_picture || this.previous_picture;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send the identification request and update the list of identified users.
|
||||
*/
|
||||
async submit_identification() {
|
||||
this.loading = true;
|
||||
const url = `/api/sas/picture/${this.current_picture.id}/identified`;
|
||||
await fetch(url, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(this.selector.val().map((i) => parseInt(i))),
|
||||
});
|
||||
// refresh the identified users list
|
||||
this.identifications = await (await fetch(url)).json();
|
||||
this.selector.empty().trigger("change");
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if an identification can be removed by the currently logged user
|
||||
* @param {PictureIdentification} identification
|
||||
* @return {boolean}
|
||||
*/
|
||||
can_be_removed(identification) {
|
||||
return user_is_sas_admin || identification.user.id === user_id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Untag a user from the current picture
|
||||
* @param {PictureIdentification} identification
|
||||
*/
|
||||
async remove_identification(identification) {
|
||||
this.loading = true;
|
||||
const res = await fetch(`/api/sas/relation/${identification.id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (res.ok) {
|
||||
this.identifications = this.identifications.filter(
|
||||
(i) => i.id !== identification.id,
|
||||
);
|
||||
}
|
||||
this.loading = false;
|
||||
},
|
||||
}));
|
||||
});
|
@ -1,52 +0,0 @@
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("user_identification", () => ({
|
||||
identifications: [],
|
||||
selector: undefined,
|
||||
|
||||
async init() {
|
||||
this.loading = true;
|
||||
this.identifications = await (
|
||||
await fetch(`/api/sas/picture/${picture_id}/identified`)
|
||||
).json();
|
||||
this.selector = sithSelect2({
|
||||
element: $(this.$refs.search),
|
||||
data_source: remote_data_source("/api/user/search", {
|
||||
excluded: () => [...this.identifications.map((i) => i.user.id)],
|
||||
result_converter: (obj) => Object({ ...obj, text: obj.display_name }),
|
||||
}),
|
||||
picture_getter: (user) => user.profile_pict,
|
||||
});
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
async submit_identification() {
|
||||
this.loading = true;
|
||||
const url = `/api/sas/picture/${picture_id}/identified`;
|
||||
await fetch(url, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(this.selector.val().map((i) => parseInt(i))),
|
||||
});
|
||||
// refresh the identified users list
|
||||
this.identifications = await (await fetch(url)).json();
|
||||
this.selector.empty().trigger("change");
|
||||
this.loading = false;
|
||||
},
|
||||
|
||||
can_be_removed(item) {
|
||||
return user_is_sas_admin || item.user.id === user_id;
|
||||
},
|
||||
|
||||
async remove(item) {
|
||||
this.loading = true;
|
||||
const res = await fetch(`/api/sas/relation/${item.id}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (res.ok) {
|
||||
this.identifications = this.identifications.filter(
|
||||
(i) => i.id !== item.id,
|
||||
);
|
||||
}
|
||||
this.loading = false;
|
||||
},
|
||||
}));
|
||||
});
|
Reference in New Issue
Block a user