1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-18 12:52:42 +01:00

Turn thumbs into a history grid thing

Summary:
This could probably use some refinement (and, like, explanatory text, and stronger cues about what rows and columns mean) but feels fairly good to me, at least on test data.

I didn't do any scrolling for now since we have to do full height on mobile anyway I think. I did swap it so the newer ones are on top.

Left/right navigate you among current images only, but you can click any thumb to review history.

Removed history view since it's no longer useful.

Some things that would probably help:

  - Some kind of header explaining what this is ("Mock History" or something).
  - Stronger visual cue that columns are related by being the same image.
  - Clearer cues about obsolete/deleted images (e.g., on the stage itself?)
  - Maybe general tweaks.
  - Maybe a placeholder (like a grey "X") for images which have been deleted.

(I'm planning to add comment counts too, which I think will be pretty useful, but that felt good to put in another diff.)

Test Plan: See screenshots.

Reviewers: chad

Reviewed By: chad

Subscribers: epriestley

Differential Revision: https://secure.phabricator.com/D9543
This commit is contained in:
epriestley 2014-06-14 21:12:19 -07:00
parent 2dfb2cd432
commit 7ce8a1f437
10 changed files with 205 additions and 256 deletions

View file

@ -2410,7 +2410,6 @@ phutil_register_library_map(array(
'PholioController' => 'applications/pholio/controller/PholioController.php', 'PholioController' => 'applications/pholio/controller/PholioController.php',
'PholioDAO' => 'applications/pholio/storage/PholioDAO.php', 'PholioDAO' => 'applications/pholio/storage/PholioDAO.php',
'PholioImage' => 'applications/pholio/storage/PholioImage.php', 'PholioImage' => 'applications/pholio/storage/PholioImage.php',
'PholioImageHistoryController' => 'applications/pholio/controller/PholioImageHistoryController.php',
'PholioImageQuery' => 'applications/pholio/query/PholioImageQuery.php', 'PholioImageQuery' => 'applications/pholio/query/PholioImageQuery.php',
'PholioImageUploadController' => 'applications/pholio/controller/PholioImageUploadController.php', 'PholioImageUploadController' => 'applications/pholio/controller/PholioImageUploadController.php',
'PholioInlineController' => 'applications/pholio/controller/PholioInlineController.php', 'PholioInlineController' => 'applications/pholio/controller/PholioInlineController.php',
@ -2426,6 +2425,7 @@ phutil_register_library_map(array(
'PholioMockMailReceiver' => 'applications/pholio/mail/PholioMockMailReceiver.php', 'PholioMockMailReceiver' => 'applications/pholio/mail/PholioMockMailReceiver.php',
'PholioMockQuery' => 'applications/pholio/query/PholioMockQuery.php', 'PholioMockQuery' => 'applications/pholio/query/PholioMockQuery.php',
'PholioMockSearchEngine' => 'applications/pholio/query/PholioMockSearchEngine.php', 'PholioMockSearchEngine' => 'applications/pholio/query/PholioMockSearchEngine.php',
'PholioMockThumbGridView' => 'applications/pholio/view/PholioMockThumbGridView.php',
'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php', 'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php',
'PholioPHIDTypeImage' => 'applications/pholio/phid/PholioPHIDTypeImage.php', 'PholioPHIDTypeImage' => 'applications/pholio/phid/PholioPHIDTypeImage.php',
'PholioPHIDTypeMock' => 'applications/pholio/phid/PholioPHIDTypeMock.php', 'PholioPHIDTypeMock' => 'applications/pholio/phid/PholioPHIDTypeMock.php',
@ -5312,7 +5312,6 @@ phutil_register_library_map(array(
1 => 'PhabricatorMarkupInterface', 1 => 'PhabricatorMarkupInterface',
2 => 'PhabricatorPolicyInterface', 2 => 'PhabricatorPolicyInterface',
), ),
'PholioImageHistoryController' => 'PholioController',
'PholioImageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PholioImageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PholioImageUploadController' => 'PholioController', 'PholioImageUploadController' => 'PholioController',
'PholioInlineController' => 'PholioController', 'PholioInlineController' => 'PholioController',
@ -5338,6 +5337,7 @@ phutil_register_library_map(array(
'PholioMockMailReceiver' => 'PhabricatorObjectMailReceiver', 'PholioMockMailReceiver' => 'PhabricatorObjectMailReceiver',
'PholioMockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PholioMockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PholioMockSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PholioMockSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PholioMockThumbGridView' => 'AphrontView',
'PholioMockViewController' => 'PholioController', 'PholioMockViewController' => 'PholioController',
'PholioPHIDTypeImage' => 'PhabricatorPHIDType', 'PholioPHIDTypeImage' => 'PhabricatorPHIDType',
'PholioPHIDTypeMock' => 'PhabricatorPHIDType', 'PholioPHIDTypeMock' => 'PhabricatorPHIDType',

View file

@ -65,6 +65,9 @@ final class PhabricatorFileTransformController
case 'thumb-220x165': case 'thumb-220x165':
$xformed_file = $this->executeThumbTransform($file, 220, 165); $xformed_file = $this->executeThumbTransform($file, 220, 165);
break; break;
case 'preview-100':
$xformed_file = $this->executePreviewTransform($file, 100);
break;
case 'preview-140': case 'preview-140':
$xformed_file = $this->executePreviewTransform($file, 140); $xformed_file = $this->executePreviewTransform($file, 140);
break; break;

View file

@ -538,6 +538,12 @@ final class PhabricatorFile extends PhabricatorFileDAO
return PhabricatorEnv::getCDNURI($path); return PhabricatorEnv::getCDNURI($path);
} }
public function getPreview100URI() {
$path = '/file/xform/preview-100/'.$this->getPHID().'/'
.$this->getSecretKey().'/';
return PhabricatorEnv::getCDNURI($path);
}
public function getPreview140URI() { public function getPreview140URI() {
$path = '/file/xform/preview-140/'.$this->getPHID().'/' $path = '/file/xform/preview-140/'.$this->getPHID().'/'
.$this->getSecretKey().'/'; .$this->getSecretKey().'/';

View file

@ -53,7 +53,6 @@ final class PhabricatorApplicationPholio extends PhabricatorApplication {
), ),
'image/' => array( 'image/' => array(
'upload/' => 'PholioImageUploadController', 'upload/' => 'PholioImageUploadController',
'history/(?P<id>\d+)/' => 'PholioImageHistoryController',
), ),
), ),
); );

View file

@ -1,90 +0,0 @@
<?php
/**
* @group pholio
*/
final class PholioImageHistoryController extends PholioController {
private $imageID;
public function willProcessRequest(array $data) {
$this->imageID = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$image = id(new PholioImageQuery())
->setViewer($user)
->withIDs(array($this->imageID))
->executeOne();
if (!$image) {
return new Aphront404Response();
}
// note while we have a mock object, its missing images we need to show
// the history of what's happened here.
// fetch the real deal
$mock = id(new PholioMockQuery())
->setViewer($user)
->needImages(true)
->withIDs(array($image->getMockID()))
->executeOne();
$phids = array($mock->getAuthorPHID());
$this->loadHandles($phids);
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$engine->addObject($mock, PholioMock::MARKUP_FIELD_DESCRIPTION);
$engine->process();
$images = $mock->getImageHistorySet($this->imageID);
$mock->attachImages($images);
$latest_image = last($images);
$title = pht(
'Image history for "%s" from the mock "%s."',
$latest_image->getName(),
$mock->getName());
$header = id(new PHUIHeaderView())
->setHeader($title);
require_celerity_resource('pholio-css');
require_celerity_resource('pholio-inline-comments-css');
$comment_form_id = null;
$output = id(new PholioMockImagesView())
->setRequestURI($request->getRequestURI())
->setCommentFormID($comment_form_id)
->setUser($user)
->setMock($mock)
->setImageID($this->imageID)
->setViewMode('history');
$crumbs = $this->buildApplicationCrumbs();
$crumbs
->addTextCrumb('M'.$mock->getID(), '/M'.$mock->getID())
->addTextCrumb('Image History', $request->getRequestURI());
$content = array(
$crumbs,
$header,
$output->render(),
);
return $this->buildApplicationPage(
$content,
array(
'title' => 'M'.$mock->getID().' '.$title,
'device' => true,
'pageObjects' => array($mock->getPHID()),
));
}
}

View file

@ -1,8 +1,5 @@
<?php <?php
/**
* @group pholio
*/
final class PholioMockViewController extends PholioController { final class PholioMockViewController extends PholioController {
private $id; private $id;
@ -89,8 +86,6 @@ final class PholioMockViewController extends PholioController {
require_celerity_resource('pholio-css'); require_celerity_resource('pholio-css');
require_celerity_resource('pholio-inline-comments-css'); require_celerity_resource('pholio-inline-comments-css');
$image_status = $this->getImageStatus($mock, $this->imageID);
$comment_form_id = celerity_generate_unique_node_id(); $comment_form_id = celerity_generate_unique_node_id();
$output = id(new PholioMockImagesView()) $output = id(new PholioMockImagesView())
->setRequestURI($request->getRequestURI()) ->setRequestURI($request->getRequestURI())
@ -115,11 +110,16 @@ final class PholioMockViewController extends PholioController {
->setHeader($header) ->setHeader($header)
->addPropertyList($properties); ->addPropertyList($properties);
$thumb_grid = id(new PholioMockThumbGridView())
->setUser($user)
->setMock($mock);
$content = array( $content = array(
$crumbs, $crumbs,
$image_status,
$object_box, $object_box,
$output->render(), $output,
phutil_tag('br'),
$thumb_grid,
$xaction_view, $xaction_view,
$add_comment, $add_comment,
); );
@ -133,43 +133,6 @@ final class PholioMockViewController extends PholioController {
)); ));
} }
private function getImageStatus(PholioMock $mock, $image_id) {
$status = null;
$images = $mock->getImages();
foreach ($images as $image) {
if ($image->getID() == $image_id) {
return $status;
}
}
$images = $mock->getAllImages();
$images = mpull($images, null, 'getID');
$image = idx($images, $image_id);
if ($image) {
$history = $mock->getImageHistorySet($image_id);
$latest_image = last($history);
$href = $this->getApplicationURI(
'image/history/'.$latest_image->getID().'/');
$status = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle(pht('The requested image is obsolete.'))
->appendChild(phutil_tag(
'p',
array(),
array(
pht('You are viewing this mock with the latest image set.'),
' ',
phutil_tag(
'a',
array('href' => $href),
pht(
'Click here to see the history of the now obsolete image.')))));
}
return $status;
}
private function buildActionView(PholioMock $mock) { private function buildActionView(PholioMock $mock) {
$user = $this->getRequest()->getUser(); $user = $this->getRequest()->getUser();

View file

@ -9,25 +9,12 @@ final class PholioMockImagesView extends AphrontView {
private $imageID; private $imageID;
private $requestURI; private $requestURI;
private $commentFormID; private $commentFormID;
private $viewMode = 'normal';
/**
* Supports normal (/MX, /MX/Y/) and history (/pholio/image/history/Y/)
* modes. The former has handy dandy commenting functionality and the
* latter does not.
*/
public function setViewMode($view_mode) {
$this->viewMode = $view_mode;
return $this;
}
public function getViewMode() {
return $this->viewMode;
}
public function setCommentFormID($comment_form_id) { public function setCommentFormID($comment_form_id) {
$this->commentFormID = $comment_form_id; $this->commentFormID = $comment_form_id;
return $this; return $this;
} }
public function getCommentFormID() { public function getCommentFormID() {
return $this->commentFormID; return $this->commentFormID;
} }
@ -36,6 +23,7 @@ final class PholioMockImagesView extends AphrontView {
$this->requestURI = $request_uri; $this->requestURI = $request_uri;
return $this; return $this;
} }
public function getRequestURI() { public function getRequestURI() {
return $this->requestURI; return $this->requestURI;
} }
@ -74,7 +62,7 @@ final class PholioMockImagesView extends AphrontView {
$selected_id = head_key($ids); $selected_id = head_key($ids);
} }
foreach ($mock->getImages() as $image) { foreach ($mock->getAllImages() as $image) {
$file = $image->getFile(); $file = $image->getFile();
$metadata = $file->getMetadata(); $metadata = $file->getMetadata();
$x = idx($metadata, PhabricatorFile::METADATA_IMAGE_WIDTH); $x = idx($metadata, PhabricatorFile::METADATA_IMAGE_WIDTH);
@ -90,10 +78,15 @@ final class PholioMockImagesView extends AphrontView {
'height' => $y, 'height' => $y,
'title' => $image->getName(), 'title' => $image->getName(),
'desc' => $image->getDescription(), 'desc' => $image->getDescription(),
'isObsolete' => (bool) $image->getIsObsolete(), 'isObsolete' => (bool)$image->getIsObsolete(),
); );
} }
$navsequence = array();
foreach ($mock->getImages() as $image) {
$navsequence[] = $image->getID();
}
$login_uri = id(new PhutilURI('/login/')) $login_uri = id(new PhutilURI('/login/'))
->setQueryParam('next', (string) $this->getRequestURI()); ->setQueryParam('next', (string) $this->getRequestURI());
$config = array( $config = array(
@ -105,7 +98,7 @@ final class PholioMockImagesView extends AphrontView {
'selectedID' => $selected_id, 'selectedID' => $selected_id,
'loggedIn' => $this->getUser()->isLoggedIn(), 'loggedIn' => $this->getUser()->isLoggedIn(),
'logInLink' => (string) $login_uri, 'logInLink' => (string) $login_uri,
'viewMode' => $this->getViewMode() 'navsequence' => $navsequence,
); );
Javelin::initBehavior('pholio-mock-view', $config); Javelin::initBehavior('pholio-mock-view', $config);
@ -149,65 +142,19 @@ final class PholioMockImagesView extends AphrontView {
), ),
''); '');
$carousel_holder = '';
if (count($mock->getImages()) > 0) {
$thumbnails = array();
foreach ($mock->getImages() as $image) {
$thumbfile = $image->getFile();
$dimensions = PhabricatorImageTransformer::getPreviewDimensions(
$thumbfile,
140);
$tag = phutil_tag(
'img',
array(
'width' => $dimensions['sdx'],
'height' => $dimensions['sdy'],
'src' => $thumbfile->getPreview140URI(),
'class' => 'pholio-mock-carousel-thumbnail',
'style' => 'top: '.floor((140 - $dimensions['sdy'] ) / 2).'px',
));
$thumbnails[] = javelin_tag(
'a',
array(
'sigil' => 'mock-thumbnail',
'class' => 'pholio-mock-carousel-thumb-item',
'href' => $this->getImagePageURI($image, $mock),
'meta' => array(
'imageID' => $image->getID(),
),
),
$tag);
}
$carousel_holder = phutil_tag(
'div',
array(
'id' => 'pholio-mock-carousel',
'class' => 'pholio-mock-carousel',
),
$thumbnails);
}
$mockview[] = phutil_tag( $mockview[] = phutil_tag(
'div', 'div',
array( array(
'class' => 'pholio-mock-image-container', 'class' => 'pholio-mock-image-container',
'id' => 'pholio-mock-image-container' 'id' => 'pholio-mock-image-container'
), ),
array($mock_wrapper, $inline_comments_holder, $carousel_holder)); array($mock_wrapper, $inline_comments_holder));
return $mockview; return $mockview;
} }
private function getImagePageURI(PholioImage $image, PholioMock $mock) { private function getImagePageURI(PholioImage $image, PholioMock $mock) {
if ($this->getViewMode() == 'normal') { $uri = '/M'.$mock->getID().'/'.$image->getID().'/';
$uri = '/M'.$mock->getID().'/'.$image->getID().'/';
} else {
$uri = '/pholio/image/history/'.$image->getID().'/';
}
return $uri; return $uri;
} }
} }

View file

@ -0,0 +1,135 @@
<?php
final class PholioMockThumbGridView extends AphrontView {
private $mock;
public function setMock(PholioMock $mock) {
$this->mock = $mock;
return $this;
}
public function render() {
$mock = $this->mock;
$all_images = $mock->getAllImages();
$all_images = mpull($all_images, null, 'getPHID');
$history = mpull($all_images, 'getReplacesImagePHID', 'getPHID');
$replaced = array();
foreach ($history as $phid => $replaces_phid) {
if ($replaces_phid) {
$replaced[$replaces_phid] = true;
}
}
// Figure out the columns. Start with all the active images.
$images = mpull($mock->getImages(), null, 'getPHID');
// Now, find deleted images: obsolete images which were not replaced.
foreach ($mock->getAllImages() as $image) {
if (!$image->getIsObsolete()) {
// Image is current.
continue;
}
if (isset($replaced[$image->getPHID()])) {
// Image was replaced.
continue;
}
// This is an obsolete image which was not replaced, so it must be
// a deleted image.
$images[$image->getPHID()] = $image;
}
$cols = array();
$depth = 0;
foreach ($images as $image) {
$phid = $image->getPHID();
$col = array();
// If this is a deleted image, null out the final column.
if ($image->getIsObsolete()) {
$col[] = null;
}
$col[] = $phid;
while ($phid && isset($history[$phid])) {
$col[] = $history[$phid];
$phid = $history[$phid];
}
$cols[] = $col;
$depth = max($depth, count($col));
}
$grid = array();
for ($ii = 0; $ii < $depth; $ii++) {
$row = array();
foreach ($cols as $col) {
if (empty($col[$ii])) {
$row[] = phutil_tag('td', array(), null);
} else {
$thumb = $this->renderThumbnail($all_images[$col[$ii]]);
$row[] = phutil_tag('td', array(), $thumb);
}
}
$grid[] = phutil_tag('tr', array(), $row);
}
$grid = phutil_tag(
'table',
array(
'id' => 'pholio-mock-thumb-grid',
'class' => 'pholio-mock-thumb-grid',
),
$grid);
return phutil_tag(
'div',
array(
'class' => 'pholio-mock-thumb-grid-container',
),
$grid);
}
private function renderThumbnail(PholioImage $image) {
$thumbfile = $image->getFile();
$dimensions = PhabricatorImageTransformer::getPreviewDimensions(
$thumbfile,
100);
$tag = phutil_tag(
'img',
array(
'width' => $dimensions['sdx'],
'height' => $dimensions['sdy'],
'src' => $thumbfile->getPreview100URI(),
'class' => 'pholio-mock-thumb-grid-image',
'style' => 'top: '.floor((100 - $dimensions['sdy'] ) / 2).'px',
));
$classes = array('pholio-mock-thumb-grid-item');
if ($image->getIsObsolete()) {
$classes[] = 'pholio-mock-thumb-grid-item-obsolete';
}
return javelin_tag(
'a',
array(
'sigil' => 'mock-thumbnail',
'class' => implode(' ', $classes),
'href' => '#',
'meta' => array(
'imageID' => $image->getID(),
),
),
$tag);
}
}

View file

@ -10,50 +10,41 @@
background: url('/rsrc/image/texture/pholio-background.gif'); background: url('/rsrc/image/texture/pholio-background.gif');
} }
.pholio-mock-carousel { .pholio-mock-thumb-grid-container {
background-color: #282828; background-color: #282828;
text-align: center; padding: 12px;
overflow-x: auto;
overflow-y: hidden;
} }
.pholio-mock-carousel-thumb-item { .pholio-mock-thumb-grid {
margin: 0 auto;
}
.pholio-mock-thumb-grid-item {
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
width: 140px; width: 100px;
height: 140px; height: 100px;
padding: 5px; padding: 5px;
margin: 3px; margin: 3px;
background: #181818; background: #181818;
vertical-align: middle; vertical-align: middle;
border: 1px solid #383838; border: 1px solid {$greyborder};
position: relative; position: relative;
} }
.device-desktop .pholio-mock-carousel-thumb-item:hover, .device-desktop .pholio-mock-thumb-grid-item:hover,
.pholio-mock-carousel-thumb-current { .pholio-mock-thumb-grid-current {
background: #383838; background: #383838;
border-color: #686868; border-color: {$sky};
} }
.device .pholio-mock-carousel-thumb-item { .pholio-mock-thumb-grid-item-obsolete {
width: 5px; opacity: 0.5;
height: 5px;
padding: 0px;
border-radius: 5px;
margin: 5px 2px;
background: #383838;
border-color: #686868;
} }
.device .pholio-mock-carousel-thumb-current { .pholio-mock-thumb-grid-image {
background: #dfdfdf;
border-color: #ffffff;
}
.device .pholio-mock-carousel-thumb-item img {
display: none;
}
.pholio-mock-carousel-thumbnail {
margin: auto; margin: auto;
position: relative; position: relative;
} }
@ -78,10 +69,6 @@
opacity: 0.50; opacity: 0.50;
} }
.pholio-image-info {
border-bottom: 1px solid #101010;
margin-bottom: 5px;
}
.pholio-image-info-item { .pholio-image-info-item {
padding: 0 8px; padding: 0 8px;

View file

@ -110,6 +110,15 @@ JX.behavior('pholio-mock-view', function(config) {
return null; return null;
} }
function get_image_navindex(id) {
for (var ii = 0; ii < config.navsequence.length; ii++) {
if (config.navsequence[ii] == id) {
return ii;
}
}
return null;
}
function get_image(id) { function get_image(id) {
var idx = get_image_index(id); var idx = get_image_index(id);
if (idx === null) { if (idx === null) {
@ -133,9 +142,12 @@ JX.behavior('pholio-mock-view', function(config) {
if (!active_image) { if (!active_image) {
return; return;
} }
var idx = get_image_index(active_image.id); var idx = get_image_navindex(active_image.id);
idx = (idx + delta + config.images.length) % config.images.length; if (idx === null) {
select_image(config.images[idx].id); return;
}
idx = (idx + delta + config.navsequence.length) % config.navsequence.length;
select_image(config.navsequence[idx]);
} }
function redraw_image() { function redraw_image() {
@ -197,7 +209,7 @@ JX.behavior('pholio-mock-view', function(config) {
img.src = active_image.fullURI; img.src = active_image.fullURI;
var thumbs = JX.DOM.scry( var thumbs = JX.DOM.scry(
JX.$('pholio-mock-carousel'), JX.$('pholio-mock-thumb-grid'),
'a', 'a',
'mock-thumbnail'); 'mock-thumbnail');
@ -206,7 +218,7 @@ JX.behavior('pholio-mock-view', function(config) {
JX.DOM.alterClass( JX.DOM.alterClass(
thumbs[k], thumbs[k],
'pholio-mock-carousel-thumb-current', 'pholio-mock-thumb-grid-current',
(active_image.id == thumb_meta.imageID)); (active_image.id == thumb_meta.imageID));
} }
@ -217,7 +229,7 @@ JX.behavior('pholio-mock-view', function(config) {
} }
JX.Stratcom.listen( JX.Stratcom.listen(
['mousedown', 'click'], 'click',
'mock-thumbnail', 'mock-thumbnail',
function(e) { function(e) {
if (!e.isNormalMouseEvent()) { if (!e.isNormalMouseEvent()) {
@ -238,10 +250,6 @@ JX.behavior('pholio-mock-view', function(config) {
return; return;
} }
if (config.viewMode == 'history') {
return;
}
if (JX.Stratcom.pass()) { if (JX.Stratcom.pass()) {
return; return;
} }
@ -602,15 +610,6 @@ JX.behavior('pholio-mock-view', function(config) {
'View Full Image'); 'View Full Image');
info.push(full_link); info.push(full_link);
if (config.viewMode != 'history') {
var history_link = JX.$N(
'a',
{ href: image.historyURI },
'View Image History');
info.push(history_link);
}
for (var ii = 0; ii < info.length; ii++) { for (var ii = 0; ii < info.length; ii++) {
info[ii] = JX.$N('div', {className: 'pholio-image-info-item'}, info[ii]); info[ii] = JX.$N('div', {className: 'pholio-image-info-item'}, info[ii]);
} }