diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -81,7 +81,6 @@ 'rsrc/css/application/harbormaster/harbormaster.css' => 'f491c9f4', 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 'rsrc/css/application/herald/herald.css' => 'cd8d0134', - 'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5', 'rsrc/css/application/maniphest/report.css' => '9b9580b7', 'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b', 'rsrc/css/application/maniphest/task-summary.css' => '11cc5344', @@ -143,6 +142,7 @@ 'rsrc/css/phui/phui-basic-nav-view.css' => '98c11ab3', 'rsrc/css/phui/phui-big-info-view.css' => 'acc3492c', 'rsrc/css/phui/phui-box.css' => '4bd6cdb9', + 'rsrc/css/phui/phui-bulk-editor.css' => '1fe728a8', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-cms.css' => '504b4b23', 'rsrc/css/phui/phui-comment-form.css' => 'ac68149f', @@ -419,7 +419,6 @@ 'rsrc/js/application/herald/HeraldRuleEditor.js' => '2dff5579', 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', - 'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7', 'rsrc/js/application/maniphest/behavior-batch-selector.js' => 'ad54037e', 'rsrc/js/application/maniphest/behavior-line-chart.js' => 'e4232876', 'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2', @@ -477,6 +476,7 @@ 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', 'rsrc/js/core/behavior-badge-view.js' => '8ff5e24c', + 'rsrc/js/core/behavior-bulk-editor.js' => '5e178556', 'rsrc/js/core/behavior-choose-control.js' => '327a00d1', 'rsrc/js/core/behavior-copy.js' => 'b0b8f86d', 'rsrc/js/core/behavior-detect-timezone.js' => '4c193c96', @@ -532,7 +532,7 @@ 'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03', 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', - 'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671', + 'rsrc/js/phuix/PHUIXFormControl.js' => '68bb05aa', 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', ), 'symbols' => array( @@ -595,6 +595,7 @@ 'javelin-behavior-audio-source' => '59b251eb', 'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-badge-view' => '8ff5e24c', + 'javelin-behavior-bulk-editor' => '5e178556', 'javelin-behavior-bulk-job-reload' => 'edf8a145', 'javelin-behavior-calendar-month-view' => 'fe33e256', 'javelin-behavior-choose-control' => '327a00d1', @@ -642,7 +643,6 @@ 'javelin-behavior-lightbox-attachments' => '560f41da', 'javelin-behavior-line-chart' => 'e4232876', 'javelin-behavior-load-blame' => '42126667', - 'javelin-behavior-maniphest-batch-editor' => '782ab6e7', 'javelin-behavior-maniphest-batch-selector' => 'ad54037e', 'javelin-behavior-maniphest-list-editor' => 'a9f88de2', 'javelin-behavior-maniphest-subpriority-editor' => '71237763', @@ -756,7 +756,6 @@ 'javelin-workboard-column' => '758b4758', 'javelin-workboard-controller' => '26167537', 'javelin-workflow' => '1e911d0f', - 'maniphest-batch-editor' => 'b0f0b6d5', 'maniphest-report-css' => '9b9580b7', 'maniphest-task-edit-css' => 'fda62a9b', 'maniphest-task-summary-css' => '11cc5344', @@ -823,6 +822,7 @@ 'phui-basic-nav-view-css' => '98c11ab3', 'phui-big-info-view-css' => 'acc3492c', 'phui-box-css' => '4bd6cdb9', + 'phui-bulk-editor-css' => '1fe728a8', 'phui-button-bar-css' => 'f1ff5494', 'phui-button-css' => '1863cc6e', 'phui-button-simple-css' => '8e1baf68', @@ -884,7 +884,7 @@ 'phuix-autocomplete' => 'e0731603', 'phuix-button-view' => '8a91e1ac', 'phuix-dropdown-menu' => '04b2ae03', - 'phuix-form-control-view' => '83e03671', + 'phuix-form-control-view' => '68bb05aa', 'phuix-icon-view' => 'bff6884b', 'policy-css' => '957ea14c', 'policy-edit-css' => '815c66f7', @@ -1387,6 +1387,15 @@ 'javelin-stratcom', 'javelin-dom', ), + '5e178556' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'phabricator-prefab', + 'multirow-row-manager', + 'javelin-json', + 'phuix-form-control-view', + ), '5e2634b9' => array( 'javelin-behavior', 'javelin-aphlict', @@ -1436,6 +1445,10 @@ 'javelin-dom', 'phuix-button-view', ), + '68bb05aa' => array( + 'javelin-install', + 'javelin-dom', + ), '69adf288' => array( 'javelin-install', ), @@ -1524,14 +1537,6 @@ 'javelin-request', 'javelin-util', ), - '782ab6e7' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'phabricator-prefab', - 'multirow-row-manager', - 'javelin-json', - ), '7927a7d3' => array( 'javelin-behavior', 'javelin-quicksand', @@ -1570,10 +1575,6 @@ 'javelin-behavior', 'javelin-scrollbar', ), - '83e03671' => array( - 'javelin-install', - 'javelin-dom', - ), '8499b6ab' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -222,6 +222,8 @@ 'AuditConduitAPIMethod' => 'applications/audit/conduit/AuditConduitAPIMethod.php', 'AuditQueryConduitAPIMethod' => 'applications/audit/conduit/AuditQueryConduitAPIMethod.php', 'AuthManageProvidersCapability' => 'applications/auth/capability/AuthManageProvidersCapability.php', + 'BulkParameterType' => 'applications/transactions/bulk/type/BulkParameterType.php', + 'BulkStringParameterType' => 'applications/transactions/bulk/type/BulkStringParameterType.php', 'CalendarTimeUtil' => 'applications/calendar/util/CalendarTimeUtil.php', 'CalendarTimeUtilTestCase' => 'applications/calendar/__tests__/CalendarTimeUtilTestCase.php', 'CelerityAPI' => 'applications/celerity/CelerityAPI.php', @@ -5242,6 +5244,8 @@ 'AuditConduitAPIMethod' => 'ConduitAPIMethod', 'AuditQueryConduitAPIMethod' => 'AuditConduitAPIMethod', 'AuthManageProvidersCapability' => 'PhabricatorPolicyCapability', + 'BulkParameterType' => 'Phobject', + 'BulkStringParameterType' => 'BulkParameterType', 'CalendarTimeUtil' => 'Phobject', 'CalendarTimeUtilTestCase' => 'PhabricatorTestCase', 'CelerityAPI' => 'Phobject', diff --git a/src/applications/maniphest/bulk/ManiphestTaskBulkEngine.php b/src/applications/maniphest/bulk/ManiphestTaskBulkEngine.php --- a/src/applications/maniphest/bulk/ManiphestTaskBulkEngine.php +++ b/src/applications/maniphest/bulk/ManiphestTaskBulkEngine.php @@ -18,6 +18,10 @@ return new ManiphestTaskSearchEngine(); } + public function newEditEngine() { + return new ManiphestEditEngine(); + } + public function getDoneURI() { $board_uri = $this->getBoardURI(); if ($board_uri) { diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -178,6 +178,7 @@ id(new PhabricatorTextEditField()) ->setKey('title') ->setLabel(pht('Title')) + ->setBulkEditLabel(pht('Set title to')) ->setDescription(pht('Name of the task.')) ->setConduitDescription(pht('Rename the task.')) ->setConduitTypeDescription(pht('New task name.')) diff --git a/src/applications/transactions/bulk/PhabricatorBulkEngine.php b/src/applications/transactions/bulk/PhabricatorBulkEngine.php --- a/src/applications/transactions/bulk/PhabricatorBulkEngine.php +++ b/src/applications/transactions/bulk/PhabricatorBulkEngine.php @@ -10,7 +10,10 @@ private $editableList; private $targetList; + private $rootFormID; + abstract public function newSearchEngine(); + abstract public function newEditEngine(); public function getCancelURI() { $saved_query = $this->savedQuery; @@ -118,7 +121,7 @@ array( 'action' => $this->getBulkURI(), 'method' => 'POST', - 'id' => 'maniphest-batch-edit-form', + 'id' => $this->getRootFormID(), ), array( $this->newContextInputs(), @@ -290,95 +293,60 @@ private function newBulkActionForm() { $viewer = $this->getViewer(); + $input_id = celerity_generate_unique_node_id(); - $cancel_uri = $this->getCancelURI(); - - $template = new AphrontTokenizerTemplateView(); - $template = $template->render(); - - $projects_source = new PhabricatorProjectDatasource(); - $mailable_source = new PhabricatorMetaMTAMailableDatasource(); - $mailable_source->setViewer($viewer); - $owner_source = new ManiphestAssigneeDatasource(); - $owner_source->setViewer($viewer); - $spaces_source = id(new PhabricatorSpacesNamespaceDatasource()) + $edit_engine = id($this->newEditEngine()) ->setViewer($viewer); - require_celerity_resource('maniphest-batch-editor'); + $edit_map = $edit_engine->newBulkEditMap(); + + require_celerity_resource('phui-bulk-editor-css'); Javelin::initBehavior( - 'maniphest-batch-editor', + 'bulk-editor', array( - 'root' => 'maniphest-batch-edit-form', - 'tokenizerTemplate' => $template, - 'sources' => array( - 'project' => array( - 'src' => $projects_source->getDatasourceURI(), - 'placeholder' => $projects_source->getPlaceholderText(), - 'browseURI' => $projects_source->getBrowseURI(), - ), - 'owner' => array( - 'src' => $owner_source->getDatasourceURI(), - 'placeholder' => $owner_source->getPlaceholderText(), - 'browseURI' => $owner_source->getBrowseURI(), - 'limit' => 1, - ), - 'cc' => array( - 'src' => $mailable_source->getDatasourceURI(), - 'placeholder' => $mailable_source->getPlaceholderText(), - 'browseURI' => $mailable_source->getBrowseURI(), - ), - 'spaces' => array( - 'src' => $spaces_source->getDatasourceURI(), - 'placeholder' => $spaces_source->getPlaceholderText(), - 'browseURI' => $spaces_source->getBrowseURI(), - 'limit' => 1, - ), - ), - 'input' => 'batch-form-actions', - 'priorityMap' => ManiphestTaskPriority::getTaskPriorityMap(), - 'statusMap' => ManiphestTaskStatus::getTaskStatusMap(), + 'rootNodeID' => $this->getRootFormID(), + 'inputNodeID' => $input_id, + 'edits' => $edit_map, )); - $form = id(new PHUIFormLayoutView()) - ->setUser($viewer); + $cancel_uri = $this->getCancelURI(); - $form->appendChild( - phutil_tag( - 'input', - array( - 'type' => 'hidden', - 'name' => 'actions', - 'id' => 'batch-form-actions', - ))); - - $form->appendChild( - id(new PHUIFormInsetView()) - ->setTitle(pht('Bulk Edit Actions')) - ->setRightButton( - javelin_tag( - 'a', - array( - 'href' => '#', - 'class' => 'button button-green', - 'sigil' => 'add-action', - 'mustcapture' => true, - ), - pht('Add Another Action'))) - ->setContent( - javelin_tag( - 'table', - array( - 'sigil' => 'maniphest-batch-actions', - 'class' => 'maniphest-batch-actions-table', - ), - ''))) + return id(new PHUIFormLayoutView()) + ->setViewer($viewer) + ->appendChild( + phutil_tag( + 'input', + array( + 'type' => 'hidden', + 'name' => 'xactions', + 'id' => $input_id, + ))) + ->appendChild( + id(new PHUIFormInsetView()) + ->setTitle(pht('Bulk Edit Actions')) + ->setRightButton( + javelin_tag( + 'a', + array( + 'href' => '#', + 'class' => 'button button-green', + 'sigil' => 'add-action', + 'mustcapture' => true, + ), + pht('Add Another Action'))) + ->setContent( + javelin_tag( + 'table', + array( + 'sigil' => 'bulk-actions', + 'class' => 'bulk-edit-table', + ), + ''))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Apply Bulk Edit')) ->addCancelButton($cancel_uri)); - - return $form; } private function buildEditResponse() { @@ -405,31 +373,33 @@ 'You have not selected any objects to edit.')); } - $raw_actions = $request->getStr('actions'); - if ($raw_actions) { - $actions = phutil_json_decode($raw_actions); + $raw_xactions = $request->getStr('xactions'); + if ($raw_xactions) { + $raw_xactions = phutil_json_decode($raw_xactions); } else { - $actions = array(); + $raw_xactions = array(); } - if (!$actions) { + if (!$raw_xactions) { throw new Exception( pht( 'You have not chosen any edits to apply.')); } + $edit_engine = id($this->newEditEngine()) + ->setViewer($viewer); + + $xactions = $edit_engine->newRawBulkTransactions($raw_xactions); + $cancel_uri = $this->getCancelURI(); $done_uri = $this->getDoneURI(); $job = PhabricatorWorkerBulkJob::initializeNewJob( $viewer, - // TODO: This is a Maniphest-specific job type for now, but will become - // a generic one so it gets to live here for now instead of in the task - // specific BulkEngine subclass. - new ManiphestTaskEditBulkJobType(), + new PhabricatorEditEngineBulkJobType(), array( - 'taskPHIDs' => mpull($objects, 'getPHID'), - 'actions' => $actions, + 'objectPHIDs' => mpull($objects, 'getPHID'), + 'xactions' => $xactions, 'cancelURI' => $cancel_uri, 'doneURI' => $done_uri, )); @@ -451,4 +421,12 @@ ->setURI($job->getMonitorURI()); } + private function getRootFormID() { + if (!$this->rootFormID) { + $this->rootFormID = celerity_generate_unique_node_id(); + } + + return $this->rootFormID; + } + } diff --git a/src/applications/transactions/bulk/type/BulkParameterType.php b/src/applications/transactions/bulk/type/BulkParameterType.php new file mode 100644 --- /dev/null +++ b/src/applications/transactions/bulk/type/BulkParameterType.php @@ -0,0 +1,24 @@ +viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + abstract public function getPHUIXControlType(); + + public function getPHUIXControlSpecification() { + return array( + 'value' => null, + ); + } + +} diff --git a/src/applications/transactions/bulk/type/BulkStringParameterType.php b/src/applications/transactions/bulk/type/BulkStringParameterType.php new file mode 100644 --- /dev/null +++ b/src/applications/transactions/bulk/type/BulkStringParameterType.php @@ -0,0 +1,10 @@ +loadDefaultConfiguration(); + if (!$config) { + throw new Exception( + pht('No default edit engine configuration for bulk edit.')); + } + + $object = $this->newEditableObject(); + $fields = $this->buildEditFields($object); + + $edit_types = $this->getBulkEditTypesFromFields($fields); + + $map = array(); + foreach ($edit_types as $key => $type) { + $bulk_type = $type->getBulkParameterType(); + if ($bulk_type === null) { + continue; + } + + $bulk_label = $type->getBulkEditLabel(); + if ($bulk_label === null) { + continue; + } + + $map[] = array( + 'label' => $bulk_label, + 'xaction' => $type->getTransactionType(), + 'control' => array( + 'type' => $bulk_type->getPHUIXControlType(), + 'spec' => (object)$bulk_type->getPHUIXControlSpecification(), + ), + ); + } + + return $map; + } + + + final public function newRawBulkTransactions(array $xactions) { + return $xactions; + } + + private function getBulkEditTypesFromFields(array $fields) { + $types = array(); + + foreach ($fields as $field) { + $field_types = $field->getBulkEditTypes(); + + if ($field_types === null) { + continue; + } + + foreach ($field_types as $field_type) { + $field_type->setField($field); + $types[$field_type->getEditType()] = $field_type; + } + } + + return $types; + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/transactions/editfield/PhabricatorEditField.php b/src/applications/transactions/editfield/PhabricatorEditField.php --- a/src/applications/transactions/editfield/PhabricatorEditField.php +++ b/src/applications/transactions/editfield/PhabricatorEditField.php @@ -17,6 +17,7 @@ private $previewPanel; private $controlID; private $controlInstructions; + private $bulkEditLabel; private $description; private $conduitDescription; @@ -45,6 +46,7 @@ private $isConduitOnly = false; private $conduitEditTypes; + private $bulkEditTypes; public function setKey($key) { $this->key = $key; @@ -64,6 +66,15 @@ return $this->label; } + public function setBulkEditLabel($bulk_edit_label) { + $this->bulkEditLabel = $bulk_edit_label; + return $this; + } + + public function getBulkEditLabel() { + return $this->bulkEditLabel; + } + public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; @@ -625,6 +636,22 @@ return new AphrontStringHTTPParameterType(); } + protected function getBulkParameterType() { + $type = $this->newBulkParameterType(); + + if (!$type) { + return null; + } + + $type->setViewer($this->getViewer()); + + return $type; + } + + protected function newBulkParameterType() { + return null; + } + public function getConduitParameterType() { $type = $this->newConduitParameterType(); @@ -657,8 +684,15 @@ return null; } - return id(new PhabricatorSimpleEditType()) + $edit_type = id(new PhabricatorSimpleEditType()) ->setConduitParameterType($parameter_type); + + $bulk_type = $this->getBulkParameterType(); + if ($bulk_type) { + $edit_type->setBulkParameterType($bulk_type); + } + + return $edit_type; } protected function getEditType() { @@ -718,6 +752,31 @@ return array($edit_type); } + final public function getBulkEditTypes() { + if ($this->bulkEditTypes === null) { + $edit_types = $this->newBulkEditTypes(); + $edit_types = mpull($edit_types, null, 'getEditType'); + + foreach ($edit_types as $edit_type) { + $edit_type->setEditField($this); + } + + $this->bulkEditTypes = $edit_types; + } + + return $this->bulkEditTypes; + } + + protected function newBulkEditTypes() { + $edit_type = $this->getEditType(); + + if (!$edit_type) { + return array(); + } + + return array($edit_type); + } + public function getCommentAction() { $label = $this->getCommentActionLabel(); if ($label === null) { diff --git a/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php b/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php --- a/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php +++ b/src/applications/transactions/editfield/PhabricatorPHIDListEditField.php @@ -104,6 +104,10 @@ return $type; } + protected function newBulkEditTypes() { + return $this->newConduitEditTypes(); + } + protected function newConduitEditTypes() { if (!$this->getUseEdgeTransactions()) { return parent::newConduitEditTypes(); diff --git a/src/applications/transactions/editfield/PhabricatorTextEditField.php b/src/applications/transactions/editfield/PhabricatorTextEditField.php --- a/src/applications/transactions/editfield/PhabricatorTextEditField.php +++ b/src/applications/transactions/editfield/PhabricatorTextEditField.php @@ -29,4 +29,8 @@ return new ConduitStringParameterType(); } + protected function newBulkParameterType() { + return new BulkStringParameterType(); + } + } diff --git a/src/applications/transactions/edittype/PhabricatorEditType.php b/src/applications/transactions/edittype/PhabricatorEditType.php --- a/src/applications/transactions/edittype/PhabricatorEditType.php +++ b/src/applications/transactions/edittype/PhabricatorEditType.php @@ -14,6 +14,9 @@ private $conduitTypeDescription; private $conduitParameterType; + private $bulkParameterType; + private $bulkEditLabel; + public function setLabel($label) { $this->label = $label; return $this; @@ -23,6 +26,19 @@ return $this->label; } + public function setBulkEditLabel($bulk_edit_label) { + $this->bulkEditLabel = $bulk_edit_label; + return $this; + } + + public function getBulkEditLabel() { + if ($this->bulkEditLabel !== null) { + return $this->bulkEditLabel; + } + + return $this->getField()->getBulkEditLabel(); + } + public function setField(PhabricatorEditField $field) { $this->field = $field; return $this; @@ -85,6 +101,30 @@ return $this->editField; } + +/* -( Bulk )--------------------------------------------------------------- */ + + + protected function newBulkParameterType() { + if ($this->bulkParameterType) { + return clone $this->bulkParameterType; + } + + return null; + } + + + public function setBulkParameterType(BulkParameterType $type) { + $this->bulkParameterType = $type; + return $this; + } + + + public function getBulkParameterType() { + return $this->newBulkParameterType(); + } + + /* -( Conduit )------------------------------------------------------------ */ diff --git a/webroot/rsrc/css/application/maniphest/batch-editor.css b/webroot/rsrc/css/application/maniphest/batch-editor.css deleted file mode 100644 --- a/webroot/rsrc/css/application/maniphest/batch-editor.css +++ /dev/null @@ -1,17 +0,0 @@ -/** - * @provides maniphest-batch-editor - */ -.maniphest-batch-actions-table { - width: 100%; - margin: 12px 0; -} - -.maniphest-batch-actions-table td { - padding: 4px 8px; - vertical-align: middle; -} - -.batch-editor-input { - width: 100%; - text-align: left; -} diff --git a/webroot/rsrc/css/phui/phui-bulk-editor.css b/webroot/rsrc/css/phui/phui-bulk-editor.css new file mode 100644 --- /dev/null +++ b/webroot/rsrc/css/phui/phui-bulk-editor.css @@ -0,0 +1,22 @@ +/** + * @provides phui-bulk-editor-css + */ + +.bulk-edit-table { + width: 100%; + margin: 12px 0; +} + +.bulk-edit-table td { + padding: 4px 8px; + vertical-align: middle; +} + +.bulk-edit-input { + width: 100%; + text-align: left; +} + +.bulk-edit-input input { + width: 100%; +} diff --git a/webroot/rsrc/js/application/maniphest/behavior-batch-editor.js b/webroot/rsrc/js/application/maniphest/behavior-batch-editor.js deleted file mode 100644 --- a/webroot/rsrc/js/application/maniphest/behavior-batch-editor.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * @provides javelin-behavior-maniphest-batch-editor - * @requires javelin-behavior - * javelin-dom - * javelin-util - * phabricator-prefab - * multirow-row-manager - * javelin-json - */ - -JX.behavior('maniphest-batch-editor', function(config) { - var root = JX.$(config.root); - var editor_table = JX.DOM.find(root, 'table', 'maniphest-batch-actions'); - var manager = new JX.MultirowRowManager(editor_table); - var action_rows = []; - - function renderRow() { - var action_select = JX.Prefab.renderSelect( - { - 'add_project': 'Add Projects', - 'remove_project' : 'Remove Projects', - 'priority': 'Change Priority', - 'status': 'Change Status', - 'add_comment': 'Comment', - 'assign': 'Assign', - 'add_ccs' : 'Add CCs', - 'remove_ccs' : 'Remove CCs', - 'space': 'Shift to Space' - }); - - var proj_tokenizer = build_tokenizer(config.sources.project); - var owner_tokenizer = build_tokenizer(config.sources.owner); - var cc_tokenizer = build_tokenizer(config.sources.cc); - var space_tokenizer = build_tokenizer(config.sources.spaces); - - var priority_select = JX.Prefab.renderSelect(config.priorityMap); - var status_select = JX.Prefab.renderSelect(config.statusMap); - var comment_input = JX.$N('input', {style: {width: '100%'}}); - - var cell = JX.$N('td', {className: 'batch-editor-input'}); - var vfunc = null; - - function update() { - switch (action_select.value) { - case 'add_project': - case 'remove_project': - JX.DOM.setContent(cell, proj_tokenizer.template); - vfunc = function() { - return JX.keys(proj_tokenizer.object.getTokens()); - }; - break; - case 'add_ccs': - case 'remove_ccs': - JX.DOM.setContent(cell, cc_tokenizer.template); - vfunc = function() { - return JX.keys(cc_tokenizer.object.getTokens()); - }; - break; - case 'assign': - JX.DOM.setContent(cell, owner_tokenizer.template); - vfunc = function() { - return JX.keys(owner_tokenizer.object.getTokens()); - }; - break; - case 'space': - JX.DOM.setContent(cell, space_tokenizer.template); - vfunc = function() { - return JX.keys(space_tokenizer.object.getTokens()); - }; - break; - case 'add_comment': - JX.DOM.setContent(cell, comment_input); - vfunc = function() { - return comment_input.value; - }; - break; - case 'priority': - JX.DOM.setContent(cell, priority_select); - vfunc = function() { return priority_select.value; }; - break; - case 'status': - JX.DOM.setContent(cell, status_select); - vfunc = function() { return status_select.value; }; - break; - } - } - - JX.DOM.listen(action_select, 'change', null, update); - update(); - - return { - nodes : [JX.$N('td', {}, action_select), cell], - dataCallback : function() { - return { - action: action_select.value, - value: vfunc() - }; - } - }; - } - - function onaddaction(e) { - e.kill(); - addRow({}); - } - - function addRow(info) { - var data = renderRow(info); - var row = manager.addRow(data.nodes); - var id = manager.getRowID(row); - - action_rows[id] = data.dataCallback; - } - - function onsubmit() { - var input = JX.$(config.input); - - var actions = []; - for (var k in action_rows) { - actions.push(action_rows[k]()); - } - - input.value = JX.JSON.stringify(actions); - } - - addRow({}); - - JX.DOM.listen( - root, - 'click', - 'add-action', - onaddaction); - - JX.DOM.listen( - root, - 'submit', - null, - onsubmit); - - manager.listen( - 'row-removed', - function(row_id) { - delete action_rows[row_id]; - }); - - function build_tokenizer(tconfig) { - var built = JX.Prefab.newTokenizerFromTemplate( - config.tokenizerTemplate, - JX.copy({}, tconfig)); - built.tokenizer.start(); - - return { - object: built.tokenizer, - template: built.node - }; - } - -}); diff --git a/webroot/rsrc/js/core/behavior-bulk-editor.js b/webroot/rsrc/js/core/behavior-bulk-editor.js new file mode 100644 --- /dev/null +++ b/webroot/rsrc/js/core/behavior-bulk-editor.js @@ -0,0 +1,113 @@ +/** + * @provides javelin-behavior-bulk-editor + * @requires javelin-behavior + * javelin-dom + * javelin-util + * phabricator-prefab + * multirow-row-manager + * javelin-json + * phuix-form-control-view + */ + +JX.behavior('bulk-editor', function(config) { + + var root = JX.$(config.rootNodeID); + var editor_table = JX.DOM.find(root, 'table', 'bulk-actions'); + + var manager = new JX.MultirowRowManager(editor_table); + var action_rows = []; + + var option_map = {}; + var option_order = []; + var spec_map = {}; + + for (var ii = 0; ii < config.edits.length; ii++) { + var edit = config.edits[ii]; + + option_map[edit.xaction] = edit.label; + option_order.push(edit.xaction); + + spec_map[edit.xaction] = edit; + } + + function renderRow() { + var action_select = JX.Prefab.renderSelect( + option_map, + null, + null, + option_order); + + var cell = JX.$N('td', {className: 'bulk-edit-input'}); + var vfunc = null; + + function update() { + var spec = spec_map[action_select.value]; + var control = spec.control; + + var phuix = new JX.PHUIXFormControl() + .setControl(control.type, control.spec); + + JX.DOM.setContent(cell, phuix.getRawInputNode()); + + vfunc = JX.bind(phuix, phuix.getValue); + } + + JX.DOM.listen(action_select, 'change', null, update); + update(); + + return { + nodes : [JX.$N('td', {}, action_select), cell], + dataCallback : function() { + return { + type: action_select.value, + value: vfunc() + }; + } + }; + } + + function onaddaction(e) { + e.kill(); + addRow({}); + } + + function addRow(info) { + var data = renderRow(info); + var row = manager.addRow(data.nodes); + var id = manager.getRowID(row); + + action_rows[id] = data.dataCallback; + } + + function onsubmit() { + var input = JX.$(config.inputNodeID); + + var actions = []; + for (var k in action_rows) { + actions.push(action_rows[k]()); + } + + input.value = JX.JSON.stringify(actions); + } + + addRow({}); + + JX.DOM.listen( + root, + 'click', + 'add-action', + onaddaction); + + JX.DOM.listen( + root, + 'submit', + null, + onsubmit); + + manager.listen( + 'row-removed', + function(row_id) { + delete action_rows[row_id]; + }); + +}); diff --git a/webroot/rsrc/js/phuix/PHUIXFormControl.js b/webroot/rsrc/js/phuix/PHUIXFormControl.js --- a/webroot/rsrc/js/phuix/PHUIXFormControl.js +++ b/webroot/rsrc/js/phuix/PHUIXFormControl.js @@ -14,6 +14,7 @@ _className: null, _valueSetCallback: null, _valueGetCallback: null, + _rawInputNode: null, setLabel: function(label) { JX.DOM.setContent(this._getLabelNode(), label); @@ -53,6 +54,9 @@ case 'checkboxes': input = this._newCheckboxes(spec); break; + case 'text': + input = this._newText(spec); + break; default: // TODO: Default or better error? JX.$E('Bad Input Type'); @@ -62,6 +66,7 @@ JX.DOM.setContent(node, input.node); this._valueGetCallback = input.get; this._valueSetCallback = input.set; + this._rawInputNode = input.node; return this; }, @@ -75,6 +80,10 @@ return this._valueGetCallback(); }, + getRawInputNode: function() { + return this._rawInputNode; + }, + getNode: function() { if (!this._node) { @@ -281,6 +290,10 @@ }, _newPoints: function(spec) { + return this._newText(); + }, + + _newText: function(spec) { var attrs = { type: 'text', value: spec.value