diff --git a/.github/workflows/nuget.org.yml b/.github/workflows/nuget.org.yml index 9d60c138e9..00e2c49b1b 100644 --- a/.github/workflows/nuget.org.yml +++ b/.github/workflows/nuget.org.yml @@ -82,9 +82,13 @@ jobs: - name: dotnet pack Butil run: dotnet pack src/Butil/Bit.Butil/Bit.Butil.csproj --output . --configuration Release + - name: dotnet build Besql + run: dotnet build src/Besql/Bit.Besql/Bit.Besql.csproj -c Release -p:GeneratePackageOnBuild=false -p:WarningLevel=0 -p:RunCodeAnalysis=false + - name: dotnet pack Besql + run: dotnet pack src/Besql/Bit.Besql/Bit.Besql.csproj --output . --configuration Release + - name: dotnet build CodeAnalyzers run: dotnet build src/CodeAnalyzers/Bit.CodeAnalyzers/Bit.CodeAnalyzers.csproj -c Release -p:GeneratePackageOnBuild=false -p:WarningLevel=0 -p:RunCodeAnalysis=false - - name: dotnet pack CodeAnalyzer run: dotnet pack src/CodeAnalyzers/Bit.CodeAnalyzers/Bit.CodeAnalyzers.csproj --output . --configuration Release diff --git a/.github/workflows/prerelease.nuget.org.yml b/.github/workflows/prerelease.nuget.org.yml index 58e3f2c9f0..edd238a324 100644 --- a/.github/workflows/prerelease.nuget.org.yml +++ b/.github/workflows/prerelease.nuget.org.yml @@ -72,12 +72,16 @@ jobs: - name: dotnet pack Butil run: dotnet pack src/Butil/Bit.Butil/Bit.Butil.csproj --output . --configuration Release + - name: dotnet build Besql + run: dotnet build src/Besql/Bit.Besql/Bit.Besql.csproj -c Release -p:GeneratePackageOnBuild=false -p:WarningLevel=0 -p:RunCodeAnalysis=false + - name: dotnet pack Besql + run: dotnet pack src/Besql/Bit.Besql/Bit.Besql.csproj --output . --configuration Release + - name: dotnet build CodeAnalyzers run: dotnet build src/CodeAnalyzers/Bit.CodeAnalyzers/Bit.CodeAnalyzers.csproj -c Release -p:GeneratePackageOnBuild=false -p:WarningLevel=0 -p:RunCodeAnalysis=false - - name: dotnet pack CodeAnalyzer run: dotnet pack src/CodeAnalyzers/Bit.CodeAnalyzers/Bit.CodeAnalyzers.csproj --output . --configuration Release - + - name: build SourceGenerators nupkg file run: dotnet build src/SourceGenerators/Bit.SourceGenerators/Bit.SourceGenerators.csproj --configuration Release diff --git a/src/Besql/Bit.Besql.sln b/src/Besql/Bit.Besql.sln new file mode 100644 index 0000000000..f7b3edb682 --- /dev/null +++ b/src/Besql/Bit.Besql.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34321.82 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bit.Besql", "Bit.Besql\Bit.Besql.csproj", "{09B2C633-C120-4B89-A3FF-A1F9F76D42C5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {09B2C633-C120-4B89-A3FF-A1F9F76D42C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09B2C633-C120-4B89-A3FF-A1F9F76D42C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09B2C633-C120-4B89-A3FF-A1F9F76D42C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09B2C633-C120-4B89-A3FF-A1F9F76D42C5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {A4474E62-EBBF-4C5C-B256-EDA41E47EDC3} + EndGlobalSection +EndGlobal diff --git a/src/Besql/Bit.Besql/BesqlDbContextFactory.cs b/src/Besql/Bit.Besql/BesqlDbContextFactory.cs new file mode 100644 index 0000000000..574e56e95f --- /dev/null +++ b/src/Besql/Bit.Besql/BesqlDbContextFactory.cs @@ -0,0 +1,131 @@ +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; + +namespace Bit.Besql; + +public class BesqlDbContextFactory : DbContextFactory + where TContext : DbContext +{ + private static readonly IDictionary FileNames = new Dictionary(); + + private readonly IBesqlStorage cache; + private Task? startupTask = null; + private int lastStatus = -2; + + public BesqlDbContextFactory( + IBesqlStorage cache, + IServiceProvider serviceProvider, + DbContextOptions options, + IDbContextFactorySource factorySource) + : base(serviceProvider, options, factorySource) + { + this.cache = cache; + startupTask = RestoreAsync(); + } + + private static string Filename => FileNames[typeof(TContext)]; + + private static string BackupFile => $"{BesqlDbContextFactory.Filename}_bak"; + + public static void Reset() => FileNames.Clear(); + + public static string? GetFilenameForType() => + FileNames.ContainsKey(typeof(TContext)) ? FileNames[typeof(TContext)] : null; + + public override async Task CreateDbContextAsync(CancellationToken cancellationToken = default) + { + await CheckForStartupTaskAsync(); + + var ctx = await base.CreateDbContextAsync(cancellationToken); + + ctx.SavedChanges += SyncDbToCacheAsync; + + return ctx; + } + + private async Task DoSwap(string source, string target) + { + await using var src = new SqliteConnection($"Data Source={source}"); + await using var tgt = new SqliteConnection($"Data Source={target}"); + + await src.OpenAsync(); + await tgt.OpenAsync(); + + src.BackupDatabase(tgt); + + await tgt.CloseAsync(); + await src.CloseAsync(); + } + + private async Task GetFilename() + { + await using var ctx = await base.CreateDbContextAsync(); + var filename = "filenotfound.db"; + var type = ctx.GetType(); + if (FileNames.TryGetValue(type, out var value)) + { + return value; + } + + var cs = ctx.Database.GetConnectionString(); + + if (cs != null) + { + var file = cs.Split(';').Select(s => s.Split('=')) + .Select(split => new + { + key = split[0].ToLowerInvariant(), + value = split[1], + }) + .Where(kv => kv.key.Contains("data source") || + kv.key.Contains("datasource") || + kv.key.Contains("filename")) + .Select(kv => kv.value) + .FirstOrDefault(); + if (file != null) + { + filename = file; + } + } + + FileNames.Add(type, filename); + return filename; + } + + private async Task CheckForStartupTaskAsync() + { + if (startupTask != null) + { + lastStatus = await startupTask; + startupTask?.Dispose(); + startupTask = null; + } + } + + private async void SyncDbToCacheAsync(object sender, SavedChangesEventArgs e) + { + var ctx = (TContext)sender; + await ctx.Database.CloseConnectionAsync(); + await CheckForStartupTaskAsync(); + if (e.EntitiesSavedCount > 0) + { + // unique to avoid conflicts. Is deleted after caching. + var backupName = $"{BesqlDbContextFactory.BackupFile}-{Guid.NewGuid().ToString().Split('-')[0]}"; + await DoSwap(BesqlDbContextFactory.Filename, backupName); + lastStatus = await cache.SyncDb(backupName); + } + } + + private async Task RestoreAsync() + { + var filename = $"{await GetFilename()}_bak"; + lastStatus = await cache.SyncDb(filename); + if (lastStatus == 0) + { + await DoSwap(filename, FileNames[typeof(TContext)]); + } + + return lastStatus; + } +} diff --git a/src/Besql/Bit.Besql/Bit.Besql.csproj b/src/Besql/Bit.Besql/Bit.Besql.csproj new file mode 100644 index 0000000000..74c68d6925 --- /dev/null +++ b/src/Besql/Bit.Besql/Bit.Besql.csproj @@ -0,0 +1,32 @@ + + + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + True + \ + + + True + \ + + + + diff --git a/src/Besql/Bit.Besql/BrowserCacheBesqlStorage.cs b/src/Besql/Bit.Besql/BrowserCacheBesqlStorage.cs new file mode 100644 index 0000000000..054c9efa89 --- /dev/null +++ b/src/Besql/Bit.Besql/BrowserCacheBesqlStorage.cs @@ -0,0 +1,29 @@ +using Microsoft.JSInterop; + +namespace Bit.Besql; + +public sealed class BrowserCacheBesqlStorage : IAsyncDisposable, IBesqlStorage +{ + private readonly Lazy> moduleTask; + + public BrowserCacheBesqlStorage(IJSRuntime jsRuntime) + { + moduleTask = new(() => jsRuntime.InvokeAsync( + "import", "./_content/Bit.Besql/browserCache.js").AsTask()!); + } + + public async ValueTask DisposeAsync() + { + if (moduleTask.IsValueCreated) + { + var module = await moduleTask.Value; + await module.DisposeAsync(); + } + } + + public async Task SyncDb(string filename) + { + var module = await moduleTask.Value; + return await module.InvokeAsync("synchronizeDbWithCache", filename); + } +} diff --git a/src/Besql/Bit.Besql/IBesqlStorage.cs b/src/Besql/Bit.Besql/IBesqlStorage.cs new file mode 100644 index 0000000000..12518aa00d --- /dev/null +++ b/src/Besql/Bit.Besql/IBesqlStorage.cs @@ -0,0 +1,6 @@ +namespace Bit.Besql; + +public interface IBesqlStorage +{ + Task SyncDb(string filename); +} diff --git a/src/Besql/Bit.Besql/IServiceCollectionBesqlExtentions.cs b/src/Besql/Bit.Besql/IServiceCollectionBesqlExtentions.cs new file mode 100644 index 0000000000..4bd158ebf0 --- /dev/null +++ b/src/Besql/Bit.Besql/IServiceCollectionBesqlExtentions.cs @@ -0,0 +1,43 @@ +using Bit.Besql; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class IServiceCollectionBesqlExtentions +{ + public static IServiceCollection AddBesqlDbContextFactory( + this IServiceCollection services, + Action? optionsAction) + where TContext : DbContext + { + if (OperatingSystem.IsBrowser()) + { + services.TryAddScoped(); + services.AddDbContextFactory>( + optionsAction ?? ((s, p) => { }), ServiceLifetime.Scoped); + } + else + { + services.AddDbContextFactory( + optionsAction ?? ((s, p) => { }), ServiceLifetime.Scoped); + } + + return services; + } + + public static IServiceCollection AddBesqlDbContextFactory( + this IServiceCollection services, + Action? optionsAction) + where TContext : DbContext + { + return services.AddBesqlDbContextFactory((s, p) => optionsAction?.Invoke(p)); + } + + public static IServiceCollection AddBesqlDbContextFactory( + this IServiceCollection services) + where TContext : DbContext + { + return services.AddBesqlDbContextFactory(options => { }); + } +} diff --git a/src/Besql/Bit.Besql/wwwroot/browserCache.js b/src/Besql/Bit.Besql/wwwroot/browserCache.js new file mode 100644 index 0000000000..925cf48d9e --- /dev/null +++ b/src/Besql/Bit.Besql/wwwroot/browserCache.js @@ -0,0 +1,63 @@ +export async function synchronizeDbWithCache(file) { + + window.sqlitedb = window.sqlitedb || { + init: false, + cache: await caches.open('Bit-Besql') + }; + + const db = window.sqlitedb; + + const backupPath = `/${file}`; + const cachePath = `/data/cache/${file.substring(0, file.indexOf('_bak'))}`; + + if (!db.init) { + + db.init = true; + + const resp = await db.cache.match(cachePath); + + if (resp && resp.ok) { + + const res = await resp.arrayBuffer(); + + if (res) { + console.log(`Restoring ${res.byteLength} bytes.`); + window.Module.FS.writeFile(backupPath, new Uint8Array(res)); + return 0; + } + } + return -1; + } + + if (window.Module.FS.analyzePath(backupPath).exists) { + + const waitFlush = new Promise((done, _) => { + setTimeout(done, 10); + }); + + await waitFlush; + + const data = window.Module.FS.readFile(backupPath); + + const blob = new Blob([data], { + type: 'application/octet-stream', + ok: true, + status: 200 + }); + + const headers = new Headers({ + 'content-length': blob.size + }); + + const response = new Response(blob, { + headers + }); + + await db.cache.put(cachePath, response); + + window.Module.FS.unlink(backupPath); + + return 1; + } + return -1; +} diff --git a/src/Besql/README.md b/src/Besql/README.md new file mode 100644 index 0000000000..2d60f9a9c8 --- /dev/null +++ b/src/Besql/README.md @@ -0,0 +1,17 @@ +## bit entity framework core sqlite (bit Besql) + +How to use `Bit.Besql`: + +The usage of `Bit.Besql` is exactly the same as the regular usage of `Microsoft.EntityFrameworkCore.Sqlite` with [IDbContextFactory](https://learn.microsoft.com/en-us/aspnet/core/blazor/blazor-ef-core?view=aspnetcore-8.0#new-dbcontext-instances). + +To get start, simply install `Bit.Besql` and use `services.AddBesqlDbContextFactory` instead of `services.AddDbContextFactory`. + +Note: Don't use `IDbContextFactory` in `OnInitialized` because it relies on `IJSRuntime`. Use `OnAfterRender` instead. + +In order to download sqlite db file from browser cache storage in blazor WebAssembly run the followings in browser console: +```js +const cache = await caches.open('Bit-Besql'); +const resp = await cache.match('/data/cache/Boilerplate-ClientDb.db'); +const blob = await resp.blob(); +URL.createObjectURL(blob); +``` \ No newline at end of file diff --git a/src/Bit-CI-release.sln b/src/Bit-CI-release.sln index 5da4cbb005..bf48df2ec7 100644 --- a/src/Bit-CI-release.sln +++ b/src/Bit-CI-release.sln @@ -88,6 +88,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bit.BlazorUI.Assets", "Blaz EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bit.BlazorUI.Icons", "BlazorUI\Bit.BlazorUI.Icons\Bit.BlazorUI.Icons.csproj", "{FA2191B8-E2BB-4160-8F44-BA2F3AFDE874}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Besql", "Besql", "{784E02AE-EC39-4A98-A705-684969F3EE54}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bit.Besql", "Besql\Bit.Besql\Bit.Besql.csproj", "{103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -462,6 +466,26 @@ Global {FA2191B8-E2BB-4160-8F44-BA2F3AFDE874}.Release|x64.Build.0 = Release|Any CPU {FA2191B8-E2BB-4160-8F44-BA2F3AFDE874}.Release|x86.ActiveCfg = Release|Any CPU {FA2191B8-E2BB-4160-8F44-BA2F3AFDE874}.Release|x86.Build.0 = Release|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Debug|iPhone.Build.0 = Debug|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Debug|x64.ActiveCfg = Debug|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Debug|x64.Build.0 = Debug|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Debug|x86.ActiveCfg = Debug|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Debug|x86.Build.0 = Debug|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Release|Any CPU.Build.0 = Release|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Release|iPhone.ActiveCfg = Release|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Release|iPhone.Build.0 = Release|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Release|x64.ActiveCfg = Release|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Release|x64.Build.0 = Release|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Release|x86.ActiveCfg = Release|Any CPU + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -487,6 +511,7 @@ Global {E09679C7-B35A-4FAB-9DB1-F03E1A0585A9} = {DC165533-038F-4D71-9BA1-44CF254E116F} {835EA215-95F9-4ED5-B465-DFB76521A890} = {DC165533-038F-4D71-9BA1-44CF254E116F} {FA2191B8-E2BB-4160-8F44-BA2F3AFDE874} = {DC165533-038F-4D71-9BA1-44CF254E116F} + {103B5EA5-AF7C-43F1-BDB4-2C34E43252FF} = {784E02AE-EC39-4A98-A705-684969F3EE54} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DA107107-478F-477A-872B-787CEA7DD9B8} diff --git a/src/Bit-CI.sln b/src/Bit-CI.sln index c24064b27a..1248a9cb54 100644 --- a/src/Bit-CI.sln +++ b/src/Bit-CI.sln @@ -209,6 +209,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bit.BlazorUI.Demo.Server", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bit.BlazorUI.Demo.Shared", "BlazorUI\Demo\Bit.BlazorUI.Demo.Shared\Bit.BlazorUI.Demo.Shared.csproj", "{79023301-B334-46E5-BD6E-313BA58E530E}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Besql", "Besql", "{619FE7F0-CF56-4325-BE76-7A3464D4DF3C}" + ProjectSection(SolutionItems) = preProject + Readme.MD = Readme.MD + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bit.Besql", "Besql\Bit.Besql\Bit.Besql.csproj", "{18DFDA5D-B37A-4281-83D4-033F55DEC6CD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1014,6 +1021,26 @@ Global {79023301-B334-46E5-BD6E-313BA58E530E}.Release|x64.Build.0 = Release|Any CPU {79023301-B334-46E5-BD6E-313BA58E530E}.Release|x86.ActiveCfg = Release|Any CPU {79023301-B334-46E5-BD6E-313BA58E530E}.Release|x86.Build.0 = Release|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Debug|iPhone.Build.0 = Debug|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Debug|x64.ActiveCfg = Debug|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Debug|x64.Build.0 = Debug|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Debug|x86.ActiveCfg = Debug|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Debug|x86.Build.0 = Debug|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Release|Any CPU.Build.0 = Release|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Release|iPhone.ActiveCfg = Release|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Release|iPhone.Build.0 = Release|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Release|x64.ActiveCfg = Release|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Release|x64.Build.0 = Release|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Release|x86.ActiveCfg = Release|Any CPU + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1072,6 +1099,7 @@ Global {53022CD0-3145-403E-A7A0-70455D419588} = {D7242830-A91D-4729-BF2D-B1E7E141953E} {B61B811C-5D2B-4DF5-9D7B-212F4B464E3F} = {23426B1A-CC46-4D11-B233-208A57B2BCA2} {79023301-B334-46E5-BD6E-313BA58E530E} = {23426B1A-CC46-4D11-B233-208A57B2BCA2} + {18DFDA5D-B37A-4281-83D4-033F55DEC6CD} = {619FE7F0-CF56-4325-BE76-7A3464D4DF3C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DA107107-478F-477A-872B-787CEA7DD9B8} diff --git a/src/Bit.Build.props b/src/Bit.Build.props index 5d012d2d41..845c7ccb3c 100644 --- a/src/Bit.Build.props +++ b/src/Bit.Build.props @@ -25,7 +25,7 @@ https://github.com/bitfoundation/bitplatform https://avatars.githubusercontent.com/u/22663390 - 8.3.0 + 8.4.0 https://github.com/bitfoundation/bitplatform/releases/tag/v-$(ReleaseVersion) $(ReleaseVersion) diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj index 0ffa3aae16..af050a7d24 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj @@ -5,11 +5,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Bit.BlazorUI.Demo.Shared.csproj b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Bit.BlazorUI.Demo.Shared.csproj index e9a165cf6d..7cc945e70b 100644 --- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Bit.BlazorUI.Demo.Shared.csproj +++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Shared/Bit.BlazorUI.Demo.Shared.csproj @@ -5,11 +5,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj index fdc3a3da87..9111b5c9e2 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Bit.BlazorUI.Demo.Client.Core.csproj @@ -16,11 +16,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Maui/Bit.BlazorUI.Demo.Client.Maui.csproj b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Maui/Bit.BlazorUI.Demo.Client.Maui.csproj index 364e45b868..d8d478ebe3 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Maui/Bit.BlazorUI.Demo.Client.Maui.csproj +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Maui/Bit.BlazorUI.Demo.Client.Maui.csproj @@ -81,12 +81,12 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Bit.BlazorUI.Demo.Client.Web.csproj b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Bit.BlazorUI.Demo.Client.Web.csproj index 69c87947c3..af9638ecc9 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Bit.BlazorUI.Demo.Client.Web.csproj +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/Bit.BlazorUI.Demo.Client.Web.csproj @@ -26,12 +26,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/wwwroot/service-worker.published.js b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/wwwroot/service-worker.published.js index 32ecb3b655..c09ba56485 100644 --- a/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/wwwroot/service-worker.published.js +++ b/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Web/wwwroot/service-worker.published.js @@ -1,4 +1,4 @@ -// bit version: 8.3.0 +// bit version: 8.4.0 // https://github.com/bitfoundation/bitplatform/tree/develop/src/Bswup self.assetsInclude = []; diff --git a/src/BlazorUI/Demo/Directory.Build.props b/src/BlazorUI/Demo/Directory.Build.props index 87598d0144..29f189bc86 100644 --- a/src/BlazorUI/Demo/Directory.Build.props +++ b/src/BlazorUI/Demo/Directory.Build.props @@ -1,4 +1,4 @@ - + 12.0 diff --git a/src/Bswup/Bit.Bswup.Demo/wwwroot/service-worker.js b/src/Bswup/Bit.Bswup.Demo/wwwroot/service-worker.js index 7b8cbae33d..f7df1c14f1 100644 --- a/src/Bswup/Bit.Bswup.Demo/wwwroot/service-worker.js +++ b/src/Bswup/Bit.Bswup.Demo/wwwroot/service-worker.js @@ -1,4 +1,4 @@ -// bit version: 8.3.0 +// bit version: 8.4.0 self.assetsExclude = [/\.scp\.css$/, /weather\.json$/]; self.caseInsensitiveUrl = true; diff --git a/src/Bswup/Bit.Bswup.Demo/wwwroot/service-worker.published.js b/src/Bswup/Bit.Bswup.Demo/wwwroot/service-worker.published.js index 941ffb1fe5..f9bdfdb88d 100644 --- a/src/Bswup/Bit.Bswup.Demo/wwwroot/service-worker.published.js +++ b/src/Bswup/Bit.Bswup.Demo/wwwroot/service-worker.published.js @@ -1,4 +1,4 @@ -// bit version: 8.3.0 +// bit version: 8.4.0 self.assetsExclude = [/\.scp\.css$/, /weather\.json$/]; self.caseInsensitiveUrl = true; diff --git a/src/Bswup/Bit.Bswup.NewDemo/Bit.Bswup.NewDemo.Client/wwwroot/service-worker.js b/src/Bswup/Bit.Bswup.NewDemo/Bit.Bswup.NewDemo.Client/wwwroot/service-worker.js index f9b7c100db..d78708da2a 100644 --- a/src/Bswup/Bit.Bswup.NewDemo/Bit.Bswup.NewDemo.Client/wwwroot/service-worker.js +++ b/src/Bswup/Bit.Bswup.NewDemo/Bit.Bswup.NewDemo.Client/wwwroot/service-worker.js @@ -1,4 +1,4 @@ -// bit version: 8.3.0 +// bit version: 8.4.0 // In development, always fetch from the network and do not enable offline support. // This is because caching would make development more difficult (changes would not diff --git a/src/Bswup/Bit.Bswup.NewDemo/Bit.Bswup.NewDemo.Client/wwwroot/service-worker.published.js b/src/Bswup/Bit.Bswup.NewDemo/Bit.Bswup.NewDemo.Client/wwwroot/service-worker.published.js index 1be5d14a02..d50d846577 100644 --- a/src/Bswup/Bit.Bswup.NewDemo/Bit.Bswup.NewDemo.Client/wwwroot/service-worker.published.js +++ b/src/Bswup/Bit.Bswup.NewDemo/Bit.Bswup.NewDemo.Client/wwwroot/service-worker.published.js @@ -1,4 +1,4 @@ -// bit version: 8.3.0 +// bit version: 8.4.0 self.assetsInclude = []; self.assetsExclude = [ diff --git a/src/Bswup/Bit.Bswup/Scripts/bit-bswup.progress.ts b/src/Bswup/Bit.Bswup/Scripts/bit-bswup.progress.ts index 207cca88a4..d7b3007d42 100644 --- a/src/Bswup/Bit.Bswup/Scripts/bit-bswup.progress.ts +++ b/src/Bswup/Bit.Bswup/Scripts/bit-bswup.progress.ts @@ -1,4 +1,4 @@ -window['bit-bswup.progress version'] = '8.3.0'; +window['bit-bswup.progress version'] = '8.4.0'; ; (function () { (window as any).startBswupProgress = (autoReload: boolean, diff --git a/src/Bswup/Bit.Bswup/Scripts/bit-bswup.sw.ts b/src/Bswup/Bit.Bswup/Scripts/bit-bswup.sw.ts index 529860c0e4..d60643b01b 100644 --- a/src/Bswup/Bit.Bswup/Scripts/bit-bswup.sw.ts +++ b/src/Bswup/Bit.Bswup/Scripts/bit-bswup.sw.ts @@ -1,4 +1,4 @@ -self['bit-bswup.sw version'] = '8.3.0'; +self['bit-bswup.sw version'] = '8.4.0'; interface Window { clients: any diff --git a/src/Bswup/Bit.Bswup/Scripts/bit-bswup.ts b/src/Bswup/Bit.Bswup/Scripts/bit-bswup.ts index 098f8c3780..eab81203ef 100644 --- a/src/Bswup/Bit.Bswup/Scripts/bit-bswup.ts +++ b/src/Bswup/Bit.Bswup/Scripts/bit-bswup.ts @@ -1,4 +1,4 @@ -window['bit-bswup version'] = '8.3.0'; +window['bit-bswup version'] = '8.4.0'; declare const Blazor: any; diff --git a/src/Bswup/FullDemo/Client/wwwroot/service-worker.js b/src/Bswup/FullDemo/Client/wwwroot/service-worker.js index b446b61a61..697ec6f264 100644 --- a/src/Bswup/FullDemo/Client/wwwroot/service-worker.js +++ b/src/Bswup/FullDemo/Client/wwwroot/service-worker.js @@ -1,4 +1,4 @@ -// bit version: 8.3.0 +// bit version: 8.4.0 // In development, always fetch from the network and do not enable offline support. // This is because caching would make development more difficult (changes would not diff --git a/src/Bswup/FullDemo/Client/wwwroot/service-worker.published.js b/src/Bswup/FullDemo/Client/wwwroot/service-worker.published.js index 235acee014..e98354c8ee 100644 --- a/src/Bswup/FullDemo/Client/wwwroot/service-worker.published.js +++ b/src/Bswup/FullDemo/Client/wwwroot/service-worker.published.js @@ -1,4 +1,4 @@ -// bit version: 8.3.0 +// bit version: 8.4.0 self.assetsInclude = []; self.assetsExclude = [/\.scp\.css$/, /weather\.json$/]; diff --git a/src/Bup/Bit.Bup/Scripts/bit-bup.progress.ts b/src/Bup/Bit.Bup/Scripts/bit-bup.progress.ts index 367154e846..f385c094fa 100644 --- a/src/Bup/Bit.Bup/Scripts/bit-bup.progress.ts +++ b/src/Bup/Bit.Bup/Scripts/bit-bup.progress.ts @@ -1,4 +1,4 @@ -window['bit-bup.progress version'] = '8.3.0'; +window['bit-bup.progress version'] = '8.4.0'; ; (function () { (window as any).startBupProgress = (showLogs: boolean, showAssets: boolean, appContainerSelector: string, hideApp: boolean, autoHide: boolean) => { diff --git a/src/Bup/Bit.Bup/Scripts/bit-bup.ts b/src/Bup/Bit.Bup/Scripts/bit-bup.ts index 293fad037e..fc59c20615 100644 --- a/src/Bup/Bit.Bup/Scripts/bit-bup.ts +++ b/src/Bup/Bit.Bup/Scripts/bit-bup.ts @@ -1,4 +1,4 @@ -window['bit-bup version'] = '8.3.0'; +window['bit-bup version'] = '8.4.0'; declare const Blazor: any; diff --git a/src/Butil/Bit.Butil/Internals/JsInterops/ElementJsInterop.cs b/src/Butil/Bit.Butil/Internals/JsInterops/ElementJsInterop.cs index 25ed235c24..4c27bd1dd6 100644 --- a/src/Butil/Bit.Butil/Internals/JsInterops/ElementJsInterop.cs +++ b/src/Butil/Bit.Butil/Internals/JsInterops/ElementJsInterop.cs @@ -65,15 +65,13 @@ internal static async Task ElementToggleAttribute(this IJSRuntime js, ElementRef internal static async Task ElementGetAccessKey(this IJSRuntime js, ElementReference element) => await js.InvokeAsync("BitButil.element.getAccessKey", element); - - internal static async Task ElementSetAccessKey(this IJSRuntime js, ElementReference element, string key) - => await js.InvokeVoidAsync("BitButil.element.setAccessKey", element, key); + internal static async Task ElementSetAccessKey(this IJSRuntime js, ElementReference element, string value) + => await js.InvokeVoidAsync("BitButil.element.setAccessKey", element, value); internal static async Task ElementGetClassName(this IJSRuntime js, ElementReference element) => await js.InvokeAsync("BitButil.element.getClassName", element); - - internal static async Task ElementSetClassName(this IJSRuntime js, ElementReference element, string className) - => await js.InvokeVoidAsync("BitButil.element.setClassName", element, className); + internal static async Task ElementSetClassName(this IJSRuntime js, ElementReference element, string value) + => await js.InvokeVoidAsync("BitButil.element.setClassName", element, value); internal static async Task ElementGetClientHeight(this IJSRuntime js, ElementReference element) => await js.InvokeAsync("BitButil.element.clientHeight", element); @@ -89,21 +87,18 @@ internal static async Task ElementGetClientWidth(this IJSRuntime js, Elem internal static async Task ElementGetId(this IJSRuntime js, ElementReference element) => await js.InvokeAsync("BitButil.element.getId", element); - - internal static async Task ElementSetId(this IJSRuntime js, ElementReference element, string id) - => await js.InvokeVoidAsync("BitButil.element.setId", element, id); + internal static async Task ElementSetId(this IJSRuntime js, ElementReference element, string value) + => await js.InvokeVoidAsync("BitButil.element.setId", element, value); internal static async Task ElementGetInnerHTML(this IJSRuntime js, ElementReference element) => await js.InvokeAsync("BitButil.element.getInnerHTML", element); - - internal static async Task ElementSetInnerHTML(this IJSRuntime js, ElementReference element, string innerHTML) - => await js.InvokeVoidAsync("BitButil.element.setInnerHTML", element, innerHTML); + internal static async Task ElementSetInnerHTML(this IJSRuntime js, ElementReference element, string value) + => await js.InvokeVoidAsync("BitButil.element.setInnerHTML", element, value); internal static async Task ElementGetOuterHTML(this IJSRuntime js, ElementReference element) => await js.InvokeAsync("BitButil.element.getOuterHTML", element); - - internal static async Task ElementSetOuterHTML(this IJSRuntime js, ElementReference element, string outerHTML) - => await js.InvokeVoidAsync("BitButil.element.setOuterHTML", element, outerHTML); + internal static async Task ElementSetOuterHTML(this IJSRuntime js, ElementReference element, string value) + => await js.InvokeVoidAsync("BitButil.element.setOuterHTML", element, value); internal static async Task ElementGetScrollHeight(this IJSRuntime js, ElementReference element) => await js.InvokeAsync("BitButil.element.scrollHeight", element); @@ -119,4 +114,165 @@ internal static async Task ElementGetScrollWidth(this IJSRuntime js, Elem internal static async Task ElementGetTagName(this IJSRuntime js, ElementReference element) => await js.InvokeAsync("BitButil.element.tagName", element); + + internal static async Task ElementGetContentEditable(this IJSRuntime js, ElementReference element) + { + var value = await js.InvokeAsync("BitButil.element.getContentEditable", element); + return value switch + { + "true" => ContentEditable.True, + "false" => ContentEditable.False, + "plaintext-only" => ContentEditable.PlainTextOnly, + _ => ContentEditable.Inherit + }; + } + internal static async Task ElementSetContentEditable(this IJSRuntime js, ElementReference element, ContentEditable value) + { + var v = value switch + { + ContentEditable.False => "false", + ContentEditable.True => "true", + ContentEditable.PlainTextOnly => "plaintext-only", + _ => "inherit", + }; + await js.InvokeVoidAsync("BitButil.element.setContentEditable", element, v); + } + + internal static async Task ElementIsContentEditable(this IJSRuntime js, ElementReference element) + => await js.InvokeAsync("BitButil.element.isContentEditable", element); + + internal static async Task ElementGetDir(this IJSRuntime js, ElementReference element) + { + var value = await js.InvokeAsync("BitButil.element.getDir", element); + return value switch + { + "ltr" => Dir.Ltr, + "rtl" => Dir.Rtl, + "auto" => Dir.Auto, + _ => Dir.NotSet, + }; + } + internal static async Task ElementSetDir(this IJSRuntime js, ElementReference element, Dir value) + { + var v = value switch + { + Dir.Ltr => "ltr", + Dir.Rtl => "rtl", + Dir.Auto => "auto", + _ => "", + }; + await js.InvokeVoidAsync("BitButil.element.setDir", element, v); + } + + internal static async Task ElementGetEnterKeyHint(this IJSRuntime js, ElementReference element) + { + var value = await js.InvokeAsync("BitButil.element.getEnterKeyHint", element); + return value switch + { + "enter" => EnterKeyHint.Enter, + "done" => EnterKeyHint.Done, + "go" => EnterKeyHint.Go, + "next" => EnterKeyHint.Next, + "previous" => EnterKeyHint.Previous, + "search" => EnterKeyHint.Search, + "send" => EnterKeyHint.Send, + _ => EnterKeyHint.NotSet + }; + } + internal static async Task ElementSetEnterKeyHint(this IJSRuntime js, ElementReference element, EnterKeyHint value) + { + var v = value switch + { + EnterKeyHint.Enter => "enter", + EnterKeyHint.Done => "done", + EnterKeyHint.Go => "go", + EnterKeyHint.Next => "next", + EnterKeyHint.Previous => "previous", + EnterKeyHint.Search => "search", + EnterKeyHint.Send => "send", + _ => "", + }; + await js.InvokeVoidAsync("BitButil.element.setEnterKeyHint", element, v); + } + + internal static async Task ElementGetHidden(this IJSRuntime js, ElementReference element) + { + var value = await js.InvokeAsync("BitButil.element.getHidden", element); + return value switch + { + "true" => Hidden.True, + "until-found" => Hidden.UntilFound, + _ => Hidden.False + }; + } + internal static async Task ElementSetHidden(this IJSRuntime js, ElementReference element, Hidden value) + { + var v = value switch + { + Hidden.True => "true", + Hidden.UntilFound => "until-found", + _ => "false", + }; + await js.InvokeVoidAsync("BitButil.element.setHidden", element, v); + } + + internal static async Task ElementGetInert(this IJSRuntime js, ElementReference element) + => await js.InvokeAsync("BitButil.element.getInert", element); + internal static async Task ElementSetInert(this IJSRuntime js, ElementReference element, bool value) + => await js.InvokeVoidAsync("BitButil.element.setInert", element, value); + + internal static async Task ElementGetInnerText(this IJSRuntime js, ElementReference element) + => await js.InvokeAsync("BitButil.element.getInnerText", element); + internal static async Task ElementSetInnerText(this IJSRuntime js, ElementReference element, string value) + => await js.InvokeVoidAsync("BitButil.element.setInnerText", element, value); + + internal static async Task ElementGetInputMode(this IJSRuntime js, ElementReference element) + { + var value = await js.InvokeAsync("BitButil.element.getInputMode", element); + return value switch + { + "decimal" => InputMode.Decimal, + "email" => InputMode.Email, + "none" => InputMode.None, + "numeric" => InputMode.Numeric, + "search" => InputMode.Search, + "tel" => InputMode.Tel, + "text" => InputMode.Text, + "url" => InputMode.Url, + _ => InputMode.NotSet, + }; + } + internal static async Task ElementSetInputMode(this IJSRuntime js, ElementReference element, InputMode value) + { + var v = value switch + { + InputMode.Decimal => "decimal", + InputMode.Email => "email", + InputMode.None => "none", + InputMode.Numeric => "numeric", + InputMode.Search => "search", + InputMode.Tel => "tel", + InputMode.Text => "text", + InputMode.Url => "url", + _ => "", + }; + await js.InvokeVoidAsync("BitButil.element.setInputMode", element, v); + } + + internal static async Task ElementGetOffsetHeight(this IJSRuntime js, ElementReference element) + => await js.InvokeAsync("BitButil.element.offsetHeight", element); + + internal static async Task ElementGetOffsetLeft(this IJSRuntime js, ElementReference element) + => await js.InvokeAsync("BitButil.element.offsetLeft", element); + + internal static async Task ElementGetOffsetTop(this IJSRuntime js, ElementReference element) + => await js.InvokeAsync("BitButil.element.offsetTop", element); + + internal static async Task ElementGetOffsetWidth(this IJSRuntime js, ElementReference element) + => await js.InvokeAsync("BitButil.element.offsetWidth", element); + + internal static async Task ElementGetTabIndex(this IJSRuntime js, ElementReference element) + => await js.InvokeAsync("BitButil.element.getTabIndex", element); + internal static async Task ElementSetTabIndex(this IJSRuntime js, ElementReference element, int value) + => await js.InvokeVoidAsync("BitButil.element.setTabIndex", element, value); } diff --git a/src/Butil/Bit.Butil/Publics/Element.cs b/src/Butil/Bit.Butil/Publics/Element.cs index da72e5282f..0a3cf2b4aa 100644 --- a/src/Butil/Bit.Butil/Publics/Element.cs +++ b/src/Butil/Bit.Butil/Publics/Element.cs @@ -272,4 +272,128 @@ public async Task GetScrollWidth(ElementReference element) /// public async Task GetTagName(ElementReference element) => await js.ElementGetScrollWidth(element); + + /// + /// The contentEditable property of the HTMLElement interface specifies whether or not the element is editable. + /// + public async Task GetContentEditable(ElementReference element) + => await js.ElementGetContentEditable(element); + /// + /// The contentEditable property of the HTMLElement interface specifies whether or not the element is editable. + /// + public async Task SetContentEditable(ElementReference element, ContentEditable value) + => await js.ElementSetContentEditable(element, value); + + /// + /// Returns a boolean value indicating whether or not the content of the element can be edited. + /// + public async Task IsContentEditable(ElementReference element) + => await js.ElementIsContentEditable(element); + + /// + /// The HTMLElement.dir property gets or sets the text writing directionality of the content of the current element. + /// + public async Task GetDir(ElementReference element) + => await js.ElementGetDir(element); + /// + /// The HTMLElement.dir property gets or sets the text writing directionality of the content of the current element. + /// + public async Task SetDir(ElementReference element, Dir value) + => await js.ElementSetDir(element, value); + + /// + /// The enterKeyHint property is an enumerated property defining what action label (or icon) + /// to present for the enter key on virtual keyboards. + /// + public async Task GetEnterKeyHint(ElementReference element) + => await js.ElementGetEnterKeyHint(element); + /// + /// The enterKeyHint property is an enumerated property defining what action label (or icon) + /// to present for the enter key on virtual keyboards. + /// + public async Task SetEnterKeyHint(ElementReference element, EnterKeyHint value) + => await js.ElementSetEnterKeyHint(element, value); + + /// + /// The HTMLElement property hidden reflects the value of the element's hidden attribute. + /// + public async Task GetHidden(ElementReference element) + => await js.ElementGetHidden(element); + /// + /// The HTMLElement property hidden reflects the value of the element's hidden attribute. + /// + public async Task SetHidden(ElementReference element, Hidden value) + => await js.ElementSetHidden(element, value); + + /// + /// The HTMLElement property inert reflects the value of the element's inert attribute. It is a boolean value that, when present, + /// makes the browser "ignore" user input events for the element, including focus events and events from assistive technologies. + /// + public async Task GetInert(ElementReference element) + => await js.ElementGetInert(element); + /// + /// The HTMLElement property inert reflects the value of the element's inert attribute. It is a boolean value that, when present, + /// makes the browser "ignore" user input events for the element, including focus events and events from assistive technologies. + /// + public async Task SetInert(ElementReference element, bool value) + => await js.ElementSetInert(element, value); + + /// + /// The innerText property of the HTMLElement interface represents the rendered text content of a node and its descendants. + /// + public async Task GetInnerText(ElementReference element) + => await js.ElementGetInnerText(element); + /// + /// The innerText property of the HTMLElement interface represents the rendered text content of a node and its descendants. + /// + public async Task SetInnerText(ElementReference element, string value) + => await js.ElementSetInnerText(element, value); + + /// + /// The HTMLElement property inputMode reflects the value of the element's inputmode attribute. + /// + public async Task GetInputMode(ElementReference element) + => await js.ElementGetInputMode(element); + /// + /// The HTMLElement property inputMode reflects the value of the element's inputmode attribute. + /// + public async Task SetInputMode(ElementReference element, InputMode value) + => await js.ElementSetInputMode(element, value); + + /// + /// The HTMLElement.offsetHeight read-only property returns the height of an element, including vertical padding and borders in px. + /// + public async Task GetOffsetHeight(ElementReference element) + => await js.ElementGetOffsetHeight(element); + + /// + /// The HTMLElement.offsetLeft read-only property returns the number of pixels that the upper left corner + /// of the current element is offset to the left within the HTMLElement.offsetParent node. + /// + public async Task GetOffsetLeft(ElementReference element) + => await js.ElementGetOffsetLeft(element); + + /// + /// The HTMLElement.offsetTop read-only property returns the distance from the outer border of the current element + /// (including its margin) to the top padding edge of the offsetParent, the closest positioned ancestor element. + /// + public async Task GetOffsetTop(ElementReference element) + => await js.ElementGetOffsetTop(element); + + /// + /// The layout width of an element in px. + /// + public async Task GetOffsetWidth(ElementReference element) + => await js.ElementGetOffsetWidth(element); + + /// + /// A number representing the position of the element in the tabbing order. + /// + public async Task GetTabIndex(ElementReference element) + => await js.ElementGetTabIndex(element); + /// + /// A number representing the position of the element in the tabbing order. + /// + public async Task SetTabIndex(ElementReference element, int value) + => await js.ElementSetTabIndex(element, value); } diff --git a/src/Butil/Bit.Butil/Publics/Element/ContentEditable.cs b/src/Butil/Bit.Butil/Publics/Element/ContentEditable.cs new file mode 100644 index 0000000000..700e07eecc --- /dev/null +++ b/src/Butil/Bit.Butil/Publics/Element/ContentEditable.cs @@ -0,0 +1,25 @@ +namespace Bit.Butil; + +public enum ContentEditable +{ + + /// + /// Indicates that the element inherits its parent's editable status. + /// + Inherit, + + /// + /// Indicates that the element is contenteditable. + /// + True, + + /// + /// Indicates that the element cannot be edited. + /// + False, + + /// + /// Indicates that the element's raw text is editable, but rich text formatting is disabled. + /// + PlainTextOnly +} diff --git a/src/Butil/Bit.Butil/Publics/Element/Dir.cs b/src/Butil/Bit.Butil/Publics/Element/Dir.cs new file mode 100644 index 0000000000..3c2363cc9b --- /dev/null +++ b/src/Butil/Bit.Butil/Publics/Element/Dir.cs @@ -0,0 +1,24 @@ +namespace Bit.Butil; + +public enum Dir +{ + /// + /// The dir value is not set. + /// + NotSet, + + /// + /// Left to right. + /// + Ltr, + + /// + /// Right to left. + /// + Rtl, + + /// + /// The direction of the element will be determined based on the contents of the element. + /// + Auto +} diff --git a/src/Butil/Bit.Butil/Publics/Element/EnterKeyHint.cs b/src/Butil/Bit.Butil/Publics/Element/EnterKeyHint.cs new file mode 100644 index 0000000000..43b4845cfc --- /dev/null +++ b/src/Butil/Bit.Butil/Publics/Element/EnterKeyHint.cs @@ -0,0 +1,44 @@ +namespace Bit.Butil; + +public enum EnterKeyHint +{ + /// + /// The enterKeyHint value is not set. + /// + NotSet, + + /// + /// Typically indicating inserting a new line. + /// + Enter, + + /// + /// Typically meaning there is nothing more to input and the input method editor (IME) will be closed. + /// + Done, + + /// + /// Typically meaning to take the user to the target of the text they typed. + /// + Go, + + /// + /// Typically taking the user to the next field that will accept text. + /// + Next, + + /// + /// Typically taking the user to the previous field that will accept text. + /// + Previous, + + /// + /// Typically taking the user to the results of searching for the text they have typed. + /// + Search, + + /// + /// Typically delivering the text to its target. + /// + Send +} diff --git a/src/Butil/Bit.Butil/Publics/Element/Hidden.cs b/src/Butil/Bit.Butil/Publics/Element/Hidden.cs new file mode 100644 index 0000000000..0cc7d14f92 --- /dev/null +++ b/src/Butil/Bit.Butil/Publics/Element/Hidden.cs @@ -0,0 +1,19 @@ +namespace Bit.Butil; + +public enum Hidden +{ + /// + /// The element is not hidden. This is the default value for the attribute. + /// + False, + + /// + /// The element is hidden. + /// + True, + + /// + /// The element is hidden until found, meaning that it is hidden but will be revealed if found through in page search or reached through fragment navigation. + /// + UntilFound +} diff --git a/src/Butil/Bit.Butil/Publics/Element/InputMode.cs b/src/Butil/Bit.Butil/Publics/Element/InputMode.cs new file mode 100644 index 0000000000..9143a68f53 --- /dev/null +++ b/src/Butil/Bit.Butil/Publics/Element/InputMode.cs @@ -0,0 +1,49 @@ +namespace Bit.Butil; + +public enum InputMode +{ + /// + /// The inputmode value is not set. + /// + NotSet, + + /// + /// Fractional numeric input keyboard that contains the digits and decimal separator for the user's locale (typically . or ,). + /// + Decimal, + + /// + /// A virtual keyboard optimized for entering email addresses. Typically includes the @character as well as other optimizations. + /// + Email, + + /// + /// No virtual keyboard. This is used when the page implements its own keyboard input control. + /// + None, + + /// + /// Numeric input keyboard that only requires the digits 0–9. Devices may or may not show a minus key. + /// + Numeric, + + /// + /// A virtual keyboard optimized for search input. For instance, the return/submit key may be labeled "Search". + /// + Search, + + /// + /// A telephone keypad input that includes the digits 0–9, the asterisk (*), and the pound (#) key. + /// + Tel, + + /// + /// Standard input keyboard for the user's current locale. + /// + Text, + + /// + /// A keypad optimized for entering URLs. This may have the / key more prominent, for example. + /// + Url, +} diff --git a/src/Butil/Bit.Butil/Scripts/element.ts b/src/Butil/Bit.Butil/Scripts/element.ts index b08c18965b..b739d9fe4d 100644 --- a/src/Butil/Bit.Butil/Scripts/element.ts +++ b/src/Butil/Bit.Butil/Scripts/element.ts @@ -2,93 +2,67 @@ var BitButil = BitButil || {}; (function (butil: any) { butil.element = { - blur, - getAttribute, - getAttributeNames, - getBoundingClientRect, - hasAttribute, - hasAttributes, - hasPointerCapture, - matches, - releasePointerCapture, - remove, - removeAttribute, - requestFullScreen, - requestPointerLock, + blur(element: HTMLElement) { element.blur() }, + getAttribute(element: HTMLElement, name: string) { return element.getAttribute(name) }, + getAttributeNames(element: HTMLElement) { return element.getAttributeNames() }, + getBoundingClientRect(element: HTMLElement) { return element.getBoundingClientRect() }, + hasAttribute(element: HTMLElement, name: string) { return element.hasAttribute(name) }, + hasAttributes(element: HTMLElement) { return element.hasAttributes() }, + hasPointerCapture(element: HTMLElement, pointerId: number) { return element.hasPointerCapture(pointerId) }, + matches(element: HTMLElement, selectors: string) { return element.matches(selectors) }, + releasePointerCapture(element: HTMLElement, pointerId: number) { element.releasePointerCapture(pointerId) }, + remove(element: HTMLElement) { element.remove() }, + removeAttribute(element: HTMLElement, name: string) { return element.removeAttribute(name) }, + requestFullScreen(element: HTMLElement, options?: FullscreenOptions) { return element.requestFullscreen(options) }, + requestPointerLock(element: HTMLElement) { return element.requestPointerLock() }, scroll, scrollBy, scrollIntoView, - setAttribute, - setPointerCapture, - toggleAttribute, - getAccessKey, setAccessKey, - getClassName, setClassName, - clientHeight, - clientLeft, - clientTop, - clientWidth, - getId, setId, - getInnerHTML, setInnerHTML, - getOuterHTML, setOuterHTML, - scrollHeight, - scrollLeft, - scrollTop, - scrollWidth, - tagName + setAttribute(element: HTMLElement, name: string, value: string) { return element.setAttribute(name, value) }, + setPointerCapture(element: HTMLElement, pointerId: number) { element.setPointerCapture(pointerId) }, + toggleAttribute(element: HTMLElement, name: string, force?: boolean) { return element.toggleAttribute(name, force) }, + getAccessKey(element: HTMLElement) { return element.accessKey }, + setAccessKey(element: HTMLElement, key: string) { element.accessKey = key }, + getClassName(element: HTMLElement) { return element.className }, + setClassName(element: HTMLElement, className: string) { element.className = className }, + clientHeight(element: HTMLElement) { return element.clientHeight }, + clientLeft(element: HTMLElement) { return element.clientLeft }, + clientTop(element: HTMLElement) { return element.clientTop }, + clientWidth(element: HTMLElement) { return element.clientWidth }, + getId(element: HTMLElement) { return element.id }, + setId(element: HTMLElement, id: string) { element.id = id }, + getInnerHTML(element: HTMLElement) { return element.innerHTML }, + setInnerHTML(element: HTMLElement, innerHTML: string) { element.innerHTML = innerHTML }, + getOuterHTML(element: HTMLElement) { return element.outerHTML }, + setOuterHTML(element: HTMLElement, outerHTML: string) { element.outerHTML = outerHTML }, + scrollHeight(element: HTMLElement) { return element.scrollHeight }, + scrollLeft(element: HTMLElement) { return element.scrollLeft }, + scrollTop(element: HTMLElement) { return element.scrollTop }, + scrollWidth(element: HTMLElement) { return element.scrollWidth }, + tagName(element: HTMLElement) { return element.tagName }, + getContentEditable(element: HTMLElement) { return element.contentEditable }, + setContentEditable(element: HTMLElement, value: string) { return element.contentEditable = value }, + isContentEditable(element: HTMLElement) { return element.isContentEditable }, + getDir(element: HTMLElement) { return element.dir }, + setDir(element: HTMLElement, value: string) { element.dir = value }, + getEnterKeyHint(element: HTMLElement) { return element.enterKeyHint }, + setEnterKeyHint(element: HTMLElement, value: string) { element.enterKeyHint = value }, + getHidden(element: HTMLElement) { return element.hidden }, + setHidden(element: HTMLElement, value: boolean) { element.hidden = value }, + getInert(element: HTMLElement) { return element.inert }, + setInert(element: HTMLElement, value: boolean) { element.inert = value }, + getInnerText(element: HTMLElement) { return element.innerText }, + setInnerText(element: HTMLElement, value: string) { element.innerText = value }, + getInputMode(element: HTMLElement) { return element.inputMode }, + setInputMode(element: HTMLElement, value: string) { element.inputMode = value }, + offsetHeight(element: HTMLElement) { return element.offsetHeight }, + offsetLeft(element: HTMLElement) { return element.offsetLeft }, + offsetTop(element: HTMLElement) { return element.offsetTop }, + offsetWidth(element: HTMLElement) { return element.offsetWidth }, + getTabIndex(element: HTMLElement) { return element.tabIndex }, + setTabIndex(element: HTMLElement, value: number) { element.tabIndex = value }, }; - function blur(element: HTMLElement) { - element.blur(); - } - - function getAttribute(element: HTMLElement, name: string) { - return element.getAttribute(name); - } - - function getAttributeNames(element: HTMLElement) { - return element.getAttributeNames(); - } - - function getBoundingClientRect(element: HTMLElement) { - return element.getBoundingClientRect(); - } - - function hasAttribute(element: HTMLElement, name: string) { - return element.hasAttribute(name); - } - - function hasAttributes(element: HTMLElement) { - return element.hasAttributes(); - } - - function hasPointerCapture(element: HTMLElement, pointerId: number) { - return element.hasPointerCapture(pointerId); - } - - function matches(element: HTMLElement, selectors: string) { - return element.matches(selectors); - } - - function releasePointerCapture(element: HTMLElement, pointerId: number) { - element.releasePointerCapture(pointerId); - } - - function remove(element: HTMLElement) { - element.remove(); - } - - function removeAttribute(element: HTMLElement, name: string) { - return element.removeAttribute(name); - } - - function requestFullScreen(element: HTMLElement, options?: FullscreenOptions) { - return element.requestFullscreen(options); - } - - function requestPointerLock(element: HTMLElement) { - return element.requestPointerLock(); - } - function scroll(element: HTMLElement, options?: ScrollToOptions, x?: number, y?: number) { if (options) { element.scroll(options); @@ -108,92 +82,4 @@ var BitButil = BitButil || {}; function scrollIntoView(element: HTMLElement, alignToTop?: boolean, options?: ScrollIntoViewOptions) { element.scrollIntoView(alignToTop ?? options); } - - function setAttribute(element: HTMLElement, name: string, value: string) { - return element.setAttribute(name, value); - } - - function setPointerCapture(element: HTMLElement, pointerId: number) { - element.setPointerCapture(pointerId); - } - - function toggleAttribute(element: HTMLElement, name: string, force?: boolean) { - return element.toggleAttribute(name, force); - } - - function getAccessKey(element: HTMLElement) { - return element.accessKey; - } - - function setAccessKey(element: HTMLElement, key: string) { - element.accessKey = key; - } - - function getClassName(element: HTMLElement) { - return element.className; - } - - function setClassName(element: HTMLElement, className: string) { - element.className = className; - } - - function clientHeight(element: HTMLElement) { - return element.clientHeight; - } - - function clientLeft(element: HTMLElement) { - return element.clientLeft; - } - - function clientTop(element: HTMLElement) { - return element.clientTop; - } - - function clientWidth(element: HTMLElement) { - return element.clientWidth; - } - - function getId(element: HTMLElement) { - return element.id; - } - - function setId(element: HTMLElement, id: string) { - element.id = id; - } - - function getInnerHTML(element: HTMLElement) { - return element.innerHTML; - } - - function setInnerHTML(element: HTMLElement, innerHTML: string) { - element.innerHTML = innerHTML; - } - - function getOuterHTML(element: HTMLElement) { - return element.outerHTML; - } - - function setOuterHTML(element: HTMLElement, outerHTML: string) { - element.outerHTML = outerHTML; - } - - function scrollHeight(element: HTMLElement) { - return element.scrollHeight; - } - - function scrollLeft(element: HTMLElement) { - return element.scrollLeft; - } - - function scrollTop(element: HTMLElement) { - return element.scrollTop; - } - - function scrollWidth(element: HTMLElement) { - return element.scrollWidth; - } - - function tagName(element: HTMLElement) { - return element.tagName; - } }(BitButil)); \ No newline at end of file diff --git a/src/Templates/BlazorEmpty/Bit.BlazorEmpty/BlazorEmpty.Client/BlazorEmpty.Client.csproj b/src/Templates/BlazorEmpty/Bit.BlazorEmpty/BlazorEmpty.Client/BlazorEmpty.Client.csproj index bdaa4d3a17..78d5e3bc48 100644 --- a/src/Templates/BlazorEmpty/Bit.BlazorEmpty/BlazorEmpty.Client/BlazorEmpty.Client.csproj +++ b/src/Templates/BlazorEmpty/Bit.BlazorEmpty/BlazorEmpty.Client/BlazorEmpty.Client.csproj @@ -1,4 +1,4 @@ - + @@ -15,14 +15,14 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Templates/BlazorEmpty/Bit.BlazorEmpty/BlazorEmpty/BlazorEmpty.csproj b/src/Templates/BlazorEmpty/Bit.BlazorEmpty/BlazorEmpty/BlazorEmpty.csproj index b23cb716ea..35f2c65efc 100644 --- a/src/Templates/BlazorEmpty/Bit.BlazorEmpty/BlazorEmpty/BlazorEmpty.csproj +++ b/src/Templates/BlazorEmpty/Bit.BlazorEmpty/BlazorEmpty/BlazorEmpty.csproj @@ -1,4 +1,4 @@ - + @@ -18,14 +18,14 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/.gitignore b/src/Templates/Boilerplate/Bit.Boilerplate/.gitignore index a4961f8b65..9a3e897c1d 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/.gitignore +++ b/src/Templates/Boilerplate/Bit.Boilerplate/.gitignore @@ -231,4 +231,4 @@ custom.aprof /src/Client/Boilerplate.Client.Core/wwwroot/scripts/app*.js -/src/Boilerplate.Server/BoilerplateDb.db* \ No newline at end of file +/src/Boilerplate.Server/*.db* \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/template.json b/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/template.json index 931dcc6871..f08c43d5f3 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/template.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/.template.config/template.json @@ -97,6 +97,12 @@ "description": "None" } ] + }, + "offlineDb": { + "displayName": "Offline db", + "type": "parameter", + "datatype": "bool", + "defaultValue": "false" } }, "postActions": [], @@ -121,7 +127,8 @@ "**/*.lock.json", "**/.git/**", "**/*.nuspec", - "**/Migrations/**" + "src/Boilerplate.Server/Data/Migrations/**", + "src/Boilerplate.Server/*.db*" ] }, { @@ -134,33 +141,44 @@ }, { "condition": "(sample != Admin)", - "exclude": [ "src/Shared/Dtos/Categories/**", - "src/Shared/Dtos/Dashboard/**", - "src/Shared/Dtos/Products/**", - "src/Boilerplate.Server/Controllers/Categories/**", - "src/Boilerplate.Server/Controllers/Products/**", - "src/Boilerplate.Server/Controllers/Dashboard/**", - "src/Boilerplate.Server/Data/Configurations/Category/**", - "src/Boilerplate.Server/Data/Configurations/Product/**", - "src/Boilerplate.Server/Mappers/CategoriesMapper.cs", - "src/Boilerplate.Server/Mappers/ProductsMapper.cs", - "src/Boilerplate.Server/Models/Categories/**", - "src/Boilerplate.Server/Models/Products/**", - "src/Client/Boilerplate.Client.Core/Controllers/Categories/**", - "src/Client/Boilerplate.Client.Core/Controllers/Products/**", - "src/Client/Boilerplate.Client.Core/Controllers/Dashboard/**", - "src/Client/Boilerplate.Client.Core/Components/Pages/Categories/**", - "src/Client/Boilerplate.Client.Core/Components/Pages/Dashboard/**", - "src/Client/Boilerplate.Client.Core/Components/Pages/Products/**"] + "exclude": [ + "src/Shared/Dtos/Categories/**", + "src/Shared/Dtos/Dashboard/**", + "src/Shared/Dtos/Products/**", + "src/Boilerplate.Server/Controllers/Categories/**", + "src/Boilerplate.Server/Controllers/Products/**", + "src/Boilerplate.Server/Controllers/Dashboard/**", + "src/Boilerplate.Server/Data/Configurations/Category/**", + "src/Boilerplate.Server/Data/Configurations/Product/**", + "src/Boilerplate.Server/Mappers/CategoriesMapper.cs", + "src/Boilerplate.Server/Mappers/ProductsMapper.cs", + "src/Boilerplate.Server/Models/Categories/**", + "src/Boilerplate.Server/Models/Products/**", + "src/Client/Boilerplate.Client.Core/Controllers/Categories/**", + "src/Client/Boilerplate.Client.Core/Controllers/Products/**", + "src/Client/Boilerplate.Client.Core/Controllers/Dashboard/**", + "src/Client/Boilerplate.Client.Core/Components/Pages/Categories/**", + "src/Client/Boilerplate.Client.Core/Components/Pages/Dashboard/**", + "src/Client/Boilerplate.Client.Core/Components/Pages/Products/**" + ] }, { "condition": "(sample != Todo)", - "exclude": [ "src/Shared/Dtos/Todo/**", - "src/Boilerplate.Server/Controllers/Todo/**", - "src/Boilerplate.Server/Mappers/TodoMapper.cs", - "src/Boilerplate.Server/Models/Todo/**", - "src/Client/Boilerplate.Client.Core/Controllers/Todo/**", - "src/Client/Boilerplate.Client.Core/Components/Pages/Todo/**"] + "exclude": [ + "src/Shared/Dtos/Todo/**", + "src/Boilerplate.Server/Controllers/Todo/**", + "src/Boilerplate.Server/Mappers/TodoMapper.cs", + "src/Boilerplate.Server/Models/Todo/**", + "src/Client/Boilerplate.Client.Core/Controllers/Todo/**", + "src/Client/Boilerplate.Client.Core/Components/Pages/Todo/**" + ] + }, + { + "condition": "(offlineDb != true)", + "exclude": [ + "src/Client/Boilerplate.Client.Core/Data/**", + "src/Client/Boilerplate.Client.Core/Components/Pages/Offline/**" + ] } ] } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/Boilerplate.Web.slnf b/src/Templates/Boilerplate/Bit.Boilerplate/Boilerplate.Web.slnf deleted file mode 100644 index aa4b869889..0000000000 --- a/src/Templates/Boilerplate/Bit.Boilerplate/Boilerplate.Web.slnf +++ /dev/null @@ -1,11 +0,0 @@ -{ - "solution": { - "path": "Boilerplate.sln", - "projects": [ - "src\\Boilerplate.Server\\Boilerplate.Server.csproj", - "src\\Boilerplate.Shared\\Boilerplate.Shared.csproj", - "src\\Client\\Boilerplate.Client.Core\\Boilerplate.Client.Core.csproj", - "src\\Client\\Boilerplate.Client.Web\\Boilerplate.Client.Web.csproj" - ] - } -} \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Server/Boilerplate.Server.csproj b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Server/Boilerplate.Server.csproj index bd47cf6ede..7cb12a4916 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Server/Boilerplate.Server.csproj +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Server/Boilerplate.Server.csproj @@ -6,11 +6,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -86,4 +86,13 @@ + + + linux-x64 + True + AC87AA5B-4B37-4E52-8468-2D5DF24AF256 + Linux + ..\.. + + diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Server/Program.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Server/Program.cs index 8bb0d4cb4d..7dfe0dffd1 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Server/Program.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Server/Program.cs @@ -3,17 +3,10 @@ builder.Configuration.AddClientConfigurations(); -if (BuildConfiguration.IsDebug()) +// The following line (using the * in the URL), allows the emulators and mobile devices to access the app using the host IP address. +if (BuildConfiguration.IsDebug() && OperatingSystem.IsWindows()) { - // The following line (using the * in the URL), allows the emulators and mobile devices to access the app using the host IP address. - if (OperatingSystem.IsWindows()) - { - builder.WebHost.UseUrls("https://localhost:5031", "http://localhost:5030", "https://*:5031", "http://*:5030"); - } - else - { - builder.WebHost.UseUrls("https://localhost:5031", "http://localhost:5030"); - } + builder.WebHost.UseUrls("https://localhost:5031", "http://localhost:5030", "https://*:5031", "http://*:5030"); } Boilerplate.Server.Startup.Services.Add(builder.Services, builder.Environment, builder.Configuration); diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Server/Properties/launchSettings.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Server/Properties/launchSettings.json index 5b265d0aa9..e37a38b24a 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Server/Properties/launchSettings.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Server/Properties/launchSettings.json @@ -1,26 +1,45 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "Boilerplate.Server-Swagger": { "commandName": "Project", - "dotnetRunMessages": true, "launchBrowser": true, - "applicationUrl": "http://localhost:5030;https://localhost:5031", "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5030;https://localhost:5031" }, - // This configuration allows debugging the Blazor Web Assembly "Boilerplate.Server-BlazorWebAssembly": { "commandName": "Project", - "dotnetRunMessages": true, "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "http://localhost:5030;https://localhost:5031", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "http://localhost:5030;https://localhost:5031" + }, + "WSL": { + "commandName": "WSL2", + "launchBrowser": true, + "launchUrl": "http://localhost:5030/swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_URLS": "http://localhost:5030" } + }, + "Docker": { + "commandName": "SdkContainer", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "environmentVariables": { + "ASPNETCORE_HTTP_PORTS": "5030" + }, + "publishAllPorts": true, + "useSSL": false, + "httpPort": 5030 } - } -} + }, + "$schema": "http://json.schemastore.org/launchsettings.json" +} \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Boilerplate.Shared.csproj b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Boilerplate.Shared.csproj index 0c5991e34d..471f90befb 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Boilerplate.Shared.csproj +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Boilerplate.Shared.csproj @@ -5,11 +5,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Resources/AppStrings.Designer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Resources/AppStrings.Designer.cs index 07e6022a48..bbe600dc54 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Resources/AppStrings.Designer.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Resources/AppStrings.Designer.cs @@ -307,8 +307,7 @@ public static string ConfirmNewPassword { /// /// Looks up a localized string similar to Request could not be processed because of conflict in the request. /// - public static string ConflictException - { + public static string ConflictException { get { return ResourceManager.GetString("ConflictException", resourceCulture); } @@ -791,6 +790,15 @@ public static string NotReceivedConfirmationEmailMessage { } } + /// + /// Looks up a localized string similar to Offline Edit profile. + /// + public static string OfflineEditProfileTitle { + get { + return ResourceManager.GetString("OfflineEditProfileTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to OR. /// diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Resources/AppStrings.fr.resx b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Resources/AppStrings.fr.resx index 5ceae01417..9822b8ac6d 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Resources/AppStrings.fr.resx +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Resources/AppStrings.fr.resx @@ -194,6 +194,9 @@ Editer le profil + + + Modifier le profil hors ligne Nom et prénom diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Resources/AppStrings.resx b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Resources/AppStrings.resx index f354364ea8..4e52c3820d 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Resources/AppStrings.resx +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Boilerplate.Shared/Resources/AppStrings.resx @@ -190,6 +190,11 @@ Edit profile + + + Offline Edit profile + + FullName diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Boilerplate.Client.Core.csproj b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Boilerplate.Client.Core.csproj index 6dbeadbc1c..fa97fb7bcb 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Boilerplate.Client.Core.csproj +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Boilerplate.Client.Core.csproj @@ -16,17 +16,18 @@ - - - + + + - + + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavMenu.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavMenu.razor.cs index 65b02cb295..69803322ea 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavMenu.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Layout/NavMenu.razor.cs @@ -68,6 +68,14 @@ protected override async Task OnInitAsync() IconName = BitIconName.EditContact, Url = "/edit-profile", }, + //#if (offlineDb == true) + new BitNavItem + { + Text = Localizer[nameof(AppStrings.OfflineEditProfileTitle)], + IconName = BitIconName.EditContact, + Url = "/offline-edit-profile", + }, + //#endif new BitNavItem { Text = Localizer[nameof(AppStrings.TermsTitle)], diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Dashboard/DashboardPage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Dashboard/DashboardPage.razor.cs index ab35e8c969..fa9300d3d3 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Dashboard/DashboardPage.razor.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Dashboard/DashboardPage.razor.cs @@ -15,7 +15,7 @@ protected async override Task OnInitAsync() { if (OperatingSystem.IsBrowser()) { - await lazyAssemblyLoader.LoadAssembliesAsync(["Newtonsoft.Json.wasm", "System.Private.Xml.wasm", "System.Data.Common.wasm"]); + await lazyAssemblyLoader.LoadAssembliesAsync(["Newtonsoft.Json.wasm"]); } } finally diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Offline/OfflineEditProfilePage.razor b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Offline/OfflineEditProfilePage.razor new file mode 100644 index 0000000000..e888036cd9 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Offline/OfflineEditProfilePage.razor @@ -0,0 +1,76 @@ +@page "/offline-edit-profile" + +@inherits AppComponentBase + +@Localizer[nameof(AppStrings.EditProfileTitle)] + +
+
+ @if (string.IsNullOrEmpty(editProfileMessage) is false) + { + + @editProfileMessage + + } + +

+ @Localizer[nameof(AppStrings.EditProfileTitle)] +

+ + @if (isLoading) + { +
+ +
+ } + else + { + + + +
+ + +
+ +
+ + +
+ +
+ + + + + +
+ + + @Localizer[nameof(AppStrings.Save)] + +
+ + } +
+
diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Offline/OfflineEditProfilePage.razor.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Offline/OfflineEditProfilePage.razor.cs new file mode 100644 index 0000000000..b263b8eed1 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Offline/OfflineEditProfilePage.razor.cs @@ -0,0 +1,80 @@ +using Boilerplate.Client.Core.Data; +using Boilerplate.Shared.Dtos.Identity; +using Microsoft.EntityFrameworkCore; + +namespace Boilerplate.Client.Core.Components.Pages.Offline; + +[Authorize] +public partial class OfflineEditProfilePage +{ + [AutoInject] IDbContextFactory dbContextFactory = default!; + + private bool isSaving; + private bool isLoading = true; + private string? editProfileMessage; + private BitMessageBarType editProfileMessageType; + private UserDto user = new(); + private readonly EditUserDto userToEdit = new(); + + protected async override Task OnAfterFirstRenderAsync() + { + await base.OnAfterFirstRenderAsync(); + + try + { + await LoadEditProfileData(); + } + finally + { + isLoading = false; + StateHasChanged(); + } + } + + private async Task LoadEditProfileData() + { + user = await GetCurrentUser() ?? new(); + + user.Patch(userToEdit); + } + + private async Task GetCurrentUser() + { + await using var dbContext = await dbContextFactory.CreateDbContextAsync(CurrentCancellationToken); + + // Only for the first time, we need to migrate the database + await dbContext.Database.MigrateAsync(CurrentCancellationToken); + + return await dbContext.Users.FirstAsync(CurrentCancellationToken); + } + + private async Task DoSave() + { + if (isSaving) return; + + isSaving = true; + editProfileMessage = null; + + try + { + userToEdit.Patch(user); + + await using var dbContext = await dbContextFactory.CreateDbContextAsync(CurrentCancellationToken); + dbContext.Users.Update(user); + await dbContext.SaveChangesAsync(CurrentCancellationToken); + + editProfileMessageType = BitMessageBarType.Success; + editProfileMessage = Localizer[nameof(AppStrings.ProfileUpdatedSuccessfullyMessage)]; + } + catch (KnownException e) + { + editProfileMessageType = BitMessageBarType.Error; + + editProfileMessage = e.Message; + } + finally + { + isSaving = false; + } + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Offline/OfflineEditProfilePage.razor.scss b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Offline/OfflineEditProfilePage.razor.scss new file mode 100644 index 0000000000..a0cdd1b8c9 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Components/Pages/Offline/OfflineEditProfilePage.razor.scss @@ -0,0 +1,139 @@ +@import '../../../Styles/abstracts/_functions.scss'; +@import '../../../Styles/abstracts/_media-queries.scss'; +@import '../../../Styles/abstracts/_bit-css-variables.scss'; + +.page-container { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + flex-flow: column nowrap; + padding: rem2(48px) rem2(16px); + + @include lt-lg { + padding: rem2(16px); + } +} + +.content-container { + width: 100%; + display: flex; + position: relative; + align-items: center; + max-width: rem2(608px); + border-radius: rem2(4px); + flex-flow: column nowrap; + padding: rem2(32px) rem2(14px); + background-color: $bit-color-background-primary; + + &.profile-panel { + box-shadow: $bit-box-shadow-callout; + } + + &.danger-panel { + border: 2px solid darkred; + } +} + +.loading-container { + width: 100%; + height: 100%; + display: flex; + position: absolute; + align-items: center; + justify-content: center; +} + +.form-message-bar { + top: 0; + left: 0; + position: absolute; + border-radius: rem2(4px) rem2(4px) 0 0; +} + +.page-title { + font-weight: 600; + font-size: rem2(28px); + line-height: rem2(44px); + margin-bottom: rem2(16px); +} + +.form-profile-container { + width: 100%; + display: flex; + align-items: center; + max-width: rem2(340px); + flex-flow: row nowrap; + margin-bottom: rem2(17px); + justify-content: flex-start; + + @include lt-xl { + max-width: rem2(300px); + margin-bottom: rem2(13px); + } + + @include md { + margin-bottom: rem2(21px); + } + + @include sm { + max-width: 100%; + margin-bottom: rem2(13px); + } +} + +.form-input-container { + width: 100%; + display: flex; + max-width: rem2(340px); + flex-flow: column nowrap; + margin-bottom: rem2(17px); +} + +.form-input-error { + font-size: rem2(12px); + line-height: rem2(16px); + color: $bit-color-state-error; +} + +.form-choice-container { + width: 100%; + max-width: rem2(340px); + margin-bottom: rem2(19px); + + @include lt-xl { + max-width: rem2(300px); + margin-bottom: rem2(17px); + } + + @include md { + margin-bottom: rem2(25px); + } + + @include sm { + max-width: 100%; + } +} + +::deep .edit-profile-dtp { + .bit-dtp-wrapper { + z-index: 5; + } + + .bit-dtp-overlay { + z-index: 4; + } + + .bit-dtp-callout { + z-index: 6; + } +} + +::deep .edit-profile-form { + width: 100%; + display: flex; + align-items: center; + flex-flow: column nowrap; + justify-content: flex-start; +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20231213110821_InitialMigration.Designer.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20231213110821_InitialMigration.Designer.cs new file mode 100644 index 0000000000..757f1ce6b0 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20231213110821_InitialMigration.Designer.cs @@ -0,0 +1,67 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Boilerplate.Client.Core.Data.Migrations; + +[DbContext(typeof(OfflineDbContext))] +[Migration("20231213110821_InitialMigration")] +partial class InitialMigration +{ + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Boilerplate.Shared.Dtos.Identity.UserDto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BirthDate") + .HasColumnType("INTEGER"); + + b.Property("Email") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Gender") + .HasColumnType("INTEGER"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProfileImageName") + .HasColumnType("TEXT"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + + b.HasData( + new + { + Id = 1, + Email = "test@bitplatform.dev", + FullName = "Boilerplate test account", + Password = "123456", + UserName = "test@bitplatform.dev" + }); + }); +#pragma warning restore 612, 618 + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20231213110821_InitialMigration.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20231213110821_InitialMigration.cs new file mode 100644 index 0000000000..72d33c4116 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/20231213110821_InitialMigration.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Boilerplate.Client.Core.Data.Migrations; + +/// +public partial class InitialMigration : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserName = table.Column(type: "TEXT", nullable: false), + Email = table.Column(type: "TEXT", nullable: false), + Password = table.Column(type: "TEXT", nullable: false), + FullName = table.Column(type: "TEXT", nullable: false), + Gender = table.Column(type: "INTEGER", nullable: true), + BirthDate = table.Column(type: "INTEGER", nullable: true), + ProfileImageName = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.InsertData( + table: "Users", + columns: new[] { "Id", "BirthDate", "Email", "FullName", "Gender", "Password", "ProfileImageName", "UserName" }, + values: new object[] { 1, null, "test@bitplatform.dev", "Boilerplate test account", null, "123456", null, "test@bitplatform.dev" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Users"); + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/ClientDbContextModelSnapshot.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/ClientDbContextModelSnapshot.cs new file mode 100644 index 0000000000..27ad594631 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/Migrations/ClientDbContextModelSnapshot.cs @@ -0,0 +1,64 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; + +#nullable disable + +namespace Boilerplate.Client.Core.Data.Migrations; + +[DbContext(typeof(OfflineDbContext))] +partial class OfflineDbContextModelSnapshot : ModelSnapshot +{ + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("Boilerplate.Shared.Dtos.Identity.UserDto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("BirthDate") + .HasColumnType("INTEGER"); + + b.Property("Email") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("FullName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Gender") + .HasColumnType("INTEGER"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ProfileImageName") + .HasColumnType("TEXT"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + + b.HasData( + new + { + Id = 1, + Email = "test@bitplatform.dev", + FullName = "Boilerplate test account", + Password = "123456", + UserName = "test@bitplatform.dev" + }); + }); +#pragma warning restore 612, 618 + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/OfflineDbContext.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/OfflineDbContext.cs new file mode 100644 index 0000000000..98c6ed9926 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/OfflineDbContext.cs @@ -0,0 +1,38 @@ +using Boilerplate.Shared.Dtos.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Boilerplate.Client.Core.Data; + +public class OfflineDbContext(DbContextOptions options) : DbContext(options) +{ + public virtual DbSet Users { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasData([new() + { + Id = 1, + UserName= "test@bitplatform.dev", + Email = "test@bitplatform.dev", + Password = "123456", + FullName = "Boilerplate test account" + }]); + + base.OnModelCreating(modelBuilder); + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlite("Data Source=Boilerplate-ClientDb.db"); + + base.OnConfiguring(optionsBuilder); + } + + protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) + { + configurationBuilder.Properties().HaveConversion(); + configurationBuilder.Properties().HaveConversion(); + } +} diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/README.md b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/README.md new file mode 100644 index 0000000000..f91cb033e0 --- /dev/null +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Data/README.md @@ -0,0 +1,32 @@ +## bit entity framework core sqlite (bit Besql) + +How to use `Bit.Besql`: + +The usage of `Bit.Besql` is exactly the same as the regular usage of `Microsoft.EntityFrameworkCore.Sqlite` with [IDbContextFactory](https://learn.microsoft.com/en-us/aspnet/core/blazor/blazor-ef-core?view=aspnetcore-8.0#new-dbcontext-instances). + +To get start, simply install `Bit.Besql` and use `services.AddBesqlDbContextFactory` instead of `services.AddDbContextFactory`. + +Note: Don't use `IDbContextFactory` in `OnInitialized` because it relies on `IJSRuntime`. Use `OnAfterRender` instead. + +In order to download sqlite db file from browser cache storage in blazor WebAssembly run the followings in browser console: +```js +const cache = await caches.open('Bit-Besql'); +const resp = await cache.match('/data/cache/Boilerplate-ClientDb.db'); +const blob = await resp.blob(); +URL.createObjectURL(blob); +``` + +**Migration** + +Set `Server` as the Startup Project in solution explorer and set `Client.Core` it as the Default Project in Package Manager Console and run the following commands: +```powershell +Add-Migration InitialMigration -OutputDir Data\Migrations -Context OfflineDbContext +``` +Or open a terminal in your Server project directory and run followings: +```bash +dotnet ef migrations add InitialMigration --context OfflineDbContext --output-dir Data/Migrations --project ../Client/Boilerplate.Client.Core/Boilerplate.Client.Core.csproj +``` + +*Note*: If you encounter any problem in running these commands, first make sure that the solution builds successfully. + +*Note*: You may not run `Update-Database` command, because client app should programmatically create database and tables on every device that runs the app using `DbContext.Database.MigrateAsync()` code. \ No newline at end of file diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IServiceCollectionExtensions.cs b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IServiceCollectionExtensions.cs index 901069e04d..f9b72dabe5 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IServiceCollectionExtensions.cs +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/Extensions/IServiceCollectionExtensions.cs @@ -1,5 +1,7 @@ -//-:cnd:noEmit - +//+:cnd:noEmit +//#if (offlineDb == true) +using Boilerplate.Client.Core.Data; +//#endif using Boilerplate.Client.Core.Services.HttpMessageHandlers; using Microsoft.AspNetCore.Components.WebAssembly.Services; @@ -35,6 +37,9 @@ public static IServiceCollection AddClientSharedServices(this IServiceCollection services.AddBitBlazorUIServices(); services.AddSharedServices(); + //#if (offlineDb == true) + services.AddBesqlDbContextFactory(); + //#endif return services; } diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/compilerconfig.json b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/compilerconfig.json index 1c3c3d9369..d30ba833c8 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/compilerconfig.json +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Core/compilerconfig.json @@ -120,6 +120,14 @@ "minify": { "enabled": false }, "options": { "sourceMap": false } }, + //#if (offlineDb == true) + { + "outputFile": "Components/Pages/Offline/OfflineEditProfilePage.razor.css", + "inputFile": "Components/Pages/Offline/OfflineEditProfilePage.razor.scss", + "minify": { "enabled": false }, + "options": { "sourceMap": false } + }, + //#endif //#if (sample == "Todo") { "outputFile": "Components/Pages/Todo/TodoPage.razor.css", diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Boilerplate.Client.Maui.csproj b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Boilerplate.Client.Maui.csproj index ed511f1dc2..d64faac8ef 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Boilerplate.Client.Maui.csproj +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Maui/Boilerplate.Client.Maui.csproj @@ -84,14 +84,14 @@
- - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Boilerplate.Client.Web.csproj b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Boilerplate.Client.Web.csproj index 9297c8acf2..b62ef7bef3 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Boilerplate.Client.Web.csproj +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/Boilerplate.Client.Web.csproj @@ -29,12 +29,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -52,8 +52,6 @@ - - diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/service-worker.published.js b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/service-worker.published.js index 7ebe7b4d17..de687a19ee 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/service-worker.published.js +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Client/Boilerplate.Client.Web/wwwroot/service-worker.published.js @@ -1,4 +1,4 @@ -// bit version: 8.3.0 +// bit version: 8.4.0 // https://github.com/bitfoundation/bitplatform/tree/develop/src/Bswup self.assetsInclude = []; diff --git a/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Build.props b/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Build.props index e0ee29c9b4..c1361c6cba 100644 --- a/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Build.props +++ b/src/Templates/Boilerplate/Bit.Boilerplate/src/Directory.Build.props @@ -1,4 +1,4 @@ - + diff --git a/src/Websites/Careers/src/Bit.Websites.Careers.Client/Bit.Websites.Careers.Client.csproj b/src/Websites/Careers/src/Bit.Websites.Careers.Client/Bit.Websites.Careers.Client.csproj index 062081947f..1888baa4a7 100644 --- a/src/Websites/Careers/src/Bit.Websites.Careers.Client/Bit.Websites.Careers.Client.csproj +++ b/src/Websites/Careers/src/Bit.Websites.Careers.Client/Bit.Websites.Careers.Client.csproj @@ -24,14 +24,14 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Websites/Careers/src/Bit.Websites.Careers.Server/Bit.Websites.Careers.Server.csproj b/src/Websites/Careers/src/Bit.Websites.Careers.Server/Bit.Websites.Careers.Server.csproj index f6aaddea3d..de824762a7 100644 --- a/src/Websites/Careers/src/Bit.Websites.Careers.Server/Bit.Websites.Careers.Server.csproj +++ b/src/Websites/Careers/src/Bit.Websites.Careers.Server/Bit.Websites.Careers.Server.csproj @@ -9,11 +9,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Websites/Careers/src/Bit.Websites.Careers.Shared/Bit.Websites.Careers.Shared.csproj b/src/Websites/Careers/src/Bit.Websites.Careers.Shared/Bit.Websites.Careers.Shared.csproj index 074827a5f9..62f74ae21a 100644 --- a/src/Websites/Careers/src/Bit.Websites.Careers.Shared/Bit.Websites.Careers.Shared.csproj +++ b/src/Websites/Careers/src/Bit.Websites.Careers.Shared/Bit.Websites.Careers.Shared.csproj @@ -6,11 +6,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Websites/Careers/src/Directory.Build.props b/src/Websites/Careers/src/Directory.Build.props index 14134005d2..d6a99b68b0 100644 --- a/src/Websites/Careers/src/Directory.Build.props +++ b/src/Websites/Careers/src/Directory.Build.props @@ -1,4 +1,4 @@ - + 12.0 diff --git a/src/Websites/Platform/src/Bit.Websites.Platform.Client/Bit.Websites.Platform.Client.csproj b/src/Websites/Platform/src/Bit.Websites.Platform.Client/Bit.Websites.Platform.Client.csproj index a43fc4cec3..3b750f7c97 100644 --- a/src/Websites/Platform/src/Bit.Websites.Platform.Client/Bit.Websites.Platform.Client.csproj +++ b/src/Websites/Platform/src/Bit.Websites.Platform.Client/Bit.Websites.Platform.Client.csproj @@ -24,14 +24,14 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Websites/Platform/src/Bit.Websites.Platform.Client/Pages/Templates/CreateProjectPage.razor b/src/Websites/Platform/src/Bit.Websites.Platform.Client/Pages/Templates/CreateProjectPage.razor index 690dc02e68..d63b852569 100644 --- a/src/Websites/Platform/src/Bit.Websites.Platform.Client/Pages/Templates/CreateProjectPage.razor +++ b/src/Websites/Platform/src/Bit.Websites.Platform.Client/Pages/Templates/CreateProjectPage.razor @@ -32,18 +32,22 @@ dotnet new install Bit.Boilerplate
  • --database default to SqlServer among following options: SqlServer Sqlite, Other
  • --pipeline default to GitHub among the following options: GitHub, Azure (AzureDevOps pipelines), Other
  • --sample default to None among the following options: Admin, Todo, None
  • +
  • --offlineDb
  • Examples:
    -1- Create a cross-platform AdminPanel app for mobile, desktop, and web with Azure DevOps pipelines and a SQLite database.
    +1- Create a cross-platform AdminPanel app for mobile, desktop and web with Azure DevOps pipelines and a SQLite database.
     dotnet new bit-bp --name MyAdminPanel --database SqlServer --pipeline Azure --sample Admin
     
    -2- Create a cross-platform Todo app for mobile, desktop, and web with GitHub Actions and a SQLite database.
    +2- Create a cross-platform Todo app for mobile, desktop and web with GitHub Actions and a SQLite database.
     dotnet new bit-bp --name MyTodoApp --database Sqlite --pipeline GitHub --sample Todo
     
    -3- Create a mobile, desktop, and web app without sample pages, integrating GitHub Actions and a SQL Server database.
    -dotnet new bit-bp --name MyCompany.MyApp --database SqlServer --pipeline GitHub --sample None
    +3- Create a mobile, desktop and web app without sample pages, integrating GitHub Actions and a SQL Server database. +dotnet new bit-bp --name MyCompany.MyApp --database SqlServer --pipeline GitHub --sample None + +4- Create an offline capable mobile, desktop and web app. +dotnet new bit-bp --name MyCompany.MyApp --database SqlServer --pipeline GitHub --sample None --offlineDb
    Note: We recommend to create the project in a path that is not too long without space character in the folder names.
    diff --git a/src/Websites/Platform/src/Bit.Websites.Platform.Client/Pages/Templates/DatabasePage.razor b/src/Websites/Platform/src/Bit.Websites.Platform.Client/Pages/Templates/DatabasePage.razor index f58b6dda80..818ee40931 100644 --- a/src/Websites/Platform/src/Bit.Websites.Platform.Client/Pages/Templates/DatabasePage.razor +++ b/src/Websites/Platform/src/Bit.Websites.Platform.Client/Pages/Templates/DatabasePage.razor @@ -32,16 +32,16 @@
    Set Server as the Startup Project in solution explorer and also set it as the Default Project in Package Manager Console and run the following commands:
    -
    Add-Migration InitialMigration
    -
    Update-Database
    +
    Add-Migration InitialMigration -OutputDir Data\Migrations -Context AppDbContext
    +
    Update-Database -Context AppDbContext

    Note: If you encounter any problem in running these commands, first make sure that the solution builds successfully.

    Or open a terminal in your Server project directory and run followings:
    -
    dotnet ef migrations add InitialMigration
    -
    dotnet ef database update
    +
    dotnet ef migrations add InitialMigration --context AppDbContext --output-dir Data/Migrations
    +
    dotnet ef database update --context AppDbContext
    diff --git a/src/Websites/Platform/src/Bit.Websites.Platform.Server/Bit.Websites.Platform.Server.csproj b/src/Websites/Platform/src/Bit.Websites.Platform.Server/Bit.Websites.Platform.Server.csproj index a7ecb99d2d..262ce37947 100644 --- a/src/Websites/Platform/src/Bit.Websites.Platform.Server/Bit.Websites.Platform.Server.csproj +++ b/src/Websites/Platform/src/Bit.Websites.Platform.Server/Bit.Websites.Platform.Server.csproj @@ -9,11 +9,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Websites/Platform/src/Bit.Websites.Platform.Shared/Bit.Websites.Platform.Shared.csproj b/src/Websites/Platform/src/Bit.Websites.Platform.Shared/Bit.Websites.Platform.Shared.csproj index 074827a5f9..62f74ae21a 100644 --- a/src/Websites/Platform/src/Bit.Websites.Platform.Shared/Bit.Websites.Platform.Shared.csproj +++ b/src/Websites/Platform/src/Bit.Websites.Platform.Shared/Bit.Websites.Platform.Shared.csproj @@ -6,11 +6,11 @@
    - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Websites/Platform/src/Directory.Build.props b/src/Websites/Platform/src/Directory.Build.props index aa3e727cc7..a2836513cb 100644 --- a/src/Websites/Platform/src/Directory.Build.props +++ b/src/Websites/Platform/src/Directory.Build.props @@ -1,4 +1,4 @@ - + 12.0 diff --git a/src/Websites/Sales/src/Bit.Websites.Sales.Client/Bit.Websites.Sales.Client.csproj b/src/Websites/Sales/src/Bit.Websites.Sales.Client/Bit.Websites.Sales.Client.csproj index 478becb49b..5b6b9eb87b 100644 --- a/src/Websites/Sales/src/Bit.Websites.Sales.Client/Bit.Websites.Sales.Client.csproj +++ b/src/Websites/Sales/src/Bit.Websites.Sales.Client/Bit.Websites.Sales.Client.csproj @@ -24,14 +24,14 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Websites/Sales/src/Bit.Websites.Sales.Server/Bit.Websites.Sales.Server.csproj b/src/Websites/Sales/src/Bit.Websites.Sales.Server/Bit.Websites.Sales.Server.csproj index 30f123308c..2a851f926f 100644 --- a/src/Websites/Sales/src/Bit.Websites.Sales.Server/Bit.Websites.Sales.Server.csproj +++ b/src/Websites/Sales/src/Bit.Websites.Sales.Server/Bit.Websites.Sales.Server.csproj @@ -9,11 +9,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Websites/Sales/src/Bit.Websites.Sales.Shared/Bit.Websites.Sales.Shared.csproj b/src/Websites/Sales/src/Bit.Websites.Sales.Shared/Bit.Websites.Sales.Shared.csproj index 074827a5f9..62f74ae21a 100644 --- a/src/Websites/Sales/src/Bit.Websites.Sales.Shared/Bit.Websites.Sales.Shared.csproj +++ b/src/Websites/Sales/src/Bit.Websites.Sales.Shared/Bit.Websites.Sales.Shared.csproj @@ -6,11 +6,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Websites/Sales/src/Directory.Build.props b/src/Websites/Sales/src/Directory.Build.props index 4bf067a997..a72b23fa53 100644 --- a/src/Websites/Sales/src/Directory.Build.props +++ b/src/Websites/Sales/src/Directory.Build.props @@ -1,4 +1,4 @@ - + 12.0