From 2db55c3d261ddee52019bbd06dc5f6545db39c16 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Tue, 2 Jun 2020 16:09:59 +0200 Subject: form for making a new graph. add username field to db --- README.md | 10 +- cli/app/sql/common.py | 1 - cli/app/sql/models/graph.py | 2 + cli/app/sql/models/page.py | 2 + .../sql/versions/202006011943_adding_database.py | 66 --------- .../sql/versions/202006011944_adding_uploads.py | 37 ----- .../sql/versions/202006021608_creating_database.py | 78 +++++++++++ frontend/app.js | 20 ++- frontend/common/app.css | 37 +++-- frontend/common/form.component.js | 8 ++ frontend/common/form.css | 61 ++++++--- frontend/common/header.component.js | 14 +- frontend/common/index.js | 1 + frontend/index.js | 1 + frontend/views/index.js | 3 +- frontend/views/index/components/graph.form.js | 149 +++++++++++++++++++++ frontend/views/index/containers/graph.edit.js | 53 ++++++++ frontend/views/index/containers/graph.index.js | 28 ++++ frontend/views/index/containers/graph.new.js | 44 ++++++ frontend/views/index/index.container.js | 33 +++++ frontend/views/index/index.css | 18 +++ frontend/views/upload/index.js | 3 - frontend/views/upload/upload.container.js | 2 + static/index.html | 13 ++ 24 files changed, 520 insertions(+), 164 deletions(-) delete mode 100644 cli/app/sql/versions/202006011943_adding_database.py delete mode 100644 cli/app/sql/versions/202006011944_adding_uploads.py create mode 100644 cli/app/sql/versions/202006021608_creating_database.py create mode 100644 frontend/views/index/components/graph.form.js create mode 100644 frontend/views/index/containers/graph.edit.js create mode 100644 frontend/views/index/containers/graph.index.js create mode 100644 frontend/views/index/containers/graph.new.js create mode 100644 frontend/views/index/index.container.js create mode 100644 frontend/views/index/index.css delete mode 100644 frontend/views/upload/index.js create mode 100644 static/index.html diff --git a/README.md b/README.md index e6d5690..38a303e 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,14 @@ npm install (this should work on Linux as well but let me know if it doesn't) -Set up the database: - -``` -./cli.py db upgrade head -``` - ## running the site -Before running the commands, enter the client directory and load the Conda environment: +Before running the commands, enter the client directory, load the Conda environment, and make sure the database is current: ``` cd cli conda activate swimmer +./cli.py db upgrade head ``` Then build the frontend and run the Flask server: @@ -50,4 +45,5 @@ Generate a new migration if you've modified the database: ``` ./cli.py db revision --autogenerate -m 'describe the changes' +./cli.py db upgrade head ``` diff --git a/cli/app/sql/common.py b/cli/app/sql/common.py index d007e23..c8bd557 100644 --- a/cli/app/sql/common.py +++ b/cli/app/sql/common.py @@ -21,7 +21,6 @@ from app.settings import app_cfg os.makedirs(app_cfg.DIR_DATABASE, exist_ok=True) connection_url = "sqlite:///{}".format(os.path.join(app_cfg.DIR_DATABASE, 'swimmer.sqlite3')) -print(connection_url) engine = create_engine(connection_url, encoding="utf-8", pool_recycle=3600) diff --git a/cli/app/sql/models/graph.py b/cli/app/sql/models/graph.py index 1f553e9..0d3fdab 100644 --- a/cli/app/sql/models/graph.py +++ b/cli/app/sql/models/graph.py @@ -17,6 +17,7 @@ class Graph(Base): id = Column(Integer, primary_key=True) path = Column(String(64, convert_unicode=True), nullable=False) title = Column(String(64, convert_unicode=True), nullable=False) + username = Column(String(32, convert_unicode=True), nullable=False) description = Column(Text(convert_unicode=True), nullable=False) settings = Column(JSON, default={}, nullable=True) created_at = Column(UtcDateTime(), default=utcnow()) @@ -29,6 +30,7 @@ class Graph(Base): 'id': self.id, 'path': self.path, 'title': self.title, + 'username': self.username, 'description': self.description, 'settings': self.settings, 'created_at': self.created_at, diff --git a/cli/app/sql/models/page.py b/cli/app/sql/models/page.py index f23db8d..22fcc96 100644 --- a/cli/app/sql/models/page.py +++ b/cli/app/sql/models/page.py @@ -16,6 +16,7 @@ class Page(Base): graph_id = Column(Integer, ForeignKey('graph.id'), nullable=True) path = Column(String(64, convert_unicode=True), nullable=False) title = Column(String(64, convert_unicode=True), nullable=False) + username = Column(String(32, convert_unicode=True), nullable=False) description = Column(Text(convert_unicode=True), nullable=False) settings = Column(JSON, default={}, nullable=True) created_at = Column(UtcDateTime(), default=utcnow()) @@ -29,6 +30,7 @@ class Page(Base): 'graph_id': self.graph_id, 'path': self.path, 'title': self.title, + 'username': self.username, 'description': self.description, 'settings': self.settings, 'created_at': self.created_at, diff --git a/cli/app/sql/versions/202006011943_adding_database.py b/cli/app/sql/versions/202006011943_adding_database.py deleted file mode 100644 index 4e5b0e6..0000000 --- a/cli/app/sql/versions/202006011943_adding_database.py +++ /dev/null @@ -1,66 +0,0 @@ -"""adding database - -Revision ID: 7acd0c82a048 -Revises: -Create Date: 2020-06-01 19:43:53.359855 - -""" -from alembic import op -import sqlalchemy as sa -import sqlalchemy_utc - - -# revision identifiers, used by Alembic. -revision = '7acd0c82a048' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('graph', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('path', sa.String(length=64, _expect_unicode=True), nullable=False), - sa.Column('title', sa.String(length=64, _expect_unicode=True), nullable=False), - sa.Column('description', sa.Text(_expect_unicode=True), nullable=False), - sa.Column('settings', sa.JSON(), nullable=True), - sa.Column('created_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), - sa.Column('updated_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('page', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('graph_id', sa.Integer(), nullable=True), - sa.Column('path', sa.String(length=64, _expect_unicode=True), nullable=False), - sa.Column('title', sa.String(length=64, _expect_unicode=True), nullable=False), - sa.Column('description', sa.Text(_expect_unicode=True), nullable=False), - sa.Column('settings', sa.JSON(), nullable=True), - sa.Column('created_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), - sa.Column('updated_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), - sa.ForeignKeyConstraint(['graph_id'], ['graph.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('tile', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('graph_id', sa.Integer(), nullable=True), - sa.Column('page_id', sa.Integer(), nullable=True), - sa.Column('target_page_id', sa.Integer(), nullable=True), - sa.Column('type', sa.String(length=16, _expect_unicode=True), nullable=False), - sa.Column('settings', sa.JSON(), nullable=True), - sa.Column('created_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), - sa.Column('updated_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), - sa.ForeignKeyConstraint(['graph_id'], ['graph.id'], ), - sa.ForeignKeyConstraint(['page_id'], ['page.id'], ), - sa.ForeignKeyConstraint(['target_page_id'], ['page.id'], ), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('tile') - op.drop_table('page') - op.drop_table('graph') - # ### end Alembic commands ### diff --git a/cli/app/sql/versions/202006011944_adding_uploads.py b/cli/app/sql/versions/202006011944_adding_uploads.py deleted file mode 100644 index f09f013..0000000 --- a/cli/app/sql/versions/202006011944_adding_uploads.py +++ /dev/null @@ -1,37 +0,0 @@ -"""adding uploads - -Revision ID: 5b926731a4ac -Revises: 7acd0c82a048 -Create Date: 2020-06-01 19:44:30.400513 - -""" -from alembic import op -import sqlalchemy as sa -import sqlalchemy_utc - - -# revision identifiers, used by Alembic. -revision = '5b926731a4ac' -down_revision = '7acd0c82a048' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('upload', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('sha256', sa.String(length=256), nullable=False), - sa.Column('fn', sa.String(length=256), nullable=False), - sa.Column('ext', sa.String(length=4, _expect_unicode=True), nullable=False), - sa.Column('username', sa.String(length=16, _expect_unicode=True), nullable=False), - sa.Column('created_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('upload') - # ### end Alembic commands ### diff --git a/cli/app/sql/versions/202006021608_creating_database.py b/cli/app/sql/versions/202006021608_creating_database.py new file mode 100644 index 0000000..7c6e54a --- /dev/null +++ b/cli/app/sql/versions/202006021608_creating_database.py @@ -0,0 +1,78 @@ +"""creating database + +Revision ID: 453787357254 +Revises: +Create Date: 2020-06-02 16:08:43.195875 + +""" +from alembic import op +import sqlalchemy as sa +import sqlalchemy_utc + + +# revision identifiers, used by Alembic. +revision = '453787357254' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('graph', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('path', sa.String(length=64, _expect_unicode=True), nullable=False), + sa.Column('title', sa.String(length=64, _expect_unicode=True), nullable=False), + sa.Column('username', sa.String(length=32, _expect_unicode=True), nullable=False), + sa.Column('description', sa.Text(_expect_unicode=True), nullable=False), + sa.Column('settings', sa.JSON(), nullable=True), + sa.Column('created_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), + sa.Column('updated_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('upload', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('sha256', sa.String(length=256), nullable=False), + sa.Column('fn', sa.String(length=256), nullable=False), + sa.Column('ext', sa.String(length=4, _expect_unicode=True), nullable=False), + sa.Column('username', sa.String(length=16, _expect_unicode=True), nullable=False), + sa.Column('created_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('page', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('graph_id', sa.Integer(), nullable=True), + sa.Column('path', sa.String(length=64, _expect_unicode=True), nullable=False), + sa.Column('title', sa.String(length=64, _expect_unicode=True), nullable=False), + sa.Column('username', sa.String(length=32, _expect_unicode=True), nullable=False), + sa.Column('description', sa.Text(_expect_unicode=True), nullable=False), + sa.Column('settings', sa.JSON(), nullable=True), + sa.Column('created_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), + sa.Column('updated_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), + sa.ForeignKeyConstraint(['graph_id'], ['graph.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('tile', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('graph_id', sa.Integer(), nullable=True), + sa.Column('page_id', sa.Integer(), nullable=True), + sa.Column('target_page_id', sa.Integer(), nullable=True), + sa.Column('type', sa.String(length=16, _expect_unicode=True), nullable=False), + sa.Column('settings', sa.JSON(), nullable=True), + sa.Column('created_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), + sa.Column('updated_at', sqlalchemy_utc.sqltypes.UtcDateTime(timezone=True), nullable=True), + sa.ForeignKeyConstraint(['graph_id'], ['graph.id'], ), + sa.ForeignKeyConstraint(['page_id'], ['page.id'], ), + sa.ForeignKeyConstraint(['target_page_id'], ['page.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('tile') + op.drop_table('page') + op.drop_table('upload') + op.drop_table('graph') + # ### end Alembic commands ### diff --git a/frontend/app.js b/frontend/app.js index 38a50c6..da8dccb 100644 --- a/frontend/app.js +++ b/frontend/app.js @@ -4,7 +4,7 @@ import { Route } from 'react-router' import { Header } from './common' -import actions from './actions' +// import actions from './actions' import * as views from './views' @@ -22,17 +22,15 @@ export default class App extends Component { render() { return ( -
+
-
-
- {viewList} - { - // redirect to search!! - setTimeout(() => this.props.history.push('/search/'), 10) - return null - }} /> -
+
+ {viewList} + { + // redirect to index!! + setTimeout(() => this.props.history.push('/index'), 10) + return null + }} />
diff --git a/frontend/common/app.css b/frontend/common/app.css index a235507..41aa54e 100644 --- a/frontend/common/app.css +++ b/frontend/common/app.css @@ -2,15 +2,17 @@ html, body { margin: 0; padding: 0; - min-width: 100%; - min-height: 100%; + width: 100%; + height: 100%; } body { - background: #fff; - color: #000; + background: #000; + color: #ddd; overflow-y: scroll; font-family: 'Roboto', sans-serif; font-size: 0.875rem; + height: 100%; + width: 100%; } .gray { color: #888; @@ -18,6 +20,21 @@ body { /* layout */ +.container { + height: 100%; + width: 100%; +} +.app { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; +} +.app .body { + display: flex; + flex-grow: 1; +} + .row { display: flex; flex-direction: row; @@ -30,9 +47,7 @@ body { .row > div:last-child { margin-right: 0; } -.body section > div:last-child { - flex-grow: 1; -} + .row.menubar { justify-content: flex-end; @@ -59,7 +74,7 @@ header { flex-direction: row; justify-content: space-between; align-items: center; - background: #11f; + background: rgba(64,64,64,0.5); color: white; } header b { @@ -151,8 +166,12 @@ p { /* links */ +b { + color: #fff; +} a { - color: #11f; + text-decoration: underline; + color: #8df; } /* menu button */ diff --git a/frontend/common/form.component.js b/frontend/common/form.component.js index fb8acbe..36369b5 100644 --- a/frontend/common/form.component.js +++ b/frontend/common/form.component.js @@ -10,10 +10,18 @@ export const TextInput = props => ( onChange={props.onChange} name={props.name} value={props.data[props.name]} + autoComplete={props.autoComplete} /> ) +export const LabelDescription = props => ( + +) + export const NumberInput = props => (
- Collections - Dashboard changeUsername()}> {' → '}{props.username} @@ -34,9 +32,9 @@ const mapStateToProps = (state) => ({ auth: state.auth, username: session.get('username'), isAuthenticated: state.auth.isAuthenticated, -}); +}) const mapDispatchToProps = (dispatch) => ({ -}); +}) -export default connect(mapStateToProps, mapDispatchToProps)(Header); +export default connect(mapStateToProps, mapDispatchToProps)(Header) diff --git a/frontend/common/index.js b/frontend/common/index.js index 9e81c99..9a11eba 100644 --- a/frontend/common/index.js +++ b/frontend/common/index.js @@ -5,6 +5,7 @@ export { export { Select, Checkbox, FileInput, FileInputField, TextInput, NumberInput, TextArea, SubmitButton, + LabelDescription, } from './form.component' export { Loader, Swatch, Dot, Columns, Statistic, Detections, Progress diff --git a/frontend/index.js b/frontend/index.js index 94cac35..6f1a0a5 100644 --- a/frontend/index.js +++ b/frontend/index.js @@ -7,6 +7,7 @@ import App from './app' import { store, history } from './store' const container = document.createElement('div') +container.classList.add('container') document.body.appendChild(container) ReactDOM.render( diff --git a/frontend/views/index.js b/frontend/views/index.js index 5f88c96..69bb70e 100644 --- a/frontend/views/index.js +++ b/frontend/views/index.js @@ -1 +1,2 @@ -export { Container as upload } from './upload' +export { default as index } from './index/index.container' +export { default as upload } from './upload/upload.container' diff --git a/frontend/views/index/components/graph.form.js b/frontend/views/index/components/graph.form.js new file mode 100644 index 0000000..ef546ec --- /dev/null +++ b/frontend/views/index/components/graph.form.js @@ -0,0 +1,149 @@ +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 newGraph = () => ({ + path: '', + title: '', + username: session('username'), + description: '', +}) + +export default class GraphForm extends Component { + state = { + title: "", + submitTitle: "", + data: { ...newGraph() }, + errorFields: new Set([]), + } + + componentDidMount() { + const { data, isNew } = this.props + const title = isNew ? 'new graph' : 'editing ' + data.title + const submitTitle = isNew ? "Create Graph" : "Save Changes" + this.setState({ + title, + submitTitle, + errorFields: new Set([]), + data: { + ...newGraph(), + ...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".split(" ") + const validKeys = "title username notes archived".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 graph + session.set('username', data.username) + } else { + validData.id = data.id + } + console.log('submit', validData) + onSubmit(validData) + } + } + + render() { + const { isNew } = this.props + const { title, submitTitle, errorFields, data } = this.state + return ( +
+

{title}

+
+ + + {data.path + ? 'Project URLs will be: /' + data.path + '/example' + : 'Enter the base path for this project.'} + + + +