mirror of
https://github.com/ae-utbm/sith.git
synced 2026-05-13 20:48:06 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35d4465d47 | |||
| 35aca2b3b2 | |||
| 88ddae7042 | |||
| 2f9a4c3d4f |
@@ -1,12 +0,0 @@
|
||||
import sort from "@alpinejs/sort";
|
||||
import Alpine from "alpinejs";
|
||||
import { limitedChoices } from "#core:alpine/limited-choices.ts";
|
||||
import { alpinePlugin as notificationPlugin } from "#core:utils/notifications.ts";
|
||||
|
||||
Alpine.plugin([sort, limitedChoices]);
|
||||
Alpine.magic("notifications", notificationPlugin);
|
||||
window.Alpine = Alpine;
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
Alpine.start();
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import sort from "@alpinejs/sort";
|
||||
import Alpine from "alpinejs";
|
||||
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
|
||||
import htmx from "htmx.org";
|
||||
import { limitedChoices } from "#core:alpine/limited-choices";
|
||||
import { cacheBuster } from "#core:core/cache";
|
||||
import { default as navbar } from "#core:core/navbar";
|
||||
import { alpinePlugin as notificationPlugin } from "#core:utils/notifications";
|
||||
|
||||
/**
|
||||
* Alpine
|
||||
*/
|
||||
Alpine.plugin([sort, limitedChoices]);
|
||||
Alpine.magic("notifications", notificationPlugin);
|
||||
|
||||
// biome-ignore lint/style/useNamingConvention: it's how it's named
|
||||
Object.assign(window, { Alpine });
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
Alpine.start();
|
||||
});
|
||||
|
||||
/**
|
||||
* Polyfill for country flags (used for language choice)
|
||||
*/
|
||||
polyfillCountryFlagEmojis();
|
||||
|
||||
/**
|
||||
* HTMX
|
||||
*/
|
||||
document.body.addEventListener("htmx:beforeRequest", (event: CustomEvent) => {
|
||||
event.detail.target.ariaBusy = true;
|
||||
});
|
||||
|
||||
document.body.addEventListener("htmx:beforeSwap", (event: CustomEvent) => {
|
||||
event.detail.target.ariaBusy = null;
|
||||
});
|
||||
|
||||
Object.assign(window, { htmx });
|
||||
|
||||
/**
|
||||
* navbar
|
||||
*/
|
||||
navbar();
|
||||
|
||||
/**
|
||||
* Script that clears the cache when the cache version changes
|
||||
*/
|
||||
cacheBuster();
|
||||
@@ -0,0 +1,24 @@
|
||||
// increment this number when a breaking change is made with localStorage
|
||||
const CURRENT_CACHE_VERSION = 1;
|
||||
|
||||
export function cacheBuster() {
|
||||
const version = Number.parseInt(localStorage.getItem("version") ?? "0", 10);
|
||||
if (version === CURRENT_CACHE_VERSION) {
|
||||
// The cache schema is up-to-date. Nothing to do.
|
||||
return;
|
||||
}
|
||||
localStorage.removeItem("basket");
|
||||
localStorage.removeItem("basket1");
|
||||
// remove all storage items which key is in the form
|
||||
// `userXXXPictures` or `userXXXPicturesNumber`
|
||||
Object.keys(localStorage)
|
||||
.filter(
|
||||
(key) =>
|
||||
key.startsWith("user") &&
|
||||
(key.endsWith("Pictures") || key.endsWith("PicturesNumber")),
|
||||
)
|
||||
.forEach((key) => {
|
||||
localStorage.removeItem(key);
|
||||
});
|
||||
localStorage.setItem("version", CURRENT_CACHE_VERSION.toString());
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
import { exportToHtml } from "#core:utils/globals.ts";
|
||||
|
||||
exportToHtml("showMenu", () => {
|
||||
function showMenu() {
|
||||
const navbar = document.getElementById("navbar-content");
|
||||
const current = navbar.getAttribute("mobile-display");
|
||||
navbar.setAttribute("mobile-display", current === "hidden" ? "revealed" : "hidden");
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("alpine:init", () => {
|
||||
function navbarInit() {
|
||||
const menuItems = document.querySelectorAll(".navbar details[name='navbar'].menu");
|
||||
const isDesktop = () => {
|
||||
return window.innerWidth >= 500;
|
||||
@@ -33,4 +31,9 @@ document.addEventListener("alpine:init", () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default () => {
|
||||
Object.assign(document, { showMenu });
|
||||
document.addEventListener("alpine:init", navbarInit);
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
|
||||
|
||||
polyfillCountryFlagEmojis();
|
||||
@@ -1,11 +0,0 @@
|
||||
import htmx from "htmx.org";
|
||||
|
||||
document.body.addEventListener("htmx:beforeRequest", (event) => {
|
||||
event.detail.target.ariaBusy = true;
|
||||
});
|
||||
|
||||
document.body.addEventListener("htmx:beforeSwap", (event) => {
|
||||
event.detail.target.ariaBusy = null;
|
||||
});
|
||||
|
||||
Object.assign(window, { htmx });
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { NestedKeyOf } from "#core:utils/types.ts";
|
||||
import type { NestedKeyOf } from "#core:types/nested-key";
|
||||
|
||||
interface StringifyOptions<T extends object> {
|
||||
/** The columns to include in the resulting CSV. */
|
||||
|
||||
@@ -35,12 +35,9 @@
|
||||
<noscript><link rel="stylesheet" href="{{ static('bundled/fontawesome-index.css') }}"></noscript>
|
||||
|
||||
<script src="{{ url('javascript-catalog') }}"></script>
|
||||
<script type="module" src="{{ static("bundled/core/navbar-index.ts") }}"></script>
|
||||
<script type="module" src="{{ static("bundled/base-bundle-index.ts") }}"></script>
|
||||
<script type="module" src="{{ static("bundled/core/components/include-index.ts") }}"></script>
|
||||
<script type="module" src="{{ static('bundled/alpine-index.js') }}"></script>
|
||||
<script type="module" src="{{ static('bundled/htmx-index.js') }}"></script>
|
||||
<script type="module" src="{{ static('bundled/country-flags-index.ts') }}"></script>
|
||||
<script type="module" src="{{ static('bundled/core/tooltips-index.ts') }}"></script>
|
||||
<script type="module" src="{{ static("bundled/core/tooltips-index.ts") }}"></script>
|
||||
|
||||
{% block additional_css %}{% endblock %}
|
||||
{% block additional_js %}{% endblock %}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { showSaveFilePicker } from "native-file-system-adapter";
|
||||
import type TomSelect from "tom-select";
|
||||
import type { NestedKeyOf } from "#core:types/nested-key";
|
||||
import { paginated } from "#core:utils/api";
|
||||
import { csv } from "#core:utils/csv";
|
||||
import { getCurrentUrlParams, History, updateQueryString } from "#core:utils/history";
|
||||
import type { NestedKeyOf } from "#core:utils/types";
|
||||
import {
|
||||
type ProductSchema,
|
||||
type ProductSearchProductsDetailedData,
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
[Documentation du localStorage (mozilla)](https://developer.mozilla.org/fr/docs/Web/API/Window/localStorage)
|
||||
|
||||
## Utilité et limitations
|
||||
|
||||
Le `localStorage` est un cache géré directement par le navigateur.
|
||||
Il permet de stocker des données directement chez le client.
|
||||
Il s'agit donc d'un outil extrêmement puissant, qui permet d'éviter
|
||||
beaucoup de requêtes au serveur, améliorant ainsi les temps de chargement.
|
||||
|
||||
Cependant, il y a deux limitations majeures à prendre en compte :
|
||||
|
||||
- le `localStorage` est entièrement géré par le client,
|
||||
une fois le déploiement effectué, vous ne pouvez plus y toucher ;
|
||||
vous devez donc être sûr de vous avant d'apporter des modifications
|
||||
reposant sur le `localStorage`.
|
||||
- la quantité de données stockable est limitée à 10Mo ;
|
||||
une fois ce quota rempli, le navigateur lèvera une `QuotaExceededError`.
|
||||
|
||||
## Invalidation du `localStorage`
|
||||
|
||||
Pour résoudre le premier de ces deux problèmes, il y a un script permettant
|
||||
d'annuler une partie du cache.
|
||||
Ce dernier se trouve dans le fichier `core/static/bundled/core/cache.ts`.
|
||||
|
||||
Vous devrez modifier ce cache chaque fois que vous effectuerez
|
||||
un changement de schéma, c'est-à-dire dans un des cas suivants :
|
||||
|
||||
- une des clefs du cache n'est plus utilisée
|
||||
- la clef n'a pas changé, mais la manière dont les données attachées à cette clef
|
||||
sont formées a été modifiée.
|
||||
|
||||
!!!Note
|
||||
|
||||
Si vous ne faites qu'ajouter des données, sans modifier ni supprimer
|
||||
celles qui sont là, vous n'avez pas besoin d'invalider le cache.
|
||||
|
||||
Vous devez effectuer deux modifications dans ce fichier :
|
||||
|
||||
- incrémenter la version du cache
|
||||
- ajouter une ligne permettant de retirer votre clef du cache
|
||||
|
||||
```ts hl_lines="2 11"
|
||||
// increment this number when a breaking change is made with localStorage
|
||||
const CURRENT_CACHE_VERSION = 2; // <-- changez cette ligne
|
||||
|
||||
export function cacheBuster() {
|
||||
const version = Number.parseInt(localStorage.getItem("version") ?? "0", 10);
|
||||
if (version === CURRENT_CACHE_VERSION) {
|
||||
// The cache schema is up-to-date. Nothing to do.
|
||||
return;
|
||||
}
|
||||
// ...
|
||||
localStorage.removeItem("<clef>"); // <-- et rajoutez cette ligne
|
||||
localStorage.setItem("version", CURRENT_CACHE_VERSION.toString());
|
||||
}
|
||||
```
|
||||
@@ -7,8 +7,7 @@ interface BasketItem {
|
||||
unitPrice: number;
|
||||
}
|
||||
|
||||
// increment the key number if the data schema of the cached basket changes
|
||||
const BASKET_CACHE_KEY = "basket1";
|
||||
const BASKET_CACHE_KEY = "basket";
|
||||
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("basket", (lastPurchaseTime?: number) => ({
|
||||
|
||||
@@ -66,6 +66,8 @@ nav:
|
||||
- Gestion des permissions: tutorial/perms.md
|
||||
- Gestion des groupes: tutorial/groups.md
|
||||
- Les fragments: tutorial/fragments.md
|
||||
- Frontend:
|
||||
- localStorage: tutorial/front/localstorage.md
|
||||
- API:
|
||||
- Développement: tutorial/api/dev.md
|
||||
- Connexion à l'API: tutorial/api/connect.md
|
||||
|
||||
Generated
+11
@@ -44,6 +44,7 @@
|
||||
"@biomejs/biome": "^2.4.13",
|
||||
"@hey-api/openapi-ts": "^0.94.5",
|
||||
"@types/alpinejs": "^3.13.11",
|
||||
"@types/alpinejs__sort": "^3.13.0",
|
||||
"@types/cytoscape-cxtmenu": "^3.4.5",
|
||||
"@types/cytoscape-klay": "^3.1.5",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
@@ -2481,6 +2482,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/alpinejs__sort": {
|
||||
"version": "3.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/alpinejs__sort/-/alpinejs__sort-3.13.0.tgz",
|
||||
"integrity": "sha512-iR9vEy6e3yXbYAK45/hpulzlt8SSKTsvYUl/t5nuWjtbJPoGxzxUUqOm3egp83Gqtf//TyJnDCI4OTebAKDRAA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/alpinejs": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/codemirror": {
|
||||
"version": "5.60.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.17.tgz",
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"@biomejs/biome": "^2.4.13",
|
||||
"@hey-api/openapi-ts": "^0.94.5",
|
||||
"@types/alpinejs": "^3.13.11",
|
||||
"@types/alpinejs__sort": "^3.13.0",
|
||||
"@types/cytoscape-cxtmenu": "^3.4.5",
|
||||
"@types/cytoscape-klay": "^3.1.5",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
|
||||
@@ -22,35 +22,35 @@ document.addEventListener("alpine:init", () => {
|
||||
albums: [] as Album[],
|
||||
|
||||
async fetchPictures(): Promise<PictureSchema[]> {
|
||||
const localStorageKey = `user${config.userId}Pictures`;
|
||||
const localStorageInvalidationKey = `user${config.userId}PicturesNumber`;
|
||||
const lastCachedNumber = localStorage.getItem(localStorageInvalidationKey);
|
||||
// Check the cache before hitting the API.
|
||||
const localStorageKey = "userPictures";
|
||||
const cacheContent: { userId: number; pictures: PictureSchema[] }[] = JSON.parse(
|
||||
localStorage.getItem(localStorageKey) || "[]",
|
||||
);
|
||||
const userPictures = cacheContent.find((obj) => obj.userId === config.userId);
|
||||
if (
|
||||
lastCachedNumber !== null &&
|
||||
Number.parseInt(lastCachedNumber, 10) === config.nbPictures
|
||||
userPictures !== undefined &&
|
||||
userPictures.pictures.length === config.nbPictures
|
||||
) {
|
||||
return JSON.parse(localStorage.getItem(localStorageKey));
|
||||
// The cached value is considered valid
|
||||
// if it contains the right amount of pictures.
|
||||
// This amount is known because it is given in the template.
|
||||
return userPictures.pictures;
|
||||
}
|
||||
const pictures = await paginated(picturesFetchPictures, {
|
||||
// biome-ignore lint/style/useNamingConvention: from python api
|
||||
query: { users_identified: [config.userId] },
|
||||
} as PicturesFetchPicturesData);
|
||||
|
||||
cacheContent.push({ userId: config.userId, pictures: pictures });
|
||||
try {
|
||||
localStorage.setItem(localStorageInvalidationKey, config.nbPictures.toString());
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(pictures));
|
||||
// cache only the pictures of the last 4 visited profiles
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(cacheContent.slice(-4)));
|
||||
} catch {
|
||||
// an exception is raised if the localstorage is entirely filled
|
||||
// so just delete all cached user pictures.
|
||||
// an exception is raised if the localstorage is entirely filled.
|
||||
// To be as safe as possible, delete the cached pictures.
|
||||
// A cache hit is not worth the page breaking.
|
||||
Object.keys(localStorage)
|
||||
.filter(
|
||||
(key) =>
|
||||
key.startsWith("user") &&
|
||||
(key.endsWith("Pictures") || key.endsWith("PicturesNumber")),
|
||||
)
|
||||
.forEach((key) => {
|
||||
localStorage.removeItem(key);
|
||||
});
|
||||
localStorage.removeItem(localStorageKey);
|
||||
}
|
||||
return pictures;
|
||||
},
|
||||
|
||||
@@ -2424,11 +2424,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.7.0"
|
||||
version = "2.6.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user