Compare commits
No commits in common. "6b1a75bfcc27eb6e749da5442bd9d7f3dbad04a2" and "5283985026d73a6cbf111f62592817930b3d4c2b" have entirely different histories.
6b1a75bfcc
...
5283985026
@ -31,33 +31,6 @@ public class ContactController : Controller
|
||||
return RedirectToPage("/Contact/Index");
|
||||
}
|
||||
|
||||
[HttpPost("privacy-policy")]
|
||||
public async Task<IActionResult> HandlePrivacyPolicy()
|
||||
{
|
||||
if (!Request.HasFormContentType)
|
||||
{
|
||||
return RedirectToPage("/Contact/Privacy");
|
||||
}
|
||||
|
||||
IFormCollection form = Request.Form;
|
||||
StringValues name = form["name"];
|
||||
StringValues email = form["email"];
|
||||
StringValues subject = form["subject"];
|
||||
StringValues message = form["message"];
|
||||
StringValues privacyPolicy = form["privacy-policy"];
|
||||
|
||||
await using SmtpSender sender = CreateSender();
|
||||
await sender.WriteEmail
|
||||
.To("Oliver Booth", _destination.GetValue<string>("PrivacyPolicy"))
|
||||
.From(name, email)
|
||||
.Subject($"[{privacyPolicy}] {subject}")
|
||||
.BodyHtml(message)
|
||||
.SendAsync();
|
||||
|
||||
TempData["Success"] = true;
|
||||
return RedirectToPage("/Contact/Result");
|
||||
}
|
||||
|
||||
[HttpPost("other")]
|
||||
public async Task<IActionResult> HandleMiscellaneous()
|
||||
{
|
||||
|
@ -1,28 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace OliverBooth.Data.Web.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the configuration for the <see cref="Project" /> entity.
|
||||
/// </summary>
|
||||
internal sealed class ProjectConfiguration : IEntityTypeConfiguration<Project>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Configure(EntityTypeBuilder<Project> builder)
|
||||
{
|
||||
builder.ToTable("Project");
|
||||
builder.HasKey(e => e.Id);
|
||||
|
||||
builder.Property(e => e.Id).IsRequired();
|
||||
builder.Property(e => e.Rank).IsRequired();
|
||||
builder.Property(e => e.Slug).IsRequired();
|
||||
builder.Property(e => e.Name).IsRequired();
|
||||
builder.Property(e => e.HeroUrl).IsRequired();
|
||||
builder.Property(e => e.Description).IsRequired();
|
||||
builder.Property(e => e.Status).HasConversion<EnumToStringConverter<ProjectStatus>>().IsRequired();
|
||||
builder.Property(e => e.RemoteUrl);
|
||||
builder.Property(e => e.RemoteTarget);
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
namespace OliverBooth.Data.Web;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a project.
|
||||
/// </summary>
|
||||
public interface IProject
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the description of the project.
|
||||
/// </summary>
|
||||
/// <value>The description of the project.</value>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL of the hero image.
|
||||
/// </summary>
|
||||
/// <value>The URL of the hero image.</value>
|
||||
string HeroUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the project.
|
||||
/// </summary>
|
||||
/// <value>The ID of the project.</value>
|
||||
Guid Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the project.
|
||||
/// </summary>
|
||||
/// <value>The name of the project.</value>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rank of the project.
|
||||
/// </summary>
|
||||
/// <value>The rank of the project.</value>
|
||||
int Rank { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the host of the project.
|
||||
/// </summary>
|
||||
/// <value>The host of the project.</value>
|
||||
string? RemoteTarget { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL of the project.
|
||||
/// </summary>
|
||||
/// <value>The URL of the project.</value>
|
||||
string? RemoteUrl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the slug of the project.
|
||||
/// </summary>
|
||||
/// <value>The slug of the project.</value>
|
||||
string Slug { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of the project.
|
||||
/// </summary>
|
||||
/// <value>The status of the project.</value>
|
||||
ProjectStatus Status { get; }
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
namespace OliverBooth.Data.Web;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a project.
|
||||
/// </summary>
|
||||
internal sealed class Project : IEquatable<Project>, IProject
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Description { get; private set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string HeroUrl { get; private set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid Id { get; private set; } = Guid.NewGuid();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name { get; private set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Rank { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? RemoteTarget { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? RemoteUrl { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Slug { get; private set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ProjectStatus Status { get; private set; } = ProjectStatus.Ongoing;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether two instances of <see cref="Project" /> are equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance of <see cref="Project" /> to compare.</param>
|
||||
/// <param name="right">The second instance of <see cref="Project" /> to compare.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are equal; otherwise,
|
||||
/// <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool operator ==(Project? left, Project? right) => Equals(left, right);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether two instances of <see cref="Project" /> are not equal.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance of <see cref="Project" /> to compare.</param>
|
||||
/// <param name="right">The second instance of <see cref="Project" /> to compare.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="left" /> and <paramref name="right" /> are not equal; otherwise,
|
||||
/// <see langword="false" />.
|
||||
/// </returns>
|
||||
public static bool operator !=(Project? left, Project? right) => !(left == right);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether this instance of <see cref="Project" /> is equal to another
|
||||
/// instance.
|
||||
/// </summary>
|
||||
/// <param name="other">An instance to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="other" /> is equal to this instance; otherwise,
|
||||
/// <see langword="false" />.
|
||||
/// </returns>
|
||||
public bool Equals(Project? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Id.Equals(other.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether this instance is equal to a specified object.
|
||||
/// </summary>
|
||||
/// <param name="obj">An object to compare with this instance.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="obj" /> is an instance of <see cref="Project" /> and equals the
|
||||
/// value of this instance; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return ReferenceEquals(this, obj) || obj is Project other && Equals(other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>The hash code.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
||||
return Id.GetHashCode();
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace OliverBooth.Data.Web;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the status of a project.
|
||||
/// </summary>
|
||||
public enum ProjectStatus
|
||||
{
|
||||
[Description("The project is currently being worked on.")]
|
||||
Ongoing,
|
||||
|
||||
[Description("The project is on an indefinite hiatus.")]
|
||||
Hiatus,
|
||||
|
||||
[Description("The project is no longer being worked on.")]
|
||||
Past
|
||||
}
|
@ -19,12 +19,6 @@ internal sealed class WebContext : DbContext
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of projects in the database.
|
||||
/// </summary>
|
||||
/// <value>The collection of projects.</value>
|
||||
public DbSet<Project> Projects { get; private set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the set of site configuration items.
|
||||
/// </summary>
|
||||
@ -47,7 +41,6 @@ internal sealed class WebContext : DbContext
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.ApplyConfiguration(new ProjectConfiguration());
|
||||
modelBuilder.ApplyConfiguration(new TemplateConfiguration());
|
||||
modelBuilder.ApplyConfiguration(new SiteConfigurationConfiguration());
|
||||
}
|
||||
|
@ -18,21 +18,21 @@
|
||||
Please outline your concerns in the form below, and I will get back to you as soon as possible.
|
||||
</p>
|
||||
|
||||
<form method="post" asp-controller="Contact" asp-action="HandlePrivacyPolicy">
|
||||
<form method="post">
|
||||
<input type="hidden" name="contact-type" value="privacy-policy">
|
||||
|
||||
<div class="form-group" style="margin-top: 10px;">
|
||||
<label for="name">Your Name</label>
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="Who are you?">
|
||||
<label for="your-name">Your Name</label>
|
||||
<input type="text" class="form-control" id="your-name" name="your-name" placeholder="Who are you?">
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 10px;">
|
||||
<label for="email">Your Email Address</label>
|
||||
<input type="email" class="form-control" id="email" name="email" placeholder="How can I reach you?">
|
||||
<label for="your-email">Your Email Address</label>
|
||||
<input type="email" class="form-control" id="your-email" name="your-email" placeholder="How can I reach you?">
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 10px;">
|
||||
<label for="privacy-policy">Privacy Policy</label>
|
||||
<label for="your-email">Privacy Policy</label>
|
||||
<select name="privacy-policy" class="form-control" id="privacy-policy">
|
||||
<option value="website" selected="@(Model.Which != "google-play")">This website's policy</option>
|
||||
<option value="google-play" selected="@(Model.Which == "google-play")">Google Play policy</option>
|
||||
@ -40,13 +40,13 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 10px;">
|
||||
<label for="subject">Subject</label>
|
||||
<input type="text" class="form-control" id="subject" name="subject" placeholder="Describe your concerns here in a few words" maxlength="100">
|
||||
<label for="position-title">Subject Matter</label>
|
||||
<input type="text" class="form-control" id="position-title" name="position-title" placeholder="Describe your concerns here in a few words" maxlength="100">
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-top: 10px;">
|
||||
<label for="message">Description</label>
|
||||
<textarea class="form-control" id="message" name="message" rows="5" required placeholder="Go into detail about the nature of your concerns."></textarea>
|
||||
<label for="position-description">Description</label>
|
||||
<textarea class="form-control" id="position-description" name="position-description" rows="5" required placeholder="Go into detail about the nature of your concerns."></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" style="margin-top: 10px;">Submit</button>
|
||||
|
@ -1,105 +1,190 @@
|
||||
@page
|
||||
@using OliverBooth.Data.Web
|
||||
@using OliverBooth.Services
|
||||
@inject IProjectService ProjectService
|
||||
@{
|
||||
ViewData["Title"] = "Projects";
|
||||
}
|
||||
|
||||
<h1 class="display-4">Projects</h1>
|
||||
|
||||
@foreach (IProject[] chunk in ProjectService.GetProjects(ProjectStatus.Ongoing).OrderBy(p => p.Rank).Chunk(2))
|
||||
{
|
||||
<div class="card-group row" style="margin-top: 20px;">
|
||||
@foreach (IProject project in chunk)
|
||||
{
|
||||
<div class="card-group row">
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-success project-card">
|
||||
<div class="card-header text-bg-success">In Active Development</div>
|
||||
<img src="~/img/projects/hero/@project.HeroUrl" class="card-img-top" alt="@project.Name">
|
||||
<img src="~/img/projects/hero/x10d-1280x640.png" class="card-img-top" alt="X10D">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">@project.Name</h5>
|
||||
<p class="card-text">@Html.Raw(ProjectService.GetDescription(project))</p>
|
||||
@if (!string.IsNullOrWhiteSpace(project.RemoteUrl))
|
||||
{
|
||||
<a href="@project.RemoteUrl" class="btn btn-primary">
|
||||
@if (string.IsNullOrWhiteSpace(project.RemoteTarget))
|
||||
{
|
||||
<span>View website</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>View on @project.RemoteTarget</span>
|
||||
}
|
||||
</a>
|
||||
}
|
||||
<h5 class="card-title">X10D</h5>
|
||||
<p class="card-text">A <a href="https://nuget.org/packages/x10d">NuGet</a> offering dozens of extension methods for countless .NET types.</p>
|
||||
<a href="https://github.com/oliverbooth/X10D" class="btn btn-primary">View on GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-success project-card">
|
||||
<div class="card-header text-bg-success">In Active Development</div>
|
||||
<img src="~/img/projects/hero/brackeysbot-1280x640.png" class="card-img-top" alt="BrackeysBot">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">BrackeysBot</h5>
|
||||
<p class="card-text">A collection of self-contained Discord bots that power the <a href="https://discord.gg/brackeys">Brackeys Community</a> Discord server.</p>
|
||||
<a href="https://github.com/BrackeysBot" class="btn btn-primary">View on GitHub</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-group row" style="margin-top: 20px;">
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-success project-card">
|
||||
<div class="card-header text-bg-success">In Active Development</div>
|
||||
<img src="~/img/projects/hero/none-1280x640.png" class="card-img-top" alt="Project KW">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Project KW</h5>
|
||||
<p class="card-text">A spiritual successor to the PlayStation cult classic Kula World (aka Roll Away).</p>
|
||||
<p>No further information is available for this project at this time.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@foreach (IProject[] chunk in ProjectService.GetProjects(ProjectStatus.Past).Chunk(2))
|
||||
{
|
||||
<div class="card-group row" style="margin-top: 20px;">
|
||||
@foreach (IProject project in chunk)
|
||||
{
|
||||
<div class="card-group row" style="margin-top: 20px;">
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-info project-card">
|
||||
<div class="card-header text-bg-info">Past Work</div>
|
||||
<img src="~/img/projects/hero/@project.HeroUrl" class="card-img-top" alt="@project.Name">
|
||||
<img src="~/img/projects/hero/fiveoclock-1280x640.png" class="card-img-top" alt="It's 5 O'Clock Somewhere">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">@project.Name</h5>
|
||||
<p class="card-text">@Html.Raw(ProjectService.GetDescription(project))</p>
|
||||
@if (!string.IsNullOrWhiteSpace(project.RemoteUrl))
|
||||
{
|
||||
<a href="@project.RemoteUrl" class="btn btn-primary">
|
||||
@if (string.IsNullOrWhiteSpace(project.RemoteTarget))
|
||||
{
|
||||
<span>View website</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>View on @project.RemoteTarget</span>
|
||||
}
|
||||
</a>
|
||||
}
|
||||
<h5 class="card-title">It's 5 O'Clock Somewhere</h5>
|
||||
<p class="card-text">An alcoholic's best friend: an Android app that tells you where in the world it's socially acceptable to drink.</p>
|
||||
<a href="https://play.google.com/store/apps/details?id=me.olivr.FiveOClockSomewhere" class="btn btn-primary">View on Play Store</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-info project-card">
|
||||
<div class="card-header text-bg-info">Past Work</div>
|
||||
<img src="~/img/projects/hero/birthday-1280x640.png" class="card-img-top" alt="Is It My Birthday?">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Is It My Birthday?</h5>
|
||||
<p class="card-text">An Android app to tell you whether or not today is your birthday.</p>
|
||||
<a href="https://play.google.com/store/apps/details?id=me.olivr.isitmybirthday" class="btn btn-primary">View on Play Store</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-group row" style="margin-top: 20px;">
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-info project-card">
|
||||
<div class="card-header text-bg-info">Past Work</div>
|
||||
<img src="~/img/projects/hero/candyjam-1280x640.png" class="card-img-top" alt="Scrolling Candy Apple Saga">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Scrolling Candy Apple Saga</h5>
|
||||
<p class="card-text">My submission to the 2013 <a href="https://itch.io/jam/candyjam">Candy Jam</a>. Made with Unity.</p>
|
||||
<a href="https://oliverbooth.itch.io/scrolling-candy-apple-saga" class="btn btn-primary">View on itch.io</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-light project-card">
|
||||
<div class="card-header text-bg-light">Retired</div>
|
||||
<img src="~/img/projects/hero/sampdotnet-1280x640.png" class="card-img-top" alt="SAMP.NET">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">SAMP.NET</h5>
|
||||
<p class="card-text">A .NET wrapper for the Pawn SAMP API, made during my time in college.</p>
|
||||
<a href="https://github.com/oliverbooth/SAMP.NET" class="btn btn-primary">View on GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-group row" style="margin-top: 20px;">
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-light project-card">
|
||||
<div class="card-header text-bg-light">Retired</div>
|
||||
<img src="~/img/projects/hero/none-1280x640.png" class="card-img-top" alt="SAMP.NET">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">HaloMCCForgePatcher</h5>
|
||||
<p class="card-text">A tool to enable the Forge menu item in Halo: Master Chief Collection before it was officially released.</p>
|
||||
<a href="https://github.com/oliverbooth/HaloMCCForgePatcher" class="btn btn-primary">View on GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-light project-card">
|
||||
<div class="card-header text-bg-light">Retired</div>
|
||||
<img src="~/img/projects/hero/unitydocs-1280x640.png" class="card-img-top" alt="Unity API Docs">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Unity API Docs</h5>
|
||||
<p class="card-text">A complete rewrite and redesign of the Unity API docs, inspired by the C# and .NET documentation on Microsoft Learn.</p>
|
||||
<p>This project was cancelled following Unity's pricing change announcement.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@foreach (IProject[] chunk in ProjectService.GetProjects(ProjectStatus.Hiatus).Chunk(2))
|
||||
{
|
||||
<div class="card-group row" style="margin-top: 20px;">
|
||||
@foreach (IProject project in chunk)
|
||||
{
|
||||
<div class="card-group row" style="margin-top: 20px">
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-dark project-card">
|
||||
<div class="card-header text-bg-dark">On Hiatus</div>
|
||||
<img src="~/img/projects/hero/@project.HeroUrl" class="card-img-top" alt="@project.Name">
|
||||
<img src="~/img/projects/hero/olive-1280x640.png" class="card-img-top" alt="Olive">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">@project.Name</h5>
|
||||
<p class="card-text">@Html.Raw(ProjectService.GetDescription(project))</p>
|
||||
@if (!string.IsNullOrWhiteSpace(project.RemoteUrl))
|
||||
{
|
||||
<a href="@project.RemoteUrl" class="btn btn-primary">
|
||||
@if (string.IsNullOrWhiteSpace(project.RemoteTarget))
|
||||
{
|
||||
<span>View website</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>View on @project.RemoteTarget</span>
|
||||
}
|
||||
</a>
|
||||
}
|
||||
<h5 class="card-title">Olive</h5>
|
||||
<p class="card-text">A game engine written in C#, powered by MonoGame.</p>
|
||||
<a href="https://github.com/olive-engine/olive" class="btn btn-primary">View on GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-dark project-card">
|
||||
<div class="card-header text-bg-dark">On Hiatus</div>
|
||||
<img src="~/img/projects/hero/none-1280x640.png" class="card-img-top" alt="TCP.NET">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">TCP.NET</h5>
|
||||
<p class="card-text">A TCP library for .NET with support for RSA/AES encryption.</p>
|
||||
<a href="https://github.com/oliverbooth/TcpDotNet" class="btn btn-primary">View on GitHub</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-group row" style="margin-top: 20px;">
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-dark project-card">
|
||||
<div class="card-header text-bg-dark">On Hiatus</div>
|
||||
<img src="~/img/projects/hero/none-1280x640.png" class="card-img-top" alt="MelonSharp">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">MelonSharp</h5>
|
||||
<p class="card-text">A vanilla Minecraft server written in C#.</p>
|
||||
<p>No further information is available for this project at this time.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-dark project-card">
|
||||
<div class="card-header text-bg-dark">On Hiatus</div>
|
||||
<img src="~/img/projects/hero/syntaxgen-1280x640.png" class="card-img-top" alt="SyntaxGen.NET">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">SyntaxGen.NET</h5>
|
||||
<p class="card-text">A NuGet to be used for documentation tools that can generate the declaration syntax for .NET types and their members.</p>
|
||||
<a href="https://github.com/oliverbooth/dotnet-syntaxgen" class="btn btn-primary">View on GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-group row" style="margin-top: 20px;">
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-dark project-card">
|
||||
<div class="card-header text-bg-dark">On Hiatus</div>
|
||||
<img src="~/img/projects/hero/mutation-1280x640.png" class="card-img-top" alt="Mutation">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Mutation</h5>
|
||||
<p class="card-text">A restoration of the classic AW RPG world, modernised for <a href="https://www.virtualparadise.org/">Virtual Paradise</a>.</p>
|
||||
<a href="https://mutation3d.net" class="btn btn-primary">View website</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-1 col-md-6 col-lg-6 d-flex align-items-stretch">
|
||||
<div class="card border-dark project-card">
|
||||
<div class="card-header text-bg-dark">On Hiatus</div>
|
||||
<img src="~/img/projects/hero/none-1280x640.png" class="card-img-top" alt="Olive">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Astraeos</h5>
|
||||
<p class="card-text">An open-universe sandbox game.</p>
|
||||
<p>No further information is available for this project at this time.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -34,7 +34,6 @@ builder.Services.AddDbContextFactory<WebContext>();
|
||||
builder.Services.AddSingleton<ITemplateService, TemplateService>();
|
||||
builder.Services.AddSingleton<IBlogPostService, BlogPostService>();
|
||||
builder.Services.AddSingleton<IBlogUserService, BlogUserService>();
|
||||
builder.Services.AddSingleton<IProjectService, ProjectService>();
|
||||
builder.Services.AddRazorPages().AddRazorRuntimeCompilation();
|
||||
builder.Services.AddControllersWithViews();
|
||||
builder.Services.AddRouting(options => options.LowercaseUrls = true);
|
||||
|
@ -1,57 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using OliverBooth.Data.Web;
|
||||
|
||||
namespace OliverBooth.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a service for interacting with projects.
|
||||
/// </summary>
|
||||
public interface IProjectService
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the description of the specified project.
|
||||
/// </summary>
|
||||
/// <param name="project">The project whose description to get.</param>
|
||||
/// <returns>The description of the specified project.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="project" /> is <see langword="null" />.</exception>
|
||||
string GetDescription(IProject project);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all projects.
|
||||
/// </summary>
|
||||
/// <returns>A read-only list of projects.</returns>
|
||||
IReadOnlyList<IProject> GetAllProjects();
|
||||
|
||||
/// <summary>
|
||||
/// Gets all projects with the specified status.
|
||||
/// </summary>
|
||||
/// <param name="status">The status of the projects to get.</param>
|
||||
/// <returns>A read-only list of projects with the specified status.</returns>
|
||||
IReadOnlyList<IProject> GetProjects(ProjectStatus status = ProjectStatus.Ongoing);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to find a project with the specified ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the project.</param>
|
||||
/// <param name="project">
|
||||
/// When this method returns, contains the project associated with the specified ID, if the project is found;
|
||||
/// otherwise, <see langword="null" />.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if a project with the specified ID is found; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
bool TryGetProject(Guid id, [NotNullWhen(true)] out IProject? project);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to find a project with the specified slug.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the project.</param>
|
||||
/// <param name="project">
|
||||
/// When this method returns, contains the project associated with the specified slug, if the project is found;
|
||||
/// otherwise, <see langword="null" />.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if a project with the specified slug is found; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
bool TryGetProject(string slug, [NotNullWhen(true)] out IProject? project);
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Markdig;
|
||||
using Markdig.Parsers;
|
||||
using Markdig.Renderers.Html;
|
||||
using Markdig.Syntax;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OliverBooth.Data.Web;
|
||||
|
||||
namespace OliverBooth.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a service for interacting with projects.
|
||||
/// </summary>
|
||||
internal sealed class ProjectService : IProjectService
|
||||
{
|
||||
private readonly IDbContextFactory<WebContext> _dbContextFactory;
|
||||
private readonly MarkdownPipeline _markdownPipeline;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ProjectService" /> class.
|
||||
/// </summary>
|
||||
/// <param name="dbContextFactory">The database context factory.</param>
|
||||
/// <param name="markdownPipeline">The Markdown pipeline.</param>
|
||||
public ProjectService(IDbContextFactory<WebContext> dbContextFactory, MarkdownPipeline markdownPipeline)
|
||||
{
|
||||
_dbContextFactory = dbContextFactory;
|
||||
_markdownPipeline = markdownPipeline;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetDescription(IProject project)
|
||||
{
|
||||
return Markdig.Markdown.ToHtml(project.Description, _markdownPipeline);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<IProject> GetAllProjects()
|
||||
{
|
||||
using WebContext context = _dbContextFactory.CreateDbContext();
|
||||
return context.Projects.OrderBy(p => p.Rank).ThenBy(p => p.Name).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<IProject> GetProjects(ProjectStatus status = ProjectStatus.Ongoing)
|
||||
{
|
||||
using WebContext context = _dbContextFactory.CreateDbContext();
|
||||
return context.Projects.Where(p => p.Status == status).OrderBy(p => p.Rank).ThenBy(p => p.Name).ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetProject(Guid id, [NotNullWhen(true)] out IProject? project)
|
||||
{
|
||||
using WebContext context = _dbContextFactory.CreateDbContext();
|
||||
project = context.Projects.Find(id);
|
||||
return project is not null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetProject(string slug, [NotNullWhen(true)] out IProject? project)
|
||||
{
|
||||
using WebContext context = _dbContextFactory.CreateDbContext();
|
||||
project = context.Projects.FirstOrDefault(p => p.Slug == slug);
|
||||
return project is not null;
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 316 KiB |
@ -174,14 +174,6 @@ article {
|
||||
}
|
||||
}
|
||||
|
||||
.project-card {
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.blog-card {
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
|
@ -79,7 +79,6 @@ class UI {
|
||||
UI.renderSpoilers(element);
|
||||
UI.renderTeX(element);
|
||||
UI.renderTimestamps(element);
|
||||
UI.updateProjectCards(element);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -225,13 +224,6 @@ class UI {
|
||||
block.innerHTML = content;
|
||||
});
|
||||
}
|
||||
|
||||
private static updateProjectCards(element?: Element) {
|
||||
element = element || document.body;
|
||||
element.querySelectorAll(".project-card .card-body p").forEach((p: HTMLParagraphElement) => {
|
||||
p.classList.add("card-text");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default UI;
|
Loading…
Reference in New Issue
Block a user