Working on the editor UI. Needs some refactoring components and wire up api middleware.

This commit is contained in:
Alex Hunt 2017-11-13 18:39:08 -05:00
parent 287f15380a
commit dc4463800a
37 changed files with 322 additions and 66 deletions

BIN
JoyceEditor_Mock.pdf Normal file

Binary file not shown.

View File

@ -9,13 +9,14 @@
Short List:
- Some Testing
- Font Awesome / Better Glyphicons
- Env Configs
- Collapsable Button Lists
- Draft.JS
- DRY up Sass Modules
- Async API Middleware
- CombineReducers in App.js
- Import Actions into Reducers
- Import Action consts into Reducers
- PropTypes
- Loading Indicators

View File

@ -28,6 +28,7 @@
"clean-webpack-plugin": "^0.1.17",
"css-loader": "^0.28.7",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.5",
"manifest-revision-webpack-plugin": "^0.4.1",
"node-sass": "^4.6.0",
"postcss-loader": "^2.0.8",

View File

@ -1,22 +0,0 @@
const setCurrentChapter = id =>
({
type: 'GET_TEXT_DATA',
id: id
})
const toggleHighlight = () =>
({
type: 'TOGGLE_HIGHLIGHT'
})
const updateEditorState = editorState =>
({
type: 'UPDATE_EDITOR_STATE',
payload: editorState
})
export {
setCurrentChapter,
toggleHighlight,
updateEditorState
}

114
src/actions/index.js Normal file
View File

@ -0,0 +1,114 @@
import axios from 'axios'
import EditorState from 'draft-js'
let apiRoute = '/api/chapters/'
const SELECT_CHAPTER = 'SELECT_CHAPTER'
const GET_CHAPTER_DATA = 'GET_CHAPTER_DATA'
const GET_CHAPTER_DATA_RECEIVED = 'GET_CHAPTER_DATA_RECEIVED'
const GET_CHAPTER_DATA_ERROR = 'GET_CHAPTER_DATA_ERROR'
const GET_TEXT_DATA = 'GET_TEXT_DATA'
const GET_TEXT_DATA_RECEIVED = 'GET_TEXT_DATA_RECEIVED'
const GET_TEXT_DATA_ERROR = 'GET_TEXT_DATA_ERROR'
const UPDATE_EDITOR_STATE = 'UPDATE_EDITOR_STATE'
const TOGGLE_HIGHLIGHT = 'TOGGLE_HIGHLIGHT'
const UPDATE_CHAPTER_TITLE = 'UPDATE_CHAPTER_TITLE'
export const setCurrentChapter = id =>
({
type: GET_TEXT_DATA,
id: id
})
export const toggleHighlight = () =>
({
type: TOGGLE_HIGHLIGHT
})
export const updateChapterTitleInput = chapterTitleInput => {
return ({
type: UPDATE_CHAPTER_TITLE,
data: chapterTitleInput.target.value
})
}
export const updateEditorState = editorState =>
({
type: UPDATE_EDITOR_STATE,
data: editorState
})
export const chapterDataReceived = data =>
({
type: GET_CHAPTER_DATA_RECEIVED,
data
})
export const chapterDataError = error =>
({
type: GET_CHAPTER_DATA_ERROR,
error
})
export const textDataReceived = data =>
({
type: GET_TEXT_DATA_RECEIVED,
data
})
export const textDataError = error =>
({
type: GET_TEXT_DATA_ERROR,
error
})
export const setChapterEditor = chapter =>
({
type: SET_EDITED_CHAPTER,
chapter
})
export const logger = store => next => action => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.log('chapter count', store.getState().chapters.length)
console.groupEnd(action.type)
return result
}
export const joyceAPIService = store => next => action => {
next(action)
switch(action.type) {
case GET_CHAPTER_DATA:
axios.get(apiRoute).then(res => {
const data = res.data
return next(chapterDataReceived(data))
}).catch(error => {
return next(chapterDataError(error))
})
break
case GET_TEXT_DATA:
axios.get(apiRoute + action.id).then(res=> {
const data = res.data
return next(textDataReceived(data))
}).catch(error => {
return next(textDataError(error))
})
break
case CREATE_CHAPTER:
const nextNumber = store.getState().chapters.length + 1
const chapter = {
number: nextNumber,
title: '',
text: EditorState.createEmpty(),
}
store.dispatch(setChapterEditor(chapter))
break
defaut:
break
}
}
export const foo = 'bar'

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

30
src/assets/index.js Normal file
View File

@ -0,0 +1,30 @@
const glyphiconItalic = require('./glyphicons-102-italic.png')
const glyphiconBold = require('./glyphicons-103-bold.png')
const glyphiconUnderline = require('./glyphicons-104-text-underline.png')
const glyphiconAlignLeft = require('./glyphicons-111-align-left.png')
const glyphiconAlignCenter = require('./glyphicons-112-align-center.png')
const glyphiconAlignRight = require('./glyphicons-113-align-right.png')
const glyphiconChevronLeft = require('./glyphicons-225-chevron-left.png')
const glyphiconChevronRight = require('./glyphicons-224-chevron-right.png')
const glyphiconPlusSign = require('./glyphicons-191-plus-sign.png')
const glyphiconMinusSign = require('./glyphicons-192-minus-sign.png')
const glyphiconRemoveSign = require('./glyphicons-193-remove-sign.png')
const glyphiconOKSign = require('./glyphicons-194-ok-sign.png')
export {
glyphiconItalic,
glyphiconBold,
glyphiconUnderline,
glyphiconAlignLeft,
glyphiconAlignCenter,
glyphiconAlignRight,
glyphiconChevronLeft,
glyphiconChevronRight,
glyphiconPlusSign,
glyphiconMinusSign,
glyphiconRemoveSign,
glyphiconOKSign
}

View File

@ -1,11 +0,0 @@
import React from 'react'
const EditorButtonGroup = () =>
<div className='editor_button_group'>
<div className='btn-group' role='group'>
<button type='button' className='btn btn-secondary'>Bold</span></button>
<button type='button' className='btn btn-secondary'>Italics</span></button>
</div>
</div>
export default EditorButtonGroup

View File

@ -0,0 +1,14 @@
import React from 'react'
import ChapterListContainer from '../containers/chapterListContainer'
import NewChapterButtonContainer from '../containers/newChapterButtonContainer'
import SidebarSpacer from './sidebarSpacer'
const EditorSidebar = () =>
<div className="col-md-3" id="sidebar">
<SidebarSpacer />
<NewChapterButtonContainer />
<SidebarSpacer />
<ChapterListContainer />
</div>
export default EditorSidebar

View File

@ -0,0 +1,13 @@
import React from 'react'
import { glyphiconPlusSign } from '../assets'
const NewChapterButton = () =>
<div>
<div id="highlight_button" className="text-center">
<button className='btn btn-outline-success btn-lg'>
New Chapter <img src={glyphiconPlusSign} />
</button>
</div>
</div>
export default NewChapterButton

View File

@ -1,7 +1,7 @@
import React from 'react'
import { connect } from 'react-redux'
import { setCurrentChapter } from '../actions/actions'
import { setCurrentChapter } from '../actions'
import ChapterList from '../components/chapterList'

View File

@ -2,7 +2,7 @@ import React from 'react'
import TextEditorContainer from '../containers/textEditorContainer'
import Navbar from '../components/navbar'
import Sidebar from '../components/sidebar'
import EditorSidebar from '../components/editorSidebar'
import Content from '../components/content'
const EditorContainer = () =>
@ -10,7 +10,7 @@ const EditorContainer = () =>
<Navbar />
<div id='joyce_editor' className='container-fluid'>
<div className="row">
<Sidebar />
<EditorSidebar />
<Content>
<TextEditorContainer />
</Content>

View File

@ -1,7 +1,7 @@
import React from 'react'
import { connect } from 'react-redux'
import { toggleHighlight } from '../actions/actions'
import { toggleHighlight } from '../actions'
import HighlightButton from '../components/highlightButton'

View File

@ -0,0 +1,18 @@
import React from 'react'
import { connect } from 'react-redux'
import { createNewChapter } from '../actions'
import NewChapterButton from '../components/newChapterButton'
const mapDispatchToProps = dispatch => {
return {
onNewChapterClick: () => {
dispatch(createNewChapter())
}
}
}
const NewChapterButtonContainer = connect(mapDispatchToProps)(NewChapterButton)
export default NewChapterButtonContainer

View File

@ -1,6 +1,6 @@
import React from 'react'
import { connect } from 'react-redux'
import { setCurrentChapter } from '../actions/actions'
import { setCurrentChapter } from '../actions'
class Page extends React.Component {
render() {

View File

@ -1,7 +1,7 @@
import React from 'react'
import { connect } from 'react-redux'
import { setCurrentChapter } from '../actions/actions'
import { setCurrentChapter } from '../actions'
import PageContainer from '../containers/pageContainer'
import Content from '../components/content'

View File

View File

@ -1,21 +1,57 @@
import React from 'react'
import { connect } from 'react-redux'
import { updateEditorState } from '../actions/actions'
import { updateEditorState, updateChapterTitleInput } from '../actions'
import { Editor, convertToRaw } from 'draft-js';
import EditorButtonGroup from '../components/editorButtonGroup'
import { glyphiconBold, glyphiconItalic, glyphiconUnderline, glyphiconAlignLeft, glyphiconAlignCenter, glyphiconAlignRight } from '../assets'
const TextEditor = ({editorState, onSaveEditorState}) =>
<div id="text_editor">
<EditorButtonGroup />
<Editor editorState={editorState} onChange={onSaveEditorState} />
{JSON.stringify(editorState.getSelection())}
const TextEditor = ({editorState, onSaveEditorState, chapterTitleInput, onChapterTitleChange}) =>
<div id='editor_container'>
<div id='editor_topbar'>
<div className='row'>
<div className='col-md-4'>
<h4>Chapter 1:</h4>
</div>
<div className='col-md-8'>
<input type="text" value={chapterTitleInput} onChange={onChapterTitleChange}/>
</div>
</div>
<div className='row'>
<div className='btn-group col-md-3 offset-md-6' role='group'>
<button type='button' className='btn btn-info btn-sm'><img src={glyphiconBold} /></button>
<button type='button' className='btn btn-info btn-sm'><img src={glyphiconItalic} /></button>
<button type='button' className='btn btn-info btn-sm'><img src={glyphiconUnderline} /></button>
</div>
<div className='btn-group col-md-3' role='group'>
<button type='button' className='btn btn-info btn-sm'><img src={glyphiconAlignLeft} /></button>
<button type='button' className='btn btn-info btn-sm'><img src={glyphiconAlignCenter} /></button>
<button type='button' className='btn btn-info btn-sm'><img src={glyphiconAlignRight} /></button>
</div>
</div>
</div>
<div id='editor_content'>
<Editor editorState={editorState} onChange={onSaveEditorState} />
</div>
<div id='editor_bottombar'>
<div className='row'>
<div className='col-md-5'>
<button id='editor_delete' type='button' className='btn btn-danger btn-sm'>Delete</button>
</div>
<div className='col-md-5 offset-md-2'>
<button id='editor_save' type='button' className='btn btn-success btn-sm'>Save</button>
</div>
</div>
</div>
</div>
const mapStateToProps = state => {
return {
editorState: state.editorState
editorState: state.editorState,
chapterTitleInput: state.chapterTitleInput
}
}
@ -23,6 +59,9 @@ const mapDispatchToProps = dispatch => {
return {
onSaveEditorState: editorState => {
dispatch(updateEditorState(editorState))
},
onChapterTitleChange: chapterTitleInput => {
dispatch(updateChapterTitleInput(chapterTitleInput))
}
}
}

View File

@ -4,22 +4,26 @@ import { createStore, combineReducers, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import 'bootstrap'
import { chapters, highlightActive, currentChapter, editorState } from './reducers/editor'
import joyceAPIService from './middleware'
import { chapters, highlightActive, currentChapter, chapterTitleInput, editorState } from './reducers/editor'
import { joyceAPIService, logger } from './actions'
import EditorContainer from './containers/editorContainer'
const reduceEditor = combineReducers({
chapters,
currentChapter,
chapterTitleInput,
highlightActive,
editorState
})
let store = createStore(reduceEditor, applyMiddleware(joyceAPIService))
let store = createStore(reduceEditor, applyMiddleware(joyceAPIService, logger))
ReactDOM.render(
<Provider store={store}>
<EditorContainer />
</Provider>,
document.getElementById('wrapper')
)
)
store.dispatch({type: 'GET_CHAPTER_DATA'})
store.dispatch({type: 'GET_TEXT_DATA', id: 1})

View File

@ -5,7 +5,7 @@ import { Provider } from 'react-redux'
import 'bootstrap'
import { chapters, currentChapter, highlightActive } from './reducers/reader'
import joyceAPIService from './middleware'
import { joyceAPIService } from './actions'
import { ReaderContainer } from './containers/readerContainer'
const reduceReader = combineReducers({

View File

@ -1,6 +1,6 @@
import { EditorState } from 'draft-js'
import objectAssign from 'object-assign' // Object.assign() polyfill for older browsers
import actions from '../actions/actions'
import actions from '../actions'
const chapters = (state=[], action) => {
switch(action.type) {
@ -32,8 +32,16 @@ const currentChapter = (state={}, action) => {
const editorState = (state=(EditorState.createEmpty()), action) => {
switch(action.type) {
case 'UPDATE_EDITOR_STATE':
console.log(action.payload.getSelection())
return action.payload
return action.data
default:
return state
}
}
const chapterTitleInput = (state='', action) => {
switch(action.type) {
case 'UPDATE_CHAPTER_TITLE':
return action.data
default:
return state
}
@ -43,5 +51,6 @@ export {
chapters,
highlightActive,
currentChapter,
chapterTitleInput,
editorState
}

View File

@ -1,4 +1,7 @@
$theme-colors: (
"info": #537577,
"danger": #72030a,
"success": #118734,
"dark": #07383a,
"primary": #824500
);

View File

@ -108,13 +108,50 @@ nav a:hover {
border-radius: 5px;
}
#text_editor {
#editor_topbar {
margin: 0.8% 0;
}
#editor_topbar input {
width: 100%;
}
#editor_topbar button img {
width: 100%;
height: 100%;
}
#editor_content {
height: 100%;
background-color: rgba(256,256,256,.8);
box-shadow: 2px 5px 10px 1px rgba(0, 0, 0, 0.3);
padding: 3% 8%;
margin: 0.8% 0;
border: 1px solid $border_color;
border-radius: 5px;
padding: 3% 8%;
}
#editor_content div {
height: 100%;
overflow: scroll;
}
#editor_topbar button img {
width: 60%;
}
#editor_bottombar {
margin: 0.8% 0;
}
#editor_delete {
float: left;
width: 100%;
}
#editor_save {
float: right;
width: 100%;
}
#page {

View File

@ -1,10 +1,10 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
const ManifestRevisionPlugin = require('manifest-revision-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
const ManifestRevisionPlugin = require('manifest-revision-webpack-plugin')
const webpack = require('webpack')
const path = require('path')
const rootAssetPath = './src/';
const rootAssetPath = './src/'
let pathsToClean = [
'static/'
@ -25,7 +25,7 @@ module.exports = {
},
output: {
publicPath: "/static/js/",
filename: '[name].[chunkhash].js',
filename: '[name].[hash].js',
path: path.resolve(__dirname, 'static/js/')
},
watch: true,
@ -44,6 +44,12 @@ module.exports = {
}
}
},
{
test: /\.(png)$/,
use: {
loader: 'file-loader'
}
},
{
test: /\.(scss)$/,
use: [{
@ -75,6 +81,6 @@ module.exports = {
'window.jQuery': 'jquery',
Popper: ['popper.js', 'default']
}),
new CleanWebpackPlugin(pathsToClean)
],
new CleanWebpackPlugin(pathsToClean),
],
};