Skip to content

Instantly share code, notes, and snippets.

@miklschmidt
Last active July 13, 2017 06:11
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save miklschmidt/5896306 to your computer and use it in GitHub Desktop.
Save miklschmidt/5896306 to your computer and use it in GitHub Desktop.
Take at notifications for node-webkit with a shitload of dependencies (Require.js, chaplin, TWEEN.js and so on).. sorry for that.
define [
'jquery'
'underscore'
'backbone'
'lib/gui'
'models/notification'
'views/notification'
'vendor/tween'
'jquery-ui'
], ($, _, Backbone, gui, NotificationModel, Notification, TWEEN) ->
class NotificationManager
_(@prototype).extend Backbone.Events
counter: 0
windowWidth: 290
windowHeight: 0
window: null
loaded: no
stack: []
animationDuration: 350
defaultNoteOptions:
persistent: no
timeout: 5000
content: ''
title: ''
icon: ''
_makeID: () ->
return 'note' + ++@counter
constructor: () ->
@window = gui.Window.open 'notifications.html',
frame: no
toolbar: no
width: @windowWidth
height: @windowHeight
'always-on-top': yes
show: no
resizable: false
@window.on 'loaded', () =>
@$body = $(@window.window.document.body)
@$el = @$body.find('#notifications')
# @window.showDevTools()
@loaded = yes
@trigger 'loaded'
try
@window.moveTo(@getX()+@windowWidth, @window.window.screen.height)
catch
create: (options) ->
attributes = {}
id = @_makeID()
_.extend(attributes, @defaultNoteOptions, options)
attributes.id = id
model = new NotificationModel attributes
note = new Notification model: model, manager: @
attributes.setup(note) if typeof attributes.setup is 'function'
note.on '!close', () => @_close(note)
unless attributes.persistent
setTimeout (() => @_close(note)), attributes.timeout
if @loaded
@stack.push note
note.$el.addClass('odd') unless @stack.length % 2
@_show note
else
@once 'loaded', () =>
@stack.push note
note.$el.addClass('odd') unless @stack.length % 2
@_show note
getX: () ->
return @window.window.screen.availLeft + @window.window.screen.availWidth - @windowWidth
getY: () ->
return @window.window.screen.availTop + @window.window.screen.availHeight - @windowHeight
slideIn: (callback) ->
me = @
me.window.show()
me.shown = yes
tween = new TWEEN.Tween({x: @getX(), y: @getY()+@windowHeight})
.to({x: @getX(), y: @getY()}, @animationDuration)
.easing(TWEEN.Easing.Exponential.Out)
.onUpdate(->
me.window.moveTo(Math.round(this.x), Math.round(this.y))
)
.onComplete(->
callback?()
)
.start()
@animate tween
slideOut: (callback) ->
me = @
tween = new TWEEN.Tween({x: @getX(), y: @getY()})
.to({x: @getX(), y: @window.window.screen.height}, @animationDuration)
.easing(TWEEN.Easing.Exponential.In)
.onUpdate(->
me.window.moveTo(Math.round(this.x), Math.round(this.y))
)
.onComplete(->
me.shown = no
me.window.hide()
callback?()
)
.start()
@animate tween
fitWindow: (callback) ->
me = @
me.window.setResizable(true) # Can't programatically resize the window on linux without this.
tween = new TWEEN.Tween({x: @windowWidth, y: @windowHeight})
.to({x: @windowWidth, y: @$el.innerHeight()}, @animationDuration)
.easing(TWEEN.Easing.Exponential.InOut)
.onUpdate(->
me.windowHeight = Math.round(this.y)
me.window.moveTo(me.getX(), me.getY())
me.window.resizeTo(Math.round(this.x), Math.round(this.y))
)
.onComplete(->
me.window.setResizable(false)
)
.start()
@animate tween
tweenStack: []
animating: false
animateNext: () =>
if @tweenStack.length
tween = @tweenStack.splice(0,1)[0]
@animating = true
@_animate(tween)
animate: (tween) =>
@tweenStack.push tween
unless @animating
@animateNext()
_animate: (tween) =>
time = if window.performance?.now? then window.performance.now() else Date.now()
if tween.update(time)
setTimeout (()=>@_animate(tween)), 28
else
setTimeout () =>
@animating = false
@animateNext()
, 28
_close: (note) =>
return if note.disposed
close = (note) =>
@stack = _(@stack).reject (n) -> note.model.id is n.model.id
me = @
me.window.setResizable(true) # Can't programatically resize the window on linux without this.
note.$el.css('overflow', 'hidden')
currentHeight = note.$el.outerHeight()
oldHeight = @windowHeight
x = me.getX()
tween = new TWEEN.Tween({elHeight: currentHeight})
.to({elHeight: 0}, @animationDuration)
.easing(TWEEN.Easing.Cubic.In)
.onStart(->
oldHeight = me.windowHeight
)
.onUpdate(->
roundedElHeight = Math.round(this.elHeight)
me.windowHeight = oldHeight - currentHeight + roundedElHeight
me.window.resizeTo(me.windowWidth, me.windowHeight)
me.window.moveTo(x, me.getY())
note.$el?.css({height: roundedElHeight})
)
.onComplete(->
me.window.setResizable(false)
note.dispose()
)
.start()
@animate tween
if @stack.length is 1
@slideOut()
close note
else
close note
dispose: () ->
_show: (note) =>
@$el.append note.el
note.render()
@fitWindow()
unless @shown
@slideIn()
#TODO: Find out why the stupid height is incorrect until the friggin' window is above the fucking task bar..
@fitWindow()
return new NotificationManager
@miklschmidt
Copy link
Author

Found out there's a lot of rendering glitches when a lot of notifications are added/removed at the same time. I implemented a queue so open/close animations don't conflict while trying to resize the window. It solved some problems but also created new ones.

It looks like the DOM's innerHeight takes a while to update, so to animate the window size i have to wait for it to change, which is ugly, and a pain in the ass :/.. Working on a solution, will update the gist if i find one.

@miklschmidt
Copy link
Author

Revision 6 survived my stress testing :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment