Add OCR tool to media editing modal (#11566)
This commit is contained in:
parent
f178a01c11
commit
28636f43e4
10 changed files with 275 additions and 34 deletions
|
@ -4,6 +4,7 @@ import UploadProgressContainer from '../containers/upload_progress_container';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import UploadContainer from '../containers/upload_container';
|
||||
import SensitiveButtonContainer from '../containers/sensitive_button_container';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default class UploadForm extends ImmutablePureComponent {
|
||||
|
||||
|
@ -16,7 +17,7 @@ export default class UploadForm extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<div className='compose-form__upload-wrapper'>
|
||||
<UploadProgressContainer />
|
||||
<UploadProgressContainer icon='upload' message={<FormattedMessage id='upload_progress.label' defaultMessage='Uploading…' />} />
|
||||
|
||||
<div className='compose-form__uploads-wrapper'>
|
||||
{mediaIds.map(id => (
|
||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
||||
export default class UploadProgress extends React.PureComponent {
|
||||
|
@ -10,10 +9,12 @@ export default class UploadProgress extends React.PureComponent {
|
|||
static propTypes = {
|
||||
active: PropTypes.bool,
|
||||
progress: PropTypes.number,
|
||||
icon: PropTypes.string.isRequired,
|
||||
message: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { active, progress } = this.props;
|
||||
const { active, progress, icon, message } = this.props;
|
||||
|
||||
if (!active) {
|
||||
return null;
|
||||
|
@ -22,11 +23,11 @@ export default class UploadProgress extends React.PureComponent {
|
|||
return (
|
||||
<div className='upload-progress'>
|
||||
<div className='upload-progress__icon'>
|
||||
<Icon id='upload' />
|
||||
<Icon id={icon} />
|
||||
</div>
|
||||
|
||||
<div className='upload-progress__message'>
|
||||
<FormattedMessage id='upload_progress.label' defaultMessage='Uploading...' />
|
||||
{message}
|
||||
|
||||
<div className='upload-progress__backdrop'>
|
||||
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}>
|
||||
|
|
|
@ -10,6 +10,11 @@ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
|||
import IconButton from 'mastodon/components/icon_button';
|
||||
import Button from 'mastodon/components/button';
|
||||
import Video from 'mastodon/features/video';
|
||||
import { TesseractWorker } from 'tesseract.js';
|
||||
import Textarea from 'react-textarea-autosize';
|
||||
import UploadProgress from 'mastodon/features/compose/components/upload_progress';
|
||||
import CharacterCounter from 'mastodon/features/compose/components/character_counter';
|
||||
import { length } from 'stringz';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
|
@ -29,6 +34,12 @@ const mapDispatchToProps = (dispatch, { id }) => ({
|
|||
|
||||
});
|
||||
|
||||
const removeExtraLineBreaks = str => str.replace(/\n\n/g, '******')
|
||||
.replace(/\n/g, ' ')
|
||||
.replace(/\*\*\*\*\*\*/g, '\n\n');
|
||||
|
||||
const assetHost = process.env.CDN_HOST || '';
|
||||
|
||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class FocalPointModal extends ImmutablePureComponent {
|
||||
|
@ -47,6 +58,7 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
dragging: false,
|
||||
description: '',
|
||||
dirty: false,
|
||||
progress: 0,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
|
@ -133,9 +145,27 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
this.node = c;
|
||||
}
|
||||
|
||||
handleTextDetection = () => {
|
||||
const { media } = this.props;
|
||||
|
||||
const worker = new TesseractWorker({
|
||||
workerPath: `${assetHost}/packs/ocr/worker.min.js`,
|
||||
corePath: `${assetHost}/packs/ocr/tesseract-core.wasm.js`,
|
||||
langPath: `${assetHost}/ocr/lang-data`,
|
||||
});
|
||||
|
||||
this.setState({ detecting: true });
|
||||
|
||||
worker.recognize(media.get('url'))
|
||||
.progress(({ progress }) => this.setState({ progress }))
|
||||
.finally(() => worker.terminate())
|
||||
.then(({ text }) => this.setState({ description: removeExtraLineBreaks(text), dirty: true, detecting: false }))
|
||||
.catch(() => this.setState({ detecting: false }));
|
||||
}
|
||||
|
||||
render () {
|
||||
const { media, intl, onClose } = this.props;
|
||||
const { x, y, dragging, description, dirty } = this.state;
|
||||
const { x, y, dragging, description, dirty, detecting, progress } = this.state;
|
||||
|
||||
const width = media.getIn(['meta', 'original', 'width']) || null;
|
||||
const height = media.getIn(['meta', 'original', 'height']) || null;
|
||||
|
@ -158,15 +188,27 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
|
||||
<label className='setting-text-label' htmlFor='upload-modal__description'><FormattedMessage id='upload_form.description' defaultMessage='Describe for the visually impaired' /></label>
|
||||
|
||||
<textarea
|
||||
id='upload-modal__description'
|
||||
className='setting-text light'
|
||||
value={description}
|
||||
onChange={this.handleChange}
|
||||
autoFocus
|
||||
/>
|
||||
<div className='setting-text__wrapper'>
|
||||
<Textarea
|
||||
id='upload-modal__description'
|
||||
className='setting-text light'
|
||||
value={detecting ? '…' : description}
|
||||
onChange={this.handleChange}
|
||||
disabled={detecting}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
<Button disabled={!dirty} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} />
|
||||
<div className='setting-text__modifiers'>
|
||||
<UploadProgress progress={progress * 100} active={detecting} icon='file-text-o' message={<FormattedMessage id='upload_modal.analyzing_picture' defaultMessage='Analyzing picture…' />} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='setting-text__toolbar'>
|
||||
<button disabled={detecting || media.get('type') !== 'image'} className='link-button' onClick={this.handleTextDetection}><FormattedMessage id='upload_modal.detect_text' defaultMessage='Detect text from picture' /></button>
|
||||
<CharacterCounter max={420} text={detecting ? '' : description} />
|
||||
</div>
|
||||
|
||||
<Button disabled={!dirty || detecting || length(description) > 420} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} />
|
||||
</div>
|
||||
|
||||
<div className='report-modal__statuses'>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue