VerifoneControls.js

/**
* @module VerifoneControls
*/

import { blobToBase64, checkResponse } from './Common.js'
import { DisplayWidget } from './SignatureTabletWidgets.js'

/**
 * AccessKey
 * @classdesc Special keys which can be mapped to a button
 * @see {@link Verifone}
 */
export class AccessKey {
  static STAR = 'Š'
  static HASH = '‹'
  static CANCEL = ''
  static BACK = ''
  static ENTER = '
'
}

/**
 * Button
 * @classdesc A button for displaying clickable text on a tablet
 * @extends DisplayWidget
 * @see {@link Verifone}
 */
export class Button extends DisplayWidget {
  id
  text
  width = 200
  height = 100
  font
  border
  background
  padding
  accessKey
  beep = false

  /**
   * Instantiate a Button
   * @constructor
   * @param {number} id - ID of the button that will be returned when it is
   * clicked
   * @param {string} text - Text to display on the button
   * @param {number} x - X position to draw the button
   * @param {number} y - Y position to draw the button
   *
   * @param {object} [options] - Additional configuration options.
   * @param {number} [options.width=200] - Width of button in pixesl
   * @param {number} [options.height=100] - height of button in pixels
   * @param {object} [options.border] - Border styles
   * @param {string} [options.background] - Background color (hex or RGB)
   * @param {array} [options.padding] - Padding
   * @param {string} [options.accessKey] - Map button to a key on the keypad
   * @param {object} [options.font] - Font styles
   * @param {boolean} [options.beep=false] - On press, emit a beep
   */
  constructor (id, text, x, y, options) {
    super(x, y)
    this.id = id
    this.text = text
    if (typeof options === 'object') {
      Object.assign(this, options)
    }
  }

  toJSON () {
    return {
      type: 'button',
      text: this.text,
      x: this.x,
      y: this.y,
      id: this.id,
      width: this.width,
      height: this.height,
      font: this.font,
      border: this.border,
      padding: this.padding,
      background: this.background,
      access_key: this.accessKey,
      beep: this.beep
    }
  }
}

/**
 * Text
 * @classdesc Text to be displayed on a tablet
 * @extends DisplayWidget
 * @see {@link Verifone}
 */
export class Text extends DisplayWidget {
  id
  text
  width
  height
  font
  border
  background
  padding
  align = 'left'
  lineHeight
  escape = true
  style

  /**
   * Instantiate a Text object
   * @constructor
   * @param {number} id - ID of the text object
   * @param {string} text - Text to display
   * @param {number} x - X position to draw the text
   * @param {number} y - Y position to draw the text
   *
   * @param {object} [options] - Additional configuration options.
   * @param {number} [options.width] - Width of text bounding box in pixesl
   * @param {number} [options.height] - height of text bounding in pixels
   * @param {object} [options.border] - Border styles
   * @param {string} [options.background] - Background color (hex or RGB)
   * @param {array} [options.padding] - Padding
   * @param {string} [options.align] - Align text `left`, `right`, or `center`
   * @param {object} [options.font] - Font styles
   * @param {number} [options.lineHeight] - Text line height
   * @param {bool} [options.escape=true] - If false allows for raw HTML content
   * @param {string?} [options.style] - If set, will embed inline style
   */
  constructor (id, text, x, y, options) {
    super(x, y)
    this.id = id
    this.text = text
    if (typeof options === 'object') {
      Object.assign(this, options)
    }
  }

  toJSON () {
    return {
      type: 'text',
      text: this.text,
      align: this.align,
      x: this.x,
      y: this.y,
      id: this.id,
      width: this.width,
      height: this.height,
      font: this.font,
      border: this.border,
      padding: this.padding,
      background: this.background,
      line_height: this.lineHeight,
      escape: this.escape,
      style: this.style
    }
  }
}

/**
 * Image
 * @classdesc Image to be displayed on a tablet
 * @extends DisplayWidget
 * @see {@link Verifone}
 */
export class Image extends DisplayWidget {
  id
  url
  imageData
  format = 'jpg'
  height
  width
  border
  /**
   * Instantiate an Image object
   * @constructor
   * @param {number} id - ID of the image object
   * @param {string} url - URL of the image. To provide the image data directly
   * set the `url` argument to `null`, set the`imageData` property to the
   * base64 encoded contents of the image, and set the `format` property to
   * the file extension of the image (e.g. `jpg` or `png`)
   * @param {number} x - X position to draw the image
   * @param {number} y - Y position to draw the image
   *
   * @param {object} [options] - Additional configuration options
   * @param {string} [options.format] - `jpg` or `png`
   * @param {string} [options.imageData] - base64 encoded image data
   * @param {number} [options.width] - Width of image in pixels
   * @param {number} [options.height] - height of image in pixels
   * @param {object} [options.border] - Border styles
   */
  constructor (id, url, x = 0, y = 0, options) {
    super(x, y)
    this.id = id
    this.url = url

    // if no format is provided look for the file extension
    if ((!options || !options.format) && url) {
      this.format = /(?:\.([^.]+))?$/.exec(url)[1]
    }

    if (typeof options === 'object') {
      Object.assign(this, options)
    }
  }

  /**
   * Fetch the image data and base64 encode it
   * @async
   */
  async data () {
    if (!this.imageData && this.url) {
      const response = await fetch(this.url)
      await checkResponse(response)
      const blob = await response.blob()
      this.imageData = await blobToBase64(blob)
    }
    return this.imageData
  }

  toJSON () {
    return {
      id: this.id,
      width: this.width,
      height: this.height,
      format: this.format,
      type: 'image',
      data: this.imageData,
      x: this.x,
      y: this.y,
      border: this.border ? this.border.toJSON() : undefined
    }
  }
}

/**
 * SignatureBox
 * @classdesc Bounding box to capture a signature
 * @extends DisplayWidget
 * @see {@link Verifone}
 */
export class SignatureBox extends DisplayWidget {
  id
  width
  height
  postSignTimeout = 25

  /**
   * Instantiate a SignatureBox object
   * @constructor
   * @param {number} id - ID of the signature box object
   * @param {number} x - X position to draw the sig box
   * @param {number} y - Y position to draw the sig box
   *
   * @param {object} [options] - Additional configuration options
   * @param {string} [options.format] - `jpg` or `png`
   * @param {number} [options.width] - Width of sig box in pixels
   * @param {number} [options.height] - height of sig box in pixels
   * @param {number} [options.postSignTimeout] - Timeout after signing has started
   */
  constructor (id, x, y, options) {
    super(x, y)
    this.id = id
    if (typeof options === 'object') {
      Object.assign(this, options)
    }
  }

  toJSON () {
    return {
      id: this.id,
      type: 'signature',
      x: this.x,
      y: this.y,
      width: this.width,
      height: this.height,
      post_sign_timeout: this.postSignTimeout
    }
  }
}

/**
 * Rect
 * @classdesc A rectangle
 * @extends DisplayWidget
 * @see {@link Verifone}
 */
export class Rect extends DisplayWidget {
  id
  width = 100
  height = 100
  background
  opacity
  border
  zIndex

  /**
   * Instantiate a Rect object
   * @constructor
   * @param {number} id - ID of the rectangle object
   * @param {number} x - X position to draw the rectangle
   * @param {number} y - Y position to draw the rectangle
   *
   * @param {object} [options] - Additional configuration options
   * @param {number} [options.width=100] - Width of rectangle in pixels
   * @param {number} [options.height=100] - height of rectangle in pixels
   * @param {object} [options.border] - Border styles
   * @param {float} [options.opacity] - Set object opacity (0.0 to 1)
   * @param {number} [options.zIndex] - Manually set z-index
   */
  constructor (id, x, y, options) {
    super(x, y)
    this.id = id
    if (typeof options === 'object') {
      Object.assign(this, options)
    }
  }

  toJSON () {
    return {
      id: this.id,
      type: 'rect',
      x: this.x,
      y: this.y,
      width: this.width,
      height: this.height,
      border: this.border,
      opacity: this.opacity,
      background: this.background,
      z_index: this.zIndex
    }
  }
}

/**
 * CheckBox
 * @classdesc A check box object
 * @extends DisplayWidget
 * @see {@link Verifone}
 */
export class CheckBox extends DisplayWidget {
  id
  width = 50
  height = 50
  checked

  /**
   * Instantiate a check box object
   * @constructor
   * @param {number} id - ID of the rectangle object
   * @param {number} x - X position to draw the check box
   * @param {number} y - Y position to draw the check box
   *
   * @param {object} [options] - Additional configuration options
   * @param {number} [options.width=50] - Width of check box in pixels
   * @param {number} [options.height=50] - height of check box in pixels
   * @param {boolean} [options.boolean=false] - Set the checked state
   */
  constructor (id, x, y, options) {
    super(x, y)
    this.id = id
    if (typeof options === 'object') {
      Object.assign(this, options)
    }
  }

  toJSON () {
    return {
      type: 'checkbox',
      x: this.x,
      y: this.y,
      id: this.id,
      width: this.width,
      height: this.height,
      checked: this.checked
    }
  }
}

/**
 * RadioButton
 * @classdesc A radio button object
 * @extends DisplayWidget
 * @see {@link Verifone}
 */
export class RadioButton extends DisplayWidget {
  id
  groupId
  width = 50
  height = 50
  checked
  value

  /**
   * Instantiate a radio button object
   * @constructor
   * @param {number} id - ID of the radio button object
   * @param {number} groupId - Group ID of the radio button object
   * @param {number} x - X position to draw the radio button
   * @param {number} y - Y position to draw the radio button
   *
   * @param {object} [options] - Additional configuration options
   * @param {number} [options.width=50] - Width of radio button in pixels
   * @param {number} [options.height=50] - height of radio button in pixels
   * @param {boolean} [options.boolean=false] - Set the checked state
   */
  constructor (id, groupId, value, x, y, options) {
    super(x, y)
    this.id = id
    this.groupId = groupId
    this.value = value
    if (typeof options === 'object') {
      Object.assign(this, options)
    }
  }

  toJSON () {
    return {
      type: 'radio',
      x: this.x,
      y: this.y,
      id: this.id,
      group_id: this.groupId,
      width: this.width,
      height: this.height,
      checked: this.checked,
      value: this.value
    }
  }
}

/**
 * TextInput
 * @classdesc A text input object
 * @extends DisplayWidget
 * @see {@link Verifone}
 */
export class TextInput extends DisplayWidget {
  id
  width
  height
  value
  font
  border
  background
  padding
  placeholder
  allowedChars
  minLength
  maxLength
  align = 'left'

  /**
   * Instantiate a text input object
   * @constructor
   * @param {number} id - ID of the radio button object
   * @param {number} x - X position to draw the text input
   * @param {number} y - Y position to draw the text input
   *
   * @param {object} [options] - Additional configuration options
   * @param {number} [options.width] - Width of text input in pixels
   * @param {number} [options.height] - height of text input in pixels
   * @param {string} [options.value] - Set the value of the text input
   * @param {object} [options.font] - Font styles
   * @param {object} [options.border] - Border styles
   * @param {string} [options.background] - Background color
   * @param {array} [options.padding] - Padding
   * @param {string} [options.placeholder] - Placeholder text
   * @param {string} [options.allowedChars] - Characters allowed for input
   * @param {number} [options.minLength] - Minimum allowed number of characters
   * @param {number} [options.maxLength] - Maximum allowed number of characters
   * @param {string} [options.align] - Align text `left`, `right`, or `center`
   */
  constructor (id, x, y, options) {
    super(x, y)
    this.id = id
    if (typeof options === 'object') {
      Object.assign(this, options)
    }
  }

  toJSON () {
    return {
      type: 'input',
      id: this.id,
      x: this.x,
      y: this.y,
      width: this.width,
      height: this.height,
      value: this.value,
      font: this.font,
      border: this.border,
      background: this.background,
      padding: this.padding,
      placeholder: this.placeholder,
      allowed_chars: this.allowedChars,
      min_length: this.minLength,
      max_length: this.maxLength,
      align: this.align
    }
  }
}