From 974997f6bc3502cbf30793ead69dacd57e8365f0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 23 Jul 2013 06:16:19 -0700 Subject: [PATCH] Modernize Countdown somewhat Summary: Far from perfect, but better? Test Plan: {F51153} {F51154} {F51155} Reviewers: btrahan, chad Reviewed By: chad CC: aran Differential Revision: https://secure.phabricator.com/D6533 --- src/__celerity_resource_map__.php | 4 +- src/__phutil_library_map__.php | 10 +- .../PhabricatorApplicationCountdown.php | 2 +- .../PhabricatorCountdownController.php | 17 ++- .../PhabricatorCountdownDeleteController.php | 5 + .../PhabricatorCountdownEditController.php | 56 ++++--- .../PhabricatorCountdownListController.php | 142 +++++++----------- .../PhabricatorCountdownViewController.php | 128 +++++++++------- .../query/PhabricatorCountdownQuery.php | 13 ++ .../PhabricatorCountdownSearchEngine.php | 96 ++++++++++++ .../PhabricatorCountdownRemarkupRule.php | 24 +-- .../storage/PhabricatorCountdown.php | 9 +- .../view/PhabricatorCountdownView.php | 78 ++++++++++ .../rsrc/css/application/countdown/timer.css | 42 +++--- .../rsrc/js/application/countdown/timer.js | 28 ++-- 15 files changed, 424 insertions(+), 230 deletions(-) create mode 100644 src/applications/countdown/query/PhabricatorCountdownSearchEngine.php create mode 100644 src/applications/countdown/view/PhabricatorCountdownView.php diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 9ada5748ff..be5197b76e 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -1407,7 +1407,7 @@ celerity_register_resource_map(array( ), 'javelin-behavior-countdown-timer' => array( - 'uri' => '/res/3c52aac2/rsrc/js/application/countdown/timer.js', + 'uri' => '/res/13d40efa/rsrc/js/application/countdown/timer.js', 'type' => 'js', 'requires' => array( @@ -3100,7 +3100,7 @@ celerity_register_resource_map(array( ), 'phabricator-countdown-css' => array( - 'uri' => '/res/0f646281/rsrc/css/application/countdown/timer.css', + 'uri' => '/res/c4a30296/rsrc/css/application/countdown/timer.css', 'type' => 'css', 'requires' => array( diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 79df81b268..68ba4a9a14 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1000,6 +1000,8 @@ phutil_register_library_map(array( 'PhabricatorCountdownPHIDTypeCountdown' => 'applications/countdown/phid/PhabricatorCountdownPHIDTypeCountdown.php', 'PhabricatorCountdownQuery' => 'applications/countdown/query/PhabricatorCountdownQuery.php', 'PhabricatorCountdownRemarkupRule' => 'applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php', + 'PhabricatorCountdownSearchEngine' => 'applications/countdown/query/PhabricatorCountdownSearchEngine.php', + 'PhabricatorCountdownView' => 'applications/countdown/view/PhabricatorCountdownView.php', 'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php', 'PhabricatorCountedToggleButtonsExample' => 'applications/uiexample/examples/PhabricatorCountedToggleButtonsExample.php', 'PhabricatorCrumbView' => 'view/layout/PhabricatorCrumbView.php', @@ -2995,10 +2997,16 @@ phutil_register_library_map(array( 'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO', 'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController', 'PhabricatorCountdownEditController' => 'PhabricatorCountdownController', - 'PhabricatorCountdownListController' => 'PhabricatorCountdownController', + 'PhabricatorCountdownListController' => + array( + 0 => 'PhabricatorCountdownController', + 1 => 'PhabricatorApplicationSearchResultsControllerInterface', + ), 'PhabricatorCountdownPHIDTypeCountdown' => 'PhabricatorPHIDType', 'PhabricatorCountdownQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorCountdownRemarkupRule' => 'PhabricatorRemarkupRuleObject', + 'PhabricatorCountdownSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhabricatorCountdownView' => 'AphrontTagView', 'PhabricatorCountdownViewController' => 'PhabricatorCountdownController', 'PhabricatorCountedToggleButtonsExample' => 'PhabricatorUIExample', 'PhabricatorCrumbView' => 'AphrontView', diff --git a/src/applications/countdown/application/PhabricatorApplicationCountdown.php b/src/applications/countdown/application/PhabricatorApplicationCountdown.php index a5dea04fd1..405280ca74 100644 --- a/src/applications/countdown/application/PhabricatorApplicationCountdown.php +++ b/src/applications/countdown/application/PhabricatorApplicationCountdown.php @@ -38,7 +38,7 @@ final class PhabricatorApplicationCountdown extends PhabricatorApplication { public function getRoutes() { return array( '/countdown/' => array( - '' + '(?:query/(?P[^/]+)/)?' => 'PhabricatorCountdownListController', '(?P[1-9]\d*)/' => 'PhabricatorCountdownViewController', diff --git a/src/applications/countdown/controller/PhabricatorCountdownController.php b/src/applications/countdown/controller/PhabricatorCountdownController.php index 93d0de89ba..84714b3882 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownController.php @@ -5,22 +5,27 @@ */ abstract class PhabricatorCountdownController extends PhabricatorController { - public function buildSideNavView() { + public function buildSideNavView($for_app = false) { $user = $this->getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - $nav->addFilter('', pht('All Countdowns'), - $this->getApplicationURI('')); - $nav->addFilter('', pht('Create Countdown'), - $this->getApplicationURI('edit/')); + if ($for_app) { + $nav->addFilter('create', pht('Create Countdown')); + } + + id(new PhabricatorCountdownSearchEngine()) + ->setViewer($user) + ->addNavigationItems($nav->getMenu()); + + $nav->selectFilter(null); return $nav; } public function buildApplicationMenu() { - return $this->buildSideNavView()->getMenu(); + return $this->buildSideNavView($for_app = true)->getMenu(); } public function buildApplicationCrumbs() { diff --git a/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php b/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php index 91c7d70530..1e22ce4f00 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownDeleteController.php @@ -20,6 +20,11 @@ final class PhabricatorCountdownDeleteController $countdown = id(new PhabricatorCountdownQuery()) ->setViewer($user) ->withIDs(array($this->id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) ->executeOne(); if (!$countdown) { diff --git a/src/applications/countdown/controller/PhabricatorCountdownEditController.php b/src/applications/countdown/controller/PhabricatorCountdownEditController.php index a5d24b5574..f277cb623f 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownEditController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownEditController.php @@ -15,25 +15,22 @@ final class PhabricatorCountdownEditController $request = $this->getRequest(); $user = $request->getUser(); - $action_label = pht('Create Countdown'); if ($this->id) { $countdown = id(new PhabricatorCountdownQuery()) ->setViewer($user) ->withIDs(array($this->id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) ->executeOne(); // If no countdown is found if (!$countdown) { return new Aphront404Response(); } - - if (($countdown->getAuthorPHID() != $user->getPHID()) - && $user->getIsAdmin() == false) { - return new Aphront403Response(); - } - - $action_label = pht('Update Countdown'); } else { $countdown = new PhabricatorCountdown(); $countdown->setEpoch(time()); @@ -87,9 +84,31 @@ final class PhabricatorCountdownEditController $display_epoch = $request->getStr('epoch'); } + $crumbs = $this->buildApplicationCrumbs(); + + $cancel_uri = '/countdown/'; + if ($countdown->getID()) { + $cancel_uri = '/countdown/'.$countdown->getID().'/'; + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName('C'.$countdown->getID()) + ->setHref($cancel_uri)); + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('Edit'))); + $submit_label = pht('Save Changes'); + } else { + $crumbs->addCrumb( + id(new PhabricatorCrumbView()) + ->setName(pht('Create Countdown'))); + $submit_label = pht('Create Countdown'); + } + + $form = id(new AphrontFormView()) ->setUser($user) ->setAction($request->getRequestURI()->getPath()) + ->setFlexible(true) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Title')) @@ -97,7 +116,7 @@ final class PhabricatorCountdownEditController ->setName('title')) ->appendChild( id(new AphrontFormTextControl()) - ->setLabel(pht('End date')) + ->setLabel(pht('End Date')) ->setValue($display_epoch) ->setName('epoch') ->setCaption(pht('Examples: '. @@ -105,31 +124,20 @@ final class PhabricatorCountdownEditController 'June 8 2011, 5 PM.'))) ->appendChild( id(new AphrontFormSubmitControl()) - ->addCancelButton('/countdown/') - ->setValue($action_label)); + ->addCancelButton($cancel_uri) + ->setValue($submit_label)); - $panel = id(new AphrontPanelView()) - ->setWidth(AphrontPanelView::WIDTH_FORM) - ->setHeader($action_label) - ->setNoBackground() - ->appendChild($form); - - $crumbs = $this - ->buildApplicationCrumbs() - ->addCrumb( - id(new PhabricatorCrumbView()) - ->setName($action_label) - ->setHref($this->getApplicationURI('edit/'))); return $this->buildApplicationPage( array( $crumbs, $error_view, - $panel, + $form, ), array( 'title' => pht('Edit Countdown'), 'device' => true, + 'dust' => true, )); } } diff --git a/src/applications/countdown/controller/PhabricatorCountdownListController.php b/src/applications/countdown/controller/PhabricatorCountdownListController.php index 836bc52ee3..40f3312ef7 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownListController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownListController.php @@ -1,107 +1,69 @@ queryKey = idx($data, 'queryKey'); + } public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + $controller = id(new PhabricatorApplicationSearchController($request)) + ->setQueryKey($this->queryKey) + ->setSearchEngine(new PhabricatorCountdownSearchEngine()) + ->setNavigation($this->buildSideNavView()); - $pager = new AphrontCursorPagerView(); - $pager->readFromRequest($request); + return $this->delegateToController($controller); + } - $query = id(new PhabricatorCountdownQuery()) - ->setViewer($user); + public function renderResultsList( + array $countdowns, + PhabricatorSavedQuery $query) { + assert_instances_of($countdowns, 'PhabricatorCountdown'); - $countdowns = $query->executeWithCursorPager($pager); + $viewer = $this->getRequest()->getUser(); - $phids = mpull($countdowns, 'getAuthorPHID'); - $handles = $this->loadViewerHandles($phids); + $this->loadHandles(mpull($countdowns, 'getAuthorPHID')); - $rows = array(); - foreach ($countdowns as $timer) { - $edit_button = null; - $delete_button = null; - if ($user->getIsAdmin() || - ($user->getPHID() == $timer->getAuthorPHID())) { - $edit_button = phutil_tag( - 'a', - array( - 'class' => 'small button grey', - 'href' => '/countdown/edit/'.$timer->getID().'/' - ), - pht('Edit')); - $delete_button = javelin_tag( - 'a', - array( - 'class' => 'small button grey', - 'href' => '/countdown/delete/'.$timer->getID().'/', - 'sigil' => 'workflow' - ), - pht('Delete')); + $list = new PhabricatorObjectItemListView(); + $list->setUser($viewer); + foreach ($countdowns as $countdown) { + $id = $countdown->getID(); + + $item = id(new PhabricatorObjectItemView()) + ->setObjectName("C{$id}") + ->setHeader($countdown->getTitle()) + ->setHref($this->getApplicationURI("{$id}/")) + ->addByline( + pht( + 'Created by %s', + $this->getHandle($countdown->getAuthorPHID())->renderLink())); + + $epoch = $countdown->getEpoch(); + if ($epoch >= time()) { + $item->addIcon( + 'none', + pht('Ends %s', phabricator_datetime($epoch, $viewer))); + } else { + $item->addIcon( + 'delete', + pht('Ended %s', phabricator_datetime($epoch, $viewer))); + $item->setDisabled(true); } - $rows[] = array( - $timer->getID(), - $handles[$timer->getAuthorPHID()]->renderLink(), - phutil_tag( - 'a', - array( - 'href' => '/countdown/'.$timer->getID().'/', - ), - $timer->getTitle()), - phabricator_datetime($timer->getEpoch(), $user), - $edit_button, - $delete_button, - ); + + $list->addItem($item); } - $table = new AphrontTableView($rows); - $table->setHeaders( - array( - pht('ID'), - pht('Author'), - pht('Title'), - pht('End Date'), - '', - '' - )); - - $table->setColumnClasses( - array( - null, - null, - 'wide pri', - null, - 'action', - 'action', - )); - - $panel = id(new AphrontPanelView()) - ->appendChild($table) - ->setHeader(pht('Countdowns')) - ->setNoBackground() - ->appendChild($pager); - - $crumbs = $this - ->buildApplicationCrumbs() - ->addCrumb( - id(new PhabricatorCrumbView()) - ->setName(pht('All Countdowns')) - ->setHref($this->getApplicationURI())); - - return $this->buildApplicationPage( - array( - $crumbs, - $panel - ), - array( - 'title' => pht('Countdown'), - 'device' => true, - )); + return $list; } + } diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php index 79bde565e9..9c7186c241 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php @@ -27,72 +27,90 @@ final class PhabricatorCountdownViewController return new Aphront404Response(); } - require_celerity_resource('phabricator-countdown-css'); + $countdown_view = id(new PhabricatorCountdownView()) + ->setUser($user) + ->setCountdown($countdown) + ->setHeadless(true); - $chrome_visible = $request->getBool('chrome', true); - $chrome_new = $chrome_visible ? false : null; - $chrome_link = phutil_tag( - 'a', - array( - 'href' => $request->getRequestURI()->alter('chrome', $chrome_new), - 'class' => 'phabricator-timer-chrome-link', - ), - $chrome_visible ? pht('Disable Chrome') : pht('Enable Chrome')); - - $container = celerity_generate_unique_node_id(); - $content = hsprintf( - '
-

%s · %s

-
- - - - - - - - %s%s%s%s -
%s%s%s%s
-
- %s -
', - $container, - $countdown->getTitle(), - phabricator_datetime($countdown->getEpoch(), $user), - pht('Days'), - pht('Hours'), - pht('Minutes'), - pht('Seconds'), - javelin_tag('td', array('sigil' => 'phabricator-timer-days'), ''), - javelin_tag('td', array('sigil' => 'phabricator-timer-hours'), ''), - javelin_tag('td', array('sigil' => 'phabricator-timer-minutes'), ''), - javelin_tag('td', array('sigil' => 'phabricator-timer-seconds'), ''), - $chrome_link); - - Javelin::initBehavior('countdown-timer', array( - 'timestamp' => $countdown->getEpoch(), - 'container' => $container, - )); - - $panel = $content; + $id = $countdown->getID(); + $title = $countdown->getTitle(); $crumbs = $this ->buildApplicationCrumbs() ->addCrumb( id(new PhabricatorCrumbView()) - ->setName($countdown->getTitle()) - ->setHref($this->getApplicationURI($this->id.'/'))); + ->setName("C{$id}")); + + $header = id(new PhabricatorHeaderView()) + ->setHeader($title); + + $actions = $this->buildActionListView($countdown); + $properties = $this->buildPropertyListView($countdown); + + $content = array( + $crumbs, + $header, + $actions, + $properties, + $countdown_view, + ); return $this->buildApplicationPage( + $content, array( - ($chrome_visible ? $crumbs : ''), - $panel, - ), - array( - 'title' => pht('Countdown: %s', $countdown->getTitle()), - 'chrome' => $chrome_visible, + 'title' => $title, 'device' => true, + 'dust' => true, )); } + private function buildActionListView(PhabricatorCountdown $countdown) { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $id = $countdown->getID(); + + $view = id(new PhabricatorActionListView()) + ->setUser($viewer); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $countdown, + PhabricatorPolicyCapability::CAN_EDIT); + + $view->addAction( + id(new PhabricatorActionView()) + ->setIcon('edit') + ->setName(pht('Edit Countdown')) + ->setHref($this->getApplicationURI("edit/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + + $view->addAction( + id(new PhabricatorActionView()) + ->setIcon('delete') + ->setName(pht('Delete Countdown')) + ->setHref($this->getApplicationURI("delete/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(true)); + + return $view; + } + + private function buildPropertyListView(PhabricatorCountdown $countdown) { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $this->loadHandles(array($countdown->getAuthorPHID())); + + $view = id(new PhabricatorPropertyListView()) + ->setUser($viewer); + + $view->addProperty( + pht('Author'), + $this->getHandle($countdown->getAuthorPHID())->renderLink()); + + return $view; + } + } diff --git a/src/applications/countdown/query/PhabricatorCountdownQuery.php b/src/applications/countdown/query/PhabricatorCountdownQuery.php index e6d8018a06..dc3de9c760 100644 --- a/src/applications/countdown/query/PhabricatorCountdownQuery.php +++ b/src/applications/countdown/query/PhabricatorCountdownQuery.php @@ -9,6 +9,7 @@ final class PhabricatorCountdownQuery private $ids; private $phids; private $authorPHIDs; + private $upcoming; public function withIDs(array $ids) { $this->ids = $ids; @@ -25,6 +26,11 @@ final class PhabricatorCountdownQuery return $this; } + public function withUpcoming($upcoming) { + $this->upcoming = true; + return $this; + } + protected function loadPage() { $table = new PhabricatorCountdown(); $conn_r = $table->establishConnection('r'); @@ -69,6 +75,13 @@ final class PhabricatorCountdownQuery $this->authorPHIDs); } + if ($this->upcoming) { + $where[] = qsprintf( + $conn_r, + 'epoch >= %d', + time()); + } + return $this->formatWhereClause($where); } diff --git a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php new file mode 100644 index 0000000000..753eb6f5d6 --- /dev/null +++ b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php @@ -0,0 +1,96 @@ +setParameter( + 'authorPHIDs', + array_values($request->getArr('authors'))); + + $saved->setParameter('upcoming', $request->getBool('upcoming')); + + return $saved; + } + + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { + $query = id(new PhabricatorCountdownQuery()); + + $author_phids = $saved->getParameter('authorPHIDs', array()); + if ($author_phids) { + $query->withAuthorPHIDs($author_phids); + } + + if ($saved->getParameter('upcoming')) { + $query->withUpcoming(true); + } + + return $query; + } + + public function buildSearchForm( + AphrontFormView $form, + PhabricatorSavedQuery $saved_query) { + $phids = $saved_query->getParameter('authorPHIDs', array()); + $handles = id(new PhabricatorObjectHandleData($phids)) + ->setViewer($this->requireViewer()) + ->loadHandles(); + $author_tokens = mpull($handles, 'getFullName', 'getPHID'); + + $upcoming = $saved_query->getParameter('upcoming'); + + $form + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setDatasource('/typeahead/common/users/') + ->setName('authors') + ->setLabel(pht('Authors')) + ->setValue($author_tokens)) + ->appendChild( + id(new AphrontFormCheckboxControl()) + ->addCheckbox( + 'upcoming', + 1, + pht('Show only countdowns that are still counting down.'), + $upcoming)); + + } + + protected function getURI($path) { + return '/countdown/'.$path; + } + + public function getBuiltinQueryNames() { + $names = array( + 'upcoming' => pht('Upcoming'), + 'all' => pht('All'), + ); + + if ($this->requireViewer()->getPHID()) { + $names['authored'] = pht('Authored'); + } + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + case 'authored': + return $query->setParameter( + 'authorPHIDs', + array($this->requireViewer()->getPHID())); + case 'upcoming': + return $query->setParameter('upcoming', true); + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + +} diff --git a/src/applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php b/src/applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php index 5ea0da934a..3134a9d40d 100644 --- a/src/applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php +++ b/src/applications/countdown/remarkup/PhabricatorCountdownRemarkupRule.php @@ -19,27 +19,11 @@ final class PhabricatorCountdownRemarkupRule } protected function renderObjectEmbed($object, $handle, $options) { - require_celerity_resource('javelin-behavior-countdown-timer'); + $viewer = $this->getEngine()->getConfig('viewer'); - $prefix = 'phabricator-timer-'; - $counter = phutil_tag( - 'span', - array( - 'id' => $object->getPHID(), - ), - array( - javelin_tag('span', array('sigil' => $prefix.'days'), ''), 'd', - javelin_tag('span', array('sigil' => $prefix.'hours'), ''), 'h', - javelin_tag('span', array('sigil' => $prefix.'minutes'), ''), 'm', - javelin_tag('span', array('sigil' => $prefix.'seconds'), ''), 's', - )); - - Javelin::initBehavior('countdown-timer', array( - 'timestamp' => $object->getEpoch(), - 'container' => $object->getPHID(), - )); - - return $counter; + return id(new PhabricatorCountdownView()) + ->setCountdown($object) + ->setUser($viewer); } } diff --git a/src/applications/countdown/storage/PhabricatorCountdown.php b/src/applications/countdown/storage/PhabricatorCountdown.php index f4d0c9af5b..ea4999d79a 100644 --- a/src/applications/countdown/storage/PhabricatorCountdown.php +++ b/src/applications/countdown/storage/PhabricatorCountdown.php @@ -29,11 +29,14 @@ final class PhabricatorCountdown return PhabricatorPolicies::POLICY_USER; } -/* -( PhabricatorPolicyInterface Implementation )------------------------- */ + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + public function getCapabilities() { return array( - PhabricatorPolicyCapability::CAN_VIEW + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, ); } @@ -41,6 +44,8 @@ final class PhabricatorCountdown switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); + case PhabricatorPolicyCapability::CAN_EDIT: + return PhabricatorPolicies::POLICY_NOONE; } } diff --git a/src/applications/countdown/view/PhabricatorCountdownView.php b/src/applications/countdown/view/PhabricatorCountdownView.php new file mode 100644 index 0000000000..9c09144b51 --- /dev/null +++ b/src/applications/countdown/view/PhabricatorCountdownView.php @@ -0,0 +1,78 @@ +headless = $headless; + return $this; + } + + public function setCountdown(PhabricatorCountdown $countdown) { + $this->countdown = $countdown; + return $this; + } + + + public function getTagContent() { + $countdown = $this->countdown; + + require_celerity_resource('phabricator-countdown-css'); + + $header = null; + if (!$this->headless) { + $header = phutil_tag( + 'div', + array( + 'class' => 'phabricator-timer-header', + ), + array( + "C".$countdown->getID(), + ' ', + phutil_tag( + 'a', + array( + 'href' => '/countdown/'.$countdown->getID(), + ), + $countdown->getTitle()), + )); + } + + + $container = celerity_generate_unique_node_id(); + $content = hsprintf( + '
+ %s + + + + + + + + %s%s%s%s +
%s%s%s%s
+
', + $container, + $header, + pht('Days'), + pht('Hours'), + pht('Minutes'), + pht('Seconds'), + javelin_tag('td', array('sigil' => 'phabricator-timer-days'), '-'), + javelin_tag('td', array('sigil' => 'phabricator-timer-hours'), '-'), + javelin_tag('td', array('sigil' => 'phabricator-timer-minutes'), '-'), + javelin_tag('td', array('sigil' => 'phabricator-timer-seconds'), '-')); + + Javelin::initBehavior('countdown-timer', array( + 'timestamp' => $countdown->getEpoch(), + 'container' => $container, + )); + + return $content; + } + +} diff --git a/webroot/rsrc/css/application/countdown/timer.css b/webroot/rsrc/css/application/countdown/timer.css index 22905f10e9..82e09ce5c7 100644 --- a/webroot/rsrc/css/application/countdown/timer.css +++ b/webroot/rsrc/css/application/countdown/timer.css @@ -3,29 +3,33 @@ */ .phabricator-timer { - width: 80%; - margin: 0 auto; - margin-top: 2em; - margin-bottom: 2em; - background: #efefef; - border-top: 1px solid #cccccc; + width: 90%; + margin: 12px auto; + border: 1px solid #666666; + border-radius: 4px; + background: #ffffff; + box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.25); +} + +.phabricator-remarkup .phabricator-timer { + width: 300px; } .phabricator-timer-header { - padding: 8px; - background: #dfdfdf; -} - -.phabricator-timer-pane { - padding: 8px 2em; + font-size: 14px; + font-weight: bold; + padding: 4px 8px; + background: #f0f0f0; + border-radius: 4px 4px 0 0; + border-bottom: 1px solid #d7d7d7; } .phabricator-timer-table { width: 100%; + margin: 8px 0; } .phabricator-timer-table th { - font-weight: bold; text-align: center; color: #666666; width: 10%; @@ -35,11 +39,13 @@ .phabricator-timer-table td { padding: 4px; text-align: center; - font-size: 4em; + font-size: 36px; + overflow: hidden; + line-height: auto; + height: auto; + line-height: 36px; } -.phabricator-timer-chrome-link { - float: right; - padding: 3px 6px; - font-size: 11px; +.phabricator-timer-table small { + color: #999999; } diff --git a/webroot/rsrc/js/application/countdown/timer.js b/webroot/rsrc/js/application/countdown/timer.js index 69a3e2265a..ce8f0a6bf4 100644 --- a/webroot/rsrc/js/application/countdown/timer.js +++ b/webroot/rsrc/js/application/countdown/timer.js @@ -19,29 +19,35 @@ JX.behavior('countdown-timer', function(config) { var hours = 0; var minutes = 0; var seconds = 0; + var partial = 0; - var current_timestamp = Math.round(new Date() / 1000); - var delta = config.timestamp - current_timestamp; + var current_timestamp = +new Date(); + + var delta = (config.timestamp * 1000) - current_timestamp; if (delta > 0) { - days = Math.floor(delta/86400); - delta -= days * 86400; + days = Math.floor(delta / 86400000); + delta -= days * 86400000; - hours = Math.floor(delta/3600); - delta -= hours * 3600; + hours = Math.floor(delta / 3600000); + delta -= hours * 3600000; - minutes = Math.floor(delta / 60); - delta -= minutes * 60; + minutes = Math.floor(delta / 60000); + delta -= minutes * 60000; - seconds = delta; + seconds = Math.floor(delta / 1000); + delta -= seconds * 1000; - setTimeout(calculateTimeLeft, 1000); + partial = Math.floor(delta / 100) || '0'; + + setTimeout(calculateTimeLeft, 100); } setComponent('days', days); setComponent('hours', hours); setComponent('minutes', minutes); - setComponent('seconds', seconds); + setComponent('seconds', [seconds, JX.$N('small', {}, ['.', partial])]); } + });