port changes from rest multivalue

This commit is contained in:
Otto Streifel 2018-08-20 17:02:35 -07:00
Родитель 38b099961b
Коммит 401d7cbd2f
15 изменённых файлов: 2727 добавлений и 3462 удалений

5163
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -3,33 +3,39 @@
"scripts": {
"clean": "rimraf dist *.vsix vss-extension-release.json src/*js libs",
"dev": "webpack-dev-server --hot --progress --colors --content-base ./src --https --port 8888",
"dev:http": "webpack-dev-server -d --hot --progress --colors --content-base ./src --port 8888",
"dev:http": "webpack-dev-server --hot --progress --colors --content-base ./src --http --port 8888",
"package:dev": "node ./scripts/packageDev",
"package:dev:http": "node ./scripts/packageDevHttp",
"package:release": "node ./scripts/packageRelease",
"package:beta": "node ./scripts/packageBeta",
"publish:dev": "npm run package:dev && node ./scripts/publishDev",
"build:dev": "npm run clean && mkdir dist && webpack --progress --colors --output-path ./dist",
"build:release": "npm run clean && mkdir dist && webpack --progress --colors --output-path ./dist -p",
"publish:release": "npm run build:release && node ./scripts/publishRelease",
"test": "karma start --single-run",
"postinstall": "typings install"
},
"devDependencies": {
"@types/react": "^16.0.9",
"@types/react-dom": "^16.0.0",
"@types/applicationinsights-js": "^1.0.5",
"@types/jquery": "^2.0.41",
"@types/q": "^1.0.0",
"webpack": "^2.3.3",
"webpack-dev-server": "^2.4.2",
"@types/jsonpath": "^0.2.0",
"@types/react": "^16.4.8",
"@types/react-dom": "^16.0.7",
"copy-webpack-plugin": "^4.5.2",
"css-loader": "^0.28.0",
"jsonpath": "^1.0.0",
"office-ui-fabric-react": "^6.47.1",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"rimraf": "^2.6.1",
"style-loader": "^0.16.1",
"css-loader": "^0.28.0",
"ts-loader": "^2.0.3",
"tfx-cli": "^0.4.5",
"typescript": "2.7.2",
"typings": "^2.1.0",
"uglifyjs-webpack-plugin": "^0.4.2",
"copy-webpack-plugin": "^4.0.1"
"ts-loader": "^4.4.2",
"typescript": "^3.0.1",
"typings": "^2.1.1",
"vss-web-extension-sdk": "^2.117.0",
"webpack": "^4.16.5",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^2.4.2"
},
"dependencies": {
"vss-web-extension-sdk": "5.127.0"

Просмотреть файл

@ -6,6 +6,10 @@ var extensionId = manifest.id;
// Package extension
var command = `tfx extension create --overrides-file configs/dev.json --manifest-globs vss-extension.json --extension-id ${extensionId}-dev --no-prompt --rev-version`;
exec(command, function() {
console.log("Package created");
exec(command, function(err, stdout, stderr) {
console.log(stderr);
console.log(stdout);
if (err) {
console.error(err);
}
});

Просмотреть файл

@ -4,14 +4,10 @@ var exec = require("child_process").exec;
// Package extension
var command = `tfx extension create --overrides-file configs/release.json --manifest-globs vss-extension.json --no-prompt --json`;
exec(command, (error, stdout) => {
if (error) {
console.error(`Could not create package: '${error}'`);
return;
exec(command, function(err, stdout, stderr) {
console.log(stderr);
console.log(stdout);
if (err) {
console.error(err);
}
let output = JSON.parse(stdout);
console.log(`Package created ${output.path}`);
}
);
});

Просмотреть файл

@ -1,163 +0,0 @@
import Q = require("q");
import * as WitService from "TFS/WorkItemTracking/Services";
import * as VSSUtilsCore from "VSS/Utils/Core";
export class BaseMultiValueControl {
/**
* Field name input for the control
*/
public fieldName: string;
/**
* The container to hold the control
*/
protected containerElement: JQuery;
private _message: JQuery;
/**
* The container for error message display
*/
private _errorPane: JQuery;
private _flushing: boolean;
private _bodyElement: HTMLBodyElement;
/* Inherits from initalConfig to control if always show field border
*/
private _showFieldBorder: boolean;
/**
* Store the last recorded window width to know
* when we have been shrunk and should resize
*/
private _windowWidth: number;
private _minWindowWidthDelta: number = 10; // Minum change in window width to react to
private _windowResizeThrottleDelegate: () => void;
constructor() {
const initialConfig = VSS.getConfiguration();
this._showFieldBorder = !!initialConfig.fieldBorder;
this.containerElement = $(".container");
if (this._showFieldBorder) {
this.containerElement.addClass("fieldBorder");
}
this._errorPane = $("<div>").addClass("errorPane").appendTo(this.containerElement);
this._message = $("<div>").addClass("message").appendTo(this.containerElement);
const inputs: IDictionaryStringTo<string> = initialConfig.witInputs;
this.fieldName = inputs.FieldName;
if (!this.fieldName) {
this.showError("FieldName input has not been specified");
}
this._windowResizeThrottleDelegate = VSSUtilsCore.throttledDelegate(this, 50, () => {
this._windowWidth = window.innerWidth;
this.resize();
});
this._windowWidth = window.innerWidth;
$(window).resize(() => {
if (Math.abs(this._windowWidth - window.innerWidth) > this._minWindowWidthDelta) {
this._windowResizeThrottleDelegate.call(this);
}
});
}
/**
* Initialize a new instance of Control
*/
public initialize(): void {
this.invalidate();
}
/**
* Invalidate the control's value
*/
public invalidate(): void {
if (!this._flushing) {
this.setMessage("getting current value");
this._getCurrentFieldValue().then(
(value: string) => {
this.setMessage("");
this.setValue(value);
},
);
}
this.resize();
}
public clear(): void {
// noop
}
/**
* Flushes the control's value to the field
*/
protected flush(): void {
this._flushing = true;
WitService.WorkItemFormService.getService().then(
(service: WitService.IWorkItemFormService) => {
service.setFieldValue(this.fieldName, this.getValue()).then(
(values) => {
this._flushing = false;
},
() => {
this._flushing = false;
this.showError("Error storing the field value");
},
);
},
);
}
protected getValue(): string {
return "";
}
protected setValue(value: string): void {
// noop
}
protected showError(error: string): void {
this._errorPane.text(error);
this._errorPane.show();
}
protected clearError() {
this._errorPane.text("");
this._errorPane.hide();
}
protected setMessage(message: string) {
this._message.text(message);
}
private _getCurrentFieldValue(): IPromise<string> {
const defer = Q.defer<string>();
WitService.WorkItemFormService.getService().then(
(service) => {
service.getFieldValues([this.fieldName]).then(
(values) => {
defer.resolve(values[this.fieldName] as string);
},
() => {
this.showError("Error loading values for field: " + this.fieldName);
},
);
},
);
return defer.promise;
}
protected resize() {
this._bodyElement = document.getElementsByTagName("body").item(0) as HTMLBodyElement;
// Cast as any until declarations are updated
VSS.resize(null, this._bodyElement.offsetHeight);
}
}

Просмотреть файл

@ -1,345 +0,0 @@
import Q = require("q");
import * as WitService from "TFS/WorkItemTracking/Services";
import * as Utils_Array from "VSS/Utils/Array";
import * as VSSUtilsCore from "VSS/Utils/Core";
import * as Utils_String from "VSS/Utils/String";
import {BaseMultiValueControl} from "./BaseMultiValueControl";
export class MultiValueCombo extends BaseMultiValueControl {
/*
* UI elements for the control.
*/
private _selectedValuesWrapper: JQuery;
private _selectedValuesContainer: JQuery;
private _checkboxValuesContainer: JQuery;
private _chevron: JQuery;
private _suggestedValues: string[];
private _valueToCheckboxMap: IDictionaryStringTo<JQuery>;
private _valueToLabelMap: IDictionaryStringTo<JQuery>;
private _maxSelectedToShow = 100;
private _chevronDownClass = "bowtie-chevron-down-light";
private _chevronUpClass = "bowtie-chevron-up-light";
private _windowFocussed = false;
private _toggleThrottleDelegate: () => void;
/**
* Initialize a new instance of MultiValueControl
*/
public initialize(): void {
this._selectedValuesWrapper = $("<div>").addClass("selectedValuesWrapper").appendTo(this.containerElement);
this._selectedValuesContainer = $("<div>").addClass("selectedValuesContainer").attr("tabindex", "-1").appendTo(this._selectedValuesWrapper);
this._chevron = $("<span />").addClass("bowtie-icon " + this._chevronDownClass).appendTo(this._selectedValuesWrapper);
this._checkboxValuesContainer = $("<div>").addClass("checkboxValuesContainer").appendTo(this.containerElement);
this._valueToCheckboxMap = {};
this._valueToLabelMap = {};
this.setMessage("getting suggested values");
this._getSuggestedValues().then(
(values: string[]) => {
this._suggestedValues = values.filter((s: string): boolean => {
return s.trim() !== "";
});
this.setMessage("populating check boxes");
this._populateCheckBoxes();
this.setMessage("parent intializing");
super.initialize();
this.setMessage("");
},
);
this._toggleThrottleDelegate = VSSUtilsCore.throttledDelegate(this, 100, () => {
this._toggleCheckBoxContainer();
});
$(window).blur(() => {
this._hideCheckBoxContainer();
return false;
});
$(window).focus((e) => {
this._windowFocussed = true;
setTimeout(() => {
this._windowFocussed = false;
}, 500);
this._toggleThrottleDelegate.call(this);
return false;
});
this._selectedValuesWrapper.click(() => {
if (!this._windowFocussed) {
this._toggleThrottleDelegate.call(this);
}
return false;
});
this._chevron.click(() => {
this._toggleThrottleDelegate.call(this);
return false;
});
$(window).keydown((eventObject) => {
if (eventObject.keyCode === 38 /* Up */) {
const focusedCheckBox = $("input:focus", this._checkboxValuesContainer);
if (focusedCheckBox.length <= 0) {
// None selected, choose last
$("input:last", this._checkboxValuesContainer).focus();
} else {
// One selected to choose previous if it exists
focusedCheckBox.parent().prev().find("input").focus();
}
return false;
} else if (eventObject.keyCode === 40 /* Down */) {
const focusedCheckBox = $("input:focus", this._checkboxValuesContainer);
if (focusedCheckBox.length <= 0) {
// None selected, choose first
$("input:first", this._checkboxValuesContainer).focus();
} else {
// One selected to choose previous if it exists
focusedCheckBox.parent().next().find("input").focus();
}
return false;
} else {
return true;
}
});
}
public clear(): void {
const checkboxes: JQuery = $("input", this._checkboxValuesContainer);
const labels: JQuery = $(".checkboxLabel", this._checkboxValuesContainer);
checkboxes.prop("checked", false);
checkboxes.removeClass("selectedCheckbox");
this._selectedValuesContainer.empty();
}
protected getValue(): string {
const selectedCheckboxes: JQuery = $("input.valueOption:checked", this._checkboxValuesContainer);
let selectedValues: string[] = [];
selectedCheckboxes.each((i: number, elem: HTMLElement) => {
selectedValues.push($(elem).attr("value"));
});
selectedValues = Utils_Array.uniqueSort(selectedValues, Utils_String.localeIgnoreCaseComparer);
return selectedValues.join(";");
}
protected setValue(value: string): void {
this.clear();
const selectedValues = value ? value.split(";") : [];
this._showValues(selectedValues);
$.each(selectedValues, (i, selectedValue) => {
if (selectedValue) {
// mark the checkbox as checked
const checkbox = this._valueToCheckboxMap[selectedValue];
const label = this._valueToLabelMap[selectedValue];
if (checkbox) {
checkbox.prop("checked", true);
checkbox.addClass("selectedCheckbox");
}
}
});
}
private _toggleCheckBoxContainer() {
if (this._checkboxValuesContainer.is(":visible")) {
this._hideCheckBoxContainer();
} else {
this._showCheckBoxContainer();
}
}
private _showCheckBoxContainer() {
this._chevron.removeClass(this._chevronDownClass).addClass(this._chevronUpClass);
this.containerElement.addClass("expanded").removeClass("collapsed");
this._checkboxValuesContainer.show();
this.resize();
}
private _hideCheckBoxContainer() {
this._chevron.removeClass(this._chevronUpClass).addClass(this._chevronDownClass);
this.containerElement.removeClass("expanded").addClass("collapsed");
this._checkboxValuesContainer.hide();
this.resize();
}
private _showValues(values: string[]) {
if (values.length <= 0) {
this._selectedValuesContainer.append("<div class='noSelection'>No selection made</div>");
} else {
$.each(values, (i, value) => {
let control;
// only show first N selections and the rest as more.
if (i < this._maxSelectedToShow) {
control = this._createSelectedValueControl(value);
} else {
control = this._createSelectedValueControl(values.length - i + " more");
control.attr("title", values.slice(i).join(";"));
return false;
}
});
}
this._updateSelectAllControlState();
this.resize();
}
private _refreshValues() {
const rawValue = this.getValue();
const values = rawValue ? rawValue.split(";") : [];
this._selectedValuesContainer.empty();
this._showValues(values);
}
private _createSelectedValueControl(value: string): JQuery {
const control = $("<div />");
if (value) {
control.text(value);
control.attr("title", value);
control.addClass("selected");
this._selectedValuesContainer.append(control);
}
return control;
}
/**
* Populates the UI with the list of checkboxes to choose the value from.
*/
private _populateCheckBoxes(): void {
if (!this._suggestedValues || this._suggestedValues.length === 0) {
this.showError("No values to select.");
} else {
// Add the select all method
const selectAllBox = this._createSelectAllControl();
$.each(this._suggestedValues, (i, value) => {
this._createCheckBoxControl(value, selectAllBox);
});
}
}
private _updateSelectAllControlState() {
const selectAllBox = $("input.selectAllOption", this._checkboxValuesContainer);
const allBoxes = $("input.valueOption", this._checkboxValuesContainer);
const checkedBoxes = $("input.valueOption:checked", this._checkboxValuesContainer);
if (allBoxes.length > checkedBoxes.length) {
selectAllBox.prop("checked", false);
} else {
selectAllBox.prop("checked", true);
}
}
private _createSelectAllControl() {
const value = "Select All";
const label = this._createValueLabel(value);
const checkbox = this._createCheckBox(value, label, () => {
const checkBoxes = $("input.valueOption", this._checkboxValuesContainer);
if (checkbox.prop("checked")) {
checkBoxes.prop("checked", true);
} else {
checkBoxes.prop("checked", false);
}
});
const container = $("<div />").addClass("checkboxContainer selectAllControlContainer");
checkbox.addClass("selectAllOption");
this._valueToCheckboxMap[value] = checkbox;
this._valueToLabelMap[value] = label;
container.append(checkbox);
container.append(label);
this._checkboxValuesContainer.append(container);
return checkbox;
}
private _createCheckBoxControl(value: string, selectAllBox: JQuery) {
const label = this._createValueLabel(value);
const checkbox = this._createCheckBox(value, label);
const container = $("<div />").addClass("checkboxContainer");
checkbox.addClass("valueOption");
this._valueToCheckboxMap[value] = checkbox;
this._valueToLabelMap[value] = label;
container.append(checkbox);
container.append(label);
this._checkboxValuesContainer.append(container);
}
private _createValueLabel(value: string) {
const label = $("<label />");
label.attr("for", "checkbox" + value);
label.text(value);
label.attr("title", value);
label.addClass("checkboxLabel");
return label;
}
private _createCheckBox(value: string, label: JQuery, action?: () => void) {
const checkbox = $("<input />");
checkbox.attr("type", "checkbox");
checkbox.attr("name", value);
checkbox.attr("value", value);
checkbox.attr("tabindex", -1);
checkbox.attr("id", "checkbox" + value);
checkbox.change((e) => {
if (action) {
action.call(this);
}
this._refreshValues();
this.flush();
});
return checkbox;
}
private _getSuggestedValues(): Q.IPromise<string[]> {
const defer = Q.defer<string[]>();
const inputs: IDictionaryStringTo<string> = VSS.getConfiguration().witInputs;
const valuesString: string = inputs.Values;
if (valuesString) {
defer.resolve(valuesString.split(";"));
} else {
this.setMessage("getting form service");
// if the values input were not specified as an input, get the suggested values for the field.
WitService.WorkItemFormService.getService().then(
(service: any) => {
this.setMessage("getting allowed field values");
service.getAllowedFieldValues(this.fieldName).then(
(values: string[]) => {
defer.resolve(values);
},
() => {
this.showError("Could not load values for field " + this.fieldName);
},
);
},
);
}
return defer.promise;
}
}

173
src/MultiValueControl.tsx Normal file
Просмотреть файл

@ -0,0 +1,173 @@
import { Checkbox } from "office-ui-fabric-react/lib/components/Checkbox";
import { ITag, TagPicker } from "office-ui-fabric-react/lib/components/pickers";
import { TextField } from "office-ui-fabric-react/lib/components/TextField";
import { FocusZone, FocusZoneDirection } from "office-ui-fabric-react/lib/FocusZone";
import * as React from "react";
import { DelayedFunction } from "VSS/Utils/Core";
import { BrowserCheckUtils } from "VSS/Utils/UI";
interface IMultiValueControlProps {
selected?: string[];
width?: number;
readOnly?: boolean;
placeholder?: string;
noResultsFoundText?: string;
searchingText?: string;
onSelectionChanged?: (selection: string[]) => Promise<void>;
forceValue?: boolean;
options: string[];
onBlurred?: () => void;
onResize?: () => void;
}
interface IMultiValueControlState {
focused: boolean;
filter: string;
}
export class MultiValueControl extends React.Component<IMultiValueControlProps, IMultiValueControlState> {
private readonly _unfocusedTimeout = BrowserCheckUtils.isSafari() ? 2000 : 1;
private _setUnfocused = new DelayedFunction(null, this._unfocusedTimeout, "", () => {
this.setState({focused: false, filter: ""});
});
constructor(props, context) {
super(props, context);
this.state = { focused: false, filter: "" };
}
public render() {
const {focused} = this.state;
return <div className={`multi-value-control ${focused ? "focused" : ""}`}>
<TagPicker
className="tag-picker"
selectedItems={(this.props.selected || []).map((t) => ({ key: t, name: t }))}
inputProps={{
placeholder: this.props.placeholder,
readOnly: this.props.readOnly,
width: this.props.width || 200,
onFocus: () => this.setState({ focused: true }),
}}
onChange={this._onTagsChanged}
onResolveSuggestions={() => []}
/>
{focused ? this._getOptions() : null}
</div>;
}
public componentDidUpdate() {
if (this.props.onResize) {
this.props.onResize();
}
}
private _getOptions() {
const options = this.props.options;
const selected = (this.props.selected || []).slice(0);
const filteredOpts = this._filteredOptions();
return <div className="options">
<TextField value={this.state.filter}
autoFocus
placeholder={"Filter values"}
onKeyDown={this._onInputKeyDown}
onBlur={this._onBlur}
onFocus={this._onFocus}
onChange={this._onInputChange}
/>
<FocusZone
direction={FocusZoneDirection.vertical}
className="checkboxes"
>
{this.state.filter ? null :
<Checkbox
label="Select All"
checked={selected.join(";") === options.join(";")}
onChange={this._toggleSelectAll}
inputProps={{
onBlur: this._onBlur,
onFocus: this._onFocus,
}}
/>}
{filteredOpts
.map((o) => <Checkbox
checked={selected.indexOf(o) >= 0}
inputProps={{
onBlur: this._onBlur,
onFocus: this._onFocus,
}}
onChange={() => this._toggleOption(o)}
label={o}
/>)}
</FocusZone>
</div>;
}
private _onInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.altKey || e.shiftKey || e.ctrlKey) {
return;
}
if (e.keyCode === 13) {
const filtered = this._filteredOptions();
if (filtered.length !== 1) {
return;
}
e.preventDefault();
e.stopPropagation();
this._toggleOption(filtered[0]);
this.setState({filter: ""});
}
}
private _toggleSelectAll = () => {
const options = this.props.options;
const selected = this.props.selected || [];
if (selected.join(";") === options.join(";")) {
this._setSelected([]);
} else {
this._setSelected(options);
}
this._ifSafariCloseDropdown();
}
private _filteredOptions = () => {
const filter = this.state.filter.toLocaleLowerCase();
const opts = this.props.options;
return [
...opts.filter((o) => o.toLocaleLowerCase().indexOf(filter) === 0),
...opts.filter((o) => o.toLocaleLowerCase().indexOf(filter) > 0),
];
}
private _onInputChange = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
this.setState({filter: newValue || ""});
}
private _onBlur = () => {
this._setUnfocused.reset();
}
private _onFocus = () => {
this._setUnfocused.cancel();
}
private _setSelected = async (selected: string[]): Promise<void> => {
if (!this.props.onSelectionChanged) {
return;
}
await this.props.onSelectionChanged(selected);
}
private _toggleOption = (option: string): boolean => {
const selectedMap: {[k: string]: boolean} = {};
for (const s of this.props.selected || []) {
selectedMap[s] = true;
}
const change = option in selectedMap || this.props.options.indexOf(option) >= 0;
selectedMap[option] = !selectedMap[option];
const selected = this.props.options.filter((o) => selectedMap[o]);
this._setSelected(selected);
this._ifSafariCloseDropdown();
return change;
}
private _ifSafariCloseDropdown() {
if (BrowserCheckUtils.isSafari()) {
this.setState({filter: "", focused: false});
}
}
private _onTagsChanged = (tags: ITag[]) => {
const values = tags.map(({name}) => name);
if (this.props.onSelectionChanged) {
this.props.onSelectionChanged(values);
}
}
}

58
src/MultiValueEvents.tsx Normal file
Просмотреть файл

@ -0,0 +1,58 @@
import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
import * as React from "react";
import * as ReactDOM from "react-dom";
import { WorkItemFormService } from "TFS/WorkItemTracking/Services";
import { getSuggestedValues } from "./getSuggestedValues";
import { MultiValueControl } from "./MultiValueControl";
initializeIcons();
export class MultiValueEvents {
public readonly fieldName = VSS.getConfiguration().witInputs.FieldName;
private readonly _container = document.getElementById("container") as HTMLElement;
private _onRefreshed: () => void;
/** Counter to avoid consuming own changed field events. */
private _fired: number = 0;
public async refresh(selected?: string[]): Promise<void> {
if (!selected) {
if (this._fired) {
this._fired--;
return;
}
selected = await this._getSelected();
}
ReactDOM.render(<MultiValueControl
selected={selected}
options={await getSuggestedValues()}
onSelectionChanged={this._setSelected}
width={this._container.scrollWidth}
placeholder={selected.length ? "Click to Add" : "No selection made"}
onResize={this._resize}
/>, this._container, () => {
this._resize();
if (this._onRefreshed) {
this._onRefreshed();
}
});
}
private _resize = () => {
VSS.resize(this._container.scrollWidth, this._container.scrollHeight);
}
private async _getSelected(): Promise<string[]> {
const formService = await WorkItemFormService.getService();
const value = await formService.getFieldValue(this.fieldName);
if (typeof value !== "string") {
return [];
}
return value.split(";").filter((v) => !!v);
}
private _setSelected = async (values: string[]): Promise<void> => {
this.refresh(values);
this._fired++;
const formService = await WorkItemFormService.getService();
formService.setFieldValue(this.fieldName, values.join(";"));
return new Promise<void>((resolve) => {
this._onRefreshed = resolve;
});
}
}

13
src/getSuggestedValues.ts Normal file
Просмотреть файл

@ -0,0 +1,13 @@
import { WorkItemFormService } from "TFS/WorkItemTracking/Services";
export async function getSuggestedValues(): Promise<string[]> {
const inputs: IDictionaryStringTo<string> = VSS.getConfiguration().witInputs;
const valuesString: string = inputs.Values;
if (valuesString) {
return valuesString.split(";");
}
this.setMessage("getting form service");
// if the values input were not specified as an input, get the suggested values for the field.
const service = await WorkItemFormService.getService();
return await service.getAllowedFieldValues(this.fieldName) as string[];
}

Просмотреть файл

@ -1,146 +1,15 @@
.container {
border: 1px transparent solid;
margin-right: 4px;
#container .multi-value-control .tag-picker [role=list]:not(:hover) {
border-color: transparent;
}
html:focus, body:focus, div:focus {
outline: none;
}
input, label, .container {
cursor: pointer;
}
.container:hover, .container:active, .container.expanded, .container.fieldBorder {
border: 1px #e6e6e6 solid;
}
.container .selected {
margin: 5px 5px 0 0;
display: inline-block;
max-width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
padding: 2px 6px;
font-size: 12px;
color: #4F4F4F;
border: solid 1px #e5e5e5;
background-color: #fafafa;
}
.container .selectedValuesContainer .noSelection {
margin: 6px 6px 2px 4px;
font-size: 14px;
display:inline-block;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
color: grey;
}
.container .checkboxLabel {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
display: inline-block;
width: calc(100% - 20px);
}
input[type=checkbox]:checked + label {
font-weight: bold;
}
.container .selectedValuesContainer {
padding-left:2px;
}
.container .selectAllControlContainer {
margin-top: 5px;
}
.container .selectedValuesWrapper {
padding-right: 20px;
padding-bottom:2px;
position: relative;
}
.container.expanded .selectedValuesWrapper {
border-bottom: 1px #e6e6e6 solid;
}
.container .selectedValuesWrapper .bowtie-icon {
margin: -8px 2px 0 2px;
position: absolute;
right: 0;
top: 50%;
width: 20px;
visibility: hidden;
}
.container .selectedValuesWrapper:hover .bowtie-icon, .container .selectedValuesWrapper:active .bowtie-icon,
.container.expanded .selectedValuesWrapper .bowtie-icon {
visibility: visible;
}
.container .checkboxValuesContainer {
overflow-y: auto;
#container .multi-value-control.focused .tag-picker input {
display: none;
max-height: 290px; /* 10 items max in view */
}
.container .errorPane {
background-color: red;
margin:5px;
padding:5px;
display:none;
#container .multi-value-control .options button {
width: 100%;
}
.container .checkboxContainer {
margin-top:5px;
margin-bottom:5px;
font-size:12px;
display:flex;
#container .multi-value-control .options button:hover {
background-color: aliceblue;
}
.container .checkboxContainer:hover {
background-color: #dce6f4;
}
.container.identity-picker-container {
border: none;
height: 100%;
}
.container .search-control-container {
border: 1px #e6e6e6 solid;
}
.container .identity-list-container {
margin-top: 5px;
overflow-y: auto;
position: absolute;
top: 20px;
bottom: 0;
right: 0;
left: 0;
overflow-x: hidden;
}
.container .identity-list-container .identity-container {
width: 150px;
display: inline-block;
background-color: #d7e6f3;
margin: 1px;
padding: 1px;
}
.container .identity-list-container .identity-container > div {
width: 130px;
display: inline-block;
vertical-align: bottom;
}
.container .identity-list-container .identity-container .remove-identity {
cursor: pointer;
#container .multi-value-control .options .checkboxes {
padding: 3px;
}

Просмотреть файл

@ -22,7 +22,7 @@
VSS.notifyLoadSucceeded();
});
</script>
<div class="container">
<div id="container">
</div>
</body>

Просмотреть файл

@ -1,7 +1,6 @@
import * as WitExtensionContracts from "TFS/WorkItemTracking/ExtensionContracts";
import { WorkItemFormService } from "TFS/WorkItemTracking/Services";
import {BaseMultiValueControl} from "./BaseMultiValueControl";
import {MultiValueCombo} from "./MultiValueCombo";
import { MultiValueEvents } from "./MultiValueEvents";
// save on ctr + s
$(window).bind("keydown", (event: JQueryEventObject) => {
@ -13,30 +12,30 @@ $(window).bind("keydown", (event: JQueryEventObject) => {
}
});
let control: BaseMultiValueControl;
const provider = () => {
let control: MultiValueEvents;
const provider = (): Partial<WitExtensionContracts.IWorkItemNotificationListener> => {
const ensureControl = () => {
if (!control) {
control = new MultiValueCombo();
control.initialize();
control = new MultiValueEvents();
}
control.invalidate();
control.refresh();
};
return {
onLoaded: (args: WitExtensionContracts.IWorkItemLoadedArgs) => {
ensureControl();
},
onUnloaded: (args: WitExtensionContracts.IWorkItemChangedArgs) => {
if (control) {
control.clear();
}
},
// onUnloaded: (args: WitExtensionContracts.IWorkItemChangedArgs) => {
// if (control) {
// control.clear();
// }
// },
onFieldChanged: (args: WitExtensionContracts.IWorkItemFieldChangedArgs) => {
if (control && args.changedFields[control.fieldName] !== undefined && args.changedFields[control.fieldName] !== null) {
control.invalidate();
if (control && args.changedFields[control.fieldName] !== undefined &&
args.changedFields[control.fieldName] !== null
) {
control.refresh();
}
},
};

Просмотреть файл

@ -1,21 +1,23 @@
{
"compilerOptions": {
"module": "amd",
"sourceMap": false,
"moduleResolution": "node",
"sourceMap": true,
"target": "es5",
"strictNullChecks": true,
"noUnusedLocals": true,
"jsx": "react",
"types": [
"q",
"knockout",
"requirejs",
"jquery",
"react",
"react-dom"
],
"typeRoots": [
"../node_modules/@types"
"lib": [
"es2015.promise",
"dom",
"es5"
]
},
"exclude": [
"node_modules"
]
}
}

Просмотреть файл

@ -1,7 +1,7 @@
{
"manifestVersion": 1.0,
"id": "vsts-extensions-multivalue-control",
"version": "1.0.19",
"version": "1.0.24",
"name": "Multivalue control",
"description": "A work item form control which allows selection of multiple values.",
"publisher": "ms-devlabs",

Просмотреть файл

@ -14,9 +14,6 @@ module.exports = {
},
externals: [
{
"q": true,
"react": true,
"react-dom": true
},
/^VSS\/.*/, /^TFS\/.*/, /^q$/
],
@ -25,26 +22,19 @@ module.exports = {
moduleExtensions: ["-loader"],
},
module: {
loaders: [
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader"
use: "ts-loader"
},
{
test: /\.s?css$/,
loaders: ["style-loader", "css-loader"]
use: ["style-loader", "css-loader"]
}
]
},
mode: "development",
plugins: [
new UglifyJSPlugin({
compress: {
warnings: false
},
output: {
comments: false
}
}),
new CopyWebpackPlugin([
{ from: "./node_modules/vss-web-extension-sdk/lib/VSS.SDK.min.js", to: "libs/VSS.SDK.min.js" },
{ from: "./src/multivalue.html", to: "./" },