@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+205 -256
+2 -2
src/__phutil_library_map__.php
··· 2410 2410 'PholioController' => 'applications/pholio/controller/PholioController.php', 2411 2411 'PholioDAO' => 'applications/pholio/storage/PholioDAO.php', 2412 2412 'PholioImage' => 'applications/pholio/storage/PholioImage.php', 2413 - 'PholioImageHistoryController' => 'applications/pholio/controller/PholioImageHistoryController.php', 2414 2413 'PholioImageQuery' => 'applications/pholio/query/PholioImageQuery.php', 2415 2414 'PholioImageUploadController' => 'applications/pholio/controller/PholioImageUploadController.php', 2416 2415 'PholioInlineController' => 'applications/pholio/controller/PholioInlineController.php', ··· 2426 2425 'PholioMockMailReceiver' => 'applications/pholio/mail/PholioMockMailReceiver.php', 2427 2426 'PholioMockQuery' => 'applications/pholio/query/PholioMockQuery.php', 2428 2427 'PholioMockSearchEngine' => 'applications/pholio/query/PholioMockSearchEngine.php', 2428 + 'PholioMockThumbGridView' => 'applications/pholio/view/PholioMockThumbGridView.php', 2429 2429 'PholioMockViewController' => 'applications/pholio/controller/PholioMockViewController.php', 2430 2430 'PholioPHIDTypeImage' => 'applications/pholio/phid/PholioPHIDTypeImage.php', 2431 2431 'PholioPHIDTypeMock' => 'applications/pholio/phid/PholioPHIDTypeMock.php', ··· 5312 5312 1 => 'PhabricatorMarkupInterface', 5313 5313 2 => 'PhabricatorPolicyInterface', 5314 5314 ), 5315 - 'PholioImageHistoryController' => 'PholioController', 5316 5315 'PholioImageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5317 5316 'PholioImageUploadController' => 'PholioController', 5318 5317 'PholioInlineController' => 'PholioController', ··· 5338 5337 'PholioMockMailReceiver' => 'PhabricatorObjectMailReceiver', 5339 5338 'PholioMockQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5340 5339 'PholioMockSearchEngine' => 'PhabricatorApplicationSearchEngine', 5340 + 'PholioMockThumbGridView' => 'AphrontView', 5341 5341 'PholioMockViewController' => 'PholioController', 5342 5342 'PholioPHIDTypeImage' => 'PhabricatorPHIDType', 5343 5343 'PholioPHIDTypeMock' => 'PhabricatorPHIDType',
+3
src/applications/files/controller/PhabricatorFileTransformController.php
··· 65 65 case 'thumb-220x165': 66 66 $xformed_file = $this->executeThumbTransform($file, 220, 165); 67 67 break; 68 + case 'preview-100': 69 + $xformed_file = $this->executePreviewTransform($file, 100); 70 + break; 68 71 case 'preview-140': 69 72 $xformed_file = $this->executePreviewTransform($file, 140); 70 73 break;
+6
src/applications/files/storage/PhabricatorFile.php
··· 538 538 return PhabricatorEnv::getCDNURI($path); 539 539 } 540 540 541 + public function getPreview100URI() { 542 + $path = '/file/xform/preview-100/'.$this->getPHID().'/' 543 + .$this->getSecretKey().'/'; 544 + return PhabricatorEnv::getCDNURI($path); 545 + } 546 + 541 547 public function getPreview140URI() { 542 548 $path = '/file/xform/preview-140/'.$this->getPHID().'/' 543 549 .$this->getSecretKey().'/';
-1
src/applications/pholio/application/PhabricatorApplicationPholio.php
··· 53 53 ), 54 54 'image/' => array( 55 55 'upload/' => 'PholioImageUploadController', 56 - 'history/(?P<id>\d+)/' => 'PholioImageHistoryController', 57 56 ), 58 57 ), 59 58 );
-90
src/applications/pholio/controller/PholioImageHistoryController.php
··· 1 - <?php 2 - 3 - /** 4 - * @group pholio 5 - */ 6 - final class PholioImageHistoryController extends PholioController { 7 - 8 - private $imageID; 9 - 10 - public function willProcessRequest(array $data) { 11 - $this->imageID = $data['id']; 12 - } 13 - 14 - public function processRequest() { 15 - $request = $this->getRequest(); 16 - $user = $request->getUser(); 17 - 18 - $image = id(new PholioImageQuery()) 19 - ->setViewer($user) 20 - ->withIDs(array($this->imageID)) 21 - ->executeOne(); 22 - 23 - if (!$image) { 24 - return new Aphront404Response(); 25 - } 26 - 27 - // note while we have a mock object, its missing images we need to show 28 - // the history of what's happened here. 29 - // fetch the real deal 30 - 31 - $mock = id(new PholioMockQuery()) 32 - ->setViewer($user) 33 - ->needImages(true) 34 - ->withIDs(array($image->getMockID())) 35 - ->executeOne(); 36 - 37 - $phids = array($mock->getAuthorPHID()); 38 - $this->loadHandles($phids); 39 - 40 - $engine = id(new PhabricatorMarkupEngine()) 41 - ->setViewer($user); 42 - $engine->addObject($mock, PholioMock::MARKUP_FIELD_DESCRIPTION); 43 - $engine->process(); 44 - 45 - 46 - $images = $mock->getImageHistorySet($this->imageID); 47 - $mock->attachImages($images); 48 - $latest_image = last($images); 49 - 50 - $title = pht( 51 - 'Image history for "%s" from the mock "%s."', 52 - $latest_image->getName(), 53 - $mock->getName()); 54 - 55 - $header = id(new PHUIHeaderView()) 56 - ->setHeader($title); 57 - 58 - require_celerity_resource('pholio-css'); 59 - require_celerity_resource('pholio-inline-comments-css'); 60 - 61 - $comment_form_id = null; 62 - $output = id(new PholioMockImagesView()) 63 - ->setRequestURI($request->getRequestURI()) 64 - ->setCommentFormID($comment_form_id) 65 - ->setUser($user) 66 - ->setMock($mock) 67 - ->setImageID($this->imageID) 68 - ->setViewMode('history'); 69 - 70 - $crumbs = $this->buildApplicationCrumbs(); 71 - $crumbs 72 - ->addTextCrumb('M'.$mock->getID(), '/M'.$mock->getID()) 73 - ->addTextCrumb('Image History', $request->getRequestURI()); 74 - 75 - $content = array( 76 - $crumbs, 77 - $header, 78 - $output->render(), 79 - ); 80 - 81 - return $this->buildApplicationPage( 82 - $content, 83 - array( 84 - 'title' => 'M'.$mock->getID().' '.$title, 85 - 'device' => true, 86 - 'pageObjects' => array($mock->getPHID()), 87 - )); 88 - } 89 - 90 - }
+7 -44
src/applications/pholio/controller/PholioMockViewController.php
··· 1 1 <?php 2 2 3 - /** 4 - * @group pholio 5 - */ 6 3 final class PholioMockViewController extends PholioController { 7 4 8 5 private $id; ··· 89 86 require_celerity_resource('pholio-css'); 90 87 require_celerity_resource('pholio-inline-comments-css'); 91 88 92 - $image_status = $this->getImageStatus($mock, $this->imageID); 93 - 94 89 $comment_form_id = celerity_generate_unique_node_id(); 95 90 $output = id(new PholioMockImagesView()) 96 91 ->setRequestURI($request->getRequestURI()) ··· 115 110 ->setHeader($header) 116 111 ->addPropertyList($properties); 117 112 113 + $thumb_grid = id(new PholioMockThumbGridView()) 114 + ->setUser($user) 115 + ->setMock($mock); 116 + 118 117 $content = array( 119 118 $crumbs, 120 - $image_status, 121 119 $object_box, 122 - $output->render(), 120 + $output, 121 + phutil_tag('br'), 122 + $thumb_grid, 123 123 $xaction_view, 124 124 $add_comment, 125 125 ); ··· 131 131 'device' => true, 132 132 'pageObjects' => array($mock->getPHID()), 133 133 )); 134 - } 135 - 136 - private function getImageStatus(PholioMock $mock, $image_id) { 137 - $status = null; 138 - $images = $mock->getImages(); 139 - foreach ($images as $image) { 140 - if ($image->getID() == $image_id) { 141 - return $status; 142 - } 143 - } 144 - 145 - $images = $mock->getAllImages(); 146 - $images = mpull($images, null, 'getID'); 147 - $image = idx($images, $image_id); 148 - 149 - if ($image) { 150 - $history = $mock->getImageHistorySet($image_id); 151 - $latest_image = last($history); 152 - $href = $this->getApplicationURI( 153 - 'image/history/'.$latest_image->getID().'/'); 154 - $status = id(new AphrontErrorView()) 155 - ->setSeverity(AphrontErrorView::SEVERITY_NOTICE) 156 - ->setTitle(pht('The requested image is obsolete.')) 157 - ->appendChild(phutil_tag( 158 - 'p', 159 - array(), 160 - array( 161 - pht('You are viewing this mock with the latest image set.'), 162 - ' ', 163 - phutil_tag( 164 - 'a', 165 - array('href' => $href), 166 - pht( 167 - 'Click here to see the history of the now obsolete image.'))))); 168 - } 169 - 170 - return $status; 171 134 } 172 135 173 136 private function buildActionView(PholioMock $mock) {
+16 -69
src/applications/pholio/view/PholioMockImagesView.php
··· 9 9 private $imageID; 10 10 private $requestURI; 11 11 private $commentFormID; 12 - private $viewMode = 'normal'; 13 - 14 - /** 15 - * Supports normal (/MX, /MX/Y/) and history (/pholio/image/history/Y/) 16 - * modes. The former has handy dandy commenting functionality and the 17 - * latter does not. 18 - */ 19 - public function setViewMode($view_mode) { 20 - $this->viewMode = $view_mode; 21 - return $this; 22 - } 23 - public function getViewMode() { 24 - return $this->viewMode; 25 - } 26 12 27 13 public function setCommentFormID($comment_form_id) { 28 14 $this->commentFormID = $comment_form_id; 29 15 return $this; 30 16 } 17 + 31 18 public function getCommentFormID() { 32 19 return $this->commentFormID; 33 20 } ··· 36 23 $this->requestURI = $request_uri; 37 24 return $this; 38 25 } 26 + 39 27 public function getRequestURI() { 40 28 return $this->requestURI; 41 29 } ··· 74 62 $selected_id = head_key($ids); 75 63 } 76 64 77 - foreach ($mock->getImages() as $image) { 65 + foreach ($mock->getAllImages() as $image) { 78 66 $file = $image->getFile(); 79 67 $metadata = $file->getMetadata(); 80 68 $x = idx($metadata, PhabricatorFile::METADATA_IMAGE_WIDTH); ··· 90 78 'height' => $y, 91 79 'title' => $image->getName(), 92 80 'desc' => $image->getDescription(), 93 - 'isObsolete' => (bool) $image->getIsObsolete(), 81 + 'isObsolete' => (bool)$image->getIsObsolete(), 94 82 ); 95 83 } 96 84 85 + $navsequence = array(); 86 + foreach ($mock->getImages() as $image) { 87 + $navsequence[] = $image->getID(); 88 + } 89 + 97 90 $login_uri = id(new PhutilURI('/login/')) 98 91 ->setQueryParam('next', (string) $this->getRequestURI()); 99 92 $config = array( ··· 105 98 'selectedID' => $selected_id, 106 99 'loggedIn' => $this->getUser()->isLoggedIn(), 107 100 'logInLink' => (string) $login_uri, 108 - 'viewMode' => $this->getViewMode() 101 + 'navsequence' => $navsequence, 109 102 ); 110 103 Javelin::initBehavior('pholio-mock-view', $config); 111 104 ··· 149 142 ), 150 143 ''); 151 144 152 - $carousel_holder = ''; 153 - if (count($mock->getImages()) > 0) { 154 - $thumbnails = array(); 155 - foreach ($mock->getImages() as $image) { 156 - $thumbfile = $image->getFile(); 157 - 158 - $dimensions = PhabricatorImageTransformer::getPreviewDimensions( 159 - $thumbfile, 160 - 140); 161 - 162 - $tag = phutil_tag( 163 - 'img', 164 - array( 165 - 'width' => $dimensions['sdx'], 166 - 'height' => $dimensions['sdy'], 167 - 'src' => $thumbfile->getPreview140URI(), 168 - 'class' => 'pholio-mock-carousel-thumbnail', 169 - 'style' => 'top: '.floor((140 - $dimensions['sdy'] ) / 2).'px', 170 - )); 171 - 172 - $thumbnails[] = javelin_tag( 173 - 'a', 174 - array( 175 - 'sigil' => 'mock-thumbnail', 176 - 'class' => 'pholio-mock-carousel-thumb-item', 177 - 'href' => $this->getImagePageURI($image, $mock), 178 - 'meta' => array( 179 - 'imageID' => $image->getID(), 180 - ), 181 - ), 182 - $tag); 183 - } 184 - 185 - $carousel_holder = phutil_tag( 186 - 'div', 145 + $mockview[] = phutil_tag( 146 + 'div', 187 147 array( 188 - 'id' => 'pholio-mock-carousel', 189 - 'class' => 'pholio-mock-carousel', 148 + 'class' => 'pholio-mock-image-container', 149 + 'id' => 'pholio-mock-image-container' 190 150 ), 191 - $thumbnails); 192 - } 193 - 194 - $mockview[] = phutil_tag( 195 - 'div', 196 - array( 197 - 'class' => 'pholio-mock-image-container', 198 - 'id' => 'pholio-mock-image-container' 199 - ), 200 - array($mock_wrapper, $inline_comments_holder, $carousel_holder)); 151 + array($mock_wrapper, $inline_comments_holder)); 201 152 202 153 return $mockview; 203 154 } 204 155 205 156 private function getImagePageURI(PholioImage $image, PholioMock $mock) { 206 - if ($this->getViewMode() == 'normal') { 207 - $uri = '/M'.$mock->getID().'/'.$image->getID().'/'; 208 - } else { 209 - $uri = '/pholio/image/history/'.$image->getID().'/'; 210 - } 157 + $uri = '/M'.$mock->getID().'/'.$image->getID().'/'; 211 158 return $uri; 212 159 } 213 160 }
+135
src/applications/pholio/view/PholioMockThumbGridView.php
··· 1 + <?php 2 + 3 + final class PholioMockThumbGridView extends AphrontView { 4 + 5 + private $mock; 6 + 7 + public function setMock(PholioMock $mock) { 8 + $this->mock = $mock; 9 + return $this; 10 + } 11 + 12 + public function render() { 13 + $mock = $this->mock; 14 + 15 + $all_images = $mock->getAllImages(); 16 + $all_images = mpull($all_images, null, 'getPHID'); 17 + 18 + $history = mpull($all_images, 'getReplacesImagePHID', 'getPHID'); 19 + 20 + $replaced = array(); 21 + foreach ($history as $phid => $replaces_phid) { 22 + if ($replaces_phid) { 23 + $replaced[$replaces_phid] = true; 24 + } 25 + } 26 + 27 + // Figure out the columns. Start with all the active images. 28 + $images = mpull($mock->getImages(), null, 'getPHID'); 29 + 30 + // Now, find deleted images: obsolete images which were not replaced. 31 + foreach ($mock->getAllImages() as $image) { 32 + if (!$image->getIsObsolete()) { 33 + // Image is current. 34 + continue; 35 + } 36 + 37 + if (isset($replaced[$image->getPHID()])) { 38 + // Image was replaced. 39 + continue; 40 + } 41 + 42 + // This is an obsolete image which was not replaced, so it must be 43 + // a deleted image. 44 + $images[$image->getPHID()] = $image; 45 + } 46 + 47 + $cols = array(); 48 + $depth = 0; 49 + foreach ($images as $image) { 50 + $phid = $image->getPHID(); 51 + 52 + $col = array(); 53 + 54 + // If this is a deleted image, null out the final column. 55 + if ($image->getIsObsolete()) { 56 + $col[] = null; 57 + } 58 + 59 + $col[] = $phid; 60 + while ($phid && isset($history[$phid])) { 61 + $col[] = $history[$phid]; 62 + $phid = $history[$phid]; 63 + } 64 + 65 + $cols[] = $col; 66 + $depth = max($depth, count($col)); 67 + } 68 + 69 + $grid = array(); 70 + for ($ii = 0; $ii < $depth; $ii++) { 71 + $row = array(); 72 + foreach ($cols as $col) { 73 + if (empty($col[$ii])) { 74 + $row[] = phutil_tag('td', array(), null); 75 + } else { 76 + $thumb = $this->renderThumbnail($all_images[$col[$ii]]); 77 + $row[] = phutil_tag('td', array(), $thumb); 78 + } 79 + } 80 + $grid[] = phutil_tag('tr', array(), $row); 81 + } 82 + 83 + $grid = phutil_tag( 84 + 'table', 85 + array( 86 + 'id' => 'pholio-mock-thumb-grid', 87 + 'class' => 'pholio-mock-thumb-grid', 88 + ), 89 + $grid); 90 + 91 + return phutil_tag( 92 + 'div', 93 + array( 94 + 'class' => 'pholio-mock-thumb-grid-container', 95 + ), 96 + $grid); 97 + } 98 + 99 + 100 + private function renderThumbnail(PholioImage $image) { 101 + $thumbfile = $image->getFile(); 102 + 103 + $dimensions = PhabricatorImageTransformer::getPreviewDimensions( 104 + $thumbfile, 105 + 100); 106 + 107 + $tag = phutil_tag( 108 + 'img', 109 + array( 110 + 'width' => $dimensions['sdx'], 111 + 'height' => $dimensions['sdy'], 112 + 'src' => $thumbfile->getPreview100URI(), 113 + 'class' => 'pholio-mock-thumb-grid-image', 114 + 'style' => 'top: '.floor((100 - $dimensions['sdy'] ) / 2).'px', 115 + )); 116 + 117 + $classes = array('pholio-mock-thumb-grid-item'); 118 + if ($image->getIsObsolete()) { 119 + $classes[] = 'pholio-mock-thumb-grid-item-obsolete'; 120 + } 121 + 122 + return javelin_tag( 123 + 'a', 124 + array( 125 + 'sigil' => 'mock-thumbnail', 126 + 'class' => implode(' ', $classes), 127 + 'href' => '#', 128 + 'meta' => array( 129 + 'imageID' => $image->getID(), 130 + ), 131 + ), 132 + $tag); 133 + } 134 + 135 + }
+18 -31
webroot/rsrc/css/application/pholio/pholio.css
··· 10 10 background: url('/rsrc/image/texture/pholio-background.gif'); 11 11 } 12 12 13 - .pholio-mock-carousel { 13 + .pholio-mock-thumb-grid-container { 14 14 background-color: #282828; 15 - text-align: center; 15 + padding: 12px; 16 + overflow-x: auto; 17 + overflow-y: hidden; 18 + } 19 + 20 + .pholio-mock-thumb-grid { 21 + margin: 0 auto; 16 22 } 17 23 18 - .pholio-mock-carousel-thumb-item { 24 + .pholio-mock-thumb-grid-item { 19 25 display: inline-block; 20 26 cursor: pointer; 21 - width: 140px; 22 - height: 140px; 27 + width: 100px; 28 + height: 100px; 23 29 padding: 5px; 24 30 margin: 3px; 25 31 background: #181818; 26 32 vertical-align: middle; 27 - border: 1px solid #383838; 33 + border: 1px solid {$greyborder}; 28 34 position: relative; 29 35 } 30 36 31 - .device-desktop .pholio-mock-carousel-thumb-item:hover, 32 - .pholio-mock-carousel-thumb-current { 37 + .device-desktop .pholio-mock-thumb-grid-item:hover, 38 + .pholio-mock-thumb-grid-current { 33 39 background: #383838; 34 - border-color: #686868; 40 + border-color: {$sky}; 35 41 } 36 42 37 - .device .pholio-mock-carousel-thumb-item { 38 - width: 5px; 39 - height: 5px; 40 - padding: 0px; 41 - border-radius: 5px; 42 - margin: 5px 2px; 43 - background: #383838; 44 - border-color: #686868; 45 - } 46 - 47 - .device .pholio-mock-carousel-thumb-current { 48 - background: #dfdfdf; 49 - border-color: #ffffff; 50 - } 51 - 52 - .device .pholio-mock-carousel-thumb-item img { 53 - display: none; 43 + .pholio-mock-thumb-grid-item-obsolete { 44 + opacity: 0.5; 54 45 } 55 46 56 - .pholio-mock-carousel-thumbnail { 47 + .pholio-mock-thumb-grid-image { 57 48 margin: auto; 58 49 position: relative; 59 50 } ··· 78 69 opacity: 0.50; 79 70 } 80 71 81 - .pholio-image-info { 82 - border-bottom: 1px solid #101010; 83 - margin-bottom: 5px; 84 - } 85 72 86 73 .pholio-image-info-item { 87 74 padding: 0 8px;
+18 -19
webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js
··· 110 110 return null; 111 111 } 112 112 113 + function get_image_navindex(id) { 114 + for (var ii = 0; ii < config.navsequence.length; ii++) { 115 + if (config.navsequence[ii] == id) { 116 + return ii; 117 + } 118 + } 119 + return null; 120 + } 121 + 113 122 function get_image(id) { 114 123 var idx = get_image_index(id); 115 124 if (idx === null) { ··· 133 142 if (!active_image) { 134 143 return; 135 144 } 136 - var idx = get_image_index(active_image.id); 137 - idx = (idx + delta + config.images.length) % config.images.length; 138 - select_image(config.images[idx].id); 145 + var idx = get_image_navindex(active_image.id); 146 + if (idx === null) { 147 + return; 148 + } 149 + idx = (idx + delta + config.navsequence.length) % config.navsequence.length; 150 + select_image(config.navsequence[idx]); 139 151 } 140 152 141 153 function redraw_image() { ··· 197 209 img.src = active_image.fullURI; 198 210 199 211 var thumbs = JX.DOM.scry( 200 - JX.$('pholio-mock-carousel'), 212 + JX.$('pholio-mock-thumb-grid'), 201 213 'a', 202 214 'mock-thumbnail'); 203 215 ··· 206 218 207 219 JX.DOM.alterClass( 208 220 thumbs[k], 209 - 'pholio-mock-carousel-thumb-current', 221 + 'pholio-mock-thumb-grid-current', 210 222 (active_image.id == thumb_meta.imageID)); 211 223 } 212 224 ··· 217 229 } 218 230 219 231 JX.Stratcom.listen( 220 - ['mousedown', 'click'], 232 + 'click', 221 233 'mock-thumbnail', 222 234 function(e) { 223 235 if (!e.isNormalMouseEvent()) { ··· 235 247 } 236 248 237 249 if (JX.Device.getDevice() != 'desktop') { 238 - return; 239 - } 240 - 241 - if (config.viewMode == 'history') { 242 250 return; 243 251 } 244 252 ··· 601 609 {href: image.fullURI, target: '_blank'}, 602 610 'View Full Image'); 603 611 info.push(full_link); 604 - 605 - if (config.viewMode != 'history') { 606 - var history_link = JX.$N( 607 - 'a', 608 - { href: image.historyURI }, 609 - 'View Image History'); 610 - info.push(history_link); 611 - } 612 - 613 612 614 613 for (var ii = 0; ii < info.length; ii++) { 615 614 info[ii] = JX.$N('div', {className: 'pholio-image-info-item'}, info[ii]);