diff --git a/src/applications/calendar/view/PHUIUserAvailabilityView.php b/src/applications/calendar/view/PHUIUserAvailabilityView.php index 7ab0ace77a..f4f9696ba9 100644 --- a/src/applications/calendar/view/PHUIUserAvailabilityView.php +++ b/src/applications/calendar/view/PHUIUserAvailabilityView.php @@ -1,85 +1,85 @@ user = $user; return $this; } public function getAvailableUser() { return $this->user; } protected function getTagContent() { $viewer = $this->getViewer(); $user = $this->getAvailableUser(); $until = $user->getAwayUntil(); if (!$until) { return pht('Available'); } $const = $user->getDisplayAvailability(); $name = PhabricatorCalendarEventInvitee::getAvailabilityName($const); $color = PhabricatorCalendarEventInvitee::getAvailabilityColor($const); $away_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($color) + ->setColor($color) ->setName($name) ->setDotColor($color); $now = PhabricatorTime::getNow(); // Try to load the event handle. If it's invalid or the user can't see it, // we'll just render a generic message. $object_phid = $user->getAvailabilityEventPHID(); $handle = null; if ($object_phid) { $handles = $viewer->loadHandles(array($object_phid)); $handle = $handles[$object_phid]; if (!$handle->isComplete() || $handle->getPolicyFiltered()) { $handle = null; } } switch ($const) { case PhabricatorCalendarEventInvitee::AVAILABILITY_AWAY: if ($handle) { $description = pht( 'Away at %s until %s.', $handle->renderLink(), $viewer->formatShortDateTime($until, $now)); } else { $description = pht( 'Away until %s.', $viewer->formatShortDateTime($until, $now)); } break; case PhabricatorCalendarEventInvitee::AVAILABILITY_BUSY: default: if ($handle) { $description = pht( 'Busy at %s until %s.', $handle->renderLink(), $viewer->formatShortDateTime($until, $now)); } else { $description = pht( 'Busy until %s.', $viewer->formatShortDateTime($until, $now)); } break; } return array( $away_tag, ' ', $description, ); } } diff --git a/src/applications/conpherence/controller/ConpherenceController.php b/src/applications/conpherence/controller/ConpherenceController.php index 90328cca04..9d7473e159 100644 --- a/src/applications/conpherence/controller/ConpherenceController.php +++ b/src/applications/conpherence/controller/ConpherenceController.php @@ -1,229 +1,229 @@ conpherence = $conpherence; return $this; } public function getConpherence() { return $this->conpherence; } public function buildApplicationMenu() { $nav = new PHUIListView(); $conpherence = $this->conpherence; // Local Links if ($conpherence) { $nav->addMenuItem( id(new PHUIListItemView()) ->setName(pht('Joined Rooms')) ->setType(PHUIListItemView::TYPE_LINK) ->setHref($this->getApplicationURI())); $nav->addMenuItem( id(new PHUIListItemView()) ->setName(pht('Edit Room')) ->setType(PHUIListItemView::TYPE_LINK) ->setHref( $this->getApplicationURI('update/'.$conpherence->getID()).'/') ->setWorkflow(true)); $nav->addMenuItem( id(new PHUIListItemView()) ->setName(pht('Add Participants')) ->setType(PHUIListItemView::TYPE_LINK) ->setHref('#') ->addSigil('conpherence-widget-adder') ->setMetadata(array('widget' => 'widgets-people'))); } // Global Links $nav->newLabel(pht('Conpherence')); $nav->newLink( pht('New Room'), $this->getApplicationURI('new/')); $nav->newLink( pht('Search Rooms'), $this->getApplicationURI('search/')); return $nav; } protected function buildHeaderPaneContent( ConpherenceThread $conpherence) { $viewer = $this->getViewer(); $header = null; $id = $conpherence->getID(); if ($id) { $data = $conpherence->getDisplayData($this->getViewer()); $header = id(new PHUIHeaderView()) ->setViewer($viewer) ->setHeader($data['title']) ->setPolicyObject($conpherence) ->setImage($data['image']); if (strlen($data['topic'])) { $topic = id(new PHUITagView()) ->setName($data['topic']) - ->setShade(PHUITagView::COLOR_VIOLET) + ->setColor(PHUITagView::COLOR_VIOLET) ->setType(PHUITagView::TYPE_SHADE) ->addClass('conpherence-header-topic'); $header->addTag($topic); } $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $conpherence, PhabricatorPolicyCapability::CAN_EDIT); if ($can_edit) { $header->setImageURL( $this->getApplicationURI("picture/{$id}/")); } $participating = $conpherence->getParticipantIfExists($viewer->getPHID()); $header->addActionItem( id(new PHUIIconCircleView()) ->setHref( $this->getApplicationURI('edit/'.$conpherence->getID()).'/') ->setIcon('fa-pencil') ->addClass('hide-on-device') ->setColor('violet') ->setWorkflow(true)); $header->addActionItem( id(new PHUIIconCircleView()) ->setHref($this->getApplicationURI("preferences/{$id}/")) ->setIcon('fa-gear') ->addClass('hide-on-device') ->setColor('pink') ->setWorkflow(true)); $widget_key = PhabricatorConpherenceWidgetVisibleSetting::SETTINGKEY; $widget_view = (bool)$viewer->getUserSetting($widget_key, false); Javelin::initBehavior( 'toggle-widget', array( 'show' => (int)$widget_view, 'settingsURI' => '/settings/adjust/?key='.$widget_key, )); $header->addActionItem( id(new PHUIIconCircleView()) ->addSigil('conpherence-widget-toggle') ->setIcon('fa-group') ->setHref('#') ->addClass('conpherence-participant-toggle')); Javelin::initBehavior('conpherence-search'); $header->addActionItem( id(new PHUIIconCircleView()) ->addSigil('conpherence-search-toggle') ->setIcon('fa-search') ->setHref('#') ->setColor('green') ->addClass('conpherence-search-toggle')); if (!$participating) { $action = ConpherenceUpdateActions::JOIN_ROOM; $uri = $this->getApplicationURI("update/{$id}/"); $button = phutil_tag( 'button', array( 'type' => 'SUBMIT', 'class' => 'button green mlr', ), pht('Join Room')); $hidden = phutil_tag( 'input', array( 'type' => 'hidden', 'name' => 'action', 'value' => ConpherenceUpdateActions::JOIN_ROOM, )); $form = phabricator_form( $viewer, array( 'method' => 'POST', 'action' => (string)$uri, ), array( $hidden, $button, )); $header->addActionItem($form); } } return $header; } public function buildSearchForm() { $viewer = $this->getViewer(); $conpherence = $this->conpherence; $name = $conpherence->getTitle(); $bar = javelin_tag( 'input', array( 'type' => 'text', 'id' => 'conpherence-search-input', 'name' => 'fulltext', 'class' => 'conpherence-search-input', 'sigil' => 'conpherence-search-input', 'placeholder' => pht('Search %s...', $name), )); $id = $conpherence->getID(); $form = phabricator_form( $viewer, array( 'method' => 'POST', 'action' => '/conpherence/threadsearch/'.$id.'/', 'sigil' => 'conpherence-search-form', 'class' => 'conpherence-search-form', 'id' => 'conpherence-search-form', ), array( $bar, )); $form_view = phutil_tag( 'div', array( 'class' => 'conpherence-search-form-view', ), $form); $results = phutil_tag( 'div', array( 'id' => 'conpherence-search-results', 'class' => 'conpherence-search-results', )); $view = phutil_tag( 'div', array( 'class' => 'conpherence-search-window', ), array( $form_view, $results, )); return $view; } } diff --git a/src/applications/differential/constants/DifferentialRevisionStatus.php b/src/applications/differential/constants/DifferentialRevisionStatus.php index 4f332838ac..38e6a2dd49 100644 --- a/src/applications/differential/constants/DifferentialRevisionStatus.php +++ b/src/applications/differential/constants/DifferentialRevisionStatus.php @@ -1,107 +1,107 @@ self::COLOR_STATUS_DEFAULT, ArcanistDifferentialRevisionStatus::NEEDS_REVISION => self::COLOR_STATUS_RED, ArcanistDifferentialRevisionStatus::CHANGES_PLANNED => self::COLOR_STATUS_RED, ArcanistDifferentialRevisionStatus::ACCEPTED => self::COLOR_STATUS_GREEN, ArcanistDifferentialRevisionStatus::CLOSED => self::COLOR_STATUS_DARK, ArcanistDifferentialRevisionStatus::ABANDONED => self::COLOR_STATUS_DARK, ArcanistDifferentialRevisionStatus::IN_PREPARATION => self::COLOR_STATUS_BLUE, ); return idx($map, $status, $default); } public static function getRevisionStatusIcon($status) { $default = 'fa-square-o bluegrey'; $map = array( ArcanistDifferentialRevisionStatus::NEEDS_REVIEW => 'fa-square-o bluegrey', ArcanistDifferentialRevisionStatus::NEEDS_REVISION => 'fa-refresh', ArcanistDifferentialRevisionStatus::CHANGES_PLANNED => 'fa-headphones', ArcanistDifferentialRevisionStatus::ACCEPTED => 'fa-check', ArcanistDifferentialRevisionStatus::CLOSED => 'fa-check-square-o', ArcanistDifferentialRevisionStatus::ABANDONED => 'fa-plane', ArcanistDifferentialRevisionStatus::IN_PREPARATION => 'fa-question-circle', ); return idx($map, $status, $default); } public static function renderFullDescription($status) { $status_name = ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status); $tag = id(new PHUITagView()) ->setName($status_name) ->setIcon(self::getRevisionStatusIcon($status)) - ->setShade(self::getRevisionStatusColor($status)) + ->setColor(self::getRevisionStatusColor($status)) ->setType(PHUITagView::TYPE_SHADE); return $tag; } public static function getClosedStatuses() { $statuses = array( ArcanistDifferentialRevisionStatus::CLOSED, ArcanistDifferentialRevisionStatus::ABANDONED, ); if (PhabricatorEnv::getEnvConfig('differential.close-on-accept')) { $statuses[] = ArcanistDifferentialRevisionStatus::ACCEPTED; } return $statuses; } public static function getOpenStatuses() { return array_diff(self::getAllStatuses(), self::getClosedStatuses()); } public static function getAllStatuses() { return array( ArcanistDifferentialRevisionStatus::NEEDS_REVIEW, ArcanistDifferentialRevisionStatus::NEEDS_REVISION, ArcanistDifferentialRevisionStatus::CHANGES_PLANNED, ArcanistDifferentialRevisionStatus::ACCEPTED, ArcanistDifferentialRevisionStatus::CLOSED, ArcanistDifferentialRevisionStatus::ABANDONED, ArcanistDifferentialRevisionStatus::IN_PREPARATION, ); } public static function isClosedStatus($status) { return in_array($status, self::getClosedStatuses()); } } diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php index 061790aa31..f30421c132 100644 --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -1,458 +1,458 @@ getViewer(); $id = $request->getURIData('id'); $phid = $request->getURIData('phid'); if ($phid) { $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($phid)) ->withIsDeleted(false) ->executeOne(); if (!$file) { return new Aphront404Response(); } return id(new AphrontRedirectResponse())->setURI($file->getInfoURI()); } $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->withIsDeleted(false) ->executeOne(); if (!$file) { return new Aphront404Response(); } $phid = $file->getPHID(); $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setPolicyObject($file) ->setHeader($file->getName()) ->setHeaderIcon('fa-file-o'); $ttl = $file->getTTL(); if ($ttl !== null) { $ttl_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade(PHUITagView::COLOR_YELLOW) + ->setColor(PHUITagView::COLOR_YELLOW) ->setName(pht('Temporary')); $header->addTag($ttl_tag); } $partial = $file->getIsPartial(); if ($partial) { $partial_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade(PHUITagView::COLOR_ORANGE) + ->setColor(PHUITagView::COLOR_ORANGE) ->setName(pht('Partial Upload')); $header->addTag($partial_tag); } $curtain = $this->buildCurtainView($file); $timeline = $this->buildTransactionView($file); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb( 'F'.$file->getID(), $this->getApplicationURI("/info/{$phid}/")); $crumbs->setBorder(true); $object_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('File')) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $this->buildPropertyViews($object_box, $file); $title = $file->getName(); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->setMainColumn(array( $object_box, $timeline, )); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($file->getPHID())) ->appendChild($view); } private function buildTransactionView(PhabricatorFile $file) { $viewer = $this->getViewer(); $timeline = $this->buildTransactionTimeline( $file, new PhabricatorFileTransactionQuery()); $comment_view = id(new PhabricatorFileEditEngine()) ->setViewer($viewer) ->buildEditEngineCommentView($file); $monogram = $file->getMonogram(); $timeline->setQuoteRef($monogram); $comment_view->setTransactionTimeline($timeline); return array( $timeline, $comment_view, ); } private function buildCurtainView(PhabricatorFile $file) { $viewer = $this->getViewer(); $id = $file->getID(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $file, PhabricatorPolicyCapability::CAN_EDIT); $curtain = $this->newCurtainView($file); $can_download = !$file->getIsPartial(); if ($file->isViewableInBrowser()) { $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View File')) ->setIcon('fa-file-o') ->setHref($file->getViewURI()) ->setDisabled(!$can_download) ->setWorkflow(!$can_download)); } else { $curtain->addAction( id(new PhabricatorActionView()) ->setUser($viewer) ->setRenderAsForm($can_download) ->setDownload($can_download) ->setName(pht('Download File')) ->setIcon('fa-download') ->setHref($file->getViewURI()) ->setDisabled(!$can_download) ->setWorkflow(!$can_download)); } $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit File')) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI("/edit/{$id}/")) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete File')) ->setIcon('fa-times') ->setHref($this->getApplicationURI("/delete/{$id}/")) ->setWorkflow(true) ->setDisabled(!$can_edit)); $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('View Transforms')) ->setIcon('fa-crop') ->setHref($this->getApplicationURI("/transforms/{$id}/"))); return $curtain; } private function buildPropertyViews( PHUIObjectBoxView $box, PhabricatorFile $file) { $request = $this->getRequest(); $viewer = $request->getUser(); $tab_group = id(new PHUITabGroupView()); $box->addTabGroup($tab_group); $properties = id(new PHUIPropertyListView()); $tab_group->addTab( id(new PHUITabView()) ->setName(pht('Details')) ->setKey('details') ->appendChild($properties)); if ($file->getAuthorPHID()) { $properties->addProperty( pht('Author'), $viewer->renderHandle($file->getAuthorPHID())); } $properties->addProperty( pht('Created'), phabricator_datetime($file->getDateCreated(), $viewer)); $finfo = id(new PHUIPropertyListView()); $tab_group->addTab( id(new PHUITabView()) ->setName(pht('File Info')) ->setKey('info') ->appendChild($finfo)); $finfo->addProperty( pht('Size'), phutil_format_bytes($file->getByteSize())); $finfo->addProperty( pht('Mime Type'), $file->getMimeType()); $ttl = $file->getTtl(); if ($ttl) { $delta = $ttl - PhabricatorTime::getNow(); $finfo->addProperty( pht('Expires'), pht( '%s (%s)', phabricator_datetime($ttl, $viewer), phutil_format_relative_time_detailed($delta))); } $width = $file->getImageWidth(); if ($width) { $finfo->addProperty( pht('Width'), pht('%s px', new PhutilNumber($width))); } $height = $file->getImageHeight(); if ($height) { $finfo->addProperty( pht('Height'), pht('%s px', new PhutilNumber($height))); } $is_image = $file->isViewableImage(); if ($is_image) { $image_string = pht('Yes'); $cache_string = $file->getCanCDN() ? pht('Yes') : pht('No'); } else { $image_string = pht('No'); $cache_string = pht('Not Applicable'); } $types = array(); if ($file->isViewableImage()) { $types[] = pht('Image'); } if ($file->isVideo()) { $types[] = pht('Video'); } if ($file->isAudio()) { $types[] = pht('Audio'); } if ($file->getCanCDN()) { $types[] = pht('Can CDN'); } $builtin = $file->getBuiltinName(); if ($builtin !== null) { $types[] = pht('Builtin ("%s")', $builtin); } if ($file->getIsProfileImage()) { $types[] = pht('Profile'); } if ($types) { $types = implode(', ', $types); $finfo->addProperty(pht('Attributes'), $types); } $storage_properties = new PHUIPropertyListView(); $tab_group->addTab( id(new PHUITabView()) ->setName(pht('Storage')) ->setKey('storage') ->appendChild($storage_properties)); $storage_properties->addProperty( pht('Engine'), $file->getStorageEngine()); $engine = $this->loadStorageEngine($file); if ($engine && $engine->isChunkEngine()) { $format_name = pht('Chunks'); } else { $format_key = $file->getStorageFormat(); $format = PhabricatorFileStorageFormat::getFormat($format_key); if ($format) { $format_name = $format->getStorageFormatName(); } else { $format_name = pht('Unknown ("%s")', $format_key); } } $storage_properties->addProperty(pht('Format'), $format_name); $storage_properties->addProperty( pht('Handle'), $file->getStorageHandle()); $phids = $file->getObjectPHIDs(); if ($phids) { $attached = new PHUIPropertyListView(); $tab_group->addTab( id(new PHUITabView()) ->setName(pht('Attached')) ->setKey('attached') ->appendChild($attached)); $attached->addProperty( pht('Attached To'), $viewer->renderHandleList($phids)); } if ($file->isViewableImage()) { $image = phutil_tag( 'img', array( 'src' => $file->getViewURI(), 'class' => 'phui-property-list-image', )); $linked_image = phutil_tag( 'a', array( 'href' => $file->getViewURI(), ), $image); $media = id(new PHUIPropertyListView()) ->addImageContent($linked_image); $box->addPropertyList($media); } else if ($file->isVideo()) { $video = phutil_tag( 'video', array( 'controls' => 'controls', 'class' => 'phui-property-list-video', ), phutil_tag( 'source', array( 'src' => $file->getViewURI(), 'type' => $file->getMimeType(), ))); $media = id(new PHUIPropertyListView()) ->addImageContent($video); $box->addPropertyList($media); } else if ($file->isAudio()) { $audio = phutil_tag( 'audio', array( 'controls' => 'controls', 'class' => 'phui-property-list-audio', ), phutil_tag( 'source', array( 'src' => $file->getViewURI(), 'type' => $file->getMimeType(), ))); $media = id(new PHUIPropertyListView()) ->addImageContent($audio); $box->addPropertyList($media); } $engine = $this->loadStorageEngine($file); if ($engine) { if ($engine->isChunkEngine()) { $chunkinfo = new PHUIPropertyListView(); $tab_group->addTab( id(new PHUITabView()) ->setName(pht('Chunks')) ->setKey('chunks') ->appendChild($chunkinfo)); $chunks = id(new PhabricatorFileChunkQuery()) ->setViewer($viewer) ->withChunkHandles(array($file->getStorageHandle())) ->execute(); $chunks = msort($chunks, 'getByteStart'); $rows = array(); $completed = array(); foreach ($chunks as $chunk) { $is_complete = $chunk->getDataFilePHID(); $rows[] = array( $chunk->getByteStart(), $chunk->getByteEnd(), ($is_complete ? pht('Yes') : pht('No')), ); if ($is_complete) { $completed[] = $chunk; } } $table = id(new AphrontTableView($rows)) ->setHeaders( array( pht('Offset'), pht('End'), pht('Complete'), )) ->setColumnClasses( array( '', '', 'wide', )); $chunkinfo->addProperty( pht('Total Chunks'), count($chunks)); $chunkinfo->addProperty( pht('Completed Chunks'), count($completed)); $chunkinfo->addRawContent($table); } } } private function loadStorageEngine(PhabricatorFile $file) { $engine = null; try { $engine = $file->instantiateStorageEngine(); } catch (Exception $ex) { // Don't bother raising this anywhere for now. } return $engine; } } diff --git a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php index 28f79ee181..e5a66a3eb8 100644 --- a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php +++ b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php @@ -1,110 +1,110 @@ buildable = $buildable; return $this; } public function setUnitMessages(array $messages) { $this->messages = $messages; return $this; } public function setLimit($limit) { $this->limit = $limit; return $this; } public function setExcuse($excuse) { $this->excuse = $excuse; return $this; } public function setShowViewAll($show_view_all) { $this->showViewAll = $show_view_all; return $this; } public function render() { $messages = $this->messages; $buildable = $this->buildable; $id = $buildable->getID(); $full_uri = "/harbormaster/unit/{$id}/"; $messages = msort($messages, 'getSortKey'); $head_unit = head($messages); if ($head_unit) { $status = $head_unit->getResult(); $tag_text = HarbormasterUnitStatus::getUnitStatusLabel($status); $tag_color = HarbormasterUnitStatus::getUnitStatusColor($status); $tag_icon = HarbormasterUnitStatus::getUnitStatusIcon($status); } else { $tag_text = pht('No Unit Tests'); $tag_color = 'grey'; $tag_icon = 'fa-ban'; } $tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($tag_color) + ->setColor($tag_color) ->setIcon($tag_icon) ->setName($tag_text); $header = id(new PHUIHeaderView()) ->setHeader(array(pht('Unit Tests'), $tag)); if ($this->showViewAll) { $view_all = id(new PHUIButtonView()) ->setTag('a') ->setHref($full_uri) ->setIcon('fa-list-ul') ->setText('View All'); $header->addActionLink($view_all); } $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY); $table = id(new HarbormasterUnitPropertyView()) ->setUnitMessages($messages); if ($this->showViewAll) { $table->setFullResultsURI($full_uri); } if ($this->limit) { $table->setLimit($this->limit); } $excuse = $this->excuse; if (strlen($excuse)) { $excuse_icon = id(new PHUIIconView()) ->setIcon('fa-commenting-o red'); $table->setNotice( array( $excuse_icon, ' ', phutil_tag('strong', array(), pht('Excuse:')), ' ', $excuse, )); } $box->setTable($table); return $box; } } diff --git a/src/applications/maniphest/constants/ManiphestTaskStatus.php b/src/applications/maniphest/constants/ManiphestTaskStatus.php index 5734892f0a..6781fb7724 100644 --- a/src/applications/maniphest/constants/ManiphestTaskStatus.php +++ b/src/applications/maniphest/constants/ManiphestTaskStatus.php @@ -1,363 +1,363 @@ $status) { if ($is_serious && !empty($status['silly'])) { unset($spec[$const]); continue; } } return $spec; } public static function getTaskStatusMap() { return ipull(self::getEnabledStatusMap(), 'name'); } /** * Get the statuses and their command keywords. * * @return map Statuses to lists of command keywords. */ public static function getTaskStatusKeywordsMap() { $map = self::getEnabledStatusMap(); foreach ($map as $key => $spec) { $words = idx($spec, 'keywords', array()); if (!is_array($words)) { $words = array($words); } // For statuses, we include the status name because it's usually // at least somewhat meaningful. $words[] = $key; foreach ($words as $word_key => $word) { $words[$word_key] = phutil_utf8_strtolower($word); } $words = array_unique($words); $map[$key] = $words; } return $map; } public static function getTaskStatusName($status) { return self::getStatusAttribute($status, 'name', pht('Unknown Status')); } public static function getTaskStatusFullName($status) { $name = self::getStatusAttribute($status, 'name.full'); if ($name !== null) { return $name; } return self::getStatusAttribute($status, 'name', pht('Unknown Status')); } public static function renderFullDescription($status, $priority) { if (self::isOpenStatus($status)) { $name = pht('%s, %s', self::getTaskStatusFullName($status), $priority); $color = 'grey'; $icon = 'fa-square-o'; } else { $name = self::getTaskStatusFullName($status); $color = 'indigo'; $icon = 'fa-check-square-o'; } $tag = id(new PHUITagView()) ->setName($name) ->setIcon($icon) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($color); + ->setColor($color); return $tag; } private static function getSpecialStatus($special) { foreach (self::getStatusConfig() as $const => $status) { if (idx($status, 'special') == $special) { return $const; } } return null; } public static function getDefaultStatus() { return self::getSpecialStatus(self::SPECIAL_DEFAULT); } public static function getDefaultClosedStatus() { return self::getSpecialStatus(self::SPECIAL_CLOSED); } public static function getDuplicateStatus() { return self::getSpecialStatus(self::SPECIAL_DUPLICATE); } public static function getOpenStatusConstants() { $result = array(); foreach (self::getEnabledStatusMap() as $const => $status) { if (empty($status['closed'])) { $result[] = $const; } } return $result; } public static function getClosedStatusConstants() { $all = array_keys(self::getTaskStatusMap()); $open = self::getOpenStatusConstants(); return array_diff($all, $open); } public static function isOpenStatus($status) { foreach (self::getOpenStatusConstants() as $constant) { if ($status == $constant) { return true; } } return false; } public static function isClaimStatus($status) { return self::getStatusAttribute($status, 'claim', true); } public static function isClosedStatus($status) { return !self::isOpenStatus($status); } public static function isLockedStatus($status) { return self::getStatusAttribute($status, 'locked', false); } public static function getStatusActionName($status) { return self::getStatusAttribute($status, 'name.action'); } public static function getStatusColor($status) { return self::getStatusAttribute($status, 'transaction.color'); } public static function isDisabledStatus($status) { return self::getStatusAttribute($status, 'disabled'); } public static function getStatusIcon($status) { $icon = self::getStatusAttribute($status, 'transaction.icon'); if ($icon) { return $icon; } if (self::isOpenStatus($status)) { return 'fa-exclamation-circle'; } else { return 'fa-check-square-o'; } } public static function getStatusPrefixMap() { $map = array(); foreach (self::getEnabledStatusMap() as $const => $status) { foreach (idx($status, 'prefixes', array()) as $prefix) { $map[$prefix] = $const; } } $map += array( 'ref' => null, 'refs' => null, 'references' => null, 'cf.' => null, ); return $map; } public static function getStatusSuffixMap() { $map = array(); foreach (self::getEnabledStatusMap() as $const => $status) { foreach (idx($status, 'suffixes', array()) as $prefix) { $map[$prefix] = $const; } } return $map; } private static function getStatusAttribute($status, $key, $default = null) { $config = self::getStatusConfig(); $spec = idx($config, $status); if ($spec) { return idx($spec, $key, $default); } return $default; } /* -( Configuration Validation )------------------------------------------- */ /** * @task validate */ public static function isValidStatusConstant($constant) { if (strlen($constant) > 12) { return false; } if (!preg_match('/^[a-z0-9]+\z/', $constant)) { return false; } return true; } /** * @task validate */ public static function validateConfiguration(array $config) { foreach ($config as $key => $value) { if (!self::isValidStatusConstant($key)) { throw new Exception( pht( 'Key "%s" is not a valid status constant. Status constants must '. 'be 1-12 characters long and contain only lowercase letters (a-z) '. 'and digits (0-9). For example, "%s" or "%s" are reasonable '. 'choices.', $key, 'open', 'closed')); } if (!is_array($value)) { throw new Exception( pht( 'Value for key "%s" should be a dictionary.', $key)); } PhutilTypeSpec::checkMap( $value, array( 'name' => 'string', 'name.full' => 'optional string', 'name.action' => 'optional string', 'closed' => 'optional bool', 'special' => 'optional string', 'transaction.icon' => 'optional string', 'transaction.color' => 'optional string', 'silly' => 'optional bool', 'prefixes' => 'optional list', 'suffixes' => 'optional list', 'keywords' => 'optional list', 'disabled' => 'optional bool', 'claim' => 'optional bool', 'locked' => 'optional bool', )); } $special_map = array(); foreach ($config as $key => $value) { $special = idx($value, 'special'); if (!$special) { continue; } if (isset($special_map[$special])) { throw new Exception( pht( 'Configuration has two statuses both marked with the special '. 'attribute "%s" ("%s" and "%s"). There should be only one.', $special, $special_map[$special], $key)); } switch ($special) { case self::SPECIAL_DEFAULT: if (!empty($value['closed'])) { throw new Exception( pht( 'Status "%s" is marked as default, but it is a closed '. 'status. The default status should be an open status.', $key)); } break; case self::SPECIAL_CLOSED: if (empty($value['closed'])) { throw new Exception( pht( 'Status "%s" is marked as the default status for closing '. 'tasks, but is not a closed status. It should be a closed '. 'status.', $key)); } break; case self::SPECIAL_DUPLICATE: if (empty($value['closed'])) { throw new Exception( pht( 'Status "%s" is marked as the status for closing tasks as '. 'duplicates, but it is not a closed status. It should '. 'be a closed status.', $key)); } break; } $special_map[$special] = $key; } // NOTE: We're not explicitly validating that we have at least one open // and one closed status, because the DEFAULT and CLOSED specials imply // that to be true. If those change in the future, that might become a // reasonable thing to validate. $required = array( self::SPECIAL_DEFAULT, self::SPECIAL_CLOSED, self::SPECIAL_DUPLICATE, ); foreach ($required as $required_special) { if (!isset($special_map[$required_special])) { throw new Exception( pht( 'Configuration defines no task status with special attribute '. '"%s", but you must specify a status which fills this special '. 'role.', $required_special)); } } } } diff --git a/src/applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php b/src/applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php index 744d3fbea2..39fe30ad28 100644 --- a/src/applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php +++ b/src/applications/owners/engineextension/PhabricatorOwnersHovercardEngineExtension.php @@ -1,89 +1,89 @@ getViewer(); $phids = mpull($objects, 'getPHID'); $packages = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) ->withPHIDs($phids) ->execute(); $packages = mpull($packages, null, 'getPHID'); return array( 'packages' => $packages, ); } public function renderHovercard( PHUIHovercardView $hovercard, PhabricatorObjectHandle $handle, $object, $data) { $viewer = $this->getViewer(); $package = idx($data['packages'], $object->getPHID()); if (!$package) { return; } $title = pht('%s: %s', 'O'.$package->getID(), $package->getName()); $hovercard->setTitle($title); $dominion = $package->getDominion(); $dominion_map = PhabricatorOwnersPackage::getDominionOptionsMap(); $spec = idx($dominion_map, $dominion, array()); $name = idx($spec, 'short', $dominion); $hovercard->addField(pht('Dominion'), $name); $auto = $package->getAutoReview(); $autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap(); $spec = idx($autoreview_map, $auto, array()); $name = idx($spec, 'name', $auto); $hovercard->addField(pht('Auto Review'), $name); if ($package->isArchived()) { $tag = id(new PHUITagView()) ->setName(pht('Archived')) - ->setShade(PHUITagView::COLOR_INDIGO) + ->setColor(PHUITagView::COLOR_INDIGO) ->setType(PHUITagView::TYPE_OBJECT); $hovercard->addTag($tag); } $owner_phids = $package->getOwnerPHIDs(); $hovercard->addField( pht('Owners'), $viewer->renderHandleList($owner_phids)->setAsInline(true)); $description = $package->getDescription(); if (strlen($description)) { $description = id(new PhutilUTF8StringTruncator()) ->setMaximumGlyphs(120) ->truncateString($description); $hovercard->addField(pht('Description'), $description); } } } diff --git a/src/applications/people/view/PhabricatorUserCardView.php b/src/applications/people/view/PhabricatorUserCardView.php index 4f4f15a33d..f1fc515f88 100644 --- a/src/applications/people/view/PhabricatorUserCardView.php +++ b/src/applications/people/view/PhabricatorUserCardView.php @@ -1,173 +1,173 @@ profile = $profile; return $this; } public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; } public function setTag($tag) { $this->tag = $tag; return $this; } protected function getTagName() { if ($this->tag) { return $this->tag; } return 'div'; } protected function getTagAttributes() { $classes = array(); $classes[] = 'project-card-view'; $classes[] = 'people-card-view'; if ($this->profile->getIsDisabled()) { $classes[] = 'project-card-disabled'; } return array( 'class' => implode($classes, ' '), ); } protected function getTagContent() { $user = $this->profile; $profile = $user->loadUserProfile(); $picture = $user->getProfileImageURI(); $viewer = $this->viewer; require_celerity_resource('project-card-view-css'); // We don't have a ton of room on the hovercard, so we're trying to show // the most important tag. Users can click through to the profile to get // more details. $classes = array(); if ($user->getIsDisabled()) { $tag_icon = 'fa-ban'; $tag_title = pht('Disabled'); $tag_shade = PHUITagView::COLOR_RED; $classes[] = 'phui-image-disabled'; } else if (!$user->getIsApproved()) { $tag_icon = 'fa-ban'; $tag_title = pht('Unapproved Account'); $tag_shade = PHUITagView::COLOR_RED; } else if (!$user->getIsEmailVerified()) { $tag_icon = 'fa-envelope'; $tag_title = pht('Email Not Verified'); $tag_shade = PHUITagView::COLOR_VIOLET; } else if ($user->getIsAdmin()) { $tag_icon = 'fa-star'; $tag_title = pht('Administrator'); $tag_shade = PHUITagView::COLOR_INDIGO; } else { $tag_icon = PhabricatorPeopleIconSet::getIconIcon($profile->getIcon()); $tag_title = $profile->getDisplayTitle(); $tag_shade = null; } $tag = id(new PHUITagView()) ->setIcon($tag_icon) ->setName($tag_title) ->setType(PHUITagView::TYPE_SHADE); if ($tag_shade !== null) { - $tag->setShade($tag_shade); + $tag->setColor($tag_shade); } $body = array(); /* TODO: Replace with Conpherence Availability if we ship it */ $body[] = $this->addItem( 'fa-user-plus', phabricator_date($user->getDateCreated(), $viewer)); if (PhabricatorApplication::isClassInstalledForViewer( 'PhabricatorCalendarApplication', $viewer)) { $body[] = $this->addItem( 'fa-calendar-o', id(new PHUIUserAvailabilityView()) ->setViewer($viewer) ->setAvailableUser($user)); } $classes[] = 'project-card-image'; $image = phutil_tag( 'img', array( 'src' => $picture, 'class' => implode(' ', $classes), )); $href = urisprintf( '/p/%s/', $user->getUsername()); $image = phutil_tag( 'a', array( 'href' => $href, 'class' => 'project-card-image-href', ), $image); $name = phutil_tag_div('project-card-name', $user->getRealname()); $username = phutil_tag_div('project-card-username', '@'.$user->getUsername()); $tag = phutil_tag_div('phui-header-subheader', $tag); $header = phutil_tag( 'div', array( 'class' => 'project-card-header', ), array( $name, $username, $tag, $body, )); $card = phutil_tag( 'div', array( 'class' => 'project-card-inner', ), array( $image, $header, )); return $card; } private function addItem($icon, $value) { $icon = id(new PHUIIconView()) ->addClass('project-card-item-icon') ->setIcon($icon); $text = phutil_tag( 'span', array( 'class' => 'project-card-item-text', ), $value); return phutil_tag_div('project-card-item', array($icon, $text)); } } diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 51b907cac0..ad08df1692 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -1,1095 +1,1095 @@ getUser(); $response = $this->loadProject(); if ($response) { return $response; } $project = $this->getProject(); $this->readRequestState(); $board_uri = $this->getApplicationURI('board/'.$project->getID().'/'); $search_engine = id(new ManiphestTaskSearchEngine()) ->setViewer($viewer) ->setBaseURI($board_uri) ->setIsBoardView(true); if ($request->isFormPost() && !$request->getBool('initialize')) { $saved = $search_engine->buildSavedQueryFromRequest($request); $search_engine->saveQuery($saved); $filter_form = id(new AphrontFormView()) ->setUser($viewer); $search_engine->buildSearchForm($filter_form, $saved); if ($search_engine->getErrors()) { return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FULL) ->setTitle(pht('Advanced Filter')) ->appendChild($filter_form->buildLayoutView()) ->setErrors($search_engine->getErrors()) ->setSubmitURI($board_uri) ->addSubmitButton(pht('Apply Filter')) ->addCancelButton($board_uri); } return id(new AphrontRedirectResponse())->setURI( $this->getURIWithState( $search_engine->getQueryResultsPageURI($saved->getQueryKey()))); } $query_key = $this->getDefaultFilter($project); $request_query = $request->getStr('filter'); if (strlen($request_query)) { $query_key = $request_query; } $uri_query = $request->getURIData('queryKey'); if (strlen($uri_query)) { $query_key = $uri_query; } $this->queryKey = $query_key; $custom_query = null; if ($search_engine->isBuiltinQuery($query_key)) { $saved = $search_engine->buildSavedQueryFromBuiltin($query_key); } else { $saved = id(new PhabricatorSavedQueryQuery()) ->setViewer($viewer) ->withQueryKeys(array($query_key)) ->executeOne(); if (!$saved) { return new Aphront404Response(); } $custom_query = $saved; } if ($request->getURIData('filter')) { $filter_form = id(new AphrontFormView()) ->setUser($viewer); $search_engine->buildSearchForm($filter_form, $saved); return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FULL) ->setTitle(pht('Advanced Filter')) ->appendChild($filter_form->buildLayoutView()) ->setSubmitURI($board_uri) ->addSubmitButton(pht('Apply Filter')) ->addCancelButton($board_uri); } $task_query = $search_engine->buildQueryFromSavedQuery($saved); $select_phids = array($project->getPHID()); if ($project->getHasSubprojects() || $project->getHasMilestones()) { $descendants = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withAncestorProjectPHIDs($select_phids) ->execute(); foreach ($descendants as $descendant) { $select_phids[] = $descendant->getPHID(); } } $tasks = $task_query ->withEdgeLogicPHIDs( PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, PhabricatorQueryConstraint::OPERATOR_ANCESTOR, array($select_phids)) ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY) ->setViewer($viewer) ->execute(); $tasks = mpull($tasks, null, 'getPHID'); $board_phid = $project->getPHID(); // Regardless of display order, pass tasks to the layout engine in ID order // so layout is consistent. $board_tasks = msort($tasks, 'getID'); $layout_engine = id(new PhabricatorBoardLayoutEngine()) ->setViewer($viewer) ->setBoardPHIDs(array($board_phid)) ->setObjectPHIDs(array_keys($board_tasks)) ->setFetchAllBoards(true) ->executeLayout(); $columns = $layout_engine->getColumns($board_phid); if (!$columns || !$project->getHasWorkboard()) { $has_normal_columns = false; foreach ($columns as $column) { if (!$column->getProxyPHID()) { $has_normal_columns = true; break; } } $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); if (!$has_normal_columns) { if (!$can_edit) { $content = $this->buildNoAccessContent($project); } else { $content = $this->buildInitializeContent($project); } } else { if (!$can_edit) { $content = $this->buildDisabledContent($project); } else { $content = $this->buildEnableContent($project); } } if ($content instanceof AphrontResponse) { return $content; } $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::ITEM_WORKBOARD); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard')); return $this->newPage() ->setTitle( array( $project->getDisplayName(), pht('Workboard'), )) ->setNavigation($nav) ->setCrumbs($crumbs) ->appendChild($content); } $task_can_edit_map = id(new PhabricatorPolicyFilter()) ->setViewer($viewer) ->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT)) ->apply($tasks); // If this is a batch edit, select the editable tasks in the chosen column // and ship the user into the batch editor. $batch_edit = $request->getStr('batch'); if ($batch_edit) { if ($batch_edit !== self::BATCH_EDIT_ALL) { $column_id_map = mpull($columns, null, 'getID'); $batch_column = idx($column_id_map, $batch_edit); if (!$batch_column) { return new Aphront404Response(); } $batch_task_phids = $layout_engine->getColumnObjectPHIDs( $board_phid, $batch_column->getPHID()); foreach ($batch_task_phids as $key => $batch_task_phid) { if (empty($task_can_edit_map[$batch_task_phid])) { unset($batch_task_phids[$key]); } } $batch_tasks = array_select_keys($tasks, $batch_task_phids); } else { $batch_tasks = $task_can_edit_map; } if (!$batch_tasks) { $cancel_uri = $this->getURIWithState($board_uri); return $this->newDialog() ->setTitle(pht('No Editable Tasks')) ->appendParagraph( pht( 'The selected column contains no visible tasks which you '. 'have permission to edit.')) ->addCancelButton($board_uri); } $batch_ids = mpull($batch_tasks, 'getID'); $batch_ids = implode(',', $batch_ids); $batch_uri = new PhutilURI('/maniphest/batch/'); $batch_uri->setQueryParam('board', $this->id); $batch_uri->setQueryParam('batch', $batch_ids); return id(new AphrontRedirectResponse()) ->setURI($batch_uri); } $board_id = celerity_generate_unique_node_id(); $board = id(new PHUIWorkboardView()) ->setUser($viewer) ->setID($board_id) ->addSigil('jx-workboard') ->setMetadata( array( 'boardPHID' => $project->getPHID(), )); $visible_columns = array(); $column_phids = array(); $visible_phids = array(); foreach ($columns as $column) { if (!$this->showHidden) { if ($column->isHidden()) { continue; } } $proxy = $column->getProxy(); if ($proxy && !$proxy->isMilestone()) { // TODO: For now, don't show subproject columns because we can't // handle tasks with multiple positions yet. continue; } $task_phids = $layout_engine->getColumnObjectPHIDs( $board_phid, $column->getPHID()); $column_tasks = array_select_keys($tasks, $task_phids); // If we aren't using "natural" order, reorder the column by the original // query order. if ($this->sortKey != PhabricatorProjectColumn::ORDER_NATURAL) { $column_tasks = array_select_keys($column_tasks, array_keys($tasks)); } $column_phid = $column->getPHID(); $visible_columns[$column_phid] = $column; $column_phids[$column_phid] = $column_tasks; foreach ($column_tasks as $phid => $task) { $visible_phids[$phid] = $phid; } } $rendering_engine = id(new PhabricatorBoardRenderingEngine()) ->setViewer($viewer) ->setObjects(array_select_keys($tasks, $visible_phids)) ->setEditMap($task_can_edit_map) ->setExcludedProjectPHIDs($select_phids); $templates = array(); $column_maps = array(); $all_tasks = array(); foreach ($visible_columns as $column_phid => $column) { $column_tasks = $column_phids[$column_phid]; $panel = id(new PHUIWorkpanelView()) ->setHeader($column->getDisplayName()) ->setSubHeader($column->getDisplayType()) ->addSigil('workpanel'); $proxy = $column->getProxy(); if ($proxy) { $proxy_id = $proxy->getID(); $href = $this->getApplicationURI("view/{$proxy_id}/"); $panel->setHref($href); } $header_icon = $column->getHeaderIcon(); if ($header_icon) { $panel->setHeaderIcon($header_icon); } $display_class = $column->getDisplayClass(); if ($display_class) { $panel->addClass($display_class); } if ($column->isHidden()) { $panel->addClass('project-panel-hidden'); } $column_menu = $this->buildColumnMenu($project, $column); $panel->addHeaderAction($column_menu); $count_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade(PHUITagView::COLOR_BLUE) + ->setColor(PHUITagView::COLOR_BLUE) ->addSigil('column-points') ->setName( javelin_tag( 'span', array( 'sigil' => 'column-points-content', ), pht('-'))) ->setStyle('display: none'); $panel->setHeaderTag($count_tag); $cards = id(new PHUIObjectItemListView()) ->setUser($viewer) ->setFlush(true) ->setAllowEmptyList(true) ->addSigil('project-column') ->setItemClass('phui-workcard') ->setMetadata( array( 'columnPHID' => $column->getPHID(), 'pointLimit' => $column->getPointLimit(), )); foreach ($column_tasks as $task) { $object_phid = $task->getPHID(); $card = $rendering_engine->renderCard($object_phid); $templates[$object_phid] = hsprintf('%s', $card->getItem()); $column_maps[$column_phid][] = $object_phid; $all_tasks[$object_phid] = $task; } $panel->setCards($cards); $board->addPanel($panel); } $behavior_config = array( 'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'), 'uploadURI' => '/file/dropupload/', 'coverURI' => $this->getApplicationURI('cover/'), 'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(), 'pointsEnabled' => ManiphestTaskPoints::getIsEnabled(), 'boardPHID' => $project->getPHID(), 'order' => $this->sortKey, 'templateMap' => $templates, 'columnMaps' => $column_maps, 'orderMaps' => mpull($all_tasks, 'getWorkboardOrderVectors'), 'propertyMaps' => mpull($all_tasks, 'getWorkboardProperties'), 'boardID' => $board_id, 'projectPHID' => $project->getPHID(), ); $this->initBehavior('project-boards', $behavior_config); $sort_menu = $this->buildSortMenu( $viewer, $project, $this->sortKey); $filter_menu = $this->buildFilterMenu( $viewer, $project, $custom_query, $search_engine, $query_key); $manage_menu = $this->buildManageMenu($project, $this->showHidden); $header_link = phutil_tag( 'a', array( 'href' => $this->getApplicationURI('profile/'.$project->getID().'/'), ), $project->getName()); $board_box = id(new PHUIBoxView()) ->appendChild($board) ->addClass('project-board-wrapper'); $nav = $this->getProfileMenu(); $divider = id(new PHUIListItemView()) ->setType(PHUIListItemView::TYPE_DIVIDER); $fullscreen = $this->buildFullscreenMenu(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard')); $crumbs->setBorder(true); $crumbs->addAction($sort_menu); $crumbs->addAction($filter_menu); $crumbs->addAction($divider); $crumbs->addAction($manage_menu); $crumbs->addAction($fullscreen); $page = $this->newPage() ->setTitle( array( $project->getDisplayName(), pht('Workboard'), )) ->setPageObjectPHIDs(array($project->getPHID())) ->setShowFooter(false) ->setNavigation($nav) ->setCrumbs($crumbs) ->addQuicksandConfig( array( 'boardConfig' => $behavior_config, )) ->appendChild( array( $board_box, )); $background = $project->getDisplayWorkboardBackgroundColor(); require_celerity_resource('phui-workboard-color-css'); if ($background !== null) { $background_color_class = "phui-workboard-{$background}"; $page->addClass('phui-workboard-color'); $page->addClass($background_color_class); } else { $page->addClass('phui-workboard-no-color'); } return $page; } private function readRequestState() { $request = $this->getRequest(); $project = $this->getProject(); $this->showHidden = $request->getBool('hidden'); $this->id = $project->getID(); $sort_key = $this->getDefaultSort($project); $request_sort = $request->getStr('order'); if ($this->isValidSort($request_sort)) { $sort_key = $request_sort; } $this->sortKey = $sort_key; } private function getDefaultSort(PhabricatorProject $project) { $default_sort = $project->getDefaultWorkboardSort(); if ($this->isValidSort($default_sort)) { return $default_sort; } return PhabricatorProjectColumn::DEFAULT_ORDER; } private function getDefaultFilter(PhabricatorProject $project) { $default_filter = $project->getDefaultWorkboardFilter(); if (strlen($default_filter)) { return $default_filter; } return 'open'; } private function isValidSort($sort) { switch ($sort) { case PhabricatorProjectColumn::ORDER_NATURAL: case PhabricatorProjectColumn::ORDER_PRIORITY: return true; } return false; } private function buildSortMenu( PhabricatorUser $viewer, PhabricatorProject $project, $sort_key) { $sort_icon = id(new PHUIIconView()) ->setIcon('fa-sort-amount-asc bluegrey'); $named = array( PhabricatorProjectColumn::ORDER_NATURAL => pht('Natural'), PhabricatorProjectColumn::ORDER_PRIORITY => pht('Sort by Priority'), ); $base_uri = $this->getURIWithState(); $items = array(); foreach ($named as $key => $name) { $is_selected = ($key == $sort_key); if ($is_selected) { $active_order = $name; } $item = id(new PhabricatorActionView()) ->setIcon('fa-sort-amount-asc') ->setSelected($is_selected) ->setName($name); $uri = $base_uri->alter('order', $key); $item->setHref($uri); $items[] = $item; } $id = $project->getID(); $save_uri = "default/{$id}/sort/"; $save_uri = $this->getApplicationURI($save_uri); $save_uri = $this->getURIWithState($save_uri, $force = true); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); $items[] = id(new PhabricatorActionView()) ->setIcon('fa-floppy-o') ->setName(pht('Save as Default')) ->setHref($save_uri) ->setWorkflow(true) ->setDisabled(!$can_edit); $sort_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($items as $item) { $sort_menu->addAction($item); } $sort_button = id(new PHUIListItemView()) ->setName($active_order) ->setIcon('fa-sort-amount-asc') ->setHref('#') ->addSigil('boards-dropdown-menu') ->setMetadata( array( 'items' => hsprintf('%s', $sort_menu), )); return $sort_button; } private function buildFilterMenu( PhabricatorUser $viewer, PhabricatorProject $project, $custom_query, PhabricatorApplicationSearchEngine $engine, $query_key) { $named = array( 'open' => pht('Open Tasks'), 'all' => pht('All Tasks'), ); if ($viewer->isLoggedIn()) { $named['assigned'] = pht('Assigned to Me'); } if ($custom_query) { $named[$custom_query->getQueryKey()] = pht('Custom Filter'); } $items = array(); foreach ($named as $key => $name) { $is_selected = ($key == $query_key); if ($is_selected) { $active_filter = $name; } $is_custom = false; if ($custom_query) { $is_custom = ($key == $custom_query->getQueryKey()); } $item = id(new PhabricatorActionView()) ->setIcon('fa-search') ->setSelected($is_selected) ->setName($name); if ($is_custom) { $uri = $this->getApplicationURI( 'board/'.$this->id.'/filter/query/'.$key.'/'); $item->setWorkflow(true); } else { $uri = $engine->getQueryResultsPageURI($key); } $uri = $this->getURIWithState($uri) ->setQueryParam('filter', null); $item->setHref($uri); $items[] = $item; } $id = $project->getID(); $filter_uri = $this->getApplicationURI("board/{$id}/filter/"); $filter_uri = $this->getURIWithState($filter_uri, $force = true); $items[] = id(new PhabricatorActionView()) ->setIcon('fa-cog') ->setHref($filter_uri) ->setWorkflow(true) ->setName(pht('Advanced Filter...')); $save_uri = "default/{$id}/filter/"; $save_uri = $this->getApplicationURI($save_uri); $save_uri = $this->getURIWithState($save_uri, $force = true); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); $items[] = id(new PhabricatorActionView()) ->setIcon('fa-floppy-o') ->setName(pht('Save as Default')) ->setHref($save_uri) ->setWorkflow(true) ->setDisabled(!$can_edit); $filter_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($items as $item) { $filter_menu->addAction($item); } $filter_button = id(new PHUIListItemView()) ->setName($active_filter) ->setIcon('fa-search') ->setHref('#') ->addSigil('boards-dropdown-menu') ->setMetadata( array( 'items' => hsprintf('%s', $filter_menu), )); return $filter_button; } private function buildManageMenu( PhabricatorProject $project, $show_hidden) { $request = $this->getRequest(); $viewer = $request->getUser(); $id = $project->getID(); $manage_uri = $this->getApplicationURI("board/{$id}/manage/"); $add_uri = $this->getApplicationURI("board/{$id}/edit/"); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); $manage_items = array(); $manage_items[] = id(new PhabricatorActionView()) ->setIcon('fa-plus') ->setName(pht('Add Column')) ->setHref($add_uri) ->setDisabled(!$can_edit) ->setWorkflow(true); $reorder_uri = $this->getApplicationURI("board/{$id}/reorder/"); $manage_items[] = id(new PhabricatorActionView()) ->setIcon('fa-exchange') ->setName(pht('Reorder Columns')) ->setHref($reorder_uri) ->setDisabled(!$can_edit) ->setWorkflow(true); if ($show_hidden) { $hidden_uri = $this->getURIWithState() ->setQueryParam('hidden', null); $hidden_icon = 'fa-eye-slash'; $hidden_text = pht('Hide Hidden Columns'); } else { $hidden_uri = $this->getURIWithState() ->setQueryParam('hidden', 'true'); $hidden_icon = 'fa-eye'; $hidden_text = pht('Show Hidden Columns'); } $manage_items[] = id(new PhabricatorActionView()) ->setIcon($hidden_icon) ->setName($hidden_text) ->setHref($hidden_uri); $manage_items[] = id(new PhabricatorActionView()) ->setType(PhabricatorActionView::TYPE_DIVIDER); $background_uri = $this->getApplicationURI("board/{$id}/background/"); $manage_items[] = id(new PhabricatorActionView()) ->setIcon('fa-paint-brush') ->setName(pht('Change Background Color')) ->setHref($background_uri) ->setDisabled(!$can_edit) ->setWorkflow(false); $manage_uri = $this->getApplicationURI("board/{$id}/manage/"); $manage_items[] = id(new PhabricatorActionView()) ->setIcon('fa-gear') ->setName(pht('Manage Workboard')) ->setHref($manage_uri); $batch_edit_uri = $request->getRequestURI(); $batch_edit_uri->setQueryParam('batch', self::BATCH_EDIT_ALL); $can_batch_edit = PhabricatorPolicyFilter::hasCapability( $viewer, PhabricatorApplication::getByClass('PhabricatorManiphestApplication'), ManiphestBulkEditCapability::CAPABILITY); $manage_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($manage_items as $item) { $manage_menu->addAction($item); } $manage_button = id(new PHUIListItemView()) ->setIcon('fa-cog') ->setHref('#') ->addSigil('boards-dropdown-menu') ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => pht('Manage'), 'align' => 'S', 'items' => hsprintf('%s', $manage_menu), )); return $manage_button; } private function buildFullscreenMenu() { $up = id(new PHUIListItemView()) ->setIcon('fa-arrows-alt') ->setHref('#') ->addClass('phui-workboard-expand-icon') ->addSigil('jx-toggle-class') ->addSigil('has-tooltip') ->setMetaData(array( 'tip' => pht('Fullscreen'), 'map' => array( 'phabricator-standard-page' => 'phui-workboard-fullscreen', ), )); return $up; } private function buildColumnMenu( PhabricatorProject $project, PhabricatorProjectColumn $column) { $request = $this->getRequest(); $viewer = $request->getUser(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); $column_items = array(); if ($column->getProxyPHID()) { $default_phid = $column->getProxyPHID(); } else { $default_phid = $column->getProjectPHID(); } $specs = id(new ManiphestEditEngine()) ->setViewer($viewer) ->newCreateActionSpecifications(array()); foreach ($specs as $spec) { $column_items[] = id(new PhabricatorActionView()) ->setIcon($spec['icon']) ->setName($spec['name']) ->setHref($spec['uri']) ->setDisabled($spec['disabled']) ->addSigil('column-add-task') ->setMetadata( array( 'createURI' => $spec['uri'], 'columnPHID' => $column->getPHID(), 'boardPHID' => $project->getPHID(), 'projectPHID' => $default_phid, )); } if (count($specs) > 1) { $column_items[] = id(new PhabricatorActionView()) ->setType(PhabricatorActionView::TYPE_DIVIDER); } $batch_edit_uri = $request->getRequestURI(); $batch_edit_uri->setQueryParam('batch', $column->getID()); $can_batch_edit = PhabricatorPolicyFilter::hasCapability( $viewer, PhabricatorApplication::getByClass('PhabricatorManiphestApplication'), ManiphestBulkEditCapability::CAPABILITY); $column_items[] = id(new PhabricatorActionView()) ->setIcon('fa-list-ul') ->setName(pht('Batch Edit Tasks...')) ->setHref($batch_edit_uri) ->setDisabled(!$can_batch_edit); // Column Related Actions Below // $edit_uri = 'board/'.$this->id.'/edit/'.$column->getID().'/'; $column_items[] = id(new PhabricatorActionView()) ->setName(pht('Edit Column')) ->setIcon('fa-pencil') ->setHref($this->getApplicationURI($edit_uri)) ->setDisabled(!$can_edit) ->setWorkflow(true); $can_hide = ($can_edit && !$column->isDefaultColumn()); $hide_uri = 'board/'.$this->id.'/hide/'.$column->getID().'/'; $hide_uri = $this->getApplicationURI($hide_uri); $hide_uri = $this->getURIWithState($hide_uri); if (!$column->isHidden()) { $column_items[] = id(new PhabricatorActionView()) ->setName(pht('Hide Column')) ->setIcon('fa-eye-slash') ->setHref($hide_uri) ->setDisabled(!$can_hide) ->setWorkflow(true); } else { $column_items[] = id(new PhabricatorActionView()) ->setName(pht('Show Column')) ->setIcon('fa-eye') ->setHref($hide_uri) ->setDisabled(!$can_hide) ->setWorkflow(true); } $column_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($column_items as $item) { $column_menu->addAction($item); } $column_button = id(new PHUIIconView()) ->setIcon('fa-caret-down') ->setHref('#') ->addSigil('boards-dropdown-menu') ->setMetadata( array( 'items' => hsprintf('%s', $column_menu), )); return $column_button; } /** * Add current state parameters (like order and the visibility of hidden * columns) to a URI. * * This allows actions which toggle or adjust one piece of state to keep * the rest of the board state persistent. If no URI is provided, this method * starts with the request URI. * * @param string|null URI to add state parameters to. * @param bool True to explicitly include all state. * @return PhutilURI URI with state parameters. */ private function getURIWithState($base = null, $force = false) { $project = $this->getProject(); if ($base === null) { $base = $this->getRequest()->getRequestURI(); } $base = new PhutilURI($base); if ($force || ($this->sortKey != $this->getDefaultSort($project))) { $base->setQueryParam('order', $this->sortKey); } else { $base->setQueryParam('order', null); } if ($force || ($this->queryKey != $this->getDefaultFilter($project))) { $base->setQueryParam('filter', $this->queryKey); } else { $base->setQueryParam('filter', null); } $base->setQueryParam('hidden', $this->showHidden ? 'true' : null); return $base; } private function buildInitializeContent(PhabricatorProject $project) { $request = $this->getRequest(); $viewer = $this->getViewer(); $type = $request->getStr('initialize-type'); $id = $project->getID(); $profile_uri = $this->getApplicationURI("profile/{$id}/"); $board_uri = $this->getApplicationURI("board/{$id}/"); $import_uri = $this->getApplicationURI("board/{$id}/import/"); $set_default = $request->getBool('default'); if ($set_default) { $this ->getProfileMenuEngine() ->adjustDefault(PhabricatorProject::ITEM_WORKBOARD); } if ($request->isFormPost()) { if ($type == 'backlog-only') { $column = PhabricatorProjectColumn::initializeNewColumn($viewer) ->setSequence(0) ->setProperty('isDefault', true) ->setProjectPHID($project->getPHID()) ->save(); $project->setHasWorkboard(1)->save(); return id(new AphrontRedirectResponse()) ->setURI($board_uri); } else { return id(new AphrontRedirectResponse()) ->setURI($import_uri); } } // TODO: Tailor this UI if the project is already a parent project. We // should not offer options for creating a parent project workboard, since // they can't have their own columns. $new_selector = id(new AphrontFormRadioButtonControl()) ->setLabel(pht('Columns')) ->setName('initialize-type') ->setValue('backlog-only') ->addButton( 'backlog-only', pht('New Empty Board'), pht('Create a new board with just a backlog column.')) ->addButton( 'import', pht('Import Columns'), pht('Import board columns from another project.')); $default_checkbox = id(new AphrontFormCheckboxControl()) ->setLabel(pht('Make Default')) ->addCheckbox( 'default', 1, pht('Make the workboard the default view for this project.'), true); $form = id(new AphrontFormView()) ->setUser($viewer) ->addHiddenInput('initialize', 1) ->appendRemarkupInstructions( pht('The workboard for this project has not been created yet.')) ->appendControl($new_selector) ->appendControl($default_checkbox) ->appendControl( id(new AphrontFormSubmitControl()) ->addCancelButton($profile_uri) ->setValue(pht('Create Workboard'))); $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Create Workboard')) ->setForm($form); return $box; } private function buildNoAccessContent(PhabricatorProject $project) { $viewer = $this->getViewer(); $id = $project->getID(); $profile_uri = $this->getApplicationURI("profile/{$id}/"); return $this->newDialog() ->setTitle(pht('Unable to Create Workboard')) ->appendParagraph( pht( 'The workboard for this project has not been created yet, '. 'but you do not have permission to create it. Only users '. 'who can edit this project can create a workboard for it.')) ->addCancelButton($profile_uri); } private function buildEnableContent(PhabricatorProject $project) { $request = $this->getRequest(); $viewer = $this->getViewer(); $id = $project->getID(); $profile_uri = $this->getApplicationURI("profile/{$id}/"); $board_uri = $this->getApplicationURI("board/{$id}/"); if ($request->isFormPost()) { $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) ->setTransactionType(PhabricatorProjectTransaction::TYPE_HASWORKBOARD) ->setNewValue(1); id(new PhabricatorProjectTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->applyTransactions($project, $xactions); return id(new AphrontRedirectResponse()) ->setURI($board_uri); } return $this->newDialog() ->setTitle(pht('Workboard Disabled')) ->addHiddenInput('initialize', 1) ->appendParagraph( pht( 'This workboard has been disabled, but can be restored to its '. 'former glory.')) ->addCancelButton($profile_uri) ->addSubmitButton(pht('Enable Workboard')); } private function buildDisabledContent(PhabricatorProject $project) { $viewer = $this->getViewer(); $id = $project->getID(); $profile_uri = $this->getApplicationURI("profile/{$id}/"); return $this->newDialog() ->setTitle(pht('Workboard Disabled')) ->appendParagraph( pht( 'This workboard has been disabled, and you do not have permission '. 'to enable it. Only users who can edit this project can restore '. 'the workboard.')) ->addCancelButton($profile_uri); } } diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index df13278812..452085fa9f 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -1,261 +1,261 @@ needImages(true) ->needMembers(true) ->needWatchers(true); } protected function buildCustomSearchFields() { return array( id(new PhabricatorSearchTextField()) ->setLabel(pht('Name')) ->setKey('name'), id(new PhabricatorUsersSearchField()) ->setLabel(pht('Members')) ->setKey('memberPHIDs') ->setConduitKey('members') ->setAliases(array('member', 'members')), id(new PhabricatorUsersSearchField()) ->setLabel(pht('Watchers')) ->setKey('watcherPHIDs') ->setConduitKey('watchers') ->setAliases(array('watcher', 'watchers')), id(new PhabricatorSearchSelectField()) ->setLabel(pht('Status')) ->setKey('status') ->setOptions($this->getStatusOptions()), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Milestones')) ->setKey('isMilestone') ->setOptions( pht('(Show All)'), pht('Show Only Milestones'), pht('Hide Milestones')) ->setDescription( pht( 'Pass true to find only milestones, or false to omit '. 'milestones.')), id(new PhabricatorSearchCheckboxesField()) ->setLabel(pht('Icons')) ->setKey('icons') ->setOptions($this->getIconOptions()), id(new PhabricatorSearchCheckboxesField()) ->setLabel(pht('Colors')) ->setKey('colors') ->setOptions($this->getColorOptions()), id(new PhabricatorPHIDsSearchField()) ->setLabel(pht('Parent Projects')) ->setKey('parentPHIDs') ->setConduitKey('parents') ->setAliases(array('parent', 'parents', 'parentPHID')) ->setDescription(pht('Find direct subprojects of specified parents.')), id(new PhabricatorPHIDsSearchField()) ->setLabel(pht('Ancestor Projects')) ->setKey('ancestorPHIDs') ->setConduitKey('ancestors') ->setAliases(array('ancestor', 'ancestors', 'ancestorPHID')) ->setDescription( pht('Find all subprojects beneath specified ancestors.')), ); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); if (strlen($map['name'])) { $tokens = PhabricatorTypeaheadDatasource::tokenizeString($map['name']); $query->withNameTokens($tokens); } if ($map['memberPHIDs']) { $query->withMemberPHIDs($map['memberPHIDs']); } if ($map['watcherPHIDs']) { $query->withWatcherPHIDs($map['watcherPHIDs']); } if ($map['status']) { $status = idx($this->getStatusValues(), $map['status']); if ($status) { $query->withStatus($status); } } if ($map['icons']) { $query->withIcons($map['icons']); } if ($map['colors']) { $query->withColors($map['colors']); } if ($map['isMilestone'] !== null) { $query->withIsMilestone($map['isMilestone']); } if ($map['parentPHIDs']) { $query->withParentProjectPHIDs($map['parentPHIDs']); } if ($map['ancestorPHIDs']) { $query->withAncestorProjectPHIDs($map['ancestorPHIDs']); } return $query; } protected function getURI($path) { return '/project/'.$path; } protected function getBuiltinQueryNames() { $names = array(); if ($this->requireViewer()->isLoggedIn()) { $names['joined'] = pht('Joined'); } if ($this->requireViewer()->isLoggedIn()) { $names['watching'] = pht('Watching'); } $names['active'] = pht('Active'); $names['all'] = pht('All'); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); $viewer_phid = $this->requireViewer()->getPHID(); // By default, do not show milestones in the list view. $query->setParameter('isMilestone', false); switch ($query_key) { case 'all': return $query; case 'active': return $query ->setParameter('status', 'active'); case 'joined': return $query ->setParameter('memberPHIDs', array($viewer_phid)) ->setParameter('status', 'active'); case 'watching': return $query ->setParameter('watcherPHIDs', array($viewer_phid)) ->setParameter('status', 'active'); } return parent::buildSavedQueryFromBuiltin($query_key); } private function getStatusOptions() { return array( 'active' => pht('Show Only Active Projects'), 'archived' => pht('Show Only Archived Projects'), 'all' => pht('Show All Projects'), ); } private function getStatusValues() { return array( 'active' => PhabricatorProjectQuery::STATUS_ACTIVE, 'archived' => PhabricatorProjectQuery::STATUS_ARCHIVED, 'all' => PhabricatorProjectQuery::STATUS_ANY, ); } private function getIconOptions() { $options = array(); $set = new PhabricatorProjectIconSet(); foreach ($set->getIcons() as $icon) { if ($icon->getIsDisabled()) { continue; } $options[$icon->getKey()] = array( id(new PHUIIconView()) ->setIcon($icon->getIcon()), ' ', $icon->getLabel(), ); } return $options; } private function getColorOptions() { $options = array(); foreach (PhabricatorProjectIconSet::getColorMap() as $color => $name) { $options[$color] = array( id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($color) + ->setColor($color) ->setName($name), ); } return $options; } protected function renderResultList( array $projects, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($projects, 'PhabricatorProject'); $viewer = $this->requireViewer(); $list = id(new PhabricatorProjectListView()) ->setUser($viewer) ->setProjects($projects) ->setShowWatching(true) ->setShowMember(true) ->renderList(); return id(new PhabricatorApplicationSearchResultView()) ->setObjectList($list) ->setNoDataString(pht('No projects found.')); } protected function getNewUserBody() { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Project')) ->setHref('/project/edit/') ->setColor(PHUIButtonView::GREEN); $icon = $this->getApplication()->getIcon(); $app_name = $this->getApplication()->getName(); $view = id(new PHUIBigInfoView()) ->setIcon($icon) ->setTitle(pht('Welcome to %s', $app_name)) ->setDescription( pht('Projects are flexible storage containers used as '. 'tags, teams, projects, or anything you need to group.')) ->addAction($create_button); return $view; } } diff --git a/src/applications/project/view/ProjectBoardTaskCard.php b/src/applications/project/view/ProjectBoardTaskCard.php index ba5213a623..100fee20b3 100644 --- a/src/applications/project/view/ProjectBoardTaskCard.php +++ b/src/applications/project/view/ProjectBoardTaskCard.php @@ -1,143 +1,143 @@ viewer = $viewer; return $this; } public function getViewer() { return $this->viewer; } public function setProjectHandles(array $handles) { $this->projectHandles = $handles; return $this; } public function getProjectHandles() { return $this->projectHandles; } public function setCoverImageFile(PhabricatorFile $cover_image_file) { $this->coverImageFile = $cover_image_file; return $this; } public function getCoverImageFile() { return $this->coverImageFile; } public function setTask(ManiphestTask $task) { $this->task = $task; return $this; } public function getTask() { return $this->task; } public function setOwner(PhabricatorObjectHandle $owner = null) { $this->owner = $owner; return $this; } public function getOwner() { return $this->owner; } public function setCanEdit($can_edit) { $this->canEdit = $can_edit; return $this; } public function getCanEdit() { return $this->canEdit; } public function getItem() { $task = $this->getTask(); $owner = $this->getOwner(); $can_edit = $this->getCanEdit(); $viewer = $this->getViewer(); $color_map = ManiphestTaskPriority::getColorMap(); $bar_color = idx($color_map, $task->getPriority(), 'grey'); $card = id(new PHUIObjectItemView()) ->setObject($task) ->setUser($viewer) ->setObjectName('T'.$task->getID()) ->setHeader($task->getTitle()) ->setGrippable($can_edit) ->setHref('/T'.$task->getID()) ->addSigil('project-card') ->setDisabled($task->isClosed()) ->addAction( id(new PHUIListItemView()) ->setName(pht('Edit')) ->setIcon('fa-pencil') ->addSigil('edit-project-card') ->setHref('/maniphest/task/edit/'.$task->getID().'/')) ->setBarColor($bar_color); if ($owner) { $card->addHandleIcon($owner, $owner->getName()); } $cover_file = $this->getCoverImageFile(); if ($cover_file) { $card->setCoverImage($cover_file->getBestURI()); } if (ManiphestTaskPoints::getIsEnabled()) { $points = $task->getPoints(); if ($points !== null) { $points_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade(PHUITagView::COLOR_GREY) + ->setColor(PHUITagView::COLOR_GREY) ->setSlimShady(true) ->setName($points) ->addClass('phui-workcard-points'); $card->addAttribute($points_tag); } } if ($task->isClosed()) { $icon = ManiphestTaskStatus::getStatusIcon($task->getStatus()); $icon = id(new PHUIIconView()) ->setIcon($icon.' grey'); $card->addAttribute($icon); $card->setBarColor('grey'); } $project_handles = $this->getProjectHandles(); // Remove any archived projects from the list. if ($project_handles) { foreach ($project_handles as $key => $handle) { if ($handle->getStatus() == PhabricatorObjectHandle::STATUS_CLOSED) { unset($project_handles[$key]); } } } if ($project_handles) { $project_handles = array_reverse($project_handles); $tag_list = id(new PHUIHandleTagListView()) ->setSlim(true) ->setHandles($project_handles); $card->addAttribute($tag_list); } $card->addClass('phui-workcard'); return $card; } } diff --git a/src/applications/search/query/PhabricatorFulltextToken.php b/src/applications/search/query/PhabricatorFulltextToken.php index d42c15e9e6..4edeb098a9 100644 --- a/src/applications/search/query/PhabricatorFulltextToken.php +++ b/src/applications/search/query/PhabricatorFulltextToken.php @@ -1,88 +1,88 @@ token = $token; return $this; } public function getToken() { return $this->token; } public function isQueryable() { return !$this->getIsShort() && !$this->getIsStopword(); } public function setIsShort($is_short) { $this->isShort = $is_short; return $this; } public function getIsShort() { return $this->isShort; } public function setIsStopword($is_stopword) { $this->isStopword = $is_stopword; return $this; } public function getIsStopword() { return $this->isStopword; } public function newTag() { $token = $this->getToken(); $tip = null; $icon = null; if ($this->getIsShort()) { $shade = PHUITagView::COLOR_GREY; $tip = pht('Ignored Short Word'); } else if ($this->getIsStopword()) { $shade = PHUITagView::COLOR_GREY; $tip = pht('Ignored Common Word'); } else { $operator = $token->getOperator(); switch ($operator) { case PhutilSearchQueryCompiler::OPERATOR_NOT: $shade = PHUITagView::COLOR_RED; $icon = 'fa-minus'; break; default: $shade = PHUITagView::COLOR_BLUE; break; } } $tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($shade) + ->setColor($shade) ->setName($token->getValue()); if ($tip !== null) { Javelin::initBehavior('phabricator-tooltips'); $tag ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => $tip, )); } if ($icon !== null) { $tag->setIcon($icon); } return $tag; } } diff --git a/src/applications/uiexample/examples/PHUITagExample.php b/src/applications/uiexample/examples/PHUITagExample.php index 764caaf717..027be2f9d8 100644 --- a/src/applications/uiexample/examples/PHUITagExample.php +++ b/src/applications/uiexample/examples/PHUITagExample.php @@ -1,231 +1,231 @@ setType(PHUITagView::TYPE_PERSON) ->setName('@alincoln') ->setHref('#'); $intro[] = ' how is stuff?'; $intro[] = hsprintf('

'); $intro[] = 'Did you hear that '; $intro[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_PERSON) ->setName('@gwashington') ->setDotColor(PHUITagView::COLOR_RED) ->setHref('#'); $intro[] = ' is away, '; $intro[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_PERSON) ->setName('@tjefferson') ->setDotColor(PHUITagView::COLOR_ORANGE) ->setHref('#'); $intro[] = ' has some errands, and '; $intro[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_PERSON) ->setName('@rreagan') ->setDotColor(PHUITagView::COLOR_GREY) ->setHref('#'); $intro[] = ' is gone?'; $intro[] = hsprintf('

'); $intro[] = 'Take a look at '; $intro[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_OBJECT) ->setName('D123') ->setHref('#'); $intro[] = ' when you get a chance.'; $intro[] = hsprintf('

'); $intro[] = 'Hmm? '; $intro[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_OBJECT) ->setName('D123') ->setClosed(true) ->setHref('#'); $intro[] = ' is '; $intro[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setBackgroundColor(PHUITagView::COLOR_BLACK) ->setName('Abandoned'); $intro[] = '.'; $intro[] = hsprintf('

'); $intro[] = 'I hope someone is going to '; $intro[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_OBJECT) ->setName('T123: Water The Dog') ->setHref('#'); $intro[] = ' -- that task is '; $intro[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setBackgroundColor(PHUITagView::COLOR_RED) ->setName('High Priority'); $intro[] = '!'; $intro = id(new PHUIBoxView()) ->appendChild($intro) ->addPadding(PHUI::PADDING_LARGE); $header1 = id(new PHUIHeaderView()) ->setHeader('Colors'); $colors = PHUITagView::getColors(); $tags = array(); foreach ($colors as $color) { $tags[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setBackgroundColor($color) ->setName(ucwords($color)); $tags[] = hsprintf('

'); } $content1 = id(new PHUIBoxView()) ->appendChild($tags) ->addPadding(PHUI::PADDING_LARGE); $tags = array(); $tags[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setBackgroundColor(PHUITagView::COLOR_GREEN) ->setDotColor(PHUITagView::COLOR_RED) ->setName(pht('Christmas')); $tags[] = hsprintf('

'); $tags[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_OBJECT) ->setBackgroundColor(PHUITagView::COLOR_ORANGE) ->setDotColor(PHUITagView::COLOR_BLACK) ->setName(pht('Halloween')); $tags[] = hsprintf('

'); $tags[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setBackgroundColor(PHUITagView::COLOR_INDIGO) ->setDotColor(PHUITagView::COLOR_YELLOW) ->setName(pht('Easter')); $content2 = id(new PHUIBoxView()) ->appendChild($tags) ->addPadding(PHUI::PADDING_LARGE); $icons = array(); $icons[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setBackgroundColor(PHUITagView::COLOR_GREEN) ->setIcon('fa-check white') ->setName(pht('Passed')); $icons[] = hsprintf('

'); $icons[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setBackgroundColor(PHUITagView::COLOR_RED) ->setIcon('fa-times white') ->setName(pht('Failed')); $icons[] = hsprintf('

'); $icons[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setBackgroundColor(PHUITagView::COLOR_BLUE) ->setIcon('fa-refresh white') ->setName(pht('Running')); $icons[] = hsprintf('

'); $icons[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setBackgroundColor(PHUITagView::COLOR_GREY) ->setIcon('fa-pause white') ->setName(pht('Paused')); $icons[] = hsprintf('

'); $icons[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_STATE) ->setBackgroundColor(PHUITagView::COLOR_BLACK) ->setIcon('fa-stop white') ->setName(pht('Stopped')); $content3 = id(new PHUIBoxView()) ->appendChild($icons) ->addPadding(PHUI::PADDING_LARGE); $shades = PHUITagView::getShades(); $tags = array(); foreach ($shades as $shade) { $tags[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) ->setColor($shade) ->setIcon('fa-tags') ->setName(ucwords($shade)) ->setHref('#'); $tags[] = hsprintf(' '); $tags[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) ->setColor($shade) ->setSlimShady(true) ->setIcon('fa-tags') ->setName(ucwords($shade)) ->setHref('#'); $tags[] = hsprintf('

'); } $content4 = id(new PHUIBoxView()) ->appendChild($tags) ->addPadding(PHUI::PADDING_LARGE); $outlines = PHUITagView::getOutlines(); $tags = array(); foreach ($outlines as $outline) { $tags[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_OUTLINE) - ->setShade($outline) + ->setColor($outline) ->setName($outline); $tags[] = hsprintf(' '); $tags[] = id(new PHUITagView()) ->setType(PHUITagView::TYPE_OUTLINE) - ->setShade($outline) + ->setColor($outline) ->setSlimShady(true) ->setName($outline); $tags[] = hsprintf('

'); } $content5 = id(new PHUIBoxView()) ->appendChild($tags) ->addPadding(PHUI::PADDING_LARGE); $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Inline')) ->appendChild($intro); $box1 = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Colors')) ->appendChild($content1); $box2 = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Holidays')) ->appendChild($content2); $box3 = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Icons')) ->appendChild($content3); $box4 = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Shades')) ->appendChild($content4); $box5 = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Outlines')) ->appendChild($content5); return array($box, $box1, $box2, $box3, $box4, $box5); } } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index 821834431a..a42efa932a 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -1,493 +1,493 @@ inlineComment = $comment; return $this; } public function isHidden() { return $this->inlineComment->isHidden(); } public function setHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = $handles; return $this; } public function setMarkupEngine(PhabricatorMarkupEngine $engine) { $this->markupEngine = $engine; return $this; } public function setEditable($editable) { $this->editable = $editable; return $this; } public function setPreview($preview) { $this->preview = $preview; return $this; } public function setAllowReply($allow_reply) { $this->allowReply = $allow_reply; return $this; } public function setRenderer($renderer) { $this->renderer = $renderer; return $this; } public function getRenderer() { return $this->renderer; } public function setCanMarkDone($can_mark_done) { $this->canMarkDone = $can_mark_done; return $this; } public function getCanMarkDone() { return $this->canMarkDone; } public function setObjectOwnerPHID($phid) { $this->objectOwnerPHID = $phid; return $this; } public function getObjectOwnerPHID() { return $this->objectOwnerPHID; } public function getAnchorName() { $inline = $this->inlineComment; if ($inline->getID()) { return 'inline-'.$inline->getID(); } return null; } public function getScaffoldCellID() { $anchor = $this->getAnchorName(); if ($anchor) { return 'anchor-'.$anchor; } return null; } public function render() { require_celerity_resource('phui-inline-comment-view-css'); $inline = $this->inlineComment; $classes = array( 'differential-inline-comment', ); $is_fixed = false; switch ($inline->getFixedState()) { case PhabricatorInlineCommentInterface::STATE_DONE: case PhabricatorInlineCommentInterface::STATE_DRAFT: $is_fixed = true; break; } $metadata = array( 'id' => $inline->getID(), 'phid' => $inline->getPHID(), 'changesetID' => $inline->getChangesetID(), 'number' => $inline->getLineNumber(), 'length' => $inline->getLineLength(), 'isNewFile' => (bool)$inline->getIsNewFile(), 'on_right' => $this->getIsOnRight(), 'original' => $inline->getContent(), 'replyToCommentPHID' => $inline->getReplyToCommentPHID(), 'isDraft' => $inline->isDraft(), 'isFixed' => $is_fixed, 'isGhost' => $inline->getIsGhost(), ); $sigil = 'differential-inline-comment'; if ($this->preview) { $sigil = $sigil.' differential-inline-comment-preview'; } $classes = array( 'differential-inline-comment', ); $content = $inline->getContent(); $handles = $this->handles; $links = array(); $is_synthetic = false; if ($inline->getSyntheticAuthor()) { $is_synthetic = true; } $draft_text = null; if (!$is_synthetic) { // This display is controlled by CSS $draft_text = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) ->setName(pht('Unsubmitted')) ->setSlimShady(true) - ->setShade(PHUITagView::COLOR_RED) + ->setColor(PHUITagView::COLOR_RED) ->addClass('mml inline-draft-text'); } $ghost_tag = null; $ghost = $inline->getIsGhost(); $ghost_id = null; if ($ghost) { if ($ghost['new']) { $ghosticon = 'fa-fast-forward'; $reason = pht('View on forward revision'); } else { $ghosticon = 'fa-fast-backward'; $reason = pht('View on previous revision'); } $ghost_icon = id(new PHUIIconView()) ->setIcon($ghosticon) ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => $reason, 'size' => 300, )); $ghost_tag = phutil_tag( 'a', array( 'class' => 'ghost-icon', 'href' => $ghost['href'], 'target' => '_blank', ), $ghost_icon); $classes[] = 'inline-comment-ghost'; } // I think this is unused if ($inline->getHasReplies()) { $classes[] = 'inline-comment-has-reply'; } if ($inline->getReplyToCommentPHID()) { $classes[] = 'inline-comment-is-reply'; } $viewer_phid = $this->getUser()->getPHID(); $owner_phid = $this->getObjectOwnerPHID(); if ($viewer_phid) { if ($viewer_phid == $owner_phid) { $classes[] = 'viewer-is-object-owner'; } } $anchor_name = $this->getAnchorName(); $action_buttons = array(); $can_reply = (!$this->editable) && (!$this->preview) && ($this->allowReply) && // NOTE: No product reason why you can't reply to synthetic comments, // but the reply mechanism currently sends the inline comment ID to the // server, not file/line information, and synthetic comments don't have // an inline comment ID. (!$is_synthetic); if ($can_reply) { $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') ->setIcon('fa-reply') ->setTooltip(pht('Reply')) ->addSigil('differential-inline-reply') ->setMustCapture(true); } if ($this->editable && !$this->preview) { $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') ->setIcon('fa-pencil') ->setTooltip(pht('Edit')) ->addSigil('differential-inline-edit') ->setMustCapture(true); $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') ->setIcon('fa-trash-o') ->setTooltip(pht('Delete')) ->addSigil('differential-inline-delete') ->setMustCapture(true); } else if ($this->preview) { $links[] = javelin_tag( 'a', array( 'class' => 'inline-button-divider pml msl', 'meta' => array( 'anchor' => $anchor_name, ), 'sigil' => 'differential-inline-preview-jump', ), pht('View')); $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') ->setTooltip(pht('Delete')) ->setIcon('fa-trash-o') ->addSigil('differential-inline-delete') ->setMustCapture(true); } if (!$this->preview && $this->canHide()) { $action_buttons[] = id(new PHUIButtonView()) ->setTag('a') ->setTooltip(pht('Hide Comment')) ->setIcon('fa-times') ->addSigil('hide-inline') ->setMustCapture(true); } $done_button = null; if (!$is_synthetic) { $draft_state = false; switch ($inline->getFixedState()) { case PhabricatorInlineCommentInterface::STATE_DRAFT: $is_done = ($this->getCanMarkDone()); $draft_state = true; break; case PhabricatorInlineCommentInterface::STATE_UNDRAFT: $is_done = !($this->getCanMarkDone()); $draft_state = true; break; case PhabricatorInlineCommentInterface::STATE_DONE: $is_done = true; break; default: case PhabricatorInlineCommentInterface::STATE_UNDONE: $is_done = false; break; } // If you don't have permission to mark the comment as "Done", you also // can not see the draft state. if (!$this->getCanMarkDone()) { $draft_state = false; } if ($is_done) { $classes[] = 'inline-is-done'; } if ($draft_state) { $classes[] = 'inline-state-is-draft'; } if ($this->getCanMarkDone()) { $done_input = javelin_tag( 'input', array( 'type' => 'checkbox', 'checked' => ($is_done ? 'checked' : null), 'disabled' => ($this->getCanMarkDone() ? null : 'disabled'), 'class' => 'differential-inline-done', 'sigil' => 'differential-inline-done', )); $done_button = phutil_tag( 'label', array( 'class' => 'differential-inline-done-label '. ($this->getCanMarkDone() ? null : 'done-is-disabled'), ), array( $done_input, pht('Done'), )); } else { if ($is_done) { $icon = id(new PHUIIconView())->setIcon('fa-check sky msr'); $label = pht('Done'); $class = 'button-done'; } else { $icon = null; $label = pht('Not Done'); $class = 'button-not-done'; } $done_button = phutil_tag( 'div', array( 'class' => 'done-label '.$class, ), array( $icon, $label, )); } } $content = $this->markupEngine->getOutput( $inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); if ($this->preview) { $anchor = null; } else { $anchor = phutil_tag( 'a', array( 'name' => $anchor_name, 'id' => $anchor_name, 'class' => 'differential-inline-comment-anchor', ), ''); } if ($inline->isDraft() && !$is_synthetic) { $classes[] = 'inline-state-is-draft'; } if ($is_synthetic) { $classes[] = 'differential-inline-comment-synthetic'; } $classes = implode(' ', $classes); $author_owner = null; if ($is_synthetic) { $author = $inline->getSyntheticAuthor(); } else { $author = $handles[$inline->getAuthorPHID()]->getName(); if ($inline->getAuthorPHID() == $this->objectOwnerPHID) { $author_owner = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) ->setName(pht('Author')) ->setSlimShady(true) - ->setShade(PHUITagView::COLOR_YELLOW) + ->setColor(PHUITagView::COLOR_YELLOW) ->addClass('mml'); } } $actions = null; if ($action_buttons) { $actions = new PHUIButtonBarView(); $actions->setBorderless(true); $actions->addClass('inline-button-divider'); foreach ($action_buttons as $button) { $actions->addButton($button); } } $group_left = phutil_tag( 'div', array( 'class' => 'inline-head-left', ), array( $author, $author_owner, $draft_text, $ghost_tag, )); $group_right = phutil_tag( 'div', array( 'class' => 'inline-head-right', ), array( $done_button, $links, $actions, )); $snippet = id(new PhutilUTF8StringTruncator()) ->setMaximumGlyphs(96) ->truncateString($inline->getContent()); $metadata['snippet'] = pht('%s: %s', $author, $snippet); $markup = javelin_tag( 'div', array( 'class' => $classes, 'sigil' => $sigil, 'meta' => $metadata, ), array( javelin_tag( 'div', array( 'class' => 'differential-inline-comment-head grouped', 'sigil' => 'differential-inline-header', ), array( $group_left, $group_right, )), phutil_tag_div( 'differential-inline-comment-content', phutil_tag_div('phabricator-remarkup', $content)), )); $summary = phutil_tag( 'div', array( 'class' => 'differential-inline-summary', ), array( phutil_tag('strong', array(), pht('%s:', $author)), ' ', $snippet, )); return array( $anchor, $markup, $summary, ); } private function canHide() { $inline = $this->inlineComment; if ($inline->isDraft()) { return false; } if (!$inline->getID()) { return false; } $viewer = $this->getUser(); if (!$viewer->isLoggedIn()) { return false; } if (!$inline->supportsHiding()) { return false; } return true; } } diff --git a/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php index 458ee5b834..3fc3373001 100644 --- a/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php +++ b/src/infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php @@ -1,112 +1,112 @@ isFlatText($matches[0])) { return $matches[0]; } $elements = ltrim($matches[1], ", \n"); $elements = explode('>', $elements); $defaults = array( 'name' => null, 'type' => 'link', 'href' => null, 'icon' => null, ); $sequence = array(); $parser = new PhutilSimpleOptions(); foreach ($elements as $element) { if (strpos($element, '=') === false) { $sequence[] = array( 'name' => trim($element), ) + $defaults; } else { $sequence[] = $parser->parse($element) + $defaults; } } if ($this->getEngine()->isTextMode()) { return implode(' > ', ipull($sequence, 'name')); } static $icon_names; if (!$icon_names) { $icon_names = array_fuse(PHUIIconView::getIcons()); } $out = array(); foreach ($sequence as $item) { $item_name = $item['name']; $item_color = PHUITagView::COLOR_GREY; if ($item['type'] == 'instructions') { $item_name = phutil_tag('em', array(), $item_name); $item_color = PHUITagView::COLOR_INDIGO; } $tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) - ->setShade($item_color) + ->setColor($item_color) ->setName($item_name); if ($item['icon']) { $icon_name = 'fa-'.$item['icon']; if (isset($icon_names[$icon_name])) { $tag->setIcon($icon_name); } } if ($item['href'] !== null) { if (PhabricatorEnv::isValidRemoteURIForLink($item['href'])) { $tag->setHref($item['href']); $tag->setExternal(true); } } $out[] = $tag; } if ($this->getEngine()->isHTMLMailMode()) { $arrow_attr = array( 'style' => 'color: #92969D;', ); $nav_attr = array(); } else { $arrow_attr = array( 'class' => 'remarkup-nav-sequence-arrow', ); $nav_attr = array( 'class' => 'remarkup-nav-sequence', ); } $joiner = phutil_tag( 'span', $arrow_attr, " \xE2\x86\x92 "); $out = phutil_implode_html($joiner, $out); $out = phutil_tag( 'span', $nav_attr, $out); return $this->getEngine()->storeText($out); } } diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index 0cd8379b42..ab576c64d5 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -1,563 +1,563 @@ header = $header; return $this; } public function setNoBackground($nada) { $this->noBackground = $nada; return $this; } public function setTall($tall) { $this->tall = $tall; return $this; } public function addTag(PHUITagView $tag) { $this->tags[] = $tag; return $this; } public function addBadge(PHUIBadgeMiniView $badge) { $this->badges[] = $badge; return $this; } public function setImage($uri) { $this->image = $uri; return $this; } public function setImageURL($url) { $this->imageURL = $url; return $this; } public function setImageEditURL($url) { $this->imageEditURL = $url; return $this; } public function setSubheader($subheader) { $this->subheader = $subheader; return $this; } public function setBleedHeader($bleed) { $this->bleedHeader = $bleed; return $this; } public function setProfileHeader($bighead) { $this->profileHeader = $bighead; return $this; } public function setHeaderIcon($icon) { $this->headerIcon = $icon; return $this; } public function setActionList(PhabricatorActionListView $list) { $this->actionList = $list; return $this; } public function setActionListID($action_list_id) { $this->actionListID = $action_list_id; return $this; } public function setPolicyObject(PhabricatorPolicyInterface $object) { $this->policyObject = $object; return $this; } public function addProperty($property, $value) { $this->properties[$property] = $value; return $this; } public function addActionLink(PHUIButtonView $button) { $this->actionLinks[] = $button; return $this; } public function addActionItem($action) { $this->actionItems[] = $action; return $this; } public function setButtonBar(PHUIButtonBarView $bb) { $this->buttonBar = $bb; return $this; } public function setStatus($icon, $color, $name) { // TODO: Normalize "closed/archived" to constants. if ($color == 'dark') { $color = PHUITagView::COLOR_INDIGO; } $tag = id(new PHUITagView()) ->setName($name) ->setIcon($icon) - ->setShade($color) + ->setColor($color) ->setType(PHUITagView::TYPE_SHADE); return $this->addProperty(self::PROPERTY_STATUS, $tag); } public function setEpoch($epoch) { $age = time() - $epoch; $age = floor($age / (60 * 60 * 24)); if ($age < 1) { $when = pht('Today'); } else if ($age == 1) { $when = pht('Yesterday'); } else { $when = pht('%s Day(s) Ago', new PhutilNumber($age)); } $this->setStatus('fa-clock-o bluegrey', null, pht('Updated %s', $when)); return $this; } public function setHref($href) { $this->href = $href; return $this; } public function getHref() { return $this->href; } protected function getTagName() { return 'div'; } protected function getTagAttributes() { require_celerity_resource('phui-header-view-css'); $classes = array(); $classes[] = 'phui-header-shell'; if ($this->noBackground) { $classes[] = 'phui-header-no-backgound'; } if ($this->bleedHeader) { $classes[] = 'phui-bleed-header'; } if ($this->profileHeader) { $classes[] = 'phui-profile-header'; } if ($this->properties || $this->policyObject || $this->subheader || $this->tall) { $classes[] = 'phui-header-tall'; } return array( 'class' => $classes, ); } protected function getTagContent() { if ($this->actionList || $this->actionListID) { $action_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Actions')) ->setHref('#') ->setIcon('fa-bars') ->addClass('phui-mobile-menu'); if ($this->actionList) { $action_button->setDropdownMenu($this->actionList); } else if ($this->actionListID) { $action_button->setDropdownMenuID($this->actionListID); } $this->addActionLink($action_button); } $image = null; if ($this->image) { $image_href = null; if ($this->imageURL) { $image_href = $this->imageURL; } else if ($this->imageEditURL) { $image_href = $this->imageEditURL; } $image = phutil_tag( 'span', array( 'class' => 'phui-header-image', 'style' => 'background-image: url('.$this->image.')', )); if ($image_href) { $edit_view = null; if ($this->imageEditURL) { $edit_view = phutil_tag( 'span', array( 'class' => 'phui-header-image-edit', ), pht('Edit')); } $image = phutil_tag( 'a', array( 'href' => $image_href, 'class' => 'phui-header-image-href', ), array( $image, $edit_view, )); } } $viewer = $this->getUser(); $left = array(); $right = array(); $space_header = null; if ($viewer) { $space_header = id(new PHUISpacesNamespaceContextView()) ->setUser($viewer) ->setObject($this->policyObject); } if ($this->actionLinks) { $actions = array(); foreach ($this->actionLinks as $button) { if (!$button->getColor()) { $button->setColor(PHUIButtonView::GREY); } $button->addClass(PHUI::MARGIN_SMALL_LEFT); $button->addClass('phui-header-action-link'); $actions[] = $button; } $right[] = phutil_tag( 'div', array( 'class' => 'phui-header-action-links', ), $actions); } if ($this->buttonBar) { $right[] = phutil_tag( 'div', array( 'class' => 'phui-header-action-links', ), $this->buttonBar); } if ($this->actionItems) { $action_list = array(); if ($this->actionItems) { foreach ($this->actionItems as $item) { $action_list[] = phutil_tag( 'li', array( 'class' => 'phui-header-action-item', ), $item); } } $right[] = phutil_tag( 'ul', array( 'class' => 'phui-header-action-list', ), $action_list); } $icon = null; if ($this->headerIcon) { $icon = id(new PHUIIconView()) ->setIcon($this->headerIcon) ->addClass('phui-header-icon'); } $header_content = $this->header; $href = $this->getHref(); if ($href !== null) { $header_content = phutil_tag( 'a', array( 'href' => $href, ), $header_content); } $left[] = phutil_tag( 'span', array( 'class' => 'phui-header-header', ), array( $space_header, $icon, $header_content, )); if ($this->subheader || $this->badges) { $badges = null; if ($this->badges) { $badges = new PHUIBadgeBoxView(); $badges->addItems($this->badges); $badges->setCollapsed(true); } $left[] = phutil_tag( 'div', array( 'class' => 'phui-header-subheader', ), array( $badges, $this->subheader, )); } if ($this->properties || $this->policyObject || $this->tags) { $property_list = array(); foreach ($this->properties as $type => $property) { switch ($type) { case self::PROPERTY_STATUS: $property_list[] = $property; break; default: throw new Exception(pht('Incorrect Property Passed')); break; } } if ($this->policyObject) { $property_list[] = $this->renderPolicyProperty($this->policyObject); } if ($this->tags) { $property_list[] = $this->tags; } $left[] = phutil_tag( 'div', array( 'class' => 'phui-header-subheader', ), $property_list); } // We here at @phabricator $header_image = null; if ($image) { $header_image = phutil_tag( 'div', array( 'class' => 'phui-header-col1', ), $image); } // All really love $header_left = phutil_tag( 'div', array( 'class' => 'phui-header-col2', ), $left); // Tables and Pokemon. $header_right = phutil_tag( 'div', array( 'class' => 'phui-header-col3', ), $right); $header_row = phutil_tag( 'div', array( 'class' => 'phui-header-row', ), array( $header_image, $header_left, $header_right, )); return phutil_tag( 'h1', array( 'class' => 'phui-header-view', ), $header_row); } private function renderPolicyProperty(PhabricatorPolicyInterface $object) { $viewer = $this->getUser(); $policies = PhabricatorPolicyQuery::loadPolicies($viewer, $object); $view_capability = PhabricatorPolicyCapability::CAN_VIEW; $policy = idx($policies, $view_capability); if (!$policy) { return null; } // If an object is in a Space with a strictly stronger (more restrictive) // policy, we show the more restrictive policy. This better aligns the // UI hint with the actual behavior. // NOTE: We'll do this even if the viewer has access to only one space, and // show them information about the existence of spaces if they click // through. $use_space_policy = false; if ($object instanceof PhabricatorSpacesInterface) { $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID( $object); $spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($viewer); $space = idx($spaces, $space_phid); if ($space) { $space_policies = PhabricatorPolicyQuery::loadPolicies( $viewer, $space); $space_policy = idx($space_policies, $view_capability); if ($space_policy) { if ($space_policy->isStrongerThan($policy)) { $policy = $space_policy; $use_space_policy = true; } } } } $container_classes = array(); $container_classes[] = 'policy-header-callout'; $phid = $object->getPHID(); // If we're going to show the object policy, try to determine if the object // policy differs from the default policy. If it does, we'll call it out // as changed. if (!$use_space_policy) { $default_policy = PhabricatorPolicyQuery::getDefaultPolicyForObject( $viewer, $object, $view_capability); if ($default_policy) { if ($default_policy->getPHID() != $policy->getPHID()) { $container_classes[] = 'policy-adjusted'; if ($default_policy->isStrongerThan($policy)) { // The policy has strictly been weakened. For example, the // default might be "All Users" and the current policy is "Public". $container_classes[] = 'policy-adjusted-weaker'; } else if ($policy->isStrongerThan($default_policy)) { // The policy has strictly been strengthened, and is now more // restrictive than the default. For example, "All Users" has // been replaced with "No One". $container_classes[] = 'policy-adjusted-stronger'; } else { // The policy has been adjusted but not strictly strengthened // or weakened. For example, "Members of X" has been replaced with // "Members of Y". $container_classes[] = 'policy-adjusted-different'; } } } } $policy_name = array($policy->getShortName()); $policy_icon = $policy->getIcon().' bluegrey'; if ($object instanceof PhabricatorPolicyCodexInterface) { $codex = PhabricatorPolicyCodex::newFromObject($object, $viewer); $codex_name = $codex->getPolicyShortName($policy, $view_capability); if ($codex_name !== null) { $policy_name = $codex_name; } $codex_icon = $codex->getPolicyIcon($policy, $view_capability); if ($codex_icon !== null) { $policy_icon = $codex_icon; } $codex_classes = $codex->getPolicyTagClasses($policy, $view_capability); foreach ($codex_classes as $codex_class) { $container_classes[] = $codex_class; } } if (!is_array($policy_name)) { $policy_name = (array)$policy_name; } $arrow = id(new PHUIIconView()) ->setIcon('fa-angle-right') ->addClass('policy-tier-separator'); $policy_name = phutil_implode_html($arrow, $policy_name); $icon = id(new PHUIIconView()) ->setIcon($policy_icon); $link = javelin_tag( 'a', array( 'class' => 'policy-link', 'href' => '/policy/explain/'.$phid.'/'.$view_capability.'/', 'sigil' => 'workflow', ), $policy_name); return phutil_tag( 'span', array( 'class' => implode(' ', $container_classes), ), array($icon, $link)); } } diff --git a/src/view/phui/PHUITagView.php b/src/view/phui/PHUITagView.php index e8d30e935e..1811f8f1a9 100644 --- a/src/view/phui/PHUITagView.php +++ b/src/view/phui/PHUITagView.php @@ -1,294 +1,300 @@ type = $type; switch ($type) { case self::TYPE_SHADE: case self::TYPE_OUTLINE: break; case self::TYPE_OBJECT: $this->setBackgroundColor(self::COLOR_OBJECT); break; case self::TYPE_PERSON: $this->setBackgroundColor(self::COLOR_PERSON); break; } return $this; } - /* Deprecated, use setColor */ + /** + * This method has been deprecated, use @{method:setColor} instead. + * + * @deprecated + */ public function setShade($shade) { + phlog( + pht('Deprecated call to setShade(), use setColor() instead.')); $this->color = $shade; return $this; } public function setColor($color) { $this->color = $color; return $this; } public function setDotColor($dot_color) { $this->dotColor = $dot_color; return $this; } public function setBackgroundColor($background_color) { $this->backgroundColor = $background_color; return $this; } public function setPHID($phid) { $this->phid = $phid; return $this; } public function setName($name) { $this->name = $name; return $this; } public function setHref($href) { $this->href = $href; return $this; } public function setClosed($closed) { $this->closed = $closed; return $this; } public function setIcon($icon) { $this->icon = $icon; return $this; } public function setSlimShady($mm) { $this->slimShady = $mm; return $this; } protected function getTagName() { return strlen($this->href) ? 'a' : 'span'; } protected function getTagAttributes() { require_celerity_resource('phui-tag-view-css'); $classes = array( 'phui-tag-view', 'phui-tag-type-'.$this->type, ); if ($this->color) { $classes[] = 'phui-tag-'.$this->color; } if ($this->slimShady) { $classes[] = 'phui-tag-slim'; } if ($this->type == self::TYPE_SHADE) { $classes[] = 'phui-tag-shade'; } if ($this->icon) { $classes[] = 'phui-tag-icon-view'; } if ($this->phid) { Javelin::initBehavior('phui-hovercards'); $attributes = array( 'href' => $this->href, 'sigil' => 'hovercard', 'meta' => array( 'hoverPHID' => $this->phid, ), 'target' => $this->external ? '_blank' : null, ); } else { $attributes = array( 'href' => $this->href, 'target' => $this->external ? '_blank' : null, ); } return $attributes + array('class' => $classes); } protected function getTagContent() { if (!$this->type) { throw new PhutilInvalidStateException('setType', 'render'); } $color = null; if (!$this->shade && $this->backgroundColor) { $color = 'phui-tag-color-'.$this->backgroundColor; } if ($this->dotColor) { $dotcolor = 'phui-tag-color-'.$this->dotColor; $dot = phutil_tag( 'span', array( 'class' => 'phui-tag-dot '.$dotcolor, ), ''); } else { $dot = null; } if ($this->icon) { $icon = id(new PHUIIconView()) ->setIcon($this->icon); } else { $icon = null; } $content = phutil_tag( 'span', array( 'class' => 'phui-tag-core '.$color, ), array($dot, $icon, $this->name)); if ($this->closed) { $content = phutil_tag( 'span', array( 'class' => 'phui-tag-core-closed', ), array($icon, $content)); } return $content; } public static function getTagTypes() { return array( self::TYPE_PERSON, self::TYPE_OBJECT, self::TYPE_STATE, ); } public static function getColors() { return array( self::COLOR_RED, self::COLOR_ORANGE, self::COLOR_YELLOW, self::COLOR_BLUE, self::COLOR_INDIGO, self::COLOR_VIOLET, self::COLOR_GREEN, self::COLOR_BLACK, self::COLOR_GREY, self::COLOR_WHITE, self::COLOR_OBJECT, self::COLOR_PERSON, ); } public static function getShades() { return array_keys(self::getShadeMap()); } public static function getShadeMap() { return array( self::COLOR_RED => pht('Red'), self::COLOR_ORANGE => pht('Orange'), self::COLOR_YELLOW => pht('Yellow'), self::COLOR_BLUE => pht('Blue'), self::COLOR_INDIGO => pht('Indigo'), self::COLOR_VIOLET => pht('Violet'), self::COLOR_GREEN => pht('Green'), self::COLOR_GREY => pht('Grey'), self::COLOR_PINK => pht('Pink'), self::COLOR_CHECKERED => pht('Checkered'), self::COLOR_DISABLED => pht('Disabled'), ); } public static function getShadeName($shade) { return idx(self::getShadeMap(), $shade, $shade); } public static function getOutlines() { return array_keys(self::getOutlineMap()); } public static function getOutlineMap() { return array( self::COLOR_RED => pht('Red'), self::COLOR_ORANGE => pht('Orange'), self::COLOR_YELLOW => pht('Yellow'), self::COLOR_BLUE => pht('Blue'), self::COLOR_INDIGO => pht('Indigo'), self::COLOR_VIOLET => pht('Violet'), self::COLOR_GREEN => pht('Green'), self::COLOR_GREY => pht('Grey'), self::COLOR_PINK => pht('Pink'), self::COLOR_SKY => pht('Sky'), self::COLOR_FIRE => pht('Fire'), self::COLOR_BLACK => pht('Black'), self::COLOR_DISABLED => pht('Disabled'), ); } public static function getOutlineName($outline) { return idx(self::getOutlineMap(), $outline, $outline); } public function setExternal($external) { $this->external = $external; return $this; } public function getExternal() { return $this->external; } }