0
0
Fork 0

Add OCR tool to media editing modal (#11566)

This commit is contained in:
Eugen Rochko 2019-08-15 15:13:26 +02:00 committed by GitHub
parent f178a01c11
commit 28636f43e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 275 additions and 34 deletions

View file

@ -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 => (

View file

@ -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) }}>

View file

@ -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'>

View file

@ -3,6 +3,27 @@
-ms-overflow-style: -ms-autohiding-scrollbar;
}
.link-button {
display: block;
font-size: 15px;
line-height: 20px;
color: $ui-highlight-color;
border: 0;
background: transparent;
padding: 0;
cursor: pointer;
&:hover,
&:active {
text-decoration: underline;
}
&:disabled {
color: $ui-primary-color;
cursor: default;
}
}
.button {
background-color: $ui-highlight-color;
border: 10px none;
@ -637,18 +658,6 @@
.character-counter__wrapper {
align-self: center;
margin-right: 4px;
.character-counter {
cursor: default;
font-family: $font-sans-serif, sans-serif;
font-size: 14px;
font-weight: 600;
color: $lighter-text-color;
&.character-counter--over {
color: $warning-red;
}
}
}
}
@ -665,6 +674,18 @@
}
}
.character-counter {
cursor: default;
font-family: $font-sans-serif, sans-serif;
font-size: 14px;
font-weight: 600;
color: $lighter-text-color;
&.character-counter--over {
color: $warning-red;
}
}
.no-reduce-motion .spoiler-input {
transition: height 0.4s ease, opacity 0.4s ease;
}
@ -4555,16 +4576,48 @@ a.status-card.compact:hover {
padding: 10px;
font-family: inherit;
font-size: 14px;
resize: vertical;
resize: none;
border: 0;
outline: 0;
border-radius: 4px;
border: 1px solid $ui-secondary-color;
margin-bottom: 20px;
min-height: 100px;
max-height: 50vh;
margin-bottom: 10px;
&:focus {
border: 1px solid darken($ui-secondary-color, 8%);
}
&__wrapper {
background: $white;
border: 1px solid $ui-secondary-color;
margin-bottom: 10px;
border-radius: 4px;
.setting-text {
border: 0;
margin-bottom: 0;
border-radius: 0;
&:focus {
border: 0;
}
}
&__modifiers {
color: $inverted-text-color;
font-family: inherit;
font-size: 14px;
background: $white;
}
}
&__toolbar {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
}
.setting-text-label {