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.link.js | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 frontend/app/views/tile/handles/tile.link.js (limited to 'frontend/app/views/tile/handles/tile.link.js') diff --git a/frontend/app/views/tile/handles/tile.link.js b/frontend/app/views/tile/handles/tile.link.js new file mode 100644 index 0000000..839c18c --- /dev/null +++ b/frontend/app/views/tile/handles/tile.link.js @@ -0,0 +1,34 @@ +import React from 'react' +import { generateTransform, unitsDimension } from 'app/views/tile/tile.utils' + +export default function TileScript({ tile, box, viewing, onMouseDown, onDoubleClick }) { + // console.log(tile) + const style = { + transform: generateTransform(tile, box), + opacity: tile.settings.opacity, + } + // console.log(generateTransform(tile)) + let className = ['tile', tile.type].join(' ') + if (tile.target_page_id || (viewing && tile.href)) { + className += ' ' + (tile.settings.cursor || 'hand_up') + } + + if (!tile.settings.content) { + return null + } + let content = "" + className += ' ' + tile.settings.align + style.width = unitsDimension(tile, 'width') + style.height = unitsDimension(tile, 'height') + + return ( +
+ {content} +
+ ) +} -- cgit v1.2.3-70-g09d2 From 50d5c3c2f10725af8ebb6db47c209f7000abc8f4 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Sat, 20 Mar 2021 19:24:13 +0100 Subject: remove foreignkey constraint on target_page_id. toggle popups. show list of popups, if a tile link is open/close popup --- cli/app/sql/models/page.py | 6 ++-- cli/app/sql/models/tile.py | 2 +- ...03201916_remove_foreign_key_constraint_from_.py | 29 ++++++++++++++++++ frontend/app/types.js | 2 +- frontend/app/views/page/components/page.editor.js | 2 ++ frontend/app/views/page/page.actions.js | 14 +++++++++ frontend/app/views/page/page.container.js | 1 + frontend/app/views/page/page.reducer.js | 12 +++++++- frontend/app/views/tile/components/tile.form.js | 34 ++++++++++++++++++---- frontend/app/views/tile/components/tile.list.js | 4 +++ frontend/app/views/tile/components/tile.new.js | 1 + frontend/app/views/tile/handles/tile.link.js | 3 -- 12 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 cli/app/sql/versions/202103201916_remove_foreign_key_constraint_from_.py (limited to 'frontend/app/views/tile/handles/tile.link.js') diff --git a/cli/app/sql/models/page.py b/cli/app/sql/models/page.py index 2f7065b..35efa39 100644 --- a/cli/app/sql/models/page.py +++ b/cli/app/sql/models/page.py @@ -1,11 +1,11 @@ from sqlalchemy import create_engine, Table, Column, Text, String, Integer, DateTime, JSON, ForeignKey -from sqlalchemy.orm import relationship +from sqlalchemy.orm import relationship, foreign, remote import sqlalchemy.sql.functions as func from sqlalchemy_utc import UtcDateTime, utcnow from wtforms_alchemy import ModelForm from app.sql.common import db, Base, Session -# from app.sql.models.graph import Graph +from app.sql.models.tile import Tile from app.settings import app_cfg @@ -23,7 +23,7 @@ class Page(Base): updated_at = Column(UtcDateTime(), onupdate=utcnow()) tiles = relationship("Tile", foreign_keys="Tile.page_id", lazy='dynamic', order_by="asc(Tile.sort_order)") - backlinks = relationship("Tile", foreign_keys="Tile.target_page_id", lazy='dynamic') + backlinks = relationship("Tile", primaryjoin=id == foreign(Tile.target_page_id), lazy='dynamic') def toJSON(self): return { diff --git a/cli/app/sql/models/tile.py b/cli/app/sql/models/tile.py index 3f6ce31..ed4a5f8 100644 --- a/cli/app/sql/models/tile.py +++ b/cli/app/sql/models/tile.py @@ -18,7 +18,7 @@ class Tile(Base): id = Column(Integer, primary_key=True) graph_id = Column(Integer, ForeignKey('graph.id'), nullable=True) page_id = Column(Integer, ForeignKey('page.id'), nullable=True) - target_page_id = Column(Integer, ForeignKey('page.id'), nullable=True) + target_page_id = Column(Integer, nullable=True) type = Column(String(16, convert_unicode=True), nullable=False) sort_order = Column(Integer, default=0) settings = Column(JSON, default={}, nullable=True) diff --git a/cli/app/sql/versions/202103201916_remove_foreign_key_constraint_from_.py b/cli/app/sql/versions/202103201916_remove_foreign_key_constraint_from_.py new file mode 100644 index 0000000..ed19feb --- /dev/null +++ b/cli/app/sql/versions/202103201916_remove_foreign_key_constraint_from_.py @@ -0,0 +1,29 @@ +"""remove foreign key constraint from target_page_id + +Revision ID: 9b687880918d +Revises: 3f7df6bf63b8 +Create Date: 2021-03-20 19:16:21.582373 + +""" +from alembic import op +import sqlalchemy as sa +import sqlalchemy_utc + + +# revision identifiers, used by Alembic. +revision = '9b687880918d' +down_revision = '3f7df6bf63b8' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint('tile_ibfk_3', 'tile', type_='foreignkey') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_foreign_key('tile_ibfk_3', 'tile', 'page', ['target_page_id'], ['id']) + # ### end Alembic commands ### diff --git a/frontend/app/types.js b/frontend/app/types.js index bcd8053..f0f1e27 100644 --- a/frontend/app/types.js +++ b/frontend/app/types.js @@ -15,7 +15,7 @@ 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_popups', 'load_popups', 'toggle_sidebar_side', ]) diff --git a/frontend/app/views/page/components/page.editor.js b/frontend/app/views/page/components/page.editor.js index 2f14ffb..03190e3 100644 --- a/frontend/app/views/page/components/page.editor.js +++ b/frontend/app/views/page/components/page.editor.js @@ -174,6 +174,8 @@ class PageEditor extends Component { return (
{res.tiles && res.tiles.map(tile => { + console.log(tile.type, tile.settings.is_popup) + if (!this.props.page.editor.showingPopups && tile.settings.is_popup) return if (temporaryTile && temporaryTile.id === tile.id) { tile = temporaryTile } diff --git a/frontend/app/views/page/page.actions.js b/frontend/app/views/page/page.actions.js index c584848..0ae38e0 100644 --- a/frontend/app/views/page/page.actions.js +++ b/frontend/app/views/page/page.actions.js @@ -52,6 +52,20 @@ export const toggleTileList = () => dispatch => { // Popups +export const loadPopups = (page, popups) => dispatch => { + const state = store.getState() + page = page || state.page.show.res + popups = popups || state.page.editor.popups + popups = page.tiles.reduce((acc, tile) => { + const { is_popup, popup_group } = tile.settings + if (is_popup) { + acc[popup_group] = acc[popup_group] || false + } + return acc + }, { ...popups }) + console.log(popups) + dispatch({ type: types.page.load_popups, popups }) +} export const togglePopups = () => dispatch => { dispatch({ type: types.page.toggle_popups }) } diff --git a/frontend/app/views/page/page.container.js b/frontend/app/views/page/page.container.js index decdf79..0ad9806 100644 --- a/frontend/app/views/page/page.container.js +++ b/frontend/app/views/page/page.container.js @@ -44,6 +44,7 @@ class PageContainer extends Component { this.props.pageActions.showGraphAndPageIfUnloaded(this.props.match.params) .then(data => { actions.site.setSiteTitle(data.res.title) + this.props.pageActions.loadPopups(data.res, {}) if (!data.res.tiles.length) { this.props.pageActions.showAddTileForm() } else { diff --git a/frontend/app/views/page/page.reducer.js b/frontend/app/views/page/page.reducer.js index b0c4553..a1f281a 100644 --- a/frontend/app/views/page/page.reducer.js +++ b/frontend/app/views/page/page.reducer.js @@ -11,6 +11,7 @@ const initialState = crudState('page', { tileList: false, showingPopups: true, sidebarOnRight: true, + popups: {}, }, options: { } @@ -202,7 +203,16 @@ export default function pageReducer(state = initialState, action) { ...state, editor: { ...state.editor, - togglePopups: !state.editor.togglePopups, + showingPopups: !state.editor.showingPopups, + } + } + + case types.page.load_popups: + return { + ...state, + editor: { + ...state.editor, + popups: action.popups, } } diff --git a/frontend/app/views/tile/components/tile.form.js b/frontend/app/views/tile/components/tile.form.js index 728bc05..b33f7b8 100644 --- a/frontend/app/views/tile/components/tile.form.js +++ b/frontend/app/views/tile/components/tile.form.js @@ -76,6 +76,12 @@ const PAGE_LIST_TOP_OPTIONS = [ { name: -99, label: '──────────', disabled: true }, ] +const NO_POPUP = 0 +const POPUP_LIST_TOP_OPTIONS = [ + { name: NO_POPUP, label: 'Select a popup group' }, + { name: -99, label: '──────────', disabled: true }, +] + // target_page_id = Column(Integer, ForeignKey('page.id'), nullable=True) // https://s3.amazonaws.com/i.asdf.us/im/1c/gradient_gold1-SpringGreen1_1321159749.jpg @@ -181,6 +187,7 @@ class TileForm extends Component { errorFields: new Set([]), modified: false, pageList: [], + popupList: [], } constructor(props){ @@ -211,7 +218,11 @@ class TileForm extends Component { ...PAGE_LIST_TOP_OPTIONS, ...linkPages.map(page => ({ name: page.id, label: page.path })) ] - this.setState({ pageList }) + let popupList = [ + ...POPUP_LIST_TOP_OPTIONS, + ...Object.keys(page.editor.popups).map(popup_group => ({ name: popup_group, label: popup_group })) + ] + this.setState({ pageList, popupList }) if (isNew) { const newTile = newImage({ id: "new", @@ -760,7 +771,7 @@ class TileForm extends Component { renderHyperlinkForm() { const { temporaryTile } = this.props - const { pageList } = this.state + const { pageList, popupList } = this.state const isExternalLink = temporaryTile.target_page_id === EXTERNAL_LINK const isPopupLink = ( temporaryTile.target_page_id === OPEN_POPUP_LINK || @@ -785,8 +796,8 @@ class TileForm extends Component { onChange={this.handleSettingsSelect} />
-
- {isExternalLink && + {isExternalLink && ( +
- } -
+
+ )} + {(temporaryTile.target_page_id === OPEN_POPUP_LINK || temporaryTile.target_page_id === CLOSE_POPUP_LINK) && ( +
+
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.link.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 => (