Initial state

This commit is contained in:
SteveSandersonMS
2015-11-02 10:30:36 -08:00
parent 0e1fa2e09d
commit f693bd60e3
110 changed files with 6722 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
body {
padding-top: 50px;
}

View File

@@ -0,0 +1,14 @@
@base: #f938ab;
.box-shadow(@style, @c) when (iscolor(@c)) {
-webkit-box-shadow: @style @c;
box-shadow: @style @c;
}
.box-shadow(@style, @alpha: 50%) when (isnumber(@alpha)) {
.box-shadow(@style, rgba(0, 0, 0, @alpha));
}
.box {
color: saturate(@base, 5%);
border-color: lighten(@base, 30%);
div { .box-shadow(0 0 5px, 30%) }
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,3 @@
<h1>Store Manager</h1>
<router-outlet></router-outlet>

View File

@@ -0,0 +1,21 @@
import * as ng from 'angular2/angular2';
import * as router from 'angular2/router';
import { AlbumsList } from '../albums-list/albums-list';
import { AlbumDetails } from '../album-details/album-details';
import { AlbumEdit } from '../album-edit/album-edit';
@ng.Component({
selector: 'admin-home'
})
@router.RouteConfig([
{ path: 'albums', as: 'Albums', component: AlbumsList },
{ path: 'album/details/:albumId', as: 'AlbumDetails', component: AlbumDetails },
{ path: 'album/edit/:albumId', as: 'AlbumEdit', component: AlbumEdit }
])
@ng.View({
templateUrl: './ng-app/components/admin/admin-home/admin-home.html',
directives: [router.ROUTER_DIRECTIVES]
})
export class AdminHome {
}

View File

@@ -0,0 +1,17 @@
<div class="modal fade">
<div class="modal-dialog" *ng-if="album">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">Delete {{ album.Title }}</h4>
</div>
<div class="modal-body">
<p>Really delete <strong>{{ album.Title }}</strong> by <strong>{{ album.Artist.Name }}</strong>?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger">Confirm Delete</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

View File

@@ -0,0 +1,25 @@
import * as ng from 'angular2/angular2';
import * as models from '../../../models/models';
@ng.Component({
selector: 'album-delete-prompt'
})
@ng.View({
templateUrl: './ng-app/components/admin/album-delete-prompt/album-delete-prompt.html',
directives: [ng.NgIf]
})
export class AlbumDeletePrompt {
private modalElement: any;
public album: models.Album;
constructor(@ng.Inject(ng.ElementRef) elementRef: ng.ElementRef) {
if (typeof window !== 'undefined') {
this.modalElement = (<any>window).jQuery(".modal", elementRef.nativeElement);
}
}
public show(album: models.Album) {
this.album = album;
this.modalElement.modal();
}
}

View File

@@ -0,0 +1,50 @@
<h2>Album <small>Details</small></h2>
<hr />
<form class="form-horizontal" role="form" *ng-if="albumData">
<div class="form-group">
<label class="col-md-2 control-label">Artist</label>
<div class="col-md-10">
<p class="form-control-static">{{ albumData.Artist.Name }}</p>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Genre</label>
<div class="col-md-10">
<p class="form-control-static">{{ albumData.Genre.Name }}</p>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Title</label>
<div class="col-md-10">
<p class="form-control-static">{{ albumData.Title }}</p>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Price</label>
<div class="col-md-10">
<p class="form-control-static">{{ albumData.Price | currency:'USD':true }}</p>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Album Art URL</label>
<div class="col-md-10">
<p class="form-control-static">{{ albumData.AlbumArtUrl }}</p>
</div>
</div>
<div class="form-group">
<label class="col-md-2 control-label">Album Art</label>
<div class="col-md-10">
<img alt="{{ albumData.Title }}" src="{{ albumData.AlbumArtUrl }}">
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<a class="btn btn-primary" [router-link]="['/Admin/AlbumEdit', { albumId: albumData.AlbumId }]">Edit</a>
<button type="button" class="btn btn-danger" (click)="deleteprompt.show(albumData)">Delete</button>
<a class="btn btn-default" [router-link]="['/Admin/Albums']">Back to List</a>
</div>
</div>
</form>
<album-delete-prompt #deleteprompt></album-delete-prompt>

View File

@@ -0,0 +1,22 @@
import * as ng from 'angular2/angular2';
import * as router from 'angular2/router';
import * as models from '../../../models/models';
import { Http, HTTP_BINDINGS } from 'angular2/http';
import { AlbumDeletePrompt } from '../album-delete-prompt/album-delete-prompt';
@ng.Component({
selector: 'album-details'
})
@ng.View({
templateUrl: './ng-app/components/admin/album-details/album-details.html',
directives: [router.ROUTER_DIRECTIVES, ng.NgIf, AlbumDeletePrompt]
})
export class AlbumDetails {
public albumData: models.Album;
constructor(http: Http, routeParam: router.RouteParams) {
http.get('/api/albums/' + routeParam.params['albumId']).subscribe(result => {
this.albumData = result.json();
});
}
}

View File

@@ -0,0 +1,45 @@
<h2>Album <small>Edit</small></h2>
<hr />
<form class="form-horizontal" [ng-form-model]="form" (ng-submit)="onSubmitModelBased()">
<form-field label="Artist" [validate]="form.controls.artist">
<select class="form-control" ng-control="artist">
<option value="">-- choose Artist --</option>
<option *ng-for="#artist of artists" [value]="artist.ArtistId">{{ artist.Name }}</option>
</select>
</form-field>
<form-field label="Genre" [validate]="form.controls.genre">
<select class="form-control" ng-control="genre">
<option value="">-- choose Genre --</option>
<option *ng-for="#genre of genres" [value]="genre.GenreId">{{ genre.Name }}</option>
</select>
</form-field>
<form-field label="Title" [validate]="form.controls.title">
<input class="form-control" type="text" ng-control="title">
</form-field>
<form-field label="Price" [validate]="form.controls.price">
<div class="input-group">
<span class="input-group-addon">$</span>
<input class="form-control" type="text" ng-control="price">
</div>
</form-field>
<form-field label="Album Art URL" [validate]="form.controls.albumArtUrl">
<input class="form-control" ng-control="albumArtUrl">
</form-field>
<form-field label="Album Art">
<img src="{{ form.controls.albumArtUrl.value }}">
</form-field>
<form-field>
<button type="submit" class="btn btn-primary">Submit</button>
<button type="button" class="btn btn-danger" (click)="deleteprompt.show(originalAlbum)">Delete</button>
<a class="btn btn-default" [router-link]="['/Admin/Albums']">Back to List</a>
</form-field>
</form>
<album-delete-prompt #deleteprompt></album-delete-prompt>

View File

@@ -0,0 +1,82 @@
import * as ng from 'angular2/angular2';
import * as router from 'angular2/router';
import * as models from '../../../models/models';
import { Http, HTTP_BINDINGS, Headers } from 'angular2/http';
import { AlbumDeletePrompt } from '../album-delete-prompt/album-delete-prompt';
import { FormField } from '../form-field/form-field';
@ng.Component({
selector: 'album-edit'
})
@ng.View({
templateUrl: './ng-app/components/admin/album-edit/album-edit.html',
directives: [router.ROUTER_DIRECTIVES, ng.NgIf, ng.NgFor, AlbumDeletePrompt, FormField, ng.FORM_DIRECTIVES]
})
export class AlbumEdit {
public form: ng.ControlGroup;
public artists: models.Artist[];
public genres: models.Genre[];
public originalAlbum: models.Album;
private _http: Http;
constructor(fb: ng.FormBuilder, http: Http, routeParam: router.RouteParams) {
this._http = http;
http.get('/api/albums/' + routeParam.params['albumId']).subscribe(result => {
var json = result.json();
this.originalAlbum = json;
(<ng.Control>this.form.controls['title']).updateValue(json.Title);
(<ng.Control>this.form.controls['price']).updateValue(json.Price);
(<ng.Control>this.form.controls['artist']).updateValue(json.ArtistId);
(<ng.Control>this.form.controls['genre']).updateValue(json.GenreId);
(<ng.Control>this.form.controls['albumArtUrl']).updateValue(json.AlbumArtUrl);
});
http.get('/api/artists/lookup').subscribe(result => {
this.artists = result.json();
});
http.get('/api/genres/genre-lookup').subscribe(result => {
this.genres = result.json();
});
this.form = fb.group(<any>{
artist: fb.control('', ng.Validators.required),
genre: fb.control('', ng.Validators.required),
title: fb.control('', ng.Validators.required),
price: fb.control('', ng.Validators.compose([ng.Validators.required, AlbumEdit._validatePrice])),
albumArtUrl: fb.control('', ng.Validators.required)
});
}
public onSubmitModelBased() {
// Force all fields to show any validation errors even if they haven't been touched
Object.keys(this.form.controls).forEach(controlName => {
this.form.controls[controlName].markAsTouched();
});
if (this.form.valid) {
var controls = this.form.controls;
var albumId = this.originalAlbum.AlbumId;
(<any>window).fetch(`/api/albums/${ albumId }/update`, {
method: 'put',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
AlbumArtUrl: controls['albumArtUrl'].value,
AlbumId: albumId,
ArtistId: controls['artist'].value,
GenreId: controls['genre'].value,
Price: controls['price'].value,
Title: controls['title'].value
})
}).then(response => {
console.log(response);
});
}
}
private static _validatePrice(control: ng.Control): { [key: string]: boolean } {
return /^\d+\.\d+$/.test(control.value) ? null : { price: true };
}
}

View File

@@ -0,0 +1,44 @@
<h2>Albums</h2>
<album-delete-prompt #deleteprompt></album-delete-prompt>
<table class="table">
<thead>
<tr>
<th><a>Genre</a></th>
<th><a>Artist</a></th>
<th><a (click)="sortBy('Title')">Title</a></th>
<th><a (click)="sortBy('Price')">Price</a></th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ng-for="#row of rows">
<td>{{ row.Genre.Name }}</td>
<td>{{ row.Artist.Name }}</td>
<td>{{ row.Title }}</td>
<td>{{ row.Price | currency:'USD':true }}</td>
<td>
<div class="btn-group btn-group-xs">
<a class="btn btn-default" [router-link]="['/Admin/AlbumDetails', { albumId: row.AlbumId }]">Details</a>
<a class="btn btn-default" [router-link]="['/Admin/AlbumEdit', { albumId: row.AlbumId }]">Edit</a>
<a class="btn btn-default" (click)="deleteprompt.show(row)">Delete</a>
</div>
</td>
</tr>
</tbody>
</table>
<div class="btn-group">
<button class="btn btn-default" [disabled]="!canGoBack" (click)="goToPage(1)">First</button>
<button class="btn btn-default" [disabled]="!canGoBack" (click)="goToPage(pageIndex - 1)">Previous</button>
<button class="btn" *ng-for="#page of pageLinks"
[ng-class]="{ 'btn-info': page.isCurrent, 'btn-default': !page.isCurrent }"
(click)="goToPage(page.index)">
{{ page.text }}
</button>
<button class="btn btn-default" [disabled]="!canGoForward" (click)="goToPage(pageIndex + 1)">Next</button>
<button class="btn btn-default" [disabled]="!canGoForward" (click)="goToLast()">Last</button>
</div>
<p>{{ totalCount }} total albums</p>

View File

@@ -0,0 +1,70 @@
import * as ng from 'angular2/angular2';
import * as router from 'angular2/router';
import { Http, HTTP_BINDINGS } from 'angular2/http';
import * as models from '../../../models/models';
import { AlbumDeletePrompt } from '../album-delete-prompt/album-delete-prompt';
@ng.Component({
selector: 'albums-list'
})
@ng.View({
templateUrl: './ng-app/components/admin/albums-list/albums-list.html',
directives: [ng.NgFor, ng.NgClass, router.ROUTER_DIRECTIVES, AlbumDeletePrompt]
})
export class AlbumsList {
public rows: models.Album[];
public canGoBack: boolean;
public canGoForward: boolean;
public pageLinks: any[];
public totalCount: number;
public get pageIndex() {
return this._pageIndex;
}
private _http: Http;
private _pageIndex = 1;
private _sortBy = "Title";
private _sortByDesc = false;
constructor(http: Http) {
this._http = http;
this.refreshData();
}
public sortBy(col: string) {
this._sortByDesc = col === this._sortBy ? !this._sortByDesc : false;
this._sortBy = col;
this.refreshData();
}
public goToPage(pageIndex: number) {
this._pageIndex = pageIndex;
this.refreshData();
}
public goToLast() {
this.goToPage(this.pageLinks[this.pageLinks.length - 1].index);
}
refreshData() {
var sortBy = this._sortBy + (this._sortByDesc ? ' DESC' : '');
this._http.get(`/api/albums?page=${ this._pageIndex }&pageSize=50&sortBy=${ sortBy }`).subscribe(result => {
var json = result.json();
this.rows = json.Data;
var numPages = Math.ceil(json.TotalCount / json.PageSize);
this.pageLinks = [];
for (var i = 1; i <= numPages; i++) {
this.pageLinks.push({
index: i,
text: i.toString(),
isCurrent: i === json.Page
});
}
this.canGoBack = this.pageLinks.length && !this.pageLinks[0].isCurrent;
this.canGoForward = this.pageLinks.length && !this.pageLinks[this.pageLinks.length - 1].isCurrent;
this.totalCount = json.TotalCount;
});
}
}

View File

@@ -0,0 +1,9 @@
<div class="form-group" [class.has-error]="errorMessages.length">
<label class="col-md-2 control-label">{{ label }}</label>
<div class="col-md-5">
<ng-content></ng-content>
<div class="alert alert-danger" role="alert" *ng-if="errorMessages.length">
<p *ng-for="#message of errorMessages">{{ message }}</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
import * as ng from 'angular2/angular2';
@ng.Component({
selector: 'form-field',
properties: ['label', 'validate']
})
@ng.View({
templateUrl: './ng-app/components/admin/form-field/form-field.html',
directives: [ng.NgIf, ng.NgFor]
})
export class FormField {
private validate: ng.AbstractControl;
public get errorMessages() {
var errors = (this.validate && this.validate.touched && this.validate.errors) || {};
return Object.keys(errors).map(key => {
return 'Error: ' + key;
});
}
}

View File

@@ -0,0 +1,30 @@
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" [router-link]="['/Home']">Music Store</a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a [router-link]="['/Home']">Home</a></li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown">Store <b class="caret"></b></a>
<ul class="dropdown-menu">
<li *ng-for="#genre of genres">
<a title="{{ genre.Description }}" [router-link]="['/Genre', { genreId: genre.GenreId }]">
{{ genre.Name }}
</a>
</li>
<li class="divider"></li>
<li>
<a [router-link]="['/GenresList']">More…</a>
</li>
</ul>
</li>
<li><a [router-link]="['/Admin/Albums']">Admin</a></li>
</ul>
</div>
</div>
</nav>
<div class="container body-content">
<router-outlet></router-outlet>
</div>

View File

@@ -0,0 +1,35 @@
import * as ng from 'angular2/angular2';
import * as router from 'angular2/router';
import { Http, HTTP_BINDINGS } from 'angular2/http';
import { Home } from '../public/home/home';
import { AlbumDetails } from '../public/album-details/album-details';
import { GenreContents } from '../public/genre-contents/genre-contents';
import { GenresList } from '../public/genres-list/genres-list';
import { AdminHome } from '../admin/admin-home/admin-home';
import * as models from '../../models/models';
@ng.Component({
selector: 'app'
})
@router.RouteConfig([
{ path: '/', component: Home, as: 'Home' },
{ path: '/album/:albumId', component: AlbumDetails, as: 'Album' },
{ path: '/genre/:genreId', component: GenreContents, as: 'Genre' },
{ path: '/genres', component: GenresList, as: 'GenresList' },
{ path: '/admin/...', component: AdminHome, as: 'Admin' }
])
@ng.View({
templateUrl: './ng-app/components/app/app.html',
styleUrls: ['./ng-app/components/app/app.css'],
directives: [router.ROUTER_DIRECTIVES, ng.NgFor]
})
export class App {
public genres: models.Genre[];
constructor(http: Http) {
http.get('/api/genres/menu').subscribe(result => {
this.genres = result.json();
});
}
}

View File

@@ -0,0 +1,6 @@
import * as ng from 'angular2/angular2';
import * as router from 'angular2/router';
import { Http, HTTP_BINDINGS } from 'angular2/http';
import { App } from './app';
ng.bootstrap(App, [router.ROUTER_BINDINGS, HTTP_BINDINGS, ng.FormBuilder]);

View File

@@ -0,0 +1,26 @@
<div *ng-if="albumData">
<h2>{{ albumData.Title }}</h2>
<p>
<img alt="{{ albumData.Title }}" src="{{ albumData.AlbumArtUrl }}">
</p>
<div id="album-details">
<p>
<em>Genre:</em>
{{ albumData.Genre.Name }}
</p>
<p>
<em>Artist:</em>
{{ albumData.Artist.Name }}
</p>
<p>
<em>Price:</em>
{{ albumData.Price | currency:'USD':true }}
</p>
<p class="button">
<!-- TODO: Shopping cart functionality -->
Add to cart
</p>
</div>
</div>

View File

@@ -0,0 +1,21 @@
import * as ng from 'angular2/angular2';
import * as router from 'angular2/router';
import { Http } from 'angular2/http';
import * as models from '../../../models/models';
@ng.Component({
selector: 'album-details'
})
@ng.View({
templateUrl: './ng-app/components/public/album-details/album-details.html',
directives: [ng.NgIf]
})
export class AlbumDetails {
public albumData: models.Album;
constructor(http: Http, routeParam: router.RouteParams) {
http.get('/api/albums/' + routeParam.params['albumId']).subscribe(result => {
this.albumData = result.json();
});
}
}

View File

@@ -0,0 +1,4 @@
<a [router-link]="['/Album', { albumId: albumData.AlbumId }]">
<img alt="{{ albumData.Title }}" src="{{ albumData.AlbumArtUrl }}">
<h4>{{ albumData.Title }}</h4>
</a>

View File

@@ -0,0 +1,14 @@
import * as ng from 'angular2/angular2';
import * as router from 'angular2/router';
import * as models from '../../../models/models';
@ng.Component({
selector: 'album-tile',
properties: ['albumData: albumdata']
})
@ng.View({
templateUrl: './ng-app/components/public/album-tile/album-tile.html',
directives: [router.ROUTER_DIRECTIVES]
})
export class AlbumTile {
}

View File

@@ -0,0 +1,7 @@
<h3>Albums</h3>
<ul class="list-unstyled">
<li *ng-for="#album of albums" class="col-lg-2 col-md-2 col-sm-2 col-xs-4 container">
<album-tile [albumData]="album"></album-tile>
</li>
</ul>

View File

@@ -0,0 +1,22 @@
import * as ng from 'angular2/angular2';
import * as router from 'angular2/router';
import { Http } from 'angular2/http';
import * as models from '../../../models/models';
import { AlbumTile } from '../album-tile/album-tile';
@ng.Component({
selector: 'genre-contents'
})
@ng.View({
templateUrl: './ng-app/components/public/genre-contents/genre-contents.html',
directives: [ng.NgFor, AlbumTile]
})
export class GenreContents {
public albums: models.Album[];
constructor(http: Http, routeParam: router.RouteParams) {
http.get(`/api/genres/${ routeParam.params['genreId'] }/albums`).subscribe(result => {
this.albums = result.json();
});
}
}

View File

@@ -0,0 +1,13 @@
<h3>Browse Genres</h3>
<p *ng-if="genres">
Select from {{ genres.length }} genres:
</p>
<ul class="list-group">
<li *ng-for="#genre of genres" class="list-group-item">
<a title="{{genre.Description}}" [router-link]="['/Genre', { genreId: genre.GenreId }]">
{{ genre.Name }}
</a>
</li>
</ul>

View File

@@ -0,0 +1,21 @@
import * as ng from 'angular2/angular2';
import * as router from 'angular2/router';
import { Http } from 'angular2/http';
import * as models from '../../../models/models';
@ng.Component({
selector: 'genres-list'
})
@ng.View({
templateUrl: './ng-app/components/public/genres-list/genres-list.html',
directives: [router.ROUTER_DIRECTIVES, ng.NgIf, ng.NgFor]
})
export class GenresList {
public genres: models.Genre[];
constructor(http: Http) {
http.get('/api/genres').subscribe(result => {
this.genres = result.json();
});
}
}

View File

@@ -0,0 +1,10 @@
<div class="jumbotron">
<h1>MVC Music Store</h1>
<img src="/Images/home-showcase.png">
</div>
<ul class="row list-unstyled" id="album-list">
<li *ng-for="#album of mostPopular" class="col-lg-2 col-md-2 col-sm-2 col-xs-4 container">
<album-tile [albumData]="album"></album-tile>
</li>
</ul>

View File

@@ -0,0 +1,21 @@
import * as ng from 'angular2/angular2';
import { Http } from 'angular2/http';
import { AlbumTile } from '../album-tile/album-tile';
import * as models from '../../../models/models';
@ng.Component({
selector: 'home'
})
@ng.View({
templateUrl: './ng-app/components/public/home/home.html',
directives: [ng.NgFor, AlbumTile]
})
export class Home {
public mostPopular: models.Album[];
constructor(http: Http) {
http.get('/api/albums/mostPopular').subscribe(result => {
this.mostPopular = result.json();
});
}
}

View File

@@ -0,0 +1,16 @@
export interface Album {
AlbumId: number;
Title: string;
AlbumArtUrl: string;
}
export interface Genre {
GenreId: number;
Name: string;
Description: string;
}
export interface Artist {
ArtistId: number;
Name: string;
}

View File

@@ -0,0 +1,3 @@
System.config({
defaultJSExtensions: true
});

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/>
</handlers>
<httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" stdoutLogEnabled="false"/>
</system.webServer>
</configuration>