mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 14:52:41 +01:00
Use a tokenizer, not a gigantic poorly-ordered "<select />", to choose repositories in Owners
Summary: Depends on D19190. Fixes T12590. Ref T13099. Replaces the barely-usable, gigantic, poorly ordered "<select />" control with a tokenizer. Attempts to fix various minor issues. Test Plan: - Edited paths: include/exclude paths, from different repositories, different actual paths. - Used "Add New Path" to add rows, got repository selector prepopulated with last value. - Used "remove". - Used validation typeahead, got reasonable behaviors? The error behavior if you delete the repository for a path is a little sketchy still, but roughly okay. Maniphest Tasks: T13099, T12590 Differential Revision: https://secure.phabricator.com/D19191
This commit is contained in:
parent
b41a0e6ddd
commit
a4cc1373d3
8 changed files with 310 additions and 198 deletions
|
@ -10,7 +10,7 @@ return array(
|
||||||
'conpherence.pkg.css' => 'e68cf1fa',
|
'conpherence.pkg.css' => 'e68cf1fa',
|
||||||
'conpherence.pkg.js' => '15191c65',
|
'conpherence.pkg.js' => '15191c65',
|
||||||
'core.pkg.css' => '2fa91e14',
|
'core.pkg.css' => '2fa91e14',
|
||||||
'core.pkg.js' => 'e4d73c62',
|
'core.pkg.js' => '32bb68e9',
|
||||||
'darkconsole.pkg.js' => '1f9a31bc',
|
'darkconsole.pkg.js' => '1f9a31bc',
|
||||||
'differential.pkg.css' => '113e692c',
|
'differential.pkg.css' => '113e692c',
|
||||||
'differential.pkg.js' => 'f6d809c0',
|
'differential.pkg.js' => 'f6d809c0',
|
||||||
|
@ -85,7 +85,7 @@ return array(
|
||||||
'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b',
|
'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b',
|
||||||
'rsrc/css/application/maniphest/task-summary.css' => '11cc5344',
|
'rsrc/css/application/maniphest/task-summary.css' => '11cc5344',
|
||||||
'rsrc/css/application/objectselector/object-selector.css' => '85ee8ce6',
|
'rsrc/css/application/objectselector/object-selector.css' => '85ee8ce6',
|
||||||
'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b',
|
'rsrc/css/application/owners/owners-path-editor.css' => '9c136c29',
|
||||||
'rsrc/css/application/paste/paste.css' => '9fcc9773',
|
'rsrc/css/application/paste/paste.css' => '9fcc9773',
|
||||||
'rsrc/css/application/people/people-picture-menu-item.css' => 'a06f7f34',
|
'rsrc/css/application/people/people-picture-menu-item.css' => 'a06f7f34',
|
||||||
'rsrc/css/application/people/people-profile.css' => '4df76faf',
|
'rsrc/css/application/people/people-profile.css' => '4df76faf',
|
||||||
|
@ -268,7 +268,7 @@ return array(
|
||||||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd',
|
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd',
|
||||||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '013ffff9',
|
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '013ffff9',
|
||||||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0',
|
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0',
|
||||||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '0fcf201c',
|
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => 'ab9e0a82',
|
||||||
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '6c0e62fa',
|
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '6c0e62fa',
|
||||||
'rsrc/favicons/apple-touch-icon-114x114.png' => '12a24178',
|
'rsrc/favicons/apple-touch-icon-114x114.png' => '12a24178',
|
||||||
'rsrc/favicons/apple-touch-icon-120x120.png' => '0d1543c7',
|
'rsrc/favicons/apple-touch-icon-120x120.png' => '0d1543c7',
|
||||||
|
@ -418,13 +418,13 @@ return array(
|
||||||
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
|
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
|
||||||
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909',
|
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909',
|
||||||
'rsrc/js/application/herald/HeraldRuleEditor.js' => 'dca75c0e',
|
'rsrc/js/application/herald/HeraldRuleEditor.js' => 'dca75c0e',
|
||||||
'rsrc/js/application/herald/PathTypeahead.js' => '78039abe',
|
'rsrc/js/application/herald/PathTypeahead.js' => '662e9cea',
|
||||||
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
|
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
|
||||||
'rsrc/js/application/maniphest/behavior-batch-selector.js' => 'ad54037e',
|
'rsrc/js/application/maniphest/behavior-batch-selector.js' => 'ad54037e',
|
||||||
'rsrc/js/application/maniphest/behavior-line-chart.js' => 'e4232876',
|
'rsrc/js/application/maniphest/behavior-line-chart.js' => 'e4232876',
|
||||||
'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2',
|
'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2',
|
||||||
'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '71237763',
|
'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '71237763',
|
||||||
'rsrc/js/application/owners/OwnersPathEditor.js' => '52b9cbc4',
|
'rsrc/js/application/owners/OwnersPathEditor.js' => 'c96502cf',
|
||||||
'rsrc/js/application/owners/owners-path-editor.js' => '7a68dda3',
|
'rsrc/js/application/owners/owners-path-editor.js' => '7a68dda3',
|
||||||
'rsrc/js/application/passphrase/passphrase-credential-control.js' => '3cb0b2fc',
|
'rsrc/js/application/passphrase/passphrase-credential-control.js' => '3cb0b2fc',
|
||||||
'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => 'bee502c8',
|
'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => 'bee502c8',
|
||||||
|
@ -534,7 +534,7 @@ return array(
|
||||||
'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac',
|
'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac',
|
||||||
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03',
|
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03',
|
||||||
'rsrc/js/phuix/PHUIXExample.js' => '68af71ca',
|
'rsrc/js/phuix/PHUIXExample.js' => '68af71ca',
|
||||||
'rsrc/js/phuix/PHUIXFormControl.js' => '16ad6224',
|
'rsrc/js/phuix/PHUIXFormControl.js' => '210a16c1',
|
||||||
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
|
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
|
||||||
),
|
),
|
||||||
'symbols' => array(
|
'symbols' => array(
|
||||||
|
@ -744,7 +744,7 @@ return array(
|
||||||
'javelin-typeahead-normalizer' => '185bbd53',
|
'javelin-typeahead-normalizer' => '185bbd53',
|
||||||
'javelin-typeahead-ondemand-source' => '013ffff9',
|
'javelin-typeahead-ondemand-source' => '013ffff9',
|
||||||
'javelin-typeahead-preloaded-source' => '54f314a0',
|
'javelin-typeahead-preloaded-source' => '54f314a0',
|
||||||
'javelin-typeahead-source' => '0fcf201c',
|
'javelin-typeahead-source' => 'ab9e0a82',
|
||||||
'javelin-typeahead-static-source' => '6c0e62fa',
|
'javelin-typeahead-static-source' => '6c0e62fa',
|
||||||
'javelin-uri' => 'c989ade3',
|
'javelin-uri' => 'c989ade3',
|
||||||
'javelin-util' => '93cc50d6',
|
'javelin-util' => '93cc50d6',
|
||||||
|
@ -764,10 +764,10 @@ return array(
|
||||||
'maniphest-task-edit-css' => 'fda62a9b',
|
'maniphest-task-edit-css' => 'fda62a9b',
|
||||||
'maniphest-task-summary-css' => '11cc5344',
|
'maniphest-task-summary-css' => '11cc5344',
|
||||||
'multirow-row-manager' => 'b5d57730',
|
'multirow-row-manager' => 'b5d57730',
|
||||||
'owners-path-editor' => '52b9cbc4',
|
'owners-path-editor' => 'c96502cf',
|
||||||
'owners-path-editor-css' => '2f00933b',
|
'owners-path-editor-css' => '9c136c29',
|
||||||
'paste-css' => '9fcc9773',
|
'paste-css' => '9fcc9773',
|
||||||
'path-typeahead' => '78039abe',
|
'path-typeahead' => '662e9cea',
|
||||||
'people-picture-menu-item-css' => 'a06f7f34',
|
'people-picture-menu-item-css' => 'a06f7f34',
|
||||||
'people-profile-css' => '4df76faf',
|
'people-profile-css' => '4df76faf',
|
||||||
'phabricator-action-list-view-css' => '0bcd9a45',
|
'phabricator-action-list-view-css' => '0bcd9a45',
|
||||||
|
@ -888,7 +888,7 @@ return array(
|
||||||
'phuix-autocomplete' => '7fa5c915',
|
'phuix-autocomplete' => '7fa5c915',
|
||||||
'phuix-button-view' => '8a91e1ac',
|
'phuix-button-view' => '8a91e1ac',
|
||||||
'phuix-dropdown-menu' => '04b2ae03',
|
'phuix-dropdown-menu' => '04b2ae03',
|
||||||
'phuix-form-control-view' => '16ad6224',
|
'phuix-form-control-view' => '210a16c1',
|
||||||
'phuix-icon-view' => 'bff6884b',
|
'phuix-icon-view' => 'bff6884b',
|
||||||
'policy-css' => '957ea14c',
|
'policy-css' => '957ea14c',
|
||||||
'policy-edit-css' => '815c66f7',
|
'policy-edit-css' => '815c66f7',
|
||||||
|
@ -998,20 +998,10 @@ return array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
),
|
),
|
||||||
'0fcf201c' => array(
|
|
||||||
'javelin-install',
|
|
||||||
'javelin-util',
|
|
||||||
'javelin-dom',
|
|
||||||
'javelin-typeahead-normalizer',
|
|
||||||
),
|
|
||||||
'15d5ff71' => array(
|
'15d5ff71' => array(
|
||||||
'aphront-typeahead-control-css',
|
'aphront-typeahead-control-css',
|
||||||
'phui-tag-view-css',
|
'phui-tag-view-css',
|
||||||
),
|
),
|
||||||
'16ad6224' => array(
|
|
||||||
'javelin-install',
|
|
||||||
'javelin-dom',
|
|
||||||
),
|
|
||||||
'17bb8539' => array(
|
'17bb8539' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-stratcom',
|
'javelin-stratcom',
|
||||||
|
@ -1061,6 +1051,10 @@ return array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
),
|
),
|
||||||
|
'210a16c1' => array(
|
||||||
|
'javelin-install',
|
||||||
|
'javelin-dom',
|
||||||
|
),
|
||||||
'2290aeef' => array(
|
'2290aeef' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
|
@ -1337,14 +1331,6 @@ return array(
|
||||||
'javelin-vector',
|
'javelin-vector',
|
||||||
'javelin-typeahead-static-source',
|
'javelin-typeahead-static-source',
|
||||||
),
|
),
|
||||||
'52b9cbc4' => array(
|
|
||||||
'multirow-row-manager',
|
|
||||||
'javelin-install',
|
|
||||||
'path-typeahead',
|
|
||||||
'javelin-dom',
|
|
||||||
'javelin-util',
|
|
||||||
'phabricator-prefab',
|
|
||||||
),
|
|
||||||
'54b612ba' => array(
|
'54b612ba' => array(
|
||||||
'javelin-color',
|
'javelin-color',
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
|
@ -1439,6 +1425,14 @@ return array(
|
||||||
'javelin-workflow',
|
'javelin-workflow',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
),
|
),
|
||||||
|
'662e9cea' => array(
|
||||||
|
'javelin-install',
|
||||||
|
'javelin-typeahead',
|
||||||
|
'javelin-dom',
|
||||||
|
'javelin-request',
|
||||||
|
'javelin-typeahead-ondemand-source',
|
||||||
|
'javelin-util',
|
||||||
|
),
|
||||||
'66a6def1' => array(
|
'66a6def1' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-dom',
|
'javelin-dom',
|
||||||
|
@ -1542,14 +1536,6 @@ return array(
|
||||||
'javelin-request',
|
'javelin-request',
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
),
|
),
|
||||||
'78039abe' => array(
|
|
||||||
'javelin-install',
|
|
||||||
'javelin-typeahead',
|
|
||||||
'javelin-dom',
|
|
||||||
'javelin-request',
|
|
||||||
'javelin-typeahead-ondemand-source',
|
|
||||||
'javelin-util',
|
|
||||||
),
|
|
||||||
'7927a7d3' => array(
|
'7927a7d3' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-quicksand',
|
'javelin-quicksand',
|
||||||
|
@ -1799,6 +1785,12 @@ return array(
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
'phabricator-busy',
|
'phabricator-busy',
|
||||||
),
|
),
|
||||||
|
'ab9e0a82' => array(
|
||||||
|
'javelin-install',
|
||||||
|
'javelin-util',
|
||||||
|
'javelin-dom',
|
||||||
|
'javelin-typeahead-normalizer',
|
||||||
|
),
|
||||||
'acd29eee' => array(
|
'acd29eee' => array(
|
||||||
'javelin-behavior',
|
'javelin-behavior',
|
||||||
'javelin-stratcom',
|
'javelin-stratcom',
|
||||||
|
@ -1974,6 +1966,15 @@ return array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
),
|
),
|
||||||
|
'c96502cf' => array(
|
||||||
|
'multirow-row-manager',
|
||||||
|
'javelin-install',
|
||||||
|
'path-typeahead',
|
||||||
|
'javelin-dom',
|
||||||
|
'javelin-util',
|
||||||
|
'phabricator-prefab',
|
||||||
|
'phuix-form-control-view',
|
||||||
|
),
|
||||||
'c989ade3' => array(
|
'c989ade3' => array(
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
|
|
|
@ -45,19 +45,6 @@ final class DiffusionPathValidateController extends DiffusionController {
|
||||||
'valid' => (bool)$valid,
|
'valid' => (bool)$valid,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!$valid) {
|
|
||||||
$branch = $drequest->getBranch();
|
|
||||||
if ($branch) {
|
|
||||||
$message = pht('Not found in %s', $branch);
|
|
||||||
} else {
|
|
||||||
$message = pht('Not found at %s', 'HEAD');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$message = pht('OK');
|
|
||||||
}
|
|
||||||
|
|
||||||
$output['message'] = $message;
|
|
||||||
|
|
||||||
return id(new AphrontAjaxResponse())->setContent($output);
|
return id(new AphrontAjaxResponse())->setContent($output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ final class PhabricatorOwnersPathsController
|
||||||
|
|
||||||
$path_refs = array();
|
$path_refs = array();
|
||||||
foreach ($paths as $key => $path) {
|
foreach ($paths as $key => $path) {
|
||||||
if (!isset($repos[$key])) {
|
if (!isset($repos[$key]) || !strlen($repos[$key])) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
'No repository PHID for path "%s"!',
|
'No repository PHID for path "%s"!',
|
||||||
|
@ -70,17 +70,39 @@ final class PhabricatorOwnersPathsController
|
||||||
$path_refs = mpull($paths, 'getRef');
|
$path_refs = mpull($paths, 'getRef');
|
||||||
}
|
}
|
||||||
|
|
||||||
$repos = id(new PhabricatorRepositoryQuery())
|
$template = new AphrontTokenizerTemplateView();
|
||||||
->setViewer($viewer)
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$repo_map = array();
|
$datasource = id(new DiffusionRepositoryDatasource())
|
||||||
foreach ($repos as $key => $repo) {
|
->setViewer($viewer);
|
||||||
$monogram = $repo->getMonogram();
|
|
||||||
$name = $repo->getName();
|
$tokenizer_spec = array(
|
||||||
$repo_map[$repo->getPHID()] = "{$monogram} {$name}";
|
'markup' => $template->render(),
|
||||||
|
'config' => array(
|
||||||
|
'src' => $datasource->getDatasourceURI(),
|
||||||
|
'browseURI' => $datasource->getBrowseURI(),
|
||||||
|
'placeholder' => $datasource->getPlaceholderText(),
|
||||||
|
'limit' => 1,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($path_refs as $key => $path_ref) {
|
||||||
|
$path_refs[$key]['repositoryValue'] = $datasource->getWireTokens(
|
||||||
|
array(
|
||||||
|
$path_ref['repositoryPHID'],
|
||||||
|
));
|
||||||
}
|
}
|
||||||
asort($repos);
|
|
||||||
|
$icon_test = id(new PHUIIconView())
|
||||||
|
->setIcon('fa-spinner grey')
|
||||||
|
->setTooltip(pht('Validating...'));
|
||||||
|
|
||||||
|
$icon_okay = id(new PHUIIconView())
|
||||||
|
->setIcon('fa-check-circle green')
|
||||||
|
->setTooltip(pht('Path Exists in Repository'));
|
||||||
|
|
||||||
|
$icon_fail = id(new PHUIIconView())
|
||||||
|
->setIcon('fa-question-circle-o red')
|
||||||
|
->setTooltip(pht('Path Not Found On Default Branch'));
|
||||||
|
|
||||||
$template = new AphrontTypeaheadTemplateView();
|
$template = new AphrontTypeaheadTemplateView();
|
||||||
$template = $template->render();
|
$template = $template->render();
|
||||||
|
@ -88,15 +110,23 @@ final class PhabricatorOwnersPathsController
|
||||||
Javelin::initBehavior(
|
Javelin::initBehavior(
|
||||||
'owners-path-editor',
|
'owners-path-editor',
|
||||||
array(
|
array(
|
||||||
'root' => 'path-editor',
|
'root' => 'path-editor',
|
||||||
'table' => 'paths',
|
'table' => 'paths',
|
||||||
'add_button' => 'addpath',
|
'add_button' => 'addpath',
|
||||||
'repositories' => $repo_map,
|
'input_template' => $template,
|
||||||
'input_template' => $template,
|
'pathRefs' => $path_refs,
|
||||||
'pathRefs' => $path_refs,
|
'completeURI' => '/diffusion/services/path/complete/',
|
||||||
|
'validateURI' => '/diffusion/services/path/validate/',
|
||||||
'completeURI' => '/diffusion/services/path/complete/',
|
'repositoryTokenizerSpec' => $tokenizer_spec,
|
||||||
'validateURI' => '/diffusion/services/path/validate/',
|
'icons' => array(
|
||||||
|
'test' => hsprintf('%s', $icon_test),
|
||||||
|
'okay' => hsprintf('%s', $icon_okay),
|
||||||
|
'fail' => hsprintf('%s', $icon_fail),
|
||||||
|
),
|
||||||
|
'modeOptions' => array(
|
||||||
|
0 => pht('Include'),
|
||||||
|
1 => pht('Exclude'),
|
||||||
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
require_celerity_resource('owners-path-editor-css');
|
require_celerity_resource('owners-path-editor-css');
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.owners-path-editor-table {
|
.owners-path-editor-table {
|
||||||
margin: 10px;
|
margin: 10px 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.owners-path-editor-table td {
|
.owners-path-editor-table td {
|
||||||
|
@ -11,27 +12,38 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.owners-path-editor-table select.owners-repo {
|
.owners-path-editor-table td.owners-path-mode-control {
|
||||||
width: 150px;
|
width: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.owners-path-editor-table input {
|
.owners-path-editor-table td.owners-path-mode-control select {
|
||||||
width: 400px;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.owners-path-editor-table div.error-display {
|
.owners-path-editor-table td.owners-path-repo-control {
|
||||||
padding: 4px 12px 0;
|
width: 280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.owners-path-editor-table div.validating {
|
.owners-path-editor-table td.owners-path-path-control {
|
||||||
color: {$greytext};
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.owners-path-editor-table div.invalid {
|
.owners-path-editor-table td.owners-path-path-control input {
|
||||||
color: #aa0000;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.owners-path-editor-table div.valid {
|
.owners-path-editor-table td.owners-path-path-control .jx-typeahead-results a {
|
||||||
color: #00aa00;
|
padding: 4px;
|
||||||
font-weight: bold;
|
}
|
||||||
|
|
||||||
|
.owners-path-editor-table td.owners-path-icon-control {
|
||||||
|
width: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.owners-path-editor-table td.remove-column {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.owners-path-editor-table td.remove-column a {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,7 @@
|
||||||
|
|
||||||
JX.install('TypeaheadSource', {
|
JX.install('TypeaheadSource', {
|
||||||
construct : function() {
|
construct : function() {
|
||||||
this._raw = {};
|
this.resetResults();
|
||||||
this._lookup = {};
|
|
||||||
this.setNormalizer(JX.TypeaheadNormalizer.normalize);
|
this.setNormalizer(JX.TypeaheadNormalizer.normalize);
|
||||||
this._excludeIDs = {};
|
this._excludeIDs = {};
|
||||||
},
|
},
|
||||||
|
@ -359,6 +358,12 @@ JX.install('TypeaheadSource', {
|
||||||
}
|
}
|
||||||
return str.split(/\s+/g);
|
return str.split(/\s+/g);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resetResults: function() {
|
||||||
|
this._raw = {};
|
||||||
|
this._lookup = {};
|
||||||
|
},
|
||||||
|
|
||||||
_defaultTransformer : function(object) {
|
_defaultTransformer : function(object) {
|
||||||
return {
|
return {
|
||||||
name : object[0],
|
name : object[0],
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
JX.install('PathTypeahead', {
|
JX.install('PathTypeahead', {
|
||||||
construct : function(config) {
|
construct : function(config) {
|
||||||
this._repositorySelect = config.repo_select;
|
this._repositoryTokenizer = config.repositoryTokenizer;
|
||||||
this._hardpoint = config.hardpoint;
|
this._hardpoint = config.hardpoint;
|
||||||
this._input = config.path_input;
|
this._input = config.path_input;
|
||||||
this._completeURI = config.completeURI;
|
this._completeURI = config.completeURI;
|
||||||
|
@ -19,14 +19,14 @@ JX.install('PathTypeahead', {
|
||||||
this._errorDisplay = config.error_display;
|
this._errorDisplay = config.error_display;
|
||||||
this._textInputValues = {};
|
this._textInputValues = {};
|
||||||
|
|
||||||
|
this._icons = config.icons;
|
||||||
|
|
||||||
this._initializeDatasource();
|
this._initializeDatasource();
|
||||||
this._initializeTypeahead(this._input);
|
this._initializeTypeahead(this._input);
|
||||||
},
|
},
|
||||||
members : {
|
members : {
|
||||||
/*
|
_repositoryTokenizer : null,
|
||||||
* DOM <select> elem for choosing the repository of a path.
|
|
||||||
*/
|
|
||||||
_repositorySelect : null,
|
|
||||||
/*
|
/*
|
||||||
* DOM parent div "hardpoint" to be passed to the JX.Typeahead.
|
* DOM parent div "hardpoint" to be passed to the JX.Typeahead.
|
||||||
*/
|
*/
|
||||||
|
@ -79,31 +79,28 @@ JX.install('PathTypeahead', {
|
||||||
*/
|
*/
|
||||||
start : function() {
|
start : function() {
|
||||||
if (this._typeahead.getValue()) {
|
if (this._typeahead.getValue()) {
|
||||||
this._textInputValues[this._repositorySelect.value] =
|
var phid = this._getRepositoryPHID();
|
||||||
this._typeahead.getValue();
|
if (phid) {
|
||||||
|
this._textInputValues[phid] = this._typeahead.getValue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._typeahead.listen(
|
this._typeahead.listen(
|
||||||
'change',
|
'change',
|
||||||
JX.bind(this, function(value) {
|
JX.bind(this, function(value) {
|
||||||
this._textInputValues[this._repositorySelect.value] = value;
|
var phid = this._getRepositoryPHID();
|
||||||
this._validate();
|
if (phid) {
|
||||||
}));
|
this._textInputValues[phid] = value;
|
||||||
|
}
|
||||||
|
|
||||||
this._typeahead.listen(
|
this._validate();
|
||||||
'choose',
|
|
||||||
JX.bind(this, function() {
|
|
||||||
setTimeout(JX.bind(this._typeahead, this._typeahead.refresh), 0);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
var repo_set_input = JX.bind(this, this._onrepochange);
|
var repo_set_input = JX.bind(this, this._onrepochange);
|
||||||
|
|
||||||
this._typeahead.listen('start', repo_set_input);
|
this._typeahead.listen('start', repo_set_input);
|
||||||
JX.DOM.listen(
|
|
||||||
this._repositorySelect,
|
this._repositoryTokenizer.listen('change', repo_set_input);
|
||||||
'change',
|
|
||||||
null,
|
|
||||||
repo_set_input);
|
|
||||||
|
|
||||||
this._typeahead.start();
|
this._typeahead.start();
|
||||||
this._validate();
|
this._validate();
|
||||||
|
@ -115,13 +112,18 @@ JX.install('PathTypeahead', {
|
||||||
this._textInputValues);
|
this._textInputValues);
|
||||||
|
|
||||||
this._datasource.setAuxiliaryData(
|
this._datasource.setAuxiliaryData(
|
||||||
{repositoryPHID : this._repositorySelect.value}
|
{
|
||||||
);
|
repositoryPHID: this._getRepositoryPHID()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Since we've changed the repository, reset the results.
|
||||||
|
this._datasource.resetResults();
|
||||||
},
|
},
|
||||||
|
|
||||||
_setPathInputBasedOnRepository : function(typeahead, lookup) {
|
_setPathInputBasedOnRepository : function(typeahead, lookup) {
|
||||||
if (lookup[this._repositorySelect.value]) {
|
var phid = this._getRepositoryPHID();
|
||||||
typeahead.setValue(lookup[this._repositorySelect.value]);
|
if (phid && lookup[phid]) {
|
||||||
|
typeahead.setValue(lookup[phid]);
|
||||||
} else {
|
} else {
|
||||||
typeahead.setValue('/');
|
typeahead.setValue('/');
|
||||||
}
|
}
|
||||||
|
@ -147,9 +149,24 @@ JX.install('PathTypeahead', {
|
||||||
return ('' + str).replace(/[\/]+/g, '\/');
|
return ('' + str).replace(/[\/]+/g, '\/');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_getRepositoryPHID: function() {
|
||||||
|
var tokens = this._repositoryTokenizer.getTokens();
|
||||||
|
var keys = JX.keys(tokens);
|
||||||
|
|
||||||
|
if (keys.length) {
|
||||||
|
return keys[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
_validate : function() {
|
_validate : function() {
|
||||||
|
var repo_phid = this._getRepositoryPHID();
|
||||||
|
if (!repo_phid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var input = this._input;
|
var input = this._input;
|
||||||
var repo_id = this._repositorySelect.value;
|
|
||||||
var input_value = input.value;
|
var input_value = input.value;
|
||||||
var error_display = this._errorDisplay;
|
var error_display = this._errorDisplay;
|
||||||
|
|
||||||
|
@ -165,33 +182,32 @@ JX.install('PathTypeahead', {
|
||||||
|
|
||||||
var validation_request = new JX.Request(
|
var validation_request = new JX.Request(
|
||||||
this._validateURI,
|
this._validateURI,
|
||||||
function(payload) {
|
JX.bind(this, function(payload) {
|
||||||
// Don't change validation display state if the input has been
|
// Don't change validation display state if the input has been
|
||||||
// changed since we started validation
|
// changed since we started validation
|
||||||
if (input.value === input_value) {
|
if (input.value !== input_value) {
|
||||||
if (payload.valid) {
|
return;
|
||||||
JX.DOM.alterClass(error_display, 'invalid', false);
|
|
||||||
JX.DOM.alterClass(error_display, 'valid', true);
|
|
||||||
} else {
|
|
||||||
JX.DOM.alterClass(error_display, 'invalid', true);
|
|
||||||
JX.DOM.alterClass(error_display, 'valid', false);
|
|
||||||
}
|
|
||||||
JX.DOM.setContent(error_display, payload.message);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
if (payload.valid) {
|
||||||
|
JX.DOM.setContent(error_display, JX.$H(this._icons.okay));
|
||||||
|
} else {
|
||||||
|
JX.DOM.setContent(error_display, JX.$H(this._icons.fail));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
validation_request.listen('finally', function() {
|
validation_request.listen('finally', function() {
|
||||||
JX.DOM.alterClass(error_display, 'validating', false);
|
|
||||||
this._validationInflight = null;
|
this._validationInflight = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
validation_request.setData(
|
validation_request.setData(
|
||||||
{
|
{
|
||||||
repositoryPHID : repo_id,
|
repositoryPHID : repo_phid,
|
||||||
path : input_value
|
path : input_value
|
||||||
});
|
});
|
||||||
|
|
||||||
this._validationInflight = validation_request;
|
this._validationInflight = validation_request;
|
||||||
|
JX.DOM.setContent(error_display, JX.$H(this._icons.test));
|
||||||
|
|
||||||
validation_request.setTimeout(750);
|
validation_request.setTimeout(750);
|
||||||
validation_request.send();
|
validation_request.send();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
* javelin-dom
|
* javelin-dom
|
||||||
* javelin-util
|
* javelin-util
|
||||||
* phabricator-prefab
|
* phabricator-prefab
|
||||||
|
* phuix-form-control-view
|
||||||
* @provides owners-path-editor
|
* @provides owners-path-editor
|
||||||
* @javelin
|
* @javelin
|
||||||
*/
|
*/
|
||||||
|
@ -23,11 +24,13 @@ JX.install('OwnersPathEditor', {
|
||||||
JX.bind(this, this._onaddpath));
|
JX.bind(this, this._onaddpath));
|
||||||
|
|
||||||
this._count = 0;
|
this._count = 0;
|
||||||
this._repositories = config.repositories;
|
|
||||||
this._inputTemplate = config.input_template;
|
this._inputTemplate = config.input_template;
|
||||||
|
this._repositoryTokenizerSpec = config.repositoryTokenizerSpec;
|
||||||
|
|
||||||
this._completeURI = config.completeURI;
|
this._completeURI = config.completeURI;
|
||||||
this._validateURI = config.validateURI;
|
this._validateURI = config.validateURI;
|
||||||
|
this._icons = config.icons;
|
||||||
|
this._modeOptions = config.modeOptions;
|
||||||
|
|
||||||
this._initializePaths(config.pathRefs);
|
this._initializePaths(config.pathRefs);
|
||||||
},
|
},
|
||||||
|
@ -37,12 +40,6 @@ JX.install('OwnersPathEditor', {
|
||||||
*/
|
*/
|
||||||
_rowManager : null,
|
_rowManager : null,
|
||||||
|
|
||||||
/*
|
|
||||||
* Array of objects with 'name' and 'repo_id' keys for
|
|
||||||
* selecting the repository of a path.
|
|
||||||
*/
|
|
||||||
_repositories : null,
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* How many rows have been created, for form name generation.
|
* How many rows have been created, for form name generation.
|
||||||
*/
|
*/
|
||||||
|
@ -65,6 +62,8 @@ JX.install('OwnersPathEditor', {
|
||||||
* default for future rows.
|
* default for future rows.
|
||||||
*/
|
*/
|
||||||
_lastRepositoryChoice : null,
|
_lastRepositoryChoice : null,
|
||||||
|
_icons: null,
|
||||||
|
_modeOptions: null,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize with 0 or more rows.
|
* Initialize with 0 or more rows.
|
||||||
|
@ -85,68 +84,34 @@ JX.install('OwnersPathEditor', {
|
||||||
addPath : function(path_ref) {
|
addPath : function(path_ref) {
|
||||||
// Smart default repository. See _lastRepositoryChoice.
|
// Smart default repository. See _lastRepositoryChoice.
|
||||||
if (path_ref) {
|
if (path_ref) {
|
||||||
this._lastRepositoryChoice = path_ref.repositoryPHID;
|
this._lastRepositoryChoice = path_ref.repositoryValue;
|
||||||
|
} else {
|
||||||
|
path_ref = {
|
||||||
|
repositoryValue: this._lastRepositoryChoice || {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
path_ref = path_ref || {};
|
|
||||||
|
|
||||||
var selected_repository = path_ref.repositoryPHID ||
|
var repo = this._newRepoCell(path_ref.repositoryValue);
|
||||||
this._lastRepositoryChoice;
|
var path = this._newPathCell(path_ref.display);
|
||||||
var options = this._buildRepositoryOptions(selected_repository);
|
var icon = this._newIconCell();
|
||||||
var attrs = {
|
var mode_cell = this._newModeCell(path_ref.excluded);
|
||||||
name : 'repo[' + this._count + ']',
|
|
||||||
className : 'owners-repo'
|
|
||||||
};
|
|
||||||
var repo_select = JX.$N('select', attrs, options);
|
|
||||||
|
|
||||||
JX.DOM.listen(repo_select, 'change', null, JX.bind(this, function() {
|
|
||||||
this._lastRepositoryChoice = repo_select.value;
|
|
||||||
}));
|
|
||||||
|
|
||||||
var repo_cell = JX.$N('td', {}, repo_select);
|
|
||||||
var typeahead_cell = JX.$N(
|
|
||||||
'td',
|
|
||||||
JX.$H(this._inputTemplate));
|
|
||||||
|
|
||||||
// Text input for path.
|
|
||||||
var path_input = JX.DOM.find(typeahead_cell, 'input');
|
|
||||||
JX.copy(
|
|
||||||
path_input,
|
|
||||||
{
|
|
||||||
value : path_ref.display || '',
|
|
||||||
name : 'path[' + this._count + ']'
|
|
||||||
});
|
|
||||||
|
|
||||||
// The Typeahead requires a display div called hardpoint.
|
|
||||||
var hardpoint = JX.DOM.find(
|
|
||||||
typeahead_cell,
|
|
||||||
'div',
|
|
||||||
'typeahead-hardpoint');
|
|
||||||
|
|
||||||
var error_display = JX.$N(
|
|
||||||
'div',
|
|
||||||
{
|
|
||||||
className : 'error-display validating'
|
|
||||||
},
|
|
||||||
'Validating...');
|
|
||||||
|
|
||||||
var error_display_cell = JX.$N('td', {}, error_display);
|
|
||||||
|
|
||||||
var exclude = JX.Prefab.renderSelect(
|
|
||||||
{'0' : 'Include', '1' : 'Exclude'},
|
|
||||||
path_ref.excluded,
|
|
||||||
{name : 'exclude[' + this._count + ']'});
|
|
||||||
var exclude_cell = JX.$N('td', {}, exclude);
|
|
||||||
|
|
||||||
var row = this._rowManager.addRow(
|
var row = this._rowManager.addRow(
|
||||||
[exclude_cell, repo_cell, typeahead_cell, error_display_cell]);
|
[
|
||||||
|
mode_cell,
|
||||||
|
repo.cell,
|
||||||
|
path.cell,
|
||||||
|
icon.cell
|
||||||
|
]);
|
||||||
|
|
||||||
new JX.PathTypeahead({
|
new JX.PathTypeahead({
|
||||||
repo_select : repo_select,
|
repositoryTokenizer: repo.tokenizer,
|
||||||
path_input : path_input,
|
path_input : path.input,
|
||||||
hardpoint : hardpoint,
|
hardpoint : path.hardpoint,
|
||||||
error_display : error_display,
|
error_display : icon.cell,
|
||||||
completeURI : this._completeURI,
|
completeURI : this._completeURI,
|
||||||
validateURI : this._validateURI
|
validateURI : this._validateURI,
|
||||||
|
icons: this._icons
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
this._count++;
|
this._count++;
|
||||||
|
@ -158,20 +123,109 @@ JX.install('OwnersPathEditor', {
|
||||||
this.addPath();
|
this.addPath();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
_newModeCell: function(value) {
|
||||||
* Helper to build the options for the repository choice dropdown.
|
var options = this._modeOptions;
|
||||||
*/
|
|
||||||
_buildRepositoryOptions : function(selected) {
|
var name = 'exclude[' + this._count + ']';
|
||||||
var repos = this._repositories;
|
|
||||||
var result = [];
|
var control = JX.Prefab.renderSelect(options, value, {name: name});
|
||||||
for (var k in repos) {
|
|
||||||
var attr = {
|
return JX.$N(
|
||||||
value : k,
|
'td',
|
||||||
selected : (selected == k)
|
{
|
||||||
};
|
className: 'owners-path-mode-control'
|
||||||
result.push(JX.$N('option', attr, repos[k]));
|
},
|
||||||
|
control);
|
||||||
|
},
|
||||||
|
|
||||||
|
_newRepoCell: function(value) {
|
||||||
|
var repo_control = new JX.PHUIXFormControl()
|
||||||
|
.setControl('tokenizer', this._repositoryTokenizerSpec)
|
||||||
|
.setValue(value);
|
||||||
|
|
||||||
|
var repo_tokenizer = repo_control.getTokenizer();
|
||||||
|
var name = 'repo[' + this._count + ']';
|
||||||
|
|
||||||
|
function get_phid() {
|
||||||
|
var phids = repo_control.getValue();
|
||||||
|
if (!phids.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return phids[0];
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
|
var input = JX.$N(
|
||||||
|
'input',
|
||||||
|
{
|
||||||
|
type: 'hidden',
|
||||||
|
name: name,
|
||||||
|
value: get_phid()
|
||||||
|
});
|
||||||
|
|
||||||
|
repo_tokenizer.listen('change', JX.bind(this, function() {
|
||||||
|
this._lastRepositoryChoice = repo_tokenizer.getTokens();
|
||||||
|
|
||||||
|
input.value = get_phid();
|
||||||
|
}));
|
||||||
|
|
||||||
|
var cell = JX.$N(
|
||||||
|
'td',
|
||||||
|
{
|
||||||
|
className: 'owners-path-repo-control'
|
||||||
|
},
|
||||||
|
[
|
||||||
|
repo_control.getRawInputNode(),
|
||||||
|
input
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
cell: cell,
|
||||||
|
tokenizer: repo_tokenizer
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_newPathCell: function(value) {
|
||||||
|
var path_cell = JX.$N(
|
||||||
|
'td',
|
||||||
|
{
|
||||||
|
className: 'owners-path-path-control'
|
||||||
|
},
|
||||||
|
JX.$H(this._inputTemplate));
|
||||||
|
|
||||||
|
var path_input = JX.DOM.find(path_cell, 'input');
|
||||||
|
|
||||||
|
JX.copy(
|
||||||
|
path_input,
|
||||||
|
{
|
||||||
|
value: value || '',
|
||||||
|
name: 'path[' + this._count + ']'
|
||||||
|
});
|
||||||
|
|
||||||
|
var hardpoint = JX.DOM.find(
|
||||||
|
path_cell,
|
||||||
|
'div',
|
||||||
|
'typeahead-hardpoint');
|
||||||
|
|
||||||
|
return {
|
||||||
|
cell: path_cell,
|
||||||
|
input: path_input,
|
||||||
|
hardpoint: hardpoint
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_newIconCell: function() {
|
||||||
|
var cell = JX.$N(
|
||||||
|
'td',
|
||||||
|
{
|
||||||
|
className: 'owners-path-icon-control'
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
cell: cell
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,6 +15,7 @@ JX.install('PHUIXFormControl', {
|
||||||
_valueSetCallback: null,
|
_valueSetCallback: null,
|
||||||
_valueGetCallback: null,
|
_valueGetCallback: null,
|
||||||
_rawInputNode: null,
|
_rawInputNode: null,
|
||||||
|
_tokenizer: null,
|
||||||
|
|
||||||
setLabel: function(label) {
|
setLabel: function(label) {
|
||||||
JX.DOM.setContent(this._getLabelNode(), label);
|
JX.DOM.setContent(this._getLabelNode(), label);
|
||||||
|
@ -70,6 +71,7 @@ JX.install('PHUIXFormControl', {
|
||||||
this._valueGetCallback = input.get;
|
this._valueGetCallback = input.get;
|
||||||
this._valueSetCallback = input.set;
|
this._valueSetCallback = input.set;
|
||||||
this._rawInputNode = input.node;
|
this._rawInputNode = input.node;
|
||||||
|
this._tokenizer = input.tokenizer || null;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
@ -87,6 +89,10 @@ JX.install('PHUIXFormControl', {
|
||||||
return this._rawInputNode;
|
return this._rawInputNode;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getTokenizer: function() {
|
||||||
|
return this._tokenizer;
|
||||||
|
},
|
||||||
|
|
||||||
getNode: function() {
|
getNode: function() {
|
||||||
if (!this._node) {
|
if (!this._node) {
|
||||||
|
|
||||||
|
@ -168,7 +174,8 @@ JX.install('PHUIXFormControl', {
|
||||||
return {
|
return {
|
||||||
node: build.node,
|
node: build.node,
|
||||||
get: get_value,
|
get: get_value,
|
||||||
set: set_value
|
set: set_value,
|
||||||
|
tokenizer: build.tokenizer
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue