Merge branch 'saas-dev' into saas-showbuilder

Conflicts:
	airtime_mvc/public/css/styles.css
This commit is contained in:
Duncan Sommerville 2015-07-20 12:23:11 -04:00
commit ba90b1f1eb
27 changed files with 454 additions and 50 deletions

View file

@ -30,6 +30,7 @@ require_once "Timezone.php";
require_once "Auth.php"; require_once "Auth.php";
require_once "interface/OAuth2.php"; require_once "interface/OAuth2.php";
require_once "TaskManager.php"; require_once "TaskManager.php";
require_once "UsabilityHints.php";
require_once __DIR__.'/services/CeleryService.php'; require_once __DIR__.'/services/CeleryService.php';
require_once __DIR__.'/services/SoundcloudService.php'; require_once __DIR__.'/services/SoundcloudService.php';
require_once __DIR__.'/forms/helpers/ValidationTypes.php'; require_once __DIR__.'/forms/helpers/ValidationTypes.php';

View file

@ -46,7 +46,7 @@ class Amazon_S3StorageBackend extends StorageBackend
{ {
$urls = array(); $urls = array();
$s3args = array('ResponseContentDisposition' => 'attachment; filename="' . $contentDispositionFilename. '"'); $s3args = array('ResponseContentDisposition' => 'attachment; filename="' . urlencode($contentDispositionFilename) . '"');
$signedS3Url = $this->s3Client->getObjectUrl($this->getBucket(), $resourceId, '+60 minutes', $s3args); $signedS3Url = $this->s3Client->getObjectUrl($this->getBucket(), $resourceId, '+60 minutes', $s3args);
//If we're using the proxy cache, we need to modify the request URL after it has //If we're using the proxy cache, we need to modify the request URL after it has
@ -65,7 +65,7 @@ class Amazon_S3StorageBackend extends StorageBackend
$p["path"] = substr($p["path"], 1 + strlen($this->getBucket())); $p["path"] = substr($p["path"], 1 + strlen($this->getBucket()));
} }
$proxyUrl = $p["scheme"] . "://" . $p["host"] . $p["path"] . "?" . $p["query"]; $proxyUrl = $p["scheme"] . "://" . $p["host"] . $p["path"] . "?" . $p["query"];
//Add this proxy cache URL to the list of download URLs. //Add this proxy cache URL to the list of download URLs.s
array_push($urls, $proxyUrl); array_push($urls, $proxyUrl);
} }

View file

@ -4,22 +4,23 @@ class FileDataHelper {
public static function getAudioMimeTypeArray() { public static function getAudioMimeTypeArray() {
return array( return array(
"audio/ogg" => "ogg", "audio/ogg" => "ogg",
"application/ogg" => "ogg", "application/ogg" => "ogg",
"audio/vorbis" => "ogg", "audio/vorbis" => "ogg",
"audio/mp3" => "mp3", "audio/mp3" => "mp3",
"audio/mpeg" => "mp3", "audio/mpeg" => "mp3",
"audio/mpeg3" => "mp3", "audio/mpeg3" => "mp3",
"audio/aac" => "aac", "audio/x-aac" => "aac",
"audio/aacp" => "aac", "audio/aac" => "aac",
"audio/mp4" => "mp4", "audio/aacp" => "aac",
"audio/x-flac" => "flac", "audio/mp4" => "mp4",
"audio/wav" => "wav", "audio/x-flac" => "flac",
"audio/x-wav" => "wav", "audio/wav" => "wav",
"audio/mp2" => "mp2", "audio/x-wav" => "wav",
"audio/mp1" => "mp1", "audio/mp2" => "mp2",
"audio/x-ms-wma" => "wma", "audio/mp1" => "mp1",
"audio/basic" => "au", "audio/x-ms-wma" => "wma",
"audio/basic" => "au",
); );
} }

View file

@ -0,0 +1,184 @@
<?php
class Application_Common_UsabilityHints
{
/**
* @param $userPath User's current location in Airtime (i.e. /Plupload)
* @return string
*/
public static function getUsabilityHint($userPath=null)
{
// We want to display hints in this order:
// 1. Check if files are uploaded
// 2. Check if a show is scheduled
// 3. Check if current or next show needs content
// Once the user is on the page linked to from the hint we want to
// display a new message further describing what to do. Once this
// action has been done we can hide the message and get the next
// usability hint, if there is one.
$userIsOnCalendarPage = false;
$userIsOnAddMediaPage = false;
// If $userPath is set the request came from AJAX so the user's
// current location inside Airtime gets passed in to this function.
if (!is_null($userPath)) {
// We check if the controller names are in the user's current location
// so we can ignore leading or trailing slashes, special characters like '#',
// and additional controller action names like '/user/add-user'
if (strpos(strtolower($userPath), 'plupload') !== false) {
$userIsOnAddMediaPage = true;
}
if (strpos(strtolower($userPath), 'schedule') !== false) {
$userIsOnCalendarPage = true;
}
} else {
// If $userPath is not set the request came from inside Airtime so
// we can use Zend's Front Controller to get the user's current location.
$currentController = strtolower(Zend_Controller_Front::getInstance()->getRequest()->getControllerName());
if ($currentController == "schedule") {
$userIsOnCalendarPage = true;
}
if ($currentController == "plupload") {
$userIsOnAddMediaPage = true;
}
}
if (self::zeroFilesUploaded()) {
if ($userIsOnAddMediaPage) {
return _("Click the 'Add files' button and select files from your computer to upload.");
} else {
return sprintf(_("It looks like you have not uploaded any audio files yet. %sUpload a file now.%s "),
"<a href=\"/plupload\">",
"</a>");
}
} else if (!self::isFutureOrCurrentShowScheduled()) {
if ($userIsOnCalendarPage) {
return _("Click the 'Create New Show' button and fill out the required fields.");
} else {
return sprintf(_("It looks like you don't have any shows scheduled. %sCreate a show now.%s"),
"<a href=\"/schedule\">",
"</a>");
}
} else if (self::isCurrentShowEmpty()) {
if ($userIsOnCalendarPage) {
return _("To start broadcasting, click on the current show and select 'Add / Remove Content'");
} else {
return sprintf(_("It looks like the current show needs more tracks. %sAdd tracks to your show now.%s"),
"<a href=\"/schedule\">",
"</a>");
}
} else if (!self::getCurrentShow() && self::isNextShowEmpty()) {
if ($userIsOnCalendarPage) {
return _("Click on the show starting next and select 'Add / Remove Content'");
} else {
return sprintf(_("It looks like the next show is empty. %sAdd tracks to your show now.%s"),
"<a href=\"/schedule\">",
"</a>");
}
} else {
return "";
}
}
/**
* Returns true if no files have been uploaded.
*/
private static function zeroFilesUploaded()
{
$fileCount = CcFilesQuery::create()
->filterByDbFileExists(true)
->filterByDbHidden(false)
->count();
if ($fileCount == 0) {
return true;
} else {
return false;
}
}
/**
* Returns true if there is at least one show currently scheduled
* or in the future.
*/
private static function isFutureOrCurrentShowScheduled()
{
$futureShow = self::getNextFutureShow();
$currentShow = self::getCurrentShow();
if (is_null($futureShow) && is_null($currentShow)) {
return false;
} else {
return true;
}
}
private static function isCurrentShowEmpty()
{
$currentShow = self::getCurrentShow();
if (is_null($currentShow)) {
return false;
} else {
$now = new DateTime("now", new DateTimeZone("UTC"));
$scheduledTracks = CcScheduleQuery::create()
->filterByDbInstanceId($currentShow->getDbId())
->filterByDbEnds($now, Criteria::GREATER_EQUAL)
->find();
if ($scheduledTracks->count() == 0) {
return true;
} else {
return false;
}
}
}
private static function isNextShowEmpty()
{
$futureShow = self::getNextFutureShow();
if (is_null($futureShow)) {
return false;
} else {
$now = new DateTime("now", new DateTimeZone("UTC"));
$scheduledTracks = CcScheduleQuery::create()
->filterByDbInstanceId($futureShow->getDbId())
->filterByDbStarts($now, Criteria::GREATER_EQUAL)
->find();
if ($scheduledTracks->count() == 0) {
return true;
} else {
return false;
}
}
}
private static function getCurrentShow()
{
$now = new DateTime("now", new DateTimeZone("UTC"));
return CcShowInstancesQuery::create()
->filterByDbStarts($now, Criteria::LESS_THAN)
->filterByDbEnds($now, Criteria::GREATER_THAN)
->filterByDbModifiedInstance(false)
->findOne();
}
private static function getNextFutureShow()
{
$now = new DateTime("now", new DateTimeZone("UTC"));
return CcShowInstancesQuery::create()
->filterByDbStarts($now, Criteria::GREATER_THAN)
->filterByDbModifiedInstance(false)
->orderByDbStarts()
->findOne();
}
}

View file

@ -103,6 +103,7 @@ define('PROVISIONING_STATUS_ACTIVE' , 'Active');
define("TUNEIN_API_URL", "http://air.radiotime.com/Playing.ashx"); define("TUNEIN_API_URL", "http://air.radiotime.com/Playing.ashx");
// SoundCloud // SoundCloud
define('SOUNDCLOUD', 'SoundCloud');
define('DEFAULT_SOUNDCLOUD_LICENSE_TYPE', 'all-rights-reserved'); define('DEFAULT_SOUNDCLOUD_LICENSE_TYPE', 'all-rights-reserved');
define('DEFAULT_SOUNDCLOUD_SHARING_TYPE', 'public'); define('DEFAULT_SOUNDCLOUD_SHARING_TYPE', 'public');

View file

@ -64,6 +64,7 @@ class ApiController extends Zend_Controller_Action
->addActionContext('update-stream-setting-table' , 'json') ->addActionContext('update-stream-setting-table' , 'json')
->addActionContext('update-replay-gain-value' , 'json') ->addActionContext('update-replay-gain-value' , 'json')
->addActionContext('update-cue-values-by-silan' , 'json') ->addActionContext('update-cue-values-by-silan' , 'json')
->addActionContext('get-usability-hint' , 'json')
->initContext(); ->initContext();
} }
@ -1466,5 +1467,13 @@ class ApiController extends Zend_Controller_Action
} }
$this->_helper->json->sendJson(array(1)); $this->_helper->json->sendJson(array(1));
} }
public function getUsabilityHintAction()
{
$userPath = $this->_getParam("userPath");
$hint = Application_Common_UsabilityHints::getUsabilityHint($userPath);
$this->_helper->json->sendJson($hint);
}
} }

View file

@ -17,8 +17,6 @@ class LibraryController extends Zend_Controller_Action
->addActionContext('delete-group', 'json') ->addActionContext('delete-group', 'json')
->addActionContext('context-menu', 'json') ->addActionContext('context-menu', 'json')
->addActionContext('get-file-metadata', 'html') ->addActionContext('get-file-metadata', 'html')
->addActionContext('upload-file-soundcloud', 'json')
->addActionContext('get-upload-to-soundcloud-status', 'json')
->addActionContext('set-num-entries', 'json') ->addActionContext('set-num-entries', 'json')
->addActionContext('edit-file-md', 'json') ->addActionContext('edit-file-md', 'json')
->initContext(); ->initContext();
@ -276,7 +274,7 @@ class LibraryController extends Zend_Controller_Action
$menu["sep1"] = "-----------"; $menu["sep1"] = "-----------";
//create a sub menu for Soundcloud actions. //create a sub menu for Soundcloud actions.
$menu["soundcloud"] = array("name" => _("Soundcloud"), "icon" => "soundcloud", "items" => array()); $menu["soundcloud"] = array("name" => _(SOUNDCLOUD), "icon" => "soundcloud", "items" => array());
$serviceId = $soundcloudService->getServiceId($id); $serviceId = $soundcloudService->getServiceId($id);
if (!is_null($file) && $serviceId != 0) { if (!is_null($file) && $serviceId != 0) {

View file

@ -98,6 +98,9 @@ class ScheduleController extends Zend_Controller_Action
$this->view->headScript()->appendFile($baseUrl.'js/datatables/plugin/dataTables.FixedColumns.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $this->view->headScript()->appendFile($baseUrl.'js/datatables/plugin/dataTables.FixedColumns.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'js/datatables/plugin/dataTables.columnFilter.js?'.$CC_CONFIG['airtime_version'], 'text/javascript'); $this->view->headScript()->appendFile($baseUrl.'js/datatables/plugin/dataTables.columnFilter.js?'.$CC_CONFIG['airtime_version'], 'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'js/libs/moment.min.js?'.$CC_CONFIG['airtime_version'], 'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'js/libs/moment-timezone-with-data-2010-2020.min.js?'.$CC_CONFIG['airtime_version'], 'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'js/airtime/buttons/buttons.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $this->view->headScript()->appendFile($baseUrl.'js/airtime/buttons/buttons.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'js/airtime/library/events/library_showbuilder.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $this->view->headScript()->appendFile($baseUrl.'js/airtime/library/events/library_showbuilder.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$this->view->headScript()->appendFile($baseUrl.'js/airtime/library/library.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $this->view->headScript()->appendFile($baseUrl.'js/airtime/library/library.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
@ -584,7 +587,20 @@ class ScheduleController extends Zend_Controller_Action
$forms = $this->createShowFormAction(); $forms = $this->createShowFormAction();
$this->view->addNewShow = true; $this->view->addNewShow = true;
if ($data['add_show_start_now'] == "now") {
//have to use the timezone the user has entered in the form to check past/present
$showTimezone = new DateTimeZone($data["add_show_timezone"]);
$nowDateTime = new DateTime("now", $showTimezone);
//$showStartDateTime = new DateTime($start_time, $showTimezone);
//$showEndDateTime = new DateTime($end_time, $showTimezone);
$data['add_show_start_time'] = $nowDateTime->format("H:i");
$data['add_show_start_date'] = $nowDateTime->format("Y-m-d");
}
if ($service_showForm->validateShowForms($forms, $data)) { if ($service_showForm->validateShowForms($forms, $data)) {
// Get the show ID from the show service to pass as a parameter to the RESTful ShowImageController // Get the show ID from the show service to pass as a parameter to the RESTful ShowImageController
$this->view->showId = $service_show->addUpdateShow($data); $this->view->showId = $service_show->addUpdateShow($data);

View file

@ -15,11 +15,26 @@ class Application_Form_AddShowWhen extends Zend_Form_SubForm
"/^[0-2]?[0-9]:[0-5][0-9]$/", "/^[0-2]?[0-9]:[0-5][0-9]$/",
_("'%value%' does not fit the time format 'HH:mm'")); _("'%value%' does not fit the time format 'HH:mm'"));
// Add start date element
$startNow = new Zend_Form_Element_Radio('add_show_start_now');
$startNow->setRequired(false)
->setLabel(_('Start Time:'))
->addMultiOptions(array(
'now' => 'Now',
'future' => 'In the Future:'
))
->setValue('future')
->setDecorators(array('ViewHelper'));
//$startDate->setAttrib('alt', 'date');
$this->addElement($startNow);
// Add start date element // Add start date element
$startDate = new Zend_Form_Element_Text('add_show_start_date'); $startDate = new Zend_Form_Element_Text('add_show_start_date');
$startDate->class = 'input_text'; $startDate->class = 'input_text';
$startDate->setRequired(true) $startDate->setRequired(true)
->setLabel(_('Date/Time Start:')) ->setLabel(_('In the Future:'))
->setValue(date("Y-m-d")) ->setValue(date("Y-m-d"))
->setFilters(array('StringTrim')) ->setFilters(array('StringTrim'))
->setValidators(array( ->setValidators(array(
@ -46,7 +61,7 @@ class Application_Form_AddShowWhen extends Zend_Form_SubForm
$endDate = new Zend_Form_Element_Text('add_show_end_date_no_repeat'); $endDate = new Zend_Form_Element_Text('add_show_end_date_no_repeat');
$endDate->class = 'input_text'; $endDate->class = 'input_text';
$endDate->setRequired(true) $endDate->setRequired(true)
->setLabel(_('Date/Time End:')) ->setLabel(_('End Time:'))
->setValue(date("Y-m-d")) ->setValue(date("Y-m-d"))
->setFilters(array('StringTrim')) ->setFilters(array('StringTrim'))
->setValidators(array( ->setValidators(array(
@ -119,7 +134,7 @@ class Application_Form_AddShowWhen extends Zend_Form_SubForm
$showStartDateTime = new DateTime($start_time, $showTimezone); $showStartDateTime = new DateTime($start_time, $showTimezone);
$showEndDateTime = new DateTime($end_time, $showTimezone); $showEndDateTime = new DateTime($end_time, $showTimezone);
if ($validateStartDate) { if ($validateStartDate && ($formData['add_show_start_now'] != "now")) {
if ($showStartDateTime < $nowDateTime) { if ($showStartDateTime < $nowDateTime) {
$this->getElement('add_show_start_time')->setErrors(array(_('Cannot create show in the past'))); $this->getElement('add_show_start_time')->setErrors(array(_('Cannot create show in the past')));
$valid = false; $valid = false;

View file

@ -70,7 +70,13 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
</div> </div>
<div class="wrapper" id="content"><?php echo $this->layout()->content ?></div> <div class="wrapper" id="content">
<?php
$hint = Application_Common_UsabilityHints::getUsabilityHint(); ?>
<div class="usability_hint" <?php if ($hint == "") { echo "style='display:none'"; } ?>><?php echo $hint; ?></div>
<?php echo $this->layout()->content ?></div>
<script id="tmpl-pl-cues" type="text/template"> <script id="tmpl-pl-cues" type="text/template">
<div class="waveform-cues"> <div class="waveform-cues">

View file

@ -169,6 +169,9 @@ class Application_Service_ShowFormService
} }
} }
//Disable starting a show 'now' when editing an existing show.
$form->getElement('add_show_start_now')->setAttrib('disable', array('now'));
$form->populate( $form->populate(
array( array(
'add_show_start_date' => $showStart->format("Y-m-d"), 'add_show_start_date' => $showStart->format("Y-m-d"),
@ -225,8 +228,12 @@ class Application_Service_ShowFormService
$form->disableStartDateAndTime(); $form->disableStartDateAndTime();
} }
//Disable starting a show 'now' when editing an existing show.
$form->getElement('add_show_start_now')->setAttrib('disable', array('now'));
$form->populate( $form->populate(
array( array(
'add_show_start_now' => 'future',
'add_show_start_date' => $showStart->format("Y-m-d"), 'add_show_start_date' => $showStart->format("Y-m-d"),
'add_show_start_time' => $showStart->format("H:i"), 'add_show_start_time' => $showStart->format("H:i"),
'add_show_end_date_no_repeat' => $showEnd->format("Y-m-d"), 'add_show_end_date_no_repeat' => $showEnd->format("Y-m-d"),

View file

@ -1,8 +1,18 @@
<fieldset> <fieldset>
<dl> <dl>
<dt id="add_show_start_now-label">
<label for="add_show_start_now">
<?php echo $this->element->getElement('add_show_start_now')->getLabel()?>
</label>
</dt>
<dd>
<?php echo $this->element->getElement('add_show_start_now') ?>
</dd>
<dt id="add_show_start_date-label"> <dt id="add_show_start_date-label">
<label for="add_show_start_date" class="required"> <label for="add_show_start_date" class="required">
<?php echo $this->element->getElement('add_show_start_date')->getLabel()?> <?php //echo $this->element->getElement('add_show_start_date')->getLabel()?>
</label> </label>
</dt> </dt>
<dd> <dd>
@ -19,6 +29,9 @@
<?php endforeach; ?> <?php endforeach; ?>
</ul> </ul>
<?php } ?> <?php } ?>
<dt id="add_show_end_date_no_repeat-label"> <dt id="add_show_end_date_no_repeat-label">
<label for="add_show_end_date_no_repeat" class="required"> <label for="add_show_end_date_no_repeat" class="required">
<?php echo $this->element->getElement('add_show_end_date_no_repeat')->getLabel()?> <?php echo $this->element->getElement('add_show_end_date_no_repeat')->getLabel()?>

View file

@ -8,6 +8,7 @@
<table id="library_display" cellpadding="0" cellspacing="0" class="datatable"></table> <table id="library_display" cellpadding="0" cellspacing="0" class="datatable"></table>
</div> </div>
<div id="show_builder" class="sb-content ui-widget ui-widget-content block-shadow omega-block padded"> <div id="show_builder" class="sb-content ui-widget ui-widget-content block-shadow omega-block padded">
<div id="builder-dialog-hint"><p><?php echo _("Drag files from the library on the left to add them to your show."); ?></p></div>
<table id="show_builder_table" cellpadding="0" cellspacing="0" class="datatable"></table> <table id="show_builder_table" cellpadding="0" cellspacing="0" class="datatable"></table>
</div> </div>
</div> </div>

View file

@ -567,6 +567,22 @@ input[type="text"]:focus, input[type="password"]:focus, textarea:focus, .input_t
min-width: 470px;*/ min-width: 470px;*/
} }
#builder-dialog-hint {
text-align: center;
/*line-height: 62px;*/
height: 62px;
display: table;
width: 100%;
}
#builder-dialog-hint p
{
font-size: 14px;
display: table-cell;
text-align: center;
vertical-align: middle;
}
/***** LIBRARY QTIP METADATA SPECIFIC STYLES BEGIN *****/ /***** LIBRARY QTIP METADATA SPECIFIC STYLES BEGIN *****/
table.library-track-md{ table.library-track-md{
width: 280px; width: 280px;
@ -3369,4 +3385,23 @@ dd .stream-status {
font-size: 20px; font-size: 20px;
font-weight: 300; font-weight: 300;
line-height: 1.4rem; line-height: 1.4rem;
} }
/* Usability Hints */
.usability_hint {
padding: 5px 10px 5px 10px;
margin-bottom: 10px;
border: 1px solid #ff611f;
background-color: #ff611f;
color: white;
font-size: 14px;
}
.usability_hint a {
color: white;
}
.calendar-context-menu {
min-width: 200px !important;
}

View file

@ -161,3 +161,28 @@ function removeSuccessMsg() {
$status.fadeOut("slow", function(){$status.empty()}); $status.fadeOut("slow", function(){$status.empty()});
} }
function getUsabilityHint() {
var pathname = window.location.pathname;
$.getJSON("/api/get-usability-hint", {"format": "json", "userPath": pathname}, function(json) {
var $hint_div = $('.usability_hint');
var current_hint = $hint_div.html();
if (json === "") {
// there are no more hints to display to the user
$hint_div.hide();
} else if (current_hint !== json) {
// we only change the message if it is new
if ($hint_div.is(":visible")) {
$hint_div.hide();
}
$hint_div.html(json);
$hint_div.show("slow");
} else {
// hint is the same before we hid it so we just need to show it
if ($hint_div.is(":hidden")) {
$hint_div.show();
}
}
});
}

View file

@ -603,7 +603,9 @@ var AIRTIME = (function(AIRTIME) {
type = $("#library_display_type").find("select").val(); type = $("#library_display_type").find("select").val();
type = (type === undefined) ? 0 : type; type = (type === undefined) ? 0 : type;
aoData.push( { name: "type", value: type} ); aoData.push( { name: "type", value: type} );
getUsabilityHint();
$.ajax( { $.ajax( {
"dataType": 'json', "dataType": 'json',
"type": "POST", "type": "POST",

View file

@ -22,7 +22,7 @@ $(document).ready(function() {
unique_names : 'true', unique_names : 'true',
multiple_queues : 'true', multiple_queues : 'true',
filters : [ filters : [
{title: "Audio Files", extensions: "ogg,mp3,oga,flac,wav,m4a,mp4,opus"} {title: "Audio Files", extensions: "ogg,mp3,oga,flac,wav,m4a,mp4,opus,aac,oga,mp1,mp2,wma,au"}
], ],
multipart_params : { multipart_params : {
"csrf_token" : $("#csrf").attr('value'), "csrf_token" : $("#csrf").attr('value'),
@ -146,6 +146,9 @@ $(document).ready(function() {
} else { } else {
self.stopRefreshingRecentUploads(); self.stopRefreshingRecentUploads();
} }
// Update usability hint - in common.js
getUsabilityHint();
} }
} ); } );
} }

View file

@ -4,24 +4,18 @@
* *
*/ */
function openAddShowForm() { function openAddShowForm(nowOrFuture) {
if($("#add-show-form").length == 1) { if($("#add-show-form").length == 1) {
if( ($("#add-show-form").css('display')=='none')) { if( ($("#add-show-form").css('display')=='none')) {
$("#add-show-form").show();
/*
var windowWidth = $(window).width();
// margin on showform are 16 px on each side
var calendarWidth = 100-(($("#schedule-add-show").width() + (16 * 4))/windowWidth*100);
var widthPercent = parseInt(calendarWidth)+"%";
$("#schedule_calendar").css("width", widthPercent);
// 200 px for top dashboard and 50 for padding on main content if (nowOrFuture === true) //true means "now"
// this calculation was copied from schedule.js line 326 {
var mainHeight = document.documentElement.clientHeight - 200 - 50; $('#add_show_start_now-now').attr('checked', 'checked');
$('#schedule_calendar').fullCalendar('option', 'contentHeight', mainHeight); setupStartTimeWidgets();
*/ }
windowResize(); $("#add-show-form").show();
windowResize();
} }
$("#schedule-show-what").show(0, function(){ $("#schedule-show-what").show(0, function(){
$add_show_name = $("#add_show_name"); $add_show_name = $("#add_show_name");
@ -44,7 +38,7 @@ function makeAddShowButton() {
} }
function showForm() { function showForm() {
openAddShowForm(); openAddShowForm(true);
toggleAddShowButton(); toggleAddShowButton();
} }
@ -53,6 +47,30 @@ function toggleAddShowButton(){
aTag.prop('disabled', function(i, v) { return !v; }); aTag.prop('disabled', function(i, v) { return !v; });
} }
function setupStartTimeWidgets() {
if ($('input[name=add_show_start_now]:checked').val() == 'now') {
$('#add_show_start_date').prop('disabled', 'true');
$('#add_show_start_time').prop('disabled', 'true');
var currentTimezone = $("#add_show_timezone").val();
//Set the show start time to now (in the show timezone)
var now = moment(new Date()).tz(currentTimezone);
$('#add_show_start_date').val(now.format('YYYY-MM-DD'));
$('#add_show_start_time').val(now.format('HH:mm'));
//Set the show end time to be now + 1 hour.
var nowShowEnd = now.add(1, 'h');
$('#add_show_end_date').val(nowShowEnd.format('YYYY-MM-DD'));
$('#add_show_end_date_no_repeat').val(nowShowEnd.format('YYYY-MM-DD'));
$('#add_show_end_time').val(nowShowEnd.format('HH:mm'));
} else {
$('#add_show_start_date').removeProp('disabled');
$('#add_show_start_time').removeProp('disabled');
}
}
//$el is DOM element #add-show-form //$el is DOM element #add-show-form
//form is the new form contents to append to $el //form is the new form contents to append to $el
function redrawAddShowForm($el, form) { function redrawAddShowForm($el, form) {
@ -165,7 +183,7 @@ function beginEditShow(data){
redrawAddShowForm($("#add-show-form"), data.newForm); redrawAddShowForm($("#add-show-form"), data.newForm);
toggleAddShowButton(); toggleAddShowButton();
openAddShowForm(); openAddShowForm(false);
} }
function onStartTimeSelect(){ function onStartTimeSelect(){
@ -220,6 +238,12 @@ function setAddShowEvents(form) {
$(this).next().toggle(); $(this).next().toggle();
}); });
form.find('input:radio[name=add_show_start_now]').click(function() {
setupStartTimeWidgets();
});
if(!form.find("#add_show_repeats").attr('checked')) { if(!form.find("#add_show_repeats").attr('checked')) {
form.find("#schedule-show-when > fieldset:last").hide(); form.find("#schedule-show-when > fieldset:last").hide();
$("#add_show_rebroadcast_relative").hide(); $("#add_show_rebroadcast_relative").hide();

View file

@ -99,6 +99,11 @@ function dayClick(date, allDay, jsEvent, view){
chosenDate = selected.getFullYear() + '-' + pad(selected.getMonth()+1,2) + '-' + pad(selected.getDate(),2); chosenDate = selected.getFullYear() + '-' + pad(selected.getMonth()+1,2) + '-' + pad(selected.getDate(),2);
var endDateFormat = endDateTime.getFullYear() + '-' + pad(endDateTime.getMonth()+1,2) + '-' + pad(endDateTime.getDate(),2); var endDateFormat = endDateTime.getFullYear() + '-' + pad(endDateTime.getMonth()+1,2) + '-' + pad(endDateTime.getDate(),2);
//TODO: This should all be refactored into a proper initialize() function for the show form.
$("#add_show_start_now-future").attr('checked', 'checked');
$("#add_show_start_now-now").removeProp('disabled');
setupStartTimeWidgets(); //add-show.js
$("#add_show_start_date").val(chosenDate); $("#add_show_start_date").val(chosenDate);
$("#add_show_end_date_no_repeat").val(endDateFormat); $("#add_show_end_date_no_repeat").val(endDateFormat);
$("#add_show_end_date").val(endDateFormat); $("#add_show_end_date").val(endDateFormat);
@ -356,6 +361,7 @@ function getFullCalendarEvents(start, end, callback) {
var d = new Date(); var d = new Date();
$.post(url, {format: "json", start: start_date, end: end_date, cachep: d.getTime()}, function(json){ $.post(url, {format: "json", start: start_date, end: end_date, cachep: d.getTime()}, function(json){
callback(json.events); callback(json.events);
getUsabilityHint();
}); });
} }
} }

View file

@ -154,6 +154,7 @@ function buildScheduleDialog (json, instance_id) {
"class": "btn", "class": "btn",
click: function() { click: function() {
$(this).dialog("close"); $(this).dialog("close");
//getUsabilityHint();
} }
} }
] ]
@ -318,12 +319,14 @@ function alertShowErrorAndReload(){
} }
$(document).ready(function() { $(document).ready(function() {
checkCalendarSCUploadStatus(); checkCalendarSCUploadStatus();
$.contextMenu({ $.contextMenu({
selector: 'div.fc-event', selector: 'div.fc-event',
trigger: "left", trigger: "left",
ignoreRightClick: true, ignoreRightClick: true,
className: 'calendar-context-menu',
build: function($el, e) { build: function($el, e) {
var data, var data,
@ -481,6 +484,7 @@ $(document).ready(function() {
}); });
return { return {
className: 'calendar-context-menu',
items: items, items: items,
determinePosition : function($menu, x, y) { determinePosition : function($menu, x, y) {
$menu.css('display', 'block') $menu.css('display', 'block')

View file

@ -283,6 +283,8 @@ var AIRTIME = (function(AIRTIME){
mod.enableUI(); mod.enableUI();
//Unneccessary reload of the library pane after moving tracks in the showbuilder pane. //Unneccessary reload of the library pane after moving tracks in the showbuilder pane.
//$("#library_content").find("#library_display").dataTable().fnStandingRedraw(); //$("#library_content").find("#library_display").dataTable().fnStandingRedraw();
getUsabilityHint();
}; };
mod.getSelectedCursors = function() { mod.getSelectedCursors = function() {
@ -316,7 +318,7 @@ var AIRTIME = (function(AIRTIME){
mod.disableUI(); mod.disableUI();
$.post(baseUrl+"showbuilder/schedule-add", $.post(baseUrl+"showbuilder/schedule-add",
{"format": "json", "mediaIds": aMediaIds, "schedIds": aSchedIds}, {"format": "json", "mediaIds": aMediaIds, "schedIds": aSchedIds},
mod.fnItemCallback mod.fnItemCallback
); );
}; };

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,6 @@
//! moment-timezone.js
//! version : 0.4.0
//! author : Tim Wood
//! license : MIT
//! github.com/moment/moment-timezone
!function(a,b){"use strict";"function"==typeof define&&define.amd?define(["moment"],b):"object"==typeof exports?module.exports=b(require("moment")):b(a.moment)}(this,function(a){"use strict";function b(a){return a>96?a-87:a>64?a-29:a-48}function c(a){var c,d=0,e=a.split("."),f=e[0],g=e[1]||"",h=1,i=0,j=1;for(45===a.charCodeAt(0)&&(d=1,j=-1),d;d<f.length;d++)c=b(f.charCodeAt(d)),i=60*i+c;for(d=0;d<g.length;d++)h/=60,c=b(g.charCodeAt(d)),i+=c*h;return i*j}function d(a){for(var b=0;b<a.length;b++)a[b]=c(a[b])}function e(a,b){for(var c=0;b>c;c++)a[c]=Math.round((a[c-1]||0)+6e4*a[c]);a[b-1]=1/0}function f(a,b){var c,d=[];for(c=0;c<b.length;c++)d[c]=a[b[c]];return d}function g(a){var b=a.split("|"),c=b[2].split(" "),g=b[3].split(""),h=b[4].split(" ");return d(c),d(g),d(h),e(h,g.length),{name:b[0],abbrs:f(b[1].split(" "),g),offsets:f(c,g),untils:h}}function h(a){a&&this._set(g(a))}function i(a){return(a||"").toLowerCase().replace(/\//g,"_")}function j(a){var b,c,d;for("string"==typeof a&&(a=[a]),b=0;b<a.length;b++)c=a[b].split("|")[0],d=i(c),v[d]=a[b],x[d]=c}function k(a,b){a=i(a);var c,d=v[a];return d instanceof h?d:"string"==typeof d?(d=new h(d),v[a]=d,d):w[a]&&b!==k&&(c=k(w[a],k))?(d=v[a]=new h,d._set(c),d.name=x[a],d):null}function l(){var a,b=[];for(a in x)x.hasOwnProperty(a)&&(v[a]||v[w[a]])&&x[a]&&b.push(x[a]);return b.sort()}function m(a){var b,c,d,e;for("string"==typeof a&&(a=[a]),b=0;b<a.length;b++)c=a[b].split("|"),d=i(c[0]),e=i(c[1]),w[d]=e,x[d]=c[0],w[e]=d,x[e]=c[1]}function n(a){j(a.zones),m(a.links),r.dataVersion=a.version}function o(a){return o.didShowError||(o.didShowError=!0,q("moment.tz.zoneExists('"+a+"') has been deprecated in favor of !moment.tz.zone('"+a+"')")),!!k(a)}function p(a){return!(!a._a||void 0!==a._tzm)}function q(a){"undefined"!=typeof console&&"function"==typeof console.error&&console.error(a)}function r(b){var c=Array.prototype.slice.call(arguments,0,-1),d=arguments[arguments.length-1],e=k(d),f=a.utc.apply(null,c);return e&&!a.isMoment(b)&&p(f)&&f.add(e.parse(f),"minutes"),f.tz(d),f}function s(a){return function(){return this._z?this._z.abbr(this):a.call(this)}}function t(a){return function(){return this._z=null,a.apply(this,arguments)}}if(void 0!==a.tz)return q("Moment Timezone "+a.tz.version+" was already loaded "+(a.tz.dataVersion?"with data from ":"without any data")+a.tz.dataVersion),a;var u="0.4.0",v={},w={},x={},y=a.version.split("."),z=+y[0],A=+y[1];(2>z||2===z&&6>A)&&q("Moment Timezone requires Moment.js >= 2.6.0. You are using Moment.js "+a.version+". See momentjs.com"),h.prototype={_set:function(a){this.name=a.name,this.abbrs=a.abbrs,this.untils=a.untils,this.offsets=a.offsets},_index:function(a){var b,c=+a,d=this.untils;for(b=0;b<d.length;b++)if(c<d[b])return b},parse:function(a){var b,c,d,e,f=+a,g=this.offsets,h=this.untils,i=h.length-1;for(e=0;i>e;e++)if(b=g[e],c=g[e+1],d=g[e?e-1:e],c>b&&r.moveAmbiguousForward?b=c:b>d&&r.moveInvalidForward&&(b=d),f<h[e]-6e4*b)return g[e];return g[i]},abbr:function(a){return this.abbrs[this._index(a)]},offset:function(a){return this.offsets[this._index(a)]}},r.version=u,r.dataVersion="",r._zones=v,r._links=w,r._names=x,r.add=j,r.link=m,r.load=n,r.zone=k,r.zoneExists=o,r.names=l,r.Zone=h,r.unpack=g,r.unpackBase60=c,r.needsOffset=p,r.moveInvalidForward=!0,r.moveAmbiguousForward=!1;var B=a.fn;a.tz=r,a.defaultZone=null,a.updateOffset=function(b,c){var d,e=a.defaultZone;void 0===b._z&&(e&&p(b)&&!b._isUTC&&(b._d=a.utc(b._a)._d,b.utc().add(e.parse(b),"minutes")),b._z=e),b._z&&(d=b._z.offset(b),Math.abs(d)<16&&(d/=60),void 0!==b.utcOffset?b.utcOffset(-d,c):b.zone(d,c))},B.tz=function(b){return b?(this._z=k(b),this._z?a.updateOffset(this):q("Moment Timezone has no data for "+b+". See http://momentjs.com/timezone/docs/#/data-loading/."),this):this._z?this._z.name:void 0},B.zoneName=s(B.zoneName),B.zoneAbbr=s(B.zoneAbbr),B.utc=t(B.utc),a.tz.setDefault=function(b){return(2>z||2===z&&9>A)&&q("Moment Timezone setDefault() requires Moment.js >= 2.9.0. You are using Moment.js "+a.version+"."),a.defaultZone=b?k(b):null,a};var C=a.momentProperties;return"[object Array]"===Object.prototype.toString.call(C)?(C.push("_z"),C.push("_a")):C&&(C._z=null),a});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,17 @@
audio/ogg ogg
application/ogg ogg
audio/vorbis ogg
audio/mp3 mp3
audio/mpeg mp3
audio/mpeg3 mp3
audio/x-aac aac
audio/aac aac
audio/aacp aac
audio/mp4 mp4
audio/x-flac flac
audio/wav wav
audio/x-wav wav
audio/mp2 mp2
audio/mp1 mp1
audio/x-ms-wma wma
audio/basic au

View file

@ -12,7 +12,7 @@ import signal
from datetime import datetime from datetime import datetime
import traceback import traceback
import pure import pure
import mimetypes
from Queue import Empty from Queue import Empty
from threading import Thread, Timer from threading import Thread, Timer
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
@ -377,6 +377,21 @@ class PypoFetch(Thread):
start = datetime.strptime(media_item['start'], "%Y-%m-%d-%H-%M-%S") start = datetime.strptime(media_item['start'], "%Y-%m-%d-%H-%M-%S")
end = datetime.strptime(media_item['end'], "%Y-%m-%d-%H-%M-%S") end = datetime.strptime(media_item['end'], "%Y-%m-%d-%H-%M-%S")
root, ext = os.path.splitext(media_item['uri'])
mime = media_item['metadata']['mime']
mimetypes.init()
mime_ext = mimetypes.guess_extension(mime, strict=False)
if mime_ext is None:
mimes = mimetypes.read_mime_types("%s/mime.types" % os.path.dirname(os.path.realpath(__file__)))
for k, v in mimes.iteritems():
if v == mime:
mime_ext = k
if mime_ext is not None and mime_ext != ext:
self.logger.info("Invalid extension %s for file %s, changing to %s" % (ext, root, mime_ext))
media_item['uri'] = root + mime_ext
length1 = pure.date_interval_to_seconds(end - start) length1 = pure.date_interval_to_seconds(end - start)
length2 = media_item['cue_out'] - media_item['cue_in'] length2 = media_item['cue_out'] - media_item['cue_in']

View file

@ -39,7 +39,7 @@ setup(name='airtime-playout',
license='AGPLv3', license='AGPLv3',
packages=['pypo', 'pypo.media', 'pypo.media.update', packages=['pypo', 'pypo.media', 'pypo.media.update',
'liquidsoap', 'liquidsoap.library'], 'liquidsoap', 'liquidsoap.library'],
package_data={'': ['*.liq', '*.cfg']}, package_data={'': ['*.liq', '*.cfg', '*.types']},
scripts=[ scripts=[
'bin/airtime-playout', 'bin/airtime-playout',
'bin/airtime-liquidsoap', 'bin/airtime-liquidsoap',