diff options
Diffstat (limited to 'frontend/views/graph/components')
| -rw-r--r-- | frontend/views/graph/components/graph.canvas.js | 138 | ||||
| -rw-r--r-- | frontend/views/graph/components/graph.editor.js | 212 | ||||
| -rw-r--r-- | frontend/views/graph/components/graph.header.js | 31 | ||||
| -rw-r--r-- | frontend/views/graph/components/page.edit.js | 65 | ||||
| -rw-r--r-- | frontend/views/graph/components/page.form.js | 185 | ||||
| -rw-r--r-- | frontend/views/graph/components/page.handle.js | 59 | ||||
| -rw-r--r-- | frontend/views/graph/components/page.new.js | 47 |
7 files changed, 0 insertions, 737 deletions
diff --git a/frontend/views/graph/components/graph.canvas.js b/frontend/views/graph/components/graph.canvas.js deleted file mode 100644 index d10de4f..0000000 --- a/frontend/views/graph/components/graph.canvas.js +++ /dev/null @@ -1,138 +0,0 @@ -import React, { Component } from 'react' - -import { mod, angle } from '../../../util' - -const DEFAULT_MEASUREMENT = { width: 16, height: 16 } -const BACKLINK_SPACING = 10 -const ARROWHEAD_LENGTH = 10 -const GRAPH_LINK_COLOR = "#ff88ff" -const GRAPH_BACKLINK_COLOR = "#88ffff" -const GRAPH_UNHOVER_LINK_COLOR = "#884488" -const GRAPH_UNHOVER_BACKLINK_COLOR = "#448888" - -export default class GraphCanvas extends Component { - constructor(props) { - super(props) - this.canvasRef = React.createRef() - } - - componentDidMount() { - if (this.props.bounds) { - this.draw({}) - } - } - - componentDidUpdate(prevProps) { - this.draw(prevProps) - } - - draw(prevProps) { - const { current: canvas } = this.canvasRef - const { bounds, pages, currentPage, box, measurements, highlightedPageId } = this.props - const { width, height } = bounds - if (prevProps.bounds !== bounds) { - canvas.width = width - canvas.height = height - } - const ctx = canvas.getContext('2d') - ctx.clearRect(0, 0, width, height) - ctx.lineWidth = 2 - const coordsLookup = pages.reduce((a,b) => { - if (currentPage && box && b.id === currentPage.id) { - a[b.id] = { - x: box.x, - y: box.y, - backlinks: new Set([]), - } - } else { - a[b.id] = { - x: b.settings.x, - y: b.settings.y, - backlinks: new Set([]), - } - } - return a - }, {}) - pages.map(page => { - const sourceCoord = coordsLookup[page.id] - page.backlinks.map(tile => { - if (tile.target_page_id <= 0) return - const targetCoord = coordsLookup[tile.page_id] - const isHighlightedPage = !highlightedPageId || highlightedPageId === page.id || highlightedPageId === tile.page_id - let tile_measurement = measurements[tile.page_id] || DEFAULT_MEASUREMENT - let target_measurement = measurements[tile.target_page_id] || DEFAULT_MEASUREMENT - let theta = angle(targetCoord.x, targetCoord.y, sourceCoord.x, sourceCoord.y) - let x1_offset = tile_measurement.width / 2 // * (0.5 - Math.cos(theta)) - let y1_offset = tile_measurement.height / 2 - let x2_offset = target_measurement.width / 2 // (0.5 - Math.cos(theta)) - let y2_offset = target_measurement.height / 2 - // skip duplicate links - if (sourceCoord.backlinks.has(tile.page_id)) { - return - } - /* - if it's pointing right, cos(t) is 1 - if it's pointing left, cos(t) is -1 - */ - // if (Math.abs(Math.cos(theta)) > 0.5) { - // x1_offset += target_measurement.width / 3 * (- Math.cos(theta)) - // x1_offset += target_measurement.height / 4 * (- Math.sin(theta)) - x2_offset += target_measurement.width / 3 * (- Math.cos(theta)) - y2_offset += target_measurement.height / 6 * (- Math.sin(theta)) - // } - // if this is the first time encountering this link... - if (!targetCoord.backlinks.has(tile.target_page_id)) { - sourceCoord.backlinks.add(tile.page_id) - ctx.strokeStyle = isHighlightedPage - ? GRAPH_LINK_COLOR - : GRAPH_UNHOVER_LINK_COLOR - } else { // otherwise this is a two-way link - x1_offset += BACKLINK_SPACING * Math.sin(theta) - y1_offset += BACKLINK_SPACING * Math.cos(theta) - x2_offset += BACKLINK_SPACING * Math.sin(theta) - y2_offset += BACKLINK_SPACING * Math.cos(theta) - // x1_offset += BACKLINK_SPACING * Math.cos(theta + Math.PI /2) - // y1_offset += BACKLINK_SPACING * Math.sin(theta + Math.PI /2) - // x2_offset += BACKLINK_SPACING * Math.cos(theta + Math.PI /2) - // y2_offset += BACKLINK_SPACING * Math.sin(theta + Math.PI /2) - ctx.strokeStyle = isHighlightedPage - ? GRAPH_BACKLINK_COLOR - : GRAPH_UNHOVER_BACKLINK_COLOR - } - ctx.beginPath() - const x1 = targetCoord.x * width + x1_offset - const y1 = targetCoord.y * height + y1_offset - const x2 = sourceCoord.x * width + x2_offset - const y2 = sourceCoord.y * height + y2_offset - this.arrow(ctx, x1, y1, x2, y2) - ctx.stroke() - }) - }) - } - - arrow(ctx, x1, y1, x2, y2) { - const farOffset = 20 - const endOffset = 1 - const theta = angle(x1, y1, x2, y2) - x1 += Math.cos(theta) * 0 - x2 -= Math.cos(theta) * farOffset - y1 += Math.sin(theta) * 0 - y2 -= Math.sin(theta) * farOffset - const xEnd = x2 - Math.cos(theta) * endOffset - const yEnd = y2 - Math.sin(theta) * endOffset - const leftAngle = mod(theta - Math.PI / 6, Math.PI * 2) - const rightAngle = mod(theta + Math.PI / 6, Math.PI * 2) - ctx.moveTo(x1, y1) - ctx.lineTo(xEnd, yEnd) - ctx.moveTo(x2, y2) - ctx.lineTo(x2 - ARROWHEAD_LENGTH * Math.cos(leftAngle), y2 - ARROWHEAD_LENGTH * Math.sin(leftAngle)) - ctx.moveTo(x2, y2) - ctx.lineTo(x2 - ARROWHEAD_LENGTH * Math.cos(rightAngle), y2 - ARROWHEAD_LENGTH * Math.sin(rightAngle)) - } - - render() { - return ( - <canvas ref={this.canvasRef} /> - ) - } -} diff --git a/frontend/views/graph/components/graph.editor.js b/frontend/views/graph/components/graph.editor.js deleted file mode 100644 index f0a5753..0000000 --- a/frontend/views/graph/components/graph.editor.js +++ /dev/null @@ -1,212 +0,0 @@ -import React, { Component } from 'react' -import { Route, Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import { session } from '../../../session' -import actions from '../../../actions' -import * as graphActions from '../graph.actions' - -import { Loader } from '../../../common' -import { clamp, dist, mod, angle } from '../../../util' - -import GraphCanvas from './graph.canvas' -import PageHandle from './page.handle' - -const defaultState = { - dragging: false, - bounds: null, - mouseX: 0, - mouseY: 0, - box: { - x: 0, y: 0, - w: 0, h: 0, - }, - page: null, - highlightedPageId: null, - measurements: {}, -} - -class GraphEditor 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.handleMouseEnter = this.handleMouseEnter.bind(this) - this.handleMouseLeave = this.handleMouseLeave.bind(this) - this.graphRef = React.createRef() - this.measurements = {} - } - - getBoundingClientRect() { - if (!this.graphRef.current) return null - const rect = this.graphRef.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() }) - } - } - - addMeasurement({ id, width, height }) { - this.measurements[id] = { width, height } - this.setState({ - measurements: { ...this.measurements }, - }) - } - - handleWindowResize() { - this.setState({ bounds: this.getBoundingClientRect() }) - } - - handleMouseDown(e, page) { - if (e.shiftKey) { - e.preventDefault() - this.props.graphActions.setHomePageId(this.props.graph.show.res, page) - return - } - 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, - dragging: true, - bounds, - mouseX, - mouseY, - box: { - x, y, - w, h, - }, - initialBox: { - x, y, - w, h, - } - }) - } - - handleMouseMove(e) { - const { - dragging, - bounds, mouseX, mouseY, initialBox, box - } = this.state - if (dragging) { - 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, bounds, initialBox, box, page } = this.state - if (!dragging) return - e.preventDefault() - const { width, height } = bounds - const { x, y, w, h } = box - let url = window.location.pathname - this.setState({ - page: null, - box: null, - initialBox: null, - dragging: false, - }) - if (dist(width * x, height * y, width * initialBox.x, height * initialBox.y) < 3) return - const updatedPage = { - ...page, - settings: { - ...page.settings, - x, y, - } - } - this.props.graphActions.updateGraphPage(updatedPage) - actions.page.update(updatedPage) - } - - handleMouseEnter(e, page) { - this.setState({ highlightedPageId: page.id }) - } - handleMouseLeave(e, page) { - this.setState({ highlightedPageId: null }) - } - - render(){ - // console.log(this.props.graph.show.res) - const { page: currentPage, box, measurements, highlightedPageId } = this.state - const { res: graph } = this.props.graph.show - // console.log(res.pages) - return ( - <div className='graph' ref={this.graphRef}> - <GraphCanvas - bounds={this.state.bounds} - pages={graph.pages} - currentPage={currentPage} - highlightedPageId={highlightedPageId} - measurements={measurements} - box={box} - /> - {this.state.bounds && graph.pages.map(page => ( - <PageHandle - key={page.id} - graph={graph} - page={page} - bounds={this.state.bounds} - box={currentPage && page.id === currentPage.id && box} - onMouseDown={e => this.handleMouseDown(e, page)} - onMouseEnter={e => this.handleMouseEnter(e, page)} - onMouseLeave={e => this.handleMouseLeave(e, page)} - onMeasure={measurement => this.addMeasurement(measurement)} - /> - ))} - </div> - ) - } -} - -const mapStateToProps = state => ({ - graph: state.graph, -}) - -const mapDispatchToProps = dispatch => ({ - graphActions: bindActionCreators({ ...graphActions }, 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 deleted file mode 100644 index 46ad962..0000000 --- a/frontend/views/graph/components/graph.header.js +++ /dev/null @@ -1,31 +0,0 @@ -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.toggleAddPageForm()}>+ 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 deleted file mode 100644 index 5bc64d6..0000000 --- a/frontend/views/graph/components/page.edit.js +++ /dev/null @@ -1,65 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { connect } from 'react-redux' -import { bindActionCreators } from 'redux' - -import { history } from '../../../store' -import actions from '../../../actions' -import * as siteActions from '../../site/site.actions' -import * as graphActions from '../../graph/graph.actions' - -import { Loader } from '../../../common' - -import PageForm from '../components/page.form' - -class PageEdit extends Component { - componentDidMount() { - // actions.page.show(this.props.match.params.id) - } - - handleSubmit(data) { - const { path: graphPath } = this.props.graph.show.res - const { path: oldPagePath } = this.props.page.show.res - const { path: newPagePath } = data - actions.page.update(data) - .then(response => { - // console.log(response) - actions.site.setSiteTitle(response.res.title) - this.props.graphActions.hideEditPageForm() - if (oldPagePath !== newPagePath) { - const newPath = '/' + graphPath + '/' + newPagePath - history.push(newPath) - } - }) - } - - 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 => ({ - siteActions: bindActionCreators({ ...siteActions }, dispatch), - graphActions: bindActionCreators({ ...graphActions }, 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 deleted file mode 100644 index 38fee18..0000000 --- a/frontend/views/graph/components/page.form.js +++ /dev/null @@ -1,185 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' - -import { session } from '../../../session' - -import { TextInput, ColorInput, LabelDescription, TextArea, Checkbox, SubmitButton, Loader } from '../../../common' - -const newPage = (data) => ({ - path: '', - title: '', - username: session('username'), - description: '', - settings: { - x: 0.05, - y: 0.05, - background_color: '#000000', - }, - ...data, -}) - -export default class PageForm extends Component { - state = { - title: "", - submitTitle: "", - data: { ...newPage() }, - errorFields: new Set([]), - } - - componentDidMount() { - const { graph, 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({ 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, - } - }) - } - - handleSettingsChange(e) { - const { name, value } = e.target - this.setState({ - data: { - ...this.state.data, - settings: { - ...this.state.data.settings, - [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 page - // session.set('username', data.username) - } else { - validData.id = data.id - } - console.log('submit', validData) - onSubmit(validData) - } - } - - handleDelete() { - const { data } = this.state - console.log(data) - if (confirm('Really delete this page?')) { - actions.page.delete(page_id) - } - } - - 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" - /> - <ColorInput - title='BG' - name='background_color' - data={data.settings} - onChange={this.handleSettingsChange.bind(this)} - autoComplete="off" - /> - <TextArea - title="Description" - name="description" - data={data} - onChange={this.handleChange.bind(this)} - /> - <div className='row buttons'> - <SubmitButton - title={submitTitle} - onClick={this.handleSubmit.bind(this)} - /> - {!isNew && - <SubmitButton - title={'Delete'} - className='destroy' - onClick={this.handleDelete.bind(this)} - /> - } - </div> - {!!errorFields.size && - <label> - <span></span> - <span>Please complete the required fields =)</span> - </label> - } - </form> - </div> - ) - } -} diff --git a/frontend/views/graph/components/page.handle.js b/frontend/views/graph/components/page.handle.js deleted file mode 100644 index 7093399..0000000 --- a/frontend/views/graph/components/page.handle.js +++ /dev/null @@ -1,59 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' - -import { history } from '../../../store' - -export default class PageHandle extends Component { - constructor(props){ - super(props) - this.ref = React.createRef() - } - componentDidMount(){ - this.measure() - } - componentDidUpdate(prevProps){ - if (this.props.page.title !== prevProps.page.title) { - this.measure() - } - } - measure() { - const { offsetWidth: width, offsetHeight: height } = this.ref.current - const { id } = this.props.page - // console.log(id, width, height) - this.props.onMeasure({ id, width, height }) - } - render() { - const { graph, page, bounds, box, onMouseDown, onMouseEnter, onMouseLeave } = this.props - let style; - if (box) { - style = { - top: (bounds.height) * box.y, - left: (bounds.width) * box.x, - } - } else { - style = { - top: (bounds.height) * Math.min(page.settings.y, 0.95), - left: (bounds.width) * Math.min(page.settings.x, 0.95), - } - } - const className = (graph.home_page_id === page.id) - ? 'handle homepage' - : 'handle' - const url = '/' + graph.path + '/' + page.path - // console.log(style) - return ( - <div - className={className} - ref={this.ref} - onMouseDown={onMouseDown} - onMouseEnter={onMouseEnter} - onMouseLeave={onMouseLeave} - onDoubleClick={() => history.push(url)} - style={style} - > - {page.path} - <Link to={url}>{'>'}</Link> - </div> - ) - } -} diff --git a/frontend/views/graph/components/page.new.js b/frontend/views/graph/components/page.new.js deleted file mode 100644 index bc74358..0000000 --- a/frontend/views/graph/components/page.new.js +++ /dev/null @@ -1,47 +0,0 @@ -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) - 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', err) - }) - } - - 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) |
