added audio download button and tooltips for various buttons etc

This commit is contained in:
adueck 2024-05-04 13:49:22 +04:00
parent 3f919f609a
commit cfeaec9550
5 changed files with 126 additions and 59 deletions

View File

@ -47,6 +47,7 @@ function Entry({
<i <i
onClick={handlePlayStorageAudio} onClick={handlePlayStorageAudio}
className="clickable ml-2 fas fa-volume-down px-1" className="clickable ml-2 fas fa-volume-down px-1"
title="play audio"
/> />
)} )}
</div> </div>

View File

@ -12,6 +12,7 @@ export function EntryAudioDisplay({
opts: T.TextOptions; opts: T.TextOptions;
user: LingdocsUser | undefined; user: LingdocsUser | undefined;
}) { }) {
const audioPath = getAudioPath(entry.ts);
if (!entry.a) { if (!entry.a) {
return null; return null;
} }
@ -24,23 +25,54 @@ export function EntryAudioDisplay({
action: `play ${entry.p} - ${entry.ts}`, action: `play ${entry.p} - ${entry.ts}`,
}); });
} }
function handleDownload() {
const documentName = `${entry.p}-${entry.ts}.mp3`;
fetch(audioPath)
.then((res) => {
return res.blob();
})
.then((blob) => {
const href = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.download = documentName;
a.href = href;
a.click();
a.href = "";
})
.catch((err) => console.error(err));
}
return ( return (
<figure> <figure>
<figcaption className="mb-2 pl-2"> <figcaption className="mb-2 pl-2">
Listen to <InlinePs opts={opts}>{{ p: entry.p, f: entry.f }}</InlinePs> <div>
Listen to{" "}
<InlinePs opts={opts}>{{ p: entry.p, f: entry.f }}</InlinePs>
</div>
</figcaption> </figcaption>
<audio <div className="d-flex align-items-center">
controls <audio
controlsList="nofullscreen" controls
src={getAudioPath(entry.ts)} controlsList="nofullscreen"
preload="auto" src={audioPath}
onPlay={handlePlay} preload="auto"
> onPlay={handlePlay}
<a href={getAudioPath(entry.ts)}> >
{/* <a href={getAudioPath(entry.ts)}>
Download audio for{" "} Download audio for{" "}
<InlinePs opts={opts}>{{ p: entry.p, f: entry.f }}</InlinePs> <InlinePs opts={opts}>{{ p: entry.p, f: entry.f }}</InlinePs>
</a> </a > */}
</audio> </audio>
<button
type="button"
onClick={handleDownload}
className="ml-2 btn btn-light"
title="download audio"
>
<i className="fas fa-download"></i>
</button>
</div>
</figure> </figure>
); );
} }

View File

@ -9,17 +9,18 @@
import Mousetrap from "mousetrap"; import Mousetrap from "mousetrap";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { State } from "../types/dictionary-types"; import { State } from "../types/dictionary-types";
import { import { OptionsAction, Language, SearchType } from "../types/dictionary-types";
OptionsAction,
Language,
SearchType,
} from "../types/dictionary-types";
const SearchBar = ({ state, optionsDispatch, handleSearchValueChange, onBottom }: { const SearchBar = ({
state: State state,
optionsDispatch: (action: OptionsAction) => void, optionsDispatch,
handleSearchValueChange: (searchValue: string) => void, handleSearchValueChange,
onBottom?: boolean, onBottom,
}: {
state: State;
optionsDispatch: (action: OptionsAction) => void;
handleSearchValueChange: (searchValue: string) => void;
onBottom?: boolean;
}) => { }) => {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => { useEffect(() => {
@ -34,8 +35,8 @@ const SearchBar = ({ state, optionsDispatch, handleSearchValueChange, onBottom }
return () => { return () => {
window.removeEventListener("focus", onFocus); window.removeEventListener("focus", onFocus);
Mousetrap.unbind(["shift+space"]); Mousetrap.unbind(["shift+space"]);
} };
// eslint-disable-next-line // eslint-disable-next-line
}, []); }, []);
function onFocus() { function onFocus() {
if (["/", "/search"].includes(window.location.pathname)) { if (["/", "/search"].includes(window.location.pathname)) {
@ -50,43 +51,56 @@ const SearchBar = ({ state, optionsDispatch, handleSearchValueChange, onBottom }
onClick={() => optionsDispatch({ type: "toggleLanguage" })} onClick={() => optionsDispatch({ type: "toggleLanguage" })}
data-testid="languageToggle" data-testid="languageToggle"
> >
<div aria-label={`language-choice-${language === "Pashto" ? "ps-to-en" : "en-to-ps"}`}> <div
aria-label={`language-choice-${
language === "Pashto" ? "ps-to-en" : "en-to-ps"
}`}
>
Ps <span className={`fa fa-arrow-${arrowDirection}`} /> En Ps <span className={`fa fa-arrow-${arrowDirection}`} /> En
</div> </div>
</button> </button>
); );
} };
const SearchTypeToggle = ({ searchType }: { searchType: SearchType }) => { const SearchTypeToggle = ({ searchType }: { searchType: SearchType }) => {
const icon = (searchType === "alphabetical") ? "book" : "bolt"; const icon = searchType === "alphabetical" ? "book" : "bolt";
return ( return (
<button <button
className="btn btn-outline-secondary" className="btn btn-outline-secondary"
onClick={() => optionsDispatch({ type: "toggleSearchType" })} onClick={() => optionsDispatch({ type: "toggleSearchType" })}
data-testid="searchTypeToggle" data-testid="searchTypeToggle"
> title="toggle alphabetical/smart search"
<span className={`fa fa-${icon}`} /> >
</button> <span className={`fa fa-${icon}`} />
</button>
); );
}; };
const placeholder = (state.options.searchType === "alphabetical" && state.options.language === "Pashto") const placeholder =
? "Browse alphabetically" state.options.searchType === "alphabetical" &&
: `Search ${state.options.language === "Pashto" ? "Pashto" : "English"}`; state.options.language === "Pashto"
? "Browse alphabetically"
: `Search ${state.options.language === "Pashto" ? "Pashto" : "English"}`;
return ( return (
<nav className={`navbar bg-light${onBottom ? "" : " fixed-top"}`} style={{ zIndex: 50, width: "100%" }}> <nav
className={`navbar bg-light${onBottom ? "" : " fixed-top"}`}
style={{ zIndex: 50, width: "100%" }}
>
<div className="form-inline my-1 my-lg-1"> <div className="form-inline my-1 my-lg-1">
<div className="input-group"> <div className="input-group">
<input <input
type="text" type="text"
style={{ borderRight: "0px", zIndex: 200 }} style={{ borderRight: "0px", zIndex: 200 }}
placeholder={placeholder} placeholder={placeholder}
value={state.searchValue} value={state.searchValue}
onChange={(e) => { onChange={(e) => {
handleSearchValueChange(e.target.value); handleSearchValueChange(e.target.value);
}} }}
onBlur={e => { onBlur={(e) => {
// don't loose focus/cursor if clicking on a word/star etc if searchBarStickyFocus is enabled // don't loose focus/cursor if clicking on a word/star etc if searchBarStickyFocus is enabled
if (state.options.searchBarStickyFocus && e.relatedTarget === null) { if (
state.options.searchBarStickyFocus &&
e.relatedTarget === null
) {
e.target.focus(); e.target.focus();
} }
}} }}
@ -104,27 +118,33 @@ const SearchBar = ({ state, optionsDispatch, handleSearchValueChange, onBottom }
/> />
<span className="input-group-append"> <span className="input-group-append">
<span <span
className={`btn btn-outline-secondary${!state.searchValue ? " unclickable" : ""} clear-search-button border-left-0 border`} className={`btn btn-outline-secondary${
!state.searchValue ? " unclickable" : ""
} clear-search-button border-left-0 border`}
style={{ borderRadius: 0 }} style={{ borderRadius: 0 }}
onClick={state.searchValue ? () => { onClick={
handleSearchValueChange(""); state.searchValue
// keep the focus on the input after pressing the X ? () => {
inputRef.current && inputRef.current.focus(); handleSearchValueChange("");
} : undefined} // keep the focus on the input after pressing the X
inputRef.current && inputRef.current.focus();
}
: undefined
}
data-testid="clearButton" data-testid="clearButton"
title="clear search"
> >
<i className="fa fa-times" style={!state.searchValue ? { visibility: "hidden" } : {}}></i> <i
className="fa fa-times"
style={!state.searchValue ? { visibility: "hidden" } : {}}
></i>
</span> </span>
</span> </span>
<div className="input-group-append"> <div className="input-group-append" title="toggle search language">
{state.options.language === "Pashto" && {state.options.language === "Pashto" && (
<SearchTypeToggle <SearchTypeToggle searchType={state.options.searchType} />
searchType={state.options.searchType} )}
/> {<LanguageToggle language={state.options.language} />}
}
{<LanguageToggle
language={state.options.language}
/>}
</div> </div>
</div> </div>
</div> </div>
@ -132,4 +152,4 @@ const SearchBar = ({ state, optionsDispatch, handleSearchValueChange, onBottom }
); );
}; };
export default SearchBar; export default SearchBar;

View File

@ -176,21 +176,31 @@ function IsolatedEntry({
<div <div
className="clickable mr-3" className="clickable mr-3"
onClick={() => setExploded((os) => !os)} onClick={() => setExploded((os) => !os)}
title="separate letters"
> >
<i className={`fas fa-${exploded ? "compress" : "expand"}-alt`} /> <i className={`fas fa-${exploded ? "compress" : "expand"}-alt`} />
</div> </div>
<div className="clickable mr-3" onClick={handleClipId}> <div
className="clickable mr-3"
onClick={handleClipId}
title="copy word id"
>
<i className="fas fa-tag"></i> <i className="fas fa-tag"></i>
</div> </div>
{state.user && state.user.level === "editor" && ( {state.user && state.user.level === "editor" && (
<> <>
<div className="clickable mr-3" onClick={handleClipEntry}> <div
className="clickable mr-3"
onClick={handleClipEntry}
title="copy entry data"
>
<i className="fas fa-code"></i> <i className="fas fa-code"></i>
</div> </div>
<Link to={`/edit?id=${entry.ts}`} className="plain-link"> <Link to={`/edit?id=${entry.ts}`} className="plain-link">
<div <div
className="clickable mr-3" className="clickable mr-3"
data-testid="finalEditEntryButton" data-testid="finalEditEntryButton"
title="edit entry"
> >
<i className="fa fa-gavel" /> <i className="fa fa-gavel" />
</div> </div>
@ -203,12 +213,14 @@ function IsolatedEntry({
className="clickable mr-3" className="clickable mr-3"
data-testid="editEntryButton" data-testid="editEntryButton"
onClick={() => setEditing((os) => !os)} onClick={() => setEditing((os) => !os)}
title="suggest edit"
> >
<i className="fa fa-pen" /> <i className="fa fa-pen" />
</div> </div>
{wordlistEnabled(state.user) && ( {wordlistEnabled(state.user) && (
<div <div
className="clickable" className="clickable"
title="add to wordlist"
data-testid={ data-testid={
wordlistWord ? "fullStarButton" : "emptyStarButton" wordlistWord ? "fullStarButton" : "emptyStarButton"
} }

View File

@ -92,6 +92,7 @@ function Results({
: "" : ""
}`} }`}
onClick={startSuggestion} onClick={startSuggestion}
title="create entry suggestion"
> >
<i className="fas fa-plus" style={{ padding: "3px" }} /> <i className="fas fa-plus" style={{ padding: "3px" }} />
</button> </button>
@ -107,6 +108,7 @@ function Results({
: "" : ""
}`} }`}
onClick={handleInflectionSearch} onClick={handleInflectionSearch}
title="search in inflections/conjugations"
> >
<i className={inflectionSearchIcon} style={{ padding: "3px" }} /> <i className={inflectionSearchIcon} style={{ padding: "3px" }} />
</button> </button>