Page MenuHomePhabricator

D20306.id48504.diff
No OneTemporary

D20306.id48504.diff

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

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)

Event Timeline