initial page
flip 4 middle cards by clicking the flip button
manu-flip is added to the DOM class of flipped elements
flip back 2 middle cards by click the flip buttons again
manu-flip class is removed from the DOM classlist
//pages/albums
import React, { Component } from 'react';
import '../App.css';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import FlippyCard from '../partials/flippyCard';
import { Row, Col } from 'reactstrap'
import { getAlbums } from '../redux/actions/getAlbums';
import { getUsers } from '../redux/actions/getUser'
class Albums extends Component {
constructor(props) {
super(props);
this.state = {
AlbumAuthorData: [],
AlbumAuthorIntegrated: false,
};
}
componentDidMount() {
//start fetching database once logged in
if (this.props.loggedin) {
if (!this.props.gotUsers) {
this.props.dispatch(getUsers(this.props.token));
}
if (!this.props.gotAlbums) {
this.props.dispatch(getAlbums(this.props.token));
}
}
//check every 1 second to see if albums are fetched. If so, start integrating Albums and Authors
const waitGetAlbums = setInterval(() => {
if (this.props.gotAlbums && this.props.gotUsers) {
let newAlbums = []
this.props.albums.map((album, index) => {
const newAlbum = {
'id': album.id,
'album_title': album.album_title,
'artist': album.artist,
'genre': album.genre,
'date_posted': album.date_posted,
'author': this.props.users.filter(author => author.id == album.author)[0].username,
'album_logo': album.album_logo
}
newAlbums.push(newAlbum);
});
this.setState({ AlbumAuthorData: newAlbums, AlbumAuthorIntegrated: true });
clearInterval(waitGetAlbums);
}
}, 1000)
}
render() {
if (!this.props.loggedin) {
return <Redirect to='/login' />
}
const cards = [];
if (this.state.AlbumAuthorIntegrated) {
this.state.AlbumAuthorData.map((item, index) => {
const card = <Col key={index}>
<FlippyCard data={item}></FlippyCard>
</Col>
cards.push(card);
})
}
return (
<div style={{ marginTop: '20px' }}>
{this.props.error}
{
this.state.AlbumAuthorIntegrated ?
<Row>
{cards}
</Row>
: <p>integrating authors and albums...</p>
}
</div>
);
}
}
export default connect(
(store) => {
return {
token: store.login.token,
loggedin: store.login.fetched,
error: store.albums.error,
albums: store.albums.albums,
gotAlbums: store.albums.fetched,
users: store.users.users,
gotUsers: store.users.fetched,
};
}
)(Albums);
-----------------------------------------
//partials/flippyCard
import React, { Component } from 'react';
import '../App.css';
import { Redirect } from 'react-router-dom';
import { connect } from 'react-redux';
import FlippyCard from '../partials/flippyCard';
import { Row, Col } from 'reactstrap'
import { getAlbums } from '../redux/actions/getAlbums';
import { getUsers } from '../redux/actions/getUser'
class Albums extends Component {
constructor(props) {
super(props);
this.state = {
AlbumAuthorData: [],
AlbumAuthorIntegrated: false,
};
}
componentDidMount() {
//start fetching database once logged in
if (this.props.loggedin) {
if (!this.props.gotUsers) {
this.props.dispatch(getUsers(this.props.token));
}
if (!this.props.gotAlbums) {
this.props.dispatch(getAlbums(this.props.token));
}
}
//check every 1 second to see if albums are fetched. If so, start integrating Albums and Authors
const waitGetAlbums = setInterval(() => {
if (this.props.gotAlbums && this.props.gotUsers) {
let newAlbums = []
this.props.albums.map((album, index) => {
const newAlbum = {
'id': album.id,
'album_title': album.album_title,
'artist': album.artist,
'genre': album.genre,
'date_posted': album.date_posted,
'author': this.props.users.filter(author => author.id == album.author)[0].username,
'album_logo': album.album_logo
}
newAlbums.push(newAlbum);
});
this.setState({ AlbumAuthorData: newAlbums, AlbumAuthorIntegrated: true });
clearInterval(waitGetAlbums);
}
}, 1000)
}
render() {
if (!this.props.loggedin) {
return <Redirect to='/login' />
}
const cards = [];
if (this.state.AlbumAuthorIntegrated) {
this.state.AlbumAuthorData.map((item, index) => {
const card = <Col key={index}>
<FlippyCard data={item}></FlippyCard>
</Col>
cards.push(card);
})
}
return (
<div style={{ marginTop: '20px' }}>
{this.props.error}
{
this.state.AlbumAuthorIntegrated ?
<Row>
{cards}
</Row>
: <p>integrating authors and albums...</p>
}
</div>
);
}
}
export default connect(
(store) => {
return {
token: store.login.token,
loggedin: store.login.fetched,
error: store.albums.error,
albums: store.albums.albums,
gotAlbums: store.albums.fetched,
users: store.users.users,
gotUsers: store.users.fetched,
};
}
)(Albums);
-----------------------------------------
//partials/flippyCard
import '../App.css';
import { FaPenNib } from "react-icons/fa";
import { Button } from 'reactstrap';
import { MdEdit, MdDelete, MdFlip } from "react-icons/md";
export default class FlippyCard extends Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
flipClick = () => {
this.myRef.current.classList.toggle('manu-flip');
console.log('Album: ', this.props.data.album_title, ' DOM class: ', this.myRef.current.classList);
}
render() {
const imgSrc = 'http://127.0.0.1:8000' + this.props.data.album_logo;
//onTouchStart is for touch screen device, tigger flip click when element is touched.
return (
<div className="flip-container" ref={this.myRef} onTouchStart={()=>this.flipClick()} style={{ margin: '10px' }}>
<div className="flipper">
<div className="front" style={{ backgroundColor: 'white' }}>
<img src={imgSrc}
style={{ height: '150px', width: '100%' }}></img>
<div style={{ height: '60px' }}>
<h6 style={{ textAlign: 'center', marginTop: '10px' }}>{this.props.data.album_title}</h6>
</div>
<div style={{ backgroundColor: '#F4F6F6', height: '30px', paddingLeft: '5px' }}>
<FaPenNib /> <span>{this.props.data.author}</span>
</div>
<MdFlip className='flip-button' onClick={() => this.flipClick()} />
</div>
<div className="back" style={{ backgroundColor: 'white' }}>
<div style={{ textAlign: 'center', height: '220px', paddingTop: '10px' }}>
<p>Artist: {this.props.data.artist}</p>
<p>Genre: {this.props.data.genre}</p>
<div style={{ display: 'flex', justifyContent: 'space-around', marginLeft: '5px', marginRight: '5px' }}>
<Button color='primary' size='sm'>View Detail</Button>
<Button outline size='sm'><MdEdit /></Button>
<Button outline size='sm'><MdDelete /></Button>
</div>
</div>
<div style={{ backgroundColor: '#F4F6F6', height: '30px', paddingLeft: '5px' }}>
<FaPenNib /> <span>{this.props.data.date_posted.split('.')[0].replace('T', ' ')}</span>
</div>
<MdFlip className='flip-button' onClick={() => this.flipClick()} />
</div>
</div>
</div>
);
}
}
-----------------------------------------
//app.css
/* entire container, keeps perspective */
.flip-container {
perspective: 1000px;
}
/* flip the pane when hovered, or flip button clicked */
.flip-container:hover .flipper, .flip-container.manu-flip .flipper {
transform: rotateY(180deg);
}
.flip-container, .front, .back {
width: 200px;
height: 250px;
}
/* flip speed goes here */
.flipper {
transition: 0.6s;
transform-style: preserve-3d;
position: relative;
}
/* hide back of pane during swap */
.front, .back {
backface-visibility: hidden;
position: absolute;
top: 0;
left: 0;
}
/* front pane, placed above back */
.front {
z-index: 2;
/* for firefox 31 */
transform: rotateY(0deg);
}
/* back, initially hidden pane */
.back {
transform: rotateY(180deg);
}
.flip-button{
position: absolute;
right: 0;
bottom: 30px;
font-size: 30px;
cursor: pointer;
transition: 1s;
perspective: 1000px;
}
.flip-button:hover{
transform: rotateY(180deg);
}
reference:
https://davidwalsh.name/css-flip
https://alligator.io/js/classlist/
https://reactjs.org/docs/refs-and-the-dom.html
http://chuanshuoge2.blogspot.com/2019/06/django-45-react-flippy-card-with-image.html
No comments:
Post a Comment