mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 23:02:42 +01:00
Conpherence - add crop
Summary: mainly, this adds the image cropper - yay! - also removes the file image from the handle stuff I added in V1. now we do all this crazy photo stuff. Test Plan: - uploaded a photo by dragging to header and noted 120 x 80 showed up on reload - uploaded a photo by dragging to edit dialogue spot and noted 120 x 80 showed up on reload - cropped a photo - noted it cropped right - cropped a photo again and again and again - seems like it crops okay Reviewers: epriestley, chad Reviewed By: epriestley CC: aran, Korvin Maniphest Tasks: T2418, T2399 Differential Revision: https://secure.phabricator.com/D4790
This commit is contained in:
parent
fb7d5d17a2
commit
1cde41b994
22 changed files with 803 additions and 149 deletions
6
resources/sql/patches/20130131.conpherencepics.sql
Normal file
6
resources/sql/patches/20130131.conpherencepics.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
ALTER TABLE {$NAMESPACE}_conpherence.conpherence_thread
|
||||
DROP imagePHID,
|
||||
ADD imagePHIDs LONGTEXT COLLATE utf8_bin NOT NULL AFTER title;
|
||||
|
||||
UPDATE {$NAMESPACE}_conpherence.conpherence_thread
|
||||
SET imagePHIDs = '{}' WHERE imagePHIDs = '';
|
|
@ -639,7 +639,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'aphront-form-view-css' =>
|
||||
array(
|
||||
'uri' => '/res/428fbcd8/rsrc/css/aphront/form-view.css',
|
||||
'uri' => '/res/ec323d34/rsrc/css/aphront/form-view.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -748,7 +748,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'conpherence-header-pane-css' =>
|
||||
array(
|
||||
'uri' => '/res/4b8aebd2/rsrc/css/application/conpherence/header-pane.css',
|
||||
'uri' => '/res/11c32adc/rsrc/css/application/conpherence/header-pane.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -775,7 +775,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'conpherence-update-css' =>
|
||||
array(
|
||||
'uri' => '/res/8e4757b5/rsrc/css/application/conpherence/update.css',
|
||||
'uri' => '/res/92094ed7/rsrc/css/application/conpherence/update.css',
|
||||
'type' => 'css',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -1042,6 +1042,19 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/js/application/core/behavior-tokenizer.js',
|
||||
),
|
||||
'javelin-behavior-aphront-crop' =>
|
||||
array(
|
||||
'uri' => '/res/cda1eace/rsrc/js/application/core/behavior-crop.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
1 => 'javelin-dom',
|
||||
2 => 'javelin-vector',
|
||||
3 => 'javelin-magical-init',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/core/behavior-crop.js',
|
||||
),
|
||||
'javelin-behavior-aphront-drag-and-drop' =>
|
||||
array(
|
||||
'uri' => '/res/3d809b40/rsrc/js/application/core/behavior-drag-and-drop.js',
|
||||
|
@ -1106,6 +1119,19 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'disk' => '/rsrc/js/application/diffusion/behavior-audit-preview.js',
|
||||
),
|
||||
'javelin-behavior-conpherence-drag-and-drop-photo' =>
|
||||
array(
|
||||
'uri' => '/res/9e3eb1cd/rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
0 => 'javelin-behavior',
|
||||
1 => 'javelin-dom',
|
||||
2 => 'javelin-workflow',
|
||||
3 => 'phabricator-drag-and-drop-file-upload',
|
||||
),
|
||||
'disk' => '/rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js',
|
||||
),
|
||||
'javelin-behavior-conpherence-init' =>
|
||||
array(
|
||||
'uri' => '/res/bd911b43/rsrc/js/application/conpherence/behavior-init.js',
|
||||
|
@ -1863,7 +1889,7 @@ celerity_register_resource_map(array(
|
|||
),
|
||||
'javelin-behavior-pholio-mock-view' =>
|
||||
array(
|
||||
'uri' => '/res/1364872e/rsrc/js/application/pholio/behavior-pholio-mock-view.js',
|
||||
'uri' => '/res/5dd61f73/rsrc/js/application/pholio/behavior-pholio-mock-view.js',
|
||||
'type' => 'js',
|
||||
'requires' =>
|
||||
array(
|
||||
|
@ -3396,7 +3422,7 @@ celerity_register_resource_map(array(
|
|||
), array(
|
||||
'packages' =>
|
||||
array(
|
||||
'7d347135' =>
|
||||
'23503cb9' =>
|
||||
array(
|
||||
'name' => 'core.pkg.css',
|
||||
'symbols' =>
|
||||
|
@ -3440,7 +3466,7 @@ celerity_register_resource_map(array(
|
|||
36 => 'phabricator-object-item-list-view-css',
|
||||
37 => 'global-drag-and-drop-css',
|
||||
),
|
||||
'uri' => '/res/pkg/7d347135/core.pkg.css',
|
||||
'uri' => '/res/pkg/23503cb9/core.pkg.css',
|
||||
'type' => 'css',
|
||||
),
|
||||
'58743fec' =>
|
||||
|
@ -3630,19 +3656,19 @@ celerity_register_resource_map(array(
|
|||
'reverse' =>
|
||||
array(
|
||||
'aphront-attached-file-view-css' => 'e30a3fa8',
|
||||
'aphront-crumbs-view-css' => '7d347135',
|
||||
'aphront-dialog-view-css' => '7d347135',
|
||||
'aphront-error-view-css' => '7d347135',
|
||||
'aphront-form-view-css' => '7d347135',
|
||||
'aphront-crumbs-view-css' => '23503cb9',
|
||||
'aphront-dialog-view-css' => '23503cb9',
|
||||
'aphront-error-view-css' => '23503cb9',
|
||||
'aphront-form-view-css' => '23503cb9',
|
||||
'aphront-headsup-action-list-view-css' => 'ec01d039',
|
||||
'aphront-headsup-view-css' => '7d347135',
|
||||
'aphront-list-filter-view-css' => '7d347135',
|
||||
'aphront-pager-view-css' => '7d347135',
|
||||
'aphront-panel-view-css' => '7d347135',
|
||||
'aphront-table-view-css' => '7d347135',
|
||||
'aphront-tokenizer-control-css' => '7d347135',
|
||||
'aphront-tooltip-css' => '7d347135',
|
||||
'aphront-typeahead-control-css' => '7d347135',
|
||||
'aphront-headsup-view-css' => '23503cb9',
|
||||
'aphront-list-filter-view-css' => '23503cb9',
|
||||
'aphront-pager-view-css' => '23503cb9',
|
||||
'aphront-panel-view-css' => '23503cb9',
|
||||
'aphront-table-view-css' => '23503cb9',
|
||||
'aphront-tokenizer-control-css' => '23503cb9',
|
||||
'aphront-tooltip-css' => '23503cb9',
|
||||
'aphront-typeahead-control-css' => '23503cb9',
|
||||
'differential-changeset-view-css' => 'ec01d039',
|
||||
'differential-core-view-css' => 'ec01d039',
|
||||
'differential-inline-comment-editor' => '95d0d865',
|
||||
|
@ -3656,7 +3682,7 @@ celerity_register_resource_map(array(
|
|||
'differential-table-of-contents-css' => 'ec01d039',
|
||||
'diffusion-commit-view-css' => 'c8ce2d88',
|
||||
'diffusion-icons-css' => 'c8ce2d88',
|
||||
'global-drag-and-drop-css' => '7d347135',
|
||||
'global-drag-and-drop-css' => '23503cb9',
|
||||
'inline-comment-summary-css' => 'ec01d039',
|
||||
'javelin-aphlict' => '58743fec',
|
||||
'javelin-behavior' => '88225b70',
|
||||
|
@ -3726,48 +3752,48 @@ celerity_register_resource_map(array(
|
|||
'javelin-util' => '88225b70',
|
||||
'javelin-vector' => '88225b70',
|
||||
'javelin-workflow' => '88225b70',
|
||||
'lightbox-attachment-css' => '7d347135',
|
||||
'lightbox-attachment-css' => '23503cb9',
|
||||
'maniphest-task-summary-css' => 'e30a3fa8',
|
||||
'maniphest-transaction-detail-css' => 'e30a3fa8',
|
||||
'phabricator-busy' => '58743fec',
|
||||
'phabricator-content-source-view-css' => 'ec01d039',
|
||||
'phabricator-core-buttons-css' => '7d347135',
|
||||
'phabricator-core-css' => '7d347135',
|
||||
'phabricator-crumbs-view-css' => '7d347135',
|
||||
'phabricator-directory-css' => '7d347135',
|
||||
'phabricator-core-buttons-css' => '23503cb9',
|
||||
'phabricator-core-css' => '23503cb9',
|
||||
'phabricator-crumbs-view-css' => '23503cb9',
|
||||
'phabricator-directory-css' => '23503cb9',
|
||||
'phabricator-drag-and-drop-file-upload' => '95d0d865',
|
||||
'phabricator-dropdown-menu' => '58743fec',
|
||||
'phabricator-file-upload' => '58743fec',
|
||||
'phabricator-filetree-view-css' => '7d347135',
|
||||
'phabricator-flag-css' => '7d347135',
|
||||
'phabricator-form-view-css' => '7d347135',
|
||||
'phabricator-header-view-css' => '7d347135',
|
||||
'phabricator-jump-nav' => '7d347135',
|
||||
'phabricator-filetree-view-css' => '23503cb9',
|
||||
'phabricator-flag-css' => '23503cb9',
|
||||
'phabricator-form-view-css' => '23503cb9',
|
||||
'phabricator-header-view-css' => '23503cb9',
|
||||
'phabricator-jump-nav' => '23503cb9',
|
||||
'phabricator-keyboard-shortcut' => '58743fec',
|
||||
'phabricator-keyboard-shortcut-manager' => '58743fec',
|
||||
'phabricator-main-menu-view' => '7d347135',
|
||||
'phabricator-main-menu-view' => '23503cb9',
|
||||
'phabricator-menu-item' => '58743fec',
|
||||
'phabricator-nav-view-css' => '7d347135',
|
||||
'phabricator-nav-view-css' => '23503cb9',
|
||||
'phabricator-notification' => '58743fec',
|
||||
'phabricator-notification-css' => '7d347135',
|
||||
'phabricator-notification-menu-css' => '7d347135',
|
||||
'phabricator-object-item-list-view-css' => '7d347135',
|
||||
'phabricator-notification-css' => '23503cb9',
|
||||
'phabricator-notification-menu-css' => '23503cb9',
|
||||
'phabricator-object-item-list-view-css' => '23503cb9',
|
||||
'phabricator-object-selector-css' => 'ec01d039',
|
||||
'phabricator-paste-file-upload' => '58743fec',
|
||||
'phabricator-prefab' => '58743fec',
|
||||
'phabricator-project-tag-css' => 'e30a3fa8',
|
||||
'phabricator-remarkup-css' => '7d347135',
|
||||
'phabricator-remarkup-css' => '23503cb9',
|
||||
'phabricator-shaped-request' => '95d0d865',
|
||||
'phabricator-side-menu-view-css' => '7d347135',
|
||||
'phabricator-standard-page-view' => '7d347135',
|
||||
'phabricator-side-menu-view-css' => '23503cb9',
|
||||
'phabricator-standard-page-view' => '23503cb9',
|
||||
'phabricator-textareautils' => '58743fec',
|
||||
'phabricator-tooltip' => '58743fec',
|
||||
'phabricator-transaction-view-css' => '7d347135',
|
||||
'phabricator-zindex-css' => '7d347135',
|
||||
'sprite-apps-large-css' => '7d347135',
|
||||
'sprite-gradient-css' => '7d347135',
|
||||
'sprite-icon-css' => '7d347135',
|
||||
'sprite-menu-css' => '7d347135',
|
||||
'syntax-highlighting-css' => '7d347135',
|
||||
'phabricator-transaction-view-css' => '23503cb9',
|
||||
'phabricator-zindex-css' => '23503cb9',
|
||||
'sprite-apps-large-css' => '23503cb9',
|
||||
'sprite-gradient-css' => '23503cb9',
|
||||
'sprite-icon-css' => '23503cb9',
|
||||
'sprite-menu-css' => '23503cb9',
|
||||
'syntax-highlighting-css' => '23503cb9',
|
||||
),
|
||||
));
|
||||
|
|
|
@ -32,6 +32,7 @@ phutil_register_library_map(array(
|
|||
'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php',
|
||||
'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php',
|
||||
'AphrontFormControl' => 'view/form/control/AphrontFormControl.php',
|
||||
'AphrontFormCropControl' => 'view/form/control/AphrontFormCropControl.php',
|
||||
'AphrontFormDateControl' => 'view/form/control/AphrontFormDateControl.php',
|
||||
'AphrontFormDividerControl' => 'view/form/control/AphrontFormDividerControl.php',
|
||||
'AphrontFormDragAndDropUploadControl' => 'view/form/control/AphrontFormDragAndDropUploadControl.php',
|
||||
|
@ -201,6 +202,8 @@ phutil_register_library_map(array(
|
|||
'ConpherenceController' => 'applications/conpherence/controller/ConpherenceController.php',
|
||||
'ConpherenceDAO' => 'applications/conpherence/storage/ConpherenceDAO.php',
|
||||
'ConpherenceEditor' => 'applications/conpherence/editor/ConpherenceEditor.php',
|
||||
'ConpherenceFormDragAndDropUploadControl' => 'applications/conpherence/view/ConpherenceFormDragAndDropUploadControl.php',
|
||||
'ConpherenceImageData' => 'applications/conpherence/constants/ConpherenceImageData.php',
|
||||
'ConpherenceListController' => 'applications/conpherence/controller/ConpherenceListController.php',
|
||||
'ConpherenceMenuItemView' => 'applications/conpherence/view/ConpherenceMenuItemView.php',
|
||||
'ConpherenceNewController' => 'applications/conpherence/controller/ConpherenceNewController.php',
|
||||
|
@ -1534,6 +1537,7 @@ phutil_register_library_map(array(
|
|||
'AphrontFileResponse' => 'AphrontResponse',
|
||||
'AphrontFormCheckboxControl' => 'AphrontFormControl',
|
||||
'AphrontFormControl' => 'AphrontView',
|
||||
'AphrontFormCropControl' => 'AphrontFormControl',
|
||||
'AphrontFormDateControl' => 'AphrontFormControl',
|
||||
'AphrontFormDividerControl' => 'AphrontFormControl',
|
||||
'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl',
|
||||
|
@ -1689,6 +1693,8 @@ phutil_register_library_map(array(
|
|||
'ConpherenceController' => 'PhabricatorController',
|
||||
'ConpherenceDAO' => 'PhabricatorLiskDAO',
|
||||
'ConpherenceEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'ConpherenceFormDragAndDropUploadControl' => 'AphrontFormControl',
|
||||
'ConpherenceImageData' => 'ConpherenceConstants',
|
||||
'ConpherenceListController' => 'ConpherenceController',
|
||||
'ConpherenceMenuItemView' => 'AphrontTagView',
|
||||
'ConpherenceNewController' => 'ConpherenceController',
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
final class ConpherenceImageData extends ConpherenceConstants {
|
||||
|
||||
const SIZE_ORIG = 'original';
|
||||
const SIZE_HEAD = 'header';
|
||||
|
||||
const HEAD_WIDTH = 120;
|
||||
const HEAD_HEIGHT = 80;
|
||||
|
||||
}
|
|
@ -8,6 +8,7 @@ final class ConpherenceTransactionType extends ConpherenceConstants {
|
|||
const TYPE_FILES = 'files';
|
||||
const TYPE_TITLE = 'title';
|
||||
const TYPE_PICTURE = 'picture';
|
||||
const TYPE_PICTURE_CROP = 'picture-crop';
|
||||
const TYPE_PARTICIPANTS = 'participants';
|
||||
|
||||
}
|
||||
|
|
|
@ -131,7 +131,10 @@ abstract class ConpherenceController extends PhabricatorController {
|
|||
$user = $this->getRequest()->getUser();
|
||||
foreach ($conpherences as $conpherence) {
|
||||
$uri = $this->getApplicationURI('view/'.$conpherence->getID().'/');
|
||||
$data = $conpherence->getDisplayData($user);
|
||||
$data = $conpherence->getDisplayData(
|
||||
$user,
|
||||
null
|
||||
);
|
||||
$title = $data['title'];
|
||||
$subtitle = $data['subtitle'];
|
||||
$unread_count = $data['unread_count'];
|
||||
|
@ -220,6 +223,14 @@ abstract class ConpherenceController extends PhabricatorController {
|
|||
'form_pane' => 'conpherence-form'
|
||||
)
|
||||
);
|
||||
Javelin::initBehavior('conpherence-drag-and-drop-photo',
|
||||
array(
|
||||
'target' => 'conpherence-header-pane',
|
||||
'form_pane' => 'conpherence-form',
|
||||
'upload_uri' => '/file/dropupload/',
|
||||
'activated_class' => 'conpherence-header-upload-photo',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ final class ConpherenceUpdateController extends
|
|||
$conpherence = id(new ConpherenceThreadQuery())
|
||||
->setViewer($user)
|
||||
->withIDs(array($conpherence_id))
|
||||
->needOrigPics(true)
|
||||
->needHeaderPics(true)
|
||||
->executeOne();
|
||||
$supported_formats = PhabricatorFile::getTransformableImageFormats();
|
||||
|
||||
|
@ -59,31 +61,63 @@ final class ConpherenceUpdateController extends
|
|||
break;
|
||||
case 'metadata':
|
||||
$xactions = array();
|
||||
$images = $request->getArr('image');
|
||||
if ($images) {
|
||||
// just take the first one
|
||||
$file_phid = reset($images);
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
$top = $request->getInt('image_y');
|
||||
$left = $request->getInt('image_x');
|
||||
$file_id = $request->getInt('file_id');
|
||||
if ($file_id) {
|
||||
$orig_file = id(new PhabricatorFileQuery())
|
||||
->setViewer($user)
|
||||
->withPHIDs(array($file_phid))
|
||||
->withIDs(array($file_id))
|
||||
->executeOne();
|
||||
$okay = $file->isTransformableImage();
|
||||
$okay = $orig_file->isTransformableImage();
|
||||
if ($okay) {
|
||||
$xformer = new PhabricatorImageTransformer();
|
||||
$xformed = $xformer->executeThumbTransform(
|
||||
$file,
|
||||
$x = 50,
|
||||
$y = 50);
|
||||
$image_phid = $xformed->getPHID();
|
||||
$xactions[] = id(new ConpherenceTransaction())
|
||||
->setTransactionType(ConpherenceTransactionType::TYPE_PICTURE)
|
||||
->setNewValue($image_phid);
|
||||
->setNewValue($orig_file->getPHID());
|
||||
// do 2 transformations "crudely"
|
||||
$xformer = new PhabricatorImageTransformer();
|
||||
$header_file = $xformer->executeConpherenceTransform(
|
||||
$orig_file,
|
||||
0,
|
||||
0,
|
||||
ConpherenceImageData::HEAD_WIDTH,
|
||||
ConpherenceImageData::HEAD_HEIGHT
|
||||
);
|
||||
// this is handled outside the editor for now. no particularly
|
||||
// good reason to move it inside
|
||||
$conpherence->setImagePHIDs(
|
||||
array(
|
||||
ConpherenceImageData::SIZE_HEAD => $header_file->getPHID(),
|
||||
)
|
||||
);
|
||||
$conpherence->setImages(
|
||||
array(
|
||||
ConpherenceImageData::SIZE_HEAD => $header_file,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$e_file[] = $file;
|
||||
$e_file[] = $orig_file;
|
||||
$errors[] =
|
||||
pht('This server only supports these image formats: %s.',
|
||||
implode(', ', $supported_formats));
|
||||
}
|
||||
} else if ($top !== null || $left !== null) {
|
||||
$file = $conpherence->getImage(ConpherenceImageData::SIZE_ORIG);
|
||||
$xformer = new PhabricatorImageTransformer();
|
||||
$xformed = $xformer->executeConpherenceTransform(
|
||||
$file,
|
||||
$top,
|
||||
$left,
|
||||
ConpherenceImageData::HEAD_WIDTH,
|
||||
ConpherenceImageData::HEAD_HEIGHT
|
||||
);
|
||||
$image_phid = $xformed->getPHID();
|
||||
|
||||
$xactions[] = id(new ConpherenceTransaction())
|
||||
->setTransactionType(
|
||||
ConpherenceTransactionType::TYPE_PICTURE_CROP
|
||||
)
|
||||
->setNewValue($image_phid);
|
||||
}
|
||||
$title = $request->getStr('title');
|
||||
if ($title != $conpherence->getTitle()) {
|
||||
|
@ -131,25 +165,43 @@ final class ConpherenceUpdateController extends
|
|||
->setLabel(pht('Title'))
|
||||
->setName('title')
|
||||
->setValue($conpherence->getTitle())
|
||||
)
|
||||
);
|
||||
|
||||
$image = $conpherence->getImage(ConpherenceImageData::SIZE_ORIG);
|
||||
if ($image) {
|
||||
$form
|
||||
->appendChild(
|
||||
id(new AphrontFormMarkupControl())
|
||||
->setLabel(pht('Image'))
|
||||
->setValue(phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $conpherence->loadImageURI(),
|
||||
'src' =>
|
||||
$conpherence->loadImageURI(ConpherenceImageData::SIZE_HEAD),
|
||||
))
|
||||
)
|
||||
)
|
||||
->appendChild(
|
||||
id(new AphrontFormDragAndDropUploadControl())
|
||||
id(new AphrontFormCropControl())
|
||||
->setLabel(pht('Crop Image'))
|
||||
->setValue($image)
|
||||
->setWidth(ConpherenceImageData::HEAD_WIDTH)
|
||||
->setHeight(ConpherenceImageData::HEAD_HEIGHT)
|
||||
)
|
||||
->appendChild(
|
||||
id(new ConpherenceFormDragAndDropUploadControl())
|
||||
->setLabel(pht('Change Image'))
|
||||
->setName('image')
|
||||
->setValue($e_file)
|
||||
->setCaption('Supported formats: '.implode(', ', $supported_formats))
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
$form
|
||||
->appendChild(
|
||||
id(new ConpherenceFormDragAndDropUploadControl())
|
||||
->setLabel(pht('Image'))
|
||||
);
|
||||
}
|
||||
|
||||
require_celerity_resource('conpherence-update-css');
|
||||
return id(new AphrontDialogResponse())
|
||||
->setDialog(
|
||||
|
|
|
@ -45,6 +45,7 @@ final class ConpherenceViewController extends
|
|||
->setViewer($user)
|
||||
->withIDs(array($conpherence_id))
|
||||
->needWidgetData(true)
|
||||
->needHeaderPics(true)
|
||||
->executeOne();
|
||||
$this->setConpherence($conpherence);
|
||||
|
||||
|
@ -67,23 +68,34 @@ final class ConpherenceViewController extends
|
|||
require_celerity_resource('conpherence-header-pane-css');
|
||||
$user = $this->getRequest()->getUser();
|
||||
$conpherence = $this->getConpherence();
|
||||
$display_data = $conpherence->getDisplayData($user);
|
||||
$display_data = $conpherence->getDisplayData(
|
||||
$user,
|
||||
ConpherenceImageData::SIZE_HEAD
|
||||
);
|
||||
$edit_href = $this->getApplicationURI('update/'.$conpherence->getID().'/');
|
||||
$class_mod = $display_data['image_class'];
|
||||
|
||||
$header =
|
||||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'upload-photo'
|
||||
),
|
||||
pht('Drop photo here to change this Conpherence photo.')
|
||||
).
|
||||
javelin_tag(
|
||||
'a',
|
||||
array(
|
||||
'class' => 'edit',
|
||||
'href' => $edit_href,
|
||||
'sigil' => 'workflow',
|
||||
'sigil' => 'workflow edit-action',
|
||||
),
|
||||
''
|
||||
).
|
||||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'header-image',
|
||||
'class' => $class_mod.'header-image',
|
||||
'style' => 'background-image: url('.$display_data['image'].');'
|
||||
),
|
||||
''
|
||||
|
@ -91,14 +103,14 @@ final class ConpherenceViewController extends
|
|||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'title',
|
||||
'class' => $class_mod.'title',
|
||||
),
|
||||
$display_data['title']
|
||||
).
|
||||
phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'subtitle',
|
||||
'class' => $class_mod.'subtitle',
|
||||
),
|
||||
$display_data['subtitle']
|
||||
);
|
||||
|
|
|
@ -48,6 +48,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
|
||||
$types[] = ConpherenceTransactionType::TYPE_TITLE;
|
||||
$types[] = ConpherenceTransactionType::TYPE_PICTURE;
|
||||
$types[] = ConpherenceTransactionType::TYPE_PICTURE_CROP;
|
||||
$types[] = ConpherenceTransactionType::TYPE_PARTICIPANTS;
|
||||
$types[] = ConpherenceTransactionType::TYPE_FILES;
|
||||
|
||||
|
@ -62,7 +63,9 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
case ConpherenceTransactionType::TYPE_TITLE:
|
||||
return $object->getTitle();
|
||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
return $object->getImagePHID();
|
||||
return $object->getImagePHID(ConpherenceImageData::SIZE_ORIG);
|
||||
case ConpherenceTransactionType::TYPE_PICTURE_CROP:
|
||||
return $object->getImagePHID(ConpherenceImageData::SIZE_HEAD);
|
||||
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
||||
return $object->getParticipantPHIDs();
|
||||
case ConpherenceTransactionType::TYPE_FILES:
|
||||
|
@ -77,6 +80,7 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
switch ($xaction->getTransactionType()) {
|
||||
case ConpherenceTransactionType::TYPE_TITLE:
|
||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
case ConpherenceTransactionType::TYPE_PICTURE_CROP:
|
||||
return $xaction->getNewValue();
|
||||
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
||||
case ConpherenceTransactionType::TYPE_FILES:
|
||||
|
@ -93,7 +97,16 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor {
|
|||
$object->setTitle($xaction->getNewValue());
|
||||
break;
|
||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
$object->setImagePHID($xaction->getNewValue());
|
||||
$object->setImagePHID(
|
||||
$xaction->getNewValue(),
|
||||
ConpherenceImageData::SIZE_ORIG
|
||||
);
|
||||
break;
|
||||
case ConpherenceTransactionType::TYPE_PICTURE_CROP:
|
||||
$object->setImagePHID(
|
||||
$xaction->getNewValue(),
|
||||
ConpherenceImageData::SIZE_HEAD
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,18 @@ final class ConpherenceThreadQuery
|
|||
private $phids;
|
||||
private $ids;
|
||||
private $needWidgetData;
|
||||
private $needHeaderPics;
|
||||
private $needOrigPics;
|
||||
|
||||
public function needOrigPics($need_orig_pics) {
|
||||
$this->needOrigPics = $need_orig_pics;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needHeaderPics($need_header_pics) {
|
||||
$this->needHeaderPics = $need_header_pics;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needWidgetData($need_widget_data) {
|
||||
$this->needWidgetData = $need_widget_data;
|
||||
|
@ -47,6 +59,12 @@ final class ConpherenceThreadQuery
|
|||
if ($this->needWidgetData) {
|
||||
$this->loadWidgetData($conpherences);
|
||||
}
|
||||
if ($this->needOrigPics) {
|
||||
$this->loadOrigPics($conpherences);
|
||||
}
|
||||
if ($this->needHeaderPics) {
|
||||
$this->loadHeaderPics($conpherences);
|
||||
}
|
||||
}
|
||||
|
||||
return $conpherences;
|
||||
|
@ -187,4 +205,39 @@ final class ConpherenceThreadQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
private function loadOrigPics(array $conpherences) {
|
||||
return $this->loadPics(
|
||||
$conpherences,
|
||||
ConpherenceImageData::SIZE_ORIG
|
||||
);
|
||||
}
|
||||
|
||||
private function loadHeaderPics(array $conpherences) {
|
||||
return $this->loadPics(
|
||||
$conpherences,
|
||||
ConpherenceImageData::SIZE_HEAD
|
||||
);
|
||||
}
|
||||
|
||||
private function loadPics(array $conpherences, $size) {
|
||||
$conpherence_pic_phids = array();
|
||||
foreach ($conpherences as $conpherence) {
|
||||
$phid = $conpherence->getImagePHID($size);
|
||||
if ($phid) {
|
||||
$conpherence_pic_phids[$conpherence->getPHID()] = $phid;
|
||||
}
|
||||
}
|
||||
$files = id(new PhabricatorFileQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs($conpherence_pic_phids)
|
||||
->execute();
|
||||
$files = mpull($files, null, 'getPHID');
|
||||
|
||||
foreach ($conpherence_pic_phids as $conpherence_phid => $pic_phid) {
|
||||
$conpherences[$conpherence_phid]->setImage($files[$pic_phid], $size);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
protected $id;
|
||||
protected $phid;
|
||||
protected $title;
|
||||
protected $imagePHID;
|
||||
protected $imagePHIDs = array();
|
||||
protected $mailKey;
|
||||
|
||||
private $participants;
|
||||
|
@ -17,10 +17,14 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
private $handles;
|
||||
private $filePHIDs;
|
||||
private $widgetData;
|
||||
private $images = array();
|
||||
|
||||
public function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'imagePHIDs' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
|
@ -36,6 +40,33 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
return parent::save();
|
||||
}
|
||||
|
||||
public function getImagePHID($size) {
|
||||
$image_phids = $this->getImagePHIDs();
|
||||
return idx($image_phids, $size);
|
||||
}
|
||||
public function setImagePHID($phid, $size) {
|
||||
$image_phids = $this->getImagePHIDs();
|
||||
$image_phids[$size] = $phid;
|
||||
return $this->setImagePHIDs($image_phids);
|
||||
}
|
||||
|
||||
public function getImage($size) {
|
||||
$images = $this->getImages();
|
||||
return idx($images, $size);
|
||||
}
|
||||
public function setImage(PhabricatorFile $file, $size) {
|
||||
$files = $this->getImages();
|
||||
$files[$size] = $file;
|
||||
return $this->setImages($files);
|
||||
}
|
||||
public function setImages(array $files) {
|
||||
$this->images = $files;
|
||||
return $this;
|
||||
}
|
||||
private function getImages() {
|
||||
return $this->images;
|
||||
}
|
||||
|
||||
public function attachParticipants(array $participants) {
|
||||
assert_instances_of($participants, 'ConpherenceParticipant');
|
||||
$this->participants = $participants;
|
||||
|
@ -112,59 +143,36 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
return $this->widgetData;
|
||||
}
|
||||
|
||||
public function loadImageURI() {
|
||||
$src_phid = $this->getImagePHID();
|
||||
public function loadImageURI($size) {
|
||||
$file = $this->getImage($size);
|
||||
|
||||
if ($src_phid) {
|
||||
$file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $src_phid);
|
||||
if ($file) {
|
||||
return $file->getBestURI();
|
||||
}
|
||||
}
|
||||
|
||||
return PhabricatorUser::getDefaultProfileImageURI();
|
||||
}
|
||||
|
||||
public function getDisplayData(PhabricatorUser $user) {
|
||||
public function getDisplayData(PhabricatorUser $user, $size) {
|
||||
$transactions = $this->getTransactions();
|
||||
$latest_transaction = end($transactions);
|
||||
$latest_participant = $latest_transaction->getAuthorPHID();
|
||||
$handles = $this->getHandles();
|
||||
$latest_handle = $handles[$latest_participant];
|
||||
if ($this->getImagePHID()) {
|
||||
$img_src = $this->loadImageURI();
|
||||
} else {
|
||||
$img_src = $latest_handle->getImageURI();
|
||||
}
|
||||
$title = $this->getTitle();
|
||||
if (!$title) {
|
||||
$title = $latest_handle->getName();
|
||||
unset($handles[$latest_participant]);
|
||||
}
|
||||
unset($handles[$user->getPHID()]);
|
||||
|
||||
$subtitle = '';
|
||||
$count = 0;
|
||||
$final = false;
|
||||
foreach ($handles as $handle) {
|
||||
if ($handle->getType() != PhabricatorPHIDConstants::PHID_TYPE_USER) {
|
||||
continue;
|
||||
}
|
||||
if ($subtitle) {
|
||||
if ($final) {
|
||||
$subtitle .= '...';
|
||||
break;
|
||||
} else {
|
||||
$subtitle .= ', ';
|
||||
}
|
||||
}
|
||||
$subtitle .= $handle->getName();
|
||||
$count++;
|
||||
$final = $count == 3;
|
||||
$handles = $this->getHandles();
|
||||
// we don't want to show the user unless they are babbling to themselves
|
||||
if (count($handles) > 1) {
|
||||
unset($handles[$user->getPHID()]);
|
||||
}
|
||||
|
||||
$participants = $this->getParticipants();
|
||||
$user_participation = $participants[$user->getPHID()];
|
||||
$latest_transaction = null;
|
||||
$title = $this->getTitle();
|
||||
$subtitle = '';
|
||||
$img_src = null;
|
||||
$img_class = null;
|
||||
if ($this->getImagePHID($size)) {
|
||||
$img_src = $this->getImage($size)->getBestURI();
|
||||
$img_class = 'custom-';
|
||||
}
|
||||
$unread_count = 0;
|
||||
$max_count = 10;
|
||||
$snippet = null;
|
||||
|
@ -186,9 +194,28 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
$transaction->getComment()->getContent(),
|
||||
48
|
||||
);
|
||||
if ($transaction->getAuthorPHID() == $user->getPHID()) {
|
||||
$snippet = "\xE2\x86\xB0 " . $snippet;
|
||||
}
|
||||
}
|
||||
// fallthrough intentionally here
|
||||
case ConpherenceTransactionType::TYPE_FILES:
|
||||
if (!$latest_transaction) {
|
||||
$latest_transaction = $transaction;
|
||||
}
|
||||
$latest_participant_phid = $transaction->getAuthorPHID();
|
||||
if ((!$title || !$img_src) &&
|
||||
$latest_participant_phid != $user->getPHID()) {
|
||||
$latest_handle = $handles[$latest_participant_phid];
|
||||
if (!$img_src) {
|
||||
$img_src = $latest_handle->getImageURI();
|
||||
}
|
||||
if (!$title) {
|
||||
$title = $latest_handle->getName();
|
||||
// (maybs) used the pic, definitely used the name -- discard
|
||||
unset($handles[$latest_participant_phid]);
|
||||
}
|
||||
}
|
||||
if ($behind_transaction_phid) {
|
||||
$unread_count++;
|
||||
if ($transaction->getPHID() == $behind_transaction_phid) {
|
||||
|
@ -210,12 +237,45 @@ final class ConpherenceThread extends ConpherenceDAO
|
|||
$unread_count = $max_count.'+';
|
||||
}
|
||||
|
||||
// This happens if the user has been babbling, maybs just to themselves,
|
||||
// but enough un-responded to transactions for our SQL limit would
|
||||
// hit this too... Also happens on new threads since only the first
|
||||
// author has participated.
|
||||
// ...so just pick a different handle in these cases.
|
||||
$some_handle = reset($handles);
|
||||
if (!$img_src) {
|
||||
$img_src = $some_handle->getImageURI();
|
||||
}
|
||||
if (!$title) {
|
||||
$title = $some_handle->getName();
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$final = false;
|
||||
foreach ($handles as $handle) {
|
||||
if ($handle->getType() != PhabricatorPHIDConstants::PHID_TYPE_USER) {
|
||||
continue;
|
||||
}
|
||||
if ($subtitle) {
|
||||
if ($final) {
|
||||
$subtitle .= '...';
|
||||
break;
|
||||
} else {
|
||||
$subtitle .= ', ';
|
||||
}
|
||||
}
|
||||
$subtitle .= $handle->getName();
|
||||
$count++;
|
||||
$final = $count == 3;
|
||||
}
|
||||
|
||||
return array(
|
||||
'title' => $title,
|
||||
'subtitle' => $subtitle,
|
||||
'unread_count' => $unread_count,
|
||||
'epoch' => $latest_transaction->getDateCreated(),
|
||||
'image' => $img_src,
|
||||
'image_class' => $img_class,
|
||||
'snippet' => $snippet,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction {
|
|||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
return false;
|
||||
case ConpherenceTransactionType::TYPE_FILES:
|
||||
case ConpherenceTransactionType::TYPE_PICTURE_CROP:
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -107,12 +108,10 @@ final class ConpherenceTransaction extends PhabricatorApplicationTransaction {
|
|||
$phids[] = $this->getAuthorPHID();
|
||||
switch ($this->getTransactionType()) {
|
||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
$phids[] = $new;
|
||||
break;
|
||||
case ConpherenceTransactionType::TYPE_TITLE:
|
||||
case ConpherenceTransactionType::TYPE_FILES:
|
||||
break;
|
||||
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
||||
case ConpherenceTransactionType::TYPE_FILES:
|
||||
$phids = array_merge($phids, $this->getOldValue());
|
||||
$phids = array_merge($phids, $this->getNewValue());
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
final class ConpherenceFormDragAndDropUploadControl extends AphrontFormControl {
|
||||
|
||||
private $dropID;
|
||||
|
||||
public function setDropID($drop_id) {
|
||||
$this->dropID = $drop_id;
|
||||
return $this;
|
||||
}
|
||||
public function getDropID() {
|
||||
return $this->dropID;
|
||||
}
|
||||
|
||||
protected function getCustomControlClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function renderInput() {
|
||||
|
||||
$drop_id = celerity_generate_unique_node_id();
|
||||
Javelin::initBehavior('conpherence-drag-and-drop-photo',
|
||||
array(
|
||||
'target' => $drop_id,
|
||||
'form_pane' => 'conpherence-form',
|
||||
'upload_uri' => '/file/dropupload/',
|
||||
'activated_class' => 'conpherence-dialogue-upload-photo',
|
||||
)
|
||||
);
|
||||
require_celerity_resource('conpherence-update-css');
|
||||
|
||||
return phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'id' => $drop_id,
|
||||
'class' => 'conpherence-dialogue-drag-photo',
|
||||
),
|
||||
pht('Drag and drop an image here to upload it.')
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -38,23 +38,14 @@ final class ConpherenceTransactionView extends AphrontView {
|
|||
$content_class = null;
|
||||
switch ($transaction->getTransactionType()) {
|
||||
case ConpherenceTransactionType::TYPE_TITLE:
|
||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
case ConpherenceTransactionType::TYPE_PICTURE_CROP:
|
||||
$content = $transaction->getTitle();
|
||||
$transaction_view->addClass('conpherence-edited');
|
||||
break;
|
||||
case ConpherenceTransactionType::TYPE_FILES:
|
||||
$content = $transaction->getTitle();
|
||||
break;
|
||||
case ConpherenceTransactionType::TYPE_PICTURE:
|
||||
$img = $transaction->getHandle($transaction->getNewValue());
|
||||
$content = $transaction->getTitle() .
|
||||
phutil_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $img->getImageURI()
|
||||
)
|
||||
);
|
||||
$transaction_view->addClass('conpherence-edited');
|
||||
break;
|
||||
case ConpherenceTransactionType::TYPE_PARTICIPANTS:
|
||||
$content = $transaction->getTitle();
|
||||
$transaction_view->addClass('conpherence-edited');
|
||||
|
|
|
@ -56,6 +56,29 @@ final class PhabricatorImageTransformer {
|
|||
));
|
||||
}
|
||||
|
||||
public function executeConpherenceTransform(
|
||||
PhabricatorFile $file,
|
||||
$top,
|
||||
$left,
|
||||
$width,
|
||||
$height
|
||||
) {
|
||||
|
||||
$image = $this->crasslyCropTo(
|
||||
$file,
|
||||
$top,
|
||||
$left,
|
||||
$width,
|
||||
$height
|
||||
);
|
||||
|
||||
return PhabricatorFile::newFromFileData(
|
||||
$image,
|
||||
array(
|
||||
'name' => 'conpherence-'.$file->getName(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function crudelyCropTo(PhabricatorFile $file, $x, $min_y, $max_y) {
|
||||
$data = $file->loadFileData();
|
||||
|
@ -80,6 +103,30 @@ final class PhabricatorImageTransformer {
|
|||
return $this->saveImageDataInAnyFormat($img, $file->getMimeType());
|
||||
}
|
||||
|
||||
private function crasslyCropTo(PhabricatorFile $file, $top, $left, $w, $h) {
|
||||
$data = $file->loadFileData();
|
||||
$src = imagecreatefromstring($data);
|
||||
$dst = $this->getBlankDestinationFile($w, $h);
|
||||
|
||||
$scale = self::getScaleForCrop($file, $w, $h);
|
||||
$orig_x = $left / $scale;
|
||||
$orig_y = $top / $scale;
|
||||
$orig_w = $w / $scale;
|
||||
$orig_h = $h / $scale;
|
||||
|
||||
imagecopyresampled(
|
||||
$dst,
|
||||
$src,
|
||||
0, 0,
|
||||
$orig_x, $orig_y,
|
||||
$w, $h,
|
||||
$orig_w, $orig_h
|
||||
);
|
||||
|
||||
return $this->saveImageDataInAnyFormat($dst, $file->getMimeType());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Very crudely scale an image up or down to an exact size.
|
||||
*/
|
||||
|
@ -92,15 +139,21 @@ final class PhabricatorImageTransformer {
|
|||
return $this->saveImageDataInAnyFormat($dst, $file->getMimeType());
|
||||
}
|
||||
|
||||
private function getBlankDestinationFile($dx, $dy) {
|
||||
$dst = imagecreatetruecolor($dx, $dy);
|
||||
imagesavealpha($dst, true);
|
||||
imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 255, 255, 255, 127));
|
||||
|
||||
return $dst;
|
||||
}
|
||||
|
||||
private function applyScaleTo($src, $dx, $dy) {
|
||||
$x = imagesx($src);
|
||||
$y = imagesy($src);
|
||||
|
||||
$scale = min(($dx / $x), ($dy / $y), 1);
|
||||
|
||||
$dst = imagecreatetruecolor($dx, $dy);
|
||||
imagesavealpha($dst, true);
|
||||
imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 255, 255, 255, 127));
|
||||
$dst = $this->getBlankDestinationFile($dx, $dy);
|
||||
|
||||
$sdx = $scale * $x;
|
||||
$sdy = $scale * $y;
|
||||
|
@ -141,6 +194,27 @@ final class PhabricatorImageTransformer {
|
|||
);
|
||||
}
|
||||
|
||||
public static function getScaleForCrop(
|
||||
PhabricatorFile $file,
|
||||
$des_width,
|
||||
$des_height) {
|
||||
|
||||
$metadata = $file->getMetadata();
|
||||
$width = $metadata[PhabricatorFile::METADATA_IMAGE_WIDTH];
|
||||
$height = $metadata[PhabricatorFile::METADATA_IMAGE_HEIGHT];
|
||||
|
||||
if ($height < $des_height) {
|
||||
$scale = $height / $des_height;
|
||||
} else if ($width < $des_width) {
|
||||
$scale = $width / $des_width;
|
||||
} else {
|
||||
$scale_x = $des_width / $width;
|
||||
$scale_y = $des_height / $height;
|
||||
$scale = max($scale_x, $scale_y);
|
||||
}
|
||||
|
||||
return $scale;
|
||||
}
|
||||
private function generatePreview(PhabricatorFile $file, $size) {
|
||||
$data = $file->loadFileData();
|
||||
$src = imagecreatefromstring($data);
|
||||
|
@ -153,9 +227,7 @@ final class PhabricatorImageTransformer {
|
|||
$sdx = $dimensions['sdx'];
|
||||
$sdy = $dimensions['sdy'];
|
||||
|
||||
$dst = imagecreatetruecolor($dx, $dy);
|
||||
imagesavealpha($dst, true);
|
||||
imagefill($dst, 0, 0, imagecolorallocatealpha($dst, 255, 255, 255, 127));
|
||||
$dst = $this->getBlankDestinationFile($dx, $dy);
|
||||
|
||||
imagecopyresampled(
|
||||
$dst,
|
||||
|
|
|
@ -413,9 +413,6 @@ final class PhabricatorObjectHandleData {
|
|||
$handle->setName($file->getName());
|
||||
$handle->setURI($file->getBestURI());
|
||||
$handle->setComplete(true);
|
||||
if ($file->isViewableImage()) {
|
||||
$handle->setImageURI($file->getBestURI());
|
||||
}
|
||||
}
|
||||
$handles[$phid] = $handle;
|
||||
}
|
||||
|
|
99
src/view/form/control/AphrontFormCropControl.php
Normal file
99
src/view/form/control/AphrontFormCropControl.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
final class AphrontFormCropControl extends AphrontFormControl {
|
||||
|
||||
private $width = 50;
|
||||
private $height = 50;
|
||||
|
||||
public function setHeight($height) {
|
||||
$this->height = $height;
|
||||
return $this;
|
||||
}
|
||||
public function getHeight() {
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
public function setWidth($width) {
|
||||
$this->width = $width;
|
||||
return $this;
|
||||
}
|
||||
public function getWidth() {
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
protected function getCustomControlClass() {
|
||||
return 'aphront-form-crop';
|
||||
}
|
||||
|
||||
protected function renderInput() {
|
||||
$file = $this->getValue();
|
||||
|
||||
if ($file === null) {
|
||||
return phutil_render_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => PhabricatorUser::getDefaultProfileImageURI()
|
||||
),
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
$c_id = celerity_generate_unique_node_id();
|
||||
$metadata = $file->getMetadata();
|
||||
$scale = PhabricatorImageTransformer::getScaleForCrop(
|
||||
$file,
|
||||
$this->getWidth(),
|
||||
$this->getHeight()
|
||||
);
|
||||
|
||||
Javelin::initBehavior(
|
||||
'aphront-crop',
|
||||
array(
|
||||
'cropBoxID' => $c_id,
|
||||
'width' => $this->getWidth(),
|
||||
'height' => $this->getHeight(),
|
||||
'scale' => $scale,
|
||||
'imageH' => $metadata[PhabricatorFile::METADATA_IMAGE_HEIGHT],
|
||||
'imageW' => $metadata[PhabricatorFile::METADATA_IMAGE_WIDTH],
|
||||
)
|
||||
);
|
||||
|
||||
return javelin_render_tag(
|
||||
'div',
|
||||
array(
|
||||
'id' => $c_id,
|
||||
'sigil' => 'crop-box',
|
||||
'mustcapture' => true,
|
||||
'class' => 'crop-box'
|
||||
),
|
||||
javelin_render_tag(
|
||||
'img',
|
||||
array(
|
||||
'src' => $file->getBestURI(),
|
||||
'class' => 'crop-image',
|
||||
'sigil' => 'crop-image'
|
||||
),
|
||||
''
|
||||
).
|
||||
javelin_render_tag(
|
||||
'input',
|
||||
array(
|
||||
'type' => 'hidden',
|
||||
'name' => 'image_x',
|
||||
'sigil' => 'crop-x',
|
||||
),
|
||||
''
|
||||
).
|
||||
javelin_render_tag(
|
||||
'input',
|
||||
array(
|
||||
'type' => 'hidden',
|
||||
'name' => 'image_y',
|
||||
'sigil' => 'crop-y',
|
||||
),
|
||||
''
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -231,6 +231,18 @@ table.aphront-form-control-checkbox-layout th {
|
|||
border-color: #669966;
|
||||
}
|
||||
|
||||
.aphront-form-crop .crop-box {
|
||||
cursor: move;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.aphront-form-crop .crop-box .crop-image {
|
||||
position: relative;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
|
||||
.calendar-button {
|
||||
display: inline;
|
||||
background: url(/rsrc/image/icon/fatcow/calendar_edit.png)
|
||||
|
|
|
@ -29,6 +29,14 @@
|
|||
width: 50px;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .custom-header-image {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 80px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .title {
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
|
@ -39,6 +47,16 @@
|
|||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .custom-title {
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
left: 132px;
|
||||
top: 21px;
|
||||
max-width: 80%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .subtitle {
|
||||
position: relative;
|
||||
left: 77px;
|
||||
|
@ -47,4 +65,34 @@
|
|||
max-width: 80%;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .custom-subtitle {
|
||||
position: relative;
|
||||
left: 132px;
|
||||
top: 21px;
|
||||
color: #bfbfbf;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.conpherence-header-pane .upload-photo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.conpherence-header-upload-photo .upload-photo {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 32px;
|
||||
font-size: 16px;
|
||||
background: #99ff99;
|
||||
border-color: #669966;
|
||||
}
|
||||
|
||||
.conpherence-header-upload-photo .edit,
|
||||
.conpherence-header-upload-photo .header-image,
|
||||
.conpherence-header-upload-photo .custom-header-image,
|
||||
.conpherence-header-upload-photo .title,
|
||||
.conpherence-header-upload-photo .custom-title,
|
||||
.conpherence-header-upload-photo .subtitle,
|
||||
.conpherence-header-upload-photo .custom-subtitle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,3 +5,13 @@
|
|||
.phabricator-standard-page-body .aphront-dialog-view {
|
||||
margin: 20px auto 0px auto;
|
||||
}
|
||||
|
||||
.aphront-dialog-view .conpherence-dialogue-drag-photo {
|
||||
border: 1px dashed #bfbfbf;
|
||||
padding: 10px 0px 10px 10px;
|
||||
}
|
||||
|
||||
.aphront-dialog-view .conpherence-dialogue-upload-photo {
|
||||
background: #99ff99;
|
||||
border-color: #669966;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* @provides javelin-behavior-conpherence-drag-and-drop-photo
|
||||
* @requires javelin-behavior
|
||||
* javelin-dom
|
||||
* javelin-workflow
|
||||
* phabricator-drag-and-drop-file-upload
|
||||
*/
|
||||
|
||||
JX.behavior('conpherence-drag-and-drop-photo', function(config) {
|
||||
|
||||
var target = JX.$(config.target);
|
||||
var form_pane = JX.$(config.form_pane);
|
||||
|
||||
function onupload(f) {
|
||||
var data = {
|
||||
'file_id' : f.getID(),
|
||||
'action' : 'metadata'
|
||||
};
|
||||
|
||||
var form = JX.DOM.find(form_pane, 'form');
|
||||
var workflow = JX.Workflow.newFromForm(form, data);
|
||||
workflow.start();
|
||||
}
|
||||
|
||||
if (JX.PhabricatorDragAndDropFileUpload.isSupported()) {
|
||||
var drop = new JX.PhabricatorDragAndDropFileUpload(target)
|
||||
.setURI(config.upload_uri);
|
||||
drop.listen('didBeginDrag', function(e) {
|
||||
JX.DOM.alterClass(target, config.activated_class, true);
|
||||
});
|
||||
drop.listen('didEndDrag', function(e) {
|
||||
JX.DOM.alterClass(target, config.activated_class, false);
|
||||
});
|
||||
drop.listen('didUpload', onupload);
|
||||
drop.start();
|
||||
}
|
||||
|
||||
});
|
||||
|
94
webroot/rsrc/js/application/core/behavior-crop.js
Normal file
94
webroot/rsrc/js/application/core/behavior-crop.js
Normal file
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* @provides javelin-behavior-aphront-crop
|
||||
* @requires javelin-behavior
|
||||
* javelin-dom
|
||||
* javelin-vector
|
||||
* javelin-magical-init
|
||||
*/
|
||||
|
||||
JX.behavior('aphront-crop', function(config) {
|
||||
|
||||
var dragging = false;
|
||||
var startX, startY;
|
||||
var finalX, finalY;
|
||||
var dScale = config.scale;
|
||||
|
||||
var cropBox = JX.$(config.cropBoxID);
|
||||
var basePos = JX.$V(cropBox);
|
||||
cropBox.style.height = config.height + 'px';
|
||||
cropBox.style.width = config.width + 'px';
|
||||
var baseD = JX.$V(config.width, config.height);
|
||||
|
||||
var image = JX.DOM.find(cropBox, 'img', 'crop-image');
|
||||
image.style.height = (config.imageH * config.scale) + 'px';
|
||||
image.style.width = (config.imageW * config.scale) + 'px';
|
||||
var imageD = JX.$V(
|
||||
config.imageW * config.scale,
|
||||
config.imageH * config.scale
|
||||
);
|
||||
var minLeft = baseD.x - imageD.x;
|
||||
var minTop = baseD.y - imageD.y;
|
||||
|
||||
var minScale = Math.min(
|
||||
config.width / config.imageW,
|
||||
config.height / config.imageH,
|
||||
1
|
||||
);
|
||||
var maxScale = Math.max(
|
||||
config.imageW / config.width,
|
||||
config.imageH / config.height,
|
||||
2
|
||||
);
|
||||
|
||||
var ondrag = function(e) {
|
||||
e.kill();
|
||||
dragging = true;
|
||||
var p = JX.$V(e);
|
||||
startX = p.x;
|
||||
startY = p.y;
|
||||
};
|
||||
|
||||
var onmove = function(e) {
|
||||
if (!dragging) {
|
||||
return;
|
||||
}
|
||||
e.kill();
|
||||
|
||||
var p = JX.$V(e);
|
||||
var dx = startX - p.x;
|
||||
var dy = startY - p.y;
|
||||
var imagePos = JX.$V(image);
|
||||
var moveLeft = imagePos.x - basePos.x - dx;
|
||||
var moveTop = imagePos.y - basePos.y - dy;
|
||||
|
||||
image.style.left = Math.min(Math.max(minLeft, moveLeft), 0) + 'px';
|
||||
image.style.top = Math.min(Math.max(minTop, moveTop), 0) + 'px';
|
||||
|
||||
// reset these; a new beginning!
|
||||
startX = p.x;
|
||||
startY = p.y;
|
||||
|
||||
// save off where we are right now
|
||||
imagePos = JX.$V(image);
|
||||
finalX = Math.abs(imagePos.x - basePos.x);
|
||||
finalY = Math.abs(imagePos.y - basePos.y);
|
||||
JX.DOM.find(cropBox, 'input', 'crop-x').value = finalX;
|
||||
JX.DOM.find(cropBox, 'input', 'crop-y').value = finalY;
|
||||
};
|
||||
|
||||
var ondrop = function(e) {
|
||||
if (!dragging) {
|
||||
return;
|
||||
}
|
||||
dragging = false;
|
||||
};
|
||||
|
||||
// NOTE: Javelin does not dispatch mousemove by default.
|
||||
JX.enableDispatch(cropBox, 'mousemove');
|
||||
|
||||
JX.DOM.listen(cropBox, 'mousedown', [], ondrag);
|
||||
JX.DOM.listen(cropBox, 'mousemove', [], onmove);
|
||||
JX.DOM.listen(cropBox, 'mouseup', [], ondrop);
|
||||
JX.DOM.listen(cropBox, 'mouseout', [], ondrop);
|
||||
|
||||
});
|
Loading…
Reference in a new issue