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
=======
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
-------------
Martin Konecny (martin.konecny@sourcefabric.org)

2
README
View File

@ -8,7 +8,7 @@ Home page: http://airtime.sourcefabric.org/
Major features:
* 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.
* Automation. Airtime has a scheduler function that enables users to
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/masterpanel.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()
@ -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/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/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/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')
->allow('G', 'login')
->allow('G', 'error')
->allow('G', 'user', 'edit-user')
->allow('G', 'showbuilder')
->allow('G', 'api')
->allow('G', 'schedule')

View File

@ -307,6 +307,26 @@ class LocaleController extends Zend_Controller_Action
"DJ" => _("DJ"),
"Program Manager" => _("Program Manager"),
"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
"Show / hide columns" => _("Show / hide columns"),
//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::SetAutoTransition($values["auto_transition"]);
Application_Model_Preference::SetAutoSwitch($values["auto_switch"]);
Application_Model_Preference::setReplayGainModifier($values["replayGainModifier"]);
if (!Application_Model_Preference::GetMasterDjConnectionUrlOverride()) {
$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', 'json')
->addActionContext('remove-user', 'json')
->addActionContext('edit-user', 'json')
->initContext();
}
@ -114,6 +115,49 @@ class UserController extends Zend_Controller_Action
$id = $this->_getParam('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()
{

View File

@ -40,14 +40,12 @@ class Application_Form_AddUser extends Zend_Form
$firstName->setLabel(_('Firstname:'));
$firstName->setAttrib('class', 'input_text');
$firstName->addFilter('StringTrim');
$firstName->addValidator($notEmptyValidator);
$this->addElement($firstName);
$lastName = new Zend_Form_Element_Text('last_name');
$lastName->setLabel(_('Lastname:'));
$lastName->setAttrib('class', 'input_text');
$lastName->addFilter('StringTrim');
$lastName->addValidator($notEmptyValidator);
$this->addElement($lastName);
$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->setDecorators(array('ViewHelper'));
$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)

View File

@ -23,7 +23,9 @@
$this->navigation()->menu()->setPartial($partial); ?>
<div class="personal-block solo">
<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>
</div>

View File

@ -1183,4 +1183,19 @@ class Application_Model_Preference
$data = self::getValue("nowplaying_screen", true);
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;
$independent_event = !$same_hour;
$replay_gain = is_null($item["replay_gain"]) ? "0": $item["replay_gain"];
$replay_gain += Application_Model_Preference::getReplayGainModifier();
$schedule_item = array(
'id' => $media_id,
@ -692,7 +694,7 @@ SQL;
'start' => $start,
'end' => $end,
'show_name' => $item["show_name"],
'replay_gain' => is_null($item["replay_gain"]) ? "0": $item["replay_gain"],
'replay_gain' => $replay_gain,
'independent_event' => $independent_event,
);
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);
} elseif (preg_match("/pls\+xml/", $mime) || preg_match("/x-scpls/", $mime)) {
$media_url = self::getPlsUrl($url);
} elseif (preg_match("/(mpeg|ogg)/", $mime)) {
} elseif (preg_match("/(mpeg|ogg|audio\/aacp)/", $mime)) {
if ($content_length_found) {
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)
{
//TODO: What if invalid URL?
$headers = get_headers($url);
$headers = self::cleanHeaders($headers);
$mime = null;
$content_length_found = false;
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>
<?php endif; ?>
</dd>
</dl>
</fieldset>

View File

@ -63,6 +63,24 @@
</ul>
<?php endif; ?>
</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>
</fieldset>
<?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_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 ('de_DE', 'German');
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');
-- end of added in 2.3

View File

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

View File

@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: Airtime 2.3\n"
"Report-Msgid-Bugs-To: http://forum.sourcefabric.org/\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"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
@ -899,7 +899,7 @@ msgstr "Invité"
#: airtime_mvc/application/forms/AddUser.php:87
#: airtime_mvc/application/controllers/LocaleController.php:307
msgid "DJ"
msgstr "DJ"
msgstr "DeaJee"
#: airtime_mvc/application/forms/AddUser.php:88
#: airtime_mvc/application/controllers/LocaleController.php:308
@ -1469,7 +1469,7 @@ msgid ""
"front-end widgets work.)"
msgstr ""
"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
msgid "Disabled"
@ -2098,16 +2098,16 @@ msgid ""
"Check this box to automatically switch off Master/Show source upon source "
"disconnection."
msgstr ""
"Cochez cette case pour arrête automatiquement la source Maître/Emission "
"lors de la déconnexion."
"Cochez cette case arrête automatiquement la source Maître/Emission lors de "
"la déconnexion."
#: airtime_mvc/application/controllers/LocaleController.php:176
msgid ""
"Check this box to automatically switch on Master/Show source upon source "
"connection."
msgstr ""
"Cochez cette case pour démarrer automatiquement la source Maître/Emission "
"lors de la connexion."
"Cochez cette case démarre automatiquement la source Maître/Emission lors de "
"la connexion."
#: airtime_mvc/application/controllers/LocaleController.php:177
msgid ""
@ -2750,7 +2750,7 @@ msgstr "Lecture Programmée"
#: airtime_mvc/application/views/scripts/partialviews/header.phtml:54
msgid "ON AIR"
msgstr "EN DIRECT"
msgstr "DIRECT"
#: airtime_mvc/application/views/scripts/partialviews/header.phtml:55
msgid "Listen"
@ -2879,7 +2879,7 @@ msgstr "Alors vous êtes prêt à démarrer!"
#: airtime_mvc/application/views/scripts/dashboard/help.phtml:13
#, php-format
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
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*/
#current-user-container {
padding-bottom: 20px;
}
.override_help_icon, .icecast_metadata_help_icon {
cursor: help;
position: relative;
@ -2935,3 +2939,19 @@ dd .stream-status {
top: 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() {
if ($('#master-panel').length > 0)
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;
}
}
// 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;
}

View File

@ -386,12 +386,28 @@ function setupEventListeners() {
var json = $.parseJSON(data);
$('#content').empty().append(json.html);
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() {
setupEventListeners();
setSliderForReplayGain();
});

View File

@ -94,8 +94,83 @@ function populateUserTable() {
$(document).ready(function() {
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:""};

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
* New features
* 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
is True we recursively delete empty directories left over in
directory after the walk. """
if directory is None:
return
for root, dirs, files in os.walk(directory):
full_paths = ( os.path.join(root, name) for name in files
if is_file_supported(name) )
@ -162,8 +165,7 @@ def walk_supported(directory, clean_empties=False):
def file_locked(path):
cmd = "lsof %s" % (pipes.quote(path))
f = Popen(cmd, shell=True, stdout=PIPE).stdout
f = Popen(["lsof", path], stdout=PIPE).stdout
return bool(f.readlines())
def magic_move(old, new, after_dir_make=lambda : None):