summaryrefslogtreecommitdiff
path: root/animism-align
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2020-07-08 20:51:27 +0200
committerJules Laplace <julescarbon@gmail.com>2020-07-08 20:51:27 +0200
commitf7cf600fe1abc92ddccdbadf30315d6f9785994f (patch)
treefe9b8e32e3b54b97bfca03dc83d0dabb72972796 /animism-align
parent5cab93aa847d347e4ae7700ecdad322506072888 (diff)
adding videos to the database
Diffstat (limited to 'animism-align')
-rw-r--r--animism-align/cli/app/settings/app_cfg.py10
-rw-r--r--animism-align/environment.yml1
-rw-r--r--animism-align/frontend/common/form.component.js7
-rw-r--r--animism-align/frontend/types.js1
-rw-r--r--animism-align/frontend/util/index.js33
-rw-r--r--animism-align/frontend/views/media/components/media.form.js13
-rw-r--r--animism-align/frontend/views/media/components/media.formImage.js64
-rw-r--r--animism-align/frontend/views/media/components/media.formImageSelection.js213
-rw-r--r--animism-align/frontend/views/media/components/media.formVideo.js89
-rw-r--r--animism-align/frontend/views/media/containers/media.index.js12
-rw-r--r--animism-align/frontend/views/media/containers/media.new.js2
-rw-r--r--animism-align/frontend/views/media/media.actions.js10
-rw-r--r--animism-align/frontend/views/media/media.css54
-rw-r--r--animism-align/package-lock.json184
-rw-r--r--animism-align/package.json2
15 files changed, 653 insertions, 42 deletions
diff --git a/animism-align/cli/app/settings/app_cfg.py b/animism-align/cli/app/settings/app_cfg.py
index 0a33b50..6fb3e3e 100644
--- a/animism-align/cli/app/settings/app_cfg.py
+++ b/animism-align/cli/app/settings/app_cfg.py
@@ -87,4 +87,12 @@ HTTP_EXTERNAL_HOST = os.getenv('HTTP_EXTERNAL_HOST') or f"http://{SERVER_NAME}"
# -----------------------------------------------------------------------------
UCODE_OK = u"\u2714" # check ok
-UCODE_NOK = u'\u2718' # x no ok \ No newline at end of file
+UCODE_NOK = u'\u2718' # x no ok
+
+# -----------------------------------------------------------------------------
+# Vimeo API
+# -----------------------------------------------------------------------------
+
+VIMEO_ACCESS_TOKEN = "9dc32c0a13b05e59527661cf0c69ad5d0896d99b"
+VIMEO_CLIENT_ID = "qJgeYR1j7uu92BOdt+Kwp3hyeaGZbG8HUrGIqAAN0Lv79rsxzdQu7F/WyO2SgCXNHiz4tB6NAC0IQkV0XerWn9fiHurhO9DC/39uhF6I4T3o5TH0LJOWx5GKPLVryruM"
+VIMEO_CLIENT_SECRET = "588aa1d15488d5b32d499ea76a03d9de"
diff --git a/animism-align/environment.yml b/animism-align/environment.yml
index 3a28790..92fb95c 100644
--- a/animism-align/environment.yml
+++ b/animism-align/environment.yml
@@ -87,6 +87,7 @@ dependencies:
- urllib3==1.25.3
- validators==0.13.0
- vine==1.3.0
+ - PyVimeo==1.0.11
- werkzeug==0.15.5
- wtforms==2.2.1
- wtforms-alchemy==0.16.9
diff --git a/animism-align/frontend/common/form.component.js b/animism-align/frontend/common/form.component.js
index f3775a2..2f9162e 100644
--- a/animism-align/frontend/common/form.component.js
+++ b/animism-align/frontend/common/form.component.js
@@ -8,8 +8,9 @@ export const TextInput = props => (
type="text"
required={props.required}
onChange={props.onChange}
+ onBlur={props.onBlur}
name={props.name}
- value={props.data[props.name]}
+ value={props.data[props.name] || ""}
placeholder={props.placeholder}
autoComplete={props.autoComplete}
/>
@@ -17,7 +18,7 @@ export const TextInput = props => (
)
export const LabelDescription = props => (
- <label className={'text description'}>
+ <label className={props.className ? 'text description ' + props.className : 'text description'}>
<span>{props.title}</span>
<span>{props.children}</span>
</label>
@@ -160,7 +161,7 @@ export class FileInputField extends Component {
<span>{title}</span>
<div className="row">
<button>
- {label || "Choose files"}
+ {label || (multiple ? "Choose files" : "Choose file")}
<FileInput
mime={mime}
multiple={multiple}
diff --git a/animism-align/frontend/types.js b/animism-align/frontend/types.js
index f92f70b..3c460a9 100644
--- a/animism-align/frontend/types.js
+++ b/animism-align/frontend/types.js
@@ -8,6 +8,7 @@ export const peaks = crud_type('peaks', [])
export const text = crud_type('text', [])
export const annotation = crud_type('annotation', [])
export const paragraph = crud_type('paragraph', [])
+export const vimeo = crud_type('vimeo', [])
export const align = crud_type('align', [
'set_display_setting',
'set_temporary_annotation',
diff --git a/animism-align/frontend/util/index.js b/animism-align/frontend/util/index.js
index d723910..a50bccb 100644
--- a/animism-align/frontend/util/index.js
+++ b/animism-align/frontend/util/index.js
@@ -128,7 +128,7 @@ export const preloadImage = url => (
})
)
-export const cropImage = (url, crop) => {
+export const cropImage = (url, crop, maxSide) => {
return new Promise((resolve, reject) => {
let { x, y, w, h } = crop
const image = new Image()
@@ -143,8 +143,22 @@ export const cropImage = (url, crop) => {
image.onload = null
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
- const width = image.naturalWidth
- const height = image.naturalHeight
+ const { naturalWidth, naturalHeight } = image
+ let height, width
+
+ if (maxSide > 0) {
+ if (naturalWidth > naturalHeight) {
+ width = Math.min(maxSide, naturalWidth)
+ height = naturalHeight * canvas.width / naturalWidth
+ } else {
+ height = Math.min(maxSide, naturalHeight)
+ width = naturalWidth * canvas.height / naturalHeight
+ }
+ } else {
+ width = naturalWidth
+ height = naturalHeight
+ }
+
canvas.width = w * width
canvas.height = h * height
ctx.drawImage(
@@ -242,11 +256,14 @@ export const api = (dispatch, type=api_type, tag, url, data) => {
} else {
req = req.then(res => res.json())
}
- req = req.then(res => dispatch({
- type: type.loaded,
- tag,
- data: res,
- }))
+ req = req.then(res => {
+ dispatch({
+ type: type.loaded,
+ tag,
+ data: res,
+ })
+ return res
+ })
.catch(err => dispatch({
type: type.error,
tag,
diff --git a/animism-align/frontend/views/media/components/media.form.js b/animism-align/frontend/views/media/components/media.form.js
index 94968df..c82b384 100644
--- a/animism-align/frontend/views/media/components/media.form.js
+++ b/animism-align/frontend/views/media/components/media.form.js
@@ -84,7 +84,7 @@ export default class MediaForm extends Component {
...this.state.data,
settings: {
...this.state.data.settings,
- [name]: [value],
+ [name]: value,
}
}
})
@@ -94,8 +94,8 @@ export default class MediaForm extends Component {
e.preventDefault()
const { isNew, onSubmit } = this.props
const { data } = this.state
- const requiredKeys = "title".split(" ")
- const validKeys = "title".split(" ")
+ 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 validData = validKeys.reduce((a,b) => { a[b] = data[b]; return a }, {})
const errorFields = requiredKeys.filter(key => !validData[key])
if (errorFields.length) {
@@ -116,12 +116,7 @@ export default class MediaForm extends Component {
render() {
const { isNew } = this.props
const { title, submitTitle, errorFields, data } = this.state
- /*
- type: '',
- tag: '',
- url: '',
- */
- console.log(data)
+ // console.log(data)
return (
<div className='form'>
<h1>{title}</h1>
diff --git a/animism-align/frontend/views/media/components/media.formImage.js b/animism-align/frontend/views/media/components/media.formImage.js
index b3d227e..c757d03 100644
--- a/animism-align/frontend/views/media/components/media.formImage.js
+++ b/animism-align/frontend/views/media/components/media.formImage.js
@@ -4,7 +4,9 @@ import { Link } from 'react-router-dom'
import { session } from '../../../session'
import { capitalize } from '../../../util'
-import { TextInput, LabelDescription, Select, TextArea, Checkbox, SubmitButton, Loader } from '../../../common'
+import { TextInput, LabelDescription, FileInputField, Select, TextArea, Checkbox, SubmitButton, Loader } from '../../../common'
+
+import { ImageSelection } from './media.formImageSelection'
export default class MediaImageForm extends Component {
state = {
@@ -15,9 +17,7 @@ export default class MediaImageForm extends Component {
this.handleSelect = this.handleSelect.bind(this)
this.handleChange = this.handleChange.bind(this)
this.handleSettingsChange = this.handleSettingsChange.bind(this)
- }
-
- componentDidMount() {
+ this.handleUpload = this.handleUpload.bind(this)
}
handleChange(e) {
@@ -33,11 +33,67 @@ export default class MediaImageForm extends Component {
this.props.onSettingsChange(name, value)
}
+ handleUpload(image) {
+ // upload fullsize
+ this.uploadFullSize(image)
+ .then(res => {
+ this.props.onSettingsChange('fullsize', data.res)
+ setTimeout(() => {
+ })
+ })
+ }
+
+ uploadFullSize(image) {
+ actions.upload.upload({
+ image,
+ tag: 'fullsize',
+ username: 'animism',
+ }).then(data => {
+ console.log(data.res)
+ return data.res
+ })
+ }
+
+ uploadThumbnail(image) {
+ actions.upload.upload({
+ image,
+ tag: 'thumbnail',
+ username: 'animism',
+ }).then(data => {
+ console.log(data.res)
+ })
+ }
+
+ uploadCrop(image) {
+ actions.upload.upload({
+ image,
+ tag: 'crop',
+ username: 'animism',
+ }).then(data => {
+ console.log(data.res)
+ this.props.onSelect('url', data.res.url)
+ })
+ }
+
render() {
const { data } = this.props
console.log(data)
return (
<div className='imageForm'>
+ {!data.url &&
+ <FileInputField
+ title='Upload image'
+ onChange={this.handleUpload}
+ />
+ }
+ {data.settings.fullsize &&
+ <div>
+ <ImageSelection
+ url={data.settings.fullsize.url}
+ crop={data.settings.crop}
+ />
+ </div>
+ }
</div>
)
}
diff --git a/animism-align/frontend/views/media/components/media.formImageSelection.js b/animism-align/frontend/views/media/components/media.formImageSelection.js
new file mode 100644
index 0000000..142525b
--- /dev/null
+++ b/animism-align/frontend/views/media/components/media.formImageSelection.js
@@ -0,0 +1,213 @@
+import React, { Component } from 'react'
+import { Link } from 'react-router-dom'
+import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+import toBlob from 'data-uri-to-blob'
+
+import { clamp } from '../../../util'
+import { Loader } from '../../../common'
+
+const defaultState = {
+ dragging: false,
+ draggingBox: false,
+ bounds: null,
+ mouseX: 0,
+ mouseY: 0,
+ box: {
+ x: 0,
+ y: 0,
+ w: 0,
+ h: 0,
+ }
+}
+
+class ImageSelection extends Component {
+ state = {
+ ...defaultState
+ }
+
+ constructor() {
+ super()
+ // bind these events in the constructor, so we can remove event listeners later
+ this.handleMouseDown = this.handleMouseDown.bind(this)
+ this.handleMouseDownOnBox = this.handleMouseDownOnBox.bind(this)
+ this.handleMouseMove = this.handleMouseMove.bind(this)
+ this.handleMouseUp = this.handleMouseUp.bind(this)
+ this.handleWindowResize = this.handleWindowResize.bind(this)
+ }
+
+ componentDidMount() {
+ document.body.addEventListener('mousemove', this.handleMouseMove)
+ document.body.addEventListener('mouseup', this.handleMouseUp)
+ window.addEventListener('resize', this.handleWindowResize)
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.state.bounds && this.props.url !== prevProps.url) {
+ this.setState({
+ ...defaultState,
+ bounds: this.getBoundingClientRect(),
+ box: this.props.crop || defaultState.box,
+ })
+ }
+ }
+
+ componentWillUnmount() {
+ document.body.removeEventListener('mousemove', this.handleMouseMove)
+ document.body.removeEventListener('mouseup', this.handleMouseUp)
+ window.removeEventListener('resize', this.handleWindowResize)
+ }
+
+ getBoundingClientRect() {
+ if (!this.imgRef) return null
+ const rect = this.imgRef.getBoundingClientRect()
+ const scrollTop = document.body.scrollTop || document.body.parentNode.scrollTop
+ const scrollLeft = document.body.scrollLeft || document.body.parentNode.scrollLeft
+ const bounds = {
+ top: rect.top + scrollTop,
+ left: rect.left + scrollLeft,
+ width: rect.width,
+ height: rect.height,
+ }
+ return bounds
+ }
+
+ handleLoad() {
+ const bounds = this.getBoundingClientRect()
+ const box = this.props.crop || defaultState.box
+ this.setState({ bounds, box })
+ }
+
+ handleWindowResize() {
+ if (!this.imgRef) return
+ const bounds = this.getBoundingClientRect()
+ this.setState({ bounds })
+ }
+
+ handleMouseDown(e) {
+ e.preventDefault()
+ const bounds = this.getBoundingClientRect()
+ const mouseX = e.pageX
+ const mouseY = e.pageY
+ const x = (mouseX - bounds.left) / bounds.width
+ const y = (mouseY - bounds.top) / bounds.height
+ const w = 1 / bounds.width
+ const h = 1 / bounds.height
+ this.setState({
+ dragging: true,
+ bounds,
+ mouseX,
+ mouseY,
+ box: {
+ x, y, w, h,
+ }
+ })
+ }
+
+ handleMouseDownOnBox(e) {
+ const bounds = this.getBoundingClientRect()
+ const mouseX = e.pageX
+ const mouseY = e.pageY
+ this.setState({
+ draggingBox: true,
+ bounds,
+ mouseX,
+ mouseY,
+ initialBox: {
+ ...this.state.box
+ },
+ box: {
+ ...this.state.box
+ }
+ })
+ }
+
+ handleMouseMove(e) {
+ const {
+ dragging, draggingBox,
+ bounds, mouseX, mouseY, initialBox, box
+ } = this.state
+ if (dragging) {
+ e.preventDefault()
+ let { x, y } = box
+ let dx = (e.pageX - mouseX) / bounds.width
+ let dy = (e.pageY - mouseY) / bounds.height
+ let w = clamp(dx, 0.0, 1.0 - x)
+ let h = clamp(dy, 0.0, 1.0 - y)
+ this.setState({
+ box: {
+ x, y, w, h,
+ }
+ })
+ } else if (draggingBox) {
+ e.preventDefault()
+ let { x, y, w, h } = initialBox
+ let dx = (e.pageX - mouseX) / bounds.width
+ let dy = (e.pageY - mouseY) / bounds.height
+ this.setState({
+ box: {
+ x: clamp(x + dx, 0, 1.0 - w),
+ y: clamp(y + dy, 0, 1.0 - h),
+ w,
+ h,
+ }
+ })
+ }
+ }
+
+ handleMouseUp(e) {
+ const { onCrop } = this.props
+ const { dragging, draggingBox, bounds, box } = this.state
+ if (!dragging && !draggingBox) return
+ e.preventDefault()
+ const { x, y, w, h } = box
+ let url = window.location.pathname
+ this.setState({
+ dragging: false,
+ draggingBox: false,
+ })
+ if (w < 10 / bounds.width || h < 10 / bounds.height) {
+ this.setState({ box: { ...defaultState.box }})
+ onCrop({})
+ } else {
+ // pass the box dimensions up - do the search again
+ onCrop(box)
+ }
+ }
+
+ render() {
+ const { url } = this.props
+ const { bounds, box } = this.state
+ const { x, y, w, h } = box
+ return (
+ <div className="imageSelection">
+ <img
+ src={url}
+ ref={ref => this.imgRef = ref}
+ onMouseDown={this.handleMouseDown}
+ onLoad={this.handleLoad.bind(this)}
+ crossOrigin='anonymous'
+ />
+ {!!w &&
+ <div
+ className="box"
+ style={{
+ left: x * bounds.width,
+ top: y * bounds.height,
+ width: w * bounds.width,
+ height: h * bounds.height,
+ }}
+ onMouseDown={this.handleMouseDownOnBox}
+ />
+ }
+ </div>
+ )
+ }
+}
+
+const boxToFixed = ({ x, y, w, h }) => ({
+ x: x.toFixed(3),
+ y: y.toFixed(3),
+ w: w.toFixed(3),
+ h: h.toFixed(3),
+})
diff --git a/animism-align/frontend/views/media/components/media.formVideo.js b/animism-align/frontend/views/media/components/media.formVideo.js
index 16c1fbb..89954b9 100644
--- a/animism-align/frontend/views/media/components/media.formVideo.js
+++ b/animism-align/frontend/views/media/components/media.formVideo.js
@@ -1,11 +1,12 @@
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
+import VimeoPlayer from '@u-wave/react-vimeo'
-import { session } from '../../../session'
import { capitalize } from '../../../util'
-
import { TextInput, LabelDescription, Select, TextArea, Checkbox, SubmitButton, Loader } from '../../../common'
+import { getVimeoMetadata } from '../media.actions'
+
export default class MediaVideoForm extends Component {
state = {
}
@@ -17,27 +18,93 @@ export default class MediaVideoForm extends Component {
this.handleSettingsChange = this.handleSettingsChange.bind(this)
}
- componentDidMount() {
- }
-
handleChange(e) {
- const { name, value } = e.target
- this.handleSelect(name, value)
+ let { name, value } = e.target
+ return this.handleSelect(name, value)
}
handleSelect(name, value) {
- this.props.onSelect(name, value)
+ value = value.trim()
+ if (name === 'url') {
+ getVimeoMetadata(value)
+ .then(data => {
+ console.log('video metadata', data)
+ this.props.onChange(name, value)
+ setTimeout(() => {
+ this.props.onSettingsChange('video', {
+ thumbnail_url: data.thumbnail_url,
+ duration: data.duration,
+ video_id: data.video_id,
+ })
+ }, 20)
+ })
+ } else {
+ this.props.onChange(name, value)
+ }
+ }
+
+ handleSettingsChange(e) {
+ let { name, value } = e.target
+ this.props.onSettingsChange(name, value)
}
- handleSettingsChange(name, value) {
+ handleSettingsSelect(name, value) {
this.props.onSettingsChange(name, value)
}
render() {
const { data } = this.props
- console.log(data)
return (
- <div className='imageForm'>
+ <div className='videoForm'>
+ <TextInput
+ title="Video URL"
+ name="url"
+ required
+ data={data}
+ onChange={this.handleChange}
+ autoComplete="off"
+ />
+
+ {data.url &&
+ <div>
+ <LabelDescription className='video'>
+ <VimeoPlayer video={data.url} />
+ </LabelDescription>
+
+ {data.settings.video && data.settings.video.thumbnail &&
+ <LabelDescription className='thumbnail'>
+ <img src={data.settings.video.thumbnail} />
+ </LabelDescription>
+ }
+
+ <TextInput
+ title="Start time"
+ name="video_start_time"
+ data={data.settings}
+ placeholder="0:00"
+ onChange={this.handleSettingsChange}
+ autoComplete="off"
+ />
+
+ <TextInput
+ title="End time"
+ name="video_end_time"
+ data={data.settings}
+ placeholder="0:00"
+ onChange={this.handleSettingsChange}
+ autoComplete="off"
+ />
+
+ <TextInput
+ title="Original duration"
+ name="original_duration"
+ data={data.settings}
+ placeholder="0:00"
+ onChange={this.handleSettingsChange}
+ autoComplete="off"
+ />
+ </div>
+ }
</div>
)
}
diff --git a/animism-align/frontend/views/media/containers/media.index.js b/animism-align/frontend/views/media/containers/media.index.js
index 09ef6ca..7797fd7 100644
--- a/animism-align/frontend/views/media/containers/media.index.js
+++ b/animism-align/frontend/views/media/containers/media.index.js
@@ -79,19 +79,23 @@ class MediaIndex extends Component {
}
}
+const thumbnailURL = data => {
+ if (data.type === 'video') return data.settings.video.thumbnail_url
+}
const MediaItem = ({ data }) => {
// console.log(data)
return (
<div className='cell'>
<div className='img'>
- <Link to={"/media/" + data.id + "/show/"}>
- <img src={data.thumb_url} alt={data.title} />
+ <Link to={"/media/" + data.id + "/edit/"}>
+ <img src={thumbnailURL(data)} alt={data.title} />
</Link>
</div>
<div className='meta center'>
<div>
- {data.title}<br />
- {data.author}
+ <i>{data.title}</i><br />
+ {data.author}<br />
+ {data.date}
</div>
</div>
</div>
diff --git a/animism-align/frontend/views/media/containers/media.new.js b/animism-align/frontend/views/media/containers/media.new.js
index 88bf467..e740c0c 100644
--- a/animism-align/frontend/views/media/containers/media.new.js
+++ b/animism-align/frontend/views/media/containers/media.new.js
@@ -42,7 +42,7 @@ const mapStateToProps = state => ({
})
const mapDispatchToProps = dispatch => ({
- // searchActions: bindActionCreators({ ...searchActions }, dispatch),
+ // uploadActions: bindActionCreators({ ...uploadActions }, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(MediaNew)
diff --git a/animism-align/frontend/views/media/media.actions.js b/animism-align/frontend/views/media/media.actions.js
new file mode 100644
index 0000000..e33746e
--- /dev/null
+++ b/animism-align/frontend/views/media/media.actions.js
@@ -0,0 +1,10 @@
+import * as types from '../../types'
+import { capitalize, api } from '../../util'
+
+export const getVimeoMetadata = url => {
+ return api(() => {}, types.vimeo, 'vimeo', 'https://vimeo.com/api/oembed.json', { url })
+ .then(data => {
+ return data
+ })
+ // const id = url.match(/\d+/i)[0];
+}
diff --git a/animism-align/frontend/views/media/media.css b/animism-align/frontend/views/media/media.css
index 2f3ca0d..251afd6 100644
--- a/animism-align/frontend/views/media/media.css
+++ b/animism-align/frontend/views/media/media.css
@@ -1,3 +1,55 @@
+.app > .media {
+ width: 100%;
+ height: calc(100% - 3.125rem);
+ overflow: scroll;
+}
+
+/* new / edit media forms */
+
.formContainer {
padding-top: 1rem;
-} \ No newline at end of file
+}
+
+.imageForm,
+.videoForm {
+ padding: 1rem 1rem 0.5rem 1rem;
+ margin: 1rem 0;
+ position: relative;
+ left: -1rem;
+ border-radius: 10px;
+}
+
+/* image form */
+
+.imageForm {
+ background: #315;
+}
+
+/* video form */
+
+.videoForm {
+ background: #314;
+}
+.videoForm .thumbnail img {
+ max-height: 200px;
+}
+
+/* image crop */
+
+.imageSelection {
+ width: 30rem;
+ position: relative;
+}
+.imageSelection img {
+ display: block;
+ max-width: 100%;
+ max-height: 20rem;
+}
+.imageSelection img.loading {
+ opacity: 0.5;
+}
+.imageSelection .box {
+ position: absolute;
+ background: rgba(255,32,64,0.05);
+ border: 1px solid #f24;
+}
diff --git a/animism-align/package-lock.json b/animism-align/package-lock.json
index 1fab790..78b4c6b 100644
--- a/animism-align/package-lock.json
+++ b/animism-align/package-lock.json
@@ -3287,11 +3287,50 @@
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"dev": true
},
+ "@types/prop-types": {
+ "version": "15.7.3",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
+ "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw=="
+ },
+ "@types/react": {
+ "version": "16.9.41",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.41.tgz",
+ "integrity": "sha512-6cFei7F7L4wwuM+IND/Q2cV1koQUvJ8iSV+Gwn0c3kvABZ691g7sp3hfEQHOUBJtccl1gPi+EyNjMIl9nGA0ug==",
+ "requires": {
+ "@types/prop-types": "*",
+ "csstype": "^2.2.0"
+ }
+ },
"@types/sortablejs": {
"version": "1.10.4",
"resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.10.4.tgz",
"integrity": "sha512-iXdJUEM09Hc0RacStDvkEi5BBdHuuwdys0L5/GtsRiEht69Df4hapA3FwFIeXn2STicqJjBAkowyD1OA3jGgwA=="
},
+ "@types/vimeo__player": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/@types/vimeo__player/-/vimeo__player-2.9.0.tgz",
+ "integrity": "sha512-KLcx6GR+iXfLKnP2ecnK808rX6uf+y/aPOzOw4+lQBc8SqlMn4xv6cX6eXgKBaZgau8qNbNLzbS4RyW5ojrJhg=="
+ },
+ "@u-wave/react-vimeo": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@u-wave/react-vimeo/-/react-vimeo-0.9.0.tgz",
+ "integrity": "sha512-xGE0WQtQEkfCdMsrGwzNvGjMARdU2pMQ3xdtmR1ruIYnImIJyUG/XrbLtOy+F9f8c4fVEtiyPrJIa15B7kCayA==",
+ "requires": {
+ "@types/react": "^16.9.32",
+ "@types/vimeo__player": "^2.9.0",
+ "@vimeo/player": "^2.10.0",
+ "prop-types": "^15.7.2"
+ }
+ },
+ "@vimeo/player": {
+ "version": "2.12.0",
+ "resolved": "https://registry.npmjs.org/@vimeo/player/-/player-2.12.0.tgz",
+ "integrity": "sha512-SrlwZxeZvoFBEdbxrEg7eheevaMey7bYbip+IoTvRVsmW7JC98ulL1qDAcwAOHMWfTvFtAG6oLl6/spccJiI6w==",
+ "requires": {
+ "native-promise-only": "0.8.1",
+ "weakmap-polyfill": "2.0.1"
+ }
+ },
"@webassemblyjs/ast": {
"version": "1.8.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
@@ -4907,6 +4946,15 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
+ "combine-errors": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz",
+ "integrity": "sha1-9N9nQAg+VwOjGBEQwrEFUfAD2oY=",
+ "requires": {
+ "custom-error-instance": "2.1.1",
+ "lodash.uniqby": "4.5.0"
+ }
+ },
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
@@ -5219,11 +5267,21 @@
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
},
+ "csstype": {
+ "version": "2.6.11",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.11.tgz",
+ "integrity": "sha512-l8YyEC9NBkSm783PFTvh0FmJy7s5pFKrDp49ZL7zBGX3fWkO+N4EEyan1qqp8cwPLDcD0OSdyY6hAMoxp34JFw=="
+ },
"csv-stringify": {
"version": "5.3.6",
"resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-5.3.6.tgz",
"integrity": "sha512-kPcRbMvo5NLLD71TAqW5K+g9kbM2HpIZJLAzm73Du8U+5TXmDp9YtXKCBLyxEh0q3Jbg8QhNFBz3b5VJzjZ/jw=="
},
+ "custom-error-instance": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz",
+ "integrity": "sha1-PPY5FIemYppiR+sMoM4ACBt+Nho="
+ },
"cyclist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
@@ -7602,6 +7660,11 @@
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
},
+ "js-base64": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.2.tgz",
+ "integrity": "sha512-1hgLrLIrmCgZG+ID3VoLNLOSwjGnoZa8tyrUdEteMeIzsT6PH7PMLyUvbDwzNE56P3PNxyvuIOx4Uh2E5rzQIw=="
+ },
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -7780,6 +7843,46 @@
"resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz",
"integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI="
},
+ "lodash._baseiteratee": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz",
+ "integrity": "sha1-NKm1VDVycnw9sueO2uPA6eZr0QI=",
+ "requires": {
+ "lodash._stringtopath": "~4.8.0"
+ }
+ },
+ "lodash._basetostring": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz",
+ "integrity": "sha1-kyfJ3FFYhmt/pLnUL0Y45XZt2d8="
+ },
+ "lodash._baseuniq": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz",
+ "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=",
+ "requires": {
+ "lodash._createset": "~4.0.0",
+ "lodash._root": "~3.0.0"
+ }
+ },
+ "lodash._createset": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz",
+ "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY="
+ },
+ "lodash._root": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz",
+ "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI="
+ },
+ "lodash._stringtopath": {
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz",
+ "integrity": "sha1-lBvPDmQmbl/B1m/tCmlZVExXaCQ=",
+ "requires": {
+ "lodash._basetostring": "~4.12.0"
+ }
+ },
"lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
@@ -7829,6 +7932,15 @@
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
+ "lodash.uniqby": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz",
+ "integrity": "sha1-o6F7v2LutiQPSRhG6XwcTipeHiE=",
+ "requires": {
+ "lodash._baseiteratee": "~4.7.0",
+ "lodash._baseuniq": "~4.6.0"
+ }
+ },
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -8148,6 +8260,11 @@
}
}
},
+ "native-promise-only": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz",
+ "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE="
+ },
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -8855,6 +8972,15 @@
"react-is": "^16.8.1"
}
},
+ "proper-lockfile": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-2.0.1.tgz",
+ "integrity": "sha1-FZ+wYZPTIAP0s2kd0uwaY0qoDR0=",
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "retry": "^0.10.0"
+ }
+ },
"prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
@@ -8934,6 +9060,11 @@
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
},
+ "querystringify": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
+ "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA=="
+ },
"ramda": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
@@ -9608,6 +9739,11 @@
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
+ "requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
+ },
"resolve": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
@@ -9683,6 +9819,11 @@
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
},
+ "retry": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
+ "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q="
+ },
"rimraf": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
@@ -10780,6 +10921,27 @@
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
"integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY="
},
+ "tus-js-client": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/tus-js-client/-/tus-js-client-1.8.0.tgz",
+ "integrity": "sha512-qPX3TywqzxocTxUZtcS8X7Aik72SVMa0jKi4hWyfvRV+s9raVzzYGaP4MoJGaF0yOgm2+b6jXaVEHogxcJ8LGw==",
+ "requires": {
+ "buffer-from": "^0.1.1",
+ "combine-errors": "^3.0.3",
+ "extend": "^3.0.2",
+ "js-base64": "^2.4.9",
+ "lodash.throttle": "^4.1.1",
+ "proper-lockfile": "^2.0.1",
+ "url-parse": "^1.4.3"
+ },
+ "dependencies": {
+ "buffer-from": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz",
+ "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg=="
+ }
+ }
+ },
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
@@ -11000,6 +11162,15 @@
"querystring": "0.2.0"
}
},
+ "url-parse": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
+ "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
+ "requires": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
@@ -11074,6 +11245,14 @@
"unist-util-stringify-position": "^1.1.1"
}
},
+ "vimeo": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/vimeo/-/vimeo-2.1.1.tgz",
+ "integrity": "sha512-6aBlIOdnCgGSigkH54DHsb1n+mW0NIAgxmh+AVEC5hwjfy6zaUtkSIrlMJbYSOwwEfkjpIBR7L8gfWDRmLaEmw==",
+ "requires": {
+ "tus-js-client": "^1.5.1"
+ }
+ },
"vm-browserify": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
@@ -11089,6 +11268,11 @@
"neo-async": "^2.5.0"
}
},
+ "weakmap-polyfill": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/weakmap-polyfill/-/weakmap-polyfill-2.0.1.tgz",
+ "integrity": "sha512-Jy177Lvb1LCrPQDWJsXyyqf4eOhcdvQHFGoCqSv921kVF5i42MVbr4e2WEwetuTLBn1NX0IhPzTmMu0N3cURqQ=="
+ },
"webpack": {
"version": "4.42.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.0.tgz",
diff --git a/animism-align/package.json b/animism-align/package.json
index e1686aa..846bd81 100644
--- a/animism-align/package.json
+++ b/animism-align/package.json
@@ -19,6 +19,7 @@
"@babel/preset-env": "^7.8.6",
"@babel/preset-react": "^7.8.3",
"@babel/runtime": "^7.8.4",
+ "@u-wave/react-vimeo": "^0.9.0",
"aws-sdk": "^2.631.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.0.6",
@@ -65,6 +66,7 @@
"style-loader": "^1.1.3",
"terser-webpack-plugin": "^2.3.5",
"uuid": "^7.0.1",
+ "vimeo": "^2.1.1",
"webpack": "^4.42.0",
"webpack-cli": "^3.3.11"
},