diff --git a/package-lock.json b/package-lock.json
index 95f39e8a3..d7bb3f943 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -205,6 +205,22 @@
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.0.5.tgz",
"integrity": "sha512-WGs4Jxw5sr8GCpxMcwEVuZnDIkdNp9qtmuI2j13v/XAaMjvJ7jssCj9+JG5uI8joCi7PFVAWokPT1DdPwWb13Q=="
},
+ "@angular/cdk": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-9.1.2.tgz",
+ "integrity": "sha512-x5niyE0iYrbVtLYjJFw2MoS+OoSbJn6y/G2pNScviDwyjBBgqRh4YgUox2kMhdPumkvuh+eA6blZoE9qpvSo2w==",
+ "requires": {
+ "parse5": "^5.0.0"
+ },
+ "dependencies": {
+ "parse5": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
+ "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
+ "optional": true
+ }
+ }
+ },
"@angular/cli": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-9.0.5.tgz",
@@ -368,6 +384,11 @@
"integrity": "sha512-9ykFNYZpWdoggFPK3LuTJobBZjoCEQP6kKt88ZZV7GTMIXoE7iY413KfhINoXdMwxIAbba0xlPMR/4p6jFAa4Q==",
"dev": true
},
+ "@angular/material": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/@angular/material/-/material-9.1.2.tgz",
+ "integrity": "sha512-8uwwkSnsg/YlhqxD/+0Cj+1S97Xf5WUcgxSEXmC1r0/AD+o6PGL5ImIk4n+3tdgYqm7MoJZQthlIB2J0EVLjVw=="
+ },
"@angular/platform-browser": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-9.0.5.tgz",
diff --git a/package.json b/package.json
index ba16a065c..b056971dd 100644
--- a/package.json
+++ b/package.json
@@ -12,10 +12,12 @@
"private": true,
"dependencies": {
"@angular/animations": "~9.0.5",
+ "@angular/cdk": "^9.1.2",
"@angular/common": "~9.0.5",
"@angular/compiler": "~9.0.5",
"@angular/core": "~9.0.5",
"@angular/forms": "~9.0.5",
+ "@angular/material": "^9.1.2",
"@angular/platform-browser": "~9.0.5",
"@angular/platform-browser-dynamic": "~9.0.5",
"@angular/router": "~9.0.5",
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 0c510c671..9af6f4b83 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,534 +1,2 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ title }} app is running!
-
-
-
-
-
-
-
Resources
-
Here are some links to help you get started:
-
-
-
-
-
Next Steps
-
What do you want to do next with your app?
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Run and Watch Tests
-
-
-
-
-
-
Build for Production
-
-
-
-
-
-
ng generate component xyz
-
ng add @angular/material
-
ng add @angular/pwa
-
ng add _____
-
ng test
-
ng build --prod
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+{{title}}
+
\ No newline at end of file
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index e69de29bb..6df0acfc4 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -0,0 +1,9 @@
+/* Structure */
+table {
+ width: 100%;
+}
+
+.mat-form-field {
+ font-size: 14px;
+ width: 100%;
+}
\ No newline at end of file
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index e0ba82b20..fed51e5c5 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -6,5 +6,5 @@ import { Component } from '@angular/core';
styleUrls: ['./app.component.scss']
})
export class AppComponent {
- title = 'tech-ui';
+ title = 'Tech UI';
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 2c3ba2995..83c230204 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -3,14 +3,24 @@ import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { StatusListComponent } from './status-list/status-list.component';
+import { MatTableModule } from '@angular/material/table';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatSortModule } from '@angular/material/sort';
@NgModule({
declarations: [
- AppComponent
+ AppComponent,
+ StatusListComponent,
],
imports: [
BrowserModule,
- AppRoutingModule
+ AppRoutingModule,
+ BrowserAnimationsModule,
+ MatTableModule,
+ MatPaginatorModule,
+ MatSortModule,
],
providers: [],
bootstrap: [AppComponent]
diff --git a/src/app/folder.ts b/src/app/folder.ts
new file mode 100644
index 000000000..0ca038740
--- /dev/null
+++ b/src/app/folder.ts
@@ -0,0 +1,6 @@
+export interface Folder {
+ id: string;
+ label: string;
+
+ // TODO add additional properties
+}
\ No newline at end of file
diff --git a/src/app/mock-folders.ts b/src/app/mock-folders.ts
new file mode 100644
index 000000000..06d699b87
--- /dev/null
+++ b/src/app/mock-folders.ts
@@ -0,0 +1,9 @@
+import { Folder } from './folder';
+
+export const FOLDERS: Folder[] = [
+ { id: 'GXWxf-3zgnU', label: 'Downloads' },
+ { id: 'PXWxf-3zgnU', label: 'Music' },
+ { id: 'QXWxf-3zgnU', label: 'Photos' },
+ { id: 'RXWxf-3zgnU', label: 'Movies' },
+ { id: 'SXWxf-3zgnU', label: 'Folder 5' },
+];
\ No newline at end of file
diff --git a/src/app/status-list/status-list-folder-datasource.ts b/src/app/status-list/status-list-folder-datasource.ts
new file mode 100644
index 000000000..b072477a6
--- /dev/null
+++ b/src/app/status-list/status-list-folder-datasource.ts
@@ -0,0 +1,80 @@
+import { DataSource } from '@angular/cdk/collections';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatSort } from '@angular/material/sort';
+import { map } from 'rxjs/operators';
+import { Observable, of as observableOf, merge } from 'rxjs';
+import { Folder } from '../folder';
+import { SystemConfigService } from '../system-config.service';
+import { FOLDERS } from '../mock-folders';
+
+/**
+ * Data source for the StatusList view. This class should
+ * encapsulate all logic for fetching and manipulating the displayed data
+ * (including sorting, pagination, and filtering).
+ */
+export class StatusListFolderDataSource extends DataSource {
+ data: Folder[] = FOLDERS;
+ paginator: MatPaginator;
+ sort: MatSort;
+
+ constructor(private systemConfigService: SystemConfigService) {
+ super();
+ }
+
+ /**
+ * Connect this data source to the table. The table will only update when
+ * the returned stream emits new items.
+ * @returns A stream of the items to be rendered.
+ */
+ connect(): Observable {
+ // Combine everything that affects the rendered data into one update
+ // stream for the data-table to consume.
+ const dataMutations = [
+ observableOf(this.data),
+ this.paginator.page,
+ this.sort.sortChange
+ ];
+
+ return merge(...dataMutations).pipe(map(() => {
+ return this.getPagedData(this.getSortedData([...this.data]));
+ }));
+ }
+
+ /**
+ * Called when the table is being destroyed. Use this function, to clean up
+ * any open connections or free any held resources that were set up during connect.
+ */
+ disconnect() { }
+
+ /**
+ * Paginate the data (client-side). If you're using server-side pagination,
+ * this would be replaced by requesting the appropriate data from the server.
+ */
+ private getPagedData(data: Folder[]) {
+ const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
+ return data.splice(startIndex, this.paginator.pageSize);
+ }
+
+ /**
+ * Sort the data (client-side). If you're using server-side sorting,
+ * this would be replaced by requesting the appropriate data from the server.
+ */
+ private getSortedData(data: Folder[]) {
+ if (!this.sort.active || this.sort.direction === '') {
+ return data;
+ }
+
+ return data.sort((a, b) => {
+ const isAsc = this.sort.direction === 'asc';
+ switch (this.sort.active) {
+ case 'label': return compare(a.label, b.label, isAsc);
+ case 'id': return compare(+a.id, +b.id, isAsc);
+ default: return 0;
+ }
+ });
+ }
+}
+
+function compare(a: string | number, b: string | number, isAsc: boolean) {
+ return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
+}
diff --git a/src/app/status-list/status-list.component.html b/src/app/status-list/status-list.component.html
new file mode 100644
index 000000000..b8dda2cd5
--- /dev/null
+++ b/src/app/status-list/status-list.component.html
@@ -0,0 +1,22 @@
+
+
+
+
+ | Id |
+ {{row.id}} |
+
+
+
+
+ Label |
+ {{row.label}} |
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/status-list/status-list.component.scss b/src/app/status-list/status-list.component.scss
new file mode 100644
index 000000000..5050fb6e0
--- /dev/null
+++ b/src/app/status-list/status-list.component.scss
@@ -0,0 +1,3 @@
+.full-width-table {
+ width: 100%;
+}
diff --git a/src/app/status-list/status-list.component.spec.ts b/src/app/status-list/status-list.component.spec.ts
new file mode 100644
index 000000000..daa23d2ba
--- /dev/null
+++ b/src/app/status-list/status-list.component.spec.ts
@@ -0,0 +1,34 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatSortModule } from '@angular/material/sort';
+import { MatTableModule } from '@angular/material/table';
+
+import { StatusListComponent } from './status-list.component';
+
+describe('StatusListComponent', () => {
+ let component: StatusListComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ StatusListComponent ],
+ imports: [
+ NoopAnimationsModule,
+ MatPaginatorModule,
+ MatSortModule,
+ MatTableModule,
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(StatusListComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should compile', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/status-list/status-list.component.ts b/src/app/status-list/status-list.component.ts
new file mode 100644
index 000000000..9a4e830a6
--- /dev/null
+++ b/src/app/status-list/status-list.component.ts
@@ -0,0 +1,34 @@
+import { AfterViewInit, Component, OnInit, ViewChild } from '@angular/core';
+import { MatPaginator } from '@angular/material/paginator';
+import { MatSort } from '@angular/material/sort';
+import { MatTable } from '@angular/material/table';
+import { StatusListFolderDataSource } from './status-list-folder-datasource';
+import { Folder } from '../folder';
+import { SystemConfigService } from '../system-config.service';
+
+@Component({
+ selector: 'app-status-list',
+ templateUrl: './status-list.component.html',
+ styleUrls: ['./status-list.component.scss']
+})
+export class StatusListComponent implements AfterViewInit, OnInit {
+ @ViewChild(MatPaginator) paginator: MatPaginator;
+ @ViewChild(MatSort) sort: MatSort;
+ @ViewChild(MatTable) table: MatTable;
+ dataSource: StatusListFolderDataSource;
+
+ /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
+ displayedColumns = ['id', 'name'];
+
+ constructor(private systemConfigService: SystemConfigService) { };
+
+ ngOnInit() {
+ this.dataSource = new StatusListFolderDataSource(this.systemConfigService);
+ }
+
+ ngAfterViewInit() {
+ this.dataSource.sort = this.sort;
+ this.dataSource.paginator = this.paginator;
+ this.table.dataSource = this.dataSource;
+ }
+}
diff --git a/src/app/system-config.service.spec.ts b/src/app/system-config.service.spec.ts
new file mode 100644
index 000000000..7f2beb10a
--- /dev/null
+++ b/src/app/system-config.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { SystemConfigService } from './system-config.service';
+
+describe('SystemConfigService', () => {
+ let service: SystemConfigService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(SystemConfigService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/system-config.service.ts b/src/app/system-config.service.ts
new file mode 100644
index 000000000..852af0d09
--- /dev/null
+++ b/src/app/system-config.service.ts
@@ -0,0 +1,18 @@
+import { Injectable } from '@angular/core';
+
+import { Observable, of } from 'rxjs';
+
+import { Folder } from './folder';
+import { FOLDERS } from './mock-folders';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class SystemConfigService {
+
+ constructor() { }
+
+ getFolders(): Observable {
+ return of(FOLDERS);
+ }
+}
diff --git a/src/index.html b/src/index.html
index eaa508f86..96b91058b 100644
--- a/src/index.html
+++ b/src/index.html
@@ -6,6 +6,8 @@
+
+
diff --git a/src/styles.scss b/src/styles.scss
index 90d4ee007..7f8d3bdaf 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -1 +1,32 @@
+
+// Custom Theming for Angular Material
+// For more information: https://material.angular.io/guide/theming
+@import '~@angular/material/theming';
+// Plus imports for other components in your app.
+
+// Include the common styles for Angular Material. We include this here so that you only
+// have to load a single css file for Angular Material in your app.
+// Be sure that you only ever include this mixin once!
+@include mat-core();
+
+// Define the palettes for your theme using the Material Design palettes available in palette.scss
+// (imported above). For each palette, you can optionally specify a default, lighter, and darker
+// hue. Available color palettes: https://material.io/design/color/
+$tech-ui-primary: mat-palette($mat-indigo);
+$tech-ui-accent: mat-palette($mat-pink, A200, A100, A400);
+
+// The warn palette is optional (defaults to red).
+$tech-ui-warn: mat-palette($mat-red);
+
+// Create the theme object (a Sass map containing all of the palettes).
+$tech-ui-theme: mat-light-theme($tech-ui-primary, $tech-ui-accent, $tech-ui-warn);
+
+// Include theme styles for core and each component used in your app.
+// Alternatively, you can import and @include the theme mixins for each component
+// that you are using.
+@include angular-material-theme($tech-ui-theme);
+
/* You can add global styles to this file, and also import other style files */
+
+html, body { height: 100%; }
+body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }