From 8792e9fe1c7ab76c35f9a18d866880ba3da2c13e Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Mon, 15 Mar 2021 19:09:37 +0100 Subject: move frontend site folder. add video support --- cli/app/site/export.py | 125 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 cli/app/site/export.py (limited to 'cli/app/site/export.py') diff --git a/cli/app/site/export.py b/cli/app/site/export.py new file mode 100644 index 0000000..d513286 --- /dev/null +++ b/cli/app/site/export.py @@ -0,0 +1,125 @@ +import click + +from app.settings import app_cfg +from app.utils.file_utils import load_text, write_json, write_text +from os.path import join +import os + +from app.sql.common import db, Session, Graph, Page, Tile +from distutils.dir_util import copy_tree + +def export_site(ctx, opt_graph_path, opt_output_dir): + """Export a graph""" + + # ------------------------------------------------ + # generate HTML for index and all pages + + session = Session() + graph = session.query(Graph).filter(Graph.path == opt_graph_path).first() + if graph is None: + print(f"Not a graph: {opt_graph_path}") + return + + # build everything here + graph_dir = os.path.abspath(join(opt_output_dir, graph.path)) + + # load site index + index_html = load_text(join(app_cfg.DIR_STATIC, 'site.html'), split=False) + index_html = index_html.replace('SITE_PATH', '/' + graph.path) + + # write site JSON data + site_data = { 'graph': sanitize_graph(graph.toSiteJSON()) } + write_json(site_data, join(graph_dir, 'index.json'), default=str, minify=False) + + # import custom css + site_css = load_text(join(app_cfg.DIR_STATIC, 'site.css'), split=False) + site_css = site_css.replace('SITE_PATH', '/' + graph.path) + write_text(site_css, join(graph_dir, 'site.css')) + copy_tree(join(app_cfg.DIR_STATIC, 'fonts'), join(graph_dir, 'static/fonts')) + copy_tree(join(app_cfg.DIR_STATIC, 'img'), join(graph_dir, 'static/img')) + + # write index file, redirects to homepage + home_page = site_data['graph']['home_page'] + if home_page is None: + print("Homepage not set! Shift-click a page on the graph to make it the homepage.") + return + write_text(f'', join(graph_dir, 'index.html')) + + index_path = "" + for page in graph.pages: + page_path = f'{graph.path}/{page.path}' + if page.id == graph.home_page_id: + index_path = page_path + print(f'/{page_path} [index]') + else: + print(f'/{page_path}') + write_index(graph, page, index_html, join(graph_dir, page.path, 'index.html')) + + build_javascript(graph_dir) + + print("Site export complete!") + print(f"Graph exported to: {graph_dir}") + +def build_javascript(graph_dir): + print("Building javascript...") + print(f'NODE_ENV=production node ./node_modules/webpack-cli/bin/cli.js --config ./webpack.config.site.js -o {graph_dir}/bundle.js') + os.chdir(app_cfg.DIR_PROJECT_ROOT) + os.system(f'NODE_ENV=production node ./node_modules/webpack-cli/bin/cli.js --config ./webpack.config.site.js -o {graph_dir}/bundle.js') + +def write_index(graph, page, index_html, fp_out): + if page is None: + page_title = graph.title + else: + page_title = page.title + index_html = index_html.replace('BUNDLE_PATH', join('/', graph.path, 'bundle.js')) + index_html = index_html.replace('PAGE_TITLE', page_title) + write_text(index_html, fp_out) + +def sanitize_graph(graph): + page_path_lookup = {} + page_lookup = {} + for page in graph['pages']: + page_path = join('/', graph['path'], page['path']) + if page_path in page_path_lookup: + print(f"/!\\ WARNING! Duplicate found of {page_path}") + else: + page_path_lookup[page['id']] = page_path + for page in graph['pages']: + sanitize_page(page) + if page['id'] == 12: + print(page) + for tile in page['tiles']: + if tile['target_page_id']: + if tile['target_page_id'] == -1: + tile['href'] = tile['settings']['external_link_url'] + elif tile['target_page_id'] > 0: + tile['href'] = page_path_lookup[tile['target_page_id']] + sanitize_tile(tile) + page_path = page_path_lookup[page['id']] + page_lookup[page_path] = page + # print(page_lookup['/asdf/testttt']) + graph['pages'] = page_lookup + graph['home_page'] = page_path_lookup[graph['home_page_id']] + return graph + +def sanitize_page(data): + if 'created_at' in data: + del data['created_at'] + if 'updated_at' in data: + del data['updated_at'] + if 'graph_id' in data: + del data['graph_id'] + +def sanitize_tile(data): + if 'created_at' in data: + del data['created_at'] + if 'updated_at' in data: + del data['updated_at'] + if 'username' in data: + del data['username'] + if 'graph_id' in data: + del data['graph_id'] + if 'page_id' in data: + del data['page_id'] + if 'target_page_id' in data: + del data['target_page_id'] -- cgit v1.2.3-70-g09d2 From d165a0727e42349d935ab3ee287242f1e5029742 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Wed, 17 Mar 2021 18:11:26 +0100 Subject: frontend. export/view button. interactivity sanity check --- cli/app/controllers/graph_controller.py | 11 +- cli/app/server/demo.py | 48 + cli/app/server/web.py | 2 +- cli/app/settings/app_cfg.py | 7 +- cli/app/site/export.py | 23 +- cli/cli.py | 7 + cli/commands/site/export.py | 6 +- env-sample | 1 + frontend/app/common/app.css | 14 + frontend/app/utils/index.js | 3 +- .../app/views/audio/components/audio.select.js | 11 +- frontend/app/views/graph/components/page.form.js | 10 +- frontend/app/views/graph/graph.actions.js | 11 +- frontend/app/views/graph/graph.reducer.js | 14 + frontend/app/views/page/components/page.header.js | 18 +- frontend/app/views/page/components/tile.form.js | 41 +- frontend/app/views/page/components/tile.handle.js | 1 - frontend/site/audio/audio.player.js | 43 + frontend/site/index.js | 2 + frontend/site/site.css | 20 + frontend/site/site/site.actions.js | 4 + frontend/site/site/site.reducer.js | 7 + frontend/site/store.js | 3 +- frontend/site/types.js | 4 +- frontend/site/viewer/viewer.container.js | 60 +- package-lock.json | 11491 ------------------- package.json | 4 +- webpack.config.dev.js | 45 +- webpack.config.prod.js | 55 +- yarn.lock | 5 + 30 files changed, 391 insertions(+), 11580 deletions(-) create mode 100644 cli/app/server/demo.py create mode 100644 frontend/site/audio/audio.player.js create mode 100644 frontend/site/site.css delete mode 100644 package-lock.json (limited to 'cli/app/site/export.py') diff --git a/cli/app/controllers/graph_controller.py b/cli/app/controllers/graph_controller.py index 7efda73..fcca50a 100644 --- a/cli/app/controllers/graph_controller.py +++ b/cli/app/controllers/graph_controller.py @@ -7,6 +7,7 @@ from app.sql.models.graph import Graph, GraphForm from app.sql.models.page import Page from app.sql.models.tile import Tile from app.controllers.crud_controller import CrudView +from app.site.export import export_site class GraphView(CrudView): model = Graph @@ -20,7 +21,7 @@ class GraphView(CrudView): @route('/name/', methods=['GET']) def get_name(self, graph_path: str): """ - Fetch a single {model}. + Fetch a single graph. """ session = Session() item = session.query(self.model).filter(self.model.path == graph_path).first() @@ -36,3 +37,11 @@ class GraphView(CrudView): } session.close() return jsonify(result) + + @route('/export/', methods=['GET']) + def export(self, graph_path: str): + export_site(opt_graph_path=graph_path) + result = { + 'status': 'ok', + } + return jsonify(result) diff --git a/cli/app/server/demo.py b/cli/app/server/demo.py new file mode 100644 index 0000000..847f95b --- /dev/null +++ b/cli/app/server/demo.py @@ -0,0 +1,48 @@ +import os +import logging +import logging.handlers + +logger = logging.getLogger("") +logger.setLevel(logging.DEBUG) +handler = logging.handlers.RotatingFileHandler("flask.log", + maxBytes=3000000, backupCount=2) +formatter = logging.Formatter( + '[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s') +handler.setFormatter(formatter) +logger.addHandler(handler) +logging.getLogger().addHandler(logging.StreamHandler()) + +from flask import Flask, send_from_directory, request + +from app.settings import app_cfg + +def create_demo_app(script_info=None): + """ + functional pattern for creating the flask app + """ + logging.debug("Starting Swimmer demo server...") + app = Flask(__name__, static_folder=app_cfg.DIR_EXPORTS, static_url_path='/') + app.config['SERVER_NAME'] = app_cfg.DEMO_SERVER_NAME + app.url_map.strict_slashes = False + + @app.errorhandler(404) + def not_found(error): + path, fn = os.path.split(request.path) + path = path[1:] + dir_path = os.path.join(app_cfg.DIR_EXPORTS, path) + if os.path.isfile(os.path.join(dir_path, fn)): + return send_from_directory(dir_path, fn) + if os.path.isfile(os.path.join(dir_path, fn, 'index.html')): + return send_from_directory(os.path.join(dir_path, fn), 'index.html') + return "404", 404 + + @app.route('/') + def serve_index(): + return "Swimmer demo", 200 + + @app.route('/favicon.ico') + def favicon(): + return send_from_directory(os.path.join(app_cfg.DIR_STATIC, 'img'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') + + return app diff --git a/cli/app/server/web.py b/cli/app/server/web.py index 1a3b064..5eb172c 100644 --- a/cli/app/server/web.py +++ b/cli/app/server/web.py @@ -52,7 +52,7 @@ def create_app(script_info=None): @app.route('/favicon.ico') def favicon(): return send_from_directory(os.path.join(app.root_path, 'static/img/'), - 'favicon.ico',mimetype='image/vnd.microsoft.icon') + 'favicon.ico', mimetype='image/vnd.microsoft.icon') @app.shell_context_processor def shell_context(): diff --git a/cli/app/settings/app_cfg.py b/cli/app/settings/app_cfg.py index af6cf89..4aa4bee 100644 --- a/cli/app/settings/app_cfg.py +++ b/cli/app/settings/app_cfg.py @@ -15,9 +15,10 @@ codecs.register(lambda name: codecs.lookup('utf8') if name == 'utf8mb4' else Non LOG = logging.getLogger('swimmer') # ----------------------------------------------------------------------------- -# .env config for keys +# .env config # ----------------------------------------------------------------------------- # Project directory + SELF_CWD = os.path.dirname(os.path.realpath(__file__)) # this file DIR_PROJECT_ROOT = str(Path(SELF_CWD).parent.parent.parent) @@ -31,14 +32,13 @@ load_dotenv(dotenv_path=fp_env) # ----------------------------------------------------------------------------- CLICK_GROUPS = { - # 'process': 'commands/process', 'site': 'commands/site', 'admin': 'commands/admin', 'db': '', 'flask': '', + 'demo': '', } - # ----------------------------------------------------------------------------- # File I/O # ----------------------------------------------------------------------------- @@ -86,6 +86,7 @@ except Exception as e: # ----------------------------------------------------------------------------- SERVER_NAME = os.getenv('SERVER_NAME') or '0.0.0.0:5000' +DEMO_SERVER_NAME = os.getenv('DEMO_SERVER_NAME') or '0.0.0.0:3000' HTTP_EXTERNAL_HOST = os.getenv('HTTP_EXTERNAL_HOST') or f"http://{SERVER_NAME}" # ----------------------------------------------------------------------------- diff --git a/cli/app/site/export.py b/cli/app/site/export.py index d513286..c301a60 100644 --- a/cli/app/site/export.py +++ b/cli/app/site/export.py @@ -8,7 +8,7 @@ import os from app.sql.common import db, Session, Graph, Page, Tile from distutils.dir_util import copy_tree -def export_site(ctx, opt_graph_path, opt_output_dir): +def export_site(opt_graph_path, opt_output_dir=app_cfg.DIR_EXPORTS, opt_build_js=False): """Export a graph""" # ------------------------------------------------ @@ -42,6 +42,7 @@ def export_site(ctx, opt_graph_path, opt_output_dir): home_page = site_data['graph']['home_page'] if home_page is None: print("Homepage not set! Shift-click a page on the graph to make it the homepage.") + session.close() return write_text(f'', join(graph_dir, 'index.html')) @@ -55,8 +56,10 @@ def export_site(ctx, opt_graph_path, opt_output_dir): print(f'/{page_path}') write_index(graph, page, index_html, join(graph_dir, page.path, 'index.html')) - build_javascript(graph_dir) + if opt_build_js or not os.path.exists(f"{graph_dir}/bundle.js"): + build_javascript(graph_dir) + session.close() print("Site export complete!") print(f"Graph exported to: {graph_dir}") @@ -86,22 +89,34 @@ def sanitize_graph(graph): page_path_lookup[page['id']] = page_path for page in graph['pages']: sanitize_page(page) - if page['id'] == 12: - print(page) for tile in page['tiles']: if tile['target_page_id']: if tile['target_page_id'] == -1: tile['href'] = tile['settings']['external_link_url'] elif tile['target_page_id'] > 0: tile['href'] = page_path_lookup[tile['target_page_id']] + if 'url' in tile['settings'] and tile['settings']['url'].startswith('/static'): + tile['settings']['url'] = '/' + graph['path'] + tile['settings']['url'] sanitize_tile(tile) page_path = page_path_lookup[page['id']] page_lookup[page_path] = page + for upload in graph['uploads']: + sanitize_upload(upload) + if upload['url'].startswith('/static'): + upload['url'] = '/' + graph['path'] + upload['url'] # print(page_lookup['/asdf/testttt']) graph['pages'] = page_lookup graph['home_page'] = page_path_lookup[graph['home_page_id']] return graph +def sanitize_upload(data): + if 'created_at' in data: + del data['created_at'] + if 'username' in data: + del data['username'] + if 'graph_id' in data: + del data['graph_id'] + def sanitize_page(data): if 'created_at' in data: del data['created_at'] diff --git a/cli/cli.py b/cli/cli.py index 2158398..3534c43 100755 --- a/cli/cli.py +++ b/cli/cli.py @@ -29,6 +29,13 @@ if __name__ == '__main__': cli = FlaskGroup(create_app=create_app) + elif args.group == 'demo': + + from flask.cli import FlaskGroup + from app.server.demo import create_demo_app + + cli = FlaskGroup(create_app=create_demo_app) + elif args.group == 'db': import re diff --git a/cli/commands/site/export.py b/cli/commands/site/export.py index 68773e9..78e7228 100644 --- a/cli/commands/site/export.py +++ b/cli/commands/site/export.py @@ -8,8 +8,10 @@ from app.site.export import export_site help='Graph name') @click.option('-o', '--output', 'opt_output_dir', required=True, default=app_cfg.DIR_EXPORTS, help='Output dir') +@click.option('-j', '--js/--no-js', 'opt_js', required=False, default=False, + help='Whether to rebuild the Javascript bundle') @click.pass_context -def cli(ctx, opt_graph_path, opt_output_dir): +def cli(ctx, opt_graph_path, opt_output_dir, opt_build_js): """Export a graph""" - export_site(opt_graph_path, opt_output_dir) \ No newline at end of file + export_site(opt_graph_path, opt_output_dir, opt_build_js) \ No newline at end of file diff --git a/env-sample b/env-sample index 83c36e4..6df55d5 100644 --- a/env-sample +++ b/env-sample @@ -1,6 +1,7 @@ FLASK_RUN_PORT=7500 SERVER_NAME=swim.neural.garden HTTP_EXTERNAL_HOST=https://swim.neural.garden +EXPORT_HOST=http://3.k:3000 USE_SQLITE=False diff --git a/frontend/app/common/app.css b/frontend/app/common/app.css index 2e9dc4e..486e5fa 100644 --- a/frontend/app/common/app.css +++ b/frontend/app/common/app.css @@ -116,6 +116,20 @@ header > div > button:hover { border-color: #fff; color: #fff; } + +header .building { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + margin-left: 1rem; + color: #888; +} +header .building .loader { + transform: scale(0.75); + margin-right: 0.5rem; +} + header .vcat-btn { font-size: 0.875rem; padding-left: 0.5rem; diff --git a/frontend/app/utils/index.js b/frontend/app/utils/index.js index 1114d65..6e5a909 100644 --- a/frontend/app/utils/index.js +++ b/frontend/app/utils/index.js @@ -50,7 +50,8 @@ export const pad = (n, m) => { } export const courtesyS = (n, s) => n + ' ' + (n === 1 ? s : s + 's') - +export const capitalize = s => s.split(' ').map(capitalizeWord).join(' ') +export const capitalizeWord = s => s.substr(0, 1).toUpperCase() + s.substr(1) export const padSeconds = n => n < 10 ? '0' + n : n export const timestamp = (n = 0, fps = 25) => { diff --git a/frontend/app/views/audio/components/audio.select.js b/frontend/app/views/audio/components/audio.select.js index 73142f0..cf1dfb2 100644 --- a/frontend/app/views/audio/components/audio.select.js +++ b/frontend/app/views/audio/components/audio.select.js @@ -2,6 +2,7 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import { Select } from 'app/common' +import { unslugify } from 'app/utils' const NO_AUDIO = 0 const AUDIO_TOP_OPTIONS = [ @@ -16,14 +17,14 @@ class AudioSelect extends Component { constructor(props) { super(props) - this.handleSelect = this.handleSelect.bind(this) + this.handleChange = this.handleChange.bind(this) } componentDidMount(){ const { uploads } = this.props.graph.show.res const audioUploads = uploads .filter(upload => upload.tag === 'audio') - .map(page => ({ name: upload.id, label: page.path })) + .map(upload => ({ name: upload.id, label: unslugify(upload.fn) })) let audioList = [ ...AUDIO_TOP_OPTIONS, ...audioUploads, @@ -33,6 +34,10 @@ class AudioSelect extends Component { }) } + handleChange(name, value) { + this.props.onChange(name, parseInt(value)) + } + render() { return ( 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 5bd7c56c63559d6f8c05d50735895efa429d6dd6 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Sat, 20 Mar 2021 22:49:06 +0100 Subject: timestampToSeconds converts to float --- cli/app/site/export.py | 10 ++++++++++ frontend/app/views/page/page.actions.js | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'cli/app/site/export.py') diff --git a/cli/app/site/export.py b/cli/app/site/export.py index 4ce8b64..67b9538 100644 --- a/cli/app/site/export.py +++ b/cli/app/site/export.py @@ -101,6 +101,8 @@ def sanitize_graph(graph): tile['href'] = page_path_lookup[tile['target_page_id']] if 'url' in tile['settings'] and tile['settings']['url'].startswith('/static'): tile['settings']['url'] = '/' + graph['path'] + tile['settings']['url'] + if len(tile['settings'].get('appear_after', "")): + tile['settings']['appear_after'] = timestampToSeconds(tile['settings']['appear_after']) sanitize_tile(tile) page_path = page_path_lookup[page['id']] page_lookup[page_path] = page @@ -142,3 +144,11 @@ def sanitize_tile(data): del data['page_id'] if 'target_page_id' in data: del data['target_page_id'] + +def timestampToSeconds(time_str): + time_str_parts = list(map(float, time_str.strip().split(":"))) + if len(time_str_parts) == 3: + return (time_str_parts[0] * 60 + time_str_parts[1]) * 60 + time_str_parts[2] + if len(time_str_parts) == 2: + return time_str_parts[0] * 60 + time_str_parts[1] + return time_str_parts[0] diff --git a/frontend/app/views/page/page.actions.js b/frontend/app/views/page/page.actions.js index 0ae38e0..e42d539 100644 --- a/frontend/app/views/page/page.actions.js +++ b/frontend/app/views/page/page.actions.js @@ -63,7 +63,7 @@ export const loadPopups = (page, popups) => dispatch => { } return acc }, { ...popups }) - console.log(popups) + // console.log(popups) dispatch({ type: types.page.load_popups, popups }) } export const togglePopups = () => dispatch => { -- cgit v1.2.3-70-g09d2 From 30265efd64167e9fd6b5a489bd7f8239b496ed35 Mon Sep 17 00:00:00 2001 From: Jules Laplace Date: Sat, 20 Mar 2021 23:28:50 +0100 Subject: defer appearance of items on a timer. setup ok for julia and charles popups --- cli/app/site/export.py | 2 +- frontend/app/views/tile/components/tile.form.js | 2 +- frontend/site/viewer/viewer.container.js | 25 ++++++++++++++++++++++++- 3 files changed, 26 insertions(+), 3 deletions(-) (limited to 'cli/app/site/export.py') diff --git a/cli/app/site/export.py b/cli/app/site/export.py index 67b9538..aa74165 100644 --- a/cli/app/site/export.py +++ b/cli/app/site/export.py @@ -102,7 +102,7 @@ def sanitize_graph(graph): if 'url' in tile['settings'] and tile['settings']['url'].startswith('/static'): tile['settings']['url'] = '/' + graph['path'] + tile['settings']['url'] if len(tile['settings'].get('appear_after', "")): - tile['settings']['appear_after'] = timestampToSeconds(tile['settings']['appear_after']) + tile['settings']['appear_after'] = timestampToSeconds(tile['settings']['appear_after']) or 0 sanitize_tile(tile) page_path = page_path_lookup[page['id']] page_lookup[page_path] = page diff --git a/frontend/app/views/tile/components/tile.form.js b/frontend/app/views/tile/components/tile.form.js index c2f2f60..c2a8f79 100644 --- a/frontend/app/views/tile/components/tile.form.js +++ b/frontend/app/views/tile/components/tile.form.js @@ -811,7 +811,7 @@ class TileForm extends Component {
)} {(temporaryTile.target_page_id === OPEN_POPUP_LINK || temporaryTile.target_page_id === CLOSE_POPUP_LINK) && ( -
+