Add loading indicators and basic components/api for a search page. Still needs some wiring up.

This commit is contained in:
Alex Hunt 2017-12-16 15:00:20 -05:00
parent 1c8586f3fe
commit 83c657ceec
26 changed files with 241 additions and 25 deletions

View File

@ -4,6 +4,7 @@ from werkzeug.serving import run_simple
from blueprints.reader import reader
from blueprints.editor import editor
from blueprints.search import search
from blueprints.api import api
# Initialize application
@ -24,8 +25,9 @@ webpack.init_app(application)
# Register blueprints
application.register_blueprint(reader)
application.register_blueprint(editor, url_prefix='/editor')
application.register_blueprint(editor, url_prefix='/edit')
application.register_blueprint(api, url_prefix='/api')
application.register_blueprint(search, url_prefix='/search')
if __name__ == "__main__":
# application.debug=True

View File

@ -27,19 +27,32 @@ create_index_settings = {
'number_of_shards' : 1,
'number_of_replicas' : 0
},
'analysis': {
'analyzer': {
'html_analyzer': {
'type': 'custom',
'tokenizer': 'standard',
'char_filter': ['html_strip']
}
}
}
},
'mappings': {
'chapter': {
'properties': {
'number': {'type': 'integer'},
'title': {'type': 'keyword'},
'text': {'type': 'text'}
'text': {
'type': 'text',
}
}
},
'note': {
'properties': {
'title': {'type': 'keyword'},
'text': {'type': 'text'}
'text': {
'type': 'text',
}
}
}
}

View File

@ -158,4 +158,13 @@ export const editTextReceived = data =>
({
type: 'SELECT_ANNOTATION_NOTE',
id: id
})
})
// Search
export const updateSearchInput = searchInput => {
return ({
type: 'UPDATE_SEARCH_INPUT',
data: searchInput.target.value
})
}

View File

@ -39,6 +39,15 @@ export const HighlightButton = ({highlightActive, onHighlightClick}) =>
</div>
</div>
export const SearchButton = () =>
<div>
<div id='search_button' className='text-center'>
<button className='btn btn-primary btn-sm'>
Search <i className='fa fa_inline fa-search'></i>
</button>
</div>
</div>
export const NewChapterButton = ({onNewChapterClick}) =>
<div>
<div id='new_chapter_button' className='text-center'>

View File

@ -1,7 +1,7 @@
import React from 'react'
const Content = (props) =>
<div id='content_window' className='col-md-8'>
<div id='content_window' className='col-md-8 order-2 order-xs-1 order-sm-1'>
{props.children}
</div>

View File

@ -0,0 +1,8 @@
import React from 'react'
const LoadingSpinner = () =>
<div className='loading_spinner'>
<i className="fa fa-cog fa-spin fa-2x fa-fw"></i>
</div>
export default LoadingSpinner

View File

@ -1,15 +1,15 @@
import React from 'react'
import React from 'react'
const Navbar = () =>
<nav className='navbar navbar-inverse navbar-static-top navbar-expand-lg'>
<nav className='navbar navbar-dark navbar-static-top navbar-expand-lg'>
<a className='navbar-brand' href='/'>The Joyce Project</a>
<button className='navbar-toggler' type='button' data-toggle='collapse' data-target='#navbarItems'>
<span className='navbar-toggler-icon'><i className='fa fa-bars fa-lg' /></span>
<span className='navbar-toggler-icon'></span>
</button>
<div id='navbarItems' className='collapse navbar-collapse'>
<ul className='navbar-nav mr-auto'>
<li className='nav-item'>
<a className='nav-link' href='/editor'>Editor</a>
<a className='nav-link' href='/edit'>Edit</a>
</li>
<li className='nav-item'>
<a className='nav-link' href='/notes'>Notes</a>

View File

@ -5,11 +5,15 @@ import { Editor } from 'draft-js'
import { ReadModeTopBar, EditModeTopBar, AnnotateModeTopBar } from '../components/contentTopBar'
import { EditModeBottomBar } from '../components/contentBottomBar'
import DocumentTitle from '../components/documentTitle'
import LoadingSpinner from '../components/loadingSpinner'
import { updateEditorState, handleEditorKeyCommand, applyInlineStyles, setMode, cancelEdit, submitDocumentEdit, updateDocumentTitleChange, addAnnotation, removeAnnotation } from '../actions'
const JoyceEditorContent = ({currentDocument, editorState, mode, handleKeyCommand, onChangeEditorState, onToolButtonClick, setMode, cancelEdit, onSubmitClick, documentTitleInput, onDocumentTitleChange, onNewAnnotationClick, onRemoveAnnotationClick, docType}) =>
const JoyceEditorContent = ({currentDocument, editorState, mode, handleKeyCommand, onChangeEditorState, onToolButtonClick, setMode, cancelEdit, onSubmitClick, documentTitleInput, onDocumentTitleChange, onNewAnnotationClick, onRemoveAnnotationClick, docType, loadingToggle}) =>
<div>
<div id='editor_metadata'>
{loadingToggle === true &&
<LoadingSpinner />
}
{(mode === 'READ_MODE' || mode === 'ANNOTATE_MODE') &&
<DocumentTitle docType={docType} currentDocument={currentDocument} />
}
@ -45,6 +49,7 @@ const mapStateToProps = (state, props ) => {
docType: state.docType,
editorState: state.editorState,
documentTitleInput: state.documentTitleInput,
loadingToggle: state.loadingToggle
}
}

View File

@ -2,20 +2,25 @@ import React from 'react'
import { connect } from 'react-redux'
import { Editor } from 'draft-js'
import { setCurrentChapter, setAnnotationNote } from '../actions'
import { setCurrentChapter, setAnnotationNote, toggleLoading } from '../actions'
import DocumentTitle from '../components/documentTitle'
import LoadingSpinner from '../components/loadingSpinner'
const JoyceReaderContent = ({currentDocument, highlightActive, editorState}) =>
<div id="page" className={highlightActive ? 'annotations' : 'hidden_annotations'}>
const JoyceReaderContent = ({currentDocument, highlightToggle, editorState, loadingToggle}) =>
<div id="page" className={highlightToggle ? 'annotations' : 'hidden_annotations'}>
{loadingToggle === true &&
<LoadingSpinner />
}
<DocumentTitle docType={'chapters'} currentDocument={currentDocument} />
<Editor editorState={editorState} readOnly={true} />
</div>
</div>
const mapStateToProps = state => {
return {
currentDocument: state.currentDocument,
highlightActive: state.highlightActive,
editorState: state.editorState
editorState: state.editorState,
highlightToggle: state.highlightToggle,
loadingToggle: state.loadingToggle
}
}

View File

@ -6,7 +6,7 @@ import { HighlightButton } from '../components/button'
import SidebarSpacer from '../components/sidebarSpacer'
const JoyceReaderSidebar = ({chapters, currentDocument, onDocumentClick, highlightActive, onHighlightClick, docType}) =>
<div className='col-md-3' id='sidebar'>
<div className='col-md-3 order-1 order-xs-2 order-sm-2' id='sidebar'>
<div className='d-none d-md-block'>
<SidebarSpacer />
<HighlightButton highlightActive={highlightActive} onHighlightClick={onHighlightClick}/>

View File

@ -0,0 +1,31 @@
import React from 'react'
import { connect } from 'react-redux'
import { Editor } from 'draft-js'
import { SearchButton } from '../components/button'
import { updateSearchInput } from '../actions'
const JoyceSearchContent = ({searchResults, searchInput, onSearchInputChange}) =>
<div id='search_content' className='col-md-11'>
<input type='text' value={searchInput} onChange={onSearchInputChange}/>
<SearchButton />
</div>
const mapStateToProps = state => {
return {
searchResults: state.searchResults,
searchInput: state.searchInput
}
}
const mapDispatchToProps = dispatch => {
return {
onSearchInputChange: searchInput => {
dispatch(updateSearchInput(searchInput))
}
}
}
const JoyceSearchContentContainer = connect(mapStateToProps, mapDispatchToProps)(JoyceSearchContent)
export default JoyceSearchContentContainer

View File

@ -0,0 +1,19 @@
import React from 'react'
import { connect } from 'react-redux'
import Navbar from '../components/navbar'
import JoyceSearchContentContainer from '../containers/joyceSearchContentContainer'
const JoyceSearchPage = () =>
<div>
<Navbar />
<div id='joyce_search' className='container-fluid'>
<div className="row">
<JoyceSearchContentContainer />
</div>
</div>
</div>
const JoyceSearchPageContainer = connect()(JoyceSearchPage)
export default JoyceSearchPageContainer

View File

@ -4,14 +4,14 @@ import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import 'bootstrap'
import reduceDocuments from './reducers/reduceDocuments'
import reduceEditor from './reducers/reduceEditor'
import { getDocumentList, setCurrentDocument, setDocType } from './actions'
import { joyceAPI, logger } from './middleware/'
import { getFirstDocument } from './mixins/firstDocument'
import JoyceEditorPageContainer from './containers/joyceEditorPageContainer'
const docType = 'chapters'
const store = createStore(reduceDocuments, applyMiddleware(joyceAPI, logger))
const store = createStore(reduceEditor, applyMiddleware(joyceAPI, logger))
store.dispatch(setDocType(docType))
ReactDOM.render(

View File

@ -3,7 +3,10 @@ const currentDocument = (state={}, action) => {
case 'GET_DOCUMENT_TEXT':
if (action.status === 'success' && action.state === 'currentDocument') {
return action.data
} else { return state }
} else if (action.status ==='request' && action.state === 'currentDocument') {
return {}
}
else { return state }
case 'CREATE_DOCUMENT':
return {id: null, number: null, title: '', text: ''}
default:

View File

@ -31,6 +31,8 @@ const editorState = (state=blankEditor, action) => {
if (action.status === 'success' && action.state === 'currentDocument') {
const editorState = EditorState.createWithContent(stateFromHTML(action.data.text), decorator)
return editorState
} else if (action.status === 'request' && action.state === 'currentDocument') {
return blankEditor
} else { return state }
case 'CREATE_CHAPTER':
return blankEditor

View File

@ -1,4 +1,4 @@
const highlightActive = (state=false, action) => {
const highlightToggle = (state=false, action) => {
switch(action.type) {
case 'TOGGLE_HIGHLIGHT':
return !state
@ -7,4 +7,4 @@ const highlightActive = (state=false, action) => {
}
}
export default highlightActive
export default highlightToggle

View File

@ -0,0 +1,14 @@
const loadingToggle = (state=true, action) => {
switch(action.type) {
case 'GET_DOCUMENT_TEXT':
if (action.status === 'request' && action.state === 'currentDocument') {
return true
} else if (action.status === 'success' && action.state === 'currentDocument') {
return false
}
default:
return state
}
}
export default loadingToggle

View File

@ -9,6 +9,7 @@ import documentTitleInput from './documentTitleInput'
import editorState from './editorState'
import selectionState from './selectionState'
import annotationNote from './annotationNote'
import loadingToggle from './loadingToggle'
const reduceDocuments = combineReducers({
notes,
@ -19,7 +20,8 @@ const reduceDocuments = combineReducers({
documentTitleInput,
editorState,
docType,
selectionState
selectionState,
loadingToggle
})
export default reduceDocuments

View File

@ -5,7 +5,8 @@ import notes from './notes'
import currentDocument from './currentDocument'
import editorState from './editorState'
import annotationNote from './annotationNote'
import highlightActive from './highlightActive'
import highlightToggle from './highlightToggle'
import loadingToggle from './loadingToggle'
import docType from './docType'
const reduceReader = combineReducers({
@ -15,7 +16,8 @@ const reduceReader = combineReducers({
annotationNote,
docType,
currentDocument,
highlightActive
highlightToggle,
loadingToggle
})
export default reduceReader

View File

@ -0,0 +1,11 @@
import { combineReducers } from 'redux'
import searchResults from './searchResults'
import searchInput from './searchInput'
const reduceSearch = combineReducers({
searchResults,
searchInput
})
export default reduceSearch

View File

@ -0,0 +1,10 @@
const searchInput = (state='', action) => {
switch(action.type) {
case 'UPDATE_SEARCH_INPUT':
return action.data
default:
return state
}
}
export default searchInput

View File

@ -0,0 +1,8 @@
const searchResults = (state={}, action) => {
switch(action.type) {
default:
return state
}
}
export default searchResults

20
src/search.js Normal file
View File

@ -0,0 +1,20 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import 'bootstrap'
import reduceSearch from './reducers/reduceSearch'
import { logger, joyceAPI } from './middleware/'
import JoyceSearchPageContainer from './containers/joyceSearchPageContainer'
// TODO: Pass routing from Flask?
let store = createStore(reduceSearch, applyMiddleware(logger, joyceAPI))
ReactDOM.render(
<Provider store={store}>
<JoyceSearchPageContainer />
</Provider>,
document.getElementById('wrapper')
)

View File

@ -9,6 +9,10 @@
margin-bottom: 0.5%;
}
#editor_metadata h4 {
font-size: 1.75rem
}
#editor_topbar button img {
width: 100%;
height: 100%;

View File

@ -36,6 +36,23 @@ html body {
box-shadow: 2px 5px 10px 1px rgba(0, 0, 0, 0.3);
}
#search_content {
height: 44em;
overflow: hidden;
background-color: rgba(250,250,250,.8);
margin: 0 auto;
padding: 2% 2%;
border: 1px solid $border_color;
border-radius: 5px;
box-shadow: 2px 5px 10px 1px rgba(0, 0, 0, 0.3);
}
.fa_inline {
padding-left: 0.5rem;
}
.loading_spinner {
margin: 0 auto;
text-align:center;
color: $nav_gradient1
}

View File

@ -0,0 +1,22 @@
@import "variables";
@import "window";
@import "navbar";
@import "sidebar";
@import "page";
@import "node_modules/bootstrap/scss/bootstrap";
$fa-font-path: "../../node_modules/font-awesome/fonts";
@import "node_modules/font-awesome/scss/font-awesome";
#joyce_search {
overflow: hidden;
width: 95%;
height: 100%;
background-color: rgba(256,256,256,0.5);
border-left: 1px solid $border_color;
border-right: 1px solid $border_color;
}
#joyce_search > div {
margin-top: 20px;
}