summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--animism-align/cli/app/controllers/upload_controller.py26
-rw-r--r--animism-align/cli/app/sql/models/media.py2
-rw-r--r--animism-align/cli/app/sql/versions/202007171640_add_post_title_to_media.py29
-rw-r--r--animism-align/frontend/common/form.component.js1
-rw-r--r--animism-align/frontend/common/form.css10
-rw-r--r--animism-align/frontend/common/menubutton.component.js5
-rw-r--r--animism-align/frontend/views/align/components/annotations/annotation.form.js4
-rw-r--r--animism-align/frontend/views/align/containers/timeline.container.js2
-rw-r--r--animism-align/frontend/views/media/components/media.form.js55
-rw-r--r--animism-align/frontend/views/media/components/media.formImage.js18
-rw-r--r--animism-align/frontend/views/media/components/media.menu.js19
-rw-r--r--animism-align/frontend/views/media/containers/media.edit.js4
-rw-r--r--animism-align/frontend/views/media/containers/media.index.js5
-rw-r--r--animism-align/frontend/views/media/containers/media.new.js35
-rw-r--r--animism-align/frontend/views/media/media.container.js1
-rw-r--r--animism-align/frontend/views/media/media.css4
16 files changed, 180 insertions, 40 deletions
diff --git a/animism-align/cli/app/controllers/upload_controller.py b/animism-align/cli/app/controllers/upload_controller.py
index 1a324cb..e910f28 100644
--- a/animism-align/cli/app/controllers/upload_controller.py
+++ b/animism-align/cli/app/controllers/upload_controller.py
@@ -86,10 +86,18 @@ class UploadView(FlaskView):
# 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, 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")
+ 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',
@@ -98,16 +106,10 @@ class UploadView(FlaskView):
session.close()
return jsonify(response)
- uploaded_im_fn = secure_filename(file.filename)
- uploaded_im_abspath = os.path.join(app_cfg.DIR_UPLOADS, tag)
- uploaded_im_fullpath = os.path.join(uploaded_im_abspath, uploaded_im_fn)
- uploaded_im_stored_fn = 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, tag=tag, fn=uploaded_im_stored_fn, sha256=sha256, ext=ext)
+ upload = Upload(username=username, tag=tag, fn=uploaded_im_fn, sha256=sha256, ext=ext)
session.add(upload)
session.commit()
response = {
@@ -135,11 +137,13 @@ class UploadView(FlaskView):
tag = upload.tag
# uploaded_im_fn = secure_filename(fn)
- uploaded_im_abspath = os.path.join(app_cfg.DIR_UPLOADS, tag, fn)
+ uploaded_im_abspath = os.path.join(app_cfg.DIR_UPLOADS, tag)
+ uploaded_im_fullpath = os.path.join(uploaded_im_abspath, fn)
+
# uploaded_im_fullpath = os.path.join(uploaded_im_abspath, fn)
- if os.path.exists(uploaded_im_abspath):
- print("Removing " + uploaded_im_abspath)
- os.remove(uploaded_im_abspath)
+ if os.path.exists(uploaded_im_fullpath):
+ print("Removing " + uploaded_im_fullpath)
+ os.remove(uploaded_im_fullpath)
session.delete(upload)
session.commit()
diff --git a/animism-align/cli/app/sql/models/media.py b/animism-align/cli/app/sql/models/media.py
index e3a395d..4628f4d 100644
--- a/animism-align/cli/app/sql/models/media.py
+++ b/animism-align/cli/app/sql/models/media.py
@@ -18,6 +18,7 @@ class Media(Base):
title = Column(String(256, convert_unicode=True), nullable=True)
author = Column(String(256, convert_unicode=True), nullable=True)
pre_title = Column(String(256, convert_unicode=True), nullable=True)
+ post_title = Column(String(256, convert_unicode=True), nullable=True)
translated_title = Column(String(256, convert_unicode=True), nullable=True)
date = Column(String(256, convert_unicode=True), nullable=True)
source = Column(String(256, convert_unicode=True), nullable=True)
@@ -34,6 +35,7 @@ class Media(Base):
'url': self.url,
'title': self.title,
'pre_title': self.pre_title,
+ 'post_title': self.post_title,
'translated_title': self.translated_title,
'date': self.date,
'source': self.source,
diff --git a/animism-align/cli/app/sql/versions/202007171640_add_post_title_to_media.py b/animism-align/cli/app/sql/versions/202007171640_add_post_title_to_media.py
new file mode 100644
index 0000000..63dc00a
--- /dev/null
+++ b/animism-align/cli/app/sql/versions/202007171640_add_post_title_to_media.py
@@ -0,0 +1,29 @@
+"""add post_title to media
+
+Revision ID: 1e3c915ef21f
+Revises: 65b17598c815
+Create Date: 2020-07-17 16:40:08.620589
+
+"""
+from alembic import op
+import sqlalchemy as sa
+import sqlalchemy_utc
+
+
+# revision identifiers, used by Alembic.
+revision = '1e3c915ef21f'
+down_revision = '65b17598c815'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column('media', sa.Column('post_title', sa.String(length=256, _expect_unicode=True), nullable=True))
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column('media', 'post_title')
+ # ### end Alembic commands ###
diff --git a/animism-align/frontend/common/form.component.js b/animism-align/frontend/common/form.component.js
index 9a94136..c38a299 100644
--- a/animism-align/frontend/common/form.component.js
+++ b/animism-align/frontend/common/form.component.js
@@ -66,6 +66,7 @@ export const TextArea = props => (
<textarea
onChange={props.onChange}
name={props.name}
+ placeholder={props.placeholder}
value={props.data[props.name]}
/>
</label>
diff --git a/animism-align/frontend/common/form.css b/animism-align/frontend/common/form.css
index ca7c27b..bbee27e 100644
--- a/animism-align/frontend/common/form.css
+++ b/animism-align/frontend/common/form.css
@@ -113,19 +113,19 @@ input[type=checkbox] {
input[type=checkbox] + span {
font-size: 0.825rem;
text-transform: uppercase;
- color: #444;
+ color: #ddd;
}
input[type=checkbox]:hover + span {
- color: #000;
+ color: #fff;
}
input[type=checkbox]:focus + span {
- color: #84f;
+ color: #fff;
}
input[type="checkbox"]:checked + span {
- color: #000;
+ color: #fff;
}
input[type="checkbox"]:focus:checked + span {
- color: #84f;
+ color: #fff;
}
input[type="checkbox"]:after {
diff --git a/animism-align/frontend/common/menubutton.component.js b/animism-align/frontend/common/menubutton.component.js
index d4c2e31..a4eea39 100644
--- a/animism-align/frontend/common/menubutton.component.js
+++ b/animism-align/frontend/common/menubutton.component.js
@@ -84,6 +84,11 @@ const icons = {
image: '/static/img/fastfood.svg',
svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18.06 22.99h1.66c.84 0 1.53-.64 1.63-1.46L23 5.05h-5V1h-1.97v4.05h-4.97l.3 2.34c1.71.47 3.31 1.32 4.27 2.26 1.44 1.42 2.43 2.89 2.43 5.29v8.05zM1 21.99V21h15.03v.99c0 .55-.45 1-1.01 1H2.01c-.56 0-1.01-.45-1.01-1zm15.03-7c0-8-15.03-8-15.03 0h15.03zM1.02 17h15v2h-15z"/><path fill="none" d="M0 0h24v24H0z"/></svg>
},
+ copy: {
+ title: 'Copy',
+ image: '/static/img/copy.svg',
+ svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>,
+ },
// export: {
// title: 'Export',
// image: '/static/img/export.svg',
diff --git a/animism-align/frontend/views/align/components/annotations/annotation.form.js b/animism-align/frontend/views/align/components/annotations/annotation.form.js
index 3348804..53f640f 100644
--- a/animism-align/frontend/views/align/components/annotations/annotation.form.js
+++ b/animism-align/frontend/views/align/components/annotations/annotation.form.js
@@ -43,14 +43,14 @@ class AnnotationForm extends Component {
case 38: // up
e.preventDefault()
start_ts -= 0.1
- actions.align.updateAnnotationForm('start_ts', start_ts)
+ actions.align.updateAnnotationForm('start_ts', Math.max(0, start_ts))
actions.audio.seek(start_ts)
actions.align.setCursor(start_ts)
break
case 40: // down
e.preventDefault()
start_ts += 0.1
- actions.align.updateAnnotationForm('start_ts', start_ts)
+ actions.align.updateAnnotationForm('start_ts', Math.max(0, start_ts))
actions.audio.seek(start_ts)
actions.align.setCursor(start_ts)
break
diff --git a/animism-align/frontend/views/align/containers/timeline.container.js b/animism-align/frontend/views/align/containers/timeline.container.js
index a0f4d3a..a924eaf 100644
--- a/animism-align/frontend/views/align/containers/timeline.container.js
+++ b/animism-align/frontend/views/align/containers/timeline.container.js
@@ -54,7 +54,7 @@ class Timeline extends Component {
switch (e.keyCode) {
case 38: // up
e.preventDefault()
- selectedAnnotation.start_ts -= e.shiftKey ? 1 : 0.1
+ selectedAnnotation.start_ts = Math.max(selectedAnnotation.start_ts - (e.shiftKey ? 1 : 0.1), 0)
actions.align.updateSelectedAnnotation(selectedAnnotation)
actions.audio.seek(selectedAnnotation.start_ts)
actions.align.setCursor(selectedAnnotation.start_ts)
diff --git a/animism-align/frontend/views/media/components/media.form.js b/animism-align/frontend/views/media/components/media.form.js
index 9b93788..1463041 100644
--- a/animism-align/frontend/views/media/components/media.form.js
+++ b/animism-align/frontend/views/media/components/media.form.js
@@ -16,6 +16,7 @@ const newMedia = () => ({
title: '',
author: '',
pre_title: '',
+ post_title: '',
translated_title: '',
date: '',
source: '',
@@ -38,9 +39,11 @@ export default class MediaForm extends Component {
constructor(props) {
super(props)
+ this.handleKeyDown = this.handleKeyDown.bind(this)
this.handleSelect = this.handleSelect.bind(this)
this.handleChange = this.handleChange.bind(this)
this.handleSettingsChange = this.handleSettingsChange.bind(this)
+ this.handleSettingsChangeEvent = this.handleSettingsChangeEvent.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
@@ -57,6 +60,21 @@ export default class MediaForm extends Component {
...data
},
})
+ window.addEventListener('keydown', this.handleKeyDown)
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('keydown', this.handleKeyDown)
+ }
+
+ handleKeyDown(e) {
+ // console.log(e, e.keyCode)
+ if ((e.ctrlKey || e.metaKey) && e.keyCode === 83) {
+ if (e) {
+ e.preventDefault()
+ }
+ this.handleSubmit()
+ }
}
handleChange(e) {
@@ -78,7 +96,13 @@ export default class MediaForm extends Component {
})
}
+ handleSettingsChangeEvent(e) {
+ const { name, value } = e.target
+ this.handleSettingsChange(name, value)
+ }
+
handleSettingsChange(name, value) {
+ console.log(name, value)
if (name !== 'multiple') {
value = { [name]: value }
}
@@ -94,11 +118,13 @@ export default class MediaForm extends Component {
}
handleSubmit(e) {
- e.preventDefault()
+ if (e) {
+ e.preventDefault()
+ }
const { isNew, onSubmit } = this.props
const { data } = this.state
const requiredKeys = "author title date".split(" ")
- const validKeys = "type tag url title author pre_title translated_title date source medium start_ts settings".split(" ")
+ const validKeys = "type tag url title author pre_title post_title translated_title date source medium start_ts settings".split(" ")
const validData = validKeys.reduce((a,b) => { a[b] = data[b]; return a }, {})
const errorFields = requiredKeys.filter(key => !validData[key])
if (errorFields.length) {
@@ -167,7 +193,13 @@ export default class MediaForm extends Component {
<TextInput
title="Title Prefix"
name="pre_title"
- required
+ data={data}
+ onChange={this.handleChange}
+ autoComplete="off"
+ />
+ <TextInput
+ title="Title Suffix"
+ name="post_title"
data={data}
onChange={this.handleChange}
autoComplete="off"
@@ -175,7 +207,6 @@ export default class MediaForm extends Component {
<TextInput
title="Translated Title"
name="translated_title"
- required
data={data}
onChange={this.handleChange}
autoComplete="off"
@@ -205,6 +236,20 @@ export default class MediaForm extends Component {
autoComplete="off"
/>
<TextArea
+ title="Citation"
+ name="bibliography"
+ placeholder="Use if special HTML formatting needed"
+ data={data.settings}
+ onChange={this.handleSettingsChangeEvent}
+ />
+ <Checkbox
+ label="Hide in list of works"
+ name="hide_in_bibliography"
+ checked={data.settings.hide_in_bibliography}
+ onChange={this.handleSettingsChange}
+ autoComplete="off"
+ />
+ <TextArea
title="Description"
name="description"
data={data}
@@ -217,7 +262,7 @@ export default class MediaForm extends Component {
{!!errorFields.size &&
<label>
<span></span>
- <span>Please complete the required fields =)</span>
+ <span>Please complete the required fields</span>
</label>
}
</form>
diff --git a/animism-align/frontend/views/media/components/media.formImage.js b/animism-align/frontend/views/media/components/media.formImage.js
index abd0f94..4a97112 100644
--- a/animism-align/frontend/views/media/components/media.formImage.js
+++ b/animism-align/frontend/views/media/components/media.formImage.js
@@ -86,19 +86,23 @@ export default class MediaImageForm extends Component {
// when we upload an image, if the image already exists in this "position"
// on the record, we should also delete it
if (this.props.data.settings[tag] && this.props.data.settings[tag].id) {
- return actions.upload.destroy(this.props.data.settings[tag])
- .then(() => {
- return this.uploadTaggedSize(image, tag, fn)
- })
- .catch(() => {
- console.log('error deleting the image')
- return this.uploadTaggedSize(image, tag, fn)
+ return new Promise((resolve, reject) => {
+ actions.upload.destroy(this.props.data.settings[tag])
+ .then(() => {
+ console.log('destroy successful')
+ this.uploadTaggedSize(image, tag, fn).then(data => resolve(data))
+ })
+ .catch(() => {
+ console.log('error deleting the image')
+ this.uploadTaggedSize(image, tag, fn).then(data => resolve(data))
+ })
})
}
return this.uploadTaggedSize(image, tag, fn)
}
uploadTaggedSize(image, tag, fn) {
+ console.log('uploading size', tag)
const uploadData = {
image,
tag,
diff --git a/animism-align/frontend/views/media/components/media.menu.js b/animism-align/frontend/views/media/components/media.menu.js
index 3d7e86a..153a5c1 100644
--- a/animism-align/frontend/views/media/components/media.menu.js
+++ b/animism-align/frontend/views/media/components/media.menu.js
@@ -28,7 +28,7 @@ const MediaIndexMenu = () => ([
])
const MediaShowMenu = connect(mapStateToProps)((props) => ([
- <MenuButton key='back' name="back" />,
+ <MenuButton key='back' name="back" href="/media/" />,
<MenuButton key='edit' name="edit" href={"/media/" + props.match.params.id + "/edit/"} />,
<MenuButton key='delete' name="delete" onClick={() => {
const { res: media } = props.media.show
@@ -41,9 +41,18 @@ const MediaShowMenu = connect(mapStateToProps)((props) => ([
]))
const MediaNewMenu = (props) => ([
- <MenuButton key='back' name="back" />,
+ <MenuButton key='back' name="back" href="/media/" />,
])
-const MediaEditMenu = (props) => ([
- <MenuButton key='back' name="back" />,
-])
+const MediaEditMenu = connect(mapStateToProps)((props) => ([
+ <MenuButton key='back' name="back" href="/media/" />,
+ <MenuButton key='copy' name="copy" href={"/media/" + props.match.params.id + '/copy/'} label="Make a copy" />,
+ <MenuButton key='delete' name="delete" onClick={() => {
+ const { res: media } = props.media.show
+ if (confirm("Really delete this media?")) {
+ actions.media.destroy(media).then(() => {
+ history.push('/media/')
+ })
+ }
+ }} />,
+]))
diff --git a/animism-align/frontend/views/media/containers/media.edit.js b/animism-align/frontend/views/media/containers/media.edit.js
index 8c353d9..143cdfe 100644
--- a/animism-align/frontend/views/media/containers/media.edit.js
+++ b/animism-align/frontend/views/media/containers/media.edit.js
@@ -36,7 +36,7 @@ class MediaEdit extends Component {
}
return (
<div className='row formContainer'>
- <MediaMenu />
+ <MediaMenu mediaActions={this.props.mediaActions} />
<MediaForm
data={show.res}
onSubmit={this.handleSubmit.bind(this)}
@@ -51,7 +51,7 @@ const mapStateToProps = state => ({
})
const mapDispatchToProps = dispatch => ({
- // searchActions: bindActionCreators({ ...searchActions }, dispatch),
+ // mediaActions: bindActionCreators({ ...mediaActions }, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(MediaEdit)
diff --git a/animism-align/frontend/views/media/containers/media.index.js b/animism-align/frontend/views/media/containers/media.index.js
index bff781e..a865522 100644
--- a/animism-align/frontend/views/media/containers/media.index.js
+++ b/animism-align/frontend/views/media/containers/media.index.js
@@ -72,7 +72,10 @@ class MediaIndex extends Component {
<div className="row">
<MediaMenu />
<div className={'results ' + options.thumbnailSize}>
- {order.map(id => <MediaItem key={id} data={lookup[id]} />)}
+ <h2>Images</h2>
+ {order.filter(id => lookup[id].type === 'image').map(id => <MediaItem key={id} data={lookup[id]} />)}
+ <h2>Video</h2>
+ {order.filter(id => lookup[id].type === 'video').map(id => <MediaItem key={id} data={lookup[id]} />)}
</div>
</div>
{order.length >= 50 && <button className='loadMore' onClick={() => this.fetch(true)}>Load More</button>}
diff --git a/animism-align/frontend/views/media/containers/media.new.js b/animism-align/frontend/views/media/containers/media.new.js
index e740c0c..80879cb 100644
--- a/animism-align/frontend/views/media/containers/media.new.js
+++ b/animism-align/frontend/views/media/containers/media.new.js
@@ -9,6 +9,34 @@ import MediaForm from '../components/media.form'
import MediaMenu from '../components/media.menu'
class MediaNew extends Component {
+ state = {
+ loading: true,
+ initialData: {},
+ }
+
+ componentDidMount() {
+ // console.log(this.props.match.params.id)
+ if (this.props.match.params && this.props.match.params.id) {
+ actions.media.show(this.props.match.params.id)
+ .then(data => {
+ const { id, ...initialData } = data.res
+ delete initialData.settings.video
+ delete initialData.settings.crop
+ delete initialData.settings.display
+ delete initialData.settings.fullsize
+ delete initialData.settings.thumbnail
+ delete initialData.settings.bibliography
+ console.log("copying", id)
+ this.setState({
+ loading: false,
+ initialData,
+ })
+ })
+ } else {
+ this.setState({ loading: false })
+ }
+ }
+
handleSubmit(data) {
console.log(data)
actions.media.create(data)
@@ -24,12 +52,17 @@ class MediaNew extends Component {
}
render() {
+ if (this.state.loading) {
+ return (
+ <div className='row formContainer' />
+ )
+ }
return (
<div className='row formContainer'>
<MediaMenu />
<MediaForm
isNew
- data={{}}
+ data={this.state.initialData}
onSubmit={this.handleSubmit.bind(this)}
/>
</div>
diff --git a/animism-align/frontend/views/media/media.container.js b/animism-align/frontend/views/media/media.container.js
index 41fed4b..97a5b08 100644
--- a/animism-align/frontend/views/media/media.container.js
+++ b/animism-align/frontend/views/media/media.container.js
@@ -16,6 +16,7 @@ class Container extends Component {
render() {
return (
<div className='media'>
+ <Route exact path='/media/:id/copy/' component={MediaNew} />
<Route exact path='/media/:id/edit/' component={MediaEdit} />
<Route exact path='/media/new/' component={MediaNew} />
<Route exact path='/media/' component={MediaIndex} />
diff --git a/animism-align/frontend/views/media/media.css b/animism-align/frontend/views/media/media.css
index 701cf44..a2d95c8 100644
--- a/animism-align/frontend/views/media/media.css
+++ b/animism-align/frontend/views/media/media.css
@@ -8,6 +8,10 @@
margin-bottom: 1rem;
margin-right: 1rem;
}
+.results h2 {
+ display: block;
+ width: 100%;
+}
.media .results .meta > div {
max-width: 100%;
}