summaryrefslogtreecommitdiff
path: root/frontend/views/graph/components
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2020-09-26 14:56:02 +0200
committerJules Laplace <julescarbon@gmail.com>2020-09-26 14:56:02 +0200
commita17b76ac75f506f5da6fe8adf9c36632b60d4226 (patch)
treeabb0af0c4409b830dea2ef808c146223ee973933 /frontend/views/graph/components
parent2231a6e1c05b07bb7ec5906716aedec93d02429c (diff)
refactor to use app-rooted js imports
Diffstat (limited to 'frontend/views/graph/components')
-rw-r--r--frontend/views/graph/components/graph.canvas.js138
-rw-r--r--frontend/views/graph/components/graph.editor.js212
-rw-r--r--frontend/views/graph/components/graph.header.js31
-rw-r--r--frontend/views/graph/components/page.edit.js65
-rw-r--r--frontend/views/graph/components/page.form.js185
-rw-r--r--frontend/views/graph/components/page.handle.js59
-rw-r--r--frontend/views/graph/components/page.new.js47
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)