Skip to content

Commit

Permalink
Merge pull request #58 from kkoomen/feature/library-list-view
Browse files Browse the repository at this point in the history
Implement library list view
  • Loading branch information
kkoomen authored Sep 10, 2023
2 parents cd7ed8b + 5379658 commit 696e606
Show file tree
Hide file tree
Showing 13 changed files with 449 additions and 53 deletions.
46 changes: 42 additions & 4 deletions src/components/Library/components/PaperListItem/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import InlineEdit from './../../../InlineEdit';
import { ReactComponent as TrashcanIcon } from './../../../../assets/icons/trashcan.svg';
import { store } from './../../../../store';
import { confirm } from '@tauri-apps/api/dialog';
import { VIEW_MODE } from '../../../../constants';
import { formatDate } from '../../../../helpers';

function PaperListItem(props) {
const onDeletePaper = () => {
Expand All @@ -20,8 +22,15 @@ function PaperListItem(props) {
);
};

const onClick = (e) => {
// Do not trigger the onClick when it's not the SVG
const onClickListViewItem = (e) => {
// Do not trigger the onClick when it's not a table cell.
if (e.target.nodeName.toLowerCase() !== 'td') return false;

props.onClick();
};

const onClickGridViewItem = (e) => {
// Do not trigger the onClick when it's not the SVG.
if (e.target.nodeName.toLowerCase() !== 'svg') return false;

props.onClick();
Expand All @@ -38,9 +47,36 @@ function PaperListItem(props) {
}
};

if (props.viewMode === VIEW_MODE.LIST) {
return (
<tr className={styles['paper-list-item--view-mode--list']} onClick={onClickListViewItem}>
<td>{props.index + 1}</td>
<td>
<InlineEdit defaultValue={props.paper.name} onEditDone={onEditPaperName} />
</td>
<td>{formatDate(props.paper.updatedAt)}</td>
<td>{formatDate(props.paper.createdAt)}</td>
<td className="text-align--right">
<button
className={classNames('btn', 'btn-icon', styles['paper-list-item__delete-btn'])}
onClick={onDeletePaper}
>
<TrashcanIcon />
</button>
</td>
</tr>
);
}

// Return grid view by default
return (
<div className={styles['paper-list-item__container']} onClick={onClick}>
<Paper paperId={props.paper.id} readonly />
<div
className={classNames(styles['paper-list-item__container'], {
[styles['paper-list-item--view-mode--list']]: props.viewMode === VIEW_MODE.LIST,
})}
onClick={onClickGridViewItem}
>
{props.viewMode === VIEW_MODE.GRID && <Paper paperId={props.paper.id} readonly />}
<div className={classNames('ellipsis', styles['paper-list-item__name'])}>
<InlineEdit defaultValue={props.paper.name} onEditDone={onEditPaperName} />
</div>
Expand All @@ -56,6 +92,8 @@ function PaperListItem(props) {

PaperListItem.propTypes = {
paper: PropTypes.object.isRequired,
viewMode: PropTypes.number,
index: PropTypes.number,
onClick: PropTypes.func,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,21 @@
padding-bottom: 56.25%;
}

.paper-list-item--view-mode--list {
height: 4rem;
padding: 1rem;
}

.paper-list-item--view-mode--list:hover {
cursor: pointer;
}

.paper-list-item__container:hover {
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
cursor: pointer;
}

.paper-list-item--view-mode--list .paper-list-item__delete-btn,
.paper-list-item__container:hover .paper-list-item__delete-btn {
opacity: 1;
}
Expand All @@ -34,7 +44,7 @@
padding: 1rem;
}

.paper-list-item__delete-btn {
.paper-list-item__container .paper-list-item__delete-btn {
opacity: 0;
position: absolute;
bottom: -1px;
Expand All @@ -57,6 +67,10 @@
border: 1px solid #353535;
background-color: #292929;
}

.paper-list-item--view-mode--list:hover {
background-color: #454545;
}
}

@media (prefers-color-scheme: light) {
Expand All @@ -69,4 +83,8 @@
border: 1px solid #d1d1d1;
background-color: #fff;
}

.paper-list-item--view-mode--list:hover {
background-color: #eaeaea;
}
}
206 changes: 174 additions & 32 deletions src/components/Library/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { to } from './../../reducers/router/routerSlice';
import FolderListItem from './components/FolderListItem';
import PaperListItem from './components/PaperListItem';
import styles from './styles.module.css';
import { SORT_BY } from '../../constants';
import { setSortPapersBy } from '../../reducers/settings/settingsSlice';
import { SORT_BY, VIEW_MODE } from '../../constants';
import { setSortPapersBy, setViewMode } from '../../reducers/settings/settingsSlice';
import Sortable from '../Sortable';

class Library extends React.Component {
constructor(props) {
Expand All @@ -18,6 +19,7 @@ class Library extends React.Component {
this.state = {
currentFolderId: props.activeFolderId,
sortBy: props.preferredSortBy,
viewMode: props.preferredViewMode,
};
}

Expand Down Expand Up @@ -74,12 +76,114 @@ class Library extends React.Component {
this.props.dispatch(newPaperInFolder(this.state.currentFolderId));
};

onSort = (e) => {
const sortBy = parseInt(e.target.value);
onSort = (sortBy) => {
this.setState({ sortBy });
this.props.dispatch(setSortPapersBy(sortBy));
};

onChangeViewMode = (e) => {
const viewMode = parseInt(e.target.value);

this.setState({ viewMode });
this.props.dispatch(setViewMode(viewMode));

// We display less options in grid mode for the 'sort by' filter, so
// map the missing values to existing ones.
if (viewMode === VIEW_MODE.GRID) {
if (this.state.sortBy === SORT_BY.CREATED_ASC) {
this.onSort(SORT_BY.CREATED_DESC);
} else if (this.state.sortBy === SORT_BY.LAST_MODIFIED_ASC) {
this.onSort(SORT_BY.LAST_MODIFIED_DESC);
}
}
};

renderPaperView = (papers) => {
if (this.state.viewMode === VIEW_MODE.LIST) {
return (
<table className="table">
<thead>
<tr>
<th>#</th>
<th>
<Sortable
sortAscActive={this.state.sortBy === SORT_BY.NAME_AZ}
sortDescActive={this.state.sortBy === SORT_BY.NAME_ZA}
onSortAsc={() => this.onSort(SORT_BY.NAME_AZ)}
onSortDesc={() => this.onSort(SORT_BY.NAME_ZA)}
>
Name
</Sortable>
</th>
<th>
<Sortable
sortAscActive={this.state.sortBy === SORT_BY.LAST_MODIFIED_ASC}
sortDescActive={this.state.sortBy === SORT_BY.LAST_MODIFIED_DESC}
onSortAsc={() => this.onSort(SORT_BY.LAST_MODIFIED_ASC)}
onSortDesc={() => this.onSort(SORT_BY.LAST_MODIFIED_DESC)}
>
Last modified
</Sortable>
</th>
<th>
<Sortable
sortAscActive={this.state.sortBy === SORT_BY.CREATED_ASC}
sortDescActive={this.state.sortBy === SORT_BY.CREATED_DESC}
onSortAsc={() => this.onSort(SORT_BY.CREATED_ASC)}
onSortDesc={() => this.onSort(SORT_BY.CREATED_DESC)}
>
Created
</Sortable>
</th>
<th className="text-align--right">
<button className="btn btn-thin btn-primary" onClick={this.newPaperInFolder}>
new paper
</button>
</th>
</tr>
</thead>
<tbody>
{papers.map((paper, index) => (
<PaperListItem
key={paper.id}
index={index}
paper={paper}
onClick={() => this.openPaper(paper.id)}
viewMode={this.state.viewMode}
/>
))}
</tbody>
</table>
);
}

// Render by default the grid view.
return (
<div className="row row-cols-sm-1 row-cols-md-2 row-cols-xl-3 row-cols-xxl-4">
<div className="col">
<div className={styles['library__new-paper__container']}>
<div
className={styles['library__new-paper__inner-container']}
onClick={this.newPaperInFolder}
>
new paper
</div>
</div>
</div>

{papers.map((paper) => (
<div key={paper.id} className="col">
<PaperListItem
paper={paper}
onClick={() => this.openPaper(paper.id)}
viewMode={this.state.viewMode}
/>
</div>
))}
</div>
);
};

renderPapers = () => {
const folder = this.props.library.folders.find(
(folder) => folder.id === this.state.currentFolderId,
Expand Down Expand Up @@ -119,7 +223,43 @@ class Library extends React.Component {
});
break;

case SORT_BY.LAST_EDIT:
case SORT_BY.CREATED_ASC:
papers = papers.sort((a, b) => {
if (dayjs(a.createdAt).isBefore(dayjs(b.createdAt))) {
return -1;
} else if (dayjs(a.createdAt).isAfter(dayjs(b.createdAt))) {
return 1;
}

return 0;
});
break;

case SORT_BY.CREATED_DESC:
papers = papers.sort((a, b) => {
if (dayjs(a.createdAt).isBefore(dayjs(b.createdAt))) {
return 1;
} else if (dayjs(a.createdAt).isAfter(dayjs(b.createdAt))) {
return -1;
}

return 0;
});
break;

case SORT_BY.LAST_MODIFIED_ASC:
papers = papers.sort((a, b) => {
if (dayjs(a.updatedAt).isBefore(dayjs(b.updatedAt))) {
return -1;
} else if (dayjs(a.updatedAt).isAfter(dayjs(b.updatedAt))) {
return 1;
}

return 0;
});
break;

case SORT_BY.LAST_MODIFIED_DESC:
default:
papers = papers.sort((a, b) => {
if (dayjs(a.updatedAt).isBefore(dayjs(b.updatedAt))) {
Expand All @@ -140,37 +280,38 @@ class Library extends React.Component {
{folder.name}
</h1>
<div className={styles['library__paper-list-view__filters']}>
<label htmlFor="paperSort">sort by</label>
<select
id="paperSort"
className="select"
onChange={this.onSort}
value={this.state.sortBy}
>
<option value={SORT_BY.NAME_AZ}>Name A-Z</option>
<option value={SORT_BY.NAME_ZA}>Name Z-A</option>
<option value={SORT_BY.LAST_EDIT}>Last edit</option>
</select>
</div>
</div>
<div className="row row-cols-sm-1 row-cols-md-2 row-cols-xl-3 row-cols-xxl-4">
<div className="col">
<div className={styles['library__new-paper__container']}>
<div
className={styles['library__new-paper__inner-container']}
onClick={this.newPaperInFolder}
>
new paper
{this.state.viewMode === VIEW_MODE.GRID && (
<div className={styles['library__paper-list-view__filter-group']}>
<label htmlFor="viewMode">sort by</label>
<select
id="viewMode"
className="select"
onChange={(e) => this.onSort(parseInt(e.target.value))}
value={this.state.sortBy}
>
<option value={SORT_BY.NAME_AZ}>Name A-Z</option>
<option value={SORT_BY.NAME_ZA}>Name Z-A</option>
<option value={SORT_BY.LAST_MODIFIED_DESC}>Last modified</option>
<option value={SORT_BY.CREATED_DESC}>Created</option>
</select>
</div>
</div>
</div>
)}

{papers.map((paper) => (
<div key={paper.id} className="col">
<PaperListItem paper={paper} onClick={() => this.openPaper(paper.id)} />
<div className={styles['library__paper-list-view__filter-group']}>
<label htmlFor="viewMode">view mode</label>
<select
id="viewMode"
className="select"
onChange={this.onChangeViewMode}
value={this.state.viewMode}
>
<option value={VIEW_MODE.GRID}>Grid</option>
<option value={VIEW_MODE.LIST}>List</option>
</select>
</div>
))}
</div>
</div>
{this.renderPaperView(papers)}
</div>
);
};
Expand Down Expand Up @@ -220,6 +361,7 @@ function mapStateToProps(state) {
activeFolderId: state.router.current.args.activeFolderId,
appVersion: state.settings.appVersion,
preferredSortBy: state.settings.sortPapersBy,
preferredViewMode: state.settings.viewMode,
};
}

Expand Down
Loading

0 comments on commit 696e606

Please sign in to comment.