* displaying a quote looks slightly prettier, although still hideous

* implemented recent quotes page
This commit is contained in:
tmont 2011-02-14 11:01:31 +00:00
parent d917480c78
commit a5f96076dc
12 changed files with 151 additions and 27 deletions

View File

@ -64,6 +64,15 @@ namespace VideoGameQuotes.Api {
return this; return this;
} }
public virtual VoteDirection? VotedFor(User user) {
var vote = Votes.Where(v => v.Voter == user).SingleOrDefault();
if (vote == null) {
return null;
}
return vote.Direction;
}
public virtual int UpVotes { get { return Votes.Count(vote => vote.Direction == VoteDirection.Up); } } public virtual int UpVotes { get { return Votes.Count(vote => vote.Direction == VoteDirection.Up); } }
public virtual int DownVotes { get { return Votes.Count(vote => vote.Direction == VoteDirection.Down); } } public virtual int DownVotes { get { return Votes.Count(vote => vote.Direction == VoteDirection.Down); } }
public virtual int NetVotes { get { return Votes.Sum(vote => (int)vote); } } public virtual int NetVotes { get { return Votes.Sum(vote => (int)vote); } }

View File

@ -6,7 +6,6 @@ using System.Web.Mvc;
using JetBrains.Annotations; using JetBrains.Annotations;
using Portoa.Persistence; using Portoa.Persistence;
using Portoa.Web.Controllers; using Portoa.Web.Controllers;
using Portoa.Web.ErrorHandling;
using Portoa.Web.Results; using Portoa.Web.Results;
using VideoGameQuotes.Api; using VideoGameQuotes.Api;
using VideoGameQuotes.Web.Models; using VideoGameQuotes.Web.Models;
@ -23,6 +22,12 @@ namespace VideoGameQuotes.Web.Controllers {
this.currentUserProvider = currentUserProvider; this.currentUserProvider = currentUserProvider;
} }
public ActionResult Recent() {
//get last 10 submitted quotes
var quotes = quoteService.GetMostRecentQuotes(10);
return View(new QuoteCollectionModel { Quotes = quotes, User = currentUserProvider.CurrentUser });
}
[IsValidUser] [IsValidUser]
public ActionResult Submit() { public ActionResult Submit() {
var model = new QuoteSubmitModel(); var model = new QuoteSubmitModel();
@ -140,7 +145,12 @@ namespace VideoGameQuotes.Web.Controllers {
public ActionResult Quote(int id) { public ActionResult Quote(int id) {
try { try {
return View(quoteService.GetQuote(id)); var model = new QuoteModel {
Quote = quoteService.GetQuote(id),
User = currentUserProvider.CurrentUser
};
return View(model);
} catch (EntityNotFoundException) { } catch (EntityNotFoundException) {
return new StatusOverrideResult(View("QuoteNotFound")) { StatusCode = HttpStatusCode.NotFound }; return new StatusOverrideResult(View("QuoteNotFound")) { StatusCode = HttpStatusCode.NotFound };
} }
@ -156,6 +166,5 @@ namespace VideoGameQuotes.Web.Controllers {
return Json(this.CreateJsonErrorResponse(e)); return Json(this.CreateJsonErrorResponse(e));
} }
} }
} }
} }

View File

@ -41,6 +41,8 @@ namespace VideoGameQuotes.Web {
routes.MapRoute("contact", "contact", new { controller = "Home", action = "Contact" }); routes.MapRoute("contact", "contact", new { controller = "Home", action = "Contact" });
routes.MapRoute("submit", "submit", new { controller = "Quote", action = "Submit" }); routes.MapRoute("submit", "submit", new { controller = "Quote", action = "Submit" });
routes.MapRoute("search", "search", new { controller = "Quote", action = "Search" }); routes.MapRoute("search", "search", new { controller = "Quote", action = "Search" });
routes.MapRoute("recent", "recent", new { controller = "Quote", action = "Recent" });
routes.MapRoute("random", "random", new { controller = "Quote", action = "Random" });
routes.MapRoute("create-category", "category/create", new { controller = "Quote", action = "CreateCategory" }); routes.MapRoute("create-category", "category/create", new { controller = "Quote", action = "CreateCategory" });
routes.MapRoute("individual-quote", "quote/{id}/{*text}", new { controller = "Quote", action = "Quote" }, new { id = @"\d+" }); routes.MapRoute("individual-quote", "quote/{id}/{*text}", new { controller = "Quote", action = "Quote" }, new { id = @"\d+" });
routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional }); routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional });

View File

@ -0,0 +1,23 @@
using System.Collections.Generic;
using VideoGameQuotes.Api;
namespace VideoGameQuotes.Web.Models {
public class QuoteCollectionModel {
public IEnumerable<Quote> Quotes { get; set; }
public User User { get; set; }
}
public class QuoteModel {
public Quote Quote { get; set; }
public User User { get; set; }
public bool VotedUp {
get { return Quote.VotedFor(User) == VoteDirection.Up; }
}
public bool VotedDown {
get { return Quote.VotedFor(User) == VoteDirection.Down; }
}
}
}

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Portoa.Persistence; using Portoa.Persistence;
using VideoGameQuotes.Api; using VideoGameQuotes.Api;
@ -16,6 +17,7 @@ namespace VideoGameQuotes.Web.Services {
GamingSystem GetSystem(int systemId); GamingSystem GetSystem(int systemId);
Category GetCategory(int categoryId); Category GetCategory(int categoryId);
Category SaveCategory(Category category); Category SaveCategory(Category category);
IEnumerable<Quote> GetMostRecentQuotes(int limit);
} }
public class QuoteService : IQuoteService { public class QuoteService : IQuoteService {
@ -92,5 +94,13 @@ namespace VideoGameQuotes.Web.Services {
public Category SaveCategory(Category category) { public Category SaveCategory(Category category) {
return categoryRepository.Save(category); return categoryRepository.Save(category);
} }
[UnitOfWork]
public IEnumerable<Quote> GetMostRecentQuotes(int limit) {
return quoteRepository
.Records
.OrderByDescending(quote => quote.Created)
.Take(limit);
}
} }
} }

View File

@ -85,6 +85,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Controllers\HomeController.cs" /> <Compile Include="Controllers\HomeController.cs" />
<Compile Include="Models\QuoteModel.cs" />
<Compile Include="Validation\NonEmptyText.cs" /> <Compile Include="Validation\NonEmptyText.cs" />
<Compile Include="Security\IsValidUserAttribute.cs" /> <Compile Include="Security\IsValidUserAttribute.cs" />
<Compile Include="Controllers\QuoteController.cs" /> <Compile Include="Controllers\QuoteController.cs" />
@ -108,6 +109,7 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="media\css\global.css" /> <Content Include="media\css\global.css" />
<Content Include="media\css\quote.css" />
<Content Include="media\css\reset.css" /> <Content Include="media\css\reset.css" />
<Content Include="media\images\favicon.png" /> <Content Include="media\images\favicon.png" />
<Content Include="media\images\search.png" /> <Content Include="media\images\search.png" />
@ -116,12 +118,14 @@
<Content Include="Views\Home\ContactSuccess.aspx" /> <Content Include="Views\Home\ContactSuccess.aspx" />
<Content Include="Views\Quote\Quote.aspx" /> <Content Include="Views\Quote\Quote.aspx" />
<Content Include="Views\Quote\QuoteNotFound.aspx" /> <Content Include="Views\Quote\QuoteNotFound.aspx" />
<Content Include="Views\Quote\Recent.aspx" />
<Content Include="Views\Quote\Submit.aspx" /> <Content Include="Views\Quote\Submit.aspx" />
<Content Include="Views\Shared\ExceptionView.ascx" /> <Content Include="Views\Shared\ExceptionView.ascx" />
<Content Include="Views\Shared\Forbidden.aspx" /> <Content Include="Views\Shared\Forbidden.aspx" />
<Content Include="Views\Shared\NotFound.aspx" /> <Content Include="Views\Shared\NotFound.aspx" />
<Content Include="Views\Shared\NotFoundContent.ascx" /> <Content Include="Views\Shared\NotFoundContent.ascx" />
<Content Include="Views\Shared\RecursiveExceptionView.ascx" /> <Content Include="Views\Shared\RecursiveExceptionView.ascx" />
<Content Include="Views\Shared\SingleQuote.ascx" />
<Content Include="Views\Shared\Unknown.aspx" /> <Content Include="Views\Shared\Unknown.aspx" />
<Content Include="Web.config" /> <Content Include="Web.config" />
<Content Include="Web.Debug.config"> <Content Include="Web.Debug.config">

View File

@ -1,24 +1,5 @@
<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<VideoGameQuotes.Api.Quote>" MasterPageFile="~/Views/Shared/Site.Master" %> <%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<VideoGameQuotes.Web.Models.QuoteModel>" MasterPageFile="~/Views/Shared/Site.Master" %>
<%@ Import Namespace="VideoGameQuotes.Api" %> <asp:Content runat="server" ID="Title" ContentPlaceHolderID="TitleContent"><%: Model.Quote.Game.Name %></asp:Content>
<asp:Content runat="server" ID="Title" ContentPlaceHolderID="TitleContent"><%: Model.Game.Name %></asp:Content>
<asp:Content runat="server" ID="Main" ContentPlaceHolderID="MainContent"> <asp:Content runat="server" ID="Main" ContentPlaceHolderID="MainContent">
<div class="quote"> <% Html.RenderPartial("SingleQuote", Model); %>
<span class="vote-for" title="I like this quote"><a href="#" id="vote-for">&uarr;</a></span> </asp:Content>
<span class="vote-against" title="I dislike this quote"><a href="#" id="vote-against">&darr;</a></span>
<span class="quote-report-link" title="report this quote as inaccurate, fake, spam, duplicate, etc."><a href="#" id="quote-report-link">report</a></span>
<p class="quote-text">
<%: Model.Text %>
</p>
</div>
<div class="quote-details">
<dl>
<dt>Game</dt>
<dd><%= Html.ActionLink(Model.Game.Name, "Index", "Quote", new { game = Model.Game.Id }, null) %></dd>
<dt>Added</dt>
<dd><%: Model.GetHumanReadableTimeSinceCreated() %></dd>
</dl>
</div>
</asp:Content>
<asp:Content runat="server" ID="DeferrableScripts" ContentPlaceHolderID="DeferrableScripts"></asp:Content>

View File

@ -0,0 +1,10 @@
<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<VideoGameQuotes.Web.Models.QuoteCollectionModel>" MasterPageFile="~/Views/Shared/Site.Master" %>
<%@ Import Namespace="VideoGameQuotes.Web.Models" %>
<asp:Content runat="server" ID="Title" ContentPlaceHolderID="TitleContent">Recently Submitted Quotes</asp:Content>
<asp:Content runat="server" ID="Main" ContentPlaceHolderID="MainContent">
<%
foreach (var quote in Model.Quotes) {
Html.RenderPartial("SingleQuote", new QuoteModel { Quote = quote, User = Model.User });
}
%>
</asp:Content>

View File

@ -0,0 +1,37 @@
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<VideoGameQuotes.Web.Models.QuoteModel>" %>
<%@ Import Namespace="VideoGameQuotes.Api" %>
<div class="quote-container">
<div class="quote-data clearfix">
<div class="quote-score-container">
<div class="vote-container">
<% if (!Model.VotedUp) { %>
<span class="vote-for" title="I like this quote">&#x25B2;</span>
<% } %>
</div>
<div class="quote-score" title="+<%= Model.Quote.UpVotes %>, -<%= Model.Quote.DownVotes %>"><%= Model.Quote.NetVotes %></div>
<div class="vote-container">
<% if (!Model.VotedDown) { %>
<span class="vote-against" title="I hate this quote">&#x25BC;</span>
<% } %>
</div>
</div>
<p class="quote-text">
<%: Model.Quote.Text %>
</p>
</div>
<div class="clearfix">
<a class="quote-report-link" href="#" title="report this quote as inaccurate, fake, spam, duplicate, etc.">report</a>
</div>
<div class="quote-details">
<dl>
<dt>Game</dt>
<dd><%= Html.ActionLink(Model.Quote.Game.Name, "Index", "Quote", new { game = Model.Quote.Game.Id }, null) %></dd>
<dt>Added</dt>
<dd><%: Model.Quote.GetHumanReadableTimeSinceCreated() %></dd>
</dl>
</div>
</div>

View File

@ -8,6 +8,7 @@
<meta http-equiv="content-type" content="text/html; charset=utf-8"/> <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" href="/media/css/reset.css" /> <link rel="stylesheet" type="text/css" href="/media/css/reset.css" />
<link rel="stylesheet" type="text/css" href="/media/css/global.css" /> <link rel="stylesheet" type="text/css" href="/media/css/global.css" />
<link rel="stylesheet" type="text/css" href="/media/css/quote.css" />
<link rel="shortcut icon" type="image/png" href="/media/images/favicon.png" /> <link rel="shortcut icon" type="image/png" href="/media/images/favicon.png" />
</head> </head>

View File

@ -42,6 +42,9 @@ ul.menu {
padding: 0; padding: 0;
list-style: none; list-style: none;
} }
ul.menu li {
float: left;
}
.validation-summary-errors { .validation-summary-errors {
color: #000000; color: #000000;
@ -117,7 +120,6 @@ ul.menu {
} }
#main-menu li { #main-menu li {
float: left;
margin-right: 2px; margin-right: 2px;
} }
#main-menu li a { #main-menu li a {
@ -140,6 +142,7 @@ ul.menu {
color: #669966; color: #669966;
font-weight: bold; font-weight: bold;
text-decoration: none; text-decoration: none;
border-bottom: 2px solid transparent;
} }
#main a:hover { #main a:hover {
border-bottom: 2px solid #000000; border-bottom: 2px solid #000000;

View File

@ -0,0 +1,35 @@
.quote-container {
width: 500px;
margin: auto;
position: relative;
}
.quote-container .quote-data .quote-score-container {
float: left;
font-family: Georgia, serif;
padding: 5px;
color: #FFFFFF;
background-color: #6699FF;
}
.quote-score {
cursor: help;
}
.quote-score, .vote-container {
text-align: center;
}
.vote-for, .vote-against {
cursor: pointer;
}
.vote-for:hover, .vote-against:hover {
color: #FFFF99;
}
.quote-container .quote-data .quote-text {
float: left;
font-weight: bold;
font-size: 20px;
}
.quote-report-link {
float: right;
}