From 8b72c82efc63dd781635a939a93982ec693b6930 Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 23 Feb 2019 00:51:45 +0800 Subject: Add icon and redesign card in todo page. --- .../todo-list-page/todo-list-page.component.css | 31 ++++++----- .../todo-list-page/todo-list-page.component.html | 12 ++--- .../todo-list-page.component.spec.ts | 4 +- .../app/todo-list-page/todo-list.service.spec.ts | 63 ++++++++++++---------- .../src/app/todo-list-page/todo-list.service.ts | 23 ++++++-- 5 files changed, 79 insertions(+), 54 deletions(-) (limited to 'Timeline/ClientApp/src') diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.css b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.css index 89a0f0ce..ed260d5c 100644 --- a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.css +++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.css @@ -13,24 +13,19 @@ justify-content: space-between; } -.item-id { - display: inline-block; - text-align: center; - border-radius: 0.2rem; - width: 1.2rem; - height: 1.2rem; -} - -.item-id-open { - background: red; +.item-card { + display: flex; + align-items: center; } -.item-id-closed { - background: green; +.item-img { + padding: 1px; + background: white; + border-radius: 3px; } .item-title { - margin-left: 5px; + padding-left: 5px; color: black; text-decoration: none; } @@ -45,9 +40,17 @@ align-items: center; } -.item-id-sample { +.sample-color-block { border-radius: 0.2em; width: 1em; height: 1em; margin-right: 2px; } + +.sample-color-block-open { + background: tomato; +} + +.sample-color-block-closed { + background: mediumspringgreen; +} diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html index 4e1aa2f1..0dcc8a94 100644 --- a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html +++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html @@ -3,17 +3,17 @@
- - {{item.id}} - {{item.title}} + + + {{item.id}} {{item.title}}
- means working now. + means working now.
- means completed. + means completed.
click on item to see details.
diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts index 2da74002..9543f7ad 100644 --- a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts +++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts @@ -30,9 +30,9 @@ describe('TodoListPageComponent', () => { const todoListService: jasmine.SpyObj = jasmine.createSpyObj('TodoListService', ['getWorkItemList']); mockWorkItems = [{ - id: 0, title: 'Test title 1', closed: true, detailUrl: 'https://test.org/workitems/0' + id: 0, title: 'Test title 1', closed: true, detailUrl: 'https://test.org/workitems/0', iconUrl: 'https://test.org/icon/0' }, { - id: 1, title: 'Test title 2', closed: false, detailUrl: 'https://test.org/workitems/1' + id: 1, title: 'Test title 2', closed: false, detailUrl: 'https://test.org/workitems/1', iconUrl: 'https://test.org/icon/1' }]; todoListService.getWorkItemList.and.returnValue(asyncData(mockWorkItems)); diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts b/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts index 3ed4e004..13bae5be 100644 --- a/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts +++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts @@ -1,7 +1,10 @@ import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { TodoListService, WorkItem, AzureDevOpsAccessInfo, WiqlResult, WiqlWorkItemResult, WorkItemResult } from './todo-list.service'; +import { + TodoListService, WorkItem, AzureDevOpsAccessInfo, + WiqlResult, WiqlWorkItemResult, WorkItemResult, WorkItemTypeResult +} from './todo-list.service'; describe('TodoListServiceService', () => { @@ -26,20 +29,17 @@ describe('TodoListServiceService', () => { project: 'testproject' }; - const generateDetailUrl = (id: number) => - `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/_workitems/edit/${id}/`; - - const mockWorkItems: WorkItem[] = [{ - id: 0, - title: 'Test work item 1', - closed: true, - detailUrl: generateDetailUrl(0) - }, { - id: 1, - title: 'Test work item 2', - closed: false, - detailUrl: generateDetailUrl(1) - }]; + const baseUrl = `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/`; + + const mockWorkItems: WorkItem[] = Array.from({ length: 2 }, (_, i) => { + id: i, + title: 'Test work item ' + i, + closed: i === 0, + detailUrl: `${baseUrl}_workitems/edit/${i}/`, + iconUrl: `${baseUrl}_api/wit/icon/${i}`, + }); + + const workItemTypeMap = new Map(Array.from(mockWorkItems, v => <[WorkItem, string]>[v, 'type' + v.id])); service.getWorkItemList().subscribe(data => { expect(data).toEqual(mockWorkItems); @@ -47,31 +47,27 @@ describe('TodoListServiceService', () => { const httpController: HttpTestingController = TestBed.get(HttpTestingController); - - httpController.expectOne('/api/TodoPage/AzureDevOpsAccessInfo').flush(mockAccessInfo); - const mockWiqlWorkItems: WiqlWorkItemResult[] = [{ - id: 0, - url: `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/_apis/wit/workItems/0` - }, { - id: 1, - url: `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/_apis/wit/workItems/1` - }]; + const mockWiqlWorkItems: WiqlWorkItemResult[] = Array.from(mockWorkItems, v => { + id: v.id, + url: `${baseUrl}_apis/wit/workItems/${v.id}` + }); const authorizationHeader = 'Basic ' + btoa(mockAccessInfo.username + ':' + mockAccessInfo.personalAccessToken); httpController.expectOne(req => - req.url === `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/_apis/wit/wiql?api-version=5.0` && + req.url === `${baseUrl}_apis/wit/wiql?api-version=5.0` && req.headers.get('Authorization') === authorizationHeader ).flush({ workItems: mockWiqlWorkItems }); - function mapWorkItemToResult(workItem: WorkItem): WorkItemResult { + function mapWorkItemToResult(mockWorkItem: WorkItem): WorkItemResult { return { - id: workItem.id, + id: mockWorkItem.id, fields: { - [TodoListService.titleFieldName]: workItem.title, - [TodoListService.stateFieldName]: (workItem.closed ? 'Closed' : 'Active') + [TodoListService.titleFieldName]: mockWorkItem.title, + [TodoListService.stateFieldName]: (mockWorkItem.closed ? 'Closed' : 'Active'), + [TodoListService.typeFieldName]: workItemTypeMap.get(mockWorkItem) } }; } @@ -81,6 +77,15 @@ describe('TodoListServiceService', () => { req.url === mockWiqlWorkItems[i].url && req.headers.get('Authorization') === authorizationHeader ).flush(mapWorkItemToResult(mockWorkItems[i])); + + httpController.expectOne(req => + req.url === `${baseUrl}_apis/wit/workitemtypes/${encodeURIComponent(workItemTypeMap.get(mockWorkItems[i]))}?api-version=5.0` && + req.headers.get('Authorization') === authorizationHeader + ).flush({ + icon: { + url: mockWorkItems[i].iconUrl + } + }); } }); }); diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts b/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts index ed28bc59..17ded67b 100644 --- a/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts +++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts @@ -24,12 +24,18 @@ export interface WorkItemResult { fields: { [name: string]: any }; } +export interface WorkItemTypeResult { + icon: { + url: string + }; +} export interface WorkItem { id: number; title: string; closed: boolean; detailUrl: string; + iconUrl: string; } @Injectable({ @@ -39,6 +45,7 @@ export class TodoListService { public static titleFieldName = 'System.Title'; public static stateFieldName = 'System.State'; + public static typeFieldName = 'System.WorkItemType'; constructor(private client: HttpClient) { } @@ -46,6 +53,14 @@ export class TodoListService { return this.client.get('/api/TodoPage/AzureDevOpsAccessInfo'); } + private getItemIconUrl(baseUrl: string, headers: HttpHeaders, type: string): Observable { + return this.client.get(`${baseUrl}_apis/wit/workitemtypes/${encodeURIComponent(type)}?api-version=5.0`, { + headers: headers + }).pipe( + map(result => result.icon.url) + ); + } + getWorkItemList(): Observable { return this.getAzureDevOpsAccessInfo().pipe( switchMap( @@ -61,12 +76,14 @@ export class TodoListService { }, { headers: headers }).pipe( switchMap(result => result.workItems), concatMap(result => this.client.get(result.url, { headers: headers })), - map(result => { + concatMap(result => this.getItemIconUrl(baseUrl, headers, result.fields[TodoListService.typeFieldName]).pipe( + map(iconResult => { id: result.id, title: result.fields[TodoListService.titleFieldName], closed: ((result.fields[TodoListService.stateFieldName]).toLowerCase() === 'closed'), - detailUrl: `${baseUrl}_workitems/edit/${result.id}/` - }), + detailUrl: `${baseUrl}_workitems/edit/${result.id}/`, + iconUrl: iconResult + }))), toArray() ); } -- cgit v1.2.3 From e4b394dcfd727fd02c34de88261c813fbe24776f Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 23 Feb 2019 23:55:04 +0800 Subject: Add icon of todo list and redesign the page. --- Timeline/ClientApp/.prettierrc.json | 3 +++ Timeline/ClientApp/src/app/app.module.ts | 4 ++- .../src/app/todo-item/todo-item.component.css | 27 ++++++++++++++++++++ .../src/app/todo-item/todo-item.component.html | 11 ++++++++ .../src/app/todo-item/todo-item.component.spec.ts | 25 +++++++++++++++++++ .../src/app/todo-item/todo-item.component.ts | 14 +++++++++++ .../app/todo-list-page/todo-list-color-block.css | 7 ++++++ .../todo-list-page/todo-list-page.component.css | 29 +++++----------------- .../todo-list-page/todo-list-page.component.html | 19 +++++++------- .../app/todo-list-page/todo-list-page.component.ts | 2 +- 10 files changed, 106 insertions(+), 35 deletions(-) create mode 100644 Timeline/ClientApp/.prettierrc.json create mode 100644 Timeline/ClientApp/src/app/todo-item/todo-item.component.css create mode 100644 Timeline/ClientApp/src/app/todo-item/todo-item.component.html create mode 100644 Timeline/ClientApp/src/app/todo-item/todo-item.component.spec.ts create mode 100644 Timeline/ClientApp/src/app/todo-item/todo-item.component.ts create mode 100644 Timeline/ClientApp/src/app/todo-list-page/todo-list-color-block.css (limited to 'Timeline/ClientApp/src') diff --git a/Timeline/ClientApp/.prettierrc.json b/Timeline/ClientApp/.prettierrc.json new file mode 100644 index 00000000..30371183 --- /dev/null +++ b/Timeline/ClientApp/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "printWidth": 140 +} diff --git a/Timeline/ClientApp/src/app/app.module.ts b/Timeline/ClientApp/src/app/app.module.ts index 3247cf92..86511be8 100644 --- a/Timeline/ClientApp/src/app/app.module.ts +++ b/Timeline/ClientApp/src/app/app.module.ts @@ -12,12 +12,14 @@ import { import { AppComponent } from './app.component'; import { HomeComponent } from './home/home.component'; import { TodoListPageComponent } from './todo-list-page/todo-list-page.component'; +import { TodoItemComponent } from './todo-item/todo-item.component'; @NgModule({ declarations: [ AppComponent, HomeComponent, - TodoListPageComponent + TodoListPageComponent, + TodoItemComponent ], imports: [ BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), diff --git a/Timeline/ClientApp/src/app/todo-item/todo-item.component.css b/Timeline/ClientApp/src/app/todo-item/todo-item.component.css new file mode 100644 index 00000000..ef952a04 --- /dev/null +++ b/Timeline/ClientApp/src/app/todo-item/todo-item.component.css @@ -0,0 +1,27 @@ +.item-card { + padding: 0; + display: flex; + overflow: hidden; +} + +.item-color-block { + width: 15px; + align-self: stretch; + flex: 0 0 auto; +} + +.item-icon { + width: 1.2em; + height: 1.2em; + vertical-align: -0.25em; +} + +.item-title { + vertical-align: middle; +} + +.item-detail-button { + width: unset; + height: unset; + line-height: unset; +} diff --git a/Timeline/ClientApp/src/app/todo-item/todo-item.component.html b/Timeline/ClientApp/src/app/todo-item/todo-item.component.html new file mode 100644 index 00000000..624586cb --- /dev/null +++ b/Timeline/ClientApp/src/app/todo-item/todo-item.component.html @@ -0,0 +1,11 @@ + + + +
+ + {{ item.id }}. {{ item.title }} + + arrow_forward + +
+
diff --git a/Timeline/ClientApp/src/app/todo-item/todo-item.component.spec.ts b/Timeline/ClientApp/src/app/todo-item/todo-item.component.spec.ts new file mode 100644 index 00000000..7ec7d768 --- /dev/null +++ b/Timeline/ClientApp/src/app/todo-item/todo-item.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TodoItemComponent } from './todo-item.component'; + +describe('TodoItemComponent', () => { + let component: TodoItemComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TodoItemComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TodoItemComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/Timeline/ClientApp/src/app/todo-item/todo-item.component.ts b/Timeline/ClientApp/src/app/todo-item/todo-item.component.ts new file mode 100644 index 00000000..27d57e28 --- /dev/null +++ b/Timeline/ClientApp/src/app/todo-item/todo-item.component.ts @@ -0,0 +1,14 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { WorkItem } from '../todo-list-page/todo-list.service'; + +@Component({ + selector: 'app-todo-item', + templateUrl: './todo-item.component.html', + styleUrls: ['./todo-item.component.css', '../todo-list-page/todo-list-color-block.css'] +}) +export class TodoItemComponent { + + @Input() item: WorkItem; + + +} diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list-color-block.css b/Timeline/ClientApp/src/app/todo-list-page/todo-list-color-block.css new file mode 100644 index 00000000..5e0d4ba9 --- /dev/null +++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list-color-block.css @@ -0,0 +1,7 @@ +.color-block-open { + background: red; +} + +.color-block-closed { + background: green; +} diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.css b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.css index ed260d5c..754b786e 100644 --- a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.css +++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.css @@ -5,29 +5,20 @@ .item-box { display: flex; width: 100%; - padding: 5px; box-sizing: border-box; } .first-item-box { justify-content: space-between; + padding: 0 0 5px 5px; } -.item-card { - display: flex; - align-items: center; -} - -.item-img { - padding: 1px; - background: white; - border-radius: 3px; +.non-first-item-box { + padding: 5px; } -.item-title { - padding-left: 5px; - color: black; - text-decoration: none; +.space { + flex: 1 4 20px; } .sample-box { @@ -35,7 +26,7 @@ align-self: flex-start; } -.sample-container { +.sample-item { display: flex; align-items: center; } @@ -46,11 +37,3 @@ height: 1em; margin-right: 2px; } - -.sample-color-block-open { - background: tomato; -} - -.sample-color-block-closed { - background: mediumspringgreen; -} diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html index 0dcc8a94..3b4809ae 100644 --- a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html +++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html @@ -2,18 +2,17 @@ -
- - - {{item.id}} {{item.title}} - +
+ +
-
- means working now. +
+ + means working now.
-
- means completed. +
+ + means completed.
click on item to see details.
diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.ts b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.ts index 06f49923..e58cca7d 100644 --- a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.ts +++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.ts @@ -4,7 +4,7 @@ import { TodoListService, WorkItem } from './todo-list.service'; @Component({ selector: 'app-todo-list-page', templateUrl: './todo-list-page.component.html', - styleUrls: ['./todo-list-page.component.css'] + styleUrls: ['./todo-list-page.component.css', './todo-list-color-block.css'] }) export class TodoListPageComponent implements OnInit { -- cgit v1.2.3