diff --git a/core/static/core/js/script.js b/core/static/core/js/script.js index adb15b06..71748ffb 100644 --- a/core/static/core/js/script.js +++ b/core/static/core/js/script.js @@ -74,52 +74,3 @@ function displayNotif() { function getCSRFToken() { return $("[name=csrfmiddlewaretoken]").val(); } - -// biome-ignore lint/correctness/noUnusedVariables: used in other scripts -const initialUrlParams = new URLSearchParams(window.location.search); - -/** - * @readonly - * @enum {number} - */ -const History = { - // biome-ignore lint/style/useNamingConvention: this feels more like an enum - NONE: 0, - // biome-ignore lint/style/useNamingConvention: this feels more like an enum - PUSH: 1, - // biome-ignore lint/style/useNamingConvention: this feels more like an enum - REPLACE: 2, -}; - -/** - * @param {string} key - * @param {string | string[] | null} value - * @param {History} action - * @param {URL | null} url - */ -// biome-ignore lint/correctness/noUnusedVariables: used in other scripts -function updateQueryString(key, value, action = History.REPLACE, url = null) { - let ret = url; - if (!ret) { - ret = new URL(window.location.href); - } - if (value === undefined || value === null || value === "") { - // If the value is null, undefined or empty => delete it - ret.searchParams.delete(key); - } else if (Array.isArray(value)) { - ret.searchParams.delete(key); - for (const v of value) { - ret.searchParams.append(key, v); - } - } else { - ret.searchParams.set(key, value); - } - - if (action === History.PUSH) { - window.history.pushState(null, "", ret.toString()); - } else if (action === History.REPLACE) { - window.history.replaceState(null, "", ret.toString()); - } - - return ret; -} diff --git a/core/static/webpack/user/family-graph-index.js b/core/static/webpack/user/family-graph-index.js index c6eb7278..706697b1 100644 --- a/core/static/webpack/user/family-graph-index.js +++ b/core/static/webpack/user/family-graph-index.js @@ -1,3 +1,4 @@ +import { History, initialUrlParams, updateQueryString } from "#core:utils/history"; import cytoscape from "cytoscape"; import cxtmenu from "cytoscape-cxtmenu"; import klay from "cytoscape-klay"; @@ -184,7 +185,6 @@ window.loadFamilyGraph = (config) => { const defaultDepth = 2; function getInitialDepth(prop) { - // biome-ignore lint/correctness/noUndeclaredVariables: defined by script.js const value = Number.parseInt(initialUrlParams.get(prop)); if (Number.isNaN(value) || value < config.depthMin || value > config.depthMax) { return defaultDepth; @@ -196,7 +196,6 @@ window.loadFamilyGraph = (config) => { loading: false, godfathersDepth: getInitialDepth("godfathersDepth"), godchildrenDepth: getInitialDepth("godchildrenDepth"), - // biome-ignore lint/correctness/noUndeclaredVariables: defined by script.js reverse: initialUrlParams.get("reverse")?.toLowerCase?.() === "true", graph: undefined, graphData: {}, @@ -210,14 +209,12 @@ window.loadFamilyGraph = (config) => { if (value < config.depthMin || value > config.depthMax) { return; } - // biome-ignore lint/correctness/noUndeclaredVariables: defined by script.js - updateQueryString(param, value, History.REPLACE); + updateQueryString(param, value, History.Replace); await delayedFetch(); }); } this.$watch("reverse", async (value) => { - // biome-ignore lint/correctness/noUndeclaredVariables: defined by script.js - updateQueryString("reverse", value, History.REPLACE); + updateQueryString("reverse", value, History.Replace); await this.reverseGraph(); }); this.$watch("graphData", async () => { diff --git a/core/static/webpack/utils/history.ts b/core/static/webpack/utils/history.ts new file mode 100644 index 00000000..690b2b88 --- /dev/null +++ b/core/static/webpack/utils/history.ts @@ -0,0 +1,40 @@ +export enum History { + None = 0, + Push = 1, + Replace = 2, +} + +export const initialUrlParams = new URLSearchParams(window.location.search); +export const getCurrentUrlParams = () => { + return new URLSearchParams(window.location.search); +}; + +export function updateQueryString( + key: string, + value?: string | string[], + action?: History, + url?: string, +) { + const historyAction = action ?? History.Replace; + const ret = new URL(url ?? window.location.href); + + if (value === undefined || value === null || value === "") { + // If the value is null, undefined or empty => delete it + ret.searchParams.delete(key); + } else if (Array.isArray(value)) { + ret.searchParams.delete(key); + for (const v of value) { + ret.searchParams.append(key, v); + } + } else { + ret.searchParams.set(key, value); + } + + if (historyAction === History.Push) { + window.history.pushState(null, "", ret.toString()); + } else if (historyAction === History.Replace) { + window.history.replaceState(null, "", ret.toString()); + } + + return ret; +} diff --git a/pedagogy/static/webpack/pedagogy/guide-index.js b/pedagogy/static/webpack/pedagogy/guide-index.js index 6740b935..31c04ede 100644 --- a/pedagogy/static/webpack/pedagogy/guide-index.js +++ b/pedagogy/static/webpack/pedagogy/guide-index.js @@ -1,3 +1,4 @@ +import { History, getCurrentUrlParams, updateQueryString } from "#core:utils/history"; import { uvFetchUvList } from "#openapi"; const pageDefault = 1; @@ -22,13 +23,13 @@ document.addEventListener("alpine:init", () => { semester: [], // biome-ignore lint/style/useNamingConvention: api is in snake_case to_change: [], - pushstate: History.PUSH, + pushstate: History.Push, update: undefined, initializeArgs() { - const url = new URLSearchParams(window.location.search); - this.pushstate = History.REPLACE; + const url = getCurrentUrlParams(); + this.pushstate = History.Replace; this.page = Number.parseInt(url.get("page")) || pageDefault; this.page_size = Number.parseInt(url.get("page_size")) || pageSizeDefault; @@ -47,17 +48,14 @@ document.addEventListener("alpine:init", () => { this.update = Alpine.debounce(async () => { /* Create the whole url before changing everything all at once */ const first = this.to_change.shift(); - // biome-ignore lint/correctness/noUndeclaredVariables: defined in script.js - let url = updateQueryString(first.param, first.value, History.NONE); + let url = updateQueryString(first.param, first.value, History.None); for (const value of this.to_change) { - // biome-ignore lint/correctness/noUndeclaredVariables: defined in script.js - url = updateQueryString(value.param, value.value, History.NONE, url); + url = updateQueryString(value.param, value.value, History.None, url); } - // biome-ignore lint/correctness/noUndeclaredVariables: defined in script.js updateQueryString(first.param, first.value, this.pushstate, url); await this.fetchData(); /* reload data on form change */ this.to_change = []; - this.pushstate = History.PUSH; + this.pushstate = History.Push; }, 50); const searchParams = ["search", "department", "credit_type", "semester"]; @@ -65,7 +63,7 @@ document.addEventListener("alpine:init", () => { for (const param of searchParams) { this.$watch(param, () => { - if (this.pushstate !== History.PUSH) { + if (this.pushstate !== History.Push) { /* This means that we are doing a mass param edit */ return; } diff --git a/sas/static/webpack/sas/album-index.js b/sas/static/webpack/sas/album-index.js new file mode 100644 index 00000000..f09fa6b2 --- /dev/null +++ b/sas/static/webpack/sas/album-index.js @@ -0,0 +1,59 @@ +import { History, initialUrlParams, updateQueryString } from "#core:utils/history"; +import { picturesFetchPictures } from "#openapi"; + +/** + * @typedef AlbumConfig + * @property {number} albumId id of the album to visualize + * @property {number} maxPageSize maximum number of elements to show on a page + **/ + +/** + * Create a family graph of an user + * @param {AlbumConfig} config + **/ +window.loadAlbum = (config) => { + document.addEventListener("alpine:init", () => { + Alpine.data("pictures", () => ({ + pictures: {}, + page: Number.parseInt(initialUrlParams.get("page")) || 1, + pushstate: History.Push /* Used to avoid pushing a state on a back action */, + loading: false, + + async init() { + await this.fetchPictures(); + this.$watch("page", () => { + updateQueryString("page", this.page === 1 ? null : this.page, this.pushstate); + this.pushstate = History.Push; + this.fetchPictures(); + }); + + window.addEventListener("popstate", () => { + this.pushstate = History.Replace; + this.page = + Number.parseInt(new URLSearchParams(window.location.search).get("page")) || + 1; + }); + }, + + async fetchPictures() { + this.loading = true; + this.pictures = ( + await picturesFetchPictures({ + query: { + // biome-ignore lint/style/useNamingConvention: API is in snake_case + album_id: config.albumId, + page: this.page, + // biome-ignore lint/style/useNamingConvention: API is in snake_case + page_size: config.maxPageSize, + }, + }) + ).data; + this.loading = false; + }, + + nbPages() { + return Math.ceil(this.pictures.count / config.maxPageSize); + }, + })); + }); +}; diff --git a/sas/templates/sas/album.jinja b/sas/templates/sas/album.jinja index 1958531a..15cd10f5 100644 --- a/sas/templates/sas/album.jinja +++ b/sas/templates/sas/album.jinja @@ -5,6 +5,10 @@ {%- endblock -%} +{%- block additional_js -%} + +{%- endblock -%} + {% block title %} {% trans %}SAS{% endtrans %} {% endblock %} @@ -108,48 +112,14 @@ {% block script %} {{ super() }} + {% endblock %} +