Skip to content

Instantly share code, notes, and snippets.

@tstachl
Created May 15, 2014 17:02
Show Gist options
  • Save tstachl/2dbfd73db778686225e0 to your computer and use it in GitHub Desktop.
Save tstachl/2dbfd73db778686225e0 to your computer and use it in GitHub Desktop.
'use-strict'
Emitter = require 'emitter'
_ = require 'lodash'
binder = require 'binder'
module.exports = class DrawingTracker extends Emitter
constructor: (@_element, visible = no) ->
if typeof this._element == 'string' or this._element instanceof String
this._element = document.getElementById this._element
if not this._element.getContext
throw 'Error: no canvas.getContext'
this._context = this._element.getContext '2d'
unless this._context
throw 'Error: failed to getContext'
this._element.setAttribute 'height', innerHeight
this._element.setAttribute 'width', innerWidth
this._context.strokeStyle = '#ff0000'
this._context.lineWidth = 5
this._context.lineCap = 'round'
this._started = no
this._visible = no
do this.show if visible
_.bindAll this, '_drawHandler'
_.bindAll this, '_eventHandler'
_.bindAll this, '_tapHandler'
_.bindAll this, '_clearHandler'
_.bindAll this, 'mousedown'
_.bindAll this, 'mousemove'
_.bindAll this, 'mouseup'
binder.on this._element, 'mousedown', this._eventHandler
binder.on this._element, 'mousemove', this._eventHandler
binder.on this._element, 'mouseup', this._eventHandler
binder.on this._element, 'touchstart', this._tapHandler
this.on 'draw', this._drawHandler
this.on 'clear', this._clearHandler
_clearHandler: ->
this._context.clearRect 0, 0, this._element.width, this._element.height
_tapHandler: (ev) ->
this.emit 'clear'
do this.hide
_drawHandler: (ev) ->
if this[ev.type]
this[ev.type] ev.position
_eventHandler: (ev) ->
this.emit 'draw',
type: ev.type
position:
x: ev.offsetX
y: ev.offsetY
show: ->
if not this._visible
this._element.style.display = 'block'
this._visible = yes
hide: ->
if this._visible
this._element.style.display = 'none'
this._visible = no
mousedown: (pos) ->
this._context.beginPath()
this._context.moveTo pos.x, pos.y
this._started = yes
mousemove: (pos) ->
if this._started
this._context.lineTo pos.x, pos.y
this._context.stroke()
mouseup: (pos) ->
if this._started
this.mousemove pos.x, pos.y
this._started = no
'use-strict'
Emitter = require 'emitter'
module.exports = class History extends Emitter
history: []
@stopEvent: (evt) ->
do evt.preventDefault
do evt.stopPropagation
sendAgent: (data, url) ->
url = url or this.history[this.history.length - 1]
data = data or $('html').html()
this.emit 'contentchange', data: data, url: url
perform: (data, url) ->
history.pushState null, null, url
this.history.push url
this.sendAgent data, url
$('#sos-content').html $(data).find('#sos-content').html()
scrollTo 0, 0
do this.getSpinner().stop if this.getSpinner()
request: (method, args...) ->
do this.getSpinner().spin if this.getSpinner()
$[method].apply this, args
getSpinner: ->
this.sos = require './sos' unless this.sos
this.sos.spinner
constructor: ->
this.history.push location.href
self = this
$(document).on 'submit', 'form', (evt) ->
History.stopEvent evt
method = $(this).attr('method').toLowerCase()
data = $(this).serialize()
url = $(this).attr('action')
url += "?#{data}" if method == 'get'
if method == 'get'
self.request method, url, (data) -> self.perform data, this.url
else
self.request method, url, data, (data) -> self.perform data, this.url
$(document).on 'click', 'a', (evt) ->
if $(this).attr('href') != '#'
History.stopEvent evt
self.request 'get', $(this).attr('href'), (data) -> self.perform data, this.url
$(window).on 'popstate', (evt) ->
if self.history.length > 1
History.stopEvent evt
[this_url, last_url] = [self.history.pop(), self.history.pop()]
self.request 'get', last_url, (data) -> self.perform data, this.url
'use-strict'
Emitter = require 'emitter'
_ = require 'lodash'
binder = require 'binder'
module.exports = class ScrollTracker
constructor: (@_element = window) ->
this._emitter = new Emitter
_.bindAll this, '_scrollHandler'
_.bindAll this, 'set'
this._currentPosition = this._getPosition()
this._throttledHandler = _.throttle this._scrollHandler, 100
this._isBound = no
getPosition: -> this._currentPosition
on: (evt, listener) ->
this._emitter.on evt, listener
do this._bind if evt == 'scroll' and not this._isBound
off: (evt, listener) ->
this.emitter.off evt, listener
do this._unbind if not this._emitter.hasListener('scroll') and this._isBound
set: (value) ->
do this._unbind
if this._element == window
this._element.scrollTo value.position.left, value.position.top
else
this._element.scrollTop = value.position.top
this._element.scrollLeft = value.position.left
do this._bind
_bind: ->
binder.on this._element, 'scroll', this._throttledHandler
this._isBound = yes
_unbind: ->
binder.off this._element, 'scroll', this._throttledHandler
this._isBound = no
_scrollHandler: ->
new_pos = this._getPosition()
old_pos = this.getPosition()
this._emitter.emit 'scroll',
direction:
up: new_pos.top < old_pos.top
down: new_pos.top > old_pos.top
left: new_pos.left < old_pos.left
right: new_pos.left > old_pos.left
position: new_pos
this._currentPosition = new_pos
_getPosition: ->
pos = top: 0, left: 0
if this._element == window
pos.top = window.pageYOffset or window.documentElement?.scrollTop or 0
pos.left = window.pageXOffset or window.documentElement?.scrollLeft or 0
else
pos.top = this._element.scrollTop or 0
pos.left = this._element.scrollLeft or 0
pos
'use-strict'
_ = require 'lodash'
ScrollTracker = require './scroll_tracker'
DrawingTracker = require './drawing_tracker'
History = require './history'
Spinner = require './spinner'
module.exports = class SOS
@history: new History
@spinner: new Spinner
@session: null
@create: (options = {}) ->
do SOS.spinner.spin unless options.spinner?
$('#sos-overlay').attr height: innerHeight, width: innerWidth
$('body').css 'overflow', 'hidden' if options.agent?
user =
user: displayName: options.name
rooms: [options.room]
goinstant.connect 'https://goinstant.net/5964eaab0092/DeskSOS', user, (err, connection, room) ->
if err
console.error 'Error connecting to GoInstant: ', err
return alert 'Error connecting to GoInstant.'
SOS.session = new SOS agent: options.agent?, room: room, name: options.name
@createRequest: (name) ->
do SOS.spinner.spin
body =
url: location.href
room: 'xxxxxxxxxxxx'.replace /[xy]/g, -> ((Date.now()+Math.random()*16)%16 | 0).toString(16)
$.get '/customer/portal/emails/new', (data) ->
form = $(data).find('#new_email')
form.find('#interaction_name').val "Lauren Boyle"
form.find('#interaction_email').val "[email protected]"
form.find('#email_subject').val "Session Requested"
form.find('#email_body').val JSON.stringify body
$.post form.attr('action'), form.serialize(), (data, status) ->
if status == 'success'
SOS.create
name: name or 'Lauren Boyle'
room: body.room
spinner: yes
else
alert "Coulnd't send SOS request!"
constructor: (options = {}) ->
self = this
this._isAgent = options.agent
this._room = options.room
this._name = options.name
this._started = no
this._scrollTracker = new ScrollTracker
this._drawingTracker = new DrawingTracker 'sos-overlay', this._isAgent
script = if options.agent then 'agent' else 'cordova'
script = "https://desk-customers.s3.amazonaws.com/zzz-sos/#{script}.js"
$.getScript script, ->
$.getJSON "https://opentokrtc.com/#{self._room.name}.json", (data) ->
if self._isAgent
self.agent data
else
self.customer data
destroy: ->
do $('#sos-video').hide
agent: (data) ->
do $('#sos-video').show
room = this._room
draw = this._drawingTracker
room.channel('scroll').on 'message', (value) =>
this._scrollTracker.set value
room.channel('history').on 'message', (value) =>
SOS.history.perform value.data, value.url
room.channel('events').on 'message', (value) ->
if value.value
$(value.selector).val value.value
else
$(value.selector).trigger value.event
room.channel('drawing').on 'message', (value) ->
draw.emit value.event, value.value
draw.on 'draw', (value) ->
room.channel('drawing').message
event: 'draw'
value: value
publish = TB.initPublisher data.apiKey, 'sos-video'
session = TB.initSession data.sid
session.on
streamCreated: (evt) ->
stream = evt.stream
stream = evt.streams[0] unless stream
session.subscribe stream, 'sos-fake',
subscribeToAudio: yes
subscribeToVideo: no
do SOS.spinner.stop
streamDestroyed: (evt) ->
do session.disconnect
do SOS.session.destroy
session.connect data.apiKey, data.token, ->
session.publish publish
customer: (data) ->
room = this._room
scroll = this._scrollTracker
draw = this._drawingTracker
scroll.on 'scroll', (pos) ->
room.channel('scroll').message pos
SOS.history.on 'contentchange', (args) ->
room.channel('history').message args
draw.on 'clear', ->
room.channel('drawing').message
event: 'clear'
room.channel('drawing').on 'message', (value) ->
do draw.show
draw.emit value.event, value.value
$(document).on 'click', '.toggle', ->
room.channel('events').message
selector: '.toggle'
event: 'click'
$(document).on 'keyup', 'input[name="q"]', ->
room.channel('events').message
selector: 'input[name="q"]'
value: $(this).val()
publish = TB.initPublisher data.apiKey, 'sos-fake',
publishAudio: yes
publishVideo: no
width: 1
height: 1
session = TB.initSession data.apiKey, data.sid
session.on
streamCreated: (evt) ->
do $('#sos-video').show
session.subscribe evt.stream, 'sos-video',
subscribeToAudio: yes
subscribeToVideo: yes
do SOS.spinner.stop
do $('#sos-fake').remove
do SOS.history.sendAgent
room.channel('scroll').message scroll.getPosition()
streamDestroyed: (evt) ->
do session.disconnect
do SOS.session.destroy
session.connect data.token, ->
session.publish publish
S = require 'spin'
module.exports = class Spinner extends S
constructor: ->
this.target = $('<div class="sos-spin">').appendTo(document.body).hide()
super
lines: 13
length: 20
width: 10
radius: 30
corners: 1
rotate: 0
direction: 1
color: '#FFF'
speed: 1
trail: 60
shadow: false
hwaccel: false
className: 'spinner'
zIndex: 2e9
top: '50%'
left: '50%'
spin: ->
super this.target.get 0
do this.target.show
stop: ->
super
do this.target.hide
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment