diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-06-02 20:25:23 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-06-02 20:25:23 +0200 |
| commit | 04f7efe6c05153cbd1613e7b0c78b3b6478ae39b (patch) | |
| tree | e181c4415a271a60e505fc536219eb5e72b6f6fb /frontend | |
| parent | f6d7fcbb50ef57f1f7d7ca8cacd711ffd576e600 (diff) | |
new page form
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/app.js | 17 | ||||
| -rw-r--r-- | frontend/common/app.css | 21 | ||||
| -rw-r--r-- | frontend/types.js | 4 | ||||
| -rw-r--r-- | frontend/views/graph/components/graph.editor.js | 35 | ||||
| -rw-r--r-- | frontend/views/graph/components/graph.header.js | 31 | ||||
| -rw-r--r-- | frontend/views/graph/components/page.edit.js | 55 | ||||
| -rw-r--r-- | frontend/views/graph/components/page.form.js | 138 | ||||
| -rw-r--r-- | frontend/views/graph/components/page.new.js | 46 | ||||
| -rw-r--r-- | frontend/views/graph/graph.actions.js | 9 | ||||
| -rw-r--r-- | frontend/views/graph/graph.container.js | 28 | ||||
| -rw-r--r-- | frontend/views/graph/graph.css | 53 | ||||
| -rw-r--r-- | frontend/views/index/graph.reducer.js | 21 | ||||
| -rw-r--r-- | frontend/views/index/index.container.js | 12 | ||||
| -rw-r--r-- | frontend/views/site/site.actions.js | 11 |
14 files changed, 445 insertions, 36 deletions
diff --git a/frontend/app.js b/frontend/app.js index 5be0c0f..d6bbcb8 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -2,8 +2,6 @@ import React, { Component } from 'react' import { ConnectedRouter } from 'connected-react-router' import { Route } from 'react-router' -import { Header } from './common' - // import actions from './actions' import * as views from './views' @@ -31,15 +29,12 @@ export default class App extends Component { return ( <ConnectedRouter history={this.props.history}> <div className='app'> - <Header /> - <div className='body'> - {viewList} - <Route exact key='root' path='/' render={() => { - // redirect to index!! - setTimeout(() => this.props.history.push('/index'), 10) - return null - }} /> - </div> + {viewList} + <Route exact key='root' path='/' render={() => { + // redirect to index!! + setTimeout(() => this.props.history.push('/index'), 10) + return null + }} /> </div> </ConnectedRouter> ) diff --git a/frontend/common/app.css b/frontend/common/app.css index 08b2408..a02d0e3 100644 --- a/frontend/common/app.css +++ b/frontend/common/app.css @@ -24,7 +24,8 @@ body { height: 100%; width: 100%; } -.app { +.app, +.app > div { display: flex; flex-direction: column; height: 100%; @@ -33,6 +34,7 @@ body { .app .body { display: flex; flex-grow: 1; + position: relative; } .row { @@ -87,12 +89,24 @@ header a { font-weight: 500; } header > div:first-child { - font-weight: bold; display: flex; justify-content: flex-start; align-items: center; padding-left: 1.5rem; } +header > div:last-child { + padding-right: 1.5rem; +} +header > div > button { + padding: 0.25rem; + margin: 0 0 0 0.5rem; + border-color: #8df; + color: #8df; +} +header > div > button:hover { + border-color: #fff; + color: #fff; +} header .vcat-btn { font-size: 0.875rem; padding-left: 0.5rem; @@ -125,9 +139,6 @@ header a.navbar-brand { font-size: .8rem; } -header > div:last-child { - padding-right: 8px; -} header .username { cursor: pointer; } diff --git a/frontend/types.js b/frontend/types.js index b326994..d7119fc 100644 --- a/frontend/types.js +++ b/frontend/types.js @@ -1,7 +1,9 @@ import { with_type, crud_type } from './api/crud.types' export const api = crud_type('api', []) -export const graph = crud_type('graph', []) +export const graph = crud_type('graph', [ + 'show_add_page_form', 'hide_add_page_form', +]) export const page = crud_type('page', []) export const tile = crud_type('tile', []) export const upload = crud_type('upload', []) diff --git a/frontend/views/graph/components/graph.editor.js b/frontend/views/graph/components/graph.editor.js new file mode 100644 index 0000000..aa5d751 --- /dev/null +++ b/frontend/views/graph/components/graph.editor.js @@ -0,0 +1,35 @@ +import React, { Component } from 'react' +import { Route } from 'react-router-dom' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import { session } from '../../../session' + +import { Loader } from '../../../common' + +class GraphEditor extends Component { + state = { + } + + render(){ + console.log(this.props.graph) + const { res } = this.props.graph.show + return ( + <div className='graph'> + {res.pages.map(page => { + console.log(page) + })} + </div> + ) + } +} + +const mapStateToProps = state => ({ + graph: state.graph, +}) + +const mapDispatchToProps = dispatch => ({ + // uploadActions: bindActionCreators({ ...uploadActions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(GraphEditor) diff --git a/frontend/views/graph/components/graph.header.js b/frontend/views/graph/components/graph.header.js new file mode 100644 index 0000000..6a927ec --- /dev/null +++ b/frontend/views/graph/components/graph.header.js @@ -0,0 +1,31 @@ +import React from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' +import { Link } from 'react-router-dom' + +import * as graphActions from '../graph.actions' + +function GraphHeader(props) { + return ( + <header> + <div> + <Link to="/" className="logo"><b>{props.site.siteTitle}</b></Link> + </div> + <div> + <button onClick={() => props.graphActions.showAddPageForm()}>+ Add page</button> + </div> + </header> + ) +} + +const mapStateToProps = (state) => ({ + // auth: state.auth, + site: state.site, + // isAuthenticated: state.auth.isAuthenticated, +}) + +const mapDispatchToProps = (dispatch) => ({ + graphActions: bindActionCreators({ ...graphActions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(GraphHeader) diff --git a/frontend/views/graph/components/page.edit.js b/frontend/views/graph/components/page.edit.js new file mode 100644 index 0000000..f4f351b --- /dev/null +++ b/frontend/views/graph/components/page.edit.js @@ -0,0 +1,55 @@ +import React, { Component } from 'react' +import { Link } from 'react-router-dom' +import { connect } from 'react-redux' + +import { history } from '../../../store' +import actions from '../../../actions' + +import { Loader } from '../../../common' + +import PageForm from '../components/page.form' + +class PageEdit extends Component { + componentDidMount() { + console.log(this.props.match.params.id) + actions.page.show(this.props.match.params.id) + } + + handleSubmit(data) { + actions.page.update(data) + .then(response => { + // response + console.log(response) + history.push('/' + data.path) + }) + } + + render() { + const { show } = this.props.page + if (show.loading || !show.res) { + return ( + <div className='form'> + <Loader /> + </div> + ) + } + return ( + <PageForm + data={show.res} + graph={this.props.graph.show.res} + onSubmit={this.handleSubmit.bind(this)} + /> + ) + } +} + +const mapStateToProps = state => ({ + graph: state.graph, + page: state.page, +}) + +const mapDispatchToProps = dispatch => ({ + // searchActions: bindActionCreators({ ...searchActions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(PageEdit) diff --git a/frontend/views/graph/components/page.form.js b/frontend/views/graph/components/page.form.js new file mode 100644 index 0000000..abaf934 --- /dev/null +++ b/frontend/views/graph/components/page.form.js @@ -0,0 +1,138 @@ +import React, { Component } from 'react' +import { Link } from 'react-router-dom' + +import { session } from '../../../session' + +import { TextInput, LabelDescription, TextArea, Checkbox, SubmitButton, Loader } from '../../../common' + +const newPage = () => ({ + path: '', + title: '', + username: session('username'), + description: '', +}) + +export default class PageForm extends Component { + state = { + title: "", + submitTitle: "", + data: { ...newPage() }, + errorFields: new Set([]), + } + + componentDidMount() { + const { data, isNew } = this.props + const title = isNew ? 'new page' : 'editing ' + data.title + const submitTitle = isNew ? "Create Page" : "Save Changes" + this.setState({ + title, + submitTitle, + errorFields: new Set([]), + data: { + ...newPage(), + ...data + }, + }) + } + + handleChange(e) { + const { errorFields } = this.state + const { name, value } = e.target + if (errorFields.has(name)) { + errorFields.delete(name) + } + this.setState({ + errorFields, + data: { + ...this.state.data, + [name]: value, + } + }) + } + + handleSelect(name, value) { + const { errorFields } = this.state + if (errorFields.has(name)) { + errorFields.delete(name) + } + this.setState({ + errorFields, + data: { + ...this.state.data, + [name]: value, + } + }) + } + + handleSubmit(e) { + e.preventDefault() + const { isNew, onSubmit } = this.props + const { data } = this.state + const requiredKeys = "title username path description".split(" ") + const validKeys = "title username path description".split(" ") + const validData = validKeys.reduce((a,b) => { a[b] = data[b]; return a }, {}) + const errorFields = requiredKeys.filter(key => !validData[key]) + if (errorFields.length) { + console.log('error', errorFields, validData) + this.setState({ errorFields: new Set(errorFields) }) + } else { + if (isNew) { + // side effect: set username if we're creating a new page + // session.set('username', data.username) + } else { + validData.id = data.id + } + console.log('submit', validData) + onSubmit(validData) + } + } + + render() { + const { graph, isNew } = this.props + const { title, submitTitle, errorFields, data } = this.state + return ( + <div className='box'> + <h1>{title}</h1> + <form onSubmit={this.handleSubmit.bind(this)}> + <TextInput + title="Path" + name="path" + required + data={data} + error={errorFields.has('path')} + onChange={this.handleChange.bind(this)} + autoComplete="off" + /> + <LabelDescription> + {'Page URL: /' + graph.path + '/'}<b>{data.path}</b> + </LabelDescription> + <TextInput + title="Title" + name="title" + required + data={data} + error={errorFields.has('title')} + onChange={this.handleChange.bind(this)} + autoComplete="off" + /> + <TextArea + title="Description" + name="description" + data={data} + onChange={this.handleChange.bind(this)} + /> + <SubmitButton + title={submitTitle} + onClick={this.handleSubmit.bind(this)} + /> + {!!errorFields.size && + <label> + <span></span> + <span>Please complete the required fields =)</span> + </label> + } + </form> + </div> + ) + } +} diff --git a/frontend/views/graph/components/page.new.js b/frontend/views/graph/components/page.new.js new file mode 100644 index 0000000..5b72f89 --- /dev/null +++ b/frontend/views/graph/components/page.new.js @@ -0,0 +1,46 @@ +import React, { Component } from 'react' +import { Link } from 'react-router-dom' +import { connect } from 'react-redux' + +import { history } from '../../../store' +import actions from '../../../actions' + +import PageForm from '../components/page.form' + +class PageNew extends Component { + handleSubmit(data) { + console.log(data) + actions.page.create(data) + .then(res => { + console.log(res) + if (res.res && res.res.id) { + history.push('/' + res.res.path) + } + }) + .catch(err => { + console.error('error') + }) + } + + render() { + return ( + <PageForm + isNew + graph={this.props.graph.show.res} + data={{}} + onSubmit={this.handleSubmit.bind(this)} + /> + ) + } +} + +const mapStateToProps = state => ({ + graph: state.graph, + page: state.page, +}) + +const mapDispatchToProps = dispatch => ({ + // searchActions: bindActionCreators({ ...searchActions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(PageNew) diff --git a/frontend/views/graph/graph.actions.js b/frontend/views/graph/graph.actions.js new file mode 100644 index 0000000..2e6ca54 --- /dev/null +++ b/frontend/views/graph/graph.actions.js @@ -0,0 +1,9 @@ +import * as types from '../../types' + +export const showAddPageForm = () => dispatch => { + dispatch({ type: types.graph.show_add_page_form }) +} + +export const hideAddPageForm = () => dispatch => { + dispatch({ type: types.graph.hide_add_page_form }) +} diff --git a/frontend/views/graph/graph.container.js b/frontend/views/graph/graph.container.js index 40e2e6a..5646d71 100644 --- a/frontend/views/graph/graph.container.js +++ b/frontend/views/graph/graph.container.js @@ -10,7 +10,11 @@ import { Loader } from '../../common' // import * as uploadActions from './upload.actions' -// import GraphIndex from './containers/graph.index' +import PageNew from './components/page.new' +import PageEdit from './components/page.edit' + +import GraphHeader from './components/graph.header' +import GraphEditor from './components/graph.editor' class GraphContainer extends Component { componentDidMount() { @@ -39,22 +43,32 @@ class GraphContainer extends Component { } render() { if (!this.shouldShowGraph()) return null - if (this.props.graph.show.loading) { + if (!this.props.graph.show.res || this.props.graph.show.loading) { return ( - <div className='index'> - <Loader /> + <div> + <GraphHeader /> + <div className='body'> + <div className='graph loading'> + <Loader /> + </div> + </div> </div> ) } - console.log(this.props.graph.show) return ( - <div className='index'> + <div> + <GraphHeader /> + <div className='body'> + <GraphEditor /> + {this.props.graph.editor.addingPage && <PageNew />} + {this.props.graph.editor.editingPage && <PageEdit />} + </div> </div> ) } } - // <Route exact path='/:graph_name' component={GraphView} /> +// <Route exact path='/:graph_name' component={GraphView} /> const mapStateToProps = state => ({ graph: state.graph, }) diff --git a/frontend/views/graph/graph.css b/frontend/views/graph/graph.css index 0a490f9..1bef514 100644 --- a/frontend/views/graph/graph.css +++ b/frontend/views/graph/graph.css @@ -1,3 +1,52 @@ -* { - +.graph.loading { + padding: 1rem; +} +.graph { + position: relative; + width: 100%; + height: 100%; + overflow: auto; + background: linear-gradient( + -45deg, + rgba(0, 0, 64, 0.2), + rgba(128, 0, 64, 0.2) + ); +} + +.box { + width: 15rem; + position: absolute; + top: 1rem; + right: 1rem; + padding: 0.5rem; + background: rgba(64,12,64,0.9); + border: 2px solid #000; + box-shadow: 2px 2px 4px rgba(0,0,0,0.5); +} + +.box h1, +.box h2 { + font-size: 1rem; + margin: 0 0 0.25rem 0; +} +.box form label { + display: flex; + flex-direction: column; + align-items: flex-start; + margin-bottom: 0.25rem; +} +.box input[type=text], +.box input[type=number], +.box input[type=password] { + padding: 0.25rem; + max-width: 100%; + border-color: #888; +} +.box textarea { + max-width: 100%; + height: 5rem; + border-color: #888; +} +.box .text.description span:first-child { + display: none; }
\ No newline at end of file diff --git a/frontend/views/index/graph.reducer.js b/frontend/views/index/graph.reducer.js index 612ac14..7e573e1 100644 --- a/frontend/views/index/graph.reducer.js +++ b/frontend/views/index/graph.reducer.js @@ -4,6 +4,9 @@ import * as types from '../../types' import { crudState, crudReducer } from '../../api/crud.reducer' const initialState = crudState('graph', { + editor: { + addingPage: true, + }, options: { } }) @@ -14,6 +17,24 @@ export default function graphReducer(state = initialState, action) { // console.log(action.type, action) state = reducer(state, action) switch (action.type) { + case types.graph.show_add_page_form: + return { + ...state, + editor: { + ...state.editor, + addingPage: true, + } + } + + case types.graph.hide_add_page_form: + return { + ...state, + editor: { + ...state.editor, + addingPage: false, + } + } + default: return state } diff --git a/frontend/views/index/index.container.js b/frontend/views/index/index.container.js index 1e2326b..2da89cb 100644 --- a/frontend/views/index/index.container.js +++ b/frontend/views/index/index.container.js @@ -6,6 +6,7 @@ import { connect } from 'react-redux' import './index.css' import actions from '../../actions' +import { Header } from '../../common' // import * as uploadActions from './upload.actions' import GraphIndex from './containers/graph.index' @@ -18,10 +19,13 @@ class Container extends Component { } render() { return ( - <div className='index'> - <Route exact path='/index/new' component={GraphNew} /> - <Route exact path='/index/:id/edit' component={GraphEdit} /> - <Route exact path='/index' component={GraphIndex} /> + <div className='body'> + <Header /> + <div className='index'> + <Route exact path='/index/new' component={GraphNew} /> + <Route exact path='/index/:id/edit' component={GraphEdit} /> + <Route exact path='/index' component={GraphIndex} /> + </div> </div> ) } diff --git a/frontend/views/site/site.actions.js b/frontend/views/site/site.actions.js index 74ce72d..b5f0cbe 100644 --- a/frontend/views/site/site.actions.js +++ b/frontend/views/site/site.actions.js @@ -1,9 +1,8 @@ import * as types from '../../types' -import { store, history } from '../../store' -import { api, post, pad, preloadImage } from '../../util' -import actions from '../../actions' -import { session } from '../../session' +// import actions from '../../actions' +// import { session } from '../../session' -export const setSiteTitle = title => dispatch => ( +export const setSiteTitle = title => dispatch => { + document.querySelector('title').innerText = title dispatch({ type: types.site.set_site_title, payload: title }) -)
\ No newline at end of file +} |
