Merge branch 'dev-redux' of https://github.com/ciaochaos/qrbtf into dev-redux

This commit is contained in:
ciaochaos 2020-05-19 14:06:13 +08:00
commit e141541287
18 changed files with 212 additions and 77 deletions

5
package-lock.json generated
View File

@ -8091,6 +8091,11 @@
"verror": "1.10.0" "verror": "1.10.0"
} }
}, },
"jsqr": {
"version": "1.3.1",
"resolved": "https://registry.npm.taobao.org/jsqr/download/jsqr-1.3.1.tgz",
"integrity": "sha1-UVp2bliwDIAULzotxLh1EQDO7c8="
},
"jsx-ast-utils": { "jsx-ast-utils": {
"version": "2.2.3", "version": "2.2.3",
"resolved": "https://registry.npm.taobao.org/jsx-ast-utils/download/jsx-ast-utils-2.2.3.tgz", "resolved": "https://registry.npm.taobao.org/jsx-ast-utils/download/jsx-ast-utils-2.2.3.tgz",

View File

@ -6,6 +6,7 @@
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^7.1.2",
"jsqr": "^1.3.1",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react": "^16.13.1", "react": "^16.13.1",
"react-color": "^2.18.1", "react-color": "^2.18.1",

View File

@ -1,3 +1,4 @@
.Qr-titled { .Qr-titled {
background-color: #f5f5f7; background-color: #f5f5f7;
box-sizing: border-box; box-sizing: border-box;
@ -461,11 +462,61 @@ a:hover {
font-size: 12px; font-size: 12px;
} }
.Qr-article {
word-wrap: break-word;
margin-top: calc((10px + 1vmin) * 2);
margin-bottom: calc((10px + 2vmin) * 2);
border-spacing: 0;
}
.Qr-article p {
font-size: 14px;
line-height: 1.7em;
color: #636366;
letter-spacing: 0.05em;
}
.Qr-article p b {
font-weight: bold;
color: #000000;
}
.Qr-article p a {
font-weight: bold;
padding: 1px 0;
color: #000000;
border-bottom: 1px solid #000000;
}
.Qr-article p a:hover {
color: #000000;
border-bottom: 1px solid #000000;
text-decoration: none;
}
.Qr-article h2 {
word-break: break-all;
word-wrap: break-word;
font-size: 18px;
line-height: 1.7em;
letter-spacing: 0.05em;
margin-top: 1.5em;
margin-bottom: 1.5em;
}
@media (min-width: 500px) { @media (min-width: 500px) {
.note-font { .note-font {
color: #1D1D1F; color: #1D1D1F;
font-size: 14px; font-size: 14px;
} }
.Qr-article p {
font-size: 16px;
}
.Qr-article h2 {
font-size: 22px;
}
} }
select { select {

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from 'react';
import './App.css'; import './App.css';
import '../Qrcode.css'; import '../Qrcode.css';
import Footer from "../footer/Footer"; import PartFooter from "./PartFooter";
import Header from "../header/Header"; import PartHeader from "./PartHeader";
import PartMore from "./PartMore"; import PartMore from "./PartMore";
import PartParams from "./PartParams"; import PartParams from "./PartParams";
import PartDownloadViewer from "../../containers/app/PartDownloadViewer"; import PartDownloadViewer from "../../containers/app/PartDownloadViewer";
@ -14,12 +14,12 @@ function App() {
<header className="App-header"> <header className="App-header">
<div className="Layout"> <div className="Layout">
<div className="Qr-outer"> <div className="Qr-outer">
<Header/> <PartHeader/>
<PartStylesViewer/> <PartStylesViewer/>
<PartParams/> <PartParams/>
<PartDownloadViewer/> <PartDownloadViewer/>
<PartMore/> <PartMore/>
<Footer/> <PartFooter/>
</div> </div>
</div> </div>
</header> </header>

View File

@ -3,7 +3,7 @@ import '../Qrcode.css';
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
const Footer = () => ( const PartFooter = () => (
<div className="Qr-titled"> <div className="Qr-titled">
<div className="Qr-Centered Qr-footer note-font"> <div className="Qr-Centered Qr-footer note-font">
<div> <div>
@ -33,4 +33,4 @@ const Footer = () => (
</div> </div>
) )
export default Footer export default PartFooter

View File

@ -9,7 +9,7 @@ const logoStyle = {
backgroundPosition: 'left' backgroundPosition: 'left'
}; };
const Header = () => ( const PartHeader = () => (
<div className="Qr-Centered"> <div className="Qr-Centered">
<div style={logoStyle}> <div style={logoStyle}>
<h1 className="Qr-title">&ensp;</h1> <h1 className="Qr-title">&ensp;</h1>
@ -19,4 +19,4 @@ const Header = () => (
</div> </div>
) )
export default Header export default PartHeader

View File

@ -7,6 +7,20 @@ const PartMore = () => (
<div className="Qr-Centered title-margin"> <div className="Qr-Centered title-margin">
<div className="Qr-s-title">More</div> <div className="Qr-s-title">More</div>
<p className="Qr-s-subtitle">更多</p> <p className="Qr-s-subtitle">更多</p>
<div className="Qr-article">
<h2>为什么要做一个二维码生成器</h2>
<p>看这里<a href='https://mp.weixin.qq.com/s/_Oy9I9FqPXhfwN9IUhf6_g' rel="noopener noreferrer" target="_blank">这篇文章</a> </p>
<h2>这个生成器的特别之处在哪里</h2>
<p>普通的二维码样式单一不能与环境较好的融合这一个生成器有着 <b>丰富的参数化样式基于 SVG 的二维码生成能力</b> SVG </p>
<h2>如何使用</h2>
<p>从输入 URL 开始没有确认框没有额外的页面选择样式后自动更新调整参数后下载即可</p>
<h2>我应该下载 SVG 还是 JPG</h2>
<p>这个工具开发的初衷之一就是便利设计师将其纳入自己的工作流程中SVG 是一个优秀的标准的矢量图片格式各大设计软件如 Adobe IllustratorSketch 等都对 SVG 有着很好的支持用户可以在下载 SVG 后导入这些软件进行二次加工如删除中央的信息点 <b>放入自己的 Logo</b> JPG </p>
<h2>使用遇到了问题怎么反馈</h2>
<p>我们是两位大一的学生忙于学业可能在设计与开发的过程中有一些疏漏敬请谅解如果遇到浏览器兼容问题请暂时选择更换软件或设备尝试经常有人问怎么把已有的公众号二维码上传很抱歉的是目前我们并没有开发这个功能将来一定实现如有需要请暂时使用第三方软件解码也有人问为什么电脑端右边的样式没显示全不是 bug只是我们懒得做切换滑动按钮目前请按住 Shift 使用滚轮在样式区域滚动一定能行</p>
<p>请注意应用并不能保证二维码时刻可被识别需要多加测试</p>
<p>如果你有兴趣和我们一起玩这个项目<b>设计样式开发应用</b></p>
</div>
</div> </div>
<div className="Qr-Centered btn-row"> <div className="Qr-Centered btn-row">
<div className="div-btn"> <div className="div-btn">
@ -14,8 +28,7 @@ const PartMore = () => (
<LinkButton href={"https://www.yuque.com/qrbtf/topics"} value={"问题反馈"} /> <LinkButton href={"https://www.yuque.com/qrbtf/topics"} value={"问题反馈"} />
</div> </div>
<div className="div-btn"> <div className="div-btn">
<LinkButton href={"https://www.yuque.com/qrbtf/docs/dev"} value={"开发与设计"} /> <LinkButton href={"https://www.yuque.com/qrbtf/docs/coop"} value={"合作咨询"} />
<LinkButton href={"https://www.yuque.com/qrbtf/docs/coop"} value={"商业合作"} />
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,7 +1,5 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import {defaultViewBox, rand} from "../../utils/util"; import {defaultViewBox, rand} from "../../utils/util";
import {ParamTypes} from "../../constant/ParamTypes";
import {getTypeTable, QRPointType} from "../../utils/qrcodeHandler";
function listPoints(qrcode, params) { function listPoints(qrcode, params) {
if (!qrcode) return [] if (!qrcode) return []

View File

@ -9,10 +9,12 @@ function listPoints(qrcode, params) {
const nCount = qrcode.getModuleCount(); const nCount = qrcode.getModuleCount();
const typeTable = getTypeTable(qrcode); const typeTable = getTypeTable(qrcode);
const pointList = new Array(nCount); const pointList = new Array(nCount);
let alignType = params[1]; let contrast = params[1];
let timingType = params[2]; let exposure = params[2];
let otherColor = params[3]; let alignType = params[3];
let posColor = params[4]; let timingType = params[4];
let otherColor = params[5];
let posColor = params[6];
let id = 0; let id = 0;
for (let x = 0; x < nCount; x++) { for (let x = 0; x < nCount; x++) {
@ -74,6 +76,16 @@ function getParamInfo() {
key: '背景图片', key: '背景图片',
default: data, default: data,
}, },
{
type: ParamTypes.TEXT_EDITOR,
key: '对比度',
default: 0
},
{
type: ParamTypes.TEXT_EDITOR,
key: '曝光',
default: 0
},
{ {
type: ParamTypes.SELECTOR, type: ParamTypes.SELECTOR,
key: '小定位点样式', key: '小定位点样式',
@ -114,7 +126,7 @@ export function getViewBox(qrcode) {
return String(-nCount / 5) + ' ' + String(-nCount / 5) + ' ' + String(nCount + nCount / 5 * 2) + ' ' + String(nCount + nCount / 5 * 2); return String(-nCount / 5) + ' ' + String(-nCount / 5) + ' ' + String(nCount + nCount / 5 * 2) + ' ' + String(nCount + nCount / 5 * 2);
} }
function getGrayPointList(imgBase64, size, black, white) { function getGrayPointList(params, size, black, white) {
let canvas = document.createElement('canvas'); let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d'); let ctx = canvas.getContext('2d');
let img = document.createElement('img'); let img = document.createElement('img');
@ -122,7 +134,9 @@ function getGrayPointList(imgBase64, size, black, white) {
canvas.style.imageRendering = 'pixelated'; canvas.style.imageRendering = 'pixelated';
size *= 3; size *= 3;
img.src = imgBase64; img.src = params[0];
let contrast = params[1]/100;
let exposure = params[2]/100;
return new Promise(resolve => { return new Promise(resolve => {
img.onload = () => { img.onload = () => {
canvas.width = size; canvas.width = size;
@ -135,7 +149,7 @@ function getGrayPointList(imgBase64, size, black, white) {
let imageData = ctx.getImageData(x, y, 1, 1); let imageData = ctx.getImageData(x, y, 1, 1);
let data = imageData.data; let data = imageData.data;
let gray = gamma(data[0], data[1], data[2]); let gray = gamma(data[0], data[1], data[2]);
if (Math.random() > gray / 255 && ( x % 3 !== 1 || y % 3 !== 1 ) ) gpl.push(<use key={"g_" + x + "_" + y} x={x} y={y} xlinkHref={black} />); if (Math.random() > ((gray / 255) + exposure - 0.5) * (contrast + 1) + 0.5 && ( x % 3 !== 1 || y % 3 !== 1 ) ) gpl.push(<use key={"g_" + x + "_" + y} x={x} y={y} xlinkHref={black} />);
} }
} }
resolve(gpl); resolve(gpl);
@ -144,8 +158,7 @@ function getGrayPointList(imgBase64, size, black, white) {
} }
const RendererResImage = ({qrcode, params, setParamInfo}) => { const RendererResImage = ({qrcode, params, setParamInfo}) => {
let otherColor = params[3]; let otherColor = params[5];
let posColor = params[4];
useEffect(() => { useEffect(() => {
setParamInfo(getParamInfo()); setParamInfo(getParamInfo());
@ -153,8 +166,8 @@ const RendererResImage = ({qrcode, params, setParamInfo}) => {
const [gpl, setGPL] = useState([]); const [gpl, setGPL] = useState([]);
useMemo(() => { useMemo(() => {
getGrayPointList(params[0], qrcode.getModuleCount(), "#S-black", "#S-white").then(res => setGPL(res)); getGrayPointList(params, qrcode.getModuleCount(), "#S-black", "#S-white").then(res => setGPL(res));
}, [setGPL, params[0], qrcode]) }, [setGPL, params, qrcode])
return ( return (
<svg className="Qr-item-svg" width="100%" height="100%" viewBox={getViewBox(qrcode)} fill="white" <svg className="Qr-item-svg" width="100%" height="100%" viewBox={getViewBox(qrcode)} fill="white"

View File

@ -3,5 +3,5 @@ export const actionTypes = {
CHANGE_STYLE: 'CHANGE_STYLE', CHANGE_STYLE: 'CHANGE_STYLE',
CHANGE_CORRECT_LEVEL: 'CHANGE_CORRECT_LEVEL', CHANGE_CORRECT_LEVEL: 'CHANGE_CORRECT_LEVEL',
CREATE_PARAM: 'CREATE_PARAM', CREATE_PARAM: 'CREATE_PARAM',
CHANGE_PARAM: 'CHANGE_PARAM' CHANGE_PARAM: 'CHANGE_PARAM',
} }

View File

@ -1,19 +1,55 @@
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import {genQRInfo} from "../../actions"; import {genQRInfo} from "../../actions";
import React from "react"; import React, {useRef} from "react";
import {isPicture} from "../../utils/util";
import {decodeData} from "../../utils/qrcodeHandler";
const InputText = ({dispatch}) => ( const InputText = ({dispatch}) => {
<input const textRef = useRef();
className="Qr-input big-input"
placeholder="Input your URL here" return (
onBlur={e => dispatch(genQRInfo(e.target.value))} <React.Fragment>
onKeyPress={(e) => { <input
if (e.key === 'Enter') { className="Qr-input big-input"
dispatch(genQRInfo(e.target.value)); placeholder="Input your URL here"
e.target.blur(); ref={textRef}
} onBlur={e => dispatch(genQRInfo(e.target.value))}
}} onKeyPress={(e) => {
/> if (e.key === 'Enter') {
) dispatch(genQRInfo(e.target.value));
e.target.blur();
}
}}
/>
<label
htmlFor="image_scanner"
className="dl-btn"
style={{textAlign: "center"}}
>
扫描图片
</label>
<input
type="file"
id="image_scanner"
hidden={true}
accept=".jpg, .jpeg, .png"
onClick={(e) => e.target.value = null}
onChange={(e) => {
if (e.target.files.length > 0) {
const file = e.target.files[0];
if (isPicture(file)) {
decodeData(file).then((res) => {
if (res) {
textRef.current.value = res.data;
console.log(res.data)
dispatch(genQRInfo(res.data))
}
}).catch(console.err);
}
}
}}
/>
</React.Fragment>);
}
export default connect()(InputText); export default connect()(InputText);

View File

@ -1,41 +1,36 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PartDownload from "../../components/app/PartDownload"; import PartDownload from "../../components/app/PartDownload";
import React from "react";
import {saveImg, saveSvg} from "../../utils/downloader"; import {saveImg, saveSvg} from "../../utils/downloader";
import ReactDOMServer from "react-dom/server";
import {increaseDownloadData, recordDownloadDetail} from "../../api/db"; import {increaseDownloadData, recordDownloadDetail} from "../../api/db";
import {getParamDetailedValue, outerHtml} from "../../utils/util"; import {getParamDetailedValue, outerHtml} from "../../utils/util";
function saveEl(state, type) { function saveDB(state, type) {
const el = React.createElement(state.rendererType, {
qrcode: state.qrcode,
params: state.paramValue[state.selectedIndex],
setParamInfo: () => {}
});
increaseDownloadData(state.value) increaseDownloadData(state.value)
recordDownloadDetail({ recordDownloadDetail({
text: state.textUrl, text: state.textUrl,
value: state.value, value: state.value,
type: type, type: type,
params: state.paramInfo[state.selectedIndex].map((item, index) => { params: state.paramInfo[state.selectedIndex].map((item, index) => {
return { const value = getParamDetailedValue(item, state.paramValue[state.selectedIndex][index])
key: item.key, if (typeof value != "string" || value.length <= 128) {
value: getParamDetailedValue(item, state.paramValue[state.selectedIndex][index]) return {
key: item.key,
value: value
}
} }
}), }),
history: state.history history: state.history
}); });
return el;
} }
const mapStateToProps = (state) => ({ const mapStateToProps = (state) => ({
value: state.value, value: state.value,
onSvgDownload: () => { onSvgDownload: () => {
// saveSvg(state.value, ReactDOMServer.renderToString(saveEl(state, 'svg')))
saveSvg(state.value, outerHtml(state.selectedIndex)) saveSvg(state.value, outerHtml(state.selectedIndex))
saveDB(state, 'svg')
}, },
onJpgDownload: () => { onJpgDownload: () => {
// return saveImg(state.value, ReactDOMServer.renderToString(saveEl(state, 'jpg')), 1500, 1500) saveDB(state, 'jpg')
return saveImg(state.value, outerHtml(state.selectedIndex), 1500, 1500) return saveImg(state.value, outerHtml(state.selectedIndex), 1500, 1500)
} }
}) })

View File

@ -1,17 +1,7 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import {changeParam} from "../../actions"; import {changeParam} from "../../actions";
import ParamUpload from "../../components/param/ParamUpload"; import ParamUpload from "../../components/param/ParamUpload";
import {toBase64} from "../../utils/util"; import {isPicture, toBase64} from "../../utils/util";
const fileTypes =[
'image/jpeg',
'image/pjpeg',
'image/png'
]
function validFileType(file) {
return fileTypes.includes(file.type);
}
const mapStateToProps = (state, ownProps) => ({ const mapStateToProps = (state, ownProps) => ({
rendererIndex: ownProps.rendererIndex, rendererIndex: ownProps.rendererIndex,
@ -24,7 +14,7 @@ const mapDispatchToProps = (dispatch, ownProps) => ({
onChange: (e) => { onChange: (e) => {
if (e.target.files.length > 0) { if (e.target.files.length > 0) {
const file = e.target.files[0]; const file = e.target.files[0];
if (validFileType(file)) { if (isPicture(file)) {
toBase64(file, 500, 500).then(res => { toBase64(file, 500, 500).then(res => {
dispatch(changeParam(ownProps.rendererIndex, ownProps.paramIndex, res)) dispatch(changeParam(ownProps.rendererIndex, ownProps.paramIndex, res))
}) })

View File

@ -1,8 +1,7 @@
import {connect, useDispatch} from 'react-redux'; import {connect} from 'react-redux';
import {changeStyle, createParam} from "../../actions"; import {changeStyle} from "../../actions";
import StyleList from "../../components/style/StyleList"; import StyleList from "../../components/style/StyleList";
import RendererViewer from "./RendererViewer"; import RendererViewer from "./RendererViewer";
import RendererBlank from "../../components/renderer/RendererBlank";
import RendererBase from "../../components/renderer/RendererBase"; import RendererBase from "../../components/renderer/RendererBase";
import RendererDSJ from "../../components/renderer/RendererDSJ"; import RendererDSJ from "../../components/renderer/RendererDSJ";
import RendererRound from "../../components/renderer/RendererRound"; import RendererRound from "../../components/renderer/RendererRound";

View File

@ -1,4 +1,4 @@
import {getQrcodeData} from "../utils/qrcodeHandler"; import {encodeData} from "../utils/qrcodeHandler";
import {actionTypes} from "../constant/ActionTypes"; import {actionTypes} from "../constant/ActionTypes";
import {QRBTF_URL} from "../constant/References"; import {QRBTF_URL} from "../constant/References";
import RendererBase from "../components/renderer/RendererBase"; import RendererBase from "../components/renderer/RendererBase";
@ -11,7 +11,7 @@ const initialState = {
correctLevel: 0, correctLevel: 0,
textUrl: QRBTF_URL, textUrl: QRBTF_URL,
history: [], history: [],
qrcode: getQrcodeData({text: QRBTF_URL, correctLevel: 0}), qrcode: encodeData({text: QRBTF_URL, correctLevel: 0}),
paramInfo: new Array(16).fill(new Array(16)), paramInfo: new Array(16).fill(new Array(16)),
paramValue: new Array(16).fill(new Array(16)) paramValue: new Array(16).fill(new Array(16))
} }
@ -23,7 +23,7 @@ export default function appReducer(state = initialState, action) {
if (!text || text.length == 0) text = QRBTF_URL; if (!text || text.length == 0) text = QRBTF_URL;
return Object.assign({}, state, { return Object.assign({}, state, {
textUrl: text, textUrl: text,
qrcode: getQrcodeData({text: text, correctLevel: state.correctLevel}) qrcode: encodeData({text: text, correctLevel: state.correctLevel})
}); });
} }
case actionTypes.CHANGE_STYLE: { case actionTypes.CHANGE_STYLE: {
@ -37,7 +37,7 @@ export default function appReducer(state = initialState, action) {
case actionTypes.CHANGE_CORRECT_LEVEL: { case actionTypes.CHANGE_CORRECT_LEVEL: {
return Object.assign({}, state, { return Object.assign({}, state, {
correctLevel: parseInt(action.correctLevel), correctLevel: parseInt(action.correctLevel),
qrcode: getQrcodeData({text: state.textUrl, correctLevel: parseInt(action.correctLevel)}) qrcode: encodeData({text: state.textUrl, correctLevel: parseInt(action.correctLevel)})
}) })
} }
case actionTypes.CREATE_PARAM: { case actionTypes.CREATE_PARAM: {

View File

@ -13,6 +13,7 @@
// http://www.denso-wave.com/qrcode/faqpatent-e.html // http://www.denso-wave.com/qrcode/faqpatent-e.html
// //
//--------------------------------------------------------------------- //---------------------------------------------------------------------
/* eslint-disable */
function QR8bitByte(data) { function QR8bitByte(data) {
this.mode = QRMode.MODE_8BIT_BYTE; this.mode = QRMode.MODE_8BIT_BYTE;
this.data = data; this.data = data;
@ -1270,4 +1271,4 @@ QRBitBuffer.prototype = {
} }
}; };
export default QRCode exports.qrcode = QRCode;

View File

@ -1,4 +1,5 @@
import QRCode from "./qrcode"; import { qrcode as QRCodeEncoder } from "./qrcodeEncoder";
import jsQR from "jsqr";
function extend(target, options) { function extend(target, options) {
for (let name in options) { for (let name in options) {
@ -18,7 +19,7 @@ export var QRPointType = {
VERSION: 7 VERSION: 7
} }
export function getQrcodeData(options) { export function encodeData(options) {
if (!options.text || options.text.length <= 0) return null if (!options.text || options.text.length <= 0) return null
options = extend({ options = extend({
@ -31,7 +32,7 @@ export function getQrcodeData(options) {
foreground : "#000000" foreground : "#000000"
}, options); }, options);
let qrcode = new QRCode(options.typeNumber, options.correctLevel) let qrcode = new QRCodeEncoder(options.typeNumber, options.correctLevel)
qrcode.addData(options.text) qrcode.addData(options.text)
qrcode.make() qrcode.make()
@ -90,3 +91,26 @@ export function getTypeTable(qrcode) {
} }
return typeTable; return typeTable;
} }
export function decodeData(file) {
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
let img = document.createElement('img');
const maxSize = 400;
img.setAttribute('src', URL.createObjectURL(file));
return new Promise((resolve) => {
img.onload = () => {
let rate = Math.min(img.width, img.height) / maxSize;
canvas.width = img.width / rate;
canvas.height = img.height / rate;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
let result = jsQR(
ctx.getImageData(0, 0, canvas.width, canvas.height).data, canvas.width, canvas.height);
resolve(result);
};
})
}

View File

@ -1,8 +1,17 @@
import {ParamTypes} from "../constant/ParamTypes"; import {ParamTypes} from "../constant/ParamTypes";
import React from "react";
let seed = 0; let seed = 0;
const fileTypes =[
'image/jpeg',
'image/pjpeg',
'image/png'
]
export function isPicture(file) {
return fileTypes.includes(file.type);
}
export function srand(sd) { export function srand(sd) {
seed = sd; seed = sd;
} }