<commit_before>var port = 8080;
var prefix = "/tmp/";

var http = require("http");
var url = require("url");
var spawn = require("child_process").spawn;
var exec = require("child_process").exec;
var path = require("path");
var fs = require("fs");

var statsCache = {};

function getTimeString(timeParam) {
	var time = parseInt(timeParam);
	if(isNaN(time)) {
		console.log("ignoring NaN timestamp...");
		time = 0;
	}

	var hours = (time / 3600) | 0;
	time -= hours * 3600;
	var minutes = (time / 60) | 0;
	time -= minutes * 60;
	var seconds = time;

	return hours + ":" + minutes + ":" + seconds;
}

function chopOgg(res, file, timestring) {
	var child = spawn("oggz-chop",["-s", timestring, file]);
	res.setHeader("Content-Type", "video/ogg");
	res.on("close", function() { child.kill(); console.log("response channel closed.")});
	child.stdout.on("data", function(data) { res.write(data); });
	child.stdout.on("end", function() { res.end(); });
	child.on("exit", function() { res.end(); console.log("oggz-chop exited."); });
}

function serveOggWithStats(res, file, stats) {
	res.setHeader("Content-Type", "video/ogg");
	res.setHeader("Content-Length", stats.size);
	res.setHeader("X-Content-Duration", stats.duration);
	var stream = fs.createReadStream(file);
	stream.pipe(res);
}

function serveOgg(res, file) {

	var stats = statsCache[file];
	if(stats) {
		serveOggWithStats(res, file, stats);
	} else {
		fs.stat(file, function(err, stats) {
			if(err) {
				reportError(res);
				return;
			}
			exec("oggz-info " + file, function(error, stdout, stderr) {
				if(error) {
					reportError(res);
					return;
				}
				var duration = stdout.split("\n")[0].split(": ")[1];
				var hours = parseInt(duration.split(":")[0]);
				var minutes = parseInt(duration.split(":")[1]);
				var seconds = parseFloat(duration.split(":")[2]);

				duration = (hours * 3600) + (minutes * 60) + seconds;
				stats.duration = duration;
				statsCache[file] = stats;
				serveOggWithStats(res, file, stats);
			});

		});
	}
}

function reportError(res) {
	res.writeHead(500, {'Content-Type': 'text/plain'});
	res.write("Internal Server Error");
	res.end();
}

var server = http.createServer(function(req, res) {
	var parsedurl = url.parse(req.url, true);
	var timeParam = parsedurl.query.t;

	var timestring = getTimeString(timeParam);
	console.log("parsed path: " + parsedurl.pathname);
	console.log("parsed time: " + timestring);

	var file = prefix + parsedurl.pathname;
	file = path.normalize(file);

	if(file.indexOf(prefix) == 0) {
		fs.exists(file, function(exists) {
			if(exists) {
				if(timeParam) {
					chopOgg(res, file, timestring);
				} else {
					console.log("no time given, directly streaming file");
					serveOgg(res, file);
				}
			} else {
				reportError(res);
			}
		});
	} else {
		reportError(res);
	}
});

prefix = path.normalize(prefix);
server.listen(port);
console.log("listening on port " + port);
console.log("try http://localhost:"+port+"/test.ogv?t=5 to jump 5 seconds into " + prefix + "test.ogv");
<commit_msg>initial, poorly tested, and incomplete support for range requests<commit_after>var port = 8080;
var prefix = "/tmp/";

var http = require("http");
var url = require("url");
var spawn = require("child_process").spawn;
var exec = require("child_process").exec;
var path = require("path");
var fs = require("fs");

var statsCache = {};

function getTimeString(timeParam) {
	var time = parseInt(timeParam);
	if(isNaN(time)) {
		console.log("ignoring NaN timestamp...");
		time = 0;
	}

	var hours = (time / 3600) | 0;
	time -= hours * 3600;
	var minutes = (time / 60) | 0;
	time -= minutes * 60;
	var seconds = time;

	return hours + ":" + minutes + ":" + seconds;
}

function chopOgg(res, file, timestring) {
	var child = spawn("oggz-chop",["-s", timestring, file]);
	res.setHeader("Content-Type", "video/ogg");
	res.on("close", function() { child.kill(); console.log("response channel closed.")});
	child.stdout.on("data", function(data) { res.write(data); });
	child.stdout.on("end", function() { res.end(); });
	child.on("exit", function() { res.end(); console.log("oggz-chop exited."); });
}

function serveOggWithStats(res, file, stats, range) {
	res.setHeader("Content-Type", "video/ogg");
	res.setHeader("Accept-Ranges", "bytes");

	var length = stats.size;
	var options = {};
	if(range) {
		options.start = range.start;
		options.end = range.end;
		length = range.end - range.start;
	} else {
		res.setHeader("X-Content-Duration", stats.duration);
	}
	res.setHeader("Content-Length", length);

	var stream = fs.createReadStream(file, options);
	stream.pipe(res);
}

function serveOgg(res, file, range) {

	var stats = statsCache[file];
	if(stats) {
		serveOggWithStats(res, file, stats, range);
	} else {
		fs.stat(file, function(err, stats) {
			if(err) {
				reportError(res);
				return;
			}
			exec("oggz-info " + file, function(error, stdout, stderr) {
				if(error) {
					reportError(res);
					return;
				}
				var duration = stdout.split("\n")[0].split(": ")[1];
				var hours = parseInt(duration.split(":")[0]);
				var minutes = parseInt(duration.split(":")[1]);
				var seconds = parseFloat(duration.split(":")[2]);

				duration = (hours * 3600) + (minutes * 60) + seconds;
				stats.duration = duration;
				statsCache[file] = stats;
				serveOggWithStats(res, file, stats, range);
			});

		});
	}
}

function reportError(res) {
	res.writeHead(500, {'Content-Type': 'text/plain'});
	res.write("Internal Server Error");
	res.end();
}

function parseRange(headers) {
	if(!headers.range) {
		return;
	}
	var rangestring = headers.range.split("=")[1];
	var start = parseInt(rangestring.split("-")[0]);
	var end = parseInt(rangestring.split("-")[1]);

	start = (isNaN(start) || start < 0) ? 0 : start;
	end = (end <= start) ? null : end;

	var result = {};
	result.start = start;
	result.end = end;
	return result;
}

var server = http.createServer(function(req, res) {
	var range = parseRange(req.headers);
	console.log("### range: " + range);
	var parsedurl = url.parse(req.url, true);
	var timeParam = parsedurl.query.t;

	var timestring = getTimeString(timeParam);
	console.log("parsed path: " + parsedurl.pathname);
	console.log("parsed time: " + timestring);

	var file = prefix + parsedurl.pathname;
	file = path.normalize(file);

	if(file.indexOf(prefix) == 0) {
		fs.exists(file, function(exists) {
			if(exists) {
				if(timeParam) {
					chopOgg(res, file, timestring);
				} else {
					console.log("no time given, directly streaming file");
					serveOgg(res, file, range);
				}
			} else {
				reportError(res);
			}
		});
	} else {
		reportError(res);
	}
});

prefix = path.normalize(prefix);
server.listen(port);
console.log("listening on port " + port);
console.log("try http://localhost:"+port+"/test.ogv?t=5 to jump 5 seconds into " + prefix + "test.ogv");
<|endoftext|>


<commit_before>import React from "react";
import Input from "@hig/Input";
import Label from "@hig/label";
import Spacer from "@hig/spacer";

import ThemeRepeater from "../components/ThemeRepeater";
import Text from "../components/Text";

const states = [
  { label: "interactive", props: {} },
  { label: "with placeholder", props: { placeholder: "example content" } },
  { label: "disabled", props: { disabled: true } }
];

const variants = ["line", "box"];

function InputPage() {
  return (
    <ThemeRepeater>
      {({ id }) => {
        return (

            {variants.map(variant => {
              return (

                  <Text>{variant}</Text>
                  {states.map(({ label, props }) => {
                    const key = id + label + variant;
                    return (

                            {label}

                          <Input id={key} {...props} variant={variant} />
                          <Spacer spacing="xs" />

                        <Spacer />

                    );
                  })}

              );
            })}

        );
      }}
    </ThemeRepeater>
  );
}

export default InputPage;
<commit_msg>fix: typo in acceptance app input import<commit_after>import React from "react";
import Input from "@hig/input";
import Label from "@hig/label";
import Spacer from "@hig/spacer";

import ThemeRepeater from "../components/ThemeRepeater";
import Text from "../components/Text";

const states = [
  { label: "interactive", props: {} },
  { label: "with placeholder", props: { placeholder: "example content" } },
  { label: "disabled", props: { disabled: true } }
];

const variants = ["line", "box"];

function InputPage() {
  return (
    <ThemeRepeater>
      {({ id }) => {
        return (

            {variants.map(variant => {
              return (

                  <Text>{variant}</Text>
                  {states.map(({ label, props }) => {
                    const key = id + label + variant;
                    return (

                            {label}

                          <Input id={key} {...props} variant={variant} />
                          <Spacer spacing="xs" />

                        <Spacer />

                    );
                  })}

              );
            })}

        );
      }}
    </ThemeRepeater>
  );
}

export default InputPage;
<|endoftext|>


<commit_before>/* globals Pikaday */

import Ember from 'ember';
import moment from 'moment';

export default Ember.Component.extend({
  tagName: 'input',
  attributeBindings: ['readonly', 'disabled', 'placeholder', 'type', 'name', 'size'],
  type: 'text',

  setupPikaday: Ember.on('didInsertElement', function() {
    var that = this;
    var firstDay = this.get('firstDay');

    var options = {
      field: this.$()[0],
      onOpen: Ember.run.bind(this, this.onPikadayOpen),
      onClose: Ember.run.bind(this, this.onPikadayClose),
      onSelect: Ember.run.bind(this, this.onPikadaySelect),
      onDraw: Ember.run.bind(this, this.onPikadayRedraw),
      firstDay: (typeof firstDay !== 'undefined') ? parseInt(firstDay, 10) : 1,
      format: this.get('format') || 'DD.MM.YYYY',
      yearRange: that.determineYearRange(),
      minDate: this.get('minDate') || null,
      maxDate: this.get('maxDate') || null,
      theme: this.get('theme') || null
    };

    if (this.get('i18n')) {
      options.i18n = this.get('i18n');
    }

    var pikaday = new Pikaday(options);

    this.set('pikaday', pikaday);
    this.setPikadayDate();

    this.addObserver('value', function() {
      that.setPikadayDate();
    });

    this.addObserver('minDate', function() {
      this.setMinDate();
    });

    this.addObserver('maxDate', function() {
      this.setMaxDate();
    });
  }),

  teardownPikaday: Ember.on('willDestroyElement', function() {
    this.get('pikaday').destroy();
  }),

  setPikadayDate: function() {
    this.get('pikaday').setDate(this.get('value'), true);
  },

  setMinDate: function() {
    this.get('pikaday').setMinDate(this.get('minDate'));
  },

  setMaxDate: function() {
    this.get('pikaday').setMaxDate(this.get('maxDate'));
  },

  onPikadayOpen: Ember.K,

  onPikadayClose: function() {
    if (this.get('pikaday').getDate() === null || Ember.isEmpty(this.$().val())) {
      this.set('value', null);
    }
  },

  onPikadaySelect: function() {
    this.userSelectedDate();
  },

  onPikadayRedraw: Ember.K,

  userSelectedDate: function() {
    var selectedDate = this.get('pikaday').getDate();

    if (this.get('useUTC')) {
      selectedDate = moment.utc([selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate()]).toDate();
    }

    this.set('value', selectedDate);
  },

  determineYearRange: function() {
    var yearRange = this.get('yearRange');

    if (yearRange) {
      if (yearRange.indexOf(',') > -1) {
        var yearArray = yearRange.split(',');

        if (yearArray[1] === 'currentYear') {
          yearArray[1] = new Date().getFullYear();
        }

        return yearArray;
      } else {
        return yearRange;
      }
    } else {
      return 10;
    }
  },

  autoHideOnDisabled: Ember.observer('disabled', 'pikaday', function () {
    if (this.get('disabled') && this.get('pikaday')) {
      this.get('pikaday').hide();
    }
  })
});
<commit_msg>Added the 'required' binding to allow use of html field validations.<commit_after>/* globals Pikaday */

import Ember from 'ember';
import moment from 'moment';

export default Ember.Component.extend({
  tagName: 'input',
  attributeBindings: ['readonly', 'disabled', 'placeholder', 'type', 'name', 'size', 'required'],
  type: 'text',

  setupPikaday: Ember.on('didInsertElement', function() {
    var that = this;
    var firstDay = this.get('firstDay');

    var options = {
      field: this.$()[0],
      onOpen: Ember.run.bind(this, this.onPikadayOpen),
      onClose: Ember.run.bind(this, this.onPikadayClose),
      onSelect: Ember.run.bind(this, this.onPikadaySelect),
      onDraw: Ember.run.bind(this, this.onPikadayRedraw),
      firstDay: (typeof firstDay !== 'undefined') ? parseInt(firstDay, 10) : 1,
      format: this.get('format') || 'DD.MM.YYYY',
      yearRange: that.determineYearRange(),
      minDate: this.get('minDate') || null,
      maxDate: this.get('maxDate') || null,
      theme: this.get('theme') || null
    };

    if (this.get('i18n')) {
      options.i18n = this.get('i18n');
    }

    var pikaday = new Pikaday(options);

    this.set('pikaday', pikaday);
    this.setPikadayDate();

    this.addObserver('value', function() {
      that.setPikadayDate();
    });

    this.addObserver('minDate', function() {
      this.setMinDate();
    });

    this.addObserver('maxDate', function() {
      this.setMaxDate();
    });
  }),

  teardownPikaday: Ember.on('willDestroyElement', function() {
    this.get('pikaday').destroy();
  }),

  setPikadayDate: function() {
    this.get('pikaday').setDate(this.get('value'), true);
  },

  setMinDate: function() {
    this.get('pikaday').setMinDate(this.get('minDate'));
  },

  setMaxDate: function() {
    this.get('pikaday').setMaxDate(this.get('maxDate'));
  },

  onPikadayOpen: Ember.K,

  onPikadayClose: function() {
    if (this.get('pikaday').getDate() === null || Ember.isEmpty(this.$().val())) {
      this.set('value', null);
    }
  },

  onPikadaySelect: function() {
    this.userSelectedDate();
  },

  onPikadayRedraw: Ember.K,

  userSelectedDate: function() {
    var selectedDate = this.get('pikaday').getDate();

    if (this.get('useUTC')) {
      selectedDate = moment.utc([selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate()]).toDate();
    }

    this.set('value', selectedDate);
  },

  determineYearRange: function() {
    var yearRange = this.get('yearRange');

    if (yearRange) {
      if (yearRange.indexOf(',') > -1) {
        var yearArray = yearRange.split(',');

        if (yearArray[1] === 'currentYear') {
          yearArray[1] = new Date().getFullYear();
        }

        return yearArray;
      } else {
        return yearRange;
      }
    } else {
      return 10;
    }
  },

  autoHideOnDisabled: Ember.observer('disabled', 'pikaday', function () {
    if (this.get('disabled') && this.get('pikaday')) {
      this.get('pikaday').hide();
    }
  })
});
<|endoftext|>


<commit_before>/**
@class
@name chunkSlideshow
@extends $.ui.chunk
@memberOf $.ui
*/
$.widget('ui.chunkSlideshow', $.ui.chunk,
	/**
	@lends $.ui.chunkSlideshow
	*/
	{

	elements : {},

	/**
	@function
	*/
	_create : function() {
		this.originals = this.element
			.find( 'ul.slides li' )
			.clone( true );

		$.ui.chunk.prototype._create.call( this );

		this.slider = top.$(this.element).hasClass('flexslider')? top.$(this.element).data('flexslider') : top.$(this.element).find('.flexslider').data('flexslider');
	},

	_bind_toolbar : function() {
		var self = this;

		top.$( 'div.toolbar' )
			.on( 'click', 'button.insert', function( event ) {
				var slideshow = self.slider;
				var slide = slideshow.slides[ slideshow.currentSlide ];

				self
					._insert_slide( $( slide ) )
					.done( function(){
						self.edited = true;
					});
			})
			.on( 'click', 'button.delete', function( event ) {
				var slideshow = self.slider;

				$.boom.dialog.confirm(
					'Delete slide',
					'Delete this slide?'
				)
				.done( function(){
					self._remove_slide(slideshow.currentSlide);
				});
			})
			.on( 'click', 'button.cancel', function(){
				self._cancel();
				self._remove_ui();
			})
			.on( 'click', 'button.save', function(){
				self._remove_ui();

				if (self.edited) {
					self.insert();
				} else {
					self.destroy();
				}
			})
			.on( 'click', 'button.sort', function(){
				self._sort()
					.done( function(){
						self.edited = true;
						$.boom.log( 'sort finished' );
					});
			})
			.on( 'click', 'button.link', function(){
				var slideshow = self.slider;
				var slide = slideshow.slides[ slideshow.currentSlide ];

				self._edit_link($(slide))
					.done( function(){
						self.edited = true;
						$.boom.growl.show('Link updated');
					});
			})
			.on('click', 'button.prev', function(event) {
				var slider = self.slider;

				slider.flexAnimate(self.slider.getTarget('prev'));
			})
			.on('click', 'button.next', function(event) {
				var slider = self.slider;

				slider.flexAnimate(self.slider.getTarget('next'));
			});
	},

	/**
	@function
	*/
	_build_ui : function() {

		this._bring_forward();

		return $.get( '/media/boom/toolbars/slideshow.php' )
			.done( function( toolbar ){
				$.boom.page.toolbar.hide();
				top.$( 'body' )
					.prepend( toolbar );

				if ( ! $('a.slide-link').length) {
					$('button.link').hide();
				}
			});

	},

	/**
	@function
	*/
	_remove_ui : function() {

		this._send_back();

		$.boom.page.toolbar.show();

		this.slider.play();

		this.element
			.find( '.slide-caption' )
			.each( function(){
				$( this ).removeAttr( 'contentEditable style' );
			})
			.off( 'focus mouseover' )
			.end();
		top.$( 'body' )
			.find( 'div.toolbar' )
			.remove();
	},

	/**
	Open a slideshow dialog
	*/
	edit: function(){
		var self = this;

		$.boom.log('Slideshow chunk slot edit');

		this.slider.pause();

		this._build_ui()
			.done(function() {
				self._bind_toolbar();
			});

		this.element
			.on( 'click', function( event ) {
				event.stopPropagation();
				event.preventDefault();
			})
			.on( 'click', 'a', function( event ) {
				event.preventDefault();
				return false;
			})
			.on( 'click', '.slide-asset', function( event ) {
				self
					._edit_slide( $( this ) )
					.always( function(){
						self.edited = true;
					});
			})
			.find( '.slide-caption' )
			.each( function(){
				self
					._edit_caption( $( this ) )
					.done( function(){
						self.edited = true;
					});
			});
	},

	_edit_link : function($slide){
		var self = this;

		$.boom.log( 'edit link' );

		var link = $slide.find( 'a.slide-link' );

		return $.boom.links.picker( {
			title: 'Add a link',
			new_link : {
				url : link.attr( 'href' ),
				rid : link.attr( 'rel' ),
			}

		})
		.pipe(function(new_link) {
			link.attr( 'href', new_link.url );
		});
	},

	/**
	Edit a slide image and link
	@function
	@param {Object} $slide Slide to edit
	@returns {Deferred}
	*/
	_edit_slide : function( $slide ){
		var self = this;

		$.boom.log( 'edit slide' );
		var url_segments = $slide.attr( 'src' ).split( '/' );
		var rid = url_segments[3];
		var asset_selected = new $.Deferred();

		// cleanup code when the dialog closes.
		asset_selected
		.fail( function() {
			$.boom.log( 'slide asset cancelled' );
		})
		.always( function() {
			$.boom.log( 'slide asset cleanup' );
		});

		return $.boom.assets.picker( {
			asset_rid : rid,
			deferred: asset_selected
		} )
		.pipe( function( rid ){
			if ($slide.attr( 'src' ).substring(0, 6) == '/asset') {
				url_segments[3] = rid;
				$slide.attr( 'src', url_segments.join( '/' ) );
			} else {
				$slide.attr( 'src', '/asset/view/' + rid);
			}
		})
		.fail( function() {
			self._remove_slide( $slide );
		})
		.always( function(){
			$.boom.history.load( '' );

			$.boom.page.toolbar.hide();
		});
	},

	/**
	Insert a slide after an existing slide
	@function
	@param {Object} $li Insert the new slide after this item
	*/
	_insert_slide : function( $li ) {
		var self = this;
		var asset_selected = new $.Deferred();
		var $new_slide = $li
			.clone()
			.insertAfter( $li );

		$new_slide.find('.slide-asset').attr('src', '');
		$new_slide.find('.slide-caption').html('');

		return self._edit_slide( $new_slide.find( '.slide-asset' ) )
			.done( function() {

				var slides = $.makeArray( self.element.find( 'ul.slides li').not( '.clone' ) );
				var target = $.inArray( $new_slide[0], slides );

				self.slider.count++;
				self.slider.update();
				self.slider.flexslider( 'next' );
			});
	},

	/**
	@function
	@param Int slide Slide to remove
	*/
	_remove_slide : function( slide ) {
		var slideshow = this.slider;
		var slides = slideshow.slides;

		if (slide == (slides.length - 1)) {
			slideshow.flexAnimate(0);
		} else if (slides.length > 1) {
			slideshow.flexAnimate(slide + 1);
		}

		$(slideshow.slides[slide])
			.closest( 'li' )
			.remove();

		slideshow.update();

		this.edited = true;
	},

	/**
	Edit a caption
	@param {Object} $caption Caption node
	@returns {Deferred}
	*/
	_edit_caption : function( $caption ) {

		var edited = new $.Deferred();

		$caption
			.attr( 'contentEditable', 'true' )
			.on( 'focus mouseover', function(){
				$( this )
					.css( 'border', '1px solid black' );
			})
			.on( 'blur mouseout', function(){
				$( this )
					.removeAttr( 'style' );
			})
			.on( 'blur', function(){
				edited.resolve();
			})
			.on( 'keyup', function( e ){
				e.stopPropagation();
			} );

			if ( $.trim( $caption.text() ) == '' ) {
				$caption.text( 'Default text' );
			}

		return edited;
	},

	/**
	Open a drag-and-drop interface for sorting the slides
	@function
	@returns {Deferred}
	*/
	_sort : function() {

		var self = this;
		var slides = this.getData().slides;
		var edited = new $.Deferred();

		$.boom.log( 'sorting slides' );

		var dialog = $.boom.dialog.open({
			title : 'Sort slides',
			msg: 'hello',
			width: 1000,
			height: 400,
			deferred : edited,
			open : function() {
				var $list = $( '

');
				var new_slides = [];

				for ( i in slides ) {
					var slide = slides[ i ];
					var $li = $( '

' )
						.data( 'slide', slide );
					$list
						.append( $li );
				}

				$( this )
				.html(
					$list
						.sortable( {
							helper : 'clone'
						})
				);

				edited.done( function(){
					$list
						.find( 'li' )
						.each( function(){
							var slide = $( this ).data( 'slide' );
							new_slides.push( slide );
						});

					self._refresh( new_slides );
				});
			}
		});
		return edited;
	},

	/**
	Apply changes to the HTML
	*/
	insert : function(){
		var data = this.getData();

		if (data.slides.length == 0) {
			return this.remove();
		} else {
			return this._save(data);
		}
	},

	/**
	refresh slides after a sort
	@params {Array} slides New slide set (same length as old set)
	@function
	*/
	_refresh : function( slides ) {

		var self = this;

		self.element
			.find( 'ul.slides li')
			.not( '.clone' )
			.each( function( i, item ){
				var slide = slides[ i ];
				var $item = $( item );
				var src = $item.find( '.slide-asset' ).attr( 'src' ).split( '/' );

				src[3] = slide.asset_id;

				$item.find( '.slide-asset' ).attr( 'src', src.join( '/' ) );
				$item.find( '.slide-link' ).attr( 'href', slide.url );
				$item.find( '.slide-caption' ).text( slide.caption );
			});

		$.boom.log( 'slideshow refresh' );
	},

	/**
	Cancel changes and exit
	@function
	*/
	_cancel : function() {

		var self = this;

		if (self.edited ) {

			$.boom.dialog.confirm(
				'Cancel changes',
				'Cancel changes to this slideshow?'
			)
			.done( function(){

				self.element
					.find( 'ul.slides')
					.find( 'li' )
					.remove()
					.end()
					.append( self.originals );
				self.slider.count = self.originals.size();
				self.destroy();
			});

		} else {
			self.destroy();
		}
	},

	/**
	Remove editor UI and exit
	*/
	_destroy : function() {
		$.boom.log( 'exiting slideshow editor' );
		var self = this;

		self._remove_ui();

		$.ui.chunk.prototype._destroy.call( this );

	},

	/**
	Get slideshow slide data.
	@returns {Array}
	*/
	getData: function(){
		var slides = [];

		this.element
			.find( 'ul.slides li' )
			.not( '.clone' )
			.each( function(){
				var slide = {};
				var $this = $( this );

				slide.caption = $this.find( '.slide-caption' ).text();
				slide.caption = ( slide.caption == 'Default text' ) ? '' : slide.caption;
				slide.asset_id = $this.find( '.slide-asset' ).attr( 'src' ).split( '/' )[3];
				slide.url = $this.find( '.slide-link' ).attr( 'href' );

				slides.push( slide );

			});

		return { slides : slides };
	}

});<commit_msg>Fixed bug in slideshow editor<commit_after>/**
@class
@name chunkSlideshow
@extends $.ui.chunk
@memberOf $.ui
*/
$.widget('ui.chunkSlideshow', $.ui.chunk,
	/**
	@lends $.ui.chunkSlideshow
	*/
	{

	elements : {},

	/**
	@function
	*/
	_create : function() {
		this.originals = this.element
			.find( 'ul.slides li' )
			.clone( true );

		$.ui.chunk.prototype._create.call( this );

		this.slider = top.$(this.element).hasClass('flexslider')? top.$(this.element).data('flexslider') : top.$(this.element).find('.flexslider').data('flexslider');
	},

	_bind_toolbar : function() {
		var self = this;

		top.$( 'div.toolbar' )
			.on( 'click', 'button.insert', function( event ) {
				var slideshow = self.slider;
				var slide = slideshow.slides[ slideshow.currentSlide ];

				self
					._insert_slide( $( slide ) )
					.done( function(){
						self.edited = true;
					});
			})
			.on( 'click', 'button.delete', function( event ) {
				var slideshow = self.slider;

				$.boom.dialog.confirm(
					'Delete slide',
					'Delete this slide?'
				)
				.done( function(){
					self._remove_slide(slideshow.currentSlide);
				});
			})
			.on( 'click', 'button.cancel', function(){
				self._cancel();
				self._remove_ui();
			})
			.on( 'click', 'button.save', function(){
				self._remove_ui();

				if (self.edited) {
					self.insert();
				} else {
					self.destroy();
				}
			})
			.on( 'click', 'button.sort', function(){
				self._sort()
					.done( function(){
						self.edited = true;
						$.boom.log( 'sort finished' );
					});
			})
			.on( 'click', 'button.link', function(){
				var slideshow = self.slider;
				var slide = slideshow.slides[ slideshow.currentSlide ];

				self._edit_link($(slide))
					.done( function(){
						self.edited = true;
						$.boom.growl.show('Link updated');
					});
			})
			.on('click', 'button.prev', function(event) {
				var slider = self.slider;

				slider.flexAnimate(self.slider.getTarget('prev'));
			})
			.on('click', 'button.next', function(event) {
				var slider = self.slider;

				slider.flexAnimate(self.slider.getTarget('next'));
			});
	},

	/**
	@function
	*/
	_build_ui : function() {

		this._bring_forward();

		return $.get( '/media/boom/toolbars/slideshow.php' )
			.done( function( toolbar ){
				$.boom.page.toolbar.hide();
				top.$( 'body' )
					.prepend( toolbar );

				if ( ! $('a.slide-link').length) {
					$('button.link').hide();
				}
			});

	},

	/**
	@function
	*/
	_remove_ui : function() {

		this._send_back();

		$.boom.page.toolbar.show();

		this.slider.play();

		this.element
			.find( '.slide-caption' )
			.each( function(){
				$( this ).removeAttr( 'contentEditable style' );
			})
			.off( 'focus mouseover' )
			.end();
		top.$( 'body' )
			.find( 'div.toolbar' )
			.remove();
	},

	/**
	Open a slideshow dialog
	*/
	edit: function(){
		var self = this;

		$.boom.log('Slideshow chunk slot edit');

		this.slider.pause();

		this._build_ui()
			.done(function() {
				self._bind_toolbar();
			});

		this.element
			.on( 'click', function( event ) {
				event.stopPropagation();
				event.preventDefault();
			})
			.on( 'click', 'a', function( event ) {
				event.preventDefault();
				return false;
			})
			.on( 'click', '.slide-asset', function( event ) {
				self
					._edit_slide( $( this ) )
					.always( function(){
						self.edited = true;
					});
			})
			.find( '.slide-caption' )
			.each( function(){
				self
					._edit_caption( $( this ) )
					.done( function(){
						self.edited = true;
					});
			});
	},

	_edit_link : function($slide){
		var self = this;

		$.boom.log( 'edit link' );

		var link = $slide.find( 'a.slide-link' );

		return $.boom.links.picker( {
			title: 'Add a link',
			new_link : {
				url : link.attr( 'href' ),
				rid : link.attr( 'rel' ),
			}

		})
		.pipe(function(new_link) {
			link.attr( 'href', new_link.url );
		});
	},

	/**
	Edit a slide image and link
	@function
	@param {Object} $slide Slide to edit
	@returns {Deferred}
	*/
	_edit_slide : function( $slide ){
		var self = this;

		$.boom.log( 'edit slide' );
		var url_segments = $slide.attr( 'src' ).split( '/' );
		var rid = url_segments[3];
		var asset_selected = new $.Deferred();

		// cleanup code when the dialog closes.
		asset_selected
		.fail( function() {
			$.boom.log( 'slide asset cancelled' );
		})
		.always( function() {
			$.boom.log( 'slide asset cleanup' );
		});

		return $.boom.assets.picker( {
			asset_rid : rid,
			deferred: asset_selected
		} )
		.pipe( function( rid ){
			if ($slide.attr( 'src' ).substring(0, 6) == '/asset') {
				url_segments[3] = rid;
				$slide.attr( 'src', url_segments.join( '/' ) );
			} else {
				$slide.attr( 'src', '/asset/view/' + rid);
			}
		})
		.fail( function() {
			self._remove_slide( $slide );
		})
		.always( function(){
			$.boom.history.load( '' );

			$.boom.page.toolbar.minimise();
		});
	},

	/**
	Insert a slide after an existing slide
	@function
	@param {Object} $li Insert the new slide after this item
	*/
	_insert_slide : function( $li ) {
		var self = this;
		var asset_selected = new $.Deferred();
		var $new_slide = $li
			.clone()
			.insertAfter( $li );

		$new_slide.find('.slide-asset').attr('src', '');
		$new_slide.find('.slide-caption').html('');

		return self._edit_slide( $new_slide.find( '.slide-asset' ) )
			.done( function() {

				var slides = $.makeArray( self.element.find( 'ul.slides li').not( '.clone' ) );
				var target = $.inArray( $new_slide[0], slides );

				self.slider.count++;
				self.slider.update();
				self.slider.flexslider( 'next' );
			});
	},

	/**
	@function
	@param Int slide Slide to remove
	*/
	_remove_slide : function( slide ) {
		var slideshow = this.slider;
		var slides = slideshow.slides;

		if (slide == (slides.length - 1)) {
			slideshow.flexAnimate(0);
		} else if (slides.length > 1) {
			slideshow.flexAnimate(slide + 1);
		}

		$(slideshow.slides[slide])
			.closest( 'li' )
			.remove();

		slideshow.update();

		this.edited = true;
	},

	/**
	Edit a caption
	@param {Object} $caption Caption node
	@returns {Deferred}
	*/
	_edit_caption : function( $caption ) {

		var edited = new $.Deferred();

		$caption
			.attr( 'contentEditable', 'true' )
			.on( 'focus mouseover', function(){
				$( this )
					.css( 'border', '1px solid black' );
			})
			.on( 'blur mouseout', function(){
				$( this )
					.removeAttr( 'style' );
			})
			.on( 'blur', function(){
				edited.resolve();
			})
			.on( 'keyup', function( e ){
				e.stopPropagation();
			} );

			if ( $.trim( $caption.text() ) == '' ) {
				$caption.text( 'Default text' );
			}

		return edited;
	},

	/**
	Open a drag-and-drop interface for sorting the slides
	@function
	@returns {Deferred}
	*/
	_sort : function() {

		var self = this;
		var slides = this.getData().slides;
		var edited = new $.Deferred();

		$.boom.log( 'sorting slides' );

		var dialog = $.boom.dialog.open({
			title : 'Sort slides',
			msg: 'hello',
			width: 1000,
			height: 400,
			deferred : edited,
			open : function() {
				var $list = $( '

');
				var new_slides = [];

				for ( i in slides ) {
					var slide = slides[ i ];
					var $li = $( '

' )
						.data( 'slide', slide );
					$list
						.append( $li );
				}

				$( this )
				.html(
					$list
						.sortable( {
							helper : 'clone'
						})
				);

				edited.done( function(){
					$list
						.find( 'li' )
						.each( function(){
							var slide = $( this ).data( 'slide' );
							new_slides.push( slide );
						});

					self._refresh( new_slides );
				});
			}
		});
		return edited;
	},

	/**
	Apply changes to the HTML
	*/
	insert : function(){
		var data = this.getData();

		if (data.slides.length == 0) {
			return this.remove();
		} else {
			return this._save(data);
		}
	},

	/**
	refresh slides after a sort
	@params {Array} slides New slide set (same length as old set)
	@function
	*/
	_refresh : function( slides ) {

		var self = this;

		self.element
			.find( 'ul.slides li')
			.not( '.clone' )
			.each( function( i, item ){
				var slide = slides[ i ];
				var $item = $( item );
				var src = $item.find( '.slide-asset' ).attr( 'src' ).split( '/' );

				src[3] = slide.asset_id;

				$item.find( '.slide-asset' ).attr( 'src', src.join( '/' ) );
				$item.find( '.slide-link' ).attr( 'href', slide.url );
				$item.find( '.slide-caption' ).text( slide.caption );
			});

		$.boom.log( 'slideshow refresh' );
	},

	/**
	Cancel changes and exit
	@function
	*/
	_cancel : function() {

		var self = this;

		if (self.edited ) {

			$.boom.dialog.confirm(
				'Cancel changes',
				'Cancel changes to this slideshow?'
			)
			.done( function(){

				self.element
					.find( 'ul.slides')
					.find( 'li' )
					.remove()
					.end()
					.append( self.originals );
				self.slider.count = self.originals.size();
				self.destroy();
			});

		} else {
			self.destroy();
		}
	},

	/**
	Remove editor UI and exit
	*/
	_destroy : function() {
		$.boom.log( 'exiting slideshow editor' );
		var self = this;

		self._remove_ui();

		$.ui.chunk.prototype._destroy.call( this );

	},

	/**
	Get slideshow slide data.
	@returns {Array}
	*/
	getData: function(){
		var slides = [];

		this.element
			.find( 'ul.slides li' )
			.not( '.clone' )
			.each( function(){
				var slide = {};
				var $this = $( this );

				slide.caption = $this.find( '.slide-caption' ).text();
				slide.caption = ( slide.caption == 'Default text' ) ? '' : slide.caption;
				slide.asset_id = $this.find( '.slide-asset' ).attr( 'src' ).split( '/' )[3];
				slide.url = $this.find( '.slide-link' ).attr( 'href' );

				slides.push( slide );

			});

		return { slides : slides };
	}

});<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Mixin.create({
  navigationIndex: null,
  navigableModels: null,
  modelRouteParams: [],
  disableCycling: false,
  navigableModel: Ember.computed.alias('model'),
  firstModel: Ember.computed.equal('navigationIndex', 0),
  disableNext: Ember.computed.and('lastModel', 'disableCycling'),
  disablePrevious: Ember.computed.and('firstModel', 'disableCycling'),

  lastModel: Ember.computed('navigationIndex', 'navigableModels.[]', function() {
    return this.get('navigationIndex') === this.get('navigableModels.length') - 1;
  }),

  initializeNavigableModels: function() {
    if (this.get('navigableModels') == null) {
      this.set('navigableModels', []);
    }
  }.on('init'),

  initializeNavigationIndex: function() {
    if (this.get('navigationIndex') == null) {
      this.set('navigationIndex', this.get('navigableModels').indexOf(this.get('navigableModel')));
    }
  }.observes('navigableModel'),

  resetNavigationIndex: function() {
    this.set('navigationIndex', null);
  }.observes('navigableModels.[]'),

  navigate: function(step) {
    var models = this.get('navigableModels');
    var newIndex = this.get('navigationIndex') + step;

    if (newIndex < 0) {
      newIndex = models.get('length') + newIndex;
    }

    var newModel = models.objectAt(newIndex % models.get('length'));

    this.set('navigationIndex', newIndex);
    this.transitionToRoute(...this.get('modelRouteParams').concat(newModel.get('id')));
  },

  actions: {
    previous: function() {
      if (!this.get('disablePrevious')) {
        this.navigate(-1);
      }
    },

    next: function() {
      if (!this.get('disableNext')) {
        this.navigate(1);
      }
    }
  }
});
<commit_msg>Remove NavigationIndex so state can't hurt us anymore<commit_after>import Ember from 'ember';

export default Ember.Mixin.create({
  navigableModels: null,
  modelRouteParams: [],
  disableCycling: false,
  navigableModel: Ember.computed.alias('model'),

  isFirstModel: Ember.computed('navigableModel', 'navigableModels.[]', function() {
    return Ember.isEqual(this.get('navigableModel'), this.get('navigableModels.firstObject'));
  }),
  isLastModel: Ember.computed('navigableModel', 'navigableModels.[]', function() {
    return Ember.isEqual(this.get('navigableModel'), this.get('navigableModels.lastObject'));
  }),

  disablePrevious: Ember.computed.and('isFirstModel', 'disableCycling'),
  disableNext: Ember.computed.and('isLastModel', 'disableCycling'),

  navigateToPrevious: function() {
    var newModel;

    if (this.get('isFirstModel')) {
      newModel = this.get('navigableModels.lastObject');
    } else {
      newModel = this._getModelAtOffset(-1);
    }

    this._navigateToModel(newModel);
  },

  navigateToNext: function() {
    var newModel;

    if (this.get('isLastModel')) {
      newModel = this.get('navigableModels.firstObject');
    } else {
      newModel = this._getModelAtOffset(1);
    }

    this._navigateToModel(newModel);
  },

  _getModelAtOffset: function(offset) {
    var index = this.get('navigableModels').indexOf(this.get('navigableModel'));

    if (index < 0) {
      return this.get('navigableModels.firstObject');
    } else {
      return this.get('navigableModels').objectAt(index + offset);
    }
  },

  _navigateToModel: function(model) {
    this.transitionToRoute(...this.get('modelRouteParams').concat(model.get('id')));
  },

  actions: {
    previous: function() {
      if (!this.get('disablePrevious')) {
        this.navigateToPrevious();
      }
    },

    next: function() {
      if (!this.get('disableNext')) {
        this.navigateToNext();
      }
    }
  }
});
<|endoftext|>


<commit_before>import Ember from "ember";
import config from '../config/environment';

export default Em.Controller.extend({
  emberCli: Em.inject.service('ember-cli'),
  version: config.APP.version,
  init() {
    this._super(...arguments);
    this.setupWindowUpdate();
  },

  /**
   * Output from the build, sets the `code` attr on the component
   * @type {String}
   */
  buildOutput: Ember.Object.create({ code: '', styles: ''}),
  isBuilding: false,
  activeFile: null,
  activeEditorCol: null,
  col1File: null,
  col2File: null,
  col1Active: Em.computed.equal('activeEditorCol','1'),
  col2Active: Em.computed.equal('activeEditorCol','2'),

  /**
   * Errors during build
   * @type {Array}     Array of errors
   */
  buildErrors: null,

  /**
   * Whether user wishes the code to automatically run
   * @type {boolean}
   */
  isAutorun: true,

  /**
   * Build the application and set the iframe code
   */
  buildApp () {
    if (this.get('isDestroyed')) {
      return;
    }

    this.set('isBuilding', true);
    this.set('buildErrors', []);

    this.get('emberCli').compileGist(this.get('model')).then((buildOutput) => {
      this.set('isBuilding', false);
      this.set('buildOutput', buildOutput);
    })
    .catch((errors) => {
      this.set('isBuilding', false);
      this.set('buildErrors', errors);
      errors.forEach(error => {
        console.error(error);
      });
    });
  },

  /**
   * Set the initial file columns
   */
  initializeColumns: Em.observer('model', function() {
    var files = this.get('model.files');

    if(files.objectAt(0)) {
      this.set('col1File', files.objectAt(0));
    }

    if(files.objectAt(1)) {
      this.set('col2File', files.objectAt(1));
    }
  }),

  rebuildApp: Em.observer('model.files.@each.content', 'isAutorun', function() {
    if (this.get('isAutorun')) {
      Em.run.debounce(this, this.buildApp, 500);
    }
  }),

  actions: {
    focusEditor (editor) {
      this.set('activeEditorCol', editor.get('col'));
      this.set('activeFile', editor.get('file'));
    },

    selectFile (file) {
      this.set('activeFile', file);
    },

    runNow () {
      this.buildApp();
    },

    deleteGist (gist) {
      gist.destroyRecord();
      this.transitionToRoute('gist.new');
      this.notify.info('Gist %@ was deleted from Github'.fmt(gist.get('id')));
    },

    share () {
      prompt('Ctrl + C ;-)', window.location.href);
    },

    /**
     * Add a new file to the model
     * @param {String|null} type Blueprint name or null for empty file
     */
    addFile (type) {
      let fileProperties = type ? this.get('emberCli').buildProperties(type) : {filePath:'file.js'};
      let filePath = fileProperties.filePath;

      if (['twiddle.json','router', 'css'].indexOf(type)===-1) {
        filePath = prompt('File path', filePath);
      }

      if (filePath) {
        if(this.get('model.files').findBy('filePath', filePath)) {
          alert('A file with the name %@ already exists'.fmt(filePath));
          return;
        }

        fileProperties.filePath = filePath;
        let file = this.store.createRecord('gistFile', fileProperties);

        this.get('model.files').pushObject(file);
        this.notify.info('File %@ was added'.fmt(file.get('filePath')));
        this.set('col1File', file);
        this.set('activeEditorCol', '1');
      }
    },

    renameFile (file) {
      let filePath = prompt('File path', file.get('filePath'));
      if (filePath) {
        if(this.get('model.files').findBy('filePath', filePath)) {
          alert('A file with the name %@ already exists'.fmt(filePath));
          return;
        }

        file.set('filePath', filePath);
        this.notify.info('File %@ was added'.fmt(file.get('filePath')));
      }
    },

    showErrors () {
      this.get('buildErrors').forEach((error) => {
        console.error(error);
      });
      this.notify.info('Errors were dumped to console');
    },

    fork (gist) {
      gist.fork().then((response) => {
        this.transitionToRoute('gist.edit', response.id);
      });
    },

    removeFile (file) {
      if(confirm(`Are you sure you want to remove this file?\n\n${file.get('filePath')}`)) {
        file.deleteRecord();
        this.notify.info('File %@ was deleted'.fmt(file.get('filePath')));
        this._removeFileFromColumns(file);
      }
    }
  },

  _removeFileFromColumns (file) {
    if(this.get('col1File') === file) {
      this.set('col1File', null);
    }
    if(this.get('col2File') === file) {
      this.set('col2File', null);
    }
  },

  setupWindowUpdate: function() {
    // TODO: this in a controller seems suspect, rather this should likely be
    // part of some handshake, to ensure no races exist. This should likley not
    // be something a controller would handle - (SP)
    window.updateDemoAppUrl = Ember.run.bind(this, function() {
      this.set('applicationUrl', window.demoAppUrl || '/');
    });
  }
});
<commit_msg>Confirm before deleting a twiddle<commit_after>import Ember from "ember";
import config from '../config/environment';

export default Em.Controller.extend({
  emberCli: Em.inject.service('ember-cli'),
  version: config.APP.version,
  init() {
    this._super(...arguments);
    this.setupWindowUpdate();
  },

  /**
   * Output from the build, sets the `code` attr on the component
   * @type {String}
   */
  buildOutput: Ember.Object.create({ code: '', styles: ''}),
  isBuilding: false,
  activeFile: null,
  activeEditorCol: null,
  col1File: null,
  col2File: null,
  col1Active: Em.computed.equal('activeEditorCol','1'),
  col2Active: Em.computed.equal('activeEditorCol','2'),

  /**
   * Errors during build
   * @type {Array}     Array of errors
   */
  buildErrors: null,

  /**
   * Whether user wishes the code to automatically run
   * @type {boolean}
   */
  isAutorun: true,

  /**
   * Build the application and set the iframe code
   */
  buildApp () {
    if (this.get('isDestroyed')) {
      return;
    }

    this.set('isBuilding', true);
    this.set('buildErrors', []);

    this.get('emberCli').compileGist(this.get('model')).then((buildOutput) => {
      this.set('isBuilding', false);
      this.set('buildOutput', buildOutput);
    })
    .catch((errors) => {
      this.set('isBuilding', false);
      this.set('buildErrors', errors);
      errors.forEach(error => {
        console.error(error);
      });
    });
  },

  /**
   * Set the initial file columns
   */
  initializeColumns: Em.observer('model', function() {
    var files = this.get('model.files');

    if(files.objectAt(0)) {
      this.set('col1File', files.objectAt(0));
    }

    if(files.objectAt(1)) {
      this.set('col2File', files.objectAt(1));
    }
  }),

  rebuildApp: Em.observer('model.files.@each.content', 'isAutorun', function() {
    if (this.get('isAutorun')) {
      Em.run.debounce(this, this.buildApp, 500);
    }
  }),

  actions: {
    focusEditor (editor) {
      this.set('activeEditorCol', editor.get('col'));
      this.set('activeFile', editor.get('file'));
    },

    selectFile (file) {
      this.set('activeFile', file);
    },

    runNow () {
      this.buildApp();
    },

    deleteGist (gist) {
      if(confirm(`Are you sure you want to remove this gist from Github?\n\n${gist.get('description')}`)) {
        gist.destroyRecord();
        this.transitionToRoute('gist.new');
        this.notify.info('Gist %@ was deleted from Github'.fmt(gist.get('id')));
      }
    },

    share () {
      prompt('Ctrl + C ;-)', window.location.href);
    },

    /**
     * Add a new file to the model
     * @param {String|null} type Blueprint name or null for empty file
     */
    addFile (type) {
      let fileProperties = type ? this.get('emberCli').buildProperties(type) : {filePath:'file.js'};
      let filePath = fileProperties.filePath;

      if (['twiddle.json','router', 'css'].indexOf(type)===-1) {
        filePath = prompt('File path', filePath);
      }

      if (filePath) {
        if(this.get('model.files').findBy('filePath', filePath)) {
          alert('A file with the name %@ already exists'.fmt(filePath));
          return;
        }

        fileProperties.filePath = filePath;
        let file = this.store.createRecord('gistFile', fileProperties);

        this.get('model.files').pushObject(file);
        this.notify.info('File %@ was added'.fmt(file.get('filePath')));
        this.set('col1File', file);
        this.set('activeEditorCol', '1');
      }
    },

    renameFile (file) {
      let filePath = prompt('File path', file.get('filePath'));
      if (filePath) {
        if(this.get('model.files').findBy('filePath', filePath)) {
          alert('A file with the name %@ already exists'.fmt(filePath));
          return;
        }

        file.set('filePath', filePath);
        this.notify.info('File %@ was added'.fmt(file.get('filePath')));
      }
    },

    showErrors () {
      this.get('buildErrors').forEach((error) => {
        console.error(error);
      });
      this.notify.info('Errors were dumped to console');
    },

    fork (gist) {
      gist.fork().then((response) => {
        this.transitionToRoute('gist.edit', response.id);
      });
    },

    removeFile (file) {
      if(confirm(`Are you sure you want to remove this file?\n\n${file.get('filePath')}`)) {
        file.deleteRecord();
        this.notify.info('File %@ was deleted'.fmt(file.get('filePath')));
        this._removeFileFromColumns(file);
      }
    }
  },

  _removeFileFromColumns (file) {
    if(this.get('col1File') === file) {
      this.set('col1File', null);
    }
    if(this.get('col2File') === file) {
      this.set('col2File', null);
    }
  },

  setupWindowUpdate: function() {
    // TODO: this in a controller seems suspect, rather this should likely be
    // part of some handshake, to ensure no races exist. This should likley not
    // be something a controller would handle - (SP)
    window.updateDemoAppUrl = Ember.run.bind(this, function() {
      this.set('applicationUrl', window.demoAppUrl || '/');
    });
  }
});
<|endoftext|>


<commit_before>import Ember from 'ember';
const { Mixin, computed, observer, $, A, run, on, typeOf, defineProperty, keys, get, merge } = Ember;    // jshint ignore:line
const capitalize = Ember.String.capitalize;

let SharedItem = Mixin.create({
  // Convenience Aliases and Defaults
  mood: 'default',
  style: 'default',
  size: 'default',
  skin: computed.alias('style'),
  color: computed.alias('mood'),

  // COMPUTED PROPERTIES
  // -------------------------

  // Classy stuff
  classNames: ['ui-list','item'],
  classNameBindings: ['_size','_style','disabled:disabled:enabled', '_mood','squeezed' ],

  // Stylish stuff
  squeezed: false,
  _styleDependencies: ['_componentWidth', '_componentHeight', '_windowWidth', '_windowHeight','size'],
  _style: computed('style','_styleDependencies', function() {
    let style = this.get('style');
    if (typeOf(style) === 'function') {
      run( ()=> {
        style = style(this);
      });
    }
    return !style || style === 'default' ? '' : `style-${style}`;
  }),
  _sizeDependencies: ['_componentWidth', '_componentHeight', '_windowWidth', '_windowHeight','_cpMutex'],
  _size: computed('size', 'title','responsive.mutex', function() {
    let size = this.get('size');
    if (typeOf(size) === 'function') {
      run( ()=> {
        size = size(this);
      });
    }
    return !size || size === 'default' ? '' : size;
  }),
  _mood: computed('mood','title','subHeading','badge','icon','image','style', 'size', function() {
    let mood = this.get('mood');
    if (typeOf(mood) === 'function') {
      run( ()=> {
        mood = mood(this);
      });
    }
    return !mood || mood === 'default' ? '' : `mood-${mood}`;
  }),

  /**
   * The specific Item components should define which aspects and panes they support, this
   * just bring together the resultant array of aspectPanes which could be targetted/configured by a user
   */
  _aspectPanes: computed('_aspects','_panes',function() {
    const aspects = new A(this.get('_aspects'));
    const panes = new A(this.get('_panes'));
    let aspectPanes = [];
    aspects.forEach( aspect => {
      aspectPanes.push(aspect);
      panes.forEach( pane => {
        aspectPanes.push(aspect + capitalize(pane));
      });
    });

    return aspectPanes;
  }),

  /**
   * INITIALIZE ITEM COMPONENT
   */
  _init: on('init', function() {
    this._unpackData();       // data hash populated by list component
    this._shortcutAliases();  // add convenience API-surface shortcuts (e.g., 'icon' instead of 'leftIcon', etc.)
    this._defineAspectMappings(); // ensure all mappings are setup as CP's
    this._logicPanes();       // create hasXXX() logic flags for page existance
    this._setDefaultValues();
  }),

  // Unpack data property from a list
  _unpackData: function() {
    const ignoredProperties = new A(['toString','toArray', 'isTruthy']);
    const isEmberData = typeOf(get(this, 'data.data')) === 'object' ? true : false;
    const notPrivate = property => { return property.substr(0,1) !== '_'; };
    const notIgnored = property => { return !ignoredProperties.contains(property); };
    const safeProperties = property => { return notPrivate(property) && notIgnored(property); };
    const dataSource = isEmberData ? get(this, 'data.data') : get(this,'data');
    let   reflector = dataSource ? keys(dataSource) : [];
    const data = get(this, 'data');
    // NOTE: this whole ED nonsense can be removed if we can get the ProxyMixin working again but until then we
    // need to detect decorated properties somehow as the Ember.keys() method does not properly reflect ED arrays
    if(isEmberData) {
        let decorators = [];
        for (let property in data) {
          if(property.substr(0,1) !== '_') {
            console.log('prop: %s', property);
            if(!data.hasOwnProperty(property) && property.substr(0,1) !== '_' && typeOf(data[property]) === 'object') {
              decorators.push(property);
            }
          }
        }
        reflector = reflector.concat(decorators);
    };
    reflector.filter(safeProperties).forEach( key => {
      // create CP on root and point back to attribute on data hash
      let cp = computed.readOnly(`data.${key}`);
      defineProperty(this, key, cp);
    });
  },
  _shortcutAliases: function() {
    const defaultAliases = {
      title: 'centerTitle',
      subHeading: 'centerSubHeading'
    };
    const paneAliases = merge(defaultAliases,this.get('paneAliases'));
    keys(paneAliases).forEach( key => {
      // if property exists (as it will if data hash has mapped a CP there already), create reverse alias
      let cp;
      if(!this.get(key)) {
        cp = computed.alias(paneAliases[key]);
        defineProperty(this, key, cp);
      } else {
        cp = computed.alias(key);
        defineProperty(this, paneAliases[key], cp);
      }
    });
  },

  /**
   * setup up logical flags to indicate the existance of content on a per pane basis
   */
  _logicPanes: function() {
    const panes = new A(this.get('_panes'));
    const aspects = new A(this.get('_aspects'));
    // let panesSet = [];
    panes.forEach( pane => {
      const property = 'has' + capitalize(pane) + 'Pane';
      const relevantAspects = aspects.map(aspect=>{ return aspect + capitalize(pane); });
      const cp = computed.or(...relevantAspects);
      defineProperty(this,property,cp);
      this.notifyPropertyChange(property);
    });
  },

  /**
   * Responsible for allowing the private CP's that sit as sidecars to
   * API props which take scalars OR function callbacks. The issue at hand is
   * that scale inputs in the API are simple to handle but if a function is
   * recieved the function itself will be dependent on an unknown set of variables
   * in the item and the CP needs to respond to each of them.
   *
   * The strategy for doing this is to combine all aspectPanes which are set to non-null values
   * at construction, and then whatever properties are contained in _itemCoreProperties, _itemMetaProperties,
   * and finally all _cpProperties. These properties are setup as an
   * mutex observer which signals change to the CP. From the perspective of the CP, it is only necessary
   * to watch the mutex property for changes.
   */
  _cpInitialisation: on('init', function() {
    const props = this.get('_cpProperties'); // jshint ignore:line
    // const aspectPanes = this.get('_aspectPanes'); // jshint ignore:line

    // TODO: implement
  }),
  _cpProperties: ['style','mood','size'],

  // Initialize "Dereferenced Computed Properties"
	// ---------------------------------------------
	// NOTE: 'map' is a dereferenced hash of mappings; an item can use either a map or individual property assignments
	// of the variety item.fooMap = 'mappedTo';
  _defineAspectMappings: function() {
    const aspectPanes = new A(this.get('_aspectPanes'));
    aspectPanes.forEach( aspectPane => {
      if(this._getMap(aspectPane)) {
        let cp = computed.readOnly(this._getMap(aspectPane));
        defineProperty(this, aspectPane, cp);
      }
    });
  },
	_getMap: function(property) {
    return this.get(`map${capitalize(property)}`) || this.get(`map.${property}`);
	},

  // Default Values
  _setDefaultValues: function() {
    const aspectPanes = this.get('_aspectPanes');
    aspectPanes.forEach( item => {
      const defaultKey = 'default' + capitalize(item);
      if(!this.get(item) && this[defaultKey]) {
        this.set(item, this[defaultKey]);
      }
    });
  },

  /**
   * Registers the item with a parent list (if one exists)
   */
  _register: on('afterRender', function() {
    const list = this.get('list');
    if(this.get('list.register')) {
      list.register(this);
    }
  }),
  _deregister: on('willDestroyElement', function() {
    const list = this.get('list');
    if(this.get('list.deregister')) {
      list.deregister(this);
    }
  })
});

SharedItem[Ember.NAME_KEY] = 'Item Mixin';
export default SharedItem;<commit_msg>fixed stack size issue (by removing Ember's typeOf and using JS one)<commit_after>import Ember from 'ember';
const { Mixin, computed, observer, $, A, run, on, typeOf, defineProperty, keys, get, merge } = Ember;    // jshint ignore:line
const capitalize = Ember.String.capitalize;

let SharedItem = Mixin.create({
  // Convenience Aliases and Defaults
  mood: 'default',
  style: 'default',
  size: 'default',
  skin: computed.alias('style'),
  color: computed.alias('mood'),

  // COMPUTED PROPERTIES
  // -------------------------

  // Classy stuff
  classNames: ['ui-list','item'],
  classNameBindings: ['_size','_style','disabled:disabled:enabled', '_mood','squeezed' ],

  // Stylish stuff
  squeezed: false,
  _styleDependencies: ['_componentWidth', '_componentHeight', '_windowWidth', '_windowHeight','size'],
  _style: computed('style','_styleDependencies', function() {
    let style = this.get('style');
    if (typeOf(style) === 'function') {
      run( ()=> {
        style = style(this);
      });
    }
    return !style || style === 'default' ? '' : `style-${style}`;
  }),
  _sizeDependencies: ['_componentWidth', '_componentHeight', '_windowWidth', '_windowHeight','_cpMutex'],
  _size: computed('size', 'title','responsive.mutex', function() {
    let size = this.get('size');
    if (typeOf(size) === 'function') {
      run( ()=> {
        size = size(this);
      });
    }
    return !size || size === 'default' ? '' : size;
  }),
  _mood: computed('mood','title','subHeading','badge','icon','image','style', 'size', function() {
    let mood = this.get('mood');
    if (typeOf(mood) === 'function') {
      run( ()=> {
        mood = mood(this);
      });
    }
    return !mood || mood === 'default' ? '' : `mood-${mood}`;
  }),

  /**
   * The specific Item components should define which aspects and panes they support, this
   * just bring together the resultant array of aspectPanes which could be targetted/configured by a user
   */
  _aspectPanes: computed('_aspects','_panes',function() {
    const aspects = new A(this.get('_aspects'));
    const panes = new A(this.get('_panes'));
    let aspectPanes = [];
    aspects.forEach( aspect => {
      aspectPanes.push(aspect);
      panes.forEach( pane => {
        aspectPanes.push(aspect + capitalize(pane));
      });
    });

    return aspectPanes;
  }),

  /**
   * INITIALIZE ITEM COMPONENT
   */
  _init: on('init', function() {
    this._unpackData();       // data hash populated by list component
    this._shortcutAliases();  // add convenience API-surface shortcuts (e.g., 'icon' instead of 'leftIcon', etc.)
    this._defineAspectMappings(); // ensure all mappings are setup as CP's
    this._logicPanes();       // create hasXXX() logic flags for page existance
    this._setDefaultValues();
  }),

  // Unpack data property from a list
  _unpackData: function() {
    const ignoredProperties = new A(['toString','toArray', 'isTruthy']);
    const isEmberData = typeOf(get(this, 'data.data')) === 'object' ? true : false;
    const notPrivate = property => { return property.substr(0,1) !== '_'; };
    const notIgnored = property => { return !ignoredProperties.contains(property); };
    const safeProperties = property => { return notPrivate(property) && notIgnored(property); };
    const dataSource = isEmberData ? get(this, 'data.data') : get(this,'data');
    let   reflector = dataSource ? keys(dataSource) : [];
    const data = get(this, 'data');
    // NOTE: this whole ED nonsense can be removed if we can get the ProxyMixin working again but
		// until then we need to detect decorated properties somehow as the Ember.keys() method does
		// not properly reflect ED arrays
    if(isEmberData) {
        let decorators = [];
        for (let property in data) {
          if(property.substr(0,1) !== '_') {
            if(!data.hasOwnProperty(property) && property.substr(0,1) !== '_' && data[property] === 'object') {
	            console.log('prop: %s', property);
              decorators.push(property);
            }
          }
        }
        reflector = reflector.concat(decorators);
    };
    reflector.filter(safeProperties).forEach( key => {
      // create CP on root and point back to attribute on data hash
      let cp = computed.readOnly(`data.${key}`);
      defineProperty(this, key, cp);
    });
  },
  _shortcutAliases: function() {
    const defaultAliases = {
      title: 'centerTitle',
      subHeading: 'centerSubHeading'
    };
    const paneAliases = merge(defaultAliases,this.get('paneAliases'));
    keys(paneAliases).forEach( key => {
      // if property exists (as it will if data hash has mapped a CP there already), create reverse alias
      let cp;
      if(!this.get(key)) {
        cp = computed.alias(paneAliases[key]);
        defineProperty(this, key, cp);
      } else {
        cp = computed.alias(key);
        defineProperty(this, paneAliases[key], cp);
      }
    });
  },

  /**
   * setup up logical flags to indicate the existance of content on a per pane basis
   */
  _logicPanes: function() {
    const panes = new A(this.get('_panes'));
    const aspects = new A(this.get('_aspects'));
    // let panesSet = [];
    panes.forEach( pane => {
      const property = 'has' + capitalize(pane) + 'Pane';
      const relevantAspects = aspects.map(aspect=>{ return aspect + capitalize(pane); });
      const cp = computed.or(...relevantAspects);
      defineProperty(this,property,cp);
      this.notifyPropertyChange(property);
    });
  },

  /**
   * Responsible for allowing the private CP's that sit as sidecars to
   * API props which take scalars OR function callbacks. The issue at hand is
   * that scale inputs in the API are simple to handle but if a function is
   * recieved the function itself will be dependent on an unknown set of variables
   * in the item and the CP needs to respond to each of them.
   *
   * The strategy for doing this is to combine all aspectPanes which are set to non-null values
   * at construction, and then whatever properties are contained in _itemCoreProperties, _itemMetaProperties,
   * and finally all _cpProperties. These properties are setup as an
   * mutex observer which signals change to the CP. From the perspective of the CP, it is only necessary
   * to watch the mutex property for changes.
   */
  _cpInitialisation: on('init', function() {
    const props = this.get('_cpProperties'); // jshint ignore:line
    // const aspectPanes = this.get('_aspectPanes'); // jshint ignore:line

    // TODO: implement
  }),
  _cpProperties: ['style','mood','size'],

  // Initialize "Dereferenced Computed Properties"
	// ---------------------------------------------
	// NOTE: 'map' is a dereferenced hash of mappings; an item can use either a map or individual property assignments
	// of the variety item.fooMap = 'mappedTo';
  _defineAspectMappings: function() {
    const aspectPanes = new A(this.get('_aspectPanes'));
    aspectPanes.forEach( aspectPane => {
      if(this._getMap(aspectPane)) {
        let cp = computed.readOnly(this._getMap(aspectPane));
        defineProperty(this, aspectPane, cp);
      }
    });
  },
	_getMap: function(property) {
    return this.get(`map${capitalize(property)}`) || this.get(`map.${property}`);
	},

  // Default Values
  _setDefaultValues: function() {
    const aspectPanes = this.get('_aspectPanes');
    aspectPanes.forEach( item => {
      const defaultKey = 'default' + capitalize(item);
      if(!this.get(item) && this[defaultKey]) {
        this.set(item, this[defaultKey]);
      }
    });
  },

  /**
   * Registers the item with a parent list (if one exists)
   */
  _register: on('afterRender', function() {
    const list = this.get('list');
    if(this.get('list.register')) {
      list.register(this);
    }
  }),
  _deregister: on('willDestroyElement', function() {
    const list = this.get('list');
    if(this.get('list.deregister')) {
      list.deregister(this);
    }
  })
});

SharedItem[Ember.NAME_KEY] = 'Item Mixin';
export default SharedItem;<|endoftext|>


<commit_before>// Universal Subtitles, universalsubtitles.org
//
// Copyright (C) 2010 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

goog.provide('unisubs.subtitle.SyncPanel');

/**
 * @constructor
 * @param {unisubs.subtitle.EditableCaptionSet} subtitles The subtitles
 *     for the video, so far.
 * @param {unisubs.player.AbstractVideoPlayer} videoPlayer
 * @param {unisubs.CaptionManager} Caption manager, already containing subtitles
 *     with start_time set.
 */
unisubs.subtitle.SyncPanel = function(subtitles, videoPlayer, serverModel, captionManager, reviewOrApprovalType) {
    goog.ui.Component.call(this);
    /**
     * @type {unisubs.subtitle.EditableCaptionSet}
     */
    this.subtitles_ = subtitles;

    this.videoPlayer_ = videoPlayer;
    /**
     * @protected
     */
    this.serverModel = serverModel;
    this.captionManager_ = captionManager;
    this.videoStarted_ = false;
    this.downSub_ = null;
    this.downPlayheadTime_ = -1;
    this.downHeld_ = false;
    this.keyEventsSuspended_ = false;
    this.reviewOrApprovalType_ = reviewOrApprovalType;
    if (this.reviewOrApprovalType_){
        this.numSteps_ = 2;
        this.currentStep_ = 0;
    }else{
        this.numSteps_ = 4;
        this.currentStep_ = 1;
    }

};

goog.inherits(unisubs.subtitle.SyncPanel, goog.ui.Component);

unisubs.subtitle.SyncPanel.prototype.enterDocument = function() {
    unisubs.subtitle.SyncPanel.superClass_.enterDocument.call(this);
    var handler = this.getHandler();
    handler.listen(this.captionManager_,
                   unisubs.CaptionManager.CAPTION,
                   this.captionReached_).
        listen(document, goog.events.EventType.KEYDOWN, this.handleKeyDown_).
        listen(document, goog.events.EventType.KEYUP, this.handleKeyUp_);
};
unisubs.subtitle.SyncPanel.prototype.createDom = function() {
    unisubs.subtitle.SyncPanel.superClass_.createDom.call(this);
    var $d = goog.bind(this.getDomHelper().createDom, this.getDomHelper());
    this.getElement().appendChild(this.contentElem_ = $d('div'));
    this.addChild(this.subtitleList_ = new unisubs.subtitle.SubtitleList(
        this.videoPlayer_, this.subtitles_, true, false, false), true);
};
unisubs.subtitle.SyncPanel.prototype.getRightPanel = function() {
    if (!this.rightPanel_) {
        this.rightPanel_ = this.createRightPanelInternal();
        this.getHandler().
            listen(
                this.rightPanel_,
                unisubs.RightPanel.EventType.LEGENDKEY,
                this.handleLegendKeyPress_).
            listen(
                this.rightPanel_,
                unisubs.RightPanel.EventType.RESTART,
                this.startOverClicked_);
    }
    return this.rightPanel_;
};
unisubs.subtitle.SyncPanel.prototype.createRightPanelInternal = function() {
    var $d = goog.bind(this.getDomHelper().createDom, this.getDomHelper());
    var reviewOrApproval = true;
    var internalComponents = unisubs.RightPanel.createInternalContentsReviewOrApproval(
        $d, this.reviewOrApprovalType_, this.numSteps_, this.currentStep_, true);
    var keySpecs;
    if (! internalComponents){
        internalComponents = {

            'helpContents': new unisubs.RightPanel.HelpContents(
                "Syncing",
                ["Congratulations, you finished the hard part (all that typing)!",
                 ["Now, to line up your subtitles to the video, tap the DOWN ARROW right ",
                  "when each subtitle should appear."].join(''),
                 "Press TAB to begin, press DOWN for the first subtitle, and DOWN for each subtitle after that.",
                 ["Don't worry about small mistakes. We can correct them in the ",
                  "next step. If you need to start over, click \"restart\" ",
                  "below."].join('')],
                this.numSteps_, this.currentStep_),
            'extraHelp':
            ["Press play, then tap this button or the down arrow when the next subtitle should appear."]
        };
        keySpecs = this.makeKeySpecsInternal();
    } else {

        keySpecs = [];
        this.bodyInput_ = internalComponents['bodyInput'];

    }
    var panel = new unisubs.RightPanel(
        this.serverModel_, internalComponents['helpContents'],
            internalComponents['extraHelp'], keySpecs, true, "Done?",
        "Next Step: Subtitle info");
    if (this.reviewOrApprovalType_){
        // we never allow to stop a review midway, makes life complicated
        panel.showSaveExit = false;
    }
    return panel;
};
unisubs.subtitle.SyncPanel.prototype.makeKeySpecsInternal = function() {
    var KC = goog.events.KeyCodes;
    return [
        new unisubs.RightPanel.KeySpec(
            'unisubs-begin', 'unisubs-down', 'down',
            'Tap when next subtitle should appear', KC.DOWN, 0),
        new unisubs.RightPanel.KeySpec(
            'unisubs-play', 'unisubs-tab', 'tab', 'Play/Pause', KC.TAB, 0),
        new unisubs.RightPanel.KeySpec(
            'unisubs-skip', 'unisubs-control', 'shift\n+\ntab',
            'Skip Back 4 Seconds', KC.TAB,
            unisubs.RightPanel.KeySpec.Modifier.SHIFT)
    ];

};
unisubs.subtitle.SyncPanel.prototype.suspendKeyEvents = function(suspended) {
    this.keyEventsSuspended_ = suspended;
};
unisubs.subtitle.SyncPanel.prototype.handleLegendKeyPress_ = function(event) {
    if (event.keyCode == goog.events.KeyCodes.DOWN) {
        if (event.keyEventType == goog.events.EventType.MOUSEDOWN &&
            !this.currentlyEditingSubtitle_())
            this.downPressed_();
        else if (event.keyEventType == goog.events.EventType.MOUSEUP &&
                this.downHeld_)
            this.downReleased_();
    }
};
unisubs.subtitle.SyncPanel.prototype.handleKeyDown_ = function(event) {
    if (this.keyEventsSuspended_)
        return;
    if (event.keyCode == goog.events.KeyCodes.DOWN &&
        !this.currentlyEditingSubtitle_()) {
        event.preventDefault();
        this.downPressed_();
        this.rightPanel_.setKeyDown(event.keyCode, 0, true);
    }
    else if (event.keyCode == goog.events.KeyCodes.SPACE &&
        !this.currentlyEditingSubtitle_()) {
        event.preventDefault();
        this.spacePressed_();
        this.rightPanel_.setKeyDown(goog.events.KeyCodes.TAB, 0, true);
    }
};
unisubs.subtitle.SyncPanel.prototype.handleKeyUp_ = function(event) {
    if (event.keyCode == goog.events.KeyCodes.DOWN && this.downHeld_) {
        event.preventDefault();
        this.downReleased_();
        this.rightPanel_.setKeyDown(event.keyCode, 0, false);
    }
    else if (event.keyCode == goog.events.KeyCodes.SPACE &&
             !this.currentlyEditingSubtitle_())
        this.rightPanel_.setKeyDown(goog.events.KeyCodes.TAB, 0, false);
};
unisubs.subtitle.SyncPanel.prototype.spacePressed_ = function() {
    this.videoPlayer_.togglePause();
};
unisubs.subtitle.SyncPanel.prototype.downPressed_ = function() {
    if (this.videoPlayer_.isPlaying()) {
        if (this.downHeld_)
            return;
        this.captionManager_.disableCaptionEvents(true);
        this.downHeld_ = true;
        this.videoStarted_ = true;
        this.downPlayheadTime_ =
            this.videoPlayer_.getPlayheadTime();
        this.downSub_ =
            this.subtitles_.findLastForTime(this.downPlayheadTime_);
    }
};
unisubs.subtitle.SyncPanel.prototype.downReleased_ = function() {
    this.captionManager_.disableCaptionEvents(false);
    this.downHeld_ = false;
    var playheadTime = this.videoPlayer_.getPlayheadTime();

    if (!this.downSub_ ||
        !this.downSub_.isShownAt(this.downPlayheadTime_)) {
        // pressed down before first sub or in between subs.
        var nextSub = null;
        if ((this.downSub_ === undefined || this.downSub_ === null) && this.subtitles_.count() > 0)
            nextSub = this.subtitles_.caption(0);
        if (this.downSub_ !== null && this.downSub_ !== undefined)
            nextSub = this.downSub_.getNextCaption();
        if (nextSub !== null && nextSub !== undefined)
            nextSub.setStartTime(playheadTime);
    }
    else if (this.downSub_.isShownAt(playheadTime) &&
             this.downSub_.getNextCaption())
        this.downSub_.getNextCaption().setStartTime(playheadTime);
    else
        this.downSub_.setEndTime(playheadTime);

    this.downSub_ = null;
    this.downPlayheadTime_ = -1;
};
unisubs.subtitle.SyncPanel.prototype.startOverClicked_ = function() {
    var answer =
        confirm("Are you sure you want to start over? All timestamps " +
                "will be deleted.");
    if (answer) {
        this.videoPlayer_.setPlayheadTime(0);
    }
};
unisubs.subtitle.SyncPanel.prototype.currentlyEditingSubtitle_ = function() {
    var inSublist =  this.subtitleList_.isCurrentlyEditing() ;
    if (inSublist){
        return true;
    }
    // focus could be on the review notes text area
    // TODO: use this as an authoritative way to determine if subs are being edited
    if (document.activeElement ){
        var tagName  = document.activeElement.tagName.toLowerCase() ;
        if (tagName  == 'input' || tagName == 'textarea'){
            return true;
        }
    }
};
unisubs.subtitle.SyncPanel.prototype.captionReached_ = function(event) {
    var editableCaption = event.caption;
    this.subtitleList_.clearActiveWidget();
    if (editableCaption !== null && editableCaption !== undefined)
        this.subtitleList_.setActiveWidget(editableCaption.getCaptionID());
};
unisubs.subtitle.SyncPanel.prototype.disposeInternal = function() {
    unisubs.subtitle.SyncPanel.superClass_.disposeInternal.call(this);
    if (this.rightPanel_)
        this.rightPanel_.dispose();
};
<commit_msg>It's 2012<commit_after>// Universal Subtitles, universalsubtitles.org
//
// Copyright (C) 2012 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

goog.provide('unisubs.subtitle.SyncPanel');

/**
 * @constructor
 * @param {unisubs.subtitle.EditableCaptionSet} subtitles The subtitles
 *     for the video, so far.
 * @param {unisubs.player.AbstractVideoPlayer} videoPlayer
 * @param {unisubs.CaptionManager} Caption manager, already containing subtitles
 *     with start_time set.
 */
unisubs.subtitle.SyncPanel = function(subtitles, videoPlayer, serverModel, captionManager, reviewOrApprovalType) {
    goog.ui.Component.call(this);
    /**
     * @type {unisubs.subtitle.EditableCaptionSet}
     */
    this.subtitles_ = subtitles;

    this.videoPlayer_ = videoPlayer;
    /**
     * @protected
     */
    this.serverModel = serverModel;
    this.captionManager_ = captionManager;
    this.videoStarted_ = false;
    this.downSub_ = null;
    this.downPlayheadTime_ = -1;
    this.downHeld_ = false;
    this.keyEventsSuspended_ = false;
    this.reviewOrApprovalType_ = reviewOrApprovalType;
    if (this.reviewOrApprovalType_){
        this.numSteps_ = 2;
        this.currentStep_ = 0;
    }else{
        this.numSteps_ = 4;
        this.currentStep_ = 1;
    }

};

goog.inherits(unisubs.subtitle.SyncPanel, goog.ui.Component);

unisubs.subtitle.SyncPanel.prototype.enterDocument = function() {
    unisubs.subtitle.SyncPanel.superClass_.enterDocument.call(this);
    var handler = this.getHandler();
    handler.listen(this.captionManager_,
                   unisubs.CaptionManager.CAPTION,
                   this.captionReached_).
        listen(document, goog.events.EventType.KEYDOWN, this.handleKeyDown_).
        listen(document, goog.events.EventType.KEYUP, this.handleKeyUp_);
};
unisubs.subtitle.SyncPanel.prototype.createDom = function() {
    unisubs.subtitle.SyncPanel.superClass_.createDom.call(this);
    var $d = goog.bind(this.getDomHelper().createDom, this.getDomHelper());
    this.getElement().appendChild(this.contentElem_ = $d('div'));
    this.addChild(this.subtitleList_ = new unisubs.subtitle.SubtitleList(
        this.videoPlayer_, this.subtitles_, true, false, false), true);
};
unisubs.subtitle.SyncPanel.prototype.getRightPanel = function() {
    if (!this.rightPanel_) {
        this.rightPanel_ = this.createRightPanelInternal();
        this.getHandler().
            listen(
                this.rightPanel_,
                unisubs.RightPanel.EventType.LEGENDKEY,
                this.handleLegendKeyPress_).
            listen(
                this.rightPanel_,
                unisubs.RightPanel.EventType.RESTART,
                this.startOverClicked_);
    }
    return this.rightPanel_;
};
unisubs.subtitle.SyncPanel.prototype.createRightPanelInternal = function() {
    var $d = goog.bind(this.getDomHelper().createDom, this.getDomHelper());
    var reviewOrApproval = true;
    var internalComponents = unisubs.RightPanel.createInternalContentsReviewOrApproval(
        $d, this.reviewOrApprovalType_, this.numSteps_, this.currentStep_, true);
    var keySpecs;
    if (! internalComponents){
        internalComponents = {

            'helpContents': new unisubs.RightPanel.HelpContents(
                "Syncing",
                ["Congratulations, you finished the hard part (all that typing)!",
                 ["Now, to line up your subtitles to the video, tap the DOWN ARROW right ",
                  "when each subtitle should appear."].join(''),
                 "Press TAB to begin, press DOWN for the first subtitle, and DOWN for each subtitle after that.",
                 ["Don't worry about small mistakes. We can correct them in the ",
                  "next step. If you need to start over, click \"restart\" ",
                  "below."].join('')],
                this.numSteps_, this.currentStep_),
            'extraHelp':
            ["Press play, then tap this button or the down arrow when the next subtitle should appear."]
        };
        keySpecs = this.makeKeySpecsInternal();
    } else {

        keySpecs = [];
        this.bodyInput_ = internalComponents['bodyInput'];

    }
    var panel = new unisubs.RightPanel(
        this.serverModel_, internalComponents['helpContents'],
            internalComponents['extraHelp'], keySpecs, true, "Done?",
        "Next Step: Subtitle info");
    if (this.reviewOrApprovalType_){
        // we never allow to stop a review midway, makes life complicated
        panel.showSaveExit = false;
    }
    return panel;
};
unisubs.subtitle.SyncPanel.prototype.makeKeySpecsInternal = function() {
    var KC = goog.events.KeyCodes;
    return [
        new unisubs.RightPanel.KeySpec(
            'unisubs-begin', 'unisubs-down', 'down',
            'Tap when next subtitle should appear', KC.DOWN, 0),
        new unisubs.RightPanel.KeySpec(
            'unisubs-play', 'unisubs-tab', 'tab', 'Play/Pause', KC.TAB, 0),
        new unisubs.RightPanel.KeySpec(
            'unisubs-skip', 'unisubs-control', 'shift\n+\ntab',
            'Skip Back 4 Seconds', KC.TAB,
            unisubs.RightPanel.KeySpec.Modifier.SHIFT)
    ];

};
unisubs.subtitle.SyncPanel.prototype.suspendKeyEvents = function(suspended) {
    this.keyEventsSuspended_ = suspended;
};
unisubs.subtitle.SyncPanel.prototype.handleLegendKeyPress_ = function(event) {
    if (event.keyCode == goog.events.KeyCodes.DOWN) {
        if (event.keyEventType == goog.events.EventType.MOUSEDOWN &&
            !this.currentlyEditingSubtitle_())
            this.downPressed_();
        else if (event.keyEventType == goog.events.EventType.MOUSEUP &&
                this.downHeld_)
            this.downReleased_();
    }
};
unisubs.subtitle.SyncPanel.prototype.handleKeyDown_ = function(event) {
    if (this.keyEventsSuspended_)
        return;
    if (event.keyCode == goog.events.KeyCodes.DOWN &&
        !this.currentlyEditingSubtitle_()) {
        event.preventDefault();
        this.downPressed_();
        this.rightPanel_.setKeyDown(event.keyCode, 0, true);
    }
    else if (event.keyCode == goog.events.KeyCodes.SPACE &&
        !this.currentlyEditingSubtitle_()) {
        event.preventDefault();
        this.spacePressed_();
        this.rightPanel_.setKeyDown(goog.events.KeyCodes.TAB, 0, true);
    }
};
unisubs.subtitle.SyncPanel.prototype.handleKeyUp_ = function(event) {
    if (event.keyCode == goog.events.KeyCodes.DOWN && this.downHeld_) {
        event.preventDefault();
        this.downReleased_();
        this.rightPanel_.setKeyDown(event.keyCode, 0, false);
    }
    else if (event.keyCode == goog.events.KeyCodes.SPACE &&
             !this.currentlyEditingSubtitle_())
        this.rightPanel_.setKeyDown(goog.events.KeyCodes.TAB, 0, false);
};
unisubs.subtitle.SyncPanel.prototype.spacePressed_ = function() {
    this.videoPlayer_.togglePause();
};
unisubs.subtitle.SyncPanel.prototype.downPressed_ = function() {
    if (this.videoPlayer_.isPlaying()) {
        if (this.downHeld_)
            return;
        this.captionManager_.disableCaptionEvents(true);
        this.downHeld_ = true;
        this.videoStarted_ = true;
        this.downPlayheadTime_ =
            this.videoPlayer_.getPlayheadTime();
        this.downSub_ =
            this.subtitles_.findLastForTime(this.downPlayheadTime_);
    }
};
unisubs.subtitle.SyncPanel.prototype.downReleased_ = function() {
    this.captionManager_.disableCaptionEvents(false);
    this.downHeld_ = false;
    var playheadTime = this.videoPlayer_.getPlayheadTime();

    if (!this.downSub_ ||
        !this.downSub_.isShownAt(this.downPlayheadTime_)) {
        // pressed down before first sub or in between subs.
        var nextSub = null;
        if ((this.downSub_ === undefined || this.downSub_ === null) && this.subtitles_.count() > 0)
            nextSub = this.subtitles_.caption(0);
        if (this.downSub_ !== null && this.downSub_ !== undefined)
            nextSub = this.downSub_.getNextCaption();
        if (nextSub !== null && nextSub !== undefined)
            nextSub.setStartTime(playheadTime);
    }
    else if (this.downSub_.isShownAt(playheadTime) &&
             this.downSub_.getNextCaption())
        this.downSub_.getNextCaption().setStartTime(playheadTime);
    else
        this.downSub_.setEndTime(playheadTime);

    this.downSub_ = null;
    this.downPlayheadTime_ = -1;
};
unisubs.subtitle.SyncPanel.prototype.startOverClicked_ = function() {
    var answer =
        confirm("Are you sure you want to start over? All timestamps " +
                "will be deleted.");
    if (answer) {
        this.videoPlayer_.setPlayheadTime(0);
    }
};
unisubs.subtitle.SyncPanel.prototype.currentlyEditingSubtitle_ = function() {
    var inSublist =  this.subtitleList_.isCurrentlyEditing() ;
    if (inSublist){
        return true;
    }
    // focus could be on the review notes text area
    // TODO: use this as an authoritative way to determine if subs are being edited
    if (document.activeElement ){
        var tagName  = document.activeElement.tagName.toLowerCase() ;
        if (tagName  == 'input' || tagName == 'textarea'){
            return true;
        }
    }
};
unisubs.subtitle.SyncPanel.prototype.captionReached_ = function(event) {
    var editableCaption = event.caption;
    this.subtitleList_.clearActiveWidget();
    if (editableCaption !== null && editableCaption !== undefined)
        this.subtitleList_.setActiveWidget(editableCaption.getCaptionID());
};
unisubs.subtitle.SyncPanel.prototype.disposeInternal = function() {
    unisubs.subtitle.SyncPanel.superClass_.disposeInternal.call(this);
    if (this.rightPanel_)
        this.rightPanel_.dispose();
};
<|endoftext|>


<commit_before>// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
  "use strict";

  CodeMirror.defineExtension("annotateScrollbar", function(options) {
    if (typeof options == "string") options = {className: options};
    return new Annotation(this, options);
  });

  CodeMirror.defineOption("scrollButtonHeight", 0);

  function Annotation(cm, options) {
    this.cm = cm;
    this.options = options;
    this.buttonHeight = options.scrollButtonHeight || cm.getOption("scrollButtonHeight");
    this.annotations = [];
    this.doRedraw = this.doUpdate = null;
    this.div = cm.getWrapperElement().appendChild(document.createElement("div"));
    this.div.style.cssText = "position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none";
    this.computeScale();

    function scheduleRedraw(delay) {
      clearTimeout(self.doRedraw);
      self.doRedraw = setTimeout(function() { self.redraw(); }, delay);
    }

    var self = this;
    cm.on("refresh", this.resizeHandler = function() {
      clearTimeout(self.doUpdate);
      self.doUpdate = setTimeout(function() {
        if (self.computeScale()) scheduleRedraw(20);
      }, 100);
    });
    cm.on("markerAdded", this.resizeHandler);
    cm.on("markerCleared", this.resizeHandler);
    if (options.listenForChanges !== false)
      cm.on("changes", this.changeHandler = function() {
        scheduleRedraw(250);
      });
  }

  Annotation.prototype.computeScale = function() {
    var cm = this.cm;
    var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /
      cm.getScrollerElement().scrollHeight
    if (hScale != this.hScale) {
      this.hScale = hScale;
      return true;
    }
  };

  Annotation.prototype.update = function(annotations) {
    this.annotations = annotations;
    this.redraw();
  };

  Annotation.prototype.redraw = function(compute) {
    if (compute !== false) this.computeScale();
    var cm = this.cm, hScale = this.hScale;

    var frag = document.createDocumentFragment(), anns = this.annotations;

    var wrapping = cm.getOption("lineWrapping");
    var singleLineH = wrapping && cm.defaultTextHeight() * 1.5;
    var curLine = null, curLineObj = null;

    function getFoldLineHandle(pos) {
      var marks = cm.findMarksAt(pos);
      for (var i = 0; i < marks.length; ++i) {
        if (marks[i].collapsed)
          return marks[i].lines[0];
      }
    }

    function getY(pos, top) {
      if (curLine != pos.line) {
        curLine = pos.line;
        if(!(curLineObj = getFoldLineHandle(pos)))
          curLineObj = cm.getLineHandle(curLine);
      }
      if ((curLineObj.widgets && curLineObj.widgets.length) ||
          (wrapping && curLineObj.height > singleLineH))
        return cm.charCoords(pos, "local")[top ? "top" : "bottom"];
      var topY = cm.heightAtLine(curLineObj, "local");
      return topY + (top ? 0 : curLineObj.height);
    }

    var lastLine = cm.lastLine()
    if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {
      var ann = anns[i];
      if (ann.to.line > lastLine) continue;
      var top = nextTop || getY(ann.from, true) * hScale;
      var bottom = getY(ann.to, false) * hScale;
      while (i < anns.length - 1) {
        if (anns[i + 1].to.line > lastLine) break;
        nextTop = getY(anns[i + 1].from, true) * hScale;
        if (nextTop > bottom + .9) break;
        ann = anns[++i];
        bottom = getY(ann.to, false) * hScale;
      }
      if (bottom == top) continue;
      var height = Math.max(bottom - top, 3);

      var elt = frag.appendChild(document.createElement("div"));
      elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: "
        + (top + this.buttonHeight) + "px; height: " + height + "px";
      elt.className = this.options.className;
      if (ann.id) {
        elt.setAttribute("annotation-id", ann.id);
      }
    }
    this.div.textContent = "";
    this.div.appendChild(frag);
  };

  Annotation.prototype.clear = function() {
    this.cm.off("refresh", this.resizeHandler);
    this.cm.off("markerAdded", this.resizeHandler);
    this.cm.off("markerCleared", this.resizeHandler);
    if (this.changeHandler) this.cm.off("changes", this.changeHandler);
    this.div.parentNode.removeChild(this.div);
  };
});
<commit_msg>[annotatescrollbar addon] Simplify visual-line finding<commit_after>// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == "object" && typeof module == "object") // CommonJS
    mod(require("../../lib/codemirror"));
  else if (typeof define == "function" && define.amd) // AMD
    define(["../../lib/codemirror"], mod);
  else // Plain browser env
    mod(CodeMirror);
})(function(CodeMirror) {
  "use strict";

  CodeMirror.defineExtension("annotateScrollbar", function(options) {
    if (typeof options == "string") options = {className: options};
    return new Annotation(this, options);
  });

  CodeMirror.defineOption("scrollButtonHeight", 0);

  function Annotation(cm, options) {
    this.cm = cm;
    this.options = options;
    this.buttonHeight = options.scrollButtonHeight || cm.getOption("scrollButtonHeight");
    this.annotations = [];
    this.doRedraw = this.doUpdate = null;
    this.div = cm.getWrapperElement().appendChild(document.createElement("div"));
    this.div.style.cssText = "position: absolute; right: 0; top: 0; z-index: 7; pointer-events: none";
    this.computeScale();

    function scheduleRedraw(delay) {
      clearTimeout(self.doRedraw);
      self.doRedraw = setTimeout(function() { self.redraw(); }, delay);
    }

    var self = this;
    cm.on("refresh", this.resizeHandler = function() {
      clearTimeout(self.doUpdate);
      self.doUpdate = setTimeout(function() {
        if (self.computeScale()) scheduleRedraw(20);
      }, 100);
    });
    cm.on("markerAdded", this.resizeHandler);
    cm.on("markerCleared", this.resizeHandler);
    if (options.listenForChanges !== false)
      cm.on("changes", this.changeHandler = function() {
        scheduleRedraw(250);
      });
  }

  Annotation.prototype.computeScale = function() {
    var cm = this.cm;
    var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /
      cm.getScrollerElement().scrollHeight
    if (hScale != this.hScale) {
      this.hScale = hScale;
      return true;
    }
  };

  Annotation.prototype.update = function(annotations) {
    this.annotations = annotations;
    this.redraw();
  };

  Annotation.prototype.redraw = function(compute) {
    if (compute !== false) this.computeScale();
    var cm = this.cm, hScale = this.hScale;

    var frag = document.createDocumentFragment(), anns = this.annotations;

    var wrapping = cm.getOption("lineWrapping");
    var singleLineH = wrapping && cm.defaultTextHeight() * 1.5;
    var curLine = null, curLineObj = null;

    function getY(pos, top) {
      if (curLine != pos.line) {
        curLine = pos.line
        curLineObj = cm.getLineHandle(pos.line)
        var visual = cm.getLineHandleVisualStart(curLineObj)
        if (visual != curLineObj) {
          curLine = cm.getLineNumber(visual)
          curLineObj = visual
        }
      }
      if ((curLineObj.widgets && curLineObj.widgets.length) ||
          (wrapping && curLineObj.height > singleLineH))
        return cm.charCoords(pos, "local")[top ? "top" : "bottom"];
      var topY = cm.heightAtLine(curLineObj, "local");
      return topY + (top ? 0 : curLineObj.height);
    }

    var lastLine = cm.lastLine()
    if (cm.display.barWidth) for (var i = 0, nextTop; i < anns.length; i++) {
      var ann = anns[i];
      if (ann.to.line > lastLine) continue;
      var top = nextTop || getY(ann.from, true) * hScale;
      var bottom = getY(ann.to, false) * hScale;
      while (i < anns.length - 1) {
        if (anns[i + 1].to.line > lastLine) break;
        nextTop = getY(anns[i + 1].from, true) * hScale;
        if (nextTop > bottom + .9) break;
        ann = anns[++i];
        bottom = getY(ann.to, false) * hScale;
      }
      if (bottom == top) continue;
      var height = Math.max(bottom - top, 3);

      var elt = frag.appendChild(document.createElement("div"));
      elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: "
        + (top + this.buttonHeight) + "px; height: " + height + "px";
      elt.className = this.options.className;
      if (ann.id) {
        elt.setAttribute("annotation-id", ann.id);
      }
    }
    this.div.textContent = "";
    this.div.appendChild(frag);
  };

  Annotation.prototype.clear = function() {
    this.cm.off("refresh", this.resizeHandler);
    this.cm.off("markerAdded", this.resizeHandler);
    this.cm.off("markerCleared", this.resizeHandler);
    if (this.changeHandler) this.cm.off("changes", this.changeHandler);
    this.div.parentNode.removeChild(this.div);
  };
});
<|endoftext|>


<commit_before>/*exported QuizGameController */
/*global alert */
var QuizGameController = (function(){
  'use strict';
  return [
    '$scope',
    'GameService',
    function (
      $scope,
      GameService)
    {

      $scope.const = {
        STATE: {}
      };

      $scope.const.STATE = GameService.STATE;

      $scope.data = {
        state: $scope.const.STATE.WAITING,
        player: {},
        toCheck: '',
        processing: false
      };

      $scope.data.isJoined = GameService.isJoined;
      $scope.data.isLoading = GameService.isLoading;
      $scope.data.isDead = GameService.isDead;
      $scope.data.isActive = GameService.isActive;

      $scope.fn = {};

      $scope.fn.leave = function(){
        GameService.leave();
      };

      $scope.fn.check = function(){
        $scope.data.processing = true;
        var won = false;
        $.each(GameService.activeAnswers, function(i,v){
          if(v.toLowerCase().trim() === $scope.data.toCheck.toLowerCase().trim()){
            GameService.win();
            won = true;
            return false;
          }else{
            return true;
          }
        });
        if(!won){
          alert('Try Again!');
        }
      };

      $scope.$watch(function(){return GameService.player;}, function(data){
        $scope.data.player = data;
      });

      $scope.$watch(function(){return GameService.activeQuestionNumber;}, function(newNum){
        $scope.data.qNum = newNum;
      }, true);

      $scope.$watch(function(){return GameService.activeQuestionType;}, function(newType){
        $scope.data.qType = newType;
      }, true);

      $scope.$watch(function(){return GameService.state;}, function(newState){
        $scope.data.state = newState;
        if(newState === $scope.const.STATE.ACTIVE){
          $scope.data.processing = false;
          $scope.data.toCheck = '';
        }
      });
    }
  ];
})();<commit_msg>Fixed input bug that only let players try once<commit_after>/*exported QuizGameController */
/*global alert */
var QuizGameController = (function(){
  'use strict';
  return [
    '$scope',
    'GameService',
    function (
      $scope,
      GameService)
    {

      $scope.const = {
        STATE: {}
      };

      $scope.const.STATE = GameService.STATE;

      $scope.data = {
        state: $scope.const.STATE.WAITING,
        player: {},
        toCheck: '',
        processing: false
      };

      $scope.data.isJoined = GameService.isJoined;
      $scope.data.isLoading = GameService.isLoading;
      $scope.data.isDead = GameService.isDead;
      $scope.data.isActive = GameService.isActive;

      $scope.fn = {};

      $scope.fn.leave = function(){
        GameService.leave();
      };

      $scope.fn.check = function(){
        $scope.data.processing = true;
        var won = false;
        $.each(GameService.activeAnswers, function(i,v){
          if(v.toLowerCase().trim() === $scope.data.toCheck.toLowerCase().trim()){
            GameService.win();
            won = true;
            return false;
          }else{
            return true;
          }
        });
        if(!won){
          alert('Try Again!');
          $scope.data.processing = false;
          $scope.data.toCheck = '';
        }
      };

      $scope.$watch(function(){return GameService.player;}, function(data){
        $scope.data.player = data;
      });

      $scope.$watch(function(){return GameService.activeQuestionNumber;}, function(newNum){
        $scope.data.qNum = newNum;
      }, true);

      $scope.$watch(function(){return GameService.activeQuestionType;}, function(newType){
        $scope.data.qType = newType;
      }, true);

      $scope.$watch(function(){return GameService.state;}, function(newState){
        $scope.data.state = newState;
        if(newState === $scope.const.STATE.ACTIVE){
          $scope.data.processing = false;
          $scope.data.toCheck = '';
        }
      });
    }
  ];
})();<|endoftext|>