master
parent
69599d3c65
commit
d957786743
42 changed files with 857 additions and 194 deletions
@ -0,0 +1,3 @@ |
||||
import { createBrowserHistory } from "history"; |
||||
|
||||
export default createBrowserHistory(); |
@ -0,0 +1,45 @@ |
||||
import React from "react"; |
||||
import { Link } from "react-router-dom"; |
||||
|
||||
import "./index.scss"; |
||||
|
||||
const grades = [ |
||||
"اول", |
||||
"دوم", |
||||
"سوم", |
||||
"چهارم", |
||||
"پنجم", |
||||
"ششم", |
||||
"هفتم", |
||||
"هشتم", |
||||
"نهم", |
||||
"دهم", |
||||
"یازدهم", |
||||
"دوازدهم", |
||||
]; |
||||
|
||||
export default function LastQR(props) { |
||||
const { data } = props; |
||||
return ( |
||||
<> |
||||
{window.innerWidth > 1000 ? <div className="last-qr"></div> : null} |
||||
{window.innerWidth < 1000 ? ( |
||||
<Link to={`/qr${data.link}`} className="mobile-last-qr d-flex"> |
||||
<div className="mobile-last-qr__right d-flex"> |
||||
<img |
||||
src={`https://dnvn.ir/api/v1/file/${data.fileIds}`} |
||||
alt={data.name} |
||||
/> |
||||
<div className="d-flex flex-column"> |
||||
<strong>{data.name}</strong> |
||||
<span>{grades[data.gradeId]}</span> |
||||
</div> |
||||
</div> |
||||
<div className="mobile-last-qr__left d-flex"> |
||||
<button>صفحه {data.pagesNum}</button> |
||||
</div> |
||||
</Link> |
||||
) : null} |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,46 @@ |
||||
.mobile-last-qr { |
||||
width: 100%; |
||||
padding: 15px 10px; |
||||
justify-content: space-between; |
||||
background-color: white; |
||||
align-items: center; |
||||
border-radius: 10px; |
||||
box-shadow: 0px 0px 10px rgb(226, 226, 226); |
||||
text-decoration: none; |
||||
&__right { |
||||
align-items: center; |
||||
img { |
||||
height: 100px; |
||||
border-radius: 10px; |
||||
margin-left: 15px; |
||||
} |
||||
strong { |
||||
font-family: numeralMedium; |
||||
font-size: calc(100vw / 25); |
||||
font-weight: 600; |
||||
letter-spacing: -1px; |
||||
color: #4e4e4e; |
||||
} |
||||
span { |
||||
font-family: numeralMedium; |
||||
font-size: calc(100vw / 30); |
||||
color: #00cc69; |
||||
} |
||||
} |
||||
&__left { |
||||
button { |
||||
display: inline; |
||||
font-family: numeralLight; |
||||
text-decoration: none; |
||||
padding: 5px 20px 3px; |
||||
border: 1px solid #00cc69; |
||||
background-color: white; |
||||
border-radius: 25px; |
||||
font-size: calc(100vw / 26); |
||||
color: #00cc69; |
||||
&:focus:enabled { |
||||
text-decoration: none; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,47 @@ |
||||
import React, { Component } from "react"; |
||||
import { Link } from "react-router-dom"; |
||||
|
||||
import "./index.scss"; |
||||
|
||||
const maxMobileSize = 550; |
||||
|
||||
const fontSize = { |
||||
mobile: { |
||||
span: window.innerWidth > maxMobileSize ? 20 : window.innerWidth / 17, |
||||
a: window.innerWidth > maxMobileSize ? 20 : window.innerWidth / 20, |
||||
}, |
||||
}; |
||||
export default class QRModal extends Component { |
||||
render() { |
||||
const { tail, parent } = this.props; |
||||
return ( |
||||
<> |
||||
{window.innerWidth < 1000 ? ( |
||||
<div className="mobile-qr-modal d-flex flex-column justify-content-center align-items-center"> |
||||
<span style={{ fontSize: fontSize.mobile.span }}> |
||||
اسکن کد QR موفقیت آمیز بود. |
||||
</span> |
||||
<Link style={{ fontSize: fontSize.mobile.a }} to={"/qr"}> |
||||
{tail} |
||||
</Link> |
||||
<button onClick={() => parent.setState({ tail: null })}> |
||||
انصراف |
||||
</button> |
||||
</div> |
||||
) : ( |
||||
<div className="qr-modal d-flex flex-column justify-content-center align-items-center"> |
||||
<span style={{ fontSize: fontSize.mobile.span }}> |
||||
اسکن کد QR موفقیت آمیز بود. |
||||
</span> |
||||
<Link style={{ fontSize: fontSize.mobile.a }} to={"/qr"}> |
||||
{tail} |
||||
</Link> |
||||
<button onClick={() => parent.setState({ tail: null })}> |
||||
انصراف |
||||
</button> |
||||
</div> |
||||
)} |
||||
</> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,92 @@ |
||||
.mobile-qr-modal { |
||||
position: fixed; |
||||
left: 0px; |
||||
background-color: rgba(0, 0, 0, 0.856); |
||||
top: 0px; |
||||
width: 100vw; |
||||
height: 100vh; |
||||
z-index: 500; |
||||
animation-name: qrModal; |
||||
animation-duration: 0.4s; |
||||
span { |
||||
font-family: numeralLight; |
||||
color: white; |
||||
} |
||||
a { |
||||
text-decoration: none; |
||||
width: 50%; |
||||
margin: 25px auto; |
||||
background-color: #00cc69; |
||||
border: 2px solid white; |
||||
padding: 20px 0px; |
||||
text-align: center; |
||||
color: white; |
||||
font-family: numeralLight; |
||||
border-radius: 25px; |
||||
} |
||||
button { |
||||
text-decoration: none; |
||||
width: 30%; |
||||
min-width: 100px; |
||||
margin: 15px auto; |
||||
background-color: #cc001b; |
||||
border: 2px solid white; |
||||
padding: 5px 0px; |
||||
text-align: center; |
||||
color: white; |
||||
font-family: numeralLight; |
||||
border-radius: 25px; |
||||
} |
||||
} |
||||
|
||||
.qr-modal { |
||||
position: fixed; |
||||
left: 0px; |
||||
background-color: rgba(0, 0, 0, 0.925); |
||||
top: 0px; |
||||
width: 100vw; |
||||
height: 100vh; |
||||
z-index: 500; |
||||
animation-name: qrModal; |
||||
animation-duration: 0.4s; |
||||
span { |
||||
font-family: numeralLight; |
||||
color: white; |
||||
} |
||||
a { |
||||
text-decoration: none; |
||||
width: 50%; |
||||
margin: 25px auto; |
||||
background-color: #00cc69; |
||||
border: 2px solid white; |
||||
padding: 20px 0px; |
||||
text-align: center; |
||||
color: white; |
||||
font-family: numeralLight; |
||||
border-radius: 25px; |
||||
} |
||||
button { |
||||
text-decoration: none; |
||||
width: 25%; |
||||
max-width: 100px; |
||||
margin: 15px auto; |
||||
background-color: #cc001b; |
||||
border: 2px solid white; |
||||
padding: 10px 0px; |
||||
text-align: center; |
||||
color: white; |
||||
font-family: numeralLight; |
||||
border-radius: 25px; |
||||
} |
||||
} |
||||
|
||||
@keyframes qrModal { |
||||
0% { |
||||
left: -100vw; |
||||
top: 0px; |
||||
} |
||||
100% { |
||||
left: 0px; |
||||
top: 0px; |
||||
} |
||||
} |
@ -0,0 +1,57 @@ |
||||
import React, { Component } from "react"; |
||||
//import QrReader from "react-camera-qr";
|
||||
import QrReader from "react-qr-reader"; |
||||
|
||||
export default class Scanner extends Component { |
||||
constructor(props) { |
||||
super(props); |
||||
this.state = { |
||||
delay: 100, |
||||
result: "", |
||||
}; |
||||
|
||||
this.handleScan = this.handleScan.bind(this); |
||||
} |
||||
|
||||
handleScan(data) { |
||||
this.props.parent.setState({ |
||||
value: data, |
||||
}); |
||||
} |
||||
|
||||
handleError(err) { |
||||
console.error(err); |
||||
} |
||||
|
||||
render() { |
||||
let cam = this.props.parent.state.cameraId; |
||||
|
||||
let facingMode = this.props.parent.state.cameraId; |
||||
return ( |
||||
<> |
||||
{window.innerWidth < 1000 ? ( |
||||
<QrReader |
||||
delay={800 + Math.random() * 10} |
||||
onError={this.handleError} |
||||
onScan={this.handleScan} |
||||
style={{ width: "100%" }} |
||||
cameraId={cam} |
||||
facingMode={facingMode} |
||||
/> |
||||
) : null} |
||||
{window.innerWidth > 1000 ? ( |
||||
<QrReader |
||||
delay={800 + Math.random() * 10} |
||||
onError={this.handleError} |
||||
onScan={this.handleScan} |
||||
style={{ width: "100%" }} |
||||
cameraId={cam} |
||||
facingMode={facingMode} |
||||
/> |
||||
) : null} |
||||
{/* <div>{facingMode[this.props.parent.state.facingMode]}</div> |
||||
<div>{(this.state?.devices || []).join(",")}</div> */} |
||||
</> |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,242 @@ |
||||
import React, { Component } from "react"; |
||||
import Navbar from "../Home/Navbar/index"; |
||||
import Footer from "../Home/Footer/index"; |
||||
|
||||
import Scanner from "./Scanner/index"; |
||||
import LastQr from "./LastQr/index"; |
||||
import Modal from "./Modal/index"; |
||||
import ReplayIcon from "@material-ui/icons/Replay"; |
||||
import FlipCameraIosIcon from "@material-ui/icons/FlipCameraIos"; |
||||
|
||||
import { connect } from "react-redux"; |
||||
import { qr } from "~/Redux/actions"; |
||||
|
||||
import sciense_6 from "../../assets/images/sciense-6.png"; |
||||
|
||||
import "./index.scss"; |
||||
|
||||
const maxDesktopSize = 1250; |
||||
|
||||
const fontSize = { |
||||
desktop: { |
||||
h1: window.innerWidth > maxDesktopSize ? 25 : window.innerWidth / 70, |
||||
p: window.innerHeight > maxDesktopSize ? 15 : window.innerWidth / 90, |
||||
}, |
||||
}; |
||||
|
||||
class QRScan extends Component { |
||||
state = { |
||||
value: null, |
||||
facingMode: 0, |
||||
tail: null, |
||||
cameraIds: [], |
||||
cameraId: "rare", |
||||
}; |
||||
componentDidMount() { |
||||
navigator.mediaDevices.enumerateDevices().then((devices) => { |
||||
console.log({ devices }); |
||||
var cameraIds = devices |
||||
.filter(function (device) { |
||||
return device.kind === "videoinput"; |
||||
}) |
||||
.map((e) => [e.label, e.deviceId]); |
||||
|
||||
var environmentPattern = /rear|back|environment/gi; |
||||
var userPattern = /front|user|face/gi; |
||||
var backs = cameraIds.filter((label) => environmentPattern.test(label)); |
||||
var notFronts = cameraIds.filter((label) => !userPattern.test(label)); |
||||
let cameraId = backs[0] || notFronts[0] || cameraIds[0]; |
||||
let cameraIdx = cameraIds.findIndex((cam) => cam[1] === cameraId[1]); |
||||
console.log({ |
||||
cameraIds, |
||||
cameraId: cameraId[0], |
||||
cameraIdx, |
||||
}); |
||||
this.setState({ |
||||
cameraIds, |
||||
cameraId, |
||||
cameraIdx, |
||||
}); |
||||
}); |
||||
} |
||||
async componentDidUpdate() { |
||||
if (this.state.value) { |
||||
let tail = this.state.value.slice( |
||||
this.state.value.lastIndexOf("/") + 1, |
||||
this.state.value.length |
||||
); |
||||
this.props.info({ tail: tail }); |
||||
this.setState({ value: null, tail: tail }); |
||||
} |
||||
} |
||||
|
||||
render() { |
||||
const { data } = this.props; |
||||
return ( |
||||
<> |
||||
{window.innerWidth > 100 ? ( |
||||
<> |
||||
<div className="qr-scan d-flex flex-column align-items-center"> |
||||
<Navbar /> |
||||
<div className="qr-scan__box d-flex flex-column"> |
||||
<header className="qr-scan__box--header d-flex flex-column"> |
||||
<h1 style={{ fontSize: fontSize.desktop.h1 }}>نکته</h1> |
||||
<p style={{ fontSize: fontSize.desktop.p }}> |
||||
لطفا دوربین خود را روی کدی که در اختیار شما قرار گرفته قرار |
||||
دهید تا اسکن دوربین انجام شود |
||||
</p> |
||||
</header> |
||||
{this.state.cameraIds.length > 1 ? ( |
||||
<div className="qr-scan__box--select d-flex"> |
||||
<select |
||||
value={this.state.cameraId} |
||||
onChange={(e) => |
||||
this.setState({ |
||||
facingMode: Math.round() * 3, |
||||
cameraId: e.target.value, |
||||
}) |
||||
} |
||||
> |
||||
{this.state.cameraIds.map((e) => ( |
||||
<option value={e[1]}>{e[0] || e[1]}</option> |
||||
))} |
||||
</select> |
||||
</div> |
||||
) : null} |
||||
<section className="qr-scan__box--camera d-flex justify-content-center align-items-center"> |
||||
<FlipCameraIosIcon |
||||
style={{ |
||||
fontSize: 35, |
||||
position: "absolute", |
||||
left: 10, |
||||
top: 10, |
||||
zIndex: 100, |
||||
color: "white", |
||||
}} |
||||
onClick={() => |
||||
this.setState({ |
||||
facingMode: (this.state.facingMode + 1) % 2, |
||||
}) |
||||
} |
||||
/> |
||||
{this.state.value !== null ? ( |
||||
<div |
||||
style={{ zIndex: 1000 }} |
||||
onClick={() => this.setState({ value: null })} |
||||
> |
||||
<ReplayIcon style={{ fontSize: 40, color: "grey" }} /> |
||||
</div> |
||||
) : ( |
||||
<Scanner parent={this} cameraId={this.state.cameraId} /> |
||||
)} |
||||
</section> |
||||
{this.state.tail === null ? null : ( |
||||
<Modal tail={this.state.tail} /> |
||||
)} |
||||
</div> |
||||
</div> |
||||
<Footer /> |
||||
</> |
||||
) : null} |
||||
{window.innerWidth < 100 ? ( |
||||
<> |
||||
<div className="mobile-qr-scan d-flex flex-column"> |
||||
<Navbar page="qr-scan" /> |
||||
{/* <div className="mobile-qr-scan__back d-flex"> |
||||
<Link to="/">بازگشت</Link> |
||||
</div> */} |
||||
<header className="mobile-qr-scan__header d-flex flex-column"></header> |
||||
|
||||
<section className="mobile-qr-scan__camera d-flex justify-content-center align-items-center"> |
||||
{this.state.cameraIds.length > 1 ? ( |
||||
<> |
||||
<div |
||||
className="qr-scan__box--select d-flex" |
||||
style={{ position: "absolute", zIndex: 500 }} |
||||
> |
||||
<select |
||||
value={this.state.cameraId} |
||||
onChange={(e) => |
||||
this.setState({ |
||||
facingMode: Math.round() * 3, |
||||
cameraId: e.target.value, |
||||
}) |
||||
} |
||||
> |
||||
{this.state.cameraIds.map((e) => ( |
||||
<option value={e[1]}> |
||||
{e[0]} |
||||
{e[1]} |
||||
</option> |
||||
))} |
||||
</select> |
||||
</div> |
||||
<FlipCameraIosIcon |
||||
style={{ |
||||
fontSize: 35, |
||||
position: "absolute", |
||||
left: 10, |
||||
top: 10, |
||||
zIndex: 100, |
||||
color: "white", |
||||
}} |
||||
onClick={() => { |
||||
let cameraIdx = |
||||
(this.state.cameraIdx + 1) % |
||||
this.state.cameraIds.length; |
||||
|
||||
this.setState({ |
||||
cameraIdx, |
||||
cameraId: this.state.cameraIds[cameraIdx], |
||||
}); |
||||
}} |
||||
/> |
||||
</> |
||||
) : null} |
||||
{false && this.state.cameraId === "" ? ( |
||||
<div |
||||
style={{ zIndex: 1000 }} |
||||
onClick={() => this.setState({ value: null })} |
||||
> |
||||
<ReplayIcon style={{ fontSize: 40, color: "grey" }} /> |
||||
</div> |
||||
) : ( |
||||
<Scanner parent={this} cameraId={this.state.cameraId} /> |
||||
)} |
||||
</section> |
||||
{this.state.tail === null ? null : ( |
||||
<Modal parent={this} tail={this.state.tail} /> |
||||
)} |
||||
{localStorage.getItem("latestQr") ? ( |
||||
<section className="mobile-qr-scan__lasts d-flex flex-column"> |
||||
<h2>آخرین مشاهدات</h2> |
||||
{data.map((item, i) => ( |
||||
<LastQr |
||||
data={JSON.parse(localStorage.getItem("latestQr"))} |
||||
key={i} |
||||
/> |
||||
))} |
||||
</section> |
||||
) : null} |
||||
</div> |
||||
</> |
||||
) : null} |
||||
</> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default connect((state) => ({ list: state.qr.list }), { info: qr.info })( |
||||
QRScan |
||||
); |
||||
|
||||
QRScan.defaultProps = { |
||||
data: [ |
||||
{ |
||||
title: "ریاضیات گسسته خیلی پیشرفته", |
||||
subTitle: "پایه هفتم ابتدایی", |
||||
page: 13, |
||||
image: sciense_6, |
||||
}, |
||||
], |
||||
}; |
@ -0,0 +1,167 @@ |
||||
.mobile-qr-scan { |
||||
width: 100vw; |
||||
max-width: 1250px; |
||||
margin: 0px auto; |
||||
padding: 0px 10px 40px; |
||||
direction: rtl; |
||||
padding-top: 70px; |
||||
overflow-x: hidden; |
||||
&__back { |
||||
justify-content: space-around; |
||||
a { |
||||
display: inline; |
||||
font-family: numeralLight; |
||||
text-decoration: none; |
||||
padding: 5px 20px 3px; |
||||
background-color: #00cc69; |
||||
border-radius: 25px; |
||||
font-size: calc(100vw / 26); |
||||
color: white; |
||||
&:focus:enabled { |
||||
text-decoration: none; |
||||
} |
||||
} |
||||
} |
||||
&__header { |
||||
width: 100%; |
||||
h1 { |
||||
font-family: numeralMedium; |
||||
color: #4ce1be; |
||||
font-size: calc(100vw / 13); |
||||
} |
||||
p { |
||||
text-align: justify; |
||||
font-family: numeralMedium; |
||||
color: #4ce1be; |
||||
font-size: calc(100vw / 28); |
||||
} |
||||
} |
||||
&__select { |
||||
select { |
||||
margin-top: 10px; |
||||
border-radius: 4px; |
||||
position: relative; |
||||
background-color: #efefef; |
||||
border: 1px solid #a5a5a5; |
||||
font-size: 16px; |
||||
color: black; |
||||
// maxWidth : 70; |
||||
min-width: 80; |
||||
height: 35px; |
||||
padding: 5px; |
||||
font-family: numeralLight; |
||||
&:focus { |
||||
border-radius: 4; |
||||
} |
||||
} |
||||
} |
||||
&__camera { |
||||
// margin-top: 20px; |
||||
width: 100%; |
||||
border-radius: 10px; |
||||
margin: 5px auto; |
||||
position: relative; |
||||
&--shape { |
||||
position: absolute; |
||||
background-color: rgba(0, 0, 0, 0.8); |
||||
} |
||||
} |
||||
&__text { |
||||
text-align: justify; |
||||
font-family: numeralMedium; |
||||
color: #4ce1be; |
||||
font-size: calc(100vw / 28); |
||||
} |
||||
&__lasts { |
||||
width: 100%; |
||||
margin: 15px 0px; |
||||
h2 { |
||||
font-family: numeralMedium; |
||||
color: #02a255; |
||||
font-size: calc(100vw / 24); |
||||
margin-bottom: 20px; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.qr-scan { |
||||
width: 100vw; |
||||
max-width: 1250px; |
||||
min-height: calc(100vh - 288px); |
||||
margin: 0px auto; |
||||
padding: 0px 10px; |
||||
direction: rtl; |
||||
overflow-x: hidden; |
||||
padding-top: 80px; |
||||
&__back { |
||||
justify-content: flex-start; |
||||
a { |
||||
display: inline; |
||||
font-family: numeralLight; |
||||
text-decoration: none; |
||||
padding: 5px 20px 3px; |
||||
background-color: #00cc69; |
||||
border-radius: 25px; |
||||
font-size: calc(100vw / 70); |
||||
color: white; |
||||
&:focus:enabled { |
||||
text-decoration: none; |
||||
} |
||||
} |
||||
} |
||||
&__box { |
||||
width: 100%; |
||||
max-width: 700px; |
||||
&--header { |
||||
width: 100%; |
||||
h1 { |
||||
font-family: numeralMedium; |
||||
color: #00cc69; |
||||
} |
||||
p { |
||||
font-family: numeralLight; |
||||
color: #4d4d4f; |
||||
} |
||||
} |
||||
&--select { |
||||
select { |
||||
margin-top: 10px; |
||||
border-radius: 4px; |
||||
position: relative; |
||||
background-color: #efefef; |
||||
border: 1px solid #a5a5a5; |
||||
font-size: 16px; |
||||
color: black; |
||||
// maxWidth : 70; |
||||
min-width: 80; |
||||
height: 35px; |
||||
padding: 5px; |
||||
font-family: numeralLight; |
||||
&:focus { |
||||
border-radius: 4; |
||||
} |
||||
} |
||||
} |
||||
&--camera { |
||||
margin-top: 20px; |
||||
width: 100%; |
||||
max-width: 300px; |
||||
margin: 0px auto 40px; |
||||
* { |
||||
border-radius: 10px; |
||||
} |
||||
} |
||||
&--text { |
||||
margin-top: 30px; |
||||
width: 100%; |
||||
p { |
||||
font-family: numeralLight; |
||||
color: #4d4d4f; |
||||
} |
||||
} |
||||
p { |
||||
font-family: numeralLight; |
||||
color: #4d4d4f; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue