Added comments. Using navigation service for url.

This commit is contained in:
Ruturaj Hagawane 2019-10-10 13:57:45 -04:00
Родитель eea0f1027d
Коммит 182472da75
15 изменённых файлов: 147 добавлений и 160 удалений

6
legacy/package-lock.json сгенерированный
Просмотреть файл

@ -5667,9 +5667,9 @@
"dev": true
},
"typescript": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.3.tgz",
"integrity": "sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w==",
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
"integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
"dev": true
},
"ua-parser-js": {

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

@ -55,7 +55,7 @@
"style-loader": "~0.19.0",
"tfx-cli": "~0.4.10",
"ts-loader": "~3.0.3",
"typescript": "~2.5.2",
"typescript": "~2.9.2",
"webpack": "~3.8.1"
},
"dependencies": {

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

@ -2,7 +2,7 @@ const path = require("path");
const webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractSass = new ExtractTextPlugin({
filename: path.relative(process.cwd(), path.join(__dirname, "dist", "css", "style.css")),
filename: path.relative(process.cwd(), path.join(__dirname, "dist", "css", "style.css"))
});
module.exports = env => {
@ -11,37 +11,37 @@ module.exports = env => {
plugins.push(
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
warnings: false
},
output: {
comments: false,
},
}),
comments: false
}
})
);
}
return {
entry: {
main: "./" + path.relative(process.cwd(), path.join(__dirname, "src", "Calendar", "Extension.tsx")),
calendarServices: "./" + path.relative(process.cwd(), path.join(__dirname, "src", "Calendar", "CalendarServices.ts")),
dialogs: "./" + path.relative(process.cwd(), path.join(__dirname, "src", "Calendar", "Dialogs.ts")),
dialogs: "./" + path.relative(process.cwd(), path.join(__dirname, "src", "Calendar", "Dialogs.ts"))
},
output: {
filename: path.relative(process.cwd(), path.join(__dirname, "dist", "js", "[name].js")),
libraryTarget: "amd",
libraryTarget: "amd"
},
externals: [
{
react: true,
"react-dom": true,
"react-dom": true
},
// Ignore TFS/*, VSS/*, Favorites/* since they are coming from VSTS host
/^TFS\//,
/^VSS\//,
/^Favorites\//,
/^Favorites\//
],
resolve: {
alias: { OfficeFabric: "../node_modules/office-ui-fabric-react/lib-amd" },
extensions: [".ts", ".tsx", ".js"],
extensions: [".ts", ".tsx", ".js"]
},
module: {
rules: [
@ -52,24 +52,24 @@ module.exports = env => {
use: [
{
loader: "css-loader",
options: { importLoaders: 1 },
options: { importLoaders: 1 }
},
{
loader: "sass-loader",
loader: "sass-loader"
},
{
loader: "postcss-loader",
},
loader: "postcss-loader"
}
],
fallback: "style-loader",
}),
fallback: "style-loader"
})
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
],
use: ["style-loader", "css-loader"]
}
]
},
plugins: plugins,
plugins: plugins
};
};

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

@ -43,11 +43,11 @@
},
"homepage": "https://github.com/Microsoft/vso-team-calendar#readme",
"scripts": {
"preinstall": "cd legacy && npm install && npm run build && cd ..",
"clean": "rimraf ./dist",
"postinstall": "cd legacy && npm install && cd ..",
"clean": "rimraf ./dist && cd legacy && npm run clean && cd ..",
"compile": "npm run clean && webpack --mode production",
"compile:dev": "npm run clean && webpack --mode development",
"build": "npm run compile",
"build": "npm run compile && cd legacy && npm run build && cd ..",
"build:dev": "npm run compile:dev && npm run postbuild",
"postbuild": "npm run package-extension -- --rev-version",
"package-extension": "tfx extension create --manifest-globs azure-devops-extension.json src/*.json",

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

@ -1,7 +1,7 @@
{
"contributions": [
{
"id": "hub",
"id": "new-calendar",
"type": "ms.vss-web.hub",
"description": "Calendar hub in the Work hub group.",
"targets": ["ms.vss-work-web.work-hub-group"],
@ -12,9 +12,8 @@
"ms.vss-features.host-dialog-service"
],
"properties": {
"name": "Calendar2",
"uri": "dist/calendar1.html",
"icon": "asset://static/sample-icon.png"
"name": "Calendar",
"uri": "dist/calendar.html"
}
},
{

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

@ -4,7 +4,7 @@ import * as React from "react";
import * as ReactDOM from "react-dom";
import { CommonServiceIds, IProjectPageService, getClient } from "azure-devops-extension-api";
import { IExtensionDataService, IExtensionDataManager, ILocationService } from "azure-devops-extension-api/Common";
import { IExtensionDataService, IExtensionDataManager, ILocationService, IHostNavigationService } from "azure-devops-extension-api/Common";
import { CoreRestClient, WebApiTeam } from "azure-devops-extension-api/Core";
import { TeamMember } from "azure-devops-extension-api/WebApi/WebApi";
@ -33,7 +33,6 @@ import { ICalendarEvent } from "./Contracts";
import { FreeFormId, FreeFormEventsSource } from "./FreeFormEventSource";
import { SummaryComponent } from "./SummaryComponent";
import { MonthAndYear, monthAndYearToString } from "./TimeLib";
import { getQueryVariable, setTeamQueryVariable } from "./UrlLib";
import { DaysOffId, VSOCapacityEventSource, IterationId } from "./VSOCapacityEventSource";
enum Dialogs {
@ -42,27 +41,28 @@ enum Dialogs {
NewDaysOffDialog
}
class HubContent extends React.Component {
calendarComponentRef = React.createRef<FullCalendar>();
openDialog: ObservableValue<Dialogs> = new ObservableValue(Dialogs.None);
class ExtensionContent extends React.Component {
anchorElement: ObservableValue<HTMLElement | undefined> = new ObservableValue<HTMLElement | undefined>(undefined);
showMonthPicker: ObservableValue<boolean> = new ObservableValue<boolean>(false);
selectedStartDate: Date;
selectedEndDate: Date;
currentMonthAndYear: ObservableValue<MonthAndYear>;
calendarComponentRef = React.createRef<FullCalendar>();
commandBarItems: IHeaderCommandBarItem[];
eventToEdit?: ICalendarEvent;
eventApi?: EventApi;
teams: ObservableValue<WebApiTeam[]>;
selectedTeamId: ObservableValue<string>;
projectId: string;
projectName: string;
selectedTeamName: string;
currentMonthAndYear: ObservableValue<MonthAndYear>;
dataManager: IExtensionDataManager | undefined;
displayCalendar: ObservableValue<boolean>;
eventApi?: EventApi;
eventToEdit?: ICalendarEvent;
freeFormEventSource: FreeFormEventsSource;
vsoCapacityEventSource: VSOCapacityEventSource;
hostUrl: string;
members: TeamMember[];
navigationService: IHostNavigationService | undefined;
openDialog: ObservableValue<Dialogs> = new ObservableValue(Dialogs.None);
projectId: string;
projectName: string;
selectedEndDate: Date;
selectedStartDate: Date;
selectedTeamName: string;
showMonthPicker: ObservableValue<boolean> = new ObservableValue<boolean>(false);
teams: ObservableValue<WebApiTeam[]>;
vsoCapacityEventSource: VSOCapacityEventSource;
constructor(props: {}) {
super(props);
@ -136,7 +136,7 @@ class HubContent extends React.Component {
this.selectedStartDate = new Date();
this.teams = new ObservableValue<WebApiTeam[]>([]);
this.selectedTeamName = "Select Team";
this.selectedTeamId = new ObservableValue<string>("");
this.displayCalendar = new ObservableValue<boolean>(false);
this.projectId = "";
this.projectName = "";
this.hostUrl = "";
@ -147,7 +147,7 @@ class HubContent extends React.Component {
public render(): JSX.Element {
return (
<Page className="sample-hub flex-grow flex-row">
<Page className="flex-grow flex-row">
<div className="flex-column scroll-hidden calendar-area">
<CustomHeader className="bolt-header-with-commandbar">
<HeaderTitleArea className="flex-grow">
@ -184,9 +184,9 @@ class HubContent extends React.Component {
</HeaderTitleArea>
<HeaderCommandBar items={this.commandBarItems} />
</CustomHeader>
<Observer teamId={this.selectedTeamId}>
{(props: { teamId: string }) => {
return props.teamId === "" ? null : (
<Observer display={this.displayCalendar}>
{(props: { display: boolean }) => {
return props.display ? (
<div className="calendar-component">
<FullCalendar
defaultView="dayGridMonth"
@ -207,7 +207,7 @@ class HubContent extends React.Component {
]}
/>
</div>
);
) : null;
}}
</Observer>
</div>
@ -359,15 +359,13 @@ class HubContent extends React.Component {
private async initialize() {
const dataSvc = await SDK.getService<IExtensionDataService>(CommonServiceIds.ExtensionDataService);
this.dataManager = await dataSvc.getExtensionDataManager(SDK.getExtensionContext().id, await SDK.getAccessToken());
this.navigationService = await SDK.getService<IHostNavigationService>(CommonServiceIds.HostNavigationService);
const queryParam = getQueryVariable("team");
const queryParam = await this.navigationService.getQueryParams();
let selectedTeamId;
if (queryParam) {
selectedTeamId = queryParam;
} else {
// Nothing in URL - check data service
selectedTeamId = await this.dataManager.getValue<string>("selected-team", { scopeType: "User" });
if (queryParam && queryParam["team"]) {
selectedTeamId = queryParam["team"];
}
const projectService = await SDK.getService<IProjectPageService>(CommonServiceIds.ProjectPageService);
@ -376,6 +374,11 @@ class HubContent extends React.Component {
const locationService = await SDK.getService<ILocationService>(CommonServiceIds.LocationService);
if (project) {
if (!selectedTeamId) {
// Nothing in URL - check data service
selectedTeamId = await this.dataManager.getValue<string>("selected-team-" + project.id, { scopeType: "User" });
}
const client = getClient(CoreRestClient);
const teams = await client.getTeams(project.id, true, 1000);
@ -389,16 +392,16 @@ class HubContent extends React.Component {
if (!selectedTeamId) {
selectedTeamId = teams[0].id;
}
if (!queryParam) {
setTeamQueryVariable(selectedTeamId);
if (!queryParam || !queryParam["team"]) {
this.navigationService.setQueryParams({ team: selectedTeamId });
}
this.hostUrl = await locationService.getServiceLocation();
this.selectedTeamName = (await client.getTeam(project.id, selectedTeamId)).name;
this.freeFormEventSource.initialize(selectedTeamId, this.dataManager);
this.vsoCapacityEventSource.initialize(project.id, this.projectName, selectedTeamId, this.selectedTeamName, this.hostUrl);
this.selectedTeamId.value = selectedTeamId;
this.dataManager.setValue<string>("selected-team", this.selectedTeamId.value, { scopeType: "User" });
this.displayCalendar.value = true;
this.dataManager.setValue<string>("selected-team-" + project.id, selectedTeamId, { scopeType: "User" });
this.teams.value = teams;
this.members = await client.getTeamMembersWithExtendedProperties(project.id, selectedTeamId);
}
@ -508,7 +511,8 @@ class HubContent extends React.Component {
this.freeFormEventSource.initialize(newTeam.id, this.dataManager!);
this.vsoCapacityEventSource.initialize(this.projectId, this.projectName, newTeam.id, newTeam.name, this.hostUrl);
this.getCalendarApi().refetchEvents();
this.dataManager!.setValue<string>("selected-team", newTeam.id, { scopeType: "User" });
this.dataManager!.setValue<string>("selected-team-" + this.projectId, newTeam.id, { scopeType: "User" });
this.navigationService!.setQueryParams({ team: newTeam.id });
this.members = await getClient(CoreRestClient).getTeamMembersWithExtendedProperties(this.projectId, newTeam.id);
};
@ -523,4 +527,4 @@ function showRootComponent(component: React.ReactElement<any>) {
ReactDOM.render(component, document.getElementById("team-calendar"));
}
showRootComponent(<HubContent />);
showRootComponent(<ExtensionContent />);

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

@ -1,6 +1,4 @@
import { IEventCategory } from "./Contracts";
const allColors = ["0072C6", "4617B4", "8C0095", "008A17", "D24726", "008299", "AC193D", "DC4FAD", "FF8F32", "82BA00", "03B3B2", "5DB2FF"];
const allColors = ["0072C6", "4617B4", "8C0095", "008A17", "D24726", "008299", "AC193D", "DC4FAD", "FF8F32", "82BA00", "03B3B2", "5DB2FF"];
const currentIterationColor = "#C1E6FF"; /*Pattens Blue*/
const otherIterationColor = "#EEF9FF"; /*lighter shade of Pattens Blue*/

6
src/Contracts.d.ts поставляемый
Просмотреть файл

@ -22,6 +22,9 @@ export interface IEventCategory {
*/
color?: string;
/**
* Number of event under this Category
*/
eventCount: number;
}
@ -34,6 +37,9 @@ export interface ICalendarEvent {
*/
title: string;
/**
* Used by collection to
*/
__etag?: number;
/**

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

@ -126,21 +126,17 @@ export class FreeFormEventsSource {
}
});
successCallback(inputs);
this.summaryData.splice(
0,
this.summaryData.length,
...Object.keys(catagoryMap).map(key => {
const catagory = catagoryMap[key];
if (catagory.eventCount > 1) {
catagory.subTitle = catagory.eventCount + " events";
}
return catagory;
})
);
this.summaryData.value = Object.keys(catagoryMap).map(key => {
const catagory = catagoryMap[key];
if (catagory.eventCount > 1) {
catagory.subTitle = catagory.eventCount + " events";
}
return catagory;
});
});
};
getSummaryData = (): ObservableArray<IEventCategory> => {
public getSummaryData = (): ObservableArray<IEventCategory> => {
return this.summaryData;
};

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

@ -3,6 +3,9 @@ import React = require("react");
import { Dialog } from "azure-devops-ui/Dialog";
interface IMessageDialogProps {
/**
* Content for dialog
*/
message: string;
/**
@ -15,6 +18,9 @@ interface IMessageDialogProps {
*/
onDismiss: () => void;
/**
* Title for dialog.
*/
title: string;
}

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

@ -1,18 +1,12 @@
import React = require("react");
import { ObservableValue } from "azure-devops-ui/Core/Observable";
import { Link } from "azure-devops-ui/Link";
import { IListItemDetails, ListItem, ScrollableList } from "azure-devops-ui/List";
import { Observer } from "azure-devops-ui/Observer";
import { IEventCategory } from "./Contracts";
import { TwoLineTableCell, ITableColumn, Table } from "azure-devops-ui/Table";
import { css } from "azure-devops-ui/Util";
import { ArrayItemProvider } from "azure-devops-ui/Utilities/Provider";
import { Observer } from "azure-devops-ui/Observer";
import { Link } from "azure-devops-ui/Link";
import { VSOCapacityEventSource } from "./VSOCapacityEventSource";
import { FreeFormEventsSource } from "./FreeFormEventSource";
import { IListItemDetails, ListItem, ScrollableList } from "azure-devops-ui/List";
import { Icon, IconSize } from "azure-devops-ui/Icon";
import { VSOCapacityEventSource } from "./VSOCapacityEventSource";
interface ISummaryComponentProps {
/**

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

@ -1,14 +0,0 @@
export function getQueryVariable(variable: string): string | undefined {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
if (decodeURIComponent(pair[0]) == variable) {
return decodeURIComponent(pair[1]);
}
}
}
export function setTeamQueryVariable(teamId: string) {
window.history.replaceState({}, "", window.location.href + "?team=" + teamId);
}

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

@ -126,51 +126,53 @@ export class VSOCapacityEventSource {
calendarEnd.setDate(arg.end.getDate() - 1);
for (const iteration of iterations) {
const start = shiftToUTC(iteration.attributes.startDate);
const end = shiftToUTC(iteration.attributes.finishDate);
if (iteration.attributes.startDate && iteration.attributes.finishDate) {
const start = shiftToUTC(iteration.attributes.startDate);
const end = shiftToUTC(iteration.attributes.finishDate);
if (
(calendarStart <= start && start <= calendarEnd) ||
(calendarStart <= end && end <= calendarEnd) ||
(start <= calendarStart && end >= calendarEnd)
) {
const now = new Date();
let color = generateColor("otherIteration");
if (iteration.attributes.startDate <= now && now <= iteration.attributes.finishDate) {
color = generateColor("currentIteration");
if (
(calendarStart <= start && start <= calendarEnd) ||
(calendarStart <= end && end <= calendarEnd) ||
(start <= calendarStart && end >= calendarEnd)
) {
const now = new Date();
let color = generateColor("otherIteration");
if (iteration.attributes.startDate <= now && now <= iteration.attributes.finishDate) {
color = generateColor("currentIteration");
}
const exclusiveEndDate = new Date(end);
exclusiveEndDate.setDate(end.getDate() + 1);
renderedEvents.push({
id: IterationId + iteration.name,
allDay: true,
start: start,
end: exclusiveEndDate,
title: iteration.name,
textColor: "#FFFFFF",
backgroundColor: color,
rendering: "background"
});
currentIterations.push({
color: color,
subTitle: formatDate(start, "MONTH-DD") + " - " + formatDate(end, "MONTH-DD"),
title: iteration.name,
eventCount: 1
});
const teamsDayOffPromise = this.fetchTeamDaysOff(iteration.id);
teamDaysOffPromises.push(teamsDayOffPromise);
teamsDayOffPromise.then((teamDaysOff: TeamSettingsDaysOff) => {
this.processTeamDaysOff(teamDaysOff, iteration.id, capacityCatagoryMap, calendarStart, calendarEnd);
});
const capacityPromise = this.fetchCapacities(iteration.id);
capacityPromises.push(capacityPromise);
capacityPromise.then((capacities: TeamMemberCapacityIdentityRef[]) => {
this.processCapacity(capacities, iteration.id, capacityCatagoryMap, calendarStart, calendarEnd);
});
}
const exclusiveEndDate = new Date(end);
exclusiveEndDate.setDate(end.getDate() + 1);
renderedEvents.push({
id: IterationId + iteration.name,
allDay: true,
start: start,
end: exclusiveEndDate,
title: iteration.name,
textColor: "#FFFFFF",
backgroundColor: color,
rendering: "background"
});
currentIterations.push({
color: color,
subTitle: formatDate(start, "MONTH-DD") + " - " + formatDate(end, "MONTH-DD"),
title: iteration.name,
eventCount: 1
});
const teamsDayOffPromise = this.fetchTeamDaysOff(iteration.id);
teamDaysOffPromises.push(teamsDayOffPromise);
teamsDayOffPromise.then((teamDaysOff: TeamSettingsDaysOff) => {
this.processTeamDaysOff(teamDaysOff, iteration.id, capacityCatagoryMap, calendarStart, calendarEnd);
});
const capacityPromise = this.fetchCapacities(iteration.id);
capacityPromises.push(capacityPromise);
capacityPromise.then((capacities: TeamMemberCapacityIdentityRef[]) => {
this.processCapacity(capacities, iteration.id, capacityCatagoryMap, calendarStart, calendarEnd);
});
}
}
@ -194,18 +196,14 @@ export class VSOCapacityEventSource {
}
});
successCallback(renderedEvents);
this.iterationSummaryData.splice(0, this.iterationSummaryData.length, ...currentIterations);
this.capacitySummaryData.splice(
0,
this.capacitySummaryData.length,
...Object.keys(capacityCatagoryMap).map(key => {
const catagory = capacityCatagoryMap[key];
if (catagory.eventCount > 1) {
catagory.subTitle = catagory.eventCount + " days off";
}
return catagory;
})
);
this.iterationSummaryData.value = currentIterations;
this.capacitySummaryData.value = Object.keys(capacityCatagoryMap).map(key => {
const catagory = capacityCatagoryMap[key];
if (catagory.eventCount > 1) {
catagory.subTitle = catagory.eventCount + " days off";
}
return catagory;
});
});
});
});

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

Двоичные данные
static/sample-icon.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 2.8 KiB