mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
update script loading
This commit is contained in:
parent
31b3061157
commit
22f689e089
65 changed files with 732 additions and 6138 deletions
|
@ -1,736 +0,0 @@
|
||||||
(function (name, context, definition) {
|
|
||||||
if (typeof module != 'undefined' && module.exports) module.exports = definition()
|
|
||||||
else if (typeof define == 'function' && define.amd) define(definition)
|
|
||||||
else context[name] = definition()
|
|
||||||
})('bean', this, function (name, context) {
|
|
||||||
name = name || 'bean'
|
|
||||||
context = context || this
|
|
||||||
|
|
||||||
var win = window
|
|
||||||
, old = context[name]
|
|
||||||
, namespaceRegex = /[^\.]*(?=\..*)\.|.*/
|
|
||||||
, nameRegex = /\..*/
|
|
||||||
, addEvent = 'addEventListener'
|
|
||||||
, removeEvent = 'removeEventListener'
|
|
||||||
, doc = document || {}
|
|
||||||
, root = doc.documentElement || {}
|
|
||||||
, W3C_MODEL = root[addEvent]
|
|
||||||
, eventSupport = W3C_MODEL ? addEvent : 'attachEvent'
|
|
||||||
, ONE = {} // singleton for quick matching making add() do one()
|
|
||||||
|
|
||||||
, slice = Array.prototype.slice
|
|
||||||
, str2arr = function (s, d) { return s.split(d || ' ') }
|
|
||||||
, isString = function (o) { return typeof o == 'string' }
|
|
||||||
, isFunction = function (o) { return typeof o == 'function' }
|
|
||||||
|
|
||||||
// events that we consider to be 'native', anything not in this list will
|
|
||||||
// be treated as a custom event
|
|
||||||
, standardNativeEvents =
|
|
||||||
'click dblclick mouseup mousedown contextmenu ' + // mouse buttons
|
|
||||||
'mousewheel mousemultiwheel DOMMouseScroll ' + // mouse wheel
|
|
||||||
'mouseover mouseout mousemove selectstart selectend ' + // mouse movement
|
|
||||||
'keydown keypress keyup ' + // keyboard
|
|
||||||
'orientationchange ' + // mobile
|
|
||||||
'focus blur change reset select submit ' + // form elements
|
|
||||||
'load unload beforeunload resize move DOMContentLoaded ' + // window
|
|
||||||
'readystatechange message ' + // window
|
|
||||||
'error abort scroll ' // misc
|
|
||||||
// element.fireEvent('onXYZ'... is not forgiving if we try to fire an event
|
|
||||||
// that doesn't actually exist, so make sure we only do these on newer browsers
|
|
||||||
, w3cNativeEvents =
|
|
||||||
'show ' + // mouse buttons
|
|
||||||
'input invalid ' + // form elements
|
|
||||||
'touchstart touchmove touchend touchcancel ' + // touch
|
|
||||||
'gesturestart gesturechange gestureend ' + // gesture
|
|
||||||
'textinput ' + // TextEvent
|
|
||||||
'readystatechange pageshow pagehide popstate ' + // window
|
|
||||||
'hashchange offline online ' + // window
|
|
||||||
'afterprint beforeprint ' + // printing
|
|
||||||
'dragstart dragenter dragover dragleave drag drop dragend ' + // dnd
|
|
||||||
'loadstart progress suspend emptied stalled loadmetadata ' + // media
|
|
||||||
'loadeddata canplay canplaythrough playing waiting seeking ' + // media
|
|
||||||
'seeked ended durationchange timeupdate play pause ratechange ' + // media
|
|
||||||
'volumechange cuechange ' + // media
|
|
||||||
'checking noupdate downloading cached updateready obsolete ' // appcache
|
|
||||||
|
|
||||||
// convert to a hash for quick lookups
|
|
||||||
, nativeEvents = (function (hash, events, i) {
|
|
||||||
for (i = 0; i < events.length; i++) events[i] && (hash[events[i]] = 1)
|
|
||||||
return hash
|
|
||||||
}({}, str2arr(standardNativeEvents + (W3C_MODEL ? w3cNativeEvents : ''))))
|
|
||||||
|
|
||||||
// custom events are events that we *fake*, they are not provided natively but
|
|
||||||
// we can use native events to generate them
|
|
||||||
, customEvents = (function () {
|
|
||||||
var isAncestor = 'compareDocumentPosition' in root
|
|
||||||
? function (element, container) {
|
|
||||||
return container.compareDocumentPosition && (container.compareDocumentPosition(element) & 16) === 16
|
|
||||||
}
|
|
||||||
: 'contains' in root
|
|
||||||
? function (element, container) {
|
|
||||||
container = container.nodeType === 9 || container === window ? root : container
|
|
||||||
return container !== element && container.contains(element)
|
|
||||||
}
|
|
||||||
: function (element, container) {
|
|
||||||
while (element = element.parentNode) if (element === container) return 1
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
, check = function (event) {
|
|
||||||
var related = event.relatedTarget
|
|
||||||
return !related
|
|
||||||
? related == null
|
|
||||||
: (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString())
|
|
||||||
&& !isAncestor(related, this))
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
mouseenter: { base: 'mouseover', condition: check }
|
|
||||||
, mouseleave: { base: 'mouseout', condition: check }
|
|
||||||
, mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' }
|
|
||||||
}
|
|
||||||
}())
|
|
||||||
|
|
||||||
// we provide a consistent Event object across browsers by taking the actual DOM
|
|
||||||
// event object and generating a new one from its properties.
|
|
||||||
, Event = (function () {
|
|
||||||
// a whitelist of properties (for different event types) tells us what to check for and copy
|
|
||||||
var commonProps = str2arr('altKey attrChange attrName bubbles cancelable ctrlKey currentTarget ' +
|
|
||||||
'detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey ' +
|
|
||||||
'srcElement target timeStamp type view which propertyName path')
|
|
||||||
, mouseProps = commonProps.concat(str2arr('button buttons clientX clientY dataTransfer ' +
|
|
||||||
'fromElement offsetX offsetY pageX pageY screenX screenY toElement movementX movementY region'))
|
|
||||||
, mouseWheelProps = mouseProps.concat(str2arr('wheelDelta wheelDeltaX wheelDeltaY wheelDeltaZ ' +
|
|
||||||
'axis')) // 'axis' is FF specific
|
|
||||||
, keyProps = commonProps.concat(str2arr('char charCode key keyCode keyIdentifier ' +
|
|
||||||
'keyLocation location isComposing code'))
|
|
||||||
, textProps = commonProps.concat(str2arr('data'))
|
|
||||||
, touchProps = commonProps.concat(str2arr('touches targetTouches changedTouches scale rotation'))
|
|
||||||
, messageProps = commonProps.concat(str2arr('data origin source'))
|
|
||||||
, stateProps = commonProps.concat(str2arr('state'))
|
|
||||||
, overOutRegex = /over|out/
|
|
||||||
// some event types need special handling and some need special properties, do that all here
|
|
||||||
, typeFixers = [
|
|
||||||
{ // key events
|
|
||||||
reg: /key/i
|
|
||||||
, fix: function (event, newEvent) {
|
|
||||||
newEvent.keyCode = event.keyCode || event.which
|
|
||||||
return keyProps
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, { // mouse events
|
|
||||||
reg: /click|mouse(?!(.*wheel|scroll))|menu|drag|drop/i
|
|
||||||
, fix: function (event, newEvent, type) {
|
|
||||||
newEvent.rightClick = event.which === 3 || event.button === 2
|
|
||||||
newEvent.pos = { x: 0, y: 0 }
|
|
||||||
if (event.pageX || event.pageY) {
|
|
||||||
newEvent.clientX = event.pageX
|
|
||||||
newEvent.clientY = event.pageY
|
|
||||||
} else if (event.clientX || event.clientY) {
|
|
||||||
newEvent.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft
|
|
||||||
newEvent.clientY = event.clientY + doc.body.scrollTop + root.scrollTop
|
|
||||||
}
|
|
||||||
if (overOutRegex.test(type)) {
|
|
||||||
newEvent.relatedTarget = event.relatedTarget
|
|
||||||
|| event[(type == 'mouseover' ? 'from' : 'to') + 'Element']
|
|
||||||
}
|
|
||||||
return mouseProps
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, { // mouse wheel events
|
|
||||||
reg: /mouse.*(wheel|scroll)/i
|
|
||||||
, fix: function () { return mouseWheelProps }
|
|
||||||
}
|
|
||||||
, { // TextEvent
|
|
||||||
reg: /^text/i
|
|
||||||
, fix: function () { return textProps }
|
|
||||||
}
|
|
||||||
, { // touch and gesture events
|
|
||||||
reg: /^touch|^gesture/i
|
|
||||||
, fix: function () { return touchProps }
|
|
||||||
}
|
|
||||||
, { // message events
|
|
||||||
reg: /^message$/i
|
|
||||||
, fix: function () { return messageProps }
|
|
||||||
}
|
|
||||||
, { // popstate events
|
|
||||||
reg: /^popstate$/i
|
|
||||||
, fix: function () { return stateProps }
|
|
||||||
}
|
|
||||||
, { // everything else
|
|
||||||
reg: /.*/
|
|
||||||
, fix: function () { return commonProps }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
, typeFixerMap = {} // used to map event types to fixer functions (above), a basic cache mechanism
|
|
||||||
|
|
||||||
, Event = function (event, element, isNative) {
|
|
||||||
if (!arguments.length) return
|
|
||||||
event = event || ((element.ownerDocument || element.document || element).parentWindow || win).event
|
|
||||||
this.originalEvent = event
|
|
||||||
this.isNative = isNative
|
|
||||||
this.isBean = true
|
|
||||||
|
|
||||||
if (!event) return
|
|
||||||
|
|
||||||
var type = event.type
|
|
||||||
, target = event.target || event.srcElement
|
|
||||||
, i, l, p, props, fixer
|
|
||||||
|
|
||||||
this.target = target && target.nodeType === 3 ? target.parentNode : target
|
|
||||||
|
|
||||||
if (isNative) { // we only need basic augmentation on custom events, the rest expensive & pointless
|
|
||||||
fixer = typeFixerMap[type]
|
|
||||||
if (!fixer) { // haven't encountered this event type before, map a fixer function for it
|
|
||||||
for (i = 0, l = typeFixers.length; i < l; i++) {
|
|
||||||
if (typeFixers[i].reg.test(type)) { // guaranteed to match at least one, last is .*
|
|
||||||
typeFixerMap[type] = fixer = typeFixers[i].fix
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
props = fixer(event, this, type)
|
|
||||||
for (i = props.length; i--;) {
|
|
||||||
if (!((p = props[i]) in this) && p in event) this[p] = event[p]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// preventDefault() and stopPropagation() are a consistent interface to those functions
|
|
||||||
// on the DOM, stop() is an alias for both of them together
|
|
||||||
Event.prototype.preventDefault = function () {
|
|
||||||
if (this.originalEvent.preventDefault) this.originalEvent.preventDefault()
|
|
||||||
else this.originalEvent.returnValue = false
|
|
||||||
}
|
|
||||||
Event.prototype.stopPropagation = function () {
|
|
||||||
if (this.originalEvent.stopPropagation) this.originalEvent.stopPropagation()
|
|
||||||
else this.originalEvent.cancelBubble = true
|
|
||||||
}
|
|
||||||
Event.prototype.stop = function () {
|
|
||||||
this.preventDefault()
|
|
||||||
this.stopPropagation()
|
|
||||||
this.stopped = true
|
|
||||||
}
|
|
||||||
// stopImmediatePropagation() has to be handled internally because we manage the event list for
|
|
||||||
// each element
|
|
||||||
// note that originalElement may be a Bean#Event object in some situations
|
|
||||||
Event.prototype.stopImmediatePropagation = function () {
|
|
||||||
if (this.originalEvent.stopImmediatePropagation) this.originalEvent.stopImmediatePropagation()
|
|
||||||
this.isImmediatePropagationStopped = function () { return true }
|
|
||||||
}
|
|
||||||
Event.prototype.isImmediatePropagationStopped = function () {
|
|
||||||
return this.originalEvent.isImmediatePropagationStopped && this.originalEvent.isImmediatePropagationStopped()
|
|
||||||
}
|
|
||||||
Event.prototype.clone = function (currentTarget) {
|
|
||||||
//TODO: this is ripe for optimisation, new events are *expensive*
|
|
||||||
// improving this will speed up delegated events
|
|
||||||
var ne = new Event(this, this.element, this.isNative)
|
|
||||||
ne.currentTarget = currentTarget
|
|
||||||
return ne
|
|
||||||
}
|
|
||||||
|
|
||||||
return Event
|
|
||||||
}())
|
|
||||||
|
|
||||||
// if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both
|
|
||||||
, targetElement = function (element, isNative) {
|
|
||||||
return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bean maintains an internal registry for event listeners. We don't touch elements, objects
|
|
||||||
* or functions to identify them, instead we store everything in the registry.
|
|
||||||
* Each event listener has a RegEntry object, we have one 'registry' for the whole instance.
|
|
||||||
*/
|
|
||||||
, RegEntry = (function () {
|
|
||||||
// each handler is wrapped so we can handle delegation and custom events
|
|
||||||
var wrappedHandler = function (element, fn, condition, args) {
|
|
||||||
var call = function (event, eargs) {
|
|
||||||
return fn.apply(element, args ? slice.call(eargs, event ? 0 : 1).concat(args) : eargs)
|
|
||||||
}
|
|
||||||
, findTarget = function (event, eventElement) {
|
|
||||||
return fn.__beanDel ? fn.__beanDel.ft(event.target, element) : eventElement
|
|
||||||
}
|
|
||||||
, handler = condition
|
|
||||||
? function (event) {
|
|
||||||
var target = findTarget(event, this) // deleated event
|
|
||||||
if (condition.apply(target, arguments)) {
|
|
||||||
if (event) event.currentTarget = target
|
|
||||||
return call(event, arguments)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: function (event) {
|
|
||||||
if (fn.__beanDel) event = event.clone(findTarget(event)) // delegated event, fix the fix
|
|
||||||
return call(event, arguments)
|
|
||||||
}
|
|
||||||
handler.__beanDel = fn.__beanDel
|
|
||||||
return handler
|
|
||||||
}
|
|
||||||
|
|
||||||
, RegEntry = function (element, type, handler, original, namespaces, args, root) {
|
|
||||||
var customType = customEvents[type]
|
|
||||||
, isNative
|
|
||||||
|
|
||||||
if (type == 'unload') {
|
|
||||||
// self clean-up
|
|
||||||
handler = once(removeListener, element, type, handler, original)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customType) {
|
|
||||||
if (customType.condition) {
|
|
||||||
handler = wrappedHandler(element, handler, customType.condition, args)
|
|
||||||
}
|
|
||||||
type = customType.base || type
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isNative = isNative = nativeEvents[type] && !!element[eventSupport]
|
|
||||||
this.customType = !W3C_MODEL && !isNative && type
|
|
||||||
this.element = element
|
|
||||||
this.type = type
|
|
||||||
this.original = original
|
|
||||||
this.namespaces = namespaces
|
|
||||||
this.eventType = W3C_MODEL || isNative ? type : 'propertychange'
|
|
||||||
this.target = targetElement(element, isNative)
|
|
||||||
this[eventSupport] = !!this.target[eventSupport]
|
|
||||||
this.root = root
|
|
||||||
this.handler = wrappedHandler(element, handler, null, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
// given a list of namespaces, is our entry in any of them?
|
|
||||||
RegEntry.prototype.inNamespaces = function (checkNamespaces) {
|
|
||||||
var i, j, c = 0
|
|
||||||
if (!checkNamespaces) return true
|
|
||||||
if (!this.namespaces) return false
|
|
||||||
for (i = checkNamespaces.length; i--;) {
|
|
||||||
for (j = this.namespaces.length; j--;) {
|
|
||||||
if (checkNamespaces[i] == this.namespaces[j]) c++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return checkNamespaces.length === c
|
|
||||||
}
|
|
||||||
|
|
||||||
// match by element, original fn (opt), handler fn (opt)
|
|
||||||
RegEntry.prototype.matches = function (checkElement, checkOriginal, checkHandler) {
|
|
||||||
return this.element === checkElement &&
|
|
||||||
(!checkOriginal || this.original === checkOriginal) &&
|
|
||||||
(!checkHandler || this.handler === checkHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
return RegEntry
|
|
||||||
}())
|
|
||||||
|
|
||||||
, registry = (function () {
|
|
||||||
// our map stores arrays by event type, just because it's better than storing
|
|
||||||
// everything in a single array.
|
|
||||||
// uses '$' as a prefix for the keys for safety and 'r' as a special prefix for
|
|
||||||
// rootListeners so we can look them up fast
|
|
||||||
var map = {}
|
|
||||||
|
|
||||||
// generic functional search of our registry for matching listeners,
|
|
||||||
// `fn` returns false to break out of the loop
|
|
||||||
, forAll = function (element, type, original, handler, root, fn) {
|
|
||||||
var pfx = root ? 'r' : '$'
|
|
||||||
if (!type || type == '*') {
|
|
||||||
// search the whole registry
|
|
||||||
for (var t in map) {
|
|
||||||
if (t.charAt(0) == pfx) {
|
|
||||||
forAll(element, t.substr(1), original, handler, root, fn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var i = 0, l, list = map[pfx + type], all = element == '*'
|
|
||||||
if (!list) return
|
|
||||||
for (l = list.length; i < l; i++) {
|
|
||||||
if ((all || list[i].matches(element, original, handler)) && !fn(list[i], list, i, type)) return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
, has = function (element, type, original, root) {
|
|
||||||
// we're not using forAll here simply because it's a bit slower and this
|
|
||||||
// needs to be fast
|
|
||||||
var i, list = map[(root ? 'r' : '$') + type]
|
|
||||||
if (list) {
|
|
||||||
for (i = list.length; i--;) {
|
|
||||||
if (!list[i].root && list[i].matches(element, original, null)) return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
, get = function (element, type, original, root) {
|
|
||||||
var entries = []
|
|
||||||
forAll(element, type, original, null, root, function (entry) {
|
|
||||||
return entries.push(entry)
|
|
||||||
})
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
, put = function (entry) {
|
|
||||||
var has = !entry.root && !this.has(entry.element, entry.type, null, false)
|
|
||||||
, key = (entry.root ? 'r' : '$') + entry.type
|
|
||||||
;(map[key] || (map[key] = [])).push(entry)
|
|
||||||
return has
|
|
||||||
}
|
|
||||||
|
|
||||||
, del = function (entry) {
|
|
||||||
forAll(entry.element, entry.type, null, entry.handler, entry.root, function (entry, list, i) {
|
|
||||||
list.splice(i, 1)
|
|
||||||
entry.removed = true
|
|
||||||
if (list.length === 0) delete map[(entry.root ? 'r' : '$') + entry.type]
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// dump all entries, used for onunload
|
|
||||||
, entries = function () {
|
|
||||||
var t, entries = []
|
|
||||||
for (t in map) {
|
|
||||||
if (t.charAt(0) == '$') entries = entries.concat(map[t])
|
|
||||||
}
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
return { has: has, get: get, put: put, del: del, entries: entries }
|
|
||||||
}())
|
|
||||||
|
|
||||||
// we need a selector engine for delegated events, use querySelectorAll if it exists
|
|
||||||
// but for older browsers we need Qwery, Sizzle or similar
|
|
||||||
, selectorEngine
|
|
||||||
, setSelectorEngine = function (e) {
|
|
||||||
if (!arguments.length) {
|
|
||||||
selectorEngine = doc.querySelectorAll
|
|
||||||
? function (s, r) {
|
|
||||||
return r.querySelectorAll(s)
|
|
||||||
}
|
|
||||||
: function () {
|
|
||||||
throw new Error('Bean: No selector engine installed') // eeek
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
selectorEngine = e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we attach this listener to each DOM event that we need to listen to, only once
|
|
||||||
// per event type per DOM element
|
|
||||||
, rootListener = function (event, type) {
|
|
||||||
if (!W3C_MODEL && type && event && event.propertyName != '_on' + type) return
|
|
||||||
|
|
||||||
var listeners = registry.get(this, type || event.type, null, false)
|
|
||||||
, l = listeners.length
|
|
||||||
, i = 0
|
|
||||||
|
|
||||||
event = new Event(event, this, true)
|
|
||||||
if (type) event.type = type
|
|
||||||
|
|
||||||
// iterate through all handlers registered for this type, calling them unless they have
|
|
||||||
// been removed by a previous handler or stopImmediatePropagation() has been called
|
|
||||||
for (; i < l && !event.isImmediatePropagationStopped(); i++) {
|
|
||||||
if (!listeners[i].removed) listeners[i].handler.call(this, event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add and remove listeners to DOM elements
|
|
||||||
, listener = W3C_MODEL
|
|
||||||
? function (element, type, add) {
|
|
||||||
// new browsers
|
|
||||||
element[add ? addEvent : removeEvent](type, rootListener, false)
|
|
||||||
}
|
|
||||||
: function (element, type, add, custom) {
|
|
||||||
// IE8 and below, use attachEvent/detachEvent and we have to piggy-back propertychange events
|
|
||||||
// to simulate event bubbling etc.
|
|
||||||
var entry
|
|
||||||
if (add) {
|
|
||||||
registry.put(entry = new RegEntry(
|
|
||||||
element
|
|
||||||
, custom || type
|
|
||||||
, function (event) { // handler
|
|
||||||
rootListener.call(element, event, custom)
|
|
||||||
}
|
|
||||||
, rootListener
|
|
||||||
, null
|
|
||||||
, null
|
|
||||||
, true // is root
|
|
||||||
))
|
|
||||||
if (custom && element['_on' + custom] == null) element['_on' + custom] = 0
|
|
||||||
entry.target.attachEvent('on' + entry.eventType, entry.handler)
|
|
||||||
} else {
|
|
||||||
entry = registry.get(element, custom || type, rootListener, true)[0]
|
|
||||||
if (entry) {
|
|
||||||
entry.target.detachEvent('on' + entry.eventType, entry.handler)
|
|
||||||
registry.del(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
, once = function (rm, element, type, fn, originalFn) {
|
|
||||||
// wrap the handler in a handler that does a remove as well
|
|
||||||
return function () {
|
|
||||||
fn.apply(this, arguments)
|
|
||||||
rm(element, type, originalFn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
, removeListener = function (element, orgType, handler, namespaces) {
|
|
||||||
var type = orgType && orgType.replace(nameRegex, '')
|
|
||||||
, handlers = registry.get(element, type, null, false)
|
|
||||||
, removed = {}
|
|
||||||
, i, l
|
|
||||||
|
|
||||||
for (i = 0, l = handlers.length; i < l; i++) {
|
|
||||||
if ((!handler || handlers[i].original === handler) && handlers[i].inNamespaces(namespaces)) {
|
|
||||||
// TODO: this is problematic, we have a registry.get() and registry.del() that
|
|
||||||
// both do registry searches so we waste cycles doing this. Needs to be rolled into
|
|
||||||
// a single registry.forAll(fn) that removes while finding, but the catch is that
|
|
||||||
// we'll be splicing the arrays that we're iterating over. Needs extra tests to
|
|
||||||
// make sure we don't screw it up. @rvagg
|
|
||||||
registry.del(handlers[i])
|
|
||||||
if (!removed[handlers[i].eventType] && handlers[i][eventSupport])
|
|
||||||
removed[handlers[i].eventType] = { t: handlers[i].eventType, c: handlers[i].type }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// check each type/element for removed listeners and remove the rootListener where it's no longer needed
|
|
||||||
for (i in removed) {
|
|
||||||
if (!registry.has(element, removed[i].t, null, false)) {
|
|
||||||
// last listener of this type, remove the rootListener
|
|
||||||
listener(element, removed[i].t, false, removed[i].c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set up a delegate helper using the given selector, wrap the handler function
|
|
||||||
, delegate = function (selector, fn) {
|
|
||||||
//TODO: findTarget (therefore $) is called twice, once for match and once for
|
|
||||||
// setting e.currentTarget, fix this so it's only needed once
|
|
||||||
var findTarget = function (target, root) {
|
|
||||||
var i, array = isString(selector) ? selectorEngine(selector, root) : selector
|
|
||||||
for (; target && target !== root; target = target.parentNode) {
|
|
||||||
for (i = array.length; i--;) {
|
|
||||||
if (array[i] === target) return target
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, handler = function (e) {
|
|
||||||
var match = findTarget(e.target, this)
|
|
||||||
if (match) fn.apply(match, arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
// __beanDel isn't pleasant but it's a private function, not exposed outside of Bean
|
|
||||||
handler.__beanDel = {
|
|
||||||
ft : findTarget // attach it here for customEvents to use too
|
|
||||||
, selector : selector
|
|
||||||
}
|
|
||||||
return handler
|
|
||||||
}
|
|
||||||
|
|
||||||
, fireListener = W3C_MODEL ? function (isNative, type, element) {
|
|
||||||
// modern browsers, do a proper dispatchEvent()
|
|
||||||
var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents')
|
|
||||||
evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1)
|
|
||||||
element.dispatchEvent(evt)
|
|
||||||
} : function (isNative, type, element) {
|
|
||||||
// old browser use onpropertychange, just increment a custom property to trigger the event
|
|
||||||
element = targetElement(element, isNative)
|
|
||||||
isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Public API: off(), on(), add(), (remove()), one(), fire(), clone()
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* off(element[, eventType(s)[, handler ]])
|
|
||||||
*/
|
|
||||||
, off = function (element, typeSpec, fn) {
|
|
||||||
var isTypeStr = isString(typeSpec)
|
|
||||||
, k, type, namespaces, i
|
|
||||||
|
|
||||||
if (isTypeStr && typeSpec.indexOf(' ') > 0) {
|
|
||||||
// off(el, 't1 t2 t3', fn) or off(el, 't1 t2 t3')
|
|
||||||
typeSpec = str2arr(typeSpec)
|
|
||||||
for (i = typeSpec.length; i--;)
|
|
||||||
off(element, typeSpec[i], fn)
|
|
||||||
return element
|
|
||||||
}
|
|
||||||
|
|
||||||
type = isTypeStr && typeSpec.replace(nameRegex, '')
|
|
||||||
if (type && customEvents[type]) type = customEvents[type].base
|
|
||||||
|
|
||||||
if (!typeSpec || isTypeStr) {
|
|
||||||
// off(el) or off(el, t1.ns) or off(el, .ns) or off(el, .ns1.ns2.ns3)
|
|
||||||
if (namespaces = isTypeStr && typeSpec.replace(namespaceRegex, '')) namespaces = str2arr(namespaces, '.')
|
|
||||||
removeListener(element, type, fn, namespaces)
|
|
||||||
} else if (isFunction(typeSpec)) {
|
|
||||||
// off(el, fn)
|
|
||||||
removeListener(element, null, typeSpec)
|
|
||||||
} else {
|
|
||||||
// off(el, { t1: fn1, t2, fn2 })
|
|
||||||
for (k in typeSpec) {
|
|
||||||
if (typeSpec.hasOwnProperty(k)) off(element, k, typeSpec[k])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return element
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* on(element, eventType(s)[, selector], handler[, args ])
|
|
||||||
*/
|
|
||||||
, on = function(element, events, selector, fn) {
|
|
||||||
var originalFn, type, types, i, args, entry, first
|
|
||||||
|
|
||||||
//TODO: the undefined check means you can't pass an 'args' argument, fix this perhaps?
|
|
||||||
if (selector === undefined && typeof events == 'object') {
|
|
||||||
//TODO: this can't handle delegated events
|
|
||||||
for (type in events) {
|
|
||||||
if (events.hasOwnProperty(type)) {
|
|
||||||
on.call(this, element, type, events[type])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isFunction(selector)) {
|
|
||||||
// delegated event
|
|
||||||
originalFn = fn
|
|
||||||
args = slice.call(arguments, 4)
|
|
||||||
fn = delegate(selector, originalFn, selectorEngine)
|
|
||||||
} else {
|
|
||||||
args = slice.call(arguments, 3)
|
|
||||||
fn = originalFn = selector
|
|
||||||
}
|
|
||||||
|
|
||||||
types = str2arr(events)
|
|
||||||
|
|
||||||
// special case for one(), wrap in a self-removing handler
|
|
||||||
if (this === ONE) {
|
|
||||||
fn = once(off, element, events, fn, originalFn)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = types.length; i--;) {
|
|
||||||
// add new handler to the registry and check if it's the first for this element/type
|
|
||||||
first = registry.put(entry = new RegEntry(
|
|
||||||
element
|
|
||||||
, types[i].replace(nameRegex, '') // event type
|
|
||||||
, fn
|
|
||||||
, originalFn
|
|
||||||
, str2arr(types[i].replace(namespaceRegex, ''), '.') // namespaces
|
|
||||||
, args
|
|
||||||
, false // not root
|
|
||||||
))
|
|
||||||
if (entry[eventSupport] && first) {
|
|
||||||
// first event of this type on this element, add root listener
|
|
||||||
listener(element, entry.eventType, true, entry.customType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return element
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add(element[, selector], eventType(s), handler[, args ])
|
|
||||||
*
|
|
||||||
* Deprecated: kept (for now) for backward-compatibility
|
|
||||||
*/
|
|
||||||
, add = function (element, events, fn, delfn) {
|
|
||||||
return on.apply(
|
|
||||||
null
|
|
||||||
, !isString(fn)
|
|
||||||
? slice.call(arguments)
|
|
||||||
: [ element, fn, events, delfn ].concat(arguments.length > 3 ? slice.call(arguments, 5) : [])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* one(element, eventType(s)[, selector], handler[, args ])
|
|
||||||
*/
|
|
||||||
, one = function () {
|
|
||||||
return on.apply(ONE, arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* fire(element, eventType(s)[, args ])
|
|
||||||
*
|
|
||||||
* The optional 'args' argument must be an array, if no 'args' argument is provided
|
|
||||||
* then we can use the browser's DOM event system, otherwise we trigger handlers manually
|
|
||||||
*/
|
|
||||||
, fire = function (element, type, args) {
|
|
||||||
var types = str2arr(type)
|
|
||||||
, i, j, l, names, handlers
|
|
||||||
|
|
||||||
for (i = types.length; i--;) {
|
|
||||||
type = types[i].replace(nameRegex, '')
|
|
||||||
if (names = types[i].replace(namespaceRegex, '')) names = str2arr(names, '.')
|
|
||||||
if (!names && !args && element[eventSupport]) {
|
|
||||||
fireListener(nativeEvents[type], type, element)
|
|
||||||
} else {
|
|
||||||
// non-native event, either because of a namespace, arguments or a non DOM element
|
|
||||||
// iterate over all listeners and manually 'fire'
|
|
||||||
handlers = registry.get(element, type, null, false)
|
|
||||||
args = [false].concat(args)
|
|
||||||
for (j = 0, l = handlers.length; j < l; j++) {
|
|
||||||
if (handlers[j].inNamespaces(names)) {
|
|
||||||
handlers[j].handler.apply(element, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return element
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* clone(dstElement, srcElement[, eventType ])
|
|
||||||
*
|
|
||||||
* TODO: perhaps for consistency we should allow the same flexibility in type specifiers?
|
|
||||||
*/
|
|
||||||
, clone = function (element, from, type) {
|
|
||||||
var handlers = registry.get(from, type, null, false)
|
|
||||||
, l = handlers.length
|
|
||||||
, i = 0
|
|
||||||
, args, beanDel
|
|
||||||
|
|
||||||
for (; i < l; i++) {
|
|
||||||
if (handlers[i].original) {
|
|
||||||
args = [ element, handlers[i].type ]
|
|
||||||
if (beanDel = handlers[i].handler.__beanDel) args.push(beanDel.selector)
|
|
||||||
args.push(handlers[i].original)
|
|
||||||
on.apply(null, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return element
|
|
||||||
}
|
|
||||||
|
|
||||||
, bean = {
|
|
||||||
'on' : on
|
|
||||||
, 'add' : add
|
|
||||||
, 'one' : one
|
|
||||||
, 'off' : off
|
|
||||||
, 'remove' : off
|
|
||||||
, 'clone' : clone
|
|
||||||
, 'fire' : fire
|
|
||||||
, 'Event' : Event
|
|
||||||
, 'setSelectorEngine' : setSelectorEngine
|
|
||||||
, 'noConflict' : function () {
|
|
||||||
context[name] = old
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for IE, clean up on unload to avoid leaks
|
|
||||||
if (win.attachEvent) {
|
|
||||||
var cleanup = function () {
|
|
||||||
var i, entries = registry.entries()
|
|
||||||
for (i in entries) {
|
|
||||||
if (entries[i].type && entries[i].type !== 'unload') off(entries[i].element, entries[i].type)
|
|
||||||
}
|
|
||||||
win.detachEvent('onunload', cleanup)
|
|
||||||
win.CollectGarbage && win.CollectGarbage()
|
|
||||||
}
|
|
||||||
win.attachEvent('onunload', cleanup)
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize selector engine to internal default (qSA or throw Error)
|
|
||||||
setSelectorEngine()
|
|
||||||
|
|
||||||
return bean
|
|
||||||
});
|
|
|
@ -1,31 +0,0 @@
|
||||||
(function (globalScope) {
|
|
||||||
|
|
||||||
globalScope.Events = {
|
|
||||||
|
|
||||||
on: function (obj, eventName, selector, fn) {
|
|
||||||
|
|
||||||
bean.on(obj, eventName, selector, fn);
|
|
||||||
},
|
|
||||||
|
|
||||||
off: function (obj, eventName, selector, fn) {
|
|
||||||
|
|
||||||
bean.off(obj, eventName, selector);
|
|
||||||
},
|
|
||||||
|
|
||||||
trigger: function (obj, eventName, params) {
|
|
||||||
|
|
||||||
// Need to push an extra param to make the argument order consistent with jquery
|
|
||||||
var newParams = [];
|
|
||||||
newParams.push({});
|
|
||||||
|
|
||||||
if (params && params.length) {
|
|
||||||
for (var i = 0, length = params.length; i < length; i++) {
|
|
||||||
newParams.push(params[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bean.fire(obj, eventName, newParams);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})(window);
|
|
|
@ -39,6 +39,6 @@
|
||||||
"commit": "cec8e49744a1e18b14a711eea77e201bb70de544"
|
"commit": "cec8e49744a1e18b14a711eea77e201bb70de544"
|
||||||
},
|
},
|
||||||
"_source": "git://github.com/desandro/doc-ready.git",
|
"_source": "git://github.com/desandro/doc-ready.git",
|
||||||
"_target": "~1.0.4",
|
"_target": "1.0.x",
|
||||||
"_originalSource": "doc-ready"
|
"_originalSource": "doc-ready"
|
||||||
}
|
}
|
|
@ -31,6 +31,6 @@
|
||||||
"commit": "34fc5e4a0f252964ed2790138b8d7d30d04b55c1"
|
"commit": "34fc5e4a0f252964ed2790138b8d7d30d04b55c1"
|
||||||
},
|
},
|
||||||
"_source": "git://github.com/desandro/get-style-property.git",
|
"_source": "git://github.com/desandro/get-style-property.git",
|
||||||
"_target": "~1.0.4",
|
"_target": "1.x",
|
||||||
"_originalSource": "get-style-property"
|
"_originalSource": "get-style-property"
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "iron-media-query",
|
"name": "iron-media-query",
|
||||||
"version": "1.0.7",
|
"version": "1.0.8",
|
||||||
"description": "Lets you bind to a CSS media query",
|
"description": "Lets you bind to a CSS media query",
|
||||||
"authors": [
|
"authors": [
|
||||||
"The Polymer Authors"
|
"The Polymer Authors"
|
||||||
|
@ -29,11 +29,11 @@
|
||||||
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
|
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
|
||||||
},
|
},
|
||||||
"main": "iron-media-query.html",
|
"main": "iron-media-query.html",
|
||||||
"_release": "1.0.7",
|
"_release": "1.0.8",
|
||||||
"_resolution": {
|
"_resolution": {
|
||||||
"type": "version",
|
"type": "version",
|
||||||
"tag": "v1.0.7",
|
"tag": "v1.0.8",
|
||||||
"commit": "a78cdbadb6de48857d176b964ab906a12872f2d3"
|
"commit": "3f916be171af7a3e03eb019acdfea71055d3c744"
|
||||||
},
|
},
|
||||||
"_source": "git://github.com/PolymerElements/iron-media-query.git",
|
"_source": "git://github.com/PolymerElements/iron-media-query.git",
|
||||||
"_target": "^1.0.0",
|
"_target": "^1.0.0",
|
||||||
|
|
|
@ -11,7 +11,7 @@ env:
|
||||||
- secure: LgnZP4BNGBkTZhf8Vr7r9LdrOwq2/58TqqYkFFloEGBRT6HmumNSRwNbIwOh1U9jSTVkqjC2rn4G27u4XlEIs+QTD2PVSSEKy7Vbn0KxSNCvCGaOB1ZaxWTwZa7nkg09ZFRCHGh+WIbuV+BxyzsjOqlN82GSzFNSb3rxhqDM6dU=
|
- secure: LgnZP4BNGBkTZhf8Vr7r9LdrOwq2/58TqqYkFFloEGBRT6HmumNSRwNbIwOh1U9jSTVkqjC2rn4G27u4XlEIs+QTD2PVSSEKy7Vbn0KxSNCvCGaOB1ZaxWTwZa7nkg09ZFRCHGh+WIbuV+BxyzsjOqlN82GSzFNSb3rxhqDM6dU=
|
||||||
node_js: 4
|
node_js: 4
|
||||||
addons:
|
addons:
|
||||||
firefox: '42.0'
|
firefox: latest
|
||||||
apt:
|
apt:
|
||||||
sources:
|
sources:
|
||||||
- google-chrome
|
- google-chrome
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "iron-media-query",
|
"name": "iron-media-query",
|
||||||
"version": "1.0.7",
|
"version": "1.0.8",
|
||||||
"description": "Lets you bind to a CSS media query",
|
"description": "Lets you bind to a CSS media query",
|
||||||
"authors": [
|
"authors": [
|
||||||
"The Polymer Authors"
|
"The Polymer Authors"
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<!--
|
<!--
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
Code distributed by Google as part of the polymer project is also
|
Code distributed by Google as part of the polymer project is also
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
-->
|
-->
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
|
@ -77,6 +77,7 @@ Example:
|
||||||
},
|
},
|
||||||
|
|
||||||
attached: function() {
|
attached: function() {
|
||||||
|
this.style.display = 'none';
|
||||||
this.queryChanged();
|
this.queryChanged();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html><!--
|
||||||
<!--
|
|
||||||
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
Code distributed by Google as part of the polymer project is also
|
Code distributed by Google as part of the polymer project is also
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
-->
|
--><html><head>
|
||||||
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
|
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Tests</title>
|
<title>Tests</title>
|
||||||
|
@ -19,12 +15,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
WCT.loadSuites([
|
WCT.loadSuites([
|
||||||
'basic.html'
|
'basic.html',
|
||||||
|
'basic.html?dom=shadow'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
</body></html>
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
"tag": "v1.0.8",
|
"tag": "v1.0.8",
|
||||||
"commit": "e9a66727f3da0446f04956d4e4f1dcd51cdec2ff"
|
"commit": "e9a66727f3da0446f04956d4e4f1dcd51cdec2ff"
|
||||||
},
|
},
|
||||||
"_source": "git://github.com/polymerelements/iron-selector.git",
|
"_source": "git://github.com/PolymerElements/iron-selector.git",
|
||||||
"_target": "^1.0.0",
|
"_target": "^1.0.0",
|
||||||
"_originalSource": "polymerelements/iron-selector"
|
"_originalSource": "PolymerElements/iron-selector"
|
||||||
}
|
}
|
46
dashboard-ui/bower_components/paper-collapse-item/.bower.json
vendored
Normal file
46
dashboard-ui/bower_components/paper-collapse-item/.bower.json
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"name": "paper-collapse-item",
|
||||||
|
"version": "1.0.5",
|
||||||
|
"authors": [
|
||||||
|
"collaborne"
|
||||||
|
],
|
||||||
|
"description": "A Material Design item with header and collapsible content (Polymer 1.x)",
|
||||||
|
"main": "paper-collapse-item.html",
|
||||||
|
"keywords": [
|
||||||
|
"web-components",
|
||||||
|
"polymer",
|
||||||
|
"paper",
|
||||||
|
"material design",
|
||||||
|
"lists",
|
||||||
|
"item"
|
||||||
|
],
|
||||||
|
"license": "Apache 2",
|
||||||
|
"homepage": "http://www.apache.org/licenses/LICENSE-2.0",
|
||||||
|
"ignore": [
|
||||||
|
"**/.*"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"polymer": "Polymer/polymer#^1.0.0",
|
||||||
|
"iron-icon": "PolymerElements/iron-icon#^1.0.0",
|
||||||
|
"iron-icons": "PolymerElements/iron-icons#^1.0.0",
|
||||||
|
"iron-collapse": "PolymerElements/iron-collapse#^1.0.0",
|
||||||
|
"paper-icon-button": "PolymerElements/paper-icon-button#^1.0.0",
|
||||||
|
"paper-item": "PolymerElements/paper-item#^1.0.0",
|
||||||
|
"paper-styles": "PolymerElements/paper-styles#^1.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"test-fixture": "PolymerElements/test-fixture#^1.0.0",
|
||||||
|
"web-component-tester": "*",
|
||||||
|
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
|
||||||
|
},
|
||||||
|
"_release": "1.0.5",
|
||||||
|
"_resolution": {
|
||||||
|
"type": "version",
|
||||||
|
"tag": "1.0.5",
|
||||||
|
"commit": "26aef07fcfd1c09265916f6a384cb2adfbb34727"
|
||||||
|
},
|
||||||
|
"_source": "git://github.com/Collaborne/paper-collapse-item.git",
|
||||||
|
"_target": "~1.0.5",
|
||||||
|
"_originalSource": "paper-collapse-item",
|
||||||
|
"_direct": true
|
||||||
|
}
|
202
dashboard-ui/bower_components/paper-collapse-item/LICENSE
vendored
Normal file
202
dashboard-ui/bower_components/paper-collapse-item/LICENSE
vendored
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
36
dashboard-ui/bower_components/paper-collapse-item/bower.json
vendored
Normal file
36
dashboard-ui/bower_components/paper-collapse-item/bower.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"name": "paper-collapse-item",
|
||||||
|
"version": "1.0.5",
|
||||||
|
"authors": [
|
||||||
|
"collaborne"
|
||||||
|
],
|
||||||
|
"description": "A Material Design item with header and collapsible content (Polymer 1.x)",
|
||||||
|
"main": "paper-collapse-item.html",
|
||||||
|
"keywords": [
|
||||||
|
"web-components",
|
||||||
|
"polymer",
|
||||||
|
"paper",
|
||||||
|
"material design",
|
||||||
|
"lists",
|
||||||
|
"item"
|
||||||
|
],
|
||||||
|
"license": "Apache 2",
|
||||||
|
"homepage": "http://www.apache.org/licenses/LICENSE-2.0",
|
||||||
|
"ignore": [
|
||||||
|
"**/.*"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"polymer": "Polymer/polymer#^1.0.0",
|
||||||
|
"iron-icon": "PolymerElements/iron-icon#^1.0.0",
|
||||||
|
"iron-icons": "PolymerElements/iron-icons#^1.0.0",
|
||||||
|
"iron-collapse": "PolymerElements/iron-collapse#^1.0.0",
|
||||||
|
"paper-icon-button": "PolymerElements/paper-icon-button#^1.0.0",
|
||||||
|
"paper-item": "PolymerElements/paper-item#^1.0.0",
|
||||||
|
"paper-styles": "PolymerElements/paper-styles#^1.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"test-fixture": "PolymerElements/test-fixture#^1.0.0",
|
||||||
|
"web-component-tester": "*",
|
||||||
|
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
|
||||||
|
}
|
||||||
|
}
|
19
dashboard-ui/bower_components/paper-collapse-item/demo/index.html
vendored
Normal file
19
dashboard-ui/bower_components/paper-collapse-item/demo/index.html
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="../../webcomponentsjs/webcomponents-lite.js"></script>
|
||||||
|
<link rel="import" href="../../iron-icons/iron-icons.html">
|
||||||
|
<link rel="import" href="../paper-collapse-item.html">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<paper-collapse-item icon="icons:favorite" header="Item 1" opened>
|
||||||
|
Lots of very interesting content.
|
||||||
|
</paper-collapse-item>
|
||||||
|
<paper-collapse-item icon="icons:info" header="Item 2">
|
||||||
|
Lots of very interesting content.
|
||||||
|
</paper-collapse-item>
|
||||||
|
<paper-collapse-item icon="icons:help" header="Item 3">
|
||||||
|
Lots of very interesting content.
|
||||||
|
</paper-collapse-item>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
dashboard-ui/bower_components/paper-collapse-item/doc/screenshot.png
vendored
Normal file
BIN
dashboard-ui/bower_components/paper-collapse-item/doc/screenshot.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
83
dashboard-ui/bower_components/paper-collapse-item/paper-collapse-item.html
vendored
Normal file
83
dashboard-ui/bower_components/paper-collapse-item/paper-collapse-item.html
vendored
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<link rel="import" href="../polymer/polymer.html">
|
||||||
|
<link rel="import" href="../iron-icon/iron-icon.html">
|
||||||
|
<link rel="import" href="../iron-collapse/iron-collapse.html">
|
||||||
|
<link rel="import" href="../paper-icon-button/paper-icon-button.html">
|
||||||
|
<link rel="import" href="../paper-item/paper-item.html">
|
||||||
|
<link rel="import" href="../paper-item/paper-item-body.html">
|
||||||
|
<link rel="import" href="../paper-styles/paper-styles.html">
|
||||||
|
|
||||||
|
<dom-module id='paper-collapse-item'>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.header {
|
||||||
|
min-height: 48px;
|
||||||
|
opacity: var(--dark-primary-opacity);
|
||||||
|
@apply(--layout-horizontal);
|
||||||
|
@apply(--layout-center-center);
|
||||||
|
@apply(--paper-font-subhead);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: 24px;
|
||||||
|
--iron-icon-height: 32px;
|
||||||
|
--iron-icon-width: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon,.toogle {
|
||||||
|
opacity: var(--dark-disabled-opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
opacity: var(--dark-primary-opacity);
|
||||||
|
@apply(--paper-font-body1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<paper-item on-tap="_toggleOpened">
|
||||||
|
<paper-item-body>
|
||||||
|
<div class="header">
|
||||||
|
<iron-icon class='icon' icon=[[icon]]></iron-icon>
|
||||||
|
<div class="flex">[[header]]</div>
|
||||||
|
<paper-icon-button class='toogle' icon=[[_toggleIcon]]></paper-icon-button>
|
||||||
|
</div>
|
||||||
|
<iron-collapse class="content" opened={{opened}}>
|
||||||
|
<content></content>
|
||||||
|
</iron-collapse>
|
||||||
|
</paper-item-body>
|
||||||
|
</paper-item>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
</dom-module>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
Polymer({
|
||||||
|
is: 'paper-collapse-item',
|
||||||
|
properties: {
|
||||||
|
header: String,
|
||||||
|
icon: String,
|
||||||
|
|
||||||
|
opened: Boolean,
|
||||||
|
|
||||||
|
_toggleIcon: {
|
||||||
|
type: String,
|
||||||
|
computed: '_computeToggleIcon(opened)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Private methods
|
||||||
|
_toggleOpened: function(e) {
|
||||||
|
this.opened = !this.opened;
|
||||||
|
},
|
||||||
|
_computeToggleIcon: function(opened) {
|
||||||
|
return opened ? "icons:expand-less" : "icons:expand-more";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
</script>
|
13
dashboard-ui/bower_components/paper-collapse-item/test/index.html
vendored
Normal file
13
dashboard-ui/bower_components/paper-collapse-item/test/index.html
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Tests</title>
|
||||||
|
<script src="../../web-component-tester/browser.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
WCT.loadSuites([ 'paper-collapse-item.html' ]);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
50
dashboard-ui/bower_components/paper-collapse-item/test/paper-collapse-item.html
vendored
Normal file
50
dashboard-ui/bower_components/paper-collapse-item/test/paper-collapse-item.html
vendored
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>paper-collapse-item</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<script src="../../webcomponentsjs/webcomponents-lite.js"></script>
|
||||||
|
<script src="../../web-component-tester/browser.js"></script>
|
||||||
|
<script src="../../test-fixture/test-fixture-mocha.js"></script>
|
||||||
|
|
||||||
|
<link rel="import" href="../paper-collapse-item.html">
|
||||||
|
<link rel="import" href="../../test-fixture/test-fixture.html">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<test-fixture id="TrivialElement">
|
||||||
|
<template>
|
||||||
|
<paper-collapse-item></paper-collapse-item>
|
||||||
|
</template>
|
||||||
|
</test-fixture>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
suite('<paper-collapse-item>', function() {
|
||||||
|
suite('open/close behavior', function() {
|
||||||
|
var element;
|
||||||
|
|
||||||
|
setup(function() {
|
||||||
|
element = fixture('TrivialElement');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('defaults to closed', function() {
|
||||||
|
expect(element.opened).to.be.eql(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows open toggle icon when closed', function() {
|
||||||
|
element.opened = false;
|
||||||
|
expect(element._toggleIcon).to.be.eql('icons:expand-more');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('shows open toggle icon when opened', function() {
|
||||||
|
element.opened = true;
|
||||||
|
expect(element._toggleIcon).to.be.eql('icons:expand-less');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "paper-input",
|
"name": "paper-input",
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"description": "Material design text fields",
|
"description": "Material design text fields",
|
||||||
"authors": [
|
"authors": [
|
||||||
"The Polymer Authors"
|
"The Polymer Authors"
|
||||||
|
@ -32,7 +32,8 @@
|
||||||
"iron-behaviors": "PolymerElements/iron-behaviors#^1.0.0",
|
"iron-behaviors": "PolymerElements/iron-behaviors#^1.0.0",
|
||||||
"iron-form-element-behavior": "PolymerElements/iron-form-element-behavior#^1.0.0",
|
"iron-form-element-behavior": "PolymerElements/iron-form-element-behavior#^1.0.0",
|
||||||
"iron-input": "PolymerElements/iron-input#^1.0.0",
|
"iron-input": "PolymerElements/iron-input#^1.0.0",
|
||||||
"paper-styles": "PolymerElements/paper-styles#^1.0.0"
|
"paper-styles": "PolymerElements/paper-styles#^1.0.0",
|
||||||
|
"iron-a11y-keys-behavior": "PolymerElements/iron-a11y-keys-behavior#^1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"iron-component-page": "PolymerElements/iron-component-page#^1.0.0",
|
"iron-component-page": "PolymerElements/iron-component-page#^1.0.0",
|
||||||
|
@ -44,11 +45,11 @@
|
||||||
"web-component-tester": "Polymer/web-component-tester#^3.3.0",
|
"web-component-tester": "Polymer/web-component-tester#^3.3.0",
|
||||||
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
|
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
|
||||||
},
|
},
|
||||||
"_release": "1.1.1",
|
"_release": "1.1.2",
|
||||||
"_resolution": {
|
"_resolution": {
|
||||||
"type": "version",
|
"type": "version",
|
||||||
"tag": "v1.1.1",
|
"tag": "v1.1.2",
|
||||||
"commit": "1bbce220b027dc030b294163f7da6f3e9052ab13"
|
"commit": "6c6ba4b5e3e4b18ee387d2b922e00e3edfe0e347"
|
||||||
},
|
},
|
||||||
"_source": "git://github.com/polymerelements/paper-input.git",
|
"_source": "git://github.com/polymerelements/paper-input.git",
|
||||||
"_target": "^1.0.9",
|
"_target": "^1.0.9",
|
||||||
|
|
|
@ -11,7 +11,7 @@ env:
|
||||||
- secure: nh65tvhnhOrK05qKvDJKMV7Jm9yiCoG1wFkP3ZnqOHix9Ny+KmcTa41Bl6NXQdvYaMTFtzS7lMZX5cqIziyKyGWHVN30LzGMHJNz12fhcMi3nJ84trhQGcu/9qR9yDv16q9ouGlcz1VxnDOHaRAHnIKjLIbhN3aJtMtZBbnWihA=
|
- secure: nh65tvhnhOrK05qKvDJKMV7Jm9yiCoG1wFkP3ZnqOHix9Ny+KmcTa41Bl6NXQdvYaMTFtzS7lMZX5cqIziyKyGWHVN30LzGMHJNz12fhcMi3nJ84trhQGcu/9qR9yDv16q9ouGlcz1VxnDOHaRAHnIKjLIbhN3aJtMtZBbnWihA=
|
||||||
node_js: 4
|
node_js: 4
|
||||||
addons:
|
addons:
|
||||||
firefox: '42.0'
|
firefox: latest
|
||||||
apt:
|
apt:
|
||||||
sources:
|
sources:
|
||||||
- google-chrome
|
- google-chrome
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "paper-input",
|
"name": "paper-input",
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"description": "Material design text fields",
|
"description": "Material design text fields",
|
||||||
"authors": [
|
"authors": [
|
||||||
"The Polymer Authors"
|
"The Polymer Authors"
|
||||||
|
@ -32,7 +32,8 @@
|
||||||
"iron-behaviors": "PolymerElements/iron-behaviors#^1.0.0",
|
"iron-behaviors": "PolymerElements/iron-behaviors#^1.0.0",
|
||||||
"iron-form-element-behavior": "PolymerElements/iron-form-element-behavior#^1.0.0",
|
"iron-form-element-behavior": "PolymerElements/iron-form-element-behavior#^1.0.0",
|
||||||
"iron-input": "PolymerElements/iron-input#^1.0.0",
|
"iron-input": "PolymerElements/iron-input#^1.0.0",
|
||||||
"paper-styles": "PolymerElements/paper-styles#^1.0.0"
|
"paper-styles": "PolymerElements/paper-styles#^1.0.0",
|
||||||
|
"iron-a11y-keys-behavior": "PolymerElements/iron-a11y-keys-behavior#^1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"iron-component-page": "PolymerElements/iron-component-page#^1.0.0",
|
"iron-component-page": "PolymerElements/iron-component-page#^1.0.0",
|
||||||
|
|
|
@ -56,6 +56,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
|
|
||||||
<div class="vertical center-justified layout">
|
<div class="vertical center-justified layout">
|
||||||
<h4>Text input</h4>
|
<h4>Text input</h4>
|
||||||
|
|
||||||
<div class="vertical-section">
|
<div class="vertical-section">
|
||||||
<paper-input label="label"></paper-input>
|
<paper-input label="label"></paper-input>
|
||||||
|
|
||||||
|
@ -90,7 +91,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
|
|
||||||
<paper-textarea label="textarea" char-counter></paper-textarea>
|
<paper-textarea label="textarea" char-counter></paper-textarea>
|
||||||
|
|
||||||
<paper-textarea label="textarea with maxlength" char-counter maxlength="10"></paper-textarea>
|
<paper-textarea label="textarea with maxlength" maxlength="10" char-counter></paper-textarea>
|
||||||
|
|
||||||
<paper-textarea label="text area with rows and max-rows" rows="3" max-rows="4"></paper-textarea>
|
<paper-textarea label="text area with rows and max-rows" rows="3" max-rows="4"></paper-textarea>
|
||||||
</div>
|
</div>
|
||||||
|
@ -108,7 +109,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
<paper-input class="short" label="icons and buttons" id="inputWithButton">
|
<paper-input class="short" label="icons and buttons" id="inputWithButton">
|
||||||
<iron-icon icon="search" prefix></iron-icon>
|
<iron-icon icon="search" prefix></iron-icon>
|
||||||
<paper-icon-button suffix onclick="clearInput()"
|
<paper-icon-button suffix onclick="clearInput()"
|
||||||
icon="clear" alt="clear" title="clear" tabindex="0">
|
icon="clear" alt="clear" title="clear">
|
||||||
</paper-icon-button>
|
</paper-icon-button>
|
||||||
</paper-input>
|
</paper-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,6 +9,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
-->
|
-->
|
||||||
<link rel="import" href="../polymer/polymer.html">
|
<link rel="import" href="../polymer/polymer.html">
|
||||||
<link rel="import" href="../iron-behaviors/iron-control-state.html">
|
<link rel="import" href="../iron-behaviors/iron-control-state.html">
|
||||||
|
<link rel="import" href="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
@ -32,7 +33,8 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The label for this input. Bind this to `<paper-input-container>`'s `label` property.
|
* The label for this input. Bind this to `<label>`'s content and `hidden` property, e.g.
|
||||||
|
* `<label hidden$="[[!label]]">[[label]]</label>` in your `template`
|
||||||
*/
|
*/
|
||||||
label: {
|
label: {
|
||||||
type: String
|
type: String
|
||||||
|
@ -287,21 +289,21 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind this to the `<input is="iron-input">`'s `results` property, , used with type=search.
|
* Bind this to the `<input is="iron-input">`'s `results` property, used with type=search.
|
||||||
*/
|
*/
|
||||||
results: {
|
results: {
|
||||||
type: Number
|
type: Number
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind this to the `<input is="iron-input">`'s `accept` property, , used with type=file.
|
* Bind this to the `<input is="iron-input">`'s `accept` property, used with type=file.
|
||||||
*/
|
*/
|
||||||
accept: {
|
accept: {
|
||||||
type: String
|
type: String
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind this to the `<input is="iron-input">`'s `multiple` property, , used with type=file.
|
* Bind this to the `<input is="iron-input">`'s `multiple` property, used with type=file.
|
||||||
*/
|
*/
|
||||||
multiple: {
|
multiple: {
|
||||||
type: Boolean
|
type: Boolean
|
||||||
|
@ -320,13 +322,22 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
},
|
},
|
||||||
|
|
||||||
listeners: {
|
listeners: {
|
||||||
'addon-attached': '_onAddonAttached'
|
'addon-attached': '_onAddonAttached',
|
||||||
|
'focus': '_onFocus'
|
||||||
},
|
},
|
||||||
|
|
||||||
observers: [
|
observers: [
|
||||||
'_focusedControlStateChanged(focused)'
|
'_focusedControlStateChanged(focused)'
|
||||||
],
|
],
|
||||||
|
|
||||||
|
keyBindings: {
|
||||||
|
'shift+tab:keydown': '_onShiftTabDown'
|
||||||
|
},
|
||||||
|
|
||||||
|
hostAttributes: {
|
||||||
|
tabindex: 0
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a reference to the input element.
|
* Returns a reference to the input element.
|
||||||
*/
|
*/
|
||||||
|
@ -334,6 +345,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
return this.$.input;
|
return this.$.input;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a reference to the focusable element.
|
||||||
|
*/
|
||||||
|
get _focusableElement() {
|
||||||
|
return this.inputElement;
|
||||||
|
},
|
||||||
|
|
||||||
attached: function() {
|
attached: function() {
|
||||||
this._updateAriaLabelledBy();
|
this._updateAriaLabelledBy();
|
||||||
},
|
},
|
||||||
|
@ -367,6 +385,29 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
return this.inputElement.validate();
|
return this.inputElement.validate();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forward focus to inputElement
|
||||||
|
*/
|
||||||
|
_onFocus: function() {
|
||||||
|
if (!this._shiftTabPressed) {
|
||||||
|
this._focusableElement.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler that is called when a shift+tab keypress is detected by the menu.
|
||||||
|
*
|
||||||
|
* @param {CustomEvent} event A key combination event.
|
||||||
|
*/
|
||||||
|
_onShiftTabDown: function(event) {
|
||||||
|
var oldTabIndex = this.getAttribute('tabindex');
|
||||||
|
this._shiftTabPressed = true;
|
||||||
|
this.setAttribute('tabindex', '-1');
|
||||||
|
this.async(function() {
|
||||||
|
this.setAttribute('tabindex', oldTabIndex);
|
||||||
|
this._shiftTabPressed = false;
|
||||||
|
}, 1);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* If `autoValidate` is true, then validates the element.
|
* If `autoValidate` is true, then validates the element.
|
||||||
*/
|
*/
|
||||||
|
@ -451,6 +492,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @polymerBehavior */
|
/** @polymerBehavior */
|
||||||
Polymer.PaperInputBehavior = [Polymer.IronControlState, Polymer.PaperInputBehaviorImpl];
|
Polymer.PaperInputBehavior = [
|
||||||
|
Polymer.IronControlState,
|
||||||
|
Polymer.IronA11yKeysBehavior,
|
||||||
|
Polymer.PaperInputBehaviorImpl
|
||||||
|
];
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -83,7 +83,7 @@ Custom property | Description | Default
|
||||||
`--paper-input-container-label-focus` | Mixin applied to the label when the input is focused | `{}`
|
`--paper-input-container-label-focus` | Mixin applied to the label when the input is focused | `{}`
|
||||||
`--paper-input-container-input` | Mixin applied to the input | `{}`
|
`--paper-input-container-input` | Mixin applied to the input | `{}`
|
||||||
`--paper-input-container-underline` | Mixin applied to the underline | `{}`
|
`--paper-input-container-underline` | Mixin applied to the underline | `{}`
|
||||||
`--paper-input-container-underline-focus` | Mixin applied to the underline when the input is focued | `{}`
|
`--paper-input-container-underline-focus` | Mixin applied to the underline when the input is focused | `{}`
|
||||||
`--paper-input-container-underline-disabled` | Mixin applied to the underline when the input is disabled | `{}`
|
`--paper-input-container-underline-disabled` | Mixin applied to the underline when the input is disabled | `{}`
|
||||||
`--paper-input-prefix` | Mixin applied to the input prefix | `{}`
|
`--paper-input-prefix` | Mixin applied to the input prefix | `{}`
|
||||||
`--paper-input-suffix` | Mixin applied to the input suffix | `{}`
|
`--paper-input-suffix` | Mixin applied to the input suffix | `{}`
|
||||||
|
@ -179,7 +179,7 @@ This element is `display:block` by default, but you can set the `inline` attribu
|
||||||
.input-content {
|
.input-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
@apply(--layout-horizontal);
|
@apply(--layout-horizontal);
|
||||||
@apply(--layout-end);
|
@apply(--layout-center);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-content ::content label,
|
.input-content ::content label,
|
||||||
|
|
|
@ -48,11 +48,17 @@ as to not overlap the native UI (search icon, file button, etc.).
|
||||||
|
|
||||||
See `Polymer.PaperInputBehavior` for more API docs.
|
See `Polymer.PaperInputBehavior` for more API docs.
|
||||||
|
|
||||||
|
### Focus
|
||||||
|
|
||||||
|
To focus a paper-input, you can call the native `focus()` method as long as the
|
||||||
|
paper input has a tab index.
|
||||||
|
|
||||||
### Styling
|
### Styling
|
||||||
|
|
||||||
See `Polymer.PaperInputContainer` for a list of custom properties used to
|
See `Polymer.PaperInputContainer` for a list of custom properties used to
|
||||||
style this element.
|
style this element.
|
||||||
|
|
||||||
|
|
||||||
@group Paper Elements
|
@group Paper Elements
|
||||||
@element paper-input
|
@element paper-input
|
||||||
@hero hero.svg
|
@hero hero.svg
|
||||||
|
@ -118,6 +124,7 @@ style this element.
|
||||||
autocapitalize$="[[autocapitalize]]"
|
autocapitalize$="[[autocapitalize]]"
|
||||||
autocorrect$="[[autocorrect]]"
|
autocorrect$="[[autocorrect]]"
|
||||||
on-change="_onChange"
|
on-change="_onChange"
|
||||||
|
tabindex$="[[tabindex]]"
|
||||||
autosave$="[[autosave]]"
|
autosave$="[[autosave]]"
|
||||||
results$="[[results]]"
|
results$="[[results]]"
|
||||||
accept$="[[accept]]"
|
accept$="[[accept]]"
|
||||||
|
@ -144,8 +151,7 @@ style this element.
|
||||||
|
|
||||||
behaviors: [
|
behaviors: [
|
||||||
Polymer.IronFormElementBehavior,
|
Polymer.IronFormElementBehavior,
|
||||||
Polymer.PaperInputBehavior,
|
Polymer.PaperInputBehavior
|
||||||
Polymer.IronControlState
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -123,6 +123,10 @@ style this element.
|
||||||
|
|
||||||
_ariaDescribedByChanged: function(ariaDescribedBy) {
|
_ariaDescribedByChanged: function(ariaDescribedBy) {
|
||||||
this.$.input.textarea.setAttribute('aria-describedby', ariaDescribedBy);
|
this.$.input.textarea.setAttribute('aria-describedby', ariaDescribedBy);
|
||||||
}
|
},
|
||||||
|
|
||||||
|
get _focusableElement() {
|
||||||
|
return this.$.input.textarea;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html><!--
|
||||||
<!--
|
|
||||||
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
||||||
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
||||||
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
||||||
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
||||||
Code distributed by Google as part of the polymer project is also
|
Code distributed by Google as part of the polymer project is also
|
||||||
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
||||||
-->
|
--><html><head>
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||||
<title>paper-input tests</title>
|
<title>paper-input tests</title>
|
||||||
|
@ -21,8 +18,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
'paper-textarea.html',
|
'paper-textarea.html',
|
||||||
'paper-input-container.html',
|
'paper-input-container.html',
|
||||||
'paper-input-error.html',
|
'paper-input-error.html',
|
||||||
'paper-input-char-counter.html'
|
'paper-input-char-counter.html',
|
||||||
|
'paper-input.html?dom=shadow',
|
||||||
|
'paper-textarea.html?dom=shadow',
|
||||||
|
'paper-input-container.html?dom=shadow',
|
||||||
|
'paper-input-error.html?dom=shadow',
|
||||||
|
'paper-input-char-counter.html?dom=shadow'
|
||||||
]);
|
]);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
</body></html>
|
||||||
|
|
|
@ -37,6 +37,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
</template>
|
</template>
|
||||||
</test-fixture>
|
</test-fixture>
|
||||||
|
|
||||||
|
<test-fixture id="has-tabindex">
|
||||||
|
<template>
|
||||||
|
<paper-input tabindex="0"></paper-input>
|
||||||
|
</template>
|
||||||
|
</test-fixture>
|
||||||
|
|
||||||
<test-fixture id="label">
|
<test-fixture id="label">
|
||||||
<template>
|
<template>
|
||||||
<paper-input label="foo"></paper-input>
|
<paper-input label="foo"></paper-input>
|
||||||
|
@ -211,7 +217,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
suite('validation', function() {
|
suite('validation', function() {
|
||||||
|
|
||||||
test('invalid attribute updated after calling validate()', function() {
|
test('invalid attribute updated after calling validate()', function() {
|
||||||
|
@ -255,6 +260,18 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
assert.equal(input.inputElement.getAttribute('aria-describedby'), Polymer.dom(input.root).querySelector('paper-input-error').id + ' ' + Polymer.dom(input.root).querySelector('paper-input-char-counter').id, 'aria-describedby points to the error message and character counter');
|
assert.equal(input.inputElement.getAttribute('aria-describedby'), Polymer.dom(input.root).querySelector('paper-input-error').id + ' ' + Polymer.dom(input.root).querySelector('paper-input-char-counter').id, 'aria-describedby points to the error message and character counter');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('focus an input with tabindex', function(done) {
|
||||||
|
var input = fixture('has-tabindex');
|
||||||
|
flush(function() {
|
||||||
|
MockInteractions.focus(input);
|
||||||
|
flush(function() {
|
||||||
|
assert.equal(input.shadowRoot ? input.shadowRoot.activeElement :
|
||||||
|
document.activeElement, input._focusableElement);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -36,6 +36,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
</template>
|
</template>
|
||||||
</test-fixture>
|
</test-fixture>
|
||||||
|
|
||||||
|
<test-fixture id="has-tabindex">
|
||||||
|
<template>
|
||||||
|
<paper-textarea tabindex="0"></paper-input>
|
||||||
|
</template>
|
||||||
|
</test-fixture>
|
||||||
|
|
||||||
<test-fixture id="label">
|
<test-fixture id="label">
|
||||||
<template>
|
<template>
|
||||||
<paper-textarea label="foo"></paper-textarea>
|
<paper-textarea label="foo"></paper-textarea>
|
||||||
|
@ -128,7 +134,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
var inputContent = Polymer.dom(container.root).querySelector('.input-content');
|
var inputContent = Polymer.dom(container.root).querySelector('.input-content');
|
||||||
assert.isTrue(inputContent.classList.contains('label-is-floating'), 'label is floating');
|
assert.isTrue(inputContent.classList.contains('label-is-floating'), 'label is floating');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
suite('focus/blur events', function() {
|
suite('focus/blur events', function() {
|
||||||
|
@ -155,6 +160,17 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
assert.isTrue(nBlurEvents >= 1, 'blur event fired')
|
assert.isTrue(nBlurEvents >= 1, 'blur event fired')
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('focus a textarea with tabindex', function(done) {
|
||||||
|
var input = fixture('has-tabindex');
|
||||||
|
flush(function() {
|
||||||
|
assert.notEqual(document.activeElement, input._focusableElement);
|
||||||
|
MockInteractions.focus(input);
|
||||||
|
setTimeout(function() {
|
||||||
|
assert.equal(document.activeElement, input.shadowRoot ? input : input._focusableElement);
|
||||||
|
done();
|
||||||
|
}, 1);
|
||||||
|
})
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
suite('a11y', function() {
|
suite('a11y', function() {
|
||||||
|
@ -186,7 +202,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
|
||||||
assert.equal(input.inputElement.textarea.getAttribute('aria-describedby'), Polymer.dom(input.root).querySelector('paper-input-error').id + ' ' + Polymer.dom(input.root).querySelector('paper-input-char-counter').id, 'aria-describedby points to the error message and character counter');
|
assert.equal(input.inputElement.textarea.getAttribute('aria-describedby'), Polymer.dom(input.root).querySelector('paper-input-error').id + ' ' + Polymer.dom(input.root).querySelector('paper-input-char-counter').id, 'aria-describedby points to the error message and character counter');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,14 +32,14 @@
|
||||||
"iron-test-helpers": "PolymerElements/iron-test-helpers#^1.0.0"
|
"iron-test-helpers": "PolymerElements/iron-test-helpers#^1.0.0"
|
||||||
},
|
},
|
||||||
"ignore": [],
|
"ignore": [],
|
||||||
"homepage": "https://github.com/PolymerElements/paper-ripple",
|
"homepage": "https://github.com/polymerelements/paper-ripple",
|
||||||
"_release": "1.0.5",
|
"_release": "1.0.5",
|
||||||
"_resolution": {
|
"_resolution": {
|
||||||
"type": "version",
|
"type": "version",
|
||||||
"tag": "v1.0.5",
|
"tag": "v1.0.5",
|
||||||
"commit": "d72e7a9a8ab518b901ed18dde492df3b87a93be5"
|
"commit": "d72e7a9a8ab518b901ed18dde492df3b87a93be5"
|
||||||
},
|
},
|
||||||
"_source": "git://github.com/PolymerElements/paper-ripple.git",
|
"_source": "git://github.com/polymerelements/paper-ripple.git",
|
||||||
"_target": "^1.0.0",
|
"_target": "^1.0.0",
|
||||||
"_originalSource": "PolymerElements/paper-ripple"
|
"_originalSource": "polymerelements/paper-ripple"
|
||||||
}
|
}
|
46
dashboard-ui/components/requirehtml.js
Normal file
46
dashboard-ui/components/requirehtml.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
define(function () {
|
||||||
|
var cssAPI = {};
|
||||||
|
|
||||||
|
cssAPI.normalize = function (name, normalize) {
|
||||||
|
if (name.substr(name.length - 5, 5) == '.html')
|
||||||
|
name = name.substr(0, name.length - 5);
|
||||||
|
|
||||||
|
return normalize(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
var importedFiles = [];
|
||||||
|
|
||||||
|
cssAPI.load = function (cssId, req, load, config) {
|
||||||
|
|
||||||
|
// Somehow if the url starts with /css, require will get all screwed up since this extension is also called css
|
||||||
|
cssId = cssId.replace('js/requirehtml', 'html');
|
||||||
|
|
||||||
|
var url = cssId + '.html';
|
||||||
|
|
||||||
|
if (url.indexOf('http') != 0 && url.indexOf('file:') != 0) {
|
||||||
|
url = config.baseUrl + url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importedFiles.indexOf(url) == -1) {
|
||||||
|
importedFiles.push(url);
|
||||||
|
|
||||||
|
var link = document.createElement('link');
|
||||||
|
link.rel = 'import';
|
||||||
|
|
||||||
|
if (url.toLowerCase().indexOf('bower_') == -1) {
|
||||||
|
url = url + "?" + config.urlArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
link.onload = load;
|
||||||
|
link.href = url;
|
||||||
|
|
||||||
|
document.head.appendChild(link);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cssAPI;
|
||||||
|
});
|
63
dashboard-ui/cordova/actionsheet.js
vendored
63
dashboard-ui/cordova/actionsheet.js
vendored
|
@ -1,63 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function show(options) {
|
|
||||||
|
|
||||||
// items
|
|
||||||
// positionTo
|
|
||||||
// showCancel
|
|
||||||
// title
|
|
||||||
|
|
||||||
// If any items have an icon, give them all an icon just to make sure they're all lined up evenly
|
|
||||||
var renderIcon = options.items.filter(function (o) {
|
|
||||||
return o.ironIcon == 'check';
|
|
||||||
}).length;
|
|
||||||
|
|
||||||
if (renderIcon) {
|
|
||||||
|
|
||||||
for (var i = 0, length = options.items.length; i < length; i++) {
|
|
||||||
|
|
||||||
var option = options.items[i];
|
|
||||||
|
|
||||||
switch (option.ironIcon) {
|
|
||||||
|
|
||||||
case 'check':
|
|
||||||
option.name = '• ' + option.name;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
option.name = ' ' + option.name;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var innerOptions = {
|
|
||||||
'title': options.title,
|
|
||||||
'buttonLabels': options.items.map(function (i) {
|
|
||||||
return i.name;
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// Show cancel unless the caller explicitly set it to false
|
|
||||||
if (options.showCancel !== false) {
|
|
||||||
innerOptions.addCancelButtonWithLabel = Globalize.translate('ButtonCancel');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Depending on the buttonIndex, you can now call shareViaFacebook or shareViaTwitter
|
|
||||||
// of the SocialSharing plugin (https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin)
|
|
||||||
window.plugins.actionsheet.show(innerOptions, function (index) {
|
|
||||||
|
|
||||||
if (options.callback) {
|
|
||||||
|
|
||||||
// Results are 1-based
|
|
||||||
if (index >= 1 && options.items.length >= index) {
|
|
||||||
|
|
||||||
options.callback(options.items[index - 1].id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.ActionSheetElement = {
|
|
||||||
show: show
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -1,95 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function updateCredentials() {
|
|
||||||
|
|
||||||
Logger.log('sending updated credentials to ApiClientBridge');
|
|
||||||
|
|
||||||
var json = JSON.stringify(ConnectionManager.credentialProvider().credentials());
|
|
||||||
var credentials = JSON.parse(json);
|
|
||||||
|
|
||||||
for (var i = 0, length = credentials.Servers.length; i < length; i++) {
|
|
||||||
var server = credentials.Servers[i];
|
|
||||||
|
|
||||||
if (server.DateLastAccessed != null) {
|
|
||||||
server.DateLastAccessed = new Date(server.DateLastAccessed).toISOString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
json = JSON.stringify(credentials);
|
|
||||||
ApiClientBridge.updateCredentials(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initNativeConnectionManager() {
|
|
||||||
|
|
||||||
Logger.log('initNativeConnectionManager');
|
|
||||||
|
|
||||||
var capabilities = ConnectionManager.capabilities();
|
|
||||||
|
|
||||||
ApiClientBridge.init(AppInfo.appName, AppInfo.appVersion, AppInfo.deviceId, AppInfo.deviceName, JSON.stringify(capabilities));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDownloadSpeed(bytes, url) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
ApiClientBridge.getDownloadSpeed(bytes, url);
|
|
||||||
|
|
||||||
Events.on(AndroidAjax, 'downloadspeedresponse', function (e, response) {
|
|
||||||
|
|
||||||
Events.off(AndroidAjax, 'downloadspeedresponse');
|
|
||||||
|
|
||||||
if (response) {
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [response]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
// Need to mimic the jquery ajax error response
|
|
||||||
deferred.reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function initApiClient(newApiClient) {
|
|
||||||
newApiClient.getDownloadSpeed = function (bytes) {
|
|
||||||
return getDownloadSpeed(bytes, newApiClient.getUrl('Playback/BitrateTest', {
|
|
||||||
api_key: newApiClient.accessToken(),
|
|
||||||
Size: bytes
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Events.on(ConnectionManager, 'apiclientcreated', function (e, newApiClient) {
|
|
||||||
|
|
||||||
initApiClient(newApiClient);
|
|
||||||
});
|
|
||||||
|
|
||||||
Events.on(ConnectionManager.credentialProvider(), 'credentialsupdated', updateCredentials);
|
|
||||||
|
|
||||||
updateCredentials();
|
|
||||||
initNativeConnectionManager();
|
|
||||||
|
|
||||||
if (window.ApiClient) {
|
|
||||||
initApiClient(window.ApiClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.AndroidAjax = {
|
|
||||||
|
|
||||||
onResponse: function (id, response) {
|
|
||||||
|
|
||||||
Events.trigger(AndroidAjax, 'response' + id, [true, response]);
|
|
||||||
},
|
|
||||||
onError: function (id, response) {
|
|
||||||
|
|
||||||
Events.trigger(AndroidAjax, 'response' + id, [false, response]);
|
|
||||||
},
|
|
||||||
onDownloadSpeedResponse: function (response) {
|
|
||||||
|
|
||||||
Events.trigger(AndroidAjax, 'downloadspeedresponse', [response]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
70
dashboard-ui/cordova/android/appstorage.js
vendored
70
dashboard-ui/cordova/android/appstorage.js
vendored
|
@ -1,70 +0,0 @@
|
||||||
(function (globalScope, localStorage, sessionStorage) {
|
|
||||||
|
|
||||||
function myStore(defaultObject) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
self.localData = {};
|
|
||||||
|
|
||||||
var isDefaultAvailable;
|
|
||||||
|
|
||||||
if (defaultObject) {
|
|
||||||
try {
|
|
||||||
defaultObject.setItem('_test', '0');
|
|
||||||
isDefaultAvailable = true;
|
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.setItem = function (name, value) {
|
|
||||||
|
|
||||||
if (isDefaultAvailable) {
|
|
||||||
defaultObject.setItem(name, value);
|
|
||||||
} else {
|
|
||||||
self.localData[name] = value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.getItem = function (name) {
|
|
||||||
|
|
||||||
if (isDefaultAvailable) {
|
|
||||||
return defaultObject.getItem(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.localData[name];
|
|
||||||
};
|
|
||||||
|
|
||||||
self.removeItem = function (name) {
|
|
||||||
|
|
||||||
if (isDefaultAvailable) {
|
|
||||||
defaultObject.removeItem(name);
|
|
||||||
} else {
|
|
||||||
self.localData[name] = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function preferencesStore() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.setItem = function (name, value) {
|
|
||||||
|
|
||||||
AndroidSharedPreferences.set(name, value);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.getItem = function (name) {
|
|
||||||
|
|
||||||
return AndroidSharedPreferences.get(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.removeItem = function (name) {
|
|
||||||
|
|
||||||
AndroidSharedPreferences.remove(name);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
globalScope.appStorage = new preferencesStore();
|
|
||||||
globalScope.sessionStore = new myStore(sessionStorage);
|
|
||||||
|
|
||||||
})(window, window.localStorage, window.sessionStorage);
|
|
195
dashboard-ui/cordova/android/iap.js
vendored
195
dashboard-ui/cordova/android/iap.js
vendored
|
@ -1,195 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
var updatedProducts = [];
|
|
||||||
|
|
||||||
function updateProductInfo(id, owned, price) {
|
|
||||||
|
|
||||||
updatedProducts = updatedProducts.filter(function (r) {
|
|
||||||
return r.id != id;
|
|
||||||
});
|
|
||||||
|
|
||||||
var product = {
|
|
||||||
id: id,
|
|
||||||
owned: owned,
|
|
||||||
price: price
|
|
||||||
};
|
|
||||||
|
|
||||||
updatedProducts.push(product);
|
|
||||||
|
|
||||||
Events.trigger(IapManager, 'productupdated', [product]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getProduct(feature) {
|
|
||||||
|
|
||||||
var id;
|
|
||||||
if (feature == 'embypremieremonthly') {
|
|
||||||
id = NativeIapManager.getPremiereMonthlySku();
|
|
||||||
} else {
|
|
||||||
id = NativeIapManager.getUnlockProductSku();
|
|
||||||
}
|
|
||||||
|
|
||||||
var products = updatedProducts.filter(function (r) {
|
|
||||||
return r.id == id;
|
|
||||||
});
|
|
||||||
|
|
||||||
return products.length ? products[0] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var storeReady = false;
|
|
||||||
function onStoreReady() {
|
|
||||||
storeReady = true;
|
|
||||||
refreshPurchases();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPurchaseAvailable() {
|
|
||||||
|
|
||||||
return storeReady;
|
|
||||||
}
|
|
||||||
|
|
||||||
function beginPurchase(feature, email) {
|
|
||||||
|
|
||||||
if (feature == 'embypremieremonthly') {
|
|
||||||
return MainActivity.purchasePremiereMonthly(email);
|
|
||||||
}
|
|
||||||
return MainActivity.purchaseUnlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPurchaseComplete(result) {
|
|
||||||
|
|
||||||
if (result === true) {
|
|
||||||
|
|
||||||
refreshPurchases();
|
|
||||||
}
|
|
||||||
else if (result) {
|
|
||||||
|
|
||||||
ApiClient.ajax({
|
|
||||||
type: "POST",
|
|
||||||
url: ApiClient.getUrl("Appstore/Register"),
|
|
||||||
data: {
|
|
||||||
Parameters: JSON.stringify(result)
|
|
||||||
}
|
|
||||||
}).then(function () {
|
|
||||||
|
|
||||||
refreshPurchases();
|
|
||||||
|
|
||||||
}, function (e) {
|
|
||||||
|
|
||||||
refreshPurchases();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshPurchases() {
|
|
||||||
NativeIapManager.getPurchaseInfos("window.IapManager.updateProduct");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSubscriptionOptions() {
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
var options = [];
|
|
||||||
|
|
||||||
options.push({
|
|
||||||
feature: 'embypremieremonthly',
|
|
||||||
buttonText: 'EmbyPremiereMonthly'
|
|
||||||
});
|
|
||||||
|
|
||||||
options = options.filter(function (o) {
|
|
||||||
return getProduct(o.feature) != null;
|
|
||||||
|
|
||||||
}).map(function (o) {
|
|
||||||
|
|
||||||
var prod = getProduct(o.feature);
|
|
||||||
o.buttonText = Globalize.translate(o.buttonText, prod.price);
|
|
||||||
o.owned = prod.owned;
|
|
||||||
return o;
|
|
||||||
});
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [options]);
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isUnlockedOverride(feature) {
|
|
||||||
|
|
||||||
if (feature == 'playback') {
|
|
||||||
return isPlaybackUnlockedViaOldApp();
|
|
||||||
} else {
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
|
|
||||||
resolve(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPlaybackUnlockedViaOldApp() {
|
|
||||||
|
|
||||||
return testDeviceId(ConnectionManager.deviceId()).then(function (isUnlocked) {
|
|
||||||
|
|
||||||
if (isUnlocked) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return testDeviceId(device.uuid).then(function (isUnlocked) {
|
|
||||||
|
|
||||||
if (isUnlocked) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var legacyDeviceId = MainActivity.getLegacyDeviceId();
|
|
||||||
if (legacyDeviceId) {
|
|
||||||
return testDeviceId(legacyDeviceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function testDeviceId(deviceId) {
|
|
||||||
|
|
||||||
|
|
||||||
var cacheKey = 'oldapp-' + deviceId;
|
|
||||||
var cacheValue = appStorage.getItem(cacheKey);
|
|
||||||
if (cacheValue) {
|
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
|
|
||||||
resolve(cacheValue == 'true');
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
return fetch('https://mb3admin.com/admin/service/statistics/appAccess?application=AndroidV1&deviceId=' + deviceId, {
|
|
||||||
method: 'GET'
|
|
||||||
|
|
||||||
}).then(function (response) {
|
|
||||||
|
|
||||||
if (response.status == 404) {
|
|
||||||
appStorage.setItem(cacheKey, 'false');
|
|
||||||
} else if (response.status < 400) {
|
|
||||||
appStorage.setItem(cacheKey, 'true');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}, function (e) {
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.IapManager = {
|
|
||||||
isPurchaseAvailable: isPurchaseAvailable,
|
|
||||||
getProductInfo: getProduct,
|
|
||||||
updateProduct: updateProductInfo,
|
|
||||||
beginPurchase: beginPurchase,
|
|
||||||
onPurchaseComplete: onPurchaseComplete,
|
|
||||||
getSubscriptionOptions: getSubscriptionOptions,
|
|
||||||
onStoreReady: onStoreReady,
|
|
||||||
isUnlockedOverride: isUnlockedOverride
|
|
||||||
};
|
|
||||||
|
|
||||||
NativeIapManager.initStore();
|
|
||||||
|
|
||||||
})();
|
|
18
dashboard-ui/cordova/android/localsync.js
vendored
18
dashboard-ui/cordova/android/localsync.js
vendored
|
@ -1,18 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
window.LocalSync = {
|
|
||||||
|
|
||||||
isSupported: function () {
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
sync: function () {
|
|
||||||
AndroidSync.startSync();
|
|
||||||
},
|
|
||||||
|
|
||||||
getSyncStatus: function () {
|
|
||||||
return AndroidSync.getSyncStatus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
6
dashboard-ui/cordova/android/logging.js
vendored
6
dashboard-ui/cordova/android/logging.js
vendored
|
@ -1,6 +0,0 @@
|
||||||
window.Logger = {
|
|
||||||
|
|
||||||
log: function (str) {
|
|
||||||
LoggingBridge.log(str);
|
|
||||||
}
|
|
||||||
};
|
|
183
dashboard-ui/cordova/android/mediasession.js
vendored
183
dashboard-ui/cordova/android/mediasession.js
vendored
|
@ -1,183 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
// Reports media playback to the device for lock screen control
|
|
||||||
|
|
||||||
var currentPlayer;
|
|
||||||
var lastUpdateTime = 0;
|
|
||||||
|
|
||||||
function allowLocalPlayer() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatePlayerState(state, eventName) {
|
|
||||||
|
|
||||||
if (!state.NowPlayingItem) {
|
|
||||||
hideMediaControls();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var isLocalPlayer = MediaController.getPlayerInfo().isLocalPlayer || false;
|
|
||||||
|
|
||||||
// Local players do their own notifications
|
|
||||||
if (isLocalPlayer && !allowLocalPlayer()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dummy this up
|
|
||||||
if (eventName == 'init') {
|
|
||||||
eventName = 'positionchange';
|
|
||||||
}
|
|
||||||
|
|
||||||
var playState = state.PlayState || {};
|
|
||||||
|
|
||||||
var nameHtml = MediaController.getNowPlayingNameHtml(state.NowPlayingItem) || '';
|
|
||||||
var parts = nameHtml.split('<br/>');
|
|
||||||
|
|
||||||
var artist = parts.length == 1 ? '' : parts[0];
|
|
||||||
var title = parts[parts.length - 1];
|
|
||||||
|
|
||||||
// Switch these two around for video
|
|
||||||
if (state.NowPlayingItem.MediaType == 'Video' && parts.length > 1) {
|
|
||||||
var temp = artist;
|
|
||||||
artist = title;
|
|
||||||
title = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var album = state.NowPlayingItem.Album || '';
|
|
||||||
var itemId = state.NowPlayingItem.Id;
|
|
||||||
|
|
||||||
// Convert to ms
|
|
||||||
var duration = state.NowPlayingItem.RunTimeTicks ? (state.NowPlayingItem.RunTimeTicks / 10000) : 0;
|
|
||||||
var position = playState.PositionTicks ? (playState.PositionTicks / 10000) : 0;
|
|
||||||
|
|
||||||
var isPaused = playState.IsPaused || false;
|
|
||||||
var canSeek = playState.CanSeek || false;
|
|
||||||
|
|
||||||
var url = '';
|
|
||||||
var imgHeight = 400;
|
|
||||||
|
|
||||||
var nowPlayingItem = state.NowPlayingItem;
|
|
||||||
|
|
||||||
if (nowPlayingItem.PrimaryImageTag) {
|
|
||||||
|
|
||||||
url = ApiClient.getScaledImageUrl(nowPlayingItem.PrimaryImageItemId, {
|
|
||||||
type: "Primary",
|
|
||||||
height: imgHeight,
|
|
||||||
tag: nowPlayingItem.PrimaryImageTag
|
|
||||||
});
|
|
||||||
} else if (nowPlayingItem.ThumbImageTag) {
|
|
||||||
|
|
||||||
url = ApiClient.getScaledImageUrl(nowPlayingItem.ThumbImageItemId, {
|
|
||||||
type: "Thumb",
|
|
||||||
height: imgHeight,
|
|
||||||
tag: nowPlayingItem.ThumbImageTag
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (nowPlayingItem.BackdropImageTag) {
|
|
||||||
|
|
||||||
url = ApiClient.getScaledImageUrl(nowPlayingItem.BackdropItemId, {
|
|
||||||
type: "Backdrop",
|
|
||||||
height: imgHeight,
|
|
||||||
tag: nowPlayingItem.BackdropImageTag,
|
|
||||||
index: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't go crazy reporting position changes
|
|
||||||
if (eventName == 'positionchange') {
|
|
||||||
if (lastUpdateTime) {
|
|
||||||
// Only report if this item hasn't been reported yet, or if there's an actual playback change.
|
|
||||||
// Don't report on simple time updates
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MainActivity.updateMediaSession(eventName, isLocalPlayer, itemId, title, artist, album, parseInt(duration), parseInt(position), url, canSeek, isPaused);
|
|
||||||
lastUpdateTime = new Date().getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onStateChanged(e, state) {
|
|
||||||
|
|
||||||
updatePlayerState(state, e.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPlaybackStart(e, state) {
|
|
||||||
|
|
||||||
Logger.log('nowplaying event: ' + e.type);
|
|
||||||
|
|
||||||
var player = this;
|
|
||||||
|
|
||||||
player.beginPlayerUpdates();
|
|
||||||
|
|
||||||
onStateChanged.call(player, e, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPlaybackStopped(e, state) {
|
|
||||||
|
|
||||||
Logger.log('nowplaying event: ' + e.type);
|
|
||||||
var player = this;
|
|
||||||
|
|
||||||
player.endPlayerUpdates();
|
|
||||||
|
|
||||||
hideMediaControls();
|
|
||||||
}
|
|
||||||
|
|
||||||
function releaseCurrentPlayer() {
|
|
||||||
|
|
||||||
if (currentPlayer) {
|
|
||||||
|
|
||||||
$(currentPlayer).off('playbackstart', onPlaybackStart)
|
|
||||||
.off('playbackstop', onPlaybackStopped)
|
|
||||||
.off('playstatechange', onStateChanged)
|
|
||||||
.off('positionchange', onStateChanged);
|
|
||||||
|
|
||||||
currentPlayer.endPlayerUpdates();
|
|
||||||
currentPlayer = null;
|
|
||||||
|
|
||||||
hideMediaControls();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideMediaControls() {
|
|
||||||
MainActivity.hideMediaSession();
|
|
||||||
lastUpdateTime = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function bindToPlayer(player) {
|
|
||||||
|
|
||||||
releaseCurrentPlayer();
|
|
||||||
|
|
||||||
if (player.isLocalPlayer && !allowLocalPlayer()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPlayer = player;
|
|
||||||
|
|
||||||
Logger.log('binding remotecontrols to ' + player.name);
|
|
||||||
|
|
||||||
player.getPlayerState().then(function (state) {
|
|
||||||
|
|
||||||
if (state.NowPlayingItem) {
|
|
||||||
player.beginPlayerUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
onStateChanged.call(player, { type: 'init' }, state);
|
|
||||||
});
|
|
||||||
|
|
||||||
$(player).on('playbackstart', onPlaybackStart)
|
|
||||||
.on('playbackstop', onPlaybackStopped)
|
|
||||||
.on('playstatechange', onStateChanged)
|
|
||||||
.on('positionchange', onStateChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.log('binding remotecontrols to MediaController');
|
|
||||||
|
|
||||||
$(MediaController).on('playerchange', function () {
|
|
||||||
|
|
||||||
bindToPlayer(MediaController.getCurrentPlayer());
|
|
||||||
});
|
|
||||||
|
|
||||||
bindToPlayer(MediaController.getCurrentPlayer());
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,31 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
var currentDeferred;
|
|
||||||
function chooseDirectory() {
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
AndroidDirectoryChooser.chooseDirectory();
|
|
||||||
currentDeferred = deferred;
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onChosen(path) {
|
|
||||||
|
|
||||||
var deferred = currentDeferred;
|
|
||||||
|
|
||||||
if (deferred) {
|
|
||||||
if (path) {
|
|
||||||
deferred.resolveWith(null, [path]);
|
|
||||||
} else {
|
|
||||||
deferred.reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
currentDeferred = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.NativeDirectoryChooser = {
|
|
||||||
chooseDirectory: chooseDirectory,
|
|
||||||
onChosen: onChosen
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
291
dashboard-ui/cordova/android/vlcplayer.js
vendored
291
dashboard-ui/cordova/android/vlcplayer.js
vendored
|
@ -1,291 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function vlcRenderer(options) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.enableProgressReporting = options.type == 'audio';
|
|
||||||
|
|
||||||
function onEnded() {
|
|
||||||
Events.trigger(self, 'ended');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onTimeUpdate() {
|
|
||||||
Events.trigger(self, 'timeupdate');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onVolumeChange() {
|
|
||||||
Events.trigger(self, 'volumechange');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPlaying() {
|
|
||||||
Events.trigger(self, 'playing');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPlay() {
|
|
||||||
Events.trigger(self, 'play');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPause() {
|
|
||||||
Events.trigger(self, 'pause');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClick() {
|
|
||||||
Events.trigger(self, 'click');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDblClick() {
|
|
||||||
Events.trigger(self, 'dblclick');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onError() {
|
|
||||||
|
|
||||||
var errorCode = this.error ? this.error.code : '';
|
|
||||||
Logger.log('Media element error code: ' + errorCode);
|
|
||||||
|
|
||||||
Events.trigger(self, 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
var playerState = {};
|
|
||||||
|
|
||||||
self.currentTime = function (val) {
|
|
||||||
|
|
||||||
if (val != null) {
|
|
||||||
AndroidVlcPlayer.sendVlcCommand("setposition", val.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return playerState.currentTime;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.duration = function (val) {
|
|
||||||
|
|
||||||
if (playerState) {
|
|
||||||
return playerState.duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.stop = function () {
|
|
||||||
AndroidVlcPlayer.sendVlcCommand("stop", null);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.pause = function () {
|
|
||||||
AndroidVlcPlayer.sendVlcCommand("pause", null);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.unpause = function () {
|
|
||||||
AndroidVlcPlayer.sendVlcCommand("unpause", null);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.volume = function (val) {
|
|
||||||
if (playerState) {
|
|
||||||
if (val != null) {
|
|
||||||
AndroidVlcPlayer.sendVlcCommand("setvolume", (val * 100).toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return playerState.volume;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function getPlaybackStartInfoForVideoActivity(streamInfo, mediaSource, item) {
|
|
||||||
|
|
||||||
var playbackStartInfo = {
|
|
||||||
QueueableMediaTypes: item.MediaType,
|
|
||||||
ItemId: item.Id,
|
|
||||||
NowPlayingItem: {},
|
|
||||||
MediaSourceId: mediaSource.Id
|
|
||||||
};
|
|
||||||
|
|
||||||
if (mediaSource.RunTimeTicks) {
|
|
||||||
playbackStartInfo.NowPlayingItem.RunTimeTicks = mediaSource.RunTimeTicks;
|
|
||||||
}
|
|
||||||
|
|
||||||
var videoUrl = streamInfo.url;
|
|
||||||
var audioStreamIndex = getParameterByName('AudioStreamIndex', videoUrl);
|
|
||||||
|
|
||||||
if (audioStreamIndex) {
|
|
||||||
playbackStartInfo.AudioStreamIndex = parseInt(audioStreamIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This should be passed in rather than going out to get it
|
|
||||||
if (MediaPlayer.currentSubtitleStreamIndex != null) {
|
|
||||||
playbackStartInfo.SubtitleStreamIndex = MediaPlayer.currentSubtitleStreamIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
playbackStartInfo.PlayMethod = streamInfo.playMethod;
|
|
||||||
|
|
||||||
playbackStartInfo.LiveStreamId = mediaSource.LiveStreamId;
|
|
||||||
playbackStartInfo.PlaySessionId = getParameterByName('PlaySessionId', videoUrl);
|
|
||||||
|
|
||||||
// Seeing some deserialization errors around this property
|
|
||||||
if (mediaSource.RunTimeTicks && mediaSource.RunTimeTicks > 0) {
|
|
||||||
playbackStartInfo.CanSeek = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return playbackStartInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.setCurrentSrc = function (streamInfo, item, mediaSource, tracks) {
|
|
||||||
|
|
||||||
if (!streamInfo) {
|
|
||||||
self.destroy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var val = streamInfo.url;
|
|
||||||
var tIndex = val.indexOf('#t=');
|
|
||||||
var startPosMs = 0;
|
|
||||||
|
|
||||||
if (tIndex != -1) {
|
|
||||||
startPosMs = val.substring(tIndex + 3);
|
|
||||||
startPosMs = parseFloat(startPosMs) * 1000;
|
|
||||||
val = val.split('#')[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.type == 'audio') {
|
|
||||||
|
|
||||||
AndroidVlcPlayer.playAudioVlc(val, JSON.stringify(item), JSON.stringify(mediaSource), options.poster);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
var playbackStartInfo = getPlaybackStartInfoForVideoActivity(streamInfo, mediaSource, item);
|
|
||||||
|
|
||||||
var serverUrl = ApiClient.serverAddress();
|
|
||||||
|
|
||||||
var videoStream = mediaSource.MediaStreams.filter(function (stream) {
|
|
||||||
return stream.Type == "Video";
|
|
||||||
})[0];
|
|
||||||
var videoWidth = videoStream ? videoStream.Width : null;
|
|
||||||
var videoHeight = videoStream ? videoStream.Height : null;
|
|
||||||
|
|
||||||
var videoQualityOptions = MediaPlayer.getVideoQualityOptions(videoWidth, videoHeight).map(function (o) {
|
|
||||||
return {
|
|
||||||
Name: o.name,
|
|
||||||
Value: o.bitrate + "-" + o.maxHeight
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
var deviceProfile = MediaPlayer.getDeviceProfile();
|
|
||||||
|
|
||||||
var timeLimitMs = MediaController.playbackTimeLimitMs || 0;
|
|
||||||
|
|
||||||
AndroidVlcPlayer.playVideoVlc(val,
|
|
||||||
startPosMs,
|
|
||||||
item.Name,
|
|
||||||
JSON.stringify(item),
|
|
||||||
JSON.stringify(mediaSource),
|
|
||||||
JSON.stringify(playbackStartInfo),
|
|
||||||
ApiClient.serverInfo().Id,
|
|
||||||
serverUrl,
|
|
||||||
ApiClient.appName(),
|
|
||||||
ApiClient.appVersion(),
|
|
||||||
ApiClient.deviceId(),
|
|
||||||
ApiClient.deviceName(),
|
|
||||||
ApiClient.getCurrentUserId(),
|
|
||||||
ApiClient.accessToken(),
|
|
||||||
JSON.stringify(deviceProfile),
|
|
||||||
JSON.stringify(videoQualityOptions),
|
|
||||||
timeLimitMs);
|
|
||||||
|
|
||||||
playerState.currentSrc = val;
|
|
||||||
self.report('playing', null, startPosMs, false, 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.currentSrc = function () {
|
|
||||||
if (playerState) {
|
|
||||||
return playerState.currentSrc;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.paused = function () {
|
|
||||||
|
|
||||||
if (playerState) {
|
|
||||||
return playerState.paused;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.cleanup = function (destroyRenderer) {
|
|
||||||
|
|
||||||
if (destroyRenderer !== false) {
|
|
||||||
AndroidVlcPlayer.destroyVlc();
|
|
||||||
}
|
|
||||||
|
|
||||||
playerState = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
self.enableCustomVideoControls = function () {
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.report = function (eventName, duration, position, isPaused, volume) {
|
|
||||||
|
|
||||||
var state = playerState;
|
|
||||||
|
|
||||||
state.duration = duration;
|
|
||||||
state.currentTime = position;
|
|
||||||
state.paused = isPaused;
|
|
||||||
state.volume = (volume || 0) / 100;
|
|
||||||
|
|
||||||
if (eventName == 'playbackstop') {
|
|
||||||
onEnded();
|
|
||||||
}
|
|
||||||
else if (eventName == 'volumechange') {
|
|
||||||
onVolumeChange();
|
|
||||||
}
|
|
||||||
else if (eventName == 'positionchange') {
|
|
||||||
onTimeUpdate();
|
|
||||||
}
|
|
||||||
else if (eventName == 'paused') {
|
|
||||||
onPause();
|
|
||||||
}
|
|
||||||
else if (eventName == 'unpaused') {
|
|
||||||
onPlaying();
|
|
||||||
}
|
|
||||||
else if (eventName == 'playing') {
|
|
||||||
onPlaying();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.init = function () {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
deferred.resolve();
|
|
||||||
return deferred.promise();
|
|
||||||
};
|
|
||||||
|
|
||||||
self.onActivityClosed = function (wasStopped, hasError, endPositionMs) {
|
|
||||||
|
|
||||||
playerState.currentTime = endPositionMs;
|
|
||||||
|
|
||||||
if (wasStopped) {
|
|
||||||
MediaPlayer.stop(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.report('playbackstop', playerState.duration, endPositionMs, false, 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.AudioRenderer.Current = self;
|
|
||||||
window.VideoRenderer.Current = self;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.AudioRenderer = function (options) {
|
|
||||||
options = options || {};
|
|
||||||
options.type = 'audio';
|
|
||||||
|
|
||||||
return new vlcRenderer(options);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.VideoRenderer = function (options) {
|
|
||||||
options = options || {};
|
|
||||||
options.type = 'video';
|
|
||||||
|
|
||||||
return new vlcRenderer(options);
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
24
dashboard-ui/cordova/back.js
vendored
24
dashboard-ui/cordova/back.js
vendored
|
@ -1,24 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
Dashboard.exit = function () {
|
|
||||||
|
|
||||||
if (navigator.app && navigator.app.exitApp) {
|
|
||||||
navigator.app.exitApp();
|
|
||||||
} else {
|
|
||||||
Dashboard.logout();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function onBackKeyDown(e) {
|
|
||||||
if (Dashboard.exitOnBack()) {
|
|
||||||
e.preventDefault();
|
|
||||||
Dashboard.exit();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
history.back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("backbutton", onBackKeyDown, false);
|
|
||||||
|
|
||||||
})();
|
|
709
dashboard-ui/cordova/chromecast.js
vendored
709
dashboard-ui/cordova/chromecast.js
vendored
|
@ -1,709 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function chromecastPlayer() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var PlayerName = "Chromecast";
|
|
||||||
var ApplicationID = "2D4B1DA3";
|
|
||||||
var currentWebAppSession;
|
|
||||||
var currentDevice;
|
|
||||||
var currentDeviceId;
|
|
||||||
|
|
||||||
// MediaController needs this
|
|
||||||
self.name = PlayerName;
|
|
||||||
|
|
||||||
self.getItemsForPlayback = function (query) {
|
|
||||||
|
|
||||||
var userId = Dashboard.getCurrentUserId();
|
|
||||||
|
|
||||||
if (query.Ids && query.Ids.split(',').length == 1) {
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
|
|
||||||
ApiClient.getItem(userId, query.Ids.split(',')).then(function (item) {
|
|
||||||
resolve({
|
|
||||||
Items: [item],
|
|
||||||
TotalRecordCount: 1
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
query.Limit = query.Limit || 100;
|
|
||||||
query.ExcludeLocationTypes = "Virtual";
|
|
||||||
|
|
||||||
return ApiClient.getItems(userId, query);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var castPlayer = {};
|
|
||||||
|
|
||||||
$(castPlayer).on("playbackstart", function (e, data) {
|
|
||||||
|
|
||||||
Logger.log('cc: playbackstart');
|
|
||||||
|
|
||||||
var state = self.getPlayerStateInternal(data);
|
|
||||||
$(self).trigger("playbackstart", [state]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$(castPlayer).on("playbackstop", function (e, data) {
|
|
||||||
|
|
||||||
Logger.log('cc: playbackstop');
|
|
||||||
var state = self.getPlayerStateInternal(data);
|
|
||||||
|
|
||||||
$(self).trigger("playbackstop", [state]);
|
|
||||||
|
|
||||||
// Reset this so the next query doesn't make it appear like content is playing.
|
|
||||||
self.lastPlayerData = {};
|
|
||||||
});
|
|
||||||
|
|
||||||
$(castPlayer).on("playbackprogress", function (e, data) {
|
|
||||||
|
|
||||||
Logger.log('cc: positionchange');
|
|
||||||
var state = self.getPlayerStateInternal(data);
|
|
||||||
|
|
||||||
$(self).trigger("positionchange", [state]);
|
|
||||||
});
|
|
||||||
|
|
||||||
var endpointInfo;
|
|
||||||
function getEndpointInfo() {
|
|
||||||
|
|
||||||
if (endpointInfo) {
|
|
||||||
|
|
||||||
var deferred = $.Deferred();
|
|
||||||
deferred.resolveWith(null, [endpointInfo]);
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApiClient.getJSON(ApiClient.getUrl('System/Endpoint')).then(function (info) {
|
|
||||||
|
|
||||||
endpointInfo = info;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMessageToDevice(message) {
|
|
||||||
|
|
||||||
var bitrateSetting = AppSettings.maxChromecastBitrate();
|
|
||||||
|
|
||||||
message = $.extend(message, {
|
|
||||||
userId: Dashboard.getCurrentUserId(),
|
|
||||||
deviceId: ApiClient.deviceId(),
|
|
||||||
accessToken: ApiClient.accessToken(),
|
|
||||||
serverAddress: ApiClient.serverAddress(),
|
|
||||||
maxBitrate: bitrateSetting,
|
|
||||||
receiverName: currentDevice.getFriendlyName(),
|
|
||||||
supportsAc3: AppSettings.enableChromecastAc3()
|
|
||||||
});
|
|
||||||
|
|
||||||
getEndpointInfo().then(function (endpoint) {
|
|
||||||
|
|
||||||
if (endpoint.IsInNetwork) {
|
|
||||||
ApiClient.getPublicSystemInfo().then(function (info) {
|
|
||||||
|
|
||||||
message.serverAddress = info.LocalAddress;
|
|
||||||
sendMessageInternal(message);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
sendMessageInternal(message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendMessageInternal(message) {
|
|
||||||
currentWebAppSession.sendText(JSON.stringify(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.play = function (options) {
|
|
||||||
|
|
||||||
Dashboard.getCurrentUser().then(function (user) {
|
|
||||||
|
|
||||||
if (options.items) {
|
|
||||||
|
|
||||||
self.playWithCommand(options, 'PlayNow');
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
self.getItemsForPlayback({
|
|
||||||
|
|
||||||
Ids: options.ids.join(',')
|
|
||||||
|
|
||||||
}).then(function (result) {
|
|
||||||
|
|
||||||
options.items = result.Items;
|
|
||||||
self.playWithCommand(options, 'PlayNow');
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
self.playWithCommand = function (options, command) {
|
|
||||||
|
|
||||||
if (!options.items) {
|
|
||||||
ApiClient.getItem(Dashboard.getCurrentUserId(), options.ids[0]).then(function (item) {
|
|
||||||
|
|
||||||
options.items = [item];
|
|
||||||
self.playWithCommand(options, command);
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the items to smaller stubs to send the minimal amount of information
|
|
||||||
options.items = options.items.map(function (i) {
|
|
||||||
|
|
||||||
return {
|
|
||||||
Id: i.Id,
|
|
||||||
Name: i.Name,
|
|
||||||
Type: i.Type,
|
|
||||||
MediaType: i.MediaType,
|
|
||||||
IsFolder: i.IsFolder
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
sendMessageToDevice({
|
|
||||||
options: options,
|
|
||||||
command: command
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.unpause = function () {
|
|
||||||
sendMessageToDevice({
|
|
||||||
command: 'Unpause'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.pause = function () {
|
|
||||||
sendMessageToDevice({
|
|
||||||
command: 'Pause'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.shuffle = function (id) {
|
|
||||||
|
|
||||||
var userId = Dashboard.getCurrentUserId();
|
|
||||||
|
|
||||||
ApiClient.getItem(userId, id).then(function (item) {
|
|
||||||
|
|
||||||
self.playWithCommand({
|
|
||||||
|
|
||||||
items: [item]
|
|
||||||
|
|
||||||
}, 'Shuffle');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
self.instantMix = function (id) {
|
|
||||||
|
|
||||||
var userId = Dashboard.getCurrentUserId();
|
|
||||||
|
|
||||||
ApiClient.getItem(userId, id).then(function (item) {
|
|
||||||
|
|
||||||
self.playWithCommand({
|
|
||||||
|
|
||||||
items: [item]
|
|
||||||
|
|
||||||
}, 'InstantMix');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
self.canQueueMediaType = function (mediaType) {
|
|
||||||
return mediaType == "Audio";
|
|
||||||
};
|
|
||||||
|
|
||||||
self.queue = function (options) {
|
|
||||||
self.playWithCommnd(options, 'PlayLast');
|
|
||||||
};
|
|
||||||
|
|
||||||
self.queueNext = function (options) {
|
|
||||||
self.playWithCommand(options, 'PlayNext');
|
|
||||||
};
|
|
||||||
|
|
||||||
self.stop = function () {
|
|
||||||
sendMessageToDevice({
|
|
||||||
command: 'Stop'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.displayContent = function (options) {
|
|
||||||
|
|
||||||
sendMessageToDevice({
|
|
||||||
options: options,
|
|
||||||
command: 'DisplayContent'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.mute = function () {
|
|
||||||
sendMessageToDevice({
|
|
||||||
command: 'Mute'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.unMute = function () {
|
|
||||||
self.setVolume(getCurrentVolume() + 2);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.toggleMute = function () {
|
|
||||||
|
|
||||||
var state = self.lastPlayerData || {};
|
|
||||||
state = state.PlayState || {};
|
|
||||||
|
|
||||||
if (state.IsMuted) {
|
|
||||||
self.unMute();
|
|
||||||
} else {
|
|
||||||
self.mute();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function getBaseTargetInfo() {
|
|
||||||
var target = {};
|
|
||||||
|
|
||||||
target.playerName = PlayerName;
|
|
||||||
target.playableMediaTypes = ["Audio", "Video"];
|
|
||||||
target.isLocalPlayer = false;
|
|
||||||
target.appName = PlayerName;
|
|
||||||
target.supportedCommands = [
|
|
||||||
"VolumeUp",
|
|
||||||
"VolumeDown",
|
|
||||||
"Mute",
|
|
||||||
"Unmute",
|
|
||||||
"ToggleMute",
|
|
||||||
"SetVolume",
|
|
||||||
"SetAudioStreamIndex",
|
|
||||||
"SetSubtitleStreamIndex",
|
|
||||||
"DisplayContent",
|
|
||||||
"SetRepeatMode",
|
|
||||||
"EndSession"
|
|
||||||
];
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertDeviceToTarget(device) {
|
|
||||||
|
|
||||||
var target = getBaseTargetInfo();
|
|
||||||
|
|
||||||
target.name = target.deviceName = device.getFriendlyName();
|
|
||||||
target.id = device.getId();
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isChromecastName(name) {
|
|
||||||
|
|
||||||
name = (name || '').toLowerCase();
|
|
||||||
var validTokens = ['nexusplayer'];
|
|
||||||
validTokens.push('chromecast');
|
|
||||||
validTokens.push('eurekadongle');
|
|
||||||
|
|
||||||
return validTokens.filter(function (t) {
|
|
||||||
|
|
||||||
return name.replace(' ', '').indexOf(t) != -1;
|
|
||||||
|
|
||||||
}).length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.getTargets = function () {
|
|
||||||
|
|
||||||
return ConnectSDKHelper.getDeviceList().filter(function (d) {
|
|
||||||
|
|
||||||
return d.hasService('Chromecast') || d.hasService('ChromeCast') || isChromecastName(d.getModelName()) || isChromecastName(d.getFriendlyName());
|
|
||||||
|
|
||||||
}).map(convertDeviceToTarget);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.seek = function (position) {
|
|
||||||
|
|
||||||
position = parseInt(position);
|
|
||||||
position = position / 10000000;
|
|
||||||
|
|
||||||
sendMessageToDevice({
|
|
||||||
options: {
|
|
||||||
position: position
|
|
||||||
},
|
|
||||||
command: 'Seek'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.setAudioStreamIndex = function (index) {
|
|
||||||
sendMessageToDevice({
|
|
||||||
options: {
|
|
||||||
index: index
|
|
||||||
},
|
|
||||||
command: 'SetAudioStreamIndex'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.setSubtitleStreamIndex = function (index) {
|
|
||||||
sendMessageToDevice({
|
|
||||||
options: {
|
|
||||||
index: index
|
|
||||||
},
|
|
||||||
command: 'SetSubtitleStreamIndex'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.nextTrack = function () {
|
|
||||||
sendMessageToDevice({
|
|
||||||
options: {},
|
|
||||||
command: 'NextTrack'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.previousTrack = function () {
|
|
||||||
sendMessageToDevice({
|
|
||||||
options: {},
|
|
||||||
command: 'PreviousTrack'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.beginPlayerUpdates = function () {
|
|
||||||
// Setup polling here
|
|
||||||
};
|
|
||||||
|
|
||||||
self.endPlayerUpdates = function () {
|
|
||||||
// Stop polling here
|
|
||||||
};
|
|
||||||
|
|
||||||
function getCurrentVolume() {
|
|
||||||
var state = self.lastPlayerData || {};
|
|
||||||
state = state.PlayState || {};
|
|
||||||
|
|
||||||
return state.VolumeLevel == null ? 100 : state.VolumeLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.volumeDown = function () {
|
|
||||||
|
|
||||||
sendMessageToDevice({
|
|
||||||
options: {},
|
|
||||||
command: 'VolumeDown'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.setRepeatMode = function (mode) {
|
|
||||||
sendMessageToDevice({
|
|
||||||
options: {
|
|
||||||
RepeatMode: mode
|
|
||||||
},
|
|
||||||
command: 'SetRepeatMode'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.volumeUp = function () {
|
|
||||||
|
|
||||||
sendMessageToDevice({
|
|
||||||
options: {},
|
|
||||||
command: 'VolumeUp'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.setVolume = function (vol) {
|
|
||||||
|
|
||||||
vol = Math.min(vol, 100);
|
|
||||||
vol = Math.max(vol, 0);
|
|
||||||
|
|
||||||
sendMessageToDevice({
|
|
||||||
options: {
|
|
||||||
volume: vol
|
|
||||||
},
|
|
||||||
command: 'SetVolume'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.getPlayerState = function () {
|
|
||||||
|
|
||||||
var deferred = $.Deferred();
|
|
||||||
|
|
||||||
var result = self.getPlayerStateInternal();
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [result]);
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
};
|
|
||||||
|
|
||||||
self.lastPlayerData = {};
|
|
||||||
|
|
||||||
self.getPlayerStateInternal = function (data) {
|
|
||||||
|
|
||||||
data = data || self.lastPlayerData;
|
|
||||||
self.lastPlayerData = data;
|
|
||||||
|
|
||||||
Logger.log(JSON.stringify(data));
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
function onMessage(message) {
|
|
||||||
|
|
||||||
if (message.type == 'playbackerror') {
|
|
||||||
|
|
||||||
var errorCode = message.data;
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
Dashboard.alert({
|
|
||||||
message: Globalize.translate('MessagePlaybackError' + errorCode),
|
|
||||||
title: Globalize.translate('HeaderPlaybackError')
|
|
||||||
});
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (message.type == 'connectionerror') {
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
Dashboard.alert({
|
|
||||||
message: Globalize.translate('MessageChromecastConnectionError'),
|
|
||||||
title: Globalize.translate('HeaderError')
|
|
||||||
});
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (message.type && message.type.indexOf('playback') == 0) {
|
|
||||||
$(castPlayer).trigger(message.type, [message.data]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMessage(message) {
|
|
||||||
// message could be either a string or an object
|
|
||||||
if (typeof message === 'string') {
|
|
||||||
onMessage(JSON.parse(message));
|
|
||||||
} else {
|
|
||||||
onMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSessionDisconnect() {
|
|
||||||
|
|
||||||
Logger.log("session disconnected");
|
|
||||||
|
|
||||||
// We can't trust this because we might receive events of other devices disconnecting
|
|
||||||
//cleanupSession();
|
|
||||||
//MediaController.removeActivePlayer(PlayerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onWebAppSessionConnect(webAppSession, device) {
|
|
||||||
|
|
||||||
currentWebAppSession = webAppSession;
|
|
||||||
|
|
||||||
Logger.log('session.connect succeeded');
|
|
||||||
webAppSession.setWebAppSessionListener();
|
|
||||||
|
|
||||||
currentDevice = device;
|
|
||||||
currentDeviceId = device.getId();
|
|
||||||
self.lastPlayerData = {};
|
|
||||||
MediaController.setActivePlayer(PlayerName, convertDeviceToTarget(device));
|
|
||||||
|
|
||||||
sendIdentifyMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupWebAppSession(device, session, connectToSession) {
|
|
||||||
|
|
||||||
// hold on to a reference
|
|
||||||
var currentSession = session.acquire();
|
|
||||||
|
|
||||||
currentSession.on('message', handleMessage);
|
|
||||||
currentSession.on('disconnect', handleSessionDisconnect);
|
|
||||||
|
|
||||||
if (connectToSession || browserInfo.safari) {
|
|
||||||
currentSession.connect().success(function () {
|
|
||||||
|
|
||||||
onWebAppSessionConnect(currentSession, device);
|
|
||||||
|
|
||||||
}).error(handleSessionError);
|
|
||||||
} else {
|
|
||||||
onWebAppSessionConnect(currentSession, device);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendIdentifyMessage() {
|
|
||||||
sendMessageToDevice({
|
|
||||||
options: {},
|
|
||||||
command: 'Identify'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSessionError() {
|
|
||||||
Logger.log('chromecast session connect error');
|
|
||||||
cleanupSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanupSession() {
|
|
||||||
|
|
||||||
var session = currentWebAppSession;
|
|
||||||
|
|
||||||
if (session) {
|
|
||||||
// Clean up listeners
|
|
||||||
session.off("message");
|
|
||||||
session.off("disconnect");
|
|
||||||
|
|
||||||
// Release session to free up memory
|
|
||||||
session.disconnect();
|
|
||||||
session.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.lastPlayerData = {};
|
|
||||||
currentWebAppSession = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryLaunchWebSession(device) {
|
|
||||||
|
|
||||||
Logger.log('calling launchWebApp');
|
|
||||||
device.getWebAppLauncher().launchWebApp(ApplicationID).success(function (session) {
|
|
||||||
|
|
||||||
Logger.log('launchWebApp success. calling onSessionConnected');
|
|
||||||
setupWebAppSession(device, session, true);
|
|
||||||
|
|
||||||
}).error(function (err1) {
|
|
||||||
|
|
||||||
Logger.log('launchWebApp error:' + JSON.stringify(err1));
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryJoinWebSession(device, enableRetry, enableLaunch) {
|
|
||||||
|
|
||||||
// First try to join existing session. If it fails, launch a new one
|
|
||||||
|
|
||||||
Logger.log('calling joinWebApp');
|
|
||||||
device.getWebAppLauncher().joinWebApp(ApplicationID).success(function (session) {
|
|
||||||
|
|
||||||
Logger.log('joinWebApp success. calling onSessionConnected');
|
|
||||||
setupWebAppSession(device, session, false);
|
|
||||||
|
|
||||||
}).error(function (err) {
|
|
||||||
|
|
||||||
Logger.log('joinWebApp error: ' + JSON.stringify(err));
|
|
||||||
|
|
||||||
if (enableRetry) {
|
|
||||||
tryJoinWebSession(device, false, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableLaunch) {
|
|
||||||
Logger.log('calling launchWebApp');
|
|
||||||
tryLaunchWebSession(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function launchWebApp(device) {
|
|
||||||
|
|
||||||
if (currentWebAppSession) {
|
|
||||||
cleanupSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
tryJoinWebSession(device, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDeviceReady(device) {
|
|
||||||
|
|
||||||
device.off("ready");
|
|
||||||
|
|
||||||
Logger.log('creating webAppSession');
|
|
||||||
self.lastPlayerData = {};
|
|
||||||
|
|
||||||
launchWebApp(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tryPair = function (target) {
|
|
||||||
|
|
||||||
var deferred = $.Deferred();
|
|
||||||
|
|
||||||
var device = ConnectSDKHelper.getDeviceList().filter(function (d) {
|
|
||||||
|
|
||||||
return d.getId() == target.id;
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
if (device) {
|
|
||||||
|
|
||||||
self.tryPairWithDevice(device, deferred);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
deferred.reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
};
|
|
||||||
|
|
||||||
self.tryPairWithDevice = function (device, deferred) {
|
|
||||||
|
|
||||||
Logger.log('Will attempt to connect to Chromecast');
|
|
||||||
|
|
||||||
device.on("disconnect", function () {
|
|
||||||
device.off("ready");
|
|
||||||
device.off("disconnect");
|
|
||||||
});
|
|
||||||
|
|
||||||
if (device.isReady()) {
|
|
||||||
Logger.log('Device is already ready, calling onDeviceReady');
|
|
||||||
onDeviceReady(device);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
Logger.log('Binding device ready handler');
|
|
||||||
|
|
||||||
device.on("ready", function () {
|
|
||||||
Logger.log('device.ready fired');
|
|
||||||
onDeviceReady(device);
|
|
||||||
});
|
|
||||||
|
|
||||||
Logger.log('Calling device.connect');
|
|
||||||
device.connect();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.endSession = function () {
|
|
||||||
|
|
||||||
var session = currentWebAppSession;
|
|
||||||
|
|
||||||
if (session) {
|
|
||||||
session.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentDevice) {
|
|
||||||
currentDevice.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanupSession();
|
|
||||||
currentDevice = null;
|
|
||||||
currentDeviceId = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
$(MediaController).on('playerchange', function (e, newPlayer, newTarget) {
|
|
||||||
|
|
||||||
if (newTarget.id != currentDeviceId) {
|
|
||||||
if (currentWebAppSession) {
|
|
||||||
Logger.log('Disconnecting from chromecast');
|
|
||||||
Logger.log('New target info: ' + JSON.stringify(newTarget));
|
|
||||||
Logger.log('currentDeviceId: ' + currentDeviceId);
|
|
||||||
//currentDevice.disconnect();
|
|
||||||
cleanupSession();
|
|
||||||
currentDevice = null;
|
|
||||||
currentDeviceId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function onResume() {
|
|
||||||
|
|
||||||
var deviceId = currentDeviceId;
|
|
||||||
|
|
||||||
if (deviceId) {
|
|
||||||
self.tryPair({
|
|
||||||
id: deviceId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("resume", onResume, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaController.registerPlayer(new chromecastPlayer());
|
|
||||||
|
|
||||||
})();
|
|
36
dashboard-ui/cordova/connectsdk.js
vendored
36
dashboard-ui/cordova/connectsdk.js
vendored
|
@ -1,36 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function initSdk() {
|
|
||||||
|
|
||||||
var manager = ConnectSDK.discoveryManager;
|
|
||||||
|
|
||||||
//manager.setPairingLevel(ConnectSDK.PairingLevel.OFF);
|
|
||||||
manager.setAirPlayServiceMode(ConnectSDK.AirPlayServiceMode.Media);
|
|
||||||
|
|
||||||
// Show devices that support playing videos and pausing
|
|
||||||
//manager.setCapabilityFilters([
|
|
||||||
// new ConnectSDK.CapabilityFilter(["MediaPlayer.Display.Video", "MediaControl.Pause"])
|
|
||||||
//]);
|
|
||||||
|
|
||||||
manager.on('devicelistchanged', onDeviceListChanged);
|
|
||||||
|
|
||||||
manager.startDiscovery();
|
|
||||||
|
|
||||||
requirejs(['cordova/chromecast', 'cordova/generaldevice']);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDeviceListChanged(list) {
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDeviceList() {
|
|
||||||
return ConnectSDK.discoveryManager.getDeviceList();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.ConnectSDKHelper = {
|
|
||||||
|
|
||||||
getDeviceList: getDeviceList
|
|
||||||
};
|
|
||||||
|
|
||||||
initSdk();
|
|
||||||
|
|
||||||
})();
|
|
23
dashboard-ui/cordova/externalplayer.js
vendored
23
dashboard-ui/cordova/externalplayer.js
vendored
|
@ -1,23 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function showPlayerSelectionMenu(item, url, mimeType) {
|
|
||||||
|
|
||||||
window.plugins.launcher.launch({
|
|
||||||
uri: url,
|
|
||||||
dataType: mimeType
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
|
|
||||||
Logger.log('plugin launch success');
|
|
||||||
ExternalPlayer.onPlaybackStart();
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
|
|
||||||
Logger.log('plugin launch error');
|
|
||||||
ExternalPlayer.onPlaybackStart();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.ExternalPlayer.showPlayerSelectionMenu = showPlayerSelectionMenu;
|
|
||||||
|
|
||||||
})();
|
|
68
dashboard-ui/cordova/fileupload.js
vendored
68
dashboard-ui/cordova/fileupload.js
vendored
|
@ -1,68 +0,0 @@
|
||||||
(function (globalScope) {
|
|
||||||
|
|
||||||
function fileUpload() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.upload = function (path, name, url) {
|
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
|
|
||||||
resolveLocalFileSystemURL(path, function (fileEntry) {
|
|
||||||
|
|
||||||
fileEntry.file(function (file) {
|
|
||||||
|
|
||||||
var mimeType = (file.type || '');
|
|
||||||
|
|
||||||
if (mimeType.indexOf('image/') != 0) {
|
|
||||||
Logger.log('Skipping upload because file is not an image. path: ' + path + ' mimeType: ' + mimeType);
|
|
||||||
reject();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.log('mimeType for file ' + path + ' is ' + file);
|
|
||||||
|
|
||||||
var onSuccess = function (r) {
|
|
||||||
console.log("Code = " + r.responseCode);
|
|
||||||
console.log("Response = " + r.response);
|
|
||||||
console.log("Sent = " + r.bytesSent);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
var onFail = function (error) {
|
|
||||||
console.log("upload error source " + error.source);
|
|
||||||
console.log("upload error target " + error.target);
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
var options = new FileUploadOptions();
|
|
||||||
options.fileKey = "file";
|
|
||||||
options.fileName = name;
|
|
||||||
options.mimeType = mimeType;
|
|
||||||
|
|
||||||
var params = {};
|
|
||||||
options.params = params;
|
|
||||||
|
|
||||||
new FileTransfer().upload(path, url, onSuccess, onFail, options);
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
Logger.log('File upload failed. fileEntry.file returned an error');
|
|
||||||
reject();
|
|
||||||
});
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
|
|
||||||
Logger.log('File upload failed. resolveLocalFileSystemURL returned an error');
|
|
||||||
reject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!globalScope.MediaBrowser) {
|
|
||||||
globalScope.MediaBrowser = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
globalScope.MediaBrowser.FileUpload = fileUpload;
|
|
||||||
|
|
||||||
})(this);
|
|
675
dashboard-ui/cordova/generaldevice.js
vendored
675
dashboard-ui/cordova/generaldevice.js
vendored
|
@ -1,675 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function connectSDKPlayer() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
var PlayerName = "ConnectSDK";
|
|
||||||
var currentDevice;
|
|
||||||
var currentDeviceId;
|
|
||||||
var currentMediaControl;
|
|
||||||
|
|
||||||
// MediaController needs this
|
|
||||||
self.name = PlayerName;
|
|
||||||
|
|
||||||
self.getItemsForPlayback = function (query) {
|
|
||||||
|
|
||||||
var userId = Dashboard.getCurrentUserId();
|
|
||||||
|
|
||||||
query.Limit = query.Limit || 100;
|
|
||||||
query.ExcludeLocationTypes = "Virtual";
|
|
||||||
|
|
||||||
return ApiClient.getItems(userId, query);
|
|
||||||
};
|
|
||||||
|
|
||||||
var castPlayer = {};
|
|
||||||
|
|
||||||
$(castPlayer).on("playbackstart", function (e, data) {
|
|
||||||
|
|
||||||
Logger.log('cc: playbackstart');
|
|
||||||
|
|
||||||
var state = self.getPlayerStateInternal(data);
|
|
||||||
$(self).trigger("playbackstart", [state]);
|
|
||||||
});
|
|
||||||
|
|
||||||
$(castPlayer).on("playbackstop", function (e, data) {
|
|
||||||
|
|
||||||
Logger.log('cc: playbackstop');
|
|
||||||
var state = self.getPlayerStateInternal(data);
|
|
||||||
|
|
||||||
$(self).trigger("playbackstop", [state]);
|
|
||||||
|
|
||||||
// Reset this so the next query doesn't make it appear like content is playing.
|
|
||||||
self.lastPlayerData = {};
|
|
||||||
});
|
|
||||||
|
|
||||||
$(castPlayer).on("playbackprogress", function (e, data) {
|
|
||||||
|
|
||||||
Logger.log('cc: positionchange');
|
|
||||||
var state = self.getPlayerStateInternal(data);
|
|
||||||
|
|
||||||
$(self).trigger("positionchange", [state]);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.play = function (options) {
|
|
||||||
|
|
||||||
Dashboard.getCurrentUser().then(function (user) {
|
|
||||||
|
|
||||||
if (options.items) {
|
|
||||||
|
|
||||||
self.playWithCommand(options, 'PlayNow');
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
self.getItemsForPlayback({
|
|
||||||
|
|
||||||
Ids: options.ids.join(',')
|
|
||||||
|
|
||||||
}).then(function (result) {
|
|
||||||
|
|
||||||
options.items = result.Items;
|
|
||||||
self.playWithCommand(options, 'PlayNow');
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
self.playWithCommand = function (options, command) {
|
|
||||||
|
|
||||||
if (!options.items) {
|
|
||||||
ApiClient.getItem(Dashboard.getCurrentUserId(), options.ids[0]).then(function (item) {
|
|
||||||
|
|
||||||
options.items = [item];
|
|
||||||
self.playWithCommand(options, command);
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
playItemInternal(items[0], null, serverAddress);
|
|
||||||
};
|
|
||||||
|
|
||||||
function validatePlaybackInfoResult(result) {
|
|
||||||
|
|
||||||
if (result.ErrorCode) {
|
|
||||||
|
|
||||||
MediaController.showPlaybackInfoErrorMessage(result.ErrorCode);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOptimalMediaSource(mediaType, versions) {
|
|
||||||
|
|
||||||
var optimalVersion = versions.filter(function (v) {
|
|
||||||
|
|
||||||
v.enableDirectPlay = MediaController.supportsDirectPlay(v);
|
|
||||||
|
|
||||||
return v.enableDirectPlay;
|
|
||||||
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
if (!optimalVersion) {
|
|
||||||
optimalVersion = versions.filter(function (v) {
|
|
||||||
|
|
||||||
return v.SupportsDirectStream;
|
|
||||||
|
|
||||||
})[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return optimalVersion || versions.filter(function (s) {
|
|
||||||
return s.SupportsTranscoding;
|
|
||||||
})[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
function playItemInternal(item, startPosition) {
|
|
||||||
|
|
||||||
if (item == null) {
|
|
||||||
throw new Error("item cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.MediaType !== 'Audio' && item.MediaType !== 'Video') {
|
|
||||||
throw new Error("Unrecognized media type");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.IsPlaceHolder) {
|
|
||||||
MediaController.showPlaybackInfoErrorMessage('PlaceHolder');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var deviceProfile = self.getDeviceProfile();
|
|
||||||
|
|
||||||
if (item.MediaType === "Video") {
|
|
||||||
|
|
||||||
Dashboard.showModalLoadingMsg();
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaController.getPlaybackInfo(item.Id, deviceProfile, startPosition).then(function (playbackInfoResult) {
|
|
||||||
|
|
||||||
if (validatePlaybackInfoResult(playbackInfoResult)) {
|
|
||||||
|
|
||||||
var mediaSource = getOptimalMediaSource(item.MediaType, playbackInfoResult.MediaSources);
|
|
||||||
|
|
||||||
if (mediaSource) {
|
|
||||||
|
|
||||||
if (mediaSource.RequiresOpening) {
|
|
||||||
|
|
||||||
getLiveStream(item.Id, playbackInfoResult.PlaySessionId, deviceProfile, startPosition, mediaSource, null, null).then(function (openLiveStreamResult) {
|
|
||||||
|
|
||||||
openLiveStreamResult.MediaSource.enableDirectPlay = supportsDirectPlay(openLiveStreamResult.MediaSource);
|
|
||||||
|
|
||||||
playInternalPostMediaSourceSelection(item, openLiveStreamResult.MediaSource, startPosition, callback);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
playInternalPostMediaSourceSelection(item, mediaSource, startPosition, callback);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Dashboard.hideModalLoadingMsg();
|
|
||||||
MediaController.showPlaybackInfoErrorMessage('NoCompatibleStream');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function playInternalPostMediaSourceSelection(item, mediaSource, startPosition, deferred) {
|
|
||||||
|
|
||||||
Dashboard.hideModalLoadingMsg();
|
|
||||||
|
|
||||||
var streamInfo = MediaPlayer.createStreamInfo('Video', item, mediaSource, startPosition);
|
|
||||||
|
|
||||||
currentDevice.getMediaPlayer().playMedia(
|
|
||||||
streamInfo.url,
|
|
||||||
streamInfo.MimeType,
|
|
||||||
{
|
|
||||||
title: item.Name,
|
|
||||||
description: item.Overview || '',
|
|
||||||
shouldLoop: false
|
|
||||||
}
|
|
||||||
).success(function (launchSession, mediaControl) {
|
|
||||||
|
|
||||||
Logger.log("Video launch successful");
|
|
||||||
currentMediaControl = mediaControl && mediaControl.acquire();
|
|
||||||
|
|
||||||
}).error(function (err) {
|
|
||||||
|
|
||||||
Logger.log("error: " + err.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [streamInfo]);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.unpause = function () {
|
|
||||||
if (currentMediaControl) {
|
|
||||||
currentMediaControl.pause();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.pause = function () {
|
|
||||||
if (currentMediaControl) {
|
|
||||||
currentMediaControl.pause();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.shuffle = function (id) {
|
|
||||||
|
|
||||||
var userId = Dashboard.getCurrentUserId();
|
|
||||||
|
|
||||||
ApiClient.getItem(userId, id).then(function (item) {
|
|
||||||
|
|
||||||
self.playWithCommand({
|
|
||||||
|
|
||||||
items: [item]
|
|
||||||
|
|
||||||
}, 'Shuffle');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
self.instantMix = function (id) {
|
|
||||||
|
|
||||||
var userId = Dashboard.getCurrentUserId();
|
|
||||||
|
|
||||||
ApiClient.getItem(userId, id).then(function (item) {
|
|
||||||
|
|
||||||
self.playWithCommand({
|
|
||||||
|
|
||||||
items: [item]
|
|
||||||
|
|
||||||
}, 'InstantMix');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
self.canQueueMediaType = function (mediaType) {
|
|
||||||
return mediaType == "Audio";
|
|
||||||
};
|
|
||||||
|
|
||||||
self.queue = function (options) {
|
|
||||||
self.playWithCommnd(options, 'PlayLast');
|
|
||||||
};
|
|
||||||
|
|
||||||
self.queueNext = function (options) {
|
|
||||||
self.playWithCommand(options, 'PlayNext');
|
|
||||||
};
|
|
||||||
|
|
||||||
self.stop = function () {
|
|
||||||
if (currentMediaControl) {
|
|
||||||
currentMediaControl.stop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.displayContent = function (options) {
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
};
|
|
||||||
|
|
||||||
self.mute = function () {
|
|
||||||
if (currentDevice) {
|
|
||||||
currentDevice.getVolumeControl().setMute(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.unMute = function () {
|
|
||||||
self.setVolume(getCurrentVolume() + 2);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.toggleMute = function () {
|
|
||||||
|
|
||||||
var state = self.lastPlayerData || {};
|
|
||||||
state = state.PlayState || {};
|
|
||||||
|
|
||||||
if (state.IsMuted) {
|
|
||||||
self.unMute();
|
|
||||||
} else {
|
|
||||||
self.mute();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.getDeviceProfile = function () {
|
|
||||||
|
|
||||||
var qualityOption = self.getVideoQualityOptions().filter(function (q) {
|
|
||||||
return q.selected;
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
var bitrateSetting = AppSettings.maxStreamingBitrate();
|
|
||||||
|
|
||||||
var profile = {};
|
|
||||||
|
|
||||||
profile.MaxStreamingBitrate = bitrateSetting;
|
|
||||||
profile.MaxStaticBitrate = 40000000;
|
|
||||||
profile.MusicStreamingTranscodingBitrate = Math.min(bitrateSetting, 192000);
|
|
||||||
|
|
||||||
profile.DirectPlayProfiles = [];
|
|
||||||
profile.DirectPlayProfiles.push({
|
|
||||||
Container: 'mp4,m4v',
|
|
||||||
Type: 'Video',
|
|
||||||
VideoCodec: 'h264',
|
|
||||||
AudioCodec: 'aac,mp3,ac3'
|
|
||||||
});
|
|
||||||
profile.DirectPlayProfiles.push({
|
|
||||||
Container: 'mov',
|
|
||||||
Type: 'Video'
|
|
||||||
});
|
|
||||||
|
|
||||||
profile.DirectPlayProfiles.push({
|
|
||||||
Container: 'mp3',
|
|
||||||
Type: 'Audio'
|
|
||||||
});
|
|
||||||
|
|
||||||
profile.DirectPlayProfiles.push({
|
|
||||||
Container: 'aac',
|
|
||||||
Type: 'Audio'
|
|
||||||
});
|
|
||||||
|
|
||||||
profile.TranscodingProfiles = [];
|
|
||||||
profile.TranscodingProfiles.push({
|
|
||||||
Container: 'mp3',
|
|
||||||
Type: 'Audio',
|
|
||||||
AudioCodec: 'mp3',
|
|
||||||
Context: 'Streaming',
|
|
||||||
Protocol: 'http'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (self.canPlayHls()) {
|
|
||||||
profile.TranscodingProfiles.push({
|
|
||||||
Container: 'ts',
|
|
||||||
Type: 'Video',
|
|
||||||
AudioCodec: 'aac',
|
|
||||||
VideoCodec: 'h264',
|
|
||||||
Context: 'Streaming',
|
|
||||||
Protocol: 'hls'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
profile.TranscodingProfiles.push({
|
|
||||||
Container: 'mp4',
|
|
||||||
Type: 'Video',
|
|
||||||
AudioCodec: 'aac',
|
|
||||||
VideoCodec: 'h264',
|
|
||||||
Context: 'Streaming',
|
|
||||||
Protocol: 'http'
|
|
||||||
});
|
|
||||||
|
|
||||||
profile.ContainerProfiles = [];
|
|
||||||
|
|
||||||
profile.CodecProfiles = [];
|
|
||||||
profile.CodecProfiles.push({
|
|
||||||
Type: 'Audio',
|
|
||||||
Conditions: [{
|
|
||||||
Condition: 'LessThanEqual',
|
|
||||||
Property: 'AudioChannels',
|
|
||||||
Value: '2'
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
profile.CodecProfiles.push({
|
|
||||||
Type: 'VideoAudio',
|
|
||||||
Codec: 'aac,mp3',
|
|
||||||
Conditions: [
|
|
||||||
{
|
|
||||||
Condition: 'LessThanEqual',
|
|
||||||
Property: 'AudioChannels',
|
|
||||||
Value: '6'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
profile.CodecProfiles.push({
|
|
||||||
Type: 'Video',
|
|
||||||
Codec: 'h264',
|
|
||||||
Conditions: [
|
|
||||||
{
|
|
||||||
Condition: 'NotEquals',
|
|
||||||
Property: 'IsAnamorphic',
|
|
||||||
Value: 'true',
|
|
||||||
IsRequired: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Condition: 'EqualsAny',
|
|
||||||
Property: 'VideoProfile',
|
|
||||||
Value: 'high|main|baseline|constrained baseline'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Condition: 'LessThanEqual',
|
|
||||||
Property: 'VideoLevel',
|
|
||||||
Value: '41'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Condition: 'LessThanEqual',
|
|
||||||
Property: 'Width',
|
|
||||||
Value: qualityOption.maxWidth
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Subtitle profiles
|
|
||||||
profile.SubtitleProfiles = [];
|
|
||||||
profile.ResponseProfiles = [];
|
|
||||||
|
|
||||||
profile.ResponseProfiles.push({
|
|
||||||
Type: 'Video',
|
|
||||||
Container: 'm4v',
|
|
||||||
MimeType: 'video/mp4'
|
|
||||||
});
|
|
||||||
|
|
||||||
//profile.ResponseProfiles.push({
|
|
||||||
// Type: 'Video',
|
|
||||||
// Container: 'mkv',
|
|
||||||
// MimeType: 'video/webm'
|
|
||||||
//});
|
|
||||||
|
|
||||||
return profile;
|
|
||||||
};
|
|
||||||
|
|
||||||
function getBaseTargetInfo() {
|
|
||||||
var target = {};
|
|
||||||
|
|
||||||
target.playableMediaTypes = ["Audio", "Video"];
|
|
||||||
target.isLocalPlayer = false;
|
|
||||||
target.supportedCommands = [
|
|
||||||
"VolumeUp",
|
|
||||||
"VolumeDown",
|
|
||||||
"Mute",
|
|
||||||
"Unmute",
|
|
||||||
"ToggleMute",
|
|
||||||
"SetVolume"
|
|
||||||
];
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertDeviceToTarget(device) {
|
|
||||||
|
|
||||||
var target = getBaseTargetInfo();
|
|
||||||
|
|
||||||
target.appName = target.name = target.deviceName = device.getFriendlyName();
|
|
||||||
target.playerName = PlayerName;
|
|
||||||
target.id = device.getId();
|
|
||||||
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValid(device) {
|
|
||||||
|
|
||||||
var validTokens = ['AirPlay', 'Airplay', 'airplay'];
|
|
||||||
|
|
||||||
return validTokens.filter(function (t) {
|
|
||||||
|
|
||||||
return device.hasService(t);
|
|
||||||
|
|
||||||
}).length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.getTargets = function () {
|
|
||||||
|
|
||||||
return ConnectSDKHelper.getDeviceList().filter(function (d) {
|
|
||||||
|
|
||||||
return isValid(d);
|
|
||||||
|
|
||||||
}).map(convertDeviceToTarget);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.seek = function (position) {
|
|
||||||
|
|
||||||
position = parseInt(position);
|
|
||||||
position = position / 10000000;
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
};
|
|
||||||
|
|
||||||
self.setAudioStreamIndex = function (index) {
|
|
||||||
// TODO
|
|
||||||
};
|
|
||||||
|
|
||||||
self.setSubtitleStreamIndex = function (index) {
|
|
||||||
// TODO
|
|
||||||
};
|
|
||||||
|
|
||||||
self.nextTrack = function () {
|
|
||||||
// TODO
|
|
||||||
};
|
|
||||||
|
|
||||||
self.previousTrack = function () {
|
|
||||||
// TODO
|
|
||||||
};
|
|
||||||
|
|
||||||
self.beginPlayerUpdates = function () {
|
|
||||||
// Setup polling here
|
|
||||||
};
|
|
||||||
|
|
||||||
self.endPlayerUpdates = function () {
|
|
||||||
// Stop polling here
|
|
||||||
};
|
|
||||||
|
|
||||||
function getCurrentVolume() {
|
|
||||||
var state = self.lastPlayerData || {};
|
|
||||||
state = state.PlayState || {};
|
|
||||||
|
|
||||||
return state.VolumeLevel == null ? 100 : state.VolumeLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.volumeDown = function () {
|
|
||||||
|
|
||||||
if (currentDevice) {
|
|
||||||
currentDevice.getVolumeControl().volumeDown();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.volumeUp = function () {
|
|
||||||
|
|
||||||
if (currentDevice) {
|
|
||||||
currentDevice.getVolumeControl().volumeUp();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.setVolume = function (vol) {
|
|
||||||
|
|
||||||
vol = Math.min(vol, 100);
|
|
||||||
vol = Math.max(vol, 0);
|
|
||||||
|
|
||||||
if (currentDevice) {
|
|
||||||
currentDevice.getVolumeControl().setVolume(vol / 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.getPlayerState = function () {
|
|
||||||
|
|
||||||
var deferred = $.Deferred();
|
|
||||||
|
|
||||||
var result = self.getPlayerStateInternal();
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [result]);
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
};
|
|
||||||
|
|
||||||
self.lastPlayerData = {};
|
|
||||||
|
|
||||||
self.getPlayerStateInternal = function (data) {
|
|
||||||
|
|
||||||
data = data || self.lastPlayerData;
|
|
||||||
self.lastPlayerData = data;
|
|
||||||
|
|
||||||
Logger.log(JSON.stringify(data));
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleSessionDisconnect() {
|
|
||||||
Logger.log("session disconnected");
|
|
||||||
|
|
||||||
cleanupSession();
|
|
||||||
MediaController.removeActivePlayer(PlayerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanupSession() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function launchWebApp(device) {
|
|
||||||
|
|
||||||
if (currentDevice) {
|
|
||||||
cleanupSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.log('session.connect succeeded');
|
|
||||||
|
|
||||||
MediaController.setActivePlayer(PlayerName, convertDeviceToTarget(device));
|
|
||||||
currentDevice = device;
|
|
||||||
currentDeviceId = device.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDeviceReady(device) {
|
|
||||||
|
|
||||||
device.off("ready");
|
|
||||||
|
|
||||||
Logger.log('creating webAppSession');
|
|
||||||
|
|
||||||
launchWebApp(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tryPair = function (target) {
|
|
||||||
|
|
||||||
var deferred = $.Deferred();
|
|
||||||
|
|
||||||
var device = ConnectSDKHelper.getDeviceList().filter(function (d) {
|
|
||||||
|
|
||||||
return d.getId() == target.id;
|
|
||||||
})[0];
|
|
||||||
|
|
||||||
if (device) {
|
|
||||||
|
|
||||||
self.tryPairWithDevice(device, deferred);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
deferred.reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
};
|
|
||||||
|
|
||||||
self.tryPairWithDevice = function (device, deferred) {
|
|
||||||
|
|
||||||
Logger.log('Will attempt to connect to Connect Device');
|
|
||||||
|
|
||||||
device.on("disconnect", function () {
|
|
||||||
device.off("ready");
|
|
||||||
device.off("disconnect");
|
|
||||||
});
|
|
||||||
|
|
||||||
if (device.isReady()) {
|
|
||||||
Logger.log('Device is already ready, calling onDeviceReady');
|
|
||||||
onDeviceReady(device);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
Logger.log('Binding device ready handler');
|
|
||||||
|
|
||||||
device.on("ready", function () {
|
|
||||||
Logger.log('device.ready fired');
|
|
||||||
onDeviceReady(device);
|
|
||||||
});
|
|
||||||
|
|
||||||
Logger.log('Calling device.connect');
|
|
||||||
device.connect();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$(MediaController).on('playerchange', function (e, newPlayer, newTarget) {
|
|
||||||
|
|
||||||
if (currentDevice) {
|
|
||||||
if (newTarget.id != currentDeviceId) {
|
|
||||||
if (currentDevice) {
|
|
||||||
Logger.log('Disconnecting from connect device');
|
|
||||||
//currentDevice.disconnect();
|
|
||||||
cleanupSession();
|
|
||||||
currentDevice = null;
|
|
||||||
currentDeviceId = null;
|
|
||||||
currentMediaControl = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function onResume() {
|
|
||||||
|
|
||||||
var deviceId = currentDeviceId;
|
|
||||||
|
|
||||||
if (deviceId) {
|
|
||||||
self.tryPair({
|
|
||||||
id: deviceId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("resume", onResume, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaController.registerPlayer(new connectSDKPlayer());
|
|
||||||
|
|
||||||
})();
|
|
297
dashboard-ui/cordova/iap.js
vendored
297
dashboard-ui/cordova/iap.js
vendored
|
@ -1,297 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
var updatedProducts = [];
|
|
||||||
var enteredEmail;
|
|
||||||
|
|
||||||
function getStoreFeatureId(feature) {
|
|
||||||
|
|
||||||
if (feature == 'embypremieremonthly') {
|
|
||||||
return 'emby.subscription.monthly';
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'appunlock';
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateProductInfo(product) {
|
|
||||||
|
|
||||||
//if (product.id == 'appunlock') {
|
|
||||||
// product.owned = false;
|
|
||||||
//}
|
|
||||||
|
|
||||||
updatedProducts = updatedProducts.filter(function (r) {
|
|
||||||
return r.id != product.id;
|
|
||||||
});
|
|
||||||
|
|
||||||
updatedProducts.push(product);
|
|
||||||
|
|
||||||
Events.trigger(IapManager, 'productupdated', [product]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getProduct(feature) {
|
|
||||||
|
|
||||||
var id = getStoreFeatureId(feature);
|
|
||||||
|
|
||||||
var products = updatedProducts.filter(function (r) {
|
|
||||||
return r.id == id;
|
|
||||||
});
|
|
||||||
|
|
||||||
return products.length ? products[0] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPurchaseAvailable(feature) {
|
|
||||||
|
|
||||||
var product = getProduct(feature);
|
|
||||||
|
|
||||||
return product != null && product.valid /*&& product.canPurchase*/;
|
|
||||||
}
|
|
||||||
|
|
||||||
function beginPurchase(feature, email) {
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
enteredEmail = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
validationCache = {};
|
|
||||||
|
|
||||||
var id = getStoreFeatureId(feature);
|
|
||||||
store.order(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function restorePurchase(id) {
|
|
||||||
validationCache = {};
|
|
||||||
store.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
var validationCache = {};
|
|
||||||
|
|
||||||
function validateProduct(product, callback) {
|
|
||||||
|
|
||||||
var productId = product.id;
|
|
||||||
|
|
||||||
// We should never get in here with the unlock, but in case we do
|
|
||||||
if ((productId || '').toLowerCase().indexOf('appunlock') != -1) {
|
|
||||||
callback(true, product);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheKey = productId + (product.transaction.id || '');
|
|
||||||
|
|
||||||
var cachedResult = validationCache[cacheKey];
|
|
||||||
if (cachedResult && (new Date().getTime() - cachedResult.date) < 60000) {
|
|
||||||
if (cachedResult.result) {
|
|
||||||
callback(true, product);
|
|
||||||
} else {
|
|
||||||
callback(false, {
|
|
||||||
code: cachedResult.errorCode,
|
|
||||||
error: {
|
|
||||||
message: cachedResult.errorMessage
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// product attributes:
|
|
||||||
// https://github.com/j3k0/cordova-plugin-purchase/blob/master/doc/api.md#validation-error-codes
|
|
||||||
|
|
||||||
var receipt = product.transaction.appStoreReceipt;
|
|
||||||
var price = product.price;
|
|
||||||
|
|
||||||
var postData = {
|
|
||||||
store: "Apple",
|
|
||||||
application: "com.emby.mobile",
|
|
||||||
product: productId,
|
|
||||||
type: "Subscription",
|
|
||||||
storeToken: receipt,
|
|
||||||
amt: price
|
|
||||||
};
|
|
||||||
|
|
||||||
var promise;
|
|
||||||
|
|
||||||
if (enteredEmail) {
|
|
||||||
postData.email = enteredEmail;
|
|
||||||
postData.storeId = enteredEmail;
|
|
||||||
postData.feature = "MBSClubMonthly";
|
|
||||||
|
|
||||||
promise = ApiClient.ajax({
|
|
||||||
type: "POST",
|
|
||||||
url: ApiClient.getUrl("Appstore/Register"),
|
|
||||||
data: {
|
|
||||||
Parameters: JSON.stringify(postData)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
promise = fetch("http://mb3admin.com/admin/service/appstore/register", {
|
|
||||||
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(postData),
|
|
||||||
headers: {
|
|
||||||
"X-Emby-Token": "EMBY-APPLE-VALIDATE",
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
promise.then(function () {
|
|
||||||
|
|
||||||
setCachedResult(cacheKey, true);
|
|
||||||
|
|
||||||
callback(true, product);
|
|
||||||
|
|
||||||
}, function (e) {
|
|
||||||
|
|
||||||
if (e.status == 402) {
|
|
||||||
|
|
||||||
setCachedResult(cacheKey, false, store.PURCHASE_EXPIRED, 'Subscription Expired');
|
|
||||||
|
|
||||||
callback(false, {
|
|
||||||
code: store.PURCHASE_EXPIRED,
|
|
||||||
error: {
|
|
||||||
message: "Subscription Expired"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
//alert('validate fail - other ' + e.status);
|
|
||||||
|
|
||||||
validationCache = {};
|
|
||||||
|
|
||||||
callback(false, {
|
|
||||||
code: store.CONNECTION_FAILED,
|
|
||||||
error: {
|
|
||||||
message: "Connection Failure"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCachedResult(key, result, code, message) {
|
|
||||||
|
|
||||||
validationCache[key] = {
|
|
||||||
date: new Date().getTime(),
|
|
||||||
result: result,
|
|
||||||
errorCode: code,
|
|
||||||
errorMessage: message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function initProduct(id, requiresVerification, type) {
|
|
||||||
|
|
||||||
store.register({
|
|
||||||
id: id,
|
|
||||||
alias: id,
|
|
||||||
type: type
|
|
||||||
});
|
|
||||||
|
|
||||||
// When purchase of the full version is approved,
|
|
||||||
// show some logs and finish the transaction.
|
|
||||||
store.when(id).approved(function (product) {
|
|
||||||
|
|
||||||
//product.finish();
|
|
||||||
if (requiresVerification) {
|
|
||||||
product.verify();
|
|
||||||
} else {
|
|
||||||
product.finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (requiresVerification) {
|
|
||||||
store.when(id).verified(function (p) {
|
|
||||||
//alert('verified');
|
|
||||||
updateProductInfo(p);
|
|
||||||
p.finish();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// The play button can only be accessed when the user
|
|
||||||
// owns the full version.
|
|
||||||
store.when(id).updated(function (product) {
|
|
||||||
|
|
||||||
if (product.loaded && product.valid && product.state == store.APPROVED) {
|
|
||||||
Logger.log('finishing previously created transaction');
|
|
||||||
if (requiresVerification) {
|
|
||||||
//product.verify();
|
|
||||||
if (product.owned) {
|
|
||||||
//alert('sub owned!');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
product.finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateProductInfo(product);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function initializeStore() {
|
|
||||||
|
|
||||||
// Let's set a pretty high verbosity level, so that we see a lot of stuff
|
|
||||||
// in the console (reassuring us that something is happening).
|
|
||||||
store.verbosity = store.INFO;
|
|
||||||
|
|
||||||
store.validator = validateProduct;
|
|
||||||
|
|
||||||
initProduct(getStoreFeatureId(""), false, store.NON_CONSUMABLE);
|
|
||||||
initProduct(getStoreFeatureId("embypremieremonthly"), true, store.PAID_SUBSCRIPTION);
|
|
||||||
|
|
||||||
// When every goes as expected, it's time to celebrate!
|
|
||||||
// The "ready" event should be welcomed with music and fireworks,
|
|
||||||
// go ask your boss about it! (just in case)
|
|
||||||
store.ready(function () {
|
|
||||||
|
|
||||||
Logger.log("Store ready");
|
|
||||||
});
|
|
||||||
|
|
||||||
// After we've done our setup, we tell the store to do
|
|
||||||
// it's first refresh. Nothing will happen if we do not call store.refresh()
|
|
||||||
store.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSubscriptionOptions() {
|
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
|
|
||||||
var options = [];
|
|
||||||
|
|
||||||
options.push({
|
|
||||||
feature: 'embypremieremonthly',
|
|
||||||
buttonText: 'EmbyPremiereMonthlyWithPrice'
|
|
||||||
});
|
|
||||||
|
|
||||||
options = options.filter(function (o) {
|
|
||||||
return getProduct(o.feature) != null;
|
|
||||||
|
|
||||||
}).map(function (o) {
|
|
||||||
|
|
||||||
o.id = getStoreFeatureId(o.feature);
|
|
||||||
o.buttonText = Globalize.translate(o.buttonText, getProduct(o.feature).price);
|
|
||||||
o.owned = getProduct(o.feature).owned;
|
|
||||||
return o;
|
|
||||||
});
|
|
||||||
|
|
||||||
resolve(options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function isUnlockedOverride(feature) {
|
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
|
|
||||||
resolve(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.IapManager = {
|
|
||||||
isPurchaseAvailable: isPurchaseAvailable,
|
|
||||||
getProductInfo: getProduct,
|
|
||||||
beginPurchase: beginPurchase,
|
|
||||||
restorePurchase: restorePurchase,
|
|
||||||
getSubscriptionOptions: getSubscriptionOptions,
|
|
||||||
isUnlockedOverride: isUnlockedOverride
|
|
||||||
};
|
|
||||||
|
|
||||||
initializeStore();
|
|
||||||
|
|
||||||
})();
|
|
156
dashboard-ui/cordova/imagestore.js
vendored
156
dashboard-ui/cordova/imagestore.js
vendored
|
@ -1,156 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function setImageIntoElement(elem, url) {
|
|
||||||
|
|
||||||
if (elem.tagName !== "IMG") {
|
|
||||||
|
|
||||||
elem.style.backgroundImage = "url('" + url + "')";
|
|
||||||
|
|
||||||
} else {
|
|
||||||
elem.setAttribute("src", url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileSystem;
|
|
||||||
function getFileSystem() {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
if (fileSystem) {
|
|
||||||
deferred.resolveWith(null, [fileSystem]);
|
|
||||||
} else {
|
|
||||||
requestFileSystem(PERSISTENT, 0, function (fs) {
|
|
||||||
fileSystem = fs;
|
|
||||||
deferred.resolveWith(null, [fileSystem]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function indexedDbBlobImageStore() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
function getCacheKey(url) {
|
|
||||||
|
|
||||||
// Try to strip off the domain to share the cache between local and remote connections
|
|
||||||
var index = url.indexOf('://');
|
|
||||||
|
|
||||||
if (index != -1) {
|
|
||||||
url = url.substring(index + 3);
|
|
||||||
|
|
||||||
index = url.indexOf('/');
|
|
||||||
|
|
||||||
if (index != -1) {
|
|
||||||
url = url.substring(index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return CryptoJS.MD5(url).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeReturnUrl(url) {
|
|
||||||
if (browserInfo.safari) {
|
|
||||||
|
|
||||||
// Use the embedded server for iOS8, and also if we don't know the iOS version, just to be safe
|
|
||||||
var index = url.indexOf('/Documents');
|
|
||||||
if (index != -1) {
|
|
||||||
return url.substring(index);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return url.replace('file://', '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.getImageUrl = function (originalUrl) {
|
|
||||||
|
|
||||||
if (browserInfo.android && originalUrl.indexOf('tag=') != -1) {
|
|
||||||
originalUrl += "&accept=webp";
|
|
||||||
}
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
var key = getCacheKey(originalUrl);
|
|
||||||
|
|
||||||
//Logger.log('getImageUrl:' + originalUrl);
|
|
||||||
|
|
||||||
getFileSystem().then(function (fileSystem) {
|
|
||||||
var path = fileSystem.root.toURL() + "/emby/cache/" + key;
|
|
||||||
|
|
||||||
resolveLocalFileSystemURL(path, function (fileEntry) {
|
|
||||||
var localUrl = normalizeReturnUrl(fileEntry.toURL());
|
|
||||||
//Logger.log('returning cached file: ' + localUrl);
|
|
||||||
deferred.resolveWith(null, [localUrl]);
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
|
|
||||||
//Logger.log('downloading: ' + originalUrl);
|
|
||||||
var ft = new FileTransfer();
|
|
||||||
ft.download(originalUrl, path, function (entry) {
|
|
||||||
|
|
||||||
var localUrl = normalizeReturnUrl(entry.toURL());
|
|
||||||
|
|
||||||
//Logger.log(localUrl);
|
|
||||||
deferred.resolveWith(null, [localUrl]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
};
|
|
||||||
|
|
||||||
self.setImageInto = function (elem, url) {
|
|
||||||
|
|
||||||
function onFail() {
|
|
||||||
setImageIntoElement(elem, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
//if (browserInfo.safari) {
|
|
||||||
// setImageWithSdWebImage(elem, url);
|
|
||||||
// return;
|
|
||||||
//}
|
|
||||||
|
|
||||||
self.getImageUrl(url).then(function (localUrl) {
|
|
||||||
|
|
||||||
setImageIntoElement(elem, localUrl);
|
|
||||||
|
|
||||||
}, onFail);
|
|
||||||
};
|
|
||||||
|
|
||||||
var imageIdIndex = 1;
|
|
||||||
|
|
||||||
function setImageWithSdWebImage(elem, url) {
|
|
||||||
|
|
||||||
var rect = elem.getBoundingClientRect();
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
data: url,
|
|
||||||
index: imageIdIndex,
|
|
||||||
quality: 0,
|
|
||||||
scale: Math.round(rect.width) + 'x' + Math.round(rect.height),
|
|
||||||
downloadOptions: window.CollectionRepeatImageOptions.SDWebImageRetryFailed | window.CollectionRepeatImageOptions.SDWebImageLowPriority | window.CollectionRepeatImageOptions.SDWebImageAllowInvalidSSLCertificates
|
|
||||||
};
|
|
||||||
|
|
||||||
if (elem.classList.contains('coveredCardImage')) {
|
|
||||||
options.scale += '!';
|
|
||||||
}
|
|
||||||
|
|
||||||
imageIdIndex++;
|
|
||||||
|
|
||||||
window.CollectionRepeatImage.getImage(options, function (data) {
|
|
||||||
var dataUrl = 'data:image/jpeg;base64,' + data;
|
|
||||||
elem.style.backgroundImage = "url('" + dataUrl + "')";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.ImageStore = self;
|
|
||||||
}
|
|
||||||
|
|
||||||
require(['cryptojs-sha1'], function () {
|
|
||||||
new indexedDbBlobImageStore();
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
103
dashboard-ui/cordova/ios/backgroundfetch.js
vendored
103
dashboard-ui/cordova/ios/backgroundfetch.js
vendored
|
@ -1,103 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
var lastStart = 0;
|
|
||||||
|
|
||||||
function onDeviceReady() {
|
|
||||||
|
|
||||||
//var fetcher = window.BackgroundFetch;
|
|
||||||
|
|
||||||
//fetcher.configure(onBackgroundFetch, onBackgroundFetchFailed, {
|
|
||||||
// stopOnTerminate: false // <-- false is default
|
|
||||||
//});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSyncFinish() {
|
|
||||||
|
|
||||||
Logger.log('BackgroundFetch completed');
|
|
||||||
|
|
||||||
var fetcher = window.BackgroundFetch;
|
|
||||||
fetcher.finish(); // <-- N.B. You MUST called #finish so that native-side can signal completion of the background-thread to the os.
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSyncFail() {
|
|
||||||
|
|
||||||
Logger.log('BackgroundFetch completed - sync failed');
|
|
||||||
|
|
||||||
var fetcher = window.BackgroundFetch;
|
|
||||||
fetcher.finish(); // <-- N.B. You MUST called #finish so that native-side can signal completion of the background-thread to the os.
|
|
||||||
}
|
|
||||||
|
|
||||||
function startSync(reportToFetcher, syncOptions) {
|
|
||||||
lastStart = new Date().getTime();
|
|
||||||
|
|
||||||
require(['localsync'], function () {
|
|
||||||
|
|
||||||
if (LocalSync.getSyncStatus() == 'Syncing') {
|
|
||||||
onSyncFinish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var promise = LocalSync.sync(syncOptions);
|
|
||||||
|
|
||||||
if (reportToFetcher) {
|
|
||||||
promise.then(onSyncFinish, onSyncFail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onBackgroundFetch() {
|
|
||||||
|
|
||||||
Logger.log('BackgroundFetch initiated');
|
|
||||||
|
|
||||||
startSync(true, {
|
|
||||||
uploadPhotos: false,
|
|
||||||
enableNewDownloads: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onBackgroundFetchFailed() {
|
|
||||||
Logger.log('- BackgroundFetch failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
var syncInterval = 900000;
|
|
||||||
var photoUploadInterval = 21600000;
|
|
||||||
var offlineUserSyncInterval = 43200000;
|
|
||||||
function startIntervalSync() {
|
|
||||||
|
|
||||||
startSync(false, {
|
|
||||||
uploadPhotos: true,
|
|
||||||
enableNewDownloads: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeSyncOptions(options) {
|
|
||||||
|
|
||||||
options.enableBackgroundTransfer = true;
|
|
||||||
|
|
||||||
options.uploadPhotos = (new Date().getTime() - lastStart) >= photoUploadInterval;
|
|
||||||
options.syncOfflineUsers = (new Date().getTime() - lastStart) >= offlineUserSyncInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
require(['localsync'], function () {
|
|
||||||
|
|
||||||
LocalSync.normalizeSyncOptions = normalizeSyncOptions;
|
|
||||||
});
|
|
||||||
|
|
||||||
pageClassOn('pageshow', "libraryPage", function () {
|
|
||||||
|
|
||||||
if (!Dashboard.getCurrentUserId()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((new Date().getTime() - lastStart) >= syncInterval) {
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
startIntervalSync();
|
|
||||||
|
|
||||||
}, 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
onDeviceReady();
|
|
||||||
})();
|
|
23
dashboard-ui/cordova/ios/orientation.js
vendored
23
dashboard-ui/cordova/ios/orientation.js
vendored
|
@ -1,23 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function forceScroll() {
|
|
||||||
|
|
||||||
var doc = $(document);
|
|
||||||
|
|
||||||
// Try to make it react quicker to the orientation change
|
|
||||||
doc.scrollTop(doc.scrollTop() + 1);
|
|
||||||
|
|
||||||
//$('paper-tabs').filter(':visible').hide().show();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onOrientationChange() {
|
|
||||||
|
|
||||||
forceScroll();
|
|
||||||
for (var i = 0; i <= 500; i += 100) {
|
|
||||||
setTimeout(forceScroll, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('orientationchange', onOrientationChange);
|
|
||||||
|
|
||||||
})();
|
|
205
dashboard-ui/cordova/ios/tabbar.js
vendored
205
dashboard-ui/cordova/ios/tabbar.js
vendored
|
@ -1,205 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
var initComplete = false;
|
|
||||||
var ignoreNextSelection = false;
|
|
||||||
|
|
||||||
function onTabSelected(name) {
|
|
||||||
|
|
||||||
if (ignoreNextSelection) {
|
|
||||||
ignoreNextSelection = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (name) {
|
|
||||||
case 'Favorites':
|
|
||||||
Dashboard.navigate('favorites.html');
|
|
||||||
break;
|
|
||||||
case 'Library':
|
|
||||||
Dashboard.navigate('index.html');
|
|
||||||
break;
|
|
||||||
case 'Search':
|
|
||||||
Dashboard.navigate('search.html');
|
|
||||||
break;
|
|
||||||
case 'NowPlaying':
|
|
||||||
Dashboard.navigate('nowplaying.html');
|
|
||||||
break;
|
|
||||||
case 'Sync':
|
|
||||||
Dashboard.navigate('mysync.html');
|
|
||||||
break;
|
|
||||||
case 'LiveTv':
|
|
||||||
Dashboard.navigate('livetv.html');
|
|
||||||
break;
|
|
||||||
case 'Settings':
|
|
||||||
Dashboard.navigate('mypreferencesmenu.html?userId=' + Dashboard.getCurrentUserId());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
|
|
||||||
// Use system defined items for this demo
|
|
||||||
// If an image is passed, label is not used
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new tab bar item for use on a previously created tab bar. Use ::showTabBarItems to show the new item on the tab bar.
|
|
||||||
*
|
|
||||||
* If the supplied image name is one of the labels listed below, then this method will construct a tab button
|
|
||||||
* using the standard system buttons. Note that if you use one of the system images, that the \c title you supply will be ignored.
|
|
||||||
* - <b>Tab Buttons</b>
|
|
||||||
* - tabButton:More
|
|
||||||
* - tabButton:Favorites
|
|
||||||
* - tabButton:Featured
|
|
||||||
* - tabButton:TopRated
|
|
||||||
* - tabButton:Recents
|
|
||||||
* - tabButton:Contacts
|
|
||||||
* - tabButton:History
|
|
||||||
* - tabButton:Bookmarks
|
|
||||||
* - tabButton:Search
|
|
||||||
* - tabButton:Downloads
|
|
||||||
* - tabButton:MostRecent
|
|
||||||
* - tabButton:MostViewed
|
|
||||||
* @brief create a tab bar item
|
|
||||||
* @param arguments Parameters used to create the tab bar
|
|
||||||
* -# \c name internal name to refer to this tab by
|
|
||||||
* -# \c title title text to show on the tab, or null if no text should be shown
|
|
||||||
* -# \c image image filename or internal identifier to show, or null if now image should be shown
|
|
||||||
* -# \c tag unique number to be used as an internal reference to this button
|
|
||||||
* @param options Options for customizing the individual tab item
|
|
||||||
* - \c badge value to display in the optional circular badge on the item; if nil or unspecified, the badge will be hidden
|
|
||||||
*/
|
|
||||||
|
|
||||||
var items = [
|
|
||||||
{ name: 'Library', label: Globalize.translate('ButtonLibrary'), image: 'tabbar/tab-library.png', options: {} },
|
|
||||||
{ name: 'LiveTv', label: Globalize.translate('HeaderLiveTV'), image: 'tabbar/tab-livetv.png', options: {} },
|
|
||||||
{ name: 'Favorites', label: Globalize.translate('ButtonFavorites'), image: 'tabButton:Favorites', options: {} },
|
|
||||||
{ name: 'Search', label: Globalize.translate('ButtonSearch'), image: 'tabButton:Search', options: {} },
|
|
||||||
{ name: 'NowPlaying', label: Globalize.translate('ButtonNowPlaying'), image: 'tabbar/tab-nowplaying.png', options: {} },
|
|
||||||
{ name: 'Sync', label: Globalize.translate('ButtonSync'), image: 'tabbar/tab-sync.png', options: {} },
|
|
||||||
{ name: 'Settings', label: Globalize.translate('ButtonSettings'), image: 'tabbar/tab-settings.png', options: {} }
|
|
||||||
];
|
|
||||||
|
|
||||||
for (var i = 0; i < items.length; i++) {
|
|
||||||
var item = items[i];
|
|
||||||
TabBar.createItem(item.name, item.label, item.image, onTabSelected, item.options);
|
|
||||||
};
|
|
||||||
|
|
||||||
TabBar.showItems();
|
|
||||||
initComplete = true;
|
|
||||||
|
|
||||||
ignoreNextSelection = true;
|
|
||||||
TabBar.selectItem('Library');
|
|
||||||
}
|
|
||||||
|
|
||||||
function showTabs() {
|
|
||||||
|
|
||||||
if (!initComplete) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
TabBar.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showUserTabs(user) {
|
|
||||||
|
|
||||||
if (!window.ApiClient) {
|
|
||||||
onUserViewResponse(user, []);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ApiClient.getUserViews({}, user.Id).then(function (result) {
|
|
||||||
|
|
||||||
onUserViewResponse(user, result.Items);
|
|
||||||
|
|
||||||
}, function (result) {
|
|
||||||
|
|
||||||
onUserViewResponse(user, []);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUserViewResponse(user, views) {
|
|
||||||
|
|
||||||
var tabs = ['Library'];
|
|
||||||
|
|
||||||
if (views.filter(function (v) {
|
|
||||||
|
|
||||||
return v.CollectionType == 'livetv';
|
|
||||||
|
|
||||||
}).length) {
|
|
||||||
tabs.push('LiveTv');
|
|
||||||
}
|
|
||||||
|
|
||||||
tabs.push('Favorites');
|
|
||||||
tabs.push('Search');
|
|
||||||
tabs.push('NowPlaying');
|
|
||||||
|
|
||||||
if (user.Policy.EnableSync && Dashboard.capabilities().SupportsSync) {
|
|
||||||
|
|
||||||
tabs.push('Sync');
|
|
||||||
}
|
|
||||||
|
|
||||||
tabs.push('Settings');
|
|
||||||
|
|
||||||
TabBar.showNamedItems(tabs);
|
|
||||||
|
|
||||||
// We need to make sure the above completes first
|
|
||||||
setTimeout(showTabs, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showCurrentUserTabs() {
|
|
||||||
|
|
||||||
if (!Dashboard.getCurrentUserId()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Dashboard.getCurrentUser().then(showUserTabs);
|
|
||||||
}
|
|
||||||
|
|
||||||
var isFirstHide = true;
|
|
||||||
function hideTabs() {
|
|
||||||
|
|
||||||
if (!initComplete) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hide = function () { TabBar.hide(); };
|
|
||||||
if (isFirstHide) {
|
|
||||||
isFirstHide = false;
|
|
||||||
setTimeout(hide, 1000);
|
|
||||||
} else {
|
|
||||||
hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Dashboard.ready(function () {
|
|
||||||
|
|
||||||
init();
|
|
||||||
|
|
||||||
Events.on(ConnectionManager, 'localusersignedin', function (e, user) {
|
|
||||||
showUserTabs(user);
|
|
||||||
});
|
|
||||||
|
|
||||||
Events.on(ConnectionManager, 'localusersignedout', hideTabs);
|
|
||||||
Events.on(MediaController, 'beforeplaybackstart', onPlaybackStart);
|
|
||||||
Events.on(MediaController, 'playbackstop', onPlaybackStop);
|
|
||||||
|
|
||||||
showCurrentUserTabs();
|
|
||||||
});
|
|
||||||
|
|
||||||
function onPlaybackStart(e, state, player) {
|
|
||||||
|
|
||||||
if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video' && player.isLocalPlayer) {
|
|
||||||
hideTabs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPlaybackStop(e, state, player) {
|
|
||||||
|
|
||||||
if (state.NowPlayingItem && state.NowPlayingItem.MediaType == 'Video' && player.isLocalPlayer) {
|
|
||||||
showTabs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
272
dashboard-ui/cordova/ios/vlcplayer.js
vendored
272
dashboard-ui/cordova/ios/vlcplayer.js
vendored
|
@ -1,272 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function vlcRenderer(options) {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// Need to use this to fire events because the iOS vlc callbacks always go to the first instance
|
|
||||||
window.AudioRenderer.Current = self;
|
|
||||||
|
|
||||||
self.enableProgressReporting = options.type == 'audio';
|
|
||||||
|
|
||||||
function onEnded() {
|
|
||||||
Events.trigger(window.AudioRenderer.Current, 'ended');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onTimeUpdate() {
|
|
||||||
Events.trigger(window.AudioRenderer.Current, 'timeupdate');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onVolumeChange() {
|
|
||||||
Events.trigger(window.AudioRenderer.Current, 'volumechange');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPlaying() {
|
|
||||||
Events.trigger(window.AudioRenderer.Current, 'playing');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPlay() {
|
|
||||||
Events.trigger(window.AudioRenderer.Current, 'play');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPause() {
|
|
||||||
Events.trigger(window.AudioRenderer.Current, 'pause');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClick() {
|
|
||||||
Events.trigger(window.AudioRenderer.Current, 'click');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDblClick() {
|
|
||||||
Events.trigger(window.AudioRenderer.Current, 'dblclick');
|
|
||||||
}
|
|
||||||
|
|
||||||
function onError() {
|
|
||||||
|
|
||||||
var errorCode = this.error ? this.error.code : '';
|
|
||||||
Logger.log('Media element error code: ' + errorCode);
|
|
||||||
|
|
||||||
Events.trigger(window.AudioRenderer.Current, 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
self.playerState = {};
|
|
||||||
|
|
||||||
self.currentTime = function (val) {
|
|
||||||
|
|
||||||
if (val != null) {
|
|
||||||
window.audioplayer.seekto(function () {
|
|
||||||
Logger.log('set currentTime succeeded!');
|
|
||||||
}, function () {
|
|
||||||
Logger.log('set currentTime failed!');
|
|
||||||
|
|
||||||
}, val / 1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.playerState.currentTime;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.duration = function (val) {
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// This value doesn't seem to be getting reported properly
|
|
||||||
// Right now it's only used to determine if the player can seek, so for now we can mock it
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.stop = function () {
|
|
||||||
window.audioplayer.stop(function (result) {
|
|
||||||
Logger.log('Stop succeeded!');
|
|
||||||
reportEvent('playbackstop', result);
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
Logger.log('Stop failed!');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.pause = function () {
|
|
||||||
window.audioplayer.pause(function (result) {
|
|
||||||
Logger.log('Pause succeeded!');
|
|
||||||
reportEvent('sepaused', result);
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
Logger.log('Pause failed!');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.unpause = function () {
|
|
||||||
window.audioplayer.pause(function (result) {
|
|
||||||
Logger.log('Unpause succeeded!');
|
|
||||||
reportEvent('playing', result);
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
Logger.log('Unpause failed!');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.volume = function (val) {
|
|
||||||
if (val != null) {
|
|
||||||
// TODO
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.playerState.volume;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.setCurrentSrc = function (streamInfo, item, mediaSource, tracks) {
|
|
||||||
|
|
||||||
if (!streamInfo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var val = streamInfo.url;
|
|
||||||
var tIndex = val.indexOf('#t=');
|
|
||||||
var startPosMs = 0;
|
|
||||||
|
|
||||||
if (tIndex != -1) {
|
|
||||||
startPosMs = val.substring(tIndex + 3);
|
|
||||||
startPosMs = parseFloat(startPosMs) * 1000;
|
|
||||||
val = val.split('#')[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.type == 'audio') {
|
|
||||||
|
|
||||||
//AndroidVlcPlayer.playAudioVlc(val, JSON.stringify(item), JSON.stringify(mediaSource), options.poster);
|
|
||||||
var artist = item.ArtistItems && item.ArtistItems.length ? item.ArtistItems[0].Name : null;
|
|
||||||
|
|
||||||
var metadata = {};
|
|
||||||
|
|
||||||
if (item.Name) {
|
|
||||||
metadata.title = item.Name;
|
|
||||||
}
|
|
||||||
if (artist) {
|
|
||||||
metadata.artist = artist;
|
|
||||||
}
|
|
||||||
if (item.Overview) {
|
|
||||||
metadata.description = item.Overview;
|
|
||||||
}
|
|
||||||
if (options.poster) {
|
|
||||||
metadata.image = {
|
|
||||||
url: options.poster
|
|
||||||
};
|
|
||||||
metadata.imageThumbnail = {
|
|
||||||
url: options.poster
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
window.audioplayer.playstream(successHandler, function () {
|
|
||||||
|
|
||||||
Logger.log('playstream failed!');
|
|
||||||
//onError();
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ios: val
|
|
||||||
},
|
|
||||||
// metadata used for iOS lock screen, Android 'Now Playing' notification
|
|
||||||
metadata
|
|
||||||
);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioRenderer.Current.playerState.currentSrc = val;
|
|
||||||
reportEvent('playing', {});
|
|
||||||
};
|
|
||||||
|
|
||||||
self.currentSrc = function () {
|
|
||||||
return self.playerState.currentSrc;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.paused = function () {
|
|
||||||
|
|
||||||
return self.playerState.paused;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.cleanup = function (destroyRenderer) {
|
|
||||||
|
|
||||||
self.playerState = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
function reportEvent(eventName, result) {
|
|
||||||
|
|
||||||
var duration = result.duration || 0;
|
|
||||||
var position = result.progress || 0;
|
|
||||||
|
|
||||||
var state = AudioRenderer.Current.playerState;
|
|
||||||
|
|
||||||
state.duration = duration;
|
|
||||||
state.currentTime = position;
|
|
||||||
state.paused = result.state == 3 || eventName == 'paused';
|
|
||||||
state.volume = 0;
|
|
||||||
|
|
||||||
if (eventName == 'positionchange') {
|
|
||||||
onTimeUpdate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.log('eventName: ' + eventName + '. position: ' + position);
|
|
||||||
|
|
||||||
if (eventName == 'playbackstop') {
|
|
||||||
onEnded();
|
|
||||||
}
|
|
||||||
else if (eventName == 'paused') {
|
|
||||||
onPause();
|
|
||||||
}
|
|
||||||
else if (eventName == 'unpaused') {
|
|
||||||
onPlaying();
|
|
||||||
}
|
|
||||||
else if (eventName == 'playing') {
|
|
||||||
onPlaying();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function successHandler(result) {
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.type === 'next') {
|
|
||||||
console.log('skip to next audio track'); // typically fired by remote control/lock screen controls
|
|
||||||
MediaPlayer.nextTrack();
|
|
||||||
} else if (result.type === 'previous') {
|
|
||||||
console.log('skip to previous track'); // typically fired by remote/control/lock screen controls
|
|
||||||
MediaPlayer.previousTrack();
|
|
||||||
}
|
|
||||||
else if (result.state == 4 || result.state == 6) {
|
|
||||||
reportEvent('playbackstop', result);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
var eventName = 'positionchange';
|
|
||||||
reportEvent(eventName, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function errorHandler() {
|
|
||||||
onError();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.init = function () {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
window.audioplayer.configure(successHandler, errorHandler);
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
deferred.resolve();
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
window.AudioRenderer = function (options) {
|
|
||||||
options = options || {};
|
|
||||||
options.type = 'audio';
|
|
||||||
|
|
||||||
return new vlcRenderer(options);
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
929
dashboard-ui/cordova/localassetmanager.js
vendored
929
dashboard-ui/cordova/localassetmanager.js
vendored
|
@ -1,929 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function getLocalMediaSource(serverId, itemId) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
// android
|
|
||||||
if (window.ApiClientBridge) {
|
|
||||||
var json = ApiClientBridge.getLocalMediaSource(serverId, itemId);
|
|
||||||
|
|
||||||
if (json) {
|
|
||||||
deferred.resolveWith(null, [JSON.parse(json)]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
deferred.resolveWith(null, [null]);
|
|
||||||
}
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
getLocalItem(itemId, serverId).then(function (localItem) {
|
|
||||||
|
|
||||||
if (localItem && localItem.Item.MediaSources.length) {
|
|
||||||
|
|
||||||
var mediaSource = localItem.Item.MediaSources[0];
|
|
||||||
|
|
||||||
fileExists(mediaSource.Path).then(function (exists) {
|
|
||||||
|
|
||||||
if (exists) {
|
|
||||||
deferred.resolveWith(null, [mediaSource]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
deferred.resolveWith(null, [null]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}, getOnFail(deferred));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [null]);
|
|
||||||
|
|
||||||
}, getOnFail(deferred));
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCameraPhotos() {
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
if (window.CameraRoll) {
|
|
||||||
|
|
||||||
var photos = [];
|
|
||||||
|
|
||||||
CameraRoll.getPhotos(function (result) {
|
|
||||||
photos.push(result);
|
|
||||||
});
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
|
|
||||||
// clone the array in case the callback is still getting called
|
|
||||||
Logger.log('Found ' + photos.length + ' in camera roll');
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [photos]);
|
|
||||||
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
deferred.resolveWith(null, [[]]);
|
|
||||||
}
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
var offlineUserDatabase;
|
|
||||||
function getOfflineUserdb(callback) {
|
|
||||||
|
|
||||||
if (offlineUserDatabase) {
|
|
||||||
callback(offlineUserDatabase);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create/open database
|
|
||||||
offlineUserDatabase = window.sqlitePlugin.openDatabase({ name: "offlineusers.db" });
|
|
||||||
|
|
||||||
offlineUserDatabase.transaction(function (tx) {
|
|
||||||
|
|
||||||
tx.executeSql('CREATE TABLE IF NOT EXISTS users (id text primary key, data text)');
|
|
||||||
tx.executeSql('create index if not exists idx_users on users(id)');
|
|
||||||
|
|
||||||
callback(offlineUserDatabase);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveOfflineUser(user) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
getOfflineUserdb(function (db) {
|
|
||||||
|
|
||||||
db.transaction(function (tx) {
|
|
||||||
|
|
||||||
tx.executeSql("REPLACE INTO offlineusers (id, data) VALUES (?,?)", [user.Id, JSON.stringify(user)], function (tx, res) {
|
|
||||||
|
|
||||||
deferred.resolve();
|
|
||||||
}, function (e) {
|
|
||||||
deferred.reject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteOfflineUser(id) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
getOfflineUserdb(function (db) {
|
|
||||||
|
|
||||||
db.transaction(function (tx) {
|
|
||||||
|
|
||||||
tx.executeSql("DELETE from offlineusers where id=?", [user.Id], function (tx, res) {
|
|
||||||
|
|
||||||
deferred.resolve();
|
|
||||||
}, function (e) {
|
|
||||||
deferred.reject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
var offlineActionsDatabase;
|
|
||||||
function getOfflineActionsDb(callback) {
|
|
||||||
|
|
||||||
if (offlineActionsDatabase) {
|
|
||||||
callback(offlineActionsDatabase);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create/open database
|
|
||||||
offlineActionsDatabase = window.sqlitePlugin.openDatabase({ name: "offlineactions.db" });
|
|
||||||
|
|
||||||
offlineActionsDatabase.transaction(function (tx) {
|
|
||||||
|
|
||||||
tx.executeSql('CREATE TABLE IF NOT EXISTS offlineactions (Id text primary key, ServerId text not null, Json text not null)');
|
|
||||||
tx.executeSql('create index if not exists idx_offlineactions on offlineactions(id)');
|
|
||||||
|
|
||||||
callback(offlineActionsDatabase);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOfflineActions(serverId) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
getOfflineActionsDb(function (db) {
|
|
||||||
|
|
||||||
db.transaction(function (tx) {
|
|
||||||
|
|
||||||
tx.executeSql("SELECT Json from offlineactions where ServerId=?", [serverId], function (tx, res) {
|
|
||||||
|
|
||||||
var actions = [];
|
|
||||||
for (var i = 0, length = res.rows.length; i < length; i++) {
|
|
||||||
actions.push(JSON.parse(res.rows.item(i).Json));
|
|
||||||
}
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [actions]);
|
|
||||||
|
|
||||||
}, function (e) {
|
|
||||||
deferred.reject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteOfflineActions(actions) {
|
|
||||||
|
|
||||||
var ids = actions.map(function (a) { return "'" + a.Id + "'"; }).join(',');
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
getOfflineActionsDb(function (db) {
|
|
||||||
|
|
||||||
db.transaction(function (tx) {
|
|
||||||
|
|
||||||
tx.executeSql("DELETE from offlineactions where Id in (" + ids + ")", [], function (tx, res) {
|
|
||||||
|
|
||||||
deferred.resolve();
|
|
||||||
|
|
||||||
}, function (e) {
|
|
||||||
deferred.reject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
var offlineItemsDatabase;
|
|
||||||
function getOfflineItemsDb(callback) {
|
|
||||||
|
|
||||||
if (offlineItemsDatabase) {
|
|
||||||
callback(offlineItemsDatabase);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create/open database
|
|
||||||
offlineItemsDatabase = window.sqlitePlugin.openDatabase({ name: "offlineitems.db" });
|
|
||||||
|
|
||||||
offlineItemsDatabase.transaction(function (tx) {
|
|
||||||
|
|
||||||
tx.executeSql('CREATE TABLE IF NOT EXISTS Items ( Id text primary key, ItemId text not null, ItemType text not null, MediaType text, ServerId text not null, LocalPath text not null, UserIdsWithAccess text, AlbumId text, AlbumName text, SeriesId text, SeriesName text, Json text not null)');
|
|
||||||
tx.executeSql('create index if not exists idx_items on Items(Id)');
|
|
||||||
|
|
||||||
tx.executeSql('CREATE TABLE IF NOT EXISTS AlbumArtists ( Id text not null, Name text not null, ItemId text not null)');
|
|
||||||
tx.executeSql('create index if not exists idx_AlbumArtists on AlbumArtists(id)');
|
|
||||||
|
|
||||||
callback(offlineItemsDatabase);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getServerItemIds(serverId) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
getOfflineItemsDb(function (db) {
|
|
||||||
|
|
||||||
db.transaction(function (tx) {
|
|
||||||
|
|
||||||
tx.executeSql("SELECT ItemId from Items where ServerId=?", [serverId], function (tx, res) {
|
|
||||||
|
|
||||||
var itemIds = [];
|
|
||||||
for (var i = 0, length = res.rows.length; i < length; i++) {
|
|
||||||
itemIds.push(res.rows.item(i).ItemId);
|
|
||||||
}
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [itemIds]);
|
|
||||||
|
|
||||||
}, function (e) {
|
|
||||||
deferred.reject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getUserIdsWithAccess(itemId, serverId) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
getOfflineItemsDb(function (db) {
|
|
||||||
|
|
||||||
db.transaction(function (tx) {
|
|
||||||
|
|
||||||
tx.executeSql("SELECT UserIdsWithAccess from Items where ItemId=? AND ServerId=?", [itemId, serverId], function (tx, res) {
|
|
||||||
|
|
||||||
var itemIds = [];
|
|
||||||
|
|
||||||
if (res.rows.length) {
|
|
||||||
itemIds = res.rows.item(0).UserIdsWithAccess;
|
|
||||||
itemIds = itemIds ? itemIds.split(',') : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [itemIds]);
|
|
||||||
|
|
||||||
}, function (e) {
|
|
||||||
deferred.reject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveUserIdsWithAccess(itemId, serverId, userIds) {
|
|
||||||
|
|
||||||
Logger.log('saveUserIdsWithAccess');
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
getOfflineItemsDb(function (db) {
|
|
||||||
|
|
||||||
db.transaction(function (tx) {
|
|
||||||
|
|
||||||
var values = [userIds.join(','), itemId, serverId];
|
|
||||||
|
|
||||||
tx.executeSql("Update Items set UserIdsWithAccess=? where ItemId=? and ServerId=?", values);
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLocalItem(itemId, serverId) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
getOfflineItemsDb(function (db) {
|
|
||||||
|
|
||||||
db.transaction(function (tx) {
|
|
||||||
|
|
||||||
tx.executeSql("SELECT Json from Items where itemId=? AND serverId=?", [itemId, serverId], function (tx, res) {
|
|
||||||
|
|
||||||
if (res.rows.length) {
|
|
||||||
|
|
||||||
var localItem = JSON.parse(res.rows.item(0).Json);
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [localItem]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
deferred.resolveWith(null, [null]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}, function (e) {
|
|
||||||
deferred.reject();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function addOrUpdateLocalItem(item) {
|
|
||||||
|
|
||||||
Logger.log('addOrUpdateLocalItem');
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
getOfflineItemsDb(function (db) {
|
|
||||||
|
|
||||||
db.transaction(function (tx) {
|
|
||||||
|
|
||||||
var libraryItem = item.Item || {};
|
|
||||||
|
|
||||||
var values = [item.Id, item.ItemId, libraryItem.Type, libraryItem.MediaType, item.ServerId, item.LocalPath, (item.UserIdsWithAccess || []).join(','), libraryItem.AlbumId, libraryItem.AlbumName, libraryItem.SeriesId, libraryItem.SeriesName, JSON.stringify(item)];
|
|
||||||
tx.executeSql("REPLACE INTO Items (Id, ItemId, ItemType, MediaType, ServerId, LocalPath, UserIdsWithAccess, AlbumId, AlbumName, SeriesId, SeriesName, Json) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", values);
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeLocalItem(itemId, serverId) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
getLocalItem(itemId, serverId).then(function (item) {
|
|
||||||
|
|
||||||
getOfflineItemsDb(function (db) {
|
|
||||||
|
|
||||||
db.transaction(function (tx) {
|
|
||||||
|
|
||||||
tx.executeSql("DELETE from Items where itemId=? AND serverId=?", [itemId, serverId]);
|
|
||||||
|
|
||||||
var files = item.AdditionalFiles || [];
|
|
||||||
files.push(item.LocalPath);
|
|
||||||
|
|
||||||
deleteFiles(files).then(function () {
|
|
||||||
|
|
||||||
deferred.resolve();
|
|
||||||
|
|
||||||
}, getOnFail(deferred));
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
}, getOnFail(deferred));
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteFiles(files) {
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
deleteNextFile(files, 0, deferred);
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteNextFile(files, index, deferred) {
|
|
||||||
|
|
||||||
if (index >= files.length) {
|
|
||||||
deferred.resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteFile(file).then(function () {
|
|
||||||
deleteNextFile(files, index + 1, deferred);
|
|
||||||
}, function () {
|
|
||||||
deleteNextFile(files, index + 1, deferred);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteFile(path) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
Logger.log('Deleting ' + path);
|
|
||||||
resolveFile(path, null, function (fileEntry) {
|
|
||||||
|
|
||||||
fileEntry.remove(function () {
|
|
||||||
Logger.log('Deleted ' + path);
|
|
||||||
deferred.resolve();
|
|
||||||
}, function () {
|
|
||||||
|
|
||||||
Logger.log('Error deleting ' + path);
|
|
||||||
deferred.reject();
|
|
||||||
});
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
|
|
||||||
Logger.log('Skipping deletion because file does not exist: ' + path);
|
|
||||||
deferred.resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveFile(path, options, success, fail) {
|
|
||||||
|
|
||||||
getFileSystem().then(function (fileSystem) {
|
|
||||||
|
|
||||||
fileSystem.root.getFile(path, options || { create: false }, success, fail);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createLocalItem(libraryItem, serverInfo, originalFileName) {
|
|
||||||
|
|
||||||
var enableFriendlyPath = false;
|
|
||||||
|
|
||||||
var path = getDirectoryPath(libraryItem, serverInfo, enableFriendlyPath);
|
|
||||||
|
|
||||||
if (enableFriendlyPath) {
|
|
||||||
path.push(getLocalFileName(libraryItem, originalFileName));
|
|
||||||
} else {
|
|
||||||
var nameParts = originalFileName.split('.');
|
|
||||||
var ext = nameParts.length > 1 ? ('.' + nameParts[nameParts.length - 1]) : '';
|
|
||||||
path.push('media' + ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
var item = {};
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
var localPath = path.join('/');
|
|
||||||
|
|
||||||
item.LocalPath = localPath;
|
|
||||||
|
|
||||||
for (var i = 0, length = libraryItem.MediaSources.length; i < length; i++) {
|
|
||||||
|
|
||||||
var mediaSource = libraryItem.MediaSources[i];
|
|
||||||
mediaSource.Path = localPath;
|
|
||||||
mediaSource.Protocol = 'File';
|
|
||||||
}
|
|
||||||
|
|
||||||
item.ServerId = serverInfo.Id;
|
|
||||||
item.Item = libraryItem;
|
|
||||||
item.ItemId = libraryItem.Id;
|
|
||||||
item.Id = getLocalId(item.ServerId, item.ItemId);
|
|
||||||
deferred.resolveWith(null, [item]);
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDirectoryPath(item, server, enableFriendlyPath) {
|
|
||||||
|
|
||||||
if (!enableFriendlyPath) {
|
|
||||||
return ['sync', item.Id];
|
|
||||||
}
|
|
||||||
|
|
||||||
var parts = [];
|
|
||||||
parts.push("sync");
|
|
||||||
parts.push(server.Name);
|
|
||||||
|
|
||||||
if (item.Type == "Episode") {
|
|
||||||
parts.push("TV");
|
|
||||||
parts.push(item.SeriesName);
|
|
||||||
|
|
||||||
if (item.SeasonName) {
|
|
||||||
parts.push(item.SeasonName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (item.MediaType == 'Video') {
|
|
||||||
parts.push("Videos");
|
|
||||||
parts.push(item.Name);
|
|
||||||
}
|
|
||||||
else if (item.MediaType == 'Audio') {
|
|
||||||
parts.push("Music");
|
|
||||||
|
|
||||||
if (item.AlbumArtist) {
|
|
||||||
parts.push(item.AlbumArtist);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.Album) {
|
|
||||||
parts.push(item.Album);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (item.MediaType == 'Photo') {
|
|
||||||
parts.push("Photos");
|
|
||||||
|
|
||||||
if (item.Album) {
|
|
||||||
parts.push(item.Album);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts.map(getValidFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLocalFileName(libraryItem, originalFileName) {
|
|
||||||
|
|
||||||
var filename = originalFileName || libraryItem.Name;
|
|
||||||
|
|
||||||
return getValidFileName(filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getValidFileName(filename) {
|
|
||||||
// TODO
|
|
||||||
return filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadFile(url, localPath, enableBackground, enableNewDownloads) {
|
|
||||||
|
|
||||||
if (!enableBackground) {
|
|
||||||
return downloadWithFileTransfer(url, localPath, enableBackground);
|
|
||||||
}
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
if (localStorage.getItem('sync-' + url) == '1') {
|
|
||||||
Logger.log('file was downloaded previously');
|
|
||||||
deferred.resolveWith(null, [localPath, false]);
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableNewDownloads === false) {
|
|
||||||
deferred.resolveWith(null, [localPath, true]);
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.log('downloading: ' + url + ' to ' + localPath);
|
|
||||||
|
|
||||||
createDirectory(getParentDirectoryPath(localPath)).then(function () {
|
|
||||||
|
|
||||||
resolveFile(localPath, { create: true }, function (targetFile) {
|
|
||||||
|
|
||||||
var downloader = new BackgroundTransfer.BackgroundDownloader();
|
|
||||||
// Create a new download operation.
|
|
||||||
var download = downloader.createDownload(url, targetFile);
|
|
||||||
|
|
||||||
var isResolved = false;
|
|
||||||
|
|
||||||
// Give it a short period of time to see if it has already been completed before. Either way, move on and resolve it.
|
|
||||||
var timeoutHandle = setTimeout(function () {
|
|
||||||
|
|
||||||
isResolved = true;
|
|
||||||
// true indicates that it's queued
|
|
||||||
deferred.resolveWith(null, [localPath, true]);
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
// Start the download and persist the promise to be able to cancel the download.
|
|
||||||
download.startAsync().then(function () {
|
|
||||||
|
|
||||||
clearTimeout(timeoutHandle);
|
|
||||||
// on success
|
|
||||||
Logger.log('Downloaded local url: ' + localPath);
|
|
||||||
|
|
||||||
// If we've already moved on, set this property so that we'll see it later
|
|
||||||
localStorage.setItem('sync-' + url, '1');
|
|
||||||
|
|
||||||
if (!isResolved) {
|
|
||||||
// true indicates that it's queued
|
|
||||||
deferred.resolveWith(null, [localPath, false]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
|
|
||||||
clearTimeout(timeoutHandle);
|
|
||||||
|
|
||||||
// on error
|
|
||||||
Logger.log('Error downloading url: ' + url);
|
|
||||||
|
|
||||||
if (!isResolved) {
|
|
||||||
deferred.reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
}, function (value) {
|
|
||||||
|
|
||||||
// on progress
|
|
||||||
//Logger.log('download progress: ' + value);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}, getOnFail(deferred));
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
var activeDownloads = [];
|
|
||||||
|
|
||||||
function removeDownload(key) {
|
|
||||||
|
|
||||||
for (var i = 0, length = activeDownloads.length; i < length; i++) {
|
|
||||||
if (key == activeDownloads[i]) {
|
|
||||||
activeDownloads[i] = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadWithFileTransfer(url, localPath, enableBackground) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
if (localStorage.getItem('sync-' + url) == '1') {
|
|
||||||
Logger.log('file was downloaded previously');
|
|
||||||
deferred.resolveWith(null, [localPath]);
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
var downloadKey = url + localPath;
|
|
||||||
if (activeDownloads.indexOf(downloadKey) != -1) {
|
|
||||||
deferred.resolveWith(null, [localPath, true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.log('downloading: ' + url + ' to ' + localPath);
|
|
||||||
|
|
||||||
createDirectory(getParentDirectoryPath(localPath)).then(function () {
|
|
||||||
|
|
||||||
resolveFile(localPath, { create: true }, function (targetFile) {
|
|
||||||
|
|
||||||
var isQueued = enableBackground;
|
|
||||||
var isError = false;
|
|
||||||
|
|
||||||
var ft = new FileTransfer();
|
|
||||||
activeDownloads.push(downloadKey);
|
|
||||||
ft.download(url, targetFile.toURL(), function (entry) {
|
|
||||||
|
|
||||||
removeDownload(downloadKey);
|
|
||||||
|
|
||||||
if (enableBackground) {
|
|
||||||
Logger.log('Downloaded local url: ' + localPath);
|
|
||||||
localStorage.setItem('sync-' + url, '1');
|
|
||||||
isQueued = false;
|
|
||||||
} else {
|
|
||||||
deferred.resolveWith(null, [localPath]);
|
|
||||||
}
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
|
|
||||||
removeDownload(downloadKey);
|
|
||||||
|
|
||||||
Logger.log('Error downloading url: ' + url);
|
|
||||||
|
|
||||||
if (enableBackground) {
|
|
||||||
isError = true;
|
|
||||||
} else {
|
|
||||||
deferred.reject();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (enableBackground) {
|
|
||||||
// Give it a short period of time to see if it has already been completed before. Either way, move on and resolve it.
|
|
||||||
setTimeout(function () {
|
|
||||||
|
|
||||||
if (isError) {
|
|
||||||
deferred.reject();
|
|
||||||
} else {
|
|
||||||
// true indicates that it's queued
|
|
||||||
deferred.resolveWith(null, [localPath, isQueued]);
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
|
|
||||||
Logger.log('getFile failed for ' + localPath);
|
|
||||||
deferred.reject();
|
|
||||||
});
|
|
||||||
|
|
||||||
}, getOnFail(deferred));
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDirectory(path) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
createDirectoryPart(path, 0, deferred);
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDirectoryPart(path, index, deferred) {
|
|
||||||
|
|
||||||
var parts = path.split('/');
|
|
||||||
if (index >= parts.length) {
|
|
||||||
deferred.resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
parts.length = index + 1;
|
|
||||||
var pathToCreate = parts.join('/');
|
|
||||||
|
|
||||||
createDirectoryInternal(pathToCreate).then(function () {
|
|
||||||
|
|
||||||
createDirectoryPart(path, index + 1, deferred);
|
|
||||||
|
|
||||||
}, getOnFail(deferred));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDirectoryInternal(path) {
|
|
||||||
|
|
||||||
Logger.log('creating directory: ' + path);
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
getFileSystem().then(function (fileSystem) {
|
|
||||||
|
|
||||||
fileSystem.root.getDirectory(path, { create: true, exclusive: false }, function (targetFile) {
|
|
||||||
|
|
||||||
Logger.log('createDirectory succeeded');
|
|
||||||
deferred.resolve();
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
|
|
||||||
Logger.log('createDirectory failed');
|
|
||||||
deferred.reject();
|
|
||||||
});
|
|
||||||
|
|
||||||
}, getOnFail(deferred));
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadSubtitles(url, localItem, subtitleStream) {
|
|
||||||
|
|
||||||
var path = item.LocalPath;
|
|
||||||
|
|
||||||
var filename = getSubtitleSaveFileName(item, subtitleStream.Language, subtitleStream.IsForced) + "." + subtitleStream.Codec.toLowerCase();
|
|
||||||
|
|
||||||
var parentPath = getParentDirectoryPath(path);
|
|
||||||
|
|
||||||
path = combinePaths(parentPath, filename);
|
|
||||||
|
|
||||||
return downloadFile(url, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSubtitleSaveFileName(item, language, isForced) {
|
|
||||||
|
|
||||||
var path = item.LocalPath;
|
|
||||||
|
|
||||||
var name = getNameWithoutExtension(path);
|
|
||||||
|
|
||||||
if (language) {
|
|
||||||
name += "." + language.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isForced) {
|
|
||||||
name += ".foreign";
|
|
||||||
}
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNameWithoutExtension(path) {
|
|
||||||
|
|
||||||
var parts = path.split('/');
|
|
||||||
var name = parts[parts.length - 1];
|
|
||||||
|
|
||||||
var index = name.lastIndexOf('.');
|
|
||||||
|
|
||||||
if (index != -1) {
|
|
||||||
name = name.substring(0, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getParentDirectoryPath(path) {
|
|
||||||
|
|
||||||
var parts = path.split('/');
|
|
||||||
parts.length--;
|
|
||||||
|
|
||||||
return parts.join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
function combinePaths(path1, path2) {
|
|
||||||
|
|
||||||
return path1 + path2;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLocalId(serverId, itemId) {
|
|
||||||
return serverId + '_' + itemId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasImage(serverId, itemId, imageTag) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
getImageLocalPath(serverId, itemId, imageTag).then(function (localPath) {
|
|
||||||
|
|
||||||
fileExists(localPath).then(function (exists) {
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [exists]);
|
|
||||||
|
|
||||||
}, getOnFail(deferred));
|
|
||||||
|
|
||||||
}, getOnFail(deferred));
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadImage(url, serverId, itemId, imageTag) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
getImageLocalPath(serverId, itemId, imageTag).then(function (localPath) {
|
|
||||||
|
|
||||||
downloadFile(url, localPath).then(function () {
|
|
||||||
|
|
||||||
deferred.resolve();
|
|
||||||
|
|
||||||
}, getOnFail(deferred));
|
|
||||||
|
|
||||||
}, getOnFail(deferred));
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getImageLocalPath(serverId, itemId, imageTag) {
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
var path = "images/" + serverId + "/" + itemId + "/" + imageTag;
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [path]);
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function fileExists(path) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
if (window.NativeFileSystem) {
|
|
||||||
var exists = NativeFileSystem.fileExists(path);
|
|
||||||
Logger.log('fileExists: ' + exists + ' - path: ' + path);
|
|
||||||
deferred.resolveWith(null, [exists]);
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveFile(path, null, function (fileEntry) {
|
|
||||||
Logger.log('fileExists: true - path: ' + path);
|
|
||||||
deferred.resolveWith(null, [true]);
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
Logger.log('fileExists: false - path: ' + path);
|
|
||||||
deferred.resolveWith(null, [false]);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileSystem;
|
|
||||||
function getFileSystem() {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
if (fileSystem) {
|
|
||||||
deferred.resolveWith(null, [fileSystem]);
|
|
||||||
} else {
|
|
||||||
requestFileSystem(PERSISTENT, 0, function (fs) {
|
|
||||||
fileSystem = fs;
|
|
||||||
deferred.resolveWith(null, [fileSystem]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getOnFail(deferred) {
|
|
||||||
return function () {
|
|
||||||
|
|
||||||
deferred.reject();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function translateFilePath(path) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
if (browserInfo.android) {
|
|
||||||
deferred.resolveWith(null, ['file://' + path]);
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveFile(path, null, function (fileEntry) {
|
|
||||||
Logger.log('translateFilePath fileExists: true - path: ' + path);
|
|
||||||
Logger.log('translateFilePath resolving with: ' + fileEntry.toURL());
|
|
||||||
deferred.resolveWith(null, [fileEntry.toURL()]);
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
Logger.log('translateFilePath fileExists: false - path: ' + path);
|
|
||||||
deferred.resolveWith(null, [path]);
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.LocalAssetManager = {
|
|
||||||
getLocalMediaSource: getLocalMediaSource,
|
|
||||||
saveOfflineUser: saveOfflineUser,
|
|
||||||
deleteOfflineUser: deleteOfflineUser,
|
|
||||||
getCameraPhotos: getCameraPhotos,
|
|
||||||
getOfflineActions: getOfflineActions,
|
|
||||||
deleteOfflineActions: deleteOfflineActions,
|
|
||||||
getServerItemIds: getServerItemIds,
|
|
||||||
removeLocalItem: removeLocalItem,
|
|
||||||
getLocalItem: getLocalItem,
|
|
||||||
addOrUpdateLocalItem: addOrUpdateLocalItem,
|
|
||||||
createLocalItem: createLocalItem,
|
|
||||||
downloadFile: downloadFile,
|
|
||||||
downloadSubtitles: downloadSubtitles,
|
|
||||||
hasImage: hasImage,
|
|
||||||
downloadImage: downloadImage,
|
|
||||||
fileExists: fileExists,
|
|
||||||
translateFilePath: translateFilePath,
|
|
||||||
getUserIdsWithAccess: getUserIdsWithAccess,
|
|
||||||
saveUserIdsWithAccess: saveUserIdsWithAccess
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
17
dashboard-ui/cordova/prompt.js
vendored
17
dashboard-ui/cordova/prompt.js
vendored
|
@ -1,17 +0,0 @@
|
||||||
define([], function () {
|
|
||||||
return function (options) {
|
|
||||||
|
|
||||||
var callback = function (result) {
|
|
||||||
|
|
||||||
if (result.buttonIndex == 1) {
|
|
||||||
options.callback(result.input1);
|
|
||||||
} else {
|
|
||||||
options.callback(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var buttonLabels = [Globalize.translate('ButtonOk'), Globalize.translate('ButtonCancel')];
|
|
||||||
|
|
||||||
navigator.notification.prompt(options.text, callback, options.title, buttonLabels, options.defaultText || '');
|
|
||||||
};
|
|
||||||
});
|
|
439
dashboard-ui/cordova/registrationservices.js
vendored
439
dashboard-ui/cordova/registrationservices.js
vendored
|
@ -1,439 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function getRegistrationInfo(feature) {
|
|
||||||
|
|
||||||
return ConnectionManager.getRegistrationInfo(feature, ApiClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateFeature(feature, deferred) {
|
|
||||||
|
|
||||||
var unlockableProduct = IapManager.getProductInfo(feature) || {};
|
|
||||||
|
|
||||||
if (unlockableProduct.owned) {
|
|
||||||
deferred.resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var unlockableProductInfo = IapManager.isPurchaseAvailable(feature) ? {
|
|
||||||
enableAppUnlock: true,
|
|
||||||
id: unlockableProduct.id,
|
|
||||||
price: unlockableProduct.price,
|
|
||||||
feature: feature
|
|
||||||
|
|
||||||
} : null;
|
|
||||||
|
|
||||||
var prefix = browserInfo.android ? 'android' : 'ios';
|
|
||||||
|
|
||||||
IapManager.isUnlockedOverride(feature).then(function (isUnlocked) {
|
|
||||||
|
|
||||||
if (isUnlocked) {
|
|
||||||
deferred.resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRegistrationInfoResponse(registrationInfo) {
|
|
||||||
|
|
||||||
if (registrationInfo.IsRegistered) {
|
|
||||||
deferred.resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IapManager.getSubscriptionOptions().then(function (subscriptionOptions) {
|
|
||||||
|
|
||||||
if (subscriptionOptions.filter(function (p) {
|
|
||||||
return p.owned;
|
|
||||||
}).length > 0) {
|
|
||||||
deferred.resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dialogOptions = {
|
|
||||||
title: Globalize.translate('HeaderUnlockApp'),
|
|
||||||
enablePlayMinute: feature == 'playback',
|
|
||||||
feature: feature
|
|
||||||
};
|
|
||||||
|
|
||||||
showInAppPurchaseInfo(subscriptionOptions, unlockableProductInfo, dialogOptions, deferred);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get supporter status
|
|
||||||
getRegistrationInfo(prefix + 'appunlock').then(onRegistrationInfoResponse, function () {
|
|
||||||
onRegistrationInfoResponse({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancelInAppPurchase() {
|
|
||||||
|
|
||||||
var elem = document.querySelector('.inAppPurchaseOverlay');
|
|
||||||
if (elem) {
|
|
||||||
PaperDialogHelper.close(elem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isCancelled = true;
|
|
||||||
var currentDisplayingProductInfos = [];
|
|
||||||
var currentDisplayingDeferred = null;
|
|
||||||
|
|
||||||
function clearCurrentDisplayingInfo() {
|
|
||||||
currentDisplayingProductInfos = [];
|
|
||||||
currentDisplayingDeferred = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showInAppPurchaseElement(subscriptionOptions, unlockableProductInfo, dialogOptions, deferred) {
|
|
||||||
|
|
||||||
cancelInAppPurchase();
|
|
||||||
|
|
||||||
// clone
|
|
||||||
currentDisplayingProductInfos = subscriptionOptions.slice(0);
|
|
||||||
|
|
||||||
if (unlockableProductInfo) {
|
|
||||||
currentDisplayingProductInfos.push(unlockableProductInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
var dlg = PaperDialogHelper.createDialog();
|
|
||||||
|
|
||||||
var html = '';
|
|
||||||
html += '<h2 class="dialogHeader">';
|
|
||||||
html += '<paper-fab icon="arrow-back" mini class="btnCloseDialog"></paper-fab>';
|
|
||||||
html += '<div style="display:inline-block;margin-left:.6em;vertical-align:middle;">' + dialogOptions.title + '</div>';
|
|
||||||
html += '</h2>';
|
|
||||||
|
|
||||||
html += '<div class="editorContent">';
|
|
||||||
|
|
||||||
html += '<form style="max-width: 800px;margin:auto;">';
|
|
||||||
html += '<p style="margin:2em 0;">';
|
|
||||||
|
|
||||||
if (unlockableProductInfo) {
|
|
||||||
html += Globalize.translate('MessageUnlockAppWithPurchaseOrSupporter');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
html += Globalize.translate('MessageUnlockAppWithSupporter');
|
|
||||||
}
|
|
||||||
html += '</p>';
|
|
||||||
|
|
||||||
html += '<p style="margin:2em 0;">';
|
|
||||||
html += Globalize.translate('MessageToValidateSupporter');
|
|
||||||
html += '</p>';
|
|
||||||
|
|
||||||
var hasProduct = false;
|
|
||||||
|
|
||||||
if (unlockableProductInfo) {
|
|
||||||
|
|
||||||
hasProduct = true;
|
|
||||||
var unlockText = Globalize.translate('ButtonUnlockWithPurchase');
|
|
||||||
if (unlockableProductInfo.price) {
|
|
||||||
unlockText = Globalize.translate('ButtonUnlockPrice', unlockableProductInfo.price);
|
|
||||||
}
|
|
||||||
html += '<p>';
|
|
||||||
html += '<paper-button raised class="secondary block btnPurchase" data-feature="' + unlockableProductInfo.feature + '"><iron-icon icon="check"></iron-icon><span>' + unlockText + '</span></paper-button>';
|
|
||||||
html += '</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0, length = subscriptionOptions.length; i < length; i++) {
|
|
||||||
|
|
||||||
hasProduct = true;
|
|
||||||
html += '<p>';
|
|
||||||
html += '<paper-button raised class="submit block btnPurchase" data-email="true" data-feature="' + subscriptionOptions[i].feature + '"><iron-icon icon="check"></iron-icon><span>';
|
|
||||||
html += subscriptionOptions[i].buttonText;
|
|
||||||
html += '</span></paper-button>';
|
|
||||||
html += '</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasProduct && IapManager.restorePurchase) {
|
|
||||||
html += '<p>';
|
|
||||||
html += '<paper-button raised class="secondary block btnRestorePurchase" style="background-color: #673AB7;"><iron-icon icon="check"></iron-icon><span>' + Globalize.translate('ButtonRestorePreviousPurchase') + '</span></paper-button>';
|
|
||||||
html += '</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subscriptionOptions.length) {
|
|
||||||
html += '<br/>';
|
|
||||||
html += '<h1>' + Globalize.translate('HeaderBenefitsEmbyPremiere') + '</h1>';
|
|
||||||
|
|
||||||
html += '<div class="paperList" style="margin-bottom:1em;">';
|
|
||||||
html += getSubscriptionBenefits().map(getSubscriptionBenefitHtml).join('');
|
|
||||||
html += '</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dialogOptions.enablePlayMinute) {
|
|
||||||
html += '<p>';
|
|
||||||
html += '<paper-button raised class="secondary block btnCloseDialog subdued"><iron-icon icon="play-arrow"></iron-icon><span>' + Globalize.translate('ButtonPlayOneMinute') + '</span></paper-button>';
|
|
||||||
html += '</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '</form>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
dlg.innerHTML = html;
|
|
||||||
document.body.appendChild(dlg);
|
|
||||||
|
|
||||||
initInAppPurchaseElementEvents(dlg, dialogOptions.feature, deferred);
|
|
||||||
|
|
||||||
PaperDialogHelper.openWithHash(dlg, 'iap');
|
|
||||||
|
|
||||||
$('.btnCloseDialog', dlg).on('click', function () {
|
|
||||||
|
|
||||||
PaperDialogHelper.close(dlg);
|
|
||||||
});
|
|
||||||
|
|
||||||
$(dlg).on('iron-overlay-closed', function () {
|
|
||||||
|
|
||||||
if (window.TabBar) {
|
|
||||||
TabBar.show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
dlg.classList.add('inAppPurchaseOverlay');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSubscriptionBenefits() {
|
|
||||||
|
|
||||||
var list = [];
|
|
||||||
|
|
||||||
list.push({
|
|
||||||
name: Globalize.translate('CoverArt'),
|
|
||||||
icon: 'photo',
|
|
||||||
text: Globalize.translate('CoverArtFeatureDescription')
|
|
||||||
});
|
|
||||||
|
|
||||||
list.push({
|
|
||||||
name: Globalize.translate('HeaderFreeApps'),
|
|
||||||
icon: 'check',
|
|
||||||
text: Globalize.translate('FreeAppsFeatureDescription')
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Dashboard.capabilities().SupportsSync) {
|
|
||||||
list.push({
|
|
||||||
name: Globalize.translate('HeaderMobileSync'),
|
|
||||||
icon: 'sync',
|
|
||||||
text: Globalize.translate('MobileSyncFeatureDescription')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (AppInfo.isNativeApp) {
|
|
||||||
list.push({
|
|
||||||
name: Globalize.translate('HeaderCloudSync'),
|
|
||||||
icon: 'sync',
|
|
||||||
text: Globalize.translate('CloudSyncFeatureDescription')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
list.push({
|
|
||||||
name: Globalize.translate('HeaderCinemaMode'),
|
|
||||||
icon: 'movie',
|
|
||||||
text: Globalize.translate('CinemaModeFeatureDescription')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSubscriptionBenefitHtml(item) {
|
|
||||||
|
|
||||||
var html = '';
|
|
||||||
html += '<paper-icon-item>';
|
|
||||||
|
|
||||||
html += '<paper-fab mini style="background-color:#52B54B;" icon="' + item.icon + '" item-icon></paper-fab>';
|
|
||||||
|
|
||||||
html += '<paper-item-body three-line>';
|
|
||||||
html += '<a class="clearLink" href="https://emby.media/premiere" target="_blank">';
|
|
||||||
|
|
||||||
html += '<div>';
|
|
||||||
html += item.name;
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
html += '<div secondary style="white-space:normal;">';
|
|
||||||
html += item.text;
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
html += '</a>';
|
|
||||||
html += '</paper-item-body>';
|
|
||||||
|
|
||||||
html += '</paper-icon-item>';
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initInAppPurchaseElementEvents(elem, feature, deferred) {
|
|
||||||
|
|
||||||
isCancelled = true;
|
|
||||||
|
|
||||||
$('.btnPurchase', elem).on('click', function () {
|
|
||||||
|
|
||||||
isCancelled = false;
|
|
||||||
|
|
||||||
if (this.getAttribute('data-email') == 'true') {
|
|
||||||
acquireEmail(this.getAttribute('data-feature'));
|
|
||||||
} else {
|
|
||||||
IapManager.beginPurchase(this.getAttribute('data-feature'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.btnRestorePurchase', elem).on('click', function () {
|
|
||||||
|
|
||||||
isCancelled = false;
|
|
||||||
IapManager.restorePurchase();
|
|
||||||
});
|
|
||||||
|
|
||||||
$(elem).on('iron-overlay-closed', function () {
|
|
||||||
|
|
||||||
clearCurrentDisplayingInfo();
|
|
||||||
|
|
||||||
var overlay = this;
|
|
||||||
|
|
||||||
if (isCancelled) {
|
|
||||||
|
|
||||||
if (feature == 'playback') {
|
|
||||||
Dashboard.alert({
|
|
||||||
message: Globalize.translate('ThankYouForTryingEnjoyOneMinute'),
|
|
||||||
title: Globalize.translate('HeaderTryPlayback'),
|
|
||||||
callback: function () {
|
|
||||||
deferred.reject();
|
|
||||||
$(overlay).remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
deferred.reject();
|
|
||||||
$(overlay).remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$(this).remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showInAppPurchaseInfo(subscriptionOptions, unlockableProductInfo, dialogOptions, deferred) {
|
|
||||||
|
|
||||||
require(['components/paperdialoghelper'], function () {
|
|
||||||
|
|
||||||
if (window.TabBar) {
|
|
||||||
TabBar.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
showInAppPurchaseElement(subscriptionOptions, unlockableProductInfo, dialogOptions, deferred);
|
|
||||||
|
|
||||||
currentDisplayingDeferred = deferred;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function acquireEmail(feature) {
|
|
||||||
|
|
||||||
if (ConnectionManager.isLoggedIntoConnect()) {
|
|
||||||
|
|
||||||
var connectUser = ConnectionManager.connectUser();
|
|
||||||
|
|
||||||
if (connectUser && connectUser.Email) {
|
|
||||||
IapManager.beginPurchase(feature, connectUser.Email);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
promptForEmail(feature);
|
|
||||||
}
|
|
||||||
|
|
||||||
function promptForEmail(feature) {
|
|
||||||
|
|
||||||
require(['prompt'], function (prompt) {
|
|
||||||
|
|
||||||
prompt({
|
|
||||||
text: Globalize.translate('TextPleaseEnterYourEmailAddressForSubscription'),
|
|
||||||
title: Globalize.translate('HeaderEmailAddress'),
|
|
||||||
callback: function (email) {
|
|
||||||
|
|
||||||
if (email) {
|
|
||||||
IapManager.beginPurchase(feature, email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onProductUpdated(e, product) {
|
|
||||||
|
|
||||||
if (product.owned) {
|
|
||||||
|
|
||||||
var deferred = currentDisplayingDeferred;
|
|
||||||
|
|
||||||
if (deferred && currentDisplayingProductInfos.filter(function (p) {
|
|
||||||
|
|
||||||
return product.id == p.id;
|
|
||||||
|
|
||||||
}).length) {
|
|
||||||
|
|
||||||
isCancelled = false;
|
|
||||||
|
|
||||||
cancelInAppPurchase();
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateSync(deferred) {
|
|
||||||
|
|
||||||
Dashboard.getPluginSecurityInfo().then(function (pluginSecurityInfo) {
|
|
||||||
|
|
||||||
if (pluginSecurityInfo.IsMBSupporter) {
|
|
||||||
deferred.resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onRegistrationInfoResponse(registrationInfo) {
|
|
||||||
if (registrationInfo.IsRegistered) {
|
|
||||||
deferred.resolve();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IapManager.getSubscriptionOptions().then(function (subscriptionOptions) {
|
|
||||||
|
|
||||||
var dialogOptions = {
|
|
||||||
title: Globalize.translate('HeaderUnlockSync'),
|
|
||||||
feature: 'sync'
|
|
||||||
};
|
|
||||||
|
|
||||||
showInAppPurchaseInfo(subscriptionOptions, null, dialogOptions, deferred);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get supporter status
|
|
||||||
getRegistrationInfo('Sync').then(onRegistrationInfoResponse, function () {
|
|
||||||
onRegistrationInfoResponse({});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.RegistrationServices = {
|
|
||||||
|
|
||||||
renderPluginInfo: function (page, pkg, pluginSecurityInfo) {
|
|
||||||
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
validateFeature: function (name) {
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
if (name == 'playback') {
|
|
||||||
validateFeature(name, deferred);
|
|
||||||
} else if (name == 'livetv') {
|
|
||||||
validateFeature(name, deferred);
|
|
||||||
} else if (name == 'sync') {
|
|
||||||
validateSync(deferred);
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function onIapManagerLoaded() {
|
|
||||||
Events.on(IapManager, 'productupdated', onProductUpdated);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (browserInfo.android) {
|
|
||||||
requirejs(['cordova/android/iap'], onIapManagerLoaded);
|
|
||||||
} else {
|
|
||||||
requirejs(['cordova/iap'], onIapManagerLoaded);
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
32
dashboard-ui/cordova/searchmenu.js
vendored
32
dashboard-ui/cordova/searchmenu.js
vendored
|
@ -1,32 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function searchMenu() {
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
self.show = function () {
|
|
||||||
|
|
||||||
cordova.searchbar.show();
|
|
||||||
};
|
|
||||||
|
|
||||||
self.hide = function () {
|
|
||||||
|
|
||||||
cordova.searchbar.hide();
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('searchEvent', function (data) {
|
|
||||||
|
|
||||||
Events.trigger(self, 'change', [data.text || '']);
|
|
||||||
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
document.addEventListener('searchClosed', function (data) {
|
|
||||||
|
|
||||||
Events.trigger(self, 'closed');
|
|
||||||
|
|
||||||
}, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.SearchMenu = new searchMenu();
|
|
||||||
|
|
||||||
})();
|
|
180
dashboard-ui/cordova/serverdiscovery.js
vendored
180
dashboard-ui/cordova/serverdiscovery.js
vendored
|
@ -1,180 +0,0 @@
|
||||||
(function (globalScope) {
|
|
||||||
|
|
||||||
function stringToArrayBuffer(string) {
|
|
||||||
// UTF-16LE
|
|
||||||
var buf = new ArrayBuffer(string.length * 2);
|
|
||||||
var bufView = new Uint16Array(buf);
|
|
||||||
for (var i = 0, strLen = string.length; i < strLen; i++) {
|
|
||||||
bufView[i] = string.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
function arrayBufferToString(buf) {
|
|
||||||
return String.fromCharCode.apply(null, new Uint16Array(buf));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getResultCode(result) {
|
|
||||||
|
|
||||||
if (result != null && result.resultCode != null) {
|
|
||||||
return result.resultCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeSocket(socketId) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
chrome.sockets.udp.close(socketId);
|
|
||||||
} catch (err) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findServersInternal(timeoutMs) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
var servers = [];
|
|
||||||
|
|
||||||
// Expected server properties
|
|
||||||
// Name, Id, Address, EndpointAddress (optional)
|
|
||||||
|
|
||||||
var chrome = globalScope.chrome;
|
|
||||||
|
|
||||||
if (!chrome) {
|
|
||||||
deferred.resolveWith(null, [servers]);
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeout;
|
|
||||||
var socketId;
|
|
||||||
|
|
||||||
function onTimerExpired() {
|
|
||||||
deferred.resolveWith(null, [servers]);
|
|
||||||
|
|
||||||
if (socketId) {
|
|
||||||
chrome.sockets.udp.onReceive.removeListener(onReceive);
|
|
||||||
closeSocket(socketId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function startTimer() {
|
|
||||||
|
|
||||||
Logger.log('starting udp receive timer with timeout ms: ' + timeoutMs);
|
|
||||||
|
|
||||||
timeout = setTimeout(onTimerExpired, timeoutMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onReceive(info) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
Logger.log('ServerDiscovery message received');
|
|
||||||
|
|
||||||
Logger.log(info);
|
|
||||||
|
|
||||||
if (info != null && info.socketId == socketId) {
|
|
||||||
var json = arrayBufferToString(info.data);
|
|
||||||
Logger.log('Server discovery json: ' + json);
|
|
||||||
var server = JSON.parse(json);
|
|
||||||
|
|
||||||
server.RemoteAddress = info.remoteAddress;
|
|
||||||
|
|
||||||
if (info.remotePort) {
|
|
||||||
server.RemoteAddress += ':' + info.remotePort;
|
|
||||||
}
|
|
||||||
|
|
||||||
servers.push(server);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
Logger.log('Error receiving server info: ' + err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var port = 7359;
|
|
||||||
Logger.log('chrome.sockets.udp.create');
|
|
||||||
|
|
||||||
startTimer();
|
|
||||||
|
|
||||||
chrome.sockets.udp.create(function (createInfo) {
|
|
||||||
|
|
||||||
if (!createInfo) {
|
|
||||||
Logger.log('create fail');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!createInfo.socketId) {
|
|
||||||
Logger.log('create fail');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
socketId = createInfo.socketId;
|
|
||||||
|
|
||||||
Logger.log('chrome.sockets.udp.bind');
|
|
||||||
chrome.sockets.udp.bind(createInfo.socketId, '0.0.0.0', 0, function (bindResult) {
|
|
||||||
|
|
||||||
if (getResultCode(bindResult) != 0) {
|
|
||||||
Logger.log('bind fail: ' + bindResult);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = stringToArrayBuffer('who is EmbyServer?');
|
|
||||||
|
|
||||||
Logger.log('chrome.sockets.udp.send');
|
|
||||||
|
|
||||||
chrome.sockets.udp.send(createInfo.socketId, data, '255.255.255.255', port, function (sendResult) {
|
|
||||||
|
|
||||||
if (getResultCode(sendResult) != 0) {
|
|
||||||
Logger.log('send fail: ' + sendResult);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
chrome.sockets.udp.onReceive.addListener(onReceive);
|
|
||||||
Logger.log('sendTo: success ' + port);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
globalScope.ServerDiscovery = {
|
|
||||||
|
|
||||||
findServers: function (timeoutMs) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
deviceReadyPromise.then(function () {
|
|
||||||
|
|
||||||
try {
|
|
||||||
findServersInternal(timeoutMs).then(function (result) {
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [result]);
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
|
|
||||||
deferred.resolveWith(null, [[]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
deferred.resolveWith(null, [[]]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var deviceReadyDeferred = DeferredBuilder.Deferred();
|
|
||||||
var deviceReadyPromise = deviceReadyDeferred.promise();
|
|
||||||
|
|
||||||
document.addEventListener("deviceready", function () {
|
|
||||||
|
|
||||||
deviceReadyDeferred.resolve();
|
|
||||||
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
|
|
||||||
})(window);
|
|
22
dashboard-ui/cordova/sharingwidget.js
vendored
22
dashboard-ui/cordova/sharingwidget.js
vendored
|
@ -1,22 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
function showMenu(options, successCallback, cancelCallback) {
|
|
||||||
|
|
||||||
var shareInfo = options.share;
|
|
||||||
|
|
||||||
window.plugins.socialsharing.share(shareInfo.Overview, shareInfo.Name, shareInfo.ImageUrl, shareInfo.Url, function () {
|
|
||||||
|
|
||||||
successCallback(options);
|
|
||||||
|
|
||||||
}, function () {
|
|
||||||
|
|
||||||
cancelCallback(options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.SharingWidget = {
|
|
||||||
showMenu: showMenu
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
})();
|
|
28
dashboard-ui/cordova/volume.js
vendored
28
dashboard-ui/cordova/volume.js
vendored
|
@ -1,28 +0,0 @@
|
||||||
(function () {
|
|
||||||
|
|
||||||
// Handle the volume down button
|
|
||||||
//
|
|
||||||
function onVolumeDownKeyDown() {
|
|
||||||
|
|
||||||
MediaController.volumeDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the volume up button
|
|
||||||
//
|
|
||||||
function onVolumeUpKeyDown() {
|
|
||||||
|
|
||||||
MediaController.volumeUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
$(MediaController).on('playerchange', function (e, newPlayer, newTarget) {
|
|
||||||
|
|
||||||
document.removeEventListener("volumedownbutton", onVolumeDownKeyDown, false);
|
|
||||||
document.removeEventListener("volumeupbutton", onVolumeUpKeyDown, false);
|
|
||||||
|
|
||||||
if (!newPlayer.localPlayer) {
|
|
||||||
document.addEventListener("volumedownbutton", onVolumeDownKeyDown, false);
|
|
||||||
document.addEventListener("volumeupbutton", onVolumeUpKeyDown, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
})();
|
|
87
dashboard-ui/cordova/wakeonlan.js
vendored
87
dashboard-ui/cordova/wakeonlan.js
vendored
|
@ -1,87 +0,0 @@
|
||||||
(function (globalScope) {
|
|
||||||
|
|
||||||
function getResultCode(result) {
|
|
||||||
|
|
||||||
if (result != null && result.resultCode != null) {
|
|
||||||
return result.resultCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeSocket(socketId) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
chrome.sockets.udp.close(socketId);
|
|
||||||
} catch (err) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringToArrayBuffer(string) {
|
|
||||||
// UTF-16LE
|
|
||||||
var buf = new ArrayBuffer(string.length * 2);
|
|
||||||
var bufView = new Uint16Array(buf);
|
|
||||||
for (var i = 0, strLen = string.length; i < strLen; i++) {
|
|
||||||
bufView[i] = string.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/agnat/node_wake_on_lan/blob/master/wake_on_lan.js
|
|
||||||
|
|
||||||
globalScope.WakeOnLan = {
|
|
||||||
|
|
||||||
send: function (info) {
|
|
||||||
|
|
||||||
var deferred = DeferredBuilder.Deferred();
|
|
||||||
|
|
||||||
var chrome = globalScope.chrome;
|
|
||||||
|
|
||||||
if (!chrome) {
|
|
||||||
deferred.resolve();
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
var port = info.Port || 9;
|
|
||||||
|
|
||||||
//chrome.sockets.udp.create(function (createInfo) {
|
|
||||||
|
|
||||||
// if (!createInfo) {
|
|
||||||
// console.log('create fail');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// if (!createInfo.socketId) {
|
|
||||||
// console.log('create fail');
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var socketId = createInfo.socketId;
|
|
||||||
|
|
||||||
// console.log('chrome.sockets.udp.bind');
|
|
||||||
// chrome.sockets.udp.bind(createInfo.socketId, '0.0.0.0', 0, function (bindResult) {
|
|
||||||
|
|
||||||
// if (getResultCode(bindResult) != 0) {
|
|
||||||
// console.log('bind fail: ' + bindResult);
|
|
||||||
// deferred.resolve();
|
|
||||||
// closeSocket(socketId);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var data = stringToArrayBuffer('who is EmbyServer?');
|
|
||||||
|
|
||||||
// console.log('chrome.sockets.udp.send');
|
|
||||||
|
|
||||||
// chrome.sockets.udp.send(createInfo.socketId, data, '255.255.255.255', port, function (sendResult) {
|
|
||||||
|
|
||||||
// deferred.resolve();
|
|
||||||
// closeSocket(socketId);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//});
|
|
||||||
|
|
||||||
deferred.resolve();
|
|
||||||
return deferred.promise();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})(window);
|
|
|
@ -4,7 +4,7 @@
|
||||||
<title>${TitleSync}</title>
|
<title>${TitleSync}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mySyncActivityPage" data-role="page" class="page libraryPage syncActivityPage mySyncPage noSecondaryNavPage" data-contextname="${TitleSync}" data-require="scripts/syncactivity,scripts/taskbutton,scripts/mysync">
|
<div id="mySyncActivityPage" data-role="page" class="page libraryPage syncActivityPage mySyncPage noSecondaryNavPage" data-contextname="${TitleSync}" data-require="scripts/syncactivity,scripts/taskbutton,scripts/mysync,paper-spinner">
|
||||||
|
|
||||||
<div data-role="content">
|
<div data-role="content">
|
||||||
|
|
||||||
|
|
|
@ -459,11 +459,13 @@ var Dashboard = {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
elem = document.createElement("paper-spinner");
|
require(['paper-spinner'], function () {
|
||||||
elem.classList.add('docspinner');
|
elem = document.createElement("paper-spinner");
|
||||||
|
elem.classList.add('docspinner');
|
||||||
|
|
||||||
document.body.appendChild(elem);
|
document.body.appendChild(elem);
|
||||||
elem.active = true;
|
elem.active = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1842,7 +1844,8 @@ var AppInfo = {};
|
||||||
requirejs.config({
|
requirejs.config({
|
||||||
map: {
|
map: {
|
||||||
'*': {
|
'*': {
|
||||||
'css': 'components/requirecss'
|
'css': 'components/requirecss',
|
||||||
|
'html': 'components/requirehtml'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
urlArgs: urlArgs,
|
urlArgs: urlArgs,
|
||||||
|
@ -1852,6 +1855,8 @@ var AppInfo = {};
|
||||||
|
|
||||||
define("cryptojs-sha1", ["apiclient/sha1"]);
|
define("cryptojs-sha1", ["apiclient/sha1"]);
|
||||||
define("cryptojs-md5", ["apiclient/md5"]);
|
define("cryptojs-md5", ["apiclient/md5"]);
|
||||||
|
|
||||||
|
define("paper-spinner", []);
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(promiseResolve, hostingAppInfo) {
|
function init(promiseResolve, hostingAppInfo) {
|
||||||
|
@ -2137,12 +2142,14 @@ var AppInfo = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Dashboard.isRunningInCordova()) {
|
if (Dashboard.isRunningInCordova()) {
|
||||||
deps.push('scripts/registrationservices');
|
deps.push('cordova/registrationservices');
|
||||||
deps.push('cordova/back');
|
deps.push('cordova/back');
|
||||||
|
|
||||||
if (browserInfo.android) {
|
if (browserInfo.android) {
|
||||||
deps.push('cordova/android/androidcredentials');
|
deps.push('cordova/android/androidcredentials');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
deps.push('scripts/registrationservices');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (browserInfo.msie) {
|
if (browserInfo.msie) {
|
||||||
|
@ -2399,17 +2406,11 @@ var AppInfo = {};
|
||||||
|
|
||||||
function onWebComponentsReady() {
|
function onWebComponentsReady() {
|
||||||
|
|
||||||
var link = document.createElement('link');
|
require(['html!vulcanize-out.html'], function () {
|
||||||
link.rel = 'import';
|
|
||||||
|
|
||||||
link.onload = function () {
|
|
||||||
|
|
||||||
getHostingAppInfo().then(function (hostingAppInfo) {
|
getHostingAppInfo().then(function (hostingAppInfo) {
|
||||||
init(resolve, hostingAppInfo);
|
init(resolve, hostingAppInfo);
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
link.href = "vulcanize-out.html?v=" + window.dashboardVersion;
|
|
||||||
document.head.appendChild(link);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setBrowserInfo(isMobile);
|
setBrowserInfo(isMobile);
|
||||||
|
|
2
dashboard-ui/thirdparty/emby-icons.html
vendored
2
dashboard-ui/thirdparty/emby-icons.html
vendored
|
@ -141,6 +141,8 @@ See [iron-iconset](#iron-iconset) and [iron-iconset-svg](#iron-iconset-svg) for
|
||||||
<g id="sync-problem"><path d="M3 12c0 2.21.91 4.2 2.36 5.64L3 20h6v-6l-2.24 2.24C5.68 15.15 5 13.66 5 12c0-2.61 1.67-4.83 4-5.65V4.26C5.55 5.15 3 8.27 3 12zm8 5h2v-2h-2v2zM21 4h-6v6l2.24-2.24C18.32 8.85 19 10.34 19 12c0 2.61-1.67 4.83-4 5.65v2.09c3.45-.89 6-4.01 6-7.74 0-2.21-.91-4.2-2.36-5.64L21 4zm-10 9h2V7h-2v6z" /></g>
|
<g id="sync-problem"><path d="M3 12c0 2.21.91 4.2 2.36 5.64L3 20h6v-6l-2.24 2.24C5.68 15.15 5 13.66 5 12c0-2.61 1.67-4.83 4-5.65V4.26C5.55 5.15 3 8.27 3 12zm8 5h2v-2h-2v2zM21 4h-6v6l2.24-2.24C18.32 8.85 19 10.34 19 12c0 2.61-1.67 4.83-4 5.65v2.09c3.45-.89 6-4.01 6-7.74 0-2.21-.91-4.2-2.36-5.64L21 4zm-10 9h2V7h-2v6z" /></g>
|
||||||
<g id="notifications-active"><path d="M6.58 3.58L5.15 2.15C2.76 3.97 1.18 6.8 1.03 10h2c.15-2.65 1.51-4.97 3.55-6.42zM19.97 10h2c-.15-3.2-1.73-6.03-4.13-7.85l-1.43 1.43c2.05 1.45 3.41 3.77 3.56 6.42zm-1.97.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-2.87.68-5 3.25-5 6.32V16l-2 2v1h17v-1l-2-2v-5.5zM11.5 22c.14 0 .27-.01.4-.04.65-.13 1.19-.58 1.44-1.18.1-.24.16-.5.16-.78h-4c0 1.1.9 2 2 2z" /></g>
|
<g id="notifications-active"><path d="M6.58 3.58L5.15 2.15C2.76 3.97 1.18 6.8 1.03 10h2c.15-2.65 1.51-4.97 3.55-6.42zM19.97 10h2c-.15-3.2-1.73-6.03-4.13-7.85l-1.43 1.43c2.05 1.45 3.41 3.77 3.56 6.42zm-1.97.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-2.87.68-5 3.25-5 6.32V16l-2 2v1h17v-1l-2-2v-5.5zM11.5 22c.14 0 .27-.01.4-.04.65-.13 1.19-.58 1.44-1.18.1-.24.16-.5.16-.78h-4c0 1.1.9 2 2 2z" /></g>
|
||||||
<g id="notifications-off"><path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zM18 10.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-.51.12-.99.32-1.45.56L18 14.18V10.5zm-.27 8.5l2 2L21 19.73 4.27 3 3 4.27l2.92 2.92C5.34 8.16 5 9.29 5 10.5V16l-2 2v1h14.73z" /></g>
|
<g id="notifications-off"><path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zM18 10.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-.51.12-.99.32-1.45.56L18 14.18V10.5zm-.27 8.5l2 2L21 19.73 4.27 3 3 4.27l2.92 2.92C5.34 8.16 5 9.29 5 10.5V16l-2 2v1h14.73z" /></g>
|
||||||
|
<g id="expand-less"><path d="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z" /></g>
|
||||||
|
<g id="expand-more"><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z" /></g>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</iron-iconset-svg>
|
</iron-iconset-svg>
|
||||||
|
|
|
@ -12312,6 +12312,7 @@ The `aria-labelledby` attribute will be set to the header element, if one exists
|
||||||
},
|
},
|
||||||
|
|
||||||
attached: function() {
|
attached: function() {
|
||||||
|
this.style.display = 'none';
|
||||||
this.queryChanged();
|
this.queryChanged();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -13627,7 +13628,8 @@ is separate from validation, and `allowed-pattern` does not affect how the input
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The label for this input. Bind this to `<paper-input-container>`'s `label` property.
|
* The label for this input. Bind this to `<label>`'s content and `hidden` property, e.g.
|
||||||
|
* `<label hidden$="[[!label]]">[[label]]</label>` in your `template`
|
||||||
*/
|
*/
|
||||||
label: {
|
label: {
|
||||||
type: String
|
type: String
|
||||||
|
@ -13882,21 +13884,21 @@ is separate from validation, and `allowed-pattern` does not affect how the input
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind this to the `<input is="iron-input">`'s `results` property, , used with type=search.
|
* Bind this to the `<input is="iron-input">`'s `results` property, used with type=search.
|
||||||
*/
|
*/
|
||||||
results: {
|
results: {
|
||||||
type: Number
|
type: Number
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind this to the `<input is="iron-input">`'s `accept` property, , used with type=file.
|
* Bind this to the `<input is="iron-input">`'s `accept` property, used with type=file.
|
||||||
*/
|
*/
|
||||||
accept: {
|
accept: {
|
||||||
type: String
|
type: String
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind this to the `<input is="iron-input">`'s `multiple` property, , used with type=file.
|
* Bind this to the `<input is="iron-input">`'s `multiple` property, used with type=file.
|
||||||
*/
|
*/
|
||||||
multiple: {
|
multiple: {
|
||||||
type: Boolean
|
type: Boolean
|
||||||
|
@ -13915,13 +13917,22 @@ is separate from validation, and `allowed-pattern` does not affect how the input
|
||||||
},
|
},
|
||||||
|
|
||||||
listeners: {
|
listeners: {
|
||||||
'addon-attached': '_onAddonAttached'
|
'addon-attached': '_onAddonAttached',
|
||||||
|
'focus': '_onFocus'
|
||||||
},
|
},
|
||||||
|
|
||||||
observers: [
|
observers: [
|
||||||
'_focusedControlStateChanged(focused)'
|
'_focusedControlStateChanged(focused)'
|
||||||
],
|
],
|
||||||
|
|
||||||
|
keyBindings: {
|
||||||
|
'shift+tab:keydown': '_onShiftTabDown'
|
||||||
|
},
|
||||||
|
|
||||||
|
hostAttributes: {
|
||||||
|
tabindex: 0
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a reference to the input element.
|
* Returns a reference to the input element.
|
||||||
*/
|
*/
|
||||||
|
@ -13929,6 +13940,13 @@ is separate from validation, and `allowed-pattern` does not affect how the input
|
||||||
return this.$.input;
|
return this.$.input;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a reference to the focusable element.
|
||||||
|
*/
|
||||||
|
get _focusableElement() {
|
||||||
|
return this.inputElement;
|
||||||
|
},
|
||||||
|
|
||||||
attached: function() {
|
attached: function() {
|
||||||
this._updateAriaLabelledBy();
|
this._updateAriaLabelledBy();
|
||||||
},
|
},
|
||||||
|
@ -13962,6 +13980,29 @@ is separate from validation, and `allowed-pattern` does not affect how the input
|
||||||
return this.inputElement.validate();
|
return this.inputElement.validate();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forward focus to inputElement
|
||||||
|
*/
|
||||||
|
_onFocus: function() {
|
||||||
|
if (!this._shiftTabPressed) {
|
||||||
|
this._focusableElement.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler that is called when a shift+tab keypress is detected by the menu.
|
||||||
|
*
|
||||||
|
* @param {CustomEvent} event A key combination event.
|
||||||
|
*/
|
||||||
|
_onShiftTabDown: function(event) {
|
||||||
|
var oldTabIndex = this.getAttribute('tabindex');
|
||||||
|
this._shiftTabPressed = true;
|
||||||
|
this.setAttribute('tabindex', '-1');
|
||||||
|
this.async(function() {
|
||||||
|
this.setAttribute('tabindex', oldTabIndex);
|
||||||
|
this._shiftTabPressed = false;
|
||||||
|
}, 1);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* If `autoValidate` is true, then validates the element.
|
* If `autoValidate` is true, then validates the element.
|
||||||
*/
|
*/
|
||||||
|
@ -14046,7 +14087,11 @@ is separate from validation, and `allowed-pattern` does not affect how the input
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @polymerBehavior */
|
/** @polymerBehavior */
|
||||||
Polymer.PaperInputBehavior = [Polymer.IronControlState, Polymer.PaperInputBehaviorImpl];
|
Polymer.PaperInputBehavior = [
|
||||||
|
Polymer.IronControlState,
|
||||||
|
Polymer.IronA11yKeysBehavior,
|
||||||
|
Polymer.PaperInputBehaviorImpl
|
||||||
|
];
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -18239,7 +18284,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
|
||||||
.input-content {
|
.input-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
@apply(--layout-horizontal);
|
@apply(--layout-horizontal);
|
||||||
@apply(--layout-end);
|
@apply(--layout-center);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-content ::content label,
|
.input-content ::content label,
|
||||||
|
@ -18738,7 +18783,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
|
||||||
|
|
||||||
<label hidden$="[[!label]]">[[label]]</label>
|
<label hidden$="[[!label]]">[[label]]</label>
|
||||||
|
|
||||||
<input is="iron-input" id="input" aria-labelledby$="[[_ariaLabelledBy]]" aria-describedby$="[[_ariaDescribedBy]]" disabled$="[[disabled]]" bind-value="{{value}}" invalid="{{invalid}}" prevent-invalid-input="[[preventInvalidInput]]" allowed-pattern="[[allowedPattern]]" validator="[[validator]]" type$="[[type]]" pattern$="[[pattern]]" required$="[[required]]" autocomplete$="[[autocomplete]]" autofocus$="[[autofocus]]" inputmode$="[[inputmode]]" minlength$="[[minlength]]" maxlength$="[[maxlength]]" min$="[[min]]" max$="[[max]]" step$="[[step]]" name$="[[name]]" placeholder$="[[placeholder]]" readonly$="[[readonly]]" list$="[[list]]" size$="[[size]]" autocapitalize$="[[autocapitalize]]" autocorrect$="[[autocorrect]]" on-change="_onChange" autosave$="[[autosave]]" results$="[[results]]" accept$="[[accept]]" multiple$="[[multiple]]">
|
<input is="iron-input" id="input" aria-labelledby$="[[_ariaLabelledBy]]" aria-describedby$="[[_ariaDescribedBy]]" disabled$="[[disabled]]" bind-value="{{value}}" invalid="{{invalid}}" prevent-invalid-input="[[preventInvalidInput]]" allowed-pattern="[[allowedPattern]]" validator="[[validator]]" type$="[[type]]" pattern$="[[pattern]]" required$="[[required]]" autocomplete$="[[autocomplete]]" autofocus$="[[autofocus]]" inputmode$="[[inputmode]]" minlength$="[[minlength]]" maxlength$="[[maxlength]]" min$="[[min]]" max$="[[max]]" step$="[[step]]" name$="[[name]]" placeholder$="[[placeholder]]" readonly$="[[readonly]]" list$="[[list]]" size$="[[size]]" autocapitalize$="[[autocapitalize]]" autocorrect$="[[autocorrect]]" on-change="_onChange" tabindex$="[[tabindex]]" autosave$="[[autosave]]" results$="[[results]]" accept$="[[accept]]" multiple$="[[multiple]]">
|
||||||
|
|
||||||
<content select="[suffix]"></content>
|
<content select="[suffix]"></content>
|
||||||
|
|
||||||
|
@ -18761,8 +18806,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
|
||||||
|
|
||||||
behaviors: [
|
behaviors: [
|
||||||
Polymer.IronFormElementBehavior,
|
Polymer.IronFormElementBehavior,
|
||||||
Polymer.PaperInputBehavior,
|
Polymer.PaperInputBehavior
|
||||||
Polymer.IronControlState
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -19892,7 +19936,11 @@ is separate from validation, and `allowed-pattern` does not affect how the input
|
||||||
|
|
||||||
_ariaDescribedByChanged: function(ariaDescribedBy) {
|
_ariaDescribedByChanged: function(ariaDescribedBy) {
|
||||||
this.$.input.textarea.setAttribute('aria-describedby', ariaDescribedBy);
|
this.$.input.textarea.setAttribute('aria-describedby', ariaDescribedBy);
|
||||||
}
|
},
|
||||||
|
|
||||||
|
get _focusableElement() {
|
||||||
|
return this.$.input.textarea;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<dom-module id="paper-checkbox" assetpath="bower_components/paper-checkbox/">
|
<dom-module id="paper-checkbox" assetpath="bower_components/paper-checkbox/">
|
||||||
|
@ -20668,6 +20716,8 @@ is separate from validation, and `allowed-pattern` does not affect how the input
|
||||||
<g id="sync-problem"><path d="M3 12c0 2.21.91 4.2 2.36 5.64L3 20h6v-6l-2.24 2.24C5.68 15.15 5 13.66 5 12c0-2.61 1.67-4.83 4-5.65V4.26C5.55 5.15 3 8.27 3 12zm8 5h2v-2h-2v2zM21 4h-6v6l2.24-2.24C18.32 8.85 19 10.34 19 12c0 2.61-1.67 4.83-4 5.65v2.09c3.45-.89 6-4.01 6-7.74 0-2.21-.91-4.2-2.36-5.64L21 4zm-10 9h2V7h-2v6z"></path></g>
|
<g id="sync-problem"><path d="M3 12c0 2.21.91 4.2 2.36 5.64L3 20h6v-6l-2.24 2.24C5.68 15.15 5 13.66 5 12c0-2.61 1.67-4.83 4-5.65V4.26C5.55 5.15 3 8.27 3 12zm8 5h2v-2h-2v2zM21 4h-6v6l2.24-2.24C18.32 8.85 19 10.34 19 12c0 2.61-1.67 4.83-4 5.65v2.09c3.45-.89 6-4.01 6-7.74 0-2.21-.91-4.2-2.36-5.64L21 4zm-10 9h2V7h-2v6z"></path></g>
|
||||||
<g id="notifications-active"><path d="M6.58 3.58L5.15 2.15C2.76 3.97 1.18 6.8 1.03 10h2c.15-2.65 1.51-4.97 3.55-6.42zM19.97 10h2c-.15-3.2-1.73-6.03-4.13-7.85l-1.43 1.43c2.05 1.45 3.41 3.77 3.56 6.42zm-1.97.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-2.87.68-5 3.25-5 6.32V16l-2 2v1h17v-1l-2-2v-5.5zM11.5 22c.14 0 .27-.01.4-.04.65-.13 1.19-.58 1.44-1.18.1-.24.16-.5.16-.78h-4c0 1.1.9 2 2 2z"></path></g>
|
<g id="notifications-active"><path d="M6.58 3.58L5.15 2.15C2.76 3.97 1.18 6.8 1.03 10h2c.15-2.65 1.51-4.97 3.55-6.42zM19.97 10h2c-.15-3.2-1.73-6.03-4.13-7.85l-1.43 1.43c2.05 1.45 3.41 3.77 3.56 6.42zm-1.97.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-2.87.68-5 3.25-5 6.32V16l-2 2v1h17v-1l-2-2v-5.5zM11.5 22c.14 0 .27-.01.4-.04.65-.13 1.19-.58 1.44-1.18.1-.24.16-.5.16-.78h-4c0 1.1.9 2 2 2z"></path></g>
|
||||||
<g id="notifications-off"><path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zM18 10.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-.51.12-.99.32-1.45.56L18 14.18V10.5zm-.27 8.5l2 2L21 19.73 4.27 3 3 4.27l2.92 2.92C5.34 8.16 5 9.29 5 10.5V16l-2 2v1h14.73z"></path></g>
|
<g id="notifications-off"><path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zM18 10.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-.51.12-.99.32-1.45.56L18 14.18V10.5zm-.27 8.5l2 2L21 19.73 4.27 3 3 4.27l2.92 2.92C5.34 8.16 5 9.29 5 10.5V16l-2 2v1h14.73z"></path></g>
|
||||||
|
<g id="expand-less"><path d="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"></path></g>
|
||||||
|
<g id="expand-more"><path d="M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z"></path></g>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
</iron-iconset-svg>
|
</iron-iconset-svg>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue