Migrate uv guide to webpack

This commit is contained in:
Antoine Bartuccio 2024-10-10 02:04:49 +02:00
parent 46e58bb49e
commit 86bbc4cf6e
4 changed files with 129 additions and 102 deletions

View File

@ -15,7 +15,8 @@
"sideEffects": [".css"], "sideEffects": [".css"],
"imports": { "imports": {
"#openapi": "./staticfiles/generated/openapi/index.ts", "#openapi": "./staticfiles/generated/openapi/index.ts",
"#core:*": "./core/static/webpack/*" "#core:*": "./core/static/webpack/*",
"#pedagogy:*": "./pedagogy/static/webpack/*"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",

View File

@ -0,0 +1,120 @@
import { uvFetchUvList } from "#openapi";
const pageDefault = 1;
const pageSizeDefault = 100;
document.addEventListener("alpine:init", () => {
Alpine.data("uv_search", () => ({
uvs: {
count: 0,
next: null,
previous: null,
results: [],
},
loading: false,
page: pageDefault,
// biome-ignore lint/style/useNamingConvention: api is in snake_case
page_size: pageSizeDefault,
search: "",
department: [],
// biome-ignore lint/style/useNamingConvention: api is in snake_case
credit_type: [],
semester: [],
// biome-ignore lint/style/useNamingConvention: api is in snake_case
to_change: [],
pushstate: History.PUSH,
update: undefined,
initializeArgs() {
const url = new URLSearchParams(window.location.search);
this.pushstate = History.REPLACE;
this.page = Number.parseInt(url.get("page")) || pageDefault;
this.page_size = Number.parseInt(url.get("page_size")) || pageSizeDefault;
this.search = url.get("search") || "";
this.department = url.getAll("department");
this.credit_type = url.getAll("credit_type");
/* The semester is easier to use on the backend as an enum (spring/autumn/both/none)
and easier to use on the frontend as an array ([spring, autumn]).
Thus there is some conversion involved when both communicate together */
this.semester = url.has("semester") ? url.get("semester").split("_AND_") : [];
this.update();
},
async 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);
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);
}
// 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;
}, 50);
const searchParams = ["search", "department", "credit_type", "semester"];
const paginationParams = ["page", "page_size"];
for (const param of searchParams) {
this.$watch(param, () => {
if (this.pushstate !== History.PUSH) {
/* This means that we are doing a mass param edit */
return;
}
/* Reset pagination on search */
this.page = pageDefault;
this.page_size = pageSizeDefault;
});
}
for (const param of searchParams.concat(paginationParams)) {
this.$watch(param, (value) => {
this.to_change.push({ param: param, value: value });
this.update();
});
}
window.addEventListener("popstate", () => {
this.initializeArgs();
});
this.initializeArgs();
},
async fetchData() {
this.loading = true;
const args = {
// biome-ignore lint/style/useNamingConvention: api is in snake_case
page_size: this.page_size,
};
for (const [param, value] of new URL(
window.location.href,
).searchParams.entries()) {
// Deal with array type params
if (["credit_type", "department", "semester"].includes(param)) {
if (args[param] === undefined) {
args[param] = [];
}
args[param].push(value);
} else {
args[param] = value;
}
}
this.uvs = (
await uvFetchUvList({
query: args,
})
).data;
this.loading = false;
},
maxPage() {
return Math.ceil(this.uvs.count / this.page_size);
},
}));
});

View File

@ -9,6 +9,10 @@
<link rel="stylesheet" href="{{ static('pedagogy/css/pedagogy.scss') }}"> <link rel="stylesheet" href="{{ static('pedagogy/css/pedagogy.scss') }}">
{% endblock %} {% endblock %}
{% block additional_js %}
<script src="{{ static('webpack/pedagogy/guide-index.js') }}" defer></script>
{% endblock %}
{% block head %} {% block head %}
{{ super() }} {{ super() }}
<meta name="viewport" content="width=device-width, initial-scale=0.6, maximum-scale=2"> <meta name="viewport" content="width=device-width, initial-scale=0.6, maximum-scale=2">
@ -113,105 +117,6 @@
</template> </template>
</tbody> </tbody>
</table> </table>
{{ paginate_alpine("page", "max_page()") }} {{ paginate_alpine("page", "maxPage()") }}
</div> </div>
<script>
{#
How does this work :
The page contains two main elements : the form and the results.
The form contains multiple inputs, allowing the user to apply the filter of its choice.
Each modification of those filters will modify the GET parameters of the URL,
then fetch the corresponding data from the API.
This data will then be displayed on the result part of the page.
#}
const pageDefault = 1;
const pageSizeDefault = 100;
document.addEventListener("alpine:init", () => {
Alpine.data("uv_search", () => ({
uvs: [],
loading: false,
page: pageDefault,
pageSize: pageSizeDefault,
search: "",
department: [],
credit_type: [],
semester: [],
to_change: [],
pushstate: History.PUSH,
update: undefined,
async initializeArgs() {
let url = new URLSearchParams(window.location.search);
this.pushstate = History.REPLACE;
this.page = parseInt(url.get("page")) || pageDefault;;
this.pageSize = parseInt(url.get("pageSize")) || pageSizeDefault;
this.search = url.get("search") || "";
this.department = url.getAll("department");
this.credit_type = url.getAll("credit_type");
{# The semester is easier to use on the backend as an enum (spring/autumn/both/none)
and easier to use on the frontend as an array ([spring, autumn]).
Thus there is some conversion involved when both communicate together #}
this.semester = url.has("semester") ?
url.get("semester").split("_AND_") : [];
this.update()
},
async init() {
this.update = Alpine.debounce(async () => {
{# Create the whole url before changing everything all at once #}
let first = this.to_change.shift();
let url = updateQueryString(first.param, first.value, History.NONE);
this.to_change.forEach((value) => {
url = updateQueryString(value.param, value.value, History.NONE, url);
})
updateQueryString(first.param, first.value, this.pushstate, url);
await this.fetchData(); {# reload data on form change #}
this.to_change = [];
this.pushstate = History.PUSH;
}, 50);
let search_params = ["search", "department", "credit_type", "semester"];
let pagination_params = ["page", "pageSize"];
search_params.forEach((param) => {
this.$watch(param, async (value) => {
if (this.pushstate != History.PUSH){
{# This means that we are doing a mass param edit #}
return;
}
{# Reset pagination on search #}
this.page = pageDefault;
this.pageSize = pageSizeDefault;
});
});
search_params.concat(pagination_params).forEach((param) => {
this.$watch(param, async (value) => {
this.to_change.push({ param: param, value: value })
this.update();
});
});
window.addEventListener("popstate", async (event) => {
await this.initializeArgs();
});
await this.initializeArgs();
},
async fetchData() {
this.loading = true;
const url = "{{ url("api:fetch_uvs") }}" + window.location.search;
this.uvs = await (await fetch(url)).json();
this.loading = false;
},
max_page() {
return Math.ceil(this.uvs.count / this.pageSize);
}
}))
})
</script>
{% endblock content %} {% endblock content %}

View File

@ -10,7 +10,8 @@
"moduleResolution": "node", "moduleResolution": "node",
"paths": { "paths": {
"#openapi": ["./staticfiles/generated/openapi/index.ts"], "#openapi": ["./staticfiles/generated/openapi/index.ts"],
"#core:*": ["./core/static/webpack/*"] "#core:*": ["./core/static/webpack/*"],
"#pedagogy:*": ["./pedagogy/static/webpack/*"]
} }
} }
} }