From 17fb6581d305732e2cf0add7f3444e1aa80aec5c Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Fri, 19 Mar 2021 19:10:26 +0100 Subject: split tile handles into individual files. add video subsection loop --- frontend/app/views/tile/handles/tile.video.js | 72 +++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 frontend/app/views/tile/handles/tile.video.js (limited to 'frontend/app/views/tile/handles/tile.video.js') diff --git a/frontend/app/views/tile/handles/tile.video.js b/frontend/app/views/tile/handles/tile.video.js new file mode 100644 index 0000000..001cce2 --- /dev/null +++ b/frontend/app/views/tile/handles/tile.video.js @@ -0,0 +1,72 @@ +import React, { Component } from 'react' + +import { generateTransform, generateVideoStyle } from 'app/views/tile/tile.utils' +import { timestampToSeconds } from 'app/utils' + +export default class TileVideo extends Component { + constructor(props) { + super(props) + this.videoRef = React.createRef() + this.handleTimeUpdate = this.handleTimeUpdate.bind(this) + this.handleEnded = this.handleEnded.bind(this) + } + componentDidMount() { + this.videoRef.current.addEventListener('ended', this.handleEnded) + this.videoRef.current.addEventListener('timeupdate', this.handleTimeUpdate) + } + componentWillUnmount() { + this.videoRef.current.removeEventListener('timeupdate', this.handleTimeUpdate) + this.videoRef.current.removeEventListener('ended', this.handleEnded) + } + handleTimeUpdate() { + if (this.props.tile.settings.loop && this.props.tile.settings.loop_section) { + const loop_start = timestampToSeconds(this.props.tile.settings.loop_start) || 0 + const loop_end = timestampToSeconds(this.props.tile.settings.loop_end) || this.videoRef.current.duration + if (this.videoRef.current.currentTime > loop_end) { + this.videoRef.current.currentTime = loop_start + } + } + } + handleEnded() { + this.props.onPlaybackEnded(this.props.tile) + if (this.props.tile.settings.loop && this.props.tile.settings.loop_section) { + const loop_start = timestampToSeconds(this.props.tile.settings.loop_start) || 0 + this.videoRef.current.currentTime = loop_start + } + } + render() { + let { tile, bounds, box, viewing, onMouseDown, onDoubleClick } = this.props + // console.log(tile) + const style = { + transform: generateTransform(tile, box), + opacity: tile.settings.opacity, + } + let className = ['tile', tile.type].join(' ') + if (tile.target_page_id || (viewing && tile.href)) { + className += ' ' + (tile.settings.cursor || 'hand_up') + } + // console.log(tile.settings) + if (!tile.settings.url) { + return null + } + className += ' ' + tile.settings.align + return ( +
+
+ ) + } +} -- cgit v1.2.3-70-g09d2 From d9ee2c97882ea5ace9c28ac6560ffa240daf9345 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Sat, 20 Mar 2021 18:03:31 +0100 Subject: toggle side of sidebar. popup form. wait to appear form. --- frontend/app/common/slider.component.js | 4 ++-- frontend/app/types.js | 2 ++ .../app/views/audio/components/audio.select.js | 2 +- frontend/app/views/graph/graph.css | 19 ++++++++++++++++++ frontend/app/views/page/components/page.header.js | 1 + frontend/app/views/page/page.actions.js | 10 ++++++++++ frontend/app/views/page/page.container.js | 2 +- frontend/app/views/page/page.reducer.js | 19 ++++++++++++++++++ frontend/app/views/tile/components/tile.form.js | 15 ++++++++------ frontend/app/views/tile/handles/tile.video.js | 23 +++++++++++++++++----- 10 files changed, 82 insertions(+), 15 deletions(-) (limited to 'frontend/app/views/tile/handles/tile.video.js') diff --git a/frontend/app/common/slider.component.js b/frontend/app/common/slider.component.js index 9d96b1e..d19ab9b 100644 --- a/frontend/app/common/slider.component.js +++ b/frontend/app/common/slider.component.js @@ -53,7 +53,7 @@ export default class Slider extends Component { } } handleKeyDown(e) { - console.log(e.keyCode) + // console.log(e.keyCode) } handleRange(e){ let { value: new_value } = e.target @@ -90,7 +90,7 @@ export default class Slider extends Component { step = 1 value = this.props.options.indexOf(value) } else { - step = (this.props.max - this.props.min) / 100 + step = this.props.step || (this.props.max - this.props.min) / 100 text_value = parseFloat(value).toFixed(2) } return ( diff --git a/frontend/app/types.js b/frontend/app/types.js index 19d1e69..bcd8053 100644 --- a/frontend/app/types.js +++ b/frontend/app/types.js @@ -15,6 +15,8 @@ export const page = crud_type('page', [ 'update_page_tile', 'set_tile_sort_order', 'update_tile_sort_order', 'show_tile_list', 'hide_tile_list', 'toggle_tile_list', + 'toggle_popups', + 'toggle_sidebar_side', ]) export const tile = crud_type('tile', [ diff --git a/frontend/app/views/audio/components/audio.select.js b/frontend/app/views/audio/components/audio.select.js index cf1dfb2..384bb7a 100644 --- a/frontend/app/views/audio/components/audio.select.js +++ b/frontend/app/views/audio/components/audio.select.js @@ -6,7 +6,7 @@ import { unslugify } from 'app/utils' const NO_AUDIO = 0 const AUDIO_TOP_OPTIONS = [ - { name: NO_AUDIO, label: 'No Audio' }, + { name: NO_AUDIO, label: 'No Sound' }, { name: -2, label: '──────────', disabled: true }, ] diff --git a/frontend/app/views/graph/graph.css b/frontend/app/views/graph/graph.css index c6ef115..a557280 100644 --- a/frontend/app/views/graph/graph.css +++ b/frontend/app/views/graph/graph.css @@ -29,6 +29,10 @@ max-height: 100%; z-index: 20; } +.sidebar.left { + right: auto; + left: 0; +} .box { width: 15rem; padding: 0.5rem; @@ -127,6 +131,21 @@ font-size: smaller; margin-bottom: 0.25rem; } +button.box_corner { + position: absolute; + top: 1.25rem; right: 1.25rem; + padding: 0.5rem; + background: transparent; + border: 0; + border-radius: 4px; +} +button.box_corner:hover { + color: #fff; + background: rgba(64,64,128,0.5); +} +.sidebar.left button.box_corner { + transform: scaleX(-1); +} .box .slider { display: flex; diff --git a/frontend/app/views/page/components/page.header.js b/frontend/app/views/page/components/page.header.js index dbdf1b6..d40f6e0 100644 --- a/frontend/app/views/page/components/page.header.js +++ b/frontend/app/views/page/components/page.header.js @@ -27,6 +27,7 @@ function PageHeader(props) {
+
diff --git a/frontend/app/views/page/page.actions.js b/frontend/app/views/page/page.actions.js index d2bbbe2..c584848 100644 --- a/frontend/app/views/page/page.actions.js +++ b/frontend/app/views/page/page.actions.js @@ -50,6 +50,16 @@ export const toggleTileList = () => dispatch => { dispatch({ type: types.page.toggle_tile_list }) } +// Popups + +export const togglePopups = () => dispatch => { + dispatch({ type: types.page.toggle_popups }) +} + +export const toggleSidebarSide = () => dispatch => { + dispatch({ type: types.page.toggle_sidebar_side }) +} + // Update local page tile state when we change it export const updatePageTile = tile => dispatch => { diff --git a/frontend/app/views/page/page.container.js b/frontend/app/views/page/page.container.js index 68347b7..decdf79 100644 --- a/frontend/app/views/page/page.container.js +++ b/frontend/app/views/page/page.container.js @@ -70,7 +70,7 @@ class PageContainer extends Component {
-
+
{this.props.graph.editor.editingPage && } {this.props.page.editor.addingTile && } {this.props.page.editor.editingTile && } diff --git a/frontend/app/views/page/page.reducer.js b/frontend/app/views/page/page.reducer.js index c2d231a..b0c4553 100644 --- a/frontend/app/views/page/page.reducer.js +++ b/frontend/app/views/page/page.reducer.js @@ -9,6 +9,8 @@ const initialState = crudState('page', { editingTile: false, currentEditTileId: 0, tileList: false, + showingPopups: true, + sidebarOnRight: true, }, options: { } @@ -195,6 +197,23 @@ export default function pageReducer(state = initialState, action) { } } + case types.page.toggle_popups: + return { + ...state, + editor: { + ...state.editor, + togglePopups: !state.editor.togglePopups, + } + } + + case types.page.toggle_sidebar_side: + return { + ...state, + editor: { + ...state.editor, + sidebarOnRight: !state.editor.sidebarOnRight, + } + } default: return state diff --git a/frontend/app/views/tile/components/tile.form.js b/frontend/app/views/tile/components/tile.form.js index 7d0780d..728bc05 100644 --- a/frontend/app/views/tile/components/tile.form.js +++ b/frontend/app/views/tile/components/tile.form.js @@ -13,7 +13,8 @@ import { import AudioSelect from 'app/views/audio/components/audio.select' import { preloadImage, preloadVideo } from 'app/utils' -import * as tileActions from '../../tile/tile.actions' +import * as pageActions from 'app/views/page/page.actions' +import * as tileActions from 'app/views/tile/tile.actions' const SELECT_TYPES = [ "image", "text", "video", "link", "script", @@ -66,13 +67,13 @@ const CURSORS = [ const NO_LINK = 0 const EXTERNAL_LINK = -1 const OPEN_POPUP_LINK = -2 -const CLOSE_POPUP_LINK = -2 +const CLOSE_POPUP_LINK = -3 const PAGE_LIST_TOP_OPTIONS = [ { name: NO_LINK, label: 'No link' }, { name: EXTERNAL_LINK, label: 'External link' }, { name: OPEN_POPUP_LINK, label: 'Open popup' }, { name: CLOSE_POPUP_LINK, label: 'Close popup' }, - { name: -3, label: '──────────', disabled: true }, + { name: -99, label: '──────────', disabled: true }, ] // target_page_id = Column(Integer, ForeignKey('page.id'), nullable=True) @@ -410,6 +411,9 @@ class TileForm extends Component { return (

{title}

+
diff --git a/frontend/app/views/tile/handles/tile.image.js b/frontend/app/views/tile/handles/tile.image.js index fd34876..beeb36a 100644 --- a/frontend/app/views/tile/handles/tile.image.js +++ b/frontend/app/views/tile/handles/tile.image.js @@ -1,10 +1,10 @@ import React from 'react' import { generateTransform } from 'app/views/tile/tile.utils' -export default function TileImage({ tile, box, viewing, onMouseDown, onDoubleClick }) { +export default function TileImage({ tile, box, videoBounds, viewing, onMouseDown, onDoubleClick }) { // console.log(tile) const style = { - transform: generateTransform(tile, box), + transform: generateTransform(tile, box, videoBounds), opacity: tile.settings.opacity, } // console.log(generateTransform(tile)) diff --git a/frontend/app/views/tile/handles/tile.link.js b/frontend/app/views/tile/handles/tile.link.js index 20d881b..a87b95f 100644 --- a/frontend/app/views/tile/handles/tile.link.js +++ b/frontend/app/views/tile/handles/tile.link.js @@ -1,10 +1,10 @@ import React from 'react' import { generateTransform, unitsDimension } from 'app/views/tile/tile.utils' -export default function TileScript({ tile, box, viewing, onMouseDown, onDoubleClick }) { +export default function TileScript({ tile, box, videoBounds, viewing, onMouseDown, onDoubleClick }) { // console.log(tile) const style = { - transform: generateTransform(tile, box), + transform: generateTransform(tile, box, videoBounds), opacity: tile.settings.opacity, } // console.log(generateTransform(tile)) @@ -15,8 +15,8 @@ export default function TileScript({ tile, box, viewing, onMouseDown, onDoubleCl let content = "" className += ' ' + tile.settings.align - style.width = unitsDimension(tile, 'width') - style.height = unitsDimension(tile, 'height') + style.width = unitsDimension(tile, 'width', videoBounds) + style.height = unitsDimension(tile, 'height', videoBounds) return (
className += ' ' + tile.settings.align - style.width = unitsDimension(tile, 'width') - style.height = unitsDimension(tile, 'height') + style.width = unitsDimension(tile, 'width', videoBounds) + style.height = unitsDimension(tile, 'height', videoBounds) style.fontFamily = tile.settings.font_family style.fontSize = tile.settings.font_size + 'px' style.lineHeight = 1.5 diff --git a/frontend/app/views/tile/handles/tile.video.js b/frontend/app/views/tile/handles/tile.video.js index a9f0d09..271a671 100644 --- a/frontend/app/views/tile/handles/tile.video.js +++ b/frontend/app/views/tile/handles/tile.video.js @@ -10,27 +10,33 @@ export default class TileVideo extends Component { this.handleTimeUpdate = this.handleTimeUpdate.bind(this) this.handleEnded = this.handleEnded.bind(this) } + componentDidMount() { this.bind() } + componentDidUpdate() { this.unbind() this.bind() } + componentWillUnmount() { this.unbind() } + bind() { if (!this.videoRef.current) return this.el = this.videoRef.current this.el.addEventListener('ended', this.handleEnded) this.el.addEventListener('timeupdate', this.handleTimeUpdate) } + unbind() { if (!this.el) return this.el.removeEventListener('timeupdate', this.handleTimeUpdate) this.el.removeEventListener('ended', this.handleEnded) } + handleTimeUpdate() { if (this.props.tile.settings.loop && this.props.tile.settings.loop_section) { const loop_start = timestampToSeconds(this.props.tile.settings.loop_start) || 0 @@ -40,6 +46,7 @@ export default class TileVideo extends Component { } } } + handleEnded() { this.props.onPlaybackEnded(this.props.tile) if (this.props.tile.settings.loop && this.props.tile.settings.loop_section) { @@ -47,11 +54,12 @@ export default class TileVideo extends Component { this.videoRef.current.currentTime = loop_start } } + render() { - let { tile, bounds, box, viewing, onMouseDown, onDoubleClick } = this.props + let { tile, box, bounds, videoBounds, viewing, onMouseDown, onDoubleClick } = this.props // console.log(tile) const style = { - transform: generateTransform(tile, box), + transform: generateTransform(tile, box, videoBounds), opacity: tile.settings.opacity, } let className = ['tile', tile.type].join(' ') diff --git a/frontend/app/views/tile/tile.utils.js b/frontend/app/views/tile/tile.utils.js index 8782f85..ed1cbc8 100644 --- a/frontend/app/views/tile/tile.utils.js +++ b/frontend/app/views/tile/tile.utils.js @@ -1,4 +1,4 @@ -export const generateTransform = (tile, box) => { +export const generateTransform = (tile, box, videoBounds) => { let { x, y, align, rotation, scale, units, is_tiled } = tile.settings if (is_tiled) { return 'translateZ(0)' @@ -18,8 +18,11 @@ export const generateTransform = (tile, box) => { } // if (x % 2 == 1) x += 0.5 // if (y % 2 == 1) y += 0.5 - transform.push('translateX(' + x + units + ')') - transform.push('translateY(' + y + units + ')') + const xUnits = units === 'video' ? videoUnits(x, videoBounds) : x + units + const yUnits = units === 'video' ? videoUnits(y, videoBounds) : y + units + + transform.push('translateX(' + xUnits + ')') + transform.push('translateY(' + yUnits + ')') if (scale !== 1) { transform.push('scale(' + scale + ')') } @@ -60,9 +63,18 @@ export const generateVideoStyle = (tile, bounds) => { return style } -export const unitsDimension = (tile, dimension) => { +export const unitsDimension = (tile, dimension, videoBounds) => { const value = tile.settings[dimension] if (!value) return "auto" - if (tile.settings.units) return value + tile.settings.units + if (tile.settings.units) { + if (tile.settings.units === 'video') { + return videoUnits(value, videoBounds) + } + return value + tile.settings.units + } return value + "px" } + +export const videoUnits = (value, videoBounds) => ( + (value / 1000 * Math.max(videoBounds.width, videoBounds.height)) + 'px' +) \ No newline at end of file diff --git a/frontend/site/viewer/viewer.container.js b/frontend/site/viewer/viewer.container.js index 6f6b850..d1fa885 100644 --- a/frontend/site/viewer/viewer.container.js +++ b/frontend/site/viewer/viewer.container.js @@ -15,6 +15,7 @@ class ViewerContainer extends Component { page: {}, bounds: { width: window.innerWidth, height: window.innerHeight }, roadblock: false, + popups: {}, } constructor(props) { @@ -44,9 +45,9 @@ class ViewerContainer extends Component { const { pages, home_page } = this.props.graph const page = pages[page_path] || pages[home_page] if (!this.props.interactive && hasAutoplay(page)) { - this.setState({ page, roadblock: true }) + this.setState({ page, popups: {}, roadblock: true }) } else { - this.setState({ page, roadblock: false }) + this.setState({ page, popups: {}, roadblock: false }) actions.site.interact() this.props.audio.player.playPage(page) } @@ -67,6 +68,22 @@ class ViewerContainer extends Component { window.location.href = tile.href return } + else if (tile.href === '__open_popup') { + this.setState({ + popups: { + ...this.state.popups, + [tile.settings.target_popup]: true, + } + }) + } + else if (tile.href === '__close_popup') { + this.setState({ + popups: { + ...this.state.popups, + [tile.settings.target_popup]: false, + } + }) + } else if (!tile.settings.navigate_when_audio_finishes) { history.push(tile.href) } @@ -86,7 +103,7 @@ class ViewerContainer extends Component { } render() { - const { page, audio } = this.state + const { page, audio, popups } = this.state if (this.state.roadblock) { return this.renderRoadblock() } @@ -103,11 +120,16 @@ class ViewerContainer extends Component { } const { settings } = page const pageStyle = { backgroundColor: settings ? settings.background_color : '#000000' } + const videoBounds = (page.tiles.length && page.tiles[0].type === 'video') ? { + width: page.tiles[0].settings.width, + height: page.tiles[0].settings.height, + } : this.state.bounds // console.log(page) return (
{page.tiles.map(tile => { + if (tile.settings.is_popup && !popups[tile.settings.popup_group]) return return ( this.handleMouseDown(e, tile)} onPlaybackEnded={e => this.handlePlaybackEnded(e, tile)} onDoubleClick={e => {}} -- 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 'frontend/app/views/tile/handles/tile.video.js') 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 => (