about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/features/ui/components/doodle_modal.js')
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/doodle_modal.js614
1 files changed, 0 insertions, 614 deletions
diff --git a/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js b/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js
deleted file mode 100644
index 0d10204fc..000000000
--- a/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js
+++ /dev/null
@@ -1,614 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import Button from 'flavours/glitch/components/button';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import Atrament from 'atrament'; // the doodling library
-import { connect } from 'react-redux';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { doodleSet, uploadCompose } from 'flavours/glitch/actions/compose';
-import IconButton from 'flavours/glitch/components/icon_button';
-import { debounce, mapValues } from 'lodash';
-import classNames from 'classnames';
-
-// palette nicked from MyPaint, CC0
-const palette = [
-  ['rgb(  0,    0,    0)', 'Black'],
-  ['rgb( 38,   38,   38)', 'Gray 15'],
-  ['rgb( 77,   77,   77)', 'Grey 30'],
-  ['rgb(128,  128,  128)', 'Grey 50'],
-  ['rgb(171,  171,  171)', 'Grey 67'],
-  ['rgb(217,  217,  217)', 'Grey 85'],
-  ['rgb(255,  255,  255)', 'White'],
-  ['rgb(128,    0,    0)', 'Maroon'],
-  ['rgb(209,    0,    0)', 'English-red'],
-  ['rgb(255,   54,   34)', 'Tomato'],
-  ['rgb(252,   60,    3)', 'Orange-red'],
-  ['rgb(255,  140,  105)', 'Salmon'],
-  ['rgb(252,  232,   32)', 'Cadium-yellow'],
-  ['rgb(243,  253,   37)', 'Lemon yellow'],
-  ['rgb(121,    5,   35)', 'Dark crimson'],
-  ['rgb(169,   32,   62)', 'Deep carmine'],
-  ['rgb(255,  140,    0)', 'Orange'],
-  ['rgb(255,  168,   18)', 'Dark tangerine'],
-  ['rgb(217,  144,   88)', 'Persian orange'],
-  ['rgb(194,  178,  128)', 'Sand'],
-  ['rgb(255,  229,  180)', 'Peach'],
-  ['rgb(100,   54,   46)', 'Bole'],
-  ['rgb(108,   41,   52)', 'Dark cordovan'],
-  ['rgb(163,   65,   44)', 'Chestnut'],
-  ['rgb(228,  136,  100)', 'Dark salmon'],
-  ['rgb(255,  195,  143)', 'Apricot'],
-  ['rgb(255,  219,  188)', 'Unbleached silk'],
-  ['rgb(242,  227,  198)', 'Straw'],
-  ['rgb( 53,   19,   13)', 'Bistre'],
-  ['rgb( 84,   42,   14)', 'Dark chocolate'],
-  ['rgb(102,   51,   43)', 'Burnt sienna'],
-  ['rgb(184,   66,    0)', 'Sienna'],
-  ['rgb(216,  153,   12)', 'Yellow ochre'],
-  ['rgb(210,  180,  140)', 'Tan'],
-  ['rgb(232,  204,  144)', 'Dark wheat'],
-  ['rgb(  0,   49,   83)', 'Prussian blue'],
-  ['rgb( 48,   69,  119)', 'Dark grey blue'],
-  ['rgb(  0,   71,  171)', 'Cobalt blue'],
-  ['rgb( 31,  117,  254)', 'Blue'],
-  ['rgb(120,  180,  255)', 'Bright french blue'],
-  ['rgb(171,  200,  255)', 'Bright steel blue'],
-  ['rgb(208,  231,  255)', 'Ice blue'],
-  ['rgb( 30,   51,   58)', 'Medium jungle green'],
-  ['rgb( 47,   79,   79)', 'Dark slate grey'],
-  ['rgb( 74,  104,   93)', 'Dark grullo green'],
-  ['rgb(  0,  128,  128)', 'Teal'],
-  ['rgb( 67,  170,  176)', 'Turquoise'],
-  ['rgb(109,  174,  199)', 'Cerulean frost'],
-  ['rgb(173,  217,  186)', 'Tiffany green'],
-  ['rgb( 22,   34,   29)', 'Gray-asparagus'],
-  ['rgb( 36,   48,   45)', 'Medium dark teal'],
-  ['rgb( 74,  104,   93)', 'Xanadu'],
-  ['rgb(119,  198,  121)', 'Mint'],
-  ['rgb(175,  205,  182)', 'Timberwolf'],
-  ['rgb(185,  245,  246)', 'Celeste'],
-  ['rgb(193,  255,  234)', 'Aquamarine'],
-  ['rgb( 29,   52,   35)', 'Cal Poly Pomona'],
-  ['rgb(  1,   68,   33)', 'Forest green'],
-  ['rgb( 42,  128,    0)', 'Napier green'],
-  ['rgb(128,  128,    0)', 'Olive'],
-  ['rgb( 65,  156,  105)', 'Sea green'],
-  ['rgb(189,  246,   29)', 'Green-yellow'],
-  ['rgb(231,  244,  134)', 'Bright chartreuse'],
-  ['rgb(138,   23,  137)', 'Purple'],
-  ['rgb( 78,   39,  138)', 'Violet'],
-  ['rgb(193,   75,  110)', 'Dark thulian pink'],
-  ['rgb(222,   49,   99)', 'Cerise'],
-  ['rgb(255,   20,  147)', 'Deep pink'],
-  ['rgb(255,  102,  204)', 'Rose pink'],
-  ['rgb(255,  203,  219)', 'Pink'],
-  ['rgb(255,  255,  255)', 'White'],
-  ['rgb(229,   17,    1)', 'RGB Red'],
-  ['rgb(  0,  255,    0)', 'RGB Green'],
-  ['rgb(  0,    0,  255)', 'RGB Blue'],
-  ['rgb(  0,  255,  255)', 'CMYK Cyan'],
-  ['rgb(255,    0,  255)', 'CMYK Magenta'],
-  ['rgb(255,  255,    0)', 'CMYK Yellow'],
-];
-
-// re-arrange to the right order for display
-let palReordered = [];
-for (let row = 0; row < 7; row++) {
-  for (let col = 0; col < 11; col++) {
-    palReordered.push(palette[col * 7 + row]);
-  }
-  palReordered.push(null); // null indicates a <br />
-}
-
-// Utility for converting base64 image to binary for upload
-// https://stackoverflow.com/questions/35940290/how-to-convert-base64-string-to-javascript-file-object-like-as-from-file-input-f
-function dataURLtoFile(dataurl, filename) {
-  let arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
-    bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
-  while(n--){
-    u8arr[n] = bstr.charCodeAt(n);
-  }
-  return new File([u8arr], filename, { type: mime });
-}
-/** Doodle canvas size options */
-const DOODLE_SIZES = {
-  normal: [500, 500, 'Square 500'],
-  tootbanner: [702, 330, 'Tootbanner'],
-  s640x480: [640, 480, '640×480 - 480p'],
-  s800x600: [800, 600, '800×600 - SVGA'],
-  s720x480: [720, 405, '720x405 - 16:9'],
-};
-
-
-const mapStateToProps = state => ({
-  options: state.getIn(['compose', 'doodle']),
-});
-
-const mapDispatchToProps = dispatch => ({
-  /** Set options in the redux store */
-  setOpt: (opts) => dispatch(doodleSet(opts)),
-  /** Submit doodle for upload */
-  submit: (file) => dispatch(uploadCompose([file])),
-});
-
-/**
- * Doodling dialog with drawing canvas
- *
- * Keyboard shortcuts:
- * - Delete: Clear screen, fill with background color
- * - Backspace, Ctrl+Z: Undo one step
- * - Ctrl held while drawing: Use background color
- * - Shift held while clicking screen: Use fill tool
- *
- * Palette:
- * - Left mouse button: pick foreground
- * - Ctrl + left mouse button: pick background
- * - Right mouse button: pick background
- */
-export default @connect(mapStateToProps, mapDispatchToProps)
-class DoodleModal extends ImmutablePureComponent {
-
-  static propTypes = {
-    options: ImmutablePropTypes.map,
-    onClose: PropTypes.func.isRequired,
-    setOpt: PropTypes.func.isRequired,
-    submit: PropTypes.func.isRequired,
-  };
-
-  //region Option getters/setters
-
-  /** Foreground color */
-  get fg () {
-    return this.props.options.get('fg');
-  }
-  set fg (value) {
-    this.props.setOpt({ fg: value });
-  }
-
-  /** Background color */
-  get bg () {
-    return this.props.options.get('bg');
-  }
-  set bg (value) {
-    this.props.setOpt({ bg: value });
-  }
-
-  /** Swap Fg and Bg for drawing */
-  get swapped () {
-    return this.props.options.get('swapped');
-  }
-  set swapped (value) {
-    this.props.setOpt({ swapped: value });
-  }
-
-  /** Mode - 'draw' or 'fill' */
-  get mode () {
-    return this.props.options.get('mode');
-  }
-  set mode (value) {
-    this.props.setOpt({ mode: value });
-  }
-
-  /** Base line weight */
-  get weight () {
-    return this.props.options.get('weight');
-  }
-  set weight (value) {
-    this.props.setOpt({ weight: value });
-  }
-
-  /** Drawing opacity */
-  get opacity () {
-    return this.props.options.get('opacity');
-  }
-  set opacity (value) {
-    this.props.setOpt({ opacity: value });
-  }
-
-  /** Adaptive stroke - change width with speed */
-  get adaptiveStroke () {
-    return this.props.options.get('adaptiveStroke');
-  }
-  set adaptiveStroke (value) {
-    this.props.setOpt({ adaptiveStroke: value });
-  }
-
-  /** Smoothing (for mouse drawing) */
-  get smoothing () {
-    return this.props.options.get('smoothing');
-  }
-  set smoothing (value) {
-    this.props.setOpt({ smoothing: value });
-  }
-
-  /** Size preset */
-  get size () {
-    return this.props.options.get('size');
-  }
-  set size (value) {
-    this.props.setOpt({ size: value });
-  }
-
-  //endregion
-
-  /** Key up handler */
-  handleKeyUp = (e) => {
-    if (e.target.nodeName === 'INPUT') return;
-
-    if (e.key === 'Delete') {
-      e.preventDefault();
-      this.handleClearBtn();
-      return;
-    }
-
-    if (e.key === 'Backspace' || (e.key === 'z' && (e.ctrlKey || e.metaKey))) {
-      e.preventDefault();
-      this.undo();
-    }
-
-    if (e.key === 'Control' || e.key === 'Meta') {
-      this.controlHeld = false;
-      this.swapped = false;
-    }
-
-    if (e.key === 'Shift') {
-      this.shiftHeld = false;
-      this.mode = 'draw';
-    }
-  };
-
-  /** Key down handler */
-  handleKeyDown = (e) => {
-    if (e.key === 'Control' || e.key === 'Meta') {
-      this.controlHeld = true;
-      this.swapped = true;
-    }
-
-    if (e.key === 'Shift') {
-      this.shiftHeld = true;
-      this.mode = 'fill';
-    }
-  };
-
-  /**
-   * Component installed in the DOM, do some initial set-up
-   */
-  componentDidMount () {
-    this.controlHeld = false;
-    this.shiftHeld = false;
-    this.swapped = false;
-    window.addEventListener('keyup', this.handleKeyUp, false);
-    window.addEventListener('keydown', this.handleKeyDown, false);
-  };
-
-  /**
-   * Tear component down
-   */
-  componentWillUnmount () {
-    window.removeEventListener('keyup', this.handleKeyUp, false);
-    window.removeEventListener('keydown', this.handleKeyDown, false);
-    if (this.sketcher) this.sketcher.destroy();
-  }
-
-  /**
-   * Set reference to the canvas element.
-   * This is called during component init
-   *
-   * @param elem - canvas element
-   */
-  setCanvasRef = (elem) => {
-    this.canvas = elem;
-    if (elem) {
-      elem.addEventListener('dirty', () => {
-        this.saveUndo();
-        this.sketcher._dirty = false;
-      });
-
-      elem.addEventListener('click', () => {
-        // sketcher bug - does not fire dirty on fill
-        if (this.mode === 'fill') {
-          this.saveUndo();
-        }
-      });
-
-      // prevent context menu
-      elem.addEventListener('contextmenu', (e) => {
-        e.preventDefault();
-      });
-
-      elem.addEventListener('mousedown', (e) => {
-        if (e.button === 2) {
-          this.swapped = true;
-        }
-      });
-
-      elem.addEventListener('mouseup', (e) => {
-        if (e.button === 2) {
-          this.swapped = this.controlHeld;
-        }
-      });
-
-      this.initSketcher(elem);
-      this.mode = 'draw'; // Reset mode - it's confusing if left at 'fill'
-    }
-  };
-
-  /**
-   * Set up the sketcher instance
-   *
-   * @param canvas - canvas element. Null if we're just resizing
-   */
-  initSketcher (canvas = null) {
-    const sizepreset = DOODLE_SIZES[this.size];
-
-    if (this.sketcher) this.sketcher.destroy();
-    this.sketcher = new Atrament(canvas || this.canvas, sizepreset[0], sizepreset[1]);
-
-    if (canvas) {
-      this.ctx = this.sketcher.context;
-      this.updateSketcherSettings();
-    }
-
-    this.clearScreen();
-  }
-
-  /**
-   * Done button handler
-   */
-  onDoneButton = () => {
-    const dataUrl = this.sketcher.toImage();
-    const file = dataURLtoFile(dataUrl, 'doodle.png');
-    this.props.submit(file);
-    this.props.onClose(); // close dialog
-  };
-
-  /**
-   * Cancel button handler
-   */
-  onCancelButton = () => {
-    if (this.undos.length > 1 && !confirm('Discard doodle? All changes will be lost!')) {
-      return;
-    }
-
-    this.props.onClose(); // close dialog
-  };
-
-  /**
-   * Update sketcher options based on state
-   */
-  updateSketcherSettings () {
-    if (!this.sketcher) return;
-
-    if (this.oldSize !== this.size) this.initSketcher();
-
-    this.sketcher.color = (this.swapped ? this.bg : this.fg);
-    this.sketcher.opacity = this.opacity;
-    this.sketcher.weight = this.weight;
-    this.sketcher.mode = this.mode;
-    this.sketcher.smoothing = this.smoothing;
-    this.sketcher.adaptiveStroke = this.adaptiveStroke;
-
-    this.oldSize = this.size;
-  }
-
-  /**
-   * Fill screen with background color
-   */
-  clearScreen = () => {
-    this.ctx.fillStyle = this.bg;
-    this.ctx.fillRect(-1, -1, this.canvas.width+2, this.canvas.height+2);
-    this.undos = [];
-
-    this.doSaveUndo();
-  };
-
-  /**
-   * Undo one step
-   */
-  undo = () => {
-    if (this.undos.length > 1) {
-      this.undos.pop();
-      const buf = this.undos.pop();
-
-      this.sketcher.clear();
-      this.ctx.putImageData(buf, 0, 0);
-      this.doSaveUndo();
-    }
-  };
-
-  /**
-   * Save canvas content into the undo buffer immediately
-   */
-  doSaveUndo = () => {
-    this.undos.push(this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height));
-  };
-
-  /**
-   * Called on each canvas change.
-   * Saves canvas content to the undo buffer after some period of inactivity.
-   */
-  saveUndo = debounce(() => {
-    this.doSaveUndo();
-  }, 100);
-
-  /**
-   * Palette left click.
-   * Selects Fg color (or Bg, if Control/Meta is held)
-   *
-   * @param e - event
-   */
-  onPaletteClick = (e) => {
-    const c = e.target.dataset.color;
-
-    if (this.controlHeld) {
-      this.bg = c;
-    } else {
-      this.fg = c;
-    }
-
-    e.target.blur();
-    e.preventDefault();
-  };
-
-  /**
-   * Palette right click.
-   * Selects Bg color
-   *
-   * @param e - event
-   */
-  onPaletteRClick = (e) => {
-    this.bg = e.target.dataset.color;
-    e.target.blur();
-    e.preventDefault();
-  };
-
-  /**
-   * Handle click on the Draw mode button
-   *
-   * @param e - event
-   */
-  setModeDraw = (e) => {
-    this.mode = 'draw';
-    e.target.blur();
-  };
-
-  /**
-   * Handle click on the Fill mode button
-   *
-   * @param e - event
-   */
-  setModeFill = (e) => {
-    this.mode = 'fill';
-    e.target.blur();
-  };
-
-  /**
-   * Handle click on Smooth checkbox
-   *
-   * @param e - event
-   */
-  tglSmooth = (e) => {
-    this.smoothing = !this.smoothing;
-    e.target.blur();
-  };
-
-  /**
-   * Handle click on Adaptive checkbox
-   *
-   * @param e - event
-   */
-  tglAdaptive = (e) => {
-    this.adaptiveStroke = !this.adaptiveStroke;
-    e.target.blur();
-  };
-
-  /**
-   * Handle change of the Weight input field
-   *
-   * @param e - event
-   */
-  setWeight = (e) => {
-    this.weight = +e.target.value || 1;
-  };
-
-  /**
-   * Set size - clalback from the select box
-   *
-   * @param e - event
-   */
-  changeSize = (e) => {
-    let newSize = e.target.value;
-    if (newSize === this.oldSize) return;
-
-    if (this.undos.length > 1 && !confirm('Change canvas size? This will erase your current drawing!')) {
-      return;
-    }
-
-    this.size = newSize;
-  };
-
-  handleClearBtn = () => {
-    if (this.undos.length > 1 && !confirm('Clear canvas? This will erase your current drawing!')) {
-      return;
-    }
-
-    this.clearScreen();
-  };
-
-  /**
-   * Render the component
-   */
-  render () {
-    this.updateSketcherSettings();
-
-    return (
-      <div className='modal-root__modal doodle-modal'>
-        <div className='doodle-modal__container'>
-          <canvas ref={this.setCanvasRef} />
-        </div>
-
-        <div className='doodle-modal__action-bar'>
-          <div className='doodle-toolbar'>
-            <Button text='Done' onClick={this.onDoneButton} />
-            <Button text='Cancel' onClick={this.onCancelButton} />
-          </div>
-          <div className='filler' />
-          <div className='doodle-toolbar with-inputs'>
-            <div>
-              <label htmlFor='dd_smoothing'>Smoothing</label>
-              <span className='val'>
-                <input type='checkbox' id='dd_smoothing' onChange={this.tglSmooth} checked={this.smoothing} />
-              </span>
-            </div>
-            <div>
-              <label htmlFor='dd_adaptive'>Adaptive</label>
-              <span className='val'>
-                <input type='checkbox' id='dd_adaptive' onChange={this.tglAdaptive} checked={this.adaptiveStroke} />
-              </span>
-            </div>
-            <div>
-              <label htmlFor='dd_weight'>Weight</label>
-              <span className='val'>
-                <input type='number' min={1} id='dd_weight' value={this.weight} onChange={this.setWeight} />
-              </span>
-            </div>
-            <div>
-              <select aria-label='Canvas size' onInput={this.changeSize} defaultValue={this.size}>
-                { Object.values(mapValues(DOODLE_SIZES, (val, k) =>
-                  <option key={k} value={k}>{val[2]}</option>
-                )) }
-              </select>
-            </div>
-          </div>
-          <div className='doodle-toolbar'>
-            <IconButton icon='pencil' title='Draw' label='Draw' onClick={this.setModeDraw} size={18} active={this.mode === 'draw'} inverted />
-            <IconButton icon='bath' title='Fill' label='Fill' onClick={this.setModeFill} size={18} active={this.mode === 'fill'} inverted />
-            <IconButton icon='undo' title='Undo' label='Undo' onClick={this.undo} size={18} inverted />
-            <IconButton icon='trash' title='Clear' label='Clear' onClick={this.handleClearBtn} size={18} inverted />
-          </div>
-          <div className='doodle-palette'>
-            {
-              palReordered.map((c, i) =>
-                c === null ?
-                  <br key={i} /> :
-                  <button
-                    key={i}
-                    style={{ backgroundColor: c[0] }}
-                    onClick={this.onPaletteClick}
-                    onContextMenu={this.onPaletteRClick}
-                    data-color={c[0]}
-                    title={c[1]}
-                    className={classNames({
-                      'foreground': this.fg === c[0],
-                      'background': this.bg === c[0],
-                    })}
-                  />
-              )
-            }
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-}