diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-06-02 22:34:16 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-06-02 22:34:16 +0200 |
| commit | abd277bc02d36570038de4c0646a672f5585fa84 (patch) | |
| tree | 91a8955ce1838892dc55389c920370a06ac04a91 /frontend/views/page/components | |
| parent | 70a29089cdb05df27cf50b50fcbc2e53d8975571 (diff) | |
stubbing in page editor
Diffstat (limited to 'frontend/views/page/components')
| -rw-r--r-- | frontend/views/page/components/page.editor.js | 196 | ||||
| -rw-r--r-- | frontend/views/page/components/page.header.js | 31 | ||||
| -rw-r--r-- | frontend/views/page/components/tile.edit.js | 55 | ||||
| -rw-r--r-- | frontend/views/page/components/tile.form.js | 143 | ||||
| -rw-r--r-- | frontend/views/page/components/tile.new.js | 47 |
5 files changed, 472 insertions, 0 deletions
diff --git a/frontend/views/page/components/page.editor.js b/frontend/views/page/components/page.editor.js new file mode 100644 index 0000000..952e45c --- /dev/null +++ b/frontend/views/page/components/page.editor.js @@ -0,0 +1,196 @@ +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 actions from '../../../actions' +import * as pageActions from '../page.actions' + +import { Loader } from '../../../common' +import { clamp } from '../../../util' + +const defaultState = { + dragging: false, + bounds: null, + mouseX: 0, + mouseY: 0, + box: { + x: 0, y: 0, + w: 0, h: 0, + }, + tile: null, +} + +class PageEditor extends Component { + state = { + ...defaultState, + } + + constructor() { + super() + // bind these events in the constructor, so we can remove event listeners later + this.handleMouseDown = this.handleMouseDown.bind(this) + this.handleMouseMove = this.handleMouseMove.bind(this) + this.handleMouseUp = this.handleMouseUp.bind(this) + this.handleWindowResize = this.handleWindowResize.bind(this) + this.pageRef = React.createRef() + } + + getBoundingClientRect() { + if (!this.pageRef.current) return null + const rect = this.pageRef.current.getBoundingClientRect() + const scrollTop = document.body.scrollTop || document.body.parentNode.scrollTop + const scrollLeft = document.body.scrollLeft || document.body.parentNode.scrollLeft + const bounds = { + top: rect.top + scrollTop, + left: rect.left + scrollLeft, + width: rect.width, + height: rect.height, + } + // console.log(bounds) + return bounds + } + + componentDidMount() { + document.body.addEventListener('mousemove', this.handleMouseMove) + document.body.addEventListener('mouseup', this.handleMouseUp) + window.addEventListener('resize', this.handleWindowResize) + this.setState({ bounds: this.getBoundingClientRect() }) + } + + componentDidUpdate(prevProps) { + if (!this.state.bounds) { + this.setState({ bounds: this.getBoundingClientRect() }) + } + } + + handleWindowResize() { + this.setState({ bounds: this.getBoundingClientRect() }) + } + + handleMouseDown(e, page) { + const bounds = this.getBoundingClientRect() + const mouseX = e.pageX + const mouseY = e.pageY + let w = 128 / bounds.width + let h = 16 / bounds.height + let { x, y } = page.settings + x = clamp(x, 0, 1) + y = clamp(y, 0, 1) + this.setState({ + page, + draggingBox: true, + bounds, + mouseX, + mouseY, + box: { + x, y, + w, h, + }, + initialBox: { + x, y, + w, h, + } + }) + } + + handleMouseMove(e) { + const { + dragging, draggingBox, + bounds, mouseX, mouseY, initialBox, box + } = this.state + if (draggingBox) { + e.preventDefault() + let { x, y, w, h } = initialBox + let dx = (e.pageX - mouseX) / bounds.width + let dy = (e.pageY - mouseY) / bounds.height + this.setState({ + box: { + x: clamp(x + dx, 0, 1.0 - w), + y: clamp(y + dy, 0, 1.0 - h), + w, h, + } + }) + } + } + + handleMouseUp(e) { + // const { actions } = this.props + const { dragging, draggingBox, bounds, box, page } = this.state + if (!dragging && !draggingBox) return + e.preventDefault() + const { x, y, w, h } = box + let url = window.location.pathname + this.setState({ + page: null, + box: null, + initialBox: null, + dragging: false, + draggingBox: false, + }) + // console.log(page) + const updatedTile = { + ...tile, + settings: { + ...tile.settings, + x, y, + } + } + this.props.pageActions.updatePageTile(updatedTile) + actions.tile.update(updatedTile) + } + + render(){ + // console.log(this.props.page.show.res) + const currentTile = this.state.tile + const currentBox = this.state.box + const { res } = this.props.page.show + // console.log(res.tiles) + return ( + <div className='page' ref={this.pageRef}> + {this.state.bounds && res.tiles.map(tile => ( + <TileHandle + key={tile.id} + tile={tile} + bounds={this.state.bounds} + box={currentTile && tile.id === currentTile.id && currentBox} + onMouseDown={e => this.handleMouseDown(e, tile)} + /> + ))} + </div> + ) + } +} + +const TileHandle = ({ tile, bounds, box, onMouseDown }) => { + let style; + if (box) { + style = { + top: (bounds.height) * box.y, + left: (bounds.width) * box.x, + } + } else { + style = { + top: (bounds.height) * Math.min(tile.settings.y, 0.95), + left: (bounds.width) * Math.min(tile.settings.x, 0.95), + } + } + // console.log(style) + return ( + <div className='handle' onMouseDown={onMouseDown} style={style}> + {tile.title} + </div> + ) +} + +const mapStateToProps = state => ({ + graph: state.graph, + page: state.page, +}) + +const mapDispatchToProps = dispatch => ({ + pageActions: bindActionCreators({ ...pageActions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(PageEditor) diff --git a/frontend/views/page/components/page.header.js b/frontend/views/page/components/page.header.js new file mode 100644 index 0000000..a6c47ee --- /dev/null +++ b/frontend/views/page/components/page.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 pageActions from '../page.actions' + +function PageHeader(props) { + return ( + <header> + <div> + <Link to="/" className="logo"><b>{props.site.siteTitle}</b></Link> + </div> + <div> + <button onClick={() => props.pageActions.showAddTileForm()}>+ Add tile</button> + </div> + </header> + ) +} + +const mapStateToProps = (state) => ({ + // auth: state.auth, + site: state.site, + // isAuthenticated: state.auth.isAuthenticated, +}) + +const mapDispatchToProps = (dispatch) => ({ + pageActions: bindActionCreators({ ...pageActions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(PageHeader) diff --git a/frontend/views/page/components/tile.edit.js b/frontend/views/page/components/tile.edit.js new file mode 100644 index 0000000..3420505 --- /dev/null +++ b/frontend/views/page/components/tile.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 TileForm from '../components/tile.form' + +class TileEdit extends Component { + componentDidMount() { + console.log(this.props.match.params.id) + actions.tile.show(this.props.match.params.id) + } + + handleSubmit(data) { + actions.tile.update(data) + .then(response => { + // response + console.log(response) + }) + } + + render() { + const { show } = this.props.tile + if (show.loading || !show.res) { + return ( + <div className='form'> + <Loader /> + </div> + ) + } + return ( + <TileForm + data={show.res} + graph={this.props.graph.show.res} + onSubmit={this.handleSubmit.bind(this)} + /> + ) + } +} + +const mapStateToProps = state => ({ + graph: state.graph, + page: state.page, + tile: state.tile, +}) + +const mapDispatchToProps = dispatch => ({ + // searchActions: bindActionCreators({ ...searchActions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(TileEdit) diff --git a/frontend/views/page/components/tile.form.js b/frontend/views/page/components/tile.form.js new file mode 100644 index 0000000..1e75cd8 --- /dev/null +++ b/frontend/views/page/components/tile.form.js @@ -0,0 +1,143 @@ +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 newTile = (data) => ({ + path: '', + title: '', + username: session('username'), + description: '', + settings: { + x: 0.05, y: 0.05, + }, + ...data, +}) + +export default class TileForm extends Component { + state = { + title: "", + submitTitle: "", + data: { ...newTile() }, + errorFields: new Set([]), + } + + componentDidMount() { + const { graph, data, isNew } = this.props + const title = isNew ? 'new tile' : 'editing ' + data.title + const submitTitle = isNew ? "Create Tile" : "Save Changes" + this.setState({ + title, + submitTitle, + errorFields: new Set([]), + data: { + ...newTile({ graph_id: graph.id }), + ...data, + }, + }) + } + + handleChange(e) { + const { errorFields } = this.state + const { name, value } = e.target + if (errorFields.has(name)) { + errorFields.delete(name) + } + let sanitizedValue = value + if (name === 'path') { + sanitizedValue = sanitizedValue.toLowerCase().replace(/ /, '-').replace(/[!@#$%^&*()[\]{}]/, '-').replace(/-+/, '-') + } + this.setState({ + errorFields, + data: { + ...this.state.data, + [name]: sanitizedValue, + } + }) + } + + 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 = "path title".split(" ") + const validKeys = "graph_id path title username description settings".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 tile + // 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" + /> + <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/page/components/tile.new.js b/frontend/views/page/components/tile.new.js new file mode 100644 index 0000000..7445f27 --- /dev/null +++ b/frontend/views/page/components/tile.new.js @@ -0,0 +1,47 @@ +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 TileForm from '../components/tile.form' + +class TileNew extends Component { + handleSubmit(data) { + console.log(data) + actions.tile.create(data) + .then(res => { + console.log(res) + const graph = this.props.graph.show.res + if (res.res && res.res.id) { + history.push('/' + graph.path + '/' + res.res.path) + } + }) + .catch(err => { + console.error('error') + }) + } + + render() { + return ( + <TileForm + 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)(TileNew) |
