mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-24 14:30:56 +01:00
AphrontBars
Summary: Aphront widgets that render the either a discrete or continuous value as a horizontal shape. Like a progress bar, or a five-star rating bar. Test Plan: `/uiexample/view/PhabricatorAphrontBarExample/` ...which shows this, amongst other things: {F33898} Reviewers: epriestley, chad Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T2094 Differential Revision: https://secure.phabricator.com/D5122
This commit is contained in:
parent
fd13275f7e
commit
c2c8c34e84
7 changed files with 417 additions and 0 deletions
|
@ -604,6 +604,15 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/css/aphront/attached-file-view.css',
|
||||
),
|
||||
'aphront-bars' =>
|
||||
array(
|
||||
'uri' => '/res/d7bd9032/rsrc/css/core/aphront-bars.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
),
|
||||
'disk' => '/rsrc/css/core/aphront-bars.css',
|
||||
),
|
||||
'aphront-calendar-view-css' =>
|
||||
array(
|
||||
'uri' => '/res/73061a31/rsrc/css/aphront/calendar-view.css',
|
||||
|
|
|
@ -17,6 +17,7 @@ phutil_register_library_map(array(
|
|||
'AphrontAjaxResponse' => 'aphront/response/AphrontAjaxResponse.php',
|
||||
'AphrontApplicationConfiguration' => 'aphront/configuration/AphrontApplicationConfiguration.php',
|
||||
'AphrontAttachedFileView' => 'view/control/AphrontAttachedFileView.php',
|
||||
'AphrontBarView' => 'view/widget/bars/AphrontBarView.php',
|
||||
'AphrontCSRFException' => 'aphront/exception/AphrontCSRFException.php',
|
||||
'AphrontCalendarEventView' => 'applications/calendar/view/AphrontCalendarEventView.php',
|
||||
'AphrontCalendarMonthView' => 'applications/calendar/view/AphrontCalendarMonthView.php',
|
||||
|
@ -53,6 +54,7 @@ phutil_register_library_map(array(
|
|||
'AphrontFormToggleButtonsControl' => 'view/form/control/AphrontFormToggleButtonsControl.php',
|
||||
'AphrontFormTokenizerControl' => 'view/form/control/AphrontFormTokenizerControl.php',
|
||||
'AphrontFormView' => 'view/form/AphrontFormView.php',
|
||||
'AphrontGlyphBarView' => 'view/widget/bars/AphrontGlyphBarView.php',
|
||||
'AphrontHTMLResponse' => 'aphront/response/AphrontHTMLResponse.php',
|
||||
'AphrontHTTPSink' => 'aphront/sink/AphrontHTTPSink.php',
|
||||
'AphrontHTTPSinkTestCase' => 'aphront/sink/__tests__/AphrontHTTPSinkTestCase.php',
|
||||
|
@ -71,6 +73,7 @@ phutil_register_library_map(array(
|
|||
'AphrontPagerView' => 'view/control/AphrontPagerView.php',
|
||||
'AphrontPanelView' => 'view/layout/AphrontPanelView.php',
|
||||
'AphrontPlainTextResponse' => 'aphront/response/AphrontPlainTextResponse.php',
|
||||
'AphrontProgressBarView' => 'view/widget/bars/AphrontProgressBarView.php',
|
||||
'AphrontProxyResponse' => 'aphront/response/AphrontProxyResponse.php',
|
||||
'AphrontRedirectException' => 'aphront/exception/AphrontRedirectException.php',
|
||||
'AphrontRedirectResponse' => 'aphront/response/AphrontRedirectResponse.php',
|
||||
|
@ -638,6 +641,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php',
|
||||
'PhabricatorAllCapsTranslation' => 'infrastructure/internationalization/PhabricatorAllCapsTranslation.php',
|
||||
'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php',
|
||||
'PhabricatorAphrontBarExample' => 'applications/uiexample/examples/PhabricatorAphrontBarExample.php',
|
||||
'PhabricatorApplication' => 'applications/base/PhabricatorApplication.php',
|
||||
'PhabricatorApplicationApplications' => 'applications/meta/application/PhabricatorApplicationApplications.php',
|
||||
'PhabricatorApplicationAudit' => 'applications/audit/application/PhabricatorApplicationAudit.php',
|
||||
|
@ -1570,6 +1574,7 @@ phutil_register_library_map(array(
|
|||
'Aphront404Response' => 'AphrontHTMLResponse',
|
||||
'AphrontAjaxResponse' => 'AphrontResponse',
|
||||
'AphrontAttachedFileView' => 'AphrontView',
|
||||
'AphrontBarView' => 'AphrontView',
|
||||
'AphrontCSRFException' => 'AphrontException',
|
||||
'AphrontCalendarEventView' => 'AphrontView',
|
||||
'AphrontCalendarMonthView' => 'AphrontView',
|
||||
|
@ -1606,6 +1611,7 @@ phutil_register_library_map(array(
|
|||
'AphrontFormToggleButtonsControl' => 'AphrontFormControl',
|
||||
'AphrontFormTokenizerControl' => 'AphrontFormControl',
|
||||
'AphrontFormView' => 'AphrontView',
|
||||
'AphrontGlyphBarView' => 'AphrontBarView',
|
||||
'AphrontHTMLResponse' => 'AphrontResponse',
|
||||
'AphrontHTTPSinkTestCase' => 'PhabricatorTestCase',
|
||||
'AphrontIsolatedDatabaseConnectionTestCase' => 'PhabricatorTestCase',
|
||||
|
@ -1623,6 +1629,7 @@ phutil_register_library_map(array(
|
|||
'AphrontPagerView' => 'AphrontView',
|
||||
'AphrontPanelView' => 'AphrontView',
|
||||
'AphrontPlainTextResponse' => 'AphrontResponse',
|
||||
'AphrontProgressBarView' => 'AphrontBarView',
|
||||
'AphrontProxyResponse' => 'AphrontResponse',
|
||||
'AphrontRedirectException' => 'AphrontException',
|
||||
'AphrontRedirectResponse' => 'AphrontResponse',
|
||||
|
@ -2134,6 +2141,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorActionView' => 'AphrontView',
|
||||
'PhabricatorAllCapsTranslation' => 'PhabricatorTranslation',
|
||||
'PhabricatorAnchorView' => 'AphrontView',
|
||||
'PhabricatorAphrontBarExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorApplicationApplications' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationAudit' => 'PhabricatorApplication',
|
||||
'PhabricatorApplicationAuth' => 'PhabricatorApplication',
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorAphrontBarExample extends PhabricatorUIExample {
|
||||
|
||||
public function getName() {
|
||||
return "Bars";
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
return 'Like fractions, but more horizontal.';
|
||||
}
|
||||
|
||||
public function renderExample() {
|
||||
$out = '';
|
||||
$out .= $this->renderTestThings('AphrontProgressBarView', 13, 10);
|
||||
$out .= $this->renderTestThings('AphrontGlyphBarView', 13, 10);
|
||||
$out .= $this->renderWeirdOrderGlyphBars();
|
||||
$out .= $this->renderAsciiStarBar();
|
||||
return phutil_safe_html($out);
|
||||
}
|
||||
|
||||
private function wrap($title, $thing) {
|
||||
return id(new AphrontPanelView())
|
||||
->setHeader($title)
|
||||
->appendChild($thing)
|
||||
->render();
|
||||
}
|
||||
|
||||
private function renderTestThings($class, $max, $incr) {
|
||||
$bars = array();
|
||||
for ($ii = 0; $ii <= $max; $ii++) {
|
||||
$bars[] = newv($class, array())
|
||||
->setValue($ii * $incr)
|
||||
->setMax($max * $incr)
|
||||
->setCaption("{$ii} outta {$max} ain't bad!");
|
||||
}
|
||||
return $this->wrap(
|
||||
"Test {$class}",
|
||||
phutil_implode_html('', mpull($bars, 'render')));
|
||||
}
|
||||
|
||||
private function renderWeirdOrderGlyphBars() {
|
||||
$views = array();
|
||||
$indices = array(1, 3, 7, 4, 2, 8, 9, 5, 10, 6);
|
||||
$max = count($indices);
|
||||
foreach ($indices as $index) {
|
||||
$views[] = id(new AphrontGlyphBarView())
|
||||
->setValue($index)
|
||||
->setMax($max)
|
||||
->setNumGlyphs(5)
|
||||
->setCaption("Lol score is {$index}/{$max}")
|
||||
->setGlyph(hsprintf('%s', 'LOL!'))
|
||||
->setBackgroundGlyph(hsprintf('%s', '____'))
|
||||
->render();
|
||||
$views[] = hsprintf('<div style="clear:both;"></div>');
|
||||
}
|
||||
|
||||
return $this->wrap(
|
||||
"Glyph bars in weird order",
|
||||
phutil_implode_html('', $views));
|
||||
}
|
||||
|
||||
private function renderAsciiStarBar() {
|
||||
return $this->wrap(
|
||||
"Ascii star glyph bar",
|
||||
id(new AphrontGlyphBarView())
|
||||
->setValue(50)
|
||||
->setMax(100)
|
||||
->setCaption('Glyphs!')
|
||||
->setNumGlyphs(10)
|
||||
->setGlyph(hsprintf('%s', '*'))
|
||||
->render());
|
||||
}
|
||||
|
||||
}
|
63
src/view/widget/bars/AphrontBarView.php
Normal file
63
src/view/widget/bars/AphrontBarView.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
abstract class AphrontBarView extends AphrontView {
|
||||
|
||||
private $color;
|
||||
private $caption = '';
|
||||
|
||||
const COLOR_DEFAULT = 'default';
|
||||
const COLOR_WARNING = 'warning';
|
||||
const COLOR_DANGER = 'danger';
|
||||
|
||||
const COLOR_AUTO_BADNESS = 'auto_badness'; // more = bad! :(
|
||||
const COLOR_AUTO_GOODNESS = 'auto_goodness'; // more = good! :)
|
||||
|
||||
const THRESHOLD_DANGER = 0.85;
|
||||
const THRESHOLD_WARNING = 0.75;
|
||||
|
||||
abstract protected function getRatio();
|
||||
|
||||
abstract protected function getDefaultColor();
|
||||
|
||||
final public function setColor($color) {
|
||||
$this->color = $color;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function setCaption($text) {
|
||||
$this->caption = $text;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final protected function getColor() {
|
||||
$color = $this->color;
|
||||
if (!$color) {
|
||||
$color = $this->getDefaultColor();
|
||||
}
|
||||
|
||||
switch ($color) {
|
||||
case self::COLOR_DEFAULT:
|
||||
case self::COLOR_WARNING:
|
||||
case self::COLOR_DANGER:
|
||||
return $color;
|
||||
}
|
||||
|
||||
$ratio = $this->getRatio();
|
||||
if ($color === self::COLOR_AUTO_GOODNESS) {
|
||||
$ratio = 1.0 - $ratio;
|
||||
}
|
||||
|
||||
if ($ratio >= self::THRESHOLD_DANGER) {
|
||||
return self::COLOR_DANGER;
|
||||
} else if ($ratio >= self::THRESHOLD_WARNING) {
|
||||
return self::COLOR_WARNING;
|
||||
} else {
|
||||
return self::COLOR_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
final protected function getCaption() {
|
||||
return $this->caption;
|
||||
}
|
||||
|
||||
}
|
102
src/view/widget/bars/AphrontGlyphBarView.php
Normal file
102
src/view/widget/bars/AphrontGlyphBarView.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
final class AphrontGlyphBarView extends AphrontBarView {
|
||||
|
||||
const BLACK_STAR = "\xE2\x98\x85";
|
||||
const WHITE_STAR = "\xE2\x98\x86";
|
||||
|
||||
private $value;
|
||||
private $max = 100;
|
||||
private $numGlyphs = 5;
|
||||
private $fgGlyph;
|
||||
private $bgGlyph;
|
||||
|
||||
public function getDefaultColor() {
|
||||
return AphrontBarView::COLOR_AUTO_GOODNESS;
|
||||
}
|
||||
|
||||
public function setValue($value) {
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMax($max) {
|
||||
$this->max = $max;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setNumGlyphs($nn) {
|
||||
$this->numGlyphs = $nn;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setGlyph(PhutilSafeHTML $fg_glyph) {
|
||||
$this->fgGlyph = $fg_glyph;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setBackgroundGlyph(PhutilSafeHTML $bg_glyph) {
|
||||
$this->bgGlyph = $bg_glyph;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getRatio() {
|
||||
return min($this->value, $this->max) / $this->max;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
require_celerity_resource('aphront-bars');
|
||||
$ratio = $this->getRatio();
|
||||
$percentage = 100 * $ratio;
|
||||
|
||||
$is_star = false;
|
||||
if ($this->fgGlyph) {
|
||||
$fg_glyph = $this->fgGlyph;
|
||||
if ($this->bgGlyph) {
|
||||
$bg_glyph = $this->bgGlyph;
|
||||
} else {
|
||||
$bg_glyph = $fg_glyph;
|
||||
}
|
||||
} else {
|
||||
$is_star = true;
|
||||
$fg_glyph = self::BLACK_STAR;
|
||||
$bg_glyph = self::WHITE_STAR;
|
||||
}
|
||||
|
||||
$fg_glyphs = array_fill(0, $this->numGlyphs, $fg_glyph);
|
||||
$bg_glyphs = array_fill(0, $this->numGlyphs, $bg_glyph);
|
||||
|
||||
$color = $this->getColor();
|
||||
|
||||
return phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => "aphront-bar glyph color-{$color}",
|
||||
),
|
||||
array(
|
||||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'glyphs'.($is_star ? ' starstar' : ''),
|
||||
),
|
||||
array(
|
||||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'fg',
|
||||
'style' => "width: {$percentage}%;",
|
||||
),
|
||||
$fg_glyphs),
|
||||
phutil_tag(
|
||||
'div',
|
||||
array(),
|
||||
$bg_glyphs)
|
||||
)),
|
||||
phutil_tag(
|
||||
'div',
|
||||
array('class' => 'caption'),
|
||||
$this->getCaption())
|
||||
));
|
||||
}
|
||||
|
||||
}
|
60
src/view/widget/bars/AphrontProgressBarView.php
Normal file
60
src/view/widget/bars/AphrontProgressBarView.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
final class AphrontProgressBarView extends AphrontBarView {
|
||||
|
||||
const WIDTH = 100;
|
||||
|
||||
private $value;
|
||||
private $max = 100;
|
||||
private $alt = '';
|
||||
|
||||
public function getDefaultColor() {
|
||||
return AphrontBarView::COLOR_AUTO_BADNESS;
|
||||
}
|
||||
|
||||
public function setValue($value) {
|
||||
$this->value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMax($max) {
|
||||
$this->max = $max;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setAlt($text) {
|
||||
$this->alt = $text;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getRatio() {
|
||||
return min($this->value, $this->max) / $this->max;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
require_celerity_resource('aphront-bars');
|
||||
$ratio = $this->getRatio();
|
||||
$width = self::WIDTH * $ratio;
|
||||
|
||||
$color = $this->getColor();
|
||||
|
||||
return phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => "aphront-bar progress color-{$color}",
|
||||
),
|
||||
array(
|
||||
phutil_tag(
|
||||
'div',
|
||||
array('title' => $this->alt),
|
||||
phutil_tag(
|
||||
'div',
|
||||
array('style' => hsprintf("width: %dpx;", $width)),
|
||||
'')),
|
||||
phutil_tag(
|
||||
'span',
|
||||
array(),
|
||||
$this->getCaption())));
|
||||
}
|
||||
|
||||
}
|
100
webroot/rsrc/css/core/aphront-bars.css
Normal file
100
webroot/rsrc/css/core/aphront-bars.css
Normal file
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* @provides aphront-bars
|
||||
*/
|
||||
|
||||
/**
|
||||
* Progress bars
|
||||
*/
|
||||
|
||||
div.aphront-bar.progress {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
div.aphront-bar.progress div {
|
||||
width: 100px;
|
||||
border: 1px solid;
|
||||
border-top-color: #A4A4A4;
|
||||
border-right-color: #BBB;
|
||||
border-bottom-color: #D5D5D5;
|
||||
border-left-color: #BBB;
|
||||
background: white;
|
||||
float: left;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
div.aphront-bar.progress div div {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
div.aphront-bar.progress span {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Glyph bars
|
||||
*/
|
||||
|
||||
div.aphront-bar.glyph {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
div.aphront-bar.glyph div.glyphs {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
float: left;
|
||||
position: relative;
|
||||
color: #aaa;
|
||||
font-family: monospace;
|
||||
height: 1.3em;
|
||||
}
|
||||
|
||||
div.aphront-bar.glyph div.glyphs.starstar {
|
||||
/* http://daringfireball.net/2005/08/star_star */
|
||||
font-family: "Hiragino Kaku Gothic Pro", "Osaka", "Zapf Dingbats";
|
||||
}
|
||||
|
||||
div.aphront-bar.glyph div.glyphs div.fg {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
float: left;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.aphront-bar.glyph div.caption {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
float: left;
|
||||
margin-left: 1.5em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Common color classes
|
||||
*/
|
||||
div.aphront-bar.progress.color-default div div {
|
||||
background: #6D84B4;
|
||||
}
|
||||
|
||||
div.aphront-bar.glyph.color-default div.glyphs div.fg {
|
||||
color: #6D84B4;
|
||||
}
|
||||
|
||||
div.aphront-bar.progress.color-warning div div {
|
||||
background: #FFCC5F;
|
||||
}
|
||||
|
||||
div.aphront-bar.glyph.color-warning div.glyphs div.fg {
|
||||
color: #FFCC5F;
|
||||
}
|
||||
|
||||
div.aphront-bar.progress.color-danger div div {
|
||||
background: #FF6E6E;
|
||||
}
|
||||
|
||||
div.aphront-bar.glyph.color-danger div.glyphs div.fg {
|
||||
color: #FF6E6E;
|
||||
}
|
Loading…
Reference in a new issue