Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15420047
D20306.id48504.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
18 KB
Referenced Files
None
Subscribers
None
D20306.id48504.diff
View Options
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -10,7 +10,7 @@
'conpherence.pkg.css' => '3c8a0668',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => 'b797945d',
- 'core.pkg.js' => 'eaca003c',
+ 'core.pkg.js' => 'eb53fc5b',
'differential.pkg.css' => '8d8360fb',
'differential.pkg.js' => '67e02996',
'diffusion.pkg.css' => '42c75c37',
@@ -249,7 +249,7 @@
'rsrc/externals/javelin/lib/Routable.js' => '6a18c42e',
'rsrc/externals/javelin/lib/Router.js' => '32755edb',
'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/Vector.js' => 'e9c80beb',
'rsrc/externals/javelin/lib/WebSocket.js' => 'fdc13e4e',
@@ -409,7 +409,7 @@
'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-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/WorkboardCardTemplate.js' => '2a61f8d4',
'rsrc/js/application/projects/WorkboardColumn.js' => 'c3d24e63',
@@ -418,7 +418,7 @@
'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d',
'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'ebe83a6b',
'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-reorder-columns.js' => '8ac32fd9',
'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68',
@@ -664,7 +664,7 @@
'javelin-behavior-phuix-example' => 'c2c500a7',
'javelin-behavior-policy-control' => '0eaa33a9',
'javelin-behavior-policy-rule-editor' => '9347f172',
- 'javelin-behavior-project-boards' => '8512e4ea',
+ 'javelin-behavior-project-boards' => 'aad45445',
'javelin-behavior-project-create' => '34c53422',
'javelin-behavior-quicksand-blacklist' => '5a6f6a06',
'javelin-behavior-read-only-warning' => 'b9109f8f',
@@ -718,7 +718,7 @@
'javelin-routable' => '6a18c42e',
'javelin-router' => '32755edb',
'javelin-scrollbar' => 'a43ae2ae',
- 'javelin-sound' => 'e562708c',
+ 'javelin-sound' => 'd4cc2d2a',
'javelin-stratcom' => '0889b835',
'javelin-tokenizer' => '89a1ae3a',
'javelin-typeahead' => 'a4356cde',
@@ -737,7 +737,7 @@
'javelin-view-renderer' => '9aae2b66',
'javelin-view-visitor' => '308f9fe4',
'javelin-websocket' => 'fdc13e4e',
- 'javelin-workboard-board' => '65afb173',
+ 'javelin-workboard-board' => '3ba8e6ad',
'javelin-workboard-card' => '0392a5d8',
'javelin-workboard-card-template' => '2a61f8d4',
'javelin-workboard-column' => 'c3d24e63',
@@ -1227,6 +1227,18 @@
'javelin-behavior',
'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(
'javelin-behavior',
'javelin-stratcom',
@@ -1456,18 +1468,6 @@
'60cd9241' => array(
'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(
'javelin-behavior',
'javelin-dom',
@@ -1594,16 +1594,6 @@
'javelin-dom',
'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(
'javelin-behavior',
'javelin-diffusion-locate-file-source',
@@ -1848,6 +1838,16 @@
'javelin-dom',
'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(
'javelin-install',
'javelin-dom',
@@ -2041,6 +2041,9 @@
'd3799cb4' => array(
'javelin-install',
),
+ 'd4cc2d2a' => array(
+ 'javelin-install',
+ ),
'd8a86cfb' => array(
'javelin-behavior',
'javelin-dom',
@@ -2075,9 +2078,6 @@
'javelin-dom',
'javelin-history',
),
- 'e562708c' => array(
- 'javelin-install',
- ),
'e5bdb730' => array(
'javelin-behavior',
'javelin-stratcom',
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -4183,6 +4183,7 @@
'PhabricatorProjectTriggerManiphestStatusRule' => 'applications/project/trigger/PhabricatorProjectTriggerManiphestStatusRule.php',
'PhabricatorProjectTriggerNameTransaction' => 'applications/project/xaction/trigger/PhabricatorProjectTriggerNameTransaction.php',
'PhabricatorProjectTriggerPHIDType' => 'applications/project/phid/PhabricatorProjectTriggerPHIDType.php',
+ 'PhabricatorProjectTriggerPlaySoundRule' => 'applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php',
'PhabricatorProjectTriggerQuery' => 'applications/project/query/PhabricatorProjectTriggerQuery.php',
'PhabricatorProjectTriggerRule' => 'applications/project/trigger/PhabricatorProjectTriggerRule.php',
'PhabricatorProjectTriggerRuleRecord' => 'applications/project/trigger/PhabricatorProjectTriggerRuleRecord.php',
@@ -10317,6 +10318,7 @@
'PhabricatorProjectTriggerManiphestStatusRule' => 'PhabricatorProjectTriggerRule',
'PhabricatorProjectTriggerNameTransaction' => 'PhabricatorProjectTriggerTransactionType',
'PhabricatorProjectTriggerPHIDType' => 'PhabricatorPHIDType',
+ 'PhabricatorProjectTriggerPlaySoundRule' => 'PhabricatorProjectTriggerRule',
'PhabricatorProjectTriggerQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorProjectTriggerRule' => 'Phobject',
'PhabricatorProjectTriggerRuleRecord' => 'Phobject',
diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
--- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php
+++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
@@ -542,6 +542,7 @@
$templates = array();
$all_tasks = array();
$column_templates = array();
+ $sounds = array();
foreach ($visible_columns as $column_phid => $column) {
$column_tasks = $column_phids[$column_phid];
@@ -629,6 +630,10 @@
if ($trigger) {
$preview_effect = $trigger->getPreviewEffect()
->toDictionary();
+
+ foreach ($trigger->getSoundEffects() as $sound) {
+ $sounds[] = $sound;
+ }
}
}
@@ -685,6 +690,7 @@
'boardID' => $board_id,
'projectPHID' => $project->getPHID(),
+ 'preloadSounds' => $sounds,
);
$this->initBehavior('project-boards', $behavior_config);
diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php
--- a/src/applications/project/controller/PhabricatorProjectController.php
+++ b/src/applications/project/controller/PhabricatorProjectController.php
@@ -152,7 +152,8 @@
protected function newCardResponse(
$board_phid,
$object_phid,
- PhabricatorProjectColumnOrder $ordering = null) {
+ PhabricatorProjectColumnOrder $ordering = null,
+ $sounds = array()) {
$viewer = $this->getViewer();
@@ -166,7 +167,8 @@
->setViewer($viewer)
->setBoardPHID($board_phid)
->setObjectPHID($object_phid)
- ->setVisiblePHIDs($visible_phids);
+ ->setVisiblePHIDs($visible_phids)
+ ->setSounds($sounds);
if ($ordering) {
$engine->setOrdering($ordering);
diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php
--- a/src/applications/project/controller/PhabricatorProjectMoveController.php
+++ b/src/applications/project/controller/PhabricatorProjectMoveController.php
@@ -111,6 +111,7 @@
$xactions[] = $header_xaction;
}
+ $sounds = array();
if ($column->canHaveTrigger()) {
$trigger = $column->getTrigger();
if ($trigger) {
@@ -121,6 +122,10 @@
foreach ($trigger_xactions as $trigger_xaction) {
$xactions[] = $trigger_xaction;
}
+
+ foreach ($trigger->getSoundEffects() as $effect) {
+ $sounds[] = $effect;
+ }
}
}
@@ -133,7 +138,11 @@
$editor->applyTransactions($object, $xactions);
- return $this->newCardResponse($board_phid, $object_phid, $ordering);
+ return $this->newCardResponse(
+ $board_phid,
+ $object_phid,
+ $ordering,
+ $sounds);
}
}
diff --git a/src/applications/project/engine/PhabricatorBoardResponseEngine.php b/src/applications/project/engine/PhabricatorBoardResponseEngine.php
--- a/src/applications/project/engine/PhabricatorBoardResponseEngine.php
+++ b/src/applications/project/engine/PhabricatorBoardResponseEngine.php
@@ -7,6 +7,7 @@
private $objectPHID;
private $visiblePHIDs;
private $ordering;
+ private $sounds;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
@@ -53,6 +54,15 @@
return $this->ordering;
}
+ public function setSounds(array $sounds) {
+ $this->sounds = $sounds;
+ return $this;
+ }
+
+ public function getSounds() {
+ return $this->sounds;
+ }
+
public function buildResponse() {
$viewer = $this->getViewer();
$object_phid = $this->getObjectPHID();
@@ -150,6 +160,7 @@
'columnMaps' => $natural,
'cards' => $cards,
'headers' => $headers,
+ 'sounds' => $this->getSounds(),
);
return id(new AphrontAjaxResponse())
diff --git a/src/applications/project/storage/PhabricatorProjectTrigger.php b/src/applications/project/storage/PhabricatorProjectTrigger.php
--- a/src/applications/project/storage/PhabricatorProjectTrigger.php
+++ b/src/applications/project/storage/PhabricatorProjectTrigger.php
@@ -245,6 +245,18 @@
->setContent($header);
}
+ public function getSoundEffects() {
+ $sounds = array();
+
+ foreach ($this->getTriggerRules() as $rule) {
+ foreach ($rule->getSoundEffects() as $effect) {
+ $sounds[] = $effect;
+ }
+ }
+
+ return $sounds;
+ }
+
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php
new file mode 100644
--- /dev/null
+++ b/src/applications/project/trigger/PhabricatorProjectTriggerPlaySoundRule.php
@@ -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;
+ }
+
+}
diff --git a/src/applications/project/trigger/PhabricatorProjectTriggerRule.php b/src/applications/project/trigger/PhabricatorProjectTriggerRule.php
--- a/src/applications/project/trigger/PhabricatorProjectTriggerRule.php
+++ b/src/applications/project/trigger/PhabricatorProjectTriggerRule.php
@@ -60,6 +60,10 @@
return null;
}
+ public function getSoundEffects() {
+ return array();
+ }
+
final public function getDropTransactions($object, $value) {
return $this->newDropTransactions($object, $value);
}
diff --git a/webroot/rsrc/externals/javelin/lib/Sound.js b/webroot/rsrc/externals/javelin/lib/Sound.js
--- a/webroot/rsrc/externals/javelin/lib/Sound.js
+++ b/webroot/rsrc/externals/javelin/lib/Sound.js
@@ -8,31 +8,75 @@
JX.install('Sound', {
statics: {
_sounds: {},
+ _queue: [],
+ _playingQueue: false,
load: function(uri) {
var self = JX.Sound;
if (!(uri in self._sounds)) {
- self._sounds[uri] = JX.$N(
+ var audio = JX.$N(
'audio',
{
src: uri,
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;
self.load(uri);
var sound = self._sounds[uri];
try {
- sound.play();
+ sound.onended = callback || JX.bag;
+ sound.play().then(JX.bag, callback || JX.bag);
} catch (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;
+ }
}
+
}
});
diff --git a/webroot/rsrc/js/application/projects/WorkboardBoard.js b/webroot/rsrc/js/application/projects/WorkboardBoard.js
--- a/webroot/rsrc/js/application/projects/WorkboardBoard.js
+++ b/webroot/rsrc/js/application/projects/WorkboardBoard.js
@@ -529,6 +529,11 @@
this.updateCard(response);
+ var sounds = response.sounds || [];
+ for (var ii = 0; ii < sounds.length; ii++) {
+ JX.Sound.queue(sounds[ii]);
+ }
+
list.unlock();
},
diff --git a/webroot/rsrc/js/application/projects/behavior-project-boards.js b/webroot/rsrc/js/application/projects/behavior-project-boards.js
--- a/webroot/rsrc/js/application/projects/behavior-project-boards.js
+++ b/webroot/rsrc/js/application/projects/behavior-project-boards.js
@@ -166,4 +166,16 @@
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();
+ });
+
});
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Mar 22, 10:53 AM (11 h, 11 m ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7716895
Default Alt Text
D20306.id48504.diff (18 KB)
Attached To
Mode
D20306: Add a "Play Sound" workboard trigger rule
Attached
Detach File
Event Timeline
Log In to Comment