Skip to content

Commit

Permalink
Show isRobot and Id on words. Make sorting words client side. Make th…
Browse files Browse the repository at this point in the history
…e comboboxes next to each other. Make the updoot arrow bigger
  • Loading branch information
YummyBacon5 authored Feb 5, 2024
1 parent 25a74fa commit 57d72c5
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 98 deletions.
4 changes: 2 additions & 2 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ async def upload_a_new_word(new_word: UploadWordFormat):
"uploader": uploader or "Unknown",
"updoots": 0,
"downdoots": 0,
"isRobot": False,
"isRobot": new_word.isRobot,
}

# Keep the id same as the doc_id
Expand Down Expand Up @@ -200,7 +200,7 @@ async def update_words_updoot_count(req: UpdateUpdoot):
async def get_all_words(
sortby: SortByEnum = SortByEnum.UPDOOTS, orderby: DirEnum = DirEnum.DESC
):
IS_REVERSED = orderby == DirEnum.ASC
IS_REVERSED = orderby == DirEnum.DESC

if sortby == SortByEnum.TOTALDOOTS:
return sorted(db.all(), key=lambda x: x["updoots"] - x["downdoots"], reverse=IS_REVERSED)
Expand Down
4 changes: 2 additions & 2 deletions frontend/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import js from "@eslint/js";
import tsParser from "@typescript-eslint/parser";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import reactPlugin from "eslint-plugin-react";
Expand All @@ -15,9 +16,8 @@ export default [
typescript: tsPlugin,
react: reactPlugin,
},
...js.configs.recommended,
rules: {
//...tsPlugin.rules,
//...reactPlugin.rules,
indent: [
"error",
"tab",
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/AddNewWord.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default function AddNewWord(props: AddNewWordProps) {
}

(e.target as HTMLFormElement).reset();
props.onSubmitFinished?.();
await props.onSubmitFinished?.();
setNewWordDomId(createWordDomId(newWord as Word));
}

Expand All @@ -59,7 +59,7 @@ export default function AddNewWord(props: AddNewWordProps) {
<label htmlFor="word">
Word: <span aria-hidden="true" className="required-text">(required)</span>
</label>
<input type="text" id="word" name="word" className="input" required />
<input type="text" id="word" name="word" className="input" required autoComplete="off" />
</div>

<div className="field">
Expand Down
36 changes: 22 additions & 14 deletions frontend/src/DfnArea.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useState } from "react";
import { UpdateUpdoot, UpdootStates, Word } from "./types";
import Tooltip from "./components/Tooltip";
import { AudioIcon, LinkIcon, UpdootIcon, createDescriptionDomId, createWordDomId } from "./hooks/utils";
Expand All @@ -16,16 +16,14 @@ type UpdootButtonsProps = {
function UpdootButtons({ word, onUpdootUpdate: updateState }: UpdootButtonsProps) {
const [updootState, setUpdootState] = useState<UpdootStates>(localStorage.getItem(`${word.id}-updootState`) as UpdootStates || "none");

useEffect(() => {
if(updootState == "none") {
async function handleUpdootClick(state: UpdootStates) {
setUpdootState(state);

if(state == "none") {
localStorage.removeItem(`${word.id}-updootState`);
return;
}
localStorage.setItem(`${word.id}-updootState`, updootState);
}, [updootState]);

async function handleUpdootClick(state: UpdootStates) {
setUpdootState(state);
localStorage.setItem(`${word.id}-updootState`, state);

const updatedWord: Word = await fetch("/api/update_updoot", {
headers: {
Expand Down Expand Up @@ -82,8 +80,10 @@ export default function DfnArea({ word }: DfnAreaProps) {
const relativeTime = useRelativeTime(creationDateAsDate);

function speakWord() {
if(isSpeaking) return;
setIsSpeaking(true);
if(isSpeaking) {
window.speechSynthesis.cancel();
return;
};

window.speechSynthesis.cancel();

Expand All @@ -92,6 +92,7 @@ export default function DfnArea({ word }: DfnAreaProps) {
utterance.voice = voices[0];
utterance.lang = "en-GB";

utterance.addEventListener("start", () => setIsSpeaking(true));
utterance.addEventListener("end", () => setIsSpeaking(false));

window.speechSynthesis.speak(utterance);
Expand All @@ -100,7 +101,7 @@ export default function DfnArea({ word }: DfnAreaProps) {
return (
<article id={domId} className="dictionary-entry">
<header className="header-section">
<small>Id: {wordData.id}</small>
<small className="word-id">Id: {wordData.id}</small>
<h2 className="word">
<dfn aria-details={descriptionDomId}>{wordData.word}</dfn>
</h2>
Expand All @@ -114,11 +115,11 @@ export default function DfnArea({ word }: DfnAreaProps) {
</a>
</Tooltip>

<Tooltip toolTipContent={isSpeaking ? "Speaking" : "Not speaking"}>
<Tooltip toolTipContent={isSpeaking ? "Cancel speaking" : "Speak word"} ariaRelationships="none">
<button
onClick={speakWord}
className="speak-button"
aria-label="Speak word"
aria-label={isSpeaking ? "Cancel speaking" : "Speak word"}
aria-disabled={isSpeaking}
>
<AudioIcon />
Expand All @@ -133,7 +134,14 @@ export default function DfnArea({ word }: DfnAreaProps) {

<footer className="footer-section">
<p>
Uploaded by: <span className="uploader-word">{wordData.uploader}</span>
Uploaded by:
<span className="uploader-word">
{wordData.isRobot &&
<Tooltip toolTipContent={"This word was uploaded by a robot"}>
<span aria-label="Robot" aria-roledescription="emoji" role="img">🤖</span>
</Tooltip>}
{wordData.uploader}
</span>
</p>
<p>
Creation date:{" "}
Expand Down
154 changes: 99 additions & 55 deletions frontend/src/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function Home() {

useEffect(() => {
(async () => {
await GetAllWords();
await GetAllWords(true);
setIsFirstIsLoading(false);
})();
}, []);
Expand All @@ -25,16 +25,56 @@ export default function Home() {
location.hash = orginalHash;
}, [isFirstLoading]);

async function GetAllWords() {
async function GetAllWords(reloadWords?: boolean) {
const searchParams = new URLSearchParams(location.search);

const sortby = searchParams.get("sortby") as GetAllWordsSortByOptions || "id";
const orderby = searchParams.get("orderby") as GetAllWordsOrderByOptions || "asc";
const orderby = searchParams.get("orderby") as GetAllWordsOrderByOptions || "desc";

const json: Word[] = await fetch(`/api/get_all_words?sortby=${sortby}&orderby=${orderby}`).then(x => x.json());
if(reloadWords) {
const json: Word[] = await fetch(`/api/get_all_words?sortby=${sortby}&orderby=${orderby}`).then(x => x.json());

setAllWords(json);
return;
setAllWords(json);
return;
}
const isDesc = orderby == "desc";

if(sortby == "totaldoots") {
setAllWords((prevWords) =>
prevWords.toSorted((a, b) =>
isDesc
? a.updoots - a.downdoots - (b.updoots - b.downdoots)
: b.updoots - b.downdoots - (a.updoots - a.downdoots),
),
);
} else if(sortby == "id") {
setAllWords((prevWords) =>
prevWords.toSorted((a, b) => (isDesc ? a.id - b.id : b.id - a.id)),
);
} else if(sortby == "updoots") {
setAllWords((prevWords) =>
prevWords.toSorted((a, b) => (isDesc ? a.updoots - b.updoots : b.updoots - a.updoots)),
);
} else if(sortby == "downdoots") {
setAllWords((prevWords) =>
prevWords.toSorted((a, b) => (isDesc ? a.downdoots - b.downdoots : b.downdoots - a.downdoots)),
);
} else if(sortby == "date") {
setAllWords((prevWords) =>
prevWords.toSorted((a, b) =>
isDesc ? a.creationDate - b.creationDate : b.creationDate - a.creationDate,
),
);
} else if(sortby == "alphabet") {
setAllWords((prevWords) =>
prevWords.toSorted((a, b) =>
isDesc ? a.word.localeCompare(b.word) : b.word.localeCompare(a.word),
),
);
}

if(orderby)
setAllWords(x => x.toReversed());
}

return (
Expand All @@ -47,60 +87,64 @@ export default function Home() {
</header>
<section className="add-new-word">
<AddNewWord
onSubmitFinished={() => GetAllWords()}
onSubmitFinished={() => GetAllWords(true)}
/>
</section>
<main>
<search className="filter-area">
<label htmlFor="sort-by">Sort by:</label>
<Combobox
id="sort-by"
urlKey="sortby"
options={[
{
content:"Total doots",
urlValue: "totaldoots",
},
{
content: "Updoots",
urlValue: "updoots",
},
{
content: "Downdoots",
urlValue: "downdoots",
},
{
content: "Id",
urlValue: "id",
},
{
content: "Date",
urlValue: "date",
},
{
content: "Alphabet",
urlValue: "alphabet",
},
]}
onUpdate={() => GetAllWords()}
/>
<div>
<label htmlFor="sort-by">Sort by:</label>
<Combobox
id="sort-by"
urlKey="sortby"
options={[
{
content:"Total doots",
urlValue: "totaldoots",
},
{
content: "Updoots",
urlValue: "updoots",
},
{
content: "Downdoots",
urlValue: "downdoots",
},
{
content: "Id",
urlValue: "id",
},
{
content: "Date",
urlValue: "date",
},
{
content: "Alphabet",
urlValue: "alphabet",
},
]}
onUpdate={() => GetAllWords()}
/>
</div>

<label htmlFor="order-by">Order by:</label>
<Combobox
id="order-by"
urlKey="orderby"
options={[
{
content: "Ascending (lowest to highest)",
urlValue: "asc",
},
{
content: "Descending (highest to lowest)",
urlValue: "desc",
},
]}
onUpdate={() => GetAllWords()}
/>
<div>
<label htmlFor="order-by">Order by:</label>
<Combobox
id="order-by"
urlKey="orderby"
options={[
{
content: "Descending",
urlValue: "desc",
},
{
content: "Ascending",
urlValue: "asc",
},
]}
onUpdate={() => GetAllWords()}
/>
</div>
</search>
{isFirstLoading && <progress className="loading" />}
{allWords.length ?
Expand Down
11 changes: 3 additions & 8 deletions frontend/src/components/Combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ import React, { ReactNode, useEffect, useId, useMemo, useRef, useState } from "r
import { ChevronIcon } from "../hooks/utils";
import { usePopper } from "react-popper";

type OptionUpdate = {
urlKey: string;
urlValue: string;
};

type ComboboxOption = {
content: ReactNode;
urlValue: string;
Expand Down Expand Up @@ -60,7 +55,7 @@ export default function Combobox(props: ComboboxProps) {

useEffect(() => {
focusedOption.current?.focus();
}, [focusedOptionIndex, popper]);
}, [focusedOptionIndex]);

useEffect(() => {
props.onUpdate?.(props.urlKey, props.options[confirmedOptionIndex].urlValue);
Expand Down Expand Up @@ -116,7 +111,7 @@ export default function Combobox(props: ComboboxProps) {
}}
//@ts-expect-error The Popover API isn't supported by React or its types yet
popovertarget={listboxId}
aria-controls="listbox"
aria-controls={listboxId}
aria-haspopup="listbox"
role="combobox"
>
Expand Down Expand Up @@ -156,7 +151,7 @@ export default function Combobox(props: ComboboxProps) {
e.currentTarget.click();
}}
tabIndex={focusedOptionIndex == i ? undefined : -1}
data-test={focusedOptionIndex == i}
autoFocus={focusedOptionIndex == i}
ref={focusedOptionIndex == i ? focusedOption : undefined}
role="option"
aria-selected={confirmedOptionIndex == i}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function AudioIcon() {
export function ChevronIcon() {
return (
<svg className="chevron-icon" width={30} height={30} viewBox="0 0 50 50">
<polyline points="10 35 25 15 40 35" stroke="currentColor" fill="none" strokeWidth={4} />
<polyline points="10 15 25 35 40 15" stroke="currentColor" fill="none" strokeWidth={4} />
</svg>
);
}
Loading

0 comments on commit 57d72c5

Please sign in to comment.