mirror of
https://github.com/mastodon/mastodon.git
synced 2024-11-27 23:55:52 +00:00
[Glitch] Add OCR tool to media editing modal
Port 28636f43e4
to glitch-soc
Signed-off-by: Thibaut Girka <thib@sitedethib.com>
This commit is contained in:
parent
066034c62e
commit
41c7fec796
|
@ -10,6 +10,11 @@ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||||
import IconButton from 'flavours/glitch/components/icon_button';
|
import IconButton from 'flavours/glitch/components/icon_button';
|
||||||
import Button from 'flavours/glitch/components/button';
|
import Button from 'flavours/glitch/components/button';
|
||||||
import Video from 'flavours/glitch/features/video';
|
import Video from 'flavours/glitch/features/video';
|
||||||
|
import { TesseractWorker } from 'tesseract.js';
|
||||||
|
import Textarea from 'react-textarea-autosize';
|
||||||
|
import UploadProgress from 'flavours/glitch/features/compose/components/upload_progress';
|
||||||
|
import CharacterCounter from 'flavours/glitch/features/compose/components/character_counter';
|
||||||
|
import { length } from 'stringz';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
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)
|
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class FocalPointModal extends ImmutablePureComponent {
|
class FocalPointModal extends ImmutablePureComponent {
|
||||||
|
@ -47,6 +58,7 @@ class FocalPointModal extends ImmutablePureComponent {
|
||||||
dragging: false,
|
dragging: false,
|
||||||
description: '',
|
description: '',
|
||||||
dirty: false,
|
dirty: false,
|
||||||
|
progress: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
|
@ -133,9 +145,27 @@ class FocalPointModal extends ImmutablePureComponent {
|
||||||
this.node = c;
|
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 () {
|
render () {
|
||||||
const { media, intl, onClose } = this.props;
|
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 width = media.getIn(['meta', 'original', 'width']) || null;
|
||||||
const height = media.getIn(['meta', 'original', 'height']) || 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>
|
<label className='setting-text-label' htmlFor='upload-modal__description'><FormattedMessage id='upload_form.description' defaultMessage='Describe for the visually impaired' /></label>
|
||||||
|
|
||||||
<textarea
|
<div className='setting-text__wrapper'>
|
||||||
id='upload-modal__description'
|
<Textarea
|
||||||
className='setting-text light'
|
id='upload-modal__description'
|
||||||
value={description}
|
className='setting-text light'
|
||||||
onChange={this.handleChange}
|
value={detecting ? '…' : description}
|
||||||
autoFocus
|
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>
|
||||||
|
|
||||||
<div className='report-modal__statuses'>
|
<div className='report-modal__statuses'>
|
||||||
|
|
|
@ -2,6 +2,18 @@
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 .composer--spoiler {
|
.no-reduce-motion .composer--spoiler {
|
||||||
transition: height 0.4s ease, opacity 0.4s ease;
|
transition: height 0.4s ease, opacity 0.4s ease;
|
||||||
}
|
}
|
||||||
|
@ -589,13 +601,6 @@
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
& > .character-counter {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 16px 0 8px;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .primary {
|
& > .primary {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
@ -3,6 +3,27 @@
|
||||||
-ms-overflow-style: -ms-autohiding-scrollbar;
|
-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 {
|
.button {
|
||||||
background-color: darken($ui-highlight-color, 3%);
|
background-color: darken($ui-highlight-color, 3%);
|
||||||
border: 10px none;
|
border: 10px none;
|
||||||
|
|
|
@ -565,16 +565,48 @@
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
resize: vertical;
|
resize: none;
|
||||||
border: 0;
|
border: 0;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid $ui-secondary-color;
|
border: 1px solid $ui-secondary-color;
|
||||||
margin-bottom: 20px;
|
min-height: 100px;
|
||||||
|
max-height: 50vh;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border: 1px solid darken($ui-secondary-color, 8%);
|
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 {
|
.setting-text-label {
|
||||||
|
|
Loading…
Reference in a new issue