summaryrefslogtreecommitdiff
path: root/frontend/app
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/app')
-rw-r--r--frontend/app/site/actions.js19
-rw-r--r--frontend/app/site/app.js35
-rw-r--r--frontend/app/site/index.js17
-rw-r--r--frontend/app/site/site/site.actions.js11
-rw-r--r--frontend/app/site/site/site.reducer.js36
-rw-r--r--frontend/app/site/store.js37
-rw-r--r--frontend/app/site/types.js11
-rw-r--r--frontend/app/site/viewer/viewer.container.js96
-rw-r--r--frontend/app/utils/index.js29
-rw-r--r--frontend/app/views/page/components/page.editor.js1
-rw-r--r--frontend/app/views/page/components/tile.form.js103
-rw-r--r--frontend/app/views/page/components/tile.handle.js52
-rw-r--r--frontend/app/views/page/cursors.css3
-rw-r--r--frontend/app/views/page/page.css3
14 files changed, 189 insertions, 264 deletions
diff --git a/frontend/app/site/actions.js b/frontend/app/site/actions.js
deleted file mode 100644
index e672028..0000000
--- a/frontend/app/site/actions.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { bindActionCreators } from 'redux'
-// import { actions as crudActions } from './api'
-
-import * as siteActions from './site/site.actions'
-
-import { store } from './store'
-
-export default
- // Object.keys(crudActions)
- // .map(a => [a, crudActions[a]])
- // .concat(
- [
- ['site', siteActions],
- ] //)
- .map(p => [p[0], bindActionCreators(p[1], store.dispatch)])
- .concat([
- // ['socket', socketActions],
- ])
- .reduce((a,b) => (a[b[0]] = b[1])&&a,{}) \ No newline at end of file
diff --git a/frontend/app/site/app.js b/frontend/app/site/app.js
deleted file mode 100644
index 389e5b5..0000000
--- a/frontend/app/site/app.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import React, { Component } from 'react'
-import { ConnectedRouter } from 'connected-react-router'
-import { Route } from 'react-router'
-
-import ViewerContainer from './viewer/viewer.container'
-import actions from './actions'
-
-export default class App extends Component {
- componentDidMount() {
- const path_partz = window.location.pathname.split('/')
- const graph_name = path_partz[1]
- let path_name = null
- if (path_partz.length > 2) {
- path_name = path_partz[2]
- }
- // console.log('loading', graph_name, path_name)
- actions.site.loadSite(graph_name, path_name)
- }
-
- render() {
- return (
- <ConnectedRouter history={this.props.history}>
- <div className='app'>
- <Route path={'/:graph_name/:page_name'} component={ViewerContainer} exact />
- <Route exact key='root' path='/' render={() => {
- // setTimeout(() => this.props.history.push('/'), 10)
- return null
- }} />
- </div>
- </ConnectedRouter>
- )
- }
-}
-/*
-*/
diff --git a/frontend/app/site/index.js b/frontend/app/site/index.js
deleted file mode 100644
index 6f1a0a5..0000000
--- a/frontend/app/site/index.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom'
-import { Provider } from 'react-redux'
-
-import App from './app'
-
-import { store, history } from './store'
-
-const container = document.createElement('div')
-container.classList.add('container')
-document.body.appendChild(container)
-
-ReactDOM.render(
- <Provider store={store}>
- <App history={history} />
- </Provider>, container
-)
diff --git a/frontend/app/site/site/site.actions.js b/frontend/app/site/site/site.actions.js
deleted file mode 100644
index 79e4573..0000000
--- a/frontend/app/site/site/site.actions.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import * as types from '../types'
-import { api } from 'app/utils'
-
-export const setSiteTitle = title => dispatch => {
- document.querySelector('title').innerText = title
- dispatch({ type: types.site.set_site_title, payload: title })
-}
-
-export const loadSite = (graph_name, path_name) => dispatch => (
- api(dispatch, types.site, 'site', '/' + graph_name + '/index.json')
-)
diff --git a/frontend/app/site/site/site.reducer.js b/frontend/app/site/site/site.reducer.js
deleted file mode 100644
index 85c3486..0000000
--- a/frontend/app/site/site/site.reducer.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import * as types from '../types'
-
-const initialState = {
- siteTitle: 'swimmer',
- graph: {
- loading: true,
- }
-}
-
-export default function siteReducer(state = initialState, action) {
- console.log(action.type, action)
- switch (action.type) {
- case types.site.set_site_title:
- return {
- ...state,
- siteTitle: action.payload,
- }
-
- case types.site.loaded:
- return {
- ...state,
- graph: action.data.graph,
- }
-
- case '@@router/LOCATION_CHANGE':
- return {
- ...state,
- graph: {
- ...state.graph,
- }
- }
-
- default:
- return state
- }
-}
diff --git a/frontend/app/site/store.js b/frontend/app/site/store.js
deleted file mode 100644
index a228e2b..0000000
--- a/frontend/app/site/store.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import { applyMiddleware, compose, combineReducers, createStore } from 'redux'
-import { connectRouter, routerMiddleware } from 'connected-react-router'
-import { createBrowserHistory } from 'history'
-import thunk from 'redux-thunk'
-
-import siteReducer from './site/site.reducer'
-
-const createRootReducer = history => (
- combineReducers({
- auth: (state = {}) => state,
- router: connectRouter(history),
- site: siteReducer,
- })
-)
-
-const configureStore = (initialState = {}, history) => {
- const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
-
- const store = createStore(
- createRootReducer(history),
- initialState,
- composeEnhancers(
- applyMiddleware(
- thunk,
- routerMiddleware(history)
- ),
- ),
- )
-
- return store
-}
-
-const history = createBrowserHistory()
-const store = configureStore({}, history)
-const { dispatch } = store
-
-export { store, history, dispatch }
diff --git a/frontend/app/site/types.js b/frontend/app/site/types.js
deleted file mode 100644
index 23bed98..0000000
--- a/frontend/app/site/types.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { with_type, crud_type } from 'app/api/crud.types'
-
-export const site = with_type('site', [
- 'set_site_title', 'loading', 'loaded', 'error',
-])
-
-export const system = with_type('system', [
- 'load_site',
-])
-
-export const init = '@@INIT'
diff --git a/frontend/app/site/viewer/viewer.container.js b/frontend/app/site/viewer/viewer.container.js
deleted file mode 100644
index 42ce6c2..0000000
--- a/frontend/app/site/viewer/viewer.container.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import React, { Component } from 'react'
-import { Route } from 'react-router-dom'
-import { bindActionCreators } from 'redux'
-import { connect } from 'react-redux'
-
-import actions from '../actions'
-import { Loader } from 'app/common/loader.component'
-import TileHandle from 'app/views/page/components/tile.handle'
-
-import '../../views/page/page.css'
-
-class ViewerContainer extends Component {
- state = {
- page: {},
- }
-
- constructor(props) {
- super(props)
- this.pageRef = React.createRef()
- this.handleMouseDown = this.handleMouseDown.bind(this)
- }
-
- componentDidUpdate(prevProps) {
- // console.log('didUpdate', this.props.graph !== prevProps.graph, this.props.location.pathname !== prevProps.location.pathname)
- if (this.props.graph !== prevProps.graph || this.props.location.pathname !== prevProps.location.pathname) {
- this.load()
- }
- }
-
- load() {
- const { graph_name, page_name } = this.props.match.params
- const page_path = ["", graph_name, page_name].join('/')
- const { pages, home_page } = this.props.graph
- const page = pages[page_path]
- if (!page) {
- // console.log('-> home page')
- console.log(page_path)
- const { home_page } = this.props.graph
- this.setState({ page: pages[home_page] })
- } else {
- // console.log(page)
- console.log(page_path)
- this.setState({ page })
- }
- }
-
- handleMouseDown(e, tile) {
- // console.log(tile)
- }
-
- render() {
- const { page } = this.state
- if (this.props.graph.loading || !page.id) {
- return (
- <div>
- <div className='body'>
- <div className='page loading'>
- <Loader />
- </div>
- </div>
- </div>
- )
- }
- const { settings } = page
- const pageStyle = { backgroundColor: settings ? settings.background_color : '#000000' }
- // console.log(page)
- return (
- <div className='body'>
- <div className='page' ref={this.pageRef} style={pageStyle}>
- {page.tiles.map(tile => {
- return (
- <TileHandle
- viewing
- key={tile.id}
- tile={tile}
- bounds={this.state.bounds}
- onMouseDown={e => this.handleMouseDown(e, tile)}
- onDoubleClick={e => {}}
- />
- )
- })}
- </div>
- </div>
- )
- }
-}
-
-const mapStateToProps = state => ({
- site: state.site,
- graph: state.site.graph,
-})
-
-const mapDispatchToProps = dispatch => ({
-})
-
-export default connect(mapStateToProps, mapDispatchToProps)(ViewerContainer)
diff --git a/frontend/app/utils/index.js b/frontend/app/utils/index.js
index bb5e01d..ce8d75c 100644
--- a/frontend/app/utils/index.js
+++ b/frontend/app/utils/index.js
@@ -120,6 +120,35 @@ export const preloadImage = url => (
})
)
+export const preloadVideo = url => (
+ new Promise((resolve, reject) => {
+ const video = document.createElement('video')
+ let loaded = false
+ const bind = () => {
+ video.addEventListener('loadedmetadata', onload)
+ video.addEventListener('error', onerror)
+ }
+ const unbind = () => {
+ video.removeEventListener('loadedmetadata', onload)
+ video.removeEventListener('error', onerror)
+ }
+ const onload = () => {
+ if (loaded) return
+ loaded = true
+ unbind()
+ resolve(video)
+ }
+ const onerror = (error) => {
+ if (loaded) return
+ loaded = true
+ unbind()
+ reject(error)
+ }
+ bind()
+ video.src = url
+ })
+)
+
export const cropImage = (url, crop) => {
return new Promise((resolve, reject) => {
let { x, y, w, h } = crop
diff --git a/frontend/app/views/page/components/page.editor.js b/frontend/app/views/page/components/page.editor.js
index d324874..25342d2 100644
--- a/frontend/app/views/page/components/page.editor.js
+++ b/frontend/app/views/page/components/page.editor.js
@@ -73,6 +73,7 @@ class PageEditor extends Component {
}
handleMouseDown(e, tile) {
+ if (e.metaKey || e.ctrlKey || e.altKey || e.button !== 0) return
const bounds = this.getBoundingClientRect()
const mouseX = e.pageX
const mouseY = e.pageY
diff --git a/frontend/app/views/page/components/tile.form.js b/frontend/app/views/page/components/tile.form.js
index 3f43dd0..a9f34a7 100644
--- a/frontend/app/views/page/components/tile.form.js
+++ b/frontend/app/views/page/components/tile.form.js
@@ -10,12 +10,12 @@ import {
TextInput, NumberInput, ColorInput, Slider,
Select, LabelDescription, TextArea, Checkbox,
SubmitButton, Loader } from 'app/common'
-import { preloadImage } from 'app/utils'
+import { preloadImage, preloadVideo } from 'app/utils'
import * as tileActions from '../../tile/tile.actions'
const SELECT_TYPES = [
- "image", "text", "link", "script",
+ "image", "text", "video", "link", "script",
].map(s => ({ name: s, label: s }))
const ALIGNMENTS = [
@@ -31,6 +31,7 @@ const ALIGNMENTS = [
const REQUIRED_KEYS = {
image: ['url'],
+ video: ['url'],
text: ['content'],
link: [],
script: [],
@@ -40,6 +41,10 @@ const IMAGE_TILE_STYLES = [
'tile', 'cover', 'contain', 'contain no-repeat'
].map(style => ({ name: style, label: style }))
+const VIDEO_STYLES = [
+ 'normal', 'cover', 'contain',
+].map(style => ({ name: style, label: style }))
+
const TEXT_FONT_FAMILIES = [
'sans-serif', 'serif', 'fantasy', 'monospace', 'cursive',
].map(style => ({ name: style, label: style }))
@@ -49,6 +54,7 @@ const TEXT_FONT_STYLES = [
].map(style => ({ name: style, label: style }))
const CURSORS = [
+ { name: 'none', label: 'None', },
{ name: 'hand_up', label: 'Up', },
{ name: 'hand_down', label: 'Down', },
{ name: 'hand_left', label: 'Left', },
@@ -80,6 +86,22 @@ const newImage = (data) => ({
...data,
})
+const newVideo = (data) => ({
+ settings: {
+ ...newPosition(),
+ video_style: 'cover',
+ url: "",
+ external_link_url: "",
+ cursor: 'none',
+ muted: false,
+ loop: false,
+ autoadvance: false,
+ },
+ type: 'video',
+ target_page_id: null,
+ ...data,
+})
+
const newText = (data) => ({
settings: {
...newPosition(),
@@ -123,12 +145,14 @@ const newPosition = (data) => ({
width: 0, height: 0,
rotation: 0, scale: 1,
opacity: 1,
+ units: false,
align: "center_center",
...data,
})
const TYPE_CONSTRUCTORS = {
image: newImage,
+ video: newVideo,
text: newText,
link: newLink,
script: newScript,
@@ -277,6 +301,24 @@ class TileForm extends Component {
})
}
+ handleVideoChange(e) {
+ const { name, value } = e.target
+ this.handleSettingsSelect(name, value)
+ preloadVideo(value).then(video => {
+ // console.log(img)
+ this.props.tileActions.updateTemporaryTile({
+ ...this.props.temporaryTile,
+ settings: {
+ ...this.props.temporaryTile.settings,
+ [name]: value,
+ width: video.videoWidth,
+ height: video.videoHeight,
+ x: 0, y: 0,
+ }
+ })
+ })
+ }
+
clearErrorField(name) {
const { errorFields } = this.state
if (errorFields.has(name)) {
@@ -362,6 +404,8 @@ class TileForm extends Component {
{temporaryTile.type === 'image'
? this.renderImageForm()
+ : temporaryTile.type === 'video'
+ ? this.renderVideoForm()
: temporaryTile.type === 'text'
? this.renderTextForm()
: temporaryTile.type === 'link'
@@ -453,6 +497,61 @@ class TileForm extends Component {
)
}
+ renderVideoForm() {
+ // const { isNew } = this.props
+ const { temporaryTile } = this.props
+ const { errorFields } = this.state
+ // console.log(temporaryTile.settings)
+ return (
+ <div>
+ <div className='row imageUrl'>
+ <TextInput
+ title=""
+ placeholder='http://'
+ name="url"
+ required
+ data={temporaryTile.settings}
+ error={errorFields.has('url')}
+ onChange={this.handleVideoChange.bind(this)}
+ autoComplete="off"
+ />
+ </div>
+ <div className='row pair'>
+ <Select
+ name='video_style'
+ selected={temporaryTile.settings.video_style || 'none'}
+ options={VIDEO_STYLES}
+ title=''
+ onChange={this.handleSettingsSelect.bind(this)}
+ />
+ <Checkbox
+ label="Loop"
+ name="loop"
+ checked={temporaryTile.settings.loop}
+ onChange={this.handleSettingsSelect.bind(this)}
+ autoComplete="off"
+ />
+ </div>
+ <div className='row pair'>
+ <Checkbox
+ label="Muted"
+ name="muted"
+ checked={temporaryTile.settings.muted}
+ onChange={this.handleSettingsSelect.bind(this)}
+ autoComplete="off"
+ />
+ <Checkbox
+ label="Autoadvance"
+ name="autoadvance"
+ checked={temporaryTile.settings.autoadvance}
+ onChange={this.handleSettingsSelect.bind(this)}
+ autoComplete="off"
+ />
+ </div>
+ </div>
+ )
+ }
+
renderTextForm() {
const { temporaryTile } = this.props
const { errorFields } = this.state
diff --git a/frontend/app/views/page/components/tile.handle.js b/frontend/app/views/page/components/tile.handle.js
index 624b175..96574ff 100644
--- a/frontend/app/views/page/components/tile.handle.js
+++ b/frontend/app/views/page/components/tile.handle.js
@@ -44,6 +44,24 @@ const TileHandle = ({ tile, bounds, box, viewing, onMouseDown, onDoubleClick })
content = <img src={tile.settings.url} />
}
break
+
+ case 'video':
+ if (!tile.settings.url) {
+ return null
+ }
+ className += ' ' + tile.settings.align
+ content = (
+ <video
+ src={tile.settings.url}
+ autoPlay={true}
+ controls={false}
+ loop={tile.settings.loop}
+ muted={tile.settings.muted}
+ style={generateVideoStyle(tile, bounds)}
+ />
+ )
+ break
+
case 'text':
if (!tile.settings.content) {
return null
@@ -60,12 +78,14 @@ const TileHandle = ({ tile, bounds, box, viewing, onMouseDown, onDoubleClick })
style.backgroundColor = tile.settings.background_color || 'transparent'
style.color = tile.settings.font_color || '#dddddd!important'
break
+
case 'link':
content = ""
className += ' ' + tile.settings.align
style.width = tile.settings.width ? tile.settings.width + 'px' : 'auto'
style.height = tile.settings.height ? tile.settings.height + 'px' : 'auto'
break
+
case 'script':
content = ""
if (viewing) {
@@ -143,4 +163,36 @@ const generateTransform = (tile, box) => {
return transform.join(' ')
}
+const generateVideoStyle = (tile, bounds) => {
+ console.log(bounds)
+ const style = {
+ pointerEvents: "none",
+ }
+ switch (tile.settings.video_style) {
+ case 'normal':
+ style.width = "auto"
+ style.height = "auto"
+ break
+ case 'cover':
+ if (tile.settings.width && (tile.settings.width / tile.settings.height) > (bounds.width / bounds.height)) {
+ style.width = Math.round((tile.settings.width / tile.settings.height) * bounds.height)
+ style.height = bounds.height
+ } else {
+ style.width = bounds.width
+ style.height = Math.round((tile.settings.height / tile.settings.width) * bounds.width)
+ }
+ break
+ case 'contain':
+ if (tile.settings.width && (tile.settings.width / tile.settings.height) > (bounds.width / bounds.height)) {
+ style.width = bounds.width
+ style.height = Math.round((tile.settings.height / tile.settings.width) * bounds.width)
+ } else {
+ style.width = Math.round((tile.settings.width / tile.settings.height) * bounds.height)
+ style.height = bounds.height
+ }
+ break
+ }
+ return style
+}
+
export default TileHandle
diff --git a/frontend/app/views/page/cursors.css b/frontend/app/views/page/cursors.css
index 56fb088..778b614 100644
--- a/frontend/app/views/page/cursors.css
+++ b/frontend/app/views/page/cursors.css
@@ -13,6 +13,9 @@
.tile.hand_left {
cursor: url(/static/img/hand_left.png) 10 60, pointer;
}
+.tile.none {
+ cursor: default;
+}
.tile.link {
cursor: pointer;
diff --git a/frontend/app/views/page/page.css b/frontend/app/views/page/page.css
index 4559543..2014289 100644
--- a/frontend/app/views/page/page.css
+++ b/frontend/app/views/page/page.css
@@ -15,6 +15,9 @@
.tile.image {
display: block;
}
+.tile.video {
+ display: block;
+}
.tile.image.is_tiled {
width: 100%;
height: 100%;