mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 00:42:41 +01:00
Support drag-and-drop to set cover images on workboard cards
Summary: This was slightly more complex than I believed, but not too terrible. Test Plan: {F1096126} - Also used some normal file uploaders to make sure I didn't break that. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D15202
This commit is contained in:
parent
b6a38b403c
commit
78c248d330
15 changed files with 352 additions and 139 deletions
BIN
resources/builtin/image-526x526.png
Normal file
BIN
resources/builtin/image-526x526.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
|
@ -8,10 +8,10 @@
|
||||||
return array(
|
return array(
|
||||||
'names' => array(
|
'names' => array(
|
||||||
'core.pkg.css' => 'a7d4cf8f',
|
'core.pkg.css' => 'a7d4cf8f',
|
||||||
'core.pkg.js' => 'ef5e33db',
|
'core.pkg.js' => '808ae845',
|
||||||
'darkconsole.pkg.js' => 'e7393ebb',
|
'darkconsole.pkg.js' => 'e7393ebb',
|
||||||
'differential.pkg.css' => '2de124c9',
|
'differential.pkg.css' => '2de124c9',
|
||||||
'differential.pkg.js' => '5c2ba922',
|
'differential.pkg.js' => '6b42b4bc',
|
||||||
'diffusion.pkg.css' => 'f45955ed',
|
'diffusion.pkg.css' => 'f45955ed',
|
||||||
'diffusion.pkg.js' => '3a9a8bfa',
|
'diffusion.pkg.js' => '3a9a8bfa',
|
||||||
'maniphest.pkg.css' => '4845691a',
|
'maniphest.pkg.css' => '4845691a',
|
||||||
|
@ -155,7 +155,7 @@ return array(
|
||||||
'rsrc/css/phui/phui-timeline-view.css' => '2efceff8',
|
'rsrc/css/phui/phui-timeline-view.css' => '2efceff8',
|
||||||
'rsrc/css/phui/phui-two-column-view.css' => 'c75bfc5b',
|
'rsrc/css/phui/phui-two-column-view.css' => 'c75bfc5b',
|
||||||
'rsrc/css/phui/workboards/phui-workboard.css' => 'b07a5524',
|
'rsrc/css/phui/workboards/phui-workboard.css' => 'b07a5524',
|
||||||
'rsrc/css/phui/workboards/phui-workcard.css' => 'adf34f58',
|
'rsrc/css/phui/workboards/phui-workcard.css' => 'a869098a',
|
||||||
'rsrc/css/phui/workboards/phui-workpanel.css' => 'e1bd8d04',
|
'rsrc/css/phui/workboards/phui-workpanel.css' => 'e1bd8d04',
|
||||||
'rsrc/css/sprite-login.css' => '60e8560e',
|
'rsrc/css/sprite-login.css' => '60e8560e',
|
||||||
'rsrc/css/sprite-menu.css' => '9dd65b92',
|
'rsrc/css/sprite-menu.css' => '9dd65b92',
|
||||||
|
@ -414,7 +414,7 @@ return array(
|
||||||
'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef',
|
'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef',
|
||||||
'rsrc/js/application/policy/behavior-policy-control.js' => 'd0c516d5',
|
'rsrc/js/application/policy/behavior-policy-control.js' => 'd0c516d5',
|
||||||
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c',
|
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c',
|
||||||
'rsrc/js/application/projects/behavior-project-boards.js' => '48470f95',
|
'rsrc/js/application/projects/behavior-project-boards.js' => '5191522f',
|
||||||
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
|
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
|
||||||
'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb',
|
'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb',
|
||||||
'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
|
'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
|
||||||
|
@ -446,9 +446,9 @@ return array(
|
||||||
'rsrc/js/application/uiexample/gesture-example.js' => '558829c2',
|
'rsrc/js/application/uiexample/gesture-example.js' => '558829c2',
|
||||||
'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5',
|
'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5',
|
||||||
'rsrc/js/core/Busy.js' => '59a7976a',
|
'rsrc/js/core/Busy.js' => '59a7976a',
|
||||||
'rsrc/js/core/DragAndDropFileUpload.js' => 'ad10aeac',
|
'rsrc/js/core/DragAndDropFileUpload.js' => 'da044194',
|
||||||
'rsrc/js/core/DraggableList.js' => '8905523d',
|
'rsrc/js/core/DraggableList.js' => '8905523d',
|
||||||
'rsrc/js/core/FileUpload.js' => '477359c8',
|
'rsrc/js/core/FileUpload.js' => '680ea2c8',
|
||||||
'rsrc/js/core/Hovercard.js' => '1bd28176',
|
'rsrc/js/core/Hovercard.js' => '1bd28176',
|
||||||
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
|
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
|
||||||
'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f',
|
'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f',
|
||||||
|
@ -654,7 +654,7 @@ return array(
|
||||||
'javelin-behavior-phui-profile-menu' => '12884df9',
|
'javelin-behavior-phui-profile-menu' => '12884df9',
|
||||||
'javelin-behavior-policy-control' => 'd0c516d5',
|
'javelin-behavior-policy-control' => 'd0c516d5',
|
||||||
'javelin-behavior-policy-rule-editor' => '5e9f347c',
|
'javelin-behavior-policy-rule-editor' => '5e9f347c',
|
||||||
'javelin-behavior-project-boards' => '48470f95',
|
'javelin-behavior-project-boards' => '5191522f',
|
||||||
'javelin-behavior-project-create' => '065227cc',
|
'javelin-behavior-project-create' => '065227cc',
|
||||||
'javelin-behavior-quicksand-blacklist' => '7927a7d3',
|
'javelin-behavior-quicksand-blacklist' => '7927a7d3',
|
||||||
'javelin-behavior-recurring-edit' => '5f1c4d5f',
|
'javelin-behavior-recurring-edit' => '5f1c4d5f',
|
||||||
|
@ -741,11 +741,11 @@ return array(
|
||||||
'phabricator-core-css' => '5b3563c8',
|
'phabricator-core-css' => '5b3563c8',
|
||||||
'phabricator-countdown-css' => 'e7544472',
|
'phabricator-countdown-css' => 'e7544472',
|
||||||
'phabricator-dashboard-css' => 'eb458607',
|
'phabricator-dashboard-css' => 'eb458607',
|
||||||
'phabricator-drag-and-drop-file-upload' => 'ad10aeac',
|
'phabricator-drag-and-drop-file-upload' => 'da044194',
|
||||||
'phabricator-draggable-list' => '8905523d',
|
'phabricator-draggable-list' => '8905523d',
|
||||||
'phabricator-fatal-config-template-css' => '8e6c6fcd',
|
'phabricator-fatal-config-template-css' => '8e6c6fcd',
|
||||||
'phabricator-feed-css' => 'ecd4ec57',
|
'phabricator-feed-css' => 'ecd4ec57',
|
||||||
'phabricator-file-upload' => '477359c8',
|
'phabricator-file-upload' => '680ea2c8',
|
||||||
'phabricator-filetree-view-css' => 'fccf9f82',
|
'phabricator-filetree-view-css' => 'fccf9f82',
|
||||||
'phabricator-flag-css' => '5337623f',
|
'phabricator-flag-css' => '5337623f',
|
||||||
'phabricator-keyboard-shortcut' => '1ae869f2',
|
'phabricator-keyboard-shortcut' => '1ae869f2',
|
||||||
|
@ -832,7 +832,7 @@ return array(
|
||||||
'phui-timeline-view-css' => '2efceff8',
|
'phui-timeline-view-css' => '2efceff8',
|
||||||
'phui-two-column-view-css' => 'c75bfc5b',
|
'phui-two-column-view-css' => 'c75bfc5b',
|
||||||
'phui-workboard-view-css' => 'b07a5524',
|
'phui-workboard-view-css' => 'b07a5524',
|
||||||
'phui-workcard-view-css' => 'adf34f58',
|
'phui-workcard-view-css' => 'a869098a',
|
||||||
'phui-workpanel-view-css' => 'e1bd8d04',
|
'phui-workpanel-view-css' => 'e1bd8d04',
|
||||||
'phuix-action-list-view' => 'b5c256b8',
|
'phuix-action-list-view' => 'b5c256b8',
|
||||||
'phuix-action-view' => '8cf6d262',
|
'phuix-action-view' => '8cf6d262',
|
||||||
|
@ -1133,11 +1133,6 @@ return array(
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
'javelin-workflow',
|
'javelin-workflow',
|
||||||
),
|
),
|
||||||
'477359c8' => array(
|
|
||||||
'javelin-install',
|
|
||||||
'javelin-dom',
|
|
||||||
'phabricator-notification',
|
|
||||||
),
|
|
||||||
47830651 => array(
|
47830651 => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
|
@ -1154,15 +1149,6 @@ return array(
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
'javelin-workflow',
|
'javelin-workflow',
|
||||||
),
|
),
|
||||||
'48470f95' => array(
|
|
||||||
'javelin-behavior',
|
|
||||||
'javelin-dom',
|
|
||||||
'javelin-util',
|
|
||||||
'javelin-vector',
|
|
||||||
'javelin-stratcom',
|
|
||||||
'javelin-workflow',
|
|
||||||
'phabricator-draggable-list',
|
|
||||||
),
|
|
||||||
'49b73b36' => array(
|
'49b73b36' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
|
@ -1204,6 +1190,16 @@ return array(
|
||||||
'javelin-typeahead-source',
|
'javelin-typeahead-source',
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
),
|
),
|
||||||
|
'5191522f' => array(
|
||||||
|
'javelin-behavior',
|
||||||
|
'javelin-dom',
|
||||||
|
'javelin-util',
|
||||||
|
'javelin-vector',
|
||||||
|
'javelin-stratcom',
|
||||||
|
'javelin-workflow',
|
||||||
|
'phabricator-draggable-list',
|
||||||
|
'phabricator-drag-and-drop-file-upload',
|
||||||
|
),
|
||||||
'519705ea' => array(
|
'519705ea' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
|
@ -1331,6 +1327,11 @@ return array(
|
||||||
'javelin-request',
|
'javelin-request',
|
||||||
'javelin-workflow',
|
'javelin-workflow',
|
||||||
),
|
),
|
||||||
|
'680ea2c8' => array(
|
||||||
|
'javelin-install',
|
||||||
|
'javelin-dom',
|
||||||
|
'phabricator-notification',
|
||||||
|
),
|
||||||
'6882e80a' => array(
|
'6882e80a' => array(
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
),
|
),
|
||||||
|
@ -1674,14 +1675,6 @@ return array(
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
'phabricator-busy',
|
'phabricator-busy',
|
||||||
),
|
),
|
||||||
'ad10aeac' => array(
|
|
||||||
'javelin-install',
|
|
||||||
'javelin-util',
|
|
||||||
'javelin-request',
|
|
||||||
'javelin-dom',
|
|
||||||
'javelin-uri',
|
|
||||||
'phabricator-file-upload',
|
|
||||||
),
|
|
||||||
'b064af76' => array(
|
'b064af76' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-stratcom',
|
'javelin-stratcom',
|
||||||
|
@ -1897,6 +1890,14 @@ return array(
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
'phabricator-shaped-request',
|
'phabricator-shaped-request',
|
||||||
),
|
),
|
||||||
|
'da044194' => array(
|
||||||
|
'javelin-install',
|
||||||
|
'javelin-util',
|
||||||
|
'javelin-request',
|
||||||
|
'javelin-dom',
|
||||||
|
'javelin-uri',
|
||||||
|
'phabricator-file-upload',
|
||||||
|
),
|
||||||
'dbbf48b6' => array(
|
'dbbf48b6' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-stratcom',
|
'javelin-stratcom',
|
||||||
|
|
|
@ -2880,6 +2880,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php',
|
'PhabricatorProjectConfiguredCustomField' => 'applications/project/customfield/PhabricatorProjectConfiguredCustomField.php',
|
||||||
'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php',
|
'PhabricatorProjectController' => 'applications/project/controller/PhabricatorProjectController.php',
|
||||||
'PhabricatorProjectCoreTestCase' => 'applications/project/__tests__/PhabricatorProjectCoreTestCase.php',
|
'PhabricatorProjectCoreTestCase' => 'applications/project/__tests__/PhabricatorProjectCoreTestCase.php',
|
||||||
|
'PhabricatorProjectCoverController' => 'applications/project/controller/PhabricatorProjectCoverController.php',
|
||||||
'PhabricatorProjectCustomField' => 'applications/project/customfield/PhabricatorProjectCustomField.php',
|
'PhabricatorProjectCustomField' => 'applications/project/customfield/PhabricatorProjectCustomField.php',
|
||||||
'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php',
|
'PhabricatorProjectCustomFieldNumericIndex' => 'applications/project/storage/PhabricatorProjectCustomFieldNumericIndex.php',
|
||||||
'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php',
|
'PhabricatorProjectCustomFieldStorage' => 'applications/project/storage/PhabricatorProjectCustomFieldStorage.php',
|
||||||
|
@ -7304,6 +7305,7 @@ phutil_register_library_map(array(
|
||||||
),
|
),
|
||||||
'PhabricatorProjectController' => 'PhabricatorController',
|
'PhabricatorProjectController' => 'PhabricatorController',
|
||||||
'PhabricatorProjectCoreTestCase' => 'PhabricatorTestCase',
|
'PhabricatorProjectCoreTestCase' => 'PhabricatorTestCase',
|
||||||
|
'PhabricatorProjectCoverController' => 'PhabricatorProjectController',
|
||||||
'PhabricatorProjectCustomField' => 'PhabricatorCustomField',
|
'PhabricatorProjectCustomField' => 'PhabricatorCustomField',
|
||||||
'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
|
'PhabricatorProjectCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
|
||||||
'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
|
'PhabricatorProjectCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
|
||||||
|
|
|
@ -28,6 +28,7 @@ final class ManiphestTransactionEditor
|
||||||
$types[] = ManiphestTransaction::TYPE_UNBLOCK;
|
$types[] = ManiphestTransaction::TYPE_UNBLOCK;
|
||||||
$types[] = ManiphestTransaction::TYPE_PARENT;
|
$types[] = ManiphestTransaction::TYPE_PARENT;
|
||||||
$types[] = ManiphestTransaction::TYPE_COLUMN;
|
$types[] = ManiphestTransaction::TYPE_COLUMN;
|
||||||
|
$types[] = ManiphestTransaction::TYPE_COVER_IMAGE;
|
||||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||||
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||||
|
|
||||||
|
@ -66,6 +67,8 @@ final class ManiphestTransactionEditor
|
||||||
return $xaction->getOldValue();
|
return $xaction->getOldValue();
|
||||||
case ManiphestTransaction::TYPE_SUBPRIORITY:
|
case ManiphestTransaction::TYPE_SUBPRIORITY:
|
||||||
return $object->getSubpriority();
|
return $object->getSubpriority();
|
||||||
|
case ManiphestTransaction::TYPE_COVER_IMAGE:
|
||||||
|
return $object->getCoverImageFilePHID();
|
||||||
case ManiphestTransaction::TYPE_MERGED_INTO:
|
case ManiphestTransaction::TYPE_MERGED_INTO:
|
||||||
case ManiphestTransaction::TYPE_MERGED_FROM:
|
case ManiphestTransaction::TYPE_MERGED_FROM:
|
||||||
return null;
|
return null;
|
||||||
|
@ -92,6 +95,7 @@ final class ManiphestTransactionEditor
|
||||||
case ManiphestTransaction::TYPE_MERGED_INTO:
|
case ManiphestTransaction::TYPE_MERGED_INTO:
|
||||||
case ManiphestTransaction::TYPE_MERGED_FROM:
|
case ManiphestTransaction::TYPE_MERGED_FROM:
|
||||||
case ManiphestTransaction::TYPE_UNBLOCK:
|
case ManiphestTransaction::TYPE_UNBLOCK:
|
||||||
|
case ManiphestTransaction::TYPE_COVER_IMAGE:
|
||||||
return $xaction->getNewValue();
|
return $xaction->getNewValue();
|
||||||
case ManiphestTransaction::TYPE_PARENT:
|
case ManiphestTransaction::TYPE_PARENT:
|
||||||
case ManiphestTransaction::TYPE_COLUMN:
|
case ManiphestTransaction::TYPE_COLUMN:
|
||||||
|
@ -161,6 +165,32 @@ final class ManiphestTransactionEditor
|
||||||
case ManiphestTransaction::TYPE_MERGED_INTO:
|
case ManiphestTransaction::TYPE_MERGED_INTO:
|
||||||
$object->setStatus(ManiphestTaskStatus::getDuplicateStatus());
|
$object->setStatus(ManiphestTaskStatus::getDuplicateStatus());
|
||||||
return;
|
return;
|
||||||
|
case ManiphestTransaction::TYPE_COVER_IMAGE:
|
||||||
|
$file_phid = $xaction->getNewValue();
|
||||||
|
|
||||||
|
if ($file_phid) {
|
||||||
|
$file = id(new PhabricatorFileQuery())
|
||||||
|
->setViewer($this->getActor())
|
||||||
|
->withPHIDs(array($file_phid))
|
||||||
|
->executeOne();
|
||||||
|
} else {
|
||||||
|
$file = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$file || !$file->isTransformableImage()) {
|
||||||
|
$object->setProperty('cover.filePHID', null);
|
||||||
|
$object->setProperty('cover.thumbnailPHID', null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$xform_key = PhabricatorFileThumbnailTransform::TRANSFORM_WORKCARD;
|
||||||
|
|
||||||
|
$xform = PhabricatorFileTransform::getTransformByKey($xform_key)
|
||||||
|
->executeTransform($file);
|
||||||
|
|
||||||
|
$object->setProperty('cover.filePHID', $file->getPHID());
|
||||||
|
$object->setProperty('cover.thumbnailPHID', $xform->getPHID());
|
||||||
|
return;
|
||||||
case ManiphestTransaction::TYPE_MERGED_FROM:
|
case ManiphestTransaction::TYPE_MERGED_FROM:
|
||||||
case ManiphestTransaction::TYPE_PARENT:
|
case ManiphestTransaction::TYPE_PARENT:
|
||||||
case ManiphestTransaction::TYPE_COLUMN:
|
case ManiphestTransaction::TYPE_COLUMN:
|
||||||
|
@ -819,6 +849,41 @@ final class ManiphestTransactionEditor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ManiphestTransaction::TYPE_COVER_IMAGE:
|
||||||
|
foreach ($xactions as $xaction) {
|
||||||
|
$old = $xaction->getOldValue();
|
||||||
|
$new = $xaction->getNewValue();
|
||||||
|
if (!$new) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($new === $old) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = id(new PhabricatorFileQuery())
|
||||||
|
->setViewer($this->getActor())
|
||||||
|
->withPHIDs(array($new))
|
||||||
|
->executeOne();
|
||||||
|
if (!$file) {
|
||||||
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('Invalid'),
|
||||||
|
pht('File "%s" is not valid.', $new),
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$file->isTransformableImage()) {
|
||||||
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
|
$type,
|
||||||
|
pht('Invalid'),
|
||||||
|
pht('File "%s" is not a valid image file.', $new),
|
||||||
|
$xaction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $errors;
|
return $errors;
|
||||||
|
@ -941,5 +1006,19 @@ final class ManiphestTransactionEditor
|
||||||
->executeOne();
|
->executeOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function extractFilePHIDsFromCustomTransaction(
|
||||||
|
PhabricatorLiskDAO $object,
|
||||||
|
PhabricatorApplicationTransaction $xaction) {
|
||||||
|
$phids = parent::extractFilePHIDsFromCustomTransaction($object, $xaction);
|
||||||
|
|
||||||
|
switch ($xaction->getTransactionType()) {
|
||||||
|
case ManiphestTransaction::TYPE_COVER_IMAGE:
|
||||||
|
$phids[] = $xaction->getNewValue();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $phids;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -226,6 +226,10 @@ final class ManiphestTask extends ManiphestDAO
|
||||||
return idx($this->properties, $key, $default);
|
return idx($this->properties, $key, $default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCoverImageFilePHID() {
|
||||||
|
return idx($this->properties, 'cover.filePHID');
|
||||||
|
}
|
||||||
|
|
||||||
public function getCoverImageThumbnailPHID() {
|
public function getCoverImageThumbnailPHID() {
|
||||||
return idx($this->properties, 'cover.thumbnailPHID');
|
return idx($this->properties, 'cover.thumbnailPHID');
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ final class ManiphestTransaction
|
||||||
const TYPE_UNBLOCK = 'unblock';
|
const TYPE_UNBLOCK = 'unblock';
|
||||||
const TYPE_PARENT = 'parent';
|
const TYPE_PARENT = 'parent';
|
||||||
const TYPE_COLUMN = 'column';
|
const TYPE_COLUMN = 'column';
|
||||||
|
const TYPE_COVER_IMAGE = 'cover-image';
|
||||||
|
|
||||||
// NOTE: this type is deprecated. Keep it around for legacy installs
|
// NOTE: this type is deprecated. Keep it around for legacy installs
|
||||||
// so any transactions render correctly.
|
// so any transactions render correctly.
|
||||||
|
@ -162,6 +163,9 @@ final class ManiphestTransaction
|
||||||
sort($new_cols);
|
sort($new_cols);
|
||||||
|
|
||||||
return ($old_cols === $new_cols);
|
return ($old_cols === $new_cols);
|
||||||
|
case self::TYPE_COVER_IMAGE:
|
||||||
|
// At least for now, don't show these.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parent::shouldHide();
|
return parent::shouldHide();
|
||||||
|
|
|
@ -72,6 +72,7 @@ final class PhabricatorProjectApplication extends PhabricatorApplication {
|
||||||
'(?:query/(?P<queryKey>[^/]+)/)?'
|
'(?:query/(?P<queryKey>[^/]+)/)?'
|
||||||
=> 'PhabricatorProjectBoardViewController',
|
=> 'PhabricatorProjectBoardViewController',
|
||||||
'move/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectMoveController',
|
'move/(?P<id>[1-9]\d*)/' => 'PhabricatorProjectMoveController',
|
||||||
|
'cover/' => 'PhabricatorProjectCoverController',
|
||||||
'board/(?P<projectID>[1-9]\d*)/' => array(
|
'board/(?P<projectID>[1-9]\d*)/' => array(
|
||||||
'edit/(?:(?P<id>\d+)/)?'
|
'edit/(?:(?P<id>\d+)/)?'
|
||||||
=> 'PhabricatorProjectColumnEditController',
|
=> 'PhabricatorProjectColumnEditController',
|
||||||
|
|
|
@ -219,6 +219,9 @@ final class PhabricatorProjectBoardViewController
|
||||||
'projectPHID' => $project->getPHID(),
|
'projectPHID' => $project->getPHID(),
|
||||||
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
|
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
|
||||||
'createURI' => $this->getCreateURI(),
|
'createURI' => $this->getCreateURI(),
|
||||||
|
'uploadURI' => '/file/dropupload/',
|
||||||
|
'coverURI' => $this->getApplicationURI('cover/'),
|
||||||
|
'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(),
|
||||||
'order' => $this->sortKey,
|
'order' => $this->sortKey,
|
||||||
);
|
);
|
||||||
$this->initBehavior(
|
$this->initBehavior(
|
||||||
|
|
|
@ -147,4 +147,53 @@ abstract class PhabricatorProjectController extends PhabricatorController {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function newCardResponse($board_phid, $object_phid) {
|
||||||
|
$viewer = $this->getViewer();
|
||||||
|
|
||||||
|
$project = id(new PhabricatorProjectQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($board_phid))
|
||||||
|
->executeOne();
|
||||||
|
if (!$project) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload the object so it reflects edits which have been applied.
|
||||||
|
$object = id(new ManiphestTaskQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($object_phid))
|
||||||
|
->needProjectPHIDs(true)
|
||||||
|
->executeOne();
|
||||||
|
if (!$object) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$except_phids = array($board_phid);
|
||||||
|
if ($project->getHasSubprojects() || $project->getHasMilestones()) {
|
||||||
|
$descendants = id(new PhabricatorProjectQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withAncestorProjectPHIDs($except_phids)
|
||||||
|
->execute();
|
||||||
|
foreach ($descendants as $descendant) {
|
||||||
|
$except_phids[] = $descendant->getPHID();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->setObjects(array($object))
|
||||||
|
->setExcludedProjectPHIDs($except_phids);
|
||||||
|
|
||||||
|
$card = $rendering_engine->renderCard($object->getPHID());
|
||||||
|
|
||||||
|
$item = $card->getItem();
|
||||||
|
$item->addClass('phui-workcard');
|
||||||
|
|
||||||
|
return id(new AphrontAjaxResponse())
|
||||||
|
->setContent(
|
||||||
|
array(
|
||||||
|
'task' => $item,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorProjectCoverController
|
||||||
|
extends PhabricatorProjectController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $request->getViewer();
|
||||||
|
|
||||||
|
$request->validateCSRF();
|
||||||
|
|
||||||
|
$board_phid = $request->getStr('boardPHID');
|
||||||
|
$object_phid = $request->getStr('objectPHID');
|
||||||
|
$file_phid = $request->getStr('filePHID');
|
||||||
|
|
||||||
|
$object = id(new ManiphestTaskQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($object_phid))
|
||||||
|
->requireCapabilities(
|
||||||
|
array(
|
||||||
|
PhabricatorPolicyCapability::CAN_VIEW,
|
||||||
|
PhabricatorPolicyCapability::CAN_EDIT,
|
||||||
|
))
|
||||||
|
->executeOne();
|
||||||
|
if (!$object) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = id(new PhabricatorFileQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($file_phid))
|
||||||
|
->executeOne();
|
||||||
|
if (!$file) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
$xactions[] = id(new ManiphestTransaction())
|
||||||
|
->setTransactionType(ManiphestTransaction::TYPE_COVER_IMAGE)
|
||||||
|
->setNewValue($file->getPHID());
|
||||||
|
|
||||||
|
$editor = id(new ManiphestTransactionEditor())
|
||||||
|
->setActor($viewer)
|
||||||
|
->setContinueOnMissingFields(true)
|
||||||
|
->setContinueOnNoEffect(true)
|
||||||
|
->setContentSourceFromRequest($request);
|
||||||
|
|
||||||
|
$editor->applyTransactions($object, $xactions);
|
||||||
|
|
||||||
|
return $this->newCardResponse($board_phid, $object_phid);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -7,13 +7,14 @@ final class PhabricatorProjectMoveController
|
||||||
$viewer = $request->getViewer();
|
$viewer = $request->getViewer();
|
||||||
$id = $request->getURIData('id');
|
$id = $request->getURIData('id');
|
||||||
|
|
||||||
|
$request->validateCSRF();
|
||||||
|
|
||||||
$column_phid = $request->getStr('columnPHID');
|
$column_phid = $request->getStr('columnPHID');
|
||||||
$object_phid = $request->getStr('objectPHID');
|
$object_phid = $request->getStr('objectPHID');
|
||||||
$after_phid = $request->getStr('afterPHID');
|
$after_phid = $request->getStr('afterPHID');
|
||||||
$before_phid = $request->getStr('beforePHID');
|
$before_phid = $request->getStr('beforePHID');
|
||||||
$order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER);
|
$order = $request->getStr('order', PhabricatorProjectColumn::DEFAULT_ORDER);
|
||||||
|
|
||||||
|
|
||||||
$project = id(new PhabricatorProjectQuery())
|
$project = id(new PhabricatorProjectQuery())
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->requireCapabilities(
|
->requireCapabilities(
|
||||||
|
@ -175,39 +176,7 @@ final class PhabricatorProjectMoveController
|
||||||
|
|
||||||
$editor->applyTransactions($object, $xactions);
|
$editor->applyTransactions($object, $xactions);
|
||||||
|
|
||||||
// Reload the object so it reflects edits which have been applied.
|
return $this->newCardResponse($board_phid, $object_phid);
|
||||||
$object = id(new ManiphestTaskQuery())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->withPHIDs(array($object_phid))
|
|
||||||
->needProjectPHIDs(true)
|
|
||||||
->executeOne();
|
|
||||||
|
|
||||||
$except_phids = array($board_phid);
|
|
||||||
if ($project->getHasSubprojects() || $project->getHasMilestones()) {
|
|
||||||
$descendants = id(new PhabricatorProjectQuery())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->withAncestorProjectPHIDs($except_phids)
|
|
||||||
->execute();
|
|
||||||
foreach ($descendants as $descendant) {
|
|
||||||
$except_phids[] = $descendant->getPHID();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
|
|
||||||
->setViewer($viewer)
|
|
||||||
->setObjects(array($object))
|
|
||||||
->setExcludedProjectPHIDs($except_phids);
|
|
||||||
|
|
||||||
$card = $rendering_engine->renderCard($object->getPHID());
|
|
||||||
|
|
||||||
$item = $card->getItem();
|
|
||||||
$item->addClass('phui-workcard');
|
|
||||||
|
|
||||||
return id(new AphrontAjaxResponse())
|
|
||||||
->setContent(
|
|
||||||
array(
|
|
||||||
'task' => $item,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,10 @@
|
||||||
width: 263px;
|
width: 263px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.phui-workcard.phui-object-item.phui-workcard-upload-target {
|
||||||
|
background-color: {$sh-greenbackground};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* - Draggable Colors --------------------------------------------------------*/
|
/* - Draggable Colors --------------------------------------------------------*/
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
* javelin-stratcom
|
* javelin-stratcom
|
||||||
* javelin-workflow
|
* javelin-workflow
|
||||||
* phabricator-draggable-list
|
* phabricator-draggable-list
|
||||||
|
* phabricator-drag-and-drop-file-upload
|
||||||
*/
|
*/
|
||||||
|
|
||||||
JX.behavior('project-boards', function(config, statics) {
|
JX.behavior('project-boards', function(config, statics) {
|
||||||
|
@ -348,6 +349,39 @@ JX.behavior('project-boards', function(config, statics) {
|
||||||
init_board();
|
init_board();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (JX.PhabricatorDragAndDropFileUpload.isSupported()) {
|
||||||
|
var drop = new JX.PhabricatorDragAndDropFileUpload('project-card')
|
||||||
|
.setURI(config.uploadURI)
|
||||||
|
.setChunkThreshold(config.chunkThreshold);
|
||||||
|
|
||||||
|
drop.listen('didBeginDrag', function(node) {
|
||||||
|
JX.DOM.alterClass(node, 'phui-workcard-upload-target', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
drop.listen('didEndDrag', function(node) {
|
||||||
|
JX.DOM.alterClass(node, 'phui-workcard-upload-target', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
drop.listen('didUpload', function(file) {
|
||||||
|
var node = file.getTargetNode();
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
boardPHID: statics.projectPHID,
|
||||||
|
objectPHID: JX.Stratcom.getData(node).objectPHID,
|
||||||
|
filePHID: file.getPHID()
|
||||||
|
};
|
||||||
|
|
||||||
|
new JX.Workflow(config.coverURI, data)
|
||||||
|
.setHandler(function(r) {
|
||||||
|
JX.DOM.replace(node, JX.$H(r.task));
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
drop.start();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,12 @@
|
||||||
|
|
||||||
JX.install('PhabricatorDragAndDropFileUpload', {
|
JX.install('PhabricatorDragAndDropFileUpload', {
|
||||||
|
|
||||||
construct : function(node) {
|
construct : function(target) {
|
||||||
this._node = node;
|
if (JX.DOM.isNode(target)) {
|
||||||
|
this._node = target;
|
||||||
|
} else {
|
||||||
|
this._sigil = target;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
events : [
|
events : [
|
||||||
|
@ -39,6 +43,7 @@ JX.install('PhabricatorDragAndDropFileUpload', {
|
||||||
|
|
||||||
members : {
|
members : {
|
||||||
_node : null,
|
_node : null,
|
||||||
|
_sigil: null,
|
||||||
_depth : 0,
|
_depth : 0,
|
||||||
_isEnabled: false,
|
_isEnabled: false,
|
||||||
|
|
||||||
|
@ -53,18 +58,21 @@ JX.install('PhabricatorDragAndDropFileUpload', {
|
||||||
|
|
||||||
_updateDepth : function(delta) {
|
_updateDepth : function(delta) {
|
||||||
if (this._depth === 0 && delta > 0) {
|
if (this._depth === 0 && delta > 0) {
|
||||||
this.invoke('didBeginDrag');
|
this.invoke('didBeginDrag', this._getTarget());
|
||||||
}
|
}
|
||||||
|
|
||||||
this._depth += delta;
|
this._depth += delta;
|
||||||
|
|
||||||
if (this._depth === 0 && delta < 0) {
|
if (this._depth === 0 && delta < 0) {
|
||||||
this.invoke('didEndDrag');
|
this.invoke('didEndDrag', this._getTarget());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
start : function() {
|
_getTarget: function() {
|
||||||
|
return this._target || this._node;
|
||||||
|
},
|
||||||
|
|
||||||
|
start : function() {
|
||||||
|
|
||||||
// TODO: move this to JX.DOM.contains()?
|
// TODO: move this to JX.DOM.contains()?
|
||||||
function contains(container, child) {
|
function contains(container, child) {
|
||||||
|
@ -80,87 +88,87 @@ JX.install('PhabricatorDragAndDropFileUpload', {
|
||||||
|
|
||||||
// Firefox has some issues sometimes; implement this click handler so
|
// Firefox has some issues sometimes; implement this click handler so
|
||||||
// the user can recover. See T5188.
|
// the user can recover. See T5188.
|
||||||
JX.DOM.listen(
|
var on_click = JX.bind(this, function (e) {
|
||||||
this._node,
|
if (!this.getIsEnabled()) {
|
||||||
'click',
|
return;
|
||||||
null,
|
}
|
||||||
JX.bind(this, function (e) {
|
|
||||||
if (!this.getIsEnabled()) {
|
if (this._depth) {
|
||||||
return;
|
e.kill();
|
||||||
}
|
// Force depth to 0.
|
||||||
if (this._depth) {
|
this._updateDepth(-this._depth);
|
||||||
e.kill();
|
}
|
||||||
// Force depth to 0.
|
});
|
||||||
this._updateDepth(-this._depth);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
// We track depth so that the _node may have children inside of it and
|
// We track depth so that the _node may have children inside of it and
|
||||||
// not become unselected when they are dragged over.
|
// not become unselected when they are dragged over.
|
||||||
JX.DOM.listen(
|
var on_dragenter = JX.bind(this, function(e) {
|
||||||
this._node,
|
if (!this.getIsEnabled()) {
|
||||||
'dragenter',
|
return;
|
||||||
null,
|
}
|
||||||
JX.bind(this, function(e) {
|
|
||||||
if (!this.getIsEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contains(this._node, e.getTarget())) {
|
if (!this._node && !this._depth) {
|
||||||
this._updateDepth(1);
|
this._target = e.getNode(this._sigil);
|
||||||
}
|
}
|
||||||
}));
|
|
||||||
|
|
||||||
JX.DOM.listen(
|
if (contains(this._getTarget(), e.getTarget())) {
|
||||||
this._node,
|
this._updateDepth(1);
|
||||||
'dragleave',
|
}
|
||||||
null,
|
});
|
||||||
JX.bind(this, function(e) {
|
|
||||||
if (!this.getIsEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contains(this._node, e.getTarget())) {
|
var on_dragleave = JX.bind(this, function(e) {
|
||||||
this._updateDepth(-1);
|
if (!this.getIsEnabled()) {
|
||||||
}
|
return;
|
||||||
}));
|
}
|
||||||
|
|
||||||
JX.DOM.listen(
|
if (contains(this._getTarget(), e.getTarget())) {
|
||||||
this._node,
|
this._updateDepth(-1);
|
||||||
'dragover',
|
}
|
||||||
null,
|
});
|
||||||
JX.bind(this, function(e) {
|
|
||||||
if (!this.getIsEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: We must set this, or Chrome refuses to drop files from the
|
var on_dragover = JX.bind(this, function(e) {
|
||||||
// download shelf.
|
if (!this.getIsEnabled()) {
|
||||||
e.getRawEvent().dataTransfer.dropEffect = 'copy';
|
return;
|
||||||
e.kill();
|
}
|
||||||
}));
|
|
||||||
|
|
||||||
JX.DOM.listen(
|
// NOTE: We must set this, or Chrome refuses to drop files from the
|
||||||
this._node,
|
// download shelf.
|
||||||
'drop',
|
e.getRawEvent().dataTransfer.dropEffect = 'copy';
|
||||||
null,
|
e.kill();
|
||||||
JX.bind(this, function(e) {
|
});
|
||||||
if (!this.getIsEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.kill();
|
var on_drop = JX.bind(this, function(e) {
|
||||||
|
if (!this.getIsEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var files = e.getRawEvent().dataTransfer.files;
|
e.kill();
|
||||||
for (var ii = 0; ii < files.length; ii++) {
|
|
||||||
this._sendRequest(files[ii]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force depth to 0.
|
var files = e.getRawEvent().dataTransfer.files;
|
||||||
this._updateDepth(-this._depth);
|
for (var ii = 0; ii < files.length; ii++) {
|
||||||
}));
|
this._sendRequest(files[ii]);
|
||||||
|
}
|
||||||
|
|
||||||
if (JX.PhabricatorDragAndDropFileUpload.isPasteSupported()) {
|
// Force depth to 0.
|
||||||
|
this._updateDepth(-this._depth);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this._node) {
|
||||||
|
JX.DOM.listen(this._node, 'click', null, on_click);
|
||||||
|
JX.DOM.listen(this._node, 'dragenter', null, on_dragenter);
|
||||||
|
JX.DOM.listen(this._node, 'dragleave', null, on_dragleave);
|
||||||
|
JX.DOM.listen(this._node, 'dragover', null, on_dragover);
|
||||||
|
JX.DOM.listen(this._node, 'drop', null, on_drop);
|
||||||
|
} else {
|
||||||
|
JX.Stratcom.listen('click', this._sigil, on_click);
|
||||||
|
JX.Stratcom.listen('dragenter', this._sigil, on_dragenter);
|
||||||
|
JX.Stratcom.listen('dragleave', this._sigil, on_dragleave);
|
||||||
|
JX.Stratcom.listen('dragover', this._sigil, on_dragover);
|
||||||
|
JX.Stratcom.listen('drop', this._sigil, on_drop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JX.PhabricatorDragAndDropFileUpload.isPasteSupported() &&
|
||||||
|
this._node) {
|
||||||
JX.DOM.listen(
|
JX.DOM.listen(
|
||||||
this._node,
|
this._node,
|
||||||
'paste',
|
'paste',
|
||||||
|
@ -399,6 +407,7 @@ JX.install('PhabricatorDragAndDropFileUpload', {
|
||||||
.setURI(r.uri)
|
.setURI(r.uri)
|
||||||
.setMarkup(r.html)
|
.setMarkup(r.html)
|
||||||
.setStatus('done')
|
.setStatus('done')
|
||||||
|
.setTargetNode(this._getTarget())
|
||||||
.update();
|
.update();
|
||||||
|
|
||||||
this.invoke('didUpload', file);
|
this.invoke('didUpload', file);
|
||||||
|
|
|
@ -23,6 +23,7 @@ JX.install('PhabricatorFileUpload', {
|
||||||
URI: null,
|
URI: null,
|
||||||
status: null,
|
status: null,
|
||||||
markup: null,
|
markup: null,
|
||||||
|
targetNode: null,
|
||||||
error: null
|
error: null
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue