Skip to content
This repository has been archived by the owner on Jan 6, 2022. It is now read-only.

Commit

Permalink
make title field editable (#509)
Browse files Browse the repository at this point in the history
* make title field editable

* add comment about `why innerRef`

* use better name for event and action

* stop propagation to row component on click save title

* add prettier

* reformat code

* use async/await to read/write dat.json

* add prettier

* reducers: hint schema of titleUnderEdit

* rename titleUnderEdit to titleEditInPlace
  • Loading branch information
aks- authored and juliangruber committed Feb 10, 2018
1 parent f7e44a6 commit ea732f0
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 11 deletions.
27 changes: 27 additions & 0 deletions app/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,33 @@ export const dropFolder = folder => async dispatch => {
addDat({ path: folder.path })(dispatch)
}

export const activateTitleEditing = title => ({
type: 'ACTIVATE_TITLE_EDITING',
title
})

export const updateTemporaryTitleValue = title => ({
type: 'UPDATE_TEMPORARY_TITLE_VALUE',
title
})

export const updateTitle = (key, path, editValue) => async dispatch => {
const filePath = `${path}/dat.json`
const blob = await readFile(filePath)
const metadata = { ...JSON.parse(blob), title: editValue }
await writeFile(filePath, JSON.stringify(metadata))

dispatch({
type: 'UPDATE_TITLE',
key,
editValue
})
}

export const deactivateTitleEditing = () => ({
type: 'DEACTIVATE_TITLE_EDITING'
})

export const loadFromDisk = () => async dispatch => {
try {
await mkdir(`${homedir()}/.dat-desktop`)
Expand Down
13 changes: 3 additions & 10 deletions app/components/table-row.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Status from './status'
import bytes from 'prettier-bytes'
import FinderButton from './finder-button'
import HexContent from './hex-content'
import TitleFieldContainer from '../containers/title-field'

const Tr = styled.tr`
transition: background-color 0.025s ease-out;
Expand Down Expand Up @@ -128,18 +129,10 @@ const DeleteButton = ({ ...props }) => (
/>
)

const TitleField = ({ dat }) => (
<div>
<h2 className='f6 f5-l normal truncate pr3'>
{dat.metadata.title || `#${dat.key}`}
</h2>
</div>
)

const Row = ({ dat, shareDat, onDeleteDat, inspectDat, onTogglePause }) => (
<Tr
onClick={ev => {
if (ev.target.tagName === 'SVG' || ev.target.tagName === 'use') return
if (['SVG', 'use', 'INPUT'].includes(ev.target.tagName)) return
inspectDat(dat.key)
}}
>
Expand All @@ -150,7 +143,7 @@ const Row = ({ dat, shareDat, onDeleteDat, inspectDat, onTogglePause }) => (
</td>
<td className='cell-2'>
<div className='cell-truncate'>
<TitleField dat={dat} />
<TitleFieldContainer dat={dat} />
<p className='f7 f6-l color-neutral-60 truncate'>
<span className='author'>
{dat.metadata.author || 'Anonymous'}{' '}
Expand Down
158 changes: 158 additions & 0 deletions app/components/title-field.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
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 EditableFieldWrapper = 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;
}
}
`

const InputField = styled.input`
:focus {
outline: none;
}
`

class TitleField extends Component {
constructor (props) {
super(props)
this.onclick = this.onclick.bind(this)
this.handleSave = this.handleSave.bind(this)
this.deactivateTitleEditing = this.deactivateTitleEditing.bind(this)
}

onclick (ev) {
ev.stopPropagation()
ev.preventDefault()
this.props.activateTitleEditing()

// setTimeout? Because ref on input is set asynchronously, and later. So we can't do focus, select on it until ref is set
setTimeout(() => {
this.titleInput.focus()
this.titleInput.select()
}, 0)
}

deactivateTitleEditing () {
this.props.deactivateTitleEditing()
}

handleSave () {
const { key, path } = this.props.dat
const { editValue } = this.props.titleEditInPlace
if (editValue) this.props.updateTitle(key, path, editValue)
this.props.deactivateTitleEditing()
}

handleKeyup (ev) {
const oldValue = this.props.titleEditInPlace.editValue
const newValue = ev.target.value
ev.stopPropagation()

if (ev.key === 'Escape') {
ev.preventDefault()
this.props.deactivateTitleEditing()
} else if (ev.key === 'Enter') {
ev.preventDefault()
this.handleSave()
} else if (oldValue !== newValue) {
this.props.updateTemporaryTitleValue(newValue)
}
}

render () {
const { dat, titleEditInPlace } = this.props
const { isEditing, editValue } = titleEditInPlace
const { writable, metadata, key } = dat
const { title } = metadata
const placeholderTitle = `#${key}`

if (isEditing && writable) {
return (
<div>
<Overlay onClick={() => this.deactivateTitleEditing()} />
<EditableFieldWrapper className='bg-white nt1 nb1 nl1 shadow-1 flex justify-between'>
{/* why innerRef in following component? check here - styled-components/styled-components#102 */}
<InputField
className='bn f6 pl1 normal w-100'
defaultValue={editValue || title}
onKeyUp={ev => this.handleKeyup(ev)}
innerRef={input => {
this.titleInput = input
}}
/>
{editValue === title ? (
<PlainButton onClick={ev => this.deactivateTitleEditing(ev)}>
Save
</PlainButton>
) : (
<GreenButton
onClick={ev => {
ev.stopPropagation()
this.handleSave()
}}
>
Save
</GreenButton>
)}
</EditableFieldWrapper>
</div>
)
}

if (writable) {
return (
<EditableFieldWrapper>
<h2
className='f6 f5-l normal truncate pr3'
onClick={ev => this.onclick(ev)}
>
{title || placeholderTitle}
<Icon
name='edit'
className='absolute top-0 bottom-0 right-0 color-neutral-30 indicator'
/>
</h2>
</EditableFieldWrapper>
)
}

return (
<div>
<h2 className='f6 f5-l normal truncate pr3'>
{title || placeholderTitle}
</h2>
</div>
)
}
}

export default TitleField
38 changes: 38 additions & 0 deletions app/containers/title-field.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use strict'

import { connect } from 'react-redux'
import TitleField from '../components/title-field'
import {
updateTitle,
activateTitleEditing,
updateTemporaryTitleValue,
deactivateTitleEditing
} from '../actions'

const makeMapStateToProps = (initialState, initialProps) => {
const { key } = initialProps.dat
const mapStateToProps = state => ({
dat: state.dats[key],
titleEditInPlace: state.titleEditInPlace
})

return mapStateToProps
}

const mapDispatchToProps = dispatch => {
return {
updateTitle: (key, path, editValue) =>
dispatch(updateTitle(key, path, editValue)),
activateTitleEditing: (title, editable) =>
dispatch(activateTitleEditing(title)),
updateTemporaryTitleValue: title =>
dispatch(updateTemporaryTitleValue(title)),
deactivateTitleEditing: () => dispatch(deactivateTitleEditing())
}
}

const TitleFieldContainer = connect(makeMapStateToProps, mapDispatchToProps)(
TitleField
)

export default TitleFieldContainer
44 changes: 43 additions & 1 deletion app/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ const defaultState = {
up: 0,
down: 0
},
inspect: { key: null }
inspect: { key: null },
titleEditInPlace: {
isEditing: false,
editValue: null
}
}

const redatApp = (state = defaultState, action) => {
Expand Down Expand Up @@ -180,6 +184,44 @@ const redatApp = (state = defaultState, action) => {
}
}
}
case 'ACTIVATE_TITLE_EDITING':
return {
...state,
titleEditInPlace: {
...state.titleEditInPlace,
isEditing: true
}
}
case 'UPDATE_TEMPORARY_TITLE_VALUE':
return {
...state,
titleEditInPlace: {
...state.titleEditInPlace,
editValue: action.title
}
}
case 'UPDATE_TITLE':
return {
...state,
dats: {
...state.dats,
[action.key]: {
...state.dats[action.key],
metadata: {
...state.dats[action.key].metadata,
title: action.editValue
}
}
}
}
case 'DEACTIVATE_TITLE_EDITING':
return {
...state,
titleEditInPlace: {
isEditing: false,
editValue: null
}
}
case 'DIALOGS_LINK_OPEN':
return {
...state,
Expand Down

0 comments on commit ea732f0

Please sign in to comment.