diff --git a/creations.php b/creations.php
index b2341320..d413c2bf 100755
--- a/creations.php
+++ b/creations.php
@@ -1,4 +1,6 @@
{
+ const didMount = useRef(false);
+
+ useEffect(() => {
+ if (!didMount.current) {
+ didMount.current = true;
+ return;
+ }
+ callback(dependencies);
+ }, dependencies);
+};
+
+export default useEffectOnUpdate;
\ No newline at end of file
diff --git a/v2/app/hooks/useFormSubmit.tsx b/v2/app/hooks/useFormSubmit.tsx
index f3ef1ff7..ce4b649d 100644
--- a/v2/app/hooks/useFormSubmit.tsx
+++ b/v2/app/hooks/useFormSubmit.tsx
@@ -1,4 +1,4 @@
-import { useRouter } from "next/router";
+import { NextRouter, useRouter } from "next/router";
import { FormEvent, useCallback } from "react";
function toJson(formData: FormData) {
@@ -9,16 +9,20 @@ function toJson(formData: FormData) {
return object;
}
+export function doSubmit(router: NextRouter, form: HTMLFormElement) {
+ router.push({
+ pathname: form.action,
+ query: toJson(new FormData(form))
+ });
+}
+
function useFormSubmit() {
const router = useRouter();
return useCallback((e: FormEvent) => {
const form = e.target;
if (form instanceof HTMLFormElement) {
e.preventDefault();
- router.push({
- pathname: form.action,
- query: toJson(new FormData(form))
- });
+ doSubmit(router, form);
}
}, [router]);
}
diff --git a/v2/app/hooks/useSmoothFetch.ts b/v2/app/hooks/useSmoothFetch.ts
index 030dcb64..0a6e89a3 100644
--- a/v2/app/hooks/useSmoothFetch.ts
+++ b/v2/app/hooks/useSmoothFetch.ts
@@ -10,7 +10,8 @@ type SmoothParams = {
requestOptions?: RequestInit;
reloadDeps?: any[],
cacheKey?: string,
- disabled?: boolean
+ disabled?: boolean,
+ onSuccess?: (data: T) => void;
}
let seed = 1;
@@ -77,7 +78,7 @@ type CacheHandler = Record void>
}>
const cacheHandler: CacheHandler = {}
-function useSmoothFetch(input: RequestInfo, { placeholder, retryDelay = 1000, retryDelayMultiplier = 2, retryCount = Infinity, requestOptions, reloadDeps = [], cacheKey, disabled }: SmoothParams = {}) {
+function useSmoothFetch(input: RequestInfo, { placeholder, retryDelay = 1000, retryDelayMultiplier = 2, retryCount = Infinity, requestOptions, reloadDeps = [], cacheKey, disabled, onSuccess }: SmoothParams = {}) {
const placeholderVal = useMemo(() => {
if (!disabled && cacheKey && cacheHandler[cacheKey]) {
const { state } = cacheHandler[cacheKey];
@@ -127,6 +128,7 @@ function useSmoothFetch(input: RequestInfo, { placeholder, retryDelay = 1000,
setAllStates({ data, loading: false, error: null })
if (cacheKey)
cacheHandler[cacheKey].setStates.length = 0;
+ onSuccess?.(data);
})
.catch(error => {
if (currentRetryCount < retryCount) {
diff --git a/v2/app/pages/creations.tsx b/v2/app/pages/creations.tsx
index afb7557d..30aaf372 100644
--- a/v2/app/pages/creations.tsx
+++ b/v2/app/pages/creations.tsx
@@ -7,35 +7,94 @@ import Ad from "../components/Ad/Ad";
import WithAppContext from "../components/WithAppContext/WithAppContext";
import useSmoothFetch, { Placeholder } from "../hooks/useSmoothFetch";
import Skeleton from "../components/Skeleton/Skeleton";
-import { useMemo } from "react";
+import { useEffect, useMemo, useRef, useState } from "react";
import { useRouter } from "next/router";
-import TrackCreationCard from "../components/TrackCreationCard/TrackCreationCard";
+import TrackCreationCard, { TrackCreation } from "../components/TrackCreationCard/TrackCreationCard";
import { buildQuery } from "../helpers/uris";
import Link from "next/link";
import useCreations from "../hooks/useCreations";
+import useFormSubmit, { doSubmit } from "../hooks/useFormSubmit";
+import useEffectOnUpdate from "../hooks/useEffectUpdate";
+const resPerPage = 60, resPerRow = 5;
const CreationsList: NextPage = () => {
const language = useLanguage();
const router = useRouter();
+ const handleSearch = useFormSubmit();
+
const { user, admin, tri, type, nom, auteur } = router.query;
const { data: creator } = useSmoothFetch<{ name: string }>(`/api/user/${user}`, {
disabled: !user
});
const nTri = +tri || 0;
- const singleType = (type !== '');
- const sortTabs = language ? ['By latest', 'Top rated', 'Trending'] : ['Les plus récents', 'Les mieux notés', 'Tendances'];
- const types = language
+ const sortTabs = useMemo(() => language ? ['By latest', 'Top rated', 'Trending'] : ['Les plus récents', 'Les mieux notés', 'Tendances'], [language]);
+ const sortTabsData = useMemo(() => {
+ return sortTabs.map((sortTab, i) => ({
+ text: sortTab,
+ url: "/creations?" + buildQuery({
+ ...router.query,
+ tri: i
+ })
+ }));
+ }, [sortTabs, router.query]);
+ const types = useMemo(() => language
? ['Complete mode - multicups', 'Quick mode - multicups', 'Complete mode - cups', 'Quick mode - cups', 'Complete mode - circuits', 'Quick mode - circuits', 'Complete mode - arenas', 'Quick mode - arenas']
: ['Mode complet - multicoupes', 'Mode simplifié - multicoupes', 'Mode complet - coupes', 'Mode simplifié - coupes', 'Mode complet - circuits', 'Mode simplifié - circuits', 'Mode complet - arènes', 'Mode simplifié - arènes']
+ , [language]);
+
+ const [page, setPage] = useState(1);
const creationParams = useMemo(() => {
const params = {
- user, admin, tri, type, nom, auteur
+ user, admin, tri, type, nom, auteur, page
};
return buildQuery(params);
- }, [router.query]);
+ }, [router.query, page]);
+ useEffectOnUpdate(() => {
+ resetAbortController();
+ setCreationsListHeights({
+ current: 0,
+ max: 0
+ });
+ setPage(1);
+ }, [buildQuery(router.query)]);
+ useEffectOnUpdate(() => {
+ setQueryId(queryId + 1);
+ }, [creationParams]);
+ let [cardHeight, setCardHeight] = useState(0);
+ let [chunkHeight, setChunkHeight] = useState(0);
+ function getCardHeight() {
+ try {
+ if (window.matchMedia('(max-width: 800px)').matches) {
+ return 126;
+ }
+ }
+ catch (e) {
+ }
+ return 146;
+ }
+ useEffect(() => {
+ setCardHeight(cardHeight = getCardHeight());
+ setChunkHeight(chunkHeight = cardHeight * resPerPage / resPerRow);
+ }, []);
+ const [creationsListHeights, setCreationsListHeights] = useState({
+ current: 0,
+ max: 0
+ });
+
+ function createAbortController() {
+ if (typeof AbortController !== "undefined")
+ return new AbortController();
+ }
+ const controller = useRef(createAbortController());
+ const [queryId, setQueryId] = useState(0);
+ function resetAbortController() {
+ controller.current?.abort();
+ controller.current = createAbortController();
+ }
+
const { data: creationsPayload, loading: creationsLoading } = useSmoothFetch(`/api/getCreations.php?${creationParams}`, {
placeholder: () => ({
- data: Placeholder.array(60, (id) => ({
+ data: Placeholder.array(resPerPage, (id) => ({
id,
author: "",
cicon: "",
@@ -50,32 +109,90 @@ const CreationsList: NextPage = () => {
})),
count: 0,
countByType: []
- })
+ }),
+ onSuccess: (payload) => {
+ if (page === 1)
+ setCreationsList(payload.data);
+ else
+ setCreationsList([...creationsList, ...payload.data]);
+ setTimeout(() => {
+ const $creationsList = document.getElementById(styles.creationsList);
+ if ($creationsList) {
+ const $creationsListWrapper = $creationsList.parentNode;
+ if ($creationsListWrapper instanceof HTMLElement) {
+ if (!$creationsListWrapper.style.width)
+ $creationsListWrapper.style.width = $creationsListWrapper.offsetWidth + "px";
+ }
+ setCreationsListHeights({
+ current: Math.min(creationsListHeights.current + chunkHeight, $creationsList.scrollHeight),
+ max: $creationsList.scrollHeight
+ });
+ setCreationsRendering(false);
+ }
+ });
+ },
+ reloadDeps: [queryId],
+ requestOptions: {
+ signal: controller.current?.signal
+ }
});
- const creationCount = useMemo(() => {
+ const [creationsList, setCreationsList] = useState(creationsPayload.data);
+ const [creationCount, setCreationCount] = useState<{ total: number, byType: number[], isTotal: boolean }>();
+ useEffect(() => {
if (creationsLoading) return;
- return {
+ setCreationCount({
total: creationsPayload.count,
- byType: creationsPayload.countByType
- }
- }, [creationsPayload, creationsLoading]);
+ byType: creationsPayload.countByType,
+ isTotal: (creationsPayload.countByType.length > 1)
+ })
+ }, [creationsLoading, creationsPayload]);
+ const [creationsRendering, setCreationsRendering] = useState(creationsLoading);
+ useEffect(() => {
+ if (creationsLoading)
+ setCreationsRendering(true);
+ }, [creationsLoading]);
const { previewCreation } = useCreations();
- function defile() {
+ const creationsListStyle = useMemo(() => ({
+ height: creationsListHeights.current,
+ minHeight: creationsRendering ? chunkHeight : Math.min(chunkHeight, creationsListHeights.max),
+ }), [creationsListHeights, chunkHeight, creationsRendering]);
+
+ const lastPage = useMemo(() => (page * resPerPage >= creationCount?.total), [page, creationCount]);
+ function loadMore() {
+ const nextHeight = creationsListHeights.current + chunkHeight;
+ if ((nextHeight > creationsListHeights.max) && !lastPage) {
+ resetAbortController();
+ setPage(page + 1);
+ }
+ else {
+ setCreationsListHeights({
+ ...creationsListHeights,
+ current: Math.min(nextHeight, creationsListHeights.max)
+ });
+ }
}
- function masque() {
+ function showLess() {
+ setCreationsListHeights({
+ ...creationsListHeights,
+ current: creationsListHeights.current - chunkHeight
+ });
}
function reduceAll() {
- }
- function handleTabSelect(e) {
- e.preventDefault();
+ setCreationsListHeights({
+ ...creationsListHeights,
+ current: chunkHeight
+ });
}
function scrollToTop(e) {
e.preventDefault();
window.scrollTo(0, 0);
}
+ if (!creationsLoading)
+ console.log(creationsList[0]?.id);
+
return (
@@ -89,23 +206,23 @@ const CreationsList: NextPage = () => {
: <>Bienvenue dans la liste des circuits et arènes partagés par la communauté de Mario Kart PC !
Vous aussi, partagez les circuits que vous créez en cliquant sur "Partager le circuit" en bas à gauche de la page du circuit.>
)}
-
@@ -113,21 +230,27 @@ const CreationsList: NextPage = () => {
-
- {creationsPayload.data.map((creation) => )}
-
+
+
+ {creationsList.map((creation, i) => )}
+
+
-
- defile()} />{" "}
- masque()} />{" "}
- reduceAll()} />
+
+ = creationsListHeights.max) && lastPage) })} value={language ? 'More' : 'Plus'} onClick={() => loadMore()} />{" "}
+ = creationsListHeights.current })} value={language ? 'Less' : 'Moins'} onClick={() => showLess()} />{" "}
+ = creationsListHeights.current })} value={language ? 'Minimize' : 'Réduire'} onClick={() => reduceAll()} />
+ {!creationsLoading && !creationsList.length &&
+ {language ? 'No result for this search' : 'Aucun résultat pour cette recherche'}
+
}
+
{language ? 'Back to top' : 'Retour haut de page'}{" - "}
{language ? 'Back to Mario Kart PC' : 'Retour à Mario Kart PC'}
-
+
);
}
diff --git a/v2/app/styles/Creations.module.scss b/v2/app/styles/Creations.module.scss
index efdda158..b0337967 100644
--- a/v2/app/styles/Creations.module.scss
+++ b/v2/app/styles/Creations.module.scss
@@ -97,6 +97,9 @@
}
.subbuttons {
margin-top: 12px;
+ &.invisible, .invisible {
+ visibility: hidden;
+ }
}
@media screen and (min-width: 880px) {
h2, .subbuttons {
@@ -139,5 +142,16 @@
margin-right: auto;
text-align: center;
line-height: 0;
+ height: 0px;
+ overflow-x: auto;
+ overflow-y: hidden;
+ transition: height 1s;
+ }
+ #creationsList {
+ display: block;
+ }
+ h4 {
+ text-align: center;
+ margin-top: 20px;
}
}
\ No newline at end of file
diff --git a/v2/php-api/endpoints/getCreations.php b/v2/php-api/endpoints/getCreations.php
index 0c54ce55..74eb2fe3 100644
--- a/v2/php-api/endpoints/getCreations.php
+++ b/v2/php-api/endpoints/getCreations.php
@@ -2,18 +2,17 @@
include('../includes/initdb.php');
require_once('../includes/creations.php');
require_once('../includes/api.php');
-$tri = isset($_GET['tri']) ? $_GET['tri']:0;
-$type = isset($_GET['type']) ? $_GET['type']:'';
-$nom = isset($_GET['nom']) ? stripslashes($_GET['nom']):'';
-$auteur = isset($_GET['auteur']) ? stripslashes($_GET['auteur']):'';
+$tri = isset($_GET['tri']) && is_numeric($_GET['tri']) ? $_GET['tri'] : 0;
+$type = isset($_GET['type']) ? $_GET['type'] : '';
+$nom = isset($_GET['nom']) ? stripslashes($_GET['nom']) : '';
+$auteur = isset($_GET['auteur']) ? stripslashes($_GET['auteur']) : '';
$pids = null;
if (isset($_GET['user'])) {
- $user = $_GET['user'];
- if ($getProfile = mysql_fetch_array(mysql_query('SELECT identifiant,identifiant2,identifiant3,identifiant4 FROM `mkprofiles` WHERE id="'. $user .'"')))
- $pids = array($getProfile['identifiant'],$getProfile['identifiant2'],$getProfile['identifiant3'],$getProfile['identifiant4']);
-}
-else
- $user = '';
+ $user = $_GET['user'];
+ if ($getProfile = mysql_fetch_array(mysql_query('SELECT identifiant,identifiant2,identifiant3,identifiant4 FROM `mkprofiles` WHERE id="' . $user . '"')))
+ $pids = array($getProfile['identifiant'], $getProfile['identifiant2'], $getProfile['identifiant3'], $getProfile['identifiant4']);
+} else
+ $user = '';
$singleType = ($type !== '');
if ($singleType) {
$aCircuits = array($aCircuits[$type]);
@@ -28,22 +27,22 @@
if ($nbCircuits > $MAX_CIRCUITS)
$nbCircuits = $MAX_CIRCUITS;
$aParams = array(
- 'type' => $type,
- 'tri' => $tri,
- 'nom' => $nom,
- 'auteur' => $auteur,
- 'pids' => $pids,
- 'max_circuits' => $nbCircuits,
+ 'type' => $type,
+ 'tri' => $tri,
+ 'nom' => $nom,
+ 'auteur' => $auteur,
+ 'pids' => $pids,
+ 'max_circuits' => $nbCircuits,
);
-$page = isset($_GET['page']) ? $_GET['page']:1;
+$page = isset($_GET['page']) ? $_GET['page'] : 1;
if ($nbByType === null)
- $nbByType = countTracksByType($aCircuits,$aParams);
-$creationsList = listCreations($page,$nbByType,$weightsByType,$aCircuits,$aParams);
+ $nbByType = countTracksByType($aCircuits, $aParams);
+$creationsList = listCreations($page, $nbByType, $weightsByType, $aCircuits, $aParams);
$data = array();
foreach ($creationsList as &$creation) {
$item = array(
'id' => isset($creation['id']) ? +$creation['id'] : +$creation['ID'],
- 'publicationDate' => strtotime($creation['publication_date'])*1000,
+ 'publicationDate' => strtotime($creation['publication_date']) * 1000,
'name' => $creation['nom'],
'author' => $creation['auteur'],
'rating' => +$creation['note'],