From 8792e9fe1c7ab76c35f9a18d866880ba3da2c13e Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 15 Mar 2021 19:09:37 +0100 Subject: move frontend site folder. add video support --- static/media | 1 + 1 file changed, 1 insertion(+) create mode 120000 static/media (limited to 'static') diff --git a/static/media b/static/media new file mode 120000 index 0000000..fc5e20c --- /dev/null +++ b/static/media @@ -0,0 +1 @@ +../data_store/media \ No newline at end of file -- cgit v1.2.3-70-g09d2 From a9d86650f40a82a64d1fd8e0525c26422d314d3a Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Tue, 16 Mar 2021 16:38:07 +0100 Subject: make uploads like animism. start audio stuff --- cli/app/controllers/upload_controller.py | 75 +++++++++++++--------- cli/app/sql/models/upload.py | 17 ++--- .../202103161637_make_uploads_like_on_animism.py | 31 +++++++++ data_store/uploads/.gitkeep | 0 frontend/app/api/crud.upload.js | 14 +++- frontend/app/common/app.css | 10 +++ frontend/app/common/form.component.js | 2 +- .../app/views/graph/components/graph.header.js | 3 +- frontend/app/views/graph/components/page.form.js | 27 +++++++- frontend/app/views/page/components/page.header.js | 3 +- frontend/app/views/page/components/tile.edit.js | 4 +- frontend/app/views/page/components/tile.handle.js | 1 - static/uploads | 1 + 13 files changed, 137 insertions(+), 51 deletions(-) create mode 100644 cli/app/sql/versions/202103161637_make_uploads_like_on_animism.py create mode 100644 data_store/uploads/.gitkeep create mode 120000 static/uploads (limited to 'static') diff --git a/cli/app/controllers/upload_controller.py b/cli/app/controllers/upload_controller.py index 86f9f29..94a7fd1 100644 --- a/cli/app/controllers/upload_controller.py +++ b/cli/app/controllers/upload_controller.py @@ -15,18 +15,22 @@ from app.server.decorators import APIError class UploadView(FlaskView): def index(self): """ - List all uploaded files. - - * Query string params: offset, limit, sort (id, date), order (asc, desc) + List all uploads """ session = Session() - uploads = session.query(Upload).all() - response = { + query = session.query(Upload) + graph_id = args.get('graph_id', default=None) + if graph_id is not None: + query = query.filter(Upload.graph_id == int(graph_id)) + + items = query.all() + + res = { 'status': 'ok', - 'res': [ upload.toJSON() for upload in uploads ], + 'res': [ item.toJSON() for item in items ], } session.close() - return jsonify(response) + return jsonify(res) def get(self, id): """ @@ -50,14 +54,31 @@ class UploadView(FlaskView): try: username = request.form.get('username') + # print(username) except: raise APIError('No username specified') - param_name = 'image' - if param_name not in request.files: - raise APIError('No file uploaded') + try: + tag = request.form.get('tag') + # print(tag) + except: + raise APIError('No tag specified') - file = request.files[param_name] + try: + graph_id = request.form.get('graph_id') + # print(graph_id) + except: + raise APIError('No graph_id specified') + + if 'image' in request.files: + file = request.files['image'] + # print(fn) + elif 'file' in request.files: + file = request.files['file'] + # print(request.form.get('__image_filename')) + # print(fn) + else: + raise APIError('No file uploaded') # get sha256 sha256 = sha256_stream(file) @@ -65,42 +86,34 @@ class UploadView(FlaskView): if ext == '.jpeg': ext = '.jpg' - # TODO: here check sha256 - # upload = Upload.query.get(id) - - if ext[1:] not in VALID_IMAGE_EXTS: - return jsonify({ 'status': 'error', 'error': 'Not a valid image' }) + ext = ext[1:] - # convert string of image data to uint8 file.seek(0) - nparr = np.fromstring(file.read(), np.uint8) - # decode image - try: - im = Image.fromarray(nparr) - except: - return jsonify({ 'status': 'error', 'error': 'Image parse error' }) + uploaded_im_fn = secure_filename(file.filename) + uploaded_im_abspath = os.path.join(app_cfg.DIR_UPLOADS, str(graph_id), tag) + uploaded_im_fullpath = os.path.join(uploaded_im_abspath, uploaded_im_fn) session = Session() upload = session.query(Upload).filter_by(sha256=sha256).first() if upload is not None: - print("Already uploaded image") + print("Already uploaded file") + if not os.path.exists(uploaded_im_fullpath): + # if we got in some weird state where the record wasnt deleted.... + os.makedirs(uploaded_im_abspath, exist_ok=True) + file.save(uploaded_im_fullpath) response = { 'status': 'ok', - 'notes': 'Image already uploaded', + 'notes': 'File already uploaded', 'res': upload.toJSON(), } session.close() return jsonify(response) - uploaded_im_fn = secure_filename(sha256 + ext) - uploaded_im_abspath = os.path.join(app_cfg.DIR_UPLOADS, sha256_tree(sha256)) - uploaded_im_fullpath = os.path.join(uploaded_im_abspath, uploaded_im_fn) - os.makedirs(uploaded_im_abspath, exist_ok=True) - nparr.tofile(uploaded_im_fullpath) + file.save(uploaded_im_fullpath) - upload = Upload(username=username, sha256=sha256, ext=ext) + upload = Upload(username=username, tag=tag, fn=uploaded_im_fn, sha256=sha256, ext=ext, graph_id=graph_id) session.add(upload) session.commit() response = { diff --git a/cli/app/sql/models/upload.py b/cli/app/sql/models/upload.py index 5863b07..87f758a 100644 --- a/cli/app/sql/models/upload.py +++ b/cli/app/sql/models/upload.py @@ -14,31 +14,28 @@ class Upload(Base): """Table for storing references to various media""" __tablename__ = 'upload' id = Column(Integer, primary_key=True) + graph_id = Column(Integer) sha256 = Column(String(256), nullable=False) fn = Column(String(256), nullable=False) 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) created_at = Column(UtcDateTime(), default=utcnow()) def toJSON(self): return { 'id': self.id, + 'graph_id': self.graph_id, 'sha256': self.sha256, 'fn': self.fn, 'ext': self.ext, + 'tag': self.tag, 'username': self.username, 'url': self.url(), 'created_at': self.created_at, } - def filename(self): - return "{}{}".format(self.fn) - - def filepath(self): - return join(app_cfg.DIR_UPLOADS, sha256_tree(self.sha256)) - - def fullpath(self): - return join(self.filepath(), self.filename()) - def url(self): - return join(app_cfg.URL_UPLOADS, sha256_tree(self.sha256), self.filename()) + if self.tag: + return join('/static/data_store/uploads', str(self.graph_id), self.tag, self.fn) + return join('/static/data_store/uploads', str(self.graph_id), self.fn) diff --git a/cli/app/sql/versions/202103161637_make_uploads_like_on_animism.py b/cli/app/sql/versions/202103161637_make_uploads_like_on_animism.py new file mode 100644 index 0000000..18bf0bc --- /dev/null +++ b/cli/app/sql/versions/202103161637_make_uploads_like_on_animism.py @@ -0,0 +1,31 @@ +"""make uploads like on animism + +Revision ID: 645f315e651d +Revises: d929da3e398b +Create Date: 2021-03-16 16:37:08.985792 + +""" +from alembic import op +import sqlalchemy as sa +import sqlalchemy_utc + + +# revision identifiers, used by Alembic. +revision = '645f315e651d' +down_revision = 'd929da3e398b' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('upload', sa.Column('graph_id', sa.Integer(), nullable=True)) + op.add_column('upload', sa.Column('tag', sa.String(length=64, _expect_unicode=True), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('upload', 'tag') + op.drop_column('upload', 'graph_id') + # ### end Alembic commands ### diff --git a/data_store/uploads/.gitkeep b/data_store/uploads/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/frontend/app/api/crud.upload.js b/frontend/app/api/crud.upload.js index 8c1b265..2837dd4 100644 --- a/frontend/app/api/crud.upload.js +++ b/frontend/app/api/crud.upload.js @@ -1,4 +1,5 @@ import { as_type } from 'app/api/crud.types' +import { session } from 'app/session' export function crud_upload(type, data, dispatch) { return new Promise( (resolve, reject) => { @@ -6,9 +7,17 @@ export function crud_upload(type, data, dispatch) { const { id } = data const fd = new FormData() + if (!data.tag) { + data.tag = 'misc' + } Object.keys(data).forEach(key => { - if (key !== 'id') { + if (key.indexOf('__') !== -1) return + if (key === 'id') return + const fn_key = `__${key}_filename` + if (fn_key in data) { + fd.append(key, data[key], data[fn_key]) + } else { fd.append(key, data[key]) } }) @@ -23,12 +32,11 @@ export function crud_upload(type, data, dispatch) { xhr.addEventListener("error", uploadFailed, false) xhr.addEventListener("abort", uploadCancelled, false) xhr.open("POST", url) + xhr.setRequestHeader("Authorization", "Bearer " + session.get("access_token")) xhr.send(fd) dispatch && dispatch({ type: as_type(type, 'upload_loading')}) - let complete = false - function uploadProgress (e) { if (e.lengthComputable) { const percent = Math.round(e.loaded * 100 / e.total) || 0 diff --git a/frontend/app/common/app.css b/frontend/app/common/app.css index d9f9946..2e9dc4e 100644 --- a/frontend/app/common/app.css +++ b/frontend/app/common/app.css @@ -147,6 +147,16 @@ header a:active { header a.navbar-brand { font-size: .8rem; } +header .arrow { + padding: 0.5rem 0.5rem 0.5rem 0.5rem; + margin-left: -0.5rem; + margin-right: 0.25rem; + transition: background 0.2s; + border-radius: 4px; +} +header .arrow:hover { + background: rgba(0,0,255,0.5); +} header .username { cursor: pointer; diff --git a/frontend/app/common/form.component.js b/frontend/app/common/form.component.js index cf3e466..927b89d 100644 --- a/frontend/app/common/form.component.js +++ b/frontend/app/common/form.component.js @@ -76,7 +76,7 @@ export const Checkbox = props => ( type="checkbox" name={props.name} value={1} - checked={props.checked} + checked={!!props.checked} onChange={(e) => props.onChange(props.name, e.target.checked)} /> {props.label} diff --git a/frontend/app/views/graph/components/graph.header.js b/frontend/app/views/graph/components/graph.header.js index 46ad962..b969400 100644 --- a/frontend/app/views/graph/components/graph.header.js +++ b/frontend/app/views/graph/components/graph.header.js @@ -9,7 +9,8 @@ function GraphHeader(props) { return (
- {props.site.siteTitle} + {"◁ "} + {props.site.siteTitle}
diff --git a/frontend/app/views/graph/components/page.form.js b/frontend/app/views/graph/components/page.form.js index 8fc00b0..2c283aa 100644 --- a/frontend/app/views/graph/components/page.form.js +++ b/frontend/app/views/graph/components/page.form.js @@ -3,7 +3,7 @@ import { Link } from 'react-router-dom' import { session } from 'app/session' -import { TextInput, ColorInput, LabelDescription, TextArea, Checkbox, SubmitButton, Loader } from 'app/common' +import { TextInput, ColorInput, Checkbox, LabelDescription, TextArea, SubmitButton, Loader } from 'app/common' const newPage = (data) => ({ path: '', @@ -14,6 +14,8 @@ const newPage = (data) => ({ x: 0.05, y: 0.05, background_color: '#000000', + audio: "", + restartAudio: false, }, ...data, }) @@ -76,6 +78,10 @@ export default class PageForm extends Component { handleSettingsChange(e) { const { name, value } = e.target + this.handleSettingsSelect(name, value) + } + + handleSettingsSelect(name, value) { this.setState({ data: { ...this.state.data, @@ -147,7 +153,7 @@ export default class PageForm extends Component { autoComplete="off" /> + + + +
- {props.site.siteTitle} + {"◁"} + {props.site.siteTitle}
diff --git a/frontend/app/views/page/components/tile.edit.js b/frontend/app/views/page/components/tile.edit.js index 2ea09d1..cae9f73 100644 --- a/frontend/app/views/page/components/tile.edit.js +++ b/frontend/app/views/page/components/tile.edit.js @@ -29,7 +29,9 @@ class TileEdit extends Component { load() { const { currentEditTileId } = this.props.page.editor - const tile = this.props.page.show.res.tiles.filter(tile => tile.id === currentEditTileId)[0] + const { tiles } = this.props.page.show.res + if (!tiles) return + const tile = tiles.filter(tile => tile.id === currentEditTileId)[0] console.log('edit', currentEditTileId) this.setState({ tile }) } diff --git a/frontend/app/views/page/components/tile.handle.js b/frontend/app/views/page/components/tile.handle.js index 9331cb3..f47c3cd 100644 --- a/frontend/app/views/page/components/tile.handle.js +++ b/frontend/app/views/page/components/tile.handle.js @@ -151,7 +151,6 @@ const generateTransform = (tile, box) => { if (xalign === 'center') { transform.push('translateX(-50%)') } - console.log(units) // if (x % 2 == 1) x += 0.5 // if (y % 2 == 1) y += 0.5 transform.push('translateX(' + x + units + ')') diff --git a/static/uploads b/static/uploads new file mode 120000 index 0000000..79c5899 --- /dev/null +++ b/static/uploads @@ -0,0 +1 @@ +../data_store/uploads \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 15d9d864b539e221c6494b3535abef724517f207 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Tue, 16 Mar 2021 18:19:26 +0100 Subject: uploading audio files and displaying them in a list --- cli/app/sql/models/upload.py | 4 +- frontend/app/types.js | 1 + frontend/app/views/graph/components/audio.list.js | 150 +++++++++++++++++++++ .../app/views/graph/components/graph.header.js | 1 + frontend/app/views/graph/graph.actions.js | 4 + frontend/app/views/graph/graph.container.js | 2 + frontend/app/views/graph/graph.css | 53 ++++++++ frontend/app/views/graph/graph.reducer.js | 31 +++++ static/img/icons_pause_white.svg | 6 + static/img/icons_play_white.svg | 6 + 10 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 frontend/app/views/graph/components/audio.list.js create mode 100755 static/img/icons_pause_white.svg create mode 100755 static/img/icons_play_white.svg (limited to 'static') diff --git a/cli/app/sql/models/upload.py b/cli/app/sql/models/upload.py index 30e53dc..d9307ff 100644 --- a/cli/app/sql/models/upload.py +++ b/cli/app/sql/models/upload.py @@ -37,5 +37,5 @@ class Upload(Base): def url(self): if self.tag: - return join('/static/data_store/uploads', str(self.graph_id), self.tag, self.fn) - return join('/static/data_store/uploads', str(self.graph_id), self.fn) + return join('/static/uploads', str(self.graph_id), self.tag, self.fn) + return join('/static/uploads', str(self.graph_id), self.fn) diff --git a/frontend/app/types.js b/frontend/app/types.js index 7120a91..19d1e69 100644 --- a/frontend/app/types.js +++ b/frontend/app/types.js @@ -6,6 +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', ]) 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 new file mode 100644 index 0000000..bd8fe16 --- /dev/null +++ b/frontend/app/views/graph/components/audio.list.js @@ -0,0 +1,150 @@ +import React, { Component } from 'react' +import { Link } from 'react-router-dom' +import { connect } from 'react-redux' + +import { history } from 'app/store' +import actions from 'app/actions' + +class AudioList extends Component { + state = { + playing: false, + play_id: -1, + } + + constructor(props) { + super(props) + this.toggleAudio = this.toggleAudio.bind(this) + this.upload = this.upload.bind(this) + this.audioDidEnd = this.audioDidEnd.bind(this) + } + + componentDidMount() { + this.audioElement = document.createElement('audio') + this.audioElement.addEventListener('ended', this.audioDidEnd) + } + + componentWillUnmount() { + this.audioElement.removeEventListener('ended', this.audioDidEnd) + this.audioElement.pause() + this.audioElement = null + } + + audioDidEnd() { + this.setState({ playing: false }) + } + + upload(e) { + e.preventDefault() + document.body.className = '' + const files = e.dataTransfer ? e.dataTransfer.files : e.target.files + let i + if (!files.length) return + Array.from(files).forEach(file => this.uploadTaggedFile(file, 'audio', file.filename)) + } + + uploadTaggedFile(file, tag, fn) { + return new Promise((resolve, reject) => { + this.setState({ status: "Uploading " + tag + "..." }) + const uploadData = { + tag, + file, + __file_filename: fn, + graph_id: this.props.graph.id, + username: 'swimmer', + } + // 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() + }) + }) + } + + toggleAudio(upload) { + console.log(upload) + let playing = false + if (this.state.play_id === upload.id && this.state.playing) { + this.audioElement.pause() + } else { + this.audioElement.src = upload.url + this.audioElement.currentTime = 0 + this.audioElement.play() + playing = true + } + this.setState({ + playing, + play_id: upload.id, + }) + } + + render() { + const { playing, play_id } = this.state + const { graph } = this.props + // console.log(graph.uploads) + console.log(playing, play_id) + return ( +
+
+ + +
+ {graph.uploads.map(upload => ( +
this.toggleAudio(upload)} > + +
+
{unslugify(upload.fn)}
+
+
+ ))} +
+ ) + } +} + +const unslugify = fn => fn.replace(/-/g, ' ').replace(/_/g, ' ').replace('.mp3', '') + +const mapStateToProps = state => ({ + graph: state.graph.show.res, +}) + +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/graph.header.js b/frontend/app/views/graph/components/graph.header.js index b969400..0766580 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) {
+
) diff --git a/frontend/app/views/graph/graph.actions.js b/frontend/app/views/graph/graph.actions.js index a24ccc2..eba3f92 100644 --- a/frontend/app/views/graph/graph.actions.js +++ b/frontend/app/views/graph/graph.actions.js @@ -25,6 +25,10 @@ export const toggleEditPageForm = () => dispatch => { dispatch({ type: types.graph.toggle_edit_page_form }) } +export const toggleAudioList = () => dispatch => { + dispatch({ type: types.graph.toggle_audio_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 9e354fc..34c3d9d 100644 --- a/frontend/app/views/graph/graph.container.js +++ b/frontend/app/views/graph/graph.container.js @@ -15,6 +15,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' class GraphContainer extends Component { componentDidMount() { @@ -63,6 +64,7 @@ class GraphContainer extends Component {
{this.props.graph.editor.addingPage && } {this.props.graph.editor.editingPage && } + {this.props.graph.editor.showingAudio && }
diff --git a/frontend/app/views/graph/graph.css b/frontend/app/views/graph/graph.css index 2805cb0..c6ef115 100644 --- a/frontend/app/views/graph/graph.css +++ b/frontend/app/views/graph/graph.css @@ -146,6 +146,59 @@ width: 5.5rem; } +/* Upload area */ + +.box .uploadButton { + position: relative; + display: flex; + justify-content: center; + align-items: center; + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} +.uploadButton input[type=file] { + position: absolute; + top: 0; left: 0; + width: 100%; height: 100%; +} +.audioList .audioItem { + display: flex; + justify-content: flex-start; + align-items: center; + cursor: pointer; + padding: 0.125rem 0; +} +.audioList .playButton { + background: transparent; + border: 0; + width: 1.5rem; + height: 1.5rem; + margin-right: 0.5rem; + opacity: 0.8; +} +.audioList .title { + display: flex; + justify-content: flex-start; + align-items: center; + overflow: hidden; + flex: 1; +} +.audioList .title div { + overflow: hidden; + text-overflow: ellipsis; + white-space: pre; + width: 100%; +} +.audioList .audioItem:hover { + background: rgba(255,255,255,0.2); +} +.audioList .audioItem:hover .title { + color: #fff; +} +.audioList .audioItem:hover .playButton { + opacity: 1.0; +} + /* Graph handles */ .handle { diff --git a/frontend/app/views/graph/graph.reducer.js b/frontend/app/views/graph/graph.reducer.js index 6be5089..30049b5 100644 --- a/frontend/app/views/graph/graph.reducer.js +++ b/frontend/app/views/graph/graph.reducer.js @@ -7,6 +7,7 @@ const initialState = crudState('graph', { editor: { addingPage: false, editingPage: false, + showingAudio: false, }, options: { } @@ -36,6 +37,19 @@ export default function graphReducer(state = initialState, action) { } } + case types.upload.upload_complete: + console.log(action) + return { + ...state, + show: { + ...state.show, + res: { + ...state.show.res, + uploads: state.show.res.uploads.concat(action.data.res) + } + } + } + case types.graph.show_add_page_form: return { ...state, @@ -43,6 +57,7 @@ export default function graphReducer(state = initialState, action) { ...state.editor, addingPage: true, editingPage: false, + showingAudio: false, } } @@ -52,6 +67,7 @@ export default function graphReducer(state = initialState, action) { editor: { ...state.editor, addingPage: false, + showingAudio: false, } } @@ -62,6 +78,7 @@ export default function graphReducer(state = initialState, action) { ...state.editor, addingPage: !state.editor.addingPage, editingPage: false, + showingAudio: false, } } @@ -72,6 +89,7 @@ export default function graphReducer(state = initialState, action) { ...state.editor, addingPage: false, editingPage: true, + showingAudio: false, } } @@ -81,6 +99,7 @@ export default function graphReducer(state = initialState, action) { editor: { ...state.editor, editingPage: false, + showingAudio: false, } } @@ -91,6 +110,18 @@ export default function graphReducer(state = initialState, action) { ...state.editor, addingPage: false, editingPage: !state.editor.editingPage, + showingAudio: false, + } + } + + case types.graph.toggle_audio_list: + return { + ...state, + editor: { + ...state.editor, + addingPage: false, + editingPage: false, + showingAudio: !state.editor.showingAudio, } } diff --git a/static/img/icons_pause_white.svg b/static/img/icons_pause_white.svg new file mode 100755 index 0000000..59c7e60 --- /dev/null +++ b/static/img/icons_pause_white.svg @@ -0,0 +1,6 @@ + + + + + diff --git a/static/img/icons_play_white.svg b/static/img/icons_play_white.svg new file mode 100755 index 0000000..78ff002 --- /dev/null +++ b/static/img/icons_play_white.svg @@ -0,0 +1,6 @@ + + + + + -- cgit v1.2.3-70-g09d2 From 66563242eebaa92cf3b0c7dfdc70d0d1051be9ab Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Wed, 17 Mar 2021 20:43:54 +0100 Subject: pointr vnts non --- frontend/app/views/page/components/tile.form.js | 1 + frontend/app/views/page/components/tile.handle.js | 5 +-- frontend/app/views/page/cursors.css | 4 +++ frontend/site/audio/audio.player.js | 2 +- frontend/site/site/site.reducer.js | 2 +- static/site.css | 44 +++++++++++++---------- 6 files changed, 34 insertions(+), 24 deletions(-) (limited to 'static') diff --git a/frontend/app/views/page/components/tile.form.js b/frontend/app/views/page/components/tile.form.js index 4fb00a3..aefecf1 100644 --- a/frontend/app/views/page/components/tile.form.js +++ b/frontend/app/views/page/components/tile.form.js @@ -60,6 +60,7 @@ const CURSORS = [ { name: 'hand_down', label: 'Down', }, { name: 'hand_left', label: 'Left', }, { name: 'hand_right', label: 'Right', }, + { name: 'unclickable', label: 'Unclickable', }, ] const NO_LINK = 0 diff --git a/frontend/app/views/page/components/tile.handle.js b/frontend/app/views/page/components/tile.handle.js index 917be74..d8184d3 100644 --- a/frontend/app/views/page/components/tile.handle.js +++ b/frontend/app/views/page/components/tile.handle.js @@ -1,12 +1,9 @@ import React, { Component } from 'react' -import { Link } from 'react-router-dom' export default class TileHandle extends Component { constructor(props) { super(props) - if (props.tile.type === 'video') { - this.videoRef = React.createRef() - } + this.videoRef = React.createRef() this.handleEnded = this.handleEnded.bind(this) } componentDidMount() { diff --git a/frontend/app/views/page/cursors.css b/frontend/app/views/page/cursors.css index 778b614..6cc37a9 100644 --- a/frontend/app/views/page/cursors.css +++ b/frontend/app/views/page/cursors.css @@ -13,6 +13,10 @@ .tile.hand_left { cursor: url(/static/img/hand_left.png) 10 60, pointer; } +.tile.unclickable { + cursor: default; + pointer-events: none; +} .tile.none { cursor: default; } diff --git a/frontend/site/audio/audio.player.js b/frontend/site/audio/audio.player.js index eea0acf..d6bc807 100644 --- a/frontend/site/audio/audio.player.js +++ b/frontend/site/audio/audio.player.js @@ -32,7 +32,7 @@ export default class AudioPlayer { playPage(page) { const { background_audio_id, restart_audio } = page.settings - console.log('playPage', background_audio_id) + console.log('playPage', page) if ( this.current_background_id && this.current_background_id !== background_audio_id diff --git a/frontend/site/site/site.reducer.js b/frontend/site/site/site.reducer.js index e0b53fb..9763e48 100644 --- a/frontend/site/site/site.reducer.js +++ b/frontend/site/site/site.reducer.js @@ -9,7 +9,7 @@ const initialState = { } export default function siteReducer(state = initialState, action) { - console.log(action.type, action) + // console.log(action.type, action) switch (action.type) { case types.site.set_site_title: return { diff --git a/static/site.css b/static/site.css index 08135de..a42f15a 100644 --- a/static/site.css +++ b/static/site.css @@ -1,6 +1,8 @@ - -* { box-sizing: border-box; } -html, body { +* { + box-sizing: border-box; +} +html, +body { margin: 0; padding: 0; width: 100%; @@ -10,7 +12,7 @@ body { background: #000; color: #ddd; overflow: hidden; - font-family: 'Roboto', sans-serif; + font-family: "Roboto", sans-serif; font-size: 0.875rem; height: 100%; width: 100%; @@ -59,13 +61,13 @@ body { } @font-face { - font-family: 'Roboto'; - src: url('SITE_PATH/static/fonts/Roboto-Bold.ttf') format('truetype'); + font-family: "Roboto"; + src: url("SITE_PATH/static/fonts/Roboto-Bold.ttf") format("truetype"); font-weight: bold; } @font-face { - font-family: 'Roboto'; - src: url('SITE_PATH/static/fonts/Roboto-BoldItalic.ttf') format('truetype'); + font-family: "Roboto"; + src: url("SITE_PATH/static/fonts/Roboto-BoldItalic.ttf") format("truetype"); font-weight: bold; font-style: italic; } @@ -82,23 +84,23 @@ body { } */ @font-face { - font-family: 'Roboto'; - src: url('SITE_PATH/static/fonts/Roboto-Medium.ttf') format('truetype'); + font-family: "Roboto"; + src: url("SITE_PATH/static/fonts/Roboto-Medium.ttf") format("truetype"); font-weight: 300; } @font-face { - font-family: 'Roboto'; - src: url('SITE_PATH/static/fonts/Roboto-MediumItalic.ttf') format('truetype'); + font-family: "Roboto"; + src: url("SITE_PATH/static/fonts/Roboto-MediumItalic.ttf") format("truetype"); font-style: italic; font-weight: 300; } @font-face { - font-family: 'Roboto'; - src: url('SITE_PATH/static/fonts/Roboto-Regular.ttf') format('truetype'); + font-family: "Roboto"; + src: url("SITE_PATH/static/fonts/Roboto-Regular.ttf") format("truetype"); } @font-face { - font-family: 'Roboto'; - src: url('SITE_PATH/static/fonts/Roboto-Italic.ttf') format('truetype'); + font-family: "Roboto"; + src: url("SITE_PATH/static/fonts/Roboto-Italic.ttf") format("truetype"); font-style: italic; } /* @@ -114,7 +116,6 @@ body { } */ - .tile.hand_up { cursor: url(SITE_PATH/static/img/hand_up.png) 40 10, pointer; } @@ -127,8 +128,15 @@ body { .tile.hand_left { cursor: url(SITE_PATH/static/img/hand_left.png) 10 60, pointer; } +.tile.unclickable { + cursor: default; + pointer-events: none; +} +.tile.none { + cursor: default; +} .tile.link { border: 0 !important; background: transparent !important; -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2 From 285bc89a400c2faa7b6c7c327300c7842711935b Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 22 Mar 2021 16:06:13 +0100 Subject: fix proportional sizing --- README.md | 16 +++++- cli/commands/site/populate.py | 19 +++---- frontend/app/common/form.component.js | 69 ++++++++++++++++++----- frontend/app/views/page/components/page.editor.js | 2 +- frontend/app/views/tile/handles/tile.image.js | 4 +- frontend/app/views/tile/handles/tile.link.js | 8 +-- frontend/app/views/tile/handles/tile.script.js | 4 +- frontend/app/views/tile/handles/tile.text.js | 8 +-- frontend/app/views/tile/handles/tile.video.js | 2 +- frontend/app/views/tile/tile.utils.js | 14 ++--- static/site.html | 2 +- 11 files changed, 98 insertions(+), 50 deletions(-) (limited to 'static') diff --git a/README.md b/README.md index dcfa219..9e8cca6 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,19 @@ Generate a new migration if you've modified the database: ./cli.py db upgrade head ``` -## Building the site +Run the frontend demo flask server (port 3000): ``` -npm run build:production -./cli.py site export --graph swimmer +yarn demo +``` + +To programmatically create pages, modify `cli/commands/site/populate.py` + +## Deploying a site + +A hypothetical rsync command: + +``` +cd data_store/exports/ +rsync -rlptuvz ./last-museum/ lens@garden:swimmer/data_store/exports/last-museum/ ``` diff --git a/cli/commands/site/populate.py b/cli/commands/site/populate.py index 03914b2..b1b9691 100644 --- a/cli/commands/site/populate.py +++ b/cli/commands/site/populate.py @@ -1,12 +1,11 @@ import click -lines = """/static/media/last-museum/juliana-cerqueira-leite/01_JCL_AndyCbs_v1.mp4 -/static/media/last-museum/juliana-cerqueira-leite/02_JCL_Containers_v1.mp4 -/static/media/last-museum/juliana-cerqueira-leite/04_JCL_Repshp_v1.mp4 -/static/media/last-museum/juliana-cerqueira-leite/07_JCL_GmShp_v1.mp4 -/static/media/last-museum/juliana-cerqueira-leite/10_JCL_StreetPrtSftwr_v1.mp4 -/static/media/last-museum/juliana-cerqueira-leite/11_JCL_Oscilloscopes_v1.mp4 -""".split("\n") +lines = """/static/media/last-museum/nicole-foreshew/establishing1.mp4 +/static/media/last-museum/nicole-foreshew/sequence1b.mp4 +/static/media/last-museum/nicole-foreshew/sequence2.mp4 +/static/media/last-museum/nicole-foreshew/sequence3.mp4 +/static/media/last-museum/nicole-foreshew/sequence4.mp4 +/static/media/last-museum/nicole-foreshew/sequence5.mp4""".split("\n") letters = ['a','b','c','d','e','f','g','h','i','j'] @@ -22,19 +21,19 @@ def cli(ctx): return None if resp.status_code != 200 else resp.json() graph_id = 3 - name = "Juliana Cerqueira Leite. Stalfigenia, Chapter " + name = "Nicole Foreshew" index = 0 for url in lines: # slug = url.split("/")[5].replace(".mp4", "").lower() - slug = "leite-" + letters[index] + slug = "foreshew-" + str(index) # + letters[index] print(slug) index += 1 page_data = { "graph_id": graph_id, "path": slug, - "title": name + str(index), + "title": name, # + str(index), "username": "jules", "description":"", "settings": { diff --git a/frontend/app/common/form.component.js b/frontend/app/common/form.component.js index d0ebea3..de1020a 100644 --- a/frontend/app/common/form.component.js +++ b/frontend/app/common/form.component.js @@ -23,21 +23,60 @@ export const LabelDescription = props => ( ) -export const NumberInput = props => ( - -) +export class NumberInput extends Component { + constructor(props) { + super(props) + this.handleKeyDown = this.handleKeyDown.bind(this) + } + handleKeyDown(e) { + const { min, max, step, data, name, onChange } = this.props + const value = data[name] + // console.log(e.keyCode) + switch (e.keyCode) { + case 38: // up + if (e.shiftKey) { + e.preventDefault() + onChange({ + target: { + name, + value: Math.min(max, parseFloat(value) + ((step || 1) * 10)) + } + }) + } + break + case 40: // down + if (e.shiftKey) { + e.preventDefault() + onChange({ + target: { + name, + value: Math.max(min, parseFloat(value) - ((step || 1) * 10)) + } + }) + } + break + } + } + render() { + const { props } = this + return ( + + ) + } +} export const ColorInput = props => (