[Glitch] Add OCR tool to media editing modal
Port 28636f43e4b0c04befa243b847c38e81c90f1289 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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user