diff --git a/resources/sql/patches/20130711.pholioimageobsolete.php b/resources/sql/patches/20130711.pholioimageobsolete.php new file mode 100644 index 0000000000..738f82085e --- /dev/null +++ b/resources/sql/patches/20130711.pholioimageobsolete.php @@ -0,0 +1,23 @@ +openTransaction(); + +foreach (new LiskMigrationIterator($table) as $image) { + if ($image->getPHID()) { + continue; + } + + echo "."; + + queryfx( + $image->establishConnection('w'), + 'UPDATE %T SET phid = %s WHERE id = %d', + $image->getTableName(), + $image->generatePHID(), + $image->getID()); +} + +$table->saveTransaction(); +echo "\nDone.\n"; diff --git a/resources/sql/patches/20130711.pholioimageobsolete.sql b/resources/sql/patches/20130711.pholioimageobsolete.sql new file mode 100644 index 0000000000..4e7b69c633 --- /dev/null +++ b/resources/sql/patches/20130711.pholioimageobsolete.sql @@ -0,0 +1,14 @@ +ALTER TABLE {$NAMESPACE}_pholio.pholio_image + ADD `isObsolete` TINYINT(1) NOT NULL DEFAULT '0'; + +ALTER TABLE {$NAMESPACE}_pholio.pholio_image + DROP KEY `mockID`; + +ALTER TABLE {$NAMESPACE}_pholio.pholio_image + ADD KEY `mockID` (`mockID`, `isObsolete`, `sequence`); + +ALTER TABLE {$NAMESPACE}_pholio.pholio_image + ADD `phid` VARCHAR(64) NOT NULL COLLATE utf8_bin AFTER `id`; + +ALTER TABLE {$NAMESPACE}_pholio.pholio_image + CHANGE `mockID` `mockID` INT(10) UNSIGNED; diff --git a/resources/sql/patches/20130711.pholioimageobsolete2.sql b/resources/sql/patches/20130711.pholioimageobsolete2.sql new file mode 100644 index 0000000000..bb551143e9 --- /dev/null +++ b/resources/sql/patches/20130711.pholioimageobsolete2.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_pholio.pholio_image + ADD UNIQUE KEY `keyPHID` (`phid`); diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index daa115c9ec..dac3902315 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -1270,7 +1270,7 @@ celerity_register_resource_map(array( ), 'javelin-behavior-aphront-drag-and-drop' => array( - 'uri' => '/res/36335362/rsrc/js/core/behavior-drag-and-drop.js', + 'uri' => '/res/fde3763f/rsrc/js/core/behavior-drag-and-drop.js', 'type' => 'js', 'requires' => array( @@ -3727,6 +3727,15 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/css/application/pholio/pholio.css', ), + 'pholio-edit-css' => + array( + 'uri' => '/res/c4a1f392/rsrc/css/application/pholio/pholio-edit.css', + 'type' => 'css', + 'requires' => + array( + ), + 'disk' => '/rsrc/css/application/pholio/pholio-edit.css', + ), 'pholio-inline-comments-css' => array( 'uri' => '/res/006fc575/rsrc/css/application/pholio/pholio-inline-comments.css', @@ -4279,7 +4288,7 @@ celerity_register_resource_map(array( 'uri' => '/res/pkg/dd27a69b/differential.pkg.css', 'type' => 'css', ), - '504ca7d2' => + 'bb59a901' => array( 'name' => 'differential.pkg.js', 'symbols' => @@ -4305,7 +4314,7 @@ celerity_register_resource_map(array( 18 => 'javelin-behavior-differential-toggle-files', 19 => 'javelin-behavior-differential-user-select', ), - 'uri' => '/res/pkg/504ca7d2/differential.pkg.js', + 'uri' => '/res/pkg/bb59a901/differential.pkg.js', 'type' => 'js', ), 'c8ce2d88' => @@ -4403,7 +4412,7 @@ celerity_register_resource_map(array( 'aphront-typeahead-control-css' => 'f32a863a', 'differential-changeset-view-css' => 'dd27a69b', 'differential-core-view-css' => 'dd27a69b', - 'differential-inline-comment-editor' => '504ca7d2', + 'differential-inline-comment-editor' => 'bb59a901', 'differential-local-commits-view-css' => 'dd27a69b', 'differential-results-table-css' => 'dd27a69b', 'differential-revision-add-comment-css' => 'dd27a69b', @@ -4421,24 +4430,24 @@ celerity_register_resource_map(array( 'javelin-behavior-aphlict-dropdown' => '75ccea43', 'javelin-behavior-aphlict-listen' => '75ccea43', 'javelin-behavior-aphront-basic-tokenizer' => '75ccea43', - 'javelin-behavior-aphront-drag-and-drop' => '504ca7d2', - 'javelin-behavior-aphront-drag-and-drop-textarea' => '504ca7d2', + 'javelin-behavior-aphront-drag-and-drop' => 'bb59a901', + 'javelin-behavior-aphront-drag-and-drop-textarea' => 'bb59a901', 'javelin-behavior-aphront-form-disable-on-submit' => '75ccea43', 'javelin-behavior-audit-preview' => '96909266', 'javelin-behavior-dark-console' => '4ccfeb47', 'javelin-behavior-device' => '75ccea43', - 'javelin-behavior-differential-accept-with-errors' => '504ca7d2', - 'javelin-behavior-differential-add-reviewers-and-ccs' => '504ca7d2', - 'javelin-behavior-differential-comment-jump' => '504ca7d2', - 'javelin-behavior-differential-diff-radios' => '504ca7d2', - 'javelin-behavior-differential-dropdown-menus' => '504ca7d2', - 'javelin-behavior-differential-edit-inline-comments' => '504ca7d2', - 'javelin-behavior-differential-feedback-preview' => '504ca7d2', - 'javelin-behavior-differential-keyboard-navigation' => '504ca7d2', - 'javelin-behavior-differential-populate' => '504ca7d2', - 'javelin-behavior-differential-show-more' => '504ca7d2', - 'javelin-behavior-differential-toggle-files' => '504ca7d2', - 'javelin-behavior-differential-user-select' => '504ca7d2', + 'javelin-behavior-differential-accept-with-errors' => 'bb59a901', + 'javelin-behavior-differential-add-reviewers-and-ccs' => 'bb59a901', + 'javelin-behavior-differential-comment-jump' => 'bb59a901', + 'javelin-behavior-differential-diff-radios' => 'bb59a901', + 'javelin-behavior-differential-dropdown-menus' => 'bb59a901', + 'javelin-behavior-differential-edit-inline-comments' => 'bb59a901', + 'javelin-behavior-differential-feedback-preview' => 'bb59a901', + 'javelin-behavior-differential-keyboard-navigation' => 'bb59a901', + 'javelin-behavior-differential-populate' => 'bb59a901', + 'javelin-behavior-differential-show-more' => 'bb59a901', + 'javelin-behavior-differential-toggle-files' => 'bb59a901', + 'javelin-behavior-differential-user-select' => 'bb59a901', 'javelin-behavior-diffusion-commit-graph' => '96909266', 'javelin-behavior-diffusion-pull-lastmodified' => '96909266', 'javelin-behavior-error-log' => '4ccfeb47', @@ -4446,7 +4455,7 @@ celerity_register_resource_map(array( 'javelin-behavior-history-install' => '75ccea43', 'javelin-behavior-konami' => '75ccea43', 'javelin-behavior-lightbox-attachments' => '75ccea43', - 'javelin-behavior-load-blame' => '504ca7d2', + 'javelin-behavior-load-blame' => 'bb59a901', 'javelin-behavior-maniphest-batch-selector' => '98f64f07', 'javelin-behavior-maniphest-subpriority-editor' => '98f64f07', 'javelin-behavior-maniphest-transaction-controls' => '98f64f07', @@ -4458,7 +4467,7 @@ celerity_register_resource_map(array( 'javelin-behavior-phabricator-hovercards' => '75ccea43', 'javelin-behavior-phabricator-keyboard-shortcuts' => '75ccea43', 'javelin-behavior-phabricator-nav' => '75ccea43', - 'javelin-behavior-phabricator-object-selector' => '504ca7d2', + 'javelin-behavior-phabricator-object-selector' => 'bb59a901', 'javelin-behavior-phabricator-oncopy' => '75ccea43', 'javelin-behavior-phabricator-remarkup-assist' => '75ccea43', 'javelin-behavior-phabricator-reveal-content' => '75ccea43', @@ -4466,7 +4475,7 @@ celerity_register_resource_map(array( 'javelin-behavior-phabricator-tooltips' => '75ccea43', 'javelin-behavior-phabricator-watch-anchor' => '75ccea43', 'javelin-behavior-refresh-csrf' => '75ccea43', - 'javelin-behavior-repository-crossreference' => '504ca7d2', + 'javelin-behavior-repository-crossreference' => 'bb59a901', 'javelin-behavior-toggle-class' => '75ccea43', 'javelin-behavior-workflow' => '75ccea43', 'javelin-dom' => 'a9f14d76', @@ -4497,7 +4506,7 @@ celerity_register_resource_map(array( 'phabricator-content-source-view-css' => 'dd27a69b', 'phabricator-core-css' => 'f32a863a', 'phabricator-crumbs-view-css' => 'f32a863a', - 'phabricator-drag-and-drop-file-upload' => '504ca7d2', + 'phabricator-drag-and-drop-file-upload' => 'bb59a901', 'phabricator-dropdown-menu' => '75ccea43', 'phabricator-file-upload' => '75ccea43', 'phabricator-filetree-view-css' => 'f32a863a', @@ -4521,7 +4530,7 @@ celerity_register_resource_map(array( 'phabricator-project-tag-css' => 'adc3c36d', 'phabricator-property-list-view-css' => 'f32a863a', 'phabricator-remarkup-css' => 'f32a863a', - 'phabricator-shaped-request' => '504ca7d2', + 'phabricator-shaped-request' => 'bb59a901', 'phabricator-side-menu-view-css' => 'f32a863a', 'phabricator-standard-page-view' => 'f32a863a', 'phabricator-tag-view-css' => 'f32a863a', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 08a8db19cb..0fb93dddb4 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -14,6 +14,8 @@ phutil_register_library_map(array( 'Aphront400Response' => 'aphront/response/Aphront400Response.php', 'Aphront403Response' => 'aphront/response/Aphront403Response.php', 'Aphront404Response' => 'aphront/response/Aphront404Response.php', + 'AphrontAbstractAttachedFileView' => 'view/control/AphrontAbstractAttachedFileView.php', + 'AphrontAbstractFormDragAndDropUploadControl' => 'view/form/control/AphrontAbstractFormDragAndDropUploadControl.php', 'AphrontAjaxResponse' => 'aphront/response/AphrontAjaxResponse.php', 'AphrontApplicationConfiguration' => 'aphront/configuration/AphrontApplicationConfiguration.php', 'AphrontAttachedFileView' => 'view/control/AphrontAttachedFileView.php', @@ -1713,6 +1715,8 @@ phutil_register_library_map(array( 'PholioConstants' => 'applications/pholio/constants/PholioConstants.php', 'PholioController' => 'applications/pholio/controller/PholioController.php', 'PholioDAO' => 'applications/pholio/storage/PholioDAO.php', + 'PholioDragAndDropUploadControl' => 'applications/pholio/view/PholioDragAndDropUploadControl.php', + 'PholioDropUploadController' => 'applications/pholio/controller/PholioDropUploadController.php', 'PholioImage' => 'applications/pholio/storage/PholioImage.php', 'PholioInlineCommentEditView' => 'applications/pholio/view/PholioInlineCommentEditView.php', 'PholioInlineCommentSaveView' => 'applications/pholio/view/PholioInlineCommentSaveView.php', @@ -1741,6 +1745,7 @@ phutil_register_library_map(array( 'PholioTransactionQuery' => 'applications/pholio/query/PholioTransactionQuery.php', 'PholioTransactionType' => 'applications/pholio/constants/PholioTransactionType.php', 'PholioTransactionView' => 'applications/pholio/view/PholioTransactionView.php', + 'PholioUploadedImageView' => 'applications/pholio/view/PholioUploadedImageView.php', 'PhortuneAccount' => 'applications/phortune/storage/PhortuneAccount.php', 'PhortuneAccountBuyController' => 'applications/phortune/controller/PhortuneAccountBuyController.php', 'PhortuneAccountEditor' => 'applications/phortune/editor/PhortuneAccountEditor.php', @@ -1967,8 +1972,10 @@ phutil_register_library_map(array( 'Aphront400Response' => 'AphrontResponse', 'Aphront403Response' => 'AphrontHTMLResponse', 'Aphront404Response' => 'AphrontHTMLResponse', + 'AphrontAbstractAttachedFileView' => 'AphrontView', + 'AphrontAbstractFormDragAndDropUploadControl' => 'AphrontFormControl', 'AphrontAjaxResponse' => 'AphrontResponse', - 'AphrontAttachedFileView' => 'AphrontView', + 'AphrontAttachedFileView' => 'AphrontAbstractAttachedFileView', 'AphrontBarView' => 'AphrontView', 'AphrontCSRFException' => 'AphrontException', 'AphrontCalendarEventView' => 'AphrontView', @@ -1988,7 +1995,7 @@ phutil_register_library_map(array( 'AphrontFormCropControl' => 'AphrontFormControl', 'AphrontFormDateControl' => 'AphrontFormControl', 'AphrontFormDividerControl' => 'AphrontFormControl', - 'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl', + 'AphrontFormDragAndDropUploadControl' => 'AphrontAbstractFormDragAndDropUploadControl', 'AphrontFormFileControl' => 'AphrontFormControl', 'AphrontFormImageControl' => 'AphrontFormControl', 'AphrontFormInsetView' => 'AphrontView', @@ -3699,6 +3706,8 @@ phutil_register_library_map(array( 'PhluxViewController' => 'PhluxController', 'PholioController' => 'PhabricatorController', 'PholioDAO' => 'PhabricatorLiskDAO', + 'PholioDragAndDropUploadControl' => 'AphrontAbstractFormDragAndDropUploadControl', + 'PholioDropUploadController' => 'PholioController', 'PholioImage' => array( 0 => 'PholioDAO', @@ -3739,6 +3748,7 @@ phutil_register_library_map(array( 'PholioTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PholioTransactionType' => 'PholioConstants', 'PholioTransactionView' => 'PhabricatorApplicationTransactionView', + 'PholioUploadedImageView' => 'AphrontAbstractAttachedFileView', 'PhortuneAccount' => array( 0 => 'PhortuneDAO', diff --git a/src/applications/files/controller/PhabricatorFileDropUploadController.php b/src/applications/files/controller/PhabricatorFileDropUploadController.php index 97e948936a..1bb5893668 100644 --- a/src/applications/files/controller/PhabricatorFileDropUploadController.php +++ b/src/applications/files/controller/PhabricatorFileDropUploadController.php @@ -3,6 +3,20 @@ final class PhabricatorFileDropUploadController extends PhabricatorFileController { + private $viewObject; + + public function setViewObject(AphrontAbstractAttachedFileView $view) { + $this->viewObject = $view; + return $this; + } + + public function getViewObject() { + if (!$this->viewObject) { + $this->viewObject = new AphrontAttachedFileView(); + } + return $this->viewObject; + } + /** * @phutil-external-symbol class PhabricatorStartup */ @@ -24,7 +38,7 @@ final class PhabricatorFileDropUploadController 'isExplicitUpload' => true, )); - $view = new AphrontAttachedFileView(); + $view = $this->getViewObject(); $view->setFile($file); return id(new AphrontAjaxResponse())->setContent( diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php index de876a857a..c8851196da 100644 --- a/src/applications/phid/PhabricatorObjectHandle.php +++ b/src/applications/phid/PhabricatorObjectHandle.php @@ -107,6 +107,7 @@ final class PhabricatorObjectHandle { PhabricatorPHIDConstants::PHID_TYPE_WIKI => 'Phriction Document', PhabricatorPHIDConstants::PHID_TYPE_MCRO => 'Image Macro', PhabricatorPHIDConstants::PHID_TYPE_MOCK => 'Pholio Mock', + PhabricatorPHIDConstants::PHID_TYPE_PIMG => 'Pholio Image', PhabricatorPHIDConstants::PHID_TYPE_FILE => 'File', PhabricatorPHIDConstants::PHID_TYPE_BLOG => 'Blog', PhabricatorPHIDConstants::PHID_TYPE_POST => 'Post', diff --git a/src/applications/phid/PhabricatorPHIDConstants.php b/src/applications/phid/PhabricatorPHIDConstants.php index bb1488e85f..c72b9e3a37 100644 --- a/src/applications/phid/PhabricatorPHIDConstants.php +++ b/src/applications/phid/PhabricatorPHIDConstants.php @@ -29,6 +29,7 @@ final class PhabricatorPHIDConstants { const PHID_TYPE_QUES = 'QUES'; const PHID_TYPE_ANSW = 'ANSW'; const PHID_TYPE_MOCK = 'MOCK'; + const PHID_TYPE_PIMG = 'PIMG'; const PHID_TYPE_MCRO = 'MCRO'; const PHID_TYPE_CONF = 'CONF'; const PHID_TYPE_CONP = 'CONP'; diff --git a/src/applications/phid/handle/PhabricatorObjectHandleData.php b/src/applications/phid/handle/PhabricatorObjectHandleData.php index c9fd12129b..f81b3d6b2a 100644 --- a/src/applications/phid/handle/PhabricatorObjectHandleData.php +++ b/src/applications/phid/handle/PhabricatorObjectHandleData.php @@ -137,6 +137,11 @@ final class PhabricatorObjectHandleData { ->execute(); return mpull($mocks, null, 'getPHID'); + case PhabricatorPHIDConstants::PHID_TYPE_PIMG: + $images = id(new PholioImage()) + ->loadAllWhere('phid IN (%Ls)', $phids); + return mpull($images, null, 'getPHID'); + case PhabricatorPHIDConstants::PHID_TYPE_POLL: $polls = id(new PhabricatorSlowvotePoll()) ->loadAllWhere('phid IN (%Ls)', $phids); @@ -649,6 +654,26 @@ final class PhabricatorObjectHandleData { } break; + case PhabricatorPHIDConstants::PHID_TYPE_PIMG: + foreach ($phids as $phid) { + $handle = new PhabricatorObjectHandle(); + $handle->setPHID($phid); + $handle->setType($type); + if (empty($objects[$phid])) { + $handle->setName('Unknown Image'); + } else { + $image = $objects[$phid]; + $handle->setName($image->getName()); + $handle->setFullName($image->getName()); + $handle->setURI( + '/M'.$image->getMockID().'/'.$image->getID().'/'); + $handle->setComplete(true); + } + $handles[$phid] = $handle; + } + break; + + case PhabricatorPHIDConstants::PHID_TYPE_POLL: foreach ($phids as $phid) { $handle = new PhabricatorObjectHandle(); diff --git a/src/applications/pholio/application/PhabricatorApplicationPholio.php b/src/applications/pholio/application/PhabricatorApplicationPholio.php index ed67840ede..c6ff35d123 100644 --- a/src/applications/pholio/application/PhabricatorApplicationPholio.php +++ b/src/applications/pholio/application/PhabricatorApplicationPholio.php @@ -57,6 +57,7 @@ final class PhabricatorApplicationPholio extends PhabricatorApplication { 'edit/(?P\d+)/' => 'PholioInlineEditController', 'thumb/(?P\d+)/' => 'PholioInlineThumbController' ), + 'image/upload/' => 'PholioDropUploadController', ), ); } diff --git a/src/applications/pholio/config/PhabricatorPholioConfigOptions.php b/src/applications/pholio/config/PhabricatorPholioConfigOptions.php index cb76a51dd7..708f623874 100644 --- a/src/applications/pholio/config/PhabricatorPholioConfigOptions.php +++ b/src/applications/pholio/config/PhabricatorPholioConfigOptions.php @@ -1,5 +1,8 @@ delegateToController( + id(new PhabricatorFileDropUploadController($this->getRequest())) + ->setViewObject(new PholioUploadedImageView())); + } + +} diff --git a/src/applications/pholio/controller/PholioMockEditController.php b/src/applications/pholio/controller/PholioMockEditController.php index cc6453d7a0..f3c0c265df 100644 --- a/src/applications/pholio/controller/PholioMockEditController.php +++ b/src/applications/pholio/controller/PholioMockEditController.php @@ -15,10 +15,10 @@ final class PholioMockEditController extends PholioController { $request = $this->getRequest(); $user = $request->getUser(); - if ($this->id) { $mock = id(new PholioMockQuery()) ->setViewer($user) + ->needImages(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -34,14 +34,20 @@ final class PholioMockEditController extends PholioController { $title = pht('Edit Mock'); $is_new = false; + $mock_images = $mock->getImages(); + $files = mpull($mock_images, 'getFile'); + $mock_images = mpull($mock_images, null, 'getFilePHID'); } else { - $mock = new PholioMock(); - $mock->setAuthorPHID($user->getPHID()); - $mock->setViewPolicy(PhabricatorPolicies::POLICY_USER); + $mock = id(new PholioMock()) + ->setAuthorPHID($user->getPHID()) + ->attachImages(array()) + ->setViewPolicy(PhabricatorPolicies::POLICY_USER); $title = pht('Create Mock'); $is_new = true; + $files = array(); + $mock_images = array(); } $e_name = true; @@ -53,7 +59,6 @@ final class PholioMockEditController extends PholioController { $v_view = $mock->getViewPolicy(); $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID( $mock->getPHID()); - $files = array(); if ($request->isFormPost()) { $xactions = array(); @@ -68,54 +73,80 @@ final class PholioMockEditController extends PholioController { $v_view = $request->getStr('can_view'); $v_cc = $request->getArr('cc'); - $xactions[$type_name] = $v_name; - $xactions[$type_desc] = $v_desc; - $xactions[$type_view] = $v_view; - $xactions[$type_cc] = array('=' => $v_cc); + $mock_xactions = array(); + $mock_xactions[$type_name] = $v_name; + $mock_xactions[$type_desc] = $v_desc; + $mock_xactions[$type_view] = $v_view; + $mock_xactions[$type_cc] = array('=' => $v_cc); if (!strlen($request->getStr('name'))) { $e_name = 'Required'; - $errors[] = pht('You must name the mock.'); } - $images = array(); - if ($is_new) { - // TODO: Make this transactional and allow edits? + $file_phids = $request->getArr('file_phids'); + if ($file_phids) { + $files = id(new PhabricatorFileQuery()) + ->setViewer($user) + ->withPHIDs($file_phids) + ->execute(); + $files = mpull($files, null, 'getPHID'); + $files = array_select_keys($files, $file_phids); + } - - $file_phids = $request->getArr('file_phids'); - if ($file_phids) { - $files = id(new PhabricatorFileQuery()) - ->setViewer($user) - ->withPHIDs($file_phids) - ->execute(); - } - - if (!$files) { - $e_images = pht('Required'); - $errors[] = pht('You must add at least one image to the mock.'); - } else { - $mock->setCoverPHID(head($files)->getPHID()); - } - - $sequence = 0; - - foreach ($files as $file) { - $image = new PholioImage(); - $image->setFilePHID($file->getPHID()); - $image->setSequence($sequence++); - - $images[] = $image; - } + if (!$files) { + $e_images = pht('Required'); + $errors[] = pht('You must add at least one image to the mock.'); + } else { + $mock->setCoverPHID(head($files)->getPHID()); } if (!$errors) { - foreach ($xactions as $type => $value) { + foreach ($mock_xactions as $type => $value) { $xactions[$type] = id(new PholioTransaction()) ->setTransactionType($type) ->setNewValue($value); } + $sequence = 0; + foreach ($files as $file_phid => $file) { + $mock_image = idx($mock_images, $file_phid); + $title = $request->getStr('title_'.$file_phid); + $description = $request->getStr('description_'.$file_phid); + if (!$mock_image) { + // this is an add + $add_image = id(new PholioImage()) + ->setFilePhid($file_phid) + ->setName(strlen($title) ? $title : $file->getName()) + ->setDescription($description) + ->setSequence($sequence); + $xactions[] = id(new PholioTransaction()) + ->setTransactionType(PholioTransactionType::TYPE_IMAGE_FILE) + ->setNewValue( + array('+' => array($add_image))); + } else { + // update (maybe) + $xactions[] = id(new PholioTransaction()) + ->setTransactionType(PholioTransactionType::TYPE_IMAGE_NAME) + ->setNewValue( + array($mock_image->getPHID() => $title)); + $xactions[] = id(new PholioTransaction()) + ->setTransactionType( + PholioTransactionType::TYPE_IMAGE_DESCRIPTION) + ->setNewValue(array($mock_image->getPHID() => $description)); + $mock_image->setSequence($sequence); + } + $sequence++; + } + foreach ($mock_images as $file_phid => $mock_image) { + if (!isset($files[$file_phid])) { + // this is a delete + $xactions[] = id(new PholioTransaction()) + ->setTransactionType(PholioTransactionType::TYPE_IMAGE_FILE) + ->setNewValue( + array('-' => array($mock_image))); + } + } + $mock->openTransaction(); $editor = id(new PholioMockEditor()) ->setContentSourceFromRequest($request) @@ -124,13 +155,6 @@ final class PholioMockEditController extends PholioController { $xactions = $editor->applyTransactions($mock, $xactions); - if ($images) { - foreach ($images as $image) { - // TODO: Move into editor? - $image->setMockID($mock->getID()); - $image->save(); - } - } $mock->saveTransaction(); return id(new AphrontRedirectResponse()) @@ -170,17 +194,17 @@ final class PholioMockEditController extends PholioController { $cc_tokens = mpull($handles, 'getFullName', 'getPHID'); - $images_controller = ''; - if ($is_new) { - $images_controller = - id(new AphrontFormDragAndDropUploadControl($request)) - ->setValue($files) - ->setName('file_phids') - ->setLabel(pht('Images')) - ->setActivatedClass('aphront-textarea-drag-and-drop') - ->setError($e_images); - } + $images_controller = + id(new PholioDragAndDropUploadControl($request)) + ->setUploadURI($this->getApplicationURI('image/upload/')) + ->setValue($files) + ->setImages($mock_images) + ->setName('file_phids') + ->setLabel(pht('Images')) + ->setActivatedClass('aphront-textarea-drag-and-drop') + ->setError($e_images); + require_celerity_resource('pholio-edit-css'); $form = id(new AphrontFormView()) ->setUser($user) ->setFlexible(true) @@ -196,7 +220,6 @@ final class PholioMockEditController extends PholioController { ->setValue($v_desc) ->setLabel(pht('Description')) ->setUser($user)) - ->appendChild($images_controller) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('CC')) @@ -211,6 +234,7 @@ final class PholioMockEditController extends PholioController { ->setPolicyObject($mock) ->setPolicies($policies) ->setName('can_view')) + ->appendChild($images_controller) ->appendChild($submit); $crumbs = $this->buildApplicationCrumbs($this->buildSideNav()); diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index abb1075843..da59ca2476 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -5,6 +5,16 @@ */ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { + private $newImages = array(); + private function setNewImages(array $new_images) { + assert_instances_of($new_images, 'PholioImage'); + $this->newImages = $new_images; + return $this; + } + private function getNewImages() { + return $this->newImages; + } + public function getTransactionTypes() { $types = parent::getTransactionTypes(); @@ -15,6 +25,11 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $types[] = PholioTransactionType::TYPE_NAME; $types[] = PholioTransactionType::TYPE_DESCRIPTION; $types[] = PholioTransactionType::TYPE_INLINE; + + $types[] = PholioTransactionType::TYPE_IMAGE_FILE; + $types[] = PholioTransactionType::TYPE_IMAGE_NAME; + $types[] = PholioTransactionType::TYPE_IMAGE_DESCRIPTION; + return $types; } @@ -27,6 +42,27 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { return $object->getName(); case PholioTransactionType::TYPE_DESCRIPTION: return $object->getDescription(); + case PholioTransactionType::TYPE_IMAGE_FILE: + $images = $object->getImages(); + return mpull($images, 'getPHID'); + case PholioTransactionType::TYPE_IMAGE_NAME: + $name = null; + $phid = null; + $image = $this->getImageForXaction($object, $xaction); + if ($image && $image->getName()) { + $name = $image->getName(); + $phid = $image->getPHID(); + } + return array ($phid => $name); + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + $description = null; + $phid = null; + $image = $this->getImageForXaction($object, $xaction); + if ($image && $image->getDescription()) { + $description = $image->getDescription(); + $phid = $image->getPHID(); + } + return array($phid => $description); } } @@ -37,7 +73,17 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { switch ($xaction->getTransactionType()) { case PholioTransactionType::TYPE_NAME: case PholioTransactionType::TYPE_DESCRIPTION: + case PholioTransactionType::TYPE_IMAGE_NAME: + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: return $xaction->getNewValue(); + case PholioTransactionType::TYPE_IMAGE_FILE: + $raw_new_value = $xaction->getNewValue(); + $new_value = array(); + foreach ($raw_new_value as $key => $images) { + $new_value[$key] = mpull($images, 'getPHID'); + } + $xaction->setNewValue($new_value); + return $this->getPHIDTransactionNewValue($xaction); } } @@ -53,6 +99,44 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { return parent::transactionHasEffect($object, $xaction); } + protected function shouldApplyInitialEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PholioTransactionType::TYPE_IMAGE_FILE: + return true; + break; + } + } + return false; + } + + protected function applyInitialEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + $new_images = array(); + foreach ($xactions as $xaction) { + switch ($xaction->getTransactionType()) { + case PholioTransactionType::TYPE_IMAGE_FILE: + $new_value = $xaction->getNewValue(); + foreach ($new_value as $key => $txn_images) { + if ($key != '+') { + continue; + } + foreach ($txn_images as $image) { + $image->save(); + $new_images[] = $image; + } + } + break; + } + } + $this->setNewImages($new_images); + } + protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { @@ -70,10 +154,64 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { } } + private function getImageForXaction( + PholioMock $mock, + PhabricatorApplicationTransaction $xaction) { + $raw_new_value = $xaction->getNewValue(); + $image_phid = key($raw_new_value); + $images = $mock->getImages(); + foreach ($images as $image) { + if ($image->getPHID() == $image_phid) { + return $image; + } + } + return null; + } + protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { - return; + + switch ($xaction->getTransactionType()) { + case PholioTransactionType::TYPE_IMAGE_FILE: + $old_map = array_fuse($xaction->getOldValue()); + $new_map = array_fuse($xaction->getNewValue()); + + $obsolete_map = array_diff_key($old_map, $new_map); + $images = $object->getImages(); + foreach ($images as $seq => $image) { + if (isset($obsolete_map[$image->getPHID()])) { + $image->setIsObsolete(1); + $image->save(); + unset($images[$seq]); + } + } + $object->attachImages($images); + break; + case PholioTransactionType::TYPE_IMAGE_NAME: + $image = $this->getImageForXaction($object, $xaction); + $value = (string) head($xaction->getNewValue()); + $image->setName($value); + $image->save(); + break; + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + $image = $this->getImageForXaction($object, $xaction); + $value = (string) head($xaction->getNewValue()); + $image->setDescription($value); + $image->save(); + break; + } + } + + protected function applyFinalEffects( + PhabricatorLiskDAO $object, + array $xactions) { + + $images = $this->getNewImages(); + foreach ($images as $image) { + $image->setMockID($object->getID()); + $image->save(); + } } protected function mergeTransactions( @@ -85,6 +223,18 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { case PholioTransactionType::TYPE_NAME: case PholioTransactionType::TYPE_DESCRIPTION: return $v; + case PholioTransactionType::TYPE_IMAGE_FILE: + return $this->mergePHIDOrEdgeTransactions($u, $v); + case PholioTransactionType::TYPE_IMAGE_NAME: + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + $raw_new_value_u = $u->getNewValue(); + $raw_new_value_v = $v->getNewValue(); + $phid_u = key($raw_new_value_u); + $phid_v = key($raw_new_value_v); + if ($phid_u == $phid_v) { + return $v; + } + break; } return parent::mergeTransactions($u, $v); @@ -126,6 +276,9 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { $inline_comments = array(); foreach ($xactions as $xaction) { + if ($xaction->shouldHide()) { + continue; + } $comment = $xaction->getComment(); switch ($xaction->getTransactionType()) { case PholioTransactionType::TYPE_INLINE: @@ -199,7 +352,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { return array_values(array_merge($head, $tail)); } - protected function shouldImplyCC( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { diff --git a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php index bf2fc78775..fe36aa67f0 100644 --- a/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php +++ b/src/applications/pholio/lipsum/PhabricatorPholioMockTestDataGenerator.php @@ -1,5 +1,8 @@ loadAllWhere( - 'mockID IN (%Ld)', - $mock_ids); + 'mockID IN (%Ld) AND isObsolete = %d', + $mock_ids, + 0); $file_phids = mpull($all_images, 'getFilePHID'); $all_files = mpull(id(new PhabricatorFile())->loadAllWhere( @@ -143,7 +144,8 @@ final class PholioMockQuery $image_groups = mgroup($all_images, 'getMockID'); foreach ($mocks as $mock) { - $mock->attachImages($image_groups[$mock->getID()]); + $mock_images = $image_groups[$mock->getID()]; + $mock->attachImages($mock_images); } } diff --git a/src/applications/pholio/remarkup/PholioRemarkupRule.php b/src/applications/pholio/remarkup/PholioRemarkupRule.php index c92d39d889..fb569837ca 100644 --- a/src/applications/pholio/remarkup/PholioRemarkupRule.php +++ b/src/applications/pholio/remarkup/PholioRemarkupRule.php @@ -1,5 +1,8 @@ true, + ) + parent::getConfiguration(); + } + + public function generatePHID() { + return PhabricatorPHID::generateNewPHID('PIMG'); + } + public function attachInlineComments(array $inline_comments) { assert_instances_of($inline_comments, 'PholioTransactionComment'); $this->inlineComments = $inline_comments; diff --git a/src/applications/pholio/storage/PholioTransaction.php b/src/applications/pholio/storage/PholioTransaction.php index 25b998ef76..50353a000d 100644 --- a/src/applications/pholio/storage/PholioTransaction.php +++ b/src/applications/pholio/storage/PholioTransaction.php @@ -25,6 +25,26 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { return pht('mock'); } + public function getRequiredHandlePHIDs() { + $phids = parent::getRequiredHandlePHIDs(); + $phids[] = $this->getObjectPHID(); + + $new = $this->getNewValue(); + $old = $this->getOldValue(); + + switch ($this->getTransactionType()) { + case PholioTransactionType::TYPE_IMAGE_FILE: + $phids = array_merge($phids, $new, $old); + break; + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + case PholioTransactionType::TYPE_IMAGE_NAME: + $phids[] = key($new); + break; + } + + return $phids; + } + public function shouldHide() { $old = $this->getOldValue(); @@ -32,6 +52,10 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { case PholioTransactionType::TYPE_NAME: case PholioTransactionType::TYPE_DESCRIPTION: return ($old === null); + case PholioTransactionType::TYPE_IMAGE_NAME: + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + $old_value = reset($old); + return ($old_value === null); } return parent::shouldHide(); @@ -41,7 +65,15 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { switch ($this->getTransactionType()) { case PholioTransactionType::TYPE_INLINE: return 'comment'; + case PholioTransactionType::TYPE_NAME: + case PholioTransactionType::TYPE_DESCRIPTION: + case PholioTransactionType::TYPE_IMAGE_NAME: + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + return 'edit'; + case PholioTransactionType::TYPE_IMAGE_FILE: + return 'attach'; } + return parent::getIcon(); } @@ -77,6 +109,48 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { '%s added %d inline comment(s).', $this->renderHandleLink($author_phid), $count); + break; + case PholioTransactionType::TYPE_IMAGE_FILE: + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + + if ($add && $rem) { + return pht( + '%s edited image(s), added %d: %s; removed %d: %s.', + $this->renderHandleLink($author_phid), + count($add), + $this->renderHandleList($add), + count($rem), + $this->renderHandleList($rem)); + } else if ($add) { + return pht( + '%s added %d image(s): %s.', + $this->renderHandleLink($author_phid), + count($add), + $this->renderHandleList($add)); + } else { + return pht( + '%s removed %d image(s): %s.', + $this->renderHandleLink($author_phid), + count($rem), + $this->renderHandleList($rem)); + } + break; + + case PholioTransactionType::TYPE_IMAGE_NAME: + return pht( + '%s renamed an image (%s) from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $this->renderHandleLink(key($new)), + reset($old), + reset($new)); + break; + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + return pht( + '%s updated an image\'s (%s) description.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink(key($new))); + break; } return parent::getTitle(); @@ -118,6 +192,24 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); break; + case PholioTransactionType::TYPE_IMAGE_FILE: + return pht( + '%s updated images of %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + break; + case PholioTransactionType::TYPE_IMAGE_NAME: + return pht( + '%s updated the image names of %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + break; + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + return pht( + '%s updated image descriptions of %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($object_phid)); + break; } return parent::getTitleForFeed(); @@ -126,6 +218,7 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { public function hasChangeDetails() { switch ($this->getTransactionType()) { case PholioTransactionType::TYPE_DESCRIPTION: + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: return true; } return parent::hasChangeDetails(); @@ -134,6 +227,11 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { public function renderChangeDetails(PhabricatorUser $viewer) { $old = $this->getOldValue(); $new = $this->getNewValue(); + if ($this->getTransactionType() == + PholioTransactionType::TYPE_IMAGE_DESCRIPTION) { + $old = reset($old); + $new = reset($new); + } $view = id(new PhabricatorApplicationTransactionTextDiffDetailView()) ->setUser($viewer) @@ -143,5 +241,28 @@ final class PholioTransaction extends PhabricatorApplicationTransaction { return $view->render(); } + public function getColor() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + switch ($this->getTransactionType()) { + case PholioTransactionType::TYPE_NAME: + case PholioTransactionType::TYPE_DESCRIPTION: + case PholioTransactionType::TYPE_IMAGE_NAME: + case PholioTransactionType::TYPE_IMAGE_DESCRIPTION: + return PhabricatorTransactions::COLOR_BLUE; + case PholioTransactionType::TYPE_IMAGE_FILE: + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + if ($add && $rem) { + return PhabricatorTransactions::COLOR_YELLOW; + } else if ($add) { + return PhabricatorTransactions::COLOR_GREEN; + } else { + return PhabricatorTransactions::COLOR_RED; + } + } + + return parent::getColor(); + } } diff --git a/src/applications/pholio/view/PholioDragAndDropUploadControl.php b/src/applications/pholio/view/PholioDragAndDropUploadControl.php new file mode 100644 index 0000000000..b611444ebf --- /dev/null +++ b/src/applications/pholio/view/PholioDragAndDropUploadControl.php @@ -0,0 +1,25 @@ +images = $images; + return $this; + } + public function getImages() { + return $this->images; + } + + protected function getFileView() { + return id(new PholioUploadedImageView()) + ->setImages($this->getImages()); + } + +} diff --git a/src/applications/pholio/view/PholioInlineCommentView.php b/src/applications/pholio/view/PholioInlineCommentView.php index 2993be226b..d59c6e847c 100644 --- a/src/applications/pholio/view/PholioInlineCommentView.php +++ b/src/applications/pholio/view/PholioInlineCommentView.php @@ -1,5 +1,8 @@ images = $images; + return $this; + } + public function getImages() { + return $this->images; + } + public function getImage($phid) { + $images = $this->getImages(); + return idx($images, $phid, new PholioImage()); + } + + public function render() { + require_celerity_resource('pholio-edit-css'); + + $file = $this->getFile(); + $phid = $file->getPHID(); + $image = $this->getImage($phid); + + $thumb = phutil_tag( + 'img', + array( + 'src' => $file->getThumb280x210URI(), + 'width' => 280, + 'height' => 210, + )); + + $file_link = $this->getName(); + if (!$image->getName()) { + $image->setName($this->getFile()->getName()); + } + $remove = $this->getRemoveElement(); + + $title = id(new AphrontFormTextControl()) + ->setName('title_'.$phid) + ->setValue($image->getName()) + ->setLabel(pht('Title')); + + $description = id(new AphrontFormTextAreaControl()) + ->setName('description_'.$phid) + ->setValue($image->getDescription()) + ->setLabel(pht('Description')); + + return hsprintf( + '
+
+
+
%s
+
%s
+
+
%s
+
+
+
%s +
%s +
', + $file_link, + $remove, + $thumb, + $title, + $description); + + } + +} diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 9eb493cc49..fd7dded312 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -179,6 +179,19 @@ abstract class PhabricatorApplicationTransactionEditor return ($xaction->getOldValue() !== $xaction->getNewValue()); } + protected function shouldApplyInitialEffects( + PhabricatorLiskDAO $object, + array $xactions) { + return false; + + } + + protected function applyInitialEffects( + PhabricatorLiskDAO $object, + array $xactions) { + throw new Exception('Not implemented.'); + } + private function applyInternalEffects( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { @@ -338,24 +351,39 @@ abstract class PhabricatorApplicationTransactionEditor $is_preview = $this->getIsPreview(); $read_locking = false; + $transaction_open = false; - if (!$is_preview && $object->getID()) { - foreach ($xactions as $xaction) { + if (!$is_preview) { + if ($object->getID()) { + foreach ($xactions as $xaction) { - // If any of the transactions require a read lock, hold one and reload - // the object. We need to do this fairly early so that the call to - // `adjustTransactionValues()` (which populates old values) is based - // on the synchronized state of the object, which may differ from the - // state when it was originally loaded. + // If any of the transactions require a read lock, hold one and + // reload the object. We need to do this fairly early so that the + // call to `adjustTransactionValues()` (which populates old values) + // is based on the synchronized state of the object, which may differ + // from the state when it was originally loaded. - if ($this->shouldReadLock($object, $xaction)) { - $object->openTransaction(); - $object->beginReadLocking(); - $read_locking = true; - $object->reload(); - break; + if ($this->shouldReadLock($object, $xaction)) { + $object->openTransaction(); + $object->beginReadLocking(); + $transaction_open = true; + $read_locking = true; + $object->reload(); + break; + } } } + + if ($this->shouldApplyInitialEffects($object, $xactions)) { + if (!$transaction_open) { + $object->openTransaction(); + $transaction_open = true; + } + } + } + + if ($this->shouldApplyInitialEffects($object, $xactions)) { + $this->applyInitialEffects($object, $xactions); } foreach ($xactions as $xaction) { @@ -368,7 +396,10 @@ abstract class PhabricatorApplicationTransactionEditor if ($read_locking) { $object->endReadLocking(); $read_locking = false; + } + if ($transaction_open) { $object->killTransaction(); + $transaction_open = false; } return array(); } @@ -384,7 +415,7 @@ abstract class PhabricatorApplicationTransactionEditor ->setActor($actor) ->setContentSource($this->getContentSource()); - if (!$read_locking) { + if (!$transaction_open) { $object->openTransaction(); } diff --git a/src/infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php b/src/infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php index 3500b991ca..5a97e4c50c 100644 --- a/src/infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php +++ b/src/infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php @@ -222,6 +222,20 @@ abstract class PhabricatorBaseEnglishTranslation ), ), + '%s added %d image(s): %s.' => array( + array( + '%s added an image: %3$s.', + '%s added images: %3$s.', + ), + ), + + '%s removed %d image(s): %s.' => array( + array( + '%s removed an image: %3$s.', + '%s removed images: %3$s.', + ), + ), + '%d people(s)' => array( array( '%d person', diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php index 6ce03e3ac2..3279846620 100644 --- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php +++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php @@ -1454,6 +1454,18 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList { 'type' => 'sql', 'name' => $this->getPatchPath('20130715.voteedges.sql'), ), + '20130711.pholioimageobsolete.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20130711.pholioimageobsolete.sql'), + ), + '20130711.pholioimageobsolete.php' => array( + 'type' => 'php', + 'name' => $this->getPatchPath('20130711.pholioimageobsolete.php'), + ), + '20130711.pholioimageobsolete2.sql' => array( + 'type' => 'sql', + 'name' => $this->getPatchPath('20130711.pholioimageobsolete2.sql'), + ), ); } } diff --git a/src/view/control/AphrontAbstractAttachedFileView.php b/src/view/control/AphrontAbstractAttachedFileView.php new file mode 100644 index 0000000000..a2b7b9ae02 --- /dev/null +++ b/src/view/control/AphrontAbstractAttachedFileView.php @@ -0,0 +1,41 @@ +file = $file; + return $this; + } + + final protected function getFile() { + return $this->file; + } + + final protected function getName() { + $file = $this->getFile(); + return phutil_tag( + 'a', + array( + 'href' => $file->getViewURI(), + 'target' => '_blank', + ), + $file->getName()); + } + + final protected function getRemoveElement() { + $file = $this->getFile(); + return javelin_tag( + 'a', + array( + 'class' => 'button grey', + 'sigil' => 'aphront-attached-file-view-remove', + // NOTE: Using 'ref' here instead of 'meta' because the file upload + // endpoint doesn't receive request metadata and thus can't generate + // a valid response with node metadata. + 'ref' => $file->getPHID(), + ), + "\xE2\x9C\x96"); // "Heavy Multiplication X" + } +} diff --git a/src/view/control/AphrontAttachedFileView.php b/src/view/control/AphrontAttachedFileView.php index c889bcf7e5..41eeaf6575 100644 --- a/src/view/control/AphrontAttachedFileView.php +++ b/src/view/control/AphrontAttachedFileView.php @@ -1,18 +1,11 @@ file = $file; - return $this; - } +final class AphrontAttachedFileView extends AphrontAbstractAttachedFileView { public function render() { require_celerity_resource('aphront-attached-file-view-css'); - $file = $this->file; + $file = $this->getFile(); $phid = $file->getPHID(); $thumb = phutil_tag( @@ -23,26 +16,10 @@ final class AphrontAttachedFileView extends AphrontView { 'height' => 45, )); - $name = phutil_tag( - 'a', - array( - 'href' => $file->getViewURI(), - 'target' => '_blank', - ), - $file->getName()); + $name = $this->getName(); $size = number_format($file->getByteSize()).' ' .pht('bytes'); - $remove = javelin_tag( - 'a', - array( - 'class' => 'button grey', - 'sigil' => 'aphront-attached-file-view-remove', - // NOTE: Using 'ref' here instead of 'meta' because the file upload - // endpoint doesn't receive request metadata and thus can't generate - // a valid response with node metadata. - 'ref' => $file->getPHID(), - ), - "\xE2\x9C\x96"); // "Heavy Multiplication X" + $remove = $this->getRemoveElement(); return hsprintf( ' diff --git a/src/view/form/control/AphrontAbstractFormDragAndDropUploadControl.php b/src/view/form/control/AphrontAbstractFormDragAndDropUploadControl.php new file mode 100644 index 0000000000..b8be293374 --- /dev/null +++ b/src/view/form/control/AphrontAbstractFormDragAndDropUploadControl.php @@ -0,0 +1,73 @@ +setControlID(celerity_generate_unique_node_id()); + $this->setControlStyle('display: none;'); + } + + protected function getCustomControlClass() { + return 'aphront-form-drag-and-drop-upload'; + } + + public function setUploadURI($upload_uri) { + $this->uploadURI = $upload_uri; + return $this; + } + + public function getUploadURI() { + return $this->uploadURI; + } + + public function setActivatedClass($class) { + $this->activatedClass = $class; + return $this; + } + + protected function getFileView() { + return new AphrontAttachedFileView(); + } + + protected function renderInput() { + require_celerity_resource('aphront-attached-file-view-css'); + $list_id = celerity_generate_unique_node_id(); + + $files = $this->getValue(); + $value = array(); + if ($files) { + foreach ($files as $file) { + $view = $this->getFileView(); + $view->setFile($file); + $value[$file->getPHID()] = array( + 'phid' => $file->getPHID(), + 'html' => $view->render(), + ); + } + } + + Javelin::initBehavior( + 'aphront-drag-and-drop', + array( + 'control' => $this->getControlID(), + 'name' => $this->getName(), + 'value' => nonempty($value, null), + 'list' => $list_id, + 'uri' => $this->getUploadURI(), + 'activatedClass' => $this->activatedClass, + )); + + return phutil_tag( + 'div', + array( + 'id' => $list_id, + 'class' => 'aphront-form-drag-and-drop-file-list', + ), + ''); + } + +} diff --git a/src/view/form/control/AphrontFormDragAndDropUploadControl.php b/src/view/form/control/AphrontFormDragAndDropUploadControl.php index d25c6190a8..a7069a8702 100644 --- a/src/view/form/control/AphrontFormDragAndDropUploadControl.php +++ b/src/view/form/control/AphrontFormDragAndDropUploadControl.php @@ -1,58 +1,6 @@ setControlID(celerity_generate_unique_node_id()); - $this->setControlStyle('display: none;'); - } - - protected function getCustomControlClass() { - return 'aphront-form-drag-and-drop-upload'; - } - - public function setActivatedClass($class) { - $this->activatedClass = $class; - return $this; - } - - protected function renderInput() { - require_celerity_resource('aphront-attached-file-view-css'); - $list_id = celerity_generate_unique_node_id(); - - $files = $this->getValue(); - $value = array(); - if ($files) { - foreach ($files as $file) { - $view = new AphrontAttachedFileView(); - $view->setFile($file); - $value[$file->getPHID()] = array( - 'phid' => $file->getPHID(), - 'html' => $view->render(), - ); - } - } - - Javelin::initBehavior( - 'aphront-drag-and-drop', - array( - 'control' => $this->getControlID(), - 'name' => $this->getName(), - 'value' => nonempty($value, null), - 'list' => $list_id, - 'uri' => '/file/dropupload/', - 'activatedClass' => $this->activatedClass, - )); - - return phutil_tag( - 'div', - array( - 'id' => $list_id, - 'class' => 'aphront-form-drag-and-drop-file-list', - ), - ''); - } +final class AphrontFormDragAndDropUploadControl + extends AphrontAbstractFormDragAndDropUploadControl { } diff --git a/webroot/rsrc/css/application/pholio/pholio-edit.css b/webroot/rsrc/css/application/pholio/pholio-edit.css new file mode 100644 index 0000000000..2503ef9380 --- /dev/null +++ b/webroot/rsrc/css/application/pholio/pholio-edit.css @@ -0,0 +1,81 @@ +/** + * @provides pholio-edit-css + */ + +.pholio-uploaded-image { + float: left; + margin: 0px 0px 12px 0px; +} + +.pholio-uploaded-image .thumb-box { + border: 1px solid #D5D9DF; + border-radius: 3px; + min-width: 280px; + width: 48%; + float: left; +} + +.pholio-uploaded-image .image-data { + width: 48%; + float: right; +} + +.pholio-uploaded-image .thumb-box .title { + width: 100%; + float: left; + background: #EDF0F4; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + border-bottom: 1px solid #D5D9DF; +} + +.pholio-uploaded-image .thumb-box .thumb { + float: left; + background: white; + padding: 12px 0px 0px 0px; + width: 100%; +} + +.pholio-uploaded-image .thumb-box .title .text { + width: 220px; + float: left; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + padding: 6px 0px 0px 8px; +} + +.pholio-uploaded-image .thumb-box .title .remove { + float: right; +} + +.pholio-uploaded-image .thumb-box .title .remove .button { + background: #EDF0F4; + border: none; +} + +.pholio-uploaded-image .thumb-box .title .remove .button:hover { + box-shadow: inset 0 0 2px rgba(0,0,0,.2); +} + +.pholio-uploaded-image .image-data .aphront-form-control { + padding: 0px 0px 4px 4px; +} +.pholio-uploaded-image .image-data .aphront-form-label { + text-align: left; + margin: 0px 0px 5px 0px; +} +.pholio-uploaded-image .image-data .aphront-form-input { + margin: 0; + width: 100%; +} + +.aphront-form-input .aphront-form-drag-and-drop-file-list { + float: left; + width: 100%; +} +.aphront-form-input .aphront-form-drag-and-drop-file-list +.drag-and-drop-file-target { + padding: 20px; + clear: both; +} diff --git a/webroot/rsrc/js/core/behavior-drag-and-drop.js b/webroot/rsrc/js/core/behavior-drag-and-drop.js index 6d17aca9f1..cb869bff67 100644 --- a/webroot/rsrc/js/core/behavior-drag-and-drop.js +++ b/webroot/rsrc/js/core/behavior-drag-and-drop.js @@ -53,7 +53,7 @@ JX.behavior('aphront-drag-and-drop', function(config) { pending--; redraw(true); - // This redraws the instructions. + // This redraws the instructions and clears "Upload complete!" setTimeout(redraw, 1000); });