Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to toggle columns #648

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
10 changes: 2 additions & 8 deletions .stylelintrc
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
{
"extends": [
"stylelint-config-standard",
"stylelint-config-css-modules"
],
"ignoreFiles": [
"./src/**/*.tsx",
"./src/**/*.ts"
],
"extends": ["stylelint-config-standard", "stylelint-config-css-modules"],
"ignoreFiles": ["./src/**/*.tsx", "./src/**/*.ts"],
"rules": {
"no-descending-specificity": null,
"string-quotes": "single",
Expand Down
3 changes: 1 addition & 2 deletions src/renderer/components/Header/Header.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@
border-bottom: 1px solid var(--border-color);
background-color: var(--header-bg);
color: var(--header-color);
padding: 0 10px;
padding: 5px;
display: flex;
align-items: center;
justify-content: space-between;
height: 50px;
flex: 0 0 auto;

/* Draggable region (zone able to move the window) */
Expand Down
33 changes: 18 additions & 15 deletions src/renderer/components/Playlists/Playlist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,25 @@ const Playlist: React.FC = () => {
const params = useParams();
const playlistId = params.playlistId;

const { tracks, trackPlayingId, playerStatus, playlists, currentPlaylist } = useSelector((state: RootState) => {
const { library, player, playlists } = state;
const { tracks, trackPlayingId, playerStatus, playlists, currentPlaylist, libraryLayoutSettings } = useSelector(
(state: RootState) => {
const { library, player, playlists } = state;
const { search, tracks } = library;

const { search, tracks } = library;
const filteredTracks = filterTracks(tracks.playlist, search);
const filteredTracks = filterTracks(tracks.playlist, search);
const currentPlaylist = playlists.list.find((p) => p._id === playlistId);

const currentPlaylist = playlists.list.find((p) => p._id === playlistId);

return {
playlists: playlists.list,
currentPlaylist,
tracks: filteredTracks,
playerStatus: player.playerStatus,
trackPlayingId:
player.queue.length > 0 && player.queueCursor !== null ? player.queue[player.queueCursor]._id : null,
};
});
return {
libraryLayoutSettings: library.libraryLayoutSettings,
playlists: playlists.list,
currentPlaylist,
tracks: filteredTracks,
playerStatus: player.playerStatus,
trackPlayingId:
player.queue.length > 0 && player.queueCursor !== null ? player.queue[player.queueCursor]._id : null,
};
}
);

useEffect(() => {
if (playlistId) {
Expand Down Expand Up @@ -84,6 +86,7 @@ const Playlist: React.FC = () => {

return (
<TracksList
layout={libraryLayoutSettings}
type='playlist'
reorderable={true}
onReorder={onReorder}
Expand Down
4 changes: 3 additions & 1 deletion src/renderer/components/TrackRow/TrackRow.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 24px;
line-height: 15px;
}

.track {
position: relative;
display: flex;
outline: none;
height: 30px;
padding: 5px;

&:nth-child(odd) {
background-color: var(--tracks-bg-odd);
Expand Down
41 changes: 34 additions & 7 deletions src/renderer/components/TrackRow/TrackRow.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React from 'react';
import cx from 'classnames';

import PlayingIndicator from '../PlayingIndicator/PlayingIndicator';
import { LibraryLayoutSettings } from '../../store/actions/LibraryActions';
import { parseDuration } from '../../lib/utils';
import { TrackModel } from '../../../shared/types/museeks';

import PlayingIndicator from '../PlayingIndicator/PlayingIndicator';
import cellStyles from '../TracksListHeader/TracksListHeader.module.css';
import styles from './TrackRow.module.css';

Expand All @@ -24,6 +25,8 @@ interface Props {
onDragOver?: (trackId: string, position: 'above' | 'below') => void;
onDragEnd?: () => void;
onDrop?: (targetTrackId: string, position: 'above' | 'below') => void;

layout: LibraryLayoutSettings;
}

interface State {
Expand Down Expand Up @@ -103,7 +106,7 @@ export default class TrackRow extends React.PureComponent<Props, State> {
};

render() {
const { track, selected, reordered, draggable } = this.props;
const { track, selected, reordered, draggable, layout } = this.props;
const { reorderOver, reorderPosition } = this.state;

const trackClasses = cx(styles.track, {
Expand All @@ -114,6 +117,34 @@ export default class TrackRow extends React.PureComponent<Props, State> {
[styles.isBelow]: reorderPosition === 'below',
});

const sorts: [boolean, React.ReactElement][] = [
[
layout.visibility.includes('title'),
<div className={`${styles.cell} ${cellStyles.cellTrack}`}>{track.title}</div>,
],
[
layout.visibility.includes('duration'),
<div className={`${styles.cell} ${cellStyles.cellDuration}`}>{parseDuration(track.duration)}</div>,
],
[
layout.visibility.includes('artist'),
<div className={`${styles.cell} ${cellStyles.cellArtist}`}>{track.artist.sort().join(', ')}</div>,
],
[
layout.visibility.includes('album'),
<div className={`${styles.cell} ${cellStyles.cellAlbum}`}>{track.album}</div>,
],
[
layout.visibility.includes('genre'),
<div className={`${styles.cell} ${cellStyles.cellGenre}`}>{track.genre.join(', ')}</div>,
],
];

const rows: React.ReactElement[] = [];
sorts.forEach((element) => {
if (element[0]) rows.push(element[1]);
});

return (
<div
className={trackClasses}
Expand All @@ -140,11 +171,7 @@ export default class TrackRow extends React.PureComponent<Props, State> {
<div className={`${styles.cell} ${cellStyles.cellTrackPlaying}`}>
{this.props.isPlaying ? <PlayingIndicator /> : null}
</div>
<div className={`${styles.cell} ${cellStyles.cellTrack}`}>{track.title}</div>
<div className={`${styles.cell} ${cellStyles.cellDuration}`}>{parseDuration(track.duration)}</div>
<div className={`${styles.cell} ${cellStyles.cellArtist}`}>{track.artist.sort().join(', ')}</div>
<div className={`${styles.cell} ${cellStyles.cellAlbum}`}>{track.album}</div>
<div className={`${styles.cell} ${cellStyles.cellGenre}`}>{track.genre.join(', ')}</div>
{...rows}
</div>
);
}
Expand Down
7 changes: 5 additions & 2 deletions src/renderer/components/TracksList/TracksList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ interface Props {
playlists: PlaylistModel[];
currentPlaylist?: string;
reorderable?: boolean;
layout: LibraryActions.LibraryLayoutSettings;
onReorder?: (playlistId: string, tracksIds: string[], targetTrackId: string, position: 'above' | 'below') => void;
}

const TracksList: React.FC<Props> = (props) => {
const { tracks, type, trackPlayingId, reorderable, currentPlaylist, onReorder, playerStatus, playlists } = props;
const { tracks, type, trackPlayingId, reorderable, currentPlaylist, onReorder, playerStatus, playlists, layout } =
props;

const [tilesScrolled, setTilesScrolled] = useState(0);
const [selected, setSelected] = useState<string[]>([]);
Expand Down Expand Up @@ -492,6 +494,7 @@ const TracksList: React.FC<Props> = (props) => {

return (
<TrackRow
layout={layout}
selected={selected.includes(track._id)}
track={track}
isPlaying={trackPlayingId === track._id}
Expand Down Expand Up @@ -540,7 +543,7 @@ const TracksList: React.FC<Props> = (props) => {
return (
<div className={styles.tracksList}>
<KeyBinding onKey={onKey} preventInputConflict />
<TracksListHeader enableSort={type === 'library'} />
<TracksListHeader enableSort={type === 'library'} layout={layout} />
<CustomScrollbar className={styles.tracksListBody} onScroll={onScroll}>
<div className={styles.tiles} role='listbox' style={{ height: tracks.length * ROW_HEIGHT }}>
{trackTiles}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@

.cellArtist,
.cellAlbum,
.cellTitle,
.cellSection,
.cellGenre {
width: 20%;
}

.cellSection {
width: 20%;
height: 45px;
}
110 changes: 100 additions & 10 deletions src/renderer/components/TracksListHeader/TracksListHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import React from 'react';
import { Menu } from '@electron/remote';
import { connect } from 'react-redux';
import electron from 'electron';

import { LibraryLayoutSettings, set_context_state } from '../../store/actions/LibraryActions';
import TracksListHeaderCell from '../TracksListHeaderCell/TracksListHeaderCell';

import { SortBy, SortOrder } from '../../../shared/types/museeks';
import { RootState } from '../../store/reducers';
import { LibrarySort } from '../../store/reducers/library';

import styles from './TracksListHeader.module.css';

interface OwnProps {
enableSort: boolean;
layout: LibraryLayoutSettings;
}

interface InjectedProps {
Expand All @@ -19,6 +22,11 @@ interface InjectedProps {

type Props = OwnProps & InjectedProps;

const LAYOUT_LISTS = ['title', 'duration', 'album', 'artist', 'genre'];
const capitalize = (str: string) => {
return str.toUpperCase()[0] + str.substring(1);
};

class TracksListHeader extends React.Component<Props> {
static getIcon = (sort: LibrarySort | undefined, sortType: SortBy) => {
if (sort && sort.by === sortType) {
Expand All @@ -33,42 +41,124 @@ class TracksListHeader extends React.Component<Props> {
return null;
};

// questionable?
constructor(props: Props, state: LibraryLayoutSettings) {
super(props, state);
}

showContextMenu(_e: React.MouseEvent, id: string) {
const template: electron.MenuItemConstructorOptions[] = [
{
label: `Selected: ${capitalize(id)}`,
enabled: false,
},
];

LAYOUT_LISTS.forEach((tag) => {
template.push({
type: 'checkbox',
label: capitalize(tag),
checked: this.props.layout.visibility.includes(tag),
click: () => {
// toggles the existence of props.layout.visibility[tag]
const visibility = this.props.layout.visibility;
if (visibility.includes(tag)) {
set_context_state({
visibility: visibility.filter((value) => value !== tag),
});
} else {
set_context_state({
visibility: [...visibility, tag],
});
}
},
});
});

const context = Menu.buildFromTemplate(template);

context.popup({}); // Let it appear
}

render() {
const { enableSort, sort } = this.props;
const { enableSort, sort, layout } = this.props;

return (
<div className={styles.tracksListHeader}>
<TracksListHeaderCell className={styles.cellTrackPlaying} title='&nbsp;' />
const sorts: [boolean, React.ReactElement][] = [
[
layout.visibility.includes('title'),
<TracksListHeaderCell
onContextMenu={(e) => this.showContextMenu(e, 'title')}
className={styles.cellTrack}
title='Title'
sortBy={enableSort ? SortBy.TITLE : null}
icon={TracksListHeader.getIcon(sort, SortBy.TITLE)}
/>
layout={this.props.layout}
key='Title'
/>,
],
[
layout.visibility.includes('duration'),
<TracksListHeaderCell
onContextMenu={(e) => this.showContextMenu(e, 'duration')}
className={styles.cellDuration}
title='Duration'
sortBy={enableSort ? SortBy.DURATION : null}
icon={TracksListHeader.getIcon(sort, SortBy.DURATION)}
/>
layout={this.props.layout}
key='Duration'
/>,
],
[
layout.visibility.includes('artist'),
<TracksListHeaderCell
onContextMenu={(e) => this.showContextMenu(e, 'artist')}
className={styles.cellArtist}
title='Artist'
sortBy={enableSort ? SortBy.ARTIST : null}
icon={TracksListHeader.getIcon(sort, SortBy.ARTIST)}
/>
layout={this.props.layout}
key='Artist'
/>,
],
[
layout.visibility.includes('album'),
<TracksListHeaderCell
onContextMenu={(e) => this.showContextMenu(e, 'album')}
className={styles.cellAlbum}
title='Album'
sortBy={enableSort ? SortBy.ALBUM : null}
icon={TracksListHeader.getIcon(sort, SortBy.ALBUM)}
/>
layout={this.props.layout}
key='Album'
/>,
],
[
layout.visibility.includes('genre'),
<TracksListHeaderCell
onContextMenu={(e) => this.showContextMenu(e, 'genre')}
className={styles.cellGenre}
title='Genre'
sortBy={enableSort ? SortBy.GENRE : null}
icon={TracksListHeader.getIcon(sort, SortBy.GENRE)}
/>
layout={this.props.layout}
key='Genre'
/>,
],
];

const headers: React.ReactElement[] = [];
sorts.forEach((element) => {
if (element[0]) headers.push(element[1]);
});

return (
<div
className={styles.tracksListHeader}
onContextMenu={headers.length === 0 ? (e) => this.showContextMenu(e, 'background') : undefined}
>
{' '}
<TracksListHeaderCell className={styles.cellTrackPlaying} title='&nbsp;' layout={this.props.layout} />
{...headers}
</div>
);
}
Expand Down
Loading