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/utils/index.js | 10 + frontend/app/views/page/components/page.editor.js | 6 +- frontend/app/views/page/components/tile.edit.js | 86 --- frontend/app/views/page/components/tile.form.js | 848 --------------------- frontend/app/views/page/components/tile.handle.js | 209 ----- frontend/app/views/page/components/tile.list.js | 142 ---- frontend/app/views/page/components/tile.new.js | 55 -- frontend/app/views/page/page.container.js | 10 +- frontend/app/views/tile/components/tile.edit.js | 86 +++ frontend/app/views/tile/components/tile.form.js | 879 ++++++++++++++++++++++ frontend/app/views/tile/components/tile.handle.js | 8 + frontend/app/views/tile/components/tile.list.js | 142 ++++ frontend/app/views/tile/components/tile.new.js | 55 ++ frontend/app/views/tile/handles/index.js | 14 + frontend/app/views/tile/handles/tile.image.js | 54 ++ frontend/app/views/tile/handles/tile.link.js | 34 + frontend/app/views/tile/handles/tile.script.js | 34 + frontend/app/views/tile/handles/tile.text.js | 41 + frontend/app/views/tile/handles/tile.video.js | 72 ++ frontend/app/views/tile/tile.utils.js | 68 ++ 20 files changed, 1505 insertions(+), 1348 deletions(-) delete mode 100644 frontend/app/views/page/components/tile.edit.js delete mode 100644 frontend/app/views/page/components/tile.form.js delete mode 100644 frontend/app/views/page/components/tile.handle.js delete mode 100644 frontend/app/views/page/components/tile.list.js delete mode 100644 frontend/app/views/page/components/tile.new.js create mode 100644 frontend/app/views/tile/components/tile.edit.js create mode 100644 frontend/app/views/tile/components/tile.form.js create mode 100644 frontend/app/views/tile/components/tile.handle.js create mode 100644 frontend/app/views/tile/components/tile.list.js create mode 100644 frontend/app/views/tile/components/tile.new.js create mode 100644 frontend/app/views/tile/handles/index.js create mode 100644 frontend/app/views/tile/handles/tile.image.js create mode 100644 frontend/app/views/tile/handles/tile.link.js create mode 100644 frontend/app/views/tile/handles/tile.script.js create mode 100644 frontend/app/views/tile/handles/tile.text.js create mode 100644 frontend/app/views/tile/handles/tile.video.js create mode 100644 frontend/app/views/tile/tile.utils.js (limited to 'frontend/app') diff --git a/frontend/app/utils/index.js b/frontend/app/utils/index.js index 6e5a909..d67d89a 100644 --- a/frontend/app/utils/index.js +++ b/frontend/app/utils/index.js @@ -63,6 +63,16 @@ export const timestamp = (n = 0, fps = 25) => { } return (n % 60) + ':' + s } +export const timestampToSeconds = time_str => { + const time_str_parts = (time_str || "").trim().split(":").map(s => parseFloat(s)) + if (time_str_parts.length === 3) { + return (time_str_parts[0] * 60 + time_str_parts[1]) * 60 + time_str_parts[2] + } + if (time_str_parts.length === 2) { + return time_str_parts[0] * 60 + time_str_parts[1] + } + return time_str_parts[0] +} export const percent = n => (n * 100).toFixed(1) + '%' diff --git a/frontend/app/views/page/components/page.editor.js b/frontend/app/views/page/components/page.editor.js index 7f6182d..2f14ffb 100644 --- a/frontend/app/views/page/components/page.editor.js +++ b/frontend/app/views/page/components/page.editor.js @@ -5,13 +5,13 @@ import { connect } from 'react-redux' import { session } from 'app/session' import actions from 'app/actions' -import * as pageActions from '../page.actions' -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' import { Loader } from 'app/common' import { clamp, dist } from 'app/utils' -import TileHandle from './tile.handle' +import TileHandle from 'app/views/tile/components/tile.handle' const defaultState = { dragging: false, diff --git a/frontend/app/views/page/components/tile.edit.js b/frontend/app/views/page/components/tile.edit.js deleted file mode 100644 index cae9f73..0000000 --- a/frontend/app/views/page/components/tile.edit.js +++ /dev/null @@ -1,86 +0,0 @@ -import React, { Component } from 'react' -// import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -// import { history } from 'app/store' -import actions from 'app/actions' -import * as pageActions from '../../page/page.actions' -import * as tileActions from '../../tile/tile.actions' - -import { Loader } from 'app/common' - -import TileForm from '../components/tile.form' - -class TileEdit extends Component { - state = { - tile: null - } - - componentDidMount() { - this.load() - } - - componentDidUpdate(prevProps) { - if (prevProps.page.editor.currentEditTileId !== this.props.page.editor.currentEditTileId) { - this.load() - } - } - - load() { - const { currentEditTileId } = this.props.page.editor - const { tiles } = this.props.page.show.res - if (!tiles) return - const tile = tiles.filter(tile => tile.id === currentEditTileId)[0] - console.log('edit', currentEditTileId) - this.setState({ tile }) - } - - handleSubmit(data) { - actions.tile.update(data) - .then(response => { - // console.log(response) - if (response.status === 'ok') { - this.props.pageActions.updatePageTile(response.res) - } - }) - } - - handleClose() { - this.props.pageActions.hideEditTileForm() - this.props.tileActions.clearTemporaryTile() - } - - render() { - const { tile } = this.state - if (!tile) { - return ( -
- -
- ) - } - return ( - - ) - } -} - -const mapStateToProps = state => ({ - graph: state.graph, - page: state.page, - tile: state.tile, -}) - -const mapDispatchToProps = dispatch => ({ - pageActions: bindActionCreators({ ...pageActions }, dispatch), - tileActions: bindActionCreators({ ...tileActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(TileEdit) diff --git a/frontend/app/views/page/components/tile.form.js b/frontend/app/views/page/components/tile.form.js deleted file mode 100644 index aefecf1..0000000 --- a/frontend/app/views/page/components/tile.form.js +++ /dev/null @@ -1,848 +0,0 @@ -import React, { Component } from 'react' -import { connect } from 'react-redux' -import { bindActionCreators } from 'redux' -import { Link } from 'react-router-dom' - -import actions from 'app/actions' -import { session } from 'app/session' - -import { - TextInput, NumberInput, ColorInput, Slider, - Select, LabelDescription, TextArea, Checkbox, - SubmitButton, Loader } from 'app/common' -import AudioSelect from 'app/views/audio/components/audio.select' -import { preloadImage, preloadVideo } from 'app/utils' - -import * as tileActions from '../../tile/tile.actions' - -const SELECT_TYPES = [ - "image", "text", "video", "link", "script", -].map(s => ({ name: s, label: s })) - -const ALIGNMENTS = [ - "top_left", "top_center", "top_right", - "center_left", "center_center", "center_right", - "bottom_left", "bottom_center", "bottom_right", -].map(align => ({ - name: align, - label: align === 'center_center' - ? 'center' - : align.replace('_', ' ') - })) - -const REQUIRED_KEYS = { - image: ['url'], - video: ['url'], - text: ['content'], - link: [], - script: [], -} - -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 })) - -const TEXT_FONT_STYLES = [ - 'normal', 'bold', 'italic', 'bold-italic', -].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', }, - { name: 'hand_right', label: 'Right', }, - { name: 'unclickable', label: 'Unclickable', }, -] - -const NO_LINK = 0 -const EXTERNAL_LINK = -1 -const PAGE_LIST_TOP_OPTIONS = [ - { name: NO_LINK, label: 'No link' }, - { name: EXTERNAL_LINK, label: 'External link' }, - { name: -2, 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 - -const newImage = (data) => ({ - settings: { - ...newPosition(), - is_tiled: false, - tile_style: 'tile', - url: "", - external_link_url: "", - cursor: 'hand_up', - }, - type: 'image', - target_page_id: null, - ...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(), - content: "", - font_family: 'sans-serif', - font_size: 16, - font_style: 'normal', - font_color: '#dddddd', - background_color: 'transparent', - width: 0, - height: 0, - units: 'px', - external_link_url: "", - cursor: 'hand_up', - }, - type: 'text', - target_page_id: null, - ...data, -}) - -const newLink = (data) => ({ - settings: { - ...newPosition({ width: 100, height: 100, }), - external_link_url: "", - cursor: 'hand_up', - units: 'px', - }, - type: 'link', - target_page_id: null, - ...data, -}) - -const newScript = (data) => ({ - settings: { - ...newPosition({ width: 100, height: 100, }), - }, - type: 'script', - ...data, -}) - -const newPosition = (data) => ({ - x: 0, y: 0, - width: 0, height: 0, - rotation: 0, scale: 1, - opacity: 1, - units: false, - align: "center_center", - has_audio: false, - audio_on_click_id: 0, - audio_on_hover_id: 0, - navigate_when_audio_finishes: false, - ...data, -}) - -const TYPE_CONSTRUCTORS = { - image: newImage, - video: newVideo, - text: newText, - link: newLink, - script: newScript, -} - -class TileForm extends Component { - state = { - title: "", - submitTitle: "", - errorFields: new Set([]), - modified: false, - pageList: [], - } - - constructor(props){ - super(props) - this.handleChange = this.handleChange.bind(this) - this.handleSelect = this.handleSelect.bind(this) - this.handleSettingsChange = this.handleSettingsChange.bind(this) - this.handleSettingsSelect = this.handleSettingsSelect.bind(this) - this.handleAlignment = this.handleAlignment.bind(this) - this.handleImageChange = this.handleImageChange.bind(this) - this.handleVideoChange = this.handleVideoChange.bind(this) - this.handleSubmit = this.handleSubmit.bind(this) - this.handleDelete = this.handleDelete.bind(this) - } - - componentDidMount() { - const { graph, page, isNew, initialData, sortOrder } = this.props - const title = isNew ? 'new tile' : 'editing tile' - const submitTitle = isNew ? "Create Tile" : "Save Changes" - this.setState({ - title, - submitTitle, - errorFields: new Set([]), - }) - const { pages } = graph.show.res - const linkPages = initialData ? pages.filter(page => page.id !== initialData.id) : pages - let pageList = [ - ...PAGE_LIST_TOP_OPTIONS, - ...linkPages.map(page => ({ name: page.id, label: page.path })) - ] - this.setState({ pageList }) - if (isNew) { - const newTile = newImage({ - id: "new", - graph_id: graph.show.res.id, - page_id: page.show.res.id, - sort_order: sortOrder, - }) - this.props.tileActions.updateTemporaryTile(newTile) - } else { - this.props.tileActions.updateTemporaryTile({ ...initialData }) - } - } - - componentDidUpdate(prevProps) { - if (!this.props.isNew && this.props.initialData !== prevProps.initialData) { - this.handleSubmit() - this.props.tileActions.updateTemporaryTile({ ...this.props.initialData }) - this.setState({ - errorFields: new Set([]), - }) - } - } - - componentWillUnmount() { - // if the item has changed, save before we close the form! - if (!this.props.isNew && this.state.modified) { - this.handleSubmit() - } - } - - handleChange(e) { - const { name, value } = e.target - this.clearErrorField(name) - this.props.tileActions.updateTemporaryTile({ - ...this.props.temporaryTile, - [name]: value, - }) - } - - handleTypeChange(type) { - const { graph, page, temporaryTile } = this.props - let newTile = TYPE_CONSTRUCTORS[type]({ - id: temporaryTile.id, - graph_id: temporaryTile.graph_id, - page_id: temporaryTile.page_id, - }) - newTile.settings.align = temporaryTile.settings.align - this.clearErrorField('type') - this.props.tileActions.updateTemporaryTile(newTile) - } - - handleSettingsChange(e) { - const { name, value } = e.target - this.clearErrorField(name) - this.props.tileActions.updateTemporaryTile({ - ...this.props.temporaryTile, - settings: { - ...this.props.temporaryTile.settings, - [name]: value, - } - }) - } - - handleSelect(name, value) { - this.clearErrorField(name) - if (name === 'type') { - return this.handleTypeChange(value) - } - if (name === 'target_page_id') { - value = parseInt(value) - } - this.props.tileActions.updateTemporaryTile({ - ...this.props.temporaryTile, - [name]: value, - }) - } - - handleSettingsSelect(name, value) { - this.clearErrorField(name) - this.props.tileActions.updateTemporaryTile({ - ...this.props.temporaryTile, - settings: { - ...this.props.temporaryTile.settings, - [name]: value, - } - }) - } - - handleAlignment(name, value) { - this.clearErrorField(name) - this.props.tileActions.updateTemporaryTile({ - ...this.props.temporaryTile, - settings: { - ...this.props.temporaryTile.settings, - [name]: value, - x: 0, y: 0, - } - }) - } - - handleImageChange(e) { - const { name, value } = e.target - this.handleSettingsSelect(name, value) - preloadImage(value).then(img => { - // console.log(img) - this.props.tileActions.updateTemporaryTile({ - ...this.props.temporaryTile, - settings: { - ...this.props.temporaryTile.settings, - [name]: value, - width: img.naturalWidth, - height: img.naturalHeight, - x: 0, y: 0, - } - }) - }) - } - - 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)) { - errorFields.delete(name) - this.setState({ - errorFields, - modified: true, - }) - } else if (!this.state.modified) { - this.setState({ - errorFields, - modified: true, - }) - } - } - - handleSubmit(e) { - if (e) e.preventDefault() - const { isNew, temporaryTile, onSubmit, onClose } = this.props - const requiredSettings = REQUIRED_KEYS[temporaryTile.type] - const validKeys = "id graph_id page_id target_page_id type settings".split(" ") - const validData = validKeys.reduce((a,b) => { a[b] = temporaryTile[b]; return a }, {}) - const errorFields = requiredSettings.filter(key => !validData.settings[key]) - if (errorFields.length) { - console.log('error', errorFields, validData) - if (e) { - this.setState({ errorFields: new Set(errorFields) }) - } - } else { - if (isNew) { - // side effect: set username if we're creating a new tile - // session.set('username', data.username) - delete validData.id - } else { - validData.id = temporaryTile.id - } - this.setState({ modified: false }) - console.log('submit', validData) - onSubmit(validData) - // if submitting after switching elements, don't close the form - if (e && onClose) { - onClose() - } - } - } - - handleDelete() { - const { temporaryTile, isNew, onClose } = this.props - if (confirm('Really delete this tile?')) { - actions.tile.destroy(temporaryTile) - .then(() => { - onClose() - }) - } - } - - render() { - const { temporaryTile, isNew } = this.props - const { title, submitTitle, errorFields } = this.state - if (!temporaryTile || !temporaryTile.settings) return
- return ( -
-

{title}

-
-
- -
- - {this.renderPositionInfo()} - - {temporaryTile.type === 'image' - ? this.renderImageForm() - : temporaryTile.type === 'video' - ? this.renderVideoForm() - : temporaryTile.type === 'text' - ? this.renderTextForm() - : temporaryTile.type === 'link' - ? this.renderLinkForm() - : temporaryTile.type === 'script' - ? this.renderScriptForm() - : ""} - - {this.renderHyperlinkForm()} - {this.renderMiscForm()} - {this.renderAudioForm()} - -
- - {!isNew && - - } -
- {!!errorFields.size && - - } -
-
- ) - } - - renderPositionInfo() { - const { temporaryTile } = this.props - const { x, y, width, height, rotation, scale } = temporaryTile.settings - return ( -
- {parseInt(x)}{', '} - {parseInt(y)}{' '} - {parseInt(width)}{'x'}{parseInt(height)}{' '} - {rotation === 0 || {parseInt(rotation)}{'\u00B0 '}} - {scale === 1 || {'x'}{scale.toFixed(2)}} -
- ) - } - - renderImageForm() { - // const { isNew } = this.props - const { temporaryTile } = this.props - const { errorFields } = this.state - // console.log(temporaryTile.settings) - return ( -
-
- {temporaryTile.settings.url &&
} - -
-
- - {temporaryTile.settings.is_tiled && - - -
-
- - -
-
- ) - } - - renderTextForm() { - const { temporaryTile } = this.props - const { errorFields } = this.state - return ( -
-