mirror of
				https://github.com/ae-utbm/sith.git
				synced 2025-10-31 09:03:06 +00:00 
			
		
		
		
	Apply standard formater and linter on js files
This commit is contained in:
		| @@ -8,6 +8,10 @@ repos: | ||||
|         args: ["--fix", "--silent"] | ||||
|       # Run the formatter. | ||||
|       - id: ruff-format | ||||
|   - repo: https://github.com/standard/standard | ||||
|     rev: v17.1.2 | ||||
|     hooks: | ||||
|       - id: standard | ||||
|   - repo: https://github.com/rtts/djhtml | ||||
|     rev: 3.0.6 | ||||
|     hooks: | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| [](#) | ||||
| [](https://ae-utbm.github.io/sith) | ||||
| [](https://squidfunk.github.io/mkdocs-material/) | ||||
| [](https://standardjs.com) | ||||
| [](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/). | ||||
|   | ||||
| @@ -1,24 +1,20 @@ | ||||
| $(document).ready(function () { | ||||
|   $('#poster_list #view').click(function (e) { | ||||
|     $('#view').removeClass('active') | ||||
|   }) | ||||
|  | ||||
|     $("#poster_list #view").click(function(e){ | ||||
|         $("#view").removeClass("active"); | ||||
|     }); | ||||
|   $('#poster_list .poster .image').click(function (e) { | ||||
|     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){ | ||||
|  | ||||
|         el = $(e.target); | ||||
|         if(el.hasClass("image")) | ||||
|             el = el.find("img") | ||||
|         $("#poster_list #view #placeholder").html(el.clone()); | ||||
|  | ||||
|         $("#view").addClass("active"); | ||||
|     }); | ||||
|     $('#view').addClass('active') | ||||
|   }) | ||||
|  | ||||
|   $(document).keyup(function (e) { | ||||
|         if (e.keyCode == 27) { // escape key maps to keycode `27` | ||||
|             e.preventDefault(); | ||||
|             $("#view").removeClass("active"); | ||||
|     if (e.keyCode === 27) { // escape key maps to keycode `27` | ||||
|       e.preventDefault() | ||||
|       $('#view').removeClass('active') | ||||
|     } | ||||
|     }); | ||||
|  | ||||
| }); | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,118 +1,104 @@ | ||||
| // TODO: remove disable | ||||
| /* eslint-disable no-undef, camelcase */ | ||||
| $(document).ready(function () { | ||||
|   transition_time = 1000 | ||||
|  | ||||
|     transition_time = 1000; | ||||
|  | ||||
|     i = 0; | ||||
|     max = $("#slideshow .slide").length; | ||||
|   i = 0 | ||||
|   max = $('#slideshow .slide').length | ||||
|  | ||||
|   next_trigger = 0 | ||||
|  | ||||
|   function enterFullscreen () { | ||||
|         element = document.getElementById("slideshow"); | ||||
|         $(element).addClass("fullscreen"); | ||||
|     element = document.getElementById('slideshow') | ||||
|     $(element).addClass('fullscreen') | ||||
|     if (element.requestFullscreen) { | ||||
|             element.requestFullscreen(); | ||||
|       element.requestFullscreen() | ||||
|     } else if (element.mozRequestFullScreen) { | ||||
|             element.mozRequestFullScreen(); | ||||
|       element.mozRequestFullScreen() | ||||
|     } else if (element.webkitRequestFullscreen) { | ||||
|             element.webkitRequestFullscreen(); | ||||
|       element.webkitRequestFullscreen() | ||||
|     } else if (element.msRequestFullscreen) { | ||||
|             element.msRequestFullscreen(); | ||||
|       element.msRequestFullscreen() | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   function exitFullscreen () { | ||||
|         element = document.getElementById("slideshow"); | ||||
|         $(element).removeClass("fullscreen"); | ||||
|     element = document.getElementById('slideshow') | ||||
|     $(element).removeClass('fullscreen') | ||||
|     if (document.exitFullscreen) { | ||||
|             document.exitFullscreen(); | ||||
|       document.exitFullscreen() | ||||
|     } else if (document.webkitExitFullscreen) { | ||||
|             document.webkitExitFullscreen(); | ||||
|       document.webkitExitFullscreen() | ||||
|     } else if (document.mozCancelFullScreen) { | ||||
|             document.mozCancelFullScreen(); | ||||
|       document.mozCancelFullScreen() | ||||
|     } else if (document.msExitFullscreen) { | ||||
|             document.msExitFullscreen(); | ||||
|       document.msExitFullscreen() | ||||
|     } | ||||
|   } | ||||
|  | ||||
|     function init_progress_bar() | ||||
|     { | ||||
|  | ||||
|         $("#slideshow #progress_bar").css("transition", "none"); | ||||
|         $("#slideshow #progress_bar").removeClass("progress"); | ||||
|         $("#slideshow #progress_bar").addClass("init"); | ||||
|  | ||||
|   function init_progress_bar () { | ||||
|     $('#slideshow #progress_bar').css('transition', 'none') | ||||
|     $('#slideshow #progress_bar').removeClass('progress') | ||||
|     $('#slideshow #progress_bar').addClass('init') | ||||
|   } | ||||
|  | ||||
|     function start_progress_bar(display_time) | ||||
|     { | ||||
|  | ||||
|         $("#slideshow #progress_bar").removeClass("init"); | ||||
|         $("#slideshow #progress_bar").addClass("progress"); | ||||
|         $("#slideshow #progress_bar").css("transition", "width " + display_time + "s linear") | ||||
|  | ||||
|   function start_progress_bar (display_time) { | ||||
|     $('#slideshow #progress_bar').removeClass('init') | ||||
|     $('#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(); | ||||
|         slide = $($("#slideshow .slide").get(i % max)); | ||||
|         slide.removeClass("center"); | ||||
|         slide.addClass("left"); | ||||
|     next_slide = $($('#slideshow .slide').get((i + 1) % max)) | ||||
|     next_slide.removeClass('right') | ||||
|     next_slide.addClass('center') | ||||
|     display_time = next_slide.attr('display_time') || 2 | ||||
|  | ||||
|         next_slide = $($("#slideshow .slide").get((i + 1) % max)); | ||||
|         next_slide.removeClass("right"); | ||||
|         next_slide.addClass("center"); | ||||
|         display_time = next_slide.attr("display_time") || 2; | ||||
|     $('#slideshow .bullet').removeClass('active') | ||||
|     bullet = $('#slideshow .bullet')[(i + 1) % max] | ||||
|     $(bullet).addClass('active') | ||||
|  | ||||
|         $("#slideshow .bullet").removeClass("active"); | ||||
|         bullet = $("#slideshow .bullet")[(i + 1) % max]; | ||||
|         $(bullet).addClass("active"); | ||||
|  | ||||
|         i = (i + 1) % max; | ||||
|     i = (i + 1) % max | ||||
|  | ||||
|     setTimeout(function () { | ||||
|       others_left = $('#slideshow .slide.left') | ||||
|       others_left.removeClass('left') | ||||
|       others_left.addClass('right') | ||||
|  | ||||
|             others_left = $("#slideshow .slide.left"); | ||||
|             others_left.removeClass("left"); | ||||
|             others_left.addClass("right"); | ||||
|  | ||||
|             start_progress_bar(display_time); | ||||
|             next_trigger = setTimeout(next, display_time * 1000); | ||||
|  | ||||
|         }, transition_time); | ||||
|  | ||||
|       start_progress_bar(display_time) | ||||
|       next_trigger = setTimeout(next, display_time * 1000) | ||||
|     }, transition_time) | ||||
|   } | ||||
|  | ||||
|  | ||||
|     display_time = $("#slideshow .center").attr("display_time"); | ||||
|     init_progress_bar(); | ||||
|   display_time = $('#slideshow .center').attr('display_time') | ||||
|   init_progress_bar() | ||||
|   setTimeout(function () { | ||||
|     if (max > 1) { | ||||
|             start_progress_bar(display_time); | ||||
|             setTimeout(next, display_time * 1000); | ||||
|       start_progress_bar(display_time) | ||||
|       setTimeout(next, display_time * 1000) | ||||
|     } | ||||
|     }, 10); | ||||
|   }, 10) | ||||
|  | ||||
|  | ||||
|     $("#slideshow").click(function(e){ | ||||
|         if(!$("#slideshow").hasClass("fullscreen")) | ||||
|         { | ||||
|             console.log("Entering fullscreen ..."); | ||||
|             enterFullscreen(); | ||||
|   $('#slideshow').click(function (e) { | ||||
|     if (!$('#slideshow').hasClass('fullscreen')) { | ||||
|       console.log('Entering fullscreen ...') | ||||
|       enterFullscreen() | ||||
|     } else { | ||||
|             console.log("Exiting fullscreen ..."); | ||||
|             exitFullscreen(); | ||||
|       console.log('Exiting fullscreen ...') | ||||
|       exitFullscreen() | ||||
|     } | ||||
|     }); | ||||
|   }) | ||||
|  | ||||
|   $(document).keyup(function (e) { | ||||
|         if (e.keyCode == 27) { // escape key maps to keycode `27` | ||||
|             e.preventDefault(); | ||||
|             console.log("Exiting fullscreen ..."); | ||||
|             exitFullscreen(); | ||||
|     if (e.keyCode === 27) { // escape key maps to keycode `27` | ||||
|       e.preventDefault() | ||||
|       console.log('Exiting fullscreen ...') | ||||
|       exitFullscreen() | ||||
|     } | ||||
|     }); | ||||
|  | ||||
| }); | ||||
|   }) | ||||
| }) | ||||
|   | ||||
| @@ -1,59 +1,60 @@ | ||||
| /* eslint-disable camelcase */ | ||||
| $(function () { | ||||
|     buttons = $(".choose_file_button"); | ||||
|     popups = $(".choose_file_widget"); | ||||
|   // const buttons = $('.choose_file_button') | ||||
|   const popups = $('.choose_file_widget') | ||||
|   popups.dialog({ | ||||
|     autoOpen: false, | ||||
|     modal: true, | ||||
|         width: "90%", | ||||
|     width: '90%', | ||||
|     create: function (event) { | ||||
|             target = $(event.target); | ||||
|       const target = $(event.target) | ||||
|       target.parent().css({ | ||||
|                 'position': 'fixed', | ||||
|                 'top': '5%', | ||||
|                 'bottom': '5%', | ||||
|             }); | ||||
|             target.css("height", "300px"); | ||||
|             console.log(target); | ||||
|         position: 'fixed', | ||||
|         top: '5%', | ||||
|         bottom: '5%' | ||||
|       }) | ||||
|       target.css('height', '300px') | ||||
|       console.log(target) | ||||
|     }, | ||||
|     buttons: [ | ||||
|       { | ||||
|             text: "Choose", | ||||
|         text: 'Choose', | ||||
|         click: function () { | ||||
|                 console.log($("#file_id")); | ||||
|                 $("input[name="+$(this).attr('name')+"]").attr('value', $("#file_id").attr('value')); | ||||
|                 $( this ).dialog( "close" ); | ||||
|           console.log($('#file_id')) | ||||
|           $('input[name=' + $(this).attr('name') + ']').attr('value', $('#file_id').attr('value')) | ||||
|           $(this).dialog('close') | ||||
|         }, | ||||
|             disabled: true, | ||||
|         disabled: true | ||||
|       } | ||||
|         ], | ||||
|     }); | ||||
|     $( ".choose_file_button" ).button().on( "click", function() { | ||||
|         popup = popups.filter("[name="+$(this).attr('name')+"]"); | ||||
|         console.log(popup); | ||||
|         popup.html('<iframe src="/file/popup" width="100%" height="95%"></iframe><div id="file_id" value="null" />'); | ||||
|         popup.dialog({title: $(this).text()}).dialog( "open" ); | ||||
|     }); | ||||
|     $("#quick_notif li").click(function () { | ||||
|         $(this).hide(); | ||||
|     ] | ||||
|   }) | ||||
|   $('.choose_file_button').button().on('click', function () { | ||||
|     const popup = popups.filter('[name=' + $(this).attr('name') + ']') | ||||
|     console.log(popup) | ||||
|     popup.html('<iframe src="/file/popup" width="100%" height="95%"></iframe><div id="file_id" value="null" />') | ||||
|     popup.dialog({ title: $(this).text() }).dialog('open') | ||||
|   }) | ||||
|   $('#quick_notif li').click(function () { | ||||
|     $(this).hide() | ||||
|   }) | ||||
| }) | ||||
| }); | ||||
|  | ||||
| function createQuickNotif(msg) { | ||||
| function createQuickNotif (msg) { // eslint-disable-line no-unused-vars | ||||
|   const el = document.createElement('li') | ||||
|   el.textContent = msg | ||||
|   el.addEventListener('click', () => el.parentNode.removeChild(el)) | ||||
|   document.getElementById('quick_notif').appendChild(el) | ||||
| } | ||||
|  | ||||
| function deleteQuickNotifs() { | ||||
| function deleteQuickNotifs () { // eslint-disable-line no-unused-vars | ||||
|   const el = document.getElementById('quick_notif') | ||||
|   while (el.firstChild) { | ||||
|     el.removeChild(el.firstChild) | ||||
|   } | ||||
| } | ||||
|  | ||||
| function display_notif() { | ||||
|     $('#header_notif').toggle().parent().toggleClass("white"); | ||||
| function display_notif () { // eslint-disable-line no-unused-vars | ||||
|   $('#header_notif').toggle().parent().toggleClass('white') | ||||
| } | ||||
|  | ||||
| // 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 | ||||
| // 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 | ||||
| function getCSRFToken() { | ||||
|     return $("[name=csrfmiddlewaretoken]").val(); | ||||
| function getCSRFToken () { // eslint-disable-line no-unused-vars | ||||
|   return $('[name=csrfmiddlewaretoken]').val() | ||||
| } | ||||
|  | ||||
|  | ||||
| const initialUrlParams = new URLSearchParams(window.location.search); | ||||
| const initialUrlParams = new URLSearchParams(window.location.search) // eslint-disable-line no-unused-vars | ||||
|  | ||||
| /** | ||||
|  * @readonly | ||||
| @@ -76,8 +76,8 @@ const initialUrlParams = new URLSearchParams(window.location.search); | ||||
| const History = { | ||||
|   NONE: 0, | ||||
|   PUSH: 1, | ||||
|     REPLACE: 2, | ||||
| }; | ||||
|   REPLACE: 2 | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param {string} key | ||||
| @@ -85,27 +85,27 @@ const History = { | ||||
|  * @param {History} action | ||||
|  * @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) { | ||||
|         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 | ||||
|     url.searchParams.delete(key) | ||||
|   } else if (Array.isArray(value)) { | ||||
|     url.searchParams.delete(key) | ||||
|     value.forEach((v) => url.searchParams.append(key, v)) | ||||
|   } else { | ||||
|         url.searchParams.set(key, value); | ||||
|     url.searchParams.set(key, value) | ||||
|   } | ||||
|  | ||||
|   if (action === History.PUSH) { | ||||
|         history.pushState(null, "", url.toString()); | ||||
|     window.history.pushState(null, '', url.toString()) | ||||
|   } 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 | ||||
| @@ -116,27 +116,27 @@ function update_query_string(key, value, action = History.REPLACE, url = null) { | ||||
|  * @param {string} url The paginated endpoint to fetch | ||||
|  * @return {Promise<Object[]>} | ||||
|  */ | ||||
| async function fetch_paginated(url) { | ||||
|   const max_per_page = 199; | ||||
|   const paginated_url = new URL(url, document.location.origin); | ||||
|   paginated_url.searchParams.set("page_size", max_per_page.toString()); | ||||
|   paginated_url.searchParams.set("page", "1"); | ||||
| async function fetch_paginated (url) { // eslint-disable-line no-unused-vars | ||||
|   const max_per_page = 199 | ||||
|   const paginated_url = new URL(url, document.location.origin) | ||||
|   paginated_url.searchParams.set('page_size', max_per_page.toString()) | ||||
|   paginated_url.searchParams.set('page', '1') | ||||
|  | ||||
|   let first_page = (await ( await fetch(paginated_url)).json()); | ||||
|   let results = first_page.results; | ||||
|   const first_page = (await (await fetch(paginated_url)).json()) | ||||
|   const results = first_page.results | ||||
|  | ||||
|   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) { | ||||
|       let promises = []; | ||||
|     const promises = [] | ||||
|     for (let i = 2; i <= nb_pages; i++) { | ||||
|         paginated_url.searchParams.set("page", i.toString()); | ||||
|       paginated_url.searchParams.set('page', i.toString()) | ||||
|       promises.push( | ||||
|         fetch(paginated_url).then(res => res.json().then(json => json.results)) | ||||
|         ); | ||||
|       ) | ||||
|     } | ||||
|     results.push(...(await Promise.all(promises)).flat()) | ||||
|   } | ||||
|   return results; | ||||
|   return results | ||||
| } | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| /* eslint-disable camelcase */ | ||||
| /** | ||||
|  * Builders to use Select2 in our templates. | ||||
|  * | ||||
| @@ -157,15 +158,15 @@ | ||||
| /** | ||||
|  * @param {Select2Options} options | ||||
|  */ | ||||
| function sithSelect2(options) { | ||||
|   const elem = $(options.element); | ||||
| function sithSelect2 (options) { // eslint-disable-line no-unused-vars | ||||
|   const elem = $(options.element) | ||||
|   return elem.select2({ | ||||
|     theme: elem[0].multiple ? "classic" : "default", | ||||
|     theme: elem[0].multiple ? 'classic' : 'default', | ||||
|     minimumInputLength: 2, | ||||
|     templateResult: select_item_builder(options.picture_getter), | ||||
|     ...options.data_source, | ||||
|     ...(options.overrides || {}), | ||||
|   }); | ||||
|     ...(options.overrides || {}) | ||||
|   }) | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -179,12 +180,12 @@ function sithSelect2(options) { | ||||
|  * @param {Select2Object[]} source The array containing the data | ||||
|  * @param {RemoteSourceOptions} options | ||||
|  */ | ||||
| function local_data_source(source, options) { | ||||
|   if (!!options.excluded) { | ||||
|     const ids = options.excluded(); | ||||
|     return { data: source.filter((i) => !ids.includes(i.id)) }; | ||||
| function local_data_source (source, options) { // eslint-disable-line no-unused-vars | ||||
|   if (options.excluded) { | ||||
|     const ids = options.excluded() | ||||
|     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 {RemoteSourceOptions} options | ||||
|  */ | ||||
| function remote_data_source(source, options) { | ||||
|   jQuery.ajaxSettings.traditional = true; | ||||
|   let params = { | ||||
| function remote_data_source (source, options) { // eslint-disable-line no-unused-vars | ||||
|   jQuery.ajaxSettings.traditional = true | ||||
|   const params = { | ||||
|     url: source, | ||||
|     dataType: "json", | ||||
|     dataType: 'json', | ||||
|     cache: true, | ||||
|     delay: 250, | ||||
|     data: function (params) { | ||||
| @@ -214,25 +215,25 @@ function remote_data_source(source, options) { | ||||
|         search: params.term, | ||||
|         exclude: [ | ||||
|           ...(this.val() || []).map((i) => parseInt(i)), | ||||
|           ...(options.excluded ? options.excluded() : []), | ||||
|         ], | ||||
|       }; | ||||
|     }, | ||||
|   }; | ||||
|   if (!!options.result_converter) { | ||||
|     params["processResults"] = function (data) { | ||||
|       return { results: data.results.map(options.result_converter) }; | ||||
|     }; | ||||
|           ...(options.excluded ? options.excluded() : []) | ||||
|         ] | ||||
|       } | ||||
|   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) { | ||||
|     return user.text; | ||||
|     return user.text | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -244,18 +245,18 @@ function item_formatter(user) { | ||||
| function select_item_builder (picture_getter) { | ||||
|   return (item) => { | ||||
|     const picture = | ||||
|       typeof picture_getter === "function" ? picture_getter(item) : null; | ||||
|       typeof picture_getter === 'function' ? picture_getter(item) : null | ||||
|     const img_html = picture | ||||
|       ? `<img  | ||||
|           src="${picture_getter(item)}"  | ||||
|           alt="${item.text}"  | ||||
|           onerror="this.src = '/static/core/img/unknown.jpg'"  | ||||
|         />` | ||||
|       : ""; | ||||
|       : '' | ||||
|  | ||||
|     return $(`<div class="select-item"> | ||||
|         ${img_html} | ||||
|          <span class="select-item-text">${item.text}</span> | ||||
|          </div>`); | ||||
|   }; | ||||
|          </div>`) | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,167 +1,171 @@ | ||||
| /* eslint-disable camelcase */ | ||||
| /* global cytoscape, initialUrlParams, History, update_query_string */ | ||||
|  | ||||
| async function get_graph_data (url, godfathers_depth, godchildren_depth) { | ||||
|   let data = await ( | ||||
|   const data = await ( | ||||
|     await fetch( | ||||
|       `${url}?godfathers_depth=${godfathers_depth}&godchildren_depth=${godchildren_depth}`, | ||||
|       `${url}?godfathers_depth=${godfathers_depth}&godchildren_depth=${godchildren_depth}` | ||||
|     ) | ||||
|   ).json(); | ||||
|   ).json() | ||||
|   return [ | ||||
|     ...data.users.map((user) => { | ||||
|       return { data: user }; | ||||
|       return { data: user } | ||||
|     }), | ||||
|     ...data.relationships.map((rel) => { | ||||
|       return { | ||||
|         data: { source: rel.godfather, target: rel.godchild }, | ||||
|       }; | ||||
|     }), | ||||
|   ]; | ||||
|         data: { source: rel.godfather, target: rel.godchild } | ||||
|       } | ||||
|     }) | ||||
|   ] | ||||
| } | ||||
|  | ||||
| function create_graph (container, data, active_user_id) { | ||||
|   let cy = cytoscape({ | ||||
|   const cy = cytoscape({ | ||||
|     boxSelectionEnabled: false, | ||||
|     autounselectify: true, | ||||
|  | ||||
|     container: container, | ||||
|     container, | ||||
|     elements: data, | ||||
|     minZoom: 0.5, | ||||
|  | ||||
|     style: [ | ||||
|       // the stylesheet for the graph | ||||
|       { | ||||
|         selector: "node", | ||||
|         selector: 'node', | ||||
|         style: { | ||||
|           label: "data(display_name)", | ||||
|           "background-image": "data(profile_pict)", | ||||
|           width: "100%", | ||||
|           height: "100%", | ||||
|           "background-fit": "cover", | ||||
|           "background-repeat": "no-repeat", | ||||
|           shape: "ellipse", | ||||
|         }, | ||||
|           label: 'data(display_name)', | ||||
|           'background-image': 'data(profile_pict)', | ||||
|           width: '100%', | ||||
|           height: '100%', | ||||
|           'background-fit': 'cover', | ||||
|           'background-repeat': 'no-repeat', | ||||
|           shape: 'ellipse' | ||||
|         } | ||||
|       }, | ||||
|  | ||||
|       { | ||||
|         selector: "edge", | ||||
|         selector: 'edge', | ||||
|         style: { | ||||
|           width: 5, | ||||
|           "line-color": "#ccc", | ||||
|           "target-arrow-color": "#ccc", | ||||
|           "target-arrow-shape": "triangle", | ||||
|           "curve-style": "bezier", | ||||
|         }, | ||||
|           'line-color': '#ccc', | ||||
|           'target-arrow-color': '#ccc', | ||||
|           'target-arrow-shape': 'triangle', | ||||
|           'curve-style': 'bezier' | ||||
|         } | ||||
|       }, | ||||
|  | ||||
|       { | ||||
|         selector: ".traversed", | ||||
|         selector: '.traversed', | ||||
|         style: { | ||||
|           "border-width": "5px", | ||||
|           "border-style": "solid", | ||||
|           "border-color": "red", | ||||
|           "target-arrow-color": "red", | ||||
|           "line-color": "red", | ||||
|         }, | ||||
|           'border-width': '5px', | ||||
|           'border-style': 'solid', | ||||
|           'border-color': 'red', | ||||
|           'target-arrow-color': 'red', | ||||
|           'line-color': 'red' | ||||
|         } | ||||
|       }, | ||||
|  | ||||
|       { | ||||
|         selector: ".not-traversed", | ||||
|         selector: '.not-traversed', | ||||
|         style: { | ||||
|           "line-opacity": "0.5", | ||||
|           "background-opacity": "0.5", | ||||
|           "background-image-opacity": "0.5", | ||||
|         }, | ||||
|       }, | ||||
|           'line-opacity': '0.5', | ||||
|           'background-opacity': '0.5', | ||||
|           'background-image-opacity': '0.5' | ||||
|         } | ||||
|       } | ||||
|     ], | ||||
|     layout: { | ||||
|       name: "klay", | ||||
|       name: 'klay', | ||||
|       nodeDimensionsIncludeLabels: true, | ||||
|       fit: true, | ||||
|       klay: { | ||||
|         addUnnecessaryBendpoints: true, | ||||
|         direction: "DOWN", | ||||
|         nodePlacement: "INTERACTIVE", | ||||
|         layoutHierarchy: true, | ||||
|       }, | ||||
|     }, | ||||
|   }); | ||||
|   let active_user = cy | ||||
|         direction: 'DOWN', | ||||
|         nodePlacement: 'INTERACTIVE', | ||||
|         layoutHierarchy: true | ||||
|       } | ||||
|     } | ||||
|   }) | ||||
|   const active_user = cy | ||||
|     .getElementById(active_user_id) | ||||
|     .style("shape", "rectangle"); | ||||
|     .style('shape', 'rectangle') | ||||
|   /* Reset graph */ | ||||
|   let reset_graph = () => { | ||||
|   const reset_graph = () => { | ||||
|     cy.elements((element) => { | ||||
|       if (element.hasClass("traversed")) { | ||||
|         element.removeClass("traversed"); | ||||
|       if (element.hasClass('traversed')) { | ||||
|         element.removeClass('traversed') | ||||
|       } | ||||
|       if (element.hasClass("not-traversed")) { | ||||
|         element.removeClass("not-traversed"); | ||||
|       if (element.hasClass('not-traversed')) { | ||||
|         element.removeClass('not-traversed') | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   let on_node_tap = (el) => { | ||||
|     reset_graph(); | ||||
|   const on_node_tap = (el) => { | ||||
|     reset_graph() | ||||
|     /* Create path on graph if selected isn't the targeted user */ | ||||
|     if (el === active_user) { | ||||
|       return; | ||||
|       return | ||||
|     } | ||||
|     cy.elements((element) => { | ||||
|       element.addClass("not-traversed"); | ||||
|     }); | ||||
|       element.addClass('not-traversed') | ||||
|     }) | ||||
|  | ||||
|     cy.elements() | ||||
|       .aStar({ | ||||
|         root: el, | ||||
|         goal: active_user, | ||||
|         goal: active_user | ||||
|       }) | ||||
|       .path.forEach((el) => { | ||||
|         el.removeClass("not-traversed"); | ||||
|         el.addClass("traversed"); | ||||
|       }); | ||||
|   }; | ||||
|         el.removeClass('not-traversed') | ||||
|         el.addClass('traversed') | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   cy.on("tap", "node", (tapped) => { | ||||
|     on_node_tap(tapped.target); | ||||
|   }); | ||||
|   cy.zoomingEnabled(false); | ||||
|   cy.on('tap', 'node', (tapped) => { | ||||
|     on_node_tap(tapped.target) | ||||
|   }) | ||||
|   cy.zoomingEnabled(false) | ||||
|  | ||||
|   /* Add context menu */ | ||||
|   if (cy.cxtmenu === undefined) { | ||||
|     console.error( | ||||
|       "ctxmenu isn't loaded, context menu won't be available on graphs", | ||||
|     ); | ||||
|     return cy; | ||||
|       "ctxmenu isn't loaded, context menu won't be available on graphs" | ||||
|     ) | ||||
|     return cy | ||||
|   } | ||||
|   cy.cxtmenu({ | ||||
|     selector: "node", | ||||
|     selector: 'node', | ||||
|  | ||||
|     commands: [ | ||||
|       { | ||||
|         content: '<i class="fa fa-external-link fa-2x"></i>', | ||||
|         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>', | ||||
|         select: function (el) { | ||||
|           on_node_tap(el); | ||||
|         }, | ||||
|           on_node_tap(el) | ||||
|         } | ||||
|       }, | ||||
|  | ||||
|       { | ||||
|         content: '<i class="fa fa-eraser fa-2x"></i>', | ||||
|         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 | ||||
|  | ||||
| @@ -170,104 +174,104 @@ document.addEventListener("alpine:init", () => { | ||||
|     depth_min:   minimum 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 ( | ||||
|     typeof api_url === "undefined" || | ||||
|     typeof active_user === "undefined" || | ||||
|     typeof depth_min === "undefined" || | ||||
|     typeof depth_max === "undefined" | ||||
|     typeof api_url === 'undefined' || | ||||
|     typeof active_user === 'undefined' || | ||||
|     typeof depth_min === 'undefined' || | ||||
|     typeof depth_max === 'undefined' | ||||
|   ) { | ||||
|     console.error( | ||||
|       "Some constants are not set before using the family_graph script, please look at the documentation", | ||||
|     ); | ||||
|     return; | ||||
|       'Some constants are not set before using the family_graph script, please look at the documentation' | ||||
|     ) | ||||
|     return | ||||
|   } | ||||
|  | ||||
|   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) { | ||||
|       return default_depth; | ||||
|       return default_depth | ||||
|     } | ||||
|     return value; | ||||
|     return value | ||||
|   } | ||||
|  | ||||
|   Alpine.data("graph", () => ({ | ||||
|   Alpine.data('graph', () => ({ | ||||
|     loading: false, | ||||
|     godfathers_depth: get_initial_depth("godfathers_depth"), | ||||
|     godchildren_depth: get_initial_depth("godchildren_depth"), | ||||
|     reverse: initialUrlParams.get("reverse")?.toLowerCase?.() === "true", | ||||
|     godfathers_depth: get_initial_depth('godfathers_depth'), | ||||
|     godchildren_depth: get_initial_depth('godchildren_depth'), | ||||
|     reverse: initialUrlParams.get('reverse')?.toLowerCase?.() === 'true', | ||||
|     graph: undefined, | ||||
|     graph_data: {}, | ||||
|  | ||||
|     async init () { | ||||
|       let delayed_fetch = Alpine.debounce(async () => { | ||||
|         this.fetch_graph_data(); | ||||
|       const delayed_fetch = Alpine.debounce(async () => { | ||||
|         this.fetch_graph_data() | ||||
|       }, 100); | ||||
|       ["godfathers_depth", "godchildren_depth"].forEach((param) => { | ||||
|       ['godfathers_depth', 'godchildren_depth'].forEach((param) => { | ||||
|         this.$watch(param, async (value) => { | ||||
|           if (value < depth_min || value > depth_max) { | ||||
|             return; | ||||
|             return | ||||
|           } | ||||
|           update_query_string(param, value, History.REPLACE); | ||||
|           delayed_fetch(); | ||||
|         }); | ||||
|       }); | ||||
|       this.$watch("reverse", async (value) => { | ||||
|         update_query_string("reverse", value, History.REPLACE); | ||||
|         this.reverse_graph(); | ||||
|       }); | ||||
|       this.$watch("graph_data", async () => { | ||||
|         await this.generate_graph(); | ||||
|           update_query_string(param, value, History.REPLACE) | ||||
|           delayed_fetch() | ||||
|         }) | ||||
|       }) | ||||
|       this.$watch('reverse', async (value) => { | ||||
|         update_query_string('reverse', value, History.REPLACE) | ||||
|         this.reverse_graph() | ||||
|       }) | ||||
|       this.$watch('graph_data', async () => { | ||||
|         await this.generate_graph() | ||||
|         if (this.reverse) { | ||||
|           await this.reverse_graph(); | ||||
|           await this.reverse_graph() | ||||
|         } | ||||
|       }); | ||||
|       this.fetch_graph_data(); | ||||
|       }) | ||||
|       this.fetch_graph_data() | ||||
|     }, | ||||
|  | ||||
|     async screenshot () { | ||||
|       const link = document.createElement("a"); | ||||
|       link.href = this.graph.jpg(); | ||||
|       const link = document.createElement('a') | ||||
|       link.href = this.graph.jpg() | ||||
|       link.download = interpolate( | ||||
|         gettext("family_tree.%(extension)s"), | ||||
|         { extension: "jpg" }, | ||||
|         true, | ||||
|       ); | ||||
|       document.body.appendChild(link); | ||||
|       link.click(); | ||||
|       document.body.removeChild(link); | ||||
|         gettext('family_tree.%(extension)s'), | ||||
|         { extension: 'jpg' }, | ||||
|         true | ||||
|       ) | ||||
|       document.body.appendChild(link) | ||||
|       link.click() | ||||
|       document.body.removeChild(link) | ||||
|     }, | ||||
|  | ||||
|     async reset () { | ||||
|       this.reverse = false; | ||||
|       this.godfathers_depth = default_depth; | ||||
|       this.godchildren_depth = default_depth; | ||||
|       this.reverse = false | ||||
|       this.godfathers_depth = default_depth | ||||
|       this.godchildren_depth = default_depth | ||||
|     }, | ||||
|  | ||||
|     async reverse_graph () { | ||||
|       this.graph.elements((el) => { | ||||
|         el.position(new Object({ x: -el.position().x, y: -el.position().y })); | ||||
|       }); | ||||
|       this.graph.center(this.graph.elements()); | ||||
|         el.position({ x: -el.position().x, y: -el.position().y }) | ||||
|       }) | ||||
|       this.graph.center(this.graph.elements()) | ||||
|     }, | ||||
|  | ||||
|     async fetch_graph_data () { | ||||
|       this.graph_data = await get_graph_data( | ||||
|         api_url, | ||||
|         this.godfathers_depth, | ||||
|         this.godchildren_depth, | ||||
|       ); | ||||
|         this.godchildren_depth | ||||
|       ) | ||||
|     }, | ||||
|  | ||||
|     async generate_graph () { | ||||
|       this.loading = true; | ||||
|       this.loading = true | ||||
|       this.graph = create_graph( | ||||
|         $(this.$refs.graph), | ||||
|         this.graph_data, | ||||
|         active_user, | ||||
|       ); | ||||
|       this.loading = false; | ||||
|     }, | ||||
|   })); | ||||
| }); | ||||
|         active_user | ||||
|       ) | ||||
|       this.loading = false | ||||
|     } | ||||
|   })) | ||||
| }) | ||||
|   | ||||
| @@ -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, | ||||
|   delete_url, | ||||
|   can_delete_picture, | ||||
|   can_delete_picture | ||||
| ) { | ||||
|   return () => ({ | ||||
|     can_edit_picture: false, | ||||
| @@ -14,97 +16,98 @@ function alpine_webcam_builder( | ||||
|     picture_form: null, | ||||
|  | ||||
|     init () { | ||||
|       this.video = this.$refs.video; | ||||
|       this.picture_form = this.$refs.form.getElementsByTagName("input"); | ||||
|       this.video = this.$refs.video | ||||
|       this.picture_form = this.$refs.form.getElementsByTagName('input') | ||||
|       if (this.picture_form.length > 0) { | ||||
|         this.picture_form = this.picture_form[0]; | ||||
|         this.can_edit_picture = true; | ||||
|         this.picture_form = this.picture_form[0] | ||||
|         this.can_edit_picture = true | ||||
|  | ||||
|         // Link the displayed element to the form input | ||||
|         this.picture_form.onchange = (event) => { | ||||
|           let files = event.srcElement.files; | ||||
|           const files = event.srcElement.files | ||||
|           if (files.length > 0) { | ||||
|             this.picture = (window.URL || window.webkitURL).createObjectURL( | ||||
|               event.srcElement.files[0], | ||||
|             ); | ||||
|               event.srcElement.files[0] | ||||
|             ) | ||||
|           } else { | ||||
|             this.picture = null; | ||||
|             this.picture = null | ||||
|           } | ||||
|         } | ||||
|         }; | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     get_picture () { | ||||
|       return this.picture || default_picture; | ||||
|       return this.picture || default_picture | ||||
|     }, | ||||
|  | ||||
|     delete_picture () { | ||||
|       // Only remove currently displayed picture | ||||
|       if (!!this.picture) { | ||||
|         let list = new DataTransfer(); | ||||
|         this.picture_form.files = list.files; | ||||
|         this.picture_form.dispatchEvent(new Event("change")); | ||||
|         return; | ||||
|       if (this.picture) { | ||||
|         const list = new DataTransfer() | ||||
|         this.picture_form.files = list.files | ||||
|         this.picture_form.dispatchEvent(new Event('change')) | ||||
|         return | ||||
|       } | ||||
|       if (!can_delete_picture) { | ||||
|         return; | ||||
|         return | ||||
|       } | ||||
|       // Remove user picture if correct rights are available | ||||
|       window.open(delete_url, "_self"); | ||||
|       window.open(delete_url, '_self') | ||||
|     }, | ||||
|  | ||||
|     enable_camera () { | ||||
|       this.picture = null; | ||||
|       this.loading = true; | ||||
|       this.is_camera_error = false; | ||||
|       this.picture = null | ||||
|       this.loading = true | ||||
|       this.is_camera_error = false | ||||
|       navigator.mediaDevices | ||||
|         .getUserMedia({ video: true, audio: false }) | ||||
|         .then((stream) => { | ||||
|           this.loading = false; | ||||
|           this.is_camera_enabled = true; | ||||
|           this.video.srcObject = stream; | ||||
|           this.video.play(); | ||||
|           this.loading = false | ||||
|           this.is_camera_enabled = true | ||||
|           this.video.srcObject = stream | ||||
|           this.video.play() | ||||
|         }) | ||||
|         .catch((err) => { | ||||
|           this.is_camera_error = true; | ||||
|           this.loading = false; | ||||
|         }); | ||||
|           this.is_camera_error = true | ||||
|           this.loading = false | ||||
|           throw (err) | ||||
|         }) | ||||
|     }, | ||||
|  | ||||
|     take_picture () { | ||||
|       let canvas = document.createElement("canvas"); | ||||
|       const context = canvas.getContext("2d"); | ||||
|       const canvas = document.createElement('canvas') | ||||
|       const context = canvas.getContext('2d') | ||||
|  | ||||
|       /* Create the image */ | ||||
|       let settings = this.video.srcObject.getTracks()[0].getSettings(); | ||||
|       canvas.width = settings.width; | ||||
|       canvas.height = settings.height; | ||||
|       context.drawImage(this.video, 0, 0, canvas.width, canvas.height); | ||||
|       const settings = this.video.srcObject.getTracks()[0].getSettings() | ||||
|       canvas.width = settings.width | ||||
|       canvas.height = settings.height | ||||
|       context.drawImage(this.video, 0, 0, canvas.width, canvas.height) | ||||
|  | ||||
|       /* Stop camera */ | ||||
|       this.video.pause(); | ||||
|       this.video.pause() | ||||
|       this.video.srcObject.getTracks().forEach((track) => { | ||||
|         if (track.readyState === "live") { | ||||
|           track.stop(); | ||||
|         if (track.readyState === 'live') { | ||||
|           track.stop() | ||||
|         } | ||||
|       }); | ||||
|       }) | ||||
|  | ||||
|       canvas.toBlob((blob) => { | ||||
|         const filename = interpolate(gettext("captured.%s"), ["webp"]); | ||||
|         let file = new File([blob], filename, { | ||||
|           type: "image/webp", | ||||
|         }); | ||||
|         const filename = interpolate(gettext('captured.%s'), ['webp']) | ||||
|         const file = new File([blob], filename, { | ||||
|           type: 'image/webp' | ||||
|         }) | ||||
|  | ||||
|         let list = new DataTransfer(); | ||||
|         list.items.add(file); | ||||
|         this.picture_form.files = list.files; | ||||
|         const list = new DataTransfer() | ||||
|         list.items.add(file) | ||||
|         this.picture_form.files = list.files | ||||
|  | ||||
|         // No change event is triggered, we trigger it manually #} | ||||
|         this.picture_form.dispatchEvent(new Event("change")); | ||||
|       }, "image/webp"); | ||||
|         this.picture_form.dispatchEvent(new Event('change')) | ||||
|       }, 'image/webp') | ||||
|  | ||||
|       canvas.remove(); | ||||
|       this.is_camera_enabled = false; | ||||
|     }, | ||||
|   }); | ||||
|       canvas.remove() | ||||
|       this.is_camera_enabled = false | ||||
|     } | ||||
|   }) | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import Alpine from "alpinejs"; | ||||
| import Alpine from 'alpinejs' | ||||
|  | ||||
| window.Alpine = Alpine; | ||||
| window.Alpine = Alpine | ||||
|  | ||||
| addEventListener("DOMContentLoaded", (event) => { | ||||
|   Alpine.start(); | ||||
| }); | ||||
| window.addEventListener('DOMContentLoaded', (event) => { | ||||
|   Alpine.start() | ||||
| }) | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import "codemirror/lib/codemirror.css"; | ||||
| import "easymde/src/css/easymde.css"; | ||||
| import EasyMDE from "easymde"; | ||||
| import 'codemirror/lib/codemirror.css' | ||||
| import 'easymde/src/css/easymde.css' | ||||
| import EasyMDE from 'easymde' | ||||
|  | ||||
| // This scripts dependens on Alpine but it should be loaded on every page | ||||
|  | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| require("@fortawesome/fontawesome-free/css/all.css"); | ||||
| require('@fortawesome/fontawesome-free/css/all.css') | ||||
|   | ||||
							
								
								
									
										20
									
								
								core/static/webpack/jquery-index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								core/static/webpack/jquery-index.js
									
									
									
									
										vendored
									
									
								
							| @@ -1,17 +1,17 @@ | ||||
| import $ from "jquery"; | ||||
| import "jquery.shorten/src/jquery.shorten.min.js"; | ||||
| import $ from 'jquery' | ||||
| import 'jquery.shorten/src/jquery.shorten.min.js' | ||||
|  | ||||
| // 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 | ||||
| // We require jquery-ui components manually and not in a loop | ||||
| // Otherwise it increases the output files by a x2 factor ! | ||||
| require("jquery-ui/ui/widgets/accordion.js"); | ||||
| require("jquery-ui/ui/widgets/autocomplete.js"); | ||||
| require("jquery-ui/ui/widgets/button.js"); | ||||
| require("jquery-ui/ui/widgets/dialog.js"); | ||||
| require("jquery-ui/ui/widgets/tabs.js"); | ||||
| require('jquery-ui/ui/widgets/accordion.js') | ||||
| require('jquery-ui/ui/widgets/autocomplete.js') | ||||
| require('jquery-ui/ui/widgets/button.js') | ||||
| require('jquery-ui/ui/widgets/dialog.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 | ||||
| @@ -19,7 +19,7 @@ require("jquery-ui/themes/base/all.css"); | ||||
|  * @param {Object} options object to pass to the shorten function | ||||
|  **/ | ||||
| export function shorten (selector, options) { | ||||
|   $(selector).shorten(options); | ||||
|   $(selector).shorten(options) | ||||
| } | ||||
|  | ||||
| window.shorten = shorten; | ||||
| window.shorten = shorten | ||||
|   | ||||
| @@ -1,77 +1,79 @@ | ||||
| /* eslint-disable camelcase */ | ||||
| /* global basket, click_api_url, csrf_token, products_autocomplete */ | ||||
| document.addEventListener('alpine:init', () => { | ||||
|   Alpine.data('counter', () => ({ | ||||
|         basket: basket, | ||||
|     basket, | ||||
|     errors: [], | ||||
|  | ||||
|     sum_basket () { | ||||
|       if (!this.basket || Object.keys(this.basket).length === 0) { | ||||
|                 return 0; | ||||
|         return 0 | ||||
|       } | ||||
|       const total = Object.values(this.basket) | ||||
|                 .reduce((acc, cur) => acc + cur["qty"] * cur["price"], 0); | ||||
|             return total / 100; | ||||
|         .reduce((acc, cur) => acc + cur.qty * cur.price, 0) | ||||
|       return total / 100 | ||||
|     }, | ||||
|  | ||||
|     async handle_code (event) { | ||||
|             const code = $(event.target).find("#code_field").val().toUpperCase(); | ||||
|             if(["FIN", "ANN"].includes(code)) { | ||||
|                 $(event.target).submit(); | ||||
|       const code = $(event.target).find('#code_field').val().toUpperCase() | ||||
|       if (['FIN', 'ANN'].includes(code)) { | ||||
|         $(event.target).submit() | ||||
|       } else { | ||||
|                 await this.handle_action(event); | ||||
|         await this.handle_action(event) | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     async handle_action (event) { | ||||
|             const payload = $(event.target).serialize(); | ||||
|             let request = new Request(click_api_url, { | ||||
|                 method: "POST", | ||||
|       const payload = $(event.target).serialize() | ||||
|       const request = new Request(click_api_url, { | ||||
|         method: 'POST', | ||||
|         body: payload, | ||||
|         headers: { | ||||
|                     'Accept': 'application/json', | ||||
|                     'X-CSRFToken': csrf_token, | ||||
|           Accept: 'application/json', | ||||
|           'X-CSRFToken': csrf_token | ||||
|         } | ||||
|       }) | ||||
|             const response = await fetch(request); | ||||
|             const json = await response.json(); | ||||
|             this.basket = json["basket"] | ||||
|             this.errors = json["errors"] | ||||
|             $('form.code_form #code_field').val("").focus(); | ||||
|       const response = await fetch(request) | ||||
|       const json = await response.json() | ||||
|       this.basket = json.basket | ||||
|       this.errors = json.errors | ||||
|       $('form.code_form #code_field').val('').focus() | ||||
|     } | ||||
|   })) | ||||
| }) | ||||
|  | ||||
| $(function () { | ||||
|   /* Autocompletion in the code field */ | ||||
|     const code_field = $("#code_field"); | ||||
|   const code_field = $('#code_field') | ||||
|  | ||||
|     let quantity = ""; | ||||
|   let quantity = '' | ||||
|   code_field.autocomplete({ | ||||
|     select: function (event, ui) { | ||||
|             event.preventDefault(); | ||||
|             code_field.val(quantity + ui.item.value); | ||||
|       event.preventDefault() | ||||
|       code_field.val(quantity + ui.item.value) | ||||
|     }, | ||||
|     focus: function (event, ui) { | ||||
|             event.preventDefault(); | ||||
|             code_field.val(quantity + ui.item.value); | ||||
|       event.preventDefault() | ||||
|       code_field.val(quantity + ui.item.value) | ||||
|     }, | ||||
|     source: function (request, response) { | ||||
|             const res = /^(\d+x)?(.*)/i.exec(request.term); | ||||
|             quantity = res[1] || ""; | ||||
|             const search = res[2]; | ||||
|             const matcher = new RegExp($.ui.autocomplete.escapeRegex(search), "i" ); | ||||
|       const res = /^(\d+x)?(.*)/i.exec(request.term) | ||||
|       quantity = res[1] || '' | ||||
|       const search = res[2] | ||||
|       const matcher = new RegExp($.ui.autocomplete.escapeRegex(search), 'i') | ||||
|       response($.grep(products_autocomplete, function (value) { | ||||
|                 value = value.tags; | ||||
|                 return matcher.test( value ); | ||||
|             })); | ||||
|         }, | ||||
|     }); | ||||
|         value = value.tags | ||||
|         return matcher.test(value) | ||||
|       })) | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|   /* Accordion UI between basket and refills */ | ||||
|     $("#click_form").accordion({ | ||||
|         heightStyle: "content", | ||||
|         activate: () => $(".focus").focus(), | ||||
|     }); | ||||
|     $("#products").tabs(); | ||||
|   $('#click_form').accordion({ | ||||
|     heightStyle: 'content', | ||||
|     activate: () => $('.focus').focus() | ||||
|   }) | ||||
|   $('#products').tabs() | ||||
|  | ||||
|     code_field.focus(); | ||||
| }); | ||||
|   code_field.focus() | ||||
| }) | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| /* eslint-disable camelcase */ | ||||
| /** | ||||
|  * @typedef {Object} BasketItem An item in the basket | ||||
|  * @property {number} id The id of the product | ||||
| @@ -6,7 +7,7 @@ | ||||
|  * @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 | ||||
| @@ -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 | ||||
|  */ | ||||
| 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(';') | ||||
|     .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 | ||||
|  */ | ||||
| function get_starting_items () { | ||||
|     const cookie = getCookie(BASKET_ITEMS_COOKIE_NAME); | ||||
|   const cookie = getCookie(BASKET_ITEMS_COOKIE_NAME) | ||||
|   if (!cookie) { | ||||
|     return [] | ||||
|   } | ||||
|   // Django cookie backend converts `,` to `\054` | ||||
|     let parsed = JSON.parse(cookie.replace(/\\054/g, ',')); | ||||
|     if (typeof parsed === "string") { | ||||
|   let parsed = JSON.parse(cookie.replace(/\\054/g, ',')) | ||||
|   if (typeof parsed === 'string') { | ||||
|     // 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)) | ||||
| } | ||||
|  | ||||
| @@ -53,7 +54,7 @@ document.addEventListener('alpine:init', () => { | ||||
|          */ | ||||
|     get_total () { | ||||
|       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 | ||||
|          */ | ||||
|     add (item) { | ||||
|             item.quantity++; | ||||
|             this.set_cookies(); | ||||
|       item.quantity++ | ||||
|       this.set_cookies() | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -70,23 +71,23 @@ document.addEventListener('alpine:init', () => { | ||||
|          * @param {BasketItem} 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; | ||||
|             this.items[index].quantity -= 1; | ||||
|       if (index < 0) return | ||||
|       this.items[index].quantity -= 1 | ||||
|  | ||||
|       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 | ||||
|          */ | ||||
|     clear_basket () { | ||||
|             this.items = []; | ||||
|             this.set_cookies(); | ||||
|       this.items = [] | ||||
|       this.set_cookies() | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -95,9 +96,9 @@ document.addEventListener('alpine:init', () => { | ||||
|          */ | ||||
|     set_cookies () { | ||||
|       if (this.items.length === 0) { | ||||
|                 document.cookie = `${BASKET_ITEMS_COOKIE_NAME}=;Max-Age=0`; | ||||
|         document.cookie = `${BASKET_ITEMS_COOKIE_NAME}=;Max-Age=0` | ||||
|       } 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 | ||||
|          */ | ||||
|     create_item (id, name, price) { | ||||
|             let new_item = { | ||||
|                 id: id, | ||||
|                 name: name, | ||||
|       const new_item = { | ||||
|         id, | ||||
|         name, | ||||
|         quantity: 0, | ||||
|         unit_price: price | ||||
|             }; | ||||
|       } | ||||
|  | ||||
|             this.items.push(new_item); | ||||
|             this.add(new_item); | ||||
|       this.items.push(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 | ||||
|       // else we add + 1 to it | ||||
|       if (!item) { | ||||
|                 item = this.create_item(id, name, price); | ||||
|         item = this.create_item(id, name, price) | ||||
|       } else { | ||||
|                 this.add(item); | ||||
|         this.add(item) | ||||
|       } | ||||
|     } | ||||
|         }, | ||||
|   })) | ||||
| }) | ||||
| @@ -1,3 +1,6 @@ | ||||
| /* global et_data, et_data_url, billing_info_url, | ||||
|   billing_info_success_message, billing_info_failure_message */ | ||||
|  | ||||
| /** | ||||
|  * @readonly | ||||
|  * @enum {number} | ||||
| @@ -5,75 +8,75 @@ | ||||
| const BillingInfoReqState = { | ||||
|   SUCCESS: 1, | ||||
|   FAILURE: 2, | ||||
|     SENDING: 3, | ||||
| }; | ||||
|   SENDING: 3 | ||||
| } | ||||
|  | ||||
| document.addEventListener("alpine:init", () => { | ||||
|     Alpine.store("billing_inputs", { | ||||
| document.addEventListener('alpine:init', () => { | ||||
|   Alpine.store('billing_inputs', { | ||||
|     data: et_data, | ||||
|  | ||||
|     async fill () { | ||||
|             document.getElementById("bank-submit-button").disabled = true; | ||||
|             const res = await fetch(et_data_url); | ||||
|       document.getElementById('bank-submit-button').disabled = true | ||||
|       const res = await fetch(et_data_url) | ||||
|       if (res.ok) { | ||||
|                 this.data = await res.json(); | ||||
|                 document.getElementById("bank-submit-button").disabled = false; | ||||
|         this.data = await res.json() | ||||
|         document.getElementById('bank-submit-button').disabled = false | ||||
|       } | ||||
|         }, | ||||
|     }); | ||||
|     } | ||||
|   }) | ||||
|  | ||||
|     Alpine.data("billing_infos", () => ({ | ||||
|   Alpine.data('billing_infos', () => ({ | ||||
|     /** @type {BillingInfoReqState | null} */ | ||||
|     req_state: null, | ||||
|  | ||||
|     async send_form () { | ||||
|             this.req_state = BillingInfoReqState.SENDING; | ||||
|             const form = document.getElementById("billing_info_form"); | ||||
|             document.getElementById("bank-submit-button").disabled = true; | ||||
|             let payload = Object.fromEntries( | ||||
|                 Array.from(form.querySelectorAll("input, select")) | ||||
|                     .filter((elem) => elem.type !== "submit" && elem.value) | ||||
|                     .map((elem) => [elem.name, elem.value]), | ||||
|             ); | ||||
|       this.req_state = BillingInfoReqState.SENDING | ||||
|       const form = document.getElementById('billing_info_form') | ||||
|       document.getElementById('bank-submit-button').disabled = true | ||||
|       const payload = Object.fromEntries( | ||||
|         Array.from(form.querySelectorAll('input, select')) | ||||
|           .filter((elem) => elem.type !== 'submit' && elem.value) | ||||
|           .map((elem) => [elem.name, elem.value]) | ||||
|       ) | ||||
|       const res = await fetch(billing_info_url, { | ||||
|                 method: "PUT", | ||||
|                 body: JSON.stringify(payload), | ||||
|             }); | ||||
|         method: 'PUT', | ||||
|         body: JSON.stringify(payload) | ||||
|       }) | ||||
|       this.req_state = res.ok | ||||
|         ? BillingInfoReqState.SUCCESS | ||||
|                 : BillingInfoReqState.FAILURE; | ||||
|         : BillingInfoReqState.FAILURE | ||||
|       if (res.status === 422) { | ||||
|                 const errors = (await res.json())["detail"].map((err) => err["loc"]).flat(); | ||||
|                 Array.from(form.querySelectorAll("input")) | ||||
|         const errors = (await res.json()).detail.map((err) => err.loc).flat() | ||||
|         Array.from(form.querySelectorAll('input')) | ||||
|           .filter((elem) => errors.includes(elem.name)) | ||||
|           .forEach((elem) => { | ||||
|                         elem.setCustomValidity(gettext("Incorrect value")); | ||||
|                         elem.reportValidity(); | ||||
|                         elem.oninput = () => elem.setCustomValidity(""); | ||||
|                     }); | ||||
|             elem.setCustomValidity(gettext('Incorrect value')) | ||||
|             elem.reportValidity() | ||||
|             elem.oninput = () => elem.setCustomValidity('') | ||||
|           }) | ||||
|       } else if (res.ok) { | ||||
|                 Alpine.store("billing_inputs").fill(); | ||||
|         Alpine.store('billing_inputs').fill() | ||||
|       } | ||||
|     }, | ||||
|  | ||||
|     get_alert_color () { | ||||
|       if (this.req_state === BillingInfoReqState.SUCCESS) { | ||||
|                 return "green"; | ||||
|         return 'green' | ||||
|       } | ||||
|       if (this.req_state === BillingInfoReqState.FAILURE) { | ||||
|                 return "red"; | ||||
|         return 'red' | ||||
|       } | ||||
|             return ""; | ||||
|       return '' | ||||
|     }, | ||||
|  | ||||
|     get_alert_message () { | ||||
|       if (this.req_state === BillingInfoReqState.SUCCESS) { | ||||
|                 return billing_info_success_message; | ||||
|         return billing_info_success_message | ||||
|       } | ||||
|       if (this.req_state === BillingInfoReqState.FAILURE) { | ||||
|                 return billing_info_failure_message; | ||||
|         return billing_info_failure_message | ||||
|       } | ||||
|             return ""; | ||||
|         }, | ||||
|     })); | ||||
| }); | ||||
|       return '' | ||||
|     } | ||||
|   })) | ||||
| }) | ||||
|   | ||||
							
								
								
									
										3138
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3138
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										14
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								package.json
									
									
									
									
									
								
							| @@ -14,18 +14,26 @@ | ||||
|     "sideEffects": [ | ||||
|         ".css" | ||||
|     ], | ||||
|     "standard": { | ||||
|       "ignore": [ | ||||
|         "core/static/vendored", | ||||
|         "staticfiles/generated" | ||||
|       ], | ||||
|       "globals": [ "Alpine", "$", "jQuery", "gettext", "interpolate" ] | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@babel/core": "^7.25.2", | ||||
|         "@babel/preset-env": "^7.25.4", | ||||
|         "babel-loader": "^9.2.1", | ||||
|         "css-loader": "^7.1.2", | ||||
|         "css-minimizer-webpack-plugin": "^7.0.0", | ||||
|         "expose-loader": "^5.0.0", | ||||
|         "mini-css-extract-plugin": "^2.9.1", | ||||
|         "source-map-loader": "^5.0.0", | ||||
|         "standard": "^17.1.2", | ||||
|         "terser-webpack-plugin": "^5.3.10", | ||||
|         "webpack": "^5.94.0", | ||||
|         "webpack-cli": "^5.1.4", | ||||
|         "css-loader": "^7.1.2", | ||||
|         "css-minimizer-webpack-plugin": "^7.0.0" | ||||
|         "webpack-cli": "^5.1.4" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@fortawesome/fontawesome-free": "^6.6.0", | ||||
|   | ||||
| @@ -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 | ||||
|  * @property {number} id The actual id of the identification | ||||
| @@ -9,21 +13,22 @@ | ||||
|  * able to prefetch its data. | ||||
|  */ | ||||
| class PictureWithIdentifications { | ||||
|   identifications = null; | ||||
|   image_loading = false; | ||||
|   identifications_loading = false; | ||||
|   identifications = null | ||||
|   image_loading = false | ||||
|   identifications_loading = false | ||||
|  | ||||
|   /** | ||||
|    * @param {Picture} picture | ||||
|    */ | ||||
|   constructor (picture) { | ||||
|     Object.assign(this, picture); | ||||
|     Object.assign(this, picture) | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * @param {Picture} picture | ||||
|    */ | ||||
|   static from_picture (picture) { | ||||
|     return new PictureWithIdentifications(picture); | ||||
|     return new PictureWithIdentifications(picture) | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -34,17 +39,17 @@ class PictureWithIdentifications { | ||||
|    */ | ||||
|   async load_identifications (options) { | ||||
|     if (this.identifications_loading) { | ||||
|       return; // The users are already being fetched. | ||||
|       return // The users are already being fetched. | ||||
|     } | ||||
|     if (!!this.identifications && !options?.force_reload) { | ||||
|       // The users are already fetched | ||||
|       // and the user does not want to force the reload | ||||
|       return; | ||||
|       return | ||||
|     } | ||||
|     this.identifications_loading = true; | ||||
|     const url = `/api/sas/picture/${this.id}/identified`; | ||||
|     this.identifications = await (await fetch(url)).json(); | ||||
|     this.identifications_loading = false; | ||||
|     this.identifications_loading = true | ||||
|     const url = `/api/sas/picture/${this.id}/identified` | ||||
|     this.identifications = await (await fetch(url)).json() | ||||
|     this.identifications_loading = false | ||||
|   } | ||||
|  | ||||
|   /** | ||||
| @@ -52,20 +57,20 @@ class PictureWithIdentifications { | ||||
|    * @return {Promise<void>} | ||||
|    */ | ||||
|   async preload () { | ||||
|     const img = new Image(); | ||||
|     img.src = this.compressed_url; | ||||
|     const img = new Image() | ||||
|     img.src = this.compressed_url | ||||
|     if (!img.complete) { | ||||
|       this.image_loading = true; | ||||
|       img.addEventListener("load", () => { | ||||
|         this.image_loading = false; | ||||
|       }); | ||||
|       this.image_loading = true | ||||
|       img.addEventListener('load', () => { | ||||
|         this.image_loading = false | ||||
|       }) | ||||
|     } | ||||
|     await this.load_identifications(); | ||||
|     await this.load_identifications() | ||||
|   } | ||||
| } | ||||
|  | ||||
| document.addEventListener("alpine:init", () => { | ||||
|   Alpine.data("picture_viewer", () => ({ | ||||
| document.addEventListener('alpine:init', () => { | ||||
|   Alpine.data('picture_viewer', () => ({ | ||||
|     /** | ||||
|      * All the pictures that can be displayed on this picture viewer | ||||
|      * @type PictureWithIdentifications[] | ||||
| @@ -80,14 +85,14 @@ document.addEventListener("alpine:init", () => { | ||||
|     current_picture: { | ||||
|       is_moderated: true, | ||||
|       id: null, | ||||
|       name: "", | ||||
|       display_name: "", | ||||
|       compressed_url: "", | ||||
|       profile_url: "", | ||||
|       full_size_url: "", | ||||
|       owner: "", | ||||
|       name: '', | ||||
|       display_name: '', | ||||
|       compressed_url: '', | ||||
|       profile_url: '', | ||||
|       full_size_url: '', | ||||
|       owner: '', | ||||
|       date: new Date(), | ||||
|       identifications: [], | ||||
|       identifications: [] | ||||
|     }, | ||||
|     /** | ||||
|      * 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 | ||||
|      * @type string | ||||
|      **/ | ||||
|     moderation_error: "", | ||||
|     moderation_error: '', | ||||
|     /** | ||||
|      * Method of pushing new url to the browser history | ||||
|      * Used by popstate event and always reset to it's default value when used | ||||
| @@ -120,40 +125,40 @@ document.addEventListener("alpine:init", () => { | ||||
|  | ||||
|     async init () { | ||||
|       this.pictures = (await fetch_paginated(picture_endpoint)).map( | ||||
|         PictureWithIdentifications.from_picture, | ||||
|       ); | ||||
|         PictureWithIdentifications.from_picture | ||||
|       ) | ||||
|       this.selector = sithSelect2({ | ||||
|         element: $(this.$refs.search), | ||||
|         data_source: remote_data_source("/api/user/search", { | ||||
|         data_source: remote_data_source('/api/user/search', { | ||||
|           excluded: () => [ | ||||
|             ...(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( | ||||
|         (i) => i.id === first_picture_id, | ||||
|       ); | ||||
|       this.$watch("current_picture", (current, previous) => { | ||||
|         (i) => i.id === first_picture_id | ||||
|       ) | ||||
|       this.$watch('current_picture', (current, previous) => { | ||||
|         if (current === previous) { /* Avoid recursive updates */ | ||||
|           return; | ||||
|           return | ||||
|         } | ||||
|         this.update_picture(); | ||||
|       }); | ||||
|       window.addEventListener("popstate", async (event) => { | ||||
|         this.update_picture() | ||||
|       }) | ||||
|       window.addEventListener('popstate', async (event) => { | ||||
|         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( | ||||
|           (i) => i.id === parseInt(event.state.sas_picture_id), | ||||
|         ); | ||||
|       }); | ||||
|       this.pushstate = History.REPLACE; /* Avoid first url push */ | ||||
|       await this.update_picture(); | ||||
|           (i) => i.id === parseInt(event.state.sas_picture_id) | ||||
|         ) | ||||
|       }) | ||||
|       this.pushstate = History.REPLACE /* Avoid first url push */ | ||||
|       await this.update_picture() | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -167,74 +172,74 @@ document.addEventListener("alpine:init", () => { | ||||
|     async update_picture () { | ||||
|       const update_args = [ | ||||
|         { sas_picture_id: this.current_picture.id }, | ||||
|         "", | ||||
|         `/sas/picture/${this.current_picture.id}/`, | ||||
|       ]; | ||||
|         '', | ||||
|         `/sas/picture/${this.current_picture.id}/` | ||||
|       ] | ||||
|       if (this.pushstate === History.REPLACE) { | ||||
|         window.history.replaceState(...update_args); | ||||
|         this.pushstate = History.PUSH; | ||||
|         window.history.replaceState(...update_args) | ||||
|         this.pushstate = History.PUSH | ||||
|       } else { | ||||
|         window.history.pushState(...update_args); | ||||
|         window.history.pushState(...update_args) | ||||
|       } | ||||
|  | ||||
|       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; | ||||
|       await this.current_picture.load_identifications(); | ||||
|       this.$refs.main_picture?.addEventListener("load", () => { | ||||
|       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 | ||||
|       await this.current_picture.load_identifications() | ||||
|       this.$refs.main_picture?.addEventListener('load', () => { | ||||
|         // once the current picture is loaded, | ||||
|         // start preloading the next and previous pictures | ||||
|         this.next_picture?.preload(); | ||||
|         this.previous_picture?.preload(); | ||||
|       }); | ||||
|         this.next_picture?.preload() | ||||
|         this.previous_picture?.preload() | ||||
|       }) | ||||
|     }, | ||||
|  | ||||
|     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; | ||||
|           method: 'PATCH' | ||||
|         } | ||||
|       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 () { | ||||
|       const res = await fetch(`/api/sas/picture/${this.current_picture.id}`, { | ||||
|         method: "DELETE", | ||||
|       }); | ||||
|         method: 'DELETE' | ||||
|       }) | ||||
|       if (!res.ok) { | ||||
|         this.moderation_error = | ||||
|           gettext("Couldn't delete picture") + " : " + res.statusText; | ||||
|         return; | ||||
|           gettext("Couldn't delete picture") + ' : ' + res.statusText | ||||
|         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) { | ||||
|         // 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; | ||||
|         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. | ||||
|      */ | ||||
|     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, { | ||||
|         method: "PUT", | ||||
|         body: JSON.stringify(this.selector.val().map((i) => parseInt(i))), | ||||
|       }); | ||||
|         method: 'PUT', | ||||
|         body: JSON.stringify(this.selector.val().map((i) => parseInt(i))) | ||||
|       }) | ||||
|       // refresh the identified users list | ||||
|       await this.current_picture.load_identifications({ force_reload: true }); | ||||
|       this.selector.empty().trigger("change"); | ||||
|       await this.current_picture.load_identifications({ force_reload: true }) | ||||
|       this.selector.empty().trigger('change') | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
| @@ -243,7 +248,7 @@ document.addEventListener("alpine:init", () => { | ||||
|      * @return {boolean} | ||||
|      */ | ||||
|     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) { | ||||
|       const res = await fetch(`/api/sas/relation/${identification.id}`, { | ||||
|         method: "DELETE", | ||||
|       }); | ||||
|         method: 'DELETE' | ||||
|       }) | ||||
|       if (res.ok && Array.isArray(this.current_picture.identifications)) { | ||||
|         this.current_picture.identifications = | ||||
|           this.current_picture.identifications.filter( | ||||
|             (i) => i.id !== identification.id, | ||||
|           ); | ||||
|             (i) => i.id !== identification.id | ||||
|           ) | ||||
|       } | ||||
|     }, | ||||
|   })); | ||||
| }); | ||||
|     } | ||||
|   })) | ||||
| }) | ||||
|   | ||||
| @@ -1,14 +1,13 @@ | ||||
| const glob = require('glob'); | ||||
| const path = require('path'); | ||||
| const webpack = require("webpack"); | ||||
| const MiniCssExtractPlugin = require("mini-css-extract-plugin"); | ||||
| const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); | ||||
| const TerserPlugin = require("terser-webpack-plugin"); | ||||
| const glob = require('glob') | ||||
| const path = require('path') | ||||
| const MiniCssExtractPlugin = require('mini-css-extract-plugin') | ||||
| const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') | ||||
| const TerserPlugin = require('terser-webpack-plugin') | ||||
|  | ||||
| module.exports = { | ||||
|   entry: glob.sync('./!(static)/static/webpack/**?(-)index.js').reduce((obj, el) => { | ||||
|     obj[path.parse(el).name] = './' + el; | ||||
|     return obj; | ||||
|     obj[path.parse(el).name] = './' + el | ||||
|     return obj | ||||
|   }, {}), | ||||
|   output: { | ||||
|     filename: '[name].js', | ||||
| @@ -16,24 +15,24 @@ module.exports = { | ||||
|     clean: true | ||||
|   }, | ||||
|   plugins: [ | ||||
|     new MiniCssExtractPlugin(), | ||||
|     new MiniCssExtractPlugin() | ||||
|   ], | ||||
|   optimization: { | ||||
|     minimizer: [ | ||||
|       "...", | ||||
|       '...', | ||||
|       new CssMinimizerPlugin({ | ||||
|         parallel: true, | ||||
|         parallel: true | ||||
|       }), | ||||
|       new TerserPlugin({ | ||||
|         parallel: true, | ||||
|         terserOptions: { | ||||
|           mangle: true, | ||||
|           compress: { | ||||
|             drop_console: true, | ||||
|           }, | ||||
|             drop_console: true | ||||
|           } | ||||
|       }), | ||||
|     ], | ||||
|         } | ||||
|       }) | ||||
|     ] | ||||
|   }, | ||||
|   module: { | ||||
|     rules: [ | ||||
| @@ -42,18 +41,18 @@ module.exports = { | ||||
|         sideEffects: true, | ||||
|         use: [ | ||||
|           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' | ||||
|       }, | ||||
|       { | ||||
|         test: /\.m?js$/, | ||||
|         exclude: /node_modules/, | ||||
|         use: { | ||||
|           loader: "babel-loader", | ||||
|           loader: 'babel-loader', | ||||
|           options: { | ||||
|             presets: ['@babel/preset-env'] | ||||
|           } | ||||
| @@ -61,12 +60,12 @@ module.exports = { | ||||
|       }, | ||||
|       { | ||||
|         test: /\.js$/, | ||||
|         enforce: "pre", | ||||
|         use: ["source-map-loader"], | ||||
|         enforce: 'pre', | ||||
|         use: ['source-map-loader'] | ||||
|       }, | ||||
|       { | ||||
|         test: require.resolve("jquery"), | ||||
|         loader: "expose-loader", | ||||
|         test: require.resolve('jquery'), | ||||
|         loader: 'expose-loader', | ||||
|         options: { | ||||
|           exposes: [ | ||||
|             { | ||||
| @@ -81,9 +80,9 @@ module.exports = { | ||||
|               globalName: ['window.jQuery'], | ||||
|               override: true | ||||
|             } | ||||
|           ], | ||||
|         }, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
|           ] | ||||
|         } | ||||
|       } | ||||
|     ] | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user