Skip to content

Commit 941956a

Browse files
committed
add functional volume control element to menubar
1 parent 0f6a141 commit 941956a

File tree

10 files changed

+117
-13
lines changed

10 files changed

+117
-13
lines changed

client/admin/FolderAdmin.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
useSensors
1515
} from '@dnd-kit/core';
1616
import {restrictToWindowEdges} from '@dnd-kit/modifiers';
17-
import {useCookie, useSessionStorage} from '../common/Storage';
17+
import {useAudioSettings, useCookie, useSessionStorage} from '../common/Storage';
1818
import FileUploader from '../common/FileUploader';
1919
import FinderSettings from './FinderSettings';
2020
import FolderTabs from './FolderTabs';
@@ -33,6 +33,8 @@ const useClipboard = useSessionStorage('filer-clipboard', []);
3333

3434
export default function FolderAdmin() {
3535
const settings = useContext(FinderSettings);
36+
const [audioSettings] = useAudioSettings();
37+
const [webAudio, setWebAudio] = useState(null);
3638
const menuBarRef = useRef(null);
3739
const folderTabsRef = useRef(null);
3840
const uploaderRef = useRef(null);
@@ -70,6 +72,18 @@ export default function FolderAdmin() {
7072
messagelist.remove();
7173
}, 5000);
7274
}
75+
76+
const context = new window.AudioContext();
77+
const gainNode = context.createGain();
78+
gainNode.connect(context.destination);
79+
gainNode.gain.value = audioSettings.volume;
80+
setWebAudio({context, gainNode});
81+
82+
return () => {
83+
if (webAudio) {
84+
webAudio.context.close();
85+
}
86+
};
7387
}, []);
7488

7589
function modifyMovement(args) {
@@ -352,6 +366,7 @@ export default function FolderAdmin() {
352366
menuBarRef={menuBarRef}
353367
folderTabsRef={folderTabsRef}
354368
layout={layout}
369+
webAudio={webAudio}
355370
clipboard={clipboard}
356371
setClipboard={setClipboard}
357372
clearClipboard={clearClipboard}
@@ -397,6 +412,7 @@ export default function FolderAdmin() {
397412
downloadFiles={downloadFiles}
398413
layout={layout}
399414
setLayout={setLayout}
415+
webAudio={webAudio}
400416
deselectAll={deselectAll}
401417
refreshColumns={refreshColumns}
402418
clipboard={clipboard}

client/admin/MenuBar.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import React, {
1010
} from 'react';
1111
import SearchField from './SearchField';
1212
import DropDownMenu from '../common/DropDownMenu';
13+
import VolumeControl from '../common/VolumeControl';
1314
import FilterByLabel from '../common/FilterByLabel';
1415
import SortingOptions from '../common/SortingOptions';
1516
import {Tooltip, TooltipContent, TooltipTrigger} from '../common/Tooltip';
@@ -349,9 +350,10 @@ const MenuBar = forwardRef((props: any, forwardedRef) => {
349350
return (
350351
// <nav aria-label={gettext("Finder List View")}>
351352
<ul role="menubar">
352-
<li className="search-field" role="menuitem" style={{marginRight: 'auto'}}>
353+
<li className="search-field" role="menuitem">
353354
<SearchField columnRefs={columnRefs} setSearchResult={setSearchResult} settings={settings}/>
354355
</li>
356+
<VolumeControl {...props} />
355357
<MenuItem aria-selected={layout === 'tiles'} onClick={() => setLayout('tiles')} tooltip={gettext("Tiles view")}>
356358
<TilesIcon/>
357359
</MenuItem>

client/common/Storage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,4 @@ export function useCookie(key, initial) : [any, (value: any) => any] {
6161

6262

6363
export const useSearchZone = initial => useCookie('django-finder-search-zone', initial);
64+
export const useAudioSettings = useSessionStorage('filer-audio-settings', {volume: 0.5});

client/common/VolumeControl.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from 'react';
2+
import {useAudioSettings} from '../common/Storage';
3+
import DropDownMenu from '../common/DropDownMenu';
4+
import SpeakerLoudIcon from '../icons/speaker-loud.svg';
5+
import SpeakerSilentIcon from '../icons/speaker-silent.svg';
6+
import SpeakerMutedicon from '../icons/speaker-muted.svg';
7+
8+
9+
export default function VolumeControl(props: any) {
10+
const [audioSettings, setAudioSettings] = useAudioSettings();
11+
12+
function handleChange(event) {
13+
const volume = parseFloat(event.target.value);
14+
setAudioSettings({volume: volume});
15+
props.webAudio.gainNode.gain.setValueAtTime(volume, props.webAudio.context.currentTime);
16+
}
17+
18+
function renderSpeakerIcon() {
19+
if (audioSettings.volume === 0.0) {
20+
return <SpeakerMutedicon/>;
21+
}
22+
if (audioSettings.volume < 0.33) {
23+
return <SpeakerSilentIcon/>;
24+
}
25+
return <SpeakerLoudIcon/>;
26+
}
27+
28+
return (
29+
<DropDownMenu
30+
icon={renderSpeakerIcon()}
31+
role="menuitem"
32+
className="volume-control"
33+
tooltip={gettext("Change volume level")}
34+
root={props.root}
35+
>
36+
<li>
37+
<label htmlFor="volume_level">{gettext("Volume level")}:</label>
38+
<input name="volume_level" type="range" min="0.0" max="1.0" step="0.001" value={audioSettings.volume} onChange={handleChange}/>
39+
</li>
40+
</DropDownMenu>
41+
);
42+
}

client/components/folderitem/Audio.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,31 @@ import React, {useEffect, useState} from 'react';
22

33

44
export default function Audio(props) {
5-
const [mouseOver, setMouseOver] = useState(false);
5+
const [audioElement, setAudioElement] = useState<HTMLAudioElement>();
66

77
useEffect(() => {
8-
if (!mouseOver || !props.sample_url)
8+
if (!props.sample_url || !props.webAudio)
99
return;
10-
const audio = new window.Audio(props.sample_url);
11-
const playPromise = audio.play();
12-
10+
const audioElement = new window.Audio(props.sample_url);
11+
const track = props.webAudio.context.createMediaElementSource(audioElement);
12+
track.connect(props.webAudio.gainNode).connect(props.webAudio.context.destination);
13+
setAudioElement(audioElement);
1314
return () => {
14-
playPromise.then(() => audio.pause());
15+
track.disconnect();
1516
};
16-
}, [mouseOver]);
17+
}, []);
18+
19+
function handleMouseOver() {
20+
if (!audioElement || !props.webAudio)
21+
return;
22+
if (props.webAudio.context.state === 'suspended') {
23+
props.webAudio.context.resume();
24+
}
25+
audioElement.play();
26+
}
1727

1828
return (
19-
<div onMouseEnter={() => setMouseOver(true)} onMouseLeave={() => setMouseOver(false)}>
29+
<div onMouseEnter={() => handleMouseOver()} onMouseLeave={() => audioElement?.pause()}>
2030
{props.children}
2131
</div>
2232
);

client/components/folderitem/Video.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1-
import React, {useRef, useEffect} from 'react';
1+
import React, {useRef, useState, useEffect} from 'react';
22
import FigureLabels from '../../common/FigureLabels';
33

44

55
export default function Video(props) {
66
const videoRef = useRef(null);
77

88
useEffect(() => {
9-
if (!(videoRef.current?.parentElement instanceof HTMLElement))
9+
if (!(videoRef.current?.parentElement instanceof HTMLElement) || !props.webAudio)
1010
return;
11+
1112
const wrapper = videoRef.current.parentElement;
1213
let timeout = null;
1314

1415
const play = () => {
1516
timeout && clearTimeout(timeout);
17+
if (props.webAudio.context.state === 'suspended') {
18+
props.webAudio.context.resume();
19+
}
1620
videoRef.current.play();
1721
};
1822

@@ -24,6 +28,9 @@ export default function Video(props) {
2428
}, 1000);
2529
};
2630

31+
const track = props.webAudio.context.createMediaElementSource(videoRef.current);
32+
track.connect(props.webAudio.gainNode).connect(props.webAudio.context.destination);
33+
2734
wrapper.addEventListener('mouseenter', play);
2835
wrapper.addEventListener('mouseleave', pause);
2936

client/icons/speaker-loud.svg

Lines changed: 3 additions & 0 deletions
Loading

client/icons/speaker-muted.svg

Lines changed: 3 additions & 0 deletions
Loading

client/icons/speaker-silent.svg

Lines changed: 3 additions & 0 deletions
Loading

client/scss/_menubar.scss

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,29 @@
102102
}
103103
}
104104

105+
&.volume-control {
106+
margin-left: 1rem;
107+
margin-right: auto;
108+
109+
ul > li {
110+
padding: 0.5rem 0.75rem;
111+
112+
label {
113+
display: block;
114+
}
115+
116+
input[type="range"] {
117+
vertical-align: initial;
118+
padding: 0;
119+
}
120+
}
121+
}
122+
105123
&[aria-haspopup="listbox"]:has(>[role~="listbox"]) {
106124
width: auto;
107125
}
108126

109127
&.sorting-options {
110-
margin-left: auto;
111128
&:not(:has(+.filter-by-label)) {
112129
margin-right: auto;
113130
}

0 commit comments

Comments
 (0)