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 %}
+