1
0
Fork 0
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:
Bob Trahan 2013-02-06 14:03:52 -08:00
parent fb7d5d17a2
commit 1cde41b994
22 changed files with 803 additions and 149 deletions

View 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 = '';

View file

@ -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',
),
));

View file

@ -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',

View file

@ -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;
}

View file

@ -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';
}

View file

@ -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',
)
);
}
}

View file

@ -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(

View file

@ -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']
);

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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,
);
}

View file

@ -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;

View file

@ -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.')
);
}
}

View file

@ -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');

View file

@ -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,

View file

@ -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;
}

View 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',
),
''
)
);
}
}

View file

@ -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)

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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();
}
});

View 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);
});