diff options
Diffstat (limited to 'animism-align/frontend/app/views/upload')
9 files changed, 520 insertions, 0 deletions
diff --git a/animism-align/frontend/app/views/upload/components/upload.form.js b/animism-align/frontend/app/views/upload/components/upload.form.js new file mode 100644 index 0000000..e35bfaa --- /dev/null +++ b/animism-align/frontend/app/views/upload/components/upload.form.js @@ -0,0 +1,16 @@ +import React, { Component } from 'react' +import { Link } from 'react-router-dom' + +import { MenuButton, FileInput } from 'app/common' + +export default class UploadForm extends Component { + render() { + return ( + <div className='uploadForm'> + <MenuButton name="upload" label={false}> + <FileInput onChange={this.props.uploadActions.upload} /> + </MenuButton> + </div> + ) + } +} diff --git a/animism-align/frontend/app/views/upload/components/upload.index.js b/animism-align/frontend/app/views/upload/components/upload.index.js new file mode 100644 index 0000000..d60231d --- /dev/null +++ b/animism-align/frontend/app/views/upload/components/upload.index.js @@ -0,0 +1,98 @@ +import React, { Component } from 'react' +import { Link } from 'react-router-dom' + +import { uploadUri, formatDateTime } from 'app/utils' +import { MenuButton, SmallMenuButton, Loader } from 'app/common' +import actions from 'app/actions' + +import UploadIndexOptions from './upload.indexOptions' +import UploadMenu from './upload.menu' + +// const { result, collectionLookup } = this.props + +export default class UploadIndex extends Component { + componentDidMount() { + this.fetch(false) + } + + componentDidUpdate(prevProps) { + if (this.props.upload.options.sort !== prevProps.upload.options.sort) { + this.fetch(false) + } + } + + fetch(load_more) { + const { options, index } = this.props.upload + const { order: index_order } = index + const [ sort, order ] = options.sort.split('-') + actions.upload.index({ + sort, order, limit: 50, offset: load_more ? index_order.length : 0, + }, load_more) + } + + render() { + const { uploadActions } = this.props + const { options } = this.props.upload + const { loading, lookup, order } = this.props.upload.index + if (loading) { + return ( + <section> + <UploadIndexOptions /> + <div className="row"> + {order && !!order.length && + <div className={'results ' + options.thumbnailSize}> + {order.map(id => <UploadItem key={id} data={lookup[id]} />)} + </div> + } + </div> + <Loader /> + </section> + ) + } + if (!lookup || !order.length) { + return ( + <section> + <UploadIndexOptions /> + <div className="row"> + <UploadMenu uploadActions={uploadActions} /> + <p className='gray'> + {"No uploads"} + </p> + </div> + </section> + ) + } + return ( + <section> + <UploadIndexOptions /> + <div className="row"> + <UploadMenu uploadActions={uploadActions} /> + <div className={'results ' + options.thumbnailSize}> + {order.map(id => <UploadItem key={id} data={lookup[id]} />)} + </div> + </div> + {order.length >= 50 && <button className='loadMore' onClick={() => this.fetch(true)}>Load More</button>} + </section> + ) + } +} + +const UploadItem = ({ data }) => { + // console.log(data) + // const imageUri = uploadUri(data) + return ( + <div className='cell'> + <div className='img'> + <Link to={"/upload/" + data.id + "/show/"}> + <img src={data.url} alt={"Uploaded image"} /> + </Link> + </div> + <div className='meta center'> + <div> + {formatDateTime(data.created_at)} + </div> + </div> + </div> + ) +} + diff --git a/animism-align/frontend/app/views/upload/components/upload.indexOptions.js b/animism-align/frontend/app/views/upload/components/upload.indexOptions.js new file mode 100644 index 0000000..75fdffc --- /dev/null +++ b/animism-align/frontend/app/views/upload/components/upload.indexOptions.js @@ -0,0 +1,61 @@ +import React, { Component } from 'react' +import { Link } from 'react-router-dom' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import actions from 'app/actions' + +import { Select, Checkbox } from 'app/common' + +const thumbnailOptions = [ + { name: 'th', label: 'Thumbnails', }, + { name: 'sm', label: 'Small', }, + { name: 'md', label: 'Medium', }, + { name: 'lg', label: 'Large', }, + { name: 'orig', label: 'Original', }, +] + +const sortOptions = [ + { name: 'id-asc', label: 'Most recent' }, + { name: 'id-desc', label: 'Oldest first' }, + { name: 'username-asc', label: 'Username (A-Z)' }, + { name: 'username-desc', label: 'Username (Z-A)' }, + // { name: '-asc', label: '' }, + // { name: '-desc', label: '' }, + // { name: '-asc', label: '' }, + // { name: '-desc', label: '' }, + // { name: '-asc', label: '' }, + // { name: '-desc', label: '' }, +] + +class IndexOptions extends Component { + render() { + const { options } = this.props + return ( + <div className='row menubar'> + <div /> + <Select + name={'sort'} + options={sortOptions} + selected={options.sort} + onChange={actions.upload.updateOption} + /> + <Select + name={'thumbnailSize'} + options={thumbnailOptions} + selected={options.thumbnailSize} + onChange={actions.upload.updateOption} + /> + </div> + ) + } +} + +const mapStateToProps = state => ({ + options: state.upload.options, +}) + +const mapDispatchToProps = dispatch => ({ +}) + +export default connect(mapStateToProps, mapDispatchToProps)(IndexOptions) diff --git a/animism-align/frontend/app/views/upload/components/upload.menu.js b/animism-align/frontend/app/views/upload/components/upload.menu.js new file mode 100644 index 0000000..485d06f --- /dev/null +++ b/animism-align/frontend/app/views/upload/components/upload.menu.js @@ -0,0 +1,18 @@ +import React, { Component } from 'react' +import { Link } from 'react-router-dom' + +import { MenuButton, FileInput } from 'app/common' + +import actions from 'app/actions' + +export default class UploadMenu extends Component { + render() { + return ( + <div className='menuButtons'> + <MenuButton name="upload"> + <FileInput onChange={this.props.uploadActions.upload} /> + </MenuButton> + </div> + ) + } +} diff --git a/animism-align/frontend/app/views/upload/components/upload.show.js b/animism-align/frontend/app/views/upload/components/upload.show.js new file mode 100644 index 0000000..0498cac --- /dev/null +++ b/animism-align/frontend/app/views/upload/components/upload.show.js @@ -0,0 +1,69 @@ +import React, { Component } from 'react' +import { Link } from 'react-router-dom' +import { connect } from 'react-redux' + +import actions from 'app/actions' +import { formatDate, formatTime, formatAge, uploadUri } from 'app/utils' +import { history } from 'app/store' +import { Loader, MenuButton } from 'app/common' + +class UploadShow extends Component { + componentDidMount() { + actions.upload.show(this.props.match.params.id) + } + + componentDidUpdate(prevProps) { + if (prevProps.match.params.id !== this.props.match.params.id) { + actions.upload.show(this.props.match.params.id) + } + } + + handleDestroy() { + const { res: data } = this.props.upload.show + if (confirm("Really delete this upload?")) { + actions.upload.destroy(data).then(() => { + history.push('/upload/') + }) + } + } + + render() { + const { show, destroy } = this.props.upload + if (show.loading || destroy.loading) { + return <Loader /> + } + if (!show.loading && !show.res || show.not_found) { + return <div className='gray'>Upload {this.props.match.params.id} not found</div> + } + const { res: data } = show + return ( + <section className="row uploadShow"> + <div className="menuButtons"> + <MenuButton name="delete" onClick={this.handleDestroy.bind(this)} /> + </div> + <div> + <img src={data.url} /> + <div className='byline'> + {'Uploaded by '} + {data.username} + {' on '} + {formatDate(data.created_at)} + {' at '} + {formatTime(data.created_at)} + {'. '} + </div> + </div> + </section> + ) + } +} + +const mapStateToProps = state => ({ + upload: state.upload, +}) + +const mapDispatchToProps = dispatch => ({ + // searchActions: bindActionCreators({ ...searchActions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(UploadShow) diff --git a/animism-align/frontend/app/views/upload/upload.actions.js b/animism-align/frontend/app/views/upload/upload.actions.js new file mode 100644 index 0000000..73c8e16 --- /dev/null +++ b/animism-align/frontend/app/views/upload/upload.actions.js @@ -0,0 +1,18 @@ +import * as types from 'app/types' +import { store, history } from 'app/store' +import { api, post, pad, preloadImage } from 'app/utils' +import actions from 'app/actions' +import { session } from 'app/session' + +export const upload = (image, tag='upload') => dispatch => { + const formData = { + image, + tag, + username: 'animism', // session('username'), + } + // console.log(formData) + return actions.upload.upload(formData).then(data => { + // console.log(data.res) + return data.res + }) +} diff --git a/animism-align/frontend/app/views/upload/upload.container.js b/animism-align/frontend/app/views/upload/upload.container.js new file mode 100644 index 0000000..7753711 --- /dev/null +++ b/animism-align/frontend/app/views/upload/upload.container.js @@ -0,0 +1,36 @@ +import React, { Component } from 'react' +import { Route, Link } from 'react-router-dom' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import './upload.css' + +import actions from 'app/actions' +import * as uploadActions from './upload.actions' + +import UploadMenu from './components/upload.menu' +import UploadIndex from './components/upload.index' +import UploadShow from './components/upload.show' + +class Container extends Component { + render() { + return ( + <div className='row upload'> + <div> + <Route exact path='/upload/:id/show/' component={UploadShow} /> + <UploadIndex {...this.props} /> + </div> + </div> + ) + } +} + +const mapStateToProps = state => ({ + upload: state.upload, +}) + +const mapDispatchToProps = dispatch => ({ + uploadActions: bindActionCreators({ ...uploadActions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(Container) diff --git a/animism-align/frontend/app/views/upload/upload.css b/animism-align/frontend/app/views/upload/upload.css new file mode 100644 index 0000000..28ce33d --- /dev/null +++ b/animism-align/frontend/app/views/upload/upload.css @@ -0,0 +1,182 @@ +.uploadShow { + margin-top: 1rem; +} +.uploadShow img { + max-width: 30rem; + max-height: 20rem; +} +.upload { + height: 100%; +} +.upload > div:last-child { + flex: 1; + width: 100%; +} + +/* results */ + +.resultsContainer { +} +.results { + display: flex; + flex-flow: row wrap; + justify-content: flex-start; + align-items: flex-end; +} +.results .result { + display: inline-block; + margin-right: 1.125rem; + margin-bottom: 1.125rem; +} +.result > a { + display: block; +} +.result > a > div { + position: relative; +} +.result img { + max-width: 100%; + display: block; + cursor: pointer; +} +.result > a { + border: 2px solid transparent; +} +.results .active img, +.desktop .result > a:hover { + border-color: #11f; +} +.results.th .result { + max-width: 10rem; +} +.results.sm .result { + max-width: 20rem; +} +.results.md .result { + max-width: 40rem; +} +.results.lg .result { + max-width: 80rem; +} +.results.orig .result { + max-width: 100%; +} +.results.th img { + max-height: 120px; +} +.results.sm img { + max-height: 160px; +} +.results.md img { + max-height: 480px; +} +.results.lg img { + max-height: 960px; +} +.results.orig img { + max-width: none; + max-height: none; +} +.results .img { + width: 100%; +} +.results .img > a { + display: inline-block; + position: relative; +} +.results .meta { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: baseline; + font-size: 0.75rem; + color: #888; + padding: 0.125rem; +} +.results .meta.center, +.row.center { + align-items: center; +} +.results .meta > div { + overflow: hidden; + max-width: 75%; + text-overflow: ellipsis; + white-space: nowrap; +} +.results .meta > span { + padding-right: 0.125rem; +} +.results .meta .buttons { +} +.score.good { + color: #11f; + font-weight: bold; +} +.score.ok { + color: #44d; + font-weight: bold; +} +.score.poor { + color: #66b; +} +.score { + color: #888; +} +.resultGroup { + display: flex; + flex-direction: row; + flex-wrap: wrap; + position: relative; + border: 2px solid #888; + max-width: 20rem; + margin-right: 1.5rem; + margin-bottom: 1.5rem; + background: #fff; + box-shadow: 0 2px 4px #888; +} +.resultGroup .sha256 { + position: absolute; + background: white; + padding: 0.25rem 0.25rem 0rem 0.25rem; + left: 0.25rem; + top: -0.75rem; + font-size: 0.75rem; + color: #333; + text-transform: uppercase; + max-width: 95%; + overflow: hidden; + text-overflow: ellipsis; +} +.results.grouped { + background: #fff; +} +.results.grouped.sm, +.results.grouped.md, +.results.grouped.lg, +.results.grouped.orig { + flex-flow: column nowrap; +} +.results.th .resultGroup { + max-width: 33.5rem; +} +.results.sm .resultGroup { + max-width: 56rem; +} +.results.md .resultGroup { + max-width: 79rem; +} +.results.lg .resultGroup { + max-width: 100%; +} +.results.orig .resultGroup { + max-width: 100%; +} +.results .resultGroup .result { + margin: 0.5rem; +} +.loadMore { + width: 100%; +} +.loadMore button { + width: 100%; +} diff --git a/animism-align/frontend/app/views/upload/upload.reducer.js b/animism-align/frontend/app/views/upload/upload.reducer.js new file mode 100644 index 0000000..818be88 --- /dev/null +++ b/animism-align/frontend/app/views/upload/upload.reducer.js @@ -0,0 +1,22 @@ +import * as types from 'app/types' +import { session, getDefault, getDefaultInt } from 'app/session' + +import { crudState, crudReducer } from 'app/api/crud.reducer' + +const initialState = crudState('upload', { + options: { + sort: getDefault('upload.sort', 'id-desc'), + thumbnailSize: getDefault('upload.thumbnailSize', 'small'), + } +}) + +const reducer = crudReducer('upload') + +export default function uploadReducer(state = initialState, action) { + // console.log(action.type, action) + state = reducer(state, action) + switch (action.type) { + default: + return state + } +} |
