Merge branch 'devel' of dev.sourcefabric.org:airtime into devel

This commit is contained in:
James 2012-12-28 10:59:03 -05:00
commit 6e77f2637a
41 changed files with 4530 additions and 23 deletions

21
CREDITS
View File

@ -1,6 +1,27 @@
======= =======
CREDITS CREDITS
======= =======
Version 2.2.1
-------------
Martin Konecny (martin.konecny@sourcefabric.org)
Role: Developer Team Lead
James Moon (james.moon@sourcefabric.org)
Role: Software Developer
Denise Rigato (denise.rigato@sourcefabric.org)
Role: Software Developer
Cliff Wang (cliff.wang@sourcefabric.org)
Role: QA
Mikayel Karapetian (michael.karapetian@sourcefabric.org)
Role: QA
Daniel James (daniel.james@sourcefabric.org)
Role: Documentor & QA
Version 2.2.0 Version 2.2.0
------------- -------------
Martin Konecny (martin.konecny@sourcefabric.org) Martin Konecny (martin.konecny@sourcefabric.org)

2
README
View File

@ -8,7 +8,7 @@ Home page: http://airtime.sourcefabric.org/
Major features: Major features:
* Web-based remote station management. Authorized personnel can add * Web-based remote station management. Authorized personnel can add
program material, create playlists, and schedule programming all via program material, create playlists and schedule programming all via
a web interface. a web interface.
* Automation. Airtime has a scheduler function that enables users to * Automation. Airtime has a scheduler function that enables users to
set shows with playlists for playback at a date and time of their choosing. set shows with playlists for playback at a date and time of their choosing.

View File

@ -88,6 +88,7 @@ class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
$view->headLink()->appendStylesheet($baseUrl.'/css/styles.css?'.$CC_CONFIG['airtime_version']); $view->headLink()->appendStylesheet($baseUrl.'/css/styles.css?'.$CC_CONFIG['airtime_version']);
$view->headLink()->appendStylesheet($baseUrl.'/css/masterpanel.css?'.$CC_CONFIG['airtime_version']); $view->headLink()->appendStylesheet($baseUrl.'/css/masterpanel.css?'.$CC_CONFIG['airtime_version']);
$view->headLink()->appendStylesheet($baseUrl.'/css/bootstrap.css?'.$CC_CONFIG['airtime_version']); $view->headLink()->appendStylesheet($baseUrl.'/css/bootstrap.css?'.$CC_CONFIG['airtime_version']);
$view->headLink()->appendStylesheet($baseUrl.'/css/tipsy/jquery.tipsy.css?'.$CC_CONFIG['airtime_version']);
} }
protected function _initHeadScript() protected function _initHeadScript()
@ -116,7 +117,7 @@ class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
$view->headScript()->appendFile($baseUrl.'/js/airtime/dashboard/helperfunctions.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $view->headScript()->appendFile($baseUrl.'/js/airtime/dashboard/helperfunctions.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$view->headScript()->appendFile($baseUrl.'/js/airtime/dashboard/dashboard.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $view->headScript()->appendFile($baseUrl.'/js/airtime/dashboard/dashboard.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$view->headScript()->appendFile($baseUrl.'/js/airtime/dashboard/versiontooltip.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $view->headScript()->appendFile($baseUrl.'/js/airtime/dashboard/versiontooltip.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$view->headScript()->appendFile($baseUrl.'/js/tipsy/jquery.tipsy.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$view->headScript()->appendFile($baseUrl.'/js/airtime/common/common.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $view->headScript()->appendFile($baseUrl.'/js/airtime/common/common.js?'.$CC_CONFIG['airtime_version'],'text/javascript');
$view->headScript()->appendFile($baseUrl.'/js/airtime/common/audioplaytest.js?'.$CC_CONFIG['airtime_version'],'text/javascript'); $view->headScript()->appendFile($baseUrl.'/js/airtime/common/audioplaytest.js?'.$CC_CONFIG['airtime_version'],'text/javascript');

View File

@ -33,6 +33,7 @@ $ccAcl->add(new Zend_Acl_Resource('library'))
$ccAcl->allow('G', 'index') $ccAcl->allow('G', 'index')
->allow('G', 'login') ->allow('G', 'login')
->allow('G', 'error') ->allow('G', 'error')
->allow('G', 'user', 'edit-user')
->allow('G', 'showbuilder') ->allow('G', 'showbuilder')
->allow('G', 'api') ->allow('G', 'api')
->allow('G', 'schedule') ->allow('G', 'schedule')

View File

@ -307,6 +307,26 @@ class LocaleController extends Zend_Controller_Action
"DJ" => _("DJ"), "DJ" => _("DJ"),
"Program Manager" => _("Program Manager"), "Program Manager" => _("Program Manager"),
"Guest" => _("Guest"), "Guest" => _("Guest"),
"Guests can do the following:" => _("Guests can do the following:"),
"View schedule" => _("View schedule"),
"View show content" => _("View show content"),
"DJs can do the following:" => _("DJs can do the following:"),
"Manage assigned show content" => _("Manage assigned show content"),
"Import media files" => _("Import media files"),
"Create playlists, smart blocks, and webstreams" => _("Create playlists, smart blocks, and webstreams"),
"Manage their own library content" => _("Manage their own library content"),
"Progam Managers can do the following:" => _("Progam Managers can do the following:"),
"View and manage show content" => _("View and manage show content"),
"Schedule shows" => _("Schedule shows"),
"Manage all library content" => _("Manage all library content"),
"Admins can do the following:" => _("Admins can do the following:"),
"Manage preferences" => _("Manage preferences"),
"Manage users" => _("Manage users"),
"Manage watched folders" => _("Manage watched folders"),
"Send support feedback" => _("Send support feedback"),
"View system status" => _("View system status"),
"Access playout history" => _("Access playout history"),
"View listener stats" => _("View listener stats"),
//dataTables/ColVis.js //dataTables/ColVis.js
"Show / hide columns" => _("Show / hide columns"), "Show / hide columns" => _("Show / hide columns"),
//datatables.columnFilter.js //datatables.columnFilter.js

View File

@ -255,6 +255,7 @@ class PreferenceController extends Zend_Controller_Action
Application_Model_Preference::SetDefaultTransitionFade($values["transition_fade"]); Application_Model_Preference::SetDefaultTransitionFade($values["transition_fade"]);
Application_Model_Preference::SetAutoTransition($values["auto_transition"]); Application_Model_Preference::SetAutoTransition($values["auto_transition"]);
Application_Model_Preference::SetAutoSwitch($values["auto_switch"]); Application_Model_Preference::SetAutoSwitch($values["auto_switch"]);
Application_Model_Preference::setReplayGainModifier($values["replayGainModifier"]);
if (!Application_Model_Preference::GetMasterDjConnectionUrlOverride()) { if (!Application_Model_Preference::GetMasterDjConnectionUrlOverride()) {
$master_connection_url = "http://".$_SERVER['SERVER_NAME'].":".$values["master_harbor_input_port"]."/".$values["master_harbor_input_mount_point"]; $master_connection_url = "http://".$_SERVER['SERVER_NAME'].":".$values["master_harbor_input_port"]."/".$values["master_harbor_input_mount_point"];

View File

@ -10,6 +10,7 @@ class UserController extends Zend_Controller_Action
->addActionContext('get-user-data-table-info', 'json') ->addActionContext('get-user-data-table-info', 'json')
->addActionContext('get-user-data', 'json') ->addActionContext('get-user-data', 'json')
->addActionContext('remove-user', 'json') ->addActionContext('remove-user', 'json')
->addActionContext('edit-user', 'json')
->initContext(); ->initContext();
} }
@ -114,6 +115,49 @@ class UserController extends Zend_Controller_Action
$id = $this->_getParam('id'); $id = $this->_getParam('id');
$this->view->entries = Application_Model_User::GetUserData($id); $this->view->entries = Application_Model_User::GetUserData($id);
} }
public function editUserAction()
{
$request = $this->getRequest();
$form = new Application_Form_EditUser();
if ($request->isPost()) {
$params = $request->getPost();
$postData = explode('&', $params['data']);
foreach($postData as $k=>$v) {
$v = explode('=', $v);
$formData[$v[0]] = urldecode($v[1]);
}
if (isset($CC_CONFIG['demo']) && $CC_CONFIG['demo'] == 1
&& $formData['cu_login'] == 'admin') {
$this->view->form = $form;
$this->view->successMessage = "<div class='errors'>"._("Specific action is not allowed in demo version!")."</div>";
die(json_encode(array("html"=>$this->view->render('user/edit-user.phtml'))));
} else if ($form->isValid($formData) &&
$form->validateLogin($formData['cu_login'], $formData['cu_user_id'])) {
$user = new Application_Model_User($formData['cu_user_id']);
$user->setFirstName($formData['cu_first_name']);
$user->setLastName($formData['cu_last_name']);
$user->setLogin($formData['cu_login']);
// We don't allow 6 x's as a password.
// The reason is because we use that as a password placeholder
// on the client side.
if ($formData['cu_password'] != "xxxxxx") {
$user->setPassword($formData['cu_password']);
}
$user->setEmail($formData['cu_email']);
$user->setCellPhone($formData['cu_cell_phone']);
$user->setSkype($formData['cu_skype']);
$user->setJabber($formData['cu_jabber']);
$user->save();
$this->view->successMessage = "<div class='success'>"._("User updated successfully!")."</div>";
}
$this->view->form = $form;
die(json_encode(array("html"=>$this->view->render('user/edit-user.phtml'))));
}
$this->view->form = $form;
$this->view->html = $this->view->render('user/edit-user.phtml');
}
public function removeUserAction() public function removeUserAction()
{ {

View File

@ -40,14 +40,12 @@ class Application_Form_AddUser extends Zend_Form
$firstName->setLabel(_('Firstname:')); $firstName->setLabel(_('Firstname:'));
$firstName->setAttrib('class', 'input_text'); $firstName->setAttrib('class', 'input_text');
$firstName->addFilter('StringTrim'); $firstName->addFilter('StringTrim');
$firstName->addValidator($notEmptyValidator);
$this->addElement($firstName); $this->addElement($firstName);
$lastName = new Zend_Form_Element_Text('last_name'); $lastName = new Zend_Form_Element_Text('last_name');
$lastName->setLabel(_('Lastname:')); $lastName->setLabel(_('Lastname:'));
$lastName->setAttrib('class', 'input_text'); $lastName->setAttrib('class', 'input_text');
$lastName->addFilter('StringTrim'); $lastName->addFilter('StringTrim');
$lastName->addValidator($notEmptyValidator);
$this->addElement($lastName); $this->addElement($lastName);
$email = new Zend_Form_Element_Text('email'); $email = new Zend_Form_Element_Text('email');

View File

@ -0,0 +1,122 @@
<?php
class Application_Form_EditUser extends Zend_Form
{
public function init()
{
/*
$this->addElementPrefixPath('Application_Validate',
'../application/validate',
'validate');
* */
$currentUser = Application_Model_User::getCurrentUser();
$userData = Application_Model_User::GetUserData($currentUser->getId());
$notEmptyValidator = Application_Form_Helper_ValidationTypes::overrideNotEmptyValidator();
$emailValidator = Application_Form_Helper_ValidationTypes::overrideEmailAddressValidator();
$this->setDecorators(array(
array('ViewScript', array('viewScript' => 'form/edit-user.phtml'))));
$this->setAttrib('id', 'current-user-form');
$hidden = new Zend_Form_Element_Hidden('cu_user_id');
$hidden->setDecorators(array('ViewHelper'));
$hidden->setValue($userData["id"]);
$this->addElement($hidden);
$login = new Zend_Form_Element_Text('cu_login');
$login->setLabel(_('Username:'));
$login->setValue($userData["login"]);
$login->setAttrib('class', 'input_text');
$login->setRequired(true);
$login->addValidator($notEmptyValidator);
$login->addFilter('StringTrim');
$login->setDecorators(array('viewHelper'));
$this->addElement($login);
$password = new Zend_Form_Element_Password('cu_password');
$password->setLabel(_('Password:'));
$password->setAttrib('class', 'input_text');
$password->setRequired(true);
$password->addFilter('StringTrim');
$password->addValidator($notEmptyValidator);
$password->setDecorators(array('viewHelper'));
$this->addElement($password);
$firstName = new Zend_Form_Element_Text('cu_first_name');
$firstName->setLabel(_('Firstname:'));
$firstName->setValue($userData["first_name"]);
$firstName->setAttrib('class', 'input_text');
$firstName->addFilter('StringTrim');
$firstName->setDecorators(array('viewHelper'));
$this->addElement($firstName);
$lastName = new Zend_Form_Element_Text('cu_last_name');
$lastName->setLabel(_('Lastname:'));
$lastName->setValue($userData["last_name"]);
$lastName->setAttrib('class', 'input_text');
$lastName->addFilter('StringTrim');
$lastName->setDecorators(array('viewHelper'));
$this->addElement($lastName);
$email = new Zend_Form_Element_Text('cu_email');
$email->setLabel(_('Email:'));
$email->setValue($userData["email"]);
$email->setAttrib('class', 'input_text');
$email->addFilter('StringTrim');
$email->setRequired(true);
$email->addValidator($notEmptyValidator);
$email->addValidator($emailValidator);
$email->setDecorators(array('viewHelper'));
$this->addElement($email);
$cellPhone = new Zend_Form_Element_Text('cu_cell_phone');
$cellPhone->setLabel(_('Mobile Phone:'));
$cellPhone->setValue($userData["cell_phone"]);
$cellPhone->setAttrib('class', 'input_text');
$cellPhone->addFilter('StringTrim');
$cellPhone->setDecorators(array('viewHelper'));
$this->addElement($cellPhone);
$skype = new Zend_Form_Element_Text('cu_skype');
$skype->setLabel(_('Skype:'));
$skype->setValue($userData["skype_contact"]);
$skype->setAttrib('class', 'input_text');
$skype->addFilter('StringTrim');
$skype->setDecorators(array('viewHelper'));
$this->addElement($skype);
$jabber = new Zend_Form_Element_Text('cu_jabber');
$jabber->setLabel(_('Jabber:'));
$jabber->setValue($userData["jabber_contact"]);
$jabber->setAttrib('class', 'input_text');
$jabber->addFilter('StringTrim');
$jabber->addValidator($emailValidator);
$jabber->setDecorators(array('viewHelper'));
$this->addElement($jabber);
/*
$saveBtn = new Zend_Form_Element_Button('cu_save_user');
$saveBtn->setAttrib('class', 'btn btn-small right-floated');
$saveBtn->setIgnore(true);
$saveBtn->setLabel(_('Save'));
$saveBtn->setDecorators(array('viewHelper'));
$this->addElement($saveBtn);
*/
}
public function validateLogin($p_login, $p_userId) {
$count = CcSubjsQuery::create()
->filterByDbLogin($p_login)
->filterByDbId($p_userId, Criteria::NOT_EQUAL)
->count();
if ($count != 0) {
$this->getElement('cu_login')->setErrors(array(_("Login name is not unique.")));
return false;
} else {
return true;
}
}
}

View File

@ -58,6 +58,13 @@ class Application_Form_StreamSetting extends Zend_Form
$stream_format->setValue(Application_Model_Preference::GetStreamLabelFormat()); $stream_format->setValue(Application_Model_Preference::GetStreamLabelFormat());
$stream_format->setDecorators(array('ViewHelper')); $stream_format->setDecorators(array('ViewHelper'));
$this->addElement($stream_format); $this->addElement($stream_format);
$replay_gain = new Zend_Form_Element_Hidden("replayGainModifier");
$replay_gain->setLabel(_("Replay Gain Modifier"))
->setValue(Application_Model_Preference::getReplayGainModifier())
->setAttribs(array('style' => "border: 0; color: #f6931f; font-weight: bold;"))
->setDecorators(array('ViewHelper'));
$this->addElement($replay_gain);
} }
public function isValid($data) public function isValid($data)

View File

@ -23,7 +23,9 @@
$this->navigation()->menu()->setPartial($partial); ?> $this->navigation()->menu()->setPartial($partial); ?>
<div class="personal-block solo"> <div class="personal-block solo">
<ul> <ul>
<li><span class="name"><?php echo $this->loggedInAs()?></span> | <a href=<?php echo $baseUrl . "/Login/logout"?>><?php echo _("Logout")?></a></li> <li>
<a id="current-user" href="#"><span class="name"><?php echo $this->loggedInAs()?></span></a> | <a href=<?php echo $baseUrl . "/Login/logout"?>><?php echo _("Logout")?></a>
</li>
</ul> </ul>
</div> </div>

View File

@ -1183,4 +1183,19 @@ class Application_Model_Preference
$data = self::getValue("nowplaying_screen", true); $data = self::getValue("nowplaying_screen", true);
return ($data != "") ? unserialize($data) : null; return ($data != "") ? unserialize($data) : null;
} }
public static function getReplayGainModifier(){
$rg_modifier = self::getValue("replay_gain_modifier");
if ($rg_modifier === "") {
return "0";
}
return $rg_modifier;
}
public static function setReplayGainModifier($rg_modifier)
{
self::setValue("replay_gain_modifier", $rg_modifier, true);
}
} }

View File

@ -679,6 +679,8 @@ SQL;
$same_hour = $start_hour == $end_hour; $same_hour = $start_hour == $end_hour;
$independent_event = !$same_hour; $independent_event = !$same_hour;
$replay_gain = is_null($item["replay_gain"]) ? "0": $item["replay_gain"];
$replay_gain += Application_Model_Preference::getReplayGainModifier();
$schedule_item = array( $schedule_item = array(
'id' => $media_id, 'id' => $media_id,
@ -692,7 +694,7 @@ SQL;
'start' => $start, 'start' => $start,
'end' => $end, 'end' => $end,
'show_name' => $item["show_name"], 'show_name' => $item["show_name"],
'replay_gain' => is_null($item["replay_gain"]) ? "0": $item["replay_gain"], 'replay_gain' => $replay_gain,
'independent_event' => $independent_event, 'independent_event' => $independent_event,
); );
self::appendScheduleItem($data, $start, $schedule_item); self::appendScheduleItem($data, $start, $schedule_item);

View File

@ -309,7 +309,7 @@ class Application_Model_Webstream implements Application_Model_LibraryEditable
$media_url = self::getXspfUrl($url); $media_url = self::getXspfUrl($url);
} elseif (preg_match("/pls\+xml/", $mime) || preg_match("/x-scpls/", $mime)) { } elseif (preg_match("/pls\+xml/", $mime) || preg_match("/x-scpls/", $mime)) {
$media_url = self::getPlsUrl($url); $media_url = self::getPlsUrl($url);
} elseif (preg_match("/(mpeg|ogg)/", $mime)) { } elseif (preg_match("/(mpeg|ogg|audio\/aacp)/", $mime)) {
if ($content_length_found) { if ($content_length_found) {
throw new Exception(_("Invalid webstream - This appears to be a file download.")); throw new Exception(_("Invalid webstream - This appears to be a file download."));
} }
@ -322,10 +322,49 @@ class Application_Model_Webstream implements Application_Model_LibraryEditable
} }
/* PHP get_headers has an annoying property where if the passed in URL is
* a redirect, then it goes to the new URL, and returns headers from both
* requests. We only want the headers from the final request. Here's an
* example:
*
* 0 => "HTTP/1.1 302 Moved Temporarily",
* 1 => "X-Powered-By: Servlet/3.0 JSP/2.2 (GlassFish Server Open Source Edition 3.1.1 Java/Sun Microsystems Inc./1.6)",
* 2 => "Server: GlassFish Server Open Source Edition 3.1.1",
* 3 => "Location: http://3043.live.streamtheworld.com:80/SAM04AAC89_SC",
* 4 => "Content-Type: text/html;charset=ISO-8859-1",
* 5 => "Content-Language: en-US",
* 6 => "Content-Length: 202",
* 7 => "Date: Thu, 27 Dec 2012 21:52:59 GMT",
* 8 => "Connection: close",
* 9 => "HTTP/1.0 200 OK",
* 10 => "Expires: Thu, 01 Dec 2003 16:00:00 GMT",
* 11 => "Cache-Control: no-cache, must-revalidate",
* 12 => "Pragma: no-cache",
* 13 => "Content-Type: audio/aacp",
* 14 => "icy-br: 68",
* 15 => "Server: MediaGateway 3.2.1-04",
* */
private static function cleanHeaders($headers) {
//find the position of HTTP/1 200 OK
//
$position = 0;
foreach ($headers as $i => $v) {
if (preg_match("/^HTTP.*200 OK$/i", $v)) {
$position = $i;
break;
}
}
return array_slice($headers, $position);
}
private static function discoverStreamMime($url) private static function discoverStreamMime($url)
{ {
//TODO: What if invalid URL? //TODO: What if invalid URL?
$headers = get_headers($url); $headers = get_headers($url);
$headers = self::cleanHeaders($headers);
$mime = null; $mime = null;
$content_length_found = false; $content_length_found = false;
foreach ($headers as $h) { foreach ($headers as $h) {

View File

@ -0,0 +1,120 @@
<div id="current-user-container">
<form id="current-user-form" class="edit-user-global" enctype="application/x-www-form-urlencoded">
<dl class="zend_form">
<?php echo $this->element->getElement('cu_user_id') ?>
<dt id="cu-username-label">
<label><?php echo $this->element->getElement('cu_login')->getLabel() ?>
</label>
</dt>
<dd id="cu-username-element">
<?php echo $this->element->getElement('cu_login') ?>
<?php if($this->element->getElement('cu_login')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->element->getElement('cu_login')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</dd>
<dt id="cu-password-label">
<label><?php echo $this->element->getElement('cu_password')->getLabel() ?>
</label>
</dt>
<dd id="cu-password-element">
<?php echo $this->element->getElement('cu_password') ?>
<?php if($this->element->getElement('cu_password')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->element->getElement('cu_password')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</dd>
<dt id="cu-firstname-label">
<label><?php echo $this->element->getElement('cu_first_name')->getLabel() ?>
</label>
</dt>
<dd id="cu-firstname-element">
<?php echo $this->element->getElement('cu_first_name') ?>
<?php if($this->element->getElement('cu_first_name')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->element->getElement('cu_first_name')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</dd>
<dt id="cu-lastname-label">
<label><?php echo $this->element->getElement('cu_last_name')->getLabel() ?>
</label>
</dt>
<dd id="cu-lastname-element">
<?php echo $this->element->getElement('cu_last_name') ?>
<?php if($this->element->getElement('cu_last_name')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->element->getElement('cu_last_name')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</dd>
<dt id="cu-email-label">
<label><?php echo $this->element->getElement('cu_email')->getLabel() ?>
</label>
</dt>
<dd id="cu-email-element">
<?php echo $this->element->getElement('cu_email') ?>
<?php if($this->element->getElement('cu_email')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->element->getElement('cu_email')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</dd>
<dt id="cu-cell_phone-label">
<label><?php echo $this->element->getElement('cu_cell_phone')->getLabel() ?>
</label>
</dt>
<dd id="cu-cell_phone-element">
<?php echo $this->element->getElement('cu_cell_phone') ?>
<?php if($this->element->getElement('cu_cell_phone')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->element->getElement('cu_cell_phone')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</dd>
<dt id="cu-skype-label">
<label><?php echo $this->element->getElement('cu_skype')->getLabel() ?>
</label>
</dt>
<dd id="cu-skype-element">
<?php echo $this->element->getElement('cu_skype') ?>
<?php if($this->element->getElement('cu_skype')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->element->getElement('cu_skype')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</dd>
<dt id="cu-jabber-label">
<label><?php echo $this->element->getElement('cu_jabber')->getLabel() ?>
</label>
</dt>
<dd id="cu-jabber-element">
<?php echo $this->element->getElement('cu_jabber') ?>
<?php if($this->element->getElement('cu_jabber')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->element->getElement('cu_jabber')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</dd>
<button id="cu_save_user" type="button" class="btn btn-small right-floated">Save</button>
</dl>
</form>
</div>

View File

@ -108,6 +108,5 @@
</ul> </ul>
<?php endif; ?> <?php endif; ?>
</dd> </dd>
</dl> </dl>
</fieldset> </fieldset>

View File

@ -63,6 +63,24 @@
</ul> </ul>
<?php endif; ?> <?php endif; ?>
</dd> </dd>
<dt id="replayGainModifier-label" class="block-display">
<label><?php echo $this->form->getElement('replayGainModifier')->getLabel() ?>:
</label>
<span id="rg_modifier_value" style="border: 0; color: #f6931f; font-weight: bold;">
<?php echo $this->form->getElement('replayGainModifier')->getValue() ?>
</span>
</dt>
<dd id="replayGainModifier-element" class="block-display">
<?php echo $this->form->getElement('replayGainModifier') ?>
<?php if($this->form->getElement('replayGainModifier')->hasErrors()) : ?>
<ul class='errors'>
<?php foreach($this->form->getElement('replayGainModifier')->getMessages() as $error): ?>
<li><?php echo $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<div id="slider-range-max"></div>
</dd>
</dl> </dl>
</fieldset> </fieldset>
<?php echo $this->form->getSubform('live_stream_subform'); ?> <?php echo $this->form->getSubform('live_stream_subform'); ?>

View File

@ -0,0 +1,3 @@
<?php echo $this->successMessage ?>
<?php echo $this->form?>

View File

@ -314,9 +314,11 @@ INSERT INTO cc_stream_setting (keyname, value, type) VALUES ('s3_channels', 'ste
INSERT INTO cc_pref("keystr", "valstr") VALUES('locale', 'en_CA'); INSERT INTO cc_pref("keystr", "valstr") VALUES('locale', 'en_CA');
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('en_CA', 'English'); INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('en_CA', 'English');
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('en_US', 'English - US');
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('fr_FR', 'French'); INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('fr_FR', 'French');
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('de_DE', 'German'); INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('de_DE', 'German');
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('ko_KR', 'Korean'); INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('ko_KR', 'Korean');
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('ru_RU', 'Russian');
INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('es_ES', 'Spanish'); INSERT INTO cc_locale (locale_code, locale_lang) VALUES ('es_ES', 'Spanish');
-- end of added in 2.3 -- end of added in 2.3

View File

@ -833,11 +833,11 @@ msgstr "Password:"
#: airtime_mvc/application/forms/AddUser.php:40 #: airtime_mvc/application/forms/AddUser.php:40
msgid "Firstname:" msgid "Firstname:"
msgstr "Firstname:" msgstr "First name:"
#: airtime_mvc/application/forms/AddUser.php:47 #: airtime_mvc/application/forms/AddUser.php:47
msgid "Lastname:" msgid "Lastname:"
msgstr "Lastname:" msgstr "Last name:"
#: airtime_mvc/application/forms/AddUser.php:63 #: airtime_mvc/application/forms/AddUser.php:63
msgid "Mobile Phone:" msgid "Mobile Phone:"

View File

@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: Airtime 2.3\n" "Project-Id-Version: Airtime 2.3\n"
"Report-Msgid-Bugs-To: http://forum.sourcefabric.org/\n" "Report-Msgid-Bugs-To: http://forum.sourcefabric.org/\n"
"POT-Creation-Date: 2012-11-29 11:44-0500\n" "POT-Creation-Date: 2012-11-29 11:44-0500\n"
"PO-Revision-Date: 2012-12-03 21:49+0100\n" "PO-Revision-Date: 2012-12-08 13:56+0100\n"
"Last-Translator: Albert Bruc <a.bruc@ab-ae.fr>\n" "Last-Translator: Albert Bruc <a.bruc@ab-ae.fr>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -899,7 +899,7 @@ msgstr "Invité"
#: airtime_mvc/application/forms/AddUser.php:87 #: airtime_mvc/application/forms/AddUser.php:87
#: airtime_mvc/application/controllers/LocaleController.php:307 #: airtime_mvc/application/controllers/LocaleController.php:307
msgid "DJ" msgid "DJ"
msgstr "DJ" msgstr "DeaJee"
#: airtime_mvc/application/forms/AddUser.php:88 #: airtime_mvc/application/forms/AddUser.php:88
#: airtime_mvc/application/controllers/LocaleController.php:308 #: airtime_mvc/application/controllers/LocaleController.php:308
@ -1469,7 +1469,7 @@ msgid ""
"front-end widgets work.)" "front-end widgets work.)"
msgstr "" msgstr ""
"Autoriser les sites internet à acceder aux informations de la Programmation ?" "Autoriser les sites internet à acceder aux informations de la Programmation ?"
"%s (Activer cette option permetra aux widgets de fonctionner.)" "%s (Activer cette option permettra aux widgets de fonctionner.)"
#: airtime_mvc/application/forms/GeneralPreferences.php:49 #: airtime_mvc/application/forms/GeneralPreferences.php:49
msgid "Disabled" msgid "Disabled"
@ -2098,16 +2098,16 @@ msgid ""
"Check this box to automatically switch off Master/Show source upon source " "Check this box to automatically switch off Master/Show source upon source "
"disconnection." "disconnection."
msgstr "" msgstr ""
"Cochez cette case pour arrête automatiquement la source Maître/Emission " "Cochez cette case arrête automatiquement la source Maître/Emission lors de "
"lors de la déconnexion." "la déconnexion."
#: airtime_mvc/application/controllers/LocaleController.php:176 #: airtime_mvc/application/controllers/LocaleController.php:176
msgid "" msgid ""
"Check this box to automatically switch on Master/Show source upon source " "Check this box to automatically switch on Master/Show source upon source "
"connection." "connection."
msgstr "" msgstr ""
"Cochez cette case pour démarrer automatiquement la source Maître/Emission " "Cochez cette case démarre automatiquement la source Maître/Emission lors de "
"lors de la connexion." "la connexion."
#: airtime_mvc/application/controllers/LocaleController.php:177 #: airtime_mvc/application/controllers/LocaleController.php:177
msgid "" msgid ""
@ -2750,7 +2750,7 @@ msgstr "Lecture Programmée"
#: airtime_mvc/application/views/scripts/partialviews/header.phtml:54 #: airtime_mvc/application/views/scripts/partialviews/header.phtml:54
msgid "ON AIR" msgid "ON AIR"
msgstr "EN DIRECT" msgstr "DIRECT"
#: airtime_mvc/application/views/scripts/partialviews/header.phtml:55 #: airtime_mvc/application/views/scripts/partialviews/header.phtml:55
msgid "Listen" msgid "Listen"
@ -2879,7 +2879,7 @@ msgstr "Alors vous êtes prêt à démarrer!"
#: airtime_mvc/application/views/scripts/dashboard/help.phtml:13 #: airtime_mvc/application/views/scripts/dashboard/help.phtml:13
#, php-format #, php-format
msgid "For more detailed help, read the %suser manual%s." msgid "For more detailed help, read the %suser manual%s."
msgstr "Pour une aide plus déraillée, lisez le %smanuel utilisateur%s." msgstr "Pour une aide plus détaillée, lisez le %smanuel utilisateur%s."
#: airtime_mvc/application/views/scripts/playlist/update.phtml:40 #: airtime_mvc/application/views/scripts/playlist/update.phtml:40
msgid "Expand Static Block" msgid "Expand Static Block"

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -93,6 +93,10 @@ select {
} }
/* Version Notification Ends*/ /* Version Notification Ends*/
#current-user-container {
padding-bottom: 20px;
}
.override_help_icon, .icecast_metadata_help_icon { .override_help_icon, .icecast_metadata_help_icon {
cursor: help; cursor: help;
position: relative; position: relative;
@ -2935,3 +2939,19 @@ dd .stream-status {
top: 0px; top: 0px;
left: 0px; left: 0px;
} }
.edit-user-global dt {
width: 90px;
float: left;
margin-top: 4px;
margin-left: 2px;
}
.edit-user-global dd {
width: 230px;
padding-bottom: 5px;
}
.edit-user-global input {
width: 170px;
}

View File

@ -0,0 +1,25 @@
.tipsy { font-size: 11px; position: absolute; padding: 5px; z-index: 100000; width: auto }
.tipsy-inner { background-color: #000; color: #FFF; max-width: 300px; padding: 5px 8px 4px 5px; }
/* Rounded corners */
.tipsy-inner { border-radius: 3px; -moz-border-radius: 3px; -webkit-border-radius: 3px; }
/* Uncomment for shadow */
/*.tipsy-inner { box-shadow: 0 0 5px #000000; -webkit-box-shadow: 0 0 5px #000000; -moz-box-shadow: 0 0 5px #000000; }*/
.tipsy-arrow { position: absolute; width: 0; height: 0; line-height: 0; border: 5px dashed #000; }
/* Rules to colour arrows */
.tipsy-arrow-n { border-bottom-color: #000; }
.tipsy-arrow-s { border-top-color: #000; }
.tipsy-arrow-e { border-left-color: #000; }
.tipsy-arrow-w { border-right-color: #000; }
.tipsy-n .tipsy-arrow { top: 0px; left: 50%; margin-left: -5px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent; }
.tipsy-nw .tipsy-arrow { top: 0; left: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
.tipsy-ne .tipsy-arrow { top: 0; right: 10px; border-bottom-style: solid; border-top: none; border-left-color: transparent; border-right-color: transparent;}
.tipsy-s .tipsy-arrow { bottom: 0; left: 50%; margin-left: -5px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
.tipsy-sw .tipsy-arrow { bottom: 0; left: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
.tipsy-se .tipsy-arrow { bottom: 0; right: 10px; border-top-style: solid; border-bottom: none; border-left-color: transparent; border-right-color: transparent; }
.tipsy-e .tipsy-arrow { right: 0; top: 50%; margin-top: -5px; border-left-style: solid; border-right: none; border-top-color: transparent; border-bottom-color: transparent; }
.tipsy-w .tipsy-arrow { left: 0; top: 50%; margin-top: -5px; border-right-style: solid; border-left: none; border-top-color: transparent; border-bottom-color: transparent; }

View File

@ -441,7 +441,64 @@ function init() {
}); });
} }
/* We never retrieve the user's password from the db
* and when we call isValid($params) the form values are cleared
* and repopulated with $params which does not have the password
* field. Therefore, we fill the password field with 6 x's
*/
function setCurrentUserPseudoPassword() {
$('#cu_password').val("xxxxxx");
}
$(document).ready(function() { $(document).ready(function() {
if ($('#master-panel').length > 0) if ($('#master-panel').length > 0)
init(); init();
var timer;
$('.tipsy').live('mouseover', function() {
clearTimeout(timer);
});
$('.tipsy').live('mouseout', function() {
timer = setTimeout("$('#current-user').tipsy('hide')", 500);
});
$('#current-user').bind('mouseover', function() {
$.ajax({
url: baseUrl+'/user/edit-user/format/json',
success: function(json) {
$('#current-user').tipsy({
gravity: 'n',
html: true,
fade: true,
opacity: 0.9,
trigger: 'manual',
title: function() {
return json.html;
}
});
},
cache: false,
complete: function() {
$('#current-user').tipsy('show');
setCurrentUserPseudoPassword();
}
});
});
$('#current-user').bind('mouseout', function() {
timer = setTimeout("$('#current-user').tipsy('hide')", 500);
});
$('#cu_save_user').live('click', function() {
var data = $('#current-user-form').serialize();
$.post(baseUrl+'/user/edit-user', {format: 'json', data: data}, function(data) {
var json = $.parseJSON(data);
$('.tipsy-inner').empty().append(json.html);
setCurrentUserPseudoPassword();
setTimeout(removeSuccessMsg, 5000);
});
});
}); });

View File

@ -915,7 +915,11 @@ var AIRTIME = (function(AIRTIME) {
soundcloud.view.callback = callback; soundcloud.view.callback = callback;
} }
} }
// remove 'Add to smart block' option if the current
// block is dynamic
if ($('input:radio[name=sp_type]:checked').val() === "1") {
delete oItems.pl_add;
}
items = oItems; items = oItems;
} }

View File

@ -386,12 +386,28 @@ function setupEventListeners() {
var json = $.parseJSON(data); var json = $.parseJSON(data);
$('#content').empty().append(json.html); $('#content').empty().append(json.html);
setupEventListeners(); setupEventListeners();
setSliderForReplayGain();
}); });
} }
}); });
} }
function setSliderForReplayGain(){
$( "#slider-range-max" ).slider({
range: "max",
min: -10,
max: 10,
value: $("#rg_modifier_value").html(),
slide: function( event, ui ) {
$( "#replayGainModifier" ).val( ui.value );
$("#rg_modifier_value").html(ui.value);
}
});
$( "#replayGainModifier" ).val( $( "#slider-range-max" ).slider( "value" ) );
}
$(document).ready(function() { $(document).ready(function() {
setupEventListeners(); setupEventListeners();
setSliderForReplayGain();
}); });

View File

@ -94,8 +94,83 @@ function populateUserTable() {
$(document).ready(function() { $(document).ready(function() {
populateUserTable(); populateUserTable();
//assign user-rights and id to each user type option so we can
//display user rights for each with tipsy tooltip
$.each($('#type').children(), function(i, opt) {
switch ($(this).val()) {
case 'G':
$(this).attr('id', 'user-type-G');
$(this).attr('user-rights',
$.i18n._('Guests can do the following:')+'<br><br>'+
$.i18n._('View schedule')+'<br>'+
$.i18n._('View show content')
);
break;
case 'H':
$(this).attr('id', 'user-type-H');
$(this).attr('user-rights',
$.i18n._('DJs can do the following:')+'<br><br>'+
$.i18n._('View schedule')+'<br>'+
$.i18n._('View show content')+'<br>'+
$.i18n._('Manage assigned show content')+'<br>'+
$.i18n._('Import media files')+'<br>'+
$.i18n._('Create playlists, smart blocks, and webstreams')+'<br>'+
$.i18n._('Manage their own library content')
);
break;
case 'P':
$(this).attr('id', 'user-type-P');
$(this).attr('user-rights',
$.i18n._('Progam Managers can do the following:')+'<br><br>'+
$.i18n._('View schedule')+'<br>'+
$.i18n._('View and manage show content')+'<br>'+
$.i18n._('Schedule shows')+'<br>'+
$.i18n._('Import media files')+'<br>'+
$.i18n._('Create playlists, smart blocks, and webstreams')+'<br>'+
$.i18n._('Manage all library content')
);
break;
case 'A':
$(this).attr('id', 'user-type-A');
$(this).attr('user-rights',
$.i18n._('Admins can do the following:')+'<br><br>'+
$.i18n._('Manage preferences')+'<br>'+
$.i18n._('Manage users')+'<br>'+
$.i18n._('Manage watched folders')+'<br>'+
$.i18n._('Send support feedback')+'<br>'+
$.i18n._('View system status')+'<br>'+
$.i18n._('Access playout history')+'<br>'+
$.i18n._('View listener stats')+'<br>'+
$.i18n._('View schedule')+'<br>'+
$.i18n._('View and manage show content')+'<br>'+
$.i18n._('Schedule shows')+'<br>'+
$.i18n._('Import media files')+'<br>'+
$.i18n._('Create playlists, smart blocks, and webstreams')+'<br>'+
$.i18n._('Manage all library content')
);
break;
}
});
$('#type').live("change", function(){
//when the title changes on live tipsy tooltips the changes take
//affect the next time tipsy is shown so we need to hide and re-show it
$(this).tipsy('hide').tipsy('show');
});
//$('#user_details').hide(); $('#type').tipsy({
gravity: 'w',
html: true,
opacity: 0.9,
trigger: 'manual',
live: true,
title: function() {
return $('#user-type-'+$(this).val()).attr('user-rights');
}
});
$('#type').tipsy('show');
var newUser = {login:"", first_name:"", last_name:"", type:"G", id:""}; var newUser = {login:"", first_name:"", last_name:"", type:"G", id:""};

View File

@ -0,0 +1,23 @@
{
"sEmptyTable": "No data available in table",
"sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
"sInfoEmpty": "Showing 0 to 0 of 0 entries",
"sInfoFiltered": "(filtered from _MAX_ total entries)",
"sInfoPostFix": "",
"sInfoThousands": ",",
"sLengthMenu": "Show _MENU_",
"sLoadingRecords": "Loading...",
"sProcessing": "Processing...",
"sSearch": "",
"sZeroRecords": "No matching records found",
"oPaginate": {
"sFirst": "First",
"sLast": "Last",
"sNext": "Next",
"sPrevious": "Previous"
},
"oAria": {
"sSortAscending": ": activate to sort column ascending",
"sSortDescending": ": activate to sort column descending"
}
}

View File

@ -0,0 +1,17 @@
{
"sProcessing": "Подождите...",
"sLengthMenu": "Показать _MENU_ записей",
"sZeroRecords": "Записи отсутствуют.",
"sInfo": "Записи с _START_ до _END_ из _TOTAL_ записей",
"sInfoEmpty": "Записи с 0 до 0 из 0 записей",
"sInfoFiltered": "(отфильтровано из _MAX_ записей)",
"sInfoPostFix": "",
"sSearch": "",
"sUrl": "",
"oPaginate": {
"sFirst": "Первая",
"sPrevious": "Предыдущая",
"sNext": "Следующая",
"sLast": "Последняя"
}
}

View File

@ -0,0 +1,26 @@
// English - US
plupload.addI18n({
'Select files' : 'Select files',
'Add files to the upload queue and click the start button.' : 'Add files to the upload queue and click the start button.',
'Filename' : 'Filename',
'Status' : 'Status',
'Size' : 'Size',
'Add files' : 'Add files',
'Stop current upload' : 'Stop current upload',
'Start uploading queue' : 'Start uploading queue',
'Uploaded %d/%d files': 'Uploaded %d/%d files',
'N/A' : 'N/A',
'Drag files here.' : 'Drag files here.',
'File extension error.': 'File extension error.',
'File size error.': 'File size error.',
'Init error.': 'Init error.',
'HTTP Error.': 'HTTP Error.',
'Security error.': 'Security error.',
'Generic error.': 'Generic error.',
'IO error.': 'IO error.',
'Stop Upload': 'Stop Upload',
'Add Files': 'Add Files',
'Start Upload': 'Start Upload',
'Start upload': 'Start upload',
'%d files queued': '%d files queued'
});

View File

@ -0,0 +1,21 @@
// Russian
plupload.addI18n({
'Select files' : 'Выберите файлы',
'Add files to the upload queue and click the start button.' : 'Добавьте файлы в очередь и нажмите кнопку "Загрузить файлы".',
'Filename' : 'Имя файла',
'Status' : 'Статус',
'Size' : 'Размер',
'Add files' : 'Добавить файлы',
'Stop current upload' : 'Остановить загрузку',
'Start upload' : 'Загрузить файлы',
'Uploaded %d/%d files': 'Загружено %d из %d файлов',
'N/A' : 'N/D',
'Drag files here.' : 'Перетащите файлы сюда.',
'File extension error.': 'Неправильное расширение файла.',
'File size error.': 'Неправильный размер файла.',
'Init error.': 'Ошибка инициализации.',
'HTTP Error.': 'Ошибка HTTP.',
'Security error.': 'Ошибка безопасности.',
'Generic error.': 'Общая ошибка.',
'IO error.': 'Ошибка ввода-вывода.'
});

View File

@ -0,0 +1,23 @@
Before overwriting jquery.tipsy.js, please note we have changed a few lines to
support manual triggering with live tipsy tooltips
denise@denise-DX4860:~/airtime/airtime_mvc/public/js/tipsy$ diff -u jquery.tipsy_orig.js jquery.tipsy.js
--- jquery.tipsy_orig.js 2012-12-13 12:03:48.780751104 -0500
+++ jquery.tipsy.js 2012-12-13 12:08:15.564761493 -0500
@@ -173,12 +173,10 @@
if (!options.live) this.each(function() { get(this); });
- if (options.trigger != 'manual') {
- var binder = options.live ? 'live' : 'bind',
- eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus',
- eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
- this[binder](eventIn, enter)[binder](eventOut, leave);
- }
+ var binder = options.live ? 'live' : 'bind',
+ eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus',
+ eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
+ this[binder](eventIn, enter)[binder](eventOut, leave);
return this;

View File

@ -0,0 +1,256 @@
// tipsy, facebook style tooltips for jquery
// version 1.0.0a
// (c) 2008-2010 jason frame [jason@onehackoranother.com]
// released under the MIT license
(function($) {
function maybeCall(thing, ctx) {
return (typeof thing == 'function') ? (thing.call(ctx)) : thing;
};
function isElementInDOM(ele) {
while (ele = ele.parentNode) {
if (ele == document) return true;
}
return false;
};
function Tipsy(element, options) {
this.$element = $(element);
this.options = options;
this.enabled = true;
this.fixTitle();
};
Tipsy.prototype = {
show: function() {
var title = this.getTitle();
if (title && this.enabled) {
var $tip = this.tip();
$tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
$tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
$tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body);
var pos = $.extend({}, this.$element.offset(), {
width: this.$element[0].offsetWidth,
height: this.$element[0].offsetHeight
});
var actualWidth = $tip[0].offsetWidth,
actualHeight = $tip[0].offsetHeight,
gravity = maybeCall(this.options.gravity, this.$element[0]);
var tp;
switch (gravity.charAt(0)) {
case 'n':
tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
break;
case 's':
tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
break;
case 'e':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
break;
case 'w':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
break;
}
if (gravity.length == 2) {
if (gravity.charAt(1) == 'w') {
tp.left = pos.left + pos.width / 2 - 15;
} else {
tp.left = pos.left + pos.width / 2 - actualWidth + 15;
}
}
$tip.css(tp).addClass('tipsy-' + gravity);
$tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0);
if (this.options.className) {
$tip.addClass(maybeCall(this.options.className, this.$element[0]));
}
if (this.options.fade) {
$tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
} else {
$tip.css({visibility: 'visible', opacity: this.options.opacity});
}
}
},
hide: function() {
if (this.options.fade) {
this.tip().stop().fadeOut(function() { $(this).remove(); });
} else {
this.tip().remove();
}
},
fixTitle: function() {
var $e = this.$element;
if ($e.attr('title') || typeof($e.attr('original-title')) != 'string') {
$e.attr('original-title', $e.attr('title') || '').removeAttr('title');
}
},
getTitle: function() {
var title, $e = this.$element, o = this.options;
this.fixTitle();
var title, o = this.options;
if (typeof o.title == 'string') {
title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
} else if (typeof o.title == 'function') {
title = o.title.call($e[0]);
}
title = ('' + title).replace(/(^\s*|\s*$)/, "");
return title || o.fallback;
},
tip: function() {
if (!this.$tip) {
this.$tip = $('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"></div>');
this.$tip.data('tipsy-pointee', this.$element[0]);
}
return this.$tip;
},
validate: function() {
if (!this.$element[0].parentNode) {
this.hide();
this.$element = null;
this.options = null;
}
},
enable: function() { this.enabled = true; },
disable: function() { this.enabled = false; },
toggleEnabled: function() { this.enabled = !this.enabled; }
};
$.fn.tipsy = function(options) {
if (options === true) {
return this.data('tipsy');
} else if (typeof options == 'string') {
var tipsy = this.data('tipsy');
if (tipsy) tipsy[options]();
return this;
}
options = $.extend({}, $.fn.tipsy.defaults, options);
function get(ele) {
var tipsy = $.data(ele, 'tipsy');
if (!tipsy) {
tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
$.data(ele, 'tipsy', tipsy);
}
return tipsy;
}
function enter() {
var tipsy = get(this);
tipsy.hoverState = 'in';
if (options.delayIn == 0) {
tipsy.show();
} else {
tipsy.fixTitle();
setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn);
}
};
function leave() {
var tipsy = get(this);
tipsy.hoverState = 'out';
if (options.delayOut == 0) {
tipsy.hide();
} else {
setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
}
};
if (!options.live) this.each(function() { get(this); });
var binder = options.live ? 'live' : 'bind',
eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus',
eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
this[binder](eventIn, enter)[binder](eventOut, leave);
return this;
};
$.fn.tipsy.defaults = {
className: null,
delayIn: 0,
delayOut: 0,
fade: false,
fallback: '',
gravity: 'n',
html: false,
live: false,
offset: 0,
opacity: 0.8,
title: 'title',
trigger: 'hover'
};
$.fn.tipsy.revalidate = function() {
$('.tipsy').each(function() {
var pointee = $.data(this, 'tipsy-pointee');
if (!pointee || !isElementInDOM(pointee)) {
$(this).remove();
}
});
};
// Overwrite this method to provide options on a per-element basis.
// For example, you could store the gravity in a 'tipsy-gravity' attribute:
// return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
// (remember - do not modify 'options' in place!)
$.fn.tipsy.elementOptions = function(ele, options) {
return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
};
$.fn.tipsy.autoNS = function() {
return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
};
$.fn.tipsy.autoWE = function() {
return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
};
/**
* yields a closure of the supplied parameters, producing a function that takes
* no arguments and is suitable for use as an autogravity function like so:
*
* @param margin (int) - distance from the viewable region edge that an
* element should be before setting its tooltip's gravity to be away
* from that edge.
* @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer
* if there are no viewable region edges effecting the tooltip's
* gravity. It will try to vary from this minimally, for example,
* if 'sw' is preferred and an element is near the right viewable
* region edge, but not the top edge, it will set the gravity for
* that element's tooltip to be 'se', preserving the southern
* component.
*/
$.fn.tipsy.autoBounds = function(margin, prefer) {
return function() {
var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)},
boundTop = $(document).scrollTop() + margin,
boundLeft = $(document).scrollLeft() + margin,
$this = $(this);
if ($this.offset().top < boundTop) dir.ns = 'n';
if ($this.offset().left < boundLeft) dir.ew = 'w';
if ($(window).width() + $(document).scrollLeft() - $this.offset().left < margin) dir.ew = 'e';
if ($(window).height() + $(document).scrollTop() - $this.offset().top < margin) dir.ns = 's';
return dir.ns + (dir.ew ? dir.ew : '');
}
};
})(jQuery);

View File

@ -0,0 +1,258 @@
// tipsy, facebook style tooltips for jquery
// version 1.0.0a
// (c) 2008-2010 jason frame [jason@onehackoranother.com]
// released under the MIT license
(function($) {
function maybeCall(thing, ctx) {
return (typeof thing == 'function') ? (thing.call(ctx)) : thing;
};
function isElementInDOM(ele) {
while (ele = ele.parentNode) {
if (ele == document) return true;
}
return false;
};
function Tipsy(element, options) {
this.$element = $(element);
this.options = options;
this.enabled = true;
this.fixTitle();
};
Tipsy.prototype = {
show: function() {
var title = this.getTitle();
if (title && this.enabled) {
var $tip = this.tip();
$tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
$tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
$tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body);
var pos = $.extend({}, this.$element.offset(), {
width: this.$element[0].offsetWidth,
height: this.$element[0].offsetHeight
});
var actualWidth = $tip[0].offsetWidth,
actualHeight = $tip[0].offsetHeight,
gravity = maybeCall(this.options.gravity, this.$element[0]);
var tp;
switch (gravity.charAt(0)) {
case 'n':
tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
break;
case 's':
tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
break;
case 'e':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
break;
case 'w':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
break;
}
if (gravity.length == 2) {
if (gravity.charAt(1) == 'w') {
tp.left = pos.left + pos.width / 2 - 15;
} else {
tp.left = pos.left + pos.width / 2 - actualWidth + 15;
}
}
$tip.css(tp).addClass('tipsy-' + gravity);
$tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0);
if (this.options.className) {
$tip.addClass(maybeCall(this.options.className, this.$element[0]));
}
if (this.options.fade) {
$tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
} else {
$tip.css({visibility: 'visible', opacity: this.options.opacity});
}
}
},
hide: function() {
if (this.options.fade) {
this.tip().stop().fadeOut(function() { $(this).remove(); });
} else {
this.tip().remove();
}
},
fixTitle: function() {
var $e = this.$element;
if ($e.attr('title') || typeof($e.attr('original-title')) != 'string') {
$e.attr('original-title', $e.attr('title') || '').removeAttr('title');
}
},
getTitle: function() {
var title, $e = this.$element, o = this.options;
this.fixTitle();
var title, o = this.options;
if (typeof o.title == 'string') {
title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
} else if (typeof o.title == 'function') {
title = o.title.call($e[0]);
}
title = ('' + title).replace(/(^\s*|\s*$)/, "");
return title || o.fallback;
},
tip: function() {
if (!this.$tip) {
this.$tip = $('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"></div>');
this.$tip.data('tipsy-pointee', this.$element[0]);
}
return this.$tip;
},
validate: function() {
if (!this.$element[0].parentNode) {
this.hide();
this.$element = null;
this.options = null;
}
},
enable: function() { this.enabled = true; },
disable: function() { this.enabled = false; },
toggleEnabled: function() { this.enabled = !this.enabled; }
};
$.fn.tipsy = function(options) {
if (options === true) {
return this.data('tipsy');
} else if (typeof options == 'string') {
var tipsy = this.data('tipsy');
if (tipsy) tipsy[options]();
return this;
}
options = $.extend({}, $.fn.tipsy.defaults, options);
function get(ele) {
var tipsy = $.data(ele, 'tipsy');
if (!tipsy) {
tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
$.data(ele, 'tipsy', tipsy);
}
return tipsy;
}
function enter() {
var tipsy = get(this);
tipsy.hoverState = 'in';
if (options.delayIn == 0) {
tipsy.show();
} else {
tipsy.fixTitle();
setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn);
}
};
function leave() {
var tipsy = get(this);
tipsy.hoverState = 'out';
if (options.delayOut == 0) {
tipsy.hide();
} else {
setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
}
};
if (!options.live) this.each(function() { get(this); });
if (options.trigger != 'manual') {
var binder = options.live ? 'live' : 'bind',
eventIn = options.trigger == 'hover' ? 'mouseenter' : 'focus',
eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
this[binder](eventIn, enter)[binder](eventOut, leave);
}
return this;
};
$.fn.tipsy.defaults = {
className: null,
delayIn: 0,
delayOut: 0,
fade: false,
fallback: '',
gravity: 'n',
html: false,
live: false,
offset: 0,
opacity: 0.8,
title: 'title',
trigger: 'hover'
};
$.fn.tipsy.revalidate = function() {
$('.tipsy').each(function() {
var pointee = $.data(this, 'tipsy-pointee');
if (!pointee || !isElementInDOM(pointee)) {
$(this).remove();
}
});
};
// Overwrite this method to provide options on a per-element basis.
// For example, you could store the gravity in a 'tipsy-gravity' attribute:
// return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
// (remember - do not modify 'options' in place!)
$.fn.tipsy.elementOptions = function(ele, options) {
return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
};
$.fn.tipsy.autoNS = function() {
return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
};
$.fn.tipsy.autoWE = function() {
return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
};
/**
* yields a closure of the supplied parameters, producing a function that takes
* no arguments and is suitable for use as an autogravity function like so:
*
* @param margin (int) - distance from the viewable region edge that an
* element should be before setting its tooltip's gravity to be away
* from that edge.
* @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer
* if there are no viewable region edges effecting the tooltip's
* gravity. It will try to vary from this minimally, for example,
* if 'sw' is preferred and an element is near the right viewable
* region edge, but not the top edge, it will set the gravity for
* that element's tooltip to be 'se', preserving the southern
* component.
*/
$.fn.tipsy.autoBounds = function(margin, prefer) {
return function() {
var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)},
boundTop = $(document).scrollTop() + margin,
boundLeft = $(document).scrollLeft() + margin,
$this = $(this);
if ($this.offset().top < boundTop) dir.ns = 'n';
if ($this.offset().left < boundLeft) dir.ew = 'w';
if ($(window).width() + $(document).scrollLeft() - $this.offset().left < margin) dir.ew = 'e';
if ($(window).height() + $(document).scrollTop() - $this.offset().top < margin) dir.ns = 's';
return dir.ns + (dir.ew ? dir.ew : '');
}
};
})(jQuery);

View File

@ -1,3 +1,15 @@
2.2.1 - December 4th, 2012
* Bug fixes
* Improved fades between webstreams
* Fix webstreams disconnecting occasionally
* Put 'and' and 'or' connectors between smart blocks
* Fix inability to preview webstreams in the Now Playing page on some
browsers
* Fix airtime-import script failing on FLAC files
* Fix DJ's being able to delete files they don't own
* Add support for 'x-scpls' webstream playlist types
* Fix media-monitor requiring a restart for initial import.
2.2.0 - October 25th, 2012 2.2.0 - October 25th, 2012
* New features * New features
* Smart Playlists * Smart Playlists

14
dev_tools/update_po_files.sh Executable file
View File

@ -0,0 +1,14 @@
#! /bin/bash
cd ..
#generate a new .po file
#this will generate a file called messages.po
find . -iname "*.phtml" -o -name "*.php" | xargs xgettext -L php
#merge the new messages from messages.po into each existing .po file
#this will generate new .po files
find ./airtime_mvc/locale/ -name "*.po" -exec msgmerge -N -U --no-wrap "{}" messages.po \;
#delete the old .po files
find ./airtime_mvc/locale/ -name "*.po~" -delete

View File

@ -154,6 +154,9 @@ def walk_supported(directory, clean_empties=False):
that support the extensions we are considering. When clean_empties that support the extensions we are considering. When clean_empties
is True we recursively delete empty directories left over in is True we recursively delete empty directories left over in
directory after the walk. """ directory after the walk. """
if directory is None:
return
for root, dirs, files in os.walk(directory): for root, dirs, files in os.walk(directory):
full_paths = ( os.path.join(root, name) for name in files full_paths = ( os.path.join(root, name) for name in files
if is_file_supported(name) ) if is_file_supported(name) )
@ -162,8 +165,7 @@ def walk_supported(directory, clean_empties=False):
def file_locked(path): def file_locked(path):
cmd = "lsof %s" % (pipes.quote(path)) f = Popen(["lsof", path], stdout=PIPE).stdout
f = Popen(cmd, shell=True, stdout=PIPE).stdout
return bool(f.readlines()) return bool(f.readlines())
def magic_move(old, new, after_dir_make=lambda : None): def magic_move(old, new, after_dir_make=lambda : None):