From b40ca6b471ee0e93a62f15967577810385ebb7b5 Mon Sep 17 00:00:00 2001 From: capdiem Date: Wed, 6 Mar 2024 18:03:04 +0800 Subject: [PATCH] wip --- .../Data/ProDatabase.cs | 112 ++++++++++ .../Masa.Blazor.Pro.Components.csproj | 1 + Masa.Blazor.Pro.Components/Models/TodoTag.cs | 12 ++ .../Models/TodoTask.cs | 20 +- Masa.Blazor.Pro.Components/RenderFragments.cs | 31 +++ Masa.Blazor.Pro.Components/Todo/TodoNav.razor | 69 +++---- Masa.Blazor.ProApp.Rcl/Data/ProDatabase.cs | 64 ------ .../Masa.Blazor.ProApp.Rcl.csproj | 4 - Masa.Blazor.ProApp.Rcl/Pages/Todo.razor | 195 ++++++++++++++---- .../ServiceCollectionExtensions.cs | 23 ++- Masa.Blazor.ProApp.Web/Program.cs | 2 +- Masa.Blazor.ProApp.Web/proapp.db | Bin 0 -> 20480 bytes 12 files changed, 378 insertions(+), 155 deletions(-) create mode 100644 Masa.Blazor.Pro.Components/Data/ProDatabase.cs create mode 100644 Masa.Blazor.Pro.Components/Models/TodoTag.cs rename Masa.Blazor.ProApp.Rcl/Models/Task.cs => Masa.Blazor.Pro.Components/Models/TodoTask.cs (56%) create mode 100644 Masa.Blazor.Pro.Components/RenderFragments.cs delete mode 100644 Masa.Blazor.ProApp.Rcl/Data/ProDatabase.cs create mode 100644 Masa.Blazor.ProApp.Web/proapp.db diff --git a/Masa.Blazor.Pro.Components/Data/ProDatabase.cs b/Masa.Blazor.Pro.Components/Data/ProDatabase.cs new file mode 100644 index 0000000..688a7d4 --- /dev/null +++ b/Masa.Blazor.Pro.Components/Data/ProDatabase.cs @@ -0,0 +1,112 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Masa.Blazor.Pro.Components.Models; +using SQLite; + +namespace Masa.Blazor.Pro.Components; + +public class ProDatabase +{ + public const string DatabaseFilename = "proapp.db"; + + public const SQLiteOpenFlags Flags = + // open the database in read/write mode + SQLiteOpenFlags.ReadWrite | + // create the database if it doesn't exist + SQLiteOpenFlags.Create | + // enable multi-threaded database access + SQLiteOpenFlags.SharedCache; + + // public static string DatabasePath => + // Path.Combine(FileSystem.AppDataDirectory, DatabaseFilename); + + private SQLiteAsyncConnection? Database { get; set; } + + [MemberNotNull(nameof(Database))] + async Task InitAsync() + { + if (Database is not null) + { + return; + } + + Database = new SQLiteAsyncConnection(DatabaseFilename, Flags); + await Database.CreateTableAsync(); + await Database.CreateTableAsync(); + } + + #region Todo task + + public async Task CreateTaskAsync(TodoTask task) + { + await InitAsync(); + return await Database.InsertAsync(task); + } + + public async Task UpdateTaskAsync(TodoTask task) + { + await InitAsync(); + await Database.UpdateAsync(task); + } + + public async Task> GetTasksAsync( + int page, + int pageSize, + DateTime dateTime = default, + int tag = 0, + TodoTaskPriority? priority = null) + { + await InitAsync(); + + var sqlBuilder = new StringBuilder(); + sqlBuilder.Append("SELECT * FROM [TodoTask]"); + + var hasWhere = false; + + if (dateTime != default) + { + hasWhere = true; + + var tick = dateTime.Ticks; + var nextDay = tick + TimeSpan.TicksPerDay; + sqlBuilder.Append(" WHERE [DueAt] >= ").Append(tick).Append(" AND [DueAt] < ").Append(nextDay); + } + + if (tag != 0) + { + sqlBuilder.Append(hasWhere ? " AND" : " WHERE"); + sqlBuilder.Append(" [Tags] LIKE '%").Append(tag).Append(";%'"); + } + + if (priority is not null) + { + sqlBuilder.Append(hasWhere ? " AND" : " WHERE"); + sqlBuilder.Append(" [Priority] = ").Append((int)priority); + } + + sqlBuilder.Append(" ORDER BY [DueAt] DESC"); + sqlBuilder.Append(" LIMIT ").Append(pageSize).Append(" OFFSET ").Append((page - 1) * pageSize); + + return await Database.QueryAsync(sqlBuilder.ToString()); + } + + #endregion + + #region Todo tag + + public async Task CreateTagAsync(TodoTag tag) + { + await InitAsync(); + + return await this.Database.InsertAsync(tag); + } + + public async Task> GetTagsAsync() + { + await InitAsync(); + + return await Database.Table().ToListAsync(); + } + + #endregion +} \ No newline at end of file diff --git a/Masa.Blazor.Pro.Components/Masa.Blazor.Pro.Components.csproj b/Masa.Blazor.Pro.Components/Masa.Blazor.Pro.Components.csproj index 6d737d8..10c9d26 100644 --- a/Masa.Blazor.Pro.Components/Masa.Blazor.Pro.Components.csproj +++ b/Masa.Blazor.Pro.Components/Masa.Blazor.Pro.Components.csproj @@ -12,6 +12,7 @@ + diff --git a/Masa.Blazor.Pro.Components/Models/TodoTag.cs b/Masa.Blazor.Pro.Components/Models/TodoTag.cs new file mode 100644 index 0000000..4e134ba --- /dev/null +++ b/Masa.Blazor.Pro.Components/Models/TodoTag.cs @@ -0,0 +1,12 @@ +using SQLite; + +namespace Masa.Blazor.Pro.Components.Models; + +public class TodoTag +{ + [PrimaryKey] [AutoIncrement] public int Id { get; set; } + + public string? Name { get; set; } + + public string? Color { get; set; } +} \ No newline at end of file diff --git a/Masa.Blazor.ProApp.Rcl/Models/Task.cs b/Masa.Blazor.Pro.Components/Models/TodoTask.cs similarity index 56% rename from Masa.Blazor.ProApp.Rcl/Models/Task.cs rename to Masa.Blazor.Pro.Components/Models/TodoTask.cs index 8de1f12..b98e81d 100644 --- a/Masa.Blazor.ProApp.Rcl/Models/Task.cs +++ b/Masa.Blazor.Pro.Components/Models/TodoTask.cs @@ -1,10 +1,12 @@ using System.ComponentModel.DataAnnotations; using SQLite; -namespace Masa.Blazor.ProApp.Rcl.Models; +namespace Masa.Blazor.Pro.Components.Models; public class TodoTask { + private int[] _tagIds = []; + public TodoTask() { DueAt = DateTime.Today; @@ -24,7 +26,21 @@ public TodoTask() public bool Completed { get; set; } - public string? Tags { get; set; } + [Ignore] + public int[] TagIds + { + get => Tags?.Split(';').Where(t => !string.IsNullOrEmpty(t)).Select(int.Parse).ToArray() ?? []; + set + { + Tags = string.Join(';', value); + if (Tags.Length > 0) + { + Tags += ";"; + } + } + } + + public string? Tags { get; private set; } } public enum TodoTaskPriority diff --git a/Masa.Blazor.Pro.Components/RenderFragments.cs b/Masa.Blazor.Pro.Components/RenderFragments.cs new file mode 100644 index 0000000..c6a075b --- /dev/null +++ b/Masa.Blazor.Pro.Components/RenderFragments.cs @@ -0,0 +1,31 @@ +using BlazorComponent; +using Masa.Blazor.Pro.Components.Models; +using Microsoft.AspNetCore.Components; + +namespace Masa.Blazor.Pro.Components; + +public static class RenderFragments +{ + public static RenderFragment GenTagItem(TodoTag tag) => builder => + { + builder.OpenComponent(0, typeof(MListItemIcon)); + builder.AddAttribute(1, "Class", "mr-4"); + builder.AddAttribute(2, "ChildContent", (RenderFragment)(sub => + { + sub.OpenComponent(0, typeof(MIcon)); + sub.AddAttribute(1, "Icon", (Icon)"mdi-circle"); + sub.AddAttribute(2, "Color", tag.Color); + sub.CloseComponent(); + })); + builder.CloseComponent(); + + builder.OpenComponent(3); + builder.AddAttribute(4, "ChildContent", (RenderFragment)(sub => + { + sub.OpenComponent(0); + sub.AddAttribute(1, "ChildContent", (RenderFragment)(c => c.AddContent(0, tag.Name))); + sub.CloseComponent(); + })); + builder.CloseComponent(); + }; +} \ No newline at end of file diff --git a/Masa.Blazor.Pro.Components/Todo/TodoNav.razor b/Masa.Blazor.Pro.Components/Todo/TodoNav.razor index 6d5d58b..faf0e39 100644 --- a/Masa.Blazor.Pro.Components/Todo/TodoNav.razor +++ b/Masa.Blazor.Pro.Components/Todo/TodoNav.razor @@ -1,7 +1,8 @@ -@namespace Masa.Blazor.Pro.Components +@using Masa.Blazor.Pro.Components.Models +@namespace Masa.Blazor.Pro.Components - @@ -10,7 +11,7 @@ Add Task } - + @foreach (var item in _categories) { @@ -27,67 +28,45 @@ Tags - @foreach (var item in _tags) + @foreach (var item in Tags) { - - - - mdi-circle - - - @item.Title - - - - } - - Priority - - @foreach (var item in _priority) - { - - - - mdi-flag-variant - - - @item.Title - - + + @RenderFragments.GenTagItem(item) } + + + mdi-plus + Add Tag + + @code { - [Parameter] public bool? Value { get; set; } + [Parameter] public bool? Show { get; set; } - [Parameter] public EventCallback ValueChanged { get; set; } + [Parameter] public EventCallback ShowChanged { get; set; } [Parameter] public bool Permanent { get; set; } [Parameter] public bool ShowAddTask { get; set; } + [Parameter] public EventCallback OnAddTagClick { get; set; } + + [Parameter] public List Tags { get; set; } = []; + private static TodoNavItem[] _categories = [ new TodoNavItem("Today", "mdi-calendar-today-outline", "/todo"), new TodoNavItem("Inbox", "mdi-inbox-outline", "/todo/inbox") ]; - private static TodoNavItem[] _tags = - [ - new TodoNavItem("Team", "purple", "/todo/team"), - new TodoNavItem("Family", "pink", "/todo/family"), - ]; - - private static TodoNavItem[] _priority = - [ - new TodoNavItem("Low", "blue", "/todo/low"), - new TodoNavItem("Medium", "orange", "/todo/medium"), - new TodoNavItem("High", "red", "/todo/high"), - ]; - record TodoNavItem(string Title, string IconOrColor, string Href); } \ No newline at end of file diff --git a/Masa.Blazor.ProApp.Rcl/Data/ProDatabase.cs b/Masa.Blazor.ProApp.Rcl/Data/ProDatabase.cs deleted file mode 100644 index bdf70a5..0000000 --- a/Masa.Blazor.ProApp.Rcl/Data/ProDatabase.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using Masa.Blazor.ProApp.Rcl.Models; -using SQLite; - -namespace Masa.Blazor.ProApp.Rcl.Data; - -public class ProDatabase -{ - public const string DatabaseFilename = "proapp.db"; - - public const SQLiteOpenFlags Flags = - // open the database in read/write mode - SQLiteOpenFlags.ReadWrite | - // create the database if it doesn't exist - SQLiteOpenFlags.Create | - // enable multi-threaded database access - SQLiteOpenFlags.SharedCache; - - // public static string DatabasePath => - // Path.Combine(FileSystem.AppDataDirectory, DatabaseFilename); - - private SQLiteAsyncConnection? Database { get; set; } - - [MemberNotNull(nameof(Database))] - async Task InitAsync() - { - if (Database is not null) - { - return; - } - - Database = new SQLiteAsyncConnection(DatabaseFilename, Flags); - await Database.CreateTableAsync(); - } - - public async Task CreateTaskAsync(TodoTask task) - { - await InitAsync(); - return await Database.InsertAsync(task); - } - - public async Task UpdateTaskAsync(TodoTask task) - { - await InitAsync(); - await Database.UpdateAsync(task); - } - - public async Task> GetTasksAsync(int page, int pageSize, DateTime dateTime = default) - { - await InitAsync(); - - var table = Database.Table(); - - if (dateTime != default) - { - table = table.Where(u => u.DueAt.Date == dateTime.Date); - } - - return await table - .Skip((page - 1) * pageSize) - .Take(pageSize) - .ToListAsync(); - } -} \ No newline at end of file diff --git a/Masa.Blazor.ProApp.Rcl/Masa.Blazor.ProApp.Rcl.csproj b/Masa.Blazor.ProApp.Rcl/Masa.Blazor.ProApp.Rcl.csproj index 4922800..f0f665b 100644 --- a/Masa.Blazor.ProApp.Rcl/Masa.Blazor.ProApp.Rcl.csproj +++ b/Masa.Blazor.ProApp.Rcl/Masa.Blazor.ProApp.Rcl.csproj @@ -14,8 +14,4 @@ - - - - diff --git a/Masa.Blazor.ProApp.Rcl/Pages/Todo.razor b/Masa.Blazor.ProApp.Rcl/Pages/Todo.razor index 3385084..cca7fe9 100644 --- a/Masa.Blazor.ProApp.Rcl/Pages/Todo.razor +++ b/Masa.Blazor.ProApp.Rcl/Pages/Todo.razor @@ -1,6 +1,6 @@ @page "/todo/{filter?}" -@using Masa.Blazor.ProApp.Rcl.Data -@using Masa.Blazor.ProApp.Rcl.Models +@page "/todo/tag/{tag:int}" +@using Masa.Blazor.Pro.Components.Models @inject ProDatabase ProDatabase To do @@ -30,7 +30,8 @@ { - + @task.Title @@ -47,7 +48,7 @@ } + ContentClass="pa-4"> + Placeholder="How to do it?"> -
- + mdi-calendar-today-outline @GetAliasOfDueAt() @@ -89,9 +85,11 @@ - mdi-flag-variant-outline + mdi-flag-variant-outline + @_todoTask.Priority @@ -107,21 +105,88 @@ - - mdi-arrow-up-circle - + + + + mdi-tag-text-outline + + + + + + @foreach (var item in _tags) + { + + @RenderFragments.GenTagItem(item) + + } + + + +
+ + + mdi-arrow-up-circle + Add task +
+ + - + + + + + @foreach (var color in colors) + { + + + mdi-circle + + + } + + + Add new tag + + @code { + private static string[] colors = ["red", "orange", "blue", "green", "purple", "pink", "yellow"]; + private static List<(TodoTaskPriority priority, string? color)> Priorities = [ (TodoTaskPriority.High, "red"), @@ -131,52 +196,88 @@ ]; private bool? _drawer = false; + private string? _prevFilter; + private int _prevTag; + private bool _newTaskSheet; private bool _calendarSheet; + + private bool _tagSheet; + + private TodoTask _todoTask = new(); + private TodoTag _newTag = new(); + private int _page = 1; private int _pageSize = 10; private List _tasks = new(); + private List _tags = new(); private List _expandedPriority = [0, 1, 2, 3, -1]; private string Category => Filter ?? "Today"; + private string? PriorityColor => _todoTask.Priority switch + { + TodoTaskPriority.High => "red", + TodoTaskPriority.Medium => "orange", + TodoTaskPriority.Low => "blue", + _ => null + }; + + [Parameter] public string? Filter { get; set; } + + [Parameter] public int Tag { get; set; } + protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { + _prevFilter = Filter; + _prevTag = Tag; + await LoadTasks(); + await LoadTags(); StateHasChanged(); } } - private async Task LoadTasks() + protected override async Task OnParametersSetAsync() { - _tasks = Filter?.ToLowerInvariant() switch + if (_prevFilter != Filter || _prevTag != Tag) { - "inbox" => await ProDatabase.GetTasksAsync(_page, _pageSize), - _ => await ProDatabase.GetTasksAsync(_page, _pageSize, DateTime.Today), - }; + _prevFilter = Filter; + _prevTag = Tag; + await LoadTasks(); + } } - private string? PriorityColor => _todoTask.Priority switch + private async Task LoadTasks() { - TodoTaskPriority.High => "red", - TodoTaskPriority.Medium => "orange", - TodoTaskPriority.Low => "blue", - _ => null - }; - - private TodoTask _todoTask = new(); + if (Tag != 0) + { + _tasks = await ProDatabase.GetTasksAsync(_page, _pageSize, tag: Tag); + } + else + { + _tasks = Filter?.ToLowerInvariant() switch + { + "inbox" => await ProDatabase.GetTasksAsync(_page, _pageSize), + _ => await ProDatabase.GetTasksAsync(_page, _pageSize, DateTime.Today) + }; + } + } - [Parameter] public string? Filter { get; set; } + private async Task LoadTags() + { + _tags = await ProDatabase.GetTagsAsync(); + } private async Task HandleOnSubmit() { - // TODO: Save the task - await ProDatabase.CreateTaskAsync(_todoTask); + await LoadTasks(); + _todoTask = new(); _newTaskSheet = false; } @@ -222,4 +323,22 @@ await ProDatabase.UpdateTaskAsync(task); } + private void ShowAddTagSheet() + { + _drawer = false; + _tagSheet = true; + } + + private void OnTagNameChanged(string val) + { + _newTag.Name = val.Trim(',').Trim(' ').Trim(';'); + } + + private async Task HandleOnAddTag() + { + await ProDatabase.CreateTagAsync(_newTag); + _newTag = new(); + _tagSheet = false; + } + } \ No newline at end of file diff --git a/Masa.Blazor.ProApp.Rcl/ServiceCollectionExtensions.cs b/Masa.Blazor.ProApp.Rcl/ServiceCollectionExtensions.cs index 3ae3773..ba6b067 100644 --- a/Masa.Blazor.ProApp.Rcl/ServiceCollectionExtensions.cs +++ b/Masa.Blazor.ProApp.Rcl/ServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ -namespace Microsoft.Extensions.DependencyInjection; +using Masa.Blazor; + +namespace Microsoft.Extensions.DependencyInjection; public static class ServiceCollectionExtensions { @@ -6,6 +8,25 @@ public static IServiceCollection AddMasaBlazorPro(this IServiceCollection servic { services.AddMasaBlazor(options => { + options.Defaults = new Dictionary?>() + { + ["MButton"] = new Dictionary() + { + [nameof(MButton.Depressed)] = true + }, + ["MTextField"] = new Dictionary() + { + ["Filled"] = true, + ["Rounded"] = true, + ["PersistentPlaceholder"] = true + }, + ["MTextarea"] = new Dictionary() + { + ["Filled"] = true, + ["Rounded"] = true, + ["PersistentPlaceholder"] = true + } + }; options.ConfigureTheme(theme => { theme.Themes.Light.Primary = "#4f33ff"; diff --git a/Masa.Blazor.ProApp.Web/Program.cs b/Masa.Blazor.ProApp.Web/Program.cs index cf47947..0cd433f 100644 --- a/Masa.Blazor.ProApp.Web/Program.cs +++ b/Masa.Blazor.ProApp.Web/Program.cs @@ -1,4 +1,4 @@ -using Masa.Blazor.ProApp.Rcl.Data; +using Masa.Blazor.Pro.Components; var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorPages(); diff --git a/Masa.Blazor.ProApp.Web/proapp.db b/Masa.Blazor.ProApp.Web/proapp.db new file mode 100644 index 0000000000000000000000000000000000000000..a2c0f340614a2f9445f1ff27a23ebd7110069ee1 GIT binary patch literal 20480 zcmeI&L2DC17zW^(-6Tzd+Z8;75(C3tY-sV~p{Ibg4H77&Y7jkW*(PILlI*6l6S233 zf`})*1uudqR8a9GXbawZEkY5GNzqdg|A4b?)}+NA;=zKvLw0xO%eOl-&+KJku3edP zBxiR#QMV<#Ku!}%$wkHpA%;4l>Ih3rRU%fGS^c_vsPo1No(G&bBo=x zMd_|Bg20Sd^F%+Ay&j9DvKiWR+%ms^(3f9bEIKkAmS2tZ22_}z&EE(I6ed~BbNexC z+}gj@$}VWJ)Wih6xj#XFxu!m07k$3m_eetUMc&)Rs+WGLX`{eoF=Ckp|kD_P4k1x~-?*U%u&le6Y9K3Udwfr~3Ziq2{i+bK(=k03ZMX2tWV=5P$## zAOHafKmY;|7)s!ju4m})-3v)gPbYeNJ%Yy=y@p>+s+MMM5p&Dz4AlXWApijgKmY;| zfB*y_009U<00I#Brvhj79Nle?=~MKN+XA$-o=Y6J4qP0X9O=xyQ0p&01L1r7ytkO literal 0 HcmV?d00001