Apply standard formater and linter on js files

This commit is contained in:
Antoine Bartuccio 2024-10-03 01:45:29 +02:00
parent e5aa7aa866
commit c57d2ece9c
19 changed files with 3980 additions and 829 deletions

View File

@ -8,6 +8,10 @@ repos:
args: ["--fix", "--silent"] args: ["--fix", "--silent"]
# Run the formatter. # Run the formatter.
- id: ruff-format - id: ruff-format
- repo: https://github.com/standard/standard
rev: v17.1.2
hooks:
- id: standard
- repo: https://github.com/rtts/djhtml - repo: https://github.com/rtts/djhtml
rev: 3.0.6 rev: 3.0.6
hooks: hooks:

View File

@ -5,6 +5,7 @@
[![CI status](https://github.com/ae-utbm/sith/actions/workflows/ci.yml/badge.svg)](#) [![CI status](https://github.com/ae-utbm/sith/actions/workflows/ci.yml/badge.svg)](#)
[![Docs status](https://github.com/ae-utbm/sith/actions/workflows/deploy_docs.yml/badge.svg)](https://ae-utbm.github.io/sith) [![Docs status](https://github.com/ae-utbm/sith/actions/workflows/deploy_docs.yml/badge.svg)](https://ae-utbm.github.io/sith)
[![Built with Material for MkDocs](https://img.shields.io/badge/Material_for_MkDocs-526CFE?style=default&logo=MaterialForMkDocs&logoColor=white)](https://squidfunk.github.io/mkdocs-material/) [![Built with Material for MkDocs](https://img.shields.io/badge/Material_for_MkDocs-526CFE?style=default&logo=MaterialForMkDocs&logoColor=white)](https://squidfunk.github.io/mkdocs-material/)
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
[![discord](https://img.shields.io/discord/971448179075731476?label=discord&logo=discord&style=default)](https://discord.gg/xk9wfpsufm) [![discord](https://img.shields.io/discord/971448179075731476?label=discord&logo=discord&style=default)](https://discord.gg/xk9wfpsufm)
### This is the source code of the UTBM's student association available at [https://ae.utbm.fr/](https://ae.utbm.fr/). ### This is the source code of the UTBM's student association available at [https://ae.utbm.fr/](https://ae.utbm.fr/).

View File

@ -1,24 +1,20 @@
$(document).ready(function () { $(document).ready(function () {
$('#poster_list #view').click(function (e) {
$('#view').removeClass('active')
})
$("#poster_list #view").click(function(e){ $('#poster_list .poster .image').click(function (e) {
$("#view").removeClass("active"); let el = $(e.target)
}); if (el.hasClass('image')) { el = el.find('img') }
$('#poster_list #view #placeholder').html(el.clone())
$("#poster_list .poster .image").click(function(e){ $('#view').addClass('active')
})
el = $(e.target);
if(el.hasClass("image"))
el = el.find("img")
$("#poster_list #view #placeholder").html(el.clone());
$("#view").addClass("active");
});
$(document).keyup(function (e) { $(document).keyup(function (e) {
if (e.keyCode == 27) { // escape key maps to keycode `27` if (e.keyCode === 27) { // escape key maps to keycode `27`
e.preventDefault(); e.preventDefault()
$("#view").removeClass("active"); $('#view').removeClass('active')
} }
}); })
})
});

View File

@ -1,118 +1,104 @@
// TODO: remove disable
/* eslint-disable no-undef, camelcase */
$(document).ready(function () { $(document).ready(function () {
transition_time = 1000
transition_time = 1000; i = 0
max = $('#slideshow .slide').length
i = 0;
max = $("#slideshow .slide").length;
next_trigger = 0 next_trigger = 0
function enterFullscreen () { function enterFullscreen () {
element = document.getElementById("slideshow"); element = document.getElementById('slideshow')
$(element).addClass("fullscreen"); $(element).addClass('fullscreen')
if (element.requestFullscreen) { if (element.requestFullscreen) {
element.requestFullscreen(); element.requestFullscreen()
} else if (element.mozRequestFullScreen) { } else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen(); element.mozRequestFullScreen()
} else if (element.webkitRequestFullscreen) { } else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen(); element.webkitRequestFullscreen()
} else if (element.msRequestFullscreen) { } else if (element.msRequestFullscreen) {
element.msRequestFullscreen(); element.msRequestFullscreen()
} }
} }
function exitFullscreen () { function exitFullscreen () {
element = document.getElementById("slideshow"); element = document.getElementById('slideshow')
$(element).removeClass("fullscreen"); $(element).removeClass('fullscreen')
if (document.exitFullscreen) { if (document.exitFullscreen) {
document.exitFullscreen(); document.exitFullscreen()
} else if (document.webkitExitFullscreen) { } else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen(); document.webkitExitFullscreen()
} else if (document.mozCancelFullScreen) { } else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen(); document.mozCancelFullScreen()
} else if (document.msExitFullscreen) { } else if (document.msExitFullscreen) {
document.msExitFullscreen(); document.msExitFullscreen()
} }
} }
function init_progress_bar() function init_progress_bar () {
{ $('#slideshow #progress_bar').css('transition', 'none')
$('#slideshow #progress_bar').removeClass('progress')
$("#slideshow #progress_bar").css("transition", "none"); $('#slideshow #progress_bar').addClass('init')
$("#slideshow #progress_bar").removeClass("progress");
$("#slideshow #progress_bar").addClass("init");
} }
function start_progress_bar(display_time) function start_progress_bar (display_time) {
{ $('#slideshow #progress_bar').removeClass('init')
$('#slideshow #progress_bar').addClass('progress')
$("#slideshow #progress_bar").removeClass("init"); $('#slideshow #progress_bar').css('transition', 'width ' + display_time + 's linear')
$("#slideshow #progress_bar").addClass("progress");
$("#slideshow #progress_bar").css("transition", "width " + display_time + "s linear")
} }
function next() function next () {
{ init_progress_bar()
slide = $($('#slideshow .slide').get(i % max))
slide.removeClass('center')
slide.addClass('left')
init_progress_bar(); next_slide = $($('#slideshow .slide').get((i + 1) % max))
slide = $($("#slideshow .slide").get(i % max)); next_slide.removeClass('right')
slide.removeClass("center"); next_slide.addClass('center')
slide.addClass("left"); display_time = next_slide.attr('display_time') || 2
next_slide = $($("#slideshow .slide").get((i + 1) % max)); $('#slideshow .bullet').removeClass('active')
next_slide.removeClass("right"); bullet = $('#slideshow .bullet')[(i + 1) % max]
next_slide.addClass("center"); $(bullet).addClass('active')
display_time = next_slide.attr("display_time") || 2;
$("#slideshow .bullet").removeClass("active"); i = (i + 1) % max
bullet = $("#slideshow .bullet")[(i + 1) % max];
$(bullet).addClass("active");
i = (i + 1) % max;
setTimeout(function () { setTimeout(function () {
others_left = $('#slideshow .slide.left')
others_left.removeClass('left')
others_left.addClass('right')
others_left = $("#slideshow .slide.left"); start_progress_bar(display_time)
others_left.removeClass("left"); next_trigger = setTimeout(next, display_time * 1000)
others_left.addClass("right"); }, transition_time)
start_progress_bar(display_time);
next_trigger = setTimeout(next, display_time * 1000);
}, transition_time);
} }
display_time = $('#slideshow .center').attr('display_time')
display_time = $("#slideshow .center").attr("display_time"); init_progress_bar()
init_progress_bar();
setTimeout(function () { setTimeout(function () {
if (max > 1) { if (max > 1) {
start_progress_bar(display_time); start_progress_bar(display_time)
setTimeout(next, display_time * 1000); setTimeout(next, display_time * 1000)
} }
}, 10); }, 10)
$('#slideshow').click(function (e) {
$("#slideshow").click(function(e){ if (!$('#slideshow').hasClass('fullscreen')) {
if(!$("#slideshow").hasClass("fullscreen")) console.log('Entering fullscreen ...')
{ enterFullscreen()
console.log("Entering fullscreen ...");
enterFullscreen();
} else { } else {
console.log("Exiting fullscreen ..."); console.log('Exiting fullscreen ...')
exitFullscreen(); exitFullscreen()
} }
}); })
$(document).keyup(function (e) { $(document).keyup(function (e) {
if (e.keyCode == 27) { // escape key maps to keycode `27` if (e.keyCode === 27) { // escape key maps to keycode `27`
e.preventDefault(); e.preventDefault()
console.log("Exiting fullscreen ..."); console.log('Exiting fullscreen ...')
exitFullscreen(); exitFullscreen()
} }
}); })
})
});

View File

@ -1,59 +1,60 @@
/* eslint-disable camelcase */
$(function () { $(function () {
buttons = $(".choose_file_button"); // const buttons = $('.choose_file_button')
popups = $(".choose_file_widget"); const popups = $('.choose_file_widget')
popups.dialog({ popups.dialog({
autoOpen: false, autoOpen: false,
modal: true, modal: true,
width: "90%", width: '90%',
create: function (event) { create: function (event) {
target = $(event.target); const target = $(event.target)
target.parent().css({ target.parent().css({
'position': 'fixed', position: 'fixed',
'top': '5%', top: '5%',
'bottom': '5%', bottom: '5%'
}); })
target.css("height", "300px"); target.css('height', '300px')
console.log(target); console.log(target)
}, },
buttons: [ buttons: [
{ {
text: "Choose", text: 'Choose',
click: function () { click: function () {
console.log($("#file_id")); console.log($('#file_id'))
$("input[name="+$(this).attr('name')+"]").attr('value', $("#file_id").attr('value')); $('input[name=' + $(this).attr('name') + ']').attr('value', $('#file_id').attr('value'))
$( this ).dialog( "close" ); $(this).dialog('close')
}, },
disabled: true, disabled: true
} }
], ]
}); })
$( ".choose_file_button" ).button().on( "click", function() { $('.choose_file_button').button().on('click', function () {
popup = popups.filter("[name="+$(this).attr('name')+"]"); const popup = popups.filter('[name=' + $(this).attr('name') + ']')
console.log(popup); console.log(popup)
popup.html('<iframe src="/file/popup" width="100%" height="95%"></iframe><div id="file_id" value="null" />'); popup.html('<iframe src="/file/popup" width="100%" height="95%"></iframe><div id="file_id" value="null" />')
popup.dialog({title: $(this).text()}).dialog( "open" ); popup.dialog({ title: $(this).text() }).dialog('open')
}); })
$("#quick_notif li").click(function () { $('#quick_notif li').click(function () {
$(this).hide(); $(this).hide()
})
}) })
});
function createQuickNotif(msg) { function createQuickNotif (msg) { // eslint-disable-line no-unused-vars
const el = document.createElement('li') const el = document.createElement('li')
el.textContent = msg el.textContent = msg
el.addEventListener('click', () => el.parentNode.removeChild(el)) el.addEventListener('click', () => el.parentNode.removeChild(el))
document.getElementById('quick_notif').appendChild(el) document.getElementById('quick_notif').appendChild(el)
} }
function deleteQuickNotifs() { function deleteQuickNotifs () { // eslint-disable-line no-unused-vars
const el = document.getElementById('quick_notif') const el = document.getElementById('quick_notif')
while (el.firstChild) { while (el.firstChild) {
el.removeChild(el.firstChild) el.removeChild(el.firstChild)
} }
} }
function display_notif() { function display_notif () { // eslint-disable-line no-unused-vars
$('#header_notif').toggle().parent().toggleClass("white"); $('#header_notif').toggle().parent().toggleClass('white')
} }
// You can't get the csrf token from the template in a widget // You can't get the csrf token from the template in a widget
@ -62,12 +63,11 @@ function display_notif() {
// Sadly, getting the cookie is not possible with CSRF_COOKIE_HTTPONLY or CSRF_USE_SESSIONS is True // Sadly, getting the cookie is not possible with CSRF_COOKIE_HTTPONLY or CSRF_USE_SESSIONS is True
// So, the true workaround is to get the token from the dom // So, the true workaround is to get the token from the dom
// https://docs.djangoproject.com/en/2.0/ref/csrf/#acquiring-the-token-if-csrf-use-sessions-is-true // https://docs.djangoproject.com/en/2.0/ref/csrf/#acquiring-the-token-if-csrf-use-sessions-is-true
function getCSRFToken() { function getCSRFToken () { // eslint-disable-line no-unused-vars
return $("[name=csrfmiddlewaretoken]").val(); return $('[name=csrfmiddlewaretoken]').val()
} }
const initialUrlParams = new URLSearchParams(window.location.search) // eslint-disable-line no-unused-vars
const initialUrlParams = new URLSearchParams(window.location.search);
/** /**
* @readonly * @readonly
@ -76,8 +76,8 @@ const initialUrlParams = new URLSearchParams(window.location.search);
const History = { const History = {
NONE: 0, NONE: 0,
PUSH: 1, PUSH: 1,
REPLACE: 2, REPLACE: 2
}; }
/** /**
* @param {string} key * @param {string} key
@ -85,27 +85,27 @@ const History = {
* @param {History} action * @param {History} action
* @param {URL | null} url * @param {URL | null} url
*/ */
function update_query_string(key, value, action = History.REPLACE, url = null) { function update_query_string (key, value, action = History.REPLACE, url = null) { // eslint-disable-line no-unused-vars
if (!url) { if (!url) {
url = new URL(window.location.href); url = new URL(window.location.href)
} }
if (value === undefined || value === null || value === "") { if (value === undefined || value === null || value === '') {
// If the value is null, undefined or empty => delete it // If the value is null, undefined or empty => delete it
url.searchParams.delete(key) url.searchParams.delete(key)
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
url.searchParams.delete(key) url.searchParams.delete(key)
value.forEach((v) => url.searchParams.append(key, v)) value.forEach((v) => url.searchParams.append(key, v))
} else { } else {
url.searchParams.set(key, value); url.searchParams.set(key, value)
} }
if (action === History.PUSH) { if (action === History.PUSH) {
history.pushState(null, "", url.toString()); window.history.pushState(null, '', url.toString())
} else if (action === History.REPLACE) { } else if (action === History.REPLACE) {
history.replaceState(null, "", url.toString()); window.history.replaceState(null, '', url.toString())
} }
return url; return url
} }
// TODO : If one day a test workflow is made for JS in this project // TODO : If one day a test workflow is made for JS in this project
@ -116,27 +116,27 @@ function update_query_string(key, value, action = History.REPLACE, url = null) {
* @param {string} url The paginated endpoint to fetch * @param {string} url The paginated endpoint to fetch
* @return {Promise<Object[]>} * @return {Promise<Object[]>}
*/ */
async function fetch_paginated(url) { async function fetch_paginated (url) { // eslint-disable-line no-unused-vars
const max_per_page = 199; const max_per_page = 199
const paginated_url = new URL(url, document.location.origin); const paginated_url = new URL(url, document.location.origin)
paginated_url.searchParams.set("page_size", max_per_page.toString()); paginated_url.searchParams.set('page_size', max_per_page.toString())
paginated_url.searchParams.set("page", "1"); paginated_url.searchParams.set('page', '1')
let first_page = (await ( await fetch(paginated_url)).json()); const first_page = (await (await fetch(paginated_url)).json())
let results = first_page.results; const results = first_page.results
const nb_pictures = first_page.count const nb_pictures = first_page.count
const nb_pages = Math.ceil(nb_pictures / max_per_page); const nb_pages = Math.ceil(nb_pictures / max_per_page)
if (nb_pages > 1) { if (nb_pages > 1) {
let promises = []; const promises = []
for (let i = 2; i <= nb_pages; i++) { for (let i = 2; i <= nb_pages; i++) {
paginated_url.searchParams.set("page", i.toString()); paginated_url.searchParams.set('page', i.toString())
promises.push( promises.push(
fetch(paginated_url).then(res => res.json().then(json => json.results)) fetch(paginated_url).then(res => res.json().then(json => json.results))
); )
} }
results.push(...(await Promise.all(promises)).flat()) results.push(...(await Promise.all(promises)).flat())
} }
return results; return results
} }

View File

@ -1,3 +1,4 @@
/* eslint-disable camelcase */
/** /**
* Builders to use Select2 in our templates. * Builders to use Select2 in our templates.
* *
@ -157,15 +158,15 @@
/** /**
* @param {Select2Options} options * @param {Select2Options} options
*/ */
function sithSelect2(options) { function sithSelect2 (options) { // eslint-disable-line no-unused-vars
const elem = $(options.element); const elem = $(options.element)
return elem.select2({ return elem.select2({
theme: elem[0].multiple ? "classic" : "default", theme: elem[0].multiple ? 'classic' : 'default',
minimumInputLength: 2, minimumInputLength: 2,
templateResult: select_item_builder(options.picture_getter), templateResult: select_item_builder(options.picture_getter),
...options.data_source, ...options.data_source,
...(options.overrides || {}), ...(options.overrides || {})
}); })
} }
/** /**
@ -179,12 +180,12 @@ function sithSelect2(options) {
* @param {Select2Object[]} source The array containing the data * @param {Select2Object[]} source The array containing the data
* @param {RemoteSourceOptions} options * @param {RemoteSourceOptions} options
*/ */
function local_data_source(source, options) { function local_data_source (source, options) { // eslint-disable-line no-unused-vars
if (!!options.excluded) { if (options.excluded) {
const ids = options.excluded(); const ids = options.excluded()
return { data: source.filter((i) => !ids.includes(i.id)) }; return { data: source.filter((i) => !ids.includes(i.id)) }
} }
return { data: source }; return { data: source }
} }
/** /**
@ -202,11 +203,11 @@ function local_data_source(source, options) {
* @param {string} source The url of the endpoint * @param {string} source The url of the endpoint
* @param {RemoteSourceOptions} options * @param {RemoteSourceOptions} options
*/ */
function remote_data_source(source, options) { function remote_data_source (source, options) { // eslint-disable-line no-unused-vars
jQuery.ajaxSettings.traditional = true; jQuery.ajaxSettings.traditional = true
let params = { const params = {
url: source, url: source,
dataType: "json", dataType: 'json',
cache: true, cache: true,
delay: 250, delay: 250,
data: function (params) { data: function (params) {
@ -214,25 +215,25 @@ function remote_data_source(source, options) {
search: params.term, search: params.term,
exclude: [ exclude: [
...(this.val() || []).map((i) => parseInt(i)), ...(this.val() || []).map((i) => parseInt(i)),
...(options.excluded ? options.excluded() : []), ...(options.excluded ? options.excluded() : [])
], ]
};
},
};
if (!!options.result_converter) {
params["processResults"] = function (data) {
return { results: data.results.map(options.result_converter) };
};
} }
if (!!options.overrides) {
Object.assign(params, options.overrides);
} }
return { ajax: params }; }
if (options.result_converter) {
params.processResults = function (data) {
return { results: data.results.map(options.result_converter) }
}
}
if (options.overrides) {
Object.assign(params, options.overrides)
}
return { ajax: params }
} }
function item_formatter(user) { function item_formatter (user) { // eslint-disable-line no-unused-vars
if (user.loading) { if (user.loading) {
return user.text; return user.text
} }
} }
@ -244,18 +245,18 @@ function item_formatter(user) {
function select_item_builder (picture_getter) { function select_item_builder (picture_getter) {
return (item) => { return (item) => {
const picture = const picture =
typeof picture_getter === "function" ? picture_getter(item) : null; typeof picture_getter === 'function' ? picture_getter(item) : null
const img_html = picture const img_html = picture
? `<img ? `<img
src="${picture_getter(item)}" src="${picture_getter(item)}"
alt="${item.text}" alt="${item.text}"
onerror="this.src = '/static/core/img/unknown.jpg'" onerror="this.src = '/static/core/img/unknown.jpg'"
/>` />`
: ""; : ''
return $(`<div class="select-item"> return $(`<div class="select-item">
${img_html} ${img_html}
<span class="select-item-text">${item.text}</span> <span class="select-item-text">${item.text}</span>
</div>`); </div>`)
}; }
} }

View File

@ -1,167 +1,171 @@
/* eslint-disable camelcase */
/* global cytoscape, initialUrlParams, History, update_query_string */
async function get_graph_data (url, godfathers_depth, godchildren_depth) { async function get_graph_data (url, godfathers_depth, godchildren_depth) {
let data = await ( const data = await (
await fetch( await fetch(
`${url}?godfathers_depth=${godfathers_depth}&godchildren_depth=${godchildren_depth}`, `${url}?godfathers_depth=${godfathers_depth}&godchildren_depth=${godchildren_depth}`
) )
).json(); ).json()
return [ return [
...data.users.map((user) => { ...data.users.map((user) => {
return { data: user }; return { data: user }
}), }),
...data.relationships.map((rel) => { ...data.relationships.map((rel) => {
return { return {
data: { source: rel.godfather, target: rel.godchild }, data: { source: rel.godfather, target: rel.godchild }
}; }
}), })
]; ]
} }
function create_graph (container, data, active_user_id) { function create_graph (container, data, active_user_id) {
let cy = cytoscape({ const cy = cytoscape({
boxSelectionEnabled: false, boxSelectionEnabled: false,
autounselectify: true, autounselectify: true,
container: container, container,
elements: data, elements: data,
minZoom: 0.5, minZoom: 0.5,
style: [ style: [
// the stylesheet for the graph // the stylesheet for the graph
{ {
selector: "node", selector: 'node',
style: { style: {
label: "data(display_name)", label: 'data(display_name)',
"background-image": "data(profile_pict)", 'background-image': 'data(profile_pict)',
width: "100%", width: '100%',
height: "100%", height: '100%',
"background-fit": "cover", 'background-fit': 'cover',
"background-repeat": "no-repeat", 'background-repeat': 'no-repeat',
shape: "ellipse", shape: 'ellipse'
}, }
}, },
{ {
selector: "edge", selector: 'edge',
style: { style: {
width: 5, width: 5,
"line-color": "#ccc", 'line-color': '#ccc',
"target-arrow-color": "#ccc", 'target-arrow-color': '#ccc',
"target-arrow-shape": "triangle", 'target-arrow-shape': 'triangle',
"curve-style": "bezier", 'curve-style': 'bezier'
}, }
}, },
{ {
selector: ".traversed", selector: '.traversed',
style: { style: {
"border-width": "5px", 'border-width': '5px',
"border-style": "solid", 'border-style': 'solid',
"border-color": "red", 'border-color': 'red',
"target-arrow-color": "red", 'target-arrow-color': 'red',
"line-color": "red", 'line-color': 'red'
}, }
}, },
{ {
selector: ".not-traversed", selector: '.not-traversed',
style: { style: {
"line-opacity": "0.5", 'line-opacity': '0.5',
"background-opacity": "0.5", 'background-opacity': '0.5',
"background-image-opacity": "0.5", 'background-image-opacity': '0.5'
}, }
}, }
], ],
layout: { layout: {
name: "klay", name: 'klay',
nodeDimensionsIncludeLabels: true, nodeDimensionsIncludeLabels: true,
fit: true, fit: true,
klay: { klay: {
addUnnecessaryBendpoints: true, addUnnecessaryBendpoints: true,
direction: "DOWN", direction: 'DOWN',
nodePlacement: "INTERACTIVE", nodePlacement: 'INTERACTIVE',
layoutHierarchy: true, layoutHierarchy: true
}, }
}, }
}); })
let active_user = cy const active_user = cy
.getElementById(active_user_id) .getElementById(active_user_id)
.style("shape", "rectangle"); .style('shape', 'rectangle')
/* Reset graph */ /* Reset graph */
let reset_graph = () => { const reset_graph = () => {
cy.elements((element) => { cy.elements((element) => {
if (element.hasClass("traversed")) { if (element.hasClass('traversed')) {
element.removeClass("traversed"); element.removeClass('traversed')
} }
if (element.hasClass("not-traversed")) { if (element.hasClass('not-traversed')) {
element.removeClass("not-traversed"); element.removeClass('not-traversed')
}
})
} }
});
};
let on_node_tap = (el) => { const on_node_tap = (el) => {
reset_graph(); reset_graph()
/* Create path on graph if selected isn't the targeted user */ /* Create path on graph if selected isn't the targeted user */
if (el === active_user) { if (el === active_user) {
return; return
} }
cy.elements((element) => { cy.elements((element) => {
element.addClass("not-traversed"); element.addClass('not-traversed')
}); })
cy.elements() cy.elements()
.aStar({ .aStar({
root: el, root: el,
goal: active_user, goal: active_user
}) })
.path.forEach((el) => { .path.forEach((el) => {
el.removeClass("not-traversed"); el.removeClass('not-traversed')
el.addClass("traversed"); el.addClass('traversed')
}); })
}; }
cy.on("tap", "node", (tapped) => { cy.on('tap', 'node', (tapped) => {
on_node_tap(tapped.target); on_node_tap(tapped.target)
}); })
cy.zoomingEnabled(false); cy.zoomingEnabled(false)
/* Add context menu */ /* Add context menu */
if (cy.cxtmenu === undefined) { if (cy.cxtmenu === undefined) {
console.error( console.error(
"ctxmenu isn't loaded, context menu won't be available on graphs", "ctxmenu isn't loaded, context menu won't be available on graphs"
); )
return cy; return cy
} }
cy.cxtmenu({ cy.cxtmenu({
selector: "node", selector: 'node',
commands: [ commands: [
{ {
content: '<i class="fa fa-external-link fa-2x"></i>', content: '<i class="fa fa-external-link fa-2x"></i>',
select: function (el) { select: function (el) {
window.open(el.data().profile_url, "_blank").focus(); window.open(el.data().profile_url, '_blank').focus()
}, }
}, },
{ {
content: '<span class="fa fa-mouse-pointer fa-2x"></span>', content: '<span class="fa fa-mouse-pointer fa-2x"></span>',
select: function (el) { select: function (el) {
on_node_tap(el); on_node_tap(el)
}, }
}, },
{ {
content: '<i class="fa fa-eraser fa-2x"></i>', content: '<i class="fa fa-eraser fa-2x"></i>',
select: function (el) { select: function (el) {
reset_graph(); reset_graph()
}, }
}, }
], ]
}); })
return cy; return cy
} }
document.addEventListener("alpine:init", () => { /* global api_url, active_user, depth_min, depth_max */
document.addEventListener('alpine:init', () => {
/* /*
This needs some constants to be set before the document has been loaded This needs some constants to be set before the document has been loaded
@ -170,104 +174,104 @@ document.addEventListener("alpine:init", () => {
depth_min: minimum tree depth for godfathers and godchildren as an int depth_min: minimum tree depth for godfathers and godchildren as an int
depth_max: maximum tree depth for godfathers and godchildren as an int depth_max: maximum tree depth for godfathers and godchildren as an int
*/ */
const default_depth = 2; const default_depth = 2
if ( if (
typeof api_url === "undefined" || typeof api_url === 'undefined' ||
typeof active_user === "undefined" || typeof active_user === 'undefined' ||
typeof depth_min === "undefined" || typeof depth_min === 'undefined' ||
typeof depth_max === "undefined" typeof depth_max === 'undefined'
) { ) {
console.error( console.error(
"Some constants are not set before using the family_graph script, please look at the documentation", 'Some constants are not set before using the family_graph script, please look at the documentation'
); )
return; return
} }
function get_initial_depth (prop) { function get_initial_depth (prop) {
let value = parseInt(initialUrlParams.get(prop)); const value = parseInt(initialUrlParams.get(prop))
if (isNaN(value) || value < depth_min || value > depth_max) { if (isNaN(value) || value < depth_min || value > depth_max) {
return default_depth; return default_depth
} }
return value; return value
} }
Alpine.data("graph", () => ({ Alpine.data('graph', () => ({
loading: false, loading: false,
godfathers_depth: get_initial_depth("godfathers_depth"), godfathers_depth: get_initial_depth('godfathers_depth'),
godchildren_depth: get_initial_depth("godchildren_depth"), godchildren_depth: get_initial_depth('godchildren_depth'),
reverse: initialUrlParams.get("reverse")?.toLowerCase?.() === "true", reverse: initialUrlParams.get('reverse')?.toLowerCase?.() === 'true',
graph: undefined, graph: undefined,
graph_data: {}, graph_data: {},
async init () { async init () {
let delayed_fetch = Alpine.debounce(async () => { const delayed_fetch = Alpine.debounce(async () => {
this.fetch_graph_data(); this.fetch_graph_data()
}, 100); }, 100);
["godfathers_depth", "godchildren_depth"].forEach((param) => { ['godfathers_depth', 'godchildren_depth'].forEach((param) => {
this.$watch(param, async (value) => { this.$watch(param, async (value) => {
if (value < depth_min || value > depth_max) { if (value < depth_min || value > depth_max) {
return; return
} }
update_query_string(param, value, History.REPLACE); update_query_string(param, value, History.REPLACE)
delayed_fetch(); delayed_fetch()
}); })
}); })
this.$watch("reverse", async (value) => { this.$watch('reverse', async (value) => {
update_query_string("reverse", value, History.REPLACE); update_query_string('reverse', value, History.REPLACE)
this.reverse_graph(); this.reverse_graph()
}); })
this.$watch("graph_data", async () => { this.$watch('graph_data', async () => {
await this.generate_graph(); await this.generate_graph()
if (this.reverse) { if (this.reverse) {
await this.reverse_graph(); await this.reverse_graph()
} }
}); })
this.fetch_graph_data(); this.fetch_graph_data()
}, },
async screenshot () { async screenshot () {
const link = document.createElement("a"); const link = document.createElement('a')
link.href = this.graph.jpg(); link.href = this.graph.jpg()
link.download = interpolate( link.download = interpolate(
gettext("family_tree.%(extension)s"), gettext('family_tree.%(extension)s'),
{ extension: "jpg" }, { extension: 'jpg' },
true, true
); )
document.body.appendChild(link); document.body.appendChild(link)
link.click(); link.click()
document.body.removeChild(link); document.body.removeChild(link)
}, },
async reset () { async reset () {
this.reverse = false; this.reverse = false
this.godfathers_depth = default_depth; this.godfathers_depth = default_depth
this.godchildren_depth = default_depth; this.godchildren_depth = default_depth
}, },
async reverse_graph () { async reverse_graph () {
this.graph.elements((el) => { this.graph.elements((el) => {
el.position(new Object({ x: -el.position().x, y: -el.position().y })); el.position({ x: -el.position().x, y: -el.position().y })
}); })
this.graph.center(this.graph.elements()); this.graph.center(this.graph.elements())
}, },
async fetch_graph_data () { async fetch_graph_data () {
this.graph_data = await get_graph_data( this.graph_data = await get_graph_data(
api_url, api_url,
this.godfathers_depth, this.godfathers_depth,
this.godchildren_depth, this.godchildren_depth
); )
}, },
async generate_graph () { async generate_graph () {
this.loading = true; this.loading = true
this.graph = create_graph( this.graph = create_graph(
$(this.$refs.graph), $(this.$refs.graph),
this.graph_data, this.graph_data,
active_user, active_user
); )
this.loading = false; this.loading = false
}, }
})); }))
}); })

View File

@ -1,7 +1,9 @@
function alpine_webcam_builder( /* eslint-disable camelcase */
/* global DataTransfer */
function alpine_webcam_builder ( // eslint-disable-line no-unused-vars
default_picture, default_picture,
delete_url, delete_url,
can_delete_picture, can_delete_picture
) { ) {
return () => ({ return () => ({
can_edit_picture: false, can_edit_picture: false,
@ -14,97 +16,98 @@ function alpine_webcam_builder(
picture_form: null, picture_form: null,
init () { init () {
this.video = this.$refs.video; this.video = this.$refs.video
this.picture_form = this.$refs.form.getElementsByTagName("input"); this.picture_form = this.$refs.form.getElementsByTagName('input')
if (this.picture_form.length > 0) { if (this.picture_form.length > 0) {
this.picture_form = this.picture_form[0]; this.picture_form = this.picture_form[0]
this.can_edit_picture = true; this.can_edit_picture = true
// Link the displayed element to the form input // Link the displayed element to the form input
this.picture_form.onchange = (event) => { this.picture_form.onchange = (event) => {
let files = event.srcElement.files; const files = event.srcElement.files
if (files.length > 0) { if (files.length > 0) {
this.picture = (window.URL || window.webkitURL).createObjectURL( this.picture = (window.URL || window.webkitURL).createObjectURL(
event.srcElement.files[0], event.srcElement.files[0]
); )
} else { } else {
this.picture = null; this.picture = null
}
} }
};
} }
}, },
get_picture () { get_picture () {
return this.picture || default_picture; return this.picture || default_picture
}, },
delete_picture () { delete_picture () {
// Only remove currently displayed picture // Only remove currently displayed picture
if (!!this.picture) { if (this.picture) {
let list = new DataTransfer(); const list = new DataTransfer()
this.picture_form.files = list.files; this.picture_form.files = list.files
this.picture_form.dispatchEvent(new Event("change")); this.picture_form.dispatchEvent(new Event('change'))
return; return
} }
if (!can_delete_picture) { if (!can_delete_picture) {
return; return
} }
// Remove user picture if correct rights are available // Remove user picture if correct rights are available
window.open(delete_url, "_self"); window.open(delete_url, '_self')
}, },
enable_camera () { enable_camera () {
this.picture = null; this.picture = null
this.loading = true; this.loading = true
this.is_camera_error = false; this.is_camera_error = false
navigator.mediaDevices navigator.mediaDevices
.getUserMedia({ video: true, audio: false }) .getUserMedia({ video: true, audio: false })
.then((stream) => { .then((stream) => {
this.loading = false; this.loading = false
this.is_camera_enabled = true; this.is_camera_enabled = true
this.video.srcObject = stream; this.video.srcObject = stream
this.video.play(); this.video.play()
}) })
.catch((err) => { .catch((err) => {
this.is_camera_error = true; this.is_camera_error = true
this.loading = false; this.loading = false
}); throw (err)
})
}, },
take_picture () { take_picture () {
let canvas = document.createElement("canvas"); const canvas = document.createElement('canvas')
const context = canvas.getContext("2d"); const context = canvas.getContext('2d')
/* Create the image */ /* Create the image */
let settings = this.video.srcObject.getTracks()[0].getSettings(); const settings = this.video.srcObject.getTracks()[0].getSettings()
canvas.width = settings.width; canvas.width = settings.width
canvas.height = settings.height; canvas.height = settings.height
context.drawImage(this.video, 0, 0, canvas.width, canvas.height); context.drawImage(this.video, 0, 0, canvas.width, canvas.height)
/* Stop camera */ /* Stop camera */
this.video.pause(); this.video.pause()
this.video.srcObject.getTracks().forEach((track) => { this.video.srcObject.getTracks().forEach((track) => {
if (track.readyState === "live") { if (track.readyState === 'live') {
track.stop(); track.stop()
} }
}); })
canvas.toBlob((blob) => { canvas.toBlob((blob) => {
const filename = interpolate(gettext("captured.%s"), ["webp"]); const filename = interpolate(gettext('captured.%s'), ['webp'])
let file = new File([blob], filename, { const file = new File([blob], filename, {
type: "image/webp", type: 'image/webp'
}); })
let list = new DataTransfer(); const list = new DataTransfer()
list.items.add(file); list.items.add(file)
this.picture_form.files = list.files; this.picture_form.files = list.files
// No change event is triggered, we trigger it manually #} // No change event is triggered, we trigger it manually #}
this.picture_form.dispatchEvent(new Event("change")); this.picture_form.dispatchEvent(new Event('change'))
}, "image/webp"); }, 'image/webp')
canvas.remove(); canvas.remove()
this.is_camera_enabled = false; this.is_camera_enabled = false
}, }
}); })
} }

View File

@ -1,7 +1,7 @@
import Alpine from "alpinejs"; import Alpine from 'alpinejs'
window.Alpine = Alpine; window.Alpine = Alpine
addEventListener("DOMContentLoaded", (event) => { window.addEventListener('DOMContentLoaded', (event) => {
Alpine.start(); Alpine.start()
}); })

View File

@ -1,6 +1,6 @@
import "codemirror/lib/codemirror.css"; import 'codemirror/lib/codemirror.css'
import "easymde/src/css/easymde.css"; import 'easymde/src/css/easymde.css'
import EasyMDE from "easymde"; import EasyMDE from 'easymde'
// This scripts dependens on Alpine but it should be loaded on every page // This scripts dependens on Alpine but it should be loaded on every page

View File

@ -1 +1 @@
require("@fortawesome/fontawesome-free/css/all.css"); require('@fortawesome/fontawesome-free/css/all.css')

View File

@ -1,17 +1,17 @@
import $ from "jquery"; import $ from 'jquery'
import "jquery.shorten/src/jquery.shorten.min.js"; import 'jquery.shorten/src/jquery.shorten.min.js'
// We ship jquery-ui with jquery because when standalone with webpack // We ship jquery-ui with jquery because when standalone with webpack
// JQuery is also included in the jquery-ui package. We do gain space by doing this // JQuery is also included in the jquery-ui package. We do gain space by doing this
// We require jquery-ui components manually and not in a loop // We require jquery-ui components manually and not in a loop
// Otherwise it increases the output files by a x2 factor ! // Otherwise it increases the output files by a x2 factor !
require("jquery-ui/ui/widgets/accordion.js"); require('jquery-ui/ui/widgets/accordion.js')
require("jquery-ui/ui/widgets/autocomplete.js"); require('jquery-ui/ui/widgets/autocomplete.js')
require("jquery-ui/ui/widgets/button.js"); require('jquery-ui/ui/widgets/button.js')
require("jquery-ui/ui/widgets/dialog.js"); require('jquery-ui/ui/widgets/dialog.js')
require("jquery-ui/ui/widgets/tabs.js"); require('jquery-ui/ui/widgets/tabs.js')
require("jquery-ui/themes/base/all.css"); require('jquery-ui/themes/base/all.css')
/** /**
* Simple wrapper to solve shorten not being able on legacy pages * Simple wrapper to solve shorten not being able on legacy pages
@ -19,7 +19,7 @@ require("jquery-ui/themes/base/all.css");
* @param {Object} options object to pass to the shorten function * @param {Object} options object to pass to the shorten function
**/ **/
export function shorten (selector, options) { export function shorten (selector, options) {
$(selector).shorten(options); $(selector).shorten(options)
} }
window.shorten = shorten; window.shorten = shorten

View File

@ -1,77 +1,79 @@
/* eslint-disable camelcase */
/* global basket, click_api_url, csrf_token, products_autocomplete */
document.addEventListener('alpine:init', () => { document.addEventListener('alpine:init', () => {
Alpine.data('counter', () => ({ Alpine.data('counter', () => ({
basket: basket, basket,
errors: [], errors: [],
sum_basket () { sum_basket () {
if (!this.basket || Object.keys(this.basket).length === 0) { if (!this.basket || Object.keys(this.basket).length === 0) {
return 0; return 0
} }
const total = Object.values(this.basket) const total = Object.values(this.basket)
.reduce((acc, cur) => acc + cur["qty"] * cur["price"], 0); .reduce((acc, cur) => acc + cur.qty * cur.price, 0)
return total / 100; return total / 100
}, },
async handle_code (event) { async handle_code (event) {
const code = $(event.target).find("#code_field").val().toUpperCase(); const code = $(event.target).find('#code_field').val().toUpperCase()
if(["FIN", "ANN"].includes(code)) { if (['FIN', 'ANN'].includes(code)) {
$(event.target).submit(); $(event.target).submit()
} else { } else {
await this.handle_action(event); await this.handle_action(event)
} }
}, },
async handle_action (event) { async handle_action (event) {
const payload = $(event.target).serialize(); const payload = $(event.target).serialize()
let request = new Request(click_api_url, { const request = new Request(click_api_url, {
method: "POST", method: 'POST',
body: payload, body: payload,
headers: { headers: {
'Accept': 'application/json', Accept: 'application/json',
'X-CSRFToken': csrf_token, 'X-CSRFToken': csrf_token
} }
}) })
const response = await fetch(request); const response = await fetch(request)
const json = await response.json(); const json = await response.json()
this.basket = json["basket"] this.basket = json.basket
this.errors = json["errors"] this.errors = json.errors
$('form.code_form #code_field').val("").focus(); $('form.code_form #code_field').val('').focus()
} }
})) }))
}) })
$(function () { $(function () {
/* Autocompletion in the code field */ /* Autocompletion in the code field */
const code_field = $("#code_field"); const code_field = $('#code_field')
let quantity = ""; let quantity = ''
code_field.autocomplete({ code_field.autocomplete({
select: function (event, ui) { select: function (event, ui) {
event.preventDefault(); event.preventDefault()
code_field.val(quantity + ui.item.value); code_field.val(quantity + ui.item.value)
}, },
focus: function (event, ui) { focus: function (event, ui) {
event.preventDefault(); event.preventDefault()
code_field.val(quantity + ui.item.value); code_field.val(quantity + ui.item.value)
}, },
source: function (request, response) { source: function (request, response) {
const res = /^(\d+x)?(.*)/i.exec(request.term); const res = /^(\d+x)?(.*)/i.exec(request.term)
quantity = res[1] || ""; quantity = res[1] || ''
const search = res[2]; const search = res[2]
const matcher = new RegExp($.ui.autocomplete.escapeRegex(search), "i" ); const matcher = new RegExp($.ui.autocomplete.escapeRegex(search), 'i')
response($.grep(products_autocomplete, function (value) { response($.grep(products_autocomplete, function (value) {
value = value.tags; value = value.tags
return matcher.test( value ); return matcher.test(value)
})); }))
}, }
}); })
/* Accordion UI between basket and refills */ /* Accordion UI between basket and refills */
$("#click_form").accordion({ $('#click_form').accordion({
heightStyle: "content", heightStyle: 'content',
activate: () => $(".focus").focus(), activate: () => $('.focus').focus()
}); })
$("#products").tabs(); $('#products').tabs()
code_field.focus(); code_field.focus()
}); })

View File

@ -1,3 +1,4 @@
/* eslint-disable camelcase */
/** /**
* @typedef {Object} BasketItem An item in the basket * @typedef {Object} BasketItem An item in the basket
* @property {number} id The id of the product * @property {number} id The id of the product
@ -6,7 +7,7 @@
* @property {number} unit_price The unit price of the product * @property {number} unit_price The unit price of the product
*/ */
const BASKET_ITEMS_COOKIE_NAME = "basket_items"; const BASKET_ITEMS_COOKIE_NAME = 'basket_items'
/** /**
* Search for a cookie by name * Search for a cookie by name
@ -14,14 +15,14 @@ const BASKET_ITEMS_COOKIE_NAME = "basket_items";
* @returns {string|null|undefined} the value of the cookie or null if it does not exist, undefined if not found * @returns {string|null|undefined} the value of the cookie or null if it does not exist, undefined if not found
*/ */
function getCookie (name) { function getCookie (name) {
if (!document.cookie || document.cookie.length === 0) return null; if (!document.cookie || document.cookie.length === 0) return null
let found = document.cookie const found = document.cookie
.split(';') .split(';')
.map(c => c.trim()) .map(c => c.trim())
.find(c => c.startsWith(name + '=')); .find(c => c.startsWith(name + '='))
return found === undefined ? undefined : decodeURIComponent(found.split('=')[1]); return found === undefined ? undefined : decodeURIComponent(found.split('=')[1])
} }
/** /**
@ -29,17 +30,17 @@ function getCookie(name) {
* @returns {BasketItem[]|[]} the items in the basket * @returns {BasketItem[]|[]} the items in the basket
*/ */
function get_starting_items () { function get_starting_items () {
const cookie = getCookie(BASKET_ITEMS_COOKIE_NAME); const cookie = getCookie(BASKET_ITEMS_COOKIE_NAME)
if (!cookie) { if (!cookie) {
return [] return []
} }
// Django cookie backend converts `,` to `\054` // Django cookie backend converts `,` to `\054`
let parsed = JSON.parse(cookie.replace(/\\054/g, ',')); let parsed = JSON.parse(cookie.replace(/\\054/g, ','))
if (typeof parsed === "string") { if (typeof parsed === 'string') {
// In some conditions, a second parsing is needed // In some conditions, a second parsing is needed
parsed = JSON.parse(parsed); parsed = JSON.parse(parsed)
} }
const res = Array.isArray(parsed) ? parsed : []; const res = Array.isArray(parsed) ? parsed : []
return res.filter((i) => !!document.getElementById(i.id)) return res.filter((i) => !!document.getElementById(i.id))
} }
@ -53,7 +54,7 @@ document.addEventListener('alpine:init', () => {
*/ */
get_total () { get_total () {
return this.items return this.items
.reduce((acc, item) => acc + item["quantity"] * item["unit_price"], 0); .reduce((acc, item) => acc + item.quantity * item.unit_price, 0)
}, },
/** /**
@ -61,8 +62,8 @@ document.addEventListener('alpine:init', () => {
* @param {BasketItem} item * @param {BasketItem} item
*/ */
add (item) { add (item) {
item.quantity++; item.quantity++
this.set_cookies(); this.set_cookies()
}, },
/** /**
@ -70,23 +71,23 @@ document.addEventListener('alpine:init', () => {
* @param {BasketItem} item_id * @param {BasketItem} item_id
*/ */
remove (item_id) { remove (item_id) {
const index = this.items.findIndex(e => e.id === item_id); const index = this.items.findIndex(e => e.id === item_id)
if (index < 0) return; if (index < 0) return
this.items[index].quantity -= 1; this.items[index].quantity -= 1
if (this.items[index].quantity === 0) { if (this.items[index].quantity === 0) {
this.items = this.items.filter((e) => e.id !== this.items[index].id); this.items = this.items.filter((e) => e.id !== this.items[index].id)
} }
this.set_cookies(); this.set_cookies()
}, },
/** /**
* Remove all the items from the basket & cleans the catalog CSS classes * Remove all the items from the basket & cleans the catalog CSS classes
*/ */
clear_basket () { clear_basket () {
this.items = []; this.items = []
this.set_cookies(); this.set_cookies()
}, },
/** /**
@ -95,9 +96,9 @@ document.addEventListener('alpine:init', () => {
*/ */
set_cookies () { set_cookies () {
if (this.items.length === 0) { if (this.items.length === 0) {
document.cookie = `${BASKET_ITEMS_COOKIE_NAME}=;Max-Age=0`; document.cookie = `${BASKET_ITEMS_COOKIE_NAME}=;Max-Age=0`
} else { } else {
document.cookie = `${BASKET_ITEMS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(this.items))};Max-Age=3600`; document.cookie = `${BASKET_ITEMS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(this.items))};Max-Age=3600`
} }
}, },
@ -109,17 +110,17 @@ document.addEventListener('alpine:init', () => {
* @returns {BasketItem} The created item * @returns {BasketItem} The created item
*/ */
create_item (id, name, price) { create_item (id, name, price) {
let new_item = { const new_item = {
id: id, id,
name: name, name,
quantity: 0, quantity: 0,
unit_price: price unit_price: price
}; }
this.items.push(new_item); this.items.push(new_item)
this.add(new_item); this.add(new_item)
return new_item; return new_item
}, },
/** /**
@ -135,10 +136,10 @@ document.addEventListener('alpine:init', () => {
// if the item is not in the basket, we create it // if the item is not in the basket, we create it
// else we add + 1 to it // else we add + 1 to it
if (!item) { if (!item) {
item = this.create_item(id, name, price); item = this.create_item(id, name, price)
} else { } else {
this.add(item); this.add(item)
}
} }
},
})) }))
}) })

View File

@ -1,3 +1,6 @@
/* global et_data, et_data_url, billing_info_url,
billing_info_success_message, billing_info_failure_message */
/** /**
* @readonly * @readonly
* @enum {number} * @enum {number}
@ -5,75 +8,75 @@
const BillingInfoReqState = { const BillingInfoReqState = {
SUCCESS: 1, SUCCESS: 1,
FAILURE: 2, FAILURE: 2,
SENDING: 3, SENDING: 3
}; }
document.addEventListener("alpine:init", () => { document.addEventListener('alpine:init', () => {
Alpine.store("billing_inputs", { Alpine.store('billing_inputs', {
data: et_data, data: et_data,
async fill () { async fill () {
document.getElementById("bank-submit-button").disabled = true; document.getElementById('bank-submit-button').disabled = true
const res = await fetch(et_data_url); const res = await fetch(et_data_url)
if (res.ok) { if (res.ok) {
this.data = await res.json(); this.data = await res.json()
document.getElementById("bank-submit-button").disabled = false; document.getElementById('bank-submit-button').disabled = false
} }
}, }
}); })
Alpine.data("billing_infos", () => ({ Alpine.data('billing_infos', () => ({
/** @type {BillingInfoReqState | null} */ /** @type {BillingInfoReqState | null} */
req_state: null, req_state: null,
async send_form () { async send_form () {
this.req_state = BillingInfoReqState.SENDING; this.req_state = BillingInfoReqState.SENDING
const form = document.getElementById("billing_info_form"); const form = document.getElementById('billing_info_form')
document.getElementById("bank-submit-button").disabled = true; document.getElementById('bank-submit-button').disabled = true
let payload = Object.fromEntries( const payload = Object.fromEntries(
Array.from(form.querySelectorAll("input, select")) Array.from(form.querySelectorAll('input, select'))
.filter((elem) => elem.type !== "submit" && elem.value) .filter((elem) => elem.type !== 'submit' && elem.value)
.map((elem) => [elem.name, elem.value]), .map((elem) => [elem.name, elem.value])
); )
const res = await fetch(billing_info_url, { const res = await fetch(billing_info_url, {
method: "PUT", method: 'PUT',
body: JSON.stringify(payload), body: JSON.stringify(payload)
}); })
this.req_state = res.ok this.req_state = res.ok
? BillingInfoReqState.SUCCESS ? BillingInfoReqState.SUCCESS
: BillingInfoReqState.FAILURE; : BillingInfoReqState.FAILURE
if (res.status === 422) { if (res.status === 422) {
const errors = (await res.json())["detail"].map((err) => err["loc"]).flat(); const errors = (await res.json()).detail.map((err) => err.loc).flat()
Array.from(form.querySelectorAll("input")) Array.from(form.querySelectorAll('input'))
.filter((elem) => errors.includes(elem.name)) .filter((elem) => errors.includes(elem.name))
.forEach((elem) => { .forEach((elem) => {
elem.setCustomValidity(gettext("Incorrect value")); elem.setCustomValidity(gettext('Incorrect value'))
elem.reportValidity(); elem.reportValidity()
elem.oninput = () => elem.setCustomValidity(""); elem.oninput = () => elem.setCustomValidity('')
}); })
} else if (res.ok) { } else if (res.ok) {
Alpine.store("billing_inputs").fill(); Alpine.store('billing_inputs').fill()
} }
}, },
get_alert_color () { get_alert_color () {
if (this.req_state === BillingInfoReqState.SUCCESS) { if (this.req_state === BillingInfoReqState.SUCCESS) {
return "green"; return 'green'
} }
if (this.req_state === BillingInfoReqState.FAILURE) { if (this.req_state === BillingInfoReqState.FAILURE) {
return "red"; return 'red'
} }
return ""; return ''
}, },
get_alert_message () { get_alert_message () {
if (this.req_state === BillingInfoReqState.SUCCESS) { if (this.req_state === BillingInfoReqState.SUCCESS) {
return billing_info_success_message; return billing_info_success_message
} }
if (this.req_state === BillingInfoReqState.FAILURE) { if (this.req_state === BillingInfoReqState.FAILURE) {
return billing_info_failure_message; return billing_info_failure_message
} }
return ""; return ''
}, }
})); }))
}); })

3138
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,18 +14,26 @@
"sideEffects": [ "sideEffects": [
".css" ".css"
], ],
"standard": {
"ignore": [
"core/static/vendored",
"staticfiles/generated"
],
"globals": [ "Alpine", "$", "jQuery", "gettext", "interpolate" ]
},
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.4", "@babel/preset-env": "^7.25.4",
"babel-loader": "^9.2.1", "babel-loader": "^9.2.1",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0",
"expose-loader": "^5.0.0", "expose-loader": "^5.0.0",
"mini-css-extract-plugin": "^2.9.1", "mini-css-extract-plugin": "^2.9.1",
"source-map-loader": "^5.0.0", "source-map-loader": "^5.0.0",
"standard": "^17.1.2",
"terser-webpack-plugin": "^5.3.10", "terser-webpack-plugin": "^5.3.10",
"webpack": "^5.94.0", "webpack": "^5.94.0",
"webpack-cli": "^5.1.4", "webpack-cli": "^5.1.4"
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",

View File

@ -1,3 +1,7 @@
/* eslint-disable camelcase */
/* global Image, History, fetch_paginated, picture_endpoint,
sithSelect2, remote_data_source, first_picture_id,
album_url, user_is_sas_admin, user_id */
/** /**
* @typedef PictureIdentification * @typedef PictureIdentification
* @property {number} id The actual id of the identification * @property {number} id The actual id of the identification
@ -9,21 +13,22 @@
* able to prefetch its data. * able to prefetch its data.
*/ */
class PictureWithIdentifications { class PictureWithIdentifications {
identifications = null; identifications = null
image_loading = false; image_loading = false
identifications_loading = false; identifications_loading = false
/** /**
* @param {Picture} picture * @param {Picture} picture
*/ */
constructor (picture) { constructor (picture) {
Object.assign(this, picture); Object.assign(this, picture)
} }
/** /**
* @param {Picture} picture * @param {Picture} picture
*/ */
static from_picture (picture) { static from_picture (picture) {
return new PictureWithIdentifications(picture); return new PictureWithIdentifications(picture)
} }
/** /**
@ -34,17 +39,17 @@ class PictureWithIdentifications {
*/ */
async load_identifications (options) { async load_identifications (options) {
if (this.identifications_loading) { if (this.identifications_loading) {
return; // The users are already being fetched. return // The users are already being fetched.
} }
if (!!this.identifications && !options?.force_reload) { if (!!this.identifications && !options?.force_reload) {
// The users are already fetched // The users are already fetched
// and the user does not want to force the reload // and the user does not want to force the reload
return; return
} }
this.identifications_loading = true; this.identifications_loading = true
const url = `/api/sas/picture/${this.id}/identified`; const url = `/api/sas/picture/${this.id}/identified`
this.identifications = await (await fetch(url)).json(); this.identifications = await (await fetch(url)).json()
this.identifications_loading = false; this.identifications_loading = false
} }
/** /**
@ -52,20 +57,20 @@ class PictureWithIdentifications {
* @return {Promise<void>} * @return {Promise<void>}
*/ */
async preload () { async preload () {
const img = new Image(); const img = new Image()
img.src = this.compressed_url; img.src = this.compressed_url
if (!img.complete) { if (!img.complete) {
this.image_loading = true; this.image_loading = true
img.addEventListener("load", () => { img.addEventListener('load', () => {
this.image_loading = false; this.image_loading = false
}); })
} }
await this.load_identifications(); await this.load_identifications()
} }
} }
document.addEventListener("alpine:init", () => { document.addEventListener('alpine:init', () => {
Alpine.data("picture_viewer", () => ({ Alpine.data('picture_viewer', () => ({
/** /**
* All the pictures that can be displayed on this picture viewer * All the pictures that can be displayed on this picture viewer
* @type PictureWithIdentifications[] * @type PictureWithIdentifications[]
@ -80,14 +85,14 @@ document.addEventListener("alpine:init", () => {
current_picture: { current_picture: {
is_moderated: true, is_moderated: true,
id: null, id: null,
name: "", name: '',
display_name: "", display_name: '',
compressed_url: "", compressed_url: '',
profile_url: "", profile_url: '',
full_size_url: "", full_size_url: '',
owner: "", owner: '',
date: new Date(), date: new Date(),
identifications: [], identifications: []
}, },
/** /**
* The picture which will be displayed next if the user press the "next" button * The picture which will be displayed next if the user press the "next" button
@ -110,7 +115,7 @@ document.addEventListener("alpine:init", () => {
* Error message when a moderation operation fails * Error message when a moderation operation fails
* @type string * @type string
**/ **/
moderation_error: "", moderation_error: '',
/** /**
* Method of pushing new url to the browser history * Method of pushing new url to the browser history
* Used by popstate event and always reset to it's default value when used * Used by popstate event and always reset to it's default value when used
@ -120,40 +125,40 @@ document.addEventListener("alpine:init", () => {
async init () { async init () {
this.pictures = (await fetch_paginated(picture_endpoint)).map( this.pictures = (await fetch_paginated(picture_endpoint)).map(
PictureWithIdentifications.from_picture, PictureWithIdentifications.from_picture
); )
this.selector = sithSelect2({ this.selector = sithSelect2({
element: $(this.$refs.search), element: $(this.$refs.search),
data_source: remote_data_source("/api/user/search", { data_source: remote_data_source('/api/user/search', {
excluded: () => [ excluded: () => [
...(this.current_picture.identifications || []).map( ...(this.current_picture.identifications || []).map(
(i) => i.user.id, (i) => i.user.id
), )
], ],
result_converter: (obj) => Object({ ...obj, text: obj.display_name }), result_converter: (obj) => Object({ ...obj, text: obj.display_name })
}), }),
picture_getter: (user) => user.profile_pict, picture_getter: (user) => user.profile_pict
}); })
this.current_picture = this.pictures.find( this.current_picture = this.pictures.find(
(i) => i.id === first_picture_id, (i) => i.id === first_picture_id
); )
this.$watch("current_picture", (current, previous) => { this.$watch('current_picture', (current, previous) => {
if (current === previous) { /* Avoid recursive updates */ if (current === previous) { /* Avoid recursive updates */
return; return
} }
this.update_picture(); this.update_picture()
}); })
window.addEventListener("popstate", async (event) => { window.addEventListener('popstate', async (event) => {
if (!event.state || event.state.sas_picture_id === undefined) { if (!event.state || event.state.sas_picture_id === undefined) {
return; return
} }
this.pushstate = History.REPLACE; this.pushstate = History.REPLACE
this.current_picture = this.pictures.find( this.current_picture = this.pictures.find(
(i) => i.id === parseInt(event.state.sas_picture_id), (i) => i.id === parseInt(event.state.sas_picture_id)
); )
}); })
this.pushstate = History.REPLACE; /* Avoid first url push */ this.pushstate = History.REPLACE /* Avoid first url push */
await this.update_picture(); await this.update_picture()
}, },
/** /**
@ -167,74 +172,74 @@ document.addEventListener("alpine:init", () => {
async update_picture () { async update_picture () {
const update_args = [ const update_args = [
{ sas_picture_id: this.current_picture.id }, { sas_picture_id: this.current_picture.id },
"", '',
`/sas/picture/${this.current_picture.id}/`, `/sas/picture/${this.current_picture.id}/`
]; ]
if (this.pushstate === History.REPLACE) { if (this.pushstate === History.REPLACE) {
window.history.replaceState(...update_args); window.history.replaceState(...update_args)
this.pushstate = History.PUSH; this.pushstate = History.PUSH
} else { } else {
window.history.pushState(...update_args); window.history.pushState(...update_args)
} }
this.moderation_error = ""; this.moderation_error = ''
const index = this.pictures.indexOf(this.current_picture); const index = this.pictures.indexOf(this.current_picture)
this.previous_picture = this.pictures[index - 1] || null; this.previous_picture = this.pictures[index - 1] || null
this.next_picture = this.pictures[index + 1] || null; this.next_picture = this.pictures[index + 1] || null
await this.current_picture.load_identifications(); await this.current_picture.load_identifications()
this.$refs.main_picture?.addEventListener("load", () => { this.$refs.main_picture?.addEventListener('load', () => {
// once the current picture is loaded, // once the current picture is loaded,
// start preloading the next and previous pictures // start preloading the next and previous pictures
this.next_picture?.preload(); this.next_picture?.preload()
this.previous_picture?.preload(); this.previous_picture?.preload()
}); })
}, },
async moderate_picture () { async moderate_picture () {
const res = await fetch( const res = await fetch(
`/api/sas/picture/${this.current_picture.id}/moderate`, `/api/sas/picture/${this.current_picture.id}/moderate`,
{ {
method: "PATCH", 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; 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 () { async delete_picture () {
const res = await fetch(`/api/sas/picture/${this.current_picture.id}`, { const res = await fetch(`/api/sas/picture/${this.current_picture.id}`, {
method: "DELETE", method: 'DELETE'
}); })
if (!res.ok) { if (!res.ok) {
this.moderation_error = this.moderation_error =
gettext("Couldn't delete picture") + " : " + res.statusText; gettext("Couldn't delete picture") + ' : ' + res.statusText
return; return
} }
this.pictures.splice(this.pictures.indexOf(this.current_picture), 1); this.pictures.splice(this.pictures.indexOf(this.current_picture), 1)
if (this.pictures.length === 0) { if (this.pictures.length === 0) {
// The deleted picture was the only one in the list. // The deleted picture was the only one in the list.
// As the album is now empty, go back to the parent page // As the album is now empty, go back to the parent page
document.location.href = album_url; document.location.href = album_url
} }
this.current_picture = this.next_picture || this.previous_picture; this.current_picture = this.next_picture || this.previous_picture
}, },
/** /**
* Send the identification request and update the list of identified users. * Send the identification request and update the list of identified users.
*/ */
async submit_identification () { async submit_identification () {
const url = `/api/sas/picture/${this.current_picture.id}/identified`; const url = `/api/sas/picture/${this.current_picture.id}/identified`
await fetch(url, { await fetch(url, {
method: "PUT", method: 'PUT',
body: JSON.stringify(this.selector.val().map((i) => parseInt(i))), body: JSON.stringify(this.selector.val().map((i) => parseInt(i)))
}); })
// refresh the identified users list // refresh the identified users list
await this.current_picture.load_identifications({ force_reload: true }); await this.current_picture.load_identifications({ force_reload: true })
this.selector.empty().trigger("change"); this.selector.empty().trigger('change')
}, },
/** /**
@ -243,7 +248,7 @@ document.addEventListener("alpine:init", () => {
* @return {boolean} * @return {boolean}
*/ */
can_be_removed (identification) { can_be_removed (identification) {
return user_is_sas_admin || identification.user.id === user_id; return user_is_sas_admin || identification.user.id === user_id
}, },
/** /**
@ -252,14 +257,14 @@ document.addEventListener("alpine:init", () => {
*/ */
async remove_identification (identification) { async remove_identification (identification) {
const res = await fetch(`/api/sas/relation/${identification.id}`, { const res = await fetch(`/api/sas/relation/${identification.id}`, {
method: "DELETE", method: 'DELETE'
}); })
if (res.ok && Array.isArray(this.current_picture.identifications)) { if (res.ok && Array.isArray(this.current_picture.identifications)) {
this.current_picture.identifications = this.current_picture.identifications =
this.current_picture.identifications.filter( this.current_picture.identifications.filter(
(i) => i.id !== identification.id, (i) => i.id !== identification.id
); )
} }
}, }
})); }))
}); })

View File

@ -1,14 +1,13 @@
const glob = require('glob'); const glob = require('glob')
const path = require('path'); const path = require('path')
const webpack = require("webpack"); const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); const TerserPlugin = require('terser-webpack-plugin')
const TerserPlugin = require("terser-webpack-plugin");
module.exports = { module.exports = {
entry: glob.sync('./!(static)/static/webpack/**?(-)index.js').reduce((obj, el) => { entry: glob.sync('./!(static)/static/webpack/**?(-)index.js').reduce((obj, el) => {
obj[path.parse(el).name] = './' + el; obj[path.parse(el).name] = './' + el
return obj; return obj
}, {}), }, {}),
output: { output: {
filename: '[name].js', filename: '[name].js',
@ -16,24 +15,24 @@ module.exports = {
clean: true clean: true
}, },
plugins: [ plugins: [
new MiniCssExtractPlugin(), new MiniCssExtractPlugin()
], ],
optimization: { optimization: {
minimizer: [ minimizer: [
"...", '...',
new CssMinimizerPlugin({ new CssMinimizerPlugin({
parallel: true, parallel: true
}), }),
new TerserPlugin({ new TerserPlugin({
parallel: true, parallel: true,
terserOptions: { terserOptions: {
mangle: true, mangle: true,
compress: { compress: {
drop_console: true, drop_console: true
},
} }
}), }
], })
]
}, },
module: { module: {
rules: [ rules: [
@ -42,18 +41,18 @@ module.exports = {
sideEffects: true, sideEffects: true,
use: [ use: [
MiniCssExtractPlugin.loader, MiniCssExtractPlugin.loader,
"css-loader", 'css-loader'
], ]
}, },
{ {
test: /\.(jpe?g|png|gif|woff|woff2|eot|ttf|otf)$/i, test: /\.(jpe?g|png|gif)$/i,
type: 'asset/resource' type: 'asset/resource'
}, },
{ {
test: /\.m?js$/, test: /\.m?js$/,
exclude: /node_modules/, exclude: /node_modules/,
use: { use: {
loader: "babel-loader", loader: 'babel-loader',
options: { options: {
presets: ['@babel/preset-env'] presets: ['@babel/preset-env']
} }
@ -61,12 +60,12 @@ module.exports = {
}, },
{ {
test: /\.js$/, test: /\.js$/,
enforce: "pre", enforce: 'pre',
use: ["source-map-loader"], use: ['source-map-loader']
}, },
{ {
test: require.resolve("jquery"), test: require.resolve('jquery'),
loader: "expose-loader", loader: 'expose-loader',
options: { options: {
exposes: [ exposes: [
{ {
@ -81,9 +80,9 @@ module.exports = {
globalName: ['window.jQuery'], globalName: ['window.jQuery'],
override: true override: true
} }
], ]
}, }
}, }
], ]
}, }
}; }