diff options
Diffstat (limited to 'animism-align/frontend')
21 files changed, 333 insertions, 43 deletions
diff --git a/animism-align/frontend/app/common/app.css b/animism-align/frontend/app/common/app.css index e6089b2..362f22b 100644 --- a/animism-align/frontend/app/common/app.css +++ b/animism-align/frontend/app/common/app.css @@ -90,12 +90,10 @@ div:first-child > h1:first-child, margin-top: 0; } h2 { - /*color: #eee;*/ font-size: 1.25rem; font-weight: normal; } h3 { - /*color: #eee;*/ margin-top: 0; margin-bottom: 1.25rem; font-size: 1.0rem; diff --git a/animism-align/frontend/app/common/fonts.css b/animism-align/frontend/app/common/fonts.css index 7d231d8..c782885 100644 --- a/animism-align/frontend/app/common/fonts.css +++ b/animism-align/frontend/app/common/fonts.css @@ -36,13 +36,11 @@ font-family: 'Roboto'; src: url('/static/fonts/Roboto-Regular.ttf') format('truetype'); } -/* @font-face { font-family: 'Roboto'; src: url('/static/fonts/Roboto-Italic.ttf') format('truetype'); font-style: italic; } -*/ /* @font-face { font-family: 'Roboto'; diff --git a/animism-align/frontend/app/router.js b/animism-align/frontend/app/router.js index df4d647..73610a6 100644 --- a/animism-align/frontend/app/router.js +++ b/animism-align/frontend/app/router.js @@ -2,6 +2,8 @@ import React, { Component } from 'react' import { ConnectedRouter } from 'connected-react-router' import { Route } from 'react-router' +import actions from 'app/actions' + import AuthGate from 'app/views/auth/auth.gate' import Header from 'app/views/nav/header.component' @@ -11,11 +13,21 @@ import VenueContainer from 'app/views/venue/venue.container' import UploadContainer from 'app/views/upload/upload.container' import UserContainer from 'app/views/user/user.container' import EditorContainer from 'app/views/editor/editor.container' +import DashboardContainer from 'app/views/dashboard/dashboard.container' export default class Router extends Component { + constructor(props) { + super(props) + this.ready = this.ready.bind(this) + } + + ready() { + actions.site.loadSite() + } + render() { return ( - <AuthGate> + <AuthGate onAuthenticate={this.ready}> <ConnectedRouter history={this.props.history}> <div className='app'> <Header /> @@ -25,13 +37,10 @@ export default class Router extends Component { <Route path='/venue/' component={VenueContainer} /> <Route path='/upload/' component={UploadContainer} /> <Route path='/users/' component={UserContainer} /> + <Route path='/' component={DashboardContainer} exact /> </div> </ConnectedRouter> </AuthGate> ) } } - -/* - <Route exact path='/' render={DashboardContainer} /> -*/ diff --git a/animism-align/frontend/app/utils/api.utils.js b/animism-align/frontend/app/utils/api.utils.js new file mode 100644 index 0000000..8210056 --- /dev/null +++ b/animism-align/frontend/app/utils/api.utils.js @@ -0,0 +1,11 @@ +export const groupResponseBy = (response, field) => { + const { lookup, order } = response + const reduction = order.reduce((groups, id) => { + const item = lookup[id] + const value = item[field] + if (!groups[value]) groups[value] = [] + groups[value].push(item) + return groups + }, {}) + return reduction +} diff --git a/animism-align/frontend/app/views/auth/auth.gate.js b/animism-align/frontend/app/views/auth/auth.gate.js index e07316e..5e759a6 100644 --- a/animism-align/frontend/app/views/auth/auth.gate.js +++ b/animism-align/frontend/app/views/auth/auth.gate.js @@ -21,6 +21,7 @@ class AuthGate extends Component { load() { if (!this.props.user.id) return + this.props.onAuthenticate() } render() { diff --git a/animism-align/frontend/app/views/dashboard/dashboard.container.js b/animism-align/frontend/app/views/dashboard/dashboard.container.js new file mode 100644 index 0000000..6befaae --- /dev/null +++ b/animism-align/frontend/app/views/dashboard/dashboard.container.js @@ -0,0 +1,127 @@ +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { Link } from 'react-router-dom' + +import './dashboard.css' + +// import actions from 'app/actions' +import { Loader } from 'app/common' +import { groupResponseBy } from 'app/utils/api.utils' + +class DashboardContainer extends Component { + state = { + ready: false, + episodes: {}, + venues: {}, + } + + constructor(props) { + super(props) + } + + componentDidMount() { + this.build() + } + + componentDidUpdate(prevProps) { + if ( + !this.state.ready || + (this.props.project !== prevProps.project) || + (this.props.episode !== prevProps.episode) || + (this.props.venue !== prevProps.venue) + ) { + this.build() + } + } + + build() { + if (this.props.loading) return + const episodes = groupResponseBy(this.props.episodes, 'project_id') + const venues = groupResponseBy(this.props.venues, 'project_id') + this.setState({ ready: true, episodes, venues }) + } + + render() { + const { loading, projects } = this.props + const { episodes, venues, ready } = this.state + if (loading || !ready) { + return ( + <div className='dashboard'> + <Loader /> + </div> + ) + } + return ( + <div className='dashboard'> + <div className='project-top'> + <div className='row project-heading'> + <div className='title'> + <h1>Projects</h1> + </div> + <div className='links'> + <Link to={`/project/new/`}>+ Add Project</Link> + </div> + </div> + </div> + {projects.order.map((project_id) => { + const project = projects.lookup[project_id] + console.log(project) + return ( + <div className='project-entry' key={project.id}> + <div className='row project-heading'> + <div className='title'> + <h2>{project.title}</h2> + {project.is_live && <span className='published'>{"(published)"}</span>} + </div> + <div className='links'> + <Link to={`/project/${project.id}/new-episode/`}>+ Add Episode</Link> + <Link to={`/project/${project.id}/venues/`}>Venues</Link> + <Link to={`/project/${project.id}/edit/`}>Settings</Link> + </div> + </div> + {(episodes[project.id] || []).map(episode => ( + <div className='episode-entry' key={episode.id}> + <div className='row'> + <div className='title'> + <Link to={`/editor/${episode.id}/viewer/`}> + {'Episode #' + episode.episode_number} + </Link> + {episode.is_live && <span className='published'>{"(published)"}</span>} + </div> + <div className='links'> + <Link to={`/editor/${episode.id}/viewer/`}>Viewer</Link> + <Link to={`/editor/${episode.id}/timeline/`}>Timeline</Link> + <Link to={`/editor/${episode.id}/transcript/`}>Transcript</Link> + <Link to={`/episode/${episode.id}/edit/`}>Settings</Link> + </div> + </div> + <div className='row'> + <div className='title'> + <i>{episode.title}</i> + </div> + </div> + </div> + ))} + </div> + ) + })} + </div> + ) + } +} + +const mapStateToProps = state => ({ + loading: ( + !state.project.index.lookup || + !state.episode.index.lookup || + !state.venue.index.lookup || + state.project.index.loading || + state.episode.index.loading || + state.venue.index.loading + ), + projects: state.project.index, + episodes: state.episode.index, + venues: state.venue.index, +}) + +export default connect(mapStateToProps)(DashboardContainer) diff --git a/animism-align/frontend/app/views/dashboard/dashboard.css b/animism-align/frontend/app/views/dashboard/dashboard.css new file mode 100644 index 0000000..34bcb66 --- /dev/null +++ b/animism-align/frontend/app/views/dashboard/dashboard.css @@ -0,0 +1,52 @@ +.dashboard { + padding: 1.5rem; +} + +.project-top { + padding: 1rem; +} +.project-top h1 { + margin: 0; +} + +.dashboard .project-entry { + padding: 1rem; + margin: 0 0 1rem 0; + background: #111; +} + +.dashboard .episode-entry { + margin-bottom: 0.5rem; +} +.dashboard .project-entry .row.project-heading { + margin-bottom: 1rem; +} +.dashboard h2 { + display: inline-block; + margin: 0; +} +.dashboard .title { + width: 25rem; + color: #fff; + text-overflow: ellipsis; + white-space: pre; + overflow: hidden; +} +.dashboard .row { + margin-bottom: 0.25rem; + align-items: center; +} +.dashboard .links a { + margin-right: 0.5rem; + text-decoration: none; +} +.dashboard .links a:hover { + text-decoration: underline; +} +.dashboard .published { + color: #888; + margin-left: 0.5rem; +} +.dashboard i { + color: #bbb; +}
\ No newline at end of file diff --git a/animism-align/frontend/app/views/episode/components/episode.form.js b/animism-align/frontend/app/views/episode/components/episode.form.js index 63d55e7..c60d3b2 100644 --- a/animism-align/frontend/app/views/episode/components/episode.form.js +++ b/animism-align/frontend/app/views/episode/components/episode.form.js @@ -20,6 +20,7 @@ const newEpisode = () => ({ export default class EpisodeForm extends Component { state = { + project_id: 0, title: "", submitTitle: "", data: { ...newEpisode() }, @@ -37,8 +38,10 @@ export default class EpisodeForm extends Component { } componentDidMount() { - const { data, isNew } = this.props - const title = isNew ? 'New episode' : 'Editing ' + data.title + const { data, isNew, project } = this.props + const title = isNew + ? 'Add episode to project ' + project.title + : 'Editing Episode ' + data.episode_number + ": " + data.title const submitTitle = isNew ? "Add Episode" : "Save Changes" this.setState({ title, @@ -112,8 +115,8 @@ export default class EpisodeForm extends Component { } const { isNew, onSubmit } = this.props const { data } = this.state - const requiredKeys = "episode_number release_date".split(" ") - const validKeys = "title episode_number release_date is_live settings".split(" ") + const requiredKeys = "project_id episode_number release_date".split(" ") + const validKeys = "project_id title episode_number release_date is_live settings".split(" ") const validData = validKeys.reduce((a,b) => { a[b] = data[b]; return a }, {}) if (!data.title) { data.title = "TBD" @@ -141,10 +144,8 @@ export default class EpisodeForm extends Component { <div className='form'> <h1>{title}</h1> <form onSubmit={this.handleSubmit}> - <SubmitButton - title={submitTitle} - onClick={this.handleSubmit} - /> + <h2>Episode information</h2> + <TextInput title="Title" name="title" @@ -176,6 +177,37 @@ export default class EpisodeForm extends Component { onChange={this.handleSelect} /> + <h2>Build configuration</h2> + + <TextInput + title="HTML title" + name="page_title" + placeholder="HTML title tag" + data={data.settings} + onChange={this.handleSettingsChangeEvent} + autoComplete="off" + /> + + <TextArea + title="Page description" + name="page_desc" + placeholder="Social media / search engine snippet" + data={data.settings} + onChange={this.handleSettingsChangeEvent} + autoComplete="off" + /> + + <TextInput + title="Build folder" + name="url_path" + placeholder="Folder to build into, e.g. episode1" + data={data.settings} + onChange={this.handleSettingsChangeEvent} + autoComplete="off" + /> + + <h2>Credits</h2> + <TextArea title="Artists" name="artists" diff --git a/animism-align/frontend/app/views/episode/components/episode.menu.js b/animism-align/frontend/app/views/episode/components/episode.menu.js index 445084b..4b75ec1 100644 --- a/animism-align/frontend/app/views/episode/components/episode.menu.js +++ b/animism-align/frontend/app/views/episode/components/episode.menu.js @@ -28,7 +28,7 @@ const EpisodeIndexMenu = () => ([ ]) const EpisodeShowMenu = connect(mapStateToProps)((props) => ([ - <MenuButton key='back' name="back" href="/episode/" />, + <MenuButton key='back' name="back" href="/" />, <MenuButton key='edit' name="edit" href={"/episode/" + props.match.params.id + "/edit/"} />, <MenuButton key='delete' name="delete" onClick={() => { const { res: episode } = props.episode.show @@ -41,11 +41,11 @@ const EpisodeShowMenu = connect(mapStateToProps)((props) => ([ ])) const EpisodeNewMenu = (props) => ([ - <MenuButton key='back' name="back" href="/episode/" />, + <MenuButton key='back' name="back" href="/" />, ]) const EpisodeEditMenu = connect(mapStateToProps)((props) => ([ - <MenuButton key='back' name="back" href="/episode/" />, + <MenuButton key='back' name="back" href="/" />, <MenuButton key='delete' name="delete" onClick={() => { const { res: episode } = props.episode.show if (confirm("Really delete this episode?")) { diff --git a/animism-align/frontend/app/views/episode/containers/episode.edit.js b/animism-align/frontend/app/views/episode/containers/episode.edit.js index 4d7e270..8ba16ae 100644 --- a/animism-align/frontend/app/views/episode/containers/episode.edit.js +++ b/animism-align/frontend/app/views/episode/containers/episode.edit.js @@ -10,9 +10,25 @@ import EpisodeForm from '../components/episode.form' import EpisodeMenu from '../components/episode.menu' class EpisodeEdit extends Component { + state = { + project: {}, + } componentDidMount() { + this.ready() + } + componentDidUpdate(prevProps) { + if (this.props.project.lookup !== prevProps.project.lookup) { + this.ready() + } + } + ready() { + if (!this.props.project.lookup) return console.log(this.props.match.params.id) actions.episode.show(this.props.match.params.id) + .then(episode => { + const project = this.props.project.lookup[episode.project_id] + this.setState({ project }) + }) } handleSubmit(data) { @@ -20,7 +36,7 @@ class EpisodeEdit extends Component { .then(response => { // response console.log(response) - history.push('/episode/') + history.push('/') }) } @@ -38,6 +54,7 @@ class EpisodeEdit extends Component { <EpisodeMenu episodeActions={this.props.episodeActions} /> <EpisodeForm data={show.res} + project={this.state.project} onSubmit={this.handleSubmit.bind(this)} /> </div> @@ -47,6 +64,7 @@ class EpisodeEdit extends Component { const mapStateToProps = state => ({ episode: state.episode, + project: state.episode.index, }) export default connect(mapStateToProps)(EpisodeEdit) diff --git a/animism-align/frontend/app/views/episode/containers/episode.new.js b/animism-align/frontend/app/views/episode/containers/episode.new.js index 42e9837..2ff279b 100644 --- a/animism-align/frontend/app/views/episode/containers/episode.new.js +++ b/animism-align/frontend/app/views/episode/containers/episode.new.js @@ -12,10 +12,26 @@ class EpisodeNew extends Component { state = { loading: true, initialData: {}, + project: {}, } - componentDidMount() { - this.setState({ loading: false }) + this.ready() + } + componentDidUpdate(prevProps) { + if (this.props.project.lookup !== prevProps.project.lookup) { + this.ready() + } + } + ready() { + if (!this.props.project.lookup) return + const { project_id } = this.props.match.params + const project = this.props.project.lookup[project_id] + this.setState({ + loading: false, + initialData: { project_id }, + project, + }) + console.log(this.props.match.params.project_id) } handleSubmit(data) { @@ -24,7 +40,7 @@ class EpisodeNew extends Component { .then(res => { console.log(res) if (res.res && res.res.id) { - history.push('/episode/') + history.push('/') } }) .catch(err => { @@ -44,6 +60,7 @@ class EpisodeNew extends Component { <EpisodeForm isNew data={this.state.initialData} + project={this.state.project} onSubmit={this.handleSubmit.bind(this)} /> </div> @@ -53,6 +70,7 @@ class EpisodeNew extends Component { const mapStateToProps = state => ({ episode: state.episode, + project: state.project.index, }) const mapDispatchToProps = dispatch => ({ diff --git a/animism-align/frontend/app/views/episode/episode.container.js b/animism-align/frontend/app/views/episode/episode.container.js index 62363ec..9cd70f8 100644 --- a/animism-align/frontend/app/views/episode/episode.container.js +++ b/animism-align/frontend/app/views/episode/episode.container.js @@ -13,7 +13,6 @@ class Container extends Component { return ( <div className='episodeContainer'> <Route exact path='/episode/:id/edit/' component={EpisodeEdit} /> - <Route exact path='/episode/new/' component={EpisodeNew} /> <Route exact path='/episode/' component={EpisodeIndex} /> </div> ) @@ -24,3 +23,8 @@ const mapStateToProps = state => ({ }) export default connect(mapStateToProps)(Container) + +/* + // episodes are now added via the project + <Route exact path='/episode/new/' component={EpisodeNew} /> +*/
\ No newline at end of file diff --git a/animism-align/frontend/app/views/episode/episode.css b/animism-align/frontend/app/views/episode/episode.css index e0747ab..96806da 100644 --- a/animism-align/frontend/app/views/episode/episode.css +++ b/animism-align/frontend/app/views/episode/episode.css @@ -7,3 +7,9 @@ .episode-index { margin-top: 1rem; } + +.episodeContainer .formContainer h2 { + color: #888; + margin-top: 1.5rem; + font-style: italic; +} diff --git a/animism-align/frontend/app/views/nav/header.component.js b/animism-align/frontend/app/views/nav/header.component.js index bc04629..c90e592 100644 --- a/animism-align/frontend/app/views/nav/header.component.js +++ b/animism-align/frontend/app/views/nav/header.component.js @@ -14,30 +14,35 @@ function Header(props) { return null } if (props.router.location.pathname.match("/editor")) { - const { episode_id } = this.props.match.params + const episode_id = props.router.location.pathname.split('/')[1] return ( <header> - <PlayButton playing={props.playing} /> <div> - <Link to={`/`}>{'<'}</Link> + <PlayButton playing={props.playing} /> + <Link to="/">Home</Link> + </div> + <div> <Link to={`/editor/${episode_id}/timeline/`}>Timeline</Link> <Link to={`/editor/${episode_id}/transcript/`}>Transcript</Link> <Link to={`/editor/${episode_id}/media/`}>Media</Link> <Link to={`/editor/${episode_id}/viewer/`}>Viewer</Link> - <Link to="/episode/">Episodes</Link> - <Link to="/venue/">Venues</Link> </div> </header> ) } return ( <header> - <PlayButton playing={props.playing} /> <div> - <Link to="/project/">Projects</Link> - <Link to="/episode/">Episodes</Link> - <Link to="/venue/">Venues</Link> - {props.currentUser.is_admin && <Link to="/users">Users</Link>} + <PlayButton playing={props.playing} /> + {props.router.location.pathname !== '/' && ( + <Link to="/">Home</Link> + )} + </div> + <div> + <span className='salutation'> + Hi {props.currentUser.username} + </span> + {props.currentUser.is_admin && <Link to="/users/">Users</Link>} <a href="#" onClick={actions.auth.logout}> Logout </a> diff --git a/animism-align/frontend/app/views/nav/nav.css b/animism-align/frontend/app/views/nav/nav.css index 485ace2..0c9a992 100644 --- a/animism-align/frontend/app/views/nav/nav.css +++ b/animism-align/frontend/app/views/nav/nav.css @@ -25,7 +25,7 @@ header > div:first-child { display: flex; justify-content: flex-start; align-items: center; - padding-left: 1.5rem; + padding-left: 0.5rem; } header > div:last-child { padding-right: 1.5rem; @@ -70,4 +70,9 @@ header a.navbar-brand { header .username { cursor: pointer; +} +header .salutation { + color: #888; + font-style: italic; + margin: 0 1rem; }
\ No newline at end of file diff --git a/animism-align/frontend/app/views/project/components/project.form.js b/animism-align/frontend/app/views/project/components/project.form.js index f4d1749..0393d81 100644 --- a/animism-align/frontend/app/views/project/components/project.form.js +++ b/animism-align/frontend/app/views/project/components/project.form.js @@ -110,7 +110,7 @@ export default class ProjectForm extends Component { } const { isNew, onSubmit } = this.props const { data } = this.state - const requiredKeys = "title is_live".split(" ") + const requiredKeys = "title".split(" ") const validKeys = "title is_live settings".split(" ") const validData = validKeys.reduce((a,b) => { a[b] = data[b]; return a }, {}) if (!data.title) { @@ -173,21 +173,24 @@ export default class ProjectForm extends Component { title="Base URL" name="base_href" data={data.settings} + placeholder="https://animism.e-flux.com" onChange={this.handleSettingsChangeEvent} autoComplete="off" /> <TextInput title="FTP URL" name="ftp_url" + placeholder="ftp://..." data={data.settings} onChange={this.handleSettingsChangeEvent} autoComplete="off" /> <TextInput - title="FTP Base Path" + title="FTP Local Path" name="ftp_base_path" data={data.settings} + placeholder="./data_store/exports/animism" onChange={this.handleSettingsChangeEvent} autoComplete="off" /> diff --git a/animism-align/frontend/app/views/project/components/project.menu.js b/animism-align/frontend/app/views/project/components/project.menu.js index a29a451..8e3abea 100644 --- a/animism-align/frontend/app/views/project/components/project.menu.js +++ b/animism-align/frontend/app/views/project/components/project.menu.js @@ -28,7 +28,7 @@ const ProjectIndexMenu = () => ([ ]) const ProjectShowMenu = connect(mapStateToProps)((props) => ([ - <MenuButton key='back' name="back" href="/project/" />, + <MenuButton key='back' name="back" href="/" />, <MenuButton key='edit' name="edit" href={"/project/" + props.match.params.id + "/edit/"} />, <MenuButton key='delete' name="delete" onClick={() => { const { res: project } = props.project.show @@ -41,11 +41,11 @@ const ProjectShowMenu = connect(mapStateToProps)((props) => ([ ])) const ProjectNewMenu = (props) => ([ - <MenuButton key='back' name="back" href="/project/" />, + <MenuButton key='back' name="back" href="/" />, ]) const ProjectEditMenu = connect(mapStateToProps)((props) => ([ - <MenuButton key='back' name="back" href="/project/" />, + <MenuButton key='back' name="back" href="/" />, <MenuButton key='delete' name="delete" onClick={() => { const { res: project } = props.project.show if (confirm("Really delete this project?")) { diff --git a/animism-align/frontend/app/views/project/containers/project.edit.js b/animism-align/frontend/app/views/project/containers/project.edit.js index dbb384d..b0b7bef 100644 --- a/animism-align/frontend/app/views/project/containers/project.edit.js +++ b/animism-align/frontend/app/views/project/containers/project.edit.js @@ -20,7 +20,7 @@ class ProjectEdit extends Component { .then(response => { // response console.log(response) - history.push('/project/') + history.push('/') }) } diff --git a/animism-align/frontend/app/views/project/containers/project.new.js b/animism-align/frontend/app/views/project/containers/project.new.js index c8a2152..149ee67 100644 --- a/animism-align/frontend/app/views/project/containers/project.new.js +++ b/animism-align/frontend/app/views/project/containers/project.new.js @@ -24,7 +24,7 @@ class ProjectNew extends Component { .then(res => { console.log(res) if (res.res && res.res.id) { - history.push('/project/') + history.push('/') } }) .catch(err => { diff --git a/animism-align/frontend/app/views/project/project.container.js b/animism-align/frontend/app/views/project/project.container.js index 0fba8c9..a3701e2 100644 --- a/animism-align/frontend/app/views/project/project.container.js +++ b/animism-align/frontend/app/views/project/project.container.js @@ -3,6 +3,7 @@ import { Route } from 'react-router-dom' import './project.css' +import EpisodeNew from 'app/views/episode/containers/episode.new' import ProjectIndex from './containers/project.index' import ProjectNew from './containers/project.new' import ProjectEdit from './containers/project.edit' @@ -11,6 +12,7 @@ export default class Container extends Component { render() { return ( <div className='projectContainer'> + <Route exact path='/project/:project_id/new-episode/' component={EpisodeNew} /> <Route exact path='/project/:id/edit/' component={ProjectEdit} /> <Route exact path='/project/new/' component={ProjectNew} /> <Route exact path='/project/' component={ProjectIndex} /> diff --git a/animism-align/frontend/app/views/site/site.actions.js b/animism-align/frontend/app/views/site/site.actions.js index 1c51b53..f5ce861 100644 --- a/animism-align/frontend/app/views/site/site.actions.js +++ b/animism-align/frontend/app/views/site/site.actions.js @@ -11,6 +11,7 @@ export const loadSite = () => dispatch => { actions.episode.index(), actions.venue.index(), ]).then(() => { + console.log('Site ready') }).catch(err => { console.error(err) }) |
