Add a "Play Sound" workboard trigger rule
Summary: Ref T5474. Allow columns to play a sound when tasks are dropped. This is a little tricky because Safari has changed somewhat recently to require some gymnastics to play sounds when the user didn't explicitly click something. Preloading the sound on the first mouse interaction, then playing and immediately pausing it seems to work, though. Test Plan: Added a trigger with 5 sounds. In Safari, Chrome, and Firefox, dropped a card into the column. In all browsers, heard a nice sequence of 5 sounds played one after the other. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T5474 Differential Revision: https://secure.phabricator.com/D20306
This commit is contained in:
@@ -10,7 +10,7 @@ return array(
|
|||||||
'conpherence.pkg.css' => '3c8a0668',
|
'conpherence.pkg.css' => '3c8a0668',
|
||||||
'conpherence.pkg.js' => '020aebcf',
|
'conpherence.pkg.js' => '020aebcf',
|
||||||
'core.pkg.css' => 'b797945d',
|
'core.pkg.css' => 'b797945d',
|
||||||
'core.pkg.js' => 'eaca003c',
|
'core.pkg.js' => 'eb53fc5b',
|
||||||
'differential.pkg.css' => '8d8360fb',
|
'differential.pkg.css' => '8d8360fb',
|
||||||
'differential.pkg.js' => '67e02996',
|
'differential.pkg.js' => '67e02996',
|
||||||
'diffusion.pkg.css' => '42c75c37',
|
'diffusion.pkg.css' => '42c75c37',
|
||||||
@@ -249,7 +249,7 @@ return array(
|
|||||||
'rsrc/externals/javelin/lib/Routable.js' => '6a18c42e',
|
'rsrc/externals/javelin/lib/Routable.js' => '6a18c42e',
|
||||||
'rsrc/externals/javelin/lib/Router.js' => '32755edb',
|
'rsrc/externals/javelin/lib/Router.js' => '32755edb',
|
||||||
'rsrc/externals/javelin/lib/Scrollbar.js' => 'a43ae2ae',
|
'rsrc/externals/javelin/lib/Scrollbar.js' => 'a43ae2ae',
|
||||||
'rsrc/externals/javelin/lib/Sound.js' => 'e562708c',
|
'rsrc/externals/javelin/lib/Sound.js' => 'd4cc2d2a',
|
||||||
'rsrc/externals/javelin/lib/URI.js' => '2e255291',
|
'rsrc/externals/javelin/lib/URI.js' => '2e255291',
|
||||||
'rsrc/externals/javelin/lib/Vector.js' => 'e9c80beb',
|
'rsrc/externals/javelin/lib/Vector.js' => 'e9c80beb',
|
||||||
'rsrc/externals/javelin/lib/WebSocket.js' => 'fdc13e4e',
|
'rsrc/externals/javelin/lib/WebSocket.js' => 'fdc13e4e',
|
||||||
@@ -409,7 +409,7 @@ return array(
|
|||||||
'rsrc/js/application/phortune/phortune-credit-card-form.js' => 'd12d214f',
|
'rsrc/js/application/phortune/phortune-credit-card-form.js' => 'd12d214f',
|
||||||
'rsrc/js/application/policy/behavior-policy-control.js' => '0eaa33a9',
|
'rsrc/js/application/policy/behavior-policy-control.js' => '0eaa33a9',
|
||||||
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '9347f172',
|
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '9347f172',
|
||||||
'rsrc/js/application/projects/WorkboardBoard.js' => '65afb173',
|
'rsrc/js/application/projects/WorkboardBoard.js' => '3ba8e6ad',
|
||||||
'rsrc/js/application/projects/WorkboardCard.js' => '0392a5d8',
|
'rsrc/js/application/projects/WorkboardCard.js' => '0392a5d8',
|
||||||
'rsrc/js/application/projects/WorkboardCardTemplate.js' => '2a61f8d4',
|
'rsrc/js/application/projects/WorkboardCardTemplate.js' => '2a61f8d4',
|
||||||
'rsrc/js/application/projects/WorkboardColumn.js' => 'c3d24e63',
|
'rsrc/js/application/projects/WorkboardColumn.js' => 'c3d24e63',
|
||||||
@@ -418,7 +418,7 @@ return array(
|
|||||||
'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d',
|
'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d',
|
||||||
'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'ebe83a6b',
|
'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'ebe83a6b',
|
||||||
'rsrc/js/application/projects/WorkboardOrderTemplate.js' => '03e8891f',
|
'rsrc/js/application/projects/WorkboardOrderTemplate.js' => '03e8891f',
|
||||||
'rsrc/js/application/projects/behavior-project-boards.js' => '8512e4ea',
|
'rsrc/js/application/projects/behavior-project-boards.js' => 'aad45445',
|
||||||
'rsrc/js/application/projects/behavior-project-create.js' => '34c53422',
|
'rsrc/js/application/projects/behavior-project-create.js' => '34c53422',
|
||||||
'rsrc/js/application/projects/behavior-reorder-columns.js' => '8ac32fd9',
|
'rsrc/js/application/projects/behavior-reorder-columns.js' => '8ac32fd9',
|
||||||
'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68',
|
'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68',
|
||||||
@@ -664,7 +664,7 @@ return array(
|
|||||||
'javelin-behavior-phuix-example' => 'c2c500a7',
|
'javelin-behavior-phuix-example' => 'c2c500a7',
|
||||||
'javelin-behavior-policy-control' => '0eaa33a9',
|
'javelin-behavior-policy-control' => '0eaa33a9',
|
||||||
'javelin-behavior-policy-rule-editor' => '9347f172',
|
'javelin-behavior-policy-rule-editor' => '9347f172',
|
||||||
'javelin-behavior-project-boards' => '8512e4ea',
|
'javelin-behavior-project-boards' => 'aad45445',
|
||||||
'javelin-behavior-project-create' => '34c53422',
|
'javelin-behavior-project-create' => '34c53422',
|
||||||
'javelin-behavior-quicksand-blacklist' => '5a6f6a06',
|
'javelin-behavior-quicksand-blacklist' => '5a6f6a06',
|
||||||
'javelin-behavior-read-only-warning' => 'b9109f8f',
|
'javelin-behavior-read-only-warning' => 'b9109f8f',
|
||||||
@@ -718,7 +718,7 @@ return array(
|
|||||||
'javelin-routable' => '6a18c42e',
|
'javelin-routable' => '6a18c42e',
|
||||||
'javelin-router' => '32755edb',
|
'javelin-router' => '32755edb',
|
||||||
'javelin-scrollbar' => 'a43ae2ae',
|
'javelin-scrollbar' => 'a43ae2ae',
|
||||||
'javelin-sound' => 'e562708c',
|
'javelin-sound' => 'd4cc2d2a',
|
||||||
'javelin-stratcom' => '0889b835',
|
'javelin-stratcom' => '0889b835',
|
||||||
'javelin-tokenizer' => '89a1ae3a',
|
'javelin-tokenizer' => '89a1ae3a',
|
||||||
'javelin-typeahead' => 'a4356cde',
|
'javelin-typeahead' => 'a4356cde',
|
||||||
@@ -737,7 +737,7 @@ return array(
|
|||||||
'javelin-view-renderer' => '9aae2b66',
|
'javelin-view-renderer' => '9aae2b66',
|
||||||
'javelin-view-visitor' => '308f9fe4',
|
'javelin-view-visitor' => '308f9fe4',
|
||||||
'javelin-websocket' => 'fdc13e4e',
|
'javelin-websocket' => 'fdc13e4e',
|
||||||
'javelin-workboard-board' => '65afb173',
|
'javelin-workboard-board' => '3ba8e6ad',
|
||||||
'javelin-workboard-card' => '0392a5d8',
|
'javelin-workboard-card' => '0392a5d8',
|
||||||
'javelin-workboard-card-template' => '2a61f8d4',
|
'javelin-workboard-card-template' => '2a61f8d4',
|
||||||
'javelin-workboard-column' => 'c3d24e63',
|
'javelin-workboard-column' => 'c3d24e63',
|
||||||
@@ -1227,6 +1227,18 @@ return array(
|
|||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'phabricator-prefab',
|
'phabricator-prefab',
|
||||||
),
|
),
|
||||||
|
'3ba8e6ad' => array(
|
||||||
|
'javelin-install',
|
||||||
|
'javelin-dom',
|
||||||
|
'javelin-util',
|
||||||
|
'javelin-stratcom',
|
||||||
|
'javelin-workflow',
|
||||||
|
'phabricator-draggable-list',
|
||||||
|
'javelin-workboard-column',
|
||||||
|
'javelin-workboard-header-template',
|
||||||
|
'javelin-workboard-card-template',
|
||||||
|
'javelin-workboard-order-template',
|
||||||
|
),
|
||||||
'3dc5ad43' => array(
|
'3dc5ad43' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-stratcom',
|
'javelin-stratcom',
|
||||||
@@ -1456,18 +1468,6 @@ return array(
|
|||||||
'60cd9241' => array(
|
'60cd9241' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
),
|
),
|
||||||
'65afb173' => array(
|
|
||||||
'javelin-install',
|
|
||||||
'javelin-dom',
|
|
||||||
'javelin-util',
|
|
||||||
'javelin-stratcom',
|
|
||||||
'javelin-workflow',
|
|
||||||
'phabricator-draggable-list',
|
|
||||||
'javelin-workboard-column',
|
|
||||||
'javelin-workboard-header-template',
|
|
||||||
'javelin-workboard-card-template',
|
|
||||||
'javelin-workboard-order-template',
|
|
||||||
),
|
|
||||||
'65bb0011' => array(
|
'65bb0011' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
@@ -1594,16 +1594,6 @@ return array(
|
|||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
'javelin-vector',
|
'javelin-vector',
|
||||||
),
|
),
|
||||||
'8512e4ea' => array(
|
|
||||||
'javelin-behavior',
|
|
||||||
'javelin-dom',
|
|
||||||
'javelin-util',
|
|
||||||
'javelin-vector',
|
|
||||||
'javelin-stratcom',
|
|
||||||
'javelin-workflow',
|
|
||||||
'javelin-workboard-controller',
|
|
||||||
'javelin-workboard-drop-effect',
|
|
||||||
),
|
|
||||||
'87428eb2' => array(
|
'87428eb2' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-diffusion-locate-file-source',
|
'javelin-diffusion-locate-file-source',
|
||||||
@@ -1848,6 +1838,16 @@ return array(
|
|||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
),
|
),
|
||||||
|
'aad45445' => array(
|
||||||
|
'javelin-behavior',
|
||||||
|
'javelin-dom',
|
||||||
|
'javelin-util',
|
||||||
|
'javelin-vector',
|
||||||
|
'javelin-stratcom',
|
||||||
|
'javelin-workflow',
|
||||||
|
'javelin-workboard-controller',
|
||||||
|
'javelin-workboard-drop-effect',
|
||||||
|
),
|
||||||
'ab85e184' => array(
|
'ab85e184' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
@@ -2041,6 +2041,9 @@ return array(
|
|||||||
'd3799cb4' => array(
|
'd3799cb4' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
),
|
),
|
||||||
|
'd4cc2d2a' => array(
|
||||||
|
'javelin-install',
|
||||||
|
),
|
||||||
'd8a86cfb' => array(
|
'd8a86cfb' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
@@ -2075,9 +2078,6 @@ return array(
|
|||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
'javelin-history',
|
'javelin-history',
|
||||||
),
|
),
|
||||||
'e562708c' => array(
|
|
||||||
'javelin-install',
|
|
||||||
),
|
|
||||||
'e5bdb730' => array(
|
'e5bdb730' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-stratcom',
|
'javelin-stratcom',
|
||||||
|
|||||||
@@ -4183,6 +4183,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorProjectTriggerManiphestStatusRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php',
|
'PhabricatorProjectTriggerManiphestStatusRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php',
|
||||||
'PhabricatorProjectTriggerNameTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerNameTransaction.php',
|
'PhabricatorProjectTriggerNameTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerNameTransaction.php',
|
||||||
'PhabricatorProjectTriggerPHIDType' => 'applications/project/phid/PhabricatorProjectTriggerPHIDType.php',
|
'PhabricatorProjectTriggerPHIDType' => 'applications/project/phid/PhabricatorProjectTriggerPHIDType.php',
|
||||||
|
'PhabricatorProjectTriggerPlaySoundRule' => 'applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php',
|
||||||
'PhabricatorProjectTriggerQuery' => 'applications/project/query/PhabricatorProjectTriggerQuery.php',
|
'PhabricatorProjectTriggerQuery' => 'applications/project/query/PhabricatorProjectTriggerQuery.php',
|
||||||
'PhabricatorProjectTriggerRule' => 'applications/project/trigger/PhabricatorProjectTriggerRule.php',
|
'PhabricatorProjectTriggerRule' => 'applications/project/trigger/PhabricatorProjectTriggerRule.php',
|
||||||
'PhabricatorProjectTriggerRuleRecord' => 'applications/project/trigger/PhabricatorProjectTriggerRuleRecord.php',
|
'PhabricatorProjectTriggerRuleRecord' => 'applications/project/trigger/PhabricatorProjectTriggerRuleRecord.php',
|
||||||
@@ -10317,6 +10318,7 @@ phutil_register_library_map(array(
|
|||||||
'PhabricatorProjectTriggerManiphestStatusRule' => 'PhabricatorProjectTriggerRule',
|
'PhabricatorProjectTriggerManiphestStatusRule' => 'PhabricatorProjectTriggerRule',
|
||||||
'PhabricatorProjectTriggerNameTransaction' => 'PhabricatorProjectTriggerTransactionType',
|
'PhabricatorProjectTriggerNameTransaction' => 'PhabricatorProjectTriggerTransactionType',
|
||||||
'PhabricatorProjectTriggerPHIDType' => 'PhabricatorPHIDType',
|
'PhabricatorProjectTriggerPHIDType' => 'PhabricatorPHIDType',
|
||||||
|
'PhabricatorProjectTriggerPlaySoundRule' => 'PhabricatorProjectTriggerRule',
|
||||||
'PhabricatorProjectTriggerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhabricatorProjectTriggerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhabricatorProjectTriggerRule' => 'Phobject',
|
'PhabricatorProjectTriggerRule' => 'Phobject',
|
||||||
'PhabricatorProjectTriggerRuleRecord' => 'Phobject',
|
'PhabricatorProjectTriggerRuleRecord' => 'Phobject',
|
||||||
|
|||||||
@@ -542,6 +542,7 @@ final class PhabricatorProjectBoardViewController
|
|||||||
$templates = array();
|
$templates = array();
|
||||||
$all_tasks = array();
|
$all_tasks = array();
|
||||||
$column_templates = array();
|
$column_templates = array();
|
||||||
|
$sounds = array();
|
||||||
foreach ($visible_columns as $column_phid => $column) {
|
foreach ($visible_columns as $column_phid => $column) {
|
||||||
$column_tasks = $column_phids[$column_phid];
|
$column_tasks = $column_phids[$column_phid];
|
||||||
|
|
||||||
@@ -629,6 +630,10 @@ final class PhabricatorProjectBoardViewController
|
|||||||
if ($trigger) {
|
if ($trigger) {
|
||||||
$preview_effect = $trigger->getPreviewEffect()
|
$preview_effect = $trigger->getPreviewEffect()
|
||||||
->toDictionary();
|
->toDictionary();
|
||||||
|
|
||||||
|
foreach ($trigger->getSoundEffects() as $sound) {
|
||||||
|
$sounds[] = $sound;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,6 +690,7 @@ final class PhabricatorProjectBoardViewController
|
|||||||
|
|
||||||
'boardID' => $board_id,
|
'boardID' => $board_id,
|
||||||
'projectPHID' => $project->getPHID(),
|
'projectPHID' => $project->getPHID(),
|
||||||
|
'preloadSounds' => $sounds,
|
||||||
);
|
);
|
||||||
$this->initBehavior('project-boards', $behavior_config);
|
$this->initBehavior('project-boards', $behavior_config);
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,8 @@ abstract class PhabricatorProjectController extends PhabricatorController {
|
|||||||
protected function newCardResponse(
|
protected function newCardResponse(
|
||||||
$board_phid,
|
$board_phid,
|
||||||
$object_phid,
|
$object_phid,
|
||||||
PhabricatorProjectColumnOrder $ordering = null) {
|
PhabricatorProjectColumnOrder $ordering = null,
|
||||||
|
$sounds = array()) {
|
||||||
|
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
@@ -166,7 +167,8 @@ abstract class PhabricatorProjectController extends PhabricatorController {
|
|||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->setBoardPHID($board_phid)
|
->setBoardPHID($board_phid)
|
||||||
->setObjectPHID($object_phid)
|
->setObjectPHID($object_phid)
|
||||||
->setVisiblePHIDs($visible_phids);
|
->setVisiblePHIDs($visible_phids)
|
||||||
|
->setSounds($sounds);
|
||||||
|
|
||||||
if ($ordering) {
|
if ($ordering) {
|
||||||
$engine->setOrdering($ordering);
|
$engine->setOrdering($ordering);
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ final class PhabricatorProjectMoveController
|
|||||||
$xactions[] = $header_xaction;
|
$xactions[] = $header_xaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$sounds = array();
|
||||||
if ($column->canHaveTrigger()) {
|
if ($column->canHaveTrigger()) {
|
||||||
$trigger = $column->getTrigger();
|
$trigger = $column->getTrigger();
|
||||||
if ($trigger) {
|
if ($trigger) {
|
||||||
@@ -121,6 +122,10 @@ final class PhabricatorProjectMoveController
|
|||||||
foreach ($trigger_xactions as $trigger_xaction) {
|
foreach ($trigger_xactions as $trigger_xaction) {
|
||||||
$xactions[] = $trigger_xaction;
|
$xactions[] = $trigger_xaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($trigger->getSoundEffects() as $effect) {
|
||||||
|
$sounds[] = $effect;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +138,11 @@ final class PhabricatorProjectMoveController
|
|||||||
|
|
||||||
$editor->applyTransactions($object, $xactions);
|
$editor->applyTransactions($object, $xactions);
|
||||||
|
|
||||||
return $this->newCardResponse($board_phid, $object_phid, $ordering);
|
return $this->newCardResponse(
|
||||||
|
$board_phid,
|
||||||
|
$object_phid,
|
||||||
|
$ordering,
|
||||||
|
$sounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ final class PhabricatorBoardResponseEngine extends Phobject {
|
|||||||
private $objectPHID;
|
private $objectPHID;
|
||||||
private $visiblePHIDs;
|
private $visiblePHIDs;
|
||||||
private $ordering;
|
private $ordering;
|
||||||
|
private $sounds;
|
||||||
|
|
||||||
public function setViewer(PhabricatorUser $viewer) {
|
public function setViewer(PhabricatorUser $viewer) {
|
||||||
$this->viewer = $viewer;
|
$this->viewer = $viewer;
|
||||||
@@ -53,6 +54,15 @@ final class PhabricatorBoardResponseEngine extends Phobject {
|
|||||||
return $this->ordering;
|
return $this->ordering;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setSounds(array $sounds) {
|
||||||
|
$this->sounds = $sounds;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSounds() {
|
||||||
|
return $this->sounds;
|
||||||
|
}
|
||||||
|
|
||||||
public function buildResponse() {
|
public function buildResponse() {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
$object_phid = $this->getObjectPHID();
|
$object_phid = $this->getObjectPHID();
|
||||||
@@ -150,6 +160,7 @@ final class PhabricatorBoardResponseEngine extends Phobject {
|
|||||||
'columnMaps' => $natural,
|
'columnMaps' => $natural,
|
||||||
'cards' => $cards,
|
'cards' => $cards,
|
||||||
'headers' => $headers,
|
'headers' => $headers,
|
||||||
|
'sounds' => $this->getSounds(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return id(new AphrontAjaxResponse())
|
return id(new AphrontAjaxResponse())
|
||||||
|
|||||||
@@ -245,6 +245,18 @@ final class PhabricatorProjectTrigger
|
|||||||
->setContent($header);
|
->setContent($header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSoundEffects() {
|
||||||
|
$sounds = array();
|
||||||
|
|
||||||
|
foreach ($this->getTriggerRules() as $rule) {
|
||||||
|
foreach ($rule->getSoundEffects() as $effect) {
|
||||||
|
$sounds[] = $effect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorProjectTriggerPlaySoundRule
|
||||||
|
extends PhabricatorProjectTriggerRule {
|
||||||
|
|
||||||
|
const TRIGGERTYPE = 'sound';
|
||||||
|
|
||||||
|
public function getSelectControlName() {
|
||||||
|
return pht('Play sound');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertValidRuleValue($value) {
|
||||||
|
if (!is_string($value)) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Status rule value should be a string, but is not (value is "%s").',
|
||||||
|
phutil_describe_type($value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$map = self::getSoundMap();
|
||||||
|
|
||||||
|
if (!isset($map[$value])) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Rule value ("%s") is not a valid sound.',
|
||||||
|
$value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newDropTransactions($object, $value) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function newDropEffects($value) {
|
||||||
|
$sound_icon = 'fa-volume-up';
|
||||||
|
$sound_color = 'blue';
|
||||||
|
$sound_name = self::getSoundName($value);
|
||||||
|
|
||||||
|
$content = pht(
|
||||||
|
'Play sound %s.',
|
||||||
|
phutil_tag('strong', array(), $sound_name));
|
||||||
|
|
||||||
|
return array(
|
||||||
|
$this->newEffect()
|
||||||
|
->setIcon($sound_icon)
|
||||||
|
->setColor($sound_color)
|
||||||
|
->setContent($content),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getDefaultValue() {
|
||||||
|
return head_key(self::getSoundMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getPHUIXControlType() {
|
||||||
|
return 'select';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getPHUIXControlSpecification() {
|
||||||
|
$map = self::getSoundMap();
|
||||||
|
$map = ipull($map, 'name');
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'options' => $map,
|
||||||
|
'order' => array_keys($map),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRuleViewLabel() {
|
||||||
|
return pht('Play Sound');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRuleViewDescription($value) {
|
||||||
|
$sound_name = self::getSoundName($value);
|
||||||
|
|
||||||
|
return pht(
|
||||||
|
'Play sound %s.',
|
||||||
|
phutil_tag('strong', array(), $sound_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRuleViewIcon($value) {
|
||||||
|
$sound_icon = 'fa-volume-up';
|
||||||
|
$sound_color = 'blue';
|
||||||
|
|
||||||
|
return id(new PHUIIconView())
|
||||||
|
->setIcon($sound_icon, $sound_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getSoundName($value) {
|
||||||
|
$map = self::getSoundMap();
|
||||||
|
$spec = idx($map, $value, array());
|
||||||
|
return idx($spec, 'name', $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getSoundMap() {
|
||||||
|
return array(
|
||||||
|
'bing' => array(
|
||||||
|
'name' => pht('Bing'),
|
||||||
|
'uri' => celerity_get_resource_uri('/rsrc/audio/basic/bing.mp3'),
|
||||||
|
),
|
||||||
|
'glass' => array(
|
||||||
|
'name' => pht('Glass'),
|
||||||
|
'uri' => celerity_get_resource_uri('/rsrc/audio/basic/ting.mp3'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSoundEffects() {
|
||||||
|
$value = $this->getValue();
|
||||||
|
|
||||||
|
$map = self::getSoundMap();
|
||||||
|
$spec = idx($map, $value, array());
|
||||||
|
|
||||||
|
$uris = array();
|
||||||
|
if (isset($spec['uri'])) {
|
||||||
|
$uris[] = $spec['uri'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $uris;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -60,6 +60,10 @@ abstract class PhabricatorProjectTriggerRule
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSoundEffects() {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
final public function getDropTransactions($object, $value) {
|
final public function getDropTransactions($object, $value) {
|
||||||
return $this->newDropTransactions($object, $value);
|
return $this->newDropTransactions($object, $value);
|
||||||
}
|
}
|
||||||
|
|||||||
50
webroot/rsrc/externals/javelin/lib/Sound.js
vendored
50
webroot/rsrc/externals/javelin/lib/Sound.js
vendored
@@ -8,31 +8,75 @@
|
|||||||
JX.install('Sound', {
|
JX.install('Sound', {
|
||||||
statics: {
|
statics: {
|
||||||
_sounds: {},
|
_sounds: {},
|
||||||
|
_queue: [],
|
||||||
|
_playingQueue: false,
|
||||||
|
|
||||||
load: function(uri) {
|
load: function(uri) {
|
||||||
var self = JX.Sound;
|
var self = JX.Sound;
|
||||||
|
|
||||||
if (!(uri in self._sounds)) {
|
if (!(uri in self._sounds)) {
|
||||||
self._sounds[uri] = JX.$N(
|
var audio = JX.$N(
|
||||||
'audio',
|
'audio',
|
||||||
{
|
{
|
||||||
src: uri,
|
src: uri,
|
||||||
preload: 'auto'
|
preload: 'auto'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// In Safari, it isn't good enough to just load a sound in response
|
||||||
|
// to a click: we must also play it. Once we've played it once, we
|
||||||
|
// can continue to play it freely.
|
||||||
|
|
||||||
|
// Play the sound, then immediately pause it. This rejects the "play()"
|
||||||
|
// promise but marks the audio as playable, so our "play()" method will
|
||||||
|
// work correctly later.
|
||||||
|
if (window.webkitAudioContext) {
|
||||||
|
audio.play().then(JX.bag, JX.bag);
|
||||||
|
audio.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
self._sounds[uri] = audio;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
play: function(uri) {
|
play: function(uri, callback) {
|
||||||
var self = JX.Sound;
|
var self = JX.Sound;
|
||||||
self.load(uri);
|
self.load(uri);
|
||||||
|
|
||||||
var sound = self._sounds[uri];
|
var sound = self._sounds[uri];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
sound.play();
|
sound.onended = callback || JX.bag;
|
||||||
|
sound.play().then(JX.bag, callback || JX.bag);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
JX.log(ex);
|
JX.log(ex);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
queue: function(uri) {
|
||||||
|
var self = JX.Sound;
|
||||||
|
self._queue.push(uri);
|
||||||
|
self._playQueue();
|
||||||
|
},
|
||||||
|
|
||||||
|
_playQueue: function() {
|
||||||
|
var self = JX.Sound;
|
||||||
|
if (self._playingQueue) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
self._playingQueue = true;
|
||||||
|
self._nextQueue();
|
||||||
|
},
|
||||||
|
|
||||||
|
_nextQueue: function() {
|
||||||
|
var self = JX.Sound;
|
||||||
|
if (self._queue.length) {
|
||||||
|
var next = self._queue[0];
|
||||||
|
self._queue.splice(0, 1);
|
||||||
|
self.play(next, self._nextQueue);
|
||||||
|
} else {
|
||||||
|
self._playingQueue = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -529,6 +529,11 @@ JX.install('WorkboardBoard', {
|
|||||||
|
|
||||||
this.updateCard(response);
|
this.updateCard(response);
|
||||||
|
|
||||||
|
var sounds = response.sounds || [];
|
||||||
|
for (var ii = 0; ii < sounds.length; ii++) {
|
||||||
|
JX.Sound.queue(sounds[ii]);
|
||||||
|
}
|
||||||
|
|
||||||
list.unlock();
|
list.unlock();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -166,4 +166,16 @@ JX.behavior('project-boards', function(config, statics) {
|
|||||||
|
|
||||||
board.start();
|
board.start();
|
||||||
|
|
||||||
|
// In Safari, we can only play sounds that we've already loaded, and we can
|
||||||
|
// only load them in response to an explicit user interaction like a click.
|
||||||
|
var sounds = config.preloadSounds;
|
||||||
|
var listener = JX.Stratcom.listen('mousedown', null, function() {
|
||||||
|
for (var ii = 0; ii < sounds.length; ii++) {
|
||||||
|
JX.Sound.load(sounds[ii]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove this callback once it has run once.
|
||||||
|
listener.remove();
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user