<commit_before>/**
 * UserDimensionsPieChart component
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

export default function UserDimensionsPieChart() {
	return null;
}
<commit_msg>Add report selection.<commit_after>/**
 * UserDimensionsPieChart component
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { STORE_NAME as CORE_SITE } from '../../../../../googlesitekit/datastore/site/constants';
import { STORE_NAME as CORE_USER } from '../../../../../googlesitekit/datastore/user/constants';
import { STORE_NAME } from '../../../datastore/constants';
const { useSelect } = Data;

export default function UserDimensionsPieChart() {
	const url = useSelect( ( select ) => select( CORE_SITE ).getCurrentEntityURL() );
	const { startDate, endDate } = useSelect( ( select ) => select( CORE_USER ).getDateRangeDates( { compare: true } ) );

	const args = {
		startDate,
		endDate,
		metrics: [
			{
				expression: 'ga:users',
				alias: 'Users',
			},
		],
		dimensions: 'ga:channelGrouping',
		orderby: {
			fieldName: 'ga:users',
			sortOrder: 'DESCENDING',
		},
		limit: 5,
	};

	if ( url ) {
		args.url = url;
	}

	const loaded = useSelect( ( select ) => select( STORE_NAME ).hasFinishedResolution( 'getReport', [ args ] ) );
	const error = useSelect( ( select ) => select( STORE_NAME ).getErrorForSelector( 'getReport', [ args ] ) );
	const data = useSelect( ( select ) => select( STORE_NAME ).getReport( args ) );

	global.console.log( loaded, error, data );

	return null;
}
<|endoftext|>


<commit_before>"use strict";

var assert = require('assert');

var cost = function(c) {
    return 1;
};

describe('a* search algorithm', function() {
    describe('costs', function() {
	it('flatlands cost 1', function() {
	    assert.equal(1, cost('.'));
	    assert.equal(1, cost('@'));
	    assert.equal(1, cost('X'));
	});

	it('forest costs 2', function() {
	    assert.equal(2, cost('*'));
	});
    });
});
<commit_msg>minimal pass<commit_after>"use strict";

var assert = require('assert');

var cost = function(c) {
    if (c === '*') return 2;

    return 1;
};

describe('a* search algorithm', function() {
    describe('costs', function() {
	it('flatlands cost 1', function() {
	    assert.equal(1, cost('.'));
	    assert.equal(1, cost('@'));
	    assert.equal(1, cost('X'));
	});

	it('forest costs 2', function() {
	    assert.equal(2, cost('*'));
	});
    });
});
<|endoftext|>


<commit_before>import { blank } from 'helpers/qunit-helpers';
import { currentUser } from 'helpers/qunit-helpers';
import KeyValueStore from 'discourse/lib/key-value-store';
import Composer from 'discourse/models/composer';
import createStore from 'helpers/create-store';

module("model:composer");

const keyValueStore = new KeyValueStore("_test_composer");

function createComposer(opts) {
  opts = opts || {};
  opts.user = opts.user || currentUser();
  return createStore().createRecord('composer', opts);
}

function openComposer(opts) {
  const composer = createComposer(opts);
  composer.open(opts);
  return composer;
}

test('replyLength', function() {
  const replyLength = function(val, expectedLength) {
    const composer = createComposer({ reply: val });
    equal(composer.get('replyLength'), expectedLength);
  };

  replyLength("basic reply", 11, "basic reply length");
  replyLength(" \nbasic reply\t", 11, "trims whitespaces");
  replyLength("ba sic\n\nreply", 12, "count only significant whitespaces");
  replyLength("1[quote=]not counted[/quote]2[quote=]at all[/quote]3", 3, "removes quotes");
  replyLength("1[quote=]not[quote=]counted[/quote]yay[/quote]2", 2, "handles nested quotes correctly");
});

test('missingReplyCharacters', function() {
  Discourse.SiteSettings.min_first_post_length = 40;
  const missingReplyCharacters = function(val, isPM, isFirstPost, expected, message) {
    const composer = createComposer({ reply: val, creatingPrivateMessage: isPM, creatingTopic: isFirstPost });
    equal(composer.get('missingReplyCharacters'), expected, message);
  };

  missingReplyCharacters('hi', false, false, Discourse.SiteSettings.min_post_length - 2, 'too short public post');
  missingReplyCharacters('hi', false, true,  Discourse.SiteSettings.min_first_post_length - 2, 'too short first post');
  missingReplyCharacters('hi', true, false,  Discourse.SiteSettings.min_private_message_post_length - 2, 'too short private message');
});

test('missingTitleCharacters', function() {
  const missingTitleCharacters = function(val, isPM, expected, message) {
    const composer = createComposer({ title: val, creatingPrivateMessage: isPM });
    equal(composer.get('missingTitleCharacters'), expected, message);
  };

  missingTitleCharacters('hi', false, Discourse.SiteSettings.min_topic_title_length - 2, 'too short post title');
  missingTitleCharacters('z', true,  Discourse.SiteSettings.min_private_message_title_length - 1, 'too short pm title');
});

test('replyDirty', function() {
  const composer = createComposer();
  ok(!composer.get('replyDirty'), "by default it's false");

  composer.setProperties({
    originalText: "hello",
    reply: "hello"
  });

  ok(!composer.get('replyDirty'), "it's false when the originalText is the same as the reply");
  composer.set('reply', 'hello world');
  ok(composer.get('replyDirty'), "it's true when the reply changes");
});

test("appendText", function() {
  const composer = createComposer();

  blank(composer.get('reply'), "the reply is blank by default");

  composer.appendText("hello");
  equal(composer.get('reply'), "hello", "it appends text to nothing");
  composer.appendText(" world");
  equal(composer.get('reply'), "hello world", "it appends text to existing text");

  composer.clearState();
  composer.appendText("a\n\n\n\nb");
  composer.appendText("c",3,{block: true});

  equal(composer.get("reply"), "a\n\nc\n\nb");

  composer.clearState();
  composer.appendText("ab");
  composer.appendText("c",1,{block: true});

  equal(composer.get("reply"), "a\n\nc\n\nb");

  composer.clearState();
  composer.appendText("\nab");
  composer.appendText("c",0,{block: true});

  equal(composer.get("reply"), "c\n\nab");
});

test("Title length for regular topics", function() {
  Discourse.SiteSettings.min_topic_title_length = 5;
  Discourse.SiteSettings.max_topic_title_length = 10;
  const composer = createComposer();

  composer.set('title', 'asdf');
  ok(!composer.get('titleLengthValid'), "short titles are not valid");

  composer.set('title', 'this is a long title');
  ok(!composer.get('titleLengthValid'), "long titles are not valid");

  composer.set('title', 'just right');
  ok(composer.get('titleLengthValid'), "in the range is okay");
});

test("Title length for private messages", function() {
  Discourse.SiteSettings.min_private_message_title_length = 5;
  Discourse.SiteSettings.max_topic_title_length = 10;
  const composer = createComposer({action: Composer.PRIVATE_MESSAGE});

  composer.set('title', 'asdf');
  ok(!composer.get('titleLengthValid'), "short titles are not valid");

  composer.set('title', 'this is a long title');
  ok(!composer.get('titleLengthValid'), "long titles are not valid");

  composer.set('title', 'just right');
  ok(composer.get('titleLengthValid'), "in the range is okay");
});

test("Title length for private messages", function() {
  Discourse.SiteSettings.min_private_message_title_length = 5;
  Discourse.SiteSettings.max_topic_title_length = 10;
  const composer = createComposer({action: Composer.PRIVATE_MESSAGE});

  composer.set('title', 'asdf');
  ok(!composer.get('titleLengthValid'), "short titles are not valid");

  composer.set('title', 'this is a long title');
  ok(!composer.get('titleLengthValid'), "long titles are not valid");

  composer.set('title', 'just right');
  ok(composer.get('titleLengthValid'), "in the range is okay");
});

test('editingFirstPost', function() {
  const composer = createComposer();
  ok(!composer.get('editingFirstPost'), "it's false by default");

  const post = Discourse.Post.create({id: 123, post_number: 2});
  composer.setProperties({post: post, action: Composer.EDIT });
  ok(!composer.get('editingFirstPost'), "it's false when not editing the first post");

  post.set('post_number', 1);
  ok(composer.get('editingFirstPost'), "it's true when editing the first post");

});

test('clearState', function() {
  const composer = createComposer({
    originalText: 'asdf',
    reply: 'asdf2',
    post: Discourse.Post.create({id: 1}),
    title: 'wat'
  });

  composer.clearState();

  blank(composer.get('originalText'));
  blank(composer.get('reply'));
  blank(composer.get('post'));
  blank(composer.get('title'));

});

test('initial category when uncategorized is allowed', function() {
  Discourse.SiteSettings.allow_uncategorized_topics = true;
  const composer = openComposer({action: 'createTopic', draftKey: 'asfd', draftSequence: 1});
  equal(composer.get('categoryId'),undefined,"Uncategorized by default");
});

test('initial category when uncategorized is not allowed', function() {
  Discourse.SiteSettings.allow_uncategorized_topics = false;
  const composer = openComposer({action: 'createTopic', draftKey: 'asfd', draftSequence: 1});
  ok(composer.get('categoryId') === undefined, "Uncategorized by default. Must choose a category.");
});

test('showPreview', function() {
  const newComposer = function() {
    return openComposer({action: 'createTopic', draftKey: 'asfd', draftSequence: 1});
  };

  Discourse.Mobile.mobileView = true;
  equal(newComposer().get('showPreview'), false, "Don't show preview in mobile view");

  keyValueStore.set({ key: 'composer.showPreview', value: 'true' });
  equal(newComposer().get('showPreview'), false, "Don't show preview in mobile view even if KeyValueStore wants to");
  keyValueStore.remove('composer.showPreview');

  Discourse.Mobile.mobileView = false;
  equal(newComposer().get('showPreview'), true, "Show preview by default in desktop view");
});

test('open with a quote', function() {
  const quote = '[quote="neil, post:5, topic:413"]\nSimmer down you two.\n[/quote]';
  const newComposer = function() {
    return openComposer({action: Discourse.Composer.REPLY, draftKey: 'asfd', draftSequence: 1, quote: quote});
  };

  equal(newComposer().get('originalText'), quote, "originalText is the quote" );
  equal(newComposer().get('replyDirty'), false, "replyDirty is initally false with a quote" );
});

test("Title length for static page topics as admin", function() {
  Discourse.SiteSettings.min_topic_title_length = 5;
  Discourse.SiteSettings.max_topic_title_length = 10;
  const composer = createComposer();

  const post = Discourse.Post.create({id: 123, post_number: 2, static_doc: true});
  composer.setProperties({post: post, action: Composer.EDIT });

  composer.set('title', 'asdf');
  ok(composer.get('titleLengthValid'), "admins can use short titles");

  composer.set('title', 'this is a long title');
  ok(composer.get('titleLengthValid'), "admins can use long titles");

  composer.set('title', 'just right');
  ok(composer.get('titleLengthValid'), "in the range is okay");

  composer.set('title', '');
  ok(!composer.get('titleLengthValid'), "admins must set title to at least 1 character");
});
<commit_msg>FIX: Broken test<commit_after>import { blank } from 'helpers/qunit-helpers';
import { currentUser } from 'helpers/qunit-helpers';
import KeyValueStore from 'discourse/lib/key-value-store';
import Composer from 'discourse/models/composer';
import createStore from 'helpers/create-store';

module("model:composer");

const keyValueStore = new KeyValueStore("_test_composer");

function createComposer(opts) {
  opts = opts || {};
  opts.user = opts.user || currentUser();
  return createStore().createRecord('composer', opts);
}

function openComposer(opts) {
  const composer = createComposer(opts);
  composer.open(opts);
  return composer;
}

test('replyLength', function() {
  const replyLength = function(val, expectedLength) {
    const composer = createComposer({ reply: val });
    equal(composer.get('replyLength'), expectedLength);
  };

  replyLength("basic reply", 11, "basic reply length");
  replyLength(" \nbasic reply\t", 11, "trims whitespaces");
  replyLength("ba sic\n\nreply", 12, "count only significant whitespaces");
  replyLength("1[quote=]not counted[/quote]2[quote=]at all[/quote]3", 3, "removes quotes");
  replyLength("1[quote=]not[quote=]counted[/quote]yay[/quote]2", 2, "handles nested quotes correctly");
});

test('missingReplyCharacters', function() {
  Discourse.SiteSettings.min_first_post_length = 40;
  const missingReplyCharacters = function(val, isPM, isFirstPost, expected, message) {
    const composer = createComposer({ reply: val, creatingPrivateMessage: isPM, creatingTopic: isFirstPost });
    equal(composer.get('missingReplyCharacters'), expected, message);
  };

  missingReplyCharacters('hi', false, false, Discourse.SiteSettings.min_post_length - 2, 'too short public post');
  missingReplyCharacters('hi', false, true,  Discourse.SiteSettings.min_first_post_length - 2, 'too short first post');
  missingReplyCharacters('hi', true, false,  Discourse.SiteSettings.min_private_message_post_length - 2, 'too short private message');
});

test('missingTitleCharacters', function() {
  const missingTitleCharacters = function(val, isPM, expected, message) {
    const composer = createComposer({ title: val, creatingPrivateMessage: isPM });
    equal(composer.get('missingTitleCharacters'), expected, message);
  };

  missingTitleCharacters('hi', false, Discourse.SiteSettings.min_topic_title_length - 2, 'too short post title');
  missingTitleCharacters('z', true,  Discourse.SiteSettings.min_private_message_title_length - 1, 'too short pm title');
});

test('replyDirty', function() {
  const composer = createComposer();
  ok(!composer.get('replyDirty'), "by default it's false");

  composer.setProperties({
    originalText: "hello",
    reply: "hello"
  });

  ok(!composer.get('replyDirty'), "it's false when the originalText is the same as the reply");
  composer.set('reply', 'hello world');
  ok(composer.get('replyDirty'), "it's true when the reply changes");
});

test("appendText", function() {
  const composer = createComposer();

  blank(composer.get('reply'), "the reply is blank by default");

  composer.appendText("hello");
  equal(composer.get('reply'), "hello", "it appends text to nothing");
  composer.appendText(" world");
  equal(composer.get('reply'), "hello world", "it appends text to existing text");

  composer.clearState();
  composer.appendText("a\n\n\n\nb");
  composer.appendText("c",3,{block: true});

  equal(composer.get("reply"), "a\n\nc\n\nb");

  composer.clearState();
  composer.appendText("ab");
  composer.appendText("c",1,{block: true});

  equal(composer.get("reply"), "a\n\nc\n\nb");

  composer.clearState();
  composer.appendText("\nab");
  composer.appendText("c",0,{block: true});

  equal(composer.get("reply"), "c\n\nab");
});

test("Title length for regular topics", function() {
  Discourse.SiteSettings.min_topic_title_length = 5;
  Discourse.SiteSettings.max_topic_title_length = 10;
  const composer = createComposer();

  composer.set('title', 'asdf');
  ok(!composer.get('titleLengthValid'), "short titles are not valid");

  composer.set('title', 'this is a long title');
  ok(!composer.get('titleLengthValid'), "long titles are not valid");

  composer.set('title', 'just right');
  ok(composer.get('titleLengthValid'), "in the range is okay");
});

test("Title length for private messages", function() {
  Discourse.SiteSettings.min_private_message_title_length = 5;
  Discourse.SiteSettings.max_topic_title_length = 10;
  const composer = createComposer({action: Composer.PRIVATE_MESSAGE});

  composer.set('title', 'asdf');
  ok(!composer.get('titleLengthValid'), "short titles are not valid");

  composer.set('title', 'this is a long title');
  ok(!composer.get('titleLengthValid'), "long titles are not valid");

  composer.set('title', 'just right');
  ok(composer.get('titleLengthValid'), "in the range is okay");
});

test("Title length for private messages", function() {
  Discourse.SiteSettings.min_private_message_title_length = 5;
  Discourse.SiteSettings.max_topic_title_length = 10;
  const composer = createComposer({action: Composer.PRIVATE_MESSAGE});

  composer.set('title', 'asdf');
  ok(!composer.get('titleLengthValid'), "short titles are not valid");

  composer.set('title', 'this is a long title');
  ok(!composer.get('titleLengthValid'), "long titles are not valid");

  composer.set('title', 'just right');
  ok(composer.get('titleLengthValid'), "in the range is okay");
});

test('editingFirstPost', function() {
  const composer = createComposer();
  ok(!composer.get('editingFirstPost'), "it's false by default");

  const post = Discourse.Post.create({id: 123, post_number: 2});
  composer.setProperties({post: post, action: Composer.EDIT });
  ok(!composer.get('editingFirstPost'), "it's false when not editing the first post");

  post.set('post_number', 1);
  ok(composer.get('editingFirstPost'), "it's true when editing the first post");

});

test('clearState', function() {
  const composer = createComposer({
    originalText: 'asdf',
    reply: 'asdf2',
    post: Discourse.Post.create({id: 1}),
    title: 'wat'
  });

  composer.clearState();

  blank(composer.get('originalText'));
  blank(composer.get('reply'));
  blank(composer.get('post'));
  blank(composer.get('title'));

});

test('initial category when uncategorized is allowed', function() {
  Discourse.SiteSettings.allow_uncategorized_topics = true;
  const composer = openComposer({action: 'createTopic', draftKey: 'asfd', draftSequence: 1});
  ok(!composer.get('categoryId'), "Uncategorized by default");
});

test('initial category when uncategorized is not allowed', function() {
  Discourse.SiteSettings.allow_uncategorized_topics = false;
  const composer = openComposer({action: 'createTopic', draftKey: 'asfd', draftSequence: 1});
  ok(!composer.get('categoryId'), "Uncategorized by default. Must choose a category.");
});

test('showPreview', function() {
  const newComposer = function() {
    return openComposer({action: 'createTopic', draftKey: 'asfd', draftSequence: 1});
  };

  Discourse.Mobile.mobileView = true;
  equal(newComposer().get('showPreview'), false, "Don't show preview in mobile view");

  keyValueStore.set({ key: 'composer.showPreview', value: 'true' });
  equal(newComposer().get('showPreview'), false, "Don't show preview in mobile view even if KeyValueStore wants to");
  keyValueStore.remove('composer.showPreview');

  Discourse.Mobile.mobileView = false;
  equal(newComposer().get('showPreview'), true, "Show preview by default in desktop view");
});

test('open with a quote', function() {
  const quote = '[quote="neil, post:5, topic:413"]\nSimmer down you two.\n[/quote]';
  const newComposer = function() {
    return openComposer({action: Discourse.Composer.REPLY, draftKey: 'asfd', draftSequence: 1, quote: quote});
  };

  equal(newComposer().get('originalText'), quote, "originalText is the quote" );
  equal(newComposer().get('replyDirty'), false, "replyDirty is initally false with a quote" );
});

test("Title length for static page topics as admin", function() {
  Discourse.SiteSettings.min_topic_title_length = 5;
  Discourse.SiteSettings.max_topic_title_length = 10;
  const composer = createComposer();

  const post = Discourse.Post.create({id: 123, post_number: 2, static_doc: true});
  composer.setProperties({post: post, action: Composer.EDIT });

  composer.set('title', 'asdf');
  ok(composer.get('titleLengthValid'), "admins can use short titles");

  composer.set('title', 'this is a long title');
  ok(composer.get('titleLengthValid'), "admins can use long titles");

  composer.set('title', 'just right');
  ok(composer.get('titleLengthValid'), "in the range is okay");

  composer.set('title', '');
  ok(!composer.get('titleLengthValid'), "admins must set title to at least 1 character");
});
<|endoftext|>


<commit_before>var originalData, width, height;

// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
var b = {
  w: 160, h: 30, s: 3, t: 10
};

//var chartDiv = document.getElementById("chart");
//var svg = d3.select("div#chart").append('svg:svg');

// Calculates each arc
var arc = d3.arc()
            .startAngle(function(d) { return d.x0; })
            .endAngle(function(d) { return d.x1; })
            .innerRadius(function(d) { return d.y0; })
            .outerRadius(function(d) { return d.y1; });

//var svg = d3.select('div#chart').append('svg:svg');

var loadData = function(cmd) {
  $.ajax({
    type: 'GET',
    contentType: 'application/json; charset=utf-8',
    url: '/welcome',
    dataType: 'json',
    success: function(data) { drawOrUpdate(data, cmd); },
    failure: function(result) { error(); }
  });
};

var drawOrUpdate = function(data, cmd) {
  originalData = data;

  if (cmd == "drawSunburst") {
    drawSunburst(data);
  } else if (cmd == "updateSunburst") {
    updatePage();
  } else {
    console.log("Unrecognized command: " + cmd);
  }

  // Redraw based on the new size whenever the browser window is resized.
  window.addEventListener("resize", updatePage);
};

function error() {
  console.log("Something went wrong!");
}

var drawSunburst = function(data) {
  //console.log(data);
  var svgBox = document.querySelector("div#chart");
  width  = svgBox.clientWidth;
  height = svgBox.clientHeight;
  var radius = Math.min(width, height) / 2;
  var leftOffset =  width / 2 - 70;

  // Sets the explanation div position
  d3.select("#explanation").style("left", leftOffset + "px");

  // Formats the Data
  var partition = d3.partition()
	                  .size([2 * Math.PI, radius]);

  // Basic setup of page elements.
  initializeBreadcrumbTrail();

 // var color = d3.scaleOrdinal(d3.quantize(d3.interpolateRainbow,
 //                                         data.children.length + 1));

  var color = d3.scaleOrdinal()
                .domain(data)
                .range(d3.schemeSet3);

  var svg = d3.select('div#chart')
              .append('svg:svg')
              .attr("id", "svg-tag")
              .attr('width', width)
              .attr('height', height)
              .style('font', '12px sans-serif')
              .append('svg:g')
              .attr("id", "container")
              .attr("transform", "translate(" + width/2 + "," + height/2 + ")");

  // Bounding circle underneath the sunburst, to make it easier to detect
  // when the mouse leaves the parent g.
  svg.append('svg:circle')
     .attr('r', radius)
     .style('opacity', 0);

  // Finds the Root Node
  var root = d3.hierarchy(data)
               .sum(function(d) { return d.size });

  // For efficiency, filter nodes to keep only those large enough to see.
  // 0.005 radians = 0.29 degrees
  var nodes = partition(root).descendants()
                             .filter(function(d) {
			                         return (d.x1 - d.x0 > 0.005);
			                       });

  svg.selectAll('g')
      .data(nodes)
      .enter()
      .append('svg:g')
      .attr('class', "node")
      .append('svg:path')
      .attr('display', function(d) { return d.depth ? null : 'none'; })
      .attr('d', arc)
      .style('stroke', '#fff')
      .style('fill', function(d) {
	      return color((d.children ? d : d.parent).data.name);
      })
      .style('opacity', 1)
      .on('mouseover', mouseover);

  // Add a Label for Each Node
  svg.selectAll('.node')
     .append('svg:text')
     .attr('transform', function(d) {
     	 return 'translate(' + arc.centroid(d) +
     	        ')rotate(' + computeTextRotation(d) + ')';
     })
     .attr('dx', '6') // margin
     .attr('dy', '.35em')
     .attr('text-anchor', 'middle')
     .style('stroke-width', 5)
     .text(function(d) { return d.parent ? d.data.name : ''; });

  // Add the mouseleave handler to the bounding circle.
  d3.select("#container").on("mouseleave", mouseleave);

  // Fade all but the current sequence, and show it in the breadcrumb trail.
  function mouseover(d) {
    var info = d.data.name + ' (' + d.value + " IPs)";
    d3.select('#statistics').text(info);

    d3.select('#explanation').style('visibility', '');

    var sequenceArray = d.ancestors().reverse();
    sequenceArray.shift(); // remove root node from the array
    updateBreadcrumbs(sequenceArray, d.data.name);

    // Fade all the segments.
    d3.selectAll('path').style('opacity', 0.3);

    // Then highlight only those that are an ancestor of the current segment.
    svg.selectAll('path')
       .filter(function(node){ return sequenceArray.indexOf(node) >= 0; })
       //.filter(function(node) {
       //  return (sequenceArray.indexOf(node) >= 0);
       //})
       .style('opacity', 1);
  }

  // Restore everything to full opacity when moving off the visualization.
  function mouseleave(d) {
    // Hide the breadcrumb trail
    d3.select("#trail").style("visibility", "hidden");

    // Deactivate all segments during transition.
    d3.selectAll("path").on("mouseover", null);

    // Transition each segment to full opacity and then reactivate it.
    d3.selectAll("path")
      .transition()
      .duration(1000)
      .style("opacity", 1)
      .on("end", function() {
        d3.select(this).on("mouseover", mouseover);
      });

    d3.select("#explanation").style("visibility", "hidden");
  }

  // Update the breadcrumb trail to show the current sequence and percentage.
  function updateBreadcrumbs(nodeArray, info) {
    // Data join; key function combines name and depth (= position in sequence).
    var trail = d3.select("#trail")
        .selectAll("g")
        .data(nodeArray, function(d) { return d.data.name + d.depth; });

    // Remove exiting nodes.
    trail.exit().remove();

    // Add breadcrumb and label for entering nodes.
    var entering = trail.enter().append("svg:g");

    entering.append("svg:polygon")
        .attr("points", breadcrumbPoints)
        .style("fill", function(d) {
          return color((d.children ? d : d.parent).data.name)
        });

    entering.append("svg:text")
        .attr("x", (b.w + b.t) / 2)
        .attr("y", b.h / 2)
        .attr("dy", "0.35em")
        .attr("text-anchor", "middle")
        .text(function(d) { return d.data.name; });

    // Merge enter and update selections; set position for all nodes.
    entering.merge(trail).attr("transform", function(d, i) {
      return "translate(" + i * (b.w + b.s) + ", 0)";
    });

    // Now move and update the percentage at the end.
    d3.select("#trail").select("#endlabel")
        .attr("x", (nodeArray.length + 0.5) * (b.w + b.s))
        .attr("y", b.h / 2)
        .attr("dy", "0.35em")
        .attr("text-anchor", "middle")
        .text(info);

    // Make the breadcrumb trail visible, if it's hidden.
    d3.select("#trail").style("visibility", "");
  }
};

function computeTextRotation(d) {
  var angle = (d.x0 + d.x1) / Math.PI * 90;
  // Avoids upside-down labels. Labels aligned with slices.
  //return (angle < 90 || angle > 270) ? angle : angle + 180;

  // Alternate label formatting, in the radian direction
  return (angle < 180) ? angle - 90 : angle + 90;
}

function initializeBreadcrumbTrail() {
  // Add the svg area.
  var trail = d3.select("#sequence").append("svg:svg")
                .attr("width", width)
                .attr("id", "trail");
  // Add the label at the end, for the percentage.
  trail.append("svg:text")
       .attr("id", "endlabel")
       .style("fill", "#000");
}

// Generate a string that describes the points of a breadcrumb polygon.
function breadcrumbPoints(d, i) {
  var points = [];
  points.push("0,0");
  points.push(b.w + ",0");
  points.push(b.w + b.t + "," + (b.h / 2));
  points.push(b.w + "," + b.h);
  points.push("0," + b.h);
  if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
    points.push(b.t + "," + (b.h / 2));
  }
  return points.join(" ");
}

//loadData("drawSunburst"); // draw the first time to initialize

var updatePage = function() {
  //console.log("Window resized!");
  d3.select("#svg-tag").remove();
  drawSunburst(originalData);
};
<commit_msg>Redraw sunburst after window resized<commit_after>var svgBox, width, height, radius, originalData;

// Breadcrumb dimensions: width, height, spacing, width of tip/tail.
var b = {
  w: 160, h: 30, s: 3, t: 10
};

//var chartDiv = document.getElementById("chart");
//var svg = d3.select("div#chart").append('svg:svg');

// Calculates each arc
var arc = d3.arc()
            .startAngle(function(d) { return d.x0; })
            .endAngle(function(d) { return d.x1; })
            .innerRadius(function(d) { return d.y0; })
            .outerRadius(function(d) { return d.y1; });

//var svg = d3.select('div#chart').append('svg:svg');

var loadData = function(cmd) {
  $.ajax({
    type: 'GET',
    contentType: 'application/json; charset=utf-8',
    url: '/welcome',
    dataType: 'json',
    success: function(data) { drawOrUpdate(data, cmd); },
    failure: function(result) { error(); }
  });
};

var drawOrUpdate = function(data, cmd) {
  originalData = data;

  if (cmd == "drawSunburst") {
    drawSunburst(data);
  } else if (cmd == "updateSunburst") {
    updatePage();
  } else {
    console.log("Unrecognized command: " + cmd);
  }

  // Redraws based on the new size whenever the browser window is resized.
  window.addEventListener("resize", updatePage);
};

function error() {
  console.log("Something went wrong!");
}

var drawSunburst = function(data) {
  //console.log(data);

  svgBox = document.querySelector("div#chart");
  width  = svgBox.clientWidth;
  height = svgBox.clientHeight;
  radius = Math.min(width, height) / 2;

  // Sets the explanation div position
  var leftOffset =  width / 2 - 70;
  d3.select("#explanation").style("left", leftOffset + "px");

  // Formats the Data
  var partition = d3.partition()
	                  .size([2 * Math.PI, radius]);

  // Basic setup of page elements.
  initializeBreadcrumbTrail();

 // var color = d3.scaleOrdinal(d3.quantize(d3.interpolateRainbow,
 //                                         data.children.length + 1));

  var color = d3.scaleOrdinal()
                .domain(data)
                .range(d3.schemeSet3);

  var svg = d3.select('div#chart')
              .append('svg:svg')
              .attr('width', width)
              .attr('height', height)
              .style('font', '12px sans-serif')
              .append('svg:g')
              .attr("id", "container")
              .attr("transform", "translate(" + width/2 + "," + height/2 + ")");

  // Bounds circle underneath the sunburst, to make it easier to detect
  // when the mouse leaves the parent g.
  svg.append('svg:circle')
     .attr('r', radius)
     .style('opacity', 0);

  // Finds the Root Node
  var root = d3.hierarchy(data)
               .sum(function(d) { return d.size });

  // For efficiency, filters nodes to keep only those large enough to see.
  // 0.005 radians = 0.29 degrees
  var nodes = partition(root).descendants()
                             .filter(function(d) {
			                         return (d.x1 - d.x0 > 0.005);
			                       });

  svg.selectAll('g')
     .data(nodes)
     .enter()
     .append('svg:g')
     .attr('class', "node")
     .append('svg:path')
     .attr('display', function(d) { return d.depth ? null : 'none'; })
     .attr('d', arc)
     .style('stroke', '#fff')
     .style('fill', function(d) {
       return color((d.children ? d : d.parent).data.name);
     })
      .style('opacity', 1)
      .on('mouseover', mouseover);

  // Adds a Label for Each Node
    svg.selectAll('.node')
     .append('svg:text')
     .attr('transform', function(d) {
     	 return 'translate(' + arc.centroid(d) +
     	        ')rotate(' + computeTextRotation(d) + ')';
     })
     .attr('dx', '6') // margin
     .attr('dy', '.35em')
     .attr('text-anchor', 'middle')
     .style('stroke-width', 5)
     .text(function(d) { return d.parent ? d.data.name : ''; });

    // Adds the mouseleave handler to the bounding circle.
  d3.select("#container").on("mouseleave", mouseleave);

  // Fades all but the current sequence, and show it in the breadcrumb trail.
  function mouseover(d) {
    var info = d.data.name + ' (' + d.value + " IPs)";
    d3.select('#statistics').text(info);

    d3.select('#explanation').style('visibility', '');

    var sequenceArray = d.ancestors().reverse();
    sequenceArray.shift(); // remove root node from the array
    updateBreadcrumbs(sequenceArray, d.data.name);

    // Fades all the segments.
    d3.selectAll('path').style('opacity', 0.3);

    // Then highlights only those that are an ancestor of the current segment.
    svg.selectAll('path')
       .filter(function(node){ return sequenceArray.indexOf(node) >= 0; })
       //.filter(function(node) {
       //  return (sequenceArray.indexOf(node) >= 0);
       //})
       .style('opacity', 1);
  }

  // Restores everything to full opacity when moving off the visualization.
  function mouseleave(d) {
    // Hides the breadcrumb trail
    d3.select("#trail").style("visibility", "hidden");

    // Deactivates all segments during transition.
    d3.selectAll("path").on("mouseover", null);

    // Transitions each segment to full opacity and then reactivate it.
    d3.selectAll("path")
      .transition()
      .duration(1000)
      .style("opacity", 1)
      .on("end", function() {
        d3.select(this).on("mouseover", mouseover);
      });

    d3.select("#explanation").style("visibility", "hidden");
  }

  // Updates the breadcrumb trail to show the current sequence and percentage.
  function updateBreadcrumbs(nodeArray, info) {
    // Data join; key function combines name and depth (= position in sequence).
    var trail = d3.select("#trail")
        .selectAll("g")
        .data(nodeArray, function(d) { return d.data.name + d.depth; });

    // Removes exiting nodes.
    trail.exit().remove();

    // Adds breadcrumb and label for entering nodes.
    var entering = trail.enter().append("svg:g");

    entering.append("svg:polygon")
        .attr("points", breadcrumbPoints)
        .style("fill", function(d) {
          return color((d.children ? d : d.parent).data.name)
        });

    entering.append("svg:text")
        .attr("x", (b.w + b.t) / 2)
        .attr("y", b.h / 2)
        .attr("dy", "0.35em")
        .attr("text-anchor", "middle")
        .text(function(d) { return d.data.name; });

    // Merge enter and update selections; set position for all nodes.
    entering.merge(trail).attr("transform", function(d, i) {
      return "translate(" + i * (b.w + b.s) + ", 0)";
    });

    // Now move and update the percentage at the end.
    d3.select("#trail").select("#endlabel")
        .attr("x", (nodeArray.length + 0.5) * (b.w + b.s))
        .attr("y", b.h / 2)
        .attr("dy", "0.35em")
        .attr("text-anchor", "middle")
        .text(info);

    // Make the breadcrumb trail visible, if it's hidden.
    d3.select("#trail").style("visibility", "");
  }
};

function computeTextRotation(d) {
  var angle = (d.x0 + d.x1) / Math.PI * 90;
  // Avoids upside-down labels. Labels aligned with slices.
  //return (angle < 90 || angle > 270) ? angle : angle + 180;

  // Alternate label formatting, in the radian direction
  return (angle < 180) ? angle - 90 : angle + 90;
}

function initializeBreadcrumbTrail() {
  // Adds the svg area.
  var trail = d3.select("#sequence").append("svg:svg")
                .attr("width", width)
                .attr("id", "trail");
  // Adds the label at the end, for the percentage.
  trail.append("svg:text")
       .attr("id", "endlabel")
       .style("fill", "#000");
}

// Generates a string that describes the points of a breadcrumb polygon.
function breadcrumbPoints(d, i) {
  var points = [];
  points.push("0,0");
  points.push(b.w + ",0");
  points.push(b.w + b.t + "," + (b.h / 2));
  points.push(b.w + "," + b.h);
  points.push("0," + b.h);
  if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex.
    points.push(b.t + "," + (b.h / 2));
  }
  return points.join(" ");
}

// Redraws the chart after the window was resized.
var updatePage = function() {
  d3.selectAll("svg").remove();

  svgBox = document.querySelector("div#chart");
  width  = svgBox.clientWidth;
  height = svgBox.clientHeight;
  radius = Math.min(width, height) / 2;
  drawSunburst(originalData);
};

// Initializes: draw the first time here or in welcome#index.
//loadData("drawSunburst");
<|endoftext|>


<commit_before>'use strict';

/* Services */

// Demonstrate how to register services
// In this case it is a simple value service.
angular.module('myApp.services', []).
  value('version', '0.1')
  .factory("usersService", function($http, Restangular){
    var _getUsers = function(){
    	var userData = Restangular.all('users').getList();
    	return userData;
    }

    var _addNewUser = function(newUser){
    	var user = Restangular.all('users').post(newUser);
    	return user;

    }
    var _fetchUser = function(user){
    	var originalUser =  Restangular.one('users', user.id).get();
    	var fetchUser = Restangular.copy(originalUser);
    	return fetchUser;
    }
    var _removeUser = function(user){
    	var originalUser =  Restangular.one('users', user.id).get();
    	return originalUser;
    }

    return{
        getUsers: _getUsers,
        addNewUser: _addNewUser,
        editUser: _fetchUser,
        removeUser:_removeUser
    };
});

<commit_msg>Updating changes<commit_after>'use strict';

/* Services */

// Demonstrate how to register services
// In this case it is a simple value service.
angular.module('myApp.services', []).
  value('version', '0.1')
  .factory("usersService", function($http, Restangular){
    var _getUsers = function(){
    	var userData = Restangular.all('users').getList();
    	return userData;
    }

    var _addNewUser = function(newUser){
    	var user = Restangular.all('users').post(newUser);
    	return user;

    }
    var _fetchUser = function(user){
    	var originalUser =  Restangular.one('users', user.id).get();
    	var fetchUser = Restangular.copy(originalUser);
    	return fetchUser;
    }
    var _removeUser = function(user){
    	var originalUser =  Restangular.one('users', user.id).get();
    	return originalUser;
    }

    return{
        getUsers: _getUsers,
        addNewUser: _addNewUser,
        fetchUser: _fetchUser,
        removeUser:_removeUser
    };
});

<|endoftext|>


<commit_before>var emojis = require('emojilib')
var clipboard = require('clipboard')
var ipc = require('ipc')
var index = buildIndex(emojis)
var searchInput = document.querySelector('.js-search')

// todo
// - up/down/left/right key navigation
// - pagination?

searchInput.focus()
searchInput.addEventListener('input', function (evt) {
  search(this.value)
})

document.addEventListener('keyup', function (evt) {
  if (evt.keyCode === 13 && evt.target.className === "code") {
    // on enter: copy data and exit
    if (evt.shiftKey) {
      var data = evt.target.dataset.char
    } else {
      var data = evt.target.value
    }
    clipboard.writeText(data)

    ipc.send('abort')
  } else if (evt.keyCode === 191) {
    // on `/`: focus on the search field
    searchInput.select()
  } else if (evt.keyCode === 27) {
    // on escape: exit
    ipc.send('abort')
  }
})

function search (query) {
  document.querySelector('.js-outcome').innerHTML =

  (Object.keys(index).filter(function matchQuery (keyword) {
    return keyword.match(query)
  })).map(function(keyword) {
    return index[keyword]
  }).join().split(',').filter(function filterUniqueResults (emoji, pos, arr) {
    return emoji && arr.indexOf(emoji) === pos
  }).map(function generateMarkup (name) {
    var unicode = (emojis[name]['char'] || '--')
    var result = '
' + unicode + ''
    result += '<input readonly type="text" data-char="' + unicode + '" class="code" value=":' + name + ':">
'
    return result
  }).join('')
}

function buildIndex (emojis) {
  var keywords = {}
  Object.keys(emojis).map(function (name) {
    var words = emojis[name]["keywords"]
    words.push(name)

    words.forEach(function(word) {
      if (keywords[word] && keywords[word].indexOf(name) < 0) {
        keywords[word].push(name)
      } else {
        keywords[word] = [name]
      }
    })
  })
  return keywords
}

function isWord (charCode) {
  var word = String.fromCharCode(charCode).match(/\w/)
  return !!word ? word : false
}<commit_msg>🌺 just type into the search box!<commit_after>var emojis = require('emojilib')
var clipboard = require('clipboard')
var ipc = require('ipc')
var index = buildIndex(emojis)
var searchInput = document.querySelector('.js-search')

// todo
// - up/down/left/right key navigation
// - pagination?

searchInput.focus()
searchInput.addEventListener('input', function (evt) {
  search(this.value)
})

document.addEventListener('keyup', function (evt) {
  if (evt.keyCode === 13 && evt.target.className === "code") {
    // on enter: copy data and exit
    if (evt.shiftKey) {
      var data = evt.target.dataset.char
    } else {
      var data = evt.target.value
    }
    clipboard.writeText(data)

    ipc.send('abort')
  } else if (evt.keyCode === 191) {
    // on `/`: focus on the search field
    searchInput.select()
  } else if (evt.keyCode === 27) {
    // on escape: exit
    ipc.send('abort')
  }
})

document.addEventListener('keypress', function (evt) {
  // if typing while navigatin, just type into the search box!
  var word = isWord(evt.charCode)
  if (word && evt.target.className === "code") {
    searchInput.focus()
    searchInput.value = word
  }
})

function search (query) {
  document.querySelector('.js-outcome').innerHTML =

  (Object.keys(index).filter(function matchQuery (keyword) {
    return keyword.match(query)
  })).map(function(keyword) {
    return index[keyword]
  }).join().split(',').filter(function filterUniqueResults (emoji, pos, arr) {
    return emoji && arr.indexOf(emoji) === pos
  }).map(function generateMarkup (name) {
    var unicode = (emojis[name]['char'] || '--')
    var result = '
' + unicode + ''
    result += '<input readonly type="text" data-char="' + unicode + '" class="code" value=":' + name + ':">
'
    return result
  }).join('')
}

function buildIndex (emojis) {
  var keywords = {}
  Object.keys(emojis).map(function (name) {
    var words = emojis[name]["keywords"]
    words.push(name)

    words.forEach(function(word) {
      if (keywords[word] && keywords[word].indexOf(name) < 0) {
        keywords[word].push(name)
      } else {
        keywords[word] = [name]
      }
    })
  })
  return keywords
}

function isWord (charCode) {
  var word = String.fromCharCode(charCode).match(/\w/)
  return !!word ? word : false
}<|endoftext|>


<commit_before>$(function() {

  var startTime = new Date();
  var lastOffset = 1.0;
  var currentTime = function() {
    return lastOffset;
  };

  var chartSize = {n: 15.0, step: 5.0};

  $('body').on('click', '.js-adjust-window', function() {
    chartSize = $(this).data();
  });

  $('body').on('click', '.chart', function() {
    $(this).data('chart').toggleSize();
  });

  var clips = 0;
  function StripChart(container, name) {
    container = container.append('div').attr('class', 'chart');
    $(container[0][0]).data('chart', this);
    container.append('h3').text(name);

    var data = [];

    var margin = {top: 10, right: 10, bottom: 20, left: 70},
        width = 960 - margin.left - margin.right,
        lgHeight = 500 - margin.top - margin.bottom,
        smHeight = 80 - margin.top - margin.bottom,
        height = smHeight;

    var make_x_scale = function() {
      var t, range;
      if (chartSize.all) {
        t = currentTime();
        range = width;
      } else {
        t = d3.max([currentTime(), chartSize.n]);
        range = t * width / chartSize.n;
      }
      return d3.scale.linear()
        .domain([0, t])
        .range([0, range]);
    };
    var x = make_x_scale();

    var make_y_scale = function() {
      return d3.scale.linear()
        .domain(d3.extent(data, function(d) { return d.y; }))
        .range([height, 0]);
    };
    var y = make_y_scale();

    var line = d3.svg.line()
        .x(function(d) { return x(d.x); })
        .y(function(d) { return y(d.y); });

    var svgElement = container.append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    var svg = svgElement.append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var clipId = "clip" + (clips++);
    var clipRect = svg.append("defs").append("clipPath")
        .attr("id", clipId)
      .append("rect")
        .attr("width", width)
        .attr("height", height);

    var make_x_axis = function() {
      var baseAxis = d3.svg.axis().scale(x).orient("bottom");
      if (chartSize.all) {
        return baseAxis.ticks(10);
      } else {
        var t = currentTime();
        var ticks = [];
        var i;
        for(i = 0; i <= t; i += chartSize.step) {
          ticks.push(i);
        }
        for(; i <= chartSize.n; i += chartSize.step) {
          ticks.push(i);
        }
        return baseAxis.tickValues(ticks);
      }
    };
    var xAxis = svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(d3.svg.axis().scale(x).orient("bottom"));

    var make_y_axis = function() {
      return d3.svg.axis().scale(y).ticks(4).orient("left");
    };
    var yAxis = svg.append("g")
        .attr("class", "y axis")
        .call(make_y_axis());

    var path = svg.append("g")
        .attr("clip-path", "url(#" + clipId + ")")
      .append("path")
        .data([data])
        .attr("class", "line")
        .attr("d", line);

    var updateInterval = 500;
    var tick = function() {
      x = make_x_scale();
      y = make_y_scale();

      var t = currentTime();
      var xtrans = x((!chartSize.all && t > chartSize.n) ? (chartSize.n - t) : 0);

      svgElement.attr("height", height + margin.top + margin.bottom);
      clipRect.attr("height", height);

      xAxis
          .call(make_x_axis())
        .transition().duration(updateInterval).ease('linear')
          .attr("transform", "translate(" + xtrans + "," + height + ")");

      yAxis.call(make_y_axis());

      path
          .attr("d", line)
        .transition()
          .duration(updateInterval)
          .ease("linear")
          .attr("transform", "translate(" + xtrans + ")")
          .each('end', tick);
    }

    tick();

    this.addPoint = function(point) {
      data.push(point);
    };

    this.toggleSize = function() {
      height = (height == lgHeight) ? smHeight : lgHeight;
    };
  }

  var charts = {};
  var ws = new WebSocket($('body').data('websocket'));
  ws.onmessage = function(e) {
    var data = JSON.parse(e.data);
    var i;
    for (i = 0; i < data.samples.length; i++) {
      var sample = data.samples[i];
      var chart = charts[sample.name];
      lastOffset = data.offset;
      if (!chart)
        chart = charts[sample.name] = new StripChart(d3.select('body'), sample.name);
      if (sample.rss)
        chart.addPoint({x: data.offset, y: sample.rss});
    }
  };

});
<commit_msg>Show horizontal grid lines.<commit_after>$(function() {

  var startTime = new Date();
  var lastOffset = 1.0;
  var currentTime = function() {
    return lastOffset;
  };

  var chartSize = {n: 15.0, step: 5.0};

  $('body').on('click', '.js-adjust-window', function() {
    chartSize = $(this).data();
  });

  $('body').on('click', '.chart', function() {
    $(this).data('chart').toggleSize();
  });

  var clips = 0;
  function StripChart(container, name) {
    container = container.append('div').attr('class', 'chart');
    $(container[0][0]).data('chart', this);
    container.append('h3').text(name);

    var data = [];

    var margin = {top: 10, right: 10, bottom: 20, left: 70},
        width = 960 - margin.left - margin.right,
        lgHeight = 500 - margin.top - margin.bottom,
        smHeight = 80 - margin.top - margin.bottom,
        height = smHeight;

    var make_x_scale = function() {
      var t, range;
      if (chartSize.all) {
        t = currentTime();
        range = width;
      } else {
        t = d3.max([currentTime(), chartSize.n]);
        range = t * width / chartSize.n;
      }
      return d3.scale.linear()
        .domain([0, t])
        .range([0, range]);
    };
    var x = make_x_scale();

    var make_y_scale = function() {
      return d3.scale.linear()
        .domain(d3.extent(data, function(d) { return d.y; }))
        .range([height, 0]);
    };
    var y = make_y_scale();

    var line = d3.svg.line()
        .x(function(d) { return x(d.x); })
        .y(function(d) { return y(d.y); });

    var svgElement = container.append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    var svg = svgElement.append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var clipId = "clip" + (clips++);
    var clipRect = svg.append("defs").append("clipPath")
        .attr("id", clipId)
      .append("rect")
        .attr("width", width)
        .attr("height", height);

    var make_x_axis = function() {
      var baseAxis = d3.svg.axis().scale(x).orient("bottom");
      if (chartSize.all) {
        return baseAxis.ticks(10);
      } else {
        var t = currentTime();
        var ticks = [];
        var i;
        for(i = 0; i <= t; i += chartSize.step) {
          ticks.push(i);
        }
        for(; i <= chartSize.n; i += chartSize.step) {
          ticks.push(i);
        }
        return baseAxis.tickValues(ticks);
      }
    };
    var xAxis = svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(d3.svg.axis().scale(x).orient("bottom"));

    var make_y_axis = function() {
      return d3.svg.axis().scale(y).ticks(4).orient("left");
    };
    var yAxis = svg.append("g")
        .attr("class", "y axis")
        .call(make_y_axis());

    var yGrid = svg.append("g")
        .attr("class", "grid")
        .call(make_y_axis().tickSize(-width, 0, 0).tickFormat(""));

    var path = svg.append("g")
        .attr("clip-path", "url(#" + clipId + ")")
      .append("path")
        .data([data])
        .attr("class", "line")
        .attr("d", line);

    var updateInterval = 500;
    var tick = function() {
      x = make_x_scale();
      y = make_y_scale();

      var t = currentTime();
      var xtrans = x((!chartSize.all && t > chartSize.n) ? (chartSize.n - t) : 0);

      svgElement.attr("height", height + margin.top + margin.bottom);
      clipRect.attr("height", height);

      xAxis
          .call(make_x_axis())
        .transition().duration(updateInterval).ease('linear')
          .attr("transform", "translate(" + xtrans + "," + height + ")");

      yAxis.call(make_y_axis());
      yGrid.call(make_y_axis().tickSize(-width, 0, 0).tickFormat(""));

      path
          .attr("d", line)
        .transition()
          .duration(updateInterval)
          .ease("linear")
          .attr("transform", "translate(" + xtrans + ")")
          .each('end', tick);
    }

    tick();

    this.addPoint = function(point) {
      data.push(point);
    };

    this.toggleSize = function() {
      height = (height == lgHeight) ? smHeight : lgHeight;
    };
  }

  var charts = {};
  var ws = new WebSocket($('body').data('websocket'));
  ws.onmessage = function(e) {
    var data = JSON.parse(e.data);
    var i;
    for (i = 0; i < data.samples.length; i++) {
      var sample = data.samples[i];
      var chart = charts[sample.name];
      lastOffset = data.offset;
      if (!chart)
        chart = charts[sample.name] = new StripChart(d3.select('body'), sample.name);
      if (sample.rss)
        chart.addPoint({x: data.offset, y: sample.rss});
    }
  };

});
<|endoftext|>


<commit_before>/***
|''Name''|ErrorHandlerPlugin|
|''Version''|0.4.0|
|''Author''|Jon Robson|
|''Description''|Localised tiddler save errors including edit conflict resolution.|
|''Requires''|TiddlyWebConfig|
***/

//{{{
(function($) {

var plugin = config.extensions.ServerSideSavingPlugin;

var msgs = config.messages.editConflict = {
	loading: "Loading..",
	resolve: "[[Edit Conflict]]@glossary: this tiddler may have been changed by someone else.",
	reviewDiff: "review (recommended)",
	reviewDiffTooltip: "review changes made to this tiddler",
	reviewDiffError: "error retrieving revision.",
	save: "overwrite",
	saveTooltip: "make this revision the top revision of this tiddler",
	discard: "cancel",
	discardTooltip: "undo changes to this tiddler and get most recent version",
	diffTitle: "%0 [edit conflict %1]",
	diffFieldTitle: "%0 - fields [edit conflict %1]",
	diffTextTitle: "%0 - text [edit conflict %1]",
	updating: "updating your version...",
	diffHeader: ["Review the changes that have been made whilst you were editing this tiddler. ",
			"Fold relevant changes back into your version.\n",
		"{{removed{Red}}} highlight shows content removed. ",
		"{{added{Green}}} highlight shows content added.\n"].join(""),
	diffTextHeader: "View changes in text",
	diffFieldsHeader: "View changes in fields"
};

var ext = config.extensions.errorHandler = {
	diffTags: ["excludeLists", "excludeMissing", "excludeSearch"],
	displayMessage: function(message, tiddler, context) {
		var desc = context && context.httpStatus ? context.statusText :
			plugin.locale.connectionError;
		var reportArea = ext.reportError(tiddler.title);
		var msg = $("<div />").appendTo(reportArea);
		if(message == "saveConflict") {
			wikify(msgs.resolve, msg[0]);
			var choiceArea = $("<div />").appendTo(reportArea)[0];
			ext.editConflictHandler(choiceArea, tiddler);
		} else {
			msg.text(plugin.locale[message].format(tiddler.title, desc));
		}
	},
	editConflictHandler: function(container, tiddler) {
		var title = tiddler.title;
		var myrev = tiddler.fields["server.page.revision"];
		// note user now needs to edit, fix problem and save.
			// todo: make sure this gets reset in save callback
		store.getTiddler(title).fields["server.page.revision"] = "false";

		var diffBtn = createTiddlyButton(container, msgs.reviewDiff, msgs.reviewDiffTooltip, function(ev) {
			var title = $(ev.target).data("title");
			ext.displayDiff(ev.target, store.getTiddler(title), myrev);
		});
		var saveBtn = createTiddlyButton(container, msgs.save, msgs.saveTooltip, function(ev) {
				var title = $(ev.target).data("title");
				var tid = store.saveTiddler(store.getTiddler(title));
				autoSaveChanges(null, [tid]);
			});
		var ignoreBtn = createTiddlyButton(container, msgs.discard, msgs.discardTooltip, function(ev) {
			var title = $(ev.target).text(msgs.updating).data("title");
			ext.resetToServerVersion(store.getTiddler(title));
		});
		$([diffBtn, ignoreBtn, saveBtn]).data("title", title);
	},
	getDiffTiddlerTexts: function(diffText) {
		var chunks = diffText.split("\n  \n");
		if(chunks.length < 2) {
			return [chunks[0], ""];
		} else {
			var diffFieldsText = "{{diff{\n%0\n}}}".format(chunks[0]);
			diffText = '{{diff{\n%0\n}}}'.format(chunks.splice(1, chunks.length).join("\n"));
			return [diffText, diffFieldsText];
		}
	},
	makeDiffTiddler: function(title, diff) {
		var newTiddler = new Tiddler(title);
		var tags = ext.diffTags;
		newTiddler.text = msgs.loading;
		newTiddler.tags = diff ? tags.concat(["diff"]) : tags;
		newTiddler = store.saveTiddler(newTiddler);
		$.extend(store.getTiddler(title).fields,
			config.defaultCustomFields); // allow option to save it.
		return newTiddler;
	},
	displayDiff: function(src, tiddler, latestRevision) {
		var adaptor = tiddler.getAdaptor();
		var title = tiddler.title;
		var ts = new Date().formatString("0hh:0mm:0ss");
		var diffTitle = msgs.diffTitle.format(title, ts);
		var diffTextTitle = msgs.diffTextTitle.format(title, ts);
		var diffFieldsTitle = msgs.diffFieldTitle.format(title, ts);
		ext.makeDiffTiddler(diffTextTitle, true);
		ext.makeDiffTiddler(diffFieldsTitle, true);
		var newTiddler = ext.makeDiffTiddler(diffTitle, false);
		newTiddler.text = ['%0\n<<slider chkViewDiffText "%1" "%2">>\n',
			'<<slider chkViewDiffField "%3" "%4">>'].join("").
			format(msgs.diffHeader, diffTextTitle, msgs.diffTextHeader,
				diffFieldsTitle, msgs.diffFieldsHeader);
		store.saveTiddler(newTiddler);

		var callback = function(r) {
			var text = ext.getDiffTiddlerTexts(r);
			store.getTiddler(diffTextTitle).text = text[0];
			store.getTiddler(diffFieldsTitle).text = text[1];
			story.refreshTiddler(diffTitle, null, true);
		};
		var workspace = "bags/%0".format(tiddler.fields["server.bag"]);
		ajaxReq({
			type: "get",
			dataType: "text",
			url: "/diff?rev1=%0/%1/%2&rev2=%0/%1".format(workspace, title, latestRevision),
			success: callback,
			error: function() {
				displayMessage(msgs.reviewDiffError);
			}
		});
		story.displayTiddler(src, diffTitle);
	},
	resetToServerVersion: function(tiddler) {
		var adaptor = tiddler.getAdaptor();
		var ctx = {
			host: tiddler.fields["server.host"],
			workspace: "bags/" + tiddler.fields["server.bag"]
		};
		adaptor.getTiddler(tiddler.title, ctx, null, function(context) {
			store.saveTiddler(context.tiddler);
			story.refreshTiddler(tiddler.title);
			store.setDirty(false);
		});
	},
	reportError: function(title) {
		var el = story.getTiddler(title);
		if(!el) {
			el = story.displayTiddler(null, title);
		}
		return $("<div />").addClass("error annotation").prependTo(el)[0];
	}
};

plugin.reportFailure = function(message, tiddler, context) {
	config.options.chkViewDiffText = config.options.chkViewDiffText == "undefined" ?
		true : config.options.chkViewDiffText;
	config.options.chkViewDiffFields = config.options.chkViewDiffFields == "undefined" ?
		false : config.options.chkViewDiffFields;

	ext.displayMessage(message, tiddler, context);
};

})(jQuery);
//}}}
<commit_msg>post-merge cleanup<commit_after>/***
|''Name''|ErrorHandlerPlugin|
|''Version''|0.4.1|
|''Author''|Jon Robson|
|''Description''|Localised tiddler save errors including edit conflict resolution.|
|''CoreVersion''|2.6.1|
|''Requires''|TiddlyWebConfig|
***/
//{{{
(function($) {

var sssp = config.extensions.ServerSideSavingPlugin;

var msgs = config.messages.editConflict = {
	loading: "Loading..",
	resolve: "[[Edit Conflict]]@glossary: this tiddler may have been changed by someone else.",
	reviewDiff: "review (recommended)",
	reviewDiffTooltip: "review changes made to this tiddler",
	reviewDiffError: "error retrieving revision.",
	save: "overwrite",
	saveTooltip: "make this revision the top revision of this tiddler",
	discard: "cancel",
	discardTooltip: "undo changes to this tiddler and get most recent version",
	diffTitle: "%0 [edit conflict %1]",
	diffFieldTitle: "%0 - fields [edit conflict %1]",
	diffTextTitle: "%0 - text [edit conflict %1]",
	updating: "updating your version...",
	diffHeader: ["Review the changes that have been made whilst you were editing this tiddler. ",
		"Fold relevant changes back into your version.\n",
		"{{removed{Red}}} highlight shows content removed. ",
		"{{added{Green}}} highlight shows content added.\n"].join(""),
	diffTextHeader: "View changes in text",
	diffFieldsHeader: "View changes in fields"
};

var plugin = config.extensions.errorHandler = {
	diffTags: ["excludeLists", "excludeMissing", "excludeSearch"],
	displayMessage: function(message, tiddler, context) {
		var desc = context && context.httpStatus ? context.statusText :
			sssp.locale.connectionError;
		var reportArea = plugin.reportError(tiddler.title);
		var msg = $("<div />").appendTo(reportArea);
		if(message == "saveConflict") {
			wikify(msgs.resolve, msg[0]);
			var choiceArea = $("<div />").appendTo(reportArea)[0];
			plugin.editConflictHandler(choiceArea, tiddler);
		} else {
			msg.text(sssp.locale[message].format(tiddler.title, desc));
		}
	},
	editConflictHandler: function(container, tiddler) {
		var title = tiddler.title;
		var myrev = tiddler.fields["server.page.revision"];
		// note user now needs to edit, fix problem and save.
		// TODO: make sure this gets reset in save callback
		store.getTiddler(title).fields["server.page.revision"] = "false";

		var diffBtn = createTiddlyButton(container, msgs.reviewDiff, msgs.reviewDiffTooltip, function(ev) {
			var title = $(ev.target).data("title");
			plugin.displayDiff(ev.target, store.getTiddler(title), myrev);
		});
		var saveBtn = createTiddlyButton(container, msgs.save, msgs.saveTooltip, function(ev) {
				var title = $(ev.target).data("title");
				var tid = store.saveTiddler(store.getTiddler(title));
				autoSaveChanges(null, [tid]);
			});
		var ignoreBtn = createTiddlyButton(container, msgs.discard, msgs.discardTooltip, function(ev) {
			var title = $(ev.target).text(msgs.updating).data("title");
			plugin.resetToServerVersion(store.getTiddler(title));
		});
		$([diffBtn, ignoreBtn, saveBtn]).data("title", title);
	},
	getDiffTiddlerTexts: function(diffText) {
		var chunks = diffText.split("\n  \n");
		if(chunks.length < 2) {
			return [chunks[0], ""];
		} else {
			var diffFieldsText = "{{diff{\n%0\n}}}".format(chunks[0]);
			diffText = '{{diff{\n%0\n}}}'.format(chunks.splice(1, chunks.length).join("\n"));
			return [diffText, diffFieldsText];
		}
	},
	makeDiffTiddler: function(title, diff) {
		var newTiddler = new Tiddler(title);
		var tags = plugin.diffTags;
		newTiddler.text = msgs.loading;
		newTiddler.tags = diff ? tags.concat(["diff"]) : tags;
		newTiddler = store.saveTiddler(newTiddler);
		$.extend(store.getTiddler(title).fields,
			config.defaultCustomFields); // allow option to save it
		return newTiddler;
	},
	displayDiff: function(src, tiddler, latestRevision) {
		var adaptor = tiddler.getAdaptor();
		var title = tiddler.title;
		var ts = new Date().formatString("0hh:0mm:0ss");
		var diffTitle = msgs.diffTitle.format(title, ts);
		var diffTextTitle = msgs.diffTextTitle.format(title, ts);
		var diffFieldsTitle = msgs.diffFieldTitle.format(title, ts);
		plugin.makeDiffTiddler(diffTextTitle, true);
		plugin.makeDiffTiddler(diffFieldsTitle, true);
		var newTiddler = plugin.makeDiffTiddler(diffTitle, false);
		newTiddler.text = ['%0\n<<slider chkViewDiffText "%1" "%2">>\n',
			'<<slider chkViewDiffField "%3" "%4">>'].join("").
			format(msgs.diffHeader, diffTextTitle, msgs.diffTextHeader,
				diffFieldsTitle, msgs.diffFieldsHeader);
		store.saveTiddler(newTiddler);

		var callback = function(r) {
			var text = plugin.getDiffTiddlerTexts(r);
			store.getTiddler(diffTextTitle).text = text[0];
			store.getTiddler(diffFieldsTitle).text = text[1];
			story.refreshTiddler(diffTitle, null, true);
		};
		var workspace = "bags/%0".format(tiddler.fields["server.bag"]);
		ajaxReq({
			type: "get",
			dataType: "text",
			url: "/diff?rev1=%0/%1/%2&rev2=%0/%1".format(workspace, title, latestRevision),
			success: callback,
			error: function() {
				displayMessage(msgs.reviewDiffError);
			}
		});
		story.displayTiddler(src, diffTitle);
	},
	resetToServerVersion: function(tiddler) {
		var adaptor = tiddler.getAdaptor();
		var ctx = {
			host: tiddler.fields["server.host"],
			workspace: "bags/" + tiddler.fields["server.bag"]
		};
		adaptor.getTiddler(tiddler.title, ctx, null, function(context) {
			store.saveTiddler(context.tiddler);
			story.refreshTiddler(tiddler.title);
			store.setDirty(false);
		});
	},
	reportError: function(title) {
		var el = story.getTiddler(title);
		if(!el) {
			el = story.displayTiddler(null, title);
		}
		return $("<div />").addClass("error annotation").prependTo(el)[0];
	}
};

sssp.reportFailure = function(message, tiddler, context) {
	config.options.chkViewDiffText = config.options.chkViewDiffText === undefined ?
		true : config.options.chkViewDiffText;
	config.options.chkViewDiffFields = config.options.chkViewDiffFields || false;
	plugin.displayMessage(message, tiddler, context);
};

})(jQuery);
//}}}
<|endoftext|>


<commit_before>var should = require('should');
var request = require('superagent');
var fs = require('fs');
var webshot = require('webshot');
var sanitize = require('sanitize-filename');
var url = require('url');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var path = require('path');
var resemble = require('node-resemble-js');
var trim = require('trim-character');
var extend = require('extend');

var options = {
  takeShotOnCallback: true,
  timeout: 30000,
  errorIfStatusIsNot200: true,
  shotSize: {
    width: 'all',
    height: 'all'
  }
};

var mobileOptions = extend({}, options);

mobileOptions.screenSize = {
  width: 320,
  height: 480
};

mobileOptions.shotSize = {
  width: 320,
  height: 'all'
};

var tolerance = 0;

rimraf.sync('test/fixtures/visual-regression/test-renderings');
rimraf.sync('test/fixtures/visual-regression/diff-renderings');

mkdirp.sync('test/fixtures/visual-regression/test-renderings');
mkdirp.sync('test/fixtures/visual-regression/diff-renderings');

/**
 * Visual regression checks
 */
describe('The pattern library page at', function() {
  var urls = JSON.parse(fs.readFileSync('.tmp/testURLs.json', 'utf8'));

  this.timeout(30000);

  urls.forEach(function(componentUrl) {

    it(componentUrl + ' should not have regressed visually on desktop', function(done) {

      var fileName = url.parse(componentUrl).pathname;
      fileName = fileName.replace(new RegExp('/', 'g'), '-');
      fileName = trim(fileName, '-');
      fileName = sanitize(fileName);
      fileName = 'desktop-' + fileName;

      var renderStream = webshot(componentUrl, options);
      var file = fs.createWriteStream('test/fixtures/visual-regression/test-renderings/' + fileName + '.png', {encoding: 'binary'});
      var referenceRendering = fs.readFileSync('test/fixtures/visual-regression/reference-renderings/' + fileName + '.png');

      renderStream.on('data', function(data) {
        file.write(data.toString('binary'), 'binary');
      });

      renderStream.on('end', function() {
        file.end();
      });

      resemble.outputSettings({
        largeImageThreshold: 0
      });

      file.on('finish', function() {

        resemble('test/fixtures/visual-regression/test-renderings/' + fileName + '.png')
          .compareTo('test/fixtures/visual-regression/reference-renderings/' + fileName + '.png')
          .onComplete(function(data){

            if(!data.isSameDimensions || data.misMatchPercentage > tolerance) {
              data.getDiffImage()
                .pack()
                .pipe(fs.createWriteStream('test/fixtures/visual-regression/diff-renderings/' + fileName + '.png'))
                .on('close', function() {

                  data.isSameDimensions.should.be.true;
                  data.misMatchPercentage.should.below(tolerance);

                  done();
                });
            } else {
              done();
            }

          });

      });

    });

    it(componentUrl + ' should not have regressed visually on mobile', function(done) {

      var fileName = url.parse(componentUrl).pathname;
      fileName = fileName.replace(new RegExp('/', 'g'), '-');
      fileName = trim(fileName, '-');
      fileName = sanitize(fileName);
      fileName = 'mobile-' + fileName;

      var renderStream = webshot(componentUrl, mobileOptions);
      var file = fs.createWriteStream('test/fixtures/visual-regression/test-renderings/' + fileName + '.png', {encoding: 'binary'});
      var referenceRendering = fs.readFileSync('test/fixtures/visual-regression/reference-renderings/' + fileName + '.png');

      renderStream.on('data', function(data) {
        file.write(data.toString('binary'), 'binary');
      });

      renderStream.on('end', function() {
        file.end();
      });

      resemble.outputSettings({
        largeImageThreshold: 0
      });

      file.on('finish', function() {

        resemble('test/fixtures/visual-regression/test-renderings/' + fileName + '.png')
          .compareTo('test/fixtures/visual-regression/reference-renderings/' + fileName + '.png')
          .onComplete(function(data){

            if(!data.isSameDimensions || data.misMatchPercentage > tolerance) {
              data.getDiffImage()
                .pack()
                .pipe(fs.createWriteStream('test/fixtures/visual-regression/diff-renderings/' + fileName + '.png'))
                .on('close', function() {

                  data.isSameDimensions.should.be.true;
                  data.misMatchPercentage.should.below(tolerance);

                  done();
                });
            } else {
              done();
            }

          });

      });

    });

  });
});
<commit_msg>Increase visual regression testing tolerance to allow for slight deviations in map rendering<commit_after>var should = require('should');
var request = require('superagent');
var fs = require('fs');
var webshot = require('webshot');
var sanitize = require('sanitize-filename');
var url = require('url');
var mkdirp = require('mkdirp');
var rimraf = require('rimraf');
var path = require('path');
var resemble = require('node-resemble-js');
var trim = require('trim-character');
var extend = require('extend');

var options = {
  takeShotOnCallback: true,
  timeout: 30000,
  errorIfStatusIsNot200: true,
  shotSize: {
    width: 'all',
    height: 'all'
  }
};

var mobileOptions = extend({}, options);

mobileOptions.screenSize = {
  width: 320,
  height: 480
};

mobileOptions.shotSize = {
  width: 320,
  height: 'all'
};

var tolerance = 0.05;

rimraf.sync('test/fixtures/visual-regression/test-renderings');
rimraf.sync('test/fixtures/visual-regression/diff-renderings');

mkdirp.sync('test/fixtures/visual-regression/test-renderings');
mkdirp.sync('test/fixtures/visual-regression/diff-renderings');

/**
 * Visual regression checks
 */
describe('The pattern library page at', function() {
  var urls = JSON.parse(fs.readFileSync('.tmp/testURLs.json', 'utf8'));

  this.timeout(30000);

  urls.forEach(function(componentUrl) {

    it(componentUrl + ' should not have regressed visually on desktop', function(done) {

      var fileName = url.parse(componentUrl).pathname;
      fileName = fileName.replace(new RegExp('/', 'g'), '-');
      fileName = trim(fileName, '-');
      fileName = sanitize(fileName);
      fileName = 'desktop-' + fileName;

      var renderStream = webshot(componentUrl, options);
      var file = fs.createWriteStream('test/fixtures/visual-regression/test-renderings/' + fileName + '.png', {encoding: 'binary'});
      var referenceRendering = fs.readFileSync('test/fixtures/visual-regression/reference-renderings/' + fileName + '.png');

      renderStream.on('data', function(data) {
        file.write(data.toString('binary'), 'binary');
      });

      renderStream.on('end', function() {
        file.end();
      });

      resemble.outputSettings({
        largeImageThreshold: 0
      });

      file.on('finish', function() {

        resemble('test/fixtures/visual-regression/test-renderings/' + fileName + '.png')
          .compareTo('test/fixtures/visual-regression/reference-renderings/' + fileName + '.png')
          .onComplete(function(data){

            if(!data.isSameDimensions || data.misMatchPercentage > tolerance) {
              data.getDiffImage()
                .pack()
                .pipe(fs.createWriteStream('test/fixtures/visual-regression/diff-renderings/' + fileName + '.png'))
                .on('close', function() {

                  data.isSameDimensions.should.be.true;
                  data.misMatchPercentage.should.below(tolerance);

                  done();
                });
            } else {
              done();
            }

          });

      });

    });

    it(componentUrl + ' should not have regressed visually on mobile', function(done) {

      var fileName = url.parse(componentUrl).pathname;
      fileName = fileName.replace(new RegExp('/', 'g'), '-');
      fileName = trim(fileName, '-');
      fileName = sanitize(fileName);
      fileName = 'mobile-' + fileName;

      var renderStream = webshot(componentUrl, mobileOptions);
      var file = fs.createWriteStream('test/fixtures/visual-regression/test-renderings/' + fileName + '.png', {encoding: 'binary'});
      var referenceRendering = fs.readFileSync('test/fixtures/visual-regression/reference-renderings/' + fileName + '.png');

      renderStream.on('data', function(data) {
        file.write(data.toString('binary'), 'binary');
      });

      renderStream.on('end', function() {
        file.end();
      });

      resemble.outputSettings({
        largeImageThreshold: 0
      });

      file.on('finish', function() {

        resemble('test/fixtures/visual-regression/test-renderings/' + fileName + '.png')
          .compareTo('test/fixtures/visual-regression/reference-renderings/' + fileName + '.png')
          .onComplete(function(data){

            if(!data.isSameDimensions || data.misMatchPercentage > tolerance) {
              data.getDiffImage()
                .pack()
                .pipe(fs.createWriteStream('test/fixtures/visual-regression/diff-renderings/' + fileName + '.png'))
                .on('close', function() {

                  data.isSameDimensions.should.be.true;
                  data.misMatchPercentage.should.below(tolerance);

                  done();
                });
            } else {
              done();
            }

          });

      });

    });

  });
});
<|endoftext|>


<commit_before>/*!
 * VisualEditor UserInterface MWGraphDialog class.
 *
 * @license The MIT License (MIT); see LICENSE.txt
 */

/**
 * MediaWiki graph dialog.
 *
 * @class
 * @extends ve.ui.NodeDialog
 *
 * @constructor
 * @param {Object} [element]
 */
ve.ui.MWGraphDialog = function VeUiMWGraphDialog() {
	// Parent constructor
	ve.ui.MWGraphDialog.super.apply( this, arguments );

	// Properties
	this.graphModel = null;
	this.cachedRawData = null;
	this.changingJsonTextInput = false;
};

/* Inheritance */

OO.inheritClass( ve.ui.MWGraphDialog, ve.ui.NodeDialog );

/* Static properties */

ve.ui.MWGraphDialog.static.name = 'graph';

ve.ui.MWGraphDialog.static.title = OO.ui.deferMsg( 'graph-ve-dialog-edit-title' );

ve.ui.MWGraphDialog.static.icon = 'code';

ve.ui.MWGraphDialog.static.size = 'large';

ve.ui.MWGraphDialog.static.actions = [
	{
		action: 'apply',
		label: OO.ui.deferMsg( 'graph-ve-dialog-edit-apply' ),
		flags: [ 'progressive', 'primary' ],
		modes: 'edit'
	},
	{
		label: OO.ui.deferMsg( 'graph-ve-dialog-edit-cancel' ),
		flags: 'safe',
		modes: [ 'edit', 'insert' ]
	}
];

ve.ui.MWGraphDialog.static.modelClasses = [ ve.dm.MWGraphNode ];

/* Methods */

/**
 * @inheritdoc
 */
ve.ui.MWGraphDialog.prototype.getBodyHeight = function () {
	// FIXME: This should depend on the dialog's content.
	return 500;
};

/**
 * @inheritdoc
 */
ve.ui.MWGraphDialog.prototype.initialize = function () {
	var graphTypeField, jsonTextField;

	// Parent method
	ve.ui.MWGraphDialog.super.prototype.initialize.call( this );

	/* Root layout */
	this.rootLayout = new OO.ui.BookletLayout( {
		classes: [ 've-ui-mwGraphDialog-panel-root' ],
		outlined: true
	} );

	this.generalPage = new OO.ui.PageLayout( 'general' );
	this.rawPage = new OO.ui.PageLayout( 'raw' );

	this.rootLayout.addPages( [
		this.generalPage, this.rawPage
	] );

	/* Graph type page */
	this.generalPage.getOutlineItem()
		.setIcon( 'parameter' )
		.setLabel( ve.msg( 'graph-ve-dialog-edit-page-general' ) );

	this.graphTypeDropdownInput = new OO.ui.DropdownInputWidget();

	graphTypeField = new OO.ui.FieldLayout( this.graphTypeDropdownInput, {
		label: ve.msg( 'graph-ve-dialog-edit-field-graph-type' )
	} );

	this.unknownGraphTypeWarningLabel = new OO.ui.LabelWidget( {
		label: ve.msg( 'graph-ve-dialog-edit-unknown-graph-type-warning' )
	} );

	this.generalPage.$element.append(
		graphTypeField.$element,
		this.unknownGraphTypeWarningLabel.$element
	);

	/* Raw JSON page */
	this.rawPage.getOutlineItem()
		.setIcon( 'code' )
		.setLabel( ve.msg( 'graph-ve-dialog-edit-page-raw' ) );

	this.jsonTextInput = new ve.ui.WhitespacePreservingTextInputWidget( {
		autosize: true,
		classes: [ 've-ui-mwGraphDialog-json' ],
		maxRows: 25,
		multiline: true,
		validate: this.validateRawData
	} );

	jsonTextField = new OO.ui.FieldLayout( this.jsonTextInput, {
		label: ve.msg( 'graph-ve-dialog-edit-field-raw-json' ),
		align: 'top'
	} );

	this.rawPage.$element.append( jsonTextField.$element );

	// Event handlers
	this.jsonTextInput.connect( this, {
		change: 'onSpecStringInputChange'
	} );

	this.graphTypeDropdownInput.connect( this, {
		change: 'onGraphTypeInputChange'
	} );

	// Initialization
	this.$body.append( this.rootLayout.$element );
};

/**
 * @inheritdoc
 */
ve.ui.MWGraphDialog.prototype.getSetupProcess = function ( data ) {
	var spec;

	return ve.ui.MWGraphDialog.super.prototype.getSetupProcess.call( this, data )
		.next( function () {
			// Set up model
			spec = this.selectedNode.getSpec();

			this.graphModel = new ve.dm.MWGraphModel( spec );
			this.graphModel.connect( this, {
				specChange: 'onSpecChange'
			} );

			// Set up default values
			this.setupFormValues();

			// If parsing fails here, cached raw data can simply remain null
			try {
				this.cachedRawData = JSON.parse( this.jsonTextInput.getValue() );
			} catch ( err ) {}

			this.checkChanges();
		}, this );
};

/**
 * @inheritdoc
 */
ve.ui.MWGraphDialog.prototype.getTeardownProcess = function () {
	return ve.ui.MWGraphDialog.super.prototype.getTeardownProcess.call( this )
		.first( function () {
			// Kill model
			this.graphModel.disconnect( this );
			this.graphModel = null;
		}, this );
};

/**
 * @inheritdoc
 */
ve.ui.MWGraphDialog.prototype.getActionProcess = function ( action ) {
	switch ( action ) {
		case 'apply':
			return new OO.ui.Process( function () {
				this.graphModel.applyChanges( this.selectedNode, this.getFragment().getSurface() );
				this.close( { action: action } );
			}, this );

		default:
			return ve.ui.MWGraphDialog.super.prototype.getActionProcess.call( this, action );
	}
};

/**
 * Setup initial values in the dialog
 *
 * @private
 */
ve.ui.MWGraphDialog.prototype.setupFormValues = function () {
	var graphType = this.graphModel.getGraphType(),
		options = [
			{
				data: 'bar',
				label: ve.msg( 'graph-ve-dialog-edit-type-bar' )
			},
			{
				data: 'area',
				label: ve.msg( 'graph-ve-dialog-edit-type-area' )
			},
			{
				data: 'line',
				label: ve.msg( 'graph-ve-dialog-edit-type-line' )
			}
		],
		unknownGraphTypeOption = {
			data: 'unknown',
			label: ve.msg( 'graph-ve-dialog-edit-type-unknown' )
		};

	// JSON text input
	this.jsonTextInput.setValue( this.graphModel.getSpecString() );

	// Graph type
	if ( graphType === 'unknown' ) {
		options.push( unknownGraphTypeOption );
	}

	this.graphTypeDropdownInput
		.setOptions( options )
		.setValue( graphType );
};

/**
 * Validate raw data input
 *
 * @private
 * @param {string} value The new input value
 */
ve.ui.MWGraphDialog.prototype.validateRawData = function ( value ) {
	var isValid = !$.isEmptyObject( ve.dm.MWGraphNode.static.parseSpecString( value ) ),
		label = ( isValid ) ? '' : ve.msg( 'graph-ve-dialog-edit-json-invalid' );

	this.setLabel( label );

	return isValid;
};

/**
 * React to spec string input change
 *
 * @private
 * @param {string} value The text input value
 */
ve.ui.MWGraphDialog.prototype.onSpecStringInputChange = function ( value ) {
	var newRawData;

	try {
		// If parsing fails here, nothing more needs to happen
		newRawData = JSON.parse( value );

		// Only pass changes to model if there was anything worthwhile to change
		if ( !OO.compare( this.cachedRawData, newRawData ) ) {
			this.cachedRawData = newRawData;
			this.graphModel.setSpecFromString( value );
		}
	} catch ( err ) {}
};

/**
 * React to graph type changes
 *
 * @param {string} [value] The new graph type
 */
ve.ui.MWGraphDialog.prototype.onGraphTypeInputChange = function ( value ) {
	this.unknownGraphTypeWarningLabel.toggle( value === 'unknown' );

	if ( value !== 'unknown' ) {
		this.graphModel.switchGraphType( value );
	}
};

/**
 * React to spec change
 *
 * @private
 */
ve.ui.MWGraphDialog.prototype.onSpecChange = function () {
	this.jsonTextInput.setValue( this.graphModel.getSpecString() );

	this.checkChanges();
};

/**
 * Check for overall validity and enables/disables action abilities accordingly
 *
 * @private
 */
ve.ui.MWGraphDialog.prototype.checkChanges = function () {
	var self = this,
		hasModelBeenChanged = this.graphModel.hasBeenChanged(),
		areChangesValid;

	this.jsonTextInput.isValid().done( function ( isJsonTextInputValid ) {
		areChangesValid = ( hasModelBeenChanged && isJsonTextInputValid );
		self.actions.setAbilities( { apply: areChangesValid } );
	} );
};

/* Registration */

ve.ui.windowFactory.register( ve.ui.MWGraphDialog );
<commit_msg>VisualEditor: Always use LTR for JSON input<commit_after>/*!
 * VisualEditor UserInterface MWGraphDialog class.
 *
 * @license The MIT License (MIT); see LICENSE.txt
 */

/**
 * MediaWiki graph dialog.
 *
 * @class
 * @extends ve.ui.NodeDialog
 *
 * @constructor
 * @param {Object} [element]
 */
ve.ui.MWGraphDialog = function VeUiMWGraphDialog() {
	// Parent constructor
	ve.ui.MWGraphDialog.super.apply( this, arguments );

	// Properties
	this.graphModel = null;
	this.cachedRawData = null;
	this.changingJsonTextInput = false;
};

/* Inheritance */

OO.inheritClass( ve.ui.MWGraphDialog, ve.ui.NodeDialog );

/* Static properties */

ve.ui.MWGraphDialog.static.name = 'graph';

ve.ui.MWGraphDialog.static.title = OO.ui.deferMsg( 'graph-ve-dialog-edit-title' );

ve.ui.MWGraphDialog.static.icon = 'code';

ve.ui.MWGraphDialog.static.size = 'large';

ve.ui.MWGraphDialog.static.actions = [
	{
		action: 'apply',
		label: OO.ui.deferMsg( 'graph-ve-dialog-edit-apply' ),
		flags: [ 'progressive', 'primary' ],
		modes: 'edit'
	},
	{
		label: OO.ui.deferMsg( 'graph-ve-dialog-edit-cancel' ),
		flags: 'safe',
		modes: [ 'edit', 'insert' ]
	}
];

ve.ui.MWGraphDialog.static.modelClasses = [ ve.dm.MWGraphNode ];

/* Methods */

/**
 * @inheritdoc
 */
ve.ui.MWGraphDialog.prototype.getBodyHeight = function () {
	// FIXME: This should depend on the dialog's content.
	return 500;
};

/**
 * @inheritdoc
 */
ve.ui.MWGraphDialog.prototype.initialize = function () {
	var graphTypeField, jsonTextField;

	// Parent method
	ve.ui.MWGraphDialog.super.prototype.initialize.call( this );

	/* Root layout */
	this.rootLayout = new OO.ui.BookletLayout( {
		classes: [ 've-ui-mwGraphDialog-panel-root' ],
		outlined: true
	} );

	this.generalPage = new OO.ui.PageLayout( 'general' );
	this.rawPage = new OO.ui.PageLayout( 'raw' );

	this.rootLayout.addPages( [
		this.generalPage, this.rawPage
	] );

	/* Graph type page */
	this.generalPage.getOutlineItem()
		.setIcon( 'parameter' )
		.setLabel( ve.msg( 'graph-ve-dialog-edit-page-general' ) );

	this.graphTypeDropdownInput = new OO.ui.DropdownInputWidget();

	graphTypeField = new OO.ui.FieldLayout( this.graphTypeDropdownInput, {
		label: ve.msg( 'graph-ve-dialog-edit-field-graph-type' )
	} );

	this.unknownGraphTypeWarningLabel = new OO.ui.LabelWidget( {
		label: ve.msg( 'graph-ve-dialog-edit-unknown-graph-type-warning' )
	} );

	this.generalPage.$element.append(
		graphTypeField.$element,
		this.unknownGraphTypeWarningLabel.$element
	);

	/* Raw JSON page */
	this.rawPage.getOutlineItem()
		.setIcon( 'code' )
		.setLabel( ve.msg( 'graph-ve-dialog-edit-page-raw' ) );

	this.jsonTextInput = new ve.ui.WhitespacePreservingTextInputWidget( {
		autosize: true,
		classes: [ 've-ui-mwGraphDialog-json' ],
		maxRows: 25,
		multiline: true,
		validate: this.validateRawData
	} );

	// Make sure JSON is LTR
	this.jsonTextInput.setRTL( false );

	jsonTextField = new OO.ui.FieldLayout( this.jsonTextInput, {
		label: ve.msg( 'graph-ve-dialog-edit-field-raw-json' ),
		align: 'top'
	} );

	this.rawPage.$element.append( jsonTextField.$element );

	// Event handlers
	this.jsonTextInput.connect( this, {
		change: 'onSpecStringInputChange'
	} );

	this.graphTypeDropdownInput.connect( this, {
		change: 'onGraphTypeInputChange'
	} );

	// Initialization
	this.$body.append( this.rootLayout.$element );
};

/**
 * @inheritdoc
 */
ve.ui.MWGraphDialog.prototype.getSetupProcess = function ( data ) {
	var spec;

	return ve.ui.MWGraphDialog.super.prototype.getSetupProcess.call( this, data )
		.next( function () {
			// Set up model
			spec = this.selectedNode.getSpec();

			this.graphModel = new ve.dm.MWGraphModel( spec );
			this.graphModel.connect( this, {
				specChange: 'onSpecChange'
			} );

			// Set up default values
			this.setupFormValues();

			// If parsing fails here, cached raw data can simply remain null
			try {
				this.cachedRawData = JSON.parse( this.jsonTextInput.getValue() );
			} catch ( err ) {}

			this.checkChanges();
		}, this );
};

/**
 * @inheritdoc
 */
ve.ui.MWGraphDialog.prototype.getTeardownProcess = function () {
	return ve.ui.MWGraphDialog.super.prototype.getTeardownProcess.call( this )
		.first( function () {
			// Kill model
			this.graphModel.disconnect( this );
			this.graphModel = null;
		}, this );
};

/**
 * @inheritdoc
 */
ve.ui.MWGraphDialog.prototype.getActionProcess = function ( action ) {
	switch ( action ) {
		case 'apply':
			return new OO.ui.Process( function () {
				this.graphModel.applyChanges( this.selectedNode, this.getFragment().getSurface() );
				this.close( { action: action } );
			}, this );

		default:
			return ve.ui.MWGraphDialog.super.prototype.getActionProcess.call( this, action );
	}
};

/**
 * Setup initial values in the dialog
 *
 * @private
 */
ve.ui.MWGraphDialog.prototype.setupFormValues = function () {
	var graphType = this.graphModel.getGraphType(),
		options = [
			{
				data: 'bar',
				label: ve.msg( 'graph-ve-dialog-edit-type-bar' )
			},
			{
				data: 'area',
				label: ve.msg( 'graph-ve-dialog-edit-type-area' )
			},
			{
				data: 'line',
				label: ve.msg( 'graph-ve-dialog-edit-type-line' )
			}
		],
		unknownGraphTypeOption = {
			data: 'unknown',
			label: ve.msg( 'graph-ve-dialog-edit-type-unknown' )
		};

	// JSON text input
	this.jsonTextInput.setValue( this.graphModel.getSpecString() );

	// Graph type
	if ( graphType === 'unknown' ) {
		options.push( unknownGraphTypeOption );
	}

	this.graphTypeDropdownInput
		.setOptions( options )
		.setValue( graphType );
};

/**
 * Validate raw data input
 *
 * @private
 * @param {string} value The new input value
 */
ve.ui.MWGraphDialog.prototype.validateRawData = function ( value ) {
	var isValid = !$.isEmptyObject( ve.dm.MWGraphNode.static.parseSpecString( value ) ),
		label = ( isValid ) ? '' : ve.msg( 'graph-ve-dialog-edit-json-invalid' );

	this.setLabel( label );

	return isValid;
};

/**
 * React to spec string input change
 *
 * @private
 * @param {string} value The text input value
 */
ve.ui.MWGraphDialog.prototype.onSpecStringInputChange = function ( value ) {
	var newRawData;

	try {
		// If parsing fails here, nothing more needs to happen
		newRawData = JSON.parse( value );

		// Only pass changes to model if there was anything worthwhile to change
		if ( !OO.compare( this.cachedRawData, newRawData ) ) {
			this.cachedRawData = newRawData;
			this.graphModel.setSpecFromString( value );
		}
	} catch ( err ) {}
};

/**
 * React to graph type changes
 *
 * @param {string} [value] The new graph type
 */
ve.ui.MWGraphDialog.prototype.onGraphTypeInputChange = function ( value ) {
	this.unknownGraphTypeWarningLabel.toggle( value === 'unknown' );

	if ( value !== 'unknown' ) {
		this.graphModel.switchGraphType( value );
	}
};

/**
 * React to spec change
 *
 * @private
 */
ve.ui.MWGraphDialog.prototype.onSpecChange = function () {
	this.jsonTextInput.setValue( this.graphModel.getSpecString() );

	this.checkChanges();
};

/**
 * Check for overall validity and enables/disables action abilities accordingly
 *
 * @private
 */
ve.ui.MWGraphDialog.prototype.checkChanges = function () {
	var self = this,
		hasModelBeenChanged = this.graphModel.hasBeenChanged(),
		areChangesValid;

	this.jsonTextInput.isValid().done( function ( isJsonTextInputValid ) {
		areChangesValid = ( hasModelBeenChanged && isJsonTextInputValid );
		self.actions.setAbilities( { apply: areChangesValid } );
	} );
};

/* Registration */

ve.ui.windowFactory.register( ve.ui.MWGraphDialog );
<|endoftext|>