diff --git a/src/applications/files/controller/PhabricatorFileComposeController.php b/src/applications/files/controller/PhabricatorFileComposeController.php index d698509e96..c3351e045f 100644 --- a/src/applications/files/controller/PhabricatorFileComposeController.php +++ b/src/applications/files/controller/PhabricatorFileComposeController.php @@ -1,250 +1,338 @@ getRequest(); $viewer = $request->getUser(); $colors = array( 'red' => pht('Verbillion'), 'orange' => pht('Navel Orange'), 'yellow' => pht('Prim Goldenrod'), 'green' => pht('Lustrous Verdant'), 'blue' => pht('Tropical Deep'), 'sky' => pht('Wide Open Sky'), 'indigo' => pht('Pleated Khaki'), 'violet' => pht('Aged Merlot'), + 'pink' => pht('Easter Bunny'), 'charcoal' => pht('Gemstone'), 'backdrop' => pht('Driven Snow'), ); $manifest = PHUIIconView::getSheetManifest(PHUIIconView::SPRITE_PROJECTS); if ($request->isFormPost()) { - $icon = $request->getStr('icon'); - $color = $request->getStr('color'); + $project_phid = $request->getStr('projectPHID'); + if ($project_phid) { + $project = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($project_phid)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$project) { + return new Aphront404Response(); + } + $icon = $project->getIcon(); + $color = $project->getColor(); + switch ($color) { + case 'grey': + $color = 'charcoal'; + break; + case 'checkered': + $color = 'backdrop'; + break; + } + } else { + $icon = $request->getStr('icon'); + $color = $request->getStr('color'); + } - if (isset($colors[$color]) && isset($manifest['projects-'.$icon])) { - $root = dirname(phutil_get_library_root('phabricator')); - $icon_file = $root.'/resources/sprite/projects_1x/'.$icon.'.png'; - $icon_data = Filesystem::readFile($icon_file); + if (!isset($colors[$color]) || !isset($manifest['projects-'.$icon])) { + return new Aphront404Response(); + } + $root = dirname(phutil_get_library_root('phabricator')); + $icon_file = $root.'/resources/sprite/projects_1x/'.$icon.'.png'; + $icon_data = Filesystem::readFile($icon_file); - $data = $this->composeImage($color, $icon_data); - $file = PhabricatorFile::buildFromFileDataOrHash( - $data, - array( - 'name' => 'project.png', - 'canCDN' => true, - )); + $data = $this->composeImage($color, $icon_data); + + $file = PhabricatorFile::buildFromFileDataOrHash( + $data, + array( + 'name' => 'project.png', + 'canCDN' => true, + )); + + if ($project_phid) { + $edit_uri = '/project/edit/'.$project->getID().'/'; + $xactions = array(); + $xactions[] = id(new PhabricatorProjectTransaction()) + ->setTransactionType(PhabricatorProjectTransaction::TYPE_IMAGE) + ->setNewValue($file->getPHID()); + + $editor = id(new PhabricatorProjectTransactionEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnMissingFields(true) + ->setContinueOnNoEffect(true); + + $editor->applyTransactions($project, $xactions); + + return id(new AphrontRedirectResponse())->setURI($edit_uri); + } else { $content = array( 'phid' => $file->getPHID(), ); return id(new AphrontAjaxResponse())->setContent($content); } } $value_color = head_key($colors); $value_icon = head_key($manifest); $value_icon = substr($value_icon, strlen('projects-')); require_celerity_resource('people-profile-css'); $buttons = array(); foreach ($colors as $color => $name) { $buttons[] = javelin_tag( 'button', array( 'class' => 'grey profile-image-button', 'sigil' => 'has-tooltip compose-select-color', 'style' => 'margin: 0 8px 8px 0', 'meta' => array( 'color' => $color, 'tip' => $name, ), ), id(new PHUIIconView()) ->addClass('compose-background-'.$color)); } + $sort_these_first = array( + 'projects-fa-briefcase', + 'projects-fa-tags', + 'projects-fa-folder', + 'projects-fa-group', + 'projects-fa-bug', + 'projects-fa-trash-o', + 'projects-fa-calendar', + 'projects-fa-flag-checkered', + 'projects-fa-envelope', + 'projects-fa-truck', + 'projects-fa-lock', + 'projects-fa-umbrella', + 'projects-fa-cloud', + 'projects-fa-building', + 'projects-fa-credit-card', + 'projects-fa-flask', + ); + + $manifest = array_select_keys( + $manifest, + $sort_these_first) + + $manifest; + $icons = array(); $icon_quips = array( '8ball' => pht('Take a Risk'), 'alien' => pht('Foreign Interface'), 'announce' => pht('Louder is Better'), 'art' => pht('Unique Snowflake'), 'award' => pht('Shooting Star'), 'bacon' => pht('Healthy Vegetables'), 'bandaid' => pht('Durable Infrastructure'), 'beer' => pht('Healthy Vegetable Juice'), 'bomb' => pht('Imminent Success'), 'briefcase' => pht('Adventure Pack'), 'bug' => pht('Costumed Egg'), 'calendar' => pht('Everyone Loves Meetings'), 'cloud' => pht('Water Cycle'), 'coffee' => pht('Half-Whip Nonfat Soy Latte'), 'creditcard' => pht('Expense It'), 'death' => pht('Calcium Promotes Bone Health'), 'desktop' => pht('Magical Portal'), 'dropbox' => pht('Cardboard Box'), 'education' => pht('Debt'), 'experimental' => pht('CAUTION: Dangerous Chemicals'), 'facebook' => pht('Popular Social Network'), 'facility' => pht('Pollution Solves Problems'), 'film' => pht('Actual Physical Film'), 'forked' => pht('You Can\'t Eat Soup'), 'games' => pht('Serious Business'), 'ghost' => pht('Haunted'), 'gift' => pht('Surprise!'), 'globe' => pht('Scanner Sweep'), 'golf' => pht('Business Meeting'), 'heart' => pht('Undergoing a Major Surgery'), 'intergalactic' => pht('Jupiter'), 'lock' => pht('Extremely Secret'), 'mail' => pht('Oragami'), 'martini' => pht('Healthy Olive Drink'), 'medical' => pht('Medic!'), 'mobile' => pht('Cellular Telephone'), 'music' => pht("\xE2\x99\xAB"), 'news' => pht('Actual Physical Newspaper'), 'orgchart' => pht('It\'s Good to be King'), 'peoples' => pht('Angel and Devil'), 'piechart' => pht('Actual Physical Pie'), 'poison' => pht('Healthy Bone Juice'), 'putabirdonit' => pht('Put a Bird On It'), 'radiate' => pht('Radiant Beauty'), 'savings' => pht('Oink Oink'), 'search' => pht('Sleuthing'), 'shield' => pht('Royal Crest'), 'speed' => pht('Slow and Steady'), 'sprint' => pht('Fire Exit'), 'star' => pht('The More You Know'), 'storage' => pht('Stack of Pancakes'), 'tablet' => pht('Cellular Telephone For Giants'), 'travel' => pht('Pretty Clearly an Airplane'), 'twitter' => pht('Bird Stencil'), 'warning' => pht('No Caution Required, Everything Looks Safe'), 'whale' => pht('Friendly Walrus'), + 'fa-flask' => pht('Experimental'), + 'fa-briefcase' => pht('Briefcase'), + 'fa-bug' => pht('Bug'), + 'fa-building' => pht('Company'), + 'fa-calendar' => pht('Deadline'), + 'fa-cloud' => pht('The Cloud'), + 'fa-credit-card' => pht('Accounting'), + 'fa-envelope' => pht('Communication'), + 'fa-flag-checkered' => pht('Goal'), + 'fa-folder' => pht('Folder'), + 'fa-group' => pht('Team'), + 'fa-lock' => pht('Policy'), + 'fa-tags' => pht('Tag'), + 'fa-trash-o' => pht('Garbage'), + 'fa-truck' => pht('Release'), + 'fa-umbrella' => pht('An Umbrella'), ); foreach ($manifest as $icon => $spec) { $icon = substr($icon, strlen('projects-')); $icons[] = javelin_tag( 'button', array( 'class' => 'grey profile-image-button', 'sigil' => 'has-tooltip compose-select-icon', 'style' => 'margin: 0 8px 8px 0', 'meta' => array( 'icon' => $icon, 'tip' => idx($icon_quips, $icon, $icon), ), ), id(new PHUIIconView()) ->setSpriteIcon($icon) ->setSpriteSheet(PHUIIconView::SPRITE_PROJECTS)); } $dialog_id = celerity_generate_unique_node_id(); $color_input_id = celerity_generate_unique_node_id();; $icon_input_id = celerity_generate_unique_node_id(); $preview_id = celerity_generate_unique_node_id(); $preview = id(new PHUIIconView()) ->setID($preview_id) ->addClass('compose-background-'.$value_color) ->setSpriteIcon($value_icon) ->setSpriteSheet(PHUIIconView::SPRITE_PROJECTS); $color_input = javelin_tag( 'input', array( 'type' => 'hidden', 'name' => 'color', 'value' => $value_color, 'id' => $color_input_id, )); $icon_input = javelin_tag( 'input', array( 'type' => 'hidden', 'name' => 'icon', 'value' => $value_icon, 'id' => $icon_input_id, )); Javelin::initBehavior('phabricator-tooltips'); Javelin::initBehavior( 'icon-composer', array( 'dialogID' => $dialog_id, 'colorInputID' => $color_input_id, 'iconInputID' => $icon_input_id, 'previewID' => $preview_id, 'defaultColor' => $value_color, 'defaultIcon' => $value_icon, )); $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setFormID($dialog_id) ->setClass('compose-dialog') ->setTitle(pht('Compose Image')) ->appendChild( phutil_tag( 'div', array( 'class' => 'compose-header', ), pht('Choose Background Color'))) ->appendChild($buttons) ->appendChild( phutil_tag( 'div', array( 'class' => 'compose-header', ), pht('Choose Icon'))) ->appendChild($icons) ->appendChild( phutil_tag( 'div', array( 'class' => 'compose-header', ), pht('Preview'))) ->appendChild($preview) ->appendChild($color_input) ->appendChild($icon_input) ->addCancelButton('/') ->addSubmitButton(pht('Save Image')); return id(new AphrontDialogResponse())->setDialog($dialog); } private function composeImage($color, $icon_data) { $icon_img = imagecreatefromstring($icon_data); $map = CelerityResourceTransformer::getCSSVariableMap(); $color_string = idx($map, $color, '#ff00ff'); $color_const = hexdec(trim($color_string, '#')); $canvas = imagecreatetruecolor(50, 50); imagefill($canvas, 0, 0, $color_const); imagecopy($canvas, $icon_img, 0, 0, 0, 0, 50, 50); return PhabricatorImageTransformer::saveImageDataInAnyFormat( $canvas, 'image/png'); } } diff --git a/src/applications/project/controller/PhabricatorProjectEditPictureController.php b/src/applications/project/controller/PhabricatorProjectEditPictureController.php index 53d5c4cbe1..44a71780d0 100644 --- a/src/applications/project/controller/PhabricatorProjectEditPictureController.php +++ b/src/applications/project/controller/PhabricatorProjectEditPictureController.php @@ -1,273 +1,305 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); $project = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$project) { return new Aphront404Response(); } $edit_uri = $this->getApplicationURI('edit/'.$project->getID().'/'); $view_uri = $this->getApplicationURI('view/'.$project->getID().'/'); $supported_formats = PhabricatorFile::getTransformableImageFormats(); $e_file = true; $errors = array(); if ($request->isFormPost()) { $phid = $request->getStr('phid'); $is_default = false; if ($phid == PhabricatorPHIDConstants::PHID_VOID) { $phid = null; $is_default = true; } else if ($phid) { $file = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($phid)) ->executeOne(); } else { if ($request->getFileExists('picture')) { $file = PhabricatorFile::newFromPHPUpload( $_FILES['picture'], array( 'authorPHID' => $viewer->getPHID(), 'canCDN' => true, )); } else { $e_file = pht('Required'); $errors[] = pht( 'You must choose a file when uploading a new project picture.'); } } if (!$errors && !$is_default) { if (!$file->isTransformableImage()) { $e_file = pht('Not Supported'); $errors[] = pht( 'This server only supports these image formats: %s.', implode(', ', $supported_formats)); } else { $xformer = new PhabricatorImageTransformer(); $xformed = $xformer->executeProfileTransform( $file, $width = 50, $min_height = 50, $max_height = 50); } } if (!$errors) { if ($is_default) { $new_value = null; } else { $new_value = $xformed->getPHID(); } $xactions = array(); $xactions[] = id(new PhabricatorProjectTransaction()) ->setTransactionType(PhabricatorProjectTransaction::TYPE_IMAGE) ->setNewValue($new_value); $editor = id(new PhabricatorProjectTransactionEditor()) ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnMissingFields(true) ->setContinueOnNoEffect(true); $editor->applyTransactions($project, $xactions); return id(new AphrontRedirectResponse())->setURI($edit_uri); } } $title = pht('Edit Project Picture'); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($project->getName(), $view_uri); $crumbs->addTextCrumb(pht('Edit'), $edit_uri); $crumbs->addTextCrumb(pht('Picture')); $form = id(new PHUIFormLayoutView()) ->setUser($viewer); $default_image = PhabricatorFile::loadBuiltin($viewer, 'project.png'); $images = array(); $current = $project->getProfileImagePHID(); $has_current = false; if ($current) { $files = id(new PhabricatorFileQuery()) ->setViewer($viewer) ->withPHIDs(array($current)) ->execute(); if ($files) { $file = head($files); if ($file->isTransformableImage()) { $has_current = true; $images[$current] = array( 'uri' => $file->getBestURI(), 'tip' => pht('Current Picture'), ); } } } $images[PhabricatorPHIDConstants::PHID_VOID] = array( 'uri' => $default_image->getBestURI(), 'tip' => pht('Default Picture'), ); require_celerity_resource('people-profile-css'); Javelin::initBehavior('phabricator-tooltips', array()); $buttons = array(); foreach ($images as $phid => $spec) { $button = javelin_tag( 'button', array( 'class' => 'grey profile-image-button', 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => $spec['tip'], 'size' => 300, ), ), phutil_tag( 'img', array( 'height' => 50, 'width' => 50, 'src' => $spec['uri'], ))); $button = array( phutil_tag( 'input', array( 'type' => 'hidden', 'name' => 'phid', 'value' => $phid, )), $button, ); $button = phabricator_form( $viewer, array( 'class' => 'profile-image-form', 'method' => 'POST', ), $button); $buttons[] = $button; } if ($has_current) { $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Current Picture')) ->setValue(array_shift($buttons))); } $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Use Picture')) ->setValue($buttons)); $launch_id = celerity_generate_unique_node_id(); $input_id = celerity_generate_unique_node_id(); Javelin::initBehavior( 'launch-icon-composer', array( 'launchID' => $launch_id, 'inputID' => $input_id, )); $compose_button = javelin_tag( 'button', array( 'class' => 'grey', 'id' => $launch_id, 'sigil' => 'icon-composer', ), pht('Choose Icon and Color...')); $compose_input = javelin_tag( 'input', array( 'type' => 'hidden', 'id' => $input_id, 'name' => 'phid', )); $compose_form = phabricator_form( $viewer, array( 'class' => 'profile-image-form', 'method' => 'POST', ), array( $compose_input, $compose_button, )); $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Quick Create')) ->setValue($compose_form)); + $default_button = javelin_tag( + 'button', + array( + 'class' => 'grey', + ), + pht('Use Project Icon')); + + $default_input = javelin_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => 'projectPHID', + 'value' => $project->getPHID(), + )); + + $default_form = phabricator_form( + $viewer, + array( + 'class' => 'profile-image-form', + 'method' => 'POST', + 'action' => '/file/compose/', + ), + array( + $default_input, + $default_button, + )); + + $form->appendChild( + id(new AphrontFormMarkupControl()) + ->setLabel(pht('Use Default')) + ->setValue($default_form)); + $upload_form = id(new AphrontFormView()) ->setUser($viewer) ->setEncType('multipart/form-data') ->appendChild( id(new AphrontFormFileControl()) ->setName('picture') ->setLabel(pht('Upload Picture')) ->setError($e_file) ->setCaption( pht('Supported formats: %s', implode(', ', $supported_formats)))) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($edit_uri) ->setValue(pht('Upload Picture'))); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormErrors($errors) ->setForm($form); $upload_box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Upload New Picture')) ->setForm($upload_form); return $this->buildApplicationPage( array( $crumbs, $form_box, $upload_box, ), array( 'title' => $title, )); } } diff --git a/webroot/rsrc/css/application/people/people-profile.css b/webroot/rsrc/css/application/people/people-profile.css index bc0e61fbf9..089402b4d7 100644 --- a/webroot/rsrc/css/application/people/people-profile.css +++ b/webroot/rsrc/css/application/people/people-profile.css @@ -1,78 +1,82 @@ /** * @provides people-profile-css */ form.profile-image-form { display: inline-block; margin: 0 8px 8px 0; } button.profile-image-button { padding: 4px; margin: 0; } .compose-dialog button.profile-image-button-selected { background-image: none; background-color: {$lightblue}; border-color: {$blueborder}; } .compose-header { color: {$bluetext}; border-bottom: 1px solid {$lightblueborder}; padding: 4px 0; margin: 0 0 8px; } form.compose-dialog { width: 80%; } .compose-dialog .phui-icon-view { display: block; position: relative; width: 50px; height: 50px; background-color: {$darkgreytext}; } .compose-dialog .compose-background-red { background-color: {$red}; } .compose-dialog .compose-background-orange { background-color: {$orange}; } .compose-dialog .compose-background-yellow { background-color: {$yellow}; } .compose-dialog .compose-background-green { background-color: {$green}; } .compose-dialog .compose-background-blue { background-color: {$blue}; } .compose-dialog .compose-background-sky { background-color: {$sky}; } .compose-dialog .compose-background-indigo { background-color: {$indigo}; } .compose-dialog .compose-background-violet { background-color: {$violet}; } +.compose-dialog .compose-background-pink { + background-color: {$pink}; +} + .compose-dialog .compose-background-charcoal { background-color: {$charcoal}; } .compose-dialog .compose-background-backdrop { background-color: {$backdrop}; }