<commit_before>/*******************************************************************************************************
 * File: sidebar.js
 * Project: hootenanny-ui
 * @author Matt Putipong - matt.putipong@radiantsolutions.com on 4/10/18
 *******************************************************************************************************/

import _                    from 'lodash-es';
import LayerManager         from './managers/layerManager';
import Event                from './managers/eventManager';
import SidebarLayerAdd      from './forms/sidebarLayerAdd';
import SidebarLayerConflate from './forms/sidebarLayerConflate';
import SidebarLayerReview   from './forms/sidebarLayerReview';
import { sidebarForms }     from './config/formMetadata';

/**
 * Create the sidebar
 *
 * @constructor
 */
export default class Sidebar {
    constructor( container, context ) {
        this.context          = context;
        this.container        = container;
        this.formData         = sidebarForms;
        this.addFormData      = _.filter( this.formData, form => form.type === 'add' );
        this.conflateFormData = _.filter( this.formData, form => form.type === 'conflate' );
        this.reviewFormData   = _.filter( this.formData, form => form.type === 'review' );
        this.addForms         = {};
        this.forms = {};
    }

    /**
     * Render all view inside sidebar
     */
    async render() {
        this.container.classed( 'col4', false );

        this.createResizer();
        this.createWrapper();
        this.createForms();

        this.listen();
    }

    /**
     * Create resize bar and attach d3 drag behavior
     */
    createResizer() {
        const self = this;

        this.resizer = this.container.append( 'div' )
            .attr( 'id', 'sidebar-resizer' )
            .on( 'dblclick', function() {
                self.resize( this, true );
            } );

        this.dragResize = d3.drag().on( 'drag', function() {
            self.resize( this );
        } );

        this.resizer.call( this.dragResize );
    }

    /**
     * Resize event
     *
     * @param target - resize bar
     * @param reset - whether to reset to original width or not
     */
    resize( target, reset ) {
        let width = reset ? 400 : d3.mouse( target.parentNode )[ 0 ],
            sidebarWidth;

        this.container.style( 'width', width + 'px' );

        sidebarWidth = this.container.node().getBoundingClientRect().width;

        d3.select( '#bar' ).style( 'width', `calc(100% - ${ sidebarWidth }px)` );
    }

    createWrapper() {
        this.wrapper = this.container.append( 'div' )
            .classed( 'wrapper', true );
    }

    /**
     * Bind form data and create a form for each item
     */
    createForms() {
        let sidebar = this;

        this.wrapper.selectAll( '.layer-add' )
            .data( this.addFormData ).enter()
            .select( function( d ) {
                sidebar.forms[ d.id ] = new SidebarLayerAdd( sidebar, d3.select( this ) );
                sidebar.forms[ d.id ].render();

                //sidebar.addForms[ d.id ] = new SidebarLayerAdd( sidebar, d3.select( this ) );
                //sidebar.addForms[ d.id ].render();
            } );

        this.wrapper.selectAll( '.layer-conflate' )
            .data( this.conflateFormData ).enter()
            .select( function() {
                sidebar.conflateForm = new SidebarLayerConflate( sidebar, d3.select( this ) );
            } );
    }

    layerRemoved( data ) {
        this.forms[ data.id ].render( data );
    }

    layerMerged( layer ) {
        let sidebar = this;

        this.wrapper.selectAll( '.layer-review' )
            .data( this.reviewFormData ).enter()
            .select( function() {
                this.reviewLayer = new SidebarLayerReview( sidebar, d3.select( this ) );

                this.reviewLayer.render( layer );
            } );

        //this.reviewLayer = new SidebarLayerReview( layer );
        //
        //this.reviewLayer.render( this, d3.select( this.wrapper ) );
    }

    conflateCheck() {
        let loadedLayers   = Object.values( LayerManager.loadedLayers ),
            addControllers = d3.selectAll( '.add-controller' );

        if ( loadedLayers.length === 2 ) {
            if ( !this.conflateForm.exists ) {
                this.conflateForm.render( loadedLayers );
            }
        } else if ( addControllers.size() > 0 ) {
            this.conflateForm.remove();
        }
    }

    listen() {
        Event.listen( 'layer-removed', this.layerRemoved, this );
        Event.listen( 'layer-merged', this.layerMerged, this );
    }
}<commit_msg>fix toolbar width when resizing sidebar<commit_after>/*******************************************************************************************************
 * File: sidebar.js
 * Project: hootenanny-ui
 * @author Matt Putipong - matt.putipong@radiantsolutions.com on 4/10/18
 *******************************************************************************************************/

import _                    from 'lodash-es';
import LayerManager         from './managers/layerManager';
import Event                from './managers/eventManager';
import SidebarLayerAdd      from './forms/sidebarLayerAdd';
import SidebarLayerConflate from './forms/sidebarLayerConflate';
import SidebarLayerReview   from './forms/sidebarLayerReview';
import { sidebarForms }     from './config/formMetadata';

/**
 * Create the sidebar
 *
 * @constructor
 */
export default class Sidebar {
    constructor( container, context ) {
        this.context          = context;
        this.container        = container;
        this.formData         = sidebarForms;
        this.addFormData      = _.filter( this.formData, form => form.type === 'add' );
        this.conflateFormData = _.filter( this.formData, form => form.type === 'conflate' );
        this.reviewFormData   = _.filter( this.formData, form => form.type === 'review' );
        this.addForms         = {};
        this.forms = {};
    }

    /**
     * Render all view inside sidebar
     */
    async render() {
        this.container.classed( 'col4', false );

        this.createResizer();
        this.createWrapper();
        this.createForms();

        this.listen();
    }

    /**
     * Create resize bar and attach d3 drag behavior
     */
    createResizer() {
        const self = this;

        this.resizer = this.container.append( 'div' )
            .attr( 'id', 'sidebar-resizer' )
            .on( 'dblclick', function() {
                self.resize( this, true );
            } );

        this.dragResize = d3.drag().on( 'drag', function() {
            self.resize( this );
        } );

        this.resizer.call( this.dragResize );
    }

    /**
     * Resize event
     *
     * @param target - resize bar
     * @param reset - whether to reset to original width or not
     */
    resize( target, reset ) {
        let width = reset ? 400 : d3.mouse( target.parentNode )[ 0 ],
            sidebarWidth;

        this.container.style( 'width', width + 'px' );

        sidebarWidth = this.container.node().getBoundingClientRect().width;
        d3.select( '#bar' ).style( 'left', `${ sidebarWidth }px` );
    }

    createWrapper() {
        this.wrapper = this.container.append( 'div' )
            .classed( 'wrapper', true );
    }

    /**
     * Bind form data and create a form for each item
     */
    createForms() {
        let sidebar = this;

        this.wrapper.selectAll( '.layer-add' )
            .data( this.addFormData ).enter()
            .select( function( d ) {
                sidebar.forms[ d.id ] = new SidebarLayerAdd( sidebar, d3.select( this ) );
                sidebar.forms[ d.id ].render();

                //sidebar.addForms[ d.id ] = new SidebarLayerAdd( sidebar, d3.select( this ) );
                //sidebar.addForms[ d.id ].render();
            } );

        this.wrapper.selectAll( '.layer-conflate' )
            .data( this.conflateFormData ).enter()
            .select( function() {
                sidebar.conflateForm = new SidebarLayerConflate( sidebar, d3.select( this ) );
            } );
    }

    layerRemoved( data ) {
        this.forms[ data.id ].render( data );
    }

    layerMerged( layer ) {
        let sidebar = this;

        this.wrapper.selectAll( '.layer-review' )
            .data( this.reviewFormData ).enter()
            .select( function() {
                this.reviewLayer = new SidebarLayerReview( sidebar, d3.select( this ) );

                this.reviewLayer.render( layer );
            } );

        //this.reviewLayer = new SidebarLayerReview( layer );
        //
        //this.reviewLayer.render( this, d3.select( this.wrapper ) );
    }

    conflateCheck() {
        let loadedLayers   = Object.values( LayerManager.loadedLayers ),
            addControllers = d3.selectAll( '.add-controller' );

        if ( loadedLayers.length === 2 ) {
            if ( !this.conflateForm.exists ) {
                this.conflateForm.render( loadedLayers );
            }
        } else if ( addControllers.size() > 0 ) {
            this.conflateForm.remove();
        }
    }

    listen() {
        Event.listen( 'layer-removed', this.layerRemoved, this );
        Event.listen( 'layer-merged', this.layerMerged, this );
    }
}<|endoftext|>


<commit_before>/*  checkforcode.js by David Jerome @GlassToeStudio - GlassToeStudio@gmail.com

    14 July, 2017
    https://github.com/GlassToeStudio
    http://glasstoestudio.weebly.com/
    https://twitter.com/GlassToeStudio

    ------------------------------------------------------------------------
    Systematically search through Discord comments to find unformatted Code.

        * Search is based in chars in codeElements array.
            * Default:  [';', '{', '}', ')']
        * Bot will parse the code line by line searchig for
        * code elements and keep track of which line are code
        * and which are plain text.
        * The bot will do his best to only format a true code
        * block, leaveing the plain text alone. One complte
        * the bot will add code block formattign aroind the
        * code block, with the current code lang 'csharp'.
        * The message will be posted anew as formatted code
        * and, if possible, the old message will be deleted.
        *
        * If the informatted code is posted in a channel with
        * 'help' in the tile, then the new messge is posted
        * there.
        * If the message is posted in any other chnannel, the
        * new message will be posted in #programing_help (if it exists)
        * If there is no programing_help channel, the message is
        * posted in the original channel.
        *
        * When a new message is posted in a new channel, the user
        * is notified that the post has been moved, with a link
        * to the channel. The new post in the new channel will
        * also mention the user, as an added bonus to help
        * the user navigate the newmessage.

    EXAMPLE-----------------------------------------------------------------

    THis is not code so it is not in the code block.

    ```csharp
    using UnityEngine;

    /// <summary>
    /// Class summary
    /// </summary>
    public class MyClass
    {
        /// <summary>   Summary of myFloat. </summary>
        public float myFloat;
        /// <summary>   Summary of myOtherFloat.</summary>
        public float myOtherFLoat;

        // Default constructor
        public MyClass(float _myFloat, float _myOtherFLoat)
        {
            this.myFloat = _myFloat;
            this.myOtherFLoat = _myOtherFLoat;
        }
    }
    ```

    THis is not code so it is not in the code block.

    ------------------------------------------------------------------------

    Discord Markdown 101 for more formatting guidelines:
    https://support.discordapp.com/hc/en-us/articles/210298617-Markdown-Text-101-Chat-Formatting-Bold-Italic-Underline-?page=4
*/

const Discord = require('discord.js');
const GlassBot = require('../bot.js')
const bot = GlassBot.bot
const config = GlassBot.config

// Salt to taste
const codeElements = [';', '{', '}', ')'] // Could be in config
const codeLang = 'csharp'
const repostThreshold = 4

// Variables
var isFormatted = false
var totalLinesOfCode = 0
var firstLine = false
var lastLine = 0

// Lets begin
bot.on('message', message => {
    if(message.author.bot){
        // If this is our reply to the user, delete after 10 seconds
        if(message.content.includes('Your unformatted code')){
            message.delete(10000)
            .then(msg => console.log(`Deleted message from ${msg.author}`))
            .catch(console.error);
        }
        return
    }
    if(message.content.length > 1900) return
    InitVariables()

    let lines = message.content.split('\n')

    ParseMessage(lines)

    if(IsBadCode() && !isFormatted){

        lines[lastLine] = FormatLastLine(lines[lastLine])

        CreateNewMessage(lines, message)
    }
    return
});

// Loop through each line in message and check for
// code-like characters. If code formatting is found
// return, else keep checking.
function ParseMessage(lines){
    for(let i = 0; i < lines.length; i++){
        if(lines[i].search("```") >= 0){
            isFormatted = true
            return
        }else
            FindCodeElements(i, lines[i], lines)
    }
    return
}

// Checks the last character in a string to see of it machess a code-like character
function FindCodeElements(index, line, lines){
    let lineLength = line.length - 1
    for(let i = 0; i < codeElements.length; i++){
        if (line.charAt(lineLength).valueOf() == codeElements[i].valueOf()){
            if(!firstLine){
                lines[index] = FormatFirstLine(line)
                return
            }else{
                lastLine = index
                totalLinesOfCode += 1
                return
            }
        }
    }
    return
}

// Recreate the new message with code formatting included
function CreateNewMessage(lines, message){
    let newMessage = ""
    for(let j = 0; j < lines.length; j++)
       newMessage += lines[j] + '\n'

    PostNewMessage(message, newMessage)
}

// Post the formatted message in the appropriate channel
function PostNewMessage(message, newMessage){
    let channel = message.guild.channels.find("name", "programing_help")
    let isHelp = message.channel.name.indexOf('help') > 0

    if(channel != null && channel != message.channel && !isHelp){
        // TODO: Would like to add some color to this message
        message.reply(':nerd: __`Your unformatted code has been formatted and moved to`__ ' + channel + '. :nerd: \nThis message will self-destruct in 10 seconds')
        channel.send(message.author + ' **★★ I have formatted your code and placed it here. Good Luck! ★★** ')
        channel.send(newMessage);
    }else{
        message.reply(' **★★ I see you forgot to format your code... Let me help you. ★★** ')
        message.channel.send(newMessage)
    }

    DeleteOldMessage(message)
}

// Deletes the old unformatted messge if bot has permission
function DeleteOldMessage(message){
    let managePerms = message.guild.member(bot.user).hasPermission('MANAGE_MESSAGES')
    if(managePerms)
        message.delete()
    else
        message.channel.send('**`Tell the server\'s owner to grant me permission to delete your old message, thank\'s`** :wink:')
}

// Adds code formatting block start to the first line of code
function FormatFirstLine(inLine){
    firstLine = true
    return '```' + codeLang + '\n' + inLine
}

// Add formatting code bock end to the last line of code
function FormatLastLine(inLine){
    return inLine + '\n```'
}

// If total line of code is greater than repostThreshold return true
function IsBadCode(){
    return (totalLinesOfCode >= repostThreshold)
}

// Initalize variables
function InitVariables(){
    isFormatted = false
    totalLinesOfCode = 0;
    firstLine = false
    lastLine = 0
}<commit_msg>Test updating self-destruct message<commit_after>/*  checkforcode.js by David Jerome @GlassToeStudio - GlassToeStudio@gmail.com

    14 July, 2017
    https://github.com/GlassToeStudio
    http://glasstoestudio.weebly.com/
    https://twitter.com/GlassToeStudio

    ------------------------------------------------------------------------
    Systematically search through Discord comments to find unformatted Code.

        * Search is based in chars in codeElements array.
            * Default:  [';', '{', '}', ')']
        * Bot will parse the code line by line searchig for
        * code elements and keep track of which line are code
        * and which are plain text.
        * The bot will do his best to only format a true code
        * block, leaveing the plain text alone. One complte
        * the bot will add code block formattign aroind the
        * code block, with the current code lang 'csharp'.
        * The message will be posted anew as formatted code
        * and, if possible, the old message will be deleted.
        *
        * If the informatted code is posted in a channel with
        * 'help' in the tile, then the new messge is posted
        * there.
        * If the message is posted in any other chnannel, the
        * new message will be posted in #programing_help (if it exists)
        * If there is no programing_help channel, the message is
        * posted in the original channel.
        *
        * When a new message is posted in a new channel, the user
        * is notified that the post has been moved, with a link
        * to the channel. The new post in the new channel will
        * also mention the user, as an added bonus to help
        * the user navigate the newmessage.

    EXAMPLE-----------------------------------------------------------------

    THis is not code so it is not in the code block.

    ```csharp
    using UnityEngine;

    /// <summary>
    /// Class summary
    /// </summary>
    public class MyClass
    {
        /// <summary>   Summary of myFloat. </summary>
        public float myFloat;
        /// <summary>   Summary of myOtherFloat.</summary>
        public float myOtherFLoat;

        // Default constructor
        public MyClass(float _myFloat, float _myOtherFLoat)
        {
            this.myFloat = _myFloat;
            this.myOtherFLoat = _myOtherFLoat;
        }
    }
    ```

    THis is not code so it is not in the code block.

    ------------------------------------------------------------------------

    Discord Markdown 101 for more formatting guidelines:
    https://support.discordapp.com/hc/en-us/articles/210298617-Markdown-Text-101-Chat-Formatting-Bold-Italic-Underline-?page=4
*/

const Discord = require('discord.js');
const GlassBot = require('../bot.js')
const bot = GlassBot.bot
const config = GlassBot.config

// Salt to taste
const codeElements = [';', '{', '}', ')'] // Could be in config
const codeLang = 'csharp'
const repostThreshold = 4

// Variables
var isFormatted = false
var totalLinesOfCode = 0
var firstLine = false
var lastLine = 0

// Lets begin
bot.on('message', message => {
    if(message.author.bot){
        // If this is our reply to the user, delete after 10 seconds
        if(message.content.includes('Your unformatted code')){
           // message.delete(10000)
           // .then(msg => console.log(`Deleted message from ${msg.author}`))
           // .catch(console.error);
           callNTimes(10, 10000, EditBotMessage, message)
        }
        return
    }
    if(message.content.length > 1900) return
    InitVariables()

    let lines = message.content.split('\n')

    ParseMessage(lines)

    if(IsBadCode() && !isFormatted){

        lines[lastLine] = FormatLastLine(lines[lastLine])

        CreateNewMessage(lines, message)
    }
    return
});

// Loop through each line in message and check for
// code-like characters. If code formatting is found
// return, else keep checking.
function ParseMessage(lines){
    for(let i = 0; i < lines.length; i++){
        if(lines[i].search("```") >= 0){
            isFormatted = true
            return
        }else
            FindCodeElements(i, lines[i], lines)
    }
    return
}

// Checks the last character in a string to see of it machess a code-like character
function FindCodeElements(index, line, lines){
    let lineLength = line.length - 1
    for(let i = 0; i < codeElements.length; i++){
        if (line.charAt(lineLength).valueOf() == codeElements[i].valueOf()){
            if(!firstLine){
                lines[index] = FormatFirstLine(line)
                return
            }else{
                lastLine = index
                totalLinesOfCode += 1
                return
            }
        }
    }
    return
}

// Recreate the new message with code formatting included
function CreateNewMessage(lines, message){
    let newMessage = ""
    for(let j = 0; j < lines.length; j++)
       newMessage += lines[j] + '\n'

    PostNewMessage(message, newMessage)
}

// Post the formatted message in the appropriate channel
function PostNewMessage(message, newMessage){
    let channel = message.guild.channels.find("name", "programing_help")
    let isHelp = message.channel.name.indexOf('help') > 0

    if(channel != null && channel != message.channel && !isHelp){
        // TODO: Would like to add some color to this message
        message.reply(':nerd: __`Your unformatted code has been formatted and moved to`__ ' + channel + '. :nerd: \n\t*This message will self-destruct in 10 seconds*')
        channel.send(message.author + ' **★★ I have formatted your code and placed it here. Good Luck! ★★** ')
        channel.send(newMessage);
    }else{
        message.reply(' **★★ I see you forgot to format your code... Let me help you. ★★** ')
        message.channel.send(newMessage)
    }

    DeleteOldMessage(message)
}

// Deletes the old unformatted messge if bot has permission
function DeleteOldMessage(message){
    let managePerms = message.guild.member(bot.user).hasPermission('MANAGE_MESSAGES')
    if(managePerms)
        message.delete()
    else
        message.channel.send('**`Tell the server\'s owner to grant me permission to delete your old message, thank\'s`** :wink:')
}

// Adds code formatting block start to the first line of code
function FormatFirstLine(inLine){
    firstLine = true
    return '```' + codeLang + '\n' + inLine
}

// Add formatting code bock end to the last line of code
function FormatLastLine(inLine){
    return inLine + '\n```'
}

// If total line of code is greater than repostThreshold return true
function IsBadCode(){
    return (totalLinesOfCode >= repostThreshold)
}

// Initalize variables
function InitVariables(){
    isFormatted = false
    totalLinesOfCode = 0;
    firstLine = false
    lastLine = 0
}

function EditBotMessage(message, t){
    message.edit(':nerd: __`Your unformatted code has been formatted and moved to`__ ' + channel + '. :nerd: \n\t*This message will self-destruct in ${t} seconds*')
}

function callNTimes(n, time, fn, msg) {
  function callFn() {
    if (--n < 0) return;
    fn(msg, n);
    setTimeout(callFn, time);
  }
  setTimeout(callFn, time);
}<|endoftext|>


<commit_before>/*  checkforcode.js by David Jerome @GlassToeStudio - GlassToeStudio@gmail.com

    14 July, 2017
    https://github.com/GlassToeStudio
    http://glasstoestudio.weebly.com/
    https://twitter.com/GlassToeStudio

    ------------------------------------------------------------------------
    Systematically search through Discord comments to find unformatted Code.

        * Look for lines ending in ; { } )

        * If found, Locate the first Code Block Line
        * If found, Locate the last Code Block Line
        *
        * RePost the message surrounded by code formatting ```csharp ```

    EXAMPLE-----------------------------------------------------------------

    ```csharp
    using UnityEngine;

    /// <summary>
    /// Class summary
    /// </summary>
    public class MyClass
    {
        /// <summary>   Summary of myFloat. </summary>
        public float myFloat;
        /// <summary>   Summary of myOtherFloat.</summary>
        public float myOtherFLoat;

        // Default constructor
        public MyClass(float _myFloat, float _myOtherFLoat)
        {
            this.myFloat = _myFloat;
            this.myOtherFLoat = _myOtherFLoat;
        }
    }
    ```
    ------------------------------------------------------------------------
*/

const Discord = require('discord.js');
const GlassBot = require('../bot.js')
const bot = GlassBot.bot
const config = GlassBot.config

var isFormatted = false
var totalLinesOfCode = 0
var lines = []
var codeLines = []
var codeElements = [';', '{', '}', ')']
var repostThreshold = 4

bot.on('message', message => {
    if (message.author.bot){
        return
    }

    if(message.content.length > 1900){
        return
    }

    isFormatted = false
    totalLinesOfCode = 0;

    lines = message.content.split('\n')

    checkMessageForCode(lines)

    if(isBadCode() && !isFormatted){

        let firstLine = Math.min.apply(Math, codeLines)
        let lastLine  = Math.max.apply(Math, codeLines) + 2

        lines.splice(firstLine, 0, '```csharp\n\t')
        lines.splice(lastLine,  0, '\n```\n'    )

        let strmessage = ""
        // Recreate the message.content with the code wrapped in ```
        for (let j = 0; j < lines.length; j++){
           strmessage += lines[j] + '\n'
        }

        // TODO: Check if channel name contains help, if so just paste the new code here
        // else paste it in programing_help
        let channel = message.guild.channels.find("name", "programing_help")
        let channelName = message.channel.name
        let isHelp = channelName.indexOf('help') > 0
        console.log(isHelp)

        if(channel != null && channel != message.channel && !isHelp){
            // TODO: Would like to add alink to #programming help for user friendliness :D
            // TODO: Would like to add some color to this message also
            // Maybe make it bold
            message.channel.send('__`Your unformatted code has been formatted and moved to`__ ' + '#'+ channel.name + '.\n`Which makes sense...`')
            channel.send('**`I have formated your code and placed it here. Good Luck!.`**')
            channel.send(strmessage);
        }
        else{
                message.channel.send('**`I see you forgot to format your code... Let me help you.`**')
                message.channel.send(strmessage)
            }
    }
    return
});

// Loop through each line in message and check for
// code-like characters. If code formaatting is found
// return else keep checking.
function checkMessageForCode(inputLines){
    for(let i = 0; i < inputLines.length; i++){
        //let line = inputLines[i].replace(/\s"/,'')
        if(inputLines[i].search("```") >= 0){
            isFormatted = true
            return
        }
        else{
            checkLastCharacter(i, inputLines[i])
        }
    }
    return
}

// Checks the last character in a string to see of it machess a code-like character (inChar)
function checkLastCharacter(index, inLine){
    let lineLength = inLine.length - 1
    for(let i = 0; i < codeElements.length; i++){
        if(inLine.charAt(lineLength).valueOf() == codeElements[i].valueOf()){
            codeLines.push(index)
            totalLinesOfCode += 1
            return
        }
    }
    return
}

// Checks the total number of code like elements in an unformatted
// code block. If greater than repostThreshold than this is bad code lol, return true
function isBadCode(){
    if (totalLinesOfCode >= repostThreshold){
        return true
    }
    else{
        return false
    }
}<commit_msg>fin<commit_after>/*  checkforcode.js by David Jerome @GlassToeStudio - GlassToeStudio@gmail.com

    14 July, 2017
    https://github.com/GlassToeStudio
    http://glasstoestudio.weebly.com/
    https://twitter.com/GlassToeStudio

    ------------------------------------------------------------------------
    Systematically search through Discord comments to find unformatted Code.

        * Look for lines ending in ; { } )

        * If found, Locate the first Code Block Line
        * If found, Locate the last Code Block Line
        *
        * RePost the message surrounded by code formatting ```csharp ```

    EXAMPLE-----------------------------------------------------------------

    ```csharp
    using UnityEngine;

    /// <summary>
    /// Class summary
    /// </summary>
    public class MyClass
    {
        /// <summary>   Summary of myFloat. </summary>
        public float myFloat;
        /// <summary>   Summary of myOtherFloat.</summary>
        public float myOtherFLoat;

        // Default constructor
        public MyClass(float _myFloat, float _myOtherFLoat)
        {
            this.myFloat = _myFloat;
            this.myOtherFLoat = _myOtherFLoat;
        }
    }
    ```
    ------------------------------------------------------------------------
*/

const Discord = require('discord.js');
const GlassBot = require('../bot.js')
const bot = GlassBot.bot
const config = GlassBot.config

// Variables
var isFormatted = false
var totalLinesOfCode = 0
var codeLines = []
var codeElements = [';', '{', '}', ')']
var repostThreshold = 4

bot.on('message', message => {
    if (message.author.bot) return

    if(message.content.length > 1900) return

    isFormatted = false
    totalLinesOfCode = 0;

    let lines = message.content.split('\n')

    checkMessageForCode(lines)

    if(isBadCode() && !isFormatted){

        let firstLine = Math.min.apply(Math, codeLines)
        let lastLine  = Math.max.apply(Math, codeLines) + 2

        lines.splice(firstLine, 0, '```csharp\n')
        lines.splice(lastLine,  0, '\n```\n'    )

        let strmessage = ""
        // Recreate the message.content with the code wrapped in ```
        for (let j = 0; j < lines.length; j++){
           strmessage += lines[j] + '\n'
        }

        let channel = message.guild.channels.find("name", "programing_help")
        let channelName = message.channel.name
        let isHelp = channelName.indexOf('help') > 0

        if(channel != null && channel != message.channel && !isHelp){
            // TODO: Would like to add alink to #programming help for user friendliness :D
            // TODO: Would like to add some color to this message also
            // Maybe make it bold
            message.channel.send('__`Your unformatted code has been formatted and moved to`__ ' + '#'+ channel.name + '.\n`Which makes sense...`')
            channel.send('**`I have formated your code and placed it here. Good Luck!.`**')
            channel.send(strmessage);
        }
        else{
                message.channel.send('**`I see you forgot to format your code... Let me help you.`**')
                message.channel.send(strmessage)
            }
    }
    return
});

// Loop through each line in message and check for
// code-like characters. If code formaatting is found
// return else keep checking.
function checkMessageForCode(inputLines){
    for(let i = 0; i < inputLines.length; i++){
        //let line = inputLines[i].replace(/\s"/,'')
        if(inputLines[i].search("```") >= 0){
            isFormatted = true
            return
        }
        else{
            checkLastCharacter(i, inputLines[i])
        }
    }
    return
}

// Checks the last character in a string to see of it machess a code-like character (inChar)
function checkLastCharacter(index, inLine){
    let lineLength = inLine.length - 1
    for(let i = 0; i < codeElements.length; i++){
        if(inLine.charAt(lineLength).valueOf() == codeElements[i].valueOf()){
            codeLines.push(index)
            totalLinesOfCode += 1
            return
        }
    }
    return
}

// Checks the total number of code like elements in an unformatted
// code block. If greater than repostThreshold than this is bad code lol, return true
function isBadCode(){
    if (totalLinesOfCode >= repostThreshold){
        return true
    }
    else{
        return false
    }
}<|endoftext|>


<commit_before>'use strict';
var d3 = require('d3');
var _ = require('lodash');
var utils = require('lightning-client-utils');
var inherits = require('inherits');

var margin = {
    top: 30,
    right: 20,
    bottom: 20,
    left: 45
};

var nestedExtent = function(data, map) {
    var max = d3.max(data, function(arr) {
        return d3.max(_.map(arr, map));
    });
    var min = d3.min(data, function(arr) {
        return d3.min(_.map(arr, map));
    });

    return [min, max];
};

var LineStreaming = function(selector, data, images, opts) {

    if(!opts) {
        opts = {
            maxTick: 300
        };
    }

    this.opts = opts;

    this.width = (opts.width || $(selector).width()) - margin.left - margin.right;
    this.height = (opts.height || (this.width * 0.6)) - margin.top - margin.bottom;

    this.data = this._formatData(data);
    this.selector = selector;
    this._init();

    // code for testing streaming functionality

    // var self = this

    // setInterval(function()  {

    //     var random = d3.random.normal(0, .2)
    //     var newdata = {
    //         series: [[random(),random(),random(),random()], [random(),random(),random(),random()], [random(),random(),random(),random()], [random(),random(),random(),random()]]}
    //     self.appendData(newdata)

    // }, 500)

};

LineStreaming.prototype._init = function() {

    var data = this.data;
    var height = this.height;
    var width = this.width;
    var opts = this.opts;
    var selector = this.selector;
    var self = this;

    var series = data.series;

    var tip;

    if(this.opts.tooltips) {
        var format = d3.format('.02f');
        tip = d3.tip()
            .attr('class', 'd3-tip')
            .html(function(d, i) {
                return 'Series: ' + d.i;
            });
    }

    var defaultSize = Math.max(10 - 0.1 * series[0].d.length, 3);

    var yDomain = nestedExtent(series.map(function(d) {return d.d}), function(d) {
        return d.y;
    });
    var xDomain = nestedExtent(series.map(function(d) {return d.d}), function(d) {
        return d.x;
    });

    var ySpread = Math.abs(yDomain[1] - yDomain[0]) || 1;
    var xSpread = Math.abs(xDomain[1] - xDomain[0]) || 1;

    this.x = d3.scale.linear()
        .domain([xDomain[0] - 0.05 * xSpread, xDomain[1] + 0.05 * xSpread])
        .range([0, width]);

    this.y = d3.scale.linear()
        .domain([yDomain[0] - 0.1 * ySpread, yDomain[1] + 0.1 * ySpread])
        .range([height, 0]);

    this.line = d3.svg.line()
        .x(function (d, i) {
            return self.x(d.x);
        })
        .y(function (d, i) {
            return self.y(d.y);
        })

    this.zoom = d3.behavior.zoom()
        .x(this.x)
        .y(this.y)
        .on('zoom', zoomed);

    var svg = d3.select(selector)
        .append('svg:svg')
        .attr('class', 'line-plot')
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom)
        .append('svg:g')
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
        .call(this.zoom)

    if(this.opts.tooltips) {
        svg.call(tip);
    }

    svg.append('svg:rect')
        .attr('width', width)
        .attr('height', height)
        .attr('class', 'plot');

    var makeXAxis = function () {
        return d3.svg.axis()
            .scale(self.x)
            .orient('bottom')
            .ticks(5);
    };

    var makeYAxis = function () {
        return d3.svg.axis()
            .scale(self.y)
            .orient('left')
            .ticks(5);
    };

    this.xAxis = d3.svg.axis()
        .scale(this.x)
        .orient('bottom')
        .ticks(5);

    svg.append('svg:g')
        .attr('class', 'x axis')
        .attr('transform', 'translate(0, ' + height + ')')
        .call(this.xAxis);

    this.yAxis = d3.svg.axis()
        .scale(this.y)
        .orient('left')
        .ticks(5);

    svg.append('g')
        .attr('class', 'y axis')
        .call(this.yAxis);

    svg.append('g')
        .attr('class', 'x grid')
        .attr('transform', 'translate(0,' + height + ')')
        .call(makeXAxis()
                .tickSize(-height, 0, 0)
                .tickFormat(''));

    svg.append('g')
        .attr('class', 'y grid')
        .call(makeYAxis()
                .tickSize(-width, 0, 0)
                .tickFormat(''));

    var clipId = utils.getUniqueId();
    var clip = svg.append('svg:clipPath')
        .attr('id', clipId)
        .append('svg:rect')
        .attr('x', 0)
        .attr('y', 0)
        .attr('width', width)
        .attr('height', height);

    var chartBody = svg.append('g')
        .attr('clip-path', 'url(#' + clipId + ')');

    var toggleOpacity = 0;

    if(_.has(this.data, 'xaxis')) {
        var txt = this.data.xaxis;
        if(_.isArray(txt)) {
            txt = txt[0];
        }
        svg.append("text")
            .attr("class", "x label")
            .attr("text-anchor", "middle")
            .attr("x", width / 2)
            .attr("y", height + margin.bottom - 10)
            .text(txt);
    }
    if(_.has(this.data, 'yaxis')) {
        var txt = this.data.yaxis;
        if(_.isArray(txt)) {
            txt = txt[0];
        }

        svg.append("text")
            .attr("class", "y label")
            .attr("text-anchor", "middle")
            .attr("transform", "rotate(-90)")
            .attr("x", - height / 2)
            .attr("y", -40)
            .text(txt);
    }

    function highlight(d, i) {

        if (toggleOpacity === 0) {
            //var d = d3.select(this)[0][0].__data__
            svg.selectAll('.line').transition().duration(100).ease('linear').delay(100).style("opacity", function (o, j) {
                return i == j ? 0.9 : 0.2;
            });
            toggleOpacity = 1;

            if(self.opts.tooltips) {
                tip.show(d, i);
            }
        } else {
            svg.selectAll('.line').transition().duration(100).ease('linear').style('opacity', 0.9)
            toggleOpacity = 0;
            if(self.opts.tooltips) {
                tip.hide(d, i);
            }
        }
    }

    var path = chartBody.selectAll('.line')
        .data(series)
        .enter()
        .append('path')
        .attr('class', 'line')
        .attr('stroke', function(d) {return d.c})
        .style('stroke-width', function(d) {return d.s ? d.s : defaultSize})
        .style('stroke-opacity', 0.9)
        .attr('d', function(d) { return self.line(d.d)})
        .on('mouseover', highlight)
        .on('mouseout', highlight)

    function updateAxis() {

        self.svg.select('.x.axis').transition().duration(500).ease('linear').call(self.xAxis);
        self.svg.select('.y.axis').transition().duration(500).ease('linear').call(self.yAxis);
        self.svg.select('.x.grid')
            .transition().duration(500).ease('linear')
            .call(makeXAxis()
                .tickSize(-height, 0, 0)
                .tickFormat(''));
        self.svg.select('.y.grid')
            .transition().duration(500).ease('linear')
            .call(makeYAxis()
                    .tickSize(-width, 0, 0)
                    .tickFormat(''));
    }

    function zoomed() {

        updateAxis();
        self.svg.selectAll('.line')
            .attr('class', 'line')
            .attr('d', function(d) { return self.line(d.d)});
    }

    this.defaultSize = defaultSize;
    this.highlight = highlight;
    this.svg = svg;
    this.zoomed = zoomed;
    this.updateAxis = updateAxis;
    this.series = series;
    this.path = path;
    this.defaultSize = defaultSize;
    this.xTick = xDomain[1] + 1

}

LineStreaming.prototype._formatData = function(data) {

    // parse the array data
    if(_.isArray(data.series[0])) {
        // handle case of mutliple series
        data.series = _.map(data.series, function(d) {
            return _.map(d, function(datum, i) {
                return {
                    x: data.index ? data.index[i] : i,
                    y: datum
                };
            });
        });
    } else {
        // handle a single series
        data.series = [_.map(data.series, function(d, i) {
            return {
                x: data.index ? data.index[i] : i,
                y: d
            };
        })];
    }

    if (data.keys) {
        var keys = data.keys
    } else {
        var keys = _.range(0, data.series.length)
    }

    // parse colors and sizes, and automatically fill colors
    // with our random colors if none provided
    var retColor = utils.getColorFromData(data);
    if (retColor.length == 0) {
        retColor = utils.getColors(data.series.length)
    }
    var retSize = data.size || []

    // embed properties in data array
    data.series = data.series.map(function(line, i) {
        var d = {'d': line, 'i': keys[i]}
        d.c = retColor.length > 1 ? retColor[i] : retColor[0]
        d.s = retSize.length > 1 ? retSize[i] : retSize[0]
        return d
    })

    return data;
};

LineStreaming.prototype.appendData = function(data) {

    var self = this
    var path = this.path
    var series = this.series
    var maxTick = this.opts.maxTick
    var xTick = this.xTick

    var newlength = data.series[0].length
    data.index = _.range(0, newlength).map( function(d) {return d + xTick})

    var newdat = self._formatData(data).series

    this.xTick = xTick + newlength
    var shift = 0

    _.forEach(newdat, function(d, i) {
        var match = _.findIndex(series, function(s) {return s.i == d.i})
        if (match > -1) {
            _.forEach(d.d, function (e) {
                series[match].d.push(e)
                if (series[match].d.length > maxTick) {
                    shift++;
                }
                if (series[match].d.length > maxTick+newlength) {
                    series[match].d.shift()
                }
            })
        } else {
            console.log("we got here")
            series.push(d)
        }
    })

    var yDomain = nestedExtent(series.map(function(d) {return d.d}), function(d) {
        return d.y;
    });
    var xDomain = nestedExtent(series.map(function(d) {return d.d}), function(d) {
        return d.x;
    });

    var ySpread = Math.abs(yDomain[1] - yDomain[0]) || 1;

    var newdat = path.data(series)

    var self = this;

    if(shift) {
        self.x.domain(self.savedXDomain);
        self.y.domain([yDomain[0] - 0.1 * ySpread, yDomain[1] + 0.1 * ySpread]);
        self.zoom.x(self.x).y(self.y);

        newdat
            .attr("d", function(d) { return self.line(d.d)})
            .transition()
            .duration(500)
            .ease("linear")
            .attr("transform", "translate(" + self.x(Math.min(0, maxTick - self.xTick)) + ")");

        self.x.domain([Math.max(xTick - maxTick, xDomain[0]), xTick]);
        self.updateAxis();
    } else {

        self.x.domain([Math.max(xTick - maxTick, xDomain[0]), xTick]);
        self.y.domain([yDomain[0] - 0.1 * ySpread, yDomain[1] + 0.1 * ySpread]);
        self.zoom.x(self.x).y(self.y);
        self.updateAxis();
        newdat
            .transition()
            .duration(500)
            .ease("linear")
            .attr("d", function(d) { return self.line(d.d)})

        self.savedXDomain = [Math.max(xTick - maxTick, xDomain[0]), xTick];
    }

    newdat
        .enter()
        .append('path')
        .attr('class', 'line')
        .attr('stroke', function(d) {return d.c})
        .style('stroke-width', function(d) {return d.s ? d.s : self.defaultSize})
        .style('stroke-opacity', 0.9)
        .attr('d', function(d) { return self.line(d.d)})
        .on('mouseover', self.highlight)
        .on('mouseout', self.highlight)
        .attr("d", function(d) { return self.line(d.d)})
    .transition()
        .duration(500)
        .ease("linear")

    this.path = newdat

};

module.exports = LineStreaming;

<commit_msg>Persist size and remove highlighting<commit_after>'use strict';
var d3 = require('d3');
var _ = require('lodash');
var utils = require('lightning-client-utils');
var inherits = require('inherits');

var margin = {
    top: 30,
    right: 20,
    bottom: 20,
    left: 45
};

var nestedExtent = function(data, map) {
    var max = d3.max(data, function(arr) {
        return d3.max(_.map(arr, map));
    });
    var min = d3.min(data, function(arr) {
        return d3.min(_.map(arr, map));
    });

    return [min, max];
};

var LineStreaming = function(selector, data, images, opts) {

    if(!opts) {
        opts = {
            maxTick: 300
        };
    }

    this.opts = opts;

    this.width = (opts.width || $(selector).width()) - margin.left - margin.right;
    this.height = (opts.height || (this.width * 0.6)) - margin.top - margin.bottom;

    this.data = this._formatData(data);
    this.selector = selector;
    this._init();

    // code for testing streaming functionality

    // var self = this

    // setInterval(function()  {

    //     var random = d3.random.normal(0, .2)
    //     var newdata = {
    //         series: [[random(),random(),random(),random()], [random(),random(),random(),random()],
    // [random(),random(),random(),random()], [random(),random(),random(),random()]]}
    //     self.appendData(newdata)

    // }, 500)

};

LineStreaming.prototype._init = function() {

    var data = this.data;
    var height = this.height;
    var width = this.width;
    var opts = this.opts;
    var selector = this.selector;
    var self = this;

    var series = data.series;

    var tip;

    if(this.opts.tooltips) {
        var format = d3.format('.02f');
        tip = d3.tip()
            .attr('class', 'd3-tip')
            .html(function(d, i) {
                return 'Series: ' + d.i;
            });
    }

    var defaultSize = 6;

    this.size = data.size ? data.size : _.fill(_.range(series.length), defaultSize)

    var yDomain = nestedExtent(series.map(function(d) {return d.d}), function(d) {
        return d.y;
    });
    var xDomain = nestedExtent(series.map(function(d) {return d.d}), function(d) {
        return d.x;
    });

    var ySpread = Math.abs(yDomain[1] - yDomain[0]) || 1;
    var xSpread = Math.abs(xDomain[1] - xDomain[0]) || 1;

    this.x = d3.scale.linear()
        .domain([xDomain[0] - 0.05 * xSpread, xDomain[1] + 0.05 * xSpread])
        .range([0, width]);

    this.y = d3.scale.linear()
        .domain([yDomain[0] - 0.1 * ySpread, yDomain[1] + 0.1 * ySpread])
        .range([height, 0]);

    this.line = d3.svg.line()
        .x(function (d, i) {
            return self.x(d.x);
        })
        .y(function (d, i) {
            return self.y(d.y);
        })

    this.zoom = d3.behavior.zoom()
        .x(this.x)
        .y(this.y)
        .on('zoom', zoomed);

    var svg = d3.select(selector)
        .append('svg:svg')
        .attr('class', 'line-plot')
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom)
        .append('svg:g')
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')

    if(this.opts.tooltips) {
        svg.call(tip);
    }

    svg.append('svg:rect')
        .attr('width', width)
        .attr('height', height)
        .attr('class', 'plot');

    var makeXAxis = function () {
        return d3.svg.axis()
            .scale(self.x)
            .orient('bottom')
            .ticks(5);
    };

    var makeYAxis = function () {
        return d3.svg.axis()
            .scale(self.y)
            .orient('left')
            .ticks(5);
    };

    this.xAxis = d3.svg.axis()
        .scale(this.x)
        .orient('bottom')
        .ticks(5);

    svg.append('svg:g')
        .attr('class', 'x axis')
        .attr('transform', 'translate(0, ' + height + ')')
        .call(this.xAxis);

    this.yAxis = d3.svg.axis()
        .scale(this.y)
        .orient('left')
        .ticks(5);

    svg.append('g')
        .attr('class', 'y axis')
        .call(this.yAxis);

    svg.append('g')
        .attr('class', 'x grid')
        .attr('transform', 'translate(0,' + height + ')')
        .call(makeXAxis()
                .tickSize(-height, 0, 0)
                .tickFormat(''));

    svg.append('g')
        .attr('class', 'y grid')
        .call(makeYAxis()
                .tickSize(-width, 0, 0)
                .tickFormat(''));

    var clipId = utils.getUniqueId();
    var clip = svg.append('svg:clipPath')
        .attr('id', clipId)
        .append('svg:rect')
        .attr('x', 0)
        .attr('y', 0)
        .attr('width', width)
        .attr('height', height);

    var chartBody = svg.append('g')
        .attr('clip-path', 'url(#' + clipId + ')');

    var toggleOpacity = 0;

    if(_.has(this.data, 'xaxis')) {
        var txt = this.data.xaxis;
        if(_.isArray(txt)) {
            txt = txt[0];
        }
        svg.append("text")
            .attr("class", "x label")
            .attr("text-anchor", "middle")
            .attr("x", width / 2)
            .attr("y", height + margin.bottom - 10)
            .text(txt);
    }
    if(_.has(this.data, 'yaxis')) {
        var txt = this.data.yaxis;
        if(_.isArray(txt)) {
            txt = txt[0];
        }

        svg.append("text")
            .attr("class", "y label")
            .attr("text-anchor", "middle")
            .attr("transform", "rotate(-90)")
            .attr("x", - height / 2)
            .attr("y", -40)
            .text(txt);
    }

    var path = chartBody.selectAll('.line')
        .data(series)
        .enter()
        .append('path')
        .attr('class', 'line')
        .attr('stroke', function(d) {return d.c})
        .style('stroke-width', function(d) {return d.s ? d.s : defaultSize})
        .style('stroke-opacity', 0.9)
        .attr('d', function(d) { return self.line(d.d)})

    function updateAxis() {

        self.svg.select('.x.axis').transition().duration(500).ease('linear').call(self.xAxis);
        self.svg.select('.y.axis').transition().duration(500).ease('linear').call(self.yAxis);
        self.svg.select('.x.grid')
            .transition().duration(500).ease('linear')
            .call(makeXAxis()
                .tickSize(-height, 0, 0)
                .tickFormat(''));
        self.svg.select('.y.grid')
            .transition().duration(500).ease('linear')
            .call(makeYAxis()
                    .tickSize(-width, 0, 0)
                    .tickFormat(''));
    }

    function zoomed() {

        updateAxis();
        self.svg.selectAll('.line')
            .attr('class', 'line')
            .attr('d', function(d) { return self.line(d.d)});
    }

    this.defaultSize = defaultSize;
    this.svg = svg;
    this.zoomed = zoomed;
    this.updateAxis = updateAxis;
    this.series = series;
    this.path = path;
    this.defaultSize = defaultSize;
    this.xTick = xDomain[1] + 1

}

LineStreaming.prototype._formatData = function(data) {

    // parse the array data
    if(_.isArray(data.series[0])) {
        // handle case of mutliple series
        data.series = _.map(data.series, function(d) {
            return _.map(d, function(datum, i) {
                return {
                    x: data.index ? data.index[i] : i,
                    y: datum
                };
            });
        });
    } else {
        // handle a single series
        data.series = [_.map(data.series, function(d, i) {
            return {
                x: data.index ? data.index[i] : i,
                y: d
            };
        })];
    }

    if (data.keys) {
        var keys = data.keys
    } else {
        var keys = _.range(0, data.series.length)
    }

    // parse colors and sizes, and automatically fill colors
    // with our random colors if none provided
    var retColor = utils.getColorFromData(data);
    if (retColor.length == 0) {
        retColor = utils.getColors(data.series.length)
    }
    var retSize = data.size || []

    // embed properties in data array
    data.series = data.series.map(function(line, i) {
        var d = {'d': line, 'i': keys[i]}
        d.c = retColor.length > 1 ? retColor[i] : retColor[0]
        d.s = retSize.length > 1 ? retSize[i] : retSize[0]
        return d
    })

    return data;
};

LineStreaming.prototype.appendData = function(data) {

    var self = this
    var path = this.path
    var series = this.series
    var maxTick = this.opts.maxTick
    var xTick = this.xTick

    var newlength = data.series[0].length
    data.index = _.range(0, newlength).map( function(d) {return d + xTick})
    data.size = this.size

    var newdat = self._formatData(data).series

    this.xTick = xTick + newlength
    var shift = 0

    _.forEach(newdat, function(d, i) {
        var match = _.findIndex(series, function(s) {return s.i == d.i})
        if (match > -1) {
            _.forEach(d.d, function (e) {
                series[match].d.push(e)
                if (series[match].d.length > maxTick) {
                    shift++;
                }
                if (series[match].d.length > maxTick+newlength) {
                    series[match].d.shift()
                }
            })
        } else {
            console.log("we got here")
            series.push(d)
        }
    })

    var yDomain = nestedExtent(series.map(function(d) {return d.d}), function(d) {
        return d.y;
    });
    var xDomain = nestedExtent(series.map(function(d) {return d.d}), function(d) {
        return d.x;
    });

    var ySpread = Math.abs(yDomain[1] - yDomain[0]) || 1;

    var newdat = path.data(series)

    var self = this;

    if(shift) {
        self.x.domain(self.savedXDomain);
        self.y.domain([yDomain[0] - 0.1 * ySpread, yDomain[1] + 0.1 * ySpread]);
        self.zoom.x(self.x).y(self.y);

        newdat
            .attr("d", function(d) { return self.line(d.d)})
            .transition()
            .duration(500)
            .ease("linear")
            .attr("transform", "translate(" + self.x(Math.min(0, maxTick - self.xTick)) + ")");

        self.x.domain([Math.max(xTick - maxTick, xDomain[0]), xTick]);
        self.updateAxis();
    } else {

        self.x.domain([Math.max(xTick - maxTick, xDomain[0]), xTick]);
        self.y.domain([yDomain[0] - 0.1 * ySpread, yDomain[1] + 0.1 * ySpread]);
        self.zoom.x(self.x).y(self.y);
        self.updateAxis();
        newdat
            .transition()
            .duration(500)
            .ease("linear")
            .attr("d", function(d) { return self.line(d.d)})

        self.savedXDomain = [Math.max(xTick - maxTick, xDomain[0]), xTick];
    }

    newdat
        .enter()
        .append('path')
        .attr('class', 'line')
        .attr('stroke', function(d) {return d.c})
        .style('stroke-width', function(d) {return d.s ? d.s : self.defaultSize})
        .style('stroke-opacity', 0.9)
        .attr('d', function(d) { return self.line(d.d)})
        .attr("d", function(d) { return self.line(d.d)})
    .transition()
        .duration(500)
        .ease("linear")

    this.path = newdat

};

module.exports = LineStreaming;

<|endoftext|>


<commit_before>// Tâmia © 2014 Artem Sapegin http://sapegin.me
// Modal

/*global tamia:false*/
;(function(window, $, undefined) {
	'use strict';

	var _body = $('body');
	var _doc = $(document);

	var _bodyClass = 'modal-opened';
	var _switchingState = 'switching';
	var _hiddenState = 'hidden';
	var _wrapperTmpl = '' +
	'
' +
		'
' +
			'

' +
		'
' +
	'
';
	var _opened = null;

	tamia.Modal = tamia.extend(tamia.Component, {
		displayName: 'tamia.Modal',
		binded: 'commit dismiss keyup shadeClick',

		init: function() {
			this.elem.data('modal', this);
			this.elem.on('click', '.js-modal-commit', this.commit_);
			this.elem.on('click', '.js-modal-dismiss', this.dismiss_);
			if (this.elem.data('modal-open')) {
				this.open();
			}
		},

		initHtml: function() {
			if (this.wrapper) return;

			this.wrapper = $(_wrapperTmpl);
			this.wrapper.find('.js-modal').append(this.elem);
			this.wrapper.on('click', this.shadeClick_);
			_body.append(this.wrapper);
			this.elem.removeState('hidden');
		},

		open: function() {
			if (this === _opened) return;

			var opened = _opened;
			this.initHtml();
			_body.addClass(_bodyClass);
			if (opened) {
				opened.close({hide: true});
				this.wrapper.addState(_switchingState);
				this.wrapper.on('appeared.tamia', function() {
					this.wrapper.removeState(_switchingState);
					opened.wrapper.addState(_hiddenState);
					opened.elem.removeState(_hiddenState);
				}.bind(this));
			}
			this.wrapper.trigger('appear.tamia');
			_doc.on('keyup', this.keyup_);
			_opened = this;

			// Set focus to element with autofocus attribute
			var autofocus = this.elem.find('[autofocus]');
			if (autofocus.length) {
				autofocus.focus();
			}
		},

		close: function(params) {
			if (params === undefined) params = {hide: false};

			var elem = params.hide ? this.elem : this.wrapper;
			elem.trigger('disappear.tamia');
			if (!params.hide) _body.removeClass(_bodyClass);
			_doc.off('keyup', this.keyup_);
			_opened = null;
		},

		commit: function(event) {
			this.done(event, 'commit');
		},

		dismiss: function(event) {
			this.done(event, 'dismiss');
		},

		done: function(event, type) {
			if (event) event.preventDefault();

			var typeEvent = $.Event(type + '.modal.tamia');
			this.elem.trigger(typeEvent);
			if (typeEvent.isDefaultPrevented()) return;

			this.close();
		},

		keyup: function(event) {
			if (event.which === 27) {  // Escape
				this.dismiss(event);
			}
		},

		shadeClick: function(event) {
			if ($(event.target).hasClass('js-modal') && this.elem.data('modal-close-on-shade') !== 'no') {
				this.dismiss(event);
			}
		}
	});

	// Events
	tamia.registerEvents({
		'open.modal': function(elem) {
			var container = $(elem);
			var modal = container.data('modal');
			if (!modal) modal = new tamia.Modal(elem);
			modal.open();
		},
		'close.modal': function(elem) {
			var container = $(elem);
			var modal = container.data('modal');
			if (!modal) return;
			modal.close();
		}
	});

}(window, jQuery));
<commit_msg>Modal: OPORJSON.<commit_after>// Tâmia © 2014 Artem Sapegin http://sapegin.me
// Modal

/*global tamia:false*/
;(function(window, $, undefined) {
	'use strict';

	var _body = $('body');
	var _doc = $(document);

	var _bodyClass = 'modal-opened';
	var _switchingState = 'switching';
	var _hiddenState = 'hidden';
	var _wrapperTmpl = {
		block: 'modal-shade',
		states: 'hidden',
		content: {
			block: 'l-center',
			content: {
				block: 'l-center',
				inner: true,
				js: 'modal',
				content: {
					node: true
				}
			}
		}
	};
	var _opened = null;

	tamia.Modal = tamia.extend(tamia.Component, {
		displayName: 'tamia.Modal',
		binded: 'commit dismiss keyup shadeClick',

		init: function() {
			this.elem.data('modal', this);
			this.elem.on('click', '.js-modal-commit', this.commit_);
			this.elem.on('click', '.js-modal-dismiss', this.dismiss_);
			if (this.elem.data('modal-open')) {
				this.open();
			}
		},

		initHtml: function() {
			if (this.wrapper) return;
			this.wrapper = tamia.OporNode(_wrapperTmpl, this.elem);
			this.wrapper.on('click', this.shadeClick_);
			_body.append(this.wrapper);
			this.elem.removeState('hidden');
		},

		open: function() {
			if (this === _opened) return;

			var opened = _opened;
			this.initHtml();
			_body.addClass(_bodyClass);
			if (opened) {
				opened.close({hide: true});
				this.wrapper.addState(_switchingState);
				this.wrapper.on('appeared.tamia', function() {
					this.wrapper.removeState(_switchingState);
					opened.wrapper.addState(_hiddenState);
					opened.elem.removeState(_hiddenState);
				}.bind(this));
			}
			this.wrapper.trigger('appear.tamia');
			_doc.on('keyup', this.keyup_);
			_opened = this;

			// Set focus to element with autofocus attribute
			var autofocus = this.elem.find('[autofocus]');
			if (autofocus.length) {
				autofocus.focus();
			}
		},

		close: function(params) {
			if (params === undefined) params = {hide: false};

			var elem = params.hide ? this.elem : this.wrapper;
			elem.trigger('disappear.tamia');
			if (!params.hide) _body.removeClass(_bodyClass);
			_doc.off('keyup', this.keyup_);
			_opened = null;
		},

		commit: function(event) {
			this.done(event, 'commit');
		},

		dismiss: function(event) {
			this.done(event, 'dismiss');
		},

		done: function(event, type) {
			if (event) event.preventDefault();

			var typeEvent = $.Event(type + '.modal.tamia');
			this.elem.trigger(typeEvent);
			if (typeEvent.isDefaultPrevented()) return;

			this.close();
		},

		keyup: function(event) {
			if (event.which === 27) {  // Escape
				this.dismiss(event);
			}
		},

		shadeClick: function(event) {
			if ($(event.target).hasClass('js-modal') && this.elem.data('modal-close-on-shade') !== 'no') {
				this.dismiss(event);
			}
		}
	});

	// Events
	tamia.registerEvents({
		'open.modal': function(elem) {
			var container = $(elem);
			var modal = container.data('modal');
			if (!modal) modal = new tamia.Modal(elem);
			modal.open();
		},
		'close.modal': function(elem) {
			var container = $(elem);
			var modal = container.data('modal');
			if (!modal) return;
			modal.close();
		}
	});

}(window, jQuery));
<|endoftext|>


<commit_before>var StyleLoader = function() {
  var sss, ios, getURI, loadedStyles = [];

  sss = Components.classes["@mozilla.org/content/style-sheet-service;1"]
                  .getService(Components.interfaces.nsIStyleSheetService);
  ios = Components.classes["@mozilla.org/network/io-service;1"]
                  .getService(Components.interfaces.nsIIOService);

  getURI = function(codeOrUri) {
    var output;
    var path = codeOrUri.indexOf('chrome://') == 0;
    if(!path) {
		  output = ios.newURI("data:text/css," + encodeURIComponent(codeOrUri), null, null);
    } else {
      output = ios.newURI(codeOrUri, null, null);
    }
    return output;
  };
  return {
    load: function(styleURI) {
      var uri = getURI(styleURI);
      if(!sss.sheetRegistered(uri, sss.AGENT_SHEET)) {
        sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
        loadedStyles.push(styleURI);
      }
    },
    unLoad: function(styleURI) {
      var uri = getURI(styleURI);
      if(sss.sheetRegistered(uri, sss.AGENT_SHEET)) {
        sss.unregisterSheet(uri, sss.AGENT_SHEET);
        for (var i = 0; i < loadedStyles.length; i++) {
          if(loadedStyles[i] === styleURI) {
            loadedStyles.slice(i, 1);
            break;
          }
        }
      }
    },
    getLoadedStyles: function() {
      return loadedStyles;
    },
  };
};
let EXPORTED_SYMBOLS = ['StyleLoader'];
<commit_msg>use sqlite storage to store loaded css names<commit_after>var logger = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService)
Components.utils.import('resource://huppermodules/hupdb.jsm');
var bind = function(fn, context) {
  var args = [];
  for (var i = 2; i < arguments.length; i++) {
    args.push(arguments[i]);
  }
  logger.logStringMessage(args.toSource());
  return function() {
    fn.apply(context, args);
  };
};

var Storage = function() {
  this.db = new HupDB();
};
Storage.prototype = {
  addStyle_: function(styleURI, isActive, onSuccess, onError) {
      var q = 'INSERT INTO styles (styleURI,isActive) VALUES (:styleURI, :isActive)';
      this.db.query(q, {styleURI: styleURI, isActive: isActive ? 1 : 0}, onSuccess, onError);
  },
  addStyle: function(styleURI, isActive, onSuccess, onError) {
    this.styleAdded(styleURI, bind(function(added) {
      if(!added) {
        this.addStyle_(styleURI, isActive, onSuccess, onError);
      }
    }, this));
  },
  styleIsActive_: function(aResultSet, onSuccess) {
    var styleActive = false;
    onSuccess = typeof onSuccess === 'function' ? onSuccess : function() {
    }
    for (let row = aResultSet.getNextRow(); row; row = aResultSet.getNextRow()) {
      let value = row.getResultByName("active");
      styleActive = !!value;
    }
    logger.logStringMessage('hupper style active: '  + styleActive);
    onSuccess(styleActive);
  },
  styleIsActive: function(styleURI, onSuccess, onError) {
    var q = 'SELECT isActive AS active FROM styles WHERE styleURI=:styleURI';
    this.db.query(q, {styleURI: styleURI}, onSuccess, onError);
  },
  styleAdded_: function(aResultSet, onSuccess) {
    var styleAdded = false;
    for (let row = aResultSet.getNextRow(); row; row = aResultSet.getNextRow()) {
      let value = row.getResultByName("count");
      styleAdded = value > 0;
    }
    logger.logStringMessage('style added: ' + styleAdded);
    onSuccess(styleAdded);
  },
  styleAdded: function(styleURI, onSuccess, onError) {
    var q = 'SELECT count(*) AS count FROM styles WHERE styleURI=:styleURI';
    var _this = this;
    this.db.query(q, {styleURI: styleURI}, function(aResultSet) {
      _this.styleAdded_(aResultSet, onSuccess);
    }, onError);
  },
  activateStyle_: function(styleURI, onSuccess) {
    var q = 'UPDATE styles SET isActive=1 WHERE styleURI=:styleURI';
    this.db.query(q, {styleURI: styleURI}, onSuccess);
  },
  activateStyle: function(styleURI, onSuccess) {
    this.styleAdded(styleURI, bind(function(added) {
      if(added) {
        this.styleIsActive(styleURI, bind(function() {
          this.activateStyle_(styleURI, onSuccess);
        }, this, styleURI));
      }
    }, this));
  },
  deactivateStyle_: function(styleURI, onSuccess) {
    var q = 'UPDATE styles SET isActive=0 WHERE styleURI=:styleURI';
    this.db.query(q, {styleURI: styleURI}, onSuccess, function() {
      logger.logStringMessage('deactivate failed');
    });
  },
  deactiveateStyle: function(styleURI, onSuccess) {
    logger.logStringMessage('call deactivateStyle_');
    this.styleAdded(styleURI, bind(function(added) {
      logger.logStringMessage('DEACTIVATE STYLE' + styleURI + ' ' + added)
      if(added) {
        this.styleIsActive(styleURI, bind(function() {
          this.deactivateStyle_(styleURI, onSuccess);
        }, this));
      } else {
        onSuccess();
      }
    }, this));
  },
  getStyles: function(onSuccess) {
    var q = 'SELECT * FROM styles';
    this.db.query(q, {}, function(aResultSet) {
      var styles = [];
      for (let row = aResultSet.getNextRow(); row; row = aResultSet.getNextRow()) {
        styles.push(row.getResultByName("styleURI"));
      }
      onSuccess(styles);
    });
  },
};

var StyleLoader = function() {
  var sss, ios, getURI;
  var storage = new Storage();

  sss = Components.classes["@mozilla.org/content/style-sheet-service;1"]
                  .getService(Components.interfaces.nsIStyleSheetService);
  ios = Components.classes["@mozilla.org/network/io-service;1"]
                  .getService(Components.interfaces.nsIIOService);

  getURI = function(codeOrUri) {
    var output;
    var path = codeOrUri.indexOf('chrome://') == 0;
    if(!path) {
		  output = ios.newURI("data:text/css," + encodeURIComponent(codeOrUri), null, null);
    } else {
      output = ios.newURI(codeOrUri, null, null);
    }
    return output;
  };
  return {
    load: function(styleURI) {
      var uri = getURI(styleURI);
      if(!sss.sheetRegistered(uri, sss.AGENT_SHEET)) {
        sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
        storage.addStyle(styleURI, true);
      }
    },
    unLoad: function(styleURI, callback) {
      var uri = getURI(styleURI);
      callback = typeof callback === 'function' ? callback : function() {
        logger.logStringMessage('hupper: dummy unload');
      };
      if(sss.sheetRegistered(uri, sss.AGENT_SHEET)) {
        sss.unregisterSheet(uri, sss.AGENT_SHEET);
        storage.deactiveateStyle(styleURI, callback);
      } else {
        callback();
      }
    },
    unloadAll: function(callback) {
      var _this = this;
      callback = typeof callback === 'function' ? callback : function() {};
      storage.getStyles(function(styles) {
        var unloadedStyles = 0;
        var onUnload = function() {
          unloadedStyles += 1;
          logger.logStringMessage('hupper unload: ' + unloadedStyles + ' !! ' + styles.length);
          if(unloadedStyles == styles.length) {
            logger.logStringMessage('styles: ' + styles.toSource());
            callback();
          }
        };
        styles.forEach(function(style) {
          logger.logStringMessage('try to unload');
          _this.unLoad(style, onUnload);
        });
      });
    },
  };
};
let EXPORTED_SYMBOLS = ['StyleLoader'];
<|endoftext|>


<commit_before>// board is double array
// snake is a list w/head and tail

snake = [[0, 0]]; // x, y
dir = 'right';
var board = [];     // top left is 0, 0, bottom right is height-1, width-1
var food = [-1, -1];

// initialize board and snake, and start snake movement
function initSnake() {
  console.log(width+" "+height);
  board.length = width; // board[x][y]
  for (var i = 0; i < width; i++) {
    board[i] = [];
    board[i].length = height;
  }
  window.setInterval(aiLevel0, 500);
  aiLevel0();
}

function aiLevel0() {
  moveHead(dir);
  if (dir == 'right' || dir == 'left') {
    if (snake[0][0] == food[0]) {   // is the snake above or below the food?
      if (snake[0][1] < food[1]) {
        dir = 'down';
      } else {
        dir = 'up';
      }
    } else if ((snake[0][0] == width - 1) || (snake[0][0] == 0)) {  // is the snake at a side wall?
      if (snake[0][1] >= (height/2)) {
		  dir = 'up';
	  } else {
		  dir = 'down';
	  }
    } // else dir is unchanged
  } else if (dir == 'up' || dir == 'down') {
    if (snake[0][1] == food[1]) {   // is the snake to the right or left of the food?
      if (snake[0][0] < food[0]) {
        dir = 'right';
      } else {
        dir = 'left';
      }
    } else if ((snake[0][1] == 0) || (snake[0][0] == width - 1)) {  // is the snake at a vertical wall?
		if (snake[0][0] >= (width/2)) {
			dir = 'left';
		} else {
			dir = 'right';
		}
	} // else dir is unchanged
  }
}

function moveHead(dir) {
  // move snake's head in given direction
  // 'right', 'left', 'up', and 'down'
  var newHead = snake[0];
  if (dir == 'up') {  // add new head above current one
    newHead[1]--;  // changes y-coordinate by -1 (going UP)
  } else if (dir == 'down') {
    newHead[1]++;
  } else if (dir == 'right') {
    newHead[0]++;
  } else if (dir == 'left') {
    newHead[0]--;
  }
  snake.unshift(newHead); // add newHead to the beginning of the list
  updateBoard(snake);
  if (snake[0] == food) { // if snake's head is on the food
    food = [-1, -1];
  } else {
    delTail();
  }
}

function delTail() {  // simple function to remove the tail
  console.log(snake.pop());
  updateBoard(snake);
}

function placeFood(x, y) {
  newFood = [x, y];
  if (food != [-1, -1]) { // already food
    console.log("Invalid food placement: There is already food");
    return false;
  } else if (x >= width || y >= height || x < 0 || y < 0) { // off of board
    console.log("Invalid food placement: Food is off board");
    return false;
  } else if (snake.includes(newFood)) {   // new food collides with snake
    console.log("Invalid food placement: Food collides with snake");
    return false;
  } else {
    food = newFood;
    return true;
  }
}
<commit_msg>add console.log(snake)<commit_after>// board is double array
// snake is a list w/head and tail

snake = [[0, 0]]; // x, y
dir = 'right';
var board = [];     // top left is 0, 0, bottom right is height-1, width-1
var food = [-1, -1];

// initialize board and snake, and start snake movement
function initSnake() {
  console.log(width+" "+height);
  board.length = width; // board[x][y]
  for (var i = 0; i < width; i++) {
    board[i] = [];
    board[i].length = height;
  }
  window.setInterval(aiLevel0, 500);
  aiLevel0();
}

function aiLevel0() {
  moveHead(dir);
  if (dir == 'right' || dir == 'left') {
    if (snake[0][0] == food[0]) {   // is the snake above or below the food?
      if (snake[0][1] < food[1]) {
        dir = 'down';
      } else {
        dir = 'up';
      }
    } else if ((snake[0][0] == width - 1) || (snake[0][0] == 0)) {  // is the snake at a side wall?
      if (snake[0][1] >= (height/2)) {
		  dir = 'up';
	  } else {
		  dir = 'down';
	  }
    } // else dir is unchanged
  } else if (dir == 'up' || dir == 'down') {
    if (snake[0][1] == food[1]) {   // is the snake to the right or left of the food?
      if (snake[0][0] < food[0]) {
        dir = 'right';
      } else {
        dir = 'left';
      }
    } else if ((snake[0][1] == 0) || (snake[0][0] == width - 1)) {  // is the snake at a vertical wall?
		if (snake[0][0] >= (width/2)) {
			dir = 'left';
		} else {
			dir = 'right';
		}
	} // else dir is unchanged
  }
}

function moveHead(dir) {
  // move snake's head in given direction
  // 'right', 'left', 'up', and 'down'
  var newHead = snake[0];
  if (dir == 'up') {  // add new head above current one
    newHead[1]--;  // changes y-coordinate by -1 (going UP)
  } else if (dir == 'down') {
    newHead[1]++;
  } else if (dir == 'right') {
    newHead[0]++;
  } else if (dir == 'left') {
    newHead[0]--;
  }
//  if (newHead[0] < 0 || newHead[0] >= width || newHead[1] < 0 || newHead[1] >= height) {

//  }
  snake.unshift(newHead); // add newHead to the beginning of the list
  console.log(snake);
  updateBoard(snake);
  if (snake[0] == food) { // if snake's head is on the food
    food = [-1, -1];
  } else {
    delTail();
  }
}

function delTail() {  // simple function to remove the tail
  console.log(snake.pop());
  updateBoard(snake);
}

function placeFood(x, y) {
  newFood = [x, y];
  if (food != [-1, -1]) { // already food
    console.log("Invalid food placement: There is already food");
    return false;
  } else if (x >= width || y >= height || x < 0 || y < 0) { // off of board
    console.log("Invalid food placement: Food is off board");
    return false;
  } else if (snake.includes(newFood)) {   // new food collides with snake
    console.log("Invalid food placement: Food collides with snake");
    return false;
  } else {
    food = newFood;
    return true;
  }
}
<|endoftext|>


<commit_before>var types = require("HAP-NodeJS/accessories/types.js");
var sonos = require('sonos');

function SonosPlatform(log, config){
    this.log = log;
    this.config = config;
    this.name = config["name"];
    this.playVolume = config["play_volume"];
}

SonosPlatform.prototype = {
    accessories: function(callback) {
        this.log("Fetching Sonos devices.");
        var that = this;

        sonos.search(function (device) {
            that.log("Found device at " + device.host);

            device.deviceDescription(function (err, description) {
                if (description["zoneType"] != '11') {
                    that.log("Found playable device - " + description["roomName"]);
                    // device is an instance of sonos.Sonos
                    var accessory = new SonosAccessory(that.log, that.config, device, description);
                    callback([accessory]);
                }
            });
        });
    }
};

function SonosAccessory(log, config, device, description) {
    this.log = log;
    this.config = config;
    this.device = device;
    this.description = description;

    this.name = this.description["roomName"] + " " + this.config["name"];
    this.serviceName = this.description["roomName"] + " Speakers";
    this.playVolume = this.config["play_volume"];
}

SonosAccessory.prototype = {

    setPlaying: function(playing) {

        if (!this.device) {
            this.log("No device found (yet?)");
            return;
        }

        var that = this;

        if (playing) {
            this.device.play(function(err, success) {
                that.log("Playback attempt with success: " + success);
            });

            if (this.playVolume) {
                this.device.setVolume(this.playVolume, function(err, success) {
                    if (!err) {
                        that.log("Set volume to " + that.playVolume);
                    }
                    else {
                        that.log("Problem setting volume: " + err);
                    }
                });
            }
        }
        else {
            this.device.stop(function(err, success) {
                that.log("Stop attempt with success: " + success);
            });
        }
    },

    getServices: function() {
        var that = this;
        return [{
            sType: types.ACCESSORY_INFORMATION_STYPE,
            characteristics: [{
                cType: types.NAME_CTYPE,
                onUpdate: null,
                perms: ["pr"],
                format: "string",
                initialValue: this.name,
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "Name of the accessory",
                designedMaxLength: 255
            },{
                cType: types.MANUFACTURER_CTYPE,
                onUpdate: null,
                perms: ["pr"],
                format: "string",
                initialValue: "Sonos",
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "Manufacturer",
                designedMaxLength: 255
            },{
                cType: types.MODEL_CTYPE,
                onUpdate: null,
                perms: ["pr"],
                format: "string",
                initialValue: "Rev-1",
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "Model",
                designedMaxLength: 255
            },{
                cType: types.SERIAL_NUMBER_CTYPE,
                onUpdate: null,
                perms: ["pr"],
                format: "string",
                initialValue: "A1S2NASF88EW",
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "SN",
                designedMaxLength: 255
            },{
                cType: types.IDENTIFY_CTYPE,
                onUpdate: null,
                perms: ["pw"],
                format: "bool",
                initialValue: false,
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "Identify Accessory",
                designedMaxLength: 1
            }]
        },{
            sType: types.SWITCH_STYPE,
            characteristics: [{
                cType: types.NAME_CTYPE,
                onUpdate: null,
                perms: ["pr"],
                format: "string",
                initialValue: this.serviceName,
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "Name of service",
                designedMaxLength: 255
            },{
                cType: types.POWER_STATE_CTYPE,
                onUpdate: function(value) { that.setPlaying(value); },
                perms: ["pw","pr","ev"],
                format: "bool",
                initialValue: false,
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "Change the playback state of the sonos",
                designedMaxLength: 1
            }]
        }];
    }
};

module.exports.accessory = SonosAccessory;
module.exports.platform = SonosPlatform;
<commit_msg>[Sonos] don't add duplicate rooms<commit_after>var types = require("HAP-NodeJS/accessories/types.js");
var sonos = require('sonos');

function SonosPlatform(log, config){
    this.log = log;
    this.config = config;
    this.name = config["name"];
    this.playVolume = config["play_volume"];
}

SonosPlatform.prototype = {
    accessories: function(callback) {
        this.log("Fetching Sonos devices.");
        var that = this;

        // track found devices so we don't add duplicates
        var roomNamesFound = {};

        sonos.search(function (device) {
            that.log("Found device at " + device.host);

            device.deviceDescription(function (err, description) {
                if (description["zoneType"] != '11' && description["zoneType"] != '8') { // 8 is the Sonos SUB

                    var roomName = description["roomName"];

                    if (!roomNamesFound[roomName]) {
                        roomNamesFound[roomName] = true;
                        that.log("Found playable device - " + roomName);
                        // device is an instance of sonos.Sonos
                        var accessory = new SonosAccessory(that.log, that.config, device, description);
                        callback([accessory]);
                    }
                    else {
                        that.log("Ignoring playable device with duplicate room name - " + roomName);
                    }
                }
            });
        });
    }
};

function SonosAccessory(log, config, device, description) {
    this.log = log;
    this.config = config;
    this.device = device;
    this.description = description;

    this.name = this.description["roomName"] + " " + this.config["name"];
    this.serviceName = this.description["roomName"] + " Speakers";
    this.playVolume = this.config["play_volume"];
}

SonosAccessory.prototype = {

    setPlaying: function(playing) {

        if (!this.device) {
            this.log("No device found (yet?)");
            return;
        }

        var that = this;

        if (playing) {
            this.device.play(function(err, success) {
                that.log("Playback attempt with success: " + success);
            });

            if (this.playVolume) {
                this.device.setVolume(this.playVolume, function(err, success) {
                    if (!err) {
                        that.log("Set volume to " + that.playVolume);
                    }
                    else {
                        that.log("Problem setting volume: " + err);
                    }
                });
            }
        }
        else {
            this.device.stop(function(err, success) {
                that.log("Stop attempt with success: " + success);
            });
        }
    },

    getServices: function() {
        var that = this;
        return [{
            sType: types.ACCESSORY_INFORMATION_STYPE,
            characteristics: [{
                cType: types.NAME_CTYPE,
                onUpdate: null,
                perms: ["pr"],
                format: "string",
                initialValue: this.name,
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "Name of the accessory",
                designedMaxLength: 255
            },{
                cType: types.MANUFACTURER_CTYPE,
                onUpdate: null,
                perms: ["pr"],
                format: "string",
                initialValue: "Sonos",
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "Manufacturer",
                designedMaxLength: 255
            },{
                cType: types.MODEL_CTYPE,
                onUpdate: null,
                perms: ["pr"],
                format: "string",
                initialValue: "Rev-1",
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "Model",
                designedMaxLength: 255
            },{
                cType: types.SERIAL_NUMBER_CTYPE,
                onUpdate: null,
                perms: ["pr"],
                format: "string",
                initialValue: "A1S2NASF88EW",
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "SN",
                designedMaxLength: 255
            },{
                cType: types.IDENTIFY_CTYPE,
                onUpdate: null,
                perms: ["pw"],
                format: "bool",
                initialValue: false,
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "Identify Accessory",
                designedMaxLength: 1
            }]
        },{
            sType: types.SWITCH_STYPE,
            characteristics: [{
                cType: types.NAME_CTYPE,
                onUpdate: null,
                perms: ["pr"],
                format: "string",
                initialValue: this.serviceName,
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "Name of service",
                designedMaxLength: 255
            },{
                cType: types.POWER_STATE_CTYPE,
                onUpdate: function(value) { that.setPlaying(value); },
                perms: ["pw","pr","ev"],
                format: "bool",
                initialValue: false,
                supportEvents: false,
                supportBonjour: false,
                manfDescription: "Change the playback state of the sonos",
                designedMaxLength: 1
            }]
        }];
    }
};

module.exports.accessory = SonosAccessory;
module.exports.platform = SonosPlatform;
<|endoftext|>