Skip to content

Commit

Permalink
Like songs
Browse files Browse the repository at this point in the history
  • Loading branch information
tonygoldcrest committed Feb 4, 2024
1 parent 1e42f66 commit 92afd61
Show file tree
Hide file tree
Showing 13 changed files with 167 additions and 33 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module.exports = {
'react/require-default-props': 'off',
'react/no-array-index-key': 'off',
'no-bitwise': 'off',
camelcase: 'off',
},
parserOptions: {
ecmaVersion: 2022,
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"@tanstack/react-virtual": "^3.0.2",
Expand All @@ -97,6 +98,7 @@
"react-dom": "^18.2.0",
"react-router-dom": "^6.16.0",
"styled-components": "^6.1.8",
"use-debounce": "^10.0.0",
"vexflow": "^4.2.3"
},
"devDependencies": {
Expand Down
4 changes: 4 additions & 0 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ ipcMain.on('rescan-songs', async (event) => {
});
});

ipcMain.on('like-song', async (event, id, liked) => {
store.set(`songs.${id}.liked`, liked);
});

if (process.env.NODE_ENV === 'production') {
const sourceMapSupport = require('source-map-support');
sourceMapSupport.install();
Expand Down
6 changes: 5 additions & 1 deletion src/main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
/* eslint no-unused-vars: off */
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';

export type Channels = 'load-song-list' | 'load-song' | 'rescan-songs';
export type Channels =
| 'load-song-list'
| 'load-song'
| 'rescan-songs'
| 'like-song';

const electronHandler = {
ipcRenderer: {
Expand Down
4 changes: 2 additions & 2 deletions src/midi-parser/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export interface RenderData {
measure: Measure;
}

const STAVE_WIDTH = 400;
const STAVE_PER_ROW = 3;
const STAVE_WIDTH = 600;
const STAVE_PER_ROW = 2;

export function renderMusic(
elementRef: React.RefObject<HTMLDivElement>,
Expand Down
4 changes: 3 additions & 1 deletion src/renderer/components/SongList/SongList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { SongData } from '../../../types';
export interface SongListProps {
songList: SongData[];
className?: string;
onLikeChange: (id: string, liked: boolean) => void;
}

export function SongList({ songList, className }: SongListProps) {
export function SongList({ songList, className, onLikeChange }: SongListProps) {
const parentRef = useRef<HTMLDivElement>(null);

const rowVirtualizer = useVirtualizer({
Expand All @@ -31,6 +32,7 @@ export function SongList({ songList, className }: SongListProps) {
return (
<SongListItem
songData={songData}
onLikeChange={onLikeChange}
key={songData.id}
style={{
height: `${virtualItem.size}px`,
Expand Down
32 changes: 31 additions & 1 deletion src/renderer/components/SongListItem/SongListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { CSSProperties } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {
faChartSimple,
faHeart as faHeartSolid,
} from '@fortawesome/free-solid-svg-icons';
import { faHeart } from '@fortawesome/free-regular-svg-icons';
import {
AdditionalInfo,
Album,
Artist,
Like,
Info,
MainInfo,
Name,
Expand All @@ -15,11 +22,13 @@ import { SongData } from '../../../types';
export interface SongListItemProps {
songData: SongData;
style: CSSProperties;
onLikeChange: (id: string, liked: boolean) => void;
}

export function SongListItem({
songData: { albumCover, id, name, artist, charter },
songData: { albumCover, id, name, artist, charter, diff_drums, liked },
style,
onLikeChange,
}: SongListItemProps) {
return (
<Wrapper to={{ pathname: `/${id}` }} style={style}>
Expand All @@ -35,7 +44,28 @@ export function SongListItem({
<Value>{charter.replace(/<\S+?>/g, '')}</Value>
</Info>
)}
{diff_drums && (
<Info>
<Parameter>
<FontAwesomeIcon size="lg" icon={faChartSimple} />
</Parameter>
<Value>{diff_drums}</Value>
</Info>
)}
</AdditionalInfo>
<Like
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onLikeChange(id, !liked);
}}
>
{liked ? (
<FontAwesomeIcon size="lg" icon={faHeartSolid} />
) : (
<FontAwesomeIcon size="lg" icon={faHeart} />
)}
</Like>
</Wrapper>
);
}
24 changes: 24 additions & 0 deletions src/renderer/components/SongListItem/styles.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

Check failure on line 3 in src/renderer/components/SongListItem/styles.ts

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

'FontAwesomeIcon' is defined but never used
import { theme } from '../../theme';

export const Wrapper = styled(Link)`
Expand Down Expand Up @@ -44,7 +45,17 @@ export const Name = styled.div`
export const Artist = styled.div``;

export const AdditionalInfo = styled.div`
margin-left: 40px;
display: flex;
flex-flow: column;
align-items: flex-end;
align-self: center;
margin-left: auto;
margin-right: 10px;
& > * + * {
margin-top: 5px;
}
`;

export const Info = styled.div`
Expand All @@ -61,3 +72,16 @@ export const Value = styled.div`
margin-left: 5px;
color: ${theme.color.text.secondary};
`;

export const Like = styled.button`
align-self: start;
background: none;
border: 0;
padding: 5px;
border-radius: 50%;
cursor: pointer;
&:hover {
box-shadow: ${theme.boxShadow.soft};
}
`;
3 changes: 2 additions & 1 deletion src/renderer/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface AudioFile {
name: string;
src: string;
element: HTMLAudioElement[];
elements: HTMLAudioElement[];
volume: number;
}
25 changes: 23 additions & 2 deletions src/renderer/views/SelectSongView/SelectSongView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ export function SelectSongView() {

const filteredSongList = useMemo(() => {
if (!nameFilter) {
return songList.sort((a, b) => a.name.localeCompare(b.name));
return songList.sort(
(a, b) =>
+(b.liked ?? 0) - +(a.liked ?? 0) || a.name.localeCompare(b.name),
);
}

const fuseOptions = {
Expand All @@ -53,7 +56,25 @@ export function SelectSongView() {
/>
</Header>
<SongListContainer>
<SongList songList={filteredSongList} />
<SongList
songList={filteredSongList}
onLikeChange={(id, liked) => {
const song = songList.find((s) => s.id === id);

if (!song) {
return;
}
window.electron.ipcRenderer.sendMessage('like-song', id, liked);

setSongList([
...songList.filter((s) => s.id !== id),
{
...song,
liked,
},
]);
}}
/>
</SongListContainer>
<SongViewOverlay>
<Outlet />
Expand Down
82 changes: 57 additions & 25 deletions src/renderer/views/SongView/SongView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';

import { Button, Layout, Slider, Switch } from 'antd';
import { useNavigate, useParams } from 'react-router-dom';
Expand All @@ -10,6 +10,7 @@ import {
faPause,
faPlay,
} from '@fortawesome/free-solid-svg-icons';
import { useDebouncedCallback } from 'use-debounce';
import {
FullHeightLayout,
PlaybackContainer,
Expand Down Expand Up @@ -55,14 +56,16 @@ export function SongView() {

const audios = otherTracks.map((file) => ({
...file,
element: [new Audio(file.src)],
elements: [new Audio(file.src)],
volume: 100,
}));

if (drumsAudio.length !== 0) {
audios.push({
src: '',
element: drumsAudio,
elements: drumsAudio,
name: 'drums',
volume: 0,
});
}

Expand All @@ -81,7 +84,7 @@ export function SongView() {
return undefined;
}

const audioElement = audioFiles[0].element[0];
const audioElement = audioFiles[0].elements[0];

const playbackEventListener = () => {
setCurrentPlayback(audioElement.currentTime);
Expand All @@ -91,19 +94,32 @@ export function SongView() {
setAudioDuration(audioElement.duration);
};

const audioPolling = setInterval(playbackEventListener, 30);
const endedListener = () => {
setIsPlaying(false);
};

const audioPolling = setInterval(playbackEventListener, 20);
audioElement.addEventListener('canplaythrough', readyEventListener);

audioElement.addEventListener('ended', endedListener);

return () => {
clearInterval(audioPolling);
audioElement.removeEventListener('canplaythrough', readyEventListener);
audioElement.removeEventListener('ended', endedListener);
};
}, [audioFiles]);

useEffect(() => {
audioFiles.forEach((audio) => {
audio.elements.forEach((element) => {
element.volume = audio.volume / 100;
});
});

return () => {
audioFiles.forEach((audio) => {
audio.element.forEach((drumAudio) => {
audio.elements.forEach((drumAudio) => {
drumAudio.pause();
});
});
Expand All @@ -117,34 +133,48 @@ export function SongView() {

if (isPlaying) {
audioFiles.forEach((audio) => {
audio.element.forEach((drumAudio) => {
audio.elements.forEach((drumAudio) => {
drumAudio.play();
});
});
} else {
audioFiles.forEach((audio) => {
audio.element.forEach((drumAudio) => {
audio.elements.forEach((drumAudio) => {
drumAudio.pause();
});
});
}
}, [audioFiles, isPlaying]);

const volumeSliders = audioFiles.map((file, index) => {
return (
<div key={index}>
<div>{file.name}</div>
<Slider
defaultValue={100}
onChange={(value) => {
file.element.forEach((drumAudio) => {
drumAudio.volume = value / 100;
});
}}
/>
</div>
);
});
const setVolume = useDebouncedCallback((file, volume) => {
const otherFiles = audioFiles.filter((f) => f !== file);

setAudioFiles([
...otherFiles,
{
...file,
volume,
},
]);
}, 300);

const volumeSliders = useMemo(() => {
return audioFiles
.sort((a, b) => a.name.localeCompare(b.name))
.map((file) => {
return (
<div key={file.name}>
<div>{file.name}</div>
<Slider
defaultValue={file.volume}
onChange={(value) => {
setVolume(file, value);
}}
/>
</div>
);
});
}, [audioFiles, setVolume]);

return (
<FullHeightLayout>
Expand Down Expand Up @@ -227,7 +257,7 @@ export function SongView() {
onChange={(value) => {
const time = (value / 100) * audioDuration;
audioFiles.forEach((audioFile) => {
audioFile.element.forEach((element) => {
audioFile.elements.forEach((element) => {
element.currentTime = time;
});
});
Expand All @@ -249,10 +279,12 @@ export function SongView() {
showBarNumbers={showBarNumbers}
onSelectMeasure={(time) => {
audioFiles.forEach((audioFile) => {
audioFile.element.forEach((element) => {
audioFile.elements.forEach((element) => {
element.currentTime = time;
});
});

setIsPlaying(true);
}}
/>
</SheetMusicView>
Expand Down
Loading

0 comments on commit 92afd61

Please sign in to comment.