diff options
| -rw-r--r-- | cli/app/controllers/upload_controller.py | 11 | ||||
| -rw-r--r-- | cli/app/sql/models/upload.py | 4 | ||||
| -rw-r--r-- | cli/app/sql/versions/202104011523_add_settings_to_uploads.py | 29 | ||||
| -rw-r--r-- | frontend/app/types.js | 2 | ||||
| -rw-r--r-- | frontend/app/views/graph/components/audio.list.js | 31 | ||||
| -rw-r--r-- | frontend/app/views/graph/components/cursor.list.js | 125 | ||||
| -rw-r--r-- | frontend/app/views/graph/components/graph.header.js | 1 | ||||
| -rw-r--r-- | frontend/app/views/graph/graph.actions.js | 4 | ||||
| -rw-r--r-- | frontend/app/views/graph/graph.container.js | 2 | ||||
| -rw-r--r-- | frontend/app/views/graph/graph.css | 26 | ||||
| -rw-r--r-- | frontend/app/views/graph/graph.reducer.js | 42 |
11 files changed, 240 insertions, 37 deletions
diff --git a/cli/app/controllers/upload_controller.py b/cli/app/controllers/upload_controller.py index 94a7fd1..022b08a 100644 --- a/cli/app/controllers/upload_controller.py +++ b/cli/app/controllers/upload_controller.py @@ -5,6 +5,7 @@ from werkzeug.utils import secure_filename import os import numpy as np from PIL import Image +import json from app.settings import app_cfg from app.sql.common import db, Session @@ -70,6 +71,14 @@ class UploadView(FlaskView): except: raise APIError('No graph_id specified') + try: + settings = request.form.get('settings') + settings = json.loads(settings) + # print(graph_id) + except: + settings = {} + + if 'image' in request.files: file = request.files['image'] # print(fn) @@ -113,7 +122,7 @@ class UploadView(FlaskView): os.makedirs(uploaded_im_abspath, exist_ok=True) file.save(uploaded_im_fullpath) - upload = Upload(username=username, tag=tag, fn=uploaded_im_fn, sha256=sha256, ext=ext, graph_id=graph_id) + upload = Upload(username=username, tag=tag, fn=uploaded_im_fn, sha256=sha256, ext=ext, graph_id=graph_id, settings=settings) session.add(upload) session.commit() response = { diff --git a/cli/app/sql/models/upload.py b/cli/app/sql/models/upload.py index d9307ff..ac3d900 100644 --- a/cli/app/sql/models/upload.py +++ b/cli/app/sql/models/upload.py @@ -1,4 +1,4 @@ -from sqlalchemy import create_engine, Table, Column, ForeignKey, String, Integer, DateTime +from sqlalchemy import create_engine, Table, Column, ForeignKey, String, Integer, DateTime, JSON import sqlalchemy.sql.functions as func from sqlalchemy_utc import UtcDateTime, utcnow from wtforms_alchemy import ModelForm @@ -20,6 +20,7 @@ class Upload(Base): ext = Column(String(4, convert_unicode=True), nullable=False) tag = Column(String(64, convert_unicode=True), nullable=True) username = Column(String(16, convert_unicode=True), nullable=False) + settings = Column(JSON, default={}, nullable=True) created_at = Column(UtcDateTime(), default=utcnow()) def toJSON(self): @@ -32,6 +33,7 @@ class Upload(Base): 'tag': self.tag, 'username': self.username, 'url': self.url(), + 'settings': self.settings, 'created_at': self.created_at, } diff --git a/cli/app/sql/versions/202104011523_add_settings_to_uploads.py b/cli/app/sql/versions/202104011523_add_settings_to_uploads.py new file mode 100644 index 0000000..b0d63b2 --- /dev/null +++ b/cli/app/sql/versions/202104011523_add_settings_to_uploads.py @@ -0,0 +1,29 @@ +"""add settings to uploads + +Revision ID: 844858ecfc5c +Revises: 9b687880918d +Create Date: 2021-04-01 15:23:29.581711 + +""" +from alembic import op +import sqlalchemy as sa +import sqlalchemy_utc + + +# revision identifiers, used by Alembic. +revision = '844858ecfc5c' +down_revision = '9b687880918d' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('upload', sa.Column('settings', sa.JSON(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('upload', 'settings') + # ### end Alembic commands ### diff --git a/frontend/app/types.js b/frontend/app/types.js index f0f1e27..a830e71 100644 --- a/frontend/app/types.js +++ b/frontend/app/types.js @@ -6,7 +6,7 @@ export const graph = crud_type('graph', [ 'show_add_page_form', 'hide_add_page_form', 'toggle_add_page_form', 'show_edit_page_form', 'hide_edit_page_form', 'toggle_edit_page_form', 'update_graph_page', - 'toggle_audio_list', + 'toggle_audio_list', 'toggle_cursor_list', ]) export const page = crud_type('page', [ diff --git a/frontend/app/views/graph/components/audio.list.js b/frontend/app/views/graph/components/audio.list.js index 011ab08..7587028 100644 --- a/frontend/app/views/graph/components/audio.list.js +++ b/frontend/app/views/graph/components/audio.list.js @@ -114,19 +114,21 @@ class AudioList extends Component { /> </div> {graph.uploads.map(upload => ( - <div className='audioItem' key={upload.id} onClick={() => this.toggleAudio(upload)} > - <img - className='playButton' - src={ - (playing && play_id === upload.id) - ? "/static/img/icons_pause_white.svg" - : "/static/img/icons_play_white.svg" - } - /> - <div className='title'> - <div>{unslugify(upload.fn)}</div> + upload.tag === 'audio' && ( + <div className='audioItem' key={upload.id} onClick={() => this.toggleAudio(upload)} > + <img + className='playButton' + src={ + (playing && play_id === upload.id) + ? "/static/img/icons_pause_white.svg" + : "/static/img/icons_play_white.svg" + } + /> + <div className='title'> + <div>{unslugify(upload.fn)}</div> + </div> </div> - </div> + ) ))} </div> ) @@ -141,8 +143,3 @@ const mapDispatchToProps = dispatch => ({ }) export default connect(mapStateToProps, mapDispatchToProps)(AudioList) - - -/* - - upload new audio file - */
\ No newline at end of file diff --git a/frontend/app/views/graph/components/cursor.list.js b/frontend/app/views/graph/components/cursor.list.js new file mode 100644 index 0000000..8c4bbed --- /dev/null +++ b/frontend/app/views/graph/components/cursor.list.js @@ -0,0 +1,125 @@ +import React, { Component } from 'react' +import { Link } from 'react-router-dom' +import { connect } from 'react-redux' + +import { history } from 'app/store' +import { unslugify } from 'app/utils' +import actions from 'app/actions' + +class CursorList extends Component { + state = { + playing: false, + play_id: -1, + } + + constructor(props) { + super(props) + this.upload = this.upload.bind(this) + } + + upload(e) { + e.preventDefault() + document.body.className = '' + const files = e.dataTransfer ? e.dataTransfer.files : e.target.files + let i + if (!files.length) return + const promises = Array.from(files).map(file => this.uploadTaggedImage(file, 'cursor', file.filename)) + Promise.all(promises) + .then(() => { + this.setState({ status: "" }) + }) + } + + uploadTaggedImage(file, tag, fn) { + return new Promise((resolve, reject) => { + this.setState({ status: "Uploading " + tag + "..." }) + const reader = new FileReader() + reader.onload = fileReaderEvent => { + reader.onload = null + const img = new Image() + img.onload = () => { + img.onload = null + upload(img) + } + img.src = fileReaderEvent.target.result + } + reader.readAsDataURL(file) + + const upload = img => { + const uploadData = { + tag, + file, + __file_filename: fn, + graph_id: this.props.graph.id, + username: 'swimmer', + settings: JSON.stringify({ + width: img.naturalWidth, + height: img.naturalHeight, + }), + } + // console.log(uploadData) + return actions.upload.upload(uploadData).then(data => { + // console.log(data) + resolve({ + ...data.res, + }) + }) + } + }) + } + + destroyFile(upload) { + return new Promise((resolve, reject) => { + actions.upload.destroy(upload) + .then(() => { + console.log('Destroy successful') + resolve() + }) + .catch(() => { + console.log('Error deleting the file') + reject() + }) + }) + } + + render() { + const { playing, play_id } = this.state + const { graph, onClick } = this.props + // console.log(graph.uploads) + return ( + <div className='box cursorList'> + <div className="uploadButton"> + <button> + <span> + {"Upload a cursor"} + </span> + </button> + <input + type="file" + accept="image/*" + onChange={this.upload} + multiple + /> + </div> + <div className='cursors'> + {graph.uploads.map(upload => ( + upload.tag === 'cursor' && ( + <div className='cursorItem' key={upload.id} onClick={() => onClick && onClick(upload)}> + <img src={upload.url} alt={upload.fn} /> + </div> + ) + ))} + </div> + </div> + ) + } +} + +const mapStateToProps = state => ({ + graph: state.graph.show.res, +}) + +const mapDispatchToProps = dispatch => ({ +}) + +export default connect(mapStateToProps, mapDispatchToProps)(CursorList) diff --git a/frontend/app/views/graph/components/graph.header.js b/frontend/app/views/graph/components/graph.header.js index 0766580..f99f7dc 100644 --- a/frontend/app/views/graph/components/graph.header.js +++ b/frontend/app/views/graph/components/graph.header.js @@ -14,6 +14,7 @@ function GraphHeader(props) { </div> <div> <button onClick={() => props.graphActions.toggleAddPageForm()}>+ Add page</button> + <button onClick={() => props.graphActions.toggleCursorList()}>+ Cursors</button> <button onClick={() => props.graphActions.toggleAudioList()}>+ Audio</button> </div> </header> diff --git a/frontend/app/views/graph/graph.actions.js b/frontend/app/views/graph/graph.actions.js index 6d5cb51..dbabbaf 100644 --- a/frontend/app/views/graph/graph.actions.js +++ b/frontend/app/views/graph/graph.actions.js @@ -30,6 +30,10 @@ export const toggleAudioList = () => dispatch => { dispatch({ type: types.graph.toggle_audio_list }) } +export const toggleCursorList = () => dispatch => { + dispatch({ type: types.graph.toggle_cursor_list }) +} + export const updateGraphPage = page => dispatch => { dispatch({ type: types.graph.update_graph_page, page }) } diff --git a/frontend/app/views/graph/graph.container.js b/frontend/app/views/graph/graph.container.js index 34c3d9d..57ea205 100644 --- a/frontend/app/views/graph/graph.container.js +++ b/frontend/app/views/graph/graph.container.js @@ -16,6 +16,7 @@ import PageEdit from './components/page.edit' import GraphHeader from './components/graph.header' import GraphEditor from './components/graph.editor' import AudioList from './components/audio.list' +import CursorList from './components/cursor.list' class GraphContainer extends Component { componentDidMount() { @@ -65,6 +66,7 @@ class GraphContainer extends Component { {this.props.graph.editor.addingPage && <PageNew />} {this.props.graph.editor.editingPage && <PageEdit />} {this.props.graph.editor.showingAudio && <AudioList />} + {this.props.graph.editor.showingCursors && <CursorList />} </div> </div> </div> diff --git a/frontend/app/views/graph/graph.css b/frontend/app/views/graph/graph.css index 8837ea2..280ce58 100644 --- a/frontend/app/views/graph/graph.css +++ b/frontend/app/views/graph/graph.css @@ -255,6 +255,32 @@ button.box_corner:hover { opacity: 1.0; } +/* cursors */ + +.cursorList .cursors { + display: flex; + flex-flow: row wrap; + justify-content: space-between; +} +.cursorList .cursors .cursorItem { + cursor: pointer; + background: rgba(0,0,0,0.0); + transition: all 0.1s; +} +.cursorList .cursors .cursorItem:hover { + border-radius: 5px; + background: rgba(255,0,255,0.1); + display: flex; + justify-content: center; + align-items: center; + width: 50px; + height: 50px; +} +.cursorList .cursors img { + max-width: 50px; + max-height: 50px; +} + /* Graph handles */ .handle { diff --git a/frontend/app/views/graph/graph.reducer.js b/frontend/app/views/graph/graph.reducer.js index 725c256..6a30a4e 100644 --- a/frontend/app/views/graph/graph.reducer.js +++ b/frontend/app/views/graph/graph.reducer.js @@ -3,11 +3,16 @@ import * as types from 'app/types' import { crudState, crudReducer } from 'app/api/crud.reducer' +const resetEditor = { + addingPage: false, + editingPage: false, + showingAudio: false, + showingCursors: false, +} + const initialState = crudState('graph', { editor: { - addingPage: false, - editingPage: false, - showingAudio: false, + ...resetEditor, building: false, }, options: { @@ -56,9 +61,8 @@ export default function graphReducer(state = initialState, action) { ...state, editor: { ...state.editor, + ...resetEditor, addingPage: true, - editingPage: false, - showingAudio: false, } } @@ -67,8 +71,7 @@ export default function graphReducer(state = initialState, action) { ...state, editor: { ...state.editor, - addingPage: false, - showingAudio: false, + ...resetEditor, } } @@ -77,9 +80,8 @@ export default function graphReducer(state = initialState, action) { ...state, editor: { ...state.editor, + ...resetEditor, addingPage: !state.editor.addingPage, - editingPage: false, - showingAudio: false, } } @@ -88,9 +90,8 @@ export default function graphReducer(state = initialState, action) { ...state, editor: { ...state.editor, - addingPage: false, + ...resetEditor, editingPage: true, - showingAudio: false, } } @@ -99,8 +100,7 @@ export default function graphReducer(state = initialState, action) { ...state, editor: { ...state.editor, - editingPage: false, - showingAudio: false, + ...resetEditor, } } @@ -109,9 +109,8 @@ export default function graphReducer(state = initialState, action) { ...state, editor: { ...state.editor, - addingPage: false, + ...resetEditor, editingPage: !state.editor.editingPage, - showingAudio: false, } } @@ -120,12 +119,21 @@ export default function graphReducer(state = initialState, action) { ...state, editor: { ...state.editor, - addingPage: false, - editingPage: false, + ...resetEditor, showingAudio: !state.editor.showingAudio, } } + case types.graph.toggle_cursor_list: + return { + ...state, + editor: { + ...state.editor, + ...resetEditor, + showingCursors: !state.editor.showingCursors, + } + } + case types.api.loading: if (action.tag !== 'view' && action.tag !== 'export') { return state |
