Greums's picture
major improvements to the app
a417977
raw
history blame
8.08 kB
import {Topic, topicsCtx} from "@/contexts/topics";
import {Layout} from "../layout";
import {Spinner} from "../spinner";
import style from "./style.module.scss";
import {Button} from "../button";
import {Input} from "../input";
import {Clipboard, Trash2} from "preact-feather";
import {useContext, useEffect, useMemo, useRef, useState} from "preact/hooks";
import {frenchToIso8601, iso8601ToFrench} from "@/utils/dates";
import {FormGroup} from "../form";
import {Slider} from "../slider";
import cn from "classnames";
import {routeCtx, routes} from "@/contexts/route";
import {settingsCtx} from "@/contexts/settings";
import {Wysiwyg} from "@/components/wysiwyg";
import {Alert} from "@/components/alert";
import {logCtx} from "@/contexts/log";
import {Pagination} from "@/components/pagination";
const topicsPerPage = 10;
export function Topics(props: {
page: number,
setPage: (newPage: number) => void,
}) {
const [importContent, setImportContent] = useState("");
const [topicsContext, setTopicsContext, topicsActions] = useContext(topicsCtx);
const [, , logActions] = useContext(logCtx);
const [settings, setSettings] = useContext(settingsCtx);
useEffect(() => {
if((topicsPerPage * props.page) >= topicsContext.topics.length) {
props.setPage(Math.ceil(topicsContext.topics.length / topicsPerPage)/* - 1*/);
}
}, [topicsContext.topics.length])
const sortedTopics = useMemo(() => {
return [...topicsContext.topics].sort((topicA, topicB) => {
if (topicA.posts.length < 1 || topicB.posts.length < 1) {
return 0;
}
return topicB.posts[topicB.posts.length - 1].date.localeCompare(topicA.posts[topicA.posts.length - 1].date);
});
}, [topicsContext.topics]);
const sortedTopicsSlice = useMemo(
() => sortedTopics.slice(props.page * topicsPerPage, (props.page + 1) * topicsPerPage),
[props.page, sortedTopics]
);
const pendingGeneration = topicsContext.generation === "pending";
return (
<div>
<div className={style.listWrapper}>
<Pagination
pageCount={Math.ceil(sortedTopics.length / topicsPerPage)}
page={props.page}
setPage={props.setPage}
/>
<List topics={sortedTopicsSlice}/>
<Pagination
pageCount={Math.ceil(sortedTopics.length / topicsPerPage)}
page={props.page}
setPage={props.setPage}
/>
</div>
<div>
<h2>Nouveau sujet</h2>
<Alert
lines={
topicsContext.generation === "error" ? [
"Erreur lors de la génération du topic",
"Veuillez regarder le log pour plus d'informations"
] : []
}
/>
<div className={style.generationSettings}>
<FormGroup>
<label for="postCount">Nombre de posts</label>
<Slider
name="postCount"
value={settings.postCount}
onChange={(v) => setSettings({...settings, postCount: v})}
min={1}
max={10}
step={1}
/>
</FormGroup>
</div>
<Button
onClick={() => {
topicsActions.generateTopic(settings, logActions.log)
}}
secondary={true}
loading={pendingGeneration}
>
{pendingGeneration ? "Génération en cours…" : "Générer"}
</Button>
</div>
<hr/>
<div>
<Wysiwyg
showTitle={true}
onSubmit={(user, title, text) => {
// setLastAddedTopicId(topicsActions.addTopic(user, title, text));
topicsActions.addTopic(user, title, text)
props.setPage(0);
window.scrollTo({top: 0, behavior: 'smooth'});
}}
disabled={pendingGeneration}
/>
</div>
<hr/>
<div>
<Alert
lines={
topicsContext.import === "error" ? [
"Erreur lors de l'importation du sujet",
] : []
}
/>
<div className={style.import}>
<textarea
placeholder="Importer un sujet…"
name="import"
id="import"
cols="30"
rows="10"
value={importContent}
onInput={(e) => setImportContent((e.target as HTMLTextAreaElement).value)}
/>
</div>
<Button
onClick={() => {
topicsActions.importTopic(importContent);
setImportContent("");
}}
secondary={true}
>
Importer
</Button>
</div>
</div>
)
}
function List(props: {
topics: Topic[],
// latestGeneratedTopicId: string | null,
}) {
return (
<ul className={style.list}>
<li className={style.head}>
<span>Sujet</span>
<span>Auteur</span>
<span>NB</span>
<span>Dernier msg</span>
<span></span>
</li>
{props.topics.length < 1 && <li><span>Aucun sujet</span><span/><span/><span/><span/></li>}
{props.topics.map(topic => <TopicElement topic={topic}/>)}
</ul>
)
}
function TopicElement(props: {
topic: Topic;
}) {
const topic = props.topic;
const [, setRoute] = useContext(routeCtx);
const [, , topicsActions] = useContext(topicsCtx);
let isRecent: boolean = false;
if (topic.posts.length > 0) {
const firstPost = topic.posts[0];
const dateDelta = new Date() - new Date(firstPost.generationDate || firstPost.date); // difference in ms
// If the topic is more recent than 3 sec
if (dateDelta < 3000) {
isRecent = true;
}
}
return (
<li className={cn({[style.highlight]: isRecent})}>
<span>
<a href={routes.topic(topic.id, 1).location} onClick={(e) => {
e.preventDefault();
setRoute(routes.topic(topic.id, 1));
}}>
{topic.title}
</a>
</span>
<span>{topic.posts[0].user}</span>
<span>{topic.posts.length}</span>
<span>{iso8601ToFrench(topic.posts[topic.posts.length - 1].date)}</span>
<span>
<span title="Copier le sujet">
<Clipboard
size={16}
className={style.clipboard}
onClick={() => {
const json = JSON.stringify(topic);
navigator.clipboard.writeText(json);
}}
/>
</span>
<span title="Supprimer le sujet">
<Trash2
size={16}
className={style.trash}
onClick={() => topicsActions.deleteTopic(topic.id)}
/>
</span>
</span>
</li>
)
}