We're all friends here. Open sourcing huegasm and copying it over from a private repo. Some things to note:
- I never intended to open source this code as I was writting it - Some of the code could likely be better componetized and made prettier - No, I didn't use ember data and just made ad-hoc calls with $.ajax() because I found it to be more flexible for working with external APIs - I've now moved on to newer projects and will not be working on huegasm at all...if at all - Let me know if something is fucky and I'll try fix it...enjoy ( ͡° ͜ʖ ͡°)
4
.bowerrc
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"directory": "bower_components",
|
||||
"analytics": false
|
||||
}
|
||||
34
.editorconfig
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.js]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.hbs]
|
||||
insert_final_newline = false
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.css]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.html]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{diff,md}]
|
||||
trim_trailing_whitespace = false
|
||||
10
.ember-cli
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
/**
|
||||
Ember CLI sends analytics information by default. The data is completely
|
||||
anonymous, but there are times when you might want to disable this behavior.
|
||||
|
||||
Setting `disableAnalytics` to true will prevent any data from being sent.
|
||||
*/
|
||||
"disableAnalytics": true,
|
||||
"usePods": true
|
||||
}
|
||||
18
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/bower_components
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage/*
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
testem.log
|
||||
/.idea/
|
||||
37
.jshintrc
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"predef": [
|
||||
"document",
|
||||
"window",
|
||||
"-Promise",
|
||||
"Dancer",
|
||||
"ID3",
|
||||
"FileAPIReader",
|
||||
"SC",
|
||||
"introJs"
|
||||
],
|
||||
"browser": true,
|
||||
"boss": true,
|
||||
"curly": true,
|
||||
"debug": false,
|
||||
"devel": true,
|
||||
"eqeqeq": true,
|
||||
"evil": true,
|
||||
"forin": false,
|
||||
"immed": false,
|
||||
"laxbreak": false,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": false,
|
||||
"nonew": false,
|
||||
"nomen": false,
|
||||
"onevar": false,
|
||||
"plusplus": false,
|
||||
"regexp": false,
|
||||
"undef": true,
|
||||
"sub": true,
|
||||
"strict": false,
|
||||
"white": false,
|
||||
"eqnull": true,
|
||||
"esnext": true,
|
||||
"unused": true
|
||||
}
|
||||
23
.travis.yml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.12"
|
||||
|
||||
sudo: false
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
before_install:
|
||||
- export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH
|
||||
- "npm config set spin false"
|
||||
- "npm install -g npm@^2"
|
||||
|
||||
install:
|
||||
- npm install -g bower
|
||||
- npm install
|
||||
- bower install
|
||||
|
||||
script:
|
||||
- npm test
|
||||
3
.watchmanconfig
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"ignore_dirs": ["tmp", "dist"]
|
||||
}
|
||||
15
README.md
|
|
@ -1,4 +1,13 @@
|
|||
# huegasm
|
||||
www.huegasm.com
|
||||
# Huegasm
|
||||
Music awesomeness for hue lights.
|
||||
|
||||
This repository here is strictly for hosting huegasm on github pages.
|
||||
# TODO
|
||||
## FEATURES
|
||||
|
||||
## BUGS
|
||||
|
||||
## POSSIBLE FUTURE FEATURES
|
||||
- better visualizations
|
||||
- beat settings by interval
|
||||
- auto beat detection mode
|
||||
- display player time when hovering over seek bar
|
||||
|
|
|
|||
18
app/app.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import Ember from 'ember';
|
||||
import Resolver from './resolver';
|
||||
import loadInitializers from 'ember-load-initializers';
|
||||
import config from './config/environment';
|
||||
|
||||
let App;
|
||||
|
||||
Ember.MODEL_FACTORY_INJECTIONS = true;
|
||||
|
||||
App = Ember.Application.extend({
|
||||
modulePrefix: config.modulePrefix,
|
||||
podModulePrefix: config.podModulePrefix,
|
||||
Resolver
|
||||
});
|
||||
|
||||
loadInitializers(App, config.modulePrefix);
|
||||
|
||||
export default App;
|
||||
63
app/index.html
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<title>Huegasm</title>
|
||||
<meta name="description" content="Huegasm is a free web application for managing and synchronizing your Philips Hue lights with the beat of your music.">
|
||||
<meta name="keywords" content="huegasm,hue,philips hue,music player">
|
||||
<meta name="author" content="Egor Philippov">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link type="text/plain" rel="author" href="http://www.huegasm.com/humans.txt" />
|
||||
|
||||
{{content-for 'head'}}
|
||||
|
||||
<link href='//fonts.googleapis.com/css?family=Slabo+27px|Open+Sans' rel='stylesheet' type='text/css'>
|
||||
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="/favicon-194x194.png" sizes="194x194">
|
||||
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96">
|
||||
<link rel="icon" type="image/png" href="/android-chrome-192x192.png" sizes="192x192">
|
||||
<link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<link rel="stylesheet" integrity="" href="assets/vendor.css">
|
||||
<link rel="stylesheet" integrity="" href="assets/huegasm.css">
|
||||
|
||||
{{content-for 'head-footer'}}
|
||||
|
||||
<script src="https://connect.soundcloud.com/sdk/sdk-3.0.0.js"></script>
|
||||
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-69470561-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
{{content-for 'body'}}
|
||||
|
||||
<script src="assets/vendor.js"></script>
|
||||
<script src="assets/huegasm.js"></script>
|
||||
|
||||
{{content-for 'body-footer'}}
|
||||
</body>
|
||||
</html>
|
||||
61
app/pods/components/add-group-modal/component.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import Em from 'ember';
|
||||
|
||||
export default Em.Component.extend({
|
||||
actions: {
|
||||
close: function(){
|
||||
this.sendAction();
|
||||
},
|
||||
save: function(){
|
||||
var newGroupData = {"name": this.get('groupName'), "lights": this.get('selectedLights')}, newGroupsData = this.get('groupsData');
|
||||
|
||||
Em.$.ajax(this.get('apiURL') + '/groups', {
|
||||
data: JSON.stringify(newGroupData),
|
||||
contentType: 'application/json',
|
||||
type: 'POST'
|
||||
});
|
||||
|
||||
// crappy code to redraw the lights
|
||||
newGroupsData['9999'] = newGroupData;
|
||||
|
||||
this.setProperties({
|
||||
updateGroupsData: true,
|
||||
groupsData: newGroupsData
|
||||
});
|
||||
this.sendAction();
|
||||
},
|
||||
clickLight: function(id) {
|
||||
var selectedLights = this.get('selectedLights');
|
||||
|
||||
if(selectedLights.contains(id)){
|
||||
selectedLights.removeObject(id);
|
||||
} else {
|
||||
selectedLights.pushObject(id);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
didInsertElement: function() {
|
||||
Em.$(document).keypress((event) => {
|
||||
if(!this.get('saveDisabled') && event.which === 13) {
|
||||
this.send('save');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
groupName: null,
|
||||
|
||||
selectedLights: [],
|
||||
|
||||
onIsShowingModalChange: function(){
|
||||
if(this.get('isShowingModal')){
|
||||
this.setProperties({
|
||||
selectedLights: [],
|
||||
groupName: null
|
||||
});
|
||||
}
|
||||
}.observes('isShowingModal'),
|
||||
|
||||
saveDisabled: function(){
|
||||
return Em.isNone(this.get('groupName')) || Em.isEmpty(this.get('selectedLights')) || Em.isEmpty(this.get('groupName').trim());
|
||||
}.property('groupName', 'selectedLights.[]')
|
||||
});
|
||||
12
app/pods/components/add-group-modal/template.hbs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{{#if isShowingModal}}
|
||||
{{#modal-dialog close="close" alignment="center" translucentOverlay=true}}
|
||||
|
||||
{{light-group lightsData=lightsData activeLights=selectedLights action="clickLight" apiURL=apiURL noHover=true}}
|
||||
|
||||
{{paper-input label="Group name" value=groupName max="32" max-errortext="The group name cannot exceed 32 characters"}}
|
||||
|
||||
{{#paper-button action="close"}}Close{{/paper-button}}
|
||||
{{#paper-button class="pull-right" action="save" disabled=saveDisabled primary=true}}Save{{/paper-button}}
|
||||
|
||||
{{/modal-dialog}}
|
||||
{{/if}}
|
||||
38
app/pods/components/add-soundcloud-sound-modal/component.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import Em from 'ember';
|
||||
|
||||
export default Em.Component.extend({
|
||||
actions: {
|
||||
close () {
|
||||
this.sendAction();
|
||||
},
|
||||
add (){
|
||||
this.sendAction('action', this.get('url'));
|
||||
}
|
||||
},
|
||||
|
||||
url: null,
|
||||
|
||||
onIsShowingModalChange: function(){
|
||||
if(this.get('isShowingModal')){
|
||||
this.set('url', null);
|
||||
setTimeout(()=>{
|
||||
Em.$('md-input-container input').focus();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
}.observes('isShowingModal'),
|
||||
|
||||
didInsertElement: function() {
|
||||
var self = this;
|
||||
|
||||
Em.$(document).keypress(function(event) {
|
||||
if(!self.get('saveDisabled') && event.which === 13) {
|
||||
self.send('add');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
saveDisabled: function(){
|
||||
return Em.isNone(this.get('url')) || Em.isEmpty(this.get('url').trim());
|
||||
}.property('url')
|
||||
});
|
||||
13
app/pods/components/add-soundcloud-sound-modal/template.hbs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{{#if isShowingModal}}
|
||||
{{#modal-dialog close="close" alignment="center" translucentOverlay=true attachment="center" targetAttachment="center"}}
|
||||
|
||||
<p>Enter a <a href="https://soundcloud.com">SoundCloud</a> track or playlist/set URL</p>
|
||||
<p>( ex. https://soundcloud.com/mrsuicidesheep/tracks )</p>
|
||||
|
||||
{{paper-input label="SoundCloud URL" icon="search" value=url}}
|
||||
|
||||
{{#paper-button action="close"}}Close{{/paper-button}}
|
||||
{{#paper-button class="pull-right" action="add" disabled=saveDisabled primary=true}}Add Music{{/paper-button}}
|
||||
|
||||
{{/modal-dialog}}
|
||||
{{/if}}
|
||||
149
app/pods/components/bridge-finder/component.js
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
import Em from 'ember';
|
||||
|
||||
export default Em.Component.extend({
|
||||
classNames: ['container', 'bridgeFinder'],
|
||||
|
||||
bridgeIp: null,
|
||||
trial: false,
|
||||
bridgeUsername: null,
|
||||
|
||||
bridgeFindStatus: null,
|
||||
bridgeFindSuccess: Em.computed.equal('bridgeFindStatus', 'success'),
|
||||
bridgeFindMultiple: Em.computed.equal('bridgeFindStatus', 'multiple'),
|
||||
bridgeFindFail: Em.computed.equal('bridgeFindStatus', 'fail'),
|
||||
|
||||
// 30 seconds
|
||||
bridgeUsernamePingMaxTime: 30000,
|
||||
bridgeUsernamePingIntervalTime: 1000,
|
||||
bridgeUserNamePingIntervalProgress: 0,
|
||||
|
||||
bridgePingIntervalHandle: null,
|
||||
bridgeAuthenticateReachedStatus: null,
|
||||
|
||||
manualBridgeIp: null,
|
||||
manualBridgeIpNotFound: false,
|
||||
multipleBridgeIps: [],
|
||||
error: false,
|
||||
|
||||
actions: {
|
||||
retry(){
|
||||
this.onBridgeIpChange();
|
||||
},
|
||||
|
||||
findBridgeByIp() {
|
||||
var manualBridgeIp = this.get('manualBridgeIp');
|
||||
|
||||
if (manualBridgeIp.toLowerCase() === 'trial' || manualBridgeIp.toLowerCase() === 'offline') {
|
||||
this.setProperties({
|
||||
trial: true,
|
||||
bridgeIp: 'trial',
|
||||
bridgeUsername: 'trial'
|
||||
});
|
||||
} else {
|
||||
Em.$.ajax('http://' + manualBridgeIp + '/api', {
|
||||
data: JSON.stringify({"devicetype": "huegasm"}),
|
||||
contentType: 'application/json',
|
||||
type: 'POST'
|
||||
}).fail(() => {
|
||||
this.set('manualBridgeIpNotFound', true);
|
||||
setTimeout(() => { this.set('manualBridgeIpNotFound', false); }, 5000);
|
||||
}).then(() => {
|
||||
this.set('bridgeIp', manualBridgeIp);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
var self = this;
|
||||
|
||||
Em.$(document).keypress(function(event) {
|
||||
if(!Em.isNone(self.get('manualBridgeIp')) && event.which === 13) {
|
||||
self.send('findBridgeByIp');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// find the bridge ip here
|
||||
init() {
|
||||
this._super();
|
||||
|
||||
if(this.get('bridgeIp') === null) {
|
||||
Em.$.ajax('https://www.meethue.com/api/nupnp', {
|
||||
timeout: 30000
|
||||
})
|
||||
.done((result, status)=> {
|
||||
var bridgeFindStatus = 'fail';
|
||||
|
||||
if (status === 'success' && result.length === 1) {
|
||||
this.set('bridgeIp', result[0].internalipaddress);
|
||||
this.get('storage').set('huegasm.bridgeIp', result[0].internalipaddress);
|
||||
bridgeFindStatus = 'success';
|
||||
} else if (result.length > 1) {
|
||||
var multipleBridgeIps = this.get('multipleBridgeIps');
|
||||
|
||||
result.forEach(function (item) {
|
||||
multipleBridgeIps.pushObject(item.internalipaddress);
|
||||
});
|
||||
|
||||
bridgeFindStatus = 'multiple';
|
||||
} else {
|
||||
bridgeFindStatus = 'fail';
|
||||
}
|
||||
|
||||
this.set('bridgeFindStatus', bridgeFindStatus);
|
||||
})
|
||||
.fail(()=>{
|
||||
this.set('bridgeFindStatus', 'fail');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// try to authenticate against the bridge here
|
||||
onBridgeIpChange: function () {
|
||||
if(!this.get('trial') && !this.get('isAuthenticating')) {
|
||||
this.setProperties({
|
||||
bridgePingIntervalHandle: setInterval(this.pingBridgeUser.bind(this), this.get('bridgeUsernamePingIntervalTime')),
|
||||
bridgeUserNamePingIntervalProgress: 0
|
||||
});
|
||||
}
|
||||
}.observes('bridgeIp').on('init'),
|
||||
|
||||
pingBridgeUser() {
|
||||
var bridgeIp = this.get('bridgeIp'),
|
||||
bridgeUserNamePingIntervalProgress = this.get('bridgeUserNamePingIntervalProgress'),
|
||||
bridgeUsernamePingMaxTime = this.get('bridgeUsernamePingMaxTime');
|
||||
|
||||
if (bridgeIp !== null && bridgeUserNamePingIntervalProgress < 100) {
|
||||
Em.$.ajax('http://' + bridgeIp + '/api', {
|
||||
data: JSON.stringify({"devicetype": "huegasm"}),
|
||||
contentType: 'application/json',
|
||||
type: 'POST'
|
||||
}).done((result, status)=>{
|
||||
if (status === 'success' && !result[0].error) {
|
||||
this.clearBridgePingIntervalHandle();
|
||||
this.set('bridgeUsername', result[0].success.username);
|
||||
this.get('storage').set('huegasm.bridgeUsername', result[0].success.username);
|
||||
}
|
||||
|
||||
this.set('bridgeAuthenticateReachedStatus', status);
|
||||
}).fail(()=>{
|
||||
this.clearBridgePingIntervalHandle();
|
||||
this.set('error', true);
|
||||
});
|
||||
|
||||
this.incrementProperty('bridgeUserNamePingIntervalProgress', this.get('bridgeUsernamePingIntervalTime')/bridgeUsernamePingMaxTime*100);
|
||||
} else {
|
||||
this.clearBridgePingIntervalHandle();
|
||||
}
|
||||
},
|
||||
|
||||
clearBridgePingIntervalHandle(){
|
||||
clearInterval(this.get('bridgePingIntervalHandle'));
|
||||
this.set('bridgePingIntervalHandle', null);
|
||||
},
|
||||
|
||||
isAuthenticating: function(){
|
||||
return this.get('bridgePingIntervalHandle') !== null;
|
||||
}.property('bridgePingIntervalHandle')
|
||||
});
|
||||
54
app/pods/components/bridge-finder/template.hbs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<div class="title"><img src="assets/images/logo.png" alt="Huegasm"></div>
|
||||
{{#unless bridgeUsername}}
|
||||
{{#if bridgeIp}}
|
||||
{{#if error}}
|
||||
<p>Huegasm encountered a critical error while trying to connect to your bridge.<br><br>
|
||||
This likely happened because you're using an outdated browser and/or because your browser does not support <a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing">CORS</a>. Feel free to contact me through the link at the bottom of the page if you feel like this is not the case.<br>
|
||||
For the best browsing experience on this site ( and every other one known to man ) please switch to <a href="https://www.google.com/chrome/">Google Chrome</a> or <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a></p>.
|
||||
{{else}}
|
||||
<img src="assets/images/pressButtonBridge.png" id="pressButtonBridgeImg">
|
||||
{{paper-progress-linear warn=true value=bridgeUserNamePingIntervalProgress}}
|
||||
|
||||
{{#if isAuthenticating}}
|
||||
<p>Your bridge IP is <b>{{bridgeIp}}</b> <br>
|
||||
Press the button on your bridge to authenticate this application.</p>
|
||||
{{else}}
|
||||
<p>You failed to press the button. <a class="noTextDecoration" href="#" {{action 'retry'}}>RETRY</a></p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{#unless bridgeFindStatus}}
|
||||
{{paper-progress-circular}}
|
||||
<p>Trying to find your bridge's IP.</p>
|
||||
{{/unless}}
|
||||
|
||||
{{#if bridgeFindMultiple}}
|
||||
<p>Found multiple hue bridges. <br>
|
||||
Please select the one you want to use for this application.</p>
|
||||
|
||||
<div id="bridgeButtonGroup">
|
||||
{{#each multipleBridgeIps as |bridge|}}
|
||||
{{#paper-radio value=bridge selected=bridgeIp}}{{bridge}}{{/paper-radio}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{#if bridgeFindFail}}
|
||||
<p>A hue bridge could not be automatically found on your network. <br>
|
||||
Enter one manually? <br><br>
|
||||
( or type <b>offline</b> to look around )
|
||||
</p>
|
||||
|
||||
<span id="bridgeInput">
|
||||
{{paper-input label="Hue bridge IP address" value=manualBridgeIp}}
|
||||
{{#paper-button action="findBridgeByIp" raised=true primary=true}}Find{{/paper-button}}
|
||||
</span>
|
||||
|
||||
{{#if manualBridgeIpNotFound}}
|
||||
<p class="bg-danger">
|
||||
Could not find a bridge with that IP address.
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
55
app/pods/components/color-picker/component.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import Em from 'ember';
|
||||
|
||||
export default Em.Component.extend({
|
||||
classNames: ['colorpicker'],
|
||||
|
||||
rgb: null,
|
||||
|
||||
canvas: null,
|
||||
canvasContext: null,
|
||||
|
||||
mouseUp(){
|
||||
this.set('pressingDown', false);
|
||||
},
|
||||
|
||||
mouseMove(event){
|
||||
if (this.get('pressingDown')) {
|
||||
this.mouseDown(event);
|
||||
}
|
||||
},
|
||||
|
||||
mouseDown(event){
|
||||
var canvasOffset = Em.$(this.get('canvas')).offset();
|
||||
var canvasX = Math.floor(event.pageX - canvasOffset.left), canvasY = Math.floor(event.pageY - canvasOffset.top);
|
||||
|
||||
// get current pixel
|
||||
var imageData = this.get('canvasContext').getImageData(canvasX, canvasY, 1, 1);
|
||||
var pixel = imageData.data;
|
||||
|
||||
this.set('pressingDown', true);
|
||||
|
||||
if (!(pixel[0] === 0 && pixel[1] === 0 && pixel[2] === 0)) {
|
||||
this.set('rgb', [pixel[0], pixel[1], pixel[2]]);
|
||||
}
|
||||
},
|
||||
|
||||
pressingDown: false,
|
||||
|
||||
// https://dzone.com/articles/creating-your-own-html5
|
||||
didInsertElement(){
|
||||
// handle color changes
|
||||
var canvas = Em.$('#picker')[0],
|
||||
canvasContext = canvas.getContext('2d'),
|
||||
image = new Image();
|
||||
|
||||
image.src = 'assets/images/colormap.png';
|
||||
image.onload = function () {
|
||||
canvasContext.drawImage(image, 0, 0, image.width, image.height); // draw the image on the canvas
|
||||
};
|
||||
|
||||
this.setProperties({
|
||||
canvas: canvas,
|
||||
canvasContext: canvasContext
|
||||
});
|
||||
}
|
||||
});
|
||||
1
app/pods/components/color-picker/template.hbs
Normal file
|
|
@ -0,0 +1 @@
|
|||
<canvas id="picker" width="256" height="256"></canvas>
|
||||
35
app/pods/components/delete-group-modal/component.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import Em from 'ember';
|
||||
|
||||
export default Em.Component.extend({
|
||||
actions: {
|
||||
close: function(){
|
||||
this.sendAction();
|
||||
},
|
||||
delete: function(){
|
||||
var groupId = this.get('groupId');
|
||||
|
||||
Em.$.ajax(this.get('apiURL') + '/groups/' + groupId, {
|
||||
contentType: 'application/json',
|
||||
type: 'DELETE'
|
||||
});
|
||||
|
||||
var groupsData = this.get('groupsData'), newGroupsData = [];
|
||||
for (let key in groupsData) {
|
||||
if(groupsData.hasOwnProperty(key) && groupsData[key].name !== this.get('groupName') ){
|
||||
newGroupsData[key] = groupsData[key];
|
||||
}
|
||||
}
|
||||
|
||||
if(groupId === this.get('groupIdSelection')){
|
||||
this.set('groupIdSelection', '0');
|
||||
}
|
||||
|
||||
this.setProperties({
|
||||
updateGroupsData: true,
|
||||
groupsData: newGroupsData
|
||||
});
|
||||
|
||||
this.sendAction();
|
||||
}
|
||||
}
|
||||
});
|
||||
10
app/pods/components/delete-group-modal/template.hbs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{{#if isShowingModal}}
|
||||
{{#modal-dialog close="close" alignment="center" translucentOverlay=true}}
|
||||
|
||||
<p>Are you sure you want to delete group "{{groupName}}"?</p>
|
||||
|
||||
{{#paper-button action="close"}}Close{{/paper-button}}
|
||||
{{#paper-button class="pull-right" action="delete" primary=true}}Delete{{/paper-button}}
|
||||
|
||||
{{/modal-dialog}}
|
||||
{{/if}}
|
||||
78
app/pods/components/groups-list/component.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import Em from 'ember';
|
||||
|
||||
export default Em.Component.extend({
|
||||
classNames: ['dropdown-menu'],
|
||||
elementId: 'groupList',
|
||||
|
||||
tagName: null,
|
||||
|
||||
groupIdSelection: null,
|
||||
|
||||
actions: {
|
||||
selectGroup(selection){
|
||||
this.set('groupIdSelection', selection);
|
||||
},
|
||||
toggleConfirmDeleteGroupsModal(groupName, groupId){
|
||||
this.setProperties({
|
||||
deleteGroupName: groupName,
|
||||
deleteGroupId: groupId
|
||||
});
|
||||
this.toggleProperty('isShowingConfirmDeleteModal');
|
||||
},
|
||||
toggleAddGroupsModal(){
|
||||
this.toggleProperty('isShowingAddGroupsModal');
|
||||
}
|
||||
},
|
||||
|
||||
groupsArrData: function(){
|
||||
var groupsData = this.get('groupsData'), lightsData = this.get('lightsData'), groupsArrData = [], ids = [], groupIdSelection = this.get('groupIdSelection');
|
||||
|
||||
for (let key in lightsData) {
|
||||
if(lightsData.hasOwnProperty(key) && lightsData[key].state.reachable){
|
||||
ids.push(key);
|
||||
}
|
||||
}
|
||||
groupsArrData.push({name: 'All', data: {lights: ids, key: '0' }, rowClass: groupIdSelection === '0' ? 'groupRow selectedRow' : 'groupRow', deletable: false});
|
||||
|
||||
for (let key in groupsData) {
|
||||
if (groupsData.hasOwnProperty(key)) {
|
||||
var rowClass = 'groupRow';
|
||||
|
||||
if(key === groupIdSelection){
|
||||
rowClass += ' selectedRow';
|
||||
}
|
||||
|
||||
groupsArrData.push({name: groupsData[key].name, data: {lights: groupsData[key].lights, key: key}, rowClass: rowClass, deletable: true});
|
||||
}
|
||||
}
|
||||
|
||||
return groupsArrData;
|
||||
}.property('groupsData', 'groupIdSelection'),
|
||||
|
||||
onGroupIdSelectionChanged: function(){
|
||||
var groupIdSelection = this.get('groupIdSelection'), lights = [];
|
||||
|
||||
this.get('groupsArrData').some(function(group){
|
||||
if(group.data.key === groupIdSelection){
|
||||
lights = group.data.lights;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
this.get('storage').set('huegasm.selectedGroup', groupIdSelection);
|
||||
|
||||
if(!Em.isNone(groupIdSelection) && !Em.isEmpty(lights)){
|
||||
this.set('activeLights', lights);
|
||||
}
|
||||
}.observes('groupIdSelection', 'groupsArrData'),
|
||||
|
||||
didInsertElement(){
|
||||
var selectGroup = '0', storageItem = this.get('storage').get('huegasm.selectedGroup');
|
||||
|
||||
if(storageItem){
|
||||
selectGroup = storageItem;
|
||||
}
|
||||
|
||||
this.set('groupIdSelection', selectGroup);
|
||||
}
|
||||
});
|
||||
15
app/pods/components/groups-list/template.hbs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{{#paper-list}}
|
||||
{{#paper-item class="newGroupRow"}}
|
||||
<div class="newGroup" {{action "toggleAddGroupsModal"}}>{{paper-icon icon="group-add"}} Add a new group</div>
|
||||
{{/paper-item}}
|
||||
|
||||
{{#each groupsArrData as |group|}}
|
||||
{{#paper-item class=group.rowClass}}
|
||||
<div class="groupSelect" {{action "selectGroup" group.data.key}}>{{group.name}}</div> {{#if group.deletable}}<span data-toggle="tooltip" data-placement="top auto" title="Remove Group" class="bootstrapTooltip removeButton cursorPointer" {{action "toggleConfirmDeleteGroupsModal" group.name group.data.key}}>{{paper-icon icon="close"}}</span>{{/if}}
|
||||
{{/paper-item}}
|
||||
{{/each}}
|
||||
{{/paper-list}}
|
||||
|
||||
{{add-group-modal lightsData=lightsData groupsData=groupsData isShowingModal=isShowingAddGroupsModal apiURL=apiURL updateGroupsData=updateGroupsData action="toggleAddGroupsModal"}}
|
||||
|
||||
{{delete-group-modal groupName=deleteGroupName groupId=deleteGroupId groupsData=groupsData isShowingModal=isShowingConfirmDeleteModal apiURL=apiURL updateGroupsData=updateGroupsData groupIdSelection=groupIdSelection action="toggleConfirmDeleteGroupsModal"}}
|
||||
280
app/pods/components/hue-controls/component.js
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
import Em from 'ember';
|
||||
|
||||
export default Em.Component.extend({
|
||||
classNames: ['container-fluid'],
|
||||
elementId: 'hueControls',
|
||||
|
||||
bridgeIp: null,
|
||||
manualBridgeIp: null,
|
||||
bridgeUsername: null,
|
||||
|
||||
updateGroupsData: true,
|
||||
groupsData: null,
|
||||
lightsData: null,
|
||||
|
||||
activeLights: [],
|
||||
|
||||
actions: {
|
||||
changeTab(tabName){
|
||||
var index = this.get('tabList').indexOf(tabName);
|
||||
this.set('selectedTab', index);
|
||||
this.get('storage').set('huegasm.selectedTab', index);
|
||||
},
|
||||
clearBridge() {
|
||||
var storage = this.get('storage');
|
||||
storage.remove('huegasm.bridgeUsername');
|
||||
storage.remove('huegasm.bridgeIp');
|
||||
location.reload();
|
||||
},
|
||||
clearAllSettings() {
|
||||
this.get('storage').clear();
|
||||
location.reload();
|
||||
},
|
||||
startIntro(){
|
||||
var INTRO = introJs,
|
||||
intro = INTRO(),
|
||||
playerBottom = Em.$('#playerBottom'),
|
||||
beatDetectionAreaArrowIcon = Em.$('#beatDetectionAreaArrowIcon');
|
||||
|
||||
this.set('dimmerOn', false);
|
||||
|
||||
intro.setOptions({
|
||||
steps: [
|
||||
{
|
||||
intro: 'Welcome! This short tutorial will introduce you to Huegasm.'
|
||||
},
|
||||
{
|
||||
element: '#musicTab',
|
||||
intro: 'This is the music player. You\'ll use this to play music and synchronize it with your active lights.<br><br>' +
|
||||
'<i><b>TIP</b>: Control which lights are active through the <b>Lights</b> tab or through the <b>Groups</b> menu dropdown.</i>'
|
||||
},
|
||||
{
|
||||
element: '#playlist',
|
||||
intro: 'You can add and select music to play from your playlist here. You may listen to local audio files, stream music from soundcloud or stream directly from a connected microphone.<br><br>' +
|
||||
'<i><b>TIP</b>: Songs added through Soundcloud will be saved for when you visit this page again.</i>'
|
||||
},
|
||||
{
|
||||
element: '#playerArea',
|
||||
intro: 'The audio playback may be controlled with the controls here. Basic music visualization effects may be shown here by selecting them from the menu ( eyeball icon in the bottom right ).'
|
||||
},
|
||||
{
|
||||
element: '#beatOptionRow',
|
||||
intro: 'These are the settings for the music tab:<br>' +
|
||||
'<b>Sensitivity</b> - The sensitivity of the beat detector ( more sensitivity results in more registered beats )<br>' +
|
||||
'<b>Hue Range</b> - The hue range that the lights may change to on beat.<br>' +
|
||||
'<b>Flashing Transitions</b> - Quickly flash the lights on beat<br>' +
|
||||
'<b>Colorloop</b> - Slowly cycle the lights through all the colors while the music is playing<br>' +
|
||||
'<i><b>TIP</b>: Your sensitivity settings are saved per song as indicated by the red star icon in the top left corner. These settings they will be restored if you ever listen to the same song again.</i>',
|
||||
position: 'top'
|
||||
},
|
||||
{
|
||||
element: '#beatContainer',
|
||||
intro: 'An interactive speaker that will bump when a beat is registered. <br><br>' +
|
||||
'<i><b>TIP</b>: Click on the center of the speaker to simulate a beat.</i>',
|
||||
position: 'top'
|
||||
},
|
||||
{
|
||||
element: '#lightsTab',
|
||||
intro: 'This is the lights tab. Here you\'ll be able to change various light properties:<br>' +
|
||||
'<b>Power</b> - Turn the selected lights on/off<br>' +
|
||||
'<b>Brightness</b> - The brightness level of the selected lights<br>' +
|
||||
'<b>Color</b> - The color of the selected lights<br>' +
|
||||
'<b>Strobe</b> - Selected lights will flash in sequential order<br>' +
|
||||
'<b>Colorloop</b> - Selected lights will slowly cycle through all the colors<br>'
|
||||
},
|
||||
{
|
||||
element: '#activeLights',
|
||||
intro: 'These icons represent the hue lights in your system. Active lights will be controlled by the application while the inactive lights will have a red X over them and will not be controlled.<br>' +
|
||||
'You may toggle a light\'s state by clicking on it.'
|
||||
},
|
||||
{
|
||||
element: Em.$('.settingsItem')[0],
|
||||
intro: 'The Groups menu allows for saving and quickly selecting groups of lights.',
|
||||
position: 'left'
|
||||
},
|
||||
{
|
||||
element: Em.$('.settingsItem')[1],
|
||||
intro: 'A few miscellaneous settings can be found here.<br><br>' +
|
||||
'<b>WARNING</b>: clearing application settings will restore the application to its original state. This will even delete your playlist and any saved song beat preferences.',
|
||||
position: 'left'
|
||||
},
|
||||
{
|
||||
element: '#dimmer',
|
||||
intro: 'And that\'s it...Hope you enjoy the application. ;)<br><br>' +
|
||||
'<i><b>TIP</b>: click on the icon to switch to a darker theme.</i>',
|
||||
position: 'top'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// it's VERY ugly but it works
|
||||
intro.onchange((element) => {
|
||||
if(element.id === 'musicTab' || element.id === 'playlist' || element.id === 'playerArea' || element.id === 'beatOptionRow' || element.id === 'beatOptionButtonGroup' || element.id === 'beatContainer' || element.id === 'usingMicAudioTooltip'){
|
||||
Em.$('#musicTab').removeClass('hidden');
|
||||
Em.$('#lightsTab').addClass('hidden');
|
||||
Em.$('.navigationItem').eq(0).removeClass('active');
|
||||
Em.$('.navigationItem').eq(1).addClass('active');
|
||||
} else {
|
||||
Em.$('#lightsTab').removeClass('hidden');
|
||||
Em.$('#musicTab').addClass('hidden');
|
||||
Em.$('.navigationItem').eq(1).removeClass('active');
|
||||
Em.$('.navigationItem').eq(0).addClass('active');
|
||||
}
|
||||
|
||||
if(element.id === 'musicTab' || element.id === 'playlist' || element.id === 'playerArea'){
|
||||
playerBottom.hide();
|
||||
|
||||
if(beatDetectionAreaArrowIcon.hasClass('keyboard-arrow-up')){
|
||||
beatDetectionAreaArrowIcon.removeClass('keyboard-arrow-up').addClass('keyboard-arrow-down');
|
||||
}
|
||||
} else if(element.id === 'beatOptionRow' || element.id === 'beatOptionButtonGroup' || element.id === 'beatContainer'){
|
||||
playerBottom.show();
|
||||
|
||||
if(beatDetectionAreaArrowIcon.hasClass('keyboard-arrow-down')){
|
||||
beatDetectionAreaArrowIcon.removeClass('keyboard-arrow-down').addClass('keyboard-arrow-up');
|
||||
}
|
||||
} else if(element.id === 'dimmer'){
|
||||
Em.$(document).click();
|
||||
}
|
||||
});
|
||||
|
||||
var onFinish = ()=>{
|
||||
this.set('activeTab', 1);
|
||||
Em.$('#musicTab').removeClass('hidden');
|
||||
Em.$('#lightsTab').addClass('hidden');
|
||||
Em.$('.navigationItem').eq(0).removeClass('active');
|
||||
Em.$('.navigationItem').eq(1).addClass('active');
|
||||
|
||||
if(beatDetectionAreaArrowIcon.hasClass('keyboard-arrow-up')){
|
||||
playerBottom.show();
|
||||
} else {
|
||||
playerBottom.hide();
|
||||
}
|
||||
}, onExit = ()=>{
|
||||
var dimmer = Em.$('#dimmer');
|
||||
|
||||
onFinish();
|
||||
dimmer.popover({
|
||||
trigger: 'manual',
|
||||
placement: 'top',
|
||||
content: 'Click on this icon to toggle the dark theme.'
|
||||
}).popover('show');
|
||||
|
||||
setTimeout(()=>{
|
||||
dimmer.popover('hide');
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
// skip hidden/missing elements
|
||||
intro.onafterchange((element)=>{
|
||||
var elem = Em.$(element);
|
||||
if(elem.html() === '<!---->'){
|
||||
Em.$('.introjs-nextbutton').click();
|
||||
}
|
||||
}).onexit(onExit).oncomplete(onFinish).start();
|
||||
}
|
||||
},
|
||||
|
||||
apiURL: function(){
|
||||
return 'http://' + this.get('bridgeIp') + '/api/' + this.get('bridgeUsername');
|
||||
}.property('bridgeIp', 'bridgeUsername'),
|
||||
|
||||
didInsertElement(){
|
||||
// here's a weird way to automatically initialize bootstrap tooltips
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
var haveTooltip = !mutations.every(function(mutation) {
|
||||
return Em.isEmpty(mutation.addedNodes) || Em.isNone(mutation.addedNodes[0].classList) || mutation.addedNodes[0].classList.contains('tooltip');
|
||||
});
|
||||
|
||||
if(haveTooltip) {
|
||||
Em.run.once(this, function(){
|
||||
Em.$('.bootstrapTooltip').tooltip();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(Em.$('#hueControls')[0], {childList: true, subtree: true});
|
||||
},
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
|
||||
if(!this.get('trial')) {
|
||||
this.doUpdateGroupsData();
|
||||
this.updateLightData();
|
||||
this.set('lightsDataIntervalHandle', setInterval(this.updateLightData.bind(this), 2000));
|
||||
}
|
||||
|
||||
if (!Em.isNone(this.get('storage').get('huegasm.selectedTab'))) {
|
||||
this.set('selectedTab', this.get('storage').get('huegasm.selectedTab'));
|
||||
}
|
||||
},
|
||||
|
||||
onUpdateGroupsDataChange: function(){
|
||||
if(this.get('updateGroupsData')){
|
||||
setTimeout(()=>{ this.doUpdateGroupsData(); }, 1000);
|
||||
}
|
||||
}.observes('updateGroupsData'),
|
||||
|
||||
doUpdateGroupsData(){
|
||||
Em.$.get(this.get('apiURL') + '/groups', (result, status)=>{
|
||||
if (status === 'success' ) {
|
||||
this.set('groupsData', result);
|
||||
}
|
||||
});
|
||||
|
||||
this.toggleProperty('updateGroupsData');
|
||||
},
|
||||
|
||||
tabList: ["Lights", "Music"],
|
||||
selectedTab: 1,
|
||||
tabData: function(){
|
||||
var tabData = [], selectedTab = this.get('selectedTab');
|
||||
|
||||
this.get('tabList').forEach(function(tab, i){
|
||||
var selected = false;
|
||||
|
||||
if(i === selectedTab){
|
||||
selected = true;
|
||||
}
|
||||
|
||||
tabData.push({"name": tab, "selected": selected });
|
||||
});
|
||||
|
||||
return tabData;
|
||||
}.property('tabList', 'selectedTab'),
|
||||
|
||||
lightsTabSelected: Em.computed.equal('selectedTab', 0),
|
||||
musicTabSelected: Em.computed.equal('selectedTab', 1),
|
||||
|
||||
pauseLightUpdates: false,
|
||||
|
||||
updateLightData(){
|
||||
var fail = ()=>{
|
||||
clearInterval(this.get('lightsDataIntervalHandle'));
|
||||
|
||||
this.get('storage').remove('huegasm.bridgeIp');
|
||||
this.get('storage').remove('huegasm.bridgeUsername');
|
||||
|
||||
location.reload();
|
||||
};
|
||||
|
||||
if(!this.get('pauseLightUpdates')){
|
||||
Em.$.get(this.get('apiURL') + '/lights', (result, status)=>{
|
||||
if(!Em.isNone(result[0]) && !Em.isNone(result[0].error)){
|
||||
fail();
|
||||
} else if (status === 'success' && JSON.stringify(this.get('lightsData')) !== JSON.stringify(result)) {
|
||||
this.set('lightsData', result);
|
||||
}
|
||||
}).fail(fail);
|
||||
}
|
||||
},
|
||||
|
||||
dimmerOnClass: function(){
|
||||
return this.get('dimmerOn') ? 'dimmerOn' : null;
|
||||
}.property('dimmerOn'),
|
||||
|
||||
ready: function() {
|
||||
return this.get('trial') || !Em.isNone(this.get('lightsData'));
|
||||
}.property('lightsData', 'trial')
|
||||
});
|
||||
34
app/pods/components/hue-controls/template.hbs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{{#if ready}}
|
||||
<div class="row navigation">
|
||||
<div class="col-sm-4 col-sm-offset-4 col-xs-8">
|
||||
{{#each tabData as |tab|}}
|
||||
<span class="navigationItem cursorPointer {{if tab.selected "active"}} text-uppercase" {{action "changeTab" tab.name}}>{{tab.name}}</span>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<div id="settings" class="col-xs-4">
|
||||
<div class="settingsItem">
|
||||
<span data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{paper-icon icon="group" class=dimmerOnClass}}Groups <span class="caret"></span>
|
||||
</span>
|
||||
|
||||
{{groups-list lightsData=lightsData groupsData=groupsData activeLights=activeLights apiURL=apiURL updateGroupsData=updateGroupsData groupControlDisplayed=groupControlDisplayed storage=storage}}
|
||||
</div>
|
||||
|
||||
<div class="settingsItem">
|
||||
<span data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" id="settingsItemSettings">
|
||||
{{paper-icon icon="settings" class=dimmerOnClass}}Settings <span class="caret"></span>
|
||||
</span>
|
||||
|
||||
<ul id="appSettings" class="dropdown-menu">
|
||||
<li {{action "clearBridge"}}><a href="#">Switch bridge</a></li>
|
||||
<li {{action "startIntro"}}><a href="#">Restart tutorial</a></li>
|
||||
<li {{action "clearAllSettings"}}><a href="#">Reset settings</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{lights-tab apiURL=apiURL lightsData=lightsData activeLights=activeLights trial=trial active=lightsTabSelected colorLoopOn=colorLoopOn dimmerOn=dimmerOn}}
|
||||
|
||||
{{music-tab apiURL=apiURL lightsData=lightsData activeLights=activeLights active=musicTabSelected pauseLightUpdates=pauseLightUpdates dimmerOn=dimmerOn storage=storage colorLoopOn=colorLoopOn action="startIntro"}}
|
||||
{{/if}}
|
||||
64
app/pods/components/huegasm-app/component.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import Em from 'ember';
|
||||
|
||||
export default Em.Component.extend({
|
||||
actions: {
|
||||
toggleDimmer(){
|
||||
this.toggleProperty('dimmerOn');
|
||||
},
|
||||
isReady(){
|
||||
this.set('ready', true);
|
||||
}
|
||||
},
|
||||
bridgeIp: null,
|
||||
|
||||
bridgeUsername: null,
|
||||
|
||||
trial: false,
|
||||
|
||||
storage: null,
|
||||
|
||||
dimmerOn: false,
|
||||
|
||||
ready: false,
|
||||
|
||||
dimmerOnClass: function () {
|
||||
var dimmerOn = this.get('dimmerOn'),
|
||||
storage = this.get('storage'),
|
||||
dimmerOnClass = null;
|
||||
|
||||
if (dimmerOn) {
|
||||
Em.$('body').addClass('dimmerOn');
|
||||
Em.$('html').addClass('dimmerOn');
|
||||
dimmerOnClass = 'active';
|
||||
} else {
|
||||
Em.$('body').removeClass('dimmerOn');
|
||||
Em.$('html').removeClass('dimmerOn');
|
||||
}
|
||||
|
||||
storage.set('huegasm.dimmerOn', dimmerOn);
|
||||
|
||||
return dimmerOnClass;
|
||||
}.property('dimmerOn'),
|
||||
|
||||
init(){
|
||||
this._super();
|
||||
|
||||
var storage = new window.Locally.Store({compress: true});
|
||||
this.set('storage', storage);
|
||||
|
||||
if (!Em.isNone(storage.get('huegasm.dimmerOn'))) {
|
||||
this.set('dimmerOn', storage.get('huegasm.dimmerOn'));
|
||||
}
|
||||
|
||||
if (!Em.isEmpty(storage.get('huegasm.bridgeIp')) && !Em.isEmpty(storage.get('huegasm.bridgeUsername'))) {
|
||||
this.setProperties({
|
||||
bridgeIp: storage.get('huegasm.bridgeIp'),
|
||||
bridgeUsername: storage.get('huegasm.bridgeUsername')
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
year: function () {
|
||||
return new Date().getFullYear();
|
||||
}.property()
|
||||
});
|
||||
24
app/pods/components/huegasm-app/template.hbs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{{#if bridgeUsername}}
|
||||
{{hue-controls bridgeIp=bridgeIp bridgeUsername=bridgeUsername trial=trial dimmerOn=dimmerOn storage=storage}}
|
||||
{{else}}
|
||||
{{#if ready}}
|
||||
{{bridge-finder bridgeIp=bridgeIp bridgeUsername=bridgeUsername trial=trial storage=storage}}
|
||||
{{else}}
|
||||
<div class="readyBlock">
|
||||
<div class="title"><img src="assets/images/logo.png" alt="Huegasm"></div>
|
||||
<p id="intro">Your lights, meet your music. Huegasm.</p>
|
||||
<p id="introParagraph">Huegasm is a free web application for managing and synchronizing your <a target="_blank" href="http://www2.meethue.com">Philips Hue lights</a> with the beat of your music.</p>
|
||||
<div class="embedContainerWrapper">
|
||||
<div class="embedContainer">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/zi9J6Qg-MPw" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
{{#paper-button raised=true primary=true action="isReady" class="goButton center-block"}}Go!{{/paper-button}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<footer id="footer">
|
||||
<p><span class="relative"><span id="dimmer" {{action "toggleDimmer"}} class={{dimmerOnClass}}>
|
||||
</span>© {{year}} <a href="http://egorphilippov.me" target="_blank">Egor Philippov</a></span></p>
|
||||
</footer>
|
||||
126
app/pods/components/light-group/component.js
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import Em from 'ember';
|
||||
|
||||
export default Em.Component.extend({
|
||||
|
||||
classNames: ['lightGroup'],
|
||||
|
||||
isHovering: false,
|
||||
|
||||
lightsList: Em.A(),
|
||||
|
||||
actions: {
|
||||
clickLight(id, data){
|
||||
var light = Em.$('.light'+id);
|
||||
|
||||
if(!light.hasClass('bootstrapTooltip')){
|
||||
light = light.parent();
|
||||
}
|
||||
|
||||
if(light.hasClass('lightInactive')){
|
||||
light.addClass('lightActive').removeClass('lightInactive');
|
||||
} else if(light.hasClass('lightActive')){
|
||||
light.addClass('lightInactive').removeClass('lightActive');
|
||||
}
|
||||
|
||||
this.sendAction('action', id, data);
|
||||
},
|
||||
lightStartHover(id){
|
||||
var hoveredLight = this.get('lightsList').filter(function(light){
|
||||
return light.activeClass !== 'unreachable' && light.id === id[0];
|
||||
});
|
||||
|
||||
if(!Em.isEmpty(hoveredLight) && this.get('noHover') !== true){
|
||||
Em.$.ajax(this.get('apiURL') + '/lights/' + id + '/state', {
|
||||
data: JSON.stringify({"alert": "lselect"}),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
}
|
||||
|
||||
this.set('isHovering', true);
|
||||
},
|
||||
lightStopHover(id){
|
||||
var hoveredLight = this.get('lightsList').filter(function(light){
|
||||
return light.activeClass !== 'unreachable' && light.id === id[0];
|
||||
});
|
||||
|
||||
if(!Em.isEmpty(hoveredLight) && this.get('noHover') !== true){
|
||||
Em.$.ajax(this.get('apiURL') + '/lights/' + id + '/state', {
|
||||
data: JSON.stringify({"alert": "none"}),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
}
|
||||
|
||||
this.set('isHovering', false);
|
||||
this.onLightsDataChange();
|
||||
}
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
if(this.get('lightsData')){
|
||||
this.onLightsDataChange();
|
||||
}
|
||||
},
|
||||
|
||||
// list of all the lights in the hue system
|
||||
onLightsDataChange: function(){
|
||||
if(!this.get('isHovering')){
|
||||
var lightsData = this.get('lightsData'), lightsList = Em.A(), type;
|
||||
for (var key in lightsData) {
|
||||
if (lightsData.hasOwnProperty(key) && lightsData[key].state.reachable) {
|
||||
switch(lightsData[key].modelid){
|
||||
case 'LCT001':
|
||||
type = 'a19';
|
||||
break;
|
||||
case 'LCT002':
|
||||
type = 'br30';
|
||||
break;
|
||||
case 'LCT003':
|
||||
type = 'gu10';
|
||||
break;
|
||||
case 'LST001':
|
||||
type = 'lightstrip';
|
||||
break;
|
||||
case 'LLC010':
|
||||
type = 'lc_iris';
|
||||
break;
|
||||
case 'LLC011':
|
||||
type = 'lc_bloom';
|
||||
break;
|
||||
case 'LLC012':
|
||||
type = 'lc_bloom';
|
||||
break;
|
||||
case 'LLC006':
|
||||
type = 'lc_iris';
|
||||
break;
|
||||
case 'LLC007':
|
||||
type = 'lc_aura';
|
||||
break;
|
||||
case 'LLC013':
|
||||
type = 'storylight';
|
||||
break;
|
||||
case 'LWB004':
|
||||
type ='a19';
|
||||
break;
|
||||
case 'LLC020':
|
||||
type = 'huego';
|
||||
break;
|
||||
default:
|
||||
type = 'a19';
|
||||
}
|
||||
|
||||
var activeClass = 'lightActive';
|
||||
|
||||
if(!this.get('activeLights').contains(key)){
|
||||
activeClass = 'lightInactive';
|
||||
}
|
||||
|
||||
lightsList.push({type: type, name: lightsData[key].name, id: key, data: lightsData[key], activeClass: activeClass});
|
||||
}
|
||||
}
|
||||
|
||||
this.set('lightsList', lightsList);
|
||||
}
|
||||
}.observes('lightsData', 'activeLights.[]', 'dimmerOn')
|
||||
});
|
||||
5
app/pods/components/light-group/template.hbs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{{#each lightsList as |light|}}
|
||||
<div class="{{light.activeClass}} bootstrapTooltip light{{light.id}}" data-toggle="tooltip" data-placement="top auto" data-title={{light.name}} {{action "clickLight" light.id light.data}} {{action "lightStartHover" light.id on="mouseEnter"}} {{action "lightStopHover" light.id on="mouseLeave"}}>
|
||||
<img class="hueLight" width="40" src="assets/images/lights/{{light.type}}{{if dimmerOn "w"}}.svg">
|
||||
</div>
|
||||
{{/each}}
|
||||
358
app/pods/components/lights-tab/component.js
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
import Em from 'ember';
|
||||
|
||||
export default Em.Component.extend({
|
||||
classNames: ['col-sm-8', 'col-sm-offset-2', 'col-xs-12'],
|
||||
classNameBindings: ['active::hidden'],
|
||||
elementId: 'lightsTab',
|
||||
|
||||
activeLights: [],
|
||||
lightsData: null,
|
||||
|
||||
lightsDataIntervalHandle: null,
|
||||
|
||||
colorPickerDisplayed: false,
|
||||
|
||||
actions: {
|
||||
clickLight(light){
|
||||
var activeLights = this.get('activeLights'),
|
||||
lightId = activeLights.indexOf(light);
|
||||
|
||||
if(lightId !== -1){
|
||||
activeLights.removeObject(light);
|
||||
} else {
|
||||
activeLights.pushObject(light);
|
||||
|
||||
// sync the current light settings to the newly added light
|
||||
var options = {on: this.get('lightsOn'), bri: this.get('lightsBrightness'), effect: this.get('colorLoopOn') ? 'colorloop' : 'none'},
|
||||
rgb = this.get('rgb');
|
||||
|
||||
if(rgb[0] !== 255 && rgb[1] !== 255 && rgb[2] !== 255) {
|
||||
options['xy'] = this.rgbToXy(rgb[0], rgb[1], rgb[2]);
|
||||
}
|
||||
|
||||
options['transitiontime'] = 0;
|
||||
|
||||
Em.$.ajax(this.get('apiURL') + '/lights/' + light + '/state', {
|
||||
data: JSON.stringify(options),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
}
|
||||
},
|
||||
toggleColorpicker() {
|
||||
this.toggleProperty('colorPickerDisplayed');
|
||||
}
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
Em.$(document).click((event)=>{
|
||||
if(this.get('colorPickerDisplayed') && !event.target.classList.contains('color') && !Em.$(event.target).closest('.colorpicker, #colorRow').length) {
|
||||
this.toggleProperty('colorPickerDisplayed');
|
||||
}
|
||||
});
|
||||
|
||||
Em.$(document).on('click', '#colorRow', () => {
|
||||
this.send('toggleColorpicker');
|
||||
});
|
||||
},
|
||||
|
||||
rgb: [255, 255, 255],
|
||||
rgbPreview: function() {
|
||||
var rgb = this.get('rgb'),
|
||||
xy = this.rgbToXy(rgb[0], rgb[1], rgb[2]);
|
||||
|
||||
this.set('colorLoopOn', false);
|
||||
|
||||
this.get('activeLights').forEach((light) => {
|
||||
Em.$.ajax(this.get('apiURL') + '/lights/' + light + '/state', {
|
||||
data: JSON.stringify({"xy": xy}),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
});
|
||||
|
||||
this.set('colorLoopOn', false);
|
||||
Em.$('.color').css('background', 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')');
|
||||
}.observes('rgb'),
|
||||
|
||||
colorRowAction: function() {
|
||||
if (this.get('trial')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'toggleColorpicker';
|
||||
}.property('trial'),
|
||||
|
||||
// COLOR LOOP related stuff
|
||||
colorLoopOn: false,
|
||||
|
||||
onColorLoopOnChange: function(){
|
||||
var lightsData = this.get('lightsData'),
|
||||
activeLights = this.get('activeLights'),
|
||||
colorLoopsOn = this.get('colorLoopOn'),
|
||||
effect = colorLoopsOn ? 'colorloop' : 'none';
|
||||
|
||||
var colorLoopsOnSystem = activeLights.some(function(light) {
|
||||
return lightsData[light].state.effect === 'colorloop';
|
||||
});
|
||||
|
||||
// if the internal lights state is different than the one from lightsData ( user manually toggled the switch ), send the request to change the bulbs state
|
||||
if(colorLoopsOn !== colorLoopsOnSystem){
|
||||
activeLights.forEach((light)=>{
|
||||
if(this.get('lightsData')[light].state.effect !== effect) {
|
||||
Em.$.ajax(this.get('apiURL') + '/lights/' + light + '/state', {
|
||||
data: JSON.stringify({'effect': effect }),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}.observes('colorLoopOn'),
|
||||
|
||||
lightsOn: false,
|
||||
|
||||
// determines whether the lights are on/off for the lights switch
|
||||
lightsOnCHange: function(){
|
||||
if(!this.get('strobeOn')){
|
||||
var lightsData = this.get('lightsData'), lightsOn = this.get('activeLights').some(function(light) {
|
||||
return lightsData[light].state.on === true;
|
||||
});
|
||||
|
||||
this.set('lightsOn', lightsOn);
|
||||
}
|
||||
}.observes('lightsData.@each.state.on', 'activeLights.[]'),
|
||||
|
||||
// determines the average brightness of the hue system for the brightness slider
|
||||
lightsBrightness: function(){
|
||||
var lightsData = this.get('lightsData'), activeLights = this.get('activeLights'), lightsBrightness = 0;
|
||||
|
||||
activeLights.forEach(function(light){
|
||||
lightsBrightness += lightsData[light].state.bri;
|
||||
});
|
||||
|
||||
return lightsBrightness/activeLights.length;
|
||||
}.property('lightsData'),
|
||||
|
||||
brightnessControlDisabled: Em.computed.not('lightsOn'),
|
||||
|
||||
onLightsOnChange: function(){
|
||||
var lightsData = this.get('lightsData'), activeLights = this.get('activeLights'), lightsOn = this.get('lightsOn'), self = this;
|
||||
|
||||
var lightsOnSystem = activeLights.some(function(light) {
|
||||
return lightsData[light].state.on === true;
|
||||
});
|
||||
|
||||
// if the internal lights state is different than the one from lightsData ( user manually toggled the switch ), send the request to change the bulbs state
|
||||
if(lightsOn !== lightsOnSystem){
|
||||
activeLights.forEach(function (light) {
|
||||
Em.$.ajax(self.get('apiURL') + '/lights/' + light + '/state', {
|
||||
data: JSON.stringify({"on": lightsOn}),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
});
|
||||
}
|
||||
}.observes('lightsOn'),
|
||||
|
||||
onBrightnessChanged: function(){
|
||||
var lightsData = this.get('lightsData'), lightsBrightnessSystem = false, lightsBrightness = this.get('lightsBrightness'), activeLights = this.get('activeLights'), self = this;
|
||||
|
||||
activeLights.forEach(function(light){
|
||||
lightsBrightnessSystem += lightsData[light].state.bri;
|
||||
});
|
||||
|
||||
lightsBrightnessSystem /= activeLights.length;
|
||||
|
||||
// if the internal lights state is different than the one from lightsData ( user manually toggled the switch ), send the request to change the bulbs state
|
||||
if(lightsBrightness !== lightsBrightnessSystem){
|
||||
activeLights.forEach(function(light){
|
||||
Em.$.ajax(self.get('apiURL') + '/lights/' + light + '/state', {
|
||||
data: JSON.stringify({"bri": lightsBrightness}),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
});
|
||||
}
|
||||
}.observes('lightsBrightness'),
|
||||
|
||||
lightsOnTxt: function(){
|
||||
return this.get('lightsOn') ? 'On' : 'Off';
|
||||
}.property('lightsOn'),
|
||||
|
||||
colorloopOnTxt: function(){
|
||||
return this.get('colorLoopOn') ? 'On' : 'Off';
|
||||
}.property('colorLoopOn'),
|
||||
|
||||
// **************** STROBE LIGHT START ****************
|
||||
|
||||
strobeOn: false,
|
||||
|
||||
strobeOnInervalHandle: null,
|
||||
strobeSat: 0,
|
||||
preStrobeOnLightsDataCache: null,
|
||||
lastStrobeLight: 0,
|
||||
|
||||
onStrobeOnChange: function () {
|
||||
var lightsData = this.get('lightsData'), self = this;
|
||||
|
||||
if (this.get('strobeOn')) {
|
||||
this.set('preStrobeOnLightsDataCache', lightsData);
|
||||
var stobeInitRequestData = {'sat': this.get('strobeSat'), 'transitiontime': 0};
|
||||
|
||||
for (let key in lightsData) {
|
||||
if (lightsData.hasOwnProperty(key)) {
|
||||
if (lightsData[key].state.on) {
|
||||
stobeInitRequestData.on = false;
|
||||
}
|
||||
|
||||
Em.$.ajax(this.get('apiURL') + '/lights/' + key + '/state', {
|
||||
data: JSON.stringify(stobeInitRequestData),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.set('strobeOnInervalHandle', setInterval(this.strobeStep.bind(this), 200));
|
||||
} else { // revert the light system to pre-strobe
|
||||
var preStrobeOnLightsDataCache = this.get('preStrobeOnLightsDataCache'), updateLight = function (lightIndx) {
|
||||
Em.$.ajax(self.get('apiURL') + '/lights/' + lightIndx + '/state', {
|
||||
data: JSON.stringify({
|
||||
'on': preStrobeOnLightsDataCache[lightIndx].state.on,
|
||||
'sat': preStrobeOnLightsDataCache[lightIndx].state.sat
|
||||
}),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
};
|
||||
|
||||
for (let key in lightsData) {
|
||||
if (lightsData.hasOwnProperty(key)) {
|
||||
setTimeout(updateLight, 2000, key);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(()=>{this.onColorLoopOnChange();}, 2000);
|
||||
clearInterval(this.get('strobeOnInervalHandle'));
|
||||
}
|
||||
}.observes('strobeOn'),
|
||||
|
||||
strobeStep() {
|
||||
var lastStrobeLight = (this.get('lastStrobeLight') + 1) % (this.get('activeLights').length + 1),
|
||||
turnOnOptions = {'on': true, 'transitiontime': 0, 'alert': 'select'};
|
||||
|
||||
// random light if in cololoop mode
|
||||
if(this.get('colorLoopOn')) {
|
||||
turnOnOptions.hue = Math.floor(Math.random() * 65535);
|
||||
}
|
||||
|
||||
Em.$.ajax(this.get('apiURL') + '/lights/' + lastStrobeLight + '/state', {
|
||||
data: JSON.stringify(turnOnOptions),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
Em.$.ajax(this.get('apiURL') + '/lights/' + lastStrobeLight + '/state', {
|
||||
data: JSON.stringify({'on': false, 'transitiontime': 0}),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
|
||||
this.set('lastStrobeLight', lastStrobeLight);
|
||||
},
|
||||
|
||||
strobeOnTxt: function () {
|
||||
return this.get('strobeOn') ? 'On' : 'Off';
|
||||
}.property('strobeOn'),
|
||||
|
||||
dimmerOnClass: function(){
|
||||
return this.get('dimmerOn') ? 'dimmerOn' : null;
|
||||
}.property('dimmerOn'),
|
||||
|
||||
// **************** STROBE LIGHT FINISH ****************
|
||||
// http://www.developers.meethue.com/documentation/color-conversions-rgb-xy
|
||||
rgbToXy(red, green, blue){
|
||||
var X, Y, Z, x, y;
|
||||
|
||||
// normalize
|
||||
red = Number((red/255));
|
||||
green = Number((green/255));
|
||||
blue = Number((blue/255));
|
||||
|
||||
// gamma correction
|
||||
red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92);
|
||||
green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92);
|
||||
blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92);
|
||||
|
||||
// RGB to XYZ
|
||||
X = red * 0.664511 + green * 0.154324 + blue * 0.162028;
|
||||
Y = red * 0.283881 + green * 0.668433 + blue * 0.047685;
|
||||
Z = red * 0.000088 + green * 0.072310 + blue * 0.986039;
|
||||
|
||||
x = X / (X + Y + Z);
|
||||
y = Y / (X + Y + Z);
|
||||
|
||||
return [x,y];
|
||||
},
|
||||
|
||||
xyToRgb(x, y){
|
||||
var r, g, b, X, Y = 1.0, Z;
|
||||
|
||||
X = (Y / y) * x;
|
||||
Z = (Y / y) * (1 - x - y);
|
||||
|
||||
r = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
|
||||
g = X * -0.707196 + Y * 1.655397 + Z * 0.036152;
|
||||
b = X * 0.051713 - Y * 0.121364 + Z * 1.011530;
|
||||
|
||||
if (r > b && r > g && r > 1.0) {
|
||||
// red is too big
|
||||
g = g / r;
|
||||
b = b / r;
|
||||
r = 1.0;
|
||||
} else if (g > b && g > r && g > 1.0) {
|
||||
// green is too big
|
||||
r = r / g;
|
||||
b = b / g;
|
||||
g = 1.0;
|
||||
} else if (b > r && b > g && b > 1.0) {
|
||||
// blue is too big
|
||||
r = r / b;
|
||||
g = g / b;
|
||||
b = 1.0;
|
||||
}
|
||||
|
||||
r = (r <= 0.0031308) ? 12.92 * r : 1.055 * Math.pow(r, (1.0 / 2.4)) - 0.055;
|
||||
g = (g <= 0.0031308) ? 12.92 * g : 1.055 * Math.pow(g, (1.0 / 2.4)) - 0.055;
|
||||
b = (b <= 0.0031308) ? 12.92 * b : 1.055 * Math.pow(b, (1.0 / 2.4)) - 0.055;
|
||||
|
||||
if (r > b && r > g) {
|
||||
// red is biggest
|
||||
if (r > 1.0) {
|
||||
g = g / r;
|
||||
b = b / r;
|
||||
r = 1.0;
|
||||
}
|
||||
} else if (g > b && g > r) {
|
||||
// green is biggest
|
||||
if (g > 1.0) {
|
||||
r = r / g;
|
||||
b = b / g;
|
||||
g = 1.0;
|
||||
}
|
||||
} else if (b > r && b > g) {
|
||||
// blue is biggest
|
||||
if (b > 1.0) {
|
||||
r = r / b;
|
||||
g = g / b;
|
||||
b = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
r = r * 255;
|
||||
g = g * 255;
|
||||
b = b * 255;
|
||||
|
||||
return [r, g, b];
|
||||
}
|
||||
});
|
||||
42
app/pods/components/lights-tab/template.hbs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{{#paper-list}}
|
||||
{{#paper-item class="item"}}
|
||||
{{light-group lightsData=lightsData activeLights=activeLights action='clickLight' apiURL=apiURL classNames="horizontalLightGroup" dimmerOn=dimmerOn id="activeLights"}}
|
||||
{{/paper-item}}
|
||||
|
||||
{{#paper-item}}
|
||||
{{paper-icon icon="power-settings-new" class=dimmerOnClass}}
|
||||
<p data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip lightCtrlTooltip" data-title="Turn the selected lights on/off">Power</p>
|
||||
{{#paper-switch checked=lightsOn disabled=trial skipProxy=trial}} {{lightsOnTxt}} {{/paper-switch}}
|
||||
{{/paper-item}}
|
||||
|
||||
{{#paper-item}}
|
||||
{{paper-icon icon="brightness-4" class=dimmerOnClass}}
|
||||
<p data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip lightCtrlTooltip" data-title="The brightness level of the selected lights">Brightness</p>
|
||||
{{paper-slider flex=true min='1' max='254' value=lightsBrightness disabled=brightnessControlDisabled}}
|
||||
{{/paper-item}}
|
||||
|
||||
{{#paper-item elementId="colorRow"}}
|
||||
{{paper-icon icon="color-lens" class=dimmerOnClass}}
|
||||
<p data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip lightCtrlTooltip" data-title="The color of the selected lights">Color</p>
|
||||
{{/paper-item}}
|
||||
|
||||
<div class="relative">
|
||||
{{#paper-button raised=true class="color" action="toggleColorpicker" disabled=trial}}{{/paper-button}}
|
||||
|
||||
{{#if colorPickerDisplayed}}
|
||||
{{color-picker lightsData=lightsData activeLights=activeLights rgb=rgb}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#paper-item}}
|
||||
{{paper-icon icon="flare" class=dimmerOnClass}}
|
||||
<p data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip lightCtrlTooltip" data-title="Selected lights will flash in sequential order">Strobe</p>
|
||||
{{#paper-switch checked=strobeOn disabled=trial skipProxy=trial}} {{strobeOnTxt}} {{/paper-switch}}
|
||||
{{/paper-item}}
|
||||
|
||||
{{#paper-item}}
|
||||
{{paper-icon icon="color-lens" class=dimmerOnClass}} {{paper-icon icon="loop" id="loopAddition" class=dimmerOnClass}}
|
||||
<p data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip lightCtrlTooltip" data-title="Selected lights will slowly cycle through all the colors">Colorloop</p>
|
||||
{{#paper-switch checked=colorLoopOn disabled=trial skipProxy=trial}} {{colorloopOnTxt}} {{/paper-switch}}
|
||||
{{/paper-item}}
|
||||
{{/paper-list}}
|
||||
883
app/pods/components/music-tab/component.js
Normal file
|
|
@ -0,0 +1,883 @@
|
|||
import Em from 'ember';
|
||||
import helperMixin from './mixins/helpers';
|
||||
import visualizerMixin from './mixins/visualizer';
|
||||
|
||||
export default Em.Component.extend(helperMixin, visualizerMixin, {
|
||||
onActiveChange: function(){
|
||||
if(this.get('active')){
|
||||
Em.$('#playNotification').removeClass('fadeOut');
|
||||
Em.$('#beatSpeakerCenterOuter').removeClass('vibrateOuter');
|
||||
Em.$('#beatSpeakerCenterInner').removeClass('vibrateInner');
|
||||
}
|
||||
}.observes('active'),
|
||||
|
||||
actions: {
|
||||
clearPlaylist(){
|
||||
this.get('playQueue').clear();
|
||||
},
|
||||
setVisName(name){
|
||||
this.set('currentVisName', name);
|
||||
},
|
||||
hideTooltip(){
|
||||
Em.$('.bootstrapTooltip').tooltip('hide');
|
||||
},
|
||||
gotoSCURL(URL){
|
||||
// need to pause the music since soundcloud is going to start playing this song anyways
|
||||
if(this.get('playing')){
|
||||
this.send('play');
|
||||
}
|
||||
|
||||
this.send('gotoURL', URL);
|
||||
},
|
||||
gotoURL(URL){
|
||||
Em.$('.tooltip').remove();
|
||||
window.open(URL, '_blank');
|
||||
},
|
||||
handleNewSoundCloudURL(URL){
|
||||
if(URL) {
|
||||
SC.resolve(URL).then((resultObj)=>{
|
||||
var processResult = (result)=>{
|
||||
if(result.kind === 'user'){
|
||||
this.get('notify').alert({html: this.get('scUserNotSupportedHtml')});
|
||||
} else if(result.kind === 'track') {
|
||||
if(result.streamable === true){
|
||||
var picture = null;
|
||||
|
||||
if(result.artwork_url){
|
||||
picture = result.artwork_url.replace('large', 't67x67');
|
||||
} else if(result.user.avatar_url){
|
||||
picture = result.user.avatar_url;
|
||||
}
|
||||
|
||||
this.get('playQueue').pushObject({url: result.stream_url + '?client_id=' + this.get('SC_CLIENT_ID'), fileName: result.title + ' - ' + result.user.username, artist: result.user.username, scUrl: result.permalink_url, title: result.title, artworkUrl: result.artwork_url, picture: picture });
|
||||
} else {
|
||||
failedSongs.push(result.title);
|
||||
}
|
||||
} else if(result.kind === 'playlist'){
|
||||
if(result.streamable === true){
|
||||
result.tracks.forEach(processResult);
|
||||
} else {
|
||||
failedSongs.push(result.title);
|
||||
}
|
||||
}
|
||||
},
|
||||
failedSongs = [];
|
||||
|
||||
if(resultObj instanceof Array){
|
||||
resultObj.forEach(processResult);
|
||||
} else {
|
||||
processResult(resultObj);
|
||||
}
|
||||
|
||||
if(failedSongs.length > 0) {
|
||||
this.get('notify').alert({html: this.get('notStreamableHtml')(failedSongs)});
|
||||
}
|
||||
|
||||
if(this.get('playQueuePointer') === -1){
|
||||
if(this.get('firstVisit')){
|
||||
this.send('goToSong', 0);
|
||||
} else {
|
||||
this.send('next');
|
||||
}
|
||||
}
|
||||
}, () => {
|
||||
this.get('notify').alert({html: this.get('urlNotFoundHtml')(URL)});
|
||||
});
|
||||
}
|
||||
|
||||
this.set('isShowingAddSoundCloudModal', false);
|
||||
},
|
||||
toggleIsShowingAddSoundCloudModal() {
|
||||
this.toggleProperty('isShowingAddSoundCloudModal');
|
||||
},
|
||||
useLocalAudio(){
|
||||
var audioStream = this.get('audioStream');
|
||||
this.changePlayerControl('audioMode', 0);
|
||||
|
||||
if(!Em.isNone(audioStream)){
|
||||
var tracks = audioStream.getVideoTracks();
|
||||
if (tracks && tracks[0] && tracks[0].stop) {
|
||||
tracks[0].stop();
|
||||
}
|
||||
|
||||
if (audioStream.stop) {
|
||||
// deprecated, may be removed in future
|
||||
audioStream.stop();
|
||||
}
|
||||
|
||||
this.setProperties({
|
||||
audioStream: null,
|
||||
playing: false
|
||||
});
|
||||
}
|
||||
|
||||
if(this.get('playQueuePointer') !== -1) {
|
||||
this.send('goToSong', this.get('playQueuePointer'));
|
||||
this.send('volumeChanged', this.get('volume'));
|
||||
}
|
||||
|
||||
// restore the old beat preferences ( before the user went into mic mode )
|
||||
if(!Em.isNone(this.get('oldThreshold'))){
|
||||
this.set('threshold', this.get('oldThreshold'));
|
||||
}
|
||||
|
||||
document.title = 'Huegasm';
|
||||
},
|
||||
useMicAudio() {
|
||||
if(this.get('usingMicAudio')) {
|
||||
this.send('useLocalAudio');
|
||||
} else {
|
||||
this.startUsingMic();
|
||||
}
|
||||
},
|
||||
slideTogglePlayerBottom(){
|
||||
this.$('#playerBottom').slideToggle();
|
||||
this.changePlayerControl('playerBottomDisplayed', !this.get('playerBottomDisplayed'));
|
||||
},
|
||||
goToSong(index, playSong, scrollToSong){
|
||||
var dancer = this.get('dancer'), playQueue = this.get('playQueue');
|
||||
|
||||
if(dancer.audio) {
|
||||
this.clearCurrentAudio(true);
|
||||
}
|
||||
|
||||
if(!Em.isNone(playQueue[index])) {
|
||||
var audio = new Audio();
|
||||
audio.src = this.get('playQueue')[index].url;
|
||||
|
||||
audio.crossOrigin = "anonymous";
|
||||
audio.oncanplay = ()=>{
|
||||
this.set('timeTotal', Math.floor(audio.duration));
|
||||
this.set('soundCloudFuckUps', 0);
|
||||
};
|
||||
audio.onerror = (event)=>{
|
||||
var playQueuePointer =this.get('playQueuePointer'),
|
||||
song = this.get('playQueue')[playQueuePointer];
|
||||
|
||||
if(this.get('soundCloudFuckUps') >= this.get('maxSoundCloudFuckUps')) {
|
||||
this.get('notify').alert({html: this.get('tooManySoundCloudFuckUps')});
|
||||
this.send('play');
|
||||
this.set('soundCloudFuckUps', 0);
|
||||
} else {
|
||||
if(song.local){
|
||||
this.send('removeAudio', playQueuePointer);
|
||||
} else {
|
||||
this.send('next', true);
|
||||
}
|
||||
|
||||
if(event.target.error.code === 2){
|
||||
this.get('notify').alert({html: this.get('failedToDecodeFileHtml')(song.fileName)});
|
||||
} else {
|
||||
this.get('notify').alert({html: this.get('failedToPlayFileHtml')(song.fileName)});
|
||||
}
|
||||
|
||||
this.set('usingBeatPreferences', false);
|
||||
this.incrementProperty('soundCloudFuckUps');
|
||||
}
|
||||
};
|
||||
audio.ontimeupdate = ()=>{
|
||||
this.set('timeElapsed', Math.floor(audio.currentTime));
|
||||
};
|
||||
audio.onended = ()=> {
|
||||
this.send('next');
|
||||
};
|
||||
|
||||
dancer.load(audio, 1);
|
||||
|
||||
this.set('playQueuePointer', index);
|
||||
|
||||
this.loadSongBeatPreferences();
|
||||
|
||||
if(playSong){
|
||||
this.send('play');
|
||||
}
|
||||
|
||||
if(scrollToSong){
|
||||
// this is just a bad workaround to make sure that the track has been rendered to the playlist
|
||||
Em.run.later(()=>{
|
||||
var track = Em.$('.track'+index), playListArea = Em.$('#playListArea');
|
||||
|
||||
if(!Em.isNone(track) && !Em.isNone(track.offset())) {
|
||||
playListArea.animate({
|
||||
scrollTop: track.offset().top - playListArea.offset().top + playListArea.scrollTop()
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
},
|
||||
removeAudio(index){
|
||||
this.get('playQueue').removeAt(index);
|
||||
|
||||
// need to manually remove the tooltip
|
||||
Em.$('body .tooltip').remove();
|
||||
|
||||
if(index === this.get('playQueuePointer')) {
|
||||
this.send('goToSong', index, true, true);
|
||||
}
|
||||
},
|
||||
playerAreaPlay(){
|
||||
if(Em.isEmpty(Em.$('#playerControls:hover')) && this.get('playQueuePointer') !== -1 ){
|
||||
this.send('play');
|
||||
this.set('fadeOutNotification', true);
|
||||
Em.$('#playNotification').removeClass('fadeOut').prop('offsetWidth', Em.$('#playNotification').prop('offsetWidth')).addClass('fadeOut');
|
||||
}
|
||||
},
|
||||
play(replayPause) {
|
||||
var dancer = this.get('dancer'),
|
||||
playQueuePointer = this.get('playQueuePointer');
|
||||
|
||||
if(playQueuePointer !== -1 ) {
|
||||
if (this.get('playing')) {
|
||||
dancer.pause();
|
||||
|
||||
if(!replayPause){
|
||||
this.set('timeElapsed', Math.floor(dancer.getTime()));
|
||||
}
|
||||
} else {
|
||||
var timeTotal = this.get('timeTotal');
|
||||
|
||||
if(this.get('volumeMuted')) {
|
||||
dancer.setVolume(0);
|
||||
} else {
|
||||
dancer.setVolume(this.get('volume')/100);
|
||||
}
|
||||
|
||||
// replay song
|
||||
if(this.get('timeElapsed') === timeTotal && timeTotal !== 0){
|
||||
this.send('next', true);
|
||||
return;
|
||||
}
|
||||
|
||||
Em.$(window).trigger('resize'); // workaround to redraw the canvas for the vitualizer
|
||||
|
||||
dancer.play();
|
||||
}
|
||||
|
||||
this.onColorloopModeChange();
|
||||
this.toggleProperty('playing');
|
||||
}
|
||||
},
|
||||
volumeChanged(value) {
|
||||
this.changePlayerControl('volume', value);
|
||||
if(this.get('playing')) {
|
||||
this.get('dancer').setVolume(value/100);
|
||||
}
|
||||
|
||||
if(this.get('volume') > 0 && this.get('volumeMuted')){
|
||||
this.changePlayerControl('volumeMuted', false);
|
||||
}
|
||||
},
|
||||
next(repeatAll) {
|
||||
var playQueuePointer = this.get('playQueuePointer'),
|
||||
playQueue = this.get('playQueue'),
|
||||
nextSong = (playQueuePointer + 1),
|
||||
repeat = this.get('repeat'),
|
||||
shuffle = this.get('shuffle');
|
||||
|
||||
if(repeat === 2){ // repeating one song takes precedence over shuffling
|
||||
if(playQueuePointer === -1 && playQueue.length > 0) {
|
||||
nextSong = 0;
|
||||
} else {
|
||||
nextSong = playQueuePointer;
|
||||
}
|
||||
} else if(shuffle){ // next shuffle song
|
||||
var shufflePlayed = this.get('shufflePlayed');
|
||||
|
||||
// played all the song in shuffle mode
|
||||
if(shufflePlayed.length === playQueue.length){
|
||||
shufflePlayed.clear();
|
||||
this.send('play', true);
|
||||
return;
|
||||
}
|
||||
|
||||
// we're going to assume that the song URL is the id
|
||||
do {
|
||||
nextSong = Math.floor(Math.random() * playQueue.length);
|
||||
} while(shufflePlayed.contains(playQueue[nextSong].url));
|
||||
|
||||
shufflePlayed.pushObject(playQueue[nextSong].url);
|
||||
} else if(nextSong > playQueue.length-1){
|
||||
if(repeat === 1 || repeatAll){
|
||||
nextSong = nextSong % playQueue.length;
|
||||
} else {
|
||||
this.send('play', true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.send('goToSong', nextSong, true, true);
|
||||
},
|
||||
previous() {
|
||||
if(this.get('timeElapsed') > 5) {
|
||||
this.send('seekChanged', 0);
|
||||
} else {
|
||||
var nextSong = this.get('playQueuePointer'),
|
||||
playQueue = this.get('playQueue');
|
||||
|
||||
if(this.get('shuffle') && !Em.isNone(playQueue[nextSong])) { // go to the previously shuffled song
|
||||
var shufflePlayed = this.get('shufflePlayed'),
|
||||
shuffledSongIndx = this.get('shufflePlayed').indexOf(playQueue[nextSong].url),
|
||||
i = 0;
|
||||
|
||||
if(shufflePlayed.length > 0 && shuffledSongIndx !== -1){ // only if there was one
|
||||
nextSong = shuffledSongIndx - 1;
|
||||
|
||||
if(nextSong < 0){
|
||||
nextSong = shufflePlayed.length - 1;
|
||||
}
|
||||
|
||||
playQueue.some(function(item){ // try to find the previous song id
|
||||
if(item.url === shufflePlayed[nextSong]){
|
||||
nextSong = i;
|
||||
return true;
|
||||
}
|
||||
i++;
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
nextSong--;
|
||||
|
||||
if(nextSong < 0) {
|
||||
nextSong = playQueue.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.send('goToSong', nextSong, true, true);
|
||||
}
|
||||
},
|
||||
seekChanged(position) {
|
||||
var dancer = this.get('dancer');
|
||||
|
||||
if(dancer.audio){
|
||||
dancer.audio.currentTime = Math.floor(this.get('timeTotal') * position / 100);
|
||||
}
|
||||
},
|
||||
volumeMutedChanged(value) {
|
||||
var dancer = this.get('dancer'),
|
||||
volumeMuted = Em.isNone(value) ? !this.get('volumeMuted') : value;
|
||||
|
||||
this.changePlayerControl('volumeMuted', volumeMuted);
|
||||
|
||||
if(this.get('playing')){
|
||||
if(volumeMuted){
|
||||
dancer.setVolume(0);
|
||||
} else {
|
||||
dancer.setVolume(this.get('volume')/100);
|
||||
}
|
||||
}
|
||||
},
|
||||
addLocalAudio: function () {
|
||||
Em.$('#fileInput').click();
|
||||
},
|
||||
shuffleChanged(value) {
|
||||
this.changePlayerControl('shuffle', Em.isNone(value) ? !this.get('shuffle') : value);
|
||||
},
|
||||
repeatChanged(value) {
|
||||
this.changePlayerControl('repeat', Em.isNone(value) ? (this.get('repeat') + 1) % 3 : value);
|
||||
},
|
||||
playerBottomDisplayedChanged(value) {
|
||||
this.changePlayerControl('playerBottomDisplayed', value);
|
||||
},
|
||||
thresholdChanged(value) {
|
||||
this.changePlayerControl('threshold', value, true);
|
||||
},
|
||||
hueRangeChanged(value) {
|
||||
this.changePlayerControl('hueRange', value);
|
||||
},
|
||||
micBoostChanged(value) {
|
||||
this.set('micBoost', value);
|
||||
this.get('storage').set('huegasm.micBoost', value);
|
||||
|
||||
if(this.get('usingMicAudio')) {
|
||||
this.get('dancer').setBoost(value);
|
||||
}
|
||||
},
|
||||
audioModeChanged(value){
|
||||
if(value === 1) {
|
||||
this.startUsingMic();
|
||||
} else if(value === 0) {
|
||||
this.send('useLocalAudio');
|
||||
} else {
|
||||
this.set('audioMode', value);
|
||||
}
|
||||
},
|
||||
playQueuePointerChanged(value){
|
||||
this.send('goToSong', value, false, true);
|
||||
},
|
||||
clickSpeaker(){
|
||||
this.simulateKick(1);
|
||||
},
|
||||
dropFiles(files){
|
||||
this.setProperties({
|
||||
dragging: false,
|
||||
draggingOverPlayListArea: false
|
||||
});
|
||||
this.send('handleNewFiles', files);
|
||||
},
|
||||
playListAreaDragOver(){
|
||||
this.set('draggingOverPlayListArea', true);
|
||||
},
|
||||
playListAreaDragLeave(){
|
||||
this.set('draggingOverPlayListArea', false);
|
||||
},
|
||||
handleNewFiles(files){
|
||||
var self = this,
|
||||
playQueue = this.get('playQueue'),
|
||||
updatePlayQueue = function(){
|
||||
var tags = ID3.getAllTags("local"),
|
||||
picture = null;
|
||||
|
||||
if(tags.picture){
|
||||
var base64String = "";
|
||||
for (var i = 0; i < tags.picture.data.length; i++) {
|
||||
base64String += String.fromCharCode(tags.picture.data[i]);
|
||||
}
|
||||
|
||||
picture = "data:" + tags.picture.format + ";base64," + window.btoa(base64String);
|
||||
}
|
||||
|
||||
playQueue.pushObject({fileName: this.name.replace(/\.[^/.]+$/, ""), url: URL.createObjectURL(this), artist: tags.artist, title: tags.title, picture: picture, local: true });
|
||||
|
||||
ID3.clearAll();
|
||||
|
||||
if(self.get('playQueuePointer') === -1){
|
||||
self.send('next');
|
||||
}
|
||||
};
|
||||
|
||||
for (var key in files) {
|
||||
if (files.hasOwnProperty(key)) {
|
||||
var file = files[key];
|
||||
|
||||
if(file.type.startsWith('audio')) {
|
||||
ID3.loadTags("local", updatePlayQueue.bind(file),{
|
||||
dataReader: new FileAPIReader(file),
|
||||
tags: ['title', 'artist', 'album', 'track', 'picture']
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
changePlayerControl(name, value, saveBeatPrefs){
|
||||
this.set(name, value);
|
||||
|
||||
if(name === 'threshold'){
|
||||
this.get('kick').set({threshold: value});
|
||||
}
|
||||
|
||||
if(saveBeatPrefs && this.get('usingLocalAudio') && this.get('playQueuePointer') !== -1){
|
||||
this.saveSongBeatPreferences();
|
||||
}
|
||||
|
||||
this.get('storage').set('huegasm.' + name, value);
|
||||
},
|
||||
|
||||
saveSongBeatPreferences() {
|
||||
var song = this.get('playQueue')[this.get('playQueuePointer')];
|
||||
if(song) {
|
||||
var title = Em.isEmpty(song.artist) ? song.fileName : song.artist + '-' + song.title,
|
||||
songBeatPreferences = this.get('songBeatPreferences');
|
||||
|
||||
songBeatPreferences[title] = {threshold: this.get('threshold')};
|
||||
|
||||
this.set('usingBeatPreferences', true);
|
||||
this.get('storage').set('huegasm.songBeatPreferences', songBeatPreferences);
|
||||
}
|
||||
},
|
||||
|
||||
loadSongBeatPreferences() {
|
||||
var song = this.get('playQueue')[this.get('playQueuePointer')],
|
||||
title = Em.isEmpty(song.artist) ? song.fileName : song.artist + '-' + song.title,
|
||||
songBeatPreferences = this.get('songBeatPreferences'),
|
||||
preference = songBeatPreferences[title],
|
||||
oldBeatPrefCache = this.get('oldBeatPrefCache'),
|
||||
newOldBeatPrefCache = null;
|
||||
|
||||
if(!Em.isNone(preference)) { // load existing beat prefs
|
||||
newOldBeatPrefCache = {threshold: this.get('threshold')};
|
||||
|
||||
this.changePlayerControl('threshold', preference.threshold);
|
||||
this.set('usingBeatPreferences', true);
|
||||
} else if(!Em.isNone(oldBeatPrefCache)) { // revert to using beat prefs before the remembered song
|
||||
this.changePlayerControl('threshold', oldBeatPrefCache.threshold);
|
||||
this.set('usingBeatPreferences', false);
|
||||
}
|
||||
|
||||
this.set('oldBeatPrefCache', newOldBeatPrefCache);
|
||||
},
|
||||
|
||||
doAmbienceLightChange: function(justOneLight){
|
||||
var activeLights = this.get('activeLights'),
|
||||
lightsData = this.get('lightsData'),
|
||||
workedLights = this.get('ambienceWorkedLights'),
|
||||
hueRange = this.get('hueRange'),
|
||||
ambienceWorkedLightsHandles = this.get('ambienceWorkedLightsHandles'),
|
||||
lightOff = (light)=>{
|
||||
if(this.get('ambienceMode') && this.get('playing')){
|
||||
Em.$.ajax(this.get('apiURL') + '/lights/' + light + '/state', {
|
||||
data: JSON.stringify({'on': false, 'transitiontime': 20}),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
}
|
||||
},
|
||||
lights = [],
|
||||
transitionTime = Math.floor(Math.random()*20),
|
||||
iterations = justOneLight ? 1 : activeLights.length/2;
|
||||
|
||||
// pick some random lights
|
||||
for(let i=0; i < iterations; i++){
|
||||
let l = activeLights[Math.floor(Math.random()*activeLights.length)];
|
||||
|
||||
if(!lights.contains(l) && !workedLights.contains(l)){
|
||||
lights.push(l);
|
||||
workedLights.push(l);
|
||||
} else if(justOneLight && workedLights.length !== activeLights.length){ // work a light if we only need one
|
||||
while(workedLights.contains(l)){
|
||||
l = activeLights[Math.floor(Math.random()*activeLights.length)];
|
||||
}
|
||||
|
||||
lights.push(l);
|
||||
workedLights.push(l);
|
||||
}
|
||||
}
|
||||
|
||||
lights.forEach((light)=>{
|
||||
var options = {'hue': Math.floor(Math.random()*(hueRange[1] - hueRange[0] + 1)+hueRange[0]), 'bri': Math.floor(Math.random()*200) + 1, 'transitiontime': transitionTime};
|
||||
|
||||
if(lightsData[light].state.on === false){
|
||||
options.on = true;
|
||||
}
|
||||
|
||||
Em.$.ajax(this.get('apiURL') + '/lights/' + light + '/state', {
|
||||
data: JSON.stringify(options),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
|
||||
// stop the light from turning off
|
||||
if(ambienceWorkedLightsHandles[light]){
|
||||
clearTimeout(ambienceWorkedLightsHandles[light]);
|
||||
delete ambienceWorkedLightsHandles[light];
|
||||
}
|
||||
|
||||
// turn the light off after it's been idle for a while
|
||||
ambienceWorkedLightsHandles[light] = setTimeout(()=>{
|
||||
lightOff(light);
|
||||
workedLights.removeObject(light);
|
||||
delete ambienceWorkedLightsHandles[light];
|
||||
}, transitionTime * 100 + 1000);
|
||||
});
|
||||
},
|
||||
|
||||
onAmbienceModeChange: function() {
|
||||
if(this.get('ambienceMode') && this.get('playing')) {
|
||||
this.set('ambienceModeHandle', setInterval(()=> {this.doAmbienceLightChange();}, 5000));
|
||||
this.setProperties({
|
||||
'colorloopMode': false,
|
||||
'flashingTransitions': false
|
||||
});
|
||||
} else if(this.get('ambienceModeHandle')) {
|
||||
this.get('activeLights').forEach((light)=>{
|
||||
Em.$.ajax(this.get('apiURL') + '/lights/' + light + '/state', {
|
||||
data: JSON.stringify({'on': true}),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
});
|
||||
|
||||
clearInterval(this.get('ambienceModeHandle'));
|
||||
this.set('ambienceModeHandle', null);
|
||||
}
|
||||
}.observes('ambienceMode', 'playing'),
|
||||
|
||||
startUsingMic() {
|
||||
navigator.getUserMedia(
|
||||
{audio: true},
|
||||
(stream) => {
|
||||
this.changePlayerControl('audioMode', 1);
|
||||
var dancer = this.get('dancer');
|
||||
|
||||
if(dancer.audio && dancer.audio.pause) {
|
||||
dancer.pause();
|
||||
}
|
||||
|
||||
this.setProperties({
|
||||
volumeCache: this.get('volume'),
|
||||
playing: true,
|
||||
audioStream: stream
|
||||
});
|
||||
|
||||
document.title = 'Listening to Mic - Huegasm';
|
||||
|
||||
dancer.load(stream, this.get('micBoost'), true);
|
||||
this.set('usingBeatPreferences', false);
|
||||
|
||||
// much more sensitive beat preference settings are needed for mic mode
|
||||
this.setProperties({
|
||||
oldThreshold: this.get('threshold'),
|
||||
threshold: 0.1
|
||||
});
|
||||
|
||||
dancer.setVolume(0);
|
||||
},
|
||||
(err) => {
|
||||
if(err.name === 'DevicesNotFoundError'){
|
||||
this.get('notify').alert({html: this.get('notFoundHtml')});
|
||||
}
|
||||
|
||||
console.log('Error during navigator.getUserMedia: ' + err.name + ', ' + err.message + ', ' + err.constraintName);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
updatePageTitle: function(){
|
||||
var title = 'Huegasm', playQueuePointer = this.get('playQueuePointer'), playQueue = this.get('playQueue');
|
||||
|
||||
if(playQueuePointer !== -1){
|
||||
var song = playQueue[playQueuePointer];
|
||||
if(song.title){
|
||||
title = song.title;
|
||||
|
||||
if(song.artist){
|
||||
title += (' - ' + song.artist);
|
||||
}
|
||||
} else {
|
||||
title = song.fileName;
|
||||
}
|
||||
|
||||
title += '- Huegasm';
|
||||
}
|
||||
|
||||
document.title = title;
|
||||
}.observes('playQueuePointer'),
|
||||
|
||||
clearCurrentAudio(resetPointer) {
|
||||
var dancer = this.get('dancer');
|
||||
|
||||
if(dancer.audio.pause) {
|
||||
dancer.pause();
|
||||
}
|
||||
|
||||
if(resetPointer){
|
||||
this.set('playQueuePointer', -1);
|
||||
}
|
||||
|
||||
this.setProperties({
|
||||
timeElapsed: 0,
|
||||
timeTotal: 0,
|
||||
playing: false
|
||||
});
|
||||
},
|
||||
|
||||
dragOver() {
|
||||
var dragLeaveTimeoutHandle = this.get('dragLeaveTimeoutHandle');
|
||||
this.set('dragging', true);
|
||||
|
||||
if (dragLeaveTimeoutHandle) {
|
||||
clearTimeout(dragLeaveTimeoutHandle);
|
||||
}
|
||||
},
|
||||
|
||||
dragLeave(){
|
||||
// need to delay the dragLeave notification to avoid flickering ( hovering over some page elements causes this event to be sent )
|
||||
var self = this;
|
||||
this.set('dragLeaveTimeoutHandle', setTimeout(function(){ self.set('dragging', false); }, 500));
|
||||
},
|
||||
|
||||
simulateKick(/*mag, ratioKickMag*/) {
|
||||
var activeLights = this.get('activeLights'),
|
||||
lightsData = this.get('lightsData'),
|
||||
color = null,
|
||||
transitiontime = this.get('flashingTransitions'),
|
||||
stimulateLight = (light, brightness, hue) => {
|
||||
var options = {'bri': brightness};
|
||||
|
||||
if(transitiontime) {
|
||||
options['transitiontime'] = 0;
|
||||
} else {
|
||||
options['transitiontime'] = 1;
|
||||
}
|
||||
|
||||
if(!Em.isNone(hue)) {
|
||||
options.hue = hue;
|
||||
}
|
||||
|
||||
if(lightsData[light].state.on === false){
|
||||
options.on = true;
|
||||
}
|
||||
|
||||
Em.$.ajax(this.get('apiURL') + '/lights/' + light + '/state', {
|
||||
data: JSON.stringify(options),
|
||||
contentType: 'application/json',
|
||||
type: 'PUT'
|
||||
});
|
||||
},
|
||||
timeToBriOff = 100;
|
||||
|
||||
if(activeLights.length > 0 && !this.get('ambienceMode')){
|
||||
var lastLightBopIndex = this.get('lastLightBopIndex'),
|
||||
lightBopIndex,
|
||||
brightnessOnBeat = 254,
|
||||
light;
|
||||
|
||||
lightBopIndex = Math.floor(Math.random() * activeLights.length);
|
||||
|
||||
// let's try not to select the same light twice in a row
|
||||
if(activeLights.length > 1) {
|
||||
while(lightBopIndex === lastLightBopIndex) {
|
||||
lightBopIndex = Math.floor(Math.random() * activeLights.length);
|
||||
}
|
||||
}
|
||||
|
||||
light = activeLights[lightBopIndex];
|
||||
this.set('lastLightBopIndex', lightBopIndex);
|
||||
|
||||
if(!this.get('colorloopMode')) {
|
||||
var hueRange = this.get('hueRange');
|
||||
|
||||
color = Math.floor(Math.random()*(hueRange[1] - hueRange[0] + 1)+hueRange[0]);
|
||||
}
|
||||
|
||||
if(transitiontime){
|
||||
timeToBriOff = 80;
|
||||
}
|
||||
|
||||
stimulateLight(light, brightnessOnBeat, color);
|
||||
setTimeout(stimulateLight, timeToBriOff, light, 1);
|
||||
}
|
||||
|
||||
this.set('paused', true);
|
||||
setTimeout(() => {
|
||||
this.set('paused', false);
|
||||
}, 150);
|
||||
|
||||
if(this.get('ambienceMode') && activeLights.length > 0){
|
||||
this.doAmbienceLightChange(true);
|
||||
}
|
||||
|
||||
//work the music beat area - simulate the speaker vibration by running a CSS animation on it
|
||||
Em.$('#beatSpeakerCenterOuter').removeClass('vibrateOuter').prop('offsetWidth', Em.$('#beatSpeakerCenterOuter').prop('offsetWidth')).addClass('vibrateOuter');
|
||||
Em.$('#beatSpeakerCenterInner').removeClass('vibrateInner').prop('offsetWidth', Em.$('#beatSpeakerCenterInner').prop('offsetWidth')).addClass('vibrateInner');
|
||||
},
|
||||
|
||||
init() {
|
||||
this._super();
|
||||
|
||||
window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame;
|
||||
window.cancelAnimationFrame = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.msCancelAnimationFrame;
|
||||
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
|
||||
|
||||
var dancer = new Dancer(),
|
||||
storage = this.get('storage'),
|
||||
kick = dancer.createKick({
|
||||
threshold: this.get('threshold'),
|
||||
onKick: (mag, ratioKickMag) => {
|
||||
if (this.get('paused') === false) {
|
||||
this.simulateKick(mag, ratioKickMag);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
kick.on();
|
||||
|
||||
this.setProperties({
|
||||
dancer: dancer,
|
||||
kick: kick
|
||||
});
|
||||
|
||||
if(navigator.getUserMedia === undefined){
|
||||
this.set('usingMicSupported', false);
|
||||
}
|
||||
|
||||
['volume', 'shuffle', 'repeat', 'volumeMuted', 'threshold', 'playerBottomDisplayed', 'audioMode', 'songBeatPreferences', 'firstVisit', 'currentVisName', 'playQueue', 'playQueuePointer', 'micBoost', 'flashingTransitions', 'colorloopMode', 'ambienceMode', 'hueRange'].forEach((item)=>{
|
||||
if (!Em.isNone(storage.get('huegasm.' + item))) {
|
||||
var itemVal = storage.get('huegasm.' + item);
|
||||
|
||||
if(Em.isNone(this.actions[item+'Changed'])){
|
||||
this.set(item, itemVal);
|
||||
} else {
|
||||
this.send(item + 'Changed', itemVal);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
SC.initialize({
|
||||
client_id: this.get('SC_CLIENT_ID')
|
||||
});
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
|
||||
var self = this;
|
||||
|
||||
// file input code
|
||||
Em.$('#fileInput').on('change', function () {
|
||||
var files = this.files;
|
||||
self.send('handleNewFiles', files);
|
||||
this.value = null; // reset in case upload the second file again
|
||||
});
|
||||
|
||||
Em.$(document).on('click', '.alert', (event)=>{
|
||||
Em.$(event.target).addClass('removed');
|
||||
});
|
||||
|
||||
// prevent space/text selection when the user repeatedly clicks on the center
|
||||
Em.$('#beatContainer').on('mousedown', '#beatSpeakerCenterInner', function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
Em.$(document).keypress((event) => {
|
||||
if(event.which === 32 && event.target.type !== 'text'){
|
||||
this.send('play');
|
||||
}
|
||||
});
|
||||
|
||||
this.$().on('drop', '#playListArea', (event)=>{
|
||||
this.send('dropFiles', event.dataTransfer.files);
|
||||
});
|
||||
|
||||
// control the volume by scrolling up/down
|
||||
Em.$('#playerArea').on('mousewheel', (event)=>{
|
||||
if(this.get('playQueueNotEmpty') && !this.get('usingMicAudio')) {
|
||||
var scrollSize = 5;
|
||||
|
||||
if(event.deltaY < 0) {
|
||||
scrollSize *= -1;
|
||||
}
|
||||
var newVolume = this.get('volume') + scrollSize;
|
||||
|
||||
this.send('volumeChanged', newVolume < 0 ? 0 : newVolume);
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// demo tracks
|
||||
if(this.get('firstVisit')){
|
||||
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/candyland-speechless-feat-rkcb');
|
||||
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/vallis-alps-young-feki-remix');
|
||||
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/andrew-luce-when-to-love-you-feat-chelsea-cutler');
|
||||
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/ahh-ooh-carefree-with-me');
|
||||
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/odesza/light-feat-little-dragon');
|
||||
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/crywolf-slow-burn');
|
||||
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/clozee-red-forest');
|
||||
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/elo-method-subranger-solace');
|
||||
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/90-pounds-of-pete-waited-too-long-feat-devon-baldwin');
|
||||
this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/draper-eyes-open');
|
||||
|
||||
this.get('storage').set('huegasm.firstVisit', false);
|
||||
|
||||
this.sendAction();
|
||||
}
|
||||
|
||||
if(!this.get('playerBottomDisplayed')) {
|
||||
Em.$('#playerBottom').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
406
app/pods/components/music-tab/mixins/helpers.js
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
import Em from 'ember';
|
||||
|
||||
export default Em.Mixin.create({
|
||||
classNames: ['col-sm-10', 'col-sm-offset-1', 'col-xs-12'],
|
||||
classNameBindings: ['active::hidden'],
|
||||
elementId: 'musicTab',
|
||||
|
||||
dancer: null,
|
||||
|
||||
notify: Em.inject.service('notify'),
|
||||
|
||||
beatOptions: {
|
||||
threshold: {
|
||||
range: {min: 0, max: 0.5},
|
||||
step: 0.01,
|
||||
defaultValue: 0.3,
|
||||
pips: {
|
||||
mode: 'values',
|
||||
values: [0, 0.25, 0.5],
|
||||
density: 10,
|
||||
format: {
|
||||
to: function ( value ) {
|
||||
if(value === 0) {
|
||||
value = 'More';
|
||||
} else if(value === 0.25) {
|
||||
value = '';
|
||||
} else {
|
||||
value = 'Less';
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
from: function ( value ) { return value; }
|
||||
}
|
||||
}
|
||||
},
|
||||
hueRange: {
|
||||
range: {min: 0, max: 65535},
|
||||
step: 1,
|
||||
defaultValue: 0.3,
|
||||
pips: {
|
||||
mode: 'values',
|
||||
values: [0, 25500, 46920, 65535],
|
||||
density: 10,
|
||||
format: {
|
||||
to: function ( value ) {
|
||||
if(value === 0 || value === 65535) {
|
||||
value = 'Red';
|
||||
} else if(value === 25500 ) {
|
||||
value = 'Green';
|
||||
} else {
|
||||
value = 'Blue';
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
from: function ( value ) { return value; }
|
||||
}
|
||||
}
|
||||
},
|
||||
micBoost: {
|
||||
range: {min: 1, max: 11},
|
||||
step: 0.5,
|
||||
defaultValue: 5,
|
||||
pips: {
|
||||
mode: 'positions',
|
||||
values: [0,20,40,60,80,100],
|
||||
density: 10,
|
||||
format: {
|
||||
to: function ( value ) {return 'x'+value;},
|
||||
from: function ( value ) { return value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
threshold: 0.3,
|
||||
hueRange: [0, 65535],
|
||||
micBoost: 5,
|
||||
oldThreshold: null,
|
||||
|
||||
playQueuePointer: -1,
|
||||
playQueue: Em.A(),
|
||||
timeElapsed: 0,
|
||||
timeTotal: 0,
|
||||
lastLightBopIndex: 0,
|
||||
|
||||
usingMicSupported: false,
|
||||
// 0 - local, 1 - mic, possibly more to come
|
||||
audioMode: 0,
|
||||
usingLocalAudio: Em.computed.equal('audioMode', 0),
|
||||
usingMicAudio: Em.computed.equal('audioMode', 1),
|
||||
|
||||
playerBottomDisplayed: false,
|
||||
dragging: false,
|
||||
draggingOverPlayListArea: false,
|
||||
dragLeaveTimeoutHandle: null,
|
||||
ambienceModeHandle: null,
|
||||
audioStream: null,
|
||||
dimmerOn: false,
|
||||
isShowingAddSoundCloudModal: false,
|
||||
|
||||
colorloopMode: false,
|
||||
ambienceMode: false,
|
||||
flashingTransitions: false,
|
||||
|
||||
SC_CLIENT_ID: 'aeec0034f58ecd85c2bd1deaecc41594',
|
||||
notFoundHtml: '<div class="alert alert-danger" role="alert">A microphone was not found.</div>',
|
||||
scUserNotSupportedHtml: '<div class="alert alert-danger" role="alert">SoundCloud user URLs are not supported.</div>',
|
||||
tooManySoundCloudFuckUps: '<div class="alert alert-danger" role="alert">The SoundCloud API is not seving the audio properly. More details <a href="https://www.soundcloudcommunity.com/soundcloud/topics/some-soundcloud-cdn-hosted-tracks-dont-have-access-control-allow-origin-header" target="_blank">HERE</a>.</div>',
|
||||
notStreamableHtml(fileNames){
|
||||
var html = '<div class="alert alert-danger" role="alert">The following file(s) could not be added because they are not allowed to be streamed:<br>' + fileNames.toString().replace(/,/g, '<br>') + '</div>';
|
||||
|
||||
return html;
|
||||
},
|
||||
urlNotFoundHtml(url){
|
||||
return '<div class="alert alert-danger" role="alert">The URL ( ' + url + ' ) could not be resolved.</div>';
|
||||
},
|
||||
failedToPlayFileHtml(fileName){
|
||||
return '<div class="alert alert-danger" role="alert">Failed to play file ( ' + fileName + ' ).</div>';
|
||||
},
|
||||
failedToDecodeFileHtml(fileName){
|
||||
return '<div class="alert alert-danger" role="alert">Failed to decode file ( ' + fileName + ' ).</div>';
|
||||
},
|
||||
|
||||
scUrl: function(){
|
||||
var rtn = null,
|
||||
currentSong = this.get('playQueue')[this.get('playQueuePointer')];
|
||||
|
||||
if(currentSong && currentSong.scUrl && !this.get('usingMicAudio')){
|
||||
rtn = currentSong.scUrl;
|
||||
}
|
||||
|
||||
return rtn;
|
||||
}.property('playQueuePointer', 'playQueue.[]', 'usingMicAudio'),
|
||||
|
||||
playQueueEmpty: Em.computed.empty('playQueue'),
|
||||
playQueueNotEmpty: Em.computed.notEmpty('playQueue'),
|
||||
playQueueMultiple: function(){
|
||||
return this.get('playQueue').length > 1;
|
||||
}.property('playQueue.[]'),
|
||||
|
||||
seekPosition: function() {
|
||||
var timeTotal = this.get('timeTotal'), timeElapsed = this.get('timeElapsed');
|
||||
|
||||
if (timeTotal === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return timeElapsed/timeTotal*100;
|
||||
}.property('timeElapsed', 'timeTotal'),
|
||||
|
||||
// 0 - no repeat, 1 - repeat all, 2 - repeat one
|
||||
repeat: 0,
|
||||
shuffle: false,
|
||||
volumeMuted: false,
|
||||
volume: 100,
|
||||
// beat detection related pausing
|
||||
paused: false,
|
||||
// audio: playing or paused
|
||||
playing: false,
|
||||
fadeOutNotification: false,
|
||||
songBeatPreferences: {},
|
||||
usingBeatPreferences: false,
|
||||
oldBeatPrefCache: null,
|
||||
storage: null,
|
||||
firstVisit: true,
|
||||
ambienceWorkedLights: [],
|
||||
ambienceWorkedLightsHandles: {},
|
||||
|
||||
soundCloudFuckUps: 0,
|
||||
maxSoundCloudFuckUps: 3,
|
||||
|
||||
largeArtworkPic: function(){
|
||||
var pic = null,
|
||||
currentVisName = this.get('currentVisName'),
|
||||
usingMicAudio = this.get('usingMicAudio'),
|
||||
playQueuePointer = this.get('playQueuePointer'),
|
||||
playQueue = this.get('playQueue');
|
||||
|
||||
if(playQueuePointer !== -1 && !usingMicAudio && currentVisName === 'None'){
|
||||
var song = playQueue[playQueuePointer];
|
||||
if(song.scUrl){
|
||||
pic = song.picture.replace('67x67', '500x500');
|
||||
} else {
|
||||
pic = song.picture;
|
||||
}
|
||||
}
|
||||
|
||||
return pic;
|
||||
}.property('playQueuePointer', 'usingMicAudio', 'currentVisName'),
|
||||
|
||||
// used to insure that we don't replay the same thing multiple times in shuffle mode
|
||||
shufflePlayed: [],
|
||||
pauseLightUpdates: function(){
|
||||
return this.get('playing');
|
||||
}.property('playing'),
|
||||
|
||||
micIcon: function () {
|
||||
if (this.get('usingMicAudio')) {
|
||||
return 'mic';
|
||||
}
|
||||
|
||||
return 'mic-off';
|
||||
}.property('usingMicAudio'),
|
||||
|
||||
repeatIcon: function () {
|
||||
if (this.get('repeat') === 2) {
|
||||
return 'repeat-one';
|
||||
}
|
||||
|
||||
return 'repeat';
|
||||
}.property('repeat'),
|
||||
|
||||
playingIcon: function () {
|
||||
if(this.get('playing')){
|
||||
return 'pause';
|
||||
} else if(this.get('timeElapsed') === this.get('timeTotal') && this.get('timeTotal') !== 0){
|
||||
return 'replay';
|
||||
} else {
|
||||
return 'play-arrow';
|
||||
}
|
||||
}.property('playing'),
|
||||
|
||||
playListAreaClass: function(){
|
||||
var classes = 'cursorPointer';
|
||||
|
||||
if(this.get('dragging')){
|
||||
classes += ' dragHereHighlight';
|
||||
}
|
||||
|
||||
if(this.get('draggingOverPlayListArea')){
|
||||
classes += ' draggingOver';
|
||||
}
|
||||
|
||||
if(this.get('dimmerOn')){
|
||||
classes += ' dimmerOn';
|
||||
}
|
||||
|
||||
return classes;
|
||||
}.property('dragging', 'draggingOverPlayListArea', 'dimmerOn'),
|
||||
|
||||
dimmerOnClass: function(){
|
||||
return this.get('dimmerOn') ? 'dimmerOn' : null;
|
||||
}.property('dimmerOn'),
|
||||
|
||||
volumeMutedClass: function(){
|
||||
var classes = 'playerControllIcon volumeButton';
|
||||
|
||||
if(this.get('volumeMuted')){
|
||||
classes += ' active';
|
||||
}
|
||||
|
||||
return classes;
|
||||
}.property('volumeMuted'),
|
||||
|
||||
usingLocalAudioClass: function() {
|
||||
return this.get('usingLocalAudio') ? 'playerControllIcon active' : 'playerControllIcon';
|
||||
}.property('usingLocalAudio'),
|
||||
|
||||
usingMicAudioClass: function() {
|
||||
return this.get('usingMicAudio') ? 'playerControllIcon active' : 'playerControllIcon';
|
||||
}.property('usingMicAudio'),
|
||||
|
||||
repeatClass: function () {
|
||||
return this.get('repeat') !== 0 ? 'playerControllIcon active' : 'playerControllIcon';
|
||||
}.property('repeat'),
|
||||
|
||||
shuffleClass: function () {
|
||||
return this.get('shuffle') ? 'playerControllIcon active' : 'playerControllIcon';
|
||||
}.property('shuffle'),
|
||||
|
||||
volumeIcon: function () {
|
||||
var volume = this.get('volume');
|
||||
|
||||
if (this.get('volumeMuted')) {
|
||||
return "volume-off";
|
||||
} else if (volume >= 70) {
|
||||
return "volume-up";
|
||||
} else if (volume > 10) {
|
||||
return "volume-down";
|
||||
} else {
|
||||
return 'volume-mute';
|
||||
}
|
||||
}.property('volumeMuted', 'volume'),
|
||||
|
||||
onColorloopModeChange: function(){
|
||||
var colorLoop = ((this.get('playing') || this.get('usingMicAudio')) && this.get('colorloopMode')) ? true : false;
|
||||
|
||||
this.set('colorLoopOn', colorLoop);
|
||||
}.observes('colorloopMode', 'usingMicAudio', 'playing'),
|
||||
|
||||
onOptionChange: function(self, option){
|
||||
option = option.replace('.[]', '');
|
||||
this.get('storage').set('huegasm.' + option, this.get(option));
|
||||
}.observes('flashingTransitions', 'playQueue.[]', 'playQueuePointer', 'colorloopMode', 'ambienceMode'),
|
||||
|
||||
onRepeatChange: function () {
|
||||
var tooltipTxt = 'Repeat all', type = 'repeat';
|
||||
|
||||
if (this.get(type) === 1) {
|
||||
tooltipTxt = 'Repeat one';
|
||||
} else if (this.get(type) === 2) {
|
||||
tooltipTxt = 'Repeat off';
|
||||
}
|
||||
|
||||
this.changeTooltipText(type, tooltipTxt);
|
||||
}.observes('repeat').on('init'),
|
||||
|
||||
onUsingMicAudioChange: function(){
|
||||
var tooltipTxt = 'Listen to audio through mic', type = 'usingMicAudio';
|
||||
|
||||
if (this.get(type)) {
|
||||
tooltipTxt = 'Listen to audio files';
|
||||
}
|
||||
|
||||
this.changeTooltipText(type, tooltipTxt);
|
||||
}.observes('usingMicAudio').on('init'),
|
||||
|
||||
onShuffleChange: function () {
|
||||
var tooltipTxt = 'Shuffle', type = 'shuffle';
|
||||
|
||||
if (this.get(type)) {
|
||||
this.get('shufflePlayed').clear();
|
||||
tooltipTxt = 'Unshuffle';
|
||||
}
|
||||
|
||||
this.changeTooltipText(type, tooltipTxt);
|
||||
}.observes('shuffle').on('init'),
|
||||
|
||||
onVolumeMutedChange: function () {
|
||||
var tooltipTxt = 'Mute', type = 'volumeMuted',
|
||||
volumeMuted = this.get(type), dancer = this.get('dancer'),
|
||||
volume=0;
|
||||
|
||||
if (volumeMuted) {
|
||||
tooltipTxt = 'Unmute';
|
||||
volume = 0;
|
||||
} else {
|
||||
volume = this.get('volume')/100;
|
||||
}
|
||||
|
||||
if(this.get('playing')){
|
||||
dancer.setVolume(volume);
|
||||
}
|
||||
|
||||
this.changeTooltipText(type, tooltipTxt);
|
||||
}.observes('volumeMuted').on('init'),
|
||||
|
||||
onPrevChange: function() {
|
||||
if(this.get('playQueueNotEmpty')){
|
||||
var tooltipTxt = 'Previous', type = 'prev';
|
||||
|
||||
if(this.get('timeElapsed') > 5 || this.get('playQueue').length === 1) {
|
||||
tooltipTxt = 'Replay';
|
||||
}
|
||||
|
||||
this.changeTooltipText(type, tooltipTxt);
|
||||
}
|
||||
}.observes('timeElapsed', 'playQueueNotEmpty', 'playQueue.[]'),
|
||||
|
||||
onPlayingChange: function () {
|
||||
var tooltipTxt = 'Play', type = 'playing';
|
||||
|
||||
if (this.get(type)) {
|
||||
tooltipTxt = 'Pause';
|
||||
} else if(this.get('timeElapsed') === this.get('timeTotal') && this.get('timeTotal') !== 0){
|
||||
tooltipTxt = 'Replay';
|
||||
}
|
||||
|
||||
this.changeTooltipText(type, tooltipTxt);
|
||||
}.observes('playing').on('init'),
|
||||
|
||||
changeTooltipText(type, text) {
|
||||
// change the tooltip text if it's already visible
|
||||
Em.$('#' + type + 'Tooltip + .tooltip .tooltip-inner').html(text);
|
||||
//change the tooltip text for hover
|
||||
Em.$('#' + type + 'Tooltip').attr('data-original-title', text);
|
||||
|
||||
if(Em.isNone(this.get(type + 'TooltipTxt'))) {
|
||||
this.set(type + 'TooltipTxt', text);
|
||||
}
|
||||
},
|
||||
|
||||
beatDetectionAreaArrowIcon: function(){
|
||||
if(!this.get('playerBottomDisplayed')){
|
||||
return 'keyboard-arrow-down';
|
||||
} else {
|
||||
return 'keyboard-arrow-up';
|
||||
}
|
||||
}.property('playerBottomDisplayed'),
|
||||
|
||||
timeElapsedTxt: function(){
|
||||
return this.formatTime(this.get('timeElapsed'));
|
||||
}.property('timeElapsed'),
|
||||
|
||||
timeTotalTxt: function() {
|
||||
return this.formatTime(this.get('timeTotal'));
|
||||
}.property('timeTotal'),
|
||||
|
||||
formatTime(time){
|
||||
return this.pad(Math.floor(time/60), 2) + ':' + this.pad(time%60, 2);
|
||||
},
|
||||
|
||||
pad(num, size){ return ('000000000' + num).substr(-size); }
|
||||
});
|
||||
88
app/pods/components/music-tab/mixins/visualizer.js
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import Em from 'ember';
|
||||
|
||||
export default Em.Mixin.create({
|
||||
currentVisName: 'None',
|
||||
|
||||
visNames: ['None', 'Bars', 'Wave'],
|
||||
|
||||
onCurrentVisNameChange: function () {
|
||||
var currentVisName = this.get('currentVisName');
|
||||
|
||||
if(currentVisName === 'None'){
|
||||
var canvasEl = Em.$('#visualization')[0],
|
||||
ctx = canvasEl.getContext('2d');
|
||||
|
||||
ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
|
||||
}
|
||||
|
||||
this.get('storage').set('huegasm.currentVisName', currentVisName);
|
||||
}.observes('currentVisName'),
|
||||
|
||||
didInsertElement(){
|
||||
var dancer = this.get('dancer'),
|
||||
canvas = Em.$('#visualization')[0],
|
||||
playerArea = Em.$('#playerArea'),
|
||||
ctx = canvas.getContext('2d'),
|
||||
spacing = 2,
|
||||
h = playerArea.height(), w;
|
||||
|
||||
canvas.height = h;
|
||||
|
||||
// must be done to preserver resolution so that things don't appear blurry
|
||||
// note that the height is set to 400px via css so it doesn't need to be recalculated
|
||||
var syncCanvasHeight = ()=>{
|
||||
w = playerArea.width();
|
||||
canvas.width = w;
|
||||
};
|
||||
|
||||
syncCanvasHeight();
|
||||
|
||||
Em.$(window).on('resize', syncCanvasHeight);
|
||||
|
||||
dancer.bind('update', () => {
|
||||
var currentVisName = this.get('currentVisName'),
|
||||
gradient = ctx.createLinearGradient(0, 0, 0, h),
|
||||
pageHidden = document.hidden || document.msHidden || document.webkitHidden || document.mozHidden;
|
||||
|
||||
// dont do anything if the page is hidden or no visualization
|
||||
if(currentVisName === 'None' || pageHidden || !this.get('active')){
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
|
||||
if (currentVisName === 'Wave') {
|
||||
let width = 3,
|
||||
count = 1024;
|
||||
|
||||
gradient.addColorStop(0.6, 'white');
|
||||
gradient.addColorStop(0, '#0036FA');
|
||||
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = gradient;
|
||||
var waveform = dancer.getWaveform();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, h / 2);
|
||||
for (let i = 0, l = waveform.length; i < l && i < count; i++) {
|
||||
ctx.lineTo(i * ( spacing + width ), ( h / 2 ) + waveform[i] * ( h / 2 ));
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
} else if (currentVisName === 'Bars') {
|
||||
let width = 4,
|
||||
count = 128;
|
||||
|
||||
gradient.addColorStop(1, '#0f0');
|
||||
gradient.addColorStop(0.6, '#ff0');
|
||||
gradient.addColorStop(0.2, '#F12B24');
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
var spectrum = dancer.getSpectrum();
|
||||
for (let i = 0, l = spectrum.length; i < l && i < count; i++) {
|
||||
ctx.fillRect(i * ( spacing + width ), h, width, -spectrum[i] * h - 60);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
207
app/pods/components/music-tab/template.hbs
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
<div class="row" id="step1">
|
||||
<div id="playerArea" class="col-sm-8 col-xs-12 {{if (eq "None" currentVisName) "displayIcon"}}" {{action "playerAreaPlay"}}>
|
||||
<canvas id="visualization"></canvas>
|
||||
<div id="artwork"><img src={{largeArtworkPic}}></div>
|
||||
<div id="playNotification" class="material-icons {{if fadeOutNotification "fadeOut"}} {{if playing "play-arrow" "pause"}}"></div>
|
||||
|
||||
<div id="playerControls">
|
||||
{{#if usingLocalAudio}}
|
||||
{{range-slider start=seekPosition min=0 max=100 id="seekSlider" slide="seekChanged"}}
|
||||
|
||||
{{#if playQueueNotEmpty}}
|
||||
<span data-toggle="tooltip" data-placement="top" class="bootstrapTooltip" id="prevTooltip"
|
||||
data-title={{prevTooltipTxt}} {{action "previous"}}>{{paper-icon icon="skip-previous" class="playerControllIcon"}}</span><!--
|
||||
-->{{/if}}<!--
|
||||
--><span data-toggle="tooltip" data-placement="top" id="playingTooltip" class="bootstrapTooltip"
|
||||
data-title={{playingTooltipTxt}} {{action "play"}}>{{paper-icon icon=playingIcon class="playerControllIcon"}}</span><!--
|
||||
-->{{#if playQueueMultiple}}<!--
|
||||
--><span data-toggle="tooltip" data-placement="top" class="bootstrapTooltip"
|
||||
data-title="Next" {{action "next" true}}>{{paper-icon icon="skip-next" action="" class="playerControllIcon"}}</span><!--
|
||||
-->{{/if}}<!--
|
||||
--><span data-toggle="tooltip" data-placement="top" class="bootstrapTooltip" id="volumeMutedTooltip"
|
||||
data-title={{volumeMutedTooltipTxt}} {{action "volumeMutedChanged"}}>{{paper-icon icon=volumeIcon class=volumeMutedClass}}</span><!--
|
||||
-->{{range-slider start=volume min=0 max=100 slide="volumeChanged" id="volumeBar" class="hidden-xs"}}
|
||||
|
||||
<div id="playerTimeControls">{{timeElapsedTxt}} / {{timeTotalTxt}}</div>
|
||||
{{/if}}
|
||||
|
||||
<span class="pull-right">
|
||||
{{#if scUrl}}
|
||||
<a href="#" data-toggle="tooltip" data-placement="top" class="soundCloudLink bootstrapTooltip" data-title="Listen on SoundCloud" {{action "gotoSCURL" scUrl}}>
|
||||
<img src="assets/images/sc-white.png" class="hidden-xs" />
|
||||
<img src="assets/images/sc-white-sm.png" class="visible-xs-inline" />
|
||||
</a>
|
||||
{{/if}}
|
||||
<span class="dropup">
|
||||
<span class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<span data-toggle="tooltip" data-placement="top" class="bootstrapTooltip" data-title="Visualizations" {{action "hideTooltip"}}>
|
||||
{{paper-icon icon="remove-red-eye" class="playerControllIcon"}}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<ul class="dropdown-menu visualizersMenu">
|
||||
{{#each visNames as |name|}}
|
||||
<li><a href="#" {{action "setVisName" name}}>{{name}} {{#if (eq currentVisName name)}}{{paper-icon icon="check"}}{{/if}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="playlist" class="col-sm-4 col-xs-12">
|
||||
<input id="fileInput" type="file" accept="audio/*" multiple="true"/>
|
||||
|
||||
<div id="playListControls">
|
||||
{{#if usingLocalAudio}}
|
||||
<button class="dropdown-toggle pull-right addNewMusic" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
Add new music
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
|
||||
<ul id="addMusicChoices" class="dropdown-menu">
|
||||
<li><a href="#" {{action "addLocalAudio"}}>Local</a></li>
|
||||
<li><a href="#" {{action "toggleIsShowingAddSoundCloudModal"}}>SoundCloud</a></li>
|
||||
</ul>
|
||||
|
||||
{{!--{{#if (and usingLocalAudio playQueueNotEmpty)}}
|
||||
<div id="extraOptionsMenu" class="hidden-xs">
|
||||
<span data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip" id="shuffleTooltip" data-title={{shuffleTooltipTxt}} {{action "shuffleChanged"}}>{{paper-icon icon="shuffle" class=shuffleClass}}</span>
|
||||
<span data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip" id="repeatTooltip" data-title={{repeatTooltipTxt}} {{action "repeatChanged"}}>{{paper-icon icon=repeatIcon class=repeatClass}}</span>
|
||||
<span data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip" data-title="Clear playlist" {{action "clearPlaylist"}}>{{paper-icon icon="clear-all" class="playerControllIcon"}}</span>
|
||||
</div>
|
||||
{{/if}} --}}
|
||||
{{/if}}
|
||||
|
||||
{{#if usingMicSupported}}
|
||||
<span data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip" id="usingMicAudioTooltip" data-title={{usingMicAudioTooltipTxt}} {{action "useMicAudio"}}>{{paper-icon icon=micIcon class=usingMicAudioClass}}</span>
|
||||
{{/if}}
|
||||
|
||||
{{#if usingLocalAudio}}
|
||||
<span data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip" id="shuffleTooltip" data-title={{shuffleTooltipTxt}} {{action "shuffleChanged"}}>{{paper-icon icon="shuffle" class=shuffleClass}}</span>
|
||||
<span data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip" id="repeatTooltip" data-title={{repeatTooltipTxt}} {{action "repeatChanged"}}>{{paper-icon icon=repeatIcon class=repeatClass}}</span>
|
||||
<span data-toggle="tooltip" data-placement="bottom auto" class="bootstrapTooltip" data-title="Clear playlist" {{action "clearPlaylist"}}>{{paper-icon icon="clear-all" class="playerControllIcon"}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if usingMicAudio}}
|
||||
<div id="playAreaMic" class="{{if dimmerOn "dimmerOn"}}">
|
||||
{{paper-icon icon="mic" class=dimmerOnClass}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{#if usingLocalAudio}}
|
||||
<div id="playListArea" class={{playListAreaClass}} {{action "addLocalAudio"}} {{action "playListAreaDragOver" on="dragOver"}} {{action "playListAreaDragLeave" on="dragLeave"}} {{action "dropFiles" on="drop"}}>
|
||||
{{#if (or playQueueEmpty dragging)}}
|
||||
<div id="dragHere">
|
||||
{{#if dragging}}
|
||||
Drag your music files here
|
||||
{{else}}
|
||||
Add your music files here
|
||||
{{/if}}
|
||||
</div>
|
||||
{{paper-icon icon="library-music" class=dimmerOnClass}}
|
||||
{{/if}}
|
||||
|
||||
{{#each playQueue as |item index|}}
|
||||
<div class="playlistItem cursorPointer track{{index}} {{if (eq index playQueuePointer) "active"}} {{if dragging "hidden"}}" {{action "goToSong" index true bubbles=false}}>
|
||||
{{#if item.picture}}
|
||||
<img class="albumArt" src={{item.picture}}>
|
||||
{{else}}
|
||||
<img class="albumArt" src="assets/images/missingArtwork.png">
|
||||
{{/if}}
|
||||
|
||||
<div class="songInfo">
|
||||
{{#if item.title}}
|
||||
<div class="songTitle">{{item.title}}</div>
|
||||
<div class="songArtist">
|
||||
{{#if item.artistUrl}}
|
||||
<a href="#" {{action "gotoURL" item.artistUrl bubbles=false}}>{{item.artist}}</a>
|
||||
{{else}}
|
||||
{{item.artist}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{item.fileName}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<span data-toggle="tooltip" data-placement="bottom auto" data-title="Remove" data-container="body" class="audioRemoveButton cursorPointer bootstrapTooltip" {{action "removeAudio" index bubbles=false}}>{{paper-icon icon="close"}}</span>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="slideToggle" class="text-center cursorPointer row" {{action "slideTogglePlayerBottom"}}>
|
||||
<div class="col-xs-offset-5 col-xs-2">
|
||||
{{paper-icon icon=beatDetectionAreaArrowIcon id="beatDetectionAreaArrowIcon"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="playerBottom" class="row {{if dimmerOn "dimmerOn"}}">
|
||||
<div id="beatArea" class="col-sm-7 col-xs-12">
|
||||
{{#if usingBeatPreferences}}
|
||||
<span data-toggle="tooltip" data-placement="bottom" data-title="Using the saved sensitivity preference from the last time you listened to this song" class="bootstrapTooltip" id="saveBeatPreferencesStar">
|
||||
{{paper-icon class=dimmerOnClass icon="star"}}
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
<div class="row" id="beatOptionRow">
|
||||
<div class="beatOption col-xs-4">
|
||||
<span data-toggle="tooltip" data-placement="bottom" data-title="The sensitivity of the beat detector ( more sensitivity results in more registered beats )" class="optionDescription bootstrapTooltip">Sensitivity</span>
|
||||
{{range-slider start=threshold orientation="vertical" step=beatOptions.threshold.step range=beatOptions.threshold.range slide="thresholdChanged" pips=beatOptions.threshold.pips}}
|
||||
</div>
|
||||
|
||||
<div class="beatOption col-xs-4">
|
||||
<span data-toggle="tooltip" data-placement="bottom" data-title="The range of hues ( colors ) that the lights may change to on beat." class="optionDescription bootstrapTooltip">Hue Range</span>
|
||||
{{range-slider start=hueRange orientation="vertical" step=beatOptions.hueRange.step range=beatOptions.hueRange.range slide="hueRangeChanged" pips=beatOptions.hueRange.pips}}
|
||||
</div>
|
||||
|
||||
{{#if usingMicAudio}}
|
||||
<div class="beatOption col-xs-4">
|
||||
<span data-toggle="tooltip" data-placement="bottom" data-title="The coefficient to boost the microphone signal by" class="optionDescription bootstrapTooltip">Mic Boost</span>
|
||||
{{range-slider start=micBoost orientation="vertical" step=beatOptions.micBoost.step range=beatOptions.micBoost.range slide="micBoostChanged" pips=beatOptions.micBoost.pips}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div id="lightOption" class="beatOption col-xs-4">
|
||||
<span data-toggle="tooltip" data-placement="bottom auto" data-title="Quickly flash the lights on beat" class="bootstrapTooltip" {{action "hideTooltip" on="mouseLeave"}}>
|
||||
{{#paper-checkbox checked=flashingTransitions}}Flashing Transitions{{/paper-checkbox}}
|
||||
</span>
|
||||
|
||||
<span data-toggle="tooltip" data-placement="bottom auto" data-title="Slowly cycle the lights through all the colors" class="bootstrapTooltip" {{action "hideTooltip" on="mouseLeave"}}>
|
||||
{{#paper-checkbox checked=colorloopMode}}Colorloop{{/paper-checkbox}}
|
||||
</span>
|
||||
|
||||
{{!--<span data-toggle="tooltip" data-placement="bottom auto" data-title="Periodically turn the lights on and off to create a cool looking ambience" class="bootstrapTooltip" {{action "hideTooltip" on="mouseLeave"}}>
|
||||
{{#paper-checkbox checked=ambienceMode}}Ambience{{/paper-checkbox}}
|
||||
</span>--}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="beatContainer" class="col-sm-5 hidden-xs">
|
||||
<div class="bezel">
|
||||
<div class="rivet1"></div>
|
||||
<div class="rivet2"></div>
|
||||
<div class="rivet3"></div>
|
||||
<div class="rivet4"></div>
|
||||
<div class="rivet5"></div>
|
||||
<div class="rivet6"></div>
|
||||
<div class="rivet7"></div>
|
||||
<div class="rivet8"></div>
|
||||
|
||||
<div id="beatSpeakerCenterOuter">
|
||||
<div id="beatSpeakerCenterInner" class="cursorPointer" {{action "clickSpeaker"}}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ember-notify closeAfter=100000 classPrefix="customNotify"}}
|
||||
|
||||
{{add-soundcloud-sound-modal action="handleNewSoundCloudURL" isShowingModal=isShowingAddSoundCloudModal}}
|
||||
3
app/resolver.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Resolver from 'ember-resolver';
|
||||
|
||||
export default Resolver;
|
||||
11
app/router.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import Ember from 'ember';
|
||||
import config from './config/environment';
|
||||
|
||||
const Router = Ember.Router.extend({
|
||||
location: config.locationType
|
||||
});
|
||||
|
||||
Router.map(function() {
|
||||
});
|
||||
|
||||
export default Router;
|
||||
1169
app/styles/app.scss
Normal file
153
app/styles/fancy-speaker.scss
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/* Variables */
|
||||
$centersize: 80px;
|
||||
$center1size: 205px;
|
||||
$bezelsize: 240px;
|
||||
$vibratesize: $centersize*1.06;
|
||||
$vibratemargin:-$vibratesize/2;
|
||||
$vibrateblurinner: 3px;
|
||||
$vibrateblurouter: 2px;
|
||||
|
||||
/* Extenders */
|
||||
%base {
|
||||
border-radius: 100%;
|
||||
}
|
||||
%rivet {
|
||||
position: absolute;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
background-color: #555;
|
||||
border-radius: 100%;
|
||||
box-shadow: inset 0 0 3px #000, 0 0 2px #000;
|
||||
}
|
||||
|
||||
/* Keyframes */
|
||||
@keyframes vibrateInner {
|
||||
50% {
|
||||
-webkit-filter: blur($vibrateblurinner);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes vibrateOuter {
|
||||
0% {
|
||||
-webkit-filter: blur($vibrateblurouter);
|
||||
filter: blur($vibrateblurouter);
|
||||
}
|
||||
30% {
|
||||
-webkit-filter: blur(0);
|
||||
filter: blur(0);
|
||||
}
|
||||
50% {
|
||||
-webkit-filter: blur($vibrateblurouter);
|
||||
filter: blur($vibrateblurouter);
|
||||
}
|
||||
65% {
|
||||
-webkit-filter: blur(0);
|
||||
filter: blur(0);
|
||||
}
|
||||
70% {
|
||||
-webkit-filter: blur($vibrateblurouter);
|
||||
filter: blur($vibrateblurouter);
|
||||
}
|
||||
80% {
|
||||
-webkit-filter: blur($vibrateblurouter);
|
||||
filter: blur($vibrateblurouter);
|
||||
}
|
||||
100% {
|
||||
-webkit-filter: blur($vibrateblurouter);
|
||||
filter: blur($vibrateblurouter);
|
||||
}
|
||||
}
|
||||
|
||||
#beatSpeakerCenterInner {
|
||||
@extend %base;
|
||||
height: $centersize;
|
||||
width: $centersize;
|
||||
position: absolute;
|
||||
bottom: 47px;
|
||||
right: 47px;
|
||||
-webkit-filter: blur(1px);
|
||||
filter: blur(1px);
|
||||
background: rgb(0,0,0);
|
||||
background: -moz-radial-gradient(center, ellipse cover, rgba(0,0,0,1) 0%, rgba(79,79,79,1) 0%, rgba(0,0,0,1) 100%);
|
||||
background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(0,0,0,1)), color-stop(0%,rgba(79,79,79,1)), color-stop(100%,rgba(0,0,0,1)));
|
||||
background: -webkit-radial-gradient(center, ellipse cover, rgba(0,0,0,1) 0%,rgba(79,79,79,1) 0%,rgba(0,0,0,1) 100%);
|
||||
background: -o-radial-gradient(center, ellipse cover, rgba(0,0,0,1) 0%,rgba(79,79,79,1) 0%,rgba(0,0,0,1) 100%);
|
||||
background: -ms-radial-gradient(center, ellipse cover, rgba(0,0,0,1) 0%,rgba(79,79,79,1) 0%,rgba(0,0,0,1) 100%);
|
||||
background: radial-gradient(ellipse at center, rgba(0,0,0,1) 0%,rgba(79,79,79,1) 0%,rgba(0,0,0,1) 100%);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 1);
|
||||
}
|
||||
.vibrateInner{
|
||||
-webkit-animation: vibrateInner 0.15s linear 1;
|
||||
animation: vibrateInner 0.15s linear 1;
|
||||
}
|
||||
#beatSpeakerCenterOuter {
|
||||
@extend %base;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
height: $center1size;
|
||||
width: $center1size;
|
||||
border: 15px solid #333;
|
||||
box-shadow: -3px -3px 15px rgba(0, 0, 0, 0.4), inset -3px -3px 15px rgba(0, 0, 0, 0.5);
|
||||
background: -moz-linear-gradient(130deg, rgba(117, 117, 117, 1) 55%, rgba(220, 220, 220, 1) 100%);
|
||||
background: -webkit-linear-gradient(130deg, rgba(117, 117, 117, 1) 55%, rgba(220, 220, 220, 1) 100%);
|
||||
background: -o-linear-gradient(130deg, rgba(117, 117, 117, 1) 55%, rgba(220, 220, 220, 1) 100%);
|
||||
background: -ms-linear-gradient(130deg, rgba(117, 117, 117, 1) 55%, rgba(220, 220, 220, 1) 100%);
|
||||
background: linear-gradient(130deg, rgba(117, 117, 117, 1) 55%, rgba(220, 220, 220, 1) 100%);
|
||||
}
|
||||
.vibrateOuter {
|
||||
-webkit-animation: vibrateOuter 0.15s linear 1;
|
||||
animation: vibrateOuter 0.15s linear 1;
|
||||
}
|
||||
.bezel {
|
||||
@extend %base;
|
||||
margin: 0 auto;
|
||||
height: $bezelsize;
|
||||
width: $bezelsize;
|
||||
position: relative;
|
||||
background-color: #A8A8A8;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8), inset 3px 3px 10px rgba(0, 0, 0, 0.8), 0 0 2px rgba(0, 0, 0, 0.8), inset 0 0 30px -5px rgba(0, 0, 0, 0.8);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.rivet1 {
|
||||
@extend %rivet;
|
||||
top: 6px;
|
||||
left: 50%;
|
||||
}
|
||||
.rivet2 {
|
||||
@extend %rivet;
|
||||
bottom: 6px;
|
||||
left: 50%;
|
||||
}
|
||||
.rivet3 {
|
||||
@extend %rivet;
|
||||
top: 50%;
|
||||
left: 6px;
|
||||
}
|
||||
.rivet4 {
|
||||
@extend %rivet;
|
||||
top: 50%;
|
||||
right: 6px;
|
||||
}
|
||||
.rivet5 {
|
||||
@extend %rivet;
|
||||
top: 18%;
|
||||
left: 13.7%;
|
||||
}
|
||||
.rivet6 {
|
||||
@extend %rivet;
|
||||
top: 18%;
|
||||
right: 13.5%;
|
||||
}
|
||||
.rivet7 {
|
||||
@extend %rivet;
|
||||
bottom: 17%;
|
||||
left: 13.5%;
|
||||
}
|
||||
.rivet8 {
|
||||
@extend %rivet;
|
||||
bottom: 17%;
|
||||
right: 13.5%;
|
||||
}
|
||||
1
app/templates/application.hbs
Normal file
|
|
@ -0,0 +1 @@
|
|||
{{huegasm-app}}
|
||||
BIN
assets/icon.psd
Normal file
BIN
assets/intro.psd
Normal file
BIN
assets/missingArtwork.psd
Normal file
BIN
assets/pressButtonBridge.psd
Normal file
19
bower.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "huegasm",
|
||||
"dependencies": {
|
||||
"bootstrap-sass": "~3.3.5",
|
||||
"ember": "~2.4.1",
|
||||
"ember-cli-shims": "0.1.0",
|
||||
"ember-cli-test-loader": "0.2.2",
|
||||
"ember-load-initializers": "0.5.1",
|
||||
"ember-qunit-notifications": "0.1.0",
|
||||
"hammerjs": "~2.0.4",
|
||||
"intro.js": "~1.1.1",
|
||||
"JavaScript-ID3-Reader": "https://github.com/aadsm/JavaScript-ID3-Reader.git",
|
||||
"jquery-mousewheel": "~3.1.13",
|
||||
"locallyjs": "~0.3.2",
|
||||
"matchMedia": "~0.2.0",
|
||||
"nouislider": "^8.3.0",
|
||||
"HackTimer": "https://github.com/turuslan/HackTimer.git#~1.0.0"
|
||||
}
|
||||
}
|
||||
48
config/environment.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/* jshint node: true */
|
||||
|
||||
module.exports = function(environment) {
|
||||
var ENV = {
|
||||
modulePrefix: 'huegasm',
|
||||
podModulePrefix: 'huegasm/pods',
|
||||
environment: environment,
|
||||
baseURL: '/',
|
||||
locationType: 'auto',
|
||||
EmberENV: {
|
||||
FEATURES: {
|
||||
// Here you can enable experimental features on an ember canary build
|
||||
// e.g. 'with-controller': true
|
||||
}
|
||||
},
|
||||
|
||||
APP: {
|
||||
// Here you can pass flags/options to your application instance
|
||||
// when it is created
|
||||
}
|
||||
};
|
||||
|
||||
if (environment === 'development') {
|
||||
// ENV.APP.LOG_RESOLVER = true;
|
||||
// ENV.APP.LOG_ACTIVE_GENERATION = true;
|
||||
// ENV.APP.LOG_TRANSITIONS = true;
|
||||
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
|
||||
// ENV.APP.LOG_VIEW_LOOKUPS = true;
|
||||
}
|
||||
|
||||
if (environment === 'test') {
|
||||
// Testem prefers this...
|
||||
ENV.baseURL = '/';
|
||||
ENV.locationType = 'none';
|
||||
|
||||
// keep test console output quieter
|
||||
ENV.APP.LOG_ACTIVE_GENERATION = false;
|
||||
ENV.APP.LOG_VIEW_LOOKUPS = false;
|
||||
|
||||
ENV.APP.rootElement = '#ember-testing';
|
||||
}
|
||||
|
||||
if (environment === 'production') {
|
||||
//ENV.baseURL = '/huegasm';
|
||||
}
|
||||
|
||||
return ENV;
|
||||
};
|
||||
32
ember-cli-build.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/* global require, module */
|
||||
var EmberApp = require('ember-cli/lib/broccoli/ember-app');
|
||||
|
||||
module.exports = function(defaults) {
|
||||
var app = new EmberApp(defaults);
|
||||
|
||||
app.import('vendor/dancer.js');
|
||||
app.import('bower_components/bootstrap-sass/assets/javascripts/bootstrap/tooltip.js');
|
||||
app.import('bower_components/bootstrap-sass/assets/javascripts/bootstrap/dropdown.js');
|
||||
app.import('bower_components/bootstrap-sass/assets/javascripts/bootstrap/popover.js');
|
||||
app.import('bower_components/JavaScript-ID3-Reader/dist/id3-minimized.js');
|
||||
app.import('bower_components/jquery-mousewheel/jquery.mousewheel.js');
|
||||
app.import('bower_components/locallyjs/dist/locally.min.js');
|
||||
app.import('bower_components/HackTimer/HackTimer.js');
|
||||
app.import('bower_components/intro.js/intro.js');
|
||||
app.import('bower_components/intro.js/introjs.css');
|
||||
|
||||
// Use `app.import` to add additional libraries to the generated
|
||||
// output files.
|
||||
//
|
||||
// If you need to use different assets in different
|
||||
// environments, specify an object as the first parameter. That
|
||||
// object's keys should be the environment name and the values
|
||||
// should be the asset to use in that environment.
|
||||
//
|
||||
// If the library that you are including contains AMD or ES6
|
||||
// modules that you would like to import into your application
|
||||
// please specify an object with the list of modules as keys
|
||||
// along with the exports of each module as its value.
|
||||
|
||||
return app.toTree();
|
||||
};
|
||||
49
package.json
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "huegasm",
|
||||
"version": "1.0.0",
|
||||
"description": "Small description for huegasm goes here",
|
||||
"private": true,
|
||||
"directories": {
|
||||
"doc": "doc",
|
||||
"test": "tests"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "ember server",
|
||||
"build": "ember build",
|
||||
"test": "ember test"
|
||||
},
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"broccoli-asset-rev": "^2.2.0",
|
||||
"ember-ajax": "0.7.1",
|
||||
"ember-cli": "^2.4.2",
|
||||
"ember-cli-app-version": "^1.0.0",
|
||||
"ember-cli-babel": "^5.1.5",
|
||||
"ember-cli-dependency-checker": "^1.2.0",
|
||||
"ember-cli-htmlbars": "^1.0.1",
|
||||
"ember-cli-htmlbars-inline-precompile": "^0.3.1",
|
||||
"ember-cli-inject-live-reload": "^1.3.1",
|
||||
"ember-cli-nouislider": "0.7.0",
|
||||
"ember-cli-qunit": "^1.2.1",
|
||||
"ember-cli-release": "0.2.8",
|
||||
"ember-cli-sass": "5.2.1",
|
||||
"ember-cli-sri": "^2.1.0",
|
||||
"ember-cli-uglify": "^1.2.0",
|
||||
"ember-cli-windows-addon": "^1.2.2",
|
||||
"ember-data": "^2.4.0",
|
||||
"ember-disable-proxy-controllers": "^1.0.1",
|
||||
"ember-export-application-global": "^1.0.4",
|
||||
"ember-load-initializers": "^0.5.0",
|
||||
"ember-modal-dialog": "0.8.3",
|
||||
"ember-notify": "^5.0.2",
|
||||
"ember-paper": "^0.2.12",
|
||||
"ember-resolver": "^2.0.3",
|
||||
"ember-truth-helpers": "1.2.0",
|
||||
"loader.js": "^4.0.0"
|
||||
}
|
||||
}
|
||||
BIN
public/android-chrome-144x144.png
Normal file
|
After Width: | Height: | Size: 7 KiB |
BIN
public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
public/android-chrome-36x36.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
public/android-chrome-48x48.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/android-chrome-72x72.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
public/android-chrome-96x96.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/apple-touch-icon-114x114.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/apple-touch-icon-120x120.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/apple-touch-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/apple-touch-icon-152x152.png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
public/apple-touch-icon-180x180.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
public/apple-touch-icon-57x57.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/apple-touch-icon-60x60.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/apple-touch-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/apple-touch-icon-76x76.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/apple-touch-icon-precomposed.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
public/assets/images/colormap.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
32
public/assets/images/lights/a19.svg
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<polyline fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" points="341.939,163.139 247.72,163.139
|
||||
246.876,163.139 158.423,163.139 "/>
|
||||
<polyline fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" points="300.502,364.005 248.822,364.005
|
||||
248.376,364.005 199.884,364.005 "/>
|
||||
|
||||
<polyline fill="none" stroke="#000000" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
304.533,379.239 248.705,384.044 248.212,384.067 195.853,388.567 "/>
|
||||
|
||||
<polyline fill="none" stroke="#000000" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
305.307,395.106 249.525,399.888 249.033,399.958 196.673,404.435 "/>
|
||||
|
||||
<polyline fill="none" stroke="#000000" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
304.533,410.974 248.705,415.755 248.212,415.802 195.853,420.302 "/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M342.314,163.139
|
||||
c0,0,12.234-21.234,26.039-61.078c4.781-14.344,6.398-32.953-19.664-45.703c-22.734-11.133-69.75-16.969-97.922-16.477
|
||||
c-28.219-0.492-75.234,5.344-97.969,16.477c-26.062,12.75-24.469,31.359-19.688,45.703c13.828,39.844,26.062,61.078,26.062,61.078
|
||||
c29.766,93.003,27.633,183.851,27.633,183.851l14.883,17.016c0,0,0,55.781,0,63.211s11.672,12.773,20.695,14.883
|
||||
c1.594,3.188,6.398,8.508,11.695,14.344c7.125,2.391,26.204,2.391,33.329,0c5.297-5.836,10.078-11.156,11.672-14.344
|
||||
c9.047-2.109,20.742-7.453,20.742-14.883s0-63.211,0-63.211l14.883-17.016C314.705,346.989,312.549,256.142,342.314,163.139z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
32
public/assets/images/lights/a19w.svg
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" points="341.939,163.139 247.72,163.139
|
||||
246.876,163.139 158.423,163.139 "/>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" points="300.502,364.005 248.822,364.005
|
||||
248.376,364.005 199.884,364.005 "/>
|
||||
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
304.533,379.239 248.705,384.044 248.212,384.067 195.853,388.567 "/>
|
||||
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
305.307,395.106 249.525,399.888 249.033,399.958 196.673,404.435 "/>
|
||||
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
304.533,410.974 248.705,415.755 248.212,415.802 195.853,420.302 "/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M342.314,163.139
|
||||
c0,0,12.234-21.234,26.039-61.078c4.781-14.344,6.398-32.953-19.664-45.703c-22.734-11.133-69.75-16.969-97.922-16.477
|
||||
c-28.219-0.492-75.234,5.344-97.969,16.477c-26.062,12.75-24.469,31.359-19.688,45.703c13.828,39.844,26.062,61.078,26.062,61.078
|
||||
c29.766,93.003,27.633,183.851,27.633,183.851l14.883,17.016c0,0,0,55.781,0,63.211s11.672,12.773,20.695,14.883
|
||||
c1.594,3.188,6.398,8.508,11.695,14.344c7.125,2.391,26.204,2.391,33.329,0c5.297-5.836,10.078-11.156,11.672-14.344
|
||||
c9.047-2.109,20.742-7.453,20.742-14.883s0-63.211,0-63.211l14.883-17.016C314.705,346.989,312.549,256.142,342.314,163.139z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
48
public/assets/images/lights/br30.svg
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M276.748,445.245c0,0-4.406,6.281-6,8.625
|
||||
c-1.57,2.344-3.047,9.586-20.602,9h0.023c-17.555,0.586-18.984-6.656-20.578-9c-1.547-2.344-7.078-8.273-7.078-8.273"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M250.17,377.042c0,0,36.844,0.422,40.969-3.188
|
||||
c4.383-3.844,5.25-15.539,5.25-15.539s3.516-82.336,17.391-114.746c12.656-29.695,36.188-58.852,36.188-58.852
|
||||
s24.234-4.711,35.25-34.945c11.016-30.211,12.094-47.367,12.094-47.367H247.709H101.436c0,0,1.125,17.156,12.117,47.367
|
||||
c11.016,30.234,35.297,34.945,35.297,34.945s23.484,29.156,36.164,58.852c13.875,32.41,17.391,114.746,17.391,114.746
|
||||
s0.891,11.695,5.297,15.539C211.779,377.464,250.17,377.042,250.17,377.042z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M98.881,87.405h302.273
|
||||
c0,0,8.531-54.352-151.734-54.352S98.881,87.405,98.881,87.405z"/>
|
||||
<polyline fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" points="113.951,60.265 246.654,60.265
|
||||
385.381,60.265 "/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M148.029,184.858
|
||||
c0,0,52.711,8.648,101.367,8.648c48.633,0,101.391-8.648,101.391-8.648"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M207.396,386.815"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M206.646,386.136"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M209.154,424.784
|
||||
c1.125,0.984,2.203,5.367,0.094,7.195c-2.133,1.758,0,3.164,0,3.164c0.117,1.992,18.914,2.414,41.953,0.938
|
||||
c21.609-1.383,39.258-4.008,41.273-5.977c0,0,1.875-2.578,0-3.797c-1.852-1.242-4.172-4.289-2.812-6.844"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M210.795,412.573
|
||||
c0.281,2.438,1.547,5.062-3.188,8.227c-2.344,1.523,0,3.164,0,3.164c4.734,2.344,18.891,2.438,41.953,0.938
|
||||
c21.562-1.383,39.258-3.984,41.297-5.953c0,0,1.828-2.578,0-3.797c-1.875-1.266-3.094-4.219-2.859-6.867"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M210.795,400.737
|
||||
c0.281,2.461,1.547,5.086-3.188,8.227c-2.344,1.523,0,3.188,0,3.188c4.734,2.32,18.891,2.391,41.953,0.938
|
||||
c21.562-1.406,39.258-4.008,41.297-6c0,0,1.828-2.531,0-3.773c-1.875-1.242-3.094-4.219-2.859-6.891"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M210.795,388.597
|
||||
c0.281,2.461,1.547,5.086-3.188,8.203c-2.344,1.523,0,3.211,0,3.211c4.734,2.297,18.891,2.438,41.953,0.938
|
||||
c21.562-1.406,39.258-3.984,41.297-5.977c0,0,1.828-2.555,0-3.82c-1.875-1.219-3.094-4.195-2.859-6.844"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M212.576,436.995c0,0,1.594,7.312,12.141,9.047
|
||||
c10.547,1.781,17.672,1.359,24.375,1.359s13.828,0.188,24.422-1.359c12.047-1.734,14.812-14.391,14.812-14.391"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M290.998,375.964c0,0,0,6.727-1.008,7.711
|
||||
c-1.008,1.031-3.352,1.242-3.352,1.242s-74.859,5.484-78.328,2.836c-2.062-1.523-1.758-11.789-1.758-11.789"/>
|
||||
|
||||
<line fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" x1="137.436" y1="87.405" x2="143.365" y2="100.483"/>
|
||||
|
||||
<line fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" x1="361.732" y1="87.405" x2="355.826" y2="100.483"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4 KiB |
48
public/assets/images/lights/br30w.svg
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M276.748,445.245c0,0-4.406,6.281-6,8.625
|
||||
c-1.57,2.344-3.047,9.586-20.602,9h0.023c-17.555,0.586-18.984-6.656-20.578-9c-1.547-2.344-7.078-8.273-7.078-8.273"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M250.17,377.042c0,0,36.844,0.422,40.969-3.188
|
||||
c4.383-3.844,5.25-15.539,5.25-15.539s3.516-82.336,17.391-114.746c12.656-29.695,36.188-58.852,36.188-58.852
|
||||
s24.234-4.711,35.25-34.945c11.016-30.211,12.094-47.367,12.094-47.367H247.709H101.436c0,0,1.125,17.156,12.117,47.367
|
||||
c11.016,30.234,35.297,34.945,35.297,34.945s23.484,29.156,36.164,58.852c13.875,32.41,17.391,114.746,17.391,114.746
|
||||
s0.891,11.695,5.297,15.539C211.779,377.464,250.17,377.042,250.17,377.042z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M98.881,87.405h302.273
|
||||
c0,0,8.531-54.352-151.734-54.352S98.881,87.405,98.881,87.405z"/>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" points="113.951,60.265 246.654,60.265
|
||||
385.381,60.265 "/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M148.029,184.858
|
||||
c0,0,52.711,8.648,101.367,8.648c48.633,0,101.391-8.648,101.391-8.648"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M207.396,386.815"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M206.646,386.136"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M209.154,424.784
|
||||
c1.125,0.984,2.203,5.367,0.094,7.195c-2.133,1.758,0,3.164,0,3.164c0.117,1.992,18.914,2.414,41.953,0.938
|
||||
c21.609-1.383,39.258-4.008,41.273-5.977c0,0,1.875-2.578,0-3.797c-1.852-1.242-4.172-4.289-2.812-6.844"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M210.795,412.573
|
||||
c0.281,2.438,1.547,5.062-3.188,8.227c-2.344,1.523,0,3.164,0,3.164c4.734,2.344,18.891,2.438,41.953,0.938
|
||||
c21.562-1.383,39.258-3.984,41.297-5.953c0,0,1.828-2.578,0-3.797c-1.875-1.266-3.094-4.219-2.859-6.867"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M210.795,400.737
|
||||
c0.281,2.461,1.547,5.086-3.188,8.227c-2.344,1.523,0,3.188,0,3.188c4.734,2.32,18.891,2.391,41.953,0.938
|
||||
c21.562-1.406,39.258-4.008,41.297-6c0,0,1.828-2.531,0-3.773c-1.875-1.242-3.094-4.219-2.859-6.891"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M210.795,388.597
|
||||
c0.281,2.461,1.547,5.086-3.188,8.203c-2.344,1.523,0,3.211,0,3.211c4.734,2.297,18.891,2.438,41.953,0.938
|
||||
c21.562-1.406,39.258-3.984,41.297-5.977c0,0,1.828-2.555,0-3.82c-1.875-1.219-3.094-4.195-2.859-6.844"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M212.576,436.995c0,0,1.594,7.312,12.141,9.047
|
||||
c10.547,1.781,17.672,1.359,24.375,1.359s13.828,0.188,24.422-1.359c12.047-1.734,14.812-14.391,14.812-14.391"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M290.998,375.964c0,0,0,6.727-1.008,7.711
|
||||
c-1.008,1.031-3.352,1.242-3.352,1.242s-74.859,5.484-78.328,2.836c-2.062-1.523-1.758-11.789-1.758-11.789"/>
|
||||
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" x1="137.436" y1="87.405" x2="143.365" y2="100.483"/>
|
||||
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" x1="361.732" y1="87.405" x2="355.826" y2="100.483"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4 KiB |
18
public/assets/images/lights/gu10.svg
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M251.452,55.93
|
||||
c-4.196,0-88.923-0.258-129.892,8.508v12c0,0,3.984,3.492,10.992,4.992c0,0,0.516,95.555,13.008,134.555
|
||||
c12.516,39,18,40.523,27.516,50.015c9.516,9.516,20.508,20.016,20.508,40.5c0,20.531,0,84.047,0,84.047l23.508,19.5h3v14.508
|
||||
h-6.984v15.516h26.484v-15.516h-6v-15.023h17.672h0.353h17.649v15.023h-6v15.516h26.531v-15.516h-7.031v-14.508h3l23.531-19.5
|
||||
c0,0,0-63.516,0-84.047c0-20.484,10.992-30.984,20.484-40.5c9.516-9.492,15.023-11.016,27.516-50.015
|
||||
c12.516-39,13.031-134.555,13.031-134.555c6.984-1.5,10.992-4.992,10.992-4.992v-12C340.352,55.672,255.625,55.93,251.452,55.93z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
18
public/assets/images/lights/gu10w.svg
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M251.452,55.93
|
||||
c-4.196,0-88.923-0.258-129.892,8.508v12c0,0,3.984,3.492,10.992,4.992c0,0,0.516,95.555,13.008,134.555
|
||||
c12.516,39,18,40.523,27.516,50.015c9.516,9.516,20.508,20.016,20.508,40.5c0,20.531,0,84.047,0,84.047l23.508,19.5h3v14.508
|
||||
h-6.984v15.516h26.484v-15.516h-6v-15.023h17.672h0.353h17.649v15.023h-6v15.516h26.531v-15.516h-7.031v-14.508h3l23.531-19.5
|
||||
c0,0,0-63.516,0-84.047c0-20.484,10.992-30.984,20.484-40.5c9.516-9.492,15.023-11.016,27.516-50.015
|
||||
c12.516-39,13.031-134.555,13.031-134.555c6.984-1.5,10.992-4.992,10.992-4.992v-12C340.352,55.672,255.625,55.93,251.452,55.93z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
23
public/assets/images/lights/huego.svg
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-linecap="round" stroke-miterlimit="10" d="M185.782,359.98
|
||||
c-4.172,5.203-10.594,8.531-17.766,8.531c-12.562,0-22.734-10.172-22.734-22.734c0-1.922,0.234-3.797,0.703-5.578"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-linecap="round" stroke-miterlimit="10" d="M444.813,174.026
|
||||
c0,106.829-86.625,197.345-193.453,197.345c-38.86,0-75.047-11.438-105.375-31.172c-53.016-34.5-88.078-99.423-88.078-167.392"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M251.501,126.073
|
||||
c106.828,0.328,193.359,21.797,193.312,47.953c-0.094,26.062-86.766,47.016-193.594,46.688
|
||||
c-106.829-0.281-193.36-21.75-193.313-47.906C58,146.698,144.625,125.745,251.501,126.073z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M251.454,131.417
|
||||
c98.203,0.281,177.75,16.969,177.703,37.266c-0.141,20.344-79.734,36.609-177.891,36.328
|
||||
c-98.204-0.281-177.75-16.969-177.704-37.312C73.61,147.401,153.297,131.089,251.454,131.417z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
23
public/assets/images/lights/huegow.svg
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-linecap="round" stroke-miterlimit="10" d="M185.782,359.98
|
||||
c-4.172,5.203-10.594,8.531-17.766,8.531c-12.562,0-22.734-10.172-22.734-22.734c0-1.922,0.234-3.797,0.703-5.578"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-linecap="round" stroke-miterlimit="10" d="M444.813,174.026
|
||||
c0,106.829-86.625,197.345-193.453,197.345c-38.86,0-75.047-11.438-105.375-31.172c-53.016-34.5-88.078-99.423-88.078-167.392"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M251.501,126.073
|
||||
c106.828,0.328,193.359,21.797,193.312,47.953c-0.094,26.062-86.766,47.016-193.594,46.688
|
||||
c-106.829-0.281-193.36-21.75-193.313-47.906C58,146.698,144.625,125.745,251.501,126.073z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M251.454,131.417
|
||||
c98.203,0.281,177.75,16.969,177.703,37.266c-0.141,20.344-79.734,36.609-177.891,36.328
|
||||
c-98.204-0.281-177.75-16.969-177.704-37.312C73.61,147.401,153.297,131.089,251.454,131.417z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
32
public/assets/images/lights/lc_aura.svg
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M114.314,82.195
|
||||
c26.32-1.031,66.891,15.984,120.938,54.375c18,12.844,85.828,65.906,132.093,124.313c36.234,44.25,72.211,104.344,53.719,129.469
|
||||
c-4.453,5.719-18.516,15.703-67.359-5.438c-48.891-21.094-105-65.766-139.148-97.641c-28.266-25.266-70.43-69.891-105.023-126.141
|
||||
C94.58,134.742,72.97,83.695,114.314,82.195z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M421.063,390.352
|
||||
c-4.453,5.719-18.516,15.703-67.359-5.438c-48.891-21.094-105-65.766-139.148-97.641c-28.266-25.266-70.43-69.891-105.023-126.141
|
||||
c-11.695-20.625-27.422-56.344-14.062-71.625c-8.18,9.328-8.531,21.75-2.789,44.812c5.789,23.109,18.586,78.75,20.766,89.578
|
||||
c2.203,10.734,18.211,59.907,13.945,99.329c-3.141,22.312-0.281,35.625,9.938,44.859c10.219,9.281,13.125,12.094,57,7.734
|
||||
c21.938-2.25,29.438-2.906,91.593,7.312c53.766,9.188,94.312,16.734,106.148,16.453S413.376,399.117,421.063,390.352z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M145.392,119.929
|
||||
c20.203-0.797,51.328,12.328,92.766,41.672c13.781,9.891,65.789,50.578,101.273,95.391
|
||||
c27.773,33.891,56.789,83.016,42.633,102.328c-3.422,4.312-19.031,16.734-56.484,0.516c-37.477-16.125-84.281-54-110.46-78.422
|
||||
c-21.68-19.406-54.258-53.954-80.789-97.079C122.892,164.132,113.705,121.054,145.392,119.929z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M192.267,185.601
|
||||
c10.406-0.422,26.484,6.422,47.953,21.75c7.125,5.156,34.007,26.391,52.382,49.735c14.367,17.672,29.391,43.266,22.055,53.297
|
||||
c-1.781,2.25-9.844,8.719-29.203,0.328c-19.383-8.438-43.593-28.219-57.14-40.969c-11.203-10.125-28.055-28.079-41.789-50.579
|
||||
C180.595,208.617,175.861,186.164,192.267,185.601z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M114.08,226.57
|
||||
c10.477,21.047,24,50.344,68.906,92.344c44.906,42.047,81.609,57.703,99.07,63.562"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
32
public/assets/images/lights/lc_auraw.svg
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M114.314,82.195
|
||||
c26.32-1.031,66.891,15.984,120.938,54.375c18,12.844,85.828,65.906,132.093,124.313c36.234,44.25,72.211,104.344,53.719,129.469
|
||||
c-4.453,5.719-18.516,15.703-67.359-5.438c-48.891-21.094-105-65.766-139.148-97.641c-28.266-25.266-70.43-69.891-105.023-126.141
|
||||
C94.58,134.742,72.97,83.695,114.314,82.195z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M421.063,390.352
|
||||
c-4.453,5.719-18.516,15.703-67.359-5.438c-48.891-21.094-105-65.766-139.148-97.641c-28.266-25.266-70.43-69.891-105.023-126.141
|
||||
c-11.695-20.625-27.422-56.344-14.062-71.625c-8.18,9.328-8.531,21.75-2.789,44.812c5.789,23.109,18.586,78.75,20.766,89.578
|
||||
c2.203,10.734,18.211,59.907,13.945,99.329c-3.141,22.312-0.281,35.625,9.938,44.859c10.219,9.281,13.125,12.094,57,7.734
|
||||
c21.938-2.25,29.438-2.906,91.593,7.312c53.766,9.188,94.312,16.734,106.148,16.453S413.376,399.117,421.063,390.352z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M145.392,119.929
|
||||
c20.203-0.797,51.328,12.328,92.766,41.672c13.781,9.891,65.789,50.578,101.273,95.391
|
||||
c27.773,33.891,56.789,83.016,42.633,102.328c-3.422,4.312-19.031,16.734-56.484,0.516c-37.477-16.125-84.281-54-110.46-78.422
|
||||
c-21.68-19.406-54.258-53.954-80.789-97.079C122.892,164.132,113.705,121.054,145.392,119.929z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M192.267,185.601
|
||||
c10.406-0.422,26.484,6.422,47.953,21.75c7.125,5.156,34.007,26.391,52.382,49.735c14.367,17.672,29.391,43.266,22.055,53.297
|
||||
c-1.781,2.25-9.844,8.719-29.203,0.328c-19.383-8.438-43.593-28.219-57.14-40.969c-11.203-10.125-28.055-28.079-41.789-50.579
|
||||
C180.595,208.617,175.861,186.164,192.267,185.601z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M114.08,226.57
|
||||
c10.477,21.047,24,50.344,68.906,92.344c44.906,42.047,81.609,57.703,99.07,63.562"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
30
public/assets/images/lights/lc_bloom.svg
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M287.609,219.602
|
||||
c32.25,29.578,49.617,63.141,38.719,75c-10.852,11.859-45.844-2.484-78.091-32.109c-32.273-29.578-49.594-63.188-38.719-75.094
|
||||
C220.393,175.539,255.338,189.93,287.609,219.602z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M299.422,203.102
|
||||
c54.656,50.203,84.516,106.594,66.656,126.094c-17.859,19.453-76.641-5.344-131.341-55.5
|
||||
c-54.656-50.109-84.516-106.641-66.656-126.094C185.94,128.102,244.721,152.945,299.422,203.102z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M313.953,187.445
|
||||
c75.984,69.703,117.422,148.125,92.625,175.219c-24.844,27.047-106.547-7.453-182.529-77.156
|
||||
c-75.961-69.656-117.445-148.125-92.625-175.219C156.245,83.242,237.948,117.789,313.953,187.445z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M149.752,298.773
|
||||
c0,0-39.656-40.125-45.422-108.609c-3.211-33.938,12.844-87.844,37.078-92.156c24.305-4.312,38.391,2.203,61.406,11.203
|
||||
c0,0,44.016,19.078,100.263,66.047c56.203,46.969,94.5,102.891,111.844,146.156c1.969,4.828,12.469,28.781,0.188,45.656
|
||||
c-3.492,4.781-17.906,15-22.594,16.969c-5.719,2.391-20.578,11.625-57.234,15.656c0,0-45.422,6.422-104.623-29.719
|
||||
c0,0-3.844,7.031-6.047,10.219c-2.25,3.188-48.633,11.859-87,8.016c-38.391-3.844-55.031-13.781-55.031-13.781s-1.266,0,0-2.578
|
||||
C83.846,369.32,149.752,298.773,149.752,298.773z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M149.752,298.773
|
||||
c0,0,39.656,48.609,80.906,71.203"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
30
public/assets/images/lights/lc_bloomw.svg
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M287.609,219.602
|
||||
c32.25,29.578,49.617,63.141,38.719,75c-10.852,11.859-45.844-2.484-78.091-32.109c-32.273-29.578-49.594-63.188-38.719-75.094
|
||||
C220.393,175.539,255.338,189.93,287.609,219.602z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M299.422,203.102
|
||||
c54.656,50.203,84.516,106.594,66.656,126.094c-17.859,19.453-76.641-5.344-131.341-55.5
|
||||
c-54.656-50.109-84.516-106.641-66.656-126.094C185.94,128.102,244.721,152.945,299.422,203.102z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M313.953,187.445
|
||||
c75.984,69.703,117.422,148.125,92.625,175.219c-24.844,27.047-106.547-7.453-182.529-77.156
|
||||
c-75.961-69.656-117.445-148.125-92.625-175.219C156.245,83.242,237.948,117.789,313.953,187.445z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M149.752,298.773
|
||||
c0,0-39.656-40.125-45.422-108.609c-3.211-33.938,12.844-87.844,37.078-92.156c24.305-4.312,38.391,2.203,61.406,11.203
|
||||
c0,0,44.016,19.078,100.263,66.047c56.203,46.969,94.5,102.891,111.844,146.156c1.969,4.828,12.469,28.781,0.188,45.656
|
||||
c-3.492,4.781-17.906,15-22.594,16.969c-5.719,2.391-20.578,11.625-57.234,15.656c0,0-45.422,6.422-104.623-29.719
|
||||
c0,0-3.844,7.031-6.047,10.219c-2.25,3.188-48.633,11.859-87,8.016c-38.391-3.844-55.031-13.781-55.031-13.781s-1.266,0,0-2.578
|
||||
C83.846,369.32,149.752,298.773,149.752,298.773z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M149.752,298.773
|
||||
c0,0,39.656,48.609,80.906,71.203"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
29
public/assets/images/lights/lc_iris.svg
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M441.079,276.902
|
||||
c-11.391,22.406-65.766,30-182.438-56.388c-59.576-46.547-97.919-101.297-98.904-133.031c0.047-5.812,0.188-14.156,7.5-20.156
|
||||
c-26.297,23.531-115.406,123-103.828,222.56c11.625,99.656,102.234,142.125,144.281,147.234
|
||||
c26.625,3.188,57.326,0.047,86.294-11.625C355.438,400.793,414.267,339.105,441.079,276.902z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M365.61,232.936
|
||||
c-5.672,11.203-32.859,13.875-91.219-29.297c-29.857-23.344-46.873-45.281-47.435-63.328c0.047-4.969-1.828-17.578,15.938-17.812
|
||||
c18.513,0.234,49.357,13.125,82.732,42.047C342.735,177.999,375.501,211.092,365.61,232.936z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M195.597,59.827
|
||||
c36.984,0.562,98.763,26.25,165.513,84.234c56.953,48.562,92.344,105.937,79.969,132.935
|
||||
c-11.391,22.406-65.766,30.047-182.484-56.388c-59.576-46.594-97.966-101.344-98.951-133.125
|
||||
C159.738,77.639,160.113,60.342,195.597,59.827z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M136.535,348.434
|
||||
c-2.766-0.094-5.344,0.516-7.734,2.062c-4.969,3.422-9.094,17.203,6.469,29.812c15.609,12.562,32.672,18.938,41.859,11.578
|
||||
c3.656-2.953,3.516-7.172,1.219-11.812 M172.16,267.434c-7.406,24.047-14.766,62.625-40.594,85.875
|
||||
c-1.359,6.375,0,16.969,11.203,24.75c11.203,7.828,19.594,11.625,30.75,8.812c3.422-6.609,40.406-51.891,76.64-60.234
|
||||
M102.785,147.483c1.031,15.703,9.094,77.812,106.5,151.169c96.091,72.422,154.169,71.203,178.732,58.641"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
29
public/assets/images/lights/lc_irisw.svg
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M441.079,276.902
|
||||
c-11.391,22.406-65.766,30-182.438-56.388c-59.576-46.547-97.919-101.297-98.904-133.031c0.047-5.812,0.188-14.156,7.5-20.156
|
||||
c-26.297,23.531-115.406,123-103.828,222.56c11.625,99.656,102.234,142.125,144.281,147.234
|
||||
c26.625,3.188,57.326,0.047,86.294-11.625C355.438,400.793,414.267,339.105,441.079,276.902z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M365.61,232.936
|
||||
c-5.672,11.203-32.859,13.875-91.219-29.297c-29.857-23.344-46.873-45.281-47.435-63.328c0.047-4.969-1.828-17.578,15.938-17.812
|
||||
c18.513,0.234,49.357,13.125,82.732,42.047C342.735,177.999,375.501,211.092,365.61,232.936z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M195.597,59.827
|
||||
c36.984,0.562,98.763,26.25,165.513,84.234c56.953,48.562,92.344,105.937,79.969,132.935
|
||||
c-11.391,22.406-65.766,30.047-182.484-56.388c-59.576-46.594-97.966-101.344-98.951-133.125
|
||||
C159.738,77.639,160.113,60.342,195.597,59.827z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M136.535,348.434
|
||||
c-2.766-0.094-5.344,0.516-7.734,2.062c-4.969,3.422-9.094,17.203,6.469,29.812c15.609,12.562,32.672,18.938,41.859,11.578
|
||||
c3.656-2.953,3.516-7.172,1.219-11.812 M172.16,267.434c-7.406,24.047-14.766,62.625-40.594,85.875
|
||||
c-1.359,6.375,0,16.969,11.203,24.75c11.203,7.828,19.594,11.625,30.75,8.812c3.422-6.609,40.406-51.891,76.64-60.234
|
||||
M102.785,147.483c1.031,15.703,9.094,77.812,106.5,151.169c96.091,72.422,154.169,71.203,178.732,58.641"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
45
public/assets/images/lights/lightstrip.svg
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" d="M404.109,402.393v-64.711H186.448
|
||||
c0,0-92.288,2.23-92.288-61.205c0,57.201,0,59.703,0,59.703s-2.002,66.213,95.292,66.213
|
||||
C266.178,402.393,404.109,402.393,404.109,402.393z"/>
|
||||
|
||||
<rect x="127.516" y="116.977" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.563" height="15.518"/>
|
||||
|
||||
<rect x="187.722" y="116.977" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.563" height="15.518"/>
|
||||
|
||||
<rect x="247.926" y="116.977" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.499" height="15.518"/>
|
||||
|
||||
<rect x="308.09" y="116.977" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.541" height="15.518"/>
|
||||
<polygon fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" points="380.354,149.172 366.339,142.506
|
||||
366.111,126.192 380.127,132.836 "/>
|
||||
|
||||
<rect x="361.311" y="365.078" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.54" height="15.562"/>
|
||||
|
||||
<rect x="301.104" y="365.078" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.54" height="15.562"/>
|
||||
|
||||
<rect x="240.919" y="365.078" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.566" height="15.562"/>
|
||||
|
||||
<rect x="180.737" y="365.078" fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" width="15.563" height="15.562"/>
|
||||
<polygon fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" points="130.884,350.697 144.945,357.342
|
||||
144.809,373.678 130.748,367.035 "/>
|
||||
<path fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" d="M94.114,91.924v64.666h217.707
|
||||
c0,0,83.278-5.506,92.288,61.207c0-57.18,0-59.705,0-59.705s2.002-66.168-95.291-66.168
|
||||
C232.045,91.924,94.114,91.924,94.114,91.924z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" d="M129.382,326.67
|
||||
c0.819-11.195,7.281-26.826,34.494-35.654c20.068-6.508,139.185-13.289,139.185-13.289s97.498-1.25,100.911-60.75
|
||||
c-3.709-25.414-18.293-40.182-34.812-48.76c-0.5,11.832-6.735,28.146-34.768,35.041c-38.612,9.51-139.208,13.287-139.208,13.287
|
||||
s-98.659-1.684-100.957,61.728C94.934,303.779,110.906,318.342,129.382,326.67z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="6.9898" stroke-miterlimit="10" d="M94.16,279.957
|
||||
c0,0.182-0.045,0.342-0.045,0.547c0,0.158,0.045,0.295,0.045,0.477C94.16,280.617,94.16,280.299,94.16,279.957z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
45
public/assets/images/lights/lightstripw.svg
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" d="M404.109,402.393v-64.711H186.448
|
||||
c0,0-92.288,2.23-92.288-61.205c0,57.201,0,59.703,0,59.703s-2.002,66.213,95.292,66.213
|
||||
C266.178,402.393,404.109,402.393,404.109,402.393z"/>
|
||||
|
||||
<rect x="127.516" y="116.977" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.563" height="15.518"/>
|
||||
|
||||
<rect x="187.722" y="116.977" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.563" height="15.518"/>
|
||||
|
||||
<rect x="247.926" y="116.977" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.499" height="15.518"/>
|
||||
|
||||
<rect x="308.09" y="116.977" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.541" height="15.518"/>
|
||||
<polygon fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" points="380.354,149.172 366.339,142.506
|
||||
366.111,126.192 380.127,132.836 "/>
|
||||
|
||||
<rect x="361.311" y="365.078" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.54" height="15.562"/>
|
||||
|
||||
<rect x="301.104" y="365.078" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.54" height="15.562"/>
|
||||
|
||||
<rect x="240.919" y="365.078" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.566" height="15.562"/>
|
||||
|
||||
<rect x="180.737" y="365.078" fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" width="15.563" height="15.562"/>
|
||||
<polygon fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" points="130.884,350.697 144.945,357.342
|
||||
144.809,373.678 130.748,367.035 "/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" d="M94.114,91.924v64.666h217.707
|
||||
c0,0,83.278-5.506,92.288,61.207c0-57.18,0-59.705,0-59.705s2.002-66.168-95.291-66.168
|
||||
C232.045,91.924,94.114,91.924,94.114,91.924z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" d="M129.382,326.67
|
||||
c0.819-11.195,7.281-26.826,34.494-35.654c20.068-6.508,139.185-13.289,139.185-13.289s97.498-1.25,100.911-60.75
|
||||
c-3.709-25.414-18.293-40.182-34.812-48.76c-0.5,11.832-6.735,28.146-34.768,35.041c-38.612,9.51-139.208,13.287-139.208,13.287
|
||||
s-98.659-1.684-100.957,61.728C94.934,303.779,110.906,318.342,129.382,326.67z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="6.9898" stroke-miterlimit="10" d="M94.16,279.957
|
||||
c0,0.182-0.045,0.342-0.045,0.547c0,0.158,0.045,0.295,0.045,0.477C94.16,280.617,94.16,280.299,94.16,279.957z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
42
public/assets/images/lights/storylight.svg
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M249.167,347.182
|
||||
c46.219,49.922,139.031,114.234,209.227,74.719c49.617-28.172,21.727-114.234-6.75-152.016
|
||||
c-38.648-52.129-67.758-73.879-84.258-84.004c-2.602-2.016-7.875-5.203-1.852-5.203c6,0,35.156,3.188,34.969-29.672
|
||||
c-0.211-32.906-39.305-77.25-78.961-95.906c-39.68-18.609-72.375-6.562-66.188,25.359c6.188,32.016,34.219,59.438,46.828,69.797
|
||||
c2.625,2.438,5.812,6-1.312,3.938c-7.172-2.062-41.766-9.984-65.812-2.25c-24.094,7.688-32.156,21.234-35.156,25.172
|
||||
c-0.188,1.547-2.836,4.5-5.086-1.5s-16.359-48.094-68.062-78.188C96.026,79.521,50.229,66.208,23.534,91.943
|
||||
s9.398,84.234,32.344,102.281s76.898,57.895,124.828,32.907c2.836-1.312,8.086-6.563,9.023,2.813
|
||||
C190.667,239.367,196.128,288.588,249.167,347.182z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M176.792,229.006
|
||||
c-1.734,22.175-12.984,95.16,64.828,169.597c77.859,74.438,188.531,62.203,227.016,15.656"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M210.917,363.447
|
||||
c-8.859,7.969-31.406,30.562-33.703,32.766c-2.25,2.297-15.469,11.859-3.656,19.172c11.859,7.359,30.984,15.75,70.805,14.953
|
||||
c8.461,0,24.375-1.734,33.633-4.219"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M176.464,396.916
|
||||
c5.859,6.656,19.172,24.188,85.734,18.891"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M235.62,161.927
|
||||
c-27.094,9.562-83.109,78.284,40.594,201.52c36.867,32.156,126.727,88.922,180.891,44.578
|
||||
c54.117-44.391-14.297-139.172-25.594-153.047c-5.766-7.125-31.781-40.551-71.438-66.192
|
||||
C322.245,164.318,269.37,148.896,235.62,161.927z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M251.183,193.99
|
||||
c-21.797,7.734-66.891,63.051,32.672,162.239c29.672,25.828,101.977,71.484,145.547,35.812
|
||||
c43.594-35.719-11.484-111.984-20.578-123.188c-4.641-5.719-25.547-32.627-57.492-53.254
|
||||
C320.909,195.912,278.37,183.49,251.183,193.99z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M52.011,92.13
|
||||
c-17.812,5.391-49.875,36.516,20.953,100.5c20.016,15.984,68.977,44.393,98.414,22.219c29.461-22.172-4.734-69.188-10.898-76.172
|
||||
c-3.117-3.562-20.297-20.578-41.883-33.375C97.995,93.115,70.386,85.662,52.011,92.13z"/>
|
||||
<path fill="none" stroke="#000000" stroke-width="4.8" stroke-miterlimit="10" d="M282.894,58.943
|
||||
c-16.57,4.172-30.914,30.516,20.508,80.297c14.531,12.516,55.969,40.5,77.344,23.25c21.375-17.297-8.062-55.969-12.867-61.078
|
||||
c-4.055-4.312-17.039-18.562-32.695-28.547C320.229,63.349,296.229,53.88,282.894,58.943z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
42
public/assets/images/lights/storylightw.svg
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="500px"
|
||||
height="500px" viewBox="0 0 500 500" enable-background="new 0 0 500 500" xml:space="preserve">
|
||||
<g id="Layer_1" display="none">
|
||||
<rect display="inline" width="500" height="500"/>
|
||||
</g>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M249.167,347.182
|
||||
c46.219,49.922,139.031,114.234,209.227,74.719c49.617-28.172,21.727-114.234-6.75-152.016
|
||||
c-38.648-52.129-67.758-73.879-84.258-84.004c-2.602-2.016-7.875-5.203-1.852-5.203c6,0,35.156,3.188,34.969-29.672
|
||||
c-0.211-32.906-39.305-77.25-78.961-95.906c-39.68-18.609-72.375-6.562-66.188,25.359c6.188,32.016,34.219,59.438,46.828,69.797
|
||||
c2.625,2.438,5.812,6-1.312,3.938c-7.172-2.062-41.766-9.984-65.812-2.25c-24.094,7.688-32.156,21.234-35.156,25.172
|
||||
c-0.188,1.547-2.836,4.5-5.086-1.5s-16.359-48.094-68.062-78.188C96.026,79.521,50.229,66.208,23.534,91.943
|
||||
s9.398,84.234,32.344,102.281s76.898,57.895,124.828,32.907c2.836-1.312,8.086-6.563,9.023,2.813
|
||||
C190.667,239.367,196.128,288.588,249.167,347.182z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M176.792,229.006
|
||||
c-1.734,22.175-12.984,95.16,64.828,169.597c77.859,74.438,188.531,62.203,227.016,15.656"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M210.917,363.447
|
||||
c-8.859,7.969-31.406,30.562-33.703,32.766c-2.25,2.297-15.469,11.859-3.656,19.172c11.859,7.359,30.984,15.75,70.805,14.953
|
||||
c8.461,0,24.375-1.734,33.633-4.219"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M176.464,396.916
|
||||
c5.859,6.656,19.172,24.188,85.734,18.891"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M235.62,161.927
|
||||
c-27.094,9.562-83.109,78.284,40.594,201.52c36.867,32.156,126.727,88.922,180.891,44.578
|
||||
c54.117-44.391-14.297-139.172-25.594-153.047c-5.766-7.125-31.781-40.551-71.438-66.192
|
||||
C322.245,164.318,269.37,148.896,235.62,161.927z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M251.183,193.99
|
||||
c-21.797,7.734-66.891,63.051,32.672,162.239c29.672,25.828,101.977,71.484,145.547,35.812
|
||||
c43.594-35.719-11.484-111.984-20.578-123.188c-4.641-5.719-25.547-32.627-57.492-53.254
|
||||
C320.909,195.912,278.37,183.49,251.183,193.99z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M52.011,92.13
|
||||
c-17.812,5.391-49.875,36.516,20.953,100.5c20.016,15.984,68.977,44.393,98.414,22.219c29.461-22.172-4.734-69.188-10.898-76.172
|
||||
c-3.117-3.562-20.297-20.578-41.883-33.375C97.995,93.115,70.386,85.662,52.011,92.13z"/>
|
||||
<path fill="none" stroke="#FFFFFF" stroke-width="4.8" stroke-miterlimit="10" d="M282.894,58.943
|
||||
c-16.57,4.172-30.914,30.516,20.508,80.297c14.531,12.516,55.969,40.5,77.344,23.25c21.375-17.297-8.062-55.969-12.867-61.078
|
||||
c-4.055-4.312-17.039-18.562-32.695-28.547C320.229,63.349,296.229,53.88,282.894,58.943z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/assets/images/logo.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
public/assets/images/missingArtwork.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/assets/images/pressButtonBridge.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
public/assets/images/sc-white-sm.png
Normal file
|
After Width: | Height: | Size: 292 B |
BIN
public/assets/images/sc-white.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
12
public/browserconfig.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square70x70logo src="/mstile-70x70.png"/>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<square310x310logo src="/mstile-310x310.png"/>
|
||||
<wide310x150logo src="/mstile-310x150.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
15
public/crossdomain.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
|
||||
<cross-domain-policy>
|
||||
<!-- Read this: www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html -->
|
||||
|
||||
<!-- Most restrictive policy: -->
|
||||
<site-control permitted-cross-domain-policies="none"/>
|
||||
|
||||
<!-- Least restrictive policy: -->
|
||||
<!--
|
||||
<site-control permitted-cross-domain-policies="all"/>
|
||||
<allow-access-from domain="*" to-ports="*" secure="false"/>
|
||||
<allow-http-request-headers-from domain="*" headers="*" secure="false"/>
|
||||
-->
|
||||
</cross-domain-policy>
|
||||
BIN
public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/favicon-194x194.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
public/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
13
public/humans.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/* TEAM */
|
||||
Your title: Egor Philippov
|
||||
Site: https://www.linkedin.com/pub/egor-philippov/7b/220/148
|
||||
Location: Vancouver, Canada.
|
||||
|
||||
/* THANKS */
|
||||
Edmond Cheung - favicons + huegasm logo
|
||||
Liviu Antonescu - filming + video editing of the intro
|
||||
|
||||
/* SITE */
|
||||
Last update: 2015
|
||||
Standards: HTML5, CSS3
|
||||
Components: ember, jQuery, bootstrap, font-awesome, intro.js, locallyjs, nouislider, dancer.js, jquery-mousewheel, ember paper, ember notify, JavaScript-ID3-Reader
|
||||
41
public/manifest.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "Huegasm",
|
||||
"icons": [
|
||||
{
|
||||
"src": "\/android-chrome-36x36.png",
|
||||
"sizes": "36x36",
|
||||
"type": "image\/png",
|
||||
"density": "0.75"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image\/png",
|
||||
"density": "1.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image\/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image\/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image\/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
public/mstile-144x144.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
public/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
public/mstile-310x150.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |