diff --git a/app/actions/index.js b/app/actions/index.js index c1fa83dc..44bef703 100644 --- a/app/actions/index.js +++ b/app/actions/index.js @@ -172,3 +172,15 @@ export const dropFolder = folder => async dispatch => { if (!isDirectory) return addDat({ path: folder.path })(dispatch) } + +export const updateTitle = (key, path, editValue) => ({ + type: 'UPDATE_TITLE', + key, + editValue +}) + +export const makeEditable = title => ({ type: 'MAKE_EDITABLE', title }) + +export const editTitle = title => ({ type: 'EDIT_TITLE', title }) + +export const deactivate = () => ({ type: 'DEACTIVATE_EDITING' }) diff --git a/app/components/table-row.js b/app/components/table-row.js index b53c0a3f..8f21940b 100644 --- a/app/components/table-row.js +++ b/app/components/table-row.js @@ -7,6 +7,7 @@ import Icon from './icon' import Status from './status' import bytes from 'prettier-bytes' import FinderButton from './finder-button' +import TitleField from './title-field' const Tr = styled.tr` transition: background-color 0.025s ease-out; @@ -161,15 +162,17 @@ const DeleteButton = ({ ...props }) => ( /> ) -const TitleField = ({ dat }) => ( -
-

- {dat.metadata.title || `#${dat.key}`} -

-
-) - -const Row = ({ dat, shareDat, onDeleteDat, inspectDat }) => ( +const Row = ({ + dat, + editing, + shareDat, + onDeleteDat, + inspectDat, + updateTitle, + makeEditable, + editTitle, + deactivate +}) => ( inspectDat(dat.key)}>
@@ -178,7 +181,14 @@ const Row = ({ dat, shareDat, onDeleteDat, inspectDat }) => (
- +

{dat.metadata.author || 'Anonymous'} •{' '} diff --git a/app/components/table.js b/app/components/table.js index 31643965..bd7375cc 100644 --- a/app/components/table.js +++ b/app/components/table.js @@ -40,7 +40,18 @@ const StyledTable = styled.table` } ` -const Table = ({ dats, show, shareDat, onDeleteDat, inspectDat }) => { +const Table = ({ + dats, + show, + editing, + shareDat, + onDeleteDat, + inspectDat, + updateTitle, + makeEditable, + editTitle, + deactivate +}) => { if (!show) { return ( @@ -68,10 +79,15 @@ const Table = ({ dats, show, shareDat, onDeleteDat, inspectDat }) => { {Object.keys(dats).map(key => ( ))} diff --git a/app/components/title-field.js b/app/components/title-field.js new file mode 100644 index 00000000..fa07de98 --- /dev/null +++ b/app/components/title-field.js @@ -0,0 +1,141 @@ +import React, { Component } from 'react' +import styled from 'styled-components' +import Icon from './icon' +import { Plain as PlainButton, Green as GreenButton } from './button' + +const Overlay = styled.div` + position: absolute; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.2); +` + +const EditableField = styled.div` + position: relative; + h2 { + position: relative; + } + .indicator { + position: absolute; + display: none; + top: 0.25rem; + right: 0; + width: 0.75rem; + } + &:hover, + &:focus { + h2 { + color: var(--color-blue); + } + .indicator { + display: block; + } + } +` + +class TitleField extends Component { + constructor (props) { + super(props) + this.onclick = this.onclick.bind(this) + this.deactivate = this.deactivate.bind(this) + } + + onclick (ev) { + ev.stopPropagation() + ev.preventDefault() + this.props.makeEditable() + setTimeout(() => { + this.titleInput.focus() + this.titleInput.select() + }, 0) + } + + deactivate () { + this.props.deactivate() + } + + handleSave () { + const { key, path } = this.props.dat + const editValue = this.props.editing.editValue + this.props.updateTitle(key, path, editValue) + this.props.deactivate() + } + + handleKeypress (ev) { + const oldValue = this.props.editing.editValue + const newValue = ev.target.value + const editValue = newValue + ev.stopPropagation() + + if (ev.code === 'Escape') { + ev.preventDefault() + this.props.deactivate() + } else if (ev.code === 'Enter') { + ev.preventDefault() + const { key, path } = this.props.dat + this.props.updateTitle(key, path, editValue) + } else if (oldValue !== newValue) { + this.props.editTitle(newValue) + } + } + + render () { + const { writable, metadata } = this.props.dat + const { title } = metadata + const { isEditing, placeholderTitle, editValue } = this.props.editing + + if (isEditing && writable) { + return ( +

+ this.deactivate()} /> + + this.handleKeypress(ev)} + ref={input => { + this.titleInput = input + }} + /> + {editValue === title ? ( + this.deactivate(ev)}> + Save + + ) : ( + this.handleSave()}>Save + )} + +
+ ) + } + + if (writable) { + return ( + +

this.onclick(ev)} + > + {title || placeholderTitle} + +

+
+ ) + } + + return ( +
+

+ {title || placeholderTitle} +

+
+ ) + } +} + +export default TitleField diff --git a/app/containers/table.js b/app/containers/table.js index b600a95f..38c4c27e 100644 --- a/app/containers/table.js +++ b/app/containers/table.js @@ -1,18 +1,32 @@ 'use strict' import Table from '../components/table' -import { shareDat, deleteDat, inspectDat } from '../actions' +import { + shareDat, + deleteDat, + inspectDat, + updateTitle, + makeEditable, + editTitle, + deactivate +} from '../actions' import { connect } from 'react-redux' const mapStateToProps = state => ({ dats: state.dats, - show: !state.inspect.key + show: !state.inspect.key, + editing: state.editing }) const mapDispatchToProps = dispatch => ({ shareDat: link => dispatch(shareDat(link)), onDeleteDat: key => dispatch(deleteDat(key)), - inspectDat: key => dispatch(inspectDat(key)) + inspectDat: key => dispatch(inspectDat(key)), + updateTitle: (key, path, editValue) => + dispatch(updateTitle(key, path, editValue)), + makeEditable: (title, editable) => dispatch(makeEditable(title)), + editTitle: title => dispatch(editTitle(title)), + deactivate: () => dispatch(deactivate()) }) const TableContainer = connect(mapStateToProps, mapDispatchToProps)(Table) diff --git a/app/reducers/index.js b/app/reducers/index.js index 70aea678..0d31bb50 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -15,7 +15,8 @@ const defaultState = { up: 0, down: 0 }, - inspect: { key: null } + inspect: { key: null }, + editing: {} } const redatApp = (state = defaultState, action) => { @@ -179,6 +180,41 @@ const redatApp = (state = defaultState, action) => { } } } + case 'UPDATE_TITLE': + return { + ...state, + dats: { + ...state.dats, + [action.key]: { + ...state.dats[action.key], + metadata: { + ...state.dats[action.key].metadata, + title: action.editValue + } + } + } + } + case 'MAKE_EDITABLE': + return { + ...state, + editing: { + ...state.editing, + isEditing: true + } + } + case 'EDIT_TITLE': + return { + ...state, + editing: { + ...state.editing, + editValue: action.title + } + } + case 'DEACTIVATE_EDITING': + return { + ...state, + editing: {} + } case 'DIALOGS_LINK_OPEN': return { ...state,