# Kool console
# ============
#
# A snippet for running CoffeeScript snippets in browser, with a cool
# centered text input.
#
# Press <meta> + K to open the console.

# Styles for the text field.
CSS = """
  .konsole {
    -webkit-transition: all .25s linear;
    -moz-transition:    all .25s linear;
    transition:         all .25s linear;
    -webkit-transform:  translate(-50%, -65%);
    -moz-transform:     translate(-50%, -65%);
    transform:          translate(-50%, -65%);
    box-sizing: border-box;
    position: fixed;
    top: 50%;
    left: 50%;
    width: 80%;
    padding: 10px;
    font-size: 36px;
    background: rgba(0, 0, 0, .75);
    box-shadow: 0 0 30px black;
    font-family: monospace;
    color: white;
    pointer-events: none;
    opacity: 0;
    z-index: 100;
  }
  .konsole.visible {
    opacity: 1;
    pointer-events: auto;
    box-shadow: 2px 2px 5px rgba(0, 0, 0, .35);
  }
  .konsole.ran,
  .konsole.error {
    -webkit-transition: all .15s linear;
    -moz-transition:    all .15s linear;
    transition:         all .15s linear;
    pointer-events: none;
  }
  .konsole.ran {
    background: transparent;
    color: transparent;
    text-shadow: 0 0 20px white;
  }
  .konsole.error {
    -webkit-transform:  translate(-50%, 35%);
    -moz-transform:     translate(-50%, 35%);
    transform:          translate(-50%, 35%);
    background: #cd414d;
    color: white;
  }
"""

# Keep our command history around.
history = ['']
history.index = 0

# Saves the current history to localStorage.
commitHistory = ->
  localStorage['konsole'] = JSON.stringify history

# Loads the command history from localStorage.
loadHistory = ->
  try
    history.length = 0
    history = history.concat JSON.parse localStorage['konsole']
    history.index = history.length-1
  catch e

# Sources the browser CoffeeScript library.
loadCoffeeScript = ->
  script = document.createElement 'script'
  script.src = 'https://raw.github.com/jashkenas/coffee-script/master/extras/coffee-script.js'
  document.body.appendChild script

# Add the text field styles to the document.
applyStyles = ->
  style = document.createElement 'style'
  style.innerHTML = CSS
  head = document.querySelector 'head'
  head.appendChild style

# Creates the text field.
createTextField = ->
  field = document.createElement 'input'
  field.type = 'text'
  field.spellcheck = no
  field.classList.add 'konsole'
  field

# Adds the text field to the DOM.
installTextField = ->
  return false if document.querySelector('.konsole')?
  textField = createTextField()
  document.body.appendChild textField

# Binds the shortcut keys.
bindKeys = ->
  window.addEventListener 'keydown',
  (e) ->
    switch e.keyCode
      when 75
        if e.metaKey
          e.preventDefault()
          toggle()
      when 13
        return false unless visible()
        run()
      when 38
        return false if not visible() or history.index is 0
        e.preventDefault()
        loadEl().value = history[--history.index]
      when 40
        return false if not visible() or history.index is history.length-1
        e.preventDefault()
        loadEl().value = history[++history.index]
  , no

# Finds the text field element. Throws an error if it can't be found.
loadEl = ->
  el = document.querySelector '.konsole'
  throw "Konsole text field not found" unless el?
  el

# Shows the text field.
showAndFocus = ->
  el = loadEl()
  el.classList.add 'visible'
  el.focus()

# Hides the text field.
hide = ->
  loadEl().classList.remove 'visible'

# True if the visible class is there.
visible = ->
  loadEl().classList.contains 'visible'

# Toggles the text field visible/invisible.
toggle = ->
  el = loadEl()
  if el.classList.contains 'visible'
    hide()
  else
    showAndFocus()

# Focus the element.
focus = ->
  loadEl.focus()

# Special FX for feedback that the command ran.
runEffect = ->
  ghost = loadEl().cloneNode()
  document.body.appendChild ghost
  setTimeout (-> ghost.classList.add 'ran'), 1
  setTimeout (-> document.body.removeChild ghost), 150

showError = (message) ->
  ghost = loadEl().cloneNode()
  ghost.value = message
  document.body.appendChild ghost
  setTimeout (-> ghost.classList.add 'error'), 1
  setTimeout (-> document.body.removeChild ghost), 3000

# Runs a script in the window context.
run = ->
  el = loadEl()
  history[history.index] = el.value
  history.push ''
  history.index = history.length-1
  commitHistory()
  try
    window.eval CoffeeScript.compile(el.value, bare: yes)
    runEffect()
  catch e
    showError e.message
  loadEl().value = history[history.index]

# Boot it up!
loadCoffeeScript()
applyStyles()
installTextField()
bindKeys()
loadHistory()