summaryrefslogtreecommitdiff
path: root/frontend/app/views/tile/forms
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2021-03-31 17:37:40 +0200
committerJules Laplace <julescarbon@gmail.com>2021-03-31 17:37:40 +0200
commitcda9c115283be8e4e224f6036ba07e5eca243289 (patch)
treed0b150bf108813873c7b59cc9f9bd9c00ea3eba7 /frontend/app/views/tile/forms
parenta6793f922991d326eeb33cf08b245863218eaef7 (diff)
refactor tile forms into own files. add full-width marquee support
Diffstat (limited to 'frontend/app/views/tile/forms')
-rw-r--r--frontend/app/views/tile/forms/index.js29
-rw-r--r--frontend/app/views/tile/forms/tile.constants.js92
-rw-r--r--frontend/app/views/tile/forms/tile.constructors.js113
-rw-r--r--frontend/app/views/tile/forms/tile.form.element.gradient.js80
-rw-r--r--frontend/app/views/tile/forms/tile.form.element.image.js46
-rw-r--r--frontend/app/views/tile/forms/tile.form.element.link.js32
-rw-r--r--frontend/app/views/tile/forms/tile.form.element.script.js22
-rw-r--r--frontend/app/views/tile/forms/tile.form.element.text.js166
-rw-r--r--frontend/app/views/tile/forms/tile.form.element.video.js101
-rw-r--r--frontend/app/views/tile/forms/tile.form.hyperlink.js64
-rw-r--r--frontend/app/views/tile/forms/tile.form.misc.js98
-rw-r--r--frontend/app/views/tile/forms/tile.form.sound.js53
-rw-r--r--frontend/app/views/tile/forms/tile.form.type.js29
13 files changed, 925 insertions, 0 deletions
diff --git a/frontend/app/views/tile/forms/index.js b/frontend/app/views/tile/forms/index.js
new file mode 100644
index 0000000..8225365
--- /dev/null
+++ b/frontend/app/views/tile/forms/index.js
@@ -0,0 +1,29 @@
+import { TILE_CONSTRUCTORS } from './tile.constructors'
+
+import TileImageForm from './tile.form.element.image'
+import TileLinkForm from './tile.form.element.link'
+import TileTextForm from './tile.form.element.text'
+import TileGradientForm from './tile.form.element.gradient'
+import TileScriptForm from './tile.form.element.script'
+import TileVideoForm from './tile.form.element.video'
+
+import TileHyperlinkForm from './tile.form.hyperlink'
+import TileMiscForm from './tile.form.misc'
+import TileSoundForm from './tile.form.sound'
+import TileTypeForm from './tile.form.type'
+
+export {
+ TILE_CONSTRUCTORS,
+
+ TileImageForm,
+ TileLinkForm,
+ TileTextForm,
+ TileGradientForm,
+ TileScriptForm,
+ TileVideoForm,
+
+ TileHyperlinkForm,
+ TileMiscForm,
+ TileSoundForm,
+ TileTypeForm,
+}
diff --git a/frontend/app/views/tile/forms/tile.constants.js b/frontend/app/views/tile/forms/tile.constants.js
new file mode 100644
index 0000000..f2dd0ad
--- /dev/null
+++ b/frontend/app/views/tile/forms/tile.constants.js
@@ -0,0 +1,92 @@
+/*
+import {
+ SELECT_TYPES, ALIGNMENTS,
+ REQUIRED_KEYS,
+ IMAGE_TILE_STYLES, VIDEO_STYLES,
+ TEXT_FONT_FAMILIES, TEXT_FONT_STYLES,
+ CURSORS, UNITS,
+ NO_LINK, EXTERNAL_LINK, OPEN_POPUP_LINK, CLOSE_POPUP_LINK,
+ PAGE_LIST_TOP_OPTIONS,
+ NO_POPUP, POPUP_LIST_TOP_OPTIONS,
+} from 'app/constants'
+*/
+
+export const SELECT_TYPES = [
+ "image", "text", "video", "link", "gradient", "script",
+].map(s => ({ name: s, label: s }))
+
+export 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('_', ' ')
+ }))
+
+export const REQUIRED_KEYS = {
+ image: ['url'],
+ video: ['url'],
+ text: ['content'],
+ link: [],
+ gradient: [],
+ script: [],
+}
+
+export const IMAGE_TILE_STYLES = [
+ 'tile', 'cover', 'contain', 'contain no-repeat'
+].map(style => ({ name: style, label: style }))
+
+export const VIDEO_STYLES = [
+ 'normal', 'cover', 'contain',
+].map(style => ({ name: style, label: style }))
+
+export const TEXT_FONT_FAMILIES = [
+ 'sans-serif', 'serif', 'fantasy', 'monospace', 'cursive',
+].map(style => ({ name: style, label: style }))
+
+export const TEXT_FONT_STYLES = [
+ 'normal', 'bold', 'italic', 'bold-italic',
+].map(style => ({ name: style, label: style }))
+
+export 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', },
+]
+
+export const MARQUEE_DIRECTIONS = [
+ { name: 'left', label: 'Left', },
+ { name: 'right', label: 'Right', },
+]
+
+export const UNITS = [
+ { name: 'px', label: 'pixels' },
+ { name: '%', label: 'percent' },
+ { name: 'video', label: 'video' },
+ { name: 'vmin', label: 'screen min' },
+ { name: 'vmax', label: 'screen max' },
+]
+
+export const NO_LINK = 0
+export const EXTERNAL_LINK = -1
+export const OPEN_POPUP_LINK = -2
+export const CLOSE_POPUP_LINK = -3
+export 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: -99, label: '──────────', disabled: true },
+]
+
+export const NO_POPUP = 0
+export const POPUP_LIST_TOP_OPTIONS = [
+ { name: NO_POPUP, label: 'Select a popup group' },
+ { name: -99, label: '──────────', disabled: true },
+]
diff --git a/frontend/app/views/tile/forms/tile.constructors.js b/frontend/app/views/tile/forms/tile.constructors.js
new file mode 100644
index 0000000..2407274
--- /dev/null
+++ b/frontend/app/views/tile/forms/tile.constructors.js
@@ -0,0 +1,113 @@
+const newImage = (data) => ({
+ settings: {
+ ...newPosition(),
+ is_tiled: false,
+ tile_style: 'tile',
+ url: "",
+ external_link_url: "",
+ cursor: 'hand_up',
+ },
+ type: 'image',
+ target_page_id: 0,
+ ...data,
+})
+
+const newVideo = (data) => ({
+ settings: {
+ ...newPosition(),
+ video_style: 'cover',
+ url: "",
+ external_link_url: "",
+ cursor: 'none',
+ muted: false,
+ loop_style: false,
+ autoadvance: false,
+ loop_section: false,
+ loop_start: 0,
+ loop_end: 0,
+ },
+ type: 'video',
+ target_page_id: 0,
+ ...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: 0,
+ ...data,
+})
+
+const newGradient = (data) => ({
+ settings: {
+ ...newPosition({ width: 100, height: 100 }),
+ from_color: '#ffffff',
+ from_opacity: 1.0,
+ to_color: '#000000',
+ to_opacity: 1.0,
+ angle: 0,
+ stop: 50,
+ units: '%',
+ external_link_url: "",
+ cursor: 'hand_up',
+ },
+ type: 'gradient',
+ target_page_id: 0,
+ ...data,
+})
+
+const newLink = (data) => ({
+ settings: {
+ ...newPosition({ width: 100, height: 100, }),
+ external_link_url: "",
+ cursor: 'hand_up',
+ units: 'px',
+ },
+ type: 'link',
+ target_page_id: 0,
+ ...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,
+})
+
+export const TILE_CONSTRUCTORS = {
+ image: newImage,
+ video: newVideo,
+ text: newText,
+ link: newLink,
+ gradient: newGradient,
+ script: newScript,
+}
diff --git a/frontend/app/views/tile/forms/tile.form.element.gradient.js b/frontend/app/views/tile/forms/tile.form.element.gradient.js
new file mode 100644
index 0000000..3e35cc0
--- /dev/null
+++ b/frontend/app/views/tile/forms/tile.form.element.gradient.js
@@ -0,0 +1,80 @@
+import React from 'react'
+
+import { NumberInput, ColorInput, Slider } from 'app/common'
+
+export default function TileGradientForm({ tile, parent }) {
+ return (
+ <div>
+ <ColorInput
+ title='From'
+ name='from_color'
+ data={tile.settings}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ <Slider
+ title='Opacity'
+ name='from_opacity'
+ value={tile.settings.from_opacity}
+ onChange={parent.handleSettingsSelect}
+ min={0.0}
+ max={1.0}
+ step={0.01}
+ />
+ <ColorInput
+ title='To'
+ name='to_color'
+ data={tile.settings}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ <Slider
+ title='Opacity'
+ name='to_opacity'
+ value={tile.settings.to_opacity}
+ onChange={parent.handleSettingsSelect}
+ min={0.0}
+ max={1.0}
+ step={0.01}
+ />
+ <Slider
+ title='Angle'
+ name='angle'
+ value={tile.settings.angle}
+ onChange={parent.handleSettingsSelect}
+ min={0.0}
+ max={360.0}
+ step={0.1}
+ />
+ <Slider
+ title='Stop'
+ name='stop'
+ value={tile.settings.stop}
+ onChange={parent.handleSettingsSelect}
+ min={0.0}
+ max={100.0}
+ step={0.1}
+ />
+ <div className='row pair'>
+ <NumberInput
+ title="Width"
+ name="width"
+ data={tile.settings}
+ min={0}
+ max={2400}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ <NumberInput
+ title="Height"
+ name="height"
+ data={tile.settings}
+ min={0}
+ max={2400}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ </div>
+ </div>
+ )
+}
diff --git a/frontend/app/views/tile/forms/tile.form.element.image.js b/frontend/app/views/tile/forms/tile.form.element.image.js
new file mode 100644
index 0000000..68ef65a
--- /dev/null
+++ b/frontend/app/views/tile/forms/tile.form.element.image.js
@@ -0,0 +1,46 @@
+import React from 'react'
+
+import {
+ TextInput,
+ Select, Checkbox,
+} from 'app/common'
+
+import { IMAGE_TILE_STYLES } from './tile.constants'
+
+export default function TileImageForm({ tile, errorFields, parent }) {
+ return (
+ <div>
+ <div className='row imageUrl'>
+ {tile.settings.url && <div className='thumb'><img src={tile.settings.url} /></div>}
+ <TextInput
+ title=""
+ placeholder='http://'
+ name="url"
+ required
+ data={tile.settings}
+ error={errorFields.has('url')}
+ onChange={parent.handleImageChange}
+ autoComplete="off"
+ />
+ </div>
+ <div className='row pair'>
+ <Checkbox
+ label="Tiled"
+ name="is_tiled"
+ checked={tile.settings.is_tiled}
+ onChange={parent.handleSettingsSelect}
+ autoComplete="off"
+ />
+ {tile.settings.is_tiled &&
+ <Select
+ name='tile_style'
+ selected={tile.settings.tile_style || 'tile'}
+ options={IMAGE_TILE_STYLES}
+ title=''
+ onChange={parent.handleSettingsSelect}
+ />
+ }
+ </div>
+ </div>
+ )
+} \ No newline at end of file
diff --git a/frontend/app/views/tile/forms/tile.form.element.link.js b/frontend/app/views/tile/forms/tile.form.element.link.js
new file mode 100644
index 0000000..37dacd6
--- /dev/null
+++ b/frontend/app/views/tile/forms/tile.form.element.link.js
@@ -0,0 +1,32 @@
+import React from 'react'
+
+import { NumberInput } from 'app/common'
+
+export default function TileLinkForm({ tile, errorFields, parent }) {
+ return (
+ <div>
+ <div className='row pair'>
+ <NumberInput
+ title="Width"
+ name="width"
+ data={tile.settings}
+ min={0}
+ max={2400}
+ error={errorFields.has('width')}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ <NumberInput
+ title="Height"
+ name="height"
+ data={tile.settings}
+ min={0}
+ max={2400}
+ error={errorFields.has('height')}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ </div>
+ </div>
+ )
+} \ No newline at end of file
diff --git a/frontend/app/views/tile/forms/tile.form.element.script.js b/frontend/app/views/tile/forms/tile.form.element.script.js
new file mode 100644
index 0000000..547e140
--- /dev/null
+++ b/frontend/app/views/tile/forms/tile.form.element.script.js
@@ -0,0 +1,22 @@
+import React from 'react'
+
+import { TextArea } from 'app/common'
+
+export default function TileScriptForm({ tile, errorFields, parent }) {
+ return (
+ <div>
+ <TextArea
+ title=""
+ name="content"
+ required
+ data={tile.settings}
+ error={errorFields.has('content')}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ <div>
+ Scripts will be run on the live site when this page loads.
+ </div>
+ </div>
+ )
+}
diff --git a/frontend/app/views/tile/forms/tile.form.element.text.js b/frontend/app/views/tile/forms/tile.form.element.text.js
new file mode 100644
index 0000000..425a605
--- /dev/null
+++ b/frontend/app/views/tile/forms/tile.form.element.text.js
@@ -0,0 +1,166 @@
+import React from 'react'
+
+import {
+ NumberInput, ColorInput,
+ Select, TextArea, Checkbox
+} from 'app/common'
+
+import { TEXT_FONT_FAMILIES, TEXT_FONT_STYLES, MARQUEE_DIRECTIONS } from './tile.constants'
+
+export default function TileTextForm({ tile, errorFields, parent }) {
+ return (
+ <div>
+ <TextArea
+ title=""
+ name="content"
+ required
+ data={tile.settings}
+ error={errorFields.has('content')}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ <div className='row font'>
+ <Select
+ title="Font"
+ name='font_family'
+ selected={tile.settings.font_family || 'sans-serif'}
+ options={TEXT_FONT_FAMILIES}
+ onChange={parent.handleSettingsSelect}
+ />
+ <NumberInput
+ title=''
+ name='font_size'
+ data={tile.settings}
+ min={1}
+ max={1200}
+ error={errorFields.has('font_size')}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ <Select
+ name='font_style'
+ selected={tile.settings.font_style || 'normal'}
+ options={TEXT_FONT_STYLES}
+ title=''
+ onChange={parent.handleSettingsSelect}
+ />
+ </div>
+ <ColorInput
+ title='Text'
+ name='font_color'
+ data={tile.settings}
+ error={errorFields.has('font_color')}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ <ColorInput
+ title='BG'
+ name='background_color'
+ data={tile.settings}
+ error={errorFields.has('background_color')}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ <div className='row pair'>
+ <NumberInput
+ title="Width"
+ name="width"
+ data={tile.settings}
+ min={0}
+ max={1200}
+ error={errorFields.has('width')}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ <NumberInput
+ title="Height"
+ name="height"
+ data={tile.settings}
+ min={0}
+ max={1200}
+ error={errorFields.has('height')}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ </div>
+
+ <Checkbox
+ label="Marquee"
+ name="is_marquee"
+ className='short'
+ checked={tile.settings.is_marquee}
+ onChange={parent.handleSettingsSelect}
+ autoComplete="off"
+ />
+ {tile.settings.is_marquee && (
+ <div>
+ <div className='row single'>
+ <Select
+ title="Direction"
+ name='marquee_direction'
+ selected={tile.settings.marquee_direction || 'left'}
+ options={MARQUEE_DIRECTIONS}
+ onChange={parent.handleSettingsSelect}
+ />
+ </div>
+ <div className="row single_text">
+ <NumberInput
+ title="Speed"
+ name="marquee_speed"
+ data={tile.settings}
+ step={1}
+ min={0}
+ max={1000}
+ defaultValue={20}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ </div>
+ <div className="row single_text">
+ <NumberInput
+ title="Content width"
+ name="marquee_content_width"
+ data={tile.settings}
+ step={1}
+ min={0}
+ max={5000}
+ defaultValue={200}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ </div>
+ <div className="row single_text">
+ <NumberInput
+ title="Content height"
+ name="marquee_content_height"
+ data={tile.settings}
+ step={1}
+ min={0}
+ max={2000}
+ defaultValue={parseInt(tile.settings.font_size) + 10}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ </div>
+ <Checkbox
+ label="Marquee gradient"
+ name="marquee_gradient"
+ className='short'
+ checked={tile.settings.marquee_gradient}
+ onChange={parent.handleSettingsSelect}
+ autoComplete="off"
+ />
+ {tile.settings.marquee_gradient && (
+ <ColorInput
+ title='Color'
+ name='marquee_gradient_color'
+ data={tile.settings}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ )}
+ </div>
+ )}
+ </div>
+ )
+}
diff --git a/frontend/app/views/tile/forms/tile.form.element.video.js b/frontend/app/views/tile/forms/tile.form.element.video.js
new file mode 100644
index 0000000..7220aa9
--- /dev/null
+++ b/frontend/app/views/tile/forms/tile.form.element.video.js
@@ -0,0 +1,101 @@
+import React from 'react'
+
+import {
+ TextInput, Slider,
+ Select, Checkbox,
+} from 'app/common'
+
+import { VIDEO_STYLES } from './tile.constants'
+
+export default function TileVideoForm({ tile, errorFields, parent }) {
+ return (
+ <div>
+ <div className='row imageUrl'>
+ <TextInput
+ title=""
+ placeholder='http://'
+ name="url"
+ required
+ data={tile.settings}
+ error={errorFields.has('url')}
+ onChange={parent.handleVideoChange}
+ autoComplete="off"
+ />
+ </div>
+ <div className='row pair with_checkbox'>
+ <Select
+ name='video_style'
+ selected={tile.settings.video_style || 'none'}
+ options={VIDEO_STYLES}
+ title=''
+ onChange={parent.handleSettingsSelect}
+ />
+ <Checkbox
+ label="Loop"
+ name="loop"
+ checked={tile.settings.loop}
+ onChange={parent.handleSettingsSelect}
+ autoComplete="off"
+ />
+ </div>
+ <div className='row pair'>
+ <Checkbox
+ label="Muted"
+ name="muted"
+ className='short'
+ checked={tile.settings.muted}
+ onChange={parent.handleSettingsSelect}
+ />
+ <Checkbox
+ label="Autoadvance"
+ name="autoadvance"
+ className='short'
+ checked={tile.settings.autoadvance}
+ onChange={parent.handleSettingsSelect}
+ />
+ </div>
+ {!tile.settings.muted && (
+ <Slider
+ title='Volume'
+ name='volume'
+ value={('volume' in tile.settings) ? tile.settings.volume : 1.0}
+ onChange={parent.handleSettingsSelect}
+ min={0.0}
+ max={1.0}
+ step={0.01}
+ />
+ )}
+ {tile.settings.loop && (
+ <div className='row'>
+ <Checkbox
+ label="Loop section?"
+ className='short'
+ name="loop_section"
+ checked={tile.settings.loop_section}
+ onChange={parent.handleSettingsSelect}
+ />
+ </div>
+ )}
+ {tile.settings.loop && tile.settings.loop_section && (
+ <div className='row pair'>
+ <TextInput
+ title="From"
+ placeholder='0:00'
+ name="loop_start"
+ data={tile.settings}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ <TextInput
+ title="To"
+ placeholder='0:00'
+ name="loop_end"
+ data={tile.settings}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ </div>
+ )}
+ </div>
+ )
+} \ No newline at end of file
diff --git a/frontend/app/views/tile/forms/tile.form.hyperlink.js b/frontend/app/views/tile/forms/tile.form.hyperlink.js
new file mode 100644
index 0000000..c444748
--- /dev/null
+++ b/frontend/app/views/tile/forms/tile.form.hyperlink.js
@@ -0,0 +1,64 @@
+import React from 'react'
+
+import {
+ TextInput,
+ Select,
+} from 'app/common'
+
+import {
+ CURSORS,
+ NO_LINK, EXTERNAL_LINK,
+ NO_POPUP, OPEN_POPUP_LINK, CLOSE_POPUP_LINK
+} from './tile.constants'
+
+export default function TileHyperlinkForm({ tile, pageList, popupList, parent }) {
+ const isExternalLink = tile.target_page_id === EXTERNAL_LINK
+ // const isPopupLink = (
+ // tile.target_page_id === OPEN_POPUP_LINK ||
+ // tile.target_page_id === CLOSE_POPUP_LINK
+ // )
+ return (
+ <div>
+ <div className={'row selects'}>
+ <Select
+ title=''
+ name='target_page_id'
+ selected={tile.target_page_id || NO_LINK}
+ options={pageList}
+ onChange={parent.handleSelect}
+ />
+ <Select
+ title=''
+ name='cursor'
+ selected={tile.settings.cursor}
+ options={CURSORS}
+ defaultOption="Cursor"
+ onChange={parent.handleSettingsSelect}
+ />
+ </div>
+ {isExternalLink && (
+ <div>
+ <TextInput
+ title=""
+ placeholder='http://'
+ name="external_link_url"
+ data={tile.settings}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ </div>
+ )}
+ {(tile.target_page_id === OPEN_POPUP_LINK || tile.target_page_id === CLOSE_POPUP_LINK) && (
+ <div className='row single'>
+ <Select
+ title="Popup"
+ name='target_popup'
+ selected={tile.settings.target_popup || NO_POPUP}
+ options={popupList}
+ onChange={parent.handleSettingsSelect}
+ />
+ </div>
+ )}
+ </div>
+ )
+}
diff --git a/frontend/app/views/tile/forms/tile.form.misc.js b/frontend/app/views/tile/forms/tile.form.misc.js
new file mode 100644
index 0000000..dff5e68
--- /dev/null
+++ b/frontend/app/views/tile/forms/tile.form.misc.js
@@ -0,0 +1,98 @@
+import React from 'react'
+
+import {
+ TextInput,
+ Select, Checkbox, Slider,
+} from 'app/common'
+
+import { UNITS } from './tile.constants'
+
+export default function TileMiscForm({ tile, parent }) {
+ return (
+ <div>
+ <div className='row single'>
+ <Select
+ name='units'
+ selected={tile.settings.units || 'px'}
+ options={UNITS}
+ title='Units'
+ onChange={parent.handleSettingsSelect}
+ />
+ </div>
+ <Slider
+ title='Opacity'
+ name='opacity'
+ value={tile.settings.opacity}
+ onChange={parent.handleSettingsSelect}
+ min={0.0}
+ max={1.0}
+ step={0.01}
+ />
+ <Slider
+ title='Scale'
+ name='scale'
+ value={tile.settings.scale}
+ onChange={parent.handleSettingsSelect}
+ min={0.01}
+ max={10.0}
+ step={0.01}
+ />
+ <Slider
+ title='Rotation'
+ name='rotation'
+ value={tile.settings.rotation}
+ onChange={parent.handleSettingsSelect}
+ min={-180.0}
+ max={180.0}
+ step={1}
+ type='int'
+ />
+ <Checkbox
+ label="Element is a Popup"
+ name="is_popup"
+ className='short'
+ checked={tile.settings.is_popup}
+ onChange={parent.handleSettingsSelect}
+ autoComplete="off"
+ />
+ {tile.settings.is_popup && (
+ <div className='row single_text'>
+ <TextInput
+ title="Popup group"
+ name="popup_group"
+ data={tile.settings}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ </div>
+ )}
+ <Checkbox
+ label="Wait to appear"
+ name="wait_to_appear"
+ className='short'
+ checked={tile.settings.wait_to_appear}
+ onChange={parent.handleSettingsSelect}
+ autoComplete="off"
+ />
+ {tile.settings.wait_to_appear && (
+ <div className='row single_text'>
+ <TextInput
+ title="Appear after"
+ name="appear_after"
+ data={tile.settings}
+ onChange={parent.handleSettingsChange}
+ autoComplete="off"
+ />
+ </div>
+ )}
+ <Checkbox
+ label="Hide on click"
+ name="hide_on_click"
+ className='short'
+ checked={tile.settings.hide_on_click}
+ onChange={parent.handleSettingsSelect}
+ autoComplete="off"
+ />
+ </div>
+ )
+}
diff --git a/frontend/app/views/tile/forms/tile.form.sound.js b/frontend/app/views/tile/forms/tile.form.sound.js
new file mode 100644
index 0000000..0662725
--- /dev/null
+++ b/frontend/app/views/tile/forms/tile.form.sound.js
@@ -0,0 +1,53 @@
+import React from 'react'
+
+import {
+ Checkbox,
+} from 'app/common'
+
+import AudioSelect from 'app/views/audio/components/audio.select'
+
+export default function TileSoundForm({ tile, parent }) {
+ return (
+ <div>
+ <Checkbox
+ label="Sound effects"
+ name="has_audio"
+ className='short'
+ checked={tile.settings.has_audio}
+ onChange={parent.handleSettingsSelect}
+ />
+ {tile.settings.has_audio && (
+ <div>
+ <div className='row single'>
+ <AudioSelect
+ title="On click"
+ name="audio_on_click_id"
+ selected={tile.settings.audio_on_click_id}
+ onChange={parent.handleSettingsSelect}
+ />
+ </div>
+
+ {!!tile.settings.audio_on_click_id && (
+ <Checkbox
+ label="Navigate when audio finishes"
+ name="navigate_when_audio_finishes"
+ className='short'
+ checked={tile.settings.navigate_when_audio_finishes}
+ onChange={parent.handleSettingsSelect}
+ autoComplete="off"
+ />
+ )}
+
+ <div className='row single'>
+ <AudioSelect
+ title="On hover"
+ name="audio_on_hover_id"
+ selected={tile.settings.audio_on_hover_id}
+ onChange={parent.handleSettingsSelect}
+ />
+ </div>
+ </div>
+ )}
+ </div>
+ )
+}
diff --git a/frontend/app/views/tile/forms/tile.form.type.js b/frontend/app/views/tile/forms/tile.form.type.js
new file mode 100644
index 0000000..8759384
--- /dev/null
+++ b/frontend/app/views/tile/forms/tile.form.type.js
@@ -0,0 +1,29 @@
+import React from 'react'
+
+import {
+ Select,
+} from 'app/common'
+
+import { SELECT_TYPES, ALIGNMENTS } from './tile.constants'
+
+export default function TileTypeForm({ tile, parent }) {
+ return (
+ <div className="row selects">
+ <Select
+ name='type'
+ selected={tile.type}
+ options={SELECT_TYPES}
+ title=''
+ onChange={parent.handleSelect}
+ />
+ <Select
+ name='align'
+ selected={tile.settings.align}
+ options={ALIGNMENTS}
+ title=''
+ onChange={parent.handleAlignment}
+ />
+ </div>
+ )
+}
+