summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/app/controllers/upload_controller.py11
-rw-r--r--cli/app/sql/models/upload.py4
-rw-r--r--cli/app/sql/versions/202104011523_add_settings_to_uploads.py29
-rw-r--r--frontend/app/types.js2
-rw-r--r--frontend/app/views/graph/components/audio.list.js31
-rw-r--r--frontend/app/views/graph/components/cursor.list.js125
-rw-r--r--frontend/app/views/graph/components/graph.header.js1
-rw-r--r--frontend/app/views/graph/graph.actions.js4
-rw-r--r--frontend/app/views/graph/graph.container.js2
-rw-r--r--frontend/app/views/graph/graph.css26
-rw-r--r--frontend/app/views/graph/graph.reducer.js42
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