split app into more components
This commit is contained in:
parent
46a1f7e2bb
commit
577f982e28
13
src/components/AnswerList/AnswerList.scss
Normal file
13
src/components/AnswerList/AnswerList.scss
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.AnswerList {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-left: 22px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 4px 22px 4px 0;
|
||||||
|
}
|
||||||
|
}
|
16
src/components/AnswerList/AnswerList.tsx
Normal file
16
src/components/AnswerList/AnswerList.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import './AnswerList.scss';
|
||||||
|
|
||||||
|
interface IAnswerListProps {
|
||||||
|
answers: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AnswerList(props: IAnswerListProps): JSX.Element {
|
||||||
|
|
||||||
|
const answers = props.answers.map(a => <p key={a}>{a}</p>);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="AnswerList">
|
||||||
|
{answers}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
grid-template-rows: 1fr 1fr 1/2fr;
|
grid-template-rows: 1fr;
|
||||||
grid-column-gap: 0;
|
grid-column-gap: 0;
|
||||||
grid-row-gap: 0;
|
grid-row-gap: 0;
|
||||||
}
|
}
|
||||||
@ -11,58 +11,3 @@
|
|||||||
.Answer {
|
.Answer {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.fileUploader {
|
|
||||||
width: 180px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fileContainer {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: none;
|
|
||||||
border-radius: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
text-align: center;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
p {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin: 10px 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.UploadSection {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ConfirmButton {
|
|
||||||
width: 180px;
|
|
||||||
height: 30px;
|
|
||||||
|
|
||||||
padding: 6px 23px;
|
|
||||||
background: #3f4257;
|
|
||||||
border-radius: 30px;
|
|
||||||
color: white;
|
|
||||||
font-weight: 300;
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 10px 10px;
|
|
||||||
transition: all 0.2s ease-in;
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
test('renders learn react link', () => {
|
|
||||||
render(<App />);
|
|
||||||
const linkElement = screen.getByText(/learn react/i);
|
|
||||||
expect(linkElement).toBeInTheDocument();
|
|
||||||
});
|
|
@ -1,22 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import ImageUploader from 'react-images-upload';
|
import AnswerList from "../AnswerList/AnswerList";
|
||||||
import MultiCrops from 'react-multi-crops'
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
import Logo from '../Logo/Logo';
|
|
||||||
// @ts-ignore
|
|
||||||
import Board from '../Board/Board';
|
import Board from '../Board/Board';
|
||||||
|
import Logo from "../Logo/Logo";
|
||||||
|
import Menu from '../Menu/Menu';
|
||||||
|
import Randomizer from "../Randomizer/Randomizer";
|
||||||
|
import Recognizer from "../Recognizer/Recognizer";
|
||||||
|
import Title from "../Title/Title";
|
||||||
|
|
||||||
import './App.scss';
|
import './App.scss';
|
||||||
// @ts-ignore
|
|
||||||
import {findTextRegions} from "../../helpers/findTextRegions";
|
|
||||||
// @ts-ignore
|
|
||||||
import {
|
|
||||||
recognizeTextOnImage,
|
|
||||||
// @ts-ignore
|
|
||||||
recognizeTextOnImageGrid
|
|
||||||
} from "../../helpers/recognizeTextOnImage";
|
|
||||||
|
|
||||||
export type Point = {
|
export type Point = {
|
||||||
x: number;
|
x: number;
|
||||||
@ -33,194 +25,127 @@ export type Solution = {
|
|||||||
key: string;
|
key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Game = {
|
export type Game = {
|
||||||
|
cells: Array<Array<string>>;
|
||||||
|
solutions: Array<Solution>;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
catchword: string;
|
catchword: string;
|
||||||
cells: Array<Array<string>>;
|
|
||||||
solutions: Array<Solution>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAppProps {
|
interface IAppProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAppState {
|
export interface IAppState {
|
||||||
image: HTMLImageElement | null,
|
game: Game;
|
||||||
selections: Array<any>;
|
mode: string;
|
||||||
cells: Array<Array<string>>;
|
ready: boolean;
|
||||||
solutions: Array<Solution>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class App extends React.Component<IAppProps, IAppState> {
|
export default class App extends React.Component<IAppProps, IAppState> {
|
||||||
private game: Game;
|
|
||||||
|
|
||||||
constructor(props: IAppProps) {
|
constructor(props: IAppProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const gameId = (Math.trunc(Math.random() * 100) % 2) + 1;
|
|
||||||
this.game = require(`../../constants/${gameId}.json`);
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
image: null,
|
game: {
|
||||||
selections: [],
|
|
||||||
cells: [],
|
cells: [],
|
||||||
solutions: [],
|
solutions: [],
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
catchword: "",
|
||||||
|
},
|
||||||
|
mode: "",
|
||||||
|
ready: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTakePhoto(pictures: any[], _: any[]): void {
|
handleGameModeSelected(mode: string) {
|
||||||
const image = document.createElement('img');
|
const ready = mode === "reset" ? false : this.state.ready;
|
||||||
image.src = URL.createObjectURL(pictures[0]);
|
this.setState({...this.state, mode, ready});
|
||||||
image.onload = () => this.setState({...this.state, image});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChangeCoordinate(_: any, __: any, selections: any) {
|
handleRecognitionFinished(game: Game) {
|
||||||
this.setState({...this.state, selections});
|
this.setState({...this.state, game, ready: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleConfirmClick() {
|
renderTitle(): JSX.Element {
|
||||||
if (!this.state.image || !this.state.selections.length)
|
if (!this.state.ready) return <div />;
|
||||||
return;
|
|
||||||
|
|
||||||
const mainCanvas = document.createElement('canvas');
|
return (
|
||||||
const mainContext = mainCanvas.getContext('2d');
|
<Title
|
||||||
mainCanvas.width = this.state.image.width;
|
title={this.state.game.title}
|
||||||
mainCanvas.height = this.state.image.height;
|
description={this.state.game.description}
|
||||||
mainContext?.drawImage(this.state.image, 0, 0);
|
/>
|
||||||
|
|
||||||
const areas = this.state.selections.map(s => s.width * s.height);
|
|
||||||
const gridIndex = areas.indexOf(Math.max(...areas));
|
|
||||||
const gridSelection = this.state.selections.splice(gridIndex, 1)[0];
|
|
||||||
const gridImage = mainContext?.getImageData(
|
|
||||||
gridSelection.x,
|
|
||||||
gridSelection.y,
|
|
||||||
gridSelection.width,
|
|
||||||
gridSelection.height
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!gridImage) {
|
|
||||||
console.log("gridImage is empty");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const gridTextRegions = findTextRegions(gridImage);
|
renderLogo(): JSX.Element {
|
||||||
if (!gridTextRegions || !gridTextRegions.grid) {
|
return <Logo />;
|
||||||
console.log("gridRegions is empty");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let reads = [];
|
renderMenu(): JSX.Element {
|
||||||
reads.push(this.handleGridTextRegionsReadyToRead(gridTextRegions.grid));
|
return (
|
||||||
|
<Menu
|
||||||
for (let s of this.state.selections) {
|
onGameModeSelected={this.handleGameModeSelected.bind(this)}
|
||||||
const tempData = mainContext?.getImageData(s.x, s.y, s.width, s.height);
|
/>
|
||||||
if (!tempData) {
|
);
|
||||||
console.log("tempData is empty");
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reads.push(this.handleStateSelectionsReadyToRead(tempData, s))
|
renderBoard(): JSX.Element {
|
||||||
|
if (!this.state.ready) return <div />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Board
|
||||||
|
cells={this.state.game.cells}
|
||||||
|
solutions={this.state.game.solutions}
|
||||||
|
cellSize={60}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all(reads).then(() => {
|
renderRandomizer(): JSX.Element {
|
||||||
// free resources
|
if (!(!this.state.ready && this.state.mode === "randomize")) return <div />;
|
||||||
if (this.state.image) {
|
|
||||||
URL.revokeObjectURL(this.state.image.src);
|
return (
|
||||||
mainCanvas.width = 0;
|
<Randomizer
|
||||||
mainCanvas.height = 0;
|
onRecognitionFinished={this.handleRecognitionFinished.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
renderRecognizer(): JSX.Element {
|
||||||
...this.state,
|
if (!(!this.state.ready && this.state.mode === "recognize")) return <div />;
|
||||||
image: null,
|
|
||||||
selections: [],
|
return (
|
||||||
});
|
<Recognizer
|
||||||
});
|
onRecognitionFinished={this.handleRecognitionFinished.bind(this)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleGridTextRegionsReadyToRead(grid: any) {
|
renderAnswerList(): JSX.Element {
|
||||||
const textGrid = await recognizeTextOnImageGrid(grid);
|
if (!this.state.ready) return <></>;
|
||||||
let cells = [];
|
|
||||||
|
|
||||||
for (let row of textGrid) {
|
const answers = this.state.game.solutions.map(s => s.key);
|
||||||
let line = [];
|
|
||||||
|
|
||||||
for (let letter of row) {
|
return (
|
||||||
line.push(letter.text);
|
<AnswerList
|
||||||
}
|
answers={answers}
|
||||||
|
/>
|
||||||
cells.push(line);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({...this.state, cells});
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleStateSelectionsReadyToRead(data: ImageData, s: any) {
|
|
||||||
const tempCanvas = document.createElement('canvas');
|
|
||||||
const tempContext = tempCanvas.getContext('2d');
|
|
||||||
tempCanvas.width = s.width;
|
|
||||||
tempCanvas.height = s.height;
|
|
||||||
tempContext?.putImageData(data, 0, 0);
|
|
||||||
|
|
||||||
const text = await recognizeTextOnImage(tempCanvas);
|
|
||||||
let solutions = this.state.solutions.slice();
|
|
||||||
|
|
||||||
for (let key of text.split("\n")) {
|
|
||||||
solutions.push({key, selection: {start: {x: 0, y: 0}, end: {x: 0, y: 0}}});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({...this.state, solutions});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderAnswers(): Array<JSX.Element> {
|
|
||||||
return this.game.solutions.map(s => s.key).map(k =>
|
|
||||||
<p key={k} className="Answer">{k}</p>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<div />
|
<div /> {this.renderLogo()} <div />
|
||||||
<div>
|
<div /> {this.renderMenu()} <div />
|
||||||
<Logo />
|
<div /> {this.renderTitle()} <div />
|
||||||
<div>
|
<div /> {this.renderBoard()} <div />
|
||||||
<p>{this.game.title}</p>
|
<div /> {this.renderRecognizer()} <div />
|
||||||
<p>{this.game.description}</p>
|
<div /> {this.renderRandomizer()} <div />
|
||||||
</div>
|
<div /> {this.renderAnswerList()} <div />
|
||||||
</div>
|
|
||||||
<div />
|
|
||||||
<div style={{paddingLeft: '300px'}}>
|
|
||||||
<div className="UploadSection">
|
|
||||||
<ImageUploader
|
|
||||||
withIcon={false}
|
|
||||||
buttonText='Wrzuć zdjęcie!'
|
|
||||||
onChange={this.handleTakePhoto.bind(this)}
|
|
||||||
imgExtension={['.jpg', '.jpeg', '.png']}
|
|
||||||
maxFileSize={5242880 * 5} // 5MB * 5
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="ConfirmButton"
|
|
||||||
onClick={this.handleConfirmClick.bind(this)}
|
|
||||||
style={{visibility: this.state.image ? 'visible' : 'hidden'}}
|
|
||||||
>Potwierdź</button>
|
|
||||||
</div>
|
|
||||||
<MultiCrops
|
|
||||||
src={this.state.image?.src || ''}
|
|
||||||
width={this.state.image?.width}
|
|
||||||
coordinates={this.state.selections}
|
|
||||||
onChange={this.handleChangeCoordinate.bind(this)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Board
|
|
||||||
cells={this.game.cells}
|
|
||||||
solutions={this.game.solutions}
|
|
||||||
cellSize={60}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div style={{paddingLeft: '30px'}}>
|
|
||||||
{this.renderAnswers()}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,9 @@ export default class Board extends React.Component<IBoardProps, IBoardState> {
|
|||||||
let lines = this.state.answers.map((s, n) =>
|
let lines = this.state.answers.map((s, n) =>
|
||||||
this.renderLine(s, n));
|
this.renderLine(s, n));
|
||||||
|
|
||||||
const gridTemplateColumns = `repeat(${this.props.cells[0].length}, ${this.props.cellSize}px)`;
|
const x = this.props.cells[0].length;
|
||||||
|
const y = this.props.cellSize;
|
||||||
|
const gridTemplateColumns = `repeat(${x}, ${y}px)`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Container">
|
<div className="Container">
|
||||||
|
@ -21,6 +21,7 @@ interface ILineProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Line(props: ILineProps): JSX.Element {
|
export default function Line(props: ILineProps): JSX.Element {
|
||||||
|
|
||||||
const [m1, sqrt2] = [props.cellSize, Math.sqrt(2)];
|
const [m1, sqrt2] = [props.cellSize, Math.sqrt(2)];
|
||||||
const [m2, m4, m8] = [m1 / 2, m1 / 4, m1 / 8];
|
const [m2, m4, m8] = [m1 / 2, m1 / 4, m1 / 8];
|
||||||
const [startX, endX] = [m1 * props.selection.start.x, m1 * props.selection.end.x];
|
const [startX, endX] = [m1 * props.selection.start.x, m1 * props.selection.end.x];
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
.Logo {
|
.Logo {
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
|
@ -2,6 +2,7 @@ import Wide from './crosski_wide.svg'
|
|||||||
import './Logo.scss';
|
import './Logo.scss';
|
||||||
|
|
||||||
export default function Logo(): JSX.Element {
|
export default function Logo(): JSX.Element {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Logo">
|
<div className="Logo">
|
||||||
<img alt="Logo" src={Wide}/>
|
<img alt="Logo" src={Wide}/>
|
||||||
|
9
src/components/Menu/Menu.scss
Normal file
9
src/components/Menu/Menu.scss
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
.Menu {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
}
|
20
src/components/Menu/Menu.tsx
Normal file
20
src/components/Menu/Menu.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import './Menu.scss';
|
||||||
|
|
||||||
|
interface IMenuProps {
|
||||||
|
onGameModeSelected: (mode: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Menu(props: IMenuProps): JSX.Element {
|
||||||
|
|
||||||
|
const randomize = () => props.onGameModeSelected('randomize');
|
||||||
|
const recognize = () => props.onGameModeSelected('recognize');
|
||||||
|
const reset = () => props.onGameModeSelected('reset');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Menu">
|
||||||
|
<button onClick={randomize}>randomize</button>
|
||||||
|
<button onClick={reset}>reset</button>
|
||||||
|
<button onClick={recognize}>recognize</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
14
src/components/Randomizer/Randomizer.tsx
Normal file
14
src/components/Randomizer/Randomizer.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import {Game} from "../App/App";
|
||||||
|
|
||||||
|
interface IRandomizerProps {
|
||||||
|
onRecognitionFinished: (game: Game) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Randomizer(props: IRandomizerProps): JSX.Element {
|
||||||
|
|
||||||
|
const gameId = (Math.trunc(Math.random() * 100) % 2) + 1;
|
||||||
|
const game = require(`../../constants/${gameId}.json`) as Game;
|
||||||
|
props.onRecognitionFinished(game);
|
||||||
|
|
||||||
|
return <div />;
|
||||||
|
}
|
55
src/components/Recognizer/Recognizer.scss
Normal file
55
src/components/Recognizer/Recognizer.scss
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
.Recognizer {
|
||||||
|
display: flex;
|
||||||
|
padding-top: 20px;
|
||||||
|
flex-flow: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileUploader {
|
||||||
|
width: 180px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileContainer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
p {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 10px 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ConfirmButton {
|
||||||
|
width: 180px;
|
||||||
|
height: 30px;
|
||||||
|
|
||||||
|
padding: 6px 23px;
|
||||||
|
background: #3f4257;
|
||||||
|
border-radius: 30px;
|
||||||
|
color: white;
|
||||||
|
font-weight: 300;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 10px 10px;
|
||||||
|
transition: all 0.2s ease-in;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
173
src/components/Recognizer/Recognizer.tsx
Normal file
173
src/components/Recognizer/Recognizer.tsx
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ImageUploader from "react-images-upload";
|
||||||
|
import MultiCrops from "react-multi-crops";
|
||||||
|
import {findTextRegions} from "../../helpers/findTextRegions";
|
||||||
|
import {
|
||||||
|
recognizeTextOnImage,
|
||||||
|
recognizeTextOnImageGrid
|
||||||
|
} from "../../helpers/recognizeTextOnImage";
|
||||||
|
|
||||||
|
import {Game, Solution} from "../App/App";
|
||||||
|
|
||||||
|
import "./Recognizer.scss";
|
||||||
|
|
||||||
|
interface IRecognizerProps {
|
||||||
|
onRecognitionFinished: (game: Game) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IRecognizerState {
|
||||||
|
image: HTMLImageElement | null,
|
||||||
|
cells: Array<Array<string>>;
|
||||||
|
solutions: Array<Solution>;
|
||||||
|
coordinates: Array<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Recognizer extends React.Component<IRecognizerProps, IRecognizerState> {
|
||||||
|
|
||||||
|
constructor(props: IRecognizerProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
image: null,
|
||||||
|
cells: [],
|
||||||
|
solutions: [],
|
||||||
|
coordinates: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTakePhoto(files: any[], _: any[]): void {
|
||||||
|
const image = document.createElement('img');
|
||||||
|
image.src = URL.createObjectURL(files[0]);
|
||||||
|
image.onload = () => this.setState({...this.state, image});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChangeCoordinate(_: any, __: any, selections: any) {
|
||||||
|
this.setState({...this.state, coordinates: selections});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleConfirmClick() {
|
||||||
|
if (!this.state.image || !this.state.coordinates.length)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const mainCanvas = document.createElement('canvas');
|
||||||
|
const mainContext = mainCanvas.getContext('2d');
|
||||||
|
mainCanvas.width = this.state.image.width;
|
||||||
|
mainCanvas.height = this.state.image.height;
|
||||||
|
mainContext?.drawImage(this.state.image, 0, 0);
|
||||||
|
|
||||||
|
const areas = this.state.coordinates.map(s => s.width * s.height);
|
||||||
|
const gridIndex = areas.indexOf(Math.max(...areas));
|
||||||
|
const gridSelection = this.state.coordinates.splice(gridIndex, 1)[0];
|
||||||
|
const gridImage = mainContext?.getImageData(
|
||||||
|
gridSelection.x,
|
||||||
|
gridSelection.y,
|
||||||
|
gridSelection.width,
|
||||||
|
gridSelection.height
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!gridImage) {
|
||||||
|
console.log("gridImage is empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gridTextRegions = findTextRegions(gridImage);
|
||||||
|
if (!gridTextRegions || !gridTextRegions.grid) {
|
||||||
|
console.log("gridRegions is empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reads = [];
|
||||||
|
reads.push(this.handleGridTextRegionsReadyToRead(gridTextRegions.grid));
|
||||||
|
|
||||||
|
for (let s of this.state.coordinates) {
|
||||||
|
const tempData = mainContext?.getImageData(s.x, s.y, s.width, s.height);
|
||||||
|
if (!tempData) {
|
||||||
|
console.log("tempData is empty");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
reads.push(this.handleStateSelectionsReadyToRead(tempData, s))
|
||||||
|
}
|
||||||
|
|
||||||
|
Promise.all(reads).then(() => {
|
||||||
|
// free resources
|
||||||
|
if (this.state.image) {
|
||||||
|
URL.revokeObjectURL(this.state.image.src);
|
||||||
|
mainCanvas.width = 0;
|
||||||
|
mainCanvas.height = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
...this.state,
|
||||||
|
image: null,
|
||||||
|
coordinates: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.props.onRecognitionFinished({
|
||||||
|
...this.state,
|
||||||
|
title: "Loaded game",
|
||||||
|
description: "Good luck!",
|
||||||
|
catchword: "",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleGridTextRegionsReadyToRead(grid: any) {
|
||||||
|
const textGrid = await recognizeTextOnImageGrid(grid);
|
||||||
|
let cells = [];
|
||||||
|
|
||||||
|
for (let row of textGrid) {
|
||||||
|
let line = [];
|
||||||
|
|
||||||
|
for (let letter of row) {
|
||||||
|
line.push(letter.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
cells.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({...this.state, cells});
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleStateSelectionsReadyToRead(data: ImageData, s: any) {
|
||||||
|
const tempCanvas = document.createElement('canvas');
|
||||||
|
const tempContext = tempCanvas.getContext('2d');
|
||||||
|
tempCanvas.width = s.width;
|
||||||
|
tempCanvas.height = s.height;
|
||||||
|
tempContext?.putImageData(data, 0, 0);
|
||||||
|
|
||||||
|
const text = await recognizeTextOnImage(tempCanvas);
|
||||||
|
let solutions = this.state.solutions.slice();
|
||||||
|
|
||||||
|
for (let key of text.split("\n")) {
|
||||||
|
solutions.push({key, selection: {start: {x: 0, y: 0}, end: {x: 0, y: 0}}});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({...this.state, solutions});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="Recognizer">
|
||||||
|
<ImageUploader
|
||||||
|
withIcon={false}
|
||||||
|
buttonText='Wybierz zdjęcie'
|
||||||
|
onChange={this.handleTakePhoto.bind(this)}
|
||||||
|
imgExtension={['.jpg', '.jpeg', '.png']}
|
||||||
|
maxFileSize={5242880 * 5} // 5MB * 5
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="ConfirmButton"
|
||||||
|
onClick={this.handleConfirmClick.bind(this)}
|
||||||
|
style={{visibility: this.state.image ? 'visible' : 'hidden'}}
|
||||||
|
>Potwierdź</button>
|
||||||
|
<MultiCrops
|
||||||
|
src={this.state.image?.src || ''}
|
||||||
|
width={this.state.image?.width}
|
||||||
|
coordinates={this.state.coordinates}
|
||||||
|
onChange={this.handleChangeCoordinate.bind(this)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
8
src/components/Title/Title.scss
Normal file
8
src/components/Title/Title.scss
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.Title {
|
||||||
|
text-align: center;
|
||||||
|
margin: 15px 80px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
}
|
16
src/components/Title/Title.tsx
Normal file
16
src/components/Title/Title.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import './Title.scss';
|
||||||
|
|
||||||
|
interface ITitleProps {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Title(props: ITitleProps): JSX.Element {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Title">
|
||||||
|
<p>{props.title}</p>
|
||||||
|
<span>{props.description}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -11,4 +11,8 @@ html, body {
|
|||||||
sans-serif;
|
sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
|
||||||
|
background: #FFEFBA; /* fallback for old browsers */
|
||||||
|
background: -webkit-linear-gradient(to right, #FFFFFF, #FFEFBA); /* Chrome 10-25, Safari 5.1-6 */
|
||||||
|
background: linear-gradient(to right, #FFFFFF, #FFEFBA); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user