diff options
| -rw-r--r-- | frontend/app/views/graph/graph.container.js | 8 | ||||
| -rw-r--r-- | frontend/app/views/tile/components/tile.form.js | 10 | ||||
| -rw-r--r-- | frontend/app/views/tile/forms/tile.form.element.text.js | 6 | ||||
| -rw-r--r-- | frontend/app/views/tile/handles/tile.video.js | 10 | ||||
| -rw-r--r-- | frontend/site/actions.js | 6 | ||||
| -rw-r--r-- | frontend/site/projects/museum/app.js | 21 | ||||
| -rw-r--r-- | frontend/site/projects/museum/constants.js | 17 | ||||
| -rw-r--r-- | frontend/site/projects/museum/museum.actions.js | 21 | ||||
| -rw-r--r-- | frontend/site/projects/museum/views/home.css | 12 | ||||
| -rw-r--r-- | frontend/site/projects/museum/views/home.js | 24 | ||||
| -rw-r--r-- | frontend/site/projects/museum/views/nav.css | 71 | ||||
| -rw-r--r-- | frontend/site/projects/museum/views/nav.overlay.js | 74 | ||||
| -rw-r--r-- | frontend/site/site/site.actions.js | 9 | ||||
| -rw-r--r-- | frontend/site/site/site.reducer.js | 7 | ||||
| -rw-r--r-- | frontend/site/types.js | 7 | ||||
| -rw-r--r-- | frontend/site/viewer/viewer.container.js | 20 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | webpack.config.site.dev.js | 4 | ||||
| -rw-r--r-- | yarn.lock | 5 |
19 files changed, 271 insertions, 62 deletions
diff --git a/frontend/app/views/graph/graph.container.js b/frontend/app/views/graph/graph.container.js index 57ea205..7d2baf9 100644 --- a/frontend/app/views/graph/graph.container.js +++ b/frontend/app/views/graph/graph.container.js @@ -1,6 +1,4 @@ import React, { Component } from 'react' -import { Route } from 'react-router-dom' -import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import './graph.css' @@ -79,8 +77,4 @@ const mapStateToProps = state => ({ graph: state.graph, }) -const mapDispatchToProps = dispatch => ({ - // uploadActions: bindActionCreators({ ...uploadActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(GraphContainer) +export default connect(mapStateToProps)(GraphContainer) diff --git a/frontend/app/views/tile/components/tile.form.js b/frontend/app/views/tile/components/tile.form.js index c258c5e..54b736e 100644 --- a/frontend/app/views/tile/components/tile.form.js +++ b/frontend/app/views/tile/components/tile.form.js @@ -55,6 +55,7 @@ class TileForm extends Component { pageList: [], popupList: [], cursors: {}, + fontFamilies: [], } constructor(props){ @@ -95,7 +96,12 @@ class TileForm extends Component { a[b.id] = b return a }, {}) - this.setState({ pageList, popupList, cursors }) + let customFonts = (this.props.graph.show.res.settings.custom_fonts || "").split("\n").map(name => ({ + name, + label: name.split(",")[0].replace(/\"/g, "") + })) + let fontFamilies = TEXT_FONT_FAMILIES.concat(customFonts) + this.setState({ pageList, popupList, cursors, fontFamilies }) if (isNew) { const newTile = TILE_CONSTRUCTORS.image({ id: "new", @@ -317,7 +323,7 @@ class TileForm extends Component { : temporaryTile.type === 'video' ? <TileVideoForm tile={temporaryTile} errorFields={errorFields} parent={this} /> : temporaryTile.type === 'text' - ? <TileTextForm tile={temporaryTile} errorFields={errorFields} parent={this} /> + ? <TileTextForm tile={temporaryTile} errorFields={errorFields} parent={this} fontFamilies={this.state.fontFamilies} /> : temporaryTile.type === 'link' ? <TileLinkForm tile={temporaryTile} errorFields={errorFields} parent={this} /> : temporaryTile.type === 'gradient' diff --git a/frontend/app/views/tile/forms/tile.form.element.text.js b/frontend/app/views/tile/forms/tile.form.element.text.js index 425a605..7fd625a 100644 --- a/frontend/app/views/tile/forms/tile.form.element.text.js +++ b/frontend/app/views/tile/forms/tile.form.element.text.js @@ -5,9 +5,9 @@ import { Select, TextArea, Checkbox } from 'app/common' -import { TEXT_FONT_FAMILIES, TEXT_FONT_STYLES, MARQUEE_DIRECTIONS } from './tile.constants' +import { TEXT_FONT_STYLES, MARQUEE_DIRECTIONS } from './tile.constants' -export default function TileTextForm({ tile, errorFields, parent }) { +export default function TileTextForm({ tile, errorFields, parent, fontFamilies }) { return ( <div> <TextArea @@ -24,7 +24,7 @@ export default function TileTextForm({ tile, errorFields, parent }) { title="Font" name='font_family' selected={tile.settings.font_family || 'sans-serif'} - options={TEXT_FONT_FAMILIES} + options={fontFamilies} onChange={parent.handleSettingsSelect} /> <NumberInput diff --git a/frontend/app/views/tile/handles/tile.video.js b/frontend/app/views/tile/handles/tile.video.js index dbcc856..760aabf 100644 --- a/frontend/app/views/tile/handles/tile.video.js +++ b/frontend/app/views/tile/handles/tile.video.js @@ -4,6 +4,10 @@ import { generateTransform, generateVideoStyle, pickCursor } from 'app/views/til import { timestampToSeconds } from 'app/utils' export default class TileVideo extends Component { + state = { + ready: false, + } + constructor(props) { super(props) this.videoRef = React.createRef() @@ -13,6 +17,9 @@ export default class TileVideo extends Component { componentDidMount() { this.bind() + setTimeout(() => { + this.setState({ ready: true }) + }, 1) } componentDidUpdate() { @@ -74,7 +81,8 @@ export default class TileVideo extends Component { // console.log(tile) const style = { transform: generateTransform(tile, box, bounds, videoBounds), - opacity: tile.settings.opacity, + opacity: !this.state.ready ? 0 : tile.settings.opacity, + transition: "opacity 0.2s", } let className = ['tile', tile.type].join(' ') diff --git a/frontend/site/actions.js b/frontend/site/actions.js index dea882c..2e3d7a6 100644 --- a/frontend/site/actions.js +++ b/frontend/site/actions.js @@ -3,7 +3,7 @@ import { bindActionCreators } from 'redux' import * as siteActions from 'site/site/site.actions' -import { store } from 'site/store' +import { dispatch } from 'site/store' export default // Object.keys(crudActions) @@ -12,8 +12,8 @@ export default [ ['site', siteActions], ] //) - .map(p => [p[0], bindActionCreators(p[1], store.dispatch)]) + .map(p => [p[0], bindActionCreators(p[1], dispatch)]) .concat([ // ['socket', socketActions], ]) - .reduce((a,b) => (a[b[0]] = b[1])&&a,{})
\ No newline at end of file + .reduce((a,b) => (a[b[0]] = b[1])&&a,{}) diff --git a/frontend/site/projects/museum/app.js b/frontend/site/projects/museum/app.js index 12dab41..44070b7 100644 --- a/frontend/site/projects/museum/app.js +++ b/frontend/site/projects/museum/app.js @@ -1,27 +1,30 @@ import React, { Component } from 'react' import { ConnectedRouter } from 'connected-react-router' import { Route } from 'react-router' +import { connect } from 'react-redux' import ViewerContainer from 'site/viewer/viewer.container' import Home from './views/home' import NavOverlay from './views/nav.overlay' -import actions from 'site/actions' -export default class App extends Component { +import { loadMuseum } from './museum.actions' + +class App extends Component { componentDidMount() { - const path_partz = window.location.pathname.split('/') - const graph_name = path_partz[1] - actions.site.loadSite(graph_name) + loadMuseum() } render() { + if (!this.props.ready) { + return <div /> + } return ( <ConnectedRouter history={this.props.history}> <div className='app'> <Route path={'/last-museum/:page_name'} component={ViewerContainer} exact /> <Route path={'/last-museum/start'} component={Home} exact /> <Route path={'/last-museum/:page_name'} component={NavOverlay} exact /> - <Route exact key='root' path='/last-museum/' render={() => { + <Route path='/last-museum/' exact render={() => { setTimeout(() => this.props.history.push('/last-museum/start'), 10) return null }} /> @@ -30,3 +33,9 @@ export default class App extends Component { ) } } + +const mapStateToProps = state => ({ + ready: state.site.ready, +}) + +export default connect(mapStateToProps)(App) diff --git a/frontend/site/projects/museum/constants.js b/frontend/site/projects/museum/constants.js index 0bb98ba..3676f2b 100644 --- a/frontend/site/projects/museum/constants.js +++ b/frontend/site/projects/museum/constants.js @@ -2,26 +2,35 @@ export const ARTISTS = { nora: { name: "Nora Al-Badri", location: "C-Base, Berlin, Germany", + start: "nora-1", }, leite: { name: "Juliana Cerqueria Leite", location: "Santa Ifigênia, São Paolo, Brazil", + start: "leite-chapter-1", }, foreshew: { name: "Nicole Foreshew", location: "Australia", + start: "foreshew-1", }, - stankievich: { - name: "Charles Stankievich", + stankievech: { + name: "Charles Stankievech", location: "Canada", + start: "stankievech-1", }, nilthamrong: { name: "Jakrawal Nilthamrong", location: "Thailand", + start: "nilthamrong-home", }, - opoko: { - name: "Zohra Opoko", + opoku: { + name: "Zohra Opoku", location: "Mortuary (Unfinished), Accra, Ghana", + start: "opoku-1-hail-to-you", } } +export const ARTIST_ORDER = [ + "nora", "foreshew", "leite", "opoku", "nilthamrong", "stankievech", +]
\ No newline at end of file diff --git a/frontend/site/projects/museum/museum.actions.js b/frontend/site/projects/museum/museum.actions.js new file mode 100644 index 0000000..7f9867a --- /dev/null +++ b/frontend/site/projects/museum/museum.actions.js @@ -0,0 +1,21 @@ +import * as types from 'site/types' +import FontFaceObserver from 'fontfaceobserver' +import actions from 'site/actions' +import { dispatch } from 'site/store' + +export const loadMuseum = () => { + Promise.all([ + loadFonts, + actions.site.loadGraph('last-museum'), + ]) + .then(() => dispatch({ type: types.site.load_site })) +} + +const loadFonts = () => { + const fonts = [ + new FontFaceObserver('Gruk'), + new FontFaceObserver('Gruk Wide', { style: 'italic' }), + new FontFaceObserver('Gruk Medium'), + ] + return Promise.all(fonts.map(font => font.load())) +} diff --git a/frontend/site/projects/museum/views/home.css b/frontend/site/projects/museum/views/home.css index 597c66b..188840b 100644 --- a/frontend/site/projects/museum/views/home.css +++ b/frontend/site/projects/museum/views/home.css @@ -113,3 +113,15 @@ html { .home.open .home-byline { opacity: 0; } + +.curtain { + position: fixed; + top: 0; left: 0; + width: 100%; height: 100%; + background: #111111; + transition: opacity 0.2s; + opacity: 1.0; +} +.curtain.hidden { + opacity: 0; +}
\ No newline at end of file diff --git a/frontend/site/projects/museum/views/home.js b/frontend/site/projects/museum/views/home.js index e3c44d5..1520c11 100644 --- a/frontend/site/projects/museum/views/home.js +++ b/frontend/site/projects/museum/views/home.js @@ -1,22 +1,27 @@ import React, { Component } from 'react' +import { connect } from 'react-redux' import actions from 'site/actions' import "./home.css" -export default class Home extends Component { - state = { - open: false, - } - +class Home extends Component { constructor(props) { super(props) this.handleClick = this.handleClick.bind(this) + this.state = { + open: false, + hidden: this.props.interactive, + showCurtain: true, + } } componentDidMount() { const roadblock = document.querySelector('.roadblock') if (roadblock) roadblock.style.display = "none" + setTimeout(() => { + this.setState({ showCurtain: false }) + }, 100) } handleClick(e) { @@ -44,13 +49,20 @@ export default class Home extends Component { <div className="home-artists artists-right"> CHARLES STANKIEVECH<br /> JAKRAWAL NILTHAMRONG<br /> - ZOHRA OPOKO + ZOHRA OPOKU </div> <div className="home-byline byline-bottom">CURATED BY NADIM SAMMAN</div> <div className="home-message"> Lorem ipsum dolor sit amet, pro ea errem nonumes, gubergren deterruisset sit eu. Quo nostrud definitiones ex, sea dicant accommodare ei, te vix habeo minim voluptatum. </div> + <div className={this.state.showCurtain ? "curtain" : "curtain hidden"} /> </div> ) } } + +const mapStateToProps = state => ({ + interactive: state.site.interactive, +}) + +export default connect(mapStateToProps)(Home) diff --git a/frontend/site/projects/museum/views/nav.css b/frontend/site/projects/museum/views/nav.css index 2b6d75a..a44758f 100644 --- a/frontend/site/projects/museum/views/nav.css +++ b/frontend/site/projects/museum/views/nav.css @@ -1,13 +1,80 @@ +.museum-nav .home-link { + position: fixed; + top: 0; left: 0; + padding: 1rem; + color: rgba(255, 121, 13, 1.0); + font-family: 'Helvetica', sans-serif; + font-size: 1.2rem; + cursor: pointer; +} + +/* footer */ + .footer { position: fixed; bottom: 0; width: 100%; - background: linear-gradient(90deg, rgba(255, 121, 13, 0.0), rgba(255, 121, 13, 1.0)); + height: 5rem; + color: black; +} +.footer-gradient { + position: absolute; + bottom: 0rem; + width: 100%; + height: 5rem; + background: linear-gradient(180deg, rgba(255, 121, 13, 0.0), rgba(255, 121, 13, 1.0) 70%); + transform: translateY(2rem); + transition: transform 0.15s; +} +.footer .arrow { + position: absolute; + bottom: 0; + height: 3rem; + width: 4rem; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + transition: transform 0.15s; + transform: translateY(3rem); +} +.footer .arrow-left { + left: 0; +} +.footer .arrow-right { + right: 0; +} +.footer .artist-desc { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + white-space: nowrap; + text-align: center; + padding-bottom: 0.5rem; + transition: transform 0.15s; + transform: translateY(3rem); } .footer .artist-name { font-family: "Druk Wide"; font-style: italic; + text-transform: uppercase; + margin-right: 1rem; + font-size: 28px; + line-height: 28px; + cursor: default; } .footer .artist-location { font-family: "Helvetica", sans-serif; -}
\ No newline at end of file + font-size: 21px; + line-height: 21px; + cursor: default; +} + +.footer.with-artist:hover .arrow, +.footer.with-artist:hover .artist-desc { + transform: translateY(0); +} +.footer.with-artist:hover .footer-gradient { + transform: translateY(0); +} diff --git a/frontend/site/projects/museum/views/nav.overlay.js b/frontend/site/projects/museum/views/nav.overlay.js index 36af7db..ed6a0f5 100644 --- a/frontend/site/projects/museum/views/nav.overlay.js +++ b/frontend/site/projects/museum/views/nav.overlay.js @@ -4,7 +4,9 @@ import actions from 'site/actions' import "./nav.css" -import { ARTISTS } from "site/projects/museum/constants" +import { ARTISTS, ARTIST_ORDER } from "site/projects/museum/constants" +import { ArrowLeft, ArrowRight } from "site/projects/museum/icons" +import { history } from "site/store" export default class NavOverlay extends Component { state = { @@ -16,58 +18,98 @@ export default class NavOverlay extends Component { constructor(props) { super(props) + this.previousArtist = this.previousArtist.bind(this) + this.nextArtist = this.nextArtist.bind(this) + this.goHome = this.goHome.bind(this) } componentDidMount() { this.load() } - componentDidUpdate() { + + componentDidUpdate(prevProps) { + // console.log(this.props.location.pathname, prevProps.location.pathname) if (this.props.location.pathname !== prevProps.location.pathname) { this.load() } } load() { - const { pathname } = this.props.location - const pathPartz = pathname.split("-") + const { page_name } = this.props.match.params + const pathPartz = page_name.split("-") const pathkey = pathPartz[0] + // console.log(pathkey) if (pathkey === 'start') { this.setState({ showFooter: true, showArtist: false, - artist: {} + currentArtist: null, + artist: {}, }) } else if (pathkey in ARTISTS) { this.setState({ + showHome: true, showFooter: true, showArtist: true, - artist: ARTISTS[pathkey] + currentArtist: pathkey, + artist: ARTISTS[pathkey], }) } else { this.setState({ showFooter: false, showArtist: false, - artist: {} + currentArtist: null, + artist: {}, }) } } + previousArtist() { + this.go(-1) + } + + nextArtist() { + this.go(1) + } + + go(step) { + if (!this.state.currentArtist) return + const index = ARTIST_ORDER.indexOf(this.state.currentArtist) + const nextIndex = (index + step + ARTIST_ORDER.length) % ARTIST_ORDER.length + const currentArtist = ARTIST_ORDER[nextIndex] + const artist = ARTISTS[currentArtist] + this.setState({ currentArtist, artist }) + history.push(`/last-museum/${artist.start}`) + } + + goHome() { + history.push(`/last-museum/`) + } + render() { - const { showArtist, showHome, artist } = this.state + const { showArtist, showHome, showFooter, artist } = this.state return ( <div className="museum-nav"> + {showHome && ( + <div className="home-link" onClick={this.goHome}> + Home + </div> + )} {showFooter && ( showArtist ? ( - <div className="footer"> - <span className="arrow-left">{ArrowLeft}</span> - <span className="artist-name">{artist.name}</span> - <span className="artist-location">{artist.location}</span> - <span className="arrow-right">{ArrowRight}</span> + <div className="footer with-artist"> + <div className="footer-gradient" /> + <div className="artist-desc"> + <span className="artist-name">{artist.name}</span> + <span className="artist-location">{artist.location}</span> + </div> + <div className="arrow arrow-left" onClick={this.previousArtist}>{ArrowLeft}</div> + <div className="arrow arrow-right" onClick={this.nextArtist}>{ArrowRight}</div> </div> - } : ( - <div className="footer" /> + ) : ( + <div className="footer no-artist" /> ) - ))} + )} </div> ) } diff --git a/frontend/site/site/site.actions.js b/frontend/site/site/site.actions.js index aab68e8..f0eeebc 100644 --- a/frontend/site/site/site.actions.js +++ b/frontend/site/site/site.actions.js @@ -6,7 +6,14 @@ export const setSiteTitle = title => dispatch => { dispatch({ type: types.site.set_site_title, payload: title }) } -export const loadSite = graph_name => dispatch => ( +export const loadSite = graph_name => dispatch => { + loadGraph(graph_name) + .then(() => { + dispatch({ type: types.site.load_site }) + }) +} + +export const loadGraph = graph_name => dispatch => ( api(dispatch, types.site, 'site', '/' + graph_name + '/index.json?t=' + (Date.now() / 3600000)) ) diff --git a/frontend/site/site/site.reducer.js b/frontend/site/site/site.reducer.js index 8b31786..8b7175c 100644 --- a/frontend/site/site/site.reducer.js +++ b/frontend/site/site/site.reducer.js @@ -2,6 +2,7 @@ import * as types from 'site/types' const initialState = { siteTitle: 'swimmer', + ready: false, interactive: false, graph: { loading: true, @@ -25,6 +26,12 @@ export default function siteReducer(state = initialState, action) { cursors: buildCursors(action.data.graph), } + case types.site.load_site: + return { + ...state, + ready: true, + } + case types.site.interact: return { ...state, diff --git a/frontend/site/types.js b/frontend/site/types.js index 4ab897f..a83febe 100644 --- a/frontend/site/types.js +++ b/frontend/site/types.js @@ -1,11 +1,8 @@ import { with_type } from 'app/api/crud.types' export const site = with_type('site', [ - 'set_site_title', 'loading', 'loaded', 'error', 'interact' -]) - -export const system = with_type('system', [ - 'load_site', + 'set_site_title', 'loading', 'loaded', 'error', + 'load_site', 'interact', ]) export const init = '@@INIT' diff --git a/frontend/site/viewer/viewer.container.js b/frontend/site/viewer/viewer.container.js index 09f04c3..4135586 100644 --- a/frontend/site/viewer/viewer.container.js +++ b/frontend/site/viewer/viewer.container.js @@ -32,13 +32,23 @@ class ViewerContainer extends Component { window.addEventListener('resize', this.handleResize) } + componentDidMount() { + if (this.props.graph && this.props.interactive) { + this.load() + } + } + componentDidUpdate(prevProps) { // console.log('didUpdate', this.props.graph !== prevProps.graph, this.props.location.pathname !== prevProps.location.pathname) - // console.log(this.props.location.pathname, prevProps.location.pathname, this.props.interactive) - if (this.props.graph !== prevProps.graph || this.props.location.pathname !== prevProps.location.pathname) { + // console.log(this.props.location.pathname, prevProps.location.pathname, this.props.interactive, prevProps.interactive) + if ( + this.props.graph !== prevProps.graph || + this.props.location.pathname !== prevProps.location.pathname || + this.props.interactive !== prevProps.interactive + ) { this.load() } - if (this.props.interactive && (this.props.interactive !== prevProps.interactive)) { + else if (this.props.interactive && (this.props.interactive !== prevProps.interactive)) { this.setState({ roadblock: false }) this.props.audio.player.playPage(this.state.page) this.resetTimer(this.state.page) @@ -52,6 +62,7 @@ class ViewerContainer extends Component { load() { const { page_name } = this.props.match.params + // console.log("load", page_name) const { pages, home_page, path: graph_name } = this.props.graph const page_path = ["", graph_name, page_name].join('/') // if ((!page_path in pages) || page_name === 'index.html') { @@ -60,7 +71,8 @@ class ViewerContainer extends Component { // } // console.log(this.props.interactive) const page = pages[page_path] || pages[home_page] - console.log("show page", page.id) + // console.log(pages, page) + // console.log("show page", page.id) if (!this.props.interactive && hasAutoplay(page)) { this.setState({ page, popups: {}, hidden: {}, roadblock: true, unloaded: false }) } else { diff --git a/package.json b/package.json index c9765a8..c2bd6d4 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "exifreader": "^3.8.0", "fetch-jsonp": "^1.1.3", "file-saver": "^2.0.2", + "fontfaceobserver": "^2.1.0", "history": "^4.10.1", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", diff --git a/webpack.config.site.dev.js b/webpack.config.site.dev.js index a3e06fd..da71040 100644 --- a/webpack.config.site.dev.js +++ b/webpack.config.site.dev.js @@ -17,6 +17,7 @@ module.exports = function (env) { path: path.resolve(__dirname, "static/js/dist"), filename: "bundle.js", }, + devtool: "cheap-module-eval-source-map", plugins: [ new webpack.NormalModuleReplacementPlugin( /(.*)APP_TARGET(\.*)/, @@ -58,7 +59,6 @@ module.exports = function (env) { // "react-dom": "preact/compat", }, }, - devtool: "cheap-module-source-map", module: { rules: [ { @@ -71,7 +71,7 @@ module.exports = function (env) { exclude: /(node_modules|bower_components|build)/, loader: "babel-loader", options: { - presets: ["@babel/preset-react"], + presets: ["@babel/preset-env"], plugins: ["@babel/plugin-transform-runtime"], }, }, @@ -3420,6 +3420,11 @@ flux-standard-action@^0.6.1: dependencies: lodash.isplainobject "^3.2.0" +fontfaceobserver@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fontfaceobserver/-/fontfaceobserver-2.1.0.tgz#e2705d293e2c585a6531c2a722905657317a2991" + integrity sha512-ReOsO2F66jUa0jmv2nlM/s1MiutJx/srhAe2+TE8dJCMi02ZZOcCTxTCQFr3Yet+uODUtnr4Mewg+tNQ+4V1Ng== + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" |
