added audio download button and tooltips for various buttons etc
This commit is contained in:
parent
3f919f609a
commit
cfeaec9550
|
@ -47,6 +47,7 @@ function Entry({
|
|||
<i
|
||||
onClick={handlePlayStorageAudio}
|
||||
className="clickable ml-2 fas fa-volume-down px-1"
|
||||
title="play audio"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -12,6 +12,7 @@ export function EntryAudioDisplay({
|
|||
opts: T.TextOptions;
|
||||
user: LingdocsUser | undefined;
|
||||
}) {
|
||||
const audioPath = getAudioPath(entry.ts);
|
||||
if (!entry.a) {
|
||||
return null;
|
||||
}
|
||||
|
@ -24,23 +25,54 @@ export function EntryAudioDisplay({
|
|||
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 (
|
||||
<figure>
|
||||
<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>
|
||||
<audio
|
||||
controls
|
||||
controlsList="nofullscreen"
|
||||
src={getAudioPath(entry.ts)}
|
||||
preload="auto"
|
||||
onPlay={handlePlay}
|
||||
>
|
||||
<a href={getAudioPath(entry.ts)}>
|
||||
<div className="d-flex align-items-center">
|
||||
<audio
|
||||
controls
|
||||
controlsList="nofullscreen"
|
||||
src={audioPath}
|
||||
preload="auto"
|
||||
onPlay={handlePlay}
|
||||
>
|
||||
{/* <a href={getAudioPath(entry.ts)}>
|
||||
Download audio for{" "}
|
||||
<InlinePs opts={opts}>{{ p: entry.p, f: entry.f }}</InlinePs>
|
||||
</a>
|
||||
</audio>
|
||||
</a > */}
|
||||
</audio>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDownload}
|
||||
className="ml-2 btn btn-light"
|
||||
title="download audio"
|
||||
>
|
||||
<i className="fas fa-download"></i>
|
||||
</button>
|
||||
</div>
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,17 +9,18 @@
|
|||
import Mousetrap from "mousetrap";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { State } from "../types/dictionary-types";
|
||||
import {
|
||||
OptionsAction,
|
||||
Language,
|
||||
SearchType,
|
||||
} from "../types/dictionary-types";
|
||||
import { OptionsAction, Language, SearchType } from "../types/dictionary-types";
|
||||
|
||||
const SearchBar = ({ state, optionsDispatch, handleSearchValueChange, onBottom }: {
|
||||
state: State
|
||||
optionsDispatch: (action: OptionsAction) => void,
|
||||
handleSearchValueChange: (searchValue: string) => void,
|
||||
onBottom?: boolean,
|
||||
const SearchBar = ({
|
||||
state,
|
||||
optionsDispatch,
|
||||
handleSearchValueChange,
|
||||
onBottom,
|
||||
}: {
|
||||
state: State;
|
||||
optionsDispatch: (action: OptionsAction) => void;
|
||||
handleSearchValueChange: (searchValue: string) => void;
|
||||
onBottom?: boolean;
|
||||
}) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
useEffect(() => {
|
||||
|
@ -34,8 +35,8 @@ const SearchBar = ({ state, optionsDispatch, handleSearchValueChange, onBottom }
|
|||
return () => {
|
||||
window.removeEventListener("focus", onFocus);
|
||||
Mousetrap.unbind(["shift+space"]);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
};
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
function onFocus() {
|
||||
if (["/", "/search"].includes(window.location.pathname)) {
|
||||
|
@ -50,43 +51,56 @@ const SearchBar = ({ state, optionsDispatch, handleSearchValueChange, onBottom }
|
|||
onClick={() => optionsDispatch({ type: "toggleLanguage" })}
|
||||
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
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
};
|
||||
const SearchTypeToggle = ({ searchType }: { searchType: SearchType }) => {
|
||||
const icon = (searchType === "alphabetical") ? "book" : "bolt";
|
||||
const icon = searchType === "alphabetical" ? "book" : "bolt";
|
||||
return (
|
||||
<button
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={() => optionsDispatch({ type: "toggleSearchType" })}
|
||||
data-testid="searchTypeToggle"
|
||||
>
|
||||
<span className={`fa fa-${icon}`} />
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={() => optionsDispatch({ type: "toggleSearchType" })}
|
||||
data-testid="searchTypeToggle"
|
||||
title="toggle alphabetical/smart search"
|
||||
>
|
||||
<span className={`fa fa-${icon}`} />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const placeholder = (state.options.searchType === "alphabetical" && state.options.language === "Pashto")
|
||||
? "Browse alphabetically"
|
||||
: `Search ${state.options.language === "Pashto" ? "Pashto" : "English"}`;
|
||||
const placeholder =
|
||||
state.options.searchType === "alphabetical" &&
|
||||
state.options.language === "Pashto"
|
||||
? "Browse alphabetically"
|
||||
: `Search ${state.options.language === "Pashto" ? "Pashto" : "English"}`;
|
||||
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="input-group">
|
||||
<input
|
||||
type="text"
|
||||
style={{ borderRight: "0px", zIndex: 200 }}
|
||||
style={{ borderRight: "0px", zIndex: 200 }}
|
||||
placeholder={placeholder}
|
||||
value={state.searchValue}
|
||||
onChange={(e) => {
|
||||
handleSearchValueChange(e.target.value);
|
||||
}}
|
||||
onBlur={e => {
|
||||
onBlur={(e) => {
|
||||
// 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();
|
||||
}
|
||||
}}
|
||||
|
@ -104,27 +118,33 @@ const SearchBar = ({ state, optionsDispatch, handleSearchValueChange, onBottom }
|
|||
/>
|
||||
<span className="input-group-append">
|
||||
<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 }}
|
||||
onClick={state.searchValue ? () => {
|
||||
handleSearchValueChange("");
|
||||
// keep the focus on the input after pressing the X
|
||||
inputRef.current && inputRef.current.focus();
|
||||
} : undefined}
|
||||
onClick={
|
||||
state.searchValue
|
||||
? () => {
|
||||
handleSearchValueChange("");
|
||||
// keep the focus on the input after pressing the X
|
||||
inputRef.current && inputRef.current.focus();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
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>
|
||||
<div className="input-group-append">
|
||||
{state.options.language === "Pashto" &&
|
||||
<SearchTypeToggle
|
||||
searchType={state.options.searchType}
|
||||
/>
|
||||
}
|
||||
{<LanguageToggle
|
||||
language={state.options.language}
|
||||
/>}
|
||||
<div className="input-group-append" title="toggle search language">
|
||||
{state.options.language === "Pashto" && (
|
||||
<SearchTypeToggle searchType={state.options.searchType} />
|
||||
)}
|
||||
{<LanguageToggle language={state.options.language} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -132,4 +152,4 @@ const SearchBar = ({ state, optionsDispatch, handleSearchValueChange, onBottom }
|
|||
);
|
||||
};
|
||||
|
||||
export default SearchBar;
|
||||
export default SearchBar;
|
||||
|
|
|
@ -176,21 +176,31 @@ function IsolatedEntry({
|
|||
<div
|
||||
className="clickable mr-3"
|
||||
onClick={() => setExploded((os) => !os)}
|
||||
title="separate letters"
|
||||
>
|
||||
<i className={`fas fa-${exploded ? "compress" : "expand"}-alt`} />
|
||||
</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>
|
||||
</div>
|
||||
{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>
|
||||
</div>
|
||||
<Link to={`/edit?id=${entry.ts}`} className="plain-link">
|
||||
<div
|
||||
className="clickable mr-3"
|
||||
data-testid="finalEditEntryButton"
|
||||
title="edit entry"
|
||||
>
|
||||
<i className="fa fa-gavel" />
|
||||
</div>
|
||||
|
@ -203,12 +213,14 @@ function IsolatedEntry({
|
|||
className="clickable mr-3"
|
||||
data-testid="editEntryButton"
|
||||
onClick={() => setEditing((os) => !os)}
|
||||
title="suggest edit"
|
||||
>
|
||||
<i className="fa fa-pen" />
|
||||
</div>
|
||||
{wordlistEnabled(state.user) && (
|
||||
<div
|
||||
className="clickable"
|
||||
title="add to wordlist"
|
||||
data-testid={
|
||||
wordlistWord ? "fullStarButton" : "emptyStarButton"
|
||||
}
|
||||
|
|
|
@ -92,6 +92,7 @@ function Results({
|
|||
: ""
|
||||
}`}
|
||||
onClick={startSuggestion}
|
||||
title="create entry suggestion"
|
||||
>
|
||||
<i className="fas fa-plus" style={{ padding: "3px" }} />
|
||||
</button>
|
||||
|
@ -107,6 +108,7 @@ function Results({
|
|||
: ""
|
||||
}`}
|
||||
onClick={handleInflectionSearch}
|
||||
title="search in inflections/conjugations"
|
||||
>
|
||||
<i className={inflectionSearchIcon} style={{ padding: "3px" }} />
|
||||
</button>
|
||||
|
|
Loading…
Reference in New Issue