Skip to content

Commit

Permalink
feat: ability to reorder entries (#51)
Browse files Browse the repository at this point in the history
* feat(wip): ability to reorder entries

* fix: minor changes

* fix: disable drag-n-drop when note is locked

* fix: align menu center

* feat: drag indicator

* fix: clean auth-drag-indicator-container css class

* fix: clear id attribute

* feat: reorder button

* fix: drag indicator fill color

* fix: alert message for confirming reording

* fix: update with develop

* refactor: remove onCopyToken from divProps

* chore: build

* fix: minor changes

* chore: build

Co-authored-by: Johnny Almonte <johnny243@users.noreply.github.com>
  • Loading branch information
johnny243 and johnny243 authored May 27, 2021
1 parent 53b79e9 commit 78e4f76
Show file tree
Hide file tree
Showing 13 changed files with 352 additions and 34 deletions.
26 changes: 22 additions & 4 deletions app/components/AuthEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { totp } from '@Lib/otp';
import CountdownPie from '@Components/CountdownPie';
import AuthMenu from '@Components/AuthMenu';
import DragIndicator from '@Components/DragIndicator';

export default class AuthEntry extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -69,17 +70,32 @@ export default class AuthEntry extends React.Component {

render() {
const { service, account, notes, color } = this.props.entry;
const { id, onEdit, onRemove, canEdit } = this.props;
const { id, onEdit, onRemove, canEdit, style, innerRef, ...divProps } = this.props;
const { token, timeLeft } = this.state;

const entryStyle = {};
if (color) {
entryStyle.backgroundColor = color;
}

delete divProps.onCopyToken;

return (
<div className="sk-notification sk-base-custom" style={entryStyle}>
<div
{...divProps}
className="sk-notification sk-base-custom"
style={{
...entryStyle,
...style
}}
ref={innerRef}
>
<div className="auth-entry">
{canEdit && (
<div className="auth-drag-indicator-container">
<DragIndicator />
</div>
)}
<div className="auth-details">
<div className="auth-info">
<div className="auth-service">{service}</div>
Expand All @@ -91,7 +107,7 @@ export default class AuthEntry extends React.Component {
<div>{token.substr(3, 3)}</div>
</div>
<div className="auth-countdown">
<CountdownPie token={token} timeLeft={timeLeft} total={30} />
<CountdownPie token={token} timeLeft={timeLeft} total={30} bgColor={color} />
</div>
</div>
</div>
Expand Down Expand Up @@ -121,5 +137,7 @@ AuthEntry.propTypes = {
onRemove: PropTypes.func.isRequired,
onEntryChange: PropTypes.func,
onCopyToken: PropTypes.func.isRequired,
canEdit: PropTypes.bool.isRequired
canEdit: PropTypes.bool.isRequired,
innerRef: PropTypes.func.isRequired,
style: PropTypes.object.isRequired
};
7 changes: 5 additions & 2 deletions app/components/CountdownPie.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,13 @@ function useRotateAnimation(token, timeLeft, total) {
);
}

const CountdownPie = ({ token, timeLeft, total }) => {
const CountdownPie = ({ token, timeLeft, total, bgColor }) => {
useRotateAnimation(token, timeLeft, total);

return (
<div className="countdown-pie">
<div className="countdown-pie" style={{
backgroundColor: bgColor
}}>
<div
className="pie spinner"
style={{
Expand Down Expand Up @@ -125,6 +127,7 @@ CountdownPie.propTypes = {
token: PropTypes.string.isRequired,
timeLeft: PropTypes.number.isRequired,
total: PropTypes.number.isRequired,
bgColor: PropTypes.string
};

export default CountdownPie;
20 changes: 20 additions & 0 deletions app/components/DragIndicator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';

const DragIndicator = () => (
<svg width="10px" height="16px" viewBox="0 0 10 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<g transform="translate(-617.000000, -246.000000)">
<g transform="translate(100.000000, 100.000000)">
<g transform="translate(510.000000, 142.000000)">
<g>
<polygon points="0 0 24 0 24 24 0 24"></polygon>
<path d="M11,18 C11,19.1 10.1,20 9,20 C7.9,20 7,19.1 7,18 C7,16.9 7.9,16 9,16 C10.1,16 11,16.9 11,18 Z M9,10 C7.9,10 7,10.9 7,12 C7,13.1 7.9,14 9,14 C10.1,14 11,13.1 11,12 C11,10.9 10.1,10 9,10 Z M9,4 C7.9,4 7,4.9 7,6 C7,7.1 7.9,8 9,8 C10.1,8 11,7.1 11,6 C11,4.9 10.1,4 9,4 Z M15,8 C16.1,8 17,7.1 17,6 C17,4.9 16.1,4 15,4 C13.9,4 13,4.9 13,6 C13,7.1 13.9,8 15,8 Z M15,10 C13.9,10 13,10.9 13,12 C13,13.1 13.9,14 15,14 C16.1,14 17,13.1 17,12 C17,10.9 16.1,10 15,10 Z M15,16 C13.9,16 13,16.9 13,18 C13,19.1 13.9,20 15,20 C16.1,20 17,19.1 17,18 C17,16.9 16.1,16 15,16 Z" fill="currentColor"></path>
</g>
</g>
</g>
</g>
</g>
</svg>
);

export default DragIndicator;
17 changes: 11 additions & 6 deletions app/components/EditEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ const defaultColorOptions = [

export default class EditEntry extends React.Component {
static defaultProps = {
entry: {}
entry: {
service: '',
account: '',
secret: '',
notes: ''
}
};

constructor(props) {
Expand Down Expand Up @@ -150,7 +155,7 @@ export default class EditEntry extends React.Component {
/>
</div>
)}
<form>
<form onSubmit={this.onSave} autoComplete="off">
<input
name="service"
className="sk-input contrast"
Expand Down Expand Up @@ -188,14 +193,14 @@ export default class EditEntry extends React.Component {
/>
<div className="sk-panel-row">
<div className="sk-button-group stretch">
<div className="sk-button neutral" onClick={this.props.onCancel}>
<button type="submit" className="sk-button neutral" onClick={this.props.onCancel}>
<div className="sk-label">Cancel</div>
</div>
<div className="sk-button info" onClick={this.onSave}>
</button>
<button type="submit" className="sk-button info">
<div className="sk-label">
{id != null ? 'Save' : 'Create'}
</div>
</div>
</button>
</div>
</div>
</form>
Expand Down
47 changes: 46 additions & 1 deletion app/components/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ViewEntries from '@Components/ViewEntries';
import ConfirmDialog from '@Components/ConfirmDialog';
import DataErrorAlert from '@Components/DataErrorAlert';
import EditorKit from '@standardnotes/editor-kit';
import ReorderIcon from '@Components/ReorderIcon';

const initialState = {
text: '',
Expand All @@ -13,6 +14,7 @@ const initialState = {
editMode: false,
editEntry: null,
confirmRemove: false,
confirmReorder: false,
displayCopy: false,
canEdit: true
};
Expand Down Expand Up @@ -149,6 +151,7 @@ export default class Home extends React.Component {
onCancel = () => {
this.setState({
confirmRemove: false,
confirmReorder: false,
editMode: false,
editEntry: null
});
Expand Down Expand Up @@ -191,9 +194,39 @@ export default class Home extends React.Component {
}, 2000);
};

updateEntries = (entries) => {
this.saveNote(entries);
this.setState({
entries
});
};

onReorderEntries = () => {
if (!this.state.canEdit) {
return;
}
this.setState({
confirmReorder: true
});
};

reorderEntries = () => {
const { entries } = this.state;
const orderedEntries = entries.sort((a, b) => {
const serviceA = a.service.toLowerCase();
const serviceB = b.service.toLowerCase();
return (serviceA < serviceB) ? -1 : (serviceA > serviceB) ? 1 : 0;
});
this.saveNote(orderedEntries);
this.setState({
entries: orderedEntries,
confirmReorder: false
});
};

render() {
const editEntry = this.state.editEntry || {};
const { canEdit, displayCopy, parseError, editMode, entries, confirmRemove } = this.state;
const { canEdit, displayCopy, parseError, editMode, entries, confirmRemove, confirmReorder } = this.state;

return (
<div className="sn-component">
Expand All @@ -211,6 +244,9 @@ export default class Home extends React.Component {
{parseError && <DataErrorAlert />}
<div id="header" className={!canEdit ? 'hidden' : '' }>
<div className="sk-button-group">
<div onClick={this.onReorderEntries} className="sk-button info" aria-disabled={!canEdit}>
<ReorderIcon />
</div>
<div onClick={this.onAddNew} className="sk-button info" aria-disabled={!canEdit}>
<div className="sk-label">Add New</div>
</div>
Expand All @@ -232,6 +268,7 @@ export default class Home extends React.Component {
onRemove={this.onRemove}
onCopyToken={this.onCopyToken}
canEdit={canEdit}
updateEntries={this.updateEntries}
/>
)}
{confirmRemove && (
Expand All @@ -242,6 +279,14 @@ export default class Home extends React.Component {
onCancel={this.onCancel}
/>
)}
{confirmReorder && (
<ConfirmDialog
title={'Auto-sort entries'}
message="Are you sure you want to auto-sort all entries alphabetically based on service name?"
onConfirm={this.reorderEntries}
onCancel={this.onCancel}
/>
)}
</div>
</div>
);
Expand Down
43 changes: 43 additions & 0 deletions app/components/ReorderIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';

const ReorderIcon = () => (
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
width="12px" height="10px"
viewBox="0 0 300 300"
xmlSpace="preserve"
>
<g fill="currentColor">
<path
d="M159.365,23.736v-10c0-5.523-4.477-10-10-10H10c-5.523,0-10,4.477-10,10v10c0,5.523,4.477,10,10,10h139.365
C154.888,33.736,159.365,29.259,159.365,23.736z"
/>
<path
d="M130.586,66.736H10c-5.523,0-10,4.477-10,10v10c0,5.523,4.477,10,10,10h120.586c5.523,0,10-4.477,10-10v-10
C140.586,71.213,136.109,66.736,130.586,66.736z"
/>
<path
d="M111.805,129.736H10c-5.523,0-10,4.477-10,10v10c0,5.523,4.477,10,10,10h101.805c5.523,0,10-4.477,10-10v-10
C121.805,134.213,117.328,129.736,111.805,129.736z"
/>
<path
d="M93.025,199.736H10c-5.523,0-10,4.477-10,10v10c0,5.523,4.477,10,10,10h83.025c5.522,0,10-4.477,10-10v-10
C103.025,204.213,98.548,199.736,93.025,199.736z"
/>
<path
d="M74.244,262.736H10c-5.523,0-10,4.477-10,10v10c0,5.523,4.477,10,10,10h64.244c5.522,0,10-4.477,10-10v-10
C84.244,267.213,79.767,262.736,74.244,262.736z"
/>
<path
d="M298.29,216.877l-7.071-7.071c-1.875-1.875-4.419-2.929-7.071-2.929c-2.652,0-5.196,1.054-7.072,2.929l-34.393,34.393
V18.736c0-5.523-4.477-10-10-10h-10c-5.523,0-10,4.477-10,10v225.462l-34.393-34.393c-1.876-1.875-4.419-2.929-7.071-2.929
c-2.652,0-5.196,1.054-7.071,2.929l-7.072,7.071c-3.904,3.905-3.904,10.237,0,14.142l63.536,63.536
c1.953,1.953,4.512,2.929,7.071,2.929c2.559,0,5.119-0.976,7.071-2.929l63.536-63.536
C302.195,227.113,302.195,220.781,298.29,216.877z"
/>
</g>
</svg>
);

export default ReorderIcon;
82 changes: 66 additions & 16 deletions app/components/ViewEntries.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,79 @@
import React from 'react';
import PropTypes from 'prop-types';
import AuthEntry from '@Components/AuthEntry';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';

const ViewEntries = ({ entries, onEdit, onRemove, onCopyToken, canEdit }) => (
<div className="auth-list">
{entries.map((entry, idx) => (
<AuthEntry
key={idx}
id={idx}
entry={entry}
onEdit={onEdit}
onRemove={onRemove}
onCopyToken={onCopyToken}
canEdit={canEdit}
/>
))}
</div>
);
const reorderEntries = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);

return result;
};

const ViewEntries = ({ entries, onEdit, onRemove, onCopyToken, canEdit, updateEntries }) => {
const onDragEnd = (result) => {
const droppedOutsideList = !result.destination;
if (droppedOutsideList) {
return;
}

const orderedEntries = reorderEntries(
entries,
result.source.index,
result.destination.index
);

updateEntries(orderedEntries);
};

return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable" isDropDisabled={!canEdit}>
{(provided) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
className="auth-list"
>
{entries.map((entry, index) => (
<Draggable
key={`${entry.service}-${index}`}
draggableId={`${entry.service}-${index}`}
index={index}
isDragDisabled={!canEdit}
>
{(provided) => (
<AuthEntry
{...provided.draggableProps}
{...provided.dragHandleProps}
innerRef={provided.innerRef}
key={index}
id={index}
entry={entry}
onEdit={onEdit}
onRemove={onRemove}
onCopyToken={onCopyToken}
canEdit={canEdit}
/>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
};

ViewEntries.propTypes = {
entries: PropTypes.arrayOf(PropTypes.object),
onEdit: PropTypes.func.isRequired,
onRemove: PropTypes.func.isRequired,
onCopyToken: PropTypes.func.isRequired,
canEdit: PropTypes.bool.isRequired
canEdit: PropTypes.bool.isRequired,
updateEntries: PropTypes.func.isRequired
};

export default ViewEntries;
Loading

0 comments on commit 78e4f76

Please sign in to comment.