diff --git a/todos/netcore/.gitignore b/todos/netcore/.gitignore new file mode 100644 index 0000000..09b57bc --- /dev/null +++ b/todos/netcore/.gitignore @@ -0,0 +1,236 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates +.vscode/ + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +bin/ +Bin/ +obj/ +Obj/ + +# Visual Studio 2015 cache/options directory +.vs/ +/wwwroot/dist/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +orleans.codegen.cs + +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ + +# Jetbrains tools +.idea/ diff --git a/todos/netcore/AspNet/AspNet.csproj b/todos/netcore/AspNet/AspNet.csproj new file mode 100644 index 0000000..dbb0aa6 --- /dev/null +++ b/todos/netcore/AspNet/AspNet.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.2 + InProcess + + + + + + + + + + + + diff --git a/todos/netcore/AspNet/Program.cs b/todos/netcore/AspNet/Program.cs new file mode 100644 index 0000000..351e53a --- /dev/null +++ b/todos/netcore/AspNet/Program.cs @@ -0,0 +1,20 @@ +using System; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace AspNet +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + Console.WriteLine(System.IO.Directory.GetCurrentDirectory()); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() + .UseUrls("http://127.0.0.1:3000"); + } +} diff --git a/todos/netcore/AspNet/Properties/launchSettings.json b/todos/netcore/AspNet/Properties/launchSettings.json new file mode 100644 index 0000000..de2ca5b --- /dev/null +++ b/todos/netcore/AspNet/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:61031", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "AspNet": { + "commandName": "Project", + "launchBrowser": false, + "applicationUrl": "http://localhost:3000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/todos/netcore/AspNet/Startup.cs b/todos/netcore/AspNet/Startup.cs new file mode 100644 index 0000000..8de52ee --- /dev/null +++ b/todos/netcore/AspNet/Startup.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Todos.Common; + +namespace AspNet +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(new TodoList()); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.Run(async (context) => + { + switch(context.Request.Method) + { + case "GET": + if(context.Request.Path.Value.Equals("/")) + { + await HandleIndex(context); + break; + } + + if (context.Request.Path.Value.StartsWith("/static")) + { + await HandleStatic(context); + break; + } + + Handle404(context); + break; + + case "POST": + HandlePost(context); + RedirectToHome(context); + break; + + default: + RedirectToHome(context); + break; + } + }); + } + + private void HandlePost(HttpContext context) + { + var todos = context.RequestServices.GetService(); + var formData = context.Request.Form; + var item = formData["item"]; + int.TryParse(item, out var id); + + switch (context.Request.Path) + { + case "/done": + case "/not-done": + todos.Toggle(id); + break; + + case "/delete": + todos.Remove(id); + break; + + case "/": + todos.Add(item); + break; + } + } + + private async Task HandleIndex(HttpContext context) + { + var todos = context.RequestServices.GetService(); + var index = IndexTemplate.Render(todos.Get()); + context.Response.ContentType = "text/html"; + context.Response.StatusCode = 200; + await context.Response.Body.WriteAsync(index, 0, index.Length); + } + + private async Task HandleStatic(HttpContext context) + { + var mimeTypes = new Dictionary() { { "css", "text/css" }, { "svg", "image/svg+xml" } }; + var filePath = $"../{context.Request.Path}"; + var extension = filePath.Split('.').Last(); + try + { + using (var file = File.OpenRead(filePath)) + { + context.Response.StatusCode = 200; + context.Response.ContentType = mimeTypes[extension]; + await file.CopyToAsync(context.Response.Body); + } + } + catch + { + Handle404(context); + } + } + + private void Handle404(HttpContext context) + { + context.Response.StatusCode = 404; + } + + private void RedirectToHome(HttpContext context) + { + context.Response.StatusCode = 303; + context.Response.Headers.Add("Location", "/"); + } + } +} diff --git a/todos/netcore/AspNet/appsettings.Development.json b/todos/netcore/AspNet/appsettings.Development.json new file mode 100644 index 0000000..ba0f20b --- /dev/null +++ b/todos/netcore/AspNet/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/todos/netcore/AspNet/appsettings.json b/todos/netcore/AspNet/appsettings.json new file mode 100644 index 0000000..def9159 --- /dev/null +++ b/todos/netcore/AspNet/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/todos/netcore/Common/Common.csproj b/todos/netcore/Common/Common.csproj new file mode 100644 index 0000000..c16c6d5 --- /dev/null +++ b/todos/netcore/Common/Common.csproj @@ -0,0 +1,7 @@ + + + + netcoreapp2.2 + + + diff --git a/todos/netcore/Common/IndexTemplate.cs b/todos/netcore/Common/IndexTemplate.cs new file mode 100644 index 0000000..96c2bf0 --- /dev/null +++ b/todos/netcore/Common/IndexTemplate.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using System.Text; + +namespace Todos.Common +{ + public static class IndexTemplate + { + public static byte[] Render(IEnumerable todos) + { + var builder = new StringBuilder(); + + builder.Append(MainTemplateStart); + foreach(var todo in todos) + { + builder.Append(ToDoTemplate(todo)); + } + builder.Append(MainTemplateEnd); + return Encoding.UTF8.GetBytes(builder.ToString()); + } + + private static string MainTemplateStart => + @" + + + Todo MVP + + + + +

Todo MVP

+
+
+ + +
+
+
+

Todo list

+
    "; + + private static string MainTemplateEnd => + @"
+
+ + "; + + private static string ToDoTemplate(Todo todo) + { + var toDoClass = todo.Done ? "todo done" : "todo"; + var name = todo.Done ? $"{todo.Name}" : todo.Name; + var markAction = todo.Done ? "/not-done" : "/done"; + var markClass = todo.Done ? "uncomplete" : "complete"; + var markValue = todo.Done ? $"Mark not done \"{todo.Name}\"" : $"Mark done \"{todo.Name}\""; + + return $@"
  • + + {name} + +
    + + +
    +
    + + +
    +
  • "; + } + } +} diff --git a/todos/netcore/Common/Todo.cs b/todos/netcore/Common/Todo.cs new file mode 100644 index 0000000..db0ecb9 --- /dev/null +++ b/todos/netcore/Common/Todo.cs @@ -0,0 +1,11 @@ +namespace Todos.Common +{ + public struct Todo + { + public int Id { get; set; } + + public string Name { get; set; } + + public bool Done { get; set; } + } +} diff --git a/todos/netcore/Common/TodoList.cs b/todos/netcore/Common/TodoList.cs new file mode 100644 index 0000000..4233531 --- /dev/null +++ b/todos/netcore/Common/TodoList.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Threading; + +namespace Todos.Common +{ + public class TodoList + { + private readonly List _todos; + private int _nextId; + + public TodoList() + { + _todos = new List(); + } + + public IEnumerable Get() + { + return _todos; + } + + public void Add(string name) + { + _todos.Add(new Todo() { Id = Interlocked.Increment(ref _nextId), Name = name }); + } + + public void Toggle(int id) + { + for (var i = 0; i < _todos.Count; i++) + { + var todo = _todos[i]; + if (todo.Id == id) + { + todo.Done = !todo.Done; + _todos[i] = todo; + break; + } + } + } + + public void Remove(int id) + { + var index = -1; + + for (var i = 0; i < _todos.Count; i++) + { + var todo = _todos[i]; + if (todo.Id == id) + { + index = i; + break; + } + } + + if (index >= 0) + { + _todos.RemoveAt(index); + } + } + } +} diff --git a/todos/netcore/HttpListener/HttpListener.csproj b/todos/netcore/HttpListener/HttpListener.csproj new file mode 100644 index 0000000..358a29b --- /dev/null +++ b/todos/netcore/HttpListener/HttpListener.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp2.2 + + + + + + + diff --git a/todos/netcore/HttpListener/HttpServer.cs b/todos/netcore/HttpListener/HttpServer.cs new file mode 100644 index 0000000..f4b77d2 --- /dev/null +++ b/todos/netcore/HttpListener/HttpServer.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using System.Web; +using Todos.Common; + +namespace Todos.HttpListener +{ + public class HttpServer : IDisposable + { + private readonly System.Net.HttpListener _listener; + private readonly TodoList _todos; + private readonly string _prefix; + + public HttpServer(string prefix) + { + try + { + _listener = new System.Net.HttpListener(); + _prefix = prefix; + _todos = new TodoList(); + } + catch (PlatformNotSupportedException) + { + throw; + } + } + + public async void Start() + { + _listener.Prefixes.Add(_prefix); + _listener.Start(); + + Console.WriteLine($"Started listening on {_prefix}"); + + while (_listener.IsListening) + { + try + { + var context = await _listener.GetContextAsync(); + var stopwatch = new Stopwatch(); + Console.WriteLine($"Request starting HTTP/{context.Request.ProtocolVersion} {context.Request.HttpMethod} {context.Request.Url}"); + stopwatch.Start(); + await ProcessRequest(context); + stopwatch.Stop(); + Console.WriteLine($"Request finished in {stopwatch.Elapsed.TotalMilliseconds:0.####}ms {context.Response.StatusCode} {context.Response.ContentType}"); + } + catch(Exception ex) + { + Console.WriteLine(ex.Message); + } + } + } + + public void Dispose() + { + if (_listener != null && _listener.IsListening) + { + _listener.Stop(); + } + } + + private async Task ProcessRequest(HttpListenerContext context) + { + switch (context.Request.HttpMethod) + { + case "GET": + if (context.Request.RawUrl.Equals("/")) + { + await HandleIndex(context); + break; + } + + if (context.Request.RawUrl.StartsWith("/static")) + { + await HandleStatic(context); + break; + } + + Handle404(context); + break; + + case "POST": + await HandlePost(context); + RedirectToHome(context); + break; + + default: + RedirectToHome(context); + break; + } + + context.Response.Close(); + } + + private async Task HandlePost(HttpListenerContext context) + { + using (var reader = new StreamReader(context.Request.InputStream)) + { + var formData = HttpUtility.ParseQueryString(await reader.ReadToEndAsync()); + var item = formData["item"]; + int.TryParse(item, out var id); + + switch (context.Request.RawUrl) + { + case "/done": + case "/not-done": + _todos.Toggle(id); + break; + + case "/delete": + _todos.Remove(id); + break; + + case "/": + _todos.Add(item); + break; + } + } + } + + private async Task HandleStatic(HttpListenerContext context) + { + var mimeTypes = new Dictionary() { { "css", "text/css" }, { "svg", "image/svg+xml" } }; + // When running in Visual studio current directory is set to bin of Debug or Release + // When running from dotnet command current directory is set tu folder you run command + //var filePath = $"../../../../{context.Request.RawUrl}"; + var filePath = $".{context.Request.RawUrl}"; + var extension = filePath.Split('.').Last(); + try + { + using (var file = File.OpenRead(filePath)) + { + context.Response.StatusCode = 200; + context.Response.ContentType = mimeTypes[extension]; + await file.CopyToAsync(context.Response.OutputStream); + } + } + catch + { + Handle404(context); + } + } + + private async Task HandleIndex(HttpListenerContext context) + { + var index = IndexTemplate.Render(_todos.Get()); + context.Response.ContentType = "text/html"; + context.Response.StatusCode = 200; + await context.Response.OutputStream.WriteAsync(index, 0, index.Length); + } + + private void Handle404(HttpListenerContext context) + { + context.Response.StatusCode = 404; + } + + private void RedirectToHome(HttpListenerContext context) + { + context.Response.StatusCode = 303; + context.Response.RedirectLocation = "/"; + } + } +} diff --git a/todos/netcore/HttpListener/Program.cs b/todos/netcore/HttpListener/Program.cs new file mode 100644 index 0000000..6c35a14 --- /dev/null +++ b/todos/netcore/HttpListener/Program.cs @@ -0,0 +1,18 @@ +using System; + +namespace Todos.HttpListener +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine(System.IO.Directory.GetCurrentDirectory()); + using (var server = new HttpServer("http://127.0.0.1:3000/")) + { + server.Start(); + Console.WriteLine("Press any key to stop listening"); + Console.ReadKey(); + } + } + } +} diff --git a/todos/netcore/README.md b/todos/netcore/README.md new file mode 100644 index 0000000..ddfa7ff --- /dev/null +++ b/todos/netcore/README.md @@ -0,0 +1,37 @@ +# TODO-MVP in .NET CORE (C#) + +This implementation is written in [.NET Core] using two different HTTP servers that are available in the framework + + - [ASP.NET Core] cross platform HTTP server ([Kestrel]) + - [HTTP.sys] windows only HTTP server ([HttpListener]) + +## Prerequisites + Install runtime or sdk from [.NET Core download] website + +## Run host + +From todo-mvp/todos/netcore folder run desired host + +#### HttpListener + +This will work on Windows + + dotnet run --project HttpListener\HttpListener.csproj + +#### Kestrel + +This will work on Windows, Linux, macOS + + dotnet run --project AspNet\AspNet.csproj + +## Go to website +Access url [http://localhost:3000/](http://localhost:3000/) + +Enjoy + +[.NET Core]: https://docs.microsoft.com/en-us/dotnet/core/ +[.NET Core download]: https://dotnet.microsoft.com/download +[ASP.NET Core]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/index?view=aspnetcore-2.2 +[HttpListener]: https://docs.microsoft.com/en-us/dotnet/framework/network-programming/httplistener +[Kestrel]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-2.2 +[HTTP.sys]: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/httpsys?view=aspnetcore-2.2 diff --git a/todos/netcore/Todos.sln b/todos/netcore/Todos.sln new file mode 100644 index 0000000..d26f081 --- /dev/null +++ b/todos/netcore/Todos.sln @@ -0,0 +1,42 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28922.388 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpListener", "HttpListener\HttpListener.csproj", "{1FDB58F2-13C9-4F12-9E57-70D774572ACE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet", "AspNet\AspNet.csproj", "{66FD3C77-1098-48DE-9059-EB892B1809EB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{E770D3AA-C98B-4276-93E3-BD51907001A8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{69263B31-B30A-40B5-8384-E37B484B4748}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1FDB58F2-13C9-4F12-9E57-70D774572ACE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FDB58F2-13C9-4F12-9E57-70D774572ACE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FDB58F2-13C9-4F12-9E57-70D774572ACE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FDB58F2-13C9-4F12-9E57-70D774572ACE}.Release|Any CPU.Build.0 = Release|Any CPU + {66FD3C77-1098-48DE-9059-EB892B1809EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {66FD3C77-1098-48DE-9059-EB892B1809EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {66FD3C77-1098-48DE-9059-EB892B1809EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {66FD3C77-1098-48DE-9059-EB892B1809EB}.Release|Any CPU.Build.0 = Release|Any CPU + {E770D3AA-C98B-4276-93E3-BD51907001A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E770D3AA-C98B-4276-93E3-BD51907001A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E770D3AA-C98B-4276-93E3-BD51907001A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E770D3AA-C98B-4276-93E3-BD51907001A8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {37AB4976-9BF6-4DC0-A5CB-FDAFF6404762} + EndGlobalSection +EndGlobal diff --git a/todos/netcore/static/check.svg b/todos/netcore/static/check.svg new file mode 100644 index 0000000..2df5dee --- /dev/null +++ b/todos/netcore/static/check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/todos/netcore/static/plus.svg b/todos/netcore/static/plus.svg new file mode 100644 index 0000000..23c27d8 --- /dev/null +++ b/todos/netcore/static/plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/todos/netcore/static/tick.png b/todos/netcore/static/tick.png new file mode 100644 index 0000000..71fb4ad Binary files /dev/null and b/todos/netcore/static/tick.png differ diff --git a/todos/netcore/static/todo.css b/todos/netcore/static/todo.css new file mode 100644 index 0000000..a4594b7 --- /dev/null +++ b/todos/netcore/static/todo.css @@ -0,0 +1,153 @@ +:root { + --text-color: rgb(20,20,20); + --border-color: black; + --title-color: rgb(60,60,60); + --done-color: rgb(200,200,200); + --background-color: white; + box-sizing: border-box; + padding: 0; + margin: 0; +} + +body { + min-height: 100vh; + padding: 0; + align-content: center; + margin: 0; + font-family: sans-serif; + line-height: 1.3rem; + font-size: 24px; + + background-color: var(--background-color); + color: var(--text-color); +} + +label { + font-size: 0.8rem; +} + +input { + box-sizing: border-box; + border: var(--border-color) solid 2px; + background-color: var(--background-color); +} + +section { + padding: 1.5rem; + margin-left: auto; + margin-right: auto; + max-width: 40rem; +} + +.new-todo form { + display: grid; + grid-template-columns: auto 29px; + grid-template-rows: auto; + column-gap: 1rem; +} + +.new-todo label[for="new-item"] { + grid-row: 1 / span 1; + grid-column: 1 / span 2; + margin: auto 1rem; +} + +.new-todo input#new-item { + grid-row: 1 / span 1; + grid-column: 1 / span 1; + padding: 0.5rem; + font-size: 1rem; +} + +.new-todo form input#add-new-item { + grid-row: 1 / span 1; + grid-column: 2 / span 1; + margin: auto; +} + +.button-container { + float: right; + display: flex; + flex-direction: column; + height: 100%; +} + +ul { + margin: 0; + padding: 0; +} + +ul:empty::after { + content: "You've not added any todos to the list. Use the box above to add a new todo."; + font-size: 1rem; + margin: 1rem; + font-style: italic; + border: 2px dotted black; + padding: 0.5rem; + display: block; + text-align: justify; + text-align-last: center; +} + +li { + display: flex; + border-bottom: 1px dotted var(--border-color); +} + +li:last-child { + border-bottom: none; +} + +li .item-name { + flex: auto; + margin: auto; + padding: 0 1rem; +} + +li form input, +.new-todo form input#add-new-item { + width: 30px; + height: 30px; + padding: 0; + + background-repeat: no-repeat; + background-size: contain; + background-position: center; + + /* hide text */ + text-indent: 100%; + white-space: nowrap; + overflow: hidden; +} + +.todo form input { + margin: 1em; +} + +.todo form input.delete { + background-image: url('trashcan.svg'); + border: none; +} +input#add-new-item { background-image: url('plus.svg'); } +.todo form input.complete { background-image: none; } +.todo form input.uncomplete { background-image: url('check.svg'); } + +.done { + color: var(--done-color); +} + +.todo form { + display: contents; +} + +h1 { + font-weight: 200; + color: var(--title-color); + margin: 2rem; + display: none; +} + +h2 { + border-bottom: 1px solid black; + padding: 0 0 1rem 1rem; +} diff --git a/todos/netcore/static/trashcan.svg b/todos/netcore/static/trashcan.svg new file mode 100644 index 0000000..3d8c051 --- /dev/null +++ b/todos/netcore/static/trashcan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/todos/netcore/static/x.svg b/todos/netcore/static/x.svg new file mode 100644 index 0000000..e377314 --- /dev/null +++ b/todos/netcore/static/x.svg @@ -0,0 +1 @@ + \ No newline at end of file